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,13 +4,13 @@ import { join, resolve } from 'node:path';
|
|
|
4
4
|
import { output } from '../output.js';
|
|
5
5
|
import { OrgDaemon } from '../orgrt/daemon.js';
|
|
6
6
|
import { startOrgServer } from '../orgrt/server.js';
|
|
7
|
-
import { ORG_DIR } from '../orgrt/types.js';
|
|
7
|
+
import { ORG_DIR, OrgDefSchema } from '../orgrt/types.js';
|
|
8
8
|
const log = (text) => { console.log(text); };
|
|
9
9
|
/** Org names are used to build filesystem paths under .monomind/orgs — reject
|
|
10
10
|
* anything that isn't a plain identifier to prevent path traversal (e.g.
|
|
11
11
|
* `monomind org stop '../../../../tmp/x'`). */
|
|
12
12
|
const ORG_NAME_RE = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
13
|
-
function validateOrgName(name) {
|
|
13
|
+
export function validateOrgName(name) {
|
|
14
14
|
if (!name)
|
|
15
15
|
return { ok: false, result: { success: false, message: 'org name required' } };
|
|
16
16
|
if (!ORG_NAME_RE.test(name)) {
|
|
@@ -30,7 +30,7 @@ const ORG_ARTIFACT_SUFFIXES = [
|
|
|
30
30
|
'-plugins', '-adapters', '-join-requests', '-bootstrap', '-project-workspaces',
|
|
31
31
|
'-approval-comments', '-skills',
|
|
32
32
|
];
|
|
33
|
-
function listOrgConfigFiles(orgsDir) {
|
|
33
|
+
export function listOrgConfigFiles(orgsDir) {
|
|
34
34
|
return readdirSync(orgsDir)
|
|
35
35
|
.filter(f => f.endsWith('.json') && !f.startsWith('._') && !ORG_ARTIFACT_SUFFIXES.some(suf => f.includes(suf)));
|
|
36
36
|
}
|
|
@@ -53,6 +53,32 @@ const runAction = async (ctx) => {
|
|
|
53
53
|
const taskFlag = ctx.flags['task'];
|
|
54
54
|
if (Array.isArray(taskFlag))
|
|
55
55
|
return { success: false, message: '--task was passed more than once — pass it exactly once' };
|
|
56
|
+
// Fail before any side effects (inbox server) when the org doesn't exist.
|
|
57
|
+
const orgsDir = join(ctx.cwd, ORG_DIR);
|
|
58
|
+
if (!existsSync(join(orgsDir, `${name}.json`))) {
|
|
59
|
+
const known = existsSync(orgsDir) ? listOrgConfigFiles(orgsDir).map(f => f.replace(/\.json$/, '')) : [];
|
|
60
|
+
log(output.error(`Org not found: ${name}${known.length ? ` — available: ${known.join(', ')}` : ' — create one with /mastermind:createorg'}`));
|
|
61
|
+
return { success: false, message: 'org not found' };
|
|
62
|
+
}
|
|
63
|
+
if (ctx.flags['dryRun'] === true) {
|
|
64
|
+
// Validate + preview each role's actual briefing without spawning sessions.
|
|
65
|
+
try {
|
|
66
|
+
const def = OrgDefSchema.parse(JSON.parse(readFileSync(join(orgsDir, `${name}.json`), 'utf8')));
|
|
67
|
+
const { buildRolePrompt } = await import('../orgrt/session.js');
|
|
68
|
+
const roster = def.roles.map(r => r.id);
|
|
69
|
+
const perRole = Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length);
|
|
70
|
+
log(output.info(`DRY RUN — org ${name}: ${def.roles.length} roles, ${perRole} tokens each, goal: ${taskFlag ?? def.goal}`));
|
|
71
|
+
for (const role of def.roles) {
|
|
72
|
+
log(output.info(`\n─── ${role.id} (${role.title || role.type})${role.adapter_config?.model ? ` [${role.adapter_config.model}]` : ''} ───`));
|
|
73
|
+
log(buildRolePrompt(role, { name: def.name, goal: taskFlag ?? def.goal }, roster));
|
|
74
|
+
}
|
|
75
|
+
return { success: true, message: 'dry run complete — no sessions started' };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
log(output.error(`Config invalid: ${err instanceof Error ? err.message : String(err)}`));
|
|
79
|
+
return { success: false, message: 'invalid org config' };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
56
82
|
const crossProcess = ctx.flags['crossProcess'] !== false;
|
|
57
83
|
const daemon = new OrgDaemon(ctx.cwd, { crossProcess });
|
|
58
84
|
let srv;
|
|
@@ -60,7 +86,20 @@ const runAction = async (ctx) => {
|
|
|
60
86
|
srv = await startOrgServer(daemon, 0);
|
|
61
87
|
daemon.setInboxUrl(`http://127.0.0.1:${srv.port}`);
|
|
62
88
|
}
|
|
63
|
-
|
|
89
|
+
let running;
|
|
90
|
+
try {
|
|
91
|
+
running = await daemon.startOrg(name, taskFlag);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Don't leave the inbox server holding the event loop open on a failed start.
|
|
95
|
+
srv?.close();
|
|
96
|
+
await daemon.stopAll().catch(() => { });
|
|
97
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
98
|
+
const hint = err instanceof Error && err.name === 'ZodError'
|
|
99
|
+
? ` — run "monomind org validate ${name}" for details` : '';
|
|
100
|
+
log(output.error(`Could not start org ${name}: ${detail}${hint}`));
|
|
101
|
+
return { success: false, message: 'org start failed' };
|
|
102
|
+
}
|
|
64
103
|
log(output.info(`org ${name} running (${running.def.roles.length} agents, run ${running.run}) — Ctrl-C or "monomind org stop ${name}" to stop`));
|
|
65
104
|
// stopfile poll lets `org stop` work from another terminal;
|
|
66
105
|
// clear any stale stopfile from a previous run before polling
|
|
@@ -79,18 +118,41 @@ const runAction = async (ctx) => {
|
|
|
79
118
|
srv?.close();
|
|
80
119
|
return { success: true, message: `org ${name} stopped` };
|
|
81
120
|
};
|
|
121
|
+
/** True when runtime.json records a running org whose recorded pid is still alive. */
|
|
122
|
+
const isOrgRunning = (cwd, name) => {
|
|
123
|
+
try {
|
|
124
|
+
const rt = JSON.parse(readFileSync(join(cwd, ORG_DIR, name, 'runtime.json'), 'utf8'));
|
|
125
|
+
if (rt.status !== 'running' || !rt.pid)
|
|
126
|
+
return false;
|
|
127
|
+
process.kill(rt.pid, 0); // throws if the pid is gone (crashed daemon left a stale file)
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
82
134
|
const stopAction = async (ctx) => {
|
|
83
135
|
const validated = validateOrgName(ctx.args[0]);
|
|
84
136
|
if (!validated.ok)
|
|
85
137
|
return validated.result;
|
|
86
138
|
const name = validated.name;
|
|
139
|
+
if (!existsSync(join(ctx.cwd, ORG_DIR, `${name}.json`))) {
|
|
140
|
+
log(output.error(`Org not found: ${name}`));
|
|
141
|
+
return { success: false, message: 'org not found' };
|
|
142
|
+
}
|
|
87
143
|
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
88
144
|
mkdirSync(join(ctx.cwd, ORG_DIR, name), { recursive: true });
|
|
89
145
|
writeFileSync(join(ctx.cwd, ORG_DIR, name, 'stop'), new Date().toISOString());
|
|
90
146
|
return { success: true, message: `stop requested for ${name} (daemon exits within 2s)` };
|
|
91
147
|
};
|
|
92
148
|
const statusAction = async (ctx) => {
|
|
93
|
-
|
|
149
|
+
let name;
|
|
150
|
+
if (ctx.args[0]) {
|
|
151
|
+
const validated = validateOrgName(ctx.args[0]);
|
|
152
|
+
if (!validated.ok)
|
|
153
|
+
return validated.result;
|
|
154
|
+
name = validated.name;
|
|
155
|
+
}
|
|
94
156
|
const orgDir = join(ctx.cwd, ORG_DIR);
|
|
95
157
|
const targets = name ? [name] : (existsSync(orgDir)
|
|
96
158
|
? listOrgConfigFiles(orgDir).map(f => f.replace(/\.json$/, ''))
|
|
@@ -107,6 +169,17 @@ const statusAction = async (ctx) => {
|
|
|
107
169
|
continue;
|
|
108
170
|
}
|
|
109
171
|
}
|
|
172
|
+
// A "running" record whose pid is gone means the daemon died without its
|
|
173
|
+
// stopOrg cleanup — surface that instead of reporting it as still running.
|
|
174
|
+
if (state.status === 'running' && state.pid) {
|
|
175
|
+
try {
|
|
176
|
+
process.kill(state.pid, 0);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
log(output.warning(`${t}: crashed (runtime.json says running but pid ${state.pid} is gone)${state.run ? ` — run ${state.run}` : ''} — close it out with "monomind org mark-complete ${t}"`));
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
110
183
|
log(output.info(`${t}: ${state.status}${state.run ? ` (run ${state.run}, pid ${state.pid})` : ''}`));
|
|
111
184
|
}
|
|
112
185
|
return { success: true };
|
|
@@ -198,8 +271,27 @@ const listAction = async (ctx) => {
|
|
|
198
271
|
return { success: true };
|
|
199
272
|
}
|
|
200
273
|
log(output.info(`Found ${configs.length} org(s):`));
|
|
201
|
-
for (const f of configs)
|
|
202
|
-
|
|
274
|
+
for (const f of configs) {
|
|
275
|
+
const stem = f.replace(/\.json$/, '');
|
|
276
|
+
let detail = '';
|
|
277
|
+
try {
|
|
278
|
+
const def = JSON.parse(readFileSync(join(orgsDir, f), 'utf8'));
|
|
279
|
+
const roles = Array.isArray(def.roles) ? def.roles.length : 0;
|
|
280
|
+
const sched = def.schedule ? `every ${def.schedule}` : 'manual';
|
|
281
|
+
let status = 'never run';
|
|
282
|
+
try {
|
|
283
|
+
status = JSON.parse(readFileSync(join(orgsDir, stem, 'runtime.json'), 'utf8')).status ?? status;
|
|
284
|
+
}
|
|
285
|
+
catch { /* no runtime state yet */ }
|
|
286
|
+
const goal = typeof def.goal === 'string' && def.goal
|
|
287
|
+
? ` — ${def.goal.length > 60 ? `${def.goal.slice(0, 57)}...` : def.goal}` : '';
|
|
288
|
+
detail = ` (${roles} role${roles === 1 ? '' : 's'}, ${sched}, ${status})${goal}`;
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
detail = ' (unreadable config — run `monomind org validate`)';
|
|
292
|
+
}
|
|
293
|
+
log(output.info(` • ${stem}${detail}`));
|
|
294
|
+
}
|
|
203
295
|
return { success: true };
|
|
204
296
|
};
|
|
205
297
|
const deleteAction = async (ctx) => {
|
|
@@ -225,6 +317,10 @@ const deleteAction = async (ctx) => {
|
|
|
225
317
|
log(output.error(`Org not found: ${orgName}`));
|
|
226
318
|
return { success: false, message: 'org not found' };
|
|
227
319
|
}
|
|
320
|
+
if (isOrgRunning(cwd, orgName) && ctx.flags['force'] !== true) {
|
|
321
|
+
log(output.error(`Org "${orgName}" is currently running — stop it first (monomind org stop ${orgName}) or pass --force.`));
|
|
322
|
+
return { success: false, message: 'org is running' };
|
|
323
|
+
}
|
|
228
324
|
let removed = 0;
|
|
229
325
|
for (const suf of ['', ...ORG_ARTIFACT_SUFFIXES]) {
|
|
230
326
|
for (const ext of ['.json', '.jsonl']) {
|
|
@@ -298,6 +394,7 @@ export const orgCommand = {
|
|
|
298
394
|
options: [
|
|
299
395
|
{ name: 'task', description: 'Override the org goal for this run', type: 'string' },
|
|
300
396
|
{ name: 'cross-process', description: 'Discover and message orgs hosted by other monomind processes on this machine (default true)', type: 'boolean', default: true },
|
|
397
|
+
{ name: 'dry-run', description: 'Validate and print each role\'s briefing without starting any agent sessions', type: 'boolean' },
|
|
301
398
|
],
|
|
302
399
|
examples: [{ command: 'monomind org run growth --task "weekly report"', description: 'Run the growth org once with a task' }],
|
|
303
400
|
action: runAction,
|
|
@@ -316,10 +413,95 @@ export const orgCommand = {
|
|
|
316
413
|
options: [{ name: 'times', short: 'n', description: 'Iterations', type: 'number', default: 5 }],
|
|
317
414
|
action: testLoopAction,
|
|
318
415
|
},
|
|
416
|
+
{
|
|
417
|
+
name: 'logs', description: 'Show (or follow) the formatted event log of an org run',
|
|
418
|
+
options: [
|
|
419
|
+
{ name: 'run', description: 'Run id (default: latest)', type: 'string' },
|
|
420
|
+
{ name: 'role', description: 'Only events from/to this role', type: 'string' },
|
|
421
|
+
{ name: 'follow', short: 'f', description: 'Keep tailing until Ctrl-C', type: 'boolean' },
|
|
422
|
+
],
|
|
423
|
+
examples: [{ command: 'monomind org logs growth --follow', description: 'Live-tail the latest run' }],
|
|
424
|
+
action: async (ctx) => {
|
|
425
|
+
const v = validateOrgName(ctx.args[0]);
|
|
426
|
+
if (!v.ok)
|
|
427
|
+
return v.result;
|
|
428
|
+
const { logsAction } = await import('./org-observe.js');
|
|
429
|
+
return logsAction(ctx, v.name);
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: 'report', description: 'Summarize an org run: outcome, per-role activity, tokens, assets, crashes',
|
|
434
|
+
options: [
|
|
435
|
+
{ name: 'run', description: 'Run id (default: latest)', type: 'string' },
|
|
436
|
+
{ name: 'all', description: 'List all recorded runs from history', type: 'boolean' },
|
|
437
|
+
],
|
|
438
|
+
examples: [{ command: 'monomind org report growth', description: 'Report on the latest run' }],
|
|
439
|
+
action: async (ctx) => {
|
|
440
|
+
const v = validateOrgName(ctx.args[0]);
|
|
441
|
+
if (!v.ok)
|
|
442
|
+
return v.result;
|
|
443
|
+
const { reportAction } = await import('./org-observe.js');
|
|
444
|
+
return reportAction(ctx, v.name);
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: 'questions', description: 'List pending ask_human questions from an org\'s agents',
|
|
449
|
+
options: [{ name: 'all', description: 'Include answered questions', type: 'boolean' }],
|
|
450
|
+
examples: [{ command: 'monomind org questions growth', description: 'Show unanswered questions' }],
|
|
451
|
+
action: async (ctx) => {
|
|
452
|
+
const v = validateOrgName(ctx.args[0]);
|
|
453
|
+
if (!v.ok)
|
|
454
|
+
return v.result;
|
|
455
|
+
const { questionsAction } = await import('./org-observe.js');
|
|
456
|
+
return questionsAction(ctx, v.name);
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: 'answer', description: 'Answer a pending ask_human question (live if the org is running, queued otherwise)',
|
|
461
|
+
examples: [{ command: 'monomind org answer growth q-123-ab "yes, ship it"', description: 'Answer question q-123-ab' }],
|
|
462
|
+
action: async (ctx) => {
|
|
463
|
+
const v = validateOrgName(ctx.args[0]);
|
|
464
|
+
if (!v.ok)
|
|
465
|
+
return v.result;
|
|
466
|
+
const { answerAction } = await import('./org-observe.js');
|
|
467
|
+
return answerAction(ctx, v.name);
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: 'create', description: 'Scaffold an org config from a starter template',
|
|
472
|
+
options: [
|
|
473
|
+
{ name: 'template', description: 'content-team | dev-team | research-pod', type: 'string' },
|
|
474
|
+
{ name: 'goal', description: 'Org goal (defaults to the template\'s placeholder)', type: 'string' },
|
|
475
|
+
{ name: 'schedule', description: 'Daemon schedule, e.g. 30m or 2h', type: 'string' },
|
|
476
|
+
{ name: 'force', description: 'Overwrite an existing org config', type: 'boolean' },
|
|
477
|
+
],
|
|
478
|
+
examples: [{ command: 'monomind org create blog --template content-team --goal "3 posts/week"', description: 'Create a content org' }],
|
|
479
|
+
action: async (ctx) => {
|
|
480
|
+
const v = validateOrgName(ctx.args[0]);
|
|
481
|
+
if (!v.ok)
|
|
482
|
+
return v.result;
|
|
483
|
+
const { createAction } = await import('./org-observe.js');
|
|
484
|
+
return createAction(ctx, v.name);
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: 'validate', description: 'Validate org config(s) against the runtime schema and structural invariants',
|
|
489
|
+
examples: [
|
|
490
|
+
{ command: 'monomind org validate growth', description: 'Validate one org config' },
|
|
491
|
+
{ command: 'monomind org validate', description: 'Validate every org config in the project' },
|
|
492
|
+
],
|
|
493
|
+
action: async (ctx) => {
|
|
494
|
+
const { validateAction } = await import('./org-observe.js');
|
|
495
|
+
return validateAction(ctx);
|
|
496
|
+
},
|
|
497
|
+
},
|
|
319
498
|
{ name: 'list', description: 'List all orgs in the current project', action: listAction },
|
|
320
499
|
{
|
|
321
500
|
name: 'delete', description: 'Delete an org and all its data',
|
|
322
|
-
options: [
|
|
501
|
+
options: [
|
|
502
|
+
{ name: 'yes', short: 'y', description: 'Skip confirmation', type: 'boolean' },
|
|
503
|
+
{ name: 'force', description: 'Delete even if the org appears to be running', type: 'boolean' },
|
|
504
|
+
],
|
|
323
505
|
action: deleteAction,
|
|
324
506
|
},
|
|
325
507
|
{ name: 'mark-complete', description: 'Manually close a stale/crashed run', action: markCompleteAction },
|
|
@@ -329,7 +511,7 @@ export const orgCommand = {
|
|
|
329
511
|
// index.ts's dispatcher never prints result.message on a failed action —
|
|
330
512
|
// it only exits with result.exitCode — so this must log itself or bare
|
|
331
513
|
// `monomind org` exits silently with code 1 and zero output.
|
|
332
|
-
const message = 'usage: monomind org <run|stop|status|serve|test-loop|list|delete|mark-complete>';
|
|
514
|
+
const message = 'usage: monomind org <run|stop|status|serve|test-loop|logs|report|questions|answer|create|validate|list|delete|mark-complete>';
|
|
333
515
|
log(output.error(message));
|
|
334
516
|
return { success: false, message };
|
|
335
517
|
},
|
|
@@ -535,7 +535,10 @@ const bottleneckCommand = {
|
|
|
535
535
|
try {
|
|
536
536
|
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
537
537
|
}
|
|
538
|
-
catch {
|
|
538
|
+
catch (e) {
|
|
539
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
540
|
+
console.error('[performance-bottleneck] failed to parse monomind.config.json:', e);
|
|
541
|
+
}
|
|
539
542
|
const perf = (config.performance ?? {});
|
|
540
543
|
const hnsw = (perf.hnsw ?? {});
|
|
541
544
|
if (!hnsw.quantization) {
|
|
@@ -224,7 +224,10 @@ function setupCursor(activateScriptPath, repoPath) {
|
|
|
224
224
|
try {
|
|
225
225
|
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
226
226
|
}
|
|
227
|
-
catch {
|
|
227
|
+
catch (e) {
|
|
228
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
229
|
+
console.error('[setupCursor] failed to parse existing .cursor/settings.json, overwriting with empty:', e);
|
|
230
|
+
}
|
|
228
231
|
}
|
|
229
232
|
const hooks = settings['hooks'] ?? {};
|
|
230
233
|
const sessionStart = hooks['SessionStart'] ?? [];
|
|
@@ -141,10 +141,16 @@ export const scanCommand = {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
-
catch { /* JSON parse failed */
|
|
144
|
+
catch (e) { /* JSON parse failed */
|
|
145
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
146
|
+
console.error('[security-scan] failed to parse npm audit output:', e);
|
|
147
|
+
}
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
|
-
catch { /* npm audit failed */
|
|
150
|
+
catch (e) { /* npm audit failed */
|
|
151
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
152
|
+
console.error('[security-scan] dependency check failed:', e);
|
|
153
|
+
}
|
|
148
154
|
}
|
|
149
155
|
if (scanType === 'all' || scanType === 'code') {
|
|
150
156
|
spinner.setText('Scanning for hardcoded secrets...');
|
|
@@ -90,7 +90,10 @@ function loadConfig(cwd) {
|
|
|
90
90
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
91
91
|
return parseSimpleYaml(content);
|
|
92
92
|
}
|
|
93
|
-
catch {
|
|
93
|
+
catch (e) {
|
|
94
|
+
// Caller falls back to hardcoded defaults (topology/maxAgents) as if no config existed
|
|
95
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
96
|
+
console.error('[loadConfig] failed to read/parse .monomind/config.yaml, falling back to defaults:', e);
|
|
94
97
|
return null;
|
|
95
98
|
}
|
|
96
99
|
}
|
|
@@ -339,8 +339,9 @@ const initCommand = {
|
|
|
339
339
|
};
|
|
340
340
|
writeJsonFileAtomic(stateFile, store);
|
|
341
341
|
}
|
|
342
|
-
catch {
|
|
343
|
-
|
|
342
|
+
catch (e) {
|
|
343
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
344
|
+
console.error('[swarm-init] failed to persist swarm state file:', e);
|
|
344
345
|
}
|
|
345
346
|
if (ctx.flags.format === 'json') {
|
|
346
347
|
output.printJson(result);
|
|
@@ -25,7 +25,9 @@ function getPackageVersion() {
|
|
|
25
25
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
26
26
|
return pkg.version || '3.0.0';
|
|
27
27
|
}
|
|
28
|
-
catch {
|
|
28
|
+
catch (e) {
|
|
29
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
30
|
+
console.error('[index] getPackageVersion failed, using fallback:', e);
|
|
29
31
|
return '3.0.0';
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -472,7 +474,11 @@ export class CLI {
|
|
|
472
474
|
});
|
|
473
475
|
void _swarmCheckpointer;
|
|
474
476
|
}
|
|
475
|
-
catch {
|
|
477
|
+
catch (e) {
|
|
478
|
+
// optional — monomind/memory may not be installed
|
|
479
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
480
|
+
console.error('[index] SwarmCheckpointer init failed:', e);
|
|
481
|
+
}
|
|
476
482
|
// Task 30: Build unified agent registry — extras (canonical) first, dev copies second.
|
|
477
483
|
// Deduplication is slug-based; agency-agents wins on conflict.
|
|
478
484
|
// Extra paths are read from MONOMIND_EXTRA_AGENT_PATHS env var (colon-separated)
|
|
@@ -486,7 +492,11 @@ export class CLI {
|
|
|
486
492
|
mkdirSync(outDir, { recursive: true });
|
|
487
493
|
buildUnifiedRegistry(roots, join(outDir, 'registry.json'));
|
|
488
494
|
}
|
|
489
|
-
catch {
|
|
495
|
+
catch (e) {
|
|
496
|
+
// optional — registry build failures must never block startup
|
|
497
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
498
|
+
console.error('[index] agent registry build failed:', e);
|
|
499
|
+
}
|
|
490
500
|
// Task 04: CapabilityMetadata validation moved to `monomind doctor -c registry`
|
|
491
501
|
// (see doctor-project-checks.ts:checkAgentRegistry). Printing this from a
|
|
492
502
|
// fire-and-forget startup task raced process exit — short-lived commands
|
|
@@ -926,8 +926,10 @@ async function writeSettings(targetDir, options, result) {
|
|
|
926
926
|
result.skipped.push('.claude/settings.json');
|
|
927
927
|
}
|
|
928
928
|
}
|
|
929
|
-
catch {
|
|
929
|
+
catch (e) {
|
|
930
930
|
// Existing file is corrupt — overwrite
|
|
931
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
932
|
+
console.error('[writeSettings] existing settings.json unparseable, overwriting with generated defaults:', e);
|
|
931
933
|
atomicWriteFile(settingsPath, JSON.stringify(generated, null, 2));
|
|
932
934
|
result.created.files.push('.claude/settings.json');
|
|
933
935
|
}
|
|
@@ -2000,8 +2002,10 @@ async function writeClaudeMd(targetDir, options, result) {
|
|
|
2000
2002
|
result.created.files.push('~/.claude/CLAUDE.md');
|
|
2001
2003
|
}
|
|
2002
2004
|
}
|
|
2003
|
-
catch {
|
|
2005
|
+
catch (e) {
|
|
2004
2006
|
// Non-critical — global CLAUDE.md is best-effort
|
|
2007
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
2008
|
+
console.error('[writeClaudeMd] failed to write/append ~/.claude/CLAUDE.md:', e);
|
|
2005
2009
|
}
|
|
2006
2010
|
// Also inject the token-display hook into ~/.claude/settings.json
|
|
2007
2011
|
const globalSettingsPath = path.join(globalClaudeDir, 'settings.json');
|
|
@@ -2014,7 +2018,11 @@ async function writeClaudeMd(targetDir, options, result) {
|
|
|
2014
2018
|
try {
|
|
2015
2019
|
globalSettings = JSON.parse(fs.readFileSync(globalSettingsPath, 'utf-8'));
|
|
2016
2020
|
}
|
|
2017
|
-
catch {
|
|
2021
|
+
catch (e) {
|
|
2022
|
+
// malformed JSON — start fresh
|
|
2023
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
2024
|
+
console.error('[writeClaudeMd] ~/.claude/settings.json unparseable, starting fresh:', e);
|
|
2025
|
+
}
|
|
2018
2026
|
}
|
|
2019
2027
|
// Inject SessionStart token hook if not already present
|
|
2020
2028
|
const hooks = globalSettings.hooks ?? {};
|
|
@@ -2031,8 +2039,10 @@ async function writeClaudeMd(targetDir, options, result) {
|
|
|
2031
2039
|
result.created.files.push('~/.claude/settings.json (added token hook)');
|
|
2032
2040
|
}
|
|
2033
2041
|
}
|
|
2034
|
-
catch {
|
|
2042
|
+
catch (e) {
|
|
2035
2043
|
// Non-critical — global settings hook is best-effort
|
|
2044
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
2045
|
+
console.error('[writeClaudeMd] failed to inject token hook into ~/.claude/settings.json:', e);
|
|
2036
2046
|
}
|
|
2037
2047
|
}
|
|
2038
2048
|
}
|
|
@@ -2163,7 +2173,11 @@ function _registerMonomindProject(dir) {
|
|
|
2163
2173
|
reg = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
|
|
2164
2174
|
}
|
|
2165
2175
|
}
|
|
2166
|
-
catch {
|
|
2176
|
+
catch (e) {
|
|
2177
|
+
// Unparseable registry — proceeding with an empty list will overwrite it below, losing previously registered projects
|
|
2178
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
2179
|
+
console.error('[_registerMonomindProject] ~/.monomind-projects.json unparseable, resetting:', e);
|
|
2180
|
+
}
|
|
2167
2181
|
if (!Array.isArray(reg.projects))
|
|
2168
2182
|
reg.projects = [];
|
|
2169
2183
|
const abs = path.resolve(dir);
|
|
@@ -159,8 +159,9 @@ export async function ingestDocument(filePath, scope = 'shared', rootDir = proce
|
|
|
159
159
|
if (storeResult?.success)
|
|
160
160
|
indexed++;
|
|
161
161
|
}
|
|
162
|
-
catch {
|
|
163
|
-
|
|
162
|
+
catch (e) {
|
|
163
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
164
|
+
console.error(`[ingestDocument] failed to store chunk ${chunk.chunkIndex} of ${resolved}:`, e);
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
}
|
|
@@ -24,6 +24,7 @@ interface AgentStore {
|
|
|
24
24
|
version: string;
|
|
25
25
|
}
|
|
26
26
|
export declare function loadAgentStore(): AgentStore;
|
|
27
|
+
export declare function loadAgentStoreOrNull(): AgentStore | null;
|
|
27
28
|
export declare const agentTools: MCPTool[];
|
|
28
29
|
export {};
|
|
29
30
|
//# sourceMappingURL=agent-tools.d.ts.map
|
|
@@ -30,21 +30,35 @@ const MAX_AGENT_STORE_BYTES = 50 * 1024 * 1024;
|
|
|
30
30
|
// (e.g. hive-mind-tools.ts) can reuse this hardened loader instead of
|
|
31
31
|
// maintaining their own weaker copy (missing the size cap / __proto__ guard).
|
|
32
32
|
export function loadAgentStore() {
|
|
33
|
+
return loadAgentStoreOrNull() ?? { agents: {}, version: '3.0.0' };
|
|
34
|
+
}
|
|
35
|
+
// Like loadAgentStore(), but distinguishes "file doesn't exist yet" (returns
|
|
36
|
+
// the empty default store — safe to build on and save) from "file exists but
|
|
37
|
+
// failed to read/parse, or is corrupt/oversized" (returns null). Handlers
|
|
38
|
+
// that are about to mutate-and-save MUST use this instead of loadAgentStore()
|
|
39
|
+
// and bail out on null, or a transient read failure silently wipes every
|
|
40
|
+
// previously-spawned agent on the next save (the exact bug found and fixed
|
|
41
|
+
// in task-tools.ts's task_assign earlier this session — same pattern here).
|
|
42
|
+
// Exported for other tool modules with their own mutate-and-save paths
|
|
43
|
+
// against the same agent store file — see hive-mind-tools.ts.
|
|
44
|
+
export function loadAgentStoreOrNull() {
|
|
33
45
|
try {
|
|
34
46
|
const path = getAgentPath();
|
|
35
47
|
migrateLegacyStoreFile(path, join(AGENT_DIR, AGENT_FILE));
|
|
36
48
|
if (existsSync(path)) {
|
|
37
49
|
if (statSync(path).size > MAX_AGENT_STORE_BYTES)
|
|
38
|
-
return
|
|
50
|
+
return null;
|
|
39
51
|
const data = readFileSync(path, 'utf-8');
|
|
40
52
|
const parsed = JSON.parse(data);
|
|
41
53
|
if (parsed && typeof parsed === 'object' && Object.prototype.hasOwnProperty.call(parsed, '__proto__'))
|
|
42
|
-
return
|
|
54
|
+
return null;
|
|
43
55
|
return parsed;
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
|
-
catch {
|
|
47
|
-
|
|
58
|
+
catch (e) {
|
|
59
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
60
|
+
console.error('[loadAgentStore] failed to read/parse agent store — refusing to proceed to avoid overwriting it with an empty one:', e);
|
|
61
|
+
return null;
|
|
48
62
|
}
|
|
49
63
|
return { agents: {}, version: '3.0.0' };
|
|
50
64
|
}
|
|
@@ -126,7 +140,10 @@ export const agentTools = [
|
|
|
126
140
|
required: ['agentType'],
|
|
127
141
|
},
|
|
128
142
|
handler: async (input) => {
|
|
129
|
-
const store =
|
|
143
|
+
const store = loadAgentStoreOrNull();
|
|
144
|
+
if (!store) {
|
|
145
|
+
return { success: false, error: 'Agent store is unreadable/corrupt — refusing to spawn to avoid overwriting real agent data. Retry, or check the store file if this persists.' };
|
|
146
|
+
}
|
|
130
147
|
// Cap agentId: used as the JSON object key in store.agents[agentId].
|
|
131
148
|
// An oversized key inflates the on-disk store for every spawned agent.
|
|
132
149
|
// Cap agentType/domain: persisted as AgentRecord field values.
|
|
@@ -215,7 +232,10 @@ export const agentTools = [
|
|
|
215
232
|
if (!agentId || typeof agentId !== 'string' || ['__proto__', 'constructor', 'prototype'].includes(agentId)) {
|
|
216
233
|
return { success: false, agentId, error: 'Invalid agent ID' };
|
|
217
234
|
}
|
|
218
|
-
const store =
|
|
235
|
+
const store = loadAgentStoreOrNull();
|
|
236
|
+
if (!store) {
|
|
237
|
+
return { success: false, agentId, error: 'Agent store is unreadable/corrupt — refusing to terminate to avoid overwriting real agent data.' };
|
|
238
|
+
}
|
|
219
239
|
if (Object.hasOwn(store.agents, agentId)) {
|
|
220
240
|
store.agents[agentId].status = 'terminated';
|
|
221
241
|
saveAgentStore(store);
|
|
@@ -277,25 +297,35 @@ export const agentTools = [
|
|
|
277
297
|
inputSchema: {
|
|
278
298
|
type: 'object',
|
|
279
299
|
properties: {
|
|
280
|
-
status: { type: 'string', description: 'Filter by status' },
|
|
300
|
+
status: { type: 'string', description: 'Filter by status (pass "all" or omit to include every status)' },
|
|
281
301
|
domain: { type: 'string', description: 'Filter by domain' },
|
|
302
|
+
agentType: { type: 'string', description: 'Filter by agent type' },
|
|
282
303
|
includeTerminated: { type: 'boolean', description: 'Include terminated agents' },
|
|
283
304
|
},
|
|
284
305
|
},
|
|
285
306
|
handler: async (input) => {
|
|
286
307
|
const store = loadAgentStore();
|
|
287
308
|
let agents = Object.values(store.agents);
|
|
288
|
-
// Filter by status
|
|
289
|
-
|
|
309
|
+
// Filter by status. 'all' (the CLI's `--all` flag sends this literal
|
|
310
|
+
// string) means "no status filter" — it used to be treated as a real
|
|
311
|
+
// status value to match against, which no agent ever has, so `--all`
|
|
312
|
+
// silently returned zero agents instead of every agent.
|
|
313
|
+
if (input.status && input.status !== 'all') {
|
|
290
314
|
agents = agents.filter(a => a.status === input.status);
|
|
291
315
|
}
|
|
292
|
-
else if (!input.includeTerminated) {
|
|
316
|
+
else if (input.status !== 'all' && !input.includeTerminated) {
|
|
293
317
|
agents = agents.filter(a => a.status !== 'terminated');
|
|
294
318
|
}
|
|
295
319
|
// Filter by domain
|
|
296
320
|
if (input.domain) {
|
|
297
321
|
agents = agents.filter(a => a.domain === input.domain);
|
|
298
322
|
}
|
|
323
|
+
// Filter by agent type — the CLI's `--type` flag has sent this since
|
|
324
|
+
// it was added, but this handler never read it, so `--type` was a
|
|
325
|
+
// silent no-op that returned every agent regardless of the filter.
|
|
326
|
+
if (input.agentType) {
|
|
327
|
+
agents = agents.filter(a => a.agentType === input.agentType);
|
|
328
|
+
}
|
|
299
329
|
return {
|
|
300
330
|
agents: agents.map(a => ({
|
|
301
331
|
agentId: a.agentId,
|
|
@@ -310,6 +340,7 @@ export const agentTools = [
|
|
|
310
340
|
filters: {
|
|
311
341
|
status: input.status,
|
|
312
342
|
domain: input.domain,
|
|
343
|
+
agentType: input.agentType,
|
|
313
344
|
includeTerminated: input.includeTerminated,
|
|
314
345
|
},
|
|
315
346
|
};
|
|
@@ -329,9 +360,15 @@ export const agentTools = [
|
|
|
329
360
|
required: ['action'],
|
|
330
361
|
},
|
|
331
362
|
handler: async (input) => {
|
|
332
|
-
const store = loadAgentStore();
|
|
333
|
-
const agents = Object.values(store.agents).filter(a => a.status !== 'terminated');
|
|
334
363
|
const action = input.action || 'status'; // Default to status
|
|
364
|
+
const store = loadAgentStoreOrNull();
|
|
365
|
+
if (!store) {
|
|
366
|
+
// 'scale'/'drain' would otherwise build on an empty store and save it,
|
|
367
|
+
// wiping every real agent; 'status' reporting all-zeros on a corrupt
|
|
368
|
+
// store would also be misleading, so all three branches bail here.
|
|
369
|
+
return { action, error: 'Agent store is unreadable/corrupt — refusing to proceed to avoid overwriting real agent data.' };
|
|
370
|
+
}
|
|
371
|
+
const agents = Object.values(store.agents).filter(a => a.status !== 'terminated');
|
|
335
372
|
if (action === 'status') {
|
|
336
373
|
const byType = {};
|
|
337
374
|
const byStatus = {};
|
|
@@ -404,13 +441,16 @@ export const agentTools = [
|
|
|
404
441
|
}
|
|
405
442
|
if (action === 'drain') {
|
|
406
443
|
const agentType = input.agentType;
|
|
444
|
+
// Scope "remaining" to the same population drain operated over — when
|
|
445
|
+
// filtered by agentType, agents.length (all non-terminated agents,
|
|
446
|
+
// any type) minus drained (only that type's count) mixed two
|
|
447
|
+
// different populations and produced a meaningless total.
|
|
448
|
+
const scoped = agentType ? agents.filter(a => a.agentType === agentType) : agents;
|
|
407
449
|
let drained = 0;
|
|
408
|
-
for (const agent of
|
|
409
|
-
if (
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
drained++;
|
|
413
|
-
}
|
|
450
|
+
for (const agent of scoped) {
|
|
451
|
+
if (agent.status === 'idle') {
|
|
452
|
+
store.agents[agent.agentId].status = 'terminated';
|
|
453
|
+
drained++;
|
|
414
454
|
}
|
|
415
455
|
}
|
|
416
456
|
saveAgentStore(store);
|
|
@@ -418,7 +458,7 @@ export const agentTools = [
|
|
|
418
458
|
action,
|
|
419
459
|
agentType: agentType || 'all',
|
|
420
460
|
drained,
|
|
421
|
-
remaining:
|
|
461
|
+
remaining: scoped.length - drained,
|
|
422
462
|
};
|
|
423
463
|
}
|
|
424
464
|
return { action, error: 'Unknown action' };
|
|
@@ -521,7 +561,10 @@ export const agentTools = [
|
|
|
521
561
|
if (!agentId || typeof agentId !== 'string' || ['__proto__', 'constructor', 'prototype'].includes(agentId)) {
|
|
522
562
|
return { success: false, agentId, error: 'Invalid agent ID' };
|
|
523
563
|
}
|
|
524
|
-
const store =
|
|
564
|
+
const store = loadAgentStoreOrNull();
|
|
565
|
+
if (!store) {
|
|
566
|
+
return { success: false, agentId, error: 'Agent store is unreadable/corrupt — refusing to update to avoid overwriting real agent data.' };
|
|
567
|
+
}
|
|
525
568
|
const agent = Object.hasOwn(store.agents, agentId) ? store.agents[agentId] : undefined;
|
|
526
569
|
if (agent) {
|
|
527
570
|
if (input.status)
|