@minpeter/pss-runtime 0.1.0-next.0 → 0.1.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -191
- package/dist/agent-child-runs.js +16 -0
- package/dist/agent-child-runs.js.map +1 -0
- package/dist/agent-host-capabilities.js +9 -0
- package/dist/agent-host-capabilities.js.map +1 -0
- package/dist/agent-host-session-store.js +12 -0
- package/dist/agent-host-session-store.js.map +1 -0
- package/dist/agent-loop.js +59 -35
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent-namespace.js +24 -0
- package/dist/agent-namespace.js.map +1 -0
- package/dist/agent-options.d.ts +35 -0
- package/dist/agent-options.js +16 -0
- package/dist/agent-options.js.map +1 -0
- package/dist/agent-resume.js +143 -0
- package/dist/agent-resume.js.map +1 -0
- package/dist/agent-session-entry.d.ts +13 -0
- package/dist/agent-validation.js +35 -0
- package/dist/agent-validation.js.map +1 -0
- package/dist/agent.d.ts +8 -33
- package/dist/agent.js +131 -55
- package/dist/agent.js.map +1 -1
- package/dist/child-session-cleanups.js +61 -0
- package/dist/child-session-cleanups.js.map +1 -0
- package/dist/execution/host.js +14 -0
- package/dist/execution/host.js.map +1 -0
- package/dist/execution/index.d.ts +4 -0
- package/dist/execution/index.js +3 -0
- package/dist/execution/memory-notifications.js +54 -0
- package/dist/execution/memory-notifications.js.map +1 -0
- package/dist/execution/memory-state.js +34 -0
- package/dist/execution/memory-state.js.map +1 -0
- package/dist/execution/memory-store.js +203 -0
- package/dist/execution/memory-store.js.map +1 -0
- package/dist/execution/memory.d.ts +7 -0
- package/dist/execution/memory.js +28 -0
- package/dist/execution/memory.js.map +1 -0
- package/dist/execution/run.js +55 -0
- package/dist/execution/run.js.map +1 -0
- package/dist/execution/types.d.ts +155 -0
- package/dist/index.d.ts +9 -10
- package/dist/index.js +1 -6
- package/dist/llm-tool-execution.d.ts +35 -0
- package/dist/llm-tool-execution.js +126 -0
- package/dist/llm-tool-execution.js.map +1 -0
- package/dist/llm.d.ts +11 -15
- package/dist/llm.js +5 -9
- package/dist/llm.js.map +1 -1
- package/dist/plugins.d.ts +20 -0
- package/dist/plugins.js +14 -0
- package/dist/plugins.js.map +1 -0
- package/dist/session/events.d.ts +26 -20
- package/dist/session/input-normalization.js +66 -0
- package/dist/session/input-normalization.js.map +1 -0
- package/dist/session/input.d.ts +0 -4
- package/dist/session/mapping.js +1 -2
- package/dist/session/mapping.js.map +1 -1
- package/dist/session/run.js +1 -0
- package/dist/session/run.js.map +1 -1
- package/dist/session/runtime-input.js +20 -58
- package/dist/session/runtime-input.js.map +1 -1
- package/dist/session/session-errors.js +18 -0
- package/dist/session/session-errors.js.map +1 -0
- package/dist/session/session-events.js +59 -0
- package/dist/session/session-events.js.map +1 -0
- package/dist/session/session-execution.js +88 -0
- package/dist/session/session-execution.js.map +1 -0
- package/dist/session/session-kill.js +23 -0
- package/dist/session/session-kill.js.map +1 -0
- package/dist/session/session-notification.js +58 -0
- package/dist/session/session-notification.js.map +1 -0
- package/dist/session/session-runtime-drain.js +22 -0
- package/dist/session/session-runtime-drain.js.map +1 -0
- package/dist/session/session-state.js +102 -0
- package/dist/session/session-state.js.map +1 -0
- package/dist/session/session-turn-error.js +35 -0
- package/dist/session/session-turn-error.js.map +1 -0
- package/dist/session/session-turn-processor.js +135 -0
- package/dist/session/session-turn-processor.js.map +1 -0
- package/dist/session/session.js +125 -335
- package/dist/session/session.js.map +1 -1
- package/dist/session/snapshot.js +5 -31
- package/dist/session/snapshot.js.map +1 -1
- package/dist/session/store/file.d.ts +1 -0
- package/dist/session/store/file.js +14 -0
- package/dist/session/store/file.js.map +1 -1
- package/dist/session/store/memory.d.ts +1 -0
- package/dist/session/store/memory.js +5 -0
- package/dist/session/store/memory.js.map +1 -1
- package/dist/session/store/types.d.ts +1 -0
- package/dist/subagent-background-child-run-state.js +51 -0
- package/dist/subagent-background-child-run-state.js.map +1 -0
- package/dist/subagent-background-child-run.js +103 -0
- package/dist/subagent-background-child-run.js.map +1 -0
- package/dist/subagent-background-in-process.js +98 -0
- package/dist/subagent-background-in-process.js.map +1 -0
- package/dist/subagent-background-notification-inbox.js +106 -0
- package/dist/subagent-background-notification-inbox.js.map +1 -0
- package/dist/subagent-background-notify.js +136 -0
- package/dist/subagent-background-notify.js.map +1 -0
- package/dist/subagent-background-resume-group.js +99 -0
- package/dist/subagent-background-resume-group.js.map +1 -0
- package/dist/subagent-background-runner.js +115 -0
- package/dist/subagent-background-runner.js.map +1 -0
- package/dist/subagent-background-schedule.js +43 -0
- package/dist/subagent-background-schedule.js.map +1 -0
- package/dist/subagent-child-run.js +68 -0
- package/dist/subagent-child-run.js.map +1 -0
- package/dist/subagent-job-cancel.js +84 -0
- package/dist/subagent-job-cancel.js.map +1 -0
- package/dist/subagent-job-observer.js +19 -0
- package/dist/subagent-job-observer.js.map +1 -0
- package/dist/subagent-job-output.js +87 -0
- package/dist/subagent-job-output.js.map +1 -0
- package/dist/subagent-job-state.js +66 -0
- package/dist/subagent-job-state.js.map +1 -0
- package/dist/subagent-jobs.js +96 -0
- package/dist/subagent-jobs.js.map +1 -0
- package/dist/subagent-prompt-schema.js +114 -0
- package/dist/subagent-prompt-schema.js.map +1 -0
- package/dist/subagent-run.js +111 -0
- package/dist/subagent-run.js.map +1 -0
- package/dist/subagents.js +125 -0
- package/dist/subagents.js.map +1 -0
- package/package.json +11 -6
- package/dist/plugins/compaction.d.ts +0 -15
- package/dist/plugins/compaction.js +0 -98
- package/dist/plugins/compaction.js.map +0 -1
- package/dist/plugins/index.d.ts +0 -5
- package/dist/plugins/index.js +0 -5
- package/dist/plugins/memory.d.ts +0 -11
- package/dist/plugins/memory.js +0 -146
- package/dist/plugins/memory.js.map +0 -1
- package/dist/plugins/runner.d.ts +0 -1
- package/dist/plugins/runner.js +0 -83
- package/dist/plugins/runner.js.map +0 -1
- package/dist/plugins/scope.js +0 -13
- package/dist/plugins/scope.js.map +0 -1
- package/dist/plugins/sessions.d.ts +0 -12
- package/dist/plugins/sessions.js +0 -34
- package/dist/plugins/sessions.js.map +0 -1
- package/dist/plugins/tool-hook-handlers.js +0 -77
- package/dist/plugins/tool-hook-handlers.js.map +0 -1
- package/dist/plugins/tool-hook-results.js +0 -64
- package/dist/plugins/tool-hook-results.js.map +0 -1
- package/dist/plugins/tool-hooks.js +0 -111
- package/dist/plugins/tool-hooks.js.map +0 -1
- package/dist/plugins/types.d.ts +0 -105
- package/dist/plugins/types.js +0 -20
- package/dist/plugins/types.js.map +0 -1
- package/dist/session/lifecycle.d.ts +0 -12
- package/dist/session/lifecycle.js +0 -126
- package/dist/session/lifecycle.js.map +0 -1
- package/dist/session/overlay-anchor.js +0 -151
- package/dist/session/overlay-anchor.js.map +0 -1
- package/dist/session/overlay.js +0 -141
- package/dist/session/overlay.js.map +0 -1
- package/dist/session/snapshot.d.ts +0 -1
- /package/dist/{agent-loop.d.ts → session/history.d.ts} +0 -0
- /package/dist/session/{runtime-input.d.ts → session-execution.d.ts} +0 -0
- /package/dist/{plugins/scope.d.ts → session/session-state.d.ts} +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//#region src/subagent-background-notification-inbox.ts
|
|
2
|
+
async function enqueueDurableBackgroundNotification({ input, jobs, observerEvents }) {
|
|
3
|
+
const host = findExecutionHost(jobs);
|
|
4
|
+
const parentSessionKey = findParentSessionKey(jobs);
|
|
5
|
+
const ownerNamespace = findOwnerNamespace(jobs);
|
|
6
|
+
if (!host) return "inline";
|
|
7
|
+
if (await allChildRunsCancelled({
|
|
8
|
+
host,
|
|
9
|
+
jobs
|
|
10
|
+
})) return "queued-only";
|
|
11
|
+
if (!(parentSessionKey && ownerNamespace)) return "inline";
|
|
12
|
+
if (host.capabilities.backgroundSubagents !== "durable") return "inline";
|
|
13
|
+
const idempotencyKey = backgroundNotificationIdempotencyKey({
|
|
14
|
+
jobs,
|
|
15
|
+
parentSessionKey
|
|
16
|
+
});
|
|
17
|
+
const existing = await host.store.notifications.getByIdempotencyKey(idempotencyKey);
|
|
18
|
+
if (existing) {
|
|
19
|
+
if (existing.status === "pending" && host.capabilities.backgroundSubagents === "durable") await host.scheduler.resumeSession(existing.sessionKey, resumePayloadFromNotification(existing));
|
|
20
|
+
return "queued-only";
|
|
21
|
+
}
|
|
22
|
+
const created = await createNotificationRecord({
|
|
23
|
+
host,
|
|
24
|
+
idempotencyKey,
|
|
25
|
+
input,
|
|
26
|
+
jobs,
|
|
27
|
+
observerEvents,
|
|
28
|
+
ownerNamespace,
|
|
29
|
+
parentSessionKey
|
|
30
|
+
});
|
|
31
|
+
if (created) await host.scheduler.resumeSession(parentSessionKey, created);
|
|
32
|
+
return "queued-only";
|
|
33
|
+
}
|
|
34
|
+
async function allChildRunsCancelled({ host, jobs }) {
|
|
35
|
+
let childRunCount = 0;
|
|
36
|
+
for (const job of jobs) {
|
|
37
|
+
if (!job.childRunId) continue;
|
|
38
|
+
childRunCount += 1;
|
|
39
|
+
if ((await host.store.runs.get(job.childRunId))?.status !== "cancelled") return false;
|
|
40
|
+
}
|
|
41
|
+
return childRunCount > 0;
|
|
42
|
+
}
|
|
43
|
+
function findExecutionHost(jobs) {
|
|
44
|
+
return jobs.find((job) => job.executionHost)?.executionHost;
|
|
45
|
+
}
|
|
46
|
+
function findParentSessionKey(jobs) {
|
|
47
|
+
return jobs.find((job) => job.parentSessionKey)?.parentSessionKey;
|
|
48
|
+
}
|
|
49
|
+
function findOwnerNamespace(jobs) {
|
|
50
|
+
return jobs.find((job) => job.ownerNamespace)?.ownerNamespace;
|
|
51
|
+
}
|
|
52
|
+
function backgroundNotificationIdempotencyKey({ jobs, parentSessionKey }) {
|
|
53
|
+
return `background-complete:${parentSessionKey}:${jobs.map((job) => job.id).sort().join(",")}`;
|
|
54
|
+
}
|
|
55
|
+
async function createNotificationRecord({ host, idempotencyKey, input, jobs, observerEvents, ownerNamespace, parentSessionKey }) {
|
|
56
|
+
const notificationId = `ntf_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
57
|
+
const runId = `notification:${notificationId}`;
|
|
58
|
+
return await host.store.transaction(async (tx) => {
|
|
59
|
+
if (await tx.notifications.getByIdempotencyKey(idempotencyKey)) return;
|
|
60
|
+
if (!(await tx.notifications.enqueue({
|
|
61
|
+
idempotencyKey,
|
|
62
|
+
input,
|
|
63
|
+
notificationId,
|
|
64
|
+
observerEvents,
|
|
65
|
+
ownerNamespace,
|
|
66
|
+
runId,
|
|
67
|
+
sessionKey: parentSessionKey,
|
|
68
|
+
status: "pending"
|
|
69
|
+
})).ok) return;
|
|
70
|
+
await tx.runs.create({
|
|
71
|
+
checkpointVersion: 0,
|
|
72
|
+
dedupeKey: idempotencyKey,
|
|
73
|
+
kind: "notification",
|
|
74
|
+
ownerNamespace,
|
|
75
|
+
parentRunId: jobs.length === 1 ? jobs[0]?.childRunId : void 0,
|
|
76
|
+
rootRunId: runId,
|
|
77
|
+
runId,
|
|
78
|
+
sessionKey: parentSessionKey,
|
|
79
|
+
status: "queued"
|
|
80
|
+
});
|
|
81
|
+
if (!(await tx.checkpoints.append({
|
|
82
|
+
checkpointId: crypto.randomUUID(),
|
|
83
|
+
phase: "before-notification",
|
|
84
|
+
runId,
|
|
85
|
+
runtimeState: {},
|
|
86
|
+
sessionSnapshot: {},
|
|
87
|
+
version: 1
|
|
88
|
+
}, { expectedVersion: 0 })).ok) throw new Error("Failed to write background notification checkpoint.");
|
|
89
|
+
return {
|
|
90
|
+
idempotencyKey,
|
|
91
|
+
notificationId,
|
|
92
|
+
runId
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function resumePayloadFromNotification(notification) {
|
|
97
|
+
return {
|
|
98
|
+
idempotencyKey: notification.idempotencyKey,
|
|
99
|
+
notificationId: notification.notificationId,
|
|
100
|
+
runId: notification.runId
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { enqueueDurableBackgroundNotification };
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=subagent-background-notification-inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-background-notification-inbox.js","names":[],"sources":["../src/subagent-background-notification-inbox.ts"],"sourcesContent":["import type { ExecutionHost } from \"./execution/types\";\nimport type { AgentEvent } from \"./session/events\";\nimport type { UserInput } from \"./session/input\";\nimport type { SubagentJob } from \"./subagent-types\";\n\ntype InlineNotificationMode = \"inline\" | \"queued-only\";\n\ninterface NotificationResumePayload {\n readonly idempotencyKey: string;\n readonly notificationId: string;\n readonly runId: string;\n}\n\nexport async function enqueueDurableBackgroundNotification({\n input,\n jobs,\n observerEvents,\n}: {\n readonly input: UserInput;\n readonly jobs: readonly SubagentJob[];\n readonly observerEvents: readonly AgentEvent[];\n}): Promise<InlineNotificationMode> {\n const host = findExecutionHost(jobs);\n const parentSessionKey = findParentSessionKey(jobs);\n const ownerNamespace = findOwnerNamespace(jobs);\n if (!host) {\n return \"inline\";\n }\n\n if (await allChildRunsCancelled({ host, jobs })) {\n return \"queued-only\";\n }\n\n if (!(parentSessionKey && ownerNamespace)) {\n return \"inline\";\n }\n\n if (host.capabilities.backgroundSubagents !== \"durable\") {\n return \"inline\";\n }\n\n const idempotencyKey = backgroundNotificationIdempotencyKey({\n jobs,\n parentSessionKey,\n });\n const existing =\n await host.store.notifications.getByIdempotencyKey(idempotencyKey);\n if (existing) {\n if (\n existing.status === \"pending\" &&\n host.capabilities.backgroundSubagents === \"durable\"\n ) {\n await host.scheduler.resumeSession(\n existing.sessionKey,\n resumePayloadFromNotification(existing)\n );\n }\n return \"queued-only\";\n }\n\n const created = await createNotificationRecord({\n host,\n idempotencyKey,\n input,\n jobs,\n observerEvents,\n ownerNamespace,\n parentSessionKey,\n });\n if (created) {\n await host.scheduler.resumeSession(parentSessionKey, created);\n }\n\n return \"queued-only\";\n}\n\nasync function allChildRunsCancelled({\n host,\n jobs,\n}: {\n readonly host: ExecutionHost;\n readonly jobs: readonly SubagentJob[];\n}): Promise<boolean> {\n let childRunCount = 0;\n for (const job of jobs) {\n if (!job.childRunId) {\n continue;\n }\n\n childRunCount += 1;\n const run = await host.store.runs.get(job.childRunId);\n if (run?.status !== \"cancelled\") {\n return false;\n }\n }\n\n return childRunCount > 0;\n}\n\nfunction findExecutionHost(\n jobs: readonly SubagentJob[]\n): ExecutionHost | undefined {\n return jobs.find((job) => job.executionHost)?.executionHost;\n}\n\nfunction findParentSessionKey(\n jobs: readonly SubagentJob[]\n): string | undefined {\n return jobs.find((job) => job.parentSessionKey)?.parentSessionKey;\n}\n\nfunction findOwnerNamespace(jobs: readonly SubagentJob[]): string | undefined {\n return jobs.find((job) => job.ownerNamespace)?.ownerNamespace;\n}\n\nfunction backgroundNotificationIdempotencyKey({\n jobs,\n parentSessionKey,\n}: {\n readonly jobs: readonly SubagentJob[];\n readonly parentSessionKey: string;\n}): string {\n const taskIds = jobs\n .map((job) => job.id)\n .sort()\n .join(\",\");\n return `background-complete:${parentSessionKey}:${taskIds}`;\n}\n\nasync function createNotificationRecord({\n host,\n idempotencyKey,\n input,\n jobs,\n observerEvents,\n ownerNamespace,\n parentSessionKey,\n}: {\n readonly host: ExecutionHost;\n readonly idempotencyKey: string;\n readonly input: UserInput;\n readonly jobs: readonly SubagentJob[];\n readonly observerEvents: readonly AgentEvent[];\n readonly ownerNamespace: string;\n readonly parentSessionKey: string;\n}): Promise<NotificationResumePayload | undefined> {\n const notificationId = `ntf_${crypto.randomUUID().replaceAll(\"-\", \"\")}`;\n const runId = `notification:${notificationId}`;\n return await host.store.transaction(async (tx) => {\n const duplicate =\n await tx.notifications.getByIdempotencyKey(idempotencyKey);\n if (duplicate) {\n return;\n }\n\n const enqueued = await tx.notifications.enqueue({\n idempotencyKey,\n input,\n notificationId,\n observerEvents,\n ownerNamespace,\n runId,\n sessionKey: parentSessionKey,\n status: \"pending\",\n });\n if (!enqueued.ok) {\n return;\n }\n\n await tx.runs.create({\n checkpointVersion: 0,\n dedupeKey: idempotencyKey,\n kind: \"notification\",\n ownerNamespace,\n parentRunId: jobs.length === 1 ? jobs[0]?.childRunId : undefined,\n rootRunId: runId,\n runId,\n sessionKey: parentSessionKey,\n status: \"queued\",\n });\n const checkpoint = await tx.checkpoints.append(\n {\n checkpointId: crypto.randomUUID(),\n phase: \"before-notification\",\n runId,\n runtimeState: {},\n sessionSnapshot: {},\n version: 1,\n },\n { expectedVersion: 0 }\n );\n if (!checkpoint.ok) {\n throw new Error(\"Failed to write background notification checkpoint.\");\n }\n return { idempotencyKey, notificationId, runId };\n });\n}\n\nfunction resumePayloadFromNotification(\n notification: NotificationResumePayload\n): NotificationResumePayload {\n return {\n idempotencyKey: notification.idempotencyKey,\n notificationId: notification.notificationId,\n runId: notification.runId,\n };\n}\n"],"mappings":";AAaA,eAAsB,qCAAqC,EACzD,OACA,MACA,kBAKkC;CAClC,MAAM,OAAO,kBAAkB,IAAI;CACnC,MAAM,mBAAmB,qBAAqB,IAAI;CAClD,MAAM,iBAAiB,mBAAmB,IAAI;CAC9C,IAAI,CAAC,MACH,OAAO;CAGT,IAAI,MAAM,sBAAsB;EAAE;EAAM;CAAK,CAAC,GAC5C,OAAO;CAGT,IAAI,EAAE,oBAAoB,iBACxB,OAAO;CAGT,IAAI,KAAK,aAAa,wBAAwB,WAC5C,OAAO;CAGT,MAAM,iBAAiB,qCAAqC;EAC1D;EACA;CACF,CAAC;CACD,MAAM,WACJ,MAAM,KAAK,MAAM,cAAc,oBAAoB,cAAc;CACnE,IAAI,UAAU;EACZ,IACE,SAAS,WAAW,aACpB,KAAK,aAAa,wBAAwB,WAE1C,MAAM,KAAK,UAAU,cACnB,SAAS,YACT,8BAA8B,QAAQ,CACxC;EAEF,OAAO;CACT;CAEA,MAAM,UAAU,MAAM,yBAAyB;EAC7C;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,IAAI,SACF,MAAM,KAAK,UAAU,cAAc,kBAAkB,OAAO;CAG9D,OAAO;AACT;AAEA,eAAe,sBAAsB,EACnC,MACA,QAImB;CACnB,IAAI,gBAAgB;CACpB,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,CAAC,IAAI,YACP;EAGF,iBAAiB;EAEjB,KAAI,MADc,KAAK,MAAM,KAAK,IAAI,IAAI,UAAU,IAC3C,WAAW,aAClB,OAAO;CAEX;CAEA,OAAO,gBAAgB;AACzB;AAEA,SAAS,kBACP,MAC2B;CAC3B,OAAO,KAAK,MAAM,QAAQ,IAAI,aAAa,GAAG;AAChD;AAEA,SAAS,qBACP,MACoB;CACpB,OAAO,KAAK,MAAM,QAAQ,IAAI,gBAAgB,GAAG;AACnD;AAEA,SAAS,mBAAmB,MAAkD;CAC5E,OAAO,KAAK,MAAM,QAAQ,IAAI,cAAc,GAAG;AACjD;AAEA,SAAS,qCAAqC,EAC5C,MACA,oBAIS;CAKT,OAAO,uBAAuB,iBAAiB,GAJ/B,KACb,KAAK,QAAQ,IAAI,EAAE,EACnB,KAAK,EACL,KAAK,GACgD;AAC1D;AAEA,eAAe,yBAAyB,EACtC,MACA,gBACA,OACA,MACA,gBACA,gBACA,oBASiD;CACjD,MAAM,iBAAiB,OAAO,OAAO,WAAW,EAAE,WAAW,KAAK,EAAE;CACpE,MAAM,QAAQ,gBAAgB;CAC9B,OAAO,MAAM,KAAK,MAAM,YAAY,OAAO,OAAO;EAGhD,IAAI,MADI,GAAG,cAAc,oBAAoB,cAAc,GAEzD;EAaF,IAAI,EAAC,MAVkB,GAAG,cAAc,QAAQ;GAC9C;GACA;GACA;GACA;GACA;GACA;GACA,YAAY;GACZ,QAAQ;EACV,CAAC,GACa,IACZ;EAGF,MAAM,GAAG,KAAK,OAAO;GACnB,mBAAmB;GACnB,WAAW;GACX,MAAM;GACN;GACA,aAAa,KAAK,WAAW,IAAI,KAAK,IAAI,aAAa,KAAA;GACvD,WAAW;GACX;GACA,YAAY;GACZ,QAAQ;EACV,CAAC;EAYD,IAAI,EAAC,MAXoB,GAAG,YAAY,OACtC;GACE,cAAc,OAAO,WAAW;GAChC,OAAO;GACP;GACA,cAAc,CAAC;GACf,iBAAiB,CAAC;GAClB,SAAS;EACX,GACA,EAAE,iBAAiB,EAAE,CACvB,GACgB,IACd,MAAM,IAAI,MAAM,qDAAqD;EAEvE,OAAO;GAAE;GAAgB;GAAgB;EAAM;CACjD,CAAC;AACH;AAEA,SAAS,8BACP,cAC2B;CAC3B,OAAO;EACL,gBAAgB,aAAa;EAC7B,gBAAgB,aAAa;EAC7B,OAAO,aAAa;CACtB;AACF"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { enqueueDurableBackgroundNotification } from "./subagent-background-notification-inbox.js";
|
|
2
|
+
//#region src/subagent-background-notify.ts
|
|
3
|
+
function registerBackgroundJobGroup({ groupId, groups, job }) {
|
|
4
|
+
if (!groupId) return;
|
|
5
|
+
let group = groups.get(groupId);
|
|
6
|
+
if (!group) {
|
|
7
|
+
group = {
|
|
8
|
+
completedEvents: [],
|
|
9
|
+
failedNotifiedJobIds: /* @__PURE__ */ new Set(),
|
|
10
|
+
finalNotified: false,
|
|
11
|
+
id: groupId,
|
|
12
|
+
jobIds: /* @__PURE__ */ new Set()
|
|
13
|
+
};
|
|
14
|
+
groups.set(groupId, group);
|
|
15
|
+
}
|
|
16
|
+
group.jobIds.add(job.id);
|
|
17
|
+
}
|
|
18
|
+
async function notifyBackgroundCompletion({ endEvent, groups, job, jobs, parentSession }) {
|
|
19
|
+
const group = job.groupId ? groups.get(job.groupId) : void 0;
|
|
20
|
+
if (!group) {
|
|
21
|
+
await notifyParentSession({
|
|
22
|
+
input: buildBackgroundReminder([job]),
|
|
23
|
+
jobs: [job],
|
|
24
|
+
observerEvents: [endEvent],
|
|
25
|
+
parentSession
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
group.completedEvents.push(endEvent);
|
|
30
|
+
if (isFailureStatus(endEvent.status)) {
|
|
31
|
+
group.failedNotifiedJobIds.add(job.id);
|
|
32
|
+
await notifyParentSession({
|
|
33
|
+
input: buildBackgroundReminder([job]),
|
|
34
|
+
jobs: [job],
|
|
35
|
+
observerEvents: [endEvent],
|
|
36
|
+
parentSession
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (!isGroupSettled(group, jobs) || group.finalNotified) return;
|
|
40
|
+
group.finalNotified = true;
|
|
41
|
+
const notifyableGroupJobs = [...group.jobIds].map((id) => jobs.get(id)).filter(isDefinedJob).filter((groupJob) => !group.failedNotifiedJobIds.has(groupJob.id));
|
|
42
|
+
if (notifyableGroupJobs.length === 0) {
|
|
43
|
+
groups.delete(group.id);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const observerEvents = group.completedEvents.filter((event) => !group.failedNotifiedJobIds.has(event.task_id ?? ""));
|
|
47
|
+
await notifyParentSession({
|
|
48
|
+
input: buildBackgroundReminder(notifyableGroupJobs),
|
|
49
|
+
jobs: notifyableGroupJobs,
|
|
50
|
+
observerEvents: [...observerEvents],
|
|
51
|
+
parentSession
|
|
52
|
+
});
|
|
53
|
+
groups.delete(group.id);
|
|
54
|
+
}
|
|
55
|
+
async function notifyParentSession({ input, jobs, observerEvents, parentSession }) {
|
|
56
|
+
try {
|
|
57
|
+
if (await enqueueDurableBackgroundNotification({
|
|
58
|
+
input,
|
|
59
|
+
jobs,
|
|
60
|
+
observerEvents
|
|
61
|
+
}) === "queued-only") return;
|
|
62
|
+
await Promise.all(observerEvents.map((event) => parentSession.emitObserverEvent(event)));
|
|
63
|
+
await parentSession.notify(input, {
|
|
64
|
+
deferWhenUnobserved: true,
|
|
65
|
+
observerEvents
|
|
66
|
+
});
|
|
67
|
+
} catch (error) {
|
|
68
|
+
await parentSession.emitObserverEvent({
|
|
69
|
+
message: errorMessage(error),
|
|
70
|
+
type: "turn-error"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function buildBackgroundReminder(jobs) {
|
|
75
|
+
return {
|
|
76
|
+
text: jobs.length === 1 ? buildSingleJobReminder(jobs[0]) : buildGroupReminder(jobs),
|
|
77
|
+
type: "user-text"
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function buildSingleJobReminder(job) {
|
|
81
|
+
if (!job) return [
|
|
82
|
+
"<system-reminder>",
|
|
83
|
+
"[BACKGROUND TASK COMPLETED]",
|
|
84
|
+
"[SUBAGENT JOB RESULT READY]",
|
|
85
|
+
"A background task completed, but its task metadata is no longer available.",
|
|
86
|
+
"</system-reminder>"
|
|
87
|
+
].join("\n");
|
|
88
|
+
return [
|
|
89
|
+
"<system-reminder>",
|
|
90
|
+
"[BACKGROUND TASK COMPLETED]",
|
|
91
|
+
"[SUBAGENT JOB RESULT READY]",
|
|
92
|
+
`Task ID: ${job.id}`,
|
|
93
|
+
`Subagent: ${job.subagent}`,
|
|
94
|
+
`Status: ${job.status}`,
|
|
95
|
+
`Description: ${sanitizeReminderField(job.description ?? "")}`,
|
|
96
|
+
`Use background_output({ task_id: "${job.id}" }) to retrieve the result.`,
|
|
97
|
+
"</system-reminder>"
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
function buildGroupReminder(jobs) {
|
|
101
|
+
return [
|
|
102
|
+
"<system-reminder>",
|
|
103
|
+
"[ALL BACKGROUND TASKS COMPLETE]",
|
|
104
|
+
`Completed task count: ${jobs.length}`,
|
|
105
|
+
"Tasks:",
|
|
106
|
+
...jobs.map((job) => `- ${job.id} (${job.subagent}): ${job.status}. Description: ${sanitizeReminderField(job.description ?? "")}. Use background_output({ task_id: "${job.id}" }) to retrieve the result.`),
|
|
107
|
+
"</system-reminder>"
|
|
108
|
+
].join("\n");
|
|
109
|
+
}
|
|
110
|
+
function isGroupSettled(group, jobs) {
|
|
111
|
+
for (const id of group.jobIds) {
|
|
112
|
+
const job = jobs.get(id);
|
|
113
|
+
if (!job || isActiveJobStatus(job.status) || !job.settled) return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
function isActiveJobStatus(status) {
|
|
118
|
+
return status === "pending" || status === "running";
|
|
119
|
+
}
|
|
120
|
+
function isFailureStatus(status) {
|
|
121
|
+
return status === "aborted" || status === "cancelled" || status === "error";
|
|
122
|
+
}
|
|
123
|
+
function isDefinedJob(job) {
|
|
124
|
+
return job !== void 0;
|
|
125
|
+
}
|
|
126
|
+
function sanitizeReminderField(value) {
|
|
127
|
+
return value.replaceAll("\r", " ").replaceAll("\n", " ").replaceAll("<", "<").replaceAll(">", ">");
|
|
128
|
+
}
|
|
129
|
+
function errorMessage(error) {
|
|
130
|
+
if (error instanceof Error) return error.message;
|
|
131
|
+
return String(error);
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { notifyBackgroundCompletion, registerBackgroundJobGroup };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=subagent-background-notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-background-notify.js","names":[],"sources":["../src/subagent-background-notify.ts"],"sourcesContent":["import type { AgentEvent } from \"./session/events\";\nimport type { UserInput } from \"./session/input\";\nimport { enqueueDurableBackgroundNotification } from \"./subagent-background-notification-inbox\";\nimport type {\n RuntimeInputSink,\n SubagentJob,\n SubagentJobGroup,\n} from \"./subagent-types\";\n\nexport function registerBackgroundJobGroup({\n groupId,\n groups,\n job,\n}: {\n readonly groupId: string | undefined;\n readonly groups: Map<string, SubagentJobGroup>;\n readonly job: SubagentJob;\n}): void {\n if (!groupId) {\n return;\n }\n\n let group = groups.get(groupId);\n if (!group) {\n group = {\n completedEvents: [],\n failedNotifiedJobIds: new Set(),\n finalNotified: false,\n id: groupId,\n jobIds: new Set(),\n };\n groups.set(groupId, group);\n }\n group.jobIds.add(job.id);\n}\n\nexport async function notifyBackgroundCompletion({\n endEvent,\n groups,\n job,\n jobs,\n parentSession,\n}: {\n readonly endEvent: Extract<AgentEvent, { type: \"subagent-job-end\" }>;\n readonly groups: Map<string, SubagentJobGroup>;\n readonly job: SubagentJob;\n readonly jobs: Map<string, SubagentJob>;\n readonly parentSession: RuntimeInputSink;\n}): Promise<void> {\n const group = job.groupId ? groups.get(job.groupId) : undefined;\n if (!group) {\n await notifyParentSession({\n input: buildBackgroundReminder([job]),\n jobs: [job],\n observerEvents: [endEvent],\n parentSession,\n });\n return;\n }\n\n group.completedEvents.push(endEvent);\n if (isFailureStatus(endEvent.status)) {\n group.failedNotifiedJobIds.add(job.id);\n await notifyParentSession({\n input: buildBackgroundReminder([job]),\n jobs: [job],\n observerEvents: [endEvent],\n parentSession,\n });\n }\n\n if (!isGroupSettled(group, jobs) || group.finalNotified) {\n return;\n }\n\n group.finalNotified = true;\n const groupJobs = [...group.jobIds]\n .map((id) => jobs.get(id))\n .filter(isDefinedJob);\n const notifyableGroupJobs = groupJobs.filter(\n (groupJob) => !group.failedNotifiedJobIds.has(groupJob.id)\n );\n if (notifyableGroupJobs.length === 0) {\n groups.delete(group.id);\n return;\n }\n const observerEvents = group.completedEvents.filter(\n (event) => !group.failedNotifiedJobIds.has(event.task_id ?? \"\")\n );\n await notifyParentSession({\n input: buildBackgroundReminder(notifyableGroupJobs),\n jobs: notifyableGroupJobs,\n observerEvents: [...observerEvents],\n parentSession,\n });\n groups.delete(group.id);\n}\n\nasync function notifyParentSession({\n input,\n jobs,\n observerEvents,\n parentSession,\n}: {\n readonly input: UserInput;\n readonly jobs: readonly SubagentJob[];\n readonly observerEvents: readonly AgentEvent[];\n readonly parentSession: RuntimeInputSink;\n}): Promise<void> {\n try {\n const mode = await enqueueDurableBackgroundNotification({\n input,\n jobs,\n observerEvents,\n });\n if (mode === \"queued-only\") {\n return;\n }\n\n await Promise.all(\n observerEvents.map((event) => parentSession.emitObserverEvent(event))\n );\n await parentSession.notify(input, {\n deferWhenUnobserved: true,\n observerEvents,\n });\n } catch (error) {\n await parentSession.emitObserverEvent({\n message: errorMessage(error),\n type: \"turn-error\",\n });\n }\n}\n\nfunction buildBackgroundReminder(jobs: readonly SubagentJob[]): UserInput {\n const text =\n jobs.length === 1\n ? buildSingleJobReminder(jobs[0])\n : buildGroupReminder(jobs);\n return { text, type: \"user-text\" };\n}\n\nfunction buildSingleJobReminder(job: SubagentJob | undefined): string {\n if (!job) {\n return [\n \"<system-reminder>\",\n \"[BACKGROUND TASK COMPLETED]\",\n \"[SUBAGENT JOB RESULT READY]\",\n \"A background task completed, but its task metadata is no longer available.\",\n \"</system-reminder>\",\n ].join(\"\\n\");\n }\n\n return [\n \"<system-reminder>\",\n \"[BACKGROUND TASK COMPLETED]\",\n \"[SUBAGENT JOB RESULT READY]\",\n `Task ID: ${job.id}`,\n `Subagent: ${job.subagent}`,\n `Status: ${job.status}`,\n `Description: ${sanitizeReminderField(job.description ?? \"\")}`,\n `Use background_output({ task_id: \"${job.id}\" }) to retrieve the result.`,\n \"</system-reminder>\",\n ].join(\"\\n\");\n}\n\nfunction buildGroupReminder(jobs: readonly SubagentJob[]): string {\n return [\n \"<system-reminder>\",\n \"[ALL BACKGROUND TASKS COMPLETE]\",\n `Completed task count: ${jobs.length}`,\n \"Tasks:\",\n ...jobs.map(\n (job) =>\n `- ${job.id} (${job.subagent}): ${job.status}. Description: ${sanitizeReminderField(\n job.description ?? \"\"\n )}. Use background_output({ task_id: \"${job.id}\" }) to retrieve the result.`\n ),\n \"</system-reminder>\",\n ].join(\"\\n\");\n}\n\nfunction isGroupSettled(\n group: SubagentJobGroup,\n jobs: Map<string, SubagentJob>\n): boolean {\n for (const id of group.jobIds) {\n const job = jobs.get(id);\n if (!job || isActiveJobStatus(job.status) || !job.settled) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction isActiveJobStatus(status: SubagentJob[\"status\"]): boolean {\n return status === \"pending\" || status === \"running\";\n}\n\nfunction isFailureStatus(\n status: Extract<AgentEvent, { type: \"subagent-job-end\" }>[\"status\"]\n): boolean {\n return status === \"aborted\" || status === \"cancelled\" || status === \"error\";\n}\n\nfunction isDefinedJob(job: SubagentJob | undefined): job is SubagentJob {\n return job !== undefined;\n}\n\nfunction sanitizeReminderField(value: string): string {\n return value\n .replaceAll(\"\\r\", \" \")\n .replaceAll(\"\\n\", \" \")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n}\n\nfunction errorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readDurableBackgroundChildRunState } from "./subagent-background-child-run-state.js";
|
|
2
|
+
import { backgroundRunJobStatus, isActiveJob } from "./subagent-job-state.js";
|
|
3
|
+
//#region src/subagent-background-resume-group.ts
|
|
4
|
+
async function buildDurableResumeGroups({ currentJob, host, jobs, run, state }) {
|
|
5
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6
|
+
if (!(state.groupId && run.parentRunId)) return groups;
|
|
7
|
+
const group = {
|
|
8
|
+
completedEvents: [],
|
|
9
|
+
failedNotifiedJobIds: /* @__PURE__ */ new Set(),
|
|
10
|
+
finalNotified: false,
|
|
11
|
+
id: state.groupId,
|
|
12
|
+
jobIds: /* @__PURE__ */ new Set()
|
|
13
|
+
};
|
|
14
|
+
groups.set(group.id, group);
|
|
15
|
+
for (const sibling of await host.store.runs.listByParentRunId(run.parentRunId)) await addSiblingToGroup({
|
|
16
|
+
currentJob,
|
|
17
|
+
group,
|
|
18
|
+
host,
|
|
19
|
+
jobs,
|
|
20
|
+
run,
|
|
21
|
+
sibling,
|
|
22
|
+
state
|
|
23
|
+
});
|
|
24
|
+
return groups;
|
|
25
|
+
}
|
|
26
|
+
async function addSiblingToGroup({ currentJob, group, host, jobs, run, sibling, state }) {
|
|
27
|
+
if (sibling.kind !== "background-subagent") return;
|
|
28
|
+
const siblingState = readDurableBackgroundChildRunState(await host.store.checkpoints.latest(sibling.runId));
|
|
29
|
+
if (!siblingState || siblingState.groupId !== state.groupId) return;
|
|
30
|
+
const siblingJob = sibling.runId === run.runId ? currentJob : durableSiblingJob({
|
|
31
|
+
host,
|
|
32
|
+
run: sibling,
|
|
33
|
+
state: siblingState
|
|
34
|
+
});
|
|
35
|
+
jobs.set(siblingJob.id, siblingJob);
|
|
36
|
+
group.jobIds.add(siblingJob.id);
|
|
37
|
+
const endEvent = durableTerminalEvent(siblingJob, sibling.output);
|
|
38
|
+
if (!endEvent || sibling.runId === run.runId) return;
|
|
39
|
+
group.completedEvents.push(endEvent);
|
|
40
|
+
if (endEvent.status === "aborted" || endEvent.status === "cancelled" || endEvent.status === "error") group.failedNotifiedJobIds.add(siblingJob.id);
|
|
41
|
+
}
|
|
42
|
+
function durableSiblingJob({ host, run, state }) {
|
|
43
|
+
const status = backgroundRunJobStatus(run.status) ?? "pending";
|
|
44
|
+
const compactResult = compactSubagentResult(run.output);
|
|
45
|
+
return {
|
|
46
|
+
abort: () => void 0,
|
|
47
|
+
childRunId: run.runId,
|
|
48
|
+
cleanup: () => Promise.resolve(),
|
|
49
|
+
dedupeKey: run.dedupeKey,
|
|
50
|
+
delegateToolCallId: state.delegateToolCallId,
|
|
51
|
+
description: state.description,
|
|
52
|
+
executionHost: host,
|
|
53
|
+
groupId: state.groupId,
|
|
54
|
+
id: run.publicTaskId ?? run.runId,
|
|
55
|
+
parentRunId: run.parentRunId,
|
|
56
|
+
parentSessionKey: state.parentSessionKey,
|
|
57
|
+
promise: Promise.resolve(),
|
|
58
|
+
result: compactResult,
|
|
59
|
+
sessionKey: run.sessionKey,
|
|
60
|
+
settled: !isActiveJob(status),
|
|
61
|
+
status,
|
|
62
|
+
subagent: state.subagent
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function durableTerminalEvent(job, output) {
|
|
66
|
+
if (job.status !== "completed" && job.status !== "cancelled") return null;
|
|
67
|
+
const result = compactSubagentResult(output);
|
|
68
|
+
return {
|
|
69
|
+
error: result?.error,
|
|
70
|
+
eventCount: result?.eventCount ?? 0,
|
|
71
|
+
delegateToolCallId: job.delegateToolCallId,
|
|
72
|
+
status: job.status,
|
|
73
|
+
subagent: job.subagent,
|
|
74
|
+
task_id: job.id,
|
|
75
|
+
type: "subagent-job-end"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function compactSubagentResult(output) {
|
|
79
|
+
if (!isRecord(output)) return;
|
|
80
|
+
if (typeof output.eventCount !== "number" || output.run_in_background !== false || typeof output.subagent !== "string" || typeof output.text !== "string" || !isCompactResultStatus(output.result)) return;
|
|
81
|
+
return {
|
|
82
|
+
...typeof output.error === "string" ? { error: output.error } : {},
|
|
83
|
+
eventCount: output.eventCount,
|
|
84
|
+
result: output.result,
|
|
85
|
+
run_in_background: false,
|
|
86
|
+
subagent: output.subagent,
|
|
87
|
+
text: output.text
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function isCompactResultStatus(value) {
|
|
91
|
+
return value === "aborted" || value === "completed" || value === "error";
|
|
92
|
+
}
|
|
93
|
+
function isRecord(value) {
|
|
94
|
+
return typeof value === "object" && value !== null;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { buildDurableResumeGroups };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=subagent-background-resume-group.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-background-resume-group.js","names":[],"sources":["../src/subagent-background-resume-group.ts"],"sourcesContent":["import type { ExecutionHost, RunRecord } from \"./execution/types\";\nimport type { AgentEvent } from \"./session/events\";\nimport type { DurableBackgroundChildRunState } from \"./subagent-background-child-run-state\";\nimport { readDurableBackgroundChildRunState } from \"./subagent-background-child-run-state\";\nimport { backgroundRunJobStatus, isActiveJob } from \"./subagent-job-state\";\nimport type {\n CompactSubagentResult,\n SubagentJob,\n SubagentJobGroup,\n} from \"./subagent-types\";\n\nexport async function buildDurableResumeGroups({\n currentJob,\n host,\n jobs,\n run,\n state,\n}: {\n readonly currentJob: SubagentJob;\n readonly host: ExecutionHost;\n readonly jobs: Map<string, SubagentJob>;\n readonly run: RunRecord;\n readonly state: DurableBackgroundChildRunState;\n}): Promise<Map<string, SubagentJobGroup>> {\n const groups = new Map<string, SubagentJobGroup>();\n if (!(state.groupId && run.parentRunId)) {\n return groups;\n }\n\n const group: SubagentJobGroup = {\n completedEvents: [],\n failedNotifiedJobIds: new Set(),\n finalNotified: false,\n id: state.groupId,\n jobIds: new Set(),\n };\n groups.set(group.id, group);\n\n for (const sibling of await host.store.runs.listByParentRunId(\n run.parentRunId\n )) {\n await addSiblingToGroup({\n currentJob,\n group,\n host,\n jobs,\n run,\n sibling,\n state,\n });\n }\n\n return groups;\n}\n\nasync function addSiblingToGroup({\n currentJob,\n group,\n host,\n jobs,\n run,\n sibling,\n state,\n}: {\n readonly currentJob: SubagentJob;\n readonly group: SubagentJobGroup;\n readonly host: ExecutionHost;\n readonly jobs: Map<string, SubagentJob>;\n readonly run: RunRecord;\n readonly sibling: RunRecord;\n readonly state: DurableBackgroundChildRunState;\n}): Promise<void> {\n if (sibling.kind !== \"background-subagent\") {\n return;\n }\n\n const siblingState = readDurableBackgroundChildRunState(\n await host.store.checkpoints.latest(sibling.runId)\n );\n if (!siblingState || siblingState.groupId !== state.groupId) {\n return;\n }\n\n const siblingJob =\n sibling.runId === run.runId\n ? currentJob\n : durableSiblingJob({ host, run: sibling, state: siblingState });\n jobs.set(siblingJob.id, siblingJob);\n group.jobIds.add(siblingJob.id);\n\n const endEvent = durableTerminalEvent(siblingJob, sibling.output);\n if (!endEvent || sibling.runId === run.runId) {\n return;\n }\n group.completedEvents.push(endEvent);\n if (\n endEvent.status === \"aborted\" ||\n endEvent.status === \"cancelled\" ||\n endEvent.status === \"error\"\n ) {\n group.failedNotifiedJobIds.add(siblingJob.id);\n }\n}\n\nfunction durableSiblingJob({\n host,\n run,\n state,\n}: {\n readonly host: ExecutionHost;\n readonly run: RunRecord;\n readonly state: DurableBackgroundChildRunState;\n}): SubagentJob {\n const status = backgroundRunJobStatus(run.status) ?? \"pending\";\n const compactResult = compactSubagentResult(run.output);\n return {\n abort: () => undefined,\n childRunId: run.runId,\n cleanup: () => Promise.resolve(),\n dedupeKey: run.dedupeKey,\n delegateToolCallId: state.delegateToolCallId,\n description: state.description,\n executionHost: host,\n groupId: state.groupId,\n id: run.publicTaskId ?? run.runId,\n parentRunId: run.parentRunId,\n parentSessionKey: state.parentSessionKey,\n promise: Promise.resolve(),\n result: compactResult,\n sessionKey: run.sessionKey,\n settled: !isActiveJob(status),\n status,\n subagent: state.subagent,\n };\n}\n\nfunction durableTerminalEvent(\n job: SubagentJob,\n output: unknown\n): Extract<AgentEvent, { type: \"subagent-job-end\" }> | null {\n if (job.status !== \"completed\" && job.status !== \"cancelled\") {\n return null;\n }\n\n const result = compactSubagentResult(output);\n return {\n error: result?.error,\n eventCount: result?.eventCount ?? 0,\n delegateToolCallId: job.delegateToolCallId,\n status: job.status,\n subagent: job.subagent,\n task_id: job.id,\n type: \"subagent-job-end\",\n };\n}\n\nfunction compactSubagentResult(\n output: unknown\n): CompactSubagentResult | undefined {\n if (!isRecord(output)) {\n return;\n }\n\n if (\n typeof output.eventCount !== \"number\" ||\n output.run_in_background !== false ||\n typeof output.subagent !== \"string\" ||\n typeof output.text !== \"string\" ||\n !isCompactResultStatus(output.result)\n ) {\n return;\n }\n\n return {\n ...(typeof output.error === \"string\" ? { error: output.error } : {}),\n eventCount: output.eventCount,\n result: output.result,\n run_in_background: false,\n subagent: output.subagent,\n text: output.text,\n };\n}\n\nfunction isCompactResultStatus(\n value: unknown\n): value is CompactSubagentResult[\"result\"] {\n return value === \"aborted\" || value === \"completed\" || value === \"error\";\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { childRunStatus, updateBackgroundRunStatus } from "./subagent-background-child-run.js";
|
|
2
|
+
import { notifyBackgroundCompletion } from "./subagent-background-notify.js";
|
|
3
|
+
import { emitBackgroundJobUpdate } from "./subagent-job-observer.js";
|
|
4
|
+
import { collectSubagentRunWithEvents } from "./subagent-run.js";
|
|
5
|
+
//#region src/subagent-background-runner.ts
|
|
6
|
+
const durableCancelPollMs = 250;
|
|
7
|
+
async function runBackgroundJob({ childSession, groups, jobs, job, parentSession, prompt }) {
|
|
8
|
+
if (job.status === "cancelled") return;
|
|
9
|
+
job.status = "running";
|
|
10
|
+
if (await syncDurableCancellation(job, childSession)) return;
|
|
11
|
+
const stopCancelWatcher = startDurableCancellationWatcher(job, childSession);
|
|
12
|
+
try {
|
|
13
|
+
const { result } = await collectSubagentRunWithEvents(await childSession.send(prompt), job.subagent, (event) => emitBackgroundJobUpdate(parentSession, job, event));
|
|
14
|
+
if (await syncDurableCancellation(job, childSession)) return;
|
|
15
|
+
const previousResult = job.result;
|
|
16
|
+
job.result = result;
|
|
17
|
+
if (!await updateBackgroundRunStatus(job, childRunStatus(result.result))) {
|
|
18
|
+
job.result = previousResult;
|
|
19
|
+
job.status = "cancelled";
|
|
20
|
+
childSession.interrupt();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
job.status = result.result;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (await syncDurableCancellation(job, childSession)) return;
|
|
26
|
+
const jobError = error instanceof Error ? error : new Error(String(error));
|
|
27
|
+
const previousResult = job.result;
|
|
28
|
+
job.result = {
|
|
29
|
+
error: errorMessage(jobError),
|
|
30
|
+
eventCount: 0,
|
|
31
|
+
result: "error",
|
|
32
|
+
run_in_background: false,
|
|
33
|
+
subagent: job.subagent,
|
|
34
|
+
text: ""
|
|
35
|
+
};
|
|
36
|
+
if (!await updateBackgroundRunStatus(job, "error")) {
|
|
37
|
+
job.result = previousResult;
|
|
38
|
+
job.status = "cancelled";
|
|
39
|
+
childSession.interrupt();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
job.status = "error";
|
|
43
|
+
} finally {
|
|
44
|
+
stopCancelWatcher();
|
|
45
|
+
}
|
|
46
|
+
if (await syncDurableCancellation(job, childSession)) return;
|
|
47
|
+
job.settled = true;
|
|
48
|
+
await notifyBackgroundCompletion({
|
|
49
|
+
endEvent: {
|
|
50
|
+
error: job.result?.error,
|
|
51
|
+
eventCount: job.result?.eventCount ?? 0,
|
|
52
|
+
delegateToolCallId: job.delegateToolCallId,
|
|
53
|
+
status: job.result?.result ?? "error",
|
|
54
|
+
subagent: job.subagent,
|
|
55
|
+
task_id: job.id,
|
|
56
|
+
type: "subagent-job-end"
|
|
57
|
+
},
|
|
58
|
+
groups,
|
|
59
|
+
job,
|
|
60
|
+
jobs,
|
|
61
|
+
parentSession
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function startDurableCancellationWatcher(job, childSession) {
|
|
65
|
+
let stopped = false;
|
|
66
|
+
let timeoutId;
|
|
67
|
+
const poll = async () => {
|
|
68
|
+
if (stopped) return;
|
|
69
|
+
await syncDurableCancellation(job, childSession);
|
|
70
|
+
if (stopped || job.status === "cancelled") return;
|
|
71
|
+
timeoutId = setTimeout(() => {
|
|
72
|
+
queueCancelPoll(poll, job, childSession);
|
|
73
|
+
}, durableCancelPollMs);
|
|
74
|
+
};
|
|
75
|
+
queueCancelPoll(poll, job, childSession);
|
|
76
|
+
return () => {
|
|
77
|
+
stopped = true;
|
|
78
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function queueCancelPoll(poll, job, childSession) {
|
|
82
|
+
poll().catch((error) => {
|
|
83
|
+
job.status = "error";
|
|
84
|
+
job.result = {
|
|
85
|
+
error: errorMessage(error),
|
|
86
|
+
eventCount: 0,
|
|
87
|
+
result: "error",
|
|
88
|
+
run_in_background: false,
|
|
89
|
+
subagent: job.subagent,
|
|
90
|
+
text: ""
|
|
91
|
+
};
|
|
92
|
+
childSession.interrupt();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function syncDurableCancellation(job, childSession) {
|
|
96
|
+
if (job.status === "cancelled") {
|
|
97
|
+
childSession.interrupt();
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (!(job.executionHost && job.childRunId)) return false;
|
|
101
|
+
const run = await job.executionHost.store.runs.get(job.childRunId);
|
|
102
|
+
const leaseLost = job.childRunLeaseId && run?.lease?.leaseId !== job.childRunLeaseId;
|
|
103
|
+
if (run?.status !== "cancelled" && !leaseLost) return false;
|
|
104
|
+
job.status = "cancelled";
|
|
105
|
+
childSession.interrupt();
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
function errorMessage(error) {
|
|
109
|
+
if (error instanceof Error) return error.message;
|
|
110
|
+
return String(error);
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { runBackgroundJob };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=subagent-background-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-background-runner.js","names":[],"sources":["../src/subagent-background-runner.ts"],"sourcesContent":["import type { AgentInput } from \"./session/input\";\nimport {\n childRunStatus,\n updateBackgroundRunStatus,\n} from \"./subagent-background-child-run\";\nimport { notifyBackgroundCompletion } from \"./subagent-background-notify\";\nimport { emitBackgroundJobUpdate } from \"./subagent-job-observer\";\nimport { collectSubagentRunWithEvents } from \"./subagent-run\";\nimport type {\n RuntimeInputSink,\n Subagent,\n SubagentJob,\n SubagentJobGroup,\n} from \"./subagent-types\";\n\ntype BackgroundChildSession = ReturnType<Subagent[\"session\"]>;\n\nconst durableCancelPollMs = 250;\n\nexport async function runBackgroundJob({\n childSession,\n groups,\n jobs,\n job,\n parentSession,\n prompt,\n}: {\n readonly childSession: BackgroundChildSession;\n readonly groups: Map<string, SubagentJobGroup>;\n readonly jobs: Map<string, SubagentJob>;\n readonly job: SubagentJob;\n readonly parentSession: RuntimeInputSink;\n readonly prompt: AgentInput;\n}): Promise<void> {\n if (job.status === \"cancelled\") {\n return;\n }\n job.status = \"running\";\n if (await syncDurableCancellation(job, childSession)) {\n return;\n }\n\n const stopCancelWatcher = startDurableCancellationWatcher(job, childSession);\n try {\n const { result } = await collectSubagentRunWithEvents(\n await childSession.send(prompt),\n job.subagent,\n (event) => emitBackgroundJobUpdate(parentSession, job, event)\n );\n if (await syncDurableCancellation(job, childSession)) {\n return;\n }\n const previousResult = job.result;\n job.result = result;\n const updated = await updateBackgroundRunStatus(\n job,\n childRunStatus(result.result)\n );\n if (!updated) {\n job.result = previousResult;\n job.status = \"cancelled\";\n childSession.interrupt();\n return;\n }\n job.status = result.result;\n } catch (error) {\n if (await syncDurableCancellation(job, childSession)) {\n return;\n }\n const jobError = error instanceof Error ? error : new Error(String(error));\n const previousResult = job.result;\n job.result = {\n error: errorMessage(jobError),\n eventCount: 0,\n result: \"error\",\n run_in_background: false,\n subagent: job.subagent,\n text: \"\",\n };\n const updated = await updateBackgroundRunStatus(job, \"error\");\n if (!updated) {\n job.result = previousResult;\n job.status = \"cancelled\";\n childSession.interrupt();\n return;\n }\n job.status = \"error\";\n } finally {\n stopCancelWatcher();\n }\n\n if (await syncDurableCancellation(job, childSession)) {\n return;\n }\n\n job.settled = true;\n await notifyBackgroundCompletion({\n endEvent: {\n error: job.result?.error,\n eventCount: job.result?.eventCount ?? 0,\n delegateToolCallId: job.delegateToolCallId,\n status: job.result?.result ?? \"error\",\n subagent: job.subagent,\n task_id: job.id,\n type: \"subagent-job-end\",\n },\n groups,\n job,\n jobs,\n parentSession,\n });\n}\n\nfunction startDurableCancellationWatcher(\n job: SubagentJob,\n childSession: BackgroundChildSession\n): () => void {\n let stopped = false;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const poll = async (): Promise<void> => {\n if (stopped) {\n return;\n }\n await syncDurableCancellation(job, childSession);\n if (stopped || job.status === \"cancelled\") {\n return;\n }\n timeoutId = setTimeout(() => {\n queueCancelPoll(poll, job, childSession);\n }, durableCancelPollMs);\n };\n\n queueCancelPoll(poll, job, childSession);\n return () => {\n stopped = true;\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n };\n}\n\nfunction queueCancelPoll(\n poll: () => Promise<void>,\n job: SubagentJob,\n childSession: BackgroundChildSession\n): void {\n poll().catch((error: unknown) => {\n job.status = \"error\";\n job.result = {\n error: errorMessage(error),\n eventCount: 0,\n result: \"error\",\n run_in_background: false,\n subagent: job.subagent,\n text: \"\",\n };\n childSession.interrupt();\n });\n}\n\nasync function syncDurableCancellation(\n job: SubagentJob,\n childSession: BackgroundChildSession\n): Promise<boolean> {\n if (job.status === \"cancelled\") {\n childSession.interrupt();\n return true;\n }\n if (!(job.executionHost && job.childRunId)) {\n return false;\n }\n\n const run = await job.executionHost.store.runs.get(job.childRunId);\n const leaseLost =\n job.childRunLeaseId && run?.lease?.leaseId !== job.childRunLeaseId;\n if (run?.status !== \"cancelled\" && !leaseLost) {\n return false;\n }\n\n job.status = \"cancelled\";\n childSession.interrupt();\n return true;\n}\n\nfunction errorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { registerBackgroundJobGroup } from "./subagent-background-notify.js";
|
|
2
|
+
//#region src/subagent-background-schedule.ts
|
|
3
|
+
async function scheduleDurableBackgroundJob({ childRun, delegateToolCallId, description, executionHost, groupId, groups, id, jobs, parentRunId, parentSession, parentSessionKey, ownerNamespace, subagent }) {
|
|
4
|
+
const job = {
|
|
5
|
+
abort: () => void 0,
|
|
6
|
+
childRunId: childRun.runId,
|
|
7
|
+
cleanup: () => Promise.resolve(),
|
|
8
|
+
dedupeKey: childRun.dedupeKey,
|
|
9
|
+
description,
|
|
10
|
+
id,
|
|
11
|
+
delegateToolCallId,
|
|
12
|
+
executionHost,
|
|
13
|
+
ownerNamespace,
|
|
14
|
+
parentSessionKey,
|
|
15
|
+
parentRunId,
|
|
16
|
+
promise: Promise.resolve(),
|
|
17
|
+
groupId,
|
|
18
|
+
sessionKey: childRun.sessionKey,
|
|
19
|
+
settled: true,
|
|
20
|
+
status: "pending",
|
|
21
|
+
subagent
|
|
22
|
+
};
|
|
23
|
+
jobs.set(id, job);
|
|
24
|
+
registerBackgroundJobGroup({
|
|
25
|
+
groupId,
|
|
26
|
+
groups,
|
|
27
|
+
job
|
|
28
|
+
});
|
|
29
|
+
await parentSession.emitObserverEvent({
|
|
30
|
+
description,
|
|
31
|
+
delegateToolCallId,
|
|
32
|
+
run_in_background: true,
|
|
33
|
+
subagent,
|
|
34
|
+
task_id: id,
|
|
35
|
+
type: "subagent-job-start"
|
|
36
|
+
});
|
|
37
|
+
await executionHost.scheduler.enqueueRun(childRun.runId);
|
|
38
|
+
return job;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { scheduleDurableBackgroundJob };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=subagent-background-schedule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-background-schedule.js","names":[],"sources":["../src/subagent-background-schedule.ts"],"sourcesContent":["import type { RunRecord } from \"./execution/types\";\nimport { registerBackgroundJobGroup } from \"./subagent-background-notify\";\nimport type {\n RuntimeInputSink,\n SubagentJob,\n SubagentJobGroup,\n} from \"./subagent-types\";\n\nexport async function scheduleDurableBackgroundJob({\n childRun,\n delegateToolCallId,\n description,\n executionHost,\n groupId,\n groups,\n id,\n jobs,\n parentRunId,\n parentSession,\n parentSessionKey,\n ownerNamespace,\n subagent,\n}: {\n readonly childRun: RunRecord;\n readonly delegateToolCallId?: string;\n readonly description?: string;\n readonly executionHost: NonNullable<SubagentJob[\"executionHost\"]>;\n readonly groupId?: string;\n readonly groups: Map<string, SubagentJobGroup>;\n readonly id: string;\n readonly jobs: Map<string, SubagentJob>;\n readonly parentRunId?: string;\n readonly parentSession: RuntimeInputSink;\n readonly parentSessionKey?: string;\n readonly ownerNamespace?: string;\n readonly subagent: string;\n}): Promise<SubagentJob> {\n const job: SubagentJob = {\n abort: () => undefined,\n childRunId: childRun.runId,\n cleanup: () => Promise.resolve(),\n dedupeKey: childRun.dedupeKey,\n description,\n id,\n delegateToolCallId,\n executionHost,\n ownerNamespace,\n parentSessionKey,\n parentRunId,\n promise: Promise.resolve(),\n groupId,\n sessionKey: childRun.sessionKey,\n settled: true,\n status: \"pending\",\n subagent,\n };\n jobs.set(id, job);\n registerBackgroundJobGroup({ groupId, groups, job });\n await parentSession.emitObserverEvent({\n description,\n delegateToolCallId,\n run_in_background: true,\n subagent,\n task_id: id,\n type: \"subagent-job-start\",\n });\n await executionHost.scheduler.enqueueRun(childRun.runId);\n return job;\n}\n"],"mappings":";;AAQA,eAAsB,6BAA6B,EACjD,UACA,oBACA,aACA,eACA,SACA,QACA,IACA,MACA,aACA,eACA,kBACA,gBACA,YAeuB;CACvB,MAAM,MAAmB;EACvB,aAAa,KAAA;EACb,YAAY,SAAS;EACrB,eAAe,QAAQ,QAAQ;EAC/B,WAAW,SAAS;EACpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAAS,QAAQ,QAAQ;EACzB;EACA,YAAY,SAAS;EACrB,SAAS;EACT,QAAQ;EACR;CACF;CACA,KAAK,IAAI,IAAI,GAAG;CAChB,2BAA2B;EAAE;EAAS;EAAQ;CAAI,CAAC;CACnD,MAAM,cAAc,kBAAkB;EACpC;EACA;EACA,mBAAmB;EACnB;EACA,SAAS;EACT,MAAM;CACR,CAAC;CACD,MAAM,cAAc,UAAU,WAAW,SAAS,KAAK;CACvD,OAAO;AACT"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { runBlockingDelegation } from "./subagent-run.js";
|
|
2
|
+
//#region src/subagent-child-run.ts
|
|
3
|
+
async function runBlockingChild({ abortSignal, dedupeKey, executionHost, parentRunId, prompt, sessionKey, subagent }) {
|
|
4
|
+
const run = await getOrCreateBlockingChildRun({
|
|
5
|
+
dedupeKey,
|
|
6
|
+
executionHost,
|
|
7
|
+
parentRunId,
|
|
8
|
+
sessionKey
|
|
9
|
+
});
|
|
10
|
+
const result = await runBlockingDelegation({
|
|
11
|
+
abortSignal,
|
|
12
|
+
prompt,
|
|
13
|
+
sessionKey,
|
|
14
|
+
subagent
|
|
15
|
+
});
|
|
16
|
+
if (executionHost && run) await executionHost.store.runs.update({
|
|
17
|
+
...run,
|
|
18
|
+
checkpointVersion: run.checkpointVersion,
|
|
19
|
+
status: childRunStatus(result.result)
|
|
20
|
+
});
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
function blockingSubagentDedupeKey(parentRunId, toolCallId) {
|
|
24
|
+
return `blocking-subagent:${parentRunId}:${toolCallId ?? "unknown"}`;
|
|
25
|
+
}
|
|
26
|
+
async function getOrCreateBlockingChildRun({ dedupeKey, executionHost, parentRunId, sessionKey }) {
|
|
27
|
+
if (!executionHost) return;
|
|
28
|
+
const existing = await executionHost.store.runs.getByDedupeKey(dedupeKey);
|
|
29
|
+
if (existing) return existing;
|
|
30
|
+
const run = {
|
|
31
|
+
checkpointVersion: 0,
|
|
32
|
+
dedupeKey,
|
|
33
|
+
kind: "subagent",
|
|
34
|
+
parentRunId,
|
|
35
|
+
rootRunId: parentRunId,
|
|
36
|
+
runId: `child:${dedupeKey}`,
|
|
37
|
+
sessionKey,
|
|
38
|
+
status: "queued"
|
|
39
|
+
};
|
|
40
|
+
await executionHost.store.runs.create(run);
|
|
41
|
+
await executionHost.store.checkpoints.append({
|
|
42
|
+
checkpointId: crypto.randomUUID(),
|
|
43
|
+
phase: "before-child-run",
|
|
44
|
+
runId: run.runId,
|
|
45
|
+
runtimeState: {},
|
|
46
|
+
sessionSnapshot: {},
|
|
47
|
+
version: 1
|
|
48
|
+
}, { expectedVersion: 0 });
|
|
49
|
+
await executionHost.store.checkpoints.append({
|
|
50
|
+
checkpointId: crypto.randomUUID(),
|
|
51
|
+
childRunId: run.runId,
|
|
52
|
+
phase: "child-linked",
|
|
53
|
+
runId: run.runId,
|
|
54
|
+
runtimeState: {},
|
|
55
|
+
sessionSnapshot: {},
|
|
56
|
+
version: 2
|
|
57
|
+
}, { expectedVersion: 1 });
|
|
58
|
+
return await executionHost.store.runs.get(run.runId) ?? run;
|
|
59
|
+
}
|
|
60
|
+
function childRunStatus(result) {
|
|
61
|
+
if (result === "aborted") return "cancelled";
|
|
62
|
+
if (result === "error") return "error";
|
|
63
|
+
return "completed";
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { blockingSubagentDedupeKey, runBlockingChild };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=subagent-child-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-child-run.js","names":[],"sources":["../src/subagent-child-run.ts"],"sourcesContent":["import type { ExecutionHost, RunRecord, RunStatus } from \"./execution/types\";\nimport type { AgentInput } from \"./session/input\";\nimport { runBlockingDelegation } from \"./subagent-run\";\nimport type { CompactSubagentResult, Subagent } from \"./subagent-types\";\n\nexport type BlockingSubagentRunCache = Map<\n string,\n Promise<CompactSubagentResult>\n>;\n\nexport async function runBlockingChild({\n abortSignal,\n dedupeKey,\n executionHost,\n parentRunId,\n prompt,\n sessionKey,\n subagent,\n}: {\n readonly abortSignal?: AbortSignal;\n readonly dedupeKey: string;\n readonly executionHost?: ExecutionHost;\n readonly parentRunId: string;\n readonly prompt: AgentInput;\n readonly sessionKey: string;\n readonly subagent: Subagent;\n}): Promise<CompactSubagentResult> {\n const run = await getOrCreateBlockingChildRun({\n dedupeKey,\n executionHost,\n parentRunId,\n sessionKey,\n });\n const result = await runBlockingDelegation({\n abortSignal,\n prompt,\n sessionKey,\n subagent,\n });\n if (executionHost && run) {\n await executionHost.store.runs.update({\n ...run,\n checkpointVersion: run.checkpointVersion,\n status: childRunStatus(result.result),\n });\n }\n return result;\n}\n\nexport function blockingSubagentDedupeKey(\n parentRunId: string,\n toolCallId: string | undefined\n): string {\n return `blocking-subagent:${parentRunId}:${toolCallId ?? \"unknown\"}`;\n}\n\nasync function getOrCreateBlockingChildRun({\n dedupeKey,\n executionHost,\n parentRunId,\n sessionKey,\n}: {\n readonly dedupeKey: string;\n readonly executionHost?: ExecutionHost;\n readonly parentRunId: string;\n readonly sessionKey: string;\n}): Promise<RunRecord | undefined> {\n if (!executionHost) {\n return;\n }\n\n const existing = await executionHost.store.runs.getByDedupeKey(dedupeKey);\n if (existing) {\n return existing;\n }\n\n const run: RunRecord = {\n checkpointVersion: 0,\n dedupeKey,\n kind: \"subagent\",\n parentRunId,\n rootRunId: parentRunId,\n runId: `child:${dedupeKey}`,\n sessionKey,\n status: \"queued\",\n };\n await executionHost.store.runs.create(run);\n await executionHost.store.checkpoints.append(\n {\n checkpointId: crypto.randomUUID(),\n phase: \"before-child-run\",\n runId: run.runId,\n runtimeState: {},\n sessionSnapshot: {},\n version: 1,\n },\n { expectedVersion: 0 }\n );\n await executionHost.store.checkpoints.append(\n {\n checkpointId: crypto.randomUUID(),\n childRunId: run.runId,\n phase: \"child-linked\",\n runId: run.runId,\n runtimeState: {},\n sessionSnapshot: {},\n version: 2,\n },\n { expectedVersion: 1 }\n );\n const linked = await executionHost.store.runs.get(run.runId);\n return linked ?? run;\n}\n\nfunction childRunStatus(\n result: CompactSubagentResult[\"result\"]\n): Extract<RunStatus, \"cancelled\" | \"completed\" | \"error\"> {\n if (result === \"aborted\") {\n return \"cancelled\";\n }\n\n if (result === \"error\") {\n return \"error\";\n }\n\n return \"completed\";\n}\n"],"mappings":";;AAUA,eAAsB,iBAAiB,EACrC,aACA,WACA,eACA,aACA,QACA,YACA,YASiC;CACjC,MAAM,MAAM,MAAM,4BAA4B;EAC5C;EACA;EACA;EACA;CACF,CAAC;CACD,MAAM,SAAS,MAAM,sBAAsB;EACzC;EACA;EACA;EACA;CACF,CAAC;CACD,IAAI,iBAAiB,KACnB,MAAM,cAAc,MAAM,KAAK,OAAO;EACpC,GAAG;EACH,mBAAmB,IAAI;EACvB,QAAQ,eAAe,OAAO,MAAM;CACtC,CAAC;CAEH,OAAO;AACT;AAEA,SAAgB,0BACd,aACA,YACQ;CACR,OAAO,qBAAqB,YAAY,GAAG,cAAc;AAC3D;AAEA,eAAe,4BAA4B,EACzC,WACA,eACA,aACA,cAMiC;CACjC,IAAI,CAAC,eACH;CAGF,MAAM,WAAW,MAAM,cAAc,MAAM,KAAK,eAAe,SAAS;CACxE,IAAI,UACF,OAAO;CAGT,MAAM,MAAiB;EACrB,mBAAmB;EACnB;EACA,MAAM;EACN;EACA,WAAW;EACX,OAAO,SAAS;EAChB;EACA,QAAQ;CACV;CACA,MAAM,cAAc,MAAM,KAAK,OAAO,GAAG;CACzC,MAAM,cAAc,MAAM,YAAY,OACpC;EACE,cAAc,OAAO,WAAW;EAChC,OAAO;EACP,OAAO,IAAI;EACX,cAAc,CAAC;EACf,iBAAiB,CAAC;EAClB,SAAS;CACX,GACA,EAAE,iBAAiB,EAAE,CACvB;CACA,MAAM,cAAc,MAAM,YAAY,OACpC;EACE,cAAc,OAAO,WAAW;EAChC,YAAY,IAAI;EAChB,OAAO;EACP,OAAO,IAAI;EACX,cAAc,CAAC;EACf,iBAAiB,CAAC;EAClB,SAAS;CACX,GACA,EAAE,iBAAiB,EAAE,CACvB;CAEA,OAAO,MADc,cAAc,MAAM,KAAK,IAAI,IAAI,KAAK,KAC1C;AACnB;AAEA,SAAS,eACP,QACyD;CACzD,IAAI,WAAW,WACb,OAAO;CAGT,IAAI,WAAW,SACb,OAAO;CAGT,OAAO;AACT"}
|