@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.
- package/dist/apps/chat/chat-trace-helpers.js +59 -2
- package/dist/apps/chat/data/timeline-query-service.js +1 -1
- package/dist/apps/chat/loop-api.js +5 -0
- package/dist/apps/chat/web-app.js +16 -8
- package/dist/apps/chat-ui/assets/{dist-D1U9ck8z.js → dist-B_8EcoB6.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-CT-91DFe.js → chat-ui/assets/dist-CELRUR8b.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-Bdudyq9I.js → chat-ui/assets/dist-CVLKu7NV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DYdgxFZX.js → dist-CVmpu9FK.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-f2E1-leP.js → chat-ui/assets/dist-CnX234jg.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D4qbF8wy.js → dist-DVPXynJE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B2Pozq2c.js → dist-DugRsLaG.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DxJWicji.js → dist-KOXty1JB.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bgv5Fm3v.js → dist-UYBI2F8E.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-la7ZfDGk.js → dist-XmG4qG-A.js} +1 -1
- package/dist/apps/chat-ui/assets/index-DhFQ1th-.js +237 -0
- package/dist/apps/chat-ui/assets/index-hjygk9Ol.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-1wWonouX.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/{chat-ui/assets/dist-Ci4lft6y.js → context-files-ui/assets/dist-B5mpygOZ.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-BYcsFgKp.js → dist-BFRDpzcv.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-DcsDivye.js → dist-BLI6sQZG.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-CkLit_da.js → context-files-ui/assets/dist-B_zXPNMb.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-uFphVGi8.js → dist-CLrlJjPP.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-BvIW3V94.js → dist-D6pYe6d_.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-DiPvo4rq.js → context-files-ui/assets/dist-D8ppb7gk.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-sus90AFR.js → dist-DM6yvMZv.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-DeozI4lc.js → dist-Em3LWmNi.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-CSLUWBzz.js → dist-SEP88Pg7.js} +1 -1
- package/dist/apps/context-files-ui/assets/{index-CQ25ablG.js → index-C7X2UvWe.js} +3 -3
- package/dist/apps/context-files-ui/assets/{index-CukPnd6A.css → index-Cs4XQITe.css} +1 -1
- package/dist/apps/context-files-ui/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.13.0.vsix +0 -0
- package/dist/core/gateway-resource-guard.js +46 -15
- package/dist/core/routed-session.js +40 -1
- package/dist/core/runtime.js +2 -0
- package/dist/core/session-errors.js +3 -0
- package/dist/core/session-router.js +25 -2
- package/dist/core/transcript-integrity.js +431 -0
- package/dist/data/pibo-store.js +2 -0
- package/dist/data/telemetry.js +148 -0
- package/dist/debug/index.js +11 -0
- package/dist/gateway/server.js +1 -0
- package/dist/reliability/store.js +11 -6
- package/dist/runs/registry.js +38 -1
- package/dist/runs/resource-isolation.js +437 -0
- package/dist/runs/tools.js +6 -3
- package/dist/sessions/pibo-data-store.js +114 -0
- package/dist/shared/trace-engine.js +3 -3
- package/dist/shared/trace-event-projection.js +165 -36
- package/dist/shared/trace-nodes.js +6 -4
- package/dist/shared/trace-page-merge.js +106 -2
- package/dist/shared/trace-transcript.js +194 -36
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-BJoo_GJP.css +0 -1
- package/dist/apps/chat-ui/assets/index-CYxPvrxL.js +0 -237
- package/dist/apps/chat-vscode-web/assets/index-CFSHKXsQ.js +0 -41
- 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
|
-
:
|
|
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
|
+
}
|
package/dist/data/pibo-store.js
CHANGED
package/dist/data/telemetry.js
CHANGED
|
@@ -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
|
}
|