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