@astrosheep/keiyaku 4.6.18 → 4.6.20
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/cli/square-identity.js +15 -15
- 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
package/build/src/akuma/body.js
CHANGED
|
@@ -1,48 +1,22 @@
|
|
|
1
1
|
import { projectAkumaBirth } from "./caller.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { sessionAdmissionSchema } from "./heart/rows.js";
|
|
5
|
-
import { appendFile, stat } from "node:fs/promises";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { abortable, abortableDelay } from "./abort.js";
|
|
2
|
+
import { appendFile } from "node:fs/promises";
|
|
3
|
+
import { abortableDelay } from "./abort.js";
|
|
8
4
|
import { BodySupervisor } from "./body-supervisor.js";
|
|
9
|
-
import { driveTurn
|
|
10
|
-
import { HeldAkumaLeash, BodyControlledError, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, readHeart, readLatestTurnForBody, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readTurnParticipants, readNonterminalRequests, resolvePendingTellDisposition, } from "./heart/index.js";
|
|
11
|
-
import {
|
|
5
|
+
import { driveTurn } from "./turn-drive.js";
|
|
6
|
+
import { HeldAkumaLeash, BodyControlledError, breakBody, cancelBodyTurns, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, readHeart, readLatestTurnForBody, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readTurnParticipants, readNonterminalRequests, resolvePendingTellDisposition, } from "./heart/index.js";
|
|
7
|
+
import { pathsForAkuId, worldRootForAkumaPaths } from "./identity.js";
|
|
12
8
|
import { recordAkumaLineage } from "./lineage-cache.js";
|
|
13
9
|
import { admitCallInitialTell, } from "./call-initial-tell.js";
|
|
14
|
-
import { akumaExecutionEnvironment } from "./providers/execution-environment.js";
|
|
15
10
|
import { emitPluginSignal } from "../plugin/akuma-signals.js";
|
|
16
11
|
import { World } from "../world.js";
|
|
17
12
|
import { resolveProviderExecution } from "./providers/index.js";
|
|
18
13
|
import { clearBodyRequestTransport, settleBodyRequests } from "./request-serve.js";
|
|
19
|
-
import {
|
|
14
|
+
import { observeBodyLaunch, launchEndWake, } from "./body-launch.js";
|
|
20
15
|
const LEASH_RETRY_MS = 100;
|
|
21
|
-
const WAKE_REREAD_MS = 100;
|
|
22
|
-
export const LEASH_HELD_EXIT = 75;
|
|
23
16
|
export { CONTROL_RESPONSE_MS } from "./body-supervisor.js";
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
context.addIssue({ code: "custom", message: "invalid Body birth seed" });
|
|
30
|
-
return z.NEVER;
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
export const bodyLaunchSchema = z
|
|
34
|
-
.object({
|
|
35
|
-
paths: akumaPathsSchema,
|
|
36
|
-
seed: launchSeedSchema.optional(),
|
|
37
|
-
birthSession: sessionAdmissionSchema.optional(),
|
|
38
|
-
initialBody: z.string().optional(),
|
|
39
|
-
initiator: z.string().optional(),
|
|
40
|
-
birthInitiator: z.string().optional(),
|
|
41
|
-
initialSchemaJson: z.string().optional(),
|
|
42
|
-
refuseIfHeld: z.boolean().optional(),
|
|
43
|
-
completion: z.object({ contractId: z.string().optional() }).strict().optional(),
|
|
44
|
-
})
|
|
45
|
-
.strict();
|
|
17
|
+
export function tellWakeRuntime(spawn) {
|
|
18
|
+
return { spawn: async (paths) => await spawn({ paths, refuseIfHeld: true }) };
|
|
19
|
+
}
|
|
46
20
|
function missing(error) {
|
|
47
21
|
return isHeartAbsent(error) || error.code === "ENOENT";
|
|
48
22
|
}
|
|
@@ -51,14 +25,19 @@ async function failOpenBoundTurnsIfPresent(paths, bodySequence, diagnostic, at)
|
|
|
51
25
|
return;
|
|
52
26
|
await failOpenBoundTurns(paths, { bodySequence, diagnostic, completedAt: at });
|
|
53
27
|
}
|
|
54
|
-
async function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
await
|
|
58
|
-
|
|
28
|
+
async function putDownBody(paths, bodySequence, now, turnOutcome, handoff = false) {
|
|
29
|
+
const settled = await cancelBodyTurns(paths, { bodySequence, at: now(), handoff });
|
|
30
|
+
for (const sequence of settled.canceled)
|
|
31
|
+
await turnOutcome?.emit(sequence);
|
|
32
|
+
return settled.disposition ?? undefined;
|
|
59
33
|
}
|
|
60
|
-
async function
|
|
34
|
+
async function putDownIfControlled(paths, supervisor, bodySequence, now, turnOutcome) {
|
|
35
|
+
if ((supervisor.reason === "control" || supervisor.reason === "launch") && (await heartExists(paths)))
|
|
36
|
+
await putDownBody(paths, bodySequence, now, turnOutcome);
|
|
37
|
+
}
|
|
38
|
+
async function takeLeash(paths, refuseIfHeld = false, signal) {
|
|
61
39
|
for (;;) {
|
|
40
|
+
signal?.throwIfAborted();
|
|
62
41
|
try {
|
|
63
42
|
const leash = await HeldAkumaLeash.try(paths);
|
|
64
43
|
if (leash !== null)
|
|
@@ -71,7 +50,7 @@ async function takeLeash(paths, refuseIfHeld = false) {
|
|
|
71
50
|
return "absent";
|
|
72
51
|
throw error;
|
|
73
52
|
}
|
|
74
|
-
await abortableDelay(LEASH_RETRY_MS);
|
|
53
|
+
await abortableDelay(LEASH_RETRY_MS, signal);
|
|
75
54
|
}
|
|
76
55
|
}
|
|
77
56
|
async function bornSoul(launch, leash, now) {
|
|
@@ -90,14 +69,6 @@ async function bornSoul(launch, leash, now) {
|
|
|
90
69
|
}
|
|
91
70
|
return { soul: born, created: result === "born" };
|
|
92
71
|
}
|
|
93
|
-
async function launchCwd(launch) {
|
|
94
|
-
if (launch.seed !== undefined)
|
|
95
|
-
return launch.seed.cwd;
|
|
96
|
-
const soul = (await readHeart(launch.paths)).soul;
|
|
97
|
-
if (soul === null)
|
|
98
|
-
throw new Error("Akuma wake has no born soul");
|
|
99
|
-
return (await turnRecipe(launch.paths, soul)).cwd;
|
|
100
|
-
}
|
|
101
72
|
function parseSchemaAnswerJson(raw) {
|
|
102
73
|
try {
|
|
103
74
|
JSON.parse(raw);
|
|
@@ -114,6 +85,7 @@ async function persistTurn(paths, turnSequence, result, completedAt) {
|
|
|
114
85
|
const turn = await readTurn(paths, turnSequence);
|
|
115
86
|
const schemaJson = turn?.schemaJson;
|
|
116
87
|
if (result.kind === "answered" && result.session !== undefined) {
|
|
88
|
+
let answerJson;
|
|
117
89
|
if (schemaJson !== undefined) {
|
|
118
90
|
const parsed = parseSchemaAnswerJson(result.answer);
|
|
119
91
|
if (parsed.kind === "invalid") {
|
|
@@ -127,18 +99,7 @@ async function persistTurn(paths, turnSequence, result, completedAt) {
|
|
|
127
99
|
await endTurn(paths, { turnSequence, outcome, completedAt });
|
|
128
100
|
return { outcome: "failed", diagnostic: parsed.diagnostic };
|
|
129
101
|
}
|
|
130
|
-
|
|
131
|
-
turnSequence,
|
|
132
|
-
outcome: {
|
|
133
|
-
kind: "answered",
|
|
134
|
-
session: result.session,
|
|
135
|
-
answer: result.answer,
|
|
136
|
-
answerJson: parsed.json,
|
|
137
|
-
...(result.historyId === undefined ? {} : { historyId: result.historyId }),
|
|
138
|
-
},
|
|
139
|
-
completedAt,
|
|
140
|
-
});
|
|
141
|
-
return { outcome: "answered", answer: result.answer };
|
|
102
|
+
answerJson = parsed.json;
|
|
142
103
|
}
|
|
143
104
|
await endTurn(paths, {
|
|
144
105
|
turnSequence,
|
|
@@ -146,11 +107,12 @@ async function persistTurn(paths, turnSequence, result, completedAt) {
|
|
|
146
107
|
kind: "answered",
|
|
147
108
|
session: result.session,
|
|
148
109
|
answer: result.answer,
|
|
110
|
+
...(answerJson === undefined ? {} : { answerJson }),
|
|
149
111
|
...(result.historyId === undefined ? {} : { historyId: result.historyId }),
|
|
150
112
|
},
|
|
151
113
|
completedAt,
|
|
152
114
|
});
|
|
153
|
-
return { outcome: "answered"
|
|
115
|
+
return { outcome: "answered" };
|
|
154
116
|
}
|
|
155
117
|
const diagnostic = result.kind === "answered" ? "Provider answered without a resumable session" : result.diagnostic;
|
|
156
118
|
await endTurn(paths, { turnSequence, outcome: { kind: "failed", diagnostic }, completedAt });
|
|
@@ -168,110 +130,30 @@ export async function recordPluginDiagnostic(paths, occurrence, error) {
|
|
|
168
130
|
/* plugin diagnostic loss never changes Heart truth */
|
|
169
131
|
}
|
|
170
132
|
}
|
|
171
|
-
const DEFAULT_RUNTIME = {
|
|
172
|
-
now: () => new Date().toISOString(),
|
|
173
|
-
spawnChild: spawnAkumaBody,
|
|
174
|
-
externalCommands: {},
|
|
175
|
-
spawnBody: spawnAkumaBody,
|
|
176
|
-
};
|
|
177
|
-
/**
|
|
178
|
-
* The Body spawn executable is the waking process's own executable, resolved
|
|
179
|
-
* at use time. There is no recorded path and no re-resolution chain: when the
|
|
180
|
-
* file is genuinely gone the spawn fails honestly and the wake diagnostic
|
|
181
|
-
* names the missing piece.
|
|
182
|
-
*/
|
|
183
|
-
async function executableMissing(path) {
|
|
184
|
-
try {
|
|
185
|
-
return !(await stat(path)).isFile();
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async function frozenCwdMissing(path) {
|
|
192
|
-
try {
|
|
193
|
-
await stat(path);
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Diagnose an ENOENT Body spawn failure by checking the attempted executable
|
|
202
|
-
* and the frozen execution cwd separately. The message names the genuinely
|
|
203
|
-
* missing piece(s) with the kill-and-call-fresh remedy; a missing cwd likely
|
|
204
|
-
* means its Contract was accepted and the worktree cleaned up. When both
|
|
205
|
-
* pieces still exist the original failure passes through unchanged, as does
|
|
206
|
-
* any non-ENOENT spawn failure.
|
|
207
|
-
*/
|
|
208
|
-
async function diagnoseSpawnEnoent(input, cause) {
|
|
209
|
-
const executable = input.argv[0] ?? process.execPath;
|
|
210
|
-
const cwd = input.cwd;
|
|
211
|
-
const [noExecutable, noCwd] = await Promise.all([executableMissing(executable), frozenCwdMissing(cwd)]);
|
|
212
|
-
if (!noExecutable && !noCwd)
|
|
213
|
-
return cause instanceof Error ? cause : new Error(String(cause));
|
|
214
|
-
const missing = [];
|
|
215
|
-
if (noCwd)
|
|
216
|
-
missing.push(`frozen execution directory ${cwd} no longer exists`);
|
|
217
|
-
if (noExecutable)
|
|
218
|
-
missing.push(`waking process executable ${executable} no longer exists (the host likely removed the running process binary)`);
|
|
219
|
-
const remedies = [];
|
|
220
|
-
if (noCwd)
|
|
221
|
-
remedies.push("a missing execution directory likely means its Contract was accepted and the worktree cleaned up, with deliverables already in Git");
|
|
222
|
-
if (noExecutable)
|
|
223
|
-
remedies.push("a missing executable likely means the host removed the running process binary: re-enter with a fresh CLI process and retry");
|
|
224
|
-
const remedy = `kill this Akuma and call a fresh one; ${remedies.join("; ")}`;
|
|
225
|
-
const error = new Error(`Body spawn failed: ${missing.join(" and ")}; ${remedy}`, { cause });
|
|
226
|
-
error.code = "ENOENT";
|
|
227
|
-
return error;
|
|
228
|
-
}
|
|
229
|
-
export async function bodyProcessInput(launch, bodyModuleUrl = import.meta.url) {
|
|
230
|
-
const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
|
|
231
|
-
const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
|
|
232
|
-
if (actorId === undefined)
|
|
233
|
-
throw new Error("Akuma wake has no born soul");
|
|
234
|
-
const executable = process.execPath;
|
|
235
|
-
const source = bodyModuleUrl.endsWith(".ts");
|
|
236
|
-
const entry = fileURLToPath(new URL(source ? "../akuma-body.ts" : "../akuma-body.js", bodyModuleUrl));
|
|
237
|
-
return {
|
|
238
|
-
argv: source ? [executable, "--import", import.meta.resolve("tsx"), entry, encoded] : [executable, entry, encoded],
|
|
239
|
-
cwd: await launchCwd(launch),
|
|
240
|
-
env: akumaExecutionEnvironment(process.env, {
|
|
241
|
-
KEIYAKU_ACTOR_ID: actorId,
|
|
242
|
-
KEIYAKU_ACTOR_WORLD: worldRootForAkumaPaths(launch.paths),
|
|
243
|
-
}),
|
|
244
|
-
log: launch.paths.log,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
133
|
async function prepareBodyExecution(launch, leash, runtime, adapter) {
|
|
134
|
+
runtime.signal?.throwIfAborted();
|
|
248
135
|
const birth = await bornSoul(launch, leash, runtime.now());
|
|
136
|
+
runtime.signal?.throwIfAborted();
|
|
249
137
|
if (birth === null)
|
|
250
138
|
return null;
|
|
251
139
|
// The immutable caller edge refreshes after Soul durability, best-effort.
|
|
252
140
|
if (birth.created)
|
|
253
141
|
void recordAkumaLineage(runtime.world, birth.soul);
|
|
254
142
|
const { soul } = birth;
|
|
255
|
-
if (!(await prepareBodyStart(launch.paths, leash)))
|
|
143
|
+
if (launch.handoffFromBodySequence === undefined && !(await prepareBodyStart(launch.paths, leash)))
|
|
256
144
|
return null;
|
|
257
145
|
if (launch.seed === undefined && launch.initialBody === undefined) {
|
|
258
146
|
const [heart, requests] = await Promise.all([readHeart(launch.paths), readNonterminalRequests(launch.paths)]);
|
|
259
147
|
if (heart.pending.length === 0 && requests.length === 0)
|
|
260
148
|
return null;
|
|
261
149
|
}
|
|
150
|
+
runtime.signal?.throwIfAborted();
|
|
262
151
|
const selected = adapter ?? (await resolveProviderExecution(soul.provider)).adapter;
|
|
152
|
+
runtime.signal?.throwIfAborted();
|
|
263
153
|
return { ...birth, adapter: selected };
|
|
264
154
|
}
|
|
265
155
|
async function runPreparedBody(input) {
|
|
266
|
-
|
|
267
|
-
try {
|
|
268
|
-
if (await recoverBodyRequests(input))
|
|
269
|
-
pendingTellDisposition = await runBodyTurns(input);
|
|
270
|
-
}
|
|
271
|
-
finally {
|
|
272
|
-
await input.supervisor.close();
|
|
273
|
-
}
|
|
274
|
-
return pendingTellDisposition;
|
|
156
|
+
return (await recoverBodyRequests(input)) ? await runBodyTurns(input) : undefined;
|
|
275
157
|
}
|
|
276
158
|
async function preserveBodyFailure(paths, leash, error, at) {
|
|
277
159
|
if (!(await heartExists(paths)))
|
|
@@ -314,10 +196,25 @@ async function recoverBodyRequests(input) {
|
|
|
314
196
|
if (!supervisor.signal.aborted)
|
|
315
197
|
throw error;
|
|
316
198
|
await clearBodyRequestTransport(launch.paths);
|
|
317
|
-
await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now);
|
|
199
|
+
await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now, input.turnOutcome);
|
|
318
200
|
}
|
|
319
201
|
return false;
|
|
320
202
|
}
|
|
203
|
+
async function emitBodyBirth(launch, soul, world, bodySequence) {
|
|
204
|
+
await emitPluginSignal({
|
|
205
|
+
world,
|
|
206
|
+
reportDiagnostic: (message) => {
|
|
207
|
+
void recordPluginDiagnostic(launch.paths, "birth", message);
|
|
208
|
+
},
|
|
209
|
+
}, {
|
|
210
|
+
kind: "akuma.born",
|
|
211
|
+
akumaId: soul.id,
|
|
212
|
+
bodySequence,
|
|
213
|
+
...projectAkumaBirth(soul),
|
|
214
|
+
...(launch.birthInitiator === undefined ? {} : { initiator: launch.birthInitiator }),
|
|
215
|
+
...(launch.completion?.contractId === undefined ? {} : { contractId: launch.completion.contractId }),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
321
218
|
function turnStartedEmitter(launch, soul, world) {
|
|
322
219
|
const reportDiagnostic = (message) => {
|
|
323
220
|
void recordPluginDiagnostic(launch.paths, "turn started", message);
|
|
@@ -346,11 +243,12 @@ function turnOutcomeEmitter(launch, soul, world) {
|
|
|
346
243
|
void recordPluginDiagnostic(launch.paths, "turn outcome", message);
|
|
347
244
|
};
|
|
348
245
|
return {
|
|
349
|
-
async emit(turnSequence
|
|
246
|
+
async emit(turnSequence) {
|
|
350
247
|
try {
|
|
351
248
|
const turn = await readTurn(launch.paths, turnSequence);
|
|
352
|
-
if (turn ===
|
|
353
|
-
throw new Error(`Committed Turn ${turnSequence} is missing from Heart`);
|
|
249
|
+
if (turn?.end === undefined)
|
|
250
|
+
throw new Error(`Committed Turn ${turnSequence} outcome is missing from Heart`);
|
|
251
|
+
const outcome = turn.end.outcome;
|
|
354
252
|
const participants = await readTurnParticipants(launch.paths, turnSequence);
|
|
355
253
|
await emitPluginSignal({ world, reportDiagnostic }, {
|
|
356
254
|
kind: "akuma.turn-outcome",
|
|
@@ -359,9 +257,11 @@ function turnOutcomeEmitter(launch, soul, world) {
|
|
|
359
257
|
turnSequence,
|
|
360
258
|
...(turn.initiator === undefined ? {} : { initiator: turn.initiator }),
|
|
361
259
|
...(participants.length === 0 ? {} : { participants }),
|
|
362
|
-
outcome: outcome.
|
|
260
|
+
outcome: outcome.kind === "answered"
|
|
363
261
|
? { kind: "answered", text: outcome.answer }
|
|
364
|
-
:
|
|
262
|
+
: outcome.kind === "canceled"
|
|
263
|
+
? { kind: "canceled" }
|
|
264
|
+
: { kind: "failed", reason: outcome.diagnostic },
|
|
365
265
|
...(launch.completion?.contractId === undefined ? {} : { contractId: launch.completion.contractId }),
|
|
366
266
|
});
|
|
367
267
|
}
|
|
@@ -405,7 +305,11 @@ async function admitInitialCallTell(runtime, input) {
|
|
|
405
305
|
signal: input.signal,
|
|
406
306
|
now: runtime.now,
|
|
407
307
|
wake: async (tell) => await wakeRecordedTell(paths, tell.id, {
|
|
408
|
-
spawn: async (tellPaths) => await
|
|
308
|
+
spawn: async (tellPaths) => await runtime.requestSpawn({
|
|
309
|
+
paths: tellPaths,
|
|
310
|
+
refuseIfHeld: true,
|
|
311
|
+
...(input.contractId === undefined ? {} : { completion: { contractId: input.contractId } }),
|
|
312
|
+
}),
|
|
409
313
|
schedule: abortableDelay,
|
|
410
314
|
}, input.signal),
|
|
411
315
|
});
|
|
@@ -417,7 +321,7 @@ async function runBodyTurns(input) {
|
|
|
417
321
|
const control = supervisor.current();
|
|
418
322
|
const launchTells = drainPendingTells(control.pending, await readOpenBoundTurns(launch.paths, bodySequence), control.pause, bodySequence);
|
|
419
323
|
if (supervisor.signal.aborted) {
|
|
420
|
-
await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now);
|
|
324
|
+
await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now, turnOutcome);
|
|
421
325
|
return;
|
|
422
326
|
}
|
|
423
327
|
if (initial === undefined && launchTells.length === 0) {
|
|
@@ -426,6 +330,7 @@ async function runBodyTurns(input) {
|
|
|
426
330
|
return;
|
|
427
331
|
if (finished.kind === "controlled") {
|
|
428
332
|
supervisor.cancel("control");
|
|
333
|
+
await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now, turnOutcome);
|
|
429
334
|
return;
|
|
430
335
|
}
|
|
431
336
|
continue;
|
|
@@ -437,7 +342,7 @@ async function runBodyTurns(input) {
|
|
|
437
342
|
adapter,
|
|
438
343
|
bodySequence,
|
|
439
344
|
supervisor,
|
|
440
|
-
runtimeSpawn: runtime.
|
|
345
|
+
runtimeSpawn: runtime.requestSpawn,
|
|
441
346
|
admitInitialTell: async (admission) => await admitInitialCallTell(runtime, admission),
|
|
442
347
|
body: initial ?? "",
|
|
443
348
|
...(initial === undefined ? {} : { call: initial }),
|
|
@@ -446,6 +351,7 @@ async function runBodyTurns(input) {
|
|
|
446
351
|
...(schema === undefined ? {} : { schemaJson: schema }),
|
|
447
352
|
world: runtime.world,
|
|
448
353
|
externalCommands: runtime.externalCommands,
|
|
354
|
+
...(runtime.executionAssociation === undefined ? {} : { executionAssociation: runtime.executionAssociation }),
|
|
449
355
|
now: runtime.now,
|
|
450
356
|
turnStarted,
|
|
451
357
|
});
|
|
@@ -454,18 +360,18 @@ async function runBodyTurns(input) {
|
|
|
454
360
|
return;
|
|
455
361
|
}
|
|
456
362
|
if (result.kind === "stopped") {
|
|
457
|
-
await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now);
|
|
363
|
+
await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now, turnOutcome);
|
|
458
364
|
return;
|
|
459
365
|
}
|
|
460
366
|
if (result.kind === "handoff")
|
|
461
|
-
return
|
|
367
|
+
return await putDownBody(launch.paths, bodySequence, runtime.now, turnOutcome, true);
|
|
462
368
|
if (result.kind === "resume-unsupported") {
|
|
463
369
|
await failOpenBoundTurnsIfPresent(launch.paths, bodySequence, "resume-unsupported with open bound Turns", runtime.now());
|
|
464
370
|
await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
|
|
465
371
|
return;
|
|
466
372
|
}
|
|
467
373
|
const outcome = await persistTurn(launch.paths, result.turnSequence, result, runtime.now());
|
|
468
|
-
await turnOutcome.emit(result.turnSequence
|
|
374
|
+
await turnOutcome.emit(result.turnSequence);
|
|
469
375
|
if (outcome.outcome === "failed") {
|
|
470
376
|
await failOpenBoundTurnsIfPresent(launch.paths, bodySequence, outcome.diagnostic, runtime.now());
|
|
471
377
|
await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
|
|
@@ -493,56 +399,27 @@ async function settleUndeliveredDisposition(paths, disposition, error) {
|
|
|
493
399
|
async function consumeProvenDisposition(paths, disposition) {
|
|
494
400
|
return await resolvePendingTellDisposition(paths, disposition.bodySequence, new Date().toISOString());
|
|
495
401
|
}
|
|
496
|
-
/**
|
|
497
|
-
* Wait for Heart custody proof of the frozen Tell-id snapshot, or for the
|
|
498
|
-
* spawned child to exit without that proof. Sequence growth, spawn resolution,
|
|
499
|
-
* and an unqualified held leash never consume the disposition.
|
|
500
|
-
*/
|
|
501
|
-
async function awaitDispositionCustody(paths, disposition, child, schedule) {
|
|
502
|
-
for (;;) {
|
|
503
|
-
if (await consumeProvenDisposition(paths, disposition)) {
|
|
504
|
-
child.release();
|
|
505
|
-
return { kind: "proven" };
|
|
506
|
-
}
|
|
507
|
-
const timerController = new AbortController();
|
|
508
|
-
const timer = schedule(WAKE_REREAD_MS, timerController.signal).then(() => ({ kind: "timer" }));
|
|
509
|
-
let winner;
|
|
510
|
-
try {
|
|
511
|
-
winner = await Promise.race([child.exited.then((exit) => ({ kind: "exited", exit })), timer]);
|
|
512
|
-
}
|
|
513
|
-
catch (error) {
|
|
514
|
-
timerController.abort();
|
|
515
|
-
await timer.catch(() => undefined);
|
|
516
|
-
throw error;
|
|
517
|
-
}
|
|
518
|
-
if (winner.kind === "timer")
|
|
519
|
-
continue;
|
|
520
|
-
timerController.abort();
|
|
521
|
-
await timer.catch(() => undefined);
|
|
522
|
-
if (await consumeProvenDisposition(paths, disposition)) {
|
|
523
|
-
child.release();
|
|
524
|
-
return { kind: "proven" };
|
|
525
|
-
}
|
|
526
|
-
return winner.exit.code === LEASH_HELD_EXIT ? { kind: "held" } : failedChild(winner.exit);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
402
|
async function resolveDecidedPendingTellDisposition(paths, disposition, spawn) {
|
|
530
403
|
try {
|
|
531
404
|
if (await consumeProvenDisposition(paths, disposition))
|
|
532
405
|
return;
|
|
533
|
-
const
|
|
534
|
-
|
|
406
|
+
const child = await spawn({ paths, refuseIfHeld: true, handoffFromBodySequence: disposition.bodySequence });
|
|
407
|
+
const result = await observeBodyLaunch(child, async () => (await consumeProvenDisposition(paths, disposition)) ? true : null);
|
|
408
|
+
if (result.kind === "observed") {
|
|
409
|
+
child.release();
|
|
535
410
|
return;
|
|
411
|
+
}
|
|
412
|
+
const wake = launchEndWake(result.end);
|
|
536
413
|
// Predecessor still holds the leash: leave the Heart disposition open.
|
|
537
414
|
if (wake.kind === "held")
|
|
538
415
|
return;
|
|
539
|
-
await settleUndeliveredDisposition(paths, disposition, wake.
|
|
416
|
+
await settleUndeliveredDisposition(paths, disposition, wake.diagnostic);
|
|
540
417
|
}
|
|
541
418
|
catch (error) {
|
|
542
419
|
await settleUndeliveredDisposition(paths, disposition, error);
|
|
543
420
|
}
|
|
544
421
|
}
|
|
545
|
-
export async function handoffPendingTells(paths, spawn
|
|
422
|
+
export async function handoffPendingTells(paths, spawn) {
|
|
546
423
|
const open = await readOpenPendingTellDisposition(paths);
|
|
547
424
|
if (open !== null) {
|
|
548
425
|
await resolveDecidedPendingTellDisposition(paths, open, spawn);
|
|
@@ -565,29 +442,30 @@ export async function handoffPendingTells(paths, spawn = spawnAkumaBody) {
|
|
|
565
442
|
return;
|
|
566
443
|
await resolveDecidedPendingTellDisposition(paths, decided, spawn);
|
|
567
444
|
}
|
|
568
|
-
async function finishBody(paths, bodySequence,
|
|
445
|
+
async function finishBody(paths, bodySequence, emitBodyEnd, now) {
|
|
569
446
|
if (bodySequence === undefined || !(await heartExists(paths)))
|
|
570
|
-
return
|
|
447
|
+
return null;
|
|
571
448
|
const body = (await readHeart(paths)).latestBody;
|
|
572
|
-
if (body?.sequence === bodySequence && (body.
|
|
449
|
+
if (body?.sequence === bodySequence && (body.hung !== undefined || body.end === "broke-off"))
|
|
573
450
|
await failOpenBoundTurns(paths, {
|
|
574
451
|
bodySequence,
|
|
575
452
|
diagnostic: body.hung?.diagnostic ?? `Body ${body.end ?? "ended"} with open bound Turns`,
|
|
576
453
|
completedAt: now(),
|
|
577
454
|
});
|
|
578
|
-
// A controlled put-down preserves pending Tells for a later ordinary wake.
|
|
579
|
-
// Only provider handoff creates this Body's frozen disposition snapshot.
|
|
580
|
-
const decided = handoff ? await decidePendingTellDisposition(paths, { bodySequence, at: now(), handoff }) : null;
|
|
581
455
|
const finalBody = (await readHeart(paths)).latestBody;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
456
|
+
if (finalBody?.sequence !== bodySequence)
|
|
457
|
+
return {};
|
|
458
|
+
const hung = finalBody.hung !== undefined;
|
|
459
|
+
const notification = emitBodyEnd && (finalBody.end || hung)
|
|
460
|
+
? emitBodyEnd(bodySequence, hung ? "hung" : finalBody.end, finalBody.hung?.diagnostic)
|
|
461
|
+
: undefined;
|
|
462
|
+
return { ...(notification === undefined ? {} : { notification }), ...(hung ? { hung: true } : {}) };
|
|
463
|
+
}
|
|
464
|
+
export async function driveAkumaBody(launch, adapter, runtimeInput) {
|
|
465
|
+
runtimeInput.signal?.throwIfAborted();
|
|
588
466
|
const world = runtimeInput.world ?? (await World.at(worldRootForAkumaPaths(launch.paths)));
|
|
589
|
-
const runtime = {
|
|
590
|
-
const acquired = await takeLeash(launch.paths, launch.refuseIfHeld);
|
|
467
|
+
const runtime = { now: () => new Date().toISOString(), externalCommands: {}, ...runtimeInput, world };
|
|
468
|
+
const acquired = await takeLeash(launch.paths, launch.refuseIfHeld, runtime.signal);
|
|
591
469
|
if (acquired === "held")
|
|
592
470
|
return "held";
|
|
593
471
|
if (acquired === "absent")
|
|
@@ -596,8 +474,9 @@ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
|
|
|
596
474
|
let pendingTellDisposition = undefined;
|
|
597
475
|
let bodySequence;
|
|
598
476
|
let emitBodyEnd;
|
|
599
|
-
let emitTurnStarted;
|
|
600
477
|
let emitTurnOutcome;
|
|
478
|
+
let supervisor;
|
|
479
|
+
let hung = false;
|
|
601
480
|
try {
|
|
602
481
|
const prepared = await prepareBodyExecution(launch, leash, runtime, adapter);
|
|
603
482
|
if (prepared === null) {
|
|
@@ -606,26 +485,19 @@ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
|
|
|
606
485
|
return;
|
|
607
486
|
}
|
|
608
487
|
emitBodyEnd = bodyEndEmitter(launch, prepared.soul, world);
|
|
609
|
-
emitTurnStarted = turnStartedEmitter(launch, prepared.soul, world);
|
|
488
|
+
const emitTurnStarted = turnStartedEmitter(launch, prepared.soul, world);
|
|
610
489
|
emitTurnOutcome = turnOutcomeEmitter(launch, prepared.soul, world);
|
|
611
|
-
|
|
490
|
+
runtime.signal?.throwIfAborted();
|
|
491
|
+
const body = await leash.recordBody(launch.paths, {
|
|
492
|
+
leashTakenAt: runtime.now(),
|
|
493
|
+
...(launch.handoffFromBodySequence === undefined
|
|
494
|
+
? {}
|
|
495
|
+
: { handoffFromBodySequence: launch.handoffFromBodySequence }),
|
|
496
|
+
});
|
|
612
497
|
bodySequence = body.sequence;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
reportDiagnostic: (message) => {
|
|
617
|
-
void recordPluginDiagnostic(launch.paths, "birth", message);
|
|
618
|
-
},
|
|
619
|
-
}, {
|
|
620
|
-
kind: "akuma.born",
|
|
621
|
-
akumaId: prepared.soul.id,
|
|
622
|
-
bodySequence: body.sequence,
|
|
623
|
-
...projectAkumaBirth(prepared.soul),
|
|
624
|
-
...(launch.birthInitiator === undefined ? {} : { initiator: launch.birthInitiator }),
|
|
625
|
-
...(launch.completion?.contractId === undefined ? {} : { contractId: launch.completion.contractId }),
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
const supervisor = await BodySupervisor.open(launch.paths, body.sequence, leash);
|
|
498
|
+
supervisor = await BodySupervisor.open(launch.paths, body.sequence, leash, runtime.signal, runtime.forceSignal);
|
|
499
|
+
if (prepared.created)
|
|
500
|
+
await emitBodyBirth(launch, prepared.soul, world, body.sequence);
|
|
629
501
|
pendingTellDisposition = await runPreparedBody({
|
|
630
502
|
launch,
|
|
631
503
|
soul: prepared.soul,
|
|
@@ -640,45 +512,33 @@ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
|
|
|
640
512
|
catch (error) {
|
|
641
513
|
if (error instanceof BodyControlledError) {
|
|
642
514
|
if (bodySequence !== undefined)
|
|
643
|
-
await
|
|
515
|
+
await putDownBody(launch.paths, bodySequence, runtime.now, emitTurnOutcome);
|
|
644
516
|
return "held";
|
|
645
517
|
}
|
|
646
518
|
await preserveBodyFailure(launch.paths, leash, error, runtime.now());
|
|
647
519
|
throw error;
|
|
648
520
|
}
|
|
649
521
|
finally {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
await
|
|
654
|
-
|
|
522
|
+
let finished;
|
|
523
|
+
try {
|
|
524
|
+
await supervisor?.close();
|
|
525
|
+
finished = await finishBody(launch.paths, bodySequence, emitBodyEnd, runtime.now);
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
leash.release();
|
|
529
|
+
}
|
|
530
|
+
if (finished !== null && pendingTellDisposition !== undefined)
|
|
531
|
+
await resolveDecidedPendingTellDisposition(launch.paths, pendingTellDisposition, runtime.spawn);
|
|
532
|
+
if (finished?.notification !== undefined)
|
|
655
533
|
await finished.notification;
|
|
534
|
+
hung = finished?.hung === true;
|
|
656
535
|
}
|
|
536
|
+
if (hung)
|
|
537
|
+
return "hung";
|
|
657
538
|
}
|
|
658
|
-
export async function spawnAkumaBody(launch) {
|
|
659
|
-
const input = await bodyProcessInput(launch, import.meta.url);
|
|
660
|
-
try {
|
|
661
|
-
return await spawnDetachedProcess(input);
|
|
662
|
-
}
|
|
663
|
-
catch (error) {
|
|
664
|
-
if (error?.code !== "ENOENT")
|
|
665
|
-
throw error;
|
|
666
|
-
throw await diagnoseSpawnEnoent(input, error);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
export async function runAkumaBody(launch, world, externalCommands) {
|
|
670
|
-
return await driveAkumaBody(launch, undefined, { world, externalCommands });
|
|
671
|
-
}
|
|
672
|
-
const DIRECT_TELL_WAKE = {
|
|
673
|
-
spawn: async (paths) => await spawnAkumaBody({ paths, refuseIfHeld: true }),
|
|
674
|
-
schedule: abortableDelay,
|
|
675
|
-
};
|
|
676
539
|
function diagnostic(error) {
|
|
677
540
|
return error instanceof Error ? error.message : String(error);
|
|
678
541
|
}
|
|
679
|
-
function preAdmissionDiagnostic(exit) {
|
|
680
|
-
return exit.code === null ? `pre-admission signal ${exit.signal ?? "unknown"}` : `pre-admission exit ${exit.code}`;
|
|
681
|
-
}
|
|
682
542
|
async function settledWake(paths, tellId, after) {
|
|
683
543
|
const heart = await readHeart(paths);
|
|
684
544
|
if (tellId !== undefined && !heart.pending.some((tell) => tell.id === tellId))
|
|
@@ -688,46 +548,14 @@ async function settledWake(paths, tellId, after) {
|
|
|
688
548
|
}
|
|
689
549
|
return null;
|
|
690
550
|
}
|
|
691
|
-
function failedChild(exit) {
|
|
692
|
-
return {
|
|
693
|
-
kind: "failed",
|
|
694
|
-
diagnostic: preAdmissionDiagnostic(exit),
|
|
695
|
-
child: { code: exit.code, signal: exit.signal, log: exit.log },
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
551
|
async function awaitWake(paths, input) {
|
|
699
552
|
const { tellId, after, child, schedule, signal } = input;
|
|
700
553
|
try {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
return settled;
|
|
707
|
-
}
|
|
708
|
-
const timerController = new AbortController();
|
|
709
|
-
const timer = schedule(WAKE_REREAD_MS, timerController.signal).then(() => ({ kind: "timer" }));
|
|
710
|
-
let winner;
|
|
711
|
-
try {
|
|
712
|
-
const waited = Promise.race([child.exited.then((exit) => ({ kind: "exited", exit })), timer]);
|
|
713
|
-
winner = signal === undefined ? await waited : await abortable(waited, signal);
|
|
714
|
-
}
|
|
715
|
-
catch (error) {
|
|
716
|
-
timerController.abort();
|
|
717
|
-
await timer.catch(() => undefined);
|
|
718
|
-
throw error;
|
|
719
|
-
}
|
|
720
|
-
if (winner.kind === "timer")
|
|
721
|
-
continue;
|
|
722
|
-
timerController.abort();
|
|
723
|
-
await timer.catch(() => undefined);
|
|
724
|
-
const final = await settledWake(paths, tellId, after);
|
|
725
|
-
if (final !== null) {
|
|
726
|
-
child.release();
|
|
727
|
-
return final;
|
|
728
|
-
}
|
|
729
|
-
return winner.exit.code === LEASH_HELD_EXIT ? { kind: "held" } : failedChild(winner.exit);
|
|
730
|
-
}
|
|
554
|
+
const result = await observeBodyLaunch(child, async () => await settledWake(paths, tellId, after), schedule, signal);
|
|
555
|
+
if (result.kind === "completed")
|
|
556
|
+
return launchEndWake(result.end);
|
|
557
|
+
child.release();
|
|
558
|
+
return result.value;
|
|
731
559
|
}
|
|
732
560
|
catch (error) {
|
|
733
561
|
child.release();
|
|
@@ -755,7 +583,7 @@ async function wakePendingTells(paths, tellId, runtime, signal) {
|
|
|
755
583
|
return { kind: "failed", diagnostic: diagnostic(error) };
|
|
756
584
|
}
|
|
757
585
|
}
|
|
758
|
-
export async function wakeRecordedTell(paths, tellId, runtime
|
|
586
|
+
export async function wakeRecordedTell(paths, tellId, runtime, signal) {
|
|
759
587
|
const wake = (await wakePendingTells(paths, tellId, runtime, signal));
|
|
760
588
|
const tell = await readTell(paths, tellId);
|
|
761
589
|
if (tell === null)
|