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/cli.js CHANGED
@@ -24,6 +24,7 @@ const { runSupervisorForeground, startSupervisorDetached, stopSupervisor, superv
24
24
  const { configureRemoteFromFlags, hasRemoteTarget, listRemoteSnapshots, replaceRemoteArchive, snapshotArchive, syncArchive, wipeRemoteArchive } = require("./sync");
25
25
  const { version } = require("./version");
26
26
  const { listWebAccounts, renameWebAccount } = require("./web-accounts");
27
+ const { webExportInstructions } = require("./web-export-instructions");
27
28
 
28
29
  const HISTORY_AUTH_COOKIE = "agentlog_history";
29
30
  const SESSION_WEB_PAYLOAD_VERSION = 3;
@@ -2185,21 +2186,20 @@ async function importCommand(args, flags, env) {
2185
2186
  printImportResults([result]);
2186
2187
  return;
2187
2188
  }
2188
- if (sub === "claude-web" || sub === "chatgpt") {
2189
- let importFile = flags.file || args[1] || "";
2190
- if (!importFile && process.stdin.isTTY) {
2191
- printSection(`${sub} Export`);
2192
- printMuted("Choose the official export JSON, ZIP, or extracted folder.");
2193
- importFile = (await ask(" Export path: ")).trim();
2189
+ if (sub === "claude-web" || sub === "claude_web" || sub === "chatgpt") {
2190
+ const instructions = webExportInstructions(sub);
2191
+ if (flags.instructions || flags.instruction || flags.docs) {
2192
+ return printWebExportInstructions(instructions, flags);
2194
2193
  }
2195
- if (!importFile) throw new Error(`usage: agentlog import ${sub} <path> --username <name> [--display-name <name>] [--account-id <id>] [--scope local|team]`);
2194
+ let importFile = flags.file || args[1] || "";
2195
+ if (!importFile) return printWebExportInstructions(instructions, flags);
2196
2196
  let username = flags.username || "";
2197
2197
  let displayName = flags["display-name"] || flags.displayName || "";
2198
2198
  if (!username && process.stdin.isTTY) {
2199
2199
  username = (await ask(`${sub} account username/email: `)).trim();
2200
2200
  if (!displayName) displayName = (await ask(`Display name [${username}]: `)).trim() || username;
2201
2201
  }
2202
- const result = importWebChat(sub, path.resolve(importFile), {
2202
+ const result = importWebChat(instructions.source, path.resolve(importFile), {
2203
2203
  scope: flags.scope || "local",
2204
2204
  dryRun: flags["dry-run"],
2205
2205
  username,
@@ -2544,7 +2544,7 @@ function resumeCommandForSession(session) {
2544
2544
  if (provider === "devin" && sourceType === "devin-cli-history") {
2545
2545
  return `devin -r ${shellArg(sessionId.replace(/^devin-/, ""))}`;
2546
2546
  }
2547
- if (provider === "opencode" && ["opencode-history", "opencode-sqlite-history"].includes(sourceType)) {
2547
+ if (provider === "opencode" && ["opencode-cli-history", "opencode-cli-sqlite-history", "opencode-desktop-history", "opencode-desktop-sqlite-history", "opencode-web-sqlite-history", "opencode-history", "opencode-sqlite-history"].includes(sourceType)) {
2548
2548
  return `opencode --session ${shellArg(sessionId.replace(/^opencode-/, ""))}`;
2549
2549
  }
2550
2550
  if (provider === "gemini_cli" && sourceType === "gemini-cli-history") {
@@ -2827,10 +2827,13 @@ function compareHistorySessionsForTree(a, b) {
2827
2827
 
2828
2828
  function statsPayload(filters, env) {
2829
2829
  const sessions = listHistorySessions({ ...filters, limit: 5000 }, env);
2830
- return statsPayloadForSessions(sessions);
2830
+ return statsPayloadForSessions(sessions, { includeSdk: statsFiltersTargetSdk(filters) });
2831
2831
  }
2832
2832
 
2833
2833
  function statsPayloadForSessions(sessions, options = {}) {
2834
+ const allSessions = Array.isArray(sessions) ? sessions : [];
2835
+ const sdkSessions = allSessions.filter(isSdkStatsSession);
2836
+ const statsSessions = options.includeSdk ? allSessions : allSessions.filter((session) => !isSdkStatsSession(session));
2834
2837
  const providerSet = new Set();
2835
2838
  const companySet = new Set();
2836
2839
  const modelGroupSet = new Set();
@@ -2853,7 +2856,7 @@ function statsPayloadForSessions(sessions, options = {}) {
2853
2856
  let totalUserMessages = 0;
2854
2857
  let peakSessionTokens = 0;
2855
2858
  let peakSessionLabel = "";
2856
- for (const session of sessions) {
2859
+ for (const session of statsSessions) {
2857
2860
  const provider = String(session.provider || "unknown");
2858
2861
  const modelGroup = statsSessionPrimaryModel(session);
2859
2862
  const companyGroup = statsSessionCompany(session, provider, modelGroup);
@@ -3114,15 +3117,25 @@ function statsPayloadForSessions(sessions, options = {}) {
3114
3117
  const avgMessagesPerConversation =
3115
3118
  totalConversations > 0 ? totalMessages / totalConversations : 0;
3116
3119
  const splitStats = options.includeSplit === false ? null : {
3117
- agent: statsPayloadForSessions(sessions.filter((session) => statsSessionCategory(session) === "agent"), { includeSplit: false, category: "agent" }),
3118
- chat: statsPayloadForSessions(sessions.filter((session) => statsSessionCategory(session) === "chat"), { includeSplit: false, category: "chat" })
3120
+ agent: statsPayloadForSessions(allSessions.filter((session) => !isSdkStatsSession(session) && statsSessionCategory(session) === "agent"), { includeSplit: false, category: "agent" }),
3121
+ chat: statsPayloadForSessions(allSessions.filter((session) => !isSdkStatsSession(session) && statsSessionCategory(session) === "chat"), { includeSplit: false, category: "chat" }),
3122
+ sdk: statsPayloadForSessions(sdkSessions, { includeSplit: false, includeSdk: true, category: "sdk" })
3119
3123
  };
3124
+ const sdkStats = splitStats?.sdk || null;
3120
3125
  return {
3121
3126
  category: options.category || "all",
3122
3127
  generated_at: new Date().toISOString(),
3123
3128
  session_count: totalConversations,
3124
3129
  agent_session_count: splitStats ? splitStats.agent.session_count : undefined,
3125
3130
  chat_session_count: splitStats ? splitStats.chat.session_count : undefined,
3131
+ sdk_session_count: sdkStats ? sdkStats.session_count : undefined,
3132
+ sdk_message_count: sdkStats ? sdkStats.message_count : undefined,
3133
+ sdk_user_message_count: sdkStats ? sdkStats.user_message_count : undefined,
3134
+ sdk_total_tokens: sdkStats ? sdkStats.total_tokens : undefined,
3135
+ sdk_total_input_tokens: sdkStats ? sdkStats.total_input_tokens : undefined,
3136
+ sdk_total_output_tokens: sdkStats ? sdkStats.total_output_tokens : undefined,
3137
+ sdk_total_cache_tokens: sdkStats ? sdkStats.total_cache_tokens : undefined,
3138
+ sdk_total_estimated_tokens: sdkStats ? sdkStats.total_estimated_tokens : undefined,
3126
3139
  message_count: totalMessages,
3127
3140
  user_message_count: totalUserMessages,
3128
3141
  total_tokens: totalTokens,
@@ -3188,6 +3201,7 @@ function statsCompanyFromProvider(provider) {
3188
3201
  if (["claude_code", "claude_desktop", "claude_sdk", "claude_web"].includes(value)) return "anthropic";
3189
3202
  if (["gemini_cli", "antigravity"].includes(value)) return "google";
3190
3203
  if (value === "devin" || value === "windsurf") return "cognition";
3204
+ if (value === "cursor") return "cursor";
3191
3205
  return "unknown";
3192
3206
  }
3193
3207
 
@@ -3201,7 +3215,7 @@ function statsCompanyFromModel(model) {
3201
3215
  if (text.includes("devin") || text.includes("cognition")) return "cognition";
3202
3216
  if (text.includes("windsurf") || text.includes("swe-")) return "cognition";
3203
3217
  if (text.includes("antigravity")) return "google";
3204
- if (text.startsWith("composer-") || text.startsWith("cursor-") || text === "auto") return "cursor";
3218
+ if (text.startsWith("composer-") || text.startsWith("cursor-") || text === "auto" || text === "default") return "cursor";
3205
3219
  return "";
3206
3220
  }
3207
3221
 
@@ -3220,6 +3234,20 @@ function statsSessionCategory(session) {
3220
3234
  return provider === "chatgpt" || provider === "claude_web" ? "chat" : "agent";
3221
3235
  }
3222
3236
 
3237
+ const SDK_STATS_SOURCE_TYPES = new Set(["codex-sdk-history", "claude-sdk-history"]);
3238
+
3239
+ function isSdkStatsSession(session) {
3240
+ const provider = String(session?.provider || "").toLowerCase();
3241
+ const sourceType = String(session?.sourceType || session?.source_type || "").toLowerCase();
3242
+ return provider === "claude_sdk" || SDK_STATS_SOURCE_TYPES.has(sourceType);
3243
+ }
3244
+
3245
+ function statsFiltersTargetSdk(filters = {}) {
3246
+ const provider = String(filters.provider || filters.source || "").trim().toLowerCase().replace(/[-\s]+/g, "_");
3247
+ const sourceType = String(filters.sourceType || filters.source_type || "").trim().toLowerCase();
3248
+ return provider === "codex_sdk" || provider === "claude_sdk" || SDK_STATS_SOURCE_TYPES.has(sourceType);
3249
+ }
3250
+
3223
3251
  function isWebChatStatsProvider(provider) {
3224
3252
  return provider === "chatgpt" || provider === "claude_web";
3225
3253
  }
@@ -3512,14 +3540,14 @@ function serverCommand(flags, env) {
3512
3540
  }
3513
3541
 
3514
3542
  const RECALL_TARGET_SOURCE_MAP = {
3515
- codex: ["codex-cli", "codex-desktop"],
3543
+ codex: ["codex-cli", "codex-desktop", "codex-sdk"],
3516
3544
  claude: ["claude", "claude-code-desktop", "claude-workspace"],
3517
3545
  gemini: ["gemini-cli"],
3518
3546
  antigravity: ["antigravity"],
3519
3547
  devin: ["devin-cli"],
3520
3548
  cursor: ["cursor"],
3521
3549
  cline: ["cline"],
3522
- opencode: ["opencode"],
3550
+ opencode: ["opencode-cli", "opencode-desktop", "opencode-web"],
3523
3551
  aider: ["aider"]
3524
3552
  };
3525
3553
 
@@ -3600,6 +3628,9 @@ function normalizeRecallTarget(target) {
3600
3628
  "gemini-cli": "gemini",
3601
3629
  "devin-cli": "devin",
3602
3630
  "open-code": "opencode",
3631
+ "opencode-cli": "opencode",
3632
+ "opencode-desktop": "opencode",
3633
+ "opencode-web": "opencode",
3603
3634
  open_code: "opencode",
3604
3635
  clinecli: "cline",
3605
3636
  "cline-cli": "cline",
@@ -4033,7 +4064,7 @@ function recallArchiveHints() {
4033
4064
  return `- Sessions live under \`~/.agentlog/data/agentlog/sessions/repo=<repo-or-path-key>/provider=<provider>/year=YYYY/month=MM/day=DD/session=<session_id>.conversation.md\`.
4034
4065
  - Git repositories use canonical keys like \`github.com/org/repo\`. Non-git directories may use stable \`path:<hash>\` storage keys, but history results include \`repo_display\` and \`cwd\` with the readable local path.
4035
4066
  - When the user names a repo or folder, add \`--repo "<repo-or-path>"\`; it matches canonical repo keys, \`path:<hash>\`, web scopes, local \`cwd\`, and display labels, so local paths and path fragments work.
4036
- - Useful filters include \`--provider <provider>\`, \`--since 30d\`, and \`--repo "<repo-or-path>"\`. Provider aliases are ordered as OpenAI (\`codex-cli\`, \`codex-desktop\`, \`chatgpt\`), Anthropic (\`claude\`, \`claude-code-desktop\`, \`claude-workspace\`, \`claude-web\`, \`claude-sdk\`), Google (\`gemini-cli\`, \`antigravity\`), then other local tools (\`devin-cli\`, \`cursor\`, \`cline\`, \`opencode\`, \`aider\`).
4067
+ - Useful filters include \`--provider <provider>\`, \`--since 30d\`, and \`--repo "<repo-or-path>"\`. Provider aliases are ordered as OpenAI (\`codex-cli\`, \`codex-desktop\`, \`codex-sdk\`, \`chatgpt\`), Anthropic (\`claude\`, \`claude-code-desktop\`, \`claude-workspace\`, \`claude-web\`, \`claude-sdk\`), Google (\`gemini-cli\`, \`antigravity\`), then other local tools (\`devin-cli\`, \`cursor\`, \`cline\`, \`opencode\`, \`aider\`).
4037
4068
  - If the user is asking about the current repository, start without \`--repo\` unless results are noisy; current-repo matches are already weighted higher.`;
4038
4069
  }
4039
4070
 
@@ -4301,6 +4332,10 @@ function printDiscovery(label, result) {
4301
4332
 
4302
4333
  function printImportResults(results, options = {}) {
4303
4334
  for (const result of results) {
4335
+ if (result.instructions) {
4336
+ printWebExportInstructionBlock(result.instructions);
4337
+ continue;
4338
+ }
4304
4339
  const detailText = result.details ? formatDetails(result.details) : "";
4305
4340
  const details = detailText ? ` ${detailText}` : "";
4306
4341
  printCheck(
@@ -4322,6 +4357,37 @@ function printImportResults(results, options = {}) {
4322
4357
  }
4323
4358
  }
4324
4359
 
4360
+ function printWebExportInstructions(instructions, flags = {}) {
4361
+ if (!instructions) throw new Error("unknown web export instruction source");
4362
+ if (flags.json) {
4363
+ console.log(JSON.stringify({
4364
+ provider: instructions.provider,
4365
+ source: instructions.source,
4366
+ manual: true,
4367
+ instructions
4368
+ }, null, 2));
4369
+ return;
4370
+ }
4371
+ printPageTitle("agentlog import", `${instructions.source} export instructions`);
4372
+ printWebExportInstructionBlock(instructions);
4373
+ }
4374
+
4375
+ function printWebExportInstructionBlock(instructions) {
4376
+ printSection(`${instructions.label} Export`);
4377
+ printMuted(`Request and download the ${instructions.fileDescription}, then import it from disk.`);
4378
+ printCheck("Request page", instructions.requestUrl);
4379
+ printCheck("Help", instructions.helpUrl);
4380
+ printSection("Steps");
4381
+ (instructions.steps || []).forEach((step, index) => {
4382
+ console.log(` ${index + 1}. ${step}`);
4383
+ });
4384
+ if (instructions.notes?.length) {
4385
+ printSection("Notes");
4386
+ for (const note of instructions.notes) console.log(` - ${note}`);
4387
+ }
4388
+ printCommand("Import after download", instructions.importCommand);
4389
+ }
4390
+
4325
4391
  function printPageTitle(title, subtitle = "") {
4326
4392
  if (!process.stdout.isTTY) {
4327
4393
  console.log(title);
@@ -5085,6 +5151,14 @@ function importSourceOptions(discovered) {
5085
5151
  description: "Codex desktop app conversations from the local Codex state database.",
5086
5152
  defaultSelected: Boolean(discovered.codexDesktop?.sessions)
5087
5153
  },
5154
+ {
5155
+ source: "codex-sdk",
5156
+ label: "Codex SDK jobs",
5157
+ count: discovered.codexSdk?.sessions || 0,
5158
+ summary: sourceSummary(discovered.codexSdk),
5159
+ description: "Codex exec and SDK-style batch runs from the local Codex state database; disabled by default because volume can be high.",
5160
+ defaultSelected: false
5161
+ },
5088
5162
  {
5089
5163
  source: "claude",
5090
5164
  label: "Claude Code CLI",
@@ -5158,12 +5232,28 @@ function importSourceOptions(discovered) {
5158
5232
  defaultSelected: Boolean(discovered.cline?.sessions)
5159
5233
  },
5160
5234
  {
5161
- source: "opencode",
5162
- label: "OpenCode",
5163
- count: discovered.opencode?.sessions || 0,
5164
- summary: sourceSummary(discovered.opencode),
5165
- description: "OpenCode JSON session, message, part, and session_diff storage from ~/.local/share/opencode.",
5166
- defaultSelected: Boolean(discovered.opencode?.sessions)
5235
+ source: "opencode-cli",
5236
+ label: "OpenCode CLI",
5237
+ count: discovered.opencodeCli?.sessions || 0,
5238
+ summary: sourceSummary(discovered.opencodeCli),
5239
+ description: "OpenCode CLI/core SQLite database plus project JSON session, message, part, and session_diff storage from ~/.local/share/opencode.",
5240
+ defaultSelected: Boolean(discovered.opencodeCli?.sessions)
5241
+ },
5242
+ {
5243
+ source: "opencode-desktop",
5244
+ label: "OpenCode Desktop",
5245
+ count: discovered.opencodeDesktop?.sessions || 0,
5246
+ summary: sourceSummary(discovered.opencodeDesktop),
5247
+ description: "OpenCode Desktop app-specific SQLite database and app storage.",
5248
+ defaultSelected: Boolean(discovered.opencodeDesktop?.sessions)
5249
+ },
5250
+ {
5251
+ source: "opencode-web",
5252
+ label: "OpenCode Web",
5253
+ count: discovered.opencodeWeb?.sessions || 0,
5254
+ summary: sourceSummary(discovered.opencodeWeb),
5255
+ description: "OpenCode web sessions from the shared OpenCode SQLite store.",
5256
+ defaultSelected: Boolean(discovered.opencodeWeb?.sessions)
5167
5257
  },
5168
5258
  {
5169
5259
  source: "aider",
@@ -5587,6 +5677,12 @@ mark.search-match.search-match-current{background:#fde047;color:#422006;box-shad
5587
5677
  .unsupported-device-block:last-child{margin-bottom:0}
5588
5678
  .unsupported-device-block:before{content:"";width:6px;height:6px;border-radius:999px;background:#94a3b8;flex:0 0 auto}
5589
5679
  .context-card{margin:4px 0;border:1px solid #e5e7eb;border-left:3px solid #cbd5e1;border-radius:8px;background:#fff;color:#475569;overflow:hidden;transition:border-color .15s ease}
5680
+ .context-line{display:flex;align-items:center;gap:8px;min-height:28px;margin:2px 0;color:#64748b;font-size:12px;line-height:1.35}
5681
+ .context-line .context-glyph{width:22px;height:22px}
5682
+ .context-line .context-title{flex:0 0 auto}
5683
+ .context-line .context-meta{flex:1 1 auto}
5684
+ .context-line .context-end{margin-left:auto}
5685
+ .context-line .message-copy{opacity:1;width:22px;height:22px;color:#94a3b8}
5590
5686
  .session-summary-message{margin-bottom:14px}
5591
5687
  .session-summary-card{border-left-color:#D97757;background:#fff}
5592
5688
  .session-summary-body{padding:11px 13px 12px;color:#334155;font-size:13px;line-height:1.55;background:#fff}
@@ -5628,13 +5724,31 @@ mark.search-match.search-match-current{background:#fde047;color:#422006;box-shad
5628
5724
  .md-table{border-collapse:collapse;width:max-content;max-width:100%;font-size:13px}
5629
5725
  .md-table th,.md-table td{border:1px solid #e5e7eb;padding:5px 7px;text-align:left;vertical-align:top}
5630
5726
  .md-table th{background:#f8fafc;font-weight:600}
5631
- .tool-callout{display:flex;align-items:flex-start;gap:10px;margin:0;padding:8px 11px 9px 11px;border:1px solid #e5e7eb;border-radius:8px;background:#fff;color:#0f172a;transition:background .15s ease}
5632
- .tool-callout:hover{background:#fafbfc}
5633
- .tool-stack{display:grid;gap:5px;margin-top:4px}
5727
+ .tool-group-card{margin:0;border:0;background:transparent}
5728
+ .tool-group-card > summary{display:flex;align-items:center;gap:8px;min-height:28px;margin:0 0 5px;color:#64748b;font-size:13px;font-weight:600;line-height:1.35;cursor:pointer;list-style:none}
5729
+ .tool-group-card > summary::-webkit-details-marker{display:none}
5730
+ .tool-group-prefix{display:inline-flex;align-items:center;gap:7px;flex:0 0 auto}
5731
+ .tool-group-caret{width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:5px solid #94a3b8;transition:transform .15s ease}
5732
+ .tool-group-card[open] .tool-group-caret{transform:rotate(90deg)}
5733
+ .tool-group-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5734
+ .tool-group-card[open] > .tool-stack{gap:0;border:1px solid #e5e7eb;border-radius:8px;background:#fff;overflow:hidden}
5735
+ .tool-group-card[open] > .tool-stack > .tool-callout{border:0;border-radius:0;border-bottom:1px solid #eef2f7}
5736
+ .tool-group-card[open] > .tool-stack > .tool-callout:last-child{border-bottom:0}
5737
+ .tool-group-card[open] > .tool-stack > .tool-callout > summary{min-height:26px;padding:2px 8px}
5738
+ .tool-callout{display:block;margin:0;border:1px solid #e5e7eb;border-radius:8px;background:#fff;color:#0f172a;overflow:hidden;transition:border-color .15s ease,background .15s ease}
5739
+ .tool-callout > summary{display:flex;align-items:center;gap:7px;min-height:28px;padding:3px 8px;cursor:pointer;list-style:none;transition:background .12s ease}
5740
+ .tool-callout > summary::-webkit-details-marker{display:none}
5741
+ .tool-callout > summary:before{content:"";flex:0 0 auto;width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:5px solid #94a3b8;transition:transform .15s ease}
5742
+ .tool-callout[open] > summary:before{transform:rotate(90deg)}
5743
+ .tool-callout:hover,.tool-callout[open]{border-color:#dbe3ef}
5744
+ .tool-callout > summary:hover{background:#fafbfc}
5745
+ .tool-callout[open] > summary{border-bottom:1px solid #f1f5f9}
5746
+ .tool-stack{display:grid;gap:3px;margin-top:3px}
5634
5747
  .tool-stack:first-child{margin-top:0}
5635
5748
  .tool-body + .tool-stack{margin-top:8px}
5636
- .tool-glyph{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:26px;height:26px;border-radius:6px;background:#f1f5f9;color:#475569;border:0}
5637
- .tool-glyph svg{width:14px;height:14px}
5749
+ .tool-stack-heading{display:flex;align-items:center;min-height:24px;margin:0 0 1px;color:#64748b;font-size:12px;font-weight:600;line-height:1.35}
5750
+ .tool-glyph{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:22px;height:22px;border-radius:6px;background:#f1f5f9;color:#475569;border:0}
5751
+ .tool-glyph svg{width:13px;height:13px}
5638
5752
  .tool-callout.shell .tool-glyph{background:#f1f5f9;color:#334155}
5639
5753
  .tool-callout.edit .tool-glyph{background:#fef3c7;color:#92400e}
5640
5754
  .tool-callout.read .tool-glyph{background:#e0f2fe;color:#075985}
@@ -5643,16 +5757,21 @@ mark.search-match.search-match-current{background:#fde047;color:#422006;box-shad
5643
5757
  .tool-callout.task .tool-glyph{background:#dcfce7;color:#166534}
5644
5758
  .tool-callout.mcp .tool-glyph{background:#ffedd5;color:#9a3412}
5645
5759
  .tool-callout.skill .tool-glyph{background:#e0e7ff;color:#3730a3}
5646
- .tool-copy{display:grid;gap:3px;min-width:0;flex:1 1 auto}
5647
- .tool-title{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:13px;font-weight:600;line-height:1.3;color:#0f172a;letter-spacing:-0.005em}
5760
+ .tool-call-line{display:flex;align-items:baseline;gap:5px;min-width:0;flex:1 1 auto}
5761
+ .tool-action{flex:0 0 auto;color:#0f172a;font-size:12.5px;font-weight:600;line-height:1.3}
5762
+ .tool-subject{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#475569;font-size:12.5px;font-weight:500;line-height:1.3}
5763
+ .tool-callout.shell .tool-subject{font:12px/1.3 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#475569}
5764
+ .tool-callout-body{display:grid;gap:6px;min-width:0;padding:6px 8px 8px 12px;background:#fff}
5765
+ .tool-call-meta{display:flex;align-items:center;gap:5px;flex-wrap:wrap;min-width:0}
5648
5766
  .tool-chip{display:inline-flex;align-items:center;height:18px;padding:0 7px;border-radius:999px;background:#f1f5f9;color:#475569;border:0;font-size:11px;font-weight:500;letter-spacing:.01em}
5649
5767
  .tool-status{display:inline-flex;align-items:center;height:18px;border-radius:999px;padding:0 7px;background:#f1f5f9;color:#64748b;border:0;font-size:11px;font-weight:600}
5650
5768
  .tool-status.completed{background:#dcfce7;color:#166534}
5651
5769
  .tool-status.pending{background:#fef9c3;color:#854d0e}
5652
5770
  .tool-status.failed,.tool-status.error{background:#fee2e2;color:#991b1b}
5653
- .tool-detail{color:#475569;font-size:12px;line-height:1.4;overflow-wrap:anywhere}
5654
- .tool-target{display:block;margin-top:3px;color:#64748b;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}
5655
- .tool-preview{display:block;color:#1e293b;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}
5771
+ .tool-preview{display:block;margin:0;padding:6px 7px;border:1px solid #edf2f7;border-radius:7px;background:#fafbfc;color:#1e293b;overflow-x:hidden;overflow-y:visible;font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}
5772
+ .tool-paired-result{display:grid;gap:4px;min-width:0}
5773
+ .tool-result-meta{display:flex;align-items:center;gap:8px;min-width:0;color:#64748b;font-size:11px;font-weight:600}
5774
+ .tool-result-meta span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5656
5775
  .tool-diff{display:block;margin-top:6px;border:1px solid #e5e7eb;border-radius:6px;background:#fff;overflow:hidden;font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
5657
5776
  .tool-diff[open] .tool-diff-summary{border-bottom:1px solid #f1f5f9}
5658
5777
  .tool-diff-summary{display:flex;align-items:center;gap:8px;min-height:28px;padding:4px 10px;cursor:pointer;color:#475569;font-size:11px;font-weight:600;list-style:none;background:#fafbfc}
@@ -5661,24 +5780,28 @@ mark.search-match.search-match-current{background:#fde047;color:#422006;box-shad
5661
5780
  .tool-diff[open] .tool-diff-summary:before{transform:rotate(90deg)}
5662
5781
  .tool-diff-summary .add-count{color:#16a34a}
5663
5782
  .tool-diff-summary .del-count{color:#dc2626}
5664
- .tool-diff-body{display:block;max-height:360px;overflow:auto;background:#f8fafc}
5783
+ .tool-diff-body{display:block;overflow-x:hidden;overflow-y:visible;background:#f8fafc}
5665
5784
  .tool-diff-block{display:block}
5666
5785
  .tool-diff-block + .tool-diff-block{border-top:1px solid #e5e7eb;margin-top:4px;padding-top:4px}
5667
- .tool-diff-line{display:block;padding:0 10px;white-space:pre-wrap;overflow-wrap:anywhere;color:#0f172a}
5786
+ .tool-diff-line{display:block;padding:0 10px;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;color:#0f172a}
5668
5787
  .tool-diff-line.add{background:#e6ffec;color:#1a7f37}
5669
5788
  .tool-diff-line.del{background:#ffebe9;color:#a40e26}
5670
5789
  .tool-diff-line.ctx{color:#6b7280}
5671
5790
  .tool-diff-line.meta{color:#6b7280;font-weight:600;background:#f1f5f9}
5672
5791
  .tool-diff-line.hunk{color:#7c3aed;background:#faf5ff;font-weight:600}
5673
5792
  .tool-result{margin:0;border:1px solid #e5e7eb;border-radius:8px;background:#fff;overflow:hidden}
5793
+ .tool-result.inline{margin-top:1px;border-radius:7px;background:#f8fafc}
5674
5794
  .tool-result summary{display:flex;align-items:center;gap:8px;min-height:34px;padding:6px 10px 6px 11px;cursor:pointer;color:#0f172a;font-weight:600;list-style:none;transition:background .12s ease}
5795
+ .tool-result.inline summary{min-height:30px;padding:5px 9px;background:#fafbfc}
5675
5796
  .tool-result summary:hover{background:#fafbfc}
5797
+ .tool-result.inline summary:hover{background:#f1f5f9}
5676
5798
  .tool-result[open] summary{border-bottom:1px solid #f1f5f9}
5677
5799
  .tool-result summary::-webkit-details-marker{display:none}
5678
5800
  .tool-result summary:before{content:"";flex:0 0 auto;width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:5px solid #94a3b8;transition:transform .15s ease}
5679
5801
  .tool-result[open] summary:before{transform:rotate(90deg)}
5680
5802
  .tool-result-kind{display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:600;color:#0f172a;letter-spacing:-0.005em;flex:0 0 auto;white-space:nowrap}
5681
- .tool-result-kind .tool-glyph{width:22px;height:22px;border-radius:6px}
5803
+ .tool-result-kind .tool-glyph{width:20px;height:20px;border-radius:6px}
5804
+ .tool-result.inline .tool-result-kind .tool-glyph{width:20px;height:20px}
5682
5805
  .tool-result-kind .tool-glyph svg{width:12px;height:12px}
5683
5806
  .tool-result[data-category="shell"] .tool-result-kind .tool-glyph{background:#f1f5f9;color:#334155}
5684
5807
  .tool-result[data-category="edit"] .tool-result-kind .tool-glyph{background:#fef3c7;color:#92400e}
@@ -5689,7 +5812,12 @@ mark.search-match.search-match-current{background:#fde047;color:#422006;box-shad
5689
5812
  .tool-result[data-category="mcp"] .tool-result-kind .tool-glyph{background:#ffedd5;color:#9a3412}
5690
5813
  .tool-result-detail{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#64748b;font-size:12px;font-weight:500}
5691
5814
  .tool-result-count{margin-left:auto;flex:0 0 auto;color:#94a3b8;font-size:11px;font-weight:600;letter-spacing:.01em;white-space:nowrap}
5692
- .tool-output{max-width:100%;max-height:520px;margin:0;padding:10px 12px;background:#f8fafc;color:#0f172a;overflow:auto;font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre}
5815
+ .tool-output{max-width:100%;margin:0;padding:6px 7px;background:#f8fafc;color:#0f172a;overflow-x:hidden;overflow-y:visible;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}
5816
+ .tool-result.inline .tool-output{background:#fff}
5817
+ .tool-output-lines{display:grid;gap:0;padding:5px 0}
5818
+ .tool-output-line{display:grid;grid-template-columns:minmax(2.4em,max-content) minmax(0,1fr);gap:10px;min-width:0;padding:0 7px}
5819
+ .tool-line-number{color:#94a3b8;text-align:right;user-select:none}
5820
+ .tool-line-text{min-width:0;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}
5693
5821
  .skill-link{display:inline-flex;align-items:center;gap:4px;max-width:100%;vertical-align:middle;border:1px solid #c7d2fe;border-radius:999px;background:#eef2ff;color:#1e1b4b;padding:1px 7px 2px;font-size:.93em;line-height:1.35;white-space:nowrap}
5694
5822
  .skill-mark{font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#4338ca}
5695
5823
  .skill-name,.skill-path{min-width:0;overflow:hidden;text-overflow:ellipsis}
@@ -5730,6 +5858,7 @@ mark.search-match.search-match-current{background:#fde047;color:#422006;box-shad
5730
5858
  <button class="select-option active" type="button" data-value="">All sources</button>
5731
5859
  <button class="select-option" type="button" data-value="codex-cli">Codex CLI</button>
5732
5860
  <button class="select-option" type="button" data-value="codex-desktop">Codex Desktop</button>
5861
+ <button class="select-option" type="button" data-value="codex-sdk">Codex SDK jobs</button>
5733
5862
  <button class="select-option" type="button" data-value="chatgpt">ChatGPT</button>
5734
5863
  <button class="select-option" type="button" data-value="claude">Claude Code CLI</button>
5735
5864
  <button class="select-option" type="button" data-value="claude-code-desktop">Claude Code Desktop</button>
@@ -5741,7 +5870,9 @@ mark.search-match.search-match-current{background:#fde047;color:#422006;box-shad
5741
5870
  <button class="select-option" type="button" data-value="devin-cli">Devin CLI</button>
5742
5871
  <button class="select-option" type="button" data-value="cursor">Cursor</button>
5743
5872
  <button class="select-option" type="button" data-value="cline">Cline</button>
5744
- <button class="select-option" type="button" data-value="opencode">OpenCode</button>
5873
+ <button class="select-option" type="button" data-value="opencode-cli">OpenCode CLI</button>
5874
+ <button class="select-option" type="button" data-value="opencode-desktop">OpenCode Desktop</button>
5875
+ <button class="select-option" type="button" data-value="opencode-web">OpenCode Web</button>
5745
5876
  <button class="select-option" type="button" data-value="aider">Aider</button>
5746
5877
  </div>
5747
5878
  </div>
@@ -5857,6 +5988,11 @@ mark.search-match.search-match-current{background:#fde047;color:#422006;box-shad
5857
5988
  <div id="statsChatActivitySub" class="stats-card-meta" hidden></div>
5858
5989
  <div id="statsChatHeatmap" class="stats-heatmap-wrap"></div>
5859
5990
  </div>
5991
+ <div class="stats-card stats-card--compact">
5992
+ <div class="stats-card-title">SDK jobs</div>
5993
+ <div id="statsSdkActivitySub" class="stats-card-meta" hidden></div>
5994
+ <div id="statsSdkHeatmap" class="stats-heatmap-wrap"></div>
5995
+ </div>
5860
5996
  </div>
5861
5997
  <div class="stats-section">
5862
5998
  <div class="stats-section-head stats-section-head--breakdown">
@@ -6011,13 +6147,13 @@ function brandIconSvg(kind, className) {
6011
6147
 
6012
6148
  function brandKeyForSourceValue(value) {
6013
6149
  const key = String(value || '').trim().toLowerCase();
6014
- if (['codex-cli', 'codex-desktop', 'chatgpt'].includes(key)) return 'openai';
6150
+ if (['codex-cli', 'codex-desktop', 'codex-sdk', 'chatgpt'].includes(key)) return 'openai';
6015
6151
  if (['claude', 'claude-code-desktop', 'claude-workspace', 'claude-web', 'claude-sdk'].includes(key)) return 'claude';
6016
6152
  if (['gemini-cli', 'antigravity'].includes(key)) return 'gemini';
6017
6153
  if (key === 'devin-cli') return 'devin';
6018
6154
  if (key === 'cursor') return 'cursor';
6019
6155
  if (key === 'cline') return 'cline';
6020
- if (key === 'opencode') return 'opencode';
6156
+ if (['opencode-cli', 'opencode-desktop', 'opencode-web'].includes(key)) return 'opencode';
6021
6157
  return '';
6022
6158
  }
6023
6159
 
@@ -6268,6 +6404,21 @@ function providerLabel(provider) {
6268
6404
  return labels[provider] || provider || 'unknown';
6269
6405
  }
6270
6406
 
6407
+ function sourceTypeLabel(provider, sourceType) {
6408
+ const labels = {
6409
+ 'opencode-cli-history': 'OpenCode CLI',
6410
+ 'opencode-cli-sqlite-history': 'OpenCode CLI',
6411
+ 'opencode-desktop-history': 'OpenCode Desktop',
6412
+ 'opencode-desktop-sqlite-history': 'OpenCode Desktop',
6413
+ 'opencode-web-sqlite-history': 'OpenCode Web',
6414
+ 'opencode-history': 'OpenCode',
6415
+ 'opencode-sqlite-history': 'OpenCode'
6416
+ };
6417
+ if (labels[sourceType]) return labels[sourceType];
6418
+ const providerText = providerLabel(provider);
6419
+ return sourceType ? providerText + ' / ' + sourceType : providerText;
6420
+ }
6421
+
6271
6422
  const PROVIDER_COLORS = {
6272
6423
  codex: '#2563EB',
6273
6424
  chatgpt: '#10A37F',
@@ -6296,6 +6447,7 @@ const COMPANY_LABELS = {
6296
6447
  google: 'Google',
6297
6448
  cognition: 'Cognition',
6298
6449
  windsurf: 'Cognition',
6450
+ cursor: 'Cursor',
6299
6451
  stealth: 'Stealth',
6300
6452
  unknown: 'Unknown'
6301
6453
  };
@@ -6306,6 +6458,7 @@ const COMPANY_COLORS = {
6306
6458
  google: PROVIDER_COLORS.gemini_cli,
6307
6459
  cognition: PROVIDER_COLORS.devin,
6308
6460
  windsurf: PROVIDER_COLORS.devin,
6461
+ cursor: PROVIDER_COLORS.cursor,
6309
6462
  stealth: PROVIDER_COLORS.unknown,
6310
6463
  unknown: PROVIDER_COLORS.unknown
6311
6464
  };
@@ -6321,8 +6474,8 @@ function companyColor(company) {
6321
6474
  }
6322
6475
 
6323
6476
  const MODEL_COLOR_PALETTES = {
6324
- openai: { family: 'openai', light: '#7DD3FC', base: '#0284C7', dark: PROVIDER_COLORS.codex, darker: '#4F46E5', top: '#3730A3' },
6325
- claude: { family: 'claude', light: '#FDBA74', base: '#F97316', dark: PROVIDER_COLORS.claude_code, darker: '#B45309', top: '#7F1D1D' },
6477
+ openai: { family: 'openai', light: '#BAE6FD', base: '#06B6D4', dark: PROVIDER_COLORS.codex, darker: '#1D4ED8', top: '#172554' },
6478
+ claude: { family: 'claude', light: '#FED7AA', base: '#FB923C', dark: PROVIDER_COLORS.claude_code, darker: '#9A3412', top: '#831843' },
6326
6479
  gemini: { family: 'gemini', light: '#C4B5FD', base: PROVIDER_COLORS.gemini_cli, dark: '#7E22CE', darker: '#581C87' },
6327
6480
  devin: { family: 'devin', light: '#5EEAD4', base: PROVIDER_COLORS.devin, dark: '#0F766E', darker: '#115E59' },
6328
6481
  cursor: { family: 'cursor', light: '#475569', base: PROVIDER_COLORS.cursor, dark: '#020617', darker: '#020617' },
@@ -6370,6 +6523,9 @@ function modelCapabilityColor(text, palette) {
6370
6523
  }
6371
6524
 
6372
6525
  function openAiModelColor(text, palette) {
6526
+ if (text.includes('gpt-5.5') || text.includes('gpt-5-5')) return palette.top;
6527
+ if (text.includes('gpt-5.4') || text.includes('gpt-5-4')) return palette.darker;
6528
+ if (text.includes('gpt-5.3') || text.includes('gpt-5-3')) return palette.dark;
6373
6529
  if (text.includes('gpt-5.1') || text.includes('gpt-5-1')) return palette.top;
6374
6530
  if (text.includes('gpt-5') || modelHasToken(text, ['o3'])) return palette.darker;
6375
6531
  if (text.includes('gpt-4.1') || modelHasToken(text, ['o4'])) return palette.dark;
@@ -6378,10 +6534,12 @@ function openAiModelColor(text, palette) {
6378
6534
  }
6379
6535
 
6380
6536
  function claudeModelColor(text, palette) {
6381
- if (text.includes('high-thinking') || text.includes('high_thinking')) return palette.top;
6382
- if (modelHasToken(text, ['opus'])) return palette.darker;
6383
- if (modelHasToken(text, ['sonnet'])) return palette.dark;
6384
6537
  if (modelHasToken(text, ['haiku'])) return palette.light;
6538
+ if (text.includes('claude-4.6') && modelHasToken(text, ['opus'])) return palette.top;
6539
+ if (text.includes('claude-4.5') && modelHasToken(text, ['opus'])) return palette.darker;
6540
+ if (text.includes('high-thinking') || text.includes('high_thinking')) return palette.darker;
6541
+ if (modelHasToken(text, ['opus'])) return palette.dark;
6542
+ if (modelHasToken(text, ['sonnet'])) return palette.base;
6385
6543
  return palette.base;
6386
6544
  }
6387
6545
 
@@ -6390,6 +6548,91 @@ function modelHasToken(text, tokens) {
6390
6548
  return new RegExp('(^|[^a-z0-9])(' + escaped + ')([^a-z0-9]|$)').test(text);
6391
6549
  }
6392
6550
 
6551
+ const CANONICAL_COMPANY_ORDER = ['openai', 'anthropic', 'google', 'cognition', 'cursor', 'stealth', 'unknown'];
6552
+ const CANONICAL_PROVIDER_ORDER = ['codex', 'chatgpt', 'claude_code', 'claude_desktop', 'claude_sdk', 'claude_web', 'gemini_cli', 'antigravity', 'devin', 'windsurf', 'cursor', 'cline', 'opencode', 'aider', 'unknown'];
6553
+
6554
+ function canonicalCompanyRank(company) {
6555
+ const idx = CANONICAL_COMPANY_ORDER.indexOf(String(company || '').toLowerCase());
6556
+ return idx < 0 ? CANONICAL_COMPANY_ORDER.length : idx;
6557
+ }
6558
+
6559
+ function canonicalProviderRank(provider) {
6560
+ const idx = CANONICAL_PROVIDER_ORDER.indexOf(String(provider || '').toLowerCase());
6561
+ return idx < 0 ? CANONICAL_PROVIDER_ORDER.length : idx;
6562
+ }
6563
+
6564
+ function companyForModel(text) {
6565
+ const value = String(text || '').toLowerCase();
6566
+ if (value.startsWith('composer-') || value.startsWith('cursor-') || value === 'auto' || value === 'default') return 'cursor';
6567
+ const palette = modelFamilyPalette(value);
6568
+ if (!palette) return 'unknown';
6569
+ if (palette.family === 'openai') return 'openai';
6570
+ if (palette.family === 'claude') return 'anthropic';
6571
+ if (palette.family === 'gemini' || palette.family === 'antigravity') return 'google';
6572
+ if (palette.family === 'devin' || palette.family === 'windsurf') return 'cognition';
6573
+ if (palette.family === 'cursor') return 'cursor';
6574
+ return 'unknown';
6575
+ }
6576
+
6577
+ function canonicalModelRank(model) {
6578
+ const text = String(model || '').toLowerCase();
6579
+ const company = companyForModel(text);
6580
+ const companyRank = canonicalCompanyRank(company);
6581
+ let withinRank = 950;
6582
+ if (company === 'openai') {
6583
+ if (modelHasToken(text, ['nano'])) withinRank = 920;
6584
+ else if (modelHasToken(text, ['mini'])) withinRank = 900;
6585
+ else if (text.includes('gpt-4') && !text.includes('gpt-4.1')) withinRank = 700;
6586
+ else if (text.includes('gpt-4.1')) withinRank = 600;
6587
+ else if (modelHasToken(text, ['o1'])) withinRank = 590;
6588
+ else if (modelHasToken(text, ['o3', 'o4'])) withinRank = 580;
6589
+ else if (text.includes('gpt-5.5') || text.includes('gpt-5-5')) withinRank = 100;
6590
+ else if (text.includes('gpt-5.4') || text.includes('gpt-5-4')) withinRank = 200;
6591
+ else if (text.includes('gpt-5.3') || text.includes('gpt-5-3')) withinRank = 300;
6592
+ else if (text.includes('gpt-5.1') || text.includes('gpt-5-1')) withinRank = 400;
6593
+ else if (text.includes('gpt-5')) withinRank = 500;
6594
+ } else if (company === 'anthropic') {
6595
+ if (modelHasToken(text, ['haiku'])) withinRank = 920;
6596
+ else if (text.includes('opus-4-7') || text.includes('claude-4.7')) withinRank = 100;
6597
+ else if (text.includes('claude-4.6') && modelHasToken(text, ['opus'])) withinRank = 200;
6598
+ else if (text.includes('claude-4.5') && modelHasToken(text, ['opus'])) withinRank = 300;
6599
+ else if (text.includes('claude-4') && modelHasToken(text, ['opus'])) withinRank = 400;
6600
+ else if (modelHasToken(text, ['opus'])) withinRank = 500;
6601
+ else if (text.includes('claude-4.5') && modelHasToken(text, ['sonnet'])) withinRank = 600;
6602
+ else if (text.includes('claude-4') && modelHasToken(text, ['sonnet'])) withinRank = 700;
6603
+ else if (modelHasToken(text, ['sonnet'])) withinRank = 800;
6604
+ } else if (company === 'google') {
6605
+ // Major version dictates the bucket (newer first); flash variants get +50 within their generation.
6606
+ let base = 700;
6607
+ if (text.includes('gemini-3')) base = 100;
6608
+ else if (text.includes('gemini-2')) base = 300;
6609
+ else if (text.includes('gemini-1.5')) base = 500;
6610
+ else if (text.includes('gemini-1')) base = 600;
6611
+ let modifier = 0;
6612
+ if (text.includes('flash-lite')) modifier = 80;
6613
+ else if (text.includes('flash')) modifier = 50;
6614
+ withinRank = base + modifier;
6615
+ } else if (company === 'cursor') {
6616
+ if (text === 'composer-1.5') withinRank = 100;
6617
+ else if (text === 'composer-1') withinRank = 200;
6618
+ else if (text.startsWith('composer-')) withinRank = 300;
6619
+ else if (text === 'auto') withinRank = 700;
6620
+ else if (text === 'default') withinRank = 800;
6621
+ else if (text.startsWith('cursor-')) withinRank = 600;
6622
+ }
6623
+ return companyRank * 10000 + withinRank;
6624
+ }
6625
+
6626
+ function canonicalGroupRank(group) {
6627
+ if (statsBreakdownMode === 'model') return canonicalModelRank(group);
6628
+ if (statsBreakdownMode === 'company') return canonicalCompanyRank(group);
6629
+ return canonicalProviderRank(group);
6630
+ }
6631
+
6632
+ function statsCanonicalOrderedGroups(groups) {
6633
+ return [...groups].sort((a, b) => canonicalGroupRank(a) - canonicalGroupRank(b) || String(a).localeCompare(String(b)));
6634
+ }
6635
+
6393
6636
  function statsBreakdownLabel(group) {
6394
6637
  if (statsBreakdownMode === 'model') return modelLabel(group);
6395
6638
  if (statsBreakdownMode === 'company') return companyLabel(group);
@@ -6788,7 +7031,7 @@ function sessionNode(item, includeExcerpt) {
6788
7031
  button.type = 'button';
6789
7032
  button.dataset.id = item.session_id || '';
6790
7033
  const title = item.title || '(untitled session)';
6791
- const source = providerLabel(item.provider) + (item.source_type ? ' / ' + item.source_type : '');
7034
+ const source = sourceTypeLabel(item.provider, item.source_type);
6792
7035
  const scope = item.repo_display || item.repo || item.scope || 'unknown repo';
6793
7036
  const folderPath = searchResultFolderPath(item);
6794
7037
  const folderLabel = folderPath || scope;
@@ -7024,7 +7267,7 @@ function renderSession(payload) {
7024
7267
  }
7025
7268
  if (sessionMetaRight) {
7026
7269
  const innerParts = [];
7027
- if (payload.source_type) innerParts.push('<span class="meta-source">' + esc(payload.source_type) + '</span>');
7270
+ if (payload.source_type) innerParts.push('<span class="meta-source">' + esc(sourceTypeLabel(payload.provider, payload.source_type)) + '</span>');
7028
7271
  const repoRaw = payload.repo_display || payload.repo || payload.scope || '';
7029
7272
  if (repoRaw) {
7030
7273
  if (innerParts.length) innerParts.push('<span class="meta-dot" aria-hidden="true">\u00b7</span>');
@@ -7090,7 +7333,8 @@ function renderMessages(messages, sessionSummary) {
7090
7333
  const visibleMessages = summaryText
7091
7334
  ? (messages || []).filter((message) => message?.metadata?.summaryKind !== 'conversation_summary')
7092
7335
  : (messages || []);
7093
- if (!visibleMessages.length && !summaryText) {
7336
+ const renderItems = pairedToolRenderItems(visibleMessages);
7337
+ if (!renderItems.length && !summaryText) {
7094
7338
  readableView.className = 'inline-empty';
7095
7339
  readableView.textContent = 'No transcript messages are available for this session.';
7096
7340
  return;
@@ -7105,16 +7349,17 @@ function renderMessages(messages, sessionSummary) {
7105
7349
  const renderChunk = () => {
7106
7350
  if (renderSerial !== renderMessagesSerial) return;
7107
7351
  const fragment = document.createDocumentFragment();
7108
- const end = Math.min(visibleMessages.length, index + MESSAGE_RENDER_CHUNK_SIZE);
7352
+ const end = Math.min(renderItems.length, index + MESSAGE_RENDER_CHUNK_SIZE);
7109
7353
  for (; index < end; index++) {
7110
- const message = visibleMessages[index];
7354
+ const item = renderItems[index];
7355
+ const message = item.message;
7111
7356
  const gap = renderTimeGap(previousTimestamp, message.timestamp);
7112
7357
  if (gap) fragment.appendChild(gap);
7113
- fragment.appendChild(messageElement(message));
7114
- if (message.timestamp) previousTimestamp = message.timestamp;
7358
+ fragment.appendChild(messageElement(message, item));
7359
+ previousTimestamp = item.lastTimestamp || message.timestamp || previousTimestamp;
7115
7360
  }
7116
7361
  readableView.appendChild(fragment);
7117
- if (index < visibleMessages.length) {
7362
+ if (index < renderItems.length) {
7118
7363
  scheduleNext(renderChunk);
7119
7364
  return;
7120
7365
  }
@@ -7129,6 +7374,185 @@ function renderMessages(messages, sessionSummary) {
7129
7374
  renderChunk();
7130
7375
  }
7131
7376
 
7377
+ function pairedToolRenderItems(messages) {
7378
+ const pairings = pairedToolResultIndexes(messages);
7379
+ const skippedResultIndexes = new Set(pairings.duplicateResultIndexes);
7380
+ const result = [];
7381
+ for (let index = 0; index < messages.length; index++) {
7382
+ if (skippedResultIndexes.has(index)) continue;
7383
+ const message = messages[index];
7384
+ const calls = messageToolCalls(message);
7385
+ if (!calls.length) {
7386
+ result.push({ type: 'message', message });
7387
+ continue;
7388
+ }
7389
+ const pairedResults = (pairings.byCallMessage.get(index) || [])
7390
+ .filter((resultIndex) => !skippedResultIndexes.has(resultIndex))
7391
+ .map((resultIndex) => {
7392
+ skippedResultIndexes.add(resultIndex);
7393
+ return messages[resultIndex];
7394
+ });
7395
+ let next = index + 1;
7396
+ while (pairedResults.length < calls.length && next < messages.length && isPairableToolResultMessage(messages[next])) {
7397
+ if (skippedResultIndexes.has(next)) {
7398
+ next += 1;
7399
+ continue;
7400
+ }
7401
+ pairedResults.push(messages[next]);
7402
+ skippedResultIndexes.add(next);
7403
+ next += 1;
7404
+ }
7405
+ if (pairedResults.length) {
7406
+ result.push({
7407
+ type: 'message',
7408
+ message,
7409
+ pairedToolResults: pairedResults,
7410
+ lastTimestamp: pairedResults[pairedResults.length - 1].timestamp || message.timestamp
7411
+ });
7412
+ index = next - 1;
7413
+ } else {
7414
+ result.push({ type: 'message', message });
7415
+ }
7416
+ }
7417
+ return groupConsecutiveToolItems(result);
7418
+ }
7419
+
7420
+ function groupConsecutiveToolItems(items) {
7421
+ const grouped = [];
7422
+ let run = [];
7423
+ const flush = () => {
7424
+ if (run.length > 1) {
7425
+ grouped.push({
7426
+ type: 'tool-group',
7427
+ message: run[0].message,
7428
+ items: run,
7429
+ lastTimestamp: run[run.length - 1].lastTimestamp || run[run.length - 1].message.timestamp || run[0].message.timestamp
7430
+ });
7431
+ } else if (run.length === 1) {
7432
+ grouped.push(run[0]);
7433
+ }
7434
+ run = [];
7435
+ };
7436
+ for (const item of items) {
7437
+ if (!isToolActivityRenderItem(item)) {
7438
+ flush();
7439
+ grouped.push(item);
7440
+ continue;
7441
+ }
7442
+ run.push(item);
7443
+ }
7444
+ flush();
7445
+ return grouped;
7446
+ }
7447
+
7448
+ function isToolActivityRenderItem(item) {
7449
+ if (!item || item.type !== 'message') return false;
7450
+ const message = item.message || {};
7451
+ const role = String(message.role || '').toLowerCase();
7452
+ if (role === 'tool') return Boolean(parseToolResult(message.content || '', message));
7453
+ if (role !== 'assistant') return false;
7454
+ const calls = messageToolCalls(message, item.pairedToolResults || []);
7455
+ if (!calls.length) return false;
7456
+ return !stripToolInvocationLines(message.content || '').trim();
7457
+ }
7458
+
7459
+ function pairedToolResultIndexes(messages) {
7460
+ const calls = [];
7461
+ const callsByEventId = new Map();
7462
+ const callsById = new Map();
7463
+ const callsByKind = new Map();
7464
+ const addCallKey = (map, key, callIndex) => {
7465
+ if (!key) return;
7466
+ if (!map.has(key)) map.set(key, []);
7467
+ map.get(key).push(callIndex);
7468
+ };
7469
+ for (let messagePosition = 0; messagePosition < messages.length; messagePosition++) {
7470
+ const toolCalls = messageToolCalls(messages[messagePosition]);
7471
+ for (const call of toolCalls) {
7472
+ const callIndex = calls.length;
7473
+ calls.push({ messagePosition, call });
7474
+ addCallKey(callsByEventId, call.eventId || '', callIndex);
7475
+ addCallKey(callsById, normalizedToolId(call.id), callIndex);
7476
+ addCallKey(callsByKind, normalizeToolToken(call.kind || call.name || call.title || ''), callIndex);
7477
+ }
7478
+ }
7479
+
7480
+ const resultRecords = preferredToolResultRecords(messages);
7481
+ const byCallMessage = new Map();
7482
+ const usedCalls = new Set();
7483
+ for (const record of resultRecords.records) {
7484
+ const parentEventId = record.event?.parentEventId || '';
7485
+ let callIndex = firstUnusedIndex(callsByEventId.get(parentEventId), usedCalls);
7486
+ if (callIndex < 0) callIndex = firstUnusedIndex(callsById.get(normalizedToolId(record.result.id)), usedCalls);
7487
+ if (callIndex < 0) callIndex = firstUnusedIndex(callsByKind.get(normalizeToolToken(record.result.name || record.result.kind || record.result.title || '')), usedCalls);
7488
+ if (callIndex < 0) continue;
7489
+ usedCalls.add(callIndex);
7490
+ const messagePosition = calls[callIndex].messagePosition;
7491
+ if (!byCallMessage.has(messagePosition)) byCallMessage.set(messagePosition, []);
7492
+ byCallMessage.get(messagePosition).push(record.index);
7493
+ }
7494
+ return { byCallMessage, duplicateResultIndexes: resultRecords.duplicateResultIndexes };
7495
+ }
7496
+
7497
+ function preferredToolResultRecords(messages) {
7498
+ const records = [];
7499
+ const byId = new Map();
7500
+ const duplicateResultIndexes = new Set();
7501
+ for (let index = 0; index < messages.length; index++) {
7502
+ if (!isPairableToolResultMessage(messages[index])) continue;
7503
+ const result = parseToolResult(messages[index]?.content || '', messages[index]);
7504
+ if (!result) continue;
7505
+ const event = canonicalEventsForMessage(messages[index], 'tool.completed')[0] || null;
7506
+ const record = { index, result, event };
7507
+ records.push(record);
7508
+ const id = normalizedToolId(result.id);
7509
+ if (!id) continue;
7510
+ const existing = byId.get(id);
7511
+ if (!existing) {
7512
+ byId.set(id, record);
7513
+ continue;
7514
+ }
7515
+ const preferred = preferredToolResultRecord(existing, record);
7516
+ const discarded = preferred === existing ? record : existing;
7517
+ duplicateResultIndexes.add(discarded.index);
7518
+ byId.set(id, preferred);
7519
+ }
7520
+ return {
7521
+ records: records.filter((record) => !duplicateResultIndexes.has(record.index)),
7522
+ duplicateResultIndexes
7523
+ };
7524
+ }
7525
+
7526
+ function preferredToolResultRecord(left, right) {
7527
+ return toolResultDisplayScore(right.result) > toolResultDisplayScore(left.result) ? right : left;
7528
+ }
7529
+
7530
+ function toolResultDisplayScore(result) {
7531
+ const rawCategory = normalizeToolToken(result?.rawCategory || '');
7532
+ const kind = normalizeToolToken(result?.kind || '');
7533
+ const category = normalizeToolToken(result?.category || '');
7534
+ let score = 0;
7535
+ if (['exec_command_end', 'patch_apply_end', 'mcp_tool_call_end', 'web_search_end', 'tool_result', 'tool_output'].includes(rawCategory)) score += 2000;
7536
+ if (rawCategory === 'function_call_output' || rawCategory === 'custom_tool_call_output') score -= 1000;
7537
+ if (category && category !== 'function') score += 250;
7538
+ if (kind && !kind.startsWith('call_')) score += 150;
7539
+ if (/^\$\s/.test(String(result?.detail || result?.output || ''))) score += 250;
7540
+ score += Math.min(200, String(result?.output || '').length / 200);
7541
+ return score;
7542
+ }
7543
+
7544
+ function firstUnusedIndex(indexes, used) {
7545
+ if (!Array.isArray(indexes)) return -1;
7546
+ for (const index of indexes) {
7547
+ if (!used.has(index)) return index;
7548
+ }
7549
+ return -1;
7550
+ }
7551
+
7552
+ function isPairableToolResultMessage(message) {
7553
+ return String(message?.role || '').toLowerCase() === 'tool' && Boolean(parseToolResult(message?.content || '', message));
7554
+ }
7555
+
7132
7556
  function sessionSummaryText(sessionSummary) {
7133
7557
  if (!sessionSummary || typeof sessionSummary !== 'object') return '';
7134
7558
  return String(
@@ -7177,11 +7601,7 @@ function summarySourceLabel(source) {
7177
7601
  }
7178
7602
 
7179
7603
  function renderTimeGap(previous, current) {
7180
- if (!previous || !current) return null;
7181
- const prev = new Date(previous).getTime();
7182
- const next = new Date(current).getTime();
7183
- if (!Number.isFinite(prev) || !Number.isFinite(next)) return null;
7184
- const diffSec = (next - prev) / 1000;
7604
+ const diffSec = timeGapSeconds(previous, current);
7185
7605
  if (diffSec < 5) return null;
7186
7606
  const label = formatGapLabel(diffSec);
7187
7607
  if (!label) return null;
@@ -7191,6 +7611,14 @@ function renderTimeGap(previous, current) {
7191
7611
  return node;
7192
7612
  }
7193
7613
 
7614
+ function timeGapSeconds(previous, current) {
7615
+ if (!previous || !current) return 0;
7616
+ const prev = new Date(previous).getTime();
7617
+ const next = new Date(current).getTime();
7618
+ if (!Number.isFinite(prev) || !Number.isFinite(next)) return 0;
7619
+ return Math.max(0, (next - prev) / 1000);
7620
+ }
7621
+
7194
7622
  function formatGapLabel(diffSec) {
7195
7623
  if (diffSec < 60) return Math.round(diffSec) + 's';
7196
7624
  if (diffSec < 3600) return Math.round(diffSec / 60) + 'm';
@@ -7263,12 +7691,13 @@ function highlightSearchMatches(root, term) {
7263
7691
  walk(root);
7264
7692
  }
7265
7693
 
7266
- function messageElement(message) {
7694
+ function messageElement(message, options = {}) {
7695
+ if (options.type === 'tool-group') return toolGroupElement(options);
7267
7696
  const context = generatedContextForMessage(message);
7268
7697
  if (context) return contextMessageElement(message, context);
7269
7698
  const role = String(message.role || 'unknown').toLowerCase();
7270
7699
  const content = String(message.content || '');
7271
- const toolCalls = messageToolCalls(message);
7700
+ const toolCalls = messageToolCalls(message, options.pairedToolResults || []);
7272
7701
  const toolResult = role === 'tool' ? parseToolResult(content, message) : null;
7273
7702
  const contentWithoutTools = toolCalls.length ? stripToolInvocationLines(content) : content;
7274
7703
  const toolOnly = toolCalls.length && !contentWithoutTools.trim();
@@ -7307,7 +7736,22 @@ function messageElement(message) {
7307
7736
  return row;
7308
7737
  }
7309
7738
 
7739
+ function toolGroupElement(group) {
7740
+ const calls = [];
7741
+ for (const item of group.items || []) {
7742
+ calls.push(...toolCardsForRenderItem(item));
7743
+ }
7744
+ const row = document.createElement('article');
7745
+ row.className = 'message tool tool-call-turn tool-group-turn';
7746
+ const bubble = document.createElement('div');
7747
+ bubble.className = 'bubble';
7748
+ bubble.innerHTML = renderToolGroupCard(calls);
7749
+ row.appendChild(bubble);
7750
+ return row;
7751
+ }
7752
+
7310
7753
  function contextMessageElement(message, context) {
7754
+ if (context.kind === 'task_notification') return contextLineElement(message, context);
7311
7755
  const row = document.createElement('article');
7312
7756
  row.className = 'message context context-' + escClass(context.kind || 'metadata');
7313
7757
  const bubble = document.createElement('div');
@@ -7332,6 +7776,25 @@ function contextMessageElement(message, context) {
7332
7776
  return row;
7333
7777
  }
7334
7778
 
7779
+ function contextLineElement(message, context) {
7780
+ const row = document.createElement('article');
7781
+ row.className = 'message context context-line-turn context-' + escClass(context.kind || 'metadata');
7782
+ const bubble = document.createElement('div');
7783
+ bubble.className = 'bubble';
7784
+ const line = document.createElement('div');
7785
+ line.className = 'context-line';
7786
+ line.innerHTML =
7787
+ '<span class="context-glyph">' + contextIconSvg(context.kind) + '</span>' +
7788
+ '<span class="context-title">' + esc(contextTitle(context.kind)) + '</span>' +
7789
+ '<span class="context-meta">' + context.chips.map((chip) => '<span class="context-chip">' + esc(chip) + '</span>').join('') + '</span>' +
7790
+ '<span class="context-end"><span class="context-time"' + timeAttr(message.timestamp) + '>' + esc(relativeTime(message.timestamp)) + '</span></span>';
7791
+ const end = line.querySelector('.context-end');
7792
+ if (end) end.appendChild(messageCopyButton(message.content || ''));
7793
+ bubble.appendChild(line);
7794
+ row.appendChild(bubble);
7795
+ return row;
7796
+ }
7797
+
7335
7798
  function generatedContextForMessage(message) {
7336
7799
  const metadata = message?.metadata || {};
7337
7800
  const content = String(message?.content || '').trim();
@@ -7456,10 +7919,55 @@ function xmlTagText(text, tag) {
7456
7919
 
7457
7920
  function renderMessageBodyWithTools(className, content, toolCalls) {
7458
7921
  const body = className === 'user' ? renderPlainText(content) : renderRichText(content);
7459
- const stack = toolCalls.length ? '<div class="tool-stack">' + toolCalls.map(renderToolCallout).join('') + '</div>' : '';
7922
+ const stack = toolCalls.length > 1
7923
+ ? renderToolGroupCard(toolCalls)
7924
+ : toolCalls.length
7925
+ ? '<div class="tool-stack">' + toolCalls.map(renderToolCallout).join('') + '</div>'
7926
+ : '';
7460
7927
  return body ? '<div class="tool-body">' + body + '</div>' + stack : stack;
7461
7928
  }
7462
7929
 
7930
+ function renderToolGroupCard(calls) {
7931
+ const tools = Array.isArray(calls) ? calls : [];
7932
+ if (!tools.length) return '';
7933
+ return '<details class="tool-group-card">' +
7934
+ '<summary><span class="tool-group-prefix"><span class="tool-group-caret"></span><span class="tool-glyph">' +
7935
+ renderToolIcon({ category: dominantToolCategory(tools), kind: 'Tool activity' }) +
7936
+ '</span></span><span class="tool-group-title">' +
7937
+ esc(toolStackSummary(tools) || 'Used ' + tools.length + ' tools') +
7938
+ '</span></summary>' +
7939
+ '<div class="tool-stack">' + tools.map(renderToolCallout).join('') + '</div></details>';
7940
+ }
7941
+
7942
+ function toolCardsForRenderItem(item) {
7943
+ const message = item?.message || {};
7944
+ if (String(message.role || '').toLowerCase() === 'tool') {
7945
+ const result = parseToolResult(message.content || '', message);
7946
+ return result ? [toolCardFromResult(result)] : [];
7947
+ }
7948
+ return messageToolCalls(message, item?.pairedToolResults || []);
7949
+ }
7950
+
7951
+ function toolCardFromResult(result) {
7952
+ const category = normalizedToolCategory(result.category, result.kind);
7953
+ const detail = result.detail || result.summary || firstLine(result.output || '') || result.kind || 'Tool output';
7954
+ return toolCard({
7955
+ kind: result.name || result.kind || 'Tool output',
7956
+ title: result.kind || result.title || '',
7957
+ category,
7958
+ categoryLabel: result.categoryLabel || toolCategoryLabel(category),
7959
+ icon: result.icon || toolIcon(category, result.kind || ''),
7960
+ status: result.status || 'completed',
7961
+ argument: detail,
7962
+ rawInputSummary: detail,
7963
+ inputPreview: '',
7964
+ target: result.target || '',
7965
+ id: result.id || '',
7966
+ result,
7967
+ resultOnly: true
7968
+ });
7969
+ }
7970
+
7463
7971
  function copyIconSvg() {
7464
7972
  return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>';
7465
7973
  }
@@ -7508,13 +8016,13 @@ function detectToolName(text) {
7508
8016
  return tool ? tool.label : '';
7509
8017
  }
7510
8018
 
7511
- function messageToolCalls(message) {
8019
+ function messageToolCalls(message, pairedResultMessages) {
7512
8020
  const eventCalls = canonicalEventsForMessage(message, 'tool.called').map(toolCallFromEvent).filter(Boolean);
7513
- if (eventCalls.length) return eventCalls;
8021
+ if (eventCalls.length) return attachPairedToolResults(eventCalls, pairedResultMessages);
7514
8022
  const metadataCalls = Array.isArray(message?.metadata?.toolCalls) ? message.metadata.toolCalls : [];
7515
8023
  const textCalls = toolInvocationsFromText(message?.content || '');
7516
8024
  if (metadataCalls.length) {
7517
- return metadataCalls.map((meta, index) => {
8025
+ const calls = metadataCalls.map((meta, index) => {
7518
8026
  const text = textCalls[index] || {};
7519
8027
  const kind = meta.displayName || meta.name || text.kind || 'Tool';
7520
8028
  const argument = meta.argument || meta.rawInputSummary || text.argument || '';
@@ -7530,10 +8038,11 @@ function messageToolCalls(message) {
7530
8038
  categoryLabel: meta.categoryLabel || '',
7531
8039
  icon: meta.icon || '',
7532
8040
  rawCategory: meta.rawCategory || '',
7533
- id: meta.id || '',
8041
+ id: meta.id || meta.callId || meta.call_id || meta.toolCallId || meta.tool_call_id || meta.toolUseId || meta.tool_use_id || '',
7534
8042
  arguments: meta.arguments || null
7535
8043
  });
7536
8044
  }).filter((call) => call.kind || call.title || call.argument);
8045
+ return attachPairedToolResults(calls, pairedResultMessages);
7537
8046
  }
7538
8047
  const total = Math.max(metadataCalls.length, textCalls.length);
7539
8048
  const calls = [];
@@ -7553,11 +8062,67 @@ function messageToolCalls(message) {
7553
8062
  categoryLabel: meta.categoryLabel || '',
7554
8063
  icon: meta.icon || '',
7555
8064
  rawCategory: meta.rawCategory || '',
7556
- id: meta.id || '',
8065
+ id: meta.id || meta.callId || meta.call_id || meta.toolCallId || meta.tool_call_id || meta.toolUseId || meta.tool_use_id || '',
7557
8066
  arguments: meta.arguments || null
7558
8067
  }));
7559
8068
  }
7560
- return calls.filter((call) => call.kind || call.title || call.argument);
8069
+ return attachPairedToolResults(calls.filter((call) => call.kind || call.title || call.argument), pairedResultMessages);
8070
+ }
8071
+
8072
+ function attachPairedToolResults(calls, pairedResultMessages) {
8073
+ if (!Array.isArray(pairedResultMessages) || !pairedResultMessages.length) return calls;
8074
+ const results = pairedResultMessages
8075
+ .map((message) => parseToolResult(message?.content || '', message))
8076
+ .filter(Boolean);
8077
+ if (!results.length) return calls;
8078
+ const remaining = results.slice();
8079
+ return calls.map((call) => {
8080
+ const matchIndex = pairedToolResultIndex(call, remaining);
8081
+ if (matchIndex < 0) return call;
8082
+ const [result] = remaining.splice(matchIndex, 1);
8083
+ return { ...call, result };
8084
+ });
8085
+ }
8086
+
8087
+ function pairedToolResultIndex(call, results) {
8088
+ const callId = normalizedToolId(call?.id);
8089
+ if (callId) {
8090
+ const idMatch = results.findIndex((result) => normalizedToolId(result?.id) === callId);
8091
+ if (idMatch >= 0) return idMatch;
8092
+ }
8093
+ const callKind = normalizeToolToken(call?.kind || call?.name || call?.title || '');
8094
+ if (callKind) {
8095
+ const kindMatch = results.findIndex((result) => normalizeToolToken(result?.name || result?.kind || result?.title || '') === callKind);
8096
+ if (kindMatch >= 0) return kindMatch;
8097
+ }
8098
+ return results.length ? 0 : -1;
8099
+ }
8100
+
8101
+ function toolStackSummary(tools) {
8102
+ const counts = {};
8103
+ for (const tool of tools) {
8104
+ const category = normalizedToolCategory(tool.category, tool.kind);
8105
+ counts[category] = (counts[category] || 0) + 1;
8106
+ }
8107
+ const parts = [];
8108
+ if (counts.read) parts.push('explored ' + counts.read + ' ' + (counts.read === 1 ? 'file' : 'files'));
8109
+ if (counts.search) parts.push('searched ' + counts.search + ' ' + (counts.search === 1 ? 'time' : 'times'));
8110
+ if (counts.shell) parts.push('ran ' + counts.shell + ' ' + (counts.shell === 1 ? 'command' : 'commands'));
8111
+ if (counts.edit) parts.push('edited ' + counts.edit + ' ' + (counts.edit === 1 ? 'file' : 'files'));
8112
+ const known = ['read', 'search', 'shell', 'edit'];
8113
+ const other = Object.entries(counts).filter(([key]) => !known.includes(key)).reduce((sum, [, count]) => sum + count, 0);
8114
+ if (other) parts.push('used ' + other + ' ' + (other === 1 ? 'tool' : 'tools'));
8115
+ const text = parts.join(', ');
8116
+ return text ? text.charAt(0).toUpperCase() + text.slice(1) : '';
8117
+ }
8118
+
8119
+ function dominantToolCategory(tools) {
8120
+ const counts = {};
8121
+ for (const tool of tools || []) {
8122
+ const category = normalizedToolCategory(tool.category, tool.kind);
8123
+ counts[category] = (counts[category] || 0) + 1;
8124
+ }
8125
+ return Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] || 'tool';
7561
8126
  }
7562
8127
 
7563
8128
  function toolCallFromEvent(event) {
@@ -7576,7 +8141,8 @@ function toolCallFromEvent(event) {
7576
8141
  categoryLabel: meta.categoryLabel || '',
7577
8142
  icon: meta.icon || event.indexed?.toolIcon || '',
7578
8143
  rawCategory: meta.rawCategory || '',
7579
- id: meta.id || '',
8144
+ id: meta.id || meta.callId || meta.call_id || meta.toolCallId || meta.tool_call_id || meta.toolUseId || meta.tool_use_id || '',
8145
+ eventId: event.eventId || '',
7580
8146
  arguments: meta.arguments || null
7581
8147
  });
7582
8148
  }
@@ -7643,6 +8209,10 @@ function normalizeToolToken(value) {
7643
8209
  return String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
7644
8210
  }
7645
8211
 
8212
+ function normalizedToolId(value) {
8213
+ return String(value || '').trim().toLowerCase();
8214
+ }
8215
+
7646
8216
  function toolCategoryLabel(category) {
7647
8217
  const labels = {
7648
8218
  shell: 'Shell',
@@ -7671,18 +8241,92 @@ function escClass(value) {
7671
8241
  function renderToolCallout(tool) {
7672
8242
  const card = toolCard(tool);
7673
8243
  const isSkill = card.category === 'skill' || card.kind === 'Skill';
7674
- const title = card.title || (isSkill ? 'Skill loaded' : card.kind);
7675
- const category = card.category ? '<span class="tool-chip">' + esc(card.categoryLabel || card.category) + '</span>' : '';
7676
- const statusClass = humanToolStatusClass(card.status);
7677
- const status = card.status ? '<span class="tool-status' + (statusClass ? ' ' + statusClass : '') + '">' + esc(humanToolStatus(card.status)) + '</span>' : '';
7678
- const target = card.target ? '<span class="tool-target">' + esc(card.target) + '</span>' : '';
7679
8244
  const diff = renderToolDiff(card);
7680
- const preview = diff ? '' : '<span class="tool-preview">' + esc(card.inputPreview || card.argument || card.kind) + '</span>';
7681
- const detail = preview + target + diff;
7682
- return '<div class="tool-callout ' + escClass(card.category || 'tool') + (isSkill ? ' skill' : '') + '">' +
7683
- '<span class="tool-glyph">' + renderToolIcon(card) + '</span>' +
7684
- '<div class="tool-copy"><div class="tool-title">' + esc(title) + category + status + '</div>' +
7685
- '<div class="tool-detail">' + detail + '</div></div></div>';
8245
+ const category = normalizedToolCategory(card.category, card.kind);
8246
+ return '<details class="tool-callout ' + escClass(category) + (isSkill ? ' skill' : '') + (card.result ? ' has-result' : '') + '" data-category="' + esc(escClass(category)) + '">' +
8247
+ renderToolActivitySummary(card) +
8248
+ renderToolCalloutBody(card, diff) +
8249
+ '</details>';
8250
+ }
8251
+
8252
+ function renderToolActivitySummary(card) {
8253
+ const label = toolActivityLabel(card);
8254
+ const count = card.result?.count || '';
8255
+ const statusClass = humanToolStatusClass(card.status);
8256
+ const status = !card.result && card.status ? '<span class="tool-status' + (statusClass ? ' ' + statusClass : '') + '">' + esc(humanToolStatus(card.status)) + '</span>' : '';
8257
+ return '<summary><span class="tool-glyph">' + renderToolIcon(card) + '</span>' +
8258
+ '<span class="tool-call-line"><span class="tool-action">' + esc(label.action) + '</span>' +
8259
+ (label.subject ? '<span class="tool-subject">' + esc(label.subject) + '</span>' : '') +
8260
+ '</span>' + status +
8261
+ (count ? '<span class="tool-result-count">' + esc(count) + '</span>' : '') +
8262
+ '</summary>';
8263
+ }
8264
+
8265
+ function renderToolCalloutBody(card, diff) {
8266
+ const rows = [];
8267
+ const meta = [];
8268
+ if (card.categoryLabel) meta.push(card.categoryLabel);
8269
+ if (card.status) meta.push(humanToolStatus(card.status));
8270
+ if (card.target) meta.push(card.target);
8271
+ if (meta.length) rows.push('<div class="tool-call-meta">' + meta.map((item) => '<span class="tool-chip">' + esc(item) + '</span>').join('') + '</div>');
8272
+ const preview = card.resultOnly ? '' : String(card.inputPreview || card.argument || '').trim();
8273
+ if (preview) rows.push('<pre class="tool-preview">' + esc(preview) + '</pre>');
8274
+ if (diff) rows.push(diff);
8275
+ if (card.result) rows.push(renderPairedToolResult(card.result));
8276
+ return rows.length ? '<div class="tool-callout-body">' + rows.join('') + '</div>' : '';
8277
+ }
8278
+
8279
+ function renderPairedToolResult(result) {
8280
+ const category = normalizedToolCategory(result.category, result.kind);
8281
+ const meta = [result.kind, result.detail, result.count].filter(Boolean);
8282
+ return '<div class="tool-paired-result" data-category="' + esc(escClass(category)) + '">' +
8283
+ (meta.length ? '<div class="tool-result-meta">' + meta.map((item) => '<span>' + esc(item) + '</span>').join('') + '</div>' : '') +
8284
+ renderToolOutput(result.output || '', { lineStart: result.lineStart }) + '</div>';
8285
+ }
8286
+
8287
+ function toolActivityLabel(card) {
8288
+ const category = normalizedToolCategory(card.category, card.kind);
8289
+ const fallback = toolSubjectFallback(card);
8290
+ if (category === 'shell') return { action: 'Ran', subject: shellCommandText(card) || fallback };
8291
+ if (category === 'read') return { action: 'Read', subject: compactPathLabel(card.target || fallback) };
8292
+ if (category === 'search') return { action: 'Searched', subject: fallback };
8293
+ if (category === 'edit') return { action: 'Edited', subject: compactPathLabel(card.target || fallback) };
8294
+ if (category === 'web') return { action: webToolVerb(card), subject: fallback };
8295
+ if (category === 'task') return { action: 'Ran', subject: fallback };
8296
+ if (category === 'skill') return { action: 'Loaded', subject: fallback };
8297
+ if (category === 'mcp') return { action: 'Called', subject: fallback };
8298
+ return { action: card.title || card.kind || 'Used', subject: fallback && fallback !== (card.title || card.kind) ? fallback : '' };
8299
+ }
8300
+
8301
+ function shellCommandText(card) {
8302
+ const args = card.arguments && typeof card.arguments === 'object' && !Array.isArray(card.arguments) ? card.arguments : {};
8303
+ return firstToolString(args.cmd, args.command, args.script, card.inputPreview, card.argument);
8304
+ }
8305
+
8306
+ function webToolVerb(card) {
8307
+ const key = normalizeToolToken(card.kind || card.title || '');
8308
+ if (key.includes('fetch')) return 'Fetched';
8309
+ if (key.includes('search')) return 'Searched';
8310
+ if (key.includes('open')) return 'Opened';
8311
+ return 'Opened';
8312
+ }
8313
+
8314
+ function toolSubjectFallback(card) {
8315
+ return firstToolString(card.target, card.inputPreview, card.argument, card.title, card.kind);
8316
+ }
8317
+
8318
+ function compactPathLabel(value) {
8319
+ const text = String(value || '').trim();
8320
+ if (!text || /\s/.test(text) || !text.includes('/')) return text;
8321
+ return text.split('/').filter(Boolean).pop() || text;
8322
+ }
8323
+
8324
+ function firstToolString() {
8325
+ for (const value of arguments) {
8326
+ if (typeof value === 'string' && value.trim()) return value.trim();
8327
+ if (Array.isArray(value) && value.length) return value.map((item) => String(item || '').trim()).filter(Boolean).join(' ');
8328
+ }
8329
+ return '';
7686
8330
  }
7687
8331
 
7688
8332
  function hasPatchMarker(value) {
@@ -7779,7 +8423,7 @@ function renderToolDiff(card) {
7779
8423
  if (adds) summaryParts.push('<span class="add-count">+' + adds + '</span>');
7780
8424
  if (dels) summaryParts.push('<span class="del-count">-' + dels + '</span>');
7781
8425
  const summaryLabel = editCount > 1 ? editCount + ' edits' : label;
7782
- return '<details class="tool-diff" open>' +
8426
+ return '<details class="tool-diff">' +
7783
8427
  '<summary class="tool-diff-summary">' + esc(summaryLabel) + summaryParts.join('') + '</summary>' +
7784
8428
  '<div class="tool-diff-body">' + blocks.join('') + '</div>' +
7785
8429
  '</details>';
@@ -7927,6 +8571,9 @@ function toolResultFromMetadata(result, indexed, fallbackText) {
7927
8571
  if (!output && !result.summary && !indexed.summary) return null;
7928
8572
  const category = normalizedToolCategory(result.category || indexed.toolCategory || result.rawCategory || '', result.kind || indexed.title || '');
7929
8573
  return {
8574
+ id: result.id || result.callId || result.call_id || result.toolCallId || result.tool_call_id || result.toolUseId || result.tool_use_id || '',
8575
+ name: result.name || result.toolName || indexed.toolName || '',
8576
+ rawCategory: result.rawCategory || '',
7930
8577
  header: 'Tool result' + (result.title || indexed.title ? ' · ' + (result.title || indexed.title) : ''),
7931
8578
  kind: result.kind || indexed.title || 'Tool output',
7932
8579
  category,
@@ -7935,6 +8582,7 @@ function toolResultFromMetadata(result, indexed, fallbackText) {
7935
8582
  detail: result.summary || indexed.summary || firstLine(output),
7936
8583
  count: result.lineCount ? result.lineCount + ' line' + (Number(result.lineCount) === 1 ? '' : 's') : lineCountLabel(output || result.summary || indexed.summary || ''),
7937
8584
  output: output || result.summary || indexed.summary || '',
8585
+ lineStart: Number(result.startLine || result.start_line || result.lineStart || result.line_start || 0) || 0,
7938
8586
  collapsed: Boolean(result.collapsed) || String(output).split('\\n').length > 18
7939
8587
  };
7940
8588
  }
@@ -7954,6 +8602,7 @@ function parseFileViewResult(text) {
7954
8602
  detail: [attrs.path || basename, lineLabel].filter(Boolean).join(' · '),
7955
8603
  count: lineCountLabel(code),
7956
8604
  output: code,
8605
+ lineStart: Number(attrs.start_line || 0) || 0,
7957
8606
  collapsed: code.split('\\n').length > 24
7958
8607
  };
7959
8608
  }
@@ -8011,14 +8660,25 @@ function genericToolResult(text) {
8011
8660
  };
8012
8661
  }
8013
8662
 
8014
- function renderToolResult(result) {
8015
- const open = result.collapsed ? '' : ' open';
8663
+ function renderToolResult(result, options) {
8664
+ const inline = Boolean(options && options.inline);
8016
8665
  const category = normalizedToolCategory(result.category, result.kind);
8017
- return '<details class="tool-result" data-category="' + esc(escClass(category)) + '"' + open + '>' +
8666
+ return '<details class="tool-result' + (inline ? ' inline' : '') + '" data-category="' + esc(escClass(category)) + '">' +
8018
8667
  '<summary><span class="tool-result-kind"><span class="tool-glyph">' + renderToolIcon({ icon: result.icon, category, kind: result.kind }) + '</span>' + esc(result.kind) + '</span>' +
8019
8668
  (result.detail ? '<span class="tool-result-detail">' + esc(result.detail) + '</span>' : '') +
8020
8669
  (result.count ? '<span class="tool-result-count">' + esc(result.count) + '</span>' : '') +
8021
- '</summary><pre class="tool-output">' + esc(result.output || '') + '</pre></details>';
8670
+ '</summary>' + renderToolOutput(result.output || '', { lineStart: result.lineStart }) + '</details>';
8671
+ }
8672
+
8673
+ function renderToolOutput(output, options = {}) {
8674
+ const text = String(output || '');
8675
+ const lines = text.split('\\n');
8676
+ if (lines.length <= 1) return '<pre class="tool-output">' + esc(text) + '</pre>';
8677
+ const start = Number(options.lineStart || 0) || 1;
8678
+ return '<div class="tool-output tool-output-lines">' + lines.map((line, index) =>
8679
+ '<div class="tool-output-line"><span class="tool-line-number">' + esc(start + index) + '</span><span class="tool-line-text">' +
8680
+ (line ? esc(line) : '&nbsp;') + '</span></div>'
8681
+ ).join('') + '</div>';
8022
8682
  }
8023
8683
 
8024
8684
  function parseXmlishAttrs(value) {
@@ -8276,7 +8936,7 @@ function sessionDetailsText(payload) {
8276
8936
  'Title: ' + (payload.title || '(untitled session)'),
8277
8937
  'Thread ID: ' + (payload.session_id || ''),
8278
8938
  'Provider: ' + [providerLabel(payload.provider), payload.provider].filter(Boolean).join(' / '),
8279
- payload.source_type ? 'Source type: ' + payload.source_type : '',
8939
+ payload.source_type ? 'Source: ' + sourceTypeLabel(payload.provider, payload.source_type) + ' (' + payload.source_type + ')' : '',
8280
8940
  payload.repo_display || payload.repo || payload.scope ? 'Repo or path: ' + (payload.repo_display || payload.repo || payload.scope) : '',
8281
8941
  payload.cwd ? 'Working directory: ' + payload.cwd : '',
8282
8942
  payload.started_at ? 'Started: ' + payload.started_at : '',
@@ -8671,7 +9331,7 @@ setupStatsActivityControls();
8671
9331
  setupStatsBreakdownControls();
8672
9332
 
8673
9333
  async function loadStats() {
8674
- const elements = ['chartTokensPerDay', 'chartSessionsPerDay', 'chartTokensPerRepo', 'chartSessionsPerRepo', 'statsAgentHeatmap', 'statsChatHeatmap'].map((id) => document.getElementById(id));
9334
+ const elements = ['chartTokensPerDay', 'chartSessionsPerDay', 'chartTokensPerRepo', 'chartSessionsPerRepo', 'statsAgentHeatmap', 'statsChatHeatmap', 'statsSdkHeatmap'].map((id) => document.getElementById(id));
8675
9335
  for (const el of elements) {
8676
9336
  if (el) {
8677
9337
  el.classList.add('stats-empty');
@@ -8964,8 +9624,9 @@ function renderStatsDailyCharts() {
8964
9624
  if (empty2) { empty2.classList.add('stats-empty'); empty2.textContent = 'No days in this range.'; }
8965
9625
  return;
8966
9626
  }
8967
- renderDailyChart('chartTokensPerDay', densified, groups, usageMetric, 336);
8968
- renderDailyChart('chartSessionsPerDay', densified, groups, activityMetric, 336);
9627
+ const canonicalGroups = statsCanonicalOrderedGroups(groups);
9628
+ renderDailyChart('chartTokensPerDay', densified, canonicalGroups, usageMetric, 336);
9629
+ renderDailyChart('chartSessionsPerDay', densified, canonicalGroups, activityMetric, 336);
8969
9630
  }
8970
9631
 
8971
9632
  function renderStats(payload) {
@@ -8980,8 +9641,9 @@ function renderStats(payload) {
8980
9641
  renderStatsLegend(groups, breakdownTotals);
8981
9642
  renderStatsDailyCharts();
8982
9643
  const repoRows = statsRepoRowsForRange(payload);
8983
- renderRepoChart('chartTokensPerRepo', repoRows, groups, statsTokenMetric());
8984
- renderRepoChart('chartSessionsPerRepo', repoRows, groups, statsActivityMetric());
9644
+ const canonicalGroupsForRepo = statsCanonicalOrderedGroups(groups);
9645
+ renderRepoChart('chartTokensPerRepo', repoRows, canonicalGroupsForRepo, statsTokenMetric());
9646
+ renderRepoChart('chartSessionsPerRepo', repoRows, canonicalGroupsForRepo, statsActivityMetric());
8985
9647
  }
8986
9648
 
8987
9649
  function statsTokenMetric() {
@@ -9353,6 +10015,10 @@ function renderStatsMetrics(payload) {
9353
10015
  const totalOutTok = Number(payload.total_output_tokens || 0);
9354
10016
  const totalCacheTok = Number(payload.total_cache_tokens || 0);
9355
10017
  const totalEstimatedTok = Number(payload.total_estimated_tokens || 0);
10018
+ const sdkSessions = Number(payload.sdk_session_count || 0);
10019
+ const sdkTokens = Number(payload.sdk_total_tokens || 0);
10020
+ const sdkInputTokens = Number(payload.sdk_total_input_tokens || 0);
10021
+ const sdkOutputTokens = Number(payload.sdk_total_output_tokens || 0);
9356
10022
  const um = Number(payload.user_message_count || 0);
9357
10023
  const tm = Number(payload.message_count || 0);
9358
10024
  const avgMsgs = Number(payload.avg_messages_per_conversation);
@@ -9377,6 +10043,13 @@ function renderStatsMetrics(payload) {
9377
10043
  value: formatFullNumber(payload.session_count || 0),
9378
10044
  sub: formatFullNumber(payload.agent_session_count || 0) + ' agent · ' + formatFullNumber(payload.chat_session_count || 0) + ' chat'
9379
10045
  },
10046
+ sdkSessions || sdkTokens ? {
10047
+ label: 'SDK jobs',
10048
+ value: formatFullNumber(sdkSessions),
10049
+ sub: (sdkTokens ? formatCompactNumber(sdkTokens) + ' tokens' : '0 tokens') +
10050
+ (sdkInputTokens || sdkOutputTokens ? ' · ' + formatCompactNumber(sdkInputTokens) + ' in / ' + formatCompactNumber(sdkOutputTokens) + ' out' : '') +
10051
+ ' · kept separate'
10052
+ } : null,
9380
10053
  {
9381
10054
  label: 'Messages',
9382
10055
  value: formatFullNumber(tm),
@@ -9433,6 +10106,7 @@ function renderStatsMetrics(payload) {
9433
10106
  }
9434
10107
  ];
9435
10108
  container.innerHTML = metrics
10109
+ .filter(Boolean)
9436
10110
  .map(function (metric) {
9437
10111
  const vt =
9438
10112
  metric.valueHtml == null &&
@@ -9506,7 +10180,11 @@ function renderHeatmapSection(payload) {
9506
10180
  renderSecondaryHeatmap(payload.split_stats && payload.split_stats.agent, 'statsAgentActivitySub', 'statsAgentHeatmap', range, metric);
9507
10181
  renderSecondaryHeatmap(payload.split_stats && payload.split_stats.chat, 'statsChatActivitySub', 'statsChatHeatmap', range, metric, {
9508
10182
  emptySubText: '',
9509
- emptyText: 'No chat activity yet. Import ChatGPT or Claude.ai exports with agentlog import chatgpt <path> --scope local or agentlog import claude-web <path> --scope local.'
10183
+ emptyText: 'No chat activity yet. Run agentlog import chatgpt or agentlog import claude-web for official export instructions.'
10184
+ });
10185
+ renderSecondaryHeatmap(payload.split_stats && payload.split_stats.sdk, 'statsSdkActivitySub', 'statsSdkHeatmap', range, metric, {
10186
+ emptySubText: '',
10187
+ emptyText: 'No SDK jobs imported.'
9510
10188
  });
9511
10189
  }
9512
10190
 
@@ -9741,15 +10419,11 @@ function renderDailyChart(elementId, daily, providers, metric, height) {
9741
10419
  return '<line class="stats-axis-line" x1="' + padding.left + '" x2="' + (padding.left + innerW) + '" y1="' + y.toFixed(2) + '" y2="' + y.toFixed(2) + '"/>'
9742
10420
  + '<text class="stats-axis-label" x="' + (padding.left - 6) + '" y="' + (y + 3).toFixed(2) + '" text-anchor="end">' + esc(formatCompactNumber(value)) + '</text>';
9743
10421
  }).join('');
9744
- let prevYm = '';
9745
- const xLabels = visibleDaily.map((day, idx) => {
9746
- const ym = day.date.slice(0, 7);
9747
- if (ym === prevYm) return '';
9748
- prevYm = ym;
9749
- const cx = padding.left + idx * xStep + xStep / 2;
9750
- const tick = formatChartMonthTick(day.date);
9751
- return '<text class="stats-axis-label" x="' + cx.toFixed(2) + '" y="' + (padding.top + innerH + 18) + '" text-anchor="middle">' + esc(tick) + '</text>';
9752
- }).join('');
10422
+ const xLabels = dailyChartMonthTicks(visibleDaily, xStep, padding.left, innerW)
10423
+ .map((tickEntry) =>
10424
+ '<text class="stats-axis-label" x="' + tickEntry.x.toFixed(2) + '" y="' + (padding.top + innerH + 18) + '" text-anchor="middle">' + esc(tickEntry.label) + '</text>'
10425
+ )
10426
+ .join('');
9753
10427
  const hits = visibleDaily.map((day, dayIdx) => {
9754
10428
  const xHit = padding.left + dayIdx * xStep;
9755
10429
  return '<rect class="stats-chart-hit" pointer-events="all" x="' + xHit.toFixed(2) + '" y="' + padding.top + '" width="' + xStep.toFixed(2) + '" height="' + innerH.toFixed(2) + '" fill="transparent" data-day-index="' + dayIdx + '"/>';
@@ -9761,6 +10435,33 @@ function renderDailyChart(elementId, daily, providers, metric, height) {
9761
10435
  bindDailyChartHover(el);
9762
10436
  }
9763
10437
 
10438
+ function dailyChartMonthTicks(daily, xStep, left, innerW) {
10439
+ const monthStarts = [];
10440
+ let prevYm = '';
10441
+ for (let idx = 0; idx < daily.length; idx += 1) {
10442
+ const day = daily[idx];
10443
+ const ym = String(day?.date || '').slice(0, 7);
10444
+ if (!/^\\d{4}-\\d{2}$/.test(ym) || ym === prevYm) continue;
10445
+ prevYm = ym;
10446
+ monthStarts.push({ idx, date: day.date, ym });
10447
+ }
10448
+ const maxLabels = Math.max(2, Math.floor(innerW / 58));
10449
+ const step = Math.max(1, Math.ceil(monthStarts.length / maxLabels));
10450
+ const selected = [];
10451
+ for (let idx = 0; idx < monthStarts.length; idx += step) selected.push(monthStarts[idx]);
10452
+ const last = monthStarts[monthStarts.length - 1];
10453
+ if (last && selected[selected.length - 1] !== last) {
10454
+ const previous = selected[selected.length - 1];
10455
+ if (previous && monthStarts.indexOf(previous) >= monthStarts.length - step) selected[selected.length - 1] = last;
10456
+ else selected.push(last);
10457
+ }
10458
+ return selected
10459
+ .map((entry) => ({
10460
+ x: left + entry.idx * xStep + xStep / 2,
10461
+ label: formatChartMonthTick(entry.date)
10462
+ }));
10463
+ }
10464
+
9764
10465
  function trimEmptyDailyChartEdges(daily, providers, metric) {
9765
10466
  const rows = Array.isArray(daily) ? daily : [];
9766
10467
  const hasValue = (day) => providers.some((provider) => statsMetricValue(day?.providers?.[provider], metric) > 0);
@@ -10207,8 +10908,8 @@ Start here:
10207
10908
  Archive and import:
10208
10909
  init interactive setup and optional first import
10209
10910
  import import local Codex, Claude, Gemini, Devin, Cursor, Cline, OpenCode, Aider, and Antigravity history
10210
- import chatgpt <path> import a ChatGPT export JSON/ZIP/folder
10211
- import claude-web <path> import a Claude.ai export JSON/ZIP/folder
10911
+ import chatgpt [path] show ChatGPT export instructions; with path, import a ZIP/folder
10912
+ import claude-web [path] show Claude.ai export instructions; with path, import a ZIP/folder
10212
10913
  import windsurf <path> import downloaded Windsurf trajectory Markdown file/folder
10213
10914
  import accounts list or rename ChatGPT/Claude.ai export accounts
10214
10915
  sync choose a remote, preview, confirm, then upload archive objects
@@ -10264,8 +10965,8 @@ agentlog import
10264
10965
  Usage:
10265
10966
  agentlog import --source <source> [--since 30d|all]
10266
10967
  agentlog import --sources <a,b,c> [--since 30d|all]
10267
- agentlog import chatgpt <path> [--username <name>] [--scope local|team]
10268
- agentlog import claude-web <path> [--username <name>] [--scope local|team]
10968
+ agentlog import chatgpt [path] [--username <name>] [--scope local|team]
10969
+ agentlog import claude-web [path] [--username <name>] [--scope local|team]
10269
10970
  agentlog import windsurf <file-or-folder>
10270
10971
  agentlog import accounts list
10271
10972
  agentlog import accounts rename <provider> <account-id-or-username> --display-name <name>
@@ -10273,6 +10974,7 @@ Usage:
10273
10974
  Import sources:
10274
10975
  codex-cli terminal Codex sessions from Codex state and rollout files
10275
10976
  codex-desktop Codex desktop app sessions from Codex state and rollout files
10977
+ codex-sdk high-volume Codex exec/SDK batch jobs; opt-in
10276
10978
  claude interactive Claude Code CLI JSONL transcripts
10277
10979
  claude-code-desktop Claude Code sessions launched from the Claude desktop app
10278
10980
  claude-workspace Claude app workspace/local-agent sessions
@@ -10282,18 +10984,22 @@ Import sources:
10282
10984
  devin-cli Devin for Terminal sessions from ~/.local/share/devin/cli/sessions.db
10283
10985
  cursor Cursor SQLite history plus newer agent-transcripts folders
10284
10986
  cline Cline task folders from VS Code/JetBrains globalStorage
10285
- opencode OpenCode JSON session/message/part storage
10987
+ opencode-cli OpenCode CLI/core SQLite database plus project JSON session/message/part storage
10988
+ opencode-desktop OpenCode Desktop app-specific SQLite database and app storage
10989
+ opencode-web OpenCode web sessions from the shared OpenCode SQLite store
10990
+ opencode OpenCode CLI, Desktop, and Web aggregate
10286
10991
  aider Aider repo-local markdown chat history
10287
10992
  all configured default local sources
10288
10993
 
10289
10994
  Web export sources:
10290
- chatgpt official ChatGPT export JSON/ZIP
10291
- claude-web official Claude.ai export JSON/ZIP
10995
+ chatgpt instructions for requesting a ChatGPT export; imports downloaded ZIP/folder
10996
+ claude-web instructions for requesting a Claude.ai export; imports downloaded ZIP/folder
10292
10997
  windsurf downloaded Cascade trajectory Markdown file/folder
10293
10998
 
10294
10999
  Examples:
10295
11000
  agentlog import --source codex-cli --since 30d
10296
11001
  agentlog import --source codex-desktop --since all
11002
+ agentlog import --source codex-sdk --since all
10297
11003
  agentlog import --source claude --since 30d
10298
11004
  agentlog import --source claude-code-desktop --since all
10299
11005
  agentlog import --source claude-workspace --since all
@@ -10304,11 +11010,13 @@ Examples:
10304
11010
  agentlog import --source cline --since all
10305
11011
  agentlog import --source opencode --since all
10306
11012
  agentlog import --source aider --since all
10307
- agentlog import --sources codex-cli,codex-desktop,claude,claude-code-desktop,claude-workspace,gemini-cli,antigravity,devin-cli,cursor,cline,opencode,aider --since all
11013
+ agentlog import --sources codex-cli,codex-desktop,claude,claude-code-desktop,claude-workspace,gemini-cli,antigravity,devin-cli,cursor,cline,opencode-cli,opencode-desktop,opencode-web,aider --since all
10308
11014
  agentlog import --source all --since all --dry-run
10309
11015
  agentlog import --source cursor --since all --explain-skips
10310
11016
  agentlog import --source cursor --since all --explain-skips --json
10311
11017
  agentlog import status --json
11018
+ agentlog import chatgpt
11019
+ agentlog import claude-web
10312
11020
  agentlog import chatgpt ~/Downloads/chatgpt-export.zip --username you@example.com
10313
11021
  agentlog import claude-web ~/Downloads/claude-export --username brian --display-name Brian --scope local
10314
11022
  agentlog import windsurf ~/Downloads/cascade-chat-conversation.md
@@ -10321,7 +11029,8 @@ Details:
10321
11029
  --dry-run shows what would be imported without writing archive files.
10322
11030
  --explain-skips includes per-session skip reasons for supported sources.
10323
11031
  --json prints machine-readable import results.
10324
- In a terminal, web and Windsurf imports prompt for the export path when omitted.
11032
+ ChatGPT and Claude.ai imports print export instructions when the path is omitted.
11033
+ Windsurf imports prompt for the export path when omitted in a terminal.
10325
11034
  Windsurf local cache scanning is disabled because current Cascade transcripts are encrypted binary stores. Use the Windsurf "Download trajectory" Markdown export with \`agentlog import windsurf <file-or-folder>\`.
10326
11035
  See docs/history-source-handling.md for source-specific storage paths.
10327
11036
  `;