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
|
|
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
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
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
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.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.
|
|
98
|
+
"moflo": "^4.10.10",
|
|
99
99
|
"tsx": "^4.21.0",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
101
|
"vitest": "^4.0.0"
|