@astrosheep/keiyaku 4.4.0 → 4.5.1

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.
Files changed (68) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +4 -2
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +9 -3
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +16 -5
  5. package/build/integrations/pi/keiyaku.ts +159 -0
  6. package/build/src/akuma/abort.js +9 -9
  7. package/build/src/akuma/akuma-handle.d.ts +4 -2
  8. package/build/src/akuma/akuma-handle.js +14 -2
  9. package/build/src/akuma/akuma-product.d.ts +32 -3
  10. package/build/src/akuma/akuma-product.js +44 -20
  11. package/build/src/akuma/akuma.d.ts +0 -1
  12. package/build/src/akuma/body.d.ts +1 -7
  13. package/build/src/akuma/body.js +35 -13
  14. package/build/src/akuma/heart/index.js +7 -3
  15. package/build/src/akuma/heart/rows.d.ts +1 -1
  16. package/build/src/akuma/heart/rows.js +3 -3
  17. package/build/src/akuma/heart/storage.d.ts +1 -0
  18. package/build/src/akuma/heart/storage.js +28 -6
  19. package/build/src/akuma/identity.d.ts +1 -0
  20. package/build/src/akuma/identity.js +7 -0
  21. package/build/src/akuma/index.d.ts +1 -2
  22. package/build/src/akuma/projection.d.ts +18 -2
  23. package/build/src/akuma/projection.js +11 -1
  24. package/build/src/akuma/providers/claude/index.js +68 -37
  25. package/build/src/akuma/publication.d.ts +9 -3
  26. package/build/src/akuma/publication.js +20 -3
  27. package/build/src/akuma/request-lifecycle.d.ts +45 -0
  28. package/build/src/akuma/request-lifecycle.js +171 -0
  29. package/build/src/akuma/request-serve.d.ts +9 -32
  30. package/build/src/akuma/request-serve.js +48 -178
  31. package/build/src/akuma/request-wire.js +1 -1
  32. package/build/src/akuma/requests.js +3 -2
  33. package/build/src/cli/commands/akuma-invoke.d.ts +11 -2
  34. package/build/src/cli/commands/akuma-invoke.js +29 -7
  35. package/build/src/cli/commands/akuma.d.ts +1 -0
  36. package/build/src/cli/commands/akuma.js +16 -6
  37. package/build/src/cli/commands/contract.d.ts +3 -2
  38. package/build/src/cli/commands/contract.js +5 -2
  39. package/build/src/cli/commands/status-set.d.ts +4 -0
  40. package/build/src/cli/commands/status-set.js +61 -0
  41. package/build/src/cli/invoke.js +6 -3
  42. package/build/src/cli/parse.js +1 -1
  43. package/build/src/cli/render/akuma-activity.js +11 -0
  44. package/build/src/cli/render/akuma.js +7 -0
  45. package/build/src/cli/render/status-set.d.ts +5 -0
  46. package/build/src/cli/render/status-set.js +14 -0
  47. package/build/src/cli/render/text.js +3 -0
  48. package/build/src/cli/result.d.ts +15 -1
  49. package/build/src/cli/runtime.js +2 -0
  50. package/build/src/cli/square-edge.d.ts +6 -0
  51. package/build/src/cli/square-edge.js +74 -0
  52. package/build/src/contract-worktree.js +18 -5
  53. package/build/src/library/akuma-creation.d.ts +14 -3
  54. package/build/src/library/akuma-creation.js +31 -18
  55. package/build/src/library/fleet.d.ts +12 -1
  56. package/build/src/library/fleet.js +21 -5
  57. package/build/src/library/repo.js +3 -1
  58. package/build/src/runtime/proc/process-exit.js +6 -1
  59. package/build/src/runtime/proc/run.js +6 -10
  60. package/build/src/runtime/proc/termination.d.ts +1 -0
  61. package/build/src/runtime/proc/termination.js +22 -2
  62. package/build/src/runtime/proc/windows-launch.exe +0 -0
  63. package/build/src/runtime/proc/windows-run.js +10 -7
  64. package/build/src/world.d.ts +2 -0
  65. package/build/src/world.js +4 -0
  66. package/package.json +8 -3
  67. package/build/src/akuma/result-route.d.ts +0 -2
  68. package/build/src/akuma/result-route.js +0 -1
