@astrosheep/keiyaku 1.0.0 → 1.0.2

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 (123) hide show
  1. package/README.md +73 -56
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms_v2.js +103 -0
  4. package/build/agents/harness/activity-values.js +54 -0
  5. package/build/agents/harness/event-channel.js +55 -20
  6. package/build/agents/harness/events.js +47 -1
  7. package/build/agents/harness/execution-handle.js +292 -61
  8. package/build/agents/harness/index.js +103 -121
  9. package/build/agents/harness/pump.js +36 -47
  10. package/build/agents/harness/runtime.js +3 -0
  11. package/build/agents/launch-snapshot_v2.js +348 -0
  12. package/build/agents/opencode-sdk.js +42 -169
  13. package/build/agents/providers/claude-agent-sdk.js +240 -79
  14. package/build/agents/providers/codex-app-server.js +280 -72
  15. package/build/agents/providers/codex-sdk.js +159 -41
  16. package/build/agents/providers/event-type-tail.js +16 -0
  17. package/build/agents/providers/opencode-sdk.js +165 -59
  18. package/build/agents/providers/pi.js +254 -42
  19. package/build/agents/selector_v2.js +122 -0
  20. package/build/cli/commands/akuma.js +120 -42
  21. package/build/cli/completion.js +6 -7
  22. package/build/cli/flags.js +36 -10
  23. package/build/cli/help.js +42 -26
  24. package/build/cli/index.js +225 -93
  25. package/build/cli/parse.js +194 -75
  26. package/build/cli/projection-address.js +11 -0
  27. package/build/cli/render/address.js +88 -0
  28. package/build/cli/render/arc.js +10 -10
  29. package/build/cli/render/call.js +59 -0
  30. package/build/cli/render/compact-text.js +3 -0
  31. package/build/cli/render/errors.js +4 -2
  32. package/build/cli/render/format.js +6 -0
  33. package/build/cli/render/line-width.js +70 -0
  34. package/build/cli/render/misc.js +7 -4
  35. package/build/cli/render/petition.js +4 -3
  36. package/build/cli/render/projection-activity.js +201 -0
  37. package/build/cli/render/shared.js +46 -12
  38. package/build/cli/render/status.js +59 -10
  39. package/build/cli/render/tool-ledger-rollup.js +74 -0
  40. package/build/cli/render/tool-presentation.js +97 -0
  41. package/build/cli/render/wait.js +229 -0
  42. package/build/cli/skills-install.js +42 -24
  43. package/build/cli/subagent-guard.js +1 -1
  44. package/build/config/akuma-loader_v2.js +222 -0
  45. package/build/config/env-keys.js +0 -4
  46. package/build/config/env.js +0 -6
  47. package/build/config/settings.js +325 -12
  48. package/build/core/addressing.js +224 -0
  49. package/build/core/amend.js +30 -47
  50. package/build/core/arc.js +68 -60
  51. package/build/core/bind.js +105 -25
  52. package/build/core/call-persist.js +69 -0
  53. package/build/core/call.js +658 -0
  54. package/build/core/claim.js +72 -44
  55. package/build/core/command-io.js +19 -19
  56. package/build/core/context.js +11 -36
  57. package/build/core/draft.js +2 -3
  58. package/build/core/entry.js +101 -6
  59. package/build/core/execution-coordinate.js +62 -0
  60. package/build/core/execution-pact.js +141 -0
  61. package/build/core/forfeit.js +2 -3
  62. package/build/core/hints.js +10 -10
  63. package/build/core/ids.js +6 -0
  64. package/build/core/ledger.js +16 -1
  65. package/build/core/petition-claim-gates.js +14 -12
  66. package/build/core/petition-claim.js +1 -0
  67. package/build/core/petition-run.js +6 -2
  68. package/build/core/petition.js +63 -21
  69. package/build/core/places.js +106 -13
  70. package/build/core/projection/generation.js +412 -0
  71. package/build/core/projection/heart.js +316 -0
  72. package/build/core/projection/identity.js +81 -0
  73. package/build/core/projection/leash.js +87 -0
  74. package/build/core/projection/mint.js +154 -0
  75. package/build/core/projection-activity.js +219 -0
  76. package/build/core/projection-coordinate.js +35 -0
  77. package/build/core/projection-core.js +69 -463
  78. package/build/core/projection-generation-continuation.js +31 -0
  79. package/build/core/projection-generation-execution.js +148 -0
  80. package/build/core/projection-generation-identity.js +17 -0
  81. package/build/core/projection-generation-launcher.js +90 -0
  82. package/build/core/projection-generation-process.js +43 -0
  83. package/build/core/projection-generation-runner.js +144 -0
  84. package/build/core/projection-generation-runtime.js +15 -0
  85. package/build/core/projection-generation-store.js +707 -0
  86. package/build/core/projection-identity.js +11 -0
  87. package/build/core/projection-life-observer.js +87 -0
  88. package/build/core/projection-life-protocol.js +95 -0
  89. package/build/core/projection-mint.js +96 -24
  90. package/build/core/projection-runner-lock.js +230 -0
  91. package/build/core/projection-status.js +207 -107
  92. package/build/core/projection-tells.js +401 -0
  93. package/build/core/projection-wait.js +155 -0
  94. package/build/core/projection-wake.js +109 -231
  95. package/build/core/queue_v2.js +342 -0
  96. package/build/core/registry.js +6 -3
  97. package/build/core/render.js +63 -1
  98. package/build/core/renew.js +80 -53
  99. package/build/core/scope.js +186 -137
  100. package/build/core/seal.js +16 -19
  101. package/build/core/status.js +88 -25
  102. package/build/core/stored-agent-event.js +101 -0
  103. package/build/core/transcripts.js +214 -228
  104. package/build/core/verdict.js +27 -15
  105. package/build/core/worktree-bootstrap.js +187 -0
  106. package/build/core/worktree-path.js +72 -18
  107. package/build/flow-error.js +4 -6
  108. package/build/generated/version.js +1 -1
  109. package/build/git/branches.js +6 -1
  110. package/build/index.js +8 -5
  111. package/build/keiyaku.js +1 -3
  112. package/package.json +6 -5
  113. package/skills/keiyaku/SKILL.md +6 -47
  114. package/skills/keiyaku-akuma/SKILL.md +75 -0
  115. package/build/agents/effective-policy.js +0 -32
  116. package/build/agents/index.js +0 -114
  117. package/build/agents/selector.js +0 -28
  118. package/build/cli/render/summon.js +0 -17
  119. package/build/cli/render/tell.js +0 -11
  120. package/build/config/akuma-loader.js +0 -268
  121. package/build/core/queue.js +0 -73
  122. package/build/core/summon-persist.js +0 -73
  123. package/build/core/summon.js +0 -377
