Skip to content

feat(codex): add bound chat plan controls#88446

Closed
SYU8384 wants to merge 45 commits into
openclaw:mainfrom
SYU8384:feat/codex-plan-controls
Closed

feat(codex): add bound chat plan controls#88446
SYU8384 wants to merge 45 commits into
openclaw:mainfrom
SYU8384:feat/codex-plan-controls

Conversation

@SYU8384

@SYU8384 SYU8384 commented May 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds native /codex plan controls for bound Codex conversations, including persisted plan-mode preference on app-server thread bindings.
  • Adds mode-specific /codex think plan|execute ... defaults so planning and execution turns can use different reasoning effort settings.
  • Adds chat-native plan approval controls for bound Codex conversations, including approve, approve with clean context, and stay in plan mode buttons.
  • Adds optional /codex live progress delivery for bound Codex turns and routes single-choice Codex user-input prompts back into chat buttons.
  • Routes typed Codex question replies, including Other answers, through the bound route/session/thread so Discord and Telegram do not treat them as unrelated chat turns.
  • Keeps Discord partial multi-question answers as follow-up prompts instead of losing pending input state.
  • Adds deterministic Codex app-server fixture proof plus QA-lab Telegram/Discord scenario coverage for typed own-answer replies.
  • Documents the full bound Codex plan-control workflow in the Codex harness docs.

Latest branch state

  • PR branch: feat/codex-plan-controls
  • Current head: 887d0f01fd fix(codex): scope typed replies to route thread
  • The latest commit was cherry-picked from 1b3103a293 onto the PR branch.
  • GitHub currently reports this draft PR as conflicting with main; this body update records the current proof and remaining live-QA gap, but does not resolve the merge conflict.

Verification

  • node scripts/run-vitest.mjs extensions/codex/src/conversation-chat-controls.test.ts extensions/codex/src/conversation-turn-collector.test.ts extensions/codex/src/conversation-control.test.ts extensions/codex/src/app-server/session-binding.test.ts extensions/codex/src/app-server/config.test.ts extensions/codex/src/app-server/run-attempt.test.ts extensions/codex/src/app-server/thread-lifecycle.test.ts extensions/codex/src/conversation-binding.test.ts extensions/codex/src/commands.test.ts
  • node scripts/run-tsgo.mjs -p tsconfig.extensions.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions.tsbuildinfo
  • node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo
  • pnpm exec oxlint --tsconfig config/tsconfig/oxlint.extensions.json ...touched Codex TS files...
  • pnpm format:docs:check docs/plugins/codex-harness.md docs/plugins/codex-harness-reference.md
  • git diff --check
  • pnpm build
  • pnpm test extensions/codex/index.test.ts extensions/codex/src/conversation-chat-controls.test.ts extensions/codex/src/app-server/user-input-bridge.test.ts -- --reporter=verbose — 43 passed on 887d0f01fd.
  • pnpm test extensions/discord/src/monitor/monitor.test.ts -- --reporter=verbose — 28 passed on 887d0f01fd.
  • node --test scripts/e2e/codex-user-input-fixture-app-server.test.mjs — passed on 887d0f01fd.
  • pnpm test extensions/qa-lab/src/live-transports/discord/discord-live.runtime.test.ts extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime.test.ts -- --reporter=verbose — 70 passed on 887d0f01fd.
  • git show --check --stat HEAD — clean on 887d0f01fd.
  • Autoreview on HEAD for the cherry-picked typed-reply/thread-scope fix reported no accepted/actionable findings.

Live QA still needed

Attempted upstream workflow dispatch for the live Telegram/Discord typed Other scenarios was blocked by repository permissions:

gh workflow run "QA-Lab - All Lanes" \
  --repo openclaw/openclaw \
  --ref main \
  -f ref=feat/codex-plan-controls \
  -f scenario=telegram-codex-user-input-other \
  -f discord_scenario=discord-codex-user-input-other \
  -f matrix_profile=fast

Observed blocker: HTTP 403: Must have admin rights to Repository. The fork has admin access but does not have the required live-QA secrets, so it cannot produce equivalent real live proof for these scenarios.

