@pasko70/pibo 2.4.1 → 2.4.3

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 (46) hide show
  1. package/dist/agent-runtime/context-build.js +100 -11
  2. package/dist/agent-runtime/profile-validation.js +3 -0
  3. package/dist/agent-runtime/resource-service.js +16 -0
  4. package/dist/agent-runtime/routed-session.js +180 -30
  5. package/dist/agent-runtime/testing/fake-adapter.js +7 -0
  6. package/dist/agent-runtimes/pi/adapter.js +2 -0
  7. package/dist/agent-runtimes/pi/routed-session.js +104 -43
  8. package/dist/agent-runtimes/pi/runtime.js +8 -5
  9. package/dist/apps/chat/web-app.js +21 -6
  10. package/dist/apps/chat-ui/assets/{dist-Cw9po47P.js → dist-C4JGcQjh.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-BeqHbnGN.js → dist-CF92Lv76.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-DTRjeLwO.js → dist-CJ0JS-bE.js} +1 -1
  13. package/dist/apps/chat-ui/assets/{dist-3YG57JXi.js → dist-DrgKyb9n.js} +1 -1
  14. package/dist/apps/chat-ui/assets/{dist-CrDtveZB.js → dist-VoMyV-PE.js} +1 -1
  15. package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +1 -0
  16. package/dist/apps/chat-ui/assets/index-DhX1_aRM.js +228 -0
  17. package/dist/apps/chat-ui/index.html +2 -2
  18. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  19. package/dist/apps/vscode-artifacts/{pibo-vscode-ext-2.4.1.vsix → pibo-vscode-ext-2.4.3.vsix} +0 -0
  20. package/dist/cli.js +16 -6
  21. package/dist/core/context-build.js +66 -6
  22. package/dist/core/model-defaults.js +11 -3
  23. package/dist/core/session-router.js +285 -112
  24. package/dist/gateway/server.js +1 -0
  25. package/dist/loops/accounting.js +80 -0
  26. package/dist/loops/service.js +52 -13
  27. package/dist/loops/store.js +67 -19
  28. package/dist/loops/tools.js +3 -1
  29. package/dist/reliability/store.js +11 -6
  30. package/dist/runs/lifecycle.js +38 -1
  31. package/dist/runs/registry.js +46 -45
  32. package/dist/runs/tools.js +34 -24
  33. package/dist/subagents/context.js +48 -0
  34. package/dist/subagents/runtime-selection.js +28 -0
  35. package/dist/subagents/tool.js +28 -6
  36. package/dist/tools/codex-compat.js +1 -0
  37. package/dist/tools/contract.js +10 -0
  38. package/dist/tools/mcp-bridge.js +4 -2
  39. package/dist/tools/runtime/node-backend.js +8 -2
  40. package/dist/tools/runtime/python-backend.js +8 -2
  41. package/dist/tools/runtime/tool.js +1 -0
  42. package/dist/tools/session-tool-set.js +19 -12
  43. package/npm-shrinkwrap.json +2 -2
  44. package/package.json +1 -1
  45. package/dist/apps/chat-ui/assets/index-AjnP3ci-.js +0 -228
  46. package/dist/apps/chat-ui/assets/index-BJ56TREg.css +0 -1
