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,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmod, readdir, rename, rm, stat } from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import {
|
|
4
5
|
type AntigravityArtifact,
|
|
@@ -11,7 +12,18 @@ import {
|
|
|
11
12
|
resolveAntigravityRoots,
|
|
12
13
|
} from './antigravity-exporter-types';
|
|
13
14
|
import { decryptAntigravitySafeStoragePayload } from './antigravity-keychain';
|
|
15
|
+
import { resolveAntigravityProjectNames } from './antigravity-projects';
|
|
16
|
+
import { getAntigravityAssistantPhase, getFinalAntigravityAssistantSequences } from './antigravity-transcript-phase';
|
|
14
17
|
import { mapWithConcurrency } from './concurrency';
|
|
18
|
+
import {
|
|
19
|
+
type ExportFormat,
|
|
20
|
+
formatInlineLiteral,
|
|
21
|
+
type MetadataEntry,
|
|
22
|
+
renderCodeBlock,
|
|
23
|
+
renderDocumentTitle,
|
|
24
|
+
renderMetadataBlock,
|
|
25
|
+
renderSection,
|
|
26
|
+
} from './shared';
|
|
15
27
|
|
|
16
28
|
type ProtoField = {
|
|
17
29
|
bytes?: Uint8Array;
|
|
@@ -25,6 +37,7 @@ type SummaryEntry = {
|
|
|
25
37
|
createdAtMs: number | null;
|
|
26
38
|
indexedItemCount: number | null;
|
|
27
39
|
lastUpdatedAtMs: number | null;
|
|
40
|
+
projectId: string | null;
|
|
28
41
|
summaryPath: string;
|
|
29
42
|
title: string;
|
|
30
43
|
workspaceFolder: string | null;
|
|
@@ -33,6 +46,11 @@ type SummaryEntry = {
|
|
|
33
46
|
workspaceUri: string | null;
|
|
34
47
|
};
|
|
35
48
|
|
|
49
|
+
export type DeleteAntigravityConversationResult = {
|
|
50
|
+
deletedConversationIds: string[];
|
|
51
|
+
deletedPaths: string[];
|
|
52
|
+
};
|
|
53
|
+
|
|
36
54
|
type ConversationFile = {
|
|
37
55
|
bytes: number;
|
|
38
56
|
mtimeMs: number;
|
|
@@ -48,7 +66,6 @@ const isSafeConversationId = (value: string) => SAFE_CONVERSATION_ID_PATTERN.tes
|
|
|
48
66
|
type TranscriptFile = {
|
|
49
67
|
bytes: number;
|
|
50
68
|
entryCount: number;
|
|
51
|
-
fullPath: string | null;
|
|
52
69
|
mtimeMs: number;
|
|
53
70
|
model: string | null;
|
|
54
71
|
path: string;
|
|
@@ -60,11 +77,19 @@ export type AntigravityConversationMessage = {
|
|
|
60
77
|
createdAtMs: number | null;
|
|
61
78
|
metadata: Record<string, unknown>;
|
|
62
79
|
order: number;
|
|
63
|
-
phase: 'final_answer' | 'reasoning' | 'tool_call' | 'tool_output' | 'unknown';
|
|
80
|
+
phase: 'commentary' | 'final_answer' | 'reasoning' | 'tool_call' | 'tool_output' | 'unknown';
|
|
64
81
|
role: 'assistant' | 'system' | 'tool' | 'unknown' | 'user';
|
|
65
82
|
text: string;
|
|
66
83
|
};
|
|
67
84
|
|
|
85
|
+
export type AntigravityConversationRenderOptions = {
|
|
86
|
+
includeCommentary?: boolean;
|
|
87
|
+
includeMetadata?: boolean;
|
|
88
|
+
includeTools?: boolean;
|
|
89
|
+
keychainSecret?: string | null;
|
|
90
|
+
outputFormat?: ExportFormat;
|
|
91
|
+
};
|
|
92
|
+
|
|
68
93
|
type WorkspaceInfo = Pick<SummaryEntry, 'workspaceFolder' | 'workspaceKey' | 'workspaceLabel' | 'workspaceUri'>;
|
|
69
94
|
|
|
70
95
|
const UNKNOWN_WORKSPACE: WorkspaceInfo = {
|
|
@@ -271,8 +296,8 @@ const parseContextWorkspaceInfo = (field: ProtoField | null): WorkspaceInfo | nu
|
|
|
271
296
|
// Antigravity summary parsing is reverse-engineered from agyhub_summaries_proto.pb:
|
|
272
297
|
// entry field 1 = conversation id, entry field 2 = summary message. Inside that summary,
|
|
273
298
|
// field 1 = title, 2 = indexed item count, 3 = last-updated timestamp, 7 = created timestamp,
|
|
274
|
-
// 9 = workspace info, and 17 = context
|
|
275
|
-
//
|
|
299
|
+
// 9 = workspace info, and 17 = context metadata. Context field 18 is the Antigravity
|
|
300
|
+
// project id; workspace parsing uses context field 7 or nested workspace field 1.
|
|
276
301
|
const parseSummaryEntry = (entryField: ProtoField, summaryPath: string): SummaryEntry | null => {
|
|
277
302
|
try {
|
|
278
303
|
const entryFields = nestedFields(entryField);
|
|
@@ -283,6 +308,7 @@ const parseSummaryEntry = (entryField: ProtoField, summaryPath: string): Summary
|
|
|
283
308
|
}
|
|
284
309
|
|
|
285
310
|
const summaryFields = nestedFields(summaryBytes);
|
|
311
|
+
const contextFields = nestedFields(firstField(summaryFields, 17));
|
|
286
312
|
const workspace =
|
|
287
313
|
parseWorkspaceInfo(firstField(summaryFields, 9)) ??
|
|
288
314
|
parseContextWorkspaceInfo(firstField(summaryFields, 17)) ??
|
|
@@ -294,6 +320,7 @@ const parseSummaryEntry = (entryField: ProtoField, summaryPath: string): Summary
|
|
|
294
320
|
createdAtMs: parseTimestampMs(firstField(summaryFields, 7)),
|
|
295
321
|
indexedItemCount: fieldNumberValue(summaryFields, 2),
|
|
296
322
|
lastUpdatedAtMs: parseTimestampMs(firstField(summaryFields, 3)),
|
|
323
|
+
projectId: fieldString(contextFields, 18),
|
|
297
324
|
summaryPath,
|
|
298
325
|
title: cleanTitle(fieldString(summaryFields, 1), conversationId),
|
|
299
326
|
};
|
|
@@ -318,6 +345,92 @@ export const readAntigravitySummaryIndex = async (summaryPath: string): Promise<
|
|
|
318
345
|
}
|
|
319
346
|
};
|
|
320
347
|
|
|
348
|
+
const getFieldBounds = (
|
|
349
|
+
buffer: Uint8Array,
|
|
350
|
+
start: number,
|
|
351
|
+
): {
|
|
352
|
+
end: number;
|
|
353
|
+
fieldNumber: number;
|
|
354
|
+
payloadEnd: number;
|
|
355
|
+
payloadStart: number;
|
|
356
|
+
wireType: number;
|
|
357
|
+
} => {
|
|
358
|
+
const key = readVarint(buffer, start, buffer.length);
|
|
359
|
+
const fieldNumber = key.value >> 3;
|
|
360
|
+
const wireType = key.value & 7;
|
|
361
|
+
let index = key.next;
|
|
362
|
+
|
|
363
|
+
if (wireType === 0) {
|
|
364
|
+
const value = readVarint(buffer, index, buffer.length);
|
|
365
|
+
return { end: value.next, fieldNumber, payloadEnd: value.next, payloadStart: index, wireType };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (wireType === 1) {
|
|
369
|
+
return { end: index + 8, fieldNumber, payloadEnd: index + 8, payloadStart: index, wireType };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (wireType === 2) {
|
|
373
|
+
const length = readVarint(buffer, index, buffer.length);
|
|
374
|
+
index = length.next;
|
|
375
|
+
return {
|
|
376
|
+
end: index + length.value,
|
|
377
|
+
fieldNumber,
|
|
378
|
+
payloadEnd: index + length.value,
|
|
379
|
+
payloadStart: index,
|
|
380
|
+
wireType,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (wireType === 5) {
|
|
385
|
+
return { end: index + 4, fieldNumber, payloadEnd: index + 4, payloadStart: index, wireType };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
throw new Error(`Unsupported protobuf wire type: ${wireType}`);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const removeConversationFromSummaryIndex = async (summaryPath: string, conversationId: string): Promise<boolean> => {
|
|
392
|
+
if (!(await pathExists(summaryPath))) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const buffer = new Uint8Array(await Bun.file(summaryPath).arrayBuffer());
|
|
397
|
+
const retained: Uint8Array[] = [];
|
|
398
|
+
let removed = false;
|
|
399
|
+
let index = 0;
|
|
400
|
+
|
|
401
|
+
while (index < buffer.length) {
|
|
402
|
+
const bounds = getFieldBounds(buffer, index);
|
|
403
|
+
const fieldBytes = buffer.slice(index, bounds.end);
|
|
404
|
+
const shouldRemove =
|
|
405
|
+
bounds.fieldNumber === 1 &&
|
|
406
|
+
bounds.wireType === 2 &&
|
|
407
|
+
fieldString(parseProtoFields(buffer, bounds.payloadStart, bounds.payloadEnd), 1) === conversationId;
|
|
408
|
+
|
|
409
|
+
if (shouldRemove) {
|
|
410
|
+
removed = true;
|
|
411
|
+
} else {
|
|
412
|
+
retained.push(fieldBytes);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
index = bounds.end;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!removed) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const tempPath = `${summaryPath}.${randomUUID()}.tmp`;
|
|
423
|
+
const mode = (await stat(summaryPath)).mode & 0o777;
|
|
424
|
+
try {
|
|
425
|
+
await Bun.write(tempPath, Buffer.concat(retained));
|
|
426
|
+
await chmod(tempPath, mode);
|
|
427
|
+
await rename(tempPath, summaryPath);
|
|
428
|
+
} finally {
|
|
429
|
+
await rm(tempPath, { force: true });
|
|
430
|
+
}
|
|
431
|
+
return true;
|
|
432
|
+
};
|
|
433
|
+
|
|
321
434
|
const preferConversationFile = (
|
|
322
435
|
current: ConversationFile | undefined,
|
|
323
436
|
candidate: ConversationFile,
|
|
@@ -491,15 +604,6 @@ const mergeArtifactMaps = async (roots: string[]): Promise<Map<string, Antigravi
|
|
|
491
604
|
return merged;
|
|
492
605
|
};
|
|
493
606
|
|
|
494
|
-
const countJsonlEntries = async (filePath: string): Promise<number> => {
|
|
495
|
-
try {
|
|
496
|
-
const text = await Bun.file(filePath).text();
|
|
497
|
-
return text.split(/\r?\n/u).filter((line) => line.trim().length > 0).length;
|
|
498
|
-
} catch {
|
|
499
|
-
return 0;
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
|
|
503
607
|
const stripModelQualifier = (value: string): string => value.replace(/\s*\([^)]*\)\s*$/u, '').trim();
|
|
504
608
|
|
|
505
609
|
const extractModelSelection = (content: string): string | null => {
|
|
@@ -524,18 +628,31 @@ const extractModelSelection = (content: string): string | null => {
|
|
|
524
628
|
return rawModel ? stripModelQualifier(rawModel) : null;
|
|
525
629
|
};
|
|
526
630
|
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
631
|
+
const transcriptAnalysisCache = new Map<string, { entryCount: number; model: string | null }>();
|
|
632
|
+
|
|
633
|
+
const analyzeTranscriptFile = async (filePath: string, size: number, mtimeMs: number) => {
|
|
634
|
+
const cacheKey = `${filePath}:${size}:${mtimeMs}`;
|
|
635
|
+
const cached = transcriptAnalysisCache.get(cacheKey);
|
|
636
|
+
if (cached) {
|
|
637
|
+
return cached;
|
|
638
|
+
}
|
|
639
|
+
const text = await Bun.file(filePath).text();
|
|
640
|
+
let model: string | null = null;
|
|
531
641
|
for (const entry of parseLogEntries(text)) {
|
|
532
|
-
|
|
533
|
-
if (model) {
|
|
534
|
-
|
|
642
|
+
model = extractModelSelection(getString(entry.content) ?? '') ?? model;
|
|
643
|
+
if (model !== null) {
|
|
644
|
+
break;
|
|
535
645
|
}
|
|
536
646
|
}
|
|
537
|
-
|
|
538
|
-
|
|
647
|
+
const analysis = {
|
|
648
|
+
entryCount: text.split(/\r?\n/u).filter((line) => line.trim().length > 0).length,
|
|
649
|
+
model,
|
|
650
|
+
};
|
|
651
|
+
if (transcriptAnalysisCache.size >= 256) {
|
|
652
|
+
transcriptAnalysisCache.delete(transcriptAnalysisCache.keys().next().value ?? '');
|
|
653
|
+
}
|
|
654
|
+
transcriptAnalysisCache.set(cacheKey, analysis);
|
|
655
|
+
return analysis;
|
|
539
656
|
};
|
|
540
657
|
|
|
541
658
|
const preferTranscriptFile = (current: TranscriptFile | undefined, candidate: TranscriptFile): TranscriptFile => {
|
|
@@ -543,15 +660,22 @@ const preferTranscriptFile = (current: TranscriptFile | undefined, candidate: Tr
|
|
|
543
660
|
return candidate;
|
|
544
661
|
}
|
|
545
662
|
|
|
663
|
+
const sourceRank = (source: TranscriptFile['source']) => (source === 'transcript' ? 2 : 1);
|
|
664
|
+
const candidateRank = sourceRank(candidate.source);
|
|
665
|
+
const currentRank = sourceRank(current.source);
|
|
666
|
+
if (candidateRank !== currentRank) {
|
|
667
|
+
return candidateRank > currentRank ? candidate : current;
|
|
668
|
+
}
|
|
669
|
+
|
|
546
670
|
if (candidate.mtimeMs !== current.mtimeMs) {
|
|
547
671
|
return candidate.mtimeMs > current.mtimeMs ? candidate : current;
|
|
548
672
|
}
|
|
549
673
|
|
|
550
|
-
if (candidate.
|
|
551
|
-
return candidate.
|
|
674
|
+
if (candidate.entryCount !== current.entryCount) {
|
|
675
|
+
return candidate.entryCount > current.entryCount ? candidate : current;
|
|
552
676
|
}
|
|
553
677
|
|
|
554
|
-
return candidate.
|
|
678
|
+
return candidate.bytes > current.bytes ? candidate : current;
|
|
555
679
|
};
|
|
556
680
|
|
|
557
681
|
const readTranscriptFileCandidate = async (
|
|
@@ -565,15 +689,12 @@ const readTranscriptFileCandidate = async (
|
|
|
565
689
|
if (!info.isFile()) {
|
|
566
690
|
return null;
|
|
567
691
|
}
|
|
692
|
+
const analysis = await analyzeTranscriptFile(transcriptPath, info.size, info.mtimeMs);
|
|
568
693
|
|
|
569
|
-
const fullPath = path.join(logsDir, 'transcript_full.jsonl');
|
|
570
|
-
const hasFullTranscript = await pathExists(fullPath);
|
|
571
|
-
const modelSourcePath = hasFullTranscript ? fullPath : transcriptPath;
|
|
572
694
|
return {
|
|
573
695
|
bytes: info.size,
|
|
574
|
-
entryCount:
|
|
575
|
-
|
|
576
|
-
model: await extractTranscriptModel(modelSourcePath),
|
|
696
|
+
entryCount: analysis.entryCount,
|
|
697
|
+
model: analysis.model,
|
|
577
698
|
mtimeMs: info.mtimeMs,
|
|
578
699
|
path: transcriptPath,
|
|
579
700
|
root,
|
|
@@ -601,8 +722,9 @@ const readTranscriptFilesForRoot = async (root: string): Promise<Map<string, Tra
|
|
|
601
722
|
|
|
602
723
|
const logsDir = path.join(brainDir, entry.name, '.system_generated', 'logs');
|
|
603
724
|
for (const candidate of [
|
|
604
|
-
{ name: '
|
|
725
|
+
{ name: 'transcript_full.jsonl', source: 'transcript' as const },
|
|
605
726
|
{ name: 'transcript.jsonl', source: 'transcript' as const },
|
|
727
|
+
{ name: 'overview.txt', source: 'overview' as const },
|
|
606
728
|
]) {
|
|
607
729
|
const transcript = await readTranscriptFileCandidate(root, logsDir, candidate);
|
|
608
730
|
if (transcript) {
|
|
@@ -689,6 +811,8 @@ const toConversation = (
|
|
|
689
811
|
): AntigravityConversation => {
|
|
690
812
|
const fallbackTitle = artifacts[0]?.summary ?? conversationId;
|
|
691
813
|
const artifactBytes = artifacts.reduce((total, artifact) => total + artifact.bytes, 0);
|
|
814
|
+
const conversationBytes = file?.bytes ?? 0;
|
|
815
|
+
const transcriptBytes = transcript?.bytes ?? 0;
|
|
692
816
|
const workspace = resolveConversationWorkspace(summary, file, transcript, artifacts);
|
|
693
817
|
const lastUpdatedAtMs = resolveConversationLastUpdatedAt(artifacts, file, summary, transcript);
|
|
694
818
|
const sourceRoot = resolveConversationSourceRoot(file, transcript, artifacts);
|
|
@@ -697,7 +821,7 @@ const toConversation = (
|
|
|
697
821
|
artifactBytes,
|
|
698
822
|
artifactCount: artifacts.length,
|
|
699
823
|
artifacts,
|
|
700
|
-
conversationBytes
|
|
824
|
+
conversationBytes,
|
|
701
825
|
conversationId,
|
|
702
826
|
conversationMtimeMs: file?.mtimeMs ?? null,
|
|
703
827
|
conversationPath: file?.path ?? null,
|
|
@@ -705,10 +829,12 @@ const toConversation = (
|
|
|
705
829
|
indexedItemCount: summary?.indexedItemCount ?? null,
|
|
706
830
|
lastUpdatedAtMs,
|
|
707
831
|
model: transcript?.model ?? null,
|
|
832
|
+
projectId: summary?.projectId ?? null,
|
|
708
833
|
sourceRoot,
|
|
709
834
|
summaryPath: summary?.summaryPath ?? null,
|
|
710
835
|
title: summary?.title ?? cleanTitle(fallbackTitle, conversationId),
|
|
711
|
-
|
|
836
|
+
totalBytes: conversationBytes + transcriptBytes + artifactBytes,
|
|
837
|
+
transcriptBytes,
|
|
712
838
|
transcriptEntryCount: transcript?.entryCount ?? 0,
|
|
713
839
|
transcriptPath: transcript?.path ?? null,
|
|
714
840
|
transcriptSource: resolveConversationTranscriptSource(file, transcript),
|
|
@@ -750,25 +876,30 @@ export const listAntigravityConversations = async (
|
|
|
750
876
|
|
|
751
877
|
export const groupAntigravityConversations = (
|
|
752
878
|
conversations: AntigravityConversation[],
|
|
879
|
+
projectNames: ReadonlyMap<string, string> = new Map(),
|
|
753
880
|
): AntigravityWorkspaceGroup[] => {
|
|
754
881
|
const groups = new Map<string, AntigravityWorkspaceGroup>();
|
|
755
882
|
for (const conversation of conversations) {
|
|
756
|
-
const
|
|
883
|
+
const projectName = conversation.projectId ? projectNames.get(conversation.projectId) : null;
|
|
884
|
+
const groupKey = conversation.projectId ? `project:${conversation.projectId}` : conversation.workspaceKey;
|
|
885
|
+
const current = groups.get(groupKey) ?? {
|
|
757
886
|
artifactCount: 0,
|
|
758
887
|
conversationBytes: 0,
|
|
759
888
|
conversationCount: 0,
|
|
760
|
-
key:
|
|
761
|
-
label: conversation.workspaceLabel,
|
|
889
|
+
key: groupKey,
|
|
890
|
+
label: projectName ?? conversation.workspaceLabel,
|
|
762
891
|
lastActiveMs: 0,
|
|
892
|
+
totalBytes: 0,
|
|
763
893
|
transcriptCount: 0,
|
|
764
|
-
uri: conversation.workspaceUri,
|
|
894
|
+
uri: conversation.projectId ? null : conversation.workspaceUri,
|
|
765
895
|
};
|
|
766
896
|
current.artifactCount += conversation.artifactCount;
|
|
767
897
|
current.conversationBytes += conversation.conversationBytes;
|
|
768
898
|
current.conversationCount += 1;
|
|
769
899
|
current.lastActiveMs = Math.max(current.lastActiveMs, conversation.lastUpdatedAtMs ?? 0);
|
|
770
900
|
current.transcriptCount += conversation.transcriptEntryCount > 0 ? 1 : 0;
|
|
771
|
-
|
|
901
|
+
current.totalBytes += conversation.totalBytes;
|
|
902
|
+
groups.set(groupKey, current);
|
|
772
903
|
}
|
|
773
904
|
|
|
774
905
|
return [...groups.values()].sort((a, b) => b.lastActiveMs - a.lastActiveMs || a.label.localeCompare(b.label));
|
|
@@ -777,16 +908,67 @@ export const groupAntigravityConversations = (
|
|
|
777
908
|
export const listAntigravityWorkspaceGroups = async (
|
|
778
909
|
roots = resolveAntigravityRoots(),
|
|
779
910
|
): Promise<AntigravityWorkspaceGroup[]> => {
|
|
780
|
-
|
|
911
|
+
const conversations = await listAntigravityConversations(roots);
|
|
912
|
+
const projectNames = await resolveAntigravityProjectNames(
|
|
913
|
+
conversations.flatMap((conversation) => (conversation.projectId ? [conversation.projectId] : [])),
|
|
914
|
+
);
|
|
915
|
+
return groupAntigravityConversations(conversations, projectNames);
|
|
781
916
|
};
|
|
782
917
|
|
|
783
918
|
export const listAntigravityConversationsForGroup = async (
|
|
784
919
|
workspaceKey: string,
|
|
785
920
|
roots = resolveAntigravityRoots(),
|
|
786
921
|
): Promise<AntigravityConversation[]> => {
|
|
787
|
-
return (await listAntigravityConversations(roots)).filter(
|
|
788
|
-
|
|
789
|
-
|
|
922
|
+
return (await listAntigravityConversations(roots)).filter((conversation) => {
|
|
923
|
+
const conversationGroupKey = conversation.projectId
|
|
924
|
+
? `project:${conversation.projectId}`
|
|
925
|
+
: conversation.workspaceKey;
|
|
926
|
+
return conversationGroupKey === workspaceKey;
|
|
927
|
+
});
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
const existingAntigravityDeletePaths = async (root: string, conversationId: string): Promise<string[]> => {
|
|
931
|
+
const conversationPath = path.join(getAntigravityConversationDir(root), `${conversationId}.pb`);
|
|
932
|
+
const artifactDir = path.join(getAntigravityBrainDir(root), conversationId);
|
|
933
|
+
const logsDir = path.join(artifactDir, '.system_generated', 'logs');
|
|
934
|
+
const candidates = [
|
|
935
|
+
conversationPath,
|
|
936
|
+
path.join(logsDir, 'overview.txt'),
|
|
937
|
+
path.join(logsDir, 'transcript.jsonl'),
|
|
938
|
+
path.join(logsDir, 'transcript_full.jsonl'),
|
|
939
|
+
artifactDir,
|
|
940
|
+
];
|
|
941
|
+
const exists = await Promise.all(candidates.map(pathExists));
|
|
942
|
+
return candidates.filter((_, index) => exists[index]);
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
export const deleteAntigravityConversation = async (
|
|
946
|
+
roots: string[],
|
|
947
|
+
conversationId: string,
|
|
948
|
+
): Promise<DeleteAntigravityConversationResult> => {
|
|
949
|
+
if (!isSafeConversationId(conversationId)) {
|
|
950
|
+
return { deletedConversationIds: [], deletedPaths: [] };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const deletedPaths: string[] = [];
|
|
954
|
+
let deletedSummary = false;
|
|
955
|
+
|
|
956
|
+
for (const root of roots) {
|
|
957
|
+
deletedSummary =
|
|
958
|
+
(await removeConversationFromSummaryIndex(getAntigravitySummaryIndexPath(root), conversationId)) ||
|
|
959
|
+
deletedSummary;
|
|
960
|
+
|
|
961
|
+
const rootPaths = await existingAntigravityDeletePaths(root, conversationId);
|
|
962
|
+
deletedPaths.push(...rootPaths);
|
|
963
|
+
|
|
964
|
+
await rm(path.join(getAntigravityConversationDir(root), `${conversationId}.pb`), { force: true });
|
|
965
|
+
await rm(path.join(getAntigravityBrainDir(root), conversationId), { force: true, recursive: true });
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return {
|
|
969
|
+
deletedConversationIds: deletedSummary || deletedPaths.length > 0 ? [conversationId] : [],
|
|
970
|
+
deletedPaths: [...new Set(deletedPaths)],
|
|
971
|
+
};
|
|
790
972
|
};
|
|
791
973
|
|
|
792
974
|
export const renderAntigravityArtifactsMarkdown = async (
|
|
@@ -893,48 +1075,99 @@ const logEntryHeading = (entry: AntigravityLogEntry): string => {
|
|
|
893
1075
|
return type ? `Tool: ${type}` : 'Event';
|
|
894
1076
|
};
|
|
895
1077
|
|
|
896
|
-
|
|
1078
|
+
type ResolvedAntigravityConversationRenderOptions = {
|
|
1079
|
+
includeCommentary: boolean;
|
|
1080
|
+
includeMetadata: boolean;
|
|
1081
|
+
includeTools: boolean;
|
|
1082
|
+
keychainSecret: string | null;
|
|
1083
|
+
outputFormat: ExportFormat;
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
const resolveAntigravityRenderOptions = (
|
|
1087
|
+
options: AntigravityConversationRenderOptions,
|
|
1088
|
+
): ResolvedAntigravityConversationRenderOptions => ({
|
|
1089
|
+
includeCommentary: options.includeCommentary ?? true,
|
|
1090
|
+
includeMetadata: options.includeMetadata ?? true,
|
|
1091
|
+
includeTools: options.includeTools ?? true,
|
|
1092
|
+
keychainSecret: options.keychainSecret ?? null,
|
|
1093
|
+
outputFormat: options.outputFormat ?? 'md',
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
const renderNestedHeading = (title: string, outputFormat: ExportFormat) => {
|
|
1097
|
+
return outputFormat === 'md' ? `### ${title}` : `${title}\n${'~'.repeat(Math.max(title.length, 3))}`;
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
const renderToolCalls = (toolCalls: unknown, outputFormat: ExportFormat): string => {
|
|
897
1101
|
if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
|
|
898
|
-
return
|
|
1102
|
+
return '';
|
|
899
1103
|
}
|
|
900
1104
|
|
|
901
|
-
const parts = [
|
|
1105
|
+
const parts: string[] = [];
|
|
902
1106
|
for (const call of toolCalls) {
|
|
903
1107
|
if (!call || typeof call !== 'object') {
|
|
904
1108
|
continue;
|
|
905
1109
|
}
|
|
906
1110
|
|
|
907
1111
|
const { args, name } = call as { args?: unknown; name?: unknown };
|
|
908
|
-
parts.push(`-
|
|
1112
|
+
parts.push(`- ${formatInlineLiteral(typeof name === 'string' ? name : 'unknown', outputFormat)}`);
|
|
909
1113
|
if (args !== undefined) {
|
|
910
|
-
parts.push('', '
|
|
1114
|
+
parts.push('', 'Input:', '', renderCodeBlock(JSON.stringify(args, null, 2), outputFormat), '');
|
|
911
1115
|
}
|
|
912
1116
|
}
|
|
913
1117
|
|
|
914
|
-
return parts;
|
|
1118
|
+
return parts.length > 0 ? [renderNestedHeading('Tool Calls', outputFormat), '', ...parts].join('\n').trimEnd() : '';
|
|
915
1119
|
};
|
|
916
1120
|
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
1121
|
+
const renderLogEntryBodyParts = (
|
|
1122
|
+
entry: AntigravityLogEntry,
|
|
1123
|
+
role: AntigravityConversationMessage['role'],
|
|
1124
|
+
sequence: number,
|
|
1125
|
+
finalAssistantSequences: Set<number>,
|
|
1126
|
+
options: ResolvedAntigravityConversationRenderOptions,
|
|
1127
|
+
): string[] => {
|
|
1128
|
+
const bodyParts: string[] = [];
|
|
1129
|
+
const thinking = getString(entry.thinking) ?? '';
|
|
1130
|
+
if (options.includeCommentary && thinking) {
|
|
1131
|
+
bodyParts.push(renderNestedHeading('Thinking', options.outputFormat), '', thinking.trim(), '');
|
|
1132
|
+
}
|
|
1133
|
+
|
|
920
1134
|
const content = cleanLogContent(entry);
|
|
921
|
-
const
|
|
922
|
-
|
|
1135
|
+
const phase = role === 'assistant' ? getAntigravityAssistantPhase(sequence, finalAssistantSequences) : 'unknown';
|
|
1136
|
+
if (content && (role !== 'assistant' || phase !== 'commentary' || options.includeCommentary)) {
|
|
1137
|
+
bodyParts.push(content, '');
|
|
1138
|
+
}
|
|
923
1139
|
|
|
924
|
-
|
|
925
|
-
|
|
1140
|
+
const toolCalls = options.includeTools ? renderToolCalls(entry.tool_calls, options.outputFormat) : '';
|
|
1141
|
+
if (toolCalls) {
|
|
1142
|
+
bodyParts.push(toolCalls);
|
|
926
1143
|
}
|
|
927
1144
|
|
|
928
|
-
|
|
929
|
-
|
|
1145
|
+
return bodyParts;
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
const renderLogEntry = (
|
|
1149
|
+
entry: AntigravityLogEntry,
|
|
1150
|
+
sequence: number,
|
|
1151
|
+
finalAssistantSequences: Set<number>,
|
|
1152
|
+
options: ResolvedAntigravityConversationRenderOptions,
|
|
1153
|
+
): string => {
|
|
1154
|
+
const heading = logEntryHeading(entry);
|
|
1155
|
+
const role = logEntryRole(entry);
|
|
1156
|
+
if (!options.includeTools && (role === 'tool' || heading.startsWith('Tool: '))) {
|
|
1157
|
+
return '';
|
|
930
1158
|
}
|
|
931
1159
|
|
|
932
|
-
|
|
933
|
-
|
|
1160
|
+
const timestamp = getString(entry.created_at);
|
|
1161
|
+
const bodyParts = renderLogEntryBodyParts(entry, role, sequence, finalAssistantSequences, options);
|
|
1162
|
+
|
|
1163
|
+
if (bodyParts.length === 0) {
|
|
1164
|
+
return '';
|
|
934
1165
|
}
|
|
935
1166
|
|
|
936
|
-
parts
|
|
937
|
-
|
|
1167
|
+
const parts = timestamp
|
|
1168
|
+
? [options.outputFormat === 'md' ? `_Timestamp: ${timestamp}_` : `Timestamp: ${timestamp}`, '', ...bodyParts]
|
|
1169
|
+
: bodyParts;
|
|
1170
|
+
return renderSection(heading, parts.join('\n').trimEnd(), options.outputFormat);
|
|
938
1171
|
};
|
|
939
1172
|
|
|
940
1173
|
const logEntryCreatedAtMs = (entry: AntigravityLogEntry): number | null => {
|
|
@@ -972,10 +1205,14 @@ const logEntryRole = (entry: AntigravityLogEntry): AntigravityConversationMessag
|
|
|
972
1205
|
return 'unknown';
|
|
973
1206
|
};
|
|
974
1207
|
|
|
975
|
-
const logEntryPhase = (
|
|
1208
|
+
const logEntryPhase = (
|
|
1209
|
+
entry: AntigravityLogEntry,
|
|
1210
|
+
sequence: number,
|
|
1211
|
+
finalAssistantSequences: Set<number>,
|
|
1212
|
+
): AntigravityConversationMessage['phase'] => {
|
|
976
1213
|
const role = logEntryRole(entry);
|
|
977
1214
|
if (role === 'assistant') {
|
|
978
|
-
return
|
|
1215
|
+
return getAntigravityAssistantPhase(sequence, finalAssistantSequences);
|
|
979
1216
|
}
|
|
980
1217
|
if (role === 'tool') {
|
|
981
1218
|
return 'tool_output';
|
|
@@ -1005,11 +1242,27 @@ const toolCallsText = (toolCalls: unknown): string => {
|
|
|
1005
1242
|
.join('\n');
|
|
1006
1243
|
};
|
|
1007
1244
|
|
|
1008
|
-
const
|
|
1245
|
+
const getAntigravityPhaseItems = (entries: AntigravityLogEntry[]) => {
|
|
1246
|
+
return entries.map((entry, sequence) => {
|
|
1247
|
+
const role = logEntryRole(entry);
|
|
1248
|
+
return {
|
|
1249
|
+
hasContent: Boolean(cleanLogContent(entry)),
|
|
1250
|
+
hasToolCalls: Array.isArray(entry.tool_calls) && entry.tool_calls.length > 0,
|
|
1251
|
+
role: role === 'assistant' || role === 'user' ? role : ('other' as const),
|
|
1252
|
+
sequence,
|
|
1253
|
+
};
|
|
1254
|
+
});
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
const logEntryToMessages = (
|
|
1258
|
+
entry: AntigravityLogEntry,
|
|
1259
|
+
index: number,
|
|
1260
|
+
finalAssistantSequences: Set<number>,
|
|
1261
|
+
): AntigravityConversationMessage[] => {
|
|
1009
1262
|
const order = logEntryOrder(entry, index);
|
|
1010
1263
|
const createdAtMs = logEntryCreatedAtMs(entry);
|
|
1011
1264
|
const role = logEntryRole(entry);
|
|
1012
|
-
const phase = logEntryPhase(entry);
|
|
1265
|
+
const phase = logEntryPhase(entry, index, finalAssistantSequences);
|
|
1013
1266
|
const metadata = logEntryMetadata(entry);
|
|
1014
1267
|
const messages: AntigravityConversationMessage[] = [];
|
|
1015
1268
|
const thinking = getString(entry.thinking)?.trim();
|
|
@@ -1058,15 +1311,39 @@ export const readAntigravityConversationMessages = async (
|
|
|
1058
1311
|
return [];
|
|
1059
1312
|
}
|
|
1060
1313
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1314
|
+
const content = await Bun.file(conversation.transcriptPath).text();
|
|
1315
|
+
const entries = parseLogEntries(content);
|
|
1316
|
+
if (content.trim() && entries.length === 0) {
|
|
1317
|
+
throw new Error(`Antigravity transcript is corrupt: ${conversation.transcriptPath}`);
|
|
1318
|
+
}
|
|
1319
|
+
const finalAssistantSequences = getFinalAntigravityAssistantSequences(getAntigravityPhaseItems(entries));
|
|
1320
|
+
return entries.flatMap((entry, index) => logEntryToMessages(entry, index, finalAssistantSequences));
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
const renderAntigravityMetadata = (entries: MetadataEntry[], outputFormat: ExportFormat): string => {
|
|
1324
|
+
if (outputFormat === 'txt') {
|
|
1325
|
+
return renderMetadataBlock(entries, outputFormat);
|
|
1066
1326
|
}
|
|
1327
|
+
|
|
1328
|
+
return entries
|
|
1329
|
+
.filter((entry) => entry.value !== null && entry.value !== undefined && entry.value !== '')
|
|
1330
|
+
.map((entry) => `- ${entry.key}: ${formatInlineLiteral(String(entry.value), outputFormat)}`)
|
|
1331
|
+
.join('\n');
|
|
1067
1332
|
};
|
|
1068
1333
|
|
|
1069
|
-
const
|
|
1334
|
+
const buildAntigravityIdentityMetadata = (
|
|
1335
|
+
conversation: AntigravityConversation,
|
|
1336
|
+
exportedFrom: string,
|
|
1337
|
+
): MetadataEntry[] => [
|
|
1338
|
+
{ key: 'exported_from', value: exportedFrom },
|
|
1339
|
+
{ key: 'conversation_id', value: conversation.conversationId },
|
|
1340
|
+
{ key: 'workspace', value: conversation.workspaceUri },
|
|
1341
|
+
];
|
|
1342
|
+
|
|
1343
|
+
const renderAntigravityTranscript = async (
|
|
1344
|
+
conversation: AntigravityConversation,
|
|
1345
|
+
options: ResolvedAntigravityConversationRenderOptions,
|
|
1346
|
+
): Promise<string | null> => {
|
|
1070
1347
|
if (!conversation.transcriptPath || !conversation.transcriptSource) {
|
|
1071
1348
|
return null;
|
|
1072
1349
|
}
|
|
@@ -1080,35 +1357,44 @@ const renderAntigravityTranscriptMarkdown = async (conversation: AntigravityConv
|
|
|
1080
1357
|
conversation.transcriptSource === 'overview'
|
|
1081
1358
|
? 'antigravity_overview_transcript'
|
|
1082
1359
|
: 'antigravity_jsonl_transcript';
|
|
1360
|
+
const finalAssistantSequences = getFinalAntigravityAssistantSequences(getAntigravityPhaseItems(entries));
|
|
1361
|
+
const sections = entries
|
|
1362
|
+
.map((entry, sequence) => renderLogEntry(entry, sequence, finalAssistantSequences, options))
|
|
1363
|
+
.filter(Boolean);
|
|
1083
1364
|
const parts = [
|
|
1084
|
-
|
|
1085
|
-
'',
|
|
1086
|
-
`- exported_from: \`${exportedFrom}\``,
|
|
1087
|
-
`- conversation_id: \`${conversation.conversationId}\``,
|
|
1088
|
-
conversation.workspaceUri ? `- workspace: \`${conversation.workspaceUri}\`` : '',
|
|
1365
|
+
renderDocumentTitle(conversation.title, options.outputFormat),
|
|
1089
1366
|
'',
|
|
1367
|
+
options.includeMetadata
|
|
1368
|
+
? renderAntigravityMetadata(
|
|
1369
|
+
buildAntigravityIdentityMetadata(conversation, exportedFrom),
|
|
1370
|
+
options.outputFormat,
|
|
1371
|
+
)
|
|
1372
|
+
: '',
|
|
1373
|
+
...sections,
|
|
1090
1374
|
].filter(Boolean);
|
|
1091
1375
|
|
|
1092
|
-
for (const entry of entries) {
|
|
1093
|
-
parts.push(...renderLogEntry(entry));
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
1376
|
return `${parts.join('\n').trimEnd()}\n`;
|
|
1097
1377
|
};
|
|
1098
1378
|
|
|
1099
|
-
const
|
|
1379
|
+
const renderDecryptedSafeStorage = (
|
|
1380
|
+
conversation: AntigravityConversation,
|
|
1381
|
+
content: string,
|
|
1382
|
+
options: ResolvedAntigravityConversationRenderOptions,
|
|
1383
|
+
): string | null => {
|
|
1100
1384
|
const trimmed = content.trim();
|
|
1101
1385
|
if (!trimmed) {
|
|
1102
1386
|
return null;
|
|
1103
1387
|
}
|
|
1104
1388
|
|
|
1105
1389
|
return [
|
|
1106
|
-
|
|
1107
|
-
'',
|
|
1108
|
-
'- exported_from: `antigravity_safe_storage_payload`',
|
|
1109
|
-
`- conversation_id: \`${conversation.conversationId}\``,
|
|
1110
|
-
conversation.workspaceUri ? `- workspace: \`${conversation.workspaceUri}\`` : '',
|
|
1390
|
+
renderDocumentTitle(conversation.title, options.outputFormat),
|
|
1111
1391
|
'',
|
|
1392
|
+
options.includeMetadata
|
|
1393
|
+
? renderAntigravityMetadata(
|
|
1394
|
+
buildAntigravityIdentityMetadata(conversation, 'antigravity_safe_storage_payload'),
|
|
1395
|
+
options.outputFormat,
|
|
1396
|
+
)
|
|
1397
|
+
: '',
|
|
1112
1398
|
trimmed,
|
|
1113
1399
|
'',
|
|
1114
1400
|
]
|
|
@@ -1116,22 +1402,66 @@ const renderDecryptedSafeStorageMarkdown = (conversation: AntigravityConversatio
|
|
|
1116
1402
|
.join('\n');
|
|
1117
1403
|
};
|
|
1118
1404
|
|
|
1405
|
+
const renderAntigravitySummary = (
|
|
1406
|
+
conversation: AntigravityConversation,
|
|
1407
|
+
options: ResolvedAntigravityConversationRenderOptions,
|
|
1408
|
+
): string | null => {
|
|
1409
|
+
if (conversation.artifactCount > 0) {
|
|
1410
|
+
return null;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
if (!conversation.summaryPath && conversation.indexedItemCount === null) {
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
return [
|
|
1418
|
+
renderDocumentTitle(conversation.title, options.outputFormat),
|
|
1419
|
+
'',
|
|
1420
|
+
options.includeMetadata
|
|
1421
|
+
? renderAntigravityMetadata(
|
|
1422
|
+
[
|
|
1423
|
+
...buildAntigravityIdentityMetadata(conversation, 'antigravity_summary_index'),
|
|
1424
|
+
{ key: 'indexed_items', value: conversation.indexedItemCount },
|
|
1425
|
+
{
|
|
1426
|
+
key: 'created_at',
|
|
1427
|
+
value: conversation.createdAtMs ? new Date(conversation.createdAtMs).toISOString() : null,
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
key: 'updated_at',
|
|
1431
|
+
value: conversation.lastUpdatedAtMs
|
|
1432
|
+
? new Date(conversation.lastUpdatedAtMs).toISOString()
|
|
1433
|
+
: null,
|
|
1434
|
+
},
|
|
1435
|
+
],
|
|
1436
|
+
options.outputFormat,
|
|
1437
|
+
)
|
|
1438
|
+
: '',
|
|
1439
|
+
options.includeMetadata
|
|
1440
|
+
? 'No local transcript log was found for this Antigravity conversation. The export contains the summary index metadata that Antigravity retained.'
|
|
1441
|
+
: 'No local transcript log was found for this Antigravity conversation.',
|
|
1442
|
+
'',
|
|
1443
|
+
]
|
|
1444
|
+
.filter(Boolean)
|
|
1445
|
+
.join('\n');
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1119
1448
|
export const renderAntigravityConversationMarkdown = async (
|
|
1120
1449
|
conversation: AntigravityConversation,
|
|
1121
|
-
options:
|
|
1450
|
+
options: AntigravityConversationRenderOptions = {},
|
|
1122
1451
|
): Promise<string | null> => {
|
|
1123
|
-
const
|
|
1452
|
+
const resolvedOptions = resolveAntigravityRenderOptions(options);
|
|
1453
|
+
const transcript = await renderAntigravityTranscript(conversation, resolvedOptions);
|
|
1124
1454
|
if (transcript) {
|
|
1125
1455
|
return transcript;
|
|
1126
1456
|
}
|
|
1127
1457
|
|
|
1128
|
-
if (
|
|
1458
|
+
if (resolvedOptions.keychainSecret && conversation.conversationPath) {
|
|
1129
1459
|
const encrypted = Buffer.from(await Bun.file(conversation.conversationPath).arrayBuffer());
|
|
1130
|
-
const decrypted = decryptAntigravitySafeStoragePayload(encrypted,
|
|
1460
|
+
const decrypted = decryptAntigravitySafeStoragePayload(encrypted, resolvedOptions.keychainSecret);
|
|
1131
1461
|
if (decrypted) {
|
|
1132
|
-
return
|
|
1462
|
+
return renderDecryptedSafeStorage(conversation, decrypted, resolvedOptions);
|
|
1133
1463
|
}
|
|
1134
1464
|
}
|
|
1135
1465
|
|
|
1136
|
-
return
|
|
1466
|
+
return renderAntigravitySummary(conversation, resolvedOptions);
|
|
1137
1467
|
};
|