@narumitw/pi-subagents 2.0.6 → 2.1.1

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 (180) hide show
  1. package/README.md +171 -60
  2. package/dist/chunks/{auto-transport-SY2VHUFH.ts → auto-transport-FUUKFDIG.ts} +6 -6
  3. package/dist/chunks/{capability-grant-CGEWOEKE.ts → capability-grant-PR72SWWS.ts} +4 -4
  4. package/dist/chunks/{chunk-CBP76ARQ.ts → chunk-2LMJU25E.ts} +172 -74
  5. package/dist/chunks/chunk-2LMJU25E.ts.map +7 -0
  6. package/dist/chunks/{chunk-DPPVEQAM.ts → chunk-4AQSF7AS.ts} +1 -1
  7. package/dist/chunks/chunk-4AQSF7AS.ts.map +7 -0
  8. package/dist/chunks/chunk-6H6TBBED.ts +108 -0
  9. package/dist/chunks/chunk-6H6TBBED.ts.map +7 -0
  10. package/dist/chunks/{chunk-YBUWBRF7.ts → chunk-6NSJVPXX.ts} +6 -16
  11. package/dist/chunks/{chunk-YBUWBRF7.ts.map → chunk-6NSJVPXX.ts.map} +2 -2
  12. package/dist/chunks/{chunk-DSOOH73Y.ts → chunk-7AAJEUSL.ts} +3 -3
  13. package/dist/chunks/{chunk-DSOOH73Y.ts.map → chunk-7AAJEUSL.ts.map} +2 -2
  14. package/dist/chunks/{chunk-7QPPBBXZ.ts → chunk-D4CR7T73.ts} +21 -3
  15. package/dist/chunks/chunk-D4CR7T73.ts.map +7 -0
  16. package/dist/chunks/{chunk-NIF42QMF.ts → chunk-FEVPWRMU.ts} +7 -4
  17. package/dist/chunks/chunk-FEVPWRMU.ts.map +7 -0
  18. package/dist/chunks/{chunk-S2IWVK3J.ts → chunk-ILEQ27AL.ts} +3 -2
  19. package/dist/chunks/chunk-ILEQ27AL.ts.map +7 -0
  20. package/dist/chunks/{chunk-DQMN4OYM.ts → chunk-ITVWPNU4.ts} +12 -106
  21. package/dist/chunks/chunk-ITVWPNU4.ts.map +7 -0
  22. package/dist/chunks/{chunk-QDVGH2X7.ts → chunk-IWC32VPY.ts} +2 -1
  23. package/dist/chunks/{chunk-QDVGH2X7.ts.map → chunk-IWC32VPY.ts.map} +2 -2
  24. package/dist/chunks/{chunk-I2FAK44T.ts → chunk-JSZIP73U.ts} +48 -10
  25. package/dist/chunks/chunk-JSZIP73U.ts.map +7 -0
  26. package/dist/chunks/{chunk-ZHTNCZIA.ts → chunk-JU6LUNLP.ts} +2 -2
  27. package/dist/chunks/{chunk-PSK43Y6A.ts → chunk-LASD73CM.ts} +2 -2
  28. package/dist/chunks/{chunk-C4L6P266.ts → chunk-LEOYDZI3.ts} +1 -1
  29. package/dist/chunks/{chunk-C4L6P266.ts.map → chunk-LEOYDZI3.ts.map} +2 -2
  30. package/dist/chunks/{chunk-H3BFJ7HJ.ts → chunk-LL4LP2T7.ts} +5 -5
  31. package/dist/chunks/chunk-N2T5IN4X.ts +18 -0
  32. package/dist/chunks/chunk-N2T5IN4X.ts.map +7 -0
  33. package/dist/chunks/chunk-NLT67IZS.ts +322 -0
  34. package/dist/chunks/chunk-NLT67IZS.ts.map +7 -0
  35. package/dist/chunks/{chunk-G3RSMSXJ.ts → chunk-NTRPLF46.ts} +1 -1
  36. package/dist/chunks/chunk-NTRPLF46.ts.map +7 -0
  37. package/dist/chunks/{chunk-EFBPISNT.ts → chunk-PBZMBTNJ.ts} +104 -103
  38. package/dist/chunks/chunk-PBZMBTNJ.ts.map +7 -0
  39. package/dist/chunks/{chunk-SCZ33MYW.ts → chunk-PGLSFLYW.ts} +2 -2
  40. package/dist/chunks/{chunk-O4VO6JUJ.ts → chunk-TM2R67J3.ts} +3 -3
  41. package/dist/chunks/{chunk-QBORTI4W.ts → chunk-TMZRHIIK.ts} +32 -3
  42. package/dist/chunks/chunk-TMZRHIIK.ts.map +7 -0
  43. package/dist/chunks/chunk-TZ34IQ3M.ts +59 -0
  44. package/dist/chunks/chunk-TZ34IQ3M.ts.map +7 -0
  45. package/dist/chunks/{chunk-7OBINKFM.ts → chunk-VDG7LTYE.ts} +11 -11
  46. package/dist/chunks/chunk-VDG7LTYE.ts.map +7 -0
  47. package/dist/chunks/{chunk-AEUYS7JC.ts → chunk-X4NMONPE.ts} +1 -1
  48. package/dist/chunks/chunk-X4NMONPE.ts.map +7 -0
  49. package/dist/chunks/{chunk-HKC4ES4B.ts → chunk-YPJEN6NU.ts} +2 -2
  50. package/dist/chunks/{chunk-434NII74.ts → chunk-YU53SHA7.ts} +54 -8
  51. package/dist/chunks/chunk-YU53SHA7.ts.map +7 -0
  52. package/dist/chunks/{completion-delivery-YPOWSSV3.ts → completion-delivery-RSJU6BXL.ts} +5 -4
  53. package/dist/chunks/{config-status-J4GPWP46.ts → config-status-FKGDECZ3.ts} +7 -6
  54. package/dist/chunks/{config-ui-2YUCUEBM.ts → config-ui-ABHYNGQ7.ts} +297 -231
  55. package/dist/chunks/config-ui-ABHYNGQ7.ts.map +7 -0
  56. package/dist/chunks/{consult-IV7UBNQQ.ts → consult-LJU3IQY5.ts} +14 -13
  57. package/dist/chunks/consult-LJU3IQY5.ts.map +7 -0
  58. package/dist/chunks/{create-stateful-transport-ZP3IXNV3.ts → create-stateful-transport-JWC2EFYL.ts} +6 -6
  59. package/dist/chunks/{delegation-contract-WJPT4FVS.ts → delegation-contract-LA56I5DT.ts} +2 -2
  60. package/dist/chunks/{discovery-K25T3SH4.ts → discovery-MIFB2U4Y.ts} +3 -3
  61. package/dist/chunks/{execution-LA7HN5YF.ts → execution-Q2JZLKJA.ts} +19 -16
  62. package/dist/chunks/execution-Q2JZLKJA.ts.map +7 -0
  63. package/dist/chunks/{in-process-transport-4RF3J6XK.ts → in-process-transport-HJ6TZXC3.ts} +9 -9
  64. package/dist/chunks/{inspect-XV2QALMR.ts → inspect-UH2TKH6E.ts} +25 -10
  65. package/dist/chunks/inspect-UH2TKH6E.ts.map +7 -0
  66. package/dist/chunks/{persistence-VSGXAW4P.ts → persistence-UY3PY6E5.ts} +11 -7
  67. package/dist/chunks/persistence-UY3PY6E5.ts.map +7 -0
  68. package/dist/chunks/{registry-E6XPJB7L.ts → registry-BT54L6CY.ts} +136 -15
  69. package/dist/chunks/registry-BT54L6CY.ts.map +7 -0
  70. package/dist/chunks/{retained-semantic-state-7WXSGRJU.ts → retained-semantic-state-GM75FZGE.ts} +3 -3
  71. package/dist/chunks/{rpc-transport-NEHBHWX4.ts → rpc-transport-7R7DVCEB.ts} +12 -16
  72. package/dist/chunks/rpc-transport-7R7DVCEB.ts.map +7 -0
  73. package/dist/chunks/{spawn-idempotency-QGHO2WKC.ts → spawn-idempotency-BNHOSMZW.ts} +2 -2
  74. package/dist/chunks/{subprocess-transport-NSHOGDC4.ts → subprocess-transport-VHZJRBTW.ts} +15 -14
  75. package/dist/chunks/subprocess-transport-VHZJRBTW.ts.map +7 -0
  76. package/dist/chunks/usage-recording-store-CW3EH2SZ.ts +164 -0
  77. package/dist/chunks/usage-recording-store-CW3EH2SZ.ts.map +7 -0
  78. package/dist/index.ts +682 -82
  79. package/dist/index.ts.map +3 -3
  80. package/docs/async-runtime-protocol.md +83 -0
  81. package/docs/implementation-notes/pi-subagents-capability-matrix.md +62 -0
  82. package/docs/implementation-notes/pi-subagents-current-direction.md +99 -0
  83. package/docs/implementation-notes/pi-subagents-rpc-v1.md +153 -0
  84. package/docs/pi-subagents-diagrams.md +183 -0
  85. package/package.json +9 -8
  86. package/src/agents/types.ts +2 -0
  87. package/src/async-subagent-benchmark.ts +532 -0
  88. package/src/completion-delivery.ts +71 -4
  89. package/src/completion-render.ts +1 -0
  90. package/src/completion-requirement.ts +479 -0
  91. package/src/config-registration.ts +5 -5
  92. package/src/config-status.ts +66 -70
  93. package/src/config-ui.ts +244 -150
  94. package/src/consult-registration.ts +4 -12
  95. package/src/consult-resources.ts +1 -1
  96. package/src/consult.ts +3 -8
  97. package/src/delegation-contract.ts +55 -9
  98. package/src/execution-plan.ts +1 -1
  99. package/src/execution-ui.ts +40 -29
  100. package/src/execution.ts +2 -3
  101. package/src/inspect.ts +24 -1
  102. package/src/orchestration-metrics.ts +1 -1
  103. package/src/panel-execution.ts +2 -3
  104. package/src/panel-failure.ts +1 -1
  105. package/src/panel-render.ts +1 -1
  106. package/src/parallel-limit-ui.ts +6 -5
  107. package/src/params.ts +2 -1
  108. package/src/persistence.ts +9 -0
  109. package/src/process-control.ts +43 -0
  110. package/src/registry-types.ts +5 -0
  111. package/src/registry.ts +162 -18
  112. package/src/render.ts +2 -1
  113. package/src/rpc-transport.ts +1 -1
  114. package/src/runner-outcome.ts +1 -1
  115. package/src/runner-result.ts +1 -1
  116. package/src/runner-types.ts +102 -0
  117. package/src/runner.ts +11 -192
  118. package/src/session-guidance-contract.ts +309 -0
  119. package/src/settings/inspection.ts +27 -0
  120. package/src/settings/schema.ts +9 -0
  121. package/src/settings-reader.ts +7 -0
  122. package/src/settings.ts +20 -0
  123. package/src/spawn-idempotency.ts +5 -0
  124. package/src/stateful-agent-view.ts +15 -19
  125. package/src/stateful-guidance.ts +11 -18
  126. package/src/stateful-limit-ui.ts +23 -20
  127. package/src/stateful-limits.ts +10 -10
  128. package/src/stateful-registration.ts +126 -36
  129. package/src/stateful-render.ts +27 -2
  130. package/src/subagent-details.ts +43 -0
  131. package/src/subagents-extension.ts +116 -68
  132. package/src/subagents.ts +2 -0
  133. package/src/subprocess-transport.ts +2 -1
  134. package/src/supervision.ts +2 -1
  135. package/src/timeout-finalization.ts +1 -1
  136. package/src/tool-schema-compatibility.ts +73 -0
  137. package/src/transport-types.ts +6 -0
  138. package/src/transport-ui.ts +18 -46
  139. package/src/usage-recording-config.ts +13 -0
  140. package/src/usage-recording-store.ts +183 -0
  141. package/src/usage-recording.ts +478 -0
  142. package/src/verification-harness.ts +1 -1
  143. package/src/workflow-ui.ts +17 -9
  144. package/dist/chunks/chunk-434NII74.ts.map +0 -7
  145. package/dist/chunks/chunk-7OBINKFM.ts.map +0 -7
  146. package/dist/chunks/chunk-7QPPBBXZ.ts.map +0 -7
  147. package/dist/chunks/chunk-AEUYS7JC.ts.map +0 -7
  148. package/dist/chunks/chunk-CBP76ARQ.ts.map +0 -7
  149. package/dist/chunks/chunk-DPPVEQAM.ts.map +0 -7
  150. package/dist/chunks/chunk-DQMN4OYM.ts.map +0 -7
  151. package/dist/chunks/chunk-EFBPISNT.ts.map +0 -7
  152. package/dist/chunks/chunk-G3RSMSXJ.ts.map +0 -7
  153. package/dist/chunks/chunk-I2FAK44T.ts.map +0 -7
  154. package/dist/chunks/chunk-NIF42QMF.ts.map +0 -7
  155. package/dist/chunks/chunk-QBORTI4W.ts.map +0 -7
  156. package/dist/chunks/chunk-S2IWVK3J.ts.map +0 -7
  157. package/dist/chunks/config-ui-2YUCUEBM.ts.map +0 -7
  158. package/dist/chunks/consult-IV7UBNQQ.ts.map +0 -7
  159. package/dist/chunks/execution-LA7HN5YF.ts.map +0 -7
  160. package/dist/chunks/inspect-XV2QALMR.ts.map +0 -7
  161. package/dist/chunks/persistence-VSGXAW4P.ts.map +0 -7
  162. package/dist/chunks/registry-E6XPJB7L.ts.map +0 -7
  163. package/dist/chunks/rpc-transport-NEHBHWX4.ts.map +0 -7
  164. package/dist/chunks/subprocess-transport-NSHOGDC4.ts.map +0 -7
  165. /package/dist/chunks/{auto-transport-SY2VHUFH.ts.map → auto-transport-FUUKFDIG.ts.map} +0 -0
  166. /package/dist/chunks/{capability-grant-CGEWOEKE.ts.map → capability-grant-PR72SWWS.ts.map} +0 -0
  167. /package/dist/chunks/{chunk-ZHTNCZIA.ts.map → chunk-JU6LUNLP.ts.map} +0 -0
  168. /package/dist/chunks/{chunk-PSK43Y6A.ts.map → chunk-LASD73CM.ts.map} +0 -0
  169. /package/dist/chunks/{chunk-H3BFJ7HJ.ts.map → chunk-LL4LP2T7.ts.map} +0 -0
  170. /package/dist/chunks/{chunk-SCZ33MYW.ts.map → chunk-PGLSFLYW.ts.map} +0 -0
  171. /package/dist/chunks/{chunk-O4VO6JUJ.ts.map → chunk-TM2R67J3.ts.map} +0 -0
  172. /package/dist/chunks/{chunk-HKC4ES4B.ts.map → chunk-YPJEN6NU.ts.map} +0 -0
  173. /package/dist/chunks/{completion-delivery-YPOWSSV3.ts.map → completion-delivery-RSJU6BXL.ts.map} +0 -0
  174. /package/dist/chunks/{config-status-J4GPWP46.ts.map → config-status-FKGDECZ3.ts.map} +0 -0
  175. /package/dist/chunks/{create-stateful-transport-ZP3IXNV3.ts.map → create-stateful-transport-JWC2EFYL.ts.map} +0 -0
  176. /package/dist/chunks/{delegation-contract-WJPT4FVS.ts.map → delegation-contract-LA56I5DT.ts.map} +0 -0
  177. /package/dist/chunks/{discovery-K25T3SH4.ts.map → discovery-MIFB2U4Y.ts.map} +0 -0
  178. /package/dist/chunks/{in-process-transport-4RF3J6XK.ts.map → in-process-transport-HJ6TZXC3.ts.map} +0 -0
  179. /package/dist/chunks/{retained-semantic-state-7WXSGRJU.ts.map → retained-semantic-state-GM75FZGE.ts.map} +0 -0
  180. /package/dist/chunks/{spawn-idempotency-QGHO2WKC.ts.map → spawn-idempotency-BNHOSMZW.ts.map} +0 -0
