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
|
@@ -15,11 +15,60 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const claudeDesktopSessions = require('./lib/claude-desktop-sessions');
|
|
18
|
+
const { codexRolloutIdFromPath, findCodexSessionFiles } = require('./lib/session-history');
|
|
18
19
|
|
|
19
20
|
const CLAUDE_PROJECTS_DIR = path.join(process.env.HOME, '.claude', 'projects');
|
|
20
21
|
|
|
21
22
|
// --- Detection ---
|
|
22
23
|
|
|
24
|
+
function sessionFileIdFromPath(filePath) {
|
|
25
|
+
const virtual = claudeDesktopSessions.parseVirtualSessionPath(filePath);
|
|
26
|
+
if (virtual) return virtual.sessionId;
|
|
27
|
+
const codexId = codexRolloutIdFromPath(filePath);
|
|
28
|
+
if (codexId) return codexId;
|
|
29
|
+
const base = path.basename(filePath).replace(/\.jsonl(\.bak)?$/, '');
|
|
30
|
+
const uuid = base.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i);
|
|
31
|
+
return uuid ? uuid[1].toLowerCase() : base;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fileEntryFromPath(filePath, expectedFileId, projectEntry = '') {
|
|
35
|
+
if (!filePath) return null;
|
|
36
|
+
const actualFileId = sessionFileIdFromPath(filePath);
|
|
37
|
+
if (expectedFileId && actualFileId !== expectedFileId) return null;
|
|
38
|
+
try {
|
|
39
|
+
const sourcePath = claudeDesktopSessions.sourcePathForStat(filePath);
|
|
40
|
+
const stat = fs.statSync(sourcePath);
|
|
41
|
+
if (!stat.isFile()) return null;
|
|
42
|
+
return { filePath, stat, projectEntry };
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function addFileIndexEntry(fileIndex, filePath, projectEntry) {
|
|
49
|
+
const file = fileEntryFromPath(filePath, null, projectEntry);
|
|
50
|
+
if (file) fileIndex[sessionFileIdFromPath(filePath)] = file;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveDbSessionFile(row, expectedFileId, fileIndex) {
|
|
54
|
+
if (!expectedFileId) return null;
|
|
55
|
+
if (fileIndex[expectedFileId]) return fileIndex[expectedFileId];
|
|
56
|
+
|
|
57
|
+
const stored = fileEntryFromPath(row?.jsonl_path, expectedFileId);
|
|
58
|
+
if (stored) return stored;
|
|
59
|
+
|
|
60
|
+
if ((row?.provider === 'codex') || String(row?.jsonl_path || '').includes(`${path.sep}.codex${path.sep}sessions${path.sep}`)) {
|
|
61
|
+
try {
|
|
62
|
+
for (const filePath of findCodexSessionFiles(expectedFileId)) {
|
|
63
|
+
const file = fileEntryFromPath(filePath, expectedFileId);
|
|
64
|
+
if (file) return file;
|
|
65
|
+
}
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
23
72
|
function dbTimestampFromIso(value) {
|
|
24
73
|
if (!value) return '';
|
|
25
74
|
const ms = new Date(value).getTime();
|
|
@@ -138,7 +187,7 @@ function detectMismatches(db, getAllSessionFiles) {
|
|
|
138
187
|
} catch {}
|
|
139
188
|
const slugCol = hasSlugColumn ? 'a.slug' : "'' AS slug";
|
|
140
189
|
allSessions = db.prepare(`
|
|
141
|
-
SELECT c.id, c.title, c.user_renamed, c.starred, c.project_path, c.cwd,
|
|
190
|
+
SELECT c.id, c.provider, c.title, c.user_renamed, c.starred, c.project_path, c.cwd,
|
|
142
191
|
c.created_at, c.updated_at,
|
|
143
192
|
a.agent_session_id, a.jsonl_path, a.file_size, a.first_message,
|
|
144
193
|
a.modified_at, a.hostname, a.model, a.git_branch, a.user_msg_count,
|
|
@@ -156,12 +205,7 @@ function detectMismatches(db, getAllSessionFiles) {
|
|
|
156
205
|
const fileIndex = {}; // uuid -> { filePath, stat, projectEntry }
|
|
157
206
|
try {
|
|
158
207
|
for (const { filePath, projectEntry } of getAllSessionFiles()) {
|
|
159
|
-
|
|
160
|
-
const uuid = virtual ? virtual.sessionId : path.basename(filePath).replace(/\.jsonl(\.bak)?$/, '');
|
|
161
|
-
try {
|
|
162
|
-
const stat = fs.statSync(claudeDesktopSessions.sourcePathForStat(filePath));
|
|
163
|
-
fileIndex[uuid] = { filePath, stat, projectEntry };
|
|
164
|
-
} catch {}
|
|
208
|
+
addFileIndexEntry(fileIndex, filePath, projectEntry);
|
|
165
209
|
}
|
|
166
210
|
} catch (e) {
|
|
167
211
|
issues.push({ type: 'scan_error', severity: 'warning', sessionId: null,
|
|
@@ -183,7 +227,7 @@ function detectMismatches(db, getAllSessionFiles) {
|
|
|
183
227
|
|
|
184
228
|
// Skip DB-only rows with no file expectation (legacy tabs with no agent_session_id)
|
|
185
229
|
const expectedFileId = (agentId && agentId !== sid) ? agentId : sid;
|
|
186
|
-
const file = fileIndex
|
|
230
|
+
const file = resolveDbSessionFile(row, expectedFileId, fileIndex);
|
|
187
231
|
|
|
188
232
|
// Check 1: Missing file
|
|
189
233
|
if (!file && row.file_size > 0) {
|
|
@@ -193,6 +237,7 @@ function detectMismatches(db, getAllSessionFiles) {
|
|
|
193
237
|
expected_file_id: expectedFileId,
|
|
194
238
|
db_file_size: row.file_size,
|
|
195
239
|
db_jsonl_path: row.jsonl_path || '',
|
|
240
|
+
db_provider: row.provider || '',
|
|
196
241
|
db_title: row.title || '',
|
|
197
242
|
},
|
|
198
243
|
suggestion: 'File may have been deleted or moved. Check .jsonl.bak variant.',
|
|
@@ -215,6 +260,7 @@ function detectMismatches(db, getAllSessionFiles) {
|
|
|
215
260
|
db_file_size: row.file_size,
|
|
216
261
|
actual_file_size: file.stat.size,
|
|
217
262
|
size_diff: sizeDiff,
|
|
263
|
+
db_jsonl_path: row.jsonl_path || '',
|
|
218
264
|
},
|
|
219
265
|
suggestion: 'DB metadata is stale — will be refreshed on next session list load.',
|
|
220
266
|
});
|
|
@@ -439,12 +485,7 @@ function recoverMismatches(db, issues, getAllSessionFiles) {
|
|
|
439
485
|
const fileIndex = {};
|
|
440
486
|
try {
|
|
441
487
|
for (const { filePath, projectEntry } of getAllSessionFiles()) {
|
|
442
|
-
|
|
443
|
-
const uuid = virtual ? virtual.sessionId : path.basename(filePath).replace(/\.jsonl(\.bak)?$/, '');
|
|
444
|
-
try {
|
|
445
|
-
const stat = fs.statSync(claudeDesktopSessions.sourcePathForStat(filePath));
|
|
446
|
-
fileIndex[uuid] = { filePath, stat, projectEntry };
|
|
447
|
-
} catch {}
|
|
488
|
+
addFileIndexEntry(fileIndex, filePath, projectEntry);
|
|
448
489
|
}
|
|
449
490
|
} catch {}
|
|
450
491
|
|
|
@@ -505,7 +546,9 @@ function recoverMismatches(db, issues, getAllSessionFiles) {
|
|
|
505
546
|
case 'stale_metadata': {
|
|
506
547
|
// Refresh metadata from actual file
|
|
507
548
|
const fileId = issue.details.file_id;
|
|
508
|
-
const file = fileIndex[fileId]
|
|
549
|
+
const file = fileIndex[fileId]
|
|
550
|
+
|| fileEntryFromPath(issue.details?.db_jsonl_path, fileId)
|
|
551
|
+
|| resolveDbSessionFile({ provider: 'codex', jsonl_path: issue.details?.db_jsonl_path || '' }, fileId, fileIndex);
|
|
509
552
|
if (!file) { result.skipped++; break; }
|
|
510
553
|
try {
|
|
511
554
|
db.prepare('UPDATE agent_sessions SET file_size = ?, modified_at = ?, updated_at = datetime(\'now\') WHERE ctm_session_id = ?')
|
|
@@ -555,11 +598,14 @@ function recoverMismatches(db, issues, getAllSessionFiles) {
|
|
|
555
598
|
return (b.updated_at || '').localeCompare(a.updated_at || '');
|
|
556
599
|
});
|
|
557
600
|
const keeper = rows[0];
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
.
|
|
562
|
-
|
|
601
|
+
const unlinkLosers = db.transaction((losers) => {
|
|
602
|
+
for (const row of losers) {
|
|
603
|
+
// Unlink the agent_session from the losing ctm_session (delete the agent_sessions row for this CTM)
|
|
604
|
+
db.prepare("DELETE FROM agent_sessions WHERE ctm_session_id = ? AND agent_session_id = ?")
|
|
605
|
+
.run(row.id, issue.details.agent_session_id);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
unlinkLosers(rows.slice(1));
|
|
563
609
|
result.fixed++;
|
|
564
610
|
result.actions.push(`Resolved duplicate claim on ${issue.details.agent_session_id.slice(0, 8)}: kept ${keeper.id.slice(0, 8)}, unlinked ${rows.length - 1} others`);
|
|
565
611
|
} catch {
|
|
@@ -574,14 +620,17 @@ function recoverMismatches(db, issues, getAllSessionFiles) {
|
|
|
574
620
|
const agentId = issue.details.agent_session_id;
|
|
575
621
|
if (!agentId) { result.skipped++; break; }
|
|
576
622
|
try {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
.
|
|
623
|
+
const txn = db.transaction(() => {
|
|
624
|
+
// Create a new ctm_session using the agent_session_id as the CTM id
|
|
625
|
+
db.prepare(`
|
|
626
|
+
INSERT OR IGNORE INTO ctm_sessions (id, provider, project_path, cwd, title)
|
|
627
|
+
VALUES (?, 'claude', ?, ?, '')
|
|
628
|
+
`).run(agentId, issue.details.project_path || '', issue.details.project_path || '');
|
|
629
|
+
// Link the agent_session to its new parent
|
|
630
|
+
db.prepare('UPDATE agent_sessions SET ctm_session_id = ? WHERE agent_session_id = ?')
|
|
631
|
+
.run(agentId, agentId);
|
|
632
|
+
});
|
|
633
|
+
txn();
|
|
585
634
|
result.fixed++;
|
|
586
635
|
result.actions.push(`Created ctm_session parent for orphan agent ${agentId.slice(0, 8)}`);
|
|
587
636
|
} catch (e) {
|
|
@@ -28,12 +28,12 @@ const ABOVE_ANCHOR_DEPTH = 40;
|
|
|
28
28
|
// Claude Code: "Esc to cancel". Codex: "Press enter to confirm or esc to cancel".
|
|
29
29
|
const ANCHOR_RE = /Esc to cancel|esc to cancel|Press enter to confirm/;
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// Approval-option pattern. Accepts an optional selection-arrow prefix in any of
|
|
32
32
|
// the forms different CLIs use: ❯ (Claude Code), ›/▶/▸ (Cursor/others), or
|
|
33
33
|
// plain ASCII > (Codex). Without this, Codex's "> 1. Yes, proceed (y)" would
|
|
34
34
|
// be skipped over and the validator would lock onto option 2 ("2. Yes, ...")
|
|
35
35
|
// — which is unstyled in Codex's renderer and trips no-widget-formatting.
|
|
36
|
-
const YES_RE = /^\s*(?:[❯›▶▸>]\s*)?\d+\.\s*Yes\b/i;
|
|
36
|
+
const YES_RE = /^\s*(?:[❯›▶▸>]\s*)?\d+\.\s*(?:Yes|Allow)\b/i;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Check if the terminal is currently displaying an active approval widget.
|
|
@@ -142,9 +142,10 @@ function _hasWidgetFormatting(buf, yesRow, totalRows) {
|
|
|
142
142
|
const yesText = yesLine.translateToString(true);
|
|
143
143
|
if (/[❯›▶▸]/.test(yesText)) return true;
|
|
144
144
|
|
|
145
|
-
// Check for
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// Check for a selection marker near the approval options. Codex MCP forms can
|
|
146
|
+
// select option 2 ("Allow for this session"), while option 1 is the first
|
|
147
|
+
// approval-shaped line used for anchoring.
|
|
148
|
+
for (let row = Math.max(0, yesRow - 1); row < Math.min(totalRows, yesRow + 8); row++) {
|
|
148
149
|
const line = buf.getLine(buf.viewportY + row);
|
|
149
150
|
if (!line) continue;
|
|
150
151
|
const text = line.translateToString(true);
|
|
@@ -152,6 +153,15 @@ function _hasWidgetFormatting(buf, yesRow, totalRows) {
|
|
|
152
153
|
if (/[❯›▶▸]/.test(text)) return true;
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
// Check for "❯" marker anywhere in bottom 5 rows for prompts whose option
|
|
157
|
+
// block is pushed down by long wrapped content.
|
|
158
|
+
for (let row = Math.max(0, totalRows - 5); row < totalRows; row++) {
|
|
159
|
+
const line = buf.getLine(buf.viewportY + row);
|
|
160
|
+
if (!line) continue;
|
|
161
|
+
const text = line.translateToString(true);
|
|
162
|
+
if (/[❯›▶▸]/.test(text)) return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
155
165
|
// Check for ANSI foreground color on the Yes-option line.
|
|
156
166
|
// xterm's BufferLine.getCell(x) returns an IBufferCell with .getFgColor()
|
|
157
167
|
// (0 = default). Any non-default fg color = styled = widget.
|
|
@@ -36,14 +36,13 @@ parentPort.on('message', (msg) => {
|
|
|
36
36
|
const insert = d.prepare('INSERT OR REPLACE INTO scrollback_log (ctm_session_id, chunk_seq, data) VALUES (?, ?, ?)');
|
|
37
37
|
const txn = d.transaction((items) => {
|
|
38
38
|
for (const c of items) insert.run(msg.sessionId, c.seq, c.data);
|
|
39
|
+
const maxSeq = items[items.length - 1]?.seq || 0;
|
|
40
|
+
if (maxSeq > SCROLLBACK_MAX_CHUNKS) {
|
|
41
|
+
const cutoff = maxSeq - SCROLLBACK_MAX_CHUNKS;
|
|
42
|
+
d.prepare('DELETE FROM scrollback_log WHERE ctm_session_id = ? AND chunk_seq < ?').run(msg.sessionId, cutoff);
|
|
43
|
+
}
|
|
39
44
|
});
|
|
40
45
|
txn(msg.chunks);
|
|
41
|
-
// Prune oldest chunks
|
|
42
|
-
const maxSeq = msg.chunks[msg.chunks.length - 1].seq;
|
|
43
|
-
if (maxSeq > SCROLLBACK_MAX_CHUNKS) {
|
|
44
|
-
const cutoff = maxSeq - SCROLLBACK_MAX_CHUNKS;
|
|
45
|
-
d.prepare('DELETE FROM scrollback_log WHERE ctm_session_id = ? AND chunk_seq < ?').run(msg.sessionId, cutoff);
|
|
46
|
-
}
|
|
47
46
|
if (msg.requestId) parentPort.postMessage({ type: 'appended', requestId: msg.requestId });
|
|
48
47
|
} catch (e) {
|
|
49
48
|
// Non-fatal — scrollback is best-effort
|
|
@@ -47,6 +47,12 @@ function isCodexStatusRedraw(data) {
|
|
|
47
47
|
module.exports = {
|
|
48
48
|
...baseDetector,
|
|
49
49
|
id: 'codex',
|
|
50
|
+
// Codex's ratatui status frames arrive in bursts. A short Claude-style
|
|
51
|
+
// debounce lets the sidebar bounce between Running and Waiting/Idle while the
|
|
52
|
+
// terminal still says "Working". Keep the busy state stable long enough for
|
|
53
|
+
// multiple server heartbeats to confirm or renew it, while explicit
|
|
54
|
+
// approval/choice prompts still bypass this elsewhere.
|
|
55
|
+
idleDebounceMs: 15000,
|
|
50
56
|
|
|
51
57
|
isActiveChunk(data) {
|
|
52
58
|
if (!baseDetector.isActiveChunk(data)) return false;
|
package/template/package.json
CHANGED
package/template/wall-e/agent.js
CHANGED
|
@@ -109,7 +109,8 @@ function bootstrapSkills() {
|
|
|
109
109
|
description: 'Scan Claude Code session files for new conversations',
|
|
110
110
|
trigger_type: 'interval',
|
|
111
111
|
trigger_config: JSON.stringify({ interval_ms: 60000 }),
|
|
112
|
-
prompt_template: '
|
|
112
|
+
prompt_template: 'INTERNAL_SKILL:scan-ctm-sessions',
|
|
113
|
+
execution: 'script',
|
|
113
114
|
});
|
|
114
115
|
|
|
115
116
|
brain.insertSkill({
|
|
@@ -140,16 +141,43 @@ function bootstrapSkills() {
|
|
|
140
141
|
function syncBundledSkills() {
|
|
141
142
|
const filesystemSkills = loadAllSkills();
|
|
142
143
|
const dbSkills = brain.listSkills({});
|
|
143
|
-
const
|
|
144
|
+
const dbByName = new Map(dbSkills.map(s => [s.name, s]));
|
|
145
|
+
const dbNames = new Set(dbByName.keys());
|
|
144
146
|
|
|
145
147
|
let added = 0;
|
|
148
|
+
let updated = 0;
|
|
146
149
|
for (const skill of filesystemSkills) {
|
|
147
|
-
if (dbNames.has(skill.name)) continue;
|
|
148
|
-
|
|
149
150
|
const triggerType = (skill.trigger && skill.trigger.type) || skill.execution || 'manual';
|
|
150
151
|
const triggerConfig = skill.trigger && skill.trigger.interval_ms
|
|
151
152
|
? JSON.stringify({ interval_ms: skill.trigger.interval_ms })
|
|
152
153
|
: null;
|
|
154
|
+
const promptTemplate = skill.execution === 'script'
|
|
155
|
+
? `INTERNAL_SKILL:${skill.name}`
|
|
156
|
+
: skill.instructions || '';
|
|
157
|
+
|
|
158
|
+
if (dbNames.has(skill.name)) {
|
|
159
|
+
const existing = dbByName.get(skill.name);
|
|
160
|
+
// Upgrade legacy prompt-based CTM scanning to the deterministic script
|
|
161
|
+
// path. Session continuity must not depend on live LLM/network access.
|
|
162
|
+
if (skill.name === 'scan-ctm-sessions' && skill.execution === 'script' && existing) {
|
|
163
|
+
const updates = {};
|
|
164
|
+
if (existing.execution !== 'script') updates.execution = 'script';
|
|
165
|
+
if (existing.prompt_template !== promptTemplate) updates.prompt_template = promptTemplate;
|
|
166
|
+
if (existing.trigger_type !== triggerType) updates.trigger_type = triggerType;
|
|
167
|
+
if (triggerConfig && existing.trigger_config !== triggerConfig) updates.trigger_config = triggerConfig;
|
|
168
|
+
if (existing.auto_disabled_at) {
|
|
169
|
+
updates.enabled = 1;
|
|
170
|
+
updates.auto_disabled_at = null;
|
|
171
|
+
}
|
|
172
|
+
if (existing.auto_disabled_reason) updates.auto_disabled_reason = null;
|
|
173
|
+
if (Object.keys(updates).length > 0) {
|
|
174
|
+
brain.updateSkill(existing.id, updates);
|
|
175
|
+
updated++;
|
|
176
|
+
console.log(`[wall-e] Updated bundled skill: ${skill.name}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
153
181
|
|
|
154
182
|
brain.insertSkill({
|
|
155
183
|
name: skill.name,
|
|
@@ -159,9 +187,7 @@ function syncBundledSkills() {
|
|
|
159
187
|
// Persist the legacy `INTERNAL_SKILL:` marker so downgrades can still
|
|
160
188
|
// dispatch script skills via the prompt_template fallback. The schema
|
|
161
189
|
// column `execution` is the authoritative source going forward.
|
|
162
|
-
prompt_template:
|
|
163
|
-
? `INTERNAL_SKILL:${skill.name}`
|
|
164
|
-
: skill.instructions || '',
|
|
190
|
+
prompt_template: promptTemplate,
|
|
165
191
|
execution: skill.execution === 'script' ? 'script' : 'agent',
|
|
166
192
|
});
|
|
167
193
|
added++;
|
|
@@ -171,6 +197,9 @@ function syncBundledSkills() {
|
|
|
171
197
|
if (added > 0) {
|
|
172
198
|
console.log(`[wall-e] Synced ${added} new bundled skill(s) to DB`);
|
|
173
199
|
}
|
|
200
|
+
if (updated > 0) {
|
|
201
|
+
console.log(`[wall-e] Updated ${updated} bundled skill(s) in DB`);
|
|
202
|
+
}
|
|
174
203
|
}
|
|
175
204
|
|
|
176
205
|
function bootstrapTasks() {
|
|
@@ -185,6 +214,7 @@ async function checkForUpdates() {
|
|
|
185
214
|
try {
|
|
186
215
|
const { execFile } = require('node:child_process');
|
|
187
216
|
const { promisify } = require('node:util');
|
|
217
|
+
const telemetry = require('./telemetry');
|
|
188
218
|
const execFileAsync = promisify(execFile);
|
|
189
219
|
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
190
220
|
const current = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
|
|
@@ -200,11 +230,21 @@ async function checkForUpdates() {
|
|
|
200
230
|
brain.setKv('update_available', JSON.stringify({
|
|
201
231
|
current, latest, checked_at: new Date().toISOString(),
|
|
202
232
|
}));
|
|
233
|
+
telemetry.track('upgrade_check', { status: 'available', current, latest });
|
|
234
|
+
telemetry.trackFunnelStep('upgrade_available');
|
|
203
235
|
console.log(`[wall-e] Update available: ${current} -> ${latest}`);
|
|
204
236
|
} else {
|
|
205
237
|
brain.setKv('update_available', '');
|
|
238
|
+
telemetry.track('upgrade_check', { status: 'current', current, latest: latest || 'unknown' });
|
|
206
239
|
}
|
|
207
|
-
} catch {
|
|
240
|
+
} catch (err) {
|
|
241
|
+
try {
|
|
242
|
+
const telemetry = require('./telemetry');
|
|
243
|
+
telemetry.track('upgrade_check', {
|
|
244
|
+
status: 'failed',
|
|
245
|
+
reason: err?.code || err?.name || 'error',
|
|
246
|
+
});
|
|
247
|
+
} catch {}
|
|
208
248
|
// npm not available or network error — skip silently
|
|
209
249
|
}
|
|
210
250
|
}
|
|
@@ -386,6 +426,17 @@ async function main() {
|
|
|
386
426
|
process.exit(e && e.code === 'EADDRINUSE' ? 98 : 1);
|
|
387
427
|
}
|
|
388
428
|
|
|
429
|
+
// Slack Socket Mode is optional and event-driven. It only starts when an
|
|
430
|
+
// app-level xapp token is configured, so the existing polling path remains
|
|
431
|
+
// unchanged for workspaces that have not registered a Wall-E Slack app.
|
|
432
|
+
let slackSocketMode = null;
|
|
433
|
+
try {
|
|
434
|
+
const { startSlackSocketModeListener } = require('./slack/socket-mode-listener');
|
|
435
|
+
slackSocketMode = startSlackSocketModeListener();
|
|
436
|
+
} catch (e) {
|
|
437
|
+
console.warn('[wall-e] Slack Socket Mode startup skipped:', e.message);
|
|
438
|
+
}
|
|
439
|
+
|
|
389
440
|
// Start telemetry (anonymous, opt-out via WALLE_TELEMETRY=0)
|
|
390
441
|
telemetry.printNoticeIfFirstRun();
|
|
391
442
|
telemetry.start();
|
|
@@ -613,6 +664,7 @@ async function main() {
|
|
|
613
664
|
startDelayMs: 25000,
|
|
614
665
|
debounceMs: 30000,
|
|
615
666
|
onResult: (r) => {
|
|
667
|
+
if (r?.skipped) return;
|
|
616
668
|
if (r.decision !== 'noop' && r.decision !== 'observed') {
|
|
617
669
|
console.log(`[wall-e] Initiative: ${r.decision} — ${r.reasoning?.slice(0, 100)}`);
|
|
618
670
|
}
|
|
@@ -923,6 +975,9 @@ async function main() {
|
|
|
923
975
|
for (const ch of channels) {
|
|
924
976
|
try { await ch.stop(); } catch {}
|
|
925
977
|
}
|
|
978
|
+
if (slackSocketMode) {
|
|
979
|
+
try { slackSocketMode.stop(); } catch {}
|
|
980
|
+
}
|
|
926
981
|
|
|
927
982
|
telemetry.track('shutdown', { uptime: process.uptime() });
|
|
928
983
|
telemetry.stop();
|