linkshell-cli 0.3.8 → 0.3.10
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/cli/src/runtime/acp/claude-sessions.d.ts +32 -4
- package/dist/cli/src/runtime/acp/claude-sessions.js +175 -7
- package/dist/cli/src/runtime/acp/claude-sessions.js.map +1 -1
- package/dist/cli/src/runtime/acp/codex-sessions.d.ts +32 -4
- package/dist/cli/src/runtime/acp/codex-sessions.js +300 -16
- package/dist/cli/src/runtime/acp/codex-sessions.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/runtime/acp/claude-sessions.ts +218 -10
- package/src/runtime/acp/codex-sessions.ts +343 -19
|
@@ -2,7 +2,7 @@ import { closeSync, existsSync, openSync, readFileSync, readdirSync, readSync, s
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, join, resolve } from "node:path";
|
|
4
4
|
|
|
5
|
-
const SAMPLE_BYTES =
|
|
5
|
+
const SAMPLE_BYTES = 512 * 1024;
|
|
6
6
|
const HISTORY_BYTES = 2 * 1024 * 1024;
|
|
7
7
|
const MAX_SESSIONS = 200;
|
|
8
8
|
const MAX_HISTORY_ITEMS = 200;
|
|
@@ -19,10 +19,38 @@ export interface CodexStoredSession {
|
|
|
19
19
|
export interface StoredAgentTimelineItem {
|
|
20
20
|
id: string;
|
|
21
21
|
conversationId: string;
|
|
22
|
-
type: "message";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
type: "message" | "tool_call";
|
|
23
|
+
kind?: "tool_activity" | "command_execution" | "file_change";
|
|
24
|
+
itemId?: string;
|
|
25
|
+
role?: "user" | "assistant" | "system";
|
|
26
|
+
content?: Array<{ type: "text"; text: string }>;
|
|
27
|
+
text?: string;
|
|
28
|
+
toolCall?: {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
input?: string;
|
|
32
|
+
output?: string;
|
|
33
|
+
createdAt?: number;
|
|
34
|
+
status: "pending" | "running" | "completed" | "failed";
|
|
35
|
+
};
|
|
36
|
+
commandExecution?: {
|
|
37
|
+
command?: string;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
output?: string;
|
|
40
|
+
exitCode?: number | null;
|
|
41
|
+
status?: "pending" | "running" | "completed" | "failed";
|
|
42
|
+
};
|
|
43
|
+
fileChange?: {
|
|
44
|
+
entries: Array<{
|
|
45
|
+
path: string;
|
|
46
|
+
kind?: string;
|
|
47
|
+
added?: number;
|
|
48
|
+
removed?: number;
|
|
49
|
+
}>;
|
|
50
|
+
diff?: string;
|
|
51
|
+
summary?: string;
|
|
52
|
+
status?: "pending" | "running" | "completed" | "failed";
|
|
53
|
+
};
|
|
26
54
|
createdAt: number;
|
|
27
55
|
updatedAt?: number;
|
|
28
56
|
metadata?: Record<string, unknown>;
|
|
@@ -169,6 +197,7 @@ function readCodexSessionFile(filePath: string, fallbackCwd: string): Omit<Codex
|
|
|
169
197
|
|
|
170
198
|
let id = sessionIdFromRolloutFile(filePath);
|
|
171
199
|
let cwd: string | undefined;
|
|
200
|
+
let title: string | undefined;
|
|
172
201
|
let createdAt: number | undefined;
|
|
173
202
|
let lastActivityAt: number | undefined;
|
|
174
203
|
for (const line of readSample(filePath, statSize).split(/\r?\n/)) {
|
|
@@ -182,6 +211,9 @@ function readCodexSessionFile(filePath: string, fallbackCwd: string): Omit<Codex
|
|
|
182
211
|
if (typeof payload.cwd === "string" && payload.cwd.trim()) cwd = payload.cwd;
|
|
183
212
|
createdAt ??= parseTimestamp(payload.timestamp);
|
|
184
213
|
}
|
|
214
|
+
if (entry?.type === "event_msg" && payload?.type === "user_message") {
|
|
215
|
+
title ??= normalizeTitle(normalizeHistoryText(payload.message));
|
|
216
|
+
}
|
|
185
217
|
const timestamp = parseTimestamp(entry?.timestamp);
|
|
186
218
|
createdAt ??= timestamp;
|
|
187
219
|
if (timestamp) lastActivityAt = timestamp;
|
|
@@ -194,6 +226,7 @@ function readCodexSessionFile(filePath: string, fallbackCwd: string): Omit<Codex
|
|
|
194
226
|
return {
|
|
195
227
|
id,
|
|
196
228
|
cwd: cwd ?? resolve(fallbackCwd),
|
|
229
|
+
title,
|
|
197
230
|
createdAt,
|
|
198
231
|
lastModified: lastActivityAt ?? statMtime,
|
|
199
232
|
};
|
|
@@ -271,6 +304,40 @@ function normalizeHistoryText(value: unknown): string | undefined {
|
|
|
271
304
|
return normalizeHistoryText(record.content ?? record.text ?? record.message);
|
|
272
305
|
}
|
|
273
306
|
|
|
307
|
+
function stringifyHistoryValue(value: unknown): string | undefined {
|
|
308
|
+
if (value === undefined || value === null || value === "") return undefined;
|
|
309
|
+
if (typeof value === "string") return value;
|
|
310
|
+
try {
|
|
311
|
+
return JSON.stringify(value, null, 2);
|
|
312
|
+
} catch {
|
|
313
|
+
return String(value);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function visibleCodexRole(value: unknown): "user" | "assistant" | undefined {
|
|
318
|
+
return value === "user" || value === "assistant" ? value : undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function isInjectedCodexContext(text: string): boolean {
|
|
322
|
+
const trimmed = text.trimStart();
|
|
323
|
+
return (
|
|
324
|
+
trimmed.startsWith("<permissions instructions>") ||
|
|
325
|
+
trimmed.startsWith("<app-context>") ||
|
|
326
|
+
trimmed.startsWith("<environment_context>") ||
|
|
327
|
+
trimmed.startsWith("<skill>") ||
|
|
328
|
+
trimmed.startsWith("<turn_aborted>") ||
|
|
329
|
+
trimmed.startsWith("<developer") ||
|
|
330
|
+
trimmed.startsWith("# AGENTS.md instructions") ||
|
|
331
|
+
trimmed.startsWith("AGENTS.md instructions for ") ||
|
|
332
|
+
trimmed.includes("\n# AGENTS.md\n") ||
|
|
333
|
+
trimmed.includes("\n<INSTRUCTIONS>")
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function messageDedupeKey(role: "user" | "assistant" | "system", text: string): string {
|
|
338
|
+
return `${role}:${text.replace(/\s+/g, " ").trim().slice(0, 400)}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
274
341
|
function historyMessage(
|
|
275
342
|
conversationId: string,
|
|
276
343
|
index: number,
|
|
@@ -292,6 +359,207 @@ function historyMessage(
|
|
|
292
359
|
};
|
|
293
360
|
}
|
|
294
361
|
|
|
362
|
+
function historyToolName(name: string | undefined): string {
|
|
363
|
+
if (!name) return "工具";
|
|
364
|
+
if (name.endsWith("exec_command") || name.endsWith("write_stdin")) return "命令";
|
|
365
|
+
if (name === "apply_patch") return "文件修改";
|
|
366
|
+
return name;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function historyToolKind(name: string | undefined): "tool_activity" | "command_execution" {
|
|
370
|
+
return name?.endsWith("exec_command") || name?.endsWith("write_stdin") ? "command_execution" : "tool_activity";
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function commandFromCodexTool(name: string | undefined, rawInput: unknown, output?: string): StoredAgentTimelineItem["commandExecution"] {
|
|
374
|
+
if (!name?.endsWith("exec_command") && !name?.endsWith("write_stdin")) return undefined;
|
|
375
|
+
let input = asRecord(rawInput);
|
|
376
|
+
if (!input && typeof rawInput === "string" && rawInput.trim().startsWith("{")) {
|
|
377
|
+
try {
|
|
378
|
+
input = asRecord(JSON.parse(rawInput));
|
|
379
|
+
} catch {
|
|
380
|
+
input = undefined;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const command = typeof input?.cmd === "string"
|
|
384
|
+
? input.cmd
|
|
385
|
+
: typeof input?.chars === "string"
|
|
386
|
+
? input.chars
|
|
387
|
+
: undefined;
|
|
388
|
+
const cwd = typeof input?.workdir === "string" ? input.workdir : undefined;
|
|
389
|
+
if (!command && !cwd && !output) return undefined;
|
|
390
|
+
return { command, cwd, output, status: output === undefined ? "running" : "completed" };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function upsertHistoryTool(
|
|
394
|
+
itemsById: Map<string, StoredAgentTimelineItem>,
|
|
395
|
+
conversationId: string,
|
|
396
|
+
callId: string,
|
|
397
|
+
name: string | undefined,
|
|
398
|
+
rawInput: unknown,
|
|
399
|
+
input: string | undefined,
|
|
400
|
+
createdAt: number,
|
|
401
|
+
): void {
|
|
402
|
+
const id = `history-tool:${callId}`;
|
|
403
|
+
const existing = itemsById.get(id);
|
|
404
|
+
const commandExecution = commandFromCodexTool(name, rawInput, existing?.commandExecution?.output);
|
|
405
|
+
itemsById.set(id, {
|
|
406
|
+
id,
|
|
407
|
+
conversationId,
|
|
408
|
+
type: "tool_call",
|
|
409
|
+
kind: historyToolKind(name),
|
|
410
|
+
itemId: callId,
|
|
411
|
+
toolCall: {
|
|
412
|
+
id: callId,
|
|
413
|
+
name: historyToolName(name),
|
|
414
|
+
input: input ?? existing?.toolCall?.input,
|
|
415
|
+
output: existing?.toolCall?.output,
|
|
416
|
+
createdAt: existing?.toolCall?.createdAt ?? createdAt,
|
|
417
|
+
status: existing?.toolCall?.status ?? "running",
|
|
418
|
+
},
|
|
419
|
+
commandExecution: commandExecution ?? existing?.commandExecution,
|
|
420
|
+
createdAt: existing?.createdAt ?? createdAt,
|
|
421
|
+
updatedAt: createdAt,
|
|
422
|
+
metadata: { source: "device-history", provider: "codex" },
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function completeHistoryTool(
|
|
427
|
+
itemsById: Map<string, StoredAgentTimelineItem>,
|
|
428
|
+
conversationId: string,
|
|
429
|
+
callId: string,
|
|
430
|
+
output: string | undefined,
|
|
431
|
+
createdAt: number,
|
|
432
|
+
): void {
|
|
433
|
+
const id = `history-tool:${callId}`;
|
|
434
|
+
const existing = itemsById.get(id);
|
|
435
|
+
const commandExecution = existing?.commandExecution
|
|
436
|
+
? { ...existing.commandExecution, output, status: "completed" as const }
|
|
437
|
+
: undefined;
|
|
438
|
+
itemsById.set(id, {
|
|
439
|
+
id,
|
|
440
|
+
conversationId,
|
|
441
|
+
type: "tool_call",
|
|
442
|
+
kind: existing?.kind ?? "tool_activity",
|
|
443
|
+
itemId: callId,
|
|
444
|
+
toolCall: {
|
|
445
|
+
id: callId,
|
|
446
|
+
name: existing?.toolCall?.name ?? "工具",
|
|
447
|
+
input: existing?.toolCall?.input,
|
|
448
|
+
output,
|
|
449
|
+
createdAt: existing?.toolCall?.createdAt ?? createdAt,
|
|
450
|
+
status: "completed",
|
|
451
|
+
},
|
|
452
|
+
commandExecution,
|
|
453
|
+
createdAt: existing?.createdAt ?? createdAt,
|
|
454
|
+
updatedAt: createdAt,
|
|
455
|
+
metadata: { source: "device-history", provider: "codex" },
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function relativeHistoryPath(path: string, cwd: string): string {
|
|
460
|
+
const normalized = path.replace(/\\/g, "/").replace(/^["']|["']$/g, "");
|
|
461
|
+
const normalizedCwd = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
462
|
+
return normalized.startsWith(`${normalizedCwd}/`)
|
|
463
|
+
? normalized.slice(normalizedCwd.length + 1)
|
|
464
|
+
: normalized;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function countDiffLines(diff: string | undefined): { added: number; removed: number } {
|
|
468
|
+
if (!diff) return { added: 0, removed: 0 };
|
|
469
|
+
let added = 0;
|
|
470
|
+
let removed = 0;
|
|
471
|
+
for (const line of diff.split("\n")) {
|
|
472
|
+
if (line.startsWith("+") && !line.startsWith("+++")) added += 1;
|
|
473
|
+
else if (line.startsWith("-") && !line.startsWith("---")) removed += 1;
|
|
474
|
+
}
|
|
475
|
+
return { added, removed };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function countContentLines(content: unknown): number {
|
|
479
|
+
if (typeof content !== "string") return 0;
|
|
480
|
+
if (!content) return 0;
|
|
481
|
+
return content.split(/\r?\n/).filter((line) => line.length > 0).length;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function changeKind(value: string | undefined): string | undefined {
|
|
485
|
+
if (value === "add") return "create";
|
|
486
|
+
if (value === "delete") return "delete";
|
|
487
|
+
if (value === "update" || value === "move") return "update";
|
|
488
|
+
return value;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function patchApplyFileChange(
|
|
492
|
+
conversationId: string,
|
|
493
|
+
payload: Record<string, unknown>,
|
|
494
|
+
cwd: string,
|
|
495
|
+
createdAt: number,
|
|
496
|
+
): StoredAgentTimelineItem | undefined {
|
|
497
|
+
const changes = asRecord(payload.changes);
|
|
498
|
+
if (!changes) return undefined;
|
|
499
|
+
const entries: NonNullable<StoredAgentTimelineItem["fileChange"]>["entries"] = [];
|
|
500
|
+
const diffParts: string[] = [];
|
|
501
|
+
for (const [absolutePath, rawChange] of Object.entries(changes)) {
|
|
502
|
+
const change = asRecord(rawChange);
|
|
503
|
+
if (!change) continue;
|
|
504
|
+
const path = relativeHistoryPath(absolutePath, cwd);
|
|
505
|
+
const kind = changeKind(typeof change.type === "string" ? change.type : undefined);
|
|
506
|
+
const diff = typeof change.unified_diff === "string" ? change.unified_diff : undefined;
|
|
507
|
+
const stats = countDiffLines(diff);
|
|
508
|
+
const added = stats.added || (kind === "create" ? countContentLines(change.content) : 0);
|
|
509
|
+
const removed = stats.removed;
|
|
510
|
+
const entry: NonNullable<StoredAgentTimelineItem["fileChange"]>["entries"][number] = { path };
|
|
511
|
+
if (kind) entry.kind = kind;
|
|
512
|
+
if (added > 0) entry.added = added;
|
|
513
|
+
if (removed > 0) entry.removed = removed;
|
|
514
|
+
entries.push(entry);
|
|
515
|
+
if (diff) {
|
|
516
|
+
diffParts.push([
|
|
517
|
+
`Path: ${path}`,
|
|
518
|
+
kind ? `Kind: ${kind}` : undefined,
|
|
519
|
+
`Totals: +${added} -${removed}`,
|
|
520
|
+
"",
|
|
521
|
+
"```diff",
|
|
522
|
+
diff.trimEnd(),
|
|
523
|
+
"```",
|
|
524
|
+
].filter((line): line is string => line !== undefined).join("\n"));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (entries.length === 0) return undefined;
|
|
528
|
+
const callId = typeof payload.call_id === "string" ? payload.call_id : `patch-${createdAt}`;
|
|
529
|
+
const status = payload.success === false ? "failed" : "completed";
|
|
530
|
+
const totalAdded = entries.reduce((sum, entry) => sum + (entry.added ?? 0), 0);
|
|
531
|
+
const totalRemoved = entries.reduce((sum, entry) => sum + (entry.removed ?? 0), 0);
|
|
532
|
+
const summary = entries
|
|
533
|
+
.map((entry) => [entry.kind, entry.path].filter(Boolean).join(" "))
|
|
534
|
+
.join("\n");
|
|
535
|
+
const diff = diffParts.length > 0 ? diffParts.join("\n\n---\n\n") : undefined;
|
|
536
|
+
return {
|
|
537
|
+
id: `history-file-change:${callId}`,
|
|
538
|
+
conversationId,
|
|
539
|
+
type: "tool_call",
|
|
540
|
+
kind: "file_change",
|
|
541
|
+
itemId: callId,
|
|
542
|
+
toolCall: {
|
|
543
|
+
id: callId,
|
|
544
|
+
name: "文件修改",
|
|
545
|
+
input: summary,
|
|
546
|
+
output: diff ?? (typeof payload.stdout === "string" ? payload.stdout : undefined),
|
|
547
|
+
createdAt,
|
|
548
|
+
status,
|
|
549
|
+
},
|
|
550
|
+
fileChange: {
|
|
551
|
+
entries,
|
|
552
|
+
diff,
|
|
553
|
+
summary,
|
|
554
|
+
status,
|
|
555
|
+
},
|
|
556
|
+
text: `已编辑 ${entries.length} 个文件 +${totalAdded} -${totalRemoved}`,
|
|
557
|
+
createdAt,
|
|
558
|
+
updatedAt: createdAt,
|
|
559
|
+
metadata: { source: "device-history", provider: "codex" },
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
295
563
|
export function listCodexStoredSessions(inputCwd: string): { sessions: CodexStoredSession[] } {
|
|
296
564
|
const root = join(homedir(), ".codex");
|
|
297
565
|
if (!existsSync(root)) return { sessions: [] };
|
|
@@ -309,7 +577,7 @@ export function listCodexStoredSessions(inputCwd: string): { sessions: CodexStor
|
|
|
309
577
|
const session: CodexStoredSession = {
|
|
310
578
|
id: metadata.id,
|
|
311
579
|
cwd: metadata.cwd,
|
|
312
|
-
title: indexed?.title,
|
|
580
|
+
title: indexed?.title ?? metadata.title,
|
|
313
581
|
createdAt: metadata.createdAt,
|
|
314
582
|
lastModified: indexed?.updatedAt ?? metadata.lastModified ?? Date.now(),
|
|
315
583
|
archived: file.archived,
|
|
@@ -355,7 +623,8 @@ export function loadCodexStoredTimeline(
|
|
|
355
623
|
return { items: [] };
|
|
356
624
|
}
|
|
357
625
|
|
|
358
|
-
const
|
|
626
|
+
const itemsById = new Map<string, StoredAgentTimelineItem>();
|
|
627
|
+
const seenMessages = new Set<string>();
|
|
359
628
|
let index = 0;
|
|
360
629
|
for (const line of readHistorySample(file.path, statSize).split(/\r?\n/)) {
|
|
361
630
|
const trimmed = line.trim();
|
|
@@ -363,21 +632,76 @@ export function loadCodexStoredTimeline(
|
|
|
363
632
|
try {
|
|
364
633
|
const entry = asRecord(JSON.parse(trimmed));
|
|
365
634
|
const payload = asRecord(entry?.payload);
|
|
366
|
-
if (entry?.type !== "event_msg" || !payload) continue;
|
|
367
|
-
const eventType = typeof payload.type === "string" ? payload.type : undefined;
|
|
368
635
|
const createdAt =
|
|
369
|
-
parseTimestamp(entry
|
|
636
|
+
parseTimestamp(entry?.timestamp ?? payload?.created_at ?? payload?.started_at ?? payload?.completed_at) ??
|
|
370
637
|
statMtime + index;
|
|
371
|
-
if (
|
|
372
|
-
const
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
638
|
+
if (entry?.type === "event_msg" && payload) {
|
|
639
|
+
const eventType = typeof payload.type === "string" ? payload.type : undefined;
|
|
640
|
+
if (eventType === "user_message") {
|
|
641
|
+
const text = normalizeHistoryText(payload.message);
|
|
642
|
+
if (!text || isInjectedCodexContext(text)) continue;
|
|
643
|
+
const dedupeKey = messageDedupeKey("user", text);
|
|
644
|
+
if (seenMessages.has(dedupeKey)) continue;
|
|
645
|
+
seenMessages.add(dedupeKey);
|
|
646
|
+
itemsById.set(`history:${sessionId}:${index}`, historyMessage(conversationId, index, "user", text, createdAt, sessionId));
|
|
647
|
+
index += 1;
|
|
648
|
+
} else if (eventType === "agent_message") {
|
|
649
|
+
const text = normalizeHistoryText(payload.message);
|
|
650
|
+
if (!text || isInjectedCodexContext(text)) continue;
|
|
651
|
+
const dedupeKey = messageDedupeKey("assistant", text);
|
|
652
|
+
if (seenMessages.has(dedupeKey)) continue;
|
|
653
|
+
seenMessages.add(dedupeKey);
|
|
654
|
+
itemsById.set(`history:${sessionId}:${index}`, historyMessage(conversationId, index, "assistant", text, createdAt, sessionId));
|
|
655
|
+
index += 1;
|
|
656
|
+
} else if (eventType === "patch_apply_end") {
|
|
657
|
+
const item = patchApplyFileChange(conversationId, payload, inputCwd, createdAt);
|
|
658
|
+
if (item) itemsById.set(item.id, item);
|
|
659
|
+
}
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (entry?.type !== "response_item" || !payload) continue;
|
|
664
|
+
const responseType = typeof payload.type === "string" ? payload.type : undefined;
|
|
665
|
+
if (responseType === "message") {
|
|
666
|
+
const role = visibleCodexRole(payload.role);
|
|
667
|
+
if (!role) continue;
|
|
377
668
|
const text = normalizeHistoryText(payload.message);
|
|
378
|
-
|
|
379
|
-
|
|
669
|
+
const contentText = text ?? normalizeHistoryText(payload.content);
|
|
670
|
+
if (!contentText || isInjectedCodexContext(contentText)) continue;
|
|
671
|
+
const dedupeKey = messageDedupeKey(role, contentText);
|
|
672
|
+
if (seenMessages.has(dedupeKey)) continue;
|
|
673
|
+
seenMessages.add(dedupeKey);
|
|
674
|
+
itemsById.set(`history:${sessionId}:${index}`, historyMessage(conversationId, index, role, contentText, createdAt, sessionId));
|
|
380
675
|
index += 1;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (responseType === "function_call") {
|
|
679
|
+
const callId = typeof payload.call_id === "string"
|
|
680
|
+
? payload.call_id
|
|
681
|
+
: typeof payload.id === "string"
|
|
682
|
+
? payload.id
|
|
683
|
+
: undefined;
|
|
684
|
+
if (!callId) continue;
|
|
685
|
+
const name = typeof payload.name === "string" ? payload.name : undefined;
|
|
686
|
+
upsertHistoryTool(
|
|
687
|
+
itemsById,
|
|
688
|
+
conversationId,
|
|
689
|
+
callId,
|
|
690
|
+
name,
|
|
691
|
+
payload.arguments,
|
|
692
|
+
stringifyHistoryValue(payload.arguments),
|
|
693
|
+
createdAt,
|
|
694
|
+
);
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
if (responseType === "function_call_output") {
|
|
698
|
+
const callId = typeof payload.call_id === "string"
|
|
699
|
+
? payload.call_id
|
|
700
|
+
: typeof payload.id === "string"
|
|
701
|
+
? payload.id
|
|
702
|
+
: undefined;
|
|
703
|
+
if (!callId) continue;
|
|
704
|
+
completeHistoryTool(itemsById, conversationId, callId, stringifyHistoryValue(payload.output), createdAt);
|
|
381
705
|
}
|
|
382
706
|
} catch {
|
|
383
707
|
// Ignore malformed or partial JSONL lines in the history window.
|
|
@@ -385,7 +709,7 @@ export function loadCodexStoredTimeline(
|
|
|
385
709
|
}
|
|
386
710
|
|
|
387
711
|
return {
|
|
388
|
-
items:
|
|
712
|
+
items: [...itemsById.values()]
|
|
389
713
|
.sort((a, b) => a.createdAt - b.createdAt)
|
|
390
714
|
.slice(-MAX_HISTORY_ITEMS),
|
|
391
715
|
};
|