monomind 2.2.0 → 2.3.1
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 +2 -2
- package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +24 -16
- package/packages/@monomind/cli/.claude/commands/mastermind/master.md +23 -7
- package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +3 -1
- package/packages/@monomind/cli/.claude/commands/mastermind/stoporg.md +6 -3
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +6 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/code-quality-reviewer-prompt.md +3 -3
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +19 -24
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/debug.md +49 -4
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/design.md +11 -10
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/final-reviewer-prompt.md +144 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/implementer-prompt.md +9 -4
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +20 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgs.md +40 -19
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgstatus.md +90 -41
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/plan.md +19 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/skill-builder.md +171 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/spec-reviewer-prompt.md +19 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +7 -5
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/taskdev.md +85 -15
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/tdd.md +30 -0
- package/packages/@monomind/cli/dist/src/autopilot-state.js +6 -4
- package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +4 -1
- package/packages/@monomind/cli/dist/src/capabilities/manager.js +3 -1
- package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +26 -0
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +28 -46
- package/packages/@monomind/cli/dist/src/commands/cleanup.d.ts +18 -0
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +121 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +13 -4
- package/packages/@monomind/cli/dist/src/commands/hooks-routing-commands.js +34 -18
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/hooks.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/mcp.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +7 -1
- package/packages/@monomind/cli/dist/src/commands/org-observe.d.ts +18 -0
- package/packages/@monomind/cli/dist/src/commands/org-observe.js +295 -0
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +9 -1
- package/packages/@monomind/cli/dist/src/commands/org.js +191 -9
- package/packages/@monomind/cli/dist/src/commands/performance.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/platforms.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/security-scan.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/start.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/swarm.js +3 -2
- package/packages/@monomind/cli/dist/src/index.js +13 -3
- package/packages/@monomind/cli/dist/src/init/executor.js +19 -5
- package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +3 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +63 -20
- package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +3 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +3 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +3 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +6 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +35 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +24 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.js +13 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +9 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +112 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +2 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +8 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +3 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +15 -5
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +4 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +6 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +38 -16
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.d.ts +2 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +41 -24
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +6 -2
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +32 -10
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +81 -31
- package/packages/@monomind/cli/dist/src/memory/memory-crud.js +26 -2
- package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +3 -2
- package/packages/@monomind/cli/dist/src/memory/memory-migrations.js +2 -0
- package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +4 -1
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +4 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +74 -19
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +3 -1
- package/packages/@monomind/cli/dist/src/orgrt/reporting.d.ts +40 -0
- package/packages/@monomind/cli/dist/src/orgrt/reporting.js +127 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +2 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.js +10 -2
- package/packages/@monomind/cli/dist/src/orgrt/templates.d.ts +16 -0
- package/packages/@monomind/cli/dist/src/orgrt/templates.js +57 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +10 -1
- package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +6 -2
- package/packages/@monomind/cli/dist/src/ui/orgs.html +32 -0
- package/packages/@monomind/cli/dist/src/ui/server.mjs +25 -0
- package/packages/@monomind/cli/dist/src/update/executor.js +3 -1
- package/packages/@monomind/cli/dist/src/update/rate-limiter.js +3 -1
- package/packages/@monomind/cli/package.json +3 -3
|
@@ -4,5 +4,25 @@
|
|
|
4
4
|
* Tool definitions for task management with file persistence.
|
|
5
5
|
*/
|
|
6
6
|
import { type MCPTool } from './types.js';
|
|
7
|
+
interface TaskRecord {
|
|
8
|
+
taskId: string;
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
priority: 'low' | 'normal' | 'high' | 'critical';
|
|
12
|
+
status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
|
|
13
|
+
progress: number;
|
|
14
|
+
assignedTo: string[];
|
|
15
|
+
tags: string[];
|
|
16
|
+
createdAt: string;
|
|
17
|
+
startedAt: string | null;
|
|
18
|
+
completedAt: string | null;
|
|
19
|
+
result?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
interface TaskStore {
|
|
22
|
+
tasks: Record<string, TaskRecord>;
|
|
23
|
+
version: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function loadTaskStore(): TaskStore;
|
|
7
26
|
export declare const taskTools: MCPTool[];
|
|
27
|
+
export {};
|
|
8
28
|
//# sourceMappingURL=task-tools.d.ts.map
|
|
@@ -23,7 +23,7 @@ function ensureTaskDir() {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
const MAX_TASK_STORE_BYTES = 50 * 1024 * 1024;
|
|
26
|
-
function loadTaskStore() {
|
|
26
|
+
export function loadTaskStore() {
|
|
27
27
|
try {
|
|
28
28
|
const path = getTaskPath();
|
|
29
29
|
if (existsSync(path)) {
|
|
@@ -36,8 +36,9 @@ function loadTaskStore() {
|
|
|
36
36
|
return parsed;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
catch {
|
|
40
|
-
|
|
39
|
+
catch (e) {
|
|
40
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
41
|
+
console.error('[loadTaskStore] failed to read/parse task store:', e);
|
|
41
42
|
}
|
|
42
43
|
return { tasks: {}, version: '3.0.0' };
|
|
43
44
|
}
|
|
@@ -273,8 +274,9 @@ export const taskTools = [
|
|
|
273
274
|
writeFileSync(tmpAgent1, JSON.stringify(agentStore, null, 2), 'utf-8');
|
|
274
275
|
renameSync(tmpAgent1, agentStorePath);
|
|
275
276
|
}
|
|
276
|
-
catch {
|
|
277
|
-
|
|
277
|
+
catch (e) {
|
|
278
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
279
|
+
console.error('[task_complete] agent store sync failed:', e);
|
|
278
280
|
}
|
|
279
281
|
}
|
|
280
282
|
return {
|
|
@@ -372,15 +374,30 @@ export const taskTools = [
|
|
|
372
374
|
return { taskId, error: 'Task not found' };
|
|
373
375
|
}
|
|
374
376
|
const previouslyAssigned = [...task.assignedTo];
|
|
375
|
-
// Load agent store to sync worker state
|
|
377
|
+
// Load agent store to sync worker state. Distinguish "file doesn't
|
|
378
|
+
// exist yet" (safe to proceed with an empty store) from "file exists
|
|
379
|
+
// but failed to read/parse" (a real store that's momentarily corrupt
|
|
380
|
+
// or oversized) — the latter must NOT fall through to the unconditional
|
|
381
|
+
// write-back below, or a transient read failure silently wipes out
|
|
382
|
+
// every agent's real on-disk state.
|
|
376
383
|
const agentStorePath = join(getMonomindDataRoot(), 'agents', 'store.json');
|
|
377
384
|
let agentStore = { agents: {} };
|
|
385
|
+
let agentStoreReadFailed = false;
|
|
378
386
|
try {
|
|
379
|
-
if (existsSync(agentStorePath)
|
|
380
|
-
|
|
387
|
+
if (existsSync(agentStorePath)) {
|
|
388
|
+
if (statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
|
|
389
|
+
agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
agentStoreReadFailed = true;
|
|
393
|
+
}
|
|
381
394
|
}
|
|
382
395
|
}
|
|
383
|
-
catch {
|
|
396
|
+
catch (e) {
|
|
397
|
+
agentStoreReadFailed = true;
|
|
398
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
399
|
+
console.error('[task_assign] failed to read agent store — skipping agent-store sync to avoid overwriting it with an empty one:', e);
|
|
400
|
+
}
|
|
384
401
|
// Reject IDs that would mutate Object.prototype when used as a key in
|
|
385
402
|
// the JSON-loaded plain object `agentStore.agents`.
|
|
386
403
|
const FORBIDDEN_AGENT_IDS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
@@ -422,19 +439,24 @@ export const taskTools = [
|
|
|
422
439
|
}
|
|
423
440
|
}
|
|
424
441
|
saveTaskStore(store);
|
|
425
|
-
// Save agent store
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
442
|
+
// Save agent store — skipped when the read above failed, so a
|
|
443
|
+
// transient corrupt/oversized store.json is left alone instead of
|
|
444
|
+
// being overwritten with an empty one.
|
|
445
|
+
if (!agentStoreReadFailed) {
|
|
446
|
+
const agentDir = join(getMonomindDataRoot(), 'agents');
|
|
447
|
+
if (!existsSync(agentDir)) {
|
|
448
|
+
mkdirSync(agentDir, { recursive: true });
|
|
449
|
+
}
|
|
450
|
+
const tmpAgent2 = `${agentStorePath}.${process.pid}.${Date.now()}.tmp`;
|
|
451
|
+
writeFileSync(tmpAgent2, JSON.stringify(agentStore, null, 2), 'utf-8');
|
|
452
|
+
renameSync(tmpAgent2, agentStorePath);
|
|
429
453
|
}
|
|
430
|
-
const tmpAgent2 = `${agentStorePath}.${process.pid}.${Date.now()}.tmp`;
|
|
431
|
-
writeFileSync(tmpAgent2, JSON.stringify(agentStore, null, 2), 'utf-8');
|
|
432
|
-
renameSync(tmpAgent2, agentStorePath);
|
|
433
454
|
return {
|
|
434
455
|
taskId: task.taskId,
|
|
435
456
|
assignedTo: task.assignedTo,
|
|
436
457
|
previouslyAssigned,
|
|
437
458
|
status: task.status,
|
|
459
|
+
...(agentStoreReadFailed ? { agentStoreSyncSkipped: true } : {}),
|
|
438
460
|
};
|
|
439
461
|
},
|
|
440
462
|
},
|
|
@@ -4,5 +4,7 @@
|
|
|
4
4
|
* Terminal session management with real command execution.
|
|
5
5
|
*/
|
|
6
6
|
import type { MCPTool } from './types.js';
|
|
7
|
+
/** Returns a copy of `env` with secret-shaped variable names removed. */
|
|
8
|
+
export declare function filterSecretEnvVars(env: NodeJS.ProcessEnv): Record<string, string>;
|
|
7
9
|
export declare const terminalTools: MCPTool[];
|
|
8
10
|
//# sourceMappingURL=terminal-tools.d.ts.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { getProjectCwd } from './types.js';
|
|
2
|
-
import { existsSync, readFileSync, statSync
|
|
2
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
6
|
import { randomBytes } from 'node:crypto';
|
|
7
|
+
import { writeJsonFileAtomic } from '../utils/json-file.js';
|
|
7
8
|
// Storage paths
|
|
8
9
|
const STORAGE_DIR = '.monomind';
|
|
9
10
|
const TERMINAL_DIR = 'terminals';
|
|
@@ -24,7 +25,7 @@ const TERMINAL_FILE = 'store.json';
|
|
|
24
25
|
// is layered on *after* the filtered process.env below).
|
|
25
26
|
const SECRET_ENV_NAME_PATTERN = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|_AUTH$|^AUTH_|PRIVATE_KEY|ACCESS_KEY)/i;
|
|
26
27
|
/** Returns a copy of `env` with secret-shaped variable names removed. */
|
|
27
|
-
function filterSecretEnvVars(env) {
|
|
28
|
+
export function filterSecretEnvVars(env) {
|
|
28
29
|
const filtered = {};
|
|
29
30
|
for (const [k, v] of Object.entries(env)) {
|
|
30
31
|
if (v === undefined)
|
|
@@ -41,32 +42,39 @@ function getTerminalDir() {
|
|
|
41
42
|
function getTerminalPath() {
|
|
42
43
|
return join(getTerminalDir(), TERMINAL_FILE);
|
|
43
44
|
}
|
|
44
|
-
function ensureTerminalDir() {
|
|
45
|
-
const dir = getTerminalDir();
|
|
46
|
-
if (!existsSync(dir)) {
|
|
47
|
-
mkdirSync(dir, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
45
|
const MAX_TERMINAL_STORE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
51
|
-
|
|
46
|
+
// Same hardened-loader convention as agent-tools.ts's loadAgentStoreOrNull:
|
|
47
|
+
// handlers that mutate and save must use the null-aware variant, since
|
|
48
|
+
// treating a corrupt/oversized store as empty and then saving that back
|
|
49
|
+
// would silently wipe every real session. Also guards against a __proto__
|
|
50
|
+
// key in the parsed JSON (agent-tools.ts's loader does the same). Can't
|
|
51
|
+
// delegate to utils/json-file.ts's readJsonFileSync here — it collapses
|
|
52
|
+
// "file absent" and "file corrupt" into the same fallback value, but this
|
|
53
|
+
// loader must tell them apart (absent → empty default is safe to build on
|
|
54
|
+
// and save; corrupt → null, refuse to save over it).
|
|
55
|
+
function loadTerminalStoreOrNull() {
|
|
52
56
|
try {
|
|
53
57
|
const path = getTerminalPath();
|
|
54
|
-
if (existsSync(path)
|
|
55
|
-
return
|
|
56
|
-
|
|
58
|
+
if (!existsSync(path))
|
|
59
|
+
return { sessions: {}, version: '3.0.0' };
|
|
60
|
+
if (statSync(path).size > MAX_TERMINAL_STORE_BYTES)
|
|
61
|
+
return null;
|
|
62
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
63
|
+
if (parsed && typeof parsed === 'object' && Object.prototype.hasOwnProperty.call(parsed, '__proto__'))
|
|
64
|
+
return null;
|
|
65
|
+
return parsed;
|
|
57
66
|
}
|
|
58
|
-
catch {
|
|
59
|
-
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
69
|
+
console.error('[loadTerminalStore] terminal store unreadable/corrupt:', e);
|
|
70
|
+
return null;
|
|
60
71
|
}
|
|
61
|
-
|
|
72
|
+
}
|
|
73
|
+
function loadTerminalStore() {
|
|
74
|
+
return loadTerminalStoreOrNull() ?? { sessions: {}, version: '3.0.0' };
|
|
62
75
|
}
|
|
63
76
|
function saveTerminalStore(store) {
|
|
64
|
-
|
|
65
|
-
// Unique tmp filename so concurrent handler invocations cannot clobber each
|
|
66
|
-
// other's .tmp mid-write (which would produce a partial JSON on rename).
|
|
67
|
-
const tmpPath = `${getTerminalPath()}.${process.pid}.${Date.now()}.tmp`;
|
|
68
|
-
writeFileSync(tmpPath, JSON.stringify(store, null, 2), 'utf-8');
|
|
69
|
-
renameSync(tmpPath, getTerminalPath());
|
|
77
|
+
writeJsonFileAtomic(getTerminalPath(), store);
|
|
70
78
|
}
|
|
71
79
|
export const terminalTools = [
|
|
72
80
|
{
|
|
@@ -82,7 +90,10 @@ export const terminalTools = [
|
|
|
82
90
|
},
|
|
83
91
|
},
|
|
84
92
|
handler: async (input) => {
|
|
85
|
-
const store =
|
|
93
|
+
const store = loadTerminalStoreOrNull();
|
|
94
|
+
if (!store) {
|
|
95
|
+
return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to create a session to avoid overwriting real session data.' };
|
|
96
|
+
}
|
|
86
97
|
const MAX_SESSIONS = 1000;
|
|
87
98
|
if (Object.keys(store.sessions).length >= MAX_SESSIONS) {
|
|
88
99
|
return { success: false, error: 'Session limit reached' };
|
|
@@ -167,7 +178,10 @@ export const terminalTools = [
|
|
|
167
178
|
required: ['command'],
|
|
168
179
|
},
|
|
169
180
|
handler: async (input) => {
|
|
170
|
-
const store =
|
|
181
|
+
const store = loadTerminalStoreOrNull();
|
|
182
|
+
if (!store) {
|
|
183
|
+
return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to execute to avoid overwriting real session data.' };
|
|
184
|
+
}
|
|
171
185
|
const sessionId = input.sessionId;
|
|
172
186
|
// Cap command: the metacharacter regex check at line 220 is O(n), and the
|
|
173
187
|
// raw command is stored verbatim in session history (up to 200 entries).
|
|
@@ -313,7 +327,10 @@ export const terminalTools = [
|
|
|
313
327
|
required: ['sessionId'],
|
|
314
328
|
},
|
|
315
329
|
handler: async (input) => {
|
|
316
|
-
const store =
|
|
330
|
+
const store = loadTerminalStoreOrNull();
|
|
331
|
+
if (!store) {
|
|
332
|
+
return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to close a session to avoid overwriting real session data.' };
|
|
333
|
+
}
|
|
317
334
|
const sessionId = input.sessionId;
|
|
318
335
|
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
319
336
|
if (!sessionId || FORBIDDEN_KEYS.has(sessionId)) {
|
|
@@ -70,8 +70,10 @@ export class EWCConsolidator {
|
|
|
70
70
|
this.initialized = true;
|
|
71
71
|
return true;
|
|
72
72
|
}
|
|
73
|
-
catch {
|
|
73
|
+
catch (e) {
|
|
74
74
|
// Start fresh if no persisted state
|
|
75
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
76
|
+
console.error('[ewc-consolidation] failed to load persisted state, starting fresh:', e);
|
|
75
77
|
this.initialized = true;
|
|
76
78
|
return true;
|
|
77
79
|
}
|
|
@@ -544,8 +546,10 @@ export class EWCConsolidator {
|
|
|
544
546
|
fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
545
547
|
fs.renameSync(tmp, this.config.storagePath);
|
|
546
548
|
}
|
|
547
|
-
catch {
|
|
549
|
+
catch (e) {
|
|
548
550
|
// Silently fail - persistence is best-effort
|
|
551
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
552
|
+
console.error('[ewc-consolidation] failed to persist state:', e);
|
|
549
553
|
}
|
|
550
554
|
}
|
|
551
555
|
/**
|
|
@@ -454,8 +454,10 @@ class LocalReasoningBank {
|
|
|
454
454
|
// ReasoningBank via intelligence.ts's public API, so no separate
|
|
455
455
|
// models.json bridge is needed.
|
|
456
456
|
}
|
|
457
|
-
catch {
|
|
457
|
+
catch (e) {
|
|
458
458
|
// Ignore load errors, start fresh
|
|
459
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
460
|
+
console.error('[intelligence] failed to load patterns.json, starting fresh:', e);
|
|
459
461
|
}
|
|
460
462
|
}
|
|
461
463
|
/**
|
|
@@ -680,8 +682,10 @@ function loadPersistedStats() {
|
|
|
680
682
|
}
|
|
681
683
|
}
|
|
682
684
|
}
|
|
683
|
-
catch {
|
|
685
|
+
catch (e) {
|
|
684
686
|
// Ignore load errors, start fresh
|
|
687
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
688
|
+
console.error('[intelligence] failed to load stats.json, starting fresh:', e);
|
|
685
689
|
}
|
|
686
690
|
}
|
|
687
691
|
/**
|
|
@@ -693,8 +697,10 @@ function savePersistedStats() {
|
|
|
693
697
|
const path = getStatsPath();
|
|
694
698
|
writeFileSync(path, JSON.stringify(globalStats, null, 2), 'utf-8');
|
|
695
699
|
}
|
|
696
|
-
catch {
|
|
700
|
+
catch (e) {
|
|
697
701
|
// Ignore save errors
|
|
702
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
703
|
+
console.error('[intelligence] failed to save stats.json:', e);
|
|
698
704
|
}
|
|
699
705
|
}
|
|
700
706
|
// ============================================================================
|
|
@@ -762,7 +768,11 @@ async function _doInitializeIntelligence(config) {
|
|
|
762
768
|
}
|
|
763
769
|
}
|
|
764
770
|
}
|
|
765
|
-
catch {
|
|
771
|
+
catch (e) {
|
|
772
|
+
/* neural patterns file unreadable — skip */
|
|
773
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
774
|
+
console.error('[intelligence] failed to seed patterns.json into ReasoningBank:', e);
|
|
775
|
+
}
|
|
766
776
|
}
|
|
767
777
|
// Seed SONA routing patterns into the ReasoningBank so keyword-based
|
|
768
778
|
// routing knowledge from .swarm/sona-patterns.json participates in
|
|
@@ -890,7 +900,9 @@ export async function recordStep(step) {
|
|
|
890
900
|
globalStats.trajectoriesRecorded++;
|
|
891
901
|
return true;
|
|
892
902
|
}
|
|
893
|
-
catch {
|
|
903
|
+
catch (e) {
|
|
904
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
905
|
+
console.error('[intelligence] recordStep failed:', e);
|
|
894
906
|
return false;
|
|
895
907
|
}
|
|
896
908
|
}
|
|
@@ -923,7 +935,9 @@ export async function recordTrajectory(steps, verdict) {
|
|
|
923
935
|
savePersistedStats();
|
|
924
936
|
return true;
|
|
925
937
|
}
|
|
926
|
-
catch {
|
|
938
|
+
catch (e) {
|
|
939
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
940
|
+
console.error('[intelligence] recordTrajectory failed:', e);
|
|
927
941
|
return false;
|
|
928
942
|
}
|
|
929
943
|
}
|
|
@@ -980,7 +994,9 @@ export async function findSimilarPatterns(query, options) {
|
|
|
980
994
|
similarity: r.similarity ?? r.confidence ?? 0.5
|
|
981
995
|
}));
|
|
982
996
|
}
|
|
983
|
-
catch {
|
|
997
|
+
catch (e) {
|
|
998
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
999
|
+
console.error('[intelligence] findSimilarPatterns failed:', e);
|
|
984
1000
|
return [];
|
|
985
1001
|
}
|
|
986
1002
|
}
|
|
@@ -1030,7 +1046,9 @@ export async function endTrajectoryWithVerdict(verdict) {
|
|
|
1030
1046
|
savePersistedStats();
|
|
1031
1047
|
return result;
|
|
1032
1048
|
}
|
|
1033
|
-
catch {
|
|
1049
|
+
catch (e) {
|
|
1050
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
1051
|
+
console.error('[intelligence] endTrajectoryWithVerdict failed:', e);
|
|
1034
1052
|
return null;
|
|
1035
1053
|
}
|
|
1036
1054
|
}
|
|
@@ -1052,7 +1070,9 @@ export async function distillLearning() {
|
|
|
1052
1070
|
savePersistedStats();
|
|
1053
1071
|
return result;
|
|
1054
1072
|
}
|
|
1055
|
-
catch {
|
|
1073
|
+
catch (e) {
|
|
1074
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
1075
|
+
console.error('[intelligence] distillLearning failed:', e);
|
|
1056
1076
|
return null;
|
|
1057
1077
|
}
|
|
1058
1078
|
}
|
|
@@ -1184,7 +1204,9 @@ function loadSonaRoutingPatterns() {
|
|
|
1184
1204
|
}
|
|
1185
1205
|
return results;
|
|
1186
1206
|
}
|
|
1187
|
-
catch {
|
|
1207
|
+
catch (e) {
|
|
1208
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
1209
|
+
console.error('[intelligence] failed to load .swarm/sona-patterns.json:', e);
|
|
1188
1210
|
return [];
|
|
1189
1211
|
}
|
|
1190
1212
|
}
|
|
@@ -121,6 +121,15 @@ let bridgeAvailable = null;
|
|
|
121
121
|
let _embedder = null;
|
|
122
122
|
const MAX_INIT_ATTEMPTS = 3;
|
|
123
123
|
let initAttempts = 0;
|
|
124
|
+
/** Flush after mutations: the sql.js fallback backend is in-memory WASM and
|
|
125
|
+
* only reaches disk via persist(); the CLI process is short-lived, so waiting
|
|
126
|
+
* for an auto-persist interval would lose writes. No-op on better-sqlite3. */
|
|
127
|
+
async function flushBackend(backend) {
|
|
128
|
+
try {
|
|
129
|
+
await backend?.persist?.();
|
|
130
|
+
}
|
|
131
|
+
catch { /* best effort */ }
|
|
132
|
+
}
|
|
124
133
|
async function getBackend(dbPath) {
|
|
125
134
|
if (bridgeAvailable === false)
|
|
126
135
|
return null;
|
|
@@ -134,7 +143,6 @@ async function getBackend(dbPath) {
|
|
|
134
143
|
backendPromise = (async () => {
|
|
135
144
|
try {
|
|
136
145
|
const mod = await import('@monoes/memory');
|
|
137
|
-
const { LanceDBBackend } = mod;
|
|
138
146
|
// Try to create embedding generator from HuggingFace transformers
|
|
139
147
|
let embeddingGenerator;
|
|
140
148
|
try {
|
|
@@ -148,27 +156,43 @@ async function getBackend(dbPath) {
|
|
|
148
156
|
};
|
|
149
157
|
_embedder = embeddingGenerator;
|
|
150
158
|
}
|
|
151
|
-
catch {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
catch (e) {
|
|
160
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
161
|
+
console.error('[memory-bridge] embedding model failed to load — store and search without vectors:', e);
|
|
162
|
+
}
|
|
163
|
+
// Local SQLite engine (LanceDB replaced 2026-07): better-sqlite3 when its
|
|
164
|
+
// native binding loads, sql.js (pure WASM) otherwise — both persist text
|
|
165
|
+
// AND embeddings, so vectors are always recomputable/derivable data.
|
|
166
|
+
// getDbPath keeps its traversal guard and directory semantics; the SQLite
|
|
167
|
+
// file lives inside that directory.
|
|
168
|
+
const dir = getDbPath(dbPath);
|
|
169
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
170
|
+
const cfg = {
|
|
171
|
+
databasePath: path.join(dir, 'memory.db'),
|
|
172
|
+
walMode: true,
|
|
173
|
+
optimize: true,
|
|
174
|
+
defaultNamespace: 'default',
|
|
159
175
|
embeddingGenerator,
|
|
160
|
-
|
|
161
|
-
nProbes: 20,
|
|
162
|
-
});
|
|
176
|
+
};
|
|
163
177
|
const origLog = console.log;
|
|
164
178
|
console.log = (...args) => {
|
|
165
179
|
const msg = String(args[0] ?? '');
|
|
166
|
-
if (msg.includes('Transformers.js') || msg.includes('
|
|
180
|
+
if (msg.includes('Transformers.js') || msg.includes('Loading model'))
|
|
167
181
|
return;
|
|
168
182
|
origLog.apply(console, args);
|
|
169
183
|
};
|
|
184
|
+
let backend;
|
|
170
185
|
try {
|
|
171
|
-
|
|
186
|
+
try {
|
|
187
|
+
backend = new mod.SQLiteBackend(cfg);
|
|
188
|
+
await backend.initialize();
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
192
|
+
console.error('[memory-bridge] better-sqlite3 unavailable — using sql.js backend:', e);
|
|
193
|
+
backend = new mod.SqlJsBackend(cfg);
|
|
194
|
+
await backend.initialize();
|
|
195
|
+
}
|
|
172
196
|
}
|
|
173
197
|
finally {
|
|
174
198
|
console.log = origLog;
|
|
@@ -212,7 +236,10 @@ export async function bridgeStoreEntry(options) {
|
|
|
212
236
|
embedding = await _embedder(value);
|
|
213
237
|
embeddingInfo = { dimensions: embedding.length, model: BRIDGE_EMBEDDING_MODEL };
|
|
214
238
|
}
|
|
215
|
-
catch {
|
|
239
|
+
catch (e) {
|
|
240
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
241
|
+
console.error('[memory-bridge] embedding generation failed — storing entry without embedding:', e);
|
|
242
|
+
}
|
|
216
243
|
}
|
|
217
244
|
const mod = await import('@monoes/memory');
|
|
218
245
|
const entry = mod.createDefaultEntry({
|
|
@@ -233,7 +260,10 @@ export async function bridgeStoreEntry(options) {
|
|
|
233
260
|
if (existing)
|
|
234
261
|
await backend.delete(existing.id);
|
|
235
262
|
}
|
|
236
|
-
catch {
|
|
263
|
+
catch (e) {
|
|
264
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
265
|
+
console.error('[memory-bridge] upsert failed to delete existing entry — may create a duplicate:', e);
|
|
266
|
+
}
|
|
237
267
|
}
|
|
238
268
|
// Dedup gate: skip if a near-duplicate already exists
|
|
239
269
|
const automemCfg = getAutomemConfig();
|
|
@@ -247,6 +277,7 @@ export async function bridgeStoreEntry(options) {
|
|
|
247
277
|
catch { /* non-fatal — store anyway */ }
|
|
248
278
|
}
|
|
249
279
|
await backend.store(entry);
|
|
280
|
+
await flushBackend(backend);
|
|
250
281
|
return { success: true, id, embedding: embeddingInfo };
|
|
251
282
|
}
|
|
252
283
|
catch (err) {
|
|
@@ -293,20 +324,31 @@ export async function bridgeSearchEntries(options) {
|
|
|
293
324
|
namespace: namespace ?? 'default',
|
|
294
325
|
limit: 50000,
|
|
295
326
|
});
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
327
|
+
// Token-based matching, not whole-phrase substring: "semantic test" must
|
|
328
|
+
// match an entry keyed "semantic-test" (the old .includes(query) required
|
|
329
|
+
// the exact phrase — including its whitespace — to appear verbatim).
|
|
330
|
+
// Score = fraction of query tokens present in key+content.
|
|
331
|
+
const tokens = queryStr.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length > 1);
|
|
332
|
+
if (tokens.length) {
|
|
333
|
+
results = entries
|
|
334
|
+
.map((e) => {
|
|
335
|
+
const haystack = `${e.key || ''} ${e.content || ''}`.toLowerCase();
|
|
336
|
+
const hits = tokens.filter(t => haystack.includes(t)).length;
|
|
337
|
+
return { e, score: hits / tokens.length };
|
|
338
|
+
})
|
|
339
|
+
.filter((x) => x.score > 0)
|
|
340
|
+
.sort((a, b) => b.score - a.score)
|
|
341
|
+
.slice(0, limit)
|
|
342
|
+
.map(({ e, score }) => ({
|
|
343
|
+
id: e.id,
|
|
344
|
+
key: e.key,
|
|
345
|
+
content: e.content || '',
|
|
346
|
+
score: Math.min(0.9, 0.3 + score * 0.6),
|
|
347
|
+
namespace: e.namespace,
|
|
348
|
+
provenance: `keyword:${score.toFixed(2)}`,
|
|
349
|
+
_createdAt: e.createdAt || 0,
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
310
352
|
searchMethod = 'keyword';
|
|
311
353
|
}
|
|
312
354
|
// Filter stale entries based on automem config — skip for knowledge
|
|
@@ -404,6 +446,8 @@ export async function bridgeDeleteEntry(options) {
|
|
|
404
446
|
if (entry)
|
|
405
447
|
deleted = await backend.delete(entry.id);
|
|
406
448
|
}
|
|
449
|
+
if (deleted)
|
|
450
|
+
await flushBackend(backend);
|
|
407
451
|
return { success: true, deleted };
|
|
408
452
|
}
|
|
409
453
|
catch {
|
|
@@ -644,7 +688,10 @@ export async function bridgeSessionEnd(options) {
|
|
|
644
688
|
try {
|
|
645
689
|
data = JSON.parse(existing.content);
|
|
646
690
|
}
|
|
647
|
-
catch {
|
|
691
|
+
catch (e) {
|
|
692
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
693
|
+
console.error('[memory-bridge] session content failed to parse — ending session with empty prior state:', e);
|
|
694
|
+
}
|
|
648
695
|
await backend.update(existing.id, {
|
|
649
696
|
content: JSON.stringify({
|
|
650
697
|
...data,
|
|
@@ -655,6 +702,7 @@ export async function bridgeSessionEnd(options) {
|
|
|
655
702
|
}),
|
|
656
703
|
tags: ['session', 'ended'],
|
|
657
704
|
});
|
|
705
|
+
await flushBackend(backend);
|
|
658
706
|
}
|
|
659
707
|
return { success: true };
|
|
660
708
|
}
|
|
@@ -746,6 +794,8 @@ export async function bridgeConsolidate(params) {
|
|
|
746
794
|
deleted++;
|
|
747
795
|
}
|
|
748
796
|
}
|
|
797
|
+
if (deleted)
|
|
798
|
+
await flushBackend(backend);
|
|
749
799
|
return { success: true, consolidated: deleted };
|
|
750
800
|
}
|
|
751
801
|
catch {
|
|
@@ -238,8 +238,32 @@ export async function storeEntry(options) {
|
|
|
238
238
|
}
|
|
239
239
|
const fileBuffer = fs.readFileSync(dbPath);
|
|
240
240
|
const db = new SQL.Database(fileBuffer);
|
|
241
|
-
|
|
241
|
+
// memory_entries has no UNIQUE constraint on (key, namespace) — only
|
|
242
|
+
// `id TEXT PRIMARY KEY`. `INSERT OR REPLACE` only replaces a row when
|
|
243
|
+
// its PRIMARY KEY collides, so generating a fresh id on every call (as
|
|
244
|
+
// this used to do unconditionally) meant upsert never actually matched
|
|
245
|
+
// the existing row for a given key+namespace — it silently inserted a
|
|
246
|
+
// duplicate instead of replacing it. When upserting, look up the
|
|
247
|
+
// existing row's real id first and reuse it so the replace actually
|
|
248
|
+
// collides; only mint a new id when no existing row is found.
|
|
249
|
+
let id = `entry_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
250
|
+
let existingCreatedAt = null;
|
|
251
|
+
if (upsert) {
|
|
252
|
+
// Only match active rows — a soft-deleted row's key+namespace should
|
|
253
|
+
// not be resurrected by an unrelated upsert; that would both restore
|
|
254
|
+
// history a delete intentionally cleared and mask the deletion.
|
|
255
|
+
const existingIdResult = db.exec("SELECT id, created_at FROM memory_entries WHERE key = ? AND namespace = ? AND status = 'active' LIMIT 1", [key, namespace]);
|
|
256
|
+
const existingRow = existingIdResult[0]?.values?.[0];
|
|
257
|
+
const existingId = existingRow?.[0];
|
|
258
|
+
if (typeof existingId === 'string') {
|
|
259
|
+
id = existingId;
|
|
260
|
+
const createdAt = existingRow?.[1];
|
|
261
|
+
if (typeof createdAt === 'number')
|
|
262
|
+
existingCreatedAt = createdAt;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
242
265
|
const now = Date.now();
|
|
266
|
+
const createdAt = existingCreatedAt ?? now;
|
|
243
267
|
let embeddingJson = null;
|
|
244
268
|
let embeddingDimensions = null;
|
|
245
269
|
let embeddingModel = null;
|
|
@@ -270,7 +294,7 @@ export async function storeEntry(options) {
|
|
|
270
294
|
embeddingModel,
|
|
271
295
|
tags.length > 0 ? JSON.stringify(tags) : null,
|
|
272
296
|
'{}',
|
|
273
|
-
|
|
297
|
+
createdAt,
|
|
274
298
|
now,
|
|
275
299
|
ttl ? now + (ttl * 1000) : null
|
|
276
300
|
]);
|
|
@@ -110,8 +110,9 @@ async function activateControllerRegistry(dbPath, verbose) {
|
|
|
110
110
|
console.log(`ControllerRegistry: ${activated.length} controllers activated`);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
catch {
|
|
114
|
-
|
|
113
|
+
catch (e) {
|
|
114
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
115
|
+
console.error('[activateControllers] ControllerRegistry activation failed:', e);
|
|
115
116
|
}
|
|
116
117
|
return { activated, failed, initTimeMs: performance.now() - startTime };
|
|
117
118
|
}
|
|
@@ -57,6 +57,8 @@ export async function ensureSchemaColumns(dbPath) {
|
|
|
57
57
|
}
|
|
58
58
|
catch (e) {
|
|
59
59
|
// Column might already exist or other error - continue
|
|
60
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
61
|
+
console.error(`[ensureSchemaColumns] failed to add column '${col.name}':`, e);
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -41,7 +41,10 @@ export async function recordCommand(baseDir, cmd) {
|
|
|
41
41
|
await fs.writeFile(path, lines.slice(-MAX_COMMAND_RECORDS).join('\n') + '\n', 'utf8');
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
catch {
|
|
44
|
+
catch (e) {
|
|
45
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
46
|
+
console.error('[recordCommand] failed to record command outcome:', e);
|
|
47
|
+
}
|
|
45
48
|
}
|
|
46
49
|
/**
|
|
47
50
|
* Derive a measured success signal from recent command outcomes.
|