agentel 0.2.4 → 0.2.6

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/src/importers.js CHANGED
@@ -19,9 +19,12 @@ const { parseGeminiCliJsonSessions, parseGeminiCliJsonlSessions } = require("./i
19
19
  const { importSourceWithAdapter, providerAdapterForSource, providerAdapterSources } = require("./importers/providers");
20
20
  const { parseSince } = require("./importers/shared");
21
21
  const { canonicalWebProvider, derivedAccountId, getWebAccount, upsertWebAccount } = require("./web-accounts");
22
+ const { manualImportInstructionResult } = require("./web-export-instructions");
22
23
 
23
24
  const WEB_TOKEN_ESTIMATE_CHARS = 4;
24
25
  const WEB_CHAT_TOKEN_ESTIMATION_METHOD = "web-message-parts-chars-v1";
26
+ const OPENCODE_SOURCE_KINDS = new Set(["cli", "desktop", "web"]);
27
+ const OPENCODE_SESSION_ID_RE = /\bses_[A-Za-z0-9]+\b/g;
25
28
 
26
29
  function importCliHistory(options = {}, env = process.env) {
27
30
  const source = options.source || "all";
@@ -44,6 +47,7 @@ function importProviderHelpers() {
44
47
  importCursorProvider,
45
48
  importJsonlProvider,
46
49
  importStructuredProvider,
50
+ manualImportInstructionResult,
47
51
  readAntigravitySessions,
48
52
  readAiderSessions,
49
53
  readClineSessions,
@@ -76,6 +80,7 @@ function importJsonlProvider(provider, roots, since, options = {}, env = process
76
80
  const archived = archivedSessionKeys(env);
77
81
  const files =
78
82
  provider === "claude_code" ? claudeFiles(env) : provider === "claude_sdk" ? claudeSdkFiles(env) : jsonlFiles(roots);
83
+ const claudeCodeMetadata = provider === "claude_code" ? claudeCodeSessionMetadataByCliSessionId(env) : new Map();
79
84
 
80
85
  const candidates = files
81
86
  .map((file) => ({ file, stat: safeStat(file) }))
@@ -95,7 +100,11 @@ function importJsonlProvider(provider, roots, since, options = {}, env = process
95
100
  for (let index = 0; index < candidates.length; index++) {
96
101
  const item = candidates[index];
97
102
  const sourceType = jsonlProviderSourceType(provider);
98
- const fingerprint = `${fingerprintPrefix(sourceType)}:${fileFingerprint(item.file, item.stat)}`;
103
+ const baseFingerprint = `${fingerprintPrefix(sourceType)}:${fileFingerprint(item.file, item.stat)}`;
104
+ const preliminaryMetadata = provider === "claude_code"
105
+ ? claudeCodeMetadata.get(claudeSessionIdFromFilename(item.file)) || null
106
+ : null;
107
+ let fingerprint = claudeCodeImportFingerprint(baseFingerprint, preliminaryMetadata);
99
108
  if (alreadyImportedFile(state, fingerprint, archived, provider)) {
100
109
  summary.skipped++;
101
110
  reportProgress(options, summary, index + 1, item.file);
@@ -115,15 +124,17 @@ function importJsonlProvider(provider, roots, since, options = {}, env = process
115
124
  reportProgress(options, summary, index + 1, item.file);
116
125
  continue;
117
126
  }
118
- const cwd = parsed.cwd || "";
127
+ const sessionId = parsed.sessionId || stableSessionId(provider, item.file, parsed.startedAt, parsed.messages);
128
+ const sessionMetadata = preliminaryMetadata || claudeCodeMetadata.get(sessionId) || null;
129
+ fingerprint = claudeCodeImportFingerprint(baseFingerprint, sessionMetadata);
130
+ const cwd = parsed.cwd || sessionMetadata?.cwd || "";
119
131
  const scopeCanonical = cwd ? "" : uncategorizedScope(provider);
120
- const repo = cwd ? canonicalRepo(cwd) : null;
132
+ const repo = repoInfoForImport(provider, cwd, sessionMetadata);
121
133
  if (options.repos && options.repos.length && (!repo || !options.repos.includes(repo.key))) {
122
134
  summary.skipped++;
123
135
  reportProgress(options, summary, index + 1, item.file);
124
136
  continue;
125
137
  }
126
- const sessionId = parsed.sessionId || stableSessionId(provider, item.file, parsed.startedAt, parsed.messages);
127
138
  if (alreadyImported(state, sessionId, fingerprint, archived, provider)) {
128
139
  state.files[fingerprint] = { sessionId, duplicate: true, at: new Date().toISOString() };
129
140
  summary.skipped++;
@@ -145,9 +156,10 @@ function importJsonlProvider(provider, roots, since, options = {}, env = process
145
156
  startedAt: parsed.startedAt,
146
157
  endedAt: parsed.endedAt,
147
158
  sourcePath: item.file,
148
- sourceFiles: [item.file, ...auxiliaryFiles],
159
+ sourceFiles: [item.file, sessionMetadata?.sourcePath || "", ...auxiliaryFiles].filter(Boolean),
149
160
  sourceType,
150
- title: parsed.title
161
+ title: jsonlSessionTitleForImport(parsed, sessionMetadata),
162
+ sessionSummary: mergeSessionSummaries(claudeCodeSidecarSessionSummary(sessionMetadata), parsed.sessionSummary)
151
163
  },
152
164
  env
153
165
  );
@@ -169,10 +181,16 @@ function jsonlProviderSourceType(provider) {
169
181
  return "cli-history";
170
182
  }
171
183
 
184
+ function codexThreadSourceType(thread) {
185
+ if (thread.source === "vscode") return "codex-desktop-history";
186
+ if (thread.source === "exec") return "codex-sdk-history";
187
+ return "codex-cli-history";
188
+ }
189
+
172
190
  function importClaudeDesktopProvider(provider, since, options = {}, env = process.env) {
173
191
  const state = loadImportState(env);
174
192
  const archived = archivedSessionKeys(env);
175
- const sessions = readClaudeDesktopSessions().filter((session) => {
193
+ const sessions = readClaudeDesktopSessions({}, env).filter((session) => {
176
194
  return !options.claudeDesktopKind || session.kind === options.claudeDesktopKind;
177
195
  });
178
196
  const candidates = sessions
@@ -199,7 +217,7 @@ function importClaudeDesktopProvider(provider, since, options = {}, env = proces
199
217
  continue;
200
218
  }
201
219
  const cwd = session.cwd || "";
202
- const repo = cwd ? canonicalRepo(cwd) : null;
220
+ const repo = repoInfoForImport(provider, cwd);
203
221
  if (options.repos && options.repos.length && !matchesImportedSessionRepo(session, repo, options.repos)) {
204
222
  summary.skipped++;
205
223
  reportProgress(options, summary, index + 1, session.sourcePath);
@@ -247,10 +265,12 @@ function matchesImportedSessionRepo(session, repo, wantedRepos) {
247
265
 
248
266
  function importCodexProvider(provider, since, options = {}, env = process.env) {
249
267
  const threads = readCodexSessionEntries(env).filter((thread) => {
250
- if (options.codexSource && thread.source !== options.codexSource) return false;
251
- if (!["cli", "vscode"].includes(thread.source)) return false;
252
- return true;
268
+ if (options.codexSource) return thread.source === options.codexSource;
269
+ return ["cli", "vscode"].includes(thread.source);
253
270
  });
271
+ if (!threads.length && options.codexSource && options.codexSource !== "cli") {
272
+ return { provider, discovered: 0, candidates: 0, imported: 0, skipped: 0, errors: [], details: {} };
273
+ }
254
274
  if (!threads.length) return importJsonlProvider(provider, codexRoots(env), since, options, env);
255
275
 
256
276
  const state = loadImportState(env);
@@ -278,7 +298,7 @@ function importCodexProvider(provider, since, options = {}, env = process.env) {
278
298
  reportProgress(options, summary, index + 1, thread.rolloutPath);
279
299
  continue;
280
300
  }
281
- const sourceType = thread.source === "vscode" ? "codex-desktop-history" : "codex-cli-history";
301
+ const sourceType = codexThreadSourceType(thread);
282
302
  const fingerprint = `${fingerprintPrefix(sourceType)}:${fileFingerprint(thread.rolloutPath, stat)}:${codexSupplementFingerprint(thread)}`;
283
303
  if (alreadyImported(state, thread.id, fingerprint, archived, provider)) {
284
304
  summary.skipped++;
@@ -564,15 +584,17 @@ function structuredSessionReplaceSourcePathCopies(provider, sourceType) {
564
584
  }
565
585
 
566
586
  function structuredSessionUsesSharedRawFiles(provider, sourceType) {
567
- return provider === "opencode" && sourceType === "opencode-sqlite-history";
587
+ return provider === "opencode" && ["opencode-cli-sqlite-history", "opencode-web-sqlite-history", "opencode-sqlite-history", "opencode-desktop-sqlite-history"].includes(sourceType);
568
588
  }
569
589
 
570
590
  function parseAgentJsonl(file, provider) {
571
591
  const text = readTextMaybeZstd(file);
592
+ const events = [];
572
593
  const messages = [];
573
594
  let cwd = "";
574
595
  let sessionId = "";
575
596
  let title = "";
597
+ let titleSource = "";
576
598
  const context = { model: "", tokenUsage: { input: 0, output: 0 } };
577
599
  for (const line of text.split(/\r?\n/)) {
578
600
  if (!line.trim()) continue;
@@ -582,6 +604,7 @@ function parseAgentJsonl(file, provider) {
582
604
  } catch {
583
605
  continue;
584
606
  }
607
+ events.push(event);
585
608
  cwd ||= firstString(event.cwd, event.working_directory, event.workingDirectory, event.context?.cwd, event.payload?.cwd);
586
609
  sessionId ||= firstString(
587
610
  event.session_id,
@@ -593,7 +616,20 @@ function parseAgentJsonl(file, provider) {
593
616
  event.payload?.session_id,
594
617
  event.sessionId
595
618
  );
596
- title ||= firstString(event.title, event.conversation_title, event.payload?.title);
619
+ if (!title) {
620
+ const sourceTitle = firstString(event.title, event.conversation_title, event.payload?.title);
621
+ const aiTitle = firstString(event.aiTitle, event.ai_title);
622
+ if (sourceTitle) {
623
+ title = sourceTitle;
624
+ titleSource = "source";
625
+ } else if (aiTitle) {
626
+ title = aiTitle;
627
+ titleSource = "ai-title";
628
+ }
629
+ }
630
+ collectClaudeJsonlSessionMetadata(event, provider, context);
631
+ }
632
+ for (const event of events) {
597
633
  updateCodexParseContext(event, provider, context);
598
634
  updateClaudeParseContext(event, provider, context);
599
635
  if (applyCodexTokenCount(event, provider, messages, context)) continue;
@@ -602,16 +638,409 @@ function parseAgentJsonl(file, provider) {
602
638
  }
603
639
  messages.sort((a, b) => String(a.timestamp || "").localeCompare(String(b.timestamp || "")));
604
640
  const deduped = dedupeAdjacentMessages(messages);
641
+ const inferredTitle = inferredJsonlSessionTitle(provider, deduped);
605
642
  return {
606
643
  cwd,
607
644
  sessionId,
608
- title,
645
+ title: title || inferredTitle,
646
+ titleSource: titleSource || (inferredTitle ? "first-user-prompt" : ""),
647
+ sessionSummary: claudeJsonlSessionSummary(provider, context),
609
648
  messages: deduped,
610
649
  startedAt: deduped[0]?.timestamp || "",
611
650
  endedAt: deduped[deduped.length - 1]?.timestamp || ""
612
651
  };
613
652
  }
614
653
 
654
+ function collectClaudeJsonlSessionMetadata(event, provider, context = {}) {
655
+ if (!isClaudeJsonlProvider(provider) || !event || typeof event !== "object") return;
656
+ const metadata = context.claudeJsonl || (context.claudeJsonl = {
657
+ eventTypeCounts: {},
658
+ entrypoints: new Set(),
659
+ userTypes: new Set(),
660
+ versions: new Set(),
661
+ gitBranches: new Set(),
662
+ permissionModes: new Set(),
663
+ promptIds: new Set(),
664
+ requestIds: new Set(),
665
+ sourceToolAssistantUUIDs: new Set(),
666
+ attachmentTypes: new Set(),
667
+ remoteToolNames: new Set(),
668
+ mcpServerNames: new Set(),
669
+ toolNames: new Set(),
670
+ agentIds: new Set(),
671
+ slugs: new Set(),
672
+ sourceToolUseIDs: new Set(),
673
+ parentToolUseIDs: new Set(),
674
+ toolUseIDs: new Set(),
675
+ attributionSkills: new Set(),
676
+ queue: {},
677
+ attachmentDetails: {},
678
+ apiErrors: {},
679
+ mcpStructuredContentCount: 0,
680
+ aiTitles: new Set(),
681
+ lastPrompt: "",
682
+ sidechainMessageCount: 0,
683
+ messageEventCount: 0,
684
+ remoteControl: false
685
+ });
686
+
687
+ const type = firstString(event.type, event.kind);
688
+ if (type) metadata.eventTypeCounts[type] = (metadata.eventTypeCounts[type] || 0) + 1;
689
+ addSetValue(metadata.entrypoints, event.entrypoint);
690
+ addSetValue(metadata.userTypes, event.userType, event.user_type);
691
+ addSetValue(metadata.versions, event.version);
692
+ addSetValue(metadata.gitBranches, event.gitBranch, event.git_branch);
693
+ addSetValue(metadata.permissionModes, event.permissionMode, event.permission_mode);
694
+ addSetValue(metadata.promptIds, event.promptId, event.prompt_id);
695
+ addSetValue(metadata.requestIds, event.requestId, event.request_id);
696
+ addSetValue(metadata.sourceToolAssistantUUIDs, event.sourceToolAssistantUUID, event.source_tool_assistant_uuid);
697
+ addSetValue(metadata.agentIds, event.agentId, event.agent_id);
698
+ addSetValue(metadata.slugs, event.slug);
699
+ addSetValue(metadata.sourceToolUseIDs, event.sourceToolUseID, event.source_tool_use_id);
700
+ addSetValue(metadata.parentToolUseIDs, event.parentToolUseID, event.parent_tool_use_id);
701
+ addSetValue(metadata.toolUseIDs, event.toolUseID, event.tool_use_id);
702
+ addSetValue(metadata.attributionSkills, event.attributionSkill, event.attribution_skill);
703
+ if (event.mcpMeta?.structuredContent && typeof event.mcpMeta.structuredContent === "object") metadata.mcpStructuredContentCount++;
704
+ collectClaudeApiError(event, metadata);
705
+ if (event.isSidechain === true) metadata.sidechainMessageCount++;
706
+ if ((event.type === "user" || event.type === "assistant") && event.message) metadata.messageEventCount++;
707
+
708
+ const aiTitle = firstString(event.aiTitle, event.ai_title);
709
+ if (aiTitle) metadata.aiTitles.add(aiTitle);
710
+ const lastPrompt = firstString(event.lastPrompt, event.last_prompt);
711
+ if (lastPrompt) metadata.lastPrompt = lastPrompt;
712
+
713
+ if (event.type === "queue-operation") collectClaudeQueueOperation(event, metadata);
714
+ if (event.type === "attachment") collectClaudeAttachmentMetadata(event, metadata);
715
+ collectClaudeToolNames(event, metadata);
716
+ }
717
+
718
+ function collectClaudeQueueOperation(event, metadata) {
719
+ const op = firstString(event.operation).toLowerCase();
720
+ if (!op) return;
721
+ const key = op.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "unknown";
722
+ metadata.queue[key] = (metadata.queue[key] || 0) + 1;
723
+ const timestamp = toIso(event.timestamp || event.created_at || event.createdAt || event.time);
724
+ if (timestamp) {
725
+ if (!metadata.queue.firstAt || timestamp < metadata.queue.firstAt) metadata.queue.firstAt = timestamp;
726
+ if (!metadata.queue.lastAt || timestamp > metadata.queue.lastAt) metadata.queue.lastAt = timestamp;
727
+ const upper = key.charAt(0).toUpperCase() + key.slice(1);
728
+ if (!metadata.queue[`first${upper}At`] || timestamp < metadata.queue[`first${upper}At`]) metadata.queue[`first${upper}At`] = timestamp;
729
+ if (!metadata.queue[`last${upper}At`] || timestamp > metadata.queue[`last${upper}At`]) metadata.queue[`last${upper}At`] = timestamp;
730
+ }
731
+ if (typeof event.content === "string" && event.content.trim()) {
732
+ metadata.queue.lastContent = event.content.trim();
733
+ }
734
+ }
735
+
736
+ function collectClaudeApiError(event, metadata) {
737
+ if (!event || typeof event !== "object") return;
738
+ if (event.isApiErrorMessage !== true && !event.error && event.apiErrorStatus == null) return;
739
+ const status = Number(event.apiErrorStatus);
740
+ if (Number.isFinite(status)) {
741
+ const key = String(status);
742
+ metadata.apiErrors.statusCounts = metadata.apiErrors.statusCounts || {};
743
+ metadata.apiErrors.statusCounts[key] = (metadata.apiErrors.statusCounts[key] || 0) + 1;
744
+ }
745
+ const type = firstString(event.error);
746
+ if (type) {
747
+ metadata.apiErrors.typeCounts = metadata.apiErrors.typeCounts || {};
748
+ metadata.apiErrors.typeCounts[type] = (metadata.apiErrors.typeCounts[type] || 0) + 1;
749
+ }
750
+ metadata.apiErrors.count = (metadata.apiErrors.count || 0) + 1;
751
+ }
752
+
753
+ function collectClaudeAttachmentMetadata(event, metadata) {
754
+ const attachment = event.attachment && typeof event.attachment === "object" ? event.attachment : {};
755
+ addSetValue(metadata.attachmentTypes, attachment.type);
756
+ collectClaudeAttachmentDetails(attachment, metadata);
757
+ for (const name of [...asStringArray(attachment.addedNames), ...asStringArray(attachment.removedNames)]) {
758
+ if (typeof name !== "string" || !name.trim()) continue;
759
+ const value = name.trim();
760
+ metadata.remoteToolNames.add(value);
761
+ if (isClaudeRemoteControlToolName(value)) metadata.remoteControl = true;
762
+ const server = claudeMcpServerNameFromTool(value);
763
+ if (server) metadata.mcpServerNames.add(server);
764
+ }
765
+ }
766
+
767
+ function collectClaudeAttachmentDetails(attachment, metadata) {
768
+ const type = firstString(attachment.type);
769
+ if (!type) return;
770
+ const details = metadata.attachmentDetails;
771
+ details.countByType = details.countByType || {};
772
+ details.countByType[type] = (details.countByType[type] || 0) + 1;
773
+ if (type === "nested_memory") {
774
+ details.nestedMemoryPaths = details.nestedMemoryPaths || new Set();
775
+ addSetValue(details.nestedMemoryPaths, firstString(attachment.path, attachment.content?.path, attachment.displayPath));
776
+ } else if (type === "edited_text_file") {
777
+ details.editedTextFiles = details.editedTextFiles || new Set();
778
+ addSetValue(details.editedTextFiles, firstString(attachment.path, attachment.content?.path, attachment.displayPath));
779
+ } else if (type === "queued_command") {
780
+ details.queuedCommandCount = (details.queuedCommandCount || 0) + 1;
781
+ details.lastQueuedCommand = compactMetadata({
782
+ prompt: firstString(attachment.prompt),
783
+ commandMode: firstString(attachment.commandMode, attachment.command_mode)
784
+ });
785
+ } else if (type === "command_permissions") {
786
+ details.commandPermissionCount = (details.commandPermissionCount || 0) + 1;
787
+ details.lastAllowedTools = asStringArray(attachment.allowedTools);
788
+ } else if (type === "date_change") {
789
+ details.dateChanges = details.dateChanges || new Set();
790
+ addSetValue(details.dateChanges, attachment.newDate, attachment.new_date);
791
+ } else if (type === "max_turns_reached") {
792
+ details.maxTurnsReachedCount = (details.maxTurnsReachedCount || 0) + 1;
793
+ details.lastMaxTurnsReached = compactMetadata({
794
+ maxTurns: numericValue(attachment.maxTurns, attachment.max_turns),
795
+ turnCount: numericValue(attachment.turnCount, attachment.turn_count)
796
+ });
797
+ } else if (type === "compact_file_reference") {
798
+ details.compactFileReferences = details.compactFileReferences || new Set();
799
+ addSetValue(details.compactFileReferences, attachment.path, attachment.displayPath);
800
+ }
801
+ }
802
+
803
+ function collectClaudeToolNames(event, metadata) {
804
+ const parts = Array.isArray(event.message?.content) ? event.message.content : [];
805
+ for (const part of parts) {
806
+ if (!part || typeof part !== "object") continue;
807
+ const type = String(part.type || part.kind || "").toLowerCase();
808
+ if (type !== "tool_use" && type !== "server_tool_use" && type !== "mcp_tool_use" && type !== "function_call") continue;
809
+ addSetValue(metadata.toolNames, part.name, part.tool_name, part.toolName, part.function?.name);
810
+ }
811
+ }
812
+
813
+ function claudeMcpServerNameFromTool(name) {
814
+ const match = String(name || "").match(/^mcp__(.+?)__/);
815
+ if (!match) return "";
816
+ return match[1];
817
+ }
818
+
819
+ const CLAUDE_REMOTE_CONTROL_TOOL_NAMES = new Set([
820
+ "RemoteTrigger",
821
+ "TaskOutput",
822
+ "TaskStop",
823
+ "PushNotification",
824
+ "AskUserQuestion",
825
+ "Monitor",
826
+ "TaskCreate",
827
+ "TaskUpdate",
828
+ "TaskList",
829
+ "TaskGet",
830
+ "CronCreate",
831
+ "CronDelete",
832
+ "CronList"
833
+ ]);
834
+
835
+ function isClaudeRemoteControlToolName(name) {
836
+ const value = String(name || "").trim();
837
+ return CLAUDE_REMOTE_CONTROL_TOOL_NAMES.has(value);
838
+ }
839
+
840
+ function claudeJsonlSessionSummary(provider, context = {}) {
841
+ if (!isClaudeJsonlProvider(provider)) return null;
842
+ const metadata = context.claudeJsonl;
843
+ if (!metadata) return null;
844
+ const remoteToolNames = sortedSet(metadata.remoteToolNames);
845
+ const mcpServerNames = sortedSet(metadata.mcpServerNames);
846
+ const attachmentDetails = claudeAttachmentDetailsSummary(metadata.attachmentDetails);
847
+ const result = compactMetadata({
848
+ claudeJsonl: compactMetadata({
849
+ entrypoint: singleOrArray(metadata.entrypoints),
850
+ userType: singleOrArray(metadata.userTypes),
851
+ version: singleOrArray(metadata.versions),
852
+ gitBranch: singleOrArray(metadata.gitBranches),
853
+ permissionModes: sortedSet(metadata.permissionModes),
854
+ eventTypeCounts: compactMetadata(metadata.eventTypeCounts),
855
+ messageEventCount: metadata.messageEventCount || undefined,
856
+ sidechainMessageCount: metadata.sidechainMessageCount || undefined,
857
+ promptIdCount: metadata.promptIds.size || undefined,
858
+ requestIdCount: metadata.requestIds.size || undefined,
859
+ sourceToolAssistantUUIDCount: metadata.sourceToolAssistantUUIDs.size || undefined,
860
+ agentIds: sortedSet(metadata.agentIds),
861
+ slugs: sortedSet(metadata.slugs),
862
+ sourceToolUseIDCount: metadata.sourceToolUseIDs.size || undefined,
863
+ parentToolUseIDCount: metadata.parentToolUseIDs.size || undefined,
864
+ toolUseIDCount: metadata.toolUseIDs.size || undefined,
865
+ attributionSkills: sortedSet(metadata.attributionSkills),
866
+ mcpStructuredContentCount: metadata.mcpStructuredContentCount || undefined,
867
+ apiErrors: compactMetadata(metadata.apiErrors),
868
+ attachmentTypes: sortedSet(metadata.attachmentTypes),
869
+ attachmentDetails,
870
+ toolNames: sortedSet(metadata.toolNames),
871
+ aiTitle: sortedSet(metadata.aiTitles)[0] || undefined,
872
+ lastPrompt: metadata.lastPrompt || undefined,
873
+ queue: compactMetadata(metadata.queue),
874
+ remoteControl: metadata.remoteControl ? compactMetadata({
875
+ detected: true,
876
+ toolCount: remoteToolNames.length || undefined,
877
+ toolNames: remoteToolNames,
878
+ mcpToolCount: remoteToolNames.filter((name) => name.startsWith("mcp__")).length || undefined,
879
+ mcpServerNames
880
+ }) : undefined
881
+ })
882
+ });
883
+ return result || null;
884
+ }
885
+
886
+ function claudeAttachmentDetailsSummary(details = {}) {
887
+ return compactMetadata({
888
+ countByType: compactMetadata(details.countByType),
889
+ nestedMemoryPaths: details.nestedMemoryPaths ? sortedSet(details.nestedMemoryPaths) : undefined,
890
+ editedTextFiles: details.editedTextFiles ? sortedSet(details.editedTextFiles) : undefined,
891
+ queuedCommandCount: details.queuedCommandCount || undefined,
892
+ lastQueuedCommand: details.lastQueuedCommand || undefined,
893
+ commandPermissionCount: details.commandPermissionCount || undefined,
894
+ lastAllowedTools: details.lastAllowedTools?.length ? details.lastAllowedTools : undefined,
895
+ dateChanges: details.dateChanges ? sortedSet(details.dateChanges) : undefined,
896
+ maxTurnsReachedCount: details.maxTurnsReachedCount || undefined,
897
+ lastMaxTurnsReached: details.lastMaxTurnsReached || undefined,
898
+ compactFileReferences: details.compactFileReferences ? sortedSet(details.compactFileReferences) : undefined
899
+ });
900
+ }
901
+
902
+ function addSetValue(set, ...values) {
903
+ for (const value of values) {
904
+ if (typeof value === "string" && value.trim()) set.add(value.trim());
905
+ }
906
+ }
907
+
908
+ function asStringArray(value) {
909
+ if (!Array.isArray(value)) return [];
910
+ return value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim());
911
+ }
912
+
913
+ function numericValue(...values) {
914
+ for (const value of values) {
915
+ if (value == null || value === "") continue;
916
+ const number = Number(value);
917
+ if (Number.isFinite(number)) return number;
918
+ }
919
+ return undefined;
920
+ }
921
+
922
+ function sortedSet(set) {
923
+ return [...set].sort((a, b) => a.localeCompare(b));
924
+ }
925
+
926
+ function singleOrArray(set) {
927
+ const values = sortedSet(set);
928
+ if (!values.length) return undefined;
929
+ return values.length === 1 ? values[0] : values;
930
+ }
931
+
932
+ function repoInfoForImport(provider, cwd, metadata = null) {
933
+ const repoCwd = repoCwdForImport(provider, cwd, metadata);
934
+ if (!repoCwd) return null;
935
+ return canonicalRepo(repoCwd);
936
+ }
937
+
938
+ function repoCwdForImport(provider, cwd, metadata = null) {
939
+ const metadataCwd = firstExistingDirectory(metadata?.originCwd, metadata?.cwd);
940
+ if (metadataCwd) return metadataCwd;
941
+ return claudeWorktreeParentRepo(provider, cwd) || cwd;
942
+ }
943
+
944
+ function claudeWorktreeParentRepo(provider, cwd) {
945
+ if (!isClaudeJsonlProvider(provider)) return "";
946
+ const resolved = path.resolve(String(cwd || ""));
947
+ const marker = `${path.sep}.claude${path.sep}worktrees${path.sep}`;
948
+ const markerIndex = resolved.indexOf(marker);
949
+ if (markerIndex === -1) return "";
950
+ const parentRepo = resolved.slice(0, markerIndex);
951
+ const stat = safeStat(parentRepo);
952
+ return stat && stat.isDirectory() ? parentRepo : "";
953
+ }
954
+
955
+ function inferredJsonlSessionTitle(provider, messages) {
956
+ if (!isClaudeJsonlProvider(provider)) return "";
957
+ const firstUser = (messages || []).find((message) => message.role === "user" && !message.metadata?.providerGenerated);
958
+ return titleFromPrompt(firstUser?.content);
959
+ }
960
+
961
+ function titleFromPrompt(value) {
962
+ const cleaned = firstLine(value).replace(/\s+/g, " ").trim();
963
+ if (!cleaned) return "";
964
+ const max = 96;
965
+ return cleaned.length > max ? `${cleaned.slice(0, max - 1).trimEnd()}…` : cleaned;
966
+ }
967
+
968
+ function isClaudeJsonlProvider(provider) {
969
+ return provider === "claude_code" || provider === "claude_sdk";
970
+ }
971
+
972
+ function jsonlSessionTitleForImport(parsed, metadata = null) {
973
+ return firstString(metadata?.title, parsed?.title);
974
+ }
975
+
976
+ function claudeCodeSidecarSessionSummary(metadata = null) {
977
+ if (!metadata) return null;
978
+ const sidecar = compactMetadata({
979
+ appSessionId: metadata.sessionId || undefined,
980
+ cliSessionId: metadata.cliSessionId || undefined,
981
+ title: metadata.title || undefined,
982
+ titleSource: metadata.titleSource || undefined,
983
+ cwd: metadata.cwd || undefined,
984
+ originCwd: metadata.originCwd || undefined,
985
+ worktreePath: metadata.worktreePath || undefined,
986
+ worktreeName: metadata.worktreeName || undefined,
987
+ sourceBranch: metadata.sourceBranch || undefined,
988
+ branch: metadata.branch || undefined,
989
+ createdAt: metadata.createdAt || undefined,
990
+ lastActivityAt: metadata.lastActivityAt || undefined,
991
+ model: metadata.model || undefined,
992
+ effort: metadata.effort || undefined,
993
+ permissionMode: metadata.permissionMode || undefined,
994
+ chromePermissionMode: metadata.chromePermissionMode || undefined,
995
+ completedTurns: metadata.completedTurns,
996
+ isArchived: typeof metadata.isArchived === "boolean" ? metadata.isArchived : undefined,
997
+ enabledMcpToolCount: metadata.enabledMcpToolCount,
998
+ mcpServerNames: metadata.mcpServerNames?.length ? metadata.mcpServerNames : undefined,
999
+ sourcePath: metadata.sourcePath || undefined
1000
+ });
1001
+ if (!sidecar) return null;
1002
+ return compactMetadata({
1003
+ claudeCodeSidecar: sidecar,
1004
+ modelUsage: metadata.model ? [{ model: metadata.model, source: "claude-code-sidecar" }] : undefined
1005
+ });
1006
+ }
1007
+
1008
+ function mergeSessionSummaries(...summaries) {
1009
+ const result = {};
1010
+ for (const summary of summaries) {
1011
+ if (!summary || typeof summary !== "object" || Array.isArray(summary)) continue;
1012
+ for (const [key, value] of Object.entries(summary)) {
1013
+ if (key === "modelUsage" && Array.isArray(value)) {
1014
+ result.modelUsage = [...(Array.isArray(result.modelUsage) ? result.modelUsage : []), ...value];
1015
+ } else if (value && typeof value === "object" && !Array.isArray(value) && result[key] && typeof result[key] === "object" && !Array.isArray(result[key])) {
1016
+ result[key] = compactMetadata({ ...result[key], ...value });
1017
+ } else {
1018
+ result[key] = value;
1019
+ }
1020
+ }
1021
+ }
1022
+ return compactMetadata(result);
1023
+ }
1024
+
1025
+ function firstExistingDirectory(...values) {
1026
+ for (const value of values) {
1027
+ if (typeof value !== "string" || !value.trim()) continue;
1028
+ const stat = safeStat(value.trim());
1029
+ if (stat && stat.isDirectory()) return value.trim();
1030
+ }
1031
+ return "";
1032
+ }
1033
+
1034
+ function claudeCodeImportFingerprint(baseFingerprint, metadata = null) {
1035
+ if (!metadata?.sourcePath) return baseFingerprint;
1036
+ return `${baseFingerprint}:claude-code-session:${fileFingerprint(metadata.sourcePath, safeStat(metadata.sourcePath))}`;
1037
+ }
1038
+
1039
+ function claudeSessionIdFromFilename(file) {
1040
+ const base = path.basename(String(file || "")).replace(/\.jsonl(?:\.zst)?$/i, "");
1041
+ 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 : "";
1042
+ }
1043
+
615
1044
  function extractMessages(event, provider, context = {}) {
616
1045
  const claudeMessages = extractClaudeMessagesFromEvent(event, provider, context);
617
1046
  if (claudeMessages.length) {
@@ -649,9 +1078,54 @@ function dedupeAdjacentMessages(messages) {
649
1078
  }
650
1079
  result.push(message);
651
1080
  }
1081
+ return dedupeDuplicateToolResults(result);
1082
+ }
1083
+
1084
+ function dedupeDuplicateToolResults(messages) {
1085
+ const result = [];
1086
+ const toolResultsByKey = new Map();
1087
+ for (const message of messages) {
1088
+ const key = duplicateToolResultKey(message);
1089
+ if (key && toolResultsByKey.has(key)) {
1090
+ const existingIndex = toolResultsByKey.get(key);
1091
+ const existing = result[existingIndex];
1092
+ if (timestampsNear(existing?.timestamp, message?.timestamp)) {
1093
+ result[existingIndex] = preferredToolResultMessage(existing, message);
1094
+ continue;
1095
+ }
1096
+ }
1097
+ if (key) toolResultsByKey.set(key, result.length);
1098
+ result.push(message);
1099
+ }
652
1100
  return result;
653
1101
  }
654
1102
 
1103
+ function duplicateToolResultKey(message) {
1104
+ const result = message?.metadata?.toolResult;
1105
+ const id = firstString(result?.id, result?.callId, result?.call_id, result?.toolCallId, result?.tool_call_id, result?.toolUseId, result?.tool_use_id);
1106
+ if (!id || message?.role !== "tool") return "";
1107
+ return `${message?.metadata?.provider || result?.provider || ""}:${id}`;
1108
+ }
1109
+
1110
+ function preferredToolResultMessage(left, right) {
1111
+ return toolResultMessageDisplayScore(right) > toolResultMessageDisplayScore(left) ? right : left;
1112
+ }
1113
+
1114
+ function toolResultMessageDisplayScore(message) {
1115
+ const result = message?.metadata?.toolResult || {};
1116
+ const rawCategory = normalizeToolToken(result.rawCategory);
1117
+ const kind = normalizeToolToken(result.kind);
1118
+ const category = normalizeToolToken(result.category);
1119
+ let score = 0;
1120
+ if (["exec_command_end", "patch_apply_end", "mcp_tool_call_end", "web_search_end", "tool_result", "tool_output"].includes(rawCategory)) score += 2000;
1121
+ if (rawCategory === "function_call_output" || rawCategory === "custom_tool_call_output") score -= 1000;
1122
+ if (category && category !== "function") score += 250;
1123
+ if (kind && !kind.startsWith("call_")) score += 150;
1124
+ if (/^\$\s/.test(String(result.summary || result.output || message?.content || ""))) score += 250;
1125
+ score += Math.min(200, String(result.output || message?.content || "").length / 200);
1126
+ return score;
1127
+ }
1128
+
655
1129
  function hasStructuredTranscriptMetadata(message) {
656
1130
  const metadata = message?.metadata || {};
657
1131
  return Boolean(
@@ -2379,6 +2853,7 @@ function discoverCliHistory(env = process.env, options = {}) {
2379
2853
  const codexThreads = readCodexSessionEntries(env);
2380
2854
  publish("codexCli", "Codex CLI", summarizeCodexThreads(codexThreads, "cli"));
2381
2855
  publish("codexDesktop", "Codex Desktop", summarizeCodexThreads(codexThreads, "vscode"));
2856
+ publish("codexSdk", "Codex SDK jobs", summarizeCodexThreads(codexThreads, "exec"));
2382
2857
  result.codex = summarizeCodexThreads(codexThreads);
2383
2858
 
2384
2859
  const claudeScan = scanClaudeProjectFiles({
@@ -2388,7 +2863,7 @@ function discoverCliHistory(env = process.env, options = {}) {
2388
2863
 
2389
2864
  const claudeDesktopSessions = readClaudeDesktopSessions({
2390
2865
  onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "Claude App" })
2391
- });
2866
+ }, env);
2392
2867
  publish("claudeCodeDesktop", "Claude Code Desktop", summarizeClaudeDesktopSessions(claudeDesktopSessions, "claude-code-desktop-metadata"));
2393
2868
  publish("claudeWorkspace", "Claude Workspace", summarizeClaudeDesktopSessions(claudeDesktopSessions, "claude-workspace-desktop"));
2394
2869
  publish("claudeSdk", "Claude SDK jobs", summarizeClaudeSdkScan(claudeScan));
@@ -2443,13 +2918,38 @@ function discoverCliHistory(env = process.env, options = {}) {
2443
2918
  );
2444
2919
 
2445
2920
  publish(
2446
- "opencode",
2447
- "OpenCode",
2921
+ "opencodeCli",
2922
+ "OpenCode CLI",
2448
2923
  summarizeStructuredSessions(
2449
2924
  readOpenCodeSessions(env, {
2450
- onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "OpenCode" })
2925
+ openCodeKind: "cli",
2926
+ onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "OpenCode CLI" })
2451
2927
  }),
2452
- "OpenCode SQLite database and JSON session/message/part storage"
2928
+ "OpenCode CLI/core SQLite database plus project JSON session/message/part storage"
2929
+ )
2930
+ );
2931
+
2932
+ publish(
2933
+ "opencodeDesktop",
2934
+ "OpenCode Desktop",
2935
+ summarizeStructuredSessions(
2936
+ readOpenCodeSessions(env, {
2937
+ openCodeKind: "desktop",
2938
+ onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "OpenCode Desktop" })
2939
+ }),
2940
+ "OpenCode Desktop app-specific SQLite database and JSON session/message/part storage"
2941
+ )
2942
+ );
2943
+
2944
+ publish(
2945
+ "opencodeWeb",
2946
+ "OpenCode Web",
2947
+ summarizeStructuredSessions(
2948
+ readOpenCodeSessions(env, {
2949
+ openCodeKind: "web",
2950
+ onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "OpenCode Web" })
2951
+ }),
2952
+ "OpenCode web sessions from the shared OpenCode SQLite store"
2453
2953
  )
2454
2954
  );
2455
2955
 
@@ -2480,8 +2980,8 @@ function summarizeCodex(env = process.env, source) {
2480
2980
 
2481
2981
  function summarizeCodexThreads(allThreads, source) {
2482
2982
  const threads = allThreads.filter((thread) => {
2483
- if (!["cli", "vscode"].includes(thread.source)) return false;
2484
- return source ? thread.source === source : true;
2983
+ if (source) return thread.source === source;
2984
+ return ["cli", "vscode"].includes(thread.source);
2485
2985
  });
2486
2986
  if (threads.length) {
2487
2987
  const oldest = threads.length
@@ -2496,10 +2996,24 @@ function summarizeCodexThreads(allThreads, source) {
2496
2996
  ? "terminal `codex` sessions"
2497
2997
  : source === "vscode"
2498
2998
  ? "Codex desktop app sessions"
2999
+ : source === "exec"
3000
+ ? "Codex exec/SDK batch jobs; opt-in"
2499
3001
  : "includes Codex CLI and Codex Desktop top-level sessions"
2500
3002
  };
2501
3003
  }
2502
- return { sessions: 0, oldest: "", details: {}, note: source === "cli" ? "terminal `codex` sessions" : source === "vscode" ? "Codex desktop app sessions" : "" };
3004
+ return {
3005
+ sessions: 0,
3006
+ oldest: "",
3007
+ details: {},
3008
+ note:
3009
+ source === "cli"
3010
+ ? "terminal `codex` sessions"
3011
+ : source === "vscode"
3012
+ ? "Codex desktop app sessions"
3013
+ : source === "exec"
3014
+ ? "Codex exec/SDK batch jobs; opt-in"
3015
+ : ""
3016
+ };
2503
3017
  }
2504
3018
 
2505
3019
  function summarizeClaude() {
@@ -2524,7 +3038,7 @@ function summarizeClaudeSdkScan(scan) {
2524
3038
  }
2525
3039
 
2526
3040
  function summarizeClaudeDesktop(env = process.env, options = {}) {
2527
- const sessions = readClaudeDesktopSessions(options);
3041
+ const sessions = readClaudeDesktopSessions(options, env);
2528
3042
  return summarizeClaudeDesktopSessions(sessions);
2529
3043
  }
2530
3044
 
@@ -2755,11 +3269,13 @@ function classifyClaudeFile(file) {
2755
3269
  }
2756
3270
  let inspected = 0;
2757
3271
  let sawSdkMessage = false;
3272
+ let sawRemoteControlTools = false;
2758
3273
  for (const line of lines) {
2759
3274
  if (!line.trim()) continue;
2760
3275
  inspected++;
2761
3276
  try {
2762
3277
  const event = JSON.parse(line);
3278
+ if (isClaudeRemoteControlToolDelta(event)) sawRemoteControlTools = true;
2763
3279
  if ((event.type === "user" || event.type === "assistant") && event.message) {
2764
3280
  if (event.entrypoint === "sdk-cli") sawSdkMessage = true;
2765
3281
  else return "conversation";
@@ -2767,9 +3283,14 @@ function classifyClaudeFile(file) {
2767
3283
  } catch {
2768
3284
  return "other";
2769
3285
  }
2770
- if (inspected >= 80) return sawSdkMessage ? "sdk-job" : "other";
3286
+ if (inspected >= 80) return sawSdkMessage ? (sawRemoteControlTools ? "conversation" : "sdk-job") : "other";
2771
3287
  }
2772
- return sawSdkMessage ? "sdk-job" : "other";
3288
+ return sawSdkMessage ? (sawRemoteControlTools ? "conversation" : "sdk-job") : "other";
3289
+ }
3290
+
3291
+ function isClaudeRemoteControlToolDelta(event) {
3292
+ if (!event || event.type !== "attachment" || !Array.isArray(event.attachment?.addedNames)) return false;
3293
+ return event.attachment.addedNames.some((name) => isClaudeRemoteControlToolName(name));
2773
3294
  }
2774
3295
 
2775
3296
  function readInitialLines(file, maxLines, maxBytes = 1024 * 1024) {
@@ -3040,15 +3561,16 @@ function codexSupplementFingerprint(thread) {
3040
3561
  function summarizeCodexSources(threads) {
3041
3562
  const cli = threads.filter((thread) => thread.source === "cli").length;
3042
3563
  const desktop = threads.filter((thread) => thread.source === "vscode").length;
3564
+ const exec = threads.filter((thread) => thread.source === "exec").length;
3043
3565
  const summaries = threads.filter((thread) => thread.rawMemory || thread.rolloutSummary).length;
3044
3566
  const archived = threads.filter((thread) => thread.sourceDetail === "archived_sessions").length;
3045
- return { cli, desktop, ...(archived ? { archived } : {}), ...(summaries ? { summaries } : {}) };
3567
+ return { cli, desktop, ...(exec ? { exec } : {}), ...(archived ? { archived } : {}), ...(summaries ? { summaries } : {}) };
3046
3568
  }
3047
3569
 
3048
- function readClaudeDesktopSessions(options = {}) {
3570
+ function readClaudeDesktopSessions(options = {}, env = process.env) {
3049
3571
  const roots = [
3050
- { root: path.join(os.homedir(), "Library", "Application Support", "Claude", "claude-code-sessions"), kind: "claude-code-desktop-metadata" },
3051
- { root: path.join(os.homedir(), "Library", "Application Support", "Claude", "local-agent-mode-sessions"), kind: "claude-workspace-desktop" }
3572
+ { root: claudeCodeSessionsRoot(env), kind: "claude-code-desktop-metadata" },
3573
+ { root: claudeWorkspaceSessionsRoot(env), kind: "claude-workspace-desktop" }
3052
3574
  ];
3053
3575
  const candidates = [];
3054
3576
  for (const { root, kind } of roots) {
@@ -3071,6 +3593,90 @@ function readClaudeDesktopSessions(options = {}) {
3071
3593
  return sessions;
3072
3594
  }
3073
3595
 
3596
+ function claudeCodeSessionMetadataByCliSessionId(env = process.env) {
3597
+ const byCliSessionId = new Map();
3598
+ collectFiles(claudeCodeSessionsRoot(env), (file) => {
3599
+ if (!path.basename(file).startsWith("local_") || !file.endsWith(".json")) return;
3600
+ const metadata = parseClaudeCodeSessionMetadataFile(file);
3601
+ if (metadata?.cliSessionId) byCliSessionId.set(metadata.cliSessionId, metadata);
3602
+ });
3603
+ return byCliSessionId;
3604
+ }
3605
+
3606
+ function parseClaudeCodeSessionMetadataFile(file) {
3607
+ let data;
3608
+ try {
3609
+ data = JSON.parse(fs.readFileSync(file, "utf8"));
3610
+ } catch {
3611
+ return null;
3612
+ }
3613
+ const cliSessionId = firstString(data.cliSessionId);
3614
+ if (!cliSessionId) return null;
3615
+ return {
3616
+ cliSessionId,
3617
+ sessionId: firstString(data.sessionId),
3618
+ title: firstString(data.title),
3619
+ titleSource: firstString(data.titleSource),
3620
+ cwd: firstString(data.cwd),
3621
+ originCwd: firstString(data.originCwd),
3622
+ worktreePath: firstString(data.worktreePath),
3623
+ worktreeName: firstString(data.worktreeName),
3624
+ sourceBranch: firstString(data.sourceBranch),
3625
+ branch: firstString(data.branch),
3626
+ createdAt: toIso(data.createdAt),
3627
+ lastActivityAt: toIso(data.lastActivityAt),
3628
+ model: firstString(data.model),
3629
+ effort: firstString(data.effort),
3630
+ permissionMode: firstString(data.permissionMode),
3631
+ chromePermissionMode: firstString(data.chromePermissionMode),
3632
+ completedTurns: numberValue(data.completedTurns),
3633
+ isArchived: typeof data.isArchived === "boolean" ? data.isArchived : undefined,
3634
+ enabledMcpToolCount: enabledMcpToolCount(data.enabledMcpTools),
3635
+ mcpServerNames: claudeMcpServerNames(data.remoteMcpServersConfig),
3636
+ sourcePath: file
3637
+ };
3638
+ }
3639
+
3640
+ function enabledMcpToolCount(value) {
3641
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
3642
+ return Object.values(value).filter(Boolean).length;
3643
+ }
3644
+
3645
+ function claudeMcpServerNames(value) {
3646
+ if (!Array.isArray(value)) return [];
3647
+ return [...new Set(value.map((server) => firstString(server?.name)).filter(Boolean))].sort((a, b) => a.localeCompare(b));
3648
+ }
3649
+
3650
+ function compactMetadata(value) {
3651
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value || null;
3652
+ const result = {};
3653
+ for (const [key, item] of Object.entries(value)) {
3654
+ if (item === undefined || item === null || item === "") continue;
3655
+ if (Array.isArray(item) && !item.length) continue;
3656
+ if (item && typeof item === "object" && !Array.isArray(item) && !Object.keys(item).length) continue;
3657
+ result[key] = item;
3658
+ }
3659
+ return Object.keys(result).length ? result : null;
3660
+ }
3661
+
3662
+ function numberValue(value) {
3663
+ const n = Number(value);
3664
+ return Number.isFinite(n) ? n : undefined;
3665
+ }
3666
+
3667
+ function claudeAppSupportRoot(env = process.env) {
3668
+ const home = env && env.HOME ? env.HOME : os.homedir();
3669
+ return env.CLAUDE_APP_SUPPORT || path.join(home, "Library", "Application Support", "Claude");
3670
+ }
3671
+
3672
+ function claudeCodeSessionsRoot(env = process.env) {
3673
+ return path.join(claudeAppSupportRoot(env), "claude-code-sessions");
3674
+ }
3675
+
3676
+ function claudeWorkspaceSessionsRoot(env = process.env) {
3677
+ return path.join(claudeAppSupportRoot(env), "local-agent-mode-sessions");
3678
+ }
3679
+
3074
3680
  function parseClaudeDesktopSessionFile(file, kind = "claude-workspace-desktop") {
3075
3681
  let data;
3076
3682
  try {
@@ -3337,7 +3943,10 @@ function cursorBuildComposerInfoLookup(env = process.env) {
3337
3943
  "key,",
3338
3944
  "json_extract(value, '$.modelId') as modelId,",
3339
3945
  "json_extract(value, '$.modelName') as modelName,",
3340
- "json_extract(value, '$.model') as model",
3946
+ "json_extract(value, '$.model') as model,",
3947
+ "json_extract(value, '$.modelInfo.modelName') as modelInfoModelName,",
3948
+ "json_extract(value, '$.modelInfo.modelId') as modelInfoModelId,",
3949
+ "json_extract(value, '$.modelInfo.model') as modelInfoModel",
3341
3950
  "from cursorDiskKV where",
3342
3951
  "json_valid(value) and",
3343
3952
  cursorDiskKvPrefixRangeCondition("bubbleId:")
@@ -3348,7 +3957,14 @@ function cursorBuildComposerInfoLookup(env = process.env) {
3348
3957
  const keyMatch = String(row.key || "").match(/^bubbleId:([^:]+):/);
3349
3958
  if (!keyMatch) continue;
3350
3959
  const composerId = keyMatch[1].toLowerCase();
3351
- const model = firstString(row.modelId, row.modelName, row.model);
3960
+ const model = firstString(
3961
+ row.modelInfoModelName,
3962
+ row.modelInfoModelId,
3963
+ row.modelInfoModel,
3964
+ row.modelId,
3965
+ row.modelName,
3966
+ row.model
3967
+ );
3352
3968
  if (!model) continue;
3353
3969
  const entry = info.get(composerId) || { title: "", modelHist: new Map() };
3354
3970
  entry.modelHist.set(model, (entry.modelHist.get(model) || 0) + 1);
@@ -3960,6 +4576,7 @@ function cursorGlobalBubbleSelectColumns(valueExpression = "value", keyExpressio
3960
4576
  `json_extract(${valueExpression}, '$.modelName') as modelName`,
3961
4577
  `json_extract(${valueExpression}, '$.modelSlug') as modelSlug`,
3962
4578
  `json_extract(${valueExpression}, '$.modelConfig') as modelConfig`,
4579
+ `json_extract(${valueExpression}, '$.modelInfo') as modelInfo`,
3963
4580
  `json_extract(${valueExpression}, '$.providerOptions') as providerOptions`,
3964
4581
  `json_extract(${valueExpression}, '$.status') as status`,
3965
4582
  `json_extract(${valueExpression}, '$.state') as state`,
@@ -4014,6 +4631,7 @@ function cursorGlobalBubbleDataFromRow(row) {
4014
4631
  modelName: row.modelName,
4015
4632
  modelSlug: row.modelSlug,
4016
4633
  modelConfig: cursorParseSqliteJsonColumn(row.modelConfig),
4634
+ modelInfo: cursorParseSqliteJsonColumn(row.modelInfo),
4017
4635
  providerOptions: cursorParseSqliteJsonColumn(row.providerOptions),
4018
4636
  status: row.status,
4019
4637
  state: cursorParseSqliteJsonColumn(row.state) || row.state,
@@ -5151,6 +5769,11 @@ function cursorMessageMetadata(record, source) {
5151
5769
 
5152
5770
  function cursorModel(record) {
5153
5771
  return firstCursorModel(
5772
+ record?.modelInfo?.modelName,
5773
+ record?.modelInfo?.modelId,
5774
+ record?.modelInfo?.model,
5775
+ record?.message?.modelInfo?.modelName,
5776
+ record?.message?.modelInfo?.modelId,
5154
5777
  record?.model,
5155
5778
  record?.modelId,
5156
5779
  record?.modelID,
@@ -6539,15 +7162,15 @@ function clineTitle(messages) {
6539
7162
  }
6540
7163
 
6541
7164
  function readOpenCodeSessions(env = process.env, options = {}) {
6542
- const dbs = openCodeDatabaseFiles(env);
6543
- const roots = openCodeStorageRoots(env);
7165
+ const dbs = openCodeDatabaseFiles(env, options);
7166
+ const roots = openCodeStorageRoots(env, options);
6544
7167
  const files = roots.flatMap((root) => openCodeSessionFiles(root).map((file) => ({ root, file })));
6545
7168
  const sessions = [];
6546
7169
  reportDiscoveryProgress(options, { current: 0, total: dbs.length, message: "reading OpenCode SQLite stores" });
6547
7170
  for (let index = 0; index < dbs.length; index++) {
6548
7171
  let dbSessions = [];
6549
7172
  try {
6550
- dbSessions = readOpenCodeSqliteSessionsFromDb(dbs[index], options);
7173
+ dbSessions = readOpenCodeSqliteSessionsFromDb(dbs[index], options, env);
6551
7174
  } catch (error) {
6552
7175
  reportDiscoveryProgress(options, {
6553
7176
  current: index + 1,
@@ -6569,7 +7192,7 @@ function readOpenCodeSessions(env = process.env, options = {}) {
6569
7192
  reportDiscoveryProgress(options, { current: 0, total: files.length, message: "reading OpenCode storage" });
6570
7193
  for (let index = 0; index < files.length; index++) {
6571
7194
  const item = files[index];
6572
- const session = parseOpenCodeSessionFile(item.file, item.root);
7195
+ const session = parseOpenCodeSessionFile(item.file, item.root, env, options);
6573
7196
  if (session) {
6574
7197
  sessions.push(session);
6575
7198
  seenSessionIds.add(session.sessionId.replace(/^opencode-/, ""));
@@ -6584,38 +7207,70 @@ function readOpenCodeSessions(env = process.env, options = {}) {
6584
7207
  for (const root of roots) {
6585
7208
  for (const sessionId of openCodeMessageSessionIds(root)) {
6586
7209
  if (seenSessionIds.has(sessionId)) continue;
6587
- const session = parseOpenCodeMessageOnlySession(root, sessionId);
7210
+ const session = parseOpenCodeMessageOnlySession(root, sessionId, env, options);
6588
7211
  if (session) {
6589
7212
  sessions.push(session);
6590
7213
  seenSessionIds.add(sessionId);
6591
7214
  }
6592
7215
  }
6593
7216
  }
6594
- return dedupeStructuredSessions(sessions, "opencode");
7217
+ return filterOpenCodeSessionsForKind(dedupeStructuredSessions(sessions, "opencode"), options.openCodeKind);
7218
+ }
7219
+
7220
+ function filterOpenCodeSessionsForKind(sessions, kind) {
7221
+ if (!OPENCODE_SOURCE_KINDS.has(kind)) return sessions;
7222
+ return (sessions || []).filter((session) => openCodeSourceKindForType(session.sourceType) === kind);
6595
7223
  }
6596
7224
 
6597
- function openCodeDataRoots(env = process.env) {
7225
+ function openCodeDataRoots(env = process.env, options = {}) {
6598
7226
  const configured = env.AGENTLOG_OPENCODE_DATA_DIR || env.OPENCODE_DATA_DIR;
6599
7227
  if (configured) return existingUniquePaths([configured]);
6600
- const home = env.HOME || os.homedir();
7228
+ if (options.openCodeKind === "cli") return existingUniquePaths(openCodeCliDataRoots(env));
7229
+ if (options.openCodeKind === "desktop") return existingUniquePaths(openCodeDesktopDataRoots(env));
7230
+ if (options.openCodeKind === "web") return [];
7231
+ return existingUniquePaths([...openCodeCliDataRoots(env), ...openCodeDesktopDataRoots(env)]);
7232
+ }
7233
+
7234
+ function openCodeCliDataRoots(env = process.env) {
7235
+ const home = env.HOME || env.USERPROFILE || os.homedir();
6601
7236
  const roots = [
6602
- path.join(home, ".local", "share", "opencode"),
7237
+ path.join(home, ".local", "share", "opencode")
7238
+ ];
7239
+ return roots;
7240
+ }
7241
+
7242
+ function openCodeDesktopDataRoots(env = process.env) {
7243
+ const home = env.HOME || env.USERPROFILE || os.homedir();
7244
+ const roots = [
7245
+ path.join(home, "Library", "Application Support", "ai.opencode.desktop"),
6603
7246
  path.join(home, "Library", "Application Support", "opencode"),
6604
7247
  path.join(home, ".local", "share", "ai.opencode.app"),
6605
7248
  path.join(home, "Library", "Application Support", "ai.opencode.app")
6606
7249
  ];
6607
7250
  const appData = env.APPDATA || env.LOCALAPPDATA || env.LocalAppData;
6608
7251
  if (appData) {
7252
+ roots.push(path.join(appData, "ai.opencode.desktop"));
6609
7253
  roots.push(path.join(appData, "opencode"));
6610
7254
  roots.push(path.join(appData, "ai.opencode.app"));
6611
7255
  }
6612
7256
  return existingUniquePaths(roots);
6613
7257
  }
6614
7258
 
6615
- function openCodeStorageRoots(env = process.env) {
7259
+ function openCodeSqliteDataRoots(env = process.env) {
7260
+ return existingUniquePaths([...openCodeCliDataRoots(env), ...openCodeDesktopDataRoots(env)]);
7261
+ }
7262
+
7263
+ function openCodeDatabaseRoots(env = process.env, options = {}) {
7264
+ const configured = env.AGENTLOG_OPENCODE_DATA_DIR || env.OPENCODE_DATA_DIR;
7265
+ if (configured) return existingUniquePaths([configured]);
7266
+ if (OPENCODE_SOURCE_KINDS.has(options.openCodeKind)) return openCodeSqliteDataRoots(env);
7267
+ return openCodeSqliteDataRoots(env);
7268
+ }
7269
+
7270
+ function openCodeStorageRoots(env = process.env, options = {}) {
6616
7271
  const explicit = envPathList(env.AGENTLOG_OPENCODE_STORAGE_ROOTS || env.AGENTLOG_OPENCODE_STORAGE_DIR);
6617
7272
  if (explicit.length) return existingUniquePaths(explicit);
6618
- const dataRoots = openCodeDataRoots(env);
7273
+ const dataRoots = openCodeDataRoots(env, options);
6619
7274
  const roots = [];
6620
7275
  for (const dataRoot of dataRoots) {
6621
7276
  roots.push(path.join(dataRoot, "storage"));
@@ -6634,11 +7289,11 @@ function openCodeStorageRoots(env = process.env) {
6634
7289
  return existingUniquePaths(roots);
6635
7290
  }
6636
7291
 
6637
- function openCodeDatabaseFiles(env = process.env) {
7292
+ function openCodeDatabaseFiles(env = process.env, options = {}) {
6638
7293
  const explicit = envPathList(env.AGENTLOG_OPENCODE_DB || env.AGENTLOG_OPENCODE_DATABASE || env.OPENCODE_DB);
6639
7294
  if (explicit.length) return existingUniquePaths(explicit);
6640
7295
  if ((env.AGENTLOG_OPENCODE_STORAGE_ROOTS || env.AGENTLOG_OPENCODE_STORAGE_DIR) && !(env.AGENTLOG_OPENCODE_DATA_DIR || env.OPENCODE_DATA_DIR)) return [];
6641
- return existingUniquePaths(openCodeDataRoots(env).flatMap((root) => [
7296
+ return existingUniquePaths(openCodeDatabaseRoots(env, options).flatMap((root) => [
6642
7297
  path.join(root, "opencode.db"),
6643
7298
  path.join(root, "storage", "opencode.db")
6644
7299
  ]));
@@ -6668,11 +7323,40 @@ function openCodeMessageSessionIds(root) {
6668
7323
  .sort((a, b) => a.localeCompare(b));
6669
7324
  }
6670
7325
 
6671
- function readOpenCodeSqliteSessionsFromDb(dbPath, options = {}) {
7326
+ function openCodeDesktopSessionHints(env = process.env) {
7327
+ const hints = new Map();
7328
+ for (const root of openCodeDesktopDataRoots(env)) {
7329
+ collectFilesLimited(root, (file) => {
7330
+ if (!openCodeDesktopHintFile(file)) return;
7331
+ const stat = safeStat(file);
7332
+ if (!stat || stat.size > 2 * 1024 * 1024) return;
7333
+ let text = "";
7334
+ try {
7335
+ text = fs.readFileSync(file, "utf8");
7336
+ } catch {
7337
+ return;
7338
+ }
7339
+ for (const id of text.match(OPENCODE_SESSION_ID_RE) || []) {
7340
+ const files = hints.get(id) || new Set();
7341
+ files.add(file);
7342
+ hints.set(id, files);
7343
+ }
7344
+ }, 2, { skipDirs: new Set(["Cache", "Cache_Data", "Code Cache", "GPUCache", "blob_storage", "logs"]) });
7345
+ }
7346
+ return hints;
7347
+ }
7348
+
7349
+ function openCodeDesktopHintFile(file) {
7350
+ const base = path.basename(String(file || ""));
7351
+ return base.endsWith(".dat") || base === "opencode.settings" || base === "settings.json";
7352
+ }
7353
+
7354
+ function readOpenCodeSqliteSessionsFromDb(dbPath, options = {}, env = process.env) {
6672
7355
  if (!safeStat(dbPath)) return [];
6673
7356
  if (!sqliteTableExists(dbPath, "session") || !sqliteTableExists(dbPath, "message") || !sqliteTableExists(dbPath, "part")) return [];
6674
7357
  const sessionRows = readOpenCodeSqliteSessionRows(dbPath, options);
6675
7358
  if (!sessionRows.length) return [];
7359
+ const classifications = openCodeSqliteSessionClassifications(sessionRows, dbPath, env, options);
6676
7360
  const sessionIds = sessionRows.map((row) => row.id).filter(Boolean);
6677
7361
  const messageRows = sortOpenCodeSqliteRows(readOpenCodeSqliteMessageRows(dbPath, sessionIds), ["session_id", "time_created", "id"]);
6678
7362
  const partRows = sortOpenCodeSqliteRows(readOpenCodeSqlitePartRows(dbPath, sessionIds), ["session_id", "message_id", "time_created", "id"]);
@@ -6681,20 +7365,22 @@ function readOpenCodeSqliteSessionsFromDb(dbPath, options = {}) {
6681
7365
  const storageRoot = path.join(path.dirname(dbPath), "storage");
6682
7366
  const sessions = [];
6683
7367
  for (const row of sessionRows) {
7368
+ const sourceType = classifications.sourceTypes.get(String(row.id || "")) || openCodeSqliteSourceType(dbPath, env, options);
6684
7369
  const rows = messagesBySession.get(row.id) || [];
6685
7370
  const messages = stampMessages(
6686
7371
  dedupeAdjacentMessages(rows.flatMap((messageRow, index) => openCodeSqliteMessagesFromRow(messageRow, partsByMessage.get(messageRow.id) || [], index)))
6687
7372
  .sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp))),
6688
- "opencode-sqlite-history"
7373
+ sourceType
6689
7374
  );
6690
7375
  const diffFile = path.join(storageRoot, "session_diff", `${row.id}.json`);
6691
7376
  const diffMessage = openCodeDiffMessage(diffFile, messages[messages.length - 1]?.timestamp || toIso(row.time_updated || row.time_created));
6692
- const finalMessages = diffMessage ? messages.concat(stampMessages([diffMessage], "opencode-sqlite-history")) : messages;
7377
+ const finalMessages = diffMessage ? messages.concat(stampMessages([diffMessage], sourceType)) : messages;
6693
7378
  if (!finalMessages.length) continue;
6694
7379
  const sourceFiles = [dbPath, safeStat(diffFile) ? diffFile : ""].filter(Boolean);
6695
7380
  const startedAt = toIso(row.time_created) || finalMessages[0]?.timestamp || new Date(safeStat(dbPath)?.mtimeMs || Date.now()).toISOString();
6696
7381
  const endedAt = toIso(row.time_updated) || finalMessages[finalMessages.length - 1]?.timestamp || startedAt;
6697
7382
  const cwd = firstString(row.directory, row.path, row.project_worktree, openCodeCwdFromMessages(finalMessages));
7383
+ const hintFiles = classifications.hintFiles.get(String(row.id || "")) || [];
6698
7384
  sessions.push({
6699
7385
  sessionId: `opencode-${row.id}`,
6700
7386
  title: firstString(row.title, row.slug, clineTitle(finalMessages), row.id),
@@ -6703,11 +7389,12 @@ function readOpenCodeSqliteSessionsFromDb(dbPath, options = {}) {
6703
7389
  endedAt,
6704
7390
  messages: finalMessages,
6705
7391
  sourcePath: `${dbPath}#${row.id}`,
6706
- sourceFiles,
6707
- sourceType: "opencode-sqlite-history",
6708
- fingerprint: openCodeSqliteSessionFingerprint(dbPath, row, rows, sourceFiles),
7392
+ sourceFiles: existingUniquePaths([...sourceFiles, ...hintFiles]),
7393
+ sourceType,
7394
+ fingerprint: openCodeSqliteSessionFingerprint(dbPath, row, rows, existingUniquePaths([...sourceFiles, ...hintFiles]), sourceType),
6709
7395
  detailKey: "sqliteSessions",
6710
7396
  sessionSummary: {
7397
+ source: openCodeSourceKindForType(sourceType),
6711
7398
  projectId: row.project_id || undefined,
6712
7399
  parentId: row.parent_id || undefined,
6713
7400
  workspaceId: row.workspace_id || undefined,
@@ -6763,6 +7450,92 @@ function readOpenCodeSqliteSessionRows(dbPath, options = {}) {
6763
7450
  return readSqliteJson(dbPath, queryParts.join(" "), "OpenCode SQLite sessions");
6764
7451
  }
6765
7452
 
7453
+ function openCodeSqliteSessionClassifications(sessionRows, dbPath, env = process.env, options = {}) {
7454
+ const baseSourceType = openCodeSqliteSourceType(dbPath, env, options);
7455
+ const rowsById = new Map((sessionRows || []).map((row) => [String(row.id || ""), row]).filter(([id]) => id));
7456
+ const desktopHints = openCodeDesktopSessionHints(env);
7457
+ const sourceTypes = new Map();
7458
+ const hintFiles = new Map();
7459
+ const classify = (row, visiting = new Set()) => {
7460
+ const id = String(row?.id || "");
7461
+ if (!id) return baseSourceType;
7462
+ if (sourceTypes.has(id)) return sourceTypes.get(id);
7463
+ if (desktopHints.has(id)) {
7464
+ sourceTypes.set(id, "opencode-desktop-sqlite-history");
7465
+ hintFiles.set(id, [...desktopHints.get(id)]);
7466
+ return "opencode-desktop-sqlite-history";
7467
+ }
7468
+ if (row?.parent_id && !visiting.has(id)) {
7469
+ visiting.add(id);
7470
+ const parentId = String(row.parent_id);
7471
+ const parent = rowsById.get(parentId);
7472
+ const parentSourceType = parent ? classify(parent, visiting) : "";
7473
+ if (parentSourceType === "opencode-desktop-sqlite-history") {
7474
+ sourceTypes.set(id, parentSourceType);
7475
+ const parentFiles = hintFiles.get(parentId);
7476
+ if (parentFiles?.length) hintFiles.set(id, parentFiles);
7477
+ return parentSourceType;
7478
+ }
7479
+ }
7480
+ const sourceType = openCodeSqliteRowSourceType(row, dbPath, env, options, baseSourceType);
7481
+ sourceTypes.set(id, sourceType);
7482
+ return sourceType;
7483
+ };
7484
+ for (const row of sessionRows || []) classify(row);
7485
+ return { sourceTypes, hintFiles };
7486
+ }
7487
+
7488
+ function openCodeSqliteRowSourceType(row, dbPath, env = process.env, options = {}, baseSourceType = openCodeSqliteSourceType(dbPath, env, options)) {
7489
+ if (baseSourceType === "opencode-desktop-sqlite-history") return baseSourceType;
7490
+ if (!pathInsideAny(dbPath, openCodeCliDataRoots(env)) && !OPENCODE_SOURCE_KINDS.has(options.openCodeKind)) return baseSourceType;
7491
+ if (openCodeSqliteRowHasCliMetadata(row)) return "opencode-cli-sqlite-history";
7492
+ if (openCodeSqliteRowLooksWeb(row, dbPath, env)) return "opencode-web-sqlite-history";
7493
+ return baseSourceType;
7494
+ }
7495
+
7496
+ function openCodeSqliteRowHasCliMetadata(row) {
7497
+ return Boolean(firstString(row?.agent, row?.model));
7498
+ }
7499
+
7500
+ function openCodeSqliteRowLooksWeb(row, dbPath, env = process.env) {
7501
+ const version = firstString(row?.version);
7502
+ if (!version || version === "local") return false;
7503
+ return pathInsideAny(dbPath, openCodeCliDataRoots(env));
7504
+ }
7505
+
7506
+ function openCodeSqliteSourceType(dbPath, env = process.env, options = {}) {
7507
+ if (pathInsideAny(dbPath, openCodeDesktopDataRoots(env))) return "opencode-desktop-sqlite-history";
7508
+ if (pathInsideAny(dbPath, openCodeCliDataRoots(env))) return "opencode-sqlite-history";
7509
+ if (options.openCodeKind === "cli") return "opencode-cli-sqlite-history";
7510
+ if (options.openCodeKind === "desktop") return "opencode-desktop-sqlite-history";
7511
+ if (options.openCodeKind === "web") return "opencode-web-sqlite-history";
7512
+ return "opencode-sqlite-history";
7513
+ }
7514
+
7515
+ function openCodeStorageSourceType(storageRoot, env = process.env, options = {}) {
7516
+ if (options.openCodeKind === "cli") return "opencode-cli-history";
7517
+ if (options.openCodeKind === "desktop") return "opencode-desktop-history";
7518
+ if (pathInsideAny(storageRoot, openCodeCliDataRoots(env))) return "opencode-cli-history";
7519
+ if (pathInsideAny(storageRoot, openCodeDesktopDataRoots(env))) return "opencode-desktop-history";
7520
+ return "opencode-history";
7521
+ }
7522
+
7523
+ function openCodeSourceKindForType(sourceType) {
7524
+ if (String(sourceType || "").includes("cli")) return "cli";
7525
+ if (String(sourceType || "").includes("desktop")) return "desktop";
7526
+ if (String(sourceType || "").includes("web")) return "web";
7527
+ return "unknown";
7528
+ }
7529
+
7530
+ function pathInsideAny(candidate, roots) {
7531
+ const resolved = path.resolve(String(candidate || ""));
7532
+ return (roots || []).some((root) => {
7533
+ if (!root) return false;
7534
+ const resolvedRoot = path.resolve(String(root));
7535
+ return resolved === resolvedRoot || resolved.startsWith(`${resolvedRoot}${path.sep}`);
7536
+ });
7537
+ }
7538
+
6766
7539
  function openCodeSqliteSessionTimestampExpr(sessionColumns) {
6767
7540
  const candidates = ["time_updated", "time_created"].filter((column) => sessionColumns.has(column)).map((column) => `s.${column}`);
6768
7541
  if (!candidates.length) return "";
@@ -6915,19 +7688,20 @@ function openCodeUsageFromMessageData(data, parts = []) {
6915
7688
  return Object.values(usage).some((value) => value !== undefined) ? usage : null;
6916
7689
  }
6917
7690
 
6918
- function openCodeSqliteSessionFingerprint(dbPath, row, messageRows, sourceFiles) {
7691
+ function openCodeSqliteSessionFingerprint(dbPath, row, messageRows, sourceFiles, sourceType = "opencode-sqlite-history") {
6919
7692
  const sessionRevision = [
6920
7693
  row.id,
6921
7694
  row.time_updated || row.time_created || "",
6922
7695
  messageRows.length,
6923
7696
  messageRows.map((message) => `${message.id}:${message.time_updated || message.time_created || ""}`).join("|")
6924
7697
  ].join(":");
6925
- return `${fingerprintPrefix("opencode-sqlite-history")}:${structuredSessionFingerprint({ sourcePath: dbPath, sourceFiles })}:${hashId(sessionRevision)}`;
7698
+ return `${fingerprintPrefix(sourceType)}:${structuredSessionFingerprint({ sourcePath: dbPath, sourceFiles })}:${hashId(sessionRevision)}`;
6926
7699
  }
6927
7700
 
6928
- function parseOpenCodeSessionFile(file, storageRoot) {
7701
+ function parseOpenCodeSessionFile(file, storageRoot, env = process.env, options = {}) {
6929
7702
  const info = readJsonMaybe(file, null);
6930
7703
  if (!info || typeof info !== "object") return null;
7704
+ const sourceType = openCodeStorageSourceType(storageRoot, env, options);
6931
7705
  const sessionId = firstString(info.id, info.sessionID, info.sessionId, path.basename(file, ".json"));
6932
7706
  if (!sessionId) return null;
6933
7707
  const projectId = firstString(info.projectID, info.projectId, path.basename(path.dirname(file)));
@@ -6938,7 +7712,7 @@ function parseOpenCodeSessionFile(file, storageRoot) {
6938
7712
  const diffMessage = openCodeDiffMessage(diffFile, parsedMessages[parsedMessages.length - 1]?.timestamp || toIso(info.time?.updated || info.updatedAt || info.createdAt));
6939
7713
  const messages = stampMessages(
6940
7714
  dedupeAdjacentMessages(parsedMessages.concat(diffMessage ? [diffMessage] : [])).sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp))),
6941
- "opencode-history"
7715
+ sourceType
6942
7716
  );
6943
7717
  if (!messages.length) return null;
6944
7718
  const sourceFiles = [
@@ -6960,21 +7734,23 @@ function parseOpenCodeSessionFile(file, storageRoot) {
6960
7734
  messages,
6961
7735
  sourcePath: file,
6962
7736
  sourceFiles,
6963
- sourceType: "opencode-history",
6964
- fingerprint: `${fingerprintPrefix("opencode-history")}:${structuredSessionFingerprint({ sourcePath: file, sourceFiles })}`,
6965
- detailKey: "sessions"
7737
+ sourceType,
7738
+ fingerprint: `${fingerprintPrefix(sourceType)}:${structuredSessionFingerprint({ sourcePath: file, sourceFiles })}`,
7739
+ detailKey: "sessions",
7740
+ sessionSummary: { source: openCodeSourceKindForType(sourceType) }
6966
7741
  };
6967
7742
  }
6968
7743
 
6969
- function parseOpenCodeMessageOnlySession(storageRoot, sessionId) {
7744
+ function parseOpenCodeMessageOnlySession(storageRoot, sessionId, env = process.env, options = {}) {
6970
7745
  if (!sessionId) return null;
7746
+ const sourceType = openCodeStorageSourceType(storageRoot, env, options);
6971
7747
  const messageFiles = openCodeMessageFiles(storageRoot, sessionId);
6972
7748
  const parsedMessages = messageFiles.flatMap((messageFile, index) => openCodeMessagesFromFile(messageFile, storageRoot, index));
6973
7749
  const diffFile = path.join(storageRoot, "session_diff", `${sessionId}.json`);
6974
7750
  const diffMessage = openCodeDiffMessage(diffFile, parsedMessages[parsedMessages.length - 1]?.timestamp);
6975
7751
  const messages = stampMessages(
6976
7752
  dedupeAdjacentMessages(parsedMessages.concat(diffMessage ? [diffMessage] : [])).sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp))),
6977
- "opencode-history"
7753
+ sourceType
6978
7754
  );
6979
7755
  if (!messages.length) return null;
6980
7756
  const sourceFiles = [
@@ -6994,9 +7770,10 @@ function parseOpenCodeMessageOnlySession(storageRoot, sessionId) {
6994
7770
  messages,
6995
7771
  sourcePath: path.join(storageRoot, "message", sessionId),
6996
7772
  sourceFiles,
6997
- sourceType: "opencode-history",
6998
- fingerprint: `${fingerprintPrefix("opencode-history")}:${structuredSessionFingerprint({ sourcePath: path.join(storageRoot, "message", sessionId), sourceFiles })}`,
6999
- detailKey: "sessions"
7773
+ sourceType,
7774
+ fingerprint: `${fingerprintPrefix(sourceType)}:${structuredSessionFingerprint({ sourcePath: path.join(storageRoot, "message", sessionId), sourceFiles })}`,
7775
+ detailKey: "sessions",
7776
+ sessionSummary: { source: openCodeSourceKindForType(sourceType), recoveredFromMessages: true }
7000
7777
  };
7001
7778
  }
7002
7779
 
@@ -7365,7 +8142,12 @@ function dedupeOpenCodeSessions(sessions) {
7365
8142
  }
7366
8143
 
7367
8144
  function openCodeSourceRank(sourceType) {
8145
+ if (sourceType === "opencode-cli-sqlite-history") return 3;
8146
+ if (sourceType === "opencode-web-sqlite-history") return 3;
8147
+ if (sourceType === "opencode-desktop-sqlite-history") return 3;
7368
8148
  if (sourceType === "opencode-sqlite-history") return 3;
8149
+ if (sourceType === "opencode-cli-history") return 2;
8150
+ if (sourceType === "opencode-desktop-history") return 2;
7369
8151
  if (sourceType === "opencode-history") return 2;
7370
8152
  return 1;
7371
8153
  }