@pasko70/pibo 2.5.2 → 3.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 (100) hide show
  1. package/context/pibo-native-tooling.md +1 -0
  2. package/dist/agent-runtime/contract.js +2 -1
  3. package/dist/agent-runtime/history.js +53 -1
  4. package/dist/agent-runtime/routed-session.js +100 -26
  5. package/dist/agent-runtimes/codex-native/adapter.js +304 -30
  6. package/dist/agent-runtimes/codex-native/client.js +22 -3
  7. package/dist/agent-runtimes/codex-native/first-use.js +294 -0
  8. package/dist/agent-runtimes/codex-native/history.js +26 -7
  9. package/dist/agent-runtimes/codex-native/process.js +14 -0
  10. package/dist/agent-runtimes/codex-native/thread.js +59 -25
  11. package/dist/agent-runtimes/codex-native/turn.js +27 -0
  12. package/dist/agent-runtimes/history-proof.js +8 -0
  13. package/dist/agent-runtimes/omp/adapter.js +61 -14
  14. package/dist/agent-runtimes/omp/history.js +189 -34
  15. package/dist/agent-runtimes/omp/thread.js +82 -36
  16. package/dist/agent-runtimes/pi/adapter.js +30 -3
  17. package/dist/agent-runtimes/pi/history.js +66 -5
  18. package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
  19. package/dist/agent-runtimes/pi/routed-session.js +344 -37
  20. package/dist/apps/chat/chat-api-routes.js +1 -1
  21. package/dist/apps/chat/chat-files.js +130 -9
  22. package/dist/apps/chat/chat-settings-routes.js +27 -2
  23. package/dist/apps/chat/chat-speech.js +100 -0
  24. package/dist/apps/chat/data/chat-data-mappers.js +25 -5
  25. package/dist/apps/chat/data/timeline-query-service.js +89 -1
  26. package/dist/apps/chat/trace-v2.js +210 -8
  27. package/dist/apps/chat/trace.js +3 -0
  28. package/dist/apps/chat/web-app.js +108 -3
  29. package/dist/apps/chat-ui/assets/{dist-DTWJf37A.js → dist-BBDMeU5-.js} +1 -1
  30. package/dist/apps/chat-ui/assets/{dist-CkYWKnvy.js → dist-BDIATCQt.js} +1 -1
  31. package/dist/apps/chat-ui/assets/{dist-DZ8Wpj-H.js → dist-JnW4v8UE.js} +1 -1
  32. package/dist/apps/chat-ui/assets/{dist-Dmblz15N.js → dist-lRiLP9rr.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-DjcwoyNY.js → dist-mqCviI-c.js} +1 -1
  34. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
  35. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
  36. package/dist/apps/chat-ui/index.html +2 -2
  37. package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
  38. package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
  39. package/dist/apps/chat-vscode-web/index.html +2 -2
  40. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  41. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
  42. package/dist/cli.js +49 -5
  43. package/dist/config/config.js +59 -8
  44. package/dist/core/preview-server-settings.js +20 -0
  45. package/dist/core/session-router.js +457 -37
  46. package/dist/core/user-settings.js +10 -0
  47. package/dist/data/payload-store.js +57 -1
  48. package/dist/data/schema.js +18 -1
  49. package/dist/debug/trace.js +5 -0
  50. package/dist/gateway/client.js +368 -65
  51. package/dist/gateway/protocol.js +2 -0
  52. package/dist/gateway/server.js +8 -0
  53. package/dist/gateway/web.js +2 -0
  54. package/dist/index.js +1 -1
  55. package/dist/loops/store.js +6 -4
  56. package/dist/mcp/config.js +89 -17
  57. package/dist/mcp/daemon-client.js +473 -153
  58. package/dist/mcp/daemon.js +401 -37
  59. package/dist/plugins/builtin.js +2 -2
  60. package/dist/plugins/codex-native.js +2 -0
  61. package/dist/plugins/registry.js +259 -1
  62. package/dist/previews/cli.js +345 -0
  63. package/dist/previews/config.js +61 -0
  64. package/dist/previews/manager.js +644 -0
  65. package/dist/previews/network.js +198 -0
  66. package/dist/previews/plugin.js +11 -0
  67. package/dist/previews/process-supervisor.js +38 -0
  68. package/dist/previews/proxy.js +387 -0
  69. package/dist/previews/store.js +750 -0
  70. package/dist/previews/types.js +1 -0
  71. package/dist/previews/web-app.js +446 -0
  72. package/dist/runs/registry.js +22 -0
  73. package/dist/runs/tools.js +46 -7
  74. package/dist/session-ui/terminalRows.js +187 -13
  75. package/dist/sessions/pibo-data-store.js +37 -0
  76. package/dist/sessions/runtime-binding-persistence.js +34 -0
  77. package/dist/sessions/runtime-binding.js +6 -0
  78. package/dist/sessions/sqlite-store.js +37 -0
  79. package/dist/shared/trace-engine.js +12 -64
  80. package/dist/shared/trace-event-projection.js +16 -0
  81. package/dist/shared/trace-history.js +684 -184
  82. package/dist/shared/trace-limits.js +7 -0
  83. package/dist/shared/trace-live-patch.js +61 -0
  84. package/dist/shared/trace-tool-identity.js +43 -0
  85. package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
  86. package/dist/speech/openai-codex.js +407 -0
  87. package/dist/speech/types.js +8 -0
  88. package/dist/subagents/context.js +5 -3
  89. package/dist/subagents/observations.js +94 -6
  90. package/dist/subagents/tool.js +103 -20
  91. package/dist/tools/codex-image-artifacts.js +24 -0
  92. package/dist/tools/codex-image-generation.js +4 -22
  93. package/dist/web/channel.js +88 -4
  94. package/dist/web/http.js +2 -1
  95. package/npm-shrinkwrap.json +2 -2
  96. package/package.json +1 -1
  97. package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
  98. package/dist/apps/chat-ui/assets/index-DsgTm1Aw.js +0 -228
  99. package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
  100. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.2.vsix +0 -0
