@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
@@ -3,6 +3,26 @@ import { dirname, resolve } from "node:path";
3
3
  import { piboHomePath } from "../core/pibo-home.js";
4
4
  import { DatabaseSync } from "node:sqlite";
5
5
  import { createPiboSession, matchesFindInput, } from "./store.js";
6
+ import { createLegacyPiRuntimeSessionBinding, nextRuntimeSessionBinding, RuntimeSessionBindingConflictError, } from "./runtime-binding.js";
7
+ const SESSION_SELECT = `
8
+ SELECT
9
+ s.*,
10
+ b.pibo_session_id AS binding_pibo_session_id,
11
+ b.runtime_instance_id AS binding_runtime_instance_id,
12
+ b.runtime_adapter_id AS binding_runtime_adapter_id,
13
+ b.native_session_id AS binding_native_session_id,
14
+ b.binding_state AS binding_state,
15
+ b.protocol AS binding_protocol,
16
+ b.protocol_version AS binding_protocol_version,
17
+ b.adapter_version AS binding_adapter_version,
18
+ b.locator_json AS binding_locator_json,
19
+ b.metadata_json AS binding_metadata_json,
20
+ b.revision AS binding_revision,
21
+ b.created_at AS binding_created_at,
22
+ b.updated_at AS binding_updated_at
23
+ FROM pibo_sessions s
24
+ LEFT JOIN pibo_session_runtime_bindings b ON b.pibo_session_id = s.id
25
+ `;
6
26
  export class SqlitePiboSessionStore {
7
27
  db;
8
28
  constructor(path) {
@@ -12,16 +32,20 @@ export class SqlitePiboSessionStore {
12
32
  }
13
33
  this.db = new DatabaseSync(resolvedPath);
14
34
  this.db.exec("PRAGMA busy_timeout = 5000");
35
+ this.db.exec("PRAGMA foreign_keys = ON");
15
36
  if (resolvedPath !== ":memory:")
16
37
  this.db.exec("PRAGMA journal_mode = WAL");
17
38
  this.applySchema();
18
39
  this.ensureActiveModelColumn();
40
+ this.ensureNullablePiSessionId();
41
+ this.applySchema();
42
+ this.applyRuntimeBindingSchema();
19
43
  }
20
44
  applySchema() {
21
45
  this.db.exec(`
22
46
  CREATE TABLE IF NOT EXISTS pibo_sessions (
23
47
  id TEXT PRIMARY KEY,
24
- pi_session_id TEXT NOT NULL UNIQUE,
48
+ pi_session_id TEXT UNIQUE,
25
49
  channel TEXT NOT NULL,
26
50
  kind TEXT NOT NULL,
27
51
  profile TEXT NOT NULL,
@@ -50,17 +74,124 @@ export class SqlitePiboSessionStore {
50
74
  if (!columns.has("active_model_json"))
51
75
  this.db.exec("ALTER TABLE pibo_sessions ADD COLUMN active_model_json TEXT");
52
76
  }
77
+ ensureNullablePiSessionId() {
78
+ const column = this.db.prepare("PRAGMA table_info(pibo_sessions)").all()
79
+ .find((candidate) => candidate.name === "pi_session_id");
80
+ if (!column || column.notnull === 0)
81
+ return;
82
+ this.db.exec(`
83
+ PRAGMA foreign_keys = OFF;
84
+ BEGIN IMMEDIATE;
85
+ ALTER TABLE pibo_sessions RENAME TO pibo_sessions_before_runtime_bindings;
86
+ CREATE TABLE pibo_sessions (
87
+ id TEXT PRIMARY KEY,
88
+ pi_session_id TEXT UNIQUE,
89
+ channel TEXT NOT NULL,
90
+ kind TEXT NOT NULL,
91
+ profile TEXT NOT NULL,
92
+ parent_id TEXT,
93
+ origin_id TEXT,
94
+ workspace TEXT,
95
+ title TEXT,
96
+ metadata_json TEXT,
97
+ active_model_json TEXT,
98
+ created_at TEXT NOT NULL,
99
+ updated_at TEXT NOT NULL,
100
+ FOREIGN KEY(parent_id) REFERENCES pibo_sessions(id),
101
+ FOREIGN KEY(origin_id) REFERENCES pibo_sessions(id)
102
+ );
103
+ INSERT INTO pibo_sessions (
104
+ id, pi_session_id, channel, kind, profile, parent_id, origin_id,
105
+ workspace, title, metadata_json, active_model_json, created_at, updated_at
106
+ )
107
+ SELECT
108
+ id, pi_session_id, channel, kind, profile, parent_id, origin_id,
109
+ workspace, title, metadata_json, active_model_json, created_at, updated_at
110
+ FROM pibo_sessions_before_runtime_bindings;
111
+ DROP TABLE pibo_sessions_before_runtime_bindings;
112
+ COMMIT;
113
+ PRAGMA foreign_keys = ON;
114
+ `);
115
+ }
116
+ applyRuntimeBindingSchema() {
117
+ this.db.exec(`
118
+ CREATE TABLE IF NOT EXISTS pibo_session_runtime_bindings (
119
+ pibo_session_id TEXT PRIMARY KEY,
120
+ runtime_instance_id TEXT NOT NULL,
121
+ runtime_adapter_id TEXT NOT NULL,
122
+ native_session_id TEXT,
123
+ binding_state TEXT NOT NULL CHECK(binding_state IN ('unbound', 'bound', 'missing', 'error'))
124
+ CHECK(binding_state NOT IN ('bound', 'missing') OR native_session_id IS NOT NULL),
125
+ protocol TEXT,
126
+ protocol_version TEXT,
127
+ adapter_version TEXT,
128
+ locator_json TEXT,
129
+ metadata_json TEXT NOT NULL DEFAULT '{}',
130
+ revision INTEGER NOT NULL DEFAULT 1 CHECK(revision > 0),
131
+ created_at TEXT NOT NULL,
132
+ updated_at TEXT NOT NULL,
133
+ FOREIGN KEY(pibo_session_id) REFERENCES pibo_sessions(id) ON DELETE CASCADE
134
+ );
135
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_pibo_session_runtime_bindings_native
136
+ ON pibo_session_runtime_bindings(runtime_adapter_id, native_session_id)
137
+ WHERE native_session_id IS NOT NULL;
138
+ CREATE INDEX IF NOT EXISTS idx_pibo_session_runtime_bindings_instance_state
139
+ ON pibo_session_runtime_bindings(runtime_instance_id, binding_state, updated_at DESC);
140
+ CREATE TRIGGER IF NOT EXISTS trg_pibo_sessions_runtime_binding_insert
141
+ AFTER INSERT ON pibo_sessions
142
+ WHEN NOT EXISTS (
143
+ SELECT 1 FROM pibo_session_runtime_bindings WHERE pibo_session_id = NEW.id
144
+ )
145
+ BEGIN
146
+ INSERT INTO pibo_session_runtime_bindings (
147
+ pibo_session_id, runtime_instance_id, runtime_adapter_id, native_session_id,
148
+ binding_state, protocol, metadata_json, revision, created_at, updated_at
149
+ ) VALUES (NEW.id, 'pi', 'pi', NULLIF(NEW.pi_session_id, ''), 'unbound', 'pi-sdk', '{}', 1, NEW.created_at, NEW.updated_at);
150
+ END;
151
+ CREATE TRIGGER IF NOT EXISTS trg_pibo_sessions_runtime_binding_pi_update
152
+ AFTER UPDATE OF pi_session_id ON pibo_sessions
153
+ WHEN EXISTS (
154
+ SELECT 1 FROM pibo_session_runtime_bindings
155
+ WHERE pibo_session_id = NEW.id
156
+ AND runtime_adapter_id = 'pi'
157
+ AND COALESCE(native_session_id, '') <> COALESCE(NEW.pi_session_id, '')
158
+ )
159
+ BEGIN
160
+ UPDATE pibo_session_runtime_bindings SET
161
+ native_session_id = NULLIF(NEW.pi_session_id, ''),
162
+ binding_state = CASE WHEN NEW.pi_session_id IS NULL OR NEW.pi_session_id = '' THEN 'unbound' ELSE 'bound' END,
163
+ revision = revision + 1,
164
+ updated_at = NEW.updated_at
165
+ WHERE pibo_session_id = NEW.id AND runtime_adapter_id = 'pi';
166
+ END;
167
+ INSERT OR IGNORE INTO pibo_session_runtime_bindings (
168
+ pibo_session_id, runtime_instance_id, runtime_adapter_id, native_session_id,
169
+ binding_state, protocol, metadata_json, revision, created_at, updated_at
170
+ )
171
+ SELECT
172
+ id, 'pi', 'pi', NULLIF(pi_session_id, ''),
173
+ CASE WHEN pi_session_id IS NULL OR pi_session_id = '' THEN 'unbound' ELSE 'bound' END,
174
+ 'pi-sdk', '{"migrationSource":"legacy-session-store","nativePresenceExpected":false}', 1, created_at, updated_at
175
+ FROM pibo_sessions
176
+ WHERE NOT EXISTS (
177
+ SELECT 1 FROM pibo_session_runtime_bindings existing
178
+ WHERE existing.pibo_session_id = pibo_sessions.id
179
+ );
180
+ `);
181
+ }
53
182
  get(id) {
54
- const row = this.db.prepare("SELECT * FROM pibo_sessions WHERE id = ?").get(id);
183
+ const row = this.db.prepare(`${SESSION_SELECT} WHERE s.id = ?`).get(id);
55
184
  return row ? sessionFromRow(row) : undefined;
56
185
  }
57
186
  list() {
58
- return this.db.prepare("SELECT * FROM pibo_sessions ORDER BY updated_at DESC").all().map(sessionFromRow);
187
+ return this.db.prepare(`${SESSION_SELECT} ORDER BY s.updated_at DESC`).all().map(sessionFromRow);
59
188
  }
60
189
  create(input) {
61
190
  const session = createPiboSession(input);
62
- this.db
63
- .prepare(`
191
+ this.db.exec("BEGIN IMMEDIATE");
192
+ try {
193
+ this.db
194
+ .prepare(`
64
195
  INSERT INTO pibo_sessions (
65
196
  id,
66
197
  pi_session_id,
@@ -77,7 +208,14 @@ export class SqlitePiboSessionStore {
77
208
  updated_at
78
209
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
79
210
  `)
80
- .run(session.id, session.piSessionId, session.channel, session.kind, session.profile, session.parentId ?? null, session.originId ?? null, session.workspace ?? null, session.title ?? null, JSON.stringify(session.metadata ?? {}), session.activeModel ? JSON.stringify(session.activeModel) : null, session.createdAt, session.updatedAt);
211
+ .run(session.id, session.piSessionId || null, session.channel, session.kind, session.profile, session.parentId ?? null, session.originId ?? null, session.workspace ?? null, session.title ?? null, JSON.stringify(session.metadata ?? {}), session.activeModel ? JSON.stringify(session.activeModel) : null, session.createdAt, session.updatedAt);
212
+ this.upsertRuntimeBinding(session.runtimeBinding ?? createLegacyPiRuntimeSessionBinding(session.id, session.piSessionId, session.createdAt));
213
+ this.db.exec("COMMIT");
214
+ }
215
+ catch (error) {
216
+ this.db.exec("ROLLBACK");
217
+ throw error;
218
+ }
81
219
  const created = this.get(session.id);
82
220
  if (!created)
83
221
  throw new Error(`Failed to create Pibo session "${session.id}"`);
@@ -113,7 +251,7 @@ export class SqlitePiboSessionStore {
113
251
  updated_at = ?
114
252
  WHERE id = ?
115
253
  `)
116
- .run(updated.piSessionId, updated.profile, updated.parentId ?? null, updated.originId ?? null, updated.workspace ?? null, updated.title ?? null, JSON.stringify(updated.metadata ?? {}), updated.activeModel ? JSON.stringify(updated.activeModel) : null, updated.updatedAt, id);
254
+ .run(updated.piSessionId || null, updated.profile, updated.parentId ?? null, updated.originId ?? null, updated.workspace ?? null, updated.title ?? null, JSON.stringify(updated.metadata ?? {}), updated.activeModel ? JSON.stringify(updated.activeModel) : null, updated.updatedAt, id);
117
255
  return this.get(id);
118
256
  }
119
257
  delete(id) {
@@ -126,45 +264,102 @@ export class SqlitePiboSessionStore {
126
264
  if (input.ids !== undefined) {
127
265
  if (input.ids.length === 0)
128
266
  return [];
129
- clauses.push(`id IN (${input.ids.map(() => "?").join(", ")})`);
267
+ clauses.push(`s.id IN (${input.ids.map(() => "?").join(", ")})`);
130
268
  values.push(...input.ids);
131
269
  }
132
270
  if (input.channel !== undefined) {
133
- clauses.push("channel = ?");
271
+ clauses.push("s.channel = ?");
134
272
  values.push(input.channel);
135
273
  }
136
274
  if (input.kind !== undefined) {
137
- clauses.push("kind = ?");
275
+ clauses.push("s.kind = ?");
138
276
  values.push(input.kind);
139
277
  }
140
278
  if (input.parentId !== undefined) {
141
279
  if (input.parentId === null)
142
- clauses.push("parent_id IS NULL");
280
+ clauses.push("s.parent_id IS NULL");
143
281
  else {
144
- clauses.push("parent_id = ?");
282
+ clauses.push("s.parent_id = ?");
145
283
  values.push(input.parentId);
146
284
  }
147
285
  }
148
286
  if (input.originId !== undefined) {
149
- clauses.push("origin_id = ?");
287
+ clauses.push("s.origin_id = ?");
150
288
  values.push(input.originId);
151
289
  }
152
290
  if (input.profile !== undefined) {
153
- clauses.push("profile = ?");
291
+ clauses.push("s.profile = ?");
154
292
  values.push(input.profile);
155
293
  }
156
294
  if (input.activeModel !== undefined) {
157
295
  if (input.activeModel === null)
158
- clauses.push("active_model_json IS NULL");
296
+ clauses.push("s.active_model_json IS NULL");
159
297
  else
160
- clauses.push("active_model_json IS NOT NULL");
298
+ clauses.push("s.active_model_json IS NOT NULL");
161
299
  }
162
300
  const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
163
301
  const rows = this.db
164
- .prepare(`SELECT * FROM pibo_sessions ${where} ORDER BY updated_at DESC`)
302
+ .prepare(`${SESSION_SELECT} ${where} ORDER BY s.updated_at DESC`)
165
303
  .all(...values);
166
304
  return rows.map(sessionFromRow).filter((session) => matchesFindInput(session, input));
167
305
  }
306
+ getRuntimeBinding(id) {
307
+ const session = this.get(id);
308
+ return session?.runtimeBinding ? structuredClone(session.runtimeBinding) : undefined;
309
+ }
310
+ updateRuntimeBinding(id, binding, options = {}) {
311
+ this.db.exec("BEGIN IMMEDIATE");
312
+ try {
313
+ const current = this.getRuntimeBinding(id);
314
+ if (!current) {
315
+ this.db.exec("ROLLBACK");
316
+ return undefined;
317
+ }
318
+ const currentRevision = current.revision ?? 1;
319
+ const updated = nextRuntimeSessionBinding(current, { ...structuredClone(binding), piboSessionId: id }, options);
320
+ const result = this.db.prepare(`
321
+ UPDATE pibo_session_runtime_bindings SET
322
+ runtime_instance_id = ?, runtime_adapter_id = ?, native_session_id = ?,
323
+ binding_state = ?, protocol = ?, protocol_version = ?, adapter_version = ?,
324
+ locator_json = ?, metadata_json = ?, revision = ?, updated_at = ?
325
+ WHERE pibo_session_id = ? AND revision = ?
326
+ `).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);
327
+ if (Number(result.changes ?? 0) === 0) {
328
+ const actual = this.getRuntimeBinding(id);
329
+ throw new RuntimeSessionBindingConflictError(id, currentRevision, actual?.revision ?? 0);
330
+ }
331
+ this.db.prepare("UPDATE pibo_sessions SET pi_session_id = ?, updated_at = ? WHERE id = ?").run(updated.adapterId === "pi" ? updated.nativeSessionId ?? null : null, updated.updatedAt, id);
332
+ this.db.exec("COMMIT");
333
+ return this.getRuntimeBinding(id);
334
+ }
335
+ catch (error) {
336
+ if (this.db.isTransaction)
337
+ this.db.exec("ROLLBACK");
338
+ throw error;
339
+ }
340
+ }
341
+ upsertRuntimeBinding(binding) {
342
+ this.db.prepare(`
343
+ INSERT INTO pibo_session_runtime_bindings (
344
+ pibo_session_id, runtime_instance_id, runtime_adapter_id, native_session_id,
345
+ binding_state, protocol, protocol_version, adapter_version, locator_json,
346
+ metadata_json, revision, created_at, updated_at
347
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
348
+ ON CONFLICT(pibo_session_id) DO UPDATE SET
349
+ runtime_instance_id = excluded.runtime_instance_id,
350
+ runtime_adapter_id = excluded.runtime_adapter_id,
351
+ native_session_id = excluded.native_session_id,
352
+ binding_state = excluded.binding_state,
353
+ protocol = excluded.protocol,
354
+ protocol_version = excluded.protocol_version,
355
+ adapter_version = excluded.adapter_version,
356
+ locator_json = excluded.locator_json,
357
+ metadata_json = excluded.metadata_json,
358
+ revision = excluded.revision,
359
+ created_at = excluded.created_at,
360
+ updated_at = excluded.updated_at
361
+ `).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());
362
+ }
168
363
  close() {
169
364
  this.db.close();
170
365
  }
@@ -175,7 +370,8 @@ export function createDefaultPiboSessionStore(_cwd) {
175
370
  function sessionFromRow(row) {
176
371
  return {
177
372
  id: row.id,
178
- piSessionId: row.pi_session_id,
373
+ piSessionId: row.pi_session_id ?? "",
374
+ runtimeBinding: runtimeBindingFromRow(row),
179
375
  channel: row.channel,
180
376
  kind: row.kind,
181
377
  profile: row.profile,
@@ -189,6 +385,31 @@ function sessionFromRow(row) {
189
385
  updatedAt: row.updated_at,
190
386
  };
191
387
  }
388
+ function runtimeBindingFromRow(row) {
389
+ if (!row.binding_runtime_instance_id || !row.binding_runtime_adapter_id || !row.binding_state) {
390
+ return createLegacyPiRuntimeSessionBinding(row.id, row.pi_session_id ?? undefined, row.created_at);
391
+ }
392
+ return {
393
+ piboSessionId: row.binding_pibo_session_id ?? row.id,
394
+ runtimeInstanceId: row.binding_runtime_instance_id,
395
+ adapterId: row.binding_runtime_adapter_id,
396
+ nativeSessionId: row.binding_native_session_id ?? undefined,
397
+ state: isRuntimeBindingState(row.binding_state) ? row.binding_state : "error",
398
+ protocol: row.binding_protocol ?? undefined,
399
+ protocolVersion: row.binding_protocol_version ?? undefined,
400
+ adapterVersion: row.binding_adapter_version ?? undefined,
401
+ locator: row.binding_locator_json
402
+ ? parseMetadata(row.binding_locator_json)
403
+ : undefined,
404
+ metadata: parseMetadata(row.binding_metadata_json),
405
+ revision: row.binding_revision ?? 1,
406
+ createdAt: row.binding_created_at ?? row.created_at,
407
+ updatedAt: row.binding_updated_at ?? row.updated_at,
408
+ };
409
+ }
410
+ function isRuntimeBindingState(value) {
411
+ return value === "unbound" || value === "bound" || value === "missing" || value === "error";
412
+ }
192
413
  function parseMetadata(value) {
193
414
  if (!value)
194
415
  return {};
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { createInitialRuntimeSessionBinding, createLegacyPiRuntimeSessionBinding, nextRuntimeSessionBinding, } from "./runtime-binding.js";
2
3
  export function createPiboSessionId() {
3
4
  return `ps_${randomUUID()}`;
4
5
  }
@@ -6,9 +7,30 @@ export function createPiSessionId() {
6
7
  return randomUUID();
7
8
  }
8
9
  export function createPiboSession(input, now = new Date().toISOString()) {
10
+ const id = input.id ?? createPiboSessionId();
11
+ const requestedBinding = input.runtimeBinding ?? {
12
+ runtimeInstanceId: "pi",
13
+ adapterId: "pi",
14
+ state: "unbound",
15
+ protocol: "pi-sdk",
16
+ };
17
+ let piSessionId = input.piSessionId?.trim() ?? "";
18
+ let nativeSessionId = requestedBinding.nativeSessionId?.trim() || undefined;
19
+ if (requestedBinding.adapterId === "pi") {
20
+ if (piSessionId && nativeSessionId && piSessionId !== nativeSessionId) {
21
+ throw new Error(`Pi session "${piSessionId}" does not match runtime binding native session "${nativeSessionId}"`);
22
+ }
23
+ nativeSessionId = nativeSessionId ?? (piSessionId || createPiSessionId());
24
+ piSessionId = nativeSessionId;
25
+ }
26
+ const runtimeBinding = createInitialRuntimeSessionBinding(id, {
27
+ ...requestedBinding,
28
+ nativeSessionId,
29
+ }, now);
9
30
  return {
10
- id: input.id ?? createPiboSessionId(),
11
- piSessionId: input.piSessionId ?? createPiSessionId(),
31
+ id,
32
+ piSessionId,
33
+ runtimeBinding,
12
34
  channel: input.channel,
13
35
  kind: input.kind,
14
36
  profile: input.profile,
@@ -25,6 +47,7 @@ export function createPiboSession(input, now = new Date().toISOString()) {
25
47
  export class InMemoryPiboSessionStore {
26
48
  byId = new Map();
27
49
  byPiSessionId = new Map();
50
+ byNativeSession = new Map();
28
51
  get(id) {
29
52
  return this.byId.get(id);
30
53
  }
@@ -36,9 +59,10 @@ export class InMemoryPiboSessionStore {
36
59
  if (this.byId.has(session.id)) {
37
60
  throw new Error(`Pibo session "${session.id}" already exists`);
38
61
  }
39
- if (this.byPiSessionId.has(session.piSessionId)) {
62
+ if (session.piSessionId && this.byPiSessionId.has(session.piSessionId)) {
40
63
  throw new Error(`Pi session "${session.piSessionId}" is already attached to a Pibo session`);
41
64
  }
65
+ this.assertNativeSessionAvailable(session.runtimeBinding, session.id);
42
66
  this.set(session);
43
67
  return session;
44
68
  }
@@ -52,9 +76,18 @@ export class InMemoryPiboSessionStore {
52
76
  throw new Error(`Pi session "${input.piSessionId}" is already attached to Pibo session "${attached.id}"`);
53
77
  }
54
78
  }
79
+ let runtimeBinding = existing.runtimeBinding ?? createLegacyPiRuntimeSessionBinding(existing.id, existing.piSessionId, existing.createdAt);
80
+ if (input.piSessionId !== undefined && input.piSessionId !== existing.piSessionId && runtimeBinding.adapterId === "pi") {
81
+ runtimeBinding = nextRuntimeSessionBinding(runtimeBinding, {
82
+ ...runtimeBinding,
83
+ nativeSessionId: input.piSessionId,
84
+ state: input.piSessionId ? "bound" : "unbound",
85
+ }, { mode: "rebind", expectedRevision: runtimeBinding.revision });
86
+ }
55
87
  const updated = {
56
88
  ...existing,
57
89
  piSessionId: input.piSessionId ?? existing.piSessionId,
90
+ runtimeBinding,
58
91
  profile: input.profile ?? existing.profile,
59
92
  parentId: input.parentId === null ? undefined : input.parentId ?? existing.parentId,
60
93
  originId: input.originId === null ? undefined : input.originId ?? existing.originId,
@@ -64,26 +97,69 @@ export class InMemoryPiboSessionStore {
64
97
  activeModel: input.activeModel === null ? undefined : input.activeModel ? { ...input.activeModel } : existing.activeModel,
65
98
  updatedAt: new Date().toISOString(),
66
99
  };
67
- this.set(updated, existing.piSessionId);
100
+ this.assertNativeSessionAvailable(updated.runtimeBinding, id);
101
+ this.set(updated, existing.piSessionId, existing.runtimeBinding);
68
102
  return updated;
69
103
  }
104
+ getRuntimeBinding(id) {
105
+ const session = this.byId.get(id);
106
+ if (!session)
107
+ return undefined;
108
+ return structuredClone(session.runtimeBinding ?? createLegacyPiRuntimeSessionBinding(session.id, session.piSessionId, session.createdAt));
109
+ }
110
+ updateRuntimeBinding(id, binding, options = {}) {
111
+ const existing = this.byId.get(id);
112
+ if (!existing)
113
+ return undefined;
114
+ const current = existing.runtimeBinding ?? createLegacyPiRuntimeSessionBinding(existing.id, existing.piSessionId, existing.createdAt);
115
+ const updatedBinding = nextRuntimeSessionBinding(current, { ...structuredClone(binding), piboSessionId: id }, options);
116
+ this.assertNativeSessionAvailable(updatedBinding, id);
117
+ const updatedSession = {
118
+ ...existing,
119
+ piSessionId: updatedBinding.adapterId === "pi" ? updatedBinding.nativeSessionId ?? "" : "",
120
+ runtimeBinding: updatedBinding,
121
+ updatedAt: updatedBinding.updatedAt,
122
+ };
123
+ this.set(updatedSession, existing.piSessionId, current);
124
+ return structuredClone(updatedBinding);
125
+ }
70
126
  delete(id) {
71
127
  const existing = this.byId.get(id);
72
128
  if (!existing)
73
129
  return false;
74
130
  this.byId.delete(id);
75
- this.byPiSessionId.delete(existing.piSessionId);
131
+ if (existing.piSessionId)
132
+ this.byPiSessionId.delete(existing.piSessionId);
133
+ const nativeKey = runtimeBindingNativeKey(existing.runtimeBinding);
134
+ if (nativeKey)
135
+ this.byNativeSession.delete(nativeKey);
76
136
  return true;
77
137
  }
78
138
  find(input) {
79
139
  return this.sort([...this.byId.values()].filter((session) => matchesFindInput(session, input)));
80
140
  }
81
- set(session, previousPiSessionId) {
141
+ set(session, previousPiSessionId, previousBinding) {
82
142
  this.byId.set(session.id, session);
83
143
  if (previousPiSessionId && previousPiSessionId !== session.piSessionId) {
84
144
  this.byPiSessionId.delete(previousPiSessionId);
85
145
  }
86
- this.byPiSessionId.set(session.piSessionId, session);
146
+ if (session.piSessionId)
147
+ this.byPiSessionId.set(session.piSessionId, session);
148
+ const previousNativeKey = runtimeBindingNativeKey(previousBinding);
149
+ const nativeKey = runtimeBindingNativeKey(session.runtimeBinding);
150
+ if (previousNativeKey && previousNativeKey !== nativeKey)
151
+ this.byNativeSession.delete(previousNativeKey);
152
+ if (nativeKey)
153
+ this.byNativeSession.set(nativeKey, session);
154
+ }
155
+ assertNativeSessionAvailable(binding, piboSessionId) {
156
+ const key = runtimeBindingNativeKey(binding);
157
+ if (!key)
158
+ return;
159
+ const attached = this.byNativeSession.get(key);
160
+ if (attached && attached.id !== piboSessionId) {
161
+ throw new Error(`Native session "${binding?.nativeSessionId}" for adapter "${binding?.adapterId}" is already attached to Pibo session "${attached.id}"`);
162
+ }
87
163
  }
88
164
  sort(sessions) {
89
165
  return sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
@@ -122,6 +198,9 @@ export function matchesFindInput(session, input) {
122
198
  return false;
123
199
  return true;
124
200
  }
201
+ function runtimeBindingNativeKey(binding) {
202
+ return binding?.nativeSessionId ? `${binding.adapterId}\0${binding.nativeSessionId}` : undefined;
203
+ }
125
204
  function metadataMatches(metadata, expected) {
126
205
  const actual = metadata ?? {};
127
206
  for (const [key, value] of Object.entries(expected)) {
@@ -3,26 +3,34 @@ import { applySingleEventToNodes, contentDeltaPatchNodeId, dedupeTraceEvents, ev
3
3
  import { flattenTraceNodes, mapTraceNodesById, nestTraceNodes } from "./trace-nodes.js";
4
4
  import { nestMutableCopiedTraceNodes, shareUnchangedTraceNodes } from "./trace-patch-nodes.js";
5
5
  import { mapTraceChildSessionsByParent, mapTraceSubagentSessionLinks, } from "./trace-subagent-links.js";
6
- import { projectTranscriptEntries, traceNodesFromEntries } from "./trace-transcript.js";
6
+ import { projectHistoryEntries, traceNodesFromHistoryEntries } from "./trace-history.js";
7
7
  export { isRunStartToolNode } from "./trace-async-agent-runs.js";
8
8
  export { assistantMessageNodeId, dedupeTraceEvents, latestTraceStreamId, messageTurnNodeId, thinkingNodeId, traceEventDedupeKey, } from "./trace-event-projection.js";
9
- export { traceNodesFromEntries } from "./trace-transcript.js";
9
+ export { traceNodesFromHistoryEntries } from "./trace-history.js";
10
10
  export { compareTraceNodes, flattenTraceNodes, mapTraceNodesById, nestTraceNodes, sortTraceNodes, } from "./trace-nodes.js";
11
11
  export function buildTraceViewFromEvents(input) {
12
12
  const sessionStatus = input.status ?? "idle";
13
13
  const events = dedupeTraceEvents(input.events);
14
- const allEntries = input.transcriptEntries ?? [];
15
- const openTranscriptEventIds = findOpenTranscriptEventIds(events, sessionStatus);
14
+ const allEntries = input.historyEntries ?? [];
15
+ const openHistoryEventIds = findOpenTranscriptEventIds(events, sessionStatus);
16
16
  const turnTimings = mergeMessageTurnTimings(input.turnTimings ?? [], messageTurnTimingsFromEvents(events));
17
- const entries = projectTranscriptEntries(allEntries, sessionStatus, openTranscriptEventIds, turnTimings);
18
- const nodes = traceNodesFromEntries(input.session.id, entries, turnTimings);
17
+ const entries = projectHistoryEntries(allEntries, sessionStatus, openHistoryEventIds, turnTimings);
18
+ const nodes = traceNodesFromHistoryEntries(input.session.id, entries, turnTimings);
19
19
  reconcileTranscriptUserMessages(nodes, events, turnTimings);
20
20
  const byId = mapTraceNodesById(nodes);
21
21
  const childByParent = mapTraceChildSessionsByParent(input.sessions ?? []);
22
22
  const linkedChildByToolCallId = mapTraceSubagentSessionLinks(events);
23
- const hasPersistedTranscript = entries.some((entry) => entry.type === "message");
23
+ const historyMode = entries.some((entry) => entry.source === "native")
24
+ ? "native"
25
+ : entries.some((entry) => entry.type === "message") ? "product" : "none";
26
+ const historyNodes = flattenTraceNodes(nodes);
27
+ const historyCoverage = {
28
+ mode: historyMode,
29
+ eventIds: new Set(historyNodes.flatMap((node) => node.eventId ? [node.eventId] : [])),
30
+ toolCallIds: new Set(historyNodes.flatMap((node) => node.toolCallId ? [node.toolCallId] : [])),
31
+ };
24
32
  for (const storedEvent of events) {
25
- applySingleEventToNodes(nodes, byId, input.session.id, storedEvent, childByParent, linkedChildByToolCallId, hasPersistedTranscript, openTranscriptEventIds, sessionStatus);
33
+ applySingleEventToNodes(nodes, byId, input.session.id, storedEvent, childByParent, linkedChildByToolCallId, historyCoverage, openHistoryEventIds, sessionStatus);
26
34
  }
27
35
  const nestedNodes = nestTraceNodes(nodes);
28
36
  reconcileAsyncAgentRunStatuses(nestedNodes);
@@ -69,6 +77,7 @@ export function patchTraceViewWithEvents(view, events, sessionStatus) {
69
77
  const childByParent = new Map();
70
78
  const linkedChildByToolCallId = new Map();
71
79
  const openTranscriptEventIds = new Set();
80
+ const emptyHistoryCoverage = { mode: "none", eventIds: new Set(), toolCallIds: new Set() };
72
81
  const appliedEvents = [];
73
82
  let contentDeltaChangedNodeIds = new Set();
74
83
  for (const event of candidateEvents) {
@@ -80,7 +89,7 @@ export function patchTraceViewWithEvents(view, events, sessionStatus) {
80
89
  contentDeltaChangedNodeIds.add(contentDeltaNodeId);
81
90
  else
82
91
  contentDeltaChangedNodeIds = undefined;
83
- applySingleEventToNodes(allNodes, byId, view.piboSessionId, event, childByParent, linkedChildByToolCallId, false, openTranscriptEventIds, sessionStatus);
92
+ applySingleEventToNodes(allNodes, byId, view.piboSessionId, event, childByParent, linkedChildByToolCallId, emptyHistoryCoverage, openTranscriptEventIds, sessionStatus);
84
93
  }
85
94
  if (!appliedEvents.length)
86
95
  return view;