@astrosheep/keiyaku 4.5.14 → 4.5.16

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 (46) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +1 -1
  3. package/build/src/akuma/akuma-errors.d.ts +11 -0
  4. package/build/src/akuma/akuma-errors.js +20 -0
  5. package/build/src/akuma/akuma-handle.d.ts +1 -0
  6. package/build/src/akuma/akuma-handle.js +36 -0
  7. package/build/src/akuma/akuma-instance.d.ts +31 -0
  8. package/build/src/akuma/akuma-instance.js +168 -0
  9. package/build/src/akuma/akuma-probe.d.ts +3 -0
  10. package/build/src/akuma/akuma-probe.js +6 -0
  11. package/build/src/akuma/akuma-product.d.ts +1 -0
  12. package/build/src/akuma/akuma-product.js +2 -0
  13. package/build/src/akuma/akuma.d.ts +4 -0
  14. package/build/src/akuma/akuma.js +1 -0
  15. package/build/src/akuma/body.d.ts +1 -0
  16. package/build/src/akuma/body.js +7 -3
  17. package/build/src/akuma/fleet-request.d.ts +35 -2
  18. package/build/src/akuma/fleet-request.js +57 -3
  19. package/build/src/akuma/heart/index.js +1 -1
  20. package/build/src/akuma/heart/tells.js +2 -6
  21. package/build/src/akuma/index.d.ts +7 -14
  22. package/build/src/akuma/index.js +3 -9
  23. package/build/src/akuma/providers/claude/index.js +6 -1
  24. package/build/src/akuma/providers/pi/index.js +3 -1
  25. package/build/src/akuma/schema.d.ts +16 -0
  26. package/build/src/akuma/schema.js +85 -0
  27. package/build/src/akuma/turn-drive.js +1 -3
  28. package/build/src/akuma-body.js +6 -0
  29. package/build/src/cli/commands/akuma-invoke.d.ts +10 -1
  30. package/build/src/cli/commands/akuma-invoke.js +64 -0
  31. package/build/src/cli/commands/akuma.d.ts +2 -0
  32. package/build/src/cli/commands/akuma.js +17 -4
  33. package/build/src/cli/render/akuma-activity.d.ts +1 -1
  34. package/build/src/cli/render/akuma-tool.d.ts +1 -1
  35. package/build/src/cli/render/akuma.js +8 -2
  36. package/build/src/kanshi/read.js +1 -1
  37. package/build/src/kanshi/report.d.ts +1 -1
  38. package/build/src/library/address.js +1 -1
  39. package/build/src/library/akuma-creation.d.ts +2 -0
  40. package/build/src/library/akuma-creation.js +2 -0
  41. package/build/src/library/fleet.d.ts +2 -2
  42. package/build/src/library/fleet.js +1 -2
  43. package/build/src/library/keiyaku.d.ts +1 -1
  44. package/build/src/plugin/runtime.d.ts +2 -0
  45. package/build/src/plugin/runtime.js +24 -1
  46. package/package.json +2 -2
@@ -33,7 +33,7 @@ keiyaku -C <repo> review [<contract>|@<contract>] --satisfied
33
33
  ```
34
34
 
35
35
  ```bash
