@astrosheep/keiyaku 4.6.19 → 4.6.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 +7 -3
- package/build/plugins/pi/README.md +111 -0
- package/build/plugins/pi/akuma-tools.ts +310 -0
- package/build/plugins/pi/keiyaku.ts +2 -0
- package/build/src/akuma/akuma-call.d.ts +4 -1
- package/build/src/akuma/akuma-call.js +24 -7
- package/build/src/akuma/akuma-instance.d.ts +28 -8
- package/build/src/akuma/akuma-instance.js +85 -42
- package/build/src/akuma/akuma-owner.d.ts +18 -9
- package/build/src/akuma/akuma-owner.js +31 -25
- package/build/src/akuma/akuma-selection-execution.d.ts +4 -1
- package/build/src/akuma/akuma-selection-execution.js +8 -4
- package/build/src/akuma/akuma.d.ts +17 -0
- package/build/src/akuma/body-launch.d.ts +107 -0
- package/build/src/akuma/body-launch.js +57 -0
- package/build/src/akuma/body-supervisor.d.ts +7 -6
- package/build/src/akuma/body-supervisor.js +17 -12
- package/build/src/akuma/body.d.ts +13 -94
- package/build/src/akuma/body.js +124 -296
- package/build/src/akuma/call-input.d.ts +2 -0
- package/build/src/akuma/call-input.js +21 -1
- package/build/src/akuma/call-request.d.ts +6 -2
- package/build/src/akuma/call-request.js +9 -1
- package/build/src/akuma/detached-body.d.ts +9 -0
- package/build/src/akuma/detached-body.js +94 -0
- package/build/src/akuma/execution-instructions.d.ts +8 -0
- package/build/src/akuma/execution-instructions.js +17 -0
- package/build/src/akuma/heart/facts.d.ts +21 -1
- package/build/src/akuma/heart/facts.js +10 -0
- package/build/src/akuma/heart/index.d.ts +16 -1
- package/build/src/akuma/heart/index.js +95 -30
- package/build/src/akuma/heart/rows.d.ts +14 -2
- package/build/src/akuma/heart/rows.js +48 -4
- package/build/src/akuma/heart/schema.d.ts +4 -1
- package/build/src/akuma/heart/schema.js +87 -30
- package/build/src/akuma/heart/soul.js +4 -0
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +59 -15
- package/build/src/akuma/heart/timeline.d.ts +6 -4
- package/build/src/akuma/heart/timeline.js +14 -4
- package/build/src/akuma/in-process-body.d.ts +5 -0
- package/build/src/akuma/in-process-body.js +57 -0
- package/build/src/akuma/index.d.ts +1 -1
- package/build/src/akuma/projection.d.ts +44 -1
- package/build/src/akuma/projection.js +13 -1
- package/build/src/akuma/provider.d.ts +4 -0
- package/build/src/akuma/providers/acp/core.js +1 -1
- package/build/src/akuma/providers/acp/index.js +3 -0
- package/build/src/akuma/providers/claude/index.js +8 -5
- package/build/src/akuma/providers/codex-app-server/configuration.d.ts +5 -0
- package/build/src/akuma/providers/codex-app-server/configuration.js +72 -0
- package/build/src/akuma/providers/codex-app-server/index.js +7 -10
- package/build/src/akuma/providers/execution-environment.d.ts +8 -2
- package/build/src/akuma/providers/execution-environment.js +10 -1
- package/build/src/akuma/providers/grok-build/index.js +23 -16
- package/build/src/akuma/providers/index.d.ts +1 -0
- package/build/src/akuma/providers/index.js +8 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +9 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +6 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +4 -3
- package/build/src/akuma/providers/paseo/index.js +8 -2
- package/build/src/akuma/providers/pi/index.js +27 -10
- package/build/src/akuma/publication.d.ts +4 -3
- package/build/src/akuma/publication.js +21 -18
- package/build/src/akuma/selection-observation.d.ts +51 -0
- package/build/src/akuma/selection-owner-port.d.ts +2 -1
- package/build/src/akuma/selection-owner-port.js +4 -3
- package/build/src/akuma/turn-drive.d.ts +4 -2
- package/build/src/akuma/turn-drive.js +58 -21
- package/build/src/akuma-body.d.ts +0 -10
- package/build/src/akuma-body.js +13 -118
- package/build/src/body-host.d.ts +28 -0
- package/build/src/body-host.js +86 -0
- package/build/src/body-requests.d.ts +13 -0
- package/build/src/body-requests.js +131 -0
- package/build/src/cli/commands/akuma-grammar.d.ts +8 -1
- package/build/src/cli/commands/akuma-grammar.js +36 -6
- package/build/src/cli/commands/akuma.js +11 -0
- package/build/src/cli/commands/report.d.ts +6 -0
- package/build/src/cli/commands/report.js +31 -0
- package/build/src/cli/parse.js +1 -0
- package/build/src/cli/render/akuma-activity.js +27 -17
- package/build/src/cli/render/terminal.d.ts +5 -0
- package/build/src/cli/render/terminal.js +7 -0
- package/build/src/cli/runtime.js +4 -1
- package/build/src/index.d.ts +3 -1
- package/build/src/index.js +1 -0
- package/build/src/library/akuma-creation.d.ts +7 -3
- package/build/src/library/akuma-creation.js +23 -11
- package/build/src/library/akumas.d.ts +4 -1
- package/build/src/library/akumas.js +13 -5
- package/build/src/library/selection.d.ts +2 -1
- package/build/src/library/selection.js +6 -3
- package/build/src/plugin/public.d.ts +11 -0
- package/build/src/plugin/runtime.js +1 -0
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/package.json +3 -3
- package/plugins/paseo/plugin/server/keiyaku.ts +1 -0
- package/plugins/paseo/plugin/server/mirror.ts +2 -2
- package/plugins/pi/README.md +111 -0
- package/plugins/pi/akuma-tools.ts +310 -0
- package/plugins/pi/keiyaku.ts +2 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { realpath, stat } from "node:fs/promises";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
export function bareOption(value) {
|
|
4
|
+
if (value !== undefined && typeof value !== "boolean")
|
|
5
|
+
throw new TypeError("bare must be a boolean");
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
export async function writableDirectoriesOption(value) {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
return [];
|
|
11
|
+
if (!Array.isArray(value))
|
|
12
|
+
throw new TypeError("writableDirectories must be an array of canonical directories");
|
|
13
|
+
const directories = await Promise.all(value.map(async (path) => {
|
|
14
|
+
if (typeof path !== "string" || path.trim() === "" || !isAbsolute(path))
|
|
15
|
+
throw new TypeError("writableDirectories must contain nonblank canonical absolute directories");
|
|
16
|
+
const canonical = await canonicalBirthCwd(path);
|
|
17
|
+
if (canonical !== path)
|
|
18
|
+
throw new TypeError(`writableDirectories path is not canonical: ${path}`);
|
|
19
|
+
return canonical;
|
|
20
|
+
}));
|
|
21
|
+
return Object.freeze([...new Set(directories)]);
|
|
22
|
+
}
|
|
3
23
|
export async function canonicalBirthCwd(input, diagnostic = `cwd is not an existing directory: ${input}`) {
|
|
4
24
|
const selected = resolve(input);
|
|
5
25
|
try {
|
|
@@ -2,7 +2,7 @@ import { type Soul } from "./heart/index.js";
|
|
|
2
2
|
import { type AkuId, type AkumaPaths } from "./identity.js";
|
|
3
3
|
import { type CallInitialTell, type CallInitialTellAdmission } from "./call-initial-tell.js";
|
|
4
4
|
import { type ChildRequestCommand, type ErasedRequestCommand, type RequestProtocol } from "./request-wire.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { LiveBodyLaunch } from "./body-launch.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import type { WorldRoot } from "../world.js";
|
|
8
8
|
declare const akumaCallRecipeSchema: z.ZodObject<{
|
|
@@ -71,6 +71,7 @@ declare const akumaCallRecipeSchema: z.ZodObject<{
|
|
|
71
71
|
systemPrompt?: string | undefined;
|
|
72
72
|
systemPromptMode?: "replace" | "append" | undefined;
|
|
73
73
|
}>>;
|
|
74
|
+
bare: z.ZodOptional<z.ZodBoolean>;
|
|
74
75
|
}, z.core.$strict>;
|
|
75
76
|
declare const akumaCallPayloadSchema: z.ZodObject<{
|
|
76
77
|
world: z.ZodString;
|
|
@@ -84,6 +85,7 @@ declare const akumaCallPayloadSchema: z.ZodObject<{
|
|
|
84
85
|
birthInitiator: z.ZodOptional<z.ZodString>;
|
|
85
86
|
contractId: z.ZodOptional<z.ZodString>;
|
|
86
87
|
cwd: z.ZodOptional<z.ZodString>;
|
|
88
|
+
writableDirectories: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
87
89
|
recipe: z.ZodObject<{
|
|
88
90
|
description: z.ZodOptional<z.ZodString>;
|
|
89
91
|
allowed: z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
|
@@ -150,6 +152,7 @@ declare const akumaCallPayloadSchema: z.ZodObject<{
|
|
|
150
152
|
systemPrompt?: string | undefined;
|
|
151
153
|
systemPromptMode?: "replace" | "append" | undefined;
|
|
152
154
|
}>>;
|
|
155
|
+
bare: z.ZodOptional<z.ZodBoolean>;
|
|
153
156
|
}, z.core.$strict>;
|
|
154
157
|
}, z.core.$strict>;
|
|
155
158
|
export type AkumaCallRecipe = z.infer<typeof akumaCallRecipeSchema>;
|
|
@@ -167,13 +170,14 @@ export type AkumaCallRequestChildLaunch = Readonly<{
|
|
|
167
170
|
export type InitialTellAdmissionRequest = Readonly<{
|
|
168
171
|
id: AkuId;
|
|
169
172
|
initialTell: CallInitialTell;
|
|
173
|
+
contractId?: string;
|
|
170
174
|
signal: AbortSignal;
|
|
171
175
|
}>;
|
|
172
176
|
type AkumaCallRequestCapabilities = Readonly<{
|
|
173
177
|
world: WorldRoot;
|
|
174
178
|
paths: AkumaPaths;
|
|
175
179
|
parent: Soul;
|
|
176
|
-
spawn(launch: AkumaCallRequestChildLaunch): Promise<
|
|
180
|
+
spawn(launch: AkumaCallRequestChildLaunch): Promise<LiveBodyLaunch | void>;
|
|
177
181
|
admitInitialTell(input: InitialTellAdmissionRequest): Promise<CallInitialTellAdmission>;
|
|
178
182
|
}>;
|
|
179
183
|
declare const liveResultSchema: z.ZodObject<{
|
|
@@ -25,6 +25,7 @@ const akumaCallRecipeSchema = z
|
|
|
25
25
|
allowed: allowedActionsSchema,
|
|
26
26
|
provider: providerRecipeSchema,
|
|
27
27
|
options: providerOptionsSchema,
|
|
28
|
+
bare: z.boolean().optional(),
|
|
28
29
|
})
|
|
29
30
|
.strict();
|
|
30
31
|
const initialTellSchema = z
|
|
@@ -43,6 +44,7 @@ const akumaCallPayloadSchema = z
|
|
|
43
44
|
birthInitiator: z.string().optional(),
|
|
44
45
|
contractId: z.string().optional(),
|
|
45
46
|
cwd: absolutePathSchema.optional(),
|
|
47
|
+
writableDirectories: z.array(absolutePathSchema).readonly().optional(),
|
|
46
48
|
recipe: akumaCallRecipeSchema,
|
|
47
49
|
})
|
|
48
50
|
.strict();
|
|
@@ -68,6 +70,7 @@ async function executeAkumaCall(request, facts, capabilities) {
|
|
|
68
70
|
const outcome = await executePreparedCall({
|
|
69
71
|
archetype: request.archetype,
|
|
70
72
|
cwd: request.cwd ?? parent.cwd,
|
|
73
|
+
...(request.writableDirectories === undefined ? {} : { writableDirectories: request.writableDirectories }),
|
|
71
74
|
...(request.initialTell === undefined
|
|
72
75
|
? {}
|
|
73
76
|
: {
|
|
@@ -88,6 +91,7 @@ async function executeAkumaCall(request, facts, capabilities) {
|
|
|
88
91
|
provider: request.recipe.provider,
|
|
89
92
|
options: request.recipe.options,
|
|
90
93
|
allowed: request.recipe.allowed,
|
|
94
|
+
...(request.recipe.bare === undefined ? {} : { bare: request.recipe.bare }),
|
|
91
95
|
},
|
|
92
96
|
parent,
|
|
93
97
|
requestId: facts.id,
|
|
@@ -103,7 +107,11 @@ async function executeAkumaCall(request, facts, capabilities) {
|
|
|
103
107
|
...(request.birthInitiator === undefined ? {} : { birthInitiator: request.birthInitiator }),
|
|
104
108
|
...(request.contractId === undefined ? {} : { completion: { contractId: request.contractId } }),
|
|
105
109
|
}),
|
|
106
|
-
admitInitialTell
|
|
110
|
+
admitInitialTell: async (input) => await admitInitialTell({
|
|
111
|
+
...input,
|
|
112
|
+
signal: facts.signal,
|
|
113
|
+
...(request.contractId === undefined ? {} : { contractId: request.contractId }),
|
|
114
|
+
}),
|
|
107
115
|
},
|
|
108
116
|
});
|
|
109
117
|
return {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BodyLaunch, LiveBodyLaunch } from "./body-launch.js";
|
|
2
|
+
export declare const LEASH_HELD_EXIT = 75;
|
|
3
|
+
export declare function bodyProcessInput(launch: BodyLaunch, bodyModuleUrl?: string): Promise<{
|
|
4
|
+
argv: string[];
|
|
5
|
+
cwd: string;
|
|
6
|
+
env: Record<string, string>;
|
|
7
|
+
log: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function spawnAkumaBody(launch: BodyLaunch): Promise<LiveBodyLaunch>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { spawnDetachedProcess } from "../runtime/proc/run.js";
|
|
4
|
+
import { readHeart } from "./heart/index.js";
|
|
5
|
+
import { worldRootForAkumaPaths } from "./identity.js";
|
|
6
|
+
import { akumaExecutionEnvironment } from "./providers/execution-environment.js";
|
|
7
|
+
export const LEASH_HELD_EXIT = 75;
|
|
8
|
+
async function launchCwd(launch) {
|
|
9
|
+
if (launch.seed !== undefined)
|
|
10
|
+
return launch.seed.cwd;
|
|
11
|
+
const soul = (await readHeart(launch.paths)).soul;
|
|
12
|
+
if (soul === null)
|
|
13
|
+
throw new Error("Akuma wake has no born soul");
|
|
14
|
+
return soul.cwd;
|
|
15
|
+
}
|
|
16
|
+
async function executableMissing(path) {
|
|
17
|
+
try {
|
|
18
|
+
return !(await stat(path)).isFile();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function frozenCwdMissing(path) {
|
|
25
|
+
try {
|
|
26
|
+
await stat(path);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Diagnose the attempted executable and frozen cwd independently; never substitute either. */
|
|
34
|
+
async function diagnoseSpawnEnoent(input, cause) {
|
|
35
|
+
const executable = input.argv[0] ?? process.execPath;
|
|
36
|
+
const cwd = input.cwd;
|
|
37
|
+
const [noExecutable, noCwd] = await Promise.all([executableMissing(executable), frozenCwdMissing(cwd)]);
|
|
38
|
+
if (!noExecutable && !noCwd)
|
|
39
|
+
return cause instanceof Error ? cause : new Error(String(cause));
|
|
40
|
+
const missing = [];
|
|
41
|
+
if (noCwd)
|
|
42
|
+
missing.push(`frozen execution directory ${cwd} no longer exists`);
|
|
43
|
+
if (noExecutable)
|
|
44
|
+
missing.push(`waking process executable ${executable} no longer exists (the host likely removed the running process binary)`);
|
|
45
|
+
const remedies = [];
|
|
46
|
+
if (noCwd)
|
|
47
|
+
remedies.push("a missing execution directory likely means its Contract was accepted and the worktree cleaned up, with deliverables already in Git");
|
|
48
|
+
if (noExecutable)
|
|
49
|
+
remedies.push("a missing executable likely means the host removed the running process binary: re-enter with a fresh CLI process and retry");
|
|
50
|
+
const remedy = `kill this Akuma and call a fresh one; ${remedies.join("; ")}`;
|
|
51
|
+
const error = new Error(`Body spawn failed: ${missing.join(" and ")}; ${remedy}`, { cause });
|
|
52
|
+
error.code = "ENOENT";
|
|
53
|
+
return error;
|
|
54
|
+
}
|
|
55
|
+
export async function bodyProcessInput(launch, bodyModuleUrl = import.meta.url) {
|
|
56
|
+
const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
|
|
57
|
+
const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
|
|
58
|
+
if (actorId === undefined)
|
|
59
|
+
throw new Error("Akuma wake has no born soul");
|
|
60
|
+
const executable = process.execPath;
|
|
61
|
+
const source = bodyModuleUrl.endsWith(".ts");
|
|
62
|
+
const entry = fileURLToPath(new URL(source ? "../akuma-body.ts" : "../akuma-body.js", bodyModuleUrl));
|
|
63
|
+
return {
|
|
64
|
+
argv: source ? [executable, "--import", import.meta.resolve("tsx"), entry, encoded] : [executable, entry, encoded],
|
|
65
|
+
cwd: await launchCwd(launch),
|
|
66
|
+
env: akumaExecutionEnvironment(process.env, {
|
|
67
|
+
KEIYAKU_ACTOR_ID: actorId,
|
|
68
|
+
KEIYAKU_ACTOR_WORLD: worldRootForAkumaPaths(launch.paths),
|
|
69
|
+
}),
|
|
70
|
+
log: launch.paths.log,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export async function spawnAkumaBody(launch) {
|
|
74
|
+
const input = await bodyProcessInput(launch);
|
|
75
|
+
try {
|
|
76
|
+
const owned = await spawnDetachedProcess(input);
|
|
77
|
+
const completed = owned.exited.then((exit) => ({
|
|
78
|
+
diagnostic: exit.code === null ? `pre-admission signal ${exit.signal ?? "unknown"}` : `pre-admission exit ${exit.code}`,
|
|
79
|
+
held: exit.code === LEASH_HELD_EXIT,
|
|
80
|
+
child: { code: exit.code, signal: exit.signal, log: exit.log },
|
|
81
|
+
}));
|
|
82
|
+
void completed.catch(() => undefined);
|
|
83
|
+
return {
|
|
84
|
+
completed,
|
|
85
|
+
cancel: (force) => owned.terminate(force),
|
|
86
|
+
release: () => owned.release(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error?.code !== "ENOENT")
|
|
91
|
+
throw error;
|
|
92
|
+
throw await diagnoseSpawnEnoent(input, error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Soul } from "./heart/index.js";
|
|
2
|
+
/** Association is supplied by composition, never read or persisted by Akuma. */
|
|
3
|
+
export type ExecutionAssociation = Readonly<{
|
|
4
|
+
contractId: string;
|
|
5
|
+
}>;
|
|
6
|
+
export type ExecutionAssociationReader = () => Promise<ExecutionAssociation | undefined>;
|
|
7
|
+
/** One current execution section; provider adapters only choose its native channel. */
|
|
8
|
+
export declare function keiyakuExecutionInstructions(soul: Soul, association?: ExecutionAssociation): string | undefined;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** One current execution section; provider adapters only choose its native channel. */
|
|
2
|
+
export function keiyakuExecutionInstructions(soul, association) {
|
|
3
|
+
if (soul.bare !== false) {
|
|
4
|
+
return soul.bare === true && soul.origin.kind === "fork"
|
|
5
|
+
? "Any inherited Keiyaku execution context is inactive for this execution."
|
|
6
|
+
: undefined;
|
|
7
|
+
}
|
|
8
|
+
return [
|
|
9
|
+
"<keiyaku>",
|
|
10
|
+
"This context supersedes any earlier Keiyaku execution context.",
|
|
11
|
+
`Akuma: ${soul.id}`,
|
|
12
|
+
`Archetype: ${soul.archetype}`,
|
|
13
|
+
...(association === undefined ? [] : [`Contract context (not a Dispatch receipt): ${association.contractId}`]),
|
|
14
|
+
'Use `keiyaku report "..."` to share updates without stopping your work.',
|
|
15
|
+
"</keiyaku>",
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
@@ -30,9 +30,10 @@ export type Soul = Readonly<{
|
|
|
30
30
|
origin: AkumaOrigin;
|
|
31
31
|
caller?: AkumaCaller;
|
|
32
32
|
allowed: AllowedActions;
|
|
33
|
+
bare?: boolean;
|
|
33
34
|
createdAt: string;
|
|
34
35
|
}>;
|
|
35
|
-
export type RequestRecipe = Pick<Soul, "description" | "provider" | "options" | "allowed">;
|
|
36
|
+
export type RequestRecipe = Pick<Soul, "description" | "provider" | "options" | "allowed" | "bare">;
|
|
36
37
|
export type BodyEnd = "exited" | "broke-off" | "put-down";
|
|
37
38
|
export type BodyFact = Readonly<{
|
|
38
39
|
sequence: number;
|
|
@@ -60,6 +61,8 @@ export type TurnOutcome = Readonly<{
|
|
|
60
61
|
session: ResumeCoordinate;
|
|
61
62
|
answer: string;
|
|
62
63
|
answerJson?: string;
|
|
64
|
+
}> | Readonly<{
|
|
65
|
+
kind: "canceled";
|
|
63
66
|
}> | Readonly<{
|
|
64
67
|
kind: "failed";
|
|
65
68
|
diagnostic: string;
|
|
@@ -88,6 +91,23 @@ export type TurnEndFact = Readonly<{
|
|
|
88
91
|
export type TurnFact = TurnStartFact & Readonly<{
|
|
89
92
|
end?: TurnEndFact;
|
|
90
93
|
}>;
|
|
94
|
+
export declare const reportFactSchema: z.ZodObject<{
|
|
95
|
+
kind: z.ZodLiteral<"report">;
|
|
96
|
+
sequence: z.ZodNumber;
|
|
97
|
+
bodySequence: z.ZodNumber;
|
|
98
|
+
turnSequence: z.ZodNumber;
|
|
99
|
+
text: z.ZodString;
|
|
100
|
+
at: z.ZodString;
|
|
101
|
+
}, z.core.$strict>;
|
|
102
|
+
export type ReportFact = z.infer<typeof reportFactSchema>;
|
|
103
|
+
export type ReportAdmission = Readonly<{
|
|
104
|
+
kind: "recorded";
|
|
105
|
+
report: ReportFact;
|
|
106
|
+
initiator?: string;
|
|
107
|
+
}> | Readonly<{
|
|
108
|
+
kind: "refused";
|
|
109
|
+
reason: "not-born" | "not-current-execution";
|
|
110
|
+
}>;
|
|
91
111
|
export type CallFact = Readonly<{
|
|
92
112
|
kind: "call";
|
|
93
113
|
sequence: number;
|
|
@@ -12,6 +12,16 @@ export const akumaOriginSchema = z.discriminatedUnion("kind", [
|
|
|
12
12
|
z.object({ kind: z.literal("request"), parent: akumaIdSchema, requestId: originText }),
|
|
13
13
|
z.object({ kind: z.literal("fork"), parent: akumaIdSchema, at: originText }),
|
|
14
14
|
]);
|
|
15
|
+
export const reportFactSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
kind: z.literal("report"),
|
|
18
|
+
sequence: z.number().int().positive().safe(),
|
|
19
|
+
bodySequence: z.number().int().positive().safe(),
|
|
20
|
+
turnSequence: z.number().int().positive().safe(),
|
|
21
|
+
text: z.string().refine((value) => value.trim() !== "", "expected nonblank report text"),
|
|
22
|
+
at: z.string().refine((value) => Number.isFinite(Date.parse(value)), "expected report timestamp"),
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
15
25
|
export function projectTell(fact) {
|
|
16
26
|
return {
|
|
17
27
|
kind: "tell",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AkumaPaths } from "../identity.js";
|
|
2
|
-
import type { BodyEnd, BodyFact, ForkPoint, HeartSnapshot, InterruptCompletion, KillFact, PauseFact, SealFact, SessionFact, Soul, TellDeliveryInput, TellFact, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact } from "./facts.js";
|
|
2
|
+
import type { BodyEnd, BodyFact, ForkPoint, HeartSnapshot, InterruptCompletion, KillFact, PauseFact, SealFact, SessionFact, Soul, TellDeliveryInput, TellFact, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact, ReportAdmission } from "./facts.js";
|
|
3
3
|
import type { AgentEvent } from "./activity-schema.js";
|
|
4
4
|
import { type StatusFactInput, type ActivityFactSlice } from "./timeline.js";
|
|
5
5
|
import { type HeartRead } from "./storage.js";
|
|
@@ -112,6 +112,12 @@ export declare function recordTell(paths: AkumaPaths, tell: TellInput, options?:
|
|
|
112
112
|
interrupt?: false;
|
|
113
113
|
signal?: AbortSignal;
|
|
114
114
|
}>): Promise<TellAdmission>;
|
|
115
|
+
export declare function recordReport(paths: AkumaPaths, input: Readonly<{
|
|
116
|
+
bodySequence: number;
|
|
117
|
+
turnSequence: number;
|
|
118
|
+
text: string;
|
|
119
|
+
at: string;
|
|
120
|
+
}>, signal?: AbortSignal): Promise<ReportAdmission>;
|
|
115
121
|
export declare function readTell(paths: AkumaPaths, id: string): Promise<TellFact | null>;
|
|
116
122
|
export declare function recordTellDeliveries(paths: AkumaPaths, inputs: readonly TellDeliveryInput[]): Promise<void>;
|
|
117
123
|
export declare function recordTellReceipt(paths: AkumaPaths, input: TellReceiptInput): Promise<void>;
|
|
@@ -143,6 +149,15 @@ export declare function breakBody(paths: AkumaPaths, input: Readonly<{
|
|
|
143
149
|
end: Exclude<BodyEnd, "exited">;
|
|
144
150
|
at: string;
|
|
145
151
|
}>): Promise<void>;
|
|
152
|
+
/** The owning Body calls this only after proving its provider custody retired. */
|
|
153
|
+
export declare function cancelBodyTurns(paths: AkumaPaths, input: Readonly<{
|
|
154
|
+
bodySequence: number;
|
|
155
|
+
at: string;
|
|
156
|
+
handoff?: boolean;
|
|
157
|
+
}>): Promise<Readonly<{
|
|
158
|
+
canceled: readonly number[];
|
|
159
|
+
disposition: PendingTellDisposition | null;
|
|
160
|
+
}>>;
|
|
146
161
|
export declare function beginTurn(paths: AkumaPaths, input: Readonly<{
|
|
147
162
|
bodySequence: number;
|
|
148
163
|
startedAt: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parsePublicHistoryId } from "../identity.js";
|
|
2
2
|
import { decodeRuntimeModel, withoutModel } from "../provider-recipe.js";
|
|
3
3
|
import { BodyControlledError } from "./facts.js";
|
|
4
|
-
import { answeredTurnFact, completeInterruptBarrier as judgeInterruptBarrier, endBodyFact, finishBodyFact, deleteStopControl, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertKillFact, insertSessionFact, insertStopControl, killFactForBody, latestBodyFact, insertInterruptBarrier, isTellProtectedByPause, latestKillFact, latestSessionFact, latestTurnFactForBody, lastAnsweredTurnFact, pauseFact, sessionFactForCoordinate, stopFact, turnFact, } from "./rows.js";
|
|
4
|
+
import { answeredTurnFact, completeInterruptBarrier as judgeInterruptBarrier, endBodyFact, finishBodyFact, deleteStopControl, insertReportFact, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertKillFact, insertSessionFact, insertStopControl, killFactForBody, hasBodyHandoffControl, latestBodyFact, insertInterruptBarrier, isTellProtectedByPause, latestKillFact, latestSessionFact, latestTurnFactForBody, lastAnsweredTurnFact, pauseFact, sessionFactForCoordinate, stopFact, turnFact, openTurnsForBody, terminalTurnForTell, } from "./rows.js";
|
|
5
5
|
import { insertTellDeliveryFact, dispositionSnapshotProven, insertTellBindingFact, insertTellDispositionSnapshot, insertTellReceiptFact, insertUndeliveredTellReceipts, latestOpenTellDisposition, openBoundTurns, openTellDispositionIds, pendingTellFacts, hasPendingTell, resolveTellDispositionSnapshot, sameTellInput, tellDispositionResolved, tellFact, tellIdsForFence, turnParticipants, } from "./tells.js";
|
|
6
6
|
import { activityFactSlice, statusFacts, readStatusBatch, decodeTimelineBatch, timelineDataVersion, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
|
|
7
7
|
import { createHeartReader, isHeartAbsent, readSealFromLeash, recordPendingTell, withHeartTransaction, withReadOnlyHeart, } from "./storage.js";
|
|
@@ -121,6 +121,7 @@ function sameStatusBatch(left, right) {
|
|
|
121
121
|
sameRawRows(left.rows, right.rows) &&
|
|
122
122
|
sameRawRows(left.turns, right.turns) &&
|
|
123
123
|
sameRawRows(left.calls, right.calls) &&
|
|
124
|
+
sameRawRows(left.reports, right.reports) &&
|
|
124
125
|
sameRawRows(left.activity, right.activity) &&
|
|
125
126
|
sameRawRows(left.tells.rows, right.tells.rows) &&
|
|
126
127
|
sameRawRows(left.tells.deliveries, right.tells.deliveries) &&
|
|
@@ -134,7 +135,9 @@ export function createStatusFactReader(paths) {
|
|
|
134
135
|
life: (signal) => readLifeSnapshot(paths, signal, reader.read),
|
|
135
136
|
tellOutcome: (id, signal) => reader.read((heart) => {
|
|
136
137
|
const tell = tellFact(heart, id);
|
|
137
|
-
|
|
138
|
+
// A pending Tell may be retried by a successor after cancellation. Its
|
|
139
|
+
// first retained terminal still belongs to this exact admission.
|
|
140
|
+
const end = terminalTurnForTell(heart, id)?.end;
|
|
138
141
|
return { tell, end };
|
|
139
142
|
}, signal),
|
|
140
143
|
clear() {
|
|
@@ -227,6 +230,36 @@ export async function recordTell(paths, tell, options = {}) {
|
|
|
227
230
|
return existing === null ? judgeNewTell(inputs) : judgeExistingTell(inputs, existing);
|
|
228
231
|
}, options.signal);
|
|
229
232
|
}
|
|
233
|
+
export async function recordReport(paths, input, signal) {
|
|
234
|
+
try {
|
|
235
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
236
|
+
if (soulFact(heart) === null)
|
|
237
|
+
return { kind: "refused", reason: "not-born" };
|
|
238
|
+
const body = latestBodyFact(heart);
|
|
239
|
+
const turn = body === null ? null : latestTurnFactForBody(heart, body.sequence);
|
|
240
|
+
if (body === null ||
|
|
241
|
+
body.sequence !== input.bodySequence ||
|
|
242
|
+
body.end !== undefined ||
|
|
243
|
+
body.hung !== undefined ||
|
|
244
|
+
turn === null ||
|
|
245
|
+
turn.sequence !== input.turnSequence ||
|
|
246
|
+
turn.end !== undefined)
|
|
247
|
+
return { kind: "refused", reason: "not-current-execution" };
|
|
248
|
+
const report = insertReportFact(heart, input);
|
|
249
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
250
|
+
return {
|
|
251
|
+
kind: "recorded",
|
|
252
|
+
report,
|
|
253
|
+
...(turn.initiator === undefined ? {} : { initiator: turn.initiator }),
|
|
254
|
+
};
|
|
255
|
+
}, signal);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (isHeartAbsent(error))
|
|
259
|
+
return { kind: "refused", reason: "not-born" };
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
230
263
|
export async function readTell(paths, id) {
|
|
231
264
|
return await withReadOnlyHeart(paths, (heart) => tellFact(heart, id));
|
|
232
265
|
}
|
|
@@ -264,36 +297,39 @@ export async function recordTellReceipt(paths, input) {
|
|
|
264
297
|
pruneActivityFacts(heart, ACTIVITY_LIMIT, changed);
|
|
265
298
|
});
|
|
266
299
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (existing !== null)
|
|
276
|
-
return { bodySequence: input.bodySequence, tellIds: existing };
|
|
277
|
-
if (pauseFact(heart)?.bodySequence === input.bodySequence) {
|
|
278
|
-
if (input.handoff && body.end === undefined)
|
|
279
|
-
endBodyFact(heart, { sequence: input.bodySequence, end: "put-down", at: input.at });
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
const pending = pendingTellFacts(heart);
|
|
283
|
-
const tellIds = pending.map((tell) => tell.id);
|
|
284
|
-
if (tellIds.length === 0)
|
|
285
|
-
return null;
|
|
286
|
-
if (body.end === undefined) {
|
|
287
|
-
if (!input.handoff)
|
|
288
|
-
return null;
|
|
300
|
+
function decidePendingTellDispositionInHeart(heart, input) {
|
|
301
|
+
const body = latestBodyFact(heart);
|
|
302
|
+
if (body === null || body.sequence !== input.bodySequence)
|
|
303
|
+
return null;
|
|
304
|
+
if (tellDispositionResolved(heart, input.bodySequence) === true)
|
|
305
|
+
return null;
|
|
306
|
+
if (hasBodyHandoffControl(heart, input.bodySequence)) {
|
|
307
|
+
if (input.handoff && body.end === undefined)
|
|
289
308
|
endBodyFact(heart, { sequence: input.bodySequence, end: "put-down", at: input.at });
|
|
290
|
-
|
|
291
|
-
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const existing = openTellDispositionIds(heart, input.bodySequence);
|
|
312
|
+
if (existing !== null)
|
|
313
|
+
return { bodySequence: input.bodySequence, tellIds: existing };
|
|
314
|
+
const tellIds = pendingTellFacts(heart).map((tell) => tell.id);
|
|
315
|
+
if (tellIds.length === 0)
|
|
316
|
+
return null;
|
|
317
|
+
if (body.end === undefined) {
|
|
318
|
+
if (!input.handoff)
|
|
292
319
|
return null;
|
|
293
|
-
}
|
|
294
|
-
|
|
320
|
+
endBodyFact(heart, { sequence: input.bodySequence, end: "put-down", at: input.at });
|
|
321
|
+
}
|
|
322
|
+
else if (body.end === "broke-off") {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
insertTellDispositionSnapshot(heart, input.bodySequence, tellIds, input.at);
|
|
326
|
+
return { bodySequence: input.bodySequence, tellIds };
|
|
327
|
+
}
|
|
328
|
+
export async function decidePendingTellDisposition(paths, input) {
|
|
329
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
330
|
+
const decided = decidePendingTellDispositionInHeart(heart, input);
|
|
295
331
|
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
296
|
-
return
|
|
332
|
+
return decided;
|
|
297
333
|
});
|
|
298
334
|
}
|
|
299
335
|
export async function readOpenPendingTellDisposition(paths) {
|
|
@@ -307,6 +343,11 @@ export async function resolvePendingTellDisposition(paths, bodySequence, at, out
|
|
|
307
343
|
const tellIds = openTellDispositionIds(heart, bodySequence);
|
|
308
344
|
if (tellIds === null)
|
|
309
345
|
throw new HeartAuthorityCorruptionError(`Akuma disposition ${bodySequence} is missing`);
|
|
346
|
+
if (hasBodyHandoffControl(heart, bodySequence)) {
|
|
347
|
+
resolveTellDispositionSnapshot(heart, bodySequence, at);
|
|
348
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, true);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
310
351
|
if (outcome === "undelivered")
|
|
311
352
|
insertUndeliveredTellReceipts(heart, tellIds, at);
|
|
312
353
|
if (!dispositionSnapshotProven(heart, tellIds))
|
|
@@ -332,6 +373,9 @@ export async function requestStop(paths, at, signal) {
|
|
|
332
373
|
// exited Body remains already-stopped, and hung custody is untouched.
|
|
333
374
|
if (body.hung === undefined && body.end !== "exited") {
|
|
334
375
|
insertKillFact(heart, body.sequence, at);
|
|
376
|
+
const released = tellDispositionResolved(heart, body.sequence) === false;
|
|
377
|
+
resolveTellDispositionSnapshot(heart, body.sequence, at);
|
|
378
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, released);
|
|
335
379
|
if (stopFact(heart)?.bodySequence === body.sequence)
|
|
336
380
|
deleteStopControl(heart);
|
|
337
381
|
return { kind: "witnessed", body };
|
|
@@ -349,6 +393,28 @@ export async function requestStop(paths, at, signal) {
|
|
|
349
393
|
export async function breakBody(paths, input) {
|
|
350
394
|
await withHeartTransaction(paths, (heart) => endBodyFact(heart, input));
|
|
351
395
|
}
|
|
396
|
+
/** The owning Body calls this only after proving its provider custody retired. */
|
|
397
|
+
export async function cancelBodyTurns(paths, input) {
|
|
398
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
399
|
+
const body = latestBodyFact(heart);
|
|
400
|
+
if (body?.sequence !== input.bodySequence ||
|
|
401
|
+
body.hung !== undefined ||
|
|
402
|
+
(body.end !== undefined && body.end !== "put-down"))
|
|
403
|
+
throw new Error(`Akuma Body ${input.bodySequence} cannot settle controlled cancellation`);
|
|
404
|
+
const sequences = openTurnsForBody(heart, input.bodySequence);
|
|
405
|
+
for (const turnSequence of sequences)
|
|
406
|
+
insertTurnEndFact(heart, {
|
|
407
|
+
kind: "turn-end",
|
|
408
|
+
turnSequence,
|
|
409
|
+
outcome: { kind: "canceled" },
|
|
410
|
+
completedAt: input.at,
|
|
411
|
+
});
|
|
412
|
+
endBodyFact(heart, { sequence: input.bodySequence, end: "put-down", at: input.at });
|
|
413
|
+
const disposition = input.handoff === true ? decidePendingTellDispositionInHeart(heart, { ...input, handoff: true }) : null;
|
|
414
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, sequences.length > 0);
|
|
415
|
+
return { canceled: sequences, disposition };
|
|
416
|
+
});
|
|
417
|
+
}
|
|
352
418
|
export async function beginTurn(paths, input) {
|
|
353
419
|
return await withHeartTransaction(paths, (heart) => {
|
|
354
420
|
if (pauseFact(heart)?.bodySequence === input.bodySequence)
|
|
@@ -412,7 +478,6 @@ export async function endTurn(paths, input) {
|
|
|
412
478
|
export async function finishBodyIfIdle(paths, input) {
|
|
413
479
|
return await withHeartTransaction(paths, (heart) => {
|
|
414
480
|
if (stopFact(heart)?.bodySequence === input.sequence || pauseFact(heart)?.bodySequence === input.sequence) {
|
|
415
|
-
endBodyFact(heart, { ...input, end: "put-down" });
|
|
416
481
|
return { kind: "controlled" };
|
|
417
482
|
}
|
|
418
483
|
const pending = pendingTellFacts(heart);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { DatabaseSync } from "node:sqlite";
|
|
3
|
-
import type { BodyEnd, BodyFact, CallFact, InterruptCompletion, KillFact, PauseFact, ResumeCoordinate, SessionFact, StopFact, TurnFact, TurnStartFact, TurnEndFact } from "./facts.js";
|
|
3
|
+
import type { BodyEnd, BodyFact, CallFact, InterruptCompletion, KillFact, PauseFact, ReportFact, ResumeCoordinate, SessionFact, StopFact, TurnFact, TurnStartFact, TurnEndFact } from "./facts.js";
|
|
4
4
|
import { type AgentEvent } from "./activity-schema.js";
|
|
5
5
|
export type SealRow = Readonly<{
|
|
6
6
|
evidence: string;
|
|
@@ -72,7 +72,7 @@ export type TurnRow = Readonly<{
|
|
|
72
72
|
body_sequence: number;
|
|
73
73
|
started_at: string;
|
|
74
74
|
end_sequence: number | null;
|
|
75
|
-
outcome: "answered" | "failed" | "invalid-output" | null;
|
|
75
|
+
outcome: "answered" | "failed" | "invalid-output" | "canceled" | null;
|
|
76
76
|
history_id: string | null;
|
|
77
77
|
session_json: string | null;
|
|
78
78
|
answer: string | null;
|
|
@@ -113,6 +113,14 @@ export declare function decodeSessionRow(row: SessionRow): SessionFact;
|
|
|
113
113
|
export declare function decodeBodyRow(row: BodyRow): BodyFact;
|
|
114
114
|
export declare function decodeTurnRow(row: TurnRow): TurnFact;
|
|
115
115
|
export declare function decodeCallRow(row: CallRow): CallFact;
|
|
116
|
+
export type ReportRow = Readonly<{
|
|
117
|
+
sequence: number;
|
|
118
|
+
body_sequence: number;
|
|
119
|
+
turn_sequence: number;
|
|
120
|
+
text: string;
|
|
121
|
+
at: string;
|
|
122
|
+
}>;
|
|
123
|
+
export declare function decodeReportRow(row: ReportRow): ReportFact;
|
|
116
124
|
export declare function encodeActivityEvent(event: unknown): string;
|
|
117
125
|
export declare function decodeActivityRow(row: ActivityRow): ActivityFact;
|
|
118
126
|
export declare function sealExists(database: DatabaseSync): boolean;
|
|
@@ -130,6 +138,7 @@ export declare function insertBodyFact(database: DatabaseSync, input: Readonly<{
|
|
|
130
138
|
leashTakenAt: string;
|
|
131
139
|
}>): number;
|
|
132
140
|
export declare function insertSessionFact(database: DatabaseSync, input: Omit<SessionFact, "sequence">): number;
|
|
141
|
+
export declare function insertReportFact(database: DatabaseSync, input: Omit<ReportFact, "kind" | "sequence">): ReportFact;
|
|
133
142
|
export declare function insertActivityFact(database: DatabaseSync, input: Readonly<{
|
|
134
143
|
turnSequence: number;
|
|
135
144
|
event: AgentEvent;
|
|
@@ -144,6 +153,7 @@ export declare function insertInterruptBarrier(database: DatabaseSync, input: Re
|
|
|
144
153
|
export declare function completeInterruptBarrier(database: DatabaseSync, expected?: PauseFact, consume?: boolean): InterruptCompletion;
|
|
145
154
|
export declare function stopFact(database: DatabaseSync): StopFact | null;
|
|
146
155
|
export declare function pauseFact(database: DatabaseSync): PauseFact | null;
|
|
156
|
+
export declare function hasBodyHandoffControl(database: DatabaseSync, bodySequence: number): boolean;
|
|
147
157
|
export declare function bodyFact(database: DatabaseSync, sequence: number): BodyFact | null;
|
|
148
158
|
export declare function isTellProtectedByPause(database: DatabaseSync, tellId: string, bodySequence: number): boolean;
|
|
149
159
|
export declare function insertKillFact(database: DatabaseSync, bodySequence: number, at: string): void;
|
|
@@ -164,6 +174,8 @@ export declare function insertTurnStartFact(database: DatabaseSync, input: Reado
|
|
|
164
174
|
schemaJson?: string;
|
|
165
175
|
initiator?: string;
|
|
166
176
|
}>): TurnStartFact;
|
|
177
|
+
export declare function openTurnsForBody(database: DatabaseSync, bodySequence: number): readonly number[];
|
|
178
|
+
export declare function terminalTurnForTell(database: DatabaseSync, tellId: string): TurnFact | null;
|
|
167
179
|
export declare function insertTurnEndFact(database: DatabaseSync, input: Omit<TurnEndFact, "sequence">): TurnEndFact;
|
|
168
180
|
export declare function finishBodyFact(database: DatabaseSync, input: Readonly<{
|
|
169
181
|
sequence: number;
|