Skip to content

Commit 4c27b60

Browse files
Myoontyeeclaude
andcommitted
feat: parse Codex compacted entries, custom_tool_call, web_search_call (v0.8.12)
Handle all previously-missing JSONL types from Codex APP long conversations: - compacted → compaction_summary blocks with handoff summaries - custom_tool_call/output → tool_call/result (apply_patch etc.) - web_search_call → tool_call with query/url Render compaction boundaries as ## Context Compacted in both formats. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c53b2b6 commit 4c27b60

File tree

5 files changed

+106
-7
lines changed

5 files changed

+106
-7
lines changed

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no \
104104

105105
| 优先级 | 问题 | 状态 |
106106
|--------|------|------|
107-
| P0 | Codex 会话标题乱(AGENTS.md 注入) | v0.8.10 已修,待验证 |
108-
| P1 | CodexWatcher 把所有会话都导出到当前 workspace,不按 cwd 分类 | 未修 |
107+
| P0 | Codex 会话标题乱(AGENTS.md 注入) | v0.8.10 已修,SSH脚本验证通过 |
108+
| P1 | CodexWatcher 把所有会话都导出到当前 workspace,不按 cwd 分类 | v0.8.11 已修,SSH脚本验证通过 |
109109

110110
P1 直接导致用户在正确的项目目录下找不到对应会话的导出文件。不要在 P0 验证通过前就去改 P1,也不要在 P1 未修的情况下假装问题解决了。
111111

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "claude-code-exporter",
33
"displayName": "Claude Code Exporter — Claude Code, Codex & Cursor Conversation History",
44
"description": "Export AI coding agent conversations to Markdown. Supports Claude Code (→ .claude-code-history/), OpenAI Codex (→ .codex-history/), and Cursor Composer (→ .cursor-history/). Features: auto-watch, session inject/import for claude --resume, and repair of thinking-block signature errors when switching API providers.",
5-
"version": "0.8.11",
5+
"version": "0.8.12",
66
"publisher": "myoontyee",
77
"engines": {
88
"vscode": "^1.85.0"

src/codexExporter.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ export class CodexExporter {
152152
lines.push('');
153153

154154
for (const msg of session.messages) {
155+
// Compaction boundary — render as a distinct separator, not a normal turn
156+
const isCompaction = msg.blocks.length === 1 && msg.blocks[0].type === 'compaction_summary';
157+
if (isCompaction) {
158+
const ts = msg.timestamp ? ` <sup>${fmtDate(msg.timestamp)}</sup>` : '';
159+
lines.push(`## Context Compacted${ts}`);
160+
lines.push('');
161+
lines.push(...this.renderBlockReadable(msg.blocks[0], opts));
162+
lines.push('---');
163+
lines.push('');
164+
continue;
165+
}
166+
155167
const header = msg.role === 'user' ? '## User' : '## Codex';
156168
const ts = msg.timestamp ? ` <sup>${fmtDate(msg.timestamp)}</sup>` : '';
157169
lines.push(`${header}${ts}`);
@@ -220,6 +232,20 @@ export class CodexExporter {
220232
'',
221233
];
222234
}
235+
case 'compaction_summary': {
236+
const display = block.text.length > 3000
237+
? block.text.slice(0, 3000) + '\n\n...(truncated)'
238+
: block.text;
239+
return [
240+
'<details>',
241+
'<summary>Handoff Summary</summary>',
242+
'',
243+
display,
244+
'',
245+
'</details>',
246+
'',
247+
];
248+
}
223249
default: return [];
224250
}
225251
}
@@ -237,6 +263,21 @@ export class CodexExporter {
237263
lines.push('');
238264

239265
for (const msg of session.messages) {
266+
// Compaction boundary
267+
const isCompaction = msg.blocks.length === 1 && msg.blocks[0].type === 'compaction_summary';
268+
if (isCompaction) {
269+
const text = this.blockToCompact(msg.blocks[0], opts);
270+
if (text) {
271+
lines.push('**[Context Compacted]**');
272+
lines.push('');
273+
lines.push(text);
274+
lines.push('');
275+
lines.push('---');
276+
lines.push('');
277+
}
278+
continue;
279+
}
280+
240281
const parts: string[] = [];
241282
for (const block of msg.blocks) {
242283
const text = this.blockToCompact(block, opts);
@@ -266,10 +307,16 @@ export class CodexExporter {
266307
case 'tool_call': {
267308
let preview = '';
268309
const args = block.toolArgs ?? {};
269-
// Extract the most useful field from shell args
270310
if (block.toolName === 'shell') {
271311
const cmd = (args.command as string[] | undefined)?.join(' ') ?? JSON.stringify(args);
272312
preview = ` — ${cmd.slice(0, 80)}`;
313+
} else if (block.toolName === 'apply_patch') {
314+
const input = (args.input as string) ?? '';
315+
const fileLine = input.split('\n').find((l: string) => l.startsWith('***')) ?? input.slice(0, 80);
316+
preview = ` — ${fileLine.slice(0, 80)}`;
317+
} else if (block.toolName === 'web_search') {
318+
const query = (args.query as string) ?? (args.url as string) ?? '';
319+
preview = query ? ` — ${query.slice(0, 80)}` : '';
273320
} else {
274321
const argsStr = JSON.stringify(args);
275322
preview = argsStr.length > 1 ? ` — ${argsStr.slice(0, 80)}` : '';
@@ -282,6 +329,10 @@ export class CodexExporter {
282329
if (!text) return '';
283330
return `[Result: ${text.slice(0, 500)}${text.length > 500 ? '...' : ''}]`;
284331
}
332+
case 'compaction_summary': {
333+
const preview = block.text.slice(0, 300);
334+
return `[Handoff Summary: ${preview}${block.text.length > 300 ? '...' : ''}]`;
335+
}
285336
default: return '';
286337
}
287338
}

src/codexParser.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export type CodexBlock =
1818
| { type: 'text'; text: string }
1919
| { type: 'reasoning'; summaryTexts: string[] }
2020
| { type: 'tool_call'; toolName: string; toolArgs: Record<string, unknown> }
21-
| { type: 'tool_result'; text: string; exitCode?: number };
21+
| { type: 'tool_result'; text: string; exitCode?: number }
22+
| { type: 'compaction_summary'; text: string };
2223

2324
export interface CodexMessage {
2425
role: 'user' | 'assistant';
@@ -98,6 +99,20 @@ export function parseCodexSessionFile(filePath: string): CodexSession {
9899
continue;
99100
}
100101

102+
// ── compacted — context compaction boundary ────────────────────────────────
103+
if (type === 'compacted') {
104+
flushAssistant(timestamp);
105+
const message = (payload as Record<string, unknown>).message as string | undefined;
106+
if (message) {
107+
session.messages.push({
108+
role: 'assistant',
109+
timestamp,
110+
blocks: [{ type: 'compaction_summary', text: message }],
111+
});
112+
}
113+
continue;
114+
}
115+
101116
// ── response_item ─────────────────────────────────────────────────────────
102117
if (type === 'response_item') {
103118
const ptype = payload.type as string;
@@ -174,6 +189,39 @@ export function parseCodexSessionFile(filePath: string): CodexSession {
174189
continue;
175190
}
176191

192+
// Custom tool call (apply_patch, etc.)
193+
if (ptype === 'custom_tool_call') {
194+
if (!assistantTimestamp) assistantTimestamp = timestamp;
195+
const input = typeof payload.input === 'string' ? payload.input : JSON.stringify(payload.input ?? {});
196+
assistantBlocks.push({
197+
type: 'tool_call',
198+
toolName: (payload.name as string) ?? 'custom_tool',
199+
toolArgs: { input },
200+
});
201+
continue;
202+
}
203+
204+
// Custom tool result
205+
if (ptype === 'custom_tool_call_output') {
206+
assistantBlocks.push({
207+
type: 'tool_result',
208+
text: (payload.output as string) ?? '',
209+
});
210+
continue;
211+
}
212+
213+
// Web search
214+
if (ptype === 'web_search_call') {
215+
if (!assistantTimestamp) assistantTimestamp = timestamp;
216+
const action = payload.action as Record<string, unknown> | undefined;
217+
assistantBlocks.push({
218+
type: 'tool_call',
219+
toolName: 'web_search',
220+
toolArgs: action ? { type: action.type, query: action.query, url: action.url } : {},
221+
});
222+
continue;
223+
}
224+
177225
continue;
178226
}
179227

0 commit comments

Comments
 (0)