@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.
- package/README.md +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +22 -8
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +237 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +6 -7
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +2 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -3
- package/build/src/akuma/akuma-handle.d.ts +5 -1
- package/build/src/akuma/akuma-handle.js +4 -1
- package/build/src/akuma/akuma-instance.d.ts +7 -2
- package/build/src/akuma/akuma-instance.js +14 -7
- package/build/src/akuma/akuma-observe.d.ts +11 -0
- package/build/src/akuma/akuma-observe.js +22 -0
- package/build/src/akuma/akuma-product.d.ts +1 -0
- package/build/src/akuma/akuma-product.js +3 -0
- package/build/src/akuma/akuma.d.ts +1 -0
- package/build/src/akuma/body.d.ts +25 -5
- package/build/src/akuma/body.js +65 -8
- package/build/src/akuma/call-request.d.ts +2 -0
- package/build/src/akuma/call-request.js +2 -0
- package/build/src/akuma/fleet-execution.d.ts +28 -0
- package/build/src/akuma/fleet-execution.js +36 -4
- package/build/src/akuma/fleet-request.d.ts +10 -0
- package/build/src/akuma/fleet-request.js +11 -2
- package/build/src/akuma/heart/facts.d.ts +2 -0
- package/build/src/akuma/heart/index.d.ts +1 -0
- package/build/src/akuma/heart/index.js +4 -1
- package/build/src/akuma/heart/rows.d.ts +2 -0
- package/build/src/akuma/heart/rows.js +7 -5
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +3 -1
- package/build/src/akuma/heart/tells.js +6 -5
- package/build/src/akuma/heart/timeline.js +1 -1
- package/build/src/akuma/provider.d.ts +1 -1
- package/build/src/akuma/provider.js +1 -1
- package/build/src/akuma/providers/acp/core.js +3 -6
- package/build/src/akuma/providers/claude/index.js +9 -12
- package/build/src/akuma/providers/codex-app-server/index.js +4 -11
- package/build/src/akuma/providers/execution-environment.d.ts +6 -0
- package/build/src/akuma/providers/execution-environment.js +24 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +30 -23
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
- package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
- package/build/src/akuma/providers/pi/events.js +7 -2
- package/build/src/akuma/providers/pi/index.js +3 -2
- package/build/src/akuma/request-rendezvous.js +8 -3
- package/build/src/akuma/turn-drive.d.ts +1 -0
- package/build/src/akuma/turn-drive.js +2 -0
- package/build/src/akuma-body.js +2 -0
- package/build/src/body/amend.js +6 -4
- package/build/src/body/decode.d.ts +3 -1
- package/build/src/body/decode.js +53 -17
- package/build/src/body/region.d.ts +9 -1
- package/build/src/body/region.js +87 -12
- package/build/src/body/render.js +1 -1
- package/build/src/body/verification.d.ts +3 -1
- package/build/src/body/verification.js +4 -1
- package/build/src/cli/accepted.js +19 -2
- package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
- package/build/src/cli/commands/akuma-invoke.js +133 -13
- package/build/src/cli/commands/akuma.js +11 -4
- package/build/src/cli/commands/contract-help.js +17 -4
- package/build/src/cli/commands/contract.js +6 -4
- package/build/src/cli/commands/status-set.js +15 -24
- package/build/src/cli/main.js +5 -0
- package/build/src/cli/parse.d.ts +2 -0
- package/build/src/cli/parse.js +5 -0
- package/build/src/cli/render/akuma-activity.d.ts +99 -2
- package/build/src/cli/render/akuma-activity.js +484 -40
- package/build/src/cli/render/akuma.d.ts +10 -0
- package/build/src/cli/render/akuma.js +28 -21
- package/build/src/cli/render/audit.js +1 -1
- package/build/src/cli/render/board.js +1 -1
- package/build/src/cli/render/catalog.js +16 -13
- package/build/src/cli/render/contract-history.js +39 -10
- package/build/src/cli/render/contract-observation.js +10 -6
- package/build/src/cli/render/contract.js +109 -106
- package/build/src/cli/render/execution-progress.d.ts +23 -1
- package/build/src/cli/render/execution-progress.js +151 -28
- package/build/src/cli/render/kanshi-akuma.js +27 -39
- package/build/src/cli/render/kanshi.js +33 -26
- package/build/src/cli/render/nuke.js +1 -1
- package/build/src/cli/render/receipt.d.ts +1 -1
- package/build/src/cli/render/receipt.js +5 -5
- package/build/src/cli/render/refusal.js +10 -7
- package/build/src/cli/render/region.js +1 -1
- package/build/src/cli/render/settings.js +12 -2
- package/build/src/cli/render/status-line.d.ts +30 -0
- package/build/src/cli/render/status-line.js +58 -0
- package/build/src/cli/render/status-set.js +1 -1
- package/build/src/cli/render/task.d.ts +2 -0
- package/build/src/cli/render/task.js +11 -7
- package/build/src/cli/render/terminal.d.ts +0 -1
- package/build/src/cli/render/terminal.js +47 -15
- package/build/src/cli/result.d.ts +1 -0
- package/build/src/cli/runtime.d.ts +2 -0
- package/build/src/cli/runtime.js +13 -7
- package/build/src/cli/usage.js +2 -2
- package/build/src/cli/version.d.ts +1 -0
- package/build/src/cli/version.js +25 -0
- package/build/src/git/workspace.d.ts +1 -1
- package/build/src/git/workspace.js +1 -1
- package/build/src/identity/mint.d.ts +13 -0
- package/build/src/identity/mint.js +26 -0
- package/build/src/identity/normalize.d.ts +4 -0
- package/build/src/identity/normalize.js +30 -0
- package/build/src/index.d.ts +2 -0
- package/build/src/index.js +1 -0
- package/build/src/kanshi/read.js +17 -4
- package/build/src/kanshi/report.d.ts +1 -0
- package/build/src/library/akuma-creation.d.ts +2 -0
- package/build/src/library/akuma-creation.js +33 -17
- package/build/src/library/bind.js +18 -16
- package/build/src/library/contract-bind.d.ts +3 -1
- package/build/src/library/contract-bind.js +5 -3
- package/build/src/library/contract-operations.d.ts +2 -2
- package/build/src/library/contract-operations.js +1 -1
- package/build/src/library/contract-types.d.ts +1 -0
- package/build/src/library/fleet.d.ts +4 -1
- package/build/src/library/fleet.js +32 -8
- package/build/src/plugin/akuma-signals.d.ts +6 -0
- package/build/src/plugin/akuma-signals.js +8 -0
- package/build/src/plugin/public.d.ts +4 -0
- package/build/src/plugin/runtime.js +4 -1
- package/build/src/protocol/amend.js +4 -3
- package/build/src/protocol/attempt.d.ts +2 -0
- package/build/src/protocol/bind.js +41 -14
- package/build/src/protocol/completion.d.ts +3 -0
- package/build/src/protocol/completion.js +7 -1
- package/build/src/protocol/deliver.js +165 -92
- package/build/src/protocol/execution-observation.d.ts +0 -4
- package/build/src/protocol/intent.d.ts +15 -20
- package/build/src/protocol/intent.js +29 -17
- package/build/src/protocol/operations.d.ts +2 -0
- package/build/src/protocol/placement.js +5 -1
- package/build/src/protocol/read/status.d.ts +1 -0
- package/build/src/protocol/read/status.js +10 -2
- package/build/src/protocol/reintegrate.d.ts +1 -1
- package/build/src/protocol/reintegrate.js +70 -43
- package/build/src/protocol/result-codec.js +9 -7
- package/build/src/protocol/review.js +53 -30
- package/build/src/protocol/run.d.ts +83 -36
- package/build/src/protocol/run.js +90 -75
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/settings.js +5 -14
- package/build/src/task/document.d.ts +5 -1
- package/build/src/task/document.js +6 -1
- package/build/src/verification/execution.d.ts +0 -1
- package/build/src/verification/execution.js +0 -24
- package/build/src/verification/observation.d.ts +0 -4
- package/build/src/verification/observation.js +1 -2
- package/package.json +3 -1
- package/build/src/git/verification-environment.d.ts +0 -10
- 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
|
-
|
|
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>;
|
package/build/src/akuma/body.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
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: {
|
|
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 &&
|
|
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 =
|
|
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 =
|
|
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);
|