@astrosheep/keiyaku 4.5.23 → 4.5.25
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/skills/keiyaku-akuma/SKILL.md +18 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +33 -14
- package/build/src/akuma/akuma-handle.d.ts +10 -0
- package/build/src/akuma/akuma-handle.js +23 -20
- package/build/src/akuma/akuma-instance.d.ts +11 -4
- package/build/src/akuma/akuma-instance.js +70 -41
- package/build/src/akuma/akuma-observe.d.ts +40 -7
- package/build/src/akuma/akuma-observe.js +51 -16
- package/build/src/akuma/akuma-product.d.ts +1 -2
- package/build/src/akuma/akuma-product.js +8 -5
- package/build/src/akuma/akuma.d.ts +3 -0
- package/build/src/akuma/akuma.js +1 -0
- package/build/src/akuma/body.d.ts +1 -1
- package/build/src/akuma/body.js +48 -30
- package/build/src/akuma/call-request.d.ts +0 -1
- package/build/src/akuma/call-request.js +1 -2
- package/build/src/akuma/fleet-execution.d.ts +5 -1
- package/build/src/akuma/fleet-execution.js +51 -75
- package/build/src/akuma/fleet-observation.d.ts +13 -2
- package/build/src/akuma/fleet-observation.js +2 -1
- package/build/src/akuma/fleet-request.js +3 -2
- package/build/src/akuma/heart/index.d.ts +3 -0
- package/build/src/akuma/heart/index.js +12 -2
- package/build/src/akuma/heart/timeline.d.ts +1 -1
- package/build/src/akuma/heart/timeline.js +5 -5
- package/build/src/akuma/index.d.ts +2 -2
- package/build/src/akuma/projection-read.d.ts +2 -0
- package/build/src/akuma/projection-read.js +36 -7
- package/build/src/akuma/projection.d.ts +2 -1
- package/build/src/akuma/projection.js +25 -10
- package/build/src/akuma/providers/pi/index.d.ts +2 -1
- package/build/src/akuma/providers/pi/index.js +2 -1
- package/build/src/akuma/publication.js +7 -4
- package/build/src/akuma/schema.d.ts +40 -0
- package/build/src/akuma/schema.js +114 -0
- package/build/src/akuma-body.js +1 -0
- package/build/src/cli/commands/akuma-invoke.d.ts +6 -0
- package/build/src/cli/commands/akuma-invoke.js +35 -6
- package/build/src/cli/invoke.js +1 -0
- package/build/src/cli/render/akuma-activity.d.ts +19 -8
- package/build/src/cli/render/akuma-activity.js +267 -150
- package/build/src/cli/runtime.js +2 -2
- package/build/src/index.d.ts +2 -1
- package/build/src/index.js +1 -0
- package/build/src/kanshi/read.js +32 -6
- package/build/src/library/address.d.ts +3 -0
- package/build/src/library/address.js +12 -9
- package/build/src/library/akuma-creation.d.ts +3 -0
- package/build/src/library/akuma-creation.js +111 -46
- package/build/src/library/composition.d.ts +3 -1
- package/build/src/library/fleet.d.ts +7 -1
- package/build/src/library/fleet.js +89 -20
- package/build/src/library/keiyaku.d.ts +3 -1
- package/build/src/protocol/read/status.d.ts +5 -0
- package/build/src/protocol/read/status.js +11 -0
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/package.json +1 -1
|
@@ -49,6 +49,24 @@ vocabulary, while an explicit empty default permits none. A nested call can use
|
|
|
49
49
|
only actions permitted by its direct parent Soul. Use `status <aku/...|@alias>`
|
|
50
50
|
to inspect the born worker's frozen effective actions.
|
|
51
51
|
|
|
52
|
+
## Answer Schemas
|
|
53
|
+
|
|
54
|
+
For a schema-bearing call or tell through the public API, pass the schema
|
|
55
|
+
directly — `{ schema: z.object({ claim: z.string() }) }` — importing `z` from
|
|
56
|
+
the package root next to `Akuma`. Any Standard Schema v1 value works the same
|
|
57
|
+
way, and the explicit `Schema.zod(...)` and `Schema.json(...)` forms remain
|
|
58
|
+
available for callers who want them.
|
|
59
|
+
|
|
60
|
+
Keep an answer contract inside simple JSON shape vocabulary: objects, arrays,
|
|
61
|
+
strings, numbers, booleans, enums, literals, and optional or nullable fields.
|
|
62
|
+
Do not attach `.max`, `.min`, `.regex`, `.refine`, `.transform`, or other
|
|
63
|
+
constraint methods. The provider must satisfy the contract, and a fragile or
|
|
64
|
+
unrepresentable constraint fails the loop after submission; the seam refuses
|
|
65
|
+
such a schema at submission and names the offending keyword instead. Enforce
|
|
66
|
+
bounds, formats, and cross-field rules in ordinary caller code after the answer
|
|
67
|
+
arrives, and treat a full JSON Schema through `Schema.json(...)` as the explicit
|
|
68
|
+
waiver a caller signs only when it owns that risk.
|
|
69
|
+
|
|
52
70
|
## Akuma Names
|
|
53
71
|
|
|
54
72
|
An Akuma name selects a reusable worker configuration, not an individual worker.
|
package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md
CHANGED
|
@@ -53,14 +53,13 @@ investigation, not as a supposedly fresh judge of its own earlier answer.
|
|
|
53
53
|
## Public Entry And A Single Structured Turn
|
|
54
54
|
|
|
55
55
|
Run an ESM script (`.mjs`) in a project where `@astrosheep/keiyaku` resolves.
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
Import `z` from the package root; a globally installed CLI alone does not
|
|
57
|
+
establish Node package resolution for an arbitrary script.
|
|
58
58
|
Use `keiyaku ls aku/` to select an available Archetype; names and upstream model
|
|
59
59
|
availability are installation-specific.
|
|
60
60
|
|
|
61
61
|
```js
|
|
62
|
-
import { Akuma,
|
|
63
|
-
import { z } from "zod";
|
|
62
|
+
import { Akuma, World, z } from "@astrosheep/keiyaku";
|
|
64
63
|
|
|
65
64
|
const root = await World.at(process.cwd());
|
|
66
65
|
const archetype = process.env.AKUMA_ARCHETYPE;
|
|
@@ -74,11 +73,11 @@ const worker = await Akuma.birth(archetype, {
|
|
|
74
73
|
console.error("worker", worker.id); // Keep the complete AkuId.
|
|
75
74
|
await worker.idle(); // Let the prompt-free birth Body settle before a schema Tell.
|
|
76
75
|
|
|
77
|
-
const Finding =
|
|
76
|
+
const Finding = z.object({
|
|
78
77
|
claim: z.string(),
|
|
79
78
|
evidence: z.array(z.object({ path: z.string(), observation: z.string() })),
|
|
80
79
|
unknowns: z.array(z.string()),
|
|
81
|
-
})
|
|
80
|
+
});
|
|
82
81
|
|
|
83
82
|
const finding = await worker.tell(
|
|
84
83
|
"Read the repository guidance and relevant owner documents. Read only; " +
|
|
@@ -90,9 +89,19 @@ console.log(JSON.stringify(finding, null, 2));
|
|
|
90
89
|
```
|
|
91
90
|
|
|
92
91
|
`birth` does not submit a prompt. Plain `tell` returns answer text; schema
|
|
93
|
-
`tell` returns the decoded value, not a JSON string to scrape.
|
|
94
|
-
Schema
|
|
95
|
-
`Schema.zod(...)
|
|
92
|
+
`tell` returns the decoded value, not a JSON string to scrape. Pass the schema
|
|
93
|
+
directly; any Standard Schema v1 value works the same way. The explicit
|
|
94
|
+
`Schema.zod(...)` wrapper still works, and `Schema.json(document, decode)` is
|
|
95
|
+
the escape hatch for a caller-owned JSON Schema and custom decoder.
|
|
96
|
+
|
|
97
|
+
Keep an answer contract inside simple JSON shape vocabulary: objects, arrays,
|
|
98
|
+
strings, numbers, booleans, enums, literals, and optional or nullable fields.
|
|
99
|
+
Do not attach `.max`, `.min`, `.regex`, `.refine`, `.transform`, or other
|
|
100
|
+
constraint methods. The provider must satisfy the contract, and a fragile or
|
|
101
|
+
unrepresentable constraint fails the loop after submission; the seam refuses
|
|
102
|
+
such a schema at submission and names the offending keyword instead. Enforce
|
|
103
|
+
bounds, formats, and cross-field rules in ordinary caller code after the answer
|
|
104
|
+
arrives.
|
|
96
105
|
|
|
97
106
|
Schema makes shape machine-usable, not claims true. Include evidence and
|
|
98
107
|
unknowns in the requested value; acceptance still needs a suitable judge.
|
|
@@ -106,14 +115,14 @@ the prompts, schemas, routing, and selection to the task rather than always
|
|
|
106
115
|
running this exact pipeline.
|
|
107
116
|
|
|
108
117
|
```js
|
|
109
|
-
const Claims =
|
|
110
|
-
claims: z.array(z.object({ id: z.string(), text: z.string() }))
|
|
111
|
-
})
|
|
112
|
-
const Verdict =
|
|
118
|
+
const Claims = z.object({
|
|
119
|
+
claims: z.array(z.object({ id: z.string(), text: z.string() })),
|
|
120
|
+
});
|
|
121
|
+
const Verdict = z.object({
|
|
113
122
|
verdict: z.enum(["supported", "contradicted", "unknown"]),
|
|
114
123
|
evidence: z.array(z.object({ path: z.string(), observation: z.string() })),
|
|
115
124
|
reason: z.string(),
|
|
116
|
-
})
|
|
125
|
+
});
|
|
117
126
|
|
|
118
127
|
// Caller-owned concurrency helper, not a Keiyaku API.
|
|
119
128
|
async function mapSettled(items, concurrency, run) {
|
|
@@ -145,6 +154,9 @@ const { claims } = await worker.tell(
|
|
|
145
154
|
if (new Set(claims.map(c => c.id)).size !== claims.length) {
|
|
146
155
|
throw new Error("Duplicate claim ids");
|
|
147
156
|
}
|
|
157
|
+
if (claims.length > 12) {
|
|
158
|
+
throw new Error("Claim list exceeded the 12-claim budget");
|
|
159
|
+
}
|
|
148
160
|
|
|
149
161
|
async function freshJudge(prompt) {
|
|
150
162
|
const judge = await Akuma.birth(archetype, { root, cwd: process.cwd(), readonly: true });
|
|
@@ -200,6 +212,13 @@ pairwise comparisons, experiment queues, or adaptive sampling instead.
|
|
|
200
212
|
- `idle({ timeoutMs })` stops waiting at its timeout, not the worker. A
|
|
201
213
|
`Promise.race` timeout also does not cancel a Tell. Use explicit lifecycle
|
|
202
214
|
operations when you intend to interrupt or stop work.
|
|
215
|
+
- `idle()` resolves an `AkumaIdleResult` saying why it returned, so there is
|
|
216
|
+
no need to re-poll `status()` to distinguish the outcomes. A completed wait
|
|
217
|
+
resolves `{ kind: "idle", status, reason }` with `reason` naming the settled
|
|
218
|
+
life (`"asleep"`, `"killed"`, `"hung"`, `"untidy"`, or `"stranded"`); a
|
|
219
|
+
passed deadline resolves `{ kind: "timeout", status, reason }` with `reason`
|
|
220
|
+
naming what was still outstanding as `{ running, pendingTell }`. Both arms
|
|
221
|
+
carry the final observed `status`.
|
|
203
222
|
- Keep input ids, AkuIds, terminal results, failures, and completed stages in
|
|
204
223
|
caller-owned artifacts if the run must survive its orchestrator process.
|
|
205
224
|
On return, `Akuma.select(root, savedId)` reconnects synchronously; `status()`
|
|
@@ -2,6 +2,7 @@ import { type TellResult, type TellWakeRuntime } from "./body.js";
|
|
|
2
2
|
import { HeldAkumaLeash, type KillEvidence } from "./heart/index.js";
|
|
3
3
|
import { type AkuId, type AkumaPaths } from "./identity.js";
|
|
4
4
|
import { type ActivityHistory, type ExactHistory } from "./projection.js";
|
|
5
|
+
import { type WaitReason } from "./akuma-observe.js";
|
|
5
6
|
import type { AkumaCallExecution, AkumaStatus, ForkReceipt, InterruptReceipt } from "./akuma.js";
|
|
6
7
|
import type { WorldRoot } from "../world.js";
|
|
7
8
|
declare const CALL_EXECUTION: unique symbol;
|
|
@@ -25,12 +26,21 @@ export declare class AkumaHandle {
|
|
|
25
26
|
since?: number;
|
|
26
27
|
limit?: number;
|
|
27
28
|
}>): Promise<ActivityHistory | ExactHistory>;
|
|
29
|
+
waitReceipt(predicate?: (status: AkumaStatus) => boolean, options?: Readonly<{
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}>): Promise<Readonly<{
|
|
33
|
+
reason: WaitReason;
|
|
34
|
+
status: AkumaStatus;
|
|
35
|
+
}>>;
|
|
28
36
|
wait(predicate?: (status: AkumaStatus) => boolean, options?: Readonly<{
|
|
29
37
|
timeoutMs?: number;
|
|
38
|
+
signal?: AbortSignal;
|
|
30
39
|
}>): Promise<AkumaStatus>;
|
|
31
40
|
tell(body: string, tellId?: string, recordedAt?: string, runtime?: TellWakeRuntime, options?: Readonly<{
|
|
32
41
|
schemaJson?: string;
|
|
33
42
|
initiator?: string;
|
|
43
|
+
signal?: AbortSignal;
|
|
34
44
|
}>): Promise<TellResult>;
|
|
35
45
|
interrupt(body: string, options?: Readonly<{
|
|
36
46
|
tellId?: string;
|
|
@@ -8,10 +8,8 @@ import { resolveProviderExecution } from "./providers/index.js";
|
|
|
8
8
|
import { publishAkuma } from "./publication.js";
|
|
9
9
|
import { spawnAkumaBody } from "./body.js";
|
|
10
10
|
import { AkumaNotBornError } from "./akuma-errors.js";
|
|
11
|
-
import { bornStatus, defaultWaitComplete, readWaitComplete } from "./akuma-observe.js";
|
|
11
|
+
import { bornStatus, defaultWaitComplete, readWaitComplete, waitForObservation, } from "./akuma-observe.js";
|
|
12
12
|
const CALL_EXECUTION = Symbol("akuma-call-execution");
|
|
13
|
-
const POLL_MS = 100;
|
|
14
|
-
const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
15
13
|
function diagnostic(error) {
|
|
16
14
|
return error instanceof Error ? error.message : String(error);
|
|
17
15
|
}
|
|
@@ -33,10 +31,13 @@ async function takeLeashUntilSignal(paths, bodySequence, signal, unbounded = fal
|
|
|
33
31
|
export async function settleAkumaKill(paths, signal, retainLeash = false) {
|
|
34
32
|
const request = await requestStop(paths, new Date().toISOString(), signal);
|
|
35
33
|
if (request.kind !== "requested") {
|
|
34
|
+
// A witnessed Body was already settled: the kill witness Heart recorded is
|
|
35
|
+
// the kill evidence, not a stop outcome.
|
|
36
|
+
const evidence = request.kind === "witnessed" ? "killed" : request.kind;
|
|
36
37
|
if (!retainLeash)
|
|
37
|
-
return { evidence
|
|
38
|
+
return { evidence };
|
|
38
39
|
const leash = await acquireLeash(paths, signal === undefined ? {} : { signal });
|
|
39
|
-
return leash === null ? { evidence: "unavailable" } : { evidence
|
|
40
|
+
return leash === null ? { evidence: "unavailable" } : { evidence, leash };
|
|
40
41
|
}
|
|
41
42
|
const target = request.body;
|
|
42
43
|
const waited = await takeLeashUntilSignal(paths, target.sequence, signal);
|
|
@@ -131,24 +132,26 @@ export class AkumaHandle {
|
|
|
131
132
|
limit,
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
|
-
async
|
|
135
|
+
async waitReceipt(predicate = defaultWaitComplete, options = {}) {
|
|
135
136
|
if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 0)) {
|
|
136
137
|
throw new TypeError("Akuma wait timeoutMs must be a nonnegative finite millisecond duration");
|
|
137
138
|
}
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
(await readWaitComplete(this.worldPath, this.id)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
139
|
+
const waited = await waitForObservation({
|
|
140
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
141
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
142
|
+
...(predicate === defaultWaitComplete
|
|
143
|
+
? { probe: async () => await readWaitComplete(this.worldPath, this.id) }
|
|
144
|
+
: {}),
|
|
145
|
+
observe: async () => await this.status(),
|
|
146
|
+
complete: predicate,
|
|
147
|
+
});
|
|
148
|
+
return { reason: waited.reason, status: waited.value };
|
|
149
|
+
}
|
|
150
|
+
async wait(predicate = defaultWaitComplete, options = {}) {
|
|
151
|
+
return (await this.waitReceipt(predicate, options)).status;
|
|
149
152
|
}
|
|
150
153
|
async tell(body, tellId = randomUUID(), recordedAt = new Date().toISOString(), runtime, options = {}) {
|
|
151
|
-
const { schemaJson, initiator } = options;
|
|
154
|
+
const { schemaJson, initiator, signal } = options;
|
|
152
155
|
const admitted = await recordTell(this.paths, {
|
|
153
156
|
kind: "tell",
|
|
154
157
|
id: tellId,
|
|
@@ -159,7 +162,7 @@ export class AkumaHandle {
|
|
|
159
162
|
});
|
|
160
163
|
if (admitted.kind === "not-born")
|
|
161
164
|
throw new AkumaNotBornError(this.id);
|
|
162
|
-
return await wakeRecordedTell(this.paths, admitted.tell.id, runtime);
|
|
165
|
+
return await wakeRecordedTell(this.paths, admitted.tell.id, runtime, signal);
|
|
163
166
|
}
|
|
164
167
|
async interrupt(body, options = {}) {
|
|
165
168
|
const request = await requestPause(this.paths, new Date().toISOString(), options.signal);
|
|
@@ -209,7 +212,7 @@ export class AkumaHandle {
|
|
|
209
212
|
return {
|
|
210
213
|
kind: "interrupted",
|
|
211
214
|
putDown,
|
|
212
|
-
tell: await wakeRecordedTell(this.paths, recorded.tellId, options.runtime),
|
|
215
|
+
tell: await wakeRecordedTell(this.paths, recorded.tellId, options.runtime, options.signal),
|
|
213
216
|
};
|
|
214
217
|
}
|
|
215
218
|
async fork(input) {
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { AllowedAction } from "./allowed.js";
|
|
2
2
|
import type { AkumaStatus } from "./akuma.js";
|
|
3
3
|
import type { InterruptReceipt, KillEvidence } from "./akuma.js";
|
|
4
|
+
import { type WaitReason } from "./akuma-observe.js";
|
|
4
5
|
import { type AkuId } from "./identity.js";
|
|
5
6
|
import { type ActivityHistory } from "./projection.js";
|
|
6
7
|
import type { Settings } from "../settings.js";
|
|
7
8
|
import type { WorldRoot } from "../world.js";
|
|
8
|
-
import { type Schema } from "./schema.js";
|
|
9
|
+
import { type Schema, type StandardSchemaV1 } from "./schema.js";
|
|
9
10
|
export type AkumaIdleOptions = Readonly<{
|
|
10
11
|
timeoutMs?: number;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
}>;
|
|
14
|
+
export type AkumaIdleResult = Readonly<{
|
|
15
|
+
reason: WaitReason;
|
|
16
|
+
status: AkumaStatus;
|
|
11
17
|
}>;
|
|
12
18
|
export type AkumaHistoryOptions = Readonly<{
|
|
13
19
|
before?: number;
|
|
@@ -26,7 +32,7 @@ export type AkumaBirthInput = Readonly<{
|
|
|
26
32
|
allowed?: readonly AllowedAction[];
|
|
27
33
|
}>;
|
|
28
34
|
export type AkumaTellOptions<T> = Readonly<{
|
|
29
|
-
schema: Schema<T>;
|
|
35
|
+
schema: Schema<T> | StandardSchemaV1<T>;
|
|
30
36
|
interrupt?: boolean;
|
|
31
37
|
initiator?: string;
|
|
32
38
|
}>;
|
|
@@ -39,13 +45,14 @@ export declare class Akuma {
|
|
|
39
45
|
static select(root: WorldRoot, selector: string): Akuma;
|
|
40
46
|
tell(text: string, options?: Readonly<{
|
|
41
47
|
initiator?: string;
|
|
48
|
+
signal?: AbortSignal;
|
|
42
49
|
}>): Promise<string>;
|
|
43
|
-
tell<T>(text: string, options: AkumaTellOptions<T>): Promise<T>;
|
|
50
|
+
tell<T>(text: string, options: AkumaTellOptions<T> & AkumaSignalOptions): Promise<T>;
|
|
44
51
|
status(): Promise<AkumaStatus>;
|
|
45
52
|
interrupt(text: string, options?: AkumaSignalOptions & Readonly<{
|
|
46
53
|
initiator?: string;
|
|
47
54
|
}>): Promise<InterruptReceipt>;
|
|
48
|
-
idle(options?: AkumaIdleOptions): Promise<
|
|
55
|
+
idle(options?: AkumaIdleOptions): Promise<AkumaIdleResult>;
|
|
49
56
|
history(options?: AkumaHistoryOptions): Promise<ActivityHistory>;
|
|
50
57
|
kill(options?: AkumaSignalOptions): Promise<KillEvidence>;
|
|
51
58
|
}
|
|
@@ -4,15 +4,14 @@ import { spawnAkumaBody } from "./body.js";
|
|
|
4
4
|
import { decodeAllowedActions, unionAllowedActions } from "./allowed.js";
|
|
5
5
|
import { AkumaDecodeError, AkumaProviderError } from "./akuma-errors.js";
|
|
6
6
|
import { AkumaHandle } from "./akuma-handle.js";
|
|
7
|
-
import {
|
|
8
|
-
import { bornStatus } from "./akuma-observe.js";
|
|
7
|
+
import { bornStatus, defaultWaitComplete, waitForObservation } from "./akuma-observe.js";
|
|
9
8
|
import { loadArchetype } from "./archetype.js";
|
|
10
9
|
import { activitySlice, readTell, readTurn } from "./heart/index.js";
|
|
11
10
|
import { parseAkuId, pathsForAkuId } from "./identity.js";
|
|
12
11
|
import { birthAkuma, launchAkuma } from "./publication.js";
|
|
13
12
|
import { projectTurns, selectHistory } from "./projection.js";
|
|
14
13
|
import { settings as readSettings } from "../settings.js";
|
|
15
|
-
import { schemaJsonText } from "./schema.js";
|
|
14
|
+
import { schemaFromStandard, schemaJsonText } from "./schema.js";
|
|
16
15
|
import { abortable } from "./abort.js";
|
|
17
16
|
const HISTORY_LIMIT = 12;
|
|
18
17
|
function signalOption(value) {
|
|
@@ -22,25 +21,37 @@ function signalOption(value) {
|
|
|
22
21
|
throw new TypeError("signal must be an AbortSignal");
|
|
23
22
|
return value;
|
|
24
23
|
}
|
|
25
|
-
function wait(milliseconds) {
|
|
26
|
-
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
27
|
-
}
|
|
28
24
|
function recordedTell(result) {
|
|
29
25
|
if (result.wake.kind === "failed")
|
|
30
26
|
throw new AkumaProviderError(result.wake.diagnostic);
|
|
31
27
|
return { tellId: result.admission.tellId };
|
|
32
28
|
}
|
|
33
|
-
async function recordPlainTell(
|
|
34
|
-
const
|
|
29
|
+
async function recordPlainTell(input) {
|
|
30
|
+
const { id, root, body, tellId, initiator, signal } = input;
|
|
31
|
+
const admitted = await new AkumaHandle(id, root).tell(body, tellId, undefined, undefined, {
|
|
32
|
+
...(initiator === undefined ? {} : { initiator }),
|
|
33
|
+
...(signal === undefined ? {} : { signal }),
|
|
34
|
+
});
|
|
35
35
|
return recordedTell(admitted);
|
|
36
36
|
}
|
|
37
|
+
function plainTellInput(input) {
|
|
38
|
+
return {
|
|
39
|
+
id: input.id,
|
|
40
|
+
root: input.root,
|
|
41
|
+
body: input.body,
|
|
42
|
+
tellId: input.tellId,
|
|
43
|
+
...(input.options?.initiator === undefined ? {} : { initiator: input.options.initiator }),
|
|
44
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
37
47
|
async function recordSchemaTell(input) {
|
|
38
|
-
const { id, body, tellId,
|
|
39
|
-
if (
|
|
48
|
+
const { id, body, tellId, schema, root } = input;
|
|
49
|
+
if (input.interrupt === true) {
|
|
40
50
|
const interrupted = await new AkumaHandle(id, root).interrupt(body, {
|
|
41
51
|
tellId,
|
|
42
|
-
schemaJson: schemaJsonText(
|
|
43
|
-
...(
|
|
52
|
+
schemaJson: schemaJsonText(schema),
|
|
53
|
+
...(input.initiator === undefined ? {} : { initiator: input.initiator }),
|
|
54
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
44
55
|
});
|
|
45
56
|
if (interrupted.kind === "unavailable") {
|
|
46
57
|
throw new AkumaProviderError(`schema interrupt unavailable: ${interrupted.evidence}`);
|
|
@@ -48,8 +59,9 @@ async function recordSchemaTell(input) {
|
|
|
48
59
|
return recordedTell(interrupted.tell);
|
|
49
60
|
}
|
|
50
61
|
const admitted = await new AkumaHandle(id, root).tell(body, tellId, undefined, undefined, {
|
|
51
|
-
schemaJson: schemaJsonText(
|
|
52
|
-
...(
|
|
62
|
+
schemaJson: schemaJsonText(schema),
|
|
63
|
+
...(input.initiator === undefined ? {} : { initiator: input.initiator }),
|
|
64
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
53
65
|
});
|
|
54
66
|
return recordedTell(admitted);
|
|
55
67
|
}
|
|
@@ -66,19 +78,21 @@ async function boundOutcome(paths, tell) {
|
|
|
66
78
|
const turn = await readTurn(paths, tell.binding.turnSequence);
|
|
67
79
|
return turn?.end?.outcome ?? null;
|
|
68
80
|
}
|
|
69
|
-
async function awaitTellOutcome(paths, tellId) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
async function awaitTellOutcome(paths, tellId, signal) {
|
|
82
|
+
const waited = await waitForObservation({
|
|
83
|
+
...(signal === undefined ? {} : { signal }),
|
|
84
|
+
observe: async () => {
|
|
85
|
+
const tell = await readTell(paths, tellId);
|
|
86
|
+
if (tell === null)
|
|
87
|
+
throw new AkumaProviderError(`recorded Tell ${tellId} is missing from Heart`);
|
|
88
|
+
const outcome = await boundOutcome(paths, tell);
|
|
89
|
+
return { outcome, terminalWithoutTurn: tell.state === "told" && tell.binding === undefined };
|
|
90
|
+
},
|
|
91
|
+
complete: (observed) => observed.outcome !== null || observed.terminalWithoutTurn,
|
|
92
|
+
});
|
|
93
|
+
if (waited.value.outcome !== null)
|
|
94
|
+
return waited.value.outcome;
|
|
95
|
+
throw new AkumaProviderError(`recorded Tell ${tellId} reached a terminal delivery without a Turn binding`);
|
|
82
96
|
}
|
|
83
97
|
export class Akuma {
|
|
84
98
|
id;
|
|
@@ -134,20 +148,34 @@ export class Akuma {
|
|
|
134
148
|
async tell(text, options) {
|
|
135
149
|
if (typeof text !== "string")
|
|
136
150
|
throw new TypeError("Akuma tell text must be a string");
|
|
151
|
+
const signal = signalOption(options?.signal);
|
|
152
|
+
signal?.throwIfAborted();
|
|
137
153
|
const tellId = randomUUID();
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
const schemaOptions = options !== undefined && "schema" in options ? options : undefined;
|
|
155
|
+
const schema = schemaOptions === undefined ? undefined : schemaFromStandard(schemaOptions.schema);
|
|
156
|
+
const recorded = schemaOptions === undefined || schema === undefined
|
|
157
|
+
? await recordPlainTell(plainTellInput({
|
|
141
158
|
id: this.id,
|
|
159
|
+
root: this.root,
|
|
142
160
|
body: text,
|
|
143
161
|
tellId,
|
|
144
162
|
options,
|
|
163
|
+
signal,
|
|
164
|
+
}))
|
|
165
|
+
: await recordSchemaTell({
|
|
166
|
+
id: this.id,
|
|
167
|
+
body: text,
|
|
168
|
+
tellId,
|
|
169
|
+
schema,
|
|
145
170
|
root: this.root,
|
|
171
|
+
...(schemaOptions.interrupt === undefined ? {} : { interrupt: schemaOptions.interrupt }),
|
|
172
|
+
...(schemaOptions.initiator === undefined ? {} : { initiator: schemaOptions.initiator }),
|
|
173
|
+
...(signal === undefined ? {} : { signal }),
|
|
146
174
|
});
|
|
147
|
-
const outcome = await awaitTellOutcome(this.paths, recorded.tellId);
|
|
175
|
+
const outcome = await awaitTellOutcome(this.paths, recorded.tellId, signal);
|
|
148
176
|
if (outcome.kind !== "answered")
|
|
149
177
|
outcomeError(outcome);
|
|
150
|
-
if (
|
|
178
|
+
if (schema === undefined)
|
|
151
179
|
return outcome.answer;
|
|
152
180
|
const raw = outcome.answerJson ?? outcome.answer;
|
|
153
181
|
let parsed;
|
|
@@ -158,7 +186,7 @@ export class Akuma {
|
|
|
158
186
|
throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer is not valid JSON", outcome.answer);
|
|
159
187
|
}
|
|
160
188
|
try {
|
|
161
|
-
return
|
|
189
|
+
return schema.decode(parsed);
|
|
162
190
|
}
|
|
163
191
|
catch (error) {
|
|
164
192
|
throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer failed schema decode", outcome.answer);
|
|
@@ -185,19 +213,20 @@ export class Akuma {
|
|
|
185
213
|
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
186
214
|
throw new TypeError("Akuma idle options must be an object");
|
|
187
215
|
}
|
|
188
|
-
const unknown = Object.keys(options).find((key) => key !== "timeoutMs");
|
|
216
|
+
const unknown = Object.keys(options).find((key) => key !== "timeoutMs" && key !== "signal");
|
|
189
217
|
if (unknown !== undefined)
|
|
190
218
|
throw new TypeError(`Akuma idle options has unknown field: ${unknown}`);
|
|
191
219
|
if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 0)) {
|
|
192
220
|
throw new TypeError("Akuma idle timeoutMs must be a nonnegative finite millisecond duration");
|
|
193
221
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
222
|
+
const signal = signalOption(options.signal);
|
|
223
|
+
const waited = await waitForObservation({
|
|
224
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
225
|
+
...(signal === undefined ? {} : { signal }),
|
|
226
|
+
observe: async () => (await bornStatus(this.paths, this.id, { aperture: "monitoring" })).status,
|
|
227
|
+
complete: defaultWaitComplete,
|
|
228
|
+
});
|
|
229
|
+
return { reason: waited.reason, status: waited.value };
|
|
201
230
|
}
|
|
202
231
|
async history(options = {}) {
|
|
203
232
|
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AkuId, type AkumaPaths } from "./identity.js";
|
|
2
|
-
import { selectHistory, type ActivityHistory, type ActivitySnapshot } from "./projection.js";
|
|
2
|
+
import { selectHistory, type ActivityHistory, type ActivityRow, type ActivitySnapshot } from "./projection.js";
|
|
3
3
|
import type { WorldRoot } from "../world.js";
|
|
4
4
|
import type { AkumaListRow, AkumaStatus, UnbornAkumaListRow } from "./akuma.js";
|
|
5
5
|
export declare function fleetListRow(paths: AkumaPaths, expected: AkuId): Promise<AkumaListRow | UnbornAkumaListRow>;
|
|
@@ -7,6 +7,14 @@ export type BudgetedStatusObservation = Readonly<{
|
|
|
7
7
|
status: AkumaStatus;
|
|
8
8
|
ordinarySelected: number;
|
|
9
9
|
}>;
|
|
10
|
+
/**
|
|
11
|
+
* The internal live-observation companion for one status frontier. It is not
|
|
12
|
+
* part of status or any returned facade result: viewers receive it only while
|
|
13
|
+
* an observation loop is still streaming.
|
|
14
|
+
*/
|
|
15
|
+
export type LiveStatusObservation = BudgetedStatusObservation & Readonly<{
|
|
16
|
+
rows: readonly ActivityRow[];
|
|
17
|
+
}>;
|
|
10
18
|
export declare function bornStatus(paths: AkumaPaths, expected: AkuId, input: Readonly<{
|
|
11
19
|
aperture: "monitoring" | "receipt";
|
|
12
20
|
ordinaryBudget?: number;
|
|
@@ -17,18 +25,43 @@ export declare function readBudgetedStatus(worldPath: WorldRoot, id: AkuId, inpu
|
|
|
17
25
|
ordinaryBudget?: number;
|
|
18
26
|
admittedTellId?: string;
|
|
19
27
|
}>): Promise<BudgetedStatusObservation>;
|
|
28
|
+
export declare function readLiveStatus(worldPath: WorldRoot, id: AkuId, input: Readonly<{
|
|
29
|
+
aperture: "monitoring" | "receipt";
|
|
30
|
+
ordinaryBudget?: number;
|
|
31
|
+
admittedTellId?: string;
|
|
32
|
+
}>): Promise<LiveStatusObservation>;
|
|
20
33
|
export declare function defaultWaitComplete(status: AkumaStatus): boolean;
|
|
21
34
|
export declare function readWaitComplete(worldPath: WorldRoot, id: AkuId): Promise<boolean>;
|
|
35
|
+
export type WaitReason = "completed" | "deadline";
|
|
36
|
+
/**
|
|
37
|
+
* The one deadline-aware observation loop for public Akuma waiting. It always
|
|
38
|
+
* reads once before judging a passed deadline, so a zero-duration wait remains
|
|
39
|
+
* an honest final snapshot and completion wins at the deadline edge.
|
|
40
|
+
*/
|
|
41
|
+
export declare function waitForObservation<T>(input: Readonly<{
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
probe?(): Promise<boolean>;
|
|
45
|
+
observe(): Promise<T>;
|
|
46
|
+
complete(value: T): boolean;
|
|
47
|
+
onObserve?(value: T): void | Promise<void>;
|
|
48
|
+
}>): Promise<Readonly<{
|
|
49
|
+
reason: WaitReason;
|
|
50
|
+
value: T;
|
|
51
|
+
}>>;
|
|
22
52
|
/**
|
|
23
53
|
* Successive status observations of one already born Akuma until its
|
|
24
|
-
* observation window closes. Every
|
|
25
|
-
*
|
|
26
|
-
* already
|
|
27
|
-
* nothing and returns the current status.
|
|
54
|
+
* observation window closes. Every acquired observation is reported to
|
|
55
|
+
* `observe` before completion or deadline judgment, including the first
|
|
56
|
+
* already-complete observation; the last status is returned.
|
|
28
57
|
*/
|
|
29
58
|
export declare function observeAkumaStatus(worldPath: WorldRoot, expected: AkuId, input: Readonly<{
|
|
30
59
|
timeoutMs: number;
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
observe: (status: LiveStatusObservation) => void;
|
|
62
|
+
}>): Promise<Readonly<{
|
|
63
|
+
reason: WaitReason;
|
|
64
|
+
status: AkumaStatus;
|
|
65
|
+
}>>;
|
|
33
66
|
export declare function readAkumaBirthCwd(worldPath: WorldRoot, id: AkuId): Promise<string>;
|
|
34
67
|
export { selectHistory, type ActivityHistory, type ActivitySnapshot };
|