36
- keiyaku -C <cwd> call <akuma-name> [--contract <kei/...>] [--alias @name] [--readonly] [--allowed <product.action>]... [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
36
+ keiyaku -C <cwd> call <akuma-name> [--contract <kei/...>] [--alias @name] [--readonly] [--allowed <product.action>]... [--schema <file>] [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
37
37
  keiyaku -C <repo> wait <akuma-selector>... [--any | --all]
38
38
  keiyaku -C <repo> tell <aku/...|@alias> (<prompt> | -)
39
39
  ```
@@ -13,7 +13,7 @@ is accepted; the identity underneath never changes.
13
13
  ## Start One
14
14
 
15
15
  ```bash
16
- keiyaku -C <cwd> call <akuma-name> [--alias @name] [--readonly] [--allowed <product.action>]... [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
16
+ keiyaku -C <cwd> call <akuma-name> [--alias @name] [--readonly] [--allowed <product.action>]... [--schema <file>] [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
17
17
  ```
18
18
 
19
19
  Give the worker's initial prompt as one argument (quote it when it contains
@@ -5,3 +5,14 @@ export declare class AkumaNotBornError extends Error {
5
5
  readonly kind = "akuma-not-born";
6
6
  constructor(id: AkuId);
7
7
  }
8
+ export declare class AkumaDecodeError extends Error {
9
+ readonly diagnostic: string;
10
+ readonly answer?: string | undefined;
11
+ readonly kind = "akuma-decode";
12
+ constructor(diagnostic: string, answer?: string | undefined);
13
+ }
14
+ export declare class AkumaProviderError extends Error {
15
+ readonly diagnostic: string;
16
+ readonly kind = "akuma-provider";
17
+ constructor(diagnostic: string);
18
+ }
@@ -8,3 +8,23 @@ export class AkumaNotBornError extends Error {
8
8
  this.name = "AkumaNotBornError";
9
9
  }
10
10
  }
11
+ export class AkumaDecodeError extends Error {
12
+ diagnostic;
13
+ answer;
14
+ kind = "akuma-decode";
15
+ constructor(diagnostic, answer) {
16
+ super(diagnostic);
17
+ this.diagnostic = diagnostic;
18
+ this.answer = answer;
19
+ this.name = "AkumaDecodeError";
20
+ }
21
+ }
22
+ export class AkumaProviderError extends Error {
23
+ diagnostic;
24
+ kind = "akuma-provider";
25
+ constructor(diagnostic) {
26
+ super(diagnostic);
27
+ this.diagnostic = diagnostic;
28
+ this.name = "AkumaProviderError";
29
+ }
30
+ }
@@ -25,6 +25,7 @@ export declare class AkumaHandle {
25
25
  }>): Promise<AkumaStatus>;
26
26
  tell(body: string): Promise<TellResult>;
27
27
  interrupt(body: string): Promise<InterruptReceipt>;
28
+ interruptSchema(body: string, schemaJson: string): Promise<TellResult>;
28
29
  fork(input: Readonly<{
29
30
  at: string;
30
31
  }>): Promise<ForkReceipt>;
@@ -196,6 +196,42 @@ export class AkumaHandle {
196
196
  }
197
197
  return { kind: "interrupted", putDown, tell: await wakeRecordedTell(this.paths, recorded.tellId) };
198
198
  }
199
+ async interruptSchema(body, schemaJson) {
200
+ const request = await requestPause(this.paths, new Date().toISOString());
201
+ if (request.kind === "not-born")
202
+ throw new AkumaNotBornError(this.id);
203
+ let tellId;
204
+ let leash = await HeldAkumaLeash.try(this.paths);
205
+ if (leash === null)
206
+ leash = await takeLeashUntil(this.paths, performance.now() + CONTROL_RESPONSE_MS);
207
+ if (leash === null)
208
+ throw new Error("schema interrupt could not acquire Body leash");
209
+ try {
210
+ const settledBody = (await readHeart(this.paths)).latestBody;
211
+ if (settledBody?.sequence === request.body.sequence && settledBody.hung !== undefined) {
212
+ await leash.clearPause(this.paths);
213
+ throw new Error("schema interrupt found a hung Body");
214
+ }
215
+ if (settledBody?.sequence !== request.body.sequence || settledBody.end === undefined) {
216
+ await leash.clearPause(this.paths);
217
+ throw new Error("schema interrupt could not prove Body settlement");
218
+ }
219
+ const admitted = await leash.recordInterruptTell(this.paths, {
220
+ kind: "tell",
221
+ id: randomUUID(),
222
+ body,
223
+ recordedAt: new Date().toISOString(),
224
+ schemaJson,
225
+ });
226
+ if (admitted.kind === "not-born")
227
+ throw new AkumaNotBornError(this.id);
228
+ tellId = admitted.tell.id;
229
+ }
230
+ finally {
231
+ leash.release();
232
+ }
233
+ return await wakeRecordedTell(this.paths, tellId);
234
+ }
199
235
  async fork(input) {
200
236
  const source = await readSoul(this.paths);
201
237
  if (source === null)
@@ -0,0 +1,31 @@
1
+ import type { AllowedAction } from "./allowed.js";
2
+ import { type AkuId } from "./identity.js";
3
+ import { type ActivityHistory } from "./projection.js";
4
+ import type { Settings } from "../settings.js";
5
+ import type { WorldRoot } from "../world.js";
6
+ import type { Schema } from "./schema.js";
7
+ export type AkumaBirthInput = Readonly<{
8
+ root: WorldRoot;
9
+ cwd?: string;
10
+ home?: string;
11
+ settings?: Settings;
12
+ readonly?: true;
13
+ allowed?: readonly AllowedAction[];
14
+ }>;
15
+ export type AkumaTellOptions<T> = Readonly<{
16
+ schema: Schema<T>;
17
+ interrupt?: boolean;
18
+ }>;
19
+ export declare class Akuma {
20
+ readonly id: AkuId;
21
+ private readonly root;
22
+ private constructor();
23
+ private get paths();
24
+ static birth(archetype: string, input: AkumaBirthInput): Promise<Akuma>;
25
+ static select(root: WorldRoot, selector: string): Akuma;
26
+ tell(text: string): Promise<string>;
27
+ tell<T>(text: string, options: AkumaTellOptions<T>): Promise<T>;
28
+ idle(): Promise<void>;
29
+ history(): Promise<ActivityHistory>;
30
+ kill(): Promise<void>;
31
+ }
@@ -0,0 +1,168 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { callReadonly, canonicalBirthCwd } from "./call-input.js";
3
+ import { spawnAkumaBody, wakeRecordedTell } from "./body.js";
4
+ import { decodeAllowedActions, unionAllowedActions } from "./allowed.js";
5
+ import { AkumaDecodeError, AkumaNotBornError, AkumaProviderError } from "./akuma-errors.js";
6
+ import { AkumaHandle } from "./akuma-handle.js";
7
+ import { POLL_MS, defaultWaitComplete, killAkumaWithRecovery } from "./akuma.js";
8
+ import { bornStatus } from "./akuma-observe.js";
9
+ import { loadArchetype } from "./archetype.js";
10
+ import { activitySlice, readTell, readTurn, recordTell } from "./heart/index.js";
11
+ import { parseAkuId, pathsForAkuId } from "./identity.js";
12
+ import { birthAkuma, launchAkuma } from "./publication.js";
13
+ import { projectTurns, selectHistory } from "./projection.js";
14
+ import { settings as readSettings } from "../settings.js";
15
+ const HISTORY_LIMIT = 12;
16
+ function wait(milliseconds) {
17
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
18
+ }
19
+ async function recordPlainTell(paths, id, body, tellId) {
20
+ const admitted = await recordTell(paths, { kind: "tell", id: tellId, body, recordedAt: new Date().toISOString() });
21
+ if (admitted.kind === "not-born")
22
+ throw new AkumaNotBornError(id);
23
+ return admitted.tell.id;
24
+ }
25
+ async function recordSchemaTell(paths, id, body, tellId, options, root) {
26
+ if (options.interrupt === true) {
27
+ const interrupted = await new AkumaHandle(id, root).interruptSchema(body, options.schema.jsonText);
28
+ return interrupted.admission.tellId;
29
+ }
30
+ const recordedAt = new Date().toISOString();
31
+ const tell = {
32
+ kind: "tell",
33
+ id: tellId,
34
+ body,
35
+ recordedAt,
36
+ schemaJson: options.schema.jsonText,
37
+ };
38
+ const admitted = await recordTell(paths, tell);
39
+ if (admitted.kind === "not-born")
40
+ throw new AkumaNotBornError(id);
41
+ return admitted.tell.id;
42
+ }
43
+ function outcomeError(outcome) {
44
+ if (outcome.kind === "invalid-output")
45
+ throw new AkumaDecodeError(outcome.diagnostic, outcome.answer);
46
+ if (outcome.kind === "failed")
47
+ throw new AkumaProviderError(outcome.diagnostic);
48
+ throw new AkumaProviderError("Akuma answered without a value");
49
+ }
50
+ async function boundOutcome(paths, tell) {
51
+ if (tell.binding === undefined)
52
+ return null;
53
+ const turn = await readTurn(paths, tell.binding.turnSequence);
54
+ return turn?.end?.outcome ?? null;
55
+ }
56
+ async function awaitTellOutcome(paths, tellId) {
57
+ const wake = await wakeRecordedTell(paths, tellId);
58
+ if (wake.wake.kind === "failed")
59
+ throw new AkumaProviderError(wake.wake.diagnostic);
60
+ for (;;) {
61
+ const tell = await readTell(paths, tellId);
62
+ if (tell === null)
63
+ throw new AkumaProviderError(`recorded Tell ${tellId} is missing from Heart`);
64
+ const outcome = await boundOutcome(paths, tell);
65
+ if (outcome !== null)
66
+ return outcome;
67
+ if (tell.state === "told" && tell.binding === undefined) {
68
+ throw new AkumaProviderError(`recorded Tell ${tellId} reached a terminal delivery without a Turn binding`);
69
+ }
70
+ await wait(POLL_MS);
71
+ }
72
+ }
73
+ export class Akuma {
74
+ id;
75
+ root;
76
+ constructor(id, root) {
77
+ this.id = id;
78
+ this.root = root;
79
+ Object.freeze(this);
80
+ }
81
+ get paths() {
82
+ return pathsForAkuId(this.root, this.id);
83
+ }
84
+ static async birth(archetype, input) {
85
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
86
+ throw new TypeError("Akuma birth input must be an object");
87
+ }
88
+ if (typeof input.root !== "string")
89
+ throw new TypeError("Akuma birth root must be a WorldRoot");
90
+ const name = archetype;
91
+ const home = input.home === undefined ? {} : { home: input.home };
92
+ const settings = input.settings ?? (await readSettings({ root: input.root, ...home }));
93
+ const readonly = callReadonly(input.readonly, "Akuma birth readonly must be true");
94
+ const loaded = await loadArchetype({ name, project: input.root, ...home, settings, ...readonly });
95
+ const allowed = input.allowed === undefined
96
+ ? loaded.allowed
97
+ : unionAllowedActions(loaded.allowed, decodeAllowedActions(input.allowed, "Akuma birth allowed"));
98
+ const cwd = input.cwd === undefined ? input.root : await canonicalBirthCwd(input.cwd);
99
+ const allocated = await birthAkuma({ worldPath: input.root, archetype: loaded.name });
100
+ await launchAkuma({
101
+ allocated,
102
+ launch: async (born) => await spawnAkumaBody({
103
+ paths: born.paths,
104
+ seed: {
105
+ id: born.id,
106
+ archetype: born.archetype,
107
+ ...(loaded.description === undefined ? {} : { description: loaded.description }),
108
+ provider: loaded.provider,
109
+ options: loaded.options,
110
+ ...(loaded.readonly === undefined ? {} : { readonly: loaded.readonly }),
111
+ allowed,
112
+ cwd,
113
+ origin: { kind: "direct" },
114
+ },
115
+ }),
116
+ });
117
+ return new Akuma(allocated.id, input.root);
118
+ }
119
+ static select(root, selector) {
120
+ if (typeof root !== "string")
121
+ throw new TypeError("Akuma.select root must be a WorldRoot");
122
+ return new Akuma(parseAkuId(selector).id, root);
123
+ }
124
+ async tell(text, options) {
125
+ if (typeof text !== "string")
126
+ throw new TypeError("Akuma tell text must be a string");
127
+ const tellId = randomUUID();
128
+ const recorded = options === undefined
129
+ ? await recordPlainTell(this.paths, this.id, text, tellId)
130
+ : await recordSchemaTell(this.paths, this.id, text, tellId, options, this.root);
131
+ const outcome = await awaitTellOutcome(this.paths, recorded);
132
+ if (outcome.kind !== "answered")
133
+ outcomeError(outcome);
134
+ if (options === undefined)
135
+ return outcome.answer;
136
+ const raw = outcome.answerJson ?? outcome.answer;
137
+ let parsed;
138
+ try {
139
+ parsed = JSON.parse(raw);
140
+ }
141
+ catch (error) {
142
+ throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer is not valid JSON", outcome.answer);
143
+ }
144
+ try {
145
+ return options.schema.parse(parsed);
146
+ }
147
+ catch (error) {
148
+ throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer failed schema decode", outcome.answer);
149
+ }
150
+ }
151
+ async idle() {
152
+ for (;;) {
153
+ const observed = await bornStatus(this.paths, this.id, { aperture: "monitoring" });
154
+ if (defaultWaitComplete(observed.status))
155
+ return;
156
+ await wait(POLL_MS);
157
+ }
158
+ }
159
+ async history() {
160
+ const slice = await activitySlice(this.paths);
161
+ return selectHistory(projectTurns(slice.rows, { lowestRetained: slice.lowestRetained, highest: slice.highest }), {
162
+ limit: HISTORY_LIMIT,
163
+ });
164
+ }
165
+ async kill() {
166
+ await killAkumaWithRecovery(this.paths);
167
+ }
168
+ }
@@ -0,0 +1,3 @@
1
+ import { type AkuId } from "./identity.js";
2
+ import type { WorldRoot } from "../world.js";
3
+ export declare function probeBornAkuma(worldPath: WorldRoot, id: AkuId): Promise<boolean>;
@@ -0,0 +1,6 @@
1
+ import { readSoul } from "./heart/index.js";
2
+ import { pathsForAkuId } from "./identity.js";
3
+ export async function probeBornAkuma(worldPath, id) {
4
+ const soul = await readSoul(pathsForAkuId(worldPath, id));
5
+ return soul !== null;
6
+ }
@@ -21,6 +21,7 @@ export type BornAkumaCall = Readonly<{
21
21
  };
22
22
  }>;
23
23
  initialBody: string;
24
+ initialSchemaJson?: string;
24
25
  execution: BornExecution;
25
26
  }>;
26
27
  export type RequestedAkumaCall = Readonly<{
@@ -185,6 +185,7 @@ export class Akuma {
185
185
  origin: { kind: "direct" },
186
186
  },
187
187
  initialBody: input.body,
188
+ ...(input.schema === undefined ? {} : { initialSchemaJson: input.schema.jsonText }),
188
189
  execution: {
189
190
  cwd,
190
191
  source: input.cwd !== undefined ? "input" : initiatorCwd === undefined ? "world" : "process",
@@ -201,6 +202,7 @@ export class Akuma {
201
202
  paths: allocated.paths,
202
203
  seed: born.seed,
203
204
  initialBody: born.initialBody,
205
+ ...(born.initialSchemaJson === undefined ? {} : { initialSchemaJson: born.initialSchemaJson }),
204
206
  ...(Object.keys(completion).length === 0 ? {} : { completion }),
205
207
  }),
206
208
  });
@@ -1,5 +1,6 @@
1
1
  import { type TellResult, type TellWakeRuntime } from "./body.js";
2
2
  import { HeldAkumaLeash, type AkumaLife, type KillEvidence, type ResumeCoordinate } from "./heart/index.js";
3
+ export type { KillEvidence };
3
4
  import { type AkuId, type AkumaPaths } from "./identity.js";
4
5
  import type { Settings } from "../settings.js";
5
6
  import type { WorldRoot } from "../world.js";
@@ -7,6 +8,7 @@ import type { AllowedAction } from "./allowed.js";
7
8
  import type { ExecutionContext } from "./requests.js";
8
9
  import { z } from "zod";
9
10
  import type { BoundedList } from "../bounded-list.js";
11
+ import type { Schema } from "./schema.js";
10
12
  export declare const POLL_MS = 100;
11
13
  export type AkumaListRow = Readonly<{
12
14
  id: AkuId;
@@ -436,6 +438,7 @@ export type AkumaCallInput = Readonly<{
436
438
  cwd?: string;
437
439
  readonly?: true;
438
440
  allowed?: readonly AllowedAction[];
441
+ schema?: Schema<unknown>;
439
442
  }>;
440
443
  export type AkumaCallContext = Readonly<{
441
444
  initiatorCwd?: string;
@@ -492,3 +495,4 @@ export type AkumaConfiguration = Readonly<{
492
495
  }>;
493
496
  export { AkumaHandle, akumaCallExecution, type LastAnswer } from "./akuma-handle.js";
494
497
  export { Akuma } from "./akuma-product.js";
498
+ export { AkumaBusyError, AkumaDecodeError, AkumaProviderError } from "./akuma-errors.js";
@@ -107,3 +107,4 @@ export async function killAkumaWithRecovery(paths, recover = handoffPendingTells
107
107
  export { bornStatus, fleetListRow, readAkumaBirthCwd, readBudgetedStatus, } from "./akuma-observe.js";
108
108
  export { AkumaHandle, akumaCallExecution } from "./akuma-handle.js";
109
109
  export { Akuma } from "./akuma-product.js";
110
+ export { AkumaBusyError, AkumaDecodeError, AkumaProviderError } from "./akuma-errors.js";
@@ -12,6 +12,7 @@ export type BodyLaunch = Readonly<{
12
12
  seed?: Omit<Soul, "createdAt">;
13
13
  birthSession?: Omit<SessionFact, "sequence">;
14
14
  initialBody?: string;
15
+ initialSchemaJson?: string;
15
16
  refuseIfHeld?: boolean;
16
17
  completion?: Readonly<{
17
18
  contractId?: string;
@@ -5,7 +5,7 @@ import { BodySupervisor } from "./body-supervisor.js";
5
5
  import { driveTurn, turnRecipe } from "./turn-drive.js";
6
6
  import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, provePendingTellDispositionCustody, readHeart, readOpenPendingTellDisposition, readTell, readTurn, readNonterminalRequests, recordUndeliveredPendingTells, resolvePendingTellDisposition, } from "./heart/index.js";
7
7
  import { worldRootForAkumaPaths } from "./identity.js";
8
- import { pluginRuntime } from "../plugin/runtime.js";
8
+ import { drainPluginRuntime, pluginRuntime } from "../plugin/runtime.js";
9
9
  import { World } from "../world.js";
10
10
  import { resolveProviderExecution } from "./providers/index.js";
11
11
  import { clearBodyRequestTransport, settleBodyRequests } from "./request-serve.js";
@@ -250,7 +250,9 @@ async function runBodyTurns(input) {
250
250
  ...(initial === undefined ? {} : { call: initial }),
251
251
  launchTells,
252
252
  ...(launchTells.find((tell) => tell.schemaJson !== undefined)?.schemaJson === undefined
253
- ? {}
253
+ ? launch.initialSchemaJson === undefined
254
+ ? {}
255
+ : { schemaJson: launch.initialSchemaJson }
254
256
  : { schemaJson: launchTells.find((tell) => tell.schemaJson !== undefined).schemaJson }),
255
257
  world: runtime.world,
256
258
  externalCommands: runtime.externalCommands,
@@ -456,7 +458,9 @@ export async function spawnAkumaBody(launch) {
456
458
  return await spawnDetachedProcess(await bodyProcessInput(launch));
457
459
  }
458
460
  export async function runAkumaBody(launch, world, externalCommands) {
459
- return await driveAkumaBody(launch, undefined, { world, externalCommands });
461
+ const result = await driveAkumaBody(launch, undefined, { world, externalCommands });
462
+ await drainPluginRuntime(world);
463
+ return result;
460
464
  }
461
465
  const DIRECT_TELL_WAKE = {
462
466
  spawn: async (paths) => await spawnAkumaBody({ paths, refuseIfHeld: true }),
@@ -3,6 +3,7 @@ import { type ErasedRequestCommand, type RequestProtocol, type ServiceRequestCom
3
3
  import type { AkumaStatus } from "./akuma.js";
4
4
  import { type AkumaKillResult, type AkumaTellResult, type AkumaWaitResult } from "./fleet-observation.js";
5
5
  import { z } from "zod";
6
+ import type { Schema } from "./schema.js";
6
7
  declare const waitRequestSchema: z.ZodPipe<z.ZodObject<{
7
8
  targets: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<import("./identity.js").AkuId, string>>>;
8
9
  completion: z.ZodEnum<{
@@ -31,6 +32,23 @@ declare const tellRequestSchema: z.ZodPipe<z.ZodObject<{
31
32
  target: import("./identity.js").AkuId;
32
33
  body: string;
33
34
  }>>;
35
+ declare const tellAnswerRequestSchema: z.ZodPipe<z.ZodObject<{
36
+ target: z.ZodPipe<z.ZodString, z.ZodTransform<import("./identity.js").AkuId, string>>;
37
+ body: z.ZodString;
38
+ schemaJson: z.ZodString;
39
+ interrupt: z.ZodOptional<z.ZodBoolean>;
40
+ }, z.core.$strict>, z.ZodTransform<{
41
+ target: import("./identity.js").AkuId;
42
+ body: string;
43
+ schemaJson: string;
44
+ interrupt?: boolean | undefined;
45
+ action: "akuma.tell-answer";
46
+ }, {
47
+ target: import("./identity.js").AkuId;
48
+ body: string;
49
+ schemaJson: string;
50
+ interrupt?: boolean | undefined;
51
+ }>>;
34
52
  declare const killRequestSchema: z.ZodPipe<z.ZodObject<{
35
53
  targets: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<import("./identity.js").AkuId, string>>>;
36
54
  }, z.core.$strict>, z.ZodTransform<{
@@ -63,7 +81,7 @@ declare const killServiceSchema: z.ZodObject<{
63
81
  }, z.core.$strict>;
64
82
  export type FleetRequest = (Omit<z.infer<typeof waitRequestSchema>, "targets"> & Readonly<{
65
83
  targets: readonly AkumaStatus["id"][];
66
- }>) | z.infer<typeof tellRequestSchema> | (Omit<z.infer<typeof killRequestSchema>, "targets"> & Readonly<{
84
+ }>) | z.infer<typeof tellRequestSchema> | z.infer<typeof tellAnswerRequestSchema> | (Omit<z.infer<typeof killRequestSchema>, "targets"> & Readonly<{
67
85
  targets: readonly AkumaStatus["id"][];
68
86
  }>);
69
87
  export type FleetService = z.infer<typeof waitServiceSchema> | z.infer<typeof tellServiceSchema> | (Omit<z.infer<typeof killServiceSchema>, "results"> & Readonly<{
@@ -86,6 +104,13 @@ export type FleetRequestPort = Readonly<{
86
104
  recordedAt: string;
87
105
  signal: AbortSignal;
88
106
  }>): Promise<AkumaTellResult>;
107
+ tellAnswer?(input: Readonly<{
108
+ target: AkumaStatus["id"];
109
+ body: string;
110
+ schemaJson: string;
111
+ interrupt?: boolean;
112
+ signal: AbortSignal;
113
+ }>): Promise<unknown>;
89
114
  kill(input: Readonly<{
90
115
  targets: readonly AkumaStatus["id"][];
91
116
  signal: AbortSignal;
@@ -100,7 +125,15 @@ export type FleetRequestPort = Readonly<{
100
125
  /** Akuma owns Body Request payload, live result, and durable service codecs for wait/tell/kill. */
101
126
  export declare function fleetRequestProtocol(action: FleetRequest["action"]): RequestProtocol<FleetRequest, unknown, FleetService>;
102
127
  export declare function fleetRequestCommand(action: FleetRequest["action"], port: FleetRequestPort): ServiceRequestCommand<FleetRequest, unknown, FleetService, FleetService>;
103
- export declare function fleetRequestCommands(port: FleetRequestPort): Readonly<Record<"akuma.wait" | "akuma.tell" | "akuma.kill", ErasedRequestCommand>>;
128
+ export declare function fleetRequestCommands(port: FleetRequestPort): Readonly<Record<"akuma.wait" | "akuma.tell" | "akuma.tell-answer" | "akuma.kill", ErasedRequestCommand>>;
129
+ export declare function requestForwardedFleetTellAnswer(input: Readonly<{
130
+ directory: string;
131
+ target: AkumaStatus["id"];
132
+ body: string;
133
+ schema: Schema<unknown>;
134
+ interrupt?: boolean;
135
+ signal?: AbortSignal;
136
+ }>): Promise<unknown>;
104
137
  export declare function requestForwardedFleetWait(input: Readonly<{
105
138
  directory: string;
106
139
  targets: readonly AkumaStatus["id"][];
@@ -27,6 +27,15 @@ const tellRequestSchema = z
27
27
  .object({ target: akumaIdSchema, body: z.string() })
28
28
  .strict()
29
29
  .transform((request) => ({ action: "akuma.tell", ...request }));
30
+ const tellAnswerRequestSchema = z
31
+ .object({
32
+ target: akumaIdSchema,
33
+ body: z.string(),
34
+ schemaJson: nonblankTextSchema,
35
+ interrupt: z.boolean().optional(),
36
+ })
37
+ .strict()
38
+ .transform((request) => ({ action: "akuma.tell-answer", ...request }));
30
39
  const killRequestSchema = z
31
40
  .object({ targets: fleetTargetsSchema })
32
41
  .strict()
@@ -42,18 +51,30 @@ const killServiceSchema = z
42
51
  })
43
52
  .strict();
44
53
  function decodeFleetRequest(action, value) {
45
- const schema = action === "akuma.wait" ? waitRequestSchema : action === "akuma.tell" ? tellRequestSchema : killRequestSchema;
54
+ const schema = action === "akuma.wait"
55
+ ? waitRequestSchema
56
+ : action === "akuma.tell"
57
+ ? tellRequestSchema
58
+ : action === "akuma.tell-answer"
59
+ ? tellAnswerRequestSchema
60
+ : killRequestSchema;
46
61
  const parsed = schema.safeParse(value);
47
62
  return parsed.success ? parsed.data : null;
48
63
  }
49
64
  function decodeFleetService(action, value) {
50
- const schema = action === "akuma.wait" ? waitServiceSchema : action === "akuma.tell" ? tellServiceSchema : killServiceSchema;
65
+ const schema = action === "akuma.wait"
66
+ ? waitServiceSchema
67
+ : action === "akuma.tell" || action === "akuma.tell-answer"
68
+ ? tellServiceSchema
69
+ : killServiceSchema;
51
70
  const parsed = schema.safeParse(value);
52
71
  if (!parsed.success)
53
72
  throw new Error("malformed stored Fleet service evidence");
54
73
  return parsed.data;
55
74
  }
56
75
  function decodedFleetResult(action, value) {
76
+ if (action === "akuma.tell-answer")
77
+ return value;
57
78
  const schema = action === "akuma.wait"
58
79
  ? fleetResultSchemas.wait
59
80
  : action === "akuma.tell"
@@ -76,7 +97,8 @@ export function fleetRequestProtocol(action) {
76
97
  encodeResult: (result) => result,
77
98
  decodeResult: (result) => decodedFleetResult(action, result),
78
99
  decodeReference: (reference) => decodeFleetService(action, reference),
79
- isPermitted: (allowed) => action === "akuma.wait" || allowed.includes(action),
100
+ isPermitted: (allowed) => action === "akuma.wait" ||
101
+ ((action === "akuma.tell" || action === "akuma.tell-answer") && allowed.includes("akuma.tell")),
80
102
  };
81
103
  }
82
104
  export function fleetRequestCommand(action, port) {
@@ -105,6 +127,20 @@ export function fleetRequestCommand(action, port) {
105
127
  service: { action: request.action, target: request.target, tellId: facts.id },
106
128
  };
107
129
  }
130
+ if (request.action === "akuma.tell-answer") {
131
+ if (port.tellAnswer === undefined)
132
+ throw new Error("schema answer Fleet port is unavailable");
133
+ return {
134
+ result: await port.tellAnswer({
135
+ target: request.target,
136
+ body: request.body,
137
+ schemaJson: request.schemaJson,
138
+ ...(request.interrupt === undefined ? {} : { interrupt: request.interrupt }),
139
+ signal: facts.signal,
140
+ }),
141
+ service: { action: "akuma.tell", target: request.target, tellId: facts.id },
142
+ };
143
+ }
108
144
  const result = await port.kill({ targets: request.targets, signal: facts.signal });
109
145
  if ("result" in result)
110
146
  return { result: result.result, service: { action: request.action, results: result.service } };
@@ -119,9 +155,27 @@ export function fleetRequestCommands(port) {
119
155
  return {
120
156
  "akuma.wait": eraseRequestCommand(fleetRequestCommand("akuma.wait", port)),
121
157
  "akuma.tell": eraseRequestCommand(fleetRequestCommand("akuma.tell", port)),
158
+ "akuma.tell-answer": eraseRequestCommand(fleetRequestCommand("akuma.tell-answer", port)),
122
159
  "akuma.kill": eraseRequestCommand(fleetRequestCommand("akuma.kill", port)),
123
160
  };
124
161
  }
162
+ export async function requestForwardedFleetTellAnswer(input) {
163
+ const response = await requestBodyCommand({
164
+ directory: input.directory,
165
+ command: fleetRequestProtocol("akuma.tell-answer"),
166
+ value: {
167
+ action: "akuma.tell-answer",
168
+ target: input.target,
169
+ body: input.body,
170
+ schemaJson: input.schema.jsonText,
171
+ ...(input.interrupt === undefined ? {} : { interrupt: input.interrupt }),
172
+ },
173
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
174
+ });
175
+ if (response.kind !== "returned")
176
+ throw new Error("Akuma body request terminal schema answer cannot reproduce an expired live result");
177
+ return response.result;
178
+ }
125
179
  function forwardedFleetCommandResult(response, action) {
126
180
  if (response.kind === "returned") {
127
181
  if (action === "akuma.wait" && isWaitResult(response.result))
@@ -54,7 +54,7 @@ export async function activitySlice(paths) {
54
54
  return await withHeart(paths, (heart) => readTransaction(heart, () => activityFactSlice(heart)));
55
55
  }
56
56
  function sameTellInput(existing, tell) {
57
- return existing.body === tell.body && existing.recordedAt === tell.recordedAt && existing.schemaJson === tell.schemaJson;
57
+ return (existing.body === tell.body && existing.recordedAt === tell.recordedAt && existing.schemaJson === tell.schemaJson);
58
58
  }
59
59
  export async function recordTell(paths, tell, options = {}) {
60
60
  return await withHeart(paths, (heart) => transaction(heart, () => {
@@ -25,9 +25,7 @@ function tellDeliveries(database, id) {
25
25
  : { turnSequence: row.turn_sequence, route: row.route, receipt: row.receipt, deliveredAt: row.delivered_at });
26
26
  }
27
27
  function tellBinding(database, id) {
28
- const row = database
29
- .prepare("SELECT turn_sequence, bound_at FROM tell_bindings WHERE tell_id = ?")
30
- .get(id);
28
+ const row = database.prepare("SELECT turn_sequence, bound_at FROM tell_bindings WHERE tell_id = ?").get(id);
31
29
  return row === undefined ? undefined : { turnSequence: row.turn_sequence, boundAt: row.bound_at };
32
30
  }
33
31
  function decodeTellRow(database, row) {
@@ -195,9 +193,7 @@ export function insertTellBindingFact(database, input) {
195
193
  const result = database
196
194
  .prepare("INSERT OR IGNORE INTO tell_bindings(tell_id, turn_sequence, bound_at) VALUES (?, ?, ?)")
197
195
  .run(input.tellId, input.turnSequence, input.boundAt);
198
- const row = database
199
- .prepare("SELECT turn_sequence FROM tell_bindings WHERE tell_id = ?")
200
- .get(input.tellId);
196
+ const row = database.prepare("SELECT turn_sequence FROM tell_bindings WHERE tell_id = ?").get(input.tellId);
201
197
  if (row === undefined || (result.changes === 0 && row.turn_sequence !== input.turnSequence)) {
202
198
  throw new Error(`tell ${input.tellId} is already bound to a different Turn`);
203
199
  }