@monoes/monomindcli 2.12.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/mastermind/SKILL.md +1 -15
- package/README.md +1 -1
- package/dist/src/commands/doctor-mcp-probe.d.ts +48 -0
- package/dist/src/commands/doctor-mcp-probe.d.ts.map +1 -0
- package/dist/src/commands/doctor-mcp-probe.js +205 -0
- package/dist/src/commands/doctor-mcp-probe.js.map +1 -0
- package/dist/src/commands/doctor-monoes-checks.d.ts +1 -0
- package/dist/src/commands/doctor-monoes-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-monoes-checks.js +96 -34
- package/dist/src/commands/doctor-monoes-checks.js.map +1 -1
- package/dist/src/commands/doctor-project-checks.d.ts +31 -1
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +109 -4
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +21 -3
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +29 -0
- 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 +4 -3
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/org-observe.d.ts.map +1 -1
- package/dist/src/commands/org-observe.js +14 -0
- 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 +21 -4
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/ui.d.ts.map +1 -1
- package/dist/src/commands/ui.js +4 -0
- package/dist/src/commands/ui.js.map +1 -1
- package/dist/src/init/claudemd-generator.d.ts.map +1 -1
- package/dist/src/init/claudemd-generator.js +2 -1
- package/dist/src/init/claudemd-generator.js.map +1 -1
- package/dist/src/init/copy-assets.d.ts.map +1 -1
- package/dist/src/init/copy-assets.js +56 -18
- package/dist/src/init/copy-assets.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +9 -1
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/mcp-generator.d.ts.map +1 -1
- package/dist/src/init/mcp-generator.js +3 -3
- package/dist/src/init/mcp-generator.js.map +1 -1
- package/dist/src/init/never-commit.d.ts +39 -0
- package/dist/src/init/never-commit.d.ts.map +1 -0
- package/dist/src/init/never-commit.js +15 -0
- package/dist/src/init/never-commit.js.map +1 -0
- package/dist/src/init/shared.d.ts +74 -1
- package/dist/src/init/shared.d.ts.map +1 -1
- package/dist/src/init/shared.js +166 -0
- package/dist/src/init/shared.js.map +1 -1
- package/dist/src/init/types.d.ts +11 -0
- package/dist/src/init/types.d.ts.map +1 -1
- package/dist/src/init/types.js.map +1 -1
- package/dist/src/init/upgrade.d.ts.map +1 -1
- package/dist/src/init/upgrade.js +1 -0
- package/dist/src/init/upgrade.js.map +1 -1
- package/dist/src/init/write-kimicode.d.ts.map +1 -1
- package/dist/src/init/write-kimicode.js +13 -9
- package/dist/src/init/write-kimicode.js.map +1 -1
- package/dist/src/init/write-opencode.d.ts.map +1 -1
- package/dist/src/init/write-opencode.js +44 -2
- package/dist/src/init/write-opencode.js.map +1 -1
- package/dist/src/init/write-runtime-config.d.ts +2 -0
- package/dist/src/init/write-runtime-config.d.ts.map +1 -1
- package/dist/src/init/write-runtime-config.js +65 -38
- package/dist/src/init/write-runtime-config.js.map +1 -1
- package/dist/src/mastermind/manifest-data.d.ts.map +1 -1
- package/dist/src/mastermind/manifest-data.js +14 -0
- package/dist/src/mastermind/manifest-data.js.map +1 -1
- package/dist/src/mcp/monoes-mcp-entry.mjs +86 -0
- package/dist/src/mcp-tools/terminal-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.js +52 -9
- package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts +84 -3
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +215 -17
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/orgrt/agent-runner.d.ts +38 -0
- package/dist/src/orgrt/agent-runner.d.ts.map +1 -1
- package/dist/src/orgrt/agent-runner.js +47 -0
- package/dist/src/orgrt/agent-runner.js.map +1 -1
- package/dist/src/orgrt/checkpoint.d.ts +12 -1
- package/dist/src/orgrt/checkpoint.d.ts.map +1 -1
- package/dist/src/orgrt/checkpoint.js +15 -6
- package/dist/src/orgrt/checkpoint.js.map +1 -1
- package/dist/src/orgrt/completion-gate.d.ts +74 -0
- package/dist/src/orgrt/completion-gate.d.ts.map +1 -1
- package/dist/src/orgrt/completion-gate.js +45 -0
- package/dist/src/orgrt/completion-gate.js.map +1 -1
- package/dist/src/orgrt/cost-tier.d.ts +144 -0
- package/dist/src/orgrt/cost-tier.d.ts.map +1 -0
- package/dist/src/orgrt/cost-tier.js +183 -0
- package/dist/src/orgrt/cost-tier.js.map +1 -0
- package/dist/src/orgrt/cross-org.d.ts.map +1 -1
- package/dist/src/orgrt/cross-org.js +14 -0
- package/dist/src/orgrt/cross-org.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +12 -1
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +216 -49
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/decisions.d.ts +14 -2
- package/dist/src/orgrt/decisions.d.ts.map +1 -1
- package/dist/src/orgrt/decisions.js +216 -10
- package/dist/src/orgrt/decisions.js.map +1 -1
- package/dist/src/orgrt/file-roots.d.ts +7 -0
- package/dist/src/orgrt/file-roots.d.ts.map +1 -1
- package/dist/src/orgrt/file-roots.js +38 -1
- package/dist/src/orgrt/file-roots.js.map +1 -1
- package/dist/src/orgrt/forwarder.d.ts.map +1 -1
- package/dist/src/orgrt/forwarder.js +16 -2
- package/dist/src/orgrt/forwarder.js.map +1 -1
- package/dist/src/orgrt/idle-deadline.d.ts +74 -1
- package/dist/src/orgrt/idle-deadline.d.ts.map +1 -1
- package/dist/src/orgrt/idle-deadline.js +52 -2
- package/dist/src/orgrt/idle-deadline.js.map +1 -1
- package/dist/src/orgrt/loadouts.d.ts +38 -0
- package/dist/src/orgrt/loadouts.d.ts.map +1 -0
- package/dist/src/orgrt/loadouts.js +129 -0
- package/dist/src/orgrt/loadouts.js.map +1 -0
- package/dist/src/orgrt/mailbox.d.ts +17 -1
- package/dist/src/orgrt/mailbox.d.ts.map +1 -1
- package/dist/src/orgrt/mailbox.js +50 -3
- package/dist/src/orgrt/mailbox.js.map +1 -1
- package/dist/src/orgrt/policy.d.ts +42 -2
- package/dist/src/orgrt/policy.d.ts.map +1 -1
- package/dist/src/orgrt/policy.js +53 -8
- package/dist/src/orgrt/policy.js.map +1 -1
- package/dist/src/orgrt/prompt-vars.d.ts +11 -0
- package/dist/src/orgrt/prompt-vars.d.ts.map +1 -0
- package/dist/src/orgrt/prompt-vars.js +49 -0
- package/dist/src/orgrt/prompt-vars.js.map +1 -0
- package/dist/src/orgrt/questions.d.ts +22 -1
- package/dist/src/orgrt/questions.d.ts.map +1 -1
- package/dist/src/orgrt/questions.js +24 -3
- package/dist/src/orgrt/questions.js.map +1 -1
- package/dist/src/orgrt/review-packet.d.ts +29 -0
- package/dist/src/orgrt/review-packet.d.ts.map +1 -0
- package/dist/src/orgrt/review-packet.js +67 -0
- package/dist/src/orgrt/review-packet.js.map +1 -0
- package/dist/src/orgrt/role-sandbox.d.ts.map +1 -1
- package/dist/src/orgrt/role-sandbox.js +16 -3
- package/dist/src/orgrt/role-sandbox.js.map +1 -1
- package/dist/src/orgrt/session-ledger.d.ts +61 -0
- package/dist/src/orgrt/session-ledger.d.ts.map +1 -0
- package/dist/src/orgrt/session-ledger.js +113 -0
- package/dist/src/orgrt/session-ledger.js.map +1 -0
- package/dist/src/orgrt/session.d.ts +45 -5
- package/dist/src/orgrt/session.d.ts.map +1 -1
- package/dist/src/orgrt/session.js +384 -27
- package/dist/src/orgrt/session.js.map +1 -1
- package/dist/src/orgrt/task-dag.d.ts +34 -1
- package/dist/src/orgrt/task-dag.d.ts.map +1 -1
- package/dist/src/orgrt/task-dag.js +58 -7
- package/dist/src/orgrt/task-dag.js.map +1 -1
- package/dist/src/orgrt/tool-spill.d.ts +94 -0
- package/dist/src/orgrt/tool-spill.d.ts.map +1 -0
- package/dist/src/orgrt/tool-spill.js +180 -0
- package/dist/src/orgrt/tool-spill.js.map +1 -0
- package/dist/src/orgrt/types.d.ts +138 -0
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +148 -0
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/platform-adapters/renderers/mcp.d.ts +20 -2
- package/dist/src/platform-adapters/renderers/mcp.d.ts.map +1 -1
- package/dist/src/platform-adapters/renderers/mcp.js +25 -5
- package/dist/src/platform-adapters/renderers/mcp.js.map +1 -1
- package/dist/src/platform-adapters/renderers/skills.d.ts +0 -6
- package/dist/src/platform-adapters/renderers/skills.d.ts.map +1 -1
- package/dist/src/platform-adapters/renderers/skills.js +12 -22
- package/dist/src/platform-adapters/renderers/skills.js.map +1 -1
- package/dist/src/protocol-capabilities.d.ts +2 -1
- package/dist/src/protocol-capabilities.d.ts.map +1 -1
- package/dist/src/protocol-capabilities.js +2 -1
- package/dist/src/protocol-capabilities.js.map +1 -1
- package/dist/src/ui/dashboard.html +457 -164
- package/dist/src/ui/org-hil.mjs +276 -0
- package/dist/src/ui/org-runtime.mjs +350 -0
- package/dist/src/ui/orgs.html +22 -4
- package/dist/src/ui/routes-monoes.mjs +28 -3
- package/dist/src/ui/routes-org.mjs +290 -541
- package/dist/src/ui/server.mjs +185 -19
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
package/dist/src/orgrt/daemon.js
CHANGED
|
@@ -25,9 +25,10 @@ import { fileToolRoots } from './file-roots.js';
|
|
|
25
25
|
import { attachForwarder } from './forwarder.js';
|
|
26
26
|
import { GrokAgentRunner } from './grok-runner.js';
|
|
27
27
|
import { HermesAgentRunner } from './hermes-runner.js';
|
|
28
|
-
import { clearIdleRecord, projectIdleStop, writeIdleRecord, } from './idle-deadline.js';
|
|
28
|
+
import { advanceHold, clearIdleRecord, noProgressRoles, projectIdleStop, writeIdleRecord, } from './idle-deadline.js';
|
|
29
29
|
import { drainInbox, newMessageId, queueMessage } from './inbox.js';
|
|
30
30
|
import { KimiCodeAgentRunner } from './kimicode-runner.js';
|
|
31
|
+
import { loadoutCatalog, resolveLoadout, sessionLoadoutFor, taskTag } from './loadouts.js';
|
|
31
32
|
import { isRecoverableCloseReason, Mailbox } from './mailbox.js';
|
|
32
33
|
import { OpencodeAgentRunner } from './opencode-runner.js';
|
|
33
34
|
import * as orgMemory from './org-memory.js';
|
|
@@ -43,6 +44,7 @@ import { buildRespawnReceipt, computeReplacementBudget, mergeEffectiveRoleConfig
|
|
|
43
44
|
import { buildRuntimeOptions } from './runtime-options.js';
|
|
44
45
|
import * as scheduler from './scheduler-integration.js';
|
|
45
46
|
import { runAgentSession } from './session.js';
|
|
47
|
+
import { SessionLedger } from './session-ledger.js';
|
|
46
48
|
import { TaskDag } from './task-dag.js';
|
|
47
49
|
import { freshChainId, roleProviderPrefixes, ToolProviderHub, } from './tool-providers.js';
|
|
48
50
|
import { ORG_DIR, OrgDefSchema, } from './types.js';
|
|
@@ -624,6 +626,22 @@ export class OrgDaemon {
|
|
|
624
626
|
getResourceLimits().maxSdkProcesses < sessionRoleCount) {
|
|
625
627
|
configureResourceLimits({ maxSdkProcesses: sessionRoleCount });
|
|
626
628
|
}
|
|
629
|
+
// ADR-O001 D8: a cost tier that can't resolve a model for a role's
|
|
630
|
+
// provider must stop the run here. The alternative — resolving it at
|
|
631
|
+
// session start — would either crash one role ten minutes in or, worse,
|
|
632
|
+
// quietly leave that role on a different model than the tier claimed.
|
|
633
|
+
const { validateCostTiers } = await import('./cost-tier.js');
|
|
634
|
+
const tierErrors = validateCostTiers(def);
|
|
635
|
+
if (tierErrors.length) {
|
|
636
|
+
throw new Error(`org ${name}: ${tierErrors.join('; ')}`);
|
|
637
|
+
}
|
|
638
|
+
// ADR-O001 D7: an oversized or unresolvable loadout catalog stops the run
|
|
639
|
+
// here, for the same reason — not ten minutes in, at some role's spawn.
|
|
640
|
+
const { validateLoadouts } = await import('./loadouts.js');
|
|
641
|
+
const loadoutErrors = validateLoadouts(def, this.root).errors;
|
|
642
|
+
if (loadoutErrors.length) {
|
|
643
|
+
throw new Error(`org ${name}: ${loadoutErrors.join('; ')}`);
|
|
644
|
+
}
|
|
627
645
|
// Validate per-role providers before spawning anything (fail-fast: a
|
|
628
646
|
// missing env var discovered 10 minutes into a run wastes the entire run).
|
|
629
647
|
const { resolveProviderEnv: validateProvider, resolveRoleProvider } = await import('./provider.js');
|
|
@@ -674,6 +692,13 @@ export class OrgDaemon {
|
|
|
674
692
|
// boss that's genuinely out of ideas can loop forever making zero
|
|
675
693
|
// progress without ever tripping the watchdog.
|
|
676
694
|
let lastToolActivity = 0;
|
|
695
|
+
// ADR-O001 D4 (no-progress detector): the idle clock above is org-wide and
|
|
696
|
+
// says nothing at all while a hold is in force — the 8.3-hour stall looked
|
|
697
|
+
// perfectly healthy from it. Per-role last-activity is what tells a role
|
|
698
|
+
// that is nominally working but producing nothing from one that is simply
|
|
699
|
+
// waiting its turn.
|
|
700
|
+
const roleActivity = new Map();
|
|
701
|
+
const noProgressAlarmed = new Set();
|
|
677
702
|
// Org-wide budget ceiling (bug 1): tracks whether run_config.budget_tokens
|
|
678
703
|
// has already been enforced this run, so the close-all-mailboxes sweep
|
|
679
704
|
// below only fires once instead of on every subsequent usage event.
|
|
@@ -683,12 +708,18 @@ export class OrgDaemon {
|
|
|
683
708
|
collected.push(slim);
|
|
684
709
|
if (collected.length > MAX_COLLECTED)
|
|
685
710
|
collected.splice(0, collected.length - MAX_COLLECTED);
|
|
686
|
-
// The watchdog's own
|
|
687
|
-
//
|
|
688
|
-
|
|
711
|
+
// The watchdog's own events must not count as org activity, or a hung
|
|
712
|
+
// boss would never trip the "nudge produced no activity" stop and a
|
|
713
|
+
// silent role would clear its own no-progress alarm.
|
|
714
|
+
const selfEmitted = e.reason === 'idle-nudge' || e.reason === 'no-progress' || e.reason === 'hold-expired';
|
|
715
|
+
if (!selfEmitted)
|
|
689
716
|
lastActivity = Date.now();
|
|
690
717
|
if (e.type === 'tool')
|
|
691
718
|
lastToolActivity = Date.now();
|
|
719
|
+
if (e.from && !selfEmitted) {
|
|
720
|
+
roleActivity.set(e.from, Date.now());
|
|
721
|
+
noProgressAlarmed.delete(e.from);
|
|
722
|
+
}
|
|
692
723
|
// org_complete IS the end of the run — self-stop instead of sitting
|
|
693
724
|
// "running" forever after a recorded outcome. Deferred (unref'd) so the
|
|
694
725
|
// tool call's receipt reaches the boss and its final turn text still
|
|
@@ -727,8 +758,11 @@ export class OrgDaemon {
|
|
|
727
758
|
const orgBudget = def.run_config.budget_tokens;
|
|
728
759
|
if (orgBudget != null) {
|
|
729
760
|
let orgUsage = 0;
|
|
761
|
+
// ADR-O001 D1: budgetedUsage, not usage — the org-wide ceiling is
|
|
762
|
+
// the same declared number as the per-role one and must be compared
|
|
763
|
+
// on the same basis.
|
|
730
764
|
for (const rt of running.agents.values())
|
|
731
|
-
orgUsage += rt.policy.
|
|
765
|
+
orgUsage += rt.policy.budgetedUsage;
|
|
732
766
|
// Mid-run role replacement retires a policy engine's usage into the
|
|
733
767
|
// slot instead of discarding it (see role-slot.ts / respawnRole) -
|
|
734
768
|
// include it here or a replacement could silently reset spend and
|
|
@@ -773,6 +807,7 @@ export class OrgDaemon {
|
|
|
773
807
|
const running = {
|
|
774
808
|
def,
|
|
775
809
|
run,
|
|
810
|
+
sessionLedger: new SessionLedger(join(dir, 'sessions.json')),
|
|
776
811
|
bus,
|
|
777
812
|
agents: new Map(),
|
|
778
813
|
roleSlots: new Map(),
|
|
@@ -1075,7 +1110,7 @@ export class OrgDaemon {
|
|
|
1075
1110
|
idleMs,
|
|
1076
1111
|
bossReachable: bossRt?.status === 'running' && !bossRt.mailbox.isClosed,
|
|
1077
1112
|
})).toISOString();
|
|
1078
|
-
const key = `${at}|${hold}`;
|
|
1113
|
+
const key = `${at}|${hold?.reason ?? null}|${hold?.until ?? null}`;
|
|
1079
1114
|
if (key === published)
|
|
1080
1115
|
return;
|
|
1081
1116
|
try {
|
|
@@ -1091,7 +1126,32 @@ export class OrgDaemon {
|
|
|
1091
1126
|
console.error(`org ${name}: could not write the idle deadline:`, err instanceof Error ? err.message : err);
|
|
1092
1127
|
}
|
|
1093
1128
|
};
|
|
1094
|
-
//
|
|
1129
|
+
// ADR-O001 D4 — Gas Town's "30 minutes hooked without progress" alarm.
|
|
1130
|
+
// The org-wide idle clock is silent while a hold is in force, so a role
|
|
1131
|
+
// that is nominally running and producing nothing gets its own, loud
|
|
1132
|
+
// audit event. Once per spell: the bus subscriber clears the flag as
|
|
1133
|
+
// soon as the role emits anything.
|
|
1134
|
+
const alarmNoProgress = (now) => {
|
|
1135
|
+
const stalled = noProgressRoles([...running.agents].map(([id, rt]) => ({
|
|
1136
|
+
id,
|
|
1137
|
+
working: rt.status === 'running' && !rt.mailbox.isClosed,
|
|
1138
|
+
lastActivity: roleActivity.get(id) ?? lastActivity,
|
|
1139
|
+
alarmed: noProgressAlarmed.has(id),
|
|
1140
|
+
})), now);
|
|
1141
|
+
for (const role of stalled) {
|
|
1142
|
+
noProgressAlarmed.add(role.id);
|
|
1143
|
+
bus.emit({
|
|
1144
|
+
type: 'audit',
|
|
1145
|
+
from: role.id,
|
|
1146
|
+
reason: 'no-progress',
|
|
1147
|
+
msg: `role "${role.id}" has been running for ${Math.round(role.silentMs / 60_000)}m ` +
|
|
1148
|
+
`without a single bus event — it is hooked but producing nothing`,
|
|
1149
|
+
data: { role: role.id, silentMinutes: Math.round(role.silentMs / 60_000) },
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
// The legitimate waits the watchdog holds through — every one of them
|
|
1154
|
+
// with a deadline attached by advanceHold (ADR-O001 D4).
|
|
1095
1155
|
const holdReason = () => {
|
|
1096
1156
|
if (this.restarting.has(name))
|
|
1097
1157
|
return 'restarting'; // boss auto-restart in flight
|
|
@@ -1106,10 +1166,12 @@ export class OrgDaemon {
|
|
|
1106
1166
|
// agent. Without this check the watchdog nudges (and, after enough
|
|
1107
1167
|
// nudges, idle-stops) an org that's simply waiting on a human answer
|
|
1108
1168
|
// that's already on its way.
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1169
|
+
//
|
|
1170
|
+
// ADR-O001 D4: only a question the asker declared BLOCKING counts. The
|
|
1171
|
+
// 8.3-hour stall was held open by a question whose own text opened
|
|
1172
|
+
// with "no answer needed for the run to continue; I am not blocking on
|
|
1173
|
+
// this" — it suppressed the watchdog exactly like a real blocker.
|
|
1174
|
+
if (questionOps.pendingBlockingQuestions(this.root, name).length > 0)
|
|
1113
1175
|
return 'pending-question';
|
|
1114
1176
|
// M1 (C-41): a pending tool approval is the same kind of legitimate
|
|
1115
1177
|
// wait — the role was told to wait for `org approve/deny`.
|
|
@@ -1118,23 +1180,28 @@ export class OrgDaemon {
|
|
|
1118
1180
|
// M2 (C-41): a delivered endpoint message whose reply is still due.
|
|
1119
1181
|
if (hasActiveEndpointWait(running))
|
|
1120
1182
|
return 'endpoint-reply-due';
|
|
1183
|
+
// A task blocked on a real-world time still in the future is
|
|
1184
|
+
// legitimate waiting, same as a pending gate — don't nudge about it.
|
|
1185
|
+
// Its deadline is the time the asker actually named, not the default
|
|
1186
|
+
// hold TTL, so a block set hours out is honoured exactly.
|
|
1187
|
+
const blockedUntil = running.taskDag?.activeBlockUntil(Date.now()) ?? null;
|
|
1188
|
+
if (blockedUntil !== null)
|
|
1189
|
+
return { reason: 'task-blocked', until: blockedUntil };
|
|
1121
1190
|
return null;
|
|
1122
1191
|
};
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
// unblocked task should reset the idle clock, not just silently
|
|
1132
|
-
// update state nobody notices until the next nudge.
|
|
1192
|
+
// Auto-resume any task whose org_task_block time has passed: flip it
|
|
1193
|
+
// back to 'running' and re-push it into the assignee's mailbox, same as
|
|
1194
|
+
// a fresh dispatch. This IS real activity, so it feeds the normal
|
|
1195
|
+
// idleFor check rather than short-circuiting it — an unblocked task
|
|
1196
|
+
// should reset the idle clock, not just silently update state nobody
|
|
1197
|
+
// notices until the next nudge. Runs on every tick, a hold in force
|
|
1198
|
+
// included: a block that expires mid-wait is real work again.
|
|
1199
|
+
const resumeExpiredBlocks = () => {
|
|
1133
1200
|
const unblocked = running.taskDag?.unblockExpired(Date.now()) ?? [];
|
|
1134
1201
|
for (const task of unblocked) {
|
|
1135
1202
|
const agent = running.agents.get(task.assignee);
|
|
1136
1203
|
if (agent && !agent.mailbox.isClosed) {
|
|
1137
|
-
agent.mailbox.push(
|
|
1204
|
+
agent.mailbox.push(`${taskTag(task)} Block expired — resuming: ${task.title}`);
|
|
1138
1205
|
}
|
|
1139
1206
|
bus.emit({
|
|
1140
1207
|
type: 'status',
|
|
@@ -1144,20 +1211,20 @@ export class OrgDaemon {
|
|
|
1144
1211
|
data: { taskId: task.id, assignee: task.assignee },
|
|
1145
1212
|
});
|
|
1146
1213
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1214
|
+
};
|
|
1215
|
+
// The normal idle path: nudge the boss, then stop if the nudge produced
|
|
1216
|
+
// nothing. Runs only when nothing (still) holds the watchdog.
|
|
1217
|
+
const check = () => {
|
|
1151
1218
|
const idleFor = Date.now() - lastActivity;
|
|
1152
1219
|
if (idleFor < idleMs) {
|
|
1153
1220
|
nudges = resolvedIdleNudgeCount(nudgedAt, nudges, lastToolActivity);
|
|
1154
1221
|
nudgedAt = 0;
|
|
1155
|
-
return
|
|
1222
|
+
return;
|
|
1156
1223
|
}
|
|
1157
1224
|
if (nudgedAt === 0) {
|
|
1158
1225
|
if (nudges >= MAX_IDLE_NUDGES) {
|
|
1159
1226
|
idleStop(`org idle again after ${nudges} nudges — stopping run`);
|
|
1160
|
-
return
|
|
1227
|
+
return;
|
|
1161
1228
|
}
|
|
1162
1229
|
const bossRt = running.agents.get(bossRole.id);
|
|
1163
1230
|
// #205: a budget-exhausted boss closed its own mailbox on
|
|
@@ -1170,11 +1237,11 @@ export class OrgDaemon {
|
|
|
1170
1237
|
idleStop(`org idle for ${Math.round(idleFor / 60_000)}m and boss "${bossRole.id}" is over its ` +
|
|
1171
1238
|
`${budgetReason === 'token-budget' ? 'token' : 'USD'} budget — raise the role's ` +
|
|
1172
1239
|
`${budgetReason === 'token-budget' ? 'budget_tokens' : 'budget_usd'} (or run_config's) and resume from checkpoint — stopping run`);
|
|
1173
|
-
return
|
|
1240
|
+
return;
|
|
1174
1241
|
}
|
|
1175
1242
|
if (bossRt?.status !== 'running' || bossRt.mailbox.isClosed) {
|
|
1176
1243
|
idleStop(`org idle for ${Math.round(idleFor / 60_000)}m and boss "${bossRole.id}" is unreachable — stopping run`);
|
|
1177
|
-
return
|
|
1244
|
+
return;
|
|
1178
1245
|
}
|
|
1179
1246
|
nudges++;
|
|
1180
1247
|
nudgedAt = Date.now();
|
|
@@ -1193,10 +1260,35 @@ export class OrgDaemon {
|
|
|
1193
1260
|
else if (Date.now() - nudgedAt >= idleMs) {
|
|
1194
1261
|
idleStop(`nudge produced no activity for another ${Math.round(idleMs / 60_000)}m — boss appears hung, stopping run`);
|
|
1195
1262
|
}
|
|
1263
|
+
};
|
|
1264
|
+
// One watchdog tick. The hold is resolved FIRST and with a deadline, so
|
|
1265
|
+
// a wait that outlives its deadline hands the run back to the idle path
|
|
1266
|
+
// instead of suppressing it forever (ADR-O001 D4).
|
|
1267
|
+
let holdTrack = null;
|
|
1268
|
+
const tick = () => {
|
|
1269
|
+
const now = Date.now();
|
|
1270
|
+
alarmNoProgress(now);
|
|
1271
|
+
resumeExpiredBlocks();
|
|
1272
|
+
const step = advanceHold(holdTrack, holdReason(), now);
|
|
1273
|
+
holdTrack = step.track;
|
|
1274
|
+
if (step.expired) {
|
|
1275
|
+
bus.emit({
|
|
1276
|
+
type: 'audit',
|
|
1277
|
+
reason: 'hold-expired',
|
|
1278
|
+
msg: `the "${step.expired}" hold on org "${name}" outlived its deadline — the idle ` +
|
|
1279
|
+
`watchdog is running again and will nudge, then stop the run if nothing happens`,
|
|
1280
|
+
data: { hold: step.expired },
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
if (step.hold)
|
|
1284
|
+
return step.hold;
|
|
1285
|
+
check();
|
|
1196
1286
|
return null;
|
|
1197
1287
|
};
|
|
1198
|
-
|
|
1199
|
-
|
|
1288
|
+
const initialHold = advanceHold(null, holdReason(), Date.now());
|
|
1289
|
+
holdTrack = initialHold.track;
|
|
1290
|
+
publishDeadline(initialHold.hold);
|
|
1291
|
+
const wd = setInterval(() => publishDeadline(tick()), Math.max(200, Math.min(idleMs / 2, 30_000)));
|
|
1200
1292
|
wd.unref?.();
|
|
1201
1293
|
this.watchdogs.set(name, wd);
|
|
1202
1294
|
}
|
|
@@ -1206,7 +1298,7 @@ export class OrgDaemon {
|
|
|
1206
1298
|
run,
|
|
1207
1299
|
idle_minutes: 0,
|
|
1208
1300
|
idle_stop_at: null,
|
|
1209
|
-
hold: 'disabled',
|
|
1301
|
+
hold: { reason: 'disabled', until: null },
|
|
1210
1302
|
});
|
|
1211
1303
|
}
|
|
1212
1304
|
catch (err) {
|
|
@@ -1344,6 +1436,11 @@ export class OrgDaemon {
|
|
|
1344
1436
|
}
|
|
1345
1437
|
const policy = new PolicyEngine(role.id, {
|
|
1346
1438
|
maxTokens: role.budget_tokens ?? perRoleBudget,
|
|
1439
|
+
// ADR-O001 D1: which basis that ceiling is enforced on. Defaults to
|
|
1440
|
+
// the historical uncached basis so the honest (cache-aware) meter
|
|
1441
|
+
// introduced alongside it cannot exhaust an existing budget_tokens —
|
|
1442
|
+
// including the schema's 1M default — roughly 100x early.
|
|
1443
|
+
maxTokensBasis: def.run_config.budget_tokens_basis ?? 'uncached',
|
|
1347
1444
|
maxUsd: role.budget_usd,
|
|
1348
1445
|
...(role.policy ?? {}),
|
|
1349
1446
|
}, bus, roleCwd,
|
|
@@ -1356,7 +1453,13 @@ export class OrgDaemon {
|
|
|
1356
1453
|
providerPrefixes: () => roleProviderPrefixes(role),
|
|
1357
1454
|
trace: () => this.roleTrace(name, role.id),
|
|
1358
1455
|
});
|
|
1359
|
-
|
|
1456
|
+
// ADR-O001 D1: prefer the persisted four-quantity breakdown; a checkpoint
|
|
1457
|
+
// written before it existed still resumes via the scalar, on the uncached
|
|
1458
|
+
// basis it was recorded on.
|
|
1459
|
+
if (roleCheckpoint?.tokenUsage) {
|
|
1460
|
+
policy.setTokenUsage(roleCheckpoint.tokenUsage);
|
|
1461
|
+
}
|
|
1462
|
+
else if (roleCheckpoint?.tokensUsed) {
|
|
1360
1463
|
policy.setUsage(roleCheckpoint.tokensUsed);
|
|
1361
1464
|
}
|
|
1362
1465
|
// ORG-7: restore accumulated USD spend across resume so a stop/resume
|
|
@@ -1364,6 +1467,31 @@ export class OrgDaemon {
|
|
|
1364
1467
|
if (roleCheckpoint?.costUsd) {
|
|
1365
1468
|
policy.setUsageUsd(roleCheckpoint.costUsd);
|
|
1366
1469
|
}
|
|
1470
|
+
// ADR-O001 D7: the loadout this incarnation's session is built with, fixed
|
|
1471
|
+
// for its life. A checkpointed role keeps what it had (its SDK session was
|
|
1472
|
+
// built with it); a replacement keeps its predecessor's; a new role takes
|
|
1473
|
+
// the loadout of the first ready task it is being spawned for.
|
|
1474
|
+
const loadoutName = roleCheckpoint
|
|
1475
|
+
? roleCheckpoint.loadout
|
|
1476
|
+
: (existingSlot?.runtime?.loadout ?? sessionLoadoutFor(running.taskDag, role.id));
|
|
1477
|
+
let loadout;
|
|
1478
|
+
if (loadoutName) {
|
|
1479
|
+
try {
|
|
1480
|
+
loadout = resolveLoadout(def, loadoutName, this.root);
|
|
1481
|
+
}
|
|
1482
|
+
catch (err) {
|
|
1483
|
+
// Validated at start, so only a config hot-reload or a deleted
|
|
1484
|
+
// instructions_file lands here. Spawn without it, loudly: a role that
|
|
1485
|
+
// fails to spawn would strand its task, which is worse.
|
|
1486
|
+
bus.emit({
|
|
1487
|
+
type: 'audit',
|
|
1488
|
+
from: role.id,
|
|
1489
|
+
reason: 'loadout-unresolvable',
|
|
1490
|
+
msg: `role "${role.id}" spawned without loadout "${loadoutName}": ${err instanceof Error ? err.message : err}`,
|
|
1491
|
+
data: { loadout: loadoutName },
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1367
1495
|
const runtime = {
|
|
1368
1496
|
mailbox,
|
|
1369
1497
|
policy,
|
|
@@ -1375,6 +1503,7 @@ export class OrgDaemon {
|
|
|
1375
1503
|
sessionId: roleCheckpoint?.sessionId,
|
|
1376
1504
|
worktreePath: roleCwd !== cwd ? roleCwd : undefined,
|
|
1377
1505
|
scrollback: new ScrollbackBuffer(),
|
|
1506
|
+
...(loadout ? { loadout: loadout.name } : {}),
|
|
1378
1507
|
};
|
|
1379
1508
|
if (roleCheckpoint?.scrollback?.length) {
|
|
1380
1509
|
for (const line of roleCheckpoint.scrollback)
|
|
@@ -1412,13 +1541,34 @@ export class OrgDaemon {
|
|
|
1412
1541
|
},
|
|
1413
1542
|
maxTurns: role.max_turns_per_message ?? def.run_config.max_turns_per_message,
|
|
1414
1543
|
resumeSessionId: roleCheckpoint?.sessionId,
|
|
1544
|
+
sessionLedger: running.sessionLedger,
|
|
1545
|
+
// ADR-O001 D3 x D7: a task-scoped session is built with its task's own
|
|
1546
|
+
// recorded loadout. Unresolvable → no loadout, loudly (as at spawn).
|
|
1547
|
+
loadoutFor: (taskId) => {
|
|
1548
|
+
const name = running.taskDag?.get(taskId)?.loadout;
|
|
1549
|
+
if (!name)
|
|
1550
|
+
return undefined;
|
|
1551
|
+
try {
|
|
1552
|
+
return resolveLoadout(def, name, this.root);
|
|
1553
|
+
}
|
|
1554
|
+
catch (err) {
|
|
1555
|
+
bus.emit({
|
|
1556
|
+
type: 'audit',
|
|
1557
|
+
from: role.id,
|
|
1558
|
+
reason: 'loadout-unresolvable',
|
|
1559
|
+
msg: `task ${taskId} session built without loadout "${name}": ${err instanceof Error ? err.message : err}`,
|
|
1560
|
+
data: { loadout: name, taskId },
|
|
1561
|
+
});
|
|
1562
|
+
return undefined;
|
|
1563
|
+
}
|
|
1564
|
+
},
|
|
1415
1565
|
lastMessageId: () => runtime.lastMessageId,
|
|
1416
1566
|
onOutput: (line) => runtime.scrollback.push(line),
|
|
1417
1567
|
onSessionId: (id) => {
|
|
1418
1568
|
runtime.sessionId = id;
|
|
1419
1569
|
},
|
|
1420
1570
|
deliver: (from, to, subject, body) => this.deliver(name, from, to, subject, body),
|
|
1421
|
-
askHuman: (r, question) => this.askHuman(name, r, question),
|
|
1571
|
+
askHuman: (r, question, blocking) => this.askHuman(name, r, question, blocking),
|
|
1422
1572
|
onGate: (r, gateName, gateDesc) => this.createGate(name, r, gateName, gateDesc),
|
|
1423
1573
|
circuitBreaker: (() => {
|
|
1424
1574
|
const cb = def.run_config.circuit_breaker;
|
|
@@ -1463,9 +1613,7 @@ export class OrgDaemon {
|
|
|
1463
1613
|
// unanswered question is the same "genuinely waiting on a
|
|
1464
1614
|
// human" fact either way.
|
|
1465
1615
|
const pendingHumanWaits = this.listGates(name, 'pending').length +
|
|
1466
|
-
questionOps
|
|
1467
|
-
.readQuestions(this.root, name)
|
|
1468
|
-
.questions.filter((q) => q.answer === null).length;
|
|
1616
|
+
questionOps.pendingBlockingQuestions(this.root, name).length;
|
|
1469
1617
|
return resolveOrgComplete(bus, r, outcome, summary, blocker, blockerDetail, {
|
|
1470
1618
|
mode: def.run_config.completion ?? 'boss',
|
|
1471
1619
|
maxBudgetFraction,
|
|
@@ -1535,12 +1683,24 @@ export class OrgDaemon {
|
|
|
1535
1683
|
});
|
|
1536
1684
|
return text;
|
|
1537
1685
|
},
|
|
1538
|
-
createTask: (r, title, assignee, deps) => {
|
|
1539
|
-
return this.dagCreateTask(name, r, title, assignee, deps);
|
|
1686
|
+
createTask: (r, title, assignee, deps, loadout) => {
|
|
1687
|
+
return this.dagCreateTask(name, r, title, assignee, deps, loadout);
|
|
1540
1688
|
},
|
|
1541
|
-
|
|
1542
|
-
|
|
1689
|
+
// ADR-O001 D7: only an org with a catalog gets the `loadout` argument;
|
|
1690
|
+
// the session itself is built with the loadout frozen above.
|
|
1691
|
+
loadoutCatalog: loadoutCatalog(def),
|
|
1692
|
+
loadout,
|
|
1693
|
+
completeTask: (r, taskId, result, evidence) => {
|
|
1694
|
+
return this.dagCompleteTask(name, r, taskId, result, evidence);
|
|
1543
1695
|
},
|
|
1696
|
+
// ADR-O001 D5: only an org that opted in advertises the evidence
|
|
1697
|
+
// argument, so every other org's tool list stays byte-identical.
|
|
1698
|
+
requireTaskEvidence: def.run_config.completion_evidence === true,
|
|
1699
|
+
// ADR-O001 D6: only an org with an artifact-only reviewer gets org_review,
|
|
1700
|
+
// so every other org's tool list stays byte-identical.
|
|
1701
|
+
requestReview: def.roles.some((r) => r.review_input === 'artifact-only')
|
|
1702
|
+
? (r, taskId, reviewer, base) => this.dagRequestReview(name, r, taskId, reviewer, base)
|
|
1703
|
+
: undefined,
|
|
1544
1704
|
listTasks: () => {
|
|
1545
1705
|
const running = this.orgs.get(name);
|
|
1546
1706
|
return JSON.stringify(running?.taskDag?.all() ?? [], null, 2);
|
|
@@ -1986,7 +2146,10 @@ export class OrgDaemon {
|
|
|
1986
2146
|
slot.queuedDuringSwap.push(...reclaimedQueue);
|
|
1987
2147
|
// Step 9: retire accounting BEFORE replacing the runtime.
|
|
1988
2148
|
slot.retiredUsage = {
|
|
1989
|
-
|
|
2149
|
+
// Budgeted basis: this total is summed with live policy.budgetedUsage
|
|
2150
|
+
// against the org-wide budget_tokens ceiling (ADR-O001 D1), so the two
|
|
2151
|
+
// terms must share a basis.
|
|
2152
|
+
tokens: slot.retiredUsage.tokens + oldRuntime.policy.budgetedUsage,
|
|
1990
2153
|
costUsd: slot.retiredUsage.costUsd + oldRuntime.metrics.costUsd,
|
|
1991
2154
|
};
|
|
1992
2155
|
// Step 10: spawn generation N+1 (generation already bumped in step 6).
|
|
@@ -2552,8 +2715,8 @@ export class OrgDaemon {
|
|
|
2552
2715
|
return approvalOps.setApproval(this, org, role, action, approved, opts);
|
|
2553
2716
|
}
|
|
2554
2717
|
// questions.ts
|
|
2555
|
-
async askHuman(org, role, question) {
|
|
2556
|
-
return questionOps.askHuman(this, org, role, question);
|
|
2718
|
+
async askHuman(org, role, question, blocking) {
|
|
2719
|
+
return questionOps.askHuman(this, org, role, question, blocking);
|
|
2557
2720
|
}
|
|
2558
2721
|
async answerQuestion(org, role, questionId, answer, resolvedBy) {
|
|
2559
2722
|
return questionOps.answerQuestion(this, org, role, questionId, answer, resolvedBy);
|
|
@@ -2571,11 +2734,15 @@ export class OrgDaemon {
|
|
|
2571
2734
|
listGates(org, status) {
|
|
2572
2735
|
return decisionOps.listGates(this, org, status);
|
|
2573
2736
|
}
|
|
2574
|
-
dagCreateTask(org, role, title, assignee, deps) {
|
|
2575
|
-
return decisionOps.dagCreateTask(this, org, role, title, assignee, deps);
|
|
2737
|
+
dagCreateTask(org, role, title, assignee, deps, loadout) {
|
|
2738
|
+
return decisionOps.dagCreateTask(this, org, role, title, assignee, deps, loadout);
|
|
2739
|
+
}
|
|
2740
|
+
dagCompleteTask(org, role, taskId, result, evidence) {
|
|
2741
|
+
return decisionOps.dagCompleteTask(this, org, role, taskId, result, evidence);
|
|
2576
2742
|
}
|
|
2577
|
-
|
|
2578
|
-
|
|
2743
|
+
/** ADR-O001 D6 — see decisions.ts's dagRequestReview. */
|
|
2744
|
+
dagRequestReview(org, role, taskId, reviewer, base) {
|
|
2745
|
+
return decisionOps.dagRequestReview(this, org, role, taskId, reviewer, base);
|
|
2579
2746
|
}
|
|
2580
2747
|
dagSplitTask(org, role, parentId, children) {
|
|
2581
2748
|
return decisionOps.dagSplitTask(this, org, role, parentId, children);
|