cogpit-memory 0.1.5 → 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 +503 -12
- package/dist/index.js +515 -12
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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")
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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")
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
package/package.json
CHANGED