@minpeter/pss-runtime 0.1.0-next.1 → 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.
Files changed (101) hide show
  1. package/README.md +169 -28
  2. package/dist/agent-child-runs.js +16 -0
  3. package/dist/agent-child-runs.js.map +1 -0
  4. package/dist/agent-host-capabilities.js +9 -0
  5. package/dist/agent-host-capabilities.js.map +1 -0
  6. package/dist/agent-host-session-store.js +12 -0
  7. package/dist/agent-host-session-store.js.map +1 -0
  8. package/dist/agent-loop.js +58 -28
  9. package/dist/agent-loop.js.map +1 -1
  10. package/dist/agent-namespace.js +8 -1
  11. package/dist/agent-namespace.js.map +1 -1
  12. package/dist/agent-options.d.ts +35 -0
  13. package/dist/agent-options.js +16 -0
  14. package/dist/agent-options.js.map +1 -0
  15. package/dist/agent-resume.js +143 -0
  16. package/dist/agent-resume.js.map +1 -0
  17. package/dist/agent-session-entry.d.ts +13 -0
  18. package/dist/agent-validation.js +2 -2
  19. package/dist/agent-validation.js.map +1 -1
  20. package/dist/agent.d.ts +5 -41
  21. package/dist/agent.js +81 -49
  22. package/dist/agent.js.map +1 -1
  23. package/dist/execution/host.js +14 -0
  24. package/dist/execution/host.js.map +1 -0
  25. package/dist/execution/index.d.ts +4 -0
  26. package/dist/execution/index.js +3 -0
  27. package/dist/execution/memory-notifications.js +54 -0
  28. package/dist/execution/memory-notifications.js.map +1 -0
  29. package/dist/execution/memory-state.js +34 -0
  30. package/dist/execution/memory-state.js.map +1 -0
  31. package/dist/execution/memory-store.js +203 -0
  32. package/dist/execution/memory-store.js.map +1 -0
  33. package/dist/execution/memory.d.ts +7 -0
  34. package/dist/execution/memory.js +28 -0
  35. package/dist/execution/memory.js.map +1 -0
  36. package/dist/execution/run.js +55 -0
  37. package/dist/execution/run.js.map +1 -0
  38. package/dist/execution/types.d.ts +155 -0
  39. package/dist/index.d.ts +8 -5
  40. package/dist/llm-tool-execution.d.ts +35 -0
  41. package/dist/llm-tool-execution.js +126 -0
  42. package/dist/llm-tool-execution.js.map +1 -0
  43. package/dist/llm.d.ts +11 -15
  44. package/dist/llm.js +5 -3
  45. package/dist/llm.js.map +1 -1
  46. package/dist/plugins.d.ts +20 -0
  47. package/dist/plugins.js +14 -0
  48. package/dist/plugins.js.map +1 -0
  49. package/dist/session/events.d.ts +3 -0
  50. package/dist/session/runtime-input.js +5 -23
  51. package/dist/session/runtime-input.js.map +1 -1
  52. package/dist/session/session-errors.js +1 -6
  53. package/dist/session/session-errors.js.map +1 -1
  54. package/dist/session/session-events.js +59 -0
  55. package/dist/session/session-events.js.map +1 -0
  56. package/dist/session/session-execution.js +88 -0
  57. package/dist/session/session-execution.js.map +1 -0
  58. package/dist/session/session-notification.js +58 -0
  59. package/dist/session/session-notification.js.map +1 -0
  60. package/dist/session/session-runtime-drain.js +2 -2
  61. package/dist/session/session-runtime-drain.js.map +1 -1
  62. package/dist/session/session-turn-processor.js +135 -0
  63. package/dist/session/session-turn-processor.js.map +1 -0
  64. package/dist/session/session.js +73 -101
  65. package/dist/session/session.js.map +1 -1
  66. package/dist/session/snapshot.js.map +1 -1
  67. package/dist/subagent-background-child-run-state.js +51 -0
  68. package/dist/subagent-background-child-run-state.js.map +1 -0
  69. package/dist/subagent-background-child-run.js +103 -0
  70. package/dist/subagent-background-child-run.js.map +1 -0
  71. package/dist/subagent-background-in-process.js +98 -0
  72. package/dist/subagent-background-in-process.js.map +1 -0
  73. package/dist/subagent-background-notification-inbox.js +106 -0
  74. package/dist/subagent-background-notification-inbox.js.map +1 -0
  75. package/dist/subagent-background-notify.js +136 -0
  76. package/dist/subagent-background-notify.js.map +1 -0
  77. package/dist/subagent-background-resume-group.js +99 -0
  78. package/dist/subagent-background-resume-group.js.map +1 -0
  79. package/dist/subagent-background-runner.js +115 -0
  80. package/dist/subagent-background-runner.js.map +1 -0
  81. package/dist/subagent-background-schedule.js +43 -0
  82. package/dist/subagent-background-schedule.js.map +1 -0
  83. package/dist/subagent-child-run.js +68 -0
  84. package/dist/subagent-child-run.js.map +1 -0
  85. package/dist/subagent-job-cancel.js +60 -4
  86. package/dist/subagent-job-cancel.js.map +1 -1
  87. package/dist/subagent-job-observer.js +19 -0
  88. package/dist/subagent-job-observer.js.map +1 -0
  89. package/dist/subagent-job-output.js +28 -4
  90. package/dist/subagent-job-output.js.map +1 -1
  91. package/dist/subagent-job-state.js +66 -0
  92. package/dist/subagent-job-state.js.map +1 -0
  93. package/dist/subagent-jobs.js +78 -133
  94. package/dist/subagent-jobs.js.map +1 -1
  95. package/dist/subagent-run.js +4 -4
  96. package/dist/subagent-run.js.map +1 -1
  97. package/dist/subagents.js +68 -35
  98. package/dist/subagents.js.map +1 -1
  99. package/package.json +11 -1
  100. package/dist/hooks.d.ts +0 -32
  101. /package/dist/session/{runtime-input.d.ts → session-execution.d.ts} +0 -0
