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/README.md +38 -25
- package/docs/code-reference.md +22 -13
- package/docs/history-source-handling.md +173 -60
- package/docs/release.md +1 -2
- package/package.json +1 -1
- package/src/archive.js +1 -1
- package/src/canonical-events.js +74 -25
- package/src/cli.js +818 -109
- package/src/config.js +4 -1
- package/src/doctor.js +15 -2
- package/src/importers/claude.js +309 -11
- package/src/importers/providers.js +47 -1
- package/src/importers.js +845 -63
- package/src/parser-versions.js +6 -0
- package/src/search.js +5 -1
- package/src/sources.js +8 -3
- package/src/web-export-instructions.js +77 -0
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
|
-
|
|
2190
|
-
if (
|
|
2191
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
3118
|
-
chat: statsPayloadForSessions(
|
|
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.
|
|
5164
|
-
summary: sourceSummary(discovered.
|
|
5165
|
-
description: "OpenCode JSON session, message, part, and session_diff storage from ~/.local/share/opencode.",
|
|
5166
|
-
defaultSelected: Boolean(discovered.
|
|
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-
|
|
5632
|
-
.tool-
|
|
5633
|
-
.tool-
|
|
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-
|
|
5637
|
-
.tool-glyph
|
|
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-
|
|
5647
|
-
.tool-
|
|
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-
|
|
5654
|
-
.tool-
|
|
5655
|
-
.tool-
|
|
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;
|
|
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:
|
|
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%;
|
|
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 (
|
|
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: '#
|
|
6325
|
-
claude: { family: 'claude', light: '#
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
7352
|
+
const end = Math.min(renderItems.length, index + MESSAGE_RENDER_CHUNK_SIZE);
|
|
7109
7353
|
for (; index < end; index++) {
|
|
7110
|
-
const
|
|
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
|
-
|
|
7358
|
+
fragment.appendChild(messageElement(message, item));
|
|
7359
|
+
previousTimestamp = item.lastTimestamp || message.timestamp || previousTimestamp;
|
|
7115
7360
|
}
|
|
7116
7361
|
readableView.appendChild(fragment);
|
|
7117
|
-
if (index <
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
'
|
|
7685
|
-
|
|
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"
|
|
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
|
|
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)) + '"
|
|
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
|
|
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) : ' ') + '</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
|
|
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
|
-
|
|
8968
|
-
renderDailyChart('
|
|
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
|
-
|
|
8984
|
-
renderRepoChart('
|
|
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.
|
|
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
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
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
|
|
10211
|
-
import claude-web
|
|
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
|
|
10268
|
-
agentlog import claude-web
|
|
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
|
|
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
|
|
10291
|
-
claude-web
|
|
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
|
-
|
|
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
|
`;
|