@goah/cli 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/dist/LICENSE +201 -0
  2. package/dist/THIRD-PARTY-NOTICES.md +2319 -0
  3. package/dist/anthropic-messages-WBFOHES5.js +7214 -0
  4. package/dist/azure-openai-responses-NZFH3PO6.js +253 -0
  5. package/dist/chunk-25XSBFX6.js +639 -0
  6. package/dist/chunk-2QBQZGXF.js +110 -0
  7. package/dist/chunk-2ZP2NBW3.js +345 -0
  8. package/dist/chunk-4KA63VNZ.js +2 -0
  9. package/dist/chunk-4M6TXTT5.js +86 -0
  10. package/dist/chunk-4US7ZHT3.js +20 -0
  11. package/dist/chunk-6M3OOCKO.js +11809 -0
  12. package/dist/chunk-7AXAFDPG.js +17 -0
  13. package/dist/chunk-7E7V6OIU.js +920 -0
  14. package/dist/chunk-BXA47DTF.js +548 -0
  15. package/dist/chunk-D2DVAH37.js +25 -0
  16. package/dist/chunk-FSBUS2BI.js +26 -0
  17. package/dist/chunk-GKDRAHLI.js +4787 -0
  18. package/dist/chunk-H462EFJP.js +89 -0
  19. package/dist/chunk-IIJQ3DW4.js +35 -0
  20. package/dist/chunk-JQD5EWJY.js +34 -0
  21. package/dist/chunk-K26BVR6O.js +61 -0
  22. package/dist/chunk-KAO7WIXG.js +31 -0
  23. package/dist/chunk-KJ4HJCTK.js +1178 -0
  24. package/dist/chunk-LBKI23CC.js +9810 -0
  25. package/dist/chunk-LKBKZEHT.js +41 -0
  26. package/dist/chunk-MD7TLTXZ.js +18 -0
  27. package/dist/chunk-MPXK7QBL.js +868 -0
  28. package/dist/chunk-NRLGWZJC.js +11 -0
  29. package/dist/chunk-QBD6EJ2Z.js +1746 -0
  30. package/dist/chunk-R6I3VKME.js +132 -0
  31. package/dist/chunk-TJON22J4.js +87 -0
  32. package/dist/chunk-ULY36ME4.js +33097 -0
  33. package/dist/chunk-XALZLRYK.js +9221 -0
  34. package/dist/chunk-XPQKM3L7.js +57 -0
  35. package/dist/chunk-YH6YJ2IB.js +1773 -0
  36. package/dist/cli.d.ts +0 -2
  37. package/dist/cli.js +13716 -580
  38. package/dist/console/assets/{index-Rtc_B7cs.js → index-B1vGMXKr.js} +11 -11
  39. package/dist/console/assets/{index-Rtc_B7cs.js.map → index-B1vGMXKr.js.map} +1 -1
  40. package/dist/console/index.html +1 -1
  41. package/dist/dist-65MDQA4Q.js +1126 -0
  42. package/dist/dist-KNR2XL6J.js +39 -0
  43. package/dist/execution.d.ts +557 -2
  44. package/dist/execution.js +37 -2
  45. package/dist/faux-runner-worker.js +128 -0
  46. package/dist/google-generative-ai-YMHXZSFE.js +422 -0
  47. package/dist/google-vertex-X6DB3ZQ6.js +471 -0
  48. package/dist/index.d.ts +853 -25
  49. package/dist/index.js +84 -385
  50. package/dist/kernel.d.ts +39 -2
  51. package/dist/kernel.js +21 -2
  52. package/dist/mistral-conversations-TY3MMD6G.js +718 -0
  53. package/dist/multipart-parser-F6M4NJZU.js +373 -0
  54. package/dist/openai-codex-responses-BRDUMLN5.js +1374 -0
  55. package/dist/openai-completions-CX634XN3.js +1139 -0
  56. package/dist/openai-responses-HMCXJIHT.js +300 -0
  57. package/dist/openrouter-images-KTWET2H2.js +148 -0
  58. package/dist/pi-messages-XL5OEC4K.js +307 -0
  59. package/dist/pi-worker.js +671 -0
  60. package/dist/runner-pi.d.ts +342 -2
  61. package/dist/runner-pi.js +46 -2
  62. package/dist/runner-registry-GD4BYCQC.js +22 -0
  63. package/dist/sqlite.d.ts +446 -2
  64. package/dist/sqlite.js +15 -2
  65. package/dist/src-F2KBSV4T.js +1398 -0
  66. package/dist/supervisor.d.ts +625 -2
  67. package/dist/supervisor.js +39 -2
  68. package/dist/testkit.d.ts +564 -2
  69. package/dist/testkit.js +127 -2
  70. package/dist/transcript.d.ts +84 -2
  71. package/dist/transcript.js +25 -2
  72. package/dist/verification-worker.js +72 -0
  73. package/package.json +2 -26
  74. package/dist/cli.d.ts.map +0 -1
  75. package/dist/cli.js.map +0 -1
  76. package/dist/control.d.ts +0 -106
  77. package/dist/control.d.ts.map +0 -1
  78. package/dist/control.js +0 -242
  79. package/dist/control.js.map +0 -1
  80. package/dist/execution.d.ts.map +0 -1
  81. package/dist/execution.js.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js.map +0 -1
  84. package/dist/kernel.d.ts.map +0 -1
  85. package/dist/kernel.js.map +0 -1
  86. package/dist/live-config.d.ts +0 -10
  87. package/dist/live-config.d.ts.map +0 -1
  88. package/dist/live-config.js +0 -44
  89. package/dist/live-config.js.map +0 -1
  90. package/dist/prompt-queue.d.ts +0 -13
  91. package/dist/prompt-queue.d.ts.map +0 -1
  92. package/dist/prompt-queue.js +0 -35
  93. package/dist/prompt-queue.js.map +0 -1
  94. package/dist/runner-pi.d.ts.map +0 -1
  95. package/dist/runner-pi.js.map +0 -1
  96. package/dist/runner-registry.d.ts +0 -8
  97. package/dist/runner-registry.d.ts.map +0 -1
  98. package/dist/runner-registry.js +0 -12
  99. package/dist/runner-registry.js.map +0 -1
  100. package/dist/searchable-select.d.ts +0 -24
  101. package/dist/searchable-select.d.ts.map +0 -1
  102. package/dist/searchable-select.js +0 -61
  103. package/dist/searchable-select.js.map +0 -1
  104. package/dist/secret-input.d.ts +0 -8
  105. package/dist/secret-input.d.ts.map +0 -1
  106. package/dist/secret-input.js +0 -14
  107. package/dist/secret-input.js.map +0 -1
  108. package/dist/setup-wizard.d.ts +0 -22
  109. package/dist/setup-wizard.d.ts.map +0 -1
  110. package/dist/setup-wizard.js +0 -154
  111. package/dist/setup-wizard.js.map +0 -1
  112. package/dist/sqlite.d.ts.map +0 -1
  113. package/dist/sqlite.js.map +0 -1
  114. package/dist/supervisor.d.ts.map +0 -1
  115. package/dist/supervisor.js.map +0 -1
  116. package/dist/testkit.d.ts.map +0 -1
  117. package/dist/testkit.js.map +0 -1
  118. package/dist/thread-inspect.d.ts +0 -47
  119. package/dist/thread-inspect.d.ts.map +0 -1
  120. package/dist/thread-inspect.js +0 -78
  121. package/dist/thread-inspect.js.map +0 -1
  122. package/dist/transcript.d.ts.map +0 -1
  123. package/dist/transcript.js.map +0 -1
  124. package/dist/tui-theme.d.ts +0 -17
  125. package/dist/tui-theme.d.ts.map +0 -1
  126. package/dist/tui-theme.js +0 -19
  127. package/dist/tui-theme.js.map +0 -1
  128. package/dist/tui.d.ts +0 -24
  129. package/dist/tui.d.ts.map +0 -1
  130. package/dist/tui.js +0 -784
  131. package/dist/tui.js.map +0 -1
  132. package/dist/update.d.ts +0 -16
  133. package/dist/update.d.ts.map +0 -1
  134. package/dist/update.js +0 -67
  135. package/dist/update.js.map +0 -1
  136. package/dist/web-console.d.ts +0 -33
  137. package/dist/web-console.d.ts.map +0 -1
  138. package/dist/web-console.js +0 -242
  139. package/dist/web-console.js.map +0 -1
  140. package/dist/welcome.d.ts +0 -30
  141. package/dist/welcome.d.ts.map +0 -1
  142. package/dist/welcome.js +0 -69
  143. package/dist/welcome.js.map +0 -1
  144. package/node_modules/goah-ledger-contract/dist/execution.d.ts +0 -544
  145. package/node_modules/goah-ledger-contract/dist/execution.d.ts.map +0 -1
  146. package/node_modules/goah-ledger-contract/dist/execution.js +0 -52
  147. package/node_modules/goah-ledger-contract/dist/execution.js.map +0 -1
  148. package/node_modules/goah-ledger-contract/dist/index.d.ts +0 -4
  149. package/node_modules/goah-ledger-contract/dist/index.d.ts.map +0 -1
  150. package/node_modules/goah-ledger-contract/dist/index.js +0 -4
  151. package/node_modules/goah-ledger-contract/dist/index.js.map +0 -1
  152. package/node_modules/goah-ledger-contract/dist/kernel.d.ts +0 -37
  153. package/node_modules/goah-ledger-contract/dist/kernel.d.ts.map +0 -1
  154. package/node_modules/goah-ledger-contract/dist/kernel.js +0 -8
  155. package/node_modules/goah-ledger-contract/dist/kernel.js.map +0 -1
  156. package/node_modules/goah-ledger-contract/dist/transcript.d.ts +0 -63
  157. package/node_modules/goah-ledger-contract/dist/transcript.d.ts.map +0 -1
  158. package/node_modules/goah-ledger-contract/dist/transcript.js +0 -115
  159. package/node_modules/goah-ledger-contract/dist/transcript.js.map +0 -1
  160. package/node_modules/goah-ledger-contract/package.json +0 -28
  161. package/node_modules/goah-ledger-sqlite/dist/index.d.ts +0 -86
  162. package/node_modules/goah-ledger-sqlite/dist/index.d.ts.map +0 -1
  163. package/node_modules/goah-ledger-sqlite/dist/index.js +0 -1254
  164. package/node_modules/goah-ledger-sqlite/dist/index.js.map +0 -1
  165. package/node_modules/goah-ledger-sqlite/package.json +0 -26
  166. package/node_modules/goah-runner-pi/dist/configurator.d.ts +0 -20
  167. package/node_modules/goah-runner-pi/dist/configurator.d.ts.map +0 -1
  168. package/node_modules/goah-runner-pi/dist/configurator.js +0 -423
  169. package/node_modules/goah-runner-pi/dist/configurator.js.map +0 -1
  170. package/node_modules/goah-runner-pi/dist/credential-store.d.ts +0 -14
  171. package/node_modules/goah-runner-pi/dist/credential-store.d.ts.map +0 -1
  172. package/node_modules/goah-runner-pi/dist/credential-store.js +0 -163
  173. package/node_modules/goah-runner-pi/dist/credential-store.js.map +0 -1
  174. package/node_modules/goah-runner-pi/dist/env-spec.d.ts +0 -17
  175. package/node_modules/goah-runner-pi/dist/env-spec.d.ts.map +0 -1
  176. package/node_modules/goah-runner-pi/dist/env-spec.js +0 -95
  177. package/node_modules/goah-runner-pi/dist/env-spec.js.map +0 -1
  178. package/node_modules/goah-runner-pi/dist/index.d.ts +0 -76
  179. package/node_modules/goah-runner-pi/dist/index.d.ts.map +0 -1
  180. package/node_modules/goah-runner-pi/dist/index.js +0 -326
  181. package/node_modules/goah-runner-pi/dist/index.js.map +0 -1
  182. package/node_modules/goah-runner-pi/dist/model-provider.d.ts +0 -30
  183. package/node_modules/goah-runner-pi/dist/model-provider.d.ts.map +0 -1
  184. package/node_modules/goah-runner-pi/dist/model-provider.js +0 -96
  185. package/node_modules/goah-runner-pi/dist/model-provider.js.map +0 -1
  186. package/node_modules/goah-runner-pi/dist/pi-worker.d.ts +0 -31
  187. package/node_modules/goah-runner-pi/dist/pi-worker.d.ts.map +0 -1
  188. package/node_modules/goah-runner-pi/dist/pi-worker.js +0 -579
  189. package/node_modules/goah-runner-pi/dist/pi-worker.js.map +0 -1
  190. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.d.ts +0 -2
  191. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.d.ts.map +0 -1
  192. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.js +0 -3
  193. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.js.map +0 -1
  194. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.d.ts +0 -2
  195. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.d.ts.map +0 -1
  196. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.js +0 -5
  197. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.js.map +0 -1
  198. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.d.ts +0 -2
  199. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.d.ts.map +0 -1
  200. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.js +0 -5
  201. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.js.map +0 -1
  202. package/node_modules/goah-runner-pi/dist/test-helpers.d.ts +0 -4
  203. package/node_modules/goah-runner-pi/dist/test-helpers.d.ts.map +0 -1
  204. package/node_modules/goah-runner-pi/dist/test-helpers.js +0 -13
  205. package/node_modules/goah-runner-pi/dist/test-helpers.js.map +0 -1
  206. package/node_modules/goah-runner-pi/dist/verification-worker.d.ts +0 -2
  207. package/node_modules/goah-runner-pi/dist/verification-worker.d.ts.map +0 -1
  208. package/node_modules/goah-runner-pi/dist/verification-worker.js +0 -46
  209. package/node_modules/goah-runner-pi/dist/verification-worker.js.map +0 -1
  210. package/node_modules/goah-runner-pi/package.json +0 -28
  211. package/node_modules/goah-supervisor/dist/context-view.d.ts +0 -30
  212. package/node_modules/goah-supervisor/dist/context-view.d.ts.map +0 -1
  213. package/node_modules/goah-supervisor/dist/context-view.js +0 -58
  214. package/node_modules/goah-supervisor/dist/context-view.js.map +0 -1
  215. package/node_modules/goah-supervisor/dist/index.d.ts +0 -96
  216. package/node_modules/goah-supervisor/dist/index.d.ts.map +0 -1
  217. package/node_modules/goah-supervisor/dist/index.js +0 -1075
  218. package/node_modules/goah-supervisor/dist/index.js.map +0 -1
  219. package/node_modules/goah-supervisor/dist/roles.d.ts +0 -4
  220. package/node_modules/goah-supervisor/dist/roles.d.ts.map +0 -1
  221. package/node_modules/goah-supervisor/dist/roles.js +0 -29
  222. package/node_modules/goah-supervisor/dist/roles.js.map +0 -1
  223. package/node_modules/goah-supervisor/dist/verification.d.ts +0 -61
  224. package/node_modules/goah-supervisor/dist/verification.d.ts.map +0 -1
  225. package/node_modules/goah-supervisor/dist/verification.js +0 -135
  226. package/node_modules/goah-supervisor/dist/verification.js.map +0 -1
  227. package/node_modules/goah-supervisor/package.json +0 -26
  228. package/node_modules/goah-testkit/dist/faux-runner-worker.d.ts +0 -2
  229. package/node_modules/goah-testkit/dist/faux-runner-worker.d.ts.map +0 -1
  230. package/node_modules/goah-testkit/dist/faux-runner-worker.js +0 -82
  231. package/node_modules/goah-testkit/dist/faux-runner-worker.js.map +0 -1
  232. package/node_modules/goah-testkit/dist/index.d.ts +0 -49
  233. package/node_modules/goah-testkit/dist/index.d.ts.map +0 -1
  234. package/node_modules/goah-testkit/dist/index.js +0 -96
  235. package/node_modules/goah-testkit/dist/index.js.map +0 -1
  236. package/node_modules/goah-testkit/package.json +0 -29
