@astrosheep/keiyaku 4.5.18 → 4.5.19

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.
@@ -8,17 +8,13 @@ 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 } from "./akuma-observe.js";
11
+ import { bornStatus, defaultWaitComplete, readWaitComplete } from "./akuma-observe.js";
12
12
  const CALL_EXECUTION = Symbol("akuma-call-execution");
13
13
  const POLL_MS = 100;
14
14
  const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
15
15
  function diagnostic(error) {
16
16
  return error instanceof Error ? error.message : String(error);
17
17
  }
18
- function defaultWaitComplete(status) {
19
- return (status.life !== "running" &&
20
- !status.timeline.entries.some((entry) => entry.kind === "row" && entry.row.kind === "tell" && entry.row.state === "pending"));
21
- }
22
18
  async function takeLeashUntilSignal(paths, bodySequence, signal, unbounded = false) {
23
19
  const leash = await acquireLeash(paths, {
24
20
  bodySequence,
@@ -35,7 +31,7 @@ async function takeLeashUntilSignal(paths, bodySequence, signal, unbounded = fal
35
31
  return { kind: "unavailable", evidence: "unavailable" };
36
32
  }
37
33
  export async function settleAkumaKill(paths, signal, retainLeash = false) {
38
- const request = await requestStop(paths, new Date().toISOString());
34
+ const request = await requestStop(paths, new Date().toISOString(), signal);
39
35
  if (request.kind !== "requested") {
40
36
  if (!retainLeash)
41
37
  return { evidence: request.kind };
@@ -141,9 +137,13 @@ export class AkumaHandle {
141
137
  }
142
138
  const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
143
139
  for (;;) {
144
- const status = await this.status();
145
- if (predicate(status) || (deadline !== undefined && performance.now() >= deadline))
146
- return status;
140
+ if (predicate !== defaultWaitComplete ||
141
+ (deadline !== undefined && performance.now() >= deadline) ||
142
+ (await readWaitComplete(this.worldPath, this.id))) {
143
+ const status = await this.status();
144
+ if (predicate(status) || (deadline !== undefined && performance.now() >= deadline))
145
+ return status;
146
+ }
147
147
  await wait(deadline === undefined ? POLL_MS : Math.min(POLL_MS, Math.max(0, deadline - performance.now())));
148
148
  }
149
149
  }
@@ -160,7 +160,7 @@ export class AkumaHandle {
160
160
  return await wakeRecordedTell(this.paths, admitted.tell.id, runtime);
161
161
  }
162
162
  async interrupt(body, options = {}) {
163
- const request = await requestPause(this.paths, new Date().toISOString());
163
+ const request = await requestPause(this.paths, new Date().toISOString(), options.signal);
164
164
  if (request.kind === "not-born") {
165
165
  throw new AkumaNotBornError(this.id);
166
166
  }
@@ -17,5 +17,7 @@ export declare function readBudgetedStatus(worldPath: WorldRoot, id: AkuId, inpu
17
17
  ordinaryBudget?: number;
18
18
  admittedTellId?: string;
19
19
  }>): Promise<BudgetedStatusObservation>;
20
+ export declare function defaultWaitComplete(status: AkumaStatus): boolean;
21
+ export declare function readWaitComplete(worldPath: WorldRoot, id: AkuId): Promise<boolean>;
20
22
  export declare function readAkumaBirthCwd(worldPath: WorldRoot, id: AkuId): Promise<string>;
21
23
  export { selectHistory, type ActivityHistory, type ActivitySnapshot };
@@ -1,12 +1,22 @@
1
- import { activitySlice, HeldAkumaLeash, isHeartAbsent, life, lifeAt, probeLeash, readHeart, readSeal, readSoul, } from "./heart/index.js";
1
+ import { readStatusFacts, readLifeSnapshot, HeldAkumaLeash, isHeartAbsent, life, lifeAt, probeLeash, readHeart, readSeal, readSoul, } from "./heart/index.js";
2
2
  import { pathsForAkuId } from "./identity.js";
3
3
  import { ordinarySnapshotBudget, projectTurns, selectHistory, selectSnapshot, } from "./projection.js";
4
4
  import { resolveProviderExecution } from "./providers/index.js";
5
5
  import { AkumaNotBornError } from "./akuma-errors.js";
6
6
  export async function fleetListRow(paths, expected) {
7
7
  const snapshot = await readHeart(paths);
8
- if (snapshot.soul !== null)
9
- return (await bornObservation(paths, expected, snapshot)).row;
8
+ if (snapshot.soul !== null) {
9
+ const observed = await bornObservation(paths, expected, () => readHeart(paths), snapshot);
10
+ return {
11
+ id: observed.soul.id,
12
+ archetype: observed.soul.archetype,
13
+ ...(observed.soul.description === undefined ? {} : { description: observed.soul.description }),
14
+ life: observed.currentLife,
15
+ lifeAt: lifeAt(observed.currentLife, observed.snapshot.latestBody, observed.snapshot.latestKill, observed.soul.createdAt),
16
+ lastActivityAt: observed.snapshot.lastActivityAt,
17
+ pending: observed.snapshot.pending.map((tell) => tell.id),
18
+ };
19
+ }
10
20
  try {
11
21
  if ((await probeLeash(paths)) === "held")
12
22
  return { id: expected, life: "unborn" };
@@ -19,8 +29,8 @@ export async function fleetListRow(paths, expected) {
19
29
  throw error;
20
30
  }
21
31
  }
22
- async function bornObservation(paths, expected, snapshot) {
23
- snapshot ??= await readHeart(paths);
32
+ async function bornObservation(paths, expected, read, snapshot) {
33
+ snapshot ??= await read();
24
34
  if (snapshot.soul === null)
25
35
  throw new AkumaNotBornError(expected);
26
36
  const claim = await HeldAkumaLeash.try(paths);
@@ -28,7 +38,7 @@ async function bornObservation(paths, expected, snapshot) {
28
38
  // A Body may finish after the first read. Refresh under the free seat so
29
39
  // neither its release nor a successor can manufacture an untidy observation.
30
40
  if (claim !== null)
31
- snapshot = await readHeart(paths);
41
+ snapshot = await read();
32
42
  const soul = snapshot.soul;
33
43
  if (soul === null)
34
44
  throw new AkumaNotBornError(expected);
@@ -39,19 +49,7 @@ async function bornObservation(paths, expected, snapshot) {
39
49
  body: snapshot.latestBody,
40
50
  kill: snapshot.latestKill,
41
51
  });
42
- return {
43
- snapshot,
44
- soul,
45
- row: {
46
- id: soul.id,
47
- archetype: soul.archetype,
48
- ...(soul.description === undefined ? {} : { description: soul.description }),
49
- life: currentLife,
50
- lifeAt: lifeAt(currentLife, snapshot.latestBody, snapshot.latestKill, soul.createdAt),
51
- lastActivityAt: snapshot.lastActivityAt,
52
- pending: snapshot.pending.map((tell) => tell.id),
53
- },
54
- };
52
+ return { snapshot, soul, currentLife };
55
53
  }
56
54
  finally {
57
55
  claim?.release();
@@ -60,20 +58,20 @@ async function bornObservation(paths, expected, snapshot) {
60
58
  export async function bornStatus(paths, expected, input) {
61
59
  if (input.ordinaryBudget !== undefined && (!Number.isSafeInteger(input.ordinaryBudget) || input.ordinaryBudget < 0))
62
60
  throw new TypeError("ordinary budget must be a nonnegative safe integer");
63
- const { snapshot, soul, row: current } = await bornObservation(paths, expected);
64
- const resumeUnsupported = current.life === "stranded" &&
61
+ const { snapshot, soul, currentLife } = await bornObservation(paths, expected, () => readLifeSnapshot(paths));
62
+ const resumeUnsupported = currentLife === "stranded" &&
65
63
  snapshot.latestSession?.provider === soul.provider.name &&
66
64
  (await resolveProviderExecution(soul.provider)).adapter.resume === undefined;
67
- const slice = await activitySlice(paths);
68
- const selected = selectSnapshot(projectTurns(slice.rows), {
65
+ const facts = await readStatusFacts(paths, input);
66
+ const selected = selectSnapshot(projectTurns(facts), {
69
67
  aperture: input.aperture,
70
68
  budget: ordinarySnapshotBudget(input.ordinaryBudget),
71
69
  ...(input.admittedTellId === undefined ? {} : { admittedTellId: input.admittedTellId }),
72
70
  });
73
71
  return {
74
72
  status: {
75
- id: current.id,
76
- life: current.life,
73
+ id: soul.id,
74
+ life: currentLife,
77
75
  ...(soul.readonly === undefined ? {} : { readonly: soul.readonly }),
78
76
  ...(resumeUnsupported ? { strandedReason: "resume-unsupported" } : {}),
79
77
  timeline: selected.snapshot,
@@ -84,6 +82,17 @@ export async function bornStatus(paths, expected, input) {
84
82
  export async function readBudgetedStatus(worldPath, id, input) {
85
83
  return await bornStatus(pathsForAkuId(worldPath, id), id, input);
86
84
  }
85
+ function complete(life, pending) {
86
+ return life !== "running" && !pending;
87
+ }
88
+ export function defaultWaitComplete(status) {
89
+ return complete(status.life, status.timeline.entries.some((entry) => entry.kind === "row" && entry.row.kind === "tell" && entry.row.state === "pending"));
90
+ }
91
+ export async function readWaitComplete(worldPath, id) {
92
+ const paths = pathsForAkuId(worldPath, id);
93
+ const observed = await bornObservation(paths, id, () => readLifeSnapshot(paths));
94
+ return complete(observed.currentLife, observed.snapshot.hasPendingTell);
95
+ }
87
96
  export async function readAkumaBirthCwd(worldPath, id) {
88
97
  const soul = await readSoul(pathsForAkuId(worldPath, id));
89
98
  if (soul === null)
@@ -408,8 +408,7 @@ export declare const akumaStatusSchema: z.ZodObject<{
408
408
  }, z.core.$strict>;
409
409
  export type AkumaStatus = z.infer<typeof akumaStatusSchema>;
410
410
  export declare function parseAkumaStatus(value: unknown): AkumaStatus;
411
- /** The default completion judgment over one complete status snapshot. */
412
- export declare function defaultWaitComplete(status: AkumaStatus): boolean;
411
+ export { defaultWaitComplete } from "./akuma-observe.js";
413
412
  export type { ReadonlyRestraint } from "./provider-recipe.js";
414
413
  export type * from "./projection.js";
415
414
  export type UnbornAkumaListRow = Readonly<{
@@ -33,11 +33,7 @@ export const akumaStatusSchema = z
33
33
  export function parseAkumaStatus(value) {
34
34
  return akumaStatusSchema.parse(value);
35
35
  }
36
- /** The default completion judgment over one complete status snapshot. */
37
- export function defaultWaitComplete(status) {
38
- return (status.life !== "running" &&
39
- !status.timeline.entries.some((entry) => entry.kind === "row" && entry.row.kind === "tell" && entry.row.state === "pending"));
40
- }
36
+ export { defaultWaitComplete } from "./akuma-observe.js";
41
37
  export { AkumaNotBornError } from "./akuma-errors.js";
42
38
  export function wait(milliseconds) {
43
39
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
3
3
  import { abortableDelay } from "./abort.js";
4
4
  import { BodySupervisor } from "./body-supervisor.js";
5
5
  import { driveTurn, turnRecipe } from "./turn-drive.js";
6
- import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, provePendingTellDispositionCustody, readHeart, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readNonterminalRequests, recordUndeliveredPendingTells, resolvePendingTellDisposition, } from "./heart/index.js";
6
+ import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, readHeart, readOpenBoundTurns, readOpenPendingTellDisposition, readTell, readTurn, readNonterminalRequests, resolvePendingTellDisposition, } from "./heart/index.js";
7
7
  import { worldRootForAkumaPaths } from "./identity.js";
8
8
  import { pluginRuntime } from "../plugin/runtime.js";
9
9
  import { World } from "../world.js";
@@ -369,15 +369,10 @@ async function settleUndeliveredDisposition(paths, disposition, error) {
369
369
  if (!(await heartExists(paths)))
370
370
  return;
371
371
  const at = new Date().toISOString();
372
- await recordUndeliveredPendingTells(paths, at, disposition.tellIds);
373
- await resolvePendingTellDisposition(paths, disposition.bodySequence, at);
372
+ await resolvePendingTellDisposition(paths, disposition.bodySequence, at, "undelivered");
374
373
  }
375
374
  async function consumeProvenDisposition(paths, disposition) {
376
- const proof = await provePendingTellDispositionCustody(paths, disposition);
377
- if (proof.kind !== "proven")
378
- return false;
379
- await resolvePendingTellDisposition(paths, disposition.bodySequence, new Date().toISOString());
380
- return true;
375
+ return await resolvePendingTellDisposition(paths, disposition.bodySequence, new Date().toISOString());
381
376
  }
382
377
  /**
383
378
  * Wait for Heart custody proof of the frozen Tell-id snapshot, or for the
@@ -386,7 +381,7 @@ async function consumeProvenDisposition(paths, disposition) {
386
381
  */
387
382
  async function awaitDispositionCustody(paths, disposition, child, schedule) {
388
383
  for (;;) {
389
- if ((await provePendingTellDispositionCustody(paths, disposition)).kind === "proven") {
384
+ if (await consumeProvenDisposition(paths, disposition)) {
390
385
  child.release();
391
386
  return { kind: "proven" };
392
387
  }
@@ -405,7 +400,7 @@ async function awaitDispositionCustody(paths, disposition, child, schedule) {
405
400
  continue;
406
401
  timerController.abort();
407
402
  await timer.catch(() => undefined);
408
- if ((await provePendingTellDispositionCustody(paths, disposition)).kind === "proven") {
403
+ if (await consumeProvenDisposition(paths, disposition)) {
409
404
  child.release();
410
405
  return { kind: "proven" };
411
406
  }
@@ -417,10 +412,8 @@ async function resolveDecidedPendingTellDisposition(paths, disposition, spawn) {
417
412
  if (await consumeProvenDisposition(paths, disposition))
418
413
  return;
419
414
  const wake = await awaitDispositionCustody(paths, disposition, await spawn({ paths, refuseIfHeld: true }), abortableDelay);
420
- if (wake.kind === "proven") {
421
- await resolvePendingTellDisposition(paths, disposition.bodySequence, new Date().toISOString());
415
+ if (wake.kind === "proven")
422
416
  return;
423
- }
424
417
  // Predecessor still holds the leash: leave the Heart disposition open.
425
418
  if (wake.kind === "held")
426
419
  return;
@@ -1,6 +1,6 @@
1
1
  import { AkumaNotBornError, defaultWaitComplete } from "./akuma.js";
2
2
  import { createAkumaProduct } from "./akuma-product.js";
3
- import { readBudgetedStatus } from "./akuma.js";
3
+ import { readBudgetedStatus, readWaitComplete } from "./akuma-observe.js";
4
4
  import { NO_DISPATCH_ASSOCIATION } from "./dispatch-association.js";
5
5
  import { EMPTY_CREATED_TASK_OBSERVATION } from "../task/created-observation.js";
6
6
  import { fleetResultSchemas, parseAkumaObservation, } from "./fleet-observation.js";
@@ -45,6 +45,29 @@ async function observeWaitRound(path, ids, signal) {
45
45
  }
46
46
  return { statuses, unobserved };
47
47
  }
48
+ async function probeWaitRound(input) {
49
+ let observed = 0;
50
+ let complete = 0;
51
+ for (const id of input.ids) {
52
+ input.signal?.throwIfAborted();
53
+ try {
54
+ if (await readWaitComplete(input.path, id))
55
+ complete += 1;
56
+ observed += 1;
57
+ }
58
+ catch (error) {
59
+ if (input.ids.length <= 1 || error instanceof AkumaNotBornError)
60
+ throw error;
61
+ // Plural wait retries unreadable members; final rendering owns diagnostics.
62
+ }
63
+ }
64
+ return observed > 0 && (input.completion === "any" ? complete > 0 : complete === input.ids.length);
65
+ }
66
+ function roundComplete(round, completion) {
67
+ const settled = round.statuses.map(defaultWaitComplete);
68
+ return (settled.length > 0 &&
69
+ (completion === "any" ? settled.some(Boolean) : round.unobserved.length === 0 && settled.every(Boolean)));
70
+ }
48
71
  function delay(milliseconds, signal) {
49
72
  return new Promise((resolve, reject) => {
50
73
  let timer;
@@ -67,16 +90,17 @@ function delay(milliseconds, signal) {
67
90
  export async function executeWaitAkuma(input) {
68
91
  const deadline = input.timeoutMs === undefined ? undefined : performance.now() + input.timeoutMs;
69
92
  for (;;) {
70
- const round = await observeWaitRound(input.path, input.ids, input.signal);
71
- const settled = round.statuses.map(defaultWaitComplete);
72
- const completed = round.statuses.length > 0 &&
73
- (input.completion === "any" ? settled.some(Boolean) : round.unobserved.length === 0 && settled.every(Boolean));
74
- if (completed || (deadline !== undefined && performance.now() >= deadline)) {
75
- return fleetResultSchemas.wait.parse({
76
- completion: input.completion,
77
- observations: round.statuses.map(akumaOnlyObservation),
78
- unobserved: round.unobserved,
79
- });
93
+ if ((deadline !== undefined && performance.now() >= deadline) || (await probeWaitRound(input))) {
94
+ const round = await observeWaitRound(input.path, input.ids, input.signal);
95
+ input.signal?.throwIfAborted();
96
+ // The probe is not a completion receipt. Judge the actual returned values.
97
+ if (roundComplete(round, input.completion) || (deadline !== undefined && performance.now() >= deadline)) {
98
+ return fleetResultSchemas.wait.parse({
99
+ completion: input.completion,
100
+ observations: round.statuses.map(akumaOnlyObservation),
101
+ unobserved: round.unobserved,
102
+ });
103
+ }
80
104
  }
81
105
  await delay(deadline === undefined ? POLL_MS : Math.min(POLL_MS, Math.max(0, deadline - performance.now())), input.signal);
82
106
  }
@@ -2,7 +2,7 @@ import type { AkumaPaths } from "../identity.js";
2
2
  import type { BodyEnd, BodyFact, ForkPoint, HeartSnapshot, KillFact, SealFact, SessionFact, Soul, TellDeliveryInput, TellFact, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact } from "./facts.js";
3
3
  export type { CallFact } from "./facts.js";
4
4
  import type { ActivityFact } from "./rows.js";
5
- import { type ActivityFactSlice } from "./timeline.js";
5
+ import { type StatusFactInput, type ActivityFactSlice } from "./timeline.js";
6
6
  export { HeartAbsentError, HeldAkumaLeash, classifyHeartSchema, initializeHeart, isHeartAbsent, probeLeash, } from "./storage.js";
7
7
  export { admitRequest, beginRequest, isRequestInputConflict, readNonterminalRequests, readRequest, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, unproveRequest, voidRequest, } from "./request-authority.js";
8
8
  export { AkumaBusyError, life, lifeAt, projectTell } from "./facts.js";
@@ -22,6 +22,12 @@ export type ActivitySlice = ActivityFactSlice;
22
22
  export type { ActivityFact };
23
23
  export type { TimelineFact } from "./timeline.js";
24
24
  export declare function activitySlice(paths: AkumaPaths): Promise<ActivitySlice>;
25
+ export declare function readStatusFacts(paths: AkumaPaths, input: StatusFactInput): Promise<ActivitySlice["rows"]>;
26
+ type LifeSnapshot = Pick<HeartSnapshot, "soul" | "latestBody" | "latestSession" | "latestKill"> & Readonly<{
27
+ hasPendingTell: boolean;
28
+ }>;
29
+ /** Lifecycle probes do not load narration, Tell bodies, or delivery histories. */
30
+ export declare function readLifeSnapshot(paths: AkumaPaths): Promise<LifeSnapshot>;
25
31
  export declare function recordTell(paths: AkumaPaths, tell: Omit<TellFact, "sequence" | "state" | "deliveries" | "binding">, options?: Readonly<{
26
32
  interrupt?: boolean;
27
33
  }>): Promise<Readonly<{
@@ -43,29 +49,17 @@ export declare function decidePendingTellDisposition(paths: AkumaPaths, input: R
43
49
  handoff: boolean;
44
50
  }>): Promise<PendingTellDisposition | null>;
45
51
  export declare function readOpenPendingTellDisposition(paths: AkumaPaths): Promise<PendingTellDisposition | null>;
46
- export declare function resolvePendingTellDisposition(paths: AkumaPaths, bodySequence: number, at: string): Promise<void>;
47
- /**
48
- * Heart-owned disposition custody proof. Sequence growth, spawn resolution, and
49
- * an unqualified held leash are never proof. Returns the successor Body sequence
50
- * when that exact Body took the frozen Tell-id snapshot by delivery, or true when
51
- * the snapshot is already fully settled (no longer pending).
52
- */
53
- export declare function provePendingTellDispositionCustody(paths: AkumaPaths, disposition: PendingTellDisposition): Promise<Readonly<{
54
- kind: "proven";
55
- successorBodySequence?: number;
56
- } | {
57
- kind: "unproven";
58
- }>>;
59
- export declare function recordUndeliveredPendingTells(paths: AkumaPaths, at: string, tellIds: readonly string[]): Promise<void>;
52
+ /** Prove and consume the persisted decision in one transaction, never a caller's stale member list. */
53
+ export declare function resolvePendingTellDisposition(paths: AkumaPaths, bodySequence: number, at: string, outcome?: "custody" | "undelivered"): Promise<boolean>;
60
54
  export declare function readKill(paths: AkumaPaths, bodySequence: number): Promise<KillFact | null>;
61
- export declare function requestStop(paths: AkumaPaths, at: string): Promise<Readonly<{
55
+ export declare function requestStop(paths: AkumaPaths, at: string, signal?: AbortSignal): Promise<Readonly<{
62
56
  kind: "requested";
63
57
  body: BodyFact;
64
58
  }> | Readonly<{
65
59
  kind: "already-killed" | "already-stopped";
66
60
  body: BodyFact;
67
61
  }>>;
68
- export declare function requestPause(paths: AkumaPaths, at: string): Promise<Readonly<{
62
+ export declare function requestPause(paths: AkumaPaths, at: string, signal?: AbortSignal): Promise<Readonly<{
69
63
  kind: "not-born";
70
64
  } | {
71
65
  kind: "requested";