@astrosheep/keiyaku 4.5.23 → 4.5.24
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.js +5 -2
- package/build/src/akuma/akuma-instance.d.ts +15 -3
- package/build/src/akuma/akuma-instance.js +27 -13
- package/build/src/akuma/akuma.d.ts +1 -0
- package/build/src/akuma/akuma.js +1 -0
- package/build/src/akuma/fleet-execution.d.ts +2 -0
- package/build/src/akuma/fleet-execution.js +22 -5
- package/build/src/akuma/heart/index.d.ts +3 -0
- package/build/src/akuma/heart/index.js +12 -2
- 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 +4 -0
- package/build/src/akuma/projection.d.ts +1 -1
- package/build/src/akuma/projection.js +1 -1
- package/build/src/akuma/schema.d.ts +40 -0
- package/build/src/akuma/schema.js +114 -0
- package/build/src/cli/commands/akuma-invoke.d.ts +5 -0
- package/build/src/cli/commands/akuma-invoke.js +19 -3
- package/build/src/cli/render/akuma-activity.d.ts +6 -3
- package/build/src/cli/render/akuma-activity.js +168 -51
- 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 +2 -1
- package/build/src/library/fleet.js +54 -11
- package/build/src/protocol/read/status.d.ts +5 -0
- package/build/src/protocol/read/status.js +11 -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()`
|
|
@@ -33,10 +33,13 @@ async function takeLeashUntilSignal(paths, bodySequence, signal, unbounded = fal
|
|
|
33
33
|
export async function settleAkumaKill(paths, signal, retainLeash = false) {
|
|
34
34
|
const request = await requestStop(paths, new Date().toISOString(), signal);
|
|
35
35
|
if (request.kind !== "requested") {
|
|
36
|
+
// A witnessed Body was already settled: the kill witness Heart recorded is
|
|
37
|
+
// the kill evidence, not a stop outcome.
|
|
38
|
+
const evidence = request.kind === "witnessed" ? "killed" : request.kind;
|
|
36
39
|
if (!retainLeash)
|
|
37
|
-
return { evidence
|
|
40
|
+
return { evidence };
|
|
38
41
|
const leash = await acquireLeash(paths, signal === undefined ? {} : { signal });
|
|
39
|
-
return leash === null ? { evidence: "unavailable" } : { evidence
|
|
42
|
+
return leash === null ? { evidence: "unavailable" } : { evidence, leash };
|
|
40
43
|
}
|
|
41
44
|
const target = request.body;
|
|
42
45
|
const waited = await takeLeashUntilSignal(paths, target.sequence, signal);
|
|
@@ -5,10 +5,22 @@ import { type AkuId } from "./identity.js";
|
|
|
5
5
|
import { type ActivityHistory } from "./projection.js";
|
|
6
6
|
import type { Settings } from "../settings.js";
|
|
7
7
|
import type { WorldRoot } from "../world.js";
|
|
8
|
-
import { type Schema } from "./schema.js";
|
|
8
|
+
import { type Schema, type StandardSchemaV1 } from "./schema.js";
|
|
9
9
|
export type AkumaIdleOptions = Readonly<{
|
|
10
10
|
timeoutMs?: number;
|
|
11
11
|
}>;
|
|
12
|
+
export type AkumaIdleResult = Readonly<{
|
|
13
|
+
kind: "idle";
|
|
14
|
+
status: AkumaStatus;
|
|
15
|
+
reason: Exclude<AkumaStatus["life"], "running">;
|
|
16
|
+
}> | Readonly<{
|
|
17
|
+
kind: "timeout";
|
|
18
|
+
status: AkumaStatus;
|
|
19
|
+
reason: Readonly<{
|
|
20
|
+
running: boolean;
|
|
21
|
+
pendingTell: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
12
24
|
export type AkumaHistoryOptions = Readonly<{
|
|
13
25
|
before?: number;
|
|
14
26
|
since?: number;
|
|
@@ -26,7 +38,7 @@ export type AkumaBirthInput = Readonly<{
|
|
|
26
38
|
allowed?: readonly AllowedAction[];
|
|
27
39
|
}>;
|
|
28
40
|
export type AkumaTellOptions<T> = Readonly<{
|
|
29
|
-
schema: Schema<T>;
|
|
41
|
+
schema: Schema<T> | StandardSchemaV1<T>;
|
|
30
42
|
interrupt?: boolean;
|
|
31
43
|
initiator?: string;
|
|
32
44
|
}>;
|
|
@@ -45,7 +57,7 @@ export declare class Akuma {
|
|
|
45
57
|
interrupt(text: string, options?: AkumaSignalOptions & Readonly<{
|
|
46
58
|
initiator?: string;
|
|
47
59
|
}>): Promise<InterruptReceipt>;
|
|
48
|
-
idle(options?: AkumaIdleOptions): Promise<
|
|
60
|
+
idle(options?: AkumaIdleOptions): Promise<AkumaIdleResult>;
|
|
49
61
|
history(options?: AkumaHistoryOptions): Promise<ActivityHistory>;
|
|
50
62
|
kill(options?: AkumaSignalOptions): Promise<KillEvidence>;
|
|
51
63
|
}
|
|
@@ -12,7 +12,7 @@ import { parseAkuId, pathsForAkuId } from "./identity.js";
|
|
|
12
12
|
import { birthAkuma, launchAkuma } from "./publication.js";
|
|
13
13
|
import { projectTurns, selectHistory } from "./projection.js";
|
|
14
14
|
import { settings as readSettings } from "../settings.js";
|
|
15
|
-
import { schemaJsonText } from "./schema.js";
|
|
15
|
+
import { schemaFromStandard, schemaJsonText } from "./schema.js";
|
|
16
16
|
import { abortable } from "./abort.js";
|
|
17
17
|
const HISTORY_LIMIT = 12;
|
|
18
18
|
function signalOption(value) {
|
|
@@ -25,6 +25,15 @@ function signalOption(value) {
|
|
|
25
25
|
function wait(milliseconds) {
|
|
26
26
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
27
27
|
}
|
|
28
|
+
function pendingTellOf(status) {
|
|
29
|
+
return status.timeline.entries.some((entry) => entry.kind === "row" && entry.row.kind === "tell" && entry.row.state === "pending");
|
|
30
|
+
}
|
|
31
|
+
function idleResult(status) {
|
|
32
|
+
const pendingTell = pendingTellOf(status);
|
|
33
|
+
if (status.life !== "running" && !pendingTell)
|
|
34
|
+
return { kind: "idle", status, reason: status.life };
|
|
35
|
+
return { kind: "timeout", status, reason: { running: status.life === "running", pendingTell } };
|
|
36
|
+
}
|
|
28
37
|
function recordedTell(result) {
|
|
29
38
|
if (result.wake.kind === "failed")
|
|
30
39
|
throw new AkumaProviderError(result.wake.diagnostic);
|
|
@@ -35,12 +44,12 @@ async function recordPlainTell(id, root, body, tellId, initiator) {
|
|
|
35
44
|
return recordedTell(admitted);
|
|
36
45
|
}
|
|
37
46
|
async function recordSchemaTell(input) {
|
|
38
|
-
const { id, body, tellId,
|
|
39
|
-
if (
|
|
47
|
+
const { id, body, tellId, schema, root } = input;
|
|
48
|
+
if (input.interrupt === true) {
|
|
40
49
|
const interrupted = await new AkumaHandle(id, root).interrupt(body, {
|
|
41
50
|
tellId,
|
|
42
|
-
schemaJson: schemaJsonText(
|
|
43
|
-
...(
|
|
51
|
+
schemaJson: schemaJsonText(schema),
|
|
52
|
+
...(input.initiator === undefined ? {} : { initiator: input.initiator }),
|
|
44
53
|
});
|
|
45
54
|
if (interrupted.kind === "unavailable") {
|
|
46
55
|
throw new AkumaProviderError(`schema interrupt unavailable: ${interrupted.evidence}`);
|
|
@@ -48,8 +57,8 @@ async function recordSchemaTell(input) {
|
|
|
48
57
|
return recordedTell(interrupted.tell);
|
|
49
58
|
}
|
|
50
59
|
const admitted = await new AkumaHandle(id, root).tell(body, tellId, undefined, undefined, {
|
|
51
|
-
schemaJson: schemaJsonText(
|
|
52
|
-
...(
|
|
60
|
+
schemaJson: schemaJsonText(schema),
|
|
61
|
+
...(input.initiator === undefined ? {} : { initiator: input.initiator }),
|
|
53
62
|
});
|
|
54
63
|
return recordedTell(admitted);
|
|
55
64
|
}
|
|
@@ -135,19 +144,23 @@ export class Akuma {
|
|
|
135
144
|
if (typeof text !== "string")
|
|
136
145
|
throw new TypeError("Akuma tell text must be a string");
|
|
137
146
|
const tellId = randomUUID();
|
|
138
|
-
const
|
|
147
|
+
const schemaOptions = options !== undefined && "schema" in options ? options : undefined;
|
|
148
|
+
const schema = schemaOptions === undefined ? undefined : schemaFromStandard(schemaOptions.schema);
|
|
149
|
+
const recorded = schemaOptions === undefined || schema === undefined
|
|
139
150
|
? await recordPlainTell(this.id, this.root, text, tellId, options?.initiator)
|
|
140
151
|
: await recordSchemaTell({
|
|
141
152
|
id: this.id,
|
|
142
153
|
body: text,
|
|
143
154
|
tellId,
|
|
144
|
-
|
|
155
|
+
schema,
|
|
145
156
|
root: this.root,
|
|
157
|
+
...(schemaOptions.interrupt === undefined ? {} : { interrupt: schemaOptions.interrupt }),
|
|
158
|
+
...(schemaOptions.initiator === undefined ? {} : { initiator: schemaOptions.initiator }),
|
|
146
159
|
});
|
|
147
160
|
const outcome = await awaitTellOutcome(this.paths, recorded.tellId);
|
|
148
161
|
if (outcome.kind !== "answered")
|
|
149
162
|
outcomeError(outcome);
|
|
150
|
-
if (
|
|
163
|
+
if (schema === undefined)
|
|
151
164
|
return outcome.answer;
|
|
152
165
|
const raw = outcome.answerJson ?? outcome.answer;
|
|
153
166
|
let parsed;
|
|
@@ -158,7 +171,7 @@ export class Akuma {
|
|
|
158
171
|
throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer is not valid JSON", outcome.answer);
|
|
159
172
|
}
|
|
160
173
|
try {
|
|
161
|
-
return
|
|
174
|
+
return schema.decode(parsed);
|
|
162
175
|
}
|
|
163
176
|
catch (error) {
|
|
164
177
|
throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer failed schema decode", outcome.answer);
|
|
@@ -194,8 +207,9 @@ export class Akuma {
|
|
|
194
207
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
195
208
|
for (;;) {
|
|
196
209
|
const observed = await bornStatus(this.paths, this.id, { aperture: "monitoring" });
|
|
197
|
-
if (defaultWaitComplete(observed.status) || (deadline !== undefined && performance.now() >= deadline))
|
|
198
|
-
return;
|
|
210
|
+
if (defaultWaitComplete(observed.status) || (deadline !== undefined && performance.now() >= deadline)) {
|
|
211
|
+
return idleResult(observed.status);
|
|
212
|
+
}
|
|
199
213
|
await wait(deadline === undefined ? POLL_MS : Math.min(POLL_MS, Math.max(0, deadline - performance.now())));
|
|
200
214
|
}
|
|
201
215
|
}
|
|
@@ -427,6 +427,7 @@ export declare const akumaStatusSchema: z.ZodObject<{
|
|
|
427
427
|
export type AkumaStatus = z.infer<typeof akumaStatusSchema>;
|
|
428
428
|
export declare function parseAkumaStatus(value: unknown): AkumaStatus;
|
|
429
429
|
export { defaultWaitComplete } from "./akuma-observe.js";
|
|
430
|
+
export { withoutReportedChanges } from "./projection.js";
|
|
430
431
|
export type { ReadonlyRestraint } from "./provider-recipe.js";
|
|
431
432
|
export type * from "./projection.js";
|
|
432
433
|
export type UnbornAkumaListRow = Readonly<{
|
package/build/src/akuma/akuma.js
CHANGED
|
@@ -36,6 +36,7 @@ export function parseAkumaStatus(value) {
|
|
|
36
36
|
return akumaStatusSchema.parse(value);
|
|
37
37
|
}
|
|
38
38
|
export { defaultWaitComplete } from "./akuma-observe.js";
|
|
39
|
+
export { withoutReportedChanges } from "./projection.js";
|
|
39
40
|
export { AkumaNotBornError } from "./akuma-errors.js";
|
|
40
41
|
export function wait(milliseconds) {
|
|
41
42
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
@@ -30,6 +30,8 @@ export type WaitExecutionInput = Readonly<{
|
|
|
30
30
|
signal?: AbortSignal;
|
|
31
31
|
/** Resolves one Akuma's identity facts; consulted once per Akuma a viewer first sees. */
|
|
32
32
|
identity?: (id: AkumaStatus["id"]) => Promise<WaitIdentityFacts>;
|
|
33
|
+
/** The selected set in the caller's order; a viewer's head and scoreboard follow it. */
|
|
34
|
+
selectionOrder?: readonly AkumaStatus["id"][];
|
|
33
35
|
/** Reports the frozen selected set before the first round, so a viewer can fix its layout. */
|
|
34
36
|
onSelected?: (selected: readonly WaitSelectedAkuma[]) => void;
|
|
35
37
|
/** Reports every observation round to a live viewer; absent keeps the cheap completion probe. */
|
|
@@ -87,20 +87,37 @@ function delay(milliseconds, signal) {
|
|
|
87
87
|
signal?.addEventListener("abort", abort, { once: true });
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
+
/** Canonical ids in the caller's selection order, appending any id the caller never named. */
|
|
91
|
+
function selectionOrderedIds(ids, order) {
|
|
92
|
+
const present = new Set(ids);
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
const ordered = [];
|
|
95
|
+
for (const id of order) {
|
|
96
|
+
if (present.has(id) && !seen.has(id)) {
|
|
97
|
+
seen.add(id);
|
|
98
|
+
ordered.push(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const id of ids) {
|
|
102
|
+
if (!seen.has(id)) {
|
|
103
|
+
seen.add(id);
|
|
104
|
+
ordered.push(id);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return ordered;
|
|
108
|
+
}
|
|
90
109
|
export async function executeWaitAkuma(input) {
|
|
91
110
|
const deadline = input.timeoutMs === undefined ? undefined : performance.now() + input.timeoutMs;
|
|
92
111
|
// Identity facts are read once per observed Akuma, not once per 100ms round.
|
|
93
112
|
const facts = new Map();
|
|
94
113
|
if (input.onSelected !== undefined) {
|
|
95
|
-
const selected = [];
|
|
96
114
|
for (const id of input.ids) {
|
|
97
115
|
input.signal?.throwIfAborted();
|
|
98
116
|
const known = input.identity === undefined ? undefined : await input.identity(id);
|
|
99
|
-
|
|
100
|
-
facts.set(id, resolved);
|
|
101
|
-
selected.push({ id, ...resolved });
|
|
117
|
+
facts.set(id, known ?? { contract: NO_DISPATCH_ASSOCIATION });
|
|
102
118
|
}
|
|
103
|
-
input.
|
|
119
|
+
const ordered = input.selectionOrder === undefined ? input.ids : selectionOrderedIds(input.ids, input.selectionOrder);
|
|
120
|
+
input.onSelected(ordered.map((id) => ({ id, ...facts.get(id) })));
|
|
104
121
|
}
|
|
105
122
|
const observeRound = async (statuses) => {
|
|
106
123
|
if (input.observe === undefined)
|
|
@@ -55,6 +55,9 @@ export declare function readKill(paths: AkumaPaths, bodySequence: number): Promi
|
|
|
55
55
|
export declare function requestStop(paths: AkumaPaths, at: string, signal?: AbortSignal): Promise<Readonly<{
|
|
56
56
|
kind: "requested";
|
|
57
57
|
body: BodyFact;
|
|
58
|
+
}> | Readonly<{
|
|
59
|
+
kind: "witnessed";
|
|
60
|
+
body: BodyFact;
|
|
58
61
|
}> | Readonly<{
|
|
59
62
|
kind: "already-killed" | "already-stopped";
|
|
60
63
|
body: BodyFact;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parsePublicHistoryId } from "../identity.js";
|
|
2
|
-
import { answeredTurnFact, endBodyFact, finishBodyFact, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertPauseControl, insertSessionFact, insertStopControl, killFactForBody, latestBodyFact, latestKillFact, latestSessionFact, lastAnsweredTurnFact, pauseFact, sessionFactForCoordinate, stopFact, turnFact, } from "./rows.js";
|
|
2
|
+
import { answeredTurnFact, endBodyFact, finishBodyFact, deleteStopControl, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertPauseControl, insertKillFact, insertSessionFact, insertStopControl, killFactForBody, latestBodyFact, latestKillFact, latestSessionFact, lastAnsweredTurnFact, pauseFact, sessionFactForCoordinate, stopFact, turnFact, } from "./rows.js";
|
|
3
3
|
import { insertTellDeliveryFact, dispositionSnapshotProven, insertTellBindingFact, insertTellDispositionSnapshot, insertTellFact, insertTellReceiptFact, insertUndeliveredTellReceipts, latestOpenTellDisposition, openBoundTurns, openTellDispositionIds, pendingTellFacts, hasPendingTell, resolveTellDispositionSnapshot, tellDispositionResolved, tellFact, tellIdsForFence, } from "./tells.js";
|
|
4
4
|
import { activityFactSlice, statusFacts, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
|
|
5
5
|
import { isHeartAbsent, readSealFromLeash, withHeartTransaction, withReadOnlyHeart } from "./storage.js";
|
|
@@ -180,8 +180,18 @@ export async function requestStop(paths, at, signal) {
|
|
|
180
180
|
throw new Error("Akuma has no Body to kill");
|
|
181
181
|
if (latestKillFact(heart)?.bodySequence === body.sequence)
|
|
182
182
|
return { kind: "already-killed", body };
|
|
183
|
-
if (body.end !== undefined)
|
|
183
|
+
if (body.end !== undefined) {
|
|
184
|
+
// A stranded Body is already explicitly settled: kill witnesses it in
|
|
185
|
+
// place rather than requesting a stop from a dead process. A normally
|
|
186
|
+
// exited Body remains already-stopped, and hung custody is untouched.
|
|
187
|
+
if (body.hung === undefined && body.end !== "exited") {
|
|
188
|
+
insertKillFact(heart, body.sequence, at);
|
|
189
|
+
if (stopFact(heart)?.bodySequence === body.sequence)
|
|
190
|
+
deleteStopControl(heart);
|
|
191
|
+
return { kind: "witnessed", body };
|
|
192
|
+
}
|
|
184
193
|
return { kind: "already-stopped", body };
|
|
194
|
+
}
|
|
185
195
|
const existing = stopFact(heart);
|
|
186
196
|
if (existing !== null && existing.bodySequence !== body.sequence) {
|
|
187
197
|
throw new Error("Akuma stop target is not the latest Body");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { Akuma } from "./akuma-instance.js";
|
|
2
|
-
export type { AkumaBirthInput, AkumaHistoryOptions, AkumaIdleOptions, AkumaSignalOptions, AkumaTellOptions, } from "./akuma-instance.js";
|
|
2
|
+
export type { AkumaBirthInput, AkumaHistoryOptions, AkumaIdleOptions, AkumaIdleResult, AkumaSignalOptions, AkumaTellOptions, } from "./akuma-instance.js";
|
|
3
3
|
export type { InterruptReceipt, KillEvidence } from "./akuma.js";
|
|
4
4
|
export { Schema } from "./schema.js";
|
|
5
|
-
export type { JsonSchema, JsonSchemaDocument } from "./schema.js";
|
|
5
|
+
export type { JsonSchema, JsonSchemaDocument, StandardSchemaV1, SchemaLike } from "./schema.js";
|
|
6
6
|
export type { AkuId } from "./identity.js";
|
|
7
7
|
export type { AkumaStatus } from "./akuma.js";
|
|
8
8
|
export type { ActivityHistory, ActivityRow } from "./projection.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ActivitySnapshot, TurnLedger } from "./projection.js";
|
|
2
|
+
/** Present the same snapshot without its reported-change summary. */
|
|
3
|
+
export declare function withoutReportedChanges(snapshot: ActivitySnapshot): ActivitySnapshot;
|
|
2
4
|
import type { HistoryCursor, HistoryPage } from "./projection.js";
|
|
3
5
|
export declare function ordinarySnapshotBudget(ordinaryBudget?: number): Readonly<{
|
|
4
6
|
tail: number;
|
|
@@ -2,6 +2,10 @@ const DEFAULT_TAIL = 3;
|
|
|
2
2
|
const DEFAULT_VOICE = 3;
|
|
3
3
|
const REPORTED_CHANGE_LIMIT = 5;
|
|
4
4
|
const EMPTY_REPORTED = { reportedChanges: [], reportedChangesOmitted: 0 };
|
|
5
|
+
/** Present the same snapshot without its reported-change summary. */
|
|
6
|
+
export function withoutReportedChanges(snapshot) {
|
|
7
|
+
return { ...snapshot, reportedChanges: [], reportedChangesOmitted: 0 };
|
|
8
|
+
}
|
|
5
9
|
function assembleEntries(ledger, window, selected) {
|
|
6
10
|
const windowRows = new Set(window);
|
|
7
11
|
const result = [];
|
|
@@ -958,4 +958,4 @@ export type TurnLedger = Readonly<{
|
|
|
958
958
|
}>;
|
|
959
959
|
/** Fold the retained fact timeline into Turn-owned rows without inventing provider facts. */
|
|
960
960
|
export declare function projectTurns(facts: readonly TimelineFact[], retained?: RetainedWindow): TurnLedger;
|
|
961
|
-
export { ordinarySnapshotBudget, selectHistory, selectSnapshot } from "./projection-read.js";
|
|
961
|
+
export { ordinarySnapshotBudget, selectHistory, selectSnapshot, withoutReportedChanges } from "./projection-read.js";
|
|
@@ -406,4 +406,4 @@ export function projectTurns(facts, retained = {
|
|
|
406
406
|
}
|
|
407
407
|
return finishLedger(state, retained);
|
|
408
408
|
}
|
|
409
|
-
export { ordinarySnapshotBudget, selectHistory, selectSnapshot } from "./projection-read.js";
|
|
409
|
+
export { ordinarySnapshotBudget, selectHistory, selectSnapshot, withoutReportedChanges } from "./projection-read.js";
|
|
@@ -3,12 +3,52 @@ export type JsonSchemaDocument = Readonly<{
|
|
|
3
3
|
readonly [key: string]: unknown;
|
|
4
4
|
}>;
|
|
5
5
|
export type JsonSchema = JsonSchemaDocument;
|
|
6
|
+
export type StandardResult<Output> = Readonly<{
|
|
7
|
+
readonly value: Output;
|
|
8
|
+
readonly issues?: undefined;
|
|
9
|
+
}> | Readonly<{
|
|
10
|
+
readonly issues: readonly Readonly<{
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}>[];
|
|
13
|
+
}>;
|
|
14
|
+
/** Structural Standard Schema v1 marker; no runtime dependency on a library. */
|
|
15
|
+
export type StandardSchemaV1<Output = unknown> = Readonly<{
|
|
16
|
+
readonly "~standard": Readonly<{
|
|
17
|
+
readonly version: 1;
|
|
18
|
+
readonly vendor: string;
|
|
19
|
+
readonly validate: (value: unknown) => StandardResult<Output> | Promise<StandardResult<Output>>;
|
|
20
|
+
readonly types?: Readonly<{
|
|
21
|
+
readonly input: unknown;
|
|
22
|
+
readonly output: Output;
|
|
23
|
+
}> | undefined;
|
|
24
|
+
readonly jsonSchema?: Readonly<{
|
|
25
|
+
readonly output: (options: Readonly<{
|
|
26
|
+
readonly target: string;
|
|
27
|
+
}>) => unknown;
|
|
28
|
+
}> | undefined;
|
|
29
|
+
}>;
|
|
30
|
+
}>;
|
|
31
|
+
/** The schema forms a Tell answer contract accepts. */
|
|
32
|
+
export type SchemaLike<T> = Schema<T> | StandardSchemaV1<T>;
|
|
6
33
|
export declare class Schema<T> {
|
|
7
34
|
readonly jsonSchema: JsonSchemaDocument;
|
|
8
35
|
readonly decode: (value: unknown) => T;
|
|
9
36
|
private constructor();
|
|
10
37
|
static zod<Output>(schema: ZodType<Output>): Schema<Output>;
|
|
11
38
|
static json<Output>(schema: JsonSchemaDocument, decode: (value: unknown) => Output): Schema<Output>;
|
|
39
|
+
/**
|
|
40
|
+
* Bless a Standard Schema v1 value as a real Schema: validate the marker,
|
|
41
|
+
* project a JSON Schema document, refuse shapes outside the simple
|
|
42
|
+
* vocabulary, and decode at the boundary. The class owns this path so every
|
|
43
|
+
* normalized schema is born through the constructor rather than around it.
|
|
44
|
+
*/
|
|
45
|
+
static standard<Output>(value: StandardSchemaV1<Output>): Schema<Output>;
|
|
12
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Normalize a Tell answer contract to the internal Schema. The package's own
|
|
49
|
+
* Schema passes through; anything else delegates to the class's own blessing
|
|
50
|
+
* path, so inferred output types keep flowing from a genuine Schema instance.
|
|
51
|
+
*/
|
|
52
|
+
export declare function schemaFromStandard<T>(value: SchemaLike<T>): Schema<T>;
|
|
13
53
|
/** Internal neutral serialization for Heart/provider forwarding. */
|
|
14
54
|
export declare function schemaJsonText(schema: Schema<unknown>): string;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { toJSONSchema } from "zod";
|
|
2
2
|
const SCHEMA_JSON_MAX_BYTES = 65_536;
|
|
3
|
+
/**
|
|
4
|
+
* Keywords a provider answer contract may carry. Provider failures from
|
|
5
|
+
* fragile constraints are expensive to diagnose, so the seam refuses any
|
|
6
|
+
* projected document that steps outside simple JSON shape vocabulary.
|
|
7
|
+
*/
|
|
8
|
+
const SIMPLE_SCHEMA_KEYWORDS = new Set([
|
|
9
|
+
"type",
|
|
10
|
+
"properties",
|
|
11
|
+
"required",
|
|
12
|
+
"items",
|
|
13
|
+
"enum",
|
|
14
|
+
"const",
|
|
15
|
+
"anyOf",
|
|
16
|
+
"additionalProperties",
|
|
17
|
+
"description",
|
|
18
|
+
"title",
|
|
19
|
+
"$schema",
|
|
20
|
+
"$defs",
|
|
21
|
+
"$ref",
|
|
22
|
+
]);
|
|
3
23
|
function isPlainObject(value) {
|
|
4
24
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
5
25
|
return false;
|
|
@@ -26,6 +46,38 @@ function assertJsonValue(value, path) {
|
|
|
26
46
|
}
|
|
27
47
|
throw new TypeError(`${path} contains a non-JSON value`);
|
|
28
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Walk every subschema position and refuse a keyword outside the blessed shape
|
|
51
|
+
* vocabulary. Property names and enum/const data are not keywords, so their
|
|
52
|
+
* keys are never judged.
|
|
53
|
+
*/
|
|
54
|
+
function assertSimpleSchema(value, path) {
|
|
55
|
+
if (!isPlainObject(value))
|
|
56
|
+
throw new TypeError(`${path} must be a JSON Schema object`);
|
|
57
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
58
|
+
if (!SIMPLE_SCHEMA_KEYWORDS.has(key)) {
|
|
59
|
+
throw new TypeError(`provider answer contract uses unsupported JSON Schema keyword "${key}" at ${path}; ` +
|
|
60
|
+
"provider answer contracts carry simple shapes only");
|
|
61
|
+
}
|
|
62
|
+
if (key === "properties" || key === "$defs") {
|
|
63
|
+
for (const [name, subschema] of Object.entries(entry)) {
|
|
64
|
+
assertSimpleSchema(subschema, `${path}.${key}.${name}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (key === "items") {
|
|
68
|
+
if (Array.isArray(entry))
|
|
69
|
+
entry.forEach((subschema, index) => assertSimpleSchema(subschema, `${path}.items[${index}]`));
|
|
70
|
+
else
|
|
71
|
+
assertSimpleSchema(entry, `${path}.items`);
|
|
72
|
+
}
|
|
73
|
+
else if (key === "anyOf") {
|
|
74
|
+
entry.forEach((subschema, index) => assertSimpleSchema(subschema, `${path}.anyOf[${index}]`));
|
|
75
|
+
}
|
|
76
|
+
else if (key === "additionalProperties" && typeof entry !== "boolean") {
|
|
77
|
+
assertSimpleSchema(entry, `${path}.additionalProperties`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
29
81
|
function sortValue(value) {
|
|
30
82
|
if (Array.isArray(value))
|
|
31
83
|
return value.map(sortValue);
|
|
@@ -62,6 +114,44 @@ function canonicalDocument(value, label) {
|
|
|
62
114
|
}
|
|
63
115
|
return { json: freezeValue(JSON.parse(jsonText)), jsonText };
|
|
64
116
|
}
|
|
117
|
+
function standardMarker(value) {
|
|
118
|
+
const marker = typeof value === "object" && value !== null ? value["~standard"] : undefined;
|
|
119
|
+
if (typeof marker !== "object" ||
|
|
120
|
+
marker === null ||
|
|
121
|
+
marker.version !== 1 ||
|
|
122
|
+
typeof marker.vendor !== "string" ||
|
|
123
|
+
typeof marker.validate !== "function") {
|
|
124
|
+
throw new TypeError("schema must be a Schema or a Standard Schema v1 value");
|
|
125
|
+
}
|
|
126
|
+
return marker;
|
|
127
|
+
}
|
|
128
|
+
/** Project a Standard Schema value to a JSON Schema document, or refuse honestly. */
|
|
129
|
+
function projectStandardSchema(value, marker) {
|
|
130
|
+
if (marker.vendor === "zod") {
|
|
131
|
+
return toJSONSchema(value, { target: "draft-07", unrepresentable: "throw", cycles: "throw" });
|
|
132
|
+
}
|
|
133
|
+
const converter = marker.jsonSchema;
|
|
134
|
+
if (converter !== undefined && typeof converter.output === "function") {
|
|
135
|
+
return converter.output({ target: "draft-07" });
|
|
136
|
+
}
|
|
137
|
+
const method = value.toJSONSchema;
|
|
138
|
+
if (typeof method === "function") {
|
|
139
|
+
return method.call(value, { target: "draft-07" });
|
|
140
|
+
}
|
|
141
|
+
throw new TypeError(`schema vendor "${marker.vendor}" does not carry a JSON Schema projection; ` +
|
|
142
|
+
"pass a JSON Schema document and decoder to Schema.json instead");
|
|
143
|
+
}
|
|
144
|
+
function decodeStandard(value, marker) {
|
|
145
|
+
const result = marker.validate(value);
|
|
146
|
+
if (result instanceof Promise || (typeof result === "object" && result !== null && "then" in result)) {
|
|
147
|
+
throw new TypeError(`schema vendor "${marker.vendor}" validates asynchronously`);
|
|
148
|
+
}
|
|
149
|
+
if ("issues" in result && result.issues !== undefined) {
|
|
150
|
+
const detail = result.issues.map((issue) => issue.message).join("; ");
|
|
151
|
+
throw new Error(detail.length === 0 ? "Standard Schema validation failed" : detail);
|
|
152
|
+
}
|
|
153
|
+
return result.value;
|
|
154
|
+
}
|
|
65
155
|
export class Schema {
|
|
66
156
|
jsonSchema;
|
|
67
157
|
decode;
|
|
@@ -72,6 +162,7 @@ export class Schema {
|
|
|
72
162
|
}
|
|
73
163
|
static zod(schema) {
|
|
74
164
|
const payload = toJSONSchema(schema, { target: "draft-07", unrepresentable: "throw", cycles: "throw" });
|
|
165
|
+
assertSimpleSchema(payload, "$");
|
|
75
166
|
const canonical = canonicalDocument(payload, "Zod JSON Schema");
|
|
76
167
|
return new Schema(canonical.json, (value) => schema.parse(value));
|
|
77
168
|
}
|
|
@@ -82,6 +173,29 @@ export class Schema {
|
|
|
82
173
|
const canonical = canonicalDocument(document, "JSON Schema");
|
|
83
174
|
return new Schema(canonical.json, decode);
|
|
84
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Bless a Standard Schema v1 value as a real Schema: validate the marker,
|
|
178
|
+
* project a JSON Schema document, refuse shapes outside the simple
|
|
179
|
+
* vocabulary, and decode at the boundary. The class owns this path so every
|
|
180
|
+
* normalized schema is born through the constructor rather than around it.
|
|
181
|
+
*/
|
|
182
|
+
static standard(value) {
|
|
183
|
+
const marker = standardMarker(value);
|
|
184
|
+
const payload = projectStandardSchema(value, marker);
|
|
185
|
+
assertSimpleSchema(payload, "$");
|
|
186
|
+
const canonical = canonicalDocument(payload, `${marker.vendor} JSON Schema`);
|
|
187
|
+
return new Schema(canonical.json, (input) => decodeStandard(input, marker));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Normalize a Tell answer contract to the internal Schema. The package's own
|
|
192
|
+
* Schema passes through; anything else delegates to the class's own blessing
|
|
193
|
+
* path, so inferred output types keep flowing from a genuine Schema instance.
|
|
194
|
+
*/
|
|
195
|
+
export function schemaFromStandard(value) {
|
|
196
|
+
if (value instanceof Schema)
|
|
197
|
+
return value;
|
|
198
|
+
return Schema.standard(value);
|
|
85
199
|
}
|
|
86
200
|
/** Internal neutral serialization for Heart/provider forwarding. */
|
|
87
201
|
export function schemaJsonText(schema) {
|