@@ -1,8 +1,13 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { agentNameSchema } from "../config/settings.js";
3
3
  import { BOOLEAN_FLAGS, CONTROL_FLAGS, HELP_FLAGS, SHORT_FLAG_ALIASES, VALUE_FLAGS, } from "./flags.js";
4
- import { EFFORT_LEVELS } from "../keiyaku.js";
5
- const ADDRESSABLE_COMMANDS = new Set([
4
+ /** Commands that may carry an explicit commission selector (@ or --contract). */
5
+ const COMMISSION_SELECTOR_COMMANDS = new Set([
6
+ "arc",
7
+ "amend",
8
+ "call",
9
+ "wait",
10
+ "tell",
6
11
  "log",
7
12
  "renew",
8
13
  "petition",
@@ -11,9 +16,9 @@ const ADDRESSABLE_COMMANDS = new Set([
11
16
  const CLI_COMMAND_ORDER = [
12
17
  "bind",
13
18
  "arc",
14
- "summon",
15
- "ask",
16
- "resume",
19
+ "call",
20
+ "revive",
21
+ "wait",
17
22
  "tell",
18
23
  "log",
19
24
  "akuma list",
@@ -32,26 +37,27 @@ const CLI_COMMAND_ORDER = [
32
37
  "dump-env",
33
38
  ];
34
39
  const COMMAND_FLAGS = {
35
- bind: ["cwd", "worktree", "after", "exclusive", "draft-only"],
36
- arc: ["cwd"],
37
- summon: ["cwd", "agent", "model", "effort", "incognito", "bare"],
38
- ask: ["cwd", "agent", "model", "effort", "incognito", "bare"],
39
- resume: ["cwd", "model", "effort", "projection"],
40
- tell: ["cwd", "effort"],
41
- log: ["cwd"],
40
+ // contract is parseable on bind only so the mouth can reject it with bind-specific copy.
41
+ bind: ["cwd", "repo", "contract", "after", "exclusive", "draft-only"],
42
+ arc: ["cwd", "repo", "contract", "waive"],
43
+ call: ["cwd", "repo", "contract", "akuma", "model", "effort", "incognito", "bare", "detach"],
44
+ revive: ["cwd", "repo", "model", "effort"],
45
+ wait: ["cwd", "repo", "contract", "timeout"],
46
+ tell: ["cwd", "repo", "contract", "effort"],
47
+ log: ["cwd", "repo", "contract"],
42
48
  akuma: [],
43
- "akuma ls": ["cwd"],
44
- "akuma list": ["cwd"],
45
- "akuma show": ["cwd"],
49
+ "akuma ls": ["cwd", "repo"],
50
+ "akuma list": ["cwd", "repo"],
51
+ "akuma show": ["cwd", "repo"],
46
52
  skills: [],
47
53
  "skills install": ["force"],
48
- amend: ["cwd"],
49
- renew: ["cwd"],
50
- petition: ["cwd"],
51
- forfeit: ["cwd", "reason"],
54
+ amend: ["cwd", "repo", "contract"],
55
+ renew: ["cwd", "repo", "contract", "waive"],
56
+ petition: ["cwd", "repo", "contract", "waive"],
57
+ forfeit: ["cwd", "repo", "contract", "reason"],
52
58
  guide: [],
53
- completion: ["cwd", "shell", "word", "previous", "complete"],
54
- status: ["cwd"],
59
+ completion: ["cwd", "repo", "shell", "word", "previous", "complete"],
60
+ status: ["cwd", "repo", "target"],
55
61
  "dump-env": [],
56
62
  };
57
63
  const STDIN_COMMANDS = new Set(["bind", "arc", "amend"]);
@@ -62,22 +68,37 @@ export function normalizeContractAddress(raw) {
62
68
  }
63
69
  return value.trim();
64
70
  }
65
- export function isSummonCommand(command) {
66
- return command === "summon" || command === "ask";
71
+ export function isCallCommand(command) {
72
+ return command === "call";
67
73
  }
68
74
  export function commandUsesPromptInput(command) {
69
- return isSummonCommand(command) || command === "resume";
75
+ return isCallCommand(command) || command === "revive";
76
+ }
77
+ export function parseWaitTimeout(value) {
78
+ const matched = /^(?<amount>[1-9][0-9]*)(?<unit>[smh])$/.exec(value);
79
+ if (!matched?.groups) {
80
+ throw new FlowError("EMPTY_PARAM", "wait timeout must use <positive integer><s|m|h>, for example 45s, 5m, or 2h (maximum 24h)");
81
+ }
82
+ const amount = Number(matched.groups.amount);
83
+ const multiplier = matched.groups.unit === "s" ? 1_000 : matched.groups.unit === "m" ? 60_000 : 3_600_000;
84
+ const timeoutMs = amount * multiplier;
85
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs > 24 * 3_600_000) {
86
+ throw new FlowError("EMPTY_PARAM", "wait timeout must use <positive integer><s|m|h>, for example 45s, 5m, or 2h (maximum 24h)");
87
+ }
88
+ return timeoutMs;
70
89
  }
71
90
  export function commandPositionalRange(command) {
72
91
  if (command === "akuma show")
73
92
  return { min: 1, max: 1, label: "<name>" };
74
93
  if (command === "log")
75
- return { min: 0, max: 1, label: "[@id]" };
76
- if (command === "resume")
77
- return { min: 0, max: 2, label: "<response-path>" };
94
+ return { min: 0, max: 0, label: "" };
95
+ if (command === "revive")
96
+ return { min: 1, max: 1, label: "<artifact-id>" };
97
+ if (command === "wait")
98
+ return { min: 1, max: 1, label: "<short-id>" };
78
99
  if (command === "tell")
79
100
  return { min: 2, max: 2, label: "<projection> <message>" };
80
- if (isSummonCommand(command))
101
+ if (isCallCommand(command))
81
102
  return { min: 0, max: 1 };
82
103
  return { min: 0, max: 0 };
83
104
  }
@@ -95,20 +116,27 @@ function assignFlag(flags, name, value) {
95
116
  case "cwd":
96
117
  flags.cwd = String(value);
97
118
  return;
98
- case "agent":
99
- flags.agent = String(value);
119
+ case "repo":
120
+ flags.repo = String(value);
121
+ return;
122
+ case "contract":
123
+ flags.contractId = normalizeContractAddress(String(value));
124
+ flags.contractAddressSource = "explicit-flag";
125
+ return;
126
+ case "akuma":
127
+ flags.akuma = String(value);
100
128
  return;
101
129
  case "projection":
102
130
  flags.projection = String(value);
103
131
  return;
132
+ case "timeout":
133
+ flags.timeoutMs = parseWaitTimeout(String(value));
134
+ return;
104
135
  case "model":
105
136
  flags.model = String(value);
106
137
  return;
107
138
  case "effort": {
108
139
  const effort = String(value);
109
- if (!EFFORT_LEVELS.includes(effort)) {
110
- throw new FlowError("INVALID_EFFORT_OVERRIDE", `Invalid effort override '${effort}'. Expected low, medium, high, or max.`);
111
- }
112
140
  flags.effort = effort;
113
141
  return;
114
142
  }
@@ -118,12 +146,12 @@ function assignFlag(flags, name, value) {
118
146
  case "bare":
119
147
  flags.bare = Boolean(value);
120
148
  return;
149
+ case "detach":
150
+ flags.detach = Boolean(value);
151
+ return;
121
152
  case "draft-only":
122
153
  flags.draftOnly = Boolean(value);
123
154
  return;
124
- case "worktree":
125
- flags.worktree = Boolean(value);
126
- return;
127
155
  case "exclusive":
128
156
  flags.exclusive = Boolean(value);
129
157
  return;
@@ -139,6 +167,9 @@ function assignFlag(flags, name, value) {
139
167
  case "dry-run":
140
168
  flags.dryRun = Boolean(value);
141
169
  return;
170
+ case "waive":
171
+ flags.waivePaths = [...(flags.waivePaths ?? []), String(value)];
172
+ return;
142
173
  case "shell":
143
174
  flags.shell = String(value);
144
175
  return;
@@ -151,6 +182,12 @@ function assignFlag(flags, name, value) {
151
182
  case "complete":
152
183
  flags.complete = Boolean(value);
153
184
  return;
185
+ case "target":
186
+ if (flags.target !== undefined) {
187
+ throw new FlowError("EMPTY_PARAM", "option --target may only be specified once");
188
+ }
189
+ flags.target = String(value);
190
+ return;
154
191
  default:
155
192
  throw new FlowError("EMPTY_PARAM", `unknown option: --${name}`);
156
193
  }
@@ -178,47 +215,94 @@ function resolveCommand(tokens) {
178
215
  throw new FlowError("EMPTY_PARAM", "command cannot be empty");
179
216
  throw new FlowError("UNKNOWN_COMMAND", `unknown command: ${head ?? ""}`.trim());
180
217
  }
181
- export function parseCliArgs(tokens) {
182
- let contractId;
183
- if (tokens[0]?.startsWith("@")) {
184
- contractId = normalizeContractAddress(tokens[0]);
185
- tokens = tokens.slice(1);
186
- }
187
- const { command, rest } = resolveCommand(tokens);
188
- if (contractId && !ADDRESSABLE_COMMANDS.has(command)) {
189
- throw new FlowError("EMPTY_PARAM", `contract address @${contractId} is not valid for ${command}`);
218
+ function splitAtLiteralBoundary(tokens) {
219
+ const boundary = tokens.indexOf("--");
220
+ if (boundary === -1)
221
+ return { head: tokens, payload: [] };
222
+ return { head: tokens.slice(0, boundary), payload: tokens.slice(boundary + 1) };
223
+ }
224
+ function readValueFlag(name, token, eq, tokens, index) {
225
+ // --target alone owns emptiness in core; empty remains a supplied value.
226
+ // Other value flags keep the historical requires-a-value rejection.
227
+ if (eq !== -1) {
228
+ const value = token.slice(eq + 1);
229
+ if (value === "" && name !== "target") {
230
+ throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
231
+ }
232
+ return { value, nextIndex: index };
190
233
  }
191
- if (rest.some((token) => HELP_FLAGS.has(token))) {
192
- return { command, flags: contractId ? { contractId } : {}, positional: [], helpRequested: true };
234
+ const value = tokens[index + 1];
235
+ if (value === undefined || value === "--" || value.startsWith("--")) {
236
+ throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
193
237
  }
194
- if (command === "akuma") {
195
- throw new FlowError("EMPTY_PARAM", "akuma requires subcommand: list, ls, or show");
238
+ if (value === "" && name !== "target") {
239
+ throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
196
240
  }
197
- if (command === "skills") {
198
- throw new FlowError("EMPTY_PARAM", "skills requires subcommand: install");
241
+ // Short-flag values may begin with @; they are flag values, not commission tokens.
242
+ return { value, nextIndex: index + 1 };
243
+ }
244
+ function commissionConflictMessage(atForm, contractForm) {
245
+ return `commission address supplied twice: ${atForm} and --contract ${contractForm}; use exactly one form`;
246
+ }
247
+ function noteContractFlag(flags, raw, previousRaw) {
248
+ if (previousRaw !== undefined || flags.contractId !== undefined) {
249
+ throw new FlowError("EMPTY_PARAM", `commission address supplied twice: --contract ${previousRaw ?? flags.contractId} and --contract ${raw}; use exactly one form`);
199
250
  }
200
- const flags = contractId ? { contractId } : {};
201
- const positional = [];
202
- for (let index = 0; index < rest.length; index += 1) {
203
- const token = rest[index];
204
- if (token.startsWith("@") && ADDRESSABLE_COMMANDS.has(command)) {
205
- if (flags.contractId) {
206
- throw new FlowError("EMPTY_PARAM", `${command} accepts at most one contract address`);
251
+ assignFlag(flags, "contract", raw);
252
+ return raw;
253
+ }
254
+ export function parseCliArgs(tokens) {
255
+ const { head, payload } = splitAtLiteralBoundary(tokens);
256
+ const flags = {};
257
+ const free = [];
258
+ const seenFlagNames = new Set();
259
+ let atContractId;
260
+ let atToken;
261
+ let contractFlagRaw;
262
+ let helpRequested = false;
263
+ for (let index = 0; index < head.length; index += 1) {
264
+ const token = head[index];
265
+ if (HELP_FLAGS.has(token)) {
266
+ helpRequested = true;
267
+ continue;
268
+ }
269
+ if (token.startsWith("@")) {
270
+ if (atContractId !== undefined) {
271
+ throw new FlowError("EMPTY_PARAM", "command accepts at most one contract address");
207
272
  }
208
- flags.contractId = normalizeContractAddress(token);
273
+ atToken = token;
274
+ atContractId = normalizeContractAddress(token);
209
275
  continue;
210
276
  }
211
277
  if (/^-[A-Za-z]$/.test(token)) {
212
- const name = SHORT_FLAG_ALIASES[token.slice(1)];
278
+ const short = token.slice(1);
279
+ const name = SHORT_FLAG_ALIASES[short];
213
280
  if (!name) {
214
281
  throw new FlowError("EMPTY_PARAM", `unknown option: ${token}`);
215
282
  }
216
- assertAllowedFlag(command, name);
217
- assignFlag(flags, name, true);
218
- continue;
283
+ seenFlagNames.add(name);
284
+ if (VALUE_FLAGS.has(name)) {
285
+ const value = head[index + 1];
286
+ if (value === undefined || value === "--" || /^--/.test(value) || (/^-[A-Za-z]$/.test(value) && SHORT_FLAG_ALIASES[value.slice(1)])) {
287
+ throw new FlowError("EMPTY_PARAM", `option -${short} requires a value`);
288
+ }
289
+ index += 1;
290
+ if (name === "contract") {
291
+ contractFlagRaw = noteContractFlag(flags, value, contractFlagRaw);
292
+ }
293
+ else {
294
+ assignFlag(flags, name, value);
295
+ }
296
+ continue;
297
+ }
298
+ if (BOOLEAN_FLAGS.has(name)) {
299
+ assignFlag(flags, name, true);
300
+ continue;
301
+ }
302
+ throw new FlowError("EMPTY_PARAM", `unknown option: ${token}`);
219
303
  }
220
304
  if (!token.startsWith("--")) {
221
- positional.push(token);
305
+ free.push(token);
222
306
  continue;
223
307
  }
224
308
  const eq = token.indexOf("=");
@@ -226,15 +310,16 @@ export function parseCliArgs(tokens) {
226
310
  if (!CONTROL_FLAGS.has(name)) {
227
311
  throw new FlowError("EMPTY_PARAM", `unknown option: --${name}`);
228
312
  }
229
- assertAllowedFlag(command, name);
313
+ seenFlagNames.add(name);
230
314
  if (VALUE_FLAGS.has(name)) {
231
- const value = eq === -1 ? rest[++index] : token.slice(eq + 1);
232
- if (!value || value.startsWith("--")) {
233
- if (name === "effort")
234
- throw new FlowError("INVALID_EFFORT_OVERRIDE", "Effort override requires one of: low, medium, high, max.");
235
- throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
315
+ const { value, nextIndex } = readValueFlag(name, token, eq, head, index);
316
+ index = nextIndex;
317
+ if (name === "contract") {
318
+ contractFlagRaw = noteContractFlag(flags, value, contractFlagRaw);
319
+ }
320
+ else {
321
+ assignFlag(flags, name, value);
236
322
  }
237
- assignFlag(flags, name, value);
238
323
  continue;
239
324
  }
240
325
  if (BOOLEAN_FLAGS.has(name)) {
@@ -244,10 +329,44 @@ export function parseCliArgs(tokens) {
244
329
  assignFlag(flags, name, true);
245
330
  }
246
331
  }
247
- if (isSummonCommand(command)) {
248
- if (flags.agent) {
332
+ if (atContractId !== undefined && flags.contractId !== undefined) {
333
+ throw new FlowError("EMPTY_PARAM", commissionConflictMessage(atToken ?? `@${atContractId}`, contractFlagRaw ?? flags.contractId));
334
+ }
335
+ if (atContractId !== undefined) {
336
+ flags.contractId = atContractId;
337
+ flags.contractAddressSource = "at-address";
338
+ }
339
+ const { command, rest } = resolveCommand(free);
340
+ if (helpRequested) {
341
+ return {
342
+ command,
343
+ flags: flags.contractId ? { contractId: flags.contractId, contractAddressSource: flags.contractAddressSource, ...(flags.cwd ? { cwd: flags.cwd } : {}), ...(flags.repo ? { repo: flags.repo } : {}) } : {
344
+ ...(flags.cwd ? { cwd: flags.cwd } : {}),
345
+ ...(flags.repo ? { repo: flags.repo } : {}),
346
+ },
347
+ positional: [],
348
+ helpRequested: true,
349
+ };
350
+ }
351
+ if (command === "akuma") {
352
+ throw new FlowError("EMPTY_PARAM", "akuma requires subcommand: list, ls, or show");
353
+ }
354
+ if (command === "skills") {
355
+ throw new FlowError("EMPTY_PARAM", "skills requires subcommand: install");
356
+ }
357
+ for (const name of seenFlagNames) {
358
+ assertAllowedFlag(command, name);
359
+ }
360
+ if (flags.contractId !== undefined && !COMMISSION_SELECTOR_COMMANDS.has(command)) {
361
+ throw new FlowError("EMPTY_PARAM", command === "bind"
362
+ ? `bind creates a commission and does not accept @${flags.contractId}; use -C/--cwd to select the repository`
363
+ : `contract address @${flags.contractId} is not valid for ${command}`);
364
+ }
365
+ const positional = [...rest, ...payload];
366
+ if (isCallCommand(command)) {
367
+ if (flags.akuma) {
249
368
  if (positional.length > 1) {
250
- throw new FlowError("EMPTY_PARAM", `${command} accepts at most one prompt argument when --agent is used`);
369
+ throw new FlowError("EMPTY_PARAM", `${command} accepts at most one prompt argument when --akuma is used`);
251
370
  }
252
371
  }
253
372
  else {
@@ -259,7 +378,7 @@ export function parseCliArgs(tokens) {
259
378
  if (!parsedName.success) {
260
379
  throw new FlowError("EMPTY_PARAM", `${command} requires a valid agent name before the optional prompt`);
261
380
  }
262
- flags.agent = parsedName.data;
381
+ flags.akuma = parsedName.data;
263
382
  positional.shift();
264
383
  if (positional.length > 1) {
265
384
  throw new FlowError("EMPTY_PARAM", `${command} accepts at most one prompt argument after the agent name`);
@@ -0,0 +1,11 @@
1
+ import { resolveProjectionPrefix } from "../core/projection-core.js";
2
+ import { FlowError } from "../flow-error.js";
3
+ export function resolveProjectionAddress(cwd, prefix, contractId) {
4
+ const resolved = resolveProjectionPrefix(cwd, prefix, contractId);
5
+ if (resolved.status === "resolved")
6
+ return resolved.id;
7
+ if (resolved.status === "none") {
8
+ throw new FlowError("EMPTY_PARAM", `projection not resolved${contractId ? ` within @${contractId}` : ""}: ${prefix}`);
9
+ }
10
+ throw new FlowError("EMPTY_PARAM", `projection address is ambiguous${contractId ? ` within @${contractId}` : ""}: ${prefix}; candidates:\n${resolved.candidates.join("\n")}`);
11
+ }
@@ -0,0 +1,88 @@
1
+ function sourceLabel(address) {
2
+ return sourceName(address.spelling.source);
3
+ }
4
+ function sourceName(source) {
5
+ switch (source) {
6
+ case "at-address": return "explicit @";
7
+ case "explicit-flag": return "explicit --contract";
8
+ case "worktree-presented": return "worktree cwd";
9
+ case "sole-active": return "only active commission";
10
+ case "artifact-provenance": return "artifact provenance";
11
+ }
12
+ }
13
+ function isCommissionAddress(address) {
14
+ return address.binding.kind === "commission";
15
+ }
16
+ export function renderResolvedAddressReceipt(address, facts = {}) {
17
+ if (!isCommissionAddress(address)) {
18
+ if (address.binding.kind === "unbound") {
19
+ if (address.repo.kind !== "repository") {
20
+ throw new Error("unbound address receipt requires repository authority");
21
+ }
22
+ return [
23
+ "Unbound",
24
+ "no active commission",
25
+ `repo ${address.repo.stableRoot}`,
26
+ `workdir ${address.workdir}`,
27
+ facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
28
+ facts.projectionId ? `projection ${facts.projectionId}` : undefined,
29
+ ].filter((part) => part !== undefined).join(" · ");
30
+ }
31
+ const repository = address.repo.kind === "repository"
32
+ ? `repo ${address.repo.stableRoot}`
33
+ : "no repository";
34
+ return [
35
+ "Bare",
36
+ "no contract",
37
+ repository,
38
+ `workdir ${address.workdir}`,
39
+ facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
40
+ facts.projectionId ? `projection ${facts.projectionId}` : undefined,
41
+ ].filter((part) => part !== undefined).join(" · ");
42
+ }
43
+ return [
44
+ address.place.display ? `Bound @${address.place.display}` : "Bound",
45
+ `commission ${address.binding.commissionId}`,
46
+ `repo ${address.repo.stableRoot}`,
47
+ `target ${address.target.branch}`,
48
+ facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
49
+ facts.projectionId ? `projection ${facts.projectionId}` : undefined,
50
+ `via ${sourceLabel(address)}`,
51
+ ].filter((part) => part !== undefined).join(" · ");
52
+ }
53
+ function renderAttemptCandidate(candidate) {
54
+ return [
55
+ candidate.place ? `@${candidate.place}` : undefined,
56
+ candidate.name,
57
+ candidate.objective,
58
+ `target ${candidate.target.branch}`,
59
+ `commission ${candidate.commissionId}`,
60
+ `lifecycle ${candidate.lifecycle.state}`,
61
+ candidate.matchedBy ? `matched by ${candidate.matchedBy}` : undefined,
62
+ ].filter((part) => part !== undefined).join(" · ");
63
+ }
64
+ export function renderResolutionAttemptPreamble(attempt) {
65
+ const parts = [
66
+ attempt.repo.kind === "repository" ? `At repo ${attempt.repo.stableRoot}` : "At no repository",
67
+ attempt.spelling ? `address ${attempt.spelling.asTyped}` : undefined,
68
+ attempt.candidates.length > 0
69
+ ? `candidates ${attempt.candidates.map(renderAttemptCandidate).join("; ")}`
70
+ : undefined,
71
+ attempt.spelling ? `via ${sourceName(attempt.spelling.source)}` : undefined,
72
+ attempt.inference.length > 0 ? `inference ${attempt.inference.join("; ")}` : undefined,
73
+ ];
74
+ return parts.filter((part) => part !== undefined).join(" · ");
75
+ }
76
+ export function renderAddressCarrier(carrier, facts = {}) {
77
+ return "expected" in carrier
78
+ ? renderResolutionAttemptPreamble(carrier)
79
+ : renderResolvedAddressReceipt(carrier, facts);
80
+ }
81
+ export function prependAddressReceipt(output, address, facts = {}) {
82
+ if (!address)
83
+ return output;
84
+ return { ...output, address, text: `${renderResolvedAddressReceipt(address, facts)}\n${output.text}` };
85
+ }
86
+ export function carryAddress(output, address) {
87
+ return { ...output, address };
88
+ }
@@ -2,6 +2,7 @@ import { shortCommitHash } from "../../core/render.js";
2
2
  import { assembleResponse, buildPayload, buildSection, formatHints, formatList, formatMaybe, formatWarnings, } from "./format.js";
3
3
  import { DIFF_OVERVIEW_SECTION_TITLE, RESPONSE_MARKERS, WARNING_SECTION_TITLE } from "./response-style.js";
4
4
  import { appendPersistenceSummary, BRANCH_LABEL, CONTENT_SECTION_TITLE, HINTS_LIST_MAX_ITEMS, HINTS_SECTION_TITLE, INFO_BRANCH_MAX_CHARS, INFO_COMMIT_MAX_CHARS, INFO_PLEA_MAX_CHARS, INFO_SECTION_TITLE, } from "./shared.js";
5
+ import { prependAddressReceipt } from "./address.js";
5
6
  function sealLines(result) {
6
7
  if (!result.sealed)
7
8
  return ["nothing sealed"];
@@ -13,7 +14,7 @@ function sealLines(result) {
13
14
  ];
14
15
  }
15
16
  export function buildArcResponse(result) {
16
- const { status: _status, ...resultData } = result;
17
+ const { status: _status, address: _address, ...resultData } = result;
17
18
  const summarySection = buildSection(CONTENT_SECTION_TITLE, sealLines(result));
18
19
  const warningSection = buildSection(WARNING_SECTION_TITLE, formatWarnings(result.meta?.warnings ?? []));
19
20
  const hintsSection = buildSection(HINTS_SECTION_TITLE, formatHints(result.meta?.hints ?? [], { maxItems: HINTS_LIST_MAX_ITEMS }));
@@ -26,15 +27,14 @@ export function buildArcResponse(result) {
26
27
  ...formatMaybe("Base Branch", result.baseBranch, INFO_BRANCH_MAX_CHARS),
27
28
  ]);
28
29
  const summaryLine = appendPersistenceSummary(result.summary, result.responsePath);
29
- return {
30
+ return prependAddressReceipt({
30
31
  text: assembleResponse(`${RESPONSE_MARKERS.arc} Arc Opened (a${result.arc})`, summaryLine, [summarySection, infoSection, buildSection(DIFF_OVERVIEW_SECTION_TITLE, result.diff), warningSection, hintsSection].filter((section) => section !== null)),
31
32
  payload: buildPayload(resultData, result.responsePath),
32
- };
33
+ }, result.address);
33
34
  }
34
35
  export function buildRenewResponse(result) {
35
- const { status: _status, ...resultData } = result;
36
+ const { status: _status, address: _address, ...resultData } = result;
36
37
  const infoSection = buildSection(INFO_SECTION_TITLE, [
37
- ...formatMaybe("Contract", result.contractId, INFO_BRANCH_MAX_CHARS),
38
38
  ...formatMaybe("Old Base", result.oldBase, INFO_COMMIT_MAX_CHARS),
39
39
  ...formatMaybe("New Base", result.newBase, INFO_COMMIT_MAX_CHARS),
40
40
  ...formatMaybe("Old Head", result.oldHead, INFO_COMMIT_MAX_CHARS),
@@ -42,13 +42,13 @@ export function buildRenewResponse(result) {
42
42
  ...formatMaybe("Ledger Head", result.ledgerHead, INFO_COMMIT_MAX_CHARS),
43
43
  ...formatList("Seal", sealLines(result)),
44
44
  ]);
45
- return {
45
+ return prependAddressReceipt({
46
46
  text: assembleResponse(`${RESPONSE_MARKERS.arc} Renewed`, appendPersistenceSummary(result.summary, result.responsePath), [buildSection(CONTENT_SECTION_TITLE, result.diff), infoSection].filter((section) => section !== null)),
47
47
  payload: buildPayload(resultData, result.responsePath),
48
- };
48
+ }, result.address);
49
49
  }
50
50
  export function buildAmendResponse(result) {
51
- const { status: _status, ...resultData } = result;
51
+ const { status: _status, address: _address, ...resultData } = result;
52
52
  const renderedLedgerCommit = shortCommitHash(result.ledgerCommit);
53
53
  const infoSection = buildSection(INFO_SECTION_TITLE, [
54
54
  ...formatMaybe("Amendment", String(result.amendmentNumber), INFO_COMMIT_MAX_CHARS),
@@ -56,8 +56,8 @@ export function buildAmendResponse(result) {
56
56
  ...formatMaybe("Base Branch", result.baseBranch, INFO_BRANCH_MAX_CHARS),
57
57
  ...formatMaybe("Ledger Commit", renderedLedgerCommit, INFO_COMMIT_MAX_CHARS),
58
58
  ]);
59
- return {
59
+ return prependAddressReceipt({
60
60
  text: assembleResponse(`${RESPONSE_MARKERS.arc} Amended`, `Amended contract ledger on '${result.currentBranch}' with Amendment ${result.amendmentNumber} [${renderedLedgerCommit}].`, [buildSection(CONTENT_SECTION_TITLE, result.summary), infoSection].filter((section) => section !== null)),
61
61
  payload: buildPayload(resultData),
62
- };
62
+ }, result.address);
63
63
  }
@@ -0,0 +1,59 @@
1
+ import { assembleResponse, buildPayload, buildSection, formatMaybe, formatWarnings } from "./format.js";
2
+ import { WARNING_SECTION_TITLE } from "./response-style.js";
3
+ import { CONTENT_SECTION_TITLE, formatPersistenceSummary, formatCallCompletionHeading, formatCallNextAction, INFO_AKUMA_MAX_CHARS, projectionRejoinHintLines, } from "./shared.js";
4
+ import { carryAddress, prependAddressReceipt, renderResolvedAddressReceipt } from "./address.js";
5
+ export function renderAdoptedLaunchReceipt(address, projectionId) {
6
+ return [
7
+ renderResolvedAddressReceipt(address, { projectionId }),
8
+ ...projectionRejoinHintLines(projectionId),
9
+ ].join("\n");
10
+ }
11
+ export function buildCallResponse(result, input) {
12
+ if (result.state === "launched") {
13
+ if (!result.projection)
14
+ throw new Error("launched call response requires projection facts");
15
+ const { session: _session, evidenceCwd: _evidenceCwd, address: _address, foregroundWait: _foregroundWait, ...responseData } = result;
16
+ const output = {
17
+ text: input.addressPlacement === "stdout"
18
+ ? renderAdoptedLaunchReceipt(result.address, result.projection.id)
19
+ : projectionRejoinHintLines(result.projection.id).join("\n"),
20
+ payload: buildPayload(responseData),
21
+ };
22
+ switch (input.addressPlacement) {
23
+ case "stdout":
24
+ return { ...output, address: result.address };
25
+ case "footer":
26
+ return carryAddress(output, result.address);
27
+ case "none":
28
+ return output;
29
+ }
30
+ }
31
+ const infoLines = [
32
+ ...formatMaybe("Akuma", result.agent ?? input.agent, INFO_AKUMA_MAX_CHARS),
33
+ ];
34
+ const summarySection = buildSection(CONTENT_SECTION_TITLE, result.summary);
35
+ const warningSection = buildSection(WARNING_SECTION_TITLE, formatWarnings(result.meta?.warnings ?? []));
36
+ const persistedSummary = formatPersistenceSummary(input.responsePath);
37
+ const response = assembleResponse(formatCallCompletionHeading(result.projection) ?? "", persistedSummary, [summarySection, warningSection].filter((s) => s !== null), infoLines);
38
+ const repositoryCoordinate = result.address.repo.kind === "repository"
39
+ ? result.address.repo.stableRoot
40
+ : undefined;
41
+ const text = `${response.trimStart()}\n\n${formatCallNextAction({
42
+ projection: result.projection,
43
+ artifactId: input.artifactId,
44
+ repositoryCoordinate,
45
+ })}`;
46
+ const { session: _session, evidenceCwd: _evidenceCwd, address: _address, foregroundWait: _foregroundWait, ...responseData } = result;
47
+ const output = {
48
+ text,
49
+ payload: buildPayload(responseData, input.responsePath),
50
+ };
51
+ switch (input.addressPlacement) {
52
+ case "stdout":
53
+ return prependAddressReceipt(output, result.address, { artifactId: result.artifactId, projectionId: result.projection?.id });
54
+ case "footer":
55
+ return carryAddress(output, result.address);
56
+ case "none":
57
+ return output;
58
+ }
59
+ }
@@ -0,0 +1,3 @@
1
+ export function compactText(value) {
2
+ return value.replace(/\s+/g, " ").trim();
3
+ }
@@ -1,9 +1,10 @@
1
1
  import { asMessage, isFlowError, pickHintsFromError } from "../../flow-error.js";
2
2
  import { assertSettingsKnobUsable, loadKeiyakuSettings } from "../../config/settings.js";
3
- import { loadAkumaCatalog } from "../../config/akuma-loader.js";
3
+ import { loadAkumaCatalogV2 } from "../../config/akuma-loader_v2.js";
4
4
  import { appendDebugLog } from "../../telemetry/debug-log.js";
5
5
  import { ACTOR_IDENTITY, TOOL_NAMES, BUILTIN_AGENT_NAMES } from "../../keiyaku.js";
6
6
  import { buildErrorResponse } from "./misc.js";
7
+ import { addressCarrierFromError } from "../../core/addressing.js";
7
8
  export function classifyCliError(error) {
8
9
  if (isFlowError(error)) {
9
10
  return { errorType: "keiyaku_error", errorCode: error.code };
@@ -17,7 +18,7 @@ async function getAvailableActorNames(cwd, fallback) {
17
18
  try {
18
19
  const loaded = await loadKeiyakuSettings(cwd);
19
20
  assertSettingsKnobUsable(loaded, "agents");
20
- return Object.keys((await loadAkumaCatalog(cwd)).definitions);
21
+ return Object.keys((await loadAkumaCatalogV2(cwd)).selected);
21
22
  }
22
23
  catch {
23
24
  return [...fallback];
@@ -42,5 +43,6 @@ export async function handleCliError(input) {
42
43
  review: isFlowError(input.error) ? input.error.review : undefined,
43
44
  errorType,
44
45
  errorCode,
46
+ addressCarrier: addressCarrierFromError(input.error),
45
47
  });
46
48
  }