monomind 2.0.3 → 2.1.3
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 +74 -92
- package/package.json +11 -7
- package/packages/@monomind/cli/.claude/helpers/control-start.cjs +13 -20
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +109 -6
- package/packages/@monomind/cli/.claude/helpers/graphify-freshen.cjs +19 -17
- package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +60 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +99 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +27 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +18 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/loops-status-handler.cjs +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +33 -4
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +75 -45
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +0 -12
- package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +53 -22
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +75 -4
- package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +94 -15
- package/packages/@monomind/cli/.claude/helpers/session.cjs +6 -1
- package/packages/@monomind/cli/.claude/helpers/statusline.cjs +10 -10
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +3 -3
- package/packages/@monomind/cli/.claude/helpers/utils/agent-registrations.cjs +41 -0
- package/packages/@monomind/cli/.claude/helpers/utils/fs-helpers.cjs +183 -0
- package/packages/@monomind/cli/.claude/helpers/utils/micro-agents.cjs +10 -6
- package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +73 -2
- package/packages/@monomind/cli/.claude/helpers/utils/telemetry.cjs +35 -19
- package/packages/@monomind/cli/README.md +74 -92
- package/packages/@monomind/cli/bin/cli.js +10 -1
- package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +9 -15
- package/packages/@monomind/cli/dist/src/commands/browse.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +20 -4
- package/packages/@monomind/cli/dist/src/commands/completions.js +66 -261
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +5 -0
- package/packages/@monomind/cli/dist/src/commands/doctor.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +7 -0
- package/packages/@monomind/cli/dist/src/commands/index.js +3 -0
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -6
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +12 -6
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +6 -13
- package/packages/@monomind/cli/dist/src/commands/monograph.js +3 -3
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/commands/org.js +321 -127
- package/packages/@monomind/cli/dist/src/commands/platforms.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/start.js +142 -37
- package/packages/@monomind/cli/dist/src/commands/tokens.js +21 -3
- package/packages/@monomind/cli/dist/src/config-adapter.js +37 -17
- package/packages/@monomind/cli/dist/src/index.js +64 -28
- package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +44 -11
- package/packages/@monomind/cli/dist/src/init/executor.js +93 -55
- package/packages/@monomind/cli/dist/src/mcp-client.d.ts +8 -3
- package/packages/@monomind/cli/dist/src/mcp-client.js +37 -3
- package/packages/@monomind/cli/dist/src/mcp-server.js +8 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +10 -5
- package/packages/@monomind/cli/dist/src/mcp-tools/auto-install.d.ts +8 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +31 -31
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +30 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +56 -35
- package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +9 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +56 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +88 -41
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +36 -36
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +6 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/types.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/types.js +36 -1
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +13 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +66 -14
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +99 -3
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +20 -0
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +73 -0
- package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +46 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +201 -18
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +10 -5
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +138 -8
- package/packages/@monomind/cli/dist/src/orgrt/inbox.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/policy.js +58 -5
- package/packages/@monomind/cli/dist/src/orgrt/provider.js +1 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.d.ts +13 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.js +48 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.js +39 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +10 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.js +24 -1
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +108 -0
- package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +52 -52
- package/packages/@monomind/cli/dist/src/output.d.ts +29 -29
- package/packages/@monomind/cli/dist/src/output.js +22 -7
- package/packages/@monomind/cli/dist/src/parser.d.ts +32 -0
- package/packages/@monomind/cli/dist/src/parser.js +130 -5
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +13 -1
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +31 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +21 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +60 -7
- package/packages/@monomind/cli/dist/src/services/crash-reporter.js +2 -1
- package/packages/@monomind/cli/dist/src/types.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +243 -49
- package/packages/@monomind/cli/dist/src/ui/orgs-files.js +192 -0
- package/packages/@monomind/cli/dist/src/ui/orgs.html +79 -11
- package/packages/@monomind/cli/dist/src/ui/server.mjs +346 -139
- package/packages/@monomind/cli/dist/src/utils/input-guards.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/utils/input-guards.js +39 -5
- package/packages/@monomind/cli/package.json +15 -15
|
@@ -24,14 +24,59 @@ async function getBridge() {
|
|
|
24
24
|
}
|
|
25
25
|
let hnswIndex = null;
|
|
26
26
|
let hnswInitializing = false;
|
|
27
|
+
/** Lazily import the pure-JS HNSWIndex class from the optional @monoes/memory package. */
|
|
28
|
+
async function loadHNSWIndexClass() {
|
|
29
|
+
try {
|
|
30
|
+
const mod = await import('@monoes/memory');
|
|
31
|
+
return mod?.HNSWIndex ?? null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Read entries with parseable embeddings of the expected dimension from the sql.js DB. */
|
|
38
|
+
async function loadEntriesFromDb(dbPath, dimensions) {
|
|
39
|
+
if (!fs.existsSync(dbPath))
|
|
40
|
+
return [];
|
|
41
|
+
const initSqlJs = (await import('sql.js')).default;
|
|
42
|
+
const SQL = await initSqlJs();
|
|
43
|
+
const fileBuffer = fs.readFileSync(dbPath);
|
|
44
|
+
const db = new SQL.Database(fileBuffer);
|
|
45
|
+
try {
|
|
46
|
+
const { safeParseEmbedding } = await import('./memory-bridge.js');
|
|
47
|
+
const stmt = db.prepare(`SELECT id, key, namespace, content, embedding FROM memory_entries WHERE status = 'active' AND embedding IS NOT NULL LIMIT 50000`);
|
|
48
|
+
const rows = [];
|
|
49
|
+
while (stmt.step())
|
|
50
|
+
rows.push(stmt.get());
|
|
51
|
+
stmt.free();
|
|
52
|
+
const results = [];
|
|
53
|
+
for (const row of rows) {
|
|
54
|
+
const [id, key, namespace, content, embeddingJson] = row;
|
|
55
|
+
const parsed = safeParseEmbedding(embeddingJson);
|
|
56
|
+
if (!parsed || parsed.length !== dimensions)
|
|
57
|
+
continue;
|
|
58
|
+
results.push({
|
|
59
|
+
id,
|
|
60
|
+
vector: new Float32Array(parsed),
|
|
61
|
+
entry: { id, key: key || id, namespace: namespace || 'default', content: content || '' },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
db.close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
27
70
|
/**
|
|
28
71
|
* Get or create the HNSW index singleton
|
|
29
72
|
* Lazily initializes from SQLite data on first use
|
|
30
73
|
*/
|
|
31
74
|
export async function getHNSWIndex(options) {
|
|
32
75
|
const dimensions = options?.dimensions ?? 384;
|
|
33
|
-
// Return existing index if already initialized
|
|
34
|
-
|
|
76
|
+
// Return existing index if already initialized for the same dimensionality.
|
|
77
|
+
// A cached index built for a different embedding size must be rebuilt —
|
|
78
|
+
// otherwise addPoint()/search() silently fail (or corrupt) on the mismatch.
|
|
79
|
+
if (hnswIndex?.initialized && hnswIndex.dimensions === dimensions && !options?.forceRebuild) {
|
|
35
80
|
return hnswIndex;
|
|
36
81
|
}
|
|
37
82
|
// Prevent concurrent initialization
|
|
@@ -50,13 +95,23 @@ export async function getHNSWIndex(options) {
|
|
|
50
95
|
}
|
|
51
96
|
hnswInitializing = true;
|
|
52
97
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
98
|
+
const HNSWIndexClass = await loadHNSWIndexClass();
|
|
99
|
+
if (!HNSWIndexClass) {
|
|
100
|
+
// @monoes/memory isn't installed — no pure-JS fallback available;
|
|
101
|
+
// callers fall through to brute-force search.
|
|
102
|
+
hnswInitializing = false;
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const dbPath = options?.dbPath ?? path.join(process.cwd(), '.swarm', 'memory.db');
|
|
106
|
+
const loaded = await loadEntriesFromDb(dbPath, dimensions);
|
|
107
|
+
const instance = new HNSWIndexClass({ dimensions });
|
|
108
|
+
await instance.rebuild(loaded.map((l) => ({ id: l.id, vector: l.vector })));
|
|
109
|
+
const entries = new Map();
|
|
110
|
+
for (const l of loaded)
|
|
111
|
+
entries.set(l.id, l.entry);
|
|
112
|
+
hnswIndex = { instance, entries, dimensions, initialized: true };
|
|
58
113
|
hnswInitializing = false;
|
|
59
|
-
return
|
|
114
|
+
return hnswIndex;
|
|
60
115
|
}
|
|
61
116
|
catch {
|
|
62
117
|
hnswInitializing = false;
|
|
@@ -90,10 +145,7 @@ export async function addToHNSWIndex(id, embedding, entry) {
|
|
|
90
145
|
return false;
|
|
91
146
|
try {
|
|
92
147
|
const vector = new Float32Array(embedding);
|
|
93
|
-
await index.
|
|
94
|
-
id,
|
|
95
|
-
vector
|
|
96
|
-
});
|
|
148
|
+
await index.instance.addPoint(id, vector);
|
|
97
149
|
index.entries.set(id, entry);
|
|
98
150
|
// Save metadata for persistence (debounced would be better for high-volume)
|
|
99
151
|
saveHNSWMetadata();
|
|
@@ -115,7 +167,7 @@ export async function searchHNSWIndex(queryEmbedding, options) {
|
|
|
115
167
|
const vector = new Float32Array(queryEmbedding);
|
|
116
168
|
const k = options?.k ?? 10;
|
|
117
169
|
// HNSW search returns results with cosine distance (lower = more similar)
|
|
118
|
-
const results = await index.
|
|
170
|
+
const results = await index.instance.search(vector, k * 2); // Get extra for filtering
|
|
119
171
|
const filtered = [];
|
|
120
172
|
for (const result of results) {
|
|
121
173
|
const entry = index.entries.get(result.id);
|
|
@@ -127,7 +179,7 @@ export async function searchHNSWIndex(queryEmbedding, options) {
|
|
|
127
179
|
}
|
|
128
180
|
// Convert cosine distance to similarity score (1 - distance)
|
|
129
181
|
// Cosine distance convention: 0 = identical, 2 = opposite
|
|
130
|
-
const score = 1 - (result.
|
|
182
|
+
const score = 1 - (result.distance / 2);
|
|
131
183
|
filtered.push({
|
|
132
184
|
id: entry.id.substring(0, 12),
|
|
133
185
|
key: entry.key || entry.id.substring(0, 15),
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module v1/cli/intelligence
|
|
12
12
|
*/
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
|
-
import { join } from 'node:path';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
16
|
import { cosineSimilarity as sharedCosineSimilarity } from '../utils/cosine-similarity.js';
|
|
17
17
|
import { readJsonFileSync, writeJsonFileAtomic } from '../utils/json-file.js';
|
|
18
18
|
// ============================================================================
|
|
@@ -49,6 +49,75 @@ function ensureDataDir() {
|
|
|
49
49
|
function getPatternsPath() {
|
|
50
50
|
return join(getDataDir(), 'patterns.json');
|
|
51
51
|
}
|
|
52
|
+
// ── multi-process-safe patterns.json flush (P1-14) ─────────────────────────
|
|
53
|
+
// patterns.json can be flushed by more than one process at once — e.g. the
|
|
54
|
+
// long-lived MCP server process and a short-lived CJS hook subprocess each
|
|
55
|
+
// load their own in-memory copy, then flush independently. Without a lock +
|
|
56
|
+
// re-read-before-write, whichever flush lands last silently erases whatever
|
|
57
|
+
// the other process added since its own load. flushToDisk() below re-reads
|
|
58
|
+
// the current on-disk file immediately before writing and merges this
|
|
59
|
+
// process's in-memory patterns in by `id` (keeping whichever record has the
|
|
60
|
+
// newer `lastUsedAt`), guarded by a short advisory lock.
|
|
61
|
+
function getPatternsLockPath() {
|
|
62
|
+
return `${getPatternsPath()}.lock`;
|
|
63
|
+
}
|
|
64
|
+
const PATTERNS_LOCK_STALE_MS = 10_000; // single JSON write — 10s is generous
|
|
65
|
+
function isPidAlive(pid) {
|
|
66
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
67
|
+
return false;
|
|
68
|
+
try {
|
|
69
|
+
process.kill(pid, 0);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
return e?.code === 'EPERM';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Mirrors the stale-lock-breaking pattern in
|
|
78
|
+
* .claude/helpers/control-start.cjs (claimSpawnLock/releaseSpawnLock):
|
|
79
|
+
* wx-flag write to claim, break the lock if its holder is dead or the lock
|
|
80
|
+
* is older than the stale threshold, retry once, then give up and proceed
|
|
81
|
+
* unlocked (best-effort — a missed lock still merges via the re-read, it
|
|
82
|
+
* just narrows a tiny TOCTOU window further).
|
|
83
|
+
*/
|
|
84
|
+
function claimPatternsLock() {
|
|
85
|
+
const lockPath = getPatternsLockPath();
|
|
86
|
+
try {
|
|
87
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* ignore */
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
writeFileSync(lockPath, String(process.pid), { flag: 'wx' });
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
try {
|
|
98
|
+
const stat = statSync(lockPath);
|
|
99
|
+
const holder = Number(readFileSync(lockPath, 'utf-8'));
|
|
100
|
+
if (Date.now() - stat.mtimeMs < PATTERNS_LOCK_STALE_MS && isPidAlive(holder))
|
|
101
|
+
return false;
|
|
102
|
+
unlinkSync(lockPath);
|
|
103
|
+
writeFileSync(lockPath, String(process.pid), { flag: 'wx' });
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function releasePatternsLock() {
|
|
112
|
+
const lockPath = getPatternsLockPath();
|
|
113
|
+
try {
|
|
114
|
+
if (Number(readFileSync(lockPath, 'utf-8')) === process.pid)
|
|
115
|
+
unlinkSync(lockPath);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* ignore */
|
|
119
|
+
}
|
|
120
|
+
}
|
|
52
121
|
/**
|
|
53
122
|
* Get the stats file path
|
|
54
123
|
*/
|
|
@@ -412,7 +481,34 @@ class LocalReasoningBank {
|
|
|
412
481
|
return;
|
|
413
482
|
try {
|
|
414
483
|
ensureDataDir();
|
|
415
|
-
|
|
484
|
+
let acquired = claimPatternsLock();
|
|
485
|
+
if (!acquired)
|
|
486
|
+
acquired = claimPatternsLock(); // one retry — lock is short-lived
|
|
487
|
+
try {
|
|
488
|
+
// Re-read the current on-disk patterns and merge this process's
|
|
489
|
+
// in-memory changes in by id (newer lastUsedAt wins) instead of
|
|
490
|
+
// blindly overwriting the whole file with this.patternList — see
|
|
491
|
+
// the P1-14 comment above claimPatternsLock() for why.
|
|
492
|
+
const diskPatterns = readJsonFileSync(getPatternsPath(), []);
|
|
493
|
+
const merged = new Map();
|
|
494
|
+
if (Array.isArray(diskPatterns)) {
|
|
495
|
+
for (const p of diskPatterns) {
|
|
496
|
+
if (p && typeof p.id === 'string')
|
|
497
|
+
merged.set(p.id, p);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
for (const p of this.patternList) {
|
|
501
|
+
const existing = merged.get(p.id);
|
|
502
|
+
if (!existing || (p.lastUsedAt ?? 0) >= (existing.lastUsedAt ?? 0)) {
|
|
503
|
+
merged.set(p.id, p);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
writeJsonFileAtomic(getPatternsPath(), Array.from(merged.values()));
|
|
507
|
+
}
|
|
508
|
+
finally {
|
|
509
|
+
if (acquired)
|
|
510
|
+
releasePatternsLock();
|
|
511
|
+
}
|
|
416
512
|
this.dirty = false;
|
|
417
513
|
}
|
|
418
514
|
catch {
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* @module v1/cli/memory-bridge
|
|
8
8
|
*/
|
|
9
9
|
export declare function safeParseEmbedding(raw: string | null | undefined): number[] | null;
|
|
10
|
+
/** Resolve the real on-disk LanceDB path for a given custom path (or the default). */
|
|
11
|
+
export declare function bridgeGetDbPath(customPath?: string): string;
|
|
10
12
|
export declare function bridgeStoreEntry(options: {
|
|
11
13
|
key: string;
|
|
12
14
|
value: string;
|
|
@@ -113,6 +115,11 @@ export declare function bridgeLoadEmbeddingModel(dbPath?: string): Promise<{
|
|
|
113
115
|
modelName: string;
|
|
114
116
|
loadTime?: number;
|
|
115
117
|
} | null>;
|
|
118
|
+
export declare function bridgeGetBackendStats(dbPath?: string): Promise<{
|
|
119
|
+
totalEntries: number;
|
|
120
|
+
entriesByNamespace: Record<string, number>;
|
|
121
|
+
memoryUsage: number;
|
|
122
|
+
} | null>;
|
|
116
123
|
export declare function bridgeGetHNSWStatus(dbPath?: string): Promise<{
|
|
117
124
|
built: boolean;
|
|
118
125
|
size: number;
|
|
@@ -88,6 +88,10 @@ function getDbPath(customPath) {
|
|
|
88
88
|
return resolved;
|
|
89
89
|
return defaultDir;
|
|
90
90
|
}
|
|
91
|
+
/** Resolve the real on-disk LanceDB path for a given custom path (or the default). */
|
|
92
|
+
export function bridgeGetDbPath(customPath) {
|
|
93
|
+
return getDbPath(customPath);
|
|
94
|
+
}
|
|
91
95
|
function getAutomemConfig() {
|
|
92
96
|
const defaults = { dedupThreshold: 0.85, staleDays: 7 };
|
|
93
97
|
try {
|
|
@@ -439,6 +443,22 @@ export async function bridgeLoadEmbeddingModel(dbPath) {
|
|
|
439
443
|
return null;
|
|
440
444
|
}
|
|
441
445
|
}
|
|
446
|
+
export async function bridgeGetBackendStats(dbPath) {
|
|
447
|
+
const backend = await getBackend(dbPath);
|
|
448
|
+
if (!backend)
|
|
449
|
+
return null;
|
|
450
|
+
try {
|
|
451
|
+
const stats = await backend.getStats();
|
|
452
|
+
return {
|
|
453
|
+
totalEntries: stats?.totalEntries ?? 0,
|
|
454
|
+
entriesByNamespace: stats?.entriesByNamespace ?? {},
|
|
455
|
+
memoryUsage: stats?.memoryUsage ?? 0,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
442
462
|
// ===== HNSW (replaced by LanceDB ANN — stubs kept for API compat) =====
|
|
443
463
|
export async function bridgeGetHNSWStatus(dbPath) {
|
|
444
464
|
const backend = await getBackend(dbPath);
|
|
@@ -94,6 +94,12 @@ export declare class SONAOptimizer {
|
|
|
94
94
|
private failedRoutings;
|
|
95
95
|
private lastUpdate;
|
|
96
96
|
private persistencePath;
|
|
97
|
+
/** Set when in-memory state diverges from disk — triggers next debounced write */
|
|
98
|
+
private dirty;
|
|
99
|
+
/** NodeJS timeout handle for debounced disk flush */
|
|
100
|
+
private saveTimer;
|
|
101
|
+
/** Debounce window for disk writes (ms) — batches rapid trajectory bursts */
|
|
102
|
+
private static readonly SAVE_DEBOUNCE_MS;
|
|
97
103
|
constructor(options?: {
|
|
98
104
|
persistencePath?: string;
|
|
99
105
|
});
|
|
@@ -182,6 +188,12 @@ export declare class SONAOptimizer {
|
|
|
182
188
|
* Load patterns from disk
|
|
183
189
|
*/
|
|
184
190
|
private loadFromDisk;
|
|
191
|
+
/**
|
|
192
|
+
* Schedule a debounced disk flush.
|
|
193
|
+
* Multiple calls within SAVE_DEBOUNCE_MS coalesce into a single write,
|
|
194
|
+
* preventing blocking I/O on every trajectory event during swarm bursts.
|
|
195
|
+
*/
|
|
196
|
+
private scheduleSave;
|
|
185
197
|
/**
|
|
186
198
|
* Save patterns to disk
|
|
187
199
|
*/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface BrokerEntry {
|
|
2
|
+
url: string;
|
|
3
|
+
pid: number;
|
|
4
|
+
updatedAt: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function defaultRegistryDir(): string;
|
|
7
|
+
/** Publish that this process hosts org `name`, reachable via `url`. Call again periodically (heartbeat) — see BrokerLease.
|
|
8
|
+
* Writes via tmp+rename (same-directory rename is atomic on POSIX/NTFS) so a concurrent lookupOrg() never observes a
|
|
9
|
+
* partially-written entry — this file is rewritten every heartbeat (default 20s) while other processes may read it. */
|
|
10
|
+
export declare function registerOrg(name: string, url: string, dir?: string): void;
|
|
11
|
+
/** Remove this process's registration for `name` (best effort). */
|
|
12
|
+
export declare function unregisterOrg(name: string, dir?: string): void;
|
|
13
|
+
/** Find which process (if any) currently hosts org `name`. Null if never registered or the registration is stale (owner crashed without cleanup). */
|
|
14
|
+
export declare function lookupOrg(name: string, dir?: string, staleMs?: number): BrokerEntry | null;
|
|
15
|
+
/** Keeps a broker registration alive with periodic heartbeats until stop() is called. */
|
|
16
|
+
export declare class BrokerLease {
|
|
17
|
+
private name;
|
|
18
|
+
private url;
|
|
19
|
+
private dir;
|
|
20
|
+
private intervalMs;
|
|
21
|
+
private timer;
|
|
22
|
+
constructor(name: string, url: string, dir?: string, intervalMs?: number);
|
|
23
|
+
start(): void;
|
|
24
|
+
stop(): void;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=broker.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// packages/@monomind/cli/src/orgrt/broker.ts
|
|
2
|
+
// monolean: file-based local broker for cross-process org discovery (different
|
|
3
|
+
// `monomind org` processes / project directories, same machine). Upgrade path:
|
|
4
|
+
// a real network registry when cross-machine discovery is needed.
|
|
5
|
+
import { mkdirSync, writeFileSync, readFileSync, unlinkSync, renameSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
const SAFE_NAME = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
9
|
+
const DEFAULT_STALE_MS = 90_000;
|
|
10
|
+
export function defaultRegistryDir() {
|
|
11
|
+
return process.env.MONOMIND_ORGRT_BROKER_DIR || join(homedir(), '.monomind', 'orgrt-broker');
|
|
12
|
+
}
|
|
13
|
+
function entryPath(name, dir) {
|
|
14
|
+
if (!SAFE_NAME.test(name))
|
|
15
|
+
throw new Error(`invalid org name for broker registry: ${name}`);
|
|
16
|
+
return join(dir, `${name}.json`);
|
|
17
|
+
}
|
|
18
|
+
/** Publish that this process hosts org `name`, reachable via `url`. Call again periodically (heartbeat) — see BrokerLease.
|
|
19
|
+
* Writes via tmp+rename (same-directory rename is atomic on POSIX/NTFS) so a concurrent lookupOrg() never observes a
|
|
20
|
+
* partially-written entry — this file is rewritten every heartbeat (default 20s) while other processes may read it. */
|
|
21
|
+
export function registerOrg(name, url, dir = defaultRegistryDir()) {
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
const entry = { url, pid: process.pid, updatedAt: Date.now() };
|
|
24
|
+
const dest = entryPath(name, dir);
|
|
25
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
26
|
+
writeFileSync(tmp, JSON.stringify(entry));
|
|
27
|
+
renameSync(tmp, dest);
|
|
28
|
+
}
|
|
29
|
+
/** Remove this process's registration for `name` (best effort). */
|
|
30
|
+
export function unregisterOrg(name, dir = defaultRegistryDir()) {
|
|
31
|
+
try {
|
|
32
|
+
unlinkSync(entryPath(name, dir));
|
|
33
|
+
}
|
|
34
|
+
catch { /* already gone */ }
|
|
35
|
+
}
|
|
36
|
+
/** Find which process (if any) currently hosts org `name`. Null if never registered or the registration is stale (owner crashed without cleanup). */
|
|
37
|
+
export function lookupOrg(name, dir = defaultRegistryDir(), staleMs = DEFAULT_STALE_MS) {
|
|
38
|
+
try {
|
|
39
|
+
const entry = JSON.parse(readFileSync(entryPath(name, dir), 'utf8'));
|
|
40
|
+
if (Date.now() - entry.updatedAt >= staleMs)
|
|
41
|
+
return null;
|
|
42
|
+
return entry;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Keeps a broker registration alive with periodic heartbeats until stop() is called. */
|
|
49
|
+
export class BrokerLease {
|
|
50
|
+
name;
|
|
51
|
+
url;
|
|
52
|
+
dir;
|
|
53
|
+
intervalMs;
|
|
54
|
+
timer = null;
|
|
55
|
+
constructor(name, url, dir = defaultRegistryDir(), intervalMs = 20_000) {
|
|
56
|
+
this.name = name;
|
|
57
|
+
this.url = url;
|
|
58
|
+
this.dir = dir;
|
|
59
|
+
this.intervalMs = intervalMs;
|
|
60
|
+
}
|
|
61
|
+
start() {
|
|
62
|
+
registerOrg(this.name, this.url, this.dir);
|
|
63
|
+
this.timer = setInterval(() => registerOrg(this.name, this.url, this.dir), this.intervalMs);
|
|
64
|
+
this.timer.unref?.();
|
|
65
|
+
}
|
|
66
|
+
stop() {
|
|
67
|
+
if (this.timer)
|
|
68
|
+
clearInterval(this.timer);
|
|
69
|
+
this.timer = null;
|
|
70
|
+
unregisterOrg(this.name, this.dir);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=broker.js.map
|
|
@@ -15,7 +15,11 @@ export declare class OrgBus {
|
|
|
15
15
|
constructor(org: string, run: string, dir: string);
|
|
16
16
|
subscribe(fn: Listener): () => void;
|
|
17
17
|
emit(partial: Omit<BusEvent, 'id' | 'ts' | 'org' | 'run'>): BusEvent;
|
|
18
|
-
/**
|
|
18
|
+
/** Await all queued disk writes (tests, shutdown). Listeners registered via subscribe() run
|
|
19
|
+
* synchronously inside emit() — flush() has no visibility into any async work they schedule
|
|
20
|
+
* off of that (e.g. the forwarder's HTTP POSTs). A caller that needs an async subscriber's
|
|
21
|
+
* work to have settled too must await that subscriber's own completion signal separately
|
|
22
|
+
* (see daemon.ts stopOrg(), which awaits forwarder.settle() alongside bus.flush()). */
|
|
19
23
|
flush(): Promise<void>;
|
|
20
24
|
static readHistory(dir: string): BusEvent[];
|
|
21
25
|
}
|
|
@@ -45,7 +45,11 @@ export class OrgBus {
|
|
|
45
45
|
}
|
|
46
46
|
return e;
|
|
47
47
|
}
|
|
48
|
-
/**
|
|
48
|
+
/** Await all queued disk writes (tests, shutdown). Listeners registered via subscribe() run
|
|
49
|
+
* synchronously inside emit() — flush() has no visibility into any async work they schedule
|
|
50
|
+
* off of that (e.g. the forwarder's HTTP POSTs). A caller that needs an async subscriber's
|
|
51
|
+
* work to have settled too must await that subscriber's own completion signal separately
|
|
52
|
+
* (see daemon.ts stopOrg(), which awaits forwarder.settle() alongside bus.flush()). */
|
|
49
53
|
flush() { return this.pending; }
|
|
50
54
|
static readHistory(dir) {
|
|
51
55
|
const f = join(dir, 'bus.jsonl');
|
|
@@ -7,6 +7,9 @@ interface AgentRuntime {
|
|
|
7
7
|
mailbox: Mailbox;
|
|
8
8
|
policy: PolicyEngine;
|
|
9
9
|
done: Promise<void>;
|
|
10
|
+
/** 'running' until the session promise settles; 'crashed' if it rejected (see error). */
|
|
11
|
+
status: 'running' | 'ended' | 'crashed';
|
|
12
|
+
error?: string;
|
|
10
13
|
}
|
|
11
14
|
export interface RunningOrg {
|
|
12
15
|
def: OrgDef;
|
|
@@ -19,20 +22,63 @@ export interface DaemonOpts {
|
|
|
19
22
|
queryFn?: typeof query;
|
|
20
23
|
forward?: boolean;
|
|
21
24
|
controlJson?: string;
|
|
25
|
+
/** Enables cross-process inter-org routing: on a local delivery miss, ask the
|
|
26
|
+
* machine-local broker whether another `monomind org` process (e.g. a
|
|
27
|
+
* different project directory) hosts the target org, and deliver over HTTP
|
|
28
|
+
* if so. Off by default — tests and single-process runs don't need it. */
|
|
29
|
+
crossProcess?: boolean;
|
|
30
|
+
/** Base URL at which OTHER processes can reach this daemon's inbox (see
|
|
31
|
+
* server.ts POST /api/xdeliver). Set this to make orgs hosted here
|
|
32
|
+
* discoverable; omit for outbound-only cross-process delivery. */
|
|
33
|
+
inboxUrl?: string;
|
|
34
|
+
/** Override the broker's file registry directory (tests only). */
|
|
35
|
+
brokerDir?: string;
|
|
36
|
+
/** Override how long stopOrg() waits for agent sessions before proceeding anyway (tests only; default 15000ms). */
|
|
37
|
+
stopWaitMs?: number;
|
|
22
38
|
}
|
|
23
39
|
export declare class OrgDaemon {
|
|
24
40
|
private root;
|
|
25
41
|
private opts;
|
|
26
42
|
private orgs;
|
|
43
|
+
private waking;
|
|
27
44
|
private globalSubscribers;
|
|
45
|
+
private leases;
|
|
46
|
+
private forwarders;
|
|
28
47
|
constructor(root: string, opts?: DaemonOpts);
|
|
48
|
+
/** Publish this daemon's inbox so orgs started AFTER this call register with the broker. */
|
|
49
|
+
setInboxUrl(url: string): void;
|
|
29
50
|
/** subscribe to events from ALL running orgs (dashboard server uses this) */
|
|
30
51
|
subscribe(fn: (e: BusEvent) => void): () => void;
|
|
31
52
|
listOrgs(): RunningOrg[];
|
|
32
53
|
getOrg(name: string): RunningOrg | undefined;
|
|
33
54
|
startOrg(name: string, taskOverride?: string): Promise<RunningOrg>;
|
|
55
|
+
/**
|
|
56
|
+
* Resolves an org_send `to` address ("role" for same-org, "org:role" for
|
|
57
|
+
* cross-org) into its parts. Centralizes the one addressing rule that
|
|
58
|
+
* matters (an "own-org:role" self-prefix is intra-org, not cross-org) so
|
|
59
|
+
* deliver()/deliverRemote() don't each re-derive it — the qualified `to`
|
|
60
|
+
* string returned is always the canonical display form for that address.
|
|
61
|
+
*/
|
|
62
|
+
private resolveAddress;
|
|
34
63
|
/** Route a message. to = "role" (same org) or "org:role" (cross-org). Returns a receipt string. */
|
|
35
64
|
deliver(fromOrg: string, fromRole: string, to: string, subject: string, body: string): Promise<string>;
|
|
65
|
+
/** Cross-process leg of deliver(): ask the machine-local broker who hosts targetOrgName, then POST over HTTP.
|
|
66
|
+
* `to` here is always the fully-qualified "org:role" display form (resolveAddress already normalized it). */
|
|
67
|
+
private deliverRemote;
|
|
68
|
+
/** Inbound handler for cross-process delivery — called by the server's POST /api/xdeliver route
|
|
69
|
+
* when ANOTHER process's deliverRemote() reaches this daemon. Pushes straight into the target
|
|
70
|
+
* agent's mailbox; the agent picks it up on its own next turn (see Mailbox — never interrupts). */
|
|
71
|
+
receiveRemote(toOrg: string, toRole: string, fromQualified: string, subject: string, body: string): {
|
|
72
|
+
ok: true;
|
|
73
|
+
receipt: string;
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
error: string;
|
|
77
|
+
};
|
|
78
|
+
private hasOrgDef;
|
|
79
|
+
/** Start an offline org in the background so queued messages get drained.
|
|
80
|
+
* Fire-and-forget — errors are logged but don't propagate to the sender. */
|
|
81
|
+
private autoWake;
|
|
36
82
|
stopOrg(name: string): Promise<void>;
|
|
37
83
|
stopAll(): Promise<void>;
|
|
38
84
|
private persistState;
|