create-walle 0.9.25 → 0.9.26
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 -0
- package/bin/create-walle.js +815 -45
- package/package.json +2 -2
- package/template/bin/ctm-dev-cleanup.js +90 -4
- package/template/bin/ctm-launch.sh +49 -1
- package/template/bin/dev.sh +45 -1
- package/template/bin/ensure-stable-node.js +132 -0
- package/template/bin/install-service.sh +9 -0
- package/template/claude-task-manager/api-prompts.js +899 -119
- package/template/claude-task-manager/approval-agent.js +360 -40
- package/template/claude-task-manager/bin/ctm-disclaim.c +42 -0
- package/template/claude-task-manager/bin/ctm-hotkey.swift +67 -81
- package/template/claude-task-manager/bin/ctm-screen-auth.swift +37 -0
- package/template/claude-task-manager/bin/install-hotkey.sh +97 -49
- package/template/claude-task-manager/bin/restart-ctm.sh +14 -0
- package/template/claude-task-manager/db.js +399 -48
- package/template/claude-task-manager/docs/approval-hook-sandbox.md +84 -0
- package/template/claude-task-manager/docs/codex-app-server-approvals.md +72 -0
- package/template/claude-task-manager/docs/codex-native-sandbox.md +47 -0
- package/template/claude-task-manager/docs/prompt-editing-tree-design.md +18 -1
- package/template/claude-task-manager/lib/approval-hook.js +200 -0
- package/template/claude-task-manager/lib/approval-self-adapt.js +1 -0
- package/template/claude-task-manager/lib/auth-rules.js +11 -0
- package/template/claude-task-manager/lib/background-llm.js +32 -4
- package/template/claude-task-manager/lib/codesign-identity.js +140 -0
- package/template/claude-task-manager/lib/codex-app-server-client.js +119 -0
- package/template/claude-task-manager/lib/codex-approval-bridge.js +118 -0
- package/template/claude-task-manager/lib/codex-history-terminal-renderer.js +571 -0
- package/template/claude-task-manager/lib/codex-paths.js +73 -0
- package/template/claude-task-manager/lib/codex-rollout-snapshot.js +164 -0
- package/template/claude-task-manager/lib/codex-rollout-tail.js +72 -0
- package/template/claude-task-manager/lib/codex-sandbox-args.js +47 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +118 -71
- package/template/claude-task-manager/lib/command-targets.js +163 -0
- package/template/claude-task-manager/lib/conversation-tail-merge.js +61 -19
- package/template/claude-task-manager/lib/db-owner-worker-client.js +29 -1
- package/template/claude-task-manager/lib/escalation-review.js +80 -3
- package/template/claude-task-manager/lib/flow-control.js +52 -0
- package/template/claude-task-manager/lib/fs-watcher.js +24 -15
- package/template/claude-task-manager/lib/ingest-cooldown.js +68 -0
- package/template/claude-task-manager/lib/jsonl-conversation-parser.js +8 -4
- package/template/claude-task-manager/lib/launchd-recovery.js +92 -0
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +207 -52
- package/template/claude-task-manager/lib/mobile-push-store.js +7 -0
- package/template/claude-task-manager/lib/model-overview-brain-fallback.js +102 -1
- package/template/claude-task-manager/lib/model-overview-cache.js +1 -0
- package/template/claude-task-manager/lib/oauth-proxy-supervisor.js +2 -1
- package/template/claude-task-manager/lib/perf-tracker.js +29 -2
- package/template/claude-task-manager/lib/permission-match.js +146 -16
- package/template/claude-task-manager/lib/project-slug.js +33 -0
- package/template/claude-task-manager/lib/prompt-intent.js +51 -4
- package/template/claude-task-manager/lib/read-pool-client.js +48 -3
- package/template/claude-task-manager/lib/real-node.js +73 -0
- package/template/claude-task-manager/lib/runtime-work-registry.js +131 -14
- package/template/claude-task-manager/lib/session-content-backfill.js +24 -5
- package/template/claude-task-manager/lib/session-diagnostics-batch.js +87 -0
- package/template/claude-task-manager/lib/session-history.js +5 -7
- package/template/claude-task-manager/lib/session-host-manager.js +19 -0
- package/template/claude-task-manager/lib/session-jobs.js +6 -0
- package/template/claude-task-manager/lib/session-message-response-cache.js +89 -0
- package/template/claude-task-manager/lib/session-messages-page.js +211 -0
- package/template/claude-task-manager/lib/session-messages-projection.js +170 -0
- package/template/claude-task-manager/lib/session-standup.js +8 -0
- package/template/claude-task-manager/lib/session-timeline-summary.js +16 -2
- package/template/claude-task-manager/lib/session-token-usage.js +30 -8
- package/template/claude-task-manager/lib/session-workspace-binding.js +29 -15
- package/template/claude-task-manager/lib/storage-migration.js +2 -1
- package/template/claude-task-manager/lib/transcript-store.js +179 -12
- package/template/claude-task-manager/lib/walle-ctm-history.js +298 -11
- package/template/claude-task-manager/lib/walle-permission-reply.js +49 -0
- package/template/claude-task-manager/lib/walle-session-cache.js +22 -1
- package/template/claude-task-manager/lib/walle-supervisor.js +42 -3
- package/template/claude-task-manager/package.json +5 -2
- package/template/claude-task-manager/prompt-harvest.js +31 -11
- package/template/claude-task-manager/providers/claude-code.js +29 -1
- package/template/claude-task-manager/providers/codex.js +13 -1
- package/template/claude-task-manager/public/css/setup.css +11 -0
- package/template/claude-task-manager/public/css/walle-session.css +132 -4
- package/template/claude-task-manager/public/css/walle.css +89 -0
- package/template/claude-task-manager/public/icon-16.png +0 -0
- package/template/claude-task-manager/public/icon-32.png +0 -0
- package/template/claude-task-manager/public/icon-512.png +0 -0
- package/template/claude-task-manager/public/index.html +2483 -165
- package/template/claude-task-manager/public/js/activation-render-check.js +55 -0
- package/template/claude-task-manager/public/js/flow-control-policy.js +52 -0
- package/template/claude-task-manager/public/js/message-renderer.js +60 -1
- package/template/claude-task-manager/public/js/prompts.js +13 -1
- package/template/claude-task-manager/public/js/session-status-precedence.js +9 -3
- package/template/claude-task-manager/public/js/setup.js +54 -10
- package/template/claude-task-manager/public/js/stream-resize-policy.js +80 -0
- package/template/claude-task-manager/public/js/stream-view.js +78 -0
- package/template/claude-task-manager/public/js/terminal-reconciler.js +52 -2
- package/template/claude-task-manager/public/js/tool-state.js +155 -0
- package/template/claude-task-manager/public/js/walle-session.js +887 -326
- package/template/claude-task-manager/public/js/walle.js +306 -195
- package/template/claude-task-manager/public/m/app.css +1 -0
- package/template/claude-task-manager/public/m/app.js +33 -3
- package/template/claude-task-manager/queue-engine.js +45 -1
- package/template/claude-task-manager/server.js +3367 -540
- package/template/claude-task-manager/workers/approval-blocklist.js +130 -17
- package/template/claude-task-manager/workers/db-owner-worker.js +31 -1
- package/template/claude-task-manager/workers/read-pool-worker.js +92 -5
- package/template/claude-task-manager/workers/session-host-process.js +10 -0
- package/template/claude-task-manager/workers/state-detectors/codex.js +58 -7
- package/template/package.json +2 -3
- package/template/shared/icons/AppIcon-ctm.icns +0 -0
- package/template/shared/icons/AppIcon-walle.icns +0 -0
- package/template/wall-e/agent.js +139 -18
- package/template/wall-e/api-walle.js +201 -22
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +1981 -0
- package/template/wall-e/brain.js +1049 -39
- package/template/wall-e/chat.js +427 -86
- package/template/wall-e/coding/acceptance-contract.js +26 -1
- package/template/wall-e/coding/action-memory-policy.js +353 -0
- package/template/wall-e/coding/action-memory-store.js +814 -0
- package/template/wall-e/coding/initial-messages.js +197 -0
- package/template/wall-e/coding/no-progress-guard.js +327 -0
- package/template/wall-e/coding/permission-service.js +88 -22
- package/template/wall-e/coding/session-workspaces.js +81 -0
- package/template/wall-e/coding/shell-sandbox.js +124 -0
- package/template/wall-e/coding/stream-processor.js +63 -2
- package/template/wall-e/coding/tool-execution-controller.js +14 -1
- package/template/wall-e/coding/tool-registry.js +1 -1
- package/template/wall-e/coding/transcript-writer.js +3 -0
- package/template/wall-e/coding-orchestrator.js +636 -35
- package/template/wall-e/coding-prompts.js +51 -2
- package/template/wall-e/docs/model-routing-policy.md +59 -0
- package/template/wall-e/docs/walle-shell-sandbox.md +61 -0
- package/template/wall-e/extraction/knowledge-extractor.js +76 -23
- package/template/wall-e/http/chat-api.js +30 -12
- package/template/wall-e/http/model-admin.js +93 -1
- package/template/wall-e/lib/background-lanes.js +133 -0
- package/template/wall-e/lib/boot-profile.js +11 -0
- package/template/wall-e/lib/brain-owner-worker-client.js +324 -0
- package/template/wall-e/lib/brain-read-pool-client.js +311 -0
- package/template/wall-e/lib/diagnostics-flags.js +87 -0
- package/template/wall-e/lib/event-loop-monitor.js +74 -3
- package/template/wall-e/lib/mcp-integration.js +7 -1
- package/template/wall-e/lib/real-node.js +98 -0
- package/template/wall-e/lib/runtime-health.js +206 -0
- package/template/wall-e/lib/runtime-worker-pool.js +101 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +231 -0
- package/template/wall-e/lib/scheduler.js +446 -17
- package/template/wall-e/lib/service-health.js +61 -2
- package/template/wall-e/lib/service-readiness.js +258 -0
- package/template/wall-e/lib/usage.js +152 -0
- package/template/wall-e/lib/worker-thread-pool.js +389 -0
- package/template/wall-e/llm/client.js +81 -4
- package/template/wall-e/llm/default-fallback.js +54 -8
- package/template/wall-e/llm/mlx.js +536 -73
- package/template/wall-e/llm/mlx.plugin.json +1 -1
- package/template/wall-e/llm/ollama.js +342 -43
- package/template/wall-e/llm/provider-error.js +18 -1
- package/template/wall-e/llm/provider-health-state.js +176 -0
- package/template/wall-e/llm/routing-policy.js +796 -0
- package/template/wall-e/llm/supported-models.js +5 -0
- package/template/wall-e/loops/tasks.js +60 -14
- package/template/wall-e/loops/think.js +89 -24
- package/template/wall-e/mcp-server.js +192 -28
- package/template/wall-e/server.js +32 -7
- package/template/wall-e/skills/script-skill-runner.js +8 -1
- package/template/wall-e/skills/skill-planner.js +64 -1
- package/template/wall-e/tools/builtin-middleware.js +67 -2
- package/template/wall-e/tools/local-tools.js +116 -26
- package/template/wall-e/tools/permission-checker.js +52 -4
- package/template/wall-e/tools/permission-rules.js +36 -0
- package/template/wall-e/tools/shell-analyzer.js +46 -1
- package/template/wall-e/training/gemma-e4b-qlora.js +314 -0
- package/template/wall-e/training/real-trajectory-miner.js +2617 -0
- package/template/wall-e/training/replay-eval-analysis.js +151 -0
- package/template/wall-e/training/run-shell-command-selector.js +277 -0
- package/template/wall-e/training/tool-sft-dataset.js +312 -0
- package/template/wall-e/training/tool-sft-renderers.js +144 -0
- package/template/wall-e/training/tool-trace-harvester.js +1440 -0
- package/template/wall-e/training/trajectory-action-selector.js +364 -0
- package/template/wall-e/weather-runtime.js +232 -0
- package/template/wall-e/workers/brain-owner-worker.js +162 -0
- package/template/wall-e/workers/brain-read-worker.js +148 -0
- package/template/wall-e/workers/runtime-worker.js +145 -0
package/template/wall-e/agent.js
CHANGED
|
@@ -56,6 +56,7 @@ const adapterRegistry = require('./adapters/registry');
|
|
|
56
56
|
const channelRegistry = require('./channels/registry');
|
|
57
57
|
const { chat: walleChat } = require('./chat');
|
|
58
58
|
const telemetry = require('./telemetry');
|
|
59
|
+
const runtimeHealth = require('./lib/runtime-health');
|
|
59
60
|
const fs = require('fs');
|
|
60
61
|
const path = require('path');
|
|
61
62
|
|
|
@@ -140,6 +141,12 @@ function clampInterval(value, fallback) {
|
|
|
140
141
|
return Math.max(MIN_INTERVAL_MS, Math.min(MAX_INTERVAL_MS, n));
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
function clampTimeoutMs(value, fallback) {
|
|
145
|
+
const n = Number(value);
|
|
146
|
+
const ms = Number.isFinite(n) && n > 0 ? n : fallback;
|
|
147
|
+
return Math.max(1000, Math.min(15 * 60 * 1000, Math.trunc(ms)));
|
|
148
|
+
}
|
|
149
|
+
|
|
143
150
|
function bootstrapSkills() {
|
|
144
151
|
const existing = brain.listSkills({});
|
|
145
152
|
if (existing.length > 0) return; // Already bootstrapped
|
|
@@ -302,11 +309,11 @@ async function checkForUpdates() {
|
|
|
302
309
|
async function main() {
|
|
303
310
|
console.log('[wall-e] Starting WALL-E agent daemon...');
|
|
304
311
|
|
|
305
|
-
// Boot profiler
|
|
306
|
-
//
|
|
307
|
-
// its own initDb.* sub-phases into the same profile.
|
|
312
|
+
// Boot profiler is opt-in developer diagnostics. It is disabled for normal
|
|
313
|
+
// npx/create-walle users unless WALL_E_RUNTIME_DIAGNOSTICS=1 is set.
|
|
308
314
|
const _bootp = require('./lib/boot-profile');
|
|
309
315
|
const { performance: _perf } = require('perf_hooks');
|
|
316
|
+
const { eventLoopMonitorEnabled, bootProfileEnabled, flagSnapshot } = require('./lib/diagnostics-flags');
|
|
310
317
|
_bootp.reset();
|
|
311
318
|
let _phaseLast = _perf.now();
|
|
312
319
|
const _phase = (label) => { const n = _perf.now(); _bootp.mark(label, n - _phaseLast); _phaseLast = n; };
|
|
@@ -317,10 +324,40 @@ async function main() {
|
|
|
317
324
|
const config = loadOrCreateConfig();
|
|
318
325
|
_phase('loadConfig');
|
|
319
326
|
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
|
|
327
|
+
// Wire the shell-permission CTM bridge ONCE at boot so the coding agent (not just
|
|
328
|
+
// chat) routes run_shell through CTM's unified approver — the user's Permission
|
|
329
|
+
// tab. Without this the coding path's permission-checker had no _ctmBaseUrl, fell
|
|
330
|
+
// to its stricter local DEFAULT_RULES, and parked on commands the tab allows
|
|
331
|
+
// (e.g. `cp`, which the tab allows but local rules default to `ask`). The
|
|
332
|
+
// permission-checker is a process singleton, so one configure() covers every
|
|
333
|
+
// entry point; chat.js still re-configures per turn (idempotent + token refresh).
|
|
334
|
+
// When CTM is unreachable the bridge fails closed to the local rules (no hang).
|
|
335
|
+
try {
|
|
336
|
+
const { configure: configurePermissions } = require('./tools/permission-checker');
|
|
337
|
+
const ctmPort = process.env.CTM_PORT || '3456';
|
|
338
|
+
configurePermissions({ ctmUrl: `http://localhost:${ctmPort}`, ctmToken: process.env.CTM_TOKEN });
|
|
339
|
+
} catch (e) { console.warn('[wall-e] permission bridge configure failed:', e.message); }
|
|
340
|
+
|
|
341
|
+
if (eventLoopMonitorEnabled()) {
|
|
342
|
+
// Developer-only monitor: surfaces synchronous DB/I/O stalls without adding
|
|
343
|
+
// timers/noise to packaged npx installs.
|
|
344
|
+
try {
|
|
345
|
+
require('./lib/event-loop-monitor').start({
|
|
346
|
+
getContext: () => {
|
|
347
|
+
let scheduler = null;
|
|
348
|
+
try { scheduler = Scheduler.getInstance()?.getActiveSnapshot?.() || null; } catch {}
|
|
349
|
+
let runtime = null;
|
|
350
|
+
try { runtime = runtimeHealth.snapshot({ recentLimit: 5, topLimit: 5 }); } catch {}
|
|
351
|
+
let workerPool = null;
|
|
352
|
+
try { workerPool = require('./lib/runtime-worker-pool').getRuntimeWorkerPoolStatus(); } catch {}
|
|
353
|
+
return { scheduler, runtime_active: runtime?.active || [], worker_pool: workerPool };
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
} catch (e) { console.warn('[wall-e] event-loop monitor unavailable:', e.message); }
|
|
357
|
+
}
|
|
358
|
+
if (flagSnapshot().runtime_diagnostics) {
|
|
359
|
+
console.log('[wall-e] Runtime diagnostics enabled:', JSON.stringify(flagSnapshot()));
|
|
360
|
+
}
|
|
324
361
|
|
|
325
362
|
// Init database (auto-creates data directory if needed)
|
|
326
363
|
brain.initDb();
|
|
@@ -382,8 +419,15 @@ async function main() {
|
|
|
382
419
|
console.warn('[wall-e] Auth profile setup failed (non-fatal):', err.message);
|
|
383
420
|
}
|
|
384
421
|
|
|
385
|
-
// Version check (async, non-blocking) — alert if a newer version is on npm
|
|
386
|
-
|
|
422
|
+
// Version check (async, non-blocking) — alert if a newer version is on npm.
|
|
423
|
+
// Track it so source/dev restarts can show whether npm/network checks are
|
|
424
|
+
// competing with the first scheduler ticks.
|
|
425
|
+
{
|
|
426
|
+
const op = runtimeHealth.beginOperation('agent.checkForUpdates');
|
|
427
|
+
checkForUpdates()
|
|
428
|
+
.then(() => op.end({ ok: true }))
|
|
429
|
+
.catch((err) => op.end({ ok: false, error: err }));
|
|
430
|
+
}
|
|
387
431
|
|
|
388
432
|
// Skill-reference sync (Learn+Reference system) does per-skill filesystem reads + a
|
|
389
433
|
// skill_reference scan of the memories table — hundreds of ms to seconds on a large
|
|
@@ -484,6 +528,7 @@ async function main() {
|
|
|
484
528
|
// Always start HTTP server — WALL-E API should be independently reachable
|
|
485
529
|
const { startServer } = require('./server');
|
|
486
530
|
let httpServer;
|
|
531
|
+
const startServerOp = runtimeHealth.beginOperation('agent.startServer');
|
|
487
532
|
try {
|
|
488
533
|
const _serverT0 = _perf.now();
|
|
489
534
|
httpServer = startServer();
|
|
@@ -491,42 +536,86 @@ async function main() {
|
|
|
491
536
|
_bootp.mark('startServer(portBind)', _perf.now() - _serverT0);
|
|
492
537
|
_phaseLast = _perf.now();
|
|
493
538
|
_bootp.done();
|
|
494
|
-
console.log(_bootp.summary());
|
|
539
|
+
if (bootProfileEnabled()) console.log(_bootp.summary());
|
|
540
|
+
startServerOp.end({ ok: true });
|
|
495
541
|
} catch (e) {
|
|
542
|
+
startServerOp.end({ ok: false, error: e });
|
|
496
543
|
console.error('[wall-e] HTTP server failed to start:', e.message);
|
|
497
544
|
process.exit(e && e.code === 'EADDRINUSE' ? 98 : 1);
|
|
498
545
|
}
|
|
499
546
|
|
|
547
|
+
const { getRuntimeWorkerPool } = require('./lib/runtime-worker-pool');
|
|
548
|
+
const { supportedSchedulerWorkerJobs } = require('./lib/scheduler-worker-jobs');
|
|
549
|
+
const runtimeWorkerPool = getRuntimeWorkerPool();
|
|
550
|
+
const runRuntimeJob = async (jobName, fallback, options = {}) => {
|
|
551
|
+
if (runtimeWorkerPool) {
|
|
552
|
+
try {
|
|
553
|
+
return await runtimeWorkerPool.request('scheduler.runJob', {
|
|
554
|
+
jobName,
|
|
555
|
+
trigger: options.trigger || 'agent',
|
|
556
|
+
modelOverride: options.modelOverride || null,
|
|
557
|
+
}, {
|
|
558
|
+
timeoutMs: options.timeoutMs || 0,
|
|
559
|
+
heavy: options.heavy !== false,
|
|
560
|
+
terminateOnTimeout: (options.timeoutMs || 0) > 0,
|
|
561
|
+
});
|
|
562
|
+
} catch (err) {
|
|
563
|
+
console.warn(`[wall-e] Runtime worker job "${jobName}" failed; falling back locally: ${err.message}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return await fallback();
|
|
567
|
+
};
|
|
568
|
+
|
|
500
569
|
// Deferred, off the readiness path: heavy/optional init that must not delay port bind +
|
|
501
570
|
// MCP readiness. Runs a few seconds after the server is up (the readiness probe completes
|
|
502
571
|
// first). unref'd so it never holds the process. Sub-phase timings are still captured in
|
|
503
572
|
// the boot profile (the marks append after startServer), so /api/wall-e/boot-profile shows
|
|
504
573
|
// exactly which embeddings sub-step is slow on the live brain.
|
|
505
574
|
setTimeout(() => {
|
|
575
|
+
(async () => {
|
|
506
576
|
// 1) Embedding subsystem (optional; degrades gracefully — inserts queue for the backfill job)
|
|
577
|
+
const embeddingsOp = runtimeHealth.beginOperation('agent.deferred.embeddingsInit');
|
|
507
578
|
try {
|
|
508
|
-
|
|
579
|
+
await runRuntimeJob('deferred:embeddings-init', async () => {
|
|
580
|
+
require('./embeddings').init();
|
|
581
|
+
return { ok: true };
|
|
582
|
+
}, { trigger: 'deferred-init', timeoutMs: 120000 });
|
|
583
|
+
embeddingsOp.end({ ok: true });
|
|
509
584
|
} catch (e) {
|
|
585
|
+
embeddingsOp.end({ ok: false, error: e });
|
|
510
586
|
console.log('[wall-e] Embeddings not available:', e.message);
|
|
511
587
|
}
|
|
512
588
|
// 2) One-time temporal-validity backfill (no-op on an already-migrated brain)
|
|
589
|
+
const temporalOp = runtimeHealth.beginOperation('agent.deferred.temporalBackfill');
|
|
513
590
|
try {
|
|
514
|
-
const
|
|
591
|
+
const temporalResult = await runRuntimeJob('deferred:temporal-backfill', async () => ({
|
|
592
|
+
rows: brain.backfillTemporalValidity() || 0,
|
|
593
|
+
}), { trigger: 'deferred-init', timeoutMs: 120000 });
|
|
594
|
+
const n = temporalResult?.rows || 0;
|
|
515
595
|
if (n > 0) console.log(`[wall-e] Temporal-validity backfill completed (${n} rows, deferred off boot)`);
|
|
596
|
+
temporalOp.end({ ok: true, meta: { rows: n || 0 } });
|
|
516
597
|
} catch (err) {
|
|
598
|
+
temporalOp.end({ ok: false, error: err });
|
|
517
599
|
console.warn('[wall-e] Deferred temporal-validity backfill failed (non-fatal):', err.message);
|
|
518
600
|
}
|
|
519
601
|
// 3) Skill-reference sync — deferred off the readiness path (was on the boot path,
|
|
520
602
|
// where its filesystem reads + skill_reference scan delayed HTTP port bind).
|
|
603
|
+
const skillRefOp = runtimeHealth.beginOperation('agent.deferred.skillReferenceSync');
|
|
521
604
|
try {
|
|
522
|
-
const
|
|
523
|
-
|
|
605
|
+
const refResult = await runRuntimeJob('deferred:skill-reference-sync', async () => {
|
|
606
|
+
const { syncSkillReferences, checkImportedSkillUpdates } = require('./skills/claude-code-reader');
|
|
607
|
+
const syncResult = syncSkillReferences();
|
|
608
|
+
const updates = checkImportedSkillUpdates();
|
|
609
|
+
return { synced: syncResult.synced || 0, updates: updates.length || 0, updateNames: updates.map(u => u.name + '(' + u.action + ')') };
|
|
610
|
+
}, { trigger: 'deferred-init', timeoutMs: 120000 });
|
|
524
611
|
if (refResult.synced > 0) console.log(`[wall-e] Synced ${refResult.synced} skill references to brain (deferred off boot)`);
|
|
525
|
-
|
|
526
|
-
|
|
612
|
+
if (refResult.updates > 0 && Array.isArray(refResult.updateNames)) console.log(`[wall-e] Imported skill updates: ${refResult.updateNames.join(', ')}`);
|
|
613
|
+
skillRefOp.end({ ok: true, meta: { synced: refResult.synced || 0, updates: refResult.updates || 0 } });
|
|
527
614
|
} catch (e) {
|
|
615
|
+
skillRefOp.end({ ok: false, error: e });
|
|
528
616
|
console.warn('[wall-e] Skill reference sync error:', e.message);
|
|
529
617
|
}
|
|
618
|
+
})().catch((e) => console.warn('[wall-e] Deferred runtime init failed:', e.message));
|
|
530
619
|
}, 2000).unref?.();
|
|
531
620
|
|
|
532
621
|
// Slack Socket Mode is optional and event-driven. It only starts when an
|
|
@@ -556,33 +645,59 @@ async function main() {
|
|
|
556
645
|
|
|
557
646
|
// Initial ingest (wrapped — must not crash the daemon)
|
|
558
647
|
let ingestResult = { memoriesIngested: 0 };
|
|
648
|
+
const initialIngestOp = runtimeHealth.beginOperation('agent.initialIngest');
|
|
559
649
|
try {
|
|
560
650
|
console.log('[wall-e] Running initial ingest...');
|
|
561
|
-
ingestResult = await ingest.runOnce(adapters)
|
|
651
|
+
ingestResult = await runRuntimeJob('ingest', () => ingest.runOnce(adapters), {
|
|
652
|
+
trigger: 'initial-ingest',
|
|
653
|
+
timeoutMs: 120000,
|
|
654
|
+
});
|
|
562
655
|
console.log(`[wall-e] Initial ingest: ${ingestResult.memoriesIngested} memories`);
|
|
563
656
|
if (ingestResult.memoriesIngested > 0) {
|
|
564
657
|
telemetry.track('ingest', { memories: ingestResult.memoriesIngested, initial: true });
|
|
565
658
|
telemetry.trackFunnelStep('first_ingest');
|
|
566
659
|
}
|
|
660
|
+
initialIngestOp.end({ ok: true, meta: { memoriesIngested: ingestResult.memoriesIngested || 0 } });
|
|
567
661
|
} catch (e) {
|
|
662
|
+
initialIngestOp.end({ ok: false, error: e });
|
|
568
663
|
console.error('[wall-e] Initial ingest failed (non-fatal):', e.message);
|
|
569
664
|
}
|
|
570
665
|
|
|
571
666
|
// Initial think (wrapped — must not crash the daemon)
|
|
572
667
|
if (ingestResult.memoriesIngested > 0) {
|
|
668
|
+
const initialThinkOp = runtimeHealth.beginOperation('agent.initialThink');
|
|
573
669
|
try {
|
|
574
670
|
console.log('[wall-e] Running initial think...');
|
|
575
|
-
const thinkResult = await think.runOnce()
|
|
671
|
+
const thinkResult = await runRuntimeJob('think', () => think.runOnce(), {
|
|
672
|
+
trigger: 'initial-think',
|
|
673
|
+
timeoutMs: 120000,
|
|
674
|
+
});
|
|
576
675
|
console.log(`[wall-e] Initial think: ${thinkResult.memoriesProcessed} processed, ${thinkResult.knowledgeExtracted} knowledge`);
|
|
676
|
+
initialThinkOp.end({
|
|
677
|
+
ok: true,
|
|
678
|
+
meta: {
|
|
679
|
+
memoriesProcessed: thinkResult.memoriesProcessed || 0,
|
|
680
|
+
knowledgeExtracted: thinkResult.knowledgeExtracted || 0,
|
|
681
|
+
},
|
|
682
|
+
});
|
|
577
683
|
} catch (e) {
|
|
684
|
+
initialThinkOp.end({ ok: false, error: e });
|
|
578
685
|
console.error('[wall-e] Initial think failed (non-fatal):', e.message);
|
|
579
686
|
}
|
|
580
687
|
}
|
|
581
688
|
|
|
582
689
|
// --- Central Scheduler ---
|
|
690
|
+
const schedulerDefaultWorkerTimeoutMs = clampTimeoutMs(
|
|
691
|
+
process.env.WALL_E_SCHEDULER_WORKER_TIMEOUT_MS ?? process.env.WALLE_SCHEDULER_WORKER_TIMEOUT_MS,
|
|
692
|
+
180000
|
|
693
|
+
);
|
|
583
694
|
const scheduler = new Scheduler({
|
|
584
695
|
tickMs: 5000,
|
|
585
696
|
pools: { llm: 2, ollama: 1, embedding: 1, io: 3 },
|
|
697
|
+
workerPool: runtimeWorkerPool,
|
|
698
|
+
workerJobs: supportedSchedulerWorkerJobs(),
|
|
699
|
+
defaultWorkerJobTimeoutMs: schedulerDefaultWorkerTimeoutMs,
|
|
700
|
+
closeWorkerPoolOnShutdown: true,
|
|
586
701
|
shutdownTimeoutMs: 5000,
|
|
587
702
|
// Failure-alert: after 2 consecutive errors on a single job (with 1h
|
|
588
703
|
// cooldown), write a Service Alert that the CTM Wall-E tab surfaces.
|
|
@@ -1087,6 +1202,7 @@ async function main() {
|
|
|
1087
1202
|
// its instructions. Best-effort; never crashes the daemon. Borrowed from
|
|
1088
1203
|
// OpenClaw's gateway/boot.ts. Runs BEFORE scheduler.start() so the user's
|
|
1089
1204
|
// contract is honored before the recurring loops fire.
|
|
1205
|
+
const bootCheckOp = runtimeHealth.beginOperation('agent.bootCheck');
|
|
1090
1206
|
try {
|
|
1091
1207
|
const { runBootCheck } = require('./loops/boot');
|
|
1092
1208
|
const bootResult = await runBootCheck();
|
|
@@ -1103,7 +1219,9 @@ async function main() {
|
|
|
1103
1219
|
console.warn(`[wall-e] BOOT.md run failed: ${bootResult.reason}${bootResult.error ? ' — ' + bootResult.error : ''}`);
|
|
1104
1220
|
telemetry.track('boot_check', { status: 'failed', reason: bootResult.reason });
|
|
1105
1221
|
}
|
|
1222
|
+
bootCheckOp.end({ ok: bootResult.status !== 'failed', meta: { status: bootResult.status || 'unknown' } });
|
|
1106
1223
|
} catch (e) {
|
|
1224
|
+
bootCheckOp.end({ ok: false, error: e });
|
|
1107
1225
|
console.error('[wall-e] BOOT.md run threw (non-fatal):', e.message);
|
|
1108
1226
|
telemetry.trackError('boot_check', e);
|
|
1109
1227
|
}
|
|
@@ -1112,6 +1230,7 @@ async function main() {
|
|
|
1112
1230
|
// while the daemon was down. Cap of 5 immediate runs (5s stagger);
|
|
1113
1231
|
// anything beyond that is deferred to the periodic tick. Borrowed
|
|
1114
1232
|
// from OpenClaw's runMissedJobs (src/cron/service/timer.ts:962-1040).
|
|
1233
|
+
const restartCatchupOp = runtimeHealth.beginOperation('agent.restartCatchup');
|
|
1115
1234
|
try {
|
|
1116
1235
|
const catchup = await scheduler.runMissedJobs();
|
|
1117
1236
|
if (catchup.ran > 0 || catchup.deferred > 0) {
|
|
@@ -1123,7 +1242,9 @@ async function main() {
|
|
|
1123
1242
|
deferred: catchup.deferred,
|
|
1124
1243
|
});
|
|
1125
1244
|
}
|
|
1245
|
+
restartCatchupOp.end({ ok: true, meta: { ran: catchup.ran || 0, deferred: catchup.deferred || 0 } });
|
|
1126
1246
|
} catch (e) {
|
|
1247
|
+
restartCatchupOp.end({ ok: false, error: e });
|
|
1127
1248
|
console.warn('[wall-e] runMissedJobs threw (non-fatal):', e.message);
|
|
1128
1249
|
}
|
|
1129
1250
|
|
|
@@ -382,6 +382,14 @@ function parseTelemetryOffset(value) {
|
|
|
382
382
|
return Math.max(parseInt(value, 10) || 0, 0);
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
function telemetryParamEnabled(params, names) {
|
|
386
|
+
for (const name of names) {
|
|
387
|
+
const raw = String(params.get(name) || '').trim().toLowerCase();
|
|
388
|
+
if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'full') return true;
|
|
389
|
+
}
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
|
|
385
393
|
function telemetryAdminAuth(params) {
|
|
386
394
|
const secret = process.env.WALLE_TELEMETRY_SECRET;
|
|
387
395
|
if (!secret) return { ok: false, status: 503, error: 'Telemetry admin secret not configured' };
|
|
@@ -591,6 +599,56 @@ function telemetryEventsByTypeSince(tdb, since) {
|
|
|
591
599
|
`).all(since, String(since || '').slice(0, 10));
|
|
592
600
|
}
|
|
593
601
|
|
|
602
|
+
function telemetrySanitizeDiagnosticText(value, max = 220) {
|
|
603
|
+
return String(value || 'unknown')
|
|
604
|
+
.replace(/https?:\/\/\S+/g, '[url]')
|
|
605
|
+
.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig, '[email]')
|
|
606
|
+
.replace(/\/Users\/[^\s:"']+/g, '<path>')
|
|
607
|
+
.replace(/\/private\/tmp\/[^\s:"']+/g, '<tmp>')
|
|
608
|
+
.replace(/\bpid=\d+/g, 'pid=<pid>')
|
|
609
|
+
.replace(/[0-9a-f]{8}-[0-9a-f-]{27,}/ig, '<id>')
|
|
610
|
+
.slice(0, max);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function telemetryErrorGroupsSince(tdb, since, limit = 20) {
|
|
614
|
+
const rows = tdb.prepare(`
|
|
615
|
+
SELECT version, meta
|
|
616
|
+
FROM telemetry_events
|
|
617
|
+
WHERE event = 'error' AND received_at >= ?
|
|
618
|
+
ORDER BY received_at DESC
|
|
619
|
+
LIMIT 500
|
|
620
|
+
`).all(since);
|
|
621
|
+
const groups = new Map();
|
|
622
|
+
for (const row of rows) {
|
|
623
|
+
let meta = {};
|
|
624
|
+
try { meta = row.meta ? JSON.parse(row.meta) : {}; } catch { meta = { message: row.meta }; }
|
|
625
|
+
const message = telemetrySanitizeDiagnosticText(meta.message || meta.error || meta.type || 'unknown');
|
|
626
|
+
const source = telemetrySanitizeDiagnosticText(meta.source || 'unknown', 80);
|
|
627
|
+
const key = `${message}\n${source}`;
|
|
628
|
+
const current = groups.get(key) || {
|
|
629
|
+
message,
|
|
630
|
+
source,
|
|
631
|
+
count: 0,
|
|
632
|
+
versions: new Set(),
|
|
633
|
+
skills: new Set(),
|
|
634
|
+
};
|
|
635
|
+
current.count += 1;
|
|
636
|
+
if (row.version) current.versions.add(String(row.version).slice(0, 80));
|
|
637
|
+
if (meta.skill) current.skills.add(telemetrySanitizeDiagnosticText(meta.skill, 80));
|
|
638
|
+
groups.set(key, current);
|
|
639
|
+
}
|
|
640
|
+
return Array.from(groups.values())
|
|
641
|
+
.sort((a, b) => b.count - a.count || a.message.localeCompare(b.message))
|
|
642
|
+
.slice(0, limit)
|
|
643
|
+
.map((row) => ({
|
|
644
|
+
message: row.message,
|
|
645
|
+
source: row.source,
|
|
646
|
+
count: row.count,
|
|
647
|
+
versions: Array.from(row.versions).sort(),
|
|
648
|
+
skills: Array.from(row.skills).sort(),
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
651
|
+
|
|
594
652
|
function telemetrySetIntersectionSize(left, right) {
|
|
595
653
|
let count = 0;
|
|
596
654
|
for (const value of left || []) {
|
|
@@ -1539,7 +1597,34 @@ function handleWalleApi(req, res, url) {
|
|
|
1539
1597
|
if (p === '/api/wall-e/health' && m === 'GET') {
|
|
1540
1598
|
let eventLoop = { enabled: false };
|
|
1541
1599
|
try { eventLoop = require('./lib/event-loop-monitor').stats(); } catch {}
|
|
1542
|
-
|
|
1600
|
+
let runtime = null;
|
|
1601
|
+
try { runtime = require('./lib/runtime-health').snapshot({ recentLimit: 5, topLimit: 10 }); } catch {}
|
|
1602
|
+
let diagnostics = null;
|
|
1603
|
+
try { diagnostics = require('./lib/diagnostics-flags').flagSnapshot(); } catch {}
|
|
1604
|
+
let scheduler = null;
|
|
1605
|
+
try {
|
|
1606
|
+
const { Scheduler } = require('./lib/scheduler');
|
|
1607
|
+
const sched = Scheduler.getInstance();
|
|
1608
|
+
scheduler = sched ? sched.getMetrics({ jobLimit: 10 }) : { enabled: false, running: false };
|
|
1609
|
+
} catch {}
|
|
1610
|
+
let brainWriter = null;
|
|
1611
|
+
try { brainWriter = brain?.getOwnerWriteQueueStatus?.() || null; } catch {}
|
|
1612
|
+
let readiness = null;
|
|
1613
|
+
try {
|
|
1614
|
+
const { getServiceReadiness } = require('./lib/service-readiness');
|
|
1615
|
+
readiness = getServiceReadiness({ eventLoop, runtime, brainWriter });
|
|
1616
|
+
} catch {}
|
|
1617
|
+
return jsonResponse(res, {
|
|
1618
|
+
status: 'ok',
|
|
1619
|
+
uptime: process.uptime(),
|
|
1620
|
+
version: '0.1.0',
|
|
1621
|
+
diagnostics,
|
|
1622
|
+
event_loop: eventLoop,
|
|
1623
|
+
runtime,
|
|
1624
|
+
scheduler,
|
|
1625
|
+
brain_writer: brainWriter,
|
|
1626
|
+
readiness,
|
|
1627
|
+
}), true;
|
|
1543
1628
|
}
|
|
1544
1629
|
|
|
1545
1630
|
// GET /api/wall-e/alerts — service alerts (auth failures, skill disabled, update available)
|
|
@@ -1548,6 +1633,7 @@ function handleWalleApi(req, res, url) {
|
|
|
1548
1633
|
res.setHeader('Cache-Control', 'no-store, max-age=0');
|
|
1549
1634
|
const { getServiceAlerts } = require('./skills/skill-planner');
|
|
1550
1635
|
const { buildServiceHealth } = require('./lib/service-health');
|
|
1636
|
+
const { getProviderHealthStatus } = require('./llm/provider-health-state');
|
|
1551
1637
|
const alerts = getServiceAlerts({ includeVersionUpdate: true });
|
|
1552
1638
|
let activeProvider = process.env.WALLE_PROVIDER || '';
|
|
1553
1639
|
let activeModel = process.env.WALLE_MODEL || '';
|
|
@@ -1558,6 +1644,7 @@ function handleWalleApi(req, res, url) {
|
|
|
1558
1644
|
const summary = buildServiceHealth(alerts, {
|
|
1559
1645
|
activeProvider,
|
|
1560
1646
|
activeModel,
|
|
1647
|
+
providerStatus: getProviderHealthStatus(activeProvider, activeModel),
|
|
1561
1648
|
});
|
|
1562
1649
|
return jsonResponse(res, { alerts, summary }), true;
|
|
1563
1650
|
} catch (e) {
|
|
@@ -1761,7 +1848,11 @@ function handleWalleApi(req, res, url) {
|
|
|
1761
1848
|
if (p.startsWith('/api/wall-e/alerts/') && m === 'DELETE') {
|
|
1762
1849
|
try {
|
|
1763
1850
|
const alertId = decodeURIComponent(p.split('/api/wall-e/alerts/')[1]);
|
|
1764
|
-
if (alertId
|
|
1851
|
+
if (alertId.startsWith('provider_status:')) {
|
|
1852
|
+
const { clearProviderHealthStatus, parseProviderStatusIssueId } = require('./llm/provider-health-state');
|
|
1853
|
+
const target = parseProviderStatusIssueId(alertId);
|
|
1854
|
+
if (target) clearProviderHealthStatus(target);
|
|
1855
|
+
} else if (alertId === 'version_update') {
|
|
1765
1856
|
try {
|
|
1766
1857
|
const telemetry = require('./telemetry');
|
|
1767
1858
|
const updateRaw = brain.getKv('update_available');
|
|
@@ -1802,6 +1893,7 @@ function handleWalleApi(req, res, url) {
|
|
|
1802
1893
|
tickMs: sched._tickMs,
|
|
1803
1894
|
jobs: sched.getState(),
|
|
1804
1895
|
pools: sched.getPoolState(),
|
|
1896
|
+
metrics: sched.getMetrics(),
|
|
1805
1897
|
}), true;
|
|
1806
1898
|
}
|
|
1807
1899
|
|
|
@@ -2507,10 +2599,12 @@ function handleWalleApi(req, res, url) {
|
|
|
2507
2599
|
try {
|
|
2508
2600
|
const { getServiceAlerts } = require('./skills/skill-planner');
|
|
2509
2601
|
const { buildServiceHealth } = require('./lib/service-health');
|
|
2602
|
+
const { getProviderHealthStatus } = require('./llm/provider-health-state');
|
|
2510
2603
|
serviceAlerts = getServiceAlerts({ includeVersionUpdate: true });
|
|
2511
2604
|
serviceAlertSummary = buildServiceHealth(serviceAlerts, {
|
|
2512
2605
|
activeProvider: walleProvider,
|
|
2513
2606
|
activeModel: walleModel,
|
|
2607
|
+
providerStatus: getProviderHealthStatus(walleProvider, walleModel),
|
|
2514
2608
|
});
|
|
2515
2609
|
} catch {}
|
|
2516
2610
|
|
|
@@ -2520,6 +2614,23 @@ function handleWalleApi(req, res, url) {
|
|
|
2520
2614
|
slackSocketMode = getSlackSocketModeStatus();
|
|
2521
2615
|
} catch {}
|
|
2522
2616
|
|
|
2617
|
+
let eventLoop = { enabled: false };
|
|
2618
|
+
try { eventLoop = require('./lib/event-loop-monitor').stats(); } catch {}
|
|
2619
|
+
let runtime = null;
|
|
2620
|
+
try { runtime = require('./lib/runtime-health').snapshot({ recentLimit: 10, topLimit: 20 }); } catch {}
|
|
2621
|
+
let brainWriter = null;
|
|
2622
|
+
try { brainWriter = brain?.getOwnerWriteQueueStatus?.() || null; } catch {}
|
|
2623
|
+
let readiness = null;
|
|
2624
|
+
try {
|
|
2625
|
+
const { getServiceReadiness } = require('./lib/service-readiness');
|
|
2626
|
+
readiness = getServiceReadiness({
|
|
2627
|
+
eventLoop,
|
|
2628
|
+
runtime,
|
|
2629
|
+
brainWriter,
|
|
2630
|
+
serviceHealth: serviceAlertSummary,
|
|
2631
|
+
});
|
|
2632
|
+
} catch {}
|
|
2633
|
+
|
|
2523
2634
|
return jsonResponse(res, {
|
|
2524
2635
|
data: {
|
|
2525
2636
|
...result,
|
|
@@ -2538,6 +2649,10 @@ function handleWalleApi(req, res, url) {
|
|
|
2538
2649
|
service_alerts: serviceAlerts,
|
|
2539
2650
|
service_alert_summary: serviceAlertSummary,
|
|
2540
2651
|
slack_socket_mode: slackSocketMode,
|
|
2652
|
+
event_loop: eventLoop,
|
|
2653
|
+
runtime,
|
|
2654
|
+
brain_writer: brainWriter,
|
|
2655
|
+
readiness,
|
|
2541
2656
|
}
|
|
2542
2657
|
}), true;
|
|
2543
2658
|
}
|
|
@@ -5909,6 +6024,7 @@ function handleWalleApi(req, res, url) {
|
|
|
5909
6024
|
}
|
|
5910
6025
|
const days = parseTelemetryDays(params.get('days'), 30);
|
|
5911
6026
|
const since = telemetrySinceDateTime(days);
|
|
6027
|
+
const includeIdentifiers = telemetryParamEnabled(params, ['full', 'include_identifiers', 'identifiers', 'debug']);
|
|
5912
6028
|
const dailyActivityDays = Math.min(days, 7);
|
|
5913
6029
|
const dailyActivitySince = telemetrySinceDateTime(dailyActivityDays);
|
|
5914
6030
|
const stableIdentitySql = "CASE WHEN machine_bucket IS NOT NULL AND machine_bucket != '' THEN 'm:' || machine_bucket ELSE 'i:' || install_id END";
|
|
@@ -5939,15 +6055,10 @@ function handleWalleApi(req, res, url) {
|
|
|
5939
6055
|
unique_ips: tdb.prepare(
|
|
5940
6056
|
"SELECT count(DISTINCT ip) as cnt FROM telemetry_installs WHERE ip IS NOT NULL AND ip != ''"
|
|
5941
6057
|
).get().cnt,
|
|
5942
|
-
|
|
5943
|
-
"SELECT
|
|
5944
|
-
).
|
|
5945
|
-
|
|
5946
|
-
"SELECT owner_name_hash, count(*) as installs, max(last_seen) as last_seen FROM telemetry_installs WHERE owner_name_hash IS NOT NULL AND owner_name_hash != '' GROUP BY owner_name_hash ORDER BY installs DESC LIMIT 100"
|
|
5947
|
-
).all(),
|
|
5948
|
-
recent_errors: tdb.prepare(
|
|
5949
|
-
"SELECT install_id, version, meta, received_at FROM telemetry_events WHERE event = 'error' AND received_at >= ? ORDER BY received_at DESC LIMIT 50"
|
|
5950
|
-
).all(since),
|
|
6058
|
+
unique_owner_hashes: tdb.prepare(
|
|
6059
|
+
"SELECT count(DISTINCT owner_name_hash) as cnt FROM telemetry_installs WHERE owner_name_hash IS NOT NULL AND owner_name_hash != ''"
|
|
6060
|
+
).get().cnt,
|
|
6061
|
+
error_groups: telemetryErrorGroupsSince(tdb, since),
|
|
5951
6062
|
daily_activity: tdb.prepare(
|
|
5952
6063
|
`SELECT date(te.received_at) as day, count(DISTINCT ${eventStableIdentitySql}) as users, count(*) as events
|
|
5953
6064
|
FROM telemetry_events te
|
|
@@ -5955,7 +6066,22 @@ function handleWalleApi(req, res, url) {
|
|
|
5955
6066
|
WHERE te.received_at >= ?
|
|
5956
6067
|
GROUP BY day ORDER BY day DESC LIMIT ?`
|
|
5957
6068
|
).all(dailyActivitySince, dailyActivityDays),
|
|
6069
|
+
privacy: {
|
|
6070
|
+
identifiers: includeIdentifiers ? 'included' : 'omitted',
|
|
6071
|
+
full: includeIdentifiers,
|
|
6072
|
+
},
|
|
5958
6073
|
};
|
|
6074
|
+
if (includeIdentifiers) {
|
|
6075
|
+
summary.ip_breakdown = tdb.prepare(
|
|
6076
|
+
"SELECT ip, count(*) as installs, group_concat(install_id) as install_ids FROM telemetry_installs WHERE ip IS NOT NULL AND ip != '' GROUP BY ip ORDER BY installs DESC LIMIT 100"
|
|
6077
|
+
).all();
|
|
6078
|
+
summary.owner_hash_breakdown = tdb.prepare(
|
|
6079
|
+
"SELECT owner_name_hash, count(*) as installs, max(last_seen) as last_seen FROM telemetry_installs WHERE owner_name_hash IS NOT NULL AND owner_name_hash != '' GROUP BY owner_name_hash ORDER BY installs DESC LIMIT 100"
|
|
6080
|
+
).all();
|
|
6081
|
+
summary.recent_errors = tdb.prepare(
|
|
6082
|
+
"SELECT install_id, version, meta, received_at FROM telemetry_events WHERE event = 'error' AND received_at >= ? ORDER BY received_at DESC LIMIT 50"
|
|
6083
|
+
).all(since);
|
|
6084
|
+
}
|
|
5959
6085
|
summary.install_machine_ratio = summary.unique_machines > 0
|
|
5960
6086
|
? Math.round(summary.total_install_ids / summary.unique_machines * 100) / 100
|
|
5961
6087
|
: null;
|
|
@@ -5968,18 +6094,23 @@ function handleWalleApi(req, res, url) {
|
|
|
5968
6094
|
summary.feedback_by_status = tdb.prepare(
|
|
5969
6095
|
'SELECT status, count(*) as cnt FROM feedback_reports WHERE received_at >= ? GROUP BY status ORDER BY cnt DESC'
|
|
5970
6096
|
).all(since);
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
6097
|
+
if (includeIdentifiers) {
|
|
6098
|
+
summary.recent_feedback = tdb.prepare(`
|
|
6099
|
+
SELECT feedback_id, type, status, severity, title, consent_level, version, received_at
|
|
6100
|
+
FROM feedback_reports
|
|
6101
|
+
WHERE received_at >= ?
|
|
6102
|
+
ORDER BY received_at DESC
|
|
6103
|
+
LIMIT 20
|
|
6104
|
+
`).all(since);
|
|
6105
|
+
} else {
|
|
6106
|
+
summary.recent_feedback_omitted = true;
|
|
6107
|
+
}
|
|
5978
6108
|
} catch {
|
|
5979
6109
|
summary.feedback_count = 0;
|
|
5980
6110
|
summary.feedback_by_type = [];
|
|
5981
6111
|
summary.feedback_by_status = [];
|
|
5982
|
-
summary.recent_feedback = [];
|
|
6112
|
+
if (includeIdentifiers) summary.recent_feedback = [];
|
|
6113
|
+
else summary.recent_feedback_omitted = true;
|
|
5983
6114
|
}
|
|
5984
6115
|
|
|
5985
6116
|
try {
|
|
@@ -6655,10 +6786,24 @@ function handleWalleApi(req, res, url) {
|
|
|
6655
6786
|
}
|
|
6656
6787
|
}
|
|
6657
6788
|
|
|
6789
|
+
// POST /api/wall-e/permissions/reevaluate — re-check parked requests against the
|
|
6790
|
+
// current rules and auto-resolve any now-allowed. CTM calls this after adding an
|
|
6791
|
+
// allow rule (Pending-tab whitelist) so a matching park self-heals without a
|
|
6792
|
+
// poller. Must precede the /:id/reply matcher (which would otherwise swallow it).
|
|
6793
|
+
if (p === '/api/wall-e/permissions/reevaluate' && m === 'POST') {
|
|
6794
|
+
readBody(req).then(body => {
|
|
6795
|
+
const sessionId = body.session_id || body.sessionId || '';
|
|
6796
|
+
Promise.resolve(_permissionRegistry.reEvaluatePending({ sessionId }))
|
|
6797
|
+
.then(() => jsonResponse(res, { ok: true }))
|
|
6798
|
+
.catch(e => jsonResponse(res, { error: e.message }, 500));
|
|
6799
|
+
}).catch(e => jsonResponse(res, { error: e.message }, 500));
|
|
6800
|
+
return true;
|
|
6801
|
+
}
|
|
6802
|
+
|
|
6658
6803
|
// POST /api/wall-e/permissions/:id/reply — reply once/always/reject
|
|
6659
6804
|
const permissionReplyMatch = p.match(/^\/api\/wall-e\/permissions\/([^/]+)\/reply$/);
|
|
6660
6805
|
if (permissionReplyMatch && m === 'POST') {
|
|
6661
|
-
readBody(req).then(body => {
|
|
6806
|
+
readBody(req).then(async body => {
|
|
6662
6807
|
const reply = body.reply || body.decision;
|
|
6663
6808
|
if (!['once', 'always', 'reject'].includes(reply)) {
|
|
6664
6809
|
return jsonResponse(res, { error: 'reply must be once, always, or reject' }, 400);
|
|
@@ -6668,8 +6813,42 @@ function handleWalleApi(req, res, url) {
|
|
|
6668
6813
|
reply,
|
|
6669
6814
|
message: body.message || '',
|
|
6670
6815
|
});
|
|
6671
|
-
|
|
6672
|
-
jsonResponse(res, { ok: true });
|
|
6816
|
+
// A live parked turn accepted the reply -> it resumes immediately.
|
|
6817
|
+
if (ok) return jsonResponse(res, { ok: true, resumed: true });
|
|
6818
|
+
|
|
6819
|
+
// ORPHANED: the parked request is gone (Wall-E restart, the turn already
|
|
6820
|
+
// ended, or a card rehydrated from history). The original turn cannot be
|
|
6821
|
+
// resumed here. If the user chose "always", persist a durable rule derived
|
|
6822
|
+
// from the command so a FRESH continuation turn (started by CTM) won't
|
|
6823
|
+
// re-park on the same command. (CTM starts that turn; this just makes it
|
|
6824
|
+
// clean.) The rule store is brain-KV backed -> shared with the orchestrator's
|
|
6825
|
+
// permission checker and survives restart.
|
|
6826
|
+
let ruleAdded = false;
|
|
6827
|
+
if (reply === 'always' && body.command) {
|
|
6828
|
+
try {
|
|
6829
|
+
const { buildToolPermissionRequests } = require('./coding/permission-service');
|
|
6830
|
+
const requests = await buildToolPermissionRequests(
|
|
6831
|
+
body.tool || 'run_shell',
|
|
6832
|
+
{ command: body.command },
|
|
6833
|
+
{ cwd: body.projectRoot || '', projectRoot: body.projectRoot || '' },
|
|
6834
|
+
);
|
|
6835
|
+
for (const r of requests) {
|
|
6836
|
+
_permissionRegistry.rulesStore.addAlways({
|
|
6837
|
+
projectRoot: r.projectRoot,
|
|
6838
|
+
permission: r.permission,
|
|
6839
|
+
pattern: r.pattern,
|
|
6840
|
+
tool: r.tool,
|
|
6841
|
+
fingerprint: r.fingerprint,
|
|
6842
|
+
});
|
|
6843
|
+
ruleAdded = true;
|
|
6844
|
+
}
|
|
6845
|
+
// The new rule may also cover OTHER still-parked requests.
|
|
6846
|
+
if (ruleAdded) await _permissionRegistry.reEvaluatePending({});
|
|
6847
|
+
} catch (e) {
|
|
6848
|
+
console.error('[walle-permission] orphan rule-add failed:', e.message);
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
return jsonResponse(res, { ok: false, resumed: false, orphaned: true, ruleAdded }, ruleAdded ? 200 : 404);
|
|
6673
6852
|
}).catch(e => jsonResponse(res, { error: e.message }, 500));
|
|
6674
6853
|
return true;
|
|
6675
6854
|
}
|