@astrosheep/keiyaku 4.5.26 → 4.5.27

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 (78) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +5 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +16 -14
  3. package/build/src/akuma/akuma-errors.d.ts +6 -0
  4. package/build/src/akuma/akuma-errors.js +11 -0
  5. package/build/src/akuma/akuma-handle.d.ts +53 -1
  6. package/build/src/akuma/akuma-handle.js +75 -7
  7. package/build/src/akuma/akuma-instance.js +6 -22
  8. package/build/src/akuma/akuma-probe.d.ts +20 -1
  9. package/build/src/akuma/akuma-probe.js +20 -3
  10. package/build/src/akuma/akuma.d.ts +12 -0
  11. package/build/src/akuma/allowed.d.ts +2 -1
  12. package/build/src/akuma/allowed.js +9 -4
  13. package/build/src/akuma/archetype.js +3 -3
  14. package/build/src/akuma/body.js +8 -3
  15. package/build/src/akuma/fleet-execution.d.ts +6 -1
  16. package/build/src/akuma/fleet-execution.js +44 -0
  17. package/build/src/akuma/fleet-observation.d.ts +238 -0
  18. package/build/src/akuma/fleet-observation.js +13 -0
  19. package/build/src/akuma/fleet-owner-port.d.ts +12 -0
  20. package/build/src/akuma/fleet-owner-port.js +46 -0
  21. package/build/src/akuma/fleet-request.d.ts +55 -4
  22. package/build/src/akuma/fleet-request.js +101 -6
  23. package/build/src/akuma/heart/index.d.ts +1 -0
  24. package/build/src/akuma/heart/index.js +4 -1
  25. package/build/src/akuma/heart/rows.d.ts +1 -0
  26. package/build/src/akuma/heart/rows.js +8 -0
  27. package/build/src/akuma/heart/soul.js +2 -2
  28. package/build/src/akuma/projection.d.ts +36 -0
  29. package/build/src/akuma/projection.js +2 -1
  30. package/build/src/akuma/provider.d.ts +14 -0
  31. package/build/src/akuma/provider.js +66 -4
  32. package/build/src/akuma/providers/acp/events.js +16 -8
  33. package/build/src/akuma/providers/claude/events.js +5 -7
  34. package/build/src/akuma/providers/opencode-sdk/events.js +9 -5
  35. package/build/src/akuma/providers/pi/events.js +4 -3
  36. package/build/src/akuma/request-lifecycle.js +10 -1
  37. package/build/src/akuma-body.js +1 -33
  38. package/build/src/cli/commands/akuma-invoke.d.ts +8 -0
  39. package/build/src/cli/commands/akuma-invoke.js +58 -6
  40. package/build/src/cli/commands/akuma.d.ts +1 -0
  41. package/build/src/cli/commands/akuma.js +10 -15
  42. package/build/src/cli/commands/contract-help.js +8 -5
  43. package/build/src/cli/render/akuma-activity.d.ts +7 -1
  44. package/build/src/cli/render/akuma-activity.js +158 -49
  45. package/build/src/cli/render/akuma-tool.d.ts +5 -0
  46. package/build/src/cli/render/akuma-tool.js +118 -14
  47. package/build/src/cli/render/akuma.d.ts +3 -1
  48. package/build/src/cli/render/akuma.js +34 -2
  49. package/build/src/cli/render/catalog.js +2 -3
  50. package/build/src/cli/render/contract-observation.d.ts +1 -0
  51. package/build/src/cli/render/contract-observation.js +20 -1
  52. package/build/src/cli/render/contract.js +4 -4
  53. package/build/src/cli/render/execution-progress.d.ts +1 -0
  54. package/build/src/cli/render/execution-progress.js +77 -3
  55. package/build/src/cli/render/kanshi.js +5 -19
  56. package/build/src/cli/render/terminal.js +3 -1
  57. package/build/src/cli/runtime.d.ts +6 -0
  58. package/build/src/cli/runtime.js +35 -17
  59. package/build/src/core/facts/offer.d.ts +1 -2
  60. package/build/src/core/verbs/abandon.js +1 -1
  61. package/build/src/core/verbs/amend.js +1 -1
  62. package/build/src/core/verbs/arc.js +1 -1
  63. package/build/src/core/verbs/attestation.js +1 -1
  64. package/build/src/core/verbs/bind.js +1 -1
  65. package/build/src/core/verbs/deliver.js +1 -1
  66. package/build/src/core/verbs/placement.js +1 -1
  67. package/build/src/core/verbs/reintegrate.js +1 -1
  68. package/build/src/git/admission.js +18 -7
  69. package/build/src/identity/coordinates.js +1 -1
  70. package/build/src/library/address.d.ts +33 -3
  71. package/build/src/library/address.js +89 -28
  72. package/build/src/library/composition.d.ts +12 -0
  73. package/build/src/library/fleet.d.ts +11 -1
  74. package/build/src/library/fleet.js +138 -48
  75. package/build/src/library/keiyaku.d.ts +12 -0
  76. package/build/src/plugin/public.d.ts +2 -0
  77. package/build/src/runtime/proc/windows-launch.exe +0 -0
  78. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  /** @architectureCompositionRoot */