@@ -1,13 +1,10 @@
1
- import { type KillEvidence, type RequestFact, type Soul } from "./heart/index.js";
2
- import { type AkuId, type AkumaPaths } from "./identity.js";
1
+ import { type KillEvidence, type RequestFact } from "./heart/index.js";
2
+ import { type AkuId } from "./identity.js";
3
3
  import { type StructuralRequestClaim } from "./request-wire.js";
4
4
  import { type TaskMutationRequest } from "../task/mutation.js";
5
- import type { OwnedProcess } from "../runtime/proc/run.js";
6
- export type RequestChildLaunch = Readonly<{
7
- paths: AkumaPaths;
8
- seed: Omit<Soul, "createdAt">;
9
- initialBody: string;
10
- }>;
5
+ import { BodyRequestPump as LifecycleBodyRequestPump, type RequestChildLaunch as LifecycleRequestChildLaunch, type PumpInput as LifecyclePumpInput } from "./request-lifecycle.js";
6
+ export { clearBodyRequestTransport, settleBodyRequests } from "./request-lifecycle.js";
7
+ export type RequestChildLaunch = LifecycleRequestChildLaunch;
11
8
  type UpstreamCall = Readonly<{
12
9
  signal: AbortSignal;
13
10
  }>;
@@ -60,17 +57,12 @@ export type UpstreamExecutionPort = Readonly<{
60
57
  request: TaskMutationRequest;
61
58
  }>): Promise<unknown>;
62
59
  }>;
