moflo 4.10.10 → 4.10.11

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 CHANGED
@@ -419,7 +419,7 @@ flo daemon status # shows whether the service is registered AND running
419
419
 
420
420
  `flo spell schedule create` warns when the daemon isn't installed so you don't quietly miss runs.
421
421
 
422
- **Monitoring.** **The Luminarium** (the moflo daemon's localhost UI) surfaces live schedules, recent executions, and per-schedule controls (disable / re-enable / run now). It starts alongside the daemon at `http://localhost:3117` (override with `--dashboard-port` or disable with `--no-dashboard`).
422
+ **Monitoring.** **[The Luminarium](#the-luminarium)** moflo's localhost daemon dashboard — surfaces live schedules, recent executions, and per-schedule controls (disable / re-enable / run now), alongside worker health, memory stats, and Claude Code session stats. Each project gets its own deterministic port (33000–33999) recorded in `.moflo/daemon.lock`; ask `/luminarium` in your Claude session and it'll print the link.
423
423
 
424
424
  For full configuration (`scheduler:` block in `moflo.yaml`), event types, and the catch-up window after restarts, see [docs/SPELLS.md#scheduling](docs/SPELLS.md#scheduling).
425
425
 
@@ -459,6 +459,35 @@ flo epic reset 42 # Reset state for re-run
459
459
 
460
460
  See the [Epic handling](#epic-handling) section above for detection criteria and the comparison between `/flo <epic>` and `flo epic run`.
461
461
 
462
+ ## The Luminarium
463
+
464
+ The Luminarium is moflo's localhost daemon dashboard. It boots automatically with the background daemon (no extra service to install) and stays running as long as the daemon is up.
465
+
466
+ ### Finding the URL
467
+
468
+ Each project gets a deterministic port in the range 33000–33999, derived from a hash of the project root so two projects never collide on the same machine. The actual bound port is written to `.moflo/daemon.lock` when the daemon starts — if the deterministic port is already taken the daemon scans forward, so the lock file is the source of truth, not the hash.
469
+
470
+ Three ways to get the URL:
471
+
472
+ - **`/luminarium`** — inside a Claude Code session in a moflo project, this skill reads `.moflo/daemon.lock` and prints `http://localhost:<port>`. Fastest path.
473
+ - **`flo daemon status`** — prints the URL alongside the health summary.
474
+ - **`cat .moflo/daemon.lock`** — read the JSON directly: `{ "pid": ..., "port": 33421, ... }`.
475
+
476
+ ### What it shows
477
+
478
+ | Tab | What you see |
479
+ |-----|--------------|
480
+ | **Workers** | Live agent processes the daemon is running (statusline updater, indexer, embedder, etc.) |
481
+ | **Schedules** | All registered spell schedules (cron / interval / one-time), with run-now and disable controls |
482
+ | **Executions** | Recent spell runs — duration, exit code, step-by-step output |
483
+ | **Memory** | Memory namespace breakdown, vector count, embedder backend, HNSW index health |
484
+ | **Claude Stats** | Per-session Claude Code transcript stats — tokens, tools called, files touched (local primary sessions only) |
485
+
486
+ ### Flags
487
+
488
+ - `flo daemon start --no-dashboard` — disable the HTTP server entirely (the daemon itself still runs)
489
+ - `flo daemon start --dashboard-port <N>` — pin to a specific port, overriding the deterministic resolver. Also accepts the `MOFLO_DAEMON_PORT` env var, which the rest of moflo respects when talking to the daemon
490
+
462
491
  ## Commands
463
492
 
464
493
  You don't need to run these for normal use — `flo init` sets everything up, and the hooks handle memory, routing, and learning automatically. These commands are here for manual setup, debugging, and tweaking.
@@ -26,6 +26,7 @@ import { existsSync } from 'fs';
26
26
  import { errorDetail } from '../shared/utils/error-detail.js';
27
27
  import { memoryDbPath } from '../services/moflo-paths.js';
28
28
  import { findProjectRoot } from '../services/project-root.js';
29
+ import { purgeMemoryProbeNamespaces } from '../services/ephemeral-namespace-purge.js';
29
30
  import { loadToolArrays, getTool, pushDetail, summarizeFunctional, } from './doctor-checks-functional-shared.js';
30
31
  const MEMORY_ACCESS_CHECK = 'Memory Access Functional';
31
32
  const MEMORY_ACCESS_FAIL_FIX = 'Run `flo doctor --json` for per-subcheck details. Common fixes: ensure fastembed installed (memory_store.hasEmbedding=false), explicit threshold:0 honored (#837), or rebuild HNSW index (`flo memory rebuild-index`)';
@@ -563,6 +564,12 @@ export async function checkMemoryAccessFunctional() {
563
564
  }
564
565
  catch { /* ignore */ }
565
566
  }
567
+ // #1166 — namespace-level sweep backstop for the per-key safeDelete
568
+ // loop above (see purgeMemoryProbeNamespaces docstring for why).
569
+ try {
570
+ await purgeMemoryProbeNamespaces({ dbPath: memoryDbPath(findProjectRoot()) });
571
+ }
572
+ catch { /* best-effort */ }
566
573
  }
567
574
  return summarizeFunctional(MEMORY_ACCESS_CHECK, details, {
568
575
  passSuffix: '(memory_store + memory_search round-trip verified across subagent, swarm-agent, and hive-mind contexts)',
@@ -116,13 +116,16 @@ export const PURGE_ON_SESSION_START_NAMESPACES = new Set([
116
116
  * spawns a NEW namespace, so namespace pollution grows linearly with
117
117
  * healer-run count if cleanup races fail.
118
118
  *
119
- * Both probes register an explicit cleanup via `safeDelete`, but the
120
- * cleanup is best-effort and silently swallows failures (e.g. daemon
121
- * races, MCP transport errors) so rows accumulate across consumer
122
- * sessions. Auto-purging matches the pattern for
123
- * `hive-mind`/`epic-state`/`test-bridge-fix`. These rows MUST still get
124
- * embeddings (see {@link EPHEMERAL_NAMESPACE_PREFIXES} for why) only
125
- * their persistence across sessions is curtailed.
119
+ * Both probes register an explicit cleanup via `safeDelete`. Post-#1166
120
+ * the doctor also runs an in-process namespace sweep over these prefixes
121
+ * inside `checkMemoryAccessFunctional`'s finally block, so a healthy
122
+ * doctor run leaves zero rows behind. The session-start launcher's
123
+ * prefix-purge is now strictly a safety net for crashed-process residue
124
+ * (the doctor never reached its finally) or pre-#1166 consumer DBs that
125
+ * still carry accumulated probe rows. Auto-purging matches the pattern
126
+ * for `hive-mind`/`epic-state`/`test-bridge-fix`. These rows MUST still
127
+ * get embeddings (see {@link EPHEMERAL_NAMESPACE_PREFIXES} for why) —
128
+ * only their persistence across sessions is curtailed.
126
129
  */
127
130
  export const PURGE_ON_SESSION_START_PREFIXES = new Set([
128
131
  'doctor-memprobe-',
@@ -105,4 +105,51 @@ export async function purgeEphemeralNamespaces(options = {}) {
105
105
  db.close();
106
106
  }
107
107
  }
108
+ /**
109
+ * Hard-delete rows whose namespace matches one of
110
+ * {@link PURGE_ON_SESSION_START_PREFIXES} — currently `doctor-memprobe-*`
111
+ * and `doctor-neighbors-*`. Scoped down from {@link purgeEphemeralNamespaces}:
112
+ * no exact-namespace pass, no tasklist trim, no VACUUM. Returns
113
+ * `{ purged: 0 }` on a missing DB / missing `memory_entries` / clean state.
114
+ *
115
+ * Intended for the doctor's Memory Access functional check finally block
116
+ * (#1166). Only the doctor writes to these namespaces in production, so
117
+ * sweeping by prefix at the end of every healer run kills the
118
+ * `populated:ephemeral-purged` flake class — a per-key `safeDelete` that
119
+ * silently no-ops (row not visible at delete time, MCP transport error,
120
+ * `memory_delete` returning `success: true, deleted: false`) no longer
121
+ * leaks a row into the next assertion. The launcher's session-start
122
+ * purge stays in place as a defence-in-depth safety net for residue from
123
+ * crashed-process scenarios where the doctor never reached its finally.
124
+ *
125
+ * Errors propagate to the caller (the doctor absorbs them so a failed
126
+ * sweep never poisons the check return value).
127
+ */
128
+ export async function purgeMemoryProbeNamespaces(options = {}) {
129
+ const fs = await import('fs');
130
+ const path = await import('path');
131
+ const dbPath = path.resolve(options.dbPath ?? memoryDbPath(process.cwd()));
132
+ if (!fs.existsSync(dbPath))
133
+ return { purged: 0 };
134
+ const prefixes = Array.from(PURGE_ON_SESSION_START_PREFIXES);
135
+ if (prefixes.length === 0)
136
+ return { purged: 0 };
137
+ const db = openDaemonDatabase(dbPath);
138
+ try {
139
+ const probe = db.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name='memory_entries' LIMIT 1`);
140
+ if (!probe[0]?.values?.[0])
141
+ return { purged: 0 };
142
+ const whereClause = prefixes.map(() => 'namespace LIKE ?').join(' OR ');
143
+ const bindings = prefixes.map((p) => `${p}%`);
144
+ const countRows = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE ${whereClause}`, bindings);
145
+ const purgeable = Number(countRows[0]?.values?.[0]?.[0] ?? 0);
146
+ if (purgeable === 0)
147
+ return { purged: 0 };
148
+ db.run(`DELETE FROM memory_entries WHERE ${whereClause}`, bindings);
149
+ return { purged: db.getRowsModified?.() ?? 0 };
150
+ }
151
+ finally {
152
+ db.close();
153
+ }
154
+ }
108
155
  //# sourceMappingURL=ephemeral-namespace-purge.js.map
@@ -2,5 +2,5 @@
2
2
  * Auto-generated by build. Do not edit manually.
3
3
  * Source of truth: root package.json → scripts/sync-version.mjs
4
4
  */
5
- export const VERSION = '4.10.10';
5
+ export const VERSION = '4.10.11';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moflo",
3
- "version": "4.10.10",
3
+ "version": "4.10.11",
4
4
  "description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
5
5
  "main": "dist/src/cli/index.js",
6
6
  "type": "module",
@@ -95,7 +95,7 @@
95
95
  "@typescript-eslint/eslint-plugin": "^7.18.0",
96
96
  "@typescript-eslint/parser": "^7.18.0",
97
97
  "eslint": "^8.0.0",
98
- "moflo": "^4.10.9",
98
+ "moflo": "^4.10.10",
99
99
  "tsx": "^4.21.0",
100
100
  "typescript": "^5.9.3",
101
101
  "vitest": "^4.0.0"