@pasko70/pibo 3.1.2 → 3.1.4
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/data/timeline-query-service.js +5 -2
- package/dist/apps/chat/output-compactor.js +33 -30
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/trace.js +3 -1
- package/dist/apps/chat/web-app.js +106 -65
- package/dist/apps/chat-ui/assets/{dist-W5HOqHym.js → dist-BOqsX6_s.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Djul9BmZ.js → dist-BcUbdOKJ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-ClUlQWYN.js → dist-DkpIJ_Pp.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-GD0JGdCi.js → dist-a0mykCz7.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CUQJqggN.js → dist-qDZ-CdlK.js} +1 -1
- package/dist/apps/chat-ui/assets/index-G2ic-FSG.js +228 -0
- package/dist/apps/chat-ui/index.html +1 -1
- package/dist/apps/chat-vscode-web/assets/index-zQ1fNz5K.js +43 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/cli-session/localSessionSource.js +26 -20
- package/dist/core/output-persistence-retry.js +23 -1
- package/dist/core/output-render-sequence.js +33 -9
- package/dist/data/ingest-service.js +8 -0
- package/dist/debug/index.js +294 -2
- package/dist/debug/output-integrity.js +608 -0
- package/dist/debug/output-repair.js +584 -0
- package/dist/debug/trace.js +2 -0
- package/dist/reliability/store.js +2 -2
- package/dist/sessions/pibo-data-store.js +26 -20
- package/dist/shared/trace-engine.js +5 -2
- package/dist/shared/trace-event-projection.js +74 -0
- package/dist/shared/trace-page-merge.js +31 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-BcjkX-iP.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-JvrPUGvI.js +0 -43
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { isPiboOutputEvent } from "../apps/chat/output-event-policy.js";
|
|
3
|
+
import { ChatDataIngestService } from "../data/ingest-service.js";
|
|
4
|
+
import { PiboDataStore } from "../data/pibo-store.js";
|
|
5
|
+
import { createDefaultPiboPluginRegistry } from "../plugins/builtin.js";
|
|
6
|
+
import { PiboDataSessionStore } from "../sessions/pibo-data-store.js";
|
|
7
|
+
const EVENT_SOURCES = new Set(["user", "ui", "service", "actor"]);
|
|
8
|
+
const REPAIR_LIMIT_MAX = 100;
|
|
9
|
+
const RELIABILITY_SCAN_LIMIT = 5000;
|
|
10
|
+
export function inspectOutputTurnRepair(input) {
|
|
11
|
+
return buildRepairPlan(input).inspection;
|
|
12
|
+
}
|
|
13
|
+
export function repairOutputTurn(input) {
|
|
14
|
+
const mode = input.apply ? "apply" : "dry-run";
|
|
15
|
+
const reliabilityEvidence = collectReliabilityEvidence(input.reliabilityStore, input.piboSessionId, input.eventId);
|
|
16
|
+
const initial = buildRepairPlan(input, reliabilityEvidence);
|
|
17
|
+
const base = resultBase(input.store, input.piboSessionId, mode);
|
|
18
|
+
if (!input.apply || !initial.inspection.repairable || !initial.terminalEvent) {
|
|
19
|
+
return { ...base, applied: false, inspection: initial.inspection };
|
|
20
|
+
}
|
|
21
|
+
const data = new PiboDataStore(input.store.path);
|
|
22
|
+
try {
|
|
23
|
+
return data.transaction(() => {
|
|
24
|
+
const currentReliabilityEvidence = collectReliabilityEvidence(input.reliabilityStore, input.piboSessionId, input.eventId);
|
|
25
|
+
const current = buildRepairPlanFromDb(data.db, input.piboSessionId, input.eventId, currentReliabilityEvidence, input.adapterEvidence);
|
|
26
|
+
if (!current.inspection.repairable || !current.terminalEvent) {
|
|
27
|
+
return { ...base, applied: false, inspection: current.inspection };
|
|
28
|
+
}
|
|
29
|
+
const sessionStore = new PiboDataSessionStore(data);
|
|
30
|
+
const session = sessionStore.get(input.piboSessionId);
|
|
31
|
+
if (!session) {
|
|
32
|
+
return {
|
|
33
|
+
...base,
|
|
34
|
+
applied: false,
|
|
35
|
+
inspection: { ...current.inspection, repairable: false, reason: "session_not_found" },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const room = data.db.prepare("SELECT room_id AS roomId FROM sessions WHERE id = ?").get(input.piboSessionId);
|
|
39
|
+
const createdAt = input.now?.() ?? new Date().toISOString();
|
|
40
|
+
const persisted = new ChatDataIngestService(data).ingestOutputEvent({
|
|
41
|
+
session,
|
|
42
|
+
...(room?.roomId ? { roomId: room.roomId } : {}),
|
|
43
|
+
actorId: "pibo-debug-repair",
|
|
44
|
+
event: current.terminalEvent,
|
|
45
|
+
createdAt,
|
|
46
|
+
});
|
|
47
|
+
const audit = data.eventLog.appendEvent({
|
|
48
|
+
sessionId: input.piboSessionId,
|
|
49
|
+
sessionSequence: nextEventSequence(data.db, input.piboSessionId),
|
|
50
|
+
...(room?.roomId ? { roomId: room.roomId } : {}),
|
|
51
|
+
topic: "pibo.audit",
|
|
52
|
+
type: "pibo.output.repair_applied",
|
|
53
|
+
source: "pibo-debug-repair",
|
|
54
|
+
actorType: "system",
|
|
55
|
+
actorId: "pibo-debug-repair",
|
|
56
|
+
eventId: input.eventId,
|
|
57
|
+
idempotencyKey: `pibo.output.repair:${input.piboSessionId}:${input.eventId}:${current.terminalEvent.type}`,
|
|
58
|
+
retentionClass: "audit_event",
|
|
59
|
+
previewText: `Output repair applied ${current.terminalEvent.type}`,
|
|
60
|
+
attributes: {
|
|
61
|
+
repairVersion: 1,
|
|
62
|
+
targetEventId: input.eventId,
|
|
63
|
+
terminalType: current.terminalEvent.type,
|
|
64
|
+
terminalStreamId: persisted.streamId,
|
|
65
|
+
evidenceSources: current.inspection.plannedEvent?.evidenceSources ?? [],
|
|
66
|
+
evidenceReferences: current.evidenceReferences,
|
|
67
|
+
},
|
|
68
|
+
createdAt,
|
|
69
|
+
indexedAt: createdAt,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
applied: true,
|
|
74
|
+
inspection: current.inspection,
|
|
75
|
+
persisted: {
|
|
76
|
+
type: current.terminalEvent.type,
|
|
77
|
+
streamId: persisted.streamId,
|
|
78
|
+
duplicate: persisted.duplicate,
|
|
79
|
+
auditStreamId: audit.streamId,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
data.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function repairOutputTurns(input) {
|
|
89
|
+
if (!input.store.exists)
|
|
90
|
+
throw new Error(`Debug store "pibo-data" not found at ${input.store.path}`);
|
|
91
|
+
const limit = normalizeRepairLimit(input.limit);
|
|
92
|
+
const db = new DatabaseSync(input.store.path, { readOnly: true });
|
|
93
|
+
let candidates;
|
|
94
|
+
try {
|
|
95
|
+
const clauses = ["startedAt IS NOT NULL", "messageStarted > 0", "terminalEvents = 0"];
|
|
96
|
+
const params = [input.piboSessionId];
|
|
97
|
+
if (input.since) {
|
|
98
|
+
clauses.push("startedAt >= ?");
|
|
99
|
+
params.push(input.since);
|
|
100
|
+
}
|
|
101
|
+
if (input.before) {
|
|
102
|
+
clauses.push("startedAt < ?");
|
|
103
|
+
params.push(input.before);
|
|
104
|
+
}
|
|
105
|
+
candidates = db.prepare(`
|
|
106
|
+
WITH turn_lifecycle AS (
|
|
107
|
+
SELECT event_id AS eventId,
|
|
108
|
+
MIN(CASE WHEN type = 'message_started' THEN created_at END) AS startedAt,
|
|
109
|
+
SUM(type = 'message_started') AS messageStarted,
|
|
110
|
+
SUM(type IN ('message_finished', 'session_error')) AS terminalEvents
|
|
111
|
+
FROM event_log
|
|
112
|
+
WHERE session_id = ? AND event_id IS NOT NULL
|
|
113
|
+
GROUP BY event_id
|
|
114
|
+
)
|
|
115
|
+
SELECT eventId, startedAt
|
|
116
|
+
FROM turn_lifecycle
|
|
117
|
+
WHERE ${clauses.join(" AND ")}
|
|
118
|
+
ORDER BY startedAt ASC
|
|
119
|
+
LIMIT ?
|
|
120
|
+
`).all(...params, limit);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
db.close();
|
|
124
|
+
}
|
|
125
|
+
const results = candidates.map((candidate) => repairOutputTurn({
|
|
126
|
+
store: input.store,
|
|
127
|
+
reliabilityStore: input.reliabilityStore,
|
|
128
|
+
piboSessionId: input.piboSessionId,
|
|
129
|
+
eventId: candidate.eventId,
|
|
130
|
+
adapterEvidence: input.adapterEvidence,
|
|
131
|
+
apply: input.apply,
|
|
132
|
+
now: input.now,
|
|
133
|
+
}));
|
|
134
|
+
const mode = input.apply ? "apply" : "dry-run";
|
|
135
|
+
return {
|
|
136
|
+
resultType: "debug.repair.output.scope",
|
|
137
|
+
mode,
|
|
138
|
+
scope: {
|
|
139
|
+
piboSessionId: input.piboSessionId,
|
|
140
|
+
...(input.since ? { since: input.since } : {}),
|
|
141
|
+
...(input.before ? { before: input.before } : {}),
|
|
142
|
+
limit,
|
|
143
|
+
},
|
|
144
|
+
candidateCount: results.length,
|
|
145
|
+
repairableCount: results.filter((result) => result.inspection.repairable).length,
|
|
146
|
+
appliedCount: results.filter((result) => result.applied).length,
|
|
147
|
+
results,
|
|
148
|
+
warnings: resultWarnings(),
|
|
149
|
+
nextCommands: resultNextCommands(input.piboSessionId),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export async function readOutputRepairAdapterEvidence(input) {
|
|
153
|
+
if (!input.store.exists)
|
|
154
|
+
return { available: false, entries: [] };
|
|
155
|
+
const db = new DatabaseSync(input.store.path, { readOnly: true });
|
|
156
|
+
try {
|
|
157
|
+
const session = db.prepare("SELECT status, workspace, created_at, updated_at, pi_session_id FROM sessions WHERE id = ?").get(input.piboSessionId);
|
|
158
|
+
if (!session)
|
|
159
|
+
return { available: false, entries: [] };
|
|
160
|
+
const binding = readRuntimeBinding(db, input.piboSessionId, session);
|
|
161
|
+
if (!binding)
|
|
162
|
+
return { available: false, entries: [] };
|
|
163
|
+
const adapter = createDefaultPiboPluginRegistry().getAgentRuntimeAdapter(binding.runtimeInstanceId);
|
|
164
|
+
if (!adapter?.descriptor.capabilities.maintenance.history || !adapter.readHistory) {
|
|
165
|
+
return { available: false, entries: [] };
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const page = await adapter.readHistory({ binding, workspace: session.workspace ?? process.cwd(), limit: 500 });
|
|
169
|
+
return { available: true, entries: page.entries };
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
return { available: false, entries: [], error: redactError(error) };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
db.close();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export function formatOutputTurnRepair(result) {
|
|
180
|
+
if (result.resultType === "debug.repair.output.scope") {
|
|
181
|
+
const lines = [
|
|
182
|
+
"pibo debug repair output",
|
|
183
|
+
`mode\t${result.mode}`,
|
|
184
|
+
`session\t${result.scope.piboSessionId}`,
|
|
185
|
+
...(result.scope.since ? [`since\t${result.scope.since}`] : []),
|
|
186
|
+
...(result.scope.before ? [`before\t${result.scope.before}`] : []),
|
|
187
|
+
`candidates\t${result.candidateCount}`,
|
|
188
|
+
`repairable\t${result.repairableCount}`,
|
|
189
|
+
`applied\t${result.appliedCount}`,
|
|
190
|
+
...result.results.map((item) => `${item.inspection.eventId}\t${item.applied ? "applied" : item.inspection.repairable ? "repairable" : item.inspection.reason ?? "not-repairable"}`),
|
|
191
|
+
"",
|
|
192
|
+
...result.warnings.map((warning) => `warning\t${warning}`),
|
|
193
|
+
"",
|
|
194
|
+
"Next:",
|
|
195
|
+
...result.nextCommands.map((command) => ` ${command}`),
|
|
196
|
+
];
|
|
197
|
+
return lines.join("\n");
|
|
198
|
+
}
|
|
199
|
+
const inspection = result.inspection;
|
|
200
|
+
const lines = [
|
|
201
|
+
"pibo debug repair output",
|
|
202
|
+
`mode\t${result.mode}`,
|
|
203
|
+
`applied\t${result.applied}`,
|
|
204
|
+
`session\t${inspection.piboSessionId}`,
|
|
205
|
+
`event\t${inspection.eventId}`,
|
|
206
|
+
`repairable\t${inspection.repairable}`,
|
|
207
|
+
...(inspection.reason ? [`reason\t${inspection.reason}`] : []),
|
|
208
|
+
...(inspection.plannedEvent ? [`plannedEvent\t${inspection.plannedEvent.type}`, `evidenceSources\t${inspection.plannedEvent.evidenceSources.join(",")}`] : []),
|
|
209
|
+
`messageStarted\t${inspection.observed.messageStarted}`,
|
|
210
|
+
`assistantMessages\t${inspection.observed.assistantMessages}`,
|
|
211
|
+
`productAssistantMessages\t${inspection.observed.productAssistantMessages}`,
|
|
212
|
+
`messageFinished\t${inspection.observed.messageFinished}`,
|
|
213
|
+
`sessionErrors\t${inspection.observed.sessionErrors}`,
|
|
214
|
+
`openThinkingParts\t${inspection.observed.openThinkingParts}`,
|
|
215
|
+
`openToolInvocations\t${inspection.observed.openToolInvocations}`,
|
|
216
|
+
`identityCollisions\t${inspection.observed.identityCollisions}`,
|
|
217
|
+
`reliabilityTerminalCandidates\t${inspection.observed.reliabilityTerminalCandidates}`,
|
|
218
|
+
`adapterCompletedAssistantMessages\t${inspection.observed.adapterCompletedAssistantMessages}`,
|
|
219
|
+
...(result.persisted ? [`persisted\t${result.persisted.type}@${result.persisted.streamId}`, `audit\t${result.persisted.auditStreamId}`] : []),
|
|
220
|
+
"",
|
|
221
|
+
...result.warnings.map((warning) => `warning\t${warning}`),
|
|
222
|
+
"",
|
|
223
|
+
"Next:",
|
|
224
|
+
...result.nextCommands.map((command) => ` ${command}`),
|
|
225
|
+
];
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
function buildRepairPlan(input, reliabilityEvidence = collectReliabilityEvidence(input.reliabilityStore, input.piboSessionId, input.eventId)) {
|
|
229
|
+
if (!input.store.exists)
|
|
230
|
+
throw new Error(`Debug store "pibo-data" not found at ${input.store.path}`);
|
|
231
|
+
const db = new DatabaseSync(input.store.path, { readOnly: true });
|
|
232
|
+
db.exec("BEGIN");
|
|
233
|
+
try {
|
|
234
|
+
const plan = buildRepairPlanFromDb(db, input.piboSessionId, input.eventId, reliabilityEvidence, input.adapterEvidence);
|
|
235
|
+
db.exec("COMMIT");
|
|
236
|
+
return plan;
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (db.isTransaction)
|
|
240
|
+
db.exec("ROLLBACK");
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
db.close();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function buildRepairPlanFromDb(db, piboSessionId, eventId, reliability, adapterEvidence) {
|
|
248
|
+
const session = db.prepare("SELECT status, workspace, created_at, updated_at, pi_session_id FROM sessions WHERE id = ? AND deleted_at IS NULL").get(piboSessionId);
|
|
249
|
+
const counts = db.prepare(`
|
|
250
|
+
SELECT
|
|
251
|
+
SUM(type = 'message_started') AS messageStarted,
|
|
252
|
+
SUM(type = 'assistant_message') AS assistantMessages,
|
|
253
|
+
SUM(type = 'message_finished') AS messageFinished,
|
|
254
|
+
SUM(type = 'session_error') AS sessionErrors,
|
|
255
|
+
SUM(type = 'pibo.output.identity_collision') AS identityCollisions
|
|
256
|
+
FROM event_log
|
|
257
|
+
WHERE session_id = ? AND event_id = ?
|
|
258
|
+
`).get(piboSessionId, eventId);
|
|
259
|
+
const productAssistantMessages = tableExists(db, "chat_messages")
|
|
260
|
+
? Number(db.prepare("SELECT COUNT(*) AS count FROM chat_messages WHERE session_id = ? AND turn_id = ? AND role = 'assistant' AND status = 'complete'").get(piboSessionId, eventId).count)
|
|
261
|
+
: 0;
|
|
262
|
+
const productRunningMessages = tableExists(db, "chat_messages")
|
|
263
|
+
? Number(db.prepare("SELECT COUNT(*) AS count FROM chat_messages WHERE session_id = ? AND turn_id = ? AND role = 'assistant' AND status IN ('running', 'streaming')").get(piboSessionId, eventId).count)
|
|
264
|
+
: 0;
|
|
265
|
+
const adapterEntries = (adapterEvidence?.entries ?? []).filter((entry) => entry.type === "message" && entry.turnId === eventId);
|
|
266
|
+
const adapterCompleted = adapterEntries.filter((entry) => entry.type === "message" && entry.role === "assistant" && entry.status !== "running" && entry.status !== "error");
|
|
267
|
+
const adapterRunning = adapterEntries.filter((entry) => entry.type === "message" && entry.status === "running");
|
|
268
|
+
const observed = {
|
|
269
|
+
...(session?.status ? { sessionStatus: session.status } : {}),
|
|
270
|
+
messageStarted: Number(counts.messageStarted ?? 0),
|
|
271
|
+
assistantMessages: Number(counts.assistantMessages ?? 0),
|
|
272
|
+
productAssistantMessages,
|
|
273
|
+
messageFinished: Number(counts.messageFinished ?? 0),
|
|
274
|
+
sessionErrors: Number(counts.sessionErrors ?? 0),
|
|
275
|
+
openThinkingParts: countOpenThinkingParts(db, piboSessionId, eventId),
|
|
276
|
+
openToolInvocations: countOpenToolInvocations(db, piboSessionId, eventId),
|
|
277
|
+
identityCollisions: Number(counts.identityCollisions ?? 0),
|
|
278
|
+
reliabilityTerminalCandidates: reliability.terminalEvents.length,
|
|
279
|
+
adapterCompletedAssistantMessages: adapterCompleted.length,
|
|
280
|
+
adapterRunningEntries: adapterRunning.length,
|
|
281
|
+
};
|
|
282
|
+
const evidence = [];
|
|
283
|
+
if (reliability.terminalEvents.length)
|
|
284
|
+
evidence.push({ source: "reliability_payload", kind: "exact_terminal", count: reliability.terminalEvents.length, references: reliability.references });
|
|
285
|
+
const productCompleted = observed.assistantMessages + productAssistantMessages;
|
|
286
|
+
if (productCompleted)
|
|
287
|
+
evidence.push({ source: "pibo_product_history", kind: "completed_assistant", count: productCompleted, references: productReferences(db, piboSessionId, eventId) });
|
|
288
|
+
if (adapterCompleted.length)
|
|
289
|
+
evidence.push({ source: "adapter_history", kind: "completed_assistant", count: adapterCompleted.length, references: adapterCompleted.map((entry) => entry.id).slice(0, 20) });
|
|
290
|
+
if (adapterRunning.length)
|
|
291
|
+
evidence.push({ source: "adapter_history", kind: "running_entry", count: adapterRunning.length, references: adapterRunning.map((entry) => entry.id).slice(0, 20) });
|
|
292
|
+
if (productRunningMessages)
|
|
293
|
+
evidence.push({ source: "pibo_product_history", kind: "running_entry", count: productRunningMessages, references: [] });
|
|
294
|
+
const base = { piboSessionId, eventId, sessionExists: Boolean(session), observed, evidence };
|
|
295
|
+
const refuse = (reason) => ({
|
|
296
|
+
inspection: { ...base, repairable: false, reason },
|
|
297
|
+
evidenceReferences: [],
|
|
298
|
+
});
|
|
299
|
+
if (!session)
|
|
300
|
+
return refuse("session_not_found");
|
|
301
|
+
if (session.status === "running")
|
|
302
|
+
return refuse("session_active");
|
|
303
|
+
if (observed.messageStarted === 0)
|
|
304
|
+
return refuse("message_start_missing");
|
|
305
|
+
if (observed.messageStarted !== 1)
|
|
306
|
+
return refuse("message_start_duplicated");
|
|
307
|
+
if (observed.messageFinished + observed.sessionErrors > 0)
|
|
308
|
+
return refuse("already_terminal");
|
|
309
|
+
if (adapterRunning.length || productRunningMessages)
|
|
310
|
+
return refuse("session_active");
|
|
311
|
+
const exactTerminal = uniqueReliabilityTerminal(reliability.terminalEvents.map((item) => item.event));
|
|
312
|
+
if (exactTerminal === "conflict")
|
|
313
|
+
return refuse("evidence_conflict");
|
|
314
|
+
if (observed.openThinkingParts || observed.openToolInvocations)
|
|
315
|
+
return refuse("lifecycle_open");
|
|
316
|
+
if (exactTerminal) {
|
|
317
|
+
const sources = ["reliability_payload"];
|
|
318
|
+
return {
|
|
319
|
+
inspection: {
|
|
320
|
+
...base,
|
|
321
|
+
repairable: true,
|
|
322
|
+
plannedEvent: {
|
|
323
|
+
type: exactTerminal.type,
|
|
324
|
+
...(exactTerminal.type === "message_finished" && "source" in exactTerminal && exactTerminal.source ? { source: exactTerminal.source } : {}),
|
|
325
|
+
evidenceSources: sources,
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
terminalEvent: exactTerminal,
|
|
329
|
+
evidenceReferences: reliability.references,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const completedSources = [];
|
|
333
|
+
if (productCompleted)
|
|
334
|
+
completedSources.push("pibo_product_history");
|
|
335
|
+
if (adapterCompleted.length)
|
|
336
|
+
completedSources.push("adapter_history");
|
|
337
|
+
if (!completedSources.length)
|
|
338
|
+
return refuse("evidence_missing");
|
|
339
|
+
const source = messageSource(db, piboSessionId, eventId);
|
|
340
|
+
const terminalEvent = { type: "message_finished", piboSessionId, eventId, ...source };
|
|
341
|
+
return {
|
|
342
|
+
inspection: {
|
|
343
|
+
...base,
|
|
344
|
+
repairable: true,
|
|
345
|
+
plannedEvent: { type: "message_finished", ...source, evidenceSources: completedSources },
|
|
346
|
+
},
|
|
347
|
+
terminalEvent,
|
|
348
|
+
evidenceReferences: evidence.flatMap((item) => item.references).slice(0, 50),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function collectReliabilityEvidence(store, piboSessionId, eventId) {
|
|
352
|
+
if (!store?.exists)
|
|
353
|
+
return { terminalEvents: [], references: [] };
|
|
354
|
+
const db = new DatabaseSync(store.path, { readOnly: true });
|
|
355
|
+
db.exec("BEGIN");
|
|
356
|
+
try {
|
|
357
|
+
const rows = [];
|
|
358
|
+
for (const table of ["pibo_jobs", "pibo_dead_jobs"]) {
|
|
359
|
+
if (!tableExists(db, table))
|
|
360
|
+
continue;
|
|
361
|
+
const tableRows = db.prepare(`
|
|
362
|
+
SELECT job_id AS jobId, payload_json AS payloadJson
|
|
363
|
+
FROM ${table}
|
|
364
|
+
WHERE queue IN ('output-persistence', 'output-persistence-cli')
|
|
365
|
+
ORDER BY updated_at DESC
|
|
366
|
+
LIMIT ?
|
|
367
|
+
`).all(RELIABILITY_SCAN_LIMIT);
|
|
368
|
+
rows.push(...tableRows.map((row) => ({ ...row, table })));
|
|
369
|
+
}
|
|
370
|
+
const terminalEvents = [];
|
|
371
|
+
for (const row of rows) {
|
|
372
|
+
const payload = parseJson(row.payloadJson);
|
|
373
|
+
for (const event of outputEventsInPayload(payload)) {
|
|
374
|
+
if (event.piboSessionId !== piboSessionId || !("eventId" in event) || event.eventId !== eventId)
|
|
375
|
+
continue;
|
|
376
|
+
if (event.type !== "message_finished" && event.type !== "session_error")
|
|
377
|
+
continue;
|
|
378
|
+
terminalEvents.push({ event, reference: `${row.table}:${row.jobId}` });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const evidence = {
|
|
382
|
+
terminalEvents,
|
|
383
|
+
references: [...new Set(terminalEvents.map((item) => item.reference))].slice(0, 50),
|
|
384
|
+
};
|
|
385
|
+
db.exec("COMMIT");
|
|
386
|
+
return evidence;
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
if (db.isTransaction)
|
|
390
|
+
db.exec("ROLLBACK");
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
db.close();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function outputEventsInPayload(value) {
|
|
398
|
+
const found = [];
|
|
399
|
+
let visited = 0;
|
|
400
|
+
const visit = (candidate, depth) => {
|
|
401
|
+
if (depth > 8 || visited >= 2000 || candidate === null || typeof candidate !== "object")
|
|
402
|
+
return;
|
|
403
|
+
visited += 1;
|
|
404
|
+
if (isPiboOutputEvent(candidate)) {
|
|
405
|
+
found.push(candidate);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (Array.isArray(candidate)) {
|
|
409
|
+
for (const item of candidate.slice(0, 500))
|
|
410
|
+
visit(item, depth + 1);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const record = candidate;
|
|
414
|
+
for (const key of ["event", "deliveries", "state", "payload"]) {
|
|
415
|
+
if (key in record)
|
|
416
|
+
visit(record[key], depth + 1);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
visit(value, 0);
|
|
420
|
+
return found;
|
|
421
|
+
}
|
|
422
|
+
function uniqueReliabilityTerminal(events) {
|
|
423
|
+
if (!events.length)
|
|
424
|
+
return undefined;
|
|
425
|
+
const bySignature = new Map();
|
|
426
|
+
for (const event of events)
|
|
427
|
+
bySignature.set(stableJson(event), event);
|
|
428
|
+
if (bySignature.size !== 1)
|
|
429
|
+
return "conflict";
|
|
430
|
+
return [...bySignature.values()][0];
|
|
431
|
+
}
|
|
432
|
+
function productReferences(db, piboSessionId, eventId) {
|
|
433
|
+
const eventRows = db.prepare("SELECT stream_id AS id FROM event_log WHERE session_id = ? AND event_id = ? AND type = 'assistant_message' ORDER BY stream_id LIMIT 20").all(piboSessionId, eventId);
|
|
434
|
+
const messageRows = tableExists(db, "chat_messages")
|
|
435
|
+
? db.prepare("SELECT id FROM chat_messages WHERE session_id = ? AND turn_id = ? AND role = 'assistant' AND status = 'complete' ORDER BY sequence LIMIT 20").all(piboSessionId, eventId)
|
|
436
|
+
: [];
|
|
437
|
+
return [...eventRows.map((row) => `event_log:${row.id}`), ...messageRows.map((row) => `chat_messages:${row.id}`)];
|
|
438
|
+
}
|
|
439
|
+
function countOpenThinkingParts(db, piboSessionId, eventId) {
|
|
440
|
+
const row = db.prepare(`
|
|
441
|
+
SELECT COUNT(*) AS count FROM (
|
|
442
|
+
SELECT CASE WHEN json_valid(attributes_json)
|
|
443
|
+
THEN COALESCE(json_extract(attributes_json, '$.thinkingIndex'), json_extract(attributes_json, '$.contentIndex'), 0)
|
|
444
|
+
ELSE 0 END AS thinking_index
|
|
445
|
+
FROM event_log
|
|
446
|
+
WHERE session_id = ? AND event_id = ? AND type IN ('thinking_started', 'thinking_finished')
|
|
447
|
+
GROUP BY thinking_index
|
|
448
|
+
HAVING SUM(type = 'thinking_started') != 1
|
|
449
|
+
OR SUM(type = 'thinking_finished') != 1
|
|
450
|
+
)
|
|
451
|
+
`).get(piboSessionId, eventId);
|
|
452
|
+
return Number(row.count);
|
|
453
|
+
}
|
|
454
|
+
function countOpenToolInvocations(db, piboSessionId, eventId) {
|
|
455
|
+
const row = db.prepare(`
|
|
456
|
+
SELECT COUNT(*) AS count FROM (
|
|
457
|
+
SELECT
|
|
458
|
+
CASE WHEN tool_call_id IS NOT NULL THEN tool_call_id
|
|
459
|
+
WHEN json_valid(attributes_json) THEN json_extract(attributes_json, '$.toolCallId') END AS tool_call_id,
|
|
460
|
+
CASE WHEN json_valid(attributes_json) THEN COALESCE(json_extract(attributes_json, '$.toolInvocationOrdinal'), 0) ELSE 0 END AS ordinal
|
|
461
|
+
FROM event_log
|
|
462
|
+
WHERE session_id = ? AND event_id = ?
|
|
463
|
+
AND type IN ('tool_call', 'tool_execution_started', 'tool_execution_finished')
|
|
464
|
+
GROUP BY tool_call_id, ordinal
|
|
465
|
+
HAVING SUM(type = 'tool_call') != 1
|
|
466
|
+
OR SUM(type = 'tool_execution_started') != 1
|
|
467
|
+
OR SUM(type = 'tool_execution_finished') != 1
|
|
468
|
+
)
|
|
469
|
+
`).get(piboSessionId, eventId);
|
|
470
|
+
return Number(row.count);
|
|
471
|
+
}
|
|
472
|
+
function messageSource(db, piboSessionId, eventId) {
|
|
473
|
+
const row = db.prepare(`
|
|
474
|
+
SELECT CASE WHEN json_valid(attributes_json)
|
|
475
|
+
THEN COALESCE(json_extract(attributes_json, '$.source'), json_extract(attributes_json, '$.inlinePayload.source')) END AS source
|
|
476
|
+
FROM event_log
|
|
477
|
+
WHERE session_id = ? AND event_id = ? AND type = 'message_started'
|
|
478
|
+
ORDER BY stream_id ASC
|
|
479
|
+
LIMIT 1
|
|
480
|
+
`).get(piboSessionId, eventId);
|
|
481
|
+
return row?.source && EVENT_SOURCES.has(row.source) ? { source: row.source } : {};
|
|
482
|
+
}
|
|
483
|
+
function readRuntimeBinding(db, piboSessionId, session) {
|
|
484
|
+
if (!tableExists(db, "session_runtime_bindings")) {
|
|
485
|
+
return {
|
|
486
|
+
piboSessionId,
|
|
487
|
+
runtimeInstanceId: "pi",
|
|
488
|
+
adapterId: "pi",
|
|
489
|
+
nativeSessionId: session.pi_session_id ?? undefined,
|
|
490
|
+
state: session.pi_session_id ? "bound" : "unbound",
|
|
491
|
+
protocol: "pi-sdk",
|
|
492
|
+
metadata: { source: "legacy-synthesized" },
|
|
493
|
+
revision: 1,
|
|
494
|
+
createdAt: session.created_at,
|
|
495
|
+
updatedAt: session.updated_at,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
const row = db.prepare("SELECT * FROM session_runtime_bindings WHERE pibo_session_id = ?").get(piboSessionId);
|
|
499
|
+
if (!row)
|
|
500
|
+
return undefined;
|
|
501
|
+
const locator = parseObject(typeof row.locator_json === "string" ? row.locator_json : null);
|
|
502
|
+
return {
|
|
503
|
+
piboSessionId,
|
|
504
|
+
runtimeInstanceId: String(row.runtime_instance_id),
|
|
505
|
+
adapterId: String(row.runtime_adapter_id),
|
|
506
|
+
nativeSessionId: typeof row.native_session_id === "string" ? row.native_session_id : undefined,
|
|
507
|
+
state: row.binding_state,
|
|
508
|
+
protocol: typeof row.protocol === "string" ? row.protocol : undefined,
|
|
509
|
+
protocolVersion: typeof row.protocol_version === "string" ? row.protocol_version : undefined,
|
|
510
|
+
adapterVersion: typeof row.adapter_version === "string" ? row.adapter_version : undefined,
|
|
511
|
+
locator: typeof locator.kind === "string" ? locator : undefined,
|
|
512
|
+
metadata: parseObject(typeof row.metadata_json === "string" ? row.metadata_json : null),
|
|
513
|
+
revision: Number(row.revision),
|
|
514
|
+
createdAt: String(row.created_at),
|
|
515
|
+
updatedAt: String(row.updated_at),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function resultBase(store, piboSessionId, mode) {
|
|
519
|
+
return {
|
|
520
|
+
resultType: "debug.repair.output",
|
|
521
|
+
mode,
|
|
522
|
+
store: { path: store.path, exists: store.exists },
|
|
523
|
+
warnings: resultWarnings(),
|
|
524
|
+
nextCommands: resultNextCommands(piboSessionId),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function resultWarnings() {
|
|
528
|
+
return [
|
|
529
|
+
"Repair refuses active or ambiguous turns and never invents assistant content.",
|
|
530
|
+
"Every applied terminal event is paired with a pibo.output.repair_applied audit event.",
|
|
531
|
+
"This repair does not delete or replay pending or dead output-persistence jobs.",
|
|
532
|
+
];
|
|
533
|
+
}
|
|
534
|
+
function resultNextCommands(piboSessionId) {
|
|
535
|
+
return [
|
|
536
|
+
`pibo debug trace ${piboSessionId} --check`,
|
|
537
|
+
`pibo debug events ${piboSessionId} --limit 50`,
|
|
538
|
+
`pibo debug jobs dead --queue output-persistence`,
|
|
539
|
+
];
|
|
540
|
+
}
|
|
541
|
+
function normalizeRepairLimit(value) {
|
|
542
|
+
const parsed = typeof value === "number" ? value : value === undefined ? 20 : Number.parseInt(value, 10);
|
|
543
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1)
|
|
544
|
+
throw new Error("--limit must be a positive integer");
|
|
545
|
+
return Math.min(parsed, REPAIR_LIMIT_MAX);
|
|
546
|
+
}
|
|
547
|
+
function nextEventSequence(db, piboSessionId) {
|
|
548
|
+
return Number(db.prepare("SELECT COALESCE(MAX(session_sequence), 0) + 1 AS nextSequence FROM event_log WHERE session_id = ?").get(piboSessionId).nextSequence);
|
|
549
|
+
}
|
|
550
|
+
function tableExists(db, table) {
|
|
551
|
+
return db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = ?").get(table) !== undefined;
|
|
552
|
+
}
|
|
553
|
+
function parseJson(value) {
|
|
554
|
+
try {
|
|
555
|
+
return JSON.parse(value);
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
return undefined;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function parseObject(value) {
|
|
562
|
+
if (!value)
|
|
563
|
+
return {};
|
|
564
|
+
try {
|
|
565
|
+
const parsed = JSON.parse(value);
|
|
566
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
return {};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function stableJson(value) {
|
|
573
|
+
if (value === null || typeof value !== "object")
|
|
574
|
+
return JSON.stringify(value);
|
|
575
|
+
if (Array.isArray(value))
|
|
576
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
577
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`).join(",")}}`;
|
|
578
|
+
}
|
|
579
|
+
function redactError(error) {
|
|
580
|
+
return (error instanceof Error ? error.message : String(error))
|
|
581
|
+
.replace(/(bearer|token|secret|password|api[_-]?key)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]")
|
|
582
|
+
.replace(/\/\/[^\s/@:]+:[^\s/@]+@/g, "//[redacted]@")
|
|
583
|
+
.slice(0, 500);
|
|
584
|
+
}
|
package/dist/debug/trace.js
CHANGED
|
@@ -86,6 +86,7 @@ export async function inspectDebugTrace(piboSessionId, stores, options = {}) {
|
|
|
86
86
|
errorNodeCount: statusSummary.errorNodeCount,
|
|
87
87
|
nodes: filtered,
|
|
88
88
|
rawNodeCount: rows.length,
|
|
89
|
+
...(view.integrityStatus ? { integrityStatus: view.integrityStatus } : {}),
|
|
89
90
|
...(options.check ? { checks: checkTraceView(view, adapterIssues) } : {}),
|
|
90
91
|
nextCommands: buildTraceNextCommands(view.piboSessionId, filtered),
|
|
91
92
|
};
|
|
@@ -121,6 +122,7 @@ export function formatDebugTrace(result, options = {}) {
|
|
|
121
122
|
...(result.nativeSessionId ? [`nativeSessionId: ${result.nativeSessionId}`] : []),
|
|
122
123
|
...(result.runtimeBindingState ? [`runtimeBindingState: ${result.runtimeBindingState}`] : []),
|
|
123
124
|
`historySource: ${result.historySource}`,
|
|
125
|
+
...(result.integrityStatus ? [`integrityStatus: ${result.integrityStatus}`] : []),
|
|
124
126
|
`title: ${result.title}`,
|
|
125
127
|
`status: ${result.status}`,
|
|
126
128
|
`statusSource: ${result.statusSource}`,
|
|
@@ -514,13 +514,13 @@ export class PiboReliabilityStore {
|
|
|
514
514
|
return Number(result.changes ?? 0) > 0;
|
|
515
515
|
});
|
|
516
516
|
}
|
|
517
|
-
fail(jobId, workerId, error, claimToken) {
|
|
517
|
+
fail(jobId, workerId, error, claimToken, reason = "failed") {
|
|
518
518
|
const timestamp = now();
|
|
519
519
|
return this.inImmediateTransaction(() => {
|
|
520
520
|
const row = this.getLiveWorkerJob(jobId, workerId, timestamp, claimToken);
|
|
521
521
|
if (!row)
|
|
522
522
|
return false;
|
|
523
|
-
this.moveJobToDead(row, error,
|
|
523
|
+
this.moveJobToDead(row, error, reason, timestamp);
|
|
524
524
|
return true;
|
|
525
525
|
});
|
|
526
526
|
}
|