63
- type PumpInput = Readonly<{
64
- paths: AkumaPaths;
65
- parent: Soul;
66
- bodySequence: number;
67
- now(): string;
68
- spawn(launch: RequestChildLaunch): Promise<OwnedProcess | void>;
60
+ type PumpInput = Omit<LifecyclePumpInput, "upstream"> & Readonly<{
69
61
  upstream?: UpstreamExecutionPort;
70
- signal: AbortSignal;
71
62
  }>;
72
63
  export type ServeInput = Omit<PumpInput, "bodySequence"> & Readonly<{
73
64
  directory: string;
65
+ transportId: string;
74
66
  claim: StructuralRequestClaim;
75
67
  admissionOpen(): boolean;
76
68
  }>;
@@ -79,22 +71,7 @@ export type UpstreamFact = Exclude<Extract<RequestFact, {
79
71
  }>, Extract<RequestFact, {
80
72
  action: "akuma.call";
81
73
  }>>;
82
- export declare class BodyRequestPump {
83
- private readonly input;
84
- readonly directory: string;
85
- readonly failure: Promise<never>;
86
- private readonly closeSignal;
87
- private readonly executionSignal;
88
- private readonly handled;
89
- private readonly running;
90
- private readonly cancelBody;
91
- private admissionClosed;
92
- private constructor();
74
+ export declare function serveRequest(input: ServeInput): Promise<void>;
75
+ export declare class BodyRequestPump extends LifecycleBodyRequestPump {
93
76
  static open(input: PumpInput): Promise<BodyRequestPump>;
94
- private run;
95
- close(): Promise<void>;
96
- stopAdmission(): void;
97
77
  }
98
- export declare function clearBodyRequestTransport(paths: AkumaPaths): Promise<void>;
99
- export declare function settleBodyRequests(paths: AkumaPaths, parent: Soul, now: () => string, signal?: AbortSignal): Promise<"settled" | "pending">;
100
- export {};
@@ -1,19 +1,26 @@
1
- import { access, mkdir, readFile, readdir, rm } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { abortableDelay } from "./abort.js";
4
- import { HeldAkumaLeash, admitRequest, readNonterminalRequests, readRequest, readSoul, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, voidRequest, } from "./heart/index.js";
5
- import { pathsForAkuId, worldRootForAkumaPaths } from "./identity.js";
6
- import { BIRTH_TIMEOUT_MS, publishAkuma } from "./publication.js";
7
- import { atomicJson, canonicalAkuId, decodeClaim, decodeRecipe, receiptPath, } from "./request-wire.js";
1
+ import { admitRequest, readRequest, refuseRequest, reserveRequest, serveRequest as serveChildRequest, serveUpstreamRequest, voidRequest, } from "./heart/index.js";
2
+ import { worldRootForAkumaPaths } from "./identity.js";
3
+ import { publishAkuma } from "./publication.js";
4
+ import { atomicJson, canonicalAkuId, decodeRecipe, receiptPath, } from "./request-wire.js";
8
5
  import { isTaskMutationAction } from "../task/mutation.js";
9
6
  import { executeRequest } from "./request-execution.js";
10
- const POLL_MS = 100;
7
+ import { BodyRequestPump as LifecycleBodyRequestPump, } from "./request-lifecycle.js";
8
+ export { clearBodyRequestTransport, settleBodyRequests } from "./request-lifecycle.js";
11
9
  function diagnostic(error) {
12
10
  return error instanceof Error ? error.message : String(error);
13
11
  }
14
12
  function isTask(value) {
15
13
  return isTaskMutationAction(value.action);
16
14
  }
15
+ function refusalRecipe(value) {
16
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
17
+ const recipe = value;
18
+ if (Array.isArray(recipe.allowed))
19
+ return recipe;
20
+ return { ...recipe, allowed: [] };
21
+ }
22
+ return { allowed: [] };
23
+ }
17
24
  function acceptedReceipt(fact, service) {
18
25
  return {
19
26
  id: fact.id,
@@ -62,7 +69,7 @@ function receiptFor(fact) {
62
69
  }
63
70
  return null;
64
71
  }
65
- async function projectReceipt(directory, fact, outcome) {
72
+ async function projectReceipt(directory, transportId, fact, outcome) {
66
73
  const receipt = outcome === undefined
67
74
  ? receiptFor(fact)
68
75
  : fact.action === "akuma.call"
@@ -71,29 +78,45 @@ async function projectReceipt(directory, fact, outcome) {
71
78
  if (receipt === null)
72
79
  return;
73
80
  try {
74
- await atomicJson(receiptPath(directory, fact.id), receipt);
81
+ await atomicJson(receiptPath(directory, transportId), receipt);
75
82
  }
76
83
  catch (error) {
77
84
  if (error.code !== "ENOENT")
78
85
  throw error;
79
86
  }
80
87
  }
88
+ async function refuseInvalidCall(input) {
89
+ let fact = await admitRequest(input.paths, {
90
+ ...input.claim,
91
+ recipe: refusalRecipe(input.claim.recipe),
92
+ admittedAt: input.now(),
93
+ });
94
+ const existing = receiptFor(fact);
95
+ if (existing !== null) {
96
+ await projectReceipt(input.directory, input.transportId, fact);
97
+ return;
98
+ }
99
+ fact = await refuseRequest(input.paths, input.claim.id, "invalid akuma.call recipe");
100
+ await projectReceipt(input.directory, input.transportId, fact);
101
+ }
81
102
  async function serveCall(input) {
82
103
  input.signal.throwIfAborted();
83
104
  if (!input.admissionOpen())
84
105
  return;
85
106
  const recipe = decodeRecipe(input.claim.recipe);
86
- if (recipe === null)
107
+ if (recipe === null) {
108
+ await refuseInvalidCall(input);
87
109
  return;
110
+ }
88
111
  let fact = await admitRequest(input.paths, { ...input.claim, recipe, admittedAt: input.now() });
89
112
  const existing = receiptFor(fact);
90
113
  if (existing !== null) {
91
- await projectReceipt(input.directory, fact);
114
+ await projectReceipt(input.directory, input.transportId, fact);
92
115
  return;
93
116
  }
94
117
  if (!input.admissionOpen()) {
95
118
  fact = await voidRequest(input.paths, input.claim.id, "body closed request admission");
96
- await projectReceipt(input.directory, fact);
119
+ await projectReceipt(input.directory, input.transportId, fact);
97
120
  return;
98
121
  }
99
122
  if (fact.action !== "akuma.call" || fact.state !== "admitted") {
@@ -103,7 +126,7 @@ async function serveCall(input) {
103
126
  const world = worldRootForAkumaPaths(input.paths);
104
127
  if (request.world !== world) {
105
128
  fact = await refuseRequest(input.paths, request.id, `request world ${request.world} does not match ${world}`);
106
- await projectReceipt(input.directory, fact);
129
+ await projectReceipt(input.directory, input.transportId, fact);
107
130
  return;
108
131
  }
109
132
  try {
@@ -111,14 +134,10 @@ async function serveCall(input) {
111
134
  worldPath: world,
112
135
  archetype: request.archetype,
113
136
  signal: input.signal,
114
- reserve: async (allocated) => {
115
- if (!input.admissionOpen())
116
- throw new Error("body closed request admission");
117
- fact = await reserveRequest(input.paths, request.id, allocated.id);
118
- },
119
137
  launch: async (allocated) => {
120
138
  if (!input.admissionOpen())
121
139
  throw new Error("body closed request admission");
140
+ fact = await reserveRequest(input.paths, request.id, allocated.id);
122
141
  return await input.spawn({
123
142
  paths: allocated.paths,
124
143
  seed: {
@@ -140,7 +159,7 @@ async function serveCall(input) {
140
159
  });
141
160
  },
142
161
  });
143
- fact = await serveRequest(input.paths, request.id, published.id);
162
+ fact = await serveChildRequest(input.paths, request.id, published.id);
144
163
  }
145
164
  catch (error) {
146
165
  const current = await readRequest(input.paths, request.id);
@@ -151,7 +170,7 @@ async function serveCall(input) {
151
170
  ? await voidRequest(input.paths, request.id, diagnostic(error))
152
171
  : current;
153
172
  }
154
- await projectReceipt(input.directory, fact);
173
+ await projectReceipt(input.directory, input.transportId, fact);
155
174
  }
156
175
  function failure(error) {
157
176
  const value = error !== null && typeof error === "object" ? error : null;
@@ -178,11 +197,11 @@ async function serveUpstream(input) {
178
197
  });
179
198
  const existing = receiptFor(fact);
180
199
  if (existing !== null) {
181
- await projectReceipt(input.directory, fact);
200
+ await projectReceipt(input.directory, input.transportId, fact);
182
201
  return;
183
202
  }
184
203
  if (fact.state === "served") {
185
- await projectReceipt(input.directory, fact, {
204
+ await projectReceipt(input.directory, input.transportId, fact, {
186
205
  kind: "failed",
187
206
  failure: {
188
207
  kind: "failed",
@@ -193,7 +212,7 @@ async function serveUpstream(input) {
193
212
  }
194
213
  if (!input.admissionOpen()) {
195
214
  fact = await voidRequest(input.paths, input.claim.id, "body closed request admission");
196
- await projectReceipt(input.directory, fact);
215
+ await projectReceipt(input.directory, input.transportId, fact);
197
216
  return;
198
217
  }
199
218
  if (fact.state !== "admitted" || fact.action === "akuma.call") {
@@ -211,171 +230,22 @@ async function serveUpstream(input) {
211
230
  catch (error) {
212
231
  outcome = { kind: "failed", failure: failure(error) };
213
232
  fact = await voidRequest(input.paths, request.id, "body failed before serving the request");
214
- await projectReceipt(input.directory, fact);
233
+ await projectReceipt(input.directory, input.transportId, fact);
215
234
  return;
216
235
  }
217
236
  fact =
218
237
  service === undefined
219
238
  ? await voidRequest(input.paths, request.id, "body completed without a durable Contract fact reference")
220
239
  : await serveUpstreamRequest(input.paths, request.id, service);
221
- await projectReceipt(input.directory, fact, outcome);
240
+ await projectReceipt(input.directory, input.transportId, fact, outcome);
222
241
  }
223
- async function serve(input) {
242
+ export async function serveRequest(input) {
224
243
  return input.claim.action === "akuma.call"
225
244
  ? await serveCall({ ...input, claim: input.claim })
226
245
  : await serveUpstream({ ...input, claim: input.claim });
227
246
  }
228
- async function requestFiles(directory) {
229
- try {
230
- return (await readdir(directory)).filter((name) => name.endsWith(".request.json")).sort();
231
- }
232
- catch (error) {
233
- if (error.code === "ENOENT")
234
- return [];
235
- throw error;
236
- }
237
- }
238
- export class BodyRequestPump {
239
- input;
240
- directory;
241
- failure;
242
- closeSignal = new AbortController();
243
- executionSignal = new AbortController();
244
- handled = new Set();
245
- running;
246
- cancelBody;
247
- admissionClosed = false;
248
- constructor(input) {
249
- this.input = input;
250
- this.directory = join(input.paths.directory, "requests", String(input.bodySequence));
251
- this.cancelBody = () => {
252
- this.stopAdmission();
253
- if (!this.executionSignal.signal.aborted) {
254
- this.executionSignal.abort(input.signal.reason);
255
- }
256
- };
257
- input.signal.addEventListener("abort", this.cancelBody, { once: true });
258
- if (input.signal.aborted)
259
- this.cancelBody();
260
- this.running = this.run();
261
- this.failure = this.running.then(() => new Promise(() => { }), (error) => Promise.reject(error));
262
- }
247
+ export class BodyRequestPump extends LifecycleBodyRequestPump {
263
248
  static async open(input) {
264
- await mkdir(join(input.paths.directory, "requests", String(input.bodySequence)), { recursive: true });
265
- return new BodyRequestPump(input);
266
- }
267
- async run() {
268
- while (!this.closeSignal.signal.aborted) {
269
- for (const name of await requestFiles(this.directory)) {
270
- if (this.closeSignal.signal.aborted)
271
- return;
272
- const id = name.slice(0, -".request.json".length);
273
- if (this.handled.has(id))
274
- continue;
275
- const path = join(this.directory, name);
276
- const claim = decodeClaim(await readFile(path, "utf8"), id);
277
- if (claim === null) {
278
- await rm(path, { force: true });
279
- this.handled.add(id);
280
- continue;
281
- }
282
- if (!this.admissionClosed) {
283
- await serve({
284
- directory: this.directory,
285
- claim,
286
- ...this.input,
287
- signal: this.executionSignal.signal,
288
- admissionOpen: () => !this.admissionClosed,
289
- });
290
- }
291
- this.handled.add(id);
292
- }
293
- try {
294
- await abortableDelay(POLL_MS, this.closeSignal.signal);
295
- }
296
- catch (error) {
297
- if (this.closeSignal.signal.aborted)
298
- return;
299
- throw error;
300
- }
301
- }
302
- }
303
- async close() {
304
- this.stopAdmission();
305
- try {
306
- await this.running;
307
- }
308
- finally {
309
- this.input.signal.removeEventListener("abort", this.cancelBody);
310
- await rm(this.directory, { recursive: true, force: true });
311
- }
312
- }
313
- stopAdmission() {
314
- this.admissionClosed = true;
315
- if (!this.closeSignal.signal.aborted) {
316
- this.closeSignal.abort(new Error("Body request pump closed"));
317
- }
318
- }
319
- }
320
- export async function clearBodyRequestTransport(paths) {
321
- await rm(join(paths.directory, "requests"), { recursive: true, force: true });
322
- }
323
- function matchingRequestOrigin(soul, parent, id) {
324
- return soul.origin.kind === "request" && soul.origin.parent === parent && soul.origin.requestId === id;
325
- }
326
- async function settleReservedSoul(paths, parent, request, soul) {
327
- await (matchingRequestOrigin(soul, parent.id, request.id)
328
- ? serveRequest(paths, request.id, request.child)
329
- : voidRequest(paths, request.id, "reserved child origin does not match the request"));
330
- }
331
- async function settleReserved(paths, parent, request, now, signal) {
332
- const childPaths = pathsForAkuId(request.world, request.child);
333
- const deadline = performance.now() + BIRTH_TIMEOUT_MS;
334
- for (;;) {
335
- signal?.throwIfAborted();
336
- if (!(await access(childPaths.directory).then(() => true, () => false))) {
337
- await voidRequest(paths, request.id, "reserved child directory is absent");
338
- return true;
339
- }
340
- const soul = await readSoul(childPaths);
341
- if (soul !== null) {
342
- await settleReservedSoul(paths, parent, request, soul);
343
- return true;
344
- }
345
- const leash = await HeldAkumaLeash.try(childPaths);
346
- if (leash !== null) {
347
- try {
348
- const settled = await readSoul(childPaths);
349
- if (settled !== null) {
350
- await settleReservedSoul(paths, parent, request, settled);
351
- }
352
- else {
353
- await leash.sealIfUnborn(childPaths, {
354
- evidence: "request settlement",
355
- at: now(),
356
- });
357
- await voidRequest(paths, request.id, "reserved child was sealed unborn");
358
- }
359
- }
360
- finally {
361
- leash.release();
362
- }
363
- return true;
364
- }
365
- if (performance.now() >= deadline)
366
- return false;
367
- await abortableDelay(Math.min(POLL_MS, Math.max(0, deadline - performance.now())), signal);
368
- }
369
- }
370
- export async function settleBodyRequests(paths, parent, now, signal) {
371
- let pending = false;
372
- for (const request of await readNonterminalRequests(paths)) {
373
- signal?.throwIfAborted();
374
- if (request.state === "admitted") {
375
- await voidRequest(paths, request.id, "body died before serving the request");
376
- }
377
- else if (request.state === "reserved" && !(await settleReserved(paths, parent, request, now, signal)))
378
- pending = true;
249
+ return (await LifecycleBodyRequestPump.openWithService(input, async (claim) => await serveRequest(claim)));
379
250
  }
380
- return pending ? "pending" : "settled";
381
251
  }
@@ -210,7 +210,7 @@ export function decodeClaim(bytes, fileId) {
210
210
  const value = object(decoded);
211
211
  if (value === null ||
212
212
  !exactKeys(value, ["action", "id", "payload"]) ||
213
- value.id !== fileId ||
213
+ !UUID.test(fileId) ||
214
214
  typeof value.id !== "string" ||
215
215
  !UUID.test(value.id))
216
216
  return null;
@@ -26,12 +26,13 @@ export function injectedBodyRequests() {
26
26
  }
27
27
  async function requestBody(input) {
28
28
  input.signal?.throwIfAborted();
29
- await atomicJson(requestPath(input.directory, input.claim.id), {
29
+ const transportId = randomUUID();
30
+ await atomicJson(requestPath(input.directory, transportId), {
30
31
  id: input.claim.id,
31
32
  action: input.claim.action,
32
33
  payload: requestPayload(input.claim),
33
34
  });
34
- const path = receiptPath(input.directory, input.claim.id);
35
+ const path = receiptPath(input.directory, transportId);
35
36
  for (;;) {
36
37
  try {
37
38
  const receipt = decodeReceipt(await readFile(path, "utf8"), input.claim.id, input.claim.action);
@@ -1,8 +1,9 @@
1
- import { type AkuId, type ActivityHistory, type ResultRoute } from "../../akuma/index.js";
1
+ import { type AkuId, type ActivityHistory } from "../../akuma/index.js";
2
2
  import { Keiyaku, type AkumaKillResult, type AkumaHistoryResult, type AkumaObservation, type AkumaTellResult, type AkumaWaitResult, type CallResult, type ForkResult, type Keiyaku as KeiyakuContract, type Repo } from "../../index.js";
3
3
  import type { Settings } from "../../settings.js";
4
4
  import type { WorldRoot } from "../../world.js";
5
5
  import type { InvokedAkumaCommand } from "./akuma.js";
6
+ import { finishCall } from "../../library/akuma-creation.js";
6
7
  export type AkumaInvocationResult = Readonly<{
7
8
  kind: "akuma";
8
9
  action: "call";
@@ -55,6 +56,13 @@ export type AkumaInvocationResult = Readonly<{
55
56
  mode: "no-answer";
56
57
  historyResult: AkumaHistoryResult;
57
58
  alias?: string;
59
+ }> | Readonly<{
60
+ kind: "akuma";
61
+ action: "history";
62
+ akuma: AkuId;
63
+ mode: "exact";
64
+ historyResult: AkumaHistoryResult;
65
+ alias?: string;
58
66
  }> | Readonly<{
59
67
  kind: "akuma";
60
68
  action: "fork";
@@ -72,8 +80,9 @@ type InvokeInput = Readonly<{
72
80
  settings?: Settings;
73
81
  contract?: KeiyakuContract;
74
82
  repo?: Repo;
75
- resultRoute?: ResultRoute;
83
+ environment: NodeJS.ProcessEnv;
76
84
  readStdin(): Promise<string>;
85
+ finishCall?: typeof finishCall;
77
86
  }>;
78
87
  export declare function invokeAkuma(command: InvokedAkumaCommand, input: InvokeInput): Promise<AkumaInvocationResult>;
79
88
  export declare function invokeAkumaStatus(path: WorldRoot, akuma: string, alias?: string, repo?: Repo): Promise<AkumaInvocationResult>;
@@ -1,4 +1,6 @@
1
1
  import { Keiyaku, } from "../../index.js";
2
+ import { beginCall, finishCall } from "../../library/akuma-creation.js";
3
+ import { recognizeAndListen } from "../square-edge.js";
2
4
  function inputAlias(selector) {
3
5
  return selector.startsWith("@") ? selector : undefined;
4
6
  }
@@ -56,6 +58,7 @@ async function invokeHistory(command, input) {
56
58
  ...(command.before === undefined ? {} : { before: command.before }),
57
59
  ...(command.since === undefined ? {} : { since: command.since }),
58
60
  ...(command.limit === undefined ? {} : { limit: command.limit }),
61
+ ...(command.id === undefined ? {} : { id: command.id }),
59
62
  last: command.last,
60
63
  });
61
64
  return {
@@ -64,11 +67,13 @@ async function invokeHistory(command, input) {
64
67
  akuma: result.id,
65
68
  historyResult: result,
66
69
  ...(inputAlias(command.akuma) === undefined ? {} : { alias: command.akuma }),
67
- ...(result.kind === "history"
68
- ? { mode: "page", history: result.history }
69
- : result.kind === "last"
70
- ? { mode: "last", answer: result.answer }
71
- : { mode: "no-answer" }),
70
+ ...(command.id !== undefined
71
+ ? { mode: "exact" }
72
+ : result.kind === "history"
73
+ ? { mode: "page", history: result.history }
74
+ : result.kind === "last"
75
+ ? { mode: "last", answer: result.answer }
76
+ : { mode: "no-answer" }),
72
77
  };
73
78
  }
74
79
  async function invokeFork(command, input) {
@@ -100,7 +105,7 @@ export async function invokeAkuma(command, input) {
100
105
  switch (command.command) {
101
106
  case "call": {
102
107
  const body = await promptBody(command, input);
103
- const result = await Keiyaku.call({
108
+ const born = await beginCall({
104
109
  path: input.path,
105
110
  archetype: command.archetype,
106
111
  body,
@@ -113,8 +118,25 @@ export async function invokeAkuma(command, input) {
113
118
  ...(input.contract === undefined ? {} : { contract: input.contract }),
114
119
  ...(command.alias === undefined ? {} : { alias: command.alias }),
115
120
  ...(command.allowed === undefined ? {} : { allowed: command.allowed }),
116
- ...(input.resultRoute === undefined ? {} : { resultRoute: input.resultRoute }),
117
121
  });
122
+ const listener = born.born.kind === "born"
123
+ ? await recognizeAndListen(input.path, input.environment, born.born.allocated)
124
+ : undefined;
125
+ let result;
126
+ try {
127
+ result = await (input.finishCall ?? finishCall)(born);
128
+ }
129
+ catch (error) {
130
+ if (listener?.committed === true) {
131
+ try {
132
+ await listener.rollback();
133
+ }
134
+ catch {
135
+ /* preserve launch failure */
136
+ }
137
+ }
138
+ throw error;
139
+ }
118
140
  return { kind: "akuma", action: "call", result, world: input.path };
119
141
  }
120
142
  case "wait":
@@ -38,6 +38,7 @@ export type ParsedAkumaCommand = Output & ((Readonly<{
38
38
  }> & Addressed & Prompted) | (Readonly<{
39
39
  command: "history";
40
40
  last: boolean;
41
+ id?: string;
41
42
  before?: number;
42
43
  since?: number;
43
44
  limit?: number;
@@ -3,6 +3,7 @@ import { parseAkuId } from "../../akuma/identity.js";
3
3
  import { parseDuration as decodeDuration } from "../../duration.js";
4
4
  import { parseAkumaAlias, parseAkumaGlob } from "../../identity/selector.js";
5
5
  import { decodeAllowedActions } from "../../akuma/allowed.js";
6
+ import { parsePublicHistoryId } from "../../akuma/identity.js";
6
7
  const AKUMA_COMMAND_SPECS = {
7
8
  call: {
8
9
  arity: 1,
@@ -49,8 +50,8 @@ const AKUMA_COMMAND_SPECS = {
49
50
  history: {
50
51
  arity: 1,
51
52
  stdin: false,
52
- flags: { before: "value", since: "value", limit: "value", last: "boolean", json: "boolean" },
53
- usage: "history <aku/...|@alias|kei/...> [--before <index> | --since <index>] [--limit <count>] [--last]",
53
+ flags: { id: "value", before: "value", since: "value", limit: "value", last: "boolean", json: "boolean" },
54
+ usage: "history <aku/...|@alias|kei/...> [--id <historyId> | --before <index> | --since <index>] [--limit <count>] [--last]",
54
55
  purpose: "Read Akuma execution history or one complete Contract journal and Dispatch timeline.",
55
56
  },
56
57
  fork: {
@@ -203,22 +204,31 @@ function parseWait(rawSelectors, flags, output, fail) {
203
204
  }
204
205
  function parseHistory(selector, flags, output, fail) {
205
206
  if (selector.startsWith("kei/")) {
206
- if (flags.before !== undefined || flags.since !== undefined || flags.limit !== undefined || flags.last === true) {
207
- fail("history kei/... does not accept --before, --since, --limit, or --last");
207
+ if ([flags.id, flags.before, flags.since, flags.limit, flags.last].some((value) => value !== undefined)) {
208
+ fail("history kei/... does not accept --id, --before, --since, --limit, or --last");
208
209
  }
209
210
  return { command: "history", contract: selector, output };
210
211
  }
212
+ return parseAkumaHistory(selector, flags, output, fail);
213
+ }
214
+ function parseAkumaHistory(selector, flags, output, fail) {
215
+ const bounded = flags.before !== undefined || flags.since !== undefined || flags.limit !== undefined;
211
216
  if (flags.before !== undefined && flags.since !== undefined)
212
217
  fail("history --before and --since are mutually exclusive");
213
- if (flags.last === true && (flags.before !== undefined || flags.since !== undefined || flags.limit !== undefined)) {
218
+ if (flags.id !== undefined && (flags.last === true || bounded))
219
+ fail("history --id cannot be combined with --last, --before, --since, or --limit");
220
+ if (flags.last === true && bounded)
214
221
  fail("history --last cannot be combined with --before, --since, or --limit");
215
- }
216
222
  const limit = flags.limit === undefined ? undefined : positiveIndex(flags.limit, "--limit", fail);
217
223
  if (limit !== undefined && limit > 5_000)
218
224
  fail("--limit must be no greater than 5000");
225
+ const id = flags.id === undefined ? undefined : stringFlag(flags.id, "history --id requires a nonblank historyId", fail);
226
+ if (id !== undefined && parsePublicHistoryId(id) === null)
227
+ fail("history --id requires turn/<positive safe integer>");
219
228
  return {
220
229
  command: "history",
221
230
  akuma: validateDirect(selector, fail),
231
+ ...(id === undefined ? {} : { id }),
222
232
  last: flags.last === true,
223
233
  ...(flags.before === undefined ? {} : { before: positiveIndex(flags.before, "--before", fail) }),
224
234
  ...(flags.since === undefined ? {} : { since: positiveIndex(flags.since, "--since", fail) }),
@@ -56,6 +56,7 @@ export type ParsedAbandon = Output & Actor & Readonly<{
56
56
  export type ParsedStatus = Output & (Readonly<{
57
57
  command: "status";
58
58
  contract?: string;
59
+ selectors?: readonly string[];
59
60
  akuma?: never;
60
61
  }> | Readonly<{
61
62
  command: "status";
@@ -178,8 +179,8 @@ export declare const CONTRACT_COMMAND_SPECS: {
178
179
  readonly flags: {
179
180
  readonly json: "boolean";
180
181
  };
181
- readonly usage: "status [<contract>|@name|<aku/...>]";
182
- readonly purpose: "Read the world status board or one Contract projection.";
182
+ readonly usage: "status [<contract>|@name|<aku/...>]...";
183
+ readonly purpose: "Read the world status board or one or more Contract and Akuma projections.";
183
184
  };
184
185
  readonly show: {
185
186
  readonly positional: "optional";
@@ -87,8 +87,8 @@ export const CONTRACT_COMMAND_SPECS = {
87
87
  positional: "optional",
88
88
  stdin: "none",
89
89
  flags: { json: "boolean" },
90
- usage: "status [<contract>|@name|<aku/...>]",
91
- purpose: "Read the world status board or one Contract projection.",
90
+ usage: "status [<contract>|@name|<aku/...>]...",
91
+ purpose: "Read the world status board or one or more Contract and Akuma projections.",
92
92
  },
93
93
  show: {
94
94
  positional: "optional",
@@ -244,6 +244,9 @@ function parseReview(parts) {
244
244
  };
245
245
  }
246
246
  function parseStatus(parts) {
247
+ if (parts.positionals.length > 1) {
248
+ return { command: "status", selectors: parts.positionals, output: parts.output };
249
+ }
247
250
  const contract = parts.positionals[0];
248
251
  if (contract?.startsWith("aku/") === true) {
249
252
  return { command: "status", contract, akuma: true, output: parts.output };
@@ -0,0 +1,4 @@
1
+ import type { StatusSetResult } from "../result.js";
2
+ import type { Repo } from "../../library/repo.js";
3
+ import type { WorldRoot } from "../../world.js";
4
+ export declare function invokeStatusSet(selectors: readonly string[], world: WorldRoot | null, repo: Repo | undefined): Promise<StatusSetResult>;