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.
- package/README.md +11 -12
- package/package.json +2 -2
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +10 -0
- package/packages/@monomind/cli/README.md +11 -12
- package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +71 -12
- package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +4 -4
- package/packages/@monomind/cli/dist/src/commands/browse-workflow.js +2 -2
- package/packages/@monomind/cli/dist/src/index.js +8 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +18 -18
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +12 -12
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +23 -0
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +64 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +41 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +101 -0
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +37 -0
- package/packages/@monomind/cli/dist/src/orgrt/mailbox.d.ts +25 -0
- package/packages/@monomind/cli/dist/src/orgrt/mailbox.js +39 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +24 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.js +86 -0
- package/packages/@monomind/cli/dist/src/orgrt/provider.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/orgrt/provider.js +54 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +23 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.js +78 -0
- package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +847 -0
- package/packages/@monomind/cli/dist/src/orgrt/types.js +51 -0
- package/packages/@monomind/cli/dist/src/parser.js +31 -5
- package/packages/@monomind/cli/dist/src/ui/collector.mjs +69 -44
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +112 -25
- package/packages/@monomind/cli/dist/src/ui/orgs.html +54 -0
- package/packages/@monomind/cli/dist/src/ui/server.mjs +87 -12
- package/packages/@monomind/cli/package.json +3 -2
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.d.ts +0 -36
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +0 -88
package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts
CHANGED
|
@@ -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: "
|
|
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?: "
|
|
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: "
|
|
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?: "
|
|
256
|
+
groupBy?: "type" | "none" | "file" | "risk" | undefined;
|
|
257
257
|
}>;
|
|
258
258
|
handler: typeof handler;
|
|
259
259
|
};
|
package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts
CHANGED
|
@@ -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
|