Real behavior proof

Behavior addressed: Bound Codex chats can toggle native plan mode, store mode-specific plan/execute thinking defaults, show live progress, render plan approval/question buttons, execute approved plans either in the current thread or a newly rebound clean Codex thread, and route typed own-answer replies to the correct pending Codex input context instead of starting a separate chat turn.
Real environment tested: Local OpenClaw Codex worktree on branch feat/codex-plan-controls at 887d0f01fd, plus the Codex app-server fixture and QA-lab Telegram/Discord runtime scenario tests.
Exact steps or command run after this patch: Focused Vitest, tsgo extension prod/test configs, targeted oxlint, docs formatting check, git diff --check, pnpm build, the Codex user-input fixture E2E, Discord monitor tests, Telegram/Discord QA-lab runtime tests, git show --check --stat HEAD, and autoreview as listed above.
Evidence after fix: Focused Vitest and fixture tests passed; production and test tsgo commands exited successfully; targeted oxlint reported no errors; docs formatting and diff whitespace checks passed; pnpm build completed successfully; autoreview reported no accepted/actionable findings.
Observed result after fix: Plan-control state, command handlers, conversation binding, app-server binding persistence, run-attempt/thread-lifecycle integration, live progress controls, button answers, and typed own-answer replies all pass focused automated coverage. Discord partial answers remain attached to the active prompt flow, and typed replies are scoped to the active route thread.
What was not tested: A live Telegram/Discord roundtrip against a real Codex app-server account was not run for this draft PR. The upstream live-QA dispatch requires maintainer/admin workflow permission or a maintainer-applied proof: override if the local fixture/runtime proof is accepted for draft review.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation extensions: codex size: XL triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 30, 2026
@SYU8384 SYU8384 changed the title feat(codex): add chat plan approval controls feat(codex): add bound chat plan controls May 30, 2026
@clawsweeper

clawsweeper Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 5, 2026, 2:24 AM ET / 06:24 UTC.

Summary
Review failed before ClawSweeper could summarize the requested change.

PR surface: Source +4075, Tests +6444, Docs +50, Other +162. Total +10731 across 76 files.

Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path.

Review metrics: none identified.

Merge readiness
Overall: 🌊 off-meta tidepool
Proof: 🌊 off-meta tidepool
Patch quality: 🌊 off-meta tidepool
Result: rating does not apply to this item.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] No close action taken because the review did not complete.

Maintainer options:

  1. Decide the mitigation before merge
    Retry the Codex review after fixing the execution failure.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] Review did not complete, so no work-lane recommendation was made.
Review details

Best possible solution:

Retry the Codex review after fixing the execution failure.

Do we have a high-confidence way to reproduce the issue?

Unclear. The review failed before ClawSweeper could establish a reproduction path.

Is this the best way to solve the issue?

Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction.

AGENTS.md: unclear because the file could not be read completely.

Codex review notes: model gpt-5.5, reasoning high; reviewed against d7759c6a35ac.

Label changes

Label changes:

  • remove P2: Current review triage priority is none.
  • remove merge-risk: 🚨 compatibility: Current PR review selected no merge-risk labels.
  • remove merge-risk: 🚨 auth-provider: Current PR review selected no merge-risk labels.
  • remove merge-risk: 🚨 message-delivery: Current PR review selected no merge-risk labels.

Label justifications:

  • rating: 🌊 off-meta tidepool: Overall readiness is 🌊 off-meta tidepool; proof is 🌊 off-meta tidepool and patch quality is 🌊 off-meta tidepool.
Evidence reviewed

PR surface:

Source +4075, Tests +6444, Docs +50, Other +162. Total +10731 across 76 files.

View PR surface stats
Area Files Added Removed Net
Source 46 4309 234 +4075
Tests 27 6483 39 +6444
Docs 2 50 0 +50
Config 0 0 0 0
Generated 0 0 0 0
Other 1 162 0 +162
Total 76 11004 273 +10731

