@astrosheep/keiyaku 4.5.0 → 4.5.2
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/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +4 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +9 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +16 -5
- package/build/integrations/pi/keiyaku.ts +159 -0
- package/build/src/akuma/abort.js +9 -9
- package/build/src/akuma/akuma-handle.d.ts +4 -2
- package/build/src/akuma/akuma-handle.js +14 -2
- package/build/src/akuma/akuma-product.d.ts +8 -2
- package/build/src/akuma/akuma-product.js +9 -4
- package/build/src/akuma/body.d.ts +4 -0
- package/build/src/akuma/body.js +21 -14
- package/build/src/akuma/heart/index.js +7 -3
- package/build/src/akuma/heart/rows.d.ts +1 -1
- package/build/src/akuma/heart/rows.js +3 -3
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +28 -6
- package/build/src/akuma/identity.d.ts +1 -0
- package/build/src/akuma/identity.js +7 -0
- package/build/src/akuma/index.d.ts +1 -1
- package/build/src/akuma/projection.d.ts +18 -2
- package/build/src/akuma/projection.js +11 -1
- package/build/src/akuma/providers/claude/index.js +68 -37
- package/build/src/akuma/publication.js +3 -0
- package/build/src/akuma/request-lifecycle.d.ts +45 -0
- package/build/src/akuma/request-lifecycle.js +171 -0
- package/build/src/akuma/request-serve.d.ts +9 -32
- package/build/src/akuma/request-serve.js +47 -173
- package/build/src/akuma/request-wire.js +1 -1
- package/build/src/akuma/requests.js +3 -2
- package/build/src/cli/commands/akuma-invoke.d.ts +9 -0
- package/build/src/cli/commands/akuma-invoke.js +9 -6
- package/build/src/cli/commands/akuma.d.ts +1 -0
- package/build/src/cli/commands/akuma.js +16 -6
- package/build/src/cli/commands/contract.d.ts +3 -2
- package/build/src/cli/commands/contract.js +5 -2
- package/build/src/cli/commands/status-set.d.ts +4 -0
- package/build/src/cli/commands/status-set.js +61 -0
- package/build/src/cli/invoke.js +5 -0
- package/build/src/cli/parse.js +1 -1
- package/build/src/cli/render/akuma-activity.js +11 -0
- package/build/src/cli/render/akuma.js +7 -0
- package/build/src/cli/render/status-set.d.ts +5 -0
- package/build/src/cli/render/status-set.js +14 -0
- package/build/src/cli/render/text.js +3 -0
- package/build/src/cli/result.d.ts +15 -1
- package/build/src/cli/runtime.js +2 -0
- package/build/src/cli/square-edge.d.ts +2 -1
- package/build/src/cli/square-edge.js +60 -33
- package/build/src/contract-worktree.js +18 -5
- package/build/src/library/akuma-creation.d.ts +1 -1
- package/build/src/library/akuma-creation.js +6 -2
- package/build/src/library/fleet.d.ts +12 -1
- package/build/src/library/fleet.js +21 -5
- package/build/src/library/repo.js +3 -1
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/world.d.ts +2 -0
- package/build/src/world.js +4 -0
- package/package.json +8 -3
|
@@ -4,6 +4,13 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { identityCoordinate, identitySegments } from "../identity/coordinates.js";
|
|
5
5
|
import { normalizeIdentityStem } from "../identity/normalize.js";
|
|
6
6
|
const HEX8 = /^[0-9a-f]{8}$/u;
|
|
7
|
+
export function parsePublicHistoryId(value) {
|
|
8
|
+
const match = /^turn\/([1-9][0-9]*)$/u.exec(value);
|
|
9
|
+
if (match === null)
|
|
10
|
+
return null;
|
|
11
|
+
const sequence = Number(match[1]);
|
|
12
|
+
return Number.isSafeInteger(sequence) ? sequence : null;
|
|
13
|
+
}
|
|
7
14
|
export function archetypeName(value) {
|
|
8
15
|
if (value.length === 0 || normalizeIdentityStem({ source: value }) !== value) {
|
|
9
16
|
throw new TypeError("Akuma name must be one normalized human identity segment");
|
|
@@ -9,7 +9,7 @@ export declare function probeBornAkuma(worldPath: WorldRoot, id: AkuId): Promise
|
|
|
9
9
|
export { AkumaArchetypeError, listArchetypeDefinitions, listArchetypes } from "./archetype.js";
|
|
10
10
|
export type { ArchetypeCatalogRow } from "./archetype.js";
|
|
11
11
|
export { AkumaBodyRequestError } from "./requests.js";
|
|
12
|
-
export type { AkumaList, AkumaListInput, AkumaListRow, AkumaStatus, ActivityHistory, ActivityRow, ActivitySnapshot, ActivitySnapshotEntry, ForkReceipt, InterruptReceipt, TellResult, TellWake, UnbornAkumaListRow, } from "./akuma.js";
|
|
12
|
+
export type { AkumaList, AkumaListInput, AkumaListRow, AkumaStatus, ActivityHistory, ExactHistory, ActivityRow, ActivitySnapshot, ActivitySnapshotEntry, ForkReceipt, InterruptReceipt, TellResult, TellWake, UnbornAkumaListRow, } from "./akuma.js";
|
|
13
13
|
export type { ActiveToolRow, ClosedTurn, ClosedTurnRow, CompletedToolRow, HistoryCursor, HistoryPage, IdleSnapshotRow, OpenSnapshotRow, OpenTurn, OpenTurnRow, OutcomeRow, ReportedFileChange, RetainedWindow, Snapshot, SnapshotRow, TellRow, TurnLedger, TurnOutcome, TurnStartRow, UnsettledToolRow, } from "./akuma.js";
|
|
14
14
|
export type { AgentEvent, ToolCall, ToolEvent, ToolResult } from "./provider.js";
|
|
15
15
|
export type { AkuId } from "./identity.js";
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { type ToolCall, type ToolResult } from "./provider.js";
|
|
2
|
-
import type { TellDelivery, TellFact, TimelineFact
|
|
3
|
-
export type TurnOutcome =
|
|
2
|
+
import type { TellDelivery, TellFact, TimelineFact } from "./heart/index.js";
|
|
3
|
+
export type TurnOutcome = Readonly<{
|
|
4
|
+
kind: "answered";
|
|
5
|
+
historyId: string;
|
|
6
|
+
answer: string;
|
|
7
|
+
}> | Readonly<{
|
|
8
|
+
kind: "failed";
|
|
9
|
+
historyId: string;
|
|
10
|
+
diagnostic: string;
|
|
11
|
+
}>;
|
|
4
12
|
export type TurnStartRow = Readonly<{
|
|
5
13
|
kind: "turn";
|
|
6
14
|
sequence: number;
|
|
@@ -153,7 +161,15 @@ export type ActivityHistory = Readonly<{
|
|
|
153
161
|
lowestRetained: number | null;
|
|
154
162
|
highest: number | null;
|
|
155
163
|
}>;
|
|
164
|
+
export type ExactHistory = Readonly<{
|
|
165
|
+
kind: "exact";
|
|
166
|
+
outcome: OutcomeRow;
|
|
167
|
+
}> | Readonly<{
|
|
168
|
+
kind: "unknown-history";
|
|
169
|
+
id: string;
|
|
170
|
+
}>;
|
|
156
171
|
export type HistoryPage = ActivityHistory;
|
|
172
|
+
export declare function selectExactHistory(rows: readonly ActivityRow[], id: string): ExactHistory;
|
|
157
173
|
export type HistoryCursor = Readonly<{
|
|
158
174
|
before?: number;
|
|
159
175
|
since?: number;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { decodeAgentEvent } from "./provider.js";
|
|
2
|
+
function publicOutcome(turnSequence, outcome) {
|
|
3
|
+
const historyId = `turn/${turnSequence}`;
|
|
4
|
+
return outcome.kind === "answered"
|
|
5
|
+
? { kind: "answered", historyId, answer: outcome.answer }
|
|
6
|
+
: { kind: "failed", historyId, diagnostic: outcome.diagnostic };
|
|
7
|
+
}
|
|
8
|
+
export function selectExactHistory(rows, id) {
|
|
9
|
+
const outcome = rows.find((row) => row.kind === "outcome" && row.outcome.historyId === id);
|
|
10
|
+
return outcome === undefined ? { kind: "unknown-history", id } : { kind: "exact", outcome };
|
|
11
|
+
}
|
|
2
12
|
function duration(startedAt, completedAt) {
|
|
3
13
|
const start = Date.parse(startedAt);
|
|
4
14
|
const end = Date.parse(completedAt);
|
|
@@ -171,7 +181,7 @@ export function projectTurns(facts, retained = {
|
|
|
171
181
|
sequence: fact.sequence,
|
|
172
182
|
turnSequence: fact.turnSequence,
|
|
173
183
|
at: fact.completedAt,
|
|
174
|
-
outcome: fact.outcome,
|
|
184
|
+
outcome: publicOutcome(fact.turnSequence, fact.outcome),
|
|
175
185
|
};
|
|
176
186
|
const turn = state.turnsBySequence.get(fact.turnSequence);
|
|
177
187
|
if (turn === undefined)
|
|
@@ -118,53 +118,64 @@ function finishClaudeInput(input, terminal) {
|
|
|
118
118
|
else
|
|
119
119
|
input.close();
|
|
120
120
|
}
|
|
121
|
+
function consumeClaudeMessage(context, state, message) {
|
|
122
|
+
const { input, events, observation, progress, admit, rejectAdmission, settle, controls } = context;
|
|
123
|
+
if (state.terminal !== null)
|
|
124
|
+
return;
|
|
125
|
+
if (!state.admitted && "session_id" in message && typeof message.session_id === "string" && admit()) {
|
|
126
|
+
state.admitted = true;
|
|
127
|
+
events.emit({ type: "session", coordinate: { sessionId: message.session_id } });
|
|
128
|
+
}
|
|
129
|
+
emitClaudeMessage(message, events, observation);
|
|
130
|
+
if (message.type === "assistant" &&
|
|
131
|
+
message.parent_tool_use_id === null &&
|
|
132
|
+
typeof message.uuid === "string" &&
|
|
133
|
+
message.uuid.length > 0)
|
|
134
|
+
state.historyId = message.uuid;
|
|
135
|
+
if (message.type !== "result")
|
|
136
|
+
return;
|
|
137
|
+
if (message.subtype === "success") {
|
|
138
|
+
state.terminal = successfulResult(message, state.historyId);
|
|
139
|
+
progress.checkpoint += 1;
|
|
140
|
+
controls.flushReceipts();
|
|
141
|
+
queueMicrotask(controls.closeWhenIdle);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
state.terminal = { kind: "failed", diagnostic: message.errors.join("; ") || message.subtype };
|
|
145
|
+
input.close();
|
|
146
|
+
}
|
|
147
|
+
progress.ended = true;
|
|
148
|
+
if (!state.admitted)
|
|
149
|
+
rejectAdmission(new Error(state.terminal.kind === "failed" ? state.terminal.diagnostic : "Claude query ended before session admission"));
|
|
150
|
+
finishClaudeInput(input, state.terminal);
|
|
151
|
+
events.end();
|
|
152
|
+
settle(state.terminal);
|
|
153
|
+
controls.settleReceipts();
|
|
154
|
+
}
|
|
121
155
|
async function consumeClaudeQuery(context) {
|
|
122
|
-
const { query, input, events,
|
|
123
|
-
|
|
124
|
-
let terminal = null;
|
|
125
|
-
let historyId;
|
|
156
|
+
const { query, input, events, progress, rejectAdmission, settle, controls } = context;
|
|
157
|
+
const state = { admitted: false, terminal: null };
|
|
126
158
|
try {
|
|
127
|
-
for await (const message of query)
|
|
128
|
-
|
|
129
|
-
admitted = true;
|
|
130
|
-
events.emit({ type: "session", coordinate: { sessionId: message.session_id } });
|
|
131
|
-
admit();
|
|
132
|
-
}
|
|
133
|
-
emitClaudeMessage(message, events, observation);
|
|
134
|
-
if (message.type === "assistant" &&
|
|
135
|
-
message.parent_tool_use_id === null &&
|
|
136
|
-
typeof message.uuid === "string" &&
|
|
137
|
-
message.uuid.length > 0)
|
|
138
|
-
historyId = message.uuid;
|
|
139
|
-
if (message.type !== "result")
|
|
140
|
-
continue;
|
|
141
|
-
if (message.subtype === "success") {
|
|
142
|
-
terminal = successfulResult(message, historyId);
|
|
143
|
-
progress.checkpoint += 1;
|
|
144
|
-
controls.flushReceipts();
|
|
145
|
-
queueMicrotask(controls.closeWhenIdle);
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
terminal = { kind: "failed", diagnostic: message.errors.join("; ") || message.subtype };
|
|
149
|
-
input.close();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
159
|
+
for await (const message of query)
|
|
160
|
+
consumeClaudeMessage(context, state, message);
|
|
152
161
|
progress.ended = true;
|
|
153
|
-
const result = terminal ?? { kind: "failed", diagnostic: "Claude query ended without a result" };
|
|
154
|
-
if (!admitted)
|
|
162
|
+
const result = state.terminal ?? { kind: "failed", diagnostic: "Claude query ended without a result" };
|
|
163
|
+
if (!state.admitted)
|
|
155
164
|
rejectAdmission(new Error(result.kind === "failed" ? result.diagnostic : "Claude query ended before session admission"));
|
|
156
165
|
settle(result);
|
|
157
166
|
}
|
|
158
167
|
catch (error) {
|
|
159
168
|
progress.ended = true;
|
|
169
|
+
if (state.terminal !== null)
|
|
170
|
+
return;
|
|
160
171
|
input.fail(error);
|
|
161
172
|
const failure = error instanceof Error ? error : new Error(String(error));
|
|
162
|
-
if (!admitted)
|
|
173
|
+
if (!state.admitted)
|
|
163
174
|
rejectAdmission(failure);
|
|
164
175
|
settle({ kind: "failed", diagnostic: failure.message });
|
|
165
176
|
}
|
|
166
177
|
finally {
|
|
167
|
-
finishClaudeInput(input, terminal);
|
|
178
|
+
finishClaudeInput(input, state.terminal);
|
|
168
179
|
events.end();
|
|
169
180
|
controls.settleReceipts();
|
|
170
181
|
}
|
|
@@ -206,9 +217,21 @@ function observeClaudeQuery(context) {
|
|
|
206
217
|
const observation = { tools: new Map() };
|
|
207
218
|
let admit;
|
|
208
219
|
let rejectAdmission;
|
|
220
|
+
let admissionOpen = true;
|
|
209
221
|
const admission = new Promise((resolve, reject) => {
|
|
210
|
-
admit =
|
|
211
|
-
|
|
222
|
+
admit = () => {
|
|
223
|
+
if (!admissionOpen)
|
|
224
|
+
return false;
|
|
225
|
+
admissionOpen = false;
|
|
226
|
+
resolve();
|
|
227
|
+
return true;
|
|
228
|
+
};
|
|
229
|
+
rejectAdmission = (error) => {
|
|
230
|
+
if (!admissionOpen)
|
|
231
|
+
return;
|
|
232
|
+
admissionOpen = false;
|
|
233
|
+
reject(error);
|
|
234
|
+
};
|
|
212
235
|
});
|
|
213
236
|
let settle;
|
|
214
237
|
const completion = new Promise((resolve) => {
|
|
@@ -247,6 +270,7 @@ function observeClaudeQuery(context) {
|
|
|
247
270
|
});
|
|
248
271
|
return {
|
|
249
272
|
admission,
|
|
273
|
+
rejectAdmission,
|
|
250
274
|
completion,
|
|
251
275
|
get ended() {
|
|
252
276
|
return progress.ended;
|
|
@@ -282,9 +306,13 @@ async function driveClaude(load, execution, drive) {
|
|
|
282
306
|
input.fail(error);
|
|
283
307
|
query.close();
|
|
284
308
|
};
|
|
285
|
-
|
|
309
|
+
let observed;
|
|
310
|
+
const abortSetup = () => {
|
|
311
|
+
shutDown(signal.reason);
|
|
312
|
+
observed?.rejectAdmission(signal.reason);
|
|
313
|
+
};
|
|
286
314
|
signal.addEventListener("abort", abortSetup, { once: true });
|
|
287
|
-
|
|
315
|
+
observed = observeClaudeQuery({
|
|
288
316
|
query,
|
|
289
317
|
input,
|
|
290
318
|
events,
|
|
@@ -296,8 +324,11 @@ async function driveClaude(load, execution, drive) {
|
|
|
296
324
|
},
|
|
297
325
|
},
|
|
298
326
|
});
|
|
327
|
+
if (signal.aborted)
|
|
328
|
+
abortSetup();
|
|
299
329
|
try {
|
|
300
330
|
await Promise.all([launchAcknowledged, observed.admission]);
|
|
331
|
+
signal.throwIfAborted();
|
|
301
332
|
}
|
|
302
333
|
finally {
|
|
303
334
|
signal.removeEventListener("abort", abortSetup);
|
|
@@ -93,6 +93,9 @@ async function awaitBirth(paths, owned, signal) {
|
|
|
93
93
|
const settled = await settleTimedOutBirth(paths);
|
|
94
94
|
if (settled !== null)
|
|
95
95
|
return settled;
|
|
96
|
+
if (owned !== undefined)
|
|
97
|
+
await owned.terminate(true);
|
|
98
|
+
throw new Error("Akuma birth timed out before Soul admission");
|
|
96
99
|
}
|
|
97
100
|
if (owned === undefined) {
|
|
98
101
|
await abortableDelay(Math.min(POLL_MS, Math.max(0, deadline - performance.now())), signal);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Soul } from "./heart/index.js";
|
|
2
|
+
import { type AkumaPaths } from "./identity.js";
|
|
3
|
+
import { type StructuralRequestClaim } from "./request-wire.js";
|
|
4
|
+
import type { OwnedProcess } from "../runtime/proc/run.js";
|
|
5
|
+
export type RequestChildLaunch = Readonly<{
|
|
6
|
+
paths: AkumaPaths;
|
|
7
|
+
seed: Omit<Soul, "createdAt">;
|
|
8
|
+
initialBody: string;
|
|
9
|
+
}>;
|
|
10
|
+
export type PumpInput = Readonly<{
|
|
11
|
+
paths: AkumaPaths;
|
|
12
|
+
parent: Soul;
|
|
13
|
+
bodySequence: number;
|
|
14
|
+
now(): string;
|
|
15
|
+
spawn(launch: RequestChildLaunch): Promise<OwnedProcess | void>;
|
|
16
|
+
upstream?: unknown;
|
|
17
|
+
signal: AbortSignal;
|
|
18
|
+
}>;
|
|
19
|
+
type ServeClaim = (input: PumpInput & Readonly<{
|
|
20
|
+
directory: string;
|
|
21
|
+
transportId: string;
|
|
22
|
+
claim: StructuralRequestClaim;
|
|
23
|
+
signal: AbortSignal;
|
|
24
|
+
admissionOpen(): boolean;
|
|
25
|
+
}>) => Promise<void>;
|
|
26
|
+
export declare class BodyRequestPump {
|
|
27
|
+
private readonly input;
|
|
28
|
+
private readonly serveClaim;
|
|
29
|
+
readonly directory: string;
|
|
30
|
+
readonly failure: Promise<never>;
|
|
31
|
+
private readonly closeSignal;
|
|
32
|
+
private readonly executionSignal;
|
|
33
|
+
private readonly handled;
|
|
34
|
+
private readonly running;
|
|
35
|
+
private readonly cancelBody;
|
|
36
|
+
private admissionClosed;
|
|
37
|
+
protected constructor(input: PumpInput, serveClaim: ServeClaim);
|
|
38
|
+
static openWithService(input: PumpInput, serveClaim: ServeClaim): Promise<BodyRequestPump>;
|
|
39
|
+
private run;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
stopAdmission(): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function clearBodyRequestTransport(paths: AkumaPaths): Promise<void>;
|
|
44
|
+
export declare function settleBodyRequests(paths: AkumaPaths, parent: Soul, now: () => string, signal?: AbortSignal): Promise<"settled" | "pending">;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { access, mkdir, readFile, readdir, rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { abortableDelay } from "./abort.js";
|
|
4
|
+
import { HeldAkumaLeash, readNonterminalRequests, readSoul, serveRequest, voidRequest, } from "./heart/index.js";
|
|
5
|
+
import { pathsForAkuId } from "./identity.js";
|
|
6
|
+
import { BIRTH_TIMEOUT_MS } from "./publication.js";
|
|
7
|
+
import { atomicJson, decodeClaim, receiptPath } from "./request-wire.js";
|
|
8
|
+
const POLL_MS = 100;
|
|
9
|
+
export class BodyRequestPump {
|
|
10
|
+
input;
|
|
11
|
+
serveClaim;
|
|
12
|
+
directory;
|
|
13
|
+
failure;
|
|
14
|
+
closeSignal = new AbortController();
|
|
15
|
+
executionSignal = new AbortController();
|
|
16
|
+
handled = new Set();
|
|
17
|
+
running;
|
|
18
|
+
cancelBody;
|
|
19
|
+
admissionClosed = false;
|
|
20
|
+
constructor(input, serveClaim) {
|
|
21
|
+
this.input = input;
|
|
22
|
+
this.serveClaim = serveClaim;
|
|
23
|
+
this.directory = join(input.paths.directory, "requests", String(input.bodySequence));
|
|
24
|
+
this.cancelBody = () => {
|
|
25
|
+
this.stopAdmission();
|
|
26
|
+
if (!this.executionSignal.signal.aborted)
|
|
27
|
+
this.executionSignal.abort(input.signal.reason);
|
|
28
|
+
};
|
|
29
|
+
input.signal.addEventListener("abort", this.cancelBody, { once: true });
|
|
30
|
+
if (input.signal.aborted)
|
|
31
|
+
this.cancelBody();
|
|
32
|
+
this.running = this.run();
|
|
33
|
+
this.failure = this.running.then(() => new Promise(() => { }), (error) => Promise.reject(error));
|
|
34
|
+
}
|
|
35
|
+
static async openWithService(input, serveClaim) {
|
|
36
|
+
await mkdir(join(input.paths.directory, "requests", String(input.bodySequence)), { recursive: true });
|
|
37
|
+
return new BodyRequestPump(input, serveClaim);
|
|
38
|
+
}
|
|
39
|
+
async run() {
|
|
40
|
+
while (!this.closeSignal.signal.aborted) {
|
|
41
|
+
for (const name of await requestFiles(this.directory)) {
|
|
42
|
+
if (this.closeSignal.signal.aborted)
|
|
43
|
+
return;
|
|
44
|
+
const transportId = name.slice(0, -".request.json".length);
|
|
45
|
+
if (this.handled.has(transportId))
|
|
46
|
+
continue;
|
|
47
|
+
const path = join(this.directory, name);
|
|
48
|
+
const claim = decodeClaim(await readFile(path, "utf8"), transportId);
|
|
49
|
+
if (claim === null) {
|
|
50
|
+
await rm(path, { force: true });
|
|
51
|
+
this.handled.add(transportId);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!this.admissionClosed) {
|
|
55
|
+
try {
|
|
56
|
+
await this.serveClaim({
|
|
57
|
+
directory: this.directory,
|
|
58
|
+
transportId,
|
|
59
|
+
claim,
|
|
60
|
+
...this.input,
|
|
61
|
+
signal: this.executionSignal.signal,
|
|
62
|
+
admissionOpen: () => !this.admissionClosed,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
await projectTransportFailure(this.directory, transportId, claim, error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.handled.add(transportId);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await abortableDelay(POLL_MS, this.closeSignal.signal);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (this.closeSignal.signal.aborted)
|
|
76
|
+
return;
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async close() {
|
|
82
|
+
this.stopAdmission();
|
|
83
|
+
try {
|
|
84
|
+
await this.running;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
this.input.signal.removeEventListener("abort", this.cancelBody);
|
|
88
|
+
await rm(this.directory, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
stopAdmission() {
|
|
92
|
+
this.admissionClosed = true;
|
|
93
|
+
if (!this.closeSignal.signal.aborted)
|
|
94
|
+
this.closeSignal.abort(new Error("Body request pump closed"));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export async function clearBodyRequestTransport(paths) {
|
|
98
|
+
await rm(join(paths.directory, "requests"), { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
async function requestFiles(directory) {
|
|
101
|
+
try {
|
|
102
|
+
return (await readdir(directory)).filter((name) => name.endsWith(".request.json")).sort();
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error.code === "ENOENT")
|
|
106
|
+
return [];
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function projectTransportFailure(directory, transportId, claim, error) {
|
|
111
|
+
await atomicJson(receiptPath(directory, transportId), {
|
|
112
|
+
id: claim.id,
|
|
113
|
+
action: claim.action,
|
|
114
|
+
state: "refused",
|
|
115
|
+
diagnostic: error instanceof Error ? error.message : String(error),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function matchingRequestOrigin(soul, parent, id) {
|
|
119
|
+
return soul.origin.kind === "request" && soul.origin.parent === parent && soul.origin.requestId === id;
|
|
120
|
+
}
|
|
121
|
+
async function settleReservedSoul(paths, parent, request, soul) {
|
|
122
|
+
await (matchingRequestOrigin(soul, parent.id, request.id)
|
|
123
|
+
? serveRequest(paths, request.id, request.child)
|
|
124
|
+
: voidRequest(paths, request.id, "reserved child origin does not match the request"));
|
|
125
|
+
}
|
|
126
|
+
async function settleReserved(paths, parent, request, now, signal) {
|
|
127
|
+
const childPaths = pathsForAkuId(request.world, request.child);
|
|
128
|
+
const deadline = performance.now() + BIRTH_TIMEOUT_MS;
|
|
129
|
+
for (;;) {
|
|
130
|
+
signal?.throwIfAborted();
|
|
131
|
+
if (!(await access(childPaths.directory).then(() => true, () => false))) {
|
|
132
|
+
await voidRequest(paths, request.id, "reserved child directory is absent");
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
const soul = await readSoul(childPaths);
|
|
136
|
+
if (soul !== null) {
|
|
137
|
+
await settleReservedSoul(paths, parent, request, soul);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
const leash = await HeldAkumaLeash.try(childPaths);
|
|
141
|
+
if (leash !== null) {
|
|
142
|
+
try {
|
|
143
|
+
const settled = await readSoul(childPaths);
|
|
144
|
+
if (settled !== null)
|
|
145
|
+
await settleReservedSoul(paths, parent, request, settled);
|
|
146
|
+
else {
|
|
147
|
+
await leash.sealIfUnborn(childPaths, { evidence: "request settlement", at: now() });
|
|
148
|
+
await voidRequest(paths, request.id, "reserved child was sealed unborn");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
leash.release();
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (performance.now() >= deadline)
|
|
157
|
+
return false;
|
|
158
|
+
await abortableDelay(Math.min(POLL_MS, Math.max(0, deadline - performance.now())), signal);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export async function settleBodyRequests(paths, parent, now, signal) {
|
|
162
|
+
let pending = false;
|
|
163
|
+
for (const request of await readNonterminalRequests(paths)) {
|
|
164
|
+
signal?.throwIfAborted();
|
|
165
|
+
if (request.state === "admitted")
|
|
166
|
+
await voidRequest(paths, request.id, "body died before serving the request");
|
|
167
|
+
else if (request.state === "reserved" && !(await settleReserved(paths, parent, request, now, signal)))
|
|
168
|
+
pending = true;
|
|
169
|
+
}
|
|
170
|
+
return pending ? "pending" : "settled";
|
|
171
|
+
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { type KillEvidence, type RequestFact
|
|
2
|
-
import { type AkuId
|
|
1
|
+
import { type KillEvidence, type RequestFact } from "./heart/index.js";
|
|
2
|
+
import { type AkuId } from "./identity.js";
|
|
3
3
|
import { type StructuralRequestClaim } from "./request-wire.js";
|
|
4
4
|
import { type TaskMutationRequest } from "../task/mutation.js";
|
|
5
|
-
import type
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
seed: Omit<Soul, "createdAt">;
|
|
9
|
-
initialBody: string;
|
|
10
|
-
}>;
|
|
5
|
+
import { BodyRequestPump as LifecycleBodyRequestPump, type RequestChildLaunch as LifecycleRequestChildLaunch, type PumpInput as LifecyclePumpInput } from "./request-lifecycle.js";
|
|
6
|
+
export { clearBodyRequestTransport, settleBodyRequests } from "./request-lifecycle.js";
|
|
7
|
+
export type RequestChildLaunch = LifecycleRequestChildLaunch;
|
|
11
8
|
type UpstreamCall = Readonly<{
|
|
12
9
|
signal: AbortSignal;
|
|
13
10
|
}>;
|
|
@@ -60,17 +57,12 @@ export type UpstreamExecutionPort = Readonly<{
|
|
|
60
57
|
request: TaskMutationRequest;
|
|
61
58
|
}>): Promise<unknown>;
|
|
62
59
|
}>;
|
|
63
|
-
type PumpInput = Readonly<{
|
|
64
|
-
paths: AkumaPaths;
|
|
65
|
-
parent: Soul;
|
|
66
|
-
bodySequence: number;
|
|
67
|
-
now(): string;
|
|
68
|
-
spawn(launch: RequestChildLaunch): Promise<OwnedProcess | void>;
|
|
60
|
+
type PumpInput = Omit<LifecyclePumpInput, "upstream"> & Readonly<{
|
|
69
61
|
upstream?: UpstreamExecutionPort;
|
|
70
|
-
signal: AbortSignal;
|
|
71
62
|
}>;
|
|
72
63
|
export type ServeInput = Omit<PumpInput, "bodySequence"> & Readonly<{
|
|
73
64
|
directory: string;
|
|
65
|
+
transportId: string;
|
|
74
66
|
claim: StructuralRequestClaim;
|
|
75
67
|
admissionOpen(): boolean;
|
|
76
68
|
}>;
|
|
@@ -79,22 +71,7 @@ export type UpstreamFact = Exclude<Extract<RequestFact, {
|
|
|
79
71
|
}>, Extract<RequestFact, {
|
|
80
72
|
action: "akuma.call";
|
|
81
73
|
}>>;
|
|
82
|
-
export declare
|
|
83
|
-
|
|
84
|
-
readonly directory: string;
|
|
85
|
-
readonly failure: Promise<never>;
|
|
86
|
-
private readonly closeSignal;
|
|
87
|
-
private readonly executionSignal;
|
|
88
|
-
private readonly handled;
|
|
89
|
-
private readonly running;
|
|
90
|
-
private readonly cancelBody;
|
|
91
|
-
private admissionClosed;
|
|
92
|
-
private constructor();
|
|
74
|
+
export declare function serveRequest(input: ServeInput): Promise<void>;
|
|
75
|
+
export declare class BodyRequestPump extends LifecycleBodyRequestPump {
|
|
93
76
|
static open(input: PumpInput): Promise<BodyRequestPump>;
|
|
94
|
-
private run;
|
|
95
|
-
close(): Promise<void>;
|
|
96
|
-
stopAdmission(): void;
|
|
97
77
|
}
|
|
98
|
-
export declare function clearBodyRequestTransport(paths: AkumaPaths): Promise<void>;
|
|
99
|
-
export declare function settleBodyRequests(paths: AkumaPaths, parent: Soul, now: () => string, signal?: AbortSignal): Promise<"settled" | "pending">;
|
|
100
|
-
export {};
|