@pasko70/pibo 1.12.0 → 1.12.1
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/web-app.js +16 -8
- package/dist/apps/chat-ui/assets/{dist-DiPvo4rq.js → dist-3bD-92Wo.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DxJWicji.js → dist-BFjy5Y59.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D1U9ck8z.js → dist-BKwQlbhS.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B2Pozq2c.js → dist-Cp68y_h5.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DYdgxFZX.js → dist-DE0y_EI7.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Ci4lft6y.js → dist-DPb0yDi4.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CkLit_da.js → dist-D_dQ5GaA.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bgv5Fm3v.js → dist-Daq2KRpq.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D4qbF8wy.js → dist-aZnuCezL.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-la7ZfDGk.js → dist-q6Vpm5yH.js} +1 -1
- package/dist/apps/chat-ui/assets/index-DXPKuGfs.js +237 -0
- package/dist/apps/chat-ui/index.html +1 -1
- package/dist/apps/chat-vscode-web/assets/index-B3dSrp-L.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.12.1.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-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
|
@@ -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
|
}
|
package/dist/debug/index.js
CHANGED
|
@@ -989,6 +989,17 @@ function compactRunRow(run) {
|
|
|
989
989
|
timeoutAt: run.timeoutAt,
|
|
990
990
|
timeoutPhase: run.timeoutPhase,
|
|
991
991
|
serviceWarning: run.serviceWarning,
|
|
992
|
+
isolation: run.resources?.isolationMode,
|
|
993
|
+
resourceUnit: run.resources?.unitName,
|
|
994
|
+
resourceLimitReason: run.resources?.limitReason,
|
|
995
|
+
minimumHostAvailableBytes: run.resources?.minimumHostAvailableBytes,
|
|
996
|
+
peakMemoryFullPsiAvg10: run.resources?.peakMemoryFullPsiAvg10,
|
|
997
|
+
peakIoFullPsiAvg10: run.resources?.peakIoFullPsiAvg10,
|
|
998
|
+
memoryPeakBytes: run.resources?.cgroup?.memoryPeakBytes,
|
|
999
|
+
memoryMaxBytes: run.resources?.cgroup?.memoryMaxBytes,
|
|
1000
|
+
tasksPeak: run.resources?.cgroup?.tasksPeak,
|
|
1001
|
+
ioReadBytes: run.resources?.cgroup?.ioReadBytes,
|
|
1002
|
+
ioWriteBytes: run.resources?.cgroup?.ioWriteBytes,
|
|
992
1003
|
summary: run.summary,
|
|
993
1004
|
};
|
|
994
1005
|
}
|
package/dist/gateway/server.js
CHANGED
|
@@ -149,6 +149,7 @@ export class PiboGatewayServer {
|
|
|
149
149
|
pluginRegistry: this.pluginRegistry,
|
|
150
150
|
sessionStore: this.sessionStore,
|
|
151
151
|
messagePreflight: createLoopMessagePreflight({ path: this.options.loopStorePath }),
|
|
152
|
+
recoverInterruptedRuntimeState: true,
|
|
152
153
|
});
|
|
153
154
|
this.unsubscribe = this.router.subscribe((event) => this.broadcastRouterEvent(event));
|
|
154
155
|
this.server = createServer((socket) => this.handleSocket(socket));
|
|
@@ -144,7 +144,8 @@ export class PiboReliabilityStore {
|
|
|
144
144
|
timeout_ms INTEGER,
|
|
145
145
|
timeout_at TEXT,
|
|
146
146
|
timeout_phase TEXT,
|
|
147
|
-
service_warning TEXT
|
|
147
|
+
service_warning TEXT,
|
|
148
|
+
resource_json TEXT
|
|
148
149
|
);
|
|
149
150
|
CREATE INDEX IF NOT EXISTS idx_pibo_runs_controller_updated
|
|
150
151
|
ON pibo_runs(controller_pibo_session_id, updated_at);
|
|
@@ -155,6 +156,7 @@ export class PiboReliabilityStore {
|
|
|
155
156
|
ensurePiboRunColumn(this.db, "timeout_at", "TEXT");
|
|
156
157
|
ensurePiboRunColumn(this.db, "timeout_phase", "TEXT");
|
|
157
158
|
ensurePiboRunColumn(this.db, "service_warning", "TEXT");
|
|
159
|
+
ensurePiboRunColumn(this.db, "resource_json", "TEXT");
|
|
158
160
|
this.appendEventStatement = this.db.prepare(`
|
|
159
161
|
INSERT INTO pibo_event_stream (topic, key, event_id, idempotency_key, created_at, retention_class, payload_json)
|
|
160
162
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
@@ -496,10 +498,10 @@ export class PiboReliabilityStore {
|
|
|
496
498
|
INSERT INTO pibo_runs (
|
|
497
499
|
run_id, kind, controller_pibo_session_id, status, completion_policy, consumed, tool_name,
|
|
498
500
|
summary, result_json, error, notified_status, acknowledged_status, created_at, updated_at,
|
|
499
|
-
completed_at, job_id, retryable, max_attempts, timeout_ms, timeout_at, timeout_phase, service_warning
|
|
500
|
-
) VALUES (?, 'tool', ?, 'running', ?, 0, ?, ?, NULL, NULL, NULL, NULL, ?, ?, NULL, ?, ?, ?, ?, ?, NULL, ?)
|
|
501
|
+
completed_at, job_id, retryable, max_attempts, timeout_ms, timeout_at, timeout_phase, service_warning, resource_json
|
|
502
|
+
) VALUES (?, 'tool', ?, 'running', ?, 0, ?, ?, NULL, NULL, NULL, NULL, ?, ?, NULL, ?, ?, ?, ?, ?, NULL, ?, ?)
|
|
501
503
|
`)
|
|
502
|
-
.run(runId, input.controllerPiboSessionId, input.completionPolicy, input.toolName, `${input.toolName} run is running.`, timestamp, timestamp, job.jobId, input.retryable ? 1 : 0, maxAttempts, input.timeoutMs ?? null, timeoutAt ?? null, input.serviceWarning ?? null);
|
|
504
|
+
.run(runId, input.controllerPiboSessionId, input.completionPolicy, input.toolName, `${input.toolName} run is running.`, timestamp, timestamp, job.jobId, input.retryable ? 1 : 0, maxAttempts, input.timeoutMs ?? null, timeoutAt ?? null, input.serviceWarning ?? null, input.resources ? JSON.stringify(input.resources) : null);
|
|
503
505
|
this.claimJob(job.jobId, input.workerId ?? `run-registry:${process.pid}`, 24 * 60 * 60 * 1000);
|
|
504
506
|
return this.requireRun(runId);
|
|
505
507
|
}
|
|
@@ -527,10 +529,11 @@ export class PiboReliabilityStore {
|
|
|
527
529
|
timeout_ms = ?,
|
|
528
530
|
timeout_at = ?,
|
|
529
531
|
timeout_phase = ?,
|
|
530
|
-
service_warning =
|
|
532
|
+
service_warning = ?,
|
|
533
|
+
resource_json = ?
|
|
531
534
|
WHERE run_id = ?
|
|
532
535
|
`)
|
|
533
|
-
.run(next.status, next.completionPolicy, next.consumed ? 1 : 0, next.summary ?? null, next.result ? JSON.stringify(next.result) : null, next.error ?? null, next.notifiedStatus ?? null, next.acknowledgedStatus ?? null, next.updatedAt, next.completedAt ?? null, next.jobId ?? null, next.retryable ? 1 : 0, next.maxAttempts, next.timeoutMs ?? null, next.timeoutAt ?? null, next.timeoutPhase ?? null, next.serviceWarning ?? null, runId);
|
|
536
|
+
.run(next.status, next.completionPolicy, next.consumed ? 1 : 0, next.summary ?? null, next.result ? JSON.stringify(next.result) : null, next.error ?? null, next.notifiedStatus ?? null, next.acknowledgedStatus ?? null, next.updatedAt, next.completedAt ?? null, next.jobId ?? null, next.retryable ? 1 : 0, next.maxAttempts, next.timeoutMs ?? null, next.timeoutAt ?? null, next.timeoutPhase ?? null, next.serviceWarning ?? null, next.resources ? JSON.stringify(next.resources) : null, runId);
|
|
534
537
|
return this.requireRun(runId);
|
|
535
538
|
}
|
|
536
539
|
getRun(runId) {
|
|
@@ -801,6 +804,8 @@ function runFromRow(row) {
|
|
|
801
804
|
output.timeoutPhase = row.timeout_phase;
|
|
802
805
|
if (row.service_warning)
|
|
803
806
|
output.serviceWarning = row.service_warning;
|
|
807
|
+
if (row.resource_json)
|
|
808
|
+
output.resources = JSON.parse(row.resource_json);
|
|
804
809
|
return output;
|
|
805
810
|
}
|
|
806
811
|
function retryDelayMs(attempts, input) {
|