agentel 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/docs/code-reference.md +3 -3
- package/docs/history-source-handling.md +88 -39
- package/docs/release.md +1 -2
- package/package.json +1 -1
- package/src/cli.js +58 -16
- package/src/config.js +4 -1
- package/src/doctor.js +13 -2
- package/src/importers/providers.js +25 -1
- package/src/importers.js +445 -52
- package/src/parser-versions.js +5 -0
- package/src/search.js +4 -1
- package/src/sources.js +7 -3
package/src/importers.js
CHANGED
|
@@ -22,6 +22,8 @@ const { canonicalWebProvider, derivedAccountId, getWebAccount, upsertWebAccount
|
|
|
22
22
|
|
|
23
23
|
const WEB_TOKEN_ESTIMATE_CHARS = 4;
|
|
24
24
|
const WEB_CHAT_TOKEN_ESTIMATION_METHOD = "web-message-parts-chars-v1";
|
|
25
|
+
const OPENCODE_SOURCE_KINDS = new Set(["cli", "desktop", "web"]);
|
|
26
|
+
const OPENCODE_SESSION_ID_RE = /\bses_[A-Za-z0-9]+\b/g;
|
|
25
27
|
|
|
26
28
|
function importCliHistory(options = {}, env = process.env) {
|
|
27
29
|
const source = options.source || "all";
|
|
@@ -76,6 +78,7 @@ function importJsonlProvider(provider, roots, since, options = {}, env = process
|
|
|
76
78
|
const archived = archivedSessionKeys(env);
|
|
77
79
|
const files =
|
|
78
80
|
provider === "claude_code" ? claudeFiles(env) : provider === "claude_sdk" ? claudeSdkFiles(env) : jsonlFiles(roots);
|
|
81
|
+
const claudeCodeMetadata = provider === "claude_code" ? claudeCodeSessionMetadataByCliSessionId(env) : new Map();
|
|
79
82
|
|
|
80
83
|
const candidates = files
|
|
81
84
|
.map((file) => ({ file, stat: safeStat(file) }))
|
|
@@ -95,7 +98,11 @@ function importJsonlProvider(provider, roots, since, options = {}, env = process
|
|
|
95
98
|
for (let index = 0; index < candidates.length; index++) {
|
|
96
99
|
const item = candidates[index];
|
|
97
100
|
const sourceType = jsonlProviderSourceType(provider);
|
|
98
|
-
const
|
|
101
|
+
const baseFingerprint = `${fingerprintPrefix(sourceType)}:${fileFingerprint(item.file, item.stat)}`;
|
|
102
|
+
const preliminaryMetadata = provider === "claude_code"
|
|
103
|
+
? claudeCodeMetadata.get(claudeSessionIdFromFilename(item.file)) || null
|
|
104
|
+
: null;
|
|
105
|
+
let fingerprint = claudeCodeImportFingerprint(baseFingerprint, preliminaryMetadata);
|
|
99
106
|
if (alreadyImportedFile(state, fingerprint, archived, provider)) {
|
|
100
107
|
summary.skipped++;
|
|
101
108
|
reportProgress(options, summary, index + 1, item.file);
|
|
@@ -115,15 +122,17 @@ function importJsonlProvider(provider, roots, since, options = {}, env = process
|
|
|
115
122
|
reportProgress(options, summary, index + 1, item.file);
|
|
116
123
|
continue;
|
|
117
124
|
}
|
|
118
|
-
const
|
|
125
|
+
const sessionId = parsed.sessionId || stableSessionId(provider, item.file, parsed.startedAt, parsed.messages);
|
|
126
|
+
const sessionMetadata = preliminaryMetadata || claudeCodeMetadata.get(sessionId) || null;
|
|
127
|
+
fingerprint = claudeCodeImportFingerprint(baseFingerprint, sessionMetadata);
|
|
128
|
+
const cwd = parsed.cwd || sessionMetadata?.cwd || "";
|
|
119
129
|
const scopeCanonical = cwd ? "" : uncategorizedScope(provider);
|
|
120
|
-
const repo = cwd
|
|
130
|
+
const repo = repoInfoForImport(provider, cwd, sessionMetadata);
|
|
121
131
|
if (options.repos && options.repos.length && (!repo || !options.repos.includes(repo.key))) {
|
|
122
132
|
summary.skipped++;
|
|
123
133
|
reportProgress(options, summary, index + 1, item.file);
|
|
124
134
|
continue;
|
|
125
135
|
}
|
|
126
|
-
const sessionId = parsed.sessionId || stableSessionId(provider, item.file, parsed.startedAt, parsed.messages);
|
|
127
136
|
if (alreadyImported(state, sessionId, fingerprint, archived, provider)) {
|
|
128
137
|
state.files[fingerprint] = { sessionId, duplicate: true, at: new Date().toISOString() };
|
|
129
138
|
summary.skipped++;
|
|
@@ -145,9 +154,10 @@ function importJsonlProvider(provider, roots, since, options = {}, env = process
|
|
|
145
154
|
startedAt: parsed.startedAt,
|
|
146
155
|
endedAt: parsed.endedAt,
|
|
147
156
|
sourcePath: item.file,
|
|
148
|
-
sourceFiles: [item.file, ...auxiliaryFiles],
|
|
157
|
+
sourceFiles: [item.file, sessionMetadata?.sourcePath || "", ...auxiliaryFiles].filter(Boolean),
|
|
149
158
|
sourceType,
|
|
150
|
-
title: parsed
|
|
159
|
+
title: jsonlSessionTitleForImport(parsed, sessionMetadata),
|
|
160
|
+
sessionSummary: claudeCodeSidecarSessionSummary(sessionMetadata)
|
|
151
161
|
},
|
|
152
162
|
env
|
|
153
163
|
);
|
|
@@ -172,7 +182,7 @@ function jsonlProviderSourceType(provider) {
|
|
|
172
182
|
function importClaudeDesktopProvider(provider, since, options = {}, env = process.env) {
|
|
173
183
|
const state = loadImportState(env);
|
|
174
184
|
const archived = archivedSessionKeys(env);
|
|
175
|
-
const sessions = readClaudeDesktopSessions().filter((session) => {
|
|
185
|
+
const sessions = readClaudeDesktopSessions({}, env).filter((session) => {
|
|
176
186
|
return !options.claudeDesktopKind || session.kind === options.claudeDesktopKind;
|
|
177
187
|
});
|
|
178
188
|
const candidates = sessions
|
|
@@ -199,7 +209,7 @@ function importClaudeDesktopProvider(provider, since, options = {}, env = proces
|
|
|
199
209
|
continue;
|
|
200
210
|
}
|
|
201
211
|
const cwd = session.cwd || "";
|
|
202
|
-
const repo =
|
|
212
|
+
const repo = repoInfoForImport(provider, cwd);
|
|
203
213
|
if (options.repos && options.repos.length && !matchesImportedSessionRepo(session, repo, options.repos)) {
|
|
204
214
|
summary.skipped++;
|
|
205
215
|
reportProgress(options, summary, index + 1, session.sourcePath);
|
|
@@ -564,7 +574,7 @@ function structuredSessionReplaceSourcePathCopies(provider, sourceType) {
|
|
|
564
574
|
}
|
|
565
575
|
|
|
566
576
|
function structuredSessionUsesSharedRawFiles(provider, sourceType) {
|
|
567
|
-
return provider === "opencode" &&
|
|
577
|
+
return provider === "opencode" && ["opencode-cli-sqlite-history", "opencode-web-sqlite-history", "opencode-sqlite-history", "opencode-desktop-sqlite-history"].includes(sourceType);
|
|
568
578
|
}
|
|
569
579
|
|
|
570
580
|
function parseAgentJsonl(file, provider) {
|
|
@@ -602,16 +612,114 @@ function parseAgentJsonl(file, provider) {
|
|
|
602
612
|
}
|
|
603
613
|
messages.sort((a, b) => String(a.timestamp || "").localeCompare(String(b.timestamp || "")));
|
|
604
614
|
const deduped = dedupeAdjacentMessages(messages);
|
|
615
|
+
const inferredTitle = inferredJsonlSessionTitle(provider, deduped);
|
|
605
616
|
return {
|
|
606
617
|
cwd,
|
|
607
618
|
sessionId,
|
|
608
|
-
title,
|
|
619
|
+
title: title || inferredTitle,
|
|
620
|
+
titleSource: title ? "source" : inferredTitle ? "first-user-prompt" : "",
|
|
609
621
|
messages: deduped,
|
|
610
622
|
startedAt: deduped[0]?.timestamp || "",
|
|
611
623
|
endedAt: deduped[deduped.length - 1]?.timestamp || ""
|
|
612
624
|
};
|
|
613
625
|
}
|
|
614
626
|
|
|
627
|
+
function repoInfoForImport(provider, cwd, metadata = null) {
|
|
628
|
+
const repoCwd = repoCwdForImport(provider, cwd, metadata);
|
|
629
|
+
if (!repoCwd) return null;
|
|
630
|
+
return canonicalRepo(repoCwd);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function repoCwdForImport(provider, cwd, metadata = null) {
|
|
634
|
+
const metadataCwd = firstExistingDirectory(metadata?.originCwd, metadata?.cwd);
|
|
635
|
+
if (metadataCwd) return metadataCwd;
|
|
636
|
+
return claudeWorktreeParentRepo(provider, cwd) || cwd;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function claudeWorktreeParentRepo(provider, cwd) {
|
|
640
|
+
if (!isClaudeJsonlProvider(provider)) return "";
|
|
641
|
+
const resolved = path.resolve(String(cwd || ""));
|
|
642
|
+
const marker = `${path.sep}.claude${path.sep}worktrees${path.sep}`;
|
|
643
|
+
const markerIndex = resolved.indexOf(marker);
|
|
644
|
+
if (markerIndex === -1) return "";
|
|
645
|
+
const parentRepo = resolved.slice(0, markerIndex);
|
|
646
|
+
const stat = safeStat(parentRepo);
|
|
647
|
+
return stat && stat.isDirectory() ? parentRepo : "";
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function inferredJsonlSessionTitle(provider, messages) {
|
|
651
|
+
if (!isClaudeJsonlProvider(provider)) return "";
|
|
652
|
+
const firstUser = (messages || []).find((message) => message.role === "user" && !message.metadata?.providerGenerated);
|
|
653
|
+
return titleFromPrompt(firstUser?.content);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function titleFromPrompt(value) {
|
|
657
|
+
const cleaned = firstLine(value).replace(/\s+/g, " ").trim();
|
|
658
|
+
if (!cleaned) return "";
|
|
659
|
+
const max = 96;
|
|
660
|
+
return cleaned.length > max ? `${cleaned.slice(0, max - 1).trimEnd()}…` : cleaned;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function isClaudeJsonlProvider(provider) {
|
|
664
|
+
return provider === "claude_code" || provider === "claude_sdk";
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function jsonlSessionTitleForImport(parsed, metadata = null) {
|
|
668
|
+
if (parsed?.titleSource === "source") return parsed.title || "";
|
|
669
|
+
return firstString(metadata?.title, parsed?.title);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function claudeCodeSidecarSessionSummary(metadata = null) {
|
|
673
|
+
if (!metadata) return null;
|
|
674
|
+
const sidecar = compactMetadata({
|
|
675
|
+
appSessionId: metadata.sessionId || undefined,
|
|
676
|
+
cliSessionId: metadata.cliSessionId || undefined,
|
|
677
|
+
title: metadata.title || undefined,
|
|
678
|
+
titleSource: metadata.titleSource || undefined,
|
|
679
|
+
cwd: metadata.cwd || undefined,
|
|
680
|
+
originCwd: metadata.originCwd || undefined,
|
|
681
|
+
worktreePath: metadata.worktreePath || undefined,
|
|
682
|
+
worktreeName: metadata.worktreeName || undefined,
|
|
683
|
+
sourceBranch: metadata.sourceBranch || undefined,
|
|
684
|
+
branch: metadata.branch || undefined,
|
|
685
|
+
createdAt: metadata.createdAt || undefined,
|
|
686
|
+
lastActivityAt: metadata.lastActivityAt || undefined,
|
|
687
|
+
model: metadata.model || undefined,
|
|
688
|
+
effort: metadata.effort || undefined,
|
|
689
|
+
permissionMode: metadata.permissionMode || undefined,
|
|
690
|
+
chromePermissionMode: metadata.chromePermissionMode || undefined,
|
|
691
|
+
completedTurns: metadata.completedTurns,
|
|
692
|
+
isArchived: typeof metadata.isArchived === "boolean" ? metadata.isArchived : undefined,
|
|
693
|
+
enabledMcpToolCount: metadata.enabledMcpToolCount,
|
|
694
|
+
mcpServerNames: metadata.mcpServerNames?.length ? metadata.mcpServerNames : undefined,
|
|
695
|
+
sourcePath: metadata.sourcePath || undefined
|
|
696
|
+
});
|
|
697
|
+
if (!sidecar) return null;
|
|
698
|
+
return compactMetadata({
|
|
699
|
+
claudeCodeSidecar: sidecar,
|
|
700
|
+
modelUsage: metadata.model ? [{ model: metadata.model, source: "claude-code-sidecar" }] : undefined
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function firstExistingDirectory(...values) {
|
|
705
|
+
for (const value of values) {
|
|
706
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
707
|
+
const stat = safeStat(value.trim());
|
|
708
|
+
if (stat && stat.isDirectory()) return value.trim();
|
|
709
|
+
}
|
|
710
|
+
return "";
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function claudeCodeImportFingerprint(baseFingerprint, metadata = null) {
|
|
714
|
+
if (!metadata?.sourcePath) return baseFingerprint;
|
|
715
|
+
return `${baseFingerprint}:claude-code-session:${fileFingerprint(metadata.sourcePath, safeStat(metadata.sourcePath))}`;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function claudeSessionIdFromFilename(file) {
|
|
719
|
+
const base = path.basename(String(file || "")).replace(/\.jsonl(?:\.zst)?$/i, "");
|
|
720
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(base) ? base : "";
|
|
721
|
+
}
|
|
722
|
+
|
|
615
723
|
function extractMessages(event, provider, context = {}) {
|
|
616
724
|
const claudeMessages = extractClaudeMessagesFromEvent(event, provider, context);
|
|
617
725
|
if (claudeMessages.length) {
|
|
@@ -2388,7 +2496,7 @@ function discoverCliHistory(env = process.env, options = {}) {
|
|
|
2388
2496
|
|
|
2389
2497
|
const claudeDesktopSessions = readClaudeDesktopSessions({
|
|
2390
2498
|
onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "Claude App" })
|
|
2391
|
-
});
|
|
2499
|
+
}, env);
|
|
2392
2500
|
publish("claudeCodeDesktop", "Claude Code Desktop", summarizeClaudeDesktopSessions(claudeDesktopSessions, "claude-code-desktop-metadata"));
|
|
2393
2501
|
publish("claudeWorkspace", "Claude Workspace", summarizeClaudeDesktopSessions(claudeDesktopSessions, "claude-workspace-desktop"));
|
|
2394
2502
|
publish("claudeSdk", "Claude SDK jobs", summarizeClaudeSdkScan(claudeScan));
|
|
@@ -2443,13 +2551,38 @@ function discoverCliHistory(env = process.env, options = {}) {
|
|
|
2443
2551
|
);
|
|
2444
2552
|
|
|
2445
2553
|
publish(
|
|
2446
|
-
"
|
|
2447
|
-
"OpenCode",
|
|
2554
|
+
"opencodeCli",
|
|
2555
|
+
"OpenCode CLI",
|
|
2556
|
+
summarizeStructuredSessions(
|
|
2557
|
+
readOpenCodeSessions(env, {
|
|
2558
|
+
openCodeKind: "cli",
|
|
2559
|
+
onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "OpenCode CLI" })
|
|
2560
|
+
}),
|
|
2561
|
+
"OpenCode CLI/core SQLite database plus project JSON session/message/part storage"
|
|
2562
|
+
)
|
|
2563
|
+
);
|
|
2564
|
+
|
|
2565
|
+
publish(
|
|
2566
|
+
"opencodeDesktop",
|
|
2567
|
+
"OpenCode Desktop",
|
|
2568
|
+
summarizeStructuredSessions(
|
|
2569
|
+
readOpenCodeSessions(env, {
|
|
2570
|
+
openCodeKind: "desktop",
|
|
2571
|
+
onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "OpenCode Desktop" })
|
|
2572
|
+
}),
|
|
2573
|
+
"OpenCode Desktop app-specific SQLite database and JSON session/message/part storage"
|
|
2574
|
+
)
|
|
2575
|
+
);
|
|
2576
|
+
|
|
2577
|
+
publish(
|
|
2578
|
+
"opencodeWeb",
|
|
2579
|
+
"OpenCode Web",
|
|
2448
2580
|
summarizeStructuredSessions(
|
|
2449
2581
|
readOpenCodeSessions(env, {
|
|
2450
|
-
|
|
2582
|
+
openCodeKind: "web",
|
|
2583
|
+
onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "OpenCode Web" })
|
|
2451
2584
|
}),
|
|
2452
|
-
"OpenCode
|
|
2585
|
+
"OpenCode web sessions from the shared OpenCode SQLite store"
|
|
2453
2586
|
)
|
|
2454
2587
|
);
|
|
2455
2588
|
|
|
@@ -2524,7 +2657,7 @@ function summarizeClaudeSdkScan(scan) {
|
|
|
2524
2657
|
}
|
|
2525
2658
|
|
|
2526
2659
|
function summarizeClaudeDesktop(env = process.env, options = {}) {
|
|
2527
|
-
const sessions = readClaudeDesktopSessions(options);
|
|
2660
|
+
const sessions = readClaudeDesktopSessions(options, env);
|
|
2528
2661
|
return summarizeClaudeDesktopSessions(sessions);
|
|
2529
2662
|
}
|
|
2530
2663
|
|
|
@@ -3045,10 +3178,10 @@ function summarizeCodexSources(threads) {
|
|
|
3045
3178
|
return { cli, desktop, ...(archived ? { archived } : {}), ...(summaries ? { summaries } : {}) };
|
|
3046
3179
|
}
|
|
3047
3180
|
|
|
3048
|
-
function readClaudeDesktopSessions(options = {}) {
|
|
3181
|
+
function readClaudeDesktopSessions(options = {}, env = process.env) {
|
|
3049
3182
|
const roots = [
|
|
3050
|
-
{ root:
|
|
3051
|
-
{ root:
|
|
3183
|
+
{ root: claudeCodeSessionsRoot(env), kind: "claude-code-desktop-metadata" },
|
|
3184
|
+
{ root: claudeWorkspaceSessionsRoot(env), kind: "claude-workspace-desktop" }
|
|
3052
3185
|
];
|
|
3053
3186
|
const candidates = [];
|
|
3054
3187
|
for (const { root, kind } of roots) {
|
|
@@ -3071,6 +3204,90 @@ function readClaudeDesktopSessions(options = {}) {
|
|
|
3071
3204
|
return sessions;
|
|
3072
3205
|
}
|
|
3073
3206
|
|
|
3207
|
+
function claudeCodeSessionMetadataByCliSessionId(env = process.env) {
|
|
3208
|
+
const byCliSessionId = new Map();
|
|
3209
|
+
collectFiles(claudeCodeSessionsRoot(env), (file) => {
|
|
3210
|
+
if (!path.basename(file).startsWith("local_") || !file.endsWith(".json")) return;
|
|
3211
|
+
const metadata = parseClaudeCodeSessionMetadataFile(file);
|
|
3212
|
+
if (metadata?.cliSessionId) byCliSessionId.set(metadata.cliSessionId, metadata);
|
|
3213
|
+
});
|
|
3214
|
+
return byCliSessionId;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
function parseClaudeCodeSessionMetadataFile(file) {
|
|
3218
|
+
let data;
|
|
3219
|
+
try {
|
|
3220
|
+
data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
3221
|
+
} catch {
|
|
3222
|
+
return null;
|
|
3223
|
+
}
|
|
3224
|
+
const cliSessionId = firstString(data.cliSessionId);
|
|
3225
|
+
if (!cliSessionId) return null;
|
|
3226
|
+
return {
|
|
3227
|
+
cliSessionId,
|
|
3228
|
+
sessionId: firstString(data.sessionId),
|
|
3229
|
+
title: firstString(data.title),
|
|
3230
|
+
titleSource: firstString(data.titleSource),
|
|
3231
|
+
cwd: firstString(data.cwd),
|
|
3232
|
+
originCwd: firstString(data.originCwd),
|
|
3233
|
+
worktreePath: firstString(data.worktreePath),
|
|
3234
|
+
worktreeName: firstString(data.worktreeName),
|
|
3235
|
+
sourceBranch: firstString(data.sourceBranch),
|
|
3236
|
+
branch: firstString(data.branch),
|
|
3237
|
+
createdAt: toIso(data.createdAt),
|
|
3238
|
+
lastActivityAt: toIso(data.lastActivityAt),
|
|
3239
|
+
model: firstString(data.model),
|
|
3240
|
+
effort: firstString(data.effort),
|
|
3241
|
+
permissionMode: firstString(data.permissionMode),
|
|
3242
|
+
chromePermissionMode: firstString(data.chromePermissionMode),
|
|
3243
|
+
completedTurns: numberValue(data.completedTurns),
|
|
3244
|
+
isArchived: typeof data.isArchived === "boolean" ? data.isArchived : undefined,
|
|
3245
|
+
enabledMcpToolCount: enabledMcpToolCount(data.enabledMcpTools),
|
|
3246
|
+
mcpServerNames: claudeMcpServerNames(data.remoteMcpServersConfig),
|
|
3247
|
+
sourcePath: file
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
function enabledMcpToolCount(value) {
|
|
3252
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
3253
|
+
return Object.values(value).filter(Boolean).length;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
function claudeMcpServerNames(value) {
|
|
3257
|
+
if (!Array.isArray(value)) return [];
|
|
3258
|
+
return [...new Set(value.map((server) => firstString(server?.name)).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
function compactMetadata(value) {
|
|
3262
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value || null;
|
|
3263
|
+
const result = {};
|
|
3264
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3265
|
+
if (item === undefined || item === null || item === "") continue;
|
|
3266
|
+
if (Array.isArray(item) && !item.length) continue;
|
|
3267
|
+
if (item && typeof item === "object" && !Array.isArray(item) && !Object.keys(item).length) continue;
|
|
3268
|
+
result[key] = item;
|
|
3269
|
+
}
|
|
3270
|
+
return Object.keys(result).length ? result : null;
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
function numberValue(value) {
|
|
3274
|
+
const n = Number(value);
|
|
3275
|
+
return Number.isFinite(n) ? n : undefined;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
function claudeAppSupportRoot(env = process.env) {
|
|
3279
|
+
const home = env && env.HOME ? env.HOME : os.homedir();
|
|
3280
|
+
return env.CLAUDE_APP_SUPPORT || path.join(home, "Library", "Application Support", "Claude");
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
function claudeCodeSessionsRoot(env = process.env) {
|
|
3284
|
+
return path.join(claudeAppSupportRoot(env), "claude-code-sessions");
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
function claudeWorkspaceSessionsRoot(env = process.env) {
|
|
3288
|
+
return path.join(claudeAppSupportRoot(env), "local-agent-mode-sessions");
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3074
3291
|
function parseClaudeDesktopSessionFile(file, kind = "claude-workspace-desktop") {
|
|
3075
3292
|
let data;
|
|
3076
3293
|
try {
|
|
@@ -3337,7 +3554,10 @@ function cursorBuildComposerInfoLookup(env = process.env) {
|
|
|
3337
3554
|
"key,",
|
|
3338
3555
|
"json_extract(value, '$.modelId') as modelId,",
|
|
3339
3556
|
"json_extract(value, '$.modelName') as modelName,",
|
|
3340
|
-
"json_extract(value, '$.model') as model",
|
|
3557
|
+
"json_extract(value, '$.model') as model,",
|
|
3558
|
+
"json_extract(value, '$.modelInfo.modelName') as modelInfoModelName,",
|
|
3559
|
+
"json_extract(value, '$.modelInfo.modelId') as modelInfoModelId,",
|
|
3560
|
+
"json_extract(value, '$.modelInfo.model') as modelInfoModel",
|
|
3341
3561
|
"from cursorDiskKV where",
|
|
3342
3562
|
"json_valid(value) and",
|
|
3343
3563
|
cursorDiskKvPrefixRangeCondition("bubbleId:")
|
|
@@ -3348,7 +3568,14 @@ function cursorBuildComposerInfoLookup(env = process.env) {
|
|
|
3348
3568
|
const keyMatch = String(row.key || "").match(/^bubbleId:([^:]+):/);
|
|
3349
3569
|
if (!keyMatch) continue;
|
|
3350
3570
|
const composerId = keyMatch[1].toLowerCase();
|
|
3351
|
-
const model = firstString(
|
|
3571
|
+
const model = firstString(
|
|
3572
|
+
row.modelInfoModelName,
|
|
3573
|
+
row.modelInfoModelId,
|
|
3574
|
+
row.modelInfoModel,
|
|
3575
|
+
row.modelId,
|
|
3576
|
+
row.modelName,
|
|
3577
|
+
row.model
|
|
3578
|
+
);
|
|
3352
3579
|
if (!model) continue;
|
|
3353
3580
|
const entry = info.get(composerId) || { title: "", modelHist: new Map() };
|
|
3354
3581
|
entry.modelHist.set(model, (entry.modelHist.get(model) || 0) + 1);
|
|
@@ -3960,6 +4187,7 @@ function cursorGlobalBubbleSelectColumns(valueExpression = "value", keyExpressio
|
|
|
3960
4187
|
`json_extract(${valueExpression}, '$.modelName') as modelName`,
|
|
3961
4188
|
`json_extract(${valueExpression}, '$.modelSlug') as modelSlug`,
|
|
3962
4189
|
`json_extract(${valueExpression}, '$.modelConfig') as modelConfig`,
|
|
4190
|
+
`json_extract(${valueExpression}, '$.modelInfo') as modelInfo`,
|
|
3963
4191
|
`json_extract(${valueExpression}, '$.providerOptions') as providerOptions`,
|
|
3964
4192
|
`json_extract(${valueExpression}, '$.status') as status`,
|
|
3965
4193
|
`json_extract(${valueExpression}, '$.state') as state`,
|
|
@@ -4014,6 +4242,7 @@ function cursorGlobalBubbleDataFromRow(row) {
|
|
|
4014
4242
|
modelName: row.modelName,
|
|
4015
4243
|
modelSlug: row.modelSlug,
|
|
4016
4244
|
modelConfig: cursorParseSqliteJsonColumn(row.modelConfig),
|
|
4245
|
+
modelInfo: cursorParseSqliteJsonColumn(row.modelInfo),
|
|
4017
4246
|
providerOptions: cursorParseSqliteJsonColumn(row.providerOptions),
|
|
4018
4247
|
status: row.status,
|
|
4019
4248
|
state: cursorParseSqliteJsonColumn(row.state) || row.state,
|
|
@@ -5151,6 +5380,11 @@ function cursorMessageMetadata(record, source) {
|
|
|
5151
5380
|
|
|
5152
5381
|
function cursorModel(record) {
|
|
5153
5382
|
return firstCursorModel(
|
|
5383
|
+
record?.modelInfo?.modelName,
|
|
5384
|
+
record?.modelInfo?.modelId,
|
|
5385
|
+
record?.modelInfo?.model,
|
|
5386
|
+
record?.message?.modelInfo?.modelName,
|
|
5387
|
+
record?.message?.modelInfo?.modelId,
|
|
5154
5388
|
record?.model,
|
|
5155
5389
|
record?.modelId,
|
|
5156
5390
|
record?.modelID,
|
|
@@ -6539,15 +6773,15 @@ function clineTitle(messages) {
|
|
|
6539
6773
|
}
|
|
6540
6774
|
|
|
6541
6775
|
function readOpenCodeSessions(env = process.env, options = {}) {
|
|
6542
|
-
const dbs = openCodeDatabaseFiles(env);
|
|
6543
|
-
const roots = openCodeStorageRoots(env);
|
|
6776
|
+
const dbs = openCodeDatabaseFiles(env, options);
|
|
6777
|
+
const roots = openCodeStorageRoots(env, options);
|
|
6544
6778
|
const files = roots.flatMap((root) => openCodeSessionFiles(root).map((file) => ({ root, file })));
|
|
6545
6779
|
const sessions = [];
|
|
6546
6780
|
reportDiscoveryProgress(options, { current: 0, total: dbs.length, message: "reading OpenCode SQLite stores" });
|
|
6547
6781
|
for (let index = 0; index < dbs.length; index++) {
|
|
6548
6782
|
let dbSessions = [];
|
|
6549
6783
|
try {
|
|
6550
|
-
dbSessions = readOpenCodeSqliteSessionsFromDb(dbs[index], options);
|
|
6784
|
+
dbSessions = readOpenCodeSqliteSessionsFromDb(dbs[index], options, env);
|
|
6551
6785
|
} catch (error) {
|
|
6552
6786
|
reportDiscoveryProgress(options, {
|
|
6553
6787
|
current: index + 1,
|
|
@@ -6569,7 +6803,7 @@ function readOpenCodeSessions(env = process.env, options = {}) {
|
|
|
6569
6803
|
reportDiscoveryProgress(options, { current: 0, total: files.length, message: "reading OpenCode storage" });
|
|
6570
6804
|
for (let index = 0; index < files.length; index++) {
|
|
6571
6805
|
const item = files[index];
|
|
6572
|
-
const session = parseOpenCodeSessionFile(item.file, item.root);
|
|
6806
|
+
const session = parseOpenCodeSessionFile(item.file, item.root, env, options);
|
|
6573
6807
|
if (session) {
|
|
6574
6808
|
sessions.push(session);
|
|
6575
6809
|
seenSessionIds.add(session.sessionId.replace(/^opencode-/, ""));
|
|
@@ -6584,38 +6818,70 @@ function readOpenCodeSessions(env = process.env, options = {}) {
|
|
|
6584
6818
|
for (const root of roots) {
|
|
6585
6819
|
for (const sessionId of openCodeMessageSessionIds(root)) {
|
|
6586
6820
|
if (seenSessionIds.has(sessionId)) continue;
|
|
6587
|
-
const session = parseOpenCodeMessageOnlySession(root, sessionId);
|
|
6821
|
+
const session = parseOpenCodeMessageOnlySession(root, sessionId, env, options);
|
|
6588
6822
|
if (session) {
|
|
6589
6823
|
sessions.push(session);
|
|
6590
6824
|
seenSessionIds.add(sessionId);
|
|
6591
6825
|
}
|
|
6592
6826
|
}
|
|
6593
6827
|
}
|
|
6594
|
-
return dedupeStructuredSessions(sessions, "opencode");
|
|
6828
|
+
return filterOpenCodeSessionsForKind(dedupeStructuredSessions(sessions, "opencode"), options.openCodeKind);
|
|
6829
|
+
}
|
|
6830
|
+
|
|
6831
|
+
function filterOpenCodeSessionsForKind(sessions, kind) {
|
|
6832
|
+
if (!OPENCODE_SOURCE_KINDS.has(kind)) return sessions;
|
|
6833
|
+
return (sessions || []).filter((session) => openCodeSourceKindForType(session.sourceType) === kind);
|
|
6595
6834
|
}
|
|
6596
6835
|
|
|
6597
|
-
function openCodeDataRoots(env = process.env) {
|
|
6836
|
+
function openCodeDataRoots(env = process.env, options = {}) {
|
|
6598
6837
|
const configured = env.AGENTLOG_OPENCODE_DATA_DIR || env.OPENCODE_DATA_DIR;
|
|
6599
6838
|
if (configured) return existingUniquePaths([configured]);
|
|
6600
|
-
|
|
6839
|
+
if (options.openCodeKind === "cli") return existingUniquePaths(openCodeCliDataRoots(env));
|
|
6840
|
+
if (options.openCodeKind === "desktop") return existingUniquePaths(openCodeDesktopDataRoots(env));
|
|
6841
|
+
if (options.openCodeKind === "web") return [];
|
|
6842
|
+
return existingUniquePaths([...openCodeCliDataRoots(env), ...openCodeDesktopDataRoots(env)]);
|
|
6843
|
+
}
|
|
6844
|
+
|
|
6845
|
+
function openCodeCliDataRoots(env = process.env) {
|
|
6846
|
+
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
6847
|
+
const roots = [
|
|
6848
|
+
path.join(home, ".local", "share", "opencode")
|
|
6849
|
+
];
|
|
6850
|
+
return roots;
|
|
6851
|
+
}
|
|
6852
|
+
|
|
6853
|
+
function openCodeDesktopDataRoots(env = process.env) {
|
|
6854
|
+
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
6601
6855
|
const roots = [
|
|
6602
|
-
path.join(home, "
|
|
6856
|
+
path.join(home, "Library", "Application Support", "ai.opencode.desktop"),
|
|
6603
6857
|
path.join(home, "Library", "Application Support", "opencode"),
|
|
6604
6858
|
path.join(home, ".local", "share", "ai.opencode.app"),
|
|
6605
6859
|
path.join(home, "Library", "Application Support", "ai.opencode.app")
|
|
6606
6860
|
];
|
|
6607
6861
|
const appData = env.APPDATA || env.LOCALAPPDATA || env.LocalAppData;
|
|
6608
6862
|
if (appData) {
|
|
6863
|
+
roots.push(path.join(appData, "ai.opencode.desktop"));
|
|
6609
6864
|
roots.push(path.join(appData, "opencode"));
|
|
6610
6865
|
roots.push(path.join(appData, "ai.opencode.app"));
|
|
6611
6866
|
}
|
|
6612
6867
|
return existingUniquePaths(roots);
|
|
6613
6868
|
}
|
|
6614
6869
|
|
|
6615
|
-
function
|
|
6870
|
+
function openCodeSqliteDataRoots(env = process.env) {
|
|
6871
|
+
return existingUniquePaths([...openCodeCliDataRoots(env), ...openCodeDesktopDataRoots(env)]);
|
|
6872
|
+
}
|
|
6873
|
+
|
|
6874
|
+
function openCodeDatabaseRoots(env = process.env, options = {}) {
|
|
6875
|
+
const configured = env.AGENTLOG_OPENCODE_DATA_DIR || env.OPENCODE_DATA_DIR;
|
|
6876
|
+
if (configured) return existingUniquePaths([configured]);
|
|
6877
|
+
if (OPENCODE_SOURCE_KINDS.has(options.openCodeKind)) return openCodeSqliteDataRoots(env);
|
|
6878
|
+
return openCodeSqliteDataRoots(env);
|
|
6879
|
+
}
|
|
6880
|
+
|
|
6881
|
+
function openCodeStorageRoots(env = process.env, options = {}) {
|
|
6616
6882
|
const explicit = envPathList(env.AGENTLOG_OPENCODE_STORAGE_ROOTS || env.AGENTLOG_OPENCODE_STORAGE_DIR);
|
|
6617
6883
|
if (explicit.length) return existingUniquePaths(explicit);
|
|
6618
|
-
const dataRoots = openCodeDataRoots(env);
|
|
6884
|
+
const dataRoots = openCodeDataRoots(env, options);
|
|
6619
6885
|
const roots = [];
|
|
6620
6886
|
for (const dataRoot of dataRoots) {
|
|
6621
6887
|
roots.push(path.join(dataRoot, "storage"));
|
|
@@ -6634,11 +6900,11 @@ function openCodeStorageRoots(env = process.env) {
|
|
|
6634
6900
|
return existingUniquePaths(roots);
|
|
6635
6901
|
}
|
|
6636
6902
|
|
|
6637
|
-
function openCodeDatabaseFiles(env = process.env) {
|
|
6903
|
+
function openCodeDatabaseFiles(env = process.env, options = {}) {
|
|
6638
6904
|
const explicit = envPathList(env.AGENTLOG_OPENCODE_DB || env.AGENTLOG_OPENCODE_DATABASE || env.OPENCODE_DB);
|
|
6639
6905
|
if (explicit.length) return existingUniquePaths(explicit);
|
|
6640
6906
|
if ((env.AGENTLOG_OPENCODE_STORAGE_ROOTS || env.AGENTLOG_OPENCODE_STORAGE_DIR) && !(env.AGENTLOG_OPENCODE_DATA_DIR || env.OPENCODE_DATA_DIR)) return [];
|
|
6641
|
-
return existingUniquePaths(
|
|
6907
|
+
return existingUniquePaths(openCodeDatabaseRoots(env, options).flatMap((root) => [
|
|
6642
6908
|
path.join(root, "opencode.db"),
|
|
6643
6909
|
path.join(root, "storage", "opencode.db")
|
|
6644
6910
|
]));
|
|
@@ -6668,11 +6934,40 @@ function openCodeMessageSessionIds(root) {
|
|
|
6668
6934
|
.sort((a, b) => a.localeCompare(b));
|
|
6669
6935
|
}
|
|
6670
6936
|
|
|
6671
|
-
function
|
|
6937
|
+
function openCodeDesktopSessionHints(env = process.env) {
|
|
6938
|
+
const hints = new Map();
|
|
6939
|
+
for (const root of openCodeDesktopDataRoots(env)) {
|
|
6940
|
+
collectFilesLimited(root, (file) => {
|
|
6941
|
+
if (!openCodeDesktopHintFile(file)) return;
|
|
6942
|
+
const stat = safeStat(file);
|
|
6943
|
+
if (!stat || stat.size > 2 * 1024 * 1024) return;
|
|
6944
|
+
let text = "";
|
|
6945
|
+
try {
|
|
6946
|
+
text = fs.readFileSync(file, "utf8");
|
|
6947
|
+
} catch {
|
|
6948
|
+
return;
|
|
6949
|
+
}
|
|
6950
|
+
for (const id of text.match(OPENCODE_SESSION_ID_RE) || []) {
|
|
6951
|
+
const files = hints.get(id) || new Set();
|
|
6952
|
+
files.add(file);
|
|
6953
|
+
hints.set(id, files);
|
|
6954
|
+
}
|
|
6955
|
+
}, 2, { skipDirs: new Set(["Cache", "Cache_Data", "Code Cache", "GPUCache", "blob_storage", "logs"]) });
|
|
6956
|
+
}
|
|
6957
|
+
return hints;
|
|
6958
|
+
}
|
|
6959
|
+
|
|
6960
|
+
function openCodeDesktopHintFile(file) {
|
|
6961
|
+
const base = path.basename(String(file || ""));
|
|
6962
|
+
return base.endsWith(".dat") || base === "opencode.settings" || base === "settings.json";
|
|
6963
|
+
}
|
|
6964
|
+
|
|
6965
|
+
function readOpenCodeSqliteSessionsFromDb(dbPath, options = {}, env = process.env) {
|
|
6672
6966
|
if (!safeStat(dbPath)) return [];
|
|
6673
6967
|
if (!sqliteTableExists(dbPath, "session") || !sqliteTableExists(dbPath, "message") || !sqliteTableExists(dbPath, "part")) return [];
|
|
6674
6968
|
const sessionRows = readOpenCodeSqliteSessionRows(dbPath, options);
|
|
6675
6969
|
if (!sessionRows.length) return [];
|
|
6970
|
+
const classifications = openCodeSqliteSessionClassifications(sessionRows, dbPath, env, options);
|
|
6676
6971
|
const sessionIds = sessionRows.map((row) => row.id).filter(Boolean);
|
|
6677
6972
|
const messageRows = sortOpenCodeSqliteRows(readOpenCodeSqliteMessageRows(dbPath, sessionIds), ["session_id", "time_created", "id"]);
|
|
6678
6973
|
const partRows = sortOpenCodeSqliteRows(readOpenCodeSqlitePartRows(dbPath, sessionIds), ["session_id", "message_id", "time_created", "id"]);
|
|
@@ -6681,20 +6976,22 @@ function readOpenCodeSqliteSessionsFromDb(dbPath, options = {}) {
|
|
|
6681
6976
|
const storageRoot = path.join(path.dirname(dbPath), "storage");
|
|
6682
6977
|
const sessions = [];
|
|
6683
6978
|
for (const row of sessionRows) {
|
|
6979
|
+
const sourceType = classifications.sourceTypes.get(String(row.id || "")) || openCodeSqliteSourceType(dbPath, env, options);
|
|
6684
6980
|
const rows = messagesBySession.get(row.id) || [];
|
|
6685
6981
|
const messages = stampMessages(
|
|
6686
6982
|
dedupeAdjacentMessages(rows.flatMap((messageRow, index) => openCodeSqliteMessagesFromRow(messageRow, partsByMessage.get(messageRow.id) || [], index)))
|
|
6687
6983
|
.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp))),
|
|
6688
|
-
|
|
6984
|
+
sourceType
|
|
6689
6985
|
);
|
|
6690
6986
|
const diffFile = path.join(storageRoot, "session_diff", `${row.id}.json`);
|
|
6691
6987
|
const diffMessage = openCodeDiffMessage(diffFile, messages[messages.length - 1]?.timestamp || toIso(row.time_updated || row.time_created));
|
|
6692
|
-
const finalMessages = diffMessage ? messages.concat(stampMessages([diffMessage],
|
|
6988
|
+
const finalMessages = diffMessage ? messages.concat(stampMessages([diffMessage], sourceType)) : messages;
|
|
6693
6989
|
if (!finalMessages.length) continue;
|
|
6694
6990
|
const sourceFiles = [dbPath, safeStat(diffFile) ? diffFile : ""].filter(Boolean);
|
|
6695
6991
|
const startedAt = toIso(row.time_created) || finalMessages[0]?.timestamp || new Date(safeStat(dbPath)?.mtimeMs || Date.now()).toISOString();
|
|
6696
6992
|
const endedAt = toIso(row.time_updated) || finalMessages[finalMessages.length - 1]?.timestamp || startedAt;
|
|
6697
6993
|
const cwd = firstString(row.directory, row.path, row.project_worktree, openCodeCwdFromMessages(finalMessages));
|
|
6994
|
+
const hintFiles = classifications.hintFiles.get(String(row.id || "")) || [];
|
|
6698
6995
|
sessions.push({
|
|
6699
6996
|
sessionId: `opencode-${row.id}`,
|
|
6700
6997
|
title: firstString(row.title, row.slug, clineTitle(finalMessages), row.id),
|
|
@@ -6703,11 +7000,12 @@ function readOpenCodeSqliteSessionsFromDb(dbPath, options = {}) {
|
|
|
6703
7000
|
endedAt,
|
|
6704
7001
|
messages: finalMessages,
|
|
6705
7002
|
sourcePath: `${dbPath}#${row.id}`,
|
|
6706
|
-
sourceFiles,
|
|
6707
|
-
sourceType
|
|
6708
|
-
fingerprint: openCodeSqliteSessionFingerprint(dbPath, row, rows, sourceFiles),
|
|
7003
|
+
sourceFiles: existingUniquePaths([...sourceFiles, ...hintFiles]),
|
|
7004
|
+
sourceType,
|
|
7005
|
+
fingerprint: openCodeSqliteSessionFingerprint(dbPath, row, rows, existingUniquePaths([...sourceFiles, ...hintFiles]), sourceType),
|
|
6709
7006
|
detailKey: "sqliteSessions",
|
|
6710
7007
|
sessionSummary: {
|
|
7008
|
+
source: openCodeSourceKindForType(sourceType),
|
|
6711
7009
|
projectId: row.project_id || undefined,
|
|
6712
7010
|
parentId: row.parent_id || undefined,
|
|
6713
7011
|
workspaceId: row.workspace_id || undefined,
|
|
@@ -6763,6 +7061,92 @@ function readOpenCodeSqliteSessionRows(dbPath, options = {}) {
|
|
|
6763
7061
|
return readSqliteJson(dbPath, queryParts.join(" "), "OpenCode SQLite sessions");
|
|
6764
7062
|
}
|
|
6765
7063
|
|
|
7064
|
+
function openCodeSqliteSessionClassifications(sessionRows, dbPath, env = process.env, options = {}) {
|
|
7065
|
+
const baseSourceType = openCodeSqliteSourceType(dbPath, env, options);
|
|
7066
|
+
const rowsById = new Map((sessionRows || []).map((row) => [String(row.id || ""), row]).filter(([id]) => id));
|
|
7067
|
+
const desktopHints = openCodeDesktopSessionHints(env);
|
|
7068
|
+
const sourceTypes = new Map();
|
|
7069
|
+
const hintFiles = new Map();
|
|
7070
|
+
const classify = (row, visiting = new Set()) => {
|
|
7071
|
+
const id = String(row?.id || "");
|
|
7072
|
+
if (!id) return baseSourceType;
|
|
7073
|
+
if (sourceTypes.has(id)) return sourceTypes.get(id);
|
|
7074
|
+
if (desktopHints.has(id)) {
|
|
7075
|
+
sourceTypes.set(id, "opencode-desktop-sqlite-history");
|
|
7076
|
+
hintFiles.set(id, [...desktopHints.get(id)]);
|
|
7077
|
+
return "opencode-desktop-sqlite-history";
|
|
7078
|
+
}
|
|
7079
|
+
if (row?.parent_id && !visiting.has(id)) {
|
|
7080
|
+
visiting.add(id);
|
|
7081
|
+
const parentId = String(row.parent_id);
|
|
7082
|
+
const parent = rowsById.get(parentId);
|
|
7083
|
+
const parentSourceType = parent ? classify(parent, visiting) : "";
|
|
7084
|
+
if (parentSourceType === "opencode-desktop-sqlite-history") {
|
|
7085
|
+
sourceTypes.set(id, parentSourceType);
|
|
7086
|
+
const parentFiles = hintFiles.get(parentId);
|
|
7087
|
+
if (parentFiles?.length) hintFiles.set(id, parentFiles);
|
|
7088
|
+
return parentSourceType;
|
|
7089
|
+
}
|
|
7090
|
+
}
|
|
7091
|
+
const sourceType = openCodeSqliteRowSourceType(row, dbPath, env, options, baseSourceType);
|
|
7092
|
+
sourceTypes.set(id, sourceType);
|
|
7093
|
+
return sourceType;
|
|
7094
|
+
};
|
|
7095
|
+
for (const row of sessionRows || []) classify(row);
|
|
7096
|
+
return { sourceTypes, hintFiles };
|
|
7097
|
+
}
|
|
7098
|
+
|
|
7099
|
+
function openCodeSqliteRowSourceType(row, dbPath, env = process.env, options = {}, baseSourceType = openCodeSqliteSourceType(dbPath, env, options)) {
|
|
7100
|
+
if (baseSourceType === "opencode-desktop-sqlite-history") return baseSourceType;
|
|
7101
|
+
if (!pathInsideAny(dbPath, openCodeCliDataRoots(env)) && !OPENCODE_SOURCE_KINDS.has(options.openCodeKind)) return baseSourceType;
|
|
7102
|
+
if (openCodeSqliteRowHasCliMetadata(row)) return "opencode-cli-sqlite-history";
|
|
7103
|
+
if (openCodeSqliteRowLooksWeb(row, dbPath, env)) return "opencode-web-sqlite-history";
|
|
7104
|
+
return baseSourceType;
|
|
7105
|
+
}
|
|
7106
|
+
|
|
7107
|
+
function openCodeSqliteRowHasCliMetadata(row) {
|
|
7108
|
+
return Boolean(firstString(row?.agent, row?.model));
|
|
7109
|
+
}
|
|
7110
|
+
|
|
7111
|
+
function openCodeSqliteRowLooksWeb(row, dbPath, env = process.env) {
|
|
7112
|
+
const version = firstString(row?.version);
|
|
7113
|
+
if (!version || version === "local") return false;
|
|
7114
|
+
return pathInsideAny(dbPath, openCodeCliDataRoots(env));
|
|
7115
|
+
}
|
|
7116
|
+
|
|
7117
|
+
function openCodeSqliteSourceType(dbPath, env = process.env, options = {}) {
|
|
7118
|
+
if (pathInsideAny(dbPath, openCodeDesktopDataRoots(env))) return "opencode-desktop-sqlite-history";
|
|
7119
|
+
if (pathInsideAny(dbPath, openCodeCliDataRoots(env))) return "opencode-sqlite-history";
|
|
7120
|
+
if (options.openCodeKind === "cli") return "opencode-cli-sqlite-history";
|
|
7121
|
+
if (options.openCodeKind === "desktop") return "opencode-desktop-sqlite-history";
|
|
7122
|
+
if (options.openCodeKind === "web") return "opencode-web-sqlite-history";
|
|
7123
|
+
return "opencode-sqlite-history";
|
|
7124
|
+
}
|
|
7125
|
+
|
|
7126
|
+
function openCodeStorageSourceType(storageRoot, env = process.env, options = {}) {
|
|
7127
|
+
if (options.openCodeKind === "cli") return "opencode-cli-history";
|
|
7128
|
+
if (options.openCodeKind === "desktop") return "opencode-desktop-history";
|
|
7129
|
+
if (pathInsideAny(storageRoot, openCodeCliDataRoots(env))) return "opencode-cli-history";
|
|
7130
|
+
if (pathInsideAny(storageRoot, openCodeDesktopDataRoots(env))) return "opencode-desktop-history";
|
|
7131
|
+
return "opencode-history";
|
|
7132
|
+
}
|
|
7133
|
+
|
|
7134
|
+
function openCodeSourceKindForType(sourceType) {
|
|
7135
|
+
if (String(sourceType || "").includes("cli")) return "cli";
|
|
7136
|
+
if (String(sourceType || "").includes("desktop")) return "desktop";
|
|
7137
|
+
if (String(sourceType || "").includes("web")) return "web";
|
|
7138
|
+
return "unknown";
|
|
7139
|
+
}
|
|
7140
|
+
|
|
7141
|
+
function pathInsideAny(candidate, roots) {
|
|
7142
|
+
const resolved = path.resolve(String(candidate || ""));
|
|
7143
|
+
return (roots || []).some((root) => {
|
|
7144
|
+
if (!root) return false;
|
|
7145
|
+
const resolvedRoot = path.resolve(String(root));
|
|
7146
|
+
return resolved === resolvedRoot || resolved.startsWith(`${resolvedRoot}${path.sep}`);
|
|
7147
|
+
});
|
|
7148
|
+
}
|
|
7149
|
+
|
|
6766
7150
|
function openCodeSqliteSessionTimestampExpr(sessionColumns) {
|
|
6767
7151
|
const candidates = ["time_updated", "time_created"].filter((column) => sessionColumns.has(column)).map((column) => `s.${column}`);
|
|
6768
7152
|
if (!candidates.length) return "";
|
|
@@ -6915,19 +7299,20 @@ function openCodeUsageFromMessageData(data, parts = []) {
|
|
|
6915
7299
|
return Object.values(usage).some((value) => value !== undefined) ? usage : null;
|
|
6916
7300
|
}
|
|
6917
7301
|
|
|
6918
|
-
function openCodeSqliteSessionFingerprint(dbPath, row, messageRows, sourceFiles) {
|
|
7302
|
+
function openCodeSqliteSessionFingerprint(dbPath, row, messageRows, sourceFiles, sourceType = "opencode-sqlite-history") {
|
|
6919
7303
|
const sessionRevision = [
|
|
6920
7304
|
row.id,
|
|
6921
7305
|
row.time_updated || row.time_created || "",
|
|
6922
7306
|
messageRows.length,
|
|
6923
7307
|
messageRows.map((message) => `${message.id}:${message.time_updated || message.time_created || ""}`).join("|")
|
|
6924
7308
|
].join(":");
|
|
6925
|
-
return `${fingerprintPrefix(
|
|
7309
|
+
return `${fingerprintPrefix(sourceType)}:${structuredSessionFingerprint({ sourcePath: dbPath, sourceFiles })}:${hashId(sessionRevision)}`;
|
|
6926
7310
|
}
|
|
6927
7311
|
|
|
6928
|
-
function parseOpenCodeSessionFile(file, storageRoot) {
|
|
7312
|
+
function parseOpenCodeSessionFile(file, storageRoot, env = process.env, options = {}) {
|
|
6929
7313
|
const info = readJsonMaybe(file, null);
|
|
6930
7314
|
if (!info || typeof info !== "object") return null;
|
|
7315
|
+
const sourceType = openCodeStorageSourceType(storageRoot, env, options);
|
|
6931
7316
|
const sessionId = firstString(info.id, info.sessionID, info.sessionId, path.basename(file, ".json"));
|
|
6932
7317
|
if (!sessionId) return null;
|
|
6933
7318
|
const projectId = firstString(info.projectID, info.projectId, path.basename(path.dirname(file)));
|
|
@@ -6938,7 +7323,7 @@ function parseOpenCodeSessionFile(file, storageRoot) {
|
|
|
6938
7323
|
const diffMessage = openCodeDiffMessage(diffFile, parsedMessages[parsedMessages.length - 1]?.timestamp || toIso(info.time?.updated || info.updatedAt || info.createdAt));
|
|
6939
7324
|
const messages = stampMessages(
|
|
6940
7325
|
dedupeAdjacentMessages(parsedMessages.concat(diffMessage ? [diffMessage] : [])).sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp))),
|
|
6941
|
-
|
|
7326
|
+
sourceType
|
|
6942
7327
|
);
|
|
6943
7328
|
if (!messages.length) return null;
|
|
6944
7329
|
const sourceFiles = [
|
|
@@ -6960,21 +7345,23 @@ function parseOpenCodeSessionFile(file, storageRoot) {
|
|
|
6960
7345
|
messages,
|
|
6961
7346
|
sourcePath: file,
|
|
6962
7347
|
sourceFiles,
|
|
6963
|
-
sourceType
|
|
6964
|
-
fingerprint: `${fingerprintPrefix(
|
|
6965
|
-
detailKey: "sessions"
|
|
7348
|
+
sourceType,
|
|
7349
|
+
fingerprint: `${fingerprintPrefix(sourceType)}:${structuredSessionFingerprint({ sourcePath: file, sourceFiles })}`,
|
|
7350
|
+
detailKey: "sessions",
|
|
7351
|
+
sessionSummary: { source: openCodeSourceKindForType(sourceType) }
|
|
6966
7352
|
};
|
|
6967
7353
|
}
|
|
6968
7354
|
|
|
6969
|
-
function parseOpenCodeMessageOnlySession(storageRoot, sessionId) {
|
|
7355
|
+
function parseOpenCodeMessageOnlySession(storageRoot, sessionId, env = process.env, options = {}) {
|
|
6970
7356
|
if (!sessionId) return null;
|
|
7357
|
+
const sourceType = openCodeStorageSourceType(storageRoot, env, options);
|
|
6971
7358
|
const messageFiles = openCodeMessageFiles(storageRoot, sessionId);
|
|
6972
7359
|
const parsedMessages = messageFiles.flatMap((messageFile, index) => openCodeMessagesFromFile(messageFile, storageRoot, index));
|
|
6973
7360
|
const diffFile = path.join(storageRoot, "session_diff", `${sessionId}.json`);
|
|
6974
7361
|
const diffMessage = openCodeDiffMessage(diffFile, parsedMessages[parsedMessages.length - 1]?.timestamp);
|
|
6975
7362
|
const messages = stampMessages(
|
|
6976
7363
|
dedupeAdjacentMessages(parsedMessages.concat(diffMessage ? [diffMessage] : [])).sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp))),
|
|
6977
|
-
|
|
7364
|
+
sourceType
|
|
6978
7365
|
);
|
|
6979
7366
|
if (!messages.length) return null;
|
|
6980
7367
|
const sourceFiles = [
|
|
@@ -6994,9 +7381,10 @@ function parseOpenCodeMessageOnlySession(storageRoot, sessionId) {
|
|
|
6994
7381
|
messages,
|
|
6995
7382
|
sourcePath: path.join(storageRoot, "message", sessionId),
|
|
6996
7383
|
sourceFiles,
|
|
6997
|
-
sourceType
|
|
6998
|
-
fingerprint: `${fingerprintPrefix(
|
|
6999
|
-
detailKey: "sessions"
|
|
7384
|
+
sourceType,
|
|
7385
|
+
fingerprint: `${fingerprintPrefix(sourceType)}:${structuredSessionFingerprint({ sourcePath: path.join(storageRoot, "message", sessionId), sourceFiles })}`,
|
|
7386
|
+
detailKey: "sessions",
|
|
7387
|
+
sessionSummary: { source: openCodeSourceKindForType(sourceType), recoveredFromMessages: true }
|
|
7000
7388
|
};
|
|
7001
7389
|
}
|
|
7002
7390
|
|
|
@@ -7365,7 +7753,12 @@ function dedupeOpenCodeSessions(sessions) {
|
|
|
7365
7753
|
}
|
|
7366
7754
|
|
|
7367
7755
|
function openCodeSourceRank(sourceType) {
|
|
7756
|
+
if (sourceType === "opencode-cli-sqlite-history") return 3;
|
|
7757
|
+
if (sourceType === "opencode-web-sqlite-history") return 3;
|
|
7758
|
+
if (sourceType === "opencode-desktop-sqlite-history") return 3;
|
|
7368
7759
|
if (sourceType === "opencode-sqlite-history") return 3;
|
|
7760
|
+
if (sourceType === "opencode-cli-history") return 2;
|
|
7761
|
+
if (sourceType === "opencode-desktop-history") return 2;
|
|
7369
7762
|
if (sourceType === "opencode-history") return 2;
|
|
7370
7763
|
return 1;
|
|
7371
7764
|
}
|