fix(codex): force message tool for source replies (#76663)

* fix(codex): force message tool for source replies

* docs: credit codex source reply fix (#76663) (thanks @VishalJ99)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Vishal Jain
2026-05-03 15:29:07 +01:00
committed by GitHub
parent ffdc76bb0c
commit 0e4d28aa9e
3 changed files with 33 additions and 0 deletions

View File

@@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
- Chat delivery: make `/verbose on|full|off` changes affect subsequent tool-use chat bubbles again, including channels with draft preview tool progress enabled, while preserving one-shot verbose directives.
- CLI/logs: auto-reconnect `openclaw logs --follow` on transient gateway disconnects (WebSocket close, timeout, connection drop) with bounded exponential backoff (up to 8 retries, capped at 30 s) and stderr retry warnings, while still exiting immediately on non-recoverable auth or configuration errors. Fixes #74782. (#75059) Thanks @shashank-poola.
- CLI/logs: announce `--follow` recovery with a `[logs] gateway reconnected` notice once a poll succeeds after a transient outage, and emit JSON `notice` records in `--json` mode for both the retry warning and the reconnect transition, so live monitoring scripts can react to the recovery. Carries forward #75059. (#75372) Thanks @romneyda.
- Codex/WhatsApp: keep the `message` dynamic tool available when Codex source replies are configured for message-tool delivery, so coding-profile chat agents do not complete turns privately without a visible channel reply. Fixes #76660. (#76663) Thanks @VishalJ99.
- Plugins/onboarding: trust optional official plugin and web-search installs selected from the official catalog so npm security scanning treats them like other source-linked official install paths. Thanks @vincentkoc.
- Agents/web_search: keep installed runtime provider discovery enabled when web-search metadata is missing, so externally installed official providers such as Brave remain visible to agent and cron turns instead of falling back to bundled-only lookup. Fixes #76626. Thanks @amknight.
- Tests/plugins: expose the Discord npm onboarding Docker lane as a package script and assert planned Docker lanes point at real scripts, so external-channel onboarding coverage can actually run. Thanks @vincentkoc.

View File

@@ -440,6 +440,37 @@ describe("runCodexAppServerAttempt", () => {
);
});
it("forces the message dynamic tool for message-tool-only source replies", async () => {
const harness = createStartedThreadHarness();
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
params.disableTools = false;
params.config = { tools: { profile: "coding" } };
params.sourceReplyDeliveryMode = "message_tool_only";
params.messageProvider = "whatsapp";
params.timeoutMs = 60_000;
const run = runCodexAppServerAttempt(params, { turnCompletionIdleTimeoutMs: 5 });
await harness.waitForMethod("thread/start");
await harness.waitForMethod("turn/start");
const startRequest = harness.requests.find((request) => request.method === "thread/start");
const dynamicToolNames = (
(startRequest?.params as { dynamicTools?: Array<{ name: string }> } | undefined)
?.dynamicTools ?? []
).map((tool) => tool.name);
expect(dynamicToolNames).toContain("message");
await new Promise<void>((resolve) => setImmediate(resolve));
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
await expect(run).resolves.toMatchObject({
timedOut: false,
aborted: false,
});
});
it("returns a failed dynamic tool response when an app-server tool call exceeds the deadline", async () => {
vi.useFakeTimers();
let capturedSignal: AbortSignal | undefined;

View File

@@ -1482,6 +1482,7 @@ async function buildDynamicTools(input: DynamicToolBuildParams) {
requireExplicitMessageTarget:
params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
disableMessageTool: params.disableMessageTool,
forceMessageTool: params.sourceReplyDeliveryMode === "message_tool_only",
enableHeartbeatTool: params.trigger === "heartbeat",
forceHeartbeatTool: params.trigger === "heartbeat",
onYield: (message) => {