@pasko70/pibo 3.1.3 → 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-BNzch-mL.js → dist-BOqsX6_s.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CoA9zNTP.js → dist-BcUbdOKJ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-liw1S7HS.js → dist-DkpIJ_Pp.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-7DhHpLCA.js → dist-a0mykCz7.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DDRlEGPR.js → dist-qDZ-CdlK.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-BKp6zKIe.js → index-G2ic-FSG.js} +89 -89
- package/dist/apps/chat-ui/index.html +1 -1
- package/dist/apps/chat-vscode-web/assets/{index-CKC-46jZ.js → index-zQ1fNz5K.js} +1 -1
- 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
|
@@ -150,7 +150,7 @@ export class PiboDataSessionStore {
|
|
|
150
150
|
claimOrAttachOutputPart(input) {
|
|
151
151
|
return this.dataStore.transaction(() => {
|
|
152
152
|
const indexAttribute = outputPartIndexAttribute(input.kind);
|
|
153
|
-
const eventTypes = outputPartEventTypes(input.kind);
|
|
153
|
+
const eventTypes = [...outputPartEventTypes(input.kind), "message_finished"];
|
|
154
154
|
const placeholders = eventTypes.map(() => "?").join(", ");
|
|
155
155
|
const rows = this.db.prepare(`
|
|
156
156
|
SELECT type, attributes_json
|
|
@@ -159,26 +159,33 @@ export class PiboDataSessionStore {
|
|
|
159
159
|
ORDER BY stream_id ASC
|
|
160
160
|
`).all(input.piboSessionId, input.eventId, ...eventTypes);
|
|
161
161
|
let maximum = -1;
|
|
162
|
-
|
|
162
|
+
let turnCompleted = false;
|
|
163
|
+
const persistedParts = [];
|
|
163
164
|
for (const row of rows) {
|
|
165
|
+
if (row.type === "message_finished") {
|
|
166
|
+
turnCompleted = true;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
164
169
|
const attributes = parseJsonObject(row.attributes_json);
|
|
165
170
|
const index = attributes[indexAttribute];
|
|
166
171
|
if (typeof index !== "number" || !Number.isSafeInteger(index) || index < 0)
|
|
167
172
|
continue;
|
|
168
173
|
maximum = Math.max(maximum, index);
|
|
169
|
-
|
|
170
|
-
|| attributes.identityFingerprint === input.identityFingerprint) {
|
|
171
|
-
this.observeOutputPartIndex(input, index);
|
|
172
|
-
return index;
|
|
173
|
-
}
|
|
174
|
-
latestTypeByIndex.set(index, row.type);
|
|
174
|
+
persistedParts.push({ index, attributes });
|
|
175
175
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
176
|
+
// An unfinished durable part may still belong to another process. Only a
|
|
177
|
+
// completed turn provides enough evidence to reattach an exact replay.
|
|
178
|
+
if (turnCompleted) {
|
|
179
|
+
const matchesReplay = ({ attributes }) => attributes.outputPartFingerprint === input.fingerprint
|
|
180
|
+
|| attributes.identityFingerprint === input.identityFingerprint;
|
|
181
|
+
const replay = (input.suppliedIndex === undefined
|
|
182
|
+
? undefined
|
|
183
|
+
: persistedParts.find((part) => part.index === input.suppliedIndex && matchesReplay(part)))
|
|
184
|
+
?? persistedParts.find(matchesReplay);
|
|
185
|
+
if (replay) {
|
|
186
|
+
this.observeOutputPartIndex(input, replay.index);
|
|
187
|
+
return replay.index;
|
|
188
|
+
}
|
|
182
189
|
}
|
|
183
190
|
const minimum = Math.max(input.proposedIndex, maximum + 1);
|
|
184
191
|
const row = this.db.prepare(`
|
|
@@ -193,6 +200,11 @@ export class PiboDataSessionStore {
|
|
|
193
200
|
return row.part_index;
|
|
194
201
|
});
|
|
195
202
|
}
|
|
203
|
+
observeOutputPart(input) {
|
|
204
|
+
this.dataStore.transaction(() => {
|
|
205
|
+
this.observeOutputPartIndex(input, input.index);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
196
208
|
observeOutputPartIndex(input, index) {
|
|
197
209
|
this.db.prepare(`
|
|
198
210
|
INSERT INTO session_output_part_counters (
|
|
@@ -639,12 +651,6 @@ function outputPartEventTypes(kind) {
|
|
|
639
651
|
case "compaction": return ["compaction_start", "compaction_end"];
|
|
640
652
|
}
|
|
641
653
|
}
|
|
642
|
-
function outputPartEventIsTerminal(eventType) {
|
|
643
|
-
return eventType === "assistant_message"
|
|
644
|
-
|| eventType === "thinking_finished"
|
|
645
|
-
|| eventType === "assistant_usage"
|
|
646
|
-
|| eventType === "compaction_end";
|
|
647
|
-
}
|
|
648
654
|
function parseJsonObject(json) {
|
|
649
655
|
if (!json)
|
|
650
656
|
return {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { reconcileAsyncAgentRunStatuses } from "./trace-async-agent-runs.js";
|
|
2
|
-
import { applySingleEventToNodes, dedupeTraceEvents, findOpenTranscriptEventIds, latestTraceStreamId, mergeMessageTurnTimings, messageTurnTimingsFromEvents, reconcileTranscriptUserMessages, } from "./trace-event-projection.js";
|
|
2
|
+
import { applySingleEventToNodes, dedupeTraceEvents, findOpenTranscriptEventIds, latestTraceStreamId, markIncompletePersistedTurns, mergeMessageTurnTimings, messageTurnTimingsFromEvents, reconcileTranscriptUserMessages, } from "./trace-event-projection.js";
|
|
3
3
|
import { flattenTraceNodes, mapTraceNodesById, nestTraceNodes } from "./trace-nodes.js";
|
|
4
4
|
import { mapTraceChildSessionsByParent, mapTraceSubagentSessionLinks, } from "./trace-subagent-links.js";
|
|
5
5
|
import { projectHistoryEntries, traceNodesFromHistoryEntries } from "./trace-history.js";
|
|
@@ -18,7 +18,8 @@ export function buildTraceViewFromEvents(input) {
|
|
|
18
18
|
const openHistoryEventIds = findOpenTranscriptEventIds(events, sessionStatus);
|
|
19
19
|
const suppliedTurnTimings = input.turnTimings ?? [];
|
|
20
20
|
const eventTurnTimings = messageTurnTimingsFromEvents(events);
|
|
21
|
-
const timingOverflow =
|
|
21
|
+
const timingOverflow = input.turnTimingOverflow === true
|
|
22
|
+
|| suppliedTurnTimings.length + eventTurnTimings.length > TRACE_RECONCILIATION_TIMING_CAP;
|
|
22
23
|
const turnTimings = timingOverflow
|
|
23
24
|
? []
|
|
24
25
|
: mergeMessageTurnTimings(suppliedTurnTimings, eventTurnTimings);
|
|
@@ -42,11 +43,13 @@ export function buildTraceViewFromEvents(input) {
|
|
|
42
43
|
for (const storedEvent of events) {
|
|
43
44
|
applySingleEventToNodes(nodes, byId, input.session.id, storedEvent, childByParent, linkedChildByToolCallId, historyCoverage, openHistoryEventIds, sessionStatus);
|
|
44
45
|
}
|
|
46
|
+
const hasIncompleteTurns = markIncompletePersistedTurns(nodes, byId, input.session.id, events, turnTimings, sessionStatus);
|
|
45
47
|
const nestedNodes = nestTraceNodes(nodes);
|
|
46
48
|
reconcileAsyncAgentRunStatuses(nestedNodes);
|
|
47
49
|
return {
|
|
48
50
|
piboSessionId: input.session.id,
|
|
49
51
|
piSessionId: input.session.piSessionId,
|
|
52
|
+
...(hasIncompleteTurns ? { integrityStatus: "incomplete" } : {}),
|
|
50
53
|
title: input.session.title ?? "Untitled Session",
|
|
51
54
|
version: "",
|
|
52
55
|
latestStreamId: latestTraceStreamId(events, input.latestStreamId),
|
|
@@ -232,6 +232,80 @@ function turnClosedAt(turn, byId) {
|
|
|
232
232
|
.at(0);
|
|
233
233
|
return error?.startedAt;
|
|
234
234
|
}
|
|
235
|
+
const INCOMPLETE_TURN_SUMMARY = "Incomplete output lifecycle";
|
|
236
|
+
const INCOMPLETE_TURN_ERROR = "Persisted output has message_started but no message_finished or session_error event.";
|
|
237
|
+
export function markIncompletePersistedTurns(nodes, byId, piboSessionId, events, turnTimings, sessionStatus) {
|
|
238
|
+
const lifecycleByEventId = new Map();
|
|
239
|
+
for (const timing of turnTimings) {
|
|
240
|
+
const lifecycle = lifecycleByEventId.get(timing.eventId) ?? { started: false, completed: false };
|
|
241
|
+
lifecycle.started ||= timing.startedAt !== undefined;
|
|
242
|
+
lifecycle.completed ||= timing.completedAt !== undefined;
|
|
243
|
+
lifecycleByEventId.set(timing.eventId, lifecycle);
|
|
244
|
+
}
|
|
245
|
+
const incompleteEventIds = new Set([...lifecycleByEventId].flatMap(([eventId, lifecycle]) => lifecycle.started && !lifecycle.completed ? [eventId] : []));
|
|
246
|
+
if (sessionStatus === "running") {
|
|
247
|
+
const currentTurn = [...turnTimings].reverse().find((timing) => timing.userMessageType !== "message_steered" &&
|
|
248
|
+
timing.startedAt !== undefined);
|
|
249
|
+
if (currentTurn?.startedAt !== undefined && currentTurn.completedAt === undefined) {
|
|
250
|
+
incompleteEventIds.delete(currentTurn.eventId);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (incompleteEventIds.size === 0)
|
|
254
|
+
return false;
|
|
255
|
+
const startByEventId = new Map();
|
|
256
|
+
const lastByEventId = new Map();
|
|
257
|
+
for (const storedEvent of events) {
|
|
258
|
+
const event = storedEvent.payload;
|
|
259
|
+
const eventId = "eventId" in event && typeof event.eventId === "string" ? event.eventId : undefined;
|
|
260
|
+
if (!eventId || !incompleteEventIds.has(eventId))
|
|
261
|
+
continue;
|
|
262
|
+
if (event.type === "message_started" && !startByEventId.has(eventId))
|
|
263
|
+
startByEventId.set(eventId, storedEvent);
|
|
264
|
+
lastByEventId.set(eventId, storedEvent);
|
|
265
|
+
}
|
|
266
|
+
for (const eventId of incompleteEventIds) {
|
|
267
|
+
const start = startByEventId.get(eventId);
|
|
268
|
+
if (!start)
|
|
269
|
+
continue;
|
|
270
|
+
const startEvent = start.payload;
|
|
271
|
+
const turnId = messageTurnNodeId(eventId);
|
|
272
|
+
let turn = byId.get(turnId);
|
|
273
|
+
if (!turn) {
|
|
274
|
+
turn = traceNodeFromEvent(piboSessionId, startEvent, new Map(), new Map(), sessionStatus, start.createdAt, start.eventSequence, start.streamId, start.streamFrameIndex, start.traceSource, start.id);
|
|
275
|
+
if (!turn)
|
|
276
|
+
continue;
|
|
277
|
+
nodes.push(turn);
|
|
278
|
+
byId.set(turn.id, turn);
|
|
279
|
+
}
|
|
280
|
+
turn.status = "error";
|
|
281
|
+
turn.summary = INCOMPLETE_TURN_SUMMARY;
|
|
282
|
+
turn.error = INCOMPLETE_TURN_ERROR;
|
|
283
|
+
const markerId = `event:incomplete-turn:${eventId}`;
|
|
284
|
+
if (byId.has(markerId))
|
|
285
|
+
continue;
|
|
286
|
+
const last = lastByEventId.get(eventId) ?? start;
|
|
287
|
+
const marker = {
|
|
288
|
+
id: markerId,
|
|
289
|
+
parentId: turnId,
|
|
290
|
+
piboSessionId,
|
|
291
|
+
eventId,
|
|
292
|
+
type: "error",
|
|
293
|
+
title: "Incomplete Turn",
|
|
294
|
+
status: "error",
|
|
295
|
+
startedAt: last.createdAt,
|
|
296
|
+
summary: INCOMPLETE_TURN_SUMMARY,
|
|
297
|
+
error: INCOMPLETE_TURN_ERROR,
|
|
298
|
+
output: INCOMPLETE_TURN_ERROR,
|
|
299
|
+
source: "event-log",
|
|
300
|
+
stableKey: `incomplete-turn:${eventId}`,
|
|
301
|
+
orderKey: eventTraceNodeOrder(last.eventSequence, "session_error", last.streamId, last.streamFrameIndex, last.traceSource, undefined, last.createdAt),
|
|
302
|
+
children: [],
|
|
303
|
+
};
|
|
304
|
+
nodes.push(marker);
|
|
305
|
+
byId.set(marker.id, marker);
|
|
306
|
+
}
|
|
307
|
+
return [...incompleteEventIds].some((eventId) => byId.has(`event:incomplete-turn:${eventId}`));
|
|
308
|
+
}
|
|
235
309
|
export function eventsCanAffectAsyncAgentRunStatus(events) {
|
|
236
310
|
return events.some((event) => {
|
|
237
311
|
const type = event.payload.type;
|
|
@@ -4,10 +4,12 @@ export function mergeOlderTracePage(current, older) {
|
|
|
4
4
|
if (current.piboSessionId !== older.piboSessionId)
|
|
5
5
|
return current;
|
|
6
6
|
const rawEvents = mergeTraceRawEvents(older.rawEvents, current.rawEvents, false);
|
|
7
|
+
const nodes = mergeTraceNodes(older.nodes, current.nodes);
|
|
7
8
|
return {
|
|
8
9
|
...current,
|
|
9
10
|
version: current.version,
|
|
10
|
-
|
|
11
|
+
integrityStatus: traceIntegrityStatus(nodes) ?? current.integrityStatus ?? older.integrityStatus,
|
|
12
|
+
nodes,
|
|
11
13
|
rawEvents,
|
|
12
14
|
beforeCursor: older.beforeCursor ?? current.beforeCursor,
|
|
13
15
|
firstEventSequence: older.firstEventSequence ?? current.firstEventSequence,
|
|
@@ -20,9 +22,11 @@ export function mergeOlderTracePage(current, older) {
|
|
|
20
22
|
export function mergeRefreshedTracePage(current, refreshed) {
|
|
21
23
|
if (current.piboSessionId !== refreshed.piboSessionId)
|
|
22
24
|
return refreshed;
|
|
25
|
+
const nodes = mergeRefreshedTraceNodes(current.nodes, refreshed);
|
|
23
26
|
return {
|
|
24
27
|
...refreshed,
|
|
25
|
-
|
|
28
|
+
integrityStatus: traceIntegrityStatus(nodes) ?? refreshed.integrityStatus,
|
|
29
|
+
nodes,
|
|
26
30
|
rawEvents: mergeTraceRawEvents(current.rawEvents, refreshed.rawEvents, true),
|
|
27
31
|
beforeCursor: current.beforeCursor,
|
|
28
32
|
firstEventSequence: current.firstEventSequence ?? refreshed.firstEventSequence,
|
|
@@ -36,10 +40,13 @@ function mergeRefreshedTraceNodes(currentNodes, refreshed) {
|
|
|
36
40
|
const flattenedCurrentNodes = flattenTraceNodes([...currentNodes]);
|
|
37
41
|
const refreshedNodes = flattenTraceNodes([...refreshed.nodes]);
|
|
38
42
|
const refreshedIds = new Set(refreshedNodes.map((node) => node.id));
|
|
43
|
+
const refreshedTerminalEventIds = terminalEventIds(refreshedNodes);
|
|
39
44
|
const canonicalTranscriptEventIds = transcriptEventIds([...flattenedCurrentNodes, ...refreshedNodes]);
|
|
40
45
|
const refreshedOrderBoundaries = earliestTraceNodeOrdersBySource(refreshedNodes);
|
|
41
46
|
const refreshedStartedAt = earliestTraceNodeTimestamp(refreshedNodes);
|
|
42
47
|
const retainedOlderNodes = flattenedCurrentNodes.filter((node) => {
|
|
48
|
+
if (isIncompleteTurnMarker(node) && node.eventId && refreshedTerminalEventIds.has(node.eventId))
|
|
49
|
+
return false;
|
|
43
50
|
if (refreshedIds.has(node.id))
|
|
44
51
|
return true;
|
|
45
52
|
if (node.type === "agent.turn" &&
|
|
@@ -71,9 +78,28 @@ function mergeRefreshedTraceNodes(currentNodes, refreshed) {
|
|
|
71
78
|
return mergeTraceNodes(clearRemovedParentIds(retainedOlderNodes, removedCurrentIds), clearRemovedParentIds(refreshedNodes, removedCurrentIds));
|
|
72
79
|
}
|
|
73
80
|
function clearRemovedParentIds(nodes, removedIds) {
|
|
74
|
-
return nodes.map((node) =>
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
return nodes.map((node) => ({
|
|
82
|
+
...node,
|
|
83
|
+
...(node.parentId && removedIds.has(node.parentId) ? { parentId: undefined } : {}),
|
|
84
|
+
children: [],
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
function traceIntegrityStatus(nodes) {
|
|
88
|
+
return flattenTraceNodes([...nodes]).some(isIncompleteTurnMarker) ? "incomplete" : undefined;
|
|
89
|
+
}
|
|
90
|
+
function isIncompleteTurnMarker(node) {
|
|
91
|
+
return node.id.startsWith("event:incomplete-turn:") || node.stableKey?.startsWith("incomplete-turn:") === true;
|
|
92
|
+
}
|
|
93
|
+
function terminalEventIds(nodes) {
|
|
94
|
+
return new Set(nodes.flatMap((node) => {
|
|
95
|
+
if (!node.eventId)
|
|
96
|
+
return [];
|
|
97
|
+
if (node.type === "agent.turn" && node.completedAt)
|
|
98
|
+
return [node.eventId];
|
|
99
|
+
if (node.type === "error" && node.title === "Session Error")
|
|
100
|
+
return [node.eventId];
|
|
101
|
+
return [];
|
|
102
|
+
}));
|
|
77
103
|
}
|
|
78
104
|
function transcriptEventIds(nodes) {
|
|
79
105
|
const eventIds = new Set();
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@pasko70/pibo",
|
|
9
|
-
"version": "3.1.
|
|
9
|
+
"version": "3.1.4",
|
|
10
10
|
"workspaces": [
|
|
11
11
|
"packages/workflows"
|
|
12
12
|
],
|