create-walle 0.9.13 → 0.9.14

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 (58) hide show
  1. package/README.md +6 -1
  2. package/bin/create-walle.js +195 -30
  3. package/bin/mcp-inject.js +18 -53
  4. package/package.json +3 -1
  5. package/template/claude-task-manager/approval-agent.js +7 -0
  6. package/template/claude-task-manager/docs/session-standup-command-center-design.md +242 -0
  7. package/template/claude-task-manager/git-utils.js +111 -3
  8. package/template/claude-task-manager/lib/session-history.js +144 -16
  9. package/template/claude-task-manager/lib/session-standup.js +409 -0
  10. package/template/claude-task-manager/lib/standup-attention.js +200 -0
  11. package/template/claude-task-manager/lib/status-hooks.js +8 -2
  12. package/template/claude-task-manager/lib/update-telemetry.js +114 -0
  13. package/template/claude-task-manager/lib/walle-default-model.js +55 -0
  14. package/template/claude-task-manager/lib/walle-mcp-auto-config.js +62 -0
  15. package/template/claude-task-manager/lib/walle-supervisor.js +83 -19
  16. package/template/claude-task-manager/lib/worktree-cwd.js +82 -0
  17. package/template/claude-task-manager/providers/codex-mcp.js +104 -0
  18. package/template/claude-task-manager/providers/index.js +2 -0
  19. package/template/claude-task-manager/public/css/setup.css +2 -1
  20. package/template/claude-task-manager/public/css/walle.css +5 -0
  21. package/template/claude-task-manager/public/index.html +1596 -283
  22. package/template/claude-task-manager/public/js/session-search-utils.js +171 -1
  23. package/template/claude-task-manager/public/js/setup.js +62 -19
  24. package/template/claude-task-manager/public/js/stream-view.js +55 -6
  25. package/template/claude-task-manager/public/js/walle-session.js +73 -16
  26. package/template/claude-task-manager/public/js/walle.js +34 -2
  27. package/template/claude-task-manager/server.js +780 -177
  28. package/template/claude-task-manager/session-integrity.js +58 -15
  29. package/template/claude-task-manager/workers/approval-widget-validator.js +15 -5
  30. package/template/claude-task-manager/workers/state-detectors/codex.js +6 -0
  31. package/template/package.json +1 -1
  32. package/template/wall-e/agent.js +36 -7
  33. package/template/wall-e/api-walle.js +72 -20
  34. package/template/wall-e/coding/stream-processor.js +22 -2
  35. package/template/wall-e/coding-orchestrator.js +26 -6
  36. package/template/wall-e/eval/agent-runner.js +16 -4
  37. package/template/wall-e/eval/benchmark-generator.js +21 -1
  38. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -596
  39. package/template/wall-e/eval/codex-cli-baseline.js +633 -0
  40. package/template/wall-e/eval/eval-orchestrator.js +3 -3
  41. package/template/wall-e/eval/run-agent-benchmarks.js +11 -3
  42. package/template/wall-e/eval/run-codex-cli-baseline.js +177 -0
  43. package/template/wall-e/lib/mcp-integration.js +220 -0
  44. package/template/wall-e/llm/ollama.js +47 -8
  45. package/template/wall-e/llm/ollama.plugin.json +1 -1
  46. package/template/wall-e/llm/tool-adapter.js +1 -0
  47. package/template/wall-e/loops/ingest.js +42 -8
  48. package/template/wall-e/mcp-server.js +272 -10
  49. package/template/wall-e/memory/ctm-session-context.js +910 -0
  50. package/template/wall-e/server.js +26 -1
  51. package/template/wall-e/skills/_bundled/scan-ctm-sessions/SKILL.md +20 -0
  52. package/template/wall-e/skills/_bundled/scan-ctm-sessions/run.js +43 -0
  53. package/template/wall-e/skills/skill-planner.js +52 -3
  54. package/template/wall-e/tools/builtin-middleware.js +55 -2
  55. package/template/wall-e/tools/shell-policy.js +1 -1
  56. package/template/wall-e/tools/slack-owner.js +104 -0
  57. package/template/website/index.html +2 -2
  58. package/template/builder-journal.md +0 -17
