moflo 4.9.21 → 4.9.23
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/.claude/agents/analysis/analyze-code-quality.md +0 -121
- package/.claude/agents/analysis/code-analyzer.md +5 -26
- package/.claude/agents/architecture/system-design/arch-system-design.md +0 -119
- package/.claude/agents/base-template-generator.md +0 -1
- package/.claude/agents/core/coder.md +0 -22
- package/.claude/agents/core/planner.md +0 -16
- package/.claude/agents/core/researcher.md +0 -16
- package/.claude/agents/core/reviewer.md +0 -17
- package/.claude/agents/core/tester.md +0 -19
- package/.claude/agents/custom/test-long-runner.md +0 -2
- package/.claude/agents/development/dev-backend-api.md +0 -167
- package/.claude/agents/development/dev-database.md +43 -0
- package/.claude/agents/development/dev-frontend.md +42 -0
- package/.claude/agents/devops/ci-cd/ops-cicd-github.md +0 -112
- package/.claude/agents/documentation/api-docs/docs-api-openapi.md +0 -111
- package/.claude/agents/security/security-auditor.md +45 -0
- package/.claude/guidance/shipped/moflo-cli-reference.md +19 -16
- package/.claude/guidance/shipped/moflo-core-guidance.md +0 -2
- package/.claude/guidance/shipped/moflo-guidance-rules.md +5 -5
- package/.claude/guidance/shipped/moflo-spell-runner.md +1 -0
- package/.claude/guidance/shipped/moflo-spell-scheduling.md +225 -0
- package/.claude/guidance/shipped/moflo-spell-troubleshooting.md +1 -0
- package/.claude/helpers/gate.cjs +70 -3
- package/.claude/skills/fl/execution-modes.md +38 -15
- package/.claude/skills/fl/phases.md +67 -0
- package/.claude/skills/spell-schedule/SKILL.md +18 -5
- package/README.md +1 -1
- package/bin/gate.cjs +70 -3
- package/bin/index-guidance.mjs +32 -6
- package/bin/lib/retired-files.mjs +146 -0
- package/bin/session-start-launcher.mjs +116 -8
- package/dist/src/cli/appliance/rvfa-builder.js +1 -1
- package/dist/src/cli/commands/agent.js +3 -9
- package/dist/src/cli/commands/daemon.js +13 -17
- package/dist/src/cli/commands/hooks.js +4 -9
- package/dist/src/cli/commands/index.js +2 -0
- package/dist/src/cli/commands/retire.js +111 -0
- package/dist/src/cli/commands/spell-schedule.js +237 -49
- package/dist/src/cli/hooks/reasoningbank/index.js +7 -7
- package/dist/src/cli/init/executor.js +26 -54
- package/dist/src/cli/init/helpers-generator.js +66 -3
- package/dist/src/cli/init/settings-generator.js +17 -6
- package/dist/src/cli/mcp-tools/agent-tools.js +9 -27
- package/dist/src/cli/mcp-tools/hooks-tools.js +23 -21
- package/dist/src/cli/mcp-tools/memory-tools.js +16 -5
- package/dist/src/cli/memory/bridge-embedder.js +26 -6
- package/dist/src/cli/memory/bridge-entries.js +33 -15
- package/dist/src/cli/memory/controllers/semantic-router.js +18 -12
- package/dist/src/cli/memory/sona-optimizer.js +6 -6
- package/dist/src/cli/neural/domain/services/learning-service.js +3 -3
- package/dist/src/cli/services/agent-router.js +2 -5
- package/dist/src/cli/services/daemon-autostart-lifecycle.js +62 -0
- package/dist/src/cli/services/daemon-dashboard.js +187 -18
- package/dist/src/cli/services/daemon-readiness.js +19 -31
- package/dist/src/cli/services/ephemeral-namespace-purge.js +61 -33
- package/dist/src/cli/services/headless-worker-executor.js +7 -94
- package/dist/src/cli/services/hook-block-hash.js +4 -0
- package/dist/src/cli/services/worker-daemon.js +40 -66
- package/dist/src/cli/shared/events/example-usage.js +6 -6
- package/dist/src/cli/shared/hooks/task-hooks.js +8 -8
- package/dist/src/cli/spells/core/runner.js +12 -0
- package/dist/src/cli/spells/scheduler/scheduler.js +24 -9
- package/dist/src/cli/spells/schema/validator.js +2 -1
- package/dist/src/cli/spells/schema/validators/top-level.js +18 -0
- package/dist/src/cli/version.js +1 -1
- package/package.json +5 -2
- package/retired-files.json +1989 -0
- package/src/cli/data/model-registry.json +2 -2
- package/.claude/agents/consensus/byzantine-coordinator.md +0 -63
- package/.claude/agents/consensus/crdt-synchronizer.md +0 -997
- package/.claude/agents/consensus/gossip-coordinator.md +0 -63
- package/.claude/agents/consensus/performance-benchmarker.md +0 -851
- package/.claude/agents/consensus/quorum-manager.md +0 -823
- package/.claude/agents/consensus/raft-manager.md +0 -63
- package/.claude/agents/consensus/security-manager.md +0 -622
- package/.claude/agents/data/ml/data-ml-model.md +0 -193
- package/.claude/agents/github/code-review-swarm.md +0 -538
- package/.claude/agents/github/github-modes.md +0 -172
- package/.claude/agents/github/issue-tracker.md +0 -311
- package/.claude/agents/github/multi-repo-swarm.md +0 -551
- package/.claude/agents/github/pr-manager.md +0 -183
- package/.claude/agents/github/project-board-sync.md +0 -508
- package/.claude/agents/github/release-manager.md +0 -360
- package/.claude/agents/github/release-swarm.md +0 -580
- package/.claude/agents/github/repo-architect.md +0 -391
- package/.claude/agents/github/swarm-issue.md +0 -566
- package/.claude/agents/github/swarm-pr.md +0 -414
- package/.claude/agents/github/sync-coordinator.md +0 -426
- package/.claude/agents/github/workflow-automation.md +0 -606
- package/.claude/agents/goal/code-goal-planner.md +0 -440
- package/.claude/agents/goal/goal-planner.md +0 -168
- package/.claude/agents/hive-mind/collective-intelligence-coordinator.md +0 -127
- package/.claude/agents/hive-mind/queen-coordinator.md +0 -198
- package/.claude/agents/hive-mind/scout-explorer.md +0 -233
- package/.claude/agents/hive-mind/swarm-memory-manager.md +0 -184
- package/.claude/agents/hive-mind/worker-specialist.md +0 -208
- package/.claude/agents/neural/safla-neural.md +0 -73
- package/.claude/agents/optimization/benchmark-suite.md +0 -665
- package/.claude/agents/optimization/load-balancer.md +0 -431
- package/.claude/agents/optimization/performance-monitor.md +0 -672
- package/.claude/agents/optimization/resource-allocator.md +0 -674
- package/.claude/agents/optimization/topology-optimizer.md +0 -808
- package/.claude/agents/reasoning/goal-planner.md +0 -67
- package/.claude/agents/sona/sona-learning-optimizer.md +0 -74
- package/.claude/agents/sparc/architecture.md +0 -472
- package/.claude/agents/sparc/pseudocode.md +0 -318
- package/.claude/agents/sparc/refinement.md +0 -525
- package/.claude/agents/sparc/specification.md +0 -276
- package/.claude/agents/specialized/mobile/spec-mobile-react-native.md +0 -225
- package/.claude/agents/swarm/adaptive-coordinator.md +0 -391
- package/.claude/agents/swarm/hierarchical-coordinator.md +0 -321
- package/.claude/agents/swarm/mesh-coordinator.md +0 -383
- package/.claude/agents/testing/production-validator.md +0 -395
- package/.claude/agents/testing/tdd-london-swarm.md +0 -244
- package/.claude/agents/v3/adr-architect.md +0 -184
- package/.claude/agents/v3/aidefence-guardian.md +0 -277
- package/.claude/agents/v3/claims-authorizer.md +0 -208
- package/.claude/agents/v3/collective-intelligence-coordinator.md +0 -988
- package/.claude/agents/v3/ddd-domain-expert.md +0 -220
- package/.claude/agents/v3/injection-analyst.md +0 -232
- package/.claude/agents/v3/memory-specialist.md +0 -987
- package/.claude/agents/v3/performance-engineer.md +0 -1225
- package/.claude/agents/v3/pii-detector.md +0 -146
- package/.claude/agents/v3/reasoningbank-learner.md +0 -213
- package/.claude/agents/v3/security-architect-aidefence.md +0 -405
- package/.claude/agents/v3/security-architect.md +0 -865
- package/.claude/agents/v3/security-auditor.md +0 -771
- package/.claude/agents/v3/sparc-orchestrator.md +0 -182
- package/.claude/agents/v3/swarm-memory-manager.md +0 -142
- package/.claude/agents/v3/v3-integration-architect.md +0 -205
- package/.claude/commands/claude-flow-help.md +0 -103
- package/.claude/commands/claude-flow-memory.md +0 -107
- package/.claude/commands/claude-flow-swarm.md +0 -205
- package/.claude/commands/flo-simplify.md +0 -101
- package/.claude/commands/github/README.md +0 -11
- package/.claude/commands/github/code-review-swarm.md +0 -514
- package/.claude/commands/github/code-review.md +0 -25
- package/.claude/commands/github/github-modes.md +0 -146
- package/.claude/commands/github/github-swarm.md +0 -113
- package/.claude/commands/github/issue-tracker.md +0 -284
- package/.claude/commands/github/issue-triage.md +0 -25
- package/.claude/commands/github/multi-repo-swarm.md +0 -519
- package/.claude/commands/github/pr-enhance.md +0 -26
- package/.claude/commands/github/pr-manager.md +0 -164
- package/.claude/commands/github/project-board-sync.md +0 -471
- package/.claude/commands/github/release-manager.md +0 -332
- package/.claude/commands/github/release-swarm.md +0 -544
- package/.claude/commands/github/repo-analyze.md +0 -25
- package/.claude/commands/github/repo-architect.md +0 -361
- package/.claude/commands/github/swarm-issue.md +0 -482
- package/.claude/commands/github/swarm-pr.md +0 -285
- package/.claude/commands/github/sync-coordinator.md +0 -294
- package/.claude/commands/github/workflow-automation.md +0 -442
- package/.claude/commands/hooks/README.md +0 -11
- package/.claude/commands/hooks/overview.md +0 -58
- package/.claude/commands/hooks/post-edit.md +0 -117
- package/.claude/commands/hooks/post-task.md +0 -112
- package/.claude/commands/hooks/pre-edit.md +0 -113
- package/.claude/commands/hooks/pre-task.md +0 -111
- package/.claude/commands/hooks/session-end.md +0 -118
- package/.claude/commands/hooks/setup.md +0 -103
- package/.claude/commands/sparc/analyzer.md +0 -42
- package/.claude/commands/sparc/architect.md +0 -43
- package/.claude/commands/sparc/ask.md +0 -86
- package/.claude/commands/sparc/batch-executor.md +0 -44
- package/.claude/commands/sparc/code.md +0 -78
- package/.claude/commands/sparc/coder.md +0 -44
- package/.claude/commands/sparc/debug.md +0 -72
- package/.claude/commands/sparc/debugger.md +0 -44
- package/.claude/commands/sparc/designer.md +0 -43
- package/.claude/commands/sparc/devops.md +0 -98
- package/.claude/commands/sparc/docs-writer.md +0 -69
- package/.claude/commands/sparc/documenter.md +0 -44
- package/.claude/commands/sparc/innovator.md +0 -44
- package/.claude/commands/sparc/integration.md +0 -72
- package/.claude/commands/sparc/mcp.md +0 -106
- package/.claude/commands/sparc/memory-manager.md +0 -44
- package/.claude/commands/sparc/optimizer.md +0 -44
- package/.claude/commands/sparc/orchestrator.md +0 -116
- package/.claude/commands/sparc/post-deployment-monitoring-mode.md +0 -72
- package/.claude/commands/sparc/refinement-optimization-mode.md +0 -72
- package/.claude/commands/sparc/researcher.md +0 -44
- package/.claude/commands/sparc/reviewer.md +0 -44
- package/.claude/commands/sparc/security-review.md +0 -69
- package/.claude/commands/sparc/sparc-modes.md +0 -139
- package/.claude/commands/sparc/sparc.md +0 -99
- package/.claude/commands/sparc/spec-pseudocode.md +0 -69
- package/.claude/commands/sparc/spell-manager.md +0 -44
- package/.claude/commands/sparc/supabase-admin.md +0 -337
- package/.claude/commands/sparc/swarm-coordinator.md +0 -44
- package/.claude/commands/sparc/tdd.md +0 -44
- package/.claude/commands/sparc/tester.md +0 -44
- package/.claude/commands/sparc/tutorial.md +0 -68
- package/.claude/commands/sparc.md +0 -151
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The Arcane Console — Lightweight localhost HTTP server
|
|
3
3
|
*
|
|
4
|
-
* Serves
|
|
5
|
-
* and memory stats. Binds to 127.0.0.1
|
|
4
|
+
* Serves the moflo Arcane Console (read-only daemon view) for status,
|
|
5
|
+
* scheduled spells, executions, and memory stats. Binds to 127.0.0.1
|
|
6
|
+
* only (no auth needed).
|
|
7
|
+
*
|
|
8
|
+
* Internal/code identifiers retain the term "dashboard" (CLI flags
|
|
9
|
+
* `--dashboard-port` / `--no-dashboard`, internal port constant) for
|
|
10
|
+
* stability with existing consumer scripts; only user-facing surface
|
|
11
|
+
* is rebranded.
|
|
6
12
|
*
|
|
7
13
|
* @module daemon-dashboard
|
|
8
14
|
*/
|
|
@@ -78,10 +84,17 @@ function tryParseSafe(s) {
|
|
|
78
84
|
}
|
|
79
85
|
function handleStatus(daemon) {
|
|
80
86
|
const status = daemon.getStatus();
|
|
87
|
+
// Index config rows by worker type so the row renderer can show a
|
|
88
|
+
// "disabled" badge instead of "Last run: never" for default-off workers
|
|
89
|
+
// (audit, predict, document — see #968 user feedback).
|
|
90
|
+
const configByType = new Map();
|
|
91
|
+
for (const w of status.config.workers)
|
|
92
|
+
configByType.set(w.type, { enabled: w.enabled });
|
|
81
93
|
const workers = [];
|
|
82
94
|
for (const [type, state] of status.workers) {
|
|
83
95
|
workers.push({
|
|
84
96
|
type,
|
|
97
|
+
enabled: configByType.get(type)?.enabled ?? true,
|
|
85
98
|
isRunning: state.isRunning,
|
|
86
99
|
lastRun: state.lastRun?.toISOString() ?? null,
|
|
87
100
|
nextRun: state.nextRun?.toISOString() ?? null,
|
|
@@ -330,6 +343,9 @@ async function handleRequest(req, res, daemon, opts) {
|
|
|
330
343
|
else if (url === '/api/schedules') {
|
|
331
344
|
sendJson(res, 200, await handleSchedules(daemon, opts));
|
|
332
345
|
}
|
|
346
|
+
else if (url === '/api/schedules/events') {
|
|
347
|
+
handleSchedulesEventStream(req, res, daemon);
|
|
348
|
+
}
|
|
333
349
|
else if (url === '/api/spells') {
|
|
334
350
|
sendJson(res, 200, await handleSpells(opts.memory));
|
|
335
351
|
}
|
|
@@ -400,6 +416,71 @@ function getSchedulerErrorCode(err) {
|
|
|
400
416
|
}
|
|
401
417
|
return null;
|
|
402
418
|
}
|
|
419
|
+
/** Heartbeat interval for the schedule-event SSE stream (keeps proxies + idle clients alive). */
|
|
420
|
+
const SSE_HEARTBEAT_MS = 25_000;
|
|
421
|
+
/**
|
|
422
|
+
* Stream `schedule:*` events to the client via Server-Sent Events.
|
|
423
|
+
*
|
|
424
|
+
* Subscribes to `scheduler.on(listener)` and forwards each event as an SSE
|
|
425
|
+
* frame (`event: <type>\ndata: <JSON>\n\n`). Sends an initial `ready` frame
|
|
426
|
+
* so the client can distinguish "connected, waiting" from "scheduler down",
|
|
427
|
+
* plus a comment heartbeat every 25s. Cleans up on client disconnect.
|
|
428
|
+
*
|
|
429
|
+
* Returns 503 when the scheduler is not attached so the client can fall back
|
|
430
|
+
* to polling. Reuses duck-typing via `daemon.getScheduler()` — no new types
|
|
431
|
+
* exported from this module.
|
|
432
|
+
*/
|
|
433
|
+
function handleSchedulesEventStream(req, res, daemon) {
|
|
434
|
+
const scheduler = daemon.getScheduler();
|
|
435
|
+
if (!scheduler) {
|
|
436
|
+
sendJson(res, 503, { error: 'Scheduler not attached' });
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
res.writeHead(200, {
|
|
440
|
+
'Content-Type': 'text/event-stream',
|
|
441
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
442
|
+
'Connection': 'keep-alive',
|
|
443
|
+
'X-Accel-Buffering': 'no',
|
|
444
|
+
});
|
|
445
|
+
// Initial frame so the client knows the channel is live even before
|
|
446
|
+
// the first scheduler event arrives (which may be minutes away).
|
|
447
|
+
res.write(`event: ready\ndata: ${JSON.stringify({ timestamp: Date.now() })}\n\n`);
|
|
448
|
+
let cleaned = false;
|
|
449
|
+
const cleanup = () => {
|
|
450
|
+
if (cleaned)
|
|
451
|
+
return;
|
|
452
|
+
cleaned = true;
|
|
453
|
+
clearInterval(heartbeat);
|
|
454
|
+
unsubscribe();
|
|
455
|
+
try {
|
|
456
|
+
res.end();
|
|
457
|
+
}
|
|
458
|
+
catch { /* already ended */ }
|
|
459
|
+
};
|
|
460
|
+
const unsubscribe = scheduler.on((event) => {
|
|
461
|
+
try {
|
|
462
|
+
res.write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// Half-open socket: the request may never emit 'close'. Defer cleanup
|
|
466
|
+
// to the next microtask so we don't splice the listeners array we are
|
|
467
|
+
// currently being iterated from inside.
|
|
468
|
+
queueMicrotask(cleanup);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
const heartbeat = setInterval(() => {
|
|
472
|
+
try {
|
|
473
|
+
res.write(`: ping ${Date.now()}\n\n`);
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
queueMicrotask(cleanup);
|
|
477
|
+
}
|
|
478
|
+
}, SSE_HEARTBEAT_MS);
|
|
479
|
+
if (typeof heartbeat.unref === 'function')
|
|
480
|
+
heartbeat.unref();
|
|
481
|
+
req.on('close', cleanup);
|
|
482
|
+
req.on('error', cleanup);
|
|
483
|
+
}
|
|
403
484
|
// ============================================================================
|
|
404
485
|
// Server lifecycle
|
|
405
486
|
// ============================================================================
|
|
@@ -479,11 +560,28 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
479
560
|
<head>
|
|
480
561
|
<meta charset="utf-8">
|
|
481
562
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
482
|
-
<title>
|
|
563
|
+
<title>The Arcane Console</title>
|
|
564
|
+
<meta name="description" content="The Arcane Console — moflo daemon, scheduled spells, and live event stream">
|
|
565
|
+
<meta property="og:title" content="The Arcane Console">
|
|
566
|
+
<meta property="og:description" content="The Arcane Console — moflo daemon, scheduled spells, and live event stream">
|
|
567
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
568
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
569
|
+
<link href="https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@700;900&display=swap" rel="stylesheet">
|
|
483
570
|
<style>
|
|
484
571
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
485
572
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; padding: 20px; }
|
|
486
|
-
|
|
573
|
+
/* Wizardy chain — Cinzel Decorative is the Google Font; the rest are
|
|
574
|
+
the most likely system serifs across macOS / Windows / Linux so the
|
|
575
|
+
title still reads "arcane" if the user is offline or behind a font-CDN
|
|
576
|
+
block (Georgia ships everywhere; serif is the universal fallback). */
|
|
577
|
+
h1 { font-family: 'Cinzel Decorative', 'Cinzel', 'Trajan Pro', 'Palatino Linotype', 'Book Antiqua', Georgia, serif; font-weight: 900; letter-spacing: 0.04em; margin-bottom: 4px; font-size: 1.85rem; }
|
|
578
|
+
/* Per-word arcane palette. Hues chosen at L≈45-50%, S≈60-70% so they read on
|
|
579
|
+
both #0d1117 (dark) and #ffffff (light) — WCAG-AA at large-text size on
|
|
580
|
+
both. Each word's shadow matches its own hue so the glow doesn't bleed
|
|
581
|
+
a single color across all three. */
|
|
582
|
+
h1 .w-the { color: #8b5cf6; text-shadow: 0 0 18px rgba(139, 92, 246, 0.18); }
|
|
583
|
+
h1 .w-arcane { color: #2563eb; text-shadow: 0 0 18px rgba(37, 99, 235, 0.18); }
|
|
584
|
+
h1 .w-console { color: #059669; text-shadow: 0 0 18px rgba(5, 150, 105, 0.18); }
|
|
487
585
|
h2 { color: #8b949e; font-size: 1.1rem; margin: 16px 0 12px; border-bottom: 1px solid #21262d; padding-bottom: 6px; }
|
|
488
586
|
.header { display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px; }
|
|
489
587
|
.subtitle { color: #8b949e; font-size: 0.85rem; }
|
|
@@ -531,13 +629,13 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
531
629
|
</head>
|
|
532
630
|
<body>
|
|
533
631
|
<div class="header">
|
|
534
|
-
<h1>
|
|
535
|
-
<span class="subtitle">
|
|
632
|
+
<h1><span class="w-the">The</span> <span class="w-arcane">Arcane</span> <span class="w-console">Console</span></h1>
|
|
633
|
+
<span class="subtitle">moflo daemon • localhost</span>
|
|
536
634
|
</div>
|
|
537
635
|
<div id="status-bar" class="status-bar"><div class="empty">Loading...</div></div>
|
|
538
636
|
<div class="nav" id="nav"></div>
|
|
539
637
|
<div id="panel-workers" class="panel"></div>
|
|
540
|
-
<div id="panel-schedules" class="panel" style="display:none"></div>
|
|
638
|
+
<div id="panel-schedules" class="panel" style="display:none"><div id="schedules-active"></div><div id="schedules-events"></div></div>
|
|
541
639
|
<div id="panel-executions" class="panel" style="display:none"></div>
|
|
542
640
|
<div id="panel-memory" class="panel" style="display:none"></div>
|
|
543
641
|
<div id="poll-indicator" class="poll-indicator"></div>
|
|
@@ -613,15 +711,25 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
613
711
|
|
|
614
712
|
function renderWorkers(s) {
|
|
615
713
|
if (!s) return;
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
714
|
+
// Disabled workers show a clear "disabled" badge and dim "—" cells
|
|
715
|
+
// instead of "idle"/"never" — those terms imply the worker is healthy
|
|
716
|
+
// but quiet, which misled users into thinking audit/predict/document
|
|
717
|
+
// were broken (#968).
|
|
718
|
+
const rows = s.workers.map(w => {
|
|
719
|
+
const statusBadge = w.enabled === false
|
|
720
|
+
? badge('disabled', 'gray')
|
|
721
|
+
: (w.isRunning ? badge('running', 'yellow') : badge('idle', 'gray'));
|
|
722
|
+
const dim = '<span class="dim">—</span>';
|
|
723
|
+
const lastRun = w.enabled === false && !w.lastRun ? dim : fmtTimeAgo(w.lastRun);
|
|
724
|
+
const nextRun = w.enabled === false ? dim : (w.nextRun ? fmtTime(w.nextRun) : '-');
|
|
725
|
+
return '<tr><td>' + esc(w.type) + '</td>' +
|
|
726
|
+
'<td>' + statusBadge + '</td>' +
|
|
727
|
+
'<td>' + w.runCount + '</td>' +
|
|
728
|
+
'<td>' + pct(w.successCount, w.failureCount) + '</td>' +
|
|
729
|
+
'<td>' + fmtDuration(w.averageDurationMs) + '</td>' +
|
|
730
|
+
'<td>' + lastRun + '</td>' +
|
|
731
|
+
'<td>' + nextRun + '</td></tr>';
|
|
732
|
+
}).join('');
|
|
625
733
|
document.getElementById('panel-workers').innerHTML =
|
|
626
734
|
'<h2>Worker Status</h2>' +
|
|
627
735
|
'<table><thead><tr><th>Worker</th><th>Status</th><th>Runs</th><th>Success</th><th>Avg</th><th>Last Run</th><th>Next Run</th></tr></thead>' +
|
|
@@ -643,7 +751,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
643
751
|
window.__schedAction = scheduleAction;
|
|
644
752
|
|
|
645
753
|
function renderSchedules(sc) {
|
|
646
|
-
const el = document.getElementById('
|
|
754
|
+
const el = document.getElementById('schedules-active');
|
|
647
755
|
if (!sc) { el.innerHTML = '<div class="empty">Loading...</div>'; return; }
|
|
648
756
|
|
|
649
757
|
if (sc.disabledInConfig) {
|
|
@@ -684,6 +792,67 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
684
792
|
if (sc.history && sc.history.length) renderSchedulesHistory(el, sc.history, /*append*/ true);
|
|
685
793
|
}
|
|
686
794
|
|
|
795
|
+
// Live events tail (Server-Sent Events from /api/schedules/events).
|
|
796
|
+
// The events div lives outside renderSchedules' write target so polled
|
|
797
|
+
// re-renders of the schedules panel don't touch it; pushSchedEvent is
|
|
798
|
+
// the only writer. Single source of truth for known event types + their
|
|
799
|
+
// badge color — adding a new schedule:* type means one entry here.
|
|
800
|
+
const SCHED_EVENT_BADGES = {
|
|
801
|
+
'schedule:due': 'gray',
|
|
802
|
+
'schedule:started': 'gray',
|
|
803
|
+
'schedule:completed': 'green',
|
|
804
|
+
'schedule:failed': 'red',
|
|
805
|
+
'schedule:skipped': 'yellow',
|
|
806
|
+
'schedule:disabled': 'yellow',
|
|
807
|
+
'schedule:catchup': 'yellow',
|
|
808
|
+
};
|
|
809
|
+
const SCHED_EVENT_TYPES = Object.keys(SCHED_EVENT_BADGES);
|
|
810
|
+
const SCHED_EVENTS_MAX = 50;
|
|
811
|
+
const schedEvents = [];
|
|
812
|
+
|
|
813
|
+
function renderEventsTail() {
|
|
814
|
+
const el = document.getElementById('schedules-events');
|
|
815
|
+
if (!el) return;
|
|
816
|
+
if (schedEvents.length === 0) {
|
|
817
|
+
el.innerHTML = '<h2>Live Events</h2><div class="empty">Waiting for scheduler activity…</div>';
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const rows = schedEvents.map(e => {
|
|
821
|
+
const t = e.type || '?';
|
|
822
|
+
const short = String(t).replace('schedule:', '');
|
|
823
|
+
return '<tr><td>' + new Date(e.timestamp || Date.now()).toLocaleTimeString() + '</td>' +
|
|
824
|
+
'<td>' + badge(short, SCHED_EVENT_BADGES[t] || 'gray') + '</td>' +
|
|
825
|
+
'<td>' + esc(e.spellName || '-') + '</td>' +
|
|
826
|
+
'<td>' + esc(e.message || '') + '</td></tr>';
|
|
827
|
+
}).join('');
|
|
828
|
+
el.innerHTML = '<h2>Live Events</h2>' +
|
|
829
|
+
'<table><thead><tr><th>Time</th><th>Event</th><th>Spell</th><th>Detail</th></tr></thead>' +
|
|
830
|
+
'<tbody>' + rows + '</tbody></table>';
|
|
831
|
+
}
|
|
832
|
+
function pushSchedEvent(ev) {
|
|
833
|
+
schedEvents.unshift(ev);
|
|
834
|
+
if (schedEvents.length > SCHED_EVENTS_MAX) schedEvents.length = SCHED_EVENTS_MAX;
|
|
835
|
+
renderEventsTail();
|
|
836
|
+
}
|
|
837
|
+
renderEventsTail(); // Initial empty-state paint.
|
|
838
|
+
|
|
839
|
+
// Subscribe via EventSource. Browser handles auto-reconnect with backoff.
|
|
840
|
+
let evtSource = null;
|
|
841
|
+
function connectEventStream() {
|
|
842
|
+
try { if (evtSource) evtSource.close(); } catch (e) { /* ignore */ }
|
|
843
|
+
try {
|
|
844
|
+
evtSource = new EventSource('/api/schedules/events');
|
|
845
|
+
SCHED_EVENT_TYPES.forEach(t => {
|
|
846
|
+
evtSource.addEventListener(t, ev => {
|
|
847
|
+
try { pushSchedEvent(JSON.parse(ev.data)); } catch (e) { /* malformed frame */ }
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
} catch (e) {
|
|
851
|
+
console.warn('Event stream unavailable:', e);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
connectEventStream();
|
|
855
|
+
|
|
687
856
|
function renderSchedulesHistory(el, history, append) {
|
|
688
857
|
const rows = history.map(h => {
|
|
689
858
|
const statusBadge = h.success === true ? badge('pass','green')
|
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Daemon Readiness Check for Scheduled Spells
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Confirms the daemon is currently running so a freshly-created schedule
|
|
5
|
+
* actually fires. Prompts the user to start it interactively, or warns
|
|
6
|
+
* non-interactively. Always returns regardless of state — the caller still
|
|
7
|
+
* writes the schedule, and the daemon picks it up on next start.
|
|
8
|
+
*
|
|
9
|
+
* OS-autostart install/uninstall is no longer handled here — see
|
|
10
|
+
* `daemon-autostart-lifecycle.ts`. That side effect is now driven by the
|
|
11
|
+
* count of enabled schedules, not a per-create prompt.
|
|
8
12
|
*/
|
|
9
13
|
import { join, resolve } from 'path';
|
|
10
14
|
import { mkdirSync, openSync, closeSync } from 'fs';
|
|
11
15
|
import { spawn } from 'child_process';
|
|
12
16
|
import { getDaemonLockHolder } from './daemon-lock.js';
|
|
13
|
-
import { isDaemonInstalled
|
|
17
|
+
import { isDaemonInstalled } from './daemon-service.js';
|
|
14
18
|
import { locateMofloCliBin } from './moflo-require.js';
|
|
15
19
|
import { registerBackgroundPid } from './process-registry.js';
|
|
16
20
|
/**
|
|
17
21
|
* Ensure the daemon is ready for scheduled spell execution.
|
|
18
22
|
*
|
|
19
|
-
* Checks daemon state and prompts the user to start
|
|
20
|
-
*
|
|
21
|
-
*
|
|
23
|
+
* Checks daemon state and prompts the user to start it as needed. Always
|
|
24
|
+
* returns — never throws. The caller should create the schedule regardless
|
|
25
|
+
* of the result, since the daemon can pick it up later. OS-autostart is
|
|
26
|
+
* reconciled separately by the create/cancel paths via
|
|
27
|
+
* `reconcileDaemonAutostart`.
|
|
22
28
|
*/
|
|
23
29
|
export async function ensureDaemonForScheduling(options) {
|
|
24
30
|
const resolvedRoot = resolve(options.projectRoot);
|
|
25
31
|
const promptFn = options.promptConfirm ?? defaultPromptConfirm;
|
|
26
32
|
const startFn = options.startDaemon ?? defaultStartDaemon;
|
|
33
|
+
const installedFn = options.isDaemonInstalledFn ?? isDaemonInstalled;
|
|
27
34
|
const result = {
|
|
28
35
|
daemonRunning: false,
|
|
29
36
|
daemonInstalled: false,
|
|
@@ -50,29 +57,10 @@ export async function ensureDaemonForScheduling(options) {
|
|
|
50
57
|
result.warnings.push('Daemon is not running. Start it with: moflo daemon start');
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
result.daemonInstalled = isDaemonInstalled(resolvedRoot);
|
|
58
|
-
if (!result.daemonInstalled) {
|
|
59
|
-
if (options.interactive && result.daemonRunning) {
|
|
60
|
-
const shouldInstall = await promptFn('Register the daemon as a login service so schedules survive reboots?');
|
|
61
|
-
if (shouldInstall) {
|
|
62
|
-
const installResult = installDaemonService(resolvedRoot);
|
|
63
|
-
result.daemonInstalled = installResult.success;
|
|
64
|
-
if (!installResult.success) {
|
|
65
|
-
result.warnings.push(`Failed to install daemon service: ${installResult.message}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
result.warnings.push("Daemon is not set to autostart. Run 'moflo daemon install' so this schedule survives reboot.");
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
result.warnings.push("Daemon is not set to autostart. Run 'moflo daemon install' so this schedule survives reboot.");
|
|
74
|
-
}
|
|
75
|
-
}
|
|
60
|
+
// Surface OS install state purely informationally — no prompts. The
|
|
61
|
+
// create/cancel commands reconcile install/uninstall via
|
|
62
|
+
// reconcileDaemonAutostart, driven by the enabled-schedule count.
|
|
63
|
+
result.daemonInstalled = installedFn(resolvedRoot);
|
|
76
64
|
return result;
|
|
77
65
|
}
|
|
78
66
|
async function defaultPromptConfirm(message) {
|
|
@@ -1,49 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Idempotent
|
|
2
|
+
* Idempotent session-start memory cleanup for moflo's memory DB
|
|
3
|
+
* (`.moflo/moflo.db`).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* they store internal moflo run-tracking — not user knowledge — and embedding
|
|
6
|
-
* them polluted the search index:
|
|
5
|
+
* Two passes run in a single sql.js open:
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - `test-bridge-fix` (single-row leftover from a one-off test)
|
|
7
|
+
* 1. **Hard-purge** namespaces in {@link PURGE_ON_SESSION_START_NAMESPACES} —
|
|
8
|
+
* `hive-mind`, `epic-state`, `test-bridge-fix`. These store internal
|
|
9
|
+
* run-tracking that does not need to survive a session restart. (#729)
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* 2. **Retention trim** the `tasklist` namespace down to the most recent
|
|
12
|
+
* {@link TASKLIST_RETENTION_CAP} rows. `tasklist` is the dashboard's
|
|
13
|
+
* "Flo Runs" tab data source (`daemon-dashboard.ts handleSpells`); the
|
|
14
|
+
* pre-#968 contract hard-purged it on every session start, leaving the tab
|
|
15
|
+
* permanently empty. Trim instead so users see recent history without
|
|
16
|
+
* unbounded growth.
|
|
17
|
+
*
|
|
18
|
+
* Both passes share the file open + final VACUUM + atomic write, so disk I/O
|
|
19
|
+
* is the same as before. Writes back to disk only when something changed.
|
|
17
20
|
*
|
|
18
21
|
* Lives in `services/` so it has no dependency on the CLI command machinery.
|
|
19
|
-
* That lets `bin/session-start-launcher.mjs` dynamic-import it and run
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
* That lets `bin/session-start-launcher.mjs` dynamic-import it and run in
|
|
23
|
+
* foreground BEFORE long-lived sql.js consumers (MCP server, daemon) open
|
|
24
|
+
* the DB — sql.js dumps the whole snapshot on every flush and would
|
|
22
25
|
* otherwise clobber our cleanup (see #727's clobber-hazard analysis).
|
|
23
26
|
*
|
|
24
27
|
* @module cli/services/ephemeral-namespace-purge
|
|
25
28
|
*/
|
|
26
29
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
27
|
-
import {
|
|
30
|
+
import { PURGE_ON_SESSION_START_NAMESPACES, TASKLIST_RETENTION_CAP, } from '../memory/bridge-embedder.js';
|
|
28
31
|
import { mofloImport } from './moflo-require.js';
|
|
29
32
|
import { atomicWriteFileSync } from './atomic-file-write.js';
|
|
30
33
|
import { memoryDbPath } from './moflo-paths.js';
|
|
31
34
|
/**
|
|
32
|
-
* Hard-delete
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* purge never blocks
|
|
35
|
+
* Hard-delete rows in {@link PURGE_ON_SESSION_START_NAMESPACES} and trim the
|
|
36
|
+
* `tasklist` namespace to its retention cap, then VACUUM. Returns
|
|
37
|
+
* `{ purged: 0, trimmed: 0 }` on the happy path: no DB, sql.js unavailable,
|
|
38
|
+
* schema lacks `memory_entries`, or nothing to clean. Errors propagate to
|
|
39
|
+
* the caller (the launcher absorbs them so a failed purge never blocks
|
|
40
|
+
* session start).
|
|
37
41
|
*/
|
|
38
42
|
export async function purgeEphemeralNamespaces(options = {}) {
|
|
39
43
|
const fs = await import('fs');
|
|
40
44
|
const path = await import('path');
|
|
41
45
|
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(process.cwd()));
|
|
42
46
|
if (!fs.existsSync(dbPath))
|
|
43
|
-
return { purged: 0 };
|
|
47
|
+
return { purged: 0, trimmed: 0 };
|
|
44
48
|
const initSqlJs = (await mofloImport('sql.js'))?.default;
|
|
45
49
|
if (!initSqlJs)
|
|
46
|
-
return { purged: 0 };
|
|
50
|
+
return { purged: 0, trimmed: 0 };
|
|
47
51
|
const SQL = await initSqlJs();
|
|
48
52
|
const buffer = fs.readFileSync(dbPath);
|
|
49
53
|
const db = new SQL.Database(buffer);
|
|
@@ -52,21 +56,45 @@ export async function purgeEphemeralNamespaces(options = {}) {
|
|
|
52
56
|
// a no-op so we don't VACUUM unrelated SQLite files.
|
|
53
57
|
const probe = db.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name='memory_entries' LIMIT 1`);
|
|
54
58
|
if (!probe[0]?.values?.[0])
|
|
55
|
-
return { purged: 0 };
|
|
56
|
-
|
|
59
|
+
return { purged: 0, trimmed: 0 };
|
|
60
|
+
// Single COUNT pass to gate both DELETEs — a clean DB is the steady
|
|
61
|
+
// state and we don't want two no-op DELETEs (with their query-planner
|
|
62
|
+
// overhead) on every session start.
|
|
63
|
+
const namespaces = Array.from(PURGE_ON_SESSION_START_NAMESPACES);
|
|
64
|
+
const cap = options.tasklistRetentionCap ?? TASKLIST_RETENTION_CAP;
|
|
57
65
|
const placeholders = namespaces.map(() => '?').join(', ');
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
const countRows = db.exec(`SELECT
|
|
67
|
+
(SELECT COUNT(*) FROM memory_entries WHERE namespace IN (${placeholders})) AS purgeable,
|
|
68
|
+
(SELECT COUNT(*) FROM memory_entries WHERE namespace = 'tasklist') AS tasklistTotal`, namespaces);
|
|
69
|
+
const counts = countRows[0]?.values?.[0] ?? [0, 0];
|
|
70
|
+
const purgeable = Number(counts[0] ?? 0);
|
|
71
|
+
const tasklistTotal = Number(counts[1] ?? 0);
|
|
72
|
+
let purged = 0;
|
|
73
|
+
if (purgeable > 0) {
|
|
74
|
+
db.run(`DELETE FROM memory_entries WHERE namespace IN (${placeholders})`, namespaces);
|
|
75
|
+
purged = db.getRowsModified?.() ?? 0;
|
|
76
|
+
}
|
|
77
|
+
let trimmed = 0;
|
|
78
|
+
if (tasklistTotal > cap) {
|
|
79
|
+
// Keep the newest `cap` rows by created_at, falling back to `id DESC`
|
|
80
|
+
// for legacy rows that predate the created_at-not-null schema (#728-era).
|
|
81
|
+
db.run(`DELETE FROM memory_entries
|
|
82
|
+
WHERE namespace = 'tasklist'
|
|
83
|
+
AND id NOT IN (
|
|
84
|
+
SELECT id FROM memory_entries
|
|
85
|
+
WHERE namespace = 'tasklist'
|
|
86
|
+
ORDER BY created_at DESC, id DESC
|
|
87
|
+
LIMIT ?
|
|
88
|
+
)`, [cap]);
|
|
89
|
+
trimmed = db.getRowsModified?.() ?? 0;
|
|
90
|
+
}
|
|
91
|
+
if (purged === 0 && trimmed === 0)
|
|
92
|
+
return { purged: 0, trimmed: 0 };
|
|
65
93
|
// VACUUM has to run outside any open transaction; sql.js auto-commits
|
|
66
94
|
// each `db.run`, so this is safe to chain.
|
|
67
95
|
db.run('VACUUM');
|
|
68
96
|
atomicWriteFileSync(dbPath, db.export());
|
|
69
|
-
return { purged };
|
|
97
|
+
return { purged, trimmed };
|
|
70
98
|
}
|
|
71
99
|
finally {
|
|
72
100
|
db.close();
|
|
@@ -2,21 +2,14 @@
|
|
|
2
2
|
* Headless Worker Executor
|
|
3
3
|
* Enables workers to invoke Claude Code in headless mode with configurable sandbox profiles.
|
|
4
4
|
*
|
|
5
|
-
* ADR-020: Headless Worker Integration Architecture
|
|
5
|
+
* ADR-020: Headless Worker Integration Architecture (#970 dropped the
|
|
6
|
+
* `audit`/`document`/`predict` workers — those entries from the original
|
|
7
|
+
* ADR are superseded; the rest still applies).
|
|
6
8
|
* - Integrates with CLAUDE_CODE_HEADLESS and CLAUDE_CODE_SANDBOX_MODE environment variables
|
|
7
9
|
* - Provides process pool for concurrent execution
|
|
8
10
|
* - Builds context from file glob patterns
|
|
9
11
|
* - Supports prompt templates and output parsing
|
|
10
12
|
* - Implements timeout and graceful error handling
|
|
11
|
-
*
|
|
12
|
-
* Key Features:
|
|
13
|
-
* - Process pool with configurable maxConcurrent
|
|
14
|
-
* - Context building from file glob patterns with caching
|
|
15
|
-
* - Prompt template system with context injection
|
|
16
|
-
* - Output parsing (text, json, markdown)
|
|
17
|
-
* - Timeout handling with graceful termination
|
|
18
|
-
* - Execution logging for debugging
|
|
19
|
-
* - Event emission for monitoring
|
|
20
13
|
*/
|
|
21
14
|
import { spawn, execSync } from 'child_process';
|
|
22
15
|
import { EventEmitter } from 'events';
|
|
@@ -27,17 +20,14 @@ import { errorDetail } from '../shared/utils/error-detail.js';
|
|
|
27
20
|
// Constants
|
|
28
21
|
// ============================================
|
|
29
22
|
/**
|
|
30
|
-
* Array of headless worker types for runtime checking
|
|
23
|
+
* Array of headless worker types for runtime checking.
|
|
31
24
|
*/
|
|
32
25
|
export const HEADLESS_WORKER_TYPES = [
|
|
33
|
-
'audit',
|
|
34
26
|
'optimize',
|
|
35
27
|
'testgaps',
|
|
36
|
-
'document',
|
|
37
28
|
'ultralearn',
|
|
38
29
|
'refactor',
|
|
39
30
|
'deepdive',
|
|
40
|
-
'predict',
|
|
41
31
|
];
|
|
42
32
|
/**
|
|
43
33
|
* Array of local worker types
|
|
@@ -57,37 +47,11 @@ const MODEL_IDS = {
|
|
|
57
47
|
haiku: 'claude-haiku-4-5-20251001',
|
|
58
48
|
};
|
|
59
49
|
/**
|
|
60
|
-
* Default headless worker configurations based on ADR-020
|
|
50
|
+
* Default headless worker configurations based on ADR-020 (the
|
|
51
|
+
* `audit`/`document`/`predict` entries from the original ADR were dropped
|
|
52
|
+
* in #970 — see worker-daemon.ts header for rationale).
|
|
61
53
|
*/
|
|
62
54
|
export const HEADLESS_WORKER_CONFIGS = {
|
|
63
|
-
audit: {
|
|
64
|
-
type: 'audit',
|
|
65
|
-
mode: 'headless',
|
|
66
|
-
intervalMs: 30 * 60 * 1000,
|
|
67
|
-
priority: 'critical',
|
|
68
|
-
description: 'AI-powered security analysis',
|
|
69
|
-
enabled: true,
|
|
70
|
-
headless: {
|
|
71
|
-
promptTemplate: `Analyze this codebase for security vulnerabilities:
|
|
72
|
-
- Check for hardcoded secrets (API keys, passwords)
|
|
73
|
-
- Identify SQL injection risks
|
|
74
|
-
- Find XSS vulnerabilities
|
|
75
|
-
- Check for insecure dependencies
|
|
76
|
-
- Identify authentication/authorization issues
|
|
77
|
-
|
|
78
|
-
Provide a JSON report with:
|
|
79
|
-
{
|
|
80
|
-
"vulnerabilities": [{ "severity": "high|medium|low", "file": "...", "line": N, "description": "..." }],
|
|
81
|
-
"riskScore": 0-100,
|
|
82
|
-
"recommendations": ["..."]
|
|
83
|
-
}`,
|
|
84
|
-
sandbox: 'strict',
|
|
85
|
-
model: 'haiku',
|
|
86
|
-
outputFormat: 'json',
|
|
87
|
-
contextPatterns: ['**/*.ts', '**/*.js', '**/.env*', '**/package.json'],
|
|
88
|
-
timeoutMs: 5 * 60 * 1000,
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
55
|
optimize: {
|
|
92
56
|
type: 'optimize',
|
|
93
57
|
mode: 'headless',
|
|
@@ -134,29 +98,6 @@ For each gap, provide a test skeleton.`,
|
|
|
134
98
|
timeoutMs: 10 * 60 * 1000,
|
|
135
99
|
},
|
|
136
100
|
},
|
|
137
|
-
document: {
|
|
138
|
-
type: 'document',
|
|
139
|
-
mode: 'headless',
|
|
140
|
-
intervalMs: 120 * 60 * 1000,
|
|
141
|
-
priority: 'low',
|
|
142
|
-
description: 'AI documentation generation',
|
|
143
|
-
enabled: false,
|
|
144
|
-
headless: {
|
|
145
|
-
promptTemplate: `Generate documentation for undocumented code:
|
|
146
|
-
- Add JSDoc comments to functions
|
|
147
|
-
- Create README sections for modules
|
|
148
|
-
- Document API endpoints
|
|
149
|
-
- Add inline comments for complex logic
|
|
150
|
-
- Generate usage examples
|
|
151
|
-
|
|
152
|
-
Focus on public APIs and exported functions.`,
|
|
153
|
-
sandbox: 'permissive',
|
|
154
|
-
model: 'haiku',
|
|
155
|
-
outputFormat: 'markdown',
|
|
156
|
-
contextPatterns: ['src/**/*.ts'],
|
|
157
|
-
timeoutMs: 10 * 60 * 1000,
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
101
|
ultralearn: {
|
|
161
102
|
type: 'ultralearn',
|
|
162
103
|
mode: 'headless',
|
|
@@ -232,34 +173,6 @@ Provide comprehensive report.`,
|
|
|
232
173
|
timeoutMs: 15 * 60 * 1000,
|
|
233
174
|
},
|
|
234
175
|
},
|
|
235
|
-
predict: {
|
|
236
|
-
type: 'predict',
|
|
237
|
-
mode: 'headless',
|
|
238
|
-
intervalMs: 10 * 60 * 1000,
|
|
239
|
-
priority: 'low',
|
|
240
|
-
description: 'Predictive preloading',
|
|
241
|
-
enabled: false,
|
|
242
|
-
headless: {
|
|
243
|
-
promptTemplate: `Based on recent activity, predict what the developer needs:
|
|
244
|
-
- Files likely to be edited next
|
|
245
|
-
- Tests that should be run
|
|
246
|
-
- Documentation to reference
|
|
247
|
-
- Dependencies to check
|
|
248
|
-
|
|
249
|
-
Provide preload suggestions as JSON:
|
|
250
|
-
{
|
|
251
|
-
"filesToPreload": ["..."],
|
|
252
|
-
"testsToRun": ["..."],
|
|
253
|
-
"docsToReference": ["..."],
|
|
254
|
-
"confidence": 0.0-1.0
|
|
255
|
-
}`,
|
|
256
|
-
sandbox: 'strict',
|
|
257
|
-
model: 'haiku',
|
|
258
|
-
outputFormat: 'json',
|
|
259
|
-
contextPatterns: ['.moflo/metrics/*.json'],
|
|
260
|
-
timeoutMs: 2 * 60 * 1000,
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
176
|
};
|
|
264
177
|
/**
|
|
265
178
|
* Local worker configurations
|
|
@@ -79,6 +79,10 @@ export function getReferenceHookBlock() {
|
|
|
79
79
|
{ matcher: '^mcp__moflo__memory_(search|retrieve|list|stats|store)$', hooks: [gateHook('record-memory-searched', 3000)] },
|
|
80
80
|
{ matcher: '^TaskUpdate$', hooks: [gateCjs('check-task-transition', 2000)] },
|
|
81
81
|
{ matcher: '^mcp__moflo__memory_store$', hooks: [gateCjs('record-learnings-stored', 2000)] },
|
|
82
|
+
// #952 — wired so /fl -s/--swarm and /fl -h/--hive runs satisfy the
|
|
83
|
+
// check-before-agent gate after the protected MCP init has been called.
|
|
84
|
+
{ matcher: '^mcp__moflo__swarm_init$', hooks: [gateCjs('record-swarm-init', 2000)] },
|
|
85
|
+
{ matcher: '^mcp__moflo__hive-mind_init$', hooks: [gateCjs('record-hive-init', 2000)] },
|
|
82
86
|
],
|
|
83
87
|
UserPromptSubmit: [
|
|
84
88
|
{ hooks: [helperHook('prompt-hook.mjs', '', 3000)] },
|