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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { ExportFormat, JsonValue } from './shared';
|
|
4
|
+
|
|
5
|
+
export const getDefaultGrokHome = (_env: NodeJS.ProcessEnv = process.env, homeDir = os.homedir()): string => {
|
|
6
|
+
return path.join(homeDir, '.grok');
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_GROK_HOME = getDefaultGrokHome();
|
|
10
|
+
|
|
11
|
+
export const resolveGrokHome = (): string => {
|
|
12
|
+
const configured = process.env.SPIRACHA_GROK_HOME?.trim() || process.env.SPIRACHA_GROK_DIR?.trim();
|
|
13
|
+
return configured ? configured : DEFAULT_GROK_HOME;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const resolveGrokSessionsDir = (): string => {
|
|
17
|
+
const configured = process.env.SPIRACHA_GROK_SESSIONS_DIR?.trim();
|
|
18
|
+
return configured ? configured : path.join(resolveGrokHome(), 'sessions');
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type GrokWorkspaceGroup = {
|
|
22
|
+
assistantMessageCount: number;
|
|
23
|
+
chatMessageCount: number;
|
|
24
|
+
directoryName: string;
|
|
25
|
+
key: string;
|
|
26
|
+
label: string;
|
|
27
|
+
lastActiveAtIso: string | null;
|
|
28
|
+
lastActiveAtMs: number | null;
|
|
29
|
+
messageCount: number;
|
|
30
|
+
reasoningCount: number;
|
|
31
|
+
sessionCount: number;
|
|
32
|
+
toolCallCount: number;
|
|
33
|
+
toolResultCount: number;
|
|
34
|
+
uri: string;
|
|
35
|
+
userMessageCount: number;
|
|
36
|
+
worktree: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type GrokSessionSummary = {
|
|
40
|
+
agentName: string | null;
|
|
41
|
+
assistantMessageCount: number;
|
|
42
|
+
chatHistoryPath: string;
|
|
43
|
+
chatMessageCount: number;
|
|
44
|
+
createdAtIso: string | null;
|
|
45
|
+
createdAtMs: number | null;
|
|
46
|
+
currentModelId: string | null;
|
|
47
|
+
cwd: string | null;
|
|
48
|
+
gitBranch: string | null;
|
|
49
|
+
gitRemotes: string[];
|
|
50
|
+
gitRootDir: string | null;
|
|
51
|
+
headCommit: string | null;
|
|
52
|
+
lastActiveAtIso: string | null;
|
|
53
|
+
lastActiveAtMs: number | null;
|
|
54
|
+
messageCount: number;
|
|
55
|
+
modelLabel: string | null;
|
|
56
|
+
reasoningCount: number;
|
|
57
|
+
renderablePartCount: number;
|
|
58
|
+
sandboxProfile: string | null;
|
|
59
|
+
sessionDir: string;
|
|
60
|
+
sessionId: string;
|
|
61
|
+
summaryPath: string;
|
|
62
|
+
title: string;
|
|
63
|
+
toolCallCount: number;
|
|
64
|
+
toolResultCount: number;
|
|
65
|
+
updatesPath: string | null;
|
|
66
|
+
userMessageCount: number;
|
|
67
|
+
workspaceKey: string;
|
|
68
|
+
workspaceLabel: string;
|
|
69
|
+
worktree: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type GrokPartType = 'reasoning' | 'text' | 'tool_call' | 'tool_result' | 'unknown';
|
|
73
|
+
|
|
74
|
+
export type GrokTranscriptPart = {
|
|
75
|
+
argumentsText?: string | null;
|
|
76
|
+
outputText?: string | null;
|
|
77
|
+
partId: string;
|
|
78
|
+
raw: Record<string, JsonValue>;
|
|
79
|
+
text?: string;
|
|
80
|
+
toolCallId?: string | null;
|
|
81
|
+
toolName?: string | null;
|
|
82
|
+
type: GrokPartType;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type GrokTranscriptEntry = {
|
|
86
|
+
createdAtMs: number | null;
|
|
87
|
+
entryId: string;
|
|
88
|
+
modelFingerprint?: string | null;
|
|
89
|
+
modelId?: string | null;
|
|
90
|
+
parts: GrokTranscriptPart[];
|
|
91
|
+
raw: Record<string, JsonValue>;
|
|
92
|
+
role: string;
|
|
93
|
+
timestamp: string | null;
|
|
94
|
+
type: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type GrokSessionTranscript = {
|
|
98
|
+
entries: GrokTranscriptEntry[];
|
|
99
|
+
rawEvents: Record<string, JsonValue>[];
|
|
100
|
+
rawPayloadsOmitted?: boolean;
|
|
101
|
+
renderablePartCount: number;
|
|
102
|
+
session: GrokSessionSummary;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type GrokExportOptions = {
|
|
106
|
+
includeCommentary: boolean;
|
|
107
|
+
includeMetadata: boolean;
|
|
108
|
+
includeTools: boolean;
|
|
109
|
+
outputFormat: ExportFormat;
|
|
110
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { GrokTranscriptEntry, GrokTranscriptPart } from './grok-exporter-types';
|
|
2
|
+
|
|
3
|
+
export type GrokAssistantMessagePhase = 'commentary' | 'final_answer' | null;
|
|
4
|
+
|
|
5
|
+
const isGrokCommentaryEnvelope = (entry: GrokTranscriptEntry, part: GrokTranscriptPart): boolean => {
|
|
6
|
+
const text = part.text?.trim() ?? '';
|
|
7
|
+
return entry.role === 'system' || text.startsWith('<user_info>') || text.startsWith('<system-reminder>');
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const getFinalGrokAssistantTextPartIds = (entries: GrokTranscriptEntry[]): Set<string> => {
|
|
11
|
+
const finalPartIds = new Set<string>();
|
|
12
|
+
let latestAssistantTextPartId: string | null = null;
|
|
13
|
+
|
|
14
|
+
const flushAssistantRun = () => {
|
|
15
|
+
if (latestAssistantTextPartId) {
|
|
16
|
+
finalPartIds.add(latestAssistantTextPartId);
|
|
17
|
+
latestAssistantTextPartId = null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (entry.role === 'user') {
|
|
23
|
+
flushAssistantRun();
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (entry.role !== 'assistant') {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const part of entry.parts) {
|
|
32
|
+
if (part.type === 'tool_call') {
|
|
33
|
+
latestAssistantTextPartId = null;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (part.type === 'text' && part.text?.trim()) {
|
|
38
|
+
latestAssistantTextPartId = part.partId;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
flushAssistantRun();
|
|
44
|
+
return finalPartIds;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const getGrokTextPartPhase = (
|
|
48
|
+
entry: GrokTranscriptEntry,
|
|
49
|
+
part: GrokTranscriptPart,
|
|
50
|
+
finalTextPartIds: Set<string>,
|
|
51
|
+
): GrokAssistantMessagePhase => {
|
|
52
|
+
if (isGrokCommentaryEnvelope(entry, part)) {
|
|
53
|
+
return 'commentary';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (entry.role !== 'assistant') {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return finalTextPartIds.has(part.partId) ? 'final_answer' : 'commentary';
|
|
61
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GrokExportOptions,
|
|
3
|
+
GrokSessionSummary,
|
|
4
|
+
GrokSessionTranscript,
|
|
5
|
+
GrokTranscriptEntry,
|
|
6
|
+
GrokTranscriptPart,
|
|
7
|
+
} from './grok-exporter-types';
|
|
8
|
+
import { getFinalGrokAssistantTextPartIds, getGrokTextPartPhase } from './grok-transcript-phase';
|
|
9
|
+
import {
|
|
10
|
+
cleanExtractedText,
|
|
11
|
+
cleanInlineTitle,
|
|
12
|
+
formatInlineLiteral,
|
|
13
|
+
type MetadataEntry,
|
|
14
|
+
renderCodeBlock,
|
|
15
|
+
renderDocumentTitle,
|
|
16
|
+
renderMetadataBlock,
|
|
17
|
+
renderSection,
|
|
18
|
+
} from './shared';
|
|
19
|
+
|
|
20
|
+
const TOOL_OUTPUT_PREVIEW_LIMIT = 4000;
|
|
21
|
+
|
|
22
|
+
const getSessionTitle = (session: GrokSessionSummary): string => {
|
|
23
|
+
return cleanInlineTitle(session.title || session.sessionId);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const buildMetadataEntries = (session: GrokSessionSummary): MetadataEntry[] => [
|
|
27
|
+
{ key: 'exported_from', value: 'grok_local_session' },
|
|
28
|
+
{ key: 'session_id', value: session.sessionId },
|
|
29
|
+
{ key: 'title', value: session.title },
|
|
30
|
+
{ key: 'source_transcript_path', value: session.chatHistoryPath },
|
|
31
|
+
{ key: 'workspace_key', value: session.workspaceKey },
|
|
32
|
+
{ key: 'worktree', value: session.worktree },
|
|
33
|
+
{ key: 'cwd', value: session.cwd },
|
|
34
|
+
{ key: 'model', value: session.currentModelId },
|
|
35
|
+
{ key: 'model_label', value: session.modelLabel },
|
|
36
|
+
{ key: 'agent_name', value: session.agentName },
|
|
37
|
+
{ key: 'git_branch', value: session.gitBranch },
|
|
38
|
+
{ key: 'created_at_iso', value: session.createdAtIso },
|
|
39
|
+
{ key: 'last_active_at_iso', value: session.lastActiveAtIso },
|
|
40
|
+
{ key: 'message_count', value: session.messageCount },
|
|
41
|
+
{ key: 'tool_call_count', value: session.toolCallCount },
|
|
42
|
+
{ key: 'tool_result_count', value: session.toolResultCount },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const roleTitle = (role: string): string => {
|
|
46
|
+
if (role === 'assistant') {
|
|
47
|
+
return 'Assistant';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (role === 'user') {
|
|
51
|
+
return 'User';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (role === 'system') {
|
|
55
|
+
return 'System';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (role === 'tool') {
|
|
59
|
+
return 'Tool';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return role ? cleanInlineTitle(role) : 'Message';
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const truncateOutput = (text: string): string => {
|
|
66
|
+
if (text.length <= TOOL_OUTPUT_PREVIEW_LIMIT) {
|
|
67
|
+
return text;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return `${text.slice(0, TOOL_OUTPUT_PREVIEW_LIMIT)}\n... (truncated)`;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const renderTextPart = (entry: GrokTranscriptEntry, part: GrokTranscriptPart, options: GrokExportOptions): string => {
|
|
74
|
+
const text = cleanExtractedText(part.text ?? '').trim();
|
|
75
|
+
return text ? renderSection(roleTitle(entry.role), text, options.outputFormat) : '';
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const renderReasoningPart = (part: GrokTranscriptPart, options: GrokExportOptions): string => {
|
|
79
|
+
if (!options.includeCommentary) {
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const text = cleanExtractedText(part.text ?? '').trim();
|
|
84
|
+
return text ? renderSection('Reasoning', text, options.outputFormat) : '';
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const renderToolCallPart = (part: GrokTranscriptPart, options: GrokExportOptions): string => {
|
|
88
|
+
if (!options.includeTools) {
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const toolName = part.toolName ?? 'unknown';
|
|
93
|
+
const lines = [`Tool: ${formatInlineLiteral(toolName, options.outputFormat)}`];
|
|
94
|
+
if (part.toolCallId) {
|
|
95
|
+
lines.push(`Call ID: ${part.toolCallId}`);
|
|
96
|
+
}
|
|
97
|
+
if (part.argumentsText?.trim()) {
|
|
98
|
+
lines.push('', 'Input:', '', renderCodeBlock(part.argumentsText.trim(), options.outputFormat));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return renderSection('Tool Call', lines.join('\n'), options.outputFormat);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const renderToolResultPart = (part: GrokTranscriptPart, options: GrokExportOptions): string => {
|
|
105
|
+
if (!options.includeTools) {
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const outputText = part.outputText?.trim();
|
|
110
|
+
if (!outputText) {
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const lines: string[] = [];
|
|
115
|
+
if (part.toolCallId) {
|
|
116
|
+
lines.push(`Call ID: ${part.toolCallId}`, '');
|
|
117
|
+
}
|
|
118
|
+
lines.push(renderCodeBlock(truncateOutput(outputText), options.outputFormat));
|
|
119
|
+
return renderSection('Tool Output', lines.join('\n'), options.outputFormat);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const renderPart = (
|
|
123
|
+
entry: GrokTranscriptEntry,
|
|
124
|
+
part: GrokTranscriptPart,
|
|
125
|
+
options: GrokExportOptions,
|
|
126
|
+
finalAssistantTextPartIds: Set<string>,
|
|
127
|
+
): string => {
|
|
128
|
+
switch (part.type) {
|
|
129
|
+
case 'text':
|
|
130
|
+
if (
|
|
131
|
+
getGrokTextPartPhase(entry, part, finalAssistantTextPartIds) === 'commentary' &&
|
|
132
|
+
!options.includeCommentary
|
|
133
|
+
) {
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
return renderTextPart(entry, part, options);
|
|
137
|
+
case 'reasoning':
|
|
138
|
+
return renderReasoningPart(part, options);
|
|
139
|
+
case 'tool_call':
|
|
140
|
+
return renderToolCallPart(part, options);
|
|
141
|
+
case 'tool_result':
|
|
142
|
+
return renderToolResultPart(part, options);
|
|
143
|
+
case 'unknown':
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const renderGrokTranscript = (transcript: GrokSessionTranscript, options: GrokExportOptions): string | null => {
|
|
149
|
+
const finalAssistantTextPartIds = getFinalGrokAssistantTextPartIds(transcript.entries);
|
|
150
|
+
const sections = transcript.entries.flatMap((entry) =>
|
|
151
|
+
entry.parts.map((part) => renderPart(entry, part, options, finalAssistantTextPartIds)).filter(Boolean),
|
|
152
|
+
);
|
|
153
|
+
if (sections.length === 0) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const parts = [
|
|
158
|
+
renderDocumentTitle(getSessionTitle(transcript.session), options.outputFormat),
|
|
159
|
+
'',
|
|
160
|
+
options.includeMetadata
|
|
161
|
+
? renderMetadataBlock(buildMetadataEntries(transcript.session), options.outputFormat)
|
|
162
|
+
: '',
|
|
163
|
+
...sections,
|
|
164
|
+
].filter(Boolean);
|
|
165
|
+
|
|
166
|
+
return `${parts.join('\n').trimEnd()}\n`;
|
|
167
|
+
};
|
package/src/lib/kiro-db.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { readdir, rename, rm, stat } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { mapWithConcurrency } from './concurrency';
|
|
4
|
+
import { createConcurrencyLimiter, mapWithConcurrency } from './concurrency';
|
|
5
5
|
import {
|
|
6
6
|
getDefaultKiroDataDir,
|
|
7
7
|
type KiroSessionSummary,
|
|
@@ -11,21 +11,26 @@ import {
|
|
|
11
11
|
type KiroWorkspaceGroup,
|
|
12
12
|
resolveKiroWorkspaceSessionsDir,
|
|
13
13
|
} from './kiro-exporter-types';
|
|
14
|
+
import { getPortablePathBasename } from './portable-path';
|
|
14
15
|
import {
|
|
15
16
|
asObject,
|
|
16
17
|
asString,
|
|
17
18
|
cleanExtractedText,
|
|
18
19
|
cleanInlineTitle,
|
|
19
|
-
getPortablePathBasename,
|
|
20
20
|
isWorkspacePathQuery,
|
|
21
21
|
type JsonValue,
|
|
22
|
+
readDirectoryEntriesIfExists,
|
|
22
23
|
workspacePathMatchesQuery,
|
|
23
24
|
} from './shared';
|
|
24
25
|
|
|
25
26
|
export { getDefaultKiroDataDir, resolveKiroWorkspaceSessionsDir };
|
|
26
27
|
|
|
27
28
|
const READ_CONCURRENCY = 8;
|
|
29
|
+
const DELETE_CONCURRENCY = 1;
|
|
30
|
+
const EXECUTION_CACHE_TTL_MS = 1_000;
|
|
28
31
|
const WORKSPACE_KEY_PREFIX = 'workspace:';
|
|
32
|
+
const kiroDeleteLimiter = createConcurrencyLimiter(DELETE_CONCURRENCY);
|
|
33
|
+
const executionFilesCache = new Map<string, { expiresAtMs: number; files: KiroExecutionFile[] }>();
|
|
29
34
|
|
|
30
35
|
type KiroSessionIndexEntry = {
|
|
31
36
|
createdAtMs: number | null;
|
|
@@ -45,6 +50,11 @@ type KiroExecutionFile = {
|
|
|
45
50
|
raw: Record<string, JsonValue>;
|
|
46
51
|
};
|
|
47
52
|
|
|
53
|
+
export type DeleteKiroSessionResult = {
|
|
54
|
+
deletedFiles: string[];
|
|
55
|
+
deletedSessionIds: string[];
|
|
56
|
+
};
|
|
57
|
+
|
|
48
58
|
type ReadSessionFileOptions = {
|
|
49
59
|
includeExecutions?: boolean;
|
|
50
60
|
sessionsDir: string;
|
|
@@ -82,19 +92,23 @@ const toIso = (value: number | null): string | null => {
|
|
|
82
92
|
return value === null ? null : new Date(value).toISOString();
|
|
83
93
|
};
|
|
84
94
|
|
|
95
|
+
const parseTimestampStringMs = (value: string): number | null => {
|
|
96
|
+
const numeric = Number(value);
|
|
97
|
+
if (Number.isFinite(numeric)) {
|
|
98
|
+
return numeric > 0 && numeric < 10_000_000_000 ? numeric * 1000 : numeric;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const parsed = Date.parse(value);
|
|
102
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
103
|
+
};
|
|
104
|
+
|
|
85
105
|
const parseTimestampMs = (value: JsonValue | undefined): number | null => {
|
|
86
106
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
87
107
|
return value > 0 && value < 10_000_000_000 ? value * 1000 : value;
|
|
88
108
|
}
|
|
89
109
|
|
|
90
110
|
if (typeof value === 'string') {
|
|
91
|
-
|
|
92
|
-
if (Number.isFinite(numeric)) {
|
|
93
|
-
return numeric > 0 && numeric < 10_000_000_000 ? numeric * 1000 : numeric;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const parsed = Date.parse(value);
|
|
97
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
111
|
+
return value.trim() ? parseTimestampStringMs(value) : null;
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
return null;
|
|
@@ -283,7 +297,9 @@ const parseHistoryEntry = (raw: Record<string, JsonValue>, index: number): KiroT
|
|
|
283
297
|
promptLogCount: getPromptLogCount(raw),
|
|
284
298
|
raw,
|
|
285
299
|
role: asString(message.role ?? null) ?? 'message',
|
|
286
|
-
timestamp:
|
|
300
|
+
timestamp: toIso(
|
|
301
|
+
parseTimestampMs(raw.timestamp ?? raw.createdAt ?? raw.created_at ?? message.timestamp ?? null),
|
|
302
|
+
),
|
|
287
303
|
};
|
|
288
304
|
};
|
|
289
305
|
|
|
@@ -439,7 +455,7 @@ const getKiroWorkspaceHash = (workspacePath: string): string => {
|
|
|
439
455
|
};
|
|
440
456
|
|
|
441
457
|
const listFilesRecursively = async (root: string, maxDepth = 3): Promise<string[]> => {
|
|
442
|
-
const entries = await
|
|
458
|
+
const entries = await readDirectoryEntriesIfExists(root);
|
|
443
459
|
const files: string[] = [];
|
|
444
460
|
|
|
445
461
|
for (const entry of entries) {
|
|
@@ -467,18 +483,31 @@ const listExecutionFilesForSession = async (
|
|
|
467
483
|
return [];
|
|
468
484
|
}
|
|
469
485
|
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
486
|
+
const cached = executionFilesCache.get(workspaceExecutionRoot);
|
|
487
|
+
let executions = cached && cached.expiresAtMs > Date.now() ? cached.files : null;
|
|
488
|
+
if (!executions) {
|
|
489
|
+
const files = await listFilesRecursively(workspaceExecutionRoot);
|
|
490
|
+
executions = (
|
|
491
|
+
await mapWithConcurrency(files, READ_CONCURRENCY, async (filePath) => {
|
|
492
|
+
const raw = await readJsonObject(filePath);
|
|
493
|
+
return raw ? { filePath, raw } : null;
|
|
494
|
+
})
|
|
495
|
+
).flatMap((execution) => (execution ? [execution] : []));
|
|
496
|
+
executionFilesCache.set(workspaceExecutionRoot, {
|
|
497
|
+
expiresAtMs: Date.now() + EXECUTION_CACHE_TTL_MS,
|
|
498
|
+
files: executions,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
475
501
|
|
|
476
|
-
return executions.
|
|
502
|
+
return executions.filter((execution) => asString(execution.raw.chatSessionId ?? null) === sessionId);
|
|
477
503
|
};
|
|
478
504
|
|
|
479
505
|
const getActionTimestamp = (action: Record<string, JsonValue>, execution: Record<string, JsonValue>): string | null => {
|
|
480
506
|
return toIso(
|
|
481
|
-
parseTimestampMs(action.emittedAt) ??
|
|
507
|
+
parseTimestampMs(action.emittedAt) ??
|
|
508
|
+
parseTimestampMs(action.endTime) ??
|
|
509
|
+
parseTimestampMs(execution.endTime) ??
|
|
510
|
+
parseTimestampMs(execution.startTime),
|
|
482
511
|
);
|
|
483
512
|
};
|
|
484
513
|
|
|
@@ -589,6 +618,7 @@ const parseExecutionActionMessageEntry = (
|
|
|
589
618
|
raw: {
|
|
590
619
|
...action,
|
|
591
620
|
executionFilePath: execution.filePath,
|
|
621
|
+
executionStartTime: execution.raw.startTime ?? null,
|
|
592
622
|
},
|
|
593
623
|
role: 'assistant',
|
|
594
624
|
timestamp: getActionTimestamp(action, execution.raw),
|
|
@@ -628,6 +658,7 @@ const parseExecutionActionToolEntry = (
|
|
|
628
658
|
raw: {
|
|
629
659
|
...action,
|
|
630
660
|
executionFilePath: execution.filePath,
|
|
661
|
+
executionStartTime: execution.raw.startTime ?? null,
|
|
631
662
|
},
|
|
632
663
|
role: 'tool',
|
|
633
664
|
timestamp: getActionTimestamp(action, execution.raw),
|
|
@@ -673,43 +704,87 @@ const readExecutionEntries = async (
|
|
|
673
704
|
return executions.sort(compareExecutionFiles).flatMap(parseExecutionEntries);
|
|
674
705
|
};
|
|
675
706
|
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
executionEntries: KiroTranscriptEntry[],
|
|
679
|
-
): KiroTranscriptEntry[] => {
|
|
680
|
-
if (executionEntries.length === 0) {
|
|
681
|
-
return historyEntries;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
const executionEntriesById = new Map<string, KiroTranscriptEntry[]>();
|
|
707
|
+
const groupExecutionEntriesById = (executionEntries: KiroTranscriptEntry[]) => {
|
|
708
|
+
const entriesById = new Map<string, KiroTranscriptEntry[]>();
|
|
685
709
|
for (const entry of executionEntries) {
|
|
686
710
|
if (!entry.executionId) {
|
|
687
711
|
continue;
|
|
688
712
|
}
|
|
689
713
|
|
|
690
|
-
const entries =
|
|
714
|
+
const entries = entriesById.get(entry.executionId) ?? [];
|
|
691
715
|
entries.push(entry);
|
|
692
|
-
|
|
716
|
+
entriesById.set(entry.executionId, entries);
|
|
693
717
|
}
|
|
718
|
+
return entriesById;
|
|
719
|
+
};
|
|
694
720
|
|
|
721
|
+
const replaceExecutionPlaceholders = (
|
|
722
|
+
historyEntries: KiroTranscriptEntry[],
|
|
723
|
+
executionEntriesById: Map<string, KiroTranscriptEntry[]>,
|
|
724
|
+
) => {
|
|
695
725
|
const usedExecutionIds = new Set<string>();
|
|
696
726
|
const visibleEntries: KiroTranscriptEntry[] = [];
|
|
697
727
|
for (const entry of historyEntries) {
|
|
698
728
|
const matchingExecutionEntries = entry.executionId ? executionEntriesById.get(entry.executionId) : undefined;
|
|
699
|
-
if (isAssistantPlaceholderEntry(entry)) {
|
|
700
|
-
|
|
701
|
-
visibleEntries.push(...matchingExecutionEntries);
|
|
702
|
-
usedExecutionIds.add(entry.executionId!);
|
|
703
|
-
}
|
|
729
|
+
if (!isAssistantPlaceholderEntry(entry) || !matchingExecutionEntries) {
|
|
730
|
+
visibleEntries.push(entry);
|
|
704
731
|
continue;
|
|
705
732
|
}
|
|
706
733
|
|
|
707
|
-
visibleEntries.push(
|
|
734
|
+
visibleEntries.push(...matchingExecutionEntries);
|
|
735
|
+
usedExecutionIds.add(entry.executionId!);
|
|
708
736
|
}
|
|
737
|
+
return { usedExecutionIds, visibleEntries };
|
|
738
|
+
};
|
|
709
739
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
)
|
|
740
|
+
const groupUnmatchedExecutionEntries = (executionEntries: KiroTranscriptEntry[], usedExecutionIds: Set<string>) => {
|
|
741
|
+
const groups = new Map<string, KiroTranscriptEntry[]>();
|
|
742
|
+
for (const entry of executionEntries) {
|
|
743
|
+
if (entry.executionId && usedExecutionIds.has(entry.executionId)) {
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const groupKey = entry.executionId ?? asString(entry.raw.executionFilePath ?? null) ?? entry.entryId;
|
|
748
|
+
const group = groups.get(groupKey) ?? [];
|
|
749
|
+
group.push(entry);
|
|
750
|
+
groups.set(groupKey, group);
|
|
751
|
+
}
|
|
752
|
+
return groups.values();
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
const insertExecutionGroupByStartTime = (
|
|
756
|
+
visibleEntries: KiroTranscriptEntry[],
|
|
757
|
+
executionGroup: KiroTranscriptEntry[],
|
|
758
|
+
) => {
|
|
759
|
+
const firstEntry = executionGroup[0]!;
|
|
760
|
+
const executionTime = parseTimestampMs(firstEntry.raw.executionStartTime) ?? parseTimestampMs(firstEntry.timestamp);
|
|
761
|
+
const insertionIndex =
|
|
762
|
+
executionTime === null
|
|
763
|
+
? -1
|
|
764
|
+
: visibleEntries.findIndex((entry) => {
|
|
765
|
+
const entryTime = parseTimestampMs(entry.timestamp);
|
|
766
|
+
return entryTime !== null && entryTime > executionTime;
|
|
767
|
+
});
|
|
768
|
+
if (insertionIndex < 0) {
|
|
769
|
+
visibleEntries.push(...executionGroup);
|
|
770
|
+
} else {
|
|
771
|
+
visibleEntries.splice(insertionIndex, 0, ...executionGroup);
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
const getVisibleEntries = (
|
|
776
|
+
historyEntries: KiroTranscriptEntry[],
|
|
777
|
+
executionEntries: KiroTranscriptEntry[],
|
|
778
|
+
): KiroTranscriptEntry[] => {
|
|
779
|
+
if (executionEntries.length === 0) {
|
|
780
|
+
return historyEntries;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const executionEntriesById = groupExecutionEntriesById(executionEntries);
|
|
784
|
+
const { usedExecutionIds, visibleEntries } = replaceExecutionPlaceholders(historyEntries, executionEntriesById);
|
|
785
|
+
for (const executionGroup of groupUnmatchedExecutionEntries(executionEntries, usedExecutionIds)) {
|
|
786
|
+
insertExecutionGroupByStartTime(visibleEntries, executionGroup);
|
|
787
|
+
}
|
|
713
788
|
return visibleEntries;
|
|
714
789
|
};
|
|
715
790
|
|
|
@@ -782,7 +857,7 @@ const readSessionFile = async (
|
|
|
782
857
|
const listSessionFilesForWorkspace = async (sessionsDir: string, directoryName: string): Promise<KiroSessionFile[]> => {
|
|
783
858
|
const workspaceDir = path.join(sessionsDir, directoryName);
|
|
784
859
|
const index = await readSessionIndex(workspaceDir);
|
|
785
|
-
const entries = await
|
|
860
|
+
const entries = await readDirectoryEntriesIfExists(workspaceDir);
|
|
786
861
|
|
|
787
862
|
return entries
|
|
788
863
|
.filter((entry) => entry.isFile() && entry.name.endsWith('.json') && entry.name !== 'sessions.json')
|
|
@@ -815,7 +890,7 @@ const readSessionFiles = async (files: KiroSessionFile[]): Promise<KiroSessionTr
|
|
|
815
890
|
const transcripts = await mapWithConcurrency(files, READ_CONCURRENCY, (file) =>
|
|
816
891
|
readSessionFile(file, { includeExecutions: false, sessionsDir: path.dirname(path.dirname(file.filePath)) }),
|
|
817
892
|
);
|
|
818
|
-
return transcripts.flatMap((transcript) => (transcript ? [transcript] : []));
|
|
893
|
+
return transcripts.flatMap((transcript) => (transcript && transcript.renderablePartCount > 0 ? [transcript] : []));
|
|
819
894
|
};
|
|
820
895
|
|
|
821
896
|
const compareNullableMsDesc = (left: number | null, right: number | null): number => {
|
|
@@ -929,7 +1004,39 @@ export const listKiroSessionsForGroup = async (
|
|
|
929
1004
|
|
|
930
1005
|
const locateSessionFile = async (sessionsDir: string, sessionId: string): Promise<KiroSessionFile | null> => {
|
|
931
1006
|
const files = await listSessionFiles(sessionsDir);
|
|
932
|
-
|
|
1007
|
+
const filenameMatch = files.find((file) => path.basename(file.filePath, '.json') === sessionId);
|
|
1008
|
+
if (filenameMatch) {
|
|
1009
|
+
return filenameMatch;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const bodyMatches = await mapWithConcurrency(files, READ_CONCURRENCY, async (file) => {
|
|
1013
|
+
const raw = await readJsonObject(file.filePath);
|
|
1014
|
+
return asString(raw?.sessionId ?? null) === sessionId ? file : null;
|
|
1015
|
+
});
|
|
1016
|
+
return bodyMatches.find((file): file is KiroSessionFile => file !== null) ?? null;
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
const removeKiroSessionIndexEntry = async (workspaceDir: string, sessionId: string): Promise<void> => {
|
|
1020
|
+
const indexPath = path.join(workspaceDir, 'sessions.json');
|
|
1021
|
+
const value = (await Bun.file(indexPath)
|
|
1022
|
+
.json()
|
|
1023
|
+
.catch(() => null)) as JsonValue | null;
|
|
1024
|
+
if (!Array.isArray(value)) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const next = value.filter((item) => asString(asObject(item)?.sessionId ?? null) !== sessionId);
|
|
1029
|
+
if (next.length === value.length) {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
const tempPath = `${indexPath}.${randomUUID()}.tmp`;
|
|
1034
|
+
try {
|
|
1035
|
+
await Bun.write(tempPath, JSON.stringify(next, null, 2));
|
|
1036
|
+
await rename(tempPath, indexPath);
|
|
1037
|
+
} finally {
|
|
1038
|
+
await rm(tempPath, { force: true });
|
|
1039
|
+
}
|
|
933
1040
|
};
|
|
934
1041
|
|
|
935
1042
|
export const readKiroSessionTranscript = async (
|
|
@@ -947,3 +1054,40 @@ export const readKiroSessionTranscript = async (
|
|
|
947
1054
|
|
|
948
1055
|
return readSessionFile(file, { includeExecutions: true, sessionsDir });
|
|
949
1056
|
};
|
|
1057
|
+
|
|
1058
|
+
export const deleteKiroSession = (sessionsDir: string, sessionId: string): Promise<DeleteKiroSessionResult> => {
|
|
1059
|
+
return kiroDeleteLimiter(async () => {
|
|
1060
|
+
if (!(await pathExists(sessionsDir))) {
|
|
1061
|
+
return { deletedFiles: [], deletedSessionIds: [] };
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const file = await locateSessionFile(sessionsDir, sessionId);
|
|
1065
|
+
if (!file) {
|
|
1066
|
+
return { deletedFiles: [], deletedSessionIds: [] };
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const transcript = await readSessionFile(file, { includeExecutions: false, sessionsDir });
|
|
1070
|
+
const executionFiles = transcript
|
|
1071
|
+
? await listExecutionFilesForSession(
|
|
1072
|
+
getKiroDataDirFromSessionsDir(sessionsDir),
|
|
1073
|
+
sessionId,
|
|
1074
|
+
transcript.session.worktree,
|
|
1075
|
+
)
|
|
1076
|
+
: [];
|
|
1077
|
+
const deletedFiles = [file.filePath, ...executionFiles.map((execution) => execution.filePath)];
|
|
1078
|
+
|
|
1079
|
+
await Promise.all(deletedFiles.map((filePath) => rm(filePath, { force: true })));
|
|
1080
|
+
executionFilesCache.delete(
|
|
1081
|
+
path.join(
|
|
1082
|
+
getKiroDataDirFromSessionsDir(sessionsDir),
|
|
1083
|
+
getKiroWorkspaceHash(transcript?.session.worktree ?? ''),
|
|
1084
|
+
),
|
|
1085
|
+
);
|
|
1086
|
+
await removeKiroSessionIndexEntry(path.dirname(file.filePath), sessionId);
|
|
1087
|
+
|
|
1088
|
+
return {
|
|
1089
|
+
deletedFiles,
|
|
1090
|
+
deletedSessionIds: [sessionId],
|
|
1091
|
+
};
|
|
1092
|
+
});
|
|
1093
|
+
};
|