@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,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { providerOptionsSchema, withoutModel } from "../provider-recipe.js";
|
|
3
3
|
import { resumeCoordinateSchema } from "../coordinate.js";
|
|
4
|
-
import { HeartAuthorityCorruptionError } from "./facts.js";
|
|
4
|
+
import { HeartAuthorityCorruptionError, reportFactSchema } from "./facts.js";
|
|
5
5
|
import { decodeResumeCoordinate, encodeResumeCoordinate as encodeCoordinate } from "../coordinate.js";
|
|
6
6
|
import { agentEventSchema } from "./activity-schema.js";
|
|
7
7
|
const sessionText = z.string().refine((value) => value.trim() !== "");
|
|
@@ -80,6 +80,8 @@ function decodeTurnOutcome(row) {
|
|
|
80
80
|
...(row.session_json === null ? {} : { session: resumeCoordinate(parsed(row.session_json)) }),
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
+
if (row.outcome === "canceled")
|
|
84
|
+
return { kind: "canceled" };
|
|
83
85
|
return { kind: "failed", diagnostic: row.diagnostic };
|
|
84
86
|
}
|
|
85
87
|
export function decodeTurnRow(row) {
|
|
@@ -105,6 +107,16 @@ export function decodeTurnRow(row) {
|
|
|
105
107
|
export function decodeCallRow(row) {
|
|
106
108
|
return { kind: "call", sequence: row.sequence, turnSequence: row.turn_sequence, body: row.body, at: row.at };
|
|
107
109
|
}
|
|
110
|
+
export function decodeReportRow(row) {
|
|
111
|
+
return reportFactSchema.parse({
|
|
112
|
+
kind: "report",
|
|
113
|
+
sequence: row.sequence,
|
|
114
|
+
bodySequence: row.body_sequence,
|
|
115
|
+
turnSequence: row.turn_sequence,
|
|
116
|
+
text: row.text,
|
|
117
|
+
at: row.at,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
108
120
|
export function encodeActivityEvent(event) {
|
|
109
121
|
return json(agentEventSchema.parse(event));
|
|
110
122
|
}
|
|
@@ -149,6 +161,14 @@ export function insertSessionFact(database, input) {
|
|
|
149
161
|
.run(...encodeSessionRow(input));
|
|
150
162
|
return Number(result.lastInsertRowid);
|
|
151
163
|
}
|
|
164
|
+
export function insertReportFact(database, input) {
|
|
165
|
+
const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('report')").run().lastInsertRowid);
|
|
166
|
+
const fact = reportFactSchema.parse({ kind: "report", sequence, ...input });
|
|
167
|
+
database
|
|
168
|
+
.prepare("INSERT INTO reports(sequence, body_sequence, turn_sequence, text, at) VALUES (?, ?, ?, ?, ?)")
|
|
169
|
+
.run(sequence, input.bodySequence, input.turnSequence, input.text, input.at);
|
|
170
|
+
return fact;
|
|
171
|
+
}
|
|
152
172
|
export function insertActivityFact(database, input) {
|
|
153
173
|
const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('activity')").run().lastInsertRowid);
|
|
154
174
|
database
|
|
@@ -231,6 +251,11 @@ export function pauseFact(database) {
|
|
|
231
251
|
...(value.frontierTellId === undefined ? {} : { frontierTellId: value.frontierTellId }),
|
|
232
252
|
};
|
|
233
253
|
}
|
|
254
|
+
export function hasBodyHandoffControl(database, bodySequence) {
|
|
255
|
+
return (killFactForBody(database, bodySequence) !== null ||
|
|
256
|
+
stopFact(database)?.bodySequence === bodySequence ||
|
|
257
|
+
pauseFact(database)?.bodySequence === bodySequence);
|
|
258
|
+
}
|
|
234
259
|
export function bodyFact(database, sequence) {
|
|
235
260
|
const row = database
|
|
236
261
|
.prepare(`SELECT sequence, leash_taken_at, hung_diagnostic, hung_at, end, ended_at FROM bodies WHERE sequence = ?`)
|
|
@@ -287,6 +312,20 @@ export function insertTurnStartFact(database, input) {
|
|
|
287
312
|
...(input.schemaJson === undefined ? {} : { schemaJson: input.schemaJson }),
|
|
288
313
|
};
|
|
289
314
|
}
|
|
315
|
+
export function openTurnsForBody(database, bodySequence) {
|
|
316
|
+
return database
|
|
317
|
+
.prepare("SELECT sequence FROM turns WHERE body_sequence = ? AND end_sequence IS NULL ORDER BY sequence")
|
|
318
|
+
.all(bodySequence)
|
|
319
|
+
.map((row) => Number(row.sequence));
|
|
320
|
+
}
|
|
321
|
+
export function terminalTurnForTell(database, tellId) {
|
|
322
|
+
const row = database
|
|
323
|
+
.prepare(`SELECT t.sequence FROM tell_bindings b
|
|
324
|
+
JOIN turns t ON t.sequence = b.turn_sequence
|
|
325
|
+
WHERE b.tell_id = ? AND t.end_sequence IS NOT NULL ORDER BY b.sequence LIMIT 1`)
|
|
326
|
+
.get(tellId);
|
|
327
|
+
return row === undefined ? null : turnFact(database, Number(row.sequence));
|
|
328
|
+
}
|
|
290
329
|
export function insertTurnEndFact(database, input) {
|
|
291
330
|
const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('turn-end')").run().lastInsertRowid);
|
|
292
331
|
const result = input.outcome.kind === "answered"
|
|
@@ -300,10 +339,15 @@ export function insertTurnEndFact(database, input) {
|
|
|
300
339
|
session_json = ?, answer = ?, diagnostic = ?, completed_at = ?
|
|
301
340
|
WHERE sequence = ? AND end_sequence IS NULL`)
|
|
302
341
|
.run(sequence, input.outcome.historyId ?? null, input.outcome.session === undefined ? null : encodeResumeCoordinate(input.outcome.session), input.outcome.answer, input.outcome.diagnostic, input.completedAt, input.turnSequence)
|
|
303
|
-
:
|
|
304
|
-
|
|
342
|
+
: input.outcome.kind === "canceled"
|
|
343
|
+
? database
|
|
344
|
+
.prepare(`UPDATE turns SET end_sequence = ?, outcome = 'canceled', completed_at = ?
|
|
345
|
+
WHERE sequence = ? AND end_sequence IS NULL`)
|
|
346
|
+
.run(sequence, input.completedAt, input.turnSequence)
|
|
347
|
+
: database
|
|
348
|
+
.prepare(`UPDATE turns SET end_sequence = ?, outcome = 'failed', diagnostic = ?, completed_at = ?
|
|
305
349
|
WHERE sequence = ? AND end_sequence IS NULL`)
|
|
306
|
-
|
|
350
|
+
.run(sequence, input.outcome.diagnostic, input.completedAt, input.turnSequence);
|
|
307
351
|
if (result.changes !== 1)
|
|
308
352
|
throw new Error(`Akuma Turn ${input.turnSequence} is not open`);
|
|
309
353
|
return { ...input, kind: "turn-end", sequence };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
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
|
+
export declare function heartSchemaVersion(database: DatabaseSync): number | undefined;
|
|
4
5
|
export declare function heartSchemaIsCurrent(database: DatabaseSync): boolean;
|
|
5
|
-
|
|
6
|
+
/** Called only under the open boundary's write transaction, with foreign keys disabled. */
|
|
7
|
+
export declare function upgradeHeartSchema(database: DatabaseSync): void;
|
|
8
|
+
export declare const HEART_SCHEMA: string;
|
|
6
9
|
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 = 31;
|
|
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();
|
|
@@ -11,50 +11,33 @@ export function assertHeartSchemaVersion(database) {
|
|
|
11
11
|
export function assertLeashSchemaVersion(database) {
|
|
12
12
|
assertSchemaVersion(database, "leash_schema", LEASH_SCHEMA_VERSION);
|
|
13
13
|
}
|
|
14
|
+
export function heartSchemaVersion(database) {
|
|
15
|
+
return database.prepare("SELECT version FROM akuma_schema WHERE singleton = 1").get()?.version;
|
|
16
|
+
}
|
|
14
17
|
export function heartSchemaIsCurrent(database) {
|
|
15
18
|
try {
|
|
16
|
-
|
|
17
|
-
return row?.version === HEART_SCHEMA_VERSION;
|
|
19
|
+
return heartSchemaVersion(database) === HEART_SCHEMA_VERSION;
|
|
18
20
|
}
|
|
19
21
|
catch {
|
|
20
22
|
return false;
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
const VERSION_SCHEMA = `
|
|
24
26
|
CREATE TABLE IF NOT EXISTS akuma_schema (
|
|
25
27
|
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
26
28
|
version INTEGER NOT NULL CHECK (version = ${HEART_SCHEMA_VERSION})
|
|
27
29
|
) STRICT;
|
|
28
30
|
INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, ${HEART_SCHEMA_VERSION});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
CREATE TABLE IF NOT EXISTS bodies (
|
|
34
|
-
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
35
|
-
leash_taken_at TEXT NOT NULL,
|
|
36
|
-
hung_diagnostic TEXT,
|
|
37
|
-
hung_at TEXT,
|
|
38
|
-
end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),
|
|
39
|
-
ended_at TEXT,
|
|
40
|
-
CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL)
|
|
41
|
-
OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL))
|
|
42
|
-
) STRICT;
|
|
43
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
44
|
-
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
45
|
-
provider TEXT NOT NULL,
|
|
46
|
-
coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),
|
|
47
|
-
cwd TEXT NOT NULL,
|
|
48
|
-
options_json TEXT NOT NULL CHECK (json_valid(options_json)),
|
|
49
|
-
admitted_at TEXT NOT NULL
|
|
50
|
-
) STRICT;
|
|
51
|
-
CREATE TABLE IF NOT EXISTS turns (
|
|
31
|
+
`;
|
|
32
|
+
function turnSchema(table) {
|
|
33
|
+
return `
|
|
34
|
+
CREATE TABLE ${table === "turns" ? "IF NOT EXISTS " : ""}${table} (
|
|
52
35
|
sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,
|
|
53
36
|
body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),
|
|
54
37
|
started_at TEXT NOT NULL,
|
|
55
38
|
initiator TEXT,
|
|
56
39
|
end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,
|
|
57
|
-
outcome TEXT CHECK (outcome IN ('answered', 'failed', 'invalid-output')),
|
|
40
|
+
outcome TEXT CHECK (outcome IN ('answered', 'failed', 'invalid-output', 'canceled')),
|
|
58
41
|
history_id TEXT UNIQUE,
|
|
59
42
|
session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),
|
|
60
43
|
answer TEXT,
|
|
@@ -69,14 +52,88 @@ export const HEART_SCHEMA = `
|
|
|
69
52
|
AND answer_json IS NULL AND diagnostic IS NOT NULL)
|
|
70
53
|
OR (outcome = 'invalid-output' AND answer IS NOT NULL AND answer_json IS NULL
|
|
71
54
|
AND diagnostic IS NOT NULL AND schema_json IS NOT NULL)
|
|
55
|
+
OR (outcome = 'canceled' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL
|
|
56
|
+
AND answer_json IS NULL AND diagnostic IS NULL)
|
|
72
57
|
OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL
|
|
73
58
|
AND answer IS NULL AND answer_json IS NULL AND diagnostic IS NULL AND completed_at IS NULL)
|
|
74
59
|
)
|
|
60
|
+
) STRICT;`;
|
|
61
|
+
}
|
|
62
|
+
/** Called only under the open boundary's write transaction, with foreign keys disabled. */
|
|
63
|
+
export function upgradeHeartSchema(database) {
|
|
64
|
+
const version = heartSchemaVersion(database);
|
|
65
|
+
// Another opener may have completed the fixed conversion while this one waited.
|
|
66
|
+
if (version !== 29 && version !== 30) {
|
|
67
|
+
assertHeartSchemaVersion(database);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (version === 29) {
|
|
71
|
+
database.exec(turnSchema("turns_upgrade"));
|
|
72
|
+
database.exec(`
|
|
73
|
+
INSERT INTO turns_upgrade (sequence, body_sequence, started_at, initiator, end_sequence,
|
|
74
|
+
outcome, history_id, session_json, answer, answer_json, schema_json, diagnostic, completed_at)
|
|
75
|
+
SELECT sequence, body_sequence, started_at, initiator, end_sequence,
|
|
76
|
+
outcome, history_id, session_json, answer, answer_json, schema_json, diagnostic, completed_at FROM turns;
|
|
77
|
+
DROP TABLE turns;
|
|
78
|
+
ALTER TABLE turns_upgrade RENAME TO turns;
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
const highWater = database.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'timeline'").get();
|
|
82
|
+
database.exec(timelineSchema("timeline_upgrade"));
|
|
83
|
+
database.exec(`
|
|
84
|
+
INSERT INTO timeline_upgrade SELECT sequence, kind FROM timeline;
|
|
85
|
+
DROP TABLE timeline;
|
|
86
|
+
ALTER TABLE timeline_upgrade RENAME TO timeline;
|
|
87
|
+
${REPORT_SCHEMA}
|
|
88
|
+
DROP TABLE akuma_schema;
|
|
89
|
+
${VERSION_SCHEMA}
|
|
90
|
+
`);
|
|
91
|
+
if (highWater !== undefined)
|
|
92
|
+
database.prepare("UPDATE sqlite_sequence SET seq = MAX(seq, ?) WHERE name = 'timeline'").run(highWater.seq);
|
|
93
|
+
if (database.prepare("PRAGMA foreign_key_check").get() !== undefined)
|
|
94
|
+
throw new Error("Akuma Heart upgrade found broken foreign key relationships");
|
|
95
|
+
}
|
|
96
|
+
function timelineSchema(table) {
|
|
97
|
+
return `CREATE TABLE ${table === "timeline" ? "IF NOT EXISTS " : ""}${table} (
|
|
98
|
+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
99
|
+
kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end', 'report'))
|
|
100
|
+
) STRICT;`;
|
|
101
|
+
}
|
|
102
|
+
const REPORT_SCHEMA = `CREATE TABLE IF NOT EXISTS reports (
|
|
103
|
+
sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,
|
|
104
|
+
body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),
|
|
105
|
+
turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,
|
|
106
|
+
text TEXT NOT NULL,
|
|
107
|
+
at TEXT NOT NULL
|
|
108
|
+
) STRICT;
|
|
109
|
+
CREATE INDEX IF NOT EXISTS reports_by_turn ON reports(turn_sequence);`;
|
|
110
|
+
export const HEART_SCHEMA = `
|
|
111
|
+
${VERSION_SCHEMA}
|
|
112
|
+
CREATE TABLE IF NOT EXISTS soul (
|
|
113
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
114
|
+
soul_json TEXT NOT NULL CHECK (json_valid(soul_json))
|
|
75
115
|
) STRICT;
|
|
76
|
-
CREATE TABLE IF NOT EXISTS
|
|
116
|
+
CREATE TABLE IF NOT EXISTS bodies (
|
|
77
117
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
78
|
-
|
|
118
|
+
leash_taken_at TEXT NOT NULL,
|
|
119
|
+
hung_diagnostic TEXT,
|
|
120
|
+
hung_at TEXT,
|
|
121
|
+
end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),
|
|
122
|
+
ended_at TEXT,
|
|
123
|
+
CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL)
|
|
124
|
+
OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL))
|
|
125
|
+
) STRICT;
|
|
126
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
127
|
+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
128
|
+
provider TEXT NOT NULL,
|
|
129
|
+
coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),
|
|
130
|
+
cwd TEXT NOT NULL,
|
|
131
|
+
options_json TEXT NOT NULL CHECK (json_valid(options_json)),
|
|
132
|
+
admitted_at TEXT NOT NULL
|
|
79
133
|
) STRICT;
|
|
134
|
+
${turnSchema("turns")}
|
|
135
|
+
${timelineSchema("timeline")}
|
|
136
|
+
${REPORT_SCHEMA}
|
|
80
137
|
CREATE TABLE IF NOT EXISTS activity_retention (
|
|
81
138
|
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
82
139
|
checked_sequence INTEGER NOT NULL
|
|
@@ -35,6 +35,9 @@ export function decodeSoulSeed(value) {
|
|
|
35
35
|
const provider = decodeProviderRecipe(soul.provider);
|
|
36
36
|
const options = decodeProviderOptions(soul.options);
|
|
37
37
|
const caller = decodeAkumaCaller(soul.caller);
|
|
38
|
+
if (soul.bare !== undefined && typeof soul.bare !== "boolean") {
|
|
39
|
+
throw new Error("Akuma soul bare must be a boolean");
|
|
40
|
+
}
|
|
38
41
|
return {
|
|
39
42
|
id,
|
|
40
43
|
archetype,
|
|
@@ -45,6 +48,7 @@ export function decodeSoulSeed(value) {
|
|
|
45
48
|
origin: akumaOriginSchema.parse(soul.origin),
|
|
46
49
|
...(caller === undefined ? {} : { caller }),
|
|
47
50
|
allowed: historicalAllowedActions(soul.allowed),
|
|
51
|
+
...(soul.bare === undefined ? {} : { bare: soul.bare }),
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
54
|
function deepFreeze(value) {
|
|
@@ -47,6 +47,7 @@ export declare class HeldAkumaLeash {
|
|
|
47
47
|
completeInterruptBarrier(paths: AkumaPaths, expected?: PauseFact): Promise<InterruptCompletion>;
|
|
48
48
|
recordBody(paths: AkumaPaths, input: Readonly<{
|
|
49
49
|
leashTakenAt: string;
|
|
50
|
+
handoffFromBodySequence?: number;
|
|
50
51
|
}>): Promise<BodyFact>;
|
|
51
52
|
recordBodyHung(paths: AkumaPaths, input: Readonly<{
|
|
52
53
|
sequence: number;
|
|
@@ -3,9 +3,9 @@ import { lstat } from "node:fs/promises";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { setTimeout } from "node:timers/promises";
|
|
5
5
|
import { BodyControlledError } from "./facts.js";
|
|
6
|
-
import { HEART_SCHEMA, LEASH_SCHEMA, assertHeartSchemaVersion, assertLeashSchemaVersion, heartSchemaIsCurrent, } from "./schema.js";
|
|
7
|
-
import { deleteStopControl, completeInterruptBarrier as judgeInterruptBarrier, insertBodyFact, insertKillFact, insertSealFact, insertSessionFact, killFactForBody, latestBodyFact, latestKillFact, markBodyHung, sealExists, sealFact, stopFact, } from "./rows.js";
|
|
8
|
-
import { insertTellFact } from "./tells.js";
|
|
6
|
+
import { HEART_SCHEMA, LEASH_SCHEMA, assertHeartSchemaVersion, assertLeashSchemaVersion, heartSchemaIsCurrent, heartSchemaVersion, upgradeHeartSchema, } from "./schema.js";
|
|
7
|
+
import { deleteStopControl, completeInterruptBarrier as judgeInterruptBarrier, insertBodyFact, insertKillFact, insertSealFact, insertSessionFact, killFactForBody, hasBodyHandoffControl, latestBodyFact, latestKillFact, markBodyHung, sealExists, sealFact, stopFact, } from "./rows.js";
|
|
8
|
+
import { insertTellFact, resolveTellDispositionSnapshot, tellDispositionResolved } from "./tells.js";
|
|
9
9
|
import { pruneActivityFacts } from "./timeline.js";
|
|
10
10
|
import { insertSoulFact, soulFact } from "./soul.js";
|
|
11
11
|
const ACTIVITY_LIMIT = 5_000;
|
|
@@ -46,16 +46,43 @@ async function openExistingDatabase(path, timeout, mode = "rw") {
|
|
|
46
46
|
throw error;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
async function openHeart(path, mode) {
|
|
49
|
+
async function openHeart(path, mode, signal) {
|
|
50
50
|
const database = await openExistingDatabase(path, 0, mode);
|
|
51
51
|
try {
|
|
52
52
|
database.exec("PRAGMA foreign_keys=ON; PRAGMA busy_timeout=0");
|
|
53
|
-
|
|
53
|
+
const version = await acquire(() => {
|
|
54
|
+
const version = heartSchemaVersion(database);
|
|
55
|
+
if (version !== 29 && version !== 30)
|
|
56
|
+
assertHeartSchemaVersion(database);
|
|
57
|
+
return version;
|
|
58
|
+
}, signal);
|
|
59
|
+
if (version !== 29 && version !== 30)
|
|
60
|
+
return database;
|
|
54
61
|
}
|
|
55
62
|
catch (error) {
|
|
56
63
|
database.close();
|
|
57
64
|
throw error;
|
|
58
65
|
}
|
|
66
|
+
database.close();
|
|
67
|
+
// Current-format reads never acquire a writer. Only the known predecessors
|
|
68
|
+
// need one, and no read transaction survives across the conversion.
|
|
69
|
+
const writer = await openExistingDatabase(path, 0);
|
|
70
|
+
try {
|
|
71
|
+
// Dropping turns with FK enforcement enabled would cascade into its children.
|
|
72
|
+
// SQLite requires this switch outside the transaction; check before commit.
|
|
73
|
+
writer.exec("PRAGMA foreign_keys=OFF; PRAGMA busy_timeout=0");
|
|
74
|
+
await beginTransaction(writer, "write", signal, heartSchemaVersion);
|
|
75
|
+
finishTransaction(writer, () => upgradeHeartSchema(writer));
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
try {
|
|
79
|
+
writer.exec("PRAGMA foreign_keys=ON");
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
writer.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return await openHeart(path, mode, signal);
|
|
59
86
|
}
|
|
60
87
|
export async function classifyHeartSchema(paths) {
|
|
61
88
|
let database;
|
|
@@ -74,20 +101,15 @@ export async function classifyHeartSchema(paths) {
|
|
|
74
101
|
database.close();
|
|
75
102
|
}
|
|
76
103
|
}
|
|
77
|
-
async function
|
|
104
|
+
async function acquire(operation, signal) {
|
|
78
105
|
const deadline = performance.now() + TRANSACTION_ACQUISITION_MS;
|
|
79
106
|
let delay = 10;
|
|
80
107
|
for (;;) {
|
|
81
108
|
signal?.throwIfAborted();
|
|
82
109
|
try {
|
|
83
|
-
|
|
84
|
-
// DEFERRED acquires its snapshot on the first read, not on BEGIN. WAL
|
|
85
|
-
// recovery and last-connection cleanup can make this read busy too.
|
|
86
|
-
assertHeartSchemaVersion(database);
|
|
87
|
-
return;
|
|
110
|
+
return operation();
|
|
88
111
|
}
|
|
89
112
|
catch (error) {
|
|
90
|
-
rollbackTransaction(database);
|
|
91
113
|
// Only failed admission can be retried: no caller statement has run.
|
|
92
114
|
const remaining = deadline - performance.now();
|
|
93
115
|
if (!isBusy(error) || remaining <= 0)
|
|
@@ -102,9 +124,22 @@ async function beginTransaction(database, mode, signal) {
|
|
|
102
124
|
}
|
|
103
125
|
}
|
|
104
126
|
}
|
|
127
|
+
async function beginTransaction(database, mode, signal, validate = assertHeartSchemaVersion) {
|
|
128
|
+
await acquire(() => {
|
|
129
|
+
try {
|
|
130
|
+
database.exec(mode === "write" ? "BEGIN IMMEDIATE" : "BEGIN DEFERRED");
|
|
131
|
+
// DEFERRED acquires its snapshot on the first read, not on BEGIN.
|
|
132
|
+
validate(database);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
rollbackTransaction(database);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}, signal);
|
|
139
|
+
}
|
|
105
140
|
export async function withReadOnlyHeart(paths, body, signal) {
|
|
106
141
|
signal?.throwIfAborted();
|
|
107
|
-
const database = await openHeart(paths.heart, "ro");
|
|
142
|
+
const database = await openHeart(paths.heart, "ro", signal);
|
|
108
143
|
try {
|
|
109
144
|
await beginTransaction(database, "read", signal);
|
|
110
145
|
return finishTransaction(database, body);
|
|
@@ -151,7 +186,7 @@ export function createHeartReader(paths) {
|
|
|
151
186
|
if (identity?.dev !== current.dev || identity?.ino !== current.ino)
|
|
152
187
|
release();
|
|
153
188
|
if (database === undefined) {
|
|
154
|
-
database = await openHeart(paths.heart, "ro");
|
|
189
|
+
database = await openHeart(paths.heart, "ro", signal);
|
|
155
190
|
identity = current;
|
|
156
191
|
opened += 1;
|
|
157
192
|
}
|
|
@@ -170,7 +205,7 @@ export function createHeartReader(paths) {
|
|
|
170
205
|
}
|
|
171
206
|
export async function withHeartTransaction(paths, body, signal) {
|
|
172
207
|
signal?.throwIfAborted();
|
|
173
|
-
const database = await openHeart(paths.heart, "rw");
|
|
208
|
+
const database = await openHeart(paths.heart, "rw", signal);
|
|
174
209
|
try {
|
|
175
210
|
await beginTransaction(database, "write", signal);
|
|
176
211
|
return finishTransaction(database, body);
|
|
@@ -282,6 +317,12 @@ export class HeldAkumaLeash {
|
|
|
282
317
|
if (this.closed || this.bodySequence !== undefined)
|
|
283
318
|
throw new Error("Akuma leash cannot start another Body");
|
|
284
319
|
const sequence = await withHeartTransaction(paths, (heart) => {
|
|
320
|
+
const predecessor = input.handoffFromBodySequence;
|
|
321
|
+
if (predecessor !== undefined &&
|
|
322
|
+
(latestBodyFact(heart)?.sequence !== predecessor ||
|
|
323
|
+
hasBodyHandoffControl(heart, predecessor) ||
|
|
324
|
+
tellDispositionResolved(heart, predecessor) !== false))
|
|
325
|
+
throw new BodyControlledError();
|
|
285
326
|
const completion = judgeInterruptBarrier(heart);
|
|
286
327
|
if (completion.kind === "unavailable")
|
|
287
328
|
throw new BodyControlledError();
|
|
@@ -318,6 +359,9 @@ export class HeldAkumaLeash {
|
|
|
318
359
|
const result = latestKillFact(heart)?.bodySequence === target.bodySequence
|
|
319
360
|
? "already-killed"
|
|
320
361
|
: (insertKillFact(heart, target.bodySequence, target.requestedAt), "recorded");
|
|
362
|
+
const released = tellDispositionResolved(heart, target.bodySequence) === false;
|
|
363
|
+
resolveTellDispositionSnapshot(heart, target.bodySequence, target.requestedAt);
|
|
364
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, released);
|
|
321
365
|
deleteStopControl(heart);
|
|
322
366
|
return { target, result };
|
|
323
367
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
-
import type { CallFact, TellFact, TurnEndFact, TurnStartFact } from "./facts.js";
|
|
3
|
-
import { type ActivityFact, type ActivityRow, type CallRow, type TurnRow } from "./rows.js";
|
|
2
|
+
import type { CallFact, ReportFact, TellFact, TurnEndFact, TurnStartFact } from "./facts.js";
|
|
3
|
+
import { type ActivityFact, type ReportRow, type ActivityRow, type CallRow, type TurnRow } from "./rows.js";
|
|
4
4
|
import { tellBatchAtSequences } from "./tells.js";
|
|
5
|
-
export type TimelineFact = TurnStartFact | CallFact | ActivityFact | TellFact | TurnEndFact;
|
|
5
|
+
export type TimelineFact = TurnStartFact | CallFact | ReportFact | ActivityFact | TellFact | TurnEndFact;
|
|
6
6
|
export type ActivityFactSlice = Readonly<{
|
|
7
7
|
rows: readonly TimelineFact[];
|
|
8
8
|
lowestRetained: number | null;
|
|
@@ -12,12 +12,13 @@ export declare function lastActivityAt(database: DatabaseSync): string | null;
|
|
|
12
12
|
export declare function pruneActivityFacts(database: DatabaseSync, limit: number, protectionReleased?: boolean): void;
|
|
13
13
|
type TimelineRow = Readonly<{
|
|
14
14
|
sequence: number;
|
|
15
|
-
kind: "turn-start" | "call" | "activity" | "tell" | "turn-end";
|
|
15
|
+
kind: "turn-start" | "call" | "report" | "activity" | "tell" | "turn-end";
|
|
16
16
|
}>;
|
|
17
17
|
type TimelineBatch = Readonly<{
|
|
18
18
|
rows: readonly TimelineRow[];
|
|
19
19
|
turns: readonly TurnRow[];
|
|
20
20
|
calls: readonly CallRow[];
|
|
21
|
+
reports: readonly ReportRow[];
|
|
21
22
|
activity: readonly ActivityRow[];
|
|
22
23
|
tells: ReturnType<typeof tellBatchAtSequences>;
|
|
23
24
|
}>;
|
|
@@ -31,6 +32,7 @@ export declare function readStatusBatch(database: DatabaseSync, input: StatusFac
|
|
|
31
32
|
rows: readonly TimelineRow[];
|
|
32
33
|
turns: readonly TurnRow[];
|
|
33
34
|
calls: readonly CallRow[];
|
|
35
|
+
reports: readonly ReportRow[];
|
|
34
36
|
activity: readonly ActivityRow[];
|
|
35
37
|
tells: ReturnType<typeof tellBatchAtSequences>;
|
|
36
38
|
frontier: number | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeActivityRow, decodeCallRow, decodeTurnRow, } from "./rows.js";
|
|
1
|
+
import { decodeReportRow, decodeActivityRow, decodeCallRow, decodeTurnRow, } from "./rows.js";
|
|
2
2
|
import { decodeTellBatch, tellBatchAtSequences, tellStateSql, pendingTellProtectionSql, pendingTellSequencesSql, } from "./tells.js";
|
|
3
3
|
export function lastActivityAt(database) {
|
|
4
4
|
const row = database
|
|
@@ -6,6 +6,7 @@ export function lastActivityAt(database) {
|
|
|
6
6
|
WHEN 'turn-start' THEN started_turn.started_at
|
|
7
7
|
WHEN 'call' THEN calls.at
|
|
8
8
|
WHEN 'activity' THEN activity.at
|
|
9
|
+
WHEN 'report' THEN reports.at
|
|
9
10
|
WHEN 'tell' THEN tells.recorded_at
|
|
10
11
|
WHEN 'turn-end' THEN ended_turn.completed_at
|
|
11
12
|
END AS at
|
|
@@ -13,6 +14,7 @@ export function lastActivityAt(database) {
|
|
|
13
14
|
LEFT JOIN turns AS started_turn ON timeline.kind = 'turn-start' AND started_turn.sequence = timeline.sequence
|
|
14
15
|
LEFT JOIN calls ON timeline.kind = 'call' AND calls.sequence = timeline.sequence
|
|
15
16
|
LEFT JOIN activity ON timeline.kind = 'activity' AND activity.sequence = timeline.sequence
|
|
17
|
+
LEFT JOIN reports ON timeline.kind = 'report' AND reports.sequence = timeline.sequence
|
|
16
18
|
LEFT JOIN tells ON timeline.kind = 'tell' AND tells.sequence = timeline.sequence
|
|
17
19
|
LEFT JOIN turns AS ended_turn ON timeline.kind = 'turn-end' AND ended_turn.end_sequence = timeline.sequence
|
|
18
20
|
ORDER BY timeline.sequence DESC LIMIT 1`)
|
|
@@ -41,6 +43,7 @@ export function pruneActivityFacts(database, limit, protectionReleased = false)
|
|
|
41
43
|
SELECT sequence FROM turns WHERE end_sequence IS NULL
|
|
42
44
|
UNION SELECT turn_sequence FROM calls WHERE sequence >= ?
|
|
43
45
|
UNION SELECT turn_sequence FROM activity WHERE sequence >= ?
|
|
46
|
+
UNION SELECT turn_sequence FROM reports WHERE sequence >= ?
|
|
44
47
|
UNION SELECT sequence FROM turns WHERE end_sequence >= ?
|
|
45
48
|
UNION ${pendingTellProtectionSql}
|
|
46
49
|
), protected(sequence) AS (
|
|
@@ -50,7 +53,7 @@ export function pruneActivityFacts(database, limit, protectionReleased = false)
|
|
|
50
53
|
UNION ${pendingTellSequencesSql}
|
|
51
54
|
)
|
|
52
55
|
DELETE FROM timeline WHERE sequence < ? AND sequence NOT IN protected`)
|
|
53
|
-
.run(cutoff.sequence, cutoff.sequence, cutoff.sequence, cutoff.sequence);
|
|
56
|
+
.run(cutoff.sequence, cutoff.sequence, cutoff.sequence, cutoff.sequence, cutoff.sequence);
|
|
54
57
|
}
|
|
55
58
|
function readTimelineBatch(database, rows) {
|
|
56
59
|
const sequences = (kind) => JSON.stringify(rows.filter((row) => row.kind === kind).map((row) => row.sequence));
|
|
@@ -68,8 +71,12 @@ function readTimelineBatch(database, rows) {
|
|
|
68
71
|
.prepare(`SELECT sequence, turn_sequence, event_json, at FROM activity
|
|
69
72
|
WHERE sequence IN (SELECT value FROM json_each(?)) ORDER BY sequence`)
|
|
70
73
|
.all(sequences("activity"));
|
|
74
|
+
const reports = database
|
|
75
|
+
.prepare(`SELECT sequence, body_sequence, turn_sequence, text, at
|
|
76
|
+
FROM reports WHERE sequence IN (SELECT value FROM json_each(?)) ORDER BY sequence`)
|
|
77
|
+
.all(sequences("report"));
|
|
71
78
|
const tells = tellBatchAtSequences(database, rows.filter((row) => row.kind === "tell").map((row) => row.sequence));
|
|
72
|
-
return { rows, turns, calls, activity, tells };
|
|
79
|
+
return { rows, turns, calls, reports, activity, tells };
|
|
73
80
|
}
|
|
74
81
|
export function decodeTimelineBatch(batch) {
|
|
75
82
|
const facts = new Map();
|
|
@@ -81,6 +88,8 @@ export function decodeTimelineBatch(batch) {
|
|
|
81
88
|
}
|
|
82
89
|
for (const row of batch.calls)
|
|
83
90
|
facts.set(row.sequence, decodeCallRow(row));
|
|
91
|
+
for (const row of batch.reports)
|
|
92
|
+
facts.set(row.sequence, decodeReportRow(row));
|
|
84
93
|
for (const row of batch.activity)
|
|
85
94
|
facts.set(row.sequence, decodeActivityRow(row));
|
|
86
95
|
for (const fact of decodeTellBatch(batch.tells))
|
|
@@ -100,6 +109,7 @@ export function readStatusBatch(database, input) {
|
|
|
100
109
|
SELECT sequence FROM turns WHERE sequence = ?
|
|
101
110
|
UNION SELECT end_sequence FROM turns WHERE sequence = ? AND end_sequence IS NOT NULL
|
|
102
111
|
UNION SELECT sequence FROM calls WHERE turn_sequence = ?
|
|
112
|
+
UNION SELECT sequence FROM reports WHERE turn_sequence = ?
|
|
103
113
|
UNION SELECT sequence FROM activity WHERE turn_sequence = ?
|
|
104
114
|
UNION SELECT tells.sequence FROM tells
|
|
105
115
|
JOIN tell_deliveries ON tell_deliveries.tell_id = tells.id
|
|
@@ -107,7 +117,7 @@ export function readStatusBatch(database, input) {
|
|
|
107
117
|
UNION SELECT sequence FROM tells WHERE id = ?
|
|
108
118
|
UNION SELECT sequence FROM tells WHERE ? AND ${tellStateSql} = 'pending'
|
|
109
119
|
) SELECT timeline.sequence, timeline.kind FROM timeline JOIN selected USING(sequence) ORDER BY sequence`)
|
|
110
|
-
.all(frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, input.admittedTellId ?? null, input.aperture === "monitoring" || input.admittedTellId === undefined ? 1 : 0);
|
|
120
|
+
.all(frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, frontier?.sequence ?? null, input.admittedTellId ?? null, input.aperture === "monitoring" || input.admittedTellId === undefined ? 1 : 0);
|
|
111
121
|
return { frontier: frontier?.sequence ?? null, ...readTimelineBatch(database, rows) };
|
|
112
122
|
}
|
|
113
123
|
export function statusFacts(database, input) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ProviderAdapter } from "./provider.js";
|
|
2
|
+
import { type BodyRuntime } from "./body.js";
|
|
3
|
+
import type { BodyLaunch, LiveBodyLaunch } from "./body-launch.js";
|
|
4
|
+
/** One owned Body in this Node runtime; preparation starts only after custody is returned. */
|
|
5
|
+
export declare function launchInProcessBody(launch: BodyLaunch, prepare: (signal: AbortSignal) => Promise<Pick<BodyRuntime, "world" | "externalCommands" | "spawn" | "requestSpawn" | "executionAssociation">>, adapter?: ProviderAdapter, scopeSignal?: AbortSignal): Promise<LiveBodyLaunch>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { driveAkumaBody } from "./body.js";
|
|
2
|
+
import { abortable } from "./abort.js";
|
|
3
|
+
/** One owned Body in this Node runtime; preparation starts only after custody is returned. */
|
|
4
|
+
export async function launchInProcessBody(launch, prepare, adapter, scopeSignal) {
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const forced = new AbortController();
|
|
7
|
+
// Scope custody is independent of the publication holder's release below.
|
|
8
|
+
const closeScope = () => {
|
|
9
|
+
forced.abort();
|
|
10
|
+
controller.abort(scopeSignal?.reason);
|
|
11
|
+
};
|
|
12
|
+
scopeSignal?.addEventListener("abort", closeScope, { once: true });
|
|
13
|
+
if (scopeSignal?.aborted)
|
|
14
|
+
closeScope();
|
|
15
|
+
let released = false;
|
|
16
|
+
// A pending SDK/setup Promise alone does not keep Node alive. Release is not
|
|
17
|
+
// detachment: this Body retains the calling runtime until its drive finishes.
|
|
18
|
+
const lifetime = setInterval(() => { }, 1_073_741_824);
|
|
19
|
+
const completed = Promise.resolve()
|
|
20
|
+
.then(async () => {
|
|
21
|
+
controller.signal.throwIfAborted();
|
|
22
|
+
const runtime = await abortable(prepare(controller.signal), controller.signal);
|
|
23
|
+
controller.signal.throwIfAborted();
|
|
24
|
+
const result = await driveAkumaBody(launch, adapter, {
|
|
25
|
+
...runtime,
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
forceSignal: forced.signal,
|
|
28
|
+
});
|
|
29
|
+
if (result === "hung")
|
|
30
|
+
throw new Error("in-process Body ended with hung provider custody; native work may still be running");
|
|
31
|
+
return { diagnostic: "in-process Body completed", ...(result === "held" ? { held: true } : {}) };
|
|
32
|
+
})
|
|
33
|
+
.catch((error) => {
|
|
34
|
+
if (controller.signal.aborted && error === controller.signal.reason)
|
|
35
|
+
return { diagnostic: "in-process Body launch canceled" };
|
|
36
|
+
throw error;
|
|
37
|
+
})
|
|
38
|
+
.finally(() => {
|
|
39
|
+
scopeSignal?.removeEventListener("abort", closeScope);
|
|
40
|
+
clearInterval(lifetime);
|
|
41
|
+
});
|
|
42
|
+
void completed.catch(() => undefined);
|
|
43
|
+
return {
|
|
44
|
+
completed,
|
|
45
|
+
async cancel(force = false) {
|
|
46
|
+
if (released)
|
|
47
|
+
return;
|
|
48
|
+
if (force)
|
|
49
|
+
forced.abort();
|
|
50
|
+
controller.abort(new Error("in-process Body launch canceled"));
|
|
51
|
+
await completed;
|
|
52
|
+
},
|
|
53
|
+
release() {
|
|
54
|
+
released = true;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -4,7 +4,7 @@ export { AkumaArchetypeError, listArchetypeDefinitions } from "./archetype.js";
|
|
|
4
4
|
export type { ArchetypeCatalog, ArchetypeCatalogInput, ArchetypeCatalogRow } from "./archetype.js";
|
|
5
5
|
export { AkumaBusyError, AkumaDecodeError, AkumaNotBornError, AkumaProviderError } from "./akuma-errors.js";
|
|
6
6
|
export { Akuma } from "./akuma-instance.js";
|
|
7
|
-
export type { AkumaAskOptions, AkumaBirthInput, AkumaHistoryOptions, AkumaHostOptions, AkumaIdleOptions, AkumaIdleResult, AkumaSignalOptions, AkumaTellOptions, } from "./akuma-instance.js";
|
|
7
|
+
export type { AkumaAskOptions, AkumaBirthInput, AkumaHistoryOptions, AkumaHostOptions, AkumaIdleOptions, AkumaIdleResult, AkumaReportOptions, AkumaReportResult, AkumaSignalOptions, AkumaTellOptions, } from "./akuma-instance.js";
|
|
8
8
|
export type { AkumaStreamSnapshot } from "./akuma-stream.js";
|
|
9
9
|
export type { InterruptReceipt } from "./akuma-owner.js";
|
|
10
10
|
export type { AkumaStatus } from "./akuma.js";
|