package/src/registry.ts CHANGED
@@ -10,6 +10,16 @@ import {
10
10
  isCapabilityGrantActive,
11
11
  revokeCapabilityGrant,
12
12
  } from "./capability-grant.js";
13
+ import {
14
+ beginCompletionRequirement,
15
+ type CompletionRequirementMode,
16
+ cancelPendingCompletionRequirements,
17
+ completionRequirementKey,
18
+ MAX_UNRESOLVED_REQUIRED_COMPLETIONS,
19
+ makeCompletionAvailable,
20
+ makeCompletionVisible,
21
+ pendingRequiredCompletionCount,
22
+ } from "./completion-requirement.js";
13
23
  import { resolveCompletionRecipient } from "./completion-routing.js";
14
24
  import type { TargetPolicyAudit } from "./cwd-policy.js";
15
25
  import type { DelegationContract } from "./delegation-contract.js";
@@ -259,8 +269,19 @@ export class AgentRegistry {
259
269
  mailbox: (record.mailbox ?? [])
260
270
  .slice(-this.maxMailboxMessages)
261
271
  .map((message) => ({ ...message, recipientId: record.id })),
272
+ completionRequirements: (record.completionRequirements ?? []).map((item) => ({
273
+ ...item,
274
+ })),
262
275
  history: record.history.slice(-this.maxHistoryTurns).map((turn) => ({ ...turn })),
263
276
  });
