@pasko70/pibo 1.12.0 → 1.13.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 (59) hide show
  1. package/dist/apps/chat/chat-trace-helpers.js +59 -2
  2. package/dist/apps/chat/data/timeline-query-service.js +1 -1
  3. package/dist/apps/chat/loop-api.js +5 -0
  4. package/dist/apps/chat/web-app.js +16 -8
  5. package/dist/apps/chat-ui/assets/{dist-D1U9ck8z.js → dist-B_8EcoB6.js} +1 -1
  6. package/dist/apps/{context-files-ui/assets/dist-CT-91DFe.js → chat-ui/assets/dist-CELRUR8b.js} +1 -1
  7. package/dist/apps/{context-files-ui/assets/dist-Bdudyq9I.js → chat-ui/assets/dist-CVLKu7NV.js} +1 -1
  8. package/dist/apps/chat-ui/assets/{dist-DYdgxFZX.js → dist-CVmpu9FK.js} +1 -1
  9. package/dist/apps/{context-files-ui/assets/dist-f2E1-leP.js → chat-ui/assets/dist-CnX234jg.js} +1 -1
  10. package/dist/apps/chat-ui/assets/{dist-D4qbF8wy.js → dist-DVPXynJE.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-B2Pozq2c.js → dist-DugRsLaG.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-DxJWicji.js → dist-KOXty1JB.js} +1 -1
  13. package/dist/apps/chat-ui/assets/{dist-Bgv5Fm3v.js → dist-UYBI2F8E.js} +1 -1
  14. package/dist/apps/chat-ui/assets/{dist-la7ZfDGk.js → dist-XmG4qG-A.js} +1 -1
  15. package/dist/apps/chat-ui/assets/index-DhFQ1th-.js +237 -0
  16. package/dist/apps/chat-ui/assets/index-hjygk9Ol.css +1 -0
  17. package/dist/apps/chat-ui/index.html +2 -2
  18. package/dist/apps/chat-vscode-web/assets/index-1wWonouX.js +41 -0
  19. package/dist/apps/chat-vscode-web/index.html +1 -1
  20. package/dist/apps/{chat-ui/assets/dist-Ci4lft6y.js → context-files-ui/assets/dist-B5mpygOZ.js} +1 -1
  21. package/dist/apps/context-files-ui/assets/{dist-BYcsFgKp.js → dist-BFRDpzcv.js} +1 -1
  22. package/dist/apps/context-files-ui/assets/{dist-DcsDivye.js → dist-BLI6sQZG.js} +1 -1
  23. package/dist/apps/{chat-ui/assets/dist-CkLit_da.js → context-files-ui/assets/dist-B_zXPNMb.js} +1 -1
  24. package/dist/apps/context-files-ui/assets/{dist-uFphVGi8.js → dist-CLrlJjPP.js} +1 -1
  25. package/dist/apps/context-files-ui/assets/{dist-BvIW3V94.js → dist-D6pYe6d_.js} +1 -1
  26. package/dist/apps/{chat-ui/assets/dist-DiPvo4rq.js → context-files-ui/assets/dist-D8ppb7gk.js} +1 -1
  27. package/dist/apps/context-files-ui/assets/{dist-sus90AFR.js → dist-DM6yvMZv.js} +1 -1
  28. package/dist/apps/context-files-ui/assets/{dist-DeozI4lc.js → dist-Em3LWmNi.js} +1 -1
  29. package/dist/apps/context-files-ui/assets/{dist-CSLUWBzz.js → dist-SEP88Pg7.js} +1 -1
  30. package/dist/apps/context-files-ui/assets/{index-CQ25ablG.js → index-C7X2UvWe.js} +3 -3
  31. package/dist/apps/context-files-ui/assets/{index-CukPnd6A.css → index-Cs4XQITe.css} +1 -1
  32. package/dist/apps/context-files-ui/index.html +2 -2
  33. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  34. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.13.0.vsix +0 -0
  35. package/dist/core/gateway-resource-guard.js +46 -15
  36. package/dist/core/routed-session.js +40 -1
  37. package/dist/core/runtime.js +2 -0
  38. package/dist/core/session-errors.js +3 -0
  39. package/dist/core/session-router.js +25 -2
  40. package/dist/core/transcript-integrity.js +431 -0
  41. package/dist/data/pibo-store.js +2 -0
  42. package/dist/data/telemetry.js +148 -0
  43. package/dist/debug/index.js +11 -0
  44. package/dist/gateway/server.js +1 -0
  45. package/dist/reliability/store.js +11 -6
  46. package/dist/runs/registry.js +38 -1
  47. package/dist/runs/resource-isolation.js +437 -0
  48. package/dist/runs/tools.js +6 -3
  49. package/dist/sessions/pibo-data-store.js +114 -0
  50. package/dist/shared/trace-engine.js +3 -3
  51. package/dist/shared/trace-event-projection.js +165 -36
  52. package/dist/shared/trace-nodes.js +6 -4
  53. package/dist/shared/trace-page-merge.js +106 -2
  54. package/dist/shared/trace-transcript.js +194 -36
  55. package/package.json +1 -1
  56. package/dist/apps/chat-ui/assets/index-BJoo_GJP.css +0 -1
  57. package/dist/apps/chat-ui/assets/index-CYxPvrxL.js +0 -237
  58. package/dist/apps/chat-vscode-web/assets/index-CFSHKXsQ.js +0 -41
  59. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.12.0.vsix +0 -0
