@pasko70/pibo 1.16.5 → 2.0.0

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 (146) hide show
  1. package/dist/agent-runtime/auth.js +28 -0
  2. package/dist/agent-runtime/capabilities.js +221 -0
  3. package/dist/agent-runtime/context-build.js +313 -0
  4. package/dist/agent-runtime/contract.js +45 -0
  5. package/dist/agent-runtime/errors.js +57 -0
  6. package/dist/agent-runtime/events.js +1 -0
  7. package/dist/agent-runtime/history.js +1 -0
  8. package/dist/agent-runtime/profile-validation.js +109 -0
  9. package/dist/agent-runtime/registry.js +608 -0
  10. package/dist/agent-runtime/resource-files.js +87 -0
  11. package/dist/agent-runtime/resource-service.js +635 -0
  12. package/dist/agent-runtime/resources.js +1 -0
  13. package/dist/agent-runtime/routed-session.js +931 -0
  14. package/dist/agent-runtime/testing/contract.js +51 -0
  15. package/dist/agent-runtime/testing/fake-adapter.js +192 -0
  16. package/dist/agent-runtime/types.js +3 -0
  17. package/dist/agent-runtimes/codex-native/adapter.js +905 -0
  18. package/dist/agent-runtimes/codex-native/auth.js +476 -0
  19. package/dist/agent-runtimes/codex-native/client.js +734 -0
  20. package/dist/agent-runtimes/codex-native/config.js +152 -0
  21. package/dist/agent-runtimes/codex-native/history.js +283 -0
  22. package/dist/agent-runtimes/codex-native/mcp-stdio-launcher.js +89 -0
  23. package/dist/agent-runtimes/codex-native/models.js +581 -0
  24. package/dist/agent-runtimes/codex-native/process.js +459 -0
  25. package/dist/agent-runtimes/codex-native/protocol-types.js +1 -0
  26. package/dist/agent-runtimes/codex-native/protocol-version.js +7 -0
  27. package/dist/agent-runtimes/codex-native/redaction.js +31 -0
  28. package/dist/agent-runtimes/codex-native/requests.js +378 -0
  29. package/dist/agent-runtimes/codex-native/resource-delivery.js +445 -0
  30. package/dist/agent-runtimes/codex-native/thread.js +388 -0
  31. package/dist/agent-runtimes/codex-native/turn.js +849 -0
  32. package/dist/agent-runtimes/pi/adapter.js +653 -0
  33. package/dist/agent-runtimes/pi/auth.js +455 -0
  34. package/dist/agent-runtimes/pi/history.js +640 -0
  35. package/dist/agent-runtimes/pi/model-catalog.js +87 -0
  36. package/dist/agent-runtimes/pi/routed-session.js +1464 -0
  37. package/dist/agent-runtimes/pi/runtime.js +534 -0
  38. package/dist/agent-runtimes/pi/tool-compiler.js +69 -0
  39. package/dist/apps/chat/agent-profiles.js +40 -23
  40. package/dist/apps/chat/agent-store.js +111 -64
  41. package/dist/apps/chat/chat-api-routes.js +1 -1
  42. package/dist/apps/chat/chat-request-normalizers.js +47 -0
  43. package/dist/apps/chat/data/chat-data-mappers.js +25 -9
  44. package/dist/apps/chat/data/history-query-service.js +108 -0
  45. package/dist/apps/chat/data/session-query-service.js +24 -3
  46. package/dist/apps/chat/data/timeline-query-service.js +3 -3
  47. package/dist/apps/chat/model-catalog.js +1 -55
  48. package/dist/apps/chat/provider-auth-actions.js +171 -23
  49. package/dist/apps/chat/stream.js +12 -0
  50. package/dist/apps/chat/trace-v2.js +1 -0
  51. package/dist/apps/chat/trace.js +126 -415
  52. package/dist/apps/chat/web-app.js +582 -157
  53. package/dist/apps/chat-ui/assets/{dist-QXwINWYY.js → dist-9v2ZXT6V.js} +1 -1
  54. package/dist/apps/chat-ui/assets/{dist-CvZNkkOi.js → dist-B1LVAytl.js} +1 -1
  55. package/dist/apps/chat-ui/assets/{dist-_SP-MjLq.js → dist-C6LPDFXU.js} +1 -1
  56. package/dist/apps/chat-ui/assets/{dist-Bad4Yz4P.js → dist-DVtXxfHc.js} +1 -1
  57. package/dist/apps/chat-ui/assets/{dist-xy-zomwl.js → dist-DW4Qrfbd.js} +1 -1
  58. package/dist/apps/chat-ui/assets/{dist-DI4i80O2.js → dist-DmbsHdMC.js} +1 -1
  59. package/dist/apps/chat-ui/assets/{dist-D17yAUqp.js → dist-DyvwuSFO.js} +1 -1
  60. package/dist/apps/chat-ui/assets/{dist-Bc3Vbgjh.js → dist-I26VB0QO.js} +1 -1
  61. package/dist/apps/chat-ui/assets/{dist-BzqH7w58.js → dist-RtHbJRPV.js} +1 -1
  62. package/dist/apps/chat-ui/assets/{dist-CMDJ-WZm.js → dist-sZgJkWZb.js} +1 -1
  63. package/dist/apps/chat-ui/assets/index-BPbNvcZe.css +1 -0
  64. package/dist/apps/chat-ui/assets/index-CIOf2uL2.js +237 -0
  65. package/dist/apps/chat-ui/index.html +2 -2
  66. package/dist/apps/chat-vscode-web/assets/index-1ivRyjFs.js +41 -0
  67. package/dist/apps/chat-vscode-web/index.html +1 -1
  68. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  69. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.0.0.vsix +0 -0
  70. package/dist/auth/login-actions.js +5 -251
  71. package/dist/cli-session/localSessionSource.js +8 -1
  72. package/dist/core/context-build.js +124 -20
  73. package/dist/core/default-profile.js +8 -0
  74. package/dist/core/profiles.js +30 -3
  75. package/dist/core/routed-session.js +5 -1446
  76. package/dist/core/runtime.js +5 -561
  77. package/dist/core/session-router.js +637 -83
  78. package/dist/data/cli.js +20 -3
  79. package/dist/data/schema.js +104 -1
  80. package/dist/data/session-store.js +24 -1
  81. package/dist/debug/events.js +25 -10
  82. package/dist/debug/failures.js +23 -3
  83. package/dist/debug/index.js +24 -6
  84. package/dist/debug/messages.js +87 -7
  85. package/dist/debug/persisted-payloads.js +31 -0
  86. package/dist/debug/pty.js +7 -6
  87. package/dist/debug/runtime-binding.js +29 -0
  88. package/dist/debug/session.js +137 -0
  89. package/dist/debug/summary.js +12 -0
  90. package/dist/debug/telemetry.js +28 -0
  91. package/dist/debug/tools.js +12 -2
  92. package/dist/debug/trace.js +154 -76
  93. package/dist/gateway/server.js +87 -2
  94. package/dist/gateway/tool.js +5 -5
  95. package/dist/index.js +21 -2
  96. package/dist/loops/tools.js +13 -12
  97. package/dist/mcp/agent-context.js +13 -4
  98. package/dist/mcp/client.js +10 -6
  99. package/dist/mcp/config.js +5 -2
  100. package/dist/mcp/runtime-session.js +166 -0
  101. package/dist/plugins/builtin.js +191 -53
  102. package/dist/plugins/codex-native.js +29 -0
  103. package/dist/plugins/registry.js +66 -2
  104. package/dist/runs/tools.js +26 -25
  105. package/dist/session-ui/terminalRows.js +2 -2
  106. package/dist/sessions/pibo-data-store.js +125 -16
  107. package/dist/sessions/runtime-binding.js +107 -0
  108. package/dist/sessions/sqlite-store.js +239 -18
  109. package/dist/sessions/store.js +86 -7
  110. package/dist/shared/trace-engine.js +18 -9
  111. package/dist/shared/trace-event-projection.js +29 -11
  112. package/dist/shared/{trace-transcript.js → trace-history.js} +193 -195
  113. package/dist/shared/trace-order.js +9 -4
  114. package/dist/shared/trace-page-merge.js +1 -1
  115. package/dist/signals/projector.js +20 -2
  116. package/dist/signals/registry.js +11 -0
  117. package/dist/subagents/tool.js +8 -6
  118. package/dist/tools/codex-browser.js +20 -19
  119. package/dist/tools/codex-compat.js +10 -9
  120. package/dist/tools/codex-image-generation.js +8 -6
  121. package/dist/tools/contract.js +45 -0
  122. package/dist/tools/credential-registry.js +182 -0
  123. package/dist/tools/mcp-bridge.js +511 -0
  124. package/dist/tools/payload-writer.js +17 -0
  125. package/dist/tools/runtime/tool.js +10 -9
  126. package/dist/tools/schema.js +9 -0
  127. package/dist/tools/session-service.js +193 -0
  128. package/dist/tools/session-tool-set.js +85 -0
  129. package/dist/web-annotations/tools.js +17 -16
  130. package/docs/README.md +11 -0
  131. package/package.json +3 -2
  132. package/skills/builtin/pibo-agent-runtime-adapter/SKILL.md +183 -0
  133. package/skills/builtin/pibo-agent-runtime-adapter/evals/README.md +10 -0
  134. package/skills/builtin/pibo-agent-runtime-adapter/evals/evals.json +45 -0
  135. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/orion-full-harness.md +60 -0
  136. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/relay-partial-harness.md +52 -0
  137. package/skills/builtin/pibo-agent-runtime-adapter/references/capabilities-and-designer.md +194 -0
  138. package/skills/builtin/pibo-agent-runtime-adapter/references/history-debug-and-security.md +213 -0
  139. package/skills/builtin/pibo-agent-runtime-adapter/references/interfaces-and-registration.md +214 -0
  140. package/skills/builtin/pibo-agent-runtime-adapter/references/lifecycle-bindings-and-events.md +193 -0
  141. package/skills/builtin/pibo-agent-runtime-adapter/references/portable-delivery-and-native-behavior.md +226 -0
  142. package/skills/builtin/pibo-agent-runtime-adapter/references/testing-migration-and-validation.md +256 -0
  143. package/dist/apps/chat-ui/assets/index-C45B0rw7.js +0 -237
  144. package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +0 -1
  145. package/dist/apps/chat-vscode-web/assets/index-D-tSsXLG.js +0 -41
  146. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.16.5.vsix +0 -0