277
+ const restored = this.agents.get(record.id);
278
+ if (restored && restored.state !== "running" && restored.state !== "starting") {
279
+ restored.completionRequirements = cancelPendingCompletionRequirements(
280
+ restored.completionRequirements,
281
+ restored.state,
282
+ this.now(),
283
+ );
284
+ }
264
285
  }
265
286
  for (const agent of this.agents.values()) {
266
287
  if (agent.parentId) {
@@ -291,6 +312,7 @@ export class AgentRegistry {
291
312
  idleTimeoutMs?: number;
292
313
  maxTurns?: number;
293
314
  maxToolCalls?: number;
315
+ completionRequirement?: CompletionRequirementMode;
294
316
  parentId?: string;
295
317
  context?: string;
296
318
  contextSourceIds?: string[];
@@ -316,6 +338,7 @@ export class AgentRegistry {
316
338
  input.spawnRequestHash,
317
339
  );
318
340
  if (existing) return existing;
341
+ this.assertRequiredCompletionCapacity(input.completionRequirement);
319
342
  const task = truncateUtf8(input.task, this.maxTaskBytes).text;
320
343
  const expired = this.evictExpired();
321
344
  let expiryReleaseError: unknown;
@@ -369,6 +392,7 @@ export class AgentRegistry {
369
392
  currentTask: task,
370
393
  turnGeneration: 0,
371
394
  pendingCompletions: [],
395
+ completionRequirements: [],
372
396
  history: [],
373
397
  mailbox: [],
374
398
  context: input.context,
@@ -393,7 +417,7 @@ export class AgentRegistry {
393
417
  parent.updatedAt = now;
394
418
  }
395
419
  await this.changed();
396
- this.startTurn(record, task, input);
420
+ this.startTurn(record, task, input, input.completionRequirement);
397
421
  return this.copy(record);
398
422
  }
399
423
 
@@ -417,7 +441,10 @@ export class AgentRegistry {
417
441
  async followUp(
418
442
  id: string,
419
443
  task: string,
420
- options: TurnLimits & { timeoutMs?: number } = {},
444
+ options: TurnLimits & {
445
+ timeoutMs?: number;
446
+ completionRequirement?: CompletionRequirementMode;
447
+ } = {},
421
448
  ): Promise<ManagedAgent> {
422
449
  if (!task.trim()) throw new Error("Subagent tasks cannot be empty");
423
450
  if (options.timeoutMs !== undefined) validateTurnTimeout(options.timeoutMs);
@@ -428,6 +455,7 @@ export class AgentRegistry {
428
455
  ![
429
456
  "idle",
430
457
  "completed",
458
+ "partial",
431
459
  "blocked",
432
460
  "needs-input",
433
461
  "abstained",
@@ -438,9 +466,10 @@ export class AgentRegistry {
438
466
  ) {
439
467
  throw new Error(`Agent ${id} cannot accept follow-up while ${agent.state}`);
440
468
  }
469
+ this.assertRequiredCompletionCapacity(options.completionRequirement);
441
470
  const unread = agent.mailbox.filter((message) => !message.readAt).slice(-20);
442
471
  agent.currentMailboxMessageIds = unread.map((message) => message.id);
443
- this.startTurn(agent, boundedTask, options);
472
+ this.startTurn(agent, boundedTask, options, options.completionRequirement);
444
473
  return this.copy(agent);
445
474
  }
446
475
 
@@ -628,6 +657,13 @@ export class AgentRegistry {
628
657
  createdAt: this.completionCreatedAt(),
629
658
  };
630
659
  agent.state = "interrupted";
660
+ agent.completionRequirements = makeCompletionAvailable(agent.completionRequirements, {
661
+ runId: persistedCompletion.runId,
662
+ generation: persistedCompletion.generation,
663
+ completionId: persistedCompletion.completionId,
664
+ terminalState: agent.state,
665
+ updatedAt: persistedCompletion.createdAt,
666
+ });
631
667
  agent.pendingCompletions = [...(agent.pendingCompletions ?? []), persistedCompletion];
632
668
  this.enqueueCompletionMessage(agent, persistedCompletion, persistedCompletion.error);
633
669
  clearCurrentTurn(agent);
@@ -698,6 +734,11 @@ export class AgentRegistry {
698
734
  await this.running.get(agentId)?.catch(() => undefined);
699
735
  agent.state = "closed";
700
736
  agent.updatedAt = this.now();
737
+ agent.completionRequirements = cancelPendingCompletionRequirements(
738
+ agent.completionRequirements,
739
+ agent.state,
740
+ agent.updatedAt,
741
+ );
701
742
  if (agent.parentId) {
702
743
  const parent = this.agents.get(agent.parentId);
703
744
  if (parent) parent.children = parent.children.filter((childId) => childId !== agentId);
@@ -766,6 +807,11 @@ export class AgentRegistry {
766
807
  if (agent.state === "running" || agent.state === "starting") {
767
808
  agent.state = "interrupted";
768
809
  }
810
+ agent.completionRequirements = cancelPendingCompletionRequirements(
811
+ agent.completionRequirements,
812
+ agent.state,
813
+ this.now(),
814
+ );
769
815
  clearCurrentTurn(agent);
770
816
  }
771
817
  }
@@ -814,6 +860,9 @@ export class AgentRegistry {
814
860
  currentTask: agent.currentTask,
815
861
  currentRunId: agent.currentRunId,
816
862
  currentTurnGeneration: agent.currentTurnGeneration,
863
+ completionRequirements: (agent.completionRequirements ?? []).map((record) => ({
864
+ ...record,
865
+ })),
817
866
  error: agent.error,
818
867
  workspaceMode: agent.workspaceMode,
819
868
  contextTurns: agent.contextTurns,
@@ -864,6 +913,37 @@ export class AgentRegistry {
864
913
  return agent ? this.copy(agent) : undefined;
865
914
  }
866
915
 
916
+ reconcileCompletionRequirements(
917
+ activeRecords: ReadonlyMap<
918
+ string,
919
+ import("./completion-requirement.js").CompletionRequirementRecord
920
+ >,
921
+ observedState: boolean,
922
+ ): void {
923
+ if (!observedState) return;
924
+ for (const agent of this.agents.values()) {
925
+ agent.completionRequirements = (agent.completionRequirements ?? []).flatMap((record) => {
926
+ const restored = activeRecords.get(completionRequirementKey(record));
927
+ if (!restored) return [];
928
+ if (
929
+ restored.state === "pending" &&
930
+ agent.state !== "running" &&
931
+ agent.state !== "starting"
932
+ ) {
933
+ return [
934
+ {
935
+ ...restored,
936
+ state: "cancelled" as const,
937
+ terminalState: "interrupted" as const,
938
+ updatedAt: Math.max(restored.updatedAt, this.now()),
939
+ },
940
+ ];
941
+ }
942
+ return [{ ...restored }];
943
+ });
944
+ }
945
+ }
946
+
867
947
  listPendingCompletions(): AgentTurnCompletion[] {
868
948
  return [...this.agents.values()]
869
949
  .flatMap((agent) =>
@@ -889,6 +969,11 @@ export class AgentRegistry {
889
969
  (completion) => completion.completionId === completionId,
890
970
  );
891
971
  if (!acknowledged) return;
972
+ agent.completionRequirements = makeCompletionVisible(
973
+ agent.completionRequirements,
974
+ completionId,
975
+ deliveredAt,
976
+ );
892
977
  agent.pendingCompletions = (agent.pendingCompletions ?? []).filter(
893
978
  (completion) => completion.completionId !== completionId,
894
979
  );
@@ -903,6 +988,11 @@ export class AgentRegistry {
903
988
  try {
904
989
  await this.changed(true);
905
990
  } catch (error) {
991
+ agent.completionRequirements = (agent.completionRequirements ?? []).map((record) =>
992
+ record.completionId === completionId && record.state === "visible"
993
+ ? { ...record, state: "available", updatedAt: acknowledged.createdAt }
994
+ : record,
995
+ );
906
996
  if (
907
997
  !agent.pendingCompletions?.some(
908
998
  (completion) => completion.completionId === acknowledged.completionId,
@@ -937,6 +1027,7 @@ export class AgentRegistry {
937
1027
  agent: ManagedAgent,
938
1028
  task: string,
939
1029
  limits: TurnLimits & { timeoutMs?: number } = {},
1030
+ completionRequirement: CompletionRequirementMode = "background",
940
1031
  ): void {
941
1032
  if ((agent.pendingCompletions?.length ?? 0) >= MAX_PENDING_COMPLETIONS_PER_AGENT) {
942
1033
  throw new Error(
@@ -946,6 +1037,13 @@ export class AgentRegistry {
946
1037
  agent.turnGeneration = (agent.turnGeneration ?? 0) + 1;
947
1038
  agent.currentTurnGeneration = agent.turnGeneration;
948
1039
  agent.currentRunId = `run:${agent.id}:${randomUUID()}`;
1040
+ if (completionRequirement === "required") {
1041
+ agent.completionRequirements = beginCompletionRequirement(agent.completionRequirements, {
1042
+ runId: agent.currentRunId,
1043
+ generation: agent.currentTurnGeneration,
1044
+ createdAt: this.now(),
1045
+ });
1046
+ }
949
1047
  agent.state = "starting";
950
1048
  agent.error = undefined;
951
1049
  agent.currentTask = task;
@@ -962,6 +1060,12 @@ export class AgentRegistry {
962
1060
  queuePosition: this.queue.length + 1,
963
1061
  updatedAt: agent.updatedAt,
964
1062
  timing: { queuedAt: agent.updatedAt },
1063
+ budgetSource: {
1064
+ timeout: agent.currentTimeoutMs === undefined ? "runtime" : "explicit",
1065
+ idleTimeout: agent.currentIdleTimeoutMs === undefined ? "runtime" : "explicit",
1066
+ turnLimit: agent.currentMaxTurns === undefined ? "runtime" : "explicit",
1067
+ toolCallLimit: agent.currentMaxToolCalls === undefined ? "runtime" : "explicit",
1068
+ },
965
1069
  };
966
1070
  let resolveQueued!: (agent: ManagedAgent) => void;
967
1071
  const completion = new Promise<ManagedAgent>((resolve) => {
@@ -1007,6 +1111,13 @@ export class AgentRegistry {
1007
1111
  error: truncateUtf8(agent.error, 512).text,
1008
1112
  createdAt: this.completionCreatedAt(),
1009
1113
  };
1114
+ agent.completionRequirements = makeCompletionAvailable(agent.completionRequirements, {
1115
+ runId: persistedCompletion.runId,
1116
+ generation: persistedCompletion.generation,
1117
+ completionId: persistedCompletion.completionId,
1118
+ terminalState: agent.state,
1119
+ updatedAt: persistedCompletion.createdAt,
1120
+ });
1010
1121
  agent.pendingCompletions = [...(agent.pendingCompletions ?? []), persistedCompletion];
1011
1122
  this.enqueueCompletionMessage(agent, persistedCompletion, persistedCompletion.error ?? "");
1012
1123
  clearCurrentTurn(agent);
@@ -1078,20 +1189,25 @@ export class AgentRegistry {
1078
1189
  agent.history = agent.history.slice(-this.maxHistoryTurns);
1079
1190
  agent.structuredResult =
1080
1191
  outcome.structuredResult ?? parseAnyStructuredSubagentResult(output, agent.resultFormat);
1081
- agent.outcome =
1082
- outcome.outcome ??
1083
- (outcome.aborted
1084
- ? classifyStructuredOutcome("interrupted", "transport-aborted")
1085
- : agent.structuredResult?.version === "pi-subagents:result:v2"
1086
- ? classifyStructuredOutcome(
1087
- agent.structuredResult.status,
1088
- agent.structuredResult.reasonCode,
1089
- )
1090
- : agent.resultFormat !== undefined &&
1091
- agent.resultFormat !== "text" &&
1092
- agent.structuredResult === undefined
1093
- ? classifyStructuredOutcome("contract-invalid", "malformed-structured-result")
1094
- : undefined);
1192
+ const malformedStructuredResult =
1193
+ agent.resultFormat !== undefined &&
1194
+ agent.resultFormat !== "text" &&
1195
+ agent.structuredResult === undefined;
1196
+ const finalizedPartial =
1197
+ outcome.termination?.finalization.status === "completed" && output.trim().length > 0;
1198
+ agent.outcome = outcome.aborted
1199
+ ? classifyStructuredOutcome("interrupted", "transport-aborted")
1200
+ : malformedStructuredResult
1201
+ ? classifyStructuredOutcome("contract-invalid", "malformed-structured-result")
1202
+ : finalizedPartial
1203
+ ? classifyStructuredOutcome("partial", outcome.termination?.reason)
1204
+ : (outcome.outcome ??
1205
+ (agent.structuredResult?.version === "pi-subagents:result:v2"
1206
+ ? classifyStructuredOutcome(
1207
+ agent.structuredResult.status,
1208
+ agent.structuredResult.reasonCode,
1209
+ )
1210
+ : undefined));
1095
1211
  const staleGeneration = Boolean(
1096
1212
  acceptedPlanId && agent.executionPlan?.id !== acceptedPlanId,
1097
1213
  );
@@ -1102,7 +1218,7 @@ export class AgentRegistry {
1102
1218
  ? "stale"
1103
1219
  : outcome.aborted
1104
1220
  ? "interrupted"
1105
- : outcome.exitCode !== 0
1221
+ : outcome.exitCode !== 0 && agent.outcome?.status !== "partial"
1106
1222
  ? "failed"
1107
1223
  : lifecycleStateForOutcome(agent.outcome?.status);
1108
1224
  agent.error = error;
@@ -1121,6 +1237,7 @@ export class AgentRegistry {
1121
1237
  queuedAt: agent.telemetry?.timing.queuedAt,
1122
1238
  ...outcome.telemetry.timing,
1123
1239
  },
1240
+ budgetSource: outcome.telemetry.budgetSource ?? agent.telemetry?.budgetSource,
1124
1241
  }
1125
1242
  : agent.telemetry;
1126
1243
  agent.termination = outcome.termination
@@ -1195,6 +1312,13 @@ export class AgentRegistry {
1195
1312
  error: completionError ? truncateUtf8(completionError, 512).text : undefined,
1196
1313
  createdAt: this.completionCreatedAt(),
1197
1314
  };
1315
+ agent.completionRequirements = makeCompletionAvailable(agent.completionRequirements, {
1316
+ runId,
1317
+ generation: turnGeneration,
1318
+ completionId: persistedCompletion.completionId,
1319
+ terminalState: agent.state,
1320
+ updatedAt: persistedCompletion.createdAt,
1321
+ });
1198
1322
  agent.pendingCompletions = [...(agent.pendingCompletions ?? []), persistedCompletion];
1199
1323
  this.enqueueCompletionMessage(agent, persistedCompletion, completionContent);
1200
1324
  clearCurrentTurn(agent);
@@ -1357,6 +1481,19 @@ export class AgentRegistry {
1357
1481
  return { taskName, taskPath };
1358
1482
  }
1359
1483
 
1484
+ private assertRequiredCompletionCapacity(mode: CompletionRequirementMode | undefined): void {
1485
+ if (mode !== "required") return;
1486
+ const unresolved = [...this.agents.values()].reduce(
1487
+ (sum, agent) => sum + pendingRequiredCompletionCount(agent),
1488
+ 0,
1489
+ );
1490
+ if (unresolved >= MAX_UNRESOLVED_REQUIRED_COMPLETIONS) {
1491
+ throw new Error(
1492
+ `Required subagent completion capacity reached (${MAX_UNRESOLVED_REQUIRED_COMPLETIONS}); wait for exact completion visibility or terminalize an existing requirement`,
1493
+ );
1494
+ }
1495
+ }
1496
+
1360
1497
  private retainedCount(): number {
1361
1498
  return [...this.agents.values()].filter((agent) => agent.state !== "closed").length;
1362
1499
  }
@@ -1479,6 +1616,7 @@ export class AgentRegistry {
1479
1616
  unreadMessages,
1480
1617
  turnGeneration: agent.turnGeneration ?? 0,
1481
1618
  pendingCompletionCount: agent.pendingCompletions?.length ?? 0,
1619
+ pendingRequiredCompletionCount: pendingRequiredCompletionCount(agent),
1482
1620
  };
1483
1621
  }
1484
1622
 
@@ -1493,6 +1631,9 @@ export class AgentRegistry {
1493
1631
  pendingCompletions: (agent.pendingCompletions ?? []).map((completion) => ({
1494
1632
  ...completion,
1495
1633
  })),
1634
+ completionRequirements: (agent.completionRequirements ?? []).map((record) => ({
1635
+ ...record,
1636
+ })),
1496
1637
  history: agent.history.map((turn) => ({ ...turn })),
1497
1638
  mailbox: agent.mailbox.map((message) => ({ ...message })),
1498
1639
  contract: agent.contract ? structuredClone(agent.contract) : undefined,
@@ -1526,6 +1667,8 @@ function lifecycleStateForOutcome(
1526
1667
  status: import("./result-contract.js").SubagentOutcomeStatus | undefined,
1527
1668
  ): AgentLifecycleState {
1528
1669
  switch (status) {
1670
+ case "partial":
1671
+ return "partial";
1529
1672
  case "blocked":
1530
1673
  case "needs-input":
1531
1674
  case "abstained":
@@ -1550,5 +1693,6 @@ function copyTelemetry(value: TransportTelemetry): TransportTelemetry {
1550
1693
  ...value,
1551
1694
  timing: { ...value.timing },
1552
1695
  usage: value.usage ? { ...value.usage } : undefined,
1696
+ budgetSource: value.budgetSource ? { ...value.budgetSource } : undefined,
1553
1697
  };
1554
1698
  }
package/src/render.ts CHANGED
@@ -11,8 +11,9 @@ import type { AgentScope } from "./agents/types.js";
11
11
  import { renderPanelCall, renderPanelResult } from "./panel-render.js";
12
12
  import { hasUsableAggregator, type SubagentParams } from "./params.js";
13
13
  import { expansionHint, formatToolActivity, safeBlock, safeLine } from "./render-common.js";
14
- import type { SingleResult, SubagentDetails } from "./runner.js";
15
14
  import { getResultFinalOutput, isResultError } from "./runner-outcome.js";
15
+ import type { SingleResult } from "./runner-types.js";
16
+ import type { SubagentDetails } from "./subagent-details.js";
16
17
  import { formatUsageStats } from "./usage-format.js";
17
18
 
18
19
  const COLLAPSED_ITEM_COUNT = 5;
@@ -24,6 +24,7 @@ import {
24
24
  peerBridgeEnvironment,
25
25
  } from "./peer-transport.js";
26
26
  import { type PiInvocation, resolvePiInvocation } from "./pi-invocation.js";
27
+ import { terminateProcess } from "./process-control.js";
27
28
  import { resolvePiPromptResources } from "./prompt-resources.js";
28
29
  import { JsonLineDecoder } from "./protocol.js";
29
30
  import type { AgentMailboxMessage, ManagedAgent, TurnOutcome } from "./registry.js";
@@ -42,7 +43,6 @@ import {
42
43
  observeRpcBudgetEvent,
43
44
  snapshotRpcUsage,
44
45
  } from "./rpc-turn-capture.js";
45
- import { terminateProcess } from "./runner.js";
46
46
  import { safeTerminalText } from "./safe-text.js";
47
47
  import { readSubagentSettings } from "./settings.js";
48
48
  import { buildStatefulTurnPrompt, resolveStatefulTurnTimeout } from "./stateful-prompt.js";
@@ -1,9 +1,9 @@
1
- import type { SingleResult } from "./runner.js";
2
1
  import {
3
2
  formatResultFailure as formatBaseResultFailure,
4
3
  getResultFinalOutput as getBaseResultFinalOutput,
5
4
  isResultError as isBaseResultError,
6
5
  } from "./runner-result.js";
6
+ import type { SingleResult } from "./runner-types.js";
7
7
 
8
8
  export function getResultFinalOutput(result: SingleResult): string {
9
9
  return getBaseResultFinalOutput(result);
@@ -1,6 +1,6 @@
1
1
  import type { Message } from "@earendil-works/pi-ai";
2
2
  import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
3
- import type { SingleResult } from "./runner.js";
3
+ import type { SingleResult } from "./runner-types.js";
4
4
  import { formatTimeoutCheckpoint } from "./timeout-checkpoint.js";
5
5
 
6
6
  export function getFinalOutput(messages: Message[]): string {
@@ -0,0 +1,102 @@
1
+ import type { Message } from "@earendil-works/pi-ai";
2
+ import type { AgentSource, SubagentThinkingLevel } from "./agents/types.js";
3
+ import type { CapabilityGrant } from "./capability-grant.js";
4
+ import type { TargetPolicyAudit } from "./cwd-policy.js";
5
+ import type { DelegationContract } from "./delegation-contract.js";
6
+ import type { ExecutionPlan } from "./execution-plan.js";
7
+ import type { ClassifiedSubagentOutcome } from "./outcome.js";
8
+ import type { AnyStructuredSubagentResult, SubagentResultFormat } from "./result-contract.js";
9
+ import type { UsageStats } from "./runner-usage.js";
10
+ import type { TurnTerminationReport } from "./timeout-checkpoint.js";
11
+ import type { TurnLimits } from "./turn-budget.js";
12
+
13
+ export type RecentActivityItem =
14
+ | { type: "text"; text: string }
15
+ | { type: "toolCall"; name: string; args: Record<string, unknown> };
16
+
17
+ export interface SingleResult {
18
+ agent: string;
19
+ agentSource: AgentSource | "unknown";
20
+ task: string;
21
+ exitCode: number;
22
+ messages: Message[];
23
+ stderr: string;
24
+ usage: UsageStats;
25
+ model?: string;
26
+ actualProvider?: string;
27
+ actualModel?: string;
28
+ recentActivity?: RecentActivityItem[];
29
+ recentActivityTotal?: number;
30
+ thinkingLevel?: SubagentThinkingLevel;
31
+ stopReason?: string;
32
+ errorMessage?: string;
33
+ step?: number;
34
+ finalOutput?: string;
35
+ partialOutput?: string;
36
+ timeoutSummary?: string;
37
+ timeoutSummaryError?: string;
38
+ termination?: TurnTerminationReport;
39
+ timedOut?: boolean;
40
+ timeoutMs?: number;
41
+ aborted?: boolean;
42
+ truncated?: boolean;
43
+ malformedEvents?: number;
44
+ launchFailed?: boolean;
45
+ processStarted?: boolean;
46
+ target?: TargetPolicyAudit;
47
+ policy?: {
48
+ inherited: string[];
49
+ overridden: string[];
50
+ unsupported: string[];
51
+ };
52
+ contract?: DelegationContract;
53
+ resultFormat?: SubagentResultFormat;
54
+ structuredResult?: AnyStructuredSubagentResult;
55
+ resultContractInvalid?: boolean;
56
+ outcome?: ClassifiedSubagentOutcome;
57
+ attemptCount?: number;
58
+ hedged?: boolean;
59
+ executionPlan?: ExecutionPlan;
60
+ capabilityGrant?: CapabilityGrant;
61
+ }
62
+
63
+ export interface ChildLaunchPolicy {
64
+ tools?: string[];
65
+ disableExtensions?: boolean;
66
+ disableSkills?: boolean;
67
+ disablePromptTemplates?: boolean;
68
+ disableContextFiles?: boolean;
69
+ projectTrust?: boolean;
70
+ baseSystemPrompt?: string;
71
+ appendSystemPromptPaths?: string[];
72
+ /** Package-owned explicit child extensions loaded even when unrelated extensions are disabled. */
73
+ extensionPaths?: string[];
74
+ /** Package-owned tools added to the child allowlist without changing delegated execution tools. */
75
+ additionalTools?: string[];
76
+ /** Ephemeral child-process environment consumed and cleared by a package-owned bridge. */
77
+ env?: NodeJS.ProcessEnv;
78
+ /** Internal timeout recovery control; omitted means enabled. */
79
+ finalizeOnTimeout?: boolean;
80
+ /** Internal hard deadline for the summary attempt. */
81
+ timeoutFinalizationMs?: number;
82
+ /** Optional stateful result contract retained during timeout finalization. */
83
+ timeoutResultFormat?: SubagentResultFormat;
84
+ /** Optional non-wall-clock limits for this turn. */
85
+ turnLimits?: TurnLimits;
86
+ /** Override the timeout reason when an orchestration deadline caps this child. */
87
+ workTimeoutReason?: "work_timeout" | "orchestration_timeout";
88
+ /** Public limit value reported when the effective child timeout is only the remaining budget. */
89
+ workTimeoutReportLimit?: number;
90
+ /** Absolute blocking-workflow deadline that also caps model finalization. */
91
+ orchestrationDeadlineAt?: number;
92
+ /** Completion contract requested for this turn. */
93
+ resultFormat?: SubagentResultFormat;
94
+ /** Normalized request contract retained in result details. */
95
+ contract?: DelegationContract;
96
+ /** Original task summary shown in result details when the executed prompt has contract metadata. */
97
+ displayTask?: string;
98
+ /** Immutable audit or enforcement decision made before launch. */
99
+ executionPlan?: ExecutionPlan;
100
+ /** Executor-owned authority lifetime bound to the accepted plan generation. */
101
+ capabilityGrant?: CapabilityGrant;
102
+ }