@astrosheep/keiyaku 4.5.20 → 4.5.21

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 (154) hide show
  1. package/README.md +4 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +22 -8
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +237 -0
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +6 -7
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +2 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -3
  8. package/build/src/akuma/akuma-handle.d.ts +5 -1
  9. package/build/src/akuma/akuma-handle.js +4 -1
  10. package/build/src/akuma/akuma-instance.d.ts +7 -2
  11. package/build/src/akuma/akuma-instance.js +14 -7
  12. package/build/src/akuma/akuma-observe.d.ts +11 -0
  13. package/build/src/akuma/akuma-observe.js +22 -0
  14. package/build/src/akuma/akuma-product.d.ts +1 -0
  15. package/build/src/akuma/akuma-product.js +3 -0
  16. package/build/src/akuma/akuma.d.ts +1 -0
  17. package/build/src/akuma/body.d.ts +25 -5
  18. package/build/src/akuma/body.js +65 -8
  19. package/build/src/akuma/call-request.d.ts +2 -0
  20. package/build/src/akuma/call-request.js +2 -0
  21. package/build/src/akuma/fleet-execution.d.ts +28 -0
  22. package/build/src/akuma/fleet-execution.js +36 -4
  23. package/build/src/akuma/fleet-request.d.ts +10 -0
  24. package/build/src/akuma/fleet-request.js +11 -2
  25. package/build/src/akuma/heart/facts.d.ts +2 -0
  26. package/build/src/akuma/heart/index.d.ts +1 -0
  27. package/build/src/akuma/heart/index.js +4 -1
  28. package/build/src/akuma/heart/rows.d.ts +2 -0
  29. package/build/src/akuma/heart/rows.js +7 -5
  30. package/build/src/akuma/heart/schema.d.ts +1 -1
  31. package/build/src/akuma/heart/schema.js +3 -1
  32. package/build/src/akuma/heart/tells.js +6 -5
  33. package/build/src/akuma/heart/timeline.js +1 -1
  34. package/build/src/akuma/provider.d.ts +1 -1
  35. package/build/src/akuma/provider.js +1 -1
  36. package/build/src/akuma/providers/acp/core.js +3 -6
  37. package/build/src/akuma/providers/claude/index.js +9 -12
  38. package/build/src/akuma/providers/codex-app-server/index.js +4 -11
  39. package/build/src/akuma/providers/execution-environment.d.ts +6 -0
  40. package/build/src/akuma/providers/execution-environment.js +24 -0
  41. package/build/src/akuma/providers/opencode-sdk/index.js +30 -23
  42. package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
  43. package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
  44. package/build/src/akuma/providers/pi/events.js +7 -2
  45. package/build/src/akuma/providers/pi/index.js +3 -2
  46. package/build/src/akuma/request-rendezvous.js +8 -3
  47. package/build/src/akuma/turn-drive.d.ts +1 -0
  48. package/build/src/akuma/turn-drive.js +2 -0
  49. package/build/src/akuma-body.js +2 -0
  50. package/build/src/body/amend.js +6 -4
  51. package/build/src/body/decode.d.ts +3 -1
  52. package/build/src/body/decode.js +53 -17
  53. package/build/src/body/region.d.ts +9 -1
  54. package/build/src/body/region.js +87 -12
  55. package/build/src/body/render.js +1 -1
  56. package/build/src/body/verification.d.ts +3 -1
  57. package/build/src/body/verification.js +4 -1
  58. package/build/src/cli/accepted.js +19 -2
  59. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  60. package/build/src/cli/commands/akuma-invoke.js +133 -13
  61. package/build/src/cli/commands/akuma.js +11 -4
  62. package/build/src/cli/commands/contract-help.js +17 -4
  63. package/build/src/cli/commands/contract.js +6 -4
  64. package/build/src/cli/commands/status-set.js +15 -24
  65. package/build/src/cli/main.js +5 -0
  66. package/build/src/cli/parse.d.ts +2 -0
  67. package/build/src/cli/parse.js +5 -0
  68. package/build/src/cli/render/akuma-activity.d.ts +99 -2
  69. package/build/src/cli/render/akuma-activity.js +484 -40
  70. package/build/src/cli/render/akuma.d.ts +10 -0
  71. package/build/src/cli/render/akuma.js +28 -21
  72. package/build/src/cli/render/audit.js +1 -1
  73. package/build/src/cli/render/board.js +1 -1
  74. package/build/src/cli/render/catalog.js +16 -13
  75. package/build/src/cli/render/contract-history.js +39 -10
  76. package/build/src/cli/render/contract-observation.js +10 -6
  77. package/build/src/cli/render/contract.js +109 -106
  78. package/build/src/cli/render/execution-progress.d.ts +23 -1
  79. package/build/src/cli/render/execution-progress.js +151 -28
  80. package/build/src/cli/render/kanshi-akuma.js +27 -39
  81. package/build/src/cli/render/kanshi.js +33 -26
  82. package/build/src/cli/render/nuke.js +1 -1
  83. package/build/src/cli/render/receipt.d.ts +1 -1
  84. package/build/src/cli/render/receipt.js +5 -5
  85. package/build/src/cli/render/refusal.js +10 -7
  86. package/build/src/cli/render/region.js +1 -1
  87. package/build/src/cli/render/settings.js +12 -2
  88. package/build/src/cli/render/status-line.d.ts +30 -0
  89. package/build/src/cli/render/status-line.js +58 -0
  90. package/build/src/cli/render/status-set.js +1 -1
  91. package/build/src/cli/render/task.d.ts +2 -0
  92. package/build/src/cli/render/task.js +11 -7
  93. package/build/src/cli/render/terminal.d.ts +0 -1
  94. package/build/src/cli/render/terminal.js +47 -15
  95. package/build/src/cli/result.d.ts +1 -0
  96. package/build/src/cli/runtime.d.ts +2 -0
  97. package/build/src/cli/runtime.js +13 -7
  98. package/build/src/cli/usage.js +2 -2
  99. package/build/src/cli/version.d.ts +1 -0
  100. package/build/src/cli/version.js +25 -0
  101. package/build/src/git/workspace.d.ts +1 -1
  102. package/build/src/git/workspace.js +1 -1
  103. package/build/src/identity/mint.d.ts +13 -0
  104. package/build/src/identity/mint.js +26 -0
  105. package/build/src/identity/normalize.d.ts +4 -0
  106. package/build/src/identity/normalize.js +30 -0
  107. package/build/src/index.d.ts +2 -0
  108. package/build/src/index.js +1 -0
  109. package/build/src/kanshi/read.js +17 -4
  110. package/build/src/kanshi/report.d.ts +1 -0
  111. package/build/src/library/akuma-creation.d.ts +2 -0
  112. package/build/src/library/akuma-creation.js +33 -17
  113. package/build/src/library/bind.js +18 -16
  114. package/build/src/library/contract-bind.d.ts +3 -1
  115. package/build/src/library/contract-bind.js +5 -3
  116. package/build/src/library/contract-operations.d.ts +2 -2
  117. package/build/src/library/contract-operations.js +1 -1
  118. package/build/src/library/contract-types.d.ts +1 -0
  119. package/build/src/library/fleet.d.ts +4 -1
  120. package/build/src/library/fleet.js +32 -8
  121. package/build/src/plugin/akuma-signals.d.ts +6 -0
  122. package/build/src/plugin/akuma-signals.js +8 -0
  123. package/build/src/plugin/public.d.ts +4 -0
  124. package/build/src/plugin/runtime.js +4 -1
  125. package/build/src/protocol/amend.js +4 -3
  126. package/build/src/protocol/attempt.d.ts +2 -0
  127. package/build/src/protocol/bind.js +41 -14
  128. package/build/src/protocol/completion.d.ts +3 -0
  129. package/build/src/protocol/completion.js +7 -1
  130. package/build/src/protocol/deliver.js +165 -92
  131. package/build/src/protocol/execution-observation.d.ts +0 -4
  132. package/build/src/protocol/intent.d.ts +15 -20
  133. package/build/src/protocol/intent.js +29 -17
  134. package/build/src/protocol/operations.d.ts +2 -0
  135. package/build/src/protocol/placement.js +5 -1
  136. package/build/src/protocol/read/status.d.ts +1 -0
  137. package/build/src/protocol/read/status.js +10 -2
  138. package/build/src/protocol/reintegrate.d.ts +1 -1
  139. package/build/src/protocol/reintegrate.js +70 -43
  140. package/build/src/protocol/result-codec.js +9 -7
  141. package/build/src/protocol/review.js +53 -30
  142. package/build/src/protocol/run.d.ts +83 -36
  143. package/build/src/protocol/run.js +90 -75
  144. package/build/src/runtime/proc/windows-launch.exe +0 -0
  145. package/build/src/settings.js +5 -14
  146. package/build/src/task/document.d.ts +5 -1
  147. package/build/src/task/document.js +6 -1
  148. package/build/src/verification/execution.d.ts +0 -1
  149. package/build/src/verification/execution.js +0 -24
  150. package/build/src/verification/observation.d.ts +0 -4
  151. package/build/src/verification/observation.js +1 -2
  152. package/package.json +3 -1
  153. package/build/src/git/verification-environment.d.ts +0 -10
  154. package/build/src/git/verification-environment.js +0 -281
