@astrosheep/keiyaku 4.5.25 → 4.5.27

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 (80) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +5 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +16 -14
  3. package/build/src/akuma/akuma-errors.d.ts +6 -0
  4. package/build/src/akuma/akuma-errors.js +11 -0
  5. package/build/src/akuma/akuma-handle.d.ts +53 -1
  6. package/build/src/akuma/akuma-handle.js +75 -7
  7. package/build/src/akuma/akuma-instance.js +6 -22
  8. package/build/src/akuma/akuma-probe.d.ts +20 -1
  9. package/build/src/akuma/akuma-probe.js +20 -3
  10. package/build/src/akuma/akuma.d.ts +12 -0
  11. package/build/src/akuma/allowed.d.ts +2 -1
  12. package/build/src/akuma/allowed.js +9 -4
  13. package/build/src/akuma/archetype.js +3 -3
  14. package/build/src/akuma/body.js +8 -3
  15. package/build/src/akuma/fleet-execution.d.ts +6 -1
  16. package/build/src/akuma/fleet-execution.js +44 -0
  17. package/build/src/akuma/fleet-observation.d.ts +238 -0
  18. package/build/src/akuma/fleet-observation.js +13 -0
  19. package/build/src/akuma/fleet-owner-port.d.ts +12 -0
  20. package/build/src/akuma/fleet-owner-port.js +46 -0
  21. package/build/src/akuma/fleet-request.d.ts +55 -4
  22. package/build/src/akuma/fleet-request.js +101 -6
  23. package/build/src/akuma/heart/index.d.ts +1 -0
  24. package/build/src/akuma/heart/index.js +4 -1
  25. package/build/src/akuma/heart/rows.d.ts +1 -0
  26. package/build/src/akuma/heart/rows.js +8 -0
  27. package/build/src/akuma/heart/soul.js +2 -2
  28. package/build/src/akuma/projection-read.js +5 -0
  29. package/build/src/akuma/projection.d.ts +36 -0
  30. package/build/src/akuma/projection.js +2 -1
  31. package/build/src/akuma/provider.d.ts +14 -0
  32. package/build/src/akuma/provider.js +66 -4
  33. package/build/src/akuma/providers/acp/events.js +16 -8
  34. package/build/src/akuma/providers/claude/events.js +5 -7
  35. package/build/src/akuma/providers/opencode-sdk/events.js +9 -5
  36. package/build/src/akuma/providers/pi/events.js +4 -3
  37. package/build/src/akuma/request-lifecycle.js +10 -1
  38. package/build/src/akuma-body.js +1 -33
  39. package/build/src/cli/commands/akuma-invoke.d.ts +8 -0
  40. package/build/src/cli/commands/akuma-invoke.js +58 -6
  41. package/build/src/cli/commands/akuma.d.ts +1 -0
  42. package/build/src/cli/commands/akuma.js +10 -15
  43. package/build/src/cli/commands/contract-help.js +8 -5
  44. package/build/src/cli/render/akuma-activity.d.ts +7 -1
  45. package/build/src/cli/render/akuma-activity.js +171 -56
  46. package/build/src/cli/render/akuma-tool.d.ts +5 -0
  47. package/build/src/cli/render/akuma-tool.js +122 -16
  48. package/build/src/cli/render/akuma.d.ts +3 -1
  49. package/build/src/cli/render/akuma.js +34 -2
  50. package/build/src/cli/render/catalog.js +2 -3
  51. package/build/src/cli/render/contract-observation.d.ts +1 -0
  52. package/build/src/cli/render/contract-observation.js +20 -1
  53. package/build/src/cli/render/contract.js +4 -4
  54. package/build/src/cli/render/execution-progress.d.ts +1 -0
  55. package/build/src/cli/render/execution-progress.js +77 -3
  56. package/build/src/cli/render/kanshi.js +5 -19
  57. package/build/src/cli/render/terminal.js +3 -1
  58. package/build/src/cli/runtime.d.ts +6 -0
  59. package/build/src/cli/runtime.js +35 -17
  60. package/build/src/core/facts/offer.d.ts +1 -2
  61. package/build/src/core/verbs/abandon.js +1 -1
  62. package/build/src/core/verbs/amend.js +1 -1
  63. package/build/src/core/verbs/arc.js +1 -1
  64. package/build/src/core/verbs/attestation.js +1 -1
  65. package/build/src/core/verbs/bind.js +1 -1
  66. package/build/src/core/verbs/deliver.js +1 -1
  67. package/build/src/core/verbs/placement.js +1 -1
  68. package/build/src/core/verbs/reintegrate.js +1 -1
  69. package/build/src/git/admission.js +18 -7
  70. package/build/src/identity/coordinates.js +1 -1
  71. package/build/src/library/address.d.ts +33 -3
  72. package/build/src/library/address.js +89 -28
  73. package/build/src/library/akuma-creation.js +1 -1
  74. package/build/src/library/composition.d.ts +12 -0
  75. package/build/src/library/fleet.d.ts +11 -1
  76. package/build/src/library/fleet.js +138 -48
  77. package/build/src/library/keiyaku.d.ts +12 -0
  78. package/build/src/plugin/public.d.ts +2 -0
  79. package/build/src/runtime/proc/windows-launch.exe +0 -0
  80. package/package.json +3 -3
