@goah/cli 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/cli.js +6 -3
  2. package/dist/cli.js.map +1 -1
  3. package/dist/console/assets/{index-BPOrMqEr.js → index-BJTGvlCH.js} +2 -2
  4. package/dist/console/assets/{index-BPOrMqEr.js.map → index-BJTGvlCH.js.map} +1 -1
  5. package/dist/console/index.html +1 -1
  6. package/dist/control.d.ts +14 -0
  7. package/dist/control.d.ts.map +1 -1
  8. package/dist/control.js +13 -5
  9. package/dist/control.js.map +1 -1
  10. package/dist/tui.d.ts +1 -1
  11. package/dist/tui.d.ts.map +1 -1
  12. package/dist/tui.js +51 -10
  13. package/dist/tui.js.map +1 -1
  14. package/dist/web-console.js +1 -2
  15. package/dist/web-console.js.map +1 -1
  16. package/node_modules/goah-ledger-contract/dist/execution.d.ts +79 -2
  17. package/node_modules/goah-ledger-contract/dist/execution.d.ts.map +1 -1
  18. package/node_modules/goah-ledger-contract/dist/execution.js +13 -2
  19. package/node_modules/goah-ledger-contract/dist/execution.js.map +1 -1
  20. package/node_modules/goah-ledger-contract/dist/kernel.d.ts +2 -1
  21. package/node_modules/goah-ledger-contract/dist/kernel.d.ts.map +1 -1
  22. package/node_modules/goah-ledger-contract/dist/kernel.js +2 -1
  23. package/node_modules/goah-ledger-contract/dist/kernel.js.map +1 -1
  24. package/node_modules/goah-ledger-sqlite/dist/index.d.ts +9 -2
  25. package/node_modules/goah-ledger-sqlite/dist/index.d.ts.map +1 -1
  26. package/node_modules/goah-ledger-sqlite/dist/index.js +183 -25
  27. package/node_modules/goah-ledger-sqlite/dist/index.js.map +1 -1
  28. package/node_modules/goah-runner-pi/dist/index.d.ts +2 -1
  29. package/node_modules/goah-runner-pi/dist/index.d.ts.map +1 -1
  30. package/node_modules/goah-runner-pi/dist/index.js +4 -2
  31. package/node_modules/goah-runner-pi/dist/index.js.map +1 -1
  32. package/node_modules/goah-runner-pi/dist/pi-worker.d.ts.map +1 -1
  33. package/node_modules/goah-runner-pi/dist/pi-worker.js +60 -16
  34. package/node_modules/goah-runner-pi/dist/pi-worker.js.map +1 -1
  35. package/node_modules/goah-runner-pi/dist/test-helpers.d.ts.map +1 -1
  36. package/node_modules/goah-runner-pi/dist/test-helpers.js +1 -0
  37. package/node_modules/goah-runner-pi/dist/test-helpers.js.map +1 -1
  38. package/node_modules/goah-supervisor/dist/context-view.d.ts.map +1 -1
  39. package/node_modules/goah-supervisor/dist/context-view.js +1 -0
  40. package/node_modules/goah-supervisor/dist/context-view.js.map +1 -1
  41. package/node_modules/goah-supervisor/dist/index.d.ts +7 -2
  42. package/node_modules/goah-supervisor/dist/index.d.ts.map +1 -1
  43. package/node_modules/goah-supervisor/dist/index.js +185 -34
  44. package/node_modules/goah-supervisor/dist/index.js.map +1 -1
  45. package/node_modules/goah-supervisor/dist/roles.js +2 -2
  46. package/node_modules/goah-supervisor/dist/roles.js.map +1 -1
  47. package/node_modules/goah-testkit/dist/faux-runner-worker.js +34 -7
  48. package/node_modules/goah-testkit/dist/faux-runner-worker.js.map +1 -1
  49. package/node_modules/goah-testkit/dist/index.d.ts +1 -0
  50. package/node_modules/goah-testkit/dist/index.d.ts.map +1 -1
  51. package/node_modules/goah-testkit/dist/index.js +5 -5
  52. package/node_modules/goah-testkit/dist/index.js.map +1 -1
  53. package/package.json +1 -1
@@ -94,16 +94,32 @@ export class Supervisor {
94
94
  }
