spiracha 2.0.0 → 2.2.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/AGENTS.md +27 -12
- package/README.md +42 -19
- package/apps/ui/AGENTS.md +18 -10
- package/apps/ui/README.md +36 -11
- package/apps/ui/src/components/analytics-breakdowns.tsx +106 -0
- package/apps/ui/src/components/antigravity-conversations-table.tsx +51 -11
- package/apps/ui/src/components/antigravity-workspaces-table.tsx +1 -1
- package/apps/ui/src/components/app-shell.tsx +46 -3
- package/apps/ui/src/components/breadcrumbs.tsx +21 -3
- package/apps/ui/src/components/claude-code-sessions-table.tsx +42 -6
- package/apps/ui/src/components/claude-code-workspaces-table.tsx +3 -1
- package/apps/ui/src/components/cursor-threads-table.tsx +10 -47
- package/apps/ui/src/components/data-table.tsx +23 -5
- package/apps/ui/src/components/delete-confirm-dialog.tsx +4 -3
- package/apps/ui/src/components/export-dialog.tsx +79 -74
- package/apps/ui/src/components/grok-sessions-table.tsx +146 -0
- package/apps/ui/src/components/grok-workspaces-table.tsx +53 -0
- package/apps/ui/src/components/kiro-sessions-table.tsx +40 -4
- package/apps/ui/src/components/opencode-sessions-table.tsx +40 -4
- package/apps/ui/src/components/page-header.tsx +15 -9
- package/apps/ui/src/components/projects-table.tsx +4 -1
- package/apps/ui/src/components/qoder-sessions-table.tsx +23 -2
- package/apps/ui/src/components/qoder-workspaces-table.tsx +5 -1
- package/apps/ui/src/components/route-error-panel.tsx +13 -0
- package/apps/ui/src/components/selection-actions-toolbar.tsx +80 -0
- package/apps/ui/src/components/thread-goals-panel.tsx +50 -0
- package/apps/ui/src/components/thread-tools-panel.tsx +142 -0
- package/apps/ui/src/components/threads-table.tsx +13 -47
- package/apps/ui/src/components/transcript-view.tsx +148 -52
- package/apps/ui/src/components/ui/select.tsx +1 -1
- package/apps/ui/src/lib/antigravity-conversation-state.ts +17 -4
- package/apps/ui/src/lib/antigravity-server.ts +209 -11
- package/apps/ui/src/lib/antigravity-transcript-events.ts +57 -73
- package/apps/ui/src/lib/claude-code-queries.ts +8 -0
- package/apps/ui/src/lib/claude-code-server.ts +185 -7
- package/apps/ui/src/lib/claude-code-transcript-events.ts +8 -1
- package/apps/ui/src/lib/codex-queries.ts +16 -0
- package/apps/ui/src/lib/codex-server.ts +134 -52
- package/apps/ui/src/lib/cursor-server.ts +68 -53
- package/apps/ui/src/lib/cursor-transcript-events.ts +11 -86
- package/apps/ui/src/lib/delete-batch.ts +52 -0
- package/apps/ui/src/lib/error-presentation.ts +43 -0
- package/apps/ui/src/lib/export-mutation.ts +20 -0
- package/apps/ui/src/lib/export-options.ts +15 -0
- package/apps/ui/src/lib/formatters.ts +3 -5
- package/apps/ui/src/lib/grok-queries.ts +22 -0
- package/apps/ui/src/lib/grok-server.ts +168 -0
- package/apps/ui/src/lib/grok-transcript-events.ts +149 -0
- package/apps/ui/src/lib/kiro-server.ts +97 -7
- package/apps/ui/src/lib/mutation-error.ts +15 -0
- package/apps/ui/src/lib/opencode-server.ts +96 -7
- package/apps/ui/src/lib/path-utils.ts +1 -1
- package/apps/ui/src/lib/qoder-queries.ts +15 -0
- package/apps/ui/src/lib/qoder-server.ts +67 -11
- package/apps/ui/src/lib/qoder-transcript-events.ts +1 -1
- package/apps/ui/src/lib/route-search.ts +114 -0
- package/apps/ui/src/lib/route-state-reset.tsx +10 -0
- package/apps/ui/src/lib/settings-server.ts +33 -0
- package/apps/ui/src/lib/settings-store.tsx +82 -38
- package/apps/ui/src/lib/settings.ts +65 -0
- package/apps/ui/src/lib/source-session-export-server.ts +86 -3
- package/apps/ui/src/lib/thread-metadata.ts +22 -0
- package/apps/ui/src/lib/thread-transcript-load.ts +15 -0
- package/apps/ui/src/lib/thread-transcript-stats.ts +10 -1
- package/apps/ui/src/lib/workspace-delete-navigation.ts +20 -0
- package/apps/ui/src/routeTree.gen.ts +341 -234
- package/apps/ui/src/routes/__root.tsx +12 -15
- package/apps/ui/src/routes/analytics.tsx +10 -61
- package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +149 -29
- package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +262 -42
- package/apps/ui/src/routes/antigravity.index.tsx +2 -2
- package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +4 -0
- package/apps/ui/src/routes/api.v1.conversations.delete.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.export.ts +12 -0
- package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +168 -28
- package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +179 -45
- package/apps/ui/src/routes/claude-code.index.tsx +2 -2
- package/apps/ui/src/routes/codex.$project.tsx +33 -31
- package/apps/ui/src/routes/codex.index.tsx +5 -10
- package/apps/ui/src/routes/cursor-threads.$composerId.tsx +31 -18
- package/apps/ui/src/routes/cursor.$workspaceKey.tsx +44 -28
- package/apps/ui/src/routes/cursor.index.tsx +5 -2
- package/apps/ui/src/routes/grok-sessions.$sessionId.tsx +401 -0
- package/apps/ui/src/routes/grok.$workspaceKey.tsx +295 -0
- package/apps/ui/src/routes/grok.index.tsx +48 -0
- package/apps/ui/src/routes/index.tsx +2 -18
- package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +91 -27
- package/apps/ui/src/routes/kiro.$workspaceKey.tsx +213 -51
- package/apps/ui/src/routes/kiro.index.tsx +2 -2
- package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +108 -29
- package/apps/ui/src/routes/opencode.$workspaceKey.tsx +231 -55
- package/apps/ui/src/routes/opencode.index.tsx +6 -2
- package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +19 -15
- package/apps/ui/src/routes/qoder.$workspaceKey.tsx +89 -48
- package/apps/ui/src/routes/qoder.index.tsx +2 -2
- package/apps/ui/src/routes/threads.$threadId.tsx +620 -105
- package/apps/ui/vite.config.ts +22 -12
- package/bin/spiracha.ts +28 -1
- package/package.json +37 -27
- package/src/client.ts +183 -5
- package/src/lib/antigravity-db.ts +424 -94
- package/src/lib/antigravity-exporter-types.ts +3 -0
- package/src/lib/antigravity-keychain.ts +7 -8
- package/src/lib/antigravity-projects.ts +201 -0
- package/src/lib/antigravity-transcript-phase.ts +47 -0
- package/src/lib/claude-code-db.ts +534 -42
- package/src/lib/claude-code-exporter-types.ts +5 -0
- package/src/lib/claude-code-transcript-phase.ts +60 -1
- package/src/lib/claude-code-transcript.ts +8 -5
- package/src/lib/codex-analytics.ts +32 -5
- package/src/lib/codex-browser-db.ts +540 -137
- package/src/lib/codex-browser-export.ts +18 -27
- package/src/lib/codex-browser-types.ts +21 -1
- package/src/lib/codex-thread-cache.ts +106 -23
- package/src/lib/codex-thread-parser.ts +149 -55
- package/src/lib/codex-thread-recovery.ts +38 -11
- package/src/lib/codex-transcript-filter.ts +31 -0
- package/src/lib/codex-transcript-renderer.ts +87 -79
- package/src/lib/concurrency.ts +56 -3
- package/src/lib/conversation-api.ts +410 -27
- package/src/lib/conversation-data/adapter-helpers.ts +25 -3
- package/src/lib/conversation-data/antigravity-adapter.ts +70 -26
- package/src/lib/conversation-data/claude-code-adapter.ts +69 -12
- package/src/lib/conversation-data/codex-adapter.ts +108 -73
- package/src/lib/conversation-data/cursor-adapter.ts +77 -29
- package/src/lib/conversation-data/grok-adapter.ts +223 -0
- package/src/lib/conversation-data/index.ts +105 -11
- package/src/lib/conversation-data/kiro-adapter.ts +59 -12
- package/src/lib/conversation-data/message-selector.ts +2 -5
- package/src/lib/conversation-data/opencode-adapter.ts +67 -17
- package/src/lib/conversation-data/path-match.ts +1 -13
- package/src/lib/conversation-data/qoder-adapter.ts +67 -37
- package/src/lib/conversation-data/types.ts +43 -0
- package/src/lib/conversation-zip-export.ts +75 -0
- package/src/lib/cursor-db.ts +68 -28
- package/src/lib/cursor-id.ts +17 -0
- package/src/lib/cursor-recovery.ts +146 -35
- package/src/lib/cursor-transcript-phase.ts +40 -0
- package/src/lib/cursor-transcript.ts +21 -5
- package/src/lib/grok-db.ts +1068 -0
- package/src/lib/grok-exporter-types.ts +110 -0
- package/src/lib/grok-transcript-phase.ts +61 -0
- package/src/lib/grok-transcript.ts +167 -0
- package/src/lib/kiro-db.ts +187 -43
- package/src/lib/kiro-transcript.ts +0 -4
- package/src/lib/model-label.ts +11 -3
- package/src/lib/opencode-db.ts +599 -60
- package/src/lib/opencode-think-tags.ts +17 -3
- package/src/lib/opencode-transcript.ts +8 -5
- package/src/lib/portable-path.ts +9 -0
- package/src/lib/qoder-acp-client.ts +11 -2
- package/src/lib/qoder-db.ts +51 -17
- package/src/lib/qoder-transcript-phase.ts +64 -0
- package/src/lib/qoder-transcript.ts +30 -2
- package/src/lib/shared.ts +63 -15
- package/src/lib/sqlite-error.ts +31 -5
- package/src/lib/transcript-load-limiter.ts +87 -0
- package/src/lib/ui-cache.ts +185 -24
- package/src/lib/ui-export-archive.ts +58 -39
- package/src/lib/ui-export-files.ts +75 -15
- package/src/lib/ui-export-zip.ts +40 -0
- package/apps/ui/package.json +0 -65
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite';
|
|
2
|
-
import { copyFile, utimes } from 'node:fs/promises';
|
|
2
|
+
import { copyFile, readdir, rm, utimes } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import type { RecoverProjectThreadsResult } from './codex-browser-types';
|
|
5
|
-
import { getPortablePathBasename } from './
|
|
5
|
+
import { getPortablePathBasename } from './portable-path';
|
|
6
6
|
import { runWithSqliteRetry } from './sqlite-retry';
|
|
7
7
|
|
|
8
8
|
type RecoveryThreadRow = {
|
|
@@ -18,6 +18,8 @@ type GlobalState = {
|
|
|
18
18
|
'project-order'?: string[];
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
const RECOVERY_BACKUP_RETENTION_COUNT = 5;
|
|
22
|
+
|
|
21
23
|
const backupFile = async (filePath: string, label: string) => {
|
|
22
24
|
const stamp = new Date()
|
|
23
25
|
.toISOString()
|
|
@@ -26,6 +28,14 @@ const backupFile = async (filePath: string, label: string) => {
|
|
|
26
28
|
.replace('T', '-');
|
|
27
29
|
const backupPath = `${filePath}.bak-${label}-${stamp}`;
|
|
28
30
|
await copyFile(filePath, backupPath);
|
|
31
|
+
const directory = path.dirname(filePath);
|
|
32
|
+
const prefix = `${path.basename(filePath)}.bak-${label}-`;
|
|
33
|
+
const backups = (await readdir(directory))
|
|
34
|
+
.filter((entry) => entry.startsWith(prefix))
|
|
35
|
+
.sort((left, right) => right.localeCompare(left));
|
|
36
|
+
await Promise.all(
|
|
37
|
+
backups.slice(RECOVERY_BACKUP_RETENTION_COUNT).map((entry) => rm(path.join(directory, entry), { force: true })),
|
|
38
|
+
);
|
|
29
39
|
return backupPath;
|
|
30
40
|
};
|
|
31
41
|
|
|
@@ -100,9 +110,9 @@ const refreshThreadRows = (db: Database, threadIds: string[]) => {
|
|
|
100
110
|
return Number(result.changes);
|
|
101
111
|
};
|
|
102
112
|
|
|
103
|
-
const
|
|
113
|
+
const prepareSessionIndexRefresh = async (sessionIndexPath: string, threadIds: string[]) => {
|
|
104
114
|
if (threadIds.length === 0) {
|
|
105
|
-
return 0;
|
|
115
|
+
return { content: null, updated: 0 };
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
const now = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
@@ -116,7 +126,19 @@ const refreshSessionIndex = async (sessionIndexPath: string, threadIds: string[]
|
|
|
116
126
|
continue;
|
|
117
127
|
}
|
|
118
128
|
|
|
119
|
-
|
|
129
|
+
let parsed: { id?: string; updated_at?: string };
|
|
130
|
+
try {
|
|
131
|
+
const value: unknown = JSON.parse(line);
|
|
132
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
133
|
+
rewrittenLines.push(line);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
parsed = value as { id?: string; updated_at?: string };
|
|
137
|
+
} catch {
|
|
138
|
+
rewrittenLines.push(line);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
120
142
|
if (parsed.id && threadIdSet.has(parsed.id)) {
|
|
121
143
|
parsed.updated_at = now;
|
|
122
144
|
updated += 1;
|
|
@@ -125,8 +147,10 @@ const refreshSessionIndex = async (sessionIndexPath: string, threadIds: string[]
|
|
|
125
147
|
rewrittenLines.push(JSON.stringify(parsed));
|
|
126
148
|
}
|
|
127
149
|
|
|
128
|
-
|
|
129
|
-
|
|
150
|
+
return {
|
|
151
|
+
content: `${rewrittenLines.join('\n')}\n`,
|
|
152
|
+
updated,
|
|
153
|
+
};
|
|
130
154
|
};
|
|
131
155
|
|
|
132
156
|
const touchRolloutFiles = async (codexDir: string, rolloutPaths: string[]) => {
|
|
@@ -177,12 +201,15 @@ export const recoverCodexProjectThreads = async (
|
|
|
177
201
|
const topLevelThreads = getProjectTopLevelThreads(db, projectName);
|
|
178
202
|
const projectCwds = [...new Set(topLevelThreads.map((thread) => thread.cwd))];
|
|
179
203
|
const rootUpdateResult = updateGlobalRoots(globalState, projectCwds);
|
|
180
|
-
await writeGlobalState(globalStatePath, rootUpdateResult.state);
|
|
181
|
-
|
|
182
204
|
const threadIds = topLevelThreads.map((thread) => thread.id);
|
|
183
205
|
const rolloutPaths = topLevelThreads.map((thread) => thread.rollout_path);
|
|
206
|
+
const sessionIndexRefresh = await prepareSessionIndexRefresh(sessionIndexPath, threadIds);
|
|
207
|
+
|
|
208
|
+
await writeGlobalState(globalStatePath, rootUpdateResult.state);
|
|
184
209
|
const threadDbRowsUpdated = refreshThreadRows(db, threadIds);
|
|
185
|
-
|
|
210
|
+
if (sessionIndexRefresh.content !== null) {
|
|
211
|
+
await Bun.write(sessionIndexPath, sessionIndexRefresh.content);
|
|
212
|
+
}
|
|
186
213
|
const rolloutFilesTouched = await touchRolloutFiles(codexDir, rolloutPaths);
|
|
187
214
|
|
|
188
215
|
return {
|
|
@@ -192,7 +219,7 @@ export const recoverCodexProjectThreads = async (
|
|
|
192
219
|
resolvedCwds: projectCwds,
|
|
193
220
|
rolloutFilesTouched,
|
|
194
221
|
savedRootsAdded: rootUpdateResult.savedRootsAdded,
|
|
195
|
-
sessionIndexRowsUpdated,
|
|
222
|
+
sessionIndexRowsUpdated: sessionIndexRefresh.updated,
|
|
196
223
|
threadDbRowsUpdated,
|
|
197
224
|
topLevelThreadsFound: threadIds.length,
|
|
198
225
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ThreadEvent } from './codex-browser-types';
|
|
2
|
+
|
|
3
|
+
const isCommentaryMessage = (event: ThreadEvent) =>
|
|
4
|
+
event.kind === 'message' && event.role === 'assistant' && event.phase === 'commentary';
|
|
5
|
+
|
|
6
|
+
export type CodexTranscriptEventFilters = {
|
|
7
|
+
showCommentary: boolean;
|
|
8
|
+
showExtraEvents: boolean;
|
|
9
|
+
showToolCalls: boolean;
|
|
10
|
+
showUserMessages: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const shouldShowCodexTranscriptEvent = (event: ThreadEvent, filters: CodexTranscriptEventFilters) => {
|
|
14
|
+
if (isCommentaryMessage(event) && !filters.showCommentary) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (event.kind === 'message') {
|
|
19
|
+
if (event.role === 'user' && !filters.showUserMessages) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return !event.isHiddenByDefault || filters.showExtraEvents;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (event.kind === 'tool_call' || event.kind === 'tool_output') {
|
|
27
|
+
return filters.showToolCalls;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return filters.showExtraEvents;
|
|
31
|
+
};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { createReadStream } from 'node:fs';
|
|
2
2
|
import { rm } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import type { Readable, Writable } from 'node:stream';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import {
|
|
7
|
+
parseCodexTranscriptRecord,
|
|
8
|
+
shouldHideCodexTranscriptText,
|
|
9
|
+
stripCodexMemoryCitationBlocks,
|
|
10
|
+
} from './codex-thread-parser';
|
|
5
11
|
import {
|
|
6
12
|
type CodexTranscriptExportTarget,
|
|
7
13
|
type CodexTranscriptRenderOptions,
|
|
@@ -23,10 +29,17 @@ import {
|
|
|
23
29
|
type JsonValue,
|
|
24
30
|
type MetadataEntry,
|
|
25
31
|
readJsonlObjects,
|
|
32
|
+
renderCodeBlock,
|
|
26
33
|
renderDocumentTitle,
|
|
27
34
|
renderMetadataBlock,
|
|
28
35
|
renderSection,
|
|
36
|
+
stripCodexAppDirectiveLines,
|
|
29
37
|
} from './shared';
|
|
38
|
+
import { runWithTranscriptLoadLimit } from './transcript-load-limiter';
|
|
39
|
+
|
|
40
|
+
export const pipeCodexExportStream = async (source: Readable, destination: Writable) => {
|
|
41
|
+
await pipeline(source, destination, { end: false });
|
|
42
|
+
};
|
|
30
43
|
|
|
31
44
|
export const renderCodexSessionFile = async (
|
|
32
45
|
target: CodexTranscriptExportTarget,
|
|
@@ -35,7 +48,14 @@ export const renderCodexSessionFile = async (
|
|
|
35
48
|
let transcriptState: CodexTranscriptState;
|
|
36
49
|
|
|
37
50
|
try {
|
|
38
|
-
transcriptState = await
|
|
51
|
+
transcriptState = await runWithTranscriptLoadLimit(
|
|
52
|
+
() => collectCodexTranscript(target.sessionFile, options, target.thread?.model ?? null),
|
|
53
|
+
{
|
|
54
|
+
id: target.thread?.id,
|
|
55
|
+
path: target.sessionFile,
|
|
56
|
+
source: 'codex-export-inline',
|
|
57
|
+
},
|
|
58
|
+
);
|
|
39
59
|
} catch (error) {
|
|
40
60
|
const message = error instanceof Error ? error.message : String(error);
|
|
41
61
|
throw new Error(`Failed to read Codex transcript ${target.sessionFile}: ${message}`);
|
|
@@ -78,19 +98,28 @@ export const writeCodexSessionFileExport = async (
|
|
|
78
98
|
let wroteSection = false;
|
|
79
99
|
|
|
80
100
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
await runWithTranscriptLoadLimit(
|
|
102
|
+
async () => {
|
|
103
|
+
transcriptStream = await createExportWriteStream(transcriptOutputPath);
|
|
104
|
+
for await (const parsed of readJsonlObjects(target.sessionFile)) {
|
|
105
|
+
captureSessionMeta(parsed, state.sessionMeta);
|
|
106
|
+
const block = renderCodexTranscriptRecord(parsed, options, state);
|
|
107
|
+
if (!block) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
transcriptStream.write(transform(wroteSection ? `${getSectionSeparator()}${block}` : block));
|
|
112
|
+
wroteSection = true;
|
|
113
|
+
}
|
|
114
|
+
await finalizeExportWriteStream(transcriptStream);
|
|
115
|
+
transcriptStream = null;
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: target.thread?.id,
|
|
119
|
+
path: target.sessionFile,
|
|
120
|
+
source: 'codex-export-stream',
|
|
121
|
+
},
|
|
122
|
+
);
|
|
94
123
|
|
|
95
124
|
if (!wroteSection) {
|
|
96
125
|
return false;
|
|
@@ -104,8 +133,7 @@ export const writeCodexSessionFileExport = async (
|
|
|
104
133
|
}
|
|
105
134
|
|
|
106
135
|
const transcriptReadStream = createReadStream(transcriptOutputPath, { encoding: 'utf8' });
|
|
107
|
-
transcriptReadStream
|
|
108
|
-
await finished(transcriptReadStream);
|
|
136
|
+
await pipeCodexExportStream(transcriptReadStream, outputStream);
|
|
109
137
|
outputStream.write('\n');
|
|
110
138
|
await finalizeExportWriteStream(outputStream);
|
|
111
139
|
} catch (error) {
|
|
@@ -189,7 +217,7 @@ const processCodexMessageRecord = (
|
|
|
189
217
|
options: CodexTranscriptRenderOptions,
|
|
190
218
|
state: CodexTranscriptState,
|
|
191
219
|
) => {
|
|
192
|
-
return renderMessageBlock(message, options.outputFormat, state
|
|
220
|
+
return renderMessageBlock(message, options.outputFormat, state, options.includeCommentary);
|
|
193
221
|
};
|
|
194
222
|
|
|
195
223
|
const buildStreamExportPrefix = (
|
|
@@ -494,45 +522,22 @@ const normalizeMessage = (value: Record<string, JsonValue>): MessageRecord | nul
|
|
|
494
522
|
};
|
|
495
523
|
|
|
496
524
|
const extractToolRecord = (parsed: Record<string, JsonValue>): ToolRecord | null => {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const payload = asObject(parsed.payload);
|
|
502
|
-
if (!payload) {
|
|
503
|
-
return null;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (payload.type === 'function_call') {
|
|
507
|
-
const name = asString(payload.name);
|
|
508
|
-
const argumentsText = asString(payload.arguments);
|
|
509
|
-
const callId = asString(payload.call_id);
|
|
510
|
-
|
|
511
|
-
if (name !== 'exec_command') {
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
|
|
525
|
+
const event = parseCodexTranscriptRecord(parsed);
|
|
526
|
+
if (event?.kind === 'tool_call') {
|
|
515
527
|
return {
|
|
516
|
-
argumentsText: argumentsText ?? undefined,
|
|
517
|
-
callId,
|
|
528
|
+
argumentsText: event.argumentsText ?? undefined,
|
|
529
|
+
callId: event.callId,
|
|
518
530
|
kind: 'call',
|
|
519
|
-
name,
|
|
531
|
+
name: event.name,
|
|
520
532
|
};
|
|
521
533
|
}
|
|
522
534
|
|
|
523
|
-
if (
|
|
524
|
-
const callId = asString(payload.call_id);
|
|
525
|
-
const outputText = asString(payload.output);
|
|
526
|
-
|
|
527
|
-
if (!outputText?.includes('Command: ')) {
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
|
|
535
|
+
if (event?.kind === 'tool_output') {
|
|
531
536
|
return {
|
|
532
|
-
callId,
|
|
537
|
+
callId: event.callId,
|
|
533
538
|
kind: 'output',
|
|
534
|
-
name: '
|
|
535
|
-
outputText: outputText
|
|
539
|
+
name: 'tool_output',
|
|
540
|
+
outputText: event.outputText,
|
|
536
541
|
};
|
|
537
542
|
}
|
|
538
543
|
|
|
@@ -542,7 +547,7 @@ const extractToolRecord = (parsed: Record<string, JsonValue>): ToolRecord | null
|
|
|
542
547
|
const renderMessageBlock = (
|
|
543
548
|
message: MessageRecord,
|
|
544
549
|
outputFormat: ExportFormat,
|
|
545
|
-
|
|
550
|
+
state: CodexTranscriptState,
|
|
546
551
|
includeCommentary: boolean,
|
|
547
552
|
): string => {
|
|
548
553
|
if (message.role !== 'user' && message.role !== 'assistant') {
|
|
@@ -553,12 +558,15 @@ const renderMessageBlock = (
|
|
|
553
558
|
return '';
|
|
554
559
|
}
|
|
555
560
|
|
|
556
|
-
const
|
|
557
|
-
|
|
561
|
+
const extractedText = extractText(message.content);
|
|
562
|
+
const text = stripCodexMemoryCitationBlocks(
|
|
563
|
+
stripCodexAppDirectiveLines(cleanExtractedText(stripPreviewBlock(extractedText))),
|
|
564
|
+
);
|
|
565
|
+
if (!text || shouldHideCodexTranscriptText(message.role, text)) {
|
|
558
566
|
return '';
|
|
559
567
|
}
|
|
560
568
|
|
|
561
|
-
const title = message.role === 'user' ? 'User' : formatModelLabel(message.model ?? assistantModel);
|
|
569
|
+
const title = message.role === 'user' ? 'User' : formatModelLabel(message.model ?? state.assistantModel);
|
|
562
570
|
const body = message.phase ? `Phase: ${message.phase}\n\n${text}` : text;
|
|
563
571
|
|
|
564
572
|
return renderSection(title, body, outputFormat);
|
|
@@ -567,11 +575,18 @@ const renderMessageBlock = (
|
|
|
567
575
|
const renderToolBlock = (tool: ToolRecord, outputFormat: ExportFormat): string => {
|
|
568
576
|
if (tool.kind === 'call') {
|
|
569
577
|
const details = formatToolCallDetails(tool, outputFormat);
|
|
570
|
-
return
|
|
578
|
+
return renderSection('Tool Call', details, outputFormat);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const outputText = tool.outputText ?? '';
|
|
582
|
+
const summary = formatToolOutputSummary(outputText, outputFormat);
|
|
583
|
+
if (!summary && !outputText.trim()) {
|
|
584
|
+
return '';
|
|
571
585
|
}
|
|
572
586
|
|
|
573
|
-
const
|
|
574
|
-
|
|
587
|
+
const lines = tool.callId ? [`Call ID: ${tool.callId}`, ''] : [];
|
|
588
|
+
lines.push(summary || renderCodeBlock(outputText.trim(), outputFormat));
|
|
589
|
+
return renderSection('Tool Output', lines.join('\n'), outputFormat);
|
|
575
590
|
};
|
|
576
591
|
|
|
577
592
|
const stripPreviewBlock = (text: string): string => {
|
|
@@ -586,7 +601,7 @@ const stripPreviewBlock = (text: string): string => {
|
|
|
586
601
|
|
|
587
602
|
const first = parts[0];
|
|
588
603
|
const second = parts[1];
|
|
589
|
-
const isTranscriptHeading = (value: string) => /^##\s
|
|
604
|
+
const isTranscriptHeading = (value: string) => /^##\s+(?:assistant|user)$/i.test(value);
|
|
590
605
|
const looksLikePreview =
|
|
591
606
|
!/^([UA]):/i.test(first) &&
|
|
592
607
|
!isTranscriptHeading(first) &&
|
|
@@ -600,33 +615,26 @@ const stripPreviewBlock = (text: string): string => {
|
|
|
600
615
|
return parts.slice(1).join('\n\n');
|
|
601
616
|
};
|
|
602
617
|
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if (text.startsWith('AGENTS.md instructions for ')) {
|
|
609
|
-
return true;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
if (text.startsWith('# AGENTS.md instructions for ')) {
|
|
613
|
-
return true;
|
|
618
|
+
const formatToolCallDetails = (tool: ToolRecord, outputFormat: ExportFormat): string => {
|
|
619
|
+
const lines = [`Tool: ${formatInlineLiteral(tool.name, outputFormat)}`];
|
|
620
|
+
if (tool.callId) {
|
|
621
|
+
lines.push(`Call ID: ${tool.callId}`);
|
|
614
622
|
}
|
|
615
623
|
|
|
616
|
-
if (
|
|
617
|
-
|
|
624
|
+
if (tool.name === 'exec_command') {
|
|
625
|
+
const details = parseExecCommandArguments(tool.argumentsText);
|
|
626
|
+
if (details.cmd) {
|
|
627
|
+
lines.push(`Command: ${formatInlineLiteral(details.cmd, outputFormat)}`);
|
|
628
|
+
return lines.join('\n');
|
|
629
|
+
}
|
|
618
630
|
}
|
|
619
631
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const formatToolCallDetails = (tool: ToolRecord, outputFormat: ExportFormat): string => {
|
|
624
|
-
if (tool.name !== 'exec_command') {
|
|
625
|
-
return '';
|
|
632
|
+
const argumentsText = tool.argumentsText?.trim();
|
|
633
|
+
if (argumentsText) {
|
|
634
|
+
lines.push('', 'Input:', '', renderCodeBlock(argumentsText, outputFormat));
|
|
626
635
|
}
|
|
627
636
|
|
|
628
|
-
|
|
629
|
-
return details.cmd ? `Command: ${formatInlineLiteral(details.cmd, outputFormat)}` : '';
|
|
637
|
+
return lines.join('\n');
|
|
630
638
|
};
|
|
631
639
|
|
|
632
640
|
const extractText = (content: JsonValue): string => {
|
package/src/lib/concurrency.ts
CHANGED
|
@@ -11,15 +11,68 @@ export const mapWithConcurrency = async <T, TResult>(
|
|
|
11
11
|
const workerLimit = Math.max(1, requestedLimit);
|
|
12
12
|
const results = new Array<TResult>(values.length);
|
|
13
13
|
let nextIndex = 0;
|
|
14
|
+
let failed = false;
|
|
14
15
|
|
|
15
16
|
const worker = async () => {
|
|
16
|
-
while (nextIndex < values.length) {
|
|
17
|
+
while (!failed && nextIndex < values.length) {
|
|
17
18
|
const currentIndex = nextIndex;
|
|
18
19
|
nextIndex += 1;
|
|
19
|
-
|
|
20
|
+
try {
|
|
21
|
+
results[currentIndex] = await mapper(values[currentIndex]!, currentIndex);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
failed = true;
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
20
26
|
}
|
|
21
27
|
};
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
const settledWorkers = await Promise.allSettled(
|
|
30
|
+
Array.from({ length: Math.min(workerLimit, values.length) }, () => worker()),
|
|
31
|
+
);
|
|
32
|
+
const failure = settledWorkers.find((result): result is PromiseRejectedResult => result.status === 'rejected');
|
|
33
|
+
if (failure) {
|
|
34
|
+
throw failure.reason;
|
|
35
|
+
}
|
|
24
36
|
return results;
|
|
25
37
|
};
|
|
38
|
+
|
|
39
|
+
export const createConcurrencyLimiter = (limit: number) => {
|
|
40
|
+
const requestedLimit = Number.isFinite(limit) ? Math.floor(limit) : 1;
|
|
41
|
+
const workerLimit = Math.max(1, requestedLimit);
|
|
42
|
+
const queue: Array<() => void> = [];
|
|
43
|
+
let activeCount = 0;
|
|
44
|
+
|
|
45
|
+
const drain = () => {
|
|
46
|
+
if (activeCount >= workerLimit) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const next = queue.shift();
|
|
51
|
+
if (next) {
|
|
52
|
+
next();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return async <T>(task: () => Promise<T>): Promise<T> => {
|
|
57
|
+
await new Promise<void>((resolve) => {
|
|
58
|
+
const start = () => {
|
|
59
|
+
activeCount += 1;
|
|
60
|
+
resolve();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (activeCount < workerLimit) {
|
|
64
|
+
start();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
queue.push(start);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
return await task();
|
|
73
|
+
} finally {
|
|
74
|
+
activeCount -= 1;
|
|
75
|
+
drain();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
};
|