@@ -44,10 +44,11 @@ to that invocation directory. Without `--workdir`, an unassociated call uses
44
44
  the invocation cwd, while a `--contract` call uses its appointed worktree.
45
45
 
46
46
  Repeated `--allowed` values add actions to the selected Akuma's defaults; they
47
- never narrow them. An omitted Archetype default permits the complete action
48
- vocabulary, while an explicit empty default permits none. A nested call can use
49
- only actions permitted by its direct parent Soul. Use `status <aku/...|@alias>`
50
- to inspect the born worker's frozen effective actions.
47
+ never narrow them. An omitted Archetype permits `akuma.*`, every `task.*`,
48
+ `contract.audit`, and `contract.deliver`; `contract.review` requires an explicit
49
+ Archetype or call-time grant. An explicit empty default permits none. A nested
50
+ call can use only actions permitted by its direct parent Soul. Use `status
51
+ <aku/...|@alias>` to inspect the born worker's frozen effective actions.
51
52
 
52
53
  ## Answer Schemas
53
54
 
@@ -19,10 +19,10 @@ resolve.
19
19
 
20
20
  The author pins every public, high-level fact: surfaces, semantics, persisted
21
21
  shapes, the observable acceptance boundary. Private decomposition, helper
22
- names, and control flow belong to the Deliverer — and that freedom comes from
23
- the author genuinely not caring, never from the author not deciding. Criteria
24
- are observable accept/reject observations a Reviewer can judge without asking
25
- the author anything further.
22
+ names, and equivalent control flow belong to the Deliverer — and that freedom
23
+ comes from the author genuinely not caring, never from the author not deciding.
24
+ Criteria are observable accept/reject observations a Reviewer can judge without
25
+ asking the author anything further.
26
26
 
27
27
  ## Author And Bind
28
28
 
@@ -47,22 +47,24 @@ fulfillment.>
47
47
 
48
48
  ## Design
49
49
  <The closed decisions. A statement belongs here exactly when a test-green
50
- candidate could still violate it: which module owns the change; the exact
51
- public surface — each type with its fields, each verb with its success,
52
- refusal, and error arms and their reason words; the persisted format; which
53
- way data flows and where it commits or refuses; which parallel shapes are
54
- forbidden. Helper names and equivalent control flow do not belong here —
55
- they are the worker's.>
50
+ candidate could still violate it: existing owner modules and entry points,
51
+ what is reused or changed, the implementation approach, critical ordering, the
52
+ exact public surface — each type with its fields, each verb with its success,
53
+ refusal, and error arms and their reason words; the persisted format; which way
54
+ data flows and where it commits or refuses; which parallel shapes are
55
+ forbidden. Unresolved architectural choices are settled before binding. Private
56
+ helper names and equivalent control flow remain the Deliverer's freedom.>
56
57
 
57
58
  ```text
58
59
  <pseudocode — only where ordering matters>
59
60
  ```
60
61
 
61
62
  ## Region
62
- <one intended write pattern per line — planning evidence for overlap
63
- detection, never ownership or the exact diff. Narrow enough that overlap is a
64
- real signal; directory patterns end with `/`. Fenced lines, list items, and
65
- bare lines are equivalent and union.>
63
+ <one intended write pattern per line — the narrowest justified intended
64
+ writes for this approach, not every potentially involved file. Planning evidence
65
+ for overlap detection, never ownership or the exact diff. No broad directory
66
+ fallback or redundant parent/child patterns; directory patterns end with `/`.
67
+ Fenced lines, list items, and bare lines are equivalent and union.>
66
68
 
67
69
  ## Criteria
68
70
  ### <one observable condition>
@@ -5,6 +5,12 @@ export declare class AkumaNotBornError extends Error {
5
5
  readonly kind = "akuma-not-born";
6
6
  constructor(id: AkuId);
7
7
  }
8
+ export declare class AkumaObservationError extends Error {
9
+ readonly id: AkuId;
10
+ readonly diagnostic: string;
11
+ readonly kind = "akuma-observation";
12
+ constructor(id: AkuId, diagnostic: string);
13
+ }
8
14
  export declare class AkumaDecodeError extends Error {
9
15
  readonly diagnostic: string;
10
16
  readonly answer?: string | undefined;
@@ -8,6 +8,17 @@ export class AkumaNotBornError extends Error {
8
8
  this.name = "AkumaNotBornError";
9
9
  }
10
10
  }
