@ellipsis-dev/sdk 0.27.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/{chunk-RMPAMUAE.js → chunk-K252XHLI.js} +13 -3
- package/dist/index.d.ts +3069 -236
- package/dist/index.js +2032 -224
- package/dist/store/index.d.ts +54 -4
- package/dist/store/index.js +716 -127
- package/dist/stream/index.d.ts +2 -2
- package/dist/stream/index.js +1 -1
- package/dist/types-DrPhhfOb.d.ts +25673 -0
- package/package.json +1 -1
- package/schema/collection_frames.schema.json +4158 -0
- package/schema/frames.schema.json +10183 -3056
- package/schema/lifecycle.schema.json +18 -0
- package/schema/openapi.v1.json +36154 -9289
- package/schema/trigger_fields.schema.json +1 -1
- package/dist/types-ClEF_IA9.d.ts +0 -12789
package/dist/store/index.js
CHANGED
|
@@ -1,6 +1,219 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SESSION_STREAM_PROTOCOL_VERSION
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-K252XHLI.js";
|
|
4
|
+
|
|
5
|
+
// src/store/claude.ts
|
|
6
|
+
function claudePayload(record) {
|
|
7
|
+
if (record.kind === "claude_sdk") return record.payload;
|
|
8
|
+
if (record.kind !== "claude_code") return null;
|
|
9
|
+
const event = record.payload;
|
|
10
|
+
switch (event.type) {
|
|
11
|
+
case "assistant":
|
|
12
|
+
return {
|
|
13
|
+
...event.message,
|
|
14
|
+
kind: "assistant",
|
|
15
|
+
message_id: event.message.id,
|
|
16
|
+
parent_tool_use_id: event.parent_tool_use_id
|
|
17
|
+
};
|
|
18
|
+
case "user":
|
|
19
|
+
return {
|
|
20
|
+
kind: "user",
|
|
21
|
+
content: event.message.content,
|
|
22
|
+
uuid: event.uuid,
|
|
23
|
+
parent_tool_use_id: event.parent_tool_use_id
|
|
24
|
+
};
|
|
25
|
+
case "system":
|
|
26
|
+
return {
|
|
27
|
+
kind: "system",
|
|
28
|
+
subtype: event.subtype,
|
|
29
|
+
data: event,
|
|
30
|
+
session_id: event.session_id,
|
|
31
|
+
uuid: event.uuid
|
|
32
|
+
};
|
|
33
|
+
case "result":
|
|
34
|
+
return {
|
|
35
|
+
...event,
|
|
36
|
+
kind: "result",
|
|
37
|
+
cost_usd: event.total_cost_usd,
|
|
38
|
+
model_usage: event.modelUsage
|
|
39
|
+
};
|
|
40
|
+
case "rate_limit_event":
|
|
41
|
+
return {
|
|
42
|
+
kind: "rate_limit",
|
|
43
|
+
status: event.rate_limit_info.status,
|
|
44
|
+
rate_limit_type: event.rate_limit_info.rateLimitType,
|
|
45
|
+
resets_at: event.rate_limit_info.resetsAt,
|
|
46
|
+
utilization: event.rate_limit_info.utilization
|
|
47
|
+
};
|
|
48
|
+
default:
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/store/codexAppServer.ts
|
|
54
|
+
function codexAppServerContent(event) {
|
|
55
|
+
if (event.method === "turn/completed") {
|
|
56
|
+
return { kind: "end", isError: event.params.turn.status !== "completed" };
|
|
57
|
+
}
|
|
58
|
+
if (event.method === "error") {
|
|
59
|
+
return event.params.willRetry ? null : { kind: "error", text: event.params.error.message };
|
|
60
|
+
}
|
|
61
|
+
if (event.method !== "item/completed") return null;
|
|
62
|
+
const item = event.params.item;
|
|
63
|
+
switch (item.type) {
|
|
64
|
+
case "agentMessage":
|
|
65
|
+
return { kind: "assistant", text: item.text, phase: item.phase };
|
|
66
|
+
case "plan":
|
|
67
|
+
return { kind: "assistant", text: item.text, phase: "commentary" };
|
|
68
|
+
case "reasoning":
|
|
69
|
+
return {
|
|
70
|
+
kind: "thinking",
|
|
71
|
+
text: (item.summary?.length ? item.summary : item.content ?? []).join(
|
|
72
|
+
"\n"
|
|
73
|
+
)
|
|
74
|
+
};
|
|
75
|
+
case "commandExecution":
|
|
76
|
+
return {
|
|
77
|
+
kind: "tool",
|
|
78
|
+
name: "Bash",
|
|
79
|
+
input: { command: item.command, commandActions: item.commandActions },
|
|
80
|
+
summary: item.command,
|
|
81
|
+
result: item.aggregatedOutput?.trim() || "(no output)",
|
|
82
|
+
isError: item.status === "failed" || item.status === "declined" || typeof item.exitCode === "number" && item.exitCode !== 0
|
|
83
|
+
};
|
|
84
|
+
case "fileChange":
|
|
85
|
+
return {
|
|
86
|
+
kind: "tool",
|
|
87
|
+
name: "Edit",
|
|
88
|
+
input: { changes: item.changes },
|
|
89
|
+
summary: item.changes.map((change) => change.path).join(", "),
|
|
90
|
+
result: item.changes.map((change) => change.diff).join("\n"),
|
|
91
|
+
isError: item.status === "failed" || item.status === "declined"
|
|
92
|
+
};
|
|
93
|
+
case "webSearch":
|
|
94
|
+
return {
|
|
95
|
+
kind: "tool",
|
|
96
|
+
name: "WebSearch",
|
|
97
|
+
input: { query: item.query },
|
|
98
|
+
summary: item.query,
|
|
99
|
+
result: null,
|
|
100
|
+
isError: false
|
|
101
|
+
};
|
|
102
|
+
default:
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/store/progress.ts
|
|
108
|
+
function messageProgress(records) {
|
|
109
|
+
const messages = /* @__PURE__ */ new Map();
|
|
110
|
+
const pending = /* @__PURE__ */ new Set();
|
|
111
|
+
const turns = /* @__PURE__ */ new Map();
|
|
112
|
+
let starting = "queued";
|
|
113
|
+
let ready = false;
|
|
114
|
+
const getTurn = (id) => {
|
|
115
|
+
let turn = turns.get(id);
|
|
116
|
+
if (!turn) {
|
|
117
|
+
turn = {
|
|
118
|
+
timing: { id, startedAt: null, completedAt: null },
|
|
119
|
+
phase: ready ? "working" : "starting",
|
|
120
|
+
messages: /* @__PURE__ */ new Set()
|
|
121
|
+
};
|
|
122
|
+
turns.set(id, turn);
|
|
123
|
+
}
|
|
124
|
+
return turn;
|
|
125
|
+
};
|
|
126
|
+
const updatePending = () => {
|
|
127
|
+
for (const id of pending) messages.get(id).phase = starting;
|
|
128
|
+
};
|
|
129
|
+
const attach = (messageId, turnId) => {
|
|
130
|
+
const message = messages.get(messageId);
|
|
131
|
+
if (!message) return;
|
|
132
|
+
const turn = getTurn(turnId);
|
|
133
|
+
if (!turn.messages.has(messageId)) {
|
|
134
|
+
turn.messages.add(messageId);
|
|
135
|
+
message.turns.push(turn.timing);
|
|
136
|
+
}
|
|
137
|
+
pending.delete(messageId);
|
|
138
|
+
message.phase = turn.phase;
|
|
139
|
+
starting = "queued";
|
|
140
|
+
updatePending();
|
|
141
|
+
};
|
|
142
|
+
for (const record of records) {
|
|
143
|
+
if (record.kind === "platform") {
|
|
144
|
+
const p = record.payload;
|
|
145
|
+
const messageId = typeof p.message_id === "string" ? p.message_id : null;
|
|
146
|
+
const turnId = typeof p.turn_id === "string" ? p.turn_id : record.turn_id;
|
|
147
|
+
switch (record.record_type) {
|
|
148
|
+
case "message_received":
|
|
149
|
+
if (messageId && !messages.has(messageId)) {
|
|
150
|
+
messages.set(messageId, { phase: starting, turns: [] });
|
|
151
|
+
pending.add(messageId);
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
case "message_delivered":
|
|
155
|
+
if (messageId && turnId) attach(messageId, turnId);
|
|
156
|
+
break;
|
|
157
|
+
case "message_requeued":
|
|
158
|
+
if (messageId && messages.has(messageId)) {
|
|
159
|
+
pending.add(messageId);
|
|
160
|
+
messages.get(messageId).phase = "queued";
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
case "session_starting":
|
|
164
|
+
case "session_retrying":
|
|
165
|
+
ready = false;
|
|
166
|
+
starting = typeof p.wake_index === "number" && p.wake_index > 0 ? "waking" : "starting";
|
|
167
|
+
updatePending();
|
|
168
|
+
break;
|
|
169
|
+
case "session_resumed":
|
|
170
|
+
starting = "starting";
|
|
171
|
+
updatePending();
|
|
172
|
+
break;
|
|
173
|
+
case "turn_started":
|
|
174
|
+
if (turnId) getTurn(turnId).timing.startedAt = record.created_at;
|
|
175
|
+
break;
|
|
176
|
+
case "turn_completed":
|
|
177
|
+
case "turn_failed":
|
|
178
|
+
if (turnId) {
|
|
179
|
+
const turn = getTurn(turnId);
|
|
180
|
+
turn.timing.completedAt = record.created_at;
|
|
181
|
+
turn.phase = null;
|
|
182
|
+
for (const id of turn.messages) {
|
|
183
|
+
if (!pending.has(id)) messages.get(id).phase = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
starting = "queued";
|
|
187
|
+
break;
|
|
188
|
+
case "session_idle":
|
|
189
|
+
case "session_closed":
|
|
190
|
+
case "session_cancelled":
|
|
191
|
+
ready = false;
|
|
192
|
+
starting = "queued";
|
|
193
|
+
for (const id of pending) messages.get(id).phase = null;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const claude = claudePayload(record);
|
|
199
|
+
const agentStarted = record.kind === "codex_app_server" && record.payload.method === "turn/started" || record.kind === "codex" && record.payload.type === "turn.started" || claude != null && (claude.kind === "assistant" || claude.kind === "user" || claude.kind === "system" && claude.subtype === "init");
|
|
200
|
+
if (agentStarted) {
|
|
201
|
+
ready = true;
|
|
202
|
+
if (record.turn_id) {
|
|
203
|
+
const turn = getTurn(record.turn_id);
|
|
204
|
+
if (turn.timing.completedAt == null) {
|
|
205
|
+
turn.timing.startedAt ??= record.created_at;
|
|
206
|
+
turn.phase = "working";
|
|
207
|
+
for (const id of turn.messages) messages.get(id).phase = "working";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (record.session_message_id && record.turn_id) {
|
|
212
|
+
attach(record.session_message_id, record.turn_id);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return messages;
|
|
216
|
+
}
|
|
4
217
|
|
|
5
218
|
// src/store/lifecycle.ts
|
|
6
219
|
function sandboxOutputStep(payload) {
|
|
@@ -120,8 +333,8 @@ function lifecycleText(recordType, payload) {
|
|
|
120
333
|
}
|
|
121
334
|
|
|
122
335
|
// src/store/transcript.ts
|
|
123
|
-
function oneLine(
|
|
124
|
-
const collapsed =
|
|
336
|
+
function oneLine(text2, max) {
|
|
337
|
+
const collapsed = text2.replace(/\s+/g, " ").trim();
|
|
125
338
|
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 3)}...`;
|
|
126
339
|
}
|
|
127
340
|
function summarizeToolInput(name, input) {
|
|
@@ -202,11 +415,11 @@ function eventToItems(event, keyBase, options = {}) {
|
|
|
202
415
|
return items;
|
|
203
416
|
}
|
|
204
417
|
if (event.kind === "rate_limit") return items;
|
|
205
|
-
const blocks = blocksOf(event.content);
|
|
418
|
+
const blocks = blocksOf(event.content ?? []);
|
|
206
419
|
if (event.kind === "user") {
|
|
207
420
|
for (const block of blocks) {
|
|
208
421
|
if (block.type === "tool_result") {
|
|
209
|
-
const body = toolResultText(block.content);
|
|
422
|
+
const body = toolResultText(block.content ?? null);
|
|
210
423
|
push({
|
|
211
424
|
kind: "tool_result",
|
|
212
425
|
gutter: "\u23BF",
|
|
@@ -234,14 +447,14 @@ function eventToItems(event, keyBase, options = {}) {
|
|
|
234
447
|
spaceBefore: true
|
|
235
448
|
});
|
|
236
449
|
} else if (block.type === "tool_use" || block.type === "server_tool_use") {
|
|
237
|
-
const summary = summarizeToolInput(block.name, block.input);
|
|
450
|
+
const summary = summarizeToolInput(block.name, block.input ?? {});
|
|
238
451
|
push({
|
|
239
452
|
kind: "tool",
|
|
240
453
|
gutter: "\u25CF",
|
|
241
454
|
text: block.name,
|
|
242
455
|
detail: summary ? `(${summary})` : void 0,
|
|
243
456
|
spaceBefore: true,
|
|
244
|
-
tool: { name: block.name, input: block.input }
|
|
457
|
+
tool: { name: block.name, input: block.input ?? {} }
|
|
245
458
|
});
|
|
246
459
|
} else if (block.type === "text" && block.text.trim()) {
|
|
247
460
|
push({ kind: "assistant", text: block.text.trim(), spaceBefore: true });
|
|
@@ -276,20 +489,20 @@ function codexEventToItems(event, keyBase) {
|
|
|
276
489
|
const item = event.item;
|
|
277
490
|
switch (item.type) {
|
|
278
491
|
case "agent_message": {
|
|
279
|
-
const
|
|
280
|
-
if (
|
|
492
|
+
const text2 = (item.text ?? "").trim();
|
|
493
|
+
if (text2) push({ kind: "assistant", text: text2, spaceBefore: true });
|
|
281
494
|
break;
|
|
282
495
|
}
|
|
283
496
|
case "reasoning": {
|
|
284
497
|
const r = item;
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
287
|
-
push({ kind: "thinking", gutter: "\u273B", text, spaceBefore: true });
|
|
498
|
+
const text2 = (r.text ?? r.summary ?? "").trim();
|
|
499
|
+
if (text2)
|
|
500
|
+
push({ kind: "thinking", gutter: "\u273B", text: text2, spaceBefore: true });
|
|
288
501
|
break;
|
|
289
502
|
}
|
|
290
503
|
case "command_execution": {
|
|
291
504
|
const c = item;
|
|
292
|
-
const summary = oneLine(c.command, 100);
|
|
505
|
+
const summary = oneLine(c.command ?? "", 100);
|
|
293
506
|
push({
|
|
294
507
|
kind: "tool",
|
|
295
508
|
gutter: "\u25CF",
|
|
@@ -361,14 +574,63 @@ function codexMcpToolName(item) {
|
|
|
361
574
|
return item.server && item.tool ? `mcp__${item.server}__${item.tool}` : item.tool ?? "mcp_tool_call";
|
|
362
575
|
}
|
|
363
576
|
function recordToItems(record, keyBase, options = {}) {
|
|
364
|
-
switch (record.
|
|
365
|
-
case "claude_sdk
|
|
366
|
-
|
|
367
|
-
|
|
577
|
+
switch (record.kind) {
|
|
578
|
+
case "claude_sdk":
|
|
579
|
+
case "claude_code": {
|
|
580
|
+
const payload = claudePayload(record);
|
|
581
|
+
return payload ? eventToItems(payload, keyBase, options) : [];
|
|
582
|
+
}
|
|
583
|
+
case "codex":
|
|
368
584
|
return codexEventToItems(record.payload, keyBase);
|
|
369
|
-
case "
|
|
370
|
-
const
|
|
371
|
-
|
|
585
|
+
case "codex_app_server": {
|
|
586
|
+
const content = codexAppServerContent(record.payload);
|
|
587
|
+
if (!content) return [];
|
|
588
|
+
if (content.kind === "end") {
|
|
589
|
+
return content.isError ? [
|
|
590
|
+
{
|
|
591
|
+
key: keyBase,
|
|
592
|
+
kind: "summary",
|
|
593
|
+
text: "turn ended with an error",
|
|
594
|
+
isError: true,
|
|
595
|
+
spaceBefore: true
|
|
596
|
+
}
|
|
597
|
+
] : [];
|
|
598
|
+
}
|
|
599
|
+
if (content.kind === "tool") {
|
|
600
|
+
const summary = oneLine(content.summary, 100);
|
|
601
|
+
return [
|
|
602
|
+
{
|
|
603
|
+
key: `${keyBase}:0`,
|
|
604
|
+
kind: "tool",
|
|
605
|
+
gutter: "\u25CF",
|
|
606
|
+
text: content.name,
|
|
607
|
+
detail: summary ? `(${summary})` : void 0,
|
|
608
|
+
spaceBefore: true,
|
|
609
|
+
tool: { name: content.name, input: content.input ?? void 0 }
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
key: `${keyBase}:1`,
|
|
613
|
+
kind: "tool_result",
|
|
614
|
+
gutter: "\u23BF",
|
|
615
|
+
text: content.result || "(completed)",
|
|
616
|
+
isError: content.isError,
|
|
617
|
+
spaceBefore: false
|
|
618
|
+
}
|
|
619
|
+
];
|
|
620
|
+
}
|
|
621
|
+
return content.text.trim() ? [
|
|
622
|
+
{
|
|
623
|
+
key: keyBase,
|
|
624
|
+
kind: content.kind,
|
|
625
|
+
text: content.text.trim(),
|
|
626
|
+
isError: content.kind === "error",
|
|
627
|
+
spaceBefore: true
|
|
628
|
+
}
|
|
629
|
+
] : [];
|
|
630
|
+
}
|
|
631
|
+
case "platform": {
|
|
632
|
+
const text2 = lifecycleText(record.record_type, record.payload);
|
|
633
|
+
return text2 ? [{ key: keyBase, kind: "notice", text: text2, spaceBefore: true }] : [];
|
|
372
634
|
}
|
|
373
635
|
default:
|
|
374
636
|
return [];
|
|
@@ -408,9 +670,11 @@ function foldLabel(group, settled) {
|
|
|
408
670
|
return `Ran ${n} tool call${plural} (${shown})`;
|
|
409
671
|
}
|
|
410
672
|
function foldRun(group, loading) {
|
|
673
|
+
const thinkingOnly = group.every((i) => i.kind === "thinking");
|
|
411
674
|
return {
|
|
412
675
|
key: `grp:${group[0].key}`,
|
|
413
|
-
kind: "notice",
|
|
676
|
+
kind: thinkingOnly ? "thinking" : "notice",
|
|
677
|
+
...thinkingOnly ? { gutter: "\u273B" } : {},
|
|
414
678
|
text: foldLabel(group, !loading),
|
|
415
679
|
spaceBefore: true,
|
|
416
680
|
...loading ? { loading } : {}
|
|
@@ -437,9 +701,9 @@ function collapseToolRuns(items, { working = false } = {}) {
|
|
|
437
701
|
flush(true);
|
|
438
702
|
return out;
|
|
439
703
|
}
|
|
440
|
-
function clampLines(
|
|
441
|
-
const lines =
|
|
442
|
-
if (lines.length <= maxLines) return { body:
|
|
704
|
+
function clampLines(text2, maxLines) {
|
|
705
|
+
const lines = text2.split("\n");
|
|
706
|
+
if (lines.length <= maxLines) return { body: text2, more: 0 };
|
|
443
707
|
return {
|
|
444
708
|
body: lines.slice(0, maxLines).join("\n"),
|
|
445
709
|
more: lines.length - maxLines
|
|
@@ -481,20 +745,29 @@ var SANDBOX_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
|
481
745
|
"sandbox_output",
|
|
482
746
|
"sandbox_ready"
|
|
483
747
|
]);
|
|
484
|
-
function groupRecordsToChatTurns(records) {
|
|
748
|
+
function groupRecordsToChatTurns(records, session) {
|
|
485
749
|
const turns = [];
|
|
486
750
|
const toolNodeByUseId = /* @__PURE__ */ new Map();
|
|
487
751
|
const userTurnByMessageId = /* @__PURE__ */ new Map();
|
|
488
|
-
const
|
|
752
|
+
const deliveredTurnByMessageId = /* @__PURE__ */ new Map();
|
|
753
|
+
const failedTurnIds = /* @__PURE__ */ new Set();
|
|
754
|
+
const activeMessageByTurnId = /* @__PURE__ */ new Map();
|
|
755
|
+
let lastDeliveredMessageId;
|
|
489
756
|
let seq = 0;
|
|
490
757
|
const nextKey = (base) => `${base}:${seq++}`;
|
|
491
758
|
let openIdx = -1;
|
|
492
759
|
let initCount = 0;
|
|
493
760
|
let pendingResume = false;
|
|
494
|
-
const
|
|
761
|
+
const openSessionTurn = (record) => {
|
|
762
|
+
const messageId = (record.turn_id ? activeMessageByTurnId.get(record.turn_id) : void 0) ?? lastDeliveredMessageId;
|
|
763
|
+
if (openIdx >= 0 && (turns[openIdx].turnId !== (record.turn_id ?? void 0) || turns[openIdx].messageId !== messageId)) {
|
|
764
|
+
openIdx = -1;
|
|
765
|
+
}
|
|
495
766
|
if (openIdx < 0) {
|
|
496
767
|
turns.push({
|
|
497
768
|
key: nextKey(record.id),
|
|
769
|
+
messageId,
|
|
770
|
+
turnId: record.turn_id ?? void 0,
|
|
498
771
|
role: "assistant",
|
|
499
772
|
nodes: [],
|
|
500
773
|
startedAt: record.created_at,
|
|
@@ -511,7 +784,7 @@ function groupRecordsToChatTurns(records) {
|
|
|
511
784
|
return turns[openIdx];
|
|
512
785
|
};
|
|
513
786
|
for (const record of records) {
|
|
514
|
-
if (record.
|
|
787
|
+
if (record.kind === "platform") {
|
|
515
788
|
const messageId = typeof record.payload.message_id === "string" ? record.payload.message_id : null;
|
|
516
789
|
if (record.record_type === "message_received" && messageId) {
|
|
517
790
|
const body = typeof record.payload.body === "string" ? record.payload.body : "";
|
|
@@ -520,6 +793,7 @@ function groupRecordsToChatTurns(records) {
|
|
|
520
793
|
resumed: false,
|
|
521
794
|
isError: false,
|
|
522
795
|
key: `msg:${messageId}`,
|
|
796
|
+
messageId,
|
|
523
797
|
role: "user",
|
|
524
798
|
nodes: [{ key: `msg:${messageId}`, kind: "user", text: body.trim() }],
|
|
525
799
|
startedAt: record.created_at,
|
|
@@ -533,19 +807,24 @@ function groupRecordsToChatTurns(records) {
|
|
|
533
807
|
continue;
|
|
534
808
|
}
|
|
535
809
|
if (record.record_type === "message_delivered" && messageId) {
|
|
536
|
-
if (typeof record.payload.turn_id === "string")
|
|
537
|
-
|
|
810
|
+
if (typeof record.payload.turn_id === "string") {
|
|
811
|
+
deliveredTurnByMessageId.set(messageId, record.payload.turn_id);
|
|
812
|
+
activeMessageByTurnId.set(record.payload.turn_id, messageId);
|
|
813
|
+
lastDeliveredMessageId = messageId;
|
|
814
|
+
}
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
if (record.record_type === "message_requeued" && messageId) {
|
|
818
|
+
deliveredTurnByMessageId.delete(messageId);
|
|
538
819
|
continue;
|
|
539
820
|
}
|
|
540
821
|
if (record.record_type === "turn_failed") {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const userTurn = owner ? userTurnByMessageId.get(owner) : void 0;
|
|
544
|
-
if (userTurn) userTurn.isError = true;
|
|
822
|
+
if (typeof record.payload.turn_id === "string")
|
|
823
|
+
failedTurnIds.add(record.payload.turn_id);
|
|
545
824
|
continue;
|
|
546
825
|
}
|
|
547
|
-
const
|
|
548
|
-
if (!
|
|
826
|
+
const text2 = lifecycleText(record.record_type, record.payload);
|
|
827
|
+
if (!text2) continue;
|
|
549
828
|
const isSandbox = SANDBOX_RECORD_TYPES.has(record.record_type);
|
|
550
829
|
const isSandboxOutput = record.record_type === "sandbox_output";
|
|
551
830
|
const step = isSandboxOutput ? sandboxOutputStep(record.payload) : void 0;
|
|
@@ -566,13 +845,13 @@ function groupRecordsToChatTurns(records) {
|
|
|
566
845
|
if (foldTarget) {
|
|
567
846
|
const last = foldTarget.nodes[foldTarget.nodes.length - 1];
|
|
568
847
|
if (isSandboxOutput && last && last.kind === "lifecycle" && last.recordType === "sandbox_output" && last.step === step) {
|
|
569
|
-
last.text =
|
|
848
|
+
last.text = text2;
|
|
570
849
|
last.lines = [...last.lines ?? [], ...lines ?? []];
|
|
571
850
|
} else {
|
|
572
851
|
foldTarget.nodes.push({
|
|
573
852
|
key: nextKey(record.id),
|
|
574
853
|
kind: "lifecycle",
|
|
575
|
-
text,
|
|
854
|
+
text: text2,
|
|
576
855
|
recordType: record.record_type,
|
|
577
856
|
step,
|
|
578
857
|
lines,
|
|
@@ -589,7 +868,7 @@ function groupRecordsToChatTurns(records) {
|
|
|
589
868
|
{
|
|
590
869
|
key: nextKey(record.id),
|
|
591
870
|
kind: "lifecycle",
|
|
592
|
-
text,
|
|
871
|
+
text: text2,
|
|
593
872
|
recordType: record.record_type,
|
|
594
873
|
step,
|
|
595
874
|
lines,
|
|
@@ -606,9 +885,14 @@ function groupRecordsToChatTurns(records) {
|
|
|
606
885
|
});
|
|
607
886
|
continue;
|
|
608
887
|
}
|
|
609
|
-
if (record.
|
|
888
|
+
if (record.session_message_id) {
|
|
889
|
+
lastDeliveredMessageId = record.session_message_id;
|
|
890
|
+
if (record.turn_id)
|
|
891
|
+
activeMessageByTurnId.set(record.turn_id, record.session_message_id);
|
|
892
|
+
}
|
|
893
|
+
if (record.kind === "codex") {
|
|
610
894
|
codexEventIntoTurns(record, record.payload, {
|
|
611
|
-
|
|
895
|
+
openSessionTurn,
|
|
612
896
|
closeTurn: (completedAt, isError) => {
|
|
613
897
|
if (openIdx >= 0) {
|
|
614
898
|
turns[openIdx].completedAt = completedAt;
|
|
@@ -620,7 +904,42 @@ function groupRecordsToChatTurns(records) {
|
|
|
620
904
|
});
|
|
621
905
|
continue;
|
|
622
906
|
}
|
|
623
|
-
|
|
907
|
+
if (record.kind === "codex_app_server") {
|
|
908
|
+
const content = codexAppServerContent(record.payload);
|
|
909
|
+
if (!content) continue;
|
|
910
|
+
if (content.kind === "end") {
|
|
911
|
+
if (openIdx >= 0) {
|
|
912
|
+
turns[openIdx].completedAt = record.created_at;
|
|
913
|
+
if (content.isError) turns[openIdx].isError = true;
|
|
914
|
+
openIdx = -1;
|
|
915
|
+
}
|
|
916
|
+
} else if (content.kind === "tool") {
|
|
917
|
+
openSessionTurn(record).nodes.push({
|
|
918
|
+
key: nextKey(record.id),
|
|
919
|
+
kind: "tool",
|
|
920
|
+
name: content.name,
|
|
921
|
+
input: content.input,
|
|
922
|
+
summary: oneLine(content.summary, 100),
|
|
923
|
+
result: content.result,
|
|
924
|
+
isError: content.isError,
|
|
925
|
+
startedAt: record.created_at,
|
|
926
|
+
completedAt: record.created_at
|
|
927
|
+
});
|
|
928
|
+
} else if (content.text.trim()) {
|
|
929
|
+
const turn2 = openSessionTurn(record);
|
|
930
|
+
if (content.kind === "error") turn2.isError = true;
|
|
931
|
+
turn2.nodes.push({
|
|
932
|
+
key: nextKey(record.id),
|
|
933
|
+
kind: content.kind === "error" ? "assistant" : content.kind,
|
|
934
|
+
text: content.text.trim(),
|
|
935
|
+
createdAt: record.created_at,
|
|
936
|
+
...content.kind === "assistant" && content.phase != null ? { phase: content.phase } : {}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
const data = claudePayload(record);
|
|
942
|
+
if (!data) continue;
|
|
624
943
|
if (data.kind === "system") {
|
|
625
944
|
if (isInitEvent(data)) {
|
|
626
945
|
initCount += 1;
|
|
@@ -666,11 +985,14 @@ function groupRecordsToChatTurns(records) {
|
|
|
666
985
|
}
|
|
667
986
|
for (const block of content) {
|
|
668
987
|
if (block.type !== "tool_result") continue;
|
|
669
|
-
const body = toolResultText(block.content) || "(no output)";
|
|
988
|
+
const body = toolResultText(block.content ?? null) || "(no output)";
|
|
670
989
|
const useId = block.tool_use_id;
|
|
671
990
|
const node = toolNodeByUseId.get(useId);
|
|
672
991
|
if (node) {
|
|
673
992
|
node.result = body;
|
|
993
|
+
if (record.kind === "claude_code" || record.kind === "claude_sdk") {
|
|
994
|
+
node.resultDetails = record.payload.tool_use_result;
|
|
995
|
+
}
|
|
674
996
|
node.isError = !!block.is_error;
|
|
675
997
|
node.completedAt = record.created_at;
|
|
676
998
|
} else {
|
|
@@ -685,18 +1007,18 @@ function groupRecordsToChatTurns(records) {
|
|
|
685
1007
|
startedAt: null,
|
|
686
1008
|
completedAt: record.created_at
|
|
687
1009
|
};
|
|
688
|
-
|
|
1010
|
+
openSessionTurn(record).nodes.push(orphan);
|
|
689
1011
|
toolNodeByUseId.set(useId, orphan);
|
|
690
1012
|
}
|
|
691
1013
|
}
|
|
692
1014
|
continue;
|
|
693
1015
|
}
|
|
694
1016
|
if (data.kind !== "assistant") continue;
|
|
695
|
-
const turn =
|
|
1017
|
+
const turn = openSessionTurn(record);
|
|
696
1018
|
if (data.usage && typeof data.usage.output_tokens === "number") {
|
|
697
1019
|
turn.tokens = (turn.tokens ?? 0) + data.usage.output_tokens;
|
|
698
1020
|
}
|
|
699
|
-
for (const block of data.content) {
|
|
1021
|
+
for (const block of data.content ?? []) {
|
|
700
1022
|
if (block.type === "thinking" && block.thinking.trim()) {
|
|
701
1023
|
turn.nodes.push({
|
|
702
1024
|
key: nextKey(record.id),
|
|
@@ -708,7 +1030,7 @@ function groupRecordsToChatTurns(records) {
|
|
|
708
1030
|
key: nextKey(record.id),
|
|
709
1031
|
kind: "tool",
|
|
710
1032
|
name: block.name,
|
|
711
|
-
input: block.input,
|
|
1033
|
+
input: block.input ?? null,
|
|
712
1034
|
summary: summarizeToolInput(block.name, block.input),
|
|
713
1035
|
result: null,
|
|
714
1036
|
isError: false,
|
|
@@ -721,11 +1043,60 @@ function groupRecordsToChatTurns(records) {
|
|
|
721
1043
|
turn.nodes.push({
|
|
722
1044
|
key: nextKey(record.id),
|
|
723
1045
|
kind: "assistant",
|
|
724
|
-
text: block.text.trim()
|
|
1046
|
+
text: block.text.trim(),
|
|
1047
|
+
createdAt: record.created_at
|
|
725
1048
|
});
|
|
726
1049
|
}
|
|
727
1050
|
}
|
|
728
1051
|
}
|
|
1052
|
+
for (const [messageId, turnId] of deliveredTurnByMessageId) {
|
|
1053
|
+
const turn = userTurnByMessageId.get(messageId);
|
|
1054
|
+
if (turn) turn.isError = failedTurnIds.has(turnId);
|
|
1055
|
+
}
|
|
1056
|
+
for (const [messageId, progress] of messageProgress(records)) {
|
|
1057
|
+
const turn = userTurnByMessageId.get(messageId);
|
|
1058
|
+
if (turn) turn.progress = progress;
|
|
1059
|
+
}
|
|
1060
|
+
const initialTurn = records.find(
|
|
1061
|
+
(r) => r.kind === "platform" && r.record_type === "turn_started" && r.payload.turn_index === 0
|
|
1062
|
+
);
|
|
1063
|
+
const initialDelivery = initialTurn && records.find(
|
|
1064
|
+
(r) => r.kind === "platform" && r.record_type === "message_delivered" && r.payload.turn_id === (initialTurn.turn_id ?? initialTurn.payload.turn_id)
|
|
1065
|
+
);
|
|
1066
|
+
const initialReceived = initialTurn && records.find(
|
|
1067
|
+
(r) => r.kind === "platform" && r.record_type === "message_received" && r.turn_id === (initialTurn.turn_id ?? initialTurn.payload.turn_id)
|
|
1068
|
+
);
|
|
1069
|
+
const initialMessage = initialReceived ?? initialDelivery;
|
|
1070
|
+
const initialId = initialMessage?.kind === "platform" && typeof initialMessage.payload.message_id === "string" ? initialMessage.payload.message_id : void 0;
|
|
1071
|
+
const opening = initialId ? userTurnByMessageId.get(initialId) : void 0;
|
|
1072
|
+
const includesStart = records.length === 0 || records.some((r) => r.record_type === "session_scheduled");
|
|
1073
|
+
const prompt = (session?.claude_code ?? session?.codex)?.prompt;
|
|
1074
|
+
if (prompt?.trim() && session && (opening || includesStart)) {
|
|
1075
|
+
const first = opening ?? {
|
|
1076
|
+
key: "prompt",
|
|
1077
|
+
role: "user",
|
|
1078
|
+
nodes: [{ key: "prompt", kind: "user", text: prompt }],
|
|
1079
|
+
startedAt: session.lifecycle.timestamps.created_at,
|
|
1080
|
+
completedAt: session.lifecycle.timestamps.created_at,
|
|
1081
|
+
durationMs: null,
|
|
1082
|
+
costUsd: null,
|
|
1083
|
+
tokens: null,
|
|
1084
|
+
resumed: false,
|
|
1085
|
+
isError: false,
|
|
1086
|
+
progress: { phase: "starting", turns: [] }
|
|
1087
|
+
};
|
|
1088
|
+
if (opening) turns.splice(turns.indexOf(opening), 1);
|
|
1089
|
+
first.key = "prompt";
|
|
1090
|
+
first.startedAt = session.lifecycle.timestamps.created_at;
|
|
1091
|
+
turns.unshift(first);
|
|
1092
|
+
if (!opening) {
|
|
1093
|
+
for (const turn of turns) {
|
|
1094
|
+
if (turn !== first && turn.role === "user" && turn.progress && !turn.progress.turns.length) {
|
|
1095
|
+
turn.progress.phase = turn.progress.phase === null ? null : "queued";
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
729
1100
|
return turns;
|
|
730
1101
|
}
|
|
731
1102
|
function codexEventIntoTurns(record, event, ops) {
|
|
@@ -734,36 +1105,38 @@ function codexEventIntoTurns(record, event, ops) {
|
|
|
734
1105
|
return;
|
|
735
1106
|
}
|
|
736
1107
|
if (event.type === "error") {
|
|
737
|
-
const turn2 = ops.
|
|
1108
|
+
const turn2 = ops.openSessionTurn(record);
|
|
738
1109
|
turn2.nodes.push({
|
|
739
1110
|
key: ops.nextKey(record.id),
|
|
740
1111
|
kind: "assistant",
|
|
741
|
-
text: event.message?.trim() || "error"
|
|
1112
|
+
text: event.message?.trim() || "error",
|
|
1113
|
+
createdAt: record.created_at
|
|
742
1114
|
});
|
|
743
1115
|
return;
|
|
744
1116
|
}
|
|
745
1117
|
if (event.type !== "item.completed") return;
|
|
746
1118
|
const item = event.item;
|
|
747
|
-
const turn = () => ops.
|
|
1119
|
+
const turn = () => ops.openSessionTurn(record);
|
|
748
1120
|
switch (item.type) {
|
|
749
1121
|
case "agent_message": {
|
|
750
|
-
const
|
|
751
|
-
if (
|
|
1122
|
+
const text2 = (item.text ?? "").trim();
|
|
1123
|
+
if (text2)
|
|
752
1124
|
turn().nodes.push({
|
|
753
1125
|
key: ops.nextKey(record.id),
|
|
754
1126
|
kind: "assistant",
|
|
755
|
-
text
|
|
1127
|
+
text: text2,
|
|
1128
|
+
createdAt: record.created_at
|
|
756
1129
|
});
|
|
757
1130
|
break;
|
|
758
1131
|
}
|
|
759
1132
|
case "reasoning": {
|
|
760
1133
|
const r = item;
|
|
761
|
-
const
|
|
762
|
-
if (
|
|
1134
|
+
const text2 = (r.text ?? r.summary ?? "").trim();
|
|
1135
|
+
if (text2)
|
|
763
1136
|
turn().nodes.push({
|
|
764
1137
|
key: ops.nextKey(record.id),
|
|
765
1138
|
kind: "thinking",
|
|
766
|
-
text
|
|
1139
|
+
text: text2
|
|
767
1140
|
});
|
|
768
1141
|
break;
|
|
769
1142
|
}
|
|
@@ -774,7 +1147,7 @@ function codexEventIntoTurns(record, event, ops) {
|
|
|
774
1147
|
kind: "tool",
|
|
775
1148
|
name: "Bash",
|
|
776
1149
|
input: { command: c.command },
|
|
777
|
-
summary: oneLine(c.command, 100),
|
|
1150
|
+
summary: oneLine(c.command ?? "", 100),
|
|
778
1151
|
result: (c.aggregated_output ?? "").trim() || "(no output)",
|
|
779
1152
|
isError: typeof c.exit_code === "number" && c.exit_code !== 0,
|
|
780
1153
|
startedAt: record.created_at,
|
|
@@ -979,8 +1352,8 @@ function deriveSandboxState(records, minFeedSeq) {
|
|
|
979
1352
|
let configCommitSha = null;
|
|
980
1353
|
let log = [];
|
|
981
1354
|
let open = /* @__PURE__ */ new Map();
|
|
982
|
-
const push = (record, kind,
|
|
983
|
-
const entry = { key: `${record.feed_seq}:${log.length}`, kind, text };
|
|
1355
|
+
const push = (record, kind, text2) => {
|
|
1356
|
+
const entry = { key: `${record.feed_seq}:${log.length}`, kind, text: text2 };
|
|
984
1357
|
log.push(entry);
|
|
985
1358
|
return entry;
|
|
986
1359
|
};
|
|
@@ -1005,8 +1378,8 @@ function deriveSandboxState(records, minFeedSeq) {
|
|
|
1005
1378
|
case "session_starting":
|
|
1006
1379
|
case "session_retrying": {
|
|
1007
1380
|
seen = true;
|
|
1008
|
-
const
|
|
1009
|
-
headline = !
|
|
1381
|
+
const text2 = lifecycleText(record.record_type, p);
|
|
1382
|
+
headline = !text2 || text2 === "Session starting\u2026" ? STARTING_SANDBOX : text2;
|
|
1010
1383
|
done = false;
|
|
1011
1384
|
readySeconds = null;
|
|
1012
1385
|
reset();
|
|
@@ -1035,7 +1408,7 @@ function deriveSandboxState(records, minFeedSeq) {
|
|
|
1035
1408
|
const dur = msLabel(p.duration_ms);
|
|
1036
1409
|
const failed = p.status === "failed";
|
|
1037
1410
|
const base = failed ? `${label} failed` : label;
|
|
1038
|
-
const
|
|
1411
|
+
const text2 = [
|
|
1039
1412
|
base,
|
|
1040
1413
|
...tier ? [tier] : [],
|
|
1041
1414
|
...dur ? [dur] : []
|
|
@@ -1043,10 +1416,10 @@ function deriveSandboxState(records, minFeedSeq) {
|
|
|
1043
1416
|
const line = open.get(key);
|
|
1044
1417
|
if (line) {
|
|
1045
1418
|
line.kind = failed ? "failed" : "done";
|
|
1046
|
-
line.text =
|
|
1419
|
+
line.text = text2;
|
|
1047
1420
|
open.delete(key);
|
|
1048
1421
|
} else {
|
|
1049
|
-
push(record, failed ? "failed" : "done",
|
|
1422
|
+
push(record, failed ? "failed" : "done", text2);
|
|
1050
1423
|
}
|
|
1051
1424
|
} else if (!open.has(key)) {
|
|
1052
1425
|
open.set(key, push(record, "step", `${label}\u2026`));
|
|
@@ -1111,8 +1484,8 @@ function startupSettled(sandbox, status) {
|
|
|
1111
1484
|
}
|
|
1112
1485
|
function startupHeadline(sandbox, status) {
|
|
1113
1486
|
const activity = statusActivityText(status);
|
|
1114
|
-
const
|
|
1115
|
-
return `${
|
|
1487
|
+
const text2 = !sandbox || sandbox.done ? activity ?? STARTING_SANDBOX : sandbox.headline;
|
|
1488
|
+
return `${text2.replace(/…$/, "")}\u2026`;
|
|
1116
1489
|
}
|
|
1117
1490
|
function sandboxSummary(sandbox) {
|
|
1118
1491
|
const seconds = sandbox?.readySeconds ?? null;
|
|
@@ -1157,13 +1530,13 @@ function chatTurnsToItems(turns) {
|
|
|
1157
1530
|
settleWake();
|
|
1158
1531
|
continue;
|
|
1159
1532
|
}
|
|
1160
|
-
const
|
|
1161
|
-
if (!
|
|
1162
|
-
const waking =
|
|
1533
|
+
const text2 = sessionLogText(node.recordType, node.payload ?? {});
|
|
1534
|
+
if (!text2) continue;
|
|
1535
|
+
const waking = text2 === SESSION_WAKING;
|
|
1163
1536
|
items.push({
|
|
1164
1537
|
key: node.key,
|
|
1165
1538
|
kind: "notice",
|
|
1166
|
-
text,
|
|
1539
|
+
text: text2,
|
|
1167
1540
|
spaceBefore: true,
|
|
1168
1541
|
...waking ? { loading: true } : {}
|
|
1169
1542
|
});
|
|
@@ -1178,6 +1551,7 @@ function chatTurnsToItems(turns) {
|
|
|
1178
1551
|
key: node.key,
|
|
1179
1552
|
kind: "user",
|
|
1180
1553
|
text: node.text,
|
|
1554
|
+
createdAt: turn.startedAt,
|
|
1181
1555
|
spaceBefore: true,
|
|
1182
1556
|
...turn.isError ? { isError: true } : {}
|
|
1183
1557
|
});
|
|
@@ -1191,6 +1565,7 @@ function chatTurnsToItems(turns) {
|
|
|
1191
1565
|
key: node.key,
|
|
1192
1566
|
kind: "assistant",
|
|
1193
1567
|
text: node.text,
|
|
1568
|
+
createdAt: node.createdAt,
|
|
1194
1569
|
spaceBefore: true
|
|
1195
1570
|
});
|
|
1196
1571
|
} else if (node.kind === "thinking") {
|
|
@@ -1245,14 +1620,19 @@ function isToolActivity(item) {
|
|
|
1245
1620
|
return item.kind === "tool" || item.kind === "tool_result" || isToolFold(item);
|
|
1246
1621
|
}
|
|
1247
1622
|
function layOutItems(items) {
|
|
1248
|
-
|
|
1623
|
+
const isActivity = (item) => item.kind === "thinking" || isToolActivity(item);
|
|
1624
|
+
return items.map((item, index) => ({
|
|
1625
|
+
item,
|
|
1626
|
+
nested: false,
|
|
1627
|
+
attach: index > 0 && isActivity(item) && isActivity(items[index - 1])
|
|
1628
|
+
}));
|
|
1249
1629
|
}
|
|
1250
1630
|
function foldGutter(nested) {
|
|
1251
1631
|
return nested ? BRANCH_GLYPH : "\u25CF";
|
|
1252
1632
|
}
|
|
1253
|
-
function tailLines(
|
|
1254
|
-
const lines =
|
|
1255
|
-
if (lines.length <= maxLines) return { body:
|
|
1633
|
+
function tailLines(text2, maxLines) {
|
|
1634
|
+
const lines = text2.split("\n");
|
|
1635
|
+
if (lines.length <= maxLines) return { body: text2, hidden: 0 };
|
|
1256
1636
|
return {
|
|
1257
1637
|
body: lines.slice(lines.length - maxLines).join("\n"),
|
|
1258
1638
|
hidden: lines.length - maxLines
|
|
@@ -1277,24 +1657,44 @@ function toolRunMembers(items) {
|
|
|
1277
1657
|
function splitLedger(turns) {
|
|
1278
1658
|
const preamble = [];
|
|
1279
1659
|
const entries = [];
|
|
1660
|
+
const entryByTurn = /* @__PURE__ */ new Map();
|
|
1661
|
+
const entryByMessage = /* @__PURE__ */ new Map();
|
|
1280
1662
|
for (const turn of turns) {
|
|
1281
1663
|
if (turn.role === "user") {
|
|
1282
1664
|
const node = turn.nodes.find((n) => n.kind === "user");
|
|
1283
|
-
|
|
1665
|
+
const entry = {
|
|
1284
1666
|
key: turn.key,
|
|
1285
1667
|
prompt: node?.text ?? "",
|
|
1668
|
+
createdAt: turn.startedAt,
|
|
1286
1669
|
cancelled: turn.isError,
|
|
1287
|
-
turns: []
|
|
1288
|
-
|
|
1670
|
+
turns: [],
|
|
1671
|
+
...turn.progress ? { progress: turn.progress } : {}
|
|
1672
|
+
};
|
|
1673
|
+
entries.push(entry);
|
|
1674
|
+
entryByTurn.set(turn, entry);
|
|
1675
|
+
if (turn.messageId) entryByMessage.set(turn.messageId, entry);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
let last;
|
|
1679
|
+
for (const turn of turns) {
|
|
1680
|
+
if (turn.role === "user") {
|
|
1681
|
+
last = entryByTurn.get(turn);
|
|
1289
1682
|
continue;
|
|
1290
1683
|
}
|
|
1291
|
-
const
|
|
1292
|
-
if (
|
|
1684
|
+
const owner = turn.messageId ? entryByMessage.get(turn.messageId) : last;
|
|
1685
|
+
if (owner) owner.turns.push(turn);
|
|
1293
1686
|
else preamble.push(turn);
|
|
1294
1687
|
}
|
|
1295
1688
|
return { preamble, entries };
|
|
1296
1689
|
}
|
|
1297
1690
|
function entrySeconds(entry, now) {
|
|
1691
|
+
if (entry.progress?.turns.length) {
|
|
1692
|
+
return entry.progress.turns.reduce((ms2, turn) => {
|
|
1693
|
+
if (turn.startedAt == null) return ms2;
|
|
1694
|
+
const end = turn.completedAt == null ? now : Date.parse(turn.completedAt);
|
|
1695
|
+
return ms2 + Math.max(0, end - Date.parse(turn.startedAt));
|
|
1696
|
+
}, 0) / 1e3;
|
|
1697
|
+
}
|
|
1298
1698
|
let ms = 0;
|
|
1299
1699
|
for (const turn of entry.turns) {
|
|
1300
1700
|
if (turn.role !== "assistant" || turn.startedAt == null) continue;
|
|
@@ -1306,12 +1706,15 @@ function entrySeconds(entry, now) {
|
|
|
1306
1706
|
}
|
|
1307
1707
|
return ms / 1e3;
|
|
1308
1708
|
}
|
|
1309
|
-
function
|
|
1709
|
+
function isWorkFold(item) {
|
|
1710
|
+
return item.key.startsWith("work:");
|
|
1711
|
+
}
|
|
1712
|
+
function foldToolTurn(raw, members) {
|
|
1310
1713
|
const items = [];
|
|
1311
1714
|
let run = [];
|
|
1312
1715
|
const flush = () => {
|
|
1313
1716
|
if (run.length === 0) return;
|
|
1314
|
-
const fold = foldRun(run,
|
|
1717
|
+
const fold = foldRun(run, false);
|
|
1315
1718
|
members.set(fold.key, run);
|
|
1316
1719
|
items.push(fold);
|
|
1317
1720
|
run = [];
|
|
@@ -1326,70 +1729,224 @@ function foldToolTurn(raw, live, members) {
|
|
|
1326
1729
|
flush();
|
|
1327
1730
|
return items;
|
|
1328
1731
|
}
|
|
1732
|
+
function finalReplyKeys(entry, live) {
|
|
1733
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1734
|
+
for (const turn of entry.turns) {
|
|
1735
|
+
if (turn.role !== "assistant") continue;
|
|
1736
|
+
for (const node of turn.nodes) {
|
|
1737
|
+
if (node.kind === "assistant" && node.phase === "final_answer") {
|
|
1738
|
+
keys.add(node.key);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
if (entry.cancelled || turn.isError) continue;
|
|
1742
|
+
if (live && turn.completedAt == null && turn.durationMs == null) continue;
|
|
1743
|
+
for (let i = turn.nodes.length - 1; i >= 0; i--) {
|
|
1744
|
+
const node = turn.nodes[i];
|
|
1745
|
+
if (node.kind !== "assistant" || node.phase === "commentary") break;
|
|
1746
|
+
keys.add(node.key);
|
|
1747
|
+
const previous = turn.nodes[i - 1];
|
|
1748
|
+
if (node.createdAt == null || previous?.kind !== "assistant" || previous.createdAt !== node.createdAt)
|
|
1749
|
+
break;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return keys;
|
|
1753
|
+
}
|
|
1754
|
+
function reasoningSummaryTitle(summary) {
|
|
1755
|
+
const line = summary.trim().split(/\r?\n/, 1)[0] ?? "";
|
|
1756
|
+
const title = line.replace(/^#{1,6}\s+/, "").replace(/^[*_`]+|[*_`]+$/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim();
|
|
1757
|
+
return title.length > 120 ? `${title.slice(0, 119).trimEnd()}\u2026` : title;
|
|
1758
|
+
}
|
|
1329
1759
|
function entryBody(entry, {
|
|
1330
1760
|
live,
|
|
1331
1761
|
now,
|
|
1332
|
-
liveText = ""
|
|
1762
|
+
liveText = "",
|
|
1763
|
+
liveSummary = ""
|
|
1333
1764
|
}) {
|
|
1765
|
+
live = live && (entry.progress === void 0 || entry.progress.phase !== null);
|
|
1334
1766
|
const raw = chatTurnsToItems(entry.turns);
|
|
1767
|
+
const replies = finalReplyKeys(entry, live);
|
|
1335
1768
|
const members = /* @__PURE__ */ new Map();
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1769
|
+
const items = [];
|
|
1770
|
+
const work = [];
|
|
1771
|
+
const wakeKeys = new Set(
|
|
1772
|
+
entry.turns.flatMap(
|
|
1773
|
+
(turn) => turn.nodes.flatMap(
|
|
1774
|
+
(node) => node.kind === "lifecycle" && (node.recordType === "session_resumed" || node.recordType === "session_starting" && typeof node.payload?.wake_index === "number" && node.payload.wake_index > 0) ? [node.key] : []
|
|
1775
|
+
)
|
|
1776
|
+
)
|
|
1777
|
+
);
|
|
1778
|
+
const phase = entry.progress?.phase;
|
|
1779
|
+
const seconds = entry.progress && !entry.progress.turns.some((turn) => turn.startedAt != null) ? null : entrySeconds(entry, now);
|
|
1780
|
+
const key = `work:${entry.key}`;
|
|
1781
|
+
const header = {
|
|
1782
|
+
key,
|
|
1783
|
+
kind: "summary",
|
|
1784
|
+
text: live ? phase === "waking" ? "Waking session" : phase === "starting" ? "Starting agent" : phase === "queued" ? "Queued" : reasoningSummaryTitle(liveSummary) || "Working" : seconds == null ? "Session startup" : `Worked for ${humanDuration(seconds)}`,
|
|
1785
|
+
spaceBefore: true,
|
|
1786
|
+
...entry.cancelled || entry.turns.some((turn) => turn.isError) ? { isError: true } : {},
|
|
1787
|
+
...live ? { loading: true } : {}
|
|
1788
|
+
};
|
|
1789
|
+
if (live && (phase === void 0 || phase === "working")) {
|
|
1790
|
+
const visible = raw.filter((item) => !wakeKeys.has(item.key)).map(({ loading: _, ...item }) => item);
|
|
1791
|
+
if (liveText !== "") {
|
|
1792
|
+
visible.push({
|
|
1793
|
+
key: `live:${entry.key}`,
|
|
1794
|
+
kind: "assistant",
|
|
1795
|
+
text: liveText,
|
|
1352
1796
|
spaceBefore: true
|
|
1353
1797
|
});
|
|
1354
1798
|
}
|
|
1799
|
+
return {
|
|
1800
|
+
items: [header, ...foldToolTurn(visible, members)],
|
|
1801
|
+
members,
|
|
1802
|
+
seconds
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
const addWork = (item) => {
|
|
1806
|
+
if (work.length === 0) items.push(header);
|
|
1807
|
+
work.push(item);
|
|
1808
|
+
};
|
|
1809
|
+
for (const item of raw) {
|
|
1810
|
+
const { loading: _, ...settled } = item;
|
|
1811
|
+
if (!wakeKeys.has(item.key) && (replies.has(item.key) || !["assistant", "thinking", "tool", "tool_result"].includes(item.kind))) {
|
|
1812
|
+
items.push(settled);
|
|
1813
|
+
} else {
|
|
1814
|
+
addWork(settled);
|
|
1815
|
+
}
|
|
1355
1816
|
}
|
|
1356
1817
|
if (live && liveText !== "") {
|
|
1357
|
-
|
|
1818
|
+
addWork({
|
|
1358
1819
|
key: `live:${entry.key}`,
|
|
1359
1820
|
kind: "assistant",
|
|
1360
1821
|
text: liveText,
|
|
1361
1822
|
spaceBefore: true
|
|
1362
1823
|
});
|
|
1363
1824
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
}
|
|
1369
|
-
return { items, members };
|
|
1825
|
+
if (work.length > 0) {
|
|
1826
|
+
members.set(key, foldToolTurn(work, members));
|
|
1827
|
+
} else if (live) {
|
|
1828
|
+
items.unshift(header);
|
|
1829
|
+
}
|
|
1830
|
+
return { items, members, seconds };
|
|
1370
1831
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1832
|
+
|
|
1833
|
+
// src/store/activity.ts
|
|
1834
|
+
var text = (value) => typeof value === "string" ? value : "";
|
|
1835
|
+
function toolActivities(item) {
|
|
1836
|
+
const input = item.tool?.input ?? {};
|
|
1837
|
+
const name = item.tool?.name ?? item.text;
|
|
1838
|
+
const actions = input.commandActions;
|
|
1839
|
+
if (Array.isArray(actions) && actions.length) {
|
|
1840
|
+
const parsed = actions.flatMap((value) => {
|
|
1841
|
+
if (!value || typeof value !== "object") return [];
|
|
1842
|
+
const action = value;
|
|
1843
|
+
switch (action.type) {
|
|
1844
|
+
case "read":
|
|
1845
|
+
return [
|
|
1846
|
+
{
|
|
1847
|
+
kind: "read",
|
|
1848
|
+
label: "Read",
|
|
1849
|
+
detail: text(action.path) || text(action.name)
|
|
1850
|
+
}
|
|
1851
|
+
];
|
|
1852
|
+
case "search": {
|
|
1853
|
+
const query = text(action.query);
|
|
1854
|
+
const path2 = text(action.path);
|
|
1855
|
+
return [
|
|
1856
|
+
{
|
|
1857
|
+
kind: "search",
|
|
1858
|
+
label: "Searched",
|
|
1859
|
+
detail: (query ? `for ${query}` : "code") + (path2 ? ` in ${path2}` : "")
|
|
1860
|
+
}
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
case "listFiles":
|
|
1864
|
+
return [
|
|
1865
|
+
{
|
|
1866
|
+
kind: "list",
|
|
1867
|
+
label: "Listed",
|
|
1868
|
+
detail: text(action.path) || "files"
|
|
1869
|
+
}
|
|
1870
|
+
];
|
|
1871
|
+
default:
|
|
1872
|
+
return [
|
|
1873
|
+
{
|
|
1874
|
+
kind: "command",
|
|
1875
|
+
label: "Ran",
|
|
1876
|
+
detail: text(action.command) || text(input.command)
|
|
1877
|
+
}
|
|
1878
|
+
];
|
|
1879
|
+
}
|
|
1880
|
+
});
|
|
1881
|
+
if (parsed.length) return parsed;
|
|
1882
|
+
}
|
|
1883
|
+
const path = text(input.file_path) || text(input.path) || text(input.notebook_path);
|
|
1884
|
+
switch (name.toLowerCase()) {
|
|
1885
|
+
case "read":
|
|
1886
|
+
return [{ kind: "read", label: "Read", detail: path }];
|
|
1887
|
+
case "edit":
|
|
1888
|
+
case "write":
|
|
1889
|
+
case "multiedit":
|
|
1890
|
+
if (Array.isArray(input.changes)) {
|
|
1891
|
+
return input.changes.map((change) => ({
|
|
1892
|
+
kind: "edit",
|
|
1893
|
+
label: "Edited",
|
|
1894
|
+
detail: text(change?.path)
|
|
1895
|
+
}));
|
|
1896
|
+
}
|
|
1897
|
+
return [
|
|
1898
|
+
{
|
|
1899
|
+
kind: "edit",
|
|
1900
|
+
label: name.toLowerCase() === "write" ? "Wrote" : "Edited",
|
|
1901
|
+
detail: path
|
|
1902
|
+
}
|
|
1903
|
+
];
|
|
1904
|
+
case "grep":
|
|
1905
|
+
case "glob":
|
|
1906
|
+
return [
|
|
1907
|
+
{
|
|
1908
|
+
kind: "search",
|
|
1909
|
+
label: "Searched",
|
|
1910
|
+
detail: `for ${text(input.pattern)}` + (path ? ` in ${path}` : "")
|
|
1911
|
+
}
|
|
1912
|
+
];
|
|
1913
|
+
case "bash":
|
|
1914
|
+
return [{ kind: "command", label: "Ran", detail: text(input.command) }];
|
|
1915
|
+
case "websearch":
|
|
1916
|
+
return [
|
|
1917
|
+
{ kind: "web", label: "Searched the web", detail: text(input.query) }
|
|
1918
|
+
];
|
|
1919
|
+
case "webfetch":
|
|
1920
|
+
return [{ kind: "web", label: "Opened", detail: text(input.url) }];
|
|
1921
|
+
default:
|
|
1922
|
+
return [
|
|
1923
|
+
{
|
|
1924
|
+
kind: "tool",
|
|
1925
|
+
label: name.replace(/^mcp__/, "").replace("__", ":"),
|
|
1926
|
+
detail: item.detail ?? ""
|
|
1927
|
+
}
|
|
1928
|
+
];
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
function activitySummary(activities) {
|
|
1932
|
+
const labels = {
|
|
1933
|
+
read: "read files",
|
|
1934
|
+
search: "searched code",
|
|
1935
|
+
list: "listed files",
|
|
1936
|
+
edit: "edited files",
|
|
1937
|
+
command: "ran commands",
|
|
1938
|
+
web: "browsed the web",
|
|
1939
|
+
tool: "used tools"
|
|
1940
|
+
};
|
|
1941
|
+
const summary = [
|
|
1942
|
+
...new Set(activities.map((activity) => labels[activity.kind]))
|
|
1943
|
+
].join(", ");
|
|
1944
|
+
return summary ? summary[0].toUpperCase() + summary.slice(1) : "Activity";
|
|
1379
1945
|
}
|
|
1380
1946
|
|
|
1381
1947
|
// src/store/index.ts
|
|
1382
|
-
var TERMINAL_SESSION_STATUSES = /* @__PURE__ */ new Set([
|
|
1383
|
-
"completed",
|
|
1384
|
-
"error",
|
|
1385
|
-
"cancelled",
|
|
1386
|
-
"stopped"
|
|
1387
|
-
]);
|
|
1388
1948
|
function isConversationOver(session) {
|
|
1389
|
-
|
|
1390
|
-
return session.session_state === "closed";
|
|
1391
|
-
}
|
|
1392
|
-
return TERMINAL_SESSION_STATUSES.has(session.status);
|
|
1949
|
+
return session.lifecycle.conversation === "closed";
|
|
1393
1950
|
}
|
|
1394
1951
|
function seedTranscriptStore(store, seed) {
|
|
1395
1952
|
store.ingest({
|
|
@@ -1410,6 +1967,7 @@ var EMPTY_SNAPSHOT = {
|
|
|
1410
1967
|
acknowledgedMessageIds: /* @__PURE__ */ new Set(),
|
|
1411
1968
|
historyTruncated: false,
|
|
1412
1969
|
liveText: "",
|
|
1970
|
+
liveSummary: "",
|
|
1413
1971
|
liveOutputTokens: null,
|
|
1414
1972
|
lastEventAt: null,
|
|
1415
1973
|
conversationOver: false
|
|
@@ -1433,7 +1991,10 @@ var SessionTranscriptStore = class {
|
|
|
1433
1991
|
cursorSeq = 0;
|
|
1434
1992
|
// Derived-turn cache, keyed on the records array identity.
|
|
1435
1993
|
turnsFor = null;
|
|
1994
|
+
turnsSession = null;
|
|
1436
1995
|
turnsCache = [];
|
|
1996
|
+
summaryTurnId = null;
|
|
1997
|
+
summaryExecutionId = null;
|
|
1437
1998
|
get cursor() {
|
|
1438
1999
|
return this.cursorSeq;
|
|
1439
2000
|
}
|
|
@@ -1448,9 +2009,10 @@ var SessionTranscriptStore = class {
|
|
|
1448
2009
|
// records_append batch) — what a chat-style transcript renders.
|
|
1449
2010
|
chatTurns = () => {
|
|
1450
2011
|
const records = this.snapshot.records;
|
|
1451
|
-
if (this.turnsFor !== records) {
|
|
1452
|
-
this.turnsCache = groupRecordsToChatTurns(records);
|
|
2012
|
+
if (this.turnsFor !== records || this.turnsSession !== this.snapshot.session) {
|
|
2013
|
+
this.turnsCache = groupRecordsToChatTurns(records, this.snapshot.session);
|
|
1453
2014
|
this.turnsFor = records;
|
|
2015
|
+
this.turnsSession = this.snapshot.session;
|
|
1454
2016
|
}
|
|
1455
2017
|
return this.turnsCache;
|
|
1456
2018
|
};
|
|
@@ -1459,6 +2021,7 @@ var SessionTranscriptStore = class {
|
|
|
1459
2021
|
// Fold one lifecycle record into the inbox projection; returns whether the
|
|
1460
2022
|
// pending view changed. Unknown fields/types are ignored (§3.6).
|
|
1461
2023
|
applyMessageRecord = (record) => {
|
|
2024
|
+
if (record.kind !== "platform") return false;
|
|
1462
2025
|
const payload = record.payload;
|
|
1463
2026
|
const messageId = typeof payload.message_id === "string" ? payload.message_id : null;
|
|
1464
2027
|
switch (record.record_type) {
|
|
@@ -1516,6 +2079,7 @@ var SessionTranscriptStore = class {
|
|
|
1516
2079
|
const frame = rawFrame;
|
|
1517
2080
|
switch (frame.type) {
|
|
1518
2081
|
case "snapshot": {
|
|
2082
|
+
next.liveSummary = "";
|
|
1519
2083
|
next.session = frame.session;
|
|
1520
2084
|
for (const message of frame.messages) {
|
|
1521
2085
|
this.inbox.set(message.id, message);
|
|
@@ -1540,6 +2104,8 @@ var SessionTranscriptStore = class {
|
|
|
1540
2104
|
acknowledgedNew = true;
|
|
1541
2105
|
}
|
|
1542
2106
|
if (record.source === "lifecycle") {
|
|
2107
|
+
if (record.record_type === "turn_started" && (record.turn_id !== this.summaryTurnId || record.session_execution_id !== this.summaryExecutionId) || (record.record_type === "turn_completed" || record.record_type === "turn_failed") && record.turn_id === this.summaryTurnId && record.session_execution_id === this.summaryExecutionId)
|
|
2108
|
+
next.liveSummary = "";
|
|
1543
2109
|
inboxChanged = this.applyMessageRecord(record) || inboxChanged;
|
|
1544
2110
|
if (record.record_type === "message_received" || record.record_type === "message_delivered") {
|
|
1545
2111
|
const payload = record.payload;
|
|
@@ -1561,17 +2127,35 @@ var SessionTranscriptStore = class {
|
|
|
1561
2127
|
case "session": {
|
|
1562
2128
|
next.session = frame.session;
|
|
1563
2129
|
next.conversationOver = isConversationOver(frame.session);
|
|
2130
|
+
if (next.conversationOver || frame.session.lifecycle.status === "idle") {
|
|
2131
|
+
next.liveSummary = "";
|
|
2132
|
+
}
|
|
1564
2133
|
break;
|
|
1565
2134
|
}
|
|
1566
2135
|
case "delta": {
|
|
2136
|
+
if (frame.kind === "thinking") {
|
|
2137
|
+
if (next.conversationOver) break;
|
|
2138
|
+
this.summaryTurnId = frame.turn_id;
|
|
2139
|
+
this.summaryExecutionId = frame.session_execution_id;
|
|
2140
|
+
if (frame.text != null) next.liveSummary = frame.text;
|
|
2141
|
+
break;
|
|
2142
|
+
}
|
|
1567
2143
|
if (frame.kind !== "text") break;
|
|
1568
|
-
if (frame.text != null)
|
|
2144
|
+
if (frame.text != null) {
|
|
2145
|
+
next.liveText = prev.liveText + frame.text;
|
|
2146
|
+
next.liveSummary = "";
|
|
2147
|
+
}
|
|
1569
2148
|
if (frame.output_tokens != null)
|
|
1570
2149
|
next.liveOutputTokens = frame.output_tokens;
|
|
1571
2150
|
break;
|
|
1572
2151
|
}
|
|
1573
2152
|
case "done": {
|
|
1574
2153
|
next.conversationOver = true;
|
|
2154
|
+
next.liveSummary = "";
|
|
2155
|
+
break;
|
|
2156
|
+
}
|
|
2157
|
+
case "error": {
|
|
2158
|
+
next.liveSummary = "";
|
|
1575
2159
|
break;
|
|
1576
2160
|
}
|
|
1577
2161
|
default:
|
|
@@ -1587,10 +2171,12 @@ export {
|
|
|
1587
2171
|
SESSION_WAKING,
|
|
1588
2172
|
SessionTranscriptStore,
|
|
1589
2173
|
USER_BAR,
|
|
2174
|
+
activitySummary,
|
|
1590
2175
|
awaitingAgentPhase,
|
|
1591
2176
|
cacheTierLabel,
|
|
1592
2177
|
chatTurnsToItems,
|
|
1593
2178
|
clampLines,
|
|
2179
|
+
claudePayload,
|
|
1594
2180
|
codexChangedPaths,
|
|
1595
2181
|
codexEventToItems,
|
|
1596
2182
|
codexMcpToolName,
|
|
@@ -1614,11 +2200,13 @@ export {
|
|
|
1614
2200
|
isFoldable,
|
|
1615
2201
|
isToolActivity,
|
|
1616
2202
|
isToolFold,
|
|
2203
|
+
isWorkFold,
|
|
1617
2204
|
lastLines,
|
|
1618
2205
|
layOutItems,
|
|
1619
2206
|
lifecycleText,
|
|
1620
2207
|
oneLine,
|
|
1621
2208
|
pendingToolCalls,
|
|
2209
|
+
reasoningSummaryTitle,
|
|
1622
2210
|
recordSlice,
|
|
1623
2211
|
recordToItems,
|
|
1624
2212
|
resultCostUsd,
|
|
@@ -1635,6 +2223,7 @@ export {
|
|
|
1635
2223
|
statusActivityText,
|
|
1636
2224
|
summarizeToolInput,
|
|
1637
2225
|
tailLines,
|
|
2226
|
+
toolActivities,
|
|
1638
2227
|
toolResultText,
|
|
1639
2228
|
toolRunMembers
|
|
1640
2229
|
};
|