@astrosheep/keiyaku 4.1.0 → 4.1.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/README.md +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +3 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -6
- package/build/src/akuma/akuma-errors.d.ts +6 -0
- package/build/src/akuma/akuma-errors.js +9 -0
- package/build/src/akuma/akuma-handle.d.ts +39 -0
- package/build/src/akuma/akuma-handle.js +263 -0
- package/build/src/akuma/akuma-observe.d.ts +19 -0
- package/build/src/akuma/akuma-observe.js +74 -0
- package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
- package/build/src/akuma/akuma-product-symbols.js +1 -0
- package/build/src/akuma/akuma-product.d.ts +18 -0
- package/build/src/akuma/akuma-product.js +154 -0
- package/build/src/akuma/akuma.d.ts +37 -81
- package/build/src/akuma/akuma.js +39 -447
- package/build/src/akuma/allowed.d.ts +1 -0
- package/build/src/akuma/allowed.js +6 -0
- package/build/src/akuma/archetype.js +8 -5
- package/build/src/akuma/body-supervisor.d.ts +26 -0
- package/build/src/akuma/body-supervisor.js +89 -0
- package/build/src/akuma/body.d.ts +45 -4
- package/build/src/akuma/body.js +145 -35
- package/build/src/akuma/coordinate.js +17 -7
- package/build/src/akuma/heart/facts.js +1 -1
- package/build/src/akuma/heart/index.d.ts +1 -1
- package/build/src/akuma/heart/index.js +2 -4
- package/build/src/akuma/heart/request-authority.js +4 -4
- package/build/src/akuma/heart/request-rows.js +27 -16
- package/build/src/akuma/heart/rows.js +79 -41
- package/build/src/akuma/heart/soul.js +1 -1
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +76 -11
- package/build/src/akuma/heart/tells.js +35 -18
- package/build/src/akuma/heart/timeline.js +19 -11
- package/build/src/akuma/identity.d.ts +1 -0
- package/build/src/akuma/identity.js +1 -0
- package/build/src/akuma/index.d.ts +3 -3
- package/build/src/akuma/index.js +1 -1
- package/build/src/akuma/nuke.js +106 -14
- package/build/src/akuma/projection-read.d.ts +17 -0
- package/build/src/akuma/projection-read.js +135 -0
- package/build/src/akuma/projection.d.ts +1 -16
- package/build/src/akuma/projection.js +77 -154
- package/build/src/akuma/provider-recipe.js +11 -5
- package/build/src/akuma/provider.js +105 -62
- package/build/src/akuma/providers/acp/config.js +5 -6
- package/build/src/akuma/providers/acp/core.js +17 -10
- package/build/src/akuma/providers/acp/events.js +38 -29
- package/build/src/akuma/providers/acp/index.js +13 -9
- package/build/src/akuma/providers/claude/events.js +8 -12
- package/build/src/akuma/providers/claude/index.d.ts +1 -0
- package/build/src/akuma/providers/claude/index.js +55 -26
- package/build/src/akuma/providers/claude/input.js +6 -2
- package/build/src/akuma/providers/codex-app-server/events.js +61 -36
- package/build/src/akuma/providers/codex-app-server/index.js +25 -13
- package/build/src/akuma/providers/grok-build/index.js +22 -25
- package/build/src/akuma/providers/index.js +2 -18
- package/build/src/akuma/providers/opencode-sdk/events.js +32 -18
- package/build/src/akuma/providers/opencode-sdk/index.js +26 -17
- package/build/src/akuma/providers/opencode-sdk/session.js +21 -5
- package/build/src/akuma/providers/pi/events.js +26 -19
- package/build/src/akuma/providers/pi/index.js +58 -40
- package/build/src/akuma/publication.js +9 -9
- package/build/src/akuma/request-execution.d.ts +6 -0
- package/build/src/akuma/request-execution.js +86 -0
- package/build/src/akuma/request-serve.d.ts +12 -1
- package/build/src/akuma/request-serve.js +19 -122
- package/build/src/akuma/request-wire.d.ts +2 -2
- package/build/src/akuma/request-wire.js +75 -102
- package/build/src/akuma/requests.js +2 -2
- package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
- package/build/src/akuma/{body-turn.js → turn-drive.js} +20 -103
- package/build/src/akuma-body.js +6 -5
- package/build/src/body/amend.d.ts +4 -1
- package/build/src/body/amend.js +14 -6
- package/build/src/body/decode.js +2 -4
- package/build/src/body/envelope.js +2 -4
- package/build/src/body/region.d.ts +1 -2
- package/build/src/body/region.js +2 -70
- package/build/src/body/render.js +17 -12
- package/build/src/body/shape.js +1 -7
- package/build/src/cli/accepted.d.ts +1 -1
- package/build/src/cli/accepted.js +8 -1
- package/build/src/cli/commands/akuma-invoke.js +18 -6
- package/build/src/cli/commands/akuma.d.ts +0 -6
- package/build/src/cli/commands/akuma.js +11 -24
- package/build/src/cli/commands/amend.d.ts +2 -2
- package/build/src/cli/commands/amend.js +2 -4
- package/build/src/cli/commands/bind.d.ts +4 -4
- package/build/src/cli/commands/bind.js +16 -6
- package/build/src/cli/commands/contract-invoke.d.ts +25 -0
- package/build/src/cli/commands/contract-invoke.js +193 -0
- package/build/src/cli/commands/contract.d.ts +109 -10
- package/build/src/cli/commands/contract.js +339 -16
- package/build/src/cli/commands/install.d.ts +1 -1
- package/build/src/cli/commands/install.js +23 -15
- package/build/src/cli/commands/task-invoke.js +187 -67
- package/build/src/cli/commands/task-query.js +48 -24
- package/build/src/cli/commands/task.js +150 -38
- package/build/src/cli/coordinates.js +2 -4
- package/build/src/cli/invoke.js +92 -208
- package/build/src/cli/main.js +4 -2
- package/build/src/cli/parse.d.ts +3 -90
- package/build/src/cli/parse.js +27 -233
- package/build/src/cli/render/akuma-activity.d.ts +38 -0
- package/build/src/cli/render/akuma-activity.js +362 -0
- package/build/src/cli/render/akuma-tool-command.js +6 -7
- package/build/src/cli/render/akuma-tool.js +10 -8
- package/build/src/cli/render/akuma.d.ts +1 -1
- package/build/src/cli/render/akuma.js +61 -353
- package/build/src/cli/render/audit.js +5 -15
- package/build/src/cli/render/board.d.ts +1 -0
- package/build/src/cli/render/board.js +17 -0
- package/build/src/cli/render/catalog.js +81 -8
- package/build/src/cli/render/contract-history.d.ts +2 -0
- package/build/src/cli/render/contract-history.js +83 -0
- package/build/src/cli/render/contract-observation.d.ts +10 -0
- package/build/src/cli/render/contract-observation.js +58 -0
- package/build/src/cli/render/contract.d.ts +1 -2
- package/build/src/cli/render/contract.js +41 -142
- package/build/src/cli/render/kanshi.js +239 -215
- package/build/src/cli/render/receipt.js +2 -9
- package/build/src/cli/render/refusal.js +10 -15
- package/build/src/cli/render/region.js +22 -6
- package/build/src/cli/render/settings.js +4 -4
- package/build/src/cli/render/task.js +34 -14
- package/build/src/cli/render/terminal.js +16 -5
- package/build/src/cli/result.d.ts +3 -3
- package/build/src/cli/runtime.d.ts +6 -1
- package/build/src/cli/runtime.js +84 -63
- package/build/src/cli/selectors.js +2 -4
- package/build/src/cli/usage.js +6 -5
- package/build/src/contract-worktree.d.ts +1 -23
- package/build/src/contract-worktree.js +79 -155
- package/build/src/coordination/durable-file.js +7 -3
- package/build/src/coordination/sqlite-transaction-lock.js +29 -12
- package/build/src/core/facts/codec.js +7 -3
- package/build/src/core/facts/types.d.ts +1 -1
- package/build/src/core/subject.js +16 -9
- package/build/src/core/verbs/abandon.d.ts +1 -1
- package/build/src/core/verbs/abandon.js +1 -1
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +7 -7
- package/build/src/core/verbs/attestation.js +4 -1
- package/build/src/core/verbs/bind.d.ts +1 -1
- package/build/src/core/verbs/bind.js +1 -1
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +10 -8
- package/build/src/dispatch/index.js +8 -6
- package/build/src/duration.js +5 -1
- package/build/src/git/admission.js +5 -3
- package/build/src/git/hooks.d.ts +8 -0
- package/build/src/git/hooks.js +63 -14
- package/build/src/git/identity.js +1 -4
- package/build/src/git/integration.d.ts +2 -3
- package/build/src/git/integration.js +32 -39
- package/build/src/git/nuke.d.ts +1 -1
- package/build/src/git/nuke.js +110 -57
- package/build/src/git/observe.d.ts +13 -1
- package/build/src/git/observe.js +51 -44
- package/build/src/git/process.js +4 -2
- package/build/src/git/read-observation.js +10 -8
- package/build/src/git/reconcile.d.ts +22 -2
- package/build/src/git/reconcile.js +38 -274
- package/build/src/git/repository.d.ts +2 -0
- package/build/src/git/repository.js +13 -22
- package/build/src/git/scratch.d.ts +8 -5
- package/build/src/git/scratch.js +29 -2
- package/build/src/git/target-placement.d.ts +9 -10
- package/build/src/git/target-placement.js +59 -62
- package/build/src/git/tender.js +13 -46
- package/build/src/git/terminal-reconcile.d.ts +21 -0
- package/build/src/git/terminal-reconcile.js +278 -0
- package/build/src/git/terminal-seal.js +15 -12
- package/build/src/git/tree.js +5 -2
- package/build/src/git/workspace.d.ts +24 -2
- package/build/src/git/workspace.js +120 -16
- package/build/src/identity/coordinates.js +3 -1
- package/build/src/identity/selector.js +6 -3
- package/build/src/kanshi/fleet.d.ts +6 -0
- package/build/src/kanshi/fleet.js +27 -0
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.js +60 -48
- package/build/src/kanshi/report.d.ts +11 -23
- package/build/src/kanshi/select.js +25 -24
- package/build/src/library/address.d.ts +2 -1
- package/build/src/library/address.js +7 -8
- package/build/src/library/akuma-creation.js +20 -12
- package/build/src/library/audit.d.ts +0 -1
- package/build/src/library/audit.js +1 -2
- package/build/src/library/bind.d.ts +53 -5
- package/build/src/library/bind.js +98 -22
- package/build/src/library/catalog.d.ts +1 -0
- package/build/src/library/catalog.js +6 -3
- package/build/src/library/configuration.js +3 -1
- package/build/src/library/continuation.d.ts +15 -4
- package/build/src/library/continuation.js +24 -16
- package/build/src/library/contract-bind.d.ts +26 -0
- package/build/src/library/contract-bind.js +106 -0
- package/build/src/library/contract-operations.d.ts +77 -0
- package/build/src/library/contract-operations.js +130 -0
- package/build/src/library/contract-types.d.ts +95 -0
- package/build/src/library/contract-types.js +1 -0
- package/build/src/library/contract.d.ts +19 -154
- package/build/src/library/contract.js +46 -261
- package/build/src/library/fleet.d.ts +1 -1
- package/build/src/library/fleet.js +19 -11
- package/build/src/library/input.d.ts +2 -0
- package/build/src/library/input.js +14 -0
- package/build/src/library/keiyaku.d.ts +8 -7
- package/build/src/library/keiyaku.js +3 -3
- package/build/src/library/nuke.js +14 -5
- package/build/src/library/reconcile.d.ts +9 -4
- package/build/src/library/reconcile.js +30 -24
- package/build/src/library/refusal.d.ts +13 -5
- package/build/src/library/refusal.js +7 -0
- package/build/src/library/region.d.ts +8 -7
- package/build/src/library/region.js +31 -11
- package/build/src/library/repo.js +2 -2
- package/build/src/markdown/lex.js +9 -2
- package/build/src/markdown/parse.js +11 -7
- package/build/src/markdown/query.js +4 -2
- package/build/src/protocol/amend.js +3 -1
- package/build/src/protocol/attempt.d.ts +7 -3
- package/build/src/protocol/attempt.js +9 -5
- package/build/src/protocol/audit.d.ts +1 -1
- package/build/src/protocol/audit.js +4 -14
- package/build/src/protocol/bind.d.ts +19 -6
- package/build/src/protocol/bind.js +28 -13
- package/build/src/protocol/completion.d.ts +0 -1
- package/build/src/protocol/completion.js +1 -3
- package/build/src/protocol/deliver.d.ts +1 -3
- package/build/src/protocol/deliver.js +15 -39
- package/build/src/protocol/intent.d.ts +1 -0
- package/build/src/protocol/intent.js +9 -7
- package/build/src/protocol/operations.d.ts +3 -7
- package/build/src/protocol/operations.js +5 -3
- package/build/src/protocol/placement.d.ts +0 -1
- package/build/src/protocol/placement.js +6 -6
- package/build/src/protocol/read/observation.d.ts +11 -0
- package/build/src/protocol/read/observation.js +37 -0
- package/build/src/protocol/read/status.d.ts +23 -12
- package/build/src/protocol/read/status.js +90 -72
- package/build/src/protocol/reconcile.d.ts +1 -0
- package/build/src/protocol/reconcile.js +2 -3
- package/build/src/protocol/reintegrate.js +1 -1
- package/build/src/protocol/review.d.ts +0 -1
- package/build/src/protocol/review.js +15 -22
- package/build/src/protocol/run.d.ts +1 -0
- package/build/src/protocol/run.js +4 -5
- package/build/src/runtime/proc/launch.d.ts +33 -0
- package/build/src/runtime/proc/launch.js +98 -0
- package/build/src/runtime/proc/line-rpc.js +9 -3
- package/build/src/runtime/proc/run.d.ts +12 -6
- package/build/src/runtime/proc/run.js +78 -21
- package/build/src/runtime/proc/stdio.js +7 -10
- package/build/src/settings.js +6 -2
- package/build/src/settlement/holder.js +13 -10
- package/build/src/settlement/settle.js +39 -6
- package/build/src/task/board.js +12 -6
- package/build/src/task/compose.js +85 -22
- package/build/src/task/context.js +21 -8
- package/build/src/task/document.js +46 -10
- package/build/src/task/identity.js +5 -2
- package/build/src/task/index.d.ts +0 -1
- package/build/src/task/index.js +119 -34
- package/build/src/task/input.js +29 -7
- package/build/src/task/mutation.js +61 -19
- package/build/src/task/operations.d.ts +6 -1
- package/build/src/task/operations.js +83 -29
- package/build/src/task/query.js +55 -26
- package/build/src/task/store.d.ts +3 -1
- package/build/src/task/store.js +81 -17
- package/build/src/verification/execution.js +14 -11
- package/build/src/workspace-place.js +19 -7
- package/build/src/world.js +16 -6
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ did not land until the gates had current evidence.
|
|
|
94
94
|
kanshi ─ 7 keiyaku · 18 akuma · 286 task ─ /Users/astrosheep/Developer/keiyaku-v4 main 9cfdca6017633e51827b9b2eba3c76a7fe08e05f
|
|
95
95
|
|
|
96
96
|
keiyaku 7
|
|
97
|
-
⧗ kei/add-acp-provider-and-grok-build-profile
|
|
97
|
+
⧗ kei/add-acp-provider-and-grok-build-profile tendered
|
|
98
98
|
worktree · integration c5cafef6 · -> refs/heads/main
|
|
99
99
|
! reviewed
|
|
100
100
|
⧗ kei/align-task-cli-truth-promises waiting
|
|
@@ -39,4 +39,7 @@ keiyaku -C <repo> tell <aku/...|@alias> (<prompt> | -)
|
|
|
39
39
|
`-C` is the invocation and Akuma execution cwd. Add `--repo <path>` only when
|
|
40
40
|
a Contract or Dispatch reader must use a different Git repository.
|
|
41
41
|
|
|
42
|
+
Repeated `--allowed` values add actions to the Archetype defaults. Restriction
|
|
43
|
+
comes from the Archetype base and, for nested calls, the direct parent Soul.
|
|
44
|
+
|
|
42
45
|
Use text by default; add `--json` when a script needs structured output.
|
|
@@ -29,6 +29,9 @@ exclusive. Decide up front whether you will stay:
|
|
|
29
29
|
Alias already points elsewhere, it moves to the born Akuma. The worker's
|
|
30
30
|
execution cwd is exactly `-C <path>`, or your own cwd when `-C` is omitted.
|
|
31
31
|
|
|
32
|
+
Repeated `--allowed` values add actions to the Archetype defaults. Restriction
|
|
33
|
+
comes from the Archetype base and, for nested calls, the direct parent Soul.
|
|
34
|
+
|
|
32
35
|
## Archetypes
|
|
33
36
|
|
|
34
37
|
An Archetype is a Markdown file at `~/.keiyaku/akuma/<name>.md`. Its
|
|
@@ -17,7 +17,7 @@ contract document -> bind -> work -> audit -> deliver -> review gates
|
|
|
17
17
|
\-> abandoned
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
The lifecycle is `waiting -> bound ->
|
|
20
|
+
The lifecycle is `waiting -> bound -> tendered -> claimed | abandoned`;
|
|
21
21
|
the last two are terminal. Reserve `--after` for true logical ordering: one
|
|
22
22
|
Contract's result must ultimately build on another's settled outcome, or their
|
|
23
23
|
intended work has a large or irreconcilable interaction that should be
|
|
@@ -219,7 +219,7 @@ included. Read the receipt:
|
|
|
219
219
|
delivery is done.
|
|
220
220
|
- When a gate is not current, the receipt shows the recorded candidate and the
|
|
221
221
|
placement stop. This is not a failed delivery. The Contract stays
|
|
222
|
-
`
|
|
222
|
+
`tendered` while you complete the gates.
|
|
223
223
|
- A lag row reports an accepted physical effect that has not finished. The
|
|
224
224
|
delivery stands; `reconcile` completes the effect later. It never changes
|
|
225
225
|
the verdict.
|
|
@@ -283,10 +283,7 @@ Placement follows the Git mental model you already have:
|
|
|
283
283
|
The deliver or review you just ran still counts: the recorded candidate and
|
|
284
284
|
any `✓ reviewed` verdict are kept, but nothing claims and nothing moves.
|
|
285
285
|
The target ref, its checkout, and your bytes stay exactly where they were,
|
|
286
|
-
and the Contract stays `
|
|
287
|
-
- A `--here` Contract behaves like a commit with gates: it lands on the current
|
|
288
|
-
branch and cannot deliver to a foreign checked-out target.
|
|
289
|
-
|
|
286
|
+
and the Contract stays `tendered`.
|
|
290
287
|
After a refusal, handle the listed paths, then `deliver` again or record a
|
|
291
288
|
satisfied review; either command requests placement again.
|
|
292
289
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type TellResult } from "./body.js";
|
|
2
|
+
import { type KillEvidence } from "./heart/index.js";
|
|
3
|
+
import { type AkuId } from "./identity.js";
|
|
4
|
+
import { type ActivityHistory } from "./projection.js";
|
|
5
|
+
import type { AkumaCallExecution, AkumaStatus, ForkReceipt, InterruptReceipt } from "./akuma.js";
|
|
6
|
+
import type { WorldRoot } from "../world.js";
|
|
7
|
+
declare const CALL_EXECUTION: unique symbol;
|
|
8
|
+
export declare class AkumaHandle {
|
|
9
|
+
readonly id: AkuId;
|
|
10
|
+
private readonly worldPath;
|
|
11
|
+
readonly [CALL_EXECUTION]?: AkumaCallExecution;
|
|
12
|
+
constructor(id: AkuId, worldPath: WorldRoot, execution?: AkumaCallExecution);
|
|
13
|
+
private get paths();
|
|
14
|
+
status(): Promise<AkumaStatus>;
|
|
15
|
+
history(input?: Readonly<{
|
|
16
|
+
before?: number;
|
|
17
|
+
since?: number;
|
|
18
|
+
limit?: number;
|
|
19
|
+
}>): Promise<ActivityHistory>;
|
|
20
|
+
wait(predicate?: (status: AkumaStatus) => boolean, options?: Readonly<{
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
}>): Promise<AkumaStatus>;
|
|
23
|
+
tell(body: string): Promise<TellResult>;
|
|
24
|
+
interrupt(body: string): Promise<InterruptReceipt>;
|
|
25
|
+
fork(input: Readonly<{
|
|
26
|
+
at: string;
|
|
27
|
+
}>): Promise<ForkReceipt>;
|
|
28
|
+
kill(): Promise<KillEvidence>;
|
|
29
|
+
lastAnswer(): Promise<LastAnswer>;
|
|
30
|
+
}
|
|
31
|
+
/** Package-internal provenance retained only by the handle returned from call. */
|
|
32
|
+
export declare function akumaCallExecution(handle: AkumaHandle): AkumaCallExecution | undefined;
|
|
33
|
+
export type LastAnswer = Readonly<{
|
|
34
|
+
kind: "answer";
|
|
35
|
+
answer: string;
|
|
36
|
+
}> | Readonly<{
|
|
37
|
+
kind: "no-answer";
|
|
38
|
+
}>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { CONTROL_RESPONSE_MS, wakeRecordedTell } from "./body.js";
|
|
3
|
+
import { HeldAkumaLeash, activitySlice, readForkPoint, readHeart, readKill, readLastAnsweredTurn, readSoul, recordTell, requestPause, requestStop, } from "./heart/index.js";
|
|
4
|
+
import { pathsForAkuId } from "./identity.js";
|
|
5
|
+
import { projectTurns, selectHistory } from "./projection.js";
|
|
6
|
+
import { resolveProviderExecution } from "./providers/index.js";
|
|
7
|
+
import { publishAkuma } from "./publication.js";
|
|
8
|
+
import { spawnAkumaBody } from "./body.js";
|
|
9
|
+
import { AkumaNotBornError } from "./akuma-errors.js";
|
|
10
|
+
import { bornStatus } from "./akuma-observe.js";
|
|
11
|
+
const CALL_EXECUTION = Symbol("akuma-call-execution");
|
|
12
|
+
const POLL_MS = 100;
|
|
13
|
+
const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
14
|
+
function diagnostic(error) {
|
|
15
|
+
return error instanceof Error ? error.message : String(error);
|
|
16
|
+
}
|
|
17
|
+
function defaultWaitComplete(status) {
|
|
18
|
+
return (status.life !== "running" &&
|
|
19
|
+
!status.timeline.entries.some((entry) => entry.kind === "row" && entry.row.kind === "tell" && entry.row.state === "pending"));
|
|
20
|
+
}
|
|
21
|
+
async function takeLeashUntil(paths, deadline) {
|
|
22
|
+
for (;;) {
|
|
23
|
+
const leash = await HeldAkumaLeash.try(paths);
|
|
24
|
+
if (leash !== null)
|
|
25
|
+
return leash;
|
|
26
|
+
if (performance.now() >= deadline)
|
|
27
|
+
return null;
|
|
28
|
+
await wait(Math.min(POLL_MS, Math.max(0, deadline - performance.now())));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function recordTellBody(paths, akuma, body, id = randomUUID(), recordedAt = new Date().toISOString()) {
|
|
32
|
+
const admitted = await recordTell(paths, { kind: "tell", id, body, recordedAt });
|
|
33
|
+
if (admitted.kind === "not-born")
|
|
34
|
+
throw new AkumaNotBornError(akuma);
|
|
35
|
+
return { kind: "recorded", tellId: admitted.tell.id };
|
|
36
|
+
}
|
|
37
|
+
async function killAkumaWithRecovery(paths, recover = async () => { }) {
|
|
38
|
+
try {
|
|
39
|
+
const request = await requestStop(paths, new Date().toISOString());
|
|
40
|
+
if (request.kind !== "requested")
|
|
41
|
+
return request.kind;
|
|
42
|
+
const target = request.body;
|
|
43
|
+
const leash = await takeLeashUntil(paths, performance.now() + CONTROL_RESPONSE_MS);
|
|
44
|
+
if ((await readKill(paths, target.sequence)) !== null) {
|
|
45
|
+
leash?.release();
|
|
46
|
+
return "killed";
|
|
47
|
+
}
|
|
48
|
+
if (leash === null) {
|
|
49
|
+
if ((await readKill(paths, target.sequence)) !== null)
|
|
50
|
+
return "killed";
|
|
51
|
+
const body = (await readHeart(paths)).latestBody;
|
|
52
|
+
return body?.sequence === target.sequence && body.hung !== undefined ? "hung" : "unavailable";
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const settledBody = (await readHeart(paths)).latestBody;
|
|
56
|
+
if (settledBody?.sequence !== target.sequence)
|
|
57
|
+
return (await readKill(paths, target.sequence)) === null ? "unavailable" : "killed";
|
|
58
|
+
if (settledBody.end !== "put-down") {
|
|
59
|
+
await leash.clearStop(paths);
|
|
60
|
+
return "untidy";
|
|
61
|
+
}
|
|
62
|
+
const settled = await leash.settleStop(paths, target.sequence);
|
|
63
|
+
return settled === null ? "unavailable" : "killed";
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
leash.release();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
void recover(paths).catch(() => undefined);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export class AkumaHandle {
|
|
74
|
+
id;
|
|
75
|
+
worldPath;
|
|
76
|
+
[CALL_EXECUTION];
|
|
77
|
+
constructor(id, worldPath, execution) {
|
|
78
|
+
this.id = id;
|
|
79
|
+
this.worldPath = worldPath;
|
|
80
|
+
if (execution !== undefined)
|
|
81
|
+
this[CALL_EXECUTION] = execution;
|
|
82
|
+
}
|
|
83
|
+
get paths() {
|
|
84
|
+
return pathsForAkuId(this.worldPath, this.id);
|
|
85
|
+
}
|
|
86
|
+
async status() {
|
|
87
|
+
return (await bornStatus(this.paths, this.id, { aperture: "monitoring" })).status;
|
|
88
|
+
}
|
|
89
|
+
async history(input = {}) {
|
|
90
|
+
if (input.before !== undefined && input.since !== undefined) {
|
|
91
|
+
throw new TypeError("Akuma history before and since are mutually exclusive");
|
|
92
|
+
}
|
|
93
|
+
for (const [name, value] of [
|
|
94
|
+
["before", input.before],
|
|
95
|
+
["since", input.since],
|
|
96
|
+
]) {
|
|
97
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {
|
|
98
|
+
throw new TypeError(`Akuma history ${name} must be a positive safe integer`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const limit = input.limit ?? 50;
|
|
102
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 5_000) {
|
|
103
|
+
throw new TypeError("Akuma history limit must be a positive safe integer no greater than 5000");
|
|
104
|
+
}
|
|
105
|
+
const slice = await activitySlice(this.paths);
|
|
106
|
+
return selectHistory(projectTurns(slice.rows, {
|
|
107
|
+
lowestRetained: slice.lowestRetained,
|
|
108
|
+
highest: slice.highest,
|
|
109
|
+
}), {
|
|
110
|
+
...(input.before === undefined ? {} : { before: input.before }),
|
|
111
|
+
...(input.since === undefined ? {} : { since: input.since }),
|
|
112
|
+
limit,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async wait(predicate = defaultWaitComplete, options = {}) {
|
|
116
|
+
if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 0)) {
|
|
117
|
+
throw new TypeError("Akuma wait timeoutMs must be a nonnegative finite millisecond duration");
|
|
118
|
+
}
|
|
119
|
+
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
120
|
+
for (;;) {
|
|
121
|
+
const status = await this.status();
|
|
122
|
+
if (predicate(status) || (deadline !== undefined && performance.now() >= deadline))
|
|
123
|
+
return status;
|
|
124
|
+
await wait(deadline === undefined ? POLL_MS : Math.min(POLL_MS, Math.max(0, deadline - performance.now())));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async tell(body) {
|
|
128
|
+
const recorded = await recordTellBody(this.paths, this.id, body);
|
|
129
|
+
return await wakeRecordedTell(this.paths, recorded.tellId);
|
|
130
|
+
}
|
|
131
|
+
async interrupt(body) {
|
|
132
|
+
const request = await requestPause(this.paths, new Date().toISOString());
|
|
133
|
+
if (request.kind === "not-born") {
|
|
134
|
+
throw new AkumaNotBornError(this.id);
|
|
135
|
+
}
|
|
136
|
+
let putDown = "was-idle";
|
|
137
|
+
let leash = await HeldAkumaLeash.try(this.paths);
|
|
138
|
+
if (leash === null) {
|
|
139
|
+
leash = await takeLeashUntil(this.paths, performance.now() + CONTROL_RESPONSE_MS);
|
|
140
|
+
putDown = "self-aborted";
|
|
141
|
+
}
|
|
142
|
+
if (leash === null) {
|
|
143
|
+
const body = (await readHeart(this.paths)).latestBody;
|
|
144
|
+
return {
|
|
145
|
+
kind: "unavailable",
|
|
146
|
+
evidence: body?.sequence === request.body.sequence && body.hung !== undefined ? "hung" : "unavailable",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const settledBody = (await readHeart(this.paths)).latestBody;
|
|
150
|
+
if (settledBody?.sequence === request.body.sequence && settledBody.hung !== undefined) {
|
|
151
|
+
try {
|
|
152
|
+
await leash.clearPause(this.paths);
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
leash.release();
|
|
156
|
+
}
|
|
157
|
+
return { kind: "unavailable", evidence: "hung" };
|
|
158
|
+
}
|
|
159
|
+
if (settledBody?.sequence !== request.body.sequence || settledBody.end === undefined) {
|
|
160
|
+
try {
|
|
161
|
+
await leash.clearPause(this.paths);
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
leash.release();
|
|
165
|
+
}
|
|
166
|
+
return { kind: "unavailable", evidence: "untidy" };
|
|
167
|
+
}
|
|
168
|
+
if (request.body.end !== undefined || settledBody.end !== "put-down")
|
|
169
|
+
putDown = "was-idle";
|
|
170
|
+
let recorded;
|
|
171
|
+
try {
|
|
172
|
+
const id = randomUUID();
|
|
173
|
+
const admitted = await leash.recordInterruptTell(this.paths, {
|
|
174
|
+
kind: "tell",
|
|
175
|
+
id,
|
|
176
|
+
body,
|
|
177
|
+
recordedAt: new Date().toISOString(),
|
|
178
|
+
});
|
|
179
|
+
if (admitted.kind === "not-born")
|
|
180
|
+
throw new AkumaNotBornError(this.id);
|
|
181
|
+
recorded = { kind: "recorded", tellId: admitted.tell.id };
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
leash.release();
|
|
185
|
+
}
|
|
186
|
+
return { kind: "interrupted", putDown, tell: await wakeRecordedTell(this.paths, recorded.tellId) };
|
|
187
|
+
}
|
|
188
|
+
async fork(input) {
|
|
189
|
+
const source = await readSoul(this.paths);
|
|
190
|
+
if (source === null)
|
|
191
|
+
throw new AkumaNotBornError(this.id);
|
|
192
|
+
if (source.id !== this.id)
|
|
193
|
+
throw new Error("Akuma soul does not match its coordinate");
|
|
194
|
+
const adapter = (await resolveProviderExecution(source.provider)).adapter;
|
|
195
|
+
if (adapter.fork === undefined)
|
|
196
|
+
return { kind: "provider-cannot-fork", provider: source.provider.name };
|
|
197
|
+
const point = await readForkPoint(this.paths, input.at);
|
|
198
|
+
if (point === null)
|
|
199
|
+
return { kind: "unknown-history", at: input.at };
|
|
200
|
+
if (point.provider !== source.provider.name)
|
|
201
|
+
throw new Error(`Akuma fork point ${input.at} has a mismatched provider`);
|
|
202
|
+
let childSession;
|
|
203
|
+
try {
|
|
204
|
+
childSession = (await adapter.fork({ session: point.session, at: point.historyId, cwd: point.cwd })).session;
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
return { kind: "fork-failed", diagnostic: diagnostic(error) };
|
|
208
|
+
}
|
|
209
|
+
const admittedAt = new Date().toISOString();
|
|
210
|
+
const birthSession = {
|
|
211
|
+
provider: point.provider,
|
|
212
|
+
coordinate: childSession,
|
|
213
|
+
cwd: point.cwd,
|
|
214
|
+
options: point.options,
|
|
215
|
+
admittedAt,
|
|
216
|
+
};
|
|
217
|
+
try {
|
|
218
|
+
const child = await publishAkuma({
|
|
219
|
+
worldPath: this.worldPath,
|
|
220
|
+
archetype: source.archetype,
|
|
221
|
+
awaitAsleep: true,
|
|
222
|
+
launch: async (allocated) => {
|
|
223
|
+
(await spawnAkumaBody({
|
|
224
|
+
paths: allocated.paths,
|
|
225
|
+
seed: {
|
|
226
|
+
id: allocated.id,
|
|
227
|
+
archetype: source.archetype,
|
|
228
|
+
...(source.description === undefined ? {} : { description: source.description }),
|
|
229
|
+
provider: source.provider,
|
|
230
|
+
options: source.options,
|
|
231
|
+
...(source.readonly === undefined ? {} : { readonly: source.readonly }),
|
|
232
|
+
allowed: source.allowed,
|
|
233
|
+
cwd: source.cwd,
|
|
234
|
+
origin: { kind: "fork", parent: this.id, at: input.at },
|
|
235
|
+
},
|
|
236
|
+
birthSession,
|
|
237
|
+
})).release();
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
return { kind: "forked", child: child.id };
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
kind: "upstream-forked",
|
|
245
|
+
childSession,
|
|
246
|
+
diagnostic: diagnostic(error),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async kill() {
|
|
251
|
+
return await killAkumaWithRecovery(this.paths);
|
|
252
|
+
}
|
|
253
|
+
async lastAnswer() {
|
|
254
|
+
const turn = await readLastAnsweredTurn(this.paths);
|
|
255
|
+
return turn?.end?.outcome.kind === "answered"
|
|
256
|
+
? { kind: "answer", answer: turn.end.outcome.answer }
|
|
257
|
+
: { kind: "no-answer" };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/** Package-internal provenance retained only by the handle returned from call. */
|
|
261
|
+
export function akumaCallExecution(handle) {
|
|
262
|
+
return handle[CALL_EXECUTION];
|
|
263
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type AkuId, type AkumaPaths } from "./identity.js";
|
|
2
|
+
import { selectHistory, type ActivityHistory, type ActivitySnapshot } from "./projection.js";
|
|
3
|
+
import type { WorldRoot } from "../world.js";
|
|
4
|
+
import type { AkumaListRow, AkumaStatus, UnbornAkumaListRow } from "./akuma.js";
|
|
5
|
+
export declare function fleetListRow(paths: AkumaPaths, expected: AkuId): Promise<AkumaListRow | UnbornAkumaListRow>;
|
|
6
|
+
export type BudgetedStatusObservation = Readonly<{
|
|
7
|
+
status: AkumaStatus;
|
|
8
|
+
ordinarySelected: number;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function bornStatus(paths: AkumaPaths, expected: AkuId, input: Readonly<{
|
|
11
|
+
aperture: "monitoring" | "receipt";
|
|
12
|
+
ordinaryBudget?: number;
|
|
13
|
+
}>): Promise<BudgetedStatusObservation>;
|
|
14
|
+
export declare function readBudgetedStatus(worldPath: WorldRoot, id: AkuId, input: Readonly<{
|
|
15
|
+
aperture: "monitoring" | "receipt";
|
|
16
|
+
ordinaryBudget?: number;
|
|
17
|
+
}>): Promise<BudgetedStatusObservation>;
|
|
18
|
+
export declare function readAkumaBirthCwd(worldPath: WorldRoot, id: AkuId): Promise<string>;
|
|
19
|
+
export { selectHistory, type ActivityHistory, type ActivitySnapshot };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { activitySlice, isHeartAbsent, life, lifeAt, probeLeash, readHeart, readSeal, readSoul, } from "./heart/index.js";
|
|
2
|
+
import { pathsForAkuId } from "./identity.js";
|
|
3
|
+
import { ordinarySnapshotBudget, projectTurns, selectHistory, selectSnapshot, } from "./projection.js";
|
|
4
|
+
import { resolveProviderExecution } from "./providers/index.js";
|
|
5
|
+
import { AkumaNotBornError } from "./akuma-errors.js";
|
|
6
|
+
export async function fleetListRow(paths, expected) {
|
|
7
|
+
const snapshot = await readHeart(paths);
|
|
8
|
+
if (snapshot.soul !== null)
|
|
9
|
+
return await bornListRow(paths, expected, snapshot);
|
|
10
|
+
try {
|
|
11
|
+
if ((await probeLeash(paths)) === "held")
|
|
12
|
+
return { id: expected, life: "unborn" };
|
|
13
|
+
const seal = await readSeal(paths);
|
|
14
|
+
return seal === null ? { id: expected, life: "unborn" } : { id: expected, life: "stillborn", seal };
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (isHeartAbsent(error))
|
|
18
|
+
return { id: expected, life: "unborn" };
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function bornListRow(paths, expected, snapshot) {
|
|
23
|
+
snapshot ??= await readHeart(paths);
|
|
24
|
+
if (snapshot.soul === null)
|
|
25
|
+
throw new AkumaNotBornError(expected);
|
|
26
|
+
if (snapshot.soul.id !== expected)
|
|
27
|
+
throw new Error("Akuma soul does not match its coordinate");
|
|
28
|
+
const currentLife = life({ leash: await probeLeash(paths), body: snapshot.latestBody, kill: snapshot.latestKill });
|
|
29
|
+
return {
|
|
30
|
+
id: snapshot.soul.id,
|
|
31
|
+
archetype: snapshot.soul.archetype,
|
|
32
|
+
...(snapshot.soul.description === undefined ? {} : { description: snapshot.soul.description }),
|
|
33
|
+
life: currentLife,
|
|
34
|
+
lifeAt: lifeAt(currentLife, snapshot.latestBody, snapshot.latestKill, snapshot.soul.createdAt),
|
|
35
|
+
lastActivityAt: snapshot.lastActivityAt,
|
|
36
|
+
pending: snapshot.pending.map((tell) => tell.id),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export async function bornStatus(paths, expected, input) {
|
|
40
|
+
if (input.ordinaryBudget !== undefined && (!Number.isSafeInteger(input.ordinaryBudget) || input.ordinaryBudget < 0))
|
|
41
|
+
throw new TypeError("ordinary budget must be a nonnegative safe integer");
|
|
42
|
+
const snapshot = await readHeart(paths);
|
|
43
|
+
if (snapshot.soul === null)
|
|
44
|
+
throw new AkumaNotBornError(expected);
|
|
45
|
+
const current = await bornListRow(paths, expected, snapshot);
|
|
46
|
+
const resumeUnsupported = current.life === "stranded" &&
|
|
47
|
+
snapshot.latestSession?.provider === snapshot.soul.provider.name &&
|
|
48
|
+
(await resolveProviderExecution(snapshot.soul.provider)).adapter.resume === undefined;
|
|
49
|
+
const slice = await activitySlice(paths);
|
|
50
|
+
const selected = selectSnapshot(projectTurns(slice.rows), {
|
|
51
|
+
aperture: input.aperture,
|
|
52
|
+
budget: ordinarySnapshotBudget(input.ordinaryBudget),
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
status: {
|
|
56
|
+
id: current.id,
|
|
57
|
+
life: current.life,
|
|
58
|
+
...(snapshot.soul.readonly === undefined ? {} : { readonly: snapshot.soul.readonly }),
|
|
59
|
+
...(resumeUnsupported ? { strandedReason: "resume-unsupported" } : {}),
|
|
60
|
+
timeline: selected.snapshot,
|
|
61
|
+
},
|
|
62
|
+
ordinarySelected: selected.ordinaryCount,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function readBudgetedStatus(worldPath, id, input) {
|
|
66
|
+
return await bornStatus(pathsForAkuId(worldPath, id), id, input);
|
|
67
|
+
}
|
|
68
|
+
export async function readAkumaBirthCwd(worldPath, id) {
|
|
69
|
+
const soul = await readSoul(pathsForAkuId(worldPath, id));
|
|
70
|
+
if (soul === null)
|
|
71
|
+
throw new AkumaNotBornError(id);
|
|
72
|
+
return soul.cwd;
|
|
73
|
+
}
|
|
74
|
+
export { selectHistory };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CALL_WITH_CONTEXT: unique symbol;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CALL_WITH_CONTEXT = Symbol("akuma-call-with-context");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AkumaHandle } from "./akuma-handle.js";
|
|
2
|
+
import type { AkumaCallContext, AkumaCallInput, AkumaConfiguration, AkumaList, AkumaListInput } from "./akuma.js";
|
|
3
|
+
import { CALL_WITH_CONTEXT } from "./akuma-product-symbols.js";
|
|
4
|
+
import type { WorldRoot } from "../world.js";
|
|
5
|
+
export declare class Akuma {
|
|
6
|
+
private readonly path;
|
|
7
|
+
private readonly configuration;
|
|
8
|
+
private constructor();
|
|
9
|
+
static of(root: WorldRoot, input?: AkumaConfiguration): Akuma;
|
|
10
|
+
of(input: Readonly<{
|
|
11
|
+
id: string;
|
|
12
|
+
}>): AkumaHandle;
|
|
13
|
+
listArchetypes(): Promise<readonly string[]>;
|
|
14
|
+
call(input: AkumaCallInput): Promise<AkumaHandle>;
|
|
15
|
+
[CALL_WITH_CONTEXT](input: AkumaCallInput, context: AkumaCallContext): Promise<AkumaHandle>;
|
|
16
|
+
list(input?: AkumaListInput): Promise<AkumaList>;
|
|
17
|
+
}
|
|
18
|
+
export declare function callAkumaWithContext(akuma: Akuma, input: AkumaCallInput, context: AkumaCallContext): Promise<AkumaHandle>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readdir, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { AkumaHandle } from "./akuma-handle.js";
|
|
5
|
+
import { CALL_WITH_CONTEXT } from "./akuma-product-symbols.js";
|
|
6
|
+
import { fleetListRow, readAkumaBirthCwd } from "./akuma-observe.js";
|
|
7
|
+
import { akuIdFromDirectoryName, akumaPaths, akumaRunRoot, archetypeName, parseAkuId } from "./identity.js";
|
|
8
|
+
import { loadArchetype, listArchetypes as readArchetypes } from "./archetype.js";
|
|
9
|
+
import { publishAkuma } from "./publication.js";
|
|
10
|
+
import { spawnAkumaBody } from "./body.js";
|
|
11
|
+
import { injectedBodyRequests, requestBodyCall } from "./requests.js";
|
|
12
|
+
import { decodeAllowedActions, unionAllowedActions } from "./allowed.js";
|
|
13
|
+
import { settings as readSettings } from "../settings.js";
|
|
14
|
+
function callReadonly(value) {
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
return {};
|
|
17
|
+
if (value !== true)
|
|
18
|
+
throw new TypeError("Akuma call readonly must be true");
|
|
19
|
+
return { readonly: true };
|
|
20
|
+
}
|
|
21
|
+
async function canonicalBirthCwd(input) {
|
|
22
|
+
const selected = resolve(input);
|
|
23
|
+
try {
|
|
24
|
+
const canonical = await realpath(selected);
|
|
25
|
+
if (!(await stat(canonical)).isDirectory())
|
|
26
|
+
throw new Error("not a directory");
|
|
27
|
+
return canonical;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new Error(`cwd is not an existing directory: ${input}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class Akuma {
|
|
34
|
+
path;
|
|
35
|
+
configuration;
|
|
36
|
+
constructor(path, configuration) {
|
|
37
|
+
this.path = path;
|
|
38
|
+
this.configuration = configuration;
|
|
39
|
+
}
|
|
40
|
+
static of(root, input = {}) {
|
|
41
|
+
if (typeof root !== "string")
|
|
42
|
+
throw new TypeError("Akuma.of root must be a WorldRoot");
|
|
43
|
+
return new Akuma(root, input);
|
|
44
|
+
}
|
|
45
|
+
of(input) {
|
|
46
|
+
return new AkumaHandle(parseAkuId(input.id).id, this.path);
|
|
47
|
+
}
|
|
48
|
+
async listArchetypes() {
|
|
49
|
+
return readArchetypes(this.configuration.home === undefined ? {} : { home: this.configuration.home });
|
|
50
|
+
}
|
|
51
|
+
async call(input) {
|
|
52
|
+
return await this[CALL_WITH_CONTEXT](input, { initiatorCwd: process.cwd() });
|
|
53
|
+
}
|
|
54
|
+
async [CALL_WITH_CONTEXT](input, context) {
|
|
55
|
+
const readonly = callReadonly(input.readonly);
|
|
56
|
+
const name = archetypeName(input.archetype);
|
|
57
|
+
const home = this.configuration.home === undefined ? {} : { home: this.configuration.home };
|
|
58
|
+
const settings = this.configuration.settings ?? (await readSettings({ root: this.path, ...home }));
|
|
59
|
+
const archetype = await loadArchetype({ name, ...home, settings, ...readonly });
|
|
60
|
+
const allowed = input.allowed === undefined
|
|
61
|
+
? archetype.allowed
|
|
62
|
+
: unionAllowedActions(archetype.allowed, decodeAllowedActions(input.allowed, "Akuma call allowed"));
|
|
63
|
+
const requests = injectedBodyRequests();
|
|
64
|
+
const requestRecipe = Object.freeze({
|
|
65
|
+
...(archetype.description === undefined ? {} : { description: archetype.description }),
|
|
66
|
+
provider: archetype.provider,
|
|
67
|
+
options: archetype.options,
|
|
68
|
+
...(archetype.readonly === undefined ? {} : { readonly: archetype.readonly }),
|
|
69
|
+
allowed,
|
|
70
|
+
});
|
|
71
|
+
if (requests !== null) {
|
|
72
|
+
const cwd = input.cwd === undefined
|
|
73
|
+
? undefined
|
|
74
|
+
: context?.cwdCanonical === true
|
|
75
|
+
? input.cwd
|
|
76
|
+
: await canonicalBirthCwd(input.cwd);
|
|
77
|
+
const child = await requestBodyCall({
|
|
78
|
+
directory: requests,
|
|
79
|
+
id: randomUUID(),
|
|
80
|
+
world: this.path,
|
|
81
|
+
archetype: name,
|
|
82
|
+
body: input.body,
|
|
83
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
84
|
+
recipe: requestRecipe,
|
|
85
|
+
});
|
|
86
|
+
const bornCwd = await readAkumaBirthCwd(this.path, child);
|
|
87
|
+
return new AkumaHandle(child, this.path, { cwd: bornCwd, source: cwd === undefined ? "caller" : "input" });
|
|
88
|
+
}
|
|
89
|
+
const initiatorCwd = context.initiatorCwd;
|
|
90
|
+
const selectedCwd = input.cwd ?? initiatorCwd ?? this.path;
|
|
91
|
+
const cwd = input.cwd !== undefined && context?.cwdCanonical === true ? input.cwd : await canonicalBirthCwd(selectedCwd);
|
|
92
|
+
const published = await publishAkuma({
|
|
93
|
+
worldPath: this.path,
|
|
94
|
+
archetype: archetype.name,
|
|
95
|
+
launch: async (allocated) => (await spawnAkumaBody({
|
|
96
|
+
paths: allocated.paths,
|
|
97
|
+
seed: {
|
|
98
|
+
id: allocated.id,
|
|
99
|
+
archetype: allocated.archetype,
|
|
100
|
+
...requestRecipe,
|
|
101
|
+
cwd,
|
|
102
|
+
origin: { kind: "direct" },
|
|
103
|
+
},
|
|
104
|
+
initialBody: input.body,
|
|
105
|
+
})).release(),
|
|
106
|
+
});
|
|
107
|
+
return new AkumaHandle(published.id, this.path, {
|
|
108
|
+
cwd,
|
|
109
|
+
source: input.cwd !== undefined ? "input" : initiatorCwd === undefined ? "world" : "process",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async list(input = {}) {
|
|
113
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
114
|
+
throw new TypeError("Akuma list input must be an object");
|
|
115
|
+
const unknown = Object.keys(input).find((key) => key !== "archetype");
|
|
116
|
+
if (unknown !== undefined)
|
|
117
|
+
throw new TypeError(`Akuma list input has unknown field: ${unknown}`);
|
|
118
|
+
const selected = input.archetype === undefined ? undefined : archetypeName(input.archetype);
|
|
119
|
+
const runRoot = akumaRunRoot(this.path);
|
|
120
|
+
let names;
|
|
121
|
+
try {
|
|
122
|
+
names = (await readdir(runRoot, { withFileTypes: true }))
|
|
123
|
+
.filter((entry) => entry.isDirectory())
|
|
124
|
+
.map((entry) => entry.name)
|
|
125
|
+
.sort();
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (error.code === "ENOENT")
|
|
129
|
+
return { rows: [], searched: [runRoot] };
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
const rows = [];
|
|
133
|
+
for (const name of names) {
|
|
134
|
+
let physical;
|
|
135
|
+
try {
|
|
136
|
+
physical = akuIdFromDirectoryName(name);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (selected !== undefined && physical.archetype !== selected)
|
|
142
|
+
continue;
|
|
143
|
+
const paths = akumaPaths({ runRoot, archetype: physical.archetype, suffix: physical.suffix });
|
|
144
|
+
try {
|
|
145
|
+
rows.push(await fleetListRow(paths, physical.id));
|
|
146
|
+
}
|
|
147
|
+
catch { }
|
|
148
|
+
}
|
|
149
|
+
return { rows, searched: [runRoot] };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export async function callAkumaWithContext(akuma, input, context) {
|
|
153
|
+
return await akuma[CALL_WITH_CONTEXT](input, context);
|
|
154
|
+
}
|