What I checked:

  • failure reason: timeout.
  • codex failure detail: Codex review failed for this PR: spawnSync codex ETIMEDOUT.
  • codex stdout: Per-item Codex failure; continuing with the rest of the shard.

Likely related people:

  • unknown: Codex failed before it could trace repository history. (role: review did not complete; confidence: low)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels May 30, 2026
@SYU8384
SYU8384 force-pushed the feat/codex-plan-controls branch 2 times, most recently from 5bb6111 to 887d0f0 Compare June 3, 2026 17:35
@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord channel: slack Channel integration: slack channel: telegram Channel integration: telegram app: web-ui App: web-ui scripts Repository scripts agents Agent runtime and tooling extensions: qa-lab labels Jun 3, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. and removed merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 3, 2026
SYU8384 added 14 commits June 4, 2026 16:20
…ion freeform

- Telegram/Discord/Slack interactive handlers now skip the reply call
  when result.message is empty, so the sequential partial-click (which
  intentionally sends no acknowledgment because the next question is
  posted as a new reply) does not send an empty message to the chat API.
  disableComponents / clearButtons / editMessage still run, so the used
  row is correctly locked before Q2 arrives.

- The freeform path now matches sequential pending entries by the
  currently-shown question's isOther flag instead of falling through to
  the legacy all-questions merge rule. Upstream Codex normalizes
  request_user_input questions to isOther=true and the prompt tells users
  they may reply with their own answer, so a user typing a custom answer
  for Q1 of a 2-3 question sequential prompt now correctly advances to
  Q2 instead of being rejected as 'matched: false'.

(cherry picked from commit 8b043d18abffb4ec4be3ba158b92e8ec8ab0615a)
…mo overview widgets

