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/index.js CHANGED
@@ -73,6 +73,9 @@ function isSystemMessage(msg) {
73
73
  function isSummaryMessage(msg) {
74
74
  return msg.type === "summary";
75
75
  }
76
+ function isCompactBoundary(msg) {
77
+ return msg.type === "system" && msg.subtype === "compact_boundary";
78
+ }
76
79
  function extractTextFromContent(content) {
77
80
  if (typeof content === "string") return content;
78
81
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -152,7 +155,7 @@ function buildTurns(messages) {
152
155
  agentBlockMap.set(parentId, block);
153
156
  }
154
157
  }
155
- function finalizeTurn() {
158
+ function finalizeTurn2() {
156
159
  if (!current) return;
157
160
  for (const tc of current.toolCalls) {
158
161
  flushSubAgentMessages(tc.id);
@@ -166,13 +169,21 @@ function buildTurns(messages) {
166
169
  }
167
170
  for (const msg of messages) {
168
171
  if (isSummaryMessage(msg)) {
169
- finalizeTurn();
172
+ finalizeTurn2();
170
173
  pendingCompaction = buildCompactionSummary(
171
174
  turns,
172
175
  msg.summary ?? "Conversation compacted"
173
176
  );
174
177
  continue;
175
178
  }
179
+ if (isCompactBoundary(msg)) {
180
+ finalizeTurn2();
181
+ pendingCompaction = buildCompactionSummary(
182
+ turns,
183
+ msg.content ?? "Conversation compacted"
184
+ );
185
+ continue;
186
+ }
176
187
  if (isUserMessage(msg) && !msg.isMeta) {
177
188
  const content = msg.message.content;
178
189
  if (typeof content !== "string" && Array.isArray(content)) {
@@ -234,7 +245,7 @@ function buildTurns(messages) {
234
245
  continue;
235
246
  }
236
247
  }
237
- finalizeTurn();
248
+ finalizeTurn2();
238
249
  current = {
239
250
  id: msg.uuid ?? crypto.randomUUID(),
240
251
  userMessage: msg.message.content,
@@ -449,7 +460,7 @@ function buildTurns(messages) {
449
460
  continue;
450
461
  }
451
462
  }
452
- finalizeTurn();
463
+ finalizeTurn2();
453
464
  return turns;
454
465
  }
455
466
 
@@ -602,6 +613,315 @@ function computeStats(turns) {
602
613
  return stats;
603
614
  }
604
615
 
616
+ // src/lib/codex.ts
617
+ var SKIP_PROMPT_PREFIXES = [
618
+ "<environment_context>",
619
+ "<permissions instructions>",
620
+ "<collaboration_mode>",
621
+ "<skills_instructions>"
622
+ ];
623
+ function randomTurnId(prefix) {
624
+ return globalThis.crypto?.randomUUID?.() ?? `${prefix}-${Math.random().toString(36).slice(2, 10)}`;
625
+ }
626
+ function safeParseLine(line) {
627
+ try {
628
+ return JSON.parse(line);
629
+ } catch {
630
+ return null;
631
+ }
632
+ }
633
+ function isObject(value) {
634
+ return typeof value === "object" && value !== null;
635
+ }
636
+ function isCodexRecord(record) {
637
+ if (!record || typeof record.type !== "string") return false;
638
+ return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item";
639
+ }
640
+ function extractMessageText(payload, blockType) {
641
+ const content = payload?.content;
642
+ if (!Array.isArray(content)) return "";
643
+ return content.filter((block) => isObject(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
644
+ }
645
+ function normalizePromptText(text) {
646
+ const trimmed = text.trim();
647
+ if (!trimmed) return "";
648
+ if (SKIP_PROMPT_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) return "";
649
+ return trimmed;
650
+ }
651
+ function mergeTokenUsage2(existing, incoming) {
652
+ if (!existing) return { ...incoming };
653
+ return {
654
+ input_tokens: existing.input_tokens + incoming.input_tokens,
655
+ output_tokens: existing.output_tokens + incoming.output_tokens,
656
+ cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
657
+ cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
658
+ };
659
+ }
660
+ function parseTokenUsage(value) {
661
+ if (!isObject(value)) return null;
662
+ const inputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
663
+ const outputTokens = typeof value.output_tokens === "number" ? value.output_tokens : 0;
664
+ const cacheCreation = typeof value.cache_creation_input_tokens === "number" ? value.cache_creation_input_tokens : 0;
665
+ const cacheRead = typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0;
666
+ if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
667
+ return {
668
+ input_tokens: inputTokens,
669
+ output_tokens: outputTokens,
670
+ cache_creation_input_tokens: cacheCreation,
671
+ cache_read_input_tokens: cacheRead
672
+ };
673
+ }
674
+ function appendAssistantText(turn, text, timestamp) {
675
+ if (!text) return;
676
+ turn.assistantText.push(text);
677
+ const last = turn.contentBlocks[turn.contentBlocks.length - 1];
678
+ if (last && last.kind === "text") {
679
+ last.text.push(text);
680
+ return;
681
+ }
682
+ turn.contentBlocks.push({ kind: "text", text: [text], timestamp });
683
+ }
684
+ function appendThinking(turn, text, timestamp) {
685
+ if (!text) return;
686
+ const block = { type: "thinking", thinking: text, signature: "" };
687
+ turn.thinking.push(block);
688
+ const last = turn.contentBlocks[turn.contentBlocks.length - 1];
689
+ if (last && last.kind === "thinking") {
690
+ last.blocks.push(block);
691
+ return;
692
+ }
693
+ turn.contentBlocks.push({ kind: "thinking", blocks: [block], timestamp });
694
+ }
695
+ function appendToolCall(turn, toolCall, timestamp) {
696
+ turn.toolCalls.push(toolCall);
697
+ const last = turn.contentBlocks[turn.contentBlocks.length - 1];
698
+ if (last && last.kind === "tool_calls" && last.timestamp === timestamp) {
699
+ last.toolCalls.push(toolCall);
700
+ return;
701
+ }
702
+ turn.contentBlocks.push({ kind: "tool_calls", toolCalls: [toolCall], timestamp });
703
+ }
704
+ function finalizeTurn(turns, current, lastTimestamp) {
705
+ if (!current) return null;
706
+ const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
707
+ if (!hasContent) return null;
708
+ if (current.timestamp && lastTimestamp) {
709
+ const start = new Date(current.timestamp).getTime();
710
+ const end = new Date(lastTimestamp).getTime();
711
+ if (Number.isFinite(start) && Number.isFinite(end) && end >= start) {
712
+ current.durationMs = end - start;
713
+ }
714
+ }
715
+ turns.push(current);
716
+ return null;
717
+ }
718
+ function parseToolInput(argumentsText) {
719
+ if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
720
+ try {
721
+ const parsed = JSON.parse(argumentsText);
722
+ return isObject(parsed) ? parsed : { value: parsed };
723
+ } catch {
724
+ return { raw: argumentsText };
725
+ }
726
+ }
727
+ function inferToolError(output) {
728
+ if (!output) return false;
729
+ const exitMatch = output.match(/Process exited with code (\d+)/);
730
+ if (exitMatch) return exitMatch[1] !== "0";
731
+ return /\b(error|failed|exception)\b/i.test(output);
732
+ }
733
+ function createTurn(turnId, timestamp, model) {
734
+ return {
735
+ id: turnId || randomTurnId("codex-turn"),
736
+ userMessage: null,
737
+ contentBlocks: [],
738
+ thinking: [],
739
+ assistantText: [],
740
+ toolCalls: [],
741
+ subAgentActivity: [],
742
+ timestamp,
743
+ durationMs: null,
744
+ tokenUsage: null,
745
+ model
746
+ };
747
+ }
748
+ function extractPromptFromRecord(record) {
749
+ if (record.type === "event_msg" && isObject(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
750
+ return normalizePromptText(record.payload.message);
751
+ }
752
+ if (record.type === "response_item" && isObject(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
753
+ return normalizePromptText(extractMessageText(record.payload, "input_text"));
754
+ }
755
+ return "";
756
+ }
757
+ function extractMetadataFromRecords(records) {
758
+ let sessionId = "";
759
+ let version = "";
760
+ let gitBranch = "";
761
+ let cwd = "";
762
+ let model = "";
763
+ let branchedFrom;
764
+ let firstUserMessage = "";
765
+ let lastUserMessage = "";
766
+ let timestamp = "";
767
+ let lastTimestamp = "";
768
+ let turnCount = 0;
769
+ let previousPrompt = "";
770
+ for (const record of records) {
771
+ if (!isObject(record.payload)) continue;
772
+ if (record.type === "session_meta") {
773
+ sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
774
+ version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
775
+ cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
776
+ if (!branchedFrom && isObject(record.payload.branchedFrom)) {
777
+ const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
778
+ if (sourceId) {
779
+ branchedFrom = {
780
+ sessionId: sourceId,
781
+ turnIndex: typeof record.payload.branchedFrom.turnIndex === "number" ? record.payload.branchedFrom.turnIndex : null
782
+ };
783
+ }
784
+ }
785
+ const git = isObject(record.payload.git) ? record.payload.git : null;
786
+ gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
787
+ }
788
+ if (record.type === "turn_context") {
789
+ model ||= typeof record.payload.model === "string" ? record.payload.model : "";
790
+ cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
791
+ }
792
+ const prompt = extractPromptFromRecord(record);
793
+ if (!prompt) continue;
794
+ if (prompt === previousPrompt) continue;
795
+ if (!firstUserMessage) firstUserMessage = prompt;
796
+ lastUserMessage = prompt;
797
+ previousPrompt = prompt;
798
+ turnCount++;
799
+ if (!timestamp) timestamp = record.timestamp ?? "";
800
+ lastTimestamp = record.timestamp ?? lastTimestamp;
801
+ }
802
+ if (lastTimestamp === "") lastTimestamp = timestamp;
803
+ return {
804
+ sessionId,
805
+ version,
806
+ gitBranch,
807
+ cwd,
808
+ model,
809
+ slug: "",
810
+ branchedFrom,
811
+ firstUserMessage,
812
+ lastUserMessage,
813
+ timestamp,
814
+ lastTimestamp,
815
+ turnCount
816
+ };
817
+ }
818
+ function isCodexSessionText(jsonlText) {
819
+ for (const line of jsonlText.split("\n")) {
820
+ const trimmed = line.trim();
821
+ if (!trimmed) continue;
822
+ return isCodexRecord(safeParseLine(trimmed));
823
+ }
824
+ return false;
825
+ }
826
+ function extractCodexMetadataFromLines(lines) {
827
+ const records = lines.map(safeParseLine).filter(isCodexRecord);
828
+ return extractMetadataFromRecords(records);
829
+ }
830
+ function parseCodexSession(jsonlText) {
831
+ const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
832
+ const metadata = extractMetadataFromRecords(records);
833
+ const turns = [];
834
+ const pendingToolCalls = /* @__PURE__ */ new Map();
835
+ let current = null;
836
+ let currentTurnId = null;
837
+ let currentModel = metadata.model || null;
838
+ let lastTurnTimestamp = "";
839
+ for (const record of records) {
840
+ const payload = isObject(record.payload) ? record.payload : void 0;
841
+ const timestamp = record.timestamp ?? "";
842
+ if (record.type === "turn_context") {
843
+ current = finalizeTurn(turns, current, lastTurnTimestamp);
844
+ currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
845
+ currentModel = typeof payload?.model === "string" ? payload.model : currentModel;
846
+ lastTurnTimestamp = timestamp;
847
+ continue;
848
+ }
849
+ if (record.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
850
+ if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
851
+ current = finalizeTurn(turns, current, lastTurnTimestamp);
852
+ }
853
+ current ??= createTurn(currentTurnId, timestamp, currentModel);
854
+ current.userMessage = payload.message;
855
+ current.timestamp = current.timestamp || timestamp;
856
+ lastTurnTimestamp = timestamp;
857
+ continue;
858
+ }
859
+ current ??= createTurn(currentTurnId, timestamp, currentModel);
860
+ if (!current.model && currentModel) current.model = currentModel;
861
+ if (!current.timestamp) current.timestamp = timestamp;
862
+ if (timestamp) lastTurnTimestamp = timestamp;
863
+ if (record.type === "event_msg" && payload?.type === "token_count") {
864
+ const info = isObject(payload.info) ? payload.info : null;
865
+ const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
866
+ if (lastUsage) {
867
+ current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
868
+ }
869
+ continue;
870
+ }
871
+ if (record.type !== "response_item" || !payload) continue;
872
+ if (payload.type === "reasoning") {
873
+ const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
874
+ appendThinking(current, summary.trim(), timestamp);
875
+ continue;
876
+ }
877
+ if (payload.type === "message" && payload.role === "assistant") {
878
+ appendAssistantText(current, extractMessageText(payload, "output_text"), timestamp);
879
+ continue;
880
+ }
881
+ if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
882
+ const text = normalizePromptText(extractMessageText(payload, "input_text"));
883
+ if (text) current.userMessage = text;
884
+ continue;
885
+ }
886
+ if (payload.type === "function_call" && typeof payload.call_id === "string") {
887
+ const toolCall = {
888
+ id: payload.call_id,
889
+ name: typeof payload.name === "string" ? payload.name : "tool",
890
+ input: parseToolInput(payload.arguments),
891
+ result: null,
892
+ isError: false,
893
+ timestamp
894
+ };
895
+ pendingToolCalls.set(toolCall.id, toolCall);
896
+ appendToolCall(current, toolCall, timestamp);
897
+ continue;
898
+ }
899
+ if (payload.type === "function_call_output" && typeof payload.call_id === "string") {
900
+ const toolCall = pendingToolCalls.get(payload.call_id);
901
+ if (!toolCall) continue;
902
+ const output = typeof payload.output === "string" ? payload.output : null;
903
+ toolCall.result = output;
904
+ toolCall.isError = inferToolError(output);
905
+ pendingToolCalls.delete(payload.call_id);
906
+ continue;
907
+ }
908
+ }
909
+ finalizeTurn(turns, current, lastTurnTimestamp);
910
+ return {
911
+ sessionId: metadata.sessionId,
912
+ version: metadata.version,
913
+ gitBranch: metadata.gitBranch,
914
+ cwd: metadata.cwd,
915
+ slug: metadata.slug,
916
+ model: metadata.model,
917
+ turns,
918
+ stats: computeStats(turns),
919
+ rawMessages: records,
920
+ branchedFrom: metadata.branchedFrom,
921
+ agentKind: "codex"
922
+ };
923
+ }
924
+
605
925
  // src/lib/parser.ts
606
926
  function isAssistantMessage2(msg) {
607
927
  return msg.type === "assistant";
@@ -622,6 +942,13 @@ function parseLines(jsonlText) {
622
942
  }
623
943
  return messages;
624
944
  }
945
+ function isCodexRawMessages(rawMessages) {
946
+ const firstType = rawMessages[0]?.type;
947
+ return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item";
948
+ }
949
+ function serializeRawMessages(rawMessages) {
950
+ return rawMessages.map((msg) => JSON.stringify(msg)).join("\n");
951
+ }
625
952
  function extractSessionMetadata(messages) {
626
953
  const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
627
954
  for (const msg of messages) {
@@ -641,6 +968,9 @@ function extractSessionMetadata(messages) {
641
968
  return meta;
642
969
  }
643
970
  function parseSession(jsonlText) {
971
+ if (isCodexSessionText(jsonlText)) {
972
+ return parseCodexSession(jsonlText);
973
+ }
644
974
  const rawMessages = parseLines(jsonlText);
645
975
  const metadata = extractSessionMetadata(rawMessages);
646
976
  const turns = buildTurns(rawMessages);
@@ -649,10 +979,16 @@ function parseSession(jsonlText) {
649
979
  ...metadata,
650
980
  turns,
651
981
  stats,
652
- rawMessages
982
+ rawMessages,
983
+ agentKind: "claude"
653
984
  };
654
985
  }
655
986
  function parseSessionAppend(existing, newJsonlText) {
987
+ if (isCodexRawMessages(existing.rawMessages) || isCodexSessionText(newJsonlText)) {
988
+ const prefix = serializeRawMessages(existing.rawMessages);
989
+ return parseCodexSession(prefix ? `${prefix}
990
+ ${newJsonlText}` : newJsonlText);
991
+ }
656
992
  const newMessages = parseLines(newJsonlText);
657
993
  if (newMessages.length === 0) return existing;
658
994
  const allRawMessages = [...existing.rawMessages, ...newMessages];
@@ -1234,6 +1570,29 @@ async function findJsonlPath(sessionId) {
1234
1570
  }
1235
1571
  } catch {
1236
1572
  }
1573
+ const codexRoot = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".codex", "sessions");
1574
+ const walk = async (dir, depth) => {
1575
+ if (depth > 4) return null;
1576
+ let entries;
1577
+ try {
1578
+ entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
1579
+ } catch {
1580
+ return null;
1581
+ }
1582
+ for (const entry of entries) {
1583
+ const filePath = (0, import_node_path3.join)(dir, entry.name);
1584
+ if (entry.isDirectory()) {
1585
+ const match = await walk(filePath, depth + 1);
1586
+ if (match) return match;
1587
+ continue;
1588
+ }
1589
+ if (!entry.name.endsWith(".jsonl")) continue;
1590
+ if (entry.name.endsWith(`${sessionId}.jsonl`)) return filePath;
1591
+ }
1592
+ return null;
1593
+ };
1594
+ const codexMatch = await walk(codexRoot, 0);
1595
+ if (codexMatch) return codexMatch;
1237
1596
  return null;
1238
1597
  }
1239
1598
  async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
@@ -1292,6 +1651,7 @@ async function searchSessions(query, opts, searchIndex) {
1292
1651
  try {
1293
1652
  index = new SearchIndex(DEFAULT_DB_PATH);
1294
1653
  ownedIndex = true;
1654
+ index.updateStale(dirs.PROJECTS_DIR);
1295
1655
  } catch {
1296
1656
  }
1297
1657
  }
@@ -1365,6 +1725,14 @@ async function cwdFromFilePath(filePath) {
1365
1725
  cwdCache.set(filePath, obj.cwd);
1366
1726
  return obj.cwd;
1367
1727
  }
1728
+ if (obj.type === "session_meta" && obj.payload?.cwd) {
1729
+ cwdCache.set(filePath, obj.payload.cwd);
1730
+ return obj.payload.cwd;
1731
+ }
1732
+ if (obj.type === "turn_context" && obj.payload?.cwd) {
1733
+ cwdCache.set(filePath, obj.payload.cwd);
1734
+ return obj.payload.cwd;
1735
+ }
1368
1736
  } catch {
1369
1737
  }
1370
1738
  }
@@ -1594,7 +1962,7 @@ async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive, d
1594
1962
  sessionsSearched,
1595
1963
  results
1596
1964
  };
1597
- } catch (err) {
1965
+ } catch {
1598
1966
  return {
1599
1967
  query,
1600
1968
  totalHits: 0,
@@ -1873,12 +2241,17 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
1873
2241
  // src/commands/sessions.ts
1874
2242
  var import_promises5 = require("node:fs/promises");
1875
2243
  var import_node_path6 = require("node:path");
2244
+ var import_node_os3 = require("node:os");
1876
2245
 
1877
2246
  // src/lib/metadata.ts
1878
2247
  var import_promises4 = require("node:fs/promises");
1879
2248
 
1880
2249
  // src/lib/sessionStatus.ts
1881
2250
  function deriveSessionStatus(rawMessages) {
2251
+ const firstType = rawMessages[0]?.type;
2252
+ if (firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item") {
2253
+ return deriveCodexSessionStatus(rawMessages);
2254
+ }
1882
2255
  let pendingEnqueues = 0;
1883
2256
  function result(status, toolName) {
1884
2257
  const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
@@ -1919,7 +2292,38 @@ function deriveSessionStatus(rawMessages) {
1919
2292
  if (isMeta) continue;
1920
2293
  return result("processing");
1921
2294
  }
1922
- if (msg.type === "summary") return result("compacting");
2295
+ if (msg.type === "summary") continue;
2296
+ if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
2297
+ }
2298
+ return { status: "idle" };
2299
+ }
2300
+ function deriveCodexSessionStatus(rawMessages) {
2301
+ for (let i = rawMessages.length - 1; i >= 0; i--) {
2302
+ const msg = rawMessages[i];
2303
+ if (msg.type === "event_msg") {
2304
+ const payload = msg.payload;
2305
+ switch (payload?.type) {
2306
+ case "task_complete":
2307
+ return { status: "completed" };
2308
+ case "task_started":
2309
+ return { status: "processing" };
2310
+ case "agent_message":
2311
+ return { status: "thinking" };
2312
+ case "token_count":
2313
+ continue;
2314
+ }
2315
+ }
2316
+ if (msg.type === "response_item") {
2317
+ const payload = msg.payload;
2318
+ if (!payload) continue;
2319
+ if (payload.type === "function_call") {
2320
+ return { status: "tool_use", toolName: payload.name };
2321
+ }
2322
+ if (payload.type === "message") {
2323
+ if (payload.role === "assistant") return { status: "thinking" };
2324
+ if (payload.role === "user") return { status: "processing" };
2325
+ }
2326
+ }
1923
2327
  }
1924
2328
  return { status: "idle" };
1925
2329
  }
@@ -1945,6 +2349,36 @@ async function getSessionMeta(filePath) {
1945
2349
  const content = await (0, import_promises4.readFile)(filePath, "utf-8");
1946
2350
  lines = content.split("\n").filter(Boolean);
1947
2351
  }
2352
+ let firstParsed = null;
2353
+ if (lines.length > 0) {
2354
+ try {
2355
+ firstParsed = JSON.parse(lines[0]);
2356
+ } catch {
2357
+ firstParsed = null;
2358
+ }
2359
+ }
2360
+ const isCodex = firstParsed?.type === "session_meta" || firstParsed?.type === "turn_context";
2361
+ if (isCodex) {
2362
+ if (isPartialRead) {
2363
+ const content = await (0, import_promises4.readFile)(filePath, "utf-8");
2364
+ lines = content.split("\n").filter(Boolean);
2365
+ }
2366
+ const meta = extractCodexMetadataFromLines(lines);
2367
+ return {
2368
+ sessionId: meta.sessionId,
2369
+ version: meta.version,
2370
+ gitBranch: meta.gitBranch,
2371
+ model: meta.model,
2372
+ slug: meta.slug,
2373
+ cwd: meta.cwd,
2374
+ firstUserMessage: meta.firstUserMessage,
2375
+ lastUserMessage: meta.lastUserMessage,
2376
+ timestamp: meta.timestamp,
2377
+ turnCount: meta.turnCount,
2378
+ lineCount: lines.length,
2379
+ branchedFrom: meta.branchedFrom
2380
+ };
2381
+ }
1948
2382
  let sessionId = "";
1949
2383
  let version = "";
1950
2384
  let gitBranch = "";
@@ -2041,6 +2475,30 @@ async function getSessionStatus(filePath) {
2041
2475
  } catch {
2042
2476
  continue;
2043
2477
  }
2478
+ if (obj.type === "event_msg") {
2479
+ const payload = obj.payload;
2480
+ switch (payload?.type) {
2481
+ case "task_complete":
2482
+ return { status: "completed" };
2483
+ case "task_started":
2484
+ return { status: "processing" };
2485
+ case "agent_message":
2486
+ return { status: "thinking" };
2487
+ case "token_count":
2488
+ continue;
2489
+ }
2490
+ }
2491
+ if (obj.type === "response_item") {
2492
+ const payload = obj.payload;
2493
+ if (payload?.type === "function_call") {
2494
+ return { status: "tool_use", toolName: payload.name };
2495
+ }
2496
+ if (payload?.type === "message") {
2497
+ const role = payload.role;
2498
+ if (role === "assistant") return { status: "thinking" };
2499
+ if (role === "user") return { status: "processing" };
2500
+ }
2501
+ }
2044
2502
  if (obj.type === "assistant" || obj.type === "user" || obj.type === "queue-operation") {
2045
2503
  meaningful.unshift(obj);
2046
2504
  const isEndTurn = obj.type === "assistant" && obj.message?.stop_reason === "end_turn";
@@ -2059,6 +2517,35 @@ async function getSessionStatus(filePath) {
2059
2517
  }
2060
2518
 
2061
2519
  // src/commands/sessions.ts
2520
+ var CODEX_SESSIONS_DIR = (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".codex", "sessions");
2521
+ async function listCodexSessionFiles(cutoff) {
2522
+ const walk = async (dir, depth) => {
2523
+ if (depth > 4) return [];
2524
+ let entries;
2525
+ try {
2526
+ entries = await (0, import_promises5.readdir)(dir, { withFileTypes: true });
2527
+ } catch {
2528
+ return [];
2529
+ }
2530
+ const results = [];
2531
+ for (const entry of entries) {
2532
+ const filePath = (0, import_node_path6.join)(dir, entry.name);
2533
+ if (entry.isDirectory()) {
2534
+ results.push(...await walk(filePath, depth + 1));
2535
+ continue;
2536
+ }
2537
+ if (!entry.name.endsWith(".jsonl")) continue;
2538
+ try {
2539
+ const s = await (0, import_promises5.stat)(filePath);
2540
+ if (s.mtimeMs >= cutoff) results.push({ path: filePath, mtimeMs: s.mtimeMs });
2541
+ } catch {
2542
+ continue;
2543
+ }
2544
+ }
2545
+ return results;
2546
+ };
2547
+ return walk(CODEX_SESSIONS_DIR, 0);
2548
+ }
2062
2549
  async function listSessions(opts = {}) {
2063
2550
  const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
2064
2551
  const maxAgeMs = parseMaxAge(opts.maxAge ?? "7d");
@@ -2092,7 +2579,8 @@ async function listSessions(opts = {}) {
2092
2579
  }
2093
2580
  })
2094
2581
  );
2095
- const allFiles = nested.flat();
2582
+ const codexFiles = await listCodexSessionFiles(cutoff);
2583
+ const allFiles = [...nested.flat(), ...codexFiles];
2096
2584
  allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
2097
2585
  const results = [];
2098
2586
  for (const file of allFiles) {
@@ -2115,7 +2603,8 @@ async function listSessions(opts = {}) {
2115
2603
  lastMessage: meta.lastUserMessage,
2116
2604
  turnCount: meta.turnCount,
2117
2605
  status: statusInfo.status,
2118
- mtime: file.mtimeMs
2606
+ mtime: file.mtimeMs,
2607
+ source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2119
2608
  });
2120
2609
  } catch {
2121
2610
  }
@@ -2129,10 +2618,9 @@ async function currentSession(cwd) {
2129
2618
  try {
2130
2619
  files = await (0, import_promises5.readdir)(projectDir);
2131
2620
  } catch {
2132
- return null;
2621
+ files = [];
2133
2622
  }
2134
2623
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2135
- if (jsonlFiles.length === 0) return null;
2136
2624
  const statResults = await Promise.all(
2137
2625
  jsonlFiles.map(async (f) => {
2138
2626
  const filePath = (0, import_node_path6.join)(projectDir, f);
@@ -2144,7 +2632,21 @@ async function currentSession(cwd) {
2144
2632
  }
2145
2633
  })
2146
2634
  );
2147
- const valid = statResults.filter((r) => r !== null);
2635
+ const codexCandidates = await listCodexSessionFiles(0);
2636
+ const codexMatches = [];
2637
+ for (const file of codexCandidates) {
2638
+ try {
2639
+ const meta2 = await getSessionMeta(file.path);
2640
+ if (meta2.cwd === cwd) codexMatches.push(file);
2641
+ } catch {
2642
+ continue;
2643
+ }
2644
+ }
2645
+ const valid = [
2646
+ ...statResults.filter((r) => r !== null),
2647
+ ...codexMatches
2648
+ ];
2649
+ if (valid.length === 0) return null;
2148
2650
  valid.sort((a, b) => b.mtimeMs - a.mtimeMs);
2149
2651
  const latest = valid[0];
2150
2652
  if (!latest) return null;
@@ -2164,7 +2666,8 @@ async function currentSession(cwd) {
2164
2666
  lastMessage: meta.lastUserMessage,
2165
2667
  turnCount: meta.turnCount,
2166
2668
  status: statusInfo.status,
2167
- mtime: latest.mtimeMs
2669
+ mtime: latest.mtimeMs,
2670
+ source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2168
2671
  };
2169
2672
  }
2170
2673