cogpit-memory 0.1.4 → 0.1.7

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/dist/cli.js CHANGED
@@ -64,6 +64,9 @@ function isSystemMessage(msg) {
64
64
  function isSummaryMessage(msg) {
65
65
  return msg.type === "summary";
66
66
  }
67
+ function isCompactBoundary(msg) {
68
+ return msg.type === "system" && msg.subtype === "compact_boundary";
69
+ }
67
70
  function extractTextFromContent(content) {
68
71
  if (typeof content === "string") return content;
69
72
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -143,7 +146,7 @@ function buildTurns(messages) {
143
146
  agentBlockMap.set(parentId, block);
144
147
  }
145
148
  }
146
- function finalizeTurn() {
149
+ function finalizeTurn2() {
147
150
  if (!current) return;
148
151
  for (const tc of current.toolCalls) {
149
152
  flushSubAgentMessages(tc.id);
@@ -157,13 +160,21 @@ function buildTurns(messages) {
157
160
  }
158
161
  for (const msg of messages) {
159
162
  if (isSummaryMessage(msg)) {
160
- finalizeTurn();
163
+ finalizeTurn2();
161
164
  pendingCompaction = buildCompactionSummary(
162
165
  turns,
163
166
  msg.summary ?? "Conversation compacted"
164
167
  );
165
168
  continue;
166
169
  }
170
+ if (isCompactBoundary(msg)) {
171
+ finalizeTurn2();
172
+ pendingCompaction = buildCompactionSummary(
173
+ turns,
174
+ msg.content ?? "Conversation compacted"
175
+ );
176
+ continue;
177
+ }
167
178
  if (isUserMessage(msg) && !msg.isMeta) {
168
179
  const content = msg.message.content;
169
180
  if (typeof content !== "string" && Array.isArray(content)) {
@@ -225,7 +236,7 @@ function buildTurns(messages) {
225
236
  continue;
226
237
  }
227
238
  }
228
- finalizeTurn();
239
+ finalizeTurn2();
229
240
  current = {
230
241
  id: msg.uuid ?? crypto.randomUUID(),
231
242
  userMessage: msg.message.content,
@@ -440,7 +451,7 @@ function buildTurns(messages) {
440
451
  continue;
441
452
  }
442
453
  }
443
- finalizeTurn();
454
+ finalizeTurn2();
444
455
  return turns;
445
456
  }
446
457
 
@@ -593,6 +604,315 @@ function computeStats(turns) {
593
604
  return stats;
594
605
  }
595
606
 
607
+ // src/lib/codex.ts
608
+ var SKIP_PROMPT_PREFIXES = [
609
+ "<environment_context>",
610
+ "<permissions instructions>",
611
+ "<collaboration_mode>",
612
+ "<skills_instructions>"
613
+ ];
614
+ function randomTurnId(prefix) {
615
+ return globalThis.crypto?.randomUUID?.() ?? `${prefix}-${Math.random().toString(36).slice(2, 10)}`;
616
+ }
617
+ function safeParseLine(line) {
618
+ try {
619
+ return JSON.parse(line);
620
+ } catch {
621
+ return null;
622
+ }
623
+ }
624
+ function isObject(value) {
625
+ return typeof value === "object" && value !== null;
626
+ }
627
+ function isCodexRecord(record) {
628
+ if (!record || typeof record.type !== "string") return false;
629
+ return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item";
630
+ }
631
+ function extractMessageText(payload, blockType) {
632
+ const content = payload?.content;
633
+ if (!Array.isArray(content)) return "";
634
+ return content.filter((block) => isObject(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
635
+ }
636
+ function normalizePromptText(text) {
637
+ const trimmed = text.trim();
638
+ if (!trimmed) return "";
639
+ if (SKIP_PROMPT_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) return "";
640
+ return trimmed;
641
+ }
642
+ function mergeTokenUsage2(existing, incoming) {
643
+ if (!existing) return { ...incoming };
644
+ return {
645
+ input_tokens: existing.input_tokens + incoming.input_tokens,
646
+ output_tokens: existing.output_tokens + incoming.output_tokens,
647
+ cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
648
+ cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
649
+ };
650
+ }
651
+ function parseTokenUsage(value) {
652
+ if (!isObject(value)) return null;
653
+ const inputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
654
+ const outputTokens = typeof value.output_tokens === "number" ? value.output_tokens : 0;
655
+ const cacheCreation = typeof value.cache_creation_input_tokens === "number" ? value.cache_creation_input_tokens : 0;
656
+ const cacheRead = typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0;
657
+ if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
658
+ return {
659
+ input_tokens: inputTokens,
660
+ output_tokens: outputTokens,
661
+ cache_creation_input_tokens: cacheCreation,
662
+ cache_read_input_tokens: cacheRead
663
+ };
664
+ }
665
+ function appendAssistantText(turn, text, timestamp) {
666
+ if (!text) return;
667
+ turn.assistantText.push(text);
668
+ const last = turn.contentBlocks[turn.contentBlocks.length - 1];
669
+ if (last && last.kind === "text") {
670
+ last.text.push(text);
671
+ return;
672
+ }
673
+ turn.contentBlocks.push({ kind: "text", text: [text], timestamp });
674
+ }
675
+ function appendThinking(turn, text, timestamp) {
676
+ if (!text) return;
677
+ const block = { type: "thinking", thinking: text, signature: "" };
678
+ turn.thinking.push(block);
679
+ const last = turn.contentBlocks[turn.contentBlocks.length - 1];
680
+ if (last && last.kind === "thinking") {
681
+ last.blocks.push(block);
682
+ return;
683
+ }
684
+ turn.contentBlocks.push({ kind: "thinking", blocks: [block], timestamp });
685
+ }
686
+ function appendToolCall(turn, toolCall, timestamp) {
687
+ turn.toolCalls.push(toolCall);
688
+ const last = turn.contentBlocks[turn.contentBlocks.length - 1];
689
+ if (last && last.kind === "tool_calls" && last.timestamp === timestamp) {
690
+ last.toolCalls.push(toolCall);
691
+ return;
692
+ }
693
+ turn.contentBlocks.push({ kind: "tool_calls", toolCalls: [toolCall], timestamp });
694
+ }
695
+ function finalizeTurn(turns, current, lastTimestamp) {
696
+ if (!current) return null;
697
+ const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
698
+ if (!hasContent) return null;
699
+ if (current.timestamp && lastTimestamp) {
700
+ const start = new Date(current.timestamp).getTime();
701
+ const end = new Date(lastTimestamp).getTime();
702
+ if (Number.isFinite(start) && Number.isFinite(end) && end >= start) {
703
+ current.durationMs = end - start;
704
+ }
705
+ }
706
+ turns.push(current);
707
+ return null;
708
+ }
709
+ function parseToolInput(argumentsText) {
710
+ if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
711
+ try {
712
+ const parsed = JSON.parse(argumentsText);
713
+ return isObject(parsed) ? parsed : { value: parsed };
714
+ } catch {
715
+ return { raw: argumentsText };
716
+ }
717
+ }
718
+ function inferToolError(output) {
719
+ if (!output) return false;
720
+ const exitMatch = output.match(/Process exited with code (\d+)/);
721
+ if (exitMatch) return exitMatch[1] !== "0";
722
+ return /\b(error|failed|exception)\b/i.test(output);
723
+ }
724
+ function createTurn(turnId, timestamp, model) {
725
+ return {
726
+ id: turnId || randomTurnId("codex-turn"),
727
+ userMessage: null,
728
+ contentBlocks: [],
729
+ thinking: [],
730
+ assistantText: [],
731
+ toolCalls: [],
732
+ subAgentActivity: [],
733
+ timestamp,
734
+ durationMs: null,
735
+ tokenUsage: null,
736
+ model
737
+ };
738
+ }
739
+ function extractPromptFromRecord(record) {
740
+ if (record.type === "event_msg" && isObject(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
741
+ return normalizePromptText(record.payload.message);
742
+ }
743
+ if (record.type === "response_item" && isObject(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
744
+ return normalizePromptText(extractMessageText(record.payload, "input_text"));
745
+ }
746
+ return "";
747
+ }
748
+ function extractMetadataFromRecords(records) {
749
+ let sessionId = "";
750
+ let version = "";
751
+ let gitBranch = "";
752
+ let cwd = "";
753
+ let model = "";
754
+ let branchedFrom;
755
+ let firstUserMessage = "";
756
+ let lastUserMessage = "";
757
+ let timestamp = "";
758
+ let lastTimestamp = "";
759
+ let turnCount = 0;
760
+ let previousPrompt = "";
761
+ for (const record of records) {
762
+ if (!isObject(record.payload)) continue;
763
+ if (record.type === "session_meta") {
764
+ sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
765
+ version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
766
+ cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
767
+ if (!branchedFrom && isObject(record.payload.branchedFrom)) {
768
+ const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
769
+ if (sourceId) {
770
+ branchedFrom = {
771
+ sessionId: sourceId,
772
+ turnIndex: typeof record.payload.branchedFrom.turnIndex === "number" ? record.payload.branchedFrom.turnIndex : null
773
+ };
774
+ }
775
+ }
776
+ const git = isObject(record.payload.git) ? record.payload.git : null;
777
+ gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
778
+ }
779
+ if (record.type === "turn_context") {
780
+ model ||= typeof record.payload.model === "string" ? record.payload.model : "";
781
+ cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
782
+ }
783
+ const prompt = extractPromptFromRecord(record);
784
+ if (!prompt) continue;
785
+ if (prompt === previousPrompt) continue;
786
+ if (!firstUserMessage) firstUserMessage = prompt;
787
+ lastUserMessage = prompt;
788
+ previousPrompt = prompt;
789
+ turnCount++;
790
+ if (!timestamp) timestamp = record.timestamp ?? "";
791
+ lastTimestamp = record.timestamp ?? lastTimestamp;
792
+ }
793
+ if (lastTimestamp === "") lastTimestamp = timestamp;
794
+ return {
795
+ sessionId,
796
+ version,
797
+ gitBranch,
798
+ cwd,
799
+ model,
800
+ slug: "",
801
+ branchedFrom,
802
+ firstUserMessage,
803
+ lastUserMessage,
804
+ timestamp,
805
+ lastTimestamp,
806
+ turnCount
807
+ };
808
+ }
809
+ function isCodexSessionText(jsonlText) {
810
+ for (const line of jsonlText.split("\n")) {
811
+ const trimmed = line.trim();
812
+ if (!trimmed) continue;
813
+ return isCodexRecord(safeParseLine(trimmed));
814
+ }
815
+ return false;
816
+ }
817
+ function extractCodexMetadataFromLines(lines) {
818
+ const records = lines.map(safeParseLine).filter(isCodexRecord);
819
+ return extractMetadataFromRecords(records);
820
+ }
821
+ function parseCodexSession(jsonlText) {
822
+ const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
823
+ const metadata = extractMetadataFromRecords(records);
824
+ const turns = [];
825
+ const pendingToolCalls = /* @__PURE__ */ new Map();
826
+ let current = null;
827
+ let currentTurnId = null;
828
+ let currentModel = metadata.model || null;
829
+ let lastTurnTimestamp = "";
830
+ for (const record of records) {
831
+ const payload = isObject(record.payload) ? record.payload : void 0;
832
+ const timestamp = record.timestamp ?? "";
833
+ if (record.type === "turn_context") {
834
+ current = finalizeTurn(turns, current, lastTurnTimestamp);
835
+ currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
836
+ currentModel = typeof payload?.model === "string" ? payload.model : currentModel;
837
+ lastTurnTimestamp = timestamp;
838
+ continue;
839
+ }
840
+ if (record.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
841
+ if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
842
+ current = finalizeTurn(turns, current, lastTurnTimestamp);
843
+ }
844
+ current ??= createTurn(currentTurnId, timestamp, currentModel);
845
+ current.userMessage = payload.message;
846
+ current.timestamp = current.timestamp || timestamp;
847
+ lastTurnTimestamp = timestamp;
848
+ continue;
849
+ }
850
+ current ??= createTurn(currentTurnId, timestamp, currentModel);
851
+ if (!current.model && currentModel) current.model = currentModel;
852
+ if (!current.timestamp) current.timestamp = timestamp;
853
+ if (timestamp) lastTurnTimestamp = timestamp;
854
+ if (record.type === "event_msg" && payload?.type === "token_count") {
855
+ const info = isObject(payload.info) ? payload.info : null;
856
+ const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
857
+ if (lastUsage) {
858
+ current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
859
+ }
860
+ continue;
861
+ }
862
+ if (record.type !== "response_item" || !payload) continue;
863
+ if (payload.type === "reasoning") {
864
+ const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
865
+ appendThinking(current, summary.trim(), timestamp);
866
+ continue;
867
+ }
868
+ if (payload.type === "message" && payload.role === "assistant") {
869
+ appendAssistantText(current, extractMessageText(payload, "output_text"), timestamp);
870
+ continue;
871
+ }
872
+ if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
873
+ const text = normalizePromptText(extractMessageText(payload, "input_text"));
874
+ if (text) current.userMessage = text;
875
+ continue;
876
+ }
877
+ if (payload.type === "function_call" && typeof payload.call_id === "string") {
878
+ const toolCall = {
879
+ id: payload.call_id,
880
+ name: typeof payload.name === "string" ? payload.name : "tool",
881
+ input: parseToolInput(payload.arguments),
882
+ result: null,
883
+ isError: false,
884
+ timestamp
885
+ };
886
+ pendingToolCalls.set(toolCall.id, toolCall);
887
+ appendToolCall(current, toolCall, timestamp);
888
+ continue;
889
+ }
890
+ if (payload.type === "function_call_output" && typeof payload.call_id === "string") {
891
+ const toolCall = pendingToolCalls.get(payload.call_id);
892
+ if (!toolCall) continue;
893
+ const output = typeof payload.output === "string" ? payload.output : null;
894
+ toolCall.result = output;
895
+ toolCall.isError = inferToolError(output);
896
+ pendingToolCalls.delete(payload.call_id);
897
+ continue;
898
+ }
899
+ }
900
+ finalizeTurn(turns, current, lastTurnTimestamp);
901
+ return {
902
+ sessionId: metadata.sessionId,
903
+ version: metadata.version,
904
+ gitBranch: metadata.gitBranch,
905
+ cwd: metadata.cwd,
906
+ slug: metadata.slug,
907
+ model: metadata.model,
908
+ turns,
909
+ stats: computeStats(turns),
910
+ rawMessages: records,
911
+ branchedFrom: metadata.branchedFrom,
912
+ agentKind: "codex"
913
+ };
914
+ }
915
+
596
916
  // src/lib/parser.ts
597
917
  function isAssistantMessage2(msg) {
598
918
  return msg.type === "assistant";
@@ -632,6 +952,9 @@ function extractSessionMetadata(messages) {
632
952
  return meta;
633
953
  }
634
954
  function parseSession(jsonlText) {
955
+ if (isCodexSessionText(jsonlText)) {
956
+ return parseCodexSession(jsonlText);
957
+ }
635
958
  const rawMessages = parseLines(jsonlText);
636
959
  const metadata = extractSessionMetadata(rawMessages);
637
960
  const turns = buildTurns(rawMessages);
@@ -640,7 +963,8 @@ function parseSession(jsonlText) {
640
963
  ...metadata,
641
964
  turns,
642
965
  stats,
643
- rawMessages
966
+ rawMessages,
967
+ agentKind: "claude"
644
968
  };
645
969
  }
646
970
  function getUserMessageText(content) {
@@ -1143,6 +1467,29 @@ async function findJsonlPath(sessionId) {
1143
1467
  }
1144
1468
  } catch {
1145
1469
  }
1470
+ const codexRoot = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".codex", "sessions");
1471
+ const walk = async (dir, depth) => {
1472
+ if (depth > 4) return null;
1473
+ let entries;
1474
+ try {
1475
+ entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
1476
+ } catch {
1477
+ return null;
1478
+ }
1479
+ for (const entry of entries) {
1480
+ const filePath = (0, import_node_path3.join)(dir, entry.name);
1481
+ if (entry.isDirectory()) {
1482
+ const match = await walk(filePath, depth + 1);
1483
+ if (match) return match;
1484
+ continue;
1485
+ }
1486
+ if (!entry.name.endsWith(".jsonl")) continue;
1487
+ if (entry.name.endsWith(`${sessionId}.jsonl`)) return filePath;
1488
+ }
1489
+ return null;
1490
+ };
1491
+ const codexMatch = await walk(codexRoot, 0);
1492
+ if (codexMatch) return codexMatch;
1146
1493
  return null;
1147
1494
  }
1148
1495
  async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
@@ -1201,6 +1548,7 @@ async function searchSessions(query, opts, searchIndex) {
1201
1548
  try {
1202
1549
  index = new SearchIndex(DEFAULT_DB_PATH);
1203
1550
  ownedIndex = true;
1551
+ index.updateStale(dirs.PROJECTS_DIR);
1204
1552
  } catch {
1205
1553
  }
1206
1554
  }
@@ -1274,6 +1622,14 @@ async function cwdFromFilePath(filePath) {
1274
1622
  cwdCache.set(filePath, obj.cwd);
1275
1623
  return obj.cwd;
1276
1624
  }
1625
+ if (obj.type === "session_meta" && obj.payload?.cwd) {
1626
+ cwdCache.set(filePath, obj.payload.cwd);
1627
+ return obj.payload.cwd;
1628
+ }
1629
+ if (obj.type === "turn_context" && obj.payload?.cwd) {
1630
+ cwdCache.set(filePath, obj.payload.cwd);
1631
+ return obj.payload.cwd;
1632
+ }
1277
1633
  } catch {
1278
1634
  }
1279
1635
  }
@@ -1503,7 +1859,7 @@ async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive, d
1503
1859
  sessionsSearched,
1504
1860
  results
1505
1861
  };
1506
- } catch (err) {
1862
+ } catch {
1507
1863
  return {
1508
1864
  query,
1509
1865
  totalHits: 0,
@@ -1782,12 +2138,17 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
1782
2138
  // src/commands/sessions.ts
1783
2139
  var import_promises5 = require("node:fs/promises");
1784
2140
  var import_node_path6 = require("node:path");
2141
+ var import_node_os3 = require("node:os");
1785
2142
 
1786
2143
  // src/lib/metadata.ts
1787
2144
  var import_promises4 = require("node:fs/promises");
1788
2145
 
1789
2146
  // src/lib/sessionStatus.ts
1790
2147
  function deriveSessionStatus(rawMessages) {
2148
+ const firstType = rawMessages[0]?.type;
2149
+ if (firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item") {
2150
+ return deriveCodexSessionStatus(rawMessages);
2151
+ }
1791
2152
  let pendingEnqueues = 0;
1792
2153
  function result(status, toolName) {
1793
2154
  const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
@@ -1828,7 +2189,38 @@ function deriveSessionStatus(rawMessages) {
1828
2189
  if (isMeta) continue;
1829
2190
  return result("processing");
1830
2191
  }
1831
- if (msg.type === "summary") return result("compacting");
2192
+ if (msg.type === "summary") continue;
2193
+ if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
2194
+ }
2195
+ return { status: "idle" };
2196
+ }
2197
+ function deriveCodexSessionStatus(rawMessages) {
2198
+ for (let i = rawMessages.length - 1; i >= 0; i--) {
2199
+ const msg = rawMessages[i];
2200
+ if (msg.type === "event_msg") {
2201
+ const payload = msg.payload;
2202
+ switch (payload?.type) {
2203
+ case "task_complete":
2204
+ return { status: "completed" };
2205
+ case "task_started":
2206
+ return { status: "processing" };
2207
+ case "agent_message":
2208
+ return { status: "thinking" };
2209
+ case "token_count":
2210
+ continue;
2211
+ }
2212
+ }
2213
+ if (msg.type === "response_item") {
2214
+ const payload = msg.payload;
2215
+ if (!payload) continue;
2216
+ if (payload.type === "function_call") {
2217
+ return { status: "tool_use", toolName: payload.name };
2218
+ }
2219
+ if (payload.type === "message") {
2220
+ if (payload.role === "assistant") return { status: "thinking" };
2221
+ if (payload.role === "user") return { status: "processing" };
2222
+ }
2223
+ }
1832
2224
  }
1833
2225
  return { status: "idle" };
1834
2226
  }
@@ -1854,6 +2246,36 @@ async function getSessionMeta(filePath) {
1854
2246
  const content = await (0, import_promises4.readFile)(filePath, "utf-8");
1855
2247
  lines = content.split("\n").filter(Boolean);
1856
2248
  }
2249
+ let firstParsed = null;
2250
+ if (lines.length > 0) {
2251
+ try {
2252
+ firstParsed = JSON.parse(lines[0]);
2253
+ } catch {
2254
+ firstParsed = null;
2255
+ }
2256
+ }
2257
+ const isCodex = firstParsed?.type === "session_meta" || firstParsed?.type === "turn_context";
2258
+ if (isCodex) {
2259
+ if (isPartialRead) {
2260
+ const content = await (0, import_promises4.readFile)(filePath, "utf-8");
2261
+ lines = content.split("\n").filter(Boolean);
2262
+ }
2263
+ const meta = extractCodexMetadataFromLines(lines);
2264
+ return {
2265
+ sessionId: meta.sessionId,
2266
+ version: meta.version,
2267
+ gitBranch: meta.gitBranch,
2268
+ model: meta.model,
2269
+ slug: meta.slug,
2270
+ cwd: meta.cwd,
2271
+ firstUserMessage: meta.firstUserMessage,
2272
+ lastUserMessage: meta.lastUserMessage,
2273
+ timestamp: meta.timestamp,
2274
+ turnCount: meta.turnCount,
2275
+ lineCount: lines.length,
2276
+ branchedFrom: meta.branchedFrom
2277
+ };
2278
+ }
1857
2279
  let sessionId = "";
1858
2280
  let version = "";
1859
2281
  let gitBranch = "";
@@ -1950,6 +2372,30 @@ async function getSessionStatus(filePath) {
1950
2372
  } catch {
1951
2373
  continue;
1952
2374
  }
2375
+ if (obj.type === "event_msg") {
2376
+ const payload = obj.payload;
2377
+ switch (payload?.type) {
2378
+ case "task_complete":
2379
+ return { status: "completed" };
2380
+ case "task_started":
2381
+ return { status: "processing" };
2382
+ case "agent_message":
2383
+ return { status: "thinking" };
2384
+ case "token_count":
2385
+ continue;
2386
+ }
2387
+ }
2388
+ if (obj.type === "response_item") {
2389
+ const payload = obj.payload;
2390
+ if (payload?.type === "function_call") {
2391
+ return { status: "tool_use", toolName: payload.name };
2392
+ }
2393
+ if (payload?.type === "message") {
2394
+ const role = payload.role;
2395
+ if (role === "assistant") return { status: "thinking" };
2396
+ if (role === "user") return { status: "processing" };
2397
+ }
2398
+ }
1953
2399
  if (obj.type === "assistant" || obj.type === "user" || obj.type === "queue-operation") {
1954
2400
  meaningful.unshift(obj);
1955
2401
  const isEndTurn = obj.type === "assistant" && obj.message?.stop_reason === "end_turn";
@@ -1968,6 +2414,35 @@ async function getSessionStatus(filePath) {
1968
2414
  }
1969
2415
 
1970
2416
  // src/commands/sessions.ts
2417
+ var CODEX_SESSIONS_DIR = (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".codex", "sessions");
2418
+ async function listCodexSessionFiles(cutoff) {
2419
+ const walk = async (dir, depth) => {
2420
+ if (depth > 4) return [];
2421
+ let entries;
2422
+ try {
2423
+ entries = await (0, import_promises5.readdir)(dir, { withFileTypes: true });
2424
+ } catch {
2425
+ return [];
2426
+ }
2427
+ const results = [];
2428
+ for (const entry of entries) {
2429
+ const filePath = (0, import_node_path6.join)(dir, entry.name);
2430
+ if (entry.isDirectory()) {
2431
+ results.push(...await walk(filePath, depth + 1));
2432
+ continue;
2433
+ }
2434
+ if (!entry.name.endsWith(".jsonl")) continue;
2435
+ try {
2436
+ const s = await (0, import_promises5.stat)(filePath);
2437
+ if (s.mtimeMs >= cutoff) results.push({ path: filePath, mtimeMs: s.mtimeMs });
2438
+ } catch {
2439
+ continue;
2440
+ }
2441
+ }
2442
+ return results;
2443
+ };
2444
+ return walk(CODEX_SESSIONS_DIR, 0);
2445
+ }
1971
2446
  async function listSessions(opts = {}) {
1972
2447
  const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
1973
2448
  const maxAgeMs = parseMaxAge(opts.maxAge ?? "7d");
@@ -2001,7 +2476,8 @@ async function listSessions(opts = {}) {
2001
2476
  }
2002
2477
  })
2003
2478
  );
2004
- const allFiles = nested.flat();
2479
+ const codexFiles = await listCodexSessionFiles(cutoff);
2480
+ const allFiles = [...nested.flat(), ...codexFiles];
2005
2481
  allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
2006
2482
  const results = [];
2007
2483
  for (const file of allFiles) {
@@ -2024,7 +2500,8 @@ async function listSessions(opts = {}) {
2024
2500
  lastMessage: meta.lastUserMessage,
2025
2501
  turnCount: meta.turnCount,
2026
2502
  status: statusInfo.status,
2027
- mtime: file.mtimeMs
2503
+ mtime: file.mtimeMs,
2504
+ source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2028
2505
  });
2029
2506
  } catch {
2030
2507
  }
@@ -2038,10 +2515,9 @@ async function currentSession(cwd) {
2038
2515
  try {
2039
2516
  files = await (0, import_promises5.readdir)(projectDir);
2040
2517
  } catch {
2041
- return null;
2518
+ files = [];
2042
2519
  }
2043
2520
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2044
- if (jsonlFiles.length === 0) return null;
2045
2521
  const statResults = await Promise.all(
2046
2522
  jsonlFiles.map(async (f) => {
2047
2523
  const filePath = (0, import_node_path6.join)(projectDir, f);
@@ -2053,7 +2529,21 @@ async function currentSession(cwd) {
2053
2529
  }
2054
2530
  })
2055
2531
  );
2056
- const valid = statResults.filter((r) => r !== null);
2532
+ const codexCandidates = await listCodexSessionFiles(0);
2533
+ const codexMatches = [];
2534
+ for (const file of codexCandidates) {
2535
+ try {
2536
+ const meta2 = await getSessionMeta(file.path);
2537
+ if (meta2.cwd === cwd) codexMatches.push(file);
2538
+ } catch {
2539
+ continue;
2540
+ }
2541
+ }
2542
+ const valid = [
2543
+ ...statResults.filter((r) => r !== null),
2544
+ ...codexMatches
2545
+ ];
2546
+ if (valid.length === 0) return null;
2057
2547
  valid.sort((a, b) => b.mtimeMs - a.mtimeMs);
2058
2548
  const latest = valid[0];
2059
2549
  if (!latest) return null;
@@ -2073,7 +2563,8 @@ async function currentSession(cwd) {
2073
2563
  lastMessage: meta.lastUserMessage,
2074
2564
  turnCount: meta.turnCount,
2075
2565
  status: statusInfo.status,
2076
- mtime: latest.mtimeMs
2566
+ mtime: latest.mtimeMs,
2567
+ source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2077
2568
  };
2078
2569
  }
2079
2570
 
@@ -2155,6 +2646,12 @@ function parseArgs(argv) {
2155
2646
  case "--limit":
2156
2647
  args.limit = parseInt(argv[++i], 10);
2157
2648
  break;
2649
+ case "--session-limit":
2650
+ args.sessionLimit = parseInt(argv[++i], 10);
2651
+ break;
2652
+ case "--hits-per-session":
2653
+ args.hitsPerSession = parseInt(argv[++i], 10);
2654
+ break;
2158
2655
  case "--case-sensitive":
2159
2656
  args.caseSensitive = true;
2160
2657
  break;
@@ -2236,6 +2733,15 @@ async function main() {
2236
2733
  limit: cmd.args.limit,
2237
2734
  caseSensitive: cmd.args.caseSensitive
2238
2735
  });
2736
+ if (result && !("error" in result) && (cmd.args.sessionLimit || cmd.args.hitsPerSession)) {
2737
+ const sl = cmd.args.sessionLimit ?? result.results.length;
2738
+ const hpp = cmd.args.hitsPerSession ?? Infinity;
2739
+ result.results = result.results.slice(0, sl).map((sr) => ({
2740
+ ...sr,
2741
+ hits: sr.hits.slice(0, hpp)
2742
+ }));
2743
+ result.returnedHits = result.results.reduce((n, sr) => n + sr.hits.length, 0);
2744
+ }
2239
2745
  break;
2240
2746
  case "context":
2241
2747
  if (!cmd.args.sessionId) {
@@ -2288,6 +2794,8 @@ Commands:
2288
2794
  --session <id> Scope to single session
2289
2795
  --max-age <5d> Time window (default: 5d)
2290
2796
  --limit <20> Max hits (default: 20)
2797
+ --session-limit <N> Cap unique sessions returned
2798
+ --hits-per-session <N> Max hits kept per session
2291
2799
  --case-sensitive Case sensitive matching
2292
2800
 
2293
2801
  context <sessionId> Session overview (L1)