@astrosheep/keiyaku 4.5.26 → 4.5.28

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 (98) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +82 -162
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +153 -76
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +68 -64
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +115 -146
  9. package/build/src/akuma/akuma-errors.d.ts +6 -0
  10. package/build/src/akuma/akuma-errors.js +11 -0
  11. package/build/src/akuma/akuma-handle.d.ts +54 -1
  12. package/build/src/akuma/akuma-handle.js +79 -8
  13. package/build/src/akuma/akuma-instance.js +6 -22
  14. package/build/src/akuma/akuma-observe.d.ts +6 -0
  15. package/build/src/akuma/akuma-observe.js +3 -1
  16. package/build/src/akuma/akuma-probe.d.ts +20 -1
  17. package/build/src/akuma/akuma-probe.js +20 -3
  18. package/build/src/akuma/akuma.d.ts +13 -0
  19. package/build/src/akuma/akuma.js +1 -0
  20. package/build/src/akuma/allowed.d.ts +2 -1
  21. package/build/src/akuma/allowed.js +9 -4
  22. package/build/src/akuma/archetype.js +3 -3
  23. package/build/src/akuma/body.js +8 -3
  24. package/build/src/akuma/fleet-execution.d.ts +13 -2
  25. package/build/src/akuma/fleet-execution.js +45 -0
  26. package/build/src/akuma/fleet-observation.d.ts +241 -0
  27. package/build/src/akuma/fleet-observation.js +13 -0
  28. package/build/src/akuma/fleet-owner-port.d.ts +12 -0
  29. package/build/src/akuma/fleet-owner-port.js +46 -0
  30. package/build/src/akuma/fleet-request.d.ts +55 -4
  31. package/build/src/akuma/fleet-request.js +101 -6
  32. package/build/src/akuma/heart/index.d.ts +1 -0
  33. package/build/src/akuma/heart/index.js +4 -1
  34. package/build/src/akuma/heart/rows.d.ts +1 -0
  35. package/build/src/akuma/heart/rows.js +8 -0
  36. package/build/src/akuma/heart/soul.js +2 -2
  37. package/build/src/akuma/identity.js +11 -9
  38. package/build/src/akuma/projection.d.ts +36 -0
  39. package/build/src/akuma/projection.js +2 -1
  40. package/build/src/akuma/provider.d.ts +14 -0
  41. package/build/src/akuma/provider.js +66 -4
  42. package/build/src/akuma/providers/acp/events.js +16 -8
  43. package/build/src/akuma/providers/claude/events.js +5 -7
  44. package/build/src/akuma/providers/opencode-sdk/events.js +9 -5
  45. package/build/src/akuma/providers/pi/events.js +4 -3
  46. package/build/src/akuma/request-lifecycle.js +10 -1
  47. package/build/src/akuma-body.js +1 -33
  48. package/build/src/alias/index.js +3 -3
  49. package/build/src/cli/commands/akuma-invoke.d.ts +8 -0
  50. package/build/src/cli/commands/akuma-invoke.js +67 -6
  51. package/build/src/cli/commands/akuma.d.ts +1 -0
  52. package/build/src/cli/commands/akuma.js +19 -30
  53. package/build/src/cli/commands/contract-help.d.ts +1 -0
  54. package/build/src/cli/commands/contract-help.js +9 -5
  55. package/build/src/cli/invoke.js +1 -1
  56. package/build/src/cli/render/akuma-activity.d.ts +7 -1
  57. package/build/src/cli/render/akuma-activity.js +164 -50
  58. package/build/src/cli/render/akuma-tool.d.ts +5 -0
  59. package/build/src/cli/render/akuma-tool.js +118 -14
  60. package/build/src/cli/render/akuma.d.ts +12 -1
  61. package/build/src/cli/render/akuma.js +74 -2
  62. package/build/src/cli/render/catalog.js +2 -3
  63. package/build/src/cli/render/contract-observation.d.ts +1 -0
  64. package/build/src/cli/render/contract-observation.js +20 -1
  65. package/build/src/cli/render/contract.js +4 -4
  66. package/build/src/cli/render/execution-progress.d.ts +1 -0
  67. package/build/src/cli/render/execution-progress.js +77 -3
  68. package/build/src/cli/render/kanshi.js +5 -19
  69. package/build/src/cli/render/terminal.js +3 -1
  70. package/build/src/cli/runtime.d.ts +6 -0
  71. package/build/src/cli/runtime.js +35 -17
  72. package/build/src/core/facts/offer.d.ts +1 -2
  73. package/build/src/core/verbs/abandon.js +1 -1
  74. package/build/src/core/verbs/amend.js +1 -1
  75. package/build/src/core/verbs/arc.js +1 -1
  76. package/build/src/core/verbs/attestation.js +1 -1
  77. package/build/src/core/verbs/bind.js +1 -1
  78. package/build/src/core/verbs/deliver.js +1 -1
  79. package/build/src/core/verbs/placement.js +1 -1
  80. package/build/src/core/verbs/reintegrate.js +1 -1
  81. package/build/src/git/admission.js +18 -7
  82. package/build/src/identity/coordinates.js +1 -1
  83. package/build/src/identity/normalize.d.ts +3 -0
  84. package/build/src/identity/normalize.js +10 -0
  85. package/build/src/identity/selector.d.ts +1 -0
  86. package/build/src/identity/selector.js +15 -4
  87. package/build/src/library/address.d.ts +33 -3
  88. package/build/src/library/address.js +90 -29
  89. package/build/src/library/akuma-creation.d.ts +1 -1
  90. package/build/src/library/akuma-creation.js +10 -52
  91. package/build/src/library/composition.d.ts +14 -1
  92. package/build/src/library/fleet.d.ts +13 -2
  93. package/build/src/library/fleet.js +142 -48
  94. package/build/src/library/keiyaku.d.ts +14 -1
  95. package/build/src/plugin/public.d.ts +2 -0
  96. package/build/src/protocol/completion.js +9 -2
  97. package/build/src/runtime/proc/windows-launch.exe +0 -0
  98. package/package.json +1 -1
