monomind 2.0.0 → 2.0.2

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.
Files changed (35) hide show
  1. package/README.md +11 -12
  2. package/package.json +2 -2
  3. package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +10 -0
  4. package/packages/@monomind/cli/README.md +11 -12
  5. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +71 -12
  6. package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +4 -4
  7. package/packages/@monomind/cli/dist/src/commands/browse-workflow.js +2 -2
  8. package/packages/@monomind/cli/dist/src/index.js +8 -7
  9. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +18 -18
  10. package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +12 -12
  11. package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
  12. package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +23 -0
  13. package/packages/@monomind/cli/dist/src/orgrt/bus.js +64 -0
  14. package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +41 -0
  15. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +101 -0
  16. package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +11 -0
  17. package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +37 -0
  18. package/packages/@monomind/cli/dist/src/orgrt/mailbox.d.ts +25 -0
  19. package/packages/@monomind/cli/dist/src/orgrt/mailbox.js +39 -0
  20. package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +24 -0
  21. package/packages/@monomind/cli/dist/src/orgrt/policy.js +86 -0
  22. package/packages/@monomind/cli/dist/src/orgrt/provider.d.ts +9 -0
  23. package/packages/@monomind/cli/dist/src/orgrt/provider.js +54 -0
  24. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +23 -0
  25. package/packages/@monomind/cli/dist/src/orgrt/session.js +78 -0
  26. package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +847 -0
  27. package/packages/@monomind/cli/dist/src/orgrt/types.js +51 -0
  28. package/packages/@monomind/cli/dist/src/parser.js +31 -5
  29. package/packages/@monomind/cli/dist/src/ui/collector.mjs +69 -44
  30. package/packages/@monomind/cli/dist/src/ui/dashboard.html +112 -25
  31. package/packages/@monomind/cli/dist/src/ui/orgs.html +54 -0
  32. package/packages/@monomind/cli/dist/src/ui/server.mjs +87 -12
  33. package/packages/@monomind/cli/package.json +3 -2
  34. package/packages/@monomind/cli/dist/src/consensus/vote-signer.d.ts +0 -36
  35. package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +0 -88
