fix: skip compaction messages in parent-session context lookup
This commit is contained in:
parent
e1e449164a
commit
285d8d58dd
@ -805,6 +805,62 @@ interface CurrentMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("BackgroundManager.notifyParentSession - dynamic message lookup", () => {
|
describe("BackgroundManager.notifyParentSession - dynamic message lookup", () => {
|
||||||
|
test("should skip compaction agent and use nearest non-compaction message", async () => {
|
||||||
|
//#given
|
||||||
|
let capturedBody: Record<string, unknown> | undefined
|
||||||
|
const client = {
|
||||||
|
session: {
|
||||||
|
prompt: async () => ({}),
|
||||||
|
promptAsync: async (args: { body: Record<string, unknown> }) => {
|
||||||
|
capturedBody = args.body
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
abort: async () => ({}),
|
||||||
|
messages: async () => ({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
agent: "sisyphus",
|
||||||
|
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
agent: "compaction",
|
||||||
|
model: { providerID: "anthropic", modelID: "claude-sonnet-4-5" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
|
||||||
|
const task: BackgroundTask = {
|
||||||
|
id: "task-skip-compaction",
|
||||||
|
sessionID: "session-child",
|
||||||
|
parentSessionID: "session-parent",
|
||||||
|
parentMessageID: "msg-parent",
|
||||||
|
description: "task with compaction at tail",
|
||||||
|
prompt: "test",
|
||||||
|
agent: "explore",
|
||||||
|
status: "completed",
|
||||||
|
startedAt: new Date(),
|
||||||
|
completedAt: new Date(),
|
||||||
|
parentAgent: "fallback-agent",
|
||||||
|
}
|
||||||
|
getPendingByParent(manager).set("session-parent", new Set([task.id, "still-running"]))
|
||||||
|
|
||||||
|
//#when
|
||||||
|
await (manager as unknown as { notifyParentSession: (value: BackgroundTask) => Promise<void> })
|
||||||
|
.notifyParentSession(task)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(capturedBody?.agent).toBe("sisyphus")
|
||||||
|
expect(capturedBody?.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||||
|
|
||||||
|
manager.shutdown()
|
||||||
|
})
|
||||||
|
|
||||||
test("should use currentMessage model/agent when available", async () => {
|
test("should use currentMessage model/agent when available", async () => {
|
||||||
// given - currentMessage has model and agent
|
// given - currentMessage has model and agent
|
||||||
const task: BackgroundTask = {
|
const task: BackgroundTask = {
|
||||||
|
|||||||
@ -23,8 +23,8 @@ import {
|
|||||||
|
|
||||||
import { subagentSessions } from "../claude-code-session-state"
|
import { subagentSessions } from "../claude-code-session-state"
|
||||||
import { getTaskToastManager } from "../task-toast-manager"
|
import { getTaskToastManager } from "../task-toast-manager"
|
||||||
import { findNearestMessageWithFields, MESSAGE_STORAGE } from "../hook-message-injector"
|
import { MESSAGE_STORAGE, type StoredMessage } from "../hook-message-injector"
|
||||||
import { existsSync, readdirSync } from "node:fs"
|
import { existsSync, readFileSync, readdirSync } from "node:fs"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
|
|
||||||
type ProcessCleanupEvent = NodeJS.Signals | "beforeExit" | "exit"
|
type ProcessCleanupEvent = NodeJS.Signals | "beforeExit" | "exit"
|
||||||
@ -1211,7 +1211,6 @@ export class BackgroundManager {
|
|||||||
.filter(t => t.parentSessionID === task.parentSessionID && t.status !== "running" && t.status !== "pending")
|
.filter(t => t.parentSessionID === task.parentSessionID && t.status !== "running" && t.status !== "pending")
|
||||||
: []
|
: []
|
||||||
|
|
||||||
if (this.enableParentSessionNotifications) {
|
|
||||||
const statusText = task.status === "completed" ? "COMPLETED" : task.status === "interrupt" ? "INTERRUPTED" : "CANCELLED"
|
const statusText = task.status === "completed" ? "COMPLETED" : task.status === "interrupt" ? "INTERRUPTED" : "CANCELLED"
|
||||||
const errorInfo = task.error ? `\n**Error:** ${task.error}` : ""
|
const errorInfo = task.error ? `\n**Error:** ${task.error}` : ""
|
||||||
|
|
||||||
@ -1247,6 +1246,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|||||||
let agent: string | undefined = task.parentAgent
|
let agent: string | undefined = task.parentAgent
|
||||||
let model: { providerID: string; modelID: string } | undefined
|
let model: { providerID: string; modelID: string } | undefined
|
||||||
|
|
||||||
|
if (this.enableParentSessionNotifications) {
|
||||||
try {
|
try {
|
||||||
const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } })
|
const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } })
|
||||||
const messages = normalizeSDKResponse(messagesResp, [] as Array<{
|
const messages = normalizeSDKResponse(messagesResp, [] as Array<{
|
||||||
@ -1254,6 +1254,9 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|||||||
}>)
|
}>)
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
const info = messages[i].info
|
const info = messages[i].info
|
||||||
|
if (isCompactionAgent(info?.agent)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (info?.agent || info?.model || (info?.modelID && info?.providerID)) {
|
if (info?.agent || info?.model || (info?.modelID && info?.providerID)) {
|
||||||
agent = info.agent ?? task.parentAgent
|
agent = info.agent ?? task.parentAgent
|
||||||
model = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined)
|
model = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined)
|
||||||
@ -1268,7 +1271,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const messageDir = getMessageDir(task.parentSessionID)
|
const messageDir = getMessageDir(task.parentSessionID)
|
||||||
const currentMessage = messageDir ? findNearestMessageWithFields(messageDir) : null
|
const currentMessage = messageDir ? findNearestMessageExcludingCompaction(messageDir) : null
|
||||||
agent = currentMessage?.agent ?? task.parentAgent
|
agent = currentMessage?.agent ?? task.parentAgent
|
||||||
model = currentMessage?.model?.providerID && currentMessage?.model?.modelID
|
model = currentMessage?.model?.providerID && currentMessage?.model?.modelID
|
||||||
? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID }
|
? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID }
|
||||||
@ -1715,3 +1718,57 @@ function getMessageDir(sessionID: string): string | null {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCompactionAgent(agent: string | undefined): boolean {
|
||||||
|
return agent?.trim().toLowerCase() === "compaction"
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasFullAgentAndModel(message: StoredMessage): boolean {
|
||||||
|
return !!message.agent &&
|
||||||
|
!isCompactionAgent(message.agent) &&
|
||||||
|
!!message.model?.providerID &&
|
||||||
|
!!message.model?.modelID
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPartialAgentOrModel(message: StoredMessage): boolean {
|
||||||
|
const hasAgent = !!message.agent && !isCompactionAgent(message.agent)
|
||||||
|
const hasModel = !!message.model?.providerID && !!message.model?.modelID
|
||||||
|
return hasAgent || hasModel
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNearestMessageExcludingCompaction(messageDir: string): StoredMessage | null {
|
||||||
|
try {
|
||||||
|
const files = readdirSync(messageDir)
|
||||||
|
.filter((name) => name.endsWith(".json"))
|
||||||
|
.sort()
|
||||||
|
.reverse()
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const content = readFileSync(join(messageDir, file), "utf-8")
|
||||||
|
const parsed = JSON.parse(content) as StoredMessage
|
||||||
|
if (hasFullAgentAndModel(parsed)) {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const content = readFileSync(join(messageDir, file), "utf-8")
|
||||||
|
const parsed = JSON.parse(content) as StoredMessage
|
||||||
|
if (hasPartialAgentOrModel(parsed)) {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user