Archived
fix(subsessions): omit oversized completion output
This commit is contained in:
@@ -782,6 +782,9 @@ describe("PiSessionService", () => {
|
||||
|
||||
it("notifies the parent once when the tracked child stops working", async () => {
|
||||
const { parent, child, service } = subsessionService({ allowed: true, cwd: "/workspace-feature" });
|
||||
child.session.sessionManager.getBranch = () => [
|
||||
{ type: "message", message: { role: "assistant", content: "all done" } },
|
||||
];
|
||||
await service.start("/workspace");
|
||||
await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "go", cwd: "/workspace-feature" });
|
||||
parent.calls.prompt.length = 0; // ignore the spawn prompt to the child; focus on the parent notification
|
||||
@@ -795,12 +798,36 @@ describe("PiSessionService", () => {
|
||||
|
||||
expect(parent.calls.sendCustomMessage).toHaveLength(1);
|
||||
expect(parent.calls.sendCustomMessage[0]?.message.content).toContain("Subsession child-1 stopped working");
|
||||
expect(parent.calls.sendCustomMessage[0]?.message.content).toContain("--- SUBSESSION OUTPUT: child-1 ---\nall done");
|
||||
expect(parent.calls.sendCustomMessage[0]?.message.customType).toBe("subsession.completion");
|
||||
expect(parent.calls.sendCustomMessage[0]?.options).toEqual({ triggerTurn: true, deliverAs: "followUp" });
|
||||
expect(parent.calls.prompt).toHaveLength(0); // not a user-authored message
|
||||
await service.dispose();
|
||||
});
|
||||
|
||||
it("omits oversized output from the completion notice while keeping it available for inspection", async () => {
|
||||
const { parent, child, service } = subsessionService({ allowed: true, cwd: "/workspace-feature" });
|
||||
const longOutput = `BEGIN_LONG_OUTPUT\n${"x".repeat(2100)}\nEND_LONG_OUTPUT`;
|
||||
child.session.sessionManager.getBranch = () => [
|
||||
{ type: "message", message: { role: "assistant", content: longOutput } },
|
||||
];
|
||||
await service.start("/workspace");
|
||||
await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "go", cwd: "/workspace-feature" });
|
||||
|
||||
child.session.isStreaming = true;
|
||||
child.emit({ type: "agent_start" });
|
||||
child.session.isStreaming = false;
|
||||
child.emit({ type: "agent_end" });
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
|
||||
expect(parent.calls.sendCustomMessage[0]?.message.content).toBe(
|
||||
"Subsession child-1 stopped working (idle).\nNo other tracked subsessions are working.\n\nOutput from subsession child-1 was too long for this completion notice and was omitted. Call check_subsession with sessionId \"child-1\" to retrieve the final output.",
|
||||
);
|
||||
expect(parent.calls.sendCustomMessage[0]?.message.content).not.toContain("BEGIN_LONG_OUTPUT");
|
||||
await expect(service.checkSubsession("parent-1", "child-1", "/tmp/parent-1.jsonl")).resolves.toMatchObject({ finalText: longOutput });
|
||||
await service.dispose();
|
||||
});
|
||||
|
||||
it("reports other working children in each completion notice", async () => {
|
||||
const { parent, children, service } = subsessionService(
|
||||
{ allowed: true, cwd: "/workspace-feature" },
|
||||
|
||||
@@ -924,12 +924,12 @@ export class PiSessionService implements SessionRouteService {
|
||||
this.subsessionNotifyArmed.set(childId, false);
|
||||
const status: SubsessionStatus = this.activities.get(childId)?.phase === "error" ? "error" : "idle";
|
||||
const finalText = finalAssistantText(historyMessages(session));
|
||||
const preview = finalText === "" ? "(no output)" : truncateForNotification(finalText);
|
||||
const outputSection = formatSubsessionNotificationOutput(childId, finalText);
|
||||
const workingIds = this.workingSubsessionIds(link.parentSessionId);
|
||||
const next = workingIds.length === 0
|
||||
? "No other tracked subsessions are working."
|
||||
: `Still working: ${workingIds.join(", ")}. Continue working, or call yield_to_subsessions alone and last at the next join point. Further completion notices arrive automatically; do not poll.`;
|
||||
const text = `Subsession ${childId} stopped working (${status}).\n${next}\n\n--- SUBSESSION OUTPUT: ${childId} ---\n${preview}`;
|
||||
const text = `Subsession ${childId} stopped working (${status}).\n${next}\n\n${outputSection}`;
|
||||
void this.notifyParentOfSubsession(link.parentSessionId, childId, text);
|
||||
}
|
||||
|
||||
@@ -2489,11 +2489,14 @@ const SUBSESSION_CHILD_LINK_CUSTOM_TYPE = "pi-web.subsession.spawned";
|
||||
/** customType marking a parent-facing subsession-completion notice. */
|
||||
const SUBSESSION_NOTIFICATION_CUSTOM_TYPE = "subsession.completion";
|
||||
|
||||
const SUBSESSION_NOTIFICATION_PREVIEW_CHARS = 2000;
|
||||
const SUBSESSION_NOTIFICATION_MAX_OUTPUT_CHARS = 2000;
|
||||
|
||||
function truncateForNotification(text: string): string {
|
||||
if (text.length <= SUBSESSION_NOTIFICATION_PREVIEW_CHARS) return text;
|
||||
return `${text.slice(0, SUBSESSION_NOTIFICATION_PREVIEW_CHARS)}…`;
|
||||
/** Avoid duplicating a partial result in context when deliberate inspection can return the full output. */
|
||||
function formatSubsessionNotificationOutput(childSessionId: string, text: string): string {
|
||||
if (text.length > SUBSESSION_NOTIFICATION_MAX_OUTPUT_CHARS) {
|
||||
return `Output from subsession ${childSessionId} was too long for this completion notice and was omitted. Call check_subsession with sessionId "${childSessionId}" to retrieve the final output.`;
|
||||
}
|
||||
return `--- SUBSESSION OUTPUT: ${childSessionId} ---\n${text === "" ? "(no output)" : text}`;
|
||||
}
|
||||
|
||||
/** Most recent assistant text from a history message list, or "" if none. */
|
||||
|
||||
Reference in New Issue
Block a user