@@ -7,6 +7,7 @@ import { runtimeSessionErrorDetails } from "./session-errors.js";
7
7
  import { createSubagentToolName } from "../subagents/tool.js";
8
8
  import { PiboRunRegistry } from "../runs/registry.js";
9
9
  import { PiboRunExecutionTimeoutError } from "../runs/lifecycle.js";
10
+ import { PiboRunResourceLimitError } from "../runs/resource-isolation.js";
10
11
  import { createPiboSignalRegistry } from "../signals/registry.js";
11
12
  import { createDefaultPiboReliabilityStore } from "../reliability/store.js";
12
13
  import { InMemoryPiboSessionStore, } from "../sessions/store.js";
@@ -86,6 +87,8 @@ function formatRunReminderMessage(notification) {
86
87
  status: run.status,
87
88
  toolName: run.toolName,
88
89
  summary: run.summary,
90
+ resourceLimitReason: run.resources?.limitReason,
91
+ resourceUnit: run.resources?.unitName,
89
92
  })),
90
93
  timedOut: notification.timedOut.map((run) => ({
91
94
  runId: run.runId,
@@ -202,6 +205,17 @@ export class PiboSessionRouter {
202
205
  this.runtimeRegistry = new RuntimeSessionRegistry({ cwd: options.cwd ?? getDefaultPiboWorkspace() });
203
206
  this.runRegistry = new PiboRunRegistry({ store: this.reliabilityStore });
204
207
  this.runRegistry.subscribe((event) => this.projectRunRegistryEvent(event));
208
+ const recoveredRuntimeState = options.recoverInterruptedRuntimeState
209
+ ? this.sessionStore.recoverInterruptedRuntimeState?.({
210
+ recoveredRuns: this.runRegistry.listRecoveredRuns(),
211
+ }) ?? []
212
+ : [];
213
+ for (const recovery of recoveredRuntimeState) {
214
+ const session = this.sessionStore.get(recovery.event.piboSessionId);
215
+ if (session)
216
+ this.signalRegistry.project({ type: "session_created", session });
217
+ this.signalRegistry.project({ type: "pibo_output", event: recovery.event });
218
+ }
205
219
  for (const run of this.runRegistry.listAll({ includeConsumed: true, includeDetached: true })) {
206
220
  this.signalRegistry.project({ type: "run_changed", run, reason: "recovered" });
207
221
  }
@@ -863,8 +877,10 @@ export class PiboSessionRouter {
863
877
  }
864
878
  createRunToolController(parentPiboSessionId) {
865
879
  return {
866
- startToolRun: ({ toolName, params, completionPolicy, retryable, maxAttempts, timeoutMs, serviceWarning, execute }) => {
880
+ startToolRun: ({ toolName, params, completionPolicy, retryable, maxAttempts, timeoutMs, serviceWarning, resources, execute }) => {
867
881
  const admission = this.gatewayWorkAdmission.reserve(`yielded run ${toolName}`);
882
+ if (resources)
883
+ resources.admission = admission.admission;
868
884
  const reminderGeneration = this.runReminderGeneration(parentPiboSessionId);
869
885
  let run;
870
886
  try {
@@ -877,6 +893,7 @@ export class PiboSessionRouter {
877
893
  maxAttempts,
878
894
  timeoutMs,
879
895
  serviceWarning,
896
+ resources,
880
897
  });
881
898
  }
882
899
  catch (error) {
@@ -886,15 +903,21 @@ export class PiboSessionRouter {
886
903
  void (async () => {
887
904
  try {
888
905
  const result = await execute();
906
+ if (resources)
907
+ this.runRegistry.updateResources(run.runId, resources);
889
908
  const completed = this.runRegistry.complete(run.runId, result);
890
909
  if (completed)
891
910
  this.handleTerminalRunReminder(parentPiboSessionId, completed.runId, reminderGeneration);
892
911
  }
893
912
  catch (error) {
894
913
  const message = error instanceof Error ? error.message : String(error);
914
+ if (resources)
915
+ this.runRegistry.updateResources(run.runId, resources);
895
916
  const terminalRun = error instanceof PiboRunExecutionTimeoutError
896
917
  ? this.runRegistry.timeOut(run.runId, message, error.timeoutPhase)
897
- : this.runRegistry.fail(run.runId, message);
918
+ : error instanceof PiboRunResourceLimitError
919
+ ? this.runRegistry.resourceLimit(run.runId, message, error.resources)
920
+ : this.runRegistry.fail(run.runId, message);
898
921
  if (terminalRun)
899
922
  this.handleTerminalRunReminder(parentPiboSessionId, terminalRun.runId, reminderGeneration);
900
923
  }
@@ -0,0 +1,431 @@
1
+ import { sessionEntryToContextMessages, } from "@earendil-works/pi-coding-agent";
2
+ export const PIBO_TRANSCRIPT_INTEGRITY_RESUME_MESSAGE_TYPE = "pibo-transcript-integrity-resume";
3
+ export const PIBO_TRANSCRIPT_INTEGRITY_RESUME_PROMPT = "Continue the interrupted task autonomously from the repaired transcript. Do not rerun completed tools, wait for additional user input, or mention transcript repair unless it affects the result.";
4
+ export const PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE = "pibo-transcript-integrity";
5
+ const installedStates = new WeakMap();
6
+ export class PiboTranscriptIntegrityError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "PiboTranscriptIntegrityError";
10
+ }
11
+ }
12
+ export function validatePiboTranscriptIntegrityMessages(messages) {
13
+ return validateProjectedMessages(messages.map((message) => ({ message })));
14
+ }
15
+ export function installPiboTranscriptIntegrity(session) {
16
+ const existing = installedStates.get(session);
17
+ if (existing)
18
+ return [...existing.reports];
19
+ const manager = session.sessionManager;
20
+ const state = {
21
+ session,
22
+ originalAppendMessage: manager.appendMessage.bind(manager),
23
+ restoredAssistants: new Map(),
24
+ reports: [],
25
+ continuationPending: false,
26
+ continuationInProgress: false,
27
+ };
28
+ installedStates.set(session, state);
29
+ manager.appendMessage = ((message) => appendMessageWithIntegrity(state, message));
30
+ patchProviderBoundary(state);
31
+ patchCompactionBoundaries(state);
32
+ reconcilePiboTranscriptIntegrity(session, "load");
33
+ return [...state.reports];
34
+ }
35
+ export function reconcilePiboTranscriptIntegrity(session, boundary) {
36
+ const state = installedStates.get(session);
37
+ if (!state)
38
+ throw new PiboTranscriptIntegrityError("Transcript integrity is not installed for this session");
39
+ const manager = session.sessionManager;
40
+ const activeEntries = manager.buildContextEntries();
41
+ const projected = projectEntries(activeEntries);
42
+ const [issue] = validateProjectedMessages(projected, manager.getEntries());
43
+ if (!issue)
44
+ return undefined;
45
+ const invalidEntry = issue.entryId ? manager.getEntry(issue.entryId) : undefined;
46
+ if (!invalidEntry) {
47
+ return quarantineRuntimeTranscript(state, boundary, issue);
48
+ }
49
+ const invalidEntryIndex = activeEntries.findIndex((entry) => entry.id === invalidEntry.id);
50
+ const removedEntryIds = invalidEntryIndex >= 0
51
+ ? activeEntries.slice(invalidEntryIndex).map((entry) => entry.id)
52
+ : [invalidEntry.id];
53
+ const resultMessage = isToolResultMessageEntry(invalidEntry) ? invalidEntry.message : undefined;
54
+ const authoritativeAssistant = resultMessage
55
+ ? uniqueAuthoritativeAssistant(manager.getEntries(), issue.toolCallId)
56
+ : undefined;
57
+ let report;
58
+ branchTo(manager, invalidEntry.parentId);
59
+ if (authoritativeAssistant && resultMessage && (issue.relation === "orphan_result" || issue.relation === "wrong_branch_result")) {
60
+ const repairId = manager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, repairMetadata({
61
+ boundary,
62
+ relation: issue.relation,
63
+ action: "restored_pair",
64
+ toolCallIds: assistantToolCalls(authoritativeAssistant.message).map((call) => call.id),
65
+ removedEntryIds,
66
+ restoredEntryIds: [authoritativeAssistant.id, invalidEntry.id],
67
+ phase: "journal_started",
68
+ }));
69
+ const restoredAssistantEntryId = state.originalAppendMessage(authoritativeAssistant.message);
70
+ rememberRestoredAssistant(state, authoritativeAssistant.message, restoredAssistantEntryId);
71
+ const restoredResultEntryId = state.originalAppendMessage(resultMessage);
72
+ manager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, {
73
+ repairId,
74
+ phase: "journal_committed",
75
+ restoredAssistantEntryId,
76
+ restoredResultEntryId,
77
+ });
78
+ report = {
79
+ repairId,
80
+ boundary,
81
+ relation: issue.relation,
82
+ action: "restored_pair",
83
+ toolCallIds: assistantToolCalls(authoritativeAssistant.message).map((call) => call.id),
84
+ removedEntryIds,
85
+ restoredEntryIds: [authoritativeAssistant.id, invalidEntry.id],
86
+ continuationRequired: boundary === "load",
87
+ };
88
+ }
89
+ else {
90
+ const repairId = manager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, repairMetadata({
91
+ boundary,
92
+ relation: issue.relation,
93
+ action: "quarantined_tail",
94
+ toolCallIds: [issue.toolCallId],
95
+ removedEntryIds,
96
+ restoredEntryIds: [],
97
+ phase: "quarantined",
98
+ }));
99
+ report = {
100
+ repairId,
101
+ boundary,
102
+ relation: issue.relation,
103
+ action: "quarantined_tail",
104
+ toolCallIds: [issue.toolCallId],
105
+ removedEntryIds,
106
+ restoredEntryIds: [],
107
+ continuationRequired: boundary === "load",
108
+ };
109
+ }
110
+ syncAgentTranscript(session);
111
+ assertSessionTranscriptValid(session, boundary);
112
+ recordReport(state, report);
113
+ return report;
114
+ }
115
+ export function claimPiboTranscriptIntegrityContinuation(session) {
116
+ const state = installedStates.get(session);
117
+ if (!state || !state.continuationPending || state.continuationInProgress)
118
+ return [];
119
+ const reports = state.reports.filter((report) => report.continuationRequired);
120
+ if (reports.length === 0) {
121
+ state.continuationPending = false;
122
+ return [];
123
+ }
124
+ state.continuationPending = false;
125
+ state.continuationInProgress = true;
126
+ session.sessionManager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, {
127
+ phase: "continuation_started",
128
+ repairIds: reports.map((report) => report.repairId),
129
+ });
130
+ return reports;
131
+ }
132
+ export function settlePiboTranscriptIntegrityContinuation(session, outcome, error) {
133
+ const state = installedStates.get(session);
134
+ if (!state?.continuationInProgress)
135
+ return;
136
+ state.continuationInProgress = false;
137
+ session.sessionManager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, {
138
+ phase: `continuation_${outcome}`,
139
+ ...(error ? { errorClass: error instanceof Error ? error.name : "Error" } : {}),
140
+ });
141
+ }
142
+ function patchProviderBoundary(state) {
143
+ const agent = state.session.agent;
144
+ const previous = agent.transformContext?.bind(agent);
145
+ const repairedProviderMessages = async (signal) => {
146
+ const repaired = state.session.sessionManager.buildSessionContext().messages;
147
+ if (!previous)
148
+ return repaired;
149
+ const transformed = await previous(repaired, signal);
150
+ return validatePiboTranscriptIntegrityMessages(transformed).length === 0 ? transformed : repaired;
151
+ };
152
+ agent.transformContext = async (messages, signal) => {
153
+ const transformed = previous ? await previous(messages, signal) : messages;
154
+ const durableRepair = reconcilePiboTranscriptIntegrity(state.session, "before_provider");
155
+ if (durableRepair)
156
+ return repairedProviderMessages(signal);
157
+ const [runtimeIssue] = validatePiboTranscriptIntegrityMessages(transformed);
158
+ if (!runtimeIssue)
159
+ return transformed;
160
+ quarantineRuntimeTranscript(state, "before_provider", runtimeIssue);
161
+ return repairedProviderMessages(signal);
162
+ };
163
+ }
164
+ function patchCompactionBoundaries(state) {
165
+ const session = state.session;
166
+ const originalCompact = session.compact.bind(session);
167
+ session.compact = (async (...args) => {
168
+ reconcilePiboTranscriptIntegrity(session, "before_compaction");
169
+ const result = await originalCompact(...args);
170
+ reconcilePiboTranscriptIntegrity(session, "after_compaction");
171
+ return result;
172
+ });
173
+ }
174
+ function appendMessageWithIntegrity(state, message) {
175
+ if (isAssistantMessage(message)) {
176
+ const restoredEntryId = restoredAssistantEntryId(state, message);
177
+ return restoredEntryId ?? state.originalAppendMessage(message);
178
+ }
179
+ if (!isToolResultMessage(message))
180
+ return state.originalAppendMessage(message);
181
+ const activeMessages = state.session.sessionManager.buildSessionContext().messages;
182
+ if (activeToolCall(activeMessages, message.toolCallId))
183
+ return state.originalAppendMessage(message);
184
+ const authoritativeAssistant = uniqueAssistantMessage(state.session.agent.state.messages, message.toolCallId);
185
+ if (authoritativeAssistant) {
186
+ const toolCallIds = assistantToolCalls(authoritativeAssistant).map((call) => call.id);
187
+ const repairId = state.session.sessionManager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, repairMetadata({
188
+ boundary: "persistence",
189
+ relation: "orphan_result",
190
+ action: "restored_pair",
191
+ toolCallIds,
192
+ removedEntryIds: [],
193
+ restoredEntryIds: [],
194
+ phase: "journal_started",
195
+ }));
196
+ const assistantEntryId = state.originalAppendMessage(authoritativeAssistant);
197
+ rememberRestoredAssistant(state, authoritativeAssistant, assistantEntryId);
198
+ const resultEntryId = state.originalAppendMessage(message);
199
+ state.session.sessionManager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, {
200
+ repairId,
201
+ phase: "journal_committed",
202
+ assistantEntryId,
203
+ resultEntryId,
204
+ });
205
+ recordReport(state, {
206
+ repairId,
207
+ boundary: "persistence",
208
+ relation: "orphan_result",
209
+ action: "restored_pair",
210
+ toolCallIds,
211
+ removedEntryIds: [],
212
+ restoredEntryIds: [assistantEntryId, resultEntryId],
213
+ continuationRequired: false,
214
+ });
215
+ return resultEntryId;
216
+ }
217
+ const repairId = state.session.sessionManager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, repairMetadata({
218
+ boundary: "persistence",
219
+ relation: "orphan_result",
220
+ action: "quarantined_runtime_result",
221
+ toolCallIds: [message.toolCallId],
222
+ removedEntryIds: [],
223
+ restoredEntryIds: [],
224
+ phase: "quarantined",
225
+ }));
226
+ recordReport(state, {
227
+ repairId,
228
+ boundary: "persistence",
229
+ relation: "orphan_result",
230
+ action: "quarantined_runtime_result",
231
+ toolCallIds: [message.toolCallId],
232
+ removedEntryIds: [],
233
+ restoredEntryIds: [],
234
+ continuationRequired: false,
235
+ });
236
+ syncAgentTranscript(state.session);
237
+ return repairId;
238
+ }
239
+ function quarantineRuntimeTranscript(state, boundary, issue) {
240
+ const repairId = state.session.sessionManager.appendCustomEntry(PIBO_TRANSCRIPT_INTEGRITY_ENTRY_TYPE, repairMetadata({
241
+ boundary,
242
+ relation: issue.relation,
243
+ action: "quarantined_runtime_result",
244
+ toolCallIds: [issue.toolCallId],
245
+ removedEntryIds: [],
246
+ restoredEntryIds: [],
247
+ phase: "quarantined",
248
+ }));
249
+ const report = {
250
+ repairId,
251
+ boundary,
252
+ relation: issue.relation,
253
+ action: "quarantined_runtime_result",
254
+ toolCallIds: [issue.toolCallId],
255
+ removedEntryIds: [],
256
+ restoredEntryIds: [],
257
+ continuationRequired: boundary === "load",
258
+ };
259
+ syncAgentTranscript(state.session);
260
+ recordReport(state, report);
261
+ return report;
262
+ }
263
+ function validateProjectedMessages(projected, allEntries = []) {
264
+ const calls = new Map();
265
+ const results = new Map();
266
+ const issues = [];
267
+ const allCalls = allAssistantCallIds(allEntries);
268
+ for (let messageIndex = 0; messageIndex < projected.length; messageIndex += 1) {
269
+ const { message, entry } = projected[messageIndex];
270
+ if (isAssistantMessage(message)) {
271
+ for (const call of assistantToolCalls(message)) {
272
+ const existing = calls.get(call.id);
273
+ if (existing) {
274
+ issues.push({ relation: "duplicate_call", toolCallId: call.id, toolName: call.name, messageIndex, entryId: entry?.id });
275
+ continue;
276
+ }
277
+ calls.set(call.id, { name: call.name, messageIndex });
278
+ }
279
+ continue;
280
+ }
281
+ if (!isToolResultMessage(message))
282
+ continue;
283
+ if (results.has(message.toolCallId)) {
284
+ issues.push({ relation: "duplicate_result", toolCallId: message.toolCallId, toolName: message.toolName, messageIndex, entryId: entry?.id });
285
+ continue;
286
+ }
287
+ results.set(message.toolCallId, messageIndex);
288
+ const call = calls.get(message.toolCallId);
289
+ if (!call) {
290
+ issues.push({
291
+ relation: allCalls.has(message.toolCallId) ? "wrong_branch_result" : "orphan_result",
292
+ toolCallId: message.toolCallId,
293
+ toolName: message.toolName,
294
+ messageIndex,
295
+ entryId: entry?.id,
296
+ });
297
+ continue;
298
+ }
299
+ if (call.name !== message.toolName) {
300
+ issues.push({
301
+ relation: "tool_name_mismatch",
302
+ toolCallId: message.toolCallId,
303
+ toolName: message.toolName,
304
+ expectedToolName: call.name,
305
+ messageIndex,
306
+ entryId: entry?.id,
307
+ });
308
+ }
309
+ }
310
+ return issues;
311
+ }
312
+ function projectEntries(entries) {
313
+ return entries.flatMap((entry) => sessionEntryToContextMessages(entry).map((message) => ({ entry, message })));
314
+ }
315
+ function allAssistantCallIds(entries) {
316
+ const ids = new Set();
317
+ for (const entry of entries) {
318
+ if (!isAssistantMessageEntry(entry))
319
+ continue;
320
+ for (const call of assistantToolCalls(entry.message))
321
+ ids.add(call.id);
322
+ }
323
+ return ids;
324
+ }
325
+ function uniqueAuthoritativeAssistant(entries, toolCallId) {
326
+ const candidates = entries.filter((entry) => isAssistantMessageEntry(entry) && assistantToolCalls(entry.message).some((call) => call.id === toolCallId));
327
+ return candidates.length === 1 ? candidates[0] : undefined;
328
+ }
329
+ function uniqueAssistantMessage(messages, toolCallId) {
330
+ const candidates = messages.filter((message) => isAssistantMessage(message) && assistantToolCalls(message).some((call) => call.id === toolCallId));
331
+ return candidates.length === 1 ? candidates[0] : undefined;
332
+ }
333
+ function activeToolCall(messages, toolCallId) {
334
+ for (const message of messages) {
335
+ if (!isAssistantMessage(message))
336
+ continue;
337
+ const call = assistantToolCalls(message).find((candidate) => candidate.id === toolCallId);
338
+ if (call)
339
+ return call;
340
+ }
341
+ return undefined;
342
+ }
343
+ function assistantToolCalls(message) {
344
+ if (!isAssistantMessage(message))
345
+ return [];
346
+ return message.content.flatMap((part) => {
347
+ if (!part || typeof part !== "object")
348
+ return [];
349
+ const candidate = part;
350
+ return candidate.type === "toolCall" && typeof candidate.id === "string" && typeof candidate.name === "string"
351
+ ? [{ type: "toolCall", id: candidate.id, name: candidate.name, arguments: candidate.arguments }]
352
+ : [];
353
+ });
354
+ }
355
+ function isAssistantMessage(message) {
356
+ return Boolean(message && typeof message === "object" && message.role === "assistant" && Array.isArray(message.content));
357
+ }
358
+ function isToolResultMessage(message) {
359
+ return Boolean(message
360
+ && typeof message === "object"
361
+ && message.role === "toolResult"
362
+ && typeof message.toolCallId === "string"
363
+ && typeof message.toolName === "string");
364
+ }
365
+ function isAssistantMessageEntry(entry) {
366
+ return entry.type === "message" && isAssistantMessage(entry.message);
367
+ }
368
+ function isToolResultMessageEntry(entry) {
369
+ return entry.type === "message" && isToolResultMessage(entry.message);
370
+ }
371
+ function branchTo(manager, parentId) {
372
+ if (parentId)
373
+ manager.branch(parentId);
374
+ else
375
+ manager.resetLeaf();
376
+ }
377
+ function syncAgentTranscript(session) {
378
+ session.agent.state.messages = session.sessionManager.buildSessionContext().messages;
379
+ }
380
+ function assertSessionTranscriptValid(session, boundary) {
381
+ const issues = validateProjectedMessages(projectEntries(session.sessionManager.buildContextEntries()), session.sessionManager.getEntries());
382
+ if (issues.length === 0)
383
+ return;
384
+ const issue = issues[0];
385
+ throw new PiboTranscriptIntegrityError(`Transcript integrity repair failed at ${boundary}: ${issue.relation} for tool call ${issue.toolCallId}`);
386
+ }
387
+ function rememberRestoredAssistant(state, message, entryId) {
388
+ const fingerprint = assistantFingerprint(message);
389
+ for (const call of assistantToolCalls(message))
390
+ state.restoredAssistants.set(call.id, { entryId, fingerprint });
391
+ }
392
+ function restoredAssistantEntryId(state, message) {
393
+ const calls = assistantToolCalls(message);
394
+ if (calls.length === 0)
395
+ return undefined;
396
+ const fingerprint = assistantFingerprint(message);
397
+ const restored = calls.map((call) => state.restoredAssistants.get(call.id));
398
+ if (restored.some((item) => !item || item.fingerprint !== fingerprint))
399
+ return undefined;
400
+ const entryIds = new Set(restored.map((item) => item.entryId));
401
+ return entryIds.size === 1 ? restored[0].entryId : undefined;
402
+ }
403
+ function assistantFingerprint(message) {
404
+ return JSON.stringify({
405
+ content: message.content,
406
+ api: message.api,
407
+ provider: message.provider,
408
+ model: message.model,
409
+ responseId: message.responseId,
410
+ timestamp: message.timestamp,
411
+ });
412
+ }
413
+ function repairMetadata(input) {
414
+ return {
415
+ version: 1,
416
+ boundary: input.boundary,
417
+ relation: input.relation,
418
+ action: input.action,
419
+ phase: input.phase,
420
+ toolCallIds: input.toolCallIds,
421
+ removedEntryIds: input.removedEntryIds,
422
+ restoredEntryIds: input.restoredEntryIds,
423
+ removedCount: input.removedEntryIds.length,
424
+ restoredCount: input.restoredEntryIds.length,
425
+ };
426
+ }
427
+ function recordReport(state, report) {
428
+ state.reports.push(report);
429
+ if (report.continuationRequired)
430
+ state.continuationPending = true;
431
+ }
@@ -39,6 +39,8 @@ export class PiboDataStore {
39
39
  this.telemetry = new TelemetryStore(this.db);
40
40
  }
41
41
  transaction(action) {
42
+ if (this.db.isTransaction)
43
+ return action();
42
44
  this.db.exec("BEGIN IMMEDIATE");
43
45
  try {
44
46
  const result = action();
@@ -86,6 +86,154 @@ export class TelemetryStore {
86
86
  `).all(turnId);
87
87
  return rows.map(toolCallFromRow);
88
88
  }
89
+ listActiveTurns() {
90
+ const rows = this.db.prepare(`
91
+ SELECT * FROM telemetry_turns
92
+ WHERE status IN ('queued', 'running')
93
+ ORDER BY queued_at ASC, created_at ASC
94
+ `).all();
95
+ return rows.map(turnFromRow);
96
+ }
97
+ recoverInterruptedTurns(input = {}) {
98
+ const at = input.at ?? new Date().toISOString();
99
+ return this.transaction(() => this.listActiveTurns().map((turn) => {
100
+ const outcome = input.resolveOutcome?.(turn) ?? {
101
+ status: "aborted",
102
+ summary: "Turn was interrupted by gateway restart.",
103
+ };
104
+ const phaseStatus = outcome.status;
105
+ const providerStatus = outcome.status;
106
+ const toolStatus = outcome.status;
107
+ const phaseName = outcome.status === "timeout"
108
+ ? "timeout"
109
+ : outcome.status === "error"
110
+ ? "error"
111
+ : "abort";
112
+ for (const phase of this.listOpenPhasesForTurn(turn.turnId)) {
113
+ this.finishPhase(phase.phaseId, {
114
+ status: phaseStatus,
115
+ endedAt: at,
116
+ lastProgressAt: at,
117
+ summary: outcome.summary,
118
+ });
119
+ }
120
+ for (const request of this.listActiveProviderRequestsForTurn(turn.turnId)) {
121
+ this.upsertProviderRequest({
122
+ providerRequestId: request.providerRequestId,
123
+ piboSessionId: request.piboSessionId,
124
+ rootSessionId: request.rootSessionId,
125
+ roomId: request.roomId,
126
+ turnId: request.turnId,
127
+ phaseId: request.phaseId,
128
+ provider: request.provider,
129
+ api: request.api,
130
+ model: request.model,
131
+ transport: request.transport,
132
+ serviceTier: request.serviceTier,
133
+ status: providerStatus,
134
+ responseHeadersAt: request.responseHeadersAt,
135
+ firstByteAt: request.firstByteAt,
136
+ lastRawEventAt: request.lastRawEventAt,
137
+ lastNormalizedEventAt: request.lastNormalizedEventAt,
138
+ completedAt: at,
139
+ httpStatus: request.httpStatus,
140
+ upstreamResponseId: request.upstreamResponseId,
141
+ rawEventCount: request.rawEventCount,
142
+ normalizedEventCount: request.normalizedEventCount,
143
+ parseErrorCount: request.parseErrorCount,
144
+ unknownEventCount: request.unknownEventCount,
145
+ bytesReceived: request.bytesReceived,
146
+ eventTypeCounts: request.eventTypeCounts,
147
+ eventStreamId: request.eventStreamId,
148
+ eventId: request.eventId,
149
+ payloadRef: request.payloadRef,
150
+ errorCategory: "runtime_restart",
151
+ errorMessage: outcome.summary,
152
+ captureMode: request.captureMode,
153
+ retentionClass: request.retentionClass,
154
+ updatedAt: at,
155
+ });
156
+ }
157
+ for (const toolCall of this.listActiveToolCallsForTurn(turn.turnId)) {
158
+ const executionEndedAt = toolCall.executionStartedAt ? at : toolCall.executionEndedAt;
159
+ this.upsertToolCall({
160
+ toolCallId: toolCall.toolCallId,
161
+ piboSessionId: toolCall.piboSessionId,
162
+ rootSessionId: toolCall.rootSessionId,
163
+ roomId: toolCall.roomId,
164
+ turnId: toolCall.turnId,
165
+ providerRequestId: toolCall.providerRequestId,
166
+ providerItemId: toolCall.providerItemId,
167
+ outputIndex: toolCall.outputIndex,
168
+ toolName: toolCall.toolName,
169
+ status: toolStatus,
170
+ argsStartedAt: toolCall.argsStartedAt,
171
+ firstDeltaAt: toolCall.firstDeltaAt,
172
+ lastDeltaAt: toolCall.lastDeltaAt,
173
+ argsCompletedAt: toolCall.argsCompletedAt,
174
+ executionStartedAt: toolCall.executionStartedAt,
175
+ executionEndedAt,
176
+ durationMs: toolCall.executionStartedAt && executionEndedAt
177
+ ? Math.max(0, Date.parse(executionEndedAt) - Date.parse(toolCall.executionStartedAt))
178
+ : toolCall.durationMs,
179
+ argsBytes: toolCall.argsBytes,
180
+ parseStatus: toolCall.parseStatus,
181
+ safeArgKeys: toolCall.safeArgKeys,
182
+ eventStreamId: toolCall.eventStreamId,
183
+ eventId: toolCall.eventId,
184
+ payloadRef: toolCall.payloadRef,
185
+ runId: toolCall.runId,
186
+ errorCategory: "runtime_restart",
187
+ errorMessage: outcome.summary,
188
+ retentionClass: toolCall.retentionClass,
189
+ updatedAt: at,
190
+ });
191
+ }
192
+ this.upsertPhase({
193
+ phaseId: `${turn.turnId}:runtime_restart`,
194
+ turnId: turn.turnId,
195
+ piboSessionId: turn.piboSessionId,
196
+ rootSessionId: turn.rootSessionId,
197
+ roomId: turn.roomId,
198
+ name: phaseName,
199
+ status: phaseStatus,
200
+ startedAt: at,
201
+ endedAt: at,
202
+ lastProgressAt: at,
203
+ eventId: turn.eventId,
204
+ runId: turn.runId,
205
+ summary: outcome.summary,
206
+ retentionClass: turn.retentionClass,
207
+ updatedAt: at,
208
+ });
209
+ const recovered = this.upsertTurn({
210
+ turnId: turn.turnId,
211
+ piboSessionId: turn.piboSessionId,
212
+ rootSessionId: turn.rootSessionId,
213
+ roomId: turn.roomId,
214
+ inputEventId: turn.inputEventId,
215
+ eventId: turn.eventId,
216
+ eventStreamId: turn.eventStreamId,
217
+ payloadRef: turn.payloadRef,
218
+ runId: turn.runId,
219
+ source: turn.source,
220
+ status: outcome.status,
221
+ currentPhase: phaseName,
222
+ queuedAt: turn.queuedAt,
223
+ startedAt: turn.startedAt,
224
+ completedAt: at,
225
+ lastProgressAt: at,
226
+ queuedBehind: turn.queuedBehind,
227
+ queueDepth: 0,
228
+ summary: outcome.summary,
229
+ retentionClass: turn.retentionClass,
230
+ createdAt: turn.createdAt,
231
+ updatedAt: at,
232
+ metadata: turn.metadata,
233
+ });
234
+ return { turn: recovered, outcome };
235
+ }));
236
+ }
89
237
  listProviderEventsPage(providerRequestId, input = {}) {
90
238
  return listTelemetryProviderEventsPage(this.db, providerRequestId, input);
91
239
  }