@astrosheep/keiyaku 4.5.19 → 4.5.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 +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +14 -7
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +53 -20
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +237 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +6 -7
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +2 -4
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -3
- package/build/src/akuma/akuma-handle.d.ts +5 -1
- package/build/src/akuma/akuma-handle.js +4 -1
- package/build/src/akuma/akuma-instance.d.ts +7 -2
- package/build/src/akuma/akuma-instance.js +14 -7
- package/build/src/akuma/akuma-observe.d.ts +11 -0
- package/build/src/akuma/akuma-observe.js +23 -0
- package/build/src/akuma/akuma-product.d.ts +2 -0
- package/build/src/akuma/akuma-product.js +6 -0
- package/build/src/akuma/akuma.d.ts +20 -1
- package/build/src/akuma/akuma.js +2 -0
- package/build/src/akuma/allowed.d.ts +20 -0
- package/build/src/akuma/allowed.js +11 -0
- package/build/src/akuma/archetype.js +3 -0
- package/build/src/akuma/body.d.ts +25 -5
- package/build/src/akuma/body.js +70 -31
- package/build/src/akuma/call-request.d.ts +5 -0
- package/build/src/akuma/call-request.js +14 -4
- package/build/src/akuma/fleet-execution.d.ts +28 -0
- package/build/src/akuma/fleet-execution.js +36 -4
- package/build/src/akuma/fleet-observation.d.ts +54 -0
- package/build/src/akuma/fleet-request.d.ts +10 -0
- package/build/src/akuma/fleet-request.js +13 -3
- package/build/src/akuma/heart/facts.d.ts +2 -0
- package/build/src/akuma/heart/index.d.ts +1 -0
- package/build/src/akuma/heart/index.js +4 -1
- package/build/src/akuma/heart/rows.d.ts +2 -0
- package/build/src/akuma/heart/rows.js +7 -5
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +3 -1
- package/build/src/akuma/heart/tells.js +6 -5
- package/build/src/akuma/heart/timeline.js +1 -1
- package/build/src/akuma/provider-recipe.d.ts +1 -0
- package/build/src/akuma/provider-recipe.js +5 -0
- package/build/src/akuma/provider.d.ts +1 -1
- package/build/src/akuma/provider.js +1 -1
- package/build/src/akuma/providers/acp/core.js +3 -6
- package/build/src/akuma/providers/acp/index.js +3 -0
- package/build/src/akuma/providers/claude/index.js +10 -10
- package/build/src/akuma/providers/codex-app-server/index.js +12 -11
- package/build/src/akuma/providers/execution-environment.d.ts +6 -0
- package/build/src/akuma/providers/execution-environment.js +24 -0
- package/build/src/akuma/providers/grok-build/index.js +3 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +32 -23
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
- package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
- package/build/src/akuma/providers/pi/events.js +7 -2
- package/build/src/akuma/providers/pi/index.js +5 -2
- package/build/src/akuma/request-observation.d.ts +45 -0
- package/build/src/akuma/request-observation.js +162 -0
- package/build/src/akuma/request-rendezvous.d.ts +6 -3
- package/build/src/akuma/request-rendezvous.js +80 -14
- package/build/src/akuma/request-serve.js +27 -4
- package/build/src/akuma/request-wire.d.ts +5 -0
- package/build/src/akuma/request-wire.js +2 -0
- package/build/src/akuma/turn-drive.d.ts +1 -0
- package/build/src/akuma/turn-drive.js +2 -0
- package/build/src/akuma-body.js +5 -0
- package/build/src/body/amend.js +6 -4
- package/build/src/body/decode.d.ts +3 -1
- package/build/src/body/decode.js +53 -17
- package/build/src/body/region.d.ts +9 -1
- package/build/src/body/region.js +87 -12
- package/build/src/body/render.js +1 -1
- package/build/src/body/verification.d.ts +3 -1
- package/build/src/body/verification.js +4 -1
- package/build/src/cli/accepted.js +19 -2
- package/build/src/cli/commands/akuma-invoke.d.ts +3 -1
- package/build/src/cli/commands/akuma-invoke.js +134 -14
- package/build/src/cli/commands/akuma.js +24 -12
- package/build/src/cli/commands/contract-help.d.ts +15 -15
- package/build/src/cli/commands/contract-help.js +45 -19
- package/build/src/cli/commands/contract-invoke.d.ts +3 -0
- package/build/src/cli/commands/contract-invoke.js +25 -11
- package/build/src/cli/commands/contract.js +6 -4
- package/build/src/cli/commands/install.d.ts +1 -1
- package/build/src/cli/commands/install.js +1 -1
- package/build/src/cli/commands/status-set.js +15 -24
- package/build/src/cli/commands/task.js +18 -18
- package/build/src/cli/invoke.d.ts +3 -0
- package/build/src/cli/invoke.js +58 -23
- package/build/src/cli/main.js +5 -0
- package/build/src/cli/parse.d.ts +3 -0
- package/build/src/cli/parse.js +26 -4
- package/build/src/cli/render/akuma-activity.d.ts +100 -2
- package/build/src/cli/render/akuma-activity.js +485 -40
- package/build/src/cli/render/akuma.d.ts +10 -0
- package/build/src/cli/render/akuma.js +31 -21
- package/build/src/cli/render/audit.js +1 -1
- package/build/src/cli/render/board.js +1 -1
- package/build/src/cli/render/catalog.js +16 -13
- package/build/src/cli/render/contract-history.js +39 -10
- package/build/src/cli/render/contract-observation.js +10 -6
- package/build/src/cli/render/contract.js +109 -106
- package/build/src/cli/render/execution-progress.d.ts +26 -0
- package/build/src/cli/render/execution-progress.js +178 -0
- package/build/src/cli/render/kanshi-akuma.js +27 -39
- package/build/src/cli/render/kanshi.js +33 -26
- package/build/src/cli/render/nuke.js +1 -1
- package/build/src/cli/render/receipt.d.ts +1 -1
- package/build/src/cli/render/receipt.js +6 -6
- package/build/src/cli/render/refusal.js +10 -7
- package/build/src/cli/render/region.js +1 -1
- package/build/src/cli/render/settings.js +12 -2
- package/build/src/cli/render/status-line.d.ts +30 -0
- package/build/src/cli/render/status-line.js +58 -0
- package/build/src/cli/render/status-set.js +1 -1
- package/build/src/cli/render/task.d.ts +2 -0
- package/build/src/cli/render/task.js +11 -7
- package/build/src/cli/render/terminal.d.ts +0 -1
- package/build/src/cli/render/terminal.js +47 -15
- package/build/src/cli/result.d.ts +1 -0
- package/build/src/cli/runtime.d.ts +2 -0
- package/build/src/cli/runtime.js +36 -4
- package/build/src/cli/usage.js +2 -2
- package/build/src/cli/version.d.ts +1 -0
- package/build/src/cli/version.js +25 -0
- package/build/src/git/hooks.d.ts +13 -1
- package/build/src/git/hooks.js +2 -1
- package/build/src/git/workspace.d.ts +1 -1
- package/build/src/git/workspace.js +1 -1
- package/build/src/identity/mint.d.ts +13 -0
- package/build/src/identity/mint.js +26 -0
- package/build/src/identity/normalize.d.ts +4 -0
- package/build/src/identity/normalize.js +30 -0
- package/build/src/index.d.ts +2 -0
- package/build/src/index.js +1 -0
- package/build/src/kanshi/read.js +17 -4
- package/build/src/kanshi/report.d.ts +1 -0
- package/build/src/library/akuma-creation.d.ts +2 -0
- package/build/src/library/akuma-creation.js +34 -18
- package/build/src/library/audit.d.ts +2 -0
- package/build/src/library/audit.js +1 -0
- package/build/src/library/bind.js +18 -16
- package/build/src/library/composition.d.ts +1 -0
- package/build/src/library/contract-bind.d.ts +3 -1
- package/build/src/library/contract-bind.js +5 -3
- package/build/src/library/contract-execution.d.ts +2 -0
- package/build/src/library/contract-execution.js +15 -1
- package/build/src/library/contract-forwarding.d.ts +4 -0
- package/build/src/library/contract-forwarding.js +3 -0
- package/build/src/library/contract-handle.d.ts +7 -0
- package/build/src/library/contract-handle.js +25 -0
- package/build/src/library/contract-operations.d.ts +7 -2
- package/build/src/library/contract-operations.js +21 -1
- package/build/src/library/contract-types.d.ts +1 -0
- package/build/src/library/execution-result.d.ts +2 -2
- package/build/src/library/execution.d.ts +8 -0
- package/build/src/library/execution.js +102 -0
- package/build/src/library/fleet.d.ts +4 -1
- package/build/src/library/fleet.js +32 -8
- package/build/src/library/keiyaku.d.ts +2 -0
- package/build/src/library/mutation.js +15 -7
- package/build/src/plugin/akuma-signals.d.ts +6 -0
- package/build/src/plugin/akuma-signals.js +9 -1
- package/build/src/plugin/public.d.ts +6 -2
- package/build/src/plugin/runtime.d.ts +1 -3
- package/build/src/plugin/runtime.js +95 -103
- package/build/src/protocol/amend.js +4 -3
- package/build/src/protocol/attempt.d.ts +2 -0
- package/build/src/protocol/bind.js +41 -14
- package/build/src/protocol/completion.d.ts +3 -0
- package/build/src/protocol/completion.js +35 -11
- package/build/src/protocol/deliver.js +165 -92
- package/build/src/protocol/execution-observation.d.ts +181 -0
- package/build/src/protocol/execution-observation.js +45 -0
- package/build/src/protocol/intent.d.ts +18 -22
- package/build/src/protocol/intent.js +63 -41
- package/build/src/protocol/operations.d.ts +2 -0
- package/build/src/protocol/placement.js +5 -1
- package/build/src/protocol/progress.d.ts +5 -1
- package/build/src/protocol/progress.js +10 -1
- package/build/src/protocol/read/status.d.ts +1 -0
- package/build/src/protocol/read/status.js +10 -2
- package/build/src/protocol/reintegrate.d.ts +1 -1
- package/build/src/protocol/reintegrate.js +70 -43
- package/build/src/protocol/result-codec.js +27 -11
- package/build/src/protocol/review.js +53 -30
- package/build/src/protocol/run.d.ts +83 -36
- package/build/src/protocol/run.js +90 -75
- package/build/src/runtime/proc/run.d.ts +16 -4
- package/build/src/runtime/proc/run.js +64 -22
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/settings.js +5 -14
- package/build/src/task/document.d.ts +5 -1
- package/build/src/task/document.js +6 -1
- package/build/src/verification/execution.d.ts +10 -2
- package/build/src/verification/execution.js +143 -42
- package/build/src/verification/observation.d.ts +41 -0
- package/build/src/verification/observation.js +29 -0
- package/package.json +3 -1
|
@@ -72,6 +72,7 @@ export async function bornStatus(paths, expected, input) {
|
|
|
72
72
|
status: {
|
|
73
73
|
id: soul.id,
|
|
74
74
|
life: currentLife,
|
|
75
|
+
allowed: soul.allowed,
|
|
75
76
|
...(soul.readonly === undefined ? {} : { readonly: soul.readonly }),
|
|
76
77
|
...(resumeUnsupported ? { strandedReason: "resume-unsupported" } : {}),
|
|
77
78
|
timeline: selected.snapshot,
|
|
@@ -93,6 +94,28 @@ export async function readWaitComplete(worldPath, id) {
|
|
|
93
94
|
const observed = await bornObservation(paths, id, () => readLifeSnapshot(paths));
|
|
94
95
|
return complete(observed.currentLife, observed.snapshot.hasPendingTell);
|
|
95
96
|
}
|
|
97
|
+
const OBSERVATION_POLL_MS = 100;
|
|
98
|
+
function observeDelay(milliseconds) {
|
|
99
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Successive status observations of one already born Akuma until its
|
|
103
|
+
* observation window closes. Every status observed while the window is open is
|
|
104
|
+
* reported to `observe`; the window's last status is returned. A window that is
|
|
105
|
+
* already closed, or an Akuma already at its completion judgment, reports
|
|
106
|
+
* nothing and returns the current status.
|
|
107
|
+
*/
|
|
108
|
+
export async function observeAkumaStatus(worldPath, expected, input) {
|
|
109
|
+
const paths = pathsForAkuId(worldPath, expected);
|
|
110
|
+
const deadline = performance.now() + input.timeoutMs;
|
|
111
|
+
for (;;) {
|
|
112
|
+
const status = (await bornStatus(paths, expected, { aperture: "monitoring" })).status;
|
|
113
|
+
if (defaultWaitComplete(status) || performance.now() >= deadline)
|
|
114
|
+
return status;
|
|
115
|
+
input.observe(status);
|
|
116
|
+
await observeDelay(Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - performance.now())));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
96
119
|
export async function readAkumaBirthCwd(worldPath, id) {
|
|
97
120
|
const soul = await readSoul(pathsForAkuId(worldPath, id));
|
|
98
121
|
if (soul === null)
|
|
@@ -26,6 +26,8 @@ async function admitBodyRequest(input) {
|
|
|
26
26
|
world: input.path,
|
|
27
27
|
archetype: input.name,
|
|
28
28
|
...(input.call.body === undefined ? {} : { body: input.call.body }),
|
|
29
|
+
...(input.call.initiator === undefined ? {} : { initiator: input.call.initiator }),
|
|
30
|
+
...(input.call.schema === undefined ? {} : { awaitAsleep: true }),
|
|
29
31
|
...(cwd === undefined ? {} : { cwd }),
|
|
30
32
|
recipe: input.recipe,
|
|
31
33
|
});
|
|
@@ -55,7 +57,9 @@ async function admitDirect(input) {
|
|
|
55
57
|
origin: { kind: "direct" },
|
|
56
58
|
},
|
|
57
59
|
...(input.call.body === undefined ? {} : { initialBody: input.call.body }),
|
|
60
|
+
...(input.call.initiator === undefined ? {} : { initiator: input.call.initiator }),
|
|
58
61
|
...(input.call.schema === undefined ? {} : { initialSchemaJson: schemaJsonText(input.call.schema) }),
|
|
62
|
+
...(input.call.schema === undefined ? {} : { awaitAsleep: true }),
|
|
59
63
|
execution: {
|
|
60
64
|
cwd,
|
|
61
65
|
source: input.call.cwd !== undefined ? "input" : initiatorCwd === undefined ? "world" : "process",
|
|
@@ -206,10 +210,12 @@ class AkumaProduct {
|
|
|
206
210
|
}
|
|
207
211
|
const published = await launchAkuma({
|
|
208
212
|
allocated: born.allocated,
|
|
213
|
+
...(born.awaitAsleep === undefined ? {} : { awaitAsleep: born.awaitAsleep }),
|
|
209
214
|
launch: async (allocated) => await spawnAkumaBody({
|
|
210
215
|
paths: allocated.paths,
|
|
211
216
|
seed: born.seed,
|
|
212
217
|
...(born.initialBody === undefined ? {} : { initialBody: born.initialBody }),
|
|
218
|
+
...(born.initiator === undefined ? {} : { initiator: born.initiator }),
|
|
213
219
|
...(born.initialSchemaJson === undefined ? {} : { initialSchemaJson: born.initialSchemaJson }),
|
|
214
220
|
...(Object.keys(completion).length === 0 ? {} : { completion }),
|
|
215
221
|
}),
|
|
@@ -3,7 +3,7 @@ export type { KillEvidence };
|
|
|
3
3
|
import { type AkuId } from "./identity.js";
|
|
4
4
|
import type { Settings } from "../settings.js";
|
|
5
5
|
import type { WorldRoot } from "../world.js";
|
|
6
|
-
import type
|
|
6
|
+
import { type AllowedAction } from "./allowed.js";
|
|
7
7
|
import type { ExecutionContext } from "./requests.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import type { BoundedList } from "../bounded-list.js";
|
|
@@ -32,6 +32,24 @@ export declare const akumaStatusSchema: z.ZodObject<{
|
|
|
32
32
|
asleep: "asleep";
|
|
33
33
|
stranded: "stranded";
|
|
34
34
|
}>;
|
|
35
|
+
allowed: z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
|
36
|
+
"task.add": "task.add";
|
|
37
|
+
"task.addDocument": "task.addDocument";
|
|
38
|
+
"task.compose": "task.compose";
|
|
39
|
+
"task.done": "task.done";
|
|
40
|
+
"task.drop": "task.drop";
|
|
41
|
+
"task.hold": "task.hold";
|
|
42
|
+
"task.resume": "task.resume";
|
|
43
|
+
"task.start": "task.start";
|
|
44
|
+
"task.stop": "task.stop";
|
|
45
|
+
"task.update": "task.update";
|
|
46
|
+
"akuma.call": "akuma.call";
|
|
47
|
+
"akuma.kill": "akuma.kill";
|
|
48
|
+
"akuma.tell": "akuma.tell";
|
|
49
|
+
"contract.audit": "contract.audit";
|
|
50
|
+
"contract.deliver": "contract.deliver";
|
|
51
|
+
"contract.review": "contract.review";
|
|
52
|
+
}>>, z.ZodTransform<import("./allowed.js").AllowedActions, ("task.add" | "task.addDocument" | "task.compose" | "task.done" | "task.drop" | "task.hold" | "task.resume" | "task.start" | "task.stop" | "task.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
|
|
35
53
|
readonly: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
36
54
|
enforcement: z.ZodLiteral<"native">;
|
|
37
55
|
}, z.core.$strict>, z.ZodObject<{
|
|
@@ -439,6 +457,7 @@ export type AkumaCallInput = Readonly<{
|
|
|
439
457
|
readonly?: true;
|
|
440
458
|
allowed?: readonly AllowedAction[];
|
|
441
459
|
schema?: Schema<unknown>;
|
|
460
|
+
initiator?: string;
|
|
442
461
|
}>;
|
|
443
462
|
export type AkumaCallContext = Readonly<{
|
|
444
463
|
initiatorCwd?: string;
|
package/build/src/akuma/akuma.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseAkuId } from "./identity.js";
|
|
2
2
|
import { activitySnapshotSchema } from "./projection.js";
|
|
3
|
+
import { allowedActionsSchema } from "./allowed.js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { createAkumaProduct } from "./akuma-product.js";
|
|
5
6
|
import { killAkumaWithRecovery } from "./akuma-handle.js";
|
|
@@ -25,6 +26,7 @@ export const akumaStatusSchema = z
|
|
|
25
26
|
.object({
|
|
26
27
|
id: akumaIdSchema,
|
|
27
28
|
life: z.enum(["running", "asleep", "stranded", "hung", "untidy", "killed"]),
|
|
29
|
+
allowed: allowedActionsSchema,
|
|
28
30
|
readonly: readonlySchema.optional(),
|
|
29
31
|
timeline: activitySnapshotSchema,
|
|
30
32
|
strandedReason: z.literal("resume-unsupported").optional(),
|
|
@@ -1,8 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
export declare const ALLOWED_ACTIONS: readonly ["akuma.call", "akuma.kill", "akuma.tell", "contract.audit", "contract.deliver", "contract.review", "task.add", "task.addDocument", "task.compose", "task.done", "task.drop", "task.hold", "task.resume", "task.start", "task.stop", "task.update"];
|
|
2
3
|
export type AllowedAction = (typeof ALLOWED_ACTIONS)[number];
|
|
3
4
|
export type AllowedActions = readonly AllowedAction[];
|
|
4
5
|
export declare function isAllowedAction(value: unknown): value is AllowedAction;
|
|
5
6
|
export declare function decodeAllowedActions(value: unknown, label?: string): AllowedActions;
|
|
7
|
+
/** Public codecs retain the Soul's canonical, duplicate-free action membership. */
|
|
8
|
+
export declare const allowedActionsSchema: z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
|
9
|
+
"task.add": "task.add";
|
|
10
|
+
"task.addDocument": "task.addDocument";
|
|
11
|
+
"task.compose": "task.compose";
|
|
12
|
+
"task.done": "task.done";
|
|
13
|
+
"task.drop": "task.drop";
|
|
14
|
+
"task.hold": "task.hold";
|
|
15
|
+
"task.resume": "task.resume";
|
|
16
|
+
"task.start": "task.start";
|
|
17
|
+
"task.stop": "task.stop";
|
|
18
|
+
"task.update": "task.update";
|
|
19
|
+
"akuma.call": "akuma.call";
|
|
20
|
+
"akuma.kill": "akuma.kill";
|
|
21
|
+
"akuma.tell": "akuma.tell";
|
|
22
|
+
"contract.audit": "contract.audit";
|
|
23
|
+
"contract.deliver": "contract.deliver";
|
|
24
|
+
"contract.review": "contract.review";
|
|
25
|
+
}>>, z.ZodTransform<AllowedActions, ("task.add" | "task.addDocument" | "task.compose" | "task.done" | "task.drop" | "task.hold" | "task.resume" | "task.start" | "task.stop" | "task.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
|
|
6
26
|
export declare function effectiveAllowedActions(value: unknown): AllowedActions;
|
|
7
27
|
export declare function unionAllowedActions(base: AllowedActions, additions: AllowedActions): AllowedActions;
|
|
8
28
|
export declare function clipAllowedActions(requested: AllowedActions, parent: AllowedActions): AllowedActions;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TASK_MUTATION_ACTIONS } from "../task/mutation.js";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
export const ALLOWED_ACTIONS = Object.freeze([
|
|
3
4
|
"akuma.call",
|
|
4
5
|
"akuma.kill",
|
|
@@ -25,6 +26,16 @@ export function decodeAllowedActions(value, label = "allowed") {
|
|
|
25
26
|
}
|
|
26
27
|
return Object.freeze([...seen].sort());
|
|
27
28
|
}
|
|
29
|
+
/** Public codecs retain the Soul's canonical, duplicate-free action membership. */
|
|
30
|
+
export const allowedActionsSchema = z.array(z.enum(ALLOWED_ACTIONS)).transform((value, context) => {
|
|
31
|
+
try {
|
|
32
|
+
return decodeAllowedActions(value);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
context.addIssue({ code: "custom", message: error instanceof Error ? error.message : String(error) });
|
|
36
|
+
return z.NEVER;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
28
39
|
export function effectiveAllowedActions(value) {
|
|
29
40
|
return value === undefined ? ALLOWED_ACTIONS : decodeAllowedActions(value);
|
|
30
41
|
}
|
|
@@ -132,6 +132,7 @@ function decodeArchetypeFields(values) {
|
|
|
132
132
|
const effort = archetypeField(values, "effort");
|
|
133
133
|
const readonly = archetypeReadonly(values);
|
|
134
134
|
const network = archetypeEnum(values, "network", ["disabled", "enabled"]);
|
|
135
|
+
const sandbox = archetypeEnum(values, "sandbox", ["full-access"]);
|
|
135
136
|
const description = archetypeField(values, "description");
|
|
136
137
|
const allowedPresent = "allowed" in values;
|
|
137
138
|
const allowed = allowedPresent ? effectiveAllowedActions(values.allowed) : undefined;
|
|
@@ -143,6 +144,7 @@ function decodeArchetypeFields(values) {
|
|
|
143
144
|
...(effort === undefined ? {} : { effort }),
|
|
144
145
|
...(readonly === undefined ? {} : { readonly }),
|
|
145
146
|
...(network === undefined ? {} : { network }),
|
|
147
|
+
...(sandbox === undefined ? {} : { sandbox }),
|
|
146
148
|
...(description === undefined ? {} : { description }),
|
|
147
149
|
...(allowed === undefined ? {} : { allowed }),
|
|
148
150
|
allowedPresent,
|
|
@@ -166,6 +168,7 @@ function decodeArchetype(name, path, markdown) {
|
|
|
166
168
|
...(fields.model === undefined ? {} : { model: fields.model }),
|
|
167
169
|
...(fields.effort === undefined ? {} : { effort: fields.effort }),
|
|
168
170
|
...(fields.network === undefined ? {} : { network: fields.network }),
|
|
171
|
+
...(fields.sandbox === undefined ? {} : { sandbox: fields.sandbox }),
|
|
169
172
|
...(systemPrompt.length === 0
|
|
170
173
|
? {}
|
|
171
174
|
: {
|
|
@@ -12,6 +12,7 @@ export type BodyLaunch = Readonly<{
|
|
|
12
12
|
seed?: Omit<Soul, "createdAt">;
|
|
13
13
|
birthSession?: Omit<SessionFact, "sequence">;
|
|
14
14
|
initialBody?: string;
|
|
15
|
+
initiator?: string;
|
|
15
16
|
initialSchemaJson?: string;
|
|
16
17
|
refuseIfHeld?: boolean;
|
|
17
18
|
completion?: Readonly<{
|
|
@@ -53,16 +54,35 @@ type BodyRuntime = Readonly<{
|
|
|
53
54
|
world: WorldRoot;
|
|
54
55
|
externalCommands: Readonly<Record<string, ErasedRequestCommand>>;
|
|
55
56
|
}>;
|
|
56
|
-
|
|
57
|
+
type RuntimeEnvironment = Readonly<{
|
|
58
|
+
/** The current process executable, preferred only while it is a different live value. */
|
|
59
|
+
current?: string;
|
|
60
|
+
/** The present PATH used to resolve the recorded executable basename. */
|
|
61
|
+
path?: string | undefined;
|
|
62
|
+
exists?: (path: string) => boolean | Promise<boolean>;
|
|
63
|
+
}>;
|
|
64
|
+
/** One Body launch's runtime input: the runtime recorded for it and its resolution environment. */
|
|
65
|
+
export type BodyLaunchRuntime = Readonly<{
|
|
66
|
+
recorded?: string;
|
|
67
|
+
environment?: RuntimeEnvironment;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the runtime executable a Body spawn will use. The path recorded for
|
|
71
|
+
* this process's birth wins while it exists. Once it is gone, wake re-resolves
|
|
72
|
+
* the runtime before any spawn can fail: the current process executable while
|
|
73
|
+
* that is a different live value, then the recorded executable basename on the
|
|
74
|
+
* present PATH. An unresolvable runtime refuses with the stale record, the
|
|
75
|
+
* failed re-resolution, and the kill-and-call-fresh remedy.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveRuntimeExecutable(recorded?: string, environment?: RuntimeEnvironment): Promise<string>;
|
|
78
|
+
export declare function bodyProcessInput(launch: BodyLaunch, bodyModuleUrl?: string, runtime?: BodyLaunchRuntime): Promise<{
|
|
57
79
|
argv: string[];
|
|
58
80
|
cwd: string;
|
|
59
|
-
env:
|
|
60
|
-
KEIYAKU_ACTOR_ID: import("./identity.js").AkuId;
|
|
61
|
-
};
|
|
81
|
+
env: Record<string, string>;
|
|
62
82
|
log: string;
|
|
63
83
|
}>;
|
|
64
84
|
export declare function handoffPendingTells(paths: AkumaPaths, spawn?: (launch: BodyLaunch) => Promise<OwnedProcess>): Promise<void>;
|
|
65
85
|
export declare function driveAkumaBody(launch: BodyLaunch, adapter?: ProviderAdapter, runtimeInput?: Partial<BodyRuntime>): Promise<"held" | void>;
|
|
66
|
-
export declare function spawnAkumaBody(launch: BodyLaunch): Promise<OwnedProcess>;
|
|
86
|
+
export declare function spawnAkumaBody(launch: BodyLaunch, runtime?: BodyLaunchRuntime): Promise<OwnedProcess>;
|
|
67
87
|
export declare function runAkumaBody(launch: BodyLaunch, world: WorldRoot, externalCommands: Readonly<Record<string, ErasedRequestCommand>>): Promise<"held" | void>;
|
|
68
88
|
export declare function wakeRecordedTell(paths: AkumaPaths, tellId: string, runtime?: TellWakeRuntime): Promise<TellResult>;
|
package/build/src/akuma/body.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { appendFile } from "node:fs/promises";
|
|
1
|
+
import { appendFile, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, delimiter, join } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { abortableDelay } from "./abort.js";
|
|
4
5
|
import { BodySupervisor } from "./body-supervisor.js";
|
|
5
6
|
import { driveTurn, turnRecipe } from "./turn-drive.js";
|
|
6
7
|
import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, readHeart, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readNonterminalRequests, resolvePendingTellDisposition, } from "./heart/index.js";
|
|
7
8
|
import { worldRootForAkumaPaths } from "./identity.js";
|
|
9
|
+
import { akumaExecutionEnvironment } from "./providers/execution-environment.js";
|
|
8
10
|
import { pluginRuntime } from "../plugin/runtime.js";
|
|
9
11
|
import { World } from "../world.js";
|
|
10
12
|
import { resolveProviderExecution } from "./providers/index.js";
|
|
@@ -144,19 +146,71 @@ const DEFAULT_RUNTIME = {
|
|
|
144
146
|
externalCommands: {},
|
|
145
147
|
spawnBody: spawnAkumaBody,
|
|
146
148
|
};
|
|
147
|
-
|
|
149
|
+
/**
|
|
150
|
+
* The runtime executable this process recorded at its birth. A package-manager
|
|
151
|
+
* upgrade can remove the file under a live process, so a wake re-resolves that
|
|
152
|
+
* record instead of surfacing a bare missing-file launch failure.
|
|
153
|
+
*/
|
|
154
|
+
const RECORDED_RUNTIME = process.execPath;
|
|
155
|
+
async function runtimeFileExists(path) {
|
|
156
|
+
try {
|
|
157
|
+
return (await stat(path)).isFile();
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function runtimeCommandOnPath(command, path, exists) {
|
|
164
|
+
if (command.length === 0 || command.includes("/") || command.includes("\\"))
|
|
165
|
+
return null;
|
|
166
|
+
for (const directory of (path ?? "").split(delimiter)) {
|
|
167
|
+
if (directory.length === 0)
|
|
168
|
+
continue;
|
|
169
|
+
const candidate = join(directory, command);
|
|
170
|
+
if (await exists(candidate))
|
|
171
|
+
return candidate;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
class StaleRuntimeError extends Error {
|
|
176
|
+
constructor(recorded, current, command) {
|
|
177
|
+
super(`recorded Body runtime ${recorded} no longer exists and re-resolution failed: neither this process executable ` +
|
|
178
|
+
`${current} nor the command "${command}" on PATH names an existing file; kill this Akuma and call a fresh one`);
|
|
179
|
+
this.name = "StaleRuntimeError";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Resolve the runtime executable a Body spawn will use. The path recorded for
|
|
184
|
+
* this process's birth wins while it exists. Once it is gone, wake re-resolves
|
|
185
|
+
* the runtime before any spawn can fail: the current process executable while
|
|
186
|
+
* that is a different live value, then the recorded executable basename on the
|
|
187
|
+
* present PATH. An unresolvable runtime refuses with the stale record, the
|
|
188
|
+
* failed re-resolution, and the kill-and-call-fresh remedy.
|
|
189
|
+
*/
|
|
190
|
+
export async function resolveRuntimeExecutable(recorded = RECORDED_RUNTIME, environment = {}) {
|
|
191
|
+
const exists = environment.exists ?? runtimeFileExists;
|
|
192
|
+
if (await exists(recorded))
|
|
193
|
+
return recorded;
|
|
194
|
+
const current = environment.current ?? process.execPath;
|
|
195
|
+
const command = basename(recorded);
|
|
196
|
+
const reResolved = (current !== recorded && (await exists(current)) ? current : null) ??
|
|
197
|
+
(await runtimeCommandOnPath(command, environment.path ?? process.env.PATH, exists));
|
|
198
|
+
if (reResolved === null)
|
|
199
|
+
throw new StaleRuntimeError(recorded, current, command);
|
|
200
|
+
return reResolved;
|
|
201
|
+
}
|
|
202
|
+
export async function bodyProcessInput(launch, bodyModuleUrl = import.meta.url, runtime = {}) {
|
|
148
203
|
const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
|
|
149
204
|
const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
|
|
150
205
|
if (actorId === undefined)
|
|
151
206
|
throw new Error("Akuma wake has no born soul");
|
|
207
|
+
const executable = await resolveRuntimeExecutable(runtime.recorded, runtime.environment);
|
|
152
208
|
const source = bodyModuleUrl.endsWith(".ts");
|
|
153
209
|
const entry = fileURLToPath(new URL(source ? "../akuma-body.ts" : "../akuma-body.js", bodyModuleUrl));
|
|
154
210
|
return {
|
|
155
|
-
argv: source
|
|
156
|
-
? [process.execPath, "--import", import.meta.resolve("tsx"), entry, encoded]
|
|
157
|
-
: [process.execPath, entry, encoded],
|
|
211
|
+
argv: source ? [executable, "--import", import.meta.resolve("tsx"), entry, encoded] : [executable, entry, encoded],
|
|
158
212
|
cwd: await launchCwd(launch),
|
|
159
|
-
env:
|
|
213
|
+
env: akumaExecutionEnvironment(process.env, { KEIYAKU_ACTOR_ID: actorId }),
|
|
160
214
|
log: launch.paths.log,
|
|
161
215
|
};
|
|
162
216
|
}
|
|
@@ -231,14 +285,13 @@ async function recoverBodyRequests(input) {
|
|
|
231
285
|
}
|
|
232
286
|
function turnOutcomeEmitter(launch, soul) {
|
|
233
287
|
let plugins;
|
|
234
|
-
const pending = new Set();
|
|
235
288
|
const reportDiagnostic = (message) => {
|
|
236
289
|
void recordPluginDiagnostic(launch.paths, "turn outcome", message);
|
|
237
290
|
};
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
delivery = (async () => {
|
|
291
|
+
return {
|
|
292
|
+
async emit(turnSequence, outcome) {
|
|
241
293
|
try {
|
|
294
|
+
const initiator = (await readTurn(launch.paths, turnSequence))?.initiator;
|
|
242
295
|
plugins ??= pluginRuntime({
|
|
243
296
|
world: await World.at(worldRootForAkumaPaths(launch.paths)),
|
|
244
297
|
reportDiagnostic,
|
|
@@ -247,6 +300,7 @@ function turnOutcomeEmitter(launch, soul) {
|
|
|
247
300
|
kind: "akuma.turn-outcome",
|
|
248
301
|
akumaId: soul.id,
|
|
249
302
|
turnSequence,
|
|
303
|
+
...(initiator === undefined ? {} : { initiator }),
|
|
250
304
|
outcome: outcome.outcome === "answered"
|
|
251
305
|
? { kind: "answered", text: outcome.answer }
|
|
252
306
|
: { kind: "failed", reason: outcome.diagnostic },
|
|
@@ -256,17 +310,8 @@ function turnOutcomeEmitter(launch, soul) {
|
|
|
256
310
|
catch (error) {
|
|
257
311
|
await recordPluginDiagnostic(launch.paths, "turn outcome", error);
|
|
258
312
|
}
|
|
259
|
-
}
|
|
260
|
-
pending.add(delivery);
|
|
261
|
-
return delivery;
|
|
313
|
+
},
|
|
262
314
|
};
|
|
263
|
-
const drain = async () => {
|
|
264
|
-
while (pending.size > 0)
|
|
265
|
-
await Promise.all([...pending]);
|
|
266
|
-
if (plugins !== undefined)
|
|
267
|
-
await (await plugins).drain("akuma.turn-outcome");
|
|
268
|
-
};
|
|
269
|
-
return { emit, drain };
|
|
270
315
|
}
|
|
271
316
|
function bodyEndEmitter(launch, soul) {
|
|
272
317
|
let plugins;
|
|
@@ -288,7 +333,6 @@ function bodyEndEmitter(launch, soul) {
|
|
|
288
333
|
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
289
334
|
...(launch.completion?.contractId === undefined ? {} : { contractId: launch.completion.contractId }),
|
|
290
335
|
}, reportDiagnostic);
|
|
291
|
-
await (await plugins).drain("akuma.body-ended");
|
|
292
336
|
}
|
|
293
337
|
catch (error) {
|
|
294
338
|
await recordPluginDiagnostic(launch.paths, "body end", error);
|
|
@@ -324,6 +368,7 @@ async function runBodyTurns(input) {
|
|
|
324
368
|
runtimeSpawn: runtime.spawnChild ?? spawnAkumaBody,
|
|
325
369
|
body: initial ?? "",
|
|
326
370
|
...(initial === undefined ? {} : { call: initial }),
|
|
371
|
+
...(launch.initiator === undefined ? {} : { initiator: launch.initiator }),
|
|
327
372
|
launchTells,
|
|
328
373
|
...(schema === undefined ? {} : { schemaJson: schema }),
|
|
329
374
|
world: runtime.world,
|
|
@@ -346,7 +391,7 @@ async function runBodyTurns(input) {
|
|
|
346
391
|
return;
|
|
347
392
|
}
|
|
348
393
|
const outcome = await persistTurn(launch.paths, result.turnSequence, result, runtime.now());
|
|
349
|
-
|
|
394
|
+
await turnOutcome.emit(result.turnSequence, outcome);
|
|
350
395
|
if (outcome.outcome === "failed") {
|
|
351
396
|
await failOpenBoundTurnsIfPresent(launch.paths, bodySequence, outcome.diagnostic, runtime.now());
|
|
352
397
|
await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
|
|
@@ -508,19 +553,13 @@ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
|
|
|
508
553
|
await resolveDecidedPendingTellDisposition(launch.paths, finished.decided, runtime.spawnBody ?? spawnAkumaBody);
|
|
509
554
|
if (finished.notification !== undefined)
|
|
510
555
|
await finished.notification;
|
|
511
|
-
void emitTurnOutcome?.drain();
|
|
512
556
|
}
|
|
513
557
|
}
|
|
514
|
-
export async function spawnAkumaBody(launch) {
|
|
515
|
-
return await spawnDetachedProcess(await bodyProcessInput(launch));
|
|
558
|
+
export async function spawnAkumaBody(launch, runtime = {}) {
|
|
559
|
+
return await spawnDetachedProcess(await bodyProcessInput(launch, import.meta.url, runtime));
|
|
516
560
|
}
|
|
517
561
|
export async function runAkumaBody(launch, world, externalCommands) {
|
|
518
|
-
|
|
519
|
-
return await driveAkumaBody(launch, undefined, { world, externalCommands });
|
|
520
|
-
}
|
|
521
|
-
finally {
|
|
522
|
-
await (await pluginRuntime({ world })).drain();
|
|
523
|
-
}
|
|
562
|
+
return await driveAkumaBody(launch, undefined, { world, externalCommands });
|
|
524
563
|
}
|
|
525
564
|
const DIRECT_TELL_WAKE = {
|
|
526
565
|
spawn: async (paths) => await spawnAkumaBody({ paths, refuseIfHeld: true }),
|
|
@@ -19,6 +19,7 @@ declare const akumaCallRecipeSchema: z.ZodObject<{
|
|
|
19
19
|
effort?: string;
|
|
20
20
|
readonly?: true;
|
|
21
21
|
network?: "disabled" | "enabled";
|
|
22
|
+
sandbox?: "full-access";
|
|
22
23
|
systemPrompt?: string;
|
|
23
24
|
systemPromptMode?: import("./provider-recipe.js").SystemPromptMode;
|
|
24
25
|
}>, unknown>>;
|
|
@@ -28,6 +29,8 @@ declare const akumaCallPayloadSchema: z.ZodObject<{
|
|
|
28
29
|
world: z.ZodString;
|
|
29
30
|
archetype: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
30
31
|
body: z.ZodOptional<z.ZodString>;
|
|
32
|
+
initiator: z.ZodOptional<z.ZodString>;
|
|
33
|
+
awaitAsleep: z.ZodOptional<z.ZodLiteral<true>>;
|
|
31
34
|
cwd: z.ZodOptional<z.ZodString>;
|
|
32
35
|
recipe: z.ZodObject<{
|
|
33
36
|
description: z.ZodOptional<z.ZodString>;
|
|
@@ -44,6 +47,7 @@ declare const akumaCallPayloadSchema: z.ZodObject<{
|
|
|
44
47
|
effort?: string;
|
|
45
48
|
readonly?: true;
|
|
46
49
|
network?: "disabled" | "enabled";
|
|
50
|
+
sandbox?: "full-access";
|
|
47
51
|
systemPrompt?: string;
|
|
48
52
|
systemPromptMode?: import("./provider-recipe.js").SystemPromptMode;
|
|
49
53
|
}>, unknown>>;
|
|
@@ -58,6 +62,7 @@ export type AkumaCallRequestChildLaunch = Readonly<{
|
|
|
58
62
|
paths: AkumaPaths;
|
|
59
63
|
seed: Omit<Soul, "createdAt">;
|
|
60
64
|
initialBody?: string;
|
|
65
|
+
initiator?: string;
|
|
61
66
|
}>;
|
|
62
67
|
type AkumaCallRequestCapabilities = Readonly<{
|
|
63
68
|
world: WorldRoot;
|
|
@@ -4,7 +4,7 @@ import { refuseRequest, reserveRequest } from "./heart/index.js";
|
|
|
4
4
|
import { archetypeName, parseAkuId } from "./identity.js";
|
|
5
5
|
import { publishAkuma } from "./publication.js";
|
|
6
6
|
import { decodeProviderOptions, decodeReadonlyRestraint } from "./provider-recipe.js";
|
|
7
|
-
import { decodeProviderExecution } from "./providers/index.js";
|
|
7
|
+
import { decodeProviderExecution, resolveProviderExecution } from "./providers/index.js";
|
|
8
8
|
import { requestBodyCommand } from "./request-rendezvous.js";
|
|
9
9
|
import { eraseRequestCommand, } from "./request-wire.js";
|
|
10
10
|
import { z } from "zod";
|
|
@@ -67,6 +67,8 @@ const akumaCallPayloadSchema = z
|
|
|
67
67
|
world: absolutePathSchema,
|
|
68
68
|
archetype: archetypeSchema,
|
|
69
69
|
body: z.string().optional(),
|
|
70
|
+
initiator: z.string().min(1).optional(),
|
|
71
|
+
awaitAsleep: z.literal(true).optional(),
|
|
70
72
|
cwd: absolutePathSchema.optional(),
|
|
71
73
|
recipe: akumaCallRecipeSchema,
|
|
72
74
|
})
|
|
@@ -86,17 +88,24 @@ async function executeAkumaCall(request, facts, capabilities) {
|
|
|
86
88
|
await refuseRequest(paths, facts.id, `request world ${requestWorld} does not match ${world}`);
|
|
87
89
|
throw new Error(`request world ${requestWorld} does not match ${world}`);
|
|
88
90
|
}
|
|
91
|
+
const selected = await resolveProviderExecution(request.recipe.provider);
|
|
92
|
+
const admission = selected.adapter.admitOptions(request.recipe.options);
|
|
93
|
+
if (admission.kind === "refused") {
|
|
94
|
+
await refuseRequest(paths, facts.id, admission.diagnostic);
|
|
95
|
+
throw new Error(admission.diagnostic);
|
|
96
|
+
}
|
|
89
97
|
const recipe = {
|
|
90
98
|
...(request.recipe.description === undefined ? {} : { description: request.recipe.description }),
|
|
91
99
|
allowed: clipAllowedActions(request.recipe.allowed, parent.allowed),
|
|
92
|
-
provider:
|
|
93
|
-
options:
|
|
94
|
-
...(
|
|
100
|
+
provider: selected.execution,
|
|
101
|
+
options: admission.options,
|
|
102
|
+
...(admission.readonly === undefined ? {} : { readonly: admission.readonly }),
|
|
95
103
|
};
|
|
96
104
|
const published = await publishAkuma({
|
|
97
105
|
worldPath: world,
|
|
98
106
|
archetype: request.archetype,
|
|
99
107
|
signal: facts.signal,
|
|
108
|
+
...(request.awaitAsleep === undefined ? {} : { awaitAsleep: request.awaitAsleep }),
|
|
100
109
|
launch: async (allocated) => {
|
|
101
110
|
if (!facts.admissionOpen())
|
|
102
111
|
throw new Error("body closed request admission");
|
|
@@ -111,6 +120,7 @@ async function executeAkumaCall(request, facts, capabilities) {
|
|
|
111
120
|
origin: { kind: "request", parent: parent.id, requestId: facts.id },
|
|
112
121
|
},
|
|
113
122
|
...(request.body === undefined ? {} : { initialBody: request.body }),
|
|
123
|
+
...(request.initiator === undefined ? {} : { initiator: request.initiator }),
|
|
114
124
|
});
|
|
115
125
|
},
|
|
116
126
|
});
|
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import { type AkumaStatus } from "./akuma.js";
|
|
2
|
+
import { type DispatchAssociation } from "./dispatch-association.js";
|
|
3
|
+
import type { AkumaAlias } from "../identity/selector.js";
|
|
2
4
|
import type { WorldRoot } from "../world.js";
|
|
3
5
|
import { type AkumaKillResult, type AkumaTellResult, type AkumaWaitResult } from "./fleet-observation.js";
|
|
6
|
+
export type WaitIdentityFacts = Readonly<{
|
|
7
|
+
/** The alias currently addressing this Akuma in its world, when one is bound to it. */
|
|
8
|
+
alias?: AkumaAlias;
|
|
9
|
+
/** This Akuma's Dispatch association, read from the observing repository. */
|
|
10
|
+
contract: DispatchAssociation;
|
|
11
|
+
}>;
|
|
12
|
+
/** One observed Akuma as a live wait viewer sees it: its status plus its identity facts. */
|
|
13
|
+
export type WaitObservedAkuma = Readonly<{
|
|
14
|
+
status: AkumaStatus;
|
|
15
|
+
}> & WaitIdentityFacts;
|
|
16
|
+
/** One selected Akuma's frozen identity, resolved before the first observation round. */
|
|
17
|
+
export type WaitSelectedAkuma = Readonly<{
|
|
18
|
+
id: AkumaStatus["id"];
|
|
19
|
+
}> & WaitIdentityFacts;
|
|
20
|
+
/** A live wait viewer: the frozen selected set, then every observation round. */
|
|
21
|
+
export type WaitObserver = Readonly<{
|
|
22
|
+
selected?: (selected: readonly WaitSelectedAkuma[]) => void;
|
|
23
|
+
observe?: (observed: readonly WaitObservedAkuma[]) => void;
|
|
24
|
+
}>;
|
|
4
25
|
export type WaitExecutionInput = Readonly<{
|
|
5
26
|
path: WorldRoot;
|
|
6
27
|
ids: readonly AkumaStatus["id"][];
|
|
7
28
|
completion: "any" | "all";
|
|
8
29
|
timeoutMs?: number;
|
|
9
30
|
signal?: AbortSignal;
|
|
31
|
+
/** Resolves one Akuma's identity facts; consulted once per Akuma a viewer first sees. */
|
|
32
|
+
identity?: (id: AkumaStatus["id"]) => Promise<WaitIdentityFacts>;
|
|
33
|
+
/** Reports the frozen selected set before the first round, so a viewer can fix its layout. */
|
|
34
|
+
onSelected?: (selected: readonly WaitSelectedAkuma[]) => void;
|
|
35
|
+
/** Reports every observation round to a live viewer; absent keeps the cheap completion probe. */
|
|
36
|
+
observe?: (observed: readonly WaitObservedAkuma[]) => void;
|
|
10
37
|
}>;
|
|
11
38
|
export declare function executeWaitAkuma(input: WaitExecutionInput): Promise<AkumaWaitResult>;
|
|
12
39
|
export type TellExecutionInput = Readonly<{
|
|
@@ -15,6 +42,7 @@ export type TellExecutionInput = Readonly<{
|
|
|
15
42
|
body: string;
|
|
16
43
|
tellId?: string;
|
|
17
44
|
recordedAt?: string;
|
|
45
|
+
initiator?: string;
|
|
18
46
|
signal?: AbortSignal;
|
|
19
47
|
}>;
|
|
20
48
|
export declare function executeTellAkuma(input: TellExecutionInput): Promise<AkumaTellResult>;
|
|
@@ -89,10 +89,44 @@ function delay(milliseconds, signal) {
|
|
|
89
89
|
}
|
|
90
90
|
export async function executeWaitAkuma(input) {
|
|
91
91
|
const deadline = input.timeoutMs === undefined ? undefined : performance.now() + input.timeoutMs;
|
|
92
|
+
// Identity facts are read once per observed Akuma, not once per 100ms round.
|
|
93
|
+
const facts = new Map();
|
|
94
|
+
if (input.onSelected !== undefined) {
|
|
95
|
+
const selected = [];
|
|
96
|
+
for (const id of input.ids) {
|
|
97
|
+
input.signal?.throwIfAborted();
|
|
98
|
+
const known = input.identity === undefined ? undefined : await input.identity(id);
|
|
99
|
+
const resolved = known ?? { contract: NO_DISPATCH_ASSOCIATION };
|
|
100
|
+
facts.set(id, resolved);
|
|
101
|
+
selected.push({ id, ...resolved });
|
|
102
|
+
}
|
|
103
|
+
input.onSelected(selected);
|
|
104
|
+
}
|
|
105
|
+
const observeRound = async (statuses) => {
|
|
106
|
+
if (input.observe === undefined)
|
|
107
|
+
return;
|
|
108
|
+
const observed = [];
|
|
109
|
+
for (const status of statuses) {
|
|
110
|
+
input.signal?.throwIfAborted();
|
|
111
|
+
let known = facts.get(status.id);
|
|
112
|
+
if (known === undefined && input.identity !== undefined) {
|
|
113
|
+
known = await input.identity(status.id);
|
|
114
|
+
facts.set(status.id, known);
|
|
115
|
+
}
|
|
116
|
+
if (known === undefined) {
|
|
117
|
+
observed.push({ status, contract: NO_DISPATCH_ASSOCIATION });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
observed.push({ status, ...known });
|
|
121
|
+
}
|
|
122
|
+
input.observe(observed);
|
|
123
|
+
};
|
|
92
124
|
for (;;) {
|
|
93
|
-
|
|
125
|
+
const expired = deadline !== undefined && performance.now() >= deadline;
|
|
126
|
+
if (expired || input.observe !== undefined || (await probeWaitRound(input))) {
|
|
94
127
|
const round = await observeWaitRound(input.path, input.ids, input.signal);
|
|
95
128
|
input.signal?.throwIfAborted();
|
|
129
|
+
await observeRound(round.statuses);
|
|
96
130
|
// The probe is not a completion receipt. Judge the actual returned values.
|
|
97
131
|
if (roundComplete(round, input.completion) || (deadline !== undefined && performance.now() >= deadline)) {
|
|
98
132
|
return fleetResultSchemas.wait.parse({
|
|
@@ -108,9 +142,7 @@ export async function executeWaitAkuma(input) {
|
|
|
108
142
|
export async function executeTellAkuma(input) {
|
|
109
143
|
input.signal?.throwIfAborted();
|
|
110
144
|
const handle = source(input.path).selectHandle({ id: input.id });
|
|
111
|
-
const tell = input.tellId === undefined
|
|
112
|
-
? await handle.tell(input.body)
|
|
113
|
-
: await handle.tell(input.body, input.tellId, input.recordedAt);
|
|
145
|
+
const tell = await handle.tell(input.body, input.tellId, input.recordedAt, undefined, input.initiator === undefined ? {} : { initiator: input.initiator });
|
|
114
146
|
input.signal?.throwIfAborted();
|
|
115
147
|
return fleetResultSchemas.tell.parse({
|
|
116
148
|
akuma: input.id,
|