@@ -1,14 +1,14 @@
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";
11
- import { bornStatus, defaultWaitComplete, readWaitComplete, waitForObservation, } from "./akuma-observe.js";
10
+ import { AkumaNotBornError, AkumaProviderError } from "./akuma-errors.js";
11
+ import { bornStatus, bornLiveStatus, defaultWaitComplete, readWaitComplete, waitForObservation, } from "./akuma-observe.js";
12
12
  const CALL_EXECUTION = Symbol("akuma-call-execution");
13
13
  function diagnostic(error) {
14
14
  return error instanceof Error ? error.message : String(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,69 @@ 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
+ if (options.observe !== undefined) {
213
+ await options.observe(await bornLiveStatus(this.paths, this.id, { aperture: "monitoring", admittedTellId: tellId }));
214
+ }
215
+ return { outcome, terminalWithoutTurn: tell.state === "told" && tell.binding === undefined };
216
+ },
217
+ complete: (observed) => observed.outcome !== null || observed.terminalWithoutTurn,
218
+ });
219
+ return { reason: waited.reason, outcome: waited.value.outcome };
220
+ }
221
+ async boundTellOutcome(tell) {
222
+ if (tell.binding === undefined)
223
+ return null;
224
+ const turn = await readTurn(this.paths, tell.binding.turnSequence);
225
+ return turn?.end?.outcome ?? null;
166
226
  }
167
227
  async interrupt(body, options = {}) {
228
+ const admitted = await this.admitInterrupt(body, options);
229
+ if (admitted.kind === "unavailable")
230
+ return admitted;
231
+ return { kind: "interrupted", putDown: admitted.putDown, tell: await admitted.wake };
232
+ }
233
+ /**
234
+ * Settle the predecessor and record the interrupt Tell, returning at the
235
+ * admission boundary so a bounded caller owns the wake wait.
236
+ */
237
+ async admitInterrupt(body, options = {}) {
168
238
  const request = await requestPause(this.paths, new Date().toISOString(), options.signal);
169
239
  if (request.kind === "not-born") {
170
240
  throw new AkumaNotBornError(this.id);
@@ -204,15 +274,16 @@ export class AkumaHandle {
204
274
  });
205
275
  if (admitted.kind === "not-born")
206
276
  throw new AkumaNotBornError(this.id);
207
- recorded = { kind: "recorded", tellId: admitted.tell.id };
277
+ recorded = admitted.tell;
208
278
  }
209
279
  finally {
210
280
  leash.release();
211
281
  }
212
282
  return {
213
- kind: "interrupted",
283
+ kind: "admitted",
284
+ tell: recorded,
214
285
  putDown,
215
- tell: await wakeRecordedTell(this.paths, recorded.tellId, options.runtime, options.signal),
286
+ wake: wakeRecordedTell(this.paths, recorded.id, options.runtime, options.signal),
216
287
  };
217
288
  }
218
289
  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)