11
+ export class AkumaObservationError extends Error {
12
+ id;
13
+ diagnostic;
14
+ kind = "akuma-observation";
15
+ constructor(id, diagnostic) {
16
+ super(`Akuma ${id} observation failed: ${diagnostic}`);
17
+ this.id = id;
18
+ this.diagnostic = diagnostic;
19
+ this.name = "AkumaObservationError";
20
+ }
21
+ }
11
22
  export class AkumaDecodeError extends Error {
12
23
  diagnostic;
13
24
  answer;
@@ -1,5 +1,5 @@
1
1
  import { type TellResult, type TellWakeRuntime } from "./body.js";
2
- import { HeldAkumaLeash, type KillEvidence } from "./heart/index.js";
2
+ import { HeldAkumaLeash, type KillEvidence, type TellFact, type TurnOutcome } from "./heart/index.js";
3
3
  import { type AkuId, type AkumaPaths } from "./identity.js";
4
4
  import { type ActivityHistory, type ExactHistory } from "./projection.js";
5
5
  import { type WaitReason } from "./akuma-observe.js";
@@ -11,6 +11,22 @@ export declare function settleAkumaKill(paths: AkumaPaths, signal?: AbortSignal,
11
11
  leash?: HeldAkumaLeash;
12
12
  }>>;
13
13
  export declare function killAkumaWithRecovery(paths: AkumaPaths, recover?: (paths: AkumaPaths) => Promise<void>, signal?: AbortSignal): Promise<KillEvidence>;
14
+ export type TellAdmission = Readonly<{
15
+ kind: "not-born";
16
+ }> | Readonly<{
17
+ kind: "admitted";
18
+ tell: TellFact;
19
+ wake: Promise<TellResult>;
20
+ }>;
21
+ export type InterruptAdmission = Readonly<{
22
+ kind: "unavailable";
23
+ evidence: "hung" | "untidy" | "unavailable";
24
+ }> | Readonly<{
25
+ kind: "admitted";
26
+ tell: TellFact;
27
+ putDown: "was-idle" | "self-aborted";
28
+ wake: Promise<TellResult>;
29
+ }>;
14
30
  export declare class AkumaHandle {
15
31
  readonly id: AkuId;
16
32
  private readonly worldPath;
@@ -42,6 +58,31 @@ export declare class AkumaHandle {
42
58
  initiator?: string;
43
59
  signal?: AbortSignal;
44
60
  }>): Promise<TellResult>;
61
+ /**
62
+ * Record one Tell and start its wake without waiting for delivery. A bounded
63
+ * caller starts its own deadline at this admission boundary while the wake
64
+ * continues in the background; the unbounded public Tell awaits `wake`.
65
+ */
66
+ admitTell(body: string, tellId?: string, recordedAt?: string, runtime?: TellWakeRuntime, options?: Readonly<{
67
+ schemaJson?: string;
68
+ initiator?: string;
69
+ signal?: AbortSignal;
70
+ }>): Promise<TellAdmission>;
71
+ /**
72
+ * The admitted Tell's receipt as it stands now. A bounded caller that stops
73
+ * waiting before the wake settles reports this honest instant rather than a
74
+ * delivery that has not happened yet.
75
+ */
76
+ admittedReceipt(tellId: string): Promise<TellResult>;
77
+ /** Observe the exact admitted Tell's bound Turn without substituting Akuma-wide idleness. */
78
+ tellOutcome(tellId: string, options?: Readonly<{
79
+ timeoutMs?: number;
80
+ signal?: AbortSignal;
81
+ }>): Promise<Readonly<{
82
+ reason: WaitReason;
83
+ outcome: TurnOutcome | null;
84
+ }>>;
85
+ private boundTellOutcome;
45
86
  interrupt(body: string, options?: Readonly<{
46
87
  tellId?: string;
47
88
  schemaJson?: string;
@@ -49,6 +90,17 @@ export declare class AkumaHandle {
49
90
  signal?: AbortSignal;
50
91
  runtime?: TellWakeRuntime;
51
92
  }>): Promise<InterruptReceipt>;
93
+ /**
94
+ * Settle the predecessor and record the interrupt Tell, returning at the
95
+ * admission boundary so a bounded caller owns the wake wait.
96
+ */
97
+ admitInterrupt(body: string, options?: Readonly<{
98
+ tellId?: string;
99
+ schemaJson?: string;
100
+ initiator?: string;
101
+ signal?: AbortSignal;
102
+ runtime?: TellWakeRuntime;
103
+ }>): Promise<InterruptAdmission>;
52
104
  fork(input: Readonly<{
53
105
  at: string;
54
106
  }>): Promise<ForkReceipt>;
@@ -1,13 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { handoffPendingTells, wakeRecordedTell } from "./body.js";
3
- import { HeldAkumaLeash, activitySlice, readForkPoint, readHeart, readKill, readLastAnsweredTurn, readSoul, recordTell, requestPause, requestStop, } from "./heart/index.js";
3
+ import { HeldAkumaLeash, activitySlice, readForkPoint, readHeart, readKill, readLastAnsweredTurn, readSoul, readTell, readTurn, recordTell, requestPause, requestStop, projectTell, } from "./heart/index.js";
4
4
  import { acquireLeash } from "./control.js";
5
5
  import { parsePublicHistoryId, pathsForAkuId } from "./identity.js";
6
6
  import { projectTurns, selectHistory, selectExactHistory, } from "./projection.js";
7
7
  import { resolveProviderExecution } from "./providers/index.js";
8
8
  import { publishAkuma } from "./publication.js";
9
9
  import { spawnAkumaBody } from "./body.js";
10
- import { AkumaNotBornError } from "./akuma-errors.js";
10
+ import { AkumaNotBornError, AkumaProviderError } from "./akuma-errors.js";
11
11
  import { bornStatus, defaultWaitComplete, readWaitComplete, waitForObservation, } from "./akuma-observe.js";
12
12
  const CALL_EXECUTION = Symbol("akuma-call-execution");
13
13
  function diagnostic(error) {
@@ -151,6 +151,17 @@ export class AkumaHandle {
151
151
  return (await this.waitReceipt(predicate, options)).status;
152
152
  }
153
153
  async tell(body, tellId = randomUUID(), recordedAt = new Date().toISOString(), runtime, options = {}) {
154
+ const admitted = await this.admitTell(body, tellId, recordedAt, runtime, options);
155
+ if (admitted.kind === "not-born")
156
+ throw new AkumaNotBornError(this.id);
157
+ return await admitted.wake;
158
+ }
159
+ /**
160
+ * Record one Tell and start its wake without waiting for delivery. A bounded
161
+ * caller starts its own deadline at this admission boundary while the wake
162
+ * continues in the background; the unbounded public Tell awaits `wake`.
163
+ */
164
+ async admitTell(body, tellId = randomUUID(), recordedAt = new Date().toISOString(), runtime, options = {}) {
154
165
  const { schemaJson, initiator, signal } = options;
155
166
  const admitted = await recordTell(this.paths, {
156
167
  kind: "tell",
@@ -161,10 +172,66 @@ export class AkumaHandle {
161
172
  ...(schemaJson === undefined ? {} : { schemaJson }),
162
173
  });
163
174
  if (admitted.kind === "not-born")
164
- throw new AkumaNotBornError(this.id);
165
- return await wakeRecordedTell(this.paths, admitted.tell.id, runtime, signal);
175
+ return admitted;
176
+ return {
177
+ kind: "admitted",
178
+ tell: admitted.tell,
179
+ wake: wakeRecordedTell(this.paths, admitted.tell.id, runtime, signal),
180
+ };
181
+ }
182
+ /**
183
+ * The admitted Tell's receipt as it stands now. A bounded caller that stops
184
+ * waiting before the wake settles reports this honest instant rather than a
185
+ * delivery that has not happened yet.
186
+ */
187
+ async admittedReceipt(tellId) {
188
+ const tell = await readTell(this.paths, tellId);
189
+ if (tell === null)
190
+ throw new AkumaProviderError(`recorded Tell ${tellId} is missing from Heart`);
191
+ const latestBody = (await readHeart(this.paths)).latestBody;
192
+ return {
193
+ admission: { tellId, fact: "recorded" },
194
+ row: projectTell(tell),
195
+ wake: tell.state === "told" || tell.binding !== undefined
196
+ ? { kind: "told" }
197
+ : latestBody !== null && latestBody.end === undefined && latestBody.hung === undefined
198
+ ? { kind: "pursuing", bodySequence: latestBody.sequence }
199
+ : { kind: "held" },
200
+ };
201
+ }
202
+ /** Observe the exact admitted Tell's bound Turn without substituting Akuma-wide idleness. */
203
+ async tellOutcome(tellId, options = {}) {
204
+ const waited = await waitForObservation({
205
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
206
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
207
+ observe: async () => {
208
+ const tell = await readTell(this.paths, tellId);
209
+ if (tell === null)
210
+ throw new AkumaProviderError(`recorded Tell ${tellId} is missing from Heart`);
211
+ const outcome = await this.boundTellOutcome(tell);
212
+ return { outcome, terminalWithoutTurn: tell.state === "told" && tell.binding === undefined };
213
+ },
214
+ complete: (observed) => observed.outcome !== null || observed.terminalWithoutTurn,
215
+ });
216
+ return { reason: waited.reason, outcome: waited.value.outcome };
217
+ }
218
+ async boundTellOutcome(tell) {
219
+ if (tell.binding === undefined)
220
+ return null;
221
+ const turn = await readTurn(this.paths, tell.binding.turnSequence);
222
+ return turn?.end?.outcome ?? null;
166
223
  }
167
224
  async interrupt(body, options = {}) {
225
+ const admitted = await this.admitInterrupt(body, options);
226
+ if (admitted.kind === "unavailable")
227
+ return admitted;
228
+ return { kind: "interrupted", putDown: admitted.putDown, tell: await admitted.wake };
229
+ }
230
+ /**
231
+ * Settle the predecessor and record the interrupt Tell, returning at the
232
+ * admission boundary so a bounded caller owns the wake wait.
233
+ */
234
+ async admitInterrupt(body, options = {}) {
168
235
  const request = await requestPause(this.paths, new Date().toISOString(), options.signal);
169
236
  if (request.kind === "not-born") {
170
237
  throw new AkumaNotBornError(this.id);
@@ -204,15 +271,16 @@ export class AkumaHandle {
204
271
  });
205
272
  if (admitted.kind === "not-born")
206
273
  throw new AkumaNotBornError(this.id);
207
- recorded = { kind: "recorded", tellId: admitted.tell.id };
274
+ recorded = admitted.tell;
208
275
  }
209
276
  finally {
210
277
  leash.release();
211
278
  }
212
279
  return {
213
- kind: "interrupted",
280
+ kind: "admitted",
281
+ tell: recorded,
214
282
  putDown,
215
- tell: await wakeRecordedTell(this.paths, recorded.tellId, options.runtime, options.signal),
283
+ wake: wakeRecordedTell(this.paths, recorded.id, options.runtime, options.signal),
216
284
  };
217
285
  }
218
286
  async fork(input) {
@@ -6,7 +6,7 @@ import { AkumaDecodeError, AkumaProviderError } from "./akuma-errors.js";
6
6
  import { AkumaHandle } from "./akuma-handle.js";
7
7
  import { bornStatus, defaultWaitComplete, waitForObservation } from "./akuma-observe.js";
8
8
  import { loadArchetype } from "./archetype.js";
9
- import { activitySlice, readTell, readTurn } from "./heart/index.js";
9
+ import { activitySlice } from "./heart/index.js";
10
10
  import { parseAkuId, pathsForAkuId } from "./identity.js";
11
11
  import { birthAkuma, launchAkuma } from "./publication.js";
12
12
  import { projectTurns, selectHistory } from "./projection.js";
@@ -72,26 +72,10 @@ function outcomeError(outcome) {
72
72
  throw new AkumaProviderError(outcome.diagnostic);
73
73
  throw new AkumaProviderError("Akuma answered without a value");
74
74
  }
75
- async function boundOutcome(paths, tell) {
76
- if (tell.binding === undefined)
77
- return null;
78
- const turn = await readTurn(paths, tell.binding.turnSequence);
79
- return turn?.end?.outcome ?? null;
80
- }
81
- async function awaitTellOutcome(paths, tellId, signal) {
82
- const waited = await waitForObservation({
83
- ...(signal === undefined ? {} : { signal }),
84
- observe: async () => {
85
- const tell = await readTell(paths, tellId);
86
- if (tell === null)
87
- throw new AkumaProviderError(`recorded Tell ${tellId} is missing from Heart`);
88
- const outcome = await boundOutcome(paths, tell);
89
- return { outcome, terminalWithoutTurn: tell.state === "told" && tell.binding === undefined };
90
- },
91
- complete: (observed) => observed.outcome !== null || observed.terminalWithoutTurn,
92
- });
93
- if (waited.value.outcome !== null)
94
- return waited.value.outcome;
75
+ async function awaitTellOutcome(handle, tellId, signal) {
76
+ const observed = await handle.tellOutcome(tellId, signal === undefined ? {} : { signal });
77
+ if (observed.outcome !== null)
78
+ return observed.outcome;
95
79
  throw new AkumaProviderError(`recorded Tell ${tellId} reached a terminal delivery without a Turn binding`);
96
80
  }
97
81
  export class Akuma {
@@ -172,7 +156,7 @@ export class Akuma {
172
156
  ...(schemaOptions.initiator === undefined ? {} : { initiator: schemaOptions.initiator }),
173
157
  ...(signal === undefined ? {} : { signal }),
174
158
  });
175
- const outcome = await awaitTellOutcome(this.paths, recorded.tellId, signal);
159
+ const outcome = await awaitTellOutcome(new AkumaHandle(this.id, this.root), recorded.tellId, signal);
176
160
  if (outcome.kind !== "answered")
177
161
  outcomeError(outcome);
178
162
  if (schema === undefined)
@@ -1,3 +1,22 @@
1
1
  import { type AkuId } from "./identity.js";
2
2
  import type { WorldRoot } from "../world.js";
3
- export declare function probeBornAkuma(worldPath: WorldRoot, id: AkuId): Promise<boolean>;
3
+ /**
4
+ * How one Akuma coordinate stands in a World, decided in the single place that
5
+ * reads a Heart for addressing. Absence and unreadability are different facts,
6
+ * and each consumer below maps them to its own caller terms.
7
+ */
8
+ export type AkumaAddressability = Readonly<{
9
+ kind: "born";
10
+ }> | Readonly<{
11
+ kind: "absent";
12
+ }> | Readonly<{
13
+ kind: "unreadable";
14
+ reason: string;
15
+ }>;
16
+ export declare function akumaAddressability(worldPath: WorldRoot, id: AkuId): Promise<AkumaAddressability>;
17
+ /**
18
+ * The one normalization of addressability into caller terms: an Akuma whose
19
+ * Heart is absent was never born, and one whose Heart cannot be read is an
20
+ * observation failure that keeps its reason with the identity.
21
+ */
22
+ export declare function requireBornAkuma(worldPath: WorldRoot, id: AkuId): Promise<void>;
@@ -1,6 +1,23 @@
1
+ import { AkumaNotBornError, AkumaObservationError } from "./akuma-errors.js";
1
2
  import { readSoul } from "./heart/index.js";
2
3
  import { pathsForAkuId } from "./identity.js";
3
- export async function probeBornAkuma(worldPath, id) {
4
- const soul = await readSoul(pathsForAkuId(worldPath, id));
5
- return soul !== null;
4
+ export async function akumaAddressability(worldPath, id) {
5
+ try {
6
+ return (await readSoul(pathsForAkuId(worldPath, id))) === null ? { kind: "absent" } : { kind: "born" };
7
+ }
8
+ catch (error) {
9
+ return { kind: "unreadable", reason: error instanceof Error ? error.message : String(error) };
10
+ }
11
+ }
12
+ /**
13
+ * The one normalization of addressability into caller terms: an Akuma whose
14
+ * Heart is absent was never born, and one whose Heart cannot be read is an
15
+ * observation failure that keeps its reason with the identity.
16
+ */
17
+ export async function requireBornAkuma(worldPath, id) {
18
+ const addressability = await akumaAddressability(worldPath, id);
19
+ if (addressability.kind === "absent")
20
+ throw new AkumaNotBornError(id);
21
+ if (addressability.kind === "unreadable")
22
+ throw new AkumaObservationError(id, addressability.reason);
6
23
  }
@@ -207,6 +207,10 @@ export declare const akumaStatusSchema: z.ZodObject<{
207
207
  }, z.core.$strict>, z.ZodObject<{
208
208
  kind: z.ZodLiteral<"other">;
209
209
  display: z.ZodString;
210
+ input: z.ZodOptional<z.ZodObject<{
211
+ json: z.ZodString;
212
+ truncated: z.ZodBoolean;
213
+ }, z.core.$strict>>;
210
214
  }, z.core.$strict>]>;
211
215
  state: z.ZodLiteral<"active">;
212
216
  truncated: z.ZodOptional<z.ZodLiteral<true>>;
@@ -254,6 +258,10 @@ export declare const akumaStatusSchema: z.ZodObject<{
254
258
  }, z.core.$strict>, z.ZodObject<{
255
259
  kind: z.ZodLiteral<"other">;
256
260
  display: z.ZodString;
261
+ input: z.ZodOptional<z.ZodObject<{
262
+ json: z.ZodString;
263
+ truncated: z.ZodBoolean;
264
+ }, z.core.$strict>>;
257
265
  }, z.core.$strict>]>;
258
266
  state: z.ZodObject<{
259
267
  status: z.ZodEnum<{
@@ -409,6 +417,10 @@ export declare const akumaStatusSchema: z.ZodObject<{
409
417
  }, z.core.$strict>, z.ZodObject<{
410
418
  kind: z.ZodLiteral<"other">;
411
419
  display: z.ZodString;
420
+ input: z.ZodOptional<z.ZodObject<{
421
+ json: z.ZodString;
422
+ truncated: z.ZodBoolean;
423
+ }, z.core.$strict>>;
412
424
  }, z.core.$strict>]>;
413
425
  state: z.ZodObject<{
414
426
  status: z.ZodEnum<{
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  export declare const ALLOWED_ACTIONS: readonly ["akuma.call", "akuma.kill", "akuma.tell", "contract.audit", "contract.deliver", "contract.review", "task.add", "task.addDocument", "task.compose", "task.done", "task.drop", "task.hold", "task.resume", "task.start", "task.stop", "task.update"];
3
3
  export type AllowedAction = (typeof ALLOWED_ACTIONS)[number];
4
4
  export type AllowedActions = readonly AllowedAction[];
5
+ export declare const DEFAULT_ALLOWED_ACTIONS: AllowedActions;
5
6
  export declare function isAllowedAction(value: unknown): value is AllowedAction;
6
7
  export declare function decodeAllowedActions(value: unknown, label?: string): AllowedActions;
7
8
  /** Public codecs retain the Soul's canonical, duplicate-free action membership. */
@@ -23,6 +24,6 @@ export declare const allowedActionsSchema: z.ZodPipe<z.ZodArray<z.ZodEnum<{
23
24
  "contract.deliver": "contract.deliver";
24
25
  "contract.review": "contract.review";
25
26
  }>>, z.ZodTransform<AllowedActions, ("task.add" | "task.addDocument" | "task.compose" | "task.done" | "task.drop" | "task.hold" | "task.resume" | "task.start" | "task.stop" | "task.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
26
- export declare function effectiveAllowedActions(value: unknown): AllowedActions;
27
+ export declare function historicalAllowedActions(value: unknown): AllowedActions;
27
28
  export declare function unionAllowedActions(base: AllowedActions, additions: AllowedActions): AllowedActions;
28
29
  export declare function clipAllowedActions(requested: AllowedActions, parent: AllowedActions): AllowedActions;
@@ -1,14 +1,19 @@
1
1
  import { TASK_MUTATION_ACTIONS } from "../task/mutation.js";
2
2
  import { z } from "zod";
3
+ const AKUMA_MUTATION_ACTIONS = Object.freeze(["akuma.call", "akuma.kill", "akuma.tell"]);
3
4
  export const ALLOWED_ACTIONS = Object.freeze([
4
- "akuma.call",
5
- "akuma.kill",
6
- "akuma.tell",
5
+ ...AKUMA_MUTATION_ACTIONS,
7
6
  "contract.audit",
8
7
  "contract.deliver",
9
8
  "contract.review",
10
9
  ...TASK_MUTATION_ACTIONS,
11
10
  ]);
11
+ export const DEFAULT_ALLOWED_ACTIONS = Object.freeze([
12
+ ...AKUMA_MUTATION_ACTIONS,
13
+ "contract.audit",
14
+ "contract.deliver",
15
+ ...TASK_MUTATION_ACTIONS,
16
+ ]);
12
17
  const ALLOWED_ACTION_SET = new Set(ALLOWED_ACTIONS);
13
18
  export function isAllowedAction(value) {
14
19
  return typeof value === "string" && ALLOWED_ACTION_SET.has(value);
@@ -36,7 +41,7 @@ export const allowedActionsSchema = z.array(z.enum(ALLOWED_ACTIONS)).transform((
36
41
  return z.NEVER;
37
42
  }
38
43
  });
39
- export function effectiveAllowedActions(value) {
44
+ export function historicalAllowedActions(value) {
40
45
  return value === undefined ? ALLOWED_ACTIONS : decodeAllowedActions(value);
41
46
  }
42
47
  export function unionAllowedActions(base, additions) {
@@ -5,7 +5,7 @@ import { parseDocument } from "yaml";
5
5
  import { archetypeName } from "./identity.js";
6
6
  import { decodeProviderOptions, decodeProviderRecipe, } from "./provider-recipe.js";
7
7
  import { resolveProviderExecution } from "./providers/index.js";
8
- import { effectiveAllowedActions } from "./allowed.js";
8
+ import { DEFAULT_ALLOWED_ACTIONS, decodeAllowedActions } from "./allowed.js";
9
9
  export class AkumaArchetypeError extends Error {
10
10
  archetype;
11
11
  searched;
@@ -135,7 +135,7 @@ function decodeArchetypeFields(values) {
135
135
  const sandbox = archetypeEnum(values, "sandbox", ["full-access"]);
136
136
  const description = archetypeField(values, "description");
137
137
  const allowedPresent = "allowed" in values;
138
- const allowed = allowedPresent ? effectiveAllowedActions(values.allowed) : undefined;
138
+ const allowed = allowedPresent ? decodeAllowedActions(values.allowed) : undefined;
139
139
  const systemPromptMode = archetypeEnum(values, "systemPromptMode", ["append", "replace"]);
140
140
  return {
141
141
  ...(base === undefined ? {} : { base }),
@@ -186,7 +186,7 @@ function mergeArchetype(base, local) {
186
186
  if (provider === undefined)
187
187
  throw new TypeError("Akuma provider must be a nonblank string");
188
188
  const options = decodeProviderOptions({ ...(base?.options ?? {}), ...local.options });
189
- const allowed = local.allowedPresent ? local.allowed : (base?.allowed ?? effectiveAllowedActions(undefined));
189
+ const allowed = local.allowedPresent ? local.allowed : (base?.allowed ?? DEFAULT_ALLOWED_ACTIONS);
190
190
  return Object.freeze({
191
191
  name: local.name,
192
192
  path: local.path,
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import { abortable, abortableDelay } from "./abort.js";
5
5
  import { BodySupervisor } from "./body-supervisor.js";
6
6
  import { driveTurn, turnRecipe } from "./turn-drive.js";
7
- import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, readHeart, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readNonterminalRequests, resolvePendingTellDisposition, } from "./heart/index.js";
7
+ import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, readHeart, readLatestTurnForBody, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readNonterminalRequests, resolvePendingTellDisposition, } from "./heart/index.js";
8
8
  import { worldRootForAkumaPaths } from "./identity.js";
9
9
  import { akumaExecutionEnvironment } from "./providers/execution-environment.js";
10
10
  import { pluginRuntime } from "../plugin/runtime.js";
@@ -291,7 +291,9 @@ function turnOutcomeEmitter(launch, soul) {
291
291
  return {
292
292
  async emit(turnSequence, outcome) {
293
293
  try {
294
- const initiator = (await readTurn(launch.paths, turnSequence))?.initiator;
294
+ const turn = await readTurn(launch.paths, turnSequence);
295
+ if (turn === null)
296
+ throw new Error(`Committed Turn ${turnSequence} is missing from Heart`);
295
297
  plugins ??= pluginRuntime({
296
298
  world: await World.at(worldRootForAkumaPaths(launch.paths)),
297
299
  reportDiagnostic,
@@ -299,8 +301,9 @@ function turnOutcomeEmitter(launch, soul) {
299
301
  await (await plugins).emit({
300
302
  kind: "akuma.turn-outcome",
301
303
  akumaId: soul.id,
304
+ bodySequence: turn.bodySequence,
302
305
  turnSequence,
303
- ...(initiator === undefined ? {} : { initiator }),
306
+ ...(turn.initiator === undefined ? {} : { initiator: turn.initiator }),
304
307
  outcome: outcome.outcome === "answered"
305
308
  ? { kind: "answered", text: outcome.answer }
306
309
  : { kind: "failed", reason: outcome.diagnostic },
@@ -320,6 +323,7 @@ function bodyEndEmitter(launch, soul) {
320
323
  };
321
324
  return (bodySequence, end, diagnostic) => (async () => {
322
325
  try {
326
+ const initiator = (await readLatestTurnForBody(launch.paths, bodySequence))?.initiator;
323
327
  plugins ??= pluginRuntime({
324
328
  world: await World.at(worldRootForAkumaPaths(launch.paths)),
325
329
  reportDiagnostic,
@@ -330,6 +334,7 @@ function bodyEndEmitter(launch, soul) {
330
334
  akumaId: soul.id,
331
335
  bodySequence,
332
336
  end,
337
+ ...(initiator === undefined ? {} : { initiator }),
333
338
  ...(diagnostic === undefined ? {} : { diagnostic }),
334
339
  ...(launch.completion?.contractId === undefined ? {} : { contractId: launch.completion.contractId }),
335
340
  }, reportDiagnostic);
@@ -3,7 +3,7 @@ import type { ActivityRow } from "./projection.js";
3
3
  import { type DispatchAssociation } from "./dispatch-association.js";
4
4
  import type { AkumaAlias } from "../identity/selector.js";
5
5
  import type { WorldRoot } from "../world.js";
6
- import { type AkumaKillResult, type AkumaTellResult, type AkumaWaitResult } from "./fleet-observation.js";
6
+ import { type AkumaKillResult, type AkumaTellResult, type AkumaTellWaitResult, type AkumaWaitResult } from "./fleet-observation.js";
7
7
  export type WaitIdentityFacts = Readonly<{
8
8
  /** The alias currently addressing this Akuma in its world, when one is bound to it. */
9
9
  alias?: AkumaAlias;
@@ -50,6 +50,11 @@ export type TellExecutionInput = Readonly<{
50
50
  signal?: AbortSignal;
51
51
  }>;
52
52
  export declare function executeTellAkuma(input: TellExecutionInput): Promise<AkumaTellResult>;
53
+ export declare function executeTellWaitAkuma(input: TellExecutionInput & Readonly<{
54
+ timeoutMs: number;
55
+ schemaJson?: string;
56
+ interrupt?: boolean;
57
+ }>): Promise<AkumaTellWaitResult>;
53
58
  export type KillExecutionInput = Readonly<{
54
59
  path: WorldRoot;
55
60
  ids: readonly AkumaStatus["id"][];
@@ -1,3 +1,4 @@
1
+ import { AkumaDecodeError, AkumaProviderError } from "./akuma-errors.js";
1
2
  import { AkumaNotBornError, defaultWaitComplete } from "./akuma.js";
2
3
  import { createAkumaProduct } from "./akuma-product.js";
3
4
  import { readLiveStatus, waitForObservation } from "./akuma-observe.js";
@@ -125,6 +126,49 @@ export async function executeTellAkuma(input) {
125
126
  tell,
126
127
  });
127
128
  }
129
+ export async function executeTellWaitAkuma(input) {
130
+ input.signal?.throwIfAborted();
131
+ const handle = source(input.path).selectHandle({ id: input.id });
132
+ const admission = input.interrupt === true
133
+ ? await handle.admitInterrupt(input.body, {
134
+ ...(input.tellId === undefined ? {} : { tellId: input.tellId }),
135
+ ...(input.schemaJson === undefined ? {} : { schemaJson: input.schemaJson }),
136
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
137
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
138
+ })
139
+ : await handle.admitTell(input.body, input.tellId, input.recordedAt, undefined, {
140
+ ...(input.schemaJson === undefined ? {} : { schemaJson: input.schemaJson }),
141
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
142
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
143
+ });
144
+ if (admission.kind === "unavailable")
145
+ throw new AkumaProviderError(`Tell interrupt unavailable: ${admission.evidence}`);
146
+ if (admission.kind === "not-born")
147
+ throw new AkumaNotBornError(input.id);
148
+ // The wake runs behind the window; a settled wake is preferred, otherwise the
149
+ // receipt reports the admitted Tell as it stands when the window closes.
150
+ let settled;
151
+ void admission.wake.then((receipt) => {
152
+ settled = receipt;
153
+ }, () => undefined);
154
+ const observed = await handle.tellOutcome(admission.tell.id, {
155
+ timeoutMs: input.timeoutMs,
156
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
157
+ });
158
+ const observation = observed.outcome === null
159
+ ? observed.reason === "deadline"
160
+ ? { reason: "deadline" }
161
+ : { reason: "unanswered" }
162
+ : observed.outcome.kind === "answered"
163
+ ? { reason: "answered", answer: observed.outcome.answerJson ?? observed.outcome.answer }
164
+ : observed.outcome.kind === "failed"
165
+ ? { reason: "failed", diagnostic: observed.outcome.diagnostic }
166
+ : (() => {
167
+ throw new AkumaDecodeError(observed.outcome.diagnostic, observed.outcome.answer);
168
+ })();
169
+ const tell = settled ?? (await handle.admittedReceipt(admission.tell.id));
170
+ return fleetResultSchemas.tellWait.parse({ akuma: input.id, tell, observation });
171
+ }
128
172
  export async function executeKillAkuma(input) {
129
173
  input.signal?.throwIfAborted();
130
174
  const handles = input.ids.map((id) => source(input.path).selectHandle({ id }));