Two follow-ups to the codex plan controls PR (openclaw#88446), both surfaced
by autoreview on the rebased feat/codex-plan-controls branch:

(1) buildCommandInboundEvent / buildCommandInboundContext now thread
    the original conversation target through the synthetic inbound
    event/ctx so the follow-up turn's progress and any Codex
    request_user_input prompts it raises are deliverable to the chat
    that approved the plan. Previously the synthetic event dropped
    metadata.to / conversationId / parentConversationId and the
    synthetic ctx dropped pluginBinding, so the progress sender
    silently returned without sending and any request_user_input
    prompt would have waited for the 10-minute timeout before
    timing out. The two call sites in approveCurrentContextPlan and
    approveConversationPlanWithCleanContext read the current binding
    via ctx.getCurrentConversationBinding() and pass the routing
    fields from ctx (to, threadParentId, sessionId). Adds a unit
    test asserting the synthetic event + ctx carry the three fields.

(2) ui/src/ui/views/overview.ts no longer renders the
    openclaw-demo-button and openclaw-demo-status-widget placeholder
    elements. These were introduced by fix: preserve codex telegram
    plan context on the production overview route and shipped to
    users as product UI noise unrelated to the Codex plan controls
    change. The two element imports + the two render usages are
    removed, and ui/src/ui/views/overview-render.test.ts (which only
    asserted the demo widgets' presence) is deleted. The standalone
    custom element source files in ui/src/ui/components/ are kept for
    parity with the rest of the custom-element set but are no longer
    referenced from the production overview route.
…tParams

The previous patch narrowed buildTurnStartParams.collaborationMode to
the Codex wire object (CodexTurnCollaborationMode), but local callers
and tests still pass stored string modes (e.g. 'plan', 'default').
Build failed TS at extensions/codex/src/app-server/run-attempt.test.ts
when the test passes collaborationMode: 'plan' to buildTurnStartParams.

Match the same fix applied to buildTurnCollaborationMode earlier in
this rebase: accept CodexTurnCollaborationMode | string at the
outer option, and let buildTurnCollaborationMode normalize the string
into the wire object.
…ad resume

The thread-resume writeCodexAppServerBinding call only carried
forward the original binding's collaborationMode and legacy
reasoningEffort. The new chat plan controls added two more
persisted per-binding preferences (reasoningEffortDefaults for
/codex think and liveProgress for /codex live) that are not part
of the resume's overridden runtime fingerprints, so a later
app-server resume on the same session silently dropped them and
the user had to re-set /codex think and /codex live after every
lifecycle operation.

Include both fields in the resumed binding write so per-binding
preferences survive across resume / reconnect operations.
The progress-reply path calls adapter.sendPayload directly without
invoking the adapter's afterDeliverPayload hook. Discord's
afterDeliverPayload registers the delivered message id with the
Codex user-input control tracker, so a typed/freeform answer can
resolve the Codex request but the original Discord buttons stay
live and stale until someone clicks them.

Invoke adapter.afterDeliverPayload?.({ cfg, target, payload,
results }) immediately after sendPayload returns, matching the
shape used by core's outbound deliver pipeline. The Discord
afterDeliverPayload then registers the delivered message id so a
later freeform answer disables the corresponding control token.
Wrap the new direct adapter.afterDeliverPayload call in try/catch so
a hook crash after a successful platform send does not fail the whole
sendProgressReply. This matches the shared outbound pipeline's
maybeNotifyAfterDeliveredPayload helper which isolates and logs
hook failures, preserving successful delivery semantics for
downstream Codex user-input prompts.

Without this guard, a Discord afterDeliverPayload failure would
cancel the pending Codex user-input token even though the prompt
had already been delivered, leaving a visible prompt whose buttons
and freeform answers no longer resolve the Codex request.
buildBoundConversationCollaborationMode emitted settings.model: null
whenever the bound CodexAppServerThreadBinding did not have a stored
model field. Earlier versions of the binding type treated model as
optional, so legacy session files (and any binding created without
a model) would now fail the next turn/start with an invalid
collaborationMode payload because the Codex app-server contract
requires Settings.model to be a string.

Return undefined from the helper when binding.model is missing so
the turn request omits the collaboration mode object entirely. The
user can re-bind or set /codex model to pick a model; the existing
turn semantics (collaborationMode + reasoningEffort) continue to
work for bindings that have a model.

Adds a regression test that asserts the turn/start request has no
collaborationMode field when the binding omits model.
The codex plan controls PR brought in repro.mjs and
tmp-proof-state/discord/thread-bindings.json as one-off local
debugging artifacts. These are not product source, not tests, and
not release artifacts; shipping them in the repository root and
in a tmp-proof-state/ directory adds stale scratch state that
confuses state/migration scans and packaging.

Drop both from the bundle. If the diagnostics ever need to be
reusable, they should be converted into a named test fixture
under test/ rather than re-added at the repo root.
…l prompts

answerCodexUserInputFreeform rejected every pending request whose
currently-shown question lacked isOther, even when the user's
typed reply was a numeric prefix (e.g. '1') or the exact option
label. Channels that cannot render or keep buttons (plain text
relays, accessibility contexts) relied on this fallback to
resolve the active request_user_input; otherwise the message was
routed to a new bound turn while the original Codex turn waited
until the 10-minute timeout.

Add resolveFreeformOptionAnswer which normalizes the typed reply
against the rendered options: numeric prefix -> option label,
case-insensitive exact match -> option label, otherwise the raw
text. The sequential filter accepts the entry when normalization
matches or the question is isOther; replies that do not normalize
to any option stay rejected so stray chat messages do not consume
the request. The sequential branch records the normalized label
on the pending entry so the resolved merge uses the canonical
option label.

Updates the existing test that was encoding the regression to
exercise the correct fallback (numeric prefix on a labeled
question) and the rejection path (unrelated reply on a labeled
question stays matched: false). Adds a new test covering both
numeric and label forms for sequential prompts.
The previous commit returned a string from resolveFreeformOptionAnswer
and used string equality as a sentinel for 'no match'. That missed
exact same-case label replies because resolveFreeformOptionAnswer
returns the same label string when the user types the label
verbatim, so the filter treated it as 'no option matched' and
rejected a perfectly valid fallback.

Return { matched, answer } from the normalizer so callers can
distinguish a real match from a raw-typed reply. Update both the
sequential filter and the sequential branch to use the new shape.

Also fix a related gate: the legacy 'some question is isOther'
prefilter still ran before the sequential numeric/label
normalization, so all-option sequential prompts (where isOther
is false for every question) were rejected before the fallback
could try to resolve the typed reply. Skip the isOther prefilter
for sequential pending entries; the option-match check below
already validates the reply.

Add a regression test covering all-option sequential prompts
(where no question has isOther) and the case-insensitive exact
label fallback.
…+ show active think defaults in /codex binding

Two P2/P3 follow-ups flagged by autoreview on the rebased
feat/codex-plan-controls branch:

(1) /codex plan and /codex think were added to
    CODEX_NATIVE_EXECUTION_SUBCOMMANDS, which made every
    '/codex plan ...' and '/codex think ...' invocation pass through
    resolveCodexNativeExecutionBlock. In sessions where native
    Codex execution is sandbox-blocked, users could not run local
    preference forms like '/codex plan off', '/codex plan status',
    '/codex plan stay <token>', or '/codex think status' even
    though those forms only read or update the stored binding.
    Existing controls (model, fast, permissions) explicitly return
    before the native execution gate for status/invalid/local
    forms. Match that pattern: plan [on|off|status|empty|stay
    <token>] is local; only plan [approve|approve-clean] <token>
    triggers a native Codex turn and must stay behind the guard.
    think is always local (it is a preference write against the
    bound binding).

(2) /codex binding still formatted threadBinding.reasoningEffort
    directly, but the new /codex think command stores into
    reasoningEffortDefaults and clears the legacy reasoningEffort
    field. After running /codex think xhigh, the binding status
    reported 'Think: default', making the new control appear not
    to work. Switch the binding status path to the same
    resolveCodexAppServerConversationReasoningEffort helper the
    turn start path uses, so /codex binding and /codex think agree
    on the active effort including the new per-mode defaults.

Adds two regression tests: one extends the existing 'local Codex
binding status forms in sandboxed sessions' test to cover
'/codex plan status' and '/codex think status'; one asserts the
binding status reports the active think effort resolved from the
new reasoningEffortDefaults field.
…ng configured think defaults

The previous commit passed the full Codex plugin config to
readCodexAppServerConversationReasoningDefaults, but that helper
only reads a flat { execute, plan } object. Configured defaults
live at plugins.entries.codex.config.appServer.conversationReasoningDefaults
(not at the top level of the plugin config), so the configured
default was silently dropped on /codex binding status while the
turn start path used the unwrapped value via runtime config.

Read the configured defaults via readCodexPluginConfig(pluginConfig)
.appServer?.conversationReasoningDefaults before passing to the
helper, matching how /codex think status resolves them.

Adds a regression test that passes a pluginConfig with
appServer.conversationReasoningDefaults and asserts the binding
status reflects it.
…request_user_input prompts

Plan-decision callbacks (Approve / Approve with clean context) ran the
follow-up turn via runCodexBoundConversationPrompt, but no
sendProgressReply was wired through. runBoundTurn returns
emptyUserInputResponse() when sendProgressReply is undefined, so any
Codex request_user_input prompt in the follow-up turn timed out after
10 minutes and the user never saw the question.

CodexCommandDeps.buildPlanApprovalProgressReply now takes the
channel of the originating callback so the progress sender is
correctly targeted for telegram, discord, and slack. The factory is
threaded through all three handleCodexPlanDecisionCallbackLazy
call sites in extensions/codex/index.ts and the slash command path.
…presentation

- Use the originating event.channel for the inbound_claim progress
  sender so Discord/Slack bound conversations no longer get their
  request_user_input prompts routed through the Telegram adapter.
- Render the portable Codex presentation via
  normalizeMessagePresentation + adaptMessagePresentationForChannel +
  channel renderPresentation before sendPayload, so plan-approval
  follow-up prompts reach the user as native buttons/components
  instead of text-only payloads.
@SYU8384
SYU8384 force-pushed the feat/codex-plan-controls branch from 887d0f0 to c78067a Compare June 4, 2026 15:47
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. labels Jun 4, 2026
SYU8384 added 4 commits June 5, 2026 15:00
…nversations

When the user types a freeform reply to a Codex request_user_input
prompt on a bound conversation and answerCodexUserInputFreeform
returns matched: false, the inbound_claim falls through to
`return { handled: true }` (the non-command-authorized path) and the
user sees no response. The most likely cause is a scope mismatch
between the pending user input (queued by sendProgressReply on the
synthetic inbound event from the plan-approval follow-up) and the
inbound ctx (the user's typed text dispatch context) on channel /
senderId / sessionKey / messageThreadId.

Add a single embeddedAgentLog.warn after the freeform check that
records:
- the typed prompt preview
- inputResult.matched and the resulting message
- the inbound event's channel / senderId / accountId / sessionKey /
  messageThreadId / commandAuthorized
- the binding's kind and sessionFile

This is a diagnostic-only change: no behavior is altered. After the
user re-tests on Discord, the log line will identify which field
mismatches and we can land the actual fix in a follow-up commit.
…orm does not match

Previously, a non-command-authorized typed message on a bound
codex-app-server conversation was silently swallowed by
`return { handled: true }` after the freeform matcher check. The
matcher could return matched: false for a typed reply that did not
exactly match a pending user_input option label, scope-mismatched
on sessionKey / messageThreadId, or had no pending at all. The
result: the user typed text into a bound Codex chat, saw no
response, and the bound turn never started.

A bound chat session should always reach Codex as a fresh turn
prompt so the user sees a response, even if the typed text is plain
prose. Slash commands are still protected upstream by
answerCodexUserInputFreeform's "/" check (line 322 of
conversation-chat-controls.ts), and the codex plugin's own /codex
command router handles explicit /codex <verb> commands before this
inbound_claim hook is reached.

Update the two existing tests that were asserting the old
silent-drop behavior to assert the new fall-through-to-turn-start
behavior, including a binding sidecar file so the new turn can
locate the thread.

A diagnostic log line is kept for the case where the freeform
matcher returns matched: false on an app-server bound conversation.
This logs only scope fields (channel / senderId / accountId /
sessionKey / messageThreadId / commandAuthorized and the binding's
kind / sessionFile) — never prompt content — so future debugging
does not require a code change and authorized prompts (which can
contain secrets) are not captured.
Previously, a typed freeform reply that was a prefix of the option
label but not the exact label was rejected by
resolveFreeformOptionAnswer. For example, a user that types
"CLI Cleanup" against a rendered option labeled
"CLI Cleanup    (Recommended)    - Keeps the fake plan small and
engineering-shaped." would not match (case-insensitive exact
match only), so the matcher returned matched: false and the user's
typed text was treated as an unmatched freeform.

Add a single-option prefix match: if exactly one option's label
starts with the typed text (case-insensitive), resolve to that
option. If two or more options share a common prefix, fall through
to the caller's freeform fallback (or the new inbound_claim
"couldn't match your reply" guard) so ambiguity is handled
elsewhere.

This unblocks the common pattern of typing a shortened option
label without copying the (Recommended) suffix or description that
the chat UI renders alongside the button.
…m the chat UI

The buildCurrentContextPlanApprovalPrompt and
buildCleanContextPlanApprovalPrompt builders used a generic
"The user approved the plan below" framing that did not name
Codex or attribute the approval to the chat-UI button click.
That left Codex without a clear mental model of how the approval
reached it, and led to a follow-up reply where Codex mis-attributed
the button to the surrounding OpenClaw shell rather than accepting
it as a real plan approval.

Update both builders to use first-person framing: "I (Codex)
just received an ... button click from the OpenClaw chat UI.
That button is OpenClaw routing the user's plan approval back
into this Codex thread — it is not a command the user typed."
The remainder of the prompt (execute the plan, re-read files,
verify) is unchanged.

Add tests that pin the new wording for both the current-context
and clean-context approval flows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: discord Channel integration: discord channel: slack Channel integration: slack channel: telegram Channel integration: telegram docs Improvements or additions to documentation extensions: codex extensions: qa-lab merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. scripts Repository scripts size: XL triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant