Skip to content

Route Codex message tool replies back to WebChat and TUI#81586

Merged
pashpashpash merged 4 commits into
mainfrom
codex/internal-ui-message-sink
May 14, 2026
Merged

Route Codex message tool replies back to WebChat and TUI#81586
pashpashpash merged 4 commits into
mainfrom
codex/internal-ui-message-sink

Conversation

@pashpashpash

@pashpashpash pashpashpash commented May 14, 2026

Copy link
Copy Markdown
Contributor

Codex harness sessions intentionally route source-visible replies through tools.message. That is the right contract for Codex: the model decides what to send to the user, and OpenClaw records that send as the visible reply. External channels already had normal outbound targets, but WebChat and the TUI are internal UI surfaces for the current run, not reusable message channels. In a Codex-bound direct UI session that left message.send with no target, so the tool failed with Action send requires a target while the normal final answer was suppressed.

This PR adds the missing current-run internal UI sink. When a message_tool_only run originates from WebChat or the TUI, a targetless message.send satisfies that same active turn, records a source reply, and mirrors the text into session history so reloads show the assistant reply. Explicit channel and target sends still use the existing outbound routing path. WebChat and the TUI still do not inherit lastChannel, and they are not made into generic targetable outbound channels for cron or background sends.

This replaces the stopgap in #81110. That PR correctly identified the collision between Codex message-tool delivery and internal WebChat, but it fixes the symptom by skipping the Codex message_tool_only default for internal WebChat and letting automatic final delivery take over. That makes the failing WebChat path visible again, but it routes around the Codex harness contract, does not give the TUI the same principled current-run sink, and leaves the tool path itself unable to satisfy a source-visible internal UI reply.

The better fix is to keep Codex harness behavior intact and teach the message tool how to deliver to the current internal UI run when, and only when, the run already declares that as its source-visible reply sink. That preserves external channel behavior, avoids accidental lastChannel delivery, keeps WebChat and the TUI non-targetable as outbound destinations, and makes the observed failure shape succeed instead of bypassing it.

Real behavior proof

  • Behavior or issue addressed: targetless Codex tools.message replies from a message-tool-only internal WebChat/TUI run should satisfy the active UI turn, suppress the private final text, and mirror the visible reply to session history.
  • Real environment tested: local OpenClaw PR branch Gateway on ws://127.0.0.1:18891, real main dev agent, openai/gpt-5.5, Codex app-server runtime, and a Codex-bound throwaway internal UI session.
  • Exact steps or command run after this patch: started this PR branch's Gateway from source, sent a TUI-mode chat.send turn to the bound session, and forced the model to call message.send with only action and message, with no target, channel, to, channelId, or fallback route fields.
  • Evidence after fix: copied live output is in Route Codex message tool replies back to WebChat and TUI #81586 (comment), including the tool call and result:
tool.call name=message arguments={"action":"send","message":"OPENCLAW-INTERNAL-UI-BOUND-MESSAGE-66056D"}
tool.result success=true channel=webchat target=current-run sourceReplyDeliveryMode=message_tool_only sourceReplySink=internal-ui
chat.history assistantTexts=["OPENCLAW-PRELUDE-AE89","OPENCLAW-INTERNAL-UI-BOUND-MESSAGE-66056D"]
  • Observed result after fix: the targetless message tool call succeeded as the current internal UI reply, returned target=current-run, persisted a delivery mirror, and a fresh chat.history readback contained the visible assistant text.
  • What was not tested: manual browser DOM reload was not repeated in that smoke; the proof used the same Gateway session/history path WebChat and TUI consume.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation agents Agent runtime and tooling extensions: codex size: L maintainer Maintainer-authored PR labels May 14, 2026
@clawsweeper

clawsweeper Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
The PR threads Codex message-tool-only source-reply context through the message tool, adds a guarded WebChat/TUI current-run sink, mirrors source replies to session history, and updates tests, docs, and changelog.

Reproducibility: yes. by source inspection. Current main sends targetless WebChat/TUI Codex message.send through target normalization without delivery-mode context, which throws before any internal UI sink can satisfy the active turn.

Real behavior proof
Sufficient (live_output): The PR body and linked copied live output show after-fix targetless message.send success as the current internal UI reply plus chat.history readback of the visible text.

Next step before merge
No automated repair is needed; the protected-label PR should proceed through maintainer review and normal merge gates.

Security
Cleared: The diff adds no dependencies, workflows, install scripts, secret access, or generic outbound targetability; the new delivery path is scoped to the active internal UI run.

Review details

Best possible solution:

Land the guarded current-run sink after maintainer review, keeping WebChat/TUI internal-only while explicit channel sends remain on the normal outbound path.

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

Yes by source inspection. Current main sends targetless WebChat/TUI Codex message.send through target normalization without delivery-mode context, which throws before any internal UI sink can satisfy the active turn.

Is this the best way to solve the issue?

Yes. The PR’s guarded internal-source sink is narrower than changing Codex delivery defaults or making WebChat/TUI generic outbound channels, and it preserves explicit route behavior.

What I checked:

  • Current-main failure path: On current main, target-requiring message actions reach normalizeMessageActionInput; a targetless send without an inferred target throws Action send requires a target. (src/infra/outbound/message-action-normalization.ts:67, faa443a45220)
  • Current-main missing runtime propagation: Current main builds toolContext for the message tool but the call to runMessageActionForTool does not pass sourceReplyDeliveryMode, so the outbound runner cannot identify message-tool-only source replies. (src/agents/tools/message-tool.ts:925, faa443a45220)
  • Internal UI source context: Gateway chat.send marks internal WebChat/TUI turns with Provider and Surface set to the internal webchat channel, which is the context the PR uses as the guarded source sink. (src/gateway/server-methods/chat.ts:2262, faa443a45220)
  • PR sink guard: The PR head only uses the internal source sink for send, message_tool_only, current provider webchat, a non-empty session key, and no explicit route params, preserving explicit outbound routes. (src/infra/outbound/message-action-runner.ts:482, 3ce4a87e19d4)
  • PR source-reply payload path: The PR head converts internal message-tool source replies into suppression-safe final payloads and attaches transcript-mirror metadata for durable history. (src/agents/pi-embedded-runner/run/payloads.ts:227, 3ce4a87e19d4)
  • Real behavior proof: The PR body and linked live-output comment show targetless message.send returning success=true, channel=webchat, target=current-run, sourceReplySink=internal-ui, followed by chat.history readback containing the visible assistant text. (3ce4a87e19d4)

Likely related people:

  • pashpashpash: Current-main history includes structured Codex tool replies, deferred Codex dynamic tools, and Codex startup changes adjacent to the message-tool-only path. (role: recent Codex runtime contributor; confidence: high; commits: 439d8edf68e2, 3f217964d1f9, 3ce922437fb6; files: extensions/codex/src/app-server/dynamic-tools.ts, src/agents/pi-embedded-runner/run/payloads.ts, src/agents/tools/message-tool.ts)
  • steipete: Current-main history annotated message-tool-only replies, added per-agent message action allowlists, and routed outbound sends through durable lifecycle around the affected contract. (role: recent outbound/message-tool area contributor; confidence: high; commits: b62166301efd, 5e8e77ed83eb, 2ead1502c9bf; files: src/agents/tools/message-tool.ts, src/infra/outbound/message-action-runner.ts, src/auto-reply/reply/dispatch-from-config.ts)

Remaining risk / open question:

  • Protected maintainer label requires explicit maintainer handling before merge.
  • The real proof used Gateway/session history paths rather than a manual browser DOM reload, though those are the paths WebChat and TUI consume.

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

@pashpashpash

Copy link
Copy Markdown
Contributor Author

Added the real behavior proof section with copied local runtime output from the production message-action, embedded payload, and transcript mirror path. The Real behavior proof check is now green on the current head b96b5ec5b541ba4710de658ddccf184cba97087b.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@pashpashpash

pashpashpash commented May 14, 2026

Copy link
Copy Markdown
Contributor Author

I also ran a real OpenClaw smoke against Pash's local dev agent, using this PR branch as a separate loopback Gateway and a TUI-mode client.

Shape tested:

  • Gateway from codex/internal-ui-message-sink on ws://127.0.0.1:18891
  • real main dev agent config, model openai/gpt-5.5, Codex app-server runtime
  • throwaway WebChat/TUI session: agent:main:pr-81586-bound-codex-smoke-1778722774408
  • session explicitly matched the failing shape by being Codex-harness-bound before the target turn

Evidence from the session trajectory:

tool.call name=message arguments={"action":"send","message":"OPENCLAW-INTERNAL-UI-BOUND-MESSAGE-66056D"}
tool.result success=true
  deliveryStatus=sent
  channel=webchat
  target=current-run
  sourceReplyDeliveryMode=message_tool_only
  sourceReplySink=internal-ui
  sourceReply.text=OPENCLAW-INTERNAL-UI-BOUND-MESSAGE-66056D

The mirrored session transcript then recorded a delivery-mirror assistant message with that same token, and a fresh chat.history readback returned:

OPENCLAW-PRELUDE-AE89
OPENCLAW-INTERNAL-UI-BOUND-MESSAGE-66056D

That proves the failing targetless message.send path now reaches the active internal UI run and survives history reload. The extra smoke Gateway was stopped after the run.

Re-review progress:

@pashpashpash pashpashpash force-pushed the codex/internal-ui-message-sink branch from b96b5ec to 1ca3f48 Compare May 14, 2026 01:48
@pashpashpash

Copy link
Copy Markdown
Contributor Author

Rebased this onto current origin/main and force-pushed the PR branch at 1ca3f48fe8e32916ee6cab3a0cc20fbf00da5b95.

The conflict was only in the now-upstreamed live profile lint helper, so I skipped the old lint-only follow-up commit and kept the internal UI message sink as a single feature commit on top of main.

Local rebase validation:

pnpm test src/infra/outbound/message-action-runner.send-validation.test.ts src/agents/pi-embedded-runner/run/payloads.test.ts src/auto-reply/reply/dispatch-from-config.test.ts extensions/codex/src/app-server/dynamic-tools.test.ts
[test] passed 4 Vitest shards in 17.52s

pnpm exec oxfmt --check --threads=1 $(git diff --name-only origin/main...HEAD)
All matched files use the correct format.

git diff --check origin/main...HEAD
passed

The Real behavior proof check is green again on the rebased head.

@clawsweeper re-review

@pashpashpash pashpashpash force-pushed the codex/internal-ui-message-sink branch from e1b42de to 0269444 Compare May 14, 2026 01:56
@pashpashpash

Copy link
Copy Markdown
Contributor Author

Rebased once more onto current origin/main after main moved again and force-pushed the PR branch at 0269444ff46c8549c7c3261ad8e465a4469f59d2.

This rebase had one changelog conflict; I kept both the new Codex startup fix entry from main and this PR's WebChat/TUI fix entry. I also added the small SDK subpath docs contract correction needed for the current main plugin guardrail to pass after the rebase.

Local validation on the latest head:

pnpm test src/infra/outbound/message-action-runner.send-validation.test.ts src/agents/pi-embedded-runner/run/payloads.test.ts src/auto-reply/reply/dispatch-from-config.test.ts extensions/codex/src/app-server/dynamic-tools.test.ts src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts src/gateway/server.sessions-send.test.ts
[test] passed 6 Vitest shards in 27.10s

pnpm exec oxfmt --check --threads=1 $(git diff --name-only origin/main...HEAD)
All matched files use the correct format.

git diff --check origin/main...HEAD
passed

GitHub now reports the PR as mergeable again and the fresh checks are running.

@clawsweeper re-review

@pashpashpash pashpashpash force-pushed the codex/internal-ui-message-sink branch from 0269444 to c87f148 Compare May 14, 2026 02:01
@pashpashpash

Copy link
Copy Markdown
Contributor Author

Rebased again after main advanced to bce56bacc7107d82bff8f2ef89f9e3697b3477a2 and force-pushed the PR branch at c87f14885ca3988421bf7b32ebeb4b579709a6e1.

This was another changelog-only conflict; I kept the new main entry and this PR's WebChat/TUI entry. Local validation on the latest head is still green:

pnpm test src/infra/outbound/message-action-runner.send-validation.test.ts src/agents/pi-embedded-runner/run/payloads.test.ts src/auto-reply/reply/dispatch-from-config.test.ts extensions/codex/src/app-server/dynamic-tools.test.ts src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts src/gateway/server.sessions-send.test.ts
[test] passed 6 Vitest shards in 16.95s

pnpm exec oxfmt --check --threads=1 $(git diff --name-only origin/main...HEAD)
All matched files use the correct format.

git diff --check origin/main...HEAD
passed

GitHub reports the PR as mergeable again and fresh CI is running on the new head.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@openclaw-barnacle openclaw-barnacle Bot added the gateway Gateway runtime label May 14, 2026
@pashpashpash

Copy link
Copy Markdown
Contributor Author

Patched the CI-only checks-node-agentic-control-plane-agent-chat failure as a test assertion issue in src/gateway/server.sessions-send.test.ts.

The failing test already proved the orion send completed through its returned details and session store, but then inspected spy.mock.calls.at(0). In the full gateway shard, a late call from the preceding label test can appear first, which is why CI sometimes saw agent:main:test-labeled-session. The assertion now finds the agent:orion:main call directly and still checks that the stored session id matches that call.

Validation before pushing e631b971890349d941ae9acf9927c017c1a59134:

OPENCLAW_VITEST_MAX_WORKERS=2 OPENCLAW_TEST_PROJECTS_PARALLEL=2 OPENCLAW_VITEST_SHARD_NAME=agentic-control-plane-agent-chat OPENCLAW_VITEST_INCLUDE_FILE=<exact CI include list> pnpm exec node scripts/test-projects.mjs test/vitest/vitest.gateway-server.config.ts
[test] passed 1 Vitest shard in 7.27s

pnpm exec oxfmt --check --threads=1 src/gateway/server.sessions-send.test.ts
All matched files use the correct format.

git diff --check origin/main...HEAD
passed

Fresh CI is running on the new head.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper

clawsweeper Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@vincentkoc vincentkoc force-pushed the codex/internal-ui-message-sink branch from dab02f0 to 2def23b Compare May 14, 2026 02:46
@pashpashpash

Copy link
Copy Markdown
Contributor Author

Reconciled the latest force-pushed PR head 2def23bd0eb31ae467ed21064100ea6209f5182f onto current origin/main at faa443a45220ec2df124008cd805e9af99f461eb and pushed the rebased branch at 3ce4a87e19d4ef1c3a57eb39244a48fbda83ca0f.

The remaining conflict was in CHANGELOG.md; I kept the new main entries and the PR's WebChat/TUI entry. The force-pushed rich internal source reply commit is preserved on top of the branch.

Local validation:

pnpm test src/infra/outbound/message-action-runner.send-validation.test.ts src/agents/pi-embedded-runner/run/payloads.test.ts src/auto-reply/reply/dispatch-from-config.test.ts extensions/codex/src/app-server/dynamic-tools.test.ts src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts src/gateway/server.sessions-send.test.ts
[test] passed 6 Vitest shards in 19.78s

pnpm exec oxfmt --check --threads=1 $(git diff --name-only origin/main...HEAD)
All matched files use the correct format.

git diff --check origin/main...HEAD
passed

GitHub now reports the PR as mergeable again; fresh CI is running on 3ce4a87e19d4ef1c3a57eb39244a48fbda83ca0f.

@pashpashpash

Copy link
Copy Markdown
Contributor Author

Latest head 3ce4a87e19d4ef1c3a57eb39244a48fbda83ca0f is now rebased, mergeable, and green. The prior ClawSweeper durable review still references the old dirty head, so asking for one fresh pass on the current head.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper could not start a re-review for this item.

Reason: re-review requires an open issue or PR.

@pashpashpash pashpashpash merged commit 78eb92e into main May 14, 2026
112 checks passed
@pashpashpash pashpashpash deleted the codex/internal-ui-message-sink branch May 14, 2026 02:55
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
)

* fix: route internal ui message tool replies

* docs: document reserved codex sdk helpers

* test(gateway): stabilize sessions send agent assertion

* fix(agents): preserve rich internal source replies

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
)

* fix: route internal ui message tool replies

* docs: document reserved codex sdk helpers

* test(gateway): stabilize sessions send agent assertion

* fix(agents): preserve rich internal source replies

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
)

* fix: route internal ui message tool replies

* docs: document reserved codex sdk helpers

* test(gateway): stabilize sessions send agent assertion

* fix(agents): preserve rich internal source replies

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation extensions: codex gateway Gateway runtime maintainer Maintainer-authored PR proof: sufficient ClawSweeper judged the real behavior proof convincing. size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants