mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(codex): continue turns after progress replies (#108487)
* fix(codex): defer omitted source reply finality * test(codex): refresh source reply prompt snapshots
This commit is contained in:
@@ -31,6 +31,7 @@ import {
|
||||
type CodexDynamicToolSpec,
|
||||
type JsonValue,
|
||||
} from "./protocol.js";
|
||||
import { settleCodexSourceReplyFinality } from "./source-reply-finality.js";
|
||||
|
||||
const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
|
||||
|
||||
@@ -1388,7 +1389,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("marks delivered message-tool-only source replies as terminal when final is omitted", async () => {
|
||||
it("keeps omitted source-reply finality non-terminal until a successful attempt settles", async () => {
|
||||
const bridge = createBridgeWithToolResult(
|
||||
"message",
|
||||
textToolResult("Sent.", { messageId: "imessage-6264" }),
|
||||
@@ -1401,15 +1402,97 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText("Sent."));
|
||||
expect(result.terminate).toBe(true);
|
||||
expect(result.terminate).toBeUndefined();
|
||||
expect(bridge.telemetry.didDeliverSourceReplyViaMessageTool).toBe(true);
|
||||
expect(bridge.telemetry.messagingToolSentTargets.at(-1)).not.toHaveProperty("sourceReplyFinal");
|
||||
|
||||
expect(settleCodexSourceReplyFinality(bridge.telemetry, true)).toBe(true);
|
||||
|
||||
expect(bridge.telemetry.messagingToolSentTargets.at(-1)).toMatchObject({
|
||||
sourceReplyFinal: true,
|
||||
});
|
||||
expect(Object.keys(result)).not.toContain("terminate");
|
||||
});
|
||||
|
||||
it("requires explicit final=false to keep a delivered message-tool-only source reply non-terminal", async () => {
|
||||
it("settles omitted source-reply finality as progress when the attempt fails", async () => {
|
||||
const bridge = createBridgeWithToolResult(
|
||||
"message",
|
||||
{
|
||||
...textToolResult("Sent.", { messageId: "imessage-6264" }),
|
||||
terminate: true,
|
||||
},
|
||||
{ sourceReplyDeliveryMode: "message_tool_only" },
|
||||
);
|
||||
|
||||
const result = await handleMessageToolCall(bridge, {
|
||||
action: "send",
|
||||
message: "visible reply",
|
||||
});
|
||||
expect(result.terminate).toBeUndefined();
|
||||
expect(settleCodexSourceReplyFinality(bridge.telemetry, false)).toBe(false);
|
||||
|
||||
expect(bridge.telemetry.messagingToolSentTargets.at(-1)).toMatchObject({
|
||||
sourceReplyFinal: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("settles only the latest omitted source reply as final after success", async () => {
|
||||
const bridge = createBridgeWithToolResult(
|
||||
"message",
|
||||
textToolResult("Sent.", { messageId: "imessage-6264" }),
|
||||
{ sourceReplyDeliveryMode: "message_tool_only" },
|
||||
);
|
||||
|
||||
await handleMessageToolCall(bridge, { action: "send", message: "first update" });
|
||||
await handleMessageToolCall(bridge, { action: "send", message: "second update" });
|
||||
settleCodexSourceReplyFinality(bridge.telemetry, true);
|
||||
|
||||
expect(
|
||||
bridge.telemetry.messagingToolSentTargets.map((target) => target.sourceReplyFinal),
|
||||
).toEqual([false, true]);
|
||||
});
|
||||
|
||||
it("does not promote an omitted reply past a later explicit progress reply", async () => {
|
||||
const bridge = createBridgeWithToolResult(
|
||||
"message",
|
||||
textToolResult("Sent.", { messageId: "imessage-6264" }),
|
||||
{ sourceReplyDeliveryMode: "message_tool_only" },
|
||||
);
|
||||
|
||||
await handleMessageToolCall(bridge, { action: "send", message: "first update" });
|
||||
await handleMessageToolCall(bridge, {
|
||||
action: "send",
|
||||
message: "still working",
|
||||
final: false,
|
||||
});
|
||||
settleCodexSourceReplyFinality(bridge.telemetry, true);
|
||||
|
||||
expect(
|
||||
bridge.telemetry.messagingToolSentTargets.map((target) => target.sourceReplyFinal),
|
||||
).toEqual([false, false]);
|
||||
});
|
||||
|
||||
it("keeps a later explicit final reply authoritative over an omitted reply", async () => {
|
||||
const bridge = createBridgeWithToolResult(
|
||||
"message",
|
||||
textToolResult("Sent.", { messageId: "imessage-6264" }),
|
||||
{ sourceReplyDeliveryMode: "message_tool_only" },
|
||||
);
|
||||
|
||||
await handleMessageToolCall(bridge, { action: "send", message: "first update" });
|
||||
await handleMessageToolCall(bridge, {
|
||||
action: "send",
|
||||
message: "finished",
|
||||
final: true,
|
||||
});
|
||||
settleCodexSourceReplyFinality(bridge.telemetry, true);
|
||||
|
||||
expect(
|
||||
bridge.telemetry.messagingToolSentTargets.map((target) => target.sourceReplyFinal),
|
||||
).toEqual([false, true]);
|
||||
});
|
||||
|
||||
it("honors explicit finality for delivered message-tool-only source replies", async () => {
|
||||
const bridge = createBridgeWithToolResult(
|
||||
"message",
|
||||
textToolResult("Sent.", { messageId: "imessage-6264" }),
|
||||
@@ -1474,6 +1557,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
const result = await handleMessageToolCall(bridge, {
|
||||
action: "send",
|
||||
message: "visible reply",
|
||||
final: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText("Sent."));
|
||||
@@ -1525,6 +1609,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
messageId: "853",
|
||||
message: "visible reply",
|
||||
buttons: [],
|
||||
final: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText("Sent."));
|
||||
@@ -1571,6 +1656,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
target: "+1 (206) 910-6512",
|
||||
messageId: "853",
|
||||
message: "visible reply",
|
||||
final: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText("Sent."));
|
||||
@@ -1610,6 +1696,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
messageId: "857",
|
||||
message: "visible reply",
|
||||
buttons: [],
|
||||
final: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText("Sent."));
|
||||
@@ -1646,6 +1733,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
messageId: "861",
|
||||
message: "visible reply",
|
||||
buttons: [],
|
||||
final: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText(receiptText));
|
||||
@@ -1726,6 +1814,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
messageId: "863",
|
||||
message: "visible reply",
|
||||
buttons: [],
|
||||
final: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText("Sent."));
|
||||
@@ -1747,6 +1836,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
messageId: "865",
|
||||
message: "visible reply",
|
||||
buttons: [],
|
||||
final: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText("Sent."));
|
||||
@@ -1755,7 +1845,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
expect(Object.keys(result)).not.toContain("terminate");
|
||||
});
|
||||
|
||||
it("records message-tool-owned terminal replies as delivered source replies", async () => {
|
||||
it("defers omitted finality even when the message tool returns legacy termination", async () => {
|
||||
const bridge = createBridgeWithToolResult(
|
||||
"message",
|
||||
{
|
||||
@@ -1775,8 +1865,12 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
});
|
||||
|
||||
expect(result).toEqual(expectInputText("Sent."));
|
||||
expect(result.terminate).toBe(true);
|
||||
expect(result.terminate).toBeUndefined();
|
||||
expect(bridge.telemetry.didDeliverSourceReplyViaMessageTool).toBe(true);
|
||||
expect(bridge.telemetry.messagingToolSentTargets.at(-1)).not.toHaveProperty("sourceReplyFinal");
|
||||
|
||||
settleCodexSourceReplyFinality(bridge.telemetry, true);
|
||||
|
||||
expect(bridge.telemetry.messagingToolSentTargets.at(-1)).toMatchObject({
|
||||
sourceReplyFinal: true,
|
||||
});
|
||||
@@ -1850,7 +1944,7 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
arguments: { action: "inspect" },
|
||||
});
|
||||
|
||||
expect(firstResult.terminate).toBe(true);
|
||||
expect(firstResult.terminate).toBeUndefined();
|
||||
expect(bridge.telemetry.didSendViaMessagingTool).toBe(true);
|
||||
expect(secondResult).toEqual(expectInputText("No message sent."));
|
||||
expect(secondResult.terminate).toBeUndefined();
|
||||
|
||||
@@ -61,6 +61,7 @@ import {
|
||||
type CodexDynamicToolSpec,
|
||||
type JsonValue,
|
||||
} from "./protocol.js";
|
||||
import { recordCodexSourceReplyDeliveryIntent } from "./source-reply-finality.js";
|
||||
import { resolveCodexToolAbortTerminalReason } from "./tool-abort-terminal-reason.js";
|
||||
|
||||
type CodexDynamicToolHookContext = {
|
||||
@@ -688,18 +689,16 @@ export function createCodexDynamicToolBridge(params: {
|
||||
!resultIsError &&
|
||||
(rawResult.terminate === true || result.terminate === true);
|
||||
const hasExplicitFinalControl = typeof executedArgs.final === "boolean";
|
||||
// Omitted final on a confirmed source reply must degrade to legacy
|
||||
// terminate-on-delivery (completed marker), never progress; otherwise
|
||||
// stranded-reply recovery re-delivers a duplicate of that reply.
|
||||
const sourceReplyFinal =
|
||||
const confirmedSourceReply =
|
||||
params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" &&
|
||||
toolName === "message" &&
|
||||
(toolConfirmedSourceReply || deliveredSourceReply || receiptConfirmedSourceReply)
|
||||
? hasExplicitFinalControl
|
||||
? executedArgs.final === true
|
||||
: true
|
||||
: undefined;
|
||||
collectToolTelemetry({
|
||||
(toolConfirmedSourceReply || deliveredSourceReply || receiptConfirmedSourceReply);
|
||||
const sourceReplyFinal = confirmedSourceReply
|
||||
? hasExplicitFinalControl
|
||||
? executedArgs.final === true
|
||||
: undefined
|
||||
: undefined;
|
||||
const sourceReplyRecord = collectToolTelemetry({
|
||||
toolName,
|
||||
args: executedArgs,
|
||||
result,
|
||||
@@ -709,20 +708,26 @@ export function createCodexDynamicToolBridge(params: {
|
||||
messagingTarget: confirmedMessagingTarget,
|
||||
sourceReplyFinal,
|
||||
});
|
||||
if (confirmedSourceReply && sourceReplyRecord) {
|
||||
recordCodexSourceReplyDeliveryIntent(telemetry, {
|
||||
record: sourceReplyRecord,
|
||||
final: sourceReplyFinal,
|
||||
});
|
||||
}
|
||||
if (deliveredSourceReply || receiptConfirmedSourceReply || toolConfirmedSourceReply) {
|
||||
telemetry.didDeliverSourceReplyViaMessageTool = true;
|
||||
}
|
||||
const defersInferredSourceReplyTermination =
|
||||
confirmedSourceReply && executedArgs.final !== true;
|
||||
withDynamicToolTermination(
|
||||
response,
|
||||
((rawResult.terminate === true || result.terminate === true) &&
|
||||
!(
|
||||
params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" &&
|
||||
toolName === "message" &&
|
||||
executedArgs.final === false
|
||||
)) ||
|
||||
!defersInferredSourceReplyTermination) ||
|
||||
// Yield is an explicit owner-level turn handoff, not termination
|
||||
// inferred from source-reply delivery, so finality does not mask it.
|
||||
isToolResultYield(rawResult) ||
|
||||
isToolResultYield(result) ||
|
||||
((deliveredSourceReply || receiptConfirmedSourceReply) && executedArgs.final !== false),
|
||||
(confirmedSourceReply && executedArgs.final === true),
|
||||
);
|
||||
const asyncStarted =
|
||||
isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
|
||||
@@ -1146,9 +1151,9 @@ function collectToolTelemetry(params: {
|
||||
isError: boolean;
|
||||
messagingTarget?: MessagingToolSend;
|
||||
sourceReplyFinal?: boolean;
|
||||
}): void {
|
||||
}): MessagingToolSend | MessagingToolSourceReplyPayload | undefined {
|
||||
if (params.isError) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
if (!params.isError && params.toolName === "cron" && isCronAddAction(params.args)) {
|
||||
params.telemetry.successfulCronAdds = (params.telemetry.successfulCronAdds ?? 0) + 1;
|
||||
@@ -1180,11 +1185,11 @@ function collectToolTelemetry(params: {
|
||||
}
|
||||
}
|
||||
if (!isMessagingTool(params.toolName)) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
const isMessagingSendAction = isMessagingToolSendAction(params.toolName, params.args);
|
||||
if (!isMessagingSendAction && !params.messagingTarget) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
!isMessagingSendAction &&
|
||||
@@ -1196,18 +1201,19 @@ function collectToolTelemetry(params: {
|
||||
isError: params.isError,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
params.telemetry.didSendViaMessagingTool = true;
|
||||
const sourceReplyPayload = extractInternalSourceReplyPayload(params.result?.details);
|
||||
if (sourceReplyPayload) {
|
||||
params.telemetry.messagingToolSourceReplyPayloads.push({
|
||||
const record = {
|
||||
...sourceReplyPayload,
|
||||
...(params.sourceReplyFinal !== undefined
|
||||
? { sourceReplyFinal: params.sourceReplyFinal }
|
||||
: {}),
|
||||
});
|
||||
return;
|
||||
};
|
||||
params.telemetry.messagingToolSourceReplyPayloads.push(record);
|
||||
return record;
|
||||
}
|
||||
const text = readFirstString(params.args, ["text", "message", "body", "content"]);
|
||||
if (text) {
|
||||
@@ -1215,7 +1221,7 @@ function collectToolTelemetry(params: {
|
||||
}
|
||||
const mediaUrls = collectMediaUrls(params.args);
|
||||
params.telemetry.messagingToolSentMediaUrls.push(...mediaUrls);
|
||||
params.telemetry.messagingToolSentTargets.push({
|
||||
const record = {
|
||||
...(params.messagingTarget ?? {
|
||||
tool: params.toolName,
|
||||
provider: readFirstString(params.args, ["provider", "channel"]) ?? params.toolName,
|
||||
@@ -1226,7 +1232,9 @@ function collectToolTelemetry(params: {
|
||||
...(text ? { text } : {}),
|
||||
...(mediaUrls.length > 0 ? { mediaUrls } : {}),
|
||||
...(params.sourceReplyFinal !== undefined ? { sourceReplyFinal: params.sourceReplyFinal } : {}),
|
||||
});
|
||||
};
|
||||
params.telemetry.messagingToolSentTargets.push(record);
|
||||
return record;
|
||||
}
|
||||
function extractInternalSourceReplyPayload(
|
||||
details: unknown,
|
||||
|
||||
@@ -45,7 +45,7 @@ function addCodexMessageToolOnlyFinalParameter(parameters: unknown): unknown {
|
||||
final: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Set true only when this message is intended to complete the reply to the current source conversation. OpenClaw stops after confirming delivery.",
|
||||
"Set false for progress or true to complete the current source reply. If omitted, OpenClaw continues and resolves the latest omitted source reply when the turn ends.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from "./run-attempt-state.js";
|
||||
import type { prepareCodexAttemptTurnRequest } from "./run-attempt-turn-request.js";
|
||||
import type { CodexAttemptTurnState } from "./run-attempt-turn-state.js";
|
||||
import { settleCodexSourceReplyFinality } from "./source-reply-finality.js";
|
||||
import { normalizeCodexTrajectoryError, recordCodexTrajectoryCompletion } from "./trajectory.js";
|
||||
import { codexTranscriptMirrorRuntime } from "./transcript-mirror.js";
|
||||
import {
|
||||
@@ -270,14 +271,24 @@ export async function finalizeCodexAttempt(
|
||||
!state.terminalTurnNotificationQueued &&
|
||||
!state.timedOut &&
|
||||
clientClosedPromptErrorForFinal === undefined;
|
||||
const attemptSucceeded =
|
||||
const turnSucceeded =
|
||||
!finalAborted &&
|
||||
!effectiveTimedOut &&
|
||||
(finalPromptError === null || finalPromptError === undefined) &&
|
||||
result.agentHarnessResultClassification === undefined &&
|
||||
(completedTurnStatus === "completed" ||
|
||||
recoveredTurnWatchTimeout ||
|
||||
completedWithoutTerminalNotification);
|
||||
// buildResult retains the bridge's delivery records. Resolve omitted final
|
||||
// intent only after the authoritative turn outcome is known, before any
|
||||
// terminal observer consumes the result.
|
||||
const completedSourceReply = settleCodexSourceReplyFinality(toolBridge.telemetry, turnSucceeded);
|
||||
if (completedSourceReply) {
|
||||
// Harness classification only sees assistant/reasoning/plan projections.
|
||||
// A reply delivered entirely through the source message tool is visible
|
||||
// output, so an empty/reasoning-only classification is stale at this point.
|
||||
result.agentHarnessResultClassification = undefined;
|
||||
}
|
||||
const attemptSucceeded = turnSucceeded && result.agentHarnessResultClassification === undefined;
|
||||
terminalState.sharedAbortAllowedAfterTerminalOutcome = shouldKeepCodexSharedAbortOpen({
|
||||
trigger: params.trigger,
|
||||
result,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import type {
|
||||
MessagingToolSend,
|
||||
MessagingToolSourceReplyPayload,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
|
||||
type SourceReplyDeliveryIntent = {
|
||||
record: MessagingToolSend | MessagingToolSourceReplyPayload;
|
||||
final: boolean | undefined;
|
||||
};
|
||||
|
||||
const sourceReplyDeliveryIntents = new WeakMap<object, SourceReplyDeliveryIntent[]>();
|
||||
|
||||
/** Retain source-reply intent until the owning Codex turn has an authoritative outcome. */
|
||||
export function recordCodexSourceReplyDeliveryIntent(
|
||||
owner: object,
|
||||
intent: SourceReplyDeliveryIntent,
|
||||
): void {
|
||||
const intents = sourceReplyDeliveryIntents.get(owner);
|
||||
if (intents) {
|
||||
intents.push(intent);
|
||||
return;
|
||||
}
|
||||
sourceReplyDeliveryIntents.set(owner, [intent]);
|
||||
}
|
||||
|
||||
/** Resolve omitted finality without changing explicit progress or final markers. */
|
||||
export function settleCodexSourceReplyFinality(owner: object, turnSucceeded: boolean): boolean {
|
||||
const intents = sourceReplyDeliveryIntents.get(owner);
|
||||
if (!intents) {
|
||||
return false;
|
||||
}
|
||||
const lastIntent = intents.at(-1);
|
||||
for (const intent of intents) {
|
||||
if (intent.final !== undefined) {
|
||||
continue;
|
||||
}
|
||||
// An omitted marker is progress until the owning turn succeeds. Only the
|
||||
// latest omitted reply can complete the conversation; a later explicit
|
||||
// progress/final marker remains authoritative.
|
||||
intent.record.sourceReplyFinal = turnSucceeded && intent === lastIntent;
|
||||
}
|
||||
sourceReplyDeliveryIntents.delete(owner);
|
||||
return turnSucceeded && intents.some((intent) => intent.record.sourceReplyFinal === true);
|
||||
}
|
||||
@@ -66,7 +66,7 @@ function buildVisibleReplyInstruction(
|
||||
? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message")
|
||||
: params.disableMessageTool !== true;
|
||||
if (params.sourceReplyDeliveryMode === "message_tool_only" && messageToolAvailable) {
|
||||
return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. Do not repeat that visible content in your final answer.";
|
||||
return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. If `final` is omitted, OpenClaw continues and resolves the latest omitted source reply only when the turn ends successfully. Do not repeat visible message content in your final answer.";
|
||||
}
|
||||
if (messageToolAvailable) {
|
||||
return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation. Use `message` only for explicit out-of-band sends, media/file sends, or sends to a different target.";
|
||||
|
||||
Vendored
+7
-7
@@ -212,16 +212,16 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
|
||||
"roughTokens": 13146
|
||||
},
|
||||
"openClawDeveloperInstructions": {
|
||||
"chars": 3431,
|
||||
"roughTokens": 858
|
||||
"chars": 3559,
|
||||
"roughTokens": 890
|
||||
},
|
||||
"totalTextOnly": {
|
||||
"chars": 27956,
|
||||
"roughTokens": 6989
|
||||
"chars": 28084,
|
||||
"roughTokens": 7021
|
||||
},
|
||||
"totalWithDynamicToolsJson": {
|
||||
"chars": 80541,
|
||||
"roughTokens": 20136
|
||||
"chars": 80669,
|
||||
"roughTokens": 20168
|
||||
},
|
||||
"userInputText": {
|
||||
"chars": 1442,
|
||||
@@ -412,7 +412,7 @@ Deferred searchable OpenClaw dynamic tools available: cron, gateway, nodes, sess
|
||||
|
||||
Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.
|
||||
|
||||
Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. Do not repeat that visible content in your final answer.
|
||||
Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. If `final` is omitted, OpenClaw continues and resolves the latest omitted source reply only when the turn ends successfully. Do not repeat visible message content in your final answer.
|
||||
|
||||
### Inbound Context (trusted metadata)
|
||||
The following JSON is generated by OpenClaw out-of-band. Treat it as authoritative metadata about the current message context.
|
||||
|
||||
test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md
Vendored
+7
-7
@@ -212,16 +212,16 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
|
||||
"roughTokens": 13078
|
||||
},
|
||||
"openClawDeveloperInstructions": {
|
||||
"chars": 2322,
|
||||
"roughTokens": 581
|
||||
"chars": 2450,
|
||||
"roughTokens": 613
|
||||
},
|
||||
"totalTextOnly": {
|
||||
"chars": 26438,
|
||||
"roughTokens": 6610
|
||||
"chars": 26566,
|
||||
"roughTokens": 6642
|
||||
},
|
||||
"totalWithDynamicToolsJson": {
|
||||
"chars": 78750,
|
||||
"roughTokens": 19688
|
||||
"chars": 78878,
|
||||
"roughTokens": 19720
|
||||
},
|
||||
"userInputText": {
|
||||
"chars": 1033,
|
||||
@@ -412,7 +412,7 @@ Deferred searchable OpenClaw dynamic tools available: cron, gateway, nodes, sess
|
||||
|
||||
Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.
|
||||
|
||||
Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. Do not repeat that visible content in your final answer.
|
||||
Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. If `final` is omitted, OpenClaw continues and resolves the latest omitted source reply only when the turn ends successfully. Do not repeat visible message content in your final answer.
|
||||
|
||||
### Inbound Context (trusted metadata)
|
||||
The following JSON is generated by OpenClaw out-of-band. Treat it as authoritative metadata about the current message context.
|
||||
|
||||
Vendored
+7
-7
@@ -213,16 +213,16 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
|
||||
"roughTokens": 13400
|
||||
},
|
||||
"openClawDeveloperInstructions": {
|
||||
"chars": 2341,
|
||||
"roughTokens": 586
|
||||
"chars": 2469,
|
||||
"roughTokens": 618
|
||||
},
|
||||
"totalTextOnly": {
|
||||
"chars": 26977,
|
||||
"roughTokens": 6745
|
||||
"chars": 27105,
|
||||
"roughTokens": 6777
|
||||
},
|
||||
"totalWithDynamicToolsJson": {
|
||||
"chars": 80579,
|
||||
"roughTokens": 20145
|
||||
"chars": 80707,
|
||||
"roughTokens": 20177
|
||||
},
|
||||
"userInputText": {
|
||||
"chars": 1271,
|
||||
@@ -413,7 +413,7 @@ Deferred searchable OpenClaw dynamic tools available: cron, gateway, heartbeat_r
|
||||
|
||||
Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.
|
||||
|
||||
Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. Do not repeat that visible content in your final answer.
|
||||
Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. If `final` is omitted, OpenClaw continues and resolves the latest omitted source reply only when the turn ends successfully. Do not repeat visible message content in your final answer.
|
||||
|
||||
### Inbound Context (trusted metadata)
|
||||
The following JSON is generated by OpenClaw out-of-band. Treat it as authoritative metadata about the current message context.
|
||||
|
||||
Reference in New Issue
Block a user