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
|
@@ -59,21 +59,21 @@ export const routeCommand = {
|
|
|
59
59
|
output.writeln();
|
|
60
60
|
output.printBox([
|
|
61
61
|
`Task: ${result.task}`,
|
|
62
|
-
`Top Agent: ${output.highlight(result.
|
|
63
|
-
`Confidence: ${(result.confidence * 100).toFixed(1)}%`,
|
|
62
|
+
`Top Agent: ${output.highlight(result.primaryAgent.type)}`,
|
|
63
|
+
`Confidence: ${(result.primaryAgent.confidence * 100).toFixed(1)}%`,
|
|
64
64
|
`Method: ${result.routing?.method ?? 'keyword'}`
|
|
65
65
|
].join('\n'), 'Routing Decision');
|
|
66
|
-
|
|
66
|
+
const recommendations = [result.primaryAgent, ...(result.alternativeAgents || [])];
|
|
67
|
+
if (recommendations.length > 0) {
|
|
67
68
|
output.writeln();
|
|
68
69
|
output.writeln(output.bold('Agent Recommendations'));
|
|
69
70
|
output.printTable({
|
|
70
71
|
columns: [
|
|
71
|
-
{ key: '
|
|
72
|
+
{ key: 'type', header: 'Agent', width: 20 },
|
|
72
73
|
{ key: 'confidence', header: 'Confidence', width: 12, align: 'right', format: (v) => `${(Number(v) * 100).toFixed(1)}%` },
|
|
73
|
-
{ key: 'reason', header: 'Reason', width: 35 }
|
|
74
|
-
{ key: 'estimatedTime', header: 'Est. Time', width: 12, format: (v) => v ? String(v) : 'N/A' }
|
|
74
|
+
{ key: 'reason', header: 'Reason', width: 35 }
|
|
75
75
|
],
|
|
76
|
-
data:
|
|
76
|
+
data: recommendations.slice(0, topK)
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
return { success: true, data: result };
|
|
@@ -447,7 +447,12 @@ export const metricsCommand = {
|
|
|
447
447
|
output.writeln(output.bold(`Learning Metrics Dashboard (${period})`));
|
|
448
448
|
output.writeln();
|
|
449
449
|
try {
|
|
450
|
-
// Call MCP tool for metrics
|
|
450
|
+
// Call MCP tool for metrics. The real handler (hooks-routing.ts) only
|
|
451
|
+
// returns a subset of these fields depending on whether any memory
|
|
452
|
+
// entries exist yet — patterns/agents/commands/performance are all
|
|
453
|
+
// Partial, not fully populated objects. Fields that aren't tracked yet
|
|
454
|
+
// are genuinely absent (not present as null), each with a `_note`
|
|
455
|
+
// string explaining what would populate them.
|
|
451
456
|
const result = await callMCPTool('hooks_metrics', {
|
|
452
457
|
period,
|
|
453
458
|
includev1: v1Dashboard,
|
|
@@ -457,6 +462,11 @@ export const metricsCommand = {
|
|
|
457
462
|
output.printJson(result);
|
|
458
463
|
return { success: true, data: result };
|
|
459
464
|
}
|
|
465
|
+
const pct = (v) => typeof v === 'number' ? `${(v * 100).toFixed(1)}%` : 'N/A';
|
|
466
|
+
if (result._note) {
|
|
467
|
+
output.writeln(output.dim(result._note));
|
|
468
|
+
output.writeln();
|
|
469
|
+
}
|
|
460
470
|
// Patterns section
|
|
461
471
|
output.writeln(output.bold('📊 Pattern Learning'));
|
|
462
472
|
output.printTable({
|
|
@@ -465,12 +475,14 @@ export const metricsCommand = {
|
|
|
465
475
|
{ key: 'value', header: 'Value', width: 20, align: 'right' }
|
|
466
476
|
],
|
|
467
477
|
data: [
|
|
468
|
-
{ metric: 'Total Patterns', value: result.patterns.total },
|
|
469
|
-
{ metric: 'Successful', value: output.success(String(result.patterns.successful)) },
|
|
470
|
-
{ metric: 'Failed', value: output.error(String(result.patterns.failed)) },
|
|
471
|
-
{ metric: 'Avg Confidence', value:
|
|
478
|
+
{ metric: 'Total Patterns', value: result.patterns.total ?? 0 },
|
|
479
|
+
{ metric: 'Successful', value: typeof result.patterns.successful === 'number' ? output.success(String(result.patterns.successful)) : 'N/A' },
|
|
480
|
+
{ metric: 'Failed', value: typeof result.patterns.failed === 'number' ? output.error(String(result.patterns.failed)) : 'N/A' },
|
|
481
|
+
{ metric: 'Avg Confidence', value: pct(result.patterns.avgConfidence) }
|
|
472
482
|
]
|
|
473
483
|
});
|
|
484
|
+
if (result.patterns._note)
|
|
485
|
+
output.writeln(output.dim(` ${result.patterns._note}`));
|
|
474
486
|
output.writeln();
|
|
475
487
|
// Agent routing section
|
|
476
488
|
output.writeln(output.bold('🤖 Agent Routing'));
|
|
@@ -480,11 +492,13 @@ export const metricsCommand = {
|
|
|
480
492
|
{ key: 'value', header: 'Value', width: 20, align: 'right' }
|
|
481
493
|
],
|
|
482
494
|
data: [
|
|
483
|
-
{ metric: 'Routing Accuracy', value:
|
|
484
|
-
{ metric: 'Total Routes', value: result.agents.totalRoutes },
|
|
485
|
-
{ metric: 'Top Agent', value: output.highlight(result.agents.topAgent) }
|
|
495
|
+
{ metric: 'Routing Accuracy', value: pct(result.agents.routingAccuracy) },
|
|
496
|
+
{ metric: 'Total Routes', value: result.agents.totalRoutes ?? 0 },
|
|
497
|
+
{ metric: 'Top Agent', value: result.agents.topAgent ? output.highlight(result.agents.topAgent) : 'N/A' }
|
|
486
498
|
]
|
|
487
499
|
});
|
|
500
|
+
if (result.agents._note)
|
|
501
|
+
output.writeln(output.dim(` ${result.agents._note}`));
|
|
488
502
|
output.writeln();
|
|
489
503
|
// Command execution section
|
|
490
504
|
output.writeln(output.bold('⚡ Command Execution'));
|
|
@@ -494,11 +508,13 @@ export const metricsCommand = {
|
|
|
494
508
|
{ key: 'value', header: 'Value', width: 20, align: 'right' }
|
|
495
509
|
],
|
|
496
510
|
data: [
|
|
497
|
-
{ metric: 'Total Executed', value: result.commands.totalExecuted },
|
|
498
|
-
{ metric: 'Success Rate', value:
|
|
499
|
-
{ metric: 'Avg Risk Score', value: result.commands.avgRiskScore.toFixed(2) }
|
|
511
|
+
{ metric: 'Total Executed', value: result.commands.totalExecuted ?? 0 },
|
|
512
|
+
{ metric: 'Success Rate', value: pct(result.commands.successRate) },
|
|
513
|
+
{ metric: 'Avg Risk Score', value: typeof result.commands.avgRiskScore === 'number' ? result.commands.avgRiskScore.toFixed(2) : 'N/A' }
|
|
500
514
|
]
|
|
501
515
|
});
|
|
516
|
+
if (result.commands._note)
|
|
517
|
+
output.writeln(output.dim(` ${result.commands._note}`));
|
|
502
518
|
if (v1Dashboard && result.performance) {
|
|
503
519
|
const p = result.performance;
|
|
504
520
|
output.writeln();
|
|
@@ -146,7 +146,10 @@ export const intelligenceCommand = {
|
|
|
146
146
|
patternsFileEntries = pData.length;
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
catch {
|
|
149
|
+
catch (e) {
|
|
150
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
151
|
+
console.error('[hooks-intelligence] patterns.json read/parse failed:', e);
|
|
152
|
+
}
|
|
150
153
|
}
|
|
151
154
|
// Read stats.json for trajectory data
|
|
152
155
|
let trajectoriesFromDisk = 0;
|
|
@@ -160,7 +163,10 @@ export const intelligenceCommand = {
|
|
|
160
163
|
lastAdaptationFromDisk = sData?.lastAdaptation ?? null;
|
|
161
164
|
}
|
|
162
165
|
}
|
|
163
|
-
catch {
|
|
166
|
+
catch (e) {
|
|
167
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
168
|
+
console.error('[hooks-intelligence] stats.json read/parse failed:', e);
|
|
169
|
+
}
|
|
164
170
|
}
|
|
165
171
|
// Merge local stats with any we can get from MCP
|
|
166
172
|
let mcpResult = null;
|
|
@@ -313,7 +313,7 @@ const sessionRestoreCommand = {
|
|
|
313
313
|
data: [
|
|
314
314
|
{ item: 'Tasks', count: result.restoredState.tasksRestored },
|
|
315
315
|
{ item: 'Agents', count: result.restoredState.agentsRestored },
|
|
316
|
-
{ item: 'Memory
|
|
316
|
+
{ item: 'Memory Bridge', count: result.restoredState.memoryBridgeInitialized ? 'ready' : 'unavailable' }
|
|
317
317
|
]
|
|
318
318
|
});
|
|
319
319
|
if (result.warnings && result.warnings.length > 0) {
|
|
@@ -466,7 +466,10 @@ const toggleCommand = {
|
|
|
466
466
|
try {
|
|
467
467
|
disabled = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
468
468
|
}
|
|
469
|
-
catch {
|
|
469
|
+
catch (e) {
|
|
470
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
471
|
+
console.error('[mcp] failed to load mcp-disabled-tools.json, treating as fresh:', e);
|
|
472
|
+
}
|
|
470
473
|
const enableArg = ctx.flags.enable;
|
|
471
474
|
const disableArg = ctx.flags.disable;
|
|
472
475
|
if (!enableArg && !disableArg) {
|
|
@@ -330,7 +330,13 @@ export const configureCommand = {
|
|
|
330
330
|
]
|
|
331
331
|
});
|
|
332
332
|
output.writeln();
|
|
333
|
-
|
|
333
|
+
try {
|
|
334
|
+
configManager.set(ctx.cwd, 'memory', config);
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
output.printError(error instanceof Error ? error.message : String(error));
|
|
338
|
+
return { success: false, exitCode: 1 };
|
|
339
|
+
}
|
|
334
340
|
output.printSuccess('Memory configuration updated');
|
|
335
341
|
return { success: true, data: config };
|
|
336
342
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CommandContext, CommandResult } from '../types.js';
|
|
2
|
+
/** Validate org config(s) against OrgDefSchema — the exact parse `org run`/`org serve`
|
|
3
|
+
* perform — plus the structural invariants the runtime assumes but the schema can't
|
|
4
|
+
* express (single root role, resolvable reports_to, unique ids, parseable schedule). */
|
|
5
|
+
export declare const validateAction: (ctx: CommandContext) => Promise<CommandResult>;
|
|
6
|
+
/** `org logs <name> [--run id] [--role r] [--follow]` — formatted bus.jsonl tail. */
|
|
7
|
+
export declare const logsAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
|
|
8
|
+
/** `org report <name> [--run id] [--all]` — summarize a run (or list run history). */
|
|
9
|
+
export declare const reportAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
|
|
10
|
+
/** `org questions <name> [--all]` — list pending (or all) ask_human questions. */
|
|
11
|
+
export declare const questionsAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
|
|
12
|
+
/** `org answer <name> <question-id> <answer...>` — answer a pending ask_human question.
|
|
13
|
+
* Delivers live via the hosting daemon's /api/answer-question when the org is running
|
|
14
|
+
* (broker lookup); otherwise records the answer and queues it for the next run. */
|
|
15
|
+
export declare const answerAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
|
|
16
|
+
/** `org create <name> --template <t> [--goal g] [--schedule s]` — scaffold a config from a template. */
|
|
17
|
+
export declare const createAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
|
|
18
|
+
//# sourceMappingURL=org-observe.d.ts.map
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// packages/@monomind/cli/src/commands/org-observe.ts
|
|
2
|
+
// Read-side org subcommands (logs / report) + template scaffolding (create).
|
|
3
|
+
// Kept out of org.ts to respect the 500-line file ceiling.
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { output } from '../output.js';
|
|
7
|
+
import { ORG_DIR, OrgDefSchema } from '../orgrt/types.js';
|
|
8
|
+
import { formatEvent, listRunDirs, readHistory, readRunEvents, summarizeRun } from '../orgrt/reporting.js';
|
|
9
|
+
import { ORG_TEMPLATES, buildFromTemplate } from '../orgrt/templates.js';
|
|
10
|
+
import { parseSchedule } from '../orgrt/scheduler.js';
|
|
11
|
+
import { listOrgConfigFiles, validateOrgName } from './org.js';
|
|
12
|
+
const log = (text) => { console.log(text); };
|
|
13
|
+
/** Validate org config(s) against OrgDefSchema — the exact parse `org run`/`org serve`
|
|
14
|
+
* perform — plus the structural invariants the runtime assumes but the schema can't
|
|
15
|
+
* express (single root role, resolvable reports_to, unique ids, parseable schedule). */
|
|
16
|
+
export const validateAction = async (ctx) => {
|
|
17
|
+
const orgsDir = join(ctx.cwd || process.cwd(), ORG_DIR);
|
|
18
|
+
let files;
|
|
19
|
+
if (ctx.args[0]) {
|
|
20
|
+
const validated = validateOrgName(ctx.args[0]);
|
|
21
|
+
if (!validated.ok)
|
|
22
|
+
return validated.result;
|
|
23
|
+
files = [`${validated.name}.json`];
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
if (!existsSync(orgsDir))
|
|
27
|
+
return { success: false, message: 'no orgs directory — create an org first with /mastermind:createorg' };
|
|
28
|
+
files = listOrgConfigFiles(orgsDir);
|
|
29
|
+
if (!files.length)
|
|
30
|
+
return { success: false, message: 'no org configs found' };
|
|
31
|
+
}
|
|
32
|
+
let failed = 0;
|
|
33
|
+
for (const f of files) {
|
|
34
|
+
const stem = f.replace(/\.json$/, '');
|
|
35
|
+
const path = join(orgsDir, f);
|
|
36
|
+
const errors = [];
|
|
37
|
+
const warnings = [];
|
|
38
|
+
if (!existsSync(path)) {
|
|
39
|
+
log(output.error(`${stem}: not found (${path})`));
|
|
40
|
+
failed++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const def = OrgDefSchema.parse(JSON.parse(readFileSync(path, 'utf8')));
|
|
45
|
+
const ids = def.roles.map(r => r.id);
|
|
46
|
+
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
47
|
+
if (dupes.length)
|
|
48
|
+
errors.push(`duplicate role id(s): ${[...new Set(dupes)].join(', ')}`);
|
|
49
|
+
const roots = def.roles.filter(r => r.reports_to === null);
|
|
50
|
+
if (roots.length === 0)
|
|
51
|
+
errors.push('no root role — exactly one role must have reports_to: null');
|
|
52
|
+
if (roots.length > 1)
|
|
53
|
+
errors.push(`multiple root roles (${roots.map(r => r.id).join(', ')}) — exactly one may have reports_to: null`);
|
|
54
|
+
for (const r of def.roles) {
|
|
55
|
+
if (r.reports_to !== null && !ids.includes(r.reports_to))
|
|
56
|
+
errors.push(`role "${r.id}": reports_to "${r.reports_to}" matches no role id`);
|
|
57
|
+
if (r.reports_to === r.id)
|
|
58
|
+
errors.push(`role "${r.id}" reports to itself`);
|
|
59
|
+
}
|
|
60
|
+
if (def.schedule != null && parseSchedule(def.schedule) === null)
|
|
61
|
+
errors.push(`schedule "${def.schedule}" is not parseable — use "<N>s", "<N>m", or "<N>h"`);
|
|
62
|
+
if (def.name !== stem)
|
|
63
|
+
warnings.push(`def.name "${def.name}" differs from filename — the runtime addresses this org as "${stem}"`);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
67
|
+
}
|
|
68
|
+
for (const w of warnings)
|
|
69
|
+
log(output.warning(`${stem}: ${w}`));
|
|
70
|
+
if (errors.length) {
|
|
71
|
+
failed++;
|
|
72
|
+
for (const e of errors)
|
|
73
|
+
log(output.error(`${stem}: ${e}`));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
log(output.success(`${stem}: valid${warnings.length ? ` (${warnings.length} warning(s))` : ''}`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return failed
|
|
80
|
+
? { success: false, message: `${failed} of ${files.length} org config(s) failed validation` }
|
|
81
|
+
: { success: true, message: `${files.length} org config(s) valid` };
|
|
82
|
+
};
|
|
83
|
+
const resolveRun = (cwd, name, runFlag) => {
|
|
84
|
+
if (typeof runFlag === 'string' && runFlag)
|
|
85
|
+
return runFlag;
|
|
86
|
+
return listRunDirs(cwd, name)[0] ?? null;
|
|
87
|
+
};
|
|
88
|
+
/** `org logs <name> [--run id] [--role r] [--follow]` — formatted bus.jsonl tail. */
|
|
89
|
+
export const logsAction = async (ctx, name) => {
|
|
90
|
+
const run = resolveRun(ctx.cwd, name, ctx.flags['run']);
|
|
91
|
+
if (!run)
|
|
92
|
+
return { success: false, message: `no runs found for org ${name} — start one with: monomind org run ${name}` };
|
|
93
|
+
const file = join(ctx.cwd, ORG_DIR, name, run, 'bus.jsonl');
|
|
94
|
+
const roleFilter = typeof ctx.flags['role'] === 'string' ? ctx.flags['role'] : null;
|
|
95
|
+
const show = (e) => {
|
|
96
|
+
if (roleFilter && e.from !== roleFilter && e.to !== roleFilter)
|
|
97
|
+
return;
|
|
98
|
+
log(formatEvent(e));
|
|
99
|
+
};
|
|
100
|
+
log(output.info(`org ${name} — ${run}${roleFilter ? ` (role: ${roleFilter})` : ''}`));
|
|
101
|
+
let seenLines = 0;
|
|
102
|
+
const drain = () => {
|
|
103
|
+
if (!existsSync(file))
|
|
104
|
+
return;
|
|
105
|
+
const lines = readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
106
|
+
for (const l of lines.slice(seenLines)) {
|
|
107
|
+
try {
|
|
108
|
+
show(JSON.parse(l));
|
|
109
|
+
seenLines++;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
break;
|
|
113
|
+
} // partial line mid-append — retry it next tick
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
drain();
|
|
117
|
+
if (ctx.flags['follow'] !== true)
|
|
118
|
+
return { success: true };
|
|
119
|
+
log(output.info('following — Ctrl-C to stop'));
|
|
120
|
+
await new Promise(resolve => {
|
|
121
|
+
const iv = setInterval(drain, 500);
|
|
122
|
+
process.once('SIGINT', () => { clearInterval(iv); resolve(); });
|
|
123
|
+
process.once('SIGTERM', () => { clearInterval(iv); resolve(); });
|
|
124
|
+
});
|
|
125
|
+
return { success: true };
|
|
126
|
+
};
|
|
127
|
+
/** `org report <name> [--run id] [--all]` — summarize a run (or list run history). */
|
|
128
|
+
export const reportAction = async (ctx, name) => {
|
|
129
|
+
if (ctx.flags['all'] === true) {
|
|
130
|
+
const history = readHistory(ctx.cwd, name);
|
|
131
|
+
if (!history.length)
|
|
132
|
+
return { success: false, message: `no run history for org ${name}` };
|
|
133
|
+
log(output.info(`org ${name} — ${history.length} recorded run(s):`));
|
|
134
|
+
for (const h of history) {
|
|
135
|
+
const dur = h.durationMs != null ? `${Math.round(h.durationMs / 1000)}s` : '?';
|
|
136
|
+
const outcome = h.outcome ? `${h.outcome.status}: ${h.outcome.summary.slice(0, 60)}` : 'no outcome recorded';
|
|
137
|
+
log(output.info(` • ${h.run} ${dur} ${h.totalTokens} tokens ${h.messages} msgs — ${outcome}`));
|
|
138
|
+
}
|
|
139
|
+
return { success: true };
|
|
140
|
+
}
|
|
141
|
+
const run = resolveRun(ctx.cwd, name, ctx.flags['run']);
|
|
142
|
+
if (!run)
|
|
143
|
+
return { success: false, message: `no runs found for org ${name}` };
|
|
144
|
+
const events = readRunEvents(ctx.cwd, name, run);
|
|
145
|
+
if (!events.length)
|
|
146
|
+
return { success: false, message: `run ${run} has no recorded events` };
|
|
147
|
+
const s = summarizeRun(events);
|
|
148
|
+
// Per-role budget ceiling: same split the daemon applies (budget ÷ role count),
|
|
149
|
+
// with any explicit policy.maxTokens override. Missing/unreadable config → no ceilings.
|
|
150
|
+
let perRoleBudget = null;
|
|
151
|
+
const roleCeiling = new Map();
|
|
152
|
+
try {
|
|
153
|
+
const def = OrgDefSchema.parse(JSON.parse(readFileSync(join(ctx.cwd, ORG_DIR, `${name}.json`), 'utf8')));
|
|
154
|
+
perRoleBudget = Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length);
|
|
155
|
+
for (const r of def.roles) {
|
|
156
|
+
const max = r.policy?.maxTokens;
|
|
157
|
+
roleCeiling.set(r.id, max ?? perRoleBudget);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch { /* config gone or invalid — report without budget context */ }
|
|
161
|
+
const budgetNote = (id, tokens) => {
|
|
162
|
+
const cap = roleCeiling.get(id);
|
|
163
|
+
if (!cap)
|
|
164
|
+
return '';
|
|
165
|
+
const pct = Math.round((tokens / cap) * 100);
|
|
166
|
+
return ` (${pct}% of ${cap}${pct >= 100 ? ' — EXHAUSTED' : pct >= 80 ? ' — near limit' : ''})`;
|
|
167
|
+
};
|
|
168
|
+
log(output.info(`ORG REPORT — ${name} / ${run}`));
|
|
169
|
+
log(output.info(` Duration: ${s.durationMs != null ? `${Math.round(s.durationMs / 1000)}s` : '?'} Events: ${s.events} Messages: ${s.messages}${s.xorgMessages ? ` (+${s.xorgMessages} cross-org)` : ''}`));
|
|
170
|
+
log(output.info(` Tokens: ${s.totalTokens}${perRoleBudget ? ` (budget: ${perRoleBudget}/role)` : ''}${s.totalCostUsd ? ` Cost: $${s.totalCostUsd.toFixed(4)}` : ''}`));
|
|
171
|
+
if (s.outcome)
|
|
172
|
+
log(output.success(` Outcome: ${s.outcome.status} (by ${s.outcome.by}) — ${s.outcome.summary}`));
|
|
173
|
+
else
|
|
174
|
+
log(output.warning(' Outcome: not recorded (coordinator never called org_complete)'));
|
|
175
|
+
log(output.info(' Roles:'));
|
|
176
|
+
for (const [id, r] of Object.entries(s.roles)) {
|
|
177
|
+
log(output.info(` ${r.crashed ? '✗' : '•'} ${id}: ${r.messagesSent} msgs, ${r.toolsAllowed} tools${r.toolsDenied ? ` (${r.toolsDenied} denied)` : ''}, ${r.tokens} tokens${budgetNote(id, r.tokens)}${r.crashed ? ' — CRASHED' : ''}`));
|
|
178
|
+
}
|
|
179
|
+
if (s.assets.length) {
|
|
180
|
+
log(output.info(` Assets (${s.assets.length}):`));
|
|
181
|
+
for (const a of s.assets.slice(0, 20))
|
|
182
|
+
log(output.info(` 📄 ${a}`));
|
|
183
|
+
if (s.assets.length > 20)
|
|
184
|
+
log(output.info(` … and ${s.assets.length - 20} more`));
|
|
185
|
+
}
|
|
186
|
+
return { success: true };
|
|
187
|
+
};
|
|
188
|
+
const readQuestions = (cwd, name) => {
|
|
189
|
+
try {
|
|
190
|
+
return JSON.parse(readFileSync(join(cwd, ORG_DIR, name, 'questions.json'), 'utf8')).questions ?? [];
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
/** `org questions <name> [--all]` — list pending (or all) ask_human questions. */
|
|
197
|
+
export const questionsAction = async (ctx, name) => {
|
|
198
|
+
const all = readQuestions(ctx.cwd, name);
|
|
199
|
+
const shown = ctx.flags['all'] === true ? all : all.filter(q => q.answer === null);
|
|
200
|
+
if (!shown.length) {
|
|
201
|
+
log(output.info(all.length ? `No pending questions for org ${name} (${all.length} answered — use --all).` : `No questions recorded for org ${name}.`));
|
|
202
|
+
return { success: true };
|
|
203
|
+
}
|
|
204
|
+
for (const q of shown) {
|
|
205
|
+
const when = new Date(q.ts).toISOString().replace('T', ' ').slice(0, 16);
|
|
206
|
+
log(output.info(`${q.answer === null ? '❓' : '✓'} [${q.questionId}] ${when} ${q.role}: ${q.question}`));
|
|
207
|
+
if (q.answer !== null)
|
|
208
|
+
log(output.info(` ↳ ${q.answer}`));
|
|
209
|
+
}
|
|
210
|
+
if (shown.some(q => q.answer === null))
|
|
211
|
+
log(output.info(`\nAnswer with: monomind org answer ${name} <question-id> "your answer"`));
|
|
212
|
+
return { success: true };
|
|
213
|
+
};
|
|
214
|
+
/** `org answer <name> <question-id> <answer...>` — answer a pending ask_human question.
|
|
215
|
+
* Delivers live via the hosting daemon's /api/answer-question when the org is running
|
|
216
|
+
* (broker lookup); otherwise records the answer and queues it for the next run. */
|
|
217
|
+
export const answerAction = async (ctx, name) => {
|
|
218
|
+
const questionId = ctx.args[1];
|
|
219
|
+
const answer = ctx.args.slice(2).join(' ').trim();
|
|
220
|
+
if (!questionId || !answer)
|
|
221
|
+
return { success: false, message: `usage: monomind org answer ${name} <question-id> "answer text"` };
|
|
222
|
+
const questions = readQuestions(ctx.cwd, name);
|
|
223
|
+
const q = questions.find(x => x.questionId === questionId);
|
|
224
|
+
if (!q) {
|
|
225
|
+
log(output.error(`Question "${questionId}" not found for org ${name} — list with: monomind org questions ${name}`));
|
|
226
|
+
return { success: false, message: 'question not found' };
|
|
227
|
+
}
|
|
228
|
+
if (q.answer !== null)
|
|
229
|
+
return { success: false, message: `question "${questionId}" was already answered` };
|
|
230
|
+
// Live path: the hosting daemon updates questions.json and pushes into the role's mailbox.
|
|
231
|
+
const { lookupOrg } = await import('../orgrt/broker.js');
|
|
232
|
+
const remote = lookupOrg(name);
|
|
233
|
+
if (remote) {
|
|
234
|
+
try {
|
|
235
|
+
const res = await fetch(`${remote.url}/api/answer-question`, {
|
|
236
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
237
|
+
body: JSON.stringify({ org: name, role: q.role, questionId, answer }),
|
|
238
|
+
signal: AbortSignal.timeout(10_000),
|
|
239
|
+
});
|
|
240
|
+
const data = await res.json().catch(() => ({}));
|
|
241
|
+
if (res.ok && data.ok) {
|
|
242
|
+
log(output.success(`Answer delivered to ${name}:${q.role} (live).`));
|
|
243
|
+
return { success: true };
|
|
244
|
+
}
|
|
245
|
+
log(output.warning(`Live delivery rejected (${data.error ?? res.status}) — falling back to offline queue.`));
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
log(output.warning(`Hosting daemon unreachable (${err instanceof Error ? err.message : 'error'}) — falling back to offline queue.`));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Offline path: mirror daemon.answerQuestion's org-not-running branch.
|
|
252
|
+
const updated = questions.map(x => x.questionId === questionId ? { ...x, answer, answeredAt: Date.now() } : x);
|
|
253
|
+
const dest = join(ctx.cwd, ORG_DIR, name, 'questions.json');
|
|
254
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
255
|
+
writeFileSync(tmp, JSON.stringify({ questions: updated }, null, 2));
|
|
256
|
+
const { renameSync } = await import('node:fs');
|
|
257
|
+
renameSync(tmp, dest);
|
|
258
|
+
const { queueMessage } = await import('../orgrt/inbox.js');
|
|
259
|
+
queueMessage(ctx.cwd, name, {
|
|
260
|
+
fromQualified: 'human', toRole: q.role,
|
|
261
|
+
subject: `answer:${questionId}`,
|
|
262
|
+
body: `question: ${q.question}\n\nanswer: ${answer}`,
|
|
263
|
+
ts: Date.now(),
|
|
264
|
+
});
|
|
265
|
+
log(output.success(`Answer recorded — ${name}:${q.role} receives it when the org next runs.`));
|
|
266
|
+
return { success: true };
|
|
267
|
+
};
|
|
268
|
+
/** `org create <name> --template <t> [--goal g] [--schedule s]` — scaffold a config from a template. */
|
|
269
|
+
export const createAction = async (ctx, name) => {
|
|
270
|
+
const templateName = typeof ctx.flags['template'] === 'string' ? ctx.flags['template'] : '';
|
|
271
|
+
if (!templateName) {
|
|
272
|
+
log(output.info(`Available templates: ${Object.keys(ORG_TEMPLATES).join(', ')}`));
|
|
273
|
+
return { success: false, message: 'usage: monomind org create <name> --template <template> [--goal "..."] [--schedule 30m]' };
|
|
274
|
+
}
|
|
275
|
+
const def = buildFromTemplate(templateName, name, typeof ctx.flags['goal'] === 'string' ? ctx.flags['goal'] : undefined);
|
|
276
|
+
if (!def) {
|
|
277
|
+
log(output.error(`Unknown template "${templateName}" — available: ${Object.keys(ORG_TEMPLATES).join(', ')}`));
|
|
278
|
+
return { success: false, message: 'unknown template' };
|
|
279
|
+
}
|
|
280
|
+
if (typeof ctx.flags['schedule'] === 'string')
|
|
281
|
+
def.schedule = ctx.flags['schedule'];
|
|
282
|
+
const file = join(ctx.cwd, ORG_DIR, `${name}.json`);
|
|
283
|
+
if (existsSync(file) && ctx.flags['force'] !== true) {
|
|
284
|
+
log(output.error(`Org "${name}" already exists — pass --force to overwrite.`));
|
|
285
|
+
return { success: false, message: 'org exists' };
|
|
286
|
+
}
|
|
287
|
+
OrgDefSchema.parse(def); // templates must always produce a runnable config
|
|
288
|
+
const { mkdirSync } = await import('node:fs');
|
|
289
|
+
mkdirSync(join(ctx.cwd, ORG_DIR), { recursive: true });
|
|
290
|
+
writeFileSync(file, JSON.stringify(def, null, 2) + '\n', 'utf8');
|
|
291
|
+
log(output.success(`Org "${name}" created from template "${templateName}" (${def.roles.length} roles).`));
|
|
292
|
+
log(output.info(` Edit the goal/roles in ${file}, then: monomind org run ${name}`));
|
|
293
|
+
return { success: true };
|
|
294
|
+
};
|
|
295
|
+
//# sourceMappingURL=org-observe.js.map
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import type { Command } from '../types.js';
|
|
1
|
+
import type { Command, CommandResult } from '../types.js';
|
|
2
|
+
export declare function validateOrgName(name: string | undefined): {
|
|
3
|
+
ok: true;
|
|
4
|
+
name: string;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
result: CommandResult;
|
|
8
|
+
};
|
|
9
|
+
export declare function listOrgConfigFiles(orgsDir: string): string[];
|
|
2
10
|
/** Remove a lingering stopfile so a fresh `org run` doesn't self-terminate. */
|
|
3
11
|
export declare const clearStopfile: (cwd: string, name: string) => void;
|
|
4
12
|
export declare const orgCommand: Command;
|