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/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, redactedMessages),
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, redactedMessages, events), { mode: 0o600 });
151
- fs.writeFileSync(transcriptPath, redactedMessages.map((message) => `${JSON.stringify(message)}\n`).join(""), {
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, redactedMessages, events));
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(redactedMessages);
189
+ const usageStats = mergeSessionUsage(computeSessionUsage(archivedMessages), sessionSummaryUsage(session.sessionSummary));
162
190
  if (usageStats) session.usage = usageStats;
163
- const models = collectSessionModels(redactedMessages);
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 finalTotalTokens = inputTokens + outputTokens || cumulativeTokens || maxUndifferentiatedTotalTokens;
657
+ const splitTotalTokens = inputTokens + outputTokens + reasoningOutputTokens + toolUsePromptTokens;
658
+ const finalTotalTokens = splitTotalTokens || cumulativeTokens || maxUndifferentiatedTotalTokens;
616
659
  if (!any && finalTotalTokens === 0) return null;
617
- return {
618
- inputTokens: inputTokens || totalInputTokens || (!outputTokens && finalTotalTokens ? finalTotalTokens : 0),
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 collectSessionModels(messages) {
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 = 1;
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
- messages.some((message) => /\[REDACTED:[^\]\n]+\]/.test(JSON.stringify(message))) ||
944
- events.some((event) => /\[REDACTED:[^\]\n]+\]/.test(JSON.stringify(event)));
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(session.startedAt || "")}`,
953
- `ended_at: ${yamlString(session.endedAt || "")}`,
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 messages) {
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,