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
|
@@ -28,7 +28,7 @@ const path = require('path');
|
|
|
28
28
|
const crypto = require('crypto');
|
|
29
29
|
process.chdir(path.join(__dirname, '..'));
|
|
30
30
|
|
|
31
|
-
const { setupSandbox, cleanupSandbox, runAgentBenchmark, runAgentBenchmarkSuite, resolveModelName } = require('./agent-runner');
|
|
31
|
+
const { setupSandbox, cleanupSandbox, runAgentBenchmark, runAgentBenchmarkSuite, resolveModelName, isTrustedAgentResult } = require('./agent-runner');
|
|
32
32
|
const { decorateBenchmarkResult, DEFAULT_SCORER_VERSION } = require('./manifest');
|
|
33
33
|
const benchmarks = require('./benchmarks/coding-agent.json');
|
|
34
34
|
|
|
@@ -334,6 +334,7 @@ function storeBenchmarkResult({ brain, runId, benchmark, result, provider, model
|
|
|
334
334
|
const scoringMethod = benchmark.agentExpectations?.testCommand
|
|
335
335
|
? 'agent-rubric+tests'
|
|
336
336
|
: 'agent-rubric';
|
|
337
|
+
const trusted = isTrustedBenchmarkResult(result);
|
|
337
338
|
brain.insertBenchmarkResult(decorateBenchmarkResult({
|
|
338
339
|
runId,
|
|
339
340
|
suite: 'coding-agent',
|
|
@@ -359,7 +360,7 @@ function storeBenchmarkResult({ brain, runId, benchmark, result, provider, model
|
|
|
359
360
|
outputTokens: result.outputTokens ?? null,
|
|
360
361
|
scorerVersion: DEFAULT_SCORER_VERSION,
|
|
361
362
|
scoringMethod,
|
|
362
|
-
trusted
|
|
363
|
+
trusted,
|
|
363
364
|
runConfig: { timeoutMs, scoringMethod },
|
|
364
365
|
}, {
|
|
365
366
|
suite: 'coding-agent',
|
|
@@ -369,10 +370,17 @@ function storeBenchmarkResult({ brain, runId, benchmark, result, provider, model
|
|
|
369
370
|
model: resolveModelName(modelId),
|
|
370
371
|
scoringMethod,
|
|
371
372
|
scorerVersion: DEFAULT_SCORER_VERSION,
|
|
372
|
-
trusted
|
|
373
|
+
trusted,
|
|
373
374
|
runConfig: { timeoutMs, scoringMethod },
|
|
374
375
|
}));
|
|
375
376
|
} catch (err) {
|
|
376
377
|
console.warn(` [WARN] Failed to store benchmark result: ${err.message}`);
|
|
377
378
|
}
|
|
378
379
|
}
|
|
380
|
+
|
|
381
|
+
function isTrustedBenchmarkResult(result = {}) {
|
|
382
|
+
return !result.error &&
|
|
383
|
+
result.testsPassed === true &&
|
|
384
|
+
(result.score?.composite || 0) > 0 &&
|
|
385
|
+
result.score?.dimensions?._zeroed !== true;
|
|
386
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
const envPath = require('path').resolve(__dirname, '..', '..', '.env');
|
|
6
|
+
const lines = require('fs').readFileSync(envPath, 'utf8').split('\n');
|
|
7
|
+
for (const line of lines) {
|
|
8
|
+
const match = line.match(/^([A-Z_]+)=(.*)$/);
|
|
9
|
+
if (match && !process.env[match[1]]) process.env[match[1]] = match[2];
|
|
10
|
+
}
|
|
11
|
+
} catch {}
|
|
12
|
+
|
|
13
|
+
const crypto = require('crypto');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
process.chdir(path.join(__dirname, '..'));
|
|
16
|
+
|
|
17
|
+
const benchmarks = require('./benchmarks/coding-agent.json');
|
|
18
|
+
const {
|
|
19
|
+
DEFAULT_RESULTS_DIR,
|
|
20
|
+
runCodexCliBaselineBenchmark,
|
|
21
|
+
storeBaselineResult,
|
|
22
|
+
summarizeBaselineResults,
|
|
23
|
+
writeBaselineArtifact,
|
|
24
|
+
} = require('./codex-cli-baseline');
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
const args = parseArgs(process.argv.slice(2));
|
|
28
|
+
if (args.help) {
|
|
29
|
+
printHelp();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const selected = selectBenchmarks({
|
|
34
|
+
id: args.id,
|
|
35
|
+
limit: args.limit ? Number(args.limit) : null,
|
|
36
|
+
difficulty: args.difficulty,
|
|
37
|
+
});
|
|
38
|
+
if (!selected.length) {
|
|
39
|
+
console.error('No benchmarks selected');
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const timeoutMs = args.timeout ? Number(args.timeout) : 600_000;
|
|
45
|
+
const runId = crypto.randomUUID();
|
|
46
|
+
const dryRun = !!args['dry-run'];
|
|
47
|
+
const model = args.model || null;
|
|
48
|
+
const resultsDir = args['results-dir'] || DEFAULT_RESULTS_DIR;
|
|
49
|
+
const brain = initBrain();
|
|
50
|
+
|
|
51
|
+
console.log('=== Codex CLI Baseline Runner ===');
|
|
52
|
+
console.log(`Run: ${runId}`);
|
|
53
|
+
console.log(`Benchmarks: ${selected.length}/${benchmarks.length}`);
|
|
54
|
+
console.log(`Mode: ${dryRun ? 'dry-run' : 'codex exec baseline'}`);
|
|
55
|
+
console.log(`Model: ${model || 'codex default'}`);
|
|
56
|
+
console.log(`Auth: ${args['use-env-openai-key'] ? 'env OPENAI_API_KEY allowed' : 'ChatGPT/Codex auth preferred (OPENAI_API_KEY stripped)'}`);
|
|
57
|
+
console.log('');
|
|
58
|
+
|
|
59
|
+
const results = [];
|
|
60
|
+
for (const benchmark of selected) {
|
|
61
|
+
console.log(`--- ${benchmark.id} (${benchmark.difficulty || 'unknown'}) ---`);
|
|
62
|
+
console.log(` Prompt: ${String(benchmark.prompt || '').replace(/\s+/g, ' ').slice(0, 120)}`);
|
|
63
|
+
console.log(` Fixture: ${benchmark.agentExpectations?.projectFixture || 'express-basic'}`);
|
|
64
|
+
|
|
65
|
+
const started = Date.now();
|
|
66
|
+
const result = await runCodexCliBaselineBenchmark(benchmark, {
|
|
67
|
+
dryRun,
|
|
68
|
+
model,
|
|
69
|
+
timeoutMs,
|
|
70
|
+
keepFailures: !!args['keep-failures'],
|
|
71
|
+
useEnvOpenAIKey: !!args['use-env-openai-key'],
|
|
72
|
+
dangerouslyBypassSandbox: !!args['dangerously-bypass-sandbox'],
|
|
73
|
+
allowMcp: !!args['allow-mcp'],
|
|
74
|
+
disableMcpServers: normalizeListArg(args['disable-mcp']),
|
|
75
|
+
});
|
|
76
|
+
result.runId = runId;
|
|
77
|
+
result.timestamp = new Date().toISOString();
|
|
78
|
+
results.push(result);
|
|
79
|
+
|
|
80
|
+
if (args.record) {
|
|
81
|
+
result.artifactPath = writeBaselineArtifact(result, { resultsDir });
|
|
82
|
+
}
|
|
83
|
+
storeBaselineResult({ brain, runId, benchmark, result, model, timeoutMs });
|
|
84
|
+
|
|
85
|
+
console.log(` Success: ${result.success}`);
|
|
86
|
+
if (result.status) console.log(` Status: ${result.status}`);
|
|
87
|
+
if (result.score) console.log(` Score: ${(result.score.composite || 0).toFixed(3)}`);
|
|
88
|
+
console.log(` Files: ${(result.actualFileChanges || []).join(', ') || 'none'}`);
|
|
89
|
+
console.log(` Tests: ${result.testsPassed == null ? 'N/A' : result.testsPassed}`);
|
|
90
|
+
console.log(` Time: ${((Date.now() - started) / 1000).toFixed(1)}s`);
|
|
91
|
+
if (result.error) console.log(` Error: ${String(result.error).split('\n')[0]}`);
|
|
92
|
+
if (result.sandboxDir) console.log(` Kept sandbox: ${result.sandboxDir}`);
|
|
93
|
+
if (result.artifactPath) console.log(` Artifact: ${result.artifactPath}`);
|
|
94
|
+
console.log('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const summary = summarizeBaselineResults(results);
|
|
98
|
+
console.log('=== SUMMARY ===');
|
|
99
|
+
console.log(`Passed: ${summary.passed}/${summary.total}`);
|
|
100
|
+
console.log(`Average composite score: ${summary.avgComposite.toFixed(3)}`);
|
|
101
|
+
console.log(`Failures: ${JSON.stringify(summary.failures)}`);
|
|
102
|
+
if (!dryRun && summary.failed > 0) process.exitCode = 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function initBrain() {
|
|
106
|
+
try {
|
|
107
|
+
const brain = require('../brain');
|
|
108
|
+
brain.initDb();
|
|
109
|
+
return brain;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.warn(`Brain not available: ${err.message}`);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function selectBenchmarks({ id, limit, difficulty } = {}) {
|
|
117
|
+
let selected = benchmarks;
|
|
118
|
+
if (id) selected = selected.filter((benchmark) => benchmark.id === id);
|
|
119
|
+
if (difficulty) selected = selected.filter((benchmark) => benchmark.difficulty === difficulty);
|
|
120
|
+
if (limit) selected = selected.slice(0, limit);
|
|
121
|
+
return selected;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseArgs(argv) {
|
|
125
|
+
const out = {};
|
|
126
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
127
|
+
const arg = argv[i];
|
|
128
|
+
if (!arg.startsWith('--')) continue;
|
|
129
|
+
const key = arg.slice(2);
|
|
130
|
+
if (['dry-run', 'record', 'keep-failures', 'use-env-openai-key', 'dangerously-bypass-sandbox', 'allow-mcp', 'help'].includes(key)) {
|
|
131
|
+
out[key] = true;
|
|
132
|
+
} else {
|
|
133
|
+
if (out[key] === undefined) out[key] = argv[i + 1];
|
|
134
|
+
else if (Array.isArray(out[key])) out[key].push(argv[i + 1]);
|
|
135
|
+
else out[key] = [out[key], argv[i + 1]];
|
|
136
|
+
i += 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeListArg(value) {
|
|
143
|
+
if (value == null) return null;
|
|
144
|
+
const values = Array.isArray(value) ? value : [value];
|
|
145
|
+
return values
|
|
146
|
+
.flatMap((entry) => String(entry || '').split(','))
|
|
147
|
+
.map((entry) => entry.trim())
|
|
148
|
+
.filter(Boolean);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function printHelp() {
|
|
152
|
+
console.log(`Usage:
|
|
153
|
+
node eval/run-codex-cli-baseline.js --id agent-001 --model gpt-5.5
|
|
154
|
+
node eval/run-codex-cli-baseline.js --limit 5 --model gpt-5-codex --record
|
|
155
|
+
node eval/run-codex-cli-baseline.js --dry-run --difficulty easy
|
|
156
|
+
|
|
157
|
+
Options:
|
|
158
|
+
--id <id> Run one benchmark id
|
|
159
|
+
--limit <n> Limit selected benchmarks
|
|
160
|
+
--difficulty <easy|medium|hard>
|
|
161
|
+
--model <id> Forwarded verbatim to: codex exec -m <id>
|
|
162
|
+
--timeout <ms> Per-benchmark timeout (default 600000)
|
|
163
|
+
--record Write result JSON artifacts
|
|
164
|
+
--results-dir <path> Artifact directory
|
|
165
|
+
--keep-failures Keep failed sandbox directories
|
|
166
|
+
--use-env-openai-key Let codex inherit OPENAI_API_KEY instead of preferring ChatGPT auth
|
|
167
|
+
--allow-mcp Let codex load enabled MCP servers from local config
|
|
168
|
+
--disable-mcp <name[,name]> Explicit MCP server names to disable; defaults to auto-discover enabled servers
|
|
169
|
+
--dangerously-bypass-sandbox Pass codex's bypass flag. Use only inside an external sandbox.
|
|
170
|
+
--dry-run Verify fixture setup only; does not invoke codex
|
|
171
|
+
`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
main().catch((err) => {
|
|
175
|
+
console.error(err.stack || err.message);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
|
@@ -324,7 +324,6 @@ async function runQuorum(options, deps = {}) {
|
|
|
324
324
|
|
|
325
325
|
// If no workerResponse, use backward-compat legacy flow
|
|
326
326
|
if (!workerResponse) {
|
|
327
|
-
try { require('../compat').recordCompatUsage('legacy_quorum_consensus'); } catch {}
|
|
328
327
|
return runLegacyQuorum(options, deps);
|
|
329
328
|
}
|
|
330
329
|
|
|
@@ -81,8 +81,7 @@ function parseExtractionResponse(text) {
|
|
|
81
81
|
knowledgeEntries = parsed.knowledge;
|
|
82
82
|
classifications = Array.isArray(parsed.classifications) ? parsed.classifications : [];
|
|
83
83
|
} else if (Array.isArray(parsed)) {
|
|
84
|
-
//
|
|
85
|
-
try { require('../compat').recordCompatUsage('legacy_knowledge_format'); } catch {}
|
|
84
|
+
// Plain array tolerance for older model outputs and tests.
|
|
86
85
|
knowledgeEntries = parsed;
|
|
87
86
|
} else {
|
|
88
87
|
return { knowledge: [], classifications: [] };
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const http = require('http');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const WALLE_SERVER_NAME = 'wall-e';
|
|
8
|
+
const AGENT_INSTRUCTIONS_BEGIN = '<!-- wall-e-memory-routing:start -->';
|
|
9
|
+
const AGENT_INSTRUCTIONS_END = '<!-- wall-e-memory-routing:end -->';
|
|
10
|
+
|
|
11
|
+
const MCP_TARGETS = Object.freeze([
|
|
12
|
+
{ id: 'claude-code', tool: 'Claude Code', configPath: '.claude/mcp.json', detectPath: '.claude', kind: 'json' },
|
|
13
|
+
{ id: 'claude-code-global', tool: 'Claude Code Global', configPath: '.claude.json', detectPath: '.claude.json', kind: 'json' },
|
|
14
|
+
{ id: 'codex', tool: 'Codex', configPath: '.codex/config.toml', detectPath: '.codex', kind: 'codex-toml' },
|
|
15
|
+
{ id: 'cursor', tool: 'Cursor', configPath: '.cursor/mcp.json', detectPath: '.cursor', kind: 'json' },
|
|
16
|
+
{ id: 'windsurf', tool: 'Windsurf', configPath: '.codeium/windsurf/mcp_config.json', detectPath: '.codeium/windsurf', kind: 'json' },
|
|
17
|
+
{ id: 'claude-desktop', tool: 'Claude Desktop', configPath: 'Library/Application Support/Claude/claude_desktop_config.json', detectPath: 'Library/Application Support/Claude', kind: 'json' },
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const AGENT_INSTRUCTION_TARGETS = Object.freeze([
|
|
21
|
+
{
|
|
22
|
+
id: 'claude-code-instructions',
|
|
23
|
+
tool: 'Claude Code Memory Instructions',
|
|
24
|
+
instructionPath: '.claude/CLAUDE.md',
|
|
25
|
+
detectPaths: ['.claude', '.claude.json'],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'codex-instructions',
|
|
29
|
+
tool: 'Codex Memory Instructions',
|
|
30
|
+
instructionPath: '.codex/AGENTS.md',
|
|
31
|
+
detectPaths: ['.codex'],
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
function wallEMcpUrl(wallePort) {
|
|
36
|
+
return `http://localhost:${Number(wallePort) || 3457}/mcp`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function wallEMcpConfig(wallePort) {
|
|
40
|
+
return { type: 'http', url: wallEMcpUrl(wallePort) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function _home(homeDir) {
|
|
44
|
+
return homeDir || process.env.HOME || process.env.USERPROFILE || '';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _targetPaths(target, homeDir) {
|
|
48
|
+
const home = _home(homeDir);
|
|
49
|
+
return {
|
|
50
|
+
detectPath: path.join(home, target.detectPath),
|
|
51
|
+
configPath: path.join(home, target.configPath),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function _isDetected(target, homeDir) {
|
|
56
|
+
const paths = _targetPaths(target, homeDir);
|
|
57
|
+
return fs.existsSync(paths.detectPath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function _isInstructionTargetDetected(target, homeDir) {
|
|
61
|
+
const home = _home(homeDir);
|
|
62
|
+
return target.detectPaths.some(detectPath => fs.existsSync(path.join(home, detectPath)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function _readJsonConfig(configPath) {
|
|
66
|
+
if (!fs.existsSync(configPath)) return { config: {}, exists: false };
|
|
67
|
+
try {
|
|
68
|
+
return { config: JSON.parse(fs.readFileSync(configPath, 'utf8')), exists: true };
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return { config: null, exists: true, error: err };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _writeJsonConfig(configPath, config) {
|
|
75
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
76
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _jsonStatus(target, configPath, wallePort) {
|
|
80
|
+
const loaded = _readJsonConfig(configPath);
|
|
81
|
+
if (loaded.error) {
|
|
82
|
+
return { kind: 'mcp_config', tool: target.tool, status: 'invalid_config', configPath, error: loaded.error.message };
|
|
83
|
+
}
|
|
84
|
+
const entry = loaded.config?.mcpServers?.[WALLE_SERVER_NAME];
|
|
85
|
+
if (entry && entry.url === wallEMcpUrl(wallePort)) {
|
|
86
|
+
return { kind: 'mcp_config', tool: target.tool, status: 'configured', configPath };
|
|
87
|
+
}
|
|
88
|
+
if (entry) return { kind: 'mcp_config', tool: target.tool, status: 'wrong_port', configPath, url: entry.url || '' };
|
|
89
|
+
return { kind: 'mcp_config', tool: target.tool, status: 'not_configured', configPath };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _ensureJsonTarget(target, configPath, wallePort) {
|
|
93
|
+
const loaded = _readJsonConfig(configPath);
|
|
94
|
+
if (loaded.error) {
|
|
95
|
+
return { kind: 'mcp_config', tool: target.tool, action: 'error', configPath, error: loaded.error.message };
|
|
96
|
+
}
|
|
97
|
+
const config = loaded.config || {};
|
|
98
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
|
|
99
|
+
config.mcpServers = {};
|
|
100
|
+
}
|
|
101
|
+
const existing = config.mcpServers[WALLE_SERVER_NAME];
|
|
102
|
+
if (existing && existing.url === wallEMcpUrl(wallePort)) {
|
|
103
|
+
return { kind: 'mcp_config', tool: target.tool, action: 'already_configured', configPath };
|
|
104
|
+
}
|
|
105
|
+
const action = existing ? 'updated' : 'added';
|
|
106
|
+
config.mcpServers[WALLE_SERVER_NAME] = wallEMcpConfig(wallePort);
|
|
107
|
+
_writeJsonConfig(configPath, config);
|
|
108
|
+
return { kind: 'mcp_config', tool: target.tool, action, configPath };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function _codexBlockRegex() {
|
|
112
|
+
return /(^|\n)\[mcp_servers\.(?:"wall-e"|wall-e)\]\n[\s\S]*?(?=\n\[[^\n]+\]|\s*$)/m;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function _extractCodexWallEBlock(text) {
|
|
116
|
+
const match = String(text || '').match(_codexBlockRegex());
|
|
117
|
+
return match ? match[0].replace(/^\n/, '') : '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function _extractTomlStringValue(block, key) {
|
|
121
|
+
const re = new RegExp(`^\\s*${key}\\s*=\\s*"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"\\s*$`, 'm');
|
|
122
|
+
const match = String(block || '').match(re);
|
|
123
|
+
if (!match) return '';
|
|
124
|
+
return match[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function _codexStatus(target, configPath, wallePort) {
|
|
128
|
+
const text = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
|
129
|
+
const block = _extractCodexWallEBlock(text);
|
|
130
|
+
if (!block) return { kind: 'mcp_config', tool: target.tool, status: 'not_configured', configPath };
|
|
131
|
+
const url = _extractTomlStringValue(block, 'url');
|
|
132
|
+
if (url === wallEMcpUrl(wallePort)) return { kind: 'mcp_config', tool: target.tool, status: 'configured', configPath };
|
|
133
|
+
return { kind: 'mcp_config', tool: target.tool, status: 'wrong_port', configPath, url };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function _upsertCodexWallEBlock(text, wallePort) {
|
|
137
|
+
const body = String(text || '').replace(_codexBlockRegex(), '').replace(/\s+$/g, '');
|
|
138
|
+
const block = `[mcp_servers."wall-e"]\nurl = "${wallEMcpUrl(wallePort)}"\n`;
|
|
139
|
+
return (body ? `${body}\n\n` : '') + block;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function _ensureCodexTarget(target, configPath, wallePort) {
|
|
143
|
+
const existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
|
144
|
+
const status = _codexStatus(target, configPath, wallePort);
|
|
145
|
+
if (status.status === 'configured') return { kind: 'mcp_config', tool: target.tool, action: 'already_configured', configPath };
|
|
146
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
147
|
+
fs.writeFileSync(configPath, _upsertCodexWallEBlock(existing, wallePort));
|
|
148
|
+
return { kind: 'mcp_config', tool: target.tool, action: status.status === 'wrong_port' ? 'updated' : 'added', configPath };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function wallEAgentMemoryInstructions(wallePort) {
|
|
152
|
+
return `${AGENT_INSTRUCTIONS_BEGIN}
|
|
153
|
+
## Wall-E MCP Memory Routing
|
|
154
|
+
|
|
155
|
+
Wall-E MCP is the user's private memory, knowledge graph, and CTM coding-session context. It is configured at ${wallEMcpUrl(wallePort)}.
|
|
156
|
+
|
|
157
|
+
Use Wall-E memory before public web search or guesswork for any private, remembered, or work-context question, including:
|
|
158
|
+
- prior sessions, previous attempts, branch/task history, regressions, decisions, preferences, and "last time" / "remember" / "what did we discuss" prompts;
|
|
159
|
+
- people, teams, projects, tools, documents, Slack/email/calendar/work context, and "do you know" / "who is" / "what is" prompts;
|
|
160
|
+
- requests that explicitly say to use the user's context, Wall-E memory, CTM sessions, or past agent work.
|
|
161
|
+
|
|
162
|
+
Recommended tools:
|
|
163
|
+
- \`walle_lookup_context\` for one-shot route + private memory/entity/CTM session context lookup.
|
|
164
|
+
- \`walle_route_query\` when you are unsure whether the request should use Wall-E, live source tools, or public web.
|
|
165
|
+
- \`walle_memory_status\` when connection health is uncertain.
|
|
166
|
+
- \`walle_context_pack\`, \`walle_search_sessions\`, and \`walle_get_session\` for prior coding-agent context.
|
|
167
|
+
- \`search_memories\`, \`ask_walle\`, \`entity_search\`, and \`knowledge_query\` for user/work memory and knowledge graph lookup.
|
|
168
|
+
|
|
169
|
+
Do not use Wall-E as a reflex for every request. Use live host tools for explicit current inbox/calendar/Slack reads or side-effecting actions such as send, post, create, schedule, update, or delete. Use public web search for public/current facts, or after Wall-E returns no useful memory. If Wall-E memory is unavailable, say that explicitly. Keep private details minimal; do not expose private links, emails, or full transcripts unless the user needs them.
|
|
170
|
+
${AGENT_INSTRUCTIONS_END}
|
|
171
|
+
`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function _upsertMarkedBlock(text, block) {
|
|
175
|
+
const existing = String(text || '').replace(/\r\n/g, '\n');
|
|
176
|
+
const nextBlock = String(block || '').trimEnd() + '\n';
|
|
177
|
+
const start = existing.indexOf(AGENT_INSTRUCTIONS_BEGIN);
|
|
178
|
+
const end = existing.indexOf(AGENT_INSTRUCTIONS_END);
|
|
179
|
+
if (start >= 0 && end > start) {
|
|
180
|
+
const afterEnd = end + AGENT_INSTRUCTIONS_END.length;
|
|
181
|
+
return `${existing.slice(0, start)}${nextBlock}${existing.slice(afterEnd).replace(/^\n+/, '\n')}`;
|
|
182
|
+
}
|
|
183
|
+
const prefix = existing.trimEnd();
|
|
184
|
+
return prefix ? `${prefix}\n\n${nextBlock}` : nextBlock;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function _instructionStatus(target, instructionPath, wallePort) {
|
|
188
|
+
if (!fs.existsSync(instructionPath)) {
|
|
189
|
+
return { kind: 'agent_instructions', tool: target.tool, status: 'not_configured', configPath: instructionPath };
|
|
190
|
+
}
|
|
191
|
+
const existing = fs.readFileSync(instructionPath, 'utf8');
|
|
192
|
+
const expected = wallEAgentMemoryInstructions(wallePort).trimEnd();
|
|
193
|
+
if (existing.includes(expected)) {
|
|
194
|
+
return { kind: 'agent_instructions', tool: target.tool, status: 'configured', configPath: instructionPath };
|
|
195
|
+
}
|
|
196
|
+
if (existing.includes(AGENT_INSTRUCTIONS_BEGIN) && existing.includes(AGENT_INSTRUCTIONS_END)) {
|
|
197
|
+
return { kind: 'agent_instructions', tool: target.tool, status: 'stale', configPath: instructionPath };
|
|
198
|
+
}
|
|
199
|
+
return { kind: 'agent_instructions', tool: target.tool, status: 'not_configured', configPath: instructionPath };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function _ensureInstructionTarget(target, homeDir, wallePort) {
|
|
203
|
+
if (!_isInstructionTargetDetected(target, homeDir)) {
|
|
204
|
+
return { kind: 'agent_instructions', tool: target.tool, action: 'not_installed' };
|
|
205
|
+
}
|
|
206
|
+
const instructionPath = path.join(_home(homeDir), target.instructionPath);
|
|
207
|
+
const status = _instructionStatus(target, instructionPath, wallePort);
|
|
208
|
+
if (status.status === 'configured') {
|
|
209
|
+
return { kind: 'agent_instructions', tool: target.tool, action: 'already_configured', configPath: instructionPath };
|
|
210
|
+
}
|
|
211
|
+
const existing = fs.existsSync(instructionPath) ? fs.readFileSync(instructionPath, 'utf8') : '';
|
|
212
|
+
fs.mkdirSync(path.dirname(instructionPath), { recursive: true });
|
|
213
|
+
fs.writeFileSync(instructionPath, _upsertMarkedBlock(existing, wallEAgentMemoryInstructions(wallePort)));
|
|
214
|
+
return {
|
|
215
|
+
kind: 'agent_instructions',
|
|
216
|
+
tool: target.tool,
|
|
217
|
+
action: status.status === 'stale' ? 'updated' : 'added',
|
|
218
|
+
configPath: instructionPath,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function detectAgentMemoryInstructions(wallePort, opts = {}) {
|
|
223
|
+
const homeDir = opts.homeDir;
|
|
224
|
+
return AGENT_INSTRUCTION_TARGETS.map((target) => {
|
|
225
|
+
if (!_isInstructionTargetDetected(target, homeDir)) {
|
|
226
|
+
return { kind: 'agent_instructions', tool: target.tool, status: 'not_installed' };
|
|
227
|
+
}
|
|
228
|
+
return _instructionStatus(target, path.join(_home(homeDir), target.instructionPath), wallePort);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function ensureAgentMemoryInstructions(wallePort, opts = {}) {
|
|
233
|
+
const homeDir = opts.homeDir;
|
|
234
|
+
return AGENT_INSTRUCTION_TARGETS.map(target => _ensureInstructionTarget(target, homeDir, wallePort));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function detectMcpIntegrations(wallePort, opts = {}) {
|
|
238
|
+
const homeDir = opts.homeDir;
|
|
239
|
+
return MCP_TARGETS.map((target) => {
|
|
240
|
+
const paths = _targetPaths(target, homeDir);
|
|
241
|
+
if (!_isDetected(target, homeDir)) return { kind: 'mcp_config', tool: target.tool, status: 'not_installed' };
|
|
242
|
+
if (target.kind === 'codex-toml') return _codexStatus(target, paths.configPath, wallePort);
|
|
243
|
+
return _jsonStatus(target, paths.configPath, wallePort);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function ensureMcpIntegrations(wallePort, opts = {}) {
|
|
248
|
+
const homeDir = opts.homeDir;
|
|
249
|
+
const mcpResults = MCP_TARGETS.map((target) => {
|
|
250
|
+
const paths = _targetPaths(target, homeDir);
|
|
251
|
+
if (!_isDetected(target, homeDir)) return { kind: 'mcp_config', tool: target.tool, action: 'not_installed' };
|
|
252
|
+
if (target.kind === 'codex-toml') return _ensureCodexTarget(target, paths.configPath, wallePort);
|
|
253
|
+
return _ensureJsonTarget(target, paths.configPath, wallePort);
|
|
254
|
+
});
|
|
255
|
+
if (opts.includeAgentInstructions === false) return mcpResults;
|
|
256
|
+
return mcpResults.concat(ensureAgentMemoryInstructions(wallePort, { homeDir }));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function injectMcpConfigs(wallePort, homeDir) {
|
|
260
|
+
return ensureMcpIntegrations(wallePort, { homeDir });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function _postMcp(port, payload, timeoutMs) {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
const body = JSON.stringify(payload);
|
|
266
|
+
const req = http.request({
|
|
267
|
+
host: '127.0.0.1',
|
|
268
|
+
port: Number(port) || 3457,
|
|
269
|
+
path: '/mcp',
|
|
270
|
+
method: 'POST',
|
|
271
|
+
timeout: timeoutMs,
|
|
272
|
+
headers: {
|
|
273
|
+
'Content-Type': 'application/json',
|
|
274
|
+
'Content-Length': Buffer.byteLength(body),
|
|
275
|
+
},
|
|
276
|
+
}, (res) => {
|
|
277
|
+
const chunks = [];
|
|
278
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
279
|
+
res.on('end', () => {
|
|
280
|
+
const raw = Buffer.concat(chunks).toString();
|
|
281
|
+
let json = null;
|
|
282
|
+
try { json = raw ? JSON.parse(raw) : null; } catch {}
|
|
283
|
+
resolve({ status: res.statusCode || 0, json, body: raw });
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
req.on('timeout', () => req.destroy(new Error('Timed out testing Wall-E MCP')));
|
|
287
|
+
req.on('error', reject);
|
|
288
|
+
req.write(body);
|
|
289
|
+
req.end();
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function testWallEMcpEndpoint(wallePort, opts = {}) {
|
|
294
|
+
const timeoutMs = opts.timeoutMs || 2000;
|
|
295
|
+
try {
|
|
296
|
+
const init = await _postMcp(wallePort, {
|
|
297
|
+
jsonrpc: '2.0',
|
|
298
|
+
id: 1,
|
|
299
|
+
method: 'initialize',
|
|
300
|
+
params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'wall-e-self-test', version: '1.0.0' } },
|
|
301
|
+
}, timeoutMs);
|
|
302
|
+
if (init.status !== 200 || !init.json?.result?.serverInfo) {
|
|
303
|
+
return { ok: false, status: init.status, error: 'initialize failed' };
|
|
304
|
+
}
|
|
305
|
+
const tools = await _postMcp(wallePort, {
|
|
306
|
+
jsonrpc: '2.0',
|
|
307
|
+
id: 2,
|
|
308
|
+
method: 'tools/list',
|
|
309
|
+
params: {},
|
|
310
|
+
}, timeoutMs);
|
|
311
|
+
const list = tools.json?.result?.tools;
|
|
312
|
+
if (tools.status !== 200 || !Array.isArray(list)) {
|
|
313
|
+
return { ok: false, status: tools.status, error: 'tools/list failed' };
|
|
314
|
+
}
|
|
315
|
+
return { ok: true, server: init.json.result.serverInfo, toolCount: list.length };
|
|
316
|
+
} catch (err) {
|
|
317
|
+
return { ok: false, error: err.message };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
module.exports = {
|
|
322
|
+
AGENT_INSTRUCTION_TARGETS,
|
|
323
|
+
AGENT_INSTRUCTIONS_BEGIN,
|
|
324
|
+
AGENT_INSTRUCTIONS_END,
|
|
325
|
+
MCP_TARGETS,
|
|
326
|
+
WALLE_SERVER_NAME,
|
|
327
|
+
detectAgentMemoryInstructions,
|
|
328
|
+
detectMcpIntegrations,
|
|
329
|
+
ensureAgentMemoryInstructions,
|
|
330
|
+
ensureMcpIntegrations,
|
|
331
|
+
injectMcpConfigs,
|
|
332
|
+
testWallEMcpEndpoint,
|
|
333
|
+
wallEAgentMemoryInstructions,
|
|
334
|
+
wallEMcpConfig,
|
|
335
|
+
wallEMcpUrl,
|
|
336
|
+
};
|