agentel 0.2.0 → 0.2.3
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 +161 -63
- package/agentlog-spec.md +42 -35
- package/bin/agentlog-recall.js +2 -0
- package/bin/agentlog.js +12 -0
- package/docs/code-reference.md +120 -34
- package/docs/history-source-handling.md +236 -81
- package/docs/release.md +8 -8
- package/package.json +5 -4
- package/src/archive.js +279 -20
- package/src/cli.js +3457 -511
- package/src/config.js +42 -1
- package/src/doctor.js +167 -10
- package/src/importers/gemini.js +369 -7
- package/src/importers.js +1893 -133
- package/src/mcp.js +4 -1
- package/src/parser-versions.js +37 -22
- package/src/paths.js +4 -2
- package/src/redaction.js +140 -17
- package/src/search.js +671 -52
- package/src/supervisor.js +206 -57
- package/src/sync.js +459 -12
package/src/archive.js
CHANGED
|
@@ -11,6 +11,16 @@ const { loadRedactionConfig, loadEnvValues, mergeSummaries, redactText, styleRed
|
|
|
11
11
|
const { ensureBaseDirs, ensureDir, paths, readJson, writeJson } = require("./paths");
|
|
12
12
|
|
|
13
13
|
const SHARED_RAW_SOURCE_CACHE = new Map();
|
|
14
|
+
const ESTIMATED_TOKEN_CHARS = 4;
|
|
15
|
+
const MESSAGE_TOKEN_ESTIMATE_METHOD = "chars-per-token-v1";
|
|
16
|
+
const CHAT_ESTIMATED_USAGE_METHOD = "visible-message-chars-v1";
|
|
17
|
+
const CURSOR_ESTIMATED_MAX_INPUT_PER_TURN = 200000;
|
|
18
|
+
const CURSOR_ESTIMATED_TOKEN_RATES = {
|
|
19
|
+
opus: { inputPerAssistantTurn: 11000, outputPerAssistantTurn: 240 },
|
|
20
|
+
sonnet: { inputPerAssistantTurn: 10000, outputPerAssistantTurn: 120 },
|
|
21
|
+
unknown: { inputPerAssistantTurn: 6900, outputPerAssistantTurn: 270 },
|
|
22
|
+
default: { inputPerAssistantTurn: 8400, outputPerAssistantTurn: 230 }
|
|
23
|
+
};
|
|
14
24
|
|
|
15
25
|
function archiveRoot(env = process.env) {
|
|
16
26
|
const cfg = loadConfig(env);
|
|
@@ -59,7 +69,7 @@ function writeSession(input, env = process.env) {
|
|
|
59
69
|
const sessionId = input.sessionId || stableSessionId(provider, input.sourcePath || "", startedAt, messages);
|
|
60
70
|
const repoCanonical = input.scopeCanonical ? "" : input.repoCanonical || repoInfo.key;
|
|
61
71
|
const redactionConfig = loadRedactionConfig(env);
|
|
62
|
-
const envValues = loadEnvValues(redactionCwd, redactionConfig.envVars, env);
|
|
72
|
+
const envValues = redactionConfig.enabled ? loadEnvValues(redactionCwd, redactionConfig.envVars, env) : [];
|
|
63
73
|
const redactionSummary = {};
|
|
64
74
|
const redactedMessages = messages.map((message) => {
|
|
65
75
|
const result = redactText(message.content, {
|
|
@@ -83,6 +93,17 @@ function writeSession(input, env = process.env) {
|
|
|
83
93
|
})
|
|
84
94
|
};
|
|
85
95
|
});
|
|
96
|
+
const archivedMessages = redactedMessages.map(withMessageTokenEstimate);
|
|
97
|
+
const sessionSummary = input.sessionSummary && typeof input.sessionSummary === "object"
|
|
98
|
+
? redactStructuredStrings(input.sessionSummary, {
|
|
99
|
+
config: redactionConfig,
|
|
100
|
+
cwd: redactionCwd,
|
|
101
|
+
env,
|
|
102
|
+
envValues,
|
|
103
|
+
repoCanonical,
|
|
104
|
+
summary: redactionSummary
|
|
105
|
+
})
|
|
106
|
+
: null;
|
|
86
107
|
|
|
87
108
|
const session = {
|
|
88
109
|
version: 1,
|
|
@@ -115,10 +136,17 @@ function writeSession(input, env = process.env) {
|
|
|
115
136
|
"chatVirtualRepo",
|
|
116
137
|
"chatDisplayPath",
|
|
117
138
|
"conversationKind",
|
|
118
|
-
"pinned"
|
|
139
|
+
"pinned",
|
|
140
|
+
"timeStatus",
|
|
141
|
+
"composerId",
|
|
142
|
+
"parentComposerId"
|
|
119
143
|
]) {
|
|
120
144
|
if (input[key] !== undefined) session[key] = input[key];
|
|
121
145
|
}
|
|
146
|
+
if (sessionSummary && Object.keys(sessionSummary).length) session.sessionSummary = sessionSummary;
|
|
147
|
+
if (session.composerId === undefined && typeof input.id === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(input.id)) {
|
|
148
|
+
session.composerId = input.id;
|
|
149
|
+
}
|
|
122
150
|
|
|
123
151
|
const dir = objectPathForSession(session, env);
|
|
124
152
|
ensureDir(dir);
|
|
@@ -129,7 +157,7 @@ function writeSession(input, env = process.env) {
|
|
|
129
157
|
const viewPath = path.join(dir, `session=${sessionId}.view.json`);
|
|
130
158
|
const rawPath = path.join(dir, `session=${sessionId}.raw`);
|
|
131
159
|
const events = redactCanonicalEvents(
|
|
132
|
-
Array.isArray(input.events) ? normalizePrecomputedEvents(input.events, session) : normalizeSessionEvents(session,
|
|
160
|
+
Array.isArray(input.events) ? normalizePrecomputedEvents(input.events, session) : normalizeSessionEvents(session, archivedMessages),
|
|
133
161
|
{
|
|
134
162
|
config: redactionConfig,
|
|
135
163
|
cwd: redactionCwd,
|
|
@@ -147,21 +175,25 @@ function writeSession(input, env = process.env) {
|
|
|
147
175
|
session.rawFiles = rawFiles;
|
|
148
176
|
session.rawFileCount = rawFiles.length;
|
|
149
177
|
}
|
|
150
|
-
fs.writeFileSync(conversationPath, renderConversationMarkdown(session,
|
|
151
|
-
fs.writeFileSync(transcriptPath,
|
|
178
|
+
fs.writeFileSync(conversationPath, renderConversationMarkdown(session, archivedMessages, events), { mode: 0o600 });
|
|
179
|
+
fs.writeFileSync(transcriptPath, archivedMessages.map((message) => `${JSON.stringify(message)}\n`).join(""), {
|
|
152
180
|
mode: 0o600
|
|
153
181
|
});
|
|
154
182
|
fs.writeFileSync(eventPath, events.map((event) => `${JSON.stringify(event)}\n`).join(""), { mode: 0o600 });
|
|
155
|
-
writeSessionView(viewPath, computeSessionView(session,
|
|
183
|
+
writeSessionView(viewPath, computeSessionView(session, archivedMessages, events));
|
|
156
184
|
session.conversationPath = conversationPath;
|
|
157
185
|
session.transcriptPath = transcriptPath;
|
|
158
186
|
session.eventPath = eventPath;
|
|
159
187
|
session.viewPath = viewPath;
|
|
160
188
|
session.viewSchemaVersion = VIEW_SCHEMA_VERSION;
|
|
161
|
-
const usageStats = computeSessionUsage(
|
|
189
|
+
const usageStats = mergeSessionUsage(computeSessionUsage(archivedMessages), sessionSummaryUsage(session.sessionSummary));
|
|
162
190
|
if (usageStats) session.usage = usageStats;
|
|
163
|
-
const models = collectSessionModels(
|
|
191
|
+
const models = collectSessionModels(archivedMessages, session.sessionSummary);
|
|
192
|
+
const estimatedUsage = usageStats ? null : estimateSessionUsage(archivedMessages, { provider, sourceType, models });
|
|
193
|
+
if (estimatedUsage) session.estimatedUsage = estimatedUsage;
|
|
164
194
|
if (models.length) session.models = models;
|
|
195
|
+
const commandTypeCounts = collectCursorCommandTypeCounts(archivedMessages);
|
|
196
|
+
if (commandTypeCounts) session.cursorCommandTypeCounts = commandTypeCounts;
|
|
165
197
|
writeJson(metadataPath, session);
|
|
166
198
|
|
|
167
199
|
if (cfg.privacy.revealCache && !input.scopeCanonical) {
|
|
@@ -578,6 +610,9 @@ function computeSessionUsage(messages) {
|
|
|
578
610
|
let totalOutputTokens = 0;
|
|
579
611
|
const seenUsageRequests = new Set();
|
|
580
612
|
let any = false;
|
|
613
|
+
let estimatedUsageCount = 0;
|
|
614
|
+
let actualUsageCount = 0;
|
|
615
|
+
const estimationMethods = new Set();
|
|
581
616
|
for (const message of messages || []) {
|
|
582
617
|
const usage = message && message.metadata && message.metadata.usage;
|
|
583
618
|
if (!usage || typeof usage !== "object") continue;
|
|
@@ -588,6 +623,12 @@ function computeSessionUsage(messages) {
|
|
|
588
623
|
}
|
|
589
624
|
const summary = usageTokenSummary(usage);
|
|
590
625
|
if (!summary.hasAny) continue;
|
|
626
|
+
if (summary.estimated) {
|
|
627
|
+
estimatedUsageCount++;
|
|
628
|
+
if (summary.estimationMethod) estimationMethods.add(summary.estimationMethod);
|
|
629
|
+
} else {
|
|
630
|
+
actualUsageCount++;
|
|
631
|
+
}
|
|
591
632
|
if (summary.inputTokens > 0) {
|
|
592
633
|
inputTokens += summary.inputTokens;
|
|
593
634
|
any = true;
|
|
@@ -607,15 +648,17 @@ function computeSessionUsage(messages) {
|
|
|
607
648
|
}
|
|
608
649
|
if (Number.isFinite(totalInputTokens) && totalInputTokens > 0) any = true;
|
|
609
650
|
if (Number.isFinite(totalOutputTokens) && totalOutputTokens > 0) any = true;
|
|
651
|
+
if (reasoningOutputTokens > 0 || toolUsePromptTokens > 0) any = true;
|
|
610
652
|
if (!any && maxUndifferentiatedTotalTokens > 0) {
|
|
611
653
|
inputTokens = maxUndifferentiatedTotalTokens;
|
|
612
654
|
any = true;
|
|
613
655
|
}
|
|
614
656
|
const cumulativeTokens = totalInputTokens + totalOutputTokens;
|
|
615
|
-
const
|
|
657
|
+
const splitTotalTokens = inputTokens + outputTokens + reasoningOutputTokens + toolUsePromptTokens;
|
|
658
|
+
const finalTotalTokens = splitTotalTokens || cumulativeTokens || maxUndifferentiatedTotalTokens;
|
|
616
659
|
if (!any && finalTotalTokens === 0) return null;
|
|
617
|
-
|
|
618
|
-
inputTokens: inputTokens || totalInputTokens || (!outputTokens &&
|
|
660
|
+
const result = {
|
|
661
|
+
inputTokens: inputTokens || totalInputTokens || (!outputTokens && !reasoningOutputTokens && !toolUsePromptTokens && maxUndifferentiatedTotalTokens ? maxUndifferentiatedTotalTokens : 0),
|
|
619
662
|
outputTokens: outputTokens || totalOutputTokens,
|
|
620
663
|
cacheInputTokens,
|
|
621
664
|
reasoningOutputTokens,
|
|
@@ -624,10 +667,148 @@ function computeSessionUsage(messages) {
|
|
|
624
667
|
totalInputTokens: totalInputTokens || inputTokens,
|
|
625
668
|
totalOutputTokens: totalOutputTokens || outputTokens
|
|
626
669
|
};
|
|
670
|
+
if (estimatedUsageCount > 0 && actualUsageCount === 0) {
|
|
671
|
+
result.estimated = true;
|
|
672
|
+
if (estimationMethods.size === 1) result.estimationMethod = Array.from(estimationMethods)[0];
|
|
673
|
+
}
|
|
674
|
+
return result;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function estimateSessionUsage(messages, options = {}) {
|
|
678
|
+
const provider = String(options.provider || "").toLowerCase();
|
|
679
|
+
if (provider === "cursor") return estimateCursorSessionUsage(messages, options);
|
|
680
|
+
if (isWebChatProvider(provider, options.sourceType)) return estimateVisibleMessageUsage(messages);
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function estimateCursorSessionUsage(messages, options = {}) {
|
|
685
|
+
let contextTokens = 0;
|
|
686
|
+
let visibleContextInputTokens = 0;
|
|
687
|
+
let visibleOutputTokens = 0;
|
|
688
|
+
let assistantTurns = 0;
|
|
689
|
+
for (const message of messages || []) {
|
|
690
|
+
const messageTokens = estimateMessageTokens(message);
|
|
691
|
+
if (message?.role === "assistant") {
|
|
692
|
+
if (messageTokens > 0 || contextTokens > 0) {
|
|
693
|
+
visibleContextInputTokens += Math.min(contextTokens, CURSOR_ESTIMATED_MAX_INPUT_PER_TURN);
|
|
694
|
+
visibleOutputTokens += messageTokens;
|
|
695
|
+
assistantTurns++;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
contextTokens += messageTokens;
|
|
699
|
+
}
|
|
700
|
+
if (!assistantTurns) return null;
|
|
701
|
+
const family = cursorEstimatedModelFamily(options);
|
|
702
|
+
const rates = cursorEstimatedTokenRates(family, options);
|
|
703
|
+
const empiricalInputTokens = rates.inputPerAssistantTurn * assistantTurns;
|
|
704
|
+
const empiricalOutputTokens = rates.outputPerAssistantTurn * assistantTurns;
|
|
705
|
+
const inputTokens = Math.max(empiricalInputTokens, visibleContextInputTokens);
|
|
706
|
+
const outputTokens = Math.max(empiricalOutputTokens, visibleOutputTokens);
|
|
707
|
+
if (inputTokens + outputTokens <= 0) return null;
|
|
708
|
+
return {
|
|
709
|
+
inputTokens,
|
|
710
|
+
outputTokens,
|
|
711
|
+
totalTokens: inputTokens + outputTokens,
|
|
712
|
+
estimated: true,
|
|
713
|
+
estimationMethod: "cursor-empirical-turns-v1",
|
|
714
|
+
estimatedModelFamily: family,
|
|
715
|
+
estimatedAssistantTurns: assistantTurns
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function estimateVisibleMessageUsage(messages) {
|
|
720
|
+
let inputTokens = 0;
|
|
721
|
+
let outputTokens = 0;
|
|
722
|
+
let messageCount = 0;
|
|
723
|
+
for (const message of messages || []) {
|
|
724
|
+
const tokens = messageTokenEstimate(message);
|
|
725
|
+
if (!tokens) continue;
|
|
726
|
+
if (message?.role === "assistant") outputTokens += tokens;
|
|
727
|
+
else inputTokens += tokens;
|
|
728
|
+
messageCount++;
|
|
729
|
+
}
|
|
730
|
+
if (inputTokens + outputTokens <= 0) return null;
|
|
731
|
+
return {
|
|
732
|
+
inputTokens,
|
|
733
|
+
outputTokens,
|
|
734
|
+
totalTokens: inputTokens + outputTokens,
|
|
735
|
+
estimated: true,
|
|
736
|
+
estimationMethod: CHAT_ESTIMATED_USAGE_METHOD,
|
|
737
|
+
estimatedMessageCount: messageCount
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function isWebChatProvider(provider, sourceType = "") {
|
|
742
|
+
if (provider === "chatgpt" || provider === "claude_web") return true;
|
|
743
|
+
return /^(web-chat-export|chatgpt-export|claude-web-export|claude-web-memory)$/.test(String(sourceType || ""));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function cursorEstimatedModelFamily(options = {}) {
|
|
747
|
+
const models = Array.isArray(options.models) ? options.models.join(" ").toLowerCase() : "";
|
|
748
|
+
if (models.includes("opus")) return "opus";
|
|
749
|
+
if (models.includes("sonnet")) return "sonnet";
|
|
750
|
+
if (models.includes("gpt") || models.includes("gemini") || models.includes("claude")) return "default";
|
|
751
|
+
if (String(options.sourceType || "") === "cursor-agent-transcripts") return "default";
|
|
752
|
+
return "unknown";
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function cursorEstimatedTokenRates(family) {
|
|
756
|
+
return CURSOR_ESTIMATED_TOKEN_RATES[family] || CURSOR_ESTIMATED_TOKEN_RATES.default;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function estimateMessageTokens(message) {
|
|
760
|
+
const parts = [];
|
|
761
|
+
if (message?.content) parts.push(String(message.content));
|
|
762
|
+
const toolCalls = Array.isArray(message?.metadata?.toolCalls) ? message.metadata.toolCalls : [];
|
|
763
|
+
for (const call of toolCalls) {
|
|
764
|
+
const summary = {
|
|
765
|
+
name: call?.name || call?.displayName,
|
|
766
|
+
target: call?.target,
|
|
767
|
+
argument: call?.argument || call?.rawInputSummary || call?.inputPreview,
|
|
768
|
+
arguments: call?.arguments
|
|
769
|
+
};
|
|
770
|
+
parts.push(JSON.stringify(summary));
|
|
771
|
+
}
|
|
772
|
+
const text = parts.filter(Boolean).join("\n");
|
|
773
|
+
if (!text.trim()) return 0;
|
|
774
|
+
return Math.max(1, Math.ceil(text.length / ESTIMATED_TOKEN_CHARS));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function withMessageTokenEstimate(message) {
|
|
778
|
+
const tokens = estimateMessageTokens(message);
|
|
779
|
+
if (!tokens) return message;
|
|
780
|
+
const existing = message?.metadata?.tokenEstimate;
|
|
781
|
+
if (existing && typeof existing === "object" && positiveTokenNumber(existing.tokens)) return message;
|
|
782
|
+
return {
|
|
783
|
+
...message,
|
|
784
|
+
metadata: {
|
|
785
|
+
...(message.metadata || {}),
|
|
786
|
+
tokenEstimate: {
|
|
787
|
+
tokens,
|
|
788
|
+
estimated: true,
|
|
789
|
+
method: MESSAGE_TOKEN_ESTIMATE_METHOD,
|
|
790
|
+
charsPerToken: ESTIMATED_TOKEN_CHARS
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function messageTokenEstimate(message) {
|
|
797
|
+
const estimate = message?.metadata?.tokenEstimate || message?.metadata?.token_estimate;
|
|
798
|
+
if (estimate && typeof estimate === "object") {
|
|
799
|
+
return positiveTokenNumber(firstUsageNumber(estimate.tokens, estimate.tokenCount, estimate.token_count, estimate.totalTokens, estimate.total_tokens));
|
|
800
|
+
}
|
|
801
|
+
return estimateMessageTokens(message);
|
|
627
802
|
}
|
|
628
803
|
|
|
629
804
|
function usageTokenSummary(usage) {
|
|
630
805
|
if (!usage || typeof usage !== "object") return emptyUsageTokenSummary();
|
|
806
|
+
const estimated = usage.estimated === true || usage.estimated === "true";
|
|
807
|
+
const estimationMethod = typeof usage.estimationMethod === "string" && usage.estimationMethod.trim()
|
|
808
|
+
? usage.estimationMethod.trim()
|
|
809
|
+
: typeof usage.estimation_method === "string" && usage.estimation_method.trim()
|
|
810
|
+
? usage.estimation_method.trim()
|
|
811
|
+
: "";
|
|
631
812
|
const inputTokens = positiveTokenNumber(firstUsageNumber(
|
|
632
813
|
usage.inputTokens,
|
|
633
814
|
usage.input_tokens,
|
|
@@ -716,7 +897,9 @@ function usageTokenSummary(usage) {
|
|
|
716
897
|
hasSplitTokens: splitTokens > 0,
|
|
717
898
|
hasExtraTokens: extraTokens > 0,
|
|
718
899
|
hasTotalTokens: explicitTotalTokens > 0,
|
|
719
|
-
hasCumulativeTokens: cumulativeTokens > 0
|
|
900
|
+
hasCumulativeTokens: cumulativeTokens > 0,
|
|
901
|
+
estimated,
|
|
902
|
+
estimationMethod
|
|
720
903
|
};
|
|
721
904
|
}
|
|
722
905
|
|
|
@@ -735,7 +918,9 @@ function emptyUsageTokenSummary() {
|
|
|
735
918
|
hasSplitTokens: false,
|
|
736
919
|
hasExtraTokens: false,
|
|
737
920
|
hasTotalTokens: false,
|
|
738
|
-
hasCumulativeTokens: false
|
|
921
|
+
hasCumulativeTokens: false,
|
|
922
|
+
estimated: false,
|
|
923
|
+
estimationMethod: ""
|
|
739
924
|
};
|
|
740
925
|
}
|
|
741
926
|
|
|
@@ -757,15 +942,76 @@ function sumPositiveTokenNumbers(...values) {
|
|
|
757
942
|
return values.reduce((sum, value) => sum + positiveTokenNumber(value), 0);
|
|
758
943
|
}
|
|
759
944
|
|
|
760
|
-
function
|
|
945
|
+
function sessionSummaryUsage(sessionSummary) {
|
|
946
|
+
const usage = sessionSummary?.usage;
|
|
947
|
+
if (!usage || typeof usage !== "object") return null;
|
|
948
|
+
const inputTokens = positiveTokenNumber(firstUsageNumber(usage.inputTokens, usage.input_tokens, usage.promptTokens, usage.prompt_tokens));
|
|
949
|
+
const outputTokens = positiveTokenNumber(firstUsageNumber(usage.outputTokens, usage.output_tokens, usage.completionTokens, usage.completion_tokens));
|
|
950
|
+
const cacheInputTokens = sumPositiveTokenNumbers(
|
|
951
|
+
firstUsageNumber(usage.cacheInputTokens, usage.cache_input_tokens),
|
|
952
|
+
firstUsageNumber(usage.cacheReadTokens, usage.cache_read_tokens)
|
|
953
|
+
);
|
|
954
|
+
const totalTokens = positiveTokenNumber(firstUsageNumber(usage.totalTokens, usage.total_tokens, usage.totalTokenCount, usage.total_token_count)) || inputTokens + outputTokens;
|
|
955
|
+
if (!inputTokens && !outputTokens && !cacheInputTokens && !totalTokens) return null;
|
|
956
|
+
return {
|
|
957
|
+
inputTokens,
|
|
958
|
+
outputTokens,
|
|
959
|
+
cacheInputTokens,
|
|
960
|
+
reasoningOutputTokens: 0,
|
|
961
|
+
toolUsePromptTokens: 0,
|
|
962
|
+
totalTokens,
|
|
963
|
+
totalInputTokens: inputTokens,
|
|
964
|
+
totalOutputTokens: outputTokens
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function mergeSessionUsage(primary, secondary) {
|
|
969
|
+
if (!primary && !secondary) return null;
|
|
970
|
+
if (!primary) return secondary;
|
|
971
|
+
if (!secondary) return primary;
|
|
972
|
+
const result = {
|
|
973
|
+
inputTokens: primary.inputTokens || secondary.inputTokens || 0,
|
|
974
|
+
outputTokens: primary.outputTokens || secondary.outputTokens || 0,
|
|
975
|
+
cacheInputTokens: primary.cacheInputTokens || secondary.cacheInputTokens || 0,
|
|
976
|
+
reasoningOutputTokens: primary.reasoningOutputTokens || secondary.reasoningOutputTokens || 0,
|
|
977
|
+
toolUsePromptTokens: primary.toolUsePromptTokens || secondary.toolUsePromptTokens || 0,
|
|
978
|
+
totalTokens: Math.max(primary.totalTokens || 0, secondary.totalTokens || 0),
|
|
979
|
+
totalInputTokens: primary.totalInputTokens || secondary.totalInputTokens || primary.inputTokens || secondary.inputTokens || 0,
|
|
980
|
+
totalOutputTokens: primary.totalOutputTokens || secondary.totalOutputTokens || primary.outputTokens || secondary.outputTokens || 0
|
|
981
|
+
};
|
|
982
|
+
if (primary.estimated && secondary.estimated) {
|
|
983
|
+
result.estimated = true;
|
|
984
|
+
if (primary.estimationMethod && primary.estimationMethod === secondary.estimationMethod) {
|
|
985
|
+
result.estimationMethod = primary.estimationMethod;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return result;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function collectSessionModels(messages, sessionSummary = null) {
|
|
761
992
|
const models = new Set();
|
|
762
993
|
for (const message of messages || []) {
|
|
763
994
|
const model = message && message.metadata && message.metadata.model;
|
|
764
995
|
if (typeof model === "string" && model.trim()) models.add(model.trim());
|
|
765
996
|
}
|
|
997
|
+
for (const item of sessionSummary?.modelUsage || []) {
|
|
998
|
+
if (typeof item?.model === "string" && item.model.trim()) models.add(item.model.trim());
|
|
999
|
+
}
|
|
766
1000
|
return Array.from(models);
|
|
767
1001
|
}
|
|
768
1002
|
|
|
1003
|
+
function collectCursorCommandTypeCounts(messages) {
|
|
1004
|
+
const counts = {};
|
|
1005
|
+
for (const message of messages || []) {
|
|
1006
|
+
if (message?.role !== "user") continue;
|
|
1007
|
+
const value = message?.metadata?.commandType;
|
|
1008
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
1009
|
+
const key = value.trim().toLowerCase();
|
|
1010
|
+
counts[key] = (counts[key] || 0) + 1;
|
|
1011
|
+
}
|
|
1012
|
+
return Object.keys(counts).length ? counts : null;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
769
1015
|
function readTranscript(file) {
|
|
770
1016
|
const messages = [];
|
|
771
1017
|
try {
|
|
@@ -814,7 +1060,7 @@ function ensureConversationMarkdown(session, env = process.env) {
|
|
|
814
1060
|
return conversationPath;
|
|
815
1061
|
}
|
|
816
1062
|
|
|
817
|
-
const VIEW_SCHEMA_VERSION =
|
|
1063
|
+
const VIEW_SCHEMA_VERSION = 2;
|
|
818
1064
|
|
|
819
1065
|
function sessionViewPathFromMetadata(metadataPath) {
|
|
820
1066
|
if (!metadataPath) return "";
|
|
@@ -881,6 +1127,13 @@ function cursorRawSyntheticTimeParts(session) {
|
|
|
881
1127
|
}
|
|
882
1128
|
|
|
883
1129
|
function sessionHistoryTime(session) {
|
|
1130
|
+
const explicitStatus = session?.timeStatus || session?.time_status || "";
|
|
1131
|
+
if (explicitStatus === "recovered-time-unknown") {
|
|
1132
|
+
return { startedAt: "", endedAt: "", status: "recovered-time-unknown" };
|
|
1133
|
+
}
|
|
1134
|
+
if (explicitStatus === "recovered-partial-time") {
|
|
1135
|
+
return { startedAt: session?.startedAt || "", endedAt: "", status: "recovered-partial-time" };
|
|
1136
|
+
}
|
|
884
1137
|
const rawTime = cursorRawSyntheticTimeParts(session);
|
|
885
1138
|
if (rawTime.startSynthetic && rawTime.endSynthetic) {
|
|
886
1139
|
return { startedAt: "", endedAt: "", status: "recovered-time-unknown" };
|
|
@@ -900,6 +1153,7 @@ function computeSessionView(session, transcriptMessages, canonicalEvents) {
|
|
|
900
1153
|
started_at: time.startedAt || "",
|
|
901
1154
|
ended_at: time.endedAt || "",
|
|
902
1155
|
time_status: time.status || "",
|
|
1156
|
+
session_summary: session?.sessionSummary || undefined,
|
|
903
1157
|
transcript_messages: display,
|
|
904
1158
|
canonical_events: Array.isArray(canonicalEvents) ? canonicalEvents : []
|
|
905
1159
|
};
|
|
@@ -939,9 +1193,11 @@ function ensureSessionView(session, env = process.env) {
|
|
|
939
1193
|
|
|
940
1194
|
function renderConversationMarkdown(session, messages, events = []) {
|
|
941
1195
|
const title = session.title || `${session.provider || "agent"} session ${session.sessionId}`;
|
|
1196
|
+
const time = sessionHistoryTime(session);
|
|
1197
|
+
const displayMessages = displayTranscriptMessages(messages, time);
|
|
942
1198
|
const hasRedactions =
|
|
943
|
-
|
|
944
|
-
events.some((event) => /\[REDACTED
|
|
1199
|
+
displayMessages.some((message) => /\[REDACTED(?::|\s+)[^\]\n]+\]/.test(JSON.stringify(message))) ||
|
|
1200
|
+
events.some((event) => /\[REDACTED(?::|\s+)[^\]\n]+\]/.test(JSON.stringify(event)));
|
|
945
1201
|
const frontmatter = [
|
|
946
1202
|
"---",
|
|
947
1203
|
`session_id: ${yamlString(session.sessionId)}`,
|
|
@@ -949,14 +1205,15 @@ function renderConversationMarkdown(session, messages, events = []) {
|
|
|
949
1205
|
`repo: ${yamlString(session.repoCanonical || "")}`,
|
|
950
1206
|
`scope: ${yamlString(session.scopeCanonical || "")}`,
|
|
951
1207
|
`cwd: ${yamlString(session.cwd || "")}`,
|
|
952
|
-
`started_at: ${yamlString(
|
|
953
|
-
`ended_at: ${yamlString(
|
|
1208
|
+
`started_at: ${yamlString(time.startedAt || "")}`,
|
|
1209
|
+
`ended_at: ${yamlString(time.endedAt || "")}`,
|
|
954
1210
|
`source_type: ${yamlString(session.sourceType || "")}`,
|
|
955
1211
|
`storage_scope: ${yamlString(session.storageScope || "")}`,
|
|
956
1212
|
`chat_account_id: ${yamlString(session.chatAccountId || "")}`,
|
|
957
1213
|
`chat_display_name: ${yamlString(session.chatDisplayName || "")}`,
|
|
958
1214
|
`chat_project_path: ${yamlString(session.chatProjectPath || "")}`,
|
|
959
1215
|
`conversation_kind: ${yamlString(session.conversationKind || "")}`,
|
|
1216
|
+
`time_status: ${yamlString(time.status || session.timeStatus || "")}`,
|
|
960
1217
|
`archive_schema_version: ${yamlString(session.archiveSchemaVersion || 1)}`,
|
|
961
1218
|
`event_count: ${yamlString(events.length || session.eventCount || 0)}`,
|
|
962
1219
|
`parser_versions: ${yamlString(JSON.stringify(session.parserVersions || {}))}`,
|
|
@@ -974,7 +1231,7 @@ function renderConversationMarkdown(session, messages, events = []) {
|
|
|
974
1231
|
""
|
|
975
1232
|
);
|
|
976
1233
|
}
|
|
977
|
-
for (const message of
|
|
1234
|
+
for (const message of displayMessages) {
|
|
978
1235
|
body.push(`## ${roleLabel(message.role)} - ${message.timestamp || "unknown time"}`);
|
|
979
1236
|
body.push("");
|
|
980
1237
|
body.push(styleRedactionMarkersForMarkdown(String(message.content || "").trim()));
|
|
@@ -1108,6 +1365,8 @@ module.exports = {
|
|
|
1108
1365
|
deleteSessionArchive,
|
|
1109
1366
|
displayTranscriptMessages,
|
|
1110
1367
|
encodeSegment,
|
|
1368
|
+
estimateMessageTokens,
|
|
1369
|
+
estimateSessionUsage,
|
|
1111
1370
|
ensureConversationMarkdown,
|
|
1112
1371
|
ensureSessionView,
|
|
1113
1372
|
findSessionById,
|