@@ -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":";;;;;AAiBA,MAAM,sBAAsB;AAE5B,eAAsB,iBAAiB,EACrC,cACA,QACA,MACA,KACA,eACA,UAQgB;CAChB,IAAI,IAAI,WAAW,aACjB;CAEF,IAAI,SAAS;CACb,IAAI,MAAM,wBAAwB,KAAK,YAAY,GACjD;CAGF,MAAM,oBAAoB,gCAAgC,KAAK,YAAY;CAC3E,IAAI;EACF,MAAM,EAAE,WAAW,MAAM,6BACvB,MAAM,aAAa,KAAK,MAAM,GAC9B,IAAI,WACH,UAAU,wBAAwB,eAAe,KAAK,KAAK,CAC9D;EACA,IAAI,MAAM,wBAAwB,KAAK,YAAY,GACjD;EAEF,MAAM,iBAAiB,IAAI;EAC3B,IAAI,SAAS;EAKb,IAAI,CAAC,MAJiB,0BACpB,KACA,eAAe,OAAO,MAAM,CAC9B,GACc;GACZ,IAAI,SAAS;GACb,IAAI,SAAS;GACb,aAAa,UAAU;GACvB;EACF;EACA,IAAI,SAAS,OAAO;CACtB,SAAS,OAAO;EACd,IAAI,MAAM,wBAAwB,KAAK,YAAY,GACjD;EAEF,MAAM,WAAW,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;EACzE,MAAM,iBAAiB,IAAI;EAC3B,IAAI,SAAS;GACX,OAAO,aAAa,QAAQ;GAC5B,YAAY;GACZ,QAAQ;GACR,mBAAmB;GACnB,UAAU,IAAI;GACd,MAAM;EACR;EAEA,IAAI,CAAC,MADiB,0BAA0B,KAAK,OAAO,GAC9C;GACZ,IAAI,SAAS;GACb,IAAI,SAAS;GACb,aAAa,UAAU;GACvB;EACF;EACA,IAAI,SAAS;CACf,UAAU;EACR,kBAAkB;CACpB;CAEA,IAAI,MAAM,wBAAwB,KAAK,YAAY,GACjD;CAGF,IAAI,UAAU;CACd,MAAM,2BAA2B;EAC/B,UAAU;GACR,OAAO,IAAI,QAAQ;GACnB,YAAY,IAAI,QAAQ,cAAc;GACtC,oBAAoB,IAAI;GACxB,QAAQ,IAAI,QAAQ,UAAU;GAC9B,UAAU,IAAI;GACd,SAAS,IAAI;GACb,MAAM;EACR;EACA;EACA;EACA;EACA;CACF,CAAC;AACH;AAEA,SAAS,gCACP,KACA,cACY;CACZ,IAAI,UAAU;CACd,IAAI;CACJ,MAAM,OAAO,YAA2B;EACtC,IAAI,SACF;EAEF,MAAM,wBAAwB,KAAK,YAAY;EAC/C,IAAI,WAAW,IAAI,WAAW,aAC5B;EAEF,YAAY,iBAAiB;GAC3B,gBAAgB,MAAM,KAAK,YAAY;EACzC,GAAG,mBAAmB;CACxB;CAEA,gBAAgB,MAAM,KAAK,YAAY;CACvC,aAAa;EACX,UAAU;EACV,IAAI,WACF,aAAa,SAAS;CAE1B;AACF;AAEA,SAAS,gBACP,MACA,KACA,cACM;CACN,KAAK,EAAE,OAAO,UAAmB;EAC/B,IAAI,SAAS;EACb,IAAI,SAAS;GACX,OAAO,aAAa,KAAK;GACzB,YAAY;GACZ,QAAQ;GACR,mBAAmB;GACnB,UAAU,IAAI;GACd,MAAM;EACR;EACA,aAAa,UAAU;CACzB,CAAC;AACH;AAEA,eAAe,wBACb,KACA,cACkB;CAClB,IAAI,IAAI,WAAW,aAAa;EAC9B,aAAa,UAAU;EACvB,OAAO;CACT;CACA,IAAI,EAAE,IAAI,iBAAiB,IAAI,aAC7B,OAAO;CAGT,MAAM,MAAM,MAAM,IAAI,cAAc,MAAM,KAAK,IAAI,IAAI,UAAU;CACjE,MAAM,YACJ,IAAI,mBAAmB,KAAK,OAAO,YAAY,IAAI;CACrD,IAAI,KAAK,WAAW,eAAe,CAAC,WAClC,OAAO;CAGT,IAAI,SAAS;CACb,aAAa,UAAU;CACvB,OAAO;AACT;AAEA,SAAS,aAAa,OAAwB;CAC5C,IAAI,iBAAiB,OACnB,OAAO,MAAM;CAGf,OAAO,OAAO,KAAK;AACrB"}
@@ -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"}
@@ -1,13 +1,20 @@
1
- import { assertBackgroundTaskId, cancelJob, isActiveJob } from "./subagent-jobs.js";
1
+ import { cancelBackgroundChildRun } from "./subagent-background-child-run.js";
2
+ import { assertBackgroundTaskId, backgroundRunJobStatus, cancelJob, isActiveJob } from "./subagent-job-state.js";
2
3
  import { jsonSchema, tool } from "ai";
3
4
  //#region src/subagent-job-cancel.ts
4
- function createBackgroundCancelTool(jobs) {
5
+ function createBackgroundCancelTool(jobs, executionHost, scope) {
5
6
  return tool({
6
7
  description: "Cancel an active background subagent job.",
7
- execute: (input) => {
8
+ execute: async (input) => {
8
9
  assertBackgroundTaskId(input.task_id, "background_cancel");
9
10
  const job = jobs.get(input.task_id);
10
- if (!job) throw new Error(`Unknown background subagent task ${input.task_id}.`);
11
+ if (!job) {
12
+ const durableCancel = await durableBackgroundCancel(input.task_id, executionHost, scope);
13
+ if (durableCancel) return durableCancel;
14
+ throw new Error(`Unknown background subagent task ${input.task_id}.`);
15
+ }
16
+ const durableLocalCancel = await cancelDurableLocalJob(job, scope);
17
+ if (durableLocalCancel) return durableLocalCancel;
11
18
  if (isActiveJob(job.status)) cancelJob(job);
12
19
  return {
13
20
  status: job.status,
@@ -22,6 +29,55 @@ function createBackgroundCancelTool(jobs) {
22
29
  })
23
30
  });
24
31
  }
32
+ async function cancelDurableLocalJob(job, scope) {
33
+ if (!(job.executionHost && job.childRunId)) return null;
34
+ const record = await job.executionHost.store.runs.get(job.childRunId);
35
+ if (record?.kind !== "background-subagent") return null;
36
+ if (scope && !record.sessionKey.startsWith(scope.childSessionKeyPrefix)) return null;
37
+ const currentStatus = backgroundRunJobStatus(record.status);
38
+ if (!currentStatus) return null;
39
+ if (currentStatus === "pending" || currentStatus === "running") {
40
+ const nextStatus = backgroundRunJobStatus((await cancelBackgroundChildRun({
41
+ executionHost: job.executionHost,
42
+ runId: record.runId
43
+ }))?.status);
44
+ if (nextStatus === "cancelled") {
45
+ cancelJob(job);
46
+ return {
47
+ status: "cancelled",
48
+ task_id: job.id
49
+ };
50
+ }
51
+ return {
52
+ status: nextStatus ?? currentStatus,
53
+ task_id: job.id
54
+ };
55
+ }
56
+ return {
57
+ status: currentStatus,
58
+ task_id: job.id
59
+ };
60
+ }
61
+ async function durableBackgroundCancel(taskId, executionHost, scope) {
62
+ if (!executionHost) return null;
63
+ const host = executionHost;
64
+ const record = await host.store.runs.get(`background:${taskId}`);
65
+ if (record?.kind !== "background-subagent") return null;
66
+ if (scope && !record.sessionKey.startsWith(scope.childSessionKeyPrefix)) return null;
67
+ const currentStatus = backgroundRunJobStatus(record.status);
68
+ if (!currentStatus) return null;
69
+ if (currentStatus === "pending" || currentStatus === "running") return {
70
+ status: backgroundRunJobStatus((await cancelBackgroundChildRun({
71
+ executionHost: host,
72
+ runId: record.runId
73
+ }))?.status) ?? currentStatus,
74
+ task_id: taskId
75
+ };
76
+ return {
77
+ status: currentStatus,
78
+ task_id: taskId
79
+ };
80
+ }
25
81
  //#endregion
26
82
  export { createBackgroundCancelTool };
27
83
 
@@ -1 +1 @@
1
- {"version":3,"file":"subagent-job-cancel.js","names":[],"sources":["../src/subagent-job-cancel.ts"],"sourcesContent":["import { jsonSchema, tool } from \"ai\";\nimport {\n assertBackgroundTaskId,\n cancelJob,\n isActiveJob,\n} from \"./subagent-jobs\";\nimport type { BackgroundCancelInput, SubagentJob } from \"./subagent-types\";\n\nexport function createBackgroundCancelTool(jobs: Map<string, SubagentJob>) {\n return tool<BackgroundCancelInput, unknown, Record<string, unknown>>({\n description: \"Cancel an active background subagent job.\",\n execute: (input: BackgroundCancelInput) => {\n assertBackgroundTaskId(input.task_id, \"background_cancel\");\n const job = jobs.get(input.task_id);\n if (!job) {\n throw new Error(`Unknown background subagent task ${input.task_id}.`);\n }\n\n if (isActiveJob(job.status)) {\n cancelJob(job);\n }\n\n return {\n status: job.status,\n task_id: job.id,\n };\n },\n inputSchema: jsonSchema<BackgroundCancelInput>({\n additionalProperties: false,\n properties: {\n task_id: { type: \"string\" },\n },\n required: [\"task_id\"],\n type: \"object\",\n }),\n });\n}\n"],"mappings":";;;AAQA,SAAgB,2BAA2B,MAAgC;CACzE,OAAO,KAA8D;EACnE,aAAa;EACb,UAAU,UAAiC;GACzC,uBAAuB,MAAM,SAAS,mBAAmB;GACzD,MAAM,MAAM,KAAK,IAAI,MAAM,OAAO;GAClC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,oCAAoC,MAAM,QAAQ,EAAE;GAGtE,IAAI,YAAY,IAAI,MAAM,GACxB,UAAU,GAAG;GAGf,OAAO;IACL,QAAQ,IAAI;IACZ,SAAS,IAAI;GACf;EACF;EACA,aAAa,WAAkC;GAC7C,sBAAsB;GACtB,YAAY,EACV,SAAS,EAAE,MAAM,SAAS,EAC5B;GACA,UAAU,CAAC,SAAS;GACpB,MAAM;EACR,CAAC;CACH,CAAC;AACH"}
1
+ {"version":3,"file":"subagent-job-cancel.js","names":[],"sources":["../src/subagent-job-cancel.ts"],"sourcesContent":["import { jsonSchema, tool } from \"ai\";\nimport type { ExecutionHost } from \"./execution/types\";\nimport { cancelBackgroundChildRun } from \"./subagent-background-child-run\";\nimport {\n assertBackgroundTaskId,\n backgroundRunJobStatus,\n cancelJob,\n isActiveJob,\n} from \"./subagent-job-state\";\nimport type { BackgroundCancelInput, SubagentJob } from \"./subagent-types\";\n\ninterface BackgroundToolScope {\n readonly childSessionKeyPrefix: string;\n}\n\nexport function createBackgroundCancelTool(\n jobs: Map<string, SubagentJob>,\n executionHost?: ExecutionHost,\n scope?: BackgroundToolScope\n) {\n return tool<BackgroundCancelInput, unknown, Record<string, unknown>>({\n description: \"Cancel an active background subagent job.\",\n execute: async (input: BackgroundCancelInput) => {\n assertBackgroundTaskId(input.task_id, \"background_cancel\");\n const job = jobs.get(input.task_id);\n if (!job) {\n const durableCancel = await durableBackgroundCancel(\n input.task_id,\n executionHost,\n scope\n );\n if (durableCancel) {\n return durableCancel;\n }\n\n throw new Error(`Unknown background subagent task ${input.task_id}.`);\n }\n\n const durableLocalCancel = await cancelDurableLocalJob(job, scope);\n if (durableLocalCancel) {\n return durableLocalCancel;\n }\n\n if (isActiveJob(job.status)) {\n cancelJob(job);\n }\n\n return {\n status: job.status,\n task_id: job.id,\n };\n },\n inputSchema: jsonSchema<BackgroundCancelInput>({\n additionalProperties: false,\n properties: {\n task_id: { type: \"string\" },\n },\n required: [\"task_id\"],\n type: \"object\",\n }),\n });\n}\n\nasync function cancelDurableLocalJob(\n job: SubagentJob,\n scope: BackgroundToolScope | undefined\n): Promise<Record<string, unknown> | null> {\n if (!(job.executionHost && job.childRunId)) {\n return null;\n }\n\n const record = await job.executionHost.store.runs.get(job.childRunId);\n if (record?.kind !== \"background-subagent\") {\n return null;\n }\n if (scope && !record.sessionKey.startsWith(scope.childSessionKeyPrefix)) {\n return null;\n }\n\n const currentStatus = backgroundRunJobStatus(record.status);\n if (!currentStatus) {\n return null;\n }\n if (currentStatus === \"pending\" || currentStatus === \"running\") {\n const cancelled = await cancelBackgroundChildRun({\n executionHost: job.executionHost,\n runId: record.runId,\n });\n const nextStatus = backgroundRunJobStatus(cancelled?.status);\n if (nextStatus === \"cancelled\") {\n cancelJob(job);\n return { status: \"cancelled\", task_id: job.id };\n }\n\n return { status: nextStatus ?? currentStatus, task_id: job.id };\n }\n\n return { status: currentStatus, task_id: job.id };\n}\n\nasync function durableBackgroundCancel(\n taskId: string,\n executionHost: ExecutionHost | undefined,\n scope: BackgroundToolScope | undefined\n): Promise<Record<string, unknown> | null> {\n if (!executionHost) {\n return null;\n }\n const host = executionHost;\n\n const record = await host.store.runs.get(`background:${taskId}`);\n if (record?.kind !== \"background-subagent\") {\n return null;\n }\n if (scope && !record.sessionKey.startsWith(scope.childSessionKeyPrefix)) {\n return null;\n }\n\n const currentStatus = backgroundRunJobStatus(record.status);\n if (!currentStatus) {\n return null;\n }\n\n if (currentStatus === \"pending\" || currentStatus === \"running\") {\n const cancelled = await cancelBackgroundChildRun({\n executionHost: host,\n runId: record.runId,\n });\n return {\n status: backgroundRunJobStatus(cancelled?.status) ?? currentStatus,\n task_id: taskId,\n };\n }\n\n return { status: currentStatus, task_id: taskId };\n}\n"],"mappings":";;;;AAeA,SAAgB,2BACd,MACA,eACA,OACA;CACA,OAAO,KAA8D;EACnE,aAAa;EACb,SAAS,OAAO,UAAiC;GAC/C,uBAAuB,MAAM,SAAS,mBAAmB;GACzD,MAAM,MAAM,KAAK,IAAI,MAAM,OAAO;GAClC,IAAI,CAAC,KAAK;IACR,MAAM,gBAAgB,MAAM,wBAC1B,MAAM,SACN,eACA,KACF;IACA,IAAI,eACF,OAAO;IAGT,MAAM,IAAI,MAAM,oCAAoC,MAAM,QAAQ,EAAE;GACtE;GAEA,MAAM,qBAAqB,MAAM,sBAAsB,KAAK,KAAK;GACjE,IAAI,oBACF,OAAO;GAGT,IAAI,YAAY,IAAI,MAAM,GACxB,UAAU,GAAG;GAGf,OAAO;IACL,QAAQ,IAAI;IACZ,SAAS,IAAI;GACf;EACF;EACA,aAAa,WAAkC;GAC7C,sBAAsB;GACtB,YAAY,EACV,SAAS,EAAE,MAAM,SAAS,EAC5B;GACA,UAAU,CAAC,SAAS;GACpB,MAAM;EACR,CAAC;CACH,CAAC;AACH;AAEA,eAAe,sBACb,KACA,OACyC;CACzC,IAAI,EAAE,IAAI,iBAAiB,IAAI,aAC7B,OAAO;CAGT,MAAM,SAAS,MAAM,IAAI,cAAc,MAAM,KAAK,IAAI,IAAI,UAAU;CACpE,IAAI,QAAQ,SAAS,uBACnB,OAAO;CAET,IAAI,SAAS,CAAC,OAAO,WAAW,WAAW,MAAM,qBAAqB,GACpE,OAAO;CAGT,MAAM,gBAAgB,uBAAuB,OAAO,MAAM;CAC1D,IAAI,CAAC,eACH,OAAO;CAET,IAAI,kBAAkB,aAAa,kBAAkB,WAAW;EAK9D,MAAM,aAAa,wBAAuB,MAJlB,yBAAyB;GAC/C,eAAe,IAAI;GACnB,OAAO,OAAO;EAChB,CAAC,IACoD,MAAM;EAC3D,IAAI,eAAe,aAAa;GAC9B,UAAU,GAAG;GACb,OAAO;IAAE,QAAQ;IAAa,SAAS,IAAI;GAAG;EAChD;EAEA,OAAO;GAAE,QAAQ,cAAc;GAAe,SAAS,IAAI;EAAG;CAChE;CAEA,OAAO;EAAE,QAAQ;EAAe,SAAS,IAAI;CAAG;AAClD;AAEA,eAAe,wBACb,QACA,eACA,OACyC;CACzC,IAAI,CAAC,eACH,OAAO;CAET,MAAM,OAAO;CAEb,MAAM,SAAS,MAAM,KAAK,MAAM,KAAK,IAAI,cAAc,QAAQ;CAC/D,IAAI,QAAQ,SAAS,uBACnB,OAAO;CAET,IAAI,SAAS,CAAC,OAAO,WAAW,WAAW,MAAM,qBAAqB,GACpE,OAAO;CAGT,MAAM,gBAAgB,uBAAuB,OAAO,MAAM;CAC1D,IAAI,CAAC,eACH,OAAO;CAGT,IAAI,kBAAkB,aAAa,kBAAkB,WAKnD,OAAO;EACL,QAAQ,wBAAuB,MALT,yBAAyB;GAC/C,eAAe;GACf,OAAO,OAAO;EAChB,CAAC,IAE2C,MAAM,KAAK;EACrD,SAAS;CACX;CAGF,OAAO;EAAE,QAAQ;EAAe,SAAS;CAAO;AAClD"}
@@ -0,0 +1,19 @@
1
+ //#region src/subagent-job-observer.ts
2
+ function emitBackgroundJobUpdate(parentSession, job, event) {
3
+ if (!isParentVisibleJobUpdate(event)) return Promise.resolve();
4
+ return parentSession.emitObserverEvent({
5
+ eventType: event.type,
6
+ delegateToolCallId: job.delegateToolCallId,
7
+ status: job.status,
8
+ subagent: job.subagent,
9
+ task_id: job.id,
10
+ type: "subagent-job-update"
11
+ });
12
+ }
13
+ function isParentVisibleJobUpdate(event) {
14
+ return event.type === "assistant-text" || event.type === "tool-call" || event.type === "tool-result" || event.type === "turn-abort" || event.type === "turn-error";
15
+ }
16
+ //#endregion
17
+ export { emitBackgroundJobUpdate };
18
+
19
+ //# sourceMappingURL=subagent-job-observer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-job-observer.js","names":[],"sources":["../src/subagent-job-observer.ts"],"sourcesContent":["import type { AgentEvent } from \"./session/events\";\nimport type { RuntimeInputSink, SubagentJob } from \"./subagent-types\";\n\nexport function emitBackgroundJobUpdate(\n parentSession: RuntimeInputSink,\n job: SubagentJob,\n event: AgentEvent\n): Promise<void> {\n if (!isParentVisibleJobUpdate(event)) {\n return Promise.resolve();\n }\n\n return parentSession.emitObserverEvent({\n eventType: event.type,\n delegateToolCallId: job.delegateToolCallId,\n status: job.status,\n subagent: job.subagent,\n task_id: job.id,\n type: \"subagent-job-update\" as const,\n });\n}\n\nfunction isParentVisibleJobUpdate(event: AgentEvent): boolean {\n return (\n event.type === \"assistant-text\" ||\n event.type === \"tool-call\" ||\n event.type === \"tool-result\" ||\n event.type === \"turn-abort\" ||\n event.type === \"turn-error\"\n );\n}\n"],"mappings":";AAGA,SAAgB,wBACd,eACA,KACA,OACe;CACf,IAAI,CAAC,yBAAyB,KAAK,GACjC,OAAO,QAAQ,QAAQ;CAGzB,OAAO,cAAc,kBAAkB;EACrC,WAAW,MAAM;EACjB,oBAAoB,IAAI;EACxB,QAAQ,IAAI;EACZ,UAAU,IAAI;EACd,SAAS,IAAI;EACb,MAAM;CACR,CAAC;AACH;AAEA,SAAS,yBAAyB,OAA4B;CAC5D,OACE,MAAM,SAAS,oBACf,MAAM,SAAS,eACf,MAAM,SAAS,iBACf,MAAM,SAAS,gBACf,MAAM,SAAS;AAEnB"}
@@ -1,15 +1,19 @@
1
- import { assertBackgroundTaskId, cleanupJob, isActiveJob } from "./subagent-jobs.js";
1
+ import { assertBackgroundTaskId, cleanupJob, isActiveJob } from "./subagent-job-state.js";
2
2
  import { jsonSchema, tool } from "ai";
3
3
  //#region src/subagent-job-output.ts
4
- function createBackgroundOutputTool(jobs) {
4
+ function createBackgroundOutputTool(jobs, executionHost, scope) {
5
5
  return tool({
6
6
  description: "Retrieve compact output for a background subagent job.",
7
7
  execute: async (input, { abortSignal }) => {
8
8
  assertBackgroundTaskId(input.task_id, "background_output");
9
9
  const job = jobs.get(input.task_id);
10
- if (!job) throw new Error(`Unknown background subagent task ${input.task_id}.`);
10
+ if (!job) {
11
+ const durableOutput = await durableBackgroundOutput(input.task_id, executionHost, scope);
12
+ if (durableOutput) return durableOutput;
13
+ throw new Error(`Unknown background subagent task ${input.task_id}.`);
14
+ }
11
15
  if (input.block === true && isActiveJob(job.status)) await waitForJob(job, input.timeout, abortSignal);
12
- const output = {
16
+ const output = await durableBackgroundOutput(input.task_id, job.executionHost ?? executionHost, scope, job.subagent) ?? {
13
17
  result: job.result,
14
18
  status: job.status,
15
19
  subagent: job.subagent,
@@ -35,6 +39,26 @@ function createBackgroundOutputTool(jobs) {
35
39
  })
36
40
  });
37
41
  }
42
+ async function durableBackgroundOutput(taskId, executionHost, scope, fallbackSubagent = "subagent") {
43
+ const record = await executionHost?.store.runs.get(`background:${taskId}`);
44
+ if (record?.kind !== "background-subagent") return null;
45
+ if (scope && !record.sessionKey.startsWith(scope.childSessionKeyPrefix)) return null;
46
+ return {
47
+ result: record.output,
48
+ status: backgroundStatus(record.status),
49
+ subagent: subagentName(record.output, fallbackSubagent),
50
+ task_id: taskId
51
+ };
52
+ }
53
+ function backgroundStatus(status) {
54
+ if (status === "completed" || status === "cancelled" || status === "error") return status;
55
+ if (status === "running" || status === "leased") return "running";
56
+ return "pending";
57
+ }
58
+ function subagentName(output, fallback) {
59
+ if (typeof output === "object" && output !== null && "subagent" in output && typeof output.subagent === "string") return output.subagent;
60
+ return fallback;
61
+ }
38
62
  async function waitForJob(job, timeout, abortSignal) {
39
63
  if (abortSignal?.aborted) return;
40
64
  const timeoutMs = Math.min(timeout ?? 6e4, 6e5);
@@ -1 +1 @@
1
- {"version":3,"file":"subagent-job-output.js","names":[],"sources":["../src/subagent-job-output.ts"],"sourcesContent":["import { jsonSchema, tool } from \"ai\";\nimport {\n assertBackgroundTaskId,\n cleanupJob,\n isActiveJob,\n} from \"./subagent-jobs\";\nimport type { BackgroundOutputInput, SubagentJob } from \"./subagent-types\";\n\nexport function createBackgroundOutputTool(jobs: Map<string, SubagentJob>) {\n return tool<BackgroundOutputInput, unknown, Record<string, unknown>>({\n description: \"Retrieve compact output for a background subagent job.\",\n execute: async (input: BackgroundOutputInput, { abortSignal }) => {\n assertBackgroundTaskId(input.task_id, \"background_output\");\n const job = jobs.get(input.task_id);\n if (!job) {\n throw new Error(`Unknown background subagent task ${input.task_id}.`);\n }\n\n if (input.block === true && isActiveJob(job.status)) {\n await waitForJob(job, input.timeout, abortSignal);\n }\n\n const output = {\n result: job.result,\n status: job.status,\n subagent: job.subagent,\n task_id: job.id,\n };\n if (!(isActiveJob(job.status) || !job.settled)) {\n const cleaned = await cleanupJob(job).then(\n () => true,\n () => false\n );\n if (cleaned) {\n jobs.delete(job.id);\n }\n }\n\n return output;\n },\n inputSchema: jsonSchema<BackgroundOutputInput>({\n additionalProperties: false,\n properties: {\n block: { type: \"boolean\" },\n task_id: { type: \"string\" },\n timeout: { minimum: 0, type: \"number\" },\n },\n required: [\"task_id\"],\n type: \"object\",\n }),\n });\n}\n\nasync function waitForJob(\n job: SubagentJob,\n timeout: number | undefined,\n abortSignal: AbortSignal | undefined\n) {\n if (abortSignal?.aborted) {\n return;\n }\n\n const timeoutMs = Math.min(timeout ?? 60_000, 600_000);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let abortListener: (() => void) | undefined;\n const abortPromise = abortSignal\n ? new Promise<void>((resolve) => {\n abortListener = resolve;\n abortSignal.addEventListener(\"abort\", abortListener, { once: true });\n })\n : undefined;\n try {\n await Promise.race([\n job.promise,\n ...(abortPromise ? [abortPromise] : []),\n new Promise<void>((resolve) => {\n timeoutId = setTimeout(resolve, timeoutMs);\n }),\n ]);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n if (abortListener) {\n abortSignal?.removeEventListener(\"abort\", abortListener);\n }\n }\n}\n"],"mappings":";;;AAQA,SAAgB,2BAA2B,MAAgC;CACzE,OAAO,KAA8D;EACnE,aAAa;EACb,SAAS,OAAO,OAA8B,EAAE,kBAAkB;GAChE,uBAAuB,MAAM,SAAS,mBAAmB;GACzD,MAAM,MAAM,KAAK,IAAI,MAAM,OAAO;GAClC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,oCAAoC,MAAM,QAAQ,EAAE;GAGtE,IAAI,MAAM,UAAU,QAAQ,YAAY,IAAI,MAAM,GAChD,MAAM,WAAW,KAAK,MAAM,SAAS,WAAW;GAGlD,MAAM,SAAS;IACb,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,UAAU,IAAI;IACd,SAAS,IAAI;GACf;GACA,IAAI,EAAE,YAAY,IAAI,MAAM,KAAK,CAAC,IAAI;QAKhC,MAJkB,WAAW,GAAG,EAAE,WAC9B,YACA,KACR,GAEE,KAAK,OAAO,IAAI,EAAE;GAAA;GAItB,OAAO;EACT;EACA,aAAa,WAAkC;GAC7C,sBAAsB;GACtB,YAAY;IACV,OAAO,EAAE,MAAM,UAAU;IACzB,SAAS,EAAE,MAAM,SAAS;IAC1B,SAAS;KAAE,SAAS;KAAG,MAAM;IAAS;GACxC;GACA,UAAU,CAAC,SAAS;GACpB,MAAM;EACR,CAAC;CACH,CAAC;AACH;AAEA,eAAe,WACb,KACA,SACA,aACA;CACA,IAAI,aAAa,SACf;CAGF,MAAM,YAAY,KAAK,IAAI,WAAW,KAAQ,GAAO;CACrD,IAAI;CACJ,IAAI;CACJ,MAAM,eAAe,cACjB,IAAI,SAAe,YAAY;EAC7B,gBAAgB;EAChB,YAAY,iBAAiB,SAAS,eAAe,EAAE,MAAM,KAAK,CAAC;CACrE,CAAC,IACD,KAAA;CACJ,IAAI;EACF,MAAM,QAAQ,KAAK;GACjB,IAAI;GACJ,GAAI,eAAe,CAAC,YAAY,IAAI,CAAC;GACrC,IAAI,SAAe,YAAY;IAC7B,YAAY,WAAW,SAAS,SAAS;GAC3C,CAAC;EACH,CAAC;CACH,UAAU;EACR,IAAI,WACF,aAAa,SAAS;EAExB,IAAI,eACF,aAAa,oBAAoB,SAAS,aAAa;CAE3D;AACF"}
1
+ {"version":3,"file":"subagent-job-output.js","names":[],"sources":["../src/subagent-job-output.ts"],"sourcesContent":["import { jsonSchema, tool } from \"ai\";\nimport type { ExecutionHost, RunStatus } from \"./execution/types\";\nimport {\n assertBackgroundTaskId,\n cleanupJob,\n isActiveJob,\n} from \"./subagent-job-state\";\nimport type { BackgroundOutputInput, SubagentJob } from \"./subagent-types\";\n\ninterface BackgroundToolScope {\n readonly childSessionKeyPrefix: string;\n}\n\nexport function createBackgroundOutputTool(\n jobs: Map<string, SubagentJob>,\n executionHost?: ExecutionHost,\n scope?: BackgroundToolScope\n) {\n return tool<BackgroundOutputInput, unknown, Record<string, unknown>>({\n description: \"Retrieve compact output for a background subagent job.\",\n execute: async (input: BackgroundOutputInput, { abortSignal }) => {\n assertBackgroundTaskId(input.task_id, \"background_output\");\n const job = jobs.get(input.task_id);\n if (!job) {\n const durableOutput = await durableBackgroundOutput(\n input.task_id,\n executionHost,\n scope\n );\n if (durableOutput) {\n return durableOutput;\n }\n\n throw new Error(`Unknown background subagent task ${input.task_id}.`);\n }\n\n if (input.block === true && isActiveJob(job.status)) {\n await waitForJob(job, input.timeout, abortSignal);\n }\n\n const durableOutput = await durableBackgroundOutput(\n input.task_id,\n job.executionHost ?? executionHost,\n scope,\n job.subagent\n );\n const output = durableOutput ?? {\n result: job.result,\n status: job.status,\n subagent: job.subagent,\n task_id: job.id,\n };\n if (!(isActiveJob(job.status) || !job.settled)) {\n const cleaned = await cleanupJob(job).then(\n () => true,\n () => false\n );\n if (cleaned) {\n jobs.delete(job.id);\n }\n }\n\n return output;\n },\n inputSchema: jsonSchema<BackgroundOutputInput>({\n additionalProperties: false,\n properties: {\n block: { type: \"boolean\" },\n task_id: { type: \"string\" },\n timeout: { minimum: 0, type: \"number\" },\n },\n required: [\"task_id\"],\n type: \"object\",\n }),\n });\n}\n\nasync function durableBackgroundOutput(\n taskId: string,\n executionHost: ExecutionHost | undefined,\n scope: BackgroundToolScope | undefined,\n fallbackSubagent = \"subagent\"\n): Promise<Record<string, unknown> | null> {\n const record = await executionHost?.store.runs.get(`background:${taskId}`);\n if (record?.kind !== \"background-subagent\") {\n return null;\n }\n if (scope && !record.sessionKey.startsWith(scope.childSessionKeyPrefix)) {\n return null;\n }\n\n return {\n result: record.output,\n status: backgroundStatus(record.status),\n subagent: subagentName(record.output, fallbackSubagent),\n task_id: taskId,\n };\n}\n\nfunction backgroundStatus(status: RunStatus): string {\n if (status === \"completed\" || status === \"cancelled\" || status === \"error\") {\n return status;\n }\n\n if (status === \"running\" || status === \"leased\") {\n return \"running\";\n }\n\n return \"pending\";\n}\n\nfunction subagentName(output: unknown, fallback: string): string {\n if (\n typeof output === \"object\" &&\n output !== null &&\n \"subagent\" in output &&\n typeof output.subagent === \"string\"\n ) {\n return output.subagent;\n }\n\n return fallback;\n}\n\nasync function waitForJob(\n job: SubagentJob,\n timeout: number | undefined,\n abortSignal: AbortSignal | undefined\n) {\n if (abortSignal?.aborted) {\n return;\n }\n\n const timeoutMs = Math.min(timeout ?? 60_000, 600_000);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let abortListener: (() => void) | undefined;\n const abortPromise = abortSignal\n ? new Promise<void>((resolve) => {\n abortListener = resolve;\n abortSignal.addEventListener(\"abort\", abortListener, { once: true });\n })\n : undefined;\n try {\n await Promise.race([\n job.promise,\n ...(abortPromise ? [abortPromise] : []),\n new Promise<void>((resolve) => {\n timeoutId = setTimeout(resolve, timeoutMs);\n }),\n ]);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n if (abortListener) {\n abortSignal?.removeEventListener(\"abort\", abortListener);\n }\n }\n}\n"],"mappings":";;;AAaA,SAAgB,2BACd,MACA,eACA,OACA;CACA,OAAO,KAA8D;EACnE,aAAa;EACb,SAAS,OAAO,OAA8B,EAAE,kBAAkB;GAChE,uBAAuB,MAAM,SAAS,mBAAmB;GACzD,MAAM,MAAM,KAAK,IAAI,MAAM,OAAO;GAClC,IAAI,CAAC,KAAK;IACR,MAAM,gBAAgB,MAAM,wBAC1B,MAAM,SACN,eACA,KACF;IACA,IAAI,eACF,OAAO;IAGT,MAAM,IAAI,MAAM,oCAAoC,MAAM,QAAQ,EAAE;GACtE;GAEA,IAAI,MAAM,UAAU,QAAQ,YAAY,IAAI,MAAM,GAChD,MAAM,WAAW,KAAK,MAAM,SAAS,WAAW;GASlD,MAAM,SAAS,MANa,wBAC1B,MAAM,SACN,IAAI,iBAAiB,eACrB,OACA,IAAI,QACN,KACgC;IAC9B,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,UAAU,IAAI;IACd,SAAS,IAAI;GACf;GACA,IAAI,EAAE,YAAY,IAAI,MAAM,KAAK,CAAC,IAAI;QAKhC,MAJkB,WAAW,GAAG,EAAE,WAC9B,YACA,KACR,GAEE,KAAK,OAAO,IAAI,EAAE;GAAA;GAItB,OAAO;EACT;EACA,aAAa,WAAkC;GAC7C,sBAAsB;GACtB,YAAY;IACV,OAAO,EAAE,MAAM,UAAU;IACzB,SAAS,EAAE,MAAM,SAAS;IAC1B,SAAS;KAAE,SAAS;KAAG,MAAM;IAAS;GACxC;GACA,UAAU,CAAC,SAAS;GACpB,MAAM;EACR,CAAC;CACH,CAAC;AACH;AAEA,eAAe,wBACb,QACA,eACA,OACA,mBAAmB,YACsB;CACzC,MAAM,SAAS,MAAM,eAAe,MAAM,KAAK,IAAI,cAAc,QAAQ;CACzE,IAAI,QAAQ,SAAS,uBACnB,OAAO;CAET,IAAI,SAAS,CAAC,OAAO,WAAW,WAAW,MAAM,qBAAqB,GACpE,OAAO;CAGT,OAAO;EACL,QAAQ,OAAO;EACf,QAAQ,iBAAiB,OAAO,MAAM;EACtC,UAAU,aAAa,OAAO,QAAQ,gBAAgB;EACtD,SAAS;CACX;AACF;AAEA,SAAS,iBAAiB,QAA2B;CACnD,IAAI,WAAW,eAAe,WAAW,eAAe,WAAW,SACjE,OAAO;CAGT,IAAI,WAAW,aAAa,WAAW,UACrC,OAAO;CAGT,OAAO;AACT;AAEA,SAAS,aAAa,QAAiB,UAA0B;CAC/D,IACE,OAAO,WAAW,YAClB,WAAW,QACX,cAAc,UACd,OAAO,OAAO,aAAa,UAE3B,OAAO,OAAO;CAGhB,OAAO;AACT;AAEA,eAAe,WACb,KACA,SACA,aACA;CACA,IAAI,aAAa,SACf;CAGF,MAAM,YAAY,KAAK,IAAI,WAAW,KAAQ,GAAO;CACrD,IAAI;CACJ,IAAI;CACJ,MAAM,eAAe,cACjB,IAAI,SAAe,YAAY;EAC7B,gBAAgB;EAChB,YAAY,iBAAiB,SAAS,eAAe,EAAE,MAAM,KAAK,CAAC;CACrE,CAAC,IACD,KAAA;CACJ,IAAI;EACF,MAAM,QAAQ,KAAK;GACjB,IAAI;GACJ,GAAI,eAAe,CAAC,YAAY,IAAI,CAAC;GACrC,IAAI,SAAe,YAAY;IAC7B,YAAY,WAAW,SAAS,SAAS;GAC3C,CAAC;EACH,CAAC;CACH,UAAU;EACR,IAAI,WACF,aAAa,SAAS;EAExB,IAAI,eACF,aAAa,oBAAoB,SAAS,aAAa;CAE3D;AACF"}
@@ -0,0 +1,66 @@
1
+ import { updateBackgroundRunStatus } from "./subagent-background-child-run.js";
2
+ //#region src/subagent-job-state.ts
3
+ function assertBackgroundTaskId(value, toolName) {
4
+ if (value.startsWith("bg_")) return;
5
+ throw new Error(`${toolName} expects a background task_id starting with bg_, not a session key: ${value}`);
6
+ }
7
+ function isActiveJob(status) {
8
+ return status === "pending" || status === "running";
9
+ }
10
+ function hasBackgroundJobCapacity({ jobs, maxActiveJobs, maxRetainedJobs }) {
11
+ if (jobs.size >= maxRetainedJobs) return false;
12
+ let activeJobs = 0;
13
+ for (const job of jobs.values()) if (isActiveJob(job.status) || !job.settled) activeJobs += 1;
14
+ return activeJobs < maxActiveJobs;
15
+ }
16
+ function cancelJob(job) {
17
+ job.status = "cancelled";
18
+ job.abort();
19
+ const statusUpdate = updateBackgroundRunStatus(job, "cancelled");
20
+ job.promise = Promise.allSettled([job.promise, statusUpdate]).then(() => void 0);
21
+ }
22
+ async function cleanupJob(job) {
23
+ await job.cleanup();
24
+ job.unregisterCleanup?.();
25
+ }
26
+ function backgroundLaunchOutput(job) {
27
+ return {
28
+ message: [
29
+ `Background subagent job ${job.id} started.`,
30
+ `Please wait for <system-reminder> before checking task ${job.id}.`,
31
+ `Do NOT call background_output({ task_id: "${job.id}" }) now; wait for <system-reminder> first.`
32
+ ].join(" "),
33
+ run_in_background: true,
34
+ status: job.status,
35
+ subagent: job.subagent,
36
+ task_id: job.id
37
+ };
38
+ }
39
+ function backgroundCancelledLaunchOutput({ id, subagent }) {
40
+ return {
41
+ message: `Background subagent job ${id} was cancelled before it started.`,
42
+ run_in_background: true,
43
+ status: "cancelled",
44
+ subagent,
45
+ task_id: id
46
+ };
47
+ }
48
+ function backgroundRunJobStatus(status) {
49
+ if (status === "cancelled" || status === "completed" || status === "error") return status;
50
+ if (status === "leased" || status === "running") return "running";
51
+ if (status === "needs-recovery" || status === "queued" || status === "suspended") return "pending";
52
+ return null;
53
+ }
54
+ function backgroundReplayOutput({ id, status, subagent }) {
55
+ return {
56
+ message: `Background subagent job ${id} was already ${status}. Reuse the existing task_id instead of launching it again.`,
57
+ run_in_background: true,
58
+ status,
59
+ subagent,
60
+ task_id: id
61
+ };
62
+ }
63
+ //#endregion
64
+ export { assertBackgroundTaskId, backgroundCancelledLaunchOutput, backgroundLaunchOutput, backgroundReplayOutput, backgroundRunJobStatus, cancelJob, cleanupJob, hasBackgroundJobCapacity, isActiveJob };
65
+
66
+ //# sourceMappingURL=subagent-job-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-job-state.js","names":[],"sources":["../src/subagent-job-state.ts"],"sourcesContent":["import type { RunStatus } from \"./execution/types\";\nimport { updateBackgroundRunStatus } from \"./subagent-background-child-run\";\nimport type { SubagentJob } from \"./subagent-types\";\n\nexport function assertBackgroundTaskId(value: string, toolName: string): void {\n if (value.startsWith(\"bg_\")) {\n return;\n }\n\n throw new Error(\n `${toolName} expects a background task_id starting with bg_, not a session key: ${value}`\n );\n}\n\nexport function isActiveJob(status: SubagentJob[\"status\"]): boolean {\n return status === \"pending\" || status === \"running\";\n}\n\nexport function hasBackgroundJobCapacity({\n jobs,\n maxActiveJobs,\n maxRetainedJobs,\n}: {\n readonly jobs: Map<string, SubagentJob>;\n readonly maxActiveJobs: number;\n readonly maxRetainedJobs: number;\n}): boolean {\n if (jobs.size >= maxRetainedJobs) {\n return false;\n }\n\n let activeJobs = 0;\n for (const job of jobs.values()) {\n if (isActiveJob(job.status) || !job.settled) {\n activeJobs += 1;\n }\n }\n\n return activeJobs < maxActiveJobs;\n}\n\nexport function cancelJob(job: SubagentJob): void {\n job.status = \"cancelled\";\n job.abort();\n const statusUpdate = updateBackgroundRunStatus(job, \"cancelled\");\n job.promise = Promise.allSettled([job.promise, statusUpdate]).then(\n () => undefined\n );\n}\n\nexport async function cleanupJob(job: SubagentJob): Promise<void> {\n await job.cleanup();\n job.unregisterCleanup?.();\n}\n\nexport function backgroundLaunchOutput(job: SubagentJob) {\n return {\n message: [\n `Background subagent job ${job.id} started.`,\n `Please wait for <system-reminder> before checking task ${job.id}.`,\n `Do NOT call background_output({ task_id: \"${job.id}\" }) now; wait for <system-reminder> first.`,\n ].join(\" \"),\n run_in_background: true,\n status: job.status,\n subagent: job.subagent,\n task_id: job.id,\n };\n}\n\nexport function backgroundCancelledLaunchOutput({\n id,\n subagent,\n}: {\n readonly id: string;\n readonly subagent?: string;\n}) {\n return {\n message: `Background subagent job ${id} was cancelled before it started.`,\n run_in_background: true,\n status: \"cancelled\",\n subagent,\n task_id: id,\n };\n}\n\nexport function backgroundRunJobStatus(\n status: RunStatus | undefined\n): Exclude<SubagentJob[\"status\"], \"aborted\"> | null {\n if (status === \"cancelled\" || status === \"completed\" || status === \"error\") {\n return status;\n }\n\n if (status === \"leased\" || status === \"running\") {\n return \"running\";\n }\n\n if (\n status === \"needs-recovery\" ||\n status === \"queued\" ||\n status === \"suspended\"\n ) {\n return \"pending\";\n }\n\n return null;\n}\n\nexport function backgroundReplayOutput({\n id,\n status,\n subagent,\n}: {\n readonly id: string;\n readonly status: Exclude<SubagentJob[\"status\"], \"aborted\">;\n readonly subagent: string;\n}) {\n return {\n message: `Background subagent job ${id} was already ${status}. Reuse the existing task_id instead of launching it again.`,\n run_in_background: true,\n status,\n subagent,\n task_id: id,\n };\n}\n"],"mappings":";;AAIA,SAAgB,uBAAuB,OAAe,UAAwB;CAC5E,IAAI,MAAM,WAAW,KAAK,GACxB;CAGF,MAAM,IAAI,MACR,GAAG,SAAS,sEAAsE,OACpF;AACF;AAEA,SAAgB,YAAY,QAAwC;CAClE,OAAO,WAAW,aAAa,WAAW;AAC5C;AAEA,SAAgB,yBAAyB,EACvC,MACA,eACA,mBAKU;CACV,IAAI,KAAK,QAAQ,iBACf,OAAO;CAGT,IAAI,aAAa;CACjB,KAAK,MAAM,OAAO,KAAK,OAAO,GAC5B,IAAI,YAAY,IAAI,MAAM,KAAK,CAAC,IAAI,SAClC,cAAc;CAIlB,OAAO,aAAa;AACtB;AAEA,SAAgB,UAAU,KAAwB;CAChD,IAAI,SAAS;CACb,IAAI,MAAM;CACV,MAAM,eAAe,0BAA0B,KAAK,WAAW;CAC/D,IAAI,UAAU,QAAQ,WAAW,CAAC,IAAI,SAAS,YAAY,CAAC,EAAE,WACtD,KAAA,CACR;AACF;AAEA,eAAsB,WAAW,KAAiC;CAChE,MAAM,IAAI,QAAQ;CAClB,IAAI,oBAAoB;AAC1B;AAEA,SAAgB,uBAAuB,KAAkB;CACvD,OAAO;EACL,SAAS;GACP,2BAA2B,IAAI,GAAG;GAClC,0DAA0D,IAAI,GAAG;GACjE,6CAA6C,IAAI,GAAG;EACtD,EAAE,KAAK,GAAG;EACV,mBAAmB;EACnB,QAAQ,IAAI;EACZ,UAAU,IAAI;EACd,SAAS,IAAI;CACf;AACF;AAEA,SAAgB,gCAAgC,EAC9C,IACA,YAIC;CACD,OAAO;EACL,SAAS,2BAA2B,GAAG;EACvC,mBAAmB;EACnB,QAAQ;EACR;EACA,SAAS;CACX;AACF;AAEA,SAAgB,uBACd,QACkD;CAClD,IAAI,WAAW,eAAe,WAAW,eAAe,WAAW,SACjE,OAAO;CAGT,IAAI,WAAW,YAAY,WAAW,WACpC,OAAO;CAGT,IACE,WAAW,oBACX,WAAW,YACX,WAAW,aAEX,OAAO;CAGT,OAAO;AACT;AAEA,SAAgB,uBAAuB,EACrC,IACA,QACA,YAKC;CACD,OAAO;EACL,SAAS,2BAA2B,GAAG,eAAe,OAAO;EAC7D,mBAAmB;EACnB;EACA;EACA,SAAS;CACX;AACF"}