@@ -1,8 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { InitialSessionContext, } from "./profiles.js";
3
3
  import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault, selectDefaultPiboProfileName } from "../plugins/builtin.js";
4
- import { createPiboRuntime } from "./runtime.js";
5
- import { RoutedSession } from "./routed-session.js";
4
+ import { RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-session.js";
6
5
  import { runtimeSessionErrorDetails } from "./session-errors.js";
7
6
  import { createSubagentToolName } from "../subagents/tool.js";
8
7
  import { PiboRunRegistry } from "../runs/registry.js";
@@ -11,6 +10,9 @@ import { PiboRunResourceLimitError } from "../runs/resource-isolation.js";
11
10
  import { createPiboSignalRegistry } from "../signals/registry.js";
12
11
  import { createDefaultPiboReliabilityStore } from "../reliability/store.js";
13
12
  import { InMemoryPiboSessionStore, } from "../sessions/store.js";
13
+ import { createLegacyPiRuntimeSessionBinding, RuntimeSessionBindingConflictError, } from "../sessions/runtime-binding.js";
14
+ import { AgentRuntimeBindingMissingError, AgentRuntimeUnavailableError } from "../agent-runtime/errors.js";
15
+ import { validateAgentRuntimeProfileCapabilities } from "../agent-runtime/profile-validation.js";
14
16
  import { getDefaultPiboWorkspace } from "./workspace.js";
15
17
  import { loadPiboModelDefaults, selectRequestedFastMode } from "./model-defaults.js";
16
18
  import { loadPiboUserSettings } from "./user-settings.js";
@@ -22,8 +24,12 @@ import { withWorkflowSessionKind } from "../sessions/workflow-session-kind.js";
22
24
  import { PiboRuntimeTelemetryRecorder } from "./runtime-telemetry.js";
23
25
  import { createPiboProviderTelemetryExtension } from "./provider-telemetry.js";
24
26
  import { AsyncTelemetryWriter } from "../data/telemetry-writer.js";
27
+ import { createPiboToolPayloadWriter } from "../tools/payload-writer.js";
28
+ import { PiboPortableToolService, } from "../tools/session-service.js";
29
+ import { PiboRuntimeResourceService, } from "../agent-runtime/resource-service.js";
25
30
  const DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS = 10 * 60 * 1000;
26
31
  const DEFAULT_SUBAGENT_MAX_DEPTH = 3;
32
+ const MAX_SUBAGENT_THREAD_KEY_BYTES = 512;
27
33
  const DEFAULT_ROUTED_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
28
34
  const DEFAULT_ROUTED_SESSION_DISPOSE_TIMEOUT_MS = 30 * 1000;
29
35
  export const LOOP_RUNTIME_RETRY_DEFAULTS = {
@@ -47,11 +53,27 @@ export function resolvePiboSessionInitialFastMode(session) {
47
53
  function hasReachedSubagentMaxDepth(subagent, depth) {
48
54
  return depth >= (subagent.maxDepth ?? DEFAULT_SUBAGENT_MAX_DEPTH);
49
55
  }
50
- function profileForSession(baseProfile, piSessionId, parentPiSessionId, subagentDepth) {
56
+ function resolveSubagentThreadKey(threadKey) {
57
+ const normalized = threadKey?.trim();
58
+ if (!normalized)
59
+ return randomUUID();
60
+ if (Buffer.byteLength(normalized, "utf8") > MAX_SUBAGENT_THREAD_KEY_BYTES) {
61
+ throw new Error(`Subagent thread key exceeds ${MAX_SUBAGENT_THREAD_KEY_BYTES} bytes.`);
62
+ }
63
+ return normalized;
64
+ }
65
+ function subagentAbortError() {
66
+ const error = new Error("Subagent request was aborted.");
67
+ error.name = "AbortError";
68
+ return error;
69
+ }
70
+ function profileForSession(baseProfile, runtimeInstanceId, nativeSessionId, parentNativeSessionId, subagentDepth) {
51
71
  const options = {
52
72
  profileName: baseProfile.profileName,
53
- sessionId: piSessionId,
54
- parentSessionId: parentPiSessionId,
73
+ runtimeInstanceId,
74
+ runtimeOptions: baseProfile.runtimeOptions,
75
+ sessionId: nativeSessionId,
76
+ parentSessionId: parentNativeSessionId,
55
77
  model: baseProfile.model,
56
78
  mainModel: baseProfile.mainModel,
57
79
  subagentModel: baseProfile.subagentModel,
@@ -131,13 +153,32 @@ function isTerminalRunStatus(status) {
131
153
  function asJsonObject(value) {
132
154
  return value ?? {};
133
155
  }
134
- function shouldResetSessionAfterAction(action) {
135
- return action === "login.complete" || action === "login.apikey" || action === "logout";
156
+ function shouldResetSessionAfterAction(action, output) {
157
+ if (action === "login.apikey" || action === "logout")
158
+ return true;
159
+ if (action !== "login.complete")
160
+ return false;
161
+ const result = output?.type === "execution_result" && output.result && typeof output.result === "object" && !Array.isArray(output.result)
162
+ ? output.result
163
+ : undefined;
164
+ return result?.state !== "pending";
136
165
  }
137
166
  function piboRoomIdFromMetadata(metadata) {
138
167
  const value = metadata?.chatRoomId;
139
168
  return typeof value === "string" && value.length > 0 ? value : undefined;
140
169
  }
170
+ function runtimeBindingsEqual(left, right) {
171
+ return left.piboSessionId === right.piboSessionId
172
+ && left.runtimeInstanceId === right.runtimeInstanceId
173
+ && left.adapterId === right.adapterId
174
+ && left.nativeSessionId === right.nativeSessionId
175
+ && left.state === right.state
176
+ && left.protocol === right.protocol
177
+ && left.protocolVersion === right.protocolVersion
178
+ && left.adapterVersion === right.adapterVersion
179
+ && JSON.stringify(left.locator ?? null) === JSON.stringify(right.locator ?? null)
180
+ && JSON.stringify(left.metadata ?? {}) === JSON.stringify(right.metadata ?? {});
181
+ }
141
182
  class PiboSessionDisposalTimeoutError extends Error {
142
183
  piboSessionId;
143
184
  timeoutMs;
@@ -151,6 +192,9 @@ class PiboSessionDisposalTimeoutError extends Error {
151
192
  function telemetryStoreFromSessionStore(store) {
152
193
  return store.getTelemetryStore?.();
153
194
  }
195
+ function payloadStoreFromSessionStore(store) {
196
+ return store.getPayloadStore?.();
197
+ }
154
198
  function providerEventTelemetryModeFromEnv(env = process.env) {
155
199
  const value = env.PIBO_TELEMETRY_PROVIDER_EVENTS?.trim().toLowerCase();
156
200
  return value === "1" || value === "true" || value === "detailed" ? "detailed" : "aggregate";
@@ -164,6 +208,12 @@ export class PiboSessionRouter {
164
208
  gatewayWorkAdmission = new GatewayWorkAdmissionController();
165
209
  signalRegistry;
166
210
  runtimeRegistry;
211
+ portableToolService;
212
+ portableToolSessions = new Map();
213
+ runtimeResourceService;
214
+ runtimeResourceSessions = new Map();
215
+ runtimeAuthFingerprints = new Map();
216
+ activeSubagentChildren = new Map();
167
217
  scheduledRunReminders = new Map();
168
218
  runReminderGenerations = new Map();
169
219
  quiescingSessions = new Set();
@@ -173,6 +223,7 @@ export class PiboSessionRouter {
173
223
  routedSessionDisposeTimeoutMs;
174
224
  baseProfile;
175
225
  pluginRegistry;
226
+ compatibilityRuntimeRegistry;
176
227
  sessionStore;
177
228
  reliabilityStore;
178
229
  telemetryStore;
@@ -183,6 +234,9 @@ export class PiboSessionRouter {
183
234
  constructor(options = {}) {
184
235
  this.options = options;
185
236
  this.pluginRegistry = options.pluginRegistry ?? createDefaultPiboPluginRegistry();
237
+ // Historical custom registries supplied only actions/profiles while runtime creation was implicit.
238
+ // Preserve that composition contract during the adapter migration without branching on adapter ids.
239
+ this.compatibilityRuntimeRegistry = options.pluginRegistry ? createDefaultPiboPluginRegistry() : undefined;
186
240
  this.sessionStore = options.sessionStore ?? new InMemoryPiboSessionStore();
187
241
  this.telemetryStore = options.telemetryStore ?? telemetryStoreFromSessionStore(this.sessionStore);
188
242
  this.telemetryWriter = this.telemetryStore ? new AsyncTelemetryWriter(this.telemetryStore) : undefined;
@@ -207,6 +261,11 @@ export class PiboSessionRouter {
207
261
  this.reliabilityStore = options.reliabilityStore ?? (options.persistSession === false ? undefined : createDefaultPiboReliabilityStore());
208
262
  this.signalRegistry = options.signalRegistry ?? createPiboSignalRegistry();
209
263
  this.runtimeRegistry = new RuntimeSessionRegistry({ cwd: options.cwd ?? getDefaultPiboWorkspace() });
264
+ const payloadStore = payloadStoreFromSessionStore(this.sessionStore);
265
+ this.portableToolService = new PiboPortableToolService({
266
+ ...(payloadStore ? { payloadWriter: createPiboToolPayloadWriter(payloadStore) } : {}),
267
+ });
268
+ this.runtimeResourceService = options.runtimeResourceService ?? new PiboRuntimeResourceService();
210
269
  this.runRegistry = new PiboRunRegistry({ store: this.reliabilityStore });
211
270
  this.runRegistry.subscribe((event) => this.projectRunRegistryEvent(event));
212
271
  const recoveredRuntimeState = options.recoverInterruptedRuntimeState
@@ -299,13 +358,23 @@ export class PiboSessionRouter {
299
358
  teardownCompleted = true;
300
359
  return output;
301
360
  }
361
+ const childAbort = event.action === "abort"
362
+ ? this.abortActiveSubagentSessions(event.piboSessionId)
363
+ : undefined;
302
364
  const output = await session.executeAction(event);
365
+ await childAbort;
303
366
  if (event.action === "kill" || event.action === "kill_all") {
304
367
  await this.disposeSessionSubtree(event.piboSessionId, `${event.action} action`, { cancelRuns: event.action === "kill_all" });
305
368
  teardownCompleted = true;
306
369
  }
307
- else if (shouldResetSessionAfterAction(event.action)) {
308
- await this.resetCachedSession(event.piboSessionId, "provider auth changed");
370
+ else if (shouldResetSessionAfterAction(event.action, output)) {
371
+ const runtimeInstanceId = this.getSessionRuntimeBinding(event.piboSessionId)?.runtimeInstanceId;
372
+ if (runtimeInstanceId) {
373
+ await this.resetCachedRuntimeAuthSessions(runtimeInstanceId, "runtime provider auth changed");
374
+ }
375
+ else {
376
+ await this.resetCachedSession(event.piboSessionId, "runtime provider auth changed");
377
+ }
309
378
  }
310
379
  return output;
311
380
  }
@@ -333,6 +402,9 @@ export class PiboSessionRouter {
333
402
  this.scheduleIdleSessionEvictionIfIdle(event.piboSessionId);
334
403
  }
335
404
  }
405
+ async disposeSession(piboSessionId, reason = "session deleted") {
406
+ await this.disposeSessionSubtree(piboSessionId, reason, { cancelRuns: true });
407
+ }
336
408
  async killSession(piboSessionId, options) {
337
409
  const rootSession = this.sessions.get(piboSessionId);
338
410
  if (!rootSession)
@@ -388,6 +460,15 @@ export class PiboSessionRouter {
388
460
  finally {
389
461
  if (timeout)
390
462
  clearTimeout(timeout);
463
+ const portableTools = this.portableToolSessions.get(piboSessionId);
464
+ portableTools?.dispose();
465
+ if (this.portableToolSessions.get(piboSessionId) === portableTools)
466
+ this.portableToolSessions.delete(piboSessionId);
467
+ const resources = this.runtimeResourceSessions.get(piboSessionId);
468
+ if (resources)
469
+ await resources.dispose();
470
+ if (this.runtimeResourceSessions.get(piboSessionId) === resources)
471
+ this.runtimeResourceSessions.delete(piboSessionId);
391
472
  }
392
473
  }
393
474
  async disposeSessionSubtree(piboSessionId, reason, options) {
@@ -480,13 +561,105 @@ export class PiboSessionRouter {
480
561
  getPiboSessionIds() {
481
562
  return [...this.sessions.keys()];
482
563
  }
564
+ async getAgentRuntimeAuthStatus(runtimeInstanceId) {
565
+ const registry = this.resolveAgentRuntimeRegistry(runtimeInstanceId);
566
+ const statuses = await registry.getAgentRuntimeAuthStatus(runtimeInstanceId);
567
+ const fingerprint = statuses
568
+ .map((status) => `${status.id}:${status.configured}:${status.details?.accountType ?? ""}:${status.details?.planType ?? ""}`)
569
+ .sort()
570
+ .join("|");
571
+ const previous = this.runtimeAuthFingerprints.get(runtimeInstanceId);
572
+ this.runtimeAuthFingerprints.set(runtimeInstanceId, fingerprint);
573
+ if (previous !== undefined && previous !== fingerprint) {
574
+ await this.resetCachedRuntimeAuthSessions(runtimeInstanceId, "runtime provider auth changed");
575
+ }
576
+ return statuses;
577
+ }
578
+ async startAgentRuntimeAuth(runtimeInstanceId, input) {
579
+ const registry = this.resolveAgentRuntimeRegistry(runtimeInstanceId);
580
+ const result = await registry.startAgentRuntimeAuth(runtimeInstanceId, input);
581
+ if (result.state !== "pending") {
582
+ await this.resetCachedRuntimeAuthSessions(runtimeInstanceId, "runtime provider auth changed");
583
+ }
584
+ return result;
585
+ }
586
+ async completeAgentRuntimeAuth(runtimeInstanceId, input) {
587
+ const registry = this.resolveAgentRuntimeRegistry(runtimeInstanceId);
588
+ const result = await registry.completeAgentRuntimeAuth(runtimeInstanceId, input);
589
+ if (result.state !== "pending") {
590
+ await this.resetCachedRuntimeAuthSessions(runtimeInstanceId, "runtime provider auth changed");
591
+ }
592
+ return result;
593
+ }
594
+ async cancelAgentRuntimeAuth(runtimeInstanceId, input) {
595
+ return await this.resolveAgentRuntimeRegistry(runtimeInstanceId)
596
+ .cancelAgentRuntimeAuth(runtimeInstanceId, input);
597
+ }
598
+ async logoutAgentRuntimeAuth(runtimeInstanceId, input) {
599
+ const result = await this.resolveAgentRuntimeRegistry(runtimeInstanceId)
600
+ .logoutAgentRuntimeAuth(runtimeInstanceId, input);
601
+ await this.resetCachedRuntimeAuthSessions(runtimeInstanceId, "runtime provider auth changed");
602
+ return result;
603
+ }
604
+ getSessionRuntimeBinding(piboSessionId) {
605
+ const session = this.sessionStore.get(piboSessionId);
606
+ return session ? structuredClone(this.resolveSessionRuntimeBinding(session)) : undefined;
607
+ }
608
+ async rebindSessionRuntime(piboSessionId, input) {
609
+ const session = this.sessionStore.get(piboSessionId);
610
+ if (!session)
611
+ throw new Error(`Unknown Pibo session "${piboSessionId}".`);
612
+ const current = this.resolveSessionRuntimeBinding(session);
613
+ const currentRevision = current.revision ?? 1;
614
+ if (currentRevision !== input.expectedRevision) {
615
+ throw new RuntimeSessionBindingConflictError(piboSessionId, input.expectedRevision, currentRevision);
616
+ }
617
+ const liveStatus = this.sessions.get(piboSessionId)?.getStatus();
618
+ if (liveStatus && (liveStatus.processing || liveStatus.streaming || liveStatus.queuedMessages > 0)) {
619
+ throw new Error("A runtime binding can only be repaired or rebound while the session is idle.");
620
+ }
621
+ if (this.sessions.has(piboSessionId) || this.pendingSessions.has(piboSessionId)) {
622
+ await this.resetCachedSession(piboSessionId, "runtime binding rebind");
623
+ }
624
+ const registry = this.resolveAgentRuntimeRegistry(input.runtimeInstanceId);
625
+ const adapter = registry.requireAgentRuntimeAdapter(input.runtimeInstanceId);
626
+ const requestedState = input.state ?? (input.nativeSessionId ? "bound" : "unbound");
627
+ let next = {
628
+ piboSessionId,
629
+ runtimeInstanceId: input.runtimeInstanceId,
630
+ adapterId: adapter.descriptor.id,
631
+ nativeSessionId: input.nativeSessionId,
632
+ state: requestedState,
633
+ protocol: adapter.descriptor.protocol?.name,
634
+ protocolVersion: current.runtimeInstanceId === input.runtimeInstanceId ? current.protocolVersion : undefined,
635
+ locator: input.locator,
636
+ metadata: {},
637
+ };
638
+ if (next.state === "bound" && adapter.resolveBinding) {
639
+ next = await adapter.resolveBinding({
640
+ binding: next,
641
+ workspace: session.workspace ?? this.options.cwd ?? getDefaultPiboWorkspace(),
642
+ });
643
+ }
644
+ const mode = (current.state === "missing" || current.state === "error")
645
+ && current.runtimeInstanceId === next.runtimeInstanceId
646
+ && current.adapterId === next.adapterId
647
+ ? "repair"
648
+ : "rebind";
649
+ const persisted = this.persistSessionRuntimeBinding(session, next, {
650
+ expectedRevision: input.expectedRevision,
651
+ mode,
652
+ });
653
+ return structuredClone(persisted);
654
+ }
483
655
  getSessionRuntimeStatus(piboSessionId) {
484
- return this.sessions.get(piboSessionId)?.getStatus();
656
+ const status = this.sessions.get(piboSessionId)?.getStatus();
657
+ return status ? this.withPersistedRuntimeBinding(status) : undefined;
485
658
  }
486
659
  async getSessionStatusSnapshot(piboSessionId) {
487
660
  const session = await this.getOrCreateSession(piboSessionId);
488
661
  try {
489
- return await session.getStatusSnapshot();
662
+ return this.withPersistedRuntimeBinding(await session.getStatusSnapshot());
490
663
  }
491
664
  finally {
492
665
  this.scheduleIdleSessionEvictionIfIdle(piboSessionId);
@@ -517,7 +690,7 @@ export class PiboSessionRouter {
517
690
  });
518
691
  }
519
692
  listSessionRuntimeStatuses() {
520
- return [...this.sessions.values()].map((session) => session.getStatus());
693
+ return [...this.sessions.values()].map((session) => this.withPersistedRuntimeBinding(session.getStatus()));
521
694
  }
522
695
  listRuns(options = {}) {
523
696
  return this.runRegistry.listAll(options);
@@ -543,12 +716,42 @@ export class PiboSessionRouter {
543
716
  subscribeSignalStatuses(listener) {
544
717
  return this.signalRegistry.subscribeAll(listener);
545
718
  }
546
- async emitMessageAndWaitForReply(event, timeoutMs = 120000) {
719
+ async emitMessageAndWaitForReply(event, timeoutMs = 120000, signal) {
547
720
  const eventWithId = { ...event, id: event.id ?? randomUUID() };
548
721
  return await new Promise((resolve, reject) => {
549
722
  let settled = false;
723
+ let messageDispatched = false;
550
724
  let lastAssistantMessage;
551
725
  let timeout;
726
+ const abortChild = () => {
727
+ if (!messageDispatched)
728
+ return;
729
+ void this.emit({
730
+ type: "execution",
731
+ piboSessionId: eventWithId.piboSessionId,
732
+ action: "abort",
733
+ id: randomUUID(),
734
+ }).catch(() => { });
735
+ };
736
+ const finish = (result) => {
737
+ if (settled)
738
+ return false;
739
+ settled = true;
740
+ if (timeout)
741
+ clearTimeout(timeout);
742
+ signal?.removeEventListener("abort", onAbort);
743
+ unsubscribe();
744
+ if (result instanceof Error)
745
+ reject(result);
746
+ else
747
+ resolve(result);
748
+ return true;
749
+ };
750
+ const onAbort = () => {
751
+ if (!finish(subagentAbortError()))
752
+ return;
753
+ abortChild();
754
+ };
552
755
  const unsubscribe = this.subscribe((output) => {
553
756
  if (output.piboSessionId !== eventWithId.piboSessionId ||
554
757
  !("eventId" in output) ||
@@ -565,34 +768,18 @@ export class PiboSessionRouter {
565
768
  finish(new Error(output.error));
566
769
  }
567
770
  });
568
- const finish = (result) => {
569
- if (settled)
570
- return;
571
- settled = true;
572
- if (timeout)
573
- clearTimeout(timeout);
574
- unsubscribe();
575
- if (result instanceof Error) {
576
- reject(result);
577
- }
578
- else {
579
- resolve(result);
580
- }
581
- };
771
+ if (signal?.aborted) {
772
+ onAbort();
773
+ return;
774
+ }
775
+ signal?.addEventListener("abort", onAbort, { once: true });
582
776
  timeout = setTimeout(() => {
583
- if (settled)
584
- return;
585
- settled = true;
586
- unsubscribe();
587
777
  const timeoutError = new Error(`Timed out waiting for assistant reply from Pibo session "${eventWithId.piboSessionId}"`);
588
- reject(timeoutError);
589
- void this.emit({
590
- type: "execution",
591
- piboSessionId: eventWithId.piboSessionId,
592
- action: "abort",
593
- id: randomUUID(),
594
- }).catch(() => { });
778
+ if (!finish(timeoutError))
779
+ return;
780
+ abortChild();
595
781
  }, timeoutMs);
782
+ messageDispatched = true;
596
783
  this.emit(eventWithId).catch(finish);
597
784
  });
598
785
  }
@@ -629,7 +816,18 @@ export class PiboSessionRouter {
629
816
  throw new AggregateError(failures, "Failed to dispose all Pibo sessions");
630
817
  }
631
818
  finally {
819
+ const authDisposals = await Promise.allSettled([
820
+ this.pluginRegistry.disposeAgentRuntimeAuth(),
821
+ ...(this.compatibilityRuntimeRegistry ? [this.compatibilityRuntimeRegistry.disposeAgentRuntimeAuth()] : []),
822
+ ]);
823
+ await this.portableToolService.dispose();
824
+ await this.runtimeResourceService.dispose();
825
+ this.runtimeResourceSessions.clear();
826
+ this.activeSubagentChildren.clear();
632
827
  await this.telemetryWriter?.dispose();
828
+ const authFailures = authDisposals.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
829
+ if (authFailures.length > 0)
830
+ throw new AggregateError(authFailures, "Failed to dispose runtime authentication controllers.");
633
831
  }
634
832
  }
635
833
  clearIdleSessionTimer(piboSessionId) {
@@ -707,55 +905,317 @@ export class PiboSessionRouter {
707
905
  let session;
708
906
  this.signalRegistry.project({ type: "session_created", session: piboSession });
709
907
  const profile = createPiboProfileFromRegistryOrDefault(this.pluginRegistry, piboSession.profile);
710
- const parentPiSessionId = piboSession.parentId
711
- ? this.resolvePiboSession(piboSession.parentId).piSessionId
908
+ let binding = this.resolveSessionRuntimeBinding(piboSession);
909
+ const parent = piboSession.parentId ? this.resolvePiboSession(piboSession.parentId) : undefined;
910
+ const parentBinding = parent ? this.resolveSessionRuntimeBinding(parent) : undefined;
911
+ const parentModelScopeId = parent ? parentBinding?.nativeSessionId ?? parent.id : undefined;
912
+ const runtimeParentNativeSessionId = parentBinding
913
+ && parentBinding.runtimeInstanceId === binding.runtimeInstanceId
914
+ && parentBinding.adapterId === binding.adapterId
915
+ ? parentBinding.nativeSessionId
712
916
  : undefined;
713
917
  const modelDefaults = this.resolveModelDefaults();
714
- const activeModel = this.ensureSessionActiveModel(piboSession, profile, parentPiSessionId, modelDefaults);
918
+ const activeModel = this.ensureSessionActiveModel(piboSession, profile, parentModelScopeId, modelDefaults);
715
919
  const initialThinkingLevel = resolvePiboSessionInitialThinkingLevel(piboSession);
716
- const sessionProfile = profileForSession(profile, piboSession.piSessionId, parentPiSessionId, this.getSubagentDepth(piboSession.id));
920
+ const sessionProfile = profileForSession(profile, binding.runtimeInstanceId, binding.nativeSessionId, runtimeParentNativeSessionId, this.getSubagentDepth(piboSession.id));
717
921
  const userSettings = loadPiboUserSettings();
718
922
  const telemetryExtension = this.telemetryStore
719
923
  ? createPiboProviderTelemetryExtension({ store: this.telemetryStore, writer: this.telemetryWriter, session: piboSession, model: activeModel })
720
924
  : undefined;
721
- const runtime = await createPiboRuntime({
722
- cwd: piboSession.workspace ?? this.options.cwd,
723
- persistSession: this.options.persistSession,
724
- thinkingLevel: initialThinkingLevel ?? this.options.thinkingLevel,
725
- retryDefaults: resolvePiboSessionRetryDefaults(piboSession.kind, this.options.retryDefaults),
925
+ const runtimeRegistry = this.resolveAgentRuntimeRegistry(binding.runtimeInstanceId);
926
+ const runtimeAdapter = runtimeRegistry.requireAgentRuntimeAdapter(binding.runtimeInstanceId);
927
+ if (runtimeAdapter.descriptor.id !== binding.adapterId) {
928
+ throw new AgentRuntimeUnavailableError(binding.runtimeInstanceId, `Runtime binding for Pibo session "${piboSession.id}" expects adapter "${binding.adapterId}", but instance "${binding.runtimeInstanceId}" uses "${runtimeAdapter.descriptor.id}".`);
929
+ }
930
+ const workspace = piboSession.workspace ?? this.options.cwd ?? getDefaultPiboWorkspace();
931
+ if (binding.state === "bound" && runtimeAdapter.resolveBinding) {
932
+ const resolved = await runtimeAdapter.resolveBinding({ binding, workspace });
933
+ if (!runtimeBindingsEqual(binding, resolved)) {
934
+ binding = this.persistSessionRuntimeBinding(piboSession, resolved, {
935
+ expectedRevision: binding.revision,
936
+ });
937
+ }
938
+ }
939
+ this.assertOpenableRuntimeBinding(binding);
940
+ const profileDiagnostics = [
941
+ ...validateAgentRuntimeProfileCapabilities(sessionProfile, runtimeAdapter.descriptor.capabilities),
942
+ ...runtimeAdapter.validateProfile({
943
+ profile: sessionProfile,
944
+ workspace,
945
+ }),
946
+ ];
947
+ const invalidProfile = profileDiagnostics.find((diagnostic) => diagnostic.severity === "error");
948
+ if (invalidProfile) {
949
+ throw new Error(`Runtime profile validation failed: ${invalidProfile.message}`);
950
+ }
951
+ const initialFastMode = resolvePiboSessionInitialFastMode(piboSession) ?? selectRequestedFastMode(sessionProfile, modelDefaults) ?? false;
952
+ const subagentRunner = this.createSubagentRunner(piboSession.id);
953
+ const runToolController = this.createRunToolController(piboSession.id);
954
+ const codeRuntimeToolController = this.runtimeRegistry.createController(piboSession.id);
955
+ const sessionGeneration = randomUUID();
956
+ const previousResources = this.runtimeResourceSessions.get(piboSession.id);
957
+ if (previousResources)
958
+ await previousResources.dispose();
959
+ this.portableToolSessions.get(piboSession.id)?.dispose();
960
+ const portableTools = this.portableToolService.createSession({
961
+ piboSessionId: piboSession.id,
962
+ piboRoomId: piboRoomIdFromMetadata(piboSession.metadata),
963
+ runtimeInstanceId: binding.runtimeInstanceId,
964
+ adapterId: binding.adapterId,
965
+ sessionGeneration,
726
966
  profile: sessionProfile,
727
- extensionFactories: [
728
- ...(telemetryExtension ? [telemetryExtension] : []),
729
- ...(this.options.extensionFactories ?? []),
730
- ],
731
- subagentRunner: this.createSubagentRunner(piboSession.id),
732
- runToolController: this.createRunToolController(piboSession.id),
733
- runtimeToolController: this.runtimeRegistry.createController(piboSession.id),
734
- modelDefaults,
735
- activeModel,
736
- sessionContext: {
967
+ cwd: workspace,
968
+ getActiveMessage: () => session?.getActiveMessage(),
969
+ subagentRunner,
970
+ runToolController,
971
+ runtimeToolController: codeRuntimeToolController,
972
+ });
973
+ this.portableToolSessions.set(piboSession.id, portableTools);
974
+ let resources;
975
+ try {
976
+ resources = await this.runtimeResourceService.createSession({
737
977
  piboSessionId: piboSession.id,
738
978
  piboRoomId: piboRoomIdFromMetadata(piboSession.metadata),
979
+ runtimeInstanceId: binding.runtimeInstanceId,
980
+ adapterId: binding.adapterId,
981
+ sessionGeneration,
982
+ profile: sessionProfile,
983
+ cwd: workspace,
739
984
  timezone: userSettings.timezone,
740
- getActiveMessage: () => session?.getActiveMessage(),
985
+ capabilities: runtimeAdapter.descriptor.capabilities,
986
+ });
987
+ this.runtimeResourceSessions.set(piboSession.id, resources);
988
+ }
989
+ catch (error) {
990
+ portableTools.dispose();
991
+ if (this.portableToolSessions.get(piboSession.id) === portableTools)
992
+ this.portableToolSessions.delete(piboSession.id);
993
+ throw error;
994
+ }
995
+ let runtimeSession;
996
+ try {
997
+ runtimeSession = await runtimeRegistry.openAgentRuntimeSession(binding.runtimeInstanceId, {
998
+ piboSession: { ...piboSession, runtimeBinding: binding },
999
+ profile: sessionProfile,
1000
+ binding,
1001
+ workspace,
1002
+ activeModel,
1003
+ productContext: {
1004
+ piboSessionId: piboSession.id,
1005
+ piboRoomId: piboRoomIdFromMetadata(piboSession.metadata),
1006
+ timezone: userSettings.timezone,
1007
+ getActiveMessage: () => session?.getActiveMessage(),
1008
+ },
1009
+ services: {
1010
+ subagentRunner,
1011
+ runToolController,
1012
+ codeRuntimeToolController,
1013
+ portableTools,
1014
+ resources,
1015
+ compatibility: {
1016
+ persistSession: this.options.persistSession,
1017
+ thinkingLevel: initialThinkingLevel ?? this.options.thinkingLevel,
1018
+ retryDefaults: resolvePiboSessionRetryDefaults(piboSession.kind, this.options.retryDefaults),
1019
+ extensionFactories: [
1020
+ ...(telemetryExtension ? [telemetryExtension] : []),
1021
+ ...(this.options.extensionFactories ?? []),
1022
+ ],
1023
+ modelDefaults,
1024
+ initialFastMode,
1025
+ },
1026
+ },
1027
+ });
1028
+ }
1029
+ catch (error) {
1030
+ portableTools.dispose();
1031
+ if (this.portableToolSessions.get(piboSession.id) === portableTools)
1032
+ this.portableToolSessions.delete(piboSession.id);
1033
+ await resources.dispose();
1034
+ if (this.runtimeResourceSessions.get(piboSession.id) === resources)
1035
+ this.runtimeResourceSessions.delete(piboSession.id);
1036
+ if (error instanceof AgentRuntimeBindingMissingError && binding.state === "bound") {
1037
+ this.persistSessionRuntimeBinding(piboSession, {
1038
+ ...binding,
1039
+ state: "missing",
1040
+ metadata: {
1041
+ ...(binding.metadata ?? {}),
1042
+ diagnosticCode: "runtime_binding_missing",
1043
+ diagnosticMessage: "The bound native session is no longer available in the configured runtime instance.",
1044
+ },
1045
+ }, {
1046
+ expectedRevision: binding.revision,
1047
+ });
1048
+ }
1049
+ throw error;
1050
+ }
1051
+ try {
1052
+ const openedBinding = runtimeSession.getBinding();
1053
+ if (openedBinding.runtimeInstanceId !== binding.runtimeInstanceId || openedBinding.adapterId !== binding.adapterId) {
1054
+ throw new AgentRuntimeUnavailableError(binding.runtimeInstanceId, `Runtime instance "${binding.runtimeInstanceId}" opened an inconsistent binding for Pibo session "${piboSession.id}".`);
1055
+ }
1056
+ if (!runtimeBindingsEqual(binding, openedBinding)) {
1057
+ binding = this.persistSessionRuntimeBinding(piboSession, openedBinding, {
1058
+ expectedRevision: binding.revision,
1059
+ });
1060
+ }
1061
+ }
1062
+ catch (error) {
1063
+ await runtimeSession.dispose().catch(() => { });
1064
+ portableTools.dispose();
1065
+ if (this.portableToolSessions.get(piboSession.id) === portableTools)
1066
+ this.portableToolSessions.delete(piboSession.id);
1067
+ await resources.dispose();
1068
+ if (this.runtimeResourceSessions.get(piboSession.id) === resources)
1069
+ this.runtimeResourceSessions.delete(piboSession.id);
1070
+ throw error;
1071
+ }
1072
+ session = new RoutedSession(piboSession.id, runtimeSession, this.emitOutput, this.pluginRegistry, {
1073
+ forwardLegacyPiEvents: this.options.forwardPiEvents ?? false,
1074
+ onNativeEventTelemetry: this.telemetryRecorder
1075
+ ? (id, event, context) => this.telemetryRecorder?.recordPiEvent(id, event, {
1076
+ session: this.sessionStore.get(id),
1077
+ status: context.status,
1078
+ activeEventId: context.activeEventId,
1079
+ })
1080
+ : undefined,
1081
+ onSessionOperation: (result, event) => this.handleSessionOperation(result, event),
1082
+ onKillChildren: (id, opts) => this.killChildSessions(id, opts),
1083
+ onStateChange: (state) => {
1084
+ this.signalRegistry.project({
1085
+ type: "session_processing_changed",
1086
+ piboSessionId: piboSession.id,
1087
+ processing: state.processing,
1088
+ queuedMessages: state.queuedMessages,
1089
+ });
1090
+ if (!state.processing && state.queuedMessages === 0 && !state.disposed) {
1091
+ this.syncLiveSessionRuntimeBinding(piboSession.id, runtimeSession);
1092
+ }
1093
+ if (state.disposed || state.processing || state.queuedMessages > 0)
1094
+ this.clearIdleSessionTimer(piboSession.id);
1095
+ else
1096
+ this.scheduleIdleSessionEvictionIfIdle(piboSession.id);
1097
+ },
1098
+ onMessagesInterrupted: (messages, reason) => this.telemetryRecorder?.recordMessagesInterrupted(messages, {
1099
+ session: this.sessionStore.get(piboSession.id),
1100
+ status: this.sessions.get(piboSession.id)?.getStatus(),
1101
+ }, reason),
1102
+ messagePreflight: this.options.messagePreflight,
1103
+ getRuntimeAuthStatus: () => runtimeRegistry.getAgentRuntimeAuthStatus(binding.runtimeInstanceId),
1104
+ startRuntimeAuth: async (input) => {
1105
+ const { runtimeInstanceId: _runtimeInstanceId, ...result } = await runtimeRegistry.startAgentRuntimeAuth(binding.runtimeInstanceId, input);
1106
+ return result;
1107
+ },
1108
+ completeRuntimeAuth: async (input) => {
1109
+ const { runtimeInstanceId: _runtimeInstanceId, ...result } = await runtimeRegistry.completeAgentRuntimeAuth(binding.runtimeInstanceId, input);
1110
+ return result;
1111
+ },
1112
+ cancelRuntimeAuth: async (input) => {
1113
+ const { runtimeInstanceId: _runtimeInstanceId, ...result } = await runtimeRegistry.cancelAgentRuntimeAuth(binding.runtimeInstanceId, input);
1114
+ return result;
1115
+ },
1116
+ logoutRuntimeAuth: async (input) => {
1117
+ const { runtimeInstanceId: _runtimeInstanceId, ...result } = await runtimeRegistry.logoutAgentRuntimeAuth(binding.runtimeInstanceId, input);
1118
+ return result;
741
1119
  },
742
1120
  });
743
- const initialFastMode = resolvePiboSessionInitialFastMode(piboSession) ?? selectRequestedFastMode(sessionProfile, modelDefaults) ?? false;
744
- session = new RoutedSession(piboSession.id, runtime, this.emitOutput, this.pluginRegistry, this.options.forwardPiEvents ?? false, this.telemetryRecorder
745
- ? (id, event, context) => this.telemetryRecorder?.recordPiEvent(id, event, { session: this.sessionStore.get(id), status: context.status, activeEventId: context.activeEventId })
746
- : undefined, initialFastMode, (result, event) => this.handleSessionOperation(result, event), (id, opts) => this.killChildSessions(id, opts), (state) => {
747
- this.signalRegistry.project({ type: "session_processing_changed", piboSessionId: piboSession.id, processing: state.processing, queuedMessages: state.queuedMessages });
748
- if (state.disposed || state.processing || state.queuedMessages > 0)
749
- this.clearIdleSessionTimer(piboSession.id);
750
- else
751
- this.scheduleIdleSessionEvictionIfIdle(piboSession.id);
752
- }, (messages, reason) => this.telemetryRecorder?.recordMessagesInterrupted(messages, {
753
- session: this.sessionStore.get(piboSession.id),
754
- status: this.sessions.get(piboSession.id)?.getStatus(),
755
- }, reason), this.options.messagePreflight);
756
1121
  this.sessions.set(piboSession.id, session);
757
1122
  return session;
758
1123
  }
1124
+ resolveAgentRuntimeRegistry(instanceId) {
1125
+ if (this.pluginRegistry.getAgentRuntimeAdapter(instanceId))
1126
+ return this.pluginRegistry;
1127
+ if (this.compatibilityRuntimeRegistry?.getAgentRuntimeAdapter(instanceId))
1128
+ return this.compatibilityRuntimeRegistry;
1129
+ throw new Error(`Unknown agent runtime instance "${instanceId}".`);
1130
+ }
1131
+ resolveSessionRuntimeBinding(session) {
1132
+ return this.sessionStore.getRuntimeBinding?.(session.id)
1133
+ ?? session.runtimeBinding
1134
+ ?? createLegacyPiRuntimeSessionBinding(session.id, session.piSessionId, session.createdAt);
1135
+ }
1136
+ withPersistedRuntimeBinding(status) {
1137
+ const session = this.sessionStore.get(status.piboSessionId);
1138
+ if (!session)
1139
+ return status;
1140
+ const binding = this.resolveSessionRuntimeBinding(session);
1141
+ return {
1142
+ ...status,
1143
+ runtimeBinding: {
1144
+ runtimeInstanceId: binding.runtimeInstanceId,
1145
+ adapterId: binding.adapterId,
1146
+ nativeSessionId: binding.nativeSessionId,
1147
+ state: binding.state,
1148
+ protocol: binding.protocol,
1149
+ protocolVersion: binding.protocolVersion,
1150
+ adapterVersion: binding.adapterVersion,
1151
+ revision: binding.revision,
1152
+ },
1153
+ };
1154
+ }
1155
+ persistSessionRuntimeBinding(session, binding, options = {}) {
1156
+ const current = this.resolveSessionRuntimeBinding(session);
1157
+ const normalized = { ...structuredClone(binding), piboSessionId: session.id };
1158
+ if (runtimeBindingsEqual(current, normalized))
1159
+ return current;
1160
+ const persisted = this.sessionStore.updateRuntimeBinding?.(session.id, normalized, options);
1161
+ if (persisted) {
1162
+ const updatedSession = this.sessionStore.get(session.id);
1163
+ if (updatedSession)
1164
+ this.signalRegistry.project({ type: "session_created", session: updatedSession });
1165
+ return persisted;
1166
+ }
1167
+ const now = new Date().toISOString();
1168
+ if (normalized.adapterId === "pi" && normalized.nativeSessionId !== session.piSessionId) {
1169
+ this.sessionStore.update(session.id, { piSessionId: normalized.nativeSessionId ?? "" });
1170
+ }
1171
+ const updatedSession = this.sessionStore.get(session.id);
1172
+ if (updatedSession)
1173
+ this.signalRegistry.project({ type: "session_created", session: updatedSession });
1174
+ return {
1175
+ ...normalized,
1176
+ revision: (current.revision ?? 1) + 1,
1177
+ createdAt: current.createdAt ?? session.createdAt,
1178
+ updatedAt: now,
1179
+ };
1180
+ }
1181
+ syncLiveSessionRuntimeBinding(piboSessionId, runtimeSession) {
1182
+ const session = this.sessionStore.get(piboSessionId);
1183
+ if (!session)
1184
+ return;
1185
+ try {
1186
+ this.persistSessionRuntimeBinding(session, runtimeSession.getBinding());
1187
+ }
1188
+ catch (error) {
1189
+ const message = error instanceof Error ? error.message : String(error);
1190
+ this.emitOutput({
1191
+ type: "session_error",
1192
+ piboSessionId,
1193
+ error: `Failed to persist the live runtime binding: ${message}`,
1194
+ errorDetails: runtimeSessionErrorDetails(message),
1195
+ });
1196
+ }
1197
+ }
1198
+ assertOpenableRuntimeBinding(binding) {
1199
+ if (binding.state === "missing") {
1200
+ throw new AgentRuntimeBindingMissingError(binding.piboSessionId, binding.runtimeInstanceId, binding.nativeSessionId);
1201
+ }
1202
+ if (binding.state === "error") {
1203
+ const diagnostic = typeof binding.metadata?.diagnosticMessage === "string"
1204
+ ? binding.metadata.diagnosticMessage
1205
+ : "The persisted runtime binding is in an error state.";
1206
+ throw new AgentRuntimeUnavailableError(binding.runtimeInstanceId, `Runtime binding for Pibo session "${binding.piboSessionId}" cannot be opened: ${diagnostic}`);
1207
+ }
1208
+ }
1209
+ createRuntimeBindingInput(profile) {
1210
+ const registry = this.resolveAgentRuntimeRegistry(profile.runtimeInstanceId);
1211
+ const adapter = registry.requireAgentRuntimeAdapter(profile.runtimeInstanceId);
1212
+ return {
1213
+ runtimeInstanceId: profile.runtimeInstanceId,
1214
+ adapterId: adapter.descriptor.id,
1215
+ state: "unbound",
1216
+ protocol: adapter.descriptor.protocol?.name,
1217
+ };
1218
+ }
759
1219
  ensureSessionActiveModel(piboSession, profile, parentPiSessionId, modelDefaults) {
760
1220
  const activeModel = resolvePiboSessionActiveModel({
761
1221
  profile,
@@ -778,19 +1238,33 @@ export class PiboSessionRouter {
778
1238
  async handleSessionOperation(result, event) {
779
1239
  if (result.cancelled)
780
1240
  return;
1241
+ const source = this.resolvePiboSession(event.piboSessionId);
1242
+ const previousBinding = this.resolveSessionRuntimeBinding(source);
1243
+ const liveBinding = this.sessions.get(event.piboSessionId)?.getRuntimeBinding();
1244
+ const currentBinding = {
1245
+ ...previousBinding,
1246
+ ...liveBinding,
1247
+ piboSessionId: source.id,
1248
+ nativeSessionId: result.current.piSessionId,
1249
+ state: "bound",
1250
+ locator: result.current.sessionFile
1251
+ ? { kind: "local-file", value: result.current.sessionFile }
1252
+ : liveBinding?.locator ?? previousBinding.locator,
1253
+ };
781
1254
  if (event.action === "session.fork" || event.action === "session.clone") {
782
1255
  const action = event.action;
783
- const created = this.createDerivedSession(result, action);
1256
+ const created = this.createDerivedSession(result, action, currentBinding);
784
1257
  result.piboSessionId = created.id;
785
1258
  await this.resetCachedSession(event.piboSessionId);
786
1259
  return;
787
1260
  }
788
- this.sessionStore.update(event.piboSessionId, {
789
- piSessionId: result.current.piSessionId,
790
- workspace: result.current.cwd,
1261
+ this.persistSessionRuntimeBinding(source, currentBinding, {
1262
+ expectedRevision: previousBinding.revision,
1263
+ mode: "rebind",
791
1264
  });
1265
+ this.sessionStore.update(event.piboSessionId, { workspace: result.current.cwd });
792
1266
  }
793
- createDerivedSession(result, action) {
1267
+ createDerivedSession(result, action, currentBinding) {
794
1268
  const source = this.resolvePiboSession(result.piboSessionId);
795
1269
  return this.sessionStore.create({
796
1270
  channel: source.channel,
@@ -798,17 +1272,54 @@ export class PiboSessionRouter {
798
1272
  profile: source.profile,
799
1273
  parentId: source.kind === "subagent" ? source.parentId : undefined,
800
1274
  originId: source.id,
801
- piSessionId: result.current.piSessionId,
1275
+ runtimeBinding: {
1276
+ runtimeInstanceId: currentBinding.runtimeInstanceId,
1277
+ adapterId: currentBinding.adapterId,
1278
+ nativeSessionId: currentBinding.nativeSessionId ?? result.current.piSessionId,
1279
+ state: "bound",
1280
+ protocol: currentBinding.protocol,
1281
+ protocolVersion: currentBinding.protocolVersion,
1282
+ adapterVersion: currentBinding.adapterVersion,
1283
+ locator: currentBinding.locator,
1284
+ metadata: currentBinding.metadata,
1285
+ },
802
1286
  workspace: result.current.cwd,
803
1287
  title: source.title,
804
1288
  activeModel: source.activeModel,
805
1289
  metadata: {
806
1290
  ...asJsonObject(source.metadata),
807
1291
  originAction: action,
808
- originPiSessionId: result.previous.piSessionId,
1292
+ originRuntimeNativeSessionId: result.previous.piSessionId,
1293
+ ...(currentBinding.adapterId === "pi" ? { originPiSessionId: result.previous.piSessionId } : {}),
809
1294
  },
810
1295
  });
811
1296
  }
1297
+ runtimeAuthAffectedInstanceIds(runtimeInstanceId) {
1298
+ const registry = this.resolveAgentRuntimeRegistry(runtimeInstanceId);
1299
+ const target = registry.requireAgentRuntimeAdapter(runtimeInstanceId);
1300
+ if (target.descriptor.capabilities.auth.credentialScope === "runtime-instance")
1301
+ return [runtimeInstanceId];
1302
+ return registry.getAgentRuntimeInstanceIds().filter((candidateId) => {
1303
+ const candidate = registry.getAgentRuntimeAdapter(candidateId);
1304
+ return candidate?.descriptor.id === target.descriptor.id
1305
+ && candidate.descriptor.capabilities.auth.credentialScope === "adapter-shared";
1306
+ });
1307
+ }
1308
+ async resetCachedRuntimeAuthSessions(runtimeInstanceId, reason) {
1309
+ const affectedInstanceIds = new Set(this.runtimeAuthAffectedInstanceIds(runtimeInstanceId));
1310
+ for (const affectedInstanceId of affectedInstanceIds)
1311
+ this.runtimeAuthFingerprints.delete(affectedInstanceId);
1312
+ const ids = [...new Set([...this.sessions.keys(), ...this.pendingSessions.keys()])]
1313
+ .filter((piboSessionId) => {
1314
+ const boundRuntimeInstanceId = this.getSessionRuntimeBinding(piboSessionId)?.runtimeInstanceId;
1315
+ return boundRuntimeInstanceId !== undefined && affectedInstanceIds.has(boundRuntimeInstanceId);
1316
+ });
1317
+ const results = await Promise.allSettled(ids.map(async (piboSessionId) => await this.resetCachedSession(piboSessionId, reason)));
1318
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
1319
+ if (failures.length > 0) {
1320
+ throw new AggregateError(failures, `Failed to reset cached sessions affected by runtime auth target "${runtimeInstanceId}".`);
1321
+ }
1322
+ }
812
1323
  async resetCachedSession(piboSessionId, reason) {
813
1324
  const existingDisposal = this.disposingSessions.get(piboSessionId);
814
1325
  if (existingDisposal)
@@ -860,14 +1371,50 @@ export class PiboSessionRouter {
860
1371
  channel: "pibo.runtime",
861
1372
  kind: "runtime",
862
1373
  profile: this.baseProfile.profileName,
1374
+ runtimeBinding: this.createRuntimeBindingInput(this.baseProfile),
863
1375
  workspace: this.options.cwd ?? getDefaultPiboWorkspace(),
864
1376
  });
865
1377
  this.signalRegistry.project({ type: "session_created", session: created });
866
1378
  return created;
867
1379
  }
1380
+ trackActiveSubagent(parentPiboSessionId, childPiboSessionId) {
1381
+ let children = this.activeSubagentChildren.get(parentPiboSessionId);
1382
+ if (!children) {
1383
+ children = new Map();
1384
+ this.activeSubagentChildren.set(parentPiboSessionId, children);
1385
+ }
1386
+ children.set(childPiboSessionId, (children.get(childPiboSessionId) ?? 0) + 1);
1387
+ let active = true;
1388
+ return () => {
1389
+ if (!active)
1390
+ return;
1391
+ active = false;
1392
+ const current = this.activeSubagentChildren.get(parentPiboSessionId);
1393
+ if (!current)
1394
+ return;
1395
+ const remaining = (current.get(childPiboSessionId) ?? 1) - 1;
1396
+ if (remaining > 0)
1397
+ current.set(childPiboSessionId, remaining);
1398
+ else
1399
+ current.delete(childPiboSessionId);
1400
+ if (current.size === 0)
1401
+ this.activeSubagentChildren.delete(parentPiboSessionId);
1402
+ };
1403
+ }
1404
+ async abortActiveSubagentSessions(parentPiboSessionId) {
1405
+ const childIds = [...(this.activeSubagentChildren.get(parentPiboSessionId)?.keys() ?? [])];
1406
+ if (childIds.length === 0)
1407
+ return;
1408
+ await Promise.allSettled(childIds.map(async (childPiboSessionId) => await this.emit({
1409
+ type: "execution",
1410
+ piboSessionId: childPiboSessionId,
1411
+ action: "abort",
1412
+ id: randomUUID(),
1413
+ })));
1414
+ }
868
1415
  createSubagentRunner(parentPiboSessionId) {
869
1416
  return {
870
- runSubagent: async ({ subagent, message, threadKey, toolCallId }) => {
1417
+ runSubagent: async ({ subagent, message, threadKey, toolCallId, signal }) => {
871
1418
  this.assertSubagentDepth(parentPiboSessionId, subagent);
872
1419
  const child = this.resolveSubagentSession(parentPiboSessionId, subagent, threadKey);
873
1420
  const toolName = createSubagentToolName(subagent.name);
@@ -887,8 +1434,14 @@ export class PiboSessionRouter {
887
1434
  childPiboSessionId: child.id,
888
1435
  threadKey: typeof child.metadata?.threadKey === "string" ? child.metadata.threadKey : undefined,
889
1436
  });
890
- const reply = await this.emitMessageAndWaitForReply(event, subagent.timeoutMs ?? DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS);
891
- return { piboSessionId: child.id, eventId: event.id, reply };
1437
+ const untrack = this.trackActiveSubagent(parentPiboSessionId, child.id);
1438
+ try {
1439
+ const reply = await this.emitMessageAndWaitForReply(event, subagent.timeoutMs ?? DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS, signal);
1440
+ return { piboSessionId: child.id, eventId: event.id, reply };
1441
+ }
1442
+ finally {
1443
+ untrack();
1444
+ }
892
1445
  },
893
1446
  };
894
1447
  }
@@ -987,7 +1540,7 @@ export class PiboSessionRouter {
987
1540
  resolveSubagentSession(parentPiboSessionId, subagent, threadKey) {
988
1541
  const targetProfile = resolvePiboProfileNameFromRegistryOrDefault(this.pluginRegistry, subagent.targetProfile);
989
1542
  const parent = this.resolvePiboSession(parentPiboSessionId);
990
- const resolvedThreadKey = threadKey?.trim() ? threadKey.trim() : randomUUID();
1543
+ const resolvedThreadKey = resolveSubagentThreadKey(threadKey);
991
1544
  const baseMetadata = {
992
1545
  subagentName: subagent.name,
993
1546
  subagentToolName: createSubagentToolName(subagent.name),
@@ -1027,6 +1580,7 @@ export class PiboSessionRouter {
1027
1580
  kind: "subagent",
1028
1581
  profile: targetProfile,
1029
1582
  parentId: parent.id,
1583
+ runtimeBinding: this.createRuntimeBindingInput(childProfile),
1030
1584
  workspace: parent.workspace,
1031
1585
  metadata,
1032
1586
  });
@@ -1034,7 +1588,7 @@ export class PiboSessionRouter {
1034
1588
  const activeModel = resolvePiboSessionActiveModel({
1035
1589
  profile: childProfile,
1036
1590
  piboSession: childSession,
1037
- parentPiSessionId: parent.piSessionId,
1591
+ parentPiSessionId: this.resolveSessionRuntimeBinding(parent).nativeSessionId ?? parent.id,
1038
1592
  modelDefaults: this.resolveModelDefaults(),
1039
1593
  });
1040
1594
  return activeModel ? this.sessionStore.update(childSession.id, { activeModel }) ?? childSession : childSession;