@pasko70/pibo 1.16.6 → 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 (145) 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-BTLtT_Vf.js → dist-9v2ZXT6V.js} +1 -1
  54. package/dist/apps/chat-ui/assets/{dist-CMk1aPug.js → dist-B1LVAytl.js} +1 -1
  55. package/dist/apps/chat-ui/assets/{dist-CcwA_IWN.js → dist-C6LPDFXU.js} +1 -1
  56. package/dist/apps/chat-ui/assets/{dist-DKOthbxr.js → dist-DVtXxfHc.js} +1 -1
  57. package/dist/apps/chat-ui/assets/{dist-ls2I6BIw.js → dist-DW4Qrfbd.js} +1 -1
  58. package/dist/apps/chat-ui/assets/{dist-D1rAtRWs.js → dist-DmbsHdMC.js} +1 -1
  59. package/dist/apps/chat-ui/assets/{dist-C1JQRskW.js → dist-DyvwuSFO.js} +1 -1
  60. package/dist/apps/chat-ui/assets/{dist-DfpaylQ-.js → dist-I26VB0QO.js} +1 -1
  61. package/dist/apps/chat-ui/assets/{dist-D10bTFD5.js → dist-RtHbJRPV.js} +1 -1
  62. package/dist/apps/chat-ui/assets/{dist-5KpmmBBZ.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-C2YI0xfw.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
@@ -1,6 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { normalizeToolProfile } from "../core/profiles.js";
2
3
  import { listInstalledCliToolAgentContexts } from "../tools/registry.js";
3
4
  import { listPiPackages } from "../pi-packages/store.js";
5
+ import { AgentRuntimeAdapterRegistry } from "../agent-runtime/registry.js";
4
6
  function getWebAppRoutes(app) {
5
7
  return [
6
8
  { label: "mountPath", prefix: app.mountPath },
@@ -18,7 +20,20 @@ function validateWebRoute(appName, label, prefix) {
18
20
  function webRoutesOverlap(left, right) {
19
21
  return left === right || left === "/" || right === "/" || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
20
22
  }
23
+ function toolIsPortable(tool) {
24
+ if (tool.definition)
25
+ return tool.definition.portable !== false;
26
+ if (!tool.createDefinition)
27
+ return true;
28
+ try {
29
+ return tool.createDefinition({}).portable !== false;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
21
35
  export class PiboPluginRegistry {
36
+ agentRuntimes = new AgentRuntimeAdapterRegistry();
22
37
  tools = new Map();
23
38
  subagents = new Map();
24
39
  skills = new Map();
@@ -52,8 +67,51 @@ export class PiboPluginRegistry {
52
67
  this.pluginNames.set(plugin.id, plugin.name ?? plugin.id);
53
68
  plugin.register(this.createApi(plugin.id));
54
69
  }
70
+ registerAgentRuntimeDriver(driver) {
71
+ this.agentRuntimes.registerDriver(driver);
72
+ }
73
+ registerAgentRuntimeInstance(instance) {
74
+ return this.agentRuntimes.registerInstance(instance);
75
+ }
76
+ getAgentRuntimeAdapter(instanceId) {
77
+ return this.agentRuntimes.getInstance(instanceId);
78
+ }
79
+ requireAgentRuntimeAdapter(instanceId) {
80
+ return this.agentRuntimes.requireInstance(instanceId);
81
+ }
82
+ openAgentRuntimeSession(instanceId, input) {
83
+ return this.agentRuntimes.openSession(instanceId, input);
84
+ }
85
+ getAgentRuntimeInstanceIds() {
86
+ return this.agentRuntimes.getInstanceIds();
87
+ }
88
+ inspectAgentRuntimeInstances() {
89
+ return this.agentRuntimes.inspectInstances();
90
+ }
91
+ getAgentRuntimeAuthStatus(runtimeInstanceId) {
92
+ return this.agentRuntimes.getAuthStatus(runtimeInstanceId);
93
+ }
94
+ startAgentRuntimeAuth(runtimeInstanceId, input) {
95
+ return this.agentRuntimes.startAuth(runtimeInstanceId, input);
96
+ }
97
+ completeAgentRuntimeAuth(runtimeInstanceId, input) {
98
+ return this.agentRuntimes.completeAuth(runtimeInstanceId, input);
99
+ }
100
+ cancelAgentRuntimeAuth(runtimeInstanceId, input) {
101
+ return this.agentRuntimes.cancelAuth(runtimeInstanceId, input);
102
+ }
103
+ logoutAgentRuntimeAuth(runtimeInstanceId, input) {
104
+ return this.agentRuntimes.logoutAuth(runtimeInstanceId, input);
105
+ }
106
+ disposeAgentRuntimeAuth() {
107
+ return this.agentRuntimes.disposeAuth();
108
+ }
109
+ validateAgentRuntimeProfile(profile, workspace) {
110
+ return this.agentRuntimes.validateProfile({ profile, workspace });
111
+ }
55
112
  registerTool(tool) {
56
- this.addUnique(this.tools, tool.name, tool, "tool");
113
+ const normalized = normalizeToolProfile(tool);
114
+ this.addUnique(this.tools, normalized.name, normalized, "tool");
57
115
  }
58
116
  registerTools(tools) {
59
117
  for (const tool of tools) {
@@ -187,6 +245,8 @@ export class PiboPluginRegistry {
187
245
  name: profile.name,
188
246
  description: profile.description,
189
247
  aliases: [...(profile.aliases ?? [])],
248
+ runtimeInstanceId: sessionContext.runtimeInstanceId,
249
+ runtimeOptions: structuredClone(sessionContext.runtimeOptions),
190
250
  nativeTools: sessionContext.tools.filter((tool) => tool.enabled !== false).map((tool) => tool.name),
191
251
  skills: sessionContext.skills.filter((skill) => skill.enabled !== false).map((skill) => skill.name),
192
252
  contextFiles: sessionContext.contextFiles.filter((contextFile) => contextFile.enabled !== false).map(contextFileKey),
@@ -212,11 +272,13 @@ export class PiboPluginRegistry {
212
272
  }
213
273
  getCapabilityCatalog() {
214
274
  return {
275
+ agentRuntimes: this.agentRuntimes.getInstanceInfos(),
215
276
  nativeTools: [...this.tools.values()].map((tool) => ({
216
277
  name: tool.name,
217
278
  description: tool.description,
218
279
  yieldable: tool.yieldable !== false,
219
280
  hasDefinition: tool.definition !== undefined || tool.createDefinition !== undefined,
281
+ portable: toolIsPortable(tool),
220
282
  pluginId: tool.pluginId,
221
283
  pluginName: tool.pluginId ? this.pluginNames.get(tool.pluginId) : undefined,
222
284
  ...(tool.providerTool ? { providerTool: tool.providerTool } : {}),
@@ -248,7 +310,7 @@ export class PiboPluginRegistry {
248
310
  packages: [
249
311
  {
250
312
  name: "pibo-run-control",
251
- description: "Expose pibo_run_* tools as one package for yielded native tools and subagents.",
313
+ description: "Expose pibo_run_* for Pibo-managed tools and subagents; private harness-native tools require explicit runtime capability.",
252
314
  toolNames: [
253
315
  "pibo_run_start",
254
316
  "pibo_run_list",
@@ -350,6 +412,8 @@ export class PiboPluginRegistry {
350
412
  pluginId: pkg.pluginId ?? pluginId,
351
413
  });
352
414
  return {
415
+ registerAgentRuntimeDriver: (driver) => this.registerAgentRuntimeDriver(driver),
416
+ registerAgentRuntimeInstance: (instance) => this.registerAgentRuntimeInstance(instance),
353
417
  registerTool: (tool) => this.registerTool(withPluginToolContext(tool)),
354
418
  registerTools: (tools) => this.registerTools(tools.map(withPluginToolContext)),
355
419
  registerSubagent: (subagent) => this.registerSubagent(subagent),
@@ -1,5 +1,6 @@
1
- import { StringEnum, Type } from "@earendil-works/pi-ai";
2
- import { defineTool } from "@earendil-works/pi-coding-agent";
1
+ import { Type } from "typebox";
2
+ import { piboStringEnum } from "../tools/schema.js";
3
+ import { definePiboTool } from "../tools/contract.js";
3
4
  import { foregroundServiceWarning, hasMeaningfulTimeoutOutput, isConfiguredTimeoutError, PiboRunExecutionTimeoutError, resolveRunTimeoutMs } from "./lifecycle.js";
4
5
  import { PiboRunResourceLimitError, prepareYieldedRunExecution } from "./resource-isolation.js";
5
6
  function resultText(prefix, value) {
@@ -29,16 +30,16 @@ function requireTool(tools, name) {
29
30
  export function createRunToolDefinitions(yieldableTools, controller) {
30
31
  const toolNames = yieldableTools.map((tool) => tool.name);
31
32
  return [
32
- defineTool({
33
+ definePiboTool({
33
34
  name: "pibo_run_start",
34
- label: "Pibo Run Start",
35
+ title: "Pibo Run Start",
35
36
  description: "Start a yieldable tool as a yielded run. The run records its configured timeout and classifies lifetime expiry separately from command failure. Use detached only for intentional fire-and-forget work.",
36
37
  promptSnippet: "Use pibo_run_start to run a yieldable tool in the background. It returns a runId. Use pibo_run_read for completed results and pibo_run_wait/status/list/cancel/ack to manage runs.",
37
38
  executionMode: "parallel",
38
- parameters: Type.Object({
39
- toolName: StringEnum(toolNames, { description: "Yieldable tool name to start" }),
39
+ inputSchema: Type.Object({
40
+ toolName: piboStringEnum(toolNames, { description: "Yieldable tool name to start" }),
40
41
  arguments: Type.Any({ description: "Arguments object for the selected tool" }),
41
- completionPolicy: Type.Optional(StringEnum(["tracked", "detached"], {
42
+ completionPolicy: Type.Optional(piboStringEnum(["tracked", "detached"], {
42
43
  description: "tracked reminds this agent about completion; detached is fire-and-forget and creates no automatic reminders.",
43
44
  default: "tracked",
44
45
  })),
@@ -87,13 +88,13 @@ export function createRunToolDefinitions(yieldableTools, controller) {
87
88
  };
88
89
  },
89
90
  }),
90
- defineTool({
91
+ definePiboTool({
91
92
  name: "pibo_run_list",
92
- label: "Pibo Run List",
93
+ title: "Pibo Run List",
93
94
  description: "List yielded runs owned by this agent session.",
94
95
  promptSnippet: "Use pibo_run_list to inspect yielded runs owned by this session.",
95
96
  executionMode: "parallel",
96
- parameters: Type.Object({
97
+ inputSchema: Type.Object({
97
98
  includeConsumed: Type.Optional(Type.Boolean({ description: "Include already read, cancelled, or acknowledged runs" })),
98
99
  includeDetached: Type.Optional(Type.Boolean({ description: "Include fire-and-forget detached runs" })),
99
100
  }),
@@ -108,13 +109,13 @@ export function createRunToolDefinitions(yieldableTools, controller) {
108
109
  };
109
110
  },
110
111
  }),
111
- defineTool({
112
+ definePiboTool({
112
113
  name: "pibo_run_status",
113
- label: "Pibo Run Status",
114
+ title: "Pibo Run Status",
114
115
  description: "Read compact status for one yielded run.",
115
116
  promptSnippet: "Use pibo_run_status to inspect one yielded run without reading its full result.",
116
117
  executionMode: "parallel",
117
- parameters: Type.Object({
118
+ inputSchema: Type.Object({
118
119
  runId: Type.String({ description: "Run id returned by pibo_run_start" }),
119
120
  }),
120
121
  async execute(_toolCallId, params) {
@@ -125,13 +126,13 @@ export function createRunToolDefinitions(yieldableTools, controller) {
125
126
  };
126
127
  },
127
128
  }),
128
- defineTool({
129
+ definePiboTool({
129
130
  name: "pibo_run_wait",
130
- label: "Pibo Run Wait",
131
+ title: "Pibo Run Wait",
131
132
  description: "Wait a bounded time for a yielded run. Timeout is normal and does not mean failure.",
132
133
  promptSnippet: "Use pibo_run_wait when blocked on a run. Timeout is normal; call again or continue other work.",
133
134
  executionMode: "parallel",
134
- parameters: Type.Object({
135
+ inputSchema: Type.Object({
135
136
  runId: Type.String({ description: "Run id returned by pibo_run_start" }),
136
137
  timeoutMs: Type.Optional(Type.Number({ description: "Maximum wait time in milliseconds, clamped to 300000" })),
137
138
  }),
@@ -150,13 +151,13 @@ export function createRunToolDefinitions(yieldableTools, controller) {
150
151
  };
151
152
  },
152
153
  }),
153
- defineTool({
154
+ definePiboTool({
154
155
  name: "pibo_run_read",
155
- label: "Pibo Run Read",
156
+ title: "Pibo Run Read",
156
157
  description: "Read the terminal result, timeout reason, or error for a yielded run.",
157
158
  promptSnippet: "Use pibo_run_read to retrieve a completed, failed, or timed_out run result. Reading terminal tracked runs consumes reminders.",
158
159
  executionMode: "parallel",
159
- parameters: Type.Object({
160
+ inputSchema: Type.Object({
160
161
  runId: Type.String({ description: "Run id returned by pibo_run_start" }),
161
162
  }),
162
163
  async execute(_toolCallId, params) {
@@ -168,13 +169,13 @@ export function createRunToolDefinitions(yieldableTools, controller) {
168
169
  };
169
170
  },
170
171
  }),
171
- defineTool({
172
+ definePiboTool({
172
173
  name: "pibo_run_cancel",
173
- label: "Pibo Run Cancel",
174
+ title: "Pibo Run Cancel",
174
175
  description: "Cancel a yielded run if possible and suppress future reminders.",
175
176
  promptSnippet: "Use pibo_run_cancel when a yielded run is no longer needed.",
176
177
  executionMode: "parallel",
177
- parameters: Type.Object({
178
+ inputSchema: Type.Object({
178
179
  runId: Type.String({ description: "Run id returned by pibo_run_start" }),
179
180
  }),
180
181
  async execute(_toolCallId, params) {
@@ -185,13 +186,13 @@ export function createRunToolDefinitions(yieldableTools, controller) {
185
186
  };
186
187
  },
187
188
  }),
188
- defineTool({
189
+ definePiboTool({
189
190
  name: "pibo_run_ack",
190
- label: "Pibo Run Ack",
191
+ title: "Pibo Run Ack",
191
192
  description: "Acknowledge a yielded run update and suppress reminders for its current state.",
192
193
  promptSnippet: "Use pibo_run_ack when you intentionally do not need to read a completed result or do not need more reminders for the current running state.",
193
194
  executionMode: "parallel",
194
- parameters: Type.Object({
195
+ inputSchema: Type.Object({
195
196
  runId: Type.String({ description: "Run id returned by pibo_run_start" }),
196
197
  }),
197
198
  async execute(_toolCallId, params) {
@@ -226,8 +226,8 @@ function createAssistantMessageRow(node) {
226
226
  lines: [],
227
227
  sourceNodeIds: [node.id],
228
228
  startedAt: node.startedAt,
229
- completedAt: node.source === "transcript" ? node.completedAt : undefined,
230
- durationMs: node.source === "transcript" ? node.durationMs : undefined,
229
+ completedAt: node.source === "transcript" || node.source === "product-history" ? node.completedAt : undefined,
230
+ durationMs: node.source === "transcript" || node.source === "product-history" ? node.durationMs : undefined,
231
231
  output: stringValue(node.output) || stringValue(node.summary) || "",
232
232
  error: node.error,
233
233
  payloadRefs: node.payloadRefs,
@@ -1,6 +1,26 @@
1
1
  import { ChatDataIngestService } from "../data/ingest-service.js";
2
2
  import { PiboDataStore } from "../data/pibo-store.js";
3
3
  import { createPiboSession, matchesFindInput, } from "./store.js";
4
+ import { createLegacyPiRuntimeSessionBinding, nextRuntimeSessionBinding, RuntimeSessionBindingConflictError, } from "./runtime-binding.js";
5
+ const SESSION_SELECT = `
6
+ SELECT
7
+ s.*,
8
+ b.pibo_session_id AS binding_pibo_session_id,
9
+ b.runtime_instance_id AS binding_runtime_instance_id,
10
+ b.runtime_adapter_id AS binding_runtime_adapter_id,
11
+ b.native_session_id AS binding_native_session_id,
12
+ b.binding_state AS binding_state,
13
+ b.protocol AS binding_protocol,
14
+ b.protocol_version AS binding_protocol_version,
15
+ b.adapter_version AS binding_adapter_version,
16
+ b.locator_json AS binding_locator_json,
17
+ b.metadata_json AS binding_metadata_json,
18
+ b.revision AS binding_revision,
19
+ b.created_at AS binding_created_at,
20
+ b.updated_at AS binding_updated_at
21
+ FROM sessions s
22
+ LEFT JOIN session_runtime_bindings b ON b.pibo_session_id = s.id
23
+ `;
4
24
  export class PiboDataSessionStore {
5
25
  dataStore;
6
26
  db;
@@ -17,15 +37,15 @@ export class PiboDataSessionStore {
17
37
  this.db = this.dataStore.db;
18
38
  }
19
39
  get(id) {
20
- const row = this.db.prepare("SELECT * FROM sessions WHERE id = ? AND deleted_at IS NULL").get(id);
40
+ const row = this.db.prepare(`${SESSION_SELECT} WHERE s.id = ? AND s.deleted_at IS NULL`).get(id);
21
41
  return row ? sessionFromRow(row) : undefined;
22
42
  }
23
43
  list() {
24
- return this.db.prepare("SELECT * FROM sessions WHERE deleted_at IS NULL ORDER BY updated_at DESC").all().map(sessionFromRow);
44
+ return this.db.prepare(`${SESSION_SELECT} WHERE s.deleted_at IS NULL ORDER BY s.updated_at DESC`).all().map(sessionFromRow);
25
45
  }
26
46
  create(input) {
27
47
  const session = createPiboSession(input);
28
- this.insertSession(session);
48
+ this.dataStore.transaction(() => this.insertSession(session));
29
49
  const created = this.get(session.id);
30
50
  if (!created)
31
51
  throw new Error(`Failed to create Pibo session "${session.id}"`);
@@ -68,7 +88,7 @@ export class PiboDataSessionStore {
68
88
  updated_at = ?,
69
89
  last_activity_at = MAX(last_activity_at, ?)
70
90
  WHERE id = ? AND deleted_at IS NULL
71
- `).run(updated.piSessionId, rootSessionId(updated), updated.parentId ?? null, updated.originId ?? null, updated.profile, updated.activeModel ? JSON.stringify(updated.activeModel) : null, updated.workspace ?? null, updated.title ?? "Untitled Session", JSON.stringify(updated.metadata ?? {}), updated.updatedAt, updated.updatedAt, id);
91
+ `).run(updated.piSessionId || null, rootSessionId(updated), updated.parentId ?? null, updated.originId ?? null, updated.profile, updated.activeModel ? JSON.stringify(updated.activeModel) : null, updated.workspace ?? null, updated.title ?? "Untitled Session", JSON.stringify(updated.metadata ?? {}), updated.updatedAt, updated.updatedAt, id);
72
92
  return this.get(id);
73
93
  }
74
94
  delete(id) {
@@ -76,50 +96,90 @@ export class PiboDataSessionStore {
76
96
  return Number(result.changes ?? 0) > 0;
77
97
  }
78
98
  find(input) {
79
- const clauses = ["deleted_at IS NULL"];
99
+ const clauses = ["s.deleted_at IS NULL"];
80
100
  const values = [];
81
101
  if (input.ids !== undefined) {
82
102
  if (input.ids.length === 0)
83
103
  return [];
84
- clauses.push(`id IN (${input.ids.map(() => "?").join(", ")})`);
104
+ clauses.push(`s.id IN (${input.ids.map(() => "?").join(", ")})`);
85
105
  values.push(...input.ids);
86
106
  }
87
107
  if (input.channel !== undefined) {
88
- clauses.push("channel = ?");
108
+ clauses.push("s.channel = ?");
89
109
  values.push(input.channel);
90
110
  }
91
111
  if (input.kind !== undefined) {
92
- clauses.push("kind = ?");
112
+ clauses.push("s.kind = ?");
93
113
  values.push(input.kind);
94
114
  }
95
115
  if (input.parentId !== undefined) {
96
116
  if (input.parentId === null)
97
- clauses.push("parent_id IS NULL");
117
+ clauses.push("s.parent_id IS NULL");
98
118
  else {
99
- clauses.push("parent_id = ?");
119
+ clauses.push("s.parent_id = ?");
100
120
  values.push(input.parentId);
101
121
  }
102
122
  }
103
123
  if (input.originId !== undefined) {
104
- clauses.push("origin_id = ?");
124
+ clauses.push("s.origin_id = ?");
105
125
  values.push(input.originId);
106
126
  }
107
127
  if (input.profile !== undefined) {
108
- clauses.push("profile = ?");
128
+ clauses.push("s.profile = ?");
109
129
  values.push(input.profile);
110
130
  }
111
131
  if (input.activeModel !== undefined) {
112
132
  if (input.activeModel === null)
113
- clauses.push("active_model_json IS NULL");
133
+ clauses.push("s.active_model_json IS NULL");
114
134
  else
115
- clauses.push("active_model_json IS NOT NULL");
135
+ clauses.push("s.active_model_json IS NOT NULL");
116
136
  }
117
- const rows = this.db.prepare(`SELECT * FROM sessions WHERE ${clauses.join(" AND ")} ORDER BY updated_at DESC`).all(...values);
137
+ const rows = this.db.prepare(`${SESSION_SELECT} WHERE ${clauses.join(" AND ")} ORDER BY s.updated_at DESC`).all(...values);
118
138
  return rows.map(sessionFromRow).filter((session) => matchesFindInput(session, input));
119
139
  }
140
+ getRuntimeBinding(id) {
141
+ const session = this.get(id);
142
+ return session?.runtimeBinding ? structuredClone(session.runtimeBinding) : undefined;
143
+ }
144
+ updateRuntimeBinding(id, binding, options = {}) {
145
+ return this.dataStore.transaction(() => {
146
+ const current = this.getRuntimeBinding(id);
147
+ if (!current)
148
+ return undefined;
149
+ const currentRevision = current.revision ?? 1;
150
+ const updated = nextRuntimeSessionBinding(current, { ...structuredClone(binding), piboSessionId: id }, options);
151
+ const result = this.db.prepare(`
152
+ UPDATE session_runtime_bindings SET
153
+ runtime_instance_id = ?,
154
+ runtime_adapter_id = ?,
155
+ native_session_id = ?,
156
+ binding_state = ?,
157
+ protocol = ?,
158
+ protocol_version = ?,
159
+ adapter_version = ?,
160
+ locator_json = ?,
161
+ metadata_json = ?,
162
+ revision = ?,
163
+ updated_at = ?
164
+ WHERE pibo_session_id = ? AND revision = ?
165
+ `).run(updated.runtimeInstanceId, updated.adapterId, updated.nativeSessionId ?? null, updated.state, updated.protocol ?? null, updated.protocolVersion ?? null, updated.adapterVersion ?? null, updated.locator ? JSON.stringify(updated.locator) : null, JSON.stringify(updated.metadata ?? {}), updated.revision, updated.updatedAt, id, currentRevision);
166
+ if (Number(result.changes ?? 0) === 0) {
167
+ const actual = this.getRuntimeBinding(id);
168
+ throw new RuntimeSessionBindingConflictError(id, currentRevision, actual?.revision ?? 0);
169
+ }
170
+ this.db.prepare(`
171
+ UPDATE sessions SET pi_session_id = ?, updated_at = ?, last_activity_at = MAX(last_activity_at, ?)
172
+ WHERE id = ? AND deleted_at IS NULL
173
+ `).run(updated.adapterId === "pi" ? updated.nativeSessionId ?? null : null, updated.updatedAt, updated.updatedAt, id);
174
+ return this.getRuntimeBinding(id);
175
+ });
176
+ }
120
177
  getTelemetryStore() {
121
178
  return this.dataStore.telemetry;
122
179
  }
180
+ getPayloadStore() {
181
+ return this.dataStore.payloads;
182
+ }
123
183
  recoverInterruptedRuntimeState(input = {}) {
124
184
  const at = input.at ?? new Date().toISOString();
125
185
  const runsBySession = groupRunsByController(input.recoveredRuns ?? []);
@@ -196,7 +256,30 @@ export class PiboDataSessionStore {
196
256
  ];
197
257
  this.db.prepare(`
198
258
  INSERT INTO sessions (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})
199
- `).run(session.id, session.piSessionId, roomIdFromMetadata(session.metadata), rootSessionId(session), session.parentId ?? null, session.originId ?? null, session.channel, session.kind, session.profile, session.activeModel ? JSON.stringify(session.activeModel) : null, session.workspace ?? null, session.title ?? "Untitled Session", previewText(session.title ?? "") ?? null, "idle", JSON.stringify(session.metadata ?? {}), session.createdAt, session.updatedAt, session.updatedAt);
259
+ `).run(session.id, session.piSessionId || null, roomIdFromMetadata(session.metadata), rootSessionId(session), session.parentId ?? null, session.originId ?? null, session.channel, session.kind, session.profile, session.activeModel ? JSON.stringify(session.activeModel) : null, session.workspace ?? null, session.title ?? "Untitled Session", previewText(session.title ?? "") ?? null, "idle", JSON.stringify(session.metadata ?? {}), session.createdAt, session.updatedAt, session.updatedAt);
260
+ this.upsertRuntimeBinding(session.runtimeBinding ?? createLegacyPiRuntimeSessionBinding(session.id, session.piSessionId, session.createdAt));
261
+ }
262
+ upsertRuntimeBinding(binding) {
263
+ this.db.prepare(`
264
+ INSERT INTO session_runtime_bindings (
265
+ pibo_session_id, runtime_instance_id, runtime_adapter_id, native_session_id,
266
+ binding_state, protocol, protocol_version, adapter_version, locator_json,
267
+ metadata_json, revision, created_at, updated_at
268
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
269
+ ON CONFLICT(pibo_session_id) DO UPDATE SET
270
+ runtime_instance_id = excluded.runtime_instance_id,
271
+ runtime_adapter_id = excluded.runtime_adapter_id,
272
+ native_session_id = excluded.native_session_id,
273
+ binding_state = excluded.binding_state,
274
+ protocol = excluded.protocol,
275
+ protocol_version = excluded.protocol_version,
276
+ adapter_version = excluded.adapter_version,
277
+ locator_json = excluded.locator_json,
278
+ metadata_json = excluded.metadata_json,
279
+ revision = excluded.revision,
280
+ created_at = excluded.created_at,
281
+ updated_at = excluded.updated_at
282
+ `).run(binding.piboSessionId, binding.runtimeInstanceId, binding.adapterId, binding.nativeSessionId ?? null, binding.state, binding.protocol ?? null, binding.protocolVersion ?? null, binding.adapterVersion ?? null, binding.locator ? JSON.stringify(binding.locator) : null, JSON.stringify(binding.metadata ?? {}), binding.revision ?? 1, binding.createdAt ?? new Date().toISOString(), binding.updatedAt ?? binding.createdAt ?? new Date().toISOString());
200
283
  }
201
284
  }
202
285
  export function createDefaultPiboDataSessionStore() {
@@ -255,6 +338,7 @@ function sessionFromRow(row) {
255
338
  return {
256
339
  id: row.id,
257
340
  piSessionId: row.pi_session_id ?? "",
341
+ runtimeBinding: runtimeBindingFromRow(row),
258
342
  channel: row.channel,
259
343
  kind: row.kind,
260
344
  profile: row.profile,
@@ -268,6 +352,31 @@ function sessionFromRow(row) {
268
352
  updatedAt: row.updated_at,
269
353
  };
270
354
  }
355
+ function runtimeBindingFromRow(row) {
356
+ if (!row.binding_runtime_instance_id || !row.binding_runtime_adapter_id || !row.binding_state) {
357
+ return createLegacyPiRuntimeSessionBinding(row.id, row.pi_session_id ?? undefined, row.created_at);
358
+ }
359
+ return {
360
+ piboSessionId: row.binding_pibo_session_id ?? row.id,
361
+ runtimeInstanceId: row.binding_runtime_instance_id,
362
+ adapterId: row.binding_runtime_adapter_id,
363
+ nativeSessionId: row.binding_native_session_id ?? undefined,
364
+ state: isRuntimeBindingState(row.binding_state) ? row.binding_state : "error",
365
+ protocol: row.binding_protocol ?? undefined,
366
+ protocolVersion: row.binding_protocol_version ?? undefined,
367
+ adapterVersion: row.binding_adapter_version ?? undefined,
368
+ locator: row.binding_locator_json
369
+ ? parseJsonObject(row.binding_locator_json)
370
+ : undefined,
371
+ metadata: parseJsonObject(row.binding_metadata_json),
372
+ revision: row.binding_revision ?? 1,
373
+ createdAt: row.binding_created_at ?? row.created_at,
374
+ updatedAt: row.binding_updated_at ?? row.updated_at,
375
+ };
376
+ }
377
+ function isRuntimeBindingState(value) {
378
+ return value === "unbound" || value === "bound" || value === "missing" || value === "error";
379
+ }
271
380
  function parseJsonObject(json) {
272
381
  if (!json)
273
382
  return {};
@@ -0,0 +1,107 @@
1
+ import { DEFAULT_AGENT_RUNTIME_INSTANCE_ID } from "../core/profiles.js";
2
+ export class RuntimeSessionBindingConflictError extends Error {
3
+ piboSessionId;
4
+ expectedRevision;
5
+ actualRevision;
6
+ constructor(piboSessionId, expectedRevision, actualRevision) {
7
+ super(`Runtime binding for Pibo session "${piboSessionId}" changed concurrently (expected revision ${expectedRevision}, actual revision ${actualRevision}).`);
8
+ this.piboSessionId = piboSessionId;
9
+ this.expectedRevision = expectedRevision;
10
+ this.actualRevision = actualRevision;
11
+ this.name = "RuntimeSessionBindingConflictError";
12
+ }
13
+ }
14
+ export class RuntimeSessionBindingTransitionError extends Error {
15
+ piboSessionId;
16
+ constructor(piboSessionId, message) {
17
+ super(`Invalid runtime binding transition for Pibo session "${piboSessionId}": ${message}`);
18
+ this.piboSessionId = piboSessionId;
19
+ this.name = "RuntimeSessionBindingTransitionError";
20
+ }
21
+ }
22
+ export function createInitialRuntimeSessionBinding(piboSessionId, input, now = new Date().toISOString()) {
23
+ if (!input.runtimeInstanceId.trim()) {
24
+ throw new RuntimeSessionBindingTransitionError(piboSessionId, "runtime instance id is required");
25
+ }
26
+ if (!input.adapterId.trim()) {
27
+ throw new RuntimeSessionBindingTransitionError(piboSessionId, "runtime adapter id is required");
28
+ }
29
+ const nativeSessionId = normalizedOptionalString(input.nativeSessionId);
30
+ if ((input.state === "bound" || input.state === "missing") && !nativeSessionId) {
31
+ throw new RuntimeSessionBindingTransitionError(piboSessionId, `${input.state} state requires a native session id`);
32
+ }
33
+ return {
34
+ piboSessionId,
35
+ runtimeInstanceId: input.runtimeInstanceId,
36
+ adapterId: input.adapterId,
37
+ nativeSessionId,
38
+ state: input.state ?? "unbound",
39
+ protocol: normalizedOptionalString(input.protocol),
40
+ protocolVersion: normalizedOptionalString(input.protocolVersion),
41
+ adapterVersion: normalizedOptionalString(input.adapterVersion),
42
+ locator: input.locator ? structuredClone(input.locator) : undefined,
43
+ metadata: input.metadata ? structuredClone(input.metadata) : {},
44
+ revision: 1,
45
+ createdAt: now,
46
+ updatedAt: now,
47
+ };
48
+ }
49
+ export function createLegacyPiRuntimeSessionBinding(piboSessionId, piSessionId, now = new Date().toISOString()) {
50
+ const nativeSessionId = normalizedOptionalString(piSessionId);
51
+ return createInitialRuntimeSessionBinding(piboSessionId, {
52
+ runtimeInstanceId: DEFAULT_AGENT_RUNTIME_INSTANCE_ID,
53
+ adapterId: "pi",
54
+ nativeSessionId,
55
+ state: nativeSessionId ? "bound" : "unbound",
56
+ protocol: "pi-sdk",
57
+ }, now);
58
+ }
59
+ export function nextRuntimeSessionBinding(current, next, options = {}, now = new Date().toISOString()) {
60
+ const currentRevision = current.revision ?? 1;
61
+ if (options.expectedRevision !== undefined && currentRevision !== options.expectedRevision) {
62
+ throw new RuntimeSessionBindingConflictError(current.piboSessionId, options.expectedRevision, currentRevision);
63
+ }
64
+ assertRuntimeSessionBindingTransition(current, next, options);
65
+ return {
66
+ ...structuredClone(next),
67
+ piboSessionId: current.piboSessionId,
68
+ revision: currentRevision + 1,
69
+ createdAt: current.createdAt ?? now,
70
+ updatedAt: now,
71
+ };
72
+ }
73
+ export function assertRuntimeSessionBindingTransition(current, next, options = {}) {
74
+ const mode = options.mode ?? "normal";
75
+ if (!next.runtimeInstanceId.trim()) {
76
+ throw new RuntimeSessionBindingTransitionError(current.piboSessionId, "runtime instance id is required");
77
+ }
78
+ if (!next.adapterId.trim()) {
79
+ throw new RuntimeSessionBindingTransitionError(current.piboSessionId, "runtime adapter id is required");
80
+ }
81
+ if ((current.runtimeInstanceId !== next.runtimeInstanceId || current.adapterId !== next.adapterId)
82
+ && mode !== "rebind") {
83
+ throw new RuntimeSessionBindingTransitionError(current.piboSessionId, "changing the runtime instance or adapter requires rebind mode");
84
+ }
85
+ if (current.state === "unbound" && next.state === "bound" && options.expectedRevision === undefined) {
86
+ throw new RuntimeSessionBindingTransitionError(current.piboSessionId, "unbound to bound requires an expected revision");
87
+ }
88
+ if ((current.state === "missing" || current.state === "error") && next.state === "bound" && mode === "normal") {
89
+ throw new RuntimeSessionBindingTransitionError(current.piboSessionId, `${current.state} to bound requires repair or rebind mode`);
90
+ }
91
+ if (current.state === "bound" && next.state === "unbound" && mode !== "rebind") {
92
+ throw new RuntimeSessionBindingTransitionError(current.piboSessionId, "bound to unbound requires rebind mode");
93
+ }
94
+ if (current.state === "bound"
95
+ && next.state === "bound"
96
+ && current.nativeSessionId !== next.nativeSessionId
97
+ && mode !== "rebind") {
98
+ throw new RuntimeSessionBindingTransitionError(current.piboSessionId, "changing a bound native session id requires rebind mode");
99
+ }
100
+ if ((next.state === "bound" || next.state === "missing") && !normalizedOptionalString(next.nativeSessionId)) {
101
+ throw new RuntimeSessionBindingTransitionError(current.piboSessionId, `${next.state} state requires a native session id`);
102
+ }
103
+ }
104
+ function normalizedOptionalString(value) {
105
+ const normalized = value?.trim();
106
+ return normalized ? normalized : undefined;
107
+ }