@pasko70/pibo 1.8.2 → 1.9.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/data/chat-data-mappers.js +8 -0
- package/dist/apps/chat/data/project-service.js +23 -0
- package/dist/apps/chat/static-assets.js +6 -5
- package/dist/apps/chat-ui/assets/{dist-CoCvyd4f.js → dist-8oSZX4UV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-6pcjNbPQ.js → dist-B1Fsqkt8.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-COlFTw2x.js → dist-BYeVKSuc.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CajLLpaw.js → dist-BZS06o95.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CMFjl7MX.js → dist-BZwDovWR.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-YdEosvsr.js → dist-BsxshSq9.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-IlQbTNzw.js → dist-CWGTu0U4.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CZSEM5So.js → dist-DE9BRlB6.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist--lraqdDn.js → dist-NWq0Jh7F.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-bLd-NaVv.js → dist-j4CZc7Hs.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dgz3iWay.js → dist-z23GNStM.js} +1 -1
- package/dist/apps/chat-ui/assets/index-C0x9nEcf.css +1 -0
- package/dist/apps/chat-ui/assets/index-Di8T05_5.js +173 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-ujYozhmx.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/cli.js +17 -0
- package/dist/compute/resource-health.js +12 -11
- package/dist/gateway/server.js +14 -1
- package/dist/gateway/web.js +2 -1
- package/dist/plugins/context-files.js +4 -2
- package/dist/ralph/store.js +1 -1
- package/dist/resources/cli.js +135 -0
- package/dist/resources/lifecycle.js +353 -0
- package/dist/resources/reaper-state.js +94 -0
- package/dist/resources/reaper.js +145 -0
- package/dist/session-ui/delegation.js +89 -0
- package/dist/session-ui/index.js +1 -0
- package/dist/session-ui/terminalRows.js +61 -0
- package/dist/shared/trace-engine.js +3 -2
- package/dist/shared/trace-event-projection.js +124 -14
- package/dist/shared/trace-transcript.js +73 -5
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-DbRZGRDd.css +0 -1
- package/dist/apps/chat-ui/assets/index-HPWlrJwv.js +0 -173
- package/dist/apps/chat-vscode-web/assets/index-Dge6XEYB.js +0 -41
|
@@ -5,12 +5,43 @@ export const COMPACT_TERMINAL_EXPLORING_PREVIEW_LINES = 6;
|
|
|
5
5
|
export function buildCompactTerminalRows(traceView, options) {
|
|
6
6
|
if (!traceView)
|
|
7
7
|
return [];
|
|
8
|
+
const turnById = mapTurnNodes(traceView.nodes);
|
|
8
9
|
const flatNodes = flattenTraceNodes(traceView.nodes)
|
|
9
10
|
.sort((left, right) => compareTraceNodes(left.node, right.node))
|
|
10
11
|
.filter((item) => item.node.type !== "agent.turn" && (options.showThinking || item.node.type !== "model.reasoning"));
|
|
11
12
|
const candidates = syncThinkingToolRows(flatNodes.map((item) => createRowCandidate(item.node, item.turnId)));
|
|
13
|
+
applyCompletedTurnTiming(candidates, turnById);
|
|
12
14
|
return groupRelatedToolCandidates(candidates).map((candidate) => candidate.row);
|
|
13
15
|
}
|
|
16
|
+
export function findActiveTurnStartedAt(traceView) {
|
|
17
|
+
if (!traceView)
|
|
18
|
+
return undefined;
|
|
19
|
+
const terminalErrorEventIds = new Set(flattenTraceNodes(traceView.nodes)
|
|
20
|
+
.map(({ node }) => node)
|
|
21
|
+
.filter((node) => node.type === "error" && node.eventId)
|
|
22
|
+
.map((node) => node.eventId));
|
|
23
|
+
return [...mapTurnNodes(traceView.nodes).values()]
|
|
24
|
+
.filter((turn) => turn.startedAt && !turn.completedAt && (!turn.eventId || !terminalErrorEventIds.has(turn.eventId)))
|
|
25
|
+
.sort(compareTraceNodes)
|
|
26
|
+
.at(-1)?.startedAt;
|
|
27
|
+
}
|
|
28
|
+
export function formatTerminalDuration(durationMs) {
|
|
29
|
+
const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
|
|
30
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
31
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
32
|
+
const seconds = totalSeconds % 60;
|
|
33
|
+
return [hours, minutes, seconds].map((value) => String(value).padStart(2, "0")).join(":");
|
|
34
|
+
}
|
|
35
|
+
function mapTurnNodes(nodes) {
|
|
36
|
+
const turns = new Map();
|
|
37
|
+
for (const node of nodes) {
|
|
38
|
+
if (node.type === "agent.turn")
|
|
39
|
+
turns.set(node.id, node);
|
|
40
|
+
for (const [id, turn] of mapTurnNodes(node.children))
|
|
41
|
+
turns.set(id, turn);
|
|
42
|
+
}
|
|
43
|
+
return turns;
|
|
44
|
+
}
|
|
14
45
|
function flattenTraceNodes(nodes, turnId) {
|
|
15
46
|
const result = [];
|
|
16
47
|
for (const node of nodes) {
|
|
@@ -71,6 +102,26 @@ function createRowCandidate(node, turnId) {
|
|
|
71
102
|
}
|
|
72
103
|
return { ...candidate, row: { ...candidate.row, ...debugFields(node) } };
|
|
73
104
|
}
|
|
105
|
+
function applyCompletedTurnTiming(candidates, turnById) {
|
|
106
|
+
for (const turn of turnById.values()) {
|
|
107
|
+
if (!turn.completedAt)
|
|
108
|
+
continue;
|
|
109
|
+
const turnCandidates = candidates.filter((candidate) => candidate.turnId === turn.id);
|
|
110
|
+
const finalCandidate = turnCandidates.at(-1);
|
|
111
|
+
if (finalCandidate?.row.kind !== "message.assistant" || finalCandidate.row.status === "running")
|
|
112
|
+
continue;
|
|
113
|
+
finalCandidate.row.startedAt = turn.startedAt;
|
|
114
|
+
finalCandidate.row.completedAt = turn.completedAt;
|
|
115
|
+
finalCandidate.row.durationMs = turn.durationMs ?? durationBetween(turn.startedAt, turn.completedAt);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function durationBetween(startedAt, completedAt) {
|
|
119
|
+
if (!startedAt || !completedAt)
|
|
120
|
+
return undefined;
|
|
121
|
+
const start = new Date(startedAt).getTime();
|
|
122
|
+
const end = new Date(completedAt).getTime();
|
|
123
|
+
return Number.isFinite(start) && Number.isFinite(end) ? Math.max(0, end - start) : undefined;
|
|
124
|
+
}
|
|
74
125
|
function debugFields(node) {
|
|
75
126
|
return {
|
|
76
127
|
eventId: node.eventId,
|
|
@@ -90,6 +141,7 @@ function createUserMessageRow(node) {
|
|
|
90
141
|
lines: [{ prefix: "prompt", tokens: [token(text)] }],
|
|
91
142
|
sourceNodeIds: [node.id],
|
|
92
143
|
forkEntryId: node.entryId,
|
|
144
|
+
startedAt: node.startedAt,
|
|
93
145
|
output: text,
|
|
94
146
|
payloadRefs: node.payloadRefs,
|
|
95
147
|
};
|
|
@@ -101,6 +153,9 @@ function createAssistantMessageRow(node) {
|
|
|
101
153
|
status: mapStatus(node.status),
|
|
102
154
|
lines: [],
|
|
103
155
|
sourceNodeIds: [node.id],
|
|
156
|
+
startedAt: node.startedAt,
|
|
157
|
+
completedAt: node.source === "transcript" ? node.completedAt : undefined,
|
|
158
|
+
durationMs: node.source === "transcript" ? node.durationMs : undefined,
|
|
104
159
|
output: stringValue(node.output) || stringValue(node.summary) || "",
|
|
105
160
|
error: node.error,
|
|
106
161
|
payloadRefs: node.payloadRefs,
|
|
@@ -261,6 +316,9 @@ function createDelegationRow(node) {
|
|
|
261
316
|
id: node.id,
|
|
262
317
|
kind: "agent.delegation",
|
|
263
318
|
status: mapStatus(node.status),
|
|
319
|
+
errorKind: node.status === "error" ? "tool" : undefined,
|
|
320
|
+
title: node.title,
|
|
321
|
+
summary: node.summary,
|
|
264
322
|
lines: [
|
|
265
323
|
{
|
|
266
324
|
prefix: "bullet",
|
|
@@ -270,6 +328,9 @@ function createDelegationRow(node) {
|
|
|
270
328
|
],
|
|
271
329
|
sourceNodeIds: [node.id],
|
|
272
330
|
linkedPiboSessionId: node.linkedPiboSessionId,
|
|
331
|
+
startedAt: node.startedAt,
|
|
332
|
+
completedAt: node.completedAt,
|
|
333
|
+
durationMs: node.durationMs,
|
|
273
334
|
input: node.input,
|
|
274
335
|
output: node.output,
|
|
275
336
|
error: node.error,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { reconcileAsyncAgentRunStatuses } from "./trace-async-agent-runs.js";
|
|
2
|
-
import { applySingleEventToNodes, contentDeltaPatchNodeId, dedupeTraceEvents, eventsCanAffectAsyncAgentRunStatus, findOpenTranscriptEventIds, isConfirmedUserMessageEcho, latestTraceStreamId, traceEventDedupeKey, } from "./trace-event-projection.js";
|
|
2
|
+
import { applySingleEventToNodes, contentDeltaPatchNodeId, dedupeTraceEvents, eventsCanAffectAsyncAgentRunStatus, findOpenTranscriptEventIds, isConfirmedUserMessageEcho, latestTraceStreamId, messageTurnTimingsFromEvents, reconcileTranscriptUserMessageTimestamps, traceEventDedupeKey, } from "./trace-event-projection.js";
|
|
3
3
|
import { flattenTraceNodes, mapTraceNodesById, nestTraceNodes } from "./trace-nodes.js";
|
|
4
4
|
import { nestMutableCopiedTraceNodes, shareUnchangedTraceNodes } from "./trace-patch-nodes.js";
|
|
5
5
|
import { mapTraceChildSessionsByParent, mapTraceSubagentSessionLinks, } from "./trace-subagent-links.js";
|
|
@@ -14,7 +14,8 @@ export function buildTraceViewFromEvents(input) {
|
|
|
14
14
|
const allEntries = input.transcriptEntries ?? [];
|
|
15
15
|
const openTranscriptEventIds = findOpenTranscriptEventIds(events, sessionStatus);
|
|
16
16
|
const entries = projectTranscriptEntries(allEntries, sessionStatus, openTranscriptEventIds);
|
|
17
|
-
const nodes = traceNodesFromEntries(input.session.id, entries);
|
|
17
|
+
const nodes = traceNodesFromEntries(input.session.id, entries, messageTurnTimingsFromEvents(events));
|
|
18
|
+
reconcileTranscriptUserMessageTimestamps(nodes, events);
|
|
18
19
|
const byId = mapTraceNodesById(nodes);
|
|
19
20
|
const childByParent = mapTraceChildSessionsByParent(input.sessions ?? []);
|
|
20
21
|
const linkedChildByToolCallId = mapTraceSubagentSessionLinks(events);
|
|
@@ -26,10 +26,8 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
|
|
|
26
26
|
const existing = byId.get(node.id);
|
|
27
27
|
if (existing) {
|
|
28
28
|
mergeAssistantMessageEvent(existing, node);
|
|
29
|
-
closeParentTurnForFinalAssistant(byId, existing);
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
|
-
closeParentTurnForFinalAssistant(byId, node);
|
|
33
31
|
nodes.push(node);
|
|
34
32
|
byId.set(node.id, node);
|
|
35
33
|
return;
|
|
@@ -49,10 +47,8 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
|
|
|
49
47
|
const existing = byId.get(node.id);
|
|
50
48
|
if (existing) {
|
|
51
49
|
mergeAssistantMessageEvent(existing, node);
|
|
52
|
-
closeParentTurnForFinalAssistant(byId, existing);
|
|
53
50
|
return;
|
|
54
51
|
}
|
|
55
|
-
closeParentTurnForFinalAssistant(byId, node);
|
|
56
52
|
}
|
|
57
53
|
if (node.type === "execution.compaction") {
|
|
58
54
|
const existing = findLatestCompactionNode(nodes);
|
|
@@ -68,6 +64,13 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
|
|
|
68
64
|
return;
|
|
69
65
|
}
|
|
70
66
|
}
|
|
67
|
+
if (node.type === "agent.delegation" && !node.toolCallId && node.linkedPiboSessionId) {
|
|
68
|
+
const existing = findLegacySubagentLinkTarget([...byId.values()], node);
|
|
69
|
+
if (existing) {
|
|
70
|
+
mergeSubagentSessionLink(existing, node);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
71
74
|
if (node.toolCallId) {
|
|
72
75
|
const existing = [...byId.values()].find((candidate) => candidate.toolCallId === node.toolCallId &&
|
|
73
76
|
(candidate.type === "tool.call" || candidate.type === "agent.delegation"));
|
|
@@ -102,6 +105,7 @@ function assistantMessageNodeFromEvent(piboSessionId, event, createdAt, eventSeq
|
|
|
102
105
|
title: "Agent Message",
|
|
103
106
|
status: "done",
|
|
104
107
|
startedAt: createdAt,
|
|
108
|
+
completedAt: createdAt,
|
|
105
109
|
summary: event.text,
|
|
106
110
|
output: event.text,
|
|
107
111
|
source: "event-log",
|
|
@@ -165,6 +169,28 @@ export function contentDeltaPatchNodeId(event) {
|
|
|
165
169
|
}
|
|
166
170
|
return undefined;
|
|
167
171
|
}
|
|
172
|
+
export function reconcileTranscriptUserMessageTimestamps(nodes, events) {
|
|
173
|
+
const transcriptUsers = nodes.filter((node) => node.type === "user.message" && node.source === "transcript");
|
|
174
|
+
let userCursor = 0;
|
|
175
|
+
for (const storedEvent of events) {
|
|
176
|
+
const event = storedEvent.payload;
|
|
177
|
+
if (event.type !== "message_queued" || event.source !== "user")
|
|
178
|
+
continue;
|
|
179
|
+
const eventId = typeof event.eventId === "string" ? event.eventId : storedEvent.eventId;
|
|
180
|
+
const text = typeof event.text === "string" ? event.text : undefined;
|
|
181
|
+
const matchIndex = transcriptUsers.findIndex((node, index) => {
|
|
182
|
+
if (index < userCursor)
|
|
183
|
+
return false;
|
|
184
|
+
if (eventId && (node.entryId === eventId || node.stableKey === `entry:${eventId}`))
|
|
185
|
+
return true;
|
|
186
|
+
return Boolean(text && traceNodeText(node) === text);
|
|
187
|
+
});
|
|
188
|
+
if (matchIndex === -1)
|
|
189
|
+
continue;
|
|
190
|
+
transcriptUsers[matchIndex].startedAt = storedEvent.createdAt;
|
|
191
|
+
userCursor = matchIndex + 1;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
168
194
|
export function isConfirmedUserMessageEcho(nodes, event) {
|
|
169
195
|
const payload = event.payload;
|
|
170
196
|
if (payload.type !== "message_queued" || payload.source !== "user")
|
|
@@ -266,6 +292,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
|
|
|
266
292
|
...base,
|
|
267
293
|
id: eventId ? messageTurnNodeId(eventId) : id,
|
|
268
294
|
type: "agent.turn",
|
|
295
|
+
startedAt: event.type === "message_started" ? createdAt : undefined,
|
|
269
296
|
title: "Agent Turn",
|
|
270
297
|
status: event.type === "message_finished" || sessionStatus !== "running" ? "done" : "running",
|
|
271
298
|
completedAt: event.type === "message_finished" ? createdAt : undefined,
|
|
@@ -296,6 +323,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
|
|
|
296
323
|
type: "assistant.message",
|
|
297
324
|
title: "Agent Message",
|
|
298
325
|
status: "done",
|
|
326
|
+
completedAt: createdAt,
|
|
299
327
|
summary: event.text,
|
|
300
328
|
output: event.text,
|
|
301
329
|
stableKey: assistantId ? `assistant:${assistantId}` : base.stableKey,
|
|
@@ -483,15 +511,6 @@ function mergeReasoningEvent(target, update) {
|
|
|
483
511
|
target.output = update.output ?? target.output;
|
|
484
512
|
target.completedAt = update.completedAt ?? target.completedAt;
|
|
485
513
|
}
|
|
486
|
-
function closeParentTurnForFinalAssistant(byId, assistant) {
|
|
487
|
-
if (!assistant.parentId || assistant.status !== "done")
|
|
488
|
-
return;
|
|
489
|
-
const parent = byId.get(assistant.parentId);
|
|
490
|
-
if (!parent || parent.type !== "agent.turn")
|
|
491
|
-
return;
|
|
492
|
-
parent.status = "done";
|
|
493
|
-
parent.completedAt = assistant.completedAt ?? assistant.startedAt ?? parent.completedAt;
|
|
494
|
-
}
|
|
495
514
|
function isInternalSessionOperation(action) {
|
|
496
515
|
return action === "session.fork" || action === "session.clone" || action === "session.switch";
|
|
497
516
|
}
|
|
@@ -512,6 +531,55 @@ function shouldKeepTranscriptEchoEvent(event, openTranscriptEventIds) {
|
|
|
512
531
|
function isStaleToolCallEchoEvent(event, sessionStatus) {
|
|
513
532
|
return sessionStatus !== "running" && event.type === "tool_call";
|
|
514
533
|
}
|
|
534
|
+
export function messageTurnTimingsFromEvents(events) {
|
|
535
|
+
const timings = new Map();
|
|
536
|
+
const completedEventIds = [];
|
|
537
|
+
const completedEventIdSet = new Set();
|
|
538
|
+
const ignoredEventIds = new Set();
|
|
539
|
+
for (const storedEvent of events) {
|
|
540
|
+
const event = storedEvent.payload;
|
|
541
|
+
if (event.type !== "message_started" && event.type !== "message_finished")
|
|
542
|
+
continue;
|
|
543
|
+
const eventId = typeof event.eventId === "string" ? event.eventId : undefined;
|
|
544
|
+
if (!eventId)
|
|
545
|
+
continue;
|
|
546
|
+
if (event.type === "message_started" && event.source === "service") {
|
|
547
|
+
ignoredEventIds.add(eventId);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (ignoredEventIds.has(eventId))
|
|
551
|
+
continue;
|
|
552
|
+
const timing = timings.get(eventId) ?? {};
|
|
553
|
+
if (event.type === "message_started") {
|
|
554
|
+
timing.userText ??= event.text;
|
|
555
|
+
timing.startedAt ??= storedEvent.createdAt;
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
timing.completedAt = storedEvent.createdAt;
|
|
559
|
+
if (!completedEventIdSet.has(eventId)) {
|
|
560
|
+
completedEventIds.push(eventId);
|
|
561
|
+
completedEventIdSet.add(eventId);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
timings.set(eventId, timing);
|
|
565
|
+
}
|
|
566
|
+
return completedEventIds.flatMap((eventId) => {
|
|
567
|
+
const timing = timings.get(eventId);
|
|
568
|
+
if (!timing?.completedAt)
|
|
569
|
+
return [];
|
|
570
|
+
const startedAtMs = parseTimestamp(timing.startedAt);
|
|
571
|
+
const completedAtMs = parseTimestamp(timing.completedAt);
|
|
572
|
+
return [{
|
|
573
|
+
eventId,
|
|
574
|
+
userText: timing.userText,
|
|
575
|
+
startedAt: timing.startedAt,
|
|
576
|
+
completedAt: timing.completedAt,
|
|
577
|
+
durationMs: startedAtMs === undefined || completedAtMs === undefined
|
|
578
|
+
? undefined
|
|
579
|
+
: Math.max(0, completedAtMs - startedAtMs),
|
|
580
|
+
}];
|
|
581
|
+
});
|
|
582
|
+
}
|
|
515
583
|
export function findOpenTranscriptEventIds(events, sessionStatus) {
|
|
516
584
|
if (sessionStatus !== "running")
|
|
517
585
|
return new Set();
|
|
@@ -626,12 +694,48 @@ function thinkingEventNodeId(event) {
|
|
|
626
694
|
function mergeToolEvent(target, update) {
|
|
627
695
|
target.status = update.status;
|
|
628
696
|
target.summary = update.summary ?? target.summary;
|
|
629
|
-
target.input = update
|
|
697
|
+
target.input = mergeDelegationInput(target, update);
|
|
630
698
|
target.output = update.output ?? target.output;
|
|
631
699
|
target.error = update.error ?? target.error;
|
|
632
700
|
target.completedAt = update.completedAt ?? target.completedAt;
|
|
633
701
|
target.linkedPiboSessionId = update.linkedPiboSessionId ?? target.linkedPiboSessionId;
|
|
634
702
|
}
|
|
703
|
+
function mergeSubagentSessionLink(target, update) {
|
|
704
|
+
target.summary = update.summary ?? target.summary;
|
|
705
|
+
target.input = mergeDelegationInput(target, update);
|
|
706
|
+
target.linkedPiboSessionId = update.linkedPiboSessionId ?? target.linkedPiboSessionId;
|
|
707
|
+
}
|
|
708
|
+
function findLegacySubagentLinkTarget(nodes, update) {
|
|
709
|
+
const delegations = [...nodes].reverse().filter((candidate) => candidate.type === "agent.delegation");
|
|
710
|
+
const agentName = delegationAgentName(update);
|
|
711
|
+
const candidates = delegations.filter((candidate) => !candidate.linkedPiboSessionId && delegationAgentName(candidate) === agentName);
|
|
712
|
+
const threadKey = delegationThreadKey(update.input);
|
|
713
|
+
const matchingCandidate = threadKey
|
|
714
|
+
? candidates.find((candidate) => delegationThreadKey(candidate.input) === threadKey)
|
|
715
|
+
: candidates.length === 1 ? candidates[0] : undefined;
|
|
716
|
+
if (matchingCandidate)
|
|
717
|
+
return matchingCandidate;
|
|
718
|
+
return delegations.find((candidate) => candidate.linkedPiboSessionId === update.linkedPiboSessionId);
|
|
719
|
+
}
|
|
720
|
+
function delegationAgentName(node) {
|
|
721
|
+
const input = isObjectRecord(node.input) ? node.input : undefined;
|
|
722
|
+
const value = typeof input?.subagentName === "string" ? input.subagentName : node.summary ?? node.title;
|
|
723
|
+
return typeof value === "string" ? value.replace(/^pibo_subagent_/, "").trim().toLowerCase() || undefined : undefined;
|
|
724
|
+
}
|
|
725
|
+
function delegationThreadKey(value) {
|
|
726
|
+
if (!isObjectRecord(value) || typeof value.threadKey !== "string")
|
|
727
|
+
return undefined;
|
|
728
|
+
return value.threadKey.trim() || undefined;
|
|
729
|
+
}
|
|
730
|
+
function mergeDelegationInput(target, update) {
|
|
731
|
+
if (target.type !== "agent.delegation" || !isObjectRecord(target.input) || !isObjectRecord(update.input)) {
|
|
732
|
+
return update.input ?? target.input;
|
|
733
|
+
}
|
|
734
|
+
return Object.fromEntries(Object.entries({ ...target.input, ...update.input }).filter(([, value]) => value !== undefined));
|
|
735
|
+
}
|
|
736
|
+
function isObjectRecord(value) {
|
|
737
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
738
|
+
}
|
|
635
739
|
function findLatestCompactionNode(nodes) {
|
|
636
740
|
return flattenTraceNodes([...nodes]).reverse().find((node) => node.type === "execution.compaction" && node.status === "running");
|
|
637
741
|
}
|
|
@@ -656,6 +760,12 @@ function stringifyPreview(value) {
|
|
|
656
760
|
return String(value);
|
|
657
761
|
}
|
|
658
762
|
}
|
|
763
|
+
function parseTimestamp(value) {
|
|
764
|
+
if (!value)
|
|
765
|
+
return undefined;
|
|
766
|
+
const timestamp = new Date(value).getTime();
|
|
767
|
+
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
768
|
+
}
|
|
659
769
|
function cryptoSafeId(value) {
|
|
660
770
|
return base64UrlEncode(new TextEncoder().encode(JSON.stringify(value))).slice(0, 48);
|
|
661
771
|
}
|
|
@@ -16,8 +16,10 @@ export function projectTranscriptEntries(entries, sessionStatus, openTranscriptE
|
|
|
16
16
|
}
|
|
17
17
|
return lastUserMessageIndex === -1 ? entries : entries.slice(0, lastUserMessageIndex);
|
|
18
18
|
}
|
|
19
|
-
export function traceNodesFromEntries(piboSessionId, entries) {
|
|
19
|
+
export function traceNodesFromEntries(piboSessionId, entries, turnTimings = []) {
|
|
20
20
|
const nodes = [];
|
|
21
|
+
const turnTimingAssignments = assignTranscriptTurnTimings(entries, turnTimings);
|
|
22
|
+
let assistantTurnIndex = 0;
|
|
21
23
|
for (let index = 0; index < entries.length; index += 1) {
|
|
22
24
|
const entry = entries[index];
|
|
23
25
|
if (entry.type === "message") {
|
|
@@ -27,7 +29,11 @@ export function traceNodesFromEntries(piboSessionId, entries) {
|
|
|
27
29
|
}
|
|
28
30
|
else if (role === "assistant" || role === "toolResult") {
|
|
29
31
|
const turn = collectAssistantTurn(entries, index);
|
|
30
|
-
|
|
32
|
+
const hasAssistant = turn.entries.some(({ entry: turnEntry }) => messageRole(turnEntry) === "assistant");
|
|
33
|
+
const timing = hasAssistant ? turnTimingAssignments[assistantTurnIndex] : undefined;
|
|
34
|
+
nodes.push(...createAssistantTurnNodes(piboSessionId, turn.entries, timing));
|
|
35
|
+
if (hasAssistant)
|
|
36
|
+
assistantTurnIndex += 1;
|
|
31
37
|
index = turn.nextIndex - 1;
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -64,6 +70,65 @@ function messageParts(entry) {
|
|
|
64
70
|
return [{ type: "text", text: content }];
|
|
65
71
|
return Array.isArray(content) ? content : [];
|
|
66
72
|
}
|
|
73
|
+
function assignTranscriptTurnTimings(entries, turnTimings) {
|
|
74
|
+
const transcriptTurns = [];
|
|
75
|
+
let latestUserText;
|
|
76
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
77
|
+
const entry = entries[index];
|
|
78
|
+
if (entry.type !== "message")
|
|
79
|
+
continue;
|
|
80
|
+
const role = messageRole(entry);
|
|
81
|
+
if (role === "user") {
|
|
82
|
+
latestUserText = normalizedPrompt(extractText(messageContent(entry)));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (role !== "assistant" && role !== "toolResult")
|
|
86
|
+
continue;
|
|
87
|
+
const turn = collectAssistantTurn(entries, index);
|
|
88
|
+
const lastAssistant = [...turn.entries].reverse().find(({ entry: turnEntry }) => messageRole(turnEntry) === "assistant");
|
|
89
|
+
if (lastAssistant) {
|
|
90
|
+
transcriptTurns.push({ prompt: latestUserText, assistantAt: parsedTimestamp(lastAssistant.entry.timestamp) });
|
|
91
|
+
}
|
|
92
|
+
index = turn.nextIndex - 1;
|
|
93
|
+
}
|
|
94
|
+
const assignments = Array(transcriptTurns.length).fill(undefined);
|
|
95
|
+
let timingCursor = turnTimings.length - 1;
|
|
96
|
+
for (let turnIndex = transcriptTurns.length - 1; turnIndex >= 0; turnIndex -= 1) {
|
|
97
|
+
const transcriptTurn = transcriptTurns[turnIndex];
|
|
98
|
+
if (!transcriptTurn?.prompt)
|
|
99
|
+
continue;
|
|
100
|
+
let matchedIndex;
|
|
101
|
+
let matchedDistance = Number.POSITIVE_INFINITY;
|
|
102
|
+
for (let timingIndex = timingCursor; timingIndex >= 0; timingIndex -= 1) {
|
|
103
|
+
const timing = turnTimings[timingIndex];
|
|
104
|
+
if (normalizedPrompt(timing?.userText) !== transcriptTurn.prompt)
|
|
105
|
+
continue;
|
|
106
|
+
const completedAt = parsedTimestamp(timing?.completedAt);
|
|
107
|
+
const distance = completedAt === undefined || transcriptTurn.assistantAt === undefined
|
|
108
|
+
? Number.POSITIVE_INFINITY
|
|
109
|
+
: Math.abs(completedAt - transcriptTurn.assistantAt);
|
|
110
|
+
if (matchedIndex === undefined || distance < matchedDistance) {
|
|
111
|
+
matchedIndex = timingIndex;
|
|
112
|
+
matchedDistance = distance;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (matchedIndex === undefined)
|
|
116
|
+
continue;
|
|
117
|
+
assignments[turnIndex] = turnTimings[matchedIndex];
|
|
118
|
+
timingCursor = matchedIndex - 1;
|
|
119
|
+
}
|
|
120
|
+
return assignments;
|
|
121
|
+
}
|
|
122
|
+
function normalizedPrompt(value) {
|
|
123
|
+
const normalized = value?.replace(/\s+/g, " ").trim();
|
|
124
|
+
return normalized || undefined;
|
|
125
|
+
}
|
|
126
|
+
function parsedTimestamp(value) {
|
|
127
|
+
if (!value)
|
|
128
|
+
return undefined;
|
|
129
|
+
const timestamp = new Date(value).getTime();
|
|
130
|
+
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
131
|
+
}
|
|
67
132
|
function collectAssistantTurn(entries, startIndex) {
|
|
68
133
|
const turnEntries = [];
|
|
69
134
|
let index = startIndex;
|
|
@@ -110,7 +175,7 @@ function createUserMessageNode(piboSessionId, entry, content, entryIndex) {
|
|
|
110
175
|
children: [],
|
|
111
176
|
};
|
|
112
177
|
}
|
|
113
|
-
function createAssistantTurnNodes(piboSessionId, entries) {
|
|
178
|
+
function createAssistantTurnNodes(piboSessionId, entries, timing) {
|
|
114
179
|
const firstAssistant = entries.find(({ entry }) => messageRole(entry) === "assistant");
|
|
115
180
|
if (!firstAssistant)
|
|
116
181
|
return [];
|
|
@@ -142,14 +207,12 @@ function createAssistantTurnNodes(piboSessionId, entries) {
|
|
|
142
207
|
error: responseError,
|
|
143
208
|
children: [],
|
|
144
209
|
startedAt: entry.timestamp,
|
|
145
|
-
completedAt: entry.timestamp,
|
|
146
210
|
});
|
|
147
211
|
orderedNodes.push(responseNode);
|
|
148
212
|
}
|
|
149
213
|
else {
|
|
150
214
|
responseNode.summary = `${typeof responseNode.summary === "string" ? responseNode.summary : ""}${typed.text}`;
|
|
151
215
|
responseNode.output = `${typeof responseNode.output === "string" ? responseNode.output : ""}${typed.text}`;
|
|
152
|
-
responseNode.completedAt = entry.timestamp;
|
|
153
216
|
}
|
|
154
217
|
}
|
|
155
218
|
else if (typed.type === "toolCall" && typeof typed.id === "string" && typeof typed.name === "string") {
|
|
@@ -163,6 +226,11 @@ function createAssistantTurnNodes(piboSessionId, entries) {
|
|
|
163
226
|
responseNode.error = responseError;
|
|
164
227
|
}
|
|
165
228
|
}
|
|
229
|
+
const finalNode = orderedNodes.at(-1);
|
|
230
|
+
if (finalNode?.type === "assistant.message" && finalNode.status === "done") {
|
|
231
|
+
finalNode.completedAt = timing?.completedAt ?? finalNode.startedAt;
|
|
232
|
+
finalNode.durationMs = timing?.durationMs;
|
|
233
|
+
}
|
|
166
234
|
return orderedNodes;
|
|
167
235
|
}
|
|
168
236
|
function createReasoningNode(piboSessionId, entry, entryIndex, index, thinking) {
|