From 1189b96d421900714a5061a025adf89fe02b4ab9 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 13 May 2026 13:25:01 +0900 Subject: [PATCH] fix(continuation): mark atlas resumes synthetic --- .../boulder-continuation-injector.test.ts | 9 +++++++ .../atlas/boulder-continuation-injector.ts | 4 +-- src/hooks/atlas/idle-event.test.ts | 25 +++++++++++++++++-- src/hooks/atlas/idle-event.ts | 4 +-- src/hooks/session-recovery/resume.test.ts | 9 ++++++- src/hooks/session-recovery/resume.ts | 4 +-- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/hooks/atlas/boulder-continuation-injector.test.ts b/src/hooks/atlas/boulder-continuation-injector.test.ts index 902bfe78f..b04e03915 100644 --- a/src/hooks/atlas/boulder-continuation-injector.test.ts +++ b/src/hooks/atlas/boulder-continuation-injector.test.ts @@ -168,6 +168,11 @@ describe("injectBoulderContinuation", () => { body?: { model?: { providerID: string; modelID: string } variant?: string + noReply?: boolean + parts?: Array<{ + synthetic?: boolean + metadata?: Record + }> } }> = [] const promptAsyncMock = mock(async (request: unknown) => { @@ -219,5 +224,9 @@ describe("injectBoulderContinuation", () => { modelID: "claude-sonnet-4-20250514", }) expect(capturedRequests[0]?.body?.variant).toBe("max") + expect(capturedRequests[0]?.body?.noReply).toBeUndefined() + const promptPart = capturedRequests[0]?.body?.parts?.[0] + expect(promptPart?.synthetic).toBe(true) + expect(promptPart?.metadata?.compaction_continue).toBe(true) }) }) diff --git a/src/hooks/atlas/boulder-continuation-injector.ts b/src/hooks/atlas/boulder-continuation-injector.ts index 8fc401b06..6037351de 100644 --- a/src/hooks/atlas/boulder-continuation-injector.ts +++ b/src/hooks/atlas/boulder-continuation-injector.ts @@ -4,7 +4,7 @@ import { resolveRegisteredAgentName, } from "../../features/claude-code-session-state" import { log } from "../../shared/logger" -import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared" +import { createInternalAgentContinuationTextPart, resolveInheritedPromptTools } from "../../shared" import { HOOK_NAME } from "./hook-name" import { BOULDER_CONTINUATION_PROMPT } from "./system-reminder-templates" import { resolveRecentPromptContextForSession } from "./recent-model-resolver" @@ -89,7 +89,7 @@ export async function injectBoulderContinuation(input: { ...(launchModel ? { model: launchModel } : {}), ...(launchVariant ? { variant: launchVariant } : {}), ...(inheritedTools ? { tools: inheritedTools } : {}), - parts: [createInternalAgentTextPart(prompt)], + parts: [createInternalAgentContinuationTextPart(prompt)], }, query: { directory: ctx.directory }, }) diff --git a/src/hooks/atlas/idle-event.test.ts b/src/hooks/atlas/idle-event.test.ts index 61f9204bf..ae3971b27 100644 --- a/src/hooks/atlas/idle-event.test.ts +++ b/src/hooks/atlas/idle-event.test.ts @@ -71,8 +71,26 @@ describe("handleAtlasSessionIdle completion nudge", () => { writeBoulderState(testDirectory, boulder) - const promptRequests: Array<{ body?: { parts?: Array<{ text?: string }> } }> = [] - const promptAsyncMock = mock(async (request: { body?: { parts?: Array<{ text?: string }> } }) => { + const promptRequests: Array<{ + body?: { + noReply?: boolean + parts?: Array<{ + text?: string + synthetic?: boolean + metadata?: Record + }> + } + }> = [] + const promptAsyncMock = mock(async (request: { + body?: { + noReply?: boolean + parts?: Array<{ + text?: string + synthetic?: boolean + metadata?: Record + }> + } + }) => { promptRequests.push(request) return { data: {} } }) @@ -118,6 +136,9 @@ describe("handleAtlasSessionIdle completion nudge", () => { expect(promptText).toContain("- 1 Parse input: 1m 1s") expect(promptText).toContain("- 2 Save output: 4s") expect(promptText).not.toContain("{ELAPSED_HUMAN}") + expect(promptRequests[0]?.body?.noReply).toBeUndefined() + expect(promptRequests[0]?.body?.parts?.[0]?.synthetic).toBe(true) + expect(promptRequests[0]?.body?.parts?.[0]?.metadata?.compaction_continue).toBe(true) const persistedState = getState(SESSION_ID) expect(persistedState.boulderCompletionNudgedAt?.[workId]).toBeNumber() diff --git a/src/hooks/atlas/idle-event.ts b/src/hooks/atlas/idle-event.ts index b4803bb5e..118ed4e2d 100644 --- a/src/hooks/atlas/idle-event.ts +++ b/src/hooks/atlas/idle-event.ts @@ -16,7 +16,7 @@ import { } from "../../features/claude-code-session-state" import { getLastAgentFromSession } from "./session-last-agent" import { isSessionInBoulderLineage } from "./boulder-session-lineage" -import { createInternalAgentTextPart } from "../../shared" +import { createInternalAgentContinuationTextPart } from "../../shared" import { getAgentConfigKey } from "../../shared/agent-display-names" import { log } from "../../shared/logger" import { settleAfterSessionIdle } from "../shared/session-idle-settle" @@ -287,7 +287,7 @@ export async function handleAtlasSessionIdle(input: { path: { id: sessionID }, body: { agent: atlasAgent, - parts: [createInternalAgentTextPart(prompt)], + parts: [createInternalAgentContinuationTextPart(prompt)], }, query: { directory: ctx.directory }, }) diff --git a/src/hooks/session-recovery/resume.test.ts b/src/hooks/session-recovery/resume.test.ts index 1c2c40c08..e7ef870d1 100644 --- a/src/hooks/session-recovery/resume.test.ts +++ b/src/hooks/session-recovery/resume.test.ts @@ -74,7 +74,14 @@ describe("session-recovery resume", () => { expect(promptBody?.variant).toBe("max") expect(promptBody?.tools).toEqual({ question: false, bash: true }) expect(Array.isArray(promptBody?.parts)).toBe(true) - const firstPart = (promptBody?.parts as Array<{ text?: string }>)?.[0] + const firstPart = (promptBody?.parts as Array<{ + text?: string + synthetic?: boolean + metadata?: Record + }>)?.[0] expect(firstPart?.text).toContain(OMO_INTERNAL_INITIATOR_MARKER) + expect(firstPart?.synthetic).toBe(true) + expect(firstPart?.metadata?.compaction_continue).toBe(true) + expect(promptBody?.noReply).toBeUndefined() }) }) diff --git a/src/hooks/session-recovery/resume.ts b/src/hooks/session-recovery/resume.ts index 6c42b6315..136ea16b4 100644 --- a/src/hooks/session-recovery/resume.ts +++ b/src/hooks/session-recovery/resume.ts @@ -1,6 +1,6 @@ import type { createOpencodeClient } from "@opencode-ai/sdk" import type { MessageData, ResumeConfig } from "./types" -import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared" +import { createInternalAgentContinuationTextPart, resolveInheritedPromptTools } from "../../shared" const RECOVERY_RESUME_TEXT = "[session recovered - continuing previous task]" @@ -35,7 +35,7 @@ export async function resumeSession(client: Client, config: ResumeConfig): Promi await client.session.promptAsync({ path: { id: config.sessionID }, body: { - parts: [createInternalAgentTextPart(RECOVERY_RESUME_TEXT)], + parts: [createInternalAgentContinuationTextPart(RECOVERY_RESUME_TEXT)], agent: config.agent, ...(launchModel ? { model: launchModel } : {}), ...(launchVariant ? { variant: launchVariant } : {}),