@@ -15,6 +15,12 @@ export type BudgetedStatusObservation = Readonly<{
15
15
  export type LiveStatusObservation = BudgetedStatusObservation & Readonly<{
16
16
  rows: readonly ActivityRow[];
17
17
  }>;
18
+ /** Internal live companion for an observation loop; it is not a public result. */
19
+ export declare function bornLiveStatus(paths: AkumaPaths, expected: AkuId, input: Readonly<{
20
+ aperture: "monitoring" | "receipt";
21
+ ordinaryBudget?: number;
22
+ admittedTellId?: string;
23
+ }>): Promise<LiveStatusObservation>;
18
24
  export declare function bornStatus(paths: AkumaPaths, expected: AkuId, input: Readonly<{
19
25
  aperture: "monitoring" | "receipt";
20
26
  ordinaryBudget?: number;
@@ -56,7 +56,8 @@ async function bornObservation(paths, expected, read, snapshot) {
56
56
  claim?.release();
57
57
  }
58
58
  }
59
- async function bornLiveStatus(paths, expected, input) {
59
+ /** Internal live companion for an observation loop; it is not a public result. */
60
+ export async function bornLiveStatus(paths, expected, input) {
60
61
  if (input.ordinaryBudget !== undefined && (!Number.isSafeInteger(input.ordinaryBudget) || input.ordinaryBudget < 0))
61
62
  throw new TypeError("ordinary budget must be a nonnegative safe integer");
62
63
  const { snapshot, soul, currentLife } = await bornObservation(paths, expected, () => readLifeSnapshot(paths));
@@ -74,6 +75,7 @@ async function bornLiveStatus(paths, expected, input) {
74
75
  status: {
75
76
  id: soul.id,
76
77
  life: currentLife,
78
+ cwd: soul.cwd,
77
79
  allowed: soul.allowed,
78
80
  ...(soul.readonly === undefined ? {} : { readonly: soul.readonly }),
79
81
  ...(resumeUnsupported ? { strandedReason: "resume-unsupported" } : {}),
@@ -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
  }
@@ -32,6 +32,7 @@ export declare const akumaStatusSchema: z.ZodObject<{
32
32
  asleep: "asleep";
33
33
  stranded: "stranded";
34
34
  }>;
35
+ cwd: z.ZodOptional<z.ZodString>;
35
36
  allowed: z.ZodPipe<z.ZodArray<z.ZodEnum<{
36
37
  "task.add": "task.add";
37
38
  "task.addDocument": "task.addDocument";
@@ -207,6 +208,10 @@ export declare const akumaStatusSchema: z.ZodObject<{
207
208
  }, z.core.$strict>, z.ZodObject<{
208
209
  kind: z.ZodLiteral<"other">;
209
210
  display: z.ZodString;
211
+ input: z.ZodOptional<z.ZodObject<{
212
+ json: z.ZodString;
213
+ truncated: z.ZodBoolean;
214
+ }, z.core.$strict>>;
210
215
  }, z.core.$strict>]>;
211
216
  state: z.ZodLiteral<"active">;
212
217
  truncated: z.ZodOptional<z.ZodLiteral<true>>;
@@ -254,6 +259,10 @@ export declare const akumaStatusSchema: z.ZodObject<{
254
259
  }, z.core.$strict>, z.ZodObject<{
255
260
  kind: z.ZodLiteral<"other">;
256
261
  display: z.ZodString;
262
+ input: z.ZodOptional<z.ZodObject<{
263
+ json: z.ZodString;
264
+ truncated: z.ZodBoolean;
265
+ }, z.core.$strict>>;
257
266
  }, z.core.$strict>]>;
258
267
  state: z.ZodObject<{
259
268
  status: z.ZodEnum<{
@@ -409,6 +418,10 @@ export declare const akumaStatusSchema: z.ZodObject<{
409
418
  }, z.core.$strict>, z.ZodObject<{
410
419
  kind: z.ZodLiteral<"other">;
411
420
  display: z.ZodString;
421
+ input: z.ZodOptional<z.ZodObject<{
422
+ json: z.ZodString;
423
+ truncated: z.ZodBoolean;
424
+ }, z.core.$strict>>;
412
425
  }, z.core.$strict>]>;
413
426
  state: z.ZodObject<{
414
427
  status: z.ZodEnum<{
@@ -26,6 +26,7 @@ export const akumaStatusSchema = z
26
26
  .object({
27
27
  id: akumaIdSchema,
28
28
  life: z.enum(["running", "asleep", "stranded", "hung", "untidy", "killed"]),
29
+ cwd: z.string().optional(),
29
30
  allowed: allowedActionsSchema,
30
31
  readonly: readonlySchema.optional(),
31
32
  timeline: activitySnapshotSchema,
@@ -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);
@@ -1,9 +1,10 @@
1
- import { type AkumaStatus } from "./akuma.js";
1
+ import { type AkumaStatus, type TellResult } from "./akuma.js";
2
+ import { type LiveStatusObservation } from "./akuma-observe.js";
2
3
  import type { ActivityRow } from "./projection.js";
3
4
  import { type DispatchAssociation } from "./dispatch-association.js";
4
5
  import type { AkumaAlias } from "../identity/selector.js";
5
6
  import type { WorldRoot } from "../world.js";
6
- import { type AkumaKillResult, type AkumaTellResult, type AkumaWaitResult } from "./fleet-observation.js";
7
+ import { type AkumaKillResult, type AkumaTellResult, type AkumaTellWaitResult, type AkumaWaitResult } from "./fleet-observation.js";
7
8
  export type WaitIdentityFacts = Readonly<{
8
9
  /** The alias currently addressing this Akuma in its world, when one is bound to it. */
9
10
  alias?: AkumaAlias;
@@ -40,6 +41,10 @@ export type WaitExecutionInput = Readonly<{
40
41
  observe?: (observed: readonly WaitObservedAkuma[]) => void;
41
42
  }>;
42
43
  export declare function executeWaitAkuma(input: WaitExecutionInput): Promise<AkumaWaitResult>;
44
+ export type TellWaitObserver = Readonly<{
45
+ admitted?: (tell: TellResult, id: AkumaStatus["id"]) => void | Promise<void>;
46
+ observe?: (observation: LiveStatusObservation) => void | Promise<void>;
47
+ }>;
43
48
  export type TellExecutionInput = Readonly<{
44
49
  path: WorldRoot;
45
50
  id: AkumaStatus["id"];
@@ -48,8 +53,14 @@ export type TellExecutionInput = Readonly<{
48
53
  recordedAt?: string;
49
54
  initiator?: string;
50
55
  signal?: AbortSignal;
56
+ onObserve?: TellWaitObserver;
51
57
  }>;
52
58
  export declare function executeTellAkuma(input: TellExecutionInput): Promise<AkumaTellResult>;
59
+ export declare function executeTellWaitAkuma(input: TellExecutionInput & Readonly<{
60
+ timeoutMs: number;
61
+ schemaJson?: string;
62
+ interrupt?: boolean;
63
+ }>): Promise<AkumaTellWaitResult>;
53
64
  export type KillExecutionInput = Readonly<{
54
65
  path: WorldRoot;
55
66
  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,50 @@ export async function executeTellAkuma(input) {
125
126
  tell,
126
127
  });
127
128
  }
129
+ function tellWaitObservation(observed) {
130
+ if (observed.outcome === null)
131
+ return observed.reason === "deadline" ? { reason: "deadline" } : { reason: "unanswered" };
132
+ if (observed.outcome.kind === "answered")
133
+ return { reason: "answered", answer: observed.outcome.answerJson ?? observed.outcome.answer };
134
+ if (observed.outcome.kind === "failed")
135
+ return { reason: "failed", diagnostic: observed.outcome.diagnostic };
136
+ throw new AkumaDecodeError(observed.outcome.diagnostic, observed.outcome.answer);
137
+ }
138
+ export async function executeTellWaitAkuma(input) {
139
+ input.signal?.throwIfAborted();
140
+ const handle = source(input.path).selectHandle({ id: input.id });
141
+ const admission = input.interrupt === true
142
+ ? await handle.admitInterrupt(input.body, {
143
+ ...(input.tellId === undefined ? {} : { tellId: input.tellId }),
144
+ ...(input.schemaJson === undefined ? {} : { schemaJson: input.schemaJson }),
145
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
146
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
147
+ })
148
+ : await handle.admitTell(input.body, input.tellId, input.recordedAt, undefined, {
149
+ ...(input.schemaJson === undefined ? {} : { schemaJson: input.schemaJson }),
150
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
151
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
152
+ });
153
+ if (admission.kind === "unavailable")
154
+ throw new AkumaProviderError(`Tell interrupt unavailable: ${admission.evidence}`);
155
+ if (admission.kind === "not-born")
156
+ throw new AkumaNotBornError(input.id);
157
+ await input.onObserve?.admitted?.(await handle.admittedReceipt(admission.tell.id), input.id);
158
+ // The wake runs behind the window; a settled wake is preferred, otherwise the
159
+ // receipt reports the admitted Tell as it stands when the window closes.
160
+ let settled;
161
+ void admission.wake.then((receipt) => {
162
+ settled = receipt;
163
+ }, () => undefined);
164
+ const observed = await handle.tellOutcome(admission.tell.id, {
165
+ timeoutMs: input.timeoutMs,
166
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
167
+ ...(input.onObserve?.observe === undefined ? {} : { observe: input.onObserve.observe }),
168
+ });
169
+ const observation = tellWaitObservation(observed);
170
+ const tell = settled ?? (await handle.admittedReceipt(admission.tell.id));
171
+ return fleetResultSchemas.tellWait.parse({ akuma: input.id, tell, observation });
172
+ }
128
173
  export async function executeKillAkuma(input) {
129
174
  input.signal?.throwIfAborted();
130
175
  const handles = input.ids.map((id) => source(input.path).selectHandle({ id }));