create-walle 0.9.13 → 0.9.15
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/README.md +8 -3
- package/bin/create-walle.js +232 -32
- package/bin/mcp-inject.js +18 -53
- package/package.json +3 -1
- package/template/claude-task-manager/api-prompts.js +11 -2
- package/template/claude-task-manager/approval-agent.js +7 -0
- package/template/claude-task-manager/db.js +94 -75
- package/template/claude-task-manager/docs/session-standup-command-center-design.md +242 -0
- package/template/claude-task-manager/docs/session-tooltip-freshness-design.md +224 -0
- package/template/claude-task-manager/docs/session-ux-issue-review-2026-05-01.md +369 -0
- package/template/claude-task-manager/fuzzy-utils.js +10 -2
- package/template/claude-task-manager/git-utils.js +140 -10
- package/template/claude-task-manager/lib/agent-capabilities.js +1 -1
- package/template/claude-task-manager/lib/agent-presets.js +38 -5
- package/template/claude-task-manager/lib/codex-terminal-final.js +53 -0
- package/template/claude-task-manager/lib/ctm-session-context-api.js +222 -0
- package/template/claude-task-manager/lib/session-diagnostics.js +56 -0
- package/template/claude-task-manager/lib/session-history.js +309 -16
- package/template/claude-task-manager/lib/session-standup.js +409 -0
- package/template/claude-task-manager/lib/session-stream.js +253 -20
- package/template/claude-task-manager/lib/standup-attention.js +200 -0
- package/template/claude-task-manager/lib/status-hooks.js +8 -2
- package/template/claude-task-manager/lib/update-telemetry.js +114 -0
- package/template/claude-task-manager/lib/walle-ctm-history.js +49 -6
- package/template/claude-task-manager/lib/walle-default-model.js +55 -0
- package/template/claude-task-manager/lib/walle-mcp-auto-config.js +66 -0
- package/template/claude-task-manager/lib/walle-supervisor.js +86 -19
- package/template/claude-task-manager/lib/walle-transcript.js +1 -3
- package/template/claude-task-manager/lib/worktree-cwd.js +82 -0
- package/template/claude-task-manager/package.json +1 -0
- package/template/claude-task-manager/providers/codex-mcp.js +104 -0
- package/template/claude-task-manager/providers/index.js +2 -0
- package/template/claude-task-manager/public/css/setup.css +2 -1
- package/template/claude-task-manager/public/css/walle.css +71 -0
- package/template/claude-task-manager/public/index.html +2388 -429
- package/template/claude-task-manager/public/js/message-renderer.js +314 -35
- package/template/claude-task-manager/public/js/session-search-utils.js +185 -3
- package/template/claude-task-manager/public/js/session-status-precedence.js +125 -0
- package/template/claude-task-manager/public/js/setup.js +62 -19
- package/template/claude-task-manager/public/js/stream-view.js +396 -55
- package/template/claude-task-manager/public/js/terminal-restore-state.js +57 -0
- package/template/claude-task-manager/public/js/walle-session.js +234 -26
- package/template/claude-task-manager/public/js/walle.js +143 -2
- package/template/claude-task-manager/server.js +1402 -433
- package/template/claude-task-manager/session-integrity.js +77 -28
- package/template/claude-task-manager/workers/approval-widget-validator.js +15 -5
- package/template/claude-task-manager/workers/scrollback-worker.js +5 -6
- package/template/claude-task-manager/workers/state-detectors/codex.js +6 -0
- package/template/package.json +1 -1
- package/template/wall-e/agent-runners/claude-code.js +2 -0
- package/template/wall-e/agent.js +63 -8
- package/template/wall-e/api-walle.js +330 -52
- package/template/wall-e/brain.js +291 -42
- package/template/wall-e/chat.js +172 -15
- package/template/wall-e/coding/compaction-service.js +19 -5
- package/template/wall-e/coding/stream-processor.js +22 -2
- package/template/wall-e/coding/workspace-replay.js +1 -4
- package/template/wall-e/coding-orchestrator.js +250 -80
- package/template/wall-e/compat.js +0 -28
- package/template/wall-e/context/context-builder.js +3 -1
- package/template/wall-e/embeddings.js +2 -7
- package/template/wall-e/eval/agent-runner.js +30 -9
- package/template/wall-e/eval/benchmark-generator.js +21 -1
- package/template/wall-e/eval/benchmarks/chat-eval.json +66 -6
- package/template/wall-e/eval/benchmarks/coding-agent.json +0 -596
- package/template/wall-e/eval/cc-replay.js +1 -0
- package/template/wall-e/eval/codex-cli-baseline.js +633 -0
- package/template/wall-e/eval/debug-agent003.js +1 -0
- package/template/wall-e/eval/eval-orchestrator.js +3 -3
- package/template/wall-e/eval/run-agent-benchmarks.js +11 -3
- package/template/wall-e/eval/run-codex-cli-baseline.js +177 -0
- package/template/wall-e/eval/run-model-comparison.js +1 -0
- package/template/wall-e/eval/swebench-adapter.js +1 -0
- package/template/wall-e/evaluation/quorum-evaluator.js +0 -1
- package/template/wall-e/extraction/knowledge-extractor.js +1 -2
- package/template/wall-e/lib/mcp-integration.js +336 -0
- package/template/wall-e/llm/ollama.js +47 -8
- package/template/wall-e/llm/ollama.plugin.json +1 -1
- package/template/wall-e/llm/tool-adapter.js +1 -0
- package/template/wall-e/loops/ingest.js +42 -8
- package/template/wall-e/loops/initiative.js +87 -2
- package/template/wall-e/mcp-server.js +872 -19
- package/template/wall-e/memory/ctm-context-client.js +230 -0
- package/template/wall-e/memory/ctm-session-context.js +1376 -0
- package/template/wall-e/prompts/coding/memory-protocol.md +6 -0
- package/template/wall-e/server.js +30 -1
- package/template/wall-e/skills/_bundled/memory-search/SKILL.md +8 -0
- package/template/wall-e/skills/_bundled/scan-ctm-sessions/SKILL.md +20 -0
- package/template/wall-e/skills/_bundled/scan-ctm-sessions/run.js +43 -0
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +471 -188
- package/template/wall-e/skills/skill-planner.js +86 -4
- package/template/wall-e/slack/socket-mode-listener.js +276 -0
- package/template/wall-e/telemetry.js +70 -2
- package/template/wall-e/tools/builtin-middleware.js +55 -2
- package/template/wall-e/tools/shell-policy.js +1 -1
- package/template/wall-e/tools/slack-owner.js +104 -0
- package/template/website/index.html +4 -4
- package/template/builder-journal.md +0 -17
|
@@ -7,6 +7,8 @@ const { getResumeSpec: getAgentResumeSpec, normalizeAgentType } = require('./age
|
|
|
7
7
|
|
|
8
8
|
const DEFAULT_CODEX_LOOKBACK_SEC = 10;
|
|
9
9
|
const DEFAULT_CODEX_MAX_DELAY_SEC = 180;
|
|
10
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11
|
+
const CODEX_ROLLOUT_BASENAME_RE = /^rollout-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.bak)?$/i;
|
|
10
12
|
|
|
11
13
|
function getResumeSpec(agentType, resumeId) {
|
|
12
14
|
return getAgentResumeSpec(agentType || 'claude', resumeId);
|
|
@@ -27,12 +29,63 @@ function extractResumeTarget(agentType, args) {
|
|
|
27
29
|
return idx >= 0 && list[idx + 1] ? { index: idx, valueIndex: idx + 1, sessionId: list[idx + 1] } : null;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
function
|
|
31
|
-
if (!
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
32
|
+
function codexRolloutFileInfo(filePath) {
|
|
33
|
+
if (!filePath) return null;
|
|
34
|
+
const match = path.basename(filePath).match(CODEX_ROLLOUT_BASENAME_RE);
|
|
35
|
+
if (!match) return null;
|
|
36
|
+
return {
|
|
37
|
+
rolloutPrefix: match[1],
|
|
38
|
+
threadId: match[2].toLowerCase(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function codexRolloutIdFromPath(filePath) {
|
|
43
|
+
return codexRolloutFileInfo(filePath)?.threadId || '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function codexSessionSearchRoots(sessionsRoot, homeDir = process.env.HOME) {
|
|
47
|
+
if (sessionsRoot) return fs.existsSync(sessionsRoot) ? [sessionsRoot] : [];
|
|
48
|
+
return [
|
|
49
|
+
path.join(homeDir, '.codex', 'sessions'),
|
|
50
|
+
path.join(homeDir, '.codex', 'archived_sessions'),
|
|
51
|
+
].filter((root) => fs.existsSync(root));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sortCodexRolloutFiles(files) {
|
|
55
|
+
return [...new Set(files)]
|
|
56
|
+
.map((filePath) => ({ filePath, info: codexRolloutFileInfo(filePath) }))
|
|
57
|
+
.filter((entry) => entry.info)
|
|
58
|
+
.sort((a, b) => {
|
|
59
|
+
const byPrefix = b.info.rolloutPrefix.localeCompare(a.info.rolloutPrefix);
|
|
60
|
+
return byPrefix || b.filePath.localeCompare(a.filePath);
|
|
61
|
+
})
|
|
62
|
+
.map((entry) => entry.filePath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function findCodexStateRolloutPath(threadId, homeDir = process.env.HOME) {
|
|
66
|
+
if (!UUID_RE.test(String(threadId || ''))) return '';
|
|
67
|
+
let db = null;
|
|
68
|
+
try {
|
|
69
|
+
db = openCodexStateDb(homeDir);
|
|
70
|
+
if (!db) return '';
|
|
71
|
+
const row = db.prepare('SELECT rollout_path FROM threads WHERE id = ?').get(threadId);
|
|
72
|
+
const rolloutPath = row?.rollout_path || '';
|
|
73
|
+
if (!rolloutPath || !fs.existsSync(rolloutPath)) return '';
|
|
74
|
+
return codexRolloutIdFromPath(rolloutPath) === threadId.toLowerCase() ? rolloutPath : '';
|
|
75
|
+
} catch {
|
|
76
|
+
return '';
|
|
77
|
+
} finally {
|
|
78
|
+
try { if (db) db.close(); } catch {}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function findCodexSessionFiles(threadId, sessionsRoot, homeDir = process.env.HOME) {
|
|
83
|
+
const wanted = String(threadId || '').trim().toLowerCase();
|
|
84
|
+
if (!UUID_RE.test(wanted)) return [];
|
|
34
85
|
const matches = [];
|
|
35
|
-
const
|
|
86
|
+
const indexedPath = sessionsRoot ? '' : findCodexStateRolloutPath(wanted, homeDir);
|
|
87
|
+
if (indexedPath) matches.push(indexedPath);
|
|
88
|
+
const stack = codexSessionSearchRoots(sessionsRoot, homeDir);
|
|
36
89
|
while (stack.length > 0) {
|
|
37
90
|
const dir = stack.pop();
|
|
38
91
|
let entries = [];
|
|
@@ -43,12 +96,12 @@ function findCodexSessionFiles(threadId, sessionsRoot) {
|
|
|
43
96
|
stack.push(fullPath);
|
|
44
97
|
continue;
|
|
45
98
|
}
|
|
46
|
-
if (entry.isFile() &&
|
|
99
|
+
if (entry.isFile() && codexRolloutIdFromPath(fullPath) === wanted) {
|
|
47
100
|
matches.push(fullPath);
|
|
48
101
|
}
|
|
49
102
|
}
|
|
50
103
|
}
|
|
51
|
-
return matches
|
|
104
|
+
return sortCodexRolloutFiles(matches);
|
|
52
105
|
}
|
|
53
106
|
|
|
54
107
|
function parseSessionStartMs(value) {
|
|
@@ -66,6 +119,46 @@ function parseSessionStartMs(value) {
|
|
|
66
119
|
return Number.isFinite(ms) ? ms : null;
|
|
67
120
|
}
|
|
68
121
|
|
|
122
|
+
function cleanCodexCwdPath(value) {
|
|
123
|
+
const raw = String(value || '').trim();
|
|
124
|
+
if (!raw) return '';
|
|
125
|
+
const normalized = path.normalize(path.isAbsolute(raw) ? raw : path.resolve(raw));
|
|
126
|
+
return normalized.replace(/\/+$/, '') || path.parse(normalized).root;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function realpathCodexCwdPath(value) {
|
|
130
|
+
const cleaned = cleanCodexCwdPath(value);
|
|
131
|
+
if (!cleaned) return '';
|
|
132
|
+
try {
|
|
133
|
+
const realpath = fs.realpathSync.native ? fs.realpathSync.native(cleaned) : fs.realpathSync(cleaned);
|
|
134
|
+
return cleanCodexCwdPath(realpath);
|
|
135
|
+
} catch {
|
|
136
|
+
// The cwd may point at an already-removed worktree; fall back to lexical aliases.
|
|
137
|
+
return cleaned;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function codexCwdVariants(value) {
|
|
142
|
+
const variants = new Set();
|
|
143
|
+
const add = (candidate) => {
|
|
144
|
+
const cleaned = cleanCodexCwdPath(candidate);
|
|
145
|
+
if (!cleaned) return;
|
|
146
|
+
variants.add(cleaned);
|
|
147
|
+
if (cleaned === '/tmp') variants.add('/private/tmp');
|
|
148
|
+
else if (cleaned.startsWith('/tmp/')) variants.add(`/private${cleaned}`);
|
|
149
|
+
else if (cleaned === '/private/tmp') variants.add('/tmp');
|
|
150
|
+
else if (cleaned.startsWith('/private/tmp/')) variants.add(`/tmp/${cleaned.slice('/private/tmp/'.length)}`);
|
|
151
|
+
};
|
|
152
|
+
add(value);
|
|
153
|
+
add(realpathCodexCwdPath(value));
|
|
154
|
+
return [...variants];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function codexCwdMatches(a, b) {
|
|
158
|
+
const bVariants = new Set(codexCwdVariants(b));
|
|
159
|
+
return codexCwdVariants(a).some((variant) => bVariants.has(variant));
|
|
160
|
+
}
|
|
161
|
+
|
|
69
162
|
function openCodexStateDb(homeDir = process.env.HOME) {
|
|
70
163
|
const Database = require('better-sqlite3');
|
|
71
164
|
const dbPath = path.join(homeDir, '.codex', 'state_5.sqlite');
|
|
@@ -80,7 +173,7 @@ function getCodexThreadById(threadId, homeDir = process.env.HOME) {
|
|
|
80
173
|
db = openCodexStateDb(homeDir);
|
|
81
174
|
if (!db) return null;
|
|
82
175
|
return db.prepare(
|
|
83
|
-
'SELECT
|
|
176
|
+
'SELECT * FROM threads WHERE id = ?'
|
|
84
177
|
).get(threadId) || null;
|
|
85
178
|
} catch {
|
|
86
179
|
return null;
|
|
@@ -98,7 +191,7 @@ function getCodexThreadResumeCwd(threadId, {
|
|
|
98
191
|
const thread = getCodexThreadById(threadId, homeDir);
|
|
99
192
|
if (thread?.cwd) return thread.cwd;
|
|
100
193
|
|
|
101
|
-
const files = findCodexSessionFiles(threadId,
|
|
194
|
+
const files = findCodexSessionFiles(threadId, null, homeDir);
|
|
102
195
|
for (const filePath of files) {
|
|
103
196
|
const row = _readCodexRolloutMetadata(filePath);
|
|
104
197
|
if (row?.cwd) return row.cwd;
|
|
@@ -124,8 +217,7 @@ function readFilePrefix(filePath, maxBytes = 64 * 1024) {
|
|
|
124
217
|
function codexThreadPreciseCreatedMs(row, homeDir = process.env.HOME) {
|
|
125
218
|
const fallback = Number.isFinite(row?.created_at) ? row.created_at * 1000 : null;
|
|
126
219
|
if (!row?.id) return fallback;
|
|
127
|
-
const
|
|
128
|
-
const files = findCodexSessionFiles(row.id, sessionsRoot);
|
|
220
|
+
const files = findCodexSessionFiles(row.id, null, homeDir);
|
|
129
221
|
for (const filePath of files) {
|
|
130
222
|
const prefix = readFilePrefix(filePath);
|
|
131
223
|
if (!prefix) continue;
|
|
@@ -142,7 +234,8 @@ function codexThreadPreciseCreatedMs(row, homeDir = process.env.HOME) {
|
|
|
142
234
|
}
|
|
143
235
|
|
|
144
236
|
function _readCodexRolloutMetadata(filePath) {
|
|
145
|
-
|
|
237
|
+
// 512KB covers the large turn_context blob that Codex injects before the user_message event
|
|
238
|
+
const prefix = readFilePrefix(filePath, 512 * 1024);
|
|
146
239
|
if (!prefix) return null;
|
|
147
240
|
let meta = null;
|
|
148
241
|
let firstUser = '';
|
|
@@ -228,7 +321,7 @@ function findCodexThreadFromRolloutsForSession({
|
|
|
228
321
|
}
|
|
229
322
|
if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;
|
|
230
323
|
const row = _readCodexRolloutMetadata(fullPath);
|
|
231
|
-
if (!row || row.cwd
|
|
324
|
+
if (!row || !codexCwdMatches(row.cwd, cwd)) continue;
|
|
232
325
|
const ms = row._preciseCreatedMs;
|
|
233
326
|
if (ms < minMs || ms > maxMs) continue;
|
|
234
327
|
matches.push({ row, distance: Math.abs(ms - startMs) });
|
|
@@ -257,6 +350,7 @@ function findCodexThreadForSession({
|
|
|
257
350
|
const startMs = parseSessionStartMs(createdAtMs);
|
|
258
351
|
if (!Number.isFinite(startMs)) return null;
|
|
259
352
|
const startSec = Math.floor(startMs / 1000);
|
|
353
|
+
const cwdVariants = codexCwdVariants(cwd);
|
|
260
354
|
const fallbackFromRollouts = () => findCodexThreadFromRolloutsForSession({
|
|
261
355
|
cwd,
|
|
262
356
|
createdAtMs,
|
|
@@ -265,16 +359,18 @@ function findCodexThreadForSession({
|
|
|
265
359
|
maxDelaySec,
|
|
266
360
|
allowAmbiguous,
|
|
267
361
|
});
|
|
362
|
+
if (cwdVariants.length === 0) return null;
|
|
268
363
|
let db = null;
|
|
269
364
|
try {
|
|
270
365
|
db = openCodexStateDb(homeDir);
|
|
271
366
|
if (!db) return fallbackFromRollouts();
|
|
367
|
+
const cwdPlaceholders = cwdVariants.map(() => '?').join(', ');
|
|
272
368
|
const rows = db.prepare(
|
|
273
369
|
`SELECT rowid, id, title, first_user_message, model, cwd, git_branch, created_at, updated_at
|
|
274
370
|
FROM threads
|
|
275
|
-
WHERE cwd
|
|
371
|
+
WHERE cwd IN (${cwdPlaceholders}) AND created_at >= ? AND created_at <= ?
|
|
276
372
|
ORDER BY ABS(created_at - ?) ASC, created_at ASC`
|
|
277
|
-
).all(
|
|
373
|
+
).all(...cwdVariants, startSec - lookbackSec, startSec + maxDelaySec, startSec);
|
|
278
374
|
if (rows.length === 0) return fallbackFromRollouts();
|
|
279
375
|
const best = rows[0];
|
|
280
376
|
const bestDistance = Math.abs((best.created_at || 0) - startSec);
|
|
@@ -482,6 +578,170 @@ function parseCodexJsonlFileIntoMessages(filePath, messages, options = {}) {
|
|
|
482
578
|
};
|
|
483
579
|
}
|
|
484
580
|
|
|
581
|
+
function normalizeCodexSearchValue(value) {
|
|
582
|
+
return String(value || '')
|
|
583
|
+
.trim()
|
|
584
|
+
.toLowerCase()
|
|
585
|
+
.replace(/(^|[\s([{])[$/]+(?=[a-z0-9_-])/g, '$1')
|
|
586
|
+
.replace(/\s+/g, ' ');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function codexSearchSnippet(text, terms) {
|
|
590
|
+
const raw = String(text || '').replace(/\s+/g, ' ').trim();
|
|
591
|
+
const normalized = normalizeCodexSearchValue(raw);
|
|
592
|
+
let idx = -1;
|
|
593
|
+
for (const term of terms || []) {
|
|
594
|
+
idx = normalized.indexOf(term);
|
|
595
|
+
if (idx >= 0) break;
|
|
596
|
+
}
|
|
597
|
+
if (idx < 0) return raw.slice(0, 180);
|
|
598
|
+
const start = Math.max(0, idx - 60);
|
|
599
|
+
const end = Math.min(raw.length, idx + 140);
|
|
600
|
+
return (start > 0 ? '...' : '') + raw.slice(start, end).trim() + (end < raw.length ? '...' : '');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function codexSearchMatchForMessage(msg, terms, order) {
|
|
604
|
+
const text = msg?.text || '';
|
|
605
|
+
const normalized = normalizeCodexSearchValue(text);
|
|
606
|
+
if (!terms.every(term => normalized.includes(term))) return null;
|
|
607
|
+
|
|
608
|
+
const rawLower = String(text).toLowerCase();
|
|
609
|
+
let score = msg.role === 'user' ? 20 : 0;
|
|
610
|
+
for (const term of terms) {
|
|
611
|
+
if (rawLower.includes(`$${term}`) || rawLower.includes(`/${term}`)) score += 50;
|
|
612
|
+
const wordRe = new RegExp(`(^|[^a-z0-9_-])${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^a-z0-9_-]|$)`, 'i');
|
|
613
|
+
if (wordRe.test(text)) score += 10;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return {
|
|
617
|
+
role: msg.role,
|
|
618
|
+
timestamp: msg.timestamp || '',
|
|
619
|
+
snippet: codexSearchSnippet(text, terms),
|
|
620
|
+
_rankScore: score,
|
|
621
|
+
_order: order,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function searchCodexRolloutFileMessages(filePath, terms, row) {
|
|
626
|
+
const state = { seenUsers: new Set(), sessionMeta: null };
|
|
627
|
+
const matches = [];
|
|
628
|
+
const chunkSize = 1024 * 1024;
|
|
629
|
+
const buf = Buffer.alloc(chunkSize);
|
|
630
|
+
const decoder = new StringDecoder('utf8');
|
|
631
|
+
let partial = '';
|
|
632
|
+
let fd = null;
|
|
633
|
+
let order = 0;
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
fd = fs.openSync(filePath, 'r');
|
|
637
|
+
while (true) {
|
|
638
|
+
const bytesRead = fs.readSync(fd, buf, 0, buf.length, null);
|
|
639
|
+
if (bytesRead <= 0) break;
|
|
640
|
+
const text = partial + decoder.write(buf.subarray(0, bytesRead));
|
|
641
|
+
let start = 0;
|
|
642
|
+
while (true) {
|
|
643
|
+
const end = text.indexOf('\n', start);
|
|
644
|
+
if (end === -1) break;
|
|
645
|
+
const line = text.slice(start, end);
|
|
646
|
+
if (line) {
|
|
647
|
+
let entry;
|
|
648
|
+
try { entry = JSON.parse(line); } catch { entry = null; }
|
|
649
|
+
if (entry) {
|
|
650
|
+
if (!state.sessionMeta) {
|
|
651
|
+
const meta = codexSessionMetaFromEntry(entry);
|
|
652
|
+
if (meta?.id) state.sessionMeta = meta;
|
|
653
|
+
}
|
|
654
|
+
const msg = codexMessageFromEntry(entry);
|
|
655
|
+
if (msg) {
|
|
656
|
+
if (msg.role === 'user') {
|
|
657
|
+
const key = codexUserKey(msg.text);
|
|
658
|
+
if (!key || state.seenUsers.has(key)) {
|
|
659
|
+
start = end + 1;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
state.seenUsers.add(key);
|
|
663
|
+
}
|
|
664
|
+
const match = codexSearchMatchForMessage(msg, terms, order++);
|
|
665
|
+
if (match) matches.push(match);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
start = end + 1;
|
|
670
|
+
}
|
|
671
|
+
partial = text.slice(start);
|
|
672
|
+
}
|
|
673
|
+
const tail = decoder.end();
|
|
674
|
+
if (tail) partial += tail;
|
|
675
|
+
if (partial && partial.trim()) {
|
|
676
|
+
let entry;
|
|
677
|
+
try { entry = JSON.parse(partial); } catch { entry = null; }
|
|
678
|
+
if (entry) {
|
|
679
|
+
if (!state.sessionMeta) {
|
|
680
|
+
const meta = codexSessionMetaFromEntry(entry);
|
|
681
|
+
if (meta?.id) state.sessionMeta = meta;
|
|
682
|
+
}
|
|
683
|
+
const msg = codexMessageFromEntry(entry);
|
|
684
|
+
if (msg) {
|
|
685
|
+
let shouldConsider = true;
|
|
686
|
+
if (msg.role === 'user') {
|
|
687
|
+
const key = codexUserKey(msg.text);
|
|
688
|
+
if (!key || state.seenUsers.has(key)) {
|
|
689
|
+
shouldConsider = false;
|
|
690
|
+
} else {
|
|
691
|
+
state.seenUsers.add(key);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const match = shouldConsider ? codexSearchMatchForMessage(msg, terms, order++) : null;
|
|
695
|
+
if (match) matches.push(match);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} finally {
|
|
700
|
+
try { if (fd != null) fs.closeSync(fd); } catch {}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (matches.length === 0) return null;
|
|
704
|
+
const meta = state.sessionMeta || {};
|
|
705
|
+
const id = row?.id || meta.id || codexRolloutIdFromPath(filePath);
|
|
706
|
+
if (!id) return null;
|
|
707
|
+
let stat = null;
|
|
708
|
+
try { stat = fs.statSync(filePath); } catch {}
|
|
709
|
+
const updatedAt = stat ? Math.floor(stat.mtimeMs / 1000) : (row?.updated_at || 0);
|
|
710
|
+
return {
|
|
711
|
+
id,
|
|
712
|
+
title: row?.title || row?.first_user_message || '',
|
|
713
|
+
first_user_message: row?.first_user_message || '',
|
|
714
|
+
model: row?.model || meta.model || '',
|
|
715
|
+
cwd: row?.cwd || meta.cwd || '',
|
|
716
|
+
git_branch: row?.git_branch || meta.git_branch || '',
|
|
717
|
+
created_at: row?.created_at || Math.floor(parseSessionStartMs(meta.timestamp) / 1000) || 0,
|
|
718
|
+
updated_at: updatedAt,
|
|
719
|
+
_jsonlPath: filePath,
|
|
720
|
+
_fileSize: stat?.size || 0,
|
|
721
|
+
_match: matches.sort((a, b) => (b._rankScore || 0) - (a._rankScore || 0) || (b._order || 0) - (a._order || 0))[0],
|
|
722
|
+
_matchCount: matches.length,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function searchCodexRolloutMessages(query, {
|
|
727
|
+
homeDir = process.env.HOME,
|
|
728
|
+
maxFiles = 100,
|
|
729
|
+
limit = 50,
|
|
730
|
+
} = {}) {
|
|
731
|
+
const terms = normalizeCodexSearchValue(query).split(/\s+/).filter(t => t.length >= 2);
|
|
732
|
+
if (terms.length === 0) return [];
|
|
733
|
+
const rows = listCodexSessionsFromRollouts(homeDir, maxFiles);
|
|
734
|
+
const hits = [];
|
|
735
|
+
for (const row of rows) {
|
|
736
|
+
if (hits.length >= limit) break;
|
|
737
|
+
const filePath = row._jsonlPath;
|
|
738
|
+
if (!filePath) continue;
|
|
739
|
+
const hit = searchCodexRolloutFileMessages(filePath, terms, row);
|
|
740
|
+
if (hit) hits.push(hit);
|
|
741
|
+
}
|
|
742
|
+
return hits.sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0)).slice(0, limit);
|
|
743
|
+
}
|
|
744
|
+
|
|
485
745
|
function commandLooksLikeCodex(value) {
|
|
486
746
|
return /(^|\/)codex(\s|$)/i.test(String(value || ''));
|
|
487
747
|
}
|
|
@@ -502,7 +762,7 @@ function findCodexThreadForCtmSession({
|
|
|
502
762
|
if (explicitId) {
|
|
503
763
|
const direct = getCodexThreadById(explicitId, homeDir);
|
|
504
764
|
if (direct) return direct;
|
|
505
|
-
const files = findCodexSessionFiles(explicitId,
|
|
765
|
+
const files = findCodexSessionFiles(explicitId, null, homeDir);
|
|
506
766
|
if (files.length > 0) {
|
|
507
767
|
return {
|
|
508
768
|
id: explicitId,
|
|
@@ -535,10 +795,41 @@ function findCodexThreadForCtmSession({
|
|
|
535
795
|
});
|
|
536
796
|
}
|
|
537
797
|
|
|
798
|
+
// Scan ~/.codex/sessions/**/*.jsonl and return session metadata rows sorted by mtime DESC.
|
|
799
|
+
// Used as a fallback when state_5.sqlite is unavailable or corrupt.
|
|
800
|
+
function listCodexSessionsFromRollouts(homeDir = process.env.HOME, limit = 50) {
|
|
801
|
+
const root = path.join(homeDir, '.codex', 'sessions');
|
|
802
|
+
if (!fs.existsSync(root)) return [];
|
|
803
|
+
const results = [];
|
|
804
|
+
const stack = [root];
|
|
805
|
+
while (stack.length > 0) {
|
|
806
|
+
const dir = stack.pop();
|
|
807
|
+
let entries = [];
|
|
808
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { continue; }
|
|
809
|
+
for (const entry of entries) {
|
|
810
|
+
const fullPath = path.join(dir, entry.name);
|
|
811
|
+
if (entry.isDirectory()) { stack.push(fullPath); continue; }
|
|
812
|
+
if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;
|
|
813
|
+
const row = _readCodexRolloutMetadata(fullPath);
|
|
814
|
+
if (!row) continue;
|
|
815
|
+
try {
|
|
816
|
+
const st = fs.statSync(fullPath);
|
|
817
|
+
row.updated_at = Math.max(row.updated_at || 0, Math.floor(st.mtimeMs / 1000));
|
|
818
|
+
row._jsonlPath = fullPath;
|
|
819
|
+
} catch {}
|
|
820
|
+
results.push(row);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
results.sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0));
|
|
824
|
+
return results.slice(0, limit);
|
|
825
|
+
}
|
|
826
|
+
|
|
538
827
|
module.exports = {
|
|
539
828
|
cleanCodexUserText,
|
|
540
829
|
codexInputText,
|
|
541
830
|
codexMessageFromEntry,
|
|
831
|
+
codexRolloutFileInfo,
|
|
832
|
+
codexRolloutIdFromPath,
|
|
542
833
|
codexUserKey,
|
|
543
834
|
extractResumeTarget,
|
|
544
835
|
findCodexSessionFiles,
|
|
@@ -548,6 +839,8 @@ module.exports = {
|
|
|
548
839
|
getCodexThreadResumeCwd,
|
|
549
840
|
getCodexThreadById,
|
|
550
841
|
getResumeSpec,
|
|
842
|
+
listCodexSessionsFromRollouts,
|
|
843
|
+
searchCodexRolloutMessages,
|
|
551
844
|
parseSessionStartMs,
|
|
552
845
|
parseCodexJsonlIntoMessages,
|
|
553
846
|
parseCodexJsonlFileIntoMessages,
|