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
|
@@ -27,8 +27,8 @@ function getTelemetryDb() {
|
|
|
27
27
|
const fs = require('fs');
|
|
28
28
|
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
29
29
|
telemetryDb = new Database(TELEMETRY_DB_PATH);
|
|
30
|
-
telemetryDb.pragma('journal_mode =
|
|
31
|
-
telemetryDb.pragma('busy_timeout =
|
|
30
|
+
telemetryDb.pragma('journal_mode = WAL');
|
|
31
|
+
telemetryDb.pragma('busy_timeout = 5000');
|
|
32
32
|
telemetryDb.exec(`
|
|
33
33
|
CREATE TABLE IF NOT EXISTS telemetry_events (
|
|
34
34
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -53,12 +53,14 @@ function getTelemetryDb() {
|
|
|
53
53
|
os TEXT,
|
|
54
54
|
node_version TEXT,
|
|
55
55
|
ip TEXT,
|
|
56
|
+
machine_bucket TEXT,
|
|
56
57
|
event_count INTEGER DEFAULT 0
|
|
57
58
|
);
|
|
58
59
|
`);
|
|
59
60
|
// Migrate: add ip column if missing
|
|
60
61
|
try { telemetryDb.prepare('ALTER TABLE telemetry_events ADD COLUMN ip TEXT').run(); } catch {}
|
|
61
62
|
try { telemetryDb.prepare('ALTER TABLE telemetry_installs ADD COLUMN ip TEXT').run(); } catch {}
|
|
63
|
+
try { telemetryDb.prepare('ALTER TABLE telemetry_installs ADD COLUMN machine_bucket TEXT').run(); } catch {}
|
|
62
64
|
return telemetryDb;
|
|
63
65
|
} catch (e) {
|
|
64
66
|
console.error('[wall-e] Failed to init telemetry DB:', e.message);
|
|
@@ -158,6 +160,53 @@ function ensureBrainInit() {
|
|
|
158
160
|
return true;
|
|
159
161
|
}
|
|
160
162
|
|
|
163
|
+
const SETUP_PROVIDER_TYPES = ['anthropic', 'openai', 'google', 'ollama', 'deepseek'];
|
|
164
|
+
const SETUP_PROVIDER_NAMES = {
|
|
165
|
+
anthropic: 'Anthropic',
|
|
166
|
+
openai: 'OpenAI',
|
|
167
|
+
google: 'Google Gemini',
|
|
168
|
+
ollama: 'Ollama (Local)',
|
|
169
|
+
deepseek: 'DeepSeek',
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
function sanitizeSetupProviderType(value) {
|
|
173
|
+
const type = String(value || '').toLowerCase().replace(/[^a-z0-9_-]/g, '').slice(0, 32);
|
|
174
|
+
return SETUP_PROVIDER_TYPES.includes(type) ? type : '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function sanitizeSetupModel(value) {
|
|
178
|
+
return typeof value === 'string' ? value.replace(/[\r\n\s]/g, '').slice(0, 100) : '';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function sanitizeTaskType(value) {
|
|
182
|
+
return typeof value === 'string' ? value.replace(/[^a-z0-9_-]/gi, '').slice(0, 64) : '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function sanitizeModelRegistryId(value) {
|
|
186
|
+
return value == null || value === ''
|
|
187
|
+
? null
|
|
188
|
+
: String(value).replace(/[^a-z0-9_:.@/\-]/gi, '').slice(0, 128);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function syncRuntimeDefaultsFromBrain() {
|
|
192
|
+
if (!brain) return;
|
|
193
|
+
try {
|
|
194
|
+
const provider = brain.getKv('walle_provider');
|
|
195
|
+
const model = brain.getKv('walle_model');
|
|
196
|
+
if (provider) process.env.WALLE_PROVIDER = provider;
|
|
197
|
+
if (model) process.env.WALLE_MODEL = model;
|
|
198
|
+
else delete process.env.WALLE_MODEL;
|
|
199
|
+
_runtimeDefaultsSynced = true;
|
|
200
|
+
} catch {}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function resetDefaultClientQuiet() {
|
|
204
|
+
try {
|
|
205
|
+
const { resetDefaultClient } = require('./llm/client');
|
|
206
|
+
resetDefaultClient();
|
|
207
|
+
} catch {}
|
|
208
|
+
}
|
|
209
|
+
|
|
161
210
|
// --- Individual route handlers (exported for testability) ---
|
|
162
211
|
|
|
163
212
|
function getStatus() {
|
|
@@ -305,12 +354,48 @@ function handleWalleApi(req, res, url) {
|
|
|
305
354
|
}
|
|
306
355
|
}
|
|
307
356
|
|
|
357
|
+
// POST /api/wall-e/update/prompt-event — anonymous upgrade prompt funnel
|
|
358
|
+
if (p === '/api/wall-e/update/prompt-event' && m === 'POST') {
|
|
359
|
+
return readBody(req).then(body => {
|
|
360
|
+
try {
|
|
361
|
+
const allowed = new Set(['shown', 'later', 'dismissed', 'copy_command']);
|
|
362
|
+
const action = allowed.has(body?.action) ? body.action : 'unknown';
|
|
363
|
+
const updateRaw = brain.getKv('update_available');
|
|
364
|
+
const update = updateRaw ? JSON.parse(updateRaw) : {};
|
|
365
|
+
const telemetry = require('./telemetry');
|
|
366
|
+
telemetry.track('upgrade_prompt', {
|
|
367
|
+
action,
|
|
368
|
+
current: update.current || 'unknown',
|
|
369
|
+
latest: update.latest || 'unknown',
|
|
370
|
+
source: 'walle_ui',
|
|
371
|
+
});
|
|
372
|
+
if (action === 'shown') telemetry.trackFunnelStep('upgrade_prompt_shown');
|
|
373
|
+
if (action === 'dismissed') telemetry.trackFunnelStep('upgrade_prompt_dismissed');
|
|
374
|
+
if (action === 'copy_command') telemetry.trackFunnelStep('upgrade_prompt_command_copied');
|
|
375
|
+
return jsonResponse(res, { ok: true }), true;
|
|
376
|
+
} catch (e) {
|
|
377
|
+
return jsonResponse(res, { error: e.message }, 500), true;
|
|
378
|
+
}
|
|
379
|
+
}).catch(e => jsonResponse(res, { error: e.message }, 500));
|
|
380
|
+
}
|
|
381
|
+
|
|
308
382
|
// DELETE /api/wall-e/alerts/:id — dismiss an alert
|
|
309
383
|
if (p.startsWith('/api/wall-e/alerts/') && m === 'DELETE') {
|
|
310
384
|
try {
|
|
311
385
|
const alertId = decodeURIComponent(p.split('/api/wall-e/alerts/')[1]);
|
|
312
386
|
if (alertId === 'version_update') {
|
|
313
|
-
|
|
387
|
+
try {
|
|
388
|
+
const telemetry = require('./telemetry');
|
|
389
|
+
const updateRaw = brain.getKv('update_available');
|
|
390
|
+
const update = updateRaw ? JSON.parse(updateRaw) : {};
|
|
391
|
+
telemetry.track('upgrade_prompt', {
|
|
392
|
+
action: 'dismissed',
|
|
393
|
+
current: update.current || 'unknown',
|
|
394
|
+
latest: update.latest || 'unknown',
|
|
395
|
+
source: 'alert_delete',
|
|
396
|
+
});
|
|
397
|
+
telemetry.trackFunnelStep('upgrade_prompt_dismissed');
|
|
398
|
+
} catch {}
|
|
314
399
|
brain.setKv('update_available', '');
|
|
315
400
|
} else {
|
|
316
401
|
const { dismissServiceAlert } = require('./skills/skill-planner');
|
|
@@ -362,14 +447,68 @@ function handleWalleApi(req, res, url) {
|
|
|
362
447
|
|
|
363
448
|
// GET /api/wall-e/slack/status — check Slack OAuth status
|
|
364
449
|
if (p === '/api/wall-e/slack/status' && m === 'GET') {
|
|
450
|
+
(async () => {
|
|
451
|
+
try {
|
|
452
|
+
const slackMcp = require('./tools/slack-mcp');
|
|
453
|
+
const { getSlackOwnerRepairState } = require('./tools/slack-owner');
|
|
454
|
+
const { clearResolvedSlackHealthAlerts } = require('./skills/skill-planner');
|
|
455
|
+
const { getSlackSocketModeStatus } = require('./slack/socket-mode-listener');
|
|
456
|
+
const authenticated = await slackMcp.isAuthenticated();
|
|
457
|
+
const token = slackMcp.loadToken();
|
|
458
|
+
const owner = getSlackOwnerRepairState();
|
|
459
|
+
let clearedAlerts = 0;
|
|
460
|
+
if (authenticated) {
|
|
461
|
+
try {
|
|
462
|
+
const cleared = clearResolvedSlackHealthAlerts({
|
|
463
|
+
authenticated: true,
|
|
464
|
+
ownerConfigured: owner.configured,
|
|
465
|
+
});
|
|
466
|
+
clearedAlerts = cleared.cleared || 0;
|
|
467
|
+
} catch {}
|
|
468
|
+
}
|
|
469
|
+
jsonResponse(res, {
|
|
470
|
+
data: {
|
|
471
|
+
authenticated,
|
|
472
|
+
team: token?.team_name,
|
|
473
|
+
user: token?.user_id,
|
|
474
|
+
obtained_at: token?.obtained_at,
|
|
475
|
+
owner_configured: owner.configured,
|
|
476
|
+
owner_can_repair: owner.canRepair,
|
|
477
|
+
socket_mode: getSlackSocketModeStatus(),
|
|
478
|
+
cleared_stale_alerts: clearedAlerts,
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
} catch (e) {
|
|
482
|
+
jsonResponse(res, { data: { authenticated: false, error: e.message } });
|
|
483
|
+
}
|
|
484
|
+
})();
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// POST /api/wall-e/slack/repair-owner — derive Slack owner id from OAuth token
|
|
489
|
+
if (p === '/api/wall-e/slack/repair-owner' && m === 'POST') {
|
|
365
490
|
try {
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
|
|
491
|
+
const { repairSlackOwnerIdentity } = require('./tools/slack-owner');
|
|
492
|
+
const { clearServiceAlerts } = require('./skills/skill-planner');
|
|
493
|
+
const result = repairSlackOwnerIdentity({ persist: true });
|
|
494
|
+
if (!result.ok) {
|
|
495
|
+
return jsonResponse(res, {
|
|
496
|
+
ok: false,
|
|
497
|
+
error: result.error || 'Could not repair Slack owner identity',
|
|
498
|
+
needsSlackAuth: !!result.needsSlackAuth,
|
|
499
|
+
}, result.needsSlackAuth ? 409 : 500), true;
|
|
500
|
+
}
|
|
501
|
+
clearServiceAlerts('slack');
|
|
502
|
+
return jsonResponse(res, {
|
|
503
|
+
ok: true,
|
|
504
|
+
user_id_configured: true,
|
|
505
|
+
source: result.source,
|
|
506
|
+
persisted: !!result.persisted,
|
|
507
|
+
already_configured: !!result.alreadyConfigured,
|
|
508
|
+
}), true;
|
|
369
509
|
} catch (e) {
|
|
370
|
-
jsonResponse(res, {
|
|
510
|
+
return jsonResponse(res, { ok: false, error: e.message }, 500), true;
|
|
371
511
|
}
|
|
372
|
-
return true;
|
|
373
512
|
}
|
|
374
513
|
|
|
375
514
|
// POST /api/wall-e/slack/auth — start OAuth flow (opens browser)
|
|
@@ -378,11 +517,27 @@ function handleWalleApi(req, res, url) {
|
|
|
378
517
|
const slackMcp = require('./tools/slack-mcp');
|
|
379
518
|
// If already authenticated, return immediately
|
|
380
519
|
if (slackMcp.isAuthenticatedSync()) {
|
|
520
|
+
try {
|
|
521
|
+
const { repairSlackOwnerIdentity } = require('./tools/slack-owner');
|
|
522
|
+
const { clearServiceAlerts } = require('./skills/skill-planner');
|
|
523
|
+
const repaired = repairSlackOwnerIdentity({ persist: true });
|
|
524
|
+
if (repaired.ok) clearServiceAlerts('slack');
|
|
525
|
+
} catch (repairErr) {
|
|
526
|
+
console.warn('[wall-e] Slack owner repair skipped:', repairErr.message);
|
|
527
|
+
}
|
|
381
528
|
jsonResponse(res, { ok: true, already: true });
|
|
382
529
|
return true;
|
|
383
530
|
}
|
|
384
531
|
// Start OAuth — opens browser, temp server on port 3118 handles callback
|
|
385
532
|
slackMcp.authenticate().then(() => {
|
|
533
|
+
try {
|
|
534
|
+
const { repairSlackOwnerIdentity } = require('./tools/slack-owner');
|
|
535
|
+
const { clearServiceAlerts } = require('./skills/skill-planner');
|
|
536
|
+
const repaired = repairSlackOwnerIdentity({ persist: true });
|
|
537
|
+
if (repaired.ok) clearServiceAlerts('slack');
|
|
538
|
+
} catch (repairErr) {
|
|
539
|
+
console.error('[wall-e] Slack owner repair failed:', repairErr.message);
|
|
540
|
+
}
|
|
386
541
|
console.log('[wall-e] Slack OAuth completed');
|
|
387
542
|
}).catch(err => {
|
|
388
543
|
console.error('[wall-e] Slack OAuth failed:', err.message);
|
|
@@ -714,24 +869,9 @@ function handleWalleApi(req, res, url) {
|
|
|
714
869
|
// GET /api/wall-e/mcp/integrations — check which AI tools have Wall-E MCP configured
|
|
715
870
|
if (p === '/api/wall-e/mcp/integrations' && m === 'GET') {
|
|
716
871
|
try {
|
|
717
|
-
const
|
|
718
|
-
const { MCP_TARGETS } = require('../create-walle/bin/mcp-inject');
|
|
872
|
+
const { detectMcpIntegrations } = require('./lib/mcp-integration');
|
|
719
873
|
const wallePort = parseInt(process.env.WALL_E_PORT) || 3457;
|
|
720
|
-
const
|
|
721
|
-
const results = MCP_TARGETS.map(target => {
|
|
722
|
-
const detectPath = path.join(home, target.detectDir);
|
|
723
|
-
const configPath = path.join(home, target.configPath);
|
|
724
|
-
if (!fs.existsSync(detectPath)) return { tool: target.tool, status: 'not_installed' };
|
|
725
|
-
try {
|
|
726
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
727
|
-
const entry = config?.mcpServers?.['wall-e'];
|
|
728
|
-
if (entry && entry.url === `http://localhost:${wallePort}/mcp`) return { tool: target.tool, status: 'configured', configPath };
|
|
729
|
-
if (entry) return { tool: target.tool, status: 'wrong_port', configPath };
|
|
730
|
-
return { tool: target.tool, status: 'not_configured', configPath };
|
|
731
|
-
} catch {
|
|
732
|
-
return { tool: target.tool, status: 'not_configured', configPath };
|
|
733
|
-
}
|
|
734
|
-
});
|
|
874
|
+
const results = detectMcpIntegrations(wallePort);
|
|
735
875
|
jsonResponse(res, { data: results, wallePort });
|
|
736
876
|
} catch (e) {
|
|
737
877
|
jsonResponse(res, { data: [], error: e.message });
|
|
@@ -742,11 +882,12 @@ function handleWalleApi(req, res, url) {
|
|
|
742
882
|
// POST /api/wall-e/mcp/inject — run MCP config injection for all detected AI tools
|
|
743
883
|
if (p === '/api/wall-e/mcp/inject' && m === 'POST') {
|
|
744
884
|
try {
|
|
745
|
-
const {
|
|
885
|
+
const { ensureMcpIntegrations } = require('./lib/mcp-integration');
|
|
746
886
|
const wallePort = parseInt(process.env.WALL_E_PORT) || 3457;
|
|
747
|
-
const results =
|
|
748
|
-
const added = results.filter(r => r.action === 'added' || r.action === 'updated').length;
|
|
749
|
-
|
|
887
|
+
const results = ensureMcpIntegrations(wallePort);
|
|
888
|
+
const added = results.filter(r => r.kind === 'mcp_config' && (r.action === 'added' || r.action === 'updated')).length;
|
|
889
|
+
const instructions = results.filter(r => r.kind === 'agent_instructions' && (r.action === 'added' || r.action === 'updated')).length;
|
|
890
|
+
try { require('./telemetry').track('mcp_inject', { added, instructions, total: results.length }); } catch {}
|
|
750
891
|
jsonResponse(res, { ok: true, results });
|
|
751
892
|
} catch (e) {
|
|
752
893
|
jsonResponse(res, { ok: false, error: e.message });
|
|
@@ -754,6 +895,20 @@ function handleWalleApi(req, res, url) {
|
|
|
754
895
|
return true;
|
|
755
896
|
}
|
|
756
897
|
|
|
898
|
+
// GET /api/wall-e/mcp/test - verify the live Wall-E MCP endpoint responds
|
|
899
|
+
if (p === '/api/wall-e/mcp/test' && m === 'GET') {
|
|
900
|
+
try {
|
|
901
|
+
const { testWallEMcpEndpoint } = require('./lib/mcp-integration');
|
|
902
|
+
const wallePort = parseInt(process.env.WALL_E_PORT) || 3457;
|
|
903
|
+
testWallEMcpEndpoint(wallePort, { timeoutMs: 1500 })
|
|
904
|
+
.then(result => jsonResponse(res, { data: result, wallePort }))
|
|
905
|
+
.catch(e => jsonResponse(res, { data: { ok: false, error: e.message }, wallePort }, 500));
|
|
906
|
+
} catch (e) {
|
|
907
|
+
jsonResponse(res, { data: { ok: false, error: e.message } }, 500);
|
|
908
|
+
}
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
|
|
757
912
|
// GET /api/wall-e/status
|
|
758
913
|
if (p === '/api/wall-e/status' && m === 'GET') {
|
|
759
914
|
const result = getStatus();
|
|
@@ -862,6 +1017,12 @@ function handleWalleApi(req, res, url) {
|
|
|
862
1017
|
serviceAlerts = getServiceAlerts({ includeVersionUpdate: true });
|
|
863
1018
|
} catch {}
|
|
864
1019
|
|
|
1020
|
+
let slackSocketMode = null;
|
|
1021
|
+
try {
|
|
1022
|
+
const { getSlackSocketModeStatus } = require('./slack/socket-mode-listener');
|
|
1023
|
+
slackSocketMode = getSlackSocketModeStatus();
|
|
1024
|
+
} catch {}
|
|
1025
|
+
|
|
865
1026
|
return jsonResponse(res, {
|
|
866
1027
|
data: {
|
|
867
1028
|
...result,
|
|
@@ -878,6 +1039,7 @@ function handleWalleApi(req, res, url) {
|
|
|
878
1039
|
walle_model: walleModel,
|
|
879
1040
|
has_api_key: hasApiKey,
|
|
880
1041
|
service_alerts: serviceAlerts,
|
|
1042
|
+
slack_socket_mode: slackSocketMode,
|
|
881
1043
|
}
|
|
882
1044
|
}), true;
|
|
883
1045
|
}
|
|
@@ -964,32 +1126,49 @@ function handleWalleApi(req, res, url) {
|
|
|
964
1126
|
const providerConfig = body.providerConfig || null;
|
|
965
1127
|
|
|
966
1128
|
if (provider) {
|
|
967
|
-
brain.
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1129
|
+
if (typeof brain.setDefaultProviderSelection === 'function') {
|
|
1130
|
+
brain.setDefaultProviderSelection({ type: provider, requestedModel: model, targetModel: model || brain.getKv('walle_model') || '' });
|
|
1131
|
+
} else {
|
|
1132
|
+
brain.setKv('walle_provider', provider);
|
|
1133
|
+
process.env.WALLE_PROVIDER = provider;
|
|
1134
|
+
if (model) {
|
|
1135
|
+
brain.setKv('walle_model', model);
|
|
1136
|
+
process.env.WALLE_MODEL = model;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
} else if (model) {
|
|
971
1140
|
brain.setKv('walle_model', model);
|
|
972
|
-
process.env.WALLE_MODEL = model;
|
|
973
1141
|
}
|
|
974
1142
|
if (ownerName) brain.setKv('walle_owner_name', ownerName);
|
|
975
1143
|
|
|
976
1144
|
if (provider && providerConfig) {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1145
|
+
if (typeof brain.saveSetupProvider === 'function') {
|
|
1146
|
+
brain.saveSetupProvider({
|
|
1147
|
+
id: providerConfig.id || `${provider}-default`,
|
|
1148
|
+
name: providerConfig.name || SETUP_PROVIDER_NAMES[provider] || provider,
|
|
1149
|
+
type: provider,
|
|
1150
|
+
baseUrl: providerConfig.baseUrl || null,
|
|
1151
|
+
apiKeyEncrypted: providerConfig.apiKeyEncrypted || null,
|
|
1152
|
+
customHeaders: providerConfig.customHeaders || null,
|
|
1153
|
+
enabled: true,
|
|
1154
|
+
model,
|
|
1155
|
+
setDefault: false,
|
|
1156
|
+
});
|
|
1157
|
+
} else {
|
|
1158
|
+
brain.upsertModelProvider({
|
|
1159
|
+
id: providerConfig.id || `${provider}-default`,
|
|
1160
|
+
name: providerConfig.name || SETUP_PROVIDER_NAMES[provider] || provider,
|
|
1161
|
+
type: provider,
|
|
1162
|
+
baseUrl: providerConfig.baseUrl || null,
|
|
1163
|
+
apiKeyEncrypted: providerConfig.apiKeyEncrypted || null,
|
|
1164
|
+
customHeaders: providerConfig.customHeaders || null,
|
|
1165
|
+
enabled: true,
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
987
1168
|
}
|
|
988
1169
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
resetDefaultClient();
|
|
992
|
-
} catch {}
|
|
1170
|
+
syncRuntimeDefaultsFromBrain();
|
|
1171
|
+
resetDefaultClientQuiet();
|
|
993
1172
|
|
|
994
1173
|
jsonResponse(res, { ok: true });
|
|
995
1174
|
} catch (e) {
|
|
@@ -999,6 +1178,88 @@ function handleWalleApi(req, res, url) {
|
|
|
999
1178
|
return true;
|
|
1000
1179
|
}
|
|
1001
1180
|
|
|
1181
|
+
if (p === '/api/wall-e/setup/provider' && m === 'POST') {
|
|
1182
|
+
readBody(req).then((body) => {
|
|
1183
|
+
try {
|
|
1184
|
+
if (!brain || !ensureBrainInit()) return jsonResponse(res, { ok: false, error: 'Brain module not available' }, 503);
|
|
1185
|
+
const type = sanitizeSetupProviderType(body.type);
|
|
1186
|
+
if (!type) return jsonResponse(res, { ok: false, error: 'Invalid provider type' }, 400);
|
|
1187
|
+
const model = sanitizeSetupModel(body.model);
|
|
1188
|
+
const result = brain.saveSetupProvider({
|
|
1189
|
+
id: body.id || `${type}-default`,
|
|
1190
|
+
name: body.name || SETUP_PROVIDER_NAMES[type] || type,
|
|
1191
|
+
type,
|
|
1192
|
+
baseUrl: body.base_url || body.baseUrl || null,
|
|
1193
|
+
apiKeyEncrypted: typeof body.api_key === 'string' ? body.api_key.replace(/[\r\n\s]/g, '').slice(0, 200) : (body.apiKeyEncrypted || null),
|
|
1194
|
+
customHeaders: body.custom_headers || body.customHeaders || null,
|
|
1195
|
+
enabled: body.enabled !== false,
|
|
1196
|
+
model,
|
|
1197
|
+
setDefault: Boolean(body.set_default),
|
|
1198
|
+
authMethod: body.auth_method || null,
|
|
1199
|
+
});
|
|
1200
|
+
syncRuntimeDefaultsFromBrain();
|
|
1201
|
+
resetDefaultClientQuiet();
|
|
1202
|
+
jsonResponse(res, { ok: true, authority: 'wall-e', ...result });
|
|
1203
|
+
} catch (e) {
|
|
1204
|
+
jsonResponse(res, { ok: false, error: e.message }, 400);
|
|
1205
|
+
}
|
|
1206
|
+
}).catch((e) => jsonResponse(res, { ok: false, error: e.message }, 400));
|
|
1207
|
+
return true;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
if (p === '/api/wall-e/setup/default-provider' && m === 'POST') {
|
|
1211
|
+
readBody(req).then((body) => {
|
|
1212
|
+
try {
|
|
1213
|
+
if (!brain || !ensureBrainInit()) return jsonResponse(res, { ok: false, error: 'Brain module not available' }, 503);
|
|
1214
|
+
const type = sanitizeSetupProviderType(body.type);
|
|
1215
|
+
if (!type) return jsonResponse(res, { ok: false, error: 'Invalid provider type' }, 400);
|
|
1216
|
+
const requestedModel = sanitizeSetupModel(body.requested_model ?? body.requestedModel ?? body.model);
|
|
1217
|
+
const targetModel = sanitizeSetupModel(body.target_model ?? body.targetModel ?? body.model);
|
|
1218
|
+
const result = brain.setDefaultProviderSelection({ type, requestedModel, targetModel });
|
|
1219
|
+
syncRuntimeDefaultsFromBrain();
|
|
1220
|
+
resetDefaultClientQuiet();
|
|
1221
|
+
jsonResponse(res, { ok: true, authority: 'wall-e', ...result });
|
|
1222
|
+
} catch (e) {
|
|
1223
|
+
jsonResponse(res, { ok: false, error: e.message }, 400);
|
|
1224
|
+
}
|
|
1225
|
+
}).catch((e) => jsonResponse(res, { ok: false, error: e.message }, 400));
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const setupProviderDelete = p.match(/^\/api\/wall-e\/setup\/provider\/([a-z0-9_-]+)$/);
|
|
1230
|
+
if (setupProviderDelete && m === 'DELETE') {
|
|
1231
|
+
try {
|
|
1232
|
+
if (!brain || !ensureBrainInit()) return jsonResponse(res, { ok: false, error: 'Brain module not available' }, 503), true;
|
|
1233
|
+
const type = sanitizeSetupProviderType(setupProviderDelete[1]);
|
|
1234
|
+
if (!type) return jsonResponse(res, { ok: false, error: 'Invalid provider type' }, 400), true;
|
|
1235
|
+
const result = brain.disableModelProviderByType(type);
|
|
1236
|
+
syncRuntimeDefaultsFromBrain();
|
|
1237
|
+
resetDefaultClientQuiet();
|
|
1238
|
+
jsonResponse(res, { ok: true, authority: 'wall-e', ...result });
|
|
1239
|
+
} catch (e) {
|
|
1240
|
+
jsonResponse(res, { ok: false, error: e.message }, 400);
|
|
1241
|
+
}
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
if (p === '/api/wall-e/setup/task-default' && m === 'POST') {
|
|
1246
|
+
readBody(req).then((body) => {
|
|
1247
|
+
try {
|
|
1248
|
+
if (!brain || !ensureBrainInit()) return jsonResponse(res, { ok: false, error: 'Brain module not available' }, 503);
|
|
1249
|
+
const taskType = sanitizeTaskType(body.task_type);
|
|
1250
|
+
if (!taskType) return jsonResponse(res, { ok: false, error: 'task_type required (alphanumeric/underscore/hyphen)' }, 400);
|
|
1251
|
+
const modelRegistryId = sanitizeModelRegistryId(body.model_registry_id);
|
|
1252
|
+
const strategy = typeof body.strategy === 'string' && /^(single|quorum)$/.test(body.strategy) ? body.strategy : 'single';
|
|
1253
|
+
const quorumSize = Number.isInteger(body.quorum_size) && body.quorum_size > 0 && body.quorum_size <= 10 ? body.quorum_size : 1;
|
|
1254
|
+
const result = brain.setTaskModelDefault({ taskType, modelRegistryId, strategy, quorumSize });
|
|
1255
|
+
jsonResponse(res, { ok: true, authority: 'wall-e', ...result });
|
|
1256
|
+
} catch (e) {
|
|
1257
|
+
jsonResponse(res, { ok: false, error: e.message }, 400);
|
|
1258
|
+
}
|
|
1259
|
+
}).catch((e) => jsonResponse(res, { ok: false, error: e.message }, 400));
|
|
1260
|
+
return true;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1002
1263
|
if (p === '/api/wall-e/prompt-embeddings/upsert' && m === 'POST') {
|
|
1003
1264
|
readBody(req).then(async (body) => {
|
|
1004
1265
|
try {
|
|
@@ -3849,7 +4110,11 @@ function handleWalleApi(req, res, url) {
|
|
|
3849
4110
|
if (!tdb) return jsonResponse(res, { error: 'Telemetry not enabled' }, 404), true;
|
|
3850
4111
|
// Basic rate limiting: max 60 requests per minute per IP
|
|
3851
4112
|
const clientIpRL = (req.headers['x-forwarded-for'] || '').split(',')[0].trim() || req.socket?.remoteAddress || 'unknown';
|
|
3852
|
-
if (!handleWalleApi._rlMap) {
|
|
4113
|
+
if (!handleWalleApi._rlMap) {
|
|
4114
|
+
handleWalleApi._rlMap = new Map();
|
|
4115
|
+
handleWalleApi._rlTimer = setInterval(() => handleWalleApi._rlMap.clear(), 60000);
|
|
4116
|
+
if (typeof handleWalleApi._rlTimer.unref === 'function') handleWalleApi._rlTimer.unref();
|
|
4117
|
+
}
|
|
3853
4118
|
const rlCount = (handleWalleApi._rlMap.get(clientIpRL) || 0) + 1;
|
|
3854
4119
|
handleWalleApi._rlMap.set(clientIpRL, rlCount);
|
|
3855
4120
|
if (rlCount > 60) return jsonResponse(res, { error: 'Rate limited' }, 429), true;
|
|
@@ -3860,25 +4125,29 @@ function handleWalleApi(req, res, url) {
|
|
|
3860
4125
|
// Capture client IP from request (supports reverse proxy headers)
|
|
3861
4126
|
const clientIp = (req.headers['x-forwarded-for'] || '').split(',')[0].trim()
|
|
3862
4127
|
|| req.socket?.remoteAddress || '';
|
|
4128
|
+
const machineBucket = typeof body.machine === 'string' && /^[a-f0-9]{12,64}$/i.test(body.machine)
|
|
4129
|
+
? body.machine.toLowerCase().slice(0, 64)
|
|
4130
|
+
: null;
|
|
3863
4131
|
const insertEvent = tdb.prepare(
|
|
3864
4132
|
'INSERT INTO telemetry_events (install_id, version, os, node_version, event, meta, client_ts, ip) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
|
|
3865
4133
|
);
|
|
3866
4134
|
const upsertInstall = tdb.prepare(`
|
|
3867
|
-
INSERT INTO telemetry_installs (install_id, version, os, node_version, ip, event_count)
|
|
3868
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
4135
|
+
INSERT INTO telemetry_installs (install_id, version, os, node_version, ip, machine_bucket, event_count)
|
|
4136
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3869
4137
|
ON CONFLICT(install_id) DO UPDATE SET
|
|
3870
4138
|
last_seen = datetime('now'),
|
|
3871
4139
|
version = excluded.version,
|
|
3872
4140
|
os = excluded.os,
|
|
3873
4141
|
node_version = excluded.node_version,
|
|
3874
4142
|
ip = excluded.ip,
|
|
4143
|
+
machine_bucket = COALESCE(excluded.machine_bucket, telemetry_installs.machine_bucket),
|
|
3875
4144
|
event_count = telemetry_installs.event_count + excluded.event_count
|
|
3876
4145
|
`);
|
|
3877
4146
|
const runBatch = tdb.transaction((events) => {
|
|
3878
4147
|
for (const ev of events) {
|
|
3879
4148
|
insertEvent.run(body.id, body.v, body.os, body.node, ev.e, JSON.stringify(ev.m || {}), ev.t, clientIp);
|
|
3880
4149
|
}
|
|
3881
|
-
upsertInstall.run(body.id, body.v, body.os, body.node, clientIp, events.length);
|
|
4150
|
+
upsertInstall.run(body.id, body.v, body.os, body.node, clientIp, machineBucket, events.length);
|
|
3882
4151
|
});
|
|
3883
4152
|
runBatch(body.events.slice(0, 500)); // cap at 500 events per request
|
|
3884
4153
|
jsonResponse(res, { ok: true, received: Math.min(body.events.length, 500) });
|
|
@@ -3899,6 +4168,12 @@ function handleWalleApi(req, res, url) {
|
|
|
3899
4168
|
const summary = {
|
|
3900
4169
|
total_installs: tdb.prepare('SELECT count(*) as cnt FROM telemetry_installs').get().cnt,
|
|
3901
4170
|
active_installs: tdb.prepare('SELECT count(*) as cnt FROM telemetry_installs WHERE last_seen >= ?').get(since).cnt,
|
|
4171
|
+
unique_machines: tdb.prepare(
|
|
4172
|
+
"SELECT count(DISTINCT machine_bucket) as cnt FROM telemetry_installs WHERE machine_bucket IS NOT NULL AND machine_bucket != ''"
|
|
4173
|
+
).get().cnt,
|
|
4174
|
+
active_machines: tdb.prepare(
|
|
4175
|
+
"SELECT count(DISTINCT machine_bucket) as cnt FROM telemetry_installs WHERE last_seen >= ? AND machine_bucket IS NOT NULL AND machine_bucket != ''"
|
|
4176
|
+
).get(since).cnt,
|
|
3902
4177
|
total_events: tdb.prepare('SELECT count(*) as cnt FROM telemetry_events WHERE received_at >= ?').get(since).cnt,
|
|
3903
4178
|
events_by_type: tdb.prepare(
|
|
3904
4179
|
'SELECT event, count(*) as cnt FROM telemetry_events WHERE received_at >= ? GROUP BY event ORDER BY cnt DESC LIMIT 50'
|
|
@@ -3922,6 +4197,9 @@ function handleWalleApi(req, res, url) {
|
|
|
3922
4197
|
"SELECT date(received_at) as day, count(DISTINCT install_id) as users, count(*) as events FROM telemetry_events WHERE received_at >= ? GROUP BY day ORDER BY day DESC LIMIT ?"
|
|
3923
4198
|
).all(since, days),
|
|
3924
4199
|
};
|
|
4200
|
+
summary.install_machine_ratio = summary.unique_machines > 0
|
|
4201
|
+
? Math.round(summary.total_installs / summary.unique_machines * 100) / 100
|
|
4202
|
+
: null;
|
|
3925
4203
|
|
|
3926
4204
|
// Compat lifecycle data (Phase 4)
|
|
3927
4205
|
try {
|