@monoes/monomindcli 2.10.30 → 2.10.31
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/.claude/helpers/graphify-freshen.cjs +11 -12
- package/.claude/helpers/handlers/capture-handler.cjs +1 -1
- package/dist/src/commands/analyze-diff.d.ts.map +1 -1
- package/dist/src/commands/analyze-diff.js +6 -0
- package/dist/src/commands/analyze-diff.js.map +1 -1
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +2 -2
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/org-observe.d.ts +15 -7
- package/dist/src/commands/org-observe.d.ts.map +1 -1
- package/dist/src/commands/org-observe.js +208 -63
- package/dist/src/commands/org-observe.js.map +1 -1
- package/dist/src/commands/org.d.ts.map +1 -1
- package/dist/src/commands/org.js +57 -4
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/status.d.ts.map +1 -1
- package/dist/src/commands/status.js +11 -2
- package/dist/src/commands/status.js.map +1 -1
- package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/system-tools.js.map +1 -1
- package/dist/src/memory/memory-kg.d.ts.map +1 -1
- package/dist/src/memory/memory-kg.js +10 -2
- package/dist/src/memory/memory-kg.js.map +1 -1
- package/dist/src/orgrt/agent-exec.d.ts.map +1 -1
- package/dist/src/orgrt/agent-exec.js +7 -3
- package/dist/src/orgrt/agent-exec.js.map +1 -1
- package/dist/src/orgrt/antigravity-runner.d.ts.map +1 -1
- package/dist/src/orgrt/antigravity-runner.js +6 -2
- package/dist/src/orgrt/antigravity-runner.js.map +1 -1
- package/dist/src/orgrt/approvals.d.ts +15 -1
- package/dist/src/orgrt/approvals.d.ts.map +1 -1
- package/dist/src/orgrt/approvals.js +76 -11
- package/dist/src/orgrt/approvals.js.map +1 -1
- package/dist/src/orgrt/broker.d.ts +10 -2
- package/dist/src/orgrt/broker.d.ts.map +1 -1
- package/dist/src/orgrt/broker.js +20 -5
- package/dist/src/orgrt/broker.js.map +1 -1
- package/dist/src/orgrt/checkpoint.d.ts.map +1 -1
- package/dist/src/orgrt/checkpoint.js.map +1 -1
- package/dist/src/orgrt/cross-org.d.ts +15 -2
- package/dist/src/orgrt/cross-org.d.ts.map +1 -1
- package/dist/src/orgrt/cross-org.js +169 -16
- package/dist/src/orgrt/cross-org.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +31 -3
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +132 -21
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/decisions.d.ts.map +1 -1
- package/dist/src/orgrt/decisions.js +13 -1
- package/dist/src/orgrt/decisions.js.map +1 -1
- package/dist/src/orgrt/endpoint-roles.d.ts +86 -0
- package/dist/src/orgrt/endpoint-roles.d.ts.map +1 -0
- package/dist/src/orgrt/endpoint-roles.js +364 -0
- package/dist/src/orgrt/endpoint-roles.js.map +1 -0
- package/dist/src/orgrt/inbox.d.ts +18 -0
- package/dist/src/orgrt/inbox.d.ts.map +1 -1
- package/dist/src/orgrt/inbox.js +73 -0
- package/dist/src/orgrt/inbox.js.map +1 -1
- package/dist/src/orgrt/migrate.d.ts.map +1 -1
- package/dist/src/orgrt/migrate.js +3 -0
- package/dist/src/orgrt/migrate.js.map +1 -1
- package/dist/src/orgrt/opencode-runner.d.ts.map +1 -1
- package/dist/src/orgrt/opencode-runner.js +2 -1
- package/dist/src/orgrt/opencode-runner.js.map +1 -1
- package/dist/src/orgrt/pi-rpc-runner.d.ts.map +1 -1
- package/dist/src/orgrt/pi-rpc-runner.js +6 -2
- package/dist/src/orgrt/pi-rpc-runner.js.map +1 -1
- package/dist/src/orgrt/policy.d.ts +22 -1
- package/dist/src/orgrt/policy.d.ts.map +1 -1
- package/dist/src/orgrt/policy.js +36 -4
- package/dist/src/orgrt/policy.js.map +1 -1
- package/dist/src/orgrt/questions.d.ts +3 -1
- package/dist/src/orgrt/questions.d.ts.map +1 -1
- package/dist/src/orgrt/questions.js +19 -3
- package/dist/src/orgrt/questions.js.map +1 -1
- package/dist/src/orgrt/qwen-rpc-runner.d.ts.map +1 -1
- package/dist/src/orgrt/qwen-rpc-runner.js +3 -1
- package/dist/src/orgrt/qwen-rpc-runner.js.map +1 -1
- package/dist/src/orgrt/role-slot.d.ts +1 -2
- package/dist/src/orgrt/role-slot.d.ts.map +1 -1
- package/dist/src/orgrt/role-slot.js +2 -1
- package/dist/src/orgrt/role-slot.js.map +1 -1
- package/dist/src/orgrt/scheduler-integration.d.ts.map +1 -1
- package/dist/src/orgrt/scheduler-integration.js +8 -1
- package/dist/src/orgrt/scheduler-integration.js.map +1 -1
- package/dist/src/orgrt/server.d.ts.map +1 -1
- package/dist/src/orgrt/server.js +43 -8
- package/dist/src/orgrt/server.js.map +1 -1
- package/dist/src/orgrt/session.d.ts +12 -1
- package/dist/src/orgrt/session.d.ts.map +1 -1
- package/dist/src/orgrt/session.js +20 -3
- package/dist/src/orgrt/session.js.map +1 -1
- package/dist/src/orgrt/tool-providers.d.ts +110 -0
- package/dist/src/orgrt/tool-providers.d.ts.map +1 -0
- package/dist/src/orgrt/tool-providers.js +617 -0
- package/dist/src/orgrt/tool-providers.js.map +1 -0
- package/dist/src/orgrt/types.d.ts +72 -0
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +56 -0
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/protocol-capabilities.d.ts +7 -1
- package/dist/src/protocol-capabilities.d.ts.map +1 -1
- package/dist/src/protocol-capabilities.js +15 -1
- package/dist/src/protocol-capabilities.js.map +1 -1
- package/dist/src/ui/routes-org.mjs +9 -7
- package/dist/src/utils/native-error.d.ts.map +1 -1
- package/dist/src/utils/native-error.js +9 -5
- package/dist/src/utils/native-error.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -26,6 +26,18 @@ export const printOrgJson = (payload) => {
|
|
|
26
26
|
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
27
27
|
return { success: true, data: payload };
|
|
28
28
|
};
|
|
29
|
+
/** M5: `--by <resolver>` — who resolved a decision (default `human`). Returns
|
|
30
|
+
* an error string for an invalid value. */
|
|
31
|
+
const resolverFlag = async (ctx) => {
|
|
32
|
+
const raw = ctx.flags.by;
|
|
33
|
+
if (raw === undefined || raw === false)
|
|
34
|
+
return { ok: true, by: 'human' };
|
|
35
|
+
const { normalizeResolver } = await import('../orgrt/approvals.js');
|
|
36
|
+
const by = normalizeResolver(raw);
|
|
37
|
+
if (!by)
|
|
38
|
+
return { ok: false, message: '--by must be 1-128 printable characters' };
|
|
39
|
+
return { ok: true, by };
|
|
40
|
+
};
|
|
29
41
|
/** Validate org config(s) against OrgDefSchema — the exact parse `org run`/`org serve`
|
|
30
42
|
* perform — plus the structural invariants the runtime assumes but the schema can't
|
|
31
43
|
* express (single root role, resolvable reports_to, unique ids, parseable schedule). */
|
|
@@ -83,6 +95,19 @@ export const validateAction = async (ctx) => {
|
|
|
83
95
|
? { success: false, message: `${failed} of ${files.length} org config(s) failed validation` }
|
|
84
96
|
: { success: true, message: `${files.length} org config(s) valid` };
|
|
85
97
|
};
|
|
98
|
+
/** M2: ids of the org's endpoint roles (automations, not agents) — excluded
|
|
99
|
+
* from the costs/report/flow role tables. Unreadable config → none. */
|
|
100
|
+
const endpointRoleIds = (cwd, name) => {
|
|
101
|
+
try {
|
|
102
|
+
const raw = JSON.parse(readFileSync(join(cwd, ORG_DIR, `${name}.json`), 'utf8'));
|
|
103
|
+
return new Set((Array.isArray(raw.roles) ? raw.roles : [])
|
|
104
|
+
.filter((r) => r?.kind === 'endpoint' && typeof r.id === 'string')
|
|
105
|
+
.map((r) => r.id));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return new Set();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
86
111
|
// Run ids are joined into filesystem paths — enforce the daemon's own id shape
|
|
87
112
|
// so a crafted --run can't traverse out of the org directory (same reason the
|
|
88
113
|
// org-name guard exists).
|
|
@@ -295,6 +320,10 @@ export const reportAction = async (ctx, name) => {
|
|
|
295
320
|
if (!events.length)
|
|
296
321
|
return { success: false, message: `run ${run} has no recorded events` };
|
|
297
322
|
const s = summarizeRun(events);
|
|
323
|
+
// M2: endpoint roles are automations — they have no row in the role tables.
|
|
324
|
+
const endpointIds = endpointRoleIds(ctx.cwd, name);
|
|
325
|
+
for (const id of endpointIds)
|
|
326
|
+
delete s.roles[id];
|
|
298
327
|
// Protocol JSON mode (§7.2): the run summary as a bare object. Emitted
|
|
299
328
|
// before the human-only flag modes (mermaid/audit/by-role) — those render
|
|
300
329
|
// views of the same summary and have no JSON shape defined by the spec.
|
|
@@ -406,8 +435,9 @@ export const reportAction = async (ctx, name) => {
|
|
|
406
435
|
const roleCeiling = new Map();
|
|
407
436
|
try {
|
|
408
437
|
const def = OrgDefSchema.parse(JSON.parse(readFileSync(join(ctx.cwd, ORG_DIR, `${name}.json`), 'utf8')));
|
|
409
|
-
|
|
410
|
-
|
|
438
|
+
const sessionRoles = def.roles.filter((r) => r.kind !== 'endpoint');
|
|
439
|
+
perRoleBudget = Math.floor((def.run_config.budget_tokens ?? 1_000_000) / Math.max(1, sessionRoles.length));
|
|
440
|
+
for (const r of sessionRoles) {
|
|
411
441
|
const max = r.policy?.maxTokens;
|
|
412
442
|
roleCeiling.set(r.id, max ?? r.budget_tokens ?? perRoleBudget);
|
|
413
443
|
}
|
|
@@ -545,7 +575,18 @@ export const approvalsAction = async (ctx, name) => {
|
|
|
545
575
|
}
|
|
546
576
|
const shown = ctx.flags.all === true ? all : all.filter((a) => a.approved === null);
|
|
547
577
|
if (orgJson(ctx))
|
|
548
|
-
return printOrgJson({
|
|
578
|
+
return printOrgJson({
|
|
579
|
+
v: 1,
|
|
580
|
+
org: name,
|
|
581
|
+
// M5: requestId / resolvedBy / input are always present (null for
|
|
582
|
+
// entries recorded before they existed).
|
|
583
|
+
items: shown.map((a) => ({
|
|
584
|
+
...a,
|
|
585
|
+
requestId: a.requestId ?? null,
|
|
586
|
+
resolvedBy: a.resolvedBy ?? null,
|
|
587
|
+
input: a.input ?? null,
|
|
588
|
+
})),
|
|
589
|
+
});
|
|
549
590
|
if (!shown.length) {
|
|
550
591
|
log(output.info(all.length
|
|
551
592
|
? `No pending approvals for org ${name} (${all.length} resolved — use --all).`
|
|
@@ -555,10 +596,12 @@ export const approvalsAction = async (ctx, name) => {
|
|
|
555
596
|
for (const a of shown) {
|
|
556
597
|
const when = new Date(a.ts).toISOString().replace('T', ' ').slice(0, 16);
|
|
557
598
|
const mark = a.approved === null ? '❓' : a.approved ? '✓' : '✗';
|
|
558
|
-
|
|
599
|
+
const ref = a.requestId ? ` [${a.requestId}]` : '';
|
|
600
|
+
const by = a.resolvedBy ? ` (by ${a.resolvedBy})` : '';
|
|
601
|
+
log(output.info(`${mark} ${when} ${a.roleId}: ${a.action}${ref}${by}`));
|
|
559
602
|
}
|
|
560
603
|
if (shown.some((a) => a.approved === null))
|
|
561
|
-
log(output.info(`\nApprove with: monomind org approve ${name} <role> <action
|
|
604
|
+
log(output.info(`\nApprove with: monomind org approve ${name} <role> <action> [--request <id>] [--by <resolver>]\nDeny with: monomind org deny ${name} <role> <action> [--request <id>] [--by <resolver>]`));
|
|
562
605
|
return { success: true };
|
|
563
606
|
};
|
|
564
607
|
/** `org answer <name> <question-id> <answer...>` — answer a pending ask_human question.
|
|
@@ -587,6 +630,10 @@ export const answerAction = async (ctx, name) => {
|
|
|
587
630
|
}
|
|
588
631
|
if (q.answer !== null)
|
|
589
632
|
return { success: false, message: `question "${questionId}" was already answered` };
|
|
633
|
+
const byFlag = await resolverFlag(ctx);
|
|
634
|
+
if (!byFlag.ok)
|
|
635
|
+
return { success: false, message: byFlag.message };
|
|
636
|
+
const resolvedBy = byFlag.by;
|
|
590
637
|
// Live path: the hosting daemon updates questions.json and pushes into the role's mailbox.
|
|
591
638
|
// SEC: answering a role's question is a human decision — authenticate with
|
|
592
639
|
// the operator credential, not the agent-facing one in the broker entry.
|
|
@@ -601,7 +648,7 @@ export const answerAction = async (ctx, name) => {
|
|
|
601
648
|
'Content-Type': 'application/json',
|
|
602
649
|
...(cred ? { 'x-monomind-cred': cred } : {}),
|
|
603
650
|
},
|
|
604
|
-
body: JSON.stringify({ org: name, role: q.role, questionId, answer }),
|
|
651
|
+
body: JSON.stringify({ org: name, role: q.role, questionId, answer, resolvedBy }),
|
|
605
652
|
signal: AbortSignal.timeout(10_000),
|
|
606
653
|
});
|
|
607
654
|
const data = (await res.json().catch(() => ({})));
|
|
@@ -614,6 +661,7 @@ export const answerAction = async (ctx, name) => {
|
|
|
614
661
|
role: q.role,
|
|
615
662
|
delivery: 'live',
|
|
616
663
|
answered: true,
|
|
664
|
+
resolvedBy,
|
|
617
665
|
});
|
|
618
666
|
log(output.success(`Answer delivered to ${name}:${q.role} (live).`));
|
|
619
667
|
return { success: true };
|
|
@@ -666,8 +714,8 @@ export const answerAction = async (ctx, name) => {
|
|
|
666
714
|
return { success: false, message: 'queueing failed — answer not recorded' };
|
|
667
715
|
}
|
|
668
716
|
const merged = fresh.some((x) => x.questionId === questionId)
|
|
669
|
-
? fresh.map((x) =>
|
|
670
|
-
: [...fresh, { ...q, answer, answeredAt: Date.now() }];
|
|
717
|
+
? fresh.map((x) => x.questionId === questionId ? { ...x, answer, answeredAt: Date.now(), resolvedBy } : x)
|
|
718
|
+
: [...fresh, { ...q, answer, answeredAt: Date.now(), resolvedBy }];
|
|
671
719
|
const dest = join(ctx.cwd, ORG_DIR, name, 'questions.json');
|
|
672
720
|
const tmp = `${dest}.${process.pid}.tmp`;
|
|
673
721
|
writeFileSync(tmp, JSON.stringify({ questions: merged }, null, 2));
|
|
@@ -681,18 +729,42 @@ export const answerAction = async (ctx, name) => {
|
|
|
681
729
|
role: q.role,
|
|
682
730
|
delivery: 'queued',
|
|
683
731
|
answered: true,
|
|
732
|
+
resolvedBy,
|
|
684
733
|
});
|
|
685
734
|
log(output.success(`Answer recorded — ${name}:${q.role} receives it when the org next runs.`));
|
|
686
735
|
return { success: true };
|
|
687
736
|
};
|
|
688
737
|
/** `org inbox <name> --json '{"from":"orgA:role","subject":"...","body":"..."}' [--to role]`
|
|
738
|
+
* (or `--from/--subject/--body`) `[--format json]`.
|
|
689
739
|
* Inbound entrypoint for cross-org/remote delivery — orgrt/remote.ts's deliverRemote()
|
|
690
|
-
* shells out to exactly this command over SSH
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
740
|
+
* shells out to exactly this command over SSH, and mono-agent replies through it.
|
|
741
|
+
*
|
|
742
|
+
* Live path (M3): POST to the hosting daemon's /api/xdeliver with the OPERATOR
|
|
743
|
+
* credential (readOperatorCredential), which lets the daemon trust `from` as
|
|
744
|
+
* given — the sender org need not be registered (`workflow`, an automation
|
|
745
|
+
* role). Without an operator credential it presents the sender org's own
|
|
746
|
+
* broker credential, if that org is registered. Offline path: spool into
|
|
747
|
+
* inbox.jsonl, drained into the role's mailbox on the org's next start.
|
|
748
|
+
*
|
|
749
|
+
* `--format json` prints `{"v":1,"org","to","from","delivery":"live"|"queued",
|
|
750
|
+
* "receipt","messageId"}`. Exit 0 for live and queued; non-zero only for
|
|
751
|
+
* invalid input or an unknown org. */
|
|
695
752
|
export const inboxAction = async (ctx, name) => {
|
|
753
|
+
const json = orgJson(ctx);
|
|
754
|
+
// JSON mode keeps stdout to the single payload — diagnostics go to stderr.
|
|
755
|
+
const note = (text) => {
|
|
756
|
+
if (json)
|
|
757
|
+
process.stderr.write(`${text}\n`);
|
|
758
|
+
else
|
|
759
|
+
log(text);
|
|
760
|
+
};
|
|
761
|
+
const fail = (message) => {
|
|
762
|
+
if (json)
|
|
763
|
+
process.stdout.write(`${JSON.stringify({ v: 1, org: name, error: message })}\n`);
|
|
764
|
+
else
|
|
765
|
+
log(output.error(message));
|
|
766
|
+
return { success: false, message };
|
|
767
|
+
};
|
|
696
768
|
let payload = {};
|
|
697
769
|
const rawJson = ctx.flags.json;
|
|
698
770
|
if (typeof rawJson === 'string') {
|
|
@@ -700,7 +772,7 @@ export const inboxAction = async (ctx, name) => {
|
|
|
700
772
|
payload = JSON.parse(rawJson);
|
|
701
773
|
}
|
|
702
774
|
catch {
|
|
703
|
-
return
|
|
775
|
+
return fail('org inbox: --json is not valid JSON');
|
|
704
776
|
}
|
|
705
777
|
}
|
|
706
778
|
else {
|
|
@@ -709,43 +781,50 @@ export const inboxAction = async (ctx, name) => {
|
|
|
709
781
|
const from = typeof payload.from === 'string' ? payload.from.trim() : '';
|
|
710
782
|
const subject = typeof payload.subject === 'string' ? payload.subject : '';
|
|
711
783
|
const body = typeof payload.body === 'string' ? payload.body : '';
|
|
712
|
-
if (!from || !body)
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
784
|
+
if (!from || !body)
|
|
785
|
+
return fail('org inbox: payload requires "from" and "body" (via --json or --from/--body)');
|
|
786
|
+
if (!/^[^\s:]{1,128}(:[^\s:]{1,128})?$/.test(from))
|
|
787
|
+
return fail(`org inbox: invalid sender "${from}" — use "<org>:<role>" or "<role>"`);
|
|
788
|
+
const { lookupOrg, normalizeCredential, readOperatorCredential } = await import('../orgrt/broker.js');
|
|
789
|
+
const remote = lookupOrg(name);
|
|
790
|
+
const defPath = join(ctx.cwd, ORG_DIR, `${name}.json`);
|
|
791
|
+
if (!remote && !existsSync(defPath))
|
|
792
|
+
return fail(`Org not found: ${name}`);
|
|
716
793
|
// Target role: explicit --to, else the org's coordinator (reports_to == null),
|
|
717
794
|
// else the first role — matching where a role-less cross-org message should land.
|
|
718
795
|
let toRole = typeof ctx.flags.to === 'string' ? ctx.flags.to : '';
|
|
719
|
-
if (toRole && !/^[a-z0-9][a-z0-9_-]*$/i.test(toRole))
|
|
720
|
-
|
|
721
|
-
return { success: false, message: 'invalid role id' };
|
|
722
|
-
}
|
|
796
|
+
if (toRole && !/^[a-z0-9][a-z0-9_-]*$/i.test(toRole))
|
|
797
|
+
return fail(`Invalid role id: ${toRole}`);
|
|
723
798
|
if (!toRole) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
log(output.error(`Org not found: ${name}`));
|
|
727
|
-
return { success: false, message: 'org not found' };
|
|
728
|
-
}
|
|
799
|
+
if (!existsSync(defPath))
|
|
800
|
+
return fail(`Org "${name}" config is not in this project — pass --to <role>.`);
|
|
729
801
|
try {
|
|
730
802
|
const def = JSON.parse(readFileSync(defPath, 'utf8'));
|
|
731
803
|
const roles = Array.isArray(def.roles) ? def.roles : [];
|
|
732
804
|
toRole = roles.find((r) => r.reports_to == null)?.id ?? roles[0]?.id ?? '';
|
|
733
805
|
}
|
|
734
806
|
catch (err) {
|
|
735
|
-
|
|
736
|
-
return { success: false, message: 'org config unreadable' };
|
|
807
|
+
return fail(`Could not read org config for ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
737
808
|
}
|
|
738
|
-
if (!toRole)
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
809
|
+
if (!toRole)
|
|
810
|
+
return fail(`Org "${name}" has no roles to deliver to — pass --to <role>.`);
|
|
811
|
+
}
|
|
812
|
+
const { newMessageId, queueMessage } = await import('../orgrt/inbox.js');
|
|
813
|
+
const messageId = newMessageId();
|
|
814
|
+
const to = `${name}:${toRole}`;
|
|
815
|
+
const done = (delivery, receipt) => {
|
|
816
|
+
if (json)
|
|
817
|
+
return printOrgJson({ v: 1, org: name, to, from, delivery, receipt, messageId });
|
|
818
|
+
log(output.success(receipt));
|
|
819
|
+
return { success: true, message: receipt };
|
|
820
|
+
};
|
|
743
821
|
// Live path: a hosting daemon registered this org with the broker.
|
|
744
|
-
const { lookupOrg, normalizeCredential } = await import('../orgrt/broker.js');
|
|
745
|
-
const remote = lookupOrg(name);
|
|
746
822
|
if (remote) {
|
|
747
823
|
const [fromOrg, fromRole] = from.includes(':') ? from.split(':', 2) : ['external', from];
|
|
748
|
-
const
|
|
824
|
+
const operatorCred = readOperatorCredential(name);
|
|
825
|
+
const agentCred = normalizeCredential(remote.credential);
|
|
826
|
+
const cred = operatorCred ?? agentCred;
|
|
827
|
+
const fromCredential = operatorCred ? undefined : lookupOrg(fromOrg)?.credential;
|
|
749
828
|
try {
|
|
750
829
|
const res = await fetch(`${remote.url}/api/xdeliver`, {
|
|
751
830
|
method: 'POST',
|
|
@@ -753,36 +832,48 @@ export const inboxAction = async (ctx, name) => {
|
|
|
753
832
|
'Content-Type': 'application/json',
|
|
754
833
|
...(cred ? { 'x-monomind-cred': cred } : {}),
|
|
755
834
|
},
|
|
756
|
-
body: JSON.stringify({
|
|
835
|
+
body: JSON.stringify({
|
|
836
|
+
fromOrg,
|
|
837
|
+
fromRole,
|
|
838
|
+
...(fromCredential ? { fromCredential } : {}),
|
|
839
|
+
toOrg: name,
|
|
840
|
+
toRole,
|
|
841
|
+
subject,
|
|
842
|
+
body,
|
|
843
|
+
messageId,
|
|
844
|
+
}),
|
|
757
845
|
signal: AbortSignal.timeout(10_000),
|
|
758
846
|
});
|
|
759
847
|
const data = (await res.json().catch(() => ({})));
|
|
760
848
|
if (res.ok && data.ok) {
|
|
761
|
-
|
|
762
|
-
|
|
849
|
+
const receipt = data.receipt ?? `delivered to ${to}`;
|
|
850
|
+
// The daemon itself may have queued it (role starting, org waking).
|
|
851
|
+
return done(/^queued/.test(receipt) ? 'queued' : 'live', receipt);
|
|
763
852
|
}
|
|
764
|
-
|
|
853
|
+
note(output.warning(`Live delivery rejected (${data.error ?? res.status}) — falling back to offline queue.`));
|
|
765
854
|
}
|
|
766
855
|
catch (err) {
|
|
767
|
-
|
|
856
|
+
note(output.warning(`Hosting daemon unreachable (${err instanceof Error ? err.message : 'error'}) — falling back to offline queue.`));
|
|
768
857
|
}
|
|
769
858
|
}
|
|
770
859
|
// Offline path: spool; drained into the role's mailbox when the org next starts.
|
|
771
|
-
const { queueMessage } = await import('../orgrt/inbox.js');
|
|
772
860
|
const queued = queueMessage(ctx.cwd, name, {
|
|
773
861
|
fromQualified: from,
|
|
774
862
|
toRole,
|
|
775
863
|
subject,
|
|
776
864
|
body,
|
|
777
865
|
ts: Date.now(),
|
|
866
|
+
messageId,
|
|
778
867
|
});
|
|
779
868
|
if (!queued) {
|
|
780
|
-
|
|
869
|
+
const message = `Could not queue the message for ${to} (disk full or permissions).`;
|
|
870
|
+
if (json)
|
|
871
|
+
process.stdout.write(`${JSON.stringify({ v: 1, org: name, error: message })}\n`);
|
|
872
|
+
else
|
|
873
|
+
log(output.error(message));
|
|
781
874
|
return { success: false, message: 'queueing failed' };
|
|
782
875
|
}
|
|
783
|
-
|
|
784
|
-
log(output.success(receipt));
|
|
785
|
-
return { success: true, message: receipt };
|
|
876
|
+
return done('queued', `queued for ${to} (delivered when the org next runs)`);
|
|
786
877
|
};
|
|
787
878
|
/** `org create <name> --template <t> [--goal g] [--schedule s]` — scaffold a config from a template. */
|
|
788
879
|
export const createAction = async (ctx, name) => {
|
|
@@ -875,9 +966,13 @@ export const costsAction = async (ctx, name) => {
|
|
|
875
966
|
log(output.info(`Per-role cost breakdown for ${name} / ${run}:`));
|
|
876
967
|
// Combine data from runtime.json (live metrics) and summary (historical)
|
|
877
968
|
const roleData = new Map();
|
|
969
|
+
// M2: endpoint roles are automations — no row in the cost table.
|
|
970
|
+
const endpointIds = endpointRoleIds(ctx.cwd, name);
|
|
878
971
|
// Add live metrics from runtime.json
|
|
879
972
|
if (rt?.roleMetrics) {
|
|
880
973
|
for (const [roleId, metrics] of Object.entries(rt.roleMetrics)) {
|
|
974
|
+
if (endpointIds.has(roleId))
|
|
975
|
+
continue;
|
|
881
976
|
roleData.set(roleId, {
|
|
882
977
|
tokens: metrics.tokens,
|
|
883
978
|
costUsd: metrics.costUsd,
|
|
@@ -888,6 +983,8 @@ export const costsAction = async (ctx, name) => {
|
|
|
888
983
|
// Add historical data from summary if available
|
|
889
984
|
if (summary?.roles) {
|
|
890
985
|
for (const [roleId, roleStats] of Object.entries(summary.roles)) {
|
|
986
|
+
if (endpointIds.has(roleId))
|
|
987
|
+
continue;
|
|
891
988
|
const existing = roleData.get(roleId) ?? { tokens: 0, costUsd: 0, messages: 0 };
|
|
892
989
|
roleData.set(roleId, {
|
|
893
990
|
tokens: existing.tokens || roleStats.tokens,
|
|
@@ -963,7 +1060,11 @@ export const flowAction = async (ctx, name) => {
|
|
|
963
1060
|
}
|
|
964
1061
|
}
|
|
965
1062
|
}
|
|
966
|
-
|
|
1063
|
+
// M2: endpoint roles are automations — not listed as role nodes.
|
|
1064
|
+
const endpointIds = endpointRoleIds(ctx.cwd, name);
|
|
1065
|
+
const roles = Array.from(roleSet)
|
|
1066
|
+
.filter((r) => !endpointIds.has(r))
|
|
1067
|
+
.sort();
|
|
967
1068
|
// Protocol JSON mode (§7.2): structured roles + edges instead of Mermaid.
|
|
968
1069
|
if (orgJson(ctx))
|
|
969
1070
|
return printOrgJson({ v: 1, org: name, run, roles, edges: edgeObjects });
|
|
@@ -992,6 +1093,14 @@ export const flowAction = async (ctx, name) => {
|
|
|
992
1093
|
* the daemon is unreachable — mirrors answerAction's live-then-offline shape. */
|
|
993
1094
|
async function resolveApproval(ctx, name, role, action, approved) {
|
|
994
1095
|
const verb = approved ? 'approved' : 'denied';
|
|
1096
|
+
// M5: attribution and request scoping.
|
|
1097
|
+
const byFlag = await resolverFlag(ctx);
|
|
1098
|
+
if (!byFlag.ok)
|
|
1099
|
+
return { success: false, message: byFlag.message };
|
|
1100
|
+
const resolvedBy = byFlag.by;
|
|
1101
|
+
const requestId = typeof ctx.flags.request === 'string' && ctx.flags.request ? ctx.flags.request : undefined;
|
|
1102
|
+
if (ctx.flags.request !== undefined && !requestId)
|
|
1103
|
+
return { success: false, message: '--request needs an approval request id (apr-…)' };
|
|
995
1104
|
// SEC: approvals carry human authority — the operator credential, never the
|
|
996
1105
|
// broker entry's agent credential (which any agent subprocess can read).
|
|
997
1106
|
const { lookupOrg, readOperatorCredential } = await import('../orgrt/broker.js');
|
|
@@ -1005,7 +1114,14 @@ async function resolveApproval(ctx, name, role, action, approved) {
|
|
|
1005
1114
|
'Content-Type': 'application/json',
|
|
1006
1115
|
...(cred ? { 'x-monomind-cred': cred } : {}),
|
|
1007
1116
|
},
|
|
1008
|
-
body: JSON.stringify({
|
|
1117
|
+
body: JSON.stringify({
|
|
1118
|
+
org: name,
|
|
1119
|
+
role,
|
|
1120
|
+
action,
|
|
1121
|
+
approved,
|
|
1122
|
+
resolvedBy,
|
|
1123
|
+
...(requestId ? { requestId } : {}),
|
|
1124
|
+
}),
|
|
1009
1125
|
signal: AbortSignal.timeout(10_000),
|
|
1010
1126
|
});
|
|
1011
1127
|
const data = (await res.json().catch(() => ({})));
|
|
@@ -1018,10 +1134,12 @@ async function resolveApproval(ctx, name, role, action, approved) {
|
|
|
1018
1134
|
action,
|
|
1019
1135
|
approved,
|
|
1020
1136
|
delivery: 'live',
|
|
1137
|
+
resolvedBy,
|
|
1138
|
+
requestId: requestId ?? null,
|
|
1021
1139
|
});
|
|
1022
1140
|
log(approved
|
|
1023
|
-
? output.success(`Approved: ${role} may execute ${action} (live).`)
|
|
1024
|
-
: output.info(`Denied: ${role} may NOT execute ${action} (live).`));
|
|
1141
|
+
? output.success(`Approved: ${role} may execute ${action} (live, by ${resolvedBy}).`)
|
|
1142
|
+
: output.info(`Denied: ${role} may NOT execute ${action} (live, by ${resolvedBy}).`));
|
|
1025
1143
|
return { success: true, message: `${verb} ${action} for ${role}` };
|
|
1026
1144
|
}
|
|
1027
1145
|
log(output.warning(`Live delivery rejected (${data.error ?? res.status}) — falling back to offline queue.`));
|
|
@@ -1037,24 +1155,45 @@ async function resolveApproval(ctx, name, role, action, approved) {
|
|
|
1037
1155
|
}
|
|
1038
1156
|
const data = JSON.parse(readFileSync(approvalsPath, 'utf8'));
|
|
1039
1157
|
const pending = data.approvals ?? [];
|
|
1040
|
-
|
|
1041
|
-
|
|
1158
|
+
// Same selection as the daemon's setApproval: the one request named by
|
|
1159
|
+
// --request, else every still-pending entry for the (role, action) pair.
|
|
1160
|
+
const items = pending.filter((a) => a.roleId === role &&
|
|
1161
|
+
a.action === action &&
|
|
1162
|
+
a.approved === null &&
|
|
1163
|
+
(requestId === undefined || a.requestId === requestId));
|
|
1164
|
+
if (items.length === 0) {
|
|
1042
1165
|
return {
|
|
1043
1166
|
success: false,
|
|
1044
|
-
message:
|
|
1167
|
+
message: requestId
|
|
1168
|
+
? `no pending approval ${requestId} found for role ${role} action ${action}`
|
|
1169
|
+
: `no pending approval found for role ${role} action ${action}`,
|
|
1045
1170
|
};
|
|
1046
1171
|
}
|
|
1047
|
-
|
|
1048
|
-
item
|
|
1172
|
+
const now = Date.now();
|
|
1173
|
+
for (const item of items) {
|
|
1174
|
+
item.approved = approved;
|
|
1175
|
+
item.ts = now;
|
|
1176
|
+
item.resolvedBy = resolvedBy;
|
|
1177
|
+
item.resolvedAt = now;
|
|
1178
|
+
}
|
|
1049
1179
|
writeFileSync(approvalsPath, JSON.stringify({ approvals: pending }, null, 2));
|
|
1050
1180
|
if (orgJson(ctx))
|
|
1051
|
-
return printOrgJson({
|
|
1181
|
+
return printOrgJson({
|
|
1182
|
+
v: 1,
|
|
1183
|
+
org: name,
|
|
1184
|
+
role,
|
|
1185
|
+
action,
|
|
1186
|
+
approved,
|
|
1187
|
+
delivery: 'recorded',
|
|
1188
|
+
resolvedBy,
|
|
1189
|
+
requestId: requestId ?? null,
|
|
1190
|
+
});
|
|
1052
1191
|
log(approved
|
|
1053
|
-
? output.success(`Approved: ${role} may execute ${action}`)
|
|
1054
|
-
: output.info(`Denied: ${role} may NOT execute ${action}`));
|
|
1192
|
+
? output.success(`Approved: ${role} may execute ${action} (by ${resolvedBy})`)
|
|
1193
|
+
: output.info(`Denied: ${role} may NOT execute ${action} (by ${resolvedBy})`));
|
|
1055
1194
|
return { success: true, message: `${verb} ${action} for ${role}` };
|
|
1056
1195
|
}
|
|
1057
|
-
/** `org approve <org> <role> <action
|
|
1196
|
+
/** `org approve <org> <role> <action> [--request <id>] [--by <resolver>]` — approve a pending tool/action approval */
|
|
1058
1197
|
export const approveAction = async (ctx, name) => {
|
|
1059
1198
|
const role = ctx.args[1];
|
|
1060
1199
|
const action = ctx.args[2];
|
|
@@ -1063,7 +1202,7 @@ export const approveAction = async (ctx, name) => {
|
|
|
1063
1202
|
}
|
|
1064
1203
|
return resolveApproval(ctx, name, role, action, true);
|
|
1065
1204
|
};
|
|
1066
|
-
/** `org deny <org> <role> <action
|
|
1205
|
+
/** `org deny <org> <role> <action> [--request <id>] [--by <resolver>]` — deny a pending tool/action approval */
|
|
1067
1206
|
export const denyAction = async (ctx, name) => {
|
|
1068
1207
|
const role = ctx.args[1];
|
|
1069
1208
|
const action = ctx.args[2];
|
|
@@ -1315,6 +1454,10 @@ export const gateResolveAction = async (ctx, name, approved) => {
|
|
|
1315
1454
|
return { success: false, message: `gate "${gateId}" not found for org "${name}"` };
|
|
1316
1455
|
if (gate.status !== 'pending')
|
|
1317
1456
|
return { success: false, message: `gate "${gateId}" already resolved (${gate.status})` };
|
|
1457
|
+
const byFlag = await resolverFlag(ctx);
|
|
1458
|
+
if (!byFlag.ok)
|
|
1459
|
+
return { success: false, message: byFlag.message };
|
|
1460
|
+
const resolvedBy = byFlag.by;
|
|
1318
1461
|
// SEC: gate resolution is a human decision — operator credential only.
|
|
1319
1462
|
const { lookupOrg, readOperatorCredential } = await import('../orgrt/broker.js');
|
|
1320
1463
|
const remote = lookupOrg(name);
|
|
@@ -1327,7 +1470,7 @@ export const gateResolveAction = async (ctx, name, approved) => {
|
|
|
1327
1470
|
'Content-Type': 'application/json',
|
|
1328
1471
|
...(cred ? { 'x-monomind-cred': cred } : {}),
|
|
1329
1472
|
},
|
|
1330
|
-
body: JSON.stringify({ org: name, gateId, approved, resolution }),
|
|
1473
|
+
body: JSON.stringify({ org: name, gateId, approved, resolution, resolvedBy }),
|
|
1331
1474
|
signal: AbortSignal.timeout(10_000),
|
|
1332
1475
|
});
|
|
1333
1476
|
const d = (await res.json().catch(() => ({})));
|
|
@@ -1339,6 +1482,7 @@ export const gateResolveAction = async (ctx, name, approved) => {
|
|
|
1339
1482
|
gate_id: gateId,
|
|
1340
1483
|
status: approved ? 'approved' : 'rejected',
|
|
1341
1484
|
delivery: 'live',
|
|
1485
|
+
resolvedBy,
|
|
1342
1486
|
});
|
|
1343
1487
|
log(output.success(`Gate ${gateId} ${approved ? 'approved' : 'rejected'} (live).`));
|
|
1344
1488
|
return { success: true, message: `gate ${approved ? 'approved' : 'rejected'}` };
|
|
@@ -1377,7 +1521,7 @@ export const gateResolveAction = async (ctx, name, approved) => {
|
|
|
1377
1521
|
...fresh.gates[idx],
|
|
1378
1522
|
status: approved ? 'approved' : 'rejected',
|
|
1379
1523
|
resolvedAt: Date.now(),
|
|
1380
|
-
resolvedBy
|
|
1524
|
+
resolvedBy,
|
|
1381
1525
|
resolution,
|
|
1382
1526
|
};
|
|
1383
1527
|
writeGates(ctx.cwd, name, fresh);
|
|
@@ -1388,6 +1532,7 @@ export const gateResolveAction = async (ctx, name, approved) => {
|
|
|
1388
1532
|
gate_id: gateId,
|
|
1389
1533
|
status: approved ? 'approved' : 'rejected',
|
|
1390
1534
|
delivery: 'recorded',
|
|
1535
|
+
resolvedBy,
|
|
1391
1536
|
});
|
|
1392
1537
|
log(output.success(`Gate ${gateId} ${approved ? 'approved' : 'rejected'} — ${name} picks it up on its next cycle.`));
|
|
1393
1538
|
return { success: true, message: `gate ${approved ? 'approved' : 'rejected'} (queued)` };
|