@@ -1,1075 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { assertAgentHandoff, assertHandoff, assertTurnOutput, goalStream, goalRoute, goalExecutionBinding, goalTurnBinding, humanInboxRoute, humanRequestRoute, humanBinding, humanTurnBinding, specialistBinding, specialistTurnBinding, memoryStream, wakeStream, } from "goah-ledger-contract";
3
- import { composeActiveContext, selectRecoveryEvents, selectWorkingMemory } from "./context-view.js";
4
- import { defaultTurnPrompt } from "./roles.js";
5
- export { composeActiveContext, selectRecoveryEvents } from "./context-view.js";
6
- /** Dispatches each wake to the runner selected by its opaque Runner Profile. */
7
- export class RunnerRouter {
8
- runners;
9
- fallback;
10
- isolation = "process";
11
- #byPid = new Map();
12
- constructor(runners, fallback = "default") {
13
- this.runners = runners;
14
- this.fallback = fallback;
15
- }
16
- prepare(request) {
17
- const context = request.context && typeof request.context === "object" && !Array.isArray(request.context) ? request.context : {};
18
- const profile = context.runnerProfile && typeof context.runnerProfile === "object" && !Array.isArray(context.runnerProfile) ? context.runnerProfile : {};
19
- const id = typeof profile.id === "string" ? profile.id : this.fallback;
20
- const runner = this.runners.get(id);
21
- if (!runner)
22
- throw new Error(`runner profile is not configured: ${id}`);
23
- const handle = runner.prepare(request);
24
- if (handle.pid)
25
- this.#byPid.set(handle.pid, runner);
26
- void handle.result.then(() => { if (handle.pid)
27
- this.#byPid.delete(handle.pid); }, () => { if (handle.pid)
28
- this.#byPid.delete(handle.pid); });
29
- return handle;
30
- }
31
- async terminateProcess(pid, runnerProfileId) {
32
- const runner = this.#byPid.get(pid) ?? this.runners.get(runnerProfileId ?? this.fallback);
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
- #claimTail = Promise.resolve();
44
- #humanTail = Promise.resolve();
45
- #retryPolicy;
46
- #turnRetryPolicy;
47
- #profiles;
48
- #runnerProfiles;
49
- #handles = new Map();
50
- #executions = new Map();
51
- #agentExecutions = new Map();
52
- #handoffValidations = new Map();
53
- #handoffAttemptSeq = new Map();
54
- #runner;
55
- constructor(ledger, runner, clock, options = {}) {
56
- this.ledger = ledger;
57
- this.clock = clock;
58
- this.#runner = runner;
59
- this.#leaseMs = options.leaseMs ?? 30_000;
60
- this.#memoryTailChars = options.memoryTailChars ?? 12_000;
61
- this.#retryPolicy = options.retryPolicy ?? { maxAttempts: 0, baseDelayMs: 1_000 };
62
- this.#turnRetryPolicy = options.turnRetryPolicy ?? { maxAttempts: 3, baseDelayMs: 1_000 };
63
- for (const profile of options.profiles ?? []) {
64
- if (profile.agent === "ceo" && profile.role !== "ceo" || profile.agent !== "ceo" && profile.role === "ceo")
65
- throw new Error("exactly the ceo Agent must use the ceo role");
66
- }
67
- this.#profiles = new Map([["ceo", { agent: "ceo", role: "ceo" }], ...(options.profiles ?? []).map((profile) => [profile.agent, profile])]);
68
- this.#runnerProfiles = new Map((options.runnerProfiles ?? []).map((profile) => [profile.id, profile]));
69
- }
70
- /**
71
- * Hot-swap the runner after a config reload. Refused while a Turn is active:
72
- * a live child must never observe its runner vanish mid-flight.
73
- * The next Turn uses the new runner, and spawn-time env
74
- * resolution applies the new credentials to the next spawn.
75
- */
76
- swapRunner(runner, profiles) {
77
- const active = this.ledger.turns().filter((turn) => turn.status === "in_progress");
78
- if (active.length > 0)
79
- throw new Error("cannot swap runner while a Turn is in progress");
80
- this.#runner = runner;
81
- if (profiles) {
82
- this.#runnerProfiles.clear();
83
- for (const profile of profiles)
84
- this.#runnerProfiles.set(profile.id, profile);
85
- }
86
- }
87
- get runner() { return this.#runner; }
88
- createGoal(goal, actor = "human") { if (!this.#validGoalOwner(goal.owner, goal))
89
- throw new Error("Goal owner role does not match Root/Child position"); if (goal.parentId === null && this.ledger.goals().some((candidate) => candidate.id !== goal.id && candidate.parentId === null && candidate.phase !== "complete"))
90
- throw new Error("CEO already has an unfinished Root Goal"); this.ledger.putGoal(goal, actor); }
91
- createRootGoal(objective, id = randomUUID(), sourceTurnId) {
92
- if (!objective.trim())
93
- throw new Error("root objective is required");
94
- if (this.ledger.goals().some((goal) => goal.parentId === null && goal.owner === "ceo" && goal.phase !== "complete"))
95
- throw new Error("CEO already has an unfinished Root Goal; use work_on_goal");
96
- const goal = { id, parentId: null, objective, observationMethod: null, verificationMethod: null, owner: "ceo", phase: "active", revision: 0 };
97
- this.ledger.putGoal(goal, "human", undefined, { operation: "create", reason: "Human established a durable Root Goal", evidence: [], authority: { kind: "human" }, ...(sourceTurnId ? { sourceTurnId } : {}) });
98
- return this.#goal(id);
99
- }
100
- startGoal(objective, id = randomUUID()) {
101
- const goal = this.createRootGoal(objective, id);
102
- const wake = this.#enqueueTrigger("ceo", `root:${id}:created`, { goalId: goal.id });
103
- if (!wake)
104
- throw new Error("CEO wake was not admitted for an active root goal");
105
- return { goal, wake };
106
- }
107
- threadFor(agent = "ceo") {
108
- const parentThreadId = agent === "ceo" ? null : this.threadFor("ceo").id;
109
- const existing = this.ledger.threads().find((thread) => thread.agent === agent && thread.parentThreadId === parentThreadId);
110
- if (existing)
111
- return existing;
112
- const now = this.#now();
113
- const thread = { id: randomUUID(), agent, parentThreadId, createdAt: now, updatedAt: now };
114
- this.ledger.putThread(thread, "supervisor");
115
- return thread;
116
- }
117
- async startHumanTurn(message) {
118
- const previous = this.#humanTail;
119
- let release;
120
- this.#humanTail = new Promise((resolve) => { release = resolve; });
121
- await previous;
122
- try {
123
- return await this.#startHumanTurn(message);
124
- }
125
- finally {
126
- release();
127
- }
128
- }
129
- async #startHumanTurn(message) {
130
- if (!message.trim())
131
- throw new Error("message is required");
132
- const thread = this.threadFor("ceo");
133
- const active = this.ledger.activeTurn(thread.id);
134
- if (active) {
135
- if (active.source !== "human") {
136
- const goalId = active.goalId;
137
- await this.interruptTurn(active.id);
138
- if (goalId) {
139
- const goal = this.ledger.goal(goalId);
140
- if (goal?.phase === "active")
141
- this.#enqueueTrigger(goal.owner, `human-interrupted:${active.id}`, { goalId: goal.id });
142
- }
143
- }
144
- else {
145
- const handle = this.#handles.get(active.id);
146
- if (handle && !handle.steer)
147
- await this.interruptTurn(active.id);
148
- else if (handle?.steer) {
149
- const item = this.#appendTurnItem(active.id, "user_message", { text: message }, "human", randomUUID(), "in_progress");
150
- try {
151
- await handle.steer(message);
152
- const stored = this.ledger.turnItems(active.id).find((candidate) => candidate.id === item.id);
153
- if (this.ledger.turn(active.id)?.status === "in_progress" && stored?.status === "in_progress") {
154
- this.ledger.putTurnItem({ ...stored, status: "completed", completedAt: this.#now() }, "human");
155
- return { threadId: thread.id, turnId: active.id, steered: true };
156
- }
157
- }
158
- catch {
159
- const stored = this.ledger.turnItems(active.id).find((candidate) => candidate.id === item.id);
160
- if (this.ledger.turn(active.id)?.status === "in_progress" && stored?.status === "in_progress")
161
- this.ledger.putTurnItem({ ...stored, status: "failed", completedAt: this.#now() }, "human");
162
- if (this.ledger.turn(active.id)?.status === "in_progress")
163
- await this.interruptTurn(active.id);
164
- }
165
- }
166
- else {
167
- this.#appendTurnItem(active.id, "user_message", { text: message }, "human");
168
- return { threadId: thread.id, turnId: active.id, steered: true };
169
- }
170
- }
171
- }
172
- await this.#awaitAgentExecution("ceo");
173
- if (this.ledger.activeTurn(thread.id))
174
- return this.#startHumanTurn(message);
175
- const now = this.#now();
176
- this.ledger.putThread({ ...thread, updatedAt: now }, "supervisor");
177
- const leaseToken = randomUUID();
178
- const turn = { id: randomUUID(), threadId: thread.id, source: "human", ...humanTurnBinding(), status: "in_progress", attempt: 1, error: null, startedAt: now, endedAt: null, leaseUntil: new Date(this.clock.now().getTime() + this.#leaseMs).toISOString(), leaseToken, runnerPid: null, runnerProfileId: this.#runnerProfileId("ceo") };
179
- this.ledger.putTurn(turn, "human");
180
- this.#appendTurnItem(turn.id, "user_message", { text: message }, "human");
181
- const context = { source: { kind: "human" } };
182
- const deliveredMail = this.#contextMail(humanBinding());
183
- void this.#trackExecution(turn, "ceo", context, () => this.#humanContext(turn.id, "ceo", deliveredMail), null, deliveredMail.map((mail) => mail.id));
184
- return { threadId: thread.id, turnId: turn.id, steered: false };
185
- }
186
- async interruptTurn(turnId) {
187
- const turn = this.ledger.turn(turnId);
188
- if (!turn || turn.status !== "in_progress")
189
- throw new Error("active Turn not found");
190
- this.#clearHandoffState(turn.id);
191
- this.ledger.finishTurn(turn.id, "interrupted", { message: "interrupted by Human" }, this.#now(), "human");
192
- const handle = this.#handles.get(turn.id);
193
- try {
194
- await handle?.terminate();
195
- }
196
- catch (error) {
197
- this.#recordCleanupFailure(turn.id, error);
198
- }
199
- finally {
200
- this.#handles.delete(turn.id);
201
- }
202
- await this.#executions.get(turn.id);
203
- return this.ledger.turn(turn.id);
204
- }
205
- async #executeTurn(initial, agent, turnContext, contextFactory, sourceWake = null, deliveredMailIds = [], recordRevisionAtStart = -1, sourceWakeTriggers = []) {
206
- while (this.ledger.turn(initial.id)?.status === "in_progress") {
207
- const execution = this.ledger.turn(initial.id);
208
- const leaseToken = execution.leaseToken;
209
- let handle = null;
210
- let renewal = null;
211
- try {
212
- handle = this.runner.prepare({ agent, execution, ...(sourceWake ? { sourceWake, sourceWakeTriggers } : {}), turn: turnContext, context: contextFactory(), now: () => this.#now(), emit: (trace) => this.#recordTurnTrace(initial.id, leaseToken, trace.type, trace.data, agent), rpc: (method, params) => this.#agentRpcForTurn(initial.id, agent, turnContext, method, params, sourceWake?.id) });
213
- this.#handles.set(initial.id, handle);
214
- if (handle.pid)
215
- this.ledger.attachTurnProcess(initial.id, leaseToken, handle.pid);
216
- renewal = setInterval(() => { try {
217
- this.ledger.renewTurnLease(initial.id, leaseToken, new Date(this.clock.now().getTime() + this.#leaseMs).toISOString(), this.#now());
218
- }
219
- catch {
220
- void this.#terminateHandle(initial.id, handle);
221
- } }, Math.max(25, Math.floor(this.#leaseMs / 3)));
222
- renewal.unref();
223
- handle.begin();
224
- const result = await handle.result;
225
- if (renewal)
226
- clearInterval(renewal);
227
- renewal = null;
228
- await this.#terminateHandle(initial.id, handle);
229
- this.#handles.delete(initial.id);
230
- const current = this.ledger.turn(initial.id);
231
- if (!current || current.status !== "in_progress")
232
- return;
233
- if (current.attempt > 1)
234
- this.ledger.appendEvent({ streamId: `turn:${current.id}`, ts: this.#now(), actor: "supervisor", type: "turn.retry_finished", data: { attempt: current.attempt, outcome: result.outcome }, ignorable: true });
235
- if (result.outcome !== "abnormal")
236
- this.#closeStreamingItems(current.id);
237
- if (result.outcome === "abnormal") {
238
- if (current.attempt < this.#turnRetryPolicy.maxAttempts) {
239
- this.ledger.repairTurnAttempt(current.id, result.reason, this.#now(), "supervisor");
240
- this.ledger.appendEvent({ streamId: `turn:${current.id}`, ts: this.#now(), actor: "supervisor", type: "turn.retry_started", data: { attempt: current.attempt + 1, reason: result.reason }, ignorable: true });
241
- this.ledger.putTurn({ ...current, attempt: current.attempt + 1, leaseUntil: new Date(this.clock.now().getTime() + this.#leaseMs).toISOString(), runnerPid: null }, "supervisor");
242
- await new Promise((resolve) => setTimeout(resolve, this.#turnRetryPolicy.baseDelayMs * 2 ** (current.attempt - 1)));
243
- continue;
244
- }
245
- this.#failTurn(current.id, result.reason);
246
- this.#scheduleTurnRecovery(sourceWake, sourceWakeTriggers, current.id, agent);
247
- return;
248
- }
249
- if (turnContext.goalBinding) {
250
- if (result.outcome !== "handoff")
251
- throw new Error("Goal-bound Turn requires Handoff");
252
- this.#commitGoalTurn(current.id, agent, turnContext, result.output, sourceWake?.id ?? null, deliveredMailIds, recordRevisionAtStart);
253
- return;
254
- }
255
- if (result.outcome !== "response")
256
- throw new Error("ordinary Turn cannot finish with Handoff");
257
- if (!this.ledger.turnItems(current.id).some((item) => item.type === "assistant_message" && item.data.text === result.response.content))
258
- this.#appendTurnItem(current.id, "assistant_message", { text: result.response.content }, agent);
259
- this.ledger.finishTurn(current.id, "completed", null, this.#now(), "supervisor", deliveredMailIds);
260
- return;
261
- }
262
- catch (error) {
263
- if (renewal)
264
- clearInterval(renewal);
265
- if (handle)
266
- await this.#terminateHandle(initial.id, handle);
267
- this.#handles.delete(initial.id);
268
- const current = this.ledger.turn(initial.id);
269
- if (current?.status === "in_progress") {
270
- this.#failTurn(current.id, error instanceof Error ? error.message : String(error));
271
- this.#scheduleTurnRecovery(sourceWake, sourceWakeTriggers, current.id, agent);
272
- }
273
- return;
274
- }
275
- }
276
- }
277
- #trackExecution(initial, agent, turnContext, contextFactory, sourceWake = null, deliveredMailIds = [], recordRevisionAtStart = -1, sourceWakeTriggers = []) { const running = this.#executeTurn(initial, agent, turnContext, contextFactory, sourceWake, deliveredMailIds, recordRevisionAtStart, sourceWakeTriggers); this.#executions.set(initial.id, running); this.#agentExecutions.set(agent, running); void running.finally(() => { if (this.#executions.get(initial.id) === running)
278
- this.#executions.delete(initial.id); if (this.#agentExecutions.get(agent) === running)
279
- this.#agentExecutions.delete(agent); }); return running; }
280
- async #awaitAgentExecution(agent) { const running = this.#agentExecutions.get(agent); if (running)
281
- await running; }
282
- #humanContext(turnId, agent, mail) {
283
- const turn = this.ledger.turn(turnId);
284
- const current = this.#turnHistory(turn.id);
285
- const turnSourceSeqs = this.ledger.readStream(`turn:${turn.id}`).filter((event) => event.type === "item.user_message.started").map((event) => event.seq);
286
- if (turn.bindingKind === "goal") {
287
- const context = { source: { kind: "human" }, goalBinding: { goalId: turn.goalId, goalRevision: turn.goalRevision } };
288
- const base = this.#loadContext({ ...goalExecutionBinding(agent, turn.goalId), attempt: turn.attempt }, context, mail, []);
289
- const sourceSeqs = Array.isArray(base.sourceSeqs) ? base.sourceSeqs.filter((value) => typeof value === "number") : [];
290
- return { ...base, text: `${String(base.text ?? "")}\n\n# Current Turn\n\n${current}`, sourceSeqs: [...new Set([...sourceSeqs, ...turnSourceSeqs])].sort((a, b) => a - b) };
291
- }
292
- const profile = this.#profiles.get(agent) ?? { agent, role: "ceo" };
293
- const runnerProfile = this.#runnerProfiles.get(profile.runnerProfile ?? "default");
294
- const recent = this.ledger.turns(turn.threadId).filter((candidate) => candidate.id !== turn.id && candidate.status === "completed").slice(-8).flatMap((candidate) => this.ledger.turnItems(candidate.id).filter((item) => item.type === "user_message" || item.type === "assistant_message").map((item) => `${item.type === "user_message" ? "Human" : "Assistant"}: ${String(item.data.text ?? "")}`));
295
- const sourceSeqs = [...turnSourceSeqs, ...this.#mailSourceSeqs(mail)];
296
- const incoming = mail.map((item) => `- [${item.level}] ${item.id} from ${item.from}: ${boundedJson(item.body)}`);
297
- const context = { source: { kind: "human" } };
298
- return { text: [...(recent.length ? [`# Recent conversation\n\n${recent.join("\n")}`] : []), ...(incoming.length ? [`# Incoming\n\n${incoming.join("\n")}`] : []), `# Current Turn\n\n${current}`].join("\n\n"), sourceSeqs, capabilities: profile.capabilities ?? defaultCapabilities(profile.role), systemPrompt: profile.systemPrompt ?? defaultTurnPrompt(profile.role, agent, context), ...(runnerProfile ? { runnerProfile } : {}) };
299
- }
300
- #goalContext(wake, turn, turnId, mail, wakeTriggers) { const base = this.#loadContext(wake, turn, mail, wakeTriggers); if (!base || typeof base !== "object" || Array.isArray(base))
301
- return base; const value = base; const history = this.#turnHistory(turnId); return { ...value, ...(history ? { text: `${String(value.text ?? "")}\n\n# Current Turn retry history\n\n${history}` } : {}) }; }
302
- #threadAgent(threadId) { const thread = this.ledger.thread(threadId); if (!thread)
303
- throw new Error("Turn Thread not found"); return thread.agent; }
304
- #turnHistory(turnId) { const items = this.ledger.turnItems(turnId).map((item) => { const data = item.data; if (item.type === "user_message")
305
- return `Human: ${String(data.text ?? "")}`; if (item.type === "assistant_message")
306
- return `Assistant: ${String(data.text ?? "")}`; if (item.type === "tool_call")
307
- return `Tool call: ${String(data.tool ?? "")} ${JSON.stringify(item.data)}`; if (item.type === "tool_result")
308
- return `Tool result: ${JSON.stringify(data.result ?? item.data)}`; return `${item.type}: ${JSON.stringify(item.data)}`; }); const facts = this.ledger.readStream(`turn:${turnId}`).filter((event) => event.type === "turn.retry_started").map((event) => `${event.type}: ${JSON.stringify(event.data)}`); return [...items, ...facts].join("\n"); }
309
- #failTurn(turnId, reason) { const current = this.ledger.turn(turnId); if (!current || current.status !== "in_progress")
310
- return; this.#clearHandoffState(turnId); this.ledger.finishTurn(turnId, "failed", { message: reason }, this.#now(), "supervisor"); }
311
- #preemptGoalTurn(goalId, reason) { const turn = this.ledger.turns().find((candidate) => candidate.status === "in_progress" && candidate.bindingKind === "goal" && candidate.goalId === goalId); if (!turn)
312
- return; this.#clearHandoffState(turn.id); this.ledger.finishTurn(turn.id, "interrupted", { message: reason }, this.#now(), "supervisor"); const handle = this.#handles.get(turn.id); if (handle)
313
- void this.#terminateHandle(turn.id, handle).finally(() => this.#handles.delete(turn.id)); }
314
- async #terminateHandle(turnId, handle) { try {
315
- await handle.terminate();
316
- }
317
- catch (error) {
318
- this.#recordCleanupFailure(turnId, error);
319
- } }
320
- #recordCleanupFailure(turnId, error) { this.ledger.appendEvent({ streamId: `turn:${turnId}`, ts: this.#now(), actor: "supervisor", type: "runner.cleanup_failed", data: { message: error instanceof Error ? error.message : String(error) }, ignorable: true }); }
321
- #scheduleTurnRecovery(sourceWake, triggers, turnId, agent) { if (!sourceWake)
322
- return; const turn = this.ledger.turn(turnId); if (!turn)
323
- return; const role = this.#role(agent); const goal = turn.bindingKind === "goal" ? this.ledger.goal(turn.goalId) : null; if (turn.bindingKind === "human" || turn.bindingKind === "goal" && (!goal || goal.phase !== "active" || goal.owner !== agent) || turn.bindingKind === "specialist" && role !== turn.specialistRole)
324
- return; const prior = Math.max(0, ...triggers.map((trigger) => parseRecoveryTrigger(trigger.triggerRef)?.attempt ?? 0)); const next = prior + 1; if (next < this.#retryPolicy.maxAttempts) {
325
- const delay = this.#retryPolicy.baseDelayMs * 2 ** prior;
326
- const binding = turn.bindingKind === "goal" ? goalExecutionBinding(agent, turn.goalId) : specialistBinding(agent, turn.specialistRole);
327
- this.ledger.putSchedule({ id: recoveryScheduleId(turnId, next), ...binding, nextWakeAt: new Date(this.clock.now().getTime() + delay).toISOString(), reason: recoveryRef(turnId), setBy: "supervisor", status: "pending", resolvedAt: null }, "supervisor", sourceWake.id);
328
- }
329
- else if (role === "child" && goal?.parentId) {
330
- const parent = this.ledger.goal(goal.parentId);
331
- if (parent?.phase === "active")
332
- this.#enqueueTrigger(parent.owner, childRetryExhaustedRef(turnId), { goalId: parent.id });
333
- } }
334
- #closeStreamingItems(turnId) { const open = this.ledger.turnItems(turnId).filter((item) => item.status === "in_progress"); if (open.some((item) => item.type === "tool_call" || item.type === "user_message"))
335
- throw new Error("Runner returned while an input or tool call was still open"); for (const item of open)
336
- this.ledger.putTurnItem({ ...item, status: "completed", completedAt: this.#now() }, "supervisor"); }
337
- #appendTurnItem(turnId, type, data, actor, id = randomUUID(), status = "completed") {
338
- const now = this.#now();
339
- const item = { id, turnId, ordinal: this.ledger.turnItems(turnId).length + 1, type, status, data, createdAt: now, completedAt: status === "in_progress" ? null : now };
340
- this.ledger.putTurnItem(item, actor);
341
- return item;
342
- }
343
- #recordTurnTrace(turnId, leaseToken, type, data, actor = "ceo") {
344
- if (type === "transcript.completed" || type === "transcript.interrupted")
345
- return;
346
- if (type === "transcript.started" && this.ledger.readStream(`turn:${turnId}`).some((event) => event.type === "transcript.started"))
347
- return;
348
- this.ledger.appendTurnEvent({ streamId: `turn:${turnId}`, ts: this.#now(), actor, type, data }, leaseToken);
349
- if (type === "message.assistant.completed") {
350
- const value = data && typeof data === "object" && !Array.isArray(data) ? data : {};
351
- if (!["ordinary", "handoff"].includes(String(value.completionIntent)) || value.commitState !== (value.completionIntent === "handoff" ? "provisional" : "committed"))
352
- throw new Error("Runner assistant completion is missing normalized intent/state");
353
- const text = messageTextContent(value.message?.content);
354
- if (text)
355
- this.#appendTurnItem(turnId, "assistant_message", { text }, actor);
356
- }
357
- else if (type === "plan.updated")
358
- this.#appendTurnItem(turnId, "plan", data, actor);
359
- else if (type === "message.assistant.delta") {
360
- const input = data;
361
- const delta = input.delta;
362
- if (delta?.type === "thinking_start" || delta?.type === "thinking_delta" || delta?.type === "thinking_end") {
363
- const turn = this.ledger.turn(turnId);
364
- const id = `${turnId}:attempt:${turn.attempt}:reasoning:${String(input.messageId ?? "message")}`;
365
- const existing = this.ledger.turnItems(turnId).find((item) => item.id === id);
366
- const text = `${String(existing?.data?.text ?? "")}${delta.type === "thinking_delta" ? String(delta.delta ?? "") : ""}`;
367
- if (!existing)
368
- this.#appendTurnItem(turnId, "reasoning", { text }, actor, id, delta.type === "thinking_end" ? "completed" : "in_progress");
369
- else
370
- this.ledger.putTurnItem({ ...existing, data: { text }, status: delta.type === "thinking_end" ? "completed" : "in_progress", completedAt: delta.type === "thinking_end" ? this.#now() : null }, actor);
371
- }
372
- }
373
- else if (type === "tool.called") {
374
- const input = data;
375
- const turn = this.ledger.turn(turnId);
376
- const id = `${turnId}:attempt:${turn.attempt}:tool:${String(input.callId)}`;
377
- if (this.ledger.turnItems(turnId).some((item) => item.id === id))
378
- throw new Error("duplicate tool call id in Turn attempt");
379
- this.#appendTurnItem(turnId, "tool_call", { callId: String(input.callId), tool: String(input.name), arguments: input.arguments ?? null }, actor, id, "in_progress");
380
- }
381
- else if (type === "tool.completed") {
382
- const input = data;
383
- const turn = this.ledger.turn(turnId);
384
- const id = `${turnId}:attempt:${turn.attempt}:tool:${String(input.callId)}`;
385
- const existing = this.ledger.turnItems(turnId).find((item) => item.id === id);
386
- if (!existing)
387
- throw new Error("tool result has no matching call in Turn attempt");
388
- this.ledger.putTurnItem({ ...existing, status: input.isError ? "failed" : "completed", completedAt: this.#now() }, actor);
389
- this.#appendTurnItem(turnId, "tool_result", { callId: String(input.callId), result: input.result ?? null }, actor);
390
- }
391
- }
392
- sendToCeo(body, level = "decision") {
393
- const mail = { id: randomUUID(), to: "ceo", from: "human", level, ...humanInboxRoute(), body, readAt: null };
394
- this.ledger.putMail(mail, "human");
395
- const wake = this.#enqueueTrigger("ceo", `mail:${mail.id}`);
396
- if (!wake)
397
- throw new Error("CEO wake was not admitted");
398
- return { mail, wake };
399
- }
400
- delegate(request, actor = "ceo", wakeId) { const candidate = { ...request.childGoal, parentId: request.parentGoalId, phase: "active", revision: 0 }; if (!this.#validGoalOwner(candidate.owner, candidate))
401
- throw new Error("Child Goal owner must use a Child Agent profile"); return this.ledger.commitDelegation(request, actor, wakeId); }
402
- async reassignGoal(request, actor = "ceo", wakeId) { const goal = this.#goal(request.goalId); if (!this.#validGoalOwner(request.newOwner, { ...goal, owner: request.newOwner }))
403
- throw new Error("Child Goal owner must use a Child Agent profile"); const thread = this.ledger.threads().find((candidate) => candidate.agent === goal.owner); const active = thread ? this.ledger.activeTurn(thread.id) : null; if (active?.bindingKind === "goal" && active.goalId === goal.id)
404
- await this.interruptTurn(active.id); return this.ledger.commitReassignment(request, actor, wakeId); }
405
- teamList(now = this.#now()) { return deriveTeam(this.ledger, now); }
406
- agentRole(agent) { return this.#role(agent); }
407
- updateGoal(id, patch, actor = "human", change) {
408
- if (patch.objective === undefined && patch.observationMethod === undefined && patch.verificationMethod === undefined)
409
- throw new Error("goal update requires objective, observation method, or verification method");
410
- const current = this.#goal(id);
411
- this.#assertGoalMutationAuthority(current, actor);
412
- if (patch.objective !== undefined && patch.objective !== current.objective && current.parentId !== null && (patch.observationMethod === undefined || patch.verificationMethod === undefined))
413
- throw new Error("child objective revision requires replacement observation and verification methods");
414
- const next = {
415
- ...current,
416
- ...patch,
417
- ...(patch.objective !== undefined && patch.objective !== current.objective && current.parentId === null && patch.observationMethod === undefined ? { observationMethod: null } : {}),
418
- ...(patch.objective !== undefined && patch.objective !== current.objective && current.parentId === null && patch.verificationMethod === undefined ? { verificationMethod: null } : {}),
419
- revision: current.revision + 1,
420
- };
421
- this.#preemptGoalTurn(current.id, "Goal definition changed");
422
- this.ledger.putGoal(next, actor, change?.sourceWakeId, { operation: "revise", reason: change?.reason ?? "Goal definition revised", evidence: change?.evidence ?? [], ...(change?.sourceTurnId ? { sourceTurnId: change.sourceTurnId } : {}), ...(change?.sourceWakeId ? { sourceWakeId: change.sourceWakeId } : {}) });
423
- if (next.parentId === null && next.owner === "ceo" && actor === "human")
424
- this.#enqueueTrigger("ceo", `root:${id}:revised:${next.revision}`, { goalId: next.id });
425
- return next;
426
- }
427
- confirmObservationMethod(id, observationMethod) {
428
- const current = this.#goal(id);
429
- if (current.parentId !== null)
430
- throw new Error("human confirmation applies only to a root goal");
431
- return this.updateGoal(id, { observationMethod, verificationMethod: observationMethod }, "human", { reason: "Human confirmed the Root observation and verification method", evidence: [] });
432
- }
433
- reviseChildGoal(id, objective, observationMethod, verificationMethod, actor, reason, evidence, wakeId, sourceTurnId) {
434
- const current = this.#goal(id);
435
- if (current.parentId === null)
436
- throw new Error("CEO cannot revise a root goal");
437
- if (!reason.trim())
438
- throw new Error("goal revision reason is required");
439
- for (const seq of evidence)
440
- if (!this.ledger.eventsSince(seq - 1).some((event) => event.seq === seq))
441
- throw new Error(`evidence event does not exist: ${seq}`);
442
- 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 } });
443
- return this.updateGoal(id, { objective, observationMethod, verificationMethod }, actor, { reason, evidence, ...(wakeId ? { sourceWakeId: wakeId } : {}), ...(sourceTurnId ? { sourceTurnId } : {}) });
444
- }
445
- completeGoal(request, actor = "human", wakeId) {
446
- const goal = this.ledger.completeGoal(request, actor, wakeId);
447
- this.#preemptGoalTurn(goal.id, "Goal completed");
448
- return goal;
449
- }
450
- transitionGoal(id, phase, actor = "human", scheduleMotion = true, sourceTurnId) {
451
- const current = this.#goal(id);
452
- this.#assertGoalMutationAuthority(current, actor);
453
- if (current.phase === phase)
454
- return current;
455
- if (phase === "complete")
456
- throw new Error("goal completion requires reason and evidence");
457
- const next = { ...current, phase, revision: current.revision + 1 };
458
- const operation = phase === "paused" ? "pause" : phase === "blocked" ? "block" : "resume";
459
- this.#preemptGoalTurn(current.id, `Goal ${operation}`);
460
- this.ledger.putGoal(next, actor, undefined, { operation, reason: `Goal ${operation} requested by ${actor}`, evidence: [], ...(sourceTurnId ? { sourceTurnId } : {}) });
461
- if (phase === "active" && scheduleMotion)
462
- this.#enqueueTrigger(next.owner, `${next.parentId ? "goal" : "root"}:${id}:resumed:${next.revision}`, { goalId: next.id });
463
- return next;
464
- }
465
- planWake(agent, at, reason, setBy = agent, target) {
466
- const parsed = Date.parse(at);
467
- if (!Number.isFinite(parsed))
468
- throw new Error("schedule time is invalid");
469
- const nextWakeAt = new Date(parsed).toISOString();
470
- const binding = target ?? this.#singleActiveGoalTarget(agent);
471
- const role = this.#role(agent);
472
- if (!binding && role !== "verifier" && role !== "audit")
473
- throw new Error("Goal-owning Agent schedules require an active Goal target");
474
- if (binding) {
475
- const goal = this.ledger.goal(binding.goalId);
476
- if (!goal || goal.phase !== "active" || !this.#validGoalOwner(agent, goal))
477
- throw new Error("schedule target is not an active Goal owned by this Agent");
478
- }
479
- const executionTarget = binding ? goalExecutionBinding(agent, binding.goalId) : specialistBinding(agent, role);
480
- const schedule = { id: `schedule:${agent}${binding ? `:${binding.goalId}` : ""}:${randomUUID()}`, ...executionTarget, nextWakeAt, reason, setBy, status: "pending", resolvedAt: null };
481
- this.ledger.putSchedule(schedule, setBy);
482
- return nextWakeAt <= this.#now() ? this.#enqueueSchedule(schedule) : null;
483
- }
484
- async stopAgentWake(agent, goalId) { const role = this.#role(agent); if (goalId) {
485
- const goal = this.#goal(goalId);
486
- if (!this.#validGoalOwner(agent, goal))
487
- throw new Error("Goal stop target is not owned by the Agent");
488
- } const binding = role === "verifier" || role === "audit" ? null : goalId ? { goalId } : this.#singleActiveGoalTarget(agent); if (role !== "verifier" && role !== "audit" && !binding)
489
- throw new Error("Goal stop requires an explicit Goal target"); const wake = this.ledger.wakes().find((item) => item.agent === agent && (item.status === "queued" || item.status === "claimed") && (binding ? item.bindingKind === "goal" && item.goalId === binding.goalId : item.bindingKind === "specialist")); if (wake)
490
- this.ledger.cancelWake(wake.id, this.#now()); const thread = this.ledger.threads().find((item) => item.agent === agent); const turn = thread ? this.ledger.activeTurn(thread.id) : null; if (turn && (binding ? turn.bindingKind === "goal" && turn.goalId === binding.goalId : turn.bindingKind === "specialist"))
491
- await this.interruptTurn(turn.id); return wake ? this.#wake(wake.id) : null; }
492
- async recover() {
493
- for (const wake of this.ledger.wakes().filter((candidate) => candidate.status === "claimed"))
494
- this.ledger.releaseWake(wake.id, this.#now());
495
- for (const turn of this.ledger.turns().filter((candidate) => candidate.status === "in_progress" && !this.#handles.has(candidate.id))) {
496
- if (turn.runnerPid)
497
- await this.runner.terminateProcess(turn.runnerPid, turn.runnerProfileId);
498
- this.#failTurn(turn.id, "orphaned Runner ownership recovered after Supervisor restart");
499
- const thread = this.ledger.thread(turn.threadId);
500
- if (!thread)
501
- continue;
502
- if (turn.bindingKind === "goal") {
503
- const goal = this.ledger.goal(turn.goalId);
504
- if (goal?.phase === "active" && goal.owner === thread.agent)
505
- this.#enqueueTrigger(thread.agent, recoveryRef(turn.id), { goalId: goal.id });
506
- }
507
- else if (turn.bindingKind === "specialist" && this.#role(thread.agent) === turn.specialistRole)
508
- this.#enqueueTrigger(thread.agent, recoveryRef(turn.id));
509
- }
510
- }
511
- async tick() {
512
- const wake = await this.#claimNextWake();
513
- if (!wake)
514
- return null;
515
- let createdTurnId = null;
516
- try {
517
- await this.#awaitAgentExecution(wake.agent);
518
- const currentWake = this.#wake(wake.id);
519
- if (currentWake.status !== "claimed")
520
- return currentWake;
521
- if (currentWake.bindingKind === "goal") {
522
- const goal = this.ledger.goal(currentWake.goalId);
523
- if (!goal || goal.owner !== currentWake.agent || goal.phase !== "active" || !this.#validGoalOwner(currentWake.agent, goal)) {
524
- this.ledger.cancelWake(currentWake.id, this.#now());
525
- return this.#wake(currentWake.id);
526
- }
527
- }
528
- const wakeTriggers = this.ledger.wakeTriggers(currentWake.id).filter((trigger) => trigger.status === "pending");
529
- const turnContext = this.#turnContext(currentWake, wakeTriggers);
530
- if (!this.#validExecution(currentWake.agent, turnContext)) {
531
- this.ledger.cancelWake(currentWake.id, this.#now());
532
- return this.#wake(currentWake.id);
533
- }
534
- const thread = this.threadFor(currentWake.agent);
535
- const now = this.#now();
536
- const leaseToken = randomUUID();
537
- const turnBinding = currentWake.bindingKind === "goal" ? goalTurnBinding(currentWake.goalId, turnContext.goalBinding.goalRevision) : currentWake.bindingKind === "human" ? humanTurnBinding() : specialistTurnBinding(currentWake.specialistRole);
538
- const execution = { id: randomUUID(), threadId: thread.id, source: turnContext.source.kind, ...turnBinding, status: "in_progress", attempt: 1, error: null, startedAt: now, endedAt: null, leaseUntil: new Date(this.clock.now().getTime() + this.#leaseMs).toISOString(), leaseToken, runnerPid: null, runnerProfileId: this.#runnerProfileId(currentWake.agent) };
539
- this.ledger.putThread({ ...thread, updatedAt: this.#now() }, "supervisor");
540
- this.ledger.startTurnFromWake(currentWake.id, execution, now);
541
- createdTurnId = execution.id;
542
- const deliveredMail = this.#contextMail(currentWake);
543
- const deliveredMailIds = deliveredMail.map((mail) => mail.id);
544
- this.ledger.appendEvent({ streamId: `turn:${execution.id}`, ts: now, actor: "supervisor", type: "run.admitted", data: { ...turnContext, wakeTriggers }, ignorable: true });
545
- const revision = turnContext.goalBinding ? this.ledger.workRecord(turnContext.goalBinding.goalId)?.recordRevision ?? -1 : -1;
546
- await this.#trackExecution(execution, currentWake.agent, turnContext, () => this.#goalContext(currentWake, turnContext, execution.id, deliveredMail, wakeTriggers), currentWake, deliveredMailIds, revision, wakeTriggers);
547
- return this.#wake(wake.id);
548
- }
549
- catch (error) {
550
- if (createdTurnId && this.ledger.turn(createdTurnId)?.status === "in_progress")
551
- this.#failTurn(createdTurnId, error instanceof Error ? error.message : String(error));
552
- const current = this.ledger.wake(wake.id);
553
- if (current?.status === "claimed")
554
- this.ledger.releaseWake(wake.id, this.#now());
555
- return this.#wake(wake.id);
556
- }
557
- }
558
- async #claimNextWake() {
559
- const previous = this.#claimTail;
560
- let release;
561
- this.#claimTail = new Promise((resolve) => { release = resolve; });
562
- await previous;
563
- try {
564
- for (const schedule of this.ledger.dueSchedules(this.#now()))
565
- this.#enqueueSchedule(schedule);
566
- for (const mail of this.ledger.triggeringMail()) {
567
- const thread = this.ledger.threads().find((candidate) => candidate.agent === mail.to);
568
- if (thread && this.ledger.activeTurn(thread.id))
569
- continue;
570
- const goal = mail.routeKind === "goal" ? this.ledger.goal(mail.goalId) : null;
571
- const target = goal && goal.owner === mail.to && goal.phase === "active" && this.#validGoalOwner(mail.to, goal) ? { goalId: goal.id } : undefined;
572
- const humanInteraction = mail.routeKind === "human_inbox" && mail.to === "ceo" && mail.from === "human";
573
- const specialistInteraction = mail.routeKind === "specialist_inbox" && this.#role(mail.to) === mail.specialistRole;
574
- if (!target && !humanInteraction && !specialistInteraction)
575
- continue;
576
- const base = `mail:${mail.id}`;
577
- const related = this.ledger.wakeTriggersForAgent(mail.to).map((trigger) => ({ trigger, attempt: mailDeliveryAttempt(trigger.triggerRef, base) })).filter((entry) => entry.attempt !== null);
578
- const pending = related.filter((entry) => entry.trigger.status === "pending").sort((a, b) => b.attempt - a.attempt)[0];
579
- const next = related.reduce((highest, entry) => Math.max(highest, entry.attempt), -1) + 1;
580
- const trigger = pending?.trigger.triggerRef ?? (next === 0 ? base : `${base}@redelivery:${next}`);
581
- this.#enqueueTrigger(mail.to, trigger, target);
582
- }
583
- return this.ledger.claimNextWake(this.#now());
584
- }
585
- finally {
586
- release();
587
- }
588
- }
589
- async runAvailable(concurrency = 4, maxWakes = 100) {
590
- const completed = [];
591
- while (true) {
592
- const batch = await Promise.all(Array.from({ length: concurrency }, () => this.tick()));
593
- const wakes = batch.filter((wake) => wake !== null);
594
- completed.push(...wakes);
595
- if (wakes.length === 0 || completed.length >= maxWakes)
596
- return completed;
597
- }
598
- }
599
- #enqueueSchedule(schedule) {
600
- if (schedule.status !== "pending")
601
- return null;
602
- if (schedule.bindingKind === "goal") {
603
- const goal = this.ledger.goal(schedule.goalId);
604
- if (!goal || goal.owner !== schedule.agent || goal.phase !== "active" || !this.#validGoalOwner(schedule.agent, goal)) {
605
- this.ledger.supersedeSchedule(schedule.id, this.#now());
606
- return null;
607
- }
608
- }
609
- else if (!["verifier", "audit"].includes(this.#role(schedule.agent))) {
610
- this.ledger.cancelSchedule(schedule.id, this.#now());
611
- return null;
612
- }
613
- const wake = { id: `wake:${schedule.id}`, ...(schedule.bindingKind === "goal" ? goalExecutionBinding(schedule.agent, schedule.goalId) : specialistBinding(schedule.agent, schedule.specialistRole)), triggerRef: `${schedule.id}@${schedule.nextWakeAt}`, status: "queued", attempt: 0, enqueuedSeq: 0, claimedAt: null, consumedAt: null, turnId: null };
614
- return this.ledger.consumeSchedule(schedule.id, wake, this.#now()).wake;
615
- }
616
- #enqueueTrigger(agent, triggerRef, target) {
617
- let binding;
618
- if (target) {
619
- const goal = this.ledger.goal(target.goalId);
620
- if (!goal || goal.owner !== agent || goal.phase !== "active" || !this.#validGoalOwner(agent, goal))
621
- return null;
622
- binding = goalExecutionBinding(agent, target.goalId);
623
- }
624
- else {
625
- const mailId = mailIdFromTriggerRef(triggerRef);
626
- const mail = mailId ? this.ledger.mailbox().find((candidate) => candidate.id === mailId) : null;
627
- const role = this.#role(agent);
628
- if (agent === "ceo" && role === "ceo" && mail?.routeKind === "human_inbox" && mail.from === "human")
629
- binding = humanBinding();
630
- else if (role === "verifier" || role === "audit")
631
- binding = specialistBinding(agent, role);
632
- else
633
- return null;
634
- }
635
- const sameBinding = (wake) => wake.bindingKind === binding.bindingKind && wake.goalId === binding.goalId && wake.specialistRole === binding.specialistRole;
636
- const exact = this.ledger.wakeByTrigger(agent, triggerRef);
637
- if (exact) {
638
- if (!sameBinding(exact))
639
- throw new Error("Wake trigger was reused with a different execution binding");
640
- return exact;
641
- }
642
- const pending = this.ledger.wakes().find((wake) => wake.agent === agent && wake.status === "claimed" && sameBinding(wake)) ?? this.ledger.wakes().find((wake) => wake.agent === agent && wake.status === "queued" && sameBinding(wake));
643
- if (pending) {
644
- this.ledger.addWakeTrigger(pending.id, triggerRef, "supervisor");
645
- return pending;
646
- }
647
- const wake = { id: randomUUID(), ...binding, triggerRef, status: "queued", attempt: 0, enqueuedSeq: 0, claimedAt: null, consumedAt: null, turnId: null };
648
- const result = this.ledger.enqueueWake(wake, "supervisor");
649
- if (result.created)
650
- return this.#wake(wake.id);
651
- const existing = this.ledger.wakeByTrigger(agent, triggerRef);
652
- if (!existing)
653
- throw new Error("deduplicated wake is missing");
654
- return existing;
655
- }
656
- #hasActiveRoot() { return this.ledger.goals().some((goal) => goal.parentId === null && goal.owner === "ceo" && goal.phase === "active"); }
657
- #activeRoot() { return this.ledger.goals().find((goal) => goal.parentId === null && goal.owner === "ceo" && goal.phase === "active") ?? null; }
658
- #singleActiveGoalTarget(agent) { const goals = this.ledger.goalsForOwner(agent).filter((goal) => goal.phase === "active" && this.#validGoalOwner(agent, goal)); if (goals.length > 1)
659
- throw new Error("Wake requires an explicit Goal target when an Agent owns multiple active Goals"); return goals.length === 1 ? { goalId: goals[0].id } : undefined; }
660
- #knownAgent(agent) { return this.#profiles.has(agent) || this.ledger.threads().some((thread) => thread.agent === agent) || this.ledger.goals().some((goal) => goal.owner === agent); }
661
- #role(agent) { return this.#profiles.get(agent)?.role ?? (agent === "ceo" ? "ceo" : "child"); }
662
- #validGoalOwner(agent, goal) { const role = this.#role(agent); return goal.owner === agent && (role === "ceo" ? agent === "ceo" && goal.parentId === null : role === "child" && goal.parentId !== null); }
663
- #validExecution(agent, turn) { const role = this.#role(agent); if (turn.goalBinding) {
664
- const goal = this.ledger.goal(turn.goalBinding.goalId);
665
- return Boolean(goal && this.#validGoalOwner(agent, goal));
666
- } if (turn.source.kind === "human")
667
- return agent === "ceo" && role === "ceo"; return role === "verifier" || role === "audit"; }
668
- #commitGoalTurn(turnId, agent, turn, raw, sourceWakeId, mailIds, revisionAtStart) { const binding = turn.goalBinding; const goal = this.#goal(binding.goalId); if (goal.revision !== binding.goalRevision)
669
- throw new Error("Goal revision changed during the Turn"); const record = this.ledger.workRecord(goal.id); if (!record || record.recordRevision <= revisionAtStart || record.updatedInTurn !== turnId || record.goalRevision !== goal.revision)
670
- throw new Error("Goal-bound Turn must update its Work Record before handoff"); assertTurnOutput(raw); const validation = this.#handoffValidations.get(raw.validationToken); const execution = this.ledger.turn(turnId); if (!validation || !execution || validation.turnId !== turnId || validation.attemptId !== raw.validationAttemptId || this.#handoffAttemptSeq.get(turnId) !== raw.validationAttemptId || validation.agent !== agent || validation.attempt !== execution.attempt || validation.leaseToken !== execution.leaseToken || validation.goalId !== goal.id || validation.goalRevision !== goal.revision || validation.handoff.outcome !== raw.handoff.outcome || !isSameNumbers(validation.handoff.evidence, raw.handoff.evidence))
671
- throw new Error("Handoff validation token is stale or does not match this Turn"); const responseItem = [...this.ledger.turnItems(turnId)].reverse().find((item) => item.type === "assistant_message" && item.status === "completed" && item.data.text === validation.message); if (!responseItem)
672
- throw new Error("accepted Handoff response was not recorded in the Turn"); const output = this.#goalTurnOutput(raw, binding); assertHandoff(output.handoff); const now = this.#now(); const item = { id: randomUUID(), turnId, ordinal: this.ledger.turnItems(turnId).length + 1, type: "handoff", status: "completed", data: output.handoff, createdAt: now, completedAt: now }; this.ledger.commitHandoff({ agent, turnId, sourceWakeId, mailIds, ts: now, output, responseItemId: responseItem.id, item }); this.#clearHandoffState(turnId); }
673
- #goalTurnOutput(output, binding) {
674
- const record = this.ledger.workRecord(binding.goalId);
675
- if (!record)
676
- throw new Error("Goal Work Record is missing");
677
- const handoff = {
678
- goalId: binding.goalId,
679
- goalRevision: binding.goalRevision,
680
- recordRevision: record.recordRevision,
681
- outcome: output.handoff.outcome,
682
- evidence: output.handoff.evidence,
683
- };
684
- return { handoff };
685
- }
686
- #turnContext(wake, triggers = this.ledger.wakeTriggers(wake.id).filter((trigger) => trigger.status === "pending")) {
687
- if (wake.bindingKind === "human")
688
- return { source: { kind: "human" } };
689
- if (wake.bindingKind === "specialist")
690
- return { source: { kind: "system", reason: triggers.map((trigger) => trigger.triggerRef).join(", ") } };
691
- const goal = this.#goal(wake.goalId);
692
- return { source: { kind: "goal", round: (this.ledger.workRecord(goal.id)?.recordRevision ?? 0) + 1 }, goalBinding: { goalId: goal.id, goalRevision: goal.revision } };
693
- }
694
- #loadContext(wake, turn, mail, wakeTriggers) {
695
- const profile = this.#profiles.get(wake.agent) ?? { agent: wake.agent, role: "child" };
696
- const role = profile.role;
697
- const capabilities = profile.capabilities ?? defaultCapabilities(role);
698
- const runnerProfile = this.#runnerProfiles.get(profile.runnerProfile ?? "default");
699
- const goals = role === "ceo" ? this.ledger.goals() : this.ledger.goalsForOwner(wake.agent);
700
- const handoff = turn.goalBinding ? this.ledger.lastGoalHandoff(turn.goalBinding.goalId) : null;
701
- const recoveryIds = [...new Set(wakeTriggers.flatMap((trigger) => { const parsed = parseRecoveryTrigger(trigger.triggerRef); const exhausted = childRetryExhaustedTurnId(trigger.triggerRef); return parsed ? [parsed.turnId] : exhausted ? [exhausted] : []; }))];
702
- const recoveryEvents = recoveryIds.flatMap((recoveryId) => selectRecoveryEvents(this.ledger.turn(recoveryId) ? this.ledger.readStream(`turn:${recoveryId}`) : this.ledger.eventsForWake(recoveryId)));
703
- const teamHandoffs = role === "ceo"
704
- ? [...this.ledger.eventsSince(0, ["handoff.recorded"])].reverse().filter((event, index, all) => all.findIndex((candidate) => candidate.actor === event.actor) === index)
705
- : [];
706
- const workingMemory = selectWorkingMemory(this.ledger.readStream(memoryStream(wake.agent)), this.#memoryTailChars);
707
- const active = composeActiveContext({ role, capabilities, systemPrompt: profile.systemPrompt ?? defaultTurnPrompt(role, wake.agent, turn), wake, wakeTriggers, goals, mail, lastHandoff: handoff, teamHandoffs, team: role === "ceo" ? this.teamList() : [], recoveryEvents, workingMemory });
708
- const records = this.ledger.workRecords();
709
- const currentRecord = turn.goalBinding ? this.ledger.workRecord(turn.goalBinding.goalId) : null;
710
- const currentGoal = turn.goalBinding ? this.ledger.goal(turn.goalBinding.goalId) : null;
711
- const parentRecord = currentGoal?.parentId ? this.ledger.workRecord(currentGoal.parentId) : null;
712
- 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"}`);
713
- 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");
714
- return { ...active, text: `${active.text}\n\n${workText}`, sourceSeqs: [...new Set([...active.sourceSeqs, ...this.#mailSourceSeqs(mail), ...records.map((record) => record.lastEventSeq)])].sort((a, b) => a - b), workRecord: currentRecord, sharedWorkRecords: records, ...(runnerProfile ? { runnerProfile } : {}) };
715
- }
716
- #wake(id) { const value = this.ledger.wake(id); if (!value)
717
- throw new Error(`wake not found: ${id}`); return value; }
718
- #goal(id) { const value = this.ledger.goal(id); if (!value)
719
- throw new Error(`goal not found: ${id}`); return value; }
720
- #assertGoalMutationAuthority(goal, actor) { if (goal.parentId === null) {
721
- if (actor !== "human")
722
- throw new Error("only human may modify a root goal");
723
- return;
724
- } const parent = this.ledger.goal(goal.parentId); if (parent?.owner !== actor)
725
- throw new Error("only the parent goal owner may modify a child goal"); }
726
- #now() { return this.clock.now().toISOString(); }
727
- #futureTime(value, after) { const parsed = Date.parse(value); if (!Number.isFinite(parsed) || parsed <= Date.parse(after))
728
- throw new Error("nextWakeAt must be later than the Turn start"); return new Date(parsed).toISOString(); }
729
- #runnerProfileId(agent) { return this.#profiles.get(agent)?.runnerProfile ?? "default"; }
730
- #contextMail(target) { const selected = []; let used = 0; const matches = (mail) => target.bindingKind === "goal" ? mail.routeKind === "goal" && mail.goalId === target.goalId : target.bindingKind === "human" ? mail.routeKind === "human_inbox" : mail.routeKind === "specialist_inbox" && mail.specialistRole === target.specialistRole; for (const mail of this.ledger.unreadMail(target.agent).filter(matches)) {
731
- const cost = Math.min(JSON.stringify(mail.body).length, 2_000) + 128;
732
- if (selected.length >= 20 || selected.length > 0 && used + cost > this.#memoryTailChars)
733
- break;
734
- selected.push(mail);
735
- used += cost;
736
- } return selected; }
737
- #mailSourceSeqs(mail) { const ids = new Set(mail.map((item) => item.id)); if (!ids.size)
738
- return []; return this.ledger.eventsSince(0, ["mail.put"]).filter((event) => { const data = event.data && typeof event.data === "object" && !Array.isArray(event.data) ? event.data : {}; return typeof data.snapshot?.id === "string" && ids.has(data.snapshot.id); }).map((event) => event.seq); }
739
- async #agentRpcForTurn(turnId, agent, context, method, params, sourceWakeId) {
740
- const handoffAttemptId = method === "goal.handoff.validate" ? this.#beginHandoffAttempt(turnId) : null;
741
- const execution = this.ledger.turn(turnId);
742
- if (!execution || execution.status !== "in_progress" || !execution.leaseToken || !execution.leaseUntil || execution.leaseUntil < this.#now())
743
- throw new Error("stale Turn RPC rejected");
744
- if (context.goalBinding) {
745
- const goal = this.ledger.goal(context.goalBinding.goalId);
746
- const thread = this.ledger.thread(execution.threadId);
747
- if (!goal || goal.phase !== "active" || goal.revision !== context.goalBinding.goalRevision || goal.owner !== agent || execution.bindingKind !== "goal" || execution.goalId !== goal.id || execution.goalRevision !== goal.revision || thread?.agent !== agent)
748
- throw new Error("stale Goal-bound Turn RPC rejected");
749
- }
750
- const input = asRecord(params);
751
- if (method === "goal.handoff.validate") {
752
- const candidate = String(input.candidateMessage ?? "");
753
- this.ledger.appendEvent({ streamId: `turn:${turnId}`, ts: this.#now(), actor: agent, type: "rpc.goal.handoff.validate", data: { attemptId: handoffAttemptId, handoff: input.handoff ?? null, candidateMessagePresent: Boolean(candidate.trim()), candidateMessageChars: candidate.length }, ignorable: true });
754
- return this.#validateHandoffDraft(turnId, handoffAttemptId, agent, execution, context, input);
755
- }
756
- const profile = this.#profiles.get(agent) ?? { agent, role: "child" };
757
- const allowed = new Set(profile.capabilities ?? defaultCapabilities(profile.role));
758
- if (!allowed.has(method))
759
- throw new Error(`${profile.role} agent is not allowed to call ${method}`);
760
- if (!context.goalBinding && goalBoundCapability(method))
761
- throw new Error(`${method} requires a Goal-bound Turn`);
762
- this.ledger.appendEvent({ streamId: `turn:${turnId}`, ts: this.#now(), actor: agent, type: `rpc.${method}`, data: params, ignorable: true });
763
- if (method === "ledger.search")
764
- return this.ledger.searchEvents(String(input.query), Number(input.limit ?? 20));
765
- if (method === "team.list")
766
- return this.teamList();
767
- if (method === "goal.get")
768
- return (context.goalBinding ? this.ledger.goal(context.goalBinding.goalId) : null);
769
- if (method === "goal.create") {
770
- if (context.source.kind !== "human" || context.goalBinding || agent !== "ceo")
771
- throw new Error("Root Goal creation requires an unbound CEO Human Turn");
772
- const goal = this.createRootGoal(String(input.objective), typeof input.id === "string" ? input.id : undefined, turnId);
773
- context.goalBinding = { goalId: goal.id, goalRevision: goal.revision };
774
- this.ledger.putTurn({ ...this.ledger.turn(turnId), ...goalTurnBinding(goal.id, goal.revision) }, "supervisor");
775
- return { goal, goalBinding: context.goalBinding };
776
- }
777
- if (method === "goal.work") {
778
- if (context.source.kind !== "human" || context.goalBinding)
779
- throw new Error("work_on_goal requires an unbound Human Turn");
780
- const goal = this.#goal(String(input.goalId));
781
- if (goal.phase !== "active" || !this.#validGoalOwner(agent, goal))
782
- throw new Error("work_on_goal requires the active owned Root Goal");
783
- context.goalBinding = { goalId: goal.id, goalRevision: goal.revision };
784
- this.ledger.putTurn({ ...this.ledger.turn(turnId), ...goalTurnBinding(goal.id, goal.revision) }, "supervisor");
785
- return { goal, goalBinding: context.goalBinding };
786
- }
787
- if (method === "work_record.list")
788
- return this.ledger.workRecords();
789
- if (method === "work_record.read")
790
- return this.ledger.workRecord(String(input.goalId ?? context.goalBinding?.goalId ?? ""));
791
- if (method === "work_record.history")
792
- return this.ledger.workRecordHistory(String(input.goalId ?? context.goalBinding?.goalId ?? ""));
793
- if (method === "work_record.diff")
794
- return this.ledger.workRecordDiff(String(input.goalId ?? context.goalBinding?.goalId ?? ""), Number(input.fromRevision), Number(input.toRevision));
795
- if (method === "work_record.search")
796
- return this.ledger.searchWorkRecords(String(input.query), Number(input.limit ?? 20));
797
- if (method === "work_record.update") {
798
- const binding = context.goalBinding;
799
- return this.ledger.updateWorkRecord({ goalId: binding.goalId, goalRevision: binding.goalRevision, expectedRevision: Number(input.expectedRevision), content: String(input.content), reason: String(input.reason), evidence: numberArray(input.evidence), turnId, ...(sourceWakeId ? { sourceWakeId } : {}) }, agent);
800
- }
801
- if (method === "goal.delegate") {
802
- const binding = context.goalBinding;
803
- if (String(input.parentGoalId) !== binding.goalId)
804
- throw new Error("delegation parent must be the Goal bound to this Turn");
805
- return this.delegate({ id: String(input.id), parentGoalId: binding.goalId, expectedParentRevision: Number(input.expectedParentRevision), childGoal: asChildGoal(input.childGoal), brief: (input.brief ?? null), reason: String(input.reason), evidence: numberArray(input.evidence), sourceTurnId: turnId }, agent, sourceWakeId);
806
- }
807
- if (method === "goal.reassign") {
808
- const goal = this.#goal(String(input.goalId));
809
- if (goal.parentId !== context.goalBinding.goalId)
810
- throw new Error("reassignment target must be a child of the Goal bound to this Turn");
811
- return await this.reassignGoal({ id: String(input.id), goalId: goal.id, expectedGoalRevision: Number(input.expectedGoalRevision), newOwner: String(input.newOwner), brief: (input.brief ?? null), reason: String(input.reason), evidence: numberArray(input.evidence), sourceTurnId: turnId }, agent, sourceWakeId);
812
- }
813
- if (method === "goal.revise")
814
- return this.reviseChildGoal(String(input.goalId), String(input.objective), String(input.observationMethod), String(input.verificationMethod), agent, String(input.reason), numberArray(input.evidence), sourceWakeId, turnId);
815
- if (method === "goal.pause" || method === "goal.resume") {
816
- const goal = this.#goal(String(input.goalId));
817
- const directRoot = !context.goalBinding && context.source.kind === "human" && goal.parentId === null;
818
- if (!context.goalBinding && !directRoot)
819
- throw new Error(`${method} requires a Goal-bound Turn`);
820
- const next = this.transitionGoal(goal.id, method === "goal.pause" ? "paused" : "active", directRoot ? "human" : agent, !directRoot, turnId);
821
- if (method === "goal.resume" && directRoot) {
822
- context.goalBinding = { goalId: next.id, goalRevision: next.revision };
823
- this.ledger.putTurn({ ...this.ledger.turn(turnId), ...goalTurnBinding(next.id, next.revision) }, "supervisor");
824
- return { goal: next, goalBinding: context.goalBinding };
825
- }
826
- return next;
827
- }
828
- if (method === "goal.complete") {
829
- const goal = this.#goal(String(input.goalId));
830
- const directRoot = !context.goalBinding && context.source.kind === "human" && goal.parentId === null;
831
- if (!context.goalBinding && !directRoot)
832
- throw new Error("goal.complete requires a Goal-bound Turn");
833
- return this.completeGoal({ goalId: goal.id, revision: Number(input.revision), reason: String(input.reason), evidence: numberArray(input.evidence), sourceTurnId: turnId }, directRoot ? "human" : agent, sourceWakeId);
834
- }
835
- if (method === "mail.send") {
836
- const to = String(input.to);
837
- if (to === "human")
838
- throw new Error("Human communication requires human.request");
839
- if (!this.#knownAgent(to))
840
- throw new Error(`unknown Agent recipient: ${to}`);
841
- const goalId = String(input.goalId ?? "").trim();
842
- if (!goalId)
843
- throw new Error("Agent Mail requires a Goal route");
844
- const goal = this.#goal(goalId);
845
- if (!this.#validGoalOwner(to, goal))
846
- throw new Error("Agent Mail Goal route does not match the recipient role and ownership");
847
- const mail = { id: randomUUID(), to, from: agent, level: String(input.level), ...goalRoute(goalId), body: (input.body ?? null), readAt: null };
848
- this.ledger.putMail(mail, agent, sourceWakeId);
849
- return mail;
850
- }
851
- if (method === "human.request") {
852
- if (agent !== "ceo")
853
- throw new Error("only CEO may request Human input");
854
- const evidence = numberArray(input.evidence);
855
- if (!evidence.length)
856
- throw new Error("evidence is required");
857
- for (const seq of evidence)
858
- if (!this.ledger.eventsSince(seq - 1).some((event) => event.seq === seq))
859
- throw new Error(`evidence event does not exist: ${seq}`);
860
- const mail = { id: randomUUID(), to: "human", from: agent, level: "decision", ...humanRequestRoute(), body: { type: String(input.type), message: input.message ?? null, evidence }, readAt: null };
861
- this.ledger.putMail(mail, agent, sourceWakeId);
862
- return mail;
863
- }
864
- if (method === "schedule.set")
865
- return (this.planWake(agent, String(input.at), String(input.reason), agent, context.goalBinding) ?? { scheduled: true });
866
- if (method === "goal.put") {
867
- const next = input.goal;
868
- if (!this.#validGoalOwner(next.owner, next))
869
- throw new Error("Goal owner role does not match Root/Child position");
870
- const current = this.ledger.goal(next.id);
871
- const operation = !current ? "create" : current.phase !== next.phase ? (next.phase === "paused" ? "pause" : next.phase === "active" ? "resume" : next.phase === "blocked" ? "block" : "complete") : current.owner !== next.owner ? "reassign" : "revise";
872
- this.ledger.putGoal(next, agent, sourceWakeId, { operation, reason: String(input.reason ?? "Advanced Goal mutation"), evidence: numberArray(input.evidence ?? []), sourceTurnId: turnId, ...(sourceWakeId ? { sourceWakeId } : {}) });
873
- return input.goal;
874
- }
875
- if (method === "memory.append") {
876
- const note = String(input.note ?? "").trim();
877
- if (!note)
878
- throw new Error("memory note cannot be empty");
879
- const event = this.ledger.appendEvent({ streamId: memoryStream(agent), ts: this.#now(), actor: agent, type: "memory.appended", data: { note, turnId } });
880
- return { seq: event.seq };
881
- }
882
- throw new Error(`unsupported Agent capability: ${method}`);
883
- }
884
- #validateHandoffDraft(turnId, attemptId, agent, execution, context, input) {
885
- if (!context.goalBinding || execution.bindingKind !== "goal")
886
- return { accepted: false, fatal: true, attemptId, issues: [{ code: "turn_not_goal_bound", message: "This Turn is no longer Goal-bound." }] };
887
- const request = { handoff: (input.handoff ?? null), candidateMessage: String(input.candidateMessage ?? "") };
888
- const issues = [];
889
- try {
890
- assertAgentHandoff(request.handoff);
891
- }
892
- catch {
893
- issues.push({ code: "handoff_invalid", message: "Handoff requires a valid outcome and at least one evidence event." });
894
- }
895
- const existingMessage = [...this.ledger.turnItems(turnId)].reverse().find((item) => item.type === "assistant_message" && item.status === "completed" && typeof item.data.text === "string" && Boolean(item.data.text.trim()));
896
- const message = request.candidateMessage.trim() || String(existingMessage?.data?.text ?? "").trim();
897
- if (!message)
898
- issues.push({ code: "message_missing", message: "This Goal Turn needs a readable assistant message before it can finish." });
899
- const goal = this.ledger.goal(context.goalBinding.goalId);
900
- if (!goal || goal.phase !== "active" || goal.owner !== agent || goal.revision !== context.goalBinding.goalRevision)
901
- return { accepted: false, fatal: true, attemptId, issues: [{ code: "goal_fence_changed", message: "Goal ownership, phase, or revision changed; this Turn can no longer finish against the old binding." }] };
902
- const record = this.ledger.workRecord(goal.id);
903
- if (!record || record.updatedInTurn !== turnId || record.goalRevision !== goal.revision)
904
- issues.push({ code: "work_record_not_updated", message: "Update this Goal's Work Record in the current Turn before Handoff.", details: { goalId: goal.id, recordRevision: record?.recordRevision ?? null } });
905
- const evidence = Array.isArray(request.handoff?.evidence) ? request.handoff.evidence : [];
906
- for (const seq of evidence)
907
- if (!Number.isInteger(seq) || !this.ledger.eventsSince(seq - 1).some((event) => event.seq === seq))
908
- issues.push({ code: "evidence_not_found", message: `Evidence event ${String(seq)} does not exist in the Ledger.`, details: { seq } });
909
- if (issues.length)
910
- return { accepted: false, fatal: false, attemptId, issues };
911
- const token = randomUUID();
912
- this.#handoffValidations.set(token, { turnId, attemptId, agent, attempt: execution.attempt, leaseToken: execution.leaseToken, goalId: goal.id, goalRevision: goal.revision, message, handoff: { outcome: request.handoff.outcome, evidence: [...request.handoff.evidence] } });
913
- return { accepted: true, fatal: false, attemptId, token, goalId: goal.id, goalRevision: goal.revision };
914
- }
915
- #beginHandoffAttempt(turnId) { this.#invalidateHandoffTokens(turnId); const attemptId = (this.#handoffAttemptSeq.get(turnId) ?? 0) + 1; this.#handoffAttemptSeq.set(turnId, attemptId); return attemptId; }
916
- #invalidateHandoffTokens(turnId) { for (const [token, value] of this.#handoffValidations)
917
- if (value.turnId === turnId)
918
- this.#handoffValidations.delete(token); }
919
- #clearHandoffState(turnId) { this.#invalidateHandoffTokens(turnId); this.#handoffAttemptSeq.delete(turnId); }
920
- }
921
- export async function runSupervisorDaemon(supervisor, options = {}) {
922
- const pollMs = options.pollMs ?? 1_000;
923
- await supervisor.recover();
924
- while (!options.signal?.aborted) {
925
- try {
926
- await supervisor.runAvailable(options.concurrency ?? 4);
927
- }
928
- catch (error) {
929
- options.onError?.(error);
930
- }
931
- await new Promise((resolve) => {
932
- if (options.signal?.aborted) {
933
- resolve();
934
- return;
935
- }
936
- const onAbort = () => { clearTimeout(timer); resolve(); };
937
- const timer = setTimeout(() => { options.signal?.removeEventListener("abort", onAbort); resolve(); }, pollMs);
938
- options.signal?.addEventListener("abort", onAbort, { once: true });
939
- if (options.signal?.aborted)
940
- onAbort();
941
- });
942
- }
943
- }
944
- export function deriveTeam(ledger, now = new Date().toISOString()) {
945
- const goals = ledger.goals();
946
- const wakes = ledger.wakes();
947
- const schedules = ledger.schedules();
948
- const threads = ledger.threads();
949
- const turns = ledger.turns();
950
- const handoffs = ledger.eventsSince(0, ["handoff.recorded"]);
951
- const owners = [...new Set(goals.map((goal) => goal.owner))].sort();
952
- return owners.map((agent) => {
953
- const owned = goals.filter((goal) => goal.owner === agent);
954
- const live = owned.filter((goal) => goal.phase !== "complete");
955
- const agentWakes = wakes.filter((wake) => wake.agent === agent).sort((a, b) => b.enqueuedSeq - a.enqueuedSeq);
956
- const activeWake = agentWakes.find((wake) => { if (wake.status !== "claimed" && wake.status !== "queued")
957
- return false; if (!wake.goalId)
958
- return true; const goal = ledger.goal(wake.goalId); return goal?.phase === "active" && goal.owner === agent; });
959
- const threadIds = new Set(threads.filter((thread) => thread.agent === agent).map((thread) => thread.id));
960
- const activeTurn = turns.find((turn) => threadIds.has(turn.threadId) && turn.status === "in_progress");
961
- const nextWakeAt = schedules.filter((schedule) => { if (schedule.agent !== agent || schedule.status !== "pending" || schedule.nextWakeAt <= now)
962
- return false; if (!schedule.goalId)
963
- return true; const goal = ledger.goal(schedule.goalId); return goal?.phase === "active" && goal.owner === agent; }).map((schedule) => schedule.nextWakeAt).sort()[0] ?? null;
964
- const lastHandoff = [...handoffs].reverse().find((event) => event.actor === agent) ?? null;
965
- const value = lastHandoff?.data;
966
- const lastOutcome = typeof value?.outcome === "string" ? value.outcome : null;
967
- let motion;
968
- if (live.length === 0)
969
- motion = "retired";
970
- else if (activeTurn)
971
- motion = "running";
972
- else if (activeWake)
973
- motion = "queued";
974
- else if (nextWakeAt)
975
- motion = "scheduled";
976
- else
977
- motion = "idle";
978
- return { agent, goalIds: owned.map((goal) => goal.id), motion, lastOutcome, lastHandoffSeq: lastHandoff?.seq ?? null, lastWakeStatus: agentWakes[0]?.status ?? null, nextWakeAt };
979
- });
980
- }
981
- export function deriveRecoveryViews(ledger) {
982
- const views = [];
983
- const wakes = ledger.wakes();
984
- const schedules = ledger.schedules();
985
- const triggers = wakes.flatMap((wake) => ledger.wakeTriggers(wake.id));
986
- for (const turn of ledger.turns()) {
987
- if (turn.status !== "failed")
988
- continue;
989
- const sourceWake = wakes.find((wake) => wake.turnId === turn.id);
990
- const thread = ledger.thread(turn.threadId);
991
- const goal = turn.bindingKind === "goal" ? ledger.goal(turn.goalId) : null;
992
- const goalFailure = Boolean(turn.bindingKind === "goal" && thread && goal && goal.phase === "active" && goal.owner === thread.agent);
993
- const specialistFailure = Boolean(turn.bindingKind === "specialist" && thread && sourceWake?.agent === thread.agent);
994
- if (!sourceWake || !thread || !goalFailure && !specialistFailure)
995
- continue;
996
- const base = { turnId: turn.id, agent: thread.agent };
997
- const scheduled = schedules.some((schedule) => {
998
- const recovery = parseRecoveryTrigger(schedule.id);
999
- const sameTarget = goalFailure ? schedule.bindingKind === "goal" && schedule.goalId === goal.id : schedule.bindingKind === "specialist" && turn.bindingKind === "specialist" && schedule.specialistRole === turn.specialistRole;
1000
- return schedule.status === "pending" && schedule.setBy === "supervisor" && schedule.agent === thread.agent && sameTarget && schedule.reason === recoveryRef(turn.id) && recovery?.turnId === turn.id && recovery.attempt > 0;
1001
- });
1002
- if (scheduled) {
1003
- views.push({ ...base, state: "scheduled", actionable: false });
1004
- continue;
1005
- }
1006
- const recoveryWake = triggers.flatMap((trigger) => {
1007
- const recovery = parseRecoveryTrigger(trigger.triggerRef);
1008
- const wake = recovery?.turnId === turn.id ? wakes.find((candidate) => candidate.id === trigger.wakeId) : null;
1009
- const sameTarget = goalFailure ? wake?.bindingKind === "goal" && wake.goalId === goal.id : wake?.bindingKind === "specialist" && turn.bindingKind === "specialist" && wake.specialistRole === turn.specialistRole;
1010
- return wake?.agent === thread.agent && sameTarget ? [wake] : [];
1011
- }).sort((left, right) => right.enqueuedSeq - left.enqueuedSeq)[0];
1012
- if (recoveryWake?.status === "queued" || recoveryWake?.status === "claimed") {
1013
- views.push({ ...base, state: "queued", actionable: false });
1014
- continue;
1015
- }
1016
- if (recoveryWake?.status === "consumed" && recoveryWake.turnId) {
1017
- const retry = ledger.turn(recoveryWake.turnId);
1018
- const state = retry?.status === "in_progress" ? "running" : retry?.status === "completed" ? "recovered" : "superseded";
1019
- views.push({ ...base, state, actionable: false });
1020
- continue;
1021
- }
1022
- const parent = goalFailure && goal.parentId ? ledger.goal(goal.parentId) : null;
1023
- const escalation = triggers.flatMap((trigger) => {
1024
- const wake = trigger.triggerRef === childRetryExhaustedRef(turn.id) ? wakes.find((candidate) => candidate.id === trigger.wakeId) : null;
1025
- return wake?.bindingKind === "goal" && parent && wake.agent === parent.owner && wake.goalId === parent.id && parent.phase === "active" ? [wake] : [];
1026
- }).find((wake) => wake.status === "queued" || wake.status === "claimed" || wake.status === "consumed");
1027
- if (escalation) {
1028
- views.push({ ...base, state: "escalated", actionable: false });
1029
- continue;
1030
- }
1031
- views.push({ ...base, state: "needed", actionable: true });
1032
- }
1033
- return views;
1034
- }
1035
- export function renderDashboard(ledger) {
1036
- const rows = (values) => values.map((value) => `<tr><td><pre>${escapeHtml(JSON.stringify(value, null, 2))}</pre></td></tr>`).join("");
1037
- 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>Mailbox</h2><table>${rows(ledger.mailbox())}</table></section></body></html>`;
1038
- }
1039
- function escapeHtml(value) { return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"); }
1040
- function messageTextContent(content) { if (typeof content === "string")
1041
- return content; if (!Array.isArray(content))
1042
- return ""; return content.map((part) => part && typeof part === "object" && !Array.isArray(part) && part.type === "text" ? String(part.text ?? "") : "").filter(Boolean).join("\n"); }
1043
- function goalBoundCapability(method) { return ["goal.delegate", "goal.reassign", "goal.revise", "goal.put", "work_record.update", "schedule.set", "human.request"].includes(method); }
1044
- function asRecord(value) { if (typeof value !== "object" || value === null || Array.isArray(value))
1045
- throw new Error("RPC params must be an object"); return value; }
1046
- function boundedJson(value, maxChars = 2_000) { const text = typeof value === "string" ? value : JSON.stringify(value); return text.length <= maxChars ? text : `${text.slice(0, maxChars)}…`; }
1047
- function recoveryRef(turnId) { return `recovery:${turnId}`; }
1048
- function recoveryScheduleId(turnId, attempt) { return `${recoveryRef(turnId)}:${attempt}`; }
1049
- function childRetryExhaustedRef(turnId) { return `child-retry-exhausted:${turnId}`; }
1050
- function childRetryExhaustedTurnId(triggerRef) { return triggerRef.startsWith("child-retry-exhausted:") ? triggerRef.slice("child-retry-exhausted:".length) || null : null; }
1051
- function parseRecoveryTrigger(triggerRef) { if (!triggerRef.startsWith("recovery:"))
1052
- return null; const [turnId, rawAttempt] = triggerRef.slice("recovery:".length).split("@")[0].split(":"); if (!turnId)
1053
- return null; const attempt = rawAttempt === undefined ? 0 : Number(rawAttempt); return Number.isInteger(attempt) && attempt >= 0 ? { turnId, attempt } : null; }
1054
- function mailIdFromTriggerRef(triggerRef) { if (!triggerRef.startsWith("mail:"))
1055
- return null; return triggerRef.slice("mail:".length).split("@redelivery:")[0] || null; }
1056
- function mailDeliveryAttempt(triggerRef, base) { if (triggerRef === base)
1057
- return 0; const prefix = `${base}@redelivery:`; if (!triggerRef.startsWith(prefix))
1058
- return null; const attempt = Number(triggerRef.slice(prefix.length)); return Number.isInteger(attempt) && attempt > 0 ? attempt : null; }
1059
- function numberArray(value) { if (!Array.isArray(value) || value.some((item) => typeof item !== "number"))
1060
- throw new Error("RPC evidence must be a number array"); return value; }
1061
- function isSameNumbers(left, right) { return left.length === right.length && left.every((value, index) => value === right[index]); }
1062
- function asChildGoal(value) {
1063
- const input = asRecord(value ?? null);
1064
- return { id: String(input.id), objective: String(input.objective), observationMethod: String(input.observationMethod), verificationMethod: String(input.verificationMethod), owner: String(input.owner) };
1065
- }
1066
- function defaultCapabilities(role) {
1067
- if (role === "ceo")
1068
- return ["ledger.search", "mail.send", "schedule.set", "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"];
1069
- if (role === "verifier" || role === "audit")
1070
- return ["ledger.search", "mail.send", "memory.append"];
1071
- return ["ledger.search", "mail.send", "schedule.set", "goal.get", "work_record.list", "work_record.read", "work_record.history", "work_record.diff", "work_record.search", "work_record.update"];
1072
- }
1073
- export * from "./verification.js";
1074
- export * from "./roles.js";
1075
- //# sourceMappingURL=index.js.map