@goah/cli 0.11.5 → 0.13.1
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/dist/LICENSE +201 -0
- package/dist/THIRD-PARTY-NOTICES.md +2319 -0
- package/dist/anthropic-messages-WBFOHES5.js +7214 -0
- package/dist/azure-openai-responses-NZFH3PO6.js +253 -0
- package/dist/chunk-25XSBFX6.js +639 -0
- package/dist/chunk-2QBQZGXF.js +110 -0
- package/dist/chunk-2ZP2NBW3.js +345 -0
- package/dist/chunk-4KA63VNZ.js +2 -0
- package/dist/chunk-4M6TXTT5.js +86 -0
- package/dist/chunk-4US7ZHT3.js +20 -0
- package/dist/chunk-6M3OOCKO.js +11809 -0
- package/dist/chunk-7AXAFDPG.js +17 -0
- package/dist/chunk-BXA47DTF.js +548 -0
- package/dist/chunk-FSBUS2BI.js +26 -0
- package/dist/chunk-GKDRAHLI.js +4787 -0
- package/dist/chunk-H462EFJP.js +89 -0
- package/dist/chunk-IIJQ3DW4.js +35 -0
- package/dist/chunk-JQD5EWJY.js +34 -0
- package/dist/chunk-K26BVR6O.js +61 -0
- package/dist/chunk-KAO7WIXG.js +31 -0
- package/dist/chunk-LBKI23CC.js +9810 -0
- package/dist/chunk-LKBKZEHT.js +41 -0
- package/dist/chunk-MD7TLTXZ.js +18 -0
- package/dist/chunk-MPXK7QBL.js +868 -0
- package/dist/chunk-NRLGWZJC.js +11 -0
- package/dist/chunk-PFEV3HWI.js +1178 -0
- package/dist/chunk-QBD6EJ2Z.js +1746 -0
- package/dist/chunk-R6I3VKME.js +132 -0
- package/dist/chunk-TJON22J4.js +87 -0
- package/dist/chunk-ULY36ME4.js +33097 -0
- package/dist/chunk-VWEV4LTH.js +25 -0
- package/dist/chunk-X7UWW2PN.js +883 -0
- package/dist/chunk-XALZLRYK.js +9221 -0
- package/dist/chunk-XPQKM3L7.js +57 -0
- package/dist/chunk-YH6YJ2IB.js +1773 -0
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +13148 -506
- package/dist/console/assets/index-3sYrDMmt.css +1 -0
- package/dist/console/assets/index-B1vGMXKr.js +52 -0
- package/dist/console/assets/index-B1vGMXKr.js.map +1 -0
- package/dist/console/index.html +2 -2
- package/dist/dist-65MDQA4Q.js +1126 -0
- package/dist/dist-KNR2XL6J.js +39 -0
- package/dist/execution.d.ts +557 -2
- package/dist/execution.js +37 -2
- package/dist/faux-runner-worker.js +128 -0
- package/dist/google-generative-ai-YMHXZSFE.js +422 -0
- package/dist/google-vertex-X6DB3ZQ6.js +471 -0
- package/dist/index.d.ts +855 -44
- package/dist/index.js +84 -257
- package/dist/kernel.d.ts +39 -2
- package/dist/kernel.js +21 -2
- package/dist/mistral-conversations-TY3MMD6G.js +718 -0
- package/dist/multipart-parser-F6M4NJZU.js +373 -0
- package/dist/openai-codex-responses-BRDUMLN5.js +1374 -0
- package/dist/openai-completions-CX634XN3.js +1139 -0
- package/dist/openai-responses-HMCXJIHT.js +300 -0
- package/dist/openrouter-images-KTWET2H2.js +148 -0
- package/dist/pi-messages-XL5OEC4K.js +307 -0
- package/dist/pi-worker.js +671 -0
- package/dist/runner-pi.d.ts +342 -2
- package/dist/runner-pi.js +46 -2
- package/dist/runner-registry-M776E3SE.js +22 -0
- package/dist/sqlite.d.ts +446 -2
- package/dist/sqlite.js +15 -2
- package/dist/src-F2KBSV4T.js +1398 -0
- package/dist/supervisor.d.ts +625 -2
- package/dist/supervisor.js +39 -2
- package/dist/testkit.d.ts +564 -2
- package/dist/testkit.js +127 -2
- package/dist/transcript.d.ts +84 -0
- package/dist/transcript.js +25 -0
- package/dist/verification-worker.js +72 -0
- package/package.json +3 -28
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/console/assets/index-BJTGvlCH.js +0 -52
- package/dist/console/assets/index-BJTGvlCH.js.map +0 -1
- package/dist/console/assets/index-msBn41Cl.css +0 -1
- package/dist/control.d.ts +0 -106
- package/dist/control.d.ts.map +0 -1
- package/dist/control.js +0 -215
- package/dist/control.js.map +0 -1
- package/dist/execution.d.ts.map +0 -1
- package/dist/execution.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/inspect.d.ts +0 -61
- package/dist/inspect.d.ts.map +0 -1
- package/dist/inspect.js +0 -110
- package/dist/inspect.js.map +0 -1
- package/dist/kernel.d.ts.map +0 -1
- package/dist/kernel.js.map +0 -1
- package/dist/live-config.d.ts +0 -10
- package/dist/live-config.d.ts.map +0 -1
- package/dist/live-config.js +0 -44
- package/dist/live-config.js.map +0 -1
- package/dist/metrics.d.ts +0 -2
- package/dist/metrics.d.ts.map +0 -1
- package/dist/metrics.js +0 -2
- package/dist/metrics.js.map +0 -1
- package/dist/prompt-queue.d.ts +0 -13
- package/dist/prompt-queue.d.ts.map +0 -1
- package/dist/prompt-queue.js +0 -30
- package/dist/prompt-queue.js.map +0 -1
- package/dist/runner-pi.d.ts.map +0 -1
- package/dist/runner-pi.js.map +0 -1
- package/dist/runner-registry.d.ts +0 -8
- package/dist/runner-registry.d.ts.map +0 -1
- package/dist/runner-registry.js +0 -12
- package/dist/runner-registry.js.map +0 -1
- package/dist/searchable-select.d.ts +0 -24
- package/dist/searchable-select.d.ts.map +0 -1
- package/dist/searchable-select.js +0 -61
- package/dist/searchable-select.js.map +0 -1
- package/dist/secret-input.d.ts +0 -8
- package/dist/secret-input.d.ts.map +0 -1
- package/dist/secret-input.js +0 -14
- package/dist/secret-input.js.map +0 -1
- package/dist/session.d.ts +0 -2
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -2
- package/dist/session.js.map +0 -1
- package/dist/setup-wizard.d.ts +0 -11
- package/dist/setup-wizard.d.ts.map +0 -1
- package/dist/setup-wizard.js +0 -130
- package/dist/setup-wizard.js.map +0 -1
- package/dist/sqlite.d.ts.map +0 -1
- package/dist/sqlite.js.map +0 -1
- package/dist/supervisor.d.ts.map +0 -1
- package/dist/supervisor.js.map +0 -1
- package/dist/testkit.d.ts.map +0 -1
- package/dist/testkit.js.map +0 -1
- package/dist/tui-theme.d.ts +0 -16
- package/dist/tui-theme.d.ts.map +0 -1
- package/dist/tui-theme.js +0 -18
- package/dist/tui-theme.js.map +0 -1
- package/dist/tui.d.ts +0 -18
- package/dist/tui.d.ts.map +0 -1
- package/dist/tui.js +0 -583
- package/dist/tui.js.map +0 -1
- package/dist/update.d.ts +0 -15
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js +0 -59
- package/dist/update.js.map +0 -1
- package/dist/web-console.d.ts +0 -33
- package/dist/web-console.d.ts.map +0 -1
- package/dist/web-console.js +0 -252
- package/dist/web-console.js.map +0 -1
- package/dist/welcome.d.ts +0 -30
- package/dist/welcome.d.ts.map +0 -1
- package/dist/welcome.js +0 -81
- package/dist/welcome.js.map +0 -1
- package/node_modules/goah-ledger-contract/dist/execution.d.ts +0 -402
- package/node_modules/goah-ledger-contract/dist/execution.d.ts.map +0 -1
- package/node_modules/goah-ledger-contract/dist/execution.js +0 -40
- package/node_modules/goah-ledger-contract/dist/execution.js.map +0 -1
- package/node_modules/goah-ledger-contract/dist/index.d.ts +0 -5
- package/node_modules/goah-ledger-contract/dist/index.d.ts.map +0 -1
- package/node_modules/goah-ledger-contract/dist/index.js +0 -5
- package/node_modules/goah-ledger-contract/dist/index.js.map +0 -1
- package/node_modules/goah-ledger-contract/dist/kernel.d.ts +0 -37
- package/node_modules/goah-ledger-contract/dist/kernel.d.ts.map +0 -1
- package/node_modules/goah-ledger-contract/dist/kernel.js +0 -8
- package/node_modules/goah-ledger-contract/dist/kernel.js.map +0 -1
- package/node_modules/goah-ledger-contract/dist/metrics.d.ts +0 -40
- package/node_modules/goah-ledger-contract/dist/metrics.d.ts.map +0 -1
- package/node_modules/goah-ledger-contract/dist/metrics.js +0 -25
- package/node_modules/goah-ledger-contract/dist/metrics.js.map +0 -1
- package/node_modules/goah-ledger-contract/dist/session.d.ts +0 -63
- package/node_modules/goah-ledger-contract/dist/session.d.ts.map +0 -1
- package/node_modules/goah-ledger-contract/dist/session.js +0 -115
- package/node_modules/goah-ledger-contract/dist/session.js.map +0 -1
- package/node_modules/goah-ledger-contract/package.json +0 -29
- package/node_modules/goah-ledger-sqlite/dist/index.d.ts +0 -77
- package/node_modules/goah-ledger-sqlite/dist/index.d.ts.map +0 -1
- package/node_modules/goah-ledger-sqlite/dist/index.js +0 -884
- package/node_modules/goah-ledger-sqlite/dist/index.js.map +0 -1
- package/node_modules/goah-ledger-sqlite/package.json +0 -26
- package/node_modules/goah-runner-pi/dist/configurator.d.ts +0 -20
- package/node_modules/goah-runner-pi/dist/configurator.d.ts.map +0 -1
- package/node_modules/goah-runner-pi/dist/configurator.js +0 -267
- package/node_modules/goah-runner-pi/dist/configurator.js.map +0 -1
- package/node_modules/goah-runner-pi/dist/credential-store.d.ts +0 -12
- package/node_modules/goah-runner-pi/dist/credential-store.d.ts.map +0 -1
- package/node_modules/goah-runner-pi/dist/credential-store.js +0 -69
- package/node_modules/goah-runner-pi/dist/credential-store.js.map +0 -1
- package/node_modules/goah-runner-pi/dist/env-spec.d.ts +0 -17
- package/node_modules/goah-runner-pi/dist/env-spec.d.ts.map +0 -1
- package/node_modules/goah-runner-pi/dist/env-spec.js +0 -95
- package/node_modules/goah-runner-pi/dist/env-spec.js.map +0 -1
- package/node_modules/goah-runner-pi/dist/index.d.ts +0 -64
- package/node_modules/goah-runner-pi/dist/index.d.ts.map +0 -1
- package/node_modules/goah-runner-pi/dist/index.js +0 -241
- package/node_modules/goah-runner-pi/dist/index.js.map +0 -1
- package/node_modules/goah-runner-pi/dist/model-provider.d.ts +0 -30
- package/node_modules/goah-runner-pi/dist/model-provider.d.ts.map +0 -1
- package/node_modules/goah-runner-pi/dist/model-provider.js +0 -96
- package/node_modules/goah-runner-pi/dist/model-provider.js.map +0 -1
- package/node_modules/goah-runner-pi/dist/pi-worker.d.ts +0 -29
- package/node_modules/goah-runner-pi/dist/pi-worker.d.ts.map +0 -1
- package/node_modules/goah-runner-pi/dist/pi-worker.js +0 -428
- package/node_modules/goah-runner-pi/dist/pi-worker.js.map +0 -1
- package/node_modules/goah-runner-pi/dist/test-helpers.d.ts +0 -4
- package/node_modules/goah-runner-pi/dist/test-helpers.d.ts.map +0 -1
- package/node_modules/goah-runner-pi/dist/test-helpers.js +0 -11
- package/node_modules/goah-runner-pi/dist/test-helpers.js.map +0 -1
- package/node_modules/goah-runner-pi/dist/verification-worker.d.ts +0 -2
- package/node_modules/goah-runner-pi/dist/verification-worker.d.ts.map +0 -1
- package/node_modules/goah-runner-pi/dist/verification-worker.js +0 -48
- package/node_modules/goah-runner-pi/dist/verification-worker.js.map +0 -1
- package/node_modules/goah-runner-pi/package.json +0 -28
- package/node_modules/goah-supervisor/dist/context-view.d.ts +0 -31
- package/node_modules/goah-supervisor/dist/context-view.d.ts.map +0 -1
- package/node_modules/goah-supervisor/dist/context-view.js +0 -71
- package/node_modules/goah-supervisor/dist/context-view.js.map +0 -1
- package/node_modules/goah-supervisor/dist/index.d.ts +0 -94
- package/node_modules/goah-supervisor/dist/index.d.ts.map +0 -1
- package/node_modules/goah-supervisor/dist/index.js +0 -888
- package/node_modules/goah-supervisor/dist/index.js.map +0 -1
- package/node_modules/goah-supervisor/dist/roles.d.ts +0 -3
- package/node_modules/goah-supervisor/dist/roles.d.ts.map +0 -1
- package/node_modules/goah-supervisor/dist/roles.js +0 -14
- package/node_modules/goah-supervisor/dist/roles.js.map +0 -1
- package/node_modules/goah-supervisor/dist/verification.d.ts +0 -82
- package/node_modules/goah-supervisor/dist/verification.d.ts.map +0 -1
- package/node_modules/goah-supervisor/dist/verification.js +0 -110
- package/node_modules/goah-supervisor/dist/verification.js.map +0 -1
- package/node_modules/goah-supervisor/package.json +0 -26
- package/node_modules/goah-testkit/dist/faux-runner-worker.d.ts +0 -2
- package/node_modules/goah-testkit/dist/faux-runner-worker.d.ts.map +0 -1
- package/node_modules/goah-testkit/dist/faux-runner-worker.js +0 -75
- package/node_modules/goah-testkit/dist/faux-runner-worker.js.map +0 -1
- package/node_modules/goah-testkit/dist/index.d.ts +0 -52
- package/node_modules/goah-testkit/dist/index.d.ts.map +0 -1
- package/node_modules/goah-testkit/dist/index.js +0 -113
- package/node_modules/goah-testkit/dist/index.js.map +0 -1
- package/node_modules/goah-testkit/dist/mock-connector-worker.d.ts +0 -2
- package/node_modules/goah-testkit/dist/mock-connector-worker.d.ts.map +0 -1
- package/node_modules/goah-testkit/dist/mock-connector-worker.js +0 -29
- package/node_modules/goah-testkit/dist/mock-connector-worker.js.map +0 -1
- package/node_modules/goah-testkit/package.json +0 -29
|
@@ -1,888 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { capabilityFor, controlStream, evaluateMetric, goalStream, interruptedSessionEvents, memoryStream, wakeStream, } from "goah-ledger-contract";
|
|
4
|
-
import { composeActiveContext, selectRecoveryEvents, selectWorkingMemory } from "./context-view.js";
|
|
5
|
-
import { defaultRolePrompt } from "./roles.js";
|
|
6
|
-
export { composeActiveContext, selectRecoveryEvents } from "./context-view.js";
|
|
7
|
-
/** Dispatches each wake to the runner selected by its opaque Runner Profile. */
|
|
8
|
-
export class RunnerRouter {
|
|
9
|
-
runners;
|
|
10
|
-
fallback;
|
|
11
|
-
isolation = "process";
|
|
12
|
-
#byPid = new Map();
|
|
13
|
-
constructor(runners, fallback = "default") {
|
|
14
|
-
this.runners = runners;
|
|
15
|
-
this.fallback = fallback;
|
|
16
|
-
}
|
|
17
|
-
prepare(request) {
|
|
18
|
-
const context = request.context && typeof request.context === "object" && !Array.isArray(request.context) ? request.context : {};
|
|
19
|
-
const profile = context.runnerProfile && typeof context.runnerProfile === "object" && !Array.isArray(context.runnerProfile) ? context.runnerProfile : {};
|
|
20
|
-
const id = typeof profile.id === "string" ? profile.id : this.fallback;
|
|
21
|
-
const runner = this.runners.get(id);
|
|
22
|
-
if (!runner)
|
|
23
|
-
throw new Error(`runner profile is not configured: ${id}`);
|
|
24
|
-
const handle = runner.prepare(request);
|
|
25
|
-
if (handle.pid)
|
|
26
|
-
this.#byPid.set(handle.pid, runner);
|
|
27
|
-
void handle.result.finally(() => { if (handle.pid)
|
|
28
|
-
this.#byPid.delete(handle.pid); });
|
|
29
|
-
return handle;
|
|
30
|
-
}
|
|
31
|
-
async terminateProcess(pid) {
|
|
32
|
-
const runner = this.#byPid.get(pid);
|
|
33
|
-
if (!runner)
|
|
34
|
-
return;
|
|
35
|
-
await runner.terminateProcess(pid);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
export class Supervisor {
|
|
39
|
-
ledger;
|
|
40
|
-
clock;
|
|
41
|
-
#leaseMs;
|
|
42
|
-
#memoryTailChars;
|
|
43
|
-
#allowExternalActions;
|
|
44
|
-
#approvers;
|
|
45
|
-
#auditWriters;
|
|
46
|
-
#claimTail = Promise.resolve();
|
|
47
|
-
#connectors = new Map();
|
|
48
|
-
#metricCollectors = new Map();
|
|
49
|
-
#metricContracts = new Map();
|
|
50
|
-
#silence;
|
|
51
|
-
#retryPolicy;
|
|
52
|
-
#profiles;
|
|
53
|
-
#runnerProfiles;
|
|
54
|
-
#handles = new Map();
|
|
55
|
-
#verifyMetricsAfterWake;
|
|
56
|
-
#runner;
|
|
57
|
-
constructor(ledger, runner, clock, options = {}) {
|
|
58
|
-
this.ledger = ledger;
|
|
59
|
-
this.clock = clock;
|
|
60
|
-
this.#runner = runner;
|
|
61
|
-
this.#leaseMs = options.leaseMs ?? 30_000;
|
|
62
|
-
this.#memoryTailChars = options.memoryTailChars ?? 12_000;
|
|
63
|
-
this.#allowExternalActions = options.allowExternalActions ?? false;
|
|
64
|
-
this.#approvers = new Set(options.approvers ?? ["human", "ceo"]);
|
|
65
|
-
this.#auditWriters = new Set(options.auditWriters ?? ["verifier", "audit"]);
|
|
66
|
-
this.#silence = options.silence === null ? null : { maxSilentMs: options.silence?.maxSilentMs ?? 12 * 3_600_000, notify: options.silence?.notify ?? "ceo" };
|
|
67
|
-
this.#retryPolicy = options.retryPolicy ?? { maxAttempts: 0, baseDelayMs: 1_000 };
|
|
68
|
-
this.#profiles = new Map([["ceo", { agent: "ceo", role: "ceo" }], ...(options.profiles ?? []).map((profile) => [profile.agent, profile])]);
|
|
69
|
-
this.#runnerProfiles = new Map((options.runnerProfiles ?? []).map((profile) => [profile.id, profile]));
|
|
70
|
-
this.#verifyMetricsAfterWake = options.verifyMetricsAfterWake ?? false;
|
|
71
|
-
}
|
|
72
|
-
registerConnector(connector) { this.#connectors.set(connector.manifest.connector, connector); }
|
|
73
|
-
registerMetricContract(goalId, contract) { this.#metricContracts.set(goalId, contract); }
|
|
74
|
-
registerMetricCollector(goalId, contract, spec, intervalMs = 60_000) {
|
|
75
|
-
this.registerMetricContract(goalId, contract);
|
|
76
|
-
this.#metricCollectors.set(goalId, { goalId, contract, spec, intervalMs, nextAt: 0 });
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Hot-swap the runner after a config reload. Refused while a wake is leased
|
|
80
|
-
* or running: a live child must never observe its runner vanish mid-flight.
|
|
81
|
-
* The next tick claims wakes through the new runner, and spawn-time env
|
|
82
|
-
* resolution applies the new credentials to the next spawn.
|
|
83
|
-
*/
|
|
84
|
-
swapRunner(runner, profiles) {
|
|
85
|
-
const active = this.ledger.wakes().filter((wake) => wake.status === "leased" || wake.status === "running");
|
|
86
|
-
if (active.length > 0)
|
|
87
|
-
throw new Error("cannot swap runner while a wake is leased or running");
|
|
88
|
-
this.#runner = runner;
|
|
89
|
-
if (profiles) {
|
|
90
|
-
this.#runnerProfiles.clear();
|
|
91
|
-
for (const profile of profiles)
|
|
92
|
-
this.#runnerProfiles.set(profile.id, profile);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
get runner() { return this.#runner; }
|
|
96
|
-
createGoal(goal, actor = "human") { this.ledger.putGoal(goal, actor); }
|
|
97
|
-
createRootGoal(objective, id = randomUUID()) {
|
|
98
|
-
if (!objective.trim())
|
|
99
|
-
throw new Error("root objective is required");
|
|
100
|
-
if (this.ledger.goals().some((goal) => goal.parentId === null && goal.owner === "ceo" && goal.phase !== "complete"))
|
|
101
|
-
throw new Error("CEO already has an unfinished Root Goal; use work_on_goal");
|
|
102
|
-
const goal = { id, parentId: null, objective, observationMethod: null, verificationMethod: null, owner: "ceo", phase: "active", revision: 0 };
|
|
103
|
-
this.ledger.putGoal(goal, "human");
|
|
104
|
-
return this.#goal(id);
|
|
105
|
-
}
|
|
106
|
-
startGoal(objective, id = randomUUID()) {
|
|
107
|
-
const goal = this.createRootGoal(objective, id);
|
|
108
|
-
const wake = this.#enqueueTrigger("ceo", `root:${id}:created`);
|
|
109
|
-
if (!wake)
|
|
110
|
-
throw new Error("CEO wake was not admitted for an active root goal");
|
|
111
|
-
return { goal, wake };
|
|
112
|
-
}
|
|
113
|
-
interactWithCeo(message) {
|
|
114
|
-
if (!message.trim())
|
|
115
|
-
throw new Error("message is required");
|
|
116
|
-
const mail = { id: randomUUID(), to: "ceo", from: "human", level: "fyi", body: { type: "interaction", message }, readAt: null };
|
|
117
|
-
this.ledger.putMail(mail, "human");
|
|
118
|
-
const wake = this.#enqueueTrigger("ceo", `interaction:${mail.id}`);
|
|
119
|
-
if (!wake)
|
|
120
|
-
throw new Error("CEO interaction was not admitted");
|
|
121
|
-
return { mail, wake };
|
|
122
|
-
}
|
|
123
|
-
sendToCeo(body, level = "decision") {
|
|
124
|
-
const mail = { id: randomUUID(), to: "ceo", from: "human", level, body, readAt: null };
|
|
125
|
-
this.ledger.putMail(mail, "human");
|
|
126
|
-
const wake = this.#enqueueTrigger("ceo", `mail:${mail.id}`);
|
|
127
|
-
if (!wake)
|
|
128
|
-
throw new Error("CEO wake was not admitted");
|
|
129
|
-
return { mail, wake };
|
|
130
|
-
}
|
|
131
|
-
delegate(request, actor = "ceo", wakeId) { return this.ledger.commitDelegation(request, actor, wakeId); }
|
|
132
|
-
reassignGoal(request, actor = "ceo", wakeId) { return this.ledger.commitReassignment(request, actor, wakeId); }
|
|
133
|
-
teamList(now = this.#now()) { return deriveTeam(this.ledger, now); }
|
|
134
|
-
updateGoal(id, patch, actor = "human") {
|
|
135
|
-
if (patch.objective === undefined && patch.observationMethod === undefined && patch.verificationMethod === undefined && patch.owner === undefined)
|
|
136
|
-
throw new Error("goal update requires objective, observation method, verification method, or owner");
|
|
137
|
-
const current = this.#goal(id);
|
|
138
|
-
if (patch.objective !== undefined && patch.objective !== current.objective && current.parentId !== null && (patch.observationMethod === undefined || patch.verificationMethod === undefined))
|
|
139
|
-
throw new Error("child objective revision requires replacement observation and verification methods");
|
|
140
|
-
const next = {
|
|
141
|
-
...current,
|
|
142
|
-
...patch,
|
|
143
|
-
...(patch.objective !== undefined && patch.objective !== current.objective && current.parentId === null && patch.observationMethod === undefined ? { observationMethod: null } : {}),
|
|
144
|
-
...(patch.objective !== undefined && patch.objective !== current.objective && current.parentId === null && patch.verificationMethod === undefined ? { verificationMethod: null } : {}),
|
|
145
|
-
revision: current.revision + 1,
|
|
146
|
-
};
|
|
147
|
-
this.ledger.putGoal(next, actor);
|
|
148
|
-
if (next.parentId === null && next.owner === "ceo" && actor === "human")
|
|
149
|
-
this.#enqueueTrigger("ceo", `root:${id}:revised:${next.revision}`);
|
|
150
|
-
return next;
|
|
151
|
-
}
|
|
152
|
-
confirmObservationMethod(id, observationMethod) {
|
|
153
|
-
const current = this.#goal(id);
|
|
154
|
-
if (current.parentId !== null)
|
|
155
|
-
throw new Error("human confirmation applies only to a root goal");
|
|
156
|
-
return this.updateGoal(id, { observationMethod, verificationMethod: observationMethod }, "human");
|
|
157
|
-
}
|
|
158
|
-
reviseChildGoal(id, objective, observationMethod, verificationMethod, actor, reason, evidence, wakeId) {
|
|
159
|
-
const current = this.#goal(id);
|
|
160
|
-
if (current.parentId === null)
|
|
161
|
-
throw new Error("CEO cannot revise a root goal");
|
|
162
|
-
if (!reason.trim())
|
|
163
|
-
throw new Error("goal revision reason is required");
|
|
164
|
-
for (const seq of evidence)
|
|
165
|
-
if (!this.ledger.eventsSince(seq - 1).some((event) => event.seq === seq))
|
|
166
|
-
throw new Error(`evidence event does not exist: ${seq}`);
|
|
167
|
-
this.ledger.appendEvent({ streamId: wakeId ? wakeStream(wakeId) : goalStream(id), ts: this.#now(), actor, type: "goal.revision_requested", data: { goalId: id, fromRevision: current.revision, objective, observationMethod, verificationMethod, reason, evidence } });
|
|
168
|
-
return this.updateGoal(id, { objective, observationMethod, verificationMethod }, actor);
|
|
169
|
-
}
|
|
170
|
-
completeGoal(request, actor = "human", wakeId) {
|
|
171
|
-
const goal = this.ledger.completeGoal(request, actor, wakeId);
|
|
172
|
-
this.#suppressQueuedWake(goal.owner, `goal:${goal.id}:complete`);
|
|
173
|
-
if (goal.parentId && actor !== "ceo")
|
|
174
|
-
this.#enqueueTrigger("ceo", `goal:${goal.id}:complete:${goal.revision}`);
|
|
175
|
-
return goal;
|
|
176
|
-
}
|
|
177
|
-
transitionGoal(id, phase, actor = "human") {
|
|
178
|
-
const current = this.#goal(id);
|
|
179
|
-
if (current.phase === phase)
|
|
180
|
-
return current;
|
|
181
|
-
if (phase === "complete")
|
|
182
|
-
throw new Error("goal completion requires reason and evidence");
|
|
183
|
-
const next = { ...current, phase, revision: current.revision + 1 };
|
|
184
|
-
this.ledger.putGoal(next, actor);
|
|
185
|
-
if (phase === "paused")
|
|
186
|
-
this.#suppressQueuedWake(next.owner, `goal:${id}:${phase}`);
|
|
187
|
-
if (phase === "active")
|
|
188
|
-
this.#enqueueTrigger(next.owner, `${next.parentId ? "goal" : "root"}:${id}:resumed:${next.revision}`);
|
|
189
|
-
if (next.parentId && actor !== "ceo" && phase === "blocked")
|
|
190
|
-
this.#enqueueTrigger("ceo", `goal:${id}:${phase}:${next.revision}`);
|
|
191
|
-
return next;
|
|
192
|
-
}
|
|
193
|
-
planWake(agent, at, reason, setBy = agent) {
|
|
194
|
-
const schedule = { id: `schedule:${agent}`, agent, nextWakeAt: at, reason, setBy };
|
|
195
|
-
this.ledger.putSchedule(schedule, setBy);
|
|
196
|
-
return at <= this.#now() ? this.#enqueueSchedule(schedule) : null;
|
|
197
|
-
}
|
|
198
|
-
async stopAgentWake(agent) {
|
|
199
|
-
const wake = this.ledger.wakes().find((item) => item.agent === agent && ["running", "leased", "queued"].includes(item.status));
|
|
200
|
-
if (!wake)
|
|
201
|
-
return null;
|
|
202
|
-
if (wake.status === "queued") {
|
|
203
|
-
this.#suppressQueuedWake(agent, "stopped by human");
|
|
204
|
-
return this.#wake(wake.id);
|
|
205
|
-
}
|
|
206
|
-
const handle = this.#handles.get(wake.id);
|
|
207
|
-
if (handle)
|
|
208
|
-
await handle.terminate();
|
|
209
|
-
else if (wake.runnerPid)
|
|
210
|
-
await this.runner.terminateProcess(wake.runnerPid);
|
|
211
|
-
return this.#wake(wake.id);
|
|
212
|
-
}
|
|
213
|
-
async recover() {
|
|
214
|
-
this.ledger.recoverDispatchingActions();
|
|
215
|
-
for (const expired of this.ledger.expiredWakes(this.#now())) {
|
|
216
|
-
if (expired.status === "running" && expired.runnerPid)
|
|
217
|
-
await this.runner.terminateProcess(expired.runnerPid);
|
|
218
|
-
this.ledger.recoverExpiredWake(expired.id, this.#now());
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
async tick() {
|
|
222
|
-
const claimed = await this.#claimNextWake();
|
|
223
|
-
const wake = claimed?.wake ?? null;
|
|
224
|
-
if (!wake)
|
|
225
|
-
return null;
|
|
226
|
-
const leaseToken = claimed.leaseToken;
|
|
227
|
-
let running = wake;
|
|
228
|
-
let handle = null;
|
|
229
|
-
let renewal;
|
|
230
|
-
try {
|
|
231
|
-
running = this.ledger.markWakeRunning(wake.id, this.#now(), leaseToken);
|
|
232
|
-
const turn = this.#turnContext(running);
|
|
233
|
-
this.ledger.appendRunnerEvent({ streamId: wakeStream(running.id), ts: this.#now(), actor: running.agent, type: "run.admitted", data: turn }, leaseToken);
|
|
234
|
-
const workRecordRevisionAtStart = turn.goalBinding ? this.ledger.workRecord(turn.goalBinding.goalId)?.recordRevision ?? -1 : -1;
|
|
235
|
-
const context = this.#loadContext(running, turn);
|
|
236
|
-
handle = this.runner.prepare({
|
|
237
|
-
wake: running,
|
|
238
|
-
turn,
|
|
239
|
-
context,
|
|
240
|
-
now: () => this.#now(),
|
|
241
|
-
emit: (trace) => this.ledger.appendRunnerEvent({ streamId: wakeStream(running.id), ts: this.#now(), actor: running.agent, type: trace.type, data: trace.data }, leaseToken),
|
|
242
|
-
rpc: (method, params) => this.#agentRpc(running, turn, leaseToken, method, params),
|
|
243
|
-
});
|
|
244
|
-
this.#handles.set(running.id, handle);
|
|
245
|
-
if (handle.pid)
|
|
246
|
-
running = this.ledger.attachWakeProcess(running.id, leaseToken, handle.pid, this.#now());
|
|
247
|
-
renewal = setInterval(() => {
|
|
248
|
-
try {
|
|
249
|
-
const now = this.clock.now();
|
|
250
|
-
this.ledger.renewWakeLease(running.id, leaseToken, new Date(now.getTime() + this.#leaseMs).toISOString(), now.toISOString());
|
|
251
|
-
}
|
|
252
|
-
catch {
|
|
253
|
-
void handle?.terminate();
|
|
254
|
-
}
|
|
255
|
-
}, Math.max(25, Math.floor(this.#leaseMs / 3)));
|
|
256
|
-
renewal.unref();
|
|
257
|
-
handle.begin();
|
|
258
|
-
const result = await handle.result;
|
|
259
|
-
clearInterval(renewal);
|
|
260
|
-
renewal = undefined;
|
|
261
|
-
await handle.terminate();
|
|
262
|
-
this.#handles.delete(running.id);
|
|
263
|
-
if (result.outcome === "abnormal") {
|
|
264
|
-
await this.#markAbnormal(running, result.reason);
|
|
265
|
-
return this.#wake(running.id);
|
|
266
|
-
}
|
|
267
|
-
if (result.outcome === "response") {
|
|
268
|
-
if (turn.goalBinding)
|
|
269
|
-
throw new Error("a Goal-bound Turn must finish with a handoff");
|
|
270
|
-
const mailId = interactionMailId(running);
|
|
271
|
-
if (!mailId)
|
|
272
|
-
throw new Error("an ordinary response requires an interaction mail");
|
|
273
|
-
this.ledger.commitInteraction({ agent: running.agent, wakeId: running.id, mailId, ts: this.#now(), response: result.response });
|
|
274
|
-
this.ledger.finishWake(running.id, "done", this.#now());
|
|
275
|
-
return this.#wake(running.id);
|
|
276
|
-
}
|
|
277
|
-
if (turn.goalBinding)
|
|
278
|
-
this.#validateGoalTurnRecord(running, turn, workRecordRevisionAtStart);
|
|
279
|
-
const output = turn.goalBinding ? this.#goalWakeOutput(result.output, turn.goalBinding) : result.output;
|
|
280
|
-
this.#validateCeoHandoff(running, output);
|
|
281
|
-
const outgoingMail = output.mail.map((draft) => ({ id: randomUUID(), to: draft.to, from: running.agent, level: draft.level, body: draft.body, readAt: null }));
|
|
282
|
-
const schedule = output.nextWakeAt
|
|
283
|
-
? { id: `schedule:${running.agent}`, agent: running.agent, nextWakeAt: output.nextWakeAt, reason: "handoff.next_steps", setBy: running.agent }
|
|
284
|
-
: null;
|
|
285
|
-
const handoffEvent = this.ledger.commitHandoff({ agent: running.agent, wakeId: running.id, ts: this.#now(), output, outgoingMail, schedule });
|
|
286
|
-
this.ledger.finishWake(running.id, "done", this.#now());
|
|
287
|
-
if (this.#role(running.agent) !== "ceo" && handoffTriggersCeo(output.handoff) && this.#hasActiveRoot()) {
|
|
288
|
-
this.#enqueueTrigger("ceo", `child-handoff:${handoffEvent.seq}`);
|
|
289
|
-
}
|
|
290
|
-
if (this.#verifyMetricsAfterWake) {
|
|
291
|
-
for (const goal of this.ledger.goalsForOwner(running.agent))
|
|
292
|
-
if (this.#metricCollectors.has(goal.id))
|
|
293
|
-
await this.collectMetricNow(goal.id);
|
|
294
|
-
}
|
|
295
|
-
return this.#wake(running.id);
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
if (renewal)
|
|
299
|
-
clearInterval(renewal);
|
|
300
|
-
if (handle)
|
|
301
|
-
await handle.terminate();
|
|
302
|
-
this.#handles.delete(running.id);
|
|
303
|
-
await this.#markAbnormal(running, error instanceof Error ? error.message : String(error));
|
|
304
|
-
return this.#wake(running.id);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
async #claimNextWake() {
|
|
308
|
-
const previous = this.#claimTail;
|
|
309
|
-
let release;
|
|
310
|
-
this.#claimTail = new Promise((resolve) => { release = resolve; });
|
|
311
|
-
await previous;
|
|
312
|
-
try {
|
|
313
|
-
for (const schedule of this.ledger.dueSchedules(this.#now()))
|
|
314
|
-
this.#enqueueSchedule(schedule);
|
|
315
|
-
await this.#collectMetrics();
|
|
316
|
-
this.#scheduleMetricAlerts();
|
|
317
|
-
this.#checkSystemSilence();
|
|
318
|
-
for (const mail of this.ledger.triggeringMail())
|
|
319
|
-
this.#enqueueTrigger(mail.to, `mail:${mail.id}`);
|
|
320
|
-
const now = this.clock.now();
|
|
321
|
-
const leaseToken = randomUUID();
|
|
322
|
-
const wake = this.ledger.claimNextWake(now.toISOString(), new Date(now.getTime() + this.#leaseMs).toISOString(), leaseToken);
|
|
323
|
-
return wake ? { wake, leaseToken } : null;
|
|
324
|
-
}
|
|
325
|
-
finally {
|
|
326
|
-
release();
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
async runAvailable(concurrency = 4, maxWakes = 100) {
|
|
330
|
-
const completed = [];
|
|
331
|
-
while (true) {
|
|
332
|
-
const batch = await Promise.all(Array.from({ length: concurrency }, () => this.tick()));
|
|
333
|
-
const wakes = batch.filter((wake) => wake !== null);
|
|
334
|
-
completed.push(...wakes);
|
|
335
|
-
if (wakes.length === 0 || completed.length >= maxWakes)
|
|
336
|
-
return completed;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
async submitAction(action, connectorName, wakeId) {
|
|
340
|
-
const connector = this.#connectors.get(connectorName);
|
|
341
|
-
const capability = connector ? capabilityFor(connector.manifest, action.kind) : null;
|
|
342
|
-
const gated = !connector || !capability || capability.risk !== "reversible"
|
|
343
|
-
|| (!connector.manifest.dryRun && !this.#allowExternalActions);
|
|
344
|
-
if (gated && action.agent !== "ceo")
|
|
345
|
-
this.#assertAgentGoalsCurrent(action.agent);
|
|
346
|
-
const requested = { ...action, connector: connectorName, gated, status: "requested", reconciledAt: null, externalRef: null };
|
|
347
|
-
this.ledger.requestAction(requested, action.agent, wakeId);
|
|
348
|
-
if (gated || !connector)
|
|
349
|
-
return this.#action(action.id);
|
|
350
|
-
this.ledger.approveAction(action.id, "supervisor", "declared reversible dry-run capability", action.evidence);
|
|
351
|
-
return this.#dispatch(action.id);
|
|
352
|
-
}
|
|
353
|
-
async approveAction(id, approver, reason, evidence) {
|
|
354
|
-
if (!this.#approvers.has(approver))
|
|
355
|
-
throw new Error("actor is not authorized to approve actions");
|
|
356
|
-
this.ledger.approveAction(id, approver, reason, evidence);
|
|
357
|
-
return this.#dispatch(id);
|
|
358
|
-
}
|
|
359
|
-
rejectAction(id, approver, reason, evidence) {
|
|
360
|
-
if (!this.#approvers.has(approver))
|
|
361
|
-
throw new Error("actor is not authorized to reject actions");
|
|
362
|
-
return this.ledger.rejectAction(id, approver, reason, evidence);
|
|
363
|
-
}
|
|
364
|
-
putAuditAdvice(id, advice, wakeId) {
|
|
365
|
-
const role = this.#profiles.get(advice.by)?.role;
|
|
366
|
-
if (!this.#auditWriters.has(advice.by) && role !== "verifier" && role !== "audit")
|
|
367
|
-
throw new Error("actor is not authorized to write audit advice");
|
|
368
|
-
return this.ledger.putAuditAdvice(id, advice, wakeId);
|
|
369
|
-
}
|
|
370
|
-
ackAuditAdvice(id, agent) { return this.ledger.ackAuditAdvice(id, agent); }
|
|
371
|
-
recordMetric(sample) {
|
|
372
|
-
const goal = this.ledger.goal(sample.goalId);
|
|
373
|
-
if (!goal)
|
|
374
|
-
throw new Error(`metric goal not found: ${sample.goalId}`);
|
|
375
|
-
const contract = this.#metricContracts.get(goal.id);
|
|
376
|
-
if (!contract)
|
|
377
|
-
throw new Error(`metric contract is not registered: ${goal.id}`);
|
|
378
|
-
if (contract.source !== sample.source)
|
|
379
|
-
throw new Error("metric source does not match registered contract");
|
|
380
|
-
this.ledger.appendEvent({ streamId: `metric:${goal.id}`, ts: this.#now(), actor: "supervisor", type: "metric.sampled", data: sample });
|
|
381
|
-
const evaluation = evaluateMetric(contract, this.ledger.metricSamples(goal.id), this.#now());
|
|
382
|
-
this.ledger.appendEvent({ streamId: `metric:${goal.id}`, ts: this.#now(), actor: "supervisor", type: "metric.evaluated", data: evaluation });
|
|
383
|
-
if (evaluation.shouldWakeOwner)
|
|
384
|
-
this.#enqueueTrigger(goal.owner, `metric:${goal.id}:${sample.observedAt}`);
|
|
385
|
-
return evaluation;
|
|
386
|
-
}
|
|
387
|
-
async collectMetricNow(goalId) {
|
|
388
|
-
const registration = this.#metricCollectors.get(goalId);
|
|
389
|
-
if (!registration)
|
|
390
|
-
throw new Error(`metric collector is not registered: ${goalId}`);
|
|
391
|
-
registration.nextAt = this.clock.now().getTime() + registration.intervalMs;
|
|
392
|
-
const sample = await runJsonProcess(registration.spec, { goalId });
|
|
393
|
-
return this.recordMetric({ ...sample, goalId });
|
|
394
|
-
}
|
|
395
|
-
async reconcileAction(id) {
|
|
396
|
-
const action = this.#action(id);
|
|
397
|
-
if (action.status !== "unknown")
|
|
398
|
-
throw new Error("only unknown actions may be reconciled");
|
|
399
|
-
const connector = this.#requiredConnector(action.connector);
|
|
400
|
-
const capability = capabilityFor(connector.manifest, action.kind);
|
|
401
|
-
if (!capability || capability.query === "none")
|
|
402
|
-
throw new Error("connector cannot reconcile this action");
|
|
403
|
-
const result = await runConnector(connector, "query", action);
|
|
404
|
-
if (result.status === "pending")
|
|
405
|
-
return action;
|
|
406
|
-
return this.ledger.transitionAction(id, result.status, { reconciledAt: this.#now(), ...(result.externalRef ? { externalRef: result.externalRef } : {}) });
|
|
407
|
-
}
|
|
408
|
-
async retryUnknownAction(id) {
|
|
409
|
-
const action = this.#action(id);
|
|
410
|
-
if (action.status !== "unknown")
|
|
411
|
-
throw new Error("only unknown actions may be retried");
|
|
412
|
-
const connector = this.#requiredConnector(action.connector);
|
|
413
|
-
const capability = capabilityFor(connector.manifest, action.kind);
|
|
414
|
-
if (!capability?.nativeIdempotency || !capability.automaticRetry)
|
|
415
|
-
throw new Error("connector manifest forbids automatic retry");
|
|
416
|
-
return this.#dispatch(id);
|
|
417
|
-
}
|
|
418
|
-
async #dispatch(id) {
|
|
419
|
-
const current = this.#action(id);
|
|
420
|
-
const connector = this.#requiredConnector(current.connector);
|
|
421
|
-
const dispatching = this.ledger.transitionAction(id, "dispatching");
|
|
422
|
-
const result = await runConnector(connector, "dispatch", dispatching);
|
|
423
|
-
return this.ledger.transitionAction(id, result.status, result.externalRef ? { externalRef: result.externalRef } : {});
|
|
424
|
-
}
|
|
425
|
-
async #markAbnormal(wake, reason) {
|
|
426
|
-
const current = this.#wake(wake.id);
|
|
427
|
-
if (current.runnerPid)
|
|
428
|
-
await this.runner.terminateProcess(current.runnerPid);
|
|
429
|
-
this.ledger.appendEvent({ streamId: wakeStream(current.id), ts: this.#now(), actor: current.agent, type: "wake.abnormal_reason", data: { reason } });
|
|
430
|
-
const repairs = interruptedSessionEvents(this.ledger.eventsForWake(current.id), this.#now(), "supervisor");
|
|
431
|
-
if (repairs.length)
|
|
432
|
-
this.ledger.appendEvents(repairs);
|
|
433
|
-
if (["leased", "running", "queued"].includes(current.status)) {
|
|
434
|
-
this.ledger.finishWake(current.id, "abnormal", this.#now());
|
|
435
|
-
if (current.attempt < this.#retryPolicy.maxAttempts) {
|
|
436
|
-
const delay = this.#retryPolicy.baseDelayMs * 2 ** Math.max(0, current.attempt - 1);
|
|
437
|
-
const schedule = { id: `retry:${current.id}`, agent: current.agent, nextWakeAt: new Date(this.clock.now().getTime() + delay).toISOString(), reason: `recovery:${current.id}`, setBy: "supervisor" };
|
|
438
|
-
this.ledger.putSchedule(schedule, "supervisor", current.id);
|
|
439
|
-
}
|
|
440
|
-
else if (current.agent !== "ceo" && this.#hasActiveRoot()) {
|
|
441
|
-
this.#enqueueTrigger("ceo", `child-retry-exhausted:${current.id}`);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
#enqueueSchedule(schedule) {
|
|
446
|
-
return this.#enqueueTrigger(schedule.agent, `${schedule.id}@${schedule.nextWakeAt}`);
|
|
447
|
-
}
|
|
448
|
-
#enqueueTrigger(agent, triggerRef) {
|
|
449
|
-
const exact = this.ledger.wakeByTrigger(agent, triggerRef);
|
|
450
|
-
if (exact)
|
|
451
|
-
return exact;
|
|
452
|
-
const ownsLiveGoal = this.ledger.goalsForOwner(agent).some((goal) => goal.phase === "active" || goal.phase === "blocked");
|
|
453
|
-
const ceoInterrupt = agent === "ceo" && (triggerRef.startsWith("interaction:") || triggerRef.startsWith("mail:") || triggerRef.startsWith("child-"));
|
|
454
|
-
if (!ownsLiveGoal && !ceoInterrupt)
|
|
455
|
-
return null;
|
|
456
|
-
const queued = triggerRef.startsWith("interaction:") ? null : this.ledger.queuedWakeForAgent(agent);
|
|
457
|
-
if (queued) {
|
|
458
|
-
this.ledger.appendEvent({ streamId: wakeStream(queued.id), ts: this.#now(), actor: "supervisor", type: "wake.trigger_coalesced", data: { wakeId: queued.id, triggerRef } });
|
|
459
|
-
return queued;
|
|
460
|
-
}
|
|
461
|
-
const wake = { id: randomUUID(), agent, triggerRef, status: "queued", leaseUntil: null, attempt: 0, startedAt: null, endedAt: null, enqueuedSeq: 0, leaseToken: null, runnerPid: null };
|
|
462
|
-
const result = this.ledger.enqueueWake(wake, "supervisor");
|
|
463
|
-
if (result.created)
|
|
464
|
-
return this.#wake(wake.id);
|
|
465
|
-
const existing = this.ledger.wakeByTrigger(agent, triggerRef);
|
|
466
|
-
if (!existing)
|
|
467
|
-
throw new Error("deduplicated wake is missing");
|
|
468
|
-
return existing;
|
|
469
|
-
}
|
|
470
|
-
#suppressQueuedWake(agent, reason) {
|
|
471
|
-
const wake = this.ledger.queuedWakeForAgent(agent);
|
|
472
|
-
if (!wake)
|
|
473
|
-
return;
|
|
474
|
-
this.ledger.appendEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: "supervisor", type: "wake.suppressed", data: { reason } });
|
|
475
|
-
this.ledger.finishWake(wake.id, "abnormal", this.#now());
|
|
476
|
-
}
|
|
477
|
-
#hasActiveRoot() { return this.ledger.goals().some((goal) => goal.parentId === null && goal.owner === "ceo" && goal.phase === "active"); }
|
|
478
|
-
#role(agent) { return this.#profiles.get(agent)?.role ?? "child"; }
|
|
479
|
-
#validateCeoHandoff(wake, output) {
|
|
480
|
-
if (this.#role(wake.agent) !== "ceo")
|
|
481
|
-
return;
|
|
482
|
-
const idle = this.teamList().filter((member) => member.agent !== "ceo" && member.status === "idle_unplanned");
|
|
483
|
-
const missingObservationGoalIds = this.ledger.goals().filter((goal) => goal.parentId !== null && goal.phase !== "complete" && goal.observationMethod === null).map((goal) => goal.id);
|
|
484
|
-
const activeRoot = this.#hasActiveRoot();
|
|
485
|
-
const hasChildMotion = this.teamList().some((member) => member.agent !== "ceo" && !["idle_unplanned", "retired"].includes(member.status));
|
|
486
|
-
const hasReview = Boolean(output.nextWakeAt);
|
|
487
|
-
const hasBlocker = handoffBlocked(output.handoff);
|
|
488
|
-
const asksHuman = output.mail.some((mail) => mail.to === "human" && (mail.level === "decision" || mail.level === "emergency"))
|
|
489
|
-
|| this.ledger.unreadMail("human").some((mail) => mail.from === "ceo" && (mail.level === "decision" || mail.level === "emergency"));
|
|
490
|
-
if (idle.length === 0 && missingObservationGoalIds.length === 0 && (!activeRoot || hasChildMotion || hasReview || hasBlocker || asksHuman))
|
|
491
|
-
return;
|
|
492
|
-
const violation = {
|
|
493
|
-
idleAgents: idle.map((member) => member.agent),
|
|
494
|
-
missingObservationGoalIds,
|
|
495
|
-
reason: missingObservationGoalIds.length ? "active child goal has no observation method" : idle.length ? "active child goal has no liveness route" : "active root has no motion, review, wait, blocker, or human request",
|
|
496
|
-
};
|
|
497
|
-
this.ledger.appendEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: "supervisor", type: "ceo.motion_invalid", data: violation });
|
|
498
|
-
throw new Error(`CEO motion invalid: ${violation.reason}${violation.idleAgents.length ? ` (${violation.idleAgents.join(", ")})` : ""}`);
|
|
499
|
-
}
|
|
500
|
-
#validateGoalTurnRecord(wake, turn, revisionAtStart) {
|
|
501
|
-
const binding = turn.goalBinding;
|
|
502
|
-
const goal = this.#goal(binding.goalId);
|
|
503
|
-
if (goal.revision !== binding.goalRevision)
|
|
504
|
-
throw new Error("Goal revision changed during the Turn");
|
|
505
|
-
const record = this.ledger.workRecord(goal.id);
|
|
506
|
-
if (!record || record.recordRevision <= revisionAtStart || record.updatedInTurn !== wake.id || record.goalRevision !== goal.revision)
|
|
507
|
-
throw new Error("Goal-bound Turn must update its Work Record before handoff");
|
|
508
|
-
}
|
|
509
|
-
#goalWakeOutput(output, binding) {
|
|
510
|
-
const record = this.ledger.workRecord(binding.goalId);
|
|
511
|
-
if (!record)
|
|
512
|
-
throw new Error("Goal Work Record is missing");
|
|
513
|
-
const legacy = "goalId" in output.handoff ? null : output.handoff;
|
|
514
|
-
const handoff = {
|
|
515
|
-
goalId: binding.goalId,
|
|
516
|
-
goalRevision: binding.goalRevision,
|
|
517
|
-
recordRevision: record.recordRevision,
|
|
518
|
-
outcome: "goalId" in output.handoff ? output.handoff.outcome : legacy?.blocker ? "blocked" : legacy?.material ? "completion_proposed" : "progress",
|
|
519
|
-
evidence: "goalId" in output.handoff && output.handoff.evidence.length ? output.handoff.evidence : record.evidence,
|
|
520
|
-
};
|
|
521
|
-
return { ...output, handoff };
|
|
522
|
-
}
|
|
523
|
-
#turnContext(wake) {
|
|
524
|
-
if (interactionMailId(wake))
|
|
525
|
-
return { source: { kind: "human" } };
|
|
526
|
-
const goals = this.ledger.goalsForOwner(wake.agent).filter((goal) => goal.phase === "active");
|
|
527
|
-
const goal = goals.find((candidate) => candidate.parentId === null) ?? goals[0];
|
|
528
|
-
const humanMail = wake.triggerRef.startsWith("mail:") && this.ledger.mailbox().some((mail) => mail.id === wake.triggerRef.slice(5) && mail.from === "human");
|
|
529
|
-
return { source: humanMail ? { kind: "human" } : { kind: "goal_driver", round: goal ? (this.ledger.workRecord(goal.id)?.recordRevision ?? 0) + 1 : 1 }, ...(goal ? { goalBinding: { goalId: goal.id, goalRevision: goal.revision } } : {}) };
|
|
530
|
-
}
|
|
531
|
-
#loadContext(wake, turn) {
|
|
532
|
-
const profile = this.#profiles.get(wake.agent) ?? { agent: wake.agent, role: "child" };
|
|
533
|
-
const role = profile.role;
|
|
534
|
-
const capabilities = profile.capabilities ?? defaultCapabilities(role);
|
|
535
|
-
const runnerProfile = this.#runnerProfiles.get(profile.runnerProfile ?? "default");
|
|
536
|
-
if (!turn.goalBinding && interactionMailId(wake)) {
|
|
537
|
-
const mail = this.ledger.mailbox().find((candidate) => candidate.id === interactionMailId(wake));
|
|
538
|
-
const body = mail?.body && typeof mail.body === "object" && !Array.isArray(mail.body) ? mail.body : {};
|
|
539
|
-
const message = typeof body.message === "string" ? body.message : "";
|
|
540
|
-
const mailEvent = this.ledger.eventsSince(0, ["mail.put"]).findLast((event) => event.data.snapshot?.id === mail?.id);
|
|
541
|
-
const recent = this.ledger.eventsSince(0, ["interaction.completed"]).filter((event) => event.actor === wake.agent).slice(-8).map((event) => {
|
|
542
|
-
const data = event.data;
|
|
543
|
-
const prior = this.ledger.mailbox().find((candidate) => candidate.id === data.mailId);
|
|
544
|
-
const priorBody = prior?.body && typeof prior.body === "object" && !Array.isArray(prior.body) ? prior.body : {};
|
|
545
|
-
return `Human: ${typeof priorBody.message === "string" ? priorBody.message : ""}\nAssistant: ${data.response?.content ?? ""}`;
|
|
546
|
-
});
|
|
547
|
-
return {
|
|
548
|
-
text: [...(recent.length ? [`# Recent conversation\n\n${recent.join("\n\n")}`] : []), `# Human message\n\n${message}`].join("\n\n"),
|
|
549
|
-
sourceSeqs: mailEvent ? [mailEvent.seq] : [],
|
|
550
|
-
capabilities,
|
|
551
|
-
systemPrompt: profile.systemPrompt ?? "You are Goah's primary Agent. Respond naturally to the Human, use tools when useful, and keep the final answer concise. Do not create or operate a Goal unless the Human expresses durable Goal intent.",
|
|
552
|
-
...(runnerProfile ? { runnerProfile } : {}),
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
const goals = role === "ceo" ? this.ledger.goals() : this.ledger.goalsForOwner(wake.agent);
|
|
556
|
-
const mail = this.ledger.unreadMail(wake.agent);
|
|
557
|
-
const handoff = this.ledger.lastEvent(wake.agent, "handoff.recorded");
|
|
558
|
-
const recoveryId = wake.triggerRef.startsWith("recovery:")
|
|
559
|
-
? wake.triggerRef.slice("recovery:".length)
|
|
560
|
-
: wake.triggerRef.startsWith("retry:") ? wake.triggerRef.slice("retry:".length).split("@")[0] : null;
|
|
561
|
-
const recoveryEvents = recoveryId ? selectRecoveryEvents(this.ledger.eventsForWake(recoveryId)) : [];
|
|
562
|
-
const teamHandoffs = role === "ceo"
|
|
563
|
-
? [...this.ledger.eventsSince(0, ["handoff.recorded"])].reverse().filter((event, index, all) => all.findIndex((candidate) => candidate.actor === event.actor) === index)
|
|
564
|
-
: [];
|
|
565
|
-
const actions = this.ledger.actions().filter((action) => action.agent === wake.agent && (action.status === "unknown" || Boolean(action.auditAdvice && !action.adviceAcked)));
|
|
566
|
-
const revisionWarnings = goals.flatMap((goal) => this.#goalRevisionWarning(goal));
|
|
567
|
-
const workingMemory = selectWorkingMemory(this.ledger.readStream(memoryStream(wake.agent)), this.#memoryTailChars);
|
|
568
|
-
const active = composeActiveContext({ role, capabilities, systemPrompt: profile.systemPrompt ?? defaultRolePrompt(role), wake, goals, mail, actions, lastHandoff: handoff, teamHandoffs, team: role === "ceo" ? this.teamList() : [], revisionWarnings, recoveryEvents, workingMemory });
|
|
569
|
-
const records = this.ledger.workRecords();
|
|
570
|
-
const currentRecord = turn.goalBinding ? this.ledger.workRecord(turn.goalBinding.goalId) : null;
|
|
571
|
-
const currentGoal = turn.goalBinding ? this.ledger.goal(turn.goalBinding.goalId) : null;
|
|
572
|
-
const parentRecord = currentGoal?.parentId ? this.ledger.workRecord(currentGoal.parentId) : null;
|
|
573
|
-
const recordIndex = records.map((record) => `- /goals/${record.goalId}.md · r${record.recordRevision} · ${this.ledger.goal(record.goalId)?.owner ?? "unknown"} · ${this.ledger.goal(record.goalId)?.phase ?? "unknown"}`);
|
|
574
|
-
const workText = [`# Shared Work Record Index\n\n${recordIndex.join("\n")}`, ...(currentRecord ? [`# Your Work Record\n\n${currentRecord.content}`] : []), ...(parentRecord ? [`# Parent Work Record\n\n${parentRecord.content}`] : [])].join("\n\n");
|
|
575
|
-
return { ...active, text: `${active.text}\n\n${workText}`, sourceSeqs: [...new Set([...active.sourceSeqs, ...records.map((record) => record.lastEventSeq)])].sort((a, b) => a - b), workRecord: currentRecord, sharedWorkRecords: records, ...(runnerProfile ? { runnerProfile } : {}) };
|
|
576
|
-
}
|
|
577
|
-
#requiredConnector(name) { const value = this.#connectors.get(name); if (!value)
|
|
578
|
-
throw new Error(`connector not registered: ${name}`); return value; }
|
|
579
|
-
#wake(id) { const value = this.ledger.wake(id); if (!value)
|
|
580
|
-
throw new Error(`wake not found: ${id}`); return value; }
|
|
581
|
-
#action(id) { const value = this.ledger.action(id); if (!value)
|
|
582
|
-
throw new Error(`action not found: ${id}`); return value; }
|
|
583
|
-
#goal(id) { const value = this.ledger.goal(id); if (!value)
|
|
584
|
-
throw new Error(`goal not found: ${id}`); return value; }
|
|
585
|
-
#goalRevisionWarning(goal) {
|
|
586
|
-
if (goal.parentId === null || goal.phase === "complete")
|
|
587
|
-
return [];
|
|
588
|
-
let root = this.#goal(goal.parentId);
|
|
589
|
-
while (root.parentId !== null)
|
|
590
|
-
root = this.#goal(root.parentId);
|
|
591
|
-
const rootSeq = this.ledger.readStream(goalStream(root.id)).filter((event) => event.type === "goal.put").at(-1)?.seq ?? 0;
|
|
592
|
-
const goalSeq = this.ledger.readStream(goalStream(goal.id)).filter((event) => event.type === "goal.put").at(-1)?.seq ?? 0;
|
|
593
|
-
return rootSeq > goalSeq ? [`Goal ${goal.id} predates root revision ${root.revision}; CEO must revise its objective/observation method before new gated actions.`] : [];
|
|
594
|
-
}
|
|
595
|
-
#assertAgentGoalsCurrent(agent) {
|
|
596
|
-
const warnings = this.ledger.goalsForOwner(agent).flatMap((goal) => this.#goalRevisionWarning(goal));
|
|
597
|
-
if (warnings.length)
|
|
598
|
-
throw new Error(warnings.join(" "));
|
|
599
|
-
}
|
|
600
|
-
#now() { return this.clock.now().toISOString(); }
|
|
601
|
-
async #agentRpc(wake, turn, leaseToken, method, params) {
|
|
602
|
-
const current = this.ledger.wake(wake.id);
|
|
603
|
-
if (!current || current.status !== "running" || current.leaseToken !== leaseToken || !current.leaseUntil || current.leaseUntil < this.#now())
|
|
604
|
-
throw new Error("stale runner RPC rejected");
|
|
605
|
-
const profile = this.#profiles.get(wake.agent) ?? { agent: wake.agent, role: "child" };
|
|
606
|
-
const allowed = new Set(profile.capabilities ?? defaultCapabilities(profile.role));
|
|
607
|
-
if (!allowed.has(method))
|
|
608
|
-
throw new Error(`${profile.role} agent is not allowed to call ${method}`);
|
|
609
|
-
if (!turn.goalBinding && goalBoundCapability(method))
|
|
610
|
-
throw new Error(`${method} requires a Goal-bound Turn`);
|
|
611
|
-
this.ledger.appendRunnerEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: wake.agent, type: `rpc.${method}`, data: params }, leaseToken);
|
|
612
|
-
const input = asRecord(params);
|
|
613
|
-
if (method === "ledger.search")
|
|
614
|
-
return this.ledger.searchEvents(String(input.query), Number(input.limit ?? 20));
|
|
615
|
-
if (method === "team.list")
|
|
616
|
-
return this.teamList();
|
|
617
|
-
if (method === "goal.get")
|
|
618
|
-
return (turn.goalBinding ? this.ledger.goal(turn.goalBinding.goalId) : null);
|
|
619
|
-
if (method === "goal.create") {
|
|
620
|
-
if (turn.source.kind !== "human" || turn.goalBinding)
|
|
621
|
-
throw new Error("a Root Goal can only be created from an unbound Human Turn");
|
|
622
|
-
if (wake.agent !== "ceo")
|
|
623
|
-
throw new Error("only CEO may translate Human intent into a Root Goal");
|
|
624
|
-
const goal = this.createRootGoal(String(input.objective), typeof input.id === "string" && input.id.trim() ? input.id : undefined);
|
|
625
|
-
turn.goalBinding = { goalId: goal.id, goalRevision: goal.revision };
|
|
626
|
-
this.ledger.appendRunnerEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: wake.agent, type: "turn.goal_bound", data: { goalId: goal.id, goalRevision: goal.revision, authority: "human" } }, leaseToken);
|
|
627
|
-
return { goal, goalBinding: turn.goalBinding, instruction: "This Turn is now Goal-bound. Update its Work Record and finish with handoff." };
|
|
628
|
-
}
|
|
629
|
-
if (method === "goal.work") {
|
|
630
|
-
if (turn.source.kind !== "human" || turn.goalBinding)
|
|
631
|
-
throw new Error("work_on_goal requires an unbound Human Turn");
|
|
632
|
-
const goal = this.#goal(String(input.goalId));
|
|
633
|
-
if (goal.owner !== wake.agent || goal.phase !== "active")
|
|
634
|
-
throw new Error("work_on_goal requires an active Goal owned by this Agent");
|
|
635
|
-
turn.goalBinding = { goalId: goal.id, goalRevision: goal.revision };
|
|
636
|
-
this.ledger.appendRunnerEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: wake.agent, type: "turn.goal_bound", data: { goalId: goal.id, goalRevision: goal.revision, authority: "human" } }, leaseToken);
|
|
637
|
-
return { goal, goalBinding: turn.goalBinding, instruction: "This Turn is now Goal-bound. Update its Work Record and finish with handoff." };
|
|
638
|
-
}
|
|
639
|
-
if (method === "work_record.list")
|
|
640
|
-
return this.ledger.workRecords();
|
|
641
|
-
if (method === "work_record.read")
|
|
642
|
-
return this.ledger.workRecord(String(input.goalId ?? turn.goalBinding?.goalId ?? ""));
|
|
643
|
-
if (method === "work_record.history")
|
|
644
|
-
return this.ledger.workRecordHistory(String(input.goalId ?? turn.goalBinding?.goalId ?? ""));
|
|
645
|
-
if (method === "work_record.diff")
|
|
646
|
-
return this.ledger.workRecordDiff(String(input.goalId ?? turn.goalBinding?.goalId ?? ""), Number(input.fromRevision), Number(input.toRevision));
|
|
647
|
-
if (method === "work_record.search")
|
|
648
|
-
return this.ledger.searchWorkRecords(String(input.query), Number(input.limit ?? 20));
|
|
649
|
-
if (method === "work_record.update") {
|
|
650
|
-
if (!turn.goalBinding)
|
|
651
|
-
throw new Error("work_record.update requires a Goal-bound Turn");
|
|
652
|
-
return this.ledger.updateWorkRecord({ goalId: turn.goalBinding.goalId, goalRevision: turn.goalBinding.goalRevision, expectedRevision: Number(input.expectedRevision), content: String(input.content), reason: String(input.reason), evidence: numberArray(input.evidence), turnId: wake.id, wakeId: wake.id }, wake.agent);
|
|
653
|
-
}
|
|
654
|
-
if (method === "goal.delegate")
|
|
655
|
-
return this.delegate({
|
|
656
|
-
id: String(input.id),
|
|
657
|
-
parentGoalId: String(input.parentGoalId),
|
|
658
|
-
childGoal: asChildGoal(input.childGoal),
|
|
659
|
-
brief: (input.brief ?? null),
|
|
660
|
-
reason: String(input.reason),
|
|
661
|
-
evidence: numberArray(input.evidence),
|
|
662
|
-
}, wake.agent, wake.id);
|
|
663
|
-
if (method === "goal.reassign")
|
|
664
|
-
return this.reassignGoal({
|
|
665
|
-
id: String(input.id),
|
|
666
|
-
goalId: String(input.goalId),
|
|
667
|
-
newOwner: String(input.newOwner),
|
|
668
|
-
brief: (input.brief ?? null),
|
|
669
|
-
reason: String(input.reason),
|
|
670
|
-
evidence: numberArray(input.evidence),
|
|
671
|
-
}, wake.agent, wake.id);
|
|
672
|
-
if (method === "goal.revise")
|
|
673
|
-
return this.reviseChildGoal(String(input.goalId), String(input.objective), String(input.observationMethod), String(input.verificationMethod), wake.agent, String(input.reason), numberArray(input.evidence), wake.id);
|
|
674
|
-
if (method === "goal.pause" || method === "goal.resume") {
|
|
675
|
-
const goalId = String(input.goalId);
|
|
676
|
-
const currentGoal = this.#goal(goalId);
|
|
677
|
-
const directHumanRoot = !turn.goalBinding && turn.source.kind === "human" && currentGoal.parentId === null;
|
|
678
|
-
if (!turn.goalBinding && !directHumanRoot)
|
|
679
|
-
throw new Error(`${method} requires a Goal-bound Turn`);
|
|
680
|
-
const goal = this.transitionGoal(goalId, method === "goal.pause" ? "paused" : "active", directHumanRoot ? "human" : wake.agent);
|
|
681
|
-
if (method === "goal.resume" && directHumanRoot) {
|
|
682
|
-
turn.goalBinding = { goalId: goal.id, goalRevision: goal.revision };
|
|
683
|
-
this.ledger.appendRunnerEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: wake.agent, type: "turn.goal_bound", data: { goalId: goal.id, goalRevision: goal.revision, authority: "human" } }, leaseToken);
|
|
684
|
-
return { goal, goalBinding: turn.goalBinding, instruction: "This Turn is now Goal-bound. Update its Work Record and finish with handoff." };
|
|
685
|
-
}
|
|
686
|
-
return goal;
|
|
687
|
-
}
|
|
688
|
-
if (method === "goal.complete") {
|
|
689
|
-
const currentGoal = this.#goal(String(input.goalId));
|
|
690
|
-
const directHumanRoot = !turn.goalBinding && turn.source.kind === "human" && currentGoal.parentId === null;
|
|
691
|
-
if (!turn.goalBinding && !directHumanRoot)
|
|
692
|
-
throw new Error("goal.complete requires a Goal-bound Turn");
|
|
693
|
-
return this.completeGoal({ goalId: currentGoal.id, revision: Number(input.revision), reason: String(input.reason), evidence: numberArray(input.evidence) }, directHumanRoot ? "human" : wake.agent, wake.id);
|
|
694
|
-
}
|
|
695
|
-
if (method === "human.request") {
|
|
696
|
-
const evidence = numberArray(input.evidence);
|
|
697
|
-
for (const seq of evidence)
|
|
698
|
-
if (!this.ledger.eventsSince(seq - 1).some((event) => event.seq === seq))
|
|
699
|
-
throw new Error(`evidence event does not exist: ${seq}`);
|
|
700
|
-
const mail = { id: randomUUID(), to: "human", from: wake.agent, level: "decision", body: { type: String(input.type ?? "decision"), message: input.message ?? null, evidence }, readAt: null };
|
|
701
|
-
this.ledger.putMail(mail, wake.agent, wake.id);
|
|
702
|
-
this.ledger.appendRunnerEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: wake.agent, type: "ceo.human_requested", data: mail.body }, leaseToken);
|
|
703
|
-
return mail;
|
|
704
|
-
}
|
|
705
|
-
if (method === "mail.send") {
|
|
706
|
-
const mail = { id: randomUUID(), to: String(input.to), from: wake.agent, level: String(input.level), body: (input.body ?? null), readAt: null };
|
|
707
|
-
this.ledger.putMail(mail, wake.agent, wake.id);
|
|
708
|
-
return mail;
|
|
709
|
-
}
|
|
710
|
-
if (method === "memory.append") {
|
|
711
|
-
const note = String(input.note ?? "").trim();
|
|
712
|
-
if (!note)
|
|
713
|
-
throw new Error("memory note cannot be empty");
|
|
714
|
-
const record = this.ledger.appendEvent({ streamId: memoryStream(wake.agent), ts: this.#now(), actor: wake.agent, type: "memory.appended", data: { note, wakeId: wake.id } });
|
|
715
|
-
return { seq: record.seq, streamSeq: record.streamSeq };
|
|
716
|
-
}
|
|
717
|
-
if (method === "schedule.set")
|
|
718
|
-
return (this.planWake(wake.agent, String(input.at), String(input.reason), wake.agent) ?? { scheduled: true });
|
|
719
|
-
if (method === "audit.ack")
|
|
720
|
-
return this.ackAuditAdvice(String(input.actionId), wake.agent);
|
|
721
|
-
if (method === "audit.write")
|
|
722
|
-
return this.putAuditAdvice(String(input.actionId), { by: wake.agent, body: (input.body ?? null), evidence: numberArray(input.evidence) }, wake.id);
|
|
723
|
-
if (method === "goal.put") {
|
|
724
|
-
this.ledger.putGoal(input.goal, wake.agent, wake.id);
|
|
725
|
-
return input.goal;
|
|
726
|
-
}
|
|
727
|
-
const action = await this.submitAction({
|
|
728
|
-
id: String(input.id), agent: wake.agent, kind: String(input.kind), payload: (input.payload ?? null), reason: String(input.reason), evidence: numberArray(input.evidence), auditAdvice: null, adviceAcked: false,
|
|
729
|
-
}, String(input.connector), wake.id);
|
|
730
|
-
return action;
|
|
731
|
-
}
|
|
732
|
-
async #collectMetrics() {
|
|
733
|
-
const now = this.clock.now().getTime();
|
|
734
|
-
for (const registration of this.#metricCollectors.values()) {
|
|
735
|
-
if (registration.nextAt > now)
|
|
736
|
-
continue;
|
|
737
|
-
registration.nextAt = now + registration.intervalMs;
|
|
738
|
-
const sample = await runJsonProcess(registration.spec, { goalId: registration.goalId });
|
|
739
|
-
this.recordMetric({ ...sample, goalId: registration.goalId });
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
#scheduleMetricAlerts() {
|
|
743
|
-
for (const [goalId, contract] of this.#metricContracts) {
|
|
744
|
-
const goal = this.ledger.goal(goalId);
|
|
745
|
-
if (!goal)
|
|
746
|
-
continue;
|
|
747
|
-
const samples = this.ledger.metricSamples(goal.id);
|
|
748
|
-
const evaluation = evaluateMetric(contract, samples, this.#now());
|
|
749
|
-
if (evaluation.shouldWakeOwner)
|
|
750
|
-
this.#enqueueTrigger(goal.owner, `metric:${goal.id}:${evaluation.status}:${samples.at(-1)?.observedAt ?? "none"}`);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
/** The one mechanical floor: total ledger silence. Any event from anyone resets the clock; stall policy is the CEO's business, not the supervisor's. */
|
|
754
|
-
#checkSystemSilence() {
|
|
755
|
-
if (!this.#silence)
|
|
756
|
-
return;
|
|
757
|
-
const last = this.ledger.latestEvent();
|
|
758
|
-
if (!last)
|
|
759
|
-
return;
|
|
760
|
-
const silentMs = this.clock.now().getTime() - Date.parse(last.ts);
|
|
761
|
-
if (silentMs <= this.#silence.maxSilentMs)
|
|
762
|
-
return;
|
|
763
|
-
const fact = { silentMs, lastEventSeq: last.seq, lastEventAt: last.ts };
|
|
764
|
-
this.ledger.appendEvent({ streamId: controlStream("watchdog"), ts: this.#now(), actor: "supervisor", type: "watchdog.system_silence", data: { ...fact, notify: this.#silence.notify } });
|
|
765
|
-
this.ledger.putMail({ id: `silence:${last.seq}`, to: this.#silence.notify, from: "supervisor", level: "decision", readAt: null, body: { type: "system_silence", ...fact, note: "No ledger events system-wide within the silence window. Confirm this is expected and hand off." } }, "supervisor");
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
export async function runSupervisorDaemon(supervisor, options = {}) {
|
|
769
|
-
const pollMs = options.pollMs ?? 1_000;
|
|
770
|
-
await supervisor.recover();
|
|
771
|
-
while (!options.signal?.aborted) {
|
|
772
|
-
try {
|
|
773
|
-
await supervisor.runAvailable(options.concurrency ?? 4);
|
|
774
|
-
}
|
|
775
|
-
catch (error) {
|
|
776
|
-
options.onError?.(error);
|
|
777
|
-
}
|
|
778
|
-
await new Promise((resolve) => {
|
|
779
|
-
const timer = setTimeout(resolve, pollMs);
|
|
780
|
-
options.signal?.addEventListener("abort", () => { clearTimeout(timer); resolve(); }, { once: true });
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
export function deriveTeam(ledger, now = new Date().toISOString()) {
|
|
785
|
-
const goals = ledger.goals();
|
|
786
|
-
const wakes = ledger.wakes();
|
|
787
|
-
const schedules = ledger.schedules();
|
|
788
|
-
const handoffs = ledger.eventsSince(0, ["handoff.recorded"]);
|
|
789
|
-
const owners = [...new Set(goals.map((goal) => goal.owner))].sort();
|
|
790
|
-
return owners.map((agent) => {
|
|
791
|
-
const owned = goals.filter((goal) => goal.owner === agent);
|
|
792
|
-
const live = owned.filter((goal) => goal.phase !== "complete");
|
|
793
|
-
const agentWakes = wakes.filter((wake) => wake.agent === agent).sort((a, b) => b.enqueuedSeq - a.enqueuedSeq);
|
|
794
|
-
const activeWake = agentWakes.find((wake) => ["running", "leased", "queued"].includes(wake.status));
|
|
795
|
-
const nextWakeAt = schedules.filter((schedule) => schedule.agent === agent && schedule.nextWakeAt > now).map((schedule) => schedule.nextWakeAt).sort()[0] ?? null;
|
|
796
|
-
const lastHandoff = [...handoffs].reverse().find((event) => event.actor === agent) ?? null;
|
|
797
|
-
const blocker = lastHandoff && typeof lastHandoff.data === "object" && lastHandoff.data !== null && !Array.isArray(lastHandoff.data)
|
|
798
|
-
? (lastHandoff.data.blocker ?? (lastHandoff.data.outcome === "blocked" ? "blocked" : null))
|
|
799
|
-
: null;
|
|
800
|
-
let status;
|
|
801
|
-
if (live.length === 0)
|
|
802
|
-
status = "retired";
|
|
803
|
-
else if (activeWake?.status === "running")
|
|
804
|
-
status = "running";
|
|
805
|
-
else if (activeWake)
|
|
806
|
-
status = "queued";
|
|
807
|
-
else if (nextWakeAt)
|
|
808
|
-
status = "scheduled";
|
|
809
|
-
else if (live.every((goal) => goal.phase === "blocked"))
|
|
810
|
-
status = "blocked";
|
|
811
|
-
else if (live.every((goal) => goal.phase === "paused") || typeof blocker === "string" && blocker.length > 0)
|
|
812
|
-
status = "waiting";
|
|
813
|
-
else
|
|
814
|
-
status = "idle_unplanned";
|
|
815
|
-
return { agent, goalIds: owned.map((goal) => goal.id), status, lastHandoffSeq: lastHandoff?.seq ?? null, lastWakeStatus: agentWakes[0]?.status ?? null, nextWakeAt };
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
export function renderDashboard(ledger) {
|
|
819
|
-
const rows = (values) => values.map((value) => `<tr><td><pre>${escapeHtml(JSON.stringify(value, null, 2))}</pre></td></tr>`).join("");
|
|
820
|
-
return `<!doctype html><html><head><meta charset="utf-8"><title>goah status</title><style>body{font:14px ui-monospace;margin:32px;background:#101418;color:#dce3e4}section{margin:32px 0}pre{white-space:pre-wrap;border:1px solid #334;padding:12px}</style></head><body><h1>goah</h1><p>seq ${ledger.events().at(-1)?.seq ?? 0}</p><section><h2>Team</h2><table>${rows(deriveTeam(ledger))}</table></section><section><h2>Goals</h2><table>${rows(ledger.goals())}</table></section><section><h2>Wakes</h2><table>${rows(ledger.wakes())}</table></section><section><h2>Actions</h2><table>${rows(ledger.actions())}</table></section><section><h2>Mailbox</h2><table>${rows(ledger.mailbox())}</table></section></body></html>`;
|
|
821
|
-
}
|
|
822
|
-
async function runConnector(spec, operation, action) {
|
|
823
|
-
return runJsonProcess(spec, { operation, action });
|
|
824
|
-
}
|
|
825
|
-
async function runJsonProcess(spec, input) {
|
|
826
|
-
const child = spawn(spec.command, spec.args, { detached: process.platform !== "win32", env: minimalEnvironment(spec.env), stdio: ["pipe", "pipe", "pipe"] });
|
|
827
|
-
let stdout = "";
|
|
828
|
-
let stderr = "";
|
|
829
|
-
child.stdout.on("data", (chunk) => { stdout += chunk.toString(); });
|
|
830
|
-
child.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
|
|
831
|
-
child.stdin.end(`${JSON.stringify(input)}\n`);
|
|
832
|
-
const timeoutMs = spec.timeoutMs ?? 30_000;
|
|
833
|
-
let timer;
|
|
834
|
-
const exit = new Promise((resolve, reject) => {
|
|
835
|
-
child.once("error", reject);
|
|
836
|
-
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
837
|
-
});
|
|
838
|
-
timer = setTimeout(() => { void terminateChild(child, 500); }, timeoutMs);
|
|
839
|
-
const result = await exit;
|
|
840
|
-
clearTimeout(timer);
|
|
841
|
-
if (result.code !== 0)
|
|
842
|
-
throw new Error(stderr.trim() || `connector exited (${result.code ?? result.signal})`);
|
|
843
|
-
return JSON.parse(stdout.trim());
|
|
844
|
-
}
|
|
845
|
-
async function terminateChild(child, graceMs) {
|
|
846
|
-
if (!child.pid || child.exitCode !== null || child.signalCode !== null)
|
|
847
|
-
return;
|
|
848
|
-
signalPid(child.pid, "SIGTERM");
|
|
849
|
-
await new Promise((resolve) => setTimeout(resolve, graceMs));
|
|
850
|
-
if (child.exitCode === null && child.signalCode === null)
|
|
851
|
-
signalPid(child.pid, "SIGKILL");
|
|
852
|
-
}
|
|
853
|
-
function signalPid(pid, signal) { try {
|
|
854
|
-
process.kill(process.platform === "win32" ? pid : -pid, signal);
|
|
855
|
-
}
|
|
856
|
-
catch { } }
|
|
857
|
-
function minimalEnvironment(explicit = {}) {
|
|
858
|
-
const env = {};
|
|
859
|
-
for (const name of ["PATH", "TMPDIR", "TMP", "TEMP", "SYSTEMROOT"])
|
|
860
|
-
if (process.env[name] !== undefined)
|
|
861
|
-
env[name] = process.env[name];
|
|
862
|
-
return { ...env, ...explicit };
|
|
863
|
-
}
|
|
864
|
-
function escapeHtml(value) { return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); }
|
|
865
|
-
function interactionMailId(wake) { return wake.triggerRef.startsWith("interaction:") ? wake.triggerRef.slice("interaction:".length) : null; }
|
|
866
|
-
function handoffBlocked(handoff) { return "goalId" in handoff ? handoff.outcome === "blocked" : Boolean(handoff.blocker); }
|
|
867
|
-
function handoffTriggersCeo(handoff) { return "goalId" in handoff ? handoff.outcome === "blocked" || handoff.outcome === "completion_proposed" : Boolean(handoff.material || handoff.blocker); }
|
|
868
|
-
function goalBoundCapability(method) { return ["goal.delegate", "goal.reassign", "goal.revise", "goal.put", "work_record.update", "mail.send", "schedule.set", "human.request"].includes(method); }
|
|
869
|
-
function asRecord(value) { if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
870
|
-
throw new Error("RPC params must be an object"); return value; }
|
|
871
|
-
function numberArray(value) { if (!Array.isArray(value) || value.some((item) => typeof item !== "number"))
|
|
872
|
-
throw new Error("RPC evidence must be a number array"); return value; }
|
|
873
|
-
function asChildGoal(value) {
|
|
874
|
-
const input = asRecord(value ?? null);
|
|
875
|
-
return { id: String(input.id), objective: String(input.objective), observationMethod: String(input.observationMethod), verificationMethod: String(input.verificationMethod), owner: String(input.owner) };
|
|
876
|
-
}
|
|
877
|
-
function defaultCapabilities(role) {
|
|
878
|
-
if (role === "ceo")
|
|
879
|
-
return ["ledger.search", "mail.send", "schedule.set", "action.submit", "audit.ack", "team.list", "goal.get", "goal.create", "goal.work", "goal.delegate", "goal.reassign", "goal.revise", "goal.pause", "goal.resume", "goal.complete", "human.request", "work_record.list", "work_record.read", "work_record.history", "work_record.diff", "work_record.search", "work_record.update"];
|
|
880
|
-
if (role === "verifier")
|
|
881
|
-
return ["ledger.search", "mail.send", "memory.append", "audit.write"];
|
|
882
|
-
if (role === "audit")
|
|
883
|
-
return ["ledger.search", "mail.send", "memory.append", "audit.write"];
|
|
884
|
-
return ["ledger.search", "mail.send", "schedule.set", "action.submit", "audit.ack", "goal.get", "work_record.list", "work_record.read", "work_record.history", "work_record.diff", "work_record.search", "work_record.update"];
|
|
885
|
-
}
|
|
886
|
-
export * from "./verification.js";
|
|
887
|
-
export * from "./roles.js";
|
|
888
|
-
//# sourceMappingURL=index.js.map
|