@astrosheep/keiyaku 4.5.20 → 4.5.21

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 (154) hide show
  1. package/README.md +4 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +22 -8
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +237 -0
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +6 -7
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +2 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -3
  8. package/build/src/akuma/akuma-handle.d.ts +5 -1
  9. package/build/src/akuma/akuma-handle.js +4 -1
  10. package/build/src/akuma/akuma-instance.d.ts +7 -2
  11. package/build/src/akuma/akuma-instance.js +14 -7
  12. package/build/src/akuma/akuma-observe.d.ts +11 -0
  13. package/build/src/akuma/akuma-observe.js +22 -0
  14. package/build/src/akuma/akuma-product.d.ts +1 -0
  15. package/build/src/akuma/akuma-product.js +3 -0
  16. package/build/src/akuma/akuma.d.ts +1 -0
  17. package/build/src/akuma/body.d.ts +25 -5
  18. package/build/src/akuma/body.js +65 -8
  19. package/build/src/akuma/call-request.d.ts +2 -0
  20. package/build/src/akuma/call-request.js +2 -0
  21. package/build/src/akuma/fleet-execution.d.ts +28 -0
  22. package/build/src/akuma/fleet-execution.js +36 -4
  23. package/build/src/akuma/fleet-request.d.ts +10 -0
  24. package/build/src/akuma/fleet-request.js +11 -2
  25. package/build/src/akuma/heart/facts.d.ts +2 -0
  26. package/build/src/akuma/heart/index.d.ts +1 -0
  27. package/build/src/akuma/heart/index.js +4 -1
  28. package/build/src/akuma/heart/rows.d.ts +2 -0
  29. package/build/src/akuma/heart/rows.js +7 -5
  30. package/build/src/akuma/heart/schema.d.ts +1 -1
  31. package/build/src/akuma/heart/schema.js +3 -1
  32. package/build/src/akuma/heart/tells.js +6 -5
  33. package/build/src/akuma/heart/timeline.js +1 -1
  34. package/build/src/akuma/provider.d.ts +1 -1
  35. package/build/src/akuma/provider.js +1 -1
  36. package/build/src/akuma/providers/acp/core.js +3 -6
  37. package/build/src/akuma/providers/claude/index.js +9 -12
  38. package/build/src/akuma/providers/codex-app-server/index.js +4 -11
  39. package/build/src/akuma/providers/execution-environment.d.ts +6 -0
  40. package/build/src/akuma/providers/execution-environment.js +24 -0
  41. package/build/src/akuma/providers/opencode-sdk/index.js +30 -23
  42. package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
  43. package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
  44. package/build/src/akuma/providers/pi/events.js +7 -2
  45. package/build/src/akuma/providers/pi/index.js +3 -2
  46. package/build/src/akuma/request-rendezvous.js +8 -3
  47. package/build/src/akuma/turn-drive.d.ts +1 -0
  48. package/build/src/akuma/turn-drive.js +2 -0
  49. package/build/src/akuma-body.js +2 -0
  50. package/build/src/body/amend.js +6 -4
  51. package/build/src/body/decode.d.ts +3 -1
  52. package/build/src/body/decode.js +53 -17
  53. package/build/src/body/region.d.ts +9 -1
  54. package/build/src/body/region.js +87 -12
  55. package/build/src/body/render.js +1 -1
  56. package/build/src/body/verification.d.ts +3 -1
  57. package/build/src/body/verification.js +4 -1
  58. package/build/src/cli/accepted.js +19 -2
  59. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  60. package/build/src/cli/commands/akuma-invoke.js +133 -13
  61. package/build/src/cli/commands/akuma.js +11 -4
  62. package/build/src/cli/commands/contract-help.js +17 -4
  63. package/build/src/cli/commands/contract.js +6 -4
  64. package/build/src/cli/commands/status-set.js +15 -24
  65. package/build/src/cli/main.js +5 -0
  66. package/build/src/cli/parse.d.ts +2 -0
  67. package/build/src/cli/parse.js +5 -0
  68. package/build/src/cli/render/akuma-activity.d.ts +99 -2
  69. package/build/src/cli/render/akuma-activity.js +484 -40
  70. package/build/src/cli/render/akuma.d.ts +10 -0
  71. package/build/src/cli/render/akuma.js +28 -21
  72. package/build/src/cli/render/audit.js +1 -1
  73. package/build/src/cli/render/board.js +1 -1
  74. package/build/src/cli/render/catalog.js +16 -13
  75. package/build/src/cli/render/contract-history.js +39 -10
  76. package/build/src/cli/render/contract-observation.js +10 -6
  77. package/build/src/cli/render/contract.js +109 -106
  78. package/build/src/cli/render/execution-progress.d.ts +23 -1
  79. package/build/src/cli/render/execution-progress.js +151 -28
  80. package/build/src/cli/render/kanshi-akuma.js +27 -39
  81. package/build/src/cli/render/kanshi.js +33 -26
  82. package/build/src/cli/render/nuke.js +1 -1
  83. package/build/src/cli/render/receipt.d.ts +1 -1
  84. package/build/src/cli/render/receipt.js +5 -5
  85. package/build/src/cli/render/refusal.js +10 -7
  86. package/build/src/cli/render/region.js +1 -1
  87. package/build/src/cli/render/settings.js +12 -2
  88. package/build/src/cli/render/status-line.d.ts +30 -0
  89. package/build/src/cli/render/status-line.js +58 -0
  90. package/build/src/cli/render/status-set.js +1 -1
  91. package/build/src/cli/render/task.d.ts +2 -0
  92. package/build/src/cli/render/task.js +11 -7
  93. package/build/src/cli/render/terminal.d.ts +0 -1
  94. package/build/src/cli/render/terminal.js +47 -15
  95. package/build/src/cli/result.d.ts +1 -0
  96. package/build/src/cli/runtime.d.ts +2 -0
  97. package/build/src/cli/runtime.js +13 -7
  98. package/build/src/cli/usage.js +2 -2
  99. package/build/src/cli/version.d.ts +1 -0
  100. package/build/src/cli/version.js +25 -0
  101. package/build/src/git/workspace.d.ts +1 -1
  102. package/build/src/git/workspace.js +1 -1
  103. package/build/src/identity/mint.d.ts +13 -0
  104. package/build/src/identity/mint.js +26 -0
  105. package/build/src/identity/normalize.d.ts +4 -0
  106. package/build/src/identity/normalize.js +30 -0
  107. package/build/src/index.d.ts +2 -0
  108. package/build/src/index.js +1 -0
  109. package/build/src/kanshi/read.js +17 -4
  110. package/build/src/kanshi/report.d.ts +1 -0
  111. package/build/src/library/akuma-creation.d.ts +2 -0
  112. package/build/src/library/akuma-creation.js +33 -17
  113. package/build/src/library/bind.js +18 -16
  114. package/build/src/library/contract-bind.d.ts +3 -1
  115. package/build/src/library/contract-bind.js +5 -3
  116. package/build/src/library/contract-operations.d.ts +2 -2
  117. package/build/src/library/contract-operations.js +1 -1
  118. package/build/src/library/contract-types.d.ts +1 -0
  119. package/build/src/library/fleet.d.ts +4 -1
  120. package/build/src/library/fleet.js +32 -8
  121. package/build/src/plugin/akuma-signals.d.ts +6 -0
  122. package/build/src/plugin/akuma-signals.js +8 -0
  123. package/build/src/plugin/public.d.ts +4 -0
  124. package/build/src/plugin/runtime.js +4 -1
  125. package/build/src/protocol/amend.js +4 -3
  126. package/build/src/protocol/attempt.d.ts +2 -0
  127. package/build/src/protocol/bind.js +41 -14
  128. package/build/src/protocol/completion.d.ts +3 -0
  129. package/build/src/protocol/completion.js +7 -1
  130. package/build/src/protocol/deliver.js +165 -92
  131. package/build/src/protocol/execution-observation.d.ts +0 -4
  132. package/build/src/protocol/intent.d.ts +15 -20
  133. package/build/src/protocol/intent.js +29 -17
  134. package/build/src/protocol/operations.d.ts +2 -0
  135. package/build/src/protocol/placement.js +5 -1
  136. package/build/src/protocol/read/status.d.ts +1 -0
  137. package/build/src/protocol/read/status.js +10 -2
  138. package/build/src/protocol/reintegrate.d.ts +1 -1
  139. package/build/src/protocol/reintegrate.js +70 -43
  140. package/build/src/protocol/result-codec.js +9 -7
  141. package/build/src/protocol/review.js +53 -30
  142. package/build/src/protocol/run.d.ts +83 -36
  143. package/build/src/protocol/run.js +90 -75
  144. package/build/src/runtime/proc/windows-launch.exe +0 -0
  145. package/build/src/settings.js +5 -14
  146. package/build/src/task/document.d.ts +5 -1
  147. package/build/src/task/document.js +6 -1
  148. package/build/src/verification/execution.d.ts +0 -1
  149. package/build/src/verification/execution.js +0 -24
  150. package/build/src/verification/observation.d.ts +0 -4
  151. package/build/src/verification/observation.js +1 -2
  152. package/package.json +3 -1
  153. package/build/src/git/verification-environment.d.ts +0 -10
  154. package/build/src/git/verification-environment.js +0 -281
