monomind 2.3.1 → 2.3.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/package.json +1 -1
- package/packages/@monomind/cli/.claude/commands/mastermind/_repeat.md +9 -9
- package/packages/@monomind/cli/.claude/commands/mastermind/approve.md +2 -2
- package/packages/@monomind/cli/.claude/commands/mastermind/architect.md +3 -3
- package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +4 -4
- package/packages/@monomind/cli/.claude/commands/mastermind/master.md +3 -3
- package/packages/@monomind/cli/.claude/commands/mastermind/repeat.md +5 -5
- package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +3 -3
- package/packages/@monomind/cli/.claude/commands/mastermind/stoporg.md +1 -1
- package/packages/@monomind/cli/.claude/helpers/control-start.cjs +52 -4
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +9 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +9 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +65 -0
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +59 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/_protocol.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/_repeat.md +3 -3
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/adapters.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/agents.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/architect.md +8 -8
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/backup.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/bootstrap.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/costs.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/heartbeat.md +2 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/idea.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/monitor.md +2 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/plugins.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorg.md +28 -22
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +2 -2
- package/packages/@monomind/cli/dist/src/commands/cleanup.d.ts +14 -0
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +99 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.d.ts +4 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +25 -0
- package/packages/@monomind/cli/dist/src/commands/doctor.js +3 -1
- package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +39 -5
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +11 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +17 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +90 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +2 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.js +5 -0
- package/packages/@monomind/cli/dist/src/ui/server.mjs +75 -0
- package/packages/@monomind/cli/package.json +2 -2
|
@@ -13,6 +13,21 @@ const DEFAULT_OVERLAP = 400;
|
|
|
13
13
|
// Inline fallback identical to @monoes/memory's knowledge/document-chunker.ts —
|
|
14
14
|
// used only if the dynamic import below fails (package not installed/built).
|
|
15
15
|
// Keep in sync if the shared chunker's boundary-snapping logic changes.
|
|
16
|
+
const HEADING_LINE_RE = /^#{1,6} /;
|
|
17
|
+
function lastHeadingBefore(text, pos) {
|
|
18
|
+
let i = text.lastIndexOf('\n#', pos - 1);
|
|
19
|
+
while (i !== -1) {
|
|
20
|
+
const eol = text.indexOf('\n', i + 1);
|
|
21
|
+
const line = text.slice(i + 1, eol === -1 ? undefined : eol);
|
|
22
|
+
if (HEADING_LINE_RE.test(line))
|
|
23
|
+
return line.replace(/^#+ /, '').trim();
|
|
24
|
+
i = text.lastIndexOf('\n#', i - 1);
|
|
25
|
+
}
|
|
26
|
+
const firstEol = text.indexOf('\n');
|
|
27
|
+
const firstLine = firstEol === -1 ? text : text.slice(0, firstEol);
|
|
28
|
+
return HEADING_LINE_RE.test(firstLine) && firstEol !== -1 && firstEol < pos
|
|
29
|
+
? firstLine.replace(/^#+ /, '').trim() : null;
|
|
30
|
+
}
|
|
16
31
|
function chunkDocumentInline(docId, text) {
|
|
17
32
|
if (text.length === 0)
|
|
18
33
|
return [];
|
|
@@ -21,18 +36,37 @@ function chunkDocumentInline(docId, text) {
|
|
|
21
36
|
let chunkIndex = 0;
|
|
22
37
|
while (startChar < text.length) {
|
|
23
38
|
let endChar = Math.min(startChar + DEFAULT_CHUNK_SIZE, text.length);
|
|
39
|
+
let brokeAtHeading = false;
|
|
24
40
|
if (endChar < text.length) {
|
|
25
41
|
const windowStart = Math.max(startChar, endChar - Math.floor(DEFAULT_CHUNK_SIZE * 0.2));
|
|
26
42
|
const window = text.slice(windowStart, endChar);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
let h = window.lastIndexOf('\n#');
|
|
44
|
+
while (h !== -1) {
|
|
45
|
+
const eol = window.indexOf('\n', h + 1);
|
|
46
|
+
const line = window.slice(h + 1, eol === -1 ? undefined : eol);
|
|
47
|
+
if (HEADING_LINE_RE.test(line) && windowStart + h > startChar)
|
|
48
|
+
break;
|
|
49
|
+
h = window.lastIndexOf('\n#', h - 1);
|
|
50
|
+
}
|
|
51
|
+
if (h !== -1 && windowStart + h > startChar) {
|
|
52
|
+
endChar = windowStart + h + 1;
|
|
53
|
+
brokeAtHeading = true;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const lastParagraph = window.lastIndexOf('\n\n');
|
|
57
|
+
if (lastParagraph !== -1)
|
|
58
|
+
endChar = windowStart + lastParagraph + 2;
|
|
59
|
+
}
|
|
30
60
|
}
|
|
31
|
-
|
|
61
|
+
let chunkText = text.slice(startChar, endChar);
|
|
62
|
+
const heading = lastHeadingBefore(text, startChar + 1);
|
|
63
|
+
if (heading && !HEADING_LINE_RE.test(chunkText.trimStart()))
|
|
64
|
+
chunkText = `§ ${heading}\n${chunkText}`;
|
|
65
|
+
chunks.push({ chunkId: `${docId}:${chunkIndex}`, docId, text: chunkText, startChar, endChar, chunkIndex });
|
|
32
66
|
chunkIndex++;
|
|
33
67
|
if (endChar >= text.length)
|
|
34
68
|
break;
|
|
35
|
-
startChar += Math.max(1, endChar - startChar - DEFAULT_OVERLAP);
|
|
69
|
+
startChar += brokeAtHeading ? Math.max(1, endChar - startChar) : Math.max(1, endChar - startChar - DEFAULT_OVERLAP);
|
|
36
70
|
}
|
|
37
71
|
return chunks;
|
|
38
72
|
}
|
|
@@ -149,7 +149,9 @@ async function getBackend(dbPath) {
|
|
|
149
149
|
const hf = await import('@huggingface/transformers');
|
|
150
150
|
// revision must be a git ref — 'main' is the HF default; 'default' 404s and
|
|
151
151
|
// silently killed embeddings (every search degraded to keyword matching)
|
|
152
|
-
|
|
152
|
+
// dtype pinned explicitly: transformers.js logs a "dtype not specified"
|
|
153
|
+
// warning to the console on every load otherwise (leaks into CLI output).
|
|
154
|
+
const extractor = await hf.pipeline('feature-extraction', BRIDGE_EMBEDDING_MODEL, { revision: 'main', dtype: 'fp32' });
|
|
153
155
|
embeddingGenerator = async (text) => {
|
|
154
156
|
const output = await extractor(text, { pooling: 'mean', normalize: true });
|
|
155
157
|
return new Float32Array(output.data);
|
|
@@ -167,6 +169,14 @@ async function getBackend(dbPath) {
|
|
|
167
169
|
// file lives inside that directory.
|
|
168
170
|
const dir = getDbPath(dbPath);
|
|
169
171
|
fs.mkdirSync(dir, { recursive: true });
|
|
172
|
+
// Origin marker: records which project this data dir belongs to, so
|
|
173
|
+
// `monomind cleanup --data` can verifiably prune dirs whose project
|
|
174
|
+
// no longer exists (the dir-name hash is one-way). Best-effort.
|
|
175
|
+
try {
|
|
176
|
+
const originFile = path.join(projectDataDir(), 'origin.json');
|
|
177
|
+
fs.writeFileSync(originFile, JSON.stringify({ path: path.resolve(process.cwd()), updatedAt: new Date().toISOString() }) + '\n', 'utf-8');
|
|
178
|
+
}
|
|
179
|
+
catch { /* non-fatal */ }
|
|
170
180
|
const cfg = {
|
|
171
181
|
databasePath: path.join(dir, 'memory.db'),
|
|
172
182
|
walMode: true,
|
|
@@ -99,6 +99,23 @@ export declare class OrgDaemon {
|
|
|
99
99
|
private autoWake;
|
|
100
100
|
stopOrg(name: string): Promise<void>;
|
|
101
101
|
stopAll(): Promise<void>;
|
|
102
|
+
private orgMemoryNamespace;
|
|
103
|
+
/** Store dir for org cross-run memory — inside the org root so the bridge's
|
|
104
|
+
* path guard accepts it when the daemon runs from the project (the normal
|
|
105
|
+
* case) and test roots stay isolated. */
|
|
106
|
+
private orgMemoryDbPath;
|
|
107
|
+
/** The memory bridge's traversal guard silently redirects out-of-tree paths
|
|
108
|
+
* to the per-project default store. For an org rooted outside cwd (tests,
|
|
109
|
+
* unusual daemon setups) that redirect would write into the WRONG project's
|
|
110
|
+
* memory — verify the guard kept our path, and skip org memory otherwise. */
|
|
111
|
+
private orgMemoryUsable;
|
|
112
|
+
/** org_recall implementation: search the org's memory namespace via the
|
|
113
|
+
* memory bridge (semantic when the local model is available, tokenized
|
|
114
|
+
* keyword otherwise). Failures return a message, never throw into the tool. */
|
|
115
|
+
private recallOrgMemory;
|
|
116
|
+
/** Persist the run's outcome into cross-run org memory so org_recall (and
|
|
117
|
+
* future runs) can find it by meaning, not just recency. Best-effort. */
|
|
118
|
+
private storeRunMemory;
|
|
102
119
|
private persistState;
|
|
103
120
|
}
|
|
104
121
|
export {};
|
|
@@ -75,6 +75,11 @@ export class OrgDaemon {
|
|
|
75
75
|
onComplete: (r, outcome, summary) => {
|
|
76
76
|
bus.emit({ type: 'status', from: r, reason: 'org-complete', msg: `run outcome: ${outcome}`, data: { outcome, summary } });
|
|
77
77
|
},
|
|
78
|
+
recall: async (r, q) => {
|
|
79
|
+
const answer = await this.recallOrgMemory(name, def, q);
|
|
80
|
+
bus.emit({ type: 'status', from: r, reason: 'org-recall', msg: `recall: ${q.slice(0, 80)}`, data: { hits: answer.hits } });
|
|
81
|
+
return answer.text;
|
|
82
|
+
},
|
|
78
83
|
queryFn: this.opts.queryFn,
|
|
79
84
|
};
|
|
80
85
|
// Supervised session: transient crashes (provider blips, network) restart
|
|
@@ -384,8 +389,11 @@ export class OrgDaemon {
|
|
|
384
389
|
try {
|
|
385
390
|
const events = readRunEvents(this.root, name, org.run);
|
|
386
391
|
if (events.length) {
|
|
392
|
+
const summary = summarizeRun(events);
|
|
387
393
|
const { appendFileSync } = await import('node:fs');
|
|
388
|
-
appendFileSync(historyFile(this.root, name), JSON.stringify(
|
|
394
|
+
appendFileSync(historyFile(this.root, name), JSON.stringify(summary) + '\n', 'utf8');
|
|
395
|
+
// Cross-run memory: make this run's outcome recallable by meaning
|
|
396
|
+
await this.storeRunMemory(name, org.def, org.run, summary);
|
|
389
397
|
}
|
|
390
398
|
}
|
|
391
399
|
catch (err) {
|
|
@@ -406,6 +414,87 @@ export class OrgDaemon {
|
|
|
406
414
|
async stopAll() {
|
|
407
415
|
await Promise.all([...this.orgs.keys()].map(n => this.stopOrg(n)));
|
|
408
416
|
}
|
|
417
|
+
orgMemoryNamespace(name, def) {
|
|
418
|
+
return def.run_config.memory_namespace ?? `org:${name}`;
|
|
419
|
+
}
|
|
420
|
+
/** Store dir for org cross-run memory — inside the org root so the bridge's
|
|
421
|
+
* path guard accepts it when the daemon runs from the project (the normal
|
|
422
|
+
* case) and test roots stay isolated. */
|
|
423
|
+
orgMemoryDbPath() {
|
|
424
|
+
return join(this.root, '.monomind', 'org-memory');
|
|
425
|
+
}
|
|
426
|
+
/** The memory bridge's traversal guard silently redirects out-of-tree paths
|
|
427
|
+
* to the per-project default store. For an org rooted outside cwd (tests,
|
|
428
|
+
* unusual daemon setups) that redirect would write into the WRONG project's
|
|
429
|
+
* memory — verify the guard kept our path, and skip org memory otherwise. */
|
|
430
|
+
async orgMemoryUsable() {
|
|
431
|
+
try {
|
|
432
|
+
const { bridgeGetDbPath } = await import('../memory/memory-bridge.js');
|
|
433
|
+
const want = this.orgMemoryDbPath();
|
|
434
|
+
const got = bridgeGetDbPath(want);
|
|
435
|
+
const { realpathSync } = await import('node:fs');
|
|
436
|
+
const real = (p) => { try {
|
|
437
|
+
return realpathSync(p);
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return p;
|
|
441
|
+
} };
|
|
442
|
+
return real(got) === real(want);
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/** org_recall implementation: search the org's memory namespace via the
|
|
449
|
+
* memory bridge (semantic when the local model is available, tokenized
|
|
450
|
+
* keyword otherwise). Failures return a message, never throw into the tool. */
|
|
451
|
+
async recallOrgMemory(name, def, query) {
|
|
452
|
+
try {
|
|
453
|
+
if (!(await this.orgMemoryUsable()))
|
|
454
|
+
return { text: 'org memory is not available in this environment.', hits: 0 };
|
|
455
|
+
const { bridgeSearchEntries } = await import('../memory/memory-bridge.js');
|
|
456
|
+
const res = await bridgeSearchEntries({
|
|
457
|
+
query, namespace: this.orgMemoryNamespace(name, def), limit: 5, dbPath: this.orgMemoryDbPath(),
|
|
458
|
+
});
|
|
459
|
+
const results = res?.results ?? [];
|
|
460
|
+
if (!results.length)
|
|
461
|
+
return { text: 'No matching org memory found — this may be the first run covering this topic.', hits: 0 };
|
|
462
|
+
const text = results.map((r, i) => `${i + 1}. [${r.key}] ${r.content}`).join('\n\n');
|
|
463
|
+
return { text, hits: results.length };
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
return { text: `org memory unavailable (${err instanceof Error ? err.message : 'error'})`, hits: 0 };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/** Persist the run's outcome into cross-run org memory so org_recall (and
|
|
470
|
+
* future runs) can find it by meaning, not just recency. Best-effort. */
|
|
471
|
+
async storeRunMemory(name, def, run, summary) {
|
|
472
|
+
try {
|
|
473
|
+
if (!(await this.orgMemoryUsable()))
|
|
474
|
+
return;
|
|
475
|
+
const { bridgeStoreEntry } = await import('../memory/memory-bridge.js');
|
|
476
|
+
const when = summary.endedAt ? new Date(summary.endedAt).toISOString().slice(0, 10) : '';
|
|
477
|
+
const lines = [
|
|
478
|
+
`Org run ${run}${when ? ` (${when})` : ''} — goal: ${def.goal}`,
|
|
479
|
+
summary.outcome
|
|
480
|
+
? `Outcome: ${summary.outcome.status} — ${summary.outcome.summary}`
|
|
481
|
+
: `Outcome: not recorded (${summary.messages} messages exchanged)`,
|
|
482
|
+
summary.assets.length ? `Assets produced: ${summary.assets.slice(0, 10).join(', ')}` : '',
|
|
483
|
+
summary.crashes.length ? `Crashed agents: ${summary.crashes.join(', ')}` : '',
|
|
484
|
+
].filter(Boolean);
|
|
485
|
+
await bridgeStoreEntry({
|
|
486
|
+
key: `run-${run}`,
|
|
487
|
+
value: lines.join('\n'),
|
|
488
|
+
namespace: this.orgMemoryNamespace(name, def),
|
|
489
|
+
dbPath: this.orgMemoryDbPath(),
|
|
490
|
+
upsert: true,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
495
|
+
console.error(`org ${name}: run memory store failed:`, err instanceof Error ? err.message : err);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
409
498
|
persistState(name, status, run) {
|
|
410
499
|
const p = join(this.root, ORG_DIR, name, 'runtime.json');
|
|
411
500
|
writeFileSync(p, JSON.stringify({ status, run, pid: process.pid, updated: new Date().toISOString() }, null, 2));
|
|
@@ -15,6 +15,8 @@ export interface SessionOpts {
|
|
|
15
15
|
askHuman?: (role: string, question: string) => Promise<string>;
|
|
16
16
|
/** Coordinator-only: records the run's outcome (daemon persists it to run history). */
|
|
17
17
|
onComplete?: (role: string, outcome: 'achieved' | 'partial' | 'failed', summary: string) => void;
|
|
18
|
+
/** Search the org's accumulated cross-run memory (memory_namespace). */
|
|
19
|
+
recall?: (role: string, query: string) => Promise<string>;
|
|
18
20
|
def?: OrgDef;
|
|
19
21
|
maxTurns?: number;
|
|
20
22
|
queryFn?: typeof query;
|
|
@@ -14,6 +14,7 @@ export function buildRolePrompt(role, def, roster) {
|
|
|
14
14
|
`The ONLY way to communicate with other agents is the org_send tool.`,
|
|
15
15
|
`Roster: ${roster.join(', ')}. Address another org's agent as "<org-name>:<role-id>".`,
|
|
16
16
|
`If you need a human decision, call ask_human with your question, then end your turn — you'll receive the human's answer as a new message when it arrives. Do not call ask_human for anything you can resolve yourself.`,
|
|
17
|
+
`Before starting substantial work, call org_recall to check what previous runs already learned or delivered — do not redo finished work.`,
|
|
17
18
|
`When you receive a message, act on it, then org_send your result to the requester.`,
|
|
18
19
|
isCoordinator
|
|
19
20
|
? `When the org's goal for this run is achieved (or clearly can't be), call org_complete exactly once with the outcome and a concise summary of what was done — it is recorded in the org's run history and briefed to the next run. Then end your turn.`
|
|
@@ -52,6 +53,10 @@ async function runOneSession(opts) {
|
|
|
52
53
|
name: 'org',
|
|
53
54
|
version: '1.0.0',
|
|
54
55
|
tools: [
|
|
56
|
+
...(opts.recall ? [tool('org_recall', 'Search this org\'s accumulated memory from previous runs (outcomes, decisions, learnings). Use before starting work that may already have been done.', { query: z.string() }, async (args) => {
|
|
57
|
+
const text = await opts.recall(role.id, args.query);
|
|
58
|
+
return { content: [{ type: 'text', text }] };
|
|
59
|
+
})] : []),
|
|
55
60
|
...(isCoordinator && opts.onComplete ? [tool('org_complete', 'Record the outcome of this run. Call exactly once, when the goal is achieved or clearly cannot be. The outcome and summary are persisted to the org run history and briefed to the next run.', { outcome: z.enum(['achieved', 'partial', 'failed']), summary: z.string() }, async (args) => {
|
|
56
61
|
opts.onComplete(role.id, args.outcome, args.summary);
|
|
57
62
|
return { content: [{ type: 'text', text: `outcome "${args.outcome}" recorded` }] };
|
|
@@ -1013,6 +1013,31 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1013
1013
|
// (not per-request inside the request handler below) since both only
|
|
1014
1014
|
// close over per-server-start state (dashboardAuthValue) plus their
|
|
1015
1015
|
// explicit req/res/corsOrigin params. ──────────────────────────────────
|
|
1016
|
+
// ── Second Brain warm bridge: lazy singleton import of the compiled memory
|
|
1017
|
+
// bridge (loads the local embedding model once for the server's lifetime).
|
|
1018
|
+
// Boot-warmed below only when this project actually has a knowledge base.
|
|
1019
|
+
let _knowledgeBridgePromise = null;
|
|
1020
|
+
const _getKnowledgeBridge = () => {
|
|
1021
|
+
if (!_knowledgeBridgePromise) {
|
|
1022
|
+
_knowledgeBridgePromise = import('../memory/memory-bridge.js').catch((err) => {
|
|
1023
|
+
_knowledgeBridgePromise = null; // allow retry on next request
|
|
1024
|
+
if (process.env.MONOMIND_DEBUG) console.error('[knowledge] bridge import failed:', err.message);
|
|
1025
|
+
return null;
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
return _knowledgeBridgePromise;
|
|
1029
|
+
};
|
|
1030
|
+
try {
|
|
1031
|
+
if (fs.existsSync(path.join(projectDir || process.cwd(), '.monomind', 'knowledge', 'chunks.jsonl'))) {
|
|
1032
|
+
// Warm off the startup path: first hook request should hit a hot model.
|
|
1033
|
+
const _warmTimer = setTimeout(() => {
|
|
1034
|
+
_getKnowledgeBridge().then(b => b?.bridgeSearchEntries?.({ query: 'warmup', namespace: 'knowledge:shared', limit: 1 }))
|
|
1035
|
+
.catch(() => { /* warm-up is best-effort */ });
|
|
1036
|
+
}, 3000);
|
|
1037
|
+
if (_warmTimer.unref) _warmTimer.unref();
|
|
1038
|
+
}
|
|
1039
|
+
} catch (_) { /* non-fatal */ }
|
|
1040
|
+
|
|
1016
1041
|
const _checkAuth = (req) => {
|
|
1017
1042
|
let _suppliedAuth = req.headers['x-monomind-token'] || '';
|
|
1018
1043
|
if (!_suppliedAuth) {
|
|
@@ -5888,6 +5913,46 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
5888
5913
|
return;
|
|
5889
5914
|
}
|
|
5890
5915
|
|
|
5916
|
+
// POST /api/knowledge/search — warm semantic knowledge search for hooks.
|
|
5917
|
+
// The dashboard server is the one long-lived process on the machine, so it
|
|
5918
|
+
// holds the local embedding model warm; per-prompt hook subprocesses (which
|
|
5919
|
+
// cannot afford a model load) POST here and fall back to keyword scoring
|
|
5920
|
+
// when this endpoint is cold/absent. Fully local — model + store on disk.
|
|
5921
|
+
if (req.method === 'POST' && url === '/api/knowledge/search') {
|
|
5922
|
+
let body = '';
|
|
5923
|
+
req.on('data', c => { body += c; });
|
|
5924
|
+
req.on('end', async () => {
|
|
5925
|
+
try {
|
|
5926
|
+
const payload = JSON.parse(body || '{}');
|
|
5927
|
+
const query = String(payload.query || '').slice(0, 2000);
|
|
5928
|
+
const limit = Math.min(Math.max(parseInt(payload.limit, 10) || 3, 1), 10);
|
|
5929
|
+
const namespace = typeof payload.namespace === 'string' && /^[A-Za-z0-9:_-]{1,128}$/.test(payload.namespace)
|
|
5930
|
+
? payload.namespace : 'knowledge:shared';
|
|
5931
|
+
if (!query) {
|
|
5932
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
5933
|
+
res.end('{"error":"query required"}');
|
|
5934
|
+
return;
|
|
5935
|
+
}
|
|
5936
|
+
const bridge = await _getKnowledgeBridge();
|
|
5937
|
+
if (!bridge) {
|
|
5938
|
+
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
5939
|
+
res.end('{"error":"knowledge bridge unavailable"}');
|
|
5940
|
+
return;
|
|
5941
|
+
}
|
|
5942
|
+
const out = await bridge.bridgeSearchEntries({ query, namespace, limit });
|
|
5943
|
+
res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
|
|
5944
|
+
res.end(JSON.stringify({
|
|
5945
|
+
method: out?.searchMethod || 'none',
|
|
5946
|
+
results: (out?.results || []).map(r => ({ key: r.key, content: String(r.content || '').slice(0, 2000), score: r.score })),
|
|
5947
|
+
}));
|
|
5948
|
+
} catch (err) {
|
|
5949
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
5950
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
5951
|
+
}
|
|
5952
|
+
});
|
|
5953
|
+
return;
|
|
5954
|
+
}
|
|
5955
|
+
|
|
5891
5956
|
// GET /api/mastermind/metrics — aggregate system metrics from token-summary and swarm-activity
|
|
5892
5957
|
if (req.method === 'GET' && url === '/api/mastermind/metrics') {
|
|
5893
5958
|
try {
|
|
@@ -6260,6 +6325,16 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
6260
6325
|
const boundPort = await bindServer(server, port);
|
|
6261
6326
|
const url = `http://localhost:${boundPort}`;
|
|
6262
6327
|
|
|
6328
|
+
// Self-report the ACTUAL bound port for the spawner (control-start.cjs).
|
|
6329
|
+
// An HTTP probe cannot distinguish this server from another project's server
|
|
6330
|
+
// already answering on the requested port — this file is identity-proof.
|
|
6331
|
+
if (process.env.MONOMIND_BOUND_REPORT) {
|
|
6332
|
+
try {
|
|
6333
|
+
fs.mkdirSync(path.dirname(process.env.MONOMIND_BOUND_REPORT), { recursive: true });
|
|
6334
|
+
fs.writeFileSync(process.env.MONOMIND_BOUND_REPORT, JSON.stringify({ pid: process.pid, port: boundPort, ts: Date.now() }));
|
|
6335
|
+
} catch (_) { /* non-fatal — spawner falls back to pid-matched HTTP probe */ }
|
|
6336
|
+
}
|
|
6337
|
+
|
|
6263
6338
|
// ── One-time migration: mastermind-sessions.json → per-session JSONL ─────
|
|
6264
6339
|
// Runs once on startup. Existing sessions in the old monolithic format are
|
|
6265
6340
|
// split into individual JSONL files + _index.json for O(1) event writes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI engine for Monomind — an open-source MCP server that extends Claude Code with a codebase knowledge graph (tree-sitter + SQLite), persistent memory, multi-agent task coordination, and session hooks. MIT licensed, fully local.",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
},
|
|
106
106
|
"optionalDependencies": {
|
|
107
107
|
"@huggingface/transformers": "^3.8.1",
|
|
108
|
-
"@monoes/memory": "^1.0.
|
|
108
|
+
"@monoes/memory": "^1.0.7",
|
|
109
109
|
"@monomind/hooks": "*",
|
|
110
110
|
"@monomind/mcp": "*",
|
|
111
111
|
"@monomind/routing": "*",
|