refactor(ci): restore LOC ratchet headroom (#106063)

* refactor(ci): restore TypeScript LOC ratchet

* fix(ci): keep source reply item type internal
This commit is contained in:
Peter Steinberger
2026-07-12 23:09:25 -07:00
committed by GitHub
parent 9cdf166d2e
commit 6410e33cb0
14 changed files with 270 additions and 209 deletions
@@ -0,0 +1,37 @@
import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness-runtime";
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
import { asOptionalRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
/** Resolves an agent override before falling back to the configured default. */
export function resolveAgentContextLimitValue(params: {
config: EmbeddedRunAttemptParams["config"] | undefined;
agentId?: string;
key: string;
}): number | undefined {
const agents = readRecord(params.config?.agents);
const defaults = readRecord(readRecord(agents?.defaults)?.contextLimits);
const defaultValue = readPositiveInteger(defaults?.[params.key]);
if (!params.agentId) {
return defaultValue;
}
const list = agents?.list;
if (!Array.isArray(list)) {
return defaultValue;
}
const normalizedAgentId = normalizeAgentId(params.agentId);
const agent = list.find((entry) => {
const entryId = readRecord(entry)?.id;
return typeof entryId === "string" && normalizeAgentId(entryId) === normalizedAgentId;
});
const agentValue = readPositiveInteger(
readRecord(readRecord(agent)?.contextLimits)?.[params.key],
);
return agentValue ?? defaultValue;
}
function readPositiveInteger(value: unknown): number | undefined {
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
return undefined;
}
return Math.floor(value);
}
@@ -28,7 +28,9 @@ import {
isForcedPrivateQaCodexRuntime,
normalizeCodexDynamicToolName,
} from "./dynamic-tool-profile.js";
import { addCodexMessageToolOnlyFinalControl } from "./message-tool-final-control.js";
import {
resolveCodexNodeExecToolOverrides,
resolveCodexNativeExecutionPolicy,
type CodexNativeExecutionPolicy,
} from "./native-execution-policy.js";
@@ -40,7 +42,6 @@ import { resolveCodexWebSearchPlan, type CodexNativeWebSearchSupport } from "./w
type OpenClawCodingToolsOptions = NonNullable<
Parameters<(typeof import("openclaw/plugin-sdk/agent-harness"))["createOpenClawCodingTools"]>[0]
>;
type OpenClawExecOptions = NonNullable<OpenClawCodingToolsOptions["exec"]>;
/** Factory seam for constructing OpenClaw runtime tools without eagerly loading agent-harness. */
type OpenClawCodingToolsFactory =
@@ -231,7 +232,7 @@ export async function buildDynamicTools(input: DynamicToolBuildParams) {
...buildEmbeddedAttemptToolRunContext(params),
exec: {
...params.execOverrides,
...resolveNodeExecToolOverrides(nativeExecutionPolicy),
...resolveCodexNodeExecToolOverrides(nativeExecutionPolicy),
config: params.config,
elevated: params.bashElevated,
},
@@ -881,51 +882,6 @@ function hideNodeExecDynamicToolParameters(
...(Array.isArray(rawRequired) ? { required: nextRequired } : {}),
};
}
/**
* `final` is a Codex-only control for message-tool-only source delivery. Keep
* it on the projected Codex schema so other agent runtimes never receive an
* API contract they do not implement.
*/
function addCodexMessageToolOnlyFinalControl(
tools: OpenClawDynamicTool[],
sourceReplyDeliveryMode: EmbeddedRunAttemptParams["sourceReplyDeliveryMode"],
): OpenClawDynamicTool[] {
if (sourceReplyDeliveryMode !== "message_tool_only") {
return tools;
}
// allTools is attempt-fresh from createOpenClawCodingTools inside
// buildDynamicTools — never a shared/cached instance across attempts or
// delivery modes. Project the Codex-only `final` property in place so
// WeakMap ownership metadata stays attached without a public SDK clone helper.
for (const tool of tools) {
if (normalizeCodexDynamicToolName(tool.name) !== "message") {
continue;
}
tool.parameters = addCodexMessageToolOnlyFinalParameter(tool.parameters);
}
return tools;
}
function addCodexMessageToolOnlyFinalParameter(parameters: OpenClawDynamicTool["parameters"]) {
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) {
return parameters;
}
const schema = parameters as Record<string, unknown>;
const rawProperties = schema.properties;
if (!rawProperties || typeof rawProperties !== "object" || Array.isArray(rawProperties)) {
return parameters;
}
return {
...schema,
properties: {
...rawProperties,
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.",
},
},
};
}
function resolveCodexNativeExecutionPolicyForDynamicTools(
input: DynamicToolBuildParams,
): CodexNativeExecutionPolicy {
@@ -939,18 +895,6 @@ function resolveCodexNativeExecutionPolicyForDynamicTools(
readRuntimeSessionEntry: true,
});
}
function resolveNodeExecToolOverrides(
policy: CodexNativeExecutionPolicy,
): Pick<OpenClawExecOptions, "host" | "node"> | undefined {
if (policy.effectiveExecHost !== "node") {
return undefined;
}
const node = policy.node?.trim();
return {
host: "node",
...(node ? { node } : {}),
};
}
/** Applies a normalized tool allowlist while preserving shell aliases for exec/process. */
function filterCodexDynamicToolsForAllowlist<T extends { name: string }>(
tools: T[],
@@ -44,12 +44,9 @@ import {
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
import type { ImageContent, TextContent } from "openclaw/plugin-sdk/llm";
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
import {
asOptionalRecord as readRecord,
isRecord,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
import { resolveAgentContextLimitValue } from "./agent-context-limits.js";
import type { CodexDynamicToolsLoading } from "./config.js";
import { invalidInlineImageText, sanitizeInlineImageDataUrl } from "./image-payload-sanitizer.js";
import {
@@ -1123,31 +1120,6 @@ function resolveCodexDynamicToolResultMaxChars(
});
return configured ?? DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS;
}
function resolveAgentContextLimitValue(params: {
config: EmbeddedRunAttemptParams["config"] | undefined;
agentId?: string;
key: string;
}): number | undefined {
const agents = readRecord(params.config?.agents);
const defaults = readRecord(readRecord(agents?.defaults)?.contextLimits);
const defaultValue = readPositiveInteger(defaults?.[params.key]);
if (!params.agentId) {
return defaultValue;
}
const list = agents?.list;
if (!Array.isArray(list)) {
return defaultValue;
}
const normalizedAgentId = normalizeAgentId(params.agentId);
const agent = list.find((entry) => {
const entryId = readRecord(entry)?.id;
return typeof entryId === "string" && normalizeAgentId(entryId) === normalizedAgentId;
});
const agentValue = readPositiveInteger(
readRecord(readRecord(agent)?.contextLimits)?.[params.key],
);
return agentValue ?? defaultValue;
}
function composeAbortSignals(...signals: Array<AbortSignal | undefined>): AbortSignal {
const activeSignals = signals.filter((signal): signal is AbortSignal => Boolean(signal));
if (activeSignals.length === 0) {
@@ -1283,12 +1255,6 @@ function extractInternalSourceReplyPayload(
? payload
: undefined;
}
function readPositiveInteger(value: unknown): number | undefined {
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
return undefined;
}
return Math.floor(value);
}
function isCodexToolResultError(result: AgentToolResult<unknown>): boolean {
if (isToolResultError(result)) {
return true;
@@ -0,0 +1,52 @@
import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness-runtime";
import { normalizeCodexDynamicToolName } from "./dynamic-tool-profile.js";
type MutableDynamicTool = {
name: string;
parameters?: unknown;
};
/**
* `final` is a Codex-only control for message-tool-only source delivery. Keep
* it on the projected Codex schema so other agent runtimes never receive an
* API contract they do not implement.
*/
export function addCodexMessageToolOnlyFinalControl<T extends MutableDynamicTool>(
tools: T[],
sourceReplyDeliveryMode: EmbeddedRunAttemptParams["sourceReplyDeliveryMode"],
): T[] {
if (sourceReplyDeliveryMode !== "message_tool_only") {
return tools;
}
// These tools are attempt-fresh. Mutating preserves their WeakMap ownership
// metadata without exposing a clone helper through the public plugin SDK.
for (const tool of tools) {
if (normalizeCodexDynamicToolName(tool.name) === "message") {
const mutableTool: MutableDynamicTool = tool;
mutableTool.parameters = addCodexMessageToolOnlyFinalParameter(mutableTool.parameters);
}
}
return tools;
}
function addCodexMessageToolOnlyFinalParameter(parameters: unknown): unknown {
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) {
return parameters;
}
const schema = parameters as Record<string, unknown>;
const rawProperties = schema.properties;
if (!rawProperties || typeof rawProperties !== "object" || Array.isArray(rawProperties)) {
return parameters;
}
return {
...schema,
properties: {
...rawProperties,
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.",
},
},
};
}
@@ -31,6 +31,17 @@ export type CodexNativeExecutionPolicy = {
blockReason?: string;
};
/** Projects node execution ownership into the runtime tool factory options. */
export function resolveCodexNodeExecToolOverrides(
policy: CodexNativeExecutionPolicy,
): { host: "node"; node?: string } | undefined {
if (policy.effectiveExecHost !== "node") {
return undefined;
}
const node = policy.node?.trim();
return { host: "node", ...(node ? { node } : {}) };
}
/** Resolves node/gateway/sandbox execution ownership from overrides, session, agent, and config. */
export function resolveCodexNativeExecutionPolicy(params: {
config?: OpenClawConfig;
+7 -7
View File
@@ -45,9 +45,9 @@
"extensions/codex/src/app-server/computer-use.ts": 1264,
"extensions/codex/src/app-server/config.ts": 2492,
"extensions/codex/src/app-server/context-engine-projection.ts": 514,
"extensions/codex/src/app-server/dynamic-tool-build.ts": 992,
"extensions/codex/src/app-server/dynamic-tool-build.ts": 936,
"extensions/codex/src/app-server/dynamic-tool-execution.ts": 583,
"extensions/codex/src/app-server/dynamic-tools.ts": 1550,
"extensions/codex/src/app-server/dynamic-tools.ts": 1516,
"extensions/codex/src/app-server/elicitation-bridge.ts": 993,
"extensions/codex/src/app-server/event-projector.ts": 3499,
"extensions/codex/src/app-server/native-subagent-monitor.ts": 1631,
@@ -551,7 +551,7 @@
"src/agents/embedded-agent-runner/model.ts": 1995,
"src/agents/embedded-agent-runner/replay-history.ts": 937,
"src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts": 953,
"src/agents/embedded-agent-runner/run.ts": 5082,
"src/agents/embedded-agent-runner/run.ts": 5081,
"src/agents/embedded-agent-runner/run/attempt.llm-boundary.ts": 637,
"src/agents/embedded-agent-runner/run/attempt.model-diagnostic-events.ts": 894,
"src/agents/embedded-agent-runner/run/attempt.prompt-helpers.ts": 721,
@@ -563,7 +563,7 @@
"src/agents/embedded-agent-runner/run/images.ts": 665,
"src/agents/embedded-agent-runner/run/incomplete-turn.ts": 823,
"src/agents/embedded-agent-runner/run/llm-idle-timeout.ts": 615,
"src/agents/embedded-agent-runner/run/payloads.ts": 1018,
"src/agents/embedded-agent-runner/run/payloads.ts": 968,
"src/agents/embedded-agent-runner/runs.ts": 950,
"src/agents/embedded-agent-runner/thinking.ts": 767,
"src/agents/embedded-agent-runner/tool-result-context-guard.ts": 573,
@@ -653,7 +653,7 @@
"src/agents/tools/image-tool.ts": 1095,
"src/agents/tools/media-generate-background-shared.ts": 865,
"src/agents/tools/media-tool-shared.ts": 689,
"src/agents/tools/message-tool.ts": 1756,
"src/agents/tools/message-tool.ts": 1733,
"src/agents/tools/music-generate-tool.ts": 853,
"src/agents/tools/pdf-tool.ts": 563,
"src/agents/tools/session-status-tool.ts": 934,
@@ -677,7 +677,7 @@
"src/auto-reply/reply/agent-runner-execution.ts": 3419,
"src/auto-reply/reply/agent-runner-memory.ts": 1586,
"src/auto-reply/reply/agent-runner-payloads.ts": 514,
"src/auto-reply/reply/agent-runner.ts": 2858,
"src/auto-reply/reply/agent-runner.ts": 2853,
"src/auto-reply/reply/commands-acp/lifecycle.ts": 895,
"src/auto-reply/reply/commands-acp/shared.ts": 539,
"src/auto-reply/reply/commands-allowlist.ts": 607,
@@ -693,7 +693,7 @@
"src/auto-reply/reply/dispatch-acp-delivery.ts": 542,
"src/auto-reply/reply/dispatch-acp.ts": 812,
"src/auto-reply/reply/dispatch-from-config.ts": 4562,
"src/auto-reply/reply/followup-runner.ts": 2065,
"src/auto-reply/reply/followup-runner.ts": 2059,
"src/auto-reply/reply/get-reply-directives-apply.ts": 537,
"src/auto-reply/reply/get-reply-directives.ts": 738,
"src/auto-reply/reply/get-reply-inline-actions.ts": 641,
@@ -30,6 +30,7 @@ type AgentDeliveryEvidence = {
errorMessage?: unknown;
};
didSendViaMessagingTool?: unknown;
didSendDeterministicApprovalPrompt?: unknown;
messagingToolSentTexts?: unknown;
messagingToolSentMediaUrls?: unknown;
messagingToolSentTargets?: unknown;
@@ -86,6 +87,18 @@ export function hasCompletedSourceReplyDeliveryEvidence(
);
}
/** Returns whether delivery evidence completes the current interactive turn. */
export function hasCompletedTerminalDeliveryEvidence(
result: AgentDeliveryEvidence & SourceReplyDeliveryEvidence & ExplicitFinalSourceReplyEvidence,
): boolean {
const explicitFinal = resolveExplicitFinalSourceReplyDeliveryEvidence(result);
return (
hasCompletedSourceReplyDeliveryEvidence(result) ||
(explicitFinal === undefined && hasVisibleOutboundDeliveryEvidence(result)) ||
result.didSendDeterministicApprovalPrompt === true
);
}
function hasNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
}
-1
View File
@@ -4317,7 +4317,6 @@ async function runEmbeddedAgentInternal(
};
const finalAssistantVisibleText = resolveFinalAssistantVisibleText(attemptAssistant);
const finalAssistantRawText = resolveFinalAssistantRawText(attemptAssistant);
const payloads = buildEmbeddedRunPayloads({
assistantTexts: attempt.assistantTexts,
assistantMessageIndex: attempt.lastAssistantTextMessageIndex,
@@ -54,7 +54,7 @@ import {
} from "../../embedded-agent-utils.js";
import { isExecLikeToolName, type ToolErrorSummary } from "../../tool-error-summary.js";
import { isLikelyMutatingToolName } from "../../tool-mutation.js";
import { resolveExplicitFinalSourceReplyDeliveryEvidence } from "../delivery-evidence.js";
import { buildSourceReplyPayloadState } from "./source-reply-payloads.js";
type ToolMetaEntry = { toolName: string; meta?: string };
type ToolErrorWarningPolicy = {
@@ -586,71 +586,21 @@ export function buildEmbeddedRunPayloads(params: {
if (params.heartbeatToolResponse) {
return [createHeartbeatToolResponsePayload(params.heartbeatToolResponse)];
}
const replyItems: Array<{
text: string;
media?: string[];
mediaUrl?: string;
isError?: boolean;
isReasoning?: boolean;
/** Marks pre-tool commentary (💬) — a display lane, suppressed unless the channel opts in. */
isCommentary?: boolean;
audioAsVoice?: boolean;
replyToId?: string;
replyToTag?: boolean;
replyToCurrent?: boolean;
presentation?: ReplyPayload["presentation"];
interactive?: ReplyPayload["interactive"];
channelData?: Record<string, unknown>;
nonTerminalToolErrorWarning?: boolean;
sourceReplyMirror?: {
idempotencyKey?: string;
};
}> = [];
// Internal source replies always need transcript/UI mirror payloads. Only a
// Internal source replies always need transcript/UI mirrors. Only a
// message_tool_only run suppresses the separate automatic final answer.
const sourceReplyPayloads = params.messagingToolSourceReplyPayloads ?? [];
const sourceReplyStartIndex = replyItems.length;
sourceReplyPayloads.forEach((payload, index) => {
const text = normalizeOptionalString(payload.text) ?? "";
const media = Array.from(
new Set([...(payload.mediaUrl ? [payload.mediaUrl] : []), ...(payload.mediaUrls ?? [])]),
).filter((value) => value.trim().length > 0);
if (
!text &&
media.length === 0 &&
!payload.presentation &&
!payload.interactive &&
!payload.channelData
) {
return;
}
// Message-tool-only replies were already sent by the tool. Mirror them into
// the transcript while marking payloads so channel delivery suppresses a duplicate send.
replyItems.push({
text,
...(payload.mediaUrl ? { mediaUrl: payload.mediaUrl } : {}),
...(media.length ? { media } : {}),
...(payload.audioAsVoice ? { audioAsVoice: true } : {}),
...(payload.presentation ? { presentation: payload.presentation } : {}),
...(payload.interactive ? { interactive: payload.interactive } : {}),
...(payload.channelData ? { channelData: payload.channelData } : {}),
sourceReplyMirror: {
idempotencyKey:
payload.idempotencyKey ??
(params.runId ? `${params.runId}:internal-source-reply:${index}` : undefined),
},
});
const {
replyItems,
hasSourceReplyPayload,
deliveredSourceReplyViaMessageTool,
explicitFinalSourceReply,
completedSourceReplyViaMessageTool,
} = buildSourceReplyPayloadState({
payloads: params.messagingToolSourceReplyPayloads,
sentTargets: params.messagingToolSentTargets,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
didDeliverSourceReplyViaMessageTool: params.didDeliverSourceReplyViaMessageTool,
runId: params.runId,
});
const hasSourceReplyPayload = replyItems.length > sourceReplyStartIndex;
const deliveredSourceReplyViaMessageTool =
params.sourceReplyDeliveryMode === "message_tool_only" &&
params.didDeliverSourceReplyViaMessageTool === true;
const explicitFinalSourceReply = resolveExplicitFinalSourceReplyDeliveryEvidence({
messagingToolSentTargets: params.messagingToolSentTargets,
messagingToolSourceReplyPayloads: sourceReplyPayloads,
});
const completedSourceReplyViaMessageTool =
explicitFinalSourceReply ?? (hasSourceReplyPayload || deliveredSourceReplyViaMessageTool);
const useMarkdown = params.toolResultFormat === "markdown";
const suppressAssistantArtifacts =
params.didSendDeterministicApprovalPrompt === true ||
@@ -0,0 +1,93 @@
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import type { SourceReplyDeliveryMode } from "../../../auto-reply/get-reply-options.types.js";
import type { ReplyPayload } from "../../../auto-reply/reply-payload.js";
import type {
MessagingToolSend,
MessagingToolSourceReplyPayload,
} from "../../embedded-agent-messaging.types.js";
import { resolveExplicitFinalSourceReplyDeliveryEvidence } from "../delivery-evidence.js";
type EmbeddedRunReplyItem = {
text: string;
media?: string[];
mediaUrl?: string;
isError?: boolean;
isReasoning?: boolean;
/** Marks pre-tool commentary (💬) — a display lane, suppressed unless the channel opts in. */
isCommentary?: boolean;
audioAsVoice?: boolean;
replyToId?: string;
replyToTag?: boolean;
replyToCurrent?: boolean;
presentation?: ReplyPayload["presentation"];
interactive?: ReplyPayload["interactive"];
channelData?: Record<string, unknown>;
nonTerminalToolErrorWarning?: boolean;
sourceReplyMirror?: { idempotencyKey?: string };
};
/** Builds transcript mirrors and completion evidence for message-tool source replies. */
export function buildSourceReplyPayloadState(params: {
payloads?: MessagingToolSourceReplyPayload[];
sentTargets?: MessagingToolSend[];
sourceReplyDeliveryMode?: SourceReplyDeliveryMode;
didDeliverSourceReplyViaMessageTool?: boolean;
runId?: string;
}): {
replyItems: EmbeddedRunReplyItem[];
hasSourceReplyPayload: boolean;
deliveredSourceReplyViaMessageTool: boolean;
explicitFinalSourceReply: boolean | undefined;
completedSourceReplyViaMessageTool: boolean;
} {
const sourceReplyPayloads = params.payloads ?? [];
const replyItems = sourceReplyPayloads.flatMap((payload, index): EmbeddedRunReplyItem[] => {
const text = normalizeOptionalString(payload.text) ?? "";
const media = Array.from(
new Set([...(payload.mediaUrl ? [payload.mediaUrl] : []), ...(payload.mediaUrls ?? [])]),
).filter((value) => value.trim().length > 0);
if (
!text &&
media.length === 0 &&
!payload.presentation &&
!payload.interactive &&
!payload.channelData
) {
return [];
}
// These replies were already sent by the tool. Mirror them into the
// transcript while marking channel delivery to suppress a duplicate send.
return [
{
text,
...(payload.mediaUrl ? { mediaUrl: payload.mediaUrl } : {}),
...(media.length ? { media } : {}),
...(payload.audioAsVoice ? { audioAsVoice: true } : {}),
...(payload.presentation ? { presentation: payload.presentation } : {}),
...(payload.interactive ? { interactive: payload.interactive } : {}),
...(payload.channelData ? { channelData: payload.channelData } : {}),
sourceReplyMirror: {
idempotencyKey:
payload.idempotencyKey ??
(params.runId ? `${params.runId}:internal-source-reply:${index}` : undefined),
},
},
];
});
const hasSourceReplyPayload = replyItems.length > 0;
const deliveredSourceReplyViaMessageTool =
params.sourceReplyDeliveryMode === "message_tool_only" &&
params.didDeliverSourceReplyViaMessageTool === true;
const explicitFinalSourceReply = resolveExplicitFinalSourceReplyDeliveryEvidence({
messagingToolSentTargets: params.sentTargets,
messagingToolSourceReplyPayloads: sourceReplyPayloads,
});
return {
replyItems,
hasSourceReplyPayload,
deliveredSourceReplyViaMessageTool,
explicitFinalSourceReply,
completedSourceReplyViaMessageTool:
explicitFinalSourceReply ?? (hasSourceReplyPayload || deliveredSourceReplyViaMessageTool),
};
}
@@ -0,0 +1,30 @@
import type { SourceReplyDeliveryMode } from "../../auto-reply/get-reply-options.types.js";
import type { ChannelMessageActionName } from "../../channels/plugins/types.public.js";
const MESSAGE_TOOL_THREAD_READ_HINT = ' Missing thread context: action="read" + threadId.';
export function appendMessageToolVisibleReplyHint(
description: string,
sourceReplyDeliveryMode?: SourceReplyDeliveryMode,
requireExplicitTarget?: boolean,
): string {
if (sourceReplyDeliveryMode !== "message_tool_only") {
return description;
}
const targetGuidance = requireExplicitTarget
? "send needs target."
: "target defaults current source; set only elsewhere.";
return `${description} This turn visible reply: action="send" + message; ${targetGuidance} Final answer private.`;
}
export function appendMessageToolReadHint(
description: string,
actions: Iterable<ChannelMessageActionName | "send">,
): string {
for (const action of actions) {
if (action === "read") {
return `${description}${MESSAGE_TOOL_THREAD_READ_HINT}`;
}
}
return description;
}
+4 -27
View File
@@ -88,10 +88,13 @@ import {
resolveMessageActionAgentRuntimeIdentityToken,
type GatewayCallOptions,
} from "./gateway.js";
import {
appendMessageToolReadHint,
appendMessageToolVisibleReplyHint,
} from "./message-tool-description.js";
import { isPollVoteEchoText } from "./poll-vote-echo.js";
const AllMessageActions = CHANNEL_MESSAGE_ACTION_NAMES;
const MESSAGE_TOOL_THREAD_READ_HINT = ' Missing thread context: action="read" + threadId.';
function actionNeedsExplicitTarget(action: ChannelMessageActionName): boolean {
return action === "broadcast" || shouldApplyCrossContextMarker(action);
}
@@ -1335,32 +1338,6 @@ function buildMessageToolDescription(options?: {
);
}
function appendMessageToolVisibleReplyHint(
description: string,
sourceReplyDeliveryMode?: SourceReplyDeliveryMode,
requireExplicitTarget?: boolean,
): string {
if (sourceReplyDeliveryMode !== "message_tool_only") {
return description;
}
const targetGuidance = requireExplicitTarget
? "send needs target."
: "target defaults current source; set only elsewhere.";
return `${description} This turn visible reply: action="send" + message; ${targetGuidance} Final answer private.`;
}
function appendMessageToolReadHint(
description: string,
actions: Iterable<ChannelMessageActionName | "send">,
): string {
for (const action of actions) {
if (action === "read") {
return `${description}${MESSAGE_TOOL_THREAD_READ_HINT}`;
}
}
return description;
}
export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
const loadConfigForTool = options?.getRuntimeConfig ?? getRuntimeConfig;
const getScopedSecretTargetsForTool =
+2 -7
View File
@@ -13,10 +13,10 @@ import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
import { isLikelyContextOverflowError } from "../../agents/embedded-agent-helpers/errors.js";
import {
hasCompletedSourceReplyDeliveryEvidence,
hasCompletedTerminalDeliveryEvidence,
hasCommittedSourceReplyDeliveryEvidence,
hasVisibleCommittedMessagingToolDeliveryEvidence,
hasVisibleOutboundDeliveryEvidence,
resolveExplicitFinalSourceReplyDeliveryEvidence,
} from "../../agents/embedded-agent-runner/delivery-evidence.js";
import { hasDeliberateSilentTerminalReply } from "../../agents/embedded-agent-runner/result-fallback-classifier.js";
import {
@@ -2011,8 +2011,6 @@ export async function runReplyAgent(params: {
const committedMessagingToolSourceReplyDelivery =
hasCommittedSourceReplyDeliveryEvidence(runResult);
const completedSourceReplyDelivery = hasCompletedSourceReplyDeliveryEvidence(runResult);
const hasExplicitSourceReplyCompletion =
resolveExplicitFinalSourceReplyDeliveryEvidence(runResult) !== undefined;
const visibleOutboundDelivery = hasVisibleOutboundDeliveryEvidence(runResult);
const successfulSideEffectDelivery =
successfulSourceReplyDelivery ||
@@ -2023,10 +2021,7 @@ export async function runReplyAgent(params: {
hasSuccessfulTerminalSourceReplyDelivery({
blockReplyPipeline,
directlySentBlockPayloads,
}) ||
completedSourceReplyDelivery ||
(!hasExplicitSourceReplyCompletion && visibleOutboundDelivery) ||
runResult.didSendDeterministicApprovalPrompt === true;
}) || hasCompletedTerminalDeliveryEvidence(runResult);
// Compaction notices are progress, not a terminal reply. Dispatcher-backed
// delivery settles after this run returns, so it cannot prove turn completion here.
const shouldDeliverTerminalFailure = Boolean(
+2 -8
View File
@@ -14,9 +14,9 @@ import { resolveContextTokensForModel } from "../../agents/context.js";
import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
import {
hasCompletedSourceReplyDeliveryEvidence,
hasCompletedTerminalDeliveryEvidence,
hasCommittedSourceReplyDeliveryEvidence,
hasVisibleOutboundDeliveryEvidence,
resolveExplicitFinalSourceReplyDeliveryEvidence,
} from "../../agents/embedded-agent-runner/delivery-evidence.js";
import {
hasDeliberateSilentTerminalReply,
@@ -1801,13 +1801,7 @@ export function createFollowupRunner(params: {
hasVisibleOutboundDeliveryEvidence(runResult) ||
hasCommittedSourceReplyDeliveryEvidence(runResult) ||
runResult.didSendDeterministicApprovalPrompt === true;
const completedSourceReplyDelivery = hasCompletedSourceReplyDeliveryEvidence(runResult);
const hasExplicitSourceReplyCompletion =
resolveExplicitFinalSourceReplyDeliveryEvidence(runResult) !== undefined;
const hasCompletedTerminalDelivery =
completedSourceReplyDelivery ||
(!hasExplicitSourceReplyCompletion && hasVisibleOutboundDeliveryEvidence(runResult)) ||
runResult.didSendDeterministicApprovalPrompt === true;
const hasCompletedTerminalDelivery = hasCompletedTerminalDeliveryEvidence(runResult);
const hasDeliveryDestination = Boolean(
(isRoutableChannel(queued.originatingChannel) && queued.originatingTo) ||
opts?.onBlockReply,