@@ -5,7 +5,7 @@ import { RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-
5
5
  import { runtimeSessionErrorDetails } from "./session-errors.js";
6
6
  import { normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, parsePiboAgentObservationTimestamp, piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationSourceFromEvent, piboAgentObservationText, } from "../subagents/observations.js";
7
7
  import { PiboRunRegistry } from "../runs/registry.js";
8
- import { PiboRunExecutionTimeoutError } from "../runs/lifecycle.js";
8
+ import { PiboRunCancellationError, PiboRunCancelledError, PiboRunExecutionTimeoutError, waitForRunCancellationSettlement } from "../runs/lifecycle.js";
9
9
  import { PiboRunResourceLimitError } from "../runs/resource-isolation.js";
10
10
  import { createPiboSignalRegistry } from "../signals/registry.js";
11
11
  import { createDefaultPiboReliabilityStore } from "../reliability/store.js";
@@ -29,7 +29,6 @@ import { createPiboToolPayloadWriter } from "../tools/payload-writer.js";
29
29
  import { PiboPortableToolService, } from "../tools/session-service.js";
30
30
  import { PiboRuntimeResourceService, } from "../agent-runtime/resource-service.js";
31
31
  import { PORTABLE_HISTORY_HANDOFF_METADATA_KEY, PORTABLE_HISTORY_LAST_IMPORT_METADATA_KEY, PiboDataPortableHistoryProvider, createPortableHistoryHandoffMetadata, readPortableHistoryHandoffMetadata, withoutPortableHistoryHandoffMetadata, withPortableHistoryHandoffMetadata, } from "../agent-runtime/portable-history.js";
32
- const DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS = 10 * 60 * 1000;
33
32
  const DEFAULT_SUBAGENT_MAX_DEPTH = 1;
34
33
  const MAX_SUBAGENT_THREAD_KEY_BYTES = 512;
35
34
  const MAX_AGENT_OBSERVATIONS = 5_000;
@@ -70,6 +69,9 @@ function subagentAbortError() {
70
69
  error.name = "AbortError";
71
70
  return error;
72
71
  }
72
+ function subagentRequestEventKey(piboSessionId, eventId) {
73
+ return `${piboSessionId}\u0000${eventId}`;
74
+ }
73
75
  function profileForSession(baseProfile, runtimeInstanceId, nativeSessionId, parentNativeSessionId, subagentDepth) {
74
76
  const usesProfileRuntime = baseProfile.runtimeInstanceId === runtimeInstanceId;
75
77
  const options = {
@@ -150,7 +152,29 @@ function formatRunReminderMessage(notification) {
150
152
  ].join("\n");
151
153
  }
152
154
  function isRunReminderServiceMessage(event) {
153
- return event.source === "service" && event.capabilityScope === "run-reminder";
155
+ return event.source === "service" && event.text.startsWith("<pibo_run_notification>");
156
+ }
157
+ function yieldedRunOrigin(event) {
158
+ if (!event?.id || event.provenance?.kind !== "loop-run")
159
+ return undefined;
160
+ return {
161
+ eventId: event.provenance.rootEventId ?? event.id,
162
+ provenance: {
163
+ kind: event.provenance.kind,
164
+ jobId: event.provenance.jobId,
165
+ runId: event.provenance.runId,
166
+ },
167
+ };
168
+ }
169
+ function runReminderProvenance(notification) {
170
+ const origin = notification.origin;
171
+ if (!origin || origin.provenance.kind !== "loop-run")
172
+ return undefined;
173
+ return {
174
+ ...origin.provenance,
175
+ cause: "run-reminder",
176
+ rootEventId: origin.eventId,
177
+ };
154
178
  }
155
179
  function isTerminalRunStatus(status) {
156
180
  return status === "completed" || status === "failed" || status === "timed_out" || status === "cancelled";
@@ -235,13 +259,15 @@ export class PiboSessionRouter {
235
259
  portableHistoryProvider;
236
260
  runtimeResourceSessions = new Map();
237
261
  runtimeAuthFingerprints = new Map();
238
- activeSubagentChildren = new Map();
262
+ activeSubagentRequests = new Map();
263
+ subagentRequestIdsByEvent = new Map();
239
264
  agentObservations = [];
240
265
  agentObservationEvictedThroughByParent = new Map();
241
266
  nextAgentObservationSequence = 1;
242
267
  scheduledRunReminders = new Map();
243
268
  runReminderGenerations = new Map();
244
269
  runCancellationHandlers = new Map();
270
+ activeRunExecutions = new Set();
245
271
  quiescingSessions = new Set();
246
272
  disposingSessions = new Map();
247
273
  idleSessionTimers = new Map();
@@ -388,11 +414,24 @@ export class PiboSessionRouter {
388
414
  teardownCompleted = true;
389
415
  return output;
390
416
  }
391
- const childAbort = event.action === "abort"
392
- ? this.abortActiveSubagentSessions(event.piboSessionId)
393
- : undefined;
394
- const output = await session.executeAction(event);
395
- await childAbort;
417
+ let output;
418
+ if (event.action === "abort") {
419
+ const [sessionAbort, childAbort] = await Promise.allSettled([
420
+ session.executeAction(event),
421
+ this.abortActiveSubagentSessions(event.piboSessionId),
422
+ ]);
423
+ if (sessionAbort.status === "rejected" && childAbort.status === "rejected") {
424
+ throw new AggregateError([sessionAbort.reason, childAbort.reason], "Failed to abort the session and its active subagent requests.");
425
+ }
426
+ if (sessionAbort.status === "rejected")
427
+ throw sessionAbort.reason;
428
+ if (childAbort.status === "rejected")
429
+ throw childAbort.reason;
430
+ output = sessionAbort.value;
431
+ }
432
+ else {
433
+ output = await session.executeAction(event);
434
+ }
396
435
  if (event.action === "kill" || event.action === "kill_all") {
397
436
  await this.disposeSessionSubtree(event.piboSessionId, `${event.action} action`, { cancelRuns: event.action === "kill_all" });
398
437
  teardownCompleted = true;
@@ -446,24 +485,24 @@ export class PiboSessionRouter {
446
485
  const failures = [];
447
486
  for (const id of ids) {
448
487
  const session = this.sessions.get(id);
449
- if (session) {
450
- this.signalRegistry.project({ type: "session_disposed", piboSessionId: id, reason: "kill" });
451
- try {
452
- killed.push(await session.kill());
453
- }
454
- catch (error) {
455
- failures.push(error);
456
- }
488
+ if (!session)
489
+ continue;
490
+ this.signalRegistry.project({ type: "session_disposed", piboSessionId: id, reason: "kill" });
491
+ try {
492
+ killed.push(await session.kill());
457
493
  }
458
- if (options?.includeRuns) {
459
- const runs = this.runRegistry.cancelControllerRuns(id);
460
- cancelledRuns.push(...runs.map((run) => run.runId));
461
- try {
462
- await this.invokeRunCancellationHandlers(runs);
463
- }
464
- catch (error) {
465
- failures.push(error);
466
- }
494
+ catch (error) {
495
+ failures.push(error);
496
+ }
497
+ }
498
+ if (options?.includeRuns) {
499
+ const runs = ids.flatMap((id) => this.runRegistry.listActiveControllerRuns(id));
500
+ try {
501
+ const cancelled = await this.cancelRunsAfterSettlement(runs, "Pibo session subtree was killed.");
502
+ cancelledRuns.push(...cancelled.map((run) => run.runId));
503
+ }
504
+ catch (error) {
505
+ failures.push(error);
467
506
  }
468
507
  }
469
508
  try {
@@ -520,10 +559,9 @@ export class PiboSessionRouter {
520
559
  const operation = (async () => {
521
560
  await startGate;
522
561
  const runCancellationResults = options.cancelRuns
523
- ? await Promise.allSettled(ids.map(async (id) => {
524
- const runs = this.runRegistry.cancelControllerRuns(id);
525
- await this.invokeRunCancellationHandlers(runs);
526
- }))
562
+ ? await Promise.allSettled([
563
+ this.cancelRunsAfterSettlement(ids.flatMap((id) => this.runRegistry.listActiveControllerRuns(id)), reason),
564
+ ])
527
565
  : [];
528
566
  const pending = ids.map((id) => this.pendingSessions.get(id)).filter((value) => Boolean(value));
529
567
  if (pending.length > 0)
@@ -597,11 +635,11 @@ export class PiboSessionRouter {
597
635
  const childSession = this.sessions.get(id);
598
636
  if (childSession)
599
637
  killed.push(await childSession.kill());
600
- if (options?.includeRuns) {
601
- const runs = this.runRegistry.cancelControllerRuns(id);
602
- cancelledRuns.push(...runs.map((run) => run.runId));
603
- await this.invokeRunCancellationHandlers(runs);
604
- }
638
+ if (!options?.includeRuns)
639
+ continue;
640
+ const runs = this.runRegistry.listActiveControllerRuns(id);
641
+ const cancelled = await this.cancelRunsAfterSettlement(runs, `Child Pibo session "${id}" was killed.`);
642
+ cancelledRuns.push(...cancelled.map((run) => run.runId));
605
643
  }
606
644
  return { killed, cancelledRuns };
607
645
  }
@@ -652,6 +690,18 @@ export class PiboSessionRouter {
652
690
  const session = this.sessionStore.get(piboSessionId);
653
691
  return session ? structuredClone(this.resolveSessionRuntimeBinding(session)) : undefined;
654
692
  }
693
+ getSessionRuntimeProfile(piboSessionId) {
694
+ const session = this.resolvePiboSession(piboSessionId);
695
+ const binding = this.resolveSessionRuntimeBinding(session);
696
+ const parent = session.parentId ? this.resolvePiboSession(session.parentId) : undefined;
697
+ const parentBinding = parent ? this.resolveSessionRuntimeBinding(parent) : undefined;
698
+ const parentNativeSessionId = parentBinding
699
+ && parentBinding.runtimeInstanceId === binding.runtimeInstanceId
700
+ && parentBinding.adapterId === binding.adapterId
701
+ ? parentBinding.nativeSessionId
702
+ : undefined;
703
+ return profileForSession(createPiboProfileFromRegistryOrDefault(this.pluginRegistry, session.profile), binding.runtimeInstanceId, binding.nativeSessionId, parentNativeSessionId, this.getSubagentDepth(session.id));
704
+ }
655
705
  async rebindSessionRuntime(piboSessionId, input) {
656
706
  if (this.quiescingSessions.has(piboSessionId)) {
657
707
  throw new Error(`Pibo session "${piboSessionId}" is already quiescing.`);
@@ -833,24 +883,14 @@ export class PiboSessionRouter {
833
883
  subscribeSignalStatuses(listener) {
834
884
  return this.signalRegistry.subscribeAll(listener);
835
885
  }
836
- async emitMessageAndWaitForReply(event, timeoutMs = 120000, signal) {
886
+ async emitMessageAndWaitForReply(event, timeoutMs, signal) {
837
887
  const eventWithId = { ...event, id: event.id ?? randomUUID() };
838
888
  return await new Promise((resolve, reject) => {
839
889
  let settled = false;
840
- let messageDispatched = false;
890
+ let dispatchPromise;
841
891
  let lastAssistantMessage;
842
892
  let timeout;
843
- const abortChild = () => {
844
- if (!messageDispatched)
845
- return;
846
- void this.emit({
847
- type: "execution",
848
- piboSessionId: eventWithId.piboSessionId,
849
- action: "abort",
850
- id: randomUUID(),
851
- }).catch(() => { });
852
- };
853
- const finish = (result) => {
893
+ const claimSettlement = () => {
854
894
  if (settled)
855
895
  return false;
856
896
  settled = true;
@@ -858,16 +898,33 @@ export class PiboSessionRouter {
858
898
  clearTimeout(timeout);
859
899
  signal?.removeEventListener("abort", onAbort);
860
900
  unsubscribe();
901
+ return true;
902
+ };
903
+ const finish = (result) => {
904
+ if (!claimSettlement())
905
+ return;
861
906
  if (result instanceof Error)
862
907
  reject(result);
863
908
  else
864
909
  resolve(result);
865
- return true;
866
910
  };
867
- const onAbort = () => {
868
- if (!finish(subagentAbortError()))
911
+ const rejectAfterMessageCancellation = (error) => {
912
+ if (!claimSettlement())
869
913
  return;
870
- abortChild();
914
+ void (async () => {
915
+ try {
916
+ await dispatchPromise;
917
+ await this.cancelSessionMessage(eventWithId.piboSessionId, eventWithId.id);
918
+ }
919
+ catch (cancellationError) {
920
+ reject(new PiboRunCancellationError(`Failed to cancel subagent request "${eventWithId.id}" in Pibo session "${eventWithId.piboSessionId}".`, { cause: cancellationError }));
921
+ return;
922
+ }
923
+ reject(error);
924
+ })();
925
+ };
926
+ const onAbort = () => {
927
+ rejectAfterMessageCancellation(subagentAbortError());
871
928
  };
872
929
  const unsubscribe = this.subscribe((output) => {
873
930
  if (output.piboSessionId !== eventWithId.piboSessionId ||
@@ -886,20 +943,27 @@ export class PiboSessionRouter {
886
943
  }
887
944
  });
888
945
  if (signal?.aborted) {
889
- onAbort();
946
+ finish(subagentAbortError());
890
947
  return;
891
948
  }
892
949
  signal?.addEventListener("abort", onAbort, { once: true });
893
- timeout = setTimeout(() => {
894
- const timeoutError = new Error(`Timed out waiting for assistant reply from Pibo session "${eventWithId.piboSessionId}"`);
895
- if (!finish(timeoutError))
896
- return;
897
- abortChild();
898
- }, timeoutMs);
899
- messageDispatched = true;
900
- this.emit(eventWithId).catch(finish);
950
+ if (timeoutMs !== undefined) {
951
+ timeout = setTimeout(() => {
952
+ rejectAfterMessageCancellation(new PiboRunExecutionTimeoutError(`Timed out waiting for assistant reply from Pibo session "${eventWithId.piboSessionId}"`, "lifetime"));
953
+ }, timeoutMs);
954
+ }
955
+ dispatchPromise = this.emit(eventWithId);
956
+ dispatchPromise.catch((error) => {
957
+ finish(error instanceof Error ? error : new Error(String(error)));
958
+ });
901
959
  });
902
960
  }
961
+ async cancelSessionMessage(piboSessionId, eventId) {
962
+ const session = this.sessions.get(piboSessionId);
963
+ if (!session || !await session.cancelMessage(eventId)) {
964
+ throw new Error(`Pibo session "${piboSessionId}" no longer owns message "${eventId}".`);
965
+ }
966
+ }
903
967
  async disposeAll() {
904
968
  if (this.disposePromise)
905
969
  return this.disposePromise;
@@ -916,8 +980,9 @@ export class PiboSessionRouter {
916
980
  for (const timer of this.idleSessionTimers.values())
917
981
  clearTimeout(timer);
918
982
  this.idleSessionTimers.clear();
919
- const cancelledRuns = this.runRegistry.cancelAll("Pibo session router was disposed.");
920
- const runCancellationResult = await Promise.allSettled([this.invokeRunCancellationHandlers(cancelledRuns)]);
983
+ const runCancellationResult = await Promise.allSettled([
984
+ this.cancelRunsAfterSettlement(this.runRegistry.listActiveRuns(), "Pibo session router was disposed."),
985
+ ]);
921
986
  this.scheduledRunReminders.clear();
922
987
  const closeResult = await Promise.allSettled([this.runtimeRegistry.closeAll({ force: true })]);
923
988
  const disposeResults = await Promise.allSettled(sessions.map(([id, session]) => this.disposeRoutedSession(id, session, "router disposed")));
@@ -946,7 +1011,8 @@ export class PiboSessionRouter {
946
1011
  await this.portableToolService.dispose();
947
1012
  await this.runtimeResourceService.dispose();
948
1013
  this.runtimeResourceSessions.clear();
949
- this.activeSubagentChildren.clear();
1014
+ this.activeSubagentRequests.clear();
1015
+ this.subagentRequestIdsByEvent.clear();
950
1016
  this.agentObservations.length = 0;
951
1017
  this.agentObservationEvictedThroughByParent.clear();
952
1018
  await this.telemetryWriter?.dispose();
@@ -1031,19 +1097,13 @@ export class PiboSessionRouter {
1031
1097
  const piboSession = this.resolvePiboSession(piboSessionId);
1032
1098
  let session;
1033
1099
  this.signalRegistry.project({ type: "session_created", session: piboSession });
1034
- const profile = createPiboProfileFromRegistryOrDefault(this.pluginRegistry, piboSession.profile);
1035
1100
  let binding = this.resolveSessionRuntimeBinding(piboSession);
1036
1101
  const parent = piboSession.parentId ? this.resolvePiboSession(piboSession.parentId) : undefined;
1037
1102
  const parentBinding = parent ? this.resolveSessionRuntimeBinding(parent) : undefined;
1038
1103
  const parentModelScopeId = parent ? parentBinding?.nativeSessionId ?? parent.id : undefined;
1039
- const runtimeParentNativeSessionId = parentBinding
1040
- && parentBinding.runtimeInstanceId === binding.runtimeInstanceId
1041
- && parentBinding.adapterId === binding.adapterId
1042
- ? parentBinding.nativeSessionId
1043
- : undefined;
1044
1104
  const modelDefaults = this.resolveModelDefaults();
1045
1105
  const initialThinkingLevel = resolvePiboSessionInitialThinkingLevel(piboSession);
1046
- const sessionProfile = profileForSession(profile, binding.runtimeInstanceId, binding.nativeSessionId, runtimeParentNativeSessionId, this.getSubagentDepth(piboSession.id));
1106
+ const sessionProfile = this.getSessionRuntimeProfile(piboSession.id);
1047
1107
  const persistedHistoryHandoff = readPortableHistoryHandoffMetadata(binding.metadata);
1048
1108
  if (binding.metadata?.[PORTABLE_HISTORY_HANDOFF_METADATA_KEY] !== undefined && !persistedHistoryHandoff) {
1049
1109
  throw new Error("The pending portable history handoff metadata is invalid; refusing to start a contextless target runtime.");
@@ -1562,77 +1622,118 @@ export class PiboSessionRouter {
1562
1622
  this.signalRegistry.project({ type: "session_created", session: created });
1563
1623
  return created;
1564
1624
  }
1565
- trackActiveSubagent(parentPiboSessionId, childPiboSessionId) {
1566
- let children = this.activeSubagentChildren.get(parentPiboSessionId);
1567
- if (!children) {
1568
- children = new Map();
1569
- this.activeSubagentChildren.set(parentPiboSessionId, children);
1625
+ trackActiveSubagent(parentPiboSessionId, request) {
1626
+ let requests = this.activeSubagentRequests.get(parentPiboSessionId);
1627
+ if (!requests) {
1628
+ requests = new Set();
1629
+ this.activeSubagentRequests.set(parentPiboSessionId, requests);
1570
1630
  }
1571
- children.set(childPiboSessionId, (children.get(childPiboSessionId) ?? 0) + 1);
1631
+ requests.add(request);
1572
1632
  let active = true;
1573
1633
  return () => {
1574
1634
  if (!active)
1575
1635
  return;
1576
1636
  active = false;
1577
- const current = this.activeSubagentChildren.get(parentPiboSessionId);
1637
+ const current = this.activeSubagentRequests.get(parentPiboSessionId);
1578
1638
  if (!current)
1579
1639
  return;
1580
- const remaining = (current.get(childPiboSessionId) ?? 1) - 1;
1581
- if (remaining > 0)
1582
- current.set(childPiboSessionId, remaining);
1583
- else
1584
- current.delete(childPiboSessionId);
1640
+ current.delete(request);
1585
1641
  if (current.size === 0)
1586
- this.activeSubagentChildren.delete(parentPiboSessionId);
1642
+ this.activeSubagentRequests.delete(parentPiboSessionId);
1587
1643
  };
1588
1644
  }
1589
1645
  async abortActiveSubagentSessions(parentPiboSessionId) {
1590
- const childIds = [...(this.activeSubagentChildren.get(parentPiboSessionId)?.keys() ?? [])];
1591
- if (childIds.length === 0)
1592
- return;
1593
- await Promise.allSettled(childIds.map(async (childPiboSessionId) => await this.emit({
1594
- type: "execution",
1595
- piboSessionId: childPiboSessionId,
1596
- action: "abort",
1597
- id: randomUUID(),
1598
- })));
1646
+ const requests = [...(this.activeSubagentRequests.get(parentPiboSessionId) ?? [])];
1647
+ for (const request of requests)
1648
+ request.abortController.abort();
1649
+ const settlements = await Promise.all(requests.map(async (request) => await request.settled));
1650
+ const failures = settlements.flatMap((settlement) => settlement.status === "rejected" ? [settlement.reason] : []);
1651
+ if (failures.length > 0)
1652
+ throw new AggregateError(failures, "Failed to cancel active subagent requests.");
1599
1653
  }
1600
1654
  createAgentsController(parentPiboSessionId) {
1601
1655
  return {
1602
- sendMessage: async ({ subagent, message, threadKey, toolCallId, signal }) => {
1656
+ sendMessage: async ({ subagent, message, threadKey, toolCallId, requestId, parentProvenance, signal }) => {
1657
+ if (signal?.aborted)
1658
+ throw subagentAbortError();
1659
+ if (typeof requestId !== "string" || !requestId.trim())
1660
+ throw new Error("Delegated agent requestId is required.");
1603
1661
  this.assertSubagentDepth(parentPiboSessionId, subagent);
1604
1662
  const child = this.resolveSubagentSession(parentPiboSessionId, subagent, threadKey);
1605
1663
  const resolvedThreadKey = typeof child.metadata?.threadKey === "string" ? child.metadata.threadKey : "";
1664
+ const loopJobId = parentProvenance?.kind === "loop-run"
1665
+ ? parentProvenance.jobId
1666
+ : parentProvenance?.kind === "subagent-request"
1667
+ ? parentProvenance.loopJobId
1668
+ : undefined;
1669
+ const loopRunId = parentProvenance?.kind === "loop-run"
1670
+ ? parentProvenance.runId
1671
+ : parentProvenance?.kind === "subagent-request"
1672
+ ? parentProvenance.loopRunId
1673
+ : undefined;
1606
1674
  const event = {
1607
1675
  type: "message",
1608
1676
  piboSessionId: child.id,
1609
1677
  text: message,
1610
1678
  source: "actor",
1611
1679
  id: randomUUID(),
1680
+ provenance: {
1681
+ kind: "subagent-request",
1682
+ requestId,
1683
+ controllerPiboSessionId: parentPiboSessionId,
1684
+ ...(loopJobId ? { loopJobId } : {}),
1685
+ ...(loopRunId ? { loopRunId } : {}),
1686
+ },
1612
1687
  };
1688
+ this.subagentRequestIdsByEvent.set(subagentRequestEventKey(child.id, event.id), requestId);
1613
1689
  this.emitOutput({
1614
1690
  type: "subagent_session",
1615
1691
  piboSessionId: parentPiboSessionId,
1692
+ requestId,
1616
1693
  toolCallId,
1617
1694
  toolName: "pibo_agents_send_message",
1618
1695
  subagentName: subagent.name,
1619
1696
  childPiboSessionId: child.id,
1620
1697
  threadKey: resolvedThreadKey,
1621
1698
  });
1622
- const untrack = this.trackActiveSubagent(parentPiboSessionId, child.id);
1699
+ const parentAbortController = new AbortController();
1700
+ const requestSignal = signal
1701
+ ? AbortSignal.any([signal, parentAbortController.signal])
1702
+ : parentAbortController.signal;
1703
+ let resolveSettled;
1704
+ const settled = new Promise((resolve) => {
1705
+ resolveSettled = resolve;
1706
+ });
1707
+ const untrack = this.trackActiveSubagent(parentPiboSessionId, {
1708
+ abortController: parentAbortController,
1709
+ settled,
1710
+ });
1711
+ let settlement = { status: "fulfilled" };
1623
1712
  try {
1624
- const reply = await this.emitMessageAndWaitForReply(event, subagent.timeoutMs ?? DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS, signal);
1713
+ const reply = await this.emitMessageAndWaitForReply(event, undefined, requestSignal);
1625
1714
  return {
1715
+ requestId,
1626
1716
  agentId: child.id,
1627
1717
  name: subagent.name,
1628
1718
  profile: child.profile,
1629
1719
  threadKey: resolvedThreadKey,
1630
1720
  eventId: event.id,
1721
+ finalMessage: reply.text,
1631
1722
  reply,
1632
1723
  };
1633
1724
  }
1725
+ catch (error) {
1726
+ const confirmedParentCancellation = parentAbortController.signal.aborted
1727
+ && error instanceof Error
1728
+ && error.name === "AbortError";
1729
+ if (!confirmedParentCancellation)
1730
+ settlement = { status: "rejected", reason: error };
1731
+ throw error;
1732
+ }
1634
1733
  finally {
1734
+ this.subagentRequestIdsByEvent.delete(subagentRequestEventKey(child.id, event.id));
1635
1735
  untrack();
1736
+ resolveSettled?.(settlement);
1636
1737
  }
1637
1738
  },
1638
1739
  listAgents: () => this.listManagedAgents(parentPiboSessionId),
@@ -1680,6 +1781,7 @@ export class PiboSessionRouter {
1680
1781
  if (since !== undefined && until !== undefined && since > until) {
1681
1782
  throw new Error("Agent observation since must not be after until.");
1682
1783
  }
1784
+ const requestIds = input.requestIds ? new Set(input.requestIds) : undefined;
1683
1785
  const agentIds = input.agentIds ? new Set(input.agentIds) : undefined;
1684
1786
  if (agentIds) {
1685
1787
  for (const agentId of agentIds)
@@ -1689,10 +1791,13 @@ export class PiboSessionRouter {
1689
1791
  const threadKeys = input.threadKeys ? new Set(input.threadKeys) : undefined;
1690
1792
  const eventTypes = input.eventTypes ? new Set(input.eventTypes) : undefined;
1691
1793
  const kinds = input.kinds ? new Set(input.kinds) : undefined;
1794
+ const roles = input.roles ? new Set(input.roles) : undefined;
1692
1795
  const textContains = input.textContains?.toLowerCase();
1693
1796
  const matches = this.agentObservations.filter((observation) => {
1694
1797
  if (observation.managingParentId !== parentPiboSessionId)
1695
1798
  return false;
1799
+ if (requestIds && (!observation.requestId || !requestIds.has(observation.requestId)))
1800
+ return false;
1696
1801
  if (agentIds && !agentIds.has(observation.agentId))
1697
1802
  return false;
1698
1803
  if (names && !names.has(observation.name))
@@ -1703,6 +1808,8 @@ export class PiboSessionRouter {
1703
1808
  return false;
1704
1809
  if (kinds && !kinds.has(observation.kind))
1705
1810
  return false;
1811
+ if (roles && (!observation.role || !roles.has(observation.role)))
1812
+ return false;
1706
1813
  if (afterSequence !== undefined && observation.sequence <= afterSequence)
1707
1814
  return false;
1708
1815
  const createdAt = Date.parse(observation.createdAt);
@@ -1744,9 +1851,8 @@ export class PiboSessionRouter {
1744
1851
  const child = this.requireManagedAgent(parentPiboSessionId, agentId);
1745
1852
  const ids = [agentId, ...this.descendantSessionIds(agentId)];
1746
1853
  const idSet = new Set(ids);
1747
- const cancelledRuns = this.runRegistry.listAll({ includeConsumed: true, includeDetached: true })
1748
- .filter((run) => idSet.has(run.controllerPiboSessionId) && !isTerminalRunStatus(run.status))
1749
- .map((run) => run.runId);
1854
+ const cancellableRuns = this.runRegistry.listAll({ includeConsumed: true, includeDetached: true })
1855
+ .filter((run) => idSet.has(run.controllerPiboSessionId) && !isTerminalRunStatus(run.status));
1750
1856
  await Promise.allSettled(ids.flatMap((id) => this.sessions.has(id)
1751
1857
  ? [this.emit({ type: "execution", piboSessionId: id, action: "abort", id: randomUUID() })]
1752
1858
  : []));
@@ -1760,19 +1866,42 @@ export class PiboSessionRouter {
1760
1866
  });
1761
1867
  }
1762
1868
  await this.disposeSessionSubtree(agentId, `killed by parent ${parentPiboSessionId}`, { cancelRuns: true });
1869
+ const cancelledRuns = cancellableRuns.flatMap((run) => {
1870
+ const current = this.runRegistry.status(run.controllerPiboSessionId, run.runId);
1871
+ return current.status === "cancelled" ? [run.runId] : [];
1872
+ });
1763
1873
  return { agentId, killed: ids, cancelledRuns };
1764
1874
  }
1765
- async invokeRunCancellationHandlers(runs) {
1766
- const results = await Promise.allSettled(runs.map(async (run) => {
1767
- const cancel = this.runCancellationHandlers.get(run.runId);
1768
- if (!cancel)
1875
+ async invokeRunCancellationHandler(run) {
1876
+ const cancel = this.runCancellationHandlers.get(run.runId);
1877
+ if (!cancel) {
1878
+ const current = this.runRegistry.status(run.controllerPiboSessionId, run.runId);
1879
+ if (isTerminalRunStatus(current.status) || !this.activeRunExecutions.has(run.runId))
1769
1880
  return;
1881
+ throw new PiboRunCancellationError(`Yielded run "${run.runId}" has active execution but does not expose a cancellation handler.`);
1882
+ }
1883
+ await cancel();
1884
+ if (this.runCancellationHandlers.get(run.runId) === cancel)
1770
1885
  this.runCancellationHandlers.delete(run.runId);
1771
- await cancel();
1886
+ }
1887
+ async invokeRunCancellationHandlers(runs) {
1888
+ const results = await Promise.allSettled(runs.map((run) => this.invokeRunCancellationHandler(run)));
1889
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
1890
+ if (failures.length > 0)
1891
+ throw new AggregateError(failures, "Failed to terminate yielded runs.");
1892
+ }
1893
+ async cancelRunsAfterSettlement(runs, reason) {
1894
+ const results = await Promise.allSettled(runs.map(async (run) => {
1895
+ await this.invokeRunCancellationHandler(run);
1896
+ const current = this.runRegistry.status(run.controllerPiboSessionId, run.runId);
1897
+ return isTerminalRunStatus(current.status)
1898
+ ? current
1899
+ : this.runRegistry.cancel(run.controllerPiboSessionId, run.runId, reason);
1772
1900
  }));
1773
1901
  const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
1774
1902
  if (failures.length > 0)
1775
- throw new AggregateError(failures, "Failed to terminate cancelled yielded runs.");
1903
+ throw new AggregateError(failures, "Failed to terminate yielded runs before cancellation settlement.");
1904
+ return results.flatMap((result) => result.status === "fulfilled" && result.value.status === "cancelled" ? [result.value] : []);
1776
1905
  }
1777
1906
  createRunToolController(parentPiboSessionId) {
1778
1907
  return {
@@ -1796,17 +1925,44 @@ export class PiboSessionRouter {
1796
1925
  timeoutMs,
1797
1926
  serviceWarning,
1798
1927
  resources,
1928
+ origin: yieldedRunOrigin(this.sessions.get(parentPiboSessionId)?.getActiveMessage?.()),
1799
1929
  });
1800
1930
  }
1801
1931
  catch (error) {
1802
1932
  admission.release();
1803
1933
  throw error;
1804
1934
  }
1805
- if (cancel)
1806
- this.runCancellationHandlers.set(run.runId, cancel);
1935
+ this.activeRunExecutions.add(run.runId);
1936
+ const cancellation = { state: "none" };
1937
+ let resolveRunTaskSettled;
1938
+ const runTaskSettled = new Promise((resolve) => { resolveRunTaskSettled = resolve; });
1939
+ if (cancel) {
1940
+ let cancellationAttempt;
1941
+ this.runCancellationHandlers.set(run.runId, () => {
1942
+ cancellationAttempt ??= (async () => {
1943
+ cancellation.state = "pending";
1944
+ let resolveDecision;
1945
+ cancellation.decision = new Promise((resolve) => { resolveDecision = resolve; });
1946
+ try {
1947
+ await waitForRunCancellationSettlement(Promise.resolve().then(cancel));
1948
+ cancellation.state = "confirmed";
1949
+ resolveDecision?.();
1950
+ await waitForRunCancellationSettlement(runTaskSettled);
1951
+ }
1952
+ catch (error) {
1953
+ cancellation.state = "failed";
1954
+ throw error;
1955
+ }
1956
+ finally {
1957
+ resolveDecision?.();
1958
+ }
1959
+ })();
1960
+ return cancellationAttempt;
1961
+ });
1962
+ }
1807
1963
  void (async () => {
1808
1964
  try {
1809
- const result = await execute();
1965
+ const result = await execute(run.runId);
1810
1966
  if (resources)
1811
1967
  this.runRegistry.updateResources(run.runId, resources);
1812
1968
  const completed = this.runRegistry.complete(run.runId, result);
@@ -1814,6 +1970,12 @@ export class PiboSessionRouter {
1814
1970
  this.handleTerminalRunReminder(parentPiboSessionId, completed.runId, reminderGeneration);
1815
1971
  }
1816
1972
  catch (error) {
1973
+ if (error instanceof PiboRunCancelledError) {
1974
+ if (cancellation.state === "pending")
1975
+ await cancellation.decision;
1976
+ if (cancellation.state === "confirmed")
1977
+ return;
1978
+ }
1817
1979
  const message = error instanceof Error ? error.message : String(error);
1818
1980
  if (resources)
1819
1981
  this.runRegistry.updateResources(run.runId, resources);
@@ -1827,7 +1989,9 @@ export class PiboSessionRouter {
1827
1989
  }
1828
1990
  finally {
1829
1991
  this.runCancellationHandlers.delete(run.runId);
1992
+ this.activeRunExecutions.delete(run.runId);
1830
1993
  admission.release();
1994
+ resolveRunTaskSettled?.();
1831
1995
  }
1832
1996
  })();
1833
1997
  return run;
@@ -1842,14 +2006,15 @@ export class PiboSessionRouter {
1842
2006
  return run;
1843
2007
  },
1844
2008
  cancelRun: async (runId) => {
1845
- const cancelled = this.runRegistry.cancel(parentPiboSessionId, runId);
2009
+ const current = this.runRegistry.status(parentPiboSessionId, runId);
1846
2010
  try {
1847
- await this.invokeRunCancellationHandlers([cancelled]);
2011
+ if (!isTerminalRunStatus(current.status))
2012
+ await this.invokeRunCancellationHandlers([current]);
2013
+ return this.runRegistry.cancel(parentPiboSessionId, runId);
1848
2014
  }
1849
2015
  finally {
1850
2016
  this.refreshQueuedRunReminders(parentPiboSessionId);
1851
2017
  }
1852
- return cancelled;
1853
2018
  },
1854
2019
  ackRun: (runId) => {
1855
2020
  const run = this.runRegistry.ack(parentPiboSessionId, runId);
@@ -1944,10 +2109,18 @@ export class PiboSessionRouter {
1944
2109
  const source = piboAgentObservationSourceFromEvent(event);
1945
2110
  const role = piboAgentObservationRole(source);
1946
2111
  const text = piboAgentObservationText(source);
2112
+ const provenance = "provenance" in event ? event.provenance : undefined;
2113
+ const eventId = "eventId" in event && typeof event.eventId === "string" ? event.eventId : undefined;
2114
+ const requestId = provenance?.kind === "subagent-request"
2115
+ ? provenance.requestId
2116
+ : eventId
2117
+ ? this.subagentRequestIdsByEvent.get(subagentRequestEventKey(session.id, eventId))
2118
+ : undefined;
1947
2119
  const observation = {
1948
2120
  managingParentId: session.parentId,
1949
2121
  sequence: this.nextAgentObservationSequence++,
1950
2122
  createdAt: new Date().toISOString(),
2123
+ ...(requestId ? { requestId } : {}),
1951
2124
  agentId: session.id,
1952
2125
  name,
1953
2126
  ...(typeof session.metadata?.threadKey === "string" ? { threadKey: session.metadata.threadKey } : {}),
@@ -2067,8 +2240,8 @@ export class PiboSessionRouter {
2067
2240
  piboSessionId,
2068
2241
  text: formatRunReminderMessage(notification),
2069
2242
  source: "service",
2070
- capabilityScope: "run-reminder",
2071
2243
  id: randomUUID(),
2244
+ provenance: runReminderProvenance(notification),
2072
2245
  });
2073
2246
  }
2074
2247
  catch (error) {