@pasko70/pibo 3.0.1 → 3.0.2

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 (77) hide show
  1. package/README.md +20 -6
  2. package/dist/agent-runtime/context-build.js +26 -2
  3. package/dist/agent-runtime/routed-session.js +108 -9
  4. package/dist/agent-runtimes/codex-native/adapter.js +12 -0
  5. package/dist/agent-runtimes/codex-native/resource-delivery.js +35 -7
  6. package/dist/agent-runtimes/omp/adapter.js +3 -7
  7. package/dist/agent-runtimes/pi/adapter.js +5 -3
  8. package/dist/agent-runtimes/pi/routed-session.js +49 -8
  9. package/dist/agent-runtimes/pi/runtime.js +6 -0
  10. package/dist/apps/chat/agent-profiles.js +31 -2
  11. package/dist/apps/chat/agent-store.js +43 -3
  12. package/dist/apps/chat/chat-request-normalizers.js +31 -0
  13. package/dist/apps/chat/data/chat-data-mappers.js +61 -11
  14. package/dist/apps/chat/data/session-query-service.js +33 -10
  15. package/dist/apps/chat/data/timeline-query-service.js +60 -35
  16. package/dist/apps/chat/output-compactor.js +144 -21
  17. package/dist/apps/chat/output-event-policy.js +179 -2
  18. package/dist/apps/chat/stream.js +16 -7
  19. package/dist/apps/chat/trace-v2.js +1 -0
  20. package/dist/apps/chat/web-app.js +236 -64
  21. package/dist/apps/chat-ui/assets/{dist-BBDMeU5-.js → dist-B6GZgWSj.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-mqCviI-c.js → dist-BPotHecb.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-JnW4v8UE.js → dist-BzqQKeVO.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-BDIATCQt.js → dist-CvaPTBTN.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{dist-lRiLP9rr.js → dist-paagejNj.js} +1 -1
  26. package/dist/apps/chat-ui/assets/index-6JJV-eic.js +228 -0
  27. package/dist/apps/chat-ui/assets/index-CeL9JPP5.css +1 -0
  28. package/dist/apps/chat-ui/index.html +2 -2
  29. package/dist/apps/chat-vscode-web/assets/{index-Dtw2Z7jz.js → index-U-MSErGa.js} +4 -4
  30. package/dist/apps/chat-vscode-web/index.html +1 -1
  31. package/dist/apps/cli-ui/cliSessionsCommand.js +34 -8
  32. package/dist/cli-session/localSessionSource.js +153 -161
  33. package/dist/cli.js +7 -11
  34. package/dist/core/context-build.js +7 -16
  35. package/dist/core/output-persistence-retry.js +484 -0
  36. package/dist/core/output-render-sequence.js +331 -0
  37. package/dist/core/profiles.js +24 -0
  38. package/dist/core/provider-recovery.js +7 -1
  39. package/dist/core/session-model.js +37 -1
  40. package/dist/core/session-router.js +53 -13
  41. package/dist/data/ingest-service.js +91 -9
  42. package/dist/data/navigation-store.js +5 -5
  43. package/dist/data/schema.js +122 -2
  44. package/dist/data/session-store.js +3 -3
  45. package/dist/debug/index.js +44 -2
  46. package/dist/gateway/web.js +8 -8
  47. package/dist/local/client.js +3 -2
  48. package/dist/plugins/context-files-store.js +30 -0
  49. package/dist/plugins/context-files.js +52 -1
  50. package/dist/plugins/registry.js +2 -0
  51. package/dist/plugins/user-profile-resources.js +22 -0
  52. package/dist/reliability/store.js +246 -37
  53. package/dist/session-ui/terminalRows.js +6 -3
  54. package/dist/sessions/pibo-data-store.js +145 -0
  55. package/dist/sessions/store.js +42 -0
  56. package/dist/setup/cli.js +422 -6
  57. package/dist/setup/installation-profiles.js +464 -0
  58. package/dist/shared/deterministic-digest.js +94 -0
  59. package/dist/shared/trace-engine.js +54 -0
  60. package/dist/shared/trace-event-projection.js +124 -67
  61. package/dist/shared/trace-history.js +47 -12
  62. package/dist/shared/trace-live-reducer.js +23 -4
  63. package/dist/shared/trace-nodes.js +17 -28
  64. package/dist/shared/trace-order.js +41 -5
  65. package/dist/shared/trace-page-merge.js +44 -14
  66. package/dist/shared/trace-patch-nodes.js +2 -0
  67. package/dist/shared/trace-subagent-links.js +4 -1
  68. package/dist/shared/trace-tool-identity.js +15 -8
  69. package/dist/signals/projector.js +28 -6
  70. package/dist/subagents/tool.js +7 -3
  71. package/docs/ops/vscode-extension-release.md +9 -1
  72. package/npm-shrinkwrap.json +2 -2
  73. package/package.json +2 -1
  74. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +0 -228
  75. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +0 -1
  76. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  77. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
@@ -332,6 +332,10 @@ export async function createPiboRuntime(options = {}) {
332
332
  }
333
333
  const resourceLoader = services.resourceLoader;
334
334
  const diagnostics = [
335
+ ...profile.diagnostics.map((diagnostic) => ({
336
+ type: diagnostic.severity,
337
+ message: `[${diagnostic.code}] ${diagnostic.message}`,
338
+ })),
335
339
  ...piPackageOptions.diagnostics,
336
340
  ...services.diagnostics,
337
341
  ...collectResourceDiagnostics(resourceLoader.getSkills().diagnostics),
@@ -416,6 +420,7 @@ export async function inspectPiboProfile(options = {}) {
416
420
  mcpServers: profile.mcpServers,
417
421
  piPackages: profile.piPackages,
418
422
  contextFiles: profile.contextFiles,
423
+ diagnostics: profile.diagnostics,
419
424
  builtinTools: profile.builtinTools,
420
425
  builtinToolNames: profile.builtinToolNames,
421
426
  autoContextFiles: profile.autoContextFiles,
@@ -456,6 +461,7 @@ export async function inspectPiboProfile(options = {}) {
456
461
  runtimeOptions: structuredClone(profile.runtimeOptions),
457
462
  ...(profile.model ? { model: { ...profile.model } } : {}),
458
463
  ...(profile.mainModel ? { mainModel: { ...profile.mainModel } } : {}),
464
+ mainModelFallbacks: profile.mainModelFallbacks.map((model) => ({ ...model })),
459
465
  ...(profile.subagentModel ? { subagentModel: { ...profile.subagentModel } } : {}),
460
466
  ...(profile.thinkingLevel ? { thinkingLevel: profile.thinkingLevel } : {}),
461
467
  ...(profile.mainThinkingLevel ? { mainThinkingLevel: profile.mainThinkingLevel } : {}),
@@ -14,6 +14,14 @@ export function createCustomAgentProfileDefinition(agent, options = {}) {
14
14
  catch (error) {
15
15
  if (!isUnknownSkillError(error, skillName))
16
16
  throw error;
17
+ const message = `Unknown skill "${skillName}" referenced by custom agent "${agent.profileName}"`;
18
+ builder.addDiagnostic({
19
+ severity: "warning",
20
+ code: "custom_agent_unknown_skill",
21
+ message,
22
+ resourceKind: "skill",
23
+ resourceName: skillName,
24
+ });
17
25
  if (shouldWarnMissingReferences)
18
26
  console.warn(`Skipping unknown skill "${skillName}" for custom agent "${agent.profileName}"`);
19
27
  }
@@ -25,12 +33,29 @@ export function createCustomAgentProfileDefinition(agent, options = {}) {
25
33
  catch (error) {
26
34
  if (!isUnknownContextFileError(error, contextFileKey))
27
35
  throw error;
36
+ const message = `Unknown context file "${contextFileKey}" referenced by custom agent "${agent.profileName}"`;
37
+ builder.addDiagnostic({
38
+ severity: "warning",
39
+ code: "custom_agent_unknown_context_file",
40
+ message,
41
+ resourceKind: "context-file",
42
+ resourceName: contextFileKey,
43
+ });
28
44
  if (shouldWarnMissingReferences)
29
45
  console.warn(`Skipping unknown context file "${contextFileKey}" for custom agent "${agent.profileName}"`);
30
46
  }
31
47
  }
32
- for (const toolName of agent.nativeTools)
33
- builder.addTool(context.getTool(toolName));
48
+ for (const toolName of agent.nativeTools) {
49
+ try {
50
+ builder.addTool(context.getTool(toolName));
51
+ }
52
+ catch (error) {
53
+ if (!isUnknownToolError(error, toolName))
54
+ throw error;
55
+ if (shouldWarnMissingReferences)
56
+ console.warn(`Skipping unknown tool "${toolName}" for custom agent "${agent.profileName}"`);
57
+ }
58
+ }
34
59
  for (const subagent of agent.subagents)
35
60
  builder.addSubagent(subagent);
36
61
  return builder.createSession();
@@ -61,6 +86,7 @@ function createCustomAgentBuilder(agent) {
61
86
  .withToolPackages({ runControl: agent.runControl, goalControl: agent.goalControl ?? true });
62
87
  if (agent.mainModel)
63
88
  builder.withMainModel(agent.mainModel);
89
+ builder.withMainModelFallbacks(agent.mainModelFallbacks ?? []);
64
90
  if (agent.subagentModel)
65
91
  builder.withSubagentModel(agent.subagentModel);
66
92
  if (agent.thinkingLevel)
@@ -80,6 +106,9 @@ function createCustomAgentBuilder(agent) {
80
106
  function uniqueAliases(aliases, profileName) {
81
107
  return [...new Set(aliases.filter((alias) => alias && alias !== profileName))];
82
108
  }
109
+ function isUnknownToolError(error, toolName) {
110
+ return error instanceof Error && error.message === `Unknown tool "${toolName}"`;
111
+ }
83
112
  function isUnknownContextFileError(error, contextFileKey) {
84
113
  return error instanceof Error && error.message === `Unknown context file "${contextFileKey}"`;
85
114
  }
@@ -27,6 +27,7 @@ export function previewCustomAgentCreate(input, options = {}) {
27
27
  mcpServers: uniqueStrings(input.mcpServers ?? []),
28
28
  piPackages: sanitizePiPackages(input.piPackages ?? []),
29
29
  mainModel: sanitizeModelProfile(input.mainModel),
30
+ mainModelFallbacks: sanitizeModelFallbacks(input.mainModelFallbacks ?? [], input.mainModel),
30
31
  subagentModel: sanitizeModelProfile(input.subagentModel),
31
32
  thinkingLevel: sanitizeThinkingLevel(input.thinkingLevel),
32
33
  mainThinkingLevel: sanitizeThinkingLevel(input.mainThinkingLevel),
@@ -61,6 +62,7 @@ export function previewCustomAgentUpdate(existing, input, options = {}) {
61
62
  mcpServers: input.mcpServers ? uniqueStrings(input.mcpServers) : existing.mcpServers,
62
63
  piPackages: input.piPackages ? sanitizePiPackages(input.piPackages) : existing.piPackages,
63
64
  mainModel: input.mainModel === undefined ? existing.mainModel : sanitizeModelProfile(input.mainModel),
65
+ mainModelFallbacks: sanitizeModelFallbacks(input.mainModelFallbacks ?? existing.mainModelFallbacks, input.mainModel === undefined ? existing.mainModel : input.mainModel),
64
66
  subagentModel: input.subagentModel === undefined ? existing.subagentModel : sanitizeModelProfile(input.subagentModel),
65
67
  thinkingLevel: input.thinkingLevel === undefined ? existing.thinkingLevel : sanitizeThinkingLevel(input.thinkingLevel),
66
68
  mainThinkingLevel: input.mainThinkingLevel === undefined ? existing.mainThinkingLevel : sanitizeThinkingLevel(input.mainThinkingLevel),
@@ -113,6 +115,7 @@ export class CustomAgentStore {
113
115
  mcp_servers_json TEXT NOT NULL DEFAULT '[]',
114
116
  pi_packages_json TEXT NOT NULL DEFAULT '[]',
115
117
  main_model_json TEXT,
118
+ main_model_fallbacks_json TEXT NOT NULL DEFAULT '[]',
116
119
  subagent_model_json TEXT,
117
120
  thinking_level TEXT,
118
121
  main_thinking_level TEXT,
@@ -140,6 +143,7 @@ export class CustomAgentStore {
140
143
  this.migrateRuntimeColumns();
141
144
  this.migratePiPackagesColumn();
142
145
  this.migrateModelColumns();
146
+ this.migrateModelFallbackColumns();
143
147
  this.migrateThinkingLevelColumn();
144
148
  this.migrateThinkingOptionColumns();
145
149
  this.migrateBuiltinToolNamesColumn();
@@ -230,6 +234,7 @@ export class CustomAgentStore {
230
234
  mcp_servers_json = ?,
231
235
  pi_packages_json = ?,
232
236
  main_model_json = ?,
237
+ main_model_fallbacks_json = ?,
233
238
  subagent_model_json = ?,
234
239
  thinking_level = ?,
235
240
  main_thinking_level = ?,
@@ -245,7 +250,7 @@ export class CustomAgentStore {
245
250
  updated_at = ?
246
251
  WHERE id = ?
247
252
  `)
248
- .run(updated.profileName, updated.displayName, updated.description ?? null, updated.folderId ?? null, updated.runtimeInstanceId, JSON.stringify(updated.runtimeOptions), serializeBoolean(updated.nativeSubagents), JSON.stringify(updated.nativeTools), JSON.stringify(updated.skills), JSON.stringify(updated.contextFiles), JSON.stringify(sanitizeSubagents(updated.subagents)), JSON.stringify(updated.mcpServers), JSON.stringify(updated.piPackages), updated.mainModel ? JSON.stringify(updated.mainModel) : null, updated.subagentModel ? JSON.stringify(updated.subagentModel) : null, updated.thinkingLevel ?? null, updated.mainThinkingLevel ?? null, updated.subagentThinkingLevel ?? null, serializeBoolean(updated.fast), serializeBoolean(updated.mainFast), serializeBoolean(updated.subagentFast), updated.builtinTools, JSON.stringify(updated.builtinToolNames), updated.autoContextFiles ? 1 : 0, updated.runControl ? 1 : 0, updated.goalControl ? 1 : 0, updated.updatedAt, id);
253
+ .run(updated.profileName, updated.displayName, updated.description ?? null, updated.folderId ?? null, updated.runtimeInstanceId, JSON.stringify(updated.runtimeOptions), serializeBoolean(updated.nativeSubagents), JSON.stringify(updated.nativeTools), JSON.stringify(updated.skills), JSON.stringify(updated.contextFiles), JSON.stringify(sanitizeSubagents(updated.subagents)), JSON.stringify(updated.mcpServers), JSON.stringify(updated.piPackages), updated.mainModel ? JSON.stringify(updated.mainModel) : null, JSON.stringify(updated.mainModelFallbacks), updated.subagentModel ? JSON.stringify(updated.subagentModel) : null, updated.thinkingLevel ?? null, updated.mainThinkingLevel ?? null, updated.subagentThinkingLevel ?? null, serializeBoolean(updated.fast), serializeBoolean(updated.mainFast), serializeBoolean(updated.subagentFast), updated.builtinTools, JSON.stringify(updated.builtinToolNames), updated.autoContextFiles ? 1 : 0, updated.runControl ? 1 : 0, updated.goalControl ? 1 : 0, updated.updatedAt, id);
249
254
  return this.get(id);
250
255
  }
251
256
  setArchived(id, archived) {
@@ -286,6 +291,7 @@ export class CustomAgentStore {
286
291
  mcp_servers_json,
287
292
  pi_packages_json,
288
293
  main_model_json,
294
+ main_model_fallbacks_json,
289
295
  subagent_model_json,
290
296
  thinking_level,
291
297
  main_thinking_level,
@@ -301,9 +307,9 @@ export class CustomAgentStore {
301
307
  created_at,
302
308
  updated_at,
303
309
  archived_at
304
- ) VALUES (${Array.from({ length: 30 }, () => "?").join(", ")})
310
+ ) VALUES (${Array.from({ length: 31 }, () => "?").join(", ")})
305
311
  `)
306
- .run(agent.id, agent.profileName, agent.displayName, agent.description ?? null, agent.folderId ?? null, agent.runtimeInstanceId, JSON.stringify(agent.runtimeOptions), serializeBoolean(agent.nativeSubagents), JSON.stringify(agent.nativeTools), JSON.stringify(agent.skills), JSON.stringify(agent.contextFiles), JSON.stringify(sanitizeSubagents(agent.subagents)), JSON.stringify(agent.mcpServers), JSON.stringify(agent.piPackages), agent.mainModel ? JSON.stringify(agent.mainModel) : null, agent.subagentModel ? JSON.stringify(agent.subagentModel) : null, agent.thinkingLevel ?? null, agent.mainThinkingLevel ?? null, agent.subagentThinkingLevel ?? null, serializeBoolean(agent.fast), serializeBoolean(agent.mainFast), serializeBoolean(agent.subagentFast), agent.builtinTools, JSON.stringify(agent.builtinToolNames), agent.autoContextFiles ? 1 : 0, agent.runControl ? 1 : 0, agent.goalControl ? 1 : 0, agent.createdAt, agent.updatedAt, agent.archivedAt ?? null);
312
+ .run(agent.id, agent.profileName, agent.displayName, agent.description ?? null, agent.folderId ?? null, agent.runtimeInstanceId, JSON.stringify(agent.runtimeOptions), serializeBoolean(agent.nativeSubagents), JSON.stringify(agent.nativeTools), JSON.stringify(agent.skills), JSON.stringify(agent.contextFiles), JSON.stringify(sanitizeSubagents(agent.subagents)), JSON.stringify(agent.mcpServers), JSON.stringify(agent.piPackages), agent.mainModel ? JSON.stringify(agent.mainModel) : null, JSON.stringify(agent.mainModelFallbacks), agent.subagentModel ? JSON.stringify(agent.subagentModel) : null, agent.thinkingLevel ?? null, agent.mainThinkingLevel ?? null, agent.subagentThinkingLevel ?? null, serializeBoolean(agent.fast), serializeBoolean(agent.mainFast), serializeBoolean(agent.subagentFast), agent.builtinTools, JSON.stringify(agent.builtinToolNames), agent.autoContextFiles ? 1 : 0, agent.runControl ? 1 : 0, agent.goalControl ? 1 : 0, agent.createdAt, agent.updatedAt, agent.archivedAt ?? null);
307
313
  }
308
314
  requireFolderExists(folderId) {
309
315
  if (folderId === undefined || folderId === null)
@@ -458,6 +464,12 @@ export class CustomAgentStore {
458
464
  this.db.prepare("ALTER TABLE chat_agents ADD COLUMN subagent_model_json TEXT").run();
459
465
  }
460
466
  }
467
+ migrateModelFallbackColumns() {
468
+ const columns = new Set(this.db.prepare("PRAGMA table_info(chat_agents)").all().map((column) => column.name));
469
+ if (!columns.has("main_model_fallbacks_json")) {
470
+ this.db.prepare("ALTER TABLE chat_agents ADD COLUMN main_model_fallbacks_json TEXT NOT NULL DEFAULT '[]'").run();
471
+ }
472
+ }
461
473
  migrateThinkingLevelColumn() {
462
474
  const columns = new Set(this.db.prepare("PRAGMA table_info(chat_agents)").all().map((column) => column.name));
463
475
  if (!columns.has("thinking_level")) {
@@ -649,6 +661,7 @@ function agentFromRow(row, profileAliases) {
649
661
  mcpServers: parseStringArray(row.mcp_servers_json),
650
662
  piPackages: parseStringArray(row.pi_packages_json),
651
663
  mainModel: parseModelProfile(row.main_model_json),
664
+ mainModelFallbacks: parseModelFallbacks(row.main_model_fallbacks_json, row.main_model_json),
652
665
  subagentModel: parseModelProfile(row.subagent_model_json),
653
666
  thinkingLevel: sanitizeThinkingLevel(row.thinking_level),
654
667
  mainThinkingLevel: sanitizeThinkingLevel(row.main_thinking_level),
@@ -774,6 +787,30 @@ function sanitizeModelProfile(value) {
774
787
  return undefined;
775
788
  return { provider, id };
776
789
  }
790
+ function parseModelFallbacks(value, primaryValue) {
791
+ try {
792
+ return sanitizeModelFallbacks(JSON.parse(value), parseModelProfile(primaryValue));
793
+ }
794
+ catch {
795
+ return [];
796
+ }
797
+ }
798
+ function sanitizeModelFallbacks(value, primary) {
799
+ if (!Array.isArray(value))
800
+ return [];
801
+ const primaryModel = sanitizeModelProfile(primary);
802
+ const seen = new Set(primaryModel ? [`${primaryModel.provider}\u0000${primaryModel.id}`] : []);
803
+ return value.flatMap((item) => {
804
+ const model = sanitizeModelProfile(item);
805
+ if (!model)
806
+ return [];
807
+ const key = `${model.provider}\u0000${model.id}`;
808
+ if (seen.has(key))
809
+ return [];
810
+ seen.add(key);
811
+ return [model];
812
+ });
813
+ }
777
814
  function sanitizeSubagents(value) {
778
815
  return value.flatMap((item) => {
779
816
  if (!item || typeof item !== "object" || Array.isArray(item))
@@ -790,6 +827,9 @@ function sanitizeSubagents(value) {
790
827
  const model = sanitizeModelProfile(candidate.model);
791
828
  if (model)
792
829
  subagent.model = model;
830
+ const modelFallbacks = sanitizeModelFallbacks(candidate.modelFallbacks, model);
831
+ if (modelFallbacks.length > 0)
832
+ subagent.modelFallbacks = modelFallbacks;
793
833
  const thinkingLevel = sanitizeThinkingLevel(candidate.thinkingLevel);
794
834
  if (thinkingLevel)
795
835
  subagent.thinkingLevel = thinkingLevel;
@@ -304,6 +304,23 @@ export function normalizeModelProfile(value, fieldName) {
304
304
  }
305
305
  return { provider, id };
306
306
  }
307
+ export function normalizeModelFallbacks(value, fieldName, primary) {
308
+ if (value === undefined || value === null)
309
+ return [];
310
+ if (!Array.isArray(value))
311
+ throw new PiboWebHttpError(`${fieldName} must be an array`, 400);
312
+ const seen = new Set(primary ? [`${primary.provider}\u0000${primary.id}`] : []);
313
+ return value.flatMap((item, index) => {
314
+ const model = normalizeModelProfile(item, `${fieldName}[${index}]`);
315
+ if (!model)
316
+ throw new PiboWebHttpError(`${fieldName}[${index}] must define a provider and model`, 400);
317
+ const key = `${model.provider}\u0000${model.id}`;
318
+ if (seen.has(key))
319
+ return [];
320
+ seen.add(key);
321
+ return [model];
322
+ });
323
+ }
307
324
  export function normalizeMcpServerDescriptionBody(value) {
308
325
  if (typeof value !== "string")
309
326
  throw new PiboWebHttpError("MCP server description must be a string", 400);
@@ -404,6 +421,9 @@ export function normalizeAgentSubagents(value) {
404
421
  const model = normalizeModelProfile(raw.model, "subagent model");
405
422
  if (model)
406
423
  subagent.model = model;
424
+ const modelFallbacks = normalizeModelFallbacks(raw.modelFallbacks, "subagent modelFallbacks", model);
425
+ if (modelFallbacks.length > 0)
426
+ subagent.modelFallbacks = modelFallbacks;
407
427
  const thinkingLevel = normalizeThinkingLevel(raw.thinkingLevel, "subagent thinkingLevel");
408
428
  if (thinkingLevel)
409
429
  subagent.thinkingLevel = thinkingLevel;
@@ -529,6 +549,13 @@ export function normalizeStreamingFixtureTraceSnapshots(value) {
529
549
  return value;
530
550
  throw new PiboWebHttpError("traceSnapshots must be a boolean", 400);
531
551
  }
552
+ export function normalizeStreamingFixtureToolInterleaving(value) {
553
+ if (value === undefined)
554
+ return false;
555
+ if (typeof value === "boolean")
556
+ return value;
557
+ throw new PiboWebHttpError("toolInterleaving must be a boolean", 400);
558
+ }
532
559
  export function normalizeStreamingFixtureSuppressLiveDeltas(value) {
533
560
  if (value === undefined)
534
561
  return false;
@@ -702,6 +729,7 @@ export function createAgentInput(body) {
702
729
  mcpServers: normalizeNameArray(body.mcpServers, "mcpServers"),
703
730
  piPackages: normalizeRegisteredPiPackages(body.piPackages),
704
731
  mainModel: normalizeModelProfile(body.mainModel, "mainModel"),
732
+ mainModelFallbacks: normalizeModelFallbacks(body.mainModelFallbacks, "mainModelFallbacks", normalizeModelProfile(body.mainModel, "mainModel")),
705
733
  subagentModel: normalizeModelProfile(body.subagentModel, "subagentModel"),
706
734
  thinkingLevel: normalizeThinkingLevel(body.thinkingLevel, "thinkingLevel"),
707
735
  mainThinkingLevel: normalizeThinkingLevel(body.mainThinkingLevel, "mainThinkingLevel"),
@@ -748,6 +776,9 @@ export function createAgentUpdate(body) {
748
776
  if (body.mainModel !== undefined) {
749
777
  update.mainModel = body.mainModel === null ? null : normalizeModelProfile(body.mainModel, "mainModel");
750
778
  }
779
+ if (body.mainModelFallbacks !== undefined) {
780
+ update.mainModelFallbacks = normalizeModelFallbacks(body.mainModelFallbacks, "mainModelFallbacks", update.mainModel ?? undefined);
781
+ }
751
782
  if (body.subagentModel !== undefined) {
752
783
  update.subagentModel = body.subagentModel === null ? null : normalizeModelProfile(body.subagentModel, "subagentModel");
753
784
  }
@@ -1,6 +1,7 @@
1
1
  import { normalizeSessionErrorDetails } from "../../../core/session-errors.js";
2
2
  import { roomWorkspaceFromMetadata } from "../types/rooms.js";
3
3
  import { tracePayloadRefForStoredPayload } from "../trace-v2.js";
4
+ import { qualifiedToolNodeId } from "../../../shared/trace-tool-identity.js";
4
5
  const MAX_TRACE_EVENT_HYDRATION_BYTES = 1024 * 1024;
5
6
  export function storedPiboEventFromV2Row(row, payloadStore) {
6
7
  const attributes = parseJsonObject(row.attributes_json);
@@ -16,7 +17,8 @@ export function storedPiboEventFromV2Row(row, payloadStore) {
16
17
  ...payloadIdentity,
17
18
  })
18
19
  : undefined;
19
- return { id: String(row.stream_id), piboSessionId: row.session_id ?? undefined, eventSequence: row.session_sequence ?? undefined, eventId: row.event_id ?? undefined, streamId: row.stream_id, storedPayloadRef, type: row.type, createdAt: row.created_at, payload };
20
+ const renderSequence = numberAttribute(attributes, "renderSequence") ?? payload.renderSequence;
21
+ return { id: String(row.stream_id), piboSessionId: row.session_id ?? undefined, eventSequence: row.session_sequence ?? undefined, renderSequence, eventId: row.event_id ?? undefined, streamId: row.stream_id, storedPayloadRef, type: row.type, createdAt: row.created_at, payload };
20
22
  }
21
23
  export function storedChatEventFromV2Row(row, payloadStore) {
22
24
  const attributes = parseJsonObject(row.attributes_json);
@@ -24,12 +26,22 @@ export function storedChatEventFromV2Row(row, payloadStore) {
24
26
  }
25
27
  function outputPayloadFromV2Row(row, attributes, persistedPayload) {
26
28
  const inlinePayload = attributes.inlinePayload ?? persistedPayload;
27
- if (inlinePayload && typeof inlinePayload === "object" && !Array.isArray(inlinePayload) && typeof inlinePayload.type === "string")
28
- return inlinePayload;
29
+ if (inlinePayload && typeof inlinePayload === "object" && !Array.isArray(inlinePayload) && typeof inlinePayload.type === "string") {
30
+ return compactObject({
31
+ ...inlinePayload,
32
+ renderSequence: numberAttribute(attributes, "renderSequence") ?? inlinePayload.renderSequence,
33
+ });
34
+ }
29
35
  const piboSessionId = row.session_id;
30
36
  if (!piboSessionId)
31
37
  return undefined;
32
- const base = compactObject({ piboSessionId, eventId: row.event_id ?? undefined });
38
+ const eventIdentityScoped = typeof attributes.eventIdentityScoped === "boolean"
39
+ ? attributes.eventIdentityScoped
40
+ : undefined;
41
+ const eventId = eventIdentityScoped === false
42
+ ? undefined
43
+ : stringAttribute(attributes, "semanticEventId") ?? row.event_id ?? undefined;
44
+ const base = compactObject({ piboSessionId, eventId, renderSequence: numberAttribute(attributes, "renderSequence"), toolInvocationOrdinal: numberAttribute(attributes, "toolInvocationOrdinal") });
33
45
  if (row.type === "assistant_message" || row.type === "assistant_delta") {
34
46
  return compactObject({
35
47
  ...base,
@@ -44,9 +56,32 @@ function outputPayloadFromV2Row(row, attributes, persistedPayload) {
44
56
  if (row.type === "message_steered")
45
57
  return { ...base, type: "message_steered", text: stringAttribute(attributes, "inlineText") ?? row.preview_text ?? "", source: stringAttribute(attributes, "source") ?? "user", activeEventId: stringAttribute(attributes, "activeEventId") };
46
58
  if (row.type === "message_started")
47
- return { ...base, type: "message_started", text: row.preview_text ?? "" };
59
+ return compactObject({
60
+ ...base,
61
+ type: "message_started",
62
+ text: stringAttribute(attributes, "inlineText") ?? row.preview_text ?? "",
63
+ source: stringAttribute(attributes, "source"),
64
+ });
48
65
  if (row.type === "message_finished")
49
- return { ...base, type: "message_finished" };
66
+ return compactObject({
67
+ ...base,
68
+ type: "message_finished",
69
+ source: stringAttribute(attributes, "source"),
70
+ });
71
+ if (row.type === "assistant_usage") {
72
+ return compactObject({
73
+ ...base,
74
+ type: "assistant_usage",
75
+ usageIndex: numberAttribute(attributes, "usageIndex"),
76
+ inputTokens: numberAttribute(attributes, "inputTokens"),
77
+ outputTokens: numberAttribute(attributes, "outputTokens"),
78
+ cacheReadTokens: numberAttribute(attributes, "cacheReadTokens"),
79
+ cacheWriteTokens: numberAttribute(attributes, "cacheWriteTokens"),
80
+ reasoningTokens: numberAttribute(attributes, "reasoningTokens"),
81
+ totalTokens: numberAttribute(attributes, "totalTokens") ?? 0,
82
+ costUsd: numberAttribute(attributes, "costUsd"),
83
+ });
84
+ }
50
85
  if (row.type === "thinking_started" || row.type === "thinking_delta" || row.type === "thinking_finished") {
51
86
  return compactObject({
52
87
  ...base,
@@ -74,6 +109,10 @@ function outputPayloadFromV2Row(row, attributes, persistedPayload) {
74
109
  }
75
110
  if (row.type === "execution_result")
76
111
  return { ...base, type: "execution_result", action: row.preview_text ?? stringAttribute(attributes, "action") ?? "execution", result: inlinePayload ?? null };
112
+ if (row.type === "compaction_start")
113
+ return { ...base, type: "compaction_start", compactionIndex: numberAttribute(attributes, "compactionIndex"), reason: stringAttribute(attributes, "reason") ?? row.preview_text ?? "unknown" };
114
+ if (row.type === "compaction_end")
115
+ return { ...base, type: "compaction_end", compactionIndex: numberAttribute(attributes, "compactionIndex"), reason: stringAttribute(attributes, "reason") ?? row.preview_text ?? "unknown", result: inlinePayload, aborted: booleanAttribute(attributes, "aborted") ?? false, errorMessage: stringAttribute(attributes, "errorMessage") };
77
116
  if (row.type === "session_error") {
78
117
  const error = stringAttribute(attributes, "error") ?? row.preview_text ?? "Error";
79
118
  return { ...base, type: "session_error", error, errorDetails: normalizeSessionErrorDetails(error, isRecord(attributes.errorDetails) ? attributes.errorDetails : undefined) };
@@ -100,10 +139,15 @@ function tracePayloadIdentityForEvent(row, attributes) {
100
139
  const toolCallId = stringAttribute(attributes, "toolCallId");
101
140
  if (!toolCallId)
102
141
  return undefined;
142
+ const eventId = stringAttribute(attributes, "semanticEventId") ?? row.event_id;
143
+ if (!eventId)
144
+ return undefined;
145
+ const ordinal = numberAttribute(attributes, "toolInvocationOrdinal") ?? 0;
146
+ const nodeId = qualifiedToolNodeId(toolCallId, eventId, ordinal);
103
147
  if (row.type === "tool_call" || row.type === "tool_execution_started")
104
- return { nodeId: `tool:${toolCallId}`, payloadKind: "input" };
148
+ return { nodeId, payloadKind: "input" };
105
149
  if (row.type === "tool_execution_updated" || row.type === "tool_execution_finished")
106
- return { nodeId: `tool:${toolCallId}`, payloadKind: "output" };
150
+ return { nodeId, payloadKind: "output" };
107
151
  return undefined;
108
152
  }
109
153
  export function sessionFromRow(row) { return { piboSessionId: row.id, piSessionId: row.pi_session_id ?? "", runtimeInstanceId: row.runtime_instance_id ?? undefined, runtimeAdapterId: row.runtime_adapter_id ?? undefined, runtimeBindingState: isRuntimeBindingState(row.binding_state) ? row.binding_state : undefined, nativeSessionId: row.native_session_id ?? undefined, parentId: row.parent_id ?? undefined, profile: row.profile, channel: row.channel, kind: row.kind, createdAt: row.created_at, updatedAt: row.updated_at, lastActivityAt: row.last_activity_at, status: row.status === "running" || row.status === "error" ? row.status : "idle" }; }
@@ -124,9 +168,15 @@ export function compactObject(value) { return Object.fromEntries(Object.entries(
124
168
  export function previewForPayload(payload) { if (typeof payload === "object" && payload && "text" in payload && typeof payload.text === "string")
125
169
  return payload.text.slice(0, 512); if (typeof payload === "string")
126
170
  return payload.slice(0, 512); return undefined; }
127
- export function statusFromOutputEvent(event) { if (event.type === "session_error")
128
- return "error"; if (event.type === "message_finished")
129
- return "idle"; return "running"; }
171
+ export function statusFromOutputEvent(event) {
172
+ if (event.type === "session_error")
173
+ return "error";
174
+ if (event.type === "message_finished")
175
+ return "idle";
176
+ if (event.type === "message_queued" || event.type === "message_steered" || event.type === "message_started")
177
+ return "running";
178
+ return undefined;
179
+ }
130
180
  function isRuntimeBindingState(value) { return value === "unbound" || value === "bound" || value === "missing" || value === "error"; }
131
181
  function actorTypeValue(value) { return value === "user" || value === "assistant" || value === "system" || value === "agent" ? value : undefined; }
132
182
  function retentionClassValue(value) { return value === "live_delta" || value === "chat_message" || value === "audit_event" ? value : "trace_event"; }
@@ -6,10 +6,10 @@ export class ChatSessionQueryService {
6
6
  constructor(store) {
7
7
  this.store = store;
8
8
  }
9
- upsertSession(session, status = "idle") {
9
+ upsertSession(session, status = "idle", lastActivityAt = session.updatedAt) {
10
10
  const roomId = chatRoomIdFromMetadata(session.metadata) ?? "room_default";
11
- this.store.sessions.upsertSession({ session, roomId, status, lastActivityAt: session.updatedAt });
12
- this.upsertNavigation(session, roomId, status, session.updatedAt);
11
+ this.store.sessions.upsertSession({ session, roomId, status, lastActivityAt });
12
+ this.upsertNavigation(session, roomId, status, lastActivityAt);
13
13
  }
14
14
  upsertSessionsIfChanged(sessions) {
15
15
  let written = 0;
@@ -24,11 +24,32 @@ export class ChatSessionQueryService {
24
24
  }
25
25
  return { checked: sessions.length, written, skipped };
26
26
  }
27
- recordEvent(event, session) {
28
- if (session)
29
- this.upsertSession(session, statusFromOutputEvent(event));
27
+ recordEvent(event, session, _streamId, createdAt = new Date().toISOString()) {
28
+ if (session) {
29
+ const status = statusFromOutputEvent(event);
30
+ if (status)
31
+ this.upsertSession(session, status, createdAt);
32
+ else
33
+ this.touchSession(session, createdAt);
34
+ }
30
35
  return undefined;
31
36
  }
37
+ touchSession(session, lastActivityAt) {
38
+ const roomId = chatRoomIdFromMetadata(session.metadata) ?? "room_default";
39
+ this.store.sessions.upsertSession({ session, roomId, lastActivityAt });
40
+ this.store.navigation.upsertSession({
41
+ roomId,
42
+ sessionId: session.id,
43
+ rootSessionId: rootSessionId(session),
44
+ parentId: session.parentId,
45
+ originId: session.originId,
46
+ title: session.title || "Untitled Session",
47
+ profile: session.profile,
48
+ lastActivityAt,
49
+ sortKey: lastActivityAt,
50
+ updatedAt: lastActivityAt,
51
+ });
52
+ }
32
53
  listSessions() {
33
54
  const rows = this.store.db.prepare(`
34
55
  SELECT s.*, b.runtime_instance_id, b.runtime_adapter_id, b.native_session_id, b.binding_state
@@ -121,12 +142,14 @@ export class ChatSessionQueryService {
121
142
  && row.status === status
122
143
  && row.metadata_json === metadataJson
123
144
  && row.created_at === session.createdAt
124
- && row.updated_at === session.updatedAt
125
- && row.last_activity_at === session.updatedAt
145
+ && row.updated_at >= session.updatedAt
146
+ && row.last_activity_at >= session.updatedAt
126
147
  && row.navigation_status === status
127
148
  && row.navigation_title === title
128
- && row.navigation_sort_key === session.updatedAt
129
- && row.navigation_updated_at === session.updatedAt;
149
+ && row.navigation_sort_key !== null
150
+ && row.navigation_sort_key >= session.updatedAt
151
+ && row.navigation_updated_at !== null
152
+ && row.navigation_updated_at >= session.updatedAt;
130
153
  }
131
154
  upsertNavigation(session, roomId, status, now) {
132
155
  this.store.navigation.upsertSession({
@@ -99,43 +99,40 @@ export class ChatTimelineQueryService {
99
99
  `).all(...values, TRACE_RECONCILIATION_ENTRY_CAP + 1);
100
100
  if (rows.length > TRACE_RECONCILIATION_ENTRY_CAP)
101
101
  return false;
102
- const lifecycles = new Map();
103
- const invocations = [];
104
- for (const row of rows) {
105
- const lifecycle = lifecycles.get(row.event_id) ?? { nextOrdinal: 0 };
106
- lifecycles.set(row.event_id, lifecycle);
107
- if (row.type === "tool_execution_started") {
108
- if (lifecycle.active)
102
+ if (nodeIdentity.qualifier) {
103
+ let hasPersistedOrdinal = false;
104
+ const explicitlyAttached = rows.some((row) => {
105
+ let attributes;
106
+ try {
107
+ const parsed = JSON.parse(row.attributes_json);
108
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
109
+ return false;
110
+ attributes = parsed;
111
+ }
112
+ catch {
109
113
  return false;
110
- lifecycle.active = {
111
- eventId: row.event_id,
112
- invocationOrdinal: lifecycle.nextOrdinal,
113
- payloadMatches: false,
114
- };
115
- lifecycle.nextOrdinal += 1;
116
- invocations.push(lifecycle.active);
117
- }
118
- else if (!lifecycle.active) {
119
- lifecycle.active = {
120
- eventId: row.event_id,
121
- invocationOrdinal: lifecycle.nextOrdinal,
122
- payloadMatches: false,
123
- };
124
- lifecycle.nextOrdinal += 1;
125
- invocations.push(lifecycle.active);
126
- }
127
- if (row.type !== "tool_execution_started"
128
- && payloadMatchesEventRow(this.store, payload.sha256, input.payloadId, row))
129
- lifecycle.active.payloadMatches = true;
130
- if (row.type === "tool_execution_finished")
131
- lifecycle.active = undefined;
132
- }
133
- if (!nodeIdentity.qualifier) {
134
- return invocations.length === 1 && invocations[0].payloadMatches;
114
+ }
115
+ const ordinal = attributes.toolInvocationOrdinal;
116
+ if (Number.isSafeInteger(ordinal))
117
+ hasPersistedOrdinal = true;
118
+ return Number.isSafeInteger(ordinal)
119
+ && Number(ordinal) === nodeIdentity.qualifier.invocationOrdinal
120
+ && row.event_id === nodeIdentity.qualifier.eventId
121
+ && row.type !== "tool_execution_started"
122
+ && payloadMatchesEventRow(this.store, payload.sha256, input.payloadId, row);
123
+ });
124
+ if (explicitlyAttached || hasPersistedOrdinal)
125
+ return explicitlyAttached;
126
+ if (nodeIdentity.qualifier.invocationOrdinal !== 0)
127
+ return false;
128
+ const inferred = inferLegacyToolInvocations(this.store, rows, payload.sha256, input.payloadId);
129
+ return !inferred.ambiguous
130
+ && inferred.invocations.length === 1
131
+ && inferred.invocations[0].eventId === nodeIdentity.qualifier.eventId
132
+ && inferred.invocations[0].payloadMatches;
135
133
  }
136
- const exact = invocations.filter((invocation) => invocation.eventId === nodeIdentity.qualifier.eventId
137
- && invocation.invocationOrdinal === nodeIdentity.qualifier.invocationOrdinal);
138
- return exact.length === 1 && exact[0].payloadMatches;
134
+ const inferred = inferLegacyToolInvocations(this.store, rows, payload.sha256, input.payloadId);
135
+ return !inferred.ambiguous && inferred.invocations.length === 1 && inferred.invocations[0].payloadMatches;
139
136
  }
140
137
  countEventsByType(input = {}) {
141
138
  const clauses = [];
@@ -199,3 +196,31 @@ function payloadMatchesEventRow(store, payloadSha256, payloadId, row) {
199
196
  return false;
200
197
  return store.payloads.findBySha256(inlineSha256)?.id === payloadId;
201
198
  }
199
+ function inferLegacyToolInvocations(store, rows, payloadSha256, payloadId) {
200
+ const activeByEvent = new Map();
201
+ const invocations = [];
202
+ let ambiguous = false;
203
+ for (const row of rows) {
204
+ let active = activeByEvent.get(row.event_id);
205
+ if (row.type === "tool_execution_started") {
206
+ if (active) {
207
+ ambiguous = true;
208
+ continue;
209
+ }
210
+ active = { eventId: row.event_id, payloadMatches: false };
211
+ activeByEvent.set(row.event_id, active);
212
+ invocations.push(active);
213
+ }
214
+ else if (!active) {
215
+ active = { eventId: row.event_id, payloadMatches: false };
216
+ activeByEvent.set(row.event_id, active);
217
+ invocations.push(active);
218
+ }
219
+ if (row.type !== "tool_execution_started" && payloadMatchesEventRow(store, payloadSha256, payloadId, row)) {
220
+ active.payloadMatches = true;
221
+ }
222
+ if (row.type === "tool_execution_finished")
223
+ activeByEvent.delete(row.event_id);
224
+ }
225
+ return { ambiguous, invocations };
226
+ }