@@ -1,4 +1,3 @@
1
- import { directChildren, rawSlice } from "../markdown/query.js";
2
1
  export class RegionDocumentError extends Error {
3
2
  constructor(message) {
4
3
  super(message);
@@ -8,9 +7,6 @@ export class RegionDocumentError extends Error {
8
7
  function refusal(message) {
9
8
  throw new RegionDocumentError(message);
10
9
  }
11
- function nonblank(document, node) {
12
- return rawSlice(document, node.span).trim().length > 0;
13
- }
14
10
  function compileSegment(segment, pattern) {
15
11
  if (segment === "**")
16
12
  return { kind: "deep" };
@@ -113,15 +109,94 @@ function patternsOverlap(left, right) {
113
109
  }
114
110
  return false;
115
111
  }
116
- export function decodeRegion(document, section) {
117
- const blocks = directChildren(section, "code_block");
118
- if (blocks.length !== 1 || !blocks[0].closed || (blocks[0].info !== "" && blocks[0].info !== "txt")) {
119
- refusal("Region must contain one closed fence with no info string or the exact 'txt' info string");
112
+ const THEMATIC_BREAK = /^ {0,3}(?:(?:-[ \t]*){3,}|(?:\*[ \t]*){3,}|(?:_[ \t]*){3,})$/u;
113
+ const TABLE_DELIMITER = /^ {0,3}\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/u;
114
+ function nonblank(line) {
115
+ return line.trim().length > 0;
116
+ }
117
+ /** The AST has no table, thematic-break, or HTML block kind, so the Region boundary classifies that prose itself. */
118
+ function foreignProseKind(lines) {
119
+ for (const line of lines) {
120
+ if (THEMATIC_BREAK.test(line))
121
+ return "thematic break";
122
+ if (/^ {0,3}</u.test(line))
123
+ return "HTML";
124
+ }
125
+ for (let index = 1; index < lines.length; index += 1) {
126
+ if (lines[index - 1].includes("|") && TABLE_DELIMITER.test(lines[index]))
127
+ return "table";
128
+ }
129
+ return null;
130
+ }
131
+ function nodeKind(node) {
132
+ return node.type.replaceAll("_", " ");
133
+ }
134
+ function article(kind) {
135
+ return kind === "HTML" || /^[aeiou]/iu.test(kind) ? "an" : "a";
136
+ }
137
+ function itemLines(item) {
138
+ const lines = [];
139
+ for (const child of item.children) {
140
+ if (child.type === "text")
141
+ lines.push(...child.lines);
142
+ else if (child.type === "list")
143
+ for (const nested of child.items)
144
+ lines.push(...itemLines(nested));
120
145
  }
121
- const other = section.children.filter((node) => node !== blocks[0] && nonblank(document, node));
122
- if (other.length > 0)
123
- refusal("Region may contain only its fenced declaration");
124
- const patterns = blocks[0].lines.slice(1, -1).filter((line) => line.trim().length > 0);
146
+ return lines.filter(nonblank);
147
+ }
148
+ /**
149
+ * Structural refusals are separate from pattern compilation so a caller may aggregate every independent failure.
150
+ * The three authoring forms — fenced lines, list items, and bare paragraph lines — union freely. Blank lines never
151
+ * count, and an info string beyond the exact `txt` still implies executable content that belongs to Verification.
152
+ */
153
+ export function regionStructure(section) {
154
+ for (const node of section.children) {
155
+ if (node.type === "code_block") {
156
+ if (!node.closed)
157
+ return "Region fence must be closed";
158
+ if (node.info !== "" && node.info !== "txt") {
159
+ return "Region fence may not carry an info string other than the exact 'txt'";
160
+ }
161
+ continue;
162
+ }
163
+ if (node.type === "list")
164
+ continue;
165
+ if (node.type === "text") {
166
+ const foreign = foreignProseKind(node.lines);
167
+ if (foreign !== null)
168
+ return `Region may not contain ${article(foreign)} ${foreign} block`;
169
+ continue;
170
+ }
171
+ const kind = nodeKind(node);
172
+ return `Region may not contain ${article(kind)} ${kind} block`;
173
+ }
174
+ return null;
175
+ }
176
+ function regionPatterns(section) {
177
+ const patterns = [];
178
+ for (const node of section.children) {
179
+ if (node.type === "code_block")
180
+ patterns.push(...node.lines.slice(1, -1).filter(nonblank));
181
+ else if (node.type === "list")
182
+ for (const item of node.items)
183
+ patterns.push(...itemLines(item));
184
+ else if (node.type === "text")
185
+ patterns.push(...node.lines.filter(nonblank));
186
+ }
187
+ return patterns;
188
+ }
189
+ /** A whitespace-bearing pattern is almost certainly strayed prose; bind warns and proceeds rather than rejecting. */
190
+ export function regionWarnings(patterns) {
191
+ return patterns
192
+ .filter((pattern) => /\s/u.test(pattern))
193
+ .map((pattern) => `Region pattern '${pattern}' contains whitespace and will never match a path`);
194
+ }
195
+ export function decodeRegion(_document, section) {
196
+ const structural = regionStructure(section);
197
+ if (structural !== null)
198
+ refusal(structural);
199
+ const patterns = regionPatterns(section);
125
200
  if (patterns.length === 0)
126
201
  refusal("Region must declare at least one path pattern");
127
202
  return patterns.map((pattern) => compileRegionPattern(pattern).source);
@@ -47,7 +47,7 @@ export function renderContractBody(body, currentArc) {
47
47
  section(CONTRACT_SECTIONS.context.title, body.context),
48
48
  section(CONTRACT_SECTIONS.objective.title, body.objective),
49
49
  section(CONTRACT_SECTIONS.design.title, body.design),
50
- section(CONTRACT_SECTIONS.region.title, fenced(body.region.join("\n"))),
50
+ section(CONTRACT_SECTIONS.region.title, body.region.join("\n")),
51
51
  section(CONTRACT_SECTIONS.criteria.title, criteria),
52
52
  ...(verification.length === 0 ? [] : [section(CONTRACT_SECTIONS.verification.title, verification)]),
53
53
  ...body.extensions.map((extension) => section(extension.title, extension.content)),
@@ -4,4 +4,6 @@ export type { VerificationDeclaration, VerificationExecutor } from "../verificat
4
4
  export declare class VerificationDocumentError extends Error {
5
5
  constructor(message: string);
6
6
  }
7
- export declare function decodeVerificationDeclarations(document: DocumentNode, section: SectionNode): readonly VerificationDeclaration[];
7
+ export declare function decodeVerificationDeclarations(document: DocumentNode, section: SectionNode, options?: Readonly<{
8
+ requireTimeout?: boolean;
9
+ }>): readonly VerificationDeclaration[];
@@ -6,7 +6,7 @@ export class VerificationDocumentError extends Error {
6
6
  this.name = "VerificationDocumentError";
7
7
  }
8
8
  }
9
- export function decodeVerificationDeclarations(document, section) {
9
+ export function decodeVerificationDeclarations(document, section, options = {}) {
10
10
  const blocks = directChildren(section, "code_block");
11
11
  if (blocks.length === 0)
12
12
  throw new VerificationDocumentError("Verification must contain one or more fenced executor declarations");
@@ -31,6 +31,9 @@ export function decodeVerificationDeclarations(document, section) {
31
31
  if (duration?.kind === "parsed" && duration.milliseconds === 0) {
32
32
  throw new VerificationDocumentError("Verification timeout must be positive");
33
33
  }
34
+ if (options.requireTimeout === true && duration === undefined) {
35
+ throw new VerificationDocumentError("Verification declarations must specify timeout=<duration>");
36
+ }
34
37
  return {
35
38
  executor: info[1],
36
39
  script,
@@ -24,6 +24,20 @@ function acceptedEnvelope(result, coordinate) {
24
24
  ...(result.executionStops.length === 0 ? {} : { executionStops: result.executionStops }),
25
25
  };
26
26
  }
27
+ /**
28
+ * The completed placement's movement identity — the reference it advanced and the head it advanced from — is part
29
+ * of the public accepted shape, so a receipt states the movement without rebuilding it from facts or folded state.
30
+ */
31
+ function acceptedCompletion(completion) {
32
+ if (completion === undefined)
33
+ return undefined;
34
+ return {
35
+ integration: completion.integration,
36
+ ...(completion.predecessor === undefined ? {} : { predecessor: completion.predecessor }),
37
+ ...(completion.target === undefined ? {} : { target: completion.target }),
38
+ ...(completion.verification === undefined ? {} : { verification: completion.verification }),
39
+ };
40
+ }
27
41
  function acceptedRegion(result) {
28
42
  if (result.overlapFailure !== undefined)
29
43
  return { overlapFailure: result.overlapFailure };
@@ -42,6 +56,7 @@ export function acceptedBind(result, coordinates) {
42
56
  verb: "bind",
43
57
  ...(result.workspace === undefined ? {} : { workspace: result.workspace }),
44
58
  target: coordinates.target ?? null,
59
+ ...(result.warnings === undefined ? {} : { warnings: result.warnings }),
45
60
  ...acceptedRegion(result),
46
61
  };
47
62
  }
@@ -56,6 +71,7 @@ export function acceptedAmend(result, coordinate) {
56
71
  export function acceptedDeliver(result, coordinate) {
57
72
  const value = result.value;
58
73
  const attestation = attestationFor(result.facts, "verified", coordinate);
74
+ const completion = acceptedCompletion(value.completion);
59
75
  const verificationVerdict = value.completion?.verification?.verdict ?? attestation?.data.verdict ?? value.verificationReuse?.verdict;
60
76
  return {
61
77
  ...acceptedEnvelope(result, coordinate),
@@ -63,7 +79,7 @@ export function acceptedDeliver(result, coordinate) {
63
79
  tenderSnapshot: value.tenderSnapshot,
64
80
  integration: { changeId: value.integration.changeId },
65
81
  ...(value.leading === undefined ? {} : { leading: value.leading }),
66
- ...(value.completion === undefined ? {} : { completion: value.completion }),
82
+ ...(completion === undefined ? {} : { completion }),
67
83
  ...(verificationVerdict === undefined ? {} : { verificationVerdict }),
68
84
  ...(value.verification === undefined ? {} : { verification: value.verification }),
69
85
  ...(value.verificationReuse === undefined ? {} : { verificationReuse: value.verificationReuse }),
@@ -78,6 +94,7 @@ export function acceptedReview(result, coordinate) {
78
94
  if (reviewAttestation === undefined)
79
95
  throw new Error("accepted review is missing its attestation fact");
80
96
  const verificationAttestation = attestationFor(result.facts, "verified", coordinate);
97
+ const completion = acceptedCompletion(value.completion);
81
98
  const verificationVerdict = value.completion?.verification?.verdict ??
82
99
  verificationAttestation?.data.verdict ??
83
100
  value.verificationReuse?.verdict;
@@ -85,7 +102,7 @@ export function acceptedReview(result, coordinate) {
85
102
  ...acceptedEnvelope(result, coordinate),
86
103
  verb: "review",
87
104
  verdict: reviewAttestation.data.verdict,
88
- ...(value.completion === undefined ? {} : { completion: value.completion }),
105
+ ...(completion === undefined ? {} : { completion }),
89
106
  ...(verificationVerdict === undefined ? {} : { verificationVerdict }),
90
107
  ...(value.verification === undefined ? {} : { verification: value.verification }),
91
108
  ...(value.verificationReuse === undefined ? {} : { verificationReuse: value.verificationReuse }),
@@ -11,6 +11,7 @@ export type AkumaInvocationResult = Readonly<{
11
11
  result: CallResult;
12
12
  world: WorldRoot;
13
13
  schemaAnswer?: unknown;
14
+ streamed?: boolean;
14
15
  }> | Readonly<{
15
16
  kind: "akuma";
16
17
  action: "status";
@@ -21,6 +22,7 @@ export type AkumaInvocationResult = Readonly<{
21
22
  action: "wait";
22
23
  result: AkumaWaitResult;
23
24
  alias?: string;
25
+ streamed?: boolean;
24
26
  }> | Readonly<{
25
27
  kind: "akuma";
26
28
  action: "tell";
@@ -1,11 +1,65 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { Keiyaku, } from "../../index.js";
2
+ import { squareAssignedParticipantName } from "@astrosheep/square";
3
+ import { emitInitiatingPluginSignal } from "../../plugin/akuma-signals.js";
4
+ import { observeAkumaStatus } from "../../akuma/akuma-observe.js";
5
+ import { AuthorityCorruptionError, Keiyaku, } from "../../index.js";
6
+ import { callObservationStream, waitObservationStream } from "../render/akuma-activity.js";
7
+ import { callObservationHead } from "../render/akuma.js";
3
8
  import { killAkuma, tellAkuma, waitAkuma } from "../../library/fleet.js";
4
9
  import { localExecutionContext } from "../../akuma/requests.js";
5
10
  import { Akuma, Schema } from "../../akuma/index.js";
6
11
  import { addressAkuma } from "../../library/address.js";
7
12
  import { executionChannel } from "../../akuma/requests.js";
8
13
  import { requestForwardedFleetTellAnswer } from "../../akuma/fleet-request.js";
14
+ /** The window a call observes for when the caller gives no `--wait` duration. */
15
+ const CALL_TIMEOUT_MS = 300_000;
16
+ function integrationFailure(error) {
17
+ return {
18
+ kind: error instanceof AuthorityCorruptionError ? "authority-corruption" : "infrastructure",
19
+ diagnostic: error instanceof Error ? error.message : String(error),
20
+ };
21
+ }
22
+ /** The display context the command result renders with, so live progress rows match the result. */
23
+ function resultContext() {
24
+ const tty = process.stderr.isTTY === true;
25
+ return {
26
+ columns: tty && Number.isInteger(process.stderr.columns) ? process.stderr.columns : 80,
27
+ color: tty && process.env.NO_COLOR === undefined,
28
+ };
29
+ }
30
+ function writeProgress(body) {
31
+ process.stderr.write(body.endsWith("\n") ? body : `${body}\n`);
32
+ }
33
+ /**
34
+ * The live half of an observe-mode call: the resolved birth opens one identity
35
+ * frame (never the detached receipt's cwd row), each later observation prints
36
+ * the settled rows that arrived since the previous one, and the return appends
37
+ * one conclusion. The stream is append-only, so no final snapshot replays.
38
+ */
39
+ async function observeCallUntilComplete(command, input, born) {
40
+ const stream = callObservationStream(resultContext(), callObservationHead(born));
41
+ try {
42
+ const status = await observeAkumaStatus(input.path, born.akuma, {
43
+ timeoutMs: command.timeoutMs ?? CALL_TIMEOUT_MS,
44
+ observe: (observed) => {
45
+ const lines = stream.observe(observed);
46
+ if (lines.length > 0)
47
+ writeProgress(lines.join("\n"));
48
+ },
49
+ });
50
+ const conclusion = stream.conclude({ kind: "observed", status });
51
+ if (conclusion.length > 0)
52
+ writeProgress(conclusion);
53
+ return { kind: "observed", status };
54
+ }
55
+ catch (error) {
56
+ const failure = integrationFailure(error);
57
+ const conclusion = stream.conclude({ kind: "failed", failure });
58
+ if (conclusion.length > 0)
59
+ writeProgress(conclusion);
60
+ return { kind: "failed", failure };
61
+ }
62
+ }
9
63
  async function schemaFromFile(path) {
10
64
  try {
11
65
  const parsed = JSON.parse(await readFile(path, "utf8"));
@@ -21,21 +75,73 @@ function inputAlias(selector) {
21
75
  async function promptBody(command, input) {
22
76
  return command.prompt.kind === "stdin" ? await input.readStdin() : command.prompt.value;
23
77
  }
78
+ /**
79
+ * The live half of a local wait: each observation round opens every newly seen
80
+ * Akuma with its identity frame, then prints only the rows that settled since
81
+ * the previous round. The first sighting of an Akuma only establishes its
82
+ * baseline, so an already settled Akuma prints no backlog rows. When the wait
83
+ * ends, `conclude` prints its closing scoreboard on the same channel.
84
+ */
85
+ function waitObserver(stream) {
86
+ return {
87
+ selected: (selected) => stream.select(selected),
88
+ observe: (observed) => {
89
+ const lines = stream.observe(observed);
90
+ if (lines.length > 0)
91
+ writeProgress(lines.join("\n"));
92
+ },
93
+ };
94
+ }
24
95
  async function invokeWait(command, input) {
25
96
  const alias = command.akuma.length === 1 ? inputAlias(command.akuma[0]) : undefined;
97
+ const stream = command.output === "text" ? waitObservationStream(resultContext()) : undefined;
98
+ const result = await waitAkuma({
99
+ path: input.path,
100
+ akuma: command.akuma,
101
+ ...(input.repo === undefined ? {} : { repo: input.repo }),
102
+ ...(command.completion === undefined ? {} : { completion: command.completion }),
103
+ ...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
104
+ }, input.execution ?? localExecutionContext(), stream === undefined ? undefined : waitObserver(stream));
105
+ if (stream !== undefined && stream.streamed()) {
106
+ const closing = stream.conclude(result);
107
+ if (closing.length > 0)
108
+ writeProgress(closing);
109
+ return {
110
+ kind: "akuma",
111
+ action: "wait",
112
+ result,
113
+ streamed: true,
114
+ ...(alias === undefined ? {} : { alias }),
115
+ };
116
+ }
26
117
  return {
27
118
  kind: "akuma",
28
119
  action: "wait",
29
- result: await waitAkuma({
30
- path: input.path,
31
- akuma: command.akuma,
32
- ...(input.repo === undefined ? {} : { repo: input.repo }),
33
- ...(command.completion === undefined ? {} : { completion: command.completion }),
34
- ...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
35
- }, input.execution ?? localExecutionContext()),
120
+ result,
36
121
  ...(alias === undefined ? {} : { alias }),
37
122
  };
38
123
  }
124
+ async function inputInitiator(input) {
125
+ let initiator;
126
+ try {
127
+ initiator = squareAssignedParticipantName(input.environment);
128
+ }
129
+ catch { }
130
+ if (initiator === undefined)
131
+ return {};
132
+ try {
133
+ await emitInitiatingPluginSignal({
134
+ world: input.path,
135
+ ...(input.settings === undefined ? {} : { settings: input.settings }),
136
+ initiator,
137
+ reportDiagnostic: (message) => process.stderr.write(`${message}\n`),
138
+ });
139
+ }
140
+ catch (error) {
141
+ process.stderr.write(`! plugin initiation: ${error instanceof Error ? error.message : String(error)}\n`);
142
+ }
143
+ return { initiator };
144
+ }
39
145
  async function invokeTell(command, input) {
40
146
  const body = await promptBody(command, input);
41
147
  if (command.schema !== undefined) {
@@ -45,6 +151,7 @@ async function invokeTell(command, input) {
45
151
  akuma: command.akuma,
46
152
  ...(input.repo === undefined ? {} : { repo: input.repo }),
47
153
  });
154
+ const initiator = await inputInitiator(input);
48
155
  const channel = executionChannel(input.execution);
49
156
  const answer = channel.kind === "body-request"
50
157
  ? await requestForwardedFleetTellAnswer({
@@ -53,9 +160,11 @@ async function invokeTell(command, input) {
53
160
  body,
54
161
  schema,
55
162
  interrupt: command.interrupt,
163
+ ...initiator,
56
164
  })
57
165
  : await Akuma.select(addressed.path, addressed.id).tell(body, {
58
166
  schema,
167
+ ...initiator,
59
168
  ...(command.interrupt ? { interrupt: true } : {}),
60
169
  });
61
170
  const alias = inputAlias(command.akuma);
@@ -68,8 +177,10 @@ async function invokeTell(command, input) {
68
177
  ...(alias === undefined ? {} : { alias }),
69
178
  };
70
179
  }
180
+ const initiator = await inputInitiator(input);
71
181
  if (command.interrupt) {
72
182
  const result = await Keiyaku.interrupt({
183
+ ...initiator,
73
184
  path: input.path,
74
185
  akuma: command.akuma,
75
186
  body,
@@ -86,6 +197,7 @@ async function invokeTell(command, input) {
86
197
  };
87
198
  }
88
199
  const result = await tellAkuma({
200
+ ...initiator,
89
201
  path: input.path,
90
202
  akuma: command.akuma,
91
203
  body,
@@ -151,24 +263,32 @@ export async function invokeAkuma(command, input) {
151
263
  const body = await promptBody(command, input);
152
264
  const schema = command.schema === undefined ? undefined : await schemaFromFile(command.schema);
153
265
  const caller = input.execution === undefined ? Keiyaku : Keiyaku.withExecution({ execution: input.execution });
154
- const result = await caller.call({
266
+ const request = {
267
+ ...(await inputInitiator(input)),
155
268
  path: input.path,
156
269
  archetype: command.archetype,
157
270
  body,
158
271
  ...(input.home === undefined ? {} : { home: input.home }),
159
272
  ...(input.settings === undefined ? {} : { settings: input.settings }),
160
273
  ...(input.executionCwd === undefined ? {} : { cwd: input.executionCwd }),
161
- mode: command.mode,
162
- ...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
163
274
  ...(input.contract === undefined ? {} : { contract: input.contract }),
164
275
  ...(command.alias === undefined ? {} : { alias: command.alias }),
165
276
  ...(command.allowed === undefined ? {} : { allowed: command.allowed }),
166
277
  ...(schema === undefined ? {} : { schema }),
167
- });
278
+ };
279
+ const observing = schema === undefined && command.mode === "wait" && command.output === "text";
280
+ const born = await caller.call(observing
281
+ ? { ...request, mode: "detach" }
282
+ : {
283
+ ...request,
284
+ mode: command.mode,
285
+ ...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
286
+ });
287
+ const result = observing ? { ...born, observation: await observeCallUntilComplete(command, input, born) } : born;
168
288
  if (schema !== undefined && command.mode === "wait" && result.schemaAnswer !== undefined) {
169
289
  return { kind: "akuma", action: "call", result, world: input.path, schemaAnswer: result.schemaAnswer };
170
290
  }
171
- return { kind: "akuma", action: "call", result, world: input.path };
291
+ return { kind: "akuma", action: "call", result, world: input.path, ...(observing ? { streamed: true } : {}) };
172
292
  }
173
293
  case "wait":
174
294
  return await invokeWait(command, input);
@@ -32,6 +32,7 @@ const AKUMA_COMMAND_SPECS = {
32
32
  "A nested birth is clipped to its direct parent's frozen actions. status <aku/...|@alias> shows the born effective set.",
33
33
  "--schema reads a JSON Schema file for the answer contract; stdin remains the prompt source.",
34
34
  "With --contract, Dispatch succeeds first. If @name exists, the alias then moves.",
35
+ "While it waits, settled timeline rows stream on stderr; the final answer is written to stdout once.",
35
36
  ].join("\n"),
36
37
  },
37
38
  wait: {
@@ -39,7 +40,16 @@ const AKUMA_COMMAND_SPECS = {
39
40
  stdin: false,
40
41
  flags: { any: "boolean", all: "boolean", timeout: "value", json: "boolean" },
41
42
  usage: "wait <akuma-selector>... [--any | --all] [--timeout <duration>]",
42
- purpose: "Wait for one Akuma or an explicitly selected Akuma set.",
43
+ purpose: "Wait for one Akuma or a selected Akuma set.",
44
+ details: [
45
+ "Without --any or --all the mode is any: the wait returns when any selected Akuma completes.",
46
+ "--all waits until every selected Akuma completes.",
47
+ "An already completed member counts immediately, so repeating a selection can return at once.",
48
+ "Identity frames, settled rows, and the closing scoreboard stream on stderr.",
49
+ "A streamed plural wait leaves stdout empty, and so does a single wait with no answer.",
50
+ "Only a single selected Akuma that answered writes to stdout, exactly its answer.",
51
+ "With --json nothing streams and stdout carries the result document.",
52
+ ].join("\n"),
43
53
  },
44
54
  tell: {
45
55
  arity: 1,
@@ -199,9 +209,6 @@ function validateSet(value, fail) {
199
209
  function parseWait(rawSelectors, flags, output, fail) {
200
210
  if (flags.any === true && flags.all === true)
201
211
  fail("wait --any and --all are mutually exclusive");
202
- if (rawSelectors.length > 1 && flags.any !== true && flags.all !== true) {
203
- fail("wait requires --any or --all when selecting multiple Akuma");
204
- }
205
212
  const completion = flags.any === true ? "any" : flags.all === true ? "all" : undefined;
206
213
  return {
207
214
  command: "wait",
@@ -15,18 +15,26 @@ export const CONTRACT_COMMAND_SPECS = {
15
15
  usage: "bind [--task <task/...>] [--target <ref>] [--after <kei/...> ...] [--gates <name,...>] [--actor <actor>] - | bind --fork-of <kei/...> [--target <ref>] [--actor <actor>]",
16
16
  purpose: "Create one Contract from stdin Markdown or a sibling fork.",
17
17
  details: [
18
+ "--gates <name,...> accepts gate words and configured bundle names together.",
19
+ "A matching bundle expands; otherwise the name is a literal gate. Duplicates",
20
+ "are removed in first-seen order. Gate words match ^[a-z][a-z0-9-]{0,63}$.",
21
+ "Omitting --gates selects gates.default, or reviewed when no default exists.",
22
+ '--gates "" explicitly binds without gates; --gates reviewed needs no bundle.',
23
+ "",
18
24
  "stdin is Contract Markdown:",
19
25
  " # <title>",
20
26
  " ## Context motivation, authority, baseline, and boundaries",
21
27
  " ## Objective one observable end state",
22
28
  " ## Design closed decisions and ordering",
23
- " ## Region one fenced path-pattern declaration",
29
+ " ## Region intended path patterns as fenced lines, list items, or bare lines",
24
30
  " ## Criteria one or more ### <criterion> entries",
25
31
  " ## Verification optional fenced executor declarations",
26
32
  "",
27
- "Region uses one closed fence with no info string or exactly 'txt'; put one",
28
- "repository-relative path pattern on each nonblank line. A trailing '/'",
29
- "is directory shorthand for 'path/**'.",
33
+ "Region accepts intended path patterns as fenced lines, list items, or bare",
34
+ "lines; the three forms union. Put one repository-relative path pattern on",
35
+ "each nonblank line. A trailing '/' is directory shorthand for 'path/**'.",
36
+ "A pattern containing whitespace binds with a warning. A fence carries no",
37
+ "info string or exactly 'txt'.",
30
38
  "",
31
39
  "Verification uses one or more closed bash, zsh, or pwsh fences. Its fence",
32
40
  "may add timeout=<integer><ms|s|m|h>; the section contains no other prose.",
@@ -40,6 +48,11 @@ export const CONTRACT_COMMAND_SPECS = {
40
48
  usage: "amend [<contract>|@<contract>] [--after <kei/...> ... | --clear-after] [--gates <name,...>] [--actor <actor>] [-]",
41
49
  purpose: "Amend one Contract's document operations or structured terms.",
42
50
  details: [
51
+ "--gates <name,...> replaces gates using gate words and configured bundle names.",
52
+ "A matching bundle expands; otherwise the name is a literal gate. Duplicates",
53
+ "are removed in first-seen order. Gate words match ^[a-z][a-z0-9-]{0,63}$.",
54
+ 'Omitting --gates leaves gates unchanged; --gates "" clears them.',
55
+ "",
43
56
  " ## Context|Objective|Design|Region|Criteria|Verification|<extension> (replace, or add new extension)",
44
57
  " ## Replace: Context|Objective|Design|Region|Criteria|Verification|<extension>",
45
58
  " ## Append: Context|Objective|Design|Criteria|<extension>",
@@ -20,13 +20,15 @@ function optionalFlag(flags, name) {
20
20
  function refuse(command, message) {
21
21
  throw new CliUsageError(message, commandGuide(command, CONTRACT_COMMAND_SPECS[command].usage));
22
22
  }
23
- function parseGateBundleNames(parts, command) {
23
+ function parseGateNames(parts, command) {
24
24
  const value = optionalFlag(parts.flags, "gates");
25
25
  if (value === undefined)
26
26
  return undefined;
27
+ if (value === "")
28
+ return [];
27
29
  const names = value.split(",");
28
30
  if (names.some((name) => name.length === 0)) {
29
- refuse(command, "--gates requires nonempty comma-separated names");
31
+ refuse(command, '--gates requires comma-separated names or "" to clear gates');
30
32
  }
31
33
  return names;
32
34
  }
@@ -53,7 +55,7 @@ function parseBind(parts) {
53
55
  const task = optionalFlag(parts.flags, "task");
54
56
  const target = optionalFlag(parts.flags, "target");
55
57
  const after = parts.flags.after === undefined ? [] : Array.isArray(parts.flags.after) ? parts.flags.after : [parts.flags.after];
56
- const gates = parseGateBundleNames(parts, "bind");
58
+ const gates = parseGateNames(parts, "bind");
57
59
  return {
58
60
  command: "bind",
59
61
  ...(task === undefined ? {} : { task }),
@@ -69,7 +71,7 @@ function parseAmend(parts) {
69
71
  const after = parts.flags.after === undefined ? [] : Array.isArray(parts.flags.after) ? parts.flags.after : [parts.flags.after];
70
72
  if (parts.flags["clear-after"] === true && after.length > 0)
71
73
  refuse("amend", "--clear-after and --after are mutually exclusive");
72
- const gates = parseGateBundleNames(parts, "amend");
74
+ const gates = parseGateNames(parts, "amend");
73
75
  if (!parts.stdin && parts.flags.after === undefined && parts.flags["clear-after"] !== true && gates === undefined) {
74
76
  refuse("amend", "amend requires stdin or --after, --clear-after, or --gates");
75
77
  }
@@ -13,33 +13,25 @@ async function statusSetAkuma(selector, id, alias, world, repo) {
13
13
  ...(result.alias === undefined ? {} : { alias: result.alias }),
14
14
  };
15
15
  }
16
- async function statusSetEntry(selector, world, repo, observed) {
17
- let resolved = selector;
18
- let alias;
16
+ async function statusSetContract(selector, id, world, repo) {
17
+ if (repo === undefined)
18
+ throw new CliUsageError("cannot select a contract while the Contract world is absent");
19
+ const observation = await (await import("../../kanshi/index.js")).observeKanshi({ world, repo, contract: canonicalContractSelector(id) });
20
+ return { selector, kind: "contract", report: observation.report };
21
+ }
22
+ async function statusSetEntry(selector, world, repo, named) {
19
23
  if (selector.startsWith("@")) {
20
- if (observed === undefined)
24
+ if (named === undefined)
21
25
  throw new CliUsageError("cannot resolve a named status selector");
22
26
  const { resolveNamedAddress } = await import("../../library/address.js");
23
- const address = resolveNamedAddress({ selector, report: observed.report, aliases: observed.aliases });
27
+ const address = resolveNamedAddress({ selector, report: named.report, aliases: named.aliases });
24
28
  if (address.kind === "akuma")
25
29
  return await statusSetAkuma(selector, address.id, selector, world, repo);
26
- resolved = address.id;
27
- alias = selector;
30
+ return await statusSetContract(selector, address.id, world, repo);
28
31
  }
29
- if (resolved.startsWith("aku/"))
30
- return await statusSetAkuma(selector, resolved, alias, world, repo);
31
- if (repo === undefined)
32
- throw new CliUsageError("cannot select a contract while the Contract world is absent");
33
- if (observed === undefined)
34
- throw new CliUsageError("cannot observe Contract status without a Kanshi report");
35
- return {
36
- selector,
37
- kind: "contract",
38
- report: (await import("../../kanshi/index.js")).selectKanshi({
39
- report: observed.report,
40
- contract: canonicalContractSelector(resolved),
41
- }),
42
- };
32
+ if (selector.startsWith("aku/"))
33
+ return await statusSetAkuma(selector, selector, undefined, world, repo);
34
+ return await statusSetContract(selector, selector, world, repo);
43
35
  }
44
36
  export async function invokeStatusSet(selectors, world, repo) {
45
37
  if (selectors.length < 2)
@@ -47,8 +39,7 @@ export async function invokeStatusSet(selectors, world, repo) {
47
39
  if (selectors.some((selector) => !selector.startsWith("aku/") && !selector.startsWith("@")) && repo === undefined) {
48
40
  throw new CliUsageError("cannot select a contract while the Contract world is absent");
49
41
  }
50
- const needsKanshi = selectors.some((selector) => !selector.startsWith("aku/"));
51
- const observed = needsKanshi
42
+ const named = selectors.some((selector) => selector.startsWith("@"))
52
43
  ? await (await import("../../kanshi/index.js")).observeKanshi({
53
44
  world,
54
45
  ...(repo === undefined ? {} : { repo }),
@@ -56,6 +47,6 @@ export async function invokeStatusSet(selectors, world, repo) {
56
47
  : undefined;
57
48
  const entries = [];
58
49
  for (const selector of selectors)
59
- entries.push(await statusSetEntry(selector, world, repo, observed));
50
+ entries.push(await statusSetEntry(selector, world, repo, named));
60
51
  return { kind: "status-set", entries };
61
52
  }
@@ -1,4 +1,5 @@
1
1
  import { CliUsageError, parseArgv, renderHelp } from "./parse.js";
2
+ import { installedPackageVersion } from "./version.js";
2
3
  export async function main(argv = process.argv.slice(2)) {
3
4
  try {
4
5
  const parsed = parseArgv(argv);
@@ -8,6 +9,10 @@ export async function main(argv = process.argv.slice(2)) {
8
9
  process.stdout.write(help.endsWith("\n") ? help : `${help}\n`);
9
10
  return 0;
10
11
  }
12
+ if ("version" in parsed) {
13
+ process.stdout.write(`${await installedPackageVersion()}\n`);
14
+ return 0;
15
+ }
11
16
  return await (await import("./runtime.js")).runCliCommand(parsed);
12
17
  }
13
18
  catch (error) {