@@ -1,5 +1,6 @@
1
- import { compareTraceNodes } from "../shared/trace-engine.js";
1
+ import { compareTraceNodes } from "../shared/trace-nodes.js";
2
2
  import { terminalTextValue } from "./terminalValue.js";
3
+ export const MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS = 20;
3
4
  export const COMPACT_TERMINAL_OUTPUT_PREVIEW_LINES = 5;
4
5
  export const COMPACT_TERMINAL_EXPLORING_PREVIEW_LINES = 6;
5
6
  const CODEX_COMPATIBLE_TOOL_LABELS = {
@@ -218,11 +219,25 @@ function reconcileConceptualRowCandidates(candidates) {
218
219
  output: candidate.row.output ?? existing.row.output,
219
220
  error: candidate.row.error ?? existing.row.error,
220
221
  payloadRefs: { ...existing.row.payloadRefs, ...candidate.row.payloadRefs },
222
+ imagePreviews: mergeImagePreviews(existing.row.imagePreviews, candidate.row.imagePreviews),
221
223
  },
222
224
  };
223
225
  }
224
226
  return reconciled;
225
227
  }
228
+ function mergeImagePreviews(existing, candidate) {
229
+ if (!existing?.length)
230
+ return candidate?.slice(0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS);
231
+ if (!candidate?.length)
232
+ return existing.slice(0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS);
233
+ const merged = new Map(existing.map((preview) => [preview.id, preview]));
234
+ for (const preview of candidate) {
235
+ merged.set(preview.id, { ...merged.get(preview.id), ...preview });
236
+ if (merged.size >= MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS)
237
+ break;
238
+ }
239
+ return [...merged.values()];
240
+ }
226
241
  function applyCompletedTurnTiming(candidates, turnById) {
227
242
  for (const turn of turnById.values()) {
228
243
  if (!turn.completedAt)
@@ -343,20 +358,51 @@ function createToolRowCandidate(node, turnId) {
343
358
  }
344
359
  function createWebSearchToolRow(node) {
345
360
  const status = mapStatus(node.status);
361
+ const actionType = webSearchActionType(node);
346
362
  const query = webSearchQuery(node);
363
+ const queries = webSearchQueries(node);
364
+ const url = webSearchUrl(node);
365
+ const pattern = webSearchPattern(node);
366
+ const sources = webSearchSources(node.output);
347
367
  const sourceCount = webSearchSourceCount(node.output);
348
368
  const lines = [
349
369
  {
350
370
  prefix: "bullet",
351
- tokens: [token(webSearchVerb(node.status), toneForStatus(node.status), node.status === "error" ? "bold" : "semibold")],
371
+ tokens: [token(webSearchVerb(node.status, actionType), toneForStatus(node.status), node.status === "error" ? "bold" : "semibold")],
352
372
  },
353
373
  ];
354
374
  if (query) {
355
375
  lines.push({ prefix: "detail", tokens: [token(`query: ${JSON.stringify(query)}`, "cyan")] });
356
376
  }
377
+ else if (queries?.length) {
378
+ lines.push({ prefix: "detail", tokens: [token(`queries: ${JSON.stringify(queries)}`, "cyan")] });
379
+ }
380
+ if (url) {
381
+ lines.push({
382
+ prefix: "detail",
383
+ tokens: [token("page: ", "dim"), webLinkToken(url, `Open web page: ${url}`)],
384
+ });
385
+ }
386
+ if (pattern) {
387
+ lines.push({ prefix: "detail", tokens: [token(`find: ${JSON.stringify(pattern)}`, "cyan")] });
388
+ }
357
389
  if (node.status === "done" && sourceCount !== undefined) {
358
390
  lines.push({ prefix: "detail", tokens: [token(`sources: ${sourceCount}`, "dim")] });
359
391
  }
392
+ const visibleSources = sources.slice(0, 3);
393
+ for (const source of visibleSources) {
394
+ lines.push({
395
+ prefix: "detail",
396
+ tokens: [
397
+ ...(source.title ? [token(`${source.title}: `, "dim")] : []),
398
+ webLinkToken(source.url, `Open source: ${source.title ?? source.url}`),
399
+ ],
400
+ });
401
+ }
402
+ const hiddenSourceCount = Math.max(0, (sourceCount ?? sources.length) - visibleSources.length);
403
+ if (visibleSources.length > 0 && hiddenSourceCount > 0) {
404
+ lines.push({ prefix: "detail", tokens: [token(`+${hiddenSourceCount} more sources`, "dim")] });
405
+ }
360
406
  if (node.status === "error" && node.error) {
361
407
  lines.push({ prefix: "detail", tokens: [token(node.error, "red")] });
362
408
  }
@@ -396,6 +442,7 @@ function createImageToolRow(node, image) {
396
442
  output: image.summary,
397
443
  error: node.error,
398
444
  expandable: node.input !== undefined || image.summary !== undefined || Boolean(node.error),
445
+ imagePreviews: image.previews,
399
446
  };
400
447
  }
401
448
  function createToolResultRowCandidate(node, turnId) {
@@ -889,6 +936,7 @@ function createImageGroup(candidates) {
889
936
  orderStreamFrameIndex: firstRow?.orderStreamFrameIndex,
890
937
  detailItems,
891
938
  expandable: detailItems.some((item) => item.input !== undefined || item.output !== undefined || Boolean(item.error)),
939
+ imagePreviews: detailItems.flatMap((item) => item.imagePreviews ?? []).slice(0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS),
892
940
  previewOmission: omittedDetailCount > 0 ? {
893
941
  source: "details",
894
942
  visibleLineCount: visibleDetailItems.length,
@@ -911,6 +959,7 @@ function detailItemsForGroup(candidates, kind) {
911
959
  payloadRefs: candidate.row.payloadRefs,
912
960
  linkedPiboSessionId: candidate.row.linkedPiboSessionId,
913
961
  previewOmission: candidate.row.previewOmission,
962
+ imagePreviews: candidate.row.imagePreviews,
914
963
  };
915
964
  });
916
965
  }
@@ -925,14 +974,17 @@ function classifyImageTool(node) {
925
974
  : previewPath(args) ?? previewPath(details) ?? previewPath(output);
926
975
  const artifactId = codexOperation ? stringValue(details?.artifactId) : undefined;
927
976
  const query = previewQuery(args);
928
- const images = collectImagePayloads(node.output);
929
- const isImageTool = matchesTool(normalized, ["view_image", "image", "screenshot"]);
930
- if (!images.length && !isImageTool && !codexOperation)
977
+ const images = collectImagePayloads(node.output).slice(0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS);
978
+ const payloadRef = node.payloadRefs?.output;
979
+ const persistedImageCount = Math.min(payloadRef?.imageCount ?? 0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS);
980
+ const isImageTool = matchesTool(normalized, ["view_image", "image", "screenshot"])
981
+ || (normalized === "read" && isPreviewableImagePath(path));
982
+ if (!images.length && !persistedImageCount && !isImageTool && !codexOperation)
931
983
  return undefined;
932
984
  const mimeType = images.map((image) => image.mimeType).find((value) => Boolean(value));
933
- const count = Math.max(1, images.length);
985
+ const count = Math.max(1, images.length, persistedImageCount);
934
986
  const summary = {
935
- type: images.length > 1 ? "images" : images.length === 1 ? "image" : "image_reference",
987
+ type: count > 1 ? "images" : images.length === 1 ? "image" : "image_reference",
936
988
  toolName: codexOperation ? node.title : undefined,
937
989
  operation: codexOperation,
938
990
  path,
@@ -943,10 +995,24 @@ function classifyImageTool(node) {
943
995
  query,
944
996
  mimeType,
945
997
  count: count > 1 ? count : undefined,
946
- detail: images.length ? "Image data hidden in terminal view." : "Image path only; binary data is hidden in terminal view.",
998
+ detail: images.length || persistedImageCount ? "Image data hidden in terminal view." : "Image path only; binary data is hidden in terminal view.",
947
999
  };
948
1000
  const labelTarget = path ?? artifactId ?? query ?? mimeType ?? "image";
949
1001
  const verb = codexOperation ? codexImageVerb(node.status, codexOperation) : imageToolVerb(node.status, count);
1002
+ const previewImages = images.length
1003
+ ? images
1004
+ : Array.from({ length: Math.max(1, persistedImageCount) }, () => ({ mimeType }));
1005
+ const previews = previewImages.map((image, index) => ({
1006
+ id: `${node.id}:image:${index}`,
1007
+ label: previewImages.length > 1 ? `${labelTarget} (${index + 1}/${previewImages.length})` : labelTarget,
1008
+ path,
1009
+ artifactId,
1010
+ payloadRef: payloadRef?.ref,
1011
+ payloadImageIndex: payloadRef ? index : undefined,
1012
+ traceNodeId: payloadRef ? payloadRef.nodeId ?? node.id : undefined,
1013
+ generatedToolCallId: codexOperation ? node.toolCallId : undefined,
1014
+ mimeType: image.mimeType ?? mimeType,
1015
+ }));
950
1016
  return {
951
1017
  count,
952
1018
  path,
@@ -956,6 +1022,7 @@ function classifyImageTool(node) {
956
1022
  verb,
957
1023
  groupable: !codexOperation,
958
1024
  summary,
1025
+ previews,
959
1026
  detail: {
960
1027
  id: node.id,
961
1028
  label: `${verb} ${labelTarget}`,
@@ -963,19 +1030,32 @@ function classifyImageTool(node) {
963
1030
  input: sanitizeImagePayload(node.input),
964
1031
  output: summary,
965
1032
  error: node.error,
1033
+ imagePreviews: previews,
966
1034
  },
967
1035
  };
968
1036
  }
1037
+ function isPreviewableImagePath(path) {
1038
+ if (!path)
1039
+ return false;
1040
+ return /\.(?:png|jpe?g|gif|webp|bmp|avif)$/i.test(path.split(/[?#]/, 1)[0] ?? "");
1041
+ }
969
1042
  function collectImagePayloads(value, depth = 0, seen = new Set()) {
970
- if (depth > 5 || value === undefined || value === null)
1043
+ if (depth > 5 || seen.size >= 1_000 || value === undefined || value === null)
971
1044
  return [];
972
1045
  if (typeof value !== "object")
973
1046
  return [];
974
1047
  if (seen.has(value))
975
1048
  return [];
976
1049
  seen.add(value);
977
- if (Array.isArray(value))
978
- return value.flatMap((item) => collectImagePayloads(item, depth + 1, seen));
1050
+ if (Array.isArray(value)) {
1051
+ const images = [];
1052
+ for (const item of value) {
1053
+ images.push(...collectImagePayloads(item, depth + 1, seen));
1054
+ if (images.length >= MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS)
1055
+ return images.slice(0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS);
1056
+ }
1057
+ return images;
1058
+ }
979
1059
  const record = value;
980
1060
  const type = typeof record.type === "string" ? record.type.toLowerCase() : undefined;
981
1061
  const mimeType = typeof record.mimeType === "string" ? record.mimeType : typeof record.mime_type === "string" ? record.mime_type : undefined;
@@ -994,7 +1074,13 @@ function collectImagePayloads(value, depth = 0, seen = new Set()) {
994
1074
  nested.push(record.result);
995
1075
  if ("output" in record)
996
1076
  nested.push(record.output);
997
- return nested.flatMap((item) => collectImagePayloads(item, depth + 1, seen));
1077
+ const images = [];
1078
+ for (const item of nested) {
1079
+ images.push(...collectImagePayloads(item, depth + 1, seen));
1080
+ if (images.length >= MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS)
1081
+ break;
1082
+ }
1083
+ return images.slice(0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS);
998
1084
  }
999
1085
  function sanitizeImagePayload(value) {
1000
1086
  if (Array.isArray(value))
@@ -1186,18 +1272,84 @@ function isShellToolName(name) {
1186
1272
  function isWebSearchToolName(name) {
1187
1273
  return (name ?? "").trim().toLowerCase() === "web_search";
1188
1274
  }
1189
- function webSearchVerb(status) {
1275
+ function webSearchVerb(status, actionType) {
1276
+ if (actionType === "open_page") {
1277
+ if (status === "running")
1278
+ return "Opening web page";
1279
+ if (status === "error")
1280
+ return "Opening web page failed";
1281
+ return "Opened web page";
1282
+ }
1283
+ if (actionType === "find_in_page") {
1284
+ if (status === "running")
1285
+ return "Finding in web page";
1286
+ if (status === "error")
1287
+ return "Finding in web page failed";
1288
+ return "Found in web page";
1289
+ }
1190
1290
  if (status === "running")
1191
1291
  return "Searching web";
1192
1292
  if (status === "error")
1193
1293
  return "Web search failed";
1194
1294
  return "Searched web";
1195
1295
  }
1296
+ function webSearchActionType(node) {
1297
+ const input = isRecord(node.input) ? node.input : undefined;
1298
+ const output = isRecord(node.output) ? node.output : undefined;
1299
+ return stringValue(input?.actionType) ?? stringValue(output?.actionType);
1300
+ }
1196
1301
  function webSearchQuery(node) {
1197
1302
  const input = isRecord(node.input) ? node.input : undefined;
1198
1303
  const output = isRecord(node.output) ? node.output : undefined;
1199
1304
  return stringValue(input?.query) ?? stringValue(output?.query) ?? stringValue(node.summary);
1200
1305
  }
1306
+ function webSearchQueries(node) {
1307
+ const input = isRecord(node.input) ? node.input : undefined;
1308
+ const output = isRecord(node.output) ? node.output : undefined;
1309
+ for (const value of [input?.queries, output?.queries]) {
1310
+ if (!Array.isArray(value))
1311
+ continue;
1312
+ const queries = [...new Set(value.flatMap((candidate) => {
1313
+ const normalized = stringValue(candidate)?.trim();
1314
+ return normalized ? [normalized] : [];
1315
+ }))];
1316
+ if (queries.length)
1317
+ return queries;
1318
+ }
1319
+ return undefined;
1320
+ }
1321
+ function webSearchUrl(node) {
1322
+ const input = isRecord(node.input) ? node.input : undefined;
1323
+ const output = isRecord(node.output) ? node.output : undefined;
1324
+ return stringValue(input?.url) ?? stringValue(output?.url);
1325
+ }
1326
+ function webSearchPattern(node) {
1327
+ const input = isRecord(node.input) ? node.input : undefined;
1328
+ const output = isRecord(node.output) ? node.output : undefined;
1329
+ return stringValue(input?.pattern) ?? stringValue(output?.pattern);
1330
+ }
1331
+ function webSearchSources(output) {
1332
+ if (!isRecord(output))
1333
+ return [];
1334
+ const candidates = output.sources ?? output.citations ?? output.results;
1335
+ if (!Array.isArray(candidates))
1336
+ return [];
1337
+ const sources = new Map();
1338
+ for (const candidate of candidates) {
1339
+ const record = isRecord(candidate) ? candidate : undefined;
1340
+ const rawUrl = typeof candidate === "string"
1341
+ ? candidate
1342
+ : stringValue(record?.url) ?? stringValue(record?.href) ?? stringValue(record?.link);
1343
+ const url = safeWebHref(rawUrl, true);
1344
+ if (!url)
1345
+ continue;
1346
+ const title = stringValue(record?.title)?.trim() ?? stringValue(record?.name)?.trim();
1347
+ const existing = sources.get(url);
1348
+ if (!existing || (!existing.title && title))
1349
+ sources.set(url, { url, ...(title ? { title } : {}) });
1350
+ }
1351
+ return [...sources.values()];
1352
+ }
1201
1353
  function webSearchSourceCount(output) {
1202
1354
  if (!isRecord(output))
1203
1355
  return undefined;
@@ -1207,6 +1359,28 @@ function webSearchSourceCount(output) {
1207
1359
  const sources = output.sources ?? output.citations ?? output.results;
1208
1360
  return Array.isArray(sources) ? sources.length : undefined;
1209
1361
  }
1362
+ function safeWebHref(value, stripHash = false) {
1363
+ if (!value)
1364
+ return undefined;
1365
+ try {
1366
+ const url = new URL(value.trim());
1367
+ if ((url.protocol !== "https:" && url.protocol !== "http:") || url.username || url.password)
1368
+ return undefined;
1369
+ if (stripHash)
1370
+ url.hash = "";
1371
+ return url.href;
1372
+ }
1373
+ catch {
1374
+ return undefined;
1375
+ }
1376
+ }
1377
+ function webLinkToken(value, ariaLabel) {
1378
+ const href = safeWebHref(value);
1379
+ return {
1380
+ ...token(value, href ? "blue" : "dim"),
1381
+ ...(href ? { href, ariaLabel } : {}),
1382
+ };
1383
+ }
1210
1384
  function shellCommandValue(value) {
1211
1385
  if (!isRecord(value))
1212
1386
  return undefined;
@@ -22,10 +22,12 @@ const SESSION_SELECT = `
22
22
  LEFT JOIN session_runtime_bindings b ON b.pibo_session_id = s.id
23
23
  `;
24
24
  export class PiboDataSessionStore {
25
+ #concreteRuntimeBindingCasIdentity;
25
26
  dataStore;
26
27
  db;
27
28
  ownsDataStore;
28
29
  constructor(dataStore = new PiboDataStore()) {
30
+ this.#concreteRuntimeBindingCasIdentity = new.target === PiboDataSessionStore;
29
31
  if (typeof dataStore === "string") {
30
32
  this.dataStore = new PiboDataStore(dataStore);
31
33
  this.ownsDataStore = true;
@@ -36,6 +38,13 @@ export class PiboDataSessionStore {
36
38
  }
37
39
  this.db = this.dataStore.db;
38
40
  }
41
+ /** @internal Read-only concrete-construction identity; it cannot mint authorization. */
42
+ static hasConcreteRuntimeBindingCasIdentity(store) {
43
+ return typeof store === "object"
44
+ && store !== null
45
+ && #concreteRuntimeBindingCasIdentity in store
46
+ && store.#concreteRuntimeBindingCasIdentity;
47
+ }
39
48
  get(id) {
40
49
  const row = this.db.prepare(`${SESSION_SELECT} WHERE s.id = ? AND s.deleted_at IS NULL`).get(id);
41
50
  return row ? sessionFromRow(row) : undefined;
@@ -285,6 +294,34 @@ export class PiboDataSessionStore {
285
294
  `).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());
286
295
  }
287
296
  }
297
+ const auditedPiboDataGet = PiboDataSessionStore.prototype.get;
298
+ const auditedPiboDataGetRuntimeBinding = PiboDataSessionStore.prototype.getRuntimeBinding;
299
+ const auditedPiboDataRuntimeBindingCas = PiboDataSessionStore.prototype.updateRuntimeBinding;
300
+ const hasConcretePiboDataRuntimeBindingCasIdentity = PiboDataSessionStore.hasConcreteRuntimeBindingCasIdentity;
301
+ /** @internal Resolves only the original CAS of an exact, genuinely constructed built-in store. */
302
+ export function resolvePiboDataRuntimeBindingCas(store) {
303
+ if (!hasConcretePiboDataRuntimeBindingCasIdentity(store)
304
+ || Object.getPrototypeOf(store) !== PiboDataSessionStore.prototype
305
+ || Object.prototype.hasOwnProperty.call(store, "get")
306
+ || Object.prototype.hasOwnProperty.call(store, "getRuntimeBinding")
307
+ || Object.prototype.hasOwnProperty.call(store, "updateRuntimeBinding")
308
+ || PiboDataSessionStore.prototype.get !== auditedPiboDataGet
309
+ || PiboDataSessionStore.prototype.getRuntimeBinding !== auditedPiboDataGetRuntimeBinding
310
+ || PiboDataSessionStore.prototype.updateRuntimeBinding !== auditedPiboDataRuntimeBindingCas)
311
+ return undefined;
312
+ const auditedStore = new Proxy(store, {
313
+ get(target, property) {
314
+ if (property === "get")
315
+ return auditedPiboDataGet;
316
+ if (property === "getRuntimeBinding")
317
+ return auditedPiboDataGetRuntimeBinding;
318
+ if (property === "updateRuntimeBinding")
319
+ return auditedPiboDataRuntimeBindingCas;
320
+ return Reflect.get(target, property, target);
321
+ },
322
+ });
323
+ return auditedPiboDataRuntimeBindingCas.bind(auditedStore);
324
+ }
288
325
  export function createDefaultPiboDataSessionStore() {
289
326
  return new PiboDataSessionStore(new PiboDataStore());
290
327
  }
@@ -0,0 +1,34 @@
1
+ import { resolvePiboDataRuntimeBindingCas } from "./pibo-data-store.js";
2
+ import { resolveSqliteRuntimeBindingCas } from "./sqlite-store.js";
3
+ const runtimeBindingPersistenceCapabilities = new WeakSet();
4
+ function auditedRuntimeBindingCas(store) {
5
+ return (resolvePiboDataRuntimeBindingCas(store) ?? resolveSqliteRuntimeBindingCas(store));
6
+ }
7
+ /**
8
+ * Mints runtime-binding persistence only for exact audited built-in stores.
9
+ * The capability closes over the audited prototype CAS, so mutable instance
10
+ * members and structural look-alikes cannot authorize native first use.
11
+ */
12
+ export function createAgentRuntimeBindingPersistence(store, options) {
13
+ const compareAndSet = auditedRuntimeBindingCas(store);
14
+ if (!compareAndSet)
15
+ return undefined;
16
+ const piboSessionId = options.piboSessionId;
17
+ const onPersisted = options.onPersisted;
18
+ const capability = Object.freeze({
19
+ async compareAndSet(binding, expectedRevision) {
20
+ const updated = compareAndSet(piboSessionId, { ...structuredClone(binding), piboSessionId }, { expectedRevision });
21
+ if (!updated)
22
+ throw new Error(`Pibo session "${piboSessionId}" no longer exists.`);
23
+ onPersisted?.(structuredClone(updated));
24
+ return updated;
25
+ },
26
+ });
27
+ runtimeBindingPersistenceCapabilities.add(capability);
28
+ return capability;
29
+ }
30
+ export function isAgentRuntimeBindingPersistence(value) {
31
+ return typeof value === "object"
32
+ && value !== null
33
+ && runtimeBindingPersistenceCapabilities.has(value);
34
+ }
@@ -1,4 +1,10 @@
1
1
  import { DEFAULT_AGENT_RUNTIME_INSTANCE_ID } from "../core/profiles.js";
2
+ export const PENDING_NATIVE_SESSION_METADATA_KEY = "piboPendingNativeSession";
3
+ export function hasPendingNativeSession(binding) {
4
+ return binding.state === "unbound"
5
+ && Boolean(binding.nativeSessionId)
6
+ && binding.metadata?.[PENDING_NATIVE_SESSION_METADATA_KEY] === true;
7
+ }
2
8
  export class RuntimeSessionBindingConflictError extends Error {
3
9
  piboSessionId;
4
10
  expectedRevision;
@@ -24,8 +24,10 @@ const SESSION_SELECT = `
24
24
  LEFT JOIN pibo_session_runtime_bindings b ON b.pibo_session_id = s.id
25
25
  `;
26
26
  export class SqlitePiboSessionStore {
27
+ #concreteRuntimeBindingCasIdentity;
27
28
  db;
28
29
  constructor(path) {
30
+ this.#concreteRuntimeBindingCasIdentity = new.target === SqlitePiboSessionStore;
29
31
  const resolvedPath = path === ":memory:" ? path : resolve(path);
30
32
  if (resolvedPath !== ":memory:") {
31
33
  mkdirSync(dirname(resolvedPath), { recursive: true });
@@ -41,6 +43,13 @@ export class SqlitePiboSessionStore {
41
43
  this.applySchema();
42
44
  this.applyRuntimeBindingSchema();
43
45
  }
46
+ /** @internal Read-only concrete-construction identity; it cannot mint authorization. */
47
+ static hasConcreteRuntimeBindingCasIdentity(store) {
48
+ return typeof store === "object"
49
+ && store !== null
50
+ && #concreteRuntimeBindingCasIdentity in store
51
+ && store.#concreteRuntimeBindingCasIdentity;
52
+ }
44
53
  applySchema() {
45
54
  this.db.exec(`
46
55
  CREATE TABLE IF NOT EXISTS pibo_sessions (
@@ -364,6 +373,34 @@ export class SqlitePiboSessionStore {
364
373
  this.db.close();
365
374
  }
366
375
  }
376
+ const auditedSqliteGet = SqlitePiboSessionStore.prototype.get;
377
+ const auditedSqliteGetRuntimeBinding = SqlitePiboSessionStore.prototype.getRuntimeBinding;
378
+ const auditedSqliteRuntimeBindingCas = SqlitePiboSessionStore.prototype.updateRuntimeBinding;
379
+ const hasConcreteSqliteRuntimeBindingCasIdentity = SqlitePiboSessionStore.hasConcreteRuntimeBindingCasIdentity;
380
+ /** @internal Resolves only the original CAS of an exact, genuinely constructed built-in store. */
381
+ export function resolveSqliteRuntimeBindingCas(store) {
382
+ if (!hasConcreteSqliteRuntimeBindingCasIdentity(store)
383
+ || Object.getPrototypeOf(store) !== SqlitePiboSessionStore.prototype
384
+ || Object.prototype.hasOwnProperty.call(store, "get")
385
+ || Object.prototype.hasOwnProperty.call(store, "getRuntimeBinding")
386
+ || Object.prototype.hasOwnProperty.call(store, "updateRuntimeBinding")
387
+ || SqlitePiboSessionStore.prototype.get !== auditedSqliteGet
388
+ || SqlitePiboSessionStore.prototype.getRuntimeBinding !== auditedSqliteGetRuntimeBinding
389
+ || SqlitePiboSessionStore.prototype.updateRuntimeBinding !== auditedSqliteRuntimeBindingCas)
390
+ return undefined;
391
+ const auditedStore = new Proxy(store, {
392
+ get(target, property) {
393
+ if (property === "get")
394
+ return auditedSqliteGet;
395
+ if (property === "getRuntimeBinding")
396
+ return auditedSqliteGetRuntimeBinding;
397
+ if (property === "updateRuntimeBinding")
398
+ return auditedSqliteRuntimeBindingCas;
399
+ return Reflect.get(target, property, target);
400
+ },
401
+ });
402
+ return auditedSqliteRuntimeBindingCas.bind(auditedStore);
403
+ }
367
404
  export function createDefaultPiboSessionStore(_cwd) {
368
405
  return new SqlitePiboSessionStore(piboHomePath("pibo-sessions.sqlite"));
369
406
  }
@@ -1,10 +1,11 @@
1
1
  import { reconcileAsyncAgentRunStatuses } from "./trace-async-agent-runs.js";
2
- import { applySingleEventToNodes, contentDeltaPatchNodeId, dedupeTraceEvents, eventsCanAffectAsyncAgentRunStatus, findOpenTranscriptEventIds, isConfirmedUserMessageEcho, latestTraceStreamId, mergeMessageTurnTimings, messageTurnTimingsFromEvents, reconcileTranscriptUserMessages, traceEventDedupeKey, } from "./trace-event-projection.js";
2
+ import { applySingleEventToNodes, dedupeTraceEvents, findOpenTranscriptEventIds, latestTraceStreamId, mergeMessageTurnTimings, messageTurnTimingsFromEvents, reconcileTranscriptUserMessages, } from "./trace-event-projection.js";
3
3
  import { flattenTraceNodes, mapTraceNodesById, nestTraceNodes } from "./trace-nodes.js";
4
- import { nestMutableCopiedTraceNodes, shareUnchangedTraceNodes } from "./trace-patch-nodes.js";
5
4
  import { mapTraceChildSessionsByParent, mapTraceSubagentSessionLinks, } from "./trace-subagent-links.js";
6
5
  import { projectHistoryEntries, traceNodesFromHistoryEntries } from "./trace-history.js";
6
+ import { TRACE_RECONCILIATION_TIMING_CAP } from "./trace-limits.js";
7
7
  export { isRunStartToolNode } from "./trace-async-agent-runs.js";
8
+ export { patchTraceViewWithEvent, patchTraceViewWithEvents } from "./trace-live-patch.js";
8
9
  export { assistantMessageNodeId, dedupeTraceEvents, latestTraceStreamId, messageTurnNodeId, thinkingNodeId, traceEventDedupeKey, } from "./trace-event-projection.js";
9
10
  export { traceNodesFromHistoryEntries } from "./trace-history.js";
10
11
  export { compareTraceNodes, flattenTraceNodes, mapTraceNodesById, nestTraceNodes, sortTraceNodes, } from "./trace-nodes.js";
@@ -13,9 +14,15 @@ export function buildTraceViewFromEvents(input) {
13
14
  const events = dedupeTraceEvents(input.events);
14
15
  const allEntries = input.historyEntries ?? [];
15
16
  const openHistoryEventIds = findOpenTranscriptEventIds(events, sessionStatus);
16
- const turnTimings = mergeMessageTurnTimings(input.turnTimings ?? [], messageTurnTimingsFromEvents(events));
17
- const entries = projectHistoryEntries(allEntries, sessionStatus, openHistoryEventIds, turnTimings);
18
- const nodes = traceNodesFromHistoryEntries(input.session.id, entries, turnTimings);
17
+ const suppliedTurnTimings = input.turnTimings ?? [];
18
+ const eventTurnTimings = messageTurnTimingsFromEvents(events);
19
+ const timingOverflow = suppliedTurnTimings.length + eventTurnTimings.length > TRACE_RECONCILIATION_TIMING_CAP;
20
+ const turnTimings = timingOverflow
21
+ ? []
22
+ : mergeMessageTurnTimings(suppliedTurnTimings, eventTurnTimings);
23
+ const historyTurnTimings = timingOverflow ? [...suppliedTurnTimings, ...eventTurnTimings] : turnTimings;
24
+ const entries = projectHistoryEntries(allEntries, sessionStatus, openHistoryEventIds, historyTurnTimings, input.historyReconciliationProof, input.historyReconciliationAuthoritative);
25
+ const nodes = traceNodesFromHistoryEntries(input.session.id, entries, historyTurnTimings, input.historyReconciliationProof, input.historyReconciliationAuthoritative);
19
26
  reconcileTranscriptUserMessages(nodes, events, turnTimings);
20
27
  const byId = mapTraceNodesById(nodes);
21
28
  const childByParent = mapTraceChildSessionsByParent(input.sessions ?? []);
@@ -46,62 +53,3 @@ export function buildTraceViewFromEvents(input) {
46
53
  : [],
47
54
  };
48
55
  }
49
- // ── transcript helpers ───────────────────────────────────────────
50
- export function patchTraceViewWithEvent(view, event, sessionStatus) {
51
- return patchTraceViewWithEvents(view, [event], sessionStatus);
52
- }
53
- export function patchTraceViewWithEvents(view, events, sessionStatus) {
54
- if (!events.length)
55
- return view;
56
- const seenEventKeys = new Set(view.rawEvents.map((event) => traceEventDedupeKey(event)));
57
- const candidateEvents = [];
58
- for (const event of events) {
59
- const eventKey = traceEventDedupeKey(event);
60
- if (seenEventKeys.has(eventKey))
61
- continue;
62
- seenEventKeys.add(eventKey);
63
- candidateEvents.push(event);
64
- }
65
- if (!candidateEvents.length)
66
- return view;
67
- const previousFlatNodes = flattenTraceNodes(view.nodes);
68
- const allNodes = [];
69
- const byId = new Map();
70
- const previousById = new Map();
71
- for (const previousNode of previousFlatNodes) {
72
- previousById.set(previousNode.id, previousNode);
73
- const nextNode = { ...previousNode, children: [] };
74
- allNodes.push(nextNode);
75
- byId.set(nextNode.id, nextNode);
76
- }
77
- const childByParent = new Map();
78
- const linkedChildByToolCallId = new Map();
79
- const openTranscriptEventIds = new Set();
80
- const emptyHistoryCoverage = { mode: "none", eventIds: new Set(), toolCallIds: new Set() };
81
- const appliedEvents = [];
82
- let contentDeltaChangedNodeIds = new Set();
83
- for (const event of candidateEvents) {
84
- if (isConfirmedUserMessageEcho(allNodes, event))
85
- continue;
86
- appliedEvents.push(event);
87
- const contentDeltaNodeId = contentDeltaPatchNodeId(event.payload);
88
- if (contentDeltaChangedNodeIds && contentDeltaNodeId)
89
- contentDeltaChangedNodeIds.add(contentDeltaNodeId);
90
- else
91
- contentDeltaChangedNodeIds = undefined;
92
- applySingleEventToNodes(allNodes, byId, view.piboSessionId, event, childByParent, linkedChildByToolCallId, emptyHistoryCoverage, openTranscriptEventIds, sessionStatus);
93
- }
94
- if (!appliedEvents.length)
95
- return view;
96
- const nestedNodes = nestMutableCopiedTraceNodes(allNodes);
97
- if (eventsCanAffectAsyncAgentRunStatus(appliedEvents)) {
98
- reconcileAsyncAgentRunStatuses(nestedNodes);
99
- }
100
- const sharedNodes = shareUnchangedTraceNodes(previousById, nestedNodes, contentDeltaChangedNodeIds);
101
- return {
102
- ...view,
103
- rawEvents: view.rawEvents.length ? [...view.rawEvents, ...appliedEvents] : appliedEvents,
104
- nodes: sharedNodes,
105
- latestStreamId: latestTraceStreamId(appliedEvents, view.latestStreamId),
106
- };
107
- }
@@ -43,6 +43,7 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
43
43
  const node = traceNodeFromEvent(piboSessionId, payload, childByParent, linkedChildByToolCallId, sessionStatus, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex, storedEvent.traceSource);
44
44
  if (!node)
45
45
  return;
46
+ applyStoredPayloadRef(node, payload, storedEvent.storedPayloadRef);
46
47
  if (node.type === "user.message" &&
47
48
  node.status === "running" &&
48
49
  !node.parentId &&
@@ -124,6 +125,16 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
124
125
  for (const indexed of flattenTraceNodes([node]))
125
126
  byId.set(indexed.id, indexed);
126
127
  }
128
+ function applyStoredPayloadRef(node, event, storedPayloadRef) {
129
+ if (!storedPayloadRef)
130
+ return;
131
+ if (event.type === "tool_call" || event.type === "tool_execution_started") {
132
+ node.payloadRefs = { ...node.payloadRefs, input: storedPayloadRef };
133
+ }
134
+ if (event.type === "tool_execution_finished" || event.type === "tool_execution_updated") {
135
+ node.payloadRefs = { ...node.payloadRefs, output: storedPayloadRef };
136
+ }
137
+ }
127
138
  function assistantMessageNodeFromEvent(piboSessionId, event, createdAt, eventSequence, streamId, streamFrameIndex, traceSource) {
128
139
  const eventId = typeof event.eventId === "string" ? event.eventId : undefined;
129
140
  const assistantId = assistantEventNodeId(event);
@@ -704,6 +715,7 @@ export function mergeMessageTurnTimings(...groups) {
704
715
  eventId: timing.eventId,
705
716
  userText: timing.userText ?? existing.userText,
706
717
  userMessageType: timing.userMessageType ?? existing.userMessageType,
718
+ activeEventId: timing.activeEventId ?? existing.activeEventId,
707
719
  startedAt: timing.startedAt ?? existing.startedAt,
708
720
  completedAt: timing.completedAt ?? existing.completedAt,
709
721
  durationMs: timing.durationMs ?? existing.durationMs,
@@ -766,6 +778,8 @@ export function messageTurnTimingsFromEvents(events) {
766
778
  else if (event.type === "message_steered") {
767
779
  timing.userText ??= event.text;
768
780
  timing.userMessageType = "message_steered";
781
+ timing.activeEventId ??= event.activeEventId;
782
+ timing.startedAt ??= storedEvent.createdAt;
769
783
  }
770
784
  else if (event.type === "message_started") {
771
785
  timing.userText ??= event.text;
@@ -797,6 +811,7 @@ export function messageTurnTimingsFromEvents(events) {
797
811
  userText: timing.userText,
798
812
  startedAt: timing.startedAt,
799
813
  ...(timing.userMessageType ? { userMessageType: timing.userMessageType } : {}),
814
+ ...(timing.activeEventId ? { activeEventId: timing.activeEventId } : {}),
800
815
  completedAt: timing.completedAt,
801
816
  durationMs: startedAtMs === undefined || completedAtMs === undefined
802
817
  ? undefined
@@ -926,6 +941,7 @@ function mergeToolEvent(target, update) {
926
941
  target.output = update.output ?? target.output;
927
942
  target.error = update.error ?? target.error;
928
943
  target.completedAt = update.completedAt ?? target.completedAt;
944
+ target.payloadRefs = { ...target.payloadRefs, ...update.payloadRefs };
929
945
  target.linkedPiboSessionId = update.linkedPiboSessionId ?? target.linkedPiboSessionId;
930
946
  }
931
947
  function mergeSubagentSessionLink(target, update) {