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