@@ -0,0 +1,114 @@
1
+ 'use strict';
2
+
3
+ const SERVER_EVENTS = new Set([
4
+ 'check_started',
5
+ 'check_completed',
6
+ 'check_failed',
7
+ 'update_available',
8
+ 'api_check',
9
+ 'apply_requested',
10
+ 'apply_ignored',
11
+ 'apply_started',
12
+ 'apply_spawn_error',
13
+ ]);
14
+
15
+ const CLIENT_EVENTS = new Set([
16
+ 'ui_prompt_shown',
17
+ 'ui_action',
18
+ ]);
19
+
20
+ const SURFACES = new Set(['banner', 'wizard', 'api', 'startup', 'scheduled', 'manual', 'unknown']);
21
+ const ACTIONS = new Set(['later', 'skip', 'apply', 'apply_started', 'apply_failed', 'already_up_to_date']);
22
+ const RESULTS = new Set(['available', 'up_to_date', 'error', 'started', 'ignored']);
23
+
24
+ function cleanEnum(value, allowed, fallback = 'unknown') {
25
+ const normalized = String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]/g, '_');
26
+ return allowed.has(normalized) ? normalized : fallback;
27
+ }
28
+
29
+ function cleanVersion(value) {
30
+ const raw = String(value || '').trim().replace(/^v/i, '');
31
+ if (!raw || raw.length > 64) return '';
32
+ return /^[0-9A-Za-z.+_-]+$/.test(raw) ? raw : '';
33
+ }
34
+
35
+ function cleanBool(value) {
36
+ return value === true ? true : value === false ? false : undefined;
37
+ }
38
+
39
+ function errorKind(error) {
40
+ const message = String(error?.message || error || '').toLowerCase();
41
+ if (!message) return 'unknown';
42
+ if (/timeout|timed out|abort/.test(message)) return 'timeout';
43
+ if (/network|fetch|enotfound|eai_again|econn|socket/.test(message)) return 'network';
44
+ if (/npm registry returned 4\d\d/.test(message)) return 'registry_client_error';
45
+ if (/npm registry returned 5\d\d/.test(message)) return 'registry_server_error';
46
+ if (/spawn|enoent/.test(message)) return 'spawn_error';
47
+ return 'error';
48
+ }
49
+
50
+ function sanitizePayload(details = {}) {
51
+ const payload = {};
52
+ const source = cleanEnum(details.source, SURFACES, '');
53
+ const surface = cleanEnum(details.surface, SURFACES, '');
54
+ const action = cleanEnum(details.action, ACTIONS, '');
55
+ const result = cleanEnum(details.result, RESULTS, '');
56
+ const currentVersion = cleanVersion(details.currentVersion || details.current || details.current_version);
57
+ const latestVersion = cleanVersion(details.latestVersion || details.latest || details.latest_version);
58
+ const updateAvailable = cleanBool(details.updateAvailable);
59
+
60
+ if (source) payload.source = source;
61
+ if (surface) payload.surface = surface;
62
+ if (action) payload.action = action;
63
+ if (result) payload.result = result;
64
+ if (currentVersion) payload.current_version = currentVersion;
65
+ if (latestVersion) payload.latest_version = latestVersion;
66
+ if (updateAvailable !== undefined) payload.update_available = updateAvailable;
67
+ if (details.error || details.errorKind) payload.error_kind = details.errorKind || errorKind(details.error);
68
+
69
+ return payload;
70
+ }
71
+
72
+ function trackUpdateTelemetry(telemetry, event, details = {}) {
73
+ const normalized = cleanEnum(event, new Set([...SERVER_EVENTS, ...CLIENT_EVENTS]), '');
74
+ if (!normalized) return false;
75
+ try {
76
+ telemetry?.track?.(`ctm_update_${normalized}`, sanitizePayload(details));
77
+ return true;
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ function clientUpdateTelemetryEvent(body = {}) {
84
+ const rawEvent = String(body.event || '').trim().toLowerCase();
85
+ if (rawEvent === 'prompt_shown') {
86
+ return {
87
+ event: 'ui_prompt_shown',
88
+ details: sanitizePayload({
89
+ surface: body.surface,
90
+ currentVersion: body.currentVersion,
91
+ latestVersion: body.latestVersion,
92
+ }),
93
+ };
94
+ }
95
+ if (rawEvent === 'action') {
96
+ return {
97
+ event: 'ui_action',
98
+ details: sanitizePayload({
99
+ surface: body.surface,
100
+ action: body.action,
101
+ currentVersion: body.currentVersion,
102
+ latestVersion: body.latestVersion,
103
+ }),
104
+ };
105
+ }
106
+ return null;
107
+ }
108
+
109
+ module.exports = {
110
+ clientUpdateTelemetryEvent,
111
+ errorKind,
112
+ sanitizePayload,
113
+ trackUpdateTelemetry,
114
+ };
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ const { sanitizeSetupProviderType } = require('./setup-provider-config');
4
+
5
+ function _kv(brain, key) {
6
+ try {
7
+ if (brain && typeof brain.getKv === 'function') return brain.getKv(key) || '';
8
+ if (brain && typeof brain.getDb === 'function') {
9
+ return brain.getDb().prepare('SELECT value FROM brain_metadata WHERE key = ?').get(key)?.value || '';
10
+ }
11
+ } catch (e) {
12
+ if (process.env.CTM_DEBUG_WALLE_DEFAULTS === '1') {
13
+ console.warn('[walle-default-model] metadata lookup failed:', e.message);
14
+ }
15
+ }
16
+ return '';
17
+ }
18
+
19
+ function _providerDefault(provider, providerRegistry) {
20
+ if (!provider) return '';
21
+ try {
22
+ const registry = providerRegistry || require('../../wall-e/llm/registry');
23
+ if (typeof registry.ensureBootstrapped === 'function') registry.ensureBootstrapped();
24
+ return registry.getDefaultModel(provider) || '';
25
+ } catch (e) {
26
+ if (process.env.CTM_DEBUG_WALLE_DEFAULTS === '1') {
27
+ console.warn('[walle-default-model] provider default lookup failed:', e.message);
28
+ }
29
+ return '';
30
+ }
31
+ }
32
+
33
+ function resolveWalleDefaultModelSelection(opts = {}) {
34
+ const env = opts.env || process.env;
35
+ const brain = opts.brain || null;
36
+ const provider = sanitizeSetupProviderType(
37
+ _kv(brain, 'walle_provider')
38
+ || env.WALLE_PROVIDER
39
+ || 'anthropic'
40
+ ) || 'anthropic';
41
+ const model = _kv(brain, 'walle_model_' + provider)
42
+ || _kv(brain, 'walle_model')
43
+ || env.WALLE_MODEL
44
+ || _providerDefault(provider, opts.providerRegistry)
45
+ || '';
46
+
47
+ return {
48
+ model_id: model,
49
+ model_provider: provider,
50
+ };
51
+ }
52
+
53
+ module.exports = {
54
+ resolveWalleDefaultModelSelection,
55
+ };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ let lastEnsureAt = 0;
6
+
7
+ function isMcpAutoConfigDisabled(env = process.env) {
8
+ const value = String(env.WALLE_MCP_AUTO_CONFIG || '').toLowerCase();
9
+ return value === '0' || value === 'false' || value === 'off';
10
+ }
11
+
12
+ function isMcpCapableAgentCommand(cmd) {
13
+ const base = path.basename(String(cmd || '')).toLowerCase();
14
+ return base === 'claude' || base === 'codex';
15
+ }
16
+
17
+ function loadMcpIntegration() {
18
+ return require(path.resolve(__dirname, '..', '..', 'wall-e', 'lib', 'mcp-integration'));
19
+ }
20
+
21
+ function ensureWalleMcpForAgentSession({
22
+ cmd,
23
+ wallePort,
24
+ homeDir,
25
+ env = process.env,
26
+ logger = console,
27
+ now = Date.now(),
28
+ minIntervalMs = 30000,
29
+ } = {}) {
30
+ if (!isMcpCapableAgentCommand(cmd)) return { skipped: 'not_mcp_capable_agent' };
31
+ if (isMcpAutoConfigDisabled(env)) return { skipped: 'disabled' };
32
+ if (lastEnsureAt && now - lastEnsureAt < minIntervalMs) return { skipped: 'throttled' };
33
+ lastEnsureAt = now;
34
+
35
+ try {
36
+ const { ensureMcpIntegrations } = loadMcpIntegration();
37
+ const results = ensureMcpIntegrations(wallePort, { homeDir: homeDir || env.HOME || process.env.HOME });
38
+ const changed = results.filter(r => r.action === 'added' || r.action === 'updated');
39
+ const errors = results.filter(r => r.action === 'error');
40
+ if (changed.length) {
41
+ logger.log(`[ctm] Auto-configured Wall-E MCP for ${changed.length} AI tool(s) before agent launch`);
42
+ }
43
+ if (errors.length) {
44
+ logger.warn(`[ctm] Wall-E MCP auto-config skipped ${errors.length} invalid config file(s): ${errors.map(r => r.tool).join(', ')}`);
45
+ }
46
+ return { results, changed: changed.length, errors: errors.length };
47
+ } catch (err) {
48
+ logger.warn(`[ctm] Wall-E MCP auto-config failed: ${err.message}`);
49
+ return { error: err.message };
50
+ }
51
+ }
52
+
53
+ function _resetWalleMcpAutoConfigState() {
54
+ lastEnsureAt = 0;
55
+ }
56
+
57
+ module.exports = {
58
+ ensureWalleMcpForAgentSession,
59
+ isMcpAutoConfigDisabled,
60
+ isMcpCapableAgentCommand,
61
+ _resetWalleMcpAutoConfigState,
62
+ };
@@ -124,6 +124,44 @@ function createWalleSupervisor({
124
124
  return pidSet;
125
125
  }
126
126
 
127
+ function readPidFile() {
128
+ try {
129
+ const pid = parseInt(fs.readFileSync(pidFile(), 'utf8').trim(), 10);
130
+ return pid || null;
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ function isAlive(pid) {
137
+ if (!pid || pid === (processOps.pid || process.pid)) return false;
138
+ try {
139
+ processOps.kill(pid, 0);
140
+ return true;
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+
146
+ function writePidFile(pid) {
147
+ try {
148
+ fs.mkdirSync(configDir, { recursive: true });
149
+ fs.writeFileSync(pidFile(), String(pid));
150
+ return true;
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ function removePidFile() {
157
+ try {
158
+ fs.unlinkSync(pidFile());
159
+ return true;
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+
127
165
  async function killAll() {
128
166
  const pidSet = await collectManagedPids();
129
167
  if (pidSet.size === 0) return waitForPortFree();
@@ -225,27 +263,53 @@ function createWalleSupervisor({
225
263
  return nextChild.pid;
226
264
  }
227
265
 
228
- function getStatus() {
229
- let running = false;
230
- let pid = null;
231
- if (childPid) {
232
- try {
233
- process.kill(childPid, 0);
234
- running = true;
235
- pid = childPid;
236
- } catch {}
266
+ async function getStatus() {
267
+ const savedPid = readPidFile();
268
+ let stalePid = null;
269
+
270
+ if (childPid && isAlive(childPid)) {
271
+ return {
272
+ running: true,
273
+ pid: childPid,
274
+ pidSource: 'child',
275
+ stalePid: savedPid && savedPid !== childPid ? savedPid : null,
276
+ repairedPidFile: savedPid !== childPid ? writePidFile(childPid) : false,
277
+ };
237
278
  }
238
- if (!running) {
239
- try {
240
- const orphanPid = parseInt(fs.readFileSync(pidFile(), 'utf8').trim(), 10);
241
- if (orphanPid && orphanPid !== process.pid) {
242
- process.kill(orphanPid, 0);
243
- running = true;
244
- pid = orphanPid;
245
- }
246
- } catch {}
279
+
280
+ if (savedPid && isAlive(savedPid)) {
281
+ return { running: true, pid: savedPid, pidSource: 'pid_file' };
247
282
  }
248
- return { running, pid };
283
+ if (savedPid) stalePid = savedPid;
284
+
285
+ const title = processTitle();
286
+ let discovered = [];
287
+ try {
288
+ const results = await Promise.all([
289
+ processOps.findPidsByExactCommand ? processOps.findPidsByExactCommand(title) : [],
290
+ processOps.findListeningPidsByExactCommand ? processOps.findListeningPidsByExactCommand(wallePort, title) : [],
291
+ ]);
292
+ discovered = [...new Set(results.flat().filter(pid => pid && isAlive(pid)))];
293
+ } catch {}
294
+
295
+ if (discovered.length > 0) {
296
+ const pid = discovered[0];
297
+ return {
298
+ running: true,
299
+ pid,
300
+ pidSource: 'discovered',
301
+ stalePid,
302
+ repairedPidFile: writePidFile(pid),
303
+ };
304
+ }
305
+
306
+ return {
307
+ running: false,
308
+ pid: null,
309
+ pidSource: stalePid ? 'stale_pid_file' : 'none',
310
+ stalePid,
311
+ removedStalePidFile: stalePid ? removePidFile() : false,
312
+ };
249
313
  }
250
314
 
251
315
  function apiStop(req, res) {
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execFileSync } = require('child_process');
6
+
7
+ function fail(error, status) {
8
+ return { ok: false, status: status || 400, error };
9
+ }
10
+
11
+ function gitText(cwd, args) {
12
+ return execFileSync('git', args, {
13
+ cwd,
14
+ encoding: 'utf8',
15
+ stdio: ['ignore', 'pipe', 'pipe'],
16
+ timeout: 5000,
17
+ }).trim();
18
+ }
19
+
20
+ function firstWorktreePath(porcelain) {
21
+ for (const line of String(porcelain || '').split('\n')) {
22
+ if (line.startsWith('worktree ')) return line.slice('worktree '.length).trim();
23
+ }
24
+ return '';
25
+ }
26
+
27
+ function realpathOrResolve(p) {
28
+ try {
29
+ return fs.realpathSync(p);
30
+ } catch (_) {
31
+ return path.resolve(p);
32
+ }
33
+ }
34
+
35
+ function resolveWorktreeRepoRoot(cwd) {
36
+ if (typeof cwd !== 'string' || !cwd.trim()) return fail('cwd is required');
37
+ const raw = cwd.trim();
38
+ if (raw.includes('~')) {
39
+ return fail('cwd must be an absolute path with no `~` (the literal-tilde is the source of ghost worktrees).');
40
+ }
41
+ if (!path.isAbsolute(raw)) return fail('cwd must be an absolute path');
42
+
43
+ let realCwd;
44
+ try {
45
+ realCwd = fs.realpathSync(raw);
46
+ } catch (_) {
47
+ return fail('cwd does not exist');
48
+ }
49
+
50
+ let stat;
51
+ try {
52
+ stat = fs.statSync(realCwd);
53
+ } catch (_) {
54
+ return fail('cwd does not exist');
55
+ }
56
+ if (!stat.isDirectory()) return fail('cwd must be a directory');
57
+
58
+ let topLevel;
59
+ try {
60
+ topLevel = gitText(realCwd, ['rev-parse', '--show-toplevel']);
61
+ } catch (_) {
62
+ return fail('cwd must be inside a Git work tree');
63
+ }
64
+ if (!topLevel) return fail('cwd must be inside a Git work tree');
65
+
66
+ let primaryRoot = '';
67
+ try {
68
+ primaryRoot = firstWorktreePath(gitText(realCwd, ['worktree', 'list', '--porcelain']));
69
+ } catch (_) {
70
+ primaryRoot = '';
71
+ }
72
+
73
+ return {
74
+ ok: true,
75
+ cwd: realpathOrResolve(primaryRoot || topLevel),
76
+ requestedCwd: realCwd,
77
+ };
78
+ }
79
+
80
+ module.exports = {
81
+ resolveWorktreeRepoRoot,
82
+ };
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ // Codex MCP permission form:
4
+ //
5
+ // Calling wall-e.walle_memory_status({})
6
+ // Field 1/1 ...
7
+ // Allow the wall-e MCP server to run tool "walle_memory_status"?
8
+ // 1. Allow
9
+ // 2. Allow for this session
10
+ // 3. Always allow
11
+ // 4. Cancel
12
+ // enter to submit | esc to cancel
13
+
14
+ const CALLING_RE = /\bCalling\s+([A-Za-z0-9_.-]+)\.([A-Za-z0-9_.-]+)\s*\(/i;
15
+ const QUESTION_RE = /Allow the ([A-Za-z0-9_.-]+) MCP server to run tool ["']?([A-Za-z0-9_.-]+)["']?\?/i;
16
+ const ANCHOR_RE = /enter to submit\s*\|\s*esc to cancel/i;
17
+ const ALLOW_RE = /^\s*[❯›> ]?\s*1\.\s*Allow\b/i;
18
+ const OPTION_RE = /^\s*[❯›> ]?\s*(\d+)\.\s*(Allow for this session|Always allow|Allow|Cancel)\b/i;
19
+
20
+ function extractMcpTool(lines) {
21
+ for (let i = lines.length - 1; i >= 0; i--) {
22
+ const q = lines[i].match(QUESTION_RE);
23
+ if (q) return { server: q[1], tool: q[2], questionIdx: i };
24
+ }
25
+ for (let i = lines.length - 1; i >= 0; i--) {
26
+ const c = lines[i].match(CALLING_RE);
27
+ if (c) return { server: c[1], tool: c[2], questionIdx: -1 };
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function parseOptions(lines) {
33
+ const out = {
34
+ allowShortcut: '1',
35
+ sessionShortcut: null,
36
+ alwaysShortcut: null,
37
+ };
38
+
39
+ for (const line of lines) {
40
+ const match = line.match(OPTION_RE);
41
+ if (!match) continue;
42
+ const index = match[1];
43
+ const label = match[2].toLowerCase();
44
+ if (label === 'allow for this session') out.sessionShortcut = index;
45
+ else if (label === 'always allow') out.alwaysShortcut = index;
46
+ else if (label === 'allow') out.allowShortcut = index;
47
+ }
48
+ return out;
49
+ }
50
+
51
+ module.exports = {
52
+ id: 'codex-mcp',
53
+ name: 'Codex MCP Permission',
54
+
55
+ detect(text) {
56
+ const lines = String(text || '').split('\n').map(l => l.trim()).filter(Boolean);
57
+ if (lines.length < 5) return false;
58
+ if (!lines.slice(-5).some(l => ANCHOR_RE.test(l))) return false;
59
+ if (!lines.some(l => ALLOW_RE.test(l))) return false;
60
+ return lines.some(l => QUESTION_RE.test(l)) || lines.some(l => CALLING_RE.test(l));
61
+ },
62
+
63
+ parse(text) {
64
+ const lines = String(text || '').split('\n').map(l => l.trim()).filter(Boolean);
65
+ const mcp = extractMcpTool(lines);
66
+ if (!mcp) return null;
67
+
68
+ let allowIdx = -1;
69
+ for (let i = 0; i < lines.length; i++) {
70
+ if (ALLOW_RE.test(lines[i])) { allowIdx = i; break; }
71
+ }
72
+ if (allowIdx < 0) return null;
73
+
74
+ const options = parseOptions(lines.slice(allowIdx, allowIdx + 8));
75
+ const ctxAnchor = mcp.questionIdx >= 0 ? mcp.questionIdx : allowIdx;
76
+ const ctxStart = Math.max(0, ctxAnchor - 5);
77
+ const ctxEnd = Math.min(lines.length, allowIdx + 8);
78
+ const command = `${mcp.server}.${mcp.tool}({})`;
79
+
80
+ return {
81
+ toolName: 'MCP',
82
+ command: command.slice(0, 2000),
83
+ warning: '',
84
+ fullContext: lines.slice(ctxStart, ctxEnd).join('\n').slice(0, 2000),
85
+ hasAllowAll: !!options.sessionShortcut,
86
+ approveShortcut: options.allowShortcut,
87
+ approveAllShortcut: options.sessionShortcut,
88
+ alwaysAllowShortcut: options.alwaysShortcut,
89
+ mcpServer: mcp.server,
90
+ mcpTool: mcp.tool,
91
+ providerId: 'codex-mcp',
92
+ };
93
+ },
94
+
95
+ approveKeystroke(ctx) {
96
+ return ctx.approveAllShortcut || ctx.approveShortcut || '1';
97
+ },
98
+
99
+ approveAllKeystroke(ctx) {
100
+ return ctx.approveAllShortcut || null;
101
+ },
102
+
103
+ requiresEnter: true,
104
+ };
@@ -4,6 +4,7 @@
4
4
  // Detection order: most distinctive signals first to minimize false positives.
5
5
 
6
6
  const claudeCode = require('./claude-code');
7
+ const codexMcp = require('./codex-mcp');
7
8
  const codex = require('./codex');
8
9
  const gemini = require('./gemini');
9
10
  const amazonQ = require('./amazon-q');
@@ -19,6 +20,7 @@ const opencode = require('./opencode');
19
20
  // Numbered-widget providers (Claude Code, Codex, Cursor, OpenCode, Kimi)
20
21
  // run before inline [Y/n] providers (Gemini, Aider, Amazon Q, Cline, Copilot).
21
22
  const providers = [
23
+ codexMcp,
22
24
  claudeCode,
23
25
  codex,
24
26
  cursor,
@@ -161,6 +161,7 @@
161
161
  #setup-panel .integration-icon { font-size: 18px; width: 28px; text-align: center; }
162
162
  #setup-panel .integration-name { font-weight: 500; font-size: 13px; }
163
163
  #setup-panel .integration-desc { color: var(--fg-dim); font-size: 12px; }
164
+ #setup-panel .integration-action { color: var(--fg-muted, #8b949e); margin-top: 2px; }
164
165
 
165
166
  /* Badges */
166
167
  #setup-panel .badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 500; }
@@ -168,7 +169,7 @@
168
169
  #setup-panel .badge-ready { background: rgba(88,166,255,0.1); color: var(--fg-dim); }
169
170
  #setup-panel .badge-missing { background: rgba(210,153,34,0.15); color: var(--yellow, #e0af68); }
170
171
  #setup-panel .badge-warn { background: rgba(248,81,73,0.15); color: var(--red, #f7768e); }
171
- #setup-panel .badge-dim { background: rgba(139,148,158,0.1); color: #484f58; }
172
+ #setup-panel .badge-dim { background: rgba(139,148,158,0.12); color: #8b949e; }
172
173
  #setup-panel .badge-free { background: rgba(63,185,80,0.15); color: var(--green, #9ece6a); font-size: 10px; padding: 1px 6px; border-radius: 8px; font-weight: 600; letter-spacing: 0.03em; }
173
174
 
174
175
  /* Embedding rows */
@@ -345,9 +345,14 @@
345
345
  overflow-wrap: anywhere;
346
346
  }
347
347
  .we-service-alert-action {
348
+ border: 0;
349
+ background: transparent;
348
350
  color: #74c0fc;
349
351
  font-weight: 700;
350
352
  text-decoration: underline;
353
+ cursor: pointer;
354
+ padding: 0;
355
+ font: inherit;
351
356
  }
352
357
 
353
358
  /* Conversation turn group — user question + assistant response paired together */