@@ -13,14 +13,14 @@ export declare const PrioritizeGapsInputSchema: z.ZodObject<{
13
13
  startLine: z.ZodNumber;
14
14
  endLine: z.ZodNumber;
15
15
  }, "strip", z.ZodTypeAny, {
16
- id: string;
17
16
  type: "function" | "line" | "branch";
17
+ id: string;
18
18
  file: string;
19
19
  startLine: number;
20
20
  endLine: number;
21
21
  }, {
22
- id: string;
23
22
  type: "function" | "line" | "branch";
23
+ id: string;
24
24
  file: string;
25
25
  startLine: number;
26
26
  endLine: number;
@@ -54,7 +54,7 @@ export declare const PrioritizeGapsInputSchema: z.ZodObject<{
54
54
  }, "strip", z.ZodTypeAny, {
55
55
  limit: number;
56
56
  factors: ("complexity" | "change-frequency" | "defect-history" | "business-critical" | "dependency-count" | "test-difficulty")[];
57
- groupBy: "none" | "type" | "file" | "risk";
57
+ groupBy: "type" | "none" | "file" | "risk";
58
58
  weights?: {
59
59
  complexity: number;
60
60
  changeFrequency: number;
@@ -64,8 +64,8 @@ export declare const PrioritizeGapsInputSchema: z.ZodObject<{
64
64
  testDifficulty: number;
65
65
  } | undefined;
66
66
  gaps?: {
67
- id: string;
68
67
  type: "function" | "line" | "branch";
68
+ id: string;
69
69
  file: string;
70
70
  startLine: number;
71
71
  endLine: number;
@@ -82,15 +82,15 @@ export declare const PrioritizeGapsInputSchema: z.ZodObject<{
82
82
  testDifficulty?: number | undefined;
83
83
  } | undefined;
84
84
  gaps?: {
85
- id: string;
86
85
  type: "function" | "line" | "branch";
86
+ id: string;
87
87
  file: string;
88
88
  startLine: number;
89
89
  endLine: number;
90
90
  }[] | undefined;
91
91
  targetPath?: string | undefined;
92
92
  factors?: ("complexity" | "change-frequency" | "defect-history" | "business-critical" | "dependency-count" | "test-difficulty")[] | undefined;
93
- groupBy?: "none" | "type" | "file" | "risk" | undefined;
93
+ groupBy?: "type" | "none" | "file" | "risk" | undefined;
94
94
  }>;
95
95
  export type PrioritizeGapsInput = z.infer<typeof PrioritizeGapsInputSchema>;
96
96
  export interface PrioritizeGapsOutput {
@@ -176,14 +176,14 @@ export declare const toolDefinition: {
176
176
  startLine: z.ZodNumber;
177
177
  endLine: z.ZodNumber;
178
178
  }, "strip", z.ZodTypeAny, {
179
- id: string;
180
179
  type: "function" | "line" | "branch";
180
+ id: string;
181
181
  file: string;
182
182
  startLine: number;
183
183
  endLine: number;
184
184
  }, {
185
- id: string;
186
185
  type: "function" | "line" | "branch";
186
+ id: string;
187
187
  file: string;
188
188
  startLine: number;
189
189
  endLine: number;
@@ -217,7 +217,7 @@ export declare const toolDefinition: {
217
217
  }, "strip", z.ZodTypeAny, {
218
218
  limit: number;
219
219
  factors: ("complexity" | "change-frequency" | "defect-history" | "business-critical" | "dependency-count" | "test-difficulty")[];
220
- groupBy: "none" | "type" | "file" | "risk";
220
+ groupBy: "type" | "none" | "file" | "risk";
221
221
  weights?: {
222
222
  complexity: number;
223
223
  changeFrequency: number;
@@ -227,8 +227,8 @@ export declare const toolDefinition: {
227
227
  testDifficulty: number;
228
228
  } | undefined;
229
229
  gaps?: {
230
- id: string;
231
230
  type: "function" | "line" | "branch";
231
+ id: string;
232
232
  file: string;
233
233
  startLine: number;
234
234
  endLine: number;
@@ -245,15 +245,15 @@ export declare const toolDefinition: {
245
245
  testDifficulty?: number | undefined;
246
246
  } | undefined;
247
247
  gaps?: {
248
- id: string;
249
248
  type: "function" | "line" | "branch";
249
+ id: string;
250
250
  file: string;
251
251
  startLine: number;
252
252
  endLine: number;
253
253
  }[] | undefined;
254
254
  targetPath?: string | undefined;
255
255
  factors?: ("complexity" | "change-frequency" | "defect-history" | "business-critical" | "dependency-count" | "test-difficulty")[] | undefined;
256
- groupBy?: "none" | "type" | "file" | "risk" | undefined;
256
+ groupBy?: "type" | "none" | "file" | "risk" | undefined;
257
257
  }>;
258
258
  handler: typeof handler;
259
259
  };
@@ -15,16 +15,16 @@ export declare const DetectSecretsInputSchema: z.ZodObject<{
15
15
  scanHistory: z.ZodDefault<z.ZodBoolean>;
16
16
  }, "strip", z.ZodTypeAny, {
17
17
  targetPath: string;
18
- secretTypes: ("password" | "api-key" | "private-key" | "token" | "connection-string" | "certificate" | "aws-key" | "aws-secret" | "gcp-key" | "azure-key" | "generic")[];
19
18
  excludePatterns: string[];
19
+ secretTypes: ("password" | "token" | "api-key" | "private-key" | "connection-string" | "certificate" | "aws-key" | "aws-secret" | "gcp-key" | "azure-key" | "generic")[];
20
20
  includeEntropy: boolean;
21
21
  entropyThreshold: number;
22
22
  verifySecrets: boolean;
23
23
  scanHistory: boolean;
24
24
  }, {
25
25
  targetPath: string;
26
- secretTypes?: ("password" | "api-key" | "private-key" | "token" | "connection-string" | "certificate" | "aws-key" | "aws-secret" | "gcp-key" | "azure-key" | "generic")[] | undefined;
27
26
  excludePatterns?: string[] | undefined;
27
+ secretTypes?: ("password" | "token" | "api-key" | "private-key" | "connection-string" | "certificate" | "aws-key" | "aws-secret" | "gcp-key" | "azure-key" | "generic")[] | undefined;
28
28
  includeEntropy?: boolean | undefined;
29
29
  entropyThreshold?: number | undefined;
30
30
  verifySecrets?: boolean | undefined;
@@ -116,16 +116,16 @@ export declare const toolDefinition: {
116
116
  scanHistory: z.ZodDefault<z.ZodBoolean>;
117
117
  }, "strip", z.ZodTypeAny, {
118
118
  targetPath: string;
119
- secretTypes: ("password" | "api-key" | "private-key" | "token" | "connection-string" | "certificate" | "aws-key" | "aws-secret" | "gcp-key" | "azure-key" | "generic")[];
120
119
  excludePatterns: string[];
120
+ secretTypes: ("password" | "token" | "api-key" | "private-key" | "connection-string" | "certificate" | "aws-key" | "aws-secret" | "gcp-key" | "azure-key" | "generic")[];
121
121
  includeEntropy: boolean;
122
122
  entropyThreshold: number;
123
123
  verifySecrets: boolean;
124
124
  scanHistory: boolean;
125
125
  }, {
126
126
  targetPath: string;
127
- secretTypes?: ("password" | "api-key" | "private-key" | "token" | "connection-string" | "certificate" | "aws-key" | "aws-secret" | "gcp-key" | "azure-key" | "generic")[] | undefined;
128
127
  excludePatterns?: string[] | undefined;
128
+ secretTypes?: ("password" | "token" | "api-key" | "private-key" | "connection-string" | "certificate" | "aws-key" | "aws-secret" | "gcp-key" | "azure-key" | "generic")[] | undefined;
129
129
  includeEntropy?: boolean | undefined;
130
130
  entropyThreshold?: number | undefined;
131
131
  verifySecrets?: boolean | undefined;
@@ -0,0 +1,23 @@
1
+ import type { BusEvent } from './types.js';
2
+ type Listener = (e: BusEvent) => void;
3
+ /**
4
+ * Ground-truth event log for one org run. Append-only JSONL + in-process fanout.
5
+ * Every message, tool decision, asset, and usage record flows through here.
6
+ */
7
+ export declare class OrgBus {
8
+ readonly org: string;
9
+ readonly run: string;
10
+ readonly dir: string;
11
+ private listeners;
12
+ private seq;
13
+ private pending;
14
+ readonly file: string;
15
+ constructor(org: string, run: string, dir: string);
16
+ subscribe(fn: Listener): () => void;
17
+ emit(partial: Omit<BusEvent, 'id' | 'ts' | 'org' | 'run'>): BusEvent;
18
+ /** await all queued disk writes (tests, shutdown) */
19
+ flush(): Promise<void>;
20
+ static readHistory(dir: string): BusEvent[];
21
+ }
22
+ export {};
23
+ //# sourceMappingURL=bus.d.ts.map
@@ -0,0 +1,64 @@
1
+ // packages/@monomind/cli/src/orgrt/bus.ts
2
+ import { appendFile, mkdir } from 'node:fs/promises';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ /**
6
+ * Ground-truth event log for one org run. Append-only JSONL + in-process fanout.
7
+ * Every message, tool decision, asset, and usage record flows through here.
8
+ */
9
+ export class OrgBus {
10
+ org;
11
+ run;
12
+ dir;
13
+ listeners = new Set();
14
+ seq = 0;
15
+ pending = Promise.resolve();
16
+ file;
17
+ constructor(org, run, dir) {
18
+ this.org = org;
19
+ this.run = run;
20
+ this.dir = dir;
21
+ this.file = join(dir, 'bus.jsonl');
22
+ }
23
+ subscribe(fn) {
24
+ this.listeners.add(fn);
25
+ return () => this.listeners.delete(fn);
26
+ }
27
+ emit(partial) {
28
+ const e = {
29
+ id: `${this.run}-${Date.now()}-${this.seq++}`,
30
+ ts: Date.now(),
31
+ org: this.org,
32
+ run: this.run,
33
+ ...partial,
34
+ };
35
+ // serialize writes; never block emitters
36
+ this.pending = this.pending.then(async () => {
37
+ await mkdir(this.dir, { recursive: true });
38
+ await appendFile(this.file, JSON.stringify(e) + '\n', 'utf8');
39
+ }).catch(() => { });
40
+ for (const fn of this.listeners) {
41
+ try {
42
+ fn(e);
43
+ }
44
+ catch { /* listener errors never break the bus */ }
45
+ }
46
+ return e;
47
+ }
48
+ /** await all queued disk writes (tests, shutdown) */
49
+ flush() { return this.pending; }
50
+ static readHistory(dir) {
51
+ const f = join(dir, 'bus.jsonl');
52
+ if (!existsSync(f))
53
+ return [];
54
+ return readFileSync(f, 'utf8').trim().split('\n').filter(Boolean)
55
+ .map(l => { try {
56
+ return JSON.parse(l);
57
+ }
58
+ catch {
59
+ return null;
60
+ } })
61
+ .filter((e) => e !== null);
62
+ }
63
+ }
64
+ //# sourceMappingURL=bus.js.map
@@ -0,0 +1,41 @@
1
+ import { OrgBus } from './bus.js';
2
+ import { PolicyEngine } from './policy.js';
3
+ import { Mailbox } from './mailbox.js';
4
+ import { type OrgDef, type BusEvent } from './types.js';
5
+ import type { query } from '@anthropic-ai/claude-agent-sdk';
6
+ interface AgentRuntime {
7
+ mailbox: Mailbox;
8
+ policy: PolicyEngine;
9
+ done: Promise<void>;
10
+ }
11
+ export interface RunningOrg {
12
+ def: OrgDef;
13
+ run: string;
14
+ bus: OrgBus;
15
+ agents: Map<string, AgentRuntime>;
16
+ busEvents: () => BusEvent[];
17
+ }
18
+ export interface DaemonOpts {
19
+ queryFn?: typeof query;
20
+ forward?: boolean;
21
+ controlJson?: string;
22
+ }
23
+ export declare class OrgDaemon {
24
+ private root;
25
+ private opts;
26
+ private orgs;
27
+ private globalSubscribers;
28
+ constructor(root: string, opts?: DaemonOpts);
29
+ /** subscribe to events from ALL running orgs (dashboard server uses this) */
30
+ subscribe(fn: (e: BusEvent) => void): () => void;
31
+ listOrgs(): RunningOrg[];
32
+ getOrg(name: string): RunningOrg | undefined;
33
+ startOrg(name: string, taskOverride?: string): Promise<RunningOrg>;
34
+ /** Route a message. to = "role" (same org) or "org:role" (cross-org). Returns a receipt string. */
35
+ deliver(fromOrg: string, fromRole: string, to: string, subject: string, body: string): Promise<string>;
36
+ stopOrg(name: string): Promise<void>;
37
+ stopAll(): Promise<void>;
38
+ private persistState;
39
+ }
40
+ export {};
41
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1,101 @@
1
+ // packages/@monomind/cli/src/orgrt/daemon.ts
2
+ // monolean: single-process inter-org — upgrade path = daemon-to-daemon HTTP when multi-host is real
3
+ import { readFileSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { OrgBus } from './bus.js';
6
+ import { PolicyEngine } from './policy.js';
7
+ import { Mailbox } from './mailbox.js';
8
+ import { runAgentSession } from './session.js';
9
+ import { attachForwarder } from './forwarder.js';
10
+ import { OrgDefSchema, ORG_DIR } from './types.js';
11
+ export class OrgDaemon {
12
+ root;
13
+ opts;
14
+ orgs = new Map();
15
+ globalSubscribers = new Set();
16
+ constructor(root, opts = {}) {
17
+ this.root = root;
18
+ this.opts = opts;
19
+ }
20
+ /** subscribe to events from ALL running orgs (dashboard server uses this) */
21
+ subscribe(fn) {
22
+ this.globalSubscribers.add(fn);
23
+ return () => this.globalSubscribers.delete(fn);
24
+ }
25
+ listOrgs() { return [...this.orgs.values()]; }
26
+ getOrg(name) { return this.orgs.get(name); }
27
+ async startOrg(name, taskOverride) {
28
+ if (this.orgs.has(name))
29
+ throw new Error(`org ${name} already running`);
30
+ const defPath = join(this.root, ORG_DIR, `${name}.json`);
31
+ const def = OrgDefSchema.parse(JSON.parse(readFileSync(defPath, 'utf8')));
32
+ const run = `run-${new Date().toISOString().replace(/[-:T]/g, '').slice(0, 15)}`;
33
+ const dir = join(this.root, ORG_DIR, name, run);
34
+ mkdirSync(dir, { recursive: true });
35
+ const cwd = join(this.root, ORG_DIR, name, 'workspace');
36
+ mkdirSync(cwd, { recursive: true });
37
+ const bus = new OrgBus(name, run, dir);
38
+ const collected = [];
39
+ bus.subscribe(e => { collected.push(e); for (const fn of this.globalSubscribers)
40
+ fn(e); });
41
+ if (this.opts.forward !== false)
42
+ attachForwarder(bus, this.opts.controlJson ?? join(this.root, '.monomind/control.json'));
43
+ const running = { def, run, bus, agents: new Map(), busEvents: () => [...collected] };
44
+ this.orgs.set(name, running);
45
+ const perRoleBudget = Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length);
46
+ for (const role of def.roles) {
47
+ const mailbox = new Mailbox();
48
+ const policy = new PolicyEngine(role.id, { maxTokens: perRoleBudget, ...(role.policy ?? {}) }, bus, cwd);
49
+ const done = runAgentSession({
50
+ org: name, role, bus, policy, mailbox, cwd, def,
51
+ maxTurns: def.run_config.max_turns_per_message,
52
+ deliver: (from, to, subject, body) => this.deliver(name, from, to, subject, body),
53
+ queryFn: this.opts.queryFn,
54
+ }).catch(() => { });
55
+ running.agents.set(role.id, { mailbox, policy, done });
56
+ }
57
+ const boss = def.roles.find(r => r.type === 'boss' || r.reports_to === null) ?? def.roles[0];
58
+ running.agents.get(boss.id).mailbox.push(`Org "${name}" started (run ${run}).\nGoal: ${taskOverride ?? def.goal}\n` +
59
+ `Coordinate your team via org_send. Report completion by ending your turn.`);
60
+ bus.emit({ type: 'status', msg: `org started (${def.roles.length} agents)`, data: { goal: taskOverride ?? def.goal } });
61
+ this.persistState(name, 'running', run);
62
+ return running;
63
+ }
64
+ /** Route a message. to = "role" (same org) or "org:role" (cross-org). Returns a receipt string. */
65
+ async deliver(fromOrg, fromRole, to, subject, body) {
66
+ const cross = to.includes(':');
67
+ const [targetOrgName, targetRole] = cross ? to.split(':', 2) : [fromOrg, to];
68
+ const targetOrg = this.orgs.get(targetOrgName);
69
+ const src = this.orgs.get(fromOrg);
70
+ if (!targetOrg || !targetOrg.agents.has(targetRole)) {
71
+ src?.bus.emit({ type: 'audit', from: fromRole, to, msg: `undeliverable: ${subject}`, reason: 'unknown recipient' });
72
+ return `ERROR: unknown recipient "${to}" (known: ${[...(targetOrg?.agents.keys() ?? this.orgs.keys())].join(', ')})`;
73
+ }
74
+ const evt = { from: cross ? `${fromOrg}:${fromRole}` : fromRole, to: cross ? to : targetRole, subject, msg: body };
75
+ src?.bus.emit({ type: cross ? 'xorg' : 'message', ...evt });
76
+ if (cross && targetOrg !== src)
77
+ targetOrg.bus.emit({ type: 'xorg', ...evt });
78
+ targetOrg.agents.get(targetRole).mailbox.push(`[message from ${evt.from}] subject: ${subject}\n\n${body}`);
79
+ return `delivered to ${to}`;
80
+ }
81
+ async stopOrg(name) {
82
+ const org = this.orgs.get(name);
83
+ if (!org)
84
+ return;
85
+ for (const a of org.agents.values())
86
+ a.mailbox.close();
87
+ await Promise.allSettled([...org.agents.values()].map(a => a.done));
88
+ org.bus.emit({ type: 'status', msg: 'org stopped' });
89
+ await org.bus.flush();
90
+ this.persistState(name, 'stopped', org.run);
91
+ this.orgs.delete(name);
92
+ }
93
+ async stopAll() {
94
+ await Promise.all([...this.orgs.keys()].map(n => this.stopOrg(n)));
95
+ }
96
+ persistState(name, status, run) {
97
+ const p = join(this.root, ORG_DIR, name, 'runtime.json');
98
+ writeFileSync(p, JSON.stringify({ status, run, pid: process.pid, updated: new Date().toISOString() }, null, 2));
99
+ }
100
+ }
101
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1,11 @@
1
+ import type { OrgBus } from './bus.js';
2
+ /**
3
+ * Forwards every bus event to the running mastermind control server
4
+ * (dist/src/ui/server.mjs, POST /api/mastermind/event) so the existing
5
+ * dashboard SSE stream shows org activity. Best-effort: failures are dropped.
6
+ */
7
+ export declare function attachForwarder(bus: OrgBus, controlJsonPath?: string): {
8
+ settle: () => Promise<void>;
9
+ unsubscribe: () => void;
10
+ };
11
+ //# sourceMappingURL=forwarder.d.ts.map
@@ -0,0 +1,37 @@
1
+ // packages/@monomind/cli/src/orgrt/forwarder.ts
2
+ import { readFileSync, existsSync } from 'node:fs';
3
+ /**
4
+ * Forwards every bus event to the running mastermind control server
5
+ * (dist/src/ui/server.mjs, POST /api/mastermind/event) so the existing
6
+ * dashboard SSE stream shows org activity. Best-effort: failures are dropped.
7
+ */
8
+ export function attachForwarder(bus, controlJsonPath = '.monomind/control.json') {
9
+ let chain = Promise.resolve();
10
+ const baseUrl = () => {
11
+ try {
12
+ if (!existsSync(controlJsonPath))
13
+ return null;
14
+ const c = JSON.parse(readFileSync(controlJsonPath, 'utf8'));
15
+ return typeof c.url === 'string' ? c.url : `http://localhost:${c.port ?? 4242}`;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ };
21
+ const unsubscribe = bus.subscribe((e) => {
22
+ chain = chain.then(async () => {
23
+ const url = baseUrl();
24
+ if (!url)
25
+ return;
26
+ const payload = { ...e, type: `org:${e.type}`, session: `${e.org}:${e.run}`, domain: 'ops' };
27
+ await fetch(`${url}/api/mastermind/event`, {
28
+ method: 'POST',
29
+ headers: { 'Content-Type': 'application/json' },
30
+ body: JSON.stringify(payload),
31
+ signal: AbortSignal.timeout(3000),
32
+ }).then(r => { r.body?.cancel(); }).catch(() => { });
33
+ }).catch(() => { });
34
+ });
35
+ return { settle: () => chain, unsubscribe };
36
+ }
37
+ //# sourceMappingURL=forwarder.js.map
@@ -0,0 +1,25 @@
1
+ /** Shape the SDK expects for streaming-input user messages. */
2
+ export interface OrgUserMessage {
3
+ type: 'user';
4
+ message: {
5
+ role: 'user';
6
+ content: string;
7
+ };
8
+ parent_tool_use_id: null;
9
+ session_id: string;
10
+ }
11
+ /**
12
+ * Async message queue feeding one persistent SDK session.
13
+ * push() from the daemon (deliveries from other agents / the user);
14
+ * stream() is passed as the `prompt` of query() to keep the session open.
15
+ */
16
+ export declare class Mailbox {
17
+ private queue;
18
+ private wake;
19
+ private closed;
20
+ push(text: string): void;
21
+ close(): void;
22
+ get isClosed(): boolean;
23
+ stream(sessionId?: string): AsyncGenerator<OrgUserMessage>;
24
+ }
25
+ //# sourceMappingURL=mailbox.d.ts.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Async message queue feeding one persistent SDK session.
3
+ * push() from the daemon (deliveries from other agents / the user);
4
+ * stream() is passed as the `prompt` of query() to keep the session open.
5
+ */
6
+ export class Mailbox {
7
+ queue = [];
8
+ wake = null;
9
+ closed = false;
10
+ push(text) {
11
+ if (this.closed)
12
+ return;
13
+ this.queue.push(text);
14
+ this.wake?.();
15
+ this.wake = null;
16
+ }
17
+ close() {
18
+ this.closed = true;
19
+ this.wake?.();
20
+ this.wake = null;
21
+ }
22
+ get isClosed() { return this.closed; }
23
+ async *stream(sessionId = '') {
24
+ while (true) {
25
+ while (this.queue.length > 0) {
26
+ yield {
27
+ type: 'user',
28
+ message: { role: 'user', content: this.queue.shift() },
29
+ parent_tool_use_id: null,
30
+ session_id: sessionId,
31
+ };
32
+ }
33
+ if (this.closed)
34
+ return;
35
+ await new Promise(r => { this.wake = r; });
36
+ }
37
+ }
38
+ }
39
+ //# sourceMappingURL=mailbox.js.map
@@ -0,0 +1,24 @@
1
+ import type { OrgBus } from './bus.js';
2
+ import type { RolePolicy } from './types.js';
3
+ export type Decision = {
4
+ behavior: 'allow';
5
+ updatedInput: Record<string, unknown>;
6
+ } | {
7
+ behavior: 'deny';
8
+ message: string;
9
+ };
10
+ /** tiny glob→RegExp: supports ** (any depth) and * (single segment). */
11
+ export declare function globToRegExp(glob: string): RegExp;
12
+ export declare class PolicyEngine {
13
+ readonly role: string;
14
+ readonly policy: RolePolicy;
15
+ private bus;
16
+ private cwd;
17
+ private used;
18
+ constructor(role: string, policy: RolePolicy, bus: OrgBus, cwd: string);
19
+ addUsage(tokens: number): void;
20
+ get usage(): number;
21
+ get overBudget(): boolean;
22
+ decide(tool: string, input: Record<string, unknown>): Promise<Decision>;
23
+ }
24
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1,86 @@
1
+ // packages/@monomind/cli/src/orgrt/policy.ts
2
+ import { relative, resolve } from 'node:path';
3
+ const WRITE_TOOLS = new Set(['Write', 'Edit', 'NotebookEdit']);
4
+ const READ_TOOLS = new Set(['Read', 'Glob', 'Grep']);
5
+ const WEB_TOOLS = new Set(['WebFetch', 'WebSearch']);
6
+ /** tiny glob→RegExp: supports ** (any depth) and * (single segment). */
7
+ export function globToRegExp(glob) {
8
+ const esc = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&')
9
+ .replace(/\*\*/g, ' ').replace(/\*/g, '[^/]*').replace(/ /g, '.*');
10
+ return new RegExp(`^${esc}$`);
11
+ }
12
+ export class PolicyEngine {
13
+ role;
14
+ policy;
15
+ bus;
16
+ cwd;
17
+ used = 0;
18
+ constructor(role, policy, bus, cwd) {
19
+ this.role = role;
20
+ this.policy = policy;
21
+ this.bus = bus;
22
+ this.cwd = cwd;
23
+ }
24
+ addUsage(tokens) { this.used += tokens; }
25
+ get usage() { return this.used; }
26
+ get overBudget() {
27
+ return this.policy.maxTokens != null && this.used >= this.policy.maxTokens;
28
+ }
29
+ async decide(tool, input) {
30
+ const deny = (reason) => {
31
+ this.bus.emit({ type: 'tool', from: this.role, tool, decision: 'deny', reason, data: { input: summarize(input) } });
32
+ return { behavior: 'deny', message: `[org-policy] ${reason}` };
33
+ };
34
+ const allow = () => {
35
+ this.bus.emit({ type: 'tool', from: this.role, tool, decision: 'allow', data: { input: summarize(input) } });
36
+ if (WRITE_TOOLS.has(tool) && typeof input.file_path === 'string') {
37
+ this.bus.emit({ type: 'asset', from: this.role, path: String(input.file_path) });
38
+ }
39
+ return { behavior: 'allow', updatedInput: input };
40
+ };
41
+ if (this.overBudget)
42
+ return deny(`token budget exhausted (${this.used}/${this.policy.maxTokens})`);
43
+ if (this.policy.denyTools?.includes(tool))
44
+ return deny(`tool ${tool} is denied for role ${this.role}`);
45
+ if (this.policy.allowTools && !this.policy.allowTools.includes(tool) && !tool.startsWith('mcp__org__'))
46
+ return deny(`tool ${tool} not in allowlist for role ${this.role}`);
47
+ if (WRITE_TOOLS.has(tool) || READ_TOOLS.has(tool)) {
48
+ const globs = WRITE_TOOLS.has(tool) ? (this.policy.fileWrite ?? ['**']) : (this.policy.fileRead ?? ['**']);
49
+ const p = typeof input.file_path === 'string' ? input.file_path
50
+ : typeof input.path === 'string' ? input.path : null;
51
+ if (p !== null) {
52
+ const rel = relative(this.cwd, resolve(this.cwd, p));
53
+ if (rel.startsWith('..'))
54
+ return deny(`path escapes org workdir: ${p}`);
55
+ if (!globs.some(g => globToRegExp(g).test(rel)))
56
+ return deny(`path ${rel} outside ${WRITE_TOOLS.has(tool) ? 'write' : 'read'} scope`);
57
+ }
58
+ }
59
+ if (WEB_TOOLS.has(tool) && this.policy.webAllow !== undefined) {
60
+ if (this.policy.webAllow.length === 0)
61
+ return deny(`web access disabled for role ${this.role}`);
62
+ if (tool === 'WebFetch') {
63
+ const host = safeHost(String(input.url ?? ''));
64
+ if (!host || !this.policy.webAllow.some(d => host === d || host.endsWith(`.${d}`)))
65
+ return deny(`domain ${host ?? '?'} not in research allowlist`);
66
+ }
67
+ // WebSearch has no URL up front; allowed if webAllow is non-empty
68
+ }
69
+ return allow();
70
+ }
71
+ }
72
+ function safeHost(url) {
73
+ try {
74
+ return new URL(url).hostname;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ function summarize(input) {
81
+ const out = {};
82
+ for (const [k, v] of Object.entries(input))
83
+ out[k] = typeof v === 'string' && v.length > 200 ? v.slice(0, 200) + '…' : v;
84
+ return out;
85
+ }
86
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1,9 @@
1
+ import type { ProviderConfig } from './types.js';
2
+ /**
3
+ * Builds the child-process env for one agent session.
4
+ * Default (no provider block) = subscription: remove the API key var so the
5
+ * spawned Claude Code engine uses the user's `claude login` credentials.
6
+ * Never stores secrets in org JSON — only env var NAMES.
7
+ */
8
+ export declare function resolveProviderEnv(cfg: ProviderConfig | undefined, parentEnv?: Record<string, string | undefined>): Record<string, string>;
9
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1,54 @@
1
+ const KEY_VAR = ['ANTHROPIC', 'API', 'KEY'].join('_');
2
+ /**
3
+ * Builds the child-process env for one agent session.
4
+ * Default (no provider block) = subscription: remove the API key var so the
5
+ * spawned Claude Code engine uses the user's `claude login` credentials.
6
+ * Never stores secrets in org JSON — only env var NAMES.
7
+ */
8
+ export function resolveProviderEnv(cfg, parentEnv = process.env) {
9
+ const env = {};
10
+ for (const [k, v] of Object.entries(parentEnv))
11
+ if (v !== undefined)
12
+ env[k] = v;
13
+ const kind = cfg?.kind ?? 'subscription';
14
+ switch (kind) {
15
+ case 'subscription':
16
+ delete env[KEY_VAR];
17
+ delete env.ANTHROPIC_BASE_URL;
18
+ delete env.ANTHROPIC_AUTH_TOKEN;
19
+ break;
20
+ case 'api-key': {
21
+ const name = cfg?.apiKeyEnv ?? KEY_VAR;
22
+ const key = parentEnv[name];
23
+ if (!key)
24
+ throw new Error(`provider api-key: env var ${name} is not set`);
25
+ env[KEY_VAR] = key;
26
+ break;
27
+ }
28
+ case 'base-url': {
29
+ if (!cfg?.baseUrl)
30
+ throw new Error('provider base-url: baseUrl is required');
31
+ env.ANTHROPIC_BASE_URL = cfg.baseUrl;
32
+ delete env[KEY_VAR];
33
+ if (cfg.authTokenEnv) {
34
+ const tok = parentEnv[cfg.authTokenEnv];
35
+ if (!tok)
36
+ throw new Error(`provider base-url: env var ${cfg.authTokenEnv} is not set`);
37
+ env.ANTHROPIC_AUTH_TOKEN = tok;
38
+ }
39
+ break;
40
+ }
41
+ case 'bedrock':
42
+ env.CLAUDE_CODE_USE_BEDROCK = '1';
43
+ delete env[KEY_VAR];
44
+ delete env.ANTHROPIC_AUTH_TOKEN;
45
+ break;
46
+ case 'vertex':
47
+ env.CLAUDE_CODE_USE_VERTEX = '1';
48
+ delete env[KEY_VAR];
49
+ delete env.ANTHROPIC_AUTH_TOKEN;
50
+ break;
51
+ }
52
+ return env;
53
+ }
54
+ //# sourceMappingURL=provider.js.map