diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index 4901fbf34e5..8389aee398f 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -107,7 +107,11 @@ export async function prepareCliRunContext( authCredential = authStore.profiles[effectiveAuthProfileId]; } const extraSystemPrompt = params.extraSystemPrompt?.trim() ?? ""; - const extraSystemPromptHash = hashCliSessionText(extraSystemPrompt); + // Use the static portion (excluding per-message inbound metadata) for session reuse hashing. + // Per-message metadata (timestamps, message IDs) changes every turn and must not trigger session resets. + const extraSystemPromptHash = hashCliSessionText(params.extraSystemPromptStatic?.trim() || undefined) ?? hashCliSessionText(extraSystemPrompt); + + const modelId = (params.model ?? "default").trim() || "default"; const normalizedModel = normalizeCliModel(modelId, backendResolved.config); const modelDisplay = `${params.provider}/${modelId}`; diff --git a/src/agents/cli-runner/types.ts b/src/agents/cli-runner/types.ts index ecae4de5e3e..aa3bfa13322 100644 --- a/src/agents/cli-runner/types.ts +++ b/src/agents/cli-runner/types.ts @@ -23,6 +23,8 @@ export type RunCliAgentParams = { timeoutMs: number; runId: string; extraSystemPrompt?: string; + /** Static portion of extraSystemPrompt (excluding per-message inbound metadata) for session reuse hashing. */ + extraSystemPromptStatic?: string; streamParams?: import("../command/types.js").AgentStreamParams; ownerNumbers?: string[]; cliSessionId?: string; diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index f2cc3554f5b..24cc9887a4f 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -305,6 +305,18 @@ export async function runPreparedReply( fullAccessBlockedReason: fullAccessState.blockedReason, }), ].filter(Boolean); + // Static parts only (no per-message inbound metadata) for CLI session reuse hashing. + const extraSystemPromptStaticParts = [ + groupChatContext, + groupIntro, + groupSystemPrompt, + buildExecOverridePromptHint({ + execOverrides, + elevatedLevel: resolvedElevatedLevel, + fullAccessAvailable: fullAccessState.available, + fullAccessBlockedReason: fullAccessState.blockedReason, + }), + ].filter(Boolean); const baseBody = sessionCtx.BodyStripped ?? sessionCtx.Body ?? ""; // Use CommandBody/RawBody for bare reset detection (clean message without structural context). const rawBodyTrimmed = (ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "").trim(); @@ -734,6 +746,7 @@ export async function runPreparedReply( ownerNumbers: command.ownerList.length > 0 ? command.ownerList : undefined, inputProvenance: ctx.InputProvenance ?? sessionCtx.InputProvenance, extraSystemPrompt: extraSystemPromptParts.join("\n\n") || undefined, + extraSystemPromptStatic: extraSystemPromptStaticParts.join("\n\n") || undefined, skipProviderRuntimeHints: useFastReplyRuntime, ...(!useFastReplyRuntime && isReasoningTagProvider(provider, {