fix(continuation): mark fallback resumes synthetic
This commit is contained in:
@@ -18,14 +18,26 @@ function setupConnectedProviderCacheMocks(): void {
|
||||
type PromptBody = {
|
||||
path: { id: string }
|
||||
body: {
|
||||
parts: Array<{ type: "text"; text: string }>
|
||||
parts: Array<{
|
||||
type: "text"
|
||||
text: string
|
||||
synthetic?: boolean
|
||||
metadata?: Record<string, unknown>
|
||||
}>
|
||||
agent?: string
|
||||
model?: { providerID: string; modelID: string }
|
||||
variant?: string
|
||||
noReply?: boolean
|
||||
}
|
||||
query: { directory: string }
|
||||
}
|
||||
|
||||
function expectSyntheticContinuation(body: PromptBody["body"]): void {
|
||||
expect(body.noReply).toBeUndefined()
|
||||
expect(body.parts[0]?.synthetic).toBe(true)
|
||||
expect(body.parts[0]?.metadata?.compaction_continue).toBe(true)
|
||||
}
|
||||
|
||||
describe("createEventHandler - model-fallback auto-continuation pins agent/model/variant", () => {
|
||||
const createHandler = (args?: {
|
||||
hooks?: any
|
||||
@@ -127,6 +139,7 @@ describe("createEventHandler - model-fallback auto-continuation pins agent/model
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
expectSyntheticContinuation(body)
|
||||
})
|
||||
|
||||
test("pins agent/model on promptAsync body when continuing after session.error fallback", async () => {
|
||||
@@ -167,6 +180,7 @@ describe("createEventHandler - model-fallback auto-continuation pins agent/model
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
expectSyntheticContinuation(body)
|
||||
})
|
||||
|
||||
test("pins agent/model on fallback prompt() body when promptAsync is not available (session.status)", async () => {
|
||||
@@ -223,6 +237,7 @@ describe("createEventHandler - model-fallback auto-continuation pins agent/model
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-7",
|
||||
})
|
||||
expectSyntheticContinuation(body)
|
||||
})
|
||||
|
||||
test("pins variant from agent config when present", async () => {
|
||||
@@ -268,5 +283,6 @@ describe("createEventHandler - model-fallback auto-continuation pins agent/model
|
||||
expect(promptAsyncBodies.length).toBe(1)
|
||||
const body = promptAsyncBodies[0]!.body
|
||||
expect(body.variant).toBe("thinking")
|
||||
expectSyntheticContinuation(body)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1470,6 +1470,15 @@ describe("createEventHandler - session recovery compaction", () => {
|
||||
const sessionID = "ses_recovery_compaction"
|
||||
setMainSession(sessionID)
|
||||
const callOrder: string[] = []
|
||||
const promptBodies: Array<{
|
||||
body?: {
|
||||
noReply?: boolean
|
||||
parts?: Array<{
|
||||
synthetic?: boolean
|
||||
metadata?: Record<string, unknown>
|
||||
}>
|
||||
}
|
||||
}> = []
|
||||
|
||||
const eventHandler = createEventHandler({
|
||||
ctx: asEventHandlerContext({
|
||||
@@ -1481,8 +1490,9 @@ describe("createEventHandler - session recovery compaction", () => {
|
||||
callOrder.push("summarize")
|
||||
return {}
|
||||
},
|
||||
prompt: async () => {
|
||||
prompt: async (input: { body?: { noReply?: boolean; parts?: Array<{ synthetic?: boolean; metadata?: Record<string, unknown> }> } }) => {
|
||||
callOrder.push("prompt")
|
||||
promptBodies.push(input)
|
||||
return {}
|
||||
},
|
||||
},
|
||||
@@ -1513,12 +1523,24 @@ describe("createEventHandler - session recovery compaction", () => {
|
||||
},
|
||||
}))
|
||||
expect(callOrder).toEqual(["summarize", "prompt"])
|
||||
expect(promptBodies[0]?.body?.noReply).toBeUndefined()
|
||||
expect(promptBodies[0]?.body?.parts?.[0]?.synthetic).toBe(true)
|
||||
expect(promptBodies[0]?.body?.parts?.[0]?.metadata?.compaction_continue).toBe(true)
|
||||
})
|
||||
|
||||
it("sends continue even if compaction fails", async () => {
|
||||
const sessionID = "ses_recovery_compaction_fail"
|
||||
setMainSession(sessionID)
|
||||
const callOrder: string[] = []
|
||||
const promptBodies: Array<{
|
||||
body?: {
|
||||
noReply?: boolean
|
||||
parts?: Array<{
|
||||
synthetic?: boolean
|
||||
metadata?: Record<string, unknown>
|
||||
}>
|
||||
}
|
||||
}> = []
|
||||
|
||||
const eventHandler = createEventHandler({
|
||||
ctx: asEventHandlerContext({
|
||||
@@ -1530,8 +1552,9 @@ describe("createEventHandler - session recovery compaction", () => {
|
||||
callOrder.push("summarize")
|
||||
throw new Error("compaction failed")
|
||||
},
|
||||
prompt: async () => {
|
||||
prompt: async (input: { body?: { noReply?: boolean; parts?: Array<{ synthetic?: boolean; metadata?: Record<string, unknown> }> } }) => {
|
||||
callOrder.push("prompt")
|
||||
promptBodies.push(input)
|
||||
return {}
|
||||
},
|
||||
},
|
||||
@@ -1562,6 +1585,9 @@ describe("createEventHandler - session recovery compaction", () => {
|
||||
},
|
||||
}))
|
||||
expect(callOrder).toEqual(["summarize", "prompt"])
|
||||
expect(promptBodies[0]?.body?.noReply).toBeUndefined()
|
||||
expect(promptBodies[0]?.body?.parts?.[0]?.synthetic).toBe(true)
|
||||
expect(promptBodies[0]?.body?.parts?.[0]?.metadata?.compaction_continue).toBe(true)
|
||||
})
|
||||
|
||||
it("continues dispatching later event hooks when an earlier hook throws", async () => {
|
||||
|
||||
+15
-5
@@ -25,7 +25,7 @@ import {
|
||||
clearBackgroundOutputConsumptionsForTaskSession,
|
||||
restoreBackgroundOutputConsumption,
|
||||
} from "../shared/background-output-consumption";
|
||||
import { resetMessageCursor } from "../shared";
|
||||
import { createInternalAgentContinuationTextPart, resetMessageCursor } from "../shared";
|
||||
import { getAgentConfigKey } from "../shared/agent-display-names";
|
||||
import { readConnectedProvidersCache } from "../shared/connected-providers-cache";
|
||||
import { invalidateContextWindowUsageCache } from "../shared/dynamic-truncator";
|
||||
@@ -162,7 +162,12 @@ export function createEventHandler(args: {
|
||||
promptAsync?: (input: {
|
||||
path: { id: string };
|
||||
body: {
|
||||
parts: Array<{ type: "text"; text: string }>;
|
||||
parts: Array<{
|
||||
type: "text";
|
||||
text: string;
|
||||
synthetic?: boolean;
|
||||
metadata?: Record<string, unknown>;
|
||||
}>;
|
||||
agent?: string;
|
||||
model?: { providerID: string; modelID: string };
|
||||
variant?: string;
|
||||
@@ -172,7 +177,12 @@ export function createEventHandler(args: {
|
||||
prompt: (input: {
|
||||
path: { id: string };
|
||||
body: {
|
||||
parts: Array<{ type: "text"; text: string }>;
|
||||
parts: Array<{
|
||||
type: "text";
|
||||
text: string;
|
||||
synthetic?: boolean;
|
||||
metadata?: Record<string, unknown>;
|
||||
}>;
|
||||
agent?: string;
|
||||
model?: { providerID: string; modelID: string };
|
||||
variant?: string;
|
||||
@@ -383,7 +393,7 @@ export function createEventHandler(args: {
|
||||
...(launchAgent ? { agent: launchAgent } : {}),
|
||||
...(launchModel ? { model: launchModel } : {}),
|
||||
...(launchVariant ? { variant: launchVariant } : {}),
|
||||
parts: [{ type: "text" as const, text: "continue" }],
|
||||
parts: [createInternalAgentContinuationTextPart("continue")],
|
||||
},
|
||||
query: { directory: pluginContext.directory },
|
||||
};
|
||||
@@ -772,7 +782,7 @@ export function createEventHandler(args: {
|
||||
await pluginContext.client.session
|
||||
.prompt({
|
||||
path: { id: sessionID },
|
||||
body: { parts: [{ type: "text", text: "continue" }] },
|
||||
body: { parts: [createInternalAgentContinuationTextPart("continue")] },
|
||||
query: { directory: pluginContext.directory },
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
Reference in New Issue
Block a user