95
95
  get runner() { return this.#runner; }
96
96
  createGoal(goal, actor = "human") { this.ledger.putGoal(goal, actor); }
97
- startGoal(objective, id = randomUUID()) {
97
+ createRootGoal(objective, id = randomUUID()) {
98
98
  if (!objective.trim())
99
99
  throw new Error("root objective is required");
100
- const goal = { id, parentId: null, objective, observationMethod: null, owner: "ceo", phase: "active", revision: 0 };
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 };
101
103
  this.ledger.putGoal(goal, "human");
104
+ return this.#goal(id);
105
+ }
106
+ startGoal(objective, id = randomUUID()) {
107
+ const goal = this.createRootGoal(objective, id);
102
108
  const wake = this.#enqueueTrigger("ceo", `root:${id}:created`);
103
109
  if (!wake)
104
110
  throw new Error("CEO wake was not admitted for an active root goal");
105
111
  return { goal, wake };
106
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
+ }
107
123
  sendToCeo(body, level = "decision") {
108
124
  const mail = { id: randomUUID(), to: "ceo", from: "human", level, body, readAt: null };
109
125
  this.ledger.putMail(mail, "human");
@@ -116,15 +132,16 @@ export class Supervisor {
116
132
  reassignGoal(request, actor = "ceo", wakeId) { return this.ledger.commitReassignment(request, actor, wakeId); }
117
133
  teamList(now = this.#now()) { return deriveTeam(this.ledger, now); }
118
134
  updateGoal(id, patch, actor = "human") {
119
- if (patch.objective === undefined && patch.observationMethod === undefined && patch.owner === undefined)
120
- throw new Error("goal update requires objective, observation method, or owner");
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");
121
137
  const current = this.#goal(id);
122
- if (patch.objective !== undefined && patch.objective !== current.objective && current.parentId !== null && patch.observationMethod === undefined)
123
- throw new Error("child objective revision requires a replacement observation method");
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");
124
140
  const next = {
125
141
  ...current,
126
142
  ...patch,
127
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 } : {}),
128
145
  revision: current.revision + 1,
129
146
  };
130
147
  this.ledger.putGoal(next, actor);
@@ -136,9 +153,9 @@ export class Supervisor {
136
153
  const current = this.#goal(id);
137
154
  if (current.parentId !== null)
138
155
  throw new Error("human confirmation applies only to a root goal");
139
- return this.updateGoal(id, { observationMethod }, "human");
156
+ return this.updateGoal(id, { observationMethod, verificationMethod: observationMethod }, "human");
140
157
  }
141
- reviseChildGoal(id, objective, observationMethod, actor, reason, evidence, wakeId) {
158
+ reviseChildGoal(id, objective, observationMethod, verificationMethod, actor, reason, evidence, wakeId) {
142
159
  const current = this.#goal(id);
143
160
  if (current.parentId === null)
144
161
  throw new Error("CEO cannot revise a root goal");
@@ -147,8 +164,8 @@ export class Supervisor {
147
164
  for (const seq of evidence)
148
165
  if (!this.ledger.eventsSince(seq - 1).some((event) => event.seq === seq))
149
166
  throw new Error(`evidence event does not exist: ${seq}`);
150
- 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, reason, evidence } });
151
- return this.updateGoal(id, { objective, observationMethod }, actor);
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);
152
169
  }
153
170
  completeGoal(request, actor = "human", wakeId) {
154
171
  const goal = this.ledger.completeGoal(request, actor, wakeId);
@@ -212,13 +229,17 @@ export class Supervisor {
212
229
  let renewal;
213
230
  try {
214
231
  running = this.ledger.markWakeRunning(wake.id, this.#now(), leaseToken);
215
- const context = this.#loadContext(running);
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);
216
236
  handle = this.runner.prepare({
217
237
  wake: running,
238
+ turn,
218
239
  context,
219
240
  now: () => this.#now(),
220
241
  emit: (trace) => this.ledger.appendRunnerEvent({ streamId: wakeStream(running.id), ts: this.#now(), actor: running.agent, type: trace.type, data: trace.data }, leaseToken),
221
- rpc: (method, params) => this.#agentRpc(running, leaseToken, method, params),
242
+ rpc: (method, params) => this.#agentRpc(running, turn, leaseToken, method, params),
222
243
  });
223
244
  this.#handles.set(running.id, handle);
224
245
  if (handle.pid)
@@ -243,14 +264,27 @@ export class Supervisor {
243
264
  await this.#markAbnormal(running, result.reason);
244
265
  return this.#wake(running.id);
245
266
  }
246
- this.#validateCeoHandoff(running, result.output);
247
- const outgoingMail = result.output.mail.map((draft) => ({ id: randomUUID(), to: draft.to, from: running.agent, level: draft.level, body: draft.body, readAt: null }));
248
- const schedule = result.output.nextWakeAt
249
- ? { id: `schedule:${running.agent}`, agent: running.agent, nextWakeAt: result.output.nextWakeAt, reason: "handoff.next_steps", setBy: running.agent }
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 }
250
284
  : null;
251
- const handoffEvent = this.ledger.commitHandoff({ agent: running.agent, wakeId: running.id, ts: this.#now(), output: result.output, outgoingMail, schedule });
285
+ const handoffEvent = this.ledger.commitHandoff({ agent: running.agent, wakeId: running.id, ts: this.#now(), output, outgoingMail, schedule });
252
286
  this.ledger.finishWake(running.id, "done", this.#now());
253
- if (this.#role(running.agent) !== "ceo" && (result.output.handoff.material || result.output.handoff.blocker) && this.#hasActiveRoot()) {
287
+ if (this.#role(running.agent) !== "ceo" && handoffTriggersCeo(output.handoff) && this.#hasActiveRoot()) {
254
288
  this.#enqueueTrigger("ceo", `child-handoff:${handoffEvent.seq}`);
255
289
  }
256
290
  if (this.#verifyMetricsAfterWake) {
@@ -416,10 +450,10 @@ export class Supervisor {
416
450
  if (exact)
417
451
  return exact;
418
452
  const ownsLiveGoal = this.ledger.goalsForOwner(agent).some((goal) => goal.phase === "active" || goal.phase === "blocked");
419
- const ceoInterrupt = agent === "ceo" && (triggerRef.startsWith("mail:") || triggerRef.startsWith("child-"));
453
+ const ceoInterrupt = agent === "ceo" && (triggerRef.startsWith("interaction:") || triggerRef.startsWith("mail:") || triggerRef.startsWith("child-"));
420
454
  if (!ownsLiveGoal && !ceoInterrupt)
421
455
  return null;
422
- const queued = this.ledger.queuedWakeForAgent(agent);
456
+ const queued = triggerRef.startsWith("interaction:") ? null : this.ledger.queuedWakeForAgent(agent);
423
457
  if (queued) {
424
458
  this.ledger.appendEvent({ streamId: wakeStream(queued.id), ts: this.#now(), actor: "supervisor", type: "wake.trigger_coalesced", data: { wakeId: queued.id, triggerRef } });
425
459
  return queued;
@@ -450,7 +484,7 @@ export class Supervisor {
450
484
  const activeRoot = this.#hasActiveRoot();
451
485
  const hasChildMotion = this.teamList().some((member) => member.agent !== "ceo" && !["idle_unplanned", "retired"].includes(member.status));
452
486
  const hasReview = Boolean(output.nextWakeAt);
453
- const hasBlocker = Boolean(output.handoff.blocker);
487
+ const hasBlocker = handoffBlocked(output.handoff);
454
488
  const asksHuman = output.mail.some((mail) => mail.to === "human" && (mail.level === "decision" || mail.level === "emergency"))
455
489
  || this.ledger.unreadMail("human").some((mail) => mail.from === "ceo" && (mail.level === "decision" || mail.level === "emergency"));
456
490
  if (idle.length === 0 && missingObservationGoalIds.length === 0 && (!activeRoot || hasChildMotion || hasReview || hasBlocker || asksHuman))
@@ -463,10 +497,61 @@ export class Supervisor {
463
497
  this.ledger.appendEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: "supervisor", type: "ceo.motion_invalid", data: violation });
464
498
  throw new Error(`CEO motion invalid: ${violation.reason}${violation.idleAgents.length ? ` (${violation.idleAgents.join(", ")})` : ""}`);
465
499
  }
466
- #loadContext(wake) {
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) {
467
532
  const profile = this.#profiles.get(wake.agent) ?? { agent: wake.agent, role: "child" };
468
533
  const role = profile.role;
469
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
+ }
470
555
  const goals = role === "ceo" ? this.ledger.goals() : this.ledger.goalsForOwner(wake.agent);
471
556
  const mail = this.ledger.unreadMail(wake.agent);
472
557
  const handoff = this.ledger.lastEvent(wake.agent, "handoff.recorded");
@@ -480,8 +565,14 @@ export class Supervisor {
480
565
  const actions = this.ledger.actions().filter((action) => action.agent === wake.agent && (action.status === "unknown" || Boolean(action.auditAdvice && !action.adviceAcked)));
481
566
  const revisionWarnings = goals.flatMap((goal) => this.#goalRevisionWarning(goal));
482
567
  const workingMemory = selectWorkingMemory(this.ledger.readStream(memoryStream(wake.agent)), this.#memoryTailChars);
483
- const runnerProfile = this.#runnerProfiles.get(profile.runnerProfile ?? "default");
484
- return { ...composeActiveContext({ role, capabilities, systemPrompt: profile.systemPrompt ?? defaultRolePrompt(role), wake, goals, mail, actions, lastHandoff: handoff, teamHandoffs, team: role === "ceo" ? this.teamList() : [], revisionWarnings, recoveryEvents, workingMemory }), ...(runnerProfile ? { runnerProfile } : {}) };
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 } : {}) };
485
576
  }
486
577
  #requiredConnector(name) { const value = this.#connectors.get(name); if (!value)
487
578
  throw new Error(`connector not registered: ${name}`); return value; }
@@ -507,7 +598,7 @@ export class Supervisor {
507
598
  throw new Error(warnings.join(" "));
508
599
  }
509
600
  #now() { return this.clock.now().toISOString(); }
510
- async #agentRpc(wake, leaseToken, method, params) {
601
+ async #agentRpc(wake, turn, leaseToken, method, params) {
511
602
  const current = this.ledger.wake(wake.id);
512
603
  if (!current || current.status !== "running" || current.leaseToken !== leaseToken || !current.leaseUntil || current.leaseUntil < this.#now())
513
604
  throw new Error("stale runner RPC rejected");
@@ -515,12 +606,51 @@ export class Supervisor {
515
606
  const allowed = new Set(profile.capabilities ?? defaultCapabilities(profile.role));
516
607
  if (!allowed.has(method))
517
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`);
518
611
  this.ledger.appendRunnerEvent({ streamId: wakeStream(wake.id), ts: this.#now(), actor: wake.agent, type: `rpc.${method}`, data: params }, leaseToken);
519
612
  const input = asRecord(params);
520
613
  if (method === "ledger.search")
521
614
  return this.ledger.searchEvents(String(input.query), Number(input.limit ?? 20));
522
615
  if (method === "team.list")
523
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
+ }
524
654
  if (method === "goal.delegate")
525
655
  return this.delegate({
526
656
  id: String(input.id),
@@ -540,11 +670,28 @@ export class Supervisor {
540
670
  evidence: numberArray(input.evidence),
541
671
  }, wake.agent, wake.id);
542
672
  if (method === "goal.revise")
543
- return this.reviseChildGoal(String(input.goalId), String(input.objective), String(input.observationMethod), wake.agent, String(input.reason), numberArray(input.evidence), wake.id);
544
- if (method === "goal.pause" || method === "goal.resume")
545
- return this.transitionGoal(String(input.goalId), method === "goal.pause" ? "paused" : "active", wake.agent);
546
- if (method === "goal.complete")
547
- return this.completeGoal({ goalId: String(input.goalId), revision: Number(input.revision), reason: String(input.reason), evidence: numberArray(input.evidence) }, wake.agent, wake.id);
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
+ }
548
695
  if (method === "human.request") {
549
696
  const evidence = numberArray(input.evidence);
550
697
  for (const seq of evidence)
@@ -648,7 +795,7 @@ export function deriveTeam(ledger, now = new Date().toISOString()) {
648
795
  const nextWakeAt = schedules.filter((schedule) => schedule.agent === agent && schedule.nextWakeAt > now).map((schedule) => schedule.nextWakeAt).sort()[0] ?? null;
649
796
  const lastHandoff = [...handoffs].reverse().find((event) => event.actor === agent) ?? null;
650
797
  const blocker = lastHandoff && typeof lastHandoff.data === "object" && lastHandoff.data !== null && !Array.isArray(lastHandoff.data)
651
- ? lastHandoff.data.blocker
798
+ ? (lastHandoff.data.blocker ?? (lastHandoff.data.outcome === "blocked" ? "blocked" : null))
652
799
  : null;
653
800
  let status;
654
801
  if (live.length === 0)
@@ -715,22 +862,26 @@ function minimalEnvironment(explicit = {}) {
715
862
  return { ...env, ...explicit };
716
863
  }
717
864
  function escapeHtml(value) { return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"); }
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); }
718
869
  function asRecord(value) { if (typeof value !== "object" || value === null || Array.isArray(value))
719
870
  throw new Error("RPC params must be an object"); return value; }
720
871
  function numberArray(value) { if (!Array.isArray(value) || value.some((item) => typeof item !== "number"))
721
872
  throw new Error("RPC evidence must be a number array"); return value; }
722
873
  function asChildGoal(value) {
723
874
  const input = asRecord(value ?? null);
724
- return { id: String(input.id), objective: String(input.objective), observationMethod: String(input.observationMethod), owner: String(input.owner) };
875
+ return { id: String(input.id), objective: String(input.objective), observationMethod: String(input.observationMethod), verificationMethod: String(input.verificationMethod), owner: String(input.owner) };
725
876
  }
726
877
  function defaultCapabilities(role) {
727
878
  if (role === "ceo")
728
- return ["ledger.search", "mail.send", "schedule.set", "action.submit", "audit.ack", "memory.append", "team.list", "goal.delegate", "goal.reassign", "goal.revise", "goal.pause", "goal.resume", "goal.complete", "human.request"];
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"];
729
880
  if (role === "verifier")
730
881
  return ["ledger.search", "mail.send", "memory.append", "audit.write"];
731
882
  if (role === "audit")
732
883
  return ["ledger.search", "mail.send", "memory.append", "audit.write"];
733
- return ["ledger.search", "mail.send", "schedule.set", "action.submit", "audit.ack", "memory.append"];
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"];
734
885
  }
735
886
  export * from "./verification.js";
736
887
  export * from "./roles.js";