@@ -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
+ initiator?: string;
15
16
  initialSchemaJson?: string;
16
17
  refuseIfHeld?: boolean;
17
18
  completion?: Readonly<{
@@ -53,16 +54,35 @@ type BodyRuntime = Readonly<{
53
54
  world: WorldRoot;
54
55
  externalCommands: Readonly<Record<string, ErasedRequestCommand>>;
55
56
  }>;
56
- export declare function bodyProcessInput(launch: BodyLaunch, bodyModuleUrl?: string): Promise<{
57
+ type RuntimeEnvironment = Readonly<{
58
+ /** The current process executable, preferred only while it is a different live value. */
59
+ current?: string;
60
+ /** The present PATH used to resolve the recorded executable basename. */
61
+ path?: string | undefined;
62
+ exists?: (path: string) => boolean | Promise<boolean>;
63
+ }>;
64
+ /** One Body launch's runtime input: the runtime recorded for it and its resolution environment. */
65
+ export type BodyLaunchRuntime = Readonly<{
66
+ recorded?: string;
67
+ environment?: RuntimeEnvironment;
68
+ }>;
69
+ /**
70
+ * Resolve the runtime executable a Body spawn will use. The path recorded for
71
+ * this process's birth wins while it exists. Once it is gone, wake re-resolves
72
+ * the runtime before any spawn can fail: the current process executable while
73
+ * that is a different live value, then the recorded executable basename on the
74
+ * present PATH. An unresolvable runtime refuses with the stale record, the
75
+ * failed re-resolution, and the kill-and-call-fresh remedy.
76
+ */
77
+ export declare function resolveRuntimeExecutable(recorded?: string, environment?: RuntimeEnvironment): Promise<string>;
78
+ export declare function bodyProcessInput(launch: BodyLaunch, bodyModuleUrl?: string, runtime?: BodyLaunchRuntime): Promise<{
57
79
  argv: string[];
58
80
  cwd: string;
59
- env: {
60
- KEIYAKU_ACTOR_ID: import("./identity.js").AkuId;
61
- };
81
+ env: Record<string, string>;
62
82
  log: string;
63
83
  }>;
64
84
  export declare function handoffPendingTells(paths: AkumaPaths, spawn?: (launch: BodyLaunch) => Promise<OwnedProcess>): Promise<void>;
65
85
  export declare function driveAkumaBody(launch: BodyLaunch, adapter?: ProviderAdapter, runtimeInput?: Partial<BodyRuntime>): Promise<"held" | void>;
66
- export declare function spawnAkumaBody(launch: BodyLaunch): Promise<OwnedProcess>;
86
+ export declare function spawnAkumaBody(launch: BodyLaunch, runtime?: BodyLaunchRuntime): Promise<OwnedProcess>;
67
87
  export declare function runAkumaBody(launch: BodyLaunch, world: WorldRoot, externalCommands: Readonly<Record<string, ErasedRequestCommand>>): Promise<"held" | void>;
68
88
  export declare function wakeRecordedTell(paths: AkumaPaths, tellId: string, runtime?: TellWakeRuntime): Promise<TellResult>;
@@ -1,10 +1,12 @@
1
- import { appendFile } from "node:fs/promises";
1
+ import { appendFile, stat } from "node:fs/promises";
2
+ import { basename, delimiter, join } from "node:path";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { abortableDelay } from "./abort.js";
4
5
  import { BodySupervisor } from "./body-supervisor.js";
5
6
  import { driveTurn, turnRecipe } from "./turn-drive.js";
6
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
8
  import { worldRootForAkumaPaths } from "./identity.js";
9
+ import { akumaExecutionEnvironment } from "./providers/execution-environment.js";
8
10
  import { pluginRuntime } from "../plugin/runtime.js";
9
11
  import { World } from "../world.js";
10
12
  import { resolveProviderExecution } from "./providers/index.js";
@@ -144,19 +146,71 @@ const DEFAULT_RUNTIME = {
144
146
  externalCommands: {},
145
147
  spawnBody: spawnAkumaBody,
146
148
  };
147
- export async function bodyProcessInput(launch, bodyModuleUrl = import.meta.url) {
149
+ /**
150
+ * The runtime executable this process recorded at its birth. A package-manager
151
+ * upgrade can remove the file under a live process, so a wake re-resolves that
152
+ * record instead of surfacing a bare missing-file launch failure.
153
+ */
154
+ const RECORDED_RUNTIME = process.execPath;
155
+ async function runtimeFileExists(path) {
156
+ try {
157
+ return (await stat(path)).isFile();
158
+ }
159
+ catch {
160
+ return false;
161
+ }
162
+ }
163
+ async function runtimeCommandOnPath(command, path, exists) {
164
+ if (command.length === 0 || command.includes("/") || command.includes("\\"))
165
+ return null;
166
+ for (const directory of (path ?? "").split(delimiter)) {
167
+ if (directory.length === 0)
168
+ continue;
169
+ const candidate = join(directory, command);
170
+ if (await exists(candidate))
171
+ return candidate;
172
+ }
173
+ return null;
174
+ }
175
+ class StaleRuntimeError extends Error {
176
+ constructor(recorded, current, command) {
177
+ super(`recorded Body runtime ${recorded} no longer exists and re-resolution failed: neither this process executable ` +
178
+ `${current} nor the command "${command}" on PATH names an existing file; kill this Akuma and call a fresh one`);
179
+ this.name = "StaleRuntimeError";
180
+ }
181
+ }
182
+ /**
183
+ * Resolve the runtime executable a Body spawn will use. The path recorded for
184
+ * this process's birth wins while it exists. Once it is gone, wake re-resolves
185
+ * the runtime before any spawn can fail: the current process executable while
186
+ * that is a different live value, then the recorded executable basename on the
187
+ * present PATH. An unresolvable runtime refuses with the stale record, the
188
+ * failed re-resolution, and the kill-and-call-fresh remedy.
189
+ */
190
+ export async function resolveRuntimeExecutable(recorded = RECORDED_RUNTIME, environment = {}) {
191
+ const exists = environment.exists ?? runtimeFileExists;
192
+ if (await exists(recorded))
193
+ return recorded;
194
+ const current = environment.current ?? process.execPath;
195
+ const command = basename(recorded);
196
+ const reResolved = (current !== recorded && (await exists(current)) ? current : null) ??
197
+ (await runtimeCommandOnPath(command, environment.path ?? process.env.PATH, exists));
198
+ if (reResolved === null)
199
+ throw new StaleRuntimeError(recorded, current, command);
200
+ return reResolved;
201
+ }
202
+ export async function bodyProcessInput(launch, bodyModuleUrl = import.meta.url, runtime = {}) {
148
203
  const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
149
204
  const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
150
205
  if (actorId === undefined)
151
206
  throw new Error("Akuma wake has no born soul");
207
+ const executable = await resolveRuntimeExecutable(runtime.recorded, runtime.environment);
152
208
  const source = bodyModuleUrl.endsWith(".ts");
153
209
  const entry = fileURLToPath(new URL(source ? "../akuma-body.ts" : "../akuma-body.js", bodyModuleUrl));
154
210
  return {
155
- argv: source
156
- ? [process.execPath, "--import", import.meta.resolve("tsx"), entry, encoded]
157
- : [process.execPath, entry, encoded],
211
+ argv: source ? [executable, "--import", import.meta.resolve("tsx"), entry, encoded] : [executable, entry, encoded],
158
212
  cwd: await launchCwd(launch),
159
- env: { ...process.env, KEIYAKU_ACTOR_ID: actorId },
213
+ env: akumaExecutionEnvironment(process.env, { KEIYAKU_ACTOR_ID: actorId }),
160
214
  log: launch.paths.log,
161
215
  };
162
216
  }
@@ -237,6 +291,7 @@ function turnOutcomeEmitter(launch, soul) {
237
291
  return {
238
292
  async emit(turnSequence, outcome) {
239
293
  try {
294
+ const initiator = (await readTurn(launch.paths, turnSequence))?.initiator;
240
295
  plugins ??= pluginRuntime({
241
296
  world: await World.at(worldRootForAkumaPaths(launch.paths)),
242
297
  reportDiagnostic,
@@ -245,6 +300,7 @@ function turnOutcomeEmitter(launch, soul) {
245
300
  kind: "akuma.turn-outcome",
246
301
  akumaId: soul.id,
247
302
  turnSequence,
303
+ ...(initiator === undefined ? {} : { initiator }),
248
304
  outcome: outcome.outcome === "answered"
249
305
  ? { kind: "answered", text: outcome.answer }
250
306
  : { kind: "failed", reason: outcome.diagnostic },
@@ -312,6 +368,7 @@ async function runBodyTurns(input) {
312
368
  runtimeSpawn: runtime.spawnChild ?? spawnAkumaBody,
313
369
  body: initial ?? "",
314
370
  ...(initial === undefined ? {} : { call: initial }),
371
+ ...(launch.initiator === undefined ? {} : { initiator: launch.initiator }),
315
372
  launchTells,
316
373
  ...(schema === undefined ? {} : { schemaJson: schema }),
317
374
  world: runtime.world,
@@ -498,8 +555,8 @@ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
498
555
  await finished.notification;
499
556
  }
500
557
  }
501
- export async function spawnAkumaBody(launch) {
502
- return await spawnDetachedProcess(await bodyProcessInput(launch));
558
+ export async function spawnAkumaBody(launch, runtime = {}) {
559
+ return await spawnDetachedProcess(await bodyProcessInput(launch, import.meta.url, runtime));
503
560
  }
504
561
  export async function runAkumaBody(launch, world, externalCommands) {
505
562
  return await driveAkumaBody(launch, undefined, { world, externalCommands });
@@ -29,6 +29,7 @@ declare const akumaCallPayloadSchema: z.ZodObject<{
29
29
  world: z.ZodString;
30
30
  archetype: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
31
31
  body: z.ZodOptional<z.ZodString>;
32
+ initiator: z.ZodOptional<z.ZodString>;
32
33
  awaitAsleep: z.ZodOptional<z.ZodLiteral<true>>;
33
34
  cwd: z.ZodOptional<z.ZodString>;
34
35
  recipe: z.ZodObject<{
@@ -61,6 +62,7 @@ export type AkumaCallRequestChildLaunch = Readonly<{
61
62
  paths: AkumaPaths;
62
63
  seed: Omit<Soul, "createdAt">;
63
64
  initialBody?: string;
65
+ initiator?: string;
64
66
  }>;
65
67
  type AkumaCallRequestCapabilities = Readonly<{
66
68
  world: WorldRoot;
@@ -67,6 +67,7 @@ const akumaCallPayloadSchema = z
67
67
  world: absolutePathSchema,
68
68
  archetype: archetypeSchema,
69
69
  body: z.string().optional(),
70
+ initiator: z.string().min(1).optional(),
70
71
  awaitAsleep: z.literal(true).optional(),
71
72
  cwd: absolutePathSchema.optional(),
72
73
  recipe: akumaCallRecipeSchema,
@@ -119,6 +120,7 @@ async function executeAkumaCall(request, facts, capabilities) {
119
120
  origin: { kind: "request", parent: parent.id, requestId: facts.id },
120
121
  },
121
122
  ...(request.body === undefined ? {} : { initialBody: request.body }),
123
+ ...(request.initiator === undefined ? {} : { initiator: request.initiator }),
122
124
  });
123
125
  },
124
126
  });
@@ -1,12 +1,39 @@
1
1
  import { type AkumaStatus } from "./akuma.js";
2
+ import { type DispatchAssociation } from "./dispatch-association.js";
3
+ import type { AkumaAlias } from "../identity/selector.js";
2
4
  import type { WorldRoot } from "../world.js";
3
5
  import { type AkumaKillResult, type AkumaTellResult, type AkumaWaitResult } from "./fleet-observation.js";
6
+ export type WaitIdentityFacts = Readonly<{
7
+ /** The alias currently addressing this Akuma in its world, when one is bound to it. */
8
+ alias?: AkumaAlias;
9
+ /** This Akuma's Dispatch association, read from the observing repository. */
10
+ contract: DispatchAssociation;
11
+ }>;
12
+ /** One observed Akuma as a live wait viewer sees it: its status plus its identity facts. */
13
+ export type WaitObservedAkuma = Readonly<{
14
+ status: AkumaStatus;
15
+ }> & WaitIdentityFacts;
16
+ /** One selected Akuma's frozen identity, resolved before the first observation round. */
17
+ export type WaitSelectedAkuma = Readonly<{
18
+ id: AkumaStatus["id"];
19
+ }> & WaitIdentityFacts;
20
+ /** A live wait viewer: the frozen selected set, then every observation round. */
21
+ export type WaitObserver = Readonly<{
22
+ selected?: (selected: readonly WaitSelectedAkuma[]) => void;
23
+ observe?: (observed: readonly WaitObservedAkuma[]) => void;
24
+ }>;
4
25
  export type WaitExecutionInput = Readonly<{
5
26
  path: WorldRoot;
6
27
  ids: readonly AkumaStatus["id"][];
7
28
  completion: "any" | "all";
8
29
  timeoutMs?: number;
9
30
  signal?: AbortSignal;
31
+ /** Resolves one Akuma's identity facts; consulted once per Akuma a viewer first sees. */
32
+ identity?: (id: AkumaStatus["id"]) => Promise<WaitIdentityFacts>;
33
+ /** Reports the frozen selected set before the first round, so a viewer can fix its layout. */
34
+ onSelected?: (selected: readonly WaitSelectedAkuma[]) => void;
35
+ /** Reports every observation round to a live viewer; absent keeps the cheap completion probe. */
36
+ observe?: (observed: readonly WaitObservedAkuma[]) => void;
10
37
  }>;
11
38
  export declare function executeWaitAkuma(input: WaitExecutionInput): Promise<AkumaWaitResult>;
12
39
  export type TellExecutionInput = Readonly<{
@@ -15,6 +42,7 @@ export type TellExecutionInput = Readonly<{
15
42
  body: string;
16
43
  tellId?: string;
17
44
  recordedAt?: string;
45
+ initiator?: string;
18
46
  signal?: AbortSignal;
19
47
  }>;
20
48
  export declare function executeTellAkuma(input: TellExecutionInput): Promise<AkumaTellResult>;
@@ -89,10 +89,44 @@ function delay(milliseconds, signal) {
89
89
  }
90
90
  export async function executeWaitAkuma(input) {
91
91
  const deadline = input.timeoutMs === undefined ? undefined : performance.now() + input.timeoutMs;
92
+ // Identity facts are read once per observed Akuma, not once per 100ms round.
93
+ const facts = new Map();
94
+ if (input.onSelected !== undefined) {
95
+ const selected = [];
96
+ for (const id of input.ids) {
97
+ input.signal?.throwIfAborted();
98
+ const known = input.identity === undefined ? undefined : await input.identity(id);
99
+ const resolved = known ?? { contract: NO_DISPATCH_ASSOCIATION };
100
+ facts.set(id, resolved);
101
+ selected.push({ id, ...resolved });
102
+ }
103
+ input.onSelected(selected);
104
+ }
105
+ const observeRound = async (statuses) => {
106
+ if (input.observe === undefined)
107
+ return;
108
+ const observed = [];
109
+ for (const status of statuses) {
110
+ input.signal?.throwIfAborted();
111
+ let known = facts.get(status.id);
112
+ if (known === undefined && input.identity !== undefined) {
113
+ known = await input.identity(status.id);
114
+ facts.set(status.id, known);
115
+ }
116
+ if (known === undefined) {
117
+ observed.push({ status, contract: NO_DISPATCH_ASSOCIATION });
118
+ continue;
119
+ }
120
+ observed.push({ status, ...known });
121
+ }
122
+ input.observe(observed);
123
+ };
92
124
  for (;;) {
93
- if ((deadline !== undefined && performance.now() >= deadline) || (await probeWaitRound(input))) {
125
+ const expired = deadline !== undefined && performance.now() >= deadline;
126
+ if (expired || input.observe !== undefined || (await probeWaitRound(input))) {
94
127
  const round = await observeWaitRound(input.path, input.ids, input.signal);
95
128
  input.signal?.throwIfAborted();
129
+ await observeRound(round.statuses);
96
130
  // The probe is not a completion receipt. Judge the actual returned values.
97
131
  if (roundComplete(round, input.completion) || (deadline !== undefined && performance.now() >= deadline)) {
98
132
  return fleetResultSchemas.wait.parse({
@@ -108,9 +142,7 @@ export async function executeWaitAkuma(input) {
108
142
  export async function executeTellAkuma(input) {
109
143
  input.signal?.throwIfAborted();
110
144
  const handle = source(input.path).selectHandle({ id: input.id });
111
- const tell = input.tellId === undefined
112
- ? await handle.tell(input.body)
113
- : await handle.tell(input.body, input.tellId, input.recordedAt);
145
+ const tell = await handle.tell(input.body, input.tellId, input.recordedAt, undefined, input.initiator === undefined ? {} : { initiator: input.initiator });
114
146
  input.signal?.throwIfAborted();
115
147
  return fleetResultSchemas.tell.parse({
116
148
  akuma: input.id,
@@ -24,29 +24,35 @@ declare const waitRequestSchema: z.ZodPipe<z.ZodObject<{
24
24
  declare const tellRequestSchema: z.ZodPipe<z.ZodObject<{
25
25
  target: z.ZodPipe<z.ZodString, z.ZodTransform<import("./identity.js").AkuId, string>>;
26
26
  body: z.ZodString;
27
+ initiator: z.ZodOptional<z.ZodString>;
27
28
  }, z.core.$strict>, z.ZodTransform<{
28
29
  target: import("./identity.js").AkuId;
29
30
  body: string;
31
+ initiator?: string | undefined;
30
32
  action: "akuma.tell";
31
33
  }, {
32
34
  target: import("./identity.js").AkuId;
33
35
  body: string;
36
+ initiator?: string | undefined;
34
37
  }>>;
35
38
  declare const tellAnswerRequestSchema: z.ZodPipe<z.ZodObject<{
36
39
  target: z.ZodPipe<z.ZodString, z.ZodTransform<import("./identity.js").AkuId, string>>;
37
40
  body: z.ZodString;
38
41
  schemaJson: z.ZodString;
42
+ initiator: z.ZodOptional<z.ZodString>;
39
43
  interrupt: z.ZodOptional<z.ZodBoolean>;
40
44
  }, z.core.$strict>, z.ZodTransform<{
41
45
  target: import("./identity.js").AkuId;
42
46
  body: string;
43
47
  schemaJson: string;
48
+ initiator?: string | undefined;
44
49
  interrupt?: boolean | undefined;
45
50
  action: "akuma.tell-answer";
46
51
  }, {
47
52
  target: import("./identity.js").AkuId;
48
53
  body: string;
49
54
  schemaJson: string;
55
+ initiator?: string | undefined;
50
56
  interrupt?: boolean | undefined;
51
57
  }>>;
52
58
  declare const killRequestSchema: z.ZodPipe<z.ZodObject<{
@@ -102,12 +108,14 @@ export type FleetRequestPort = Readonly<{
102
108
  body: string;
103
109
  tellId: string;
104
110
  recordedAt: string;
111
+ initiator?: string;
105
112
  signal: AbortSignal;
106
113
  }>): Promise<AkumaTellResult>;
107
114
  tellAnswer?(input: Readonly<{
108
115
  target: AkumaStatus["id"];
109
116
  body: string;
110
117
  schemaJson: string;
118
+ initiator?: string;
111
119
  interrupt?: boolean;
112
120
  signal: AbortSignal;
113
121
  }>): Promise<unknown>;
@@ -131,6 +139,7 @@ export declare function requestForwardedFleetTellAnswer(input: Readonly<{
131
139
  target: AkumaStatus["id"];
132
140
  body: string;
133
141
  schema: Schema<unknown>;
142
+ initiator?: string;
134
143
  interrupt?: boolean;
135
144
  signal?: AbortSignal;
136
145
  }>): Promise<unknown>;
@@ -145,6 +154,7 @@ export declare function requestForwardedFleetTell(input: Readonly<{
145
154
  directory: string;
146
155
  target: AkumaStatus["id"];
147
156
  body: string;
157
+ initiator?: string;
148
158
  signal?: AbortSignal;
149
159
  }>): Promise<AkumaTellResult>;
150
160
  export declare function requestForwardedFleetKill(input: Readonly<{
@@ -26,7 +26,7 @@ const waitRequestSchema = z
26
26
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
27
27
  }));
28
28
  const tellRequestSchema = z
29
- .object({ target: akumaIdSchema, body: z.string() })
29
+ .object({ target: akumaIdSchema, body: z.string(), initiator: nonblankTextSchema.optional() })
30
30
  .strict()
31
31
  .transform((request) => ({ action: "akuma.tell", ...request }));
32
32
  const tellAnswerRequestSchema = z
@@ -34,6 +34,7 @@ const tellAnswerRequestSchema = z
34
34
  target: akumaIdSchema,
35
35
  body: z.string(),
36
36
  schemaJson: nonblankTextSchema,
37
+ initiator: nonblankTextSchema.optional(),
37
38
  interrupt: z.boolean().optional(),
38
39
  })
39
40
  .strict()
@@ -124,6 +125,7 @@ export function fleetRequestCommand(action, port) {
124
125
  target: request.target,
125
126
  body: request.body,
126
127
  tellId: facts.id,
128
+ ...(request.initiator === undefined ? {} : { initiator: request.initiator }),
127
129
  recordedAt: facts.admittedAt,
128
130
  signal: facts.signal,
129
131
  }),
@@ -138,6 +140,7 @@ export function fleetRequestCommand(action, port) {
138
140
  target: request.target,
139
141
  body: request.body,
140
142
  schemaJson: request.schemaJson,
143
+ ...(request.initiator === undefined ? {} : { initiator: request.initiator }),
141
144
  ...(request.interrupt === undefined ? {} : { interrupt: request.interrupt }),
142
145
  signal: facts.signal,
143
146
  }),
@@ -171,6 +174,7 @@ export async function requestForwardedFleetTellAnswer(input) {
171
174
  target: input.target,
172
175
  body: input.body,
173
176
  schemaJson: schemaJsonText(input.schema),
177
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
174
178
  ...(input.interrupt === undefined ? {} : { interrupt: input.interrupt }),
175
179
  },
176
180
  ...(input.signal === undefined ? {} : { signal: input.signal }),
@@ -214,7 +218,12 @@ export async function requestForwardedFleetTell(input) {
214
218
  const response = await requestBodyCommand({
215
219
  directory: input.directory,
216
220
  command: fleetRequestProtocol("akuma.tell"),
217
- value: { action: "akuma.tell", target: input.target, body: input.body },
221
+ value: {
222
+ action: "akuma.tell",
223
+ target: input.target,
224
+ body: input.body,
225
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
226
+ },
218
227
  ...(input.signal === undefined ? {} : { signal: input.signal }),
219
228
  });
220
229
  return forwardedFleetCommandResult(response, "akuma.tell");
@@ -77,6 +77,7 @@ export type TurnStartFact = Readonly<{
77
77
  sequence: number;
78
78
  bodySequence: number;
79
79
  startedAt: string;
80
+ initiator?: string;
80
81
  schemaJson?: string;
81
82
  }>;
82
83
  export type TurnEndFact = Readonly<{
@@ -111,6 +112,7 @@ export type TellFact = Readonly<{
111
112
  sequence: number;
112
113
  id: string;
113
114
  body: string;
115
+ initiator?: string;
114
116
  schemaJson?: string;
115
117
  recordedAt: string;
116
118
  state: "pending" | "told";
@@ -77,6 +77,7 @@ export declare function beginTurn(paths: AkumaPaths, input: Readonly<{
77
77
  startedAt: string;
78
78
  call?: string;
79
79
  schemaJson?: string;
80
+ initiator?: string;
80
81
  }>): Promise<TurnStartFact>;
81
82
  export declare function bindTellsToTurn(paths: AkumaPaths, input: Readonly<{
82
83
  turnSequence: number;
@@ -74,7 +74,10 @@ export async function readLifeSnapshot(paths) {
74
74
  }
75
75
  }
76
76
  function sameTellInput(existing, tell) {
77
- return (existing.body === tell.body && existing.recordedAt === tell.recordedAt && existing.schemaJson === tell.schemaJson);
77
+ return (existing.body === tell.body &&
78
+ existing.recordedAt === tell.recordedAt &&
79
+ existing.schemaJson === tell.schemaJson &&
80
+ existing.initiator === tell.initiator);
78
81
  }
79
82
  export async function recordTell(paths, tell, options = {}) {
80
83
  return await withHeartTransaction(paths, (heart) => {
@@ -31,6 +31,7 @@ export type TurnRow = Readonly<{
31
31
  answer: string | null;
32
32
  answer_json: string | null;
33
33
  schema_json: string | null;
34
+ initiator: string | null;
34
35
  diagnostic: string | null;
35
36
  completed_at: string | null;
36
37
  }>;
@@ -107,6 +108,7 @@ export declare function insertTurnStartFact(database: DatabaseSync, input: Reado
107
108
  startedAt: string;
108
109
  call?: string;
109
110
  schemaJson?: string;
111
+ initiator?: string;
110
112
  }>): TurnStartFact;
111
113
  export declare function insertTurnEndFact(database: DatabaseSync, input: Omit<TurnEndFact, "sequence">): TurnEndFact;
112
114
  export declare function finishBodyFact(database: DatabaseSync, input: Readonly<{
@@ -72,6 +72,7 @@ export function decodeTurnRow(row) {
72
72
  bodySequence: row.body_sequence,
73
73
  startedAt: row.started_at,
74
74
  ...(row.schema_json === null || row.schema_json === undefined ? {} : { schemaJson: row.schema_json }),
75
+ ...(row.initiator == null ? {} : { initiator: row.initiator }),
75
76
  };
76
77
  if (row.outcome === null)
77
78
  return start;
@@ -192,8 +193,8 @@ export function markBodyHung(database, input) {
192
193
  export function insertTurnStartFact(database, input) {
193
194
  const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('turn-start')").run().lastInsertRowid);
194
195
  database
195
- .prepare("INSERT INTO turns(sequence, body_sequence, started_at, schema_json) VALUES (?, ?, ?, ?)")
196
- .run(sequence, input.bodySequence, input.startedAt, input.schemaJson ?? null);
196
+ .prepare("INSERT INTO turns(sequence, body_sequence, started_at, schema_json, initiator) VALUES (?, ?, ?, ?, ?)")
197
+ .run(sequence, input.bodySequence, input.startedAt, input.schemaJson ?? null, input.initiator ?? null);
197
198
  if (input.call !== undefined) {
198
199
  const callSequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('call')").run().lastInsertRowid);
199
200
  database
@@ -205,6 +206,7 @@ export function insertTurnStartFact(database, input) {
205
206
  sequence,
206
207
  bodySequence: input.bodySequence,
207
208
  startedAt: input.startedAt,
209
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
208
210
  ...(input.schemaJson === undefined ? {} : { schemaJson: input.schemaJson }),
209
211
  };
210
212
  }
@@ -286,21 +288,21 @@ export function killFactForBody(database, bodySequence) {
286
288
  export function lastAnsweredTurnFact(database) {
287
289
  const row = database
288
290
  .prepare(`SELECT sequence, body_sequence, started_at, end_sequence, outcome, history_id,
289
- session_json, answer, answer_json, schema_json, diagnostic, completed_at FROM turns WHERE outcome = 'answered' ORDER BY end_sequence DESC LIMIT 1`)
291
+ session_json, answer, answer_json, schema_json, initiator, diagnostic, completed_at FROM turns WHERE outcome = 'answered' ORDER BY end_sequence DESC LIMIT 1`)
290
292
  .get();
291
293
  return row === undefined ? null : decodeTurnRow(row);
292
294
  }
293
295
  export function answeredTurnFact(database, turnSequence) {
294
296
  const row = database
295
297
  .prepare(`SELECT sequence, body_sequence, started_at, end_sequence, outcome, history_id,
296
- session_json, answer, answer_json, schema_json, diagnostic, completed_at FROM turns WHERE outcome = 'answered' AND sequence = ?`)
298
+ session_json, answer, answer_json, schema_json, initiator, diagnostic, completed_at FROM turns WHERE outcome = 'answered' AND sequence = ?`)
297
299
  .get(turnSequence);
298
300
  return row === undefined ? null : decodeTurnRow(row);
299
301
  }
300
302
  export function turnFact(database, turnSequence) {
301
303
  const row = database
302
304
  .prepare(`SELECT sequence, body_sequence, started_at, end_sequence, outcome, history_id,
303
- session_json, answer, answer_json, schema_json, diagnostic, completed_at FROM turns WHERE sequence = ?`)
305
+ session_json, answer, answer_json, schema_json, initiator, diagnostic, completed_at FROM turns WHERE sequence = ?`)
304
306
  .get(turnSequence);
305
307
  return row === undefined ? null : decodeTurnRow(row);
306
308
  }
@@ -2,5 +2,5 @@ import type { DatabaseSync } from "node:sqlite";
2
2
  export declare function assertHeartSchemaVersion(database: DatabaseSync): void;
3
3
  export declare function assertLeashSchemaVersion(database: DatabaseSync): void;
4
4
  export declare function heartSchemaIsCurrent(database: DatabaseSync): boolean;
5
- export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 28)\n ) STRICT;\n INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, 28);\n CREATE TABLE IF NOT EXISTS soul (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n soul_json TEXT NOT NULL CHECK (json_valid(soul_json))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS bodies (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n leash_taken_at TEXT NOT NULL,\n hung_diagnostic TEXT,\n hung_at TEXT,\n end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),\n ended_at TEXT,\n CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL)\n OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS sessions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),\n cwd TEXT NOT NULL,\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n admitted_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS turns (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),\n started_at TEXT NOT NULL,\n end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,\n outcome TEXT CHECK (outcome IN ('answered', 'failed', 'invalid-output')),\n history_id TEXT UNIQUE,\n session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),\n answer TEXT,\n answer_json TEXT CHECK (answer_json IS NULL OR json_valid(answer_json)),\n schema_json TEXT CHECK (schema_json IS NULL OR json_valid(schema_json)),\n diagnostic TEXT,\n completed_at TEXT,\n CHECK (\n (outcome = 'answered' AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL\n AND ((schema_json IS NULL AND answer_json IS NULL) OR (schema_json IS NOT NULL AND answer_json IS NOT NULL)))\n OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL\n AND answer_json IS NULL AND diagnostic IS NOT NULL)\n OR (outcome = 'invalid-output' AND answer IS NOT NULL AND answer_json IS NULL\n AND diagnostic IS NOT NULL AND schema_json IS NOT NULL)\n OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL\n AND answer IS NULL AND answer_json IS NULL AND diagnostic IS NULL AND completed_at IS NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS timeline (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity_retention (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n checked_sequence INTEGER NOT NULL\n ) STRICT;\n INSERT OR IGNORE INTO activity_retention(singleton, checked_sequence) VALUES (1, 0);\n CREATE TABLE IF NOT EXISTS calls (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n event_json TEXT NOT NULL CHECK (json_valid(event_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tells (\n id TEXT PRIMARY KEY,\n sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n schema_json TEXT CHECK (schema_json IS NULL OR json_valid(schema_json)),\n recorded_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_bindings (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n bound_at TEXT NOT NULL,\n UNIQUE (tell_id, turn_sequence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n route TEXT NOT NULL CHECK (route IN ('launch', 'live')),\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT NOT NULL,\n receipt TEXT CHECK (receipt IN ('unavailable', 'required')),\n delivered_at TEXT NOT NULL,\n CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),\n UNIQUE (tell_id, turn_sequence, fence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_receipts (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),\n tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT,\n kind TEXT NOT NULL,\n received_at TEXT NOT NULL,\n CHECK (\n (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)\n OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)\n )\n ) STRICT;\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact\n ON tell_receipts(tell_id, kind);\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence\n ON tell_receipts(turn_sequence, fence, kind);\n CREATE TABLE IF NOT EXISTS tell_dispositions (\n body_sequence INTEGER PRIMARY KEY REFERENCES bodies(sequence),\n decided_at TEXT NOT NULL,\n resolved_at TEXT\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_disposition_members (\n body_sequence INTEGER NOT NULL REFERENCES tell_dispositions(body_sequence) ON DELETE CASCADE,\n tell_id TEXT NOT NULL REFERENCES tells(id),\n PRIMARY KEY (body_sequence, tell_id)\n ) STRICT;\n CREATE INDEX IF NOT EXISTS calls_by_turn ON calls(turn_sequence);\n CREATE INDEX IF NOT EXISTS activity_by_turn ON activity(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_bindings_by_turn ON tell_bindings(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_deliveries_by_turn ON tell_deliveries(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_disposition_members_by_tell ON tell_disposition_members(tell_id);\n CREATE TABLE IF NOT EXISTS requests (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n requester TEXT NOT NULL,\n action TEXT NOT NULL,\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n admitted_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'begun', 'served', 'refused', 'voided', 'unproven')),\n child TEXT,\n service_json TEXT CHECK (service_json IS NULL OR json_valid(service_json)),\n diagnostic TEXT,\n evidence TEXT,\n CHECK (\n (state = 'admitted' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'reserved' AND child IS NOT NULL AND service_json IS NULL\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'begun' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'served'\n AND ((child IS NOT NULL AND service_json IS NULL)\n OR (child IS NULL AND service_json IS NOT NULL))\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'refused' AND child IS NULL AND service_json IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)\n OR (state = 'voided' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n OR (state = 'unproven' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS control (\n kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),\n value_json TEXT NOT NULL CHECK (json_valid(value_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS kills (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),\n evidence TEXT NOT NULL CHECK (evidence = 'killed'),\n at TEXT NOT NULL\n ) STRICT;\n";
5
+ export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 29)\n ) STRICT;\n INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, 29);\n CREATE TABLE IF NOT EXISTS soul (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n soul_json TEXT NOT NULL CHECK (json_valid(soul_json))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS bodies (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n leash_taken_at TEXT NOT NULL,\n hung_diagnostic TEXT,\n hung_at TEXT,\n end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),\n ended_at TEXT,\n CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL)\n OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS sessions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),\n cwd TEXT NOT NULL,\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n admitted_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS turns (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),\n started_at TEXT NOT NULL,\n initiator TEXT,\n end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,\n outcome TEXT CHECK (outcome IN ('answered', 'failed', 'invalid-output')),\n history_id TEXT UNIQUE,\n session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),\n answer TEXT,\n answer_json TEXT CHECK (answer_json IS NULL OR json_valid(answer_json)),\n schema_json TEXT CHECK (schema_json IS NULL OR json_valid(schema_json)),\n diagnostic TEXT,\n completed_at TEXT,\n CHECK (\n (outcome = 'answered' AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL\n AND ((schema_json IS NULL AND answer_json IS NULL) OR (schema_json IS NOT NULL AND answer_json IS NOT NULL)))\n OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL\n AND answer_json IS NULL AND diagnostic IS NOT NULL)\n OR (outcome = 'invalid-output' AND answer IS NOT NULL AND answer_json IS NULL\n AND diagnostic IS NOT NULL AND schema_json IS NOT NULL)\n OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL\n AND answer IS NULL AND answer_json IS NULL AND diagnostic IS NULL AND completed_at IS NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS timeline (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity_retention (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n checked_sequence INTEGER NOT NULL\n ) STRICT;\n INSERT OR IGNORE INTO activity_retention(singleton, checked_sequence) VALUES (1, 0);\n CREATE TABLE IF NOT EXISTS calls (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n event_json TEXT NOT NULL CHECK (json_valid(event_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tells (\n id TEXT PRIMARY KEY,\n sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n initiator TEXT,\n schema_json TEXT CHECK (schema_json IS NULL OR json_valid(schema_json)),\n recorded_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_bindings (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n bound_at TEXT NOT NULL,\n UNIQUE (tell_id, turn_sequence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n route TEXT NOT NULL CHECK (route IN ('launch', 'live')),\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT NOT NULL,\n receipt TEXT CHECK (receipt IN ('unavailable', 'required')),\n delivered_at TEXT NOT NULL,\n CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),\n UNIQUE (tell_id, turn_sequence, fence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_receipts (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),\n tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT,\n kind TEXT NOT NULL,\n received_at TEXT NOT NULL,\n CHECK (\n (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)\n OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)\n )\n ) STRICT;\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact\n ON tell_receipts(tell_id, kind);\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence\n ON tell_receipts(turn_sequence, fence, kind);\n CREATE TABLE IF NOT EXISTS tell_dispositions (\n body_sequence INTEGER PRIMARY KEY REFERENCES bodies(sequence),\n decided_at TEXT NOT NULL,\n resolved_at TEXT\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_disposition_members (\n body_sequence INTEGER NOT NULL REFERENCES tell_dispositions(body_sequence) ON DELETE CASCADE,\n tell_id TEXT NOT NULL REFERENCES tells(id),\n PRIMARY KEY (body_sequence, tell_id)\n ) STRICT;\n CREATE INDEX IF NOT EXISTS calls_by_turn ON calls(turn_sequence);\n CREATE INDEX IF NOT EXISTS activity_by_turn ON activity(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_bindings_by_turn ON tell_bindings(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_deliveries_by_turn ON tell_deliveries(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_disposition_members_by_tell ON tell_disposition_members(tell_id);\n CREATE TABLE IF NOT EXISTS requests (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n requester TEXT NOT NULL,\n action TEXT NOT NULL,\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n admitted_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'begun', 'served', 'refused', 'voided', 'unproven')),\n child TEXT,\n service_json TEXT CHECK (service_json IS NULL OR json_valid(service_json)),\n diagnostic TEXT,\n evidence TEXT,\n CHECK (\n (state = 'admitted' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'reserved' AND child IS NOT NULL AND service_json IS NULL\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'begun' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'served'\n AND ((child IS NOT NULL AND service_json IS NULL)\n OR (child IS NULL AND service_json IS NOT NULL))\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'refused' AND child IS NULL AND service_json IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)\n OR (state = 'voided' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n OR (state = 'unproven' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS control (\n kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),\n value_json TEXT NOT NULL CHECK (json_valid(value_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS kills (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),\n evidence TEXT NOT NULL CHECK (evidence = 'killed'),\n at TEXT NOT NULL\n ) STRICT;\n";
6
6
  export declare const LEASH_SCHEMA = "\n PRAGMA journal_mode=DELETE;\n CREATE TABLE IF NOT EXISTS leash_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 4)\n ) STRICT;\n INSERT OR IGNORE INTO leash_schema(singleton, version) VALUES (1, 4);\n CREATE TABLE IF NOT EXISTS seal (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n evidence TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n";
@@ -1,4 +1,4 @@
1
- const HEART_SCHEMA_VERSION = 28;
1
+ const HEART_SCHEMA_VERSION = 29;
2
2
  const LEASH_SCHEMA_VERSION = 4;
3
3
  function assertSchemaVersion(database, table, expected) {
4
4
  const row = database.prepare(`SELECT version FROM ${table} WHERE singleton = 1`).get();
@@ -52,6 +52,7 @@ export const HEART_SCHEMA = `
52
52
  sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,
53
53
  body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),
54
54
  started_at TEXT NOT NULL,
55
+ initiator TEXT,
55
56
  end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,
56
57
  outcome TEXT CHECK (outcome IN ('answered', 'failed', 'invalid-output')),
57
58
  history_id TEXT UNIQUE,
@@ -97,6 +98,7 @@ export const HEART_SCHEMA = `
97
98
  id TEXT PRIMARY KEY,
98
99
  sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,
99
100
  body TEXT NOT NULL,
101
+ initiator TEXT,
100
102
  schema_json TEXT CHECK (schema_json IS NULL OR json_valid(schema_json)),
101
103
  recorded_at TEXT NOT NULL
102
104
  ) STRICT;
@@ -52,6 +52,7 @@ function decodeTellRows(database, rows) {
52
52
  sequence: row.sequence,
53
53
  id: row.id,
54
54
  body: row.body,
55
+ ...(row.initiator === null ? {} : { initiator: row.initiator }),
55
56
  ...(row.schema_json === null ? {} : { schemaJson: row.schema_json }),
56
57
  state: row.state,
57
58
  recordedAt: row.recorded_at,
@@ -63,7 +64,7 @@ export function tellFactsAtSequences(database, sequences) {
63
64
  if (sequences.length === 0)
64
65
  return [];
65
66
  const rows = database
66
- .prepare(`SELECT sequence, id, body, schema_json, recorded_at, ${tellStateSql} AS state
67
+ .prepare(`SELECT sequence, id, body, initiator, schema_json, recorded_at, ${tellStateSql} AS state
67
68
  FROM tells WHERE sequence IN (SELECT value FROM json_each(?)) ORDER BY sequence`)
68
69
  .all(JSON.stringify(sequences));
69
70
  return decodeTellRows(database, rows);
@@ -74,13 +75,13 @@ export function hasPendingTell(database) {
74
75
  export function insertTellFact(database, tell) {
75
76
  const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('tell')").run().lastInsertRowid);
76
77
  database
77
- .prepare("INSERT INTO tells(id, sequence, body, schema_json, recorded_at) VALUES (?, ?, ?, ?, ?)")
78
- .run(tell.id, sequence, tell.body, tell.schemaJson ?? null, tell.recordedAt);
78
+ .prepare("INSERT INTO tells(id, sequence, body, initiator, schema_json, recorded_at) VALUES (?, ?, ?, ?, ?, ?)")
79
+ .run(tell.id, sequence, tell.body, tell.initiator ?? null, tell.schemaJson ?? null, tell.recordedAt);
79
80
  return sequence;
80
81
  }
81
82
  export function tellFact(database, id) {
82
83
  const row = database
83
- .prepare(`SELECT sequence, id, body, schema_json, recorded_at, ${tellStateSql} AS state
84
+ .prepare(`SELECT sequence, id, body, initiator, schema_json, recorded_at, ${tellStateSql} AS state
84
85
  FROM tells WHERE id = ?`)
85
86
  .get(id);
86
87
  return row === undefined ? null : decodeTellRows(database, [row])[0];
@@ -119,7 +120,7 @@ export function tellIdsForFence(database, turnSequence, fence) {
119
120
  }
120
121
  export function pendingTellFacts(database) {
121
122
  const rows = database
122
- .prepare(`SELECT sequence, id, body, schema_json, recorded_at, ${tellStateSql} AS state FROM tells
123
+ .prepare(`SELECT sequence, id, body, initiator, schema_json, recorded_at, ${tellStateSql} AS state FROM tells
123
124
  WHERE ${tellStateSql} = 'pending' ORDER BY sequence`)
124
125
  .all();
125
126
  return decodeTellRows(database, rows);