2
2
  import { readAliases } from "../alias/index.js";
3
3
  import { createAkumaProduct } from "../akuma/akuma-product.js";
4
- import { probeBornAkuma } from "../akuma/akuma-probe.js";
4
+ import { akumaAddressability, requireBornAkuma } from "../akuma/akuma-probe.js";
5
5
  import { parseAkuId } from "../akuma/identity.js";
6
6
  import { contractId } from "../core/facts/types.js";
7
7
  import { readDispatches } from "../dispatch/index.js";
@@ -9,6 +9,16 @@ import { matchesAkumaGlob, parseAkumaAlias, parseAkumaGlob, } from "../identity/
9
9
  import { World } from "../world.js";
10
10
  import { requireInput } from "./input.js";
11
11
  import { scopeForRepo } from "./repo.js";
12
+ export class AkumaAddressError extends Error {
13
+ refusal;
14
+ constructor(refusal) {
15
+ super(refusal.kind === "akuma-alias-not-found"
16
+ ? `unknown Akuma alias: ${refusal.alias}`
17
+ : `invalid Akuma: ${refusal.selector}`);
18
+ this.refusal = refusal;
19
+ this.name = "AkumaAddressError";
20
+ }
21
+ }
12
22
  export class AkumaWorldScopeError extends TypeError {
13
23
  refusal;
14
24
  constructor(refusal) {
@@ -22,23 +32,43 @@ function nonblank(value, label) {
22
32
  throw new TypeError(`${label} must be a nonblank string`);
23
33
  return value;
24
34
  }
35
+ function parseAddressAlias(selector) {
36
+ try {
37
+ return parseAkumaAlias(selector);
38
+ }
39
+ catch (error) {
40
+ if (!(error instanceof TypeError))
41
+ throw error;
42
+ throw new AkumaAddressError({ kind: "invalid-akuma", selector });
43
+ }
44
+ }
45
+ function parseAddressId(selector) {
46
+ try {
47
+ return parseAkuId(selector);
48
+ }
49
+ catch (error) {
50
+ if (!(error instanceof TypeError))
51
+ throw error;
52
+ throw new AkumaAddressError({ kind: "invalid-akuma", selector });
53
+ }
54
+ }
25
55
  async function directId(path, selector) {
26
56
  if (selector.startsWith("@")) {
27
- const alias = parseAkumaAlias(selector);
57
+ const alias = parseAddressAlias(selector);
28
58
  const resolved = (await readAliases(path)).find((binding) => binding.alias === alias)?.akuId ?? null;
29
59
  if (resolved === null)
30
- throw new TypeError(`unknown Akuma alias: ${alias}`);
60
+ throw new AkumaAddressError({ kind: "akuma-alias-not-found", alias });
31
61
  return resolved;
32
62
  }
33
- return parseAkuId(selector).id;
63
+ return parseAddressId(selector).id;
34
64
  }
35
65
  export function resolveNamedAddress(input) {
36
66
  const selector = nonblank(input.selector, "selector");
37
67
  if (selector.startsWith("kei/"))
38
68
  return { kind: "contract", id: contractId(selector) };
39
69
  if (selector.startsWith("aku/"))
40
- return { kind: "akuma", id: parseAkuId(selector).id };
41
- const alias = parseAkumaAlias(selector);
70
+ return { kind: "akuma", id: parseAddressId(selector).id };
71
+ const alias = parseAddressAlias(selector);
42
72
  const contractMatches = (input.report.contracts.kind === "present" ? input.report.contracts.value.rows : []).filter((row) => row.disposition === "active" &&
43
73
  row.workspace === "worktree" &&
44
74
  row.worktreePath !== null &&
@@ -62,9 +92,14 @@ export function resolveNamedAddress(input) {
62
92
  if (input.aliases.kind === "failed") {
63
93
  throw new TypeError("cannot resolve a named selector while Alias authority is failed");
64
94
  }
65
- throw new TypeError(`unknown selector: ${selector}`);
95
+ throw new AkumaAddressError({ kind: "akuma-alias-not-found", alias });
66
96
  }
67
- export async function addressAkuma(input) {
97
+ /**
98
+ * Resolves one selector to a complete identity without proving it born. The
99
+ * caller is a forwarding boundary: the parent Fleet answers for the target, so
100
+ * this process must not read its own Heart files to decide.
101
+ */
102
+ export async function resolveAkuma(input) {
68
103
  const values = requireInput(input, "Akuma address input");
69
104
  for (const key of Object.keys(values)) {
70
105
  if (!["path", "akuma", "repo"].includes(key)) {
@@ -74,7 +109,17 @@ export async function addressAkuma(input) {
74
109
  if (values.repo !== undefined)
75
110
  scopeForRepo(values.repo);
76
111
  const path = await World.prove(nonblank(values.path, "path"));
77
- return { path, id: await directId(path, nonblank(values.akuma, "akuma")) };
112
+ const id = await directId(path, nonblank(values.akuma, "akuma"));
113
+ return { path, id };
114
+ }
115
+ /**
116
+ * Resolves one selector against this World, where a local caller is answered
117
+ * for the target's birth by the one addressability normalization.
118
+ */
119
+ export async function addressAkuma(input) {
120
+ const addressed = await resolveAkuma(input);
121
+ await requireBornAkuma(addressed.path, addressed.id);
122
+ return addressed;
78
123
  }
79
124
  function parseSetSelector(raw) {
80
125
  const selector = nonblank(raw, "akuma selector");
@@ -83,13 +128,13 @@ function parseSetSelector(raw) {
83
128
  if (selector.includes("*"))
84
129
  return { kind: "glob", value: parseAkumaGlob(selector) };
85
130
  if (selector.startsWith("@"))
86
- return { kind: "alias", value: parseAkumaAlias(selector) };
87
- return { kind: "direct", value: parseAkuId(selector).id };
131
+ return { kind: "alias", value: parseAddressAlias(selector) };
132
+ return { kind: "direct", value: parseAddressId(selector).id };
88
133
  }
89
134
  function hasSelectorKind(selectors, kind) {
90
135
  return selectors.some((selector) => selector.kind === kind);
91
136
  }
92
- function addSelectorIds(selector, sources, selected, contractMembers) {
137
+ function addSelectorIds(selector, sources, selected, contractMembers, explicit) {
93
138
  if (selector.kind === "contract") {
94
139
  for (const dispatch of sources.dispatches) {
95
140
  if (dispatch.contractId !== selector.value)
@@ -108,20 +153,18 @@ function addSelectorIds(selector, sources, selected, contractMembers) {
108
153
  if (selector.kind === "alias") {
109
154
  const id = sources.aliases.get(selector.value);
110
155
  if (id === undefined)
111
- throw new TypeError(`unknown Akuma alias: ${selector.value}`);
156
+ throw new AkumaAddressError({ kind: "akuma-alias-not-found", alias: selector.value });
112
157
  selected.add(id);
158
+ explicit.add(id);
113
159
  return;
114
160
  }
115
161
  selected.add(selector.value);
162
+ explicit.add(selector.value);
116
163
  }
117
164
  async function contractMemberInWorld(path, id) {
118
- try {
119
- return await probeBornAkuma(path, id);
120
- }
121
- catch {
122
- // A dispatched physical member remains selected when its Heart cannot be read.
123
- return true;
124
- }
165
+ // A dispatched physical member stays selected when its Heart cannot be read:
166
+ // the World, not this reader, remains the authority on its membership.
167
+ return (await akumaAddressability(path, id)).kind !== "absent";
125
168
  }
126
169
  async function refuseForeignContractMembers(path, ids, contractMembers) {
127
170
  const foreign = [];
@@ -132,7 +175,13 @@ async function refuseForeignContractMembers(path, ids, contractMembers) {
132
175
  if (foreign.length > 0)
133
176
  throw new AkumaWorldScopeError({ kind: "akuma-not-in-world", ids: foreign, world: path });
134
177
  }
135
- export async function addressAkumaSet(input) {
178
+ function canonicalAkumaOrder(selected) {
179
+ const ids = [...selected].sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)));
180
+ if (ids.length === 0)
181
+ throw new TypeError("Akuma selector snapshot is empty");
182
+ return ids;
183
+ }
184
+ async function readAkumaSet(input) {
136
185
  const values = requireInput(input, "Akuma set address input");
137
186
  for (const key of Object.keys(values)) {
138
187
  if (!["path", "akuma", "repo"].includes(key)) {
@@ -157,13 +206,25 @@ export async function addressAkumaSet(input) {
157
206
  : [];
158
207
  const selected = new Set();
159
208
  const contractMembers = new Set();
209
+ const explicit = new Set();
160
210
  const sources = { fleetIds, aliases, dispatches };
161
211
  for (const selector of selectors)
162
- addSelectorIds(selector, sources, selected, contractMembers);
163
- const orderedIds = [...selected];
164
- const ids = [...selected].sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)));
165
- if (ids.length === 0)
166
- throw new TypeError("Akuma selector snapshot is empty");
167
- await refuseForeignContractMembers(path, ids, contractMembers);
168
- return { path, ids, orderedIds };
212
+ addSelectorIds(selector, sources, selected, contractMembers, explicit);
213
+ return { path, orderedIds: [...selected], ids: canonicalAkumaOrder(selected), contractMembers, explicit };
214
+ }
215
+ /**
216
+ * Resolves a selector set to complete identities without proving any born. The
217
+ * caller is a forwarding boundary, the parent Fleet owns the answer, and no
218
+ * member's Heart is read here.
219
+ */
220
+ export async function resolveAkumaSet(input) {
221
+ const resolved = await readAkumaSet(input);
222
+ return { path: resolved.path, ids: resolved.ids, orderedIds: resolved.orderedIds };
223
+ }
224
+ export async function addressAkumaSet(input) {
225
+ const resolved = await readAkumaSet(input);
226
+ for (const id of resolved.explicit)
227
+ await requireBornAkuma(resolved.path, id);
228
+ await refuseForeignContractMembers(resolved.path, resolved.ids, resolved.contractMembers);
229
+ return { path: resolved.path, ids: resolved.ids, orderedIds: resolved.orderedIds };
169
230
  }
@@ -191,6 +191,10 @@ export declare function composeLibrary(context?: ExecutionContext, composition?:
191
191
  } | {
192
192
  kind: "other";
193
193
  display: string;
194
+ input?: {
195
+ json: string;
196
+ truncated: boolean;
197
+ } | undefined;
194
198
  };
195
199
  state: "active";
196
200
  truncated?: true | undefined;
@@ -227,6 +231,10 @@ export declare function composeLibrary(context?: ExecutionContext, composition?:
227
231
  } | {
228
232
  kind: "other";
229
233
  display: string;
234
+ input?: {
235
+ json: string;
236
+ truncated: boolean;
237
+ } | undefined;
230
238
  };
231
239
  state: {
232
240
  status: "error" | "ok";
@@ -317,6 +325,10 @@ export declare function composeLibrary(context?: ExecutionContext, composition?:
317
325
  } | {
318
326
  kind: "other";
319
327
  display: string;
328
+ input?: {
329
+ json: string;
330
+ truncated: boolean;
331
+ } | undefined;
320
332
  };
321
333
  state: {
322
334
  status: "error" | "ok";
@@ -3,8 +3,9 @@ import { type ActivityHistory, type OutcomeRow, type AkumaStatus, type Interrupt
3
3
  import { type ExecutionContext } from "../akuma/requests.js";
4
4
  import type { WaitObserver } from "../akuma/fleet-execution.js";
5
5
  import { type DispatchAssociation } from "../dispatch/index.js";
6
+ import { type Schema } from "../akuma/schema.js";
6
7
  import { type AkumaAddressInput, type AkumaSetAddressInput } from "./address.js";
7
- import { type AkumaKillResult, type AkumaObservation, type AkumaObservationStage, type AkumaTellResult, type AkumaWaitResult } from "../akuma/fleet-observation.js";
8
+ import { type AkumaKillResult, type AkumaObservation, type AkumaObservationStage, type AkumaTellResult, type AkumaTellWaitResult, type AkumaWaitResult } from "../akuma/fleet-observation.js";
8
9
  export type AkumaWaitInput = AkumaSetAddressInput & Readonly<{
9
10
  completion?: "any" | "all";
10
11
  timeoutMs?: number;
@@ -15,6 +16,14 @@ export type AkumaTellInput = AkumaAddressInput & Readonly<{
15
16
  initiator?: string;
16
17
  signal?: AbortSignal;
17
18
  }>;
19
+ export type AkumaTellWaitInput = AkumaAddressInput & Readonly<{
20
+ body: string;
21
+ timeoutMs: number;
22
+ schema?: Schema<unknown>;
23
+ interrupt?: boolean;
24
+ initiator?: string;
25
+ signal?: AbortSignal;
26
+ }>;
18
27
  export type { TellResult, TellWake } from "../akuma/akuma.js";
19
28
  export type { CreatedTaskObservation } from "../task/created-observation.js";
20
29
  export type { DispatchAssociation } from "../dispatch/association.js";
@@ -68,5 +77,6 @@ export declare function statusAkuma(input: AkumaAddressInput): Promise<AkumaObse
68
77
  export declare function waitAkuma(input: AkumaWaitInput, execution?: ExecutionContext, observer?: WaitObserver): Promise<AkumaWaitResult>;
69
78
  export declare function killAkuma(input: AkumaKillInput, execution?: ExecutionContext): Promise<AkumaKillResult>;
70
79
  export declare function tellAkuma(input: AkumaTellInput, execution?: ExecutionContext): Promise<AkumaTellResult>;
80
+ export declare function tellWaitAkuma(input: AkumaTellWaitInput, execution?: ExecutionContext): Promise<AkumaTellWaitResult>;
71
81
  export declare function interruptAkuma(input: AkumaInterruptInput): Promise<AkumaInterruptResult>;
72
82
  export declare function historyAkuma(input: AkumaHistoryInput): Promise<AkumaHistoryResult>;
@@ -1,15 +1,17 @@
1
1
  /** @architectureCompositionRoot */
2
2
  import { AkumaNotBornError, readBudgetedStatus, withoutReportedChanges, } from "../akuma/akuma.js";
3
+ import { AkumaObservationError } from "../akuma/akuma-errors.js";
3
4
  import { createAkumaProduct } from "../akuma/akuma-product.js";
4
5
  import { executionChannel, localExecutionContext } from "../akuma/requests.js";
5
- import { requestForwardedFleetKill, requestForwardedFleetTell, requestForwardedFleetWait, } from "../akuma/fleet-request.js";
6
- import { executeKillAkuma, executeTellAkuma, executeWaitAkuma } from "../akuma/fleet-execution.js";
6
+ import { requestForwardedFleetKill, requestForwardedFleetTell, requestForwardedFleetTellWait, requestForwardedFleetWait, } from "../akuma/fleet-request.js";
7
+ import { executeKillAkuma, executeTellAkuma, executeTellWaitAkuma, executeWaitAkuma, } from "../akuma/fleet-execution.js";
7
8
  import { readAliases } from "../alias/index.js";
8
9
  import { observeDispatchAssociation } from "../dispatch/index.js";
9
10
  import { observeContractAt } from "../git/observe.js";
10
11
  import { withGitDecodeChannel } from "../git/read-observation.js";
11
12
  import { observeCreatedTaskObservations } from "../task/created-observation.js";
12
- import { addressAkuma, addressAkumaSet } from "./address.js";
13
+ import { schemaJsonText } from "../akuma/schema.js";
14
+ import { addressAkuma, addressAkumaSet, resolveAkuma, resolveAkumaSet, } from "./address.js";
13
15
  import { requireInput } from "./input.js";
14
16
  import { fleetResultSchemas, parseAkumaObservation, } from "../akuma/fleet-observation.js";
15
17
  import { scopeForRepo } from "./repo.js";
@@ -20,6 +22,11 @@ function source(path) {
20
22
  function observationDiagnostic(error) {
21
23
  return error instanceof Error ? error.message : String(error);
22
24
  }
25
+ function observationError(id, error) {
26
+ if (error instanceof AkumaNotBornError || error instanceof AkumaObservationError)
27
+ return error;
28
+ return new AkumaObservationError(id, observationDiagnostic(error));
29
+ }
23
30
  async function dispatchAssociation(repo, id) {
24
31
  return await observeDispatchAssociation(repo === undefined ? undefined : scopeForRepo(repo), id);
25
32
  }
@@ -153,7 +160,49 @@ function setAddress(values) {
153
160
  }
154
161
  export async function statusAkuma(input) {
155
162
  const addressed = await addressAkuma(input);
156
- return await observeAkuma(await source(addressed.path).selectHandle({ id: addressed.id }).status(), addressed.path, input.repo);
163
+ try {
164
+ return await observeAkuma(await source(addressed.path).selectHandle({ id: addressed.id }).status(), addressed.path, input.repo);
165
+ }
166
+ catch (error) {
167
+ throw observationError(addressed.id, error);
168
+ }
169
+ }
170
+ function completionMode(value) {
171
+ // An omitted mode is any: a plural wait returns when any selected Akuma is complete.
172
+ if (value === undefined)
173
+ return "any";
174
+ if (value !== "any" && value !== "all")
175
+ throw new TypeError("completion must be any or all");
176
+ return value;
177
+ }
178
+ async function forwardedWait(addressed, input) {
179
+ return await attachWaitAssociations(addressed.path, input.repo, await requestForwardedFleetWait({
180
+ directory: input.directory,
181
+ targets: addressed.orderedIds,
182
+ completion: input.completion,
183
+ ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
184
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
185
+ }));
186
+ }
187
+ async function localWait(addressed, input) {
188
+ try {
189
+ return await attachWaitAssociations(addressed.path, input.repo, await executeWaitAkuma({
190
+ path: addressed.path,
191
+ ids: addressed.orderedIds,
192
+ completion: input.completion,
193
+ ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
194
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
195
+ identity: waitIdentityFacts(addressed.path, input.repo),
196
+ selectionOrder: addressed.orderedIds,
197
+ ...(input.observer?.selected === undefined ? {} : { onSelected: input.observer.selected }),
198
+ ...(input.observer?.observe === undefined ? {} : { observe: input.observer.observe }),
199
+ }));
200
+ }
201
+ catch (error) {
202
+ if (input.signal?.aborted === true || addressed.orderedIds.length !== 1)
203
+ throw error;
204
+ throw observationError(addressed.orderedIds[0], error);
205
+ }
157
206
  }
158
207
  export async function waitAkuma(input, execution = localExecutionContext(), observer) {
159
208
  const values = requireInput(input, "Keiyaku.wait input");
@@ -162,37 +211,25 @@ export async function waitAkuma(input, execution = localExecutionContext(), obse
162
211
  throw new TypeError(`Keiyaku.wait input has unknown field: ${key}`);
163
212
  }
164
213
  }
165
- const addressed = await addressAkumaSet(setAddress(values));
166
- const completion = values.completion;
167
- if (completion !== undefined && completion !== "any" && completion !== "all") {
168
- throw new TypeError("completion must be any or all");
169
- }
170
- // An omitted mode is any: a plural wait returns when any selected Akuma is complete.
171
- const selected = completion ?? "any";
214
+ const selected = completionMode(values.completion);
172
215
  const timeoutMs = timeout(values.timeoutMs);
173
216
  const callerSignal = signal(values.signal);
174
217
  const channel = executionChannel(execution);
175
218
  const repo = values.repo;
176
- if (channel.kind === "body-request") {
177
- return await attachWaitAssociations(addressed.path, repo, await requestForwardedFleetWait({
178
- directory: channel.directory,
179
- targets: addressed.orderedIds,
180
- completion: selected,
181
- ...(timeoutMs === undefined ? {} : { timeoutMs }),
182
- ...(callerSignal === undefined ? {} : { signal: callerSignal }),
183
- }));
184
- }
185
- return await attachWaitAssociations(addressed.path, repo, await executeWaitAkuma({
186
- path: addressed.path,
187
- ids: addressed.orderedIds,
219
+ const mode = {
188
220
  completion: selected,
189
221
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
190
222
  ...(callerSignal === undefined ? {} : { signal: callerSignal }),
191
- identity: waitIdentityFacts(addressed.path, repo),
192
- selectionOrder: addressed.orderedIds,
193
- ...(observer?.selected === undefined ? {} : { onSelected: observer.selected }),
194
- ...(observer?.observe === undefined ? {} : { observe: observer.observe }),
195
- }));
223
+ ...(repo === undefined ? {} : { repo }),
224
+ };
225
+ if (channel.kind === "body-request") {
226
+ // A forwarded operation resolves coordinates without proving birth: the
227
+ // parent Fleet owns the target, so this process never probes locally.
228
+ const addressed = await resolveAkumaSet(setAddress(values));
229
+ return await forwardedWait(addressed, { ...mode, directory: channel.directory });
230
+ }
231
+ const addressed = await addressAkumaSet(setAddress(values));
232
+ return await localWait(addressed, { ...mode, ...(observer === undefined ? {} : { observer }) });
196
233
  }
197
234
  export async function killAkuma(input, execution = localExecutionContext()) {
198
235
  const values = requireInput(input, "Keiyaku.kill input");
@@ -202,15 +239,16 @@ export async function killAkuma(input, execution = localExecutionContext()) {
202
239
  }
203
240
  }
204
241
  const callerSignal = signal(values.signal);
205
- const addressed = await addressAkumaSet(setAddress(values));
206
242
  const channel = executionChannel(execution);
207
243
  if (channel.kind === "body-request") {
244
+ const addressed = await resolveAkumaSet(setAddress(values));
208
245
  return await requestForwardedFleetKill({
209
246
  directory: channel.directory,
210
247
  targets: addressed.orderedIds,
211
248
  ...(callerSignal === undefined ? {} : { signal: callerSignal }),
212
249
  });
213
250
  }
251
+ const addressed = await addressAkumaSet(setAddress(values));
214
252
  return await executeKillAkuma({
215
253
  path: addressed.path,
216
254
  ids: addressed.orderedIds,
@@ -227,9 +265,9 @@ export async function tellAkuma(input, execution = localExecutionContext()) {
227
265
  if (typeof values.body !== "string")
228
266
  throw new TypeError("body must be a string");
229
267
  const callerSignal = signal(values.signal);
230
- const addressed = await addressAkuma(directAddress(values));
231
268
  const channel = executionChannel(execution);
232
269
  if (channel.kind === "body-request") {
270
+ const addressed = await resolveAkuma(directAddress(values));
233
271
  return await requestForwardedFleetTell({
234
272
  directory: channel.directory,
235
273
  target: addressed.id,
@@ -238,6 +276,7 @@ export async function tellAkuma(input, execution = localExecutionContext()) {
238
276
  ...(callerSignal === undefined ? {} : { signal: callerSignal }),
239
277
  });
240
278
  }
279
+ const addressed = await addressAkuma(directAddress(values));
241
280
  return await executeTellAkuma({
242
281
  path: addressed.path,
243
282
  id: addressed.id,
@@ -246,6 +285,52 @@ export async function tellAkuma(input, execution = localExecutionContext()) {
246
285
  ...(callerSignal === undefined ? {} : { signal: callerSignal }),
247
286
  });
248
287
  }
288
+ export async function tellWaitAkuma(input, execution = localExecutionContext()) {
289
+ const values = requireInput(input, "Keiyaku tell wait input");
290
+ for (const key of Object.keys(values)) {
291
+ if (!["path", "akuma", "body", "repo", "timeoutMs", "schema", "interrupt", "initiator", "signal"].includes(key)) {
292
+ throw new TypeError(`Keiyaku tell wait input has unknown field: ${key}`);
293
+ }
294
+ }
295
+ if (typeof values.body !== "string")
296
+ throw new TypeError("body must be a string");
297
+ if (typeof values.timeoutMs !== "number" ||
298
+ !Number.isFinite(values.timeoutMs) ||
299
+ !Number.isInteger(values.timeoutMs) ||
300
+ values.timeoutMs < 0) {
301
+ throw new TypeError("timeoutMs must be a nonnegative finite millisecond duration");
302
+ }
303
+ if (values.interrupt !== undefined && typeof values.interrupt !== "boolean")
304
+ throw new TypeError("interrupt must be a boolean");
305
+ const callerSignal = signal(values.signal);
306
+ const channel = executionChannel(execution);
307
+ if (channel.kind === "body-request") {
308
+ // A forwarded Tell is resolved by its serving parent, so this process must
309
+ // not prove the target against its own Heart files.
310
+ const addressed = await resolveAkuma(directAddress(values));
311
+ return await requestForwardedFleetTellWait({
312
+ directory: channel.directory,
313
+ target: addressed.id,
314
+ body: values.body,
315
+ timeoutMs: values.timeoutMs,
316
+ ...(input.schema === undefined ? {} : { schema: input.schema }),
317
+ ...(input.interrupt === true ? { interrupt: true } : {}),
318
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
319
+ ...(callerSignal === undefined ? {} : { signal: callerSignal }),
320
+ });
321
+ }
322
+ const addressed = await addressAkuma(directAddress(values));
323
+ return await executeTellWaitAkuma({
324
+ path: addressed.path,
325
+ id: addressed.id,
326
+ body: values.body,
327
+ timeoutMs: values.timeoutMs,
328
+ ...(input.schema === undefined ? {} : { schemaJson: schemaJsonText(input.schema) }),
329
+ ...(input.interrupt === true ? { interrupt: true } : {}),
330
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
331
+ ...(callerSignal === undefined ? {} : { signal: callerSignal }),
332
+ });
333
+ }
249
334
  export async function interruptAkuma(input) {
250
335
  const values = requireInput(input, "Keiyaku.interrupt input");
251
336
  for (const key of Object.keys(values)) {
@@ -284,24 +369,29 @@ export async function historyAkuma(input) {
284
369
  const values = requireInput(input, "Keiyaku.history input");
285
370
  validateHistoryInput(values);
286
371
  const addressed = await addressAkuma(directAddress(values));
287
- const handle = source(addressed.path).selectHandle({ id: addressed.id });
288
- const contract = await dispatchAssociation(values.repo, addressed.id);
289
- if (values.last === true) {
290
- const answer = await handle.lastAnswer();
291
- return answer.kind === "answer"
292
- ? { kind: "last", id: addressed.id, answer: answer.answer, contract }
293
- : { kind: "no-answer", id: addressed.id, contract };
372
+ try {
373
+ const handle = source(addressed.path).selectHandle({ id: addressed.id });
374
+ const contract = await dispatchAssociation(values.repo, addressed.id);
375
+ if (values.last === true) {
376
+ const answer = await handle.lastAnswer();
377
+ return answer.kind === "answer"
378
+ ? { kind: "last", id: addressed.id, answer: answer.answer, contract }
379
+ : { kind: "no-answer", id: addressed.id, contract };
380
+ }
381
+ const history = await handle.history({
382
+ ...(values.id === undefined ? {} : { id: values.id }),
383
+ ...(values.before === undefined ? {} : { before: values.before }),
384
+ ...(values.since === undefined ? {} : { since: values.since }),
385
+ ...(values.limit === undefined ? {} : { limit: values.limit }),
386
+ });
387
+ if (values.id !== undefined) {
388
+ if ("kind" in history && history.kind === "exact")
389
+ return { kind: "exact", id: addressed.id, outcome: history.outcome, contract };
390
+ return { kind: "unknown-history", id: addressed.id, historyId: values.id, contract };
391
+ }
392
+ return { kind: "history", id: addressed.id, history: history, contract };
294
393
  }
295
- const history = await handle.history({
296
- ...(values.id === undefined ? {} : { id: values.id }),
297
- ...(values.before === undefined ? {} : { before: values.before }),
298
- ...(values.since === undefined ? {} : { since: values.since }),
299
- ...(values.limit === undefined ? {} : { limit: values.limit }),
300
- });
301
- if (values.id !== undefined) {
302
- if ("kind" in history && history.kind === "exact")
303
- return { kind: "exact", id: addressed.id, outcome: history.outcome, contract };
304
- return { kind: "unknown-history", id: addressed.id, historyId: values.id, contract };
394
+ catch (error) {
395
+ throw observationError(addressed.id, error);
305
396
  }
306
- return { kind: "history", id: addressed.id, history: history, contract };
307
397
  }
@@ -223,6 +223,10 @@ export declare const Keiyaku: Readonly<{
223
223
  } | {
224
224
  kind: "other";
225
225
  display: string;
226
+ input?: {
227
+ json: string;
228
+ truncated: boolean;
229
+ } | undefined;
226
230
  };
227
231
  state: "active";
228
232
  truncated?: true | undefined;
@@ -259,6 +263,10 @@ export declare const Keiyaku: Readonly<{
259
263
  } | {
260
264
  kind: "other";
261
265
  display: string;
266
+ input?: {
267
+ json: string;
268
+ truncated: boolean;
269
+ } | undefined;
262
270
  };
263
271
  state: {
264
272
  status: "error" | "ok";
@@ -349,6 +357,10 @@ export declare const Keiyaku: Readonly<{
349
357
  } | {
350
358
  kind: "other";
351
359
  display: string;
360
+ input?: {
361
+ json: string;
362
+ truncated: boolean;
363
+ } | undefined;
352
364
  };
353
365
  state: {
354
366
  status: "error" | "ok";
@@ -26,6 +26,7 @@ export type PluginSignalMap = Readonly<{
26
26
  }>;
27
27
  "akuma.turn-outcome": Readonly<{
28
28
  akumaId: string;
29
+ bodySequence: number;
29
30
  turnSequence: number;
30
31
  initiator?: string;
31
32
  outcome: PluginOutcome;
@@ -35,6 +36,7 @@ export type PluginSignalMap = Readonly<{
35
36
  akumaId: string;
36
37
  bodySequence: number;
37
38
  end: PluginBodyEnd;
39
+ initiator?: string;
38
40
  diagnostic?: string;
39
41
  contractId?: string;
40
42
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "4.5.26",
3
+ "version": "4.5.27",
4
4
  "workspaces": [
5
5
  "plugins/*"
6
6
  ],