monomind 2.2.0 → 2.3.0
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/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 +83 -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 +120 -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 +16 -4
- 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 +2 -2
|
@@ -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
|
}
|
|
@@ -148,7 +148,10 @@ async function getBackend(dbPath) {
|
|
|
148
148
|
};
|
|
149
149
|
_embedder = embeddingGenerator;
|
|
150
150
|
}
|
|
151
|
-
catch {
|
|
151
|
+
catch (e) {
|
|
152
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
153
|
+
console.error('[memory-bridge] embedding model failed to load — store and search without vectors:', e);
|
|
154
|
+
}
|
|
152
155
|
const backend = new LanceDBBackend({
|
|
153
156
|
// Always route through getDbPath's traversal guard — dbPath here can
|
|
154
157
|
// originate from a caller/tool-supplied value, and getDbPath(undefined)
|
|
@@ -212,7 +215,10 @@ export async function bridgeStoreEntry(options) {
|
|
|
212
215
|
embedding = await _embedder(value);
|
|
213
216
|
embeddingInfo = { dimensions: embedding.length, model: BRIDGE_EMBEDDING_MODEL };
|
|
214
217
|
}
|
|
215
|
-
catch {
|
|
218
|
+
catch (e) {
|
|
219
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
220
|
+
console.error('[memory-bridge] embedding generation failed — storing entry without embedding:', e);
|
|
221
|
+
}
|
|
216
222
|
}
|
|
217
223
|
const mod = await import('@monoes/memory');
|
|
218
224
|
const entry = mod.createDefaultEntry({
|
|
@@ -233,7 +239,10 @@ export async function bridgeStoreEntry(options) {
|
|
|
233
239
|
if (existing)
|
|
234
240
|
await backend.delete(existing.id);
|
|
235
241
|
}
|
|
236
|
-
catch {
|
|
242
|
+
catch (e) {
|
|
243
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
244
|
+
console.error('[memory-bridge] upsert failed to delete existing entry — may create a duplicate:', e);
|
|
245
|
+
}
|
|
237
246
|
}
|
|
238
247
|
// Dedup gate: skip if a near-duplicate already exists
|
|
239
248
|
const automemCfg = getAutomemConfig();
|
|
@@ -644,7 +653,10 @@ export async function bridgeSessionEnd(options) {
|
|
|
644
653
|
try {
|
|
645
654
|
data = JSON.parse(existing.content);
|
|
646
655
|
}
|
|
647
|
-
catch {
|
|
656
|
+
catch (e) {
|
|
657
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
658
|
+
console.error('[memory-bridge] session content failed to parse — ending session with empty prior state:', e);
|
|
659
|
+
}
|
|
648
660
|
await backend.update(existing.id, {
|
|
649
661
|
content: JSON.stringify({
|
|
650
662
|
...data,
|
|
@@ -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.
|
|
@@ -36,7 +36,10 @@ export class OrgBus {
|
|
|
36
36
|
this.pending = this.pending.then(async () => {
|
|
37
37
|
await mkdir(this.dir, { recursive: true });
|
|
38
38
|
await appendFile(this.file, JSON.stringify(e) + '\n', 'utf8');
|
|
39
|
-
}).catch(() => {
|
|
39
|
+
}).catch((err) => {
|
|
40
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
41
|
+
console.error('[org-bus] event write failed:', err);
|
|
42
|
+
});
|
|
40
43
|
for (const fn of this.listeners) {
|
|
41
44
|
try {
|
|
42
45
|
fn(e);
|
|
@@ -10,6 +10,7 @@ import { attachForwarder } from './forwarder.js';
|
|
|
10
10
|
import { BrokerLease, lookupOrg } from './broker.js';
|
|
11
11
|
import { queueMessage, drainInbox } from './inbox.js';
|
|
12
12
|
import { OrgDefSchema, ORG_DIR } from './types.js';
|
|
13
|
+
import { summarizeRun, readRunEvents, readHistory, historyFile } from './reporting.js';
|
|
13
14
|
export class OrgDaemon {
|
|
14
15
|
root;
|
|
15
16
|
opts;
|
|
@@ -66,33 +67,74 @@ export class OrgDaemon {
|
|
|
66
67
|
const mailbox = new Mailbox();
|
|
67
68
|
const policy = new PolicyEngine(role.id, { maxTokens: perRoleBudget, ...(role.policy ?? {}) }, bus, cwd);
|
|
68
69
|
const runtime = { mailbox, policy, status: 'running', done: Promise.resolve() };
|
|
69
|
-
|
|
70
|
+
const sessionOpts = {
|
|
70
71
|
org: name, role, bus, policy, mailbox, cwd, def,
|
|
71
72
|
maxTurns: def.run_config.max_turns_per_message,
|
|
72
73
|
deliver: (from, to, subject, body) => this.deliver(name, from, to, subject, body),
|
|
73
|
-
askHuman: (
|
|
74
|
+
askHuman: (r, question) => this.askHuman(name, r, question),
|
|
75
|
+
onComplete: (r, outcome, summary) => {
|
|
76
|
+
bus.emit({ type: 'status', from: r, reason: 'org-complete', msg: `run outcome: ${outcome}`, data: { outcome, summary } });
|
|
77
|
+
},
|
|
74
78
|
queryFn: this.opts.queryFn,
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
};
|
|
80
|
+
// Supervised session: transient crashes (provider blips, network) restart
|
|
81
|
+
// with backoff; a crash with the mailbox already closed, or one that
|
|
82
|
+
// exhausts the retry budget, is terminal. runAgentSession already emits a
|
|
83
|
+
// 'status' event for the raw error; the terminal 'audit' event is for
|
|
84
|
+
// dashboards/alerts that filter on actionable failures (not routine
|
|
85
|
+
// status chatter) so a dead agent surfaces instead of a run that
|
|
86
|
+
// silently never progresses.
|
|
87
|
+
const BACKOFFS_MS = [1000, 5000, 15000];
|
|
88
|
+
runtime.done = (async () => {
|
|
89
|
+
for (let attempt = 0;; attempt++) {
|
|
90
|
+
try {
|
|
91
|
+
await runAgentSession(sessionOpts);
|
|
92
|
+
runtime.status = 'ended';
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
97
|
+
const crash = () => {
|
|
98
|
+
runtime.status = 'crashed';
|
|
99
|
+
runtime.error = message;
|
|
100
|
+
bus.emit({
|
|
101
|
+
type: 'audit', from: role.id,
|
|
102
|
+
msg: `agent "${role.id}" crashed: ${message}`,
|
|
103
|
+
reason: 'agent-session-crash',
|
|
104
|
+
data: { agentId: role.id, error: message, restarts: attempt },
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
if (mailbox.isClosed || attempt >= BACKOFFS_MS.length) {
|
|
108
|
+
crash();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
bus.emit({
|
|
112
|
+
type: 'status', from: role.id, reason: 'agent-restart',
|
|
113
|
+
msg: `agent "${role.id}" crashed (${message}) — restarting in ${BACKOFFS_MS[attempt]}ms (attempt ${attempt + 1}/${BACKOFFS_MS.length})`,
|
|
114
|
+
});
|
|
115
|
+
await new Promise(r => { const t = setTimeout(r, BACKOFFS_MS[attempt]); t.unref?.(); });
|
|
116
|
+
if (mailbox.isClosed) {
|
|
117
|
+
crash();
|
|
118
|
+
return;
|
|
119
|
+
} // org stopped during backoff — never recovered
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
91
123
|
running.agents.set(role.id, runtime);
|
|
92
124
|
}
|
|
93
125
|
const boss = def.roles.find(r => r.type === 'boss' || r.reports_to === null) ?? def.roles[0];
|
|
126
|
+
// Cross-run memory: brief the coordinator on the previous run so scheduled
|
|
127
|
+
// orgs accumulate instead of starting cold every interval.
|
|
128
|
+
const prev = readHistory(this.root, name).at(-1);
|
|
129
|
+
const prevBrief = prev
|
|
130
|
+
? `\n\nPrevious run (${prev.run}${prev.endedAt ? `, ${new Date(prev.endedAt).toISOString()}` : ''}): ` +
|
|
131
|
+
(prev.outcome
|
|
132
|
+
? `outcome "${prev.outcome.status}" — ${prev.outcome.summary}`
|
|
133
|
+
: `no recorded outcome (${prev.messages} messages, ${prev.assets.length} assets${prev.crashes.length ? `, ${prev.crashes.length} crashed agent(s)` : ''})`) +
|
|
134
|
+
`\nBuild on that work — do not redo what is already done.`
|
|
135
|
+
: '';
|
|
94
136
|
running.agents.get(boss.id).mailbox.push(`Org "${name}" started (run ${run}).\nGoal: ${taskOverride ?? def.goal}\n` +
|
|
95
|
-
`Coordinate your team via org_send.
|
|
137
|
+
`Coordinate your team via org_send. When the goal is achieved (or clearly can't be), record it with org_complete, then end your turn.${prevBrief}`);
|
|
96
138
|
bus.emit({ type: 'status', msg: `org started (${def.roles.length} agents)`, data: { goal: taskOverride ?? def.goal } });
|
|
97
139
|
this.persistState(name, 'running', run);
|
|
98
140
|
if (this.opts.crossProcess && this.opts.inboxUrl) {
|
|
@@ -336,6 +378,19 @@ export class OrgDaemon {
|
|
|
336
378
|
}
|
|
337
379
|
org.bus.emit({ type: 'status', msg: 'org stopped' });
|
|
338
380
|
await org.bus.flush();
|
|
381
|
+
// Append this run's summary to <org>/history.jsonl — read back from the
|
|
382
|
+
// flushed bus.jsonl (the full durable record) rather than the bounded
|
|
383
|
+
// in-memory buffer, so long runs summarize completely.
|
|
384
|
+
try {
|
|
385
|
+
const events = readRunEvents(this.root, name, org.run);
|
|
386
|
+
if (events.length) {
|
|
387
|
+
const { appendFileSync } = await import('node:fs');
|
|
388
|
+
appendFileSync(historyFile(this.root, name), JSON.stringify(summarizeRun(events)) + '\n', 'utf8');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
console.error(`org ${name}: could not write run history:`, err instanceof Error ? err.message : err);
|
|
393
|
+
}
|
|
339
394
|
// the "org stopped" event above triggers the forwarder's final org:complete /
|
|
340
395
|
// session:complete POST — without waiting for it here, the CLI process can exit
|
|
341
396
|
// (and kill the in-flight fetch) before that last event reaches the dashboard,
|