@bubstack/moe-memory 0.1.0
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/.mcp.json +18 -0
- package/README.md +47 -0
- package/agents/search-conversations.md +171 -0
- package/dist/LICENSE +25 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-hook-trust.d.ts +4 -0
- package/dist/codex-hook-trust.d.ts.map +1 -0
- package/dist/codex-hook-trust.js +114 -0
- package/dist/codex-hook-trust.js.map +1 -0
- package/dist/codex-support.d.ts +6 -0
- package/dist/codex-support.d.ts.map +1 -0
- package/dist/codex-support.js +29 -0
- package/dist/codex-support.js.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/db.d.ts +103 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +430 -0
- package/dist/db.js.map +1 -0
- package/dist/doctor-cli.d.ts +3 -0
- package/dist/doctor-cli.d.ts.map +1 -0
- package/dist/doctor-cli.js +43 -0
- package/dist/doctor-cli.js.map +1 -0
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +98 -0
- package/dist/doctor.js.map +1 -0
- package/dist/embedding-migration.d.ts +73 -0
- package/dist/embedding-migration.d.ts.map +1 -0
- package/dist/embedding-migration.js +120 -0
- package/dist/embedding-migration.js.map +1 -0
- package/dist/embeddings.d.ts +78 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +180 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/file-lock.d.ts +39 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +91 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/index-cli.d.ts +10 -0
- package/dist/index-cli.d.ts.map +1 -0
- package/dist/index-cli.js +195 -0
- package/dist/index-cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +4 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +318 -0
- package/dist/indexer.js.map +1 -0
- package/dist/install-check.d.ts +48 -0
- package/dist/install-check.d.ts.map +1 -0
- package/dist/install-check.js +82 -0
- package/dist/install-check.js.map +1 -0
- package/dist/journal/index.d.ts +5 -0
- package/dist/journal/index.d.ts.map +1 -0
- package/dist/journal/index.js +5 -0
- package/dist/journal/index.js.map +1 -0
- package/dist/journal/legacy-sidecars.d.ts +74 -0
- package/dist/journal/legacy-sidecars.d.ts.map +1 -0
- package/dist/journal/legacy-sidecars.js +151 -0
- package/dist/journal/legacy-sidecars.js.map +1 -0
- package/dist/journal/markdown.d.ts +91 -0
- package/dist/journal/markdown.d.ts.map +1 -0
- package/dist/journal/markdown.js +234 -0
- package/dist/journal/markdown.js.map +1 -0
- package/dist/journal/search.d.ts +91 -0
- package/dist/journal/search.d.ts.map +1 -0
- package/dist/journal/search.js +203 -0
- package/dist/journal/search.js.map +1 -0
- package/dist/journal/store.d.ts +96 -0
- package/dist/journal/store.d.ts.map +1 -0
- package/dist/journal/store.js +270 -0
- package/dist/journal/store.js.map +1 -0
- package/dist/journal-cli.d.ts +19 -0
- package/dist/journal-cli.d.ts.map +1 -0
- package/dist/journal-cli.js +153 -0
- package/dist/journal-cli.js.map +1 -0
- package/dist/logging.d.ts +6 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +20 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp-server.d.ts +34 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +607 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +445 -0
- package/dist/parser.js.map +1 -0
- package/dist/paths.d.ts +103 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +258 -0
- package/dist/paths.js.map +1 -0
- package/dist/search-cli.d.ts +3 -0
- package/dist/search-cli.d.ts.map +1 -0
- package/dist/search-cli.js +138 -0
- package/dist/search-cli.js.map +1 -0
- package/dist/search.d.ts +36 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +371 -0
- package/dist/search.js.map +1 -0
- package/dist/show-cli.d.ts +3 -0
- package/dist/show-cli.d.ts.map +1 -0
- package/dist/show-cli.js +60 -0
- package/dist/show-cli.js.map +1 -0
- package/dist/show.d.ts +3 -0
- package/dist/show.d.ts.map +1 -0
- package/dist/show.js +984 -0
- package/dist/show.js.map +1 -0
- package/dist/stats-cli.d.ts +3 -0
- package/dist/stats-cli.d.ts.map +1 -0
- package/dist/stats-cli.js +51 -0
- package/dist/stats-cli.js.map +1 -0
- package/dist/stats.d.ts +19 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +116 -0
- package/dist/stats.js.map +1 -0
- package/dist/summarizer.d.ts +83 -0
- package/dist/summarizer.d.ts.map +1 -0
- package/dist/summarizer.js +526 -0
- package/dist/summarizer.js.map +1 -0
- package/dist/summary-sentinel.d.ts +31 -0
- package/dist/summary-sentinel.d.ts.map +1 -0
- package/dist/summary-sentinel.js +80 -0
- package/dist/summary-sentinel.js.map +1 -0
- package/dist/sync-cli.d.ts +15 -0
- package/dist/sync-cli.d.ts.map +1 -0
- package/dist/sync-cli.js +202 -0
- package/dist/sync-cli.js.map +1 -0
- package/dist/sync.d.ts +35 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +204 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +125 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +32 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +30 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +174 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/hooks/hooks.json +15 -0
- package/package.json +71 -0
- package/prompts/search-agent.md +160 -0
- package/skills/remembering-conversations/MCP-TOOLS.md +147 -0
- package/skills/remembering-conversations/SKILL.md +97 -0
package/dist/show.js
ADDED
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
import { marked } from "marked";
|
|
2
|
+
export function formatConversationAsMarkdown(jsonl, startLine, endLine) {
|
|
3
|
+
const allLines = jsonl
|
|
4
|
+
.trim()
|
|
5
|
+
.split("\n")
|
|
6
|
+
.filter((line) => line.trim());
|
|
7
|
+
// Apply line range if specified (1-indexed, inclusive)
|
|
8
|
+
const lines = startLine !== undefined || endLine !== undefined
|
|
9
|
+
? allLines.slice(startLine !== undefined ? startLine - 1 : 0, endLine !== undefined ? endLine : undefined)
|
|
10
|
+
: allLines;
|
|
11
|
+
if (isCodexRollout(lines)) {
|
|
12
|
+
return formatCodexConversationAsMarkdown(lines);
|
|
13
|
+
}
|
|
14
|
+
const allMessages = lines.map((line) => JSON.parse(line));
|
|
15
|
+
// Filter out system messages and messages with no content
|
|
16
|
+
const messages = allMessages.filter((msg) => {
|
|
17
|
+
if (msg.type !== "user" && msg.type !== "assistant")
|
|
18
|
+
return false;
|
|
19
|
+
if (!msg.timestamp)
|
|
20
|
+
return false;
|
|
21
|
+
if (!msg.message?.content) {
|
|
22
|
+
if (msg.type === "assistant" && msg.message?.usage)
|
|
23
|
+
return true;
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(msg.message.content) && msg.message.content.length === 0) {
|
|
27
|
+
if (msg.type === "assistant" && msg.message?.usage)
|
|
28
|
+
return true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
if (messages.length === 0) {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
const firstMessage = messages[0];
|
|
37
|
+
if (!firstMessage) {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
let output = "# Conversation\n\n";
|
|
41
|
+
// Add metadata
|
|
42
|
+
output += "## Metadata\n\n";
|
|
43
|
+
if (firstMessage.sessionId) {
|
|
44
|
+
output += `**Session ID:** ${firstMessage.sessionId}\n\n`;
|
|
45
|
+
}
|
|
46
|
+
if (firstMessage.gitBranch) {
|
|
47
|
+
output += `**Git Branch:** ${firstMessage.gitBranch}\n\n`;
|
|
48
|
+
}
|
|
49
|
+
if (firstMessage.cwd) {
|
|
50
|
+
output += `**Working Directory:** ${firstMessage.cwd}\n\n`;
|
|
51
|
+
}
|
|
52
|
+
if (firstMessage.version) {
|
|
53
|
+
output += `**Claude Code Version:** ${firstMessage.version}\n\n`;
|
|
54
|
+
}
|
|
55
|
+
output += "---\n\n";
|
|
56
|
+
output += "## Messages\n\n";
|
|
57
|
+
let inSidechain = false;
|
|
58
|
+
for (const [i, msg] of messages.entries()) {
|
|
59
|
+
const timestamp = new Date(msg.timestamp).toLocaleString();
|
|
60
|
+
const messageId = msg.uuid || `msg-${i}`;
|
|
61
|
+
// Skip user messages that are just tool results
|
|
62
|
+
if (msg.type === "user" && Array.isArray(msg.message.content)) {
|
|
63
|
+
const hasOnlyToolResults = msg.message.content.every((block) => block.type === "tool_result");
|
|
64
|
+
if (hasOnlyToolResults) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Handle sidechain grouping
|
|
69
|
+
if (msg.isSidechain && !inSidechain) {
|
|
70
|
+
output += "\n---\n";
|
|
71
|
+
output += "**🔀 SIDECHAIN START**\n";
|
|
72
|
+
output += "---\n\n";
|
|
73
|
+
inSidechain = true;
|
|
74
|
+
}
|
|
75
|
+
else if (!msg.isSidechain && inSidechain) {
|
|
76
|
+
output += "\n---\n";
|
|
77
|
+
output += "**🔀 SIDECHAIN END**\n";
|
|
78
|
+
output += "---\n\n";
|
|
79
|
+
inSidechain = false;
|
|
80
|
+
}
|
|
81
|
+
// Determine role label
|
|
82
|
+
let roleLabel;
|
|
83
|
+
if (msg.isSidechain) {
|
|
84
|
+
roleLabel = msg.type === "user" ? "Agent" : "Subagent";
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
roleLabel = msg.type === "user" ? "User" : "Agent";
|
|
88
|
+
}
|
|
89
|
+
output += `### **${roleLabel}** (${timestamp}) {#${messageId}}\n\n`;
|
|
90
|
+
if (msg.type === "user") {
|
|
91
|
+
// Handle tool results
|
|
92
|
+
if (msg.toolUseResult) {
|
|
93
|
+
output += "**Tool Result:**\n\n";
|
|
94
|
+
if (typeof msg.toolUseResult === "string") {
|
|
95
|
+
output += `${msg.toolUseResult}\n\n`;
|
|
96
|
+
}
|
|
97
|
+
else if (Array.isArray(msg.toolUseResult)) {
|
|
98
|
+
for (const result of msg.toolUseResult) {
|
|
99
|
+
output += `${result.text || String(result)}\n\n`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (typeof msg.message.content === "string") {
|
|
104
|
+
output += `${msg.message.content}\n\n`;
|
|
105
|
+
}
|
|
106
|
+
else if (Array.isArray(msg.message.content)) {
|
|
107
|
+
for (const block of msg.message.content) {
|
|
108
|
+
if (block.type === "text" && block.text) {
|
|
109
|
+
output += `${block.text}\n\n`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (msg.type === "assistant") {
|
|
115
|
+
const content = msg.message.content;
|
|
116
|
+
if (typeof content === "string") {
|
|
117
|
+
output += `${content}\n\n`;
|
|
118
|
+
}
|
|
119
|
+
else if (Array.isArray(content)) {
|
|
120
|
+
for (const block of content) {
|
|
121
|
+
if (block.type === "text" && block.text) {
|
|
122
|
+
output += `${block.text}\n\n`;
|
|
123
|
+
}
|
|
124
|
+
else if (block.type === "tool_use") {
|
|
125
|
+
output += `**Tool Use:** \`${block.name}\`\n\n`;
|
|
126
|
+
// Format tool input inline
|
|
127
|
+
const input = block.input;
|
|
128
|
+
if (input && typeof input === "object") {
|
|
129
|
+
for (const [key, value] of Object.entries(input)) {
|
|
130
|
+
if (typeof value === "string" && value.includes("\n")) {
|
|
131
|
+
output += `- **${key}:**\n\`\`\`\n${value}\n\`\`\`\n`;
|
|
132
|
+
}
|
|
133
|
+
else if (typeof value === "string") {
|
|
134
|
+
output += `- **${key}:** ${value}\n`;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
output += `- **${key}:**\n\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\`\n`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
output += "\n";
|
|
141
|
+
}
|
|
142
|
+
// Look for corresponding tool result
|
|
143
|
+
const toolUseId = block.id;
|
|
144
|
+
if (toolUseId) {
|
|
145
|
+
let foundResult = false;
|
|
146
|
+
for (let j = i + 1; j < Math.min(i + 6, messages.length) && !foundResult; j++) {
|
|
147
|
+
const laterMsg = messages[j];
|
|
148
|
+
if (laterMsg &&
|
|
149
|
+
laterMsg.type === "user" &&
|
|
150
|
+
Array.isArray(laterMsg.message.content)) {
|
|
151
|
+
for (const resultBlock of laterMsg.message.content) {
|
|
152
|
+
if (resultBlock.type === "tool_result" &&
|
|
153
|
+
resultBlock.tool_use_id === toolUseId) {
|
|
154
|
+
const content = resultBlock.content;
|
|
155
|
+
output += "**Result:**\n";
|
|
156
|
+
if (typeof content === "string") {
|
|
157
|
+
if (content.includes("\n") || content.length > 100) {
|
|
158
|
+
output += "```\n";
|
|
159
|
+
output += content;
|
|
160
|
+
output += "\n```\n\n";
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
output += `${content}\n\n`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (Array.isArray(content)) {
|
|
167
|
+
output += "```json\n";
|
|
168
|
+
output += JSON.stringify(content, null, 2);
|
|
169
|
+
output += "\n```\n\n";
|
|
170
|
+
}
|
|
171
|
+
foundResult = true;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Add token usage if present
|
|
182
|
+
if (msg.message.usage) {
|
|
183
|
+
const usage = msg.message.usage;
|
|
184
|
+
output += `_in: ${(usage.input_tokens || 0).toLocaleString()}`;
|
|
185
|
+
if (usage.cache_read_input_tokens) {
|
|
186
|
+
output += ` | cache read: ${usage.cache_read_input_tokens.toLocaleString()}`;
|
|
187
|
+
}
|
|
188
|
+
if (usage.cache_creation_input_tokens) {
|
|
189
|
+
output += ` | cache create: ${usage.cache_creation_input_tokens.toLocaleString()}`;
|
|
190
|
+
}
|
|
191
|
+
output += ` | out: ${(usage.output_tokens || 0).toLocaleString()}_\n\n`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Close sidechain if still open
|
|
196
|
+
if (inSidechain) {
|
|
197
|
+
output += "\n---\n";
|
|
198
|
+
output += "**🔀 SIDECHAIN END**\n";
|
|
199
|
+
output += "---\n\n";
|
|
200
|
+
}
|
|
201
|
+
return output;
|
|
202
|
+
}
|
|
203
|
+
export function formatConversationAsHTML(jsonl) {
|
|
204
|
+
const lines = jsonl
|
|
205
|
+
.trim()
|
|
206
|
+
.split("\n")
|
|
207
|
+
.filter((line) => line.trim());
|
|
208
|
+
if (isCodexRollout(lines)) {
|
|
209
|
+
return formatMarkdownDocumentAsHTML(formatCodexConversationAsMarkdown(lines));
|
|
210
|
+
}
|
|
211
|
+
const allMessages = lines.map((line) => JSON.parse(line));
|
|
212
|
+
// Filter out system messages and messages with no content
|
|
213
|
+
const messages = allMessages.filter((msg) => {
|
|
214
|
+
// Skip file-history-snapshot and other non-conversation messages
|
|
215
|
+
if (msg.type !== "user" && msg.type !== "assistant")
|
|
216
|
+
return false;
|
|
217
|
+
// Skip messages with null timestamps
|
|
218
|
+
if (!msg.timestamp)
|
|
219
|
+
return false;
|
|
220
|
+
// Skip messages with no content (but keep if they have usage stats)
|
|
221
|
+
if (!msg.message?.content) {
|
|
222
|
+
// Keep assistant messages that only have token usage
|
|
223
|
+
if (msg.type === "assistant" && msg.message?.usage)
|
|
224
|
+
return true;
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (Array.isArray(msg.message.content) && msg.message.content.length === 0) {
|
|
228
|
+
// Keep assistant messages that only have token usage
|
|
229
|
+
if (msg.type === "assistant" && msg.message?.usage)
|
|
230
|
+
return true;
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
234
|
+
});
|
|
235
|
+
if (messages.length === 0) {
|
|
236
|
+
return "";
|
|
237
|
+
}
|
|
238
|
+
const firstMessage = messages[0];
|
|
239
|
+
if (!firstMessage) {
|
|
240
|
+
return "";
|
|
241
|
+
}
|
|
242
|
+
let bodyContent = "";
|
|
243
|
+
// Add header with metadata
|
|
244
|
+
bodyContent += '<div class="header">';
|
|
245
|
+
bodyContent += "<h1>Conversation</h1>";
|
|
246
|
+
bodyContent += '<table class="metadata">';
|
|
247
|
+
if (firstMessage.sessionId) {
|
|
248
|
+
bodyContent += `<tr><th>Session ID</th><td>${escapeHtml(firstMessage.sessionId)}</td></tr>`;
|
|
249
|
+
}
|
|
250
|
+
if (firstMessage.gitBranch) {
|
|
251
|
+
bodyContent += `<tr><th>Git Branch</th><td>${escapeHtml(firstMessage.gitBranch)}</td></tr>`;
|
|
252
|
+
}
|
|
253
|
+
if (firstMessage.cwd) {
|
|
254
|
+
bodyContent += `<tr><th>Working Directory</th><td>${escapeHtml(firstMessage.cwd)}</td></tr>`;
|
|
255
|
+
}
|
|
256
|
+
if (firstMessage.version) {
|
|
257
|
+
bodyContent += `<tr><th>Claude Code Version</th><td>${escapeHtml(firstMessage.version)}</td></tr>`;
|
|
258
|
+
}
|
|
259
|
+
bodyContent += "</table></div>";
|
|
260
|
+
// Render messages
|
|
261
|
+
bodyContent += '<div class="messages">';
|
|
262
|
+
// Build message index for tool results
|
|
263
|
+
const toolUseMap = new Map();
|
|
264
|
+
for (const msg of messages) {
|
|
265
|
+
if (msg.type === "assistant" && Array.isArray(msg.message.content)) {
|
|
266
|
+
for (const block of msg.message.content) {
|
|
267
|
+
if (block.type === "tool_use" && block.id) {
|
|
268
|
+
toolUseMap.set(block.id, { msg, block });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
let inSidechain = false;
|
|
274
|
+
for (const [i, msg] of messages.entries()) {
|
|
275
|
+
const timestamp = new Date(msg.timestamp).toLocaleString();
|
|
276
|
+
const messageId = `msg-${msg.uuid || i}`;
|
|
277
|
+
// Skip user messages that are just tool results - they'll be rendered with their tool use
|
|
278
|
+
if (msg.type === "user" && Array.isArray(msg.message.content)) {
|
|
279
|
+
const hasOnlyToolResults = msg.message.content.every((block) => block.type === "tool_result");
|
|
280
|
+
if (hasOnlyToolResults) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Handle sidechain grouping
|
|
285
|
+
if (msg.isSidechain && !inSidechain) {
|
|
286
|
+
// Starting a sidechain group
|
|
287
|
+
bodyContent += '<div class="sidechain-group">';
|
|
288
|
+
inSidechain = true;
|
|
289
|
+
}
|
|
290
|
+
else if (!msg.isSidechain && inSidechain) {
|
|
291
|
+
// Ending a sidechain group
|
|
292
|
+
bodyContent += "</div>";
|
|
293
|
+
inSidechain = false;
|
|
294
|
+
}
|
|
295
|
+
const messageClass = msg.type === "user" ? "message user-message" : "message assistant-message";
|
|
296
|
+
// Determine role label based on context
|
|
297
|
+
let roleLabel;
|
|
298
|
+
if (msg.isSidechain) {
|
|
299
|
+
roleLabel = msg.type === "user" ? "Agent" : "Subagent";
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
roleLabel = msg.type === "user" ? "User" : "Agent";
|
|
303
|
+
}
|
|
304
|
+
bodyContent += `<div class="${messageClass}" id="${messageId}">`;
|
|
305
|
+
bodyContent += `<div class="message-header">`;
|
|
306
|
+
bodyContent += `<span class="role">${roleLabel}</span>`;
|
|
307
|
+
bodyContent += `<a href="#${messageId}" class="timestamp">${escapeHtml(timestamp)}</a>`;
|
|
308
|
+
bodyContent += `</div>`;
|
|
309
|
+
bodyContent += `<div class="message-content">`;
|
|
310
|
+
if (msg.type === "user") {
|
|
311
|
+
// Handle tool results
|
|
312
|
+
if (msg.toolUseResult) {
|
|
313
|
+
bodyContent += '<div class="tool-result">';
|
|
314
|
+
bodyContent += "<strong>Tool Result:</strong>";
|
|
315
|
+
if (typeof msg.toolUseResult === "string") {
|
|
316
|
+
bodyContent += `<p>${escapeHtml(msg.toolUseResult)}</p>`;
|
|
317
|
+
}
|
|
318
|
+
else if (Array.isArray(msg.toolUseResult)) {
|
|
319
|
+
for (const result of msg.toolUseResult) {
|
|
320
|
+
bodyContent += `<p>${escapeHtml(result.text || String(result))}</p>`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
bodyContent += "</div>";
|
|
324
|
+
}
|
|
325
|
+
else if (typeof msg.message.content === "string") {
|
|
326
|
+
bodyContent += `<p>${escapeHtml(msg.message.content)}</p>`;
|
|
327
|
+
}
|
|
328
|
+
else if (Array.isArray(msg.message.content)) {
|
|
329
|
+
for (const block of msg.message.content) {
|
|
330
|
+
if (block.type === "text" && block.text) {
|
|
331
|
+
bodyContent += `<p>${escapeHtml(block.text)}</p>`;
|
|
332
|
+
}
|
|
333
|
+
else if (block.type === "tool_result") {
|
|
334
|
+
bodyContent += '<div class="tool-result">';
|
|
335
|
+
bodyContent += "<strong>Tool Result:</strong> ";
|
|
336
|
+
bodyContent += `<code>${escapeHtml(String(block.content || ""))}</code>`;
|
|
337
|
+
bodyContent += "</div>";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else if (msg.type === "assistant") {
|
|
343
|
+
const content = msg.message.content;
|
|
344
|
+
if (typeof content === "string") {
|
|
345
|
+
bodyContent += `<p>${escapeHtml(content)}</p>`;
|
|
346
|
+
}
|
|
347
|
+
else if (Array.isArray(content)) {
|
|
348
|
+
for (const block of content) {
|
|
349
|
+
if (block.type === "text" && block.text) {
|
|
350
|
+
// Check if text looks like markdown and render accordingly
|
|
351
|
+
if (isMarkdown(block.text)) {
|
|
352
|
+
bodyContent += '<div class="markdown-content">';
|
|
353
|
+
bodyContent += renderMarkdownSafely(block.text);
|
|
354
|
+
bodyContent += "</div>";
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
// Plain text - preserve whitespace
|
|
358
|
+
bodyContent += `<div class="plain-content">${escapeHtml(block.text)}</div>`;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else if (block.type === "tool_use") {
|
|
362
|
+
bodyContent += '<div class="tool-use">';
|
|
363
|
+
bodyContent += `<div class="tool-name"><strong>Tool Use:</strong> <code>${escapeHtml(block.name || "")}</code></div>`;
|
|
364
|
+
// Format tool input inline
|
|
365
|
+
const input = block.input;
|
|
366
|
+
if (input && typeof input === "object") {
|
|
367
|
+
bodyContent += '<div class="tool-params">';
|
|
368
|
+
for (const [key, value] of Object.entries(input)) {
|
|
369
|
+
bodyContent += `<div class="tool-param">`;
|
|
370
|
+
bodyContent += `<strong>${escapeHtml(key)}:</strong> `;
|
|
371
|
+
if (typeof value === "string") {
|
|
372
|
+
// Check if value has newlines or is long - format as code block
|
|
373
|
+
if (value.includes("\n") || value.length > 100) {
|
|
374
|
+
bodyContent += "<pre>";
|
|
375
|
+
bodyContent += escapeHtml(value);
|
|
376
|
+
bodyContent += "</pre>";
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
bodyContent += escapeHtml(value);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
// Format objects/arrays as JSON
|
|
384
|
+
bodyContent += "<pre>";
|
|
385
|
+
bodyContent += escapeHtml(JSON.stringify(value, null, 2));
|
|
386
|
+
bodyContent += "</pre>";
|
|
387
|
+
}
|
|
388
|
+
bodyContent += "</div>";
|
|
389
|
+
}
|
|
390
|
+
bodyContent += "</div>";
|
|
391
|
+
}
|
|
392
|
+
// Look for corresponding tool result in subsequent messages (not just next)
|
|
393
|
+
const toolUseId = block.id;
|
|
394
|
+
if (toolUseId) {
|
|
395
|
+
// Search forward up to 5 messages for the result
|
|
396
|
+
let foundResult = false;
|
|
397
|
+
for (let j = i + 1; j < Math.min(i + 6, messages.length) && !foundResult; j++) {
|
|
398
|
+
const laterMsg = messages[j];
|
|
399
|
+
if (laterMsg &&
|
|
400
|
+
laterMsg.type === "user" &&
|
|
401
|
+
Array.isArray(laterMsg.message.content)) {
|
|
402
|
+
for (const resultBlock of laterMsg.message.content) {
|
|
403
|
+
if (resultBlock.type === "tool_result" &&
|
|
404
|
+
resultBlock.tool_use_id === toolUseId) {
|
|
405
|
+
bodyContent += '<div class="tool-result">';
|
|
406
|
+
bodyContent += "<strong>Result:</strong> ";
|
|
407
|
+
const content = resultBlock.content;
|
|
408
|
+
if (typeof content === "string") {
|
|
409
|
+
// If content has newlines or is long, format as pre
|
|
410
|
+
if (content.includes("\n") || content.length > 100) {
|
|
411
|
+
bodyContent += "<pre>";
|
|
412
|
+
bodyContent += escapeHtml(content);
|
|
413
|
+
bodyContent += "</pre>";
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
bodyContent += escapeHtml(content);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else if (Array.isArray(content)) {
|
|
420
|
+
bodyContent += "<pre>";
|
|
421
|
+
bodyContent += escapeHtml(JSON.stringify(content, null, 2));
|
|
422
|
+
bodyContent += "</pre>";
|
|
423
|
+
}
|
|
424
|
+
bodyContent += "</div>";
|
|
425
|
+
foundResult = true;
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
bodyContent += "</div>";
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Add token usage if present
|
|
437
|
+
if (msg.message.usage) {
|
|
438
|
+
const usage = msg.message.usage;
|
|
439
|
+
bodyContent += '<div class="token-usage">';
|
|
440
|
+
bodyContent += `in: ${(usage.input_tokens || 0).toLocaleString()}`;
|
|
441
|
+
if (usage.cache_read_input_tokens) {
|
|
442
|
+
bodyContent += ` | cache read: ${usage.cache_read_input_tokens.toLocaleString()}`;
|
|
443
|
+
}
|
|
444
|
+
if (usage.cache_creation_input_tokens) {
|
|
445
|
+
bodyContent += ` | cache create: ${usage.cache_creation_input_tokens.toLocaleString()}`;
|
|
446
|
+
}
|
|
447
|
+
bodyContent += ` | out: ${(usage.output_tokens || 0).toLocaleString()}`;
|
|
448
|
+
bodyContent += "</div>";
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
bodyContent += "</div></div>";
|
|
452
|
+
}
|
|
453
|
+
// Close sidechain group if still open
|
|
454
|
+
if (inSidechain) {
|
|
455
|
+
bodyContent += "</div>";
|
|
456
|
+
}
|
|
457
|
+
bodyContent += "</div>";
|
|
458
|
+
return `<!DOCTYPE html>
|
|
459
|
+
<html>
|
|
460
|
+
<head>
|
|
461
|
+
<meta charset="UTF-8">
|
|
462
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
463
|
+
<title>Conversation</title>
|
|
464
|
+
<style>
|
|
465
|
+
* {
|
|
466
|
+
box-sizing: border-box;
|
|
467
|
+
}
|
|
468
|
+
body {
|
|
469
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
|
470
|
+
max-width: 900px;
|
|
471
|
+
margin: 0 auto;
|
|
472
|
+
padding: 24px;
|
|
473
|
+
line-height: 1.5;
|
|
474
|
+
color: #1d1d1f;
|
|
475
|
+
background: #f5f5f7;
|
|
476
|
+
}
|
|
477
|
+
.header {
|
|
478
|
+
margin-bottom: 32px;
|
|
479
|
+
padding: 24px;
|
|
480
|
+
background: #ffffff;
|
|
481
|
+
border-radius: 12px;
|
|
482
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
483
|
+
}
|
|
484
|
+
.header h1 {
|
|
485
|
+
margin: 0 0 20px 0;
|
|
486
|
+
font-size: 28px;
|
|
487
|
+
font-weight: 600;
|
|
488
|
+
color: #1d1d1f;
|
|
489
|
+
}
|
|
490
|
+
.metadata {
|
|
491
|
+
width: 100%;
|
|
492
|
+
border-collapse: collapse;
|
|
493
|
+
font-size: 13px;
|
|
494
|
+
}
|
|
495
|
+
.metadata th {
|
|
496
|
+
text-align: left;
|
|
497
|
+
padding: 10px 0;
|
|
498
|
+
color: #86868b;
|
|
499
|
+
font-weight: 500;
|
|
500
|
+
width: 180px;
|
|
501
|
+
border-bottom: 1px solid #f5f5f7;
|
|
502
|
+
}
|
|
503
|
+
.metadata td {
|
|
504
|
+
padding: 10px 0;
|
|
505
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
506
|
+
font-size: 12px;
|
|
507
|
+
color: #1d1d1f;
|
|
508
|
+
border-bottom: 1px solid #f5f5f7;
|
|
509
|
+
}
|
|
510
|
+
.messages {
|
|
511
|
+
display: flex;
|
|
512
|
+
flex-direction: column;
|
|
513
|
+
gap: 2px;
|
|
514
|
+
}
|
|
515
|
+
.message {
|
|
516
|
+
padding: 20px 24px;
|
|
517
|
+
background: #ffffff;
|
|
518
|
+
}
|
|
519
|
+
.user-message {
|
|
520
|
+
border-left: 3px solid #007aff;
|
|
521
|
+
}
|
|
522
|
+
.assistant-message {
|
|
523
|
+
border-left: 3px solid #34c759;
|
|
524
|
+
}
|
|
525
|
+
.message-header {
|
|
526
|
+
display: flex;
|
|
527
|
+
justify-content: space-between;
|
|
528
|
+
align-items: baseline;
|
|
529
|
+
margin-bottom: 12px;
|
|
530
|
+
}
|
|
531
|
+
.role {
|
|
532
|
+
font-weight: 600;
|
|
533
|
+
font-size: 14px;
|
|
534
|
+
letter-spacing: -0.01em;
|
|
535
|
+
}
|
|
536
|
+
.user-message .role {
|
|
537
|
+
color: #007aff;
|
|
538
|
+
}
|
|
539
|
+
.assistant-message .role {
|
|
540
|
+
color: #34c759;
|
|
541
|
+
}
|
|
542
|
+
.plain-content {
|
|
543
|
+
white-space: pre-wrap;
|
|
544
|
+
word-wrap: break-word;
|
|
545
|
+
color: #1d1d1f;
|
|
546
|
+
font-size: 14px;
|
|
547
|
+
line-height: 1.6;
|
|
548
|
+
}
|
|
549
|
+
.markdown-content {
|
|
550
|
+
color: #1d1d1f;
|
|
551
|
+
font-size: 14px;
|
|
552
|
+
line-height: 1.6;
|
|
553
|
+
}
|
|
554
|
+
.markdown-content p {
|
|
555
|
+
margin: 0 0 12px 0;
|
|
556
|
+
}
|
|
557
|
+
.markdown-content p:last-child {
|
|
558
|
+
margin-bottom: 0;
|
|
559
|
+
}
|
|
560
|
+
.markdown-content h1, .markdown-content h2, .markdown-content h3 {
|
|
561
|
+
margin: 20px 0 12px 0;
|
|
562
|
+
font-weight: 600;
|
|
563
|
+
color: #1d1d1f;
|
|
564
|
+
}
|
|
565
|
+
.markdown-content h1 {
|
|
566
|
+
font-size: 20px;
|
|
567
|
+
border-bottom: 1px solid #e5e5e7;
|
|
568
|
+
padding-bottom: 8px;
|
|
569
|
+
}
|
|
570
|
+
.markdown-content h2 {
|
|
571
|
+
font-size: 18px;
|
|
572
|
+
}
|
|
573
|
+
.markdown-content h3 {
|
|
574
|
+
font-size: 16px;
|
|
575
|
+
}
|
|
576
|
+
.markdown-content ul, .markdown-content ol {
|
|
577
|
+
margin: 12px 0;
|
|
578
|
+
padding-left: 24px;
|
|
579
|
+
}
|
|
580
|
+
.markdown-content li {
|
|
581
|
+
margin: 4px 0;
|
|
582
|
+
}
|
|
583
|
+
.markdown-content code {
|
|
584
|
+
background: #f5f5f7;
|
|
585
|
+
padding: 2px 6px;
|
|
586
|
+
border-radius: 4px;
|
|
587
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
588
|
+
font-size: 13px;
|
|
589
|
+
color: #5e5ce6;
|
|
590
|
+
}
|
|
591
|
+
.markdown-content pre {
|
|
592
|
+
margin: 12px 0;
|
|
593
|
+
background: #1d1d1f;
|
|
594
|
+
color: #f5f5f7;
|
|
595
|
+
padding: 16px;
|
|
596
|
+
border-radius: 8px;
|
|
597
|
+
overflow-x: auto;
|
|
598
|
+
line-height: 1.5;
|
|
599
|
+
}
|
|
600
|
+
.markdown-content pre code {
|
|
601
|
+
background: none;
|
|
602
|
+
color: #f5f5f7;
|
|
603
|
+
padding: 0;
|
|
604
|
+
font-size: 12px;
|
|
605
|
+
}
|
|
606
|
+
.markdown-content blockquote {
|
|
607
|
+
margin: 12px 0;
|
|
608
|
+
padding-left: 16px;
|
|
609
|
+
border-left: 3px solid #e5e5e7;
|
|
610
|
+
color: #6e6e73;
|
|
611
|
+
}
|
|
612
|
+
.markdown-content strong {
|
|
613
|
+
font-weight: 600;
|
|
614
|
+
color: #1d1d1f;
|
|
615
|
+
}
|
|
616
|
+
.markdown-content a {
|
|
617
|
+
color: #007aff;
|
|
618
|
+
text-decoration: none;
|
|
619
|
+
}
|
|
620
|
+
.markdown-content a:hover {
|
|
621
|
+
text-decoration: underline;
|
|
622
|
+
}
|
|
623
|
+
.timestamp {
|
|
624
|
+
font-size: 11px;
|
|
625
|
+
color: #86868b;
|
|
626
|
+
font-weight: 400;
|
|
627
|
+
text-decoration: none;
|
|
628
|
+
}
|
|
629
|
+
.timestamp:hover {
|
|
630
|
+
color: #007aff;
|
|
631
|
+
text-decoration: underline;
|
|
632
|
+
}
|
|
633
|
+
.tool-use {
|
|
634
|
+
margin: 16px 0;
|
|
635
|
+
background: #f5f5f7;
|
|
636
|
+
border-radius: 8px;
|
|
637
|
+
padding: 16px;
|
|
638
|
+
border: 1px solid #e5e5e7;
|
|
639
|
+
}
|
|
640
|
+
.tool-name {
|
|
641
|
+
margin-bottom: 12px;
|
|
642
|
+
font-size: 12px;
|
|
643
|
+
font-weight: 600;
|
|
644
|
+
color: #8e8e93;
|
|
645
|
+
text-transform: uppercase;
|
|
646
|
+
letter-spacing: 0.5px;
|
|
647
|
+
}
|
|
648
|
+
.tool-name code {
|
|
649
|
+
color: #5e5ce6;
|
|
650
|
+
background: none;
|
|
651
|
+
padding: 0;
|
|
652
|
+
font-weight: 600;
|
|
653
|
+
}
|
|
654
|
+
.tool-params {
|
|
655
|
+
display: flex;
|
|
656
|
+
flex-direction: column;
|
|
657
|
+
gap: 6px;
|
|
658
|
+
margin-top: 8px;
|
|
659
|
+
}
|
|
660
|
+
.tool-param {
|
|
661
|
+
font-size: 12px;
|
|
662
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
663
|
+
line-height: 1.6;
|
|
664
|
+
}
|
|
665
|
+
.tool-param strong {
|
|
666
|
+
color: #5e5ce6;
|
|
667
|
+
font-weight: 600;
|
|
668
|
+
}
|
|
669
|
+
.tool-param pre {
|
|
670
|
+
margin: 6px 0 0 0;
|
|
671
|
+
padding: 8px 12px;
|
|
672
|
+
background: #ffffff;
|
|
673
|
+
border: 1px solid #e5e5e7;
|
|
674
|
+
border-radius: 6px;
|
|
675
|
+
font-size: 11px;
|
|
676
|
+
white-space: pre-wrap;
|
|
677
|
+
word-wrap: break-word;
|
|
678
|
+
color: #1d1d1f;
|
|
679
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
680
|
+
line-height: 1.5;
|
|
681
|
+
overflow-x: auto;
|
|
682
|
+
}
|
|
683
|
+
.tool-result {
|
|
684
|
+
margin-top: 8px;
|
|
685
|
+
font-size: 12px;
|
|
686
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
687
|
+
line-height: 1.6;
|
|
688
|
+
}
|
|
689
|
+
.tool-result strong {
|
|
690
|
+
color: #5e5ce6;
|
|
691
|
+
font-weight: 600;
|
|
692
|
+
}
|
|
693
|
+
.tool-result pre {
|
|
694
|
+
margin: 6px 0 0 0;
|
|
695
|
+
padding: 8px 12px;
|
|
696
|
+
background: #ffffff;
|
|
697
|
+
border: 1px solid #e5e5e7;
|
|
698
|
+
border-radius: 6px;
|
|
699
|
+
font-size: 11px;
|
|
700
|
+
white-space: pre-wrap;
|
|
701
|
+
word-wrap: break-word;
|
|
702
|
+
color: #1d1d1f;
|
|
703
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
704
|
+
line-height: 1.5;
|
|
705
|
+
overflow-x: auto;
|
|
706
|
+
}
|
|
707
|
+
.token-usage {
|
|
708
|
+
margin-top: 12px;
|
|
709
|
+
font-size: 10px;
|
|
710
|
+
color: #86868b;
|
|
711
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
712
|
+
}
|
|
713
|
+
.sidechain-group {
|
|
714
|
+
background: #fffbf0;
|
|
715
|
+
border-left: 3px solid #ffcc00;
|
|
716
|
+
border-radius: 8px;
|
|
717
|
+
padding: 2px;
|
|
718
|
+
margin: 8px 0;
|
|
719
|
+
}
|
|
720
|
+
.sidechain-group .message {
|
|
721
|
+
background: #ffffff;
|
|
722
|
+
}
|
|
723
|
+
@media (max-width: 768px) {
|
|
724
|
+
body {
|
|
725
|
+
padding: 12px;
|
|
726
|
+
}
|
|
727
|
+
.header {
|
|
728
|
+
padding: 16px;
|
|
729
|
+
}
|
|
730
|
+
.message {
|
|
731
|
+
padding: 16px;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
</style>
|
|
735
|
+
</head>
|
|
736
|
+
<body>
|
|
737
|
+
${bodyContent}
|
|
738
|
+
</body>
|
|
739
|
+
</html>`;
|
|
740
|
+
}
|
|
741
|
+
function isCodexRollout(lines) {
|
|
742
|
+
for (const line of lines) {
|
|
743
|
+
try {
|
|
744
|
+
const parsed = JSON.parse(line);
|
|
745
|
+
return Boolean(parsed.payload &&
|
|
746
|
+
(parsed.type === "session_meta" ||
|
|
747
|
+
parsed.type === "turn_context" ||
|
|
748
|
+
parsed.type === "response_item" ||
|
|
749
|
+
parsed.type === "event_msg" ||
|
|
750
|
+
parsed.type === "compacted"));
|
|
751
|
+
}
|
|
752
|
+
catch { }
|
|
753
|
+
}
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
function extractCodexText(content) {
|
|
757
|
+
if (typeof content === "string") {
|
|
758
|
+
return content;
|
|
759
|
+
}
|
|
760
|
+
if (!Array.isArray(content)) {
|
|
761
|
+
return "";
|
|
762
|
+
}
|
|
763
|
+
return content
|
|
764
|
+
.filter((block) => block && typeof block === "object" && typeof block.text === "string")
|
|
765
|
+
.map((block) => block.text)
|
|
766
|
+
.join("\n");
|
|
767
|
+
}
|
|
768
|
+
function safeParseJson(value) {
|
|
769
|
+
try {
|
|
770
|
+
return JSON.parse(value);
|
|
771
|
+
}
|
|
772
|
+
catch {
|
|
773
|
+
return value;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function codexToolInput(payload) {
|
|
777
|
+
if (typeof payload.arguments === "string") {
|
|
778
|
+
return safeParseJson(payload.arguments);
|
|
779
|
+
}
|
|
780
|
+
if (payload.arguments !== undefined) {
|
|
781
|
+
return payload.arguments;
|
|
782
|
+
}
|
|
783
|
+
if (payload.input !== undefined) {
|
|
784
|
+
return payload.input;
|
|
785
|
+
}
|
|
786
|
+
if (payload.action !== undefined) {
|
|
787
|
+
return payload.action;
|
|
788
|
+
}
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
function codexToolOutput(payload) {
|
|
792
|
+
if (payload.output === undefined || payload.output === null) {
|
|
793
|
+
return "";
|
|
794
|
+
}
|
|
795
|
+
if (typeof payload.output === "string") {
|
|
796
|
+
return payload.output;
|
|
797
|
+
}
|
|
798
|
+
const text = extractCodexText(payload.output);
|
|
799
|
+
return text.trim() ? text : JSON.stringify(payload.output, null, 2);
|
|
800
|
+
}
|
|
801
|
+
function formatCodexToolInputMarkdown(input) {
|
|
802
|
+
if (input === undefined || input === null) {
|
|
803
|
+
return "";
|
|
804
|
+
}
|
|
805
|
+
if (typeof input === "string") {
|
|
806
|
+
return `\`\`\`\n${input}\n\`\`\`\n\n`;
|
|
807
|
+
}
|
|
808
|
+
if (typeof input !== "object") {
|
|
809
|
+
return `${String(input)}\n\n`;
|
|
810
|
+
}
|
|
811
|
+
let output = "";
|
|
812
|
+
for (const [key, value] of Object.entries(input)) {
|
|
813
|
+
if (typeof value === "string" && !value.includes("\n")) {
|
|
814
|
+
output += `- **${key}:** ${value}\n`;
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
output += `- **${key}:**\n\`\`\`json\n${typeof value === "string" ? value : JSON.stringify(value, null, 2)}\n\`\`\`\n`;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return output ? `${output}\n` : "";
|
|
821
|
+
}
|
|
822
|
+
function formatCodexConversationAsMarkdown(lines) {
|
|
823
|
+
const entries = lines.map((line) => JSON.parse(line));
|
|
824
|
+
const metadata = {};
|
|
825
|
+
for (const entry of entries) {
|
|
826
|
+
const payload = entry.payload;
|
|
827
|
+
if (entry.type === "session_meta" && payload) {
|
|
828
|
+
metadata.sessionId = payload.id || metadata.sessionId;
|
|
829
|
+
metadata.cwd = payload.cwd || metadata.cwd;
|
|
830
|
+
metadata.gitBranch = payload.git?.branch || metadata.gitBranch;
|
|
831
|
+
metadata.cliVersion = payload.cli_version || metadata.cliVersion;
|
|
832
|
+
metadata.modelProvider = payload.model_provider || metadata.modelProvider;
|
|
833
|
+
}
|
|
834
|
+
else if (entry.type === "turn_context" && payload) {
|
|
835
|
+
metadata.cwd = payload.cwd || metadata.cwd;
|
|
836
|
+
metadata.model = payload.model || metadata.model;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
let output = "# Conversation\n\n";
|
|
840
|
+
output += "## Metadata\n\n";
|
|
841
|
+
output += "**Harness:** Codex\n\n";
|
|
842
|
+
if (metadata.sessionId)
|
|
843
|
+
output += `**Session ID:** ${metadata.sessionId}\n\n`;
|
|
844
|
+
if (metadata.gitBranch)
|
|
845
|
+
output += `**Git Branch:** ${metadata.gitBranch}\n\n`;
|
|
846
|
+
if (metadata.cwd)
|
|
847
|
+
output += `**Working Directory:** ${metadata.cwd}\n\n`;
|
|
848
|
+
if (metadata.cliVersion)
|
|
849
|
+
output += `**Codex Version:** ${metadata.cliVersion}\n\n`;
|
|
850
|
+
if (metadata.model)
|
|
851
|
+
output += `**Model:** ${metadata.model}\n\n`;
|
|
852
|
+
if (metadata.modelProvider)
|
|
853
|
+
output += `**Model Provider:** ${metadata.modelProvider}\n\n`;
|
|
854
|
+
output += "---\n\n";
|
|
855
|
+
output += "## Messages\n\n";
|
|
856
|
+
for (let i = 0; i < entries.length; i++) {
|
|
857
|
+
const entry = entries[i];
|
|
858
|
+
const payload = entry.payload;
|
|
859
|
+
if (entry.type !== "response_item" || !payload) {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
const timestamp = entry.timestamp ? new Date(entry.timestamp).toLocaleString() : "";
|
|
863
|
+
const anchor = payload.call_id || `msg-${i}`;
|
|
864
|
+
if (payload.type === "message") {
|
|
865
|
+
const text = extractCodexText(payload.content);
|
|
866
|
+
if (!text.trim()) {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
const roleLabel = payload.role === "user" ? "User" : "Agent";
|
|
870
|
+
output += `### **${roleLabel}** (${timestamp}) {#${anchor}}\n\n`;
|
|
871
|
+
output += `${text}\n\n`;
|
|
872
|
+
}
|
|
873
|
+
else if (payload.type === "function_call" ||
|
|
874
|
+
payload.type === "custom_tool_call" ||
|
|
875
|
+
payload.type === "tool_search_call" ||
|
|
876
|
+
payload.type === "local_shell_call") {
|
|
877
|
+
const toolName = payload.name || payload.namespace || payload.type || "unknown";
|
|
878
|
+
output += `### **Tool Use** (${timestamp}) {#${anchor}}\n\n`;
|
|
879
|
+
output += `**Tool Use:** \`${toolName}\`\n\n`;
|
|
880
|
+
output += formatCodexToolInputMarkdown(codexToolInput(payload));
|
|
881
|
+
}
|
|
882
|
+
else if (payload.type === "function_call_output" ||
|
|
883
|
+
payload.type === "custom_tool_call_output" ||
|
|
884
|
+
payload.type === "tool_search_output" ||
|
|
885
|
+
payload.type === "local_shell_call_output") {
|
|
886
|
+
const result = codexToolOutput(payload);
|
|
887
|
+
output += `### **Tool Result** (${timestamp}) {#${anchor}-result}\n\n`;
|
|
888
|
+
output += "**Result:**\n";
|
|
889
|
+
if (result.includes("\n") || result.length > 100) {
|
|
890
|
+
output += `\`\`\`\n${result}\n\`\`\`\n\n`;
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
output += `${result}\n\n`;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
else if (payload.type === "reasoning") {
|
|
897
|
+
const text = extractCodexText(payload.summary);
|
|
898
|
+
if (text.trim()) {
|
|
899
|
+
output += `### **Reasoning Summary** (${timestamp}) {#${anchor}}\n\n`;
|
|
900
|
+
output += `${text}\n\n`;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return output;
|
|
905
|
+
}
|
|
906
|
+
function formatMarkdownDocumentAsHTML(markdown) {
|
|
907
|
+
return `<!DOCTYPE html>
|
|
908
|
+
<html>
|
|
909
|
+
<head>
|
|
910
|
+
<meta charset="UTF-8">
|
|
911
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
912
|
+
<title>Conversation</title>
|
|
913
|
+
<style>
|
|
914
|
+
body {
|
|
915
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
|
916
|
+
max-width: 900px;
|
|
917
|
+
margin: 0 auto;
|
|
918
|
+
padding: 24px;
|
|
919
|
+
line-height: 1.5;
|
|
920
|
+
color: #1d1d1f;
|
|
921
|
+
background: #f5f5f7;
|
|
922
|
+
}
|
|
923
|
+
.markdown-content {
|
|
924
|
+
background: #ffffff;
|
|
925
|
+
padding: 24px;
|
|
926
|
+
border-radius: 12px;
|
|
927
|
+
}
|
|
928
|
+
pre {
|
|
929
|
+
overflow-x: auto;
|
|
930
|
+
background: #f5f5f7;
|
|
931
|
+
padding: 12px;
|
|
932
|
+
border-radius: 6px;
|
|
933
|
+
}
|
|
934
|
+
code {
|
|
935
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
936
|
+
}
|
|
937
|
+
</style>
|
|
938
|
+
</head>
|
|
939
|
+
<body>
|
|
940
|
+
<div class="markdown-content">
|
|
941
|
+
${renderMarkdownSafely(markdown)}
|
|
942
|
+
</div>
|
|
943
|
+
</body>
|
|
944
|
+
</html>`;
|
|
945
|
+
}
|
|
946
|
+
function escapeHtml(text) {
|
|
947
|
+
const map = {
|
|
948
|
+
"&": "&",
|
|
949
|
+
"<": "<",
|
|
950
|
+
">": ">",
|
|
951
|
+
'"': """,
|
|
952
|
+
"'": "'",
|
|
953
|
+
};
|
|
954
|
+
return text.replace(/[&<>"']/g, (m) => map[m] ?? m);
|
|
955
|
+
}
|
|
956
|
+
function isMarkdown(text) {
|
|
957
|
+
// Detect common markdown patterns
|
|
958
|
+
const markdownPatterns = [
|
|
959
|
+
/^#{1,6}\s/m, // Headers
|
|
960
|
+
/\*\*[^*]+\*\*/, // Bold
|
|
961
|
+
/\*[^*]+\*/, // Italic
|
|
962
|
+
/`[^`]+`/, // Inline code
|
|
963
|
+
/```/, // Code blocks
|
|
964
|
+
/^\s*[-*+]\s/m, // Unordered lists
|
|
965
|
+
/^\s*\d+\.\s/m, // Ordered lists
|
|
966
|
+
/^\s*>\s/m, // Blockquotes
|
|
967
|
+
/\[.+\]\(.+\)/, // Links
|
|
968
|
+
];
|
|
969
|
+
// If text has at least 2 markdown patterns, consider it markdown
|
|
970
|
+
const matchCount = markdownPatterns.filter((pattern) => pattern.test(text)).length;
|
|
971
|
+
return matchCount >= 2;
|
|
972
|
+
}
|
|
973
|
+
function renderMarkdownSafely(text) {
|
|
974
|
+
try {
|
|
975
|
+
const renderer = new marked.Renderer();
|
|
976
|
+
renderer.html = ({ text }) => escapeHtml(text);
|
|
977
|
+
return marked.parse(text, { async: false, renderer });
|
|
978
|
+
}
|
|
979
|
+
catch {
|
|
980
|
+
// Fallback to escaped HTML if markdown parsing fails
|
|
981
|
+
return `<pre>${escapeHtml(text)}</pre>`;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
//# sourceMappingURL=show.js.map
|