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.
Files changed (98) hide show
  1. package/README.md +8 -3
  2. package/bin/create-walle.js +232 -32
  3. package/bin/mcp-inject.js +18 -53
  4. package/package.json +3 -1
  5. package/template/claude-task-manager/api-prompts.js +11 -2
  6. package/template/claude-task-manager/approval-agent.js +7 -0
  7. package/template/claude-task-manager/db.js +94 -75
  8. package/template/claude-task-manager/docs/session-standup-command-center-design.md +242 -0
  9. package/template/claude-task-manager/docs/session-tooltip-freshness-design.md +224 -0
  10. package/template/claude-task-manager/docs/session-ux-issue-review-2026-05-01.md +369 -0
  11. package/template/claude-task-manager/fuzzy-utils.js +10 -2
  12. package/template/claude-task-manager/git-utils.js +140 -10
  13. package/template/claude-task-manager/lib/agent-capabilities.js +1 -1
  14. package/template/claude-task-manager/lib/agent-presets.js +38 -5
  15. package/template/claude-task-manager/lib/codex-terminal-final.js +53 -0
  16. package/template/claude-task-manager/lib/ctm-session-context-api.js +222 -0
  17. package/template/claude-task-manager/lib/session-diagnostics.js +56 -0
  18. package/template/claude-task-manager/lib/session-history.js +309 -16
  19. package/template/claude-task-manager/lib/session-standup.js +409 -0
  20. package/template/claude-task-manager/lib/session-stream.js +253 -20
  21. package/template/claude-task-manager/lib/standup-attention.js +200 -0
  22. package/template/claude-task-manager/lib/status-hooks.js +8 -2
  23. package/template/claude-task-manager/lib/update-telemetry.js +114 -0
  24. package/template/claude-task-manager/lib/walle-ctm-history.js +49 -6
  25. package/template/claude-task-manager/lib/walle-default-model.js +55 -0
  26. package/template/claude-task-manager/lib/walle-mcp-auto-config.js +66 -0
  27. package/template/claude-task-manager/lib/walle-supervisor.js +86 -19
  28. package/template/claude-task-manager/lib/walle-transcript.js +1 -3
  29. package/template/claude-task-manager/lib/worktree-cwd.js +82 -0
  30. package/template/claude-task-manager/package.json +1 -0
  31. package/template/claude-task-manager/providers/codex-mcp.js +104 -0
  32. package/template/claude-task-manager/providers/index.js +2 -0
  33. package/template/claude-task-manager/public/css/setup.css +2 -1
  34. package/template/claude-task-manager/public/css/walle.css +71 -0
  35. package/template/claude-task-manager/public/index.html +2388 -429
  36. package/template/claude-task-manager/public/js/message-renderer.js +314 -35
  37. package/template/claude-task-manager/public/js/session-search-utils.js +185 -3
  38. package/template/claude-task-manager/public/js/session-status-precedence.js +125 -0
  39. package/template/claude-task-manager/public/js/setup.js +62 -19
  40. package/template/claude-task-manager/public/js/stream-view.js +396 -55
  41. package/template/claude-task-manager/public/js/terminal-restore-state.js +57 -0
  42. package/template/claude-task-manager/public/js/walle-session.js +234 -26
  43. package/template/claude-task-manager/public/js/walle.js +143 -2
  44. package/template/claude-task-manager/server.js +1402 -433
  45. package/template/claude-task-manager/session-integrity.js +77 -28
  46. package/template/claude-task-manager/workers/approval-widget-validator.js +15 -5
  47. package/template/claude-task-manager/workers/scrollback-worker.js +5 -6
  48. package/template/claude-task-manager/workers/state-detectors/codex.js +6 -0
  49. package/template/package.json +1 -1
  50. package/template/wall-e/agent-runners/claude-code.js +2 -0
  51. package/template/wall-e/agent.js +63 -8
  52. package/template/wall-e/api-walle.js +330 -52
  53. package/template/wall-e/brain.js +291 -42
  54. package/template/wall-e/chat.js +172 -15
  55. package/template/wall-e/coding/compaction-service.js +19 -5
  56. package/template/wall-e/coding/stream-processor.js +22 -2
  57. package/template/wall-e/coding/workspace-replay.js +1 -4
  58. package/template/wall-e/coding-orchestrator.js +250 -80
  59. package/template/wall-e/compat.js +0 -28
  60. package/template/wall-e/context/context-builder.js +3 -1
  61. package/template/wall-e/embeddings.js +2 -7
  62. package/template/wall-e/eval/agent-runner.js +30 -9
  63. package/template/wall-e/eval/benchmark-generator.js +21 -1
  64. package/template/wall-e/eval/benchmarks/chat-eval.json +66 -6
  65. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -596
  66. package/template/wall-e/eval/cc-replay.js +1 -0
  67. package/template/wall-e/eval/codex-cli-baseline.js +633 -0
  68. package/template/wall-e/eval/debug-agent003.js +1 -0
  69. package/template/wall-e/eval/eval-orchestrator.js +3 -3
  70. package/template/wall-e/eval/run-agent-benchmarks.js +11 -3
  71. package/template/wall-e/eval/run-codex-cli-baseline.js +177 -0
  72. package/template/wall-e/eval/run-model-comparison.js +1 -0
  73. package/template/wall-e/eval/swebench-adapter.js +1 -0
  74. package/template/wall-e/evaluation/quorum-evaluator.js +0 -1
  75. package/template/wall-e/extraction/knowledge-extractor.js +1 -2
  76. package/template/wall-e/lib/mcp-integration.js +336 -0
  77. package/template/wall-e/llm/ollama.js +47 -8
  78. package/template/wall-e/llm/ollama.plugin.json +1 -1
  79. package/template/wall-e/llm/tool-adapter.js +1 -0
  80. package/template/wall-e/loops/ingest.js +42 -8
  81. package/template/wall-e/loops/initiative.js +87 -2
  82. package/template/wall-e/mcp-server.js +872 -19
  83. package/template/wall-e/memory/ctm-context-client.js +230 -0
  84. package/template/wall-e/memory/ctm-session-context.js +1376 -0
  85. package/template/wall-e/prompts/coding/memory-protocol.md +6 -0
  86. package/template/wall-e/server.js +30 -1
  87. package/template/wall-e/skills/_bundled/memory-search/SKILL.md +8 -0
  88. package/template/wall-e/skills/_bundled/scan-ctm-sessions/SKILL.md +20 -0
  89. package/template/wall-e/skills/_bundled/scan-ctm-sessions/run.js +43 -0
  90. package/template/wall-e/skills/_bundled/slack-mentions/run.js +471 -188
  91. package/template/wall-e/skills/skill-planner.js +86 -4
  92. package/template/wall-e/slack/socket-mode-listener.js +276 -0
  93. package/template/wall-e/telemetry.js +70 -2
  94. package/template/wall-e/tools/builtin-middleware.js +55 -2
  95. package/template/wall-e/tools/shell-policy.js +1 -1
  96. package/template/wall-e/tools/slack-owner.js +104 -0
  97. package/template/website/index.html +4 -4
  98. 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: !result.error && result.testsPassed === true,
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: !result.error && result.testsPassed === true,
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
+ });
@@ -29,6 +29,7 @@ async function runWithModel(bench, modelName) {
29
29
  timeoutMs: 180000,
30
30
  model: modelName,
31
31
  mode: 'build',
32
+ persistTranscript: false,
32
33
  });
33
34
 
34
35
  const latencyMs = Date.now() - start;
@@ -158,6 +158,7 @@ async function runSWEBenchTask(task, options = {}) {
158
158
  provider,
159
159
  model,
160
160
  maxTurns: 30,
161
+ persistTranscript: false,
161
162
  }),
162
163
  new Promise((_, reject) => {
163
164
  timeoutHandle = setTimeout(() => reject(new Error('Agent timeout')), timeoutMs);
@@ -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
- // Legacy format: plain array of knowledge entries
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
+ };