@astrosheep/keiyaku 2.8.0 → 2.8.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 (36) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/call-terms_v2.js +6 -2
  3. package/build/agents/harness/index.js +5 -1
  4. package/build/agents/harness/pump.js +22 -5
  5. package/build/agents/launch-snapshot_v2.js +14 -9
  6. package/build/agents/selector_v2.js +15 -3
  7. package/build/cli/commands/akuma.js +21 -3
  8. package/build/cli/completion.js +4 -1
  9. package/build/cli/flags.js +10 -1
  10. package/build/cli/help.js +5 -3
  11. package/build/cli/index.js +67 -14
  12. package/build/cli/parse.js +65 -24
  13. package/build/cli/projection-address.js +97 -10
  14. package/build/cli/render/address.js +12 -3
  15. package/build/cli/render/call.js +10 -5
  16. package/build/cli/render/shared.js +2 -2
  17. package/build/cli/render/status.js +19 -4
  18. package/build/cli/render/wait.js +3 -3
  19. package/build/config/provider-policy-ownership.js +39 -0
  20. package/build/config/settings.js +1 -11
  21. package/build/core/addressing.js +82 -38
  22. package/build/core/call.js +33 -5
  23. package/build/core/projection/mint.js +23 -3
  24. package/build/core/projection-alias.js +123 -0
  25. package/build/core/projection-coordinate.js +23 -7
  26. package/build/core/projection-core.js +8 -47
  27. package/build/core/projection-kill.js +26 -0
  28. package/build/core/projection-mint.js +24 -3
  29. package/build/core/projection-runner-lock.js +1 -1
  30. package/build/core/projection-status.js +111 -12
  31. package/build/core/projection-wake.js +3 -0
  32. package/build/core/response-artifact-id.js +5 -0
  33. package/build/core/status.js +1 -1
  34. package/build/core/transcripts.js +42 -15
  35. package/build/generated/version.js +1 -1
  36. package/package.json +4 -4
@@ -1,5 +1,6 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { agentNameSchema } from "../config/settings.js";
3
+ import { isResponseArtifactId } from "../core/response-artifact-id.js";
3
4
  import { BOOLEAN_FLAGS, CONTROL_FLAGS, HELP_FLAGS, SHORT_FLAG_ALIASES, VALUE_FLAGS, } from "./flags.js";
4
5
  /** Commands that may carry an explicit commission selector (@ or --contract). */
5
6
  const COMMISSION_SELECTOR_COMMANDS = new Set([
@@ -8,6 +9,7 @@ const COMMISSION_SELECTOR_COMMANDS = new Set([
8
9
  "call",
9
10
  "wait",
10
11
  "tell",
12
+ "kill",
11
13
  "log",
12
14
  "renew",
13
15
  "petition",
@@ -20,6 +22,7 @@ const CLI_COMMAND_ORDER = [
20
22
  "revive",
21
23
  "wait",
22
24
  "tell",
25
+ "kill",
23
26
  "log",
24
27
  "akuma list",
25
28
  "akuma ls",
@@ -40,10 +43,11 @@ const COMMAND_FLAGS = {
40
43
  // contract is parseable on bind only so the mouth can reject it with bind-specific copy.
41
44
  bind: ["cwd", "repo", "contract", "after", "exclusive", "draft-only"],
42
45
  arc: ["cwd", "repo", "contract", "waive"],
43
- call: ["cwd", "repo", "contract", "akuma", "model", "effort", "incognito", "bare", "detach"],
46
+ call: ["cwd", "repo", "contract", "akuma", "model", "effort", "incognito", "bare", "detach", "alias"],
44
47
  revive: ["cwd", "repo", "model", "effort"],
45
48
  wait: ["cwd", "repo", "contract", "timeout"],
46
49
  tell: ["cwd", "repo", "contract", "effort"],
50
+ kill: ["cwd", "repo", "contract"],
47
51
  log: ["cwd", "repo", "contract"],
48
52
  akuma: [],
49
53
  "akuma ls": ["cwd", "repo"],
@@ -57,7 +61,7 @@ const COMMAND_FLAGS = {
57
61
  forfeit: ["cwd", "repo", "contract", "reason"],
58
62
  guide: [],
59
63
  completion: ["cwd", "repo", "shell", "word", "previous", "complete"],
60
- status: ["cwd", "repo", "target"],
64
+ status: ["cwd", "repo", "target", "all"],
61
65
  "dump-env": [],
62
66
  };
63
67
  const STDIN_COMMANDS = new Set(["bind", "arc", "amend"]);
@@ -95,9 +99,11 @@ export function commandPositionalRange(command) {
95
99
  if (command === "revive")
96
100
  return { min: 1, max: 1, label: "<artifact-id>" };
97
101
  if (command === "wait")
98
- return { min: 1, max: 1, label: "<short-id>" };
102
+ return { min: 1, max: 1, label: "<projection-address>" };
99
103
  if (command === "tell")
100
104
  return { min: 2, max: 2, label: "<projection> <message>" };
105
+ if (command === "kill")
106
+ return { min: 1, max: 1, label: "<projection-address>" };
101
107
  if (isCallCommand(command))
102
108
  return { min: 0, max: 1 };
103
109
  return { min: 0, max: 0 };
@@ -129,6 +135,9 @@ function assignFlag(flags, name, value) {
129
135
  case "projection":
130
136
  flags.projection = String(value);
131
137
  return;
138
+ case "alias":
139
+ flags.alias = String(value);
140
+ return;
132
141
  case "timeout":
133
142
  flags.timeoutMs = parseWaitTimeout(String(value));
134
143
  return;
@@ -182,6 +191,9 @@ function assignFlag(flags, name, value) {
182
191
  case "complete":
183
192
  flags.complete = Boolean(value);
184
193
  return;
194
+ case "all":
195
+ flags.all = Boolean(value);
196
+ return;
185
197
  case "target":
186
198
  if (flags.target !== undefined) {
187
199
  throw new FlowError("EMPTY_PARAM", "option --target may only be specified once");
@@ -244,21 +256,28 @@ function readValueFlag(name, token, eq, tokens, index) {
244
256
  function commissionConflictMessage(atForm, contractForm) {
245
257
  return `commission address supplied twice: ${atForm} and --contract ${contractForm}; use exactly one form`;
246
258
  }
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`);
250
- }
251
- assignFlag(flags, "contract", raw);
252
- return raw;
259
+ function formatSelectorList(items) {
260
+ if (items.length <= 1)
261
+ return items[0] ?? "";
262
+ if (items.length === 2)
263
+ return `${items[0]} and ${items[1]}`;
264
+ return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
265
+ }
266
+ function multiCommissionSelectorMessage(command, spellings) {
267
+ return `${command} accepts one commission address; got ${formatSelectorList(spellings)}`;
268
+ }
269
+ function commissionSelectorSpelling(source, value, rawToken) {
270
+ if (source === "at-address")
271
+ return rawToken ?? `@${value}`;
272
+ return `--contract ${rawToken ?? value}`;
253
273
  }
254
274
  export function parseCliArgs(tokens) {
255
275
  const { head, payload } = splitAtLiteralBoundary(tokens);
256
276
  const flags = {};
257
277
  const free = [];
258
278
  const seenFlagNames = new Set();
259
- let atContractId;
260
- let atToken;
261
- let contractFlagRaw;
279
+ const atTokens = [];
280
+ const contractFlagRaws = [];
262
281
  let helpRequested = false;
263
282
  for (let index = 0; index < head.length; index += 1) {
264
283
  const token = head[index];
@@ -267,11 +286,7 @@ export function parseCliArgs(tokens) {
267
286
  continue;
268
287
  }
269
288
  if (token.startsWith("@")) {
270
- if (atContractId !== undefined) {
271
- throw new FlowError("EMPTY_PARAM", "command accepts at most one contract address");
272
- }
273
- atToken = token;
274
- atContractId = normalizeContractAddress(token);
289
+ atTokens.push(token);
275
290
  continue;
276
291
  }
277
292
  if (/^-[A-Za-z]$/.test(token)) {
@@ -288,7 +303,7 @@ export function parseCliArgs(tokens) {
288
303
  }
289
304
  index += 1;
290
305
  if (name === "contract") {
291
- contractFlagRaw = noteContractFlag(flags, value, contractFlagRaw);
306
+ contractFlagRaws.push(value);
292
307
  }
293
308
  else {
294
309
  assignFlag(flags, name, value);
@@ -307,6 +322,9 @@ export function parseCliArgs(tokens) {
307
322
  }
308
323
  const eq = token.indexOf("=");
309
324
  const name = parseFlagName(eq === -1 ? token : token.slice(0, eq));
325
+ if (name === "place") {
326
+ throw new FlowError("EMPTY_PARAM", "unknown option --place; use @addr or --contract <addr>");
327
+ }
310
328
  if (!CONTROL_FLAGS.has(name)) {
311
329
  throw new FlowError("EMPTY_PARAM", `unknown option: --${name}`);
312
330
  }
@@ -315,7 +333,7 @@ export function parseCliArgs(tokens) {
315
333
  const { value, nextIndex } = readValueFlag(name, token, eq, head, index);
316
334
  index = nextIndex;
317
335
  if (name === "contract") {
318
- contractFlagRaw = noteContractFlag(flags, value, contractFlagRaw);
336
+ contractFlagRaws.push(value);
319
337
  }
320
338
  else {
321
339
  assignFlag(flags, name, value);
@@ -329,14 +347,23 @@ export function parseCliArgs(tokens) {
329
347
  assignFlag(flags, name, true);
330
348
  }
331
349
  }
332
- if (atContractId !== undefined && flags.contractId !== undefined) {
333
- throw new FlowError("EMPTY_PARAM", commissionConflictMessage(atToken ?? `@${atContractId}`, contractFlagRaw ?? flags.contractId));
350
+ const { command, rest } = resolveCommand(free);
351
+ if (atTokens.length > 0 && contractFlagRaws.length > 0) {
352
+ throw new FlowError("EMPTY_PARAM", commissionConflictMessage(atTokens[0], contractFlagRaws[0]));
353
+ }
354
+ if (atTokens.length > 1) {
355
+ throw new FlowError("EMPTY_PARAM", multiCommissionSelectorMessage(command, atTokens));
356
+ }
357
+ if (contractFlagRaws.length > 1) {
358
+ throw new FlowError("EMPTY_PARAM", multiCommissionSelectorMessage(command, contractFlagRaws.map((raw) => `--contract ${raw}`)));
334
359
  }
335
- if (atContractId !== undefined) {
336
- flags.contractId = atContractId;
360
+ if (atTokens.length === 1) {
361
+ flags.contractId = normalizeContractAddress(atTokens[0]);
337
362
  flags.contractAddressSource = "at-address";
338
363
  }
339
- const { command, rest } = resolveCommand(free);
364
+ else if (contractFlagRaws.length === 1) {
365
+ assignFlag(flags, "contract", contractFlagRaws[0]);
366
+ }
340
367
  if (helpRequested) {
341
368
  return {
342
369
  command,
@@ -362,6 +389,17 @@ export function parseCliArgs(tokens) {
362
389
  ? `bind creates a commission and does not accept @${flags.contractId}; use -C/--cwd to select the repository`
363
390
  : `contract address @${flags.contractId} is not valid for ${command}`);
364
391
  }
392
+ if ((command === "wait" || command === "tell") && flags.contractId !== undefined) {
393
+ const selectorSpelling = commissionSelectorSpelling(flags.contractAddressSource === "at-address" ? "at-address" : "explicit-flag", flags.contractId, flags.contractAddressSource === "at-address" ? atTokens[0] : contractFlagRaws[0]);
394
+ const scopeOnlyMessage = `${command} requires a projection id or alias; ${selectorSpelling} only limits projection lookup, so add the projection address`;
395
+ if (command === "wait" && rest.length + payload.length === 0) {
396
+ throw new FlowError("EMPTY_PARAM", scopeOnlyMessage);
397
+ }
398
+ if (command === "tell" && rest.length === 0) {
399
+ // Payload after -- is the message body, never the projection address.
400
+ throw new FlowError("EMPTY_PARAM", scopeOnlyMessage);
401
+ }
402
+ }
365
403
  const positional = [...rest, ...payload];
366
404
  if (isCallCommand(command)) {
367
405
  if (flags.akuma) {
@@ -374,6 +412,9 @@ export function parseCliArgs(tokens) {
374
412
  if (!name) {
375
413
  throw new FlowError("EMPTY_PARAM", `${command} requires an agent name; use \`keiyaku ${command} NAME [prompt|-]\``);
376
414
  }
415
+ if (isResponseArtifactId(name)) {
416
+ throw new FlowError("EMPTY_PARAM", `artifact ${name} is a response artifact; call expects an Akuma profile name`);
417
+ }
377
418
  const parsedName = agentNameSchema.safeParse(name);
378
419
  if (!parsedName.success) {
379
420
  throw new FlowError("EMPTY_PARAM", `${command} requires a valid agent name before the optional prompt`);
@@ -1,11 +1,98 @@
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")}`);
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { AddressResolutionError, buildObjectResolutionAttempt, } from "../core/addressing.js";
4
+ import { resolveProjectionCoordinate } from "../core/projection-coordinate.js";
5
+ import { isProjectionId, projectionDir } from "../core/projection-core.js";
6
+ import { isProjectionAlias, readProjectionAlias } from "../core/projection-alias.js";
7
+ import { readProjectionIdentity } from "../core/projection-identity.js";
8
+ import { isResponseArtifactId } from "../core/response-artifact-id.js";
9
+ import { isErrnoException } from "../errno.js";
10
+ function isRealDirectory(directory) {
11
+ try {
12
+ return fs.lstatSync(directory).isDirectory();
13
+ }
14
+ catch (error) {
15
+ if (isErrnoException(error) && error.code === "ENOENT")
16
+ return false;
17
+ throw error;
18
+ }
19
+ }
20
+ export async function resolveProjectionAddress(input) {
21
+ const workdir = path.resolve(input.cwd);
22
+ // Scoped lookup reuses the commission carrier's repository root: zero Git probes.
23
+ // Unscoped lookup performs exactly one coordinate probe.
24
+ let repo;
25
+ let physicalRoot;
26
+ let attempt;
27
+ if (input.scope) {
28
+ repo = input.scope.repo;
29
+ physicalRoot = input.scope.repo.stableRoot;
30
+ attempt = buildObjectResolutionAttempt({
31
+ repo,
32
+ workdir,
33
+ expected: "projection",
34
+ supplied: input.address,
35
+ scope: {
36
+ commissionId: input.scope.binding.commissionId,
37
+ asTyped: input.scope.spelling.asTyped,
38
+ },
39
+ inference: ["commission-scoped projection lookup"],
40
+ });
41
+ }
42
+ else {
43
+ const coordinate = resolveProjectionCoordinate(input.cwd);
44
+ repo = coordinate.authority.kind === "repository"
45
+ ? { kind: "repository", stableRoot: coordinate.authority.stableRoot }
46
+ : { kind: "user" };
47
+ physicalRoot = coordinate.physicalRoot;
48
+ attempt = buildObjectResolutionAttempt({
49
+ repo,
50
+ workdir,
51
+ expected: "projection",
52
+ supplied: input.address,
53
+ inference: ["projection directory lookup"],
54
+ });
55
+ }
56
+ if (isResponseArtifactId(input.address)) {
57
+ throw new AddressResolutionError("EMPTY_PARAM", `artifact ${input.address} is a response artifact; ${input.verb} expects a projection id or alias`, { ...attempt, actual: "artifact" });
58
+ }
59
+ const suffix = input.scope
60
+ ? `within ${input.scope.spelling.asTyped}`
61
+ : repo.kind === "repository" ? `in repository ${repo.stableRoot}` : `in workdir ${workdir}`;
62
+ let id;
63
+ let alias;
64
+ if (input.address.includes("/")) {
65
+ if (!isProjectionId(input.address))
66
+ throw new AddressResolutionError("EMPTY_PARAM", `projection ${input.address} has no match ${suffix}`, attempt);
67
+ id = input.address;
68
+ }
69
+ else {
70
+ if (!isProjectionAlias(input.address))
71
+ throw new AddressResolutionError("EMPTY_PARAM", `projection alias ${input.address} has no match ${suffix}`, attempt);
72
+ const target = readProjectionAlias(physicalRoot, input.address);
73
+ if (!target)
74
+ throw new AddressResolutionError("EMPTY_PARAM", `projection alias ${input.address} has no match ${suffix}`, attempt);
75
+ id = target;
76
+ alias = input.address;
77
+ }
78
+ const directory = projectionDir(physicalRoot, id);
79
+ if (!isRealDirectory(directory)) {
80
+ const subject = alias ? `projection alias ${alias} points to missing projection ${id}` : `projection ${id}`;
81
+ throw new AddressResolutionError("EMPTY_PARAM", `${subject} has no match ${suffix}`, attempt);
82
+ }
83
+ let identity;
84
+ try {
85
+ identity = readProjectionIdentity(directory);
86
+ }
87
+ catch (error) {
88
+ if (error instanceof Error && /ENOENT|no such file/i.test(error.message)) {
89
+ const subject = alias ? `projection alias ${alias} points to missing projection ${id}` : `projection ${id}`;
90
+ throw new AddressResolutionError("EMPTY_PARAM", `${subject} has no match ${suffix}`, attempt);
91
+ }
92
+ throw error;
93
+ }
94
+ if (input.scope && (identity.binding.kind !== "commission" || identity.binding.commissionId !== input.scope.binding.commissionId)) {
95
+ throw new AddressResolutionError("EMPTY_PARAM", `projection ${id} has no match ${suffix}`, attempt);
96
+ }
97
+ return { id, ...(alias ? { alias } : {}) };
11
98
  }
@@ -25,7 +25,7 @@ export function renderResolvedAddressReceipt(address, facts = {}) {
25
25
  `repo ${address.repo.stableRoot}`,
26
26
  `workdir ${address.workdir}`,
27
27
  facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
28
- facts.projectionId ? `projection ${facts.projectionId}` : undefined,
28
+ facts.projectionId ? `projection ${facts.projectionId}${facts.projectionAlias ? ` (${facts.projectionAlias})` : ""}` : undefined,
29
29
  ].filter((part) => part !== undefined).join(" · ");
30
30
  }
31
31
  const repository = address.repo.kind === "repository"
@@ -37,7 +37,7 @@ export function renderResolvedAddressReceipt(address, facts = {}) {
37
37
  repository,
38
38
  `workdir ${address.workdir}`,
39
39
  facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
40
- facts.projectionId ? `projection ${facts.projectionId}` : undefined,
40
+ facts.projectionId ? `projection ${facts.projectionId}${facts.projectionAlias ? ` (${facts.projectionAlias})` : ""}` : undefined,
41
41
  ].filter((part) => part !== undefined).join(" · ");
42
42
  }
43
43
  return [
@@ -46,7 +46,7 @@ export function renderResolvedAddressReceipt(address, facts = {}) {
46
46
  `repo ${address.repo.stableRoot}`,
47
47
  `target ${address.target.branch}`,
48
48
  facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
49
- facts.projectionId ? `projection ${facts.projectionId}` : undefined,
49
+ facts.projectionId ? `projection ${facts.projectionId}${facts.projectionAlias ? ` (${facts.projectionAlias})` : ""}` : undefined,
50
50
  `via ${sourceLabel(address)}`,
51
51
  ].filter((part) => part !== undefined).join(" · ");
52
52
  }
@@ -61,7 +61,16 @@ function renderAttemptCandidate(candidate) {
61
61
  candidate.matchedBy ? `matched by ${candidate.matchedBy}` : undefined,
62
62
  ].filter((part) => part !== undefined).join(" · ");
63
63
  }
64
+ function isCommissionResolutionAttempt(attempt) {
65
+ return attempt.expected === "commission";
66
+ }
64
67
  export function renderResolutionAttemptPreamble(attempt) {
68
+ if (!isCommissionResolutionAttempt(attempt)) {
69
+ return [
70
+ attempt.repo.kind === "repository" ? `At repo ${attempt.repo.stableRoot}` : "At no repository",
71
+ attempt.scope ? `scope ${attempt.scope.asTyped}` : undefined,
72
+ ].filter((part) => part !== undefined).join(" · ");
73
+ }
65
74
  const parts = [
66
75
  attempt.repo.kind === "repository" ? `At repo ${attempt.repo.stableRoot}` : "At no repository",
67
76
  attempt.spelling ? `address ${attempt.spelling.asTyped}` : undefined,
@@ -2,10 +2,12 @@ import { assembleResponse, buildPayload, buildSection, formatMaybe, formatWarnin
2
2
  import { WARNING_SECTION_TITLE } from "./response-style.js";
3
3
  import { CONTENT_SECTION_TITLE, formatPersistenceSummary, formatCallCompletionHeading, formatCallNextAction, INFO_AKUMA_MAX_CHARS, projectionRejoinHintLines, } from "./shared.js";
4
4
  import { carryAddress, prependAddressReceipt, renderResolvedAddressReceipt } from "./address.js";
5
- export function renderAdoptedLaunchReceipt(address, projectionId) {
5
+ export function renderAdoptedLaunchReceipt(address, projectionId, alias, previousAliasTarget) {
6
6
  return [
7
- renderResolvedAddressReceipt(address, { projectionId }),
8
- ...projectionRejoinHintLines(projectionId),
7
+ renderResolvedAddressReceipt(address, { projectionId, ...(alias ? { projectionAlias: alias } : {}) }),
8
+ ...(alias ? [`alias ${alias} → ${projectionId}`] : []),
9
+ ...(alias && previousAliasTarget ? [`(moved from ${previousAliasTarget}, still running)`] : []),
10
+ ...projectionRejoinHintLines(alias ?? projectionId),
9
11
  ].join("\n");
10
12
  }
11
13
  export function buildCallResponse(result, input) {
@@ -15,8 +17,11 @@ export function buildCallResponse(result, input) {
15
17
  const { session: _session, evidenceCwd: _evidenceCwd, address: _address, foregroundWait: _foregroundWait, ...responseData } = result;
16
18
  const output = {
17
19
  text: input.addressPlacement === "stdout"
18
- ? renderAdoptedLaunchReceipt(result.address, result.projection.id)
19
- : projectionRejoinHintLines(result.projection.id).join("\n"),
20
+ ? renderAdoptedLaunchReceipt(result.address, result.projection.id, result.projection.alias, result.projection.previousAliasTarget)
21
+ : [
22
+ ...(result.projection.alias ? [`alias ${result.projection.alias} → ${result.projection.id}`] : []),
23
+ ...projectionRejoinHintLines(result.projection.alias ?? result.projection.id),
24
+ ].join("\n"),
20
25
  payload: buildPayload(responseData),
21
26
  };
22
27
  switch (input.addressPlacement) {
@@ -135,14 +135,14 @@ export function projectionRejoinHintLines(projectionId) {
135
135
  }
136
136
  export function formatCallNextAction(input) {
137
137
  if (input.projection?.phase === "active") {
138
- return projectionRejoinHintLines(input.projection.id).join("\n");
138
+ return projectionRejoinHintLines(input.projection.alias ?? input.projection.id).join("\n");
139
139
  }
140
140
  if (input.projection?.phase === "done") {
141
141
  return [
142
142
  "The akuma is back.",
143
143
  ...(input.artifactId ? [`record: ${input.artifactId}`] : []),
144
144
  "talk to it:",
145
- ` » keiyaku tell ${input.projection.id} "your words"`,
145
+ ` » keiyaku tell ${input.projection.alias ?? input.projection.id} "your words"`,
146
146
  ].join("\n");
147
147
  }
148
148
  return "⟶ keiyaku status";
@@ -15,6 +15,16 @@ function renderProjectionDiagnostics(row) {
15
15
  return "";
16
16
  return ` · diag ${row.diagnostics.map((diagnostic) => diagnostic.detail).join("; ")}`;
17
17
  }
18
+ function renderProjectionOmission(board) {
19
+ if (!board.omitted)
20
+ return undefined;
21
+ const counts = ["failed", "dead", "done", "dismissed"]
22
+ .flatMap((state) => {
23
+ const count = board.omitted?.byState[state] ?? 0;
24
+ return count > 0 ? [`${count} ${state}`] : [];
25
+ });
26
+ return ` · +${board.omitted.total} not shown (${counts.join(" · ")}) — keiyaku status --all`;
27
+ }
18
28
  function renderProjectionAge(row) {
19
29
  if (row.state === "done")
20
30
  return "done";
@@ -63,18 +73,19 @@ function renderProjectionRow(row) {
63
73
  : row.state === "startup-timeout"
64
74
  ? "minting"
65
75
  : row.state);
76
+ const identity = row.alias ? `${row.id} (${row.alias})` : row.id;
66
77
  let heading;
67
78
  if (row.state === "startup-timeout" || row.state === "unknown") {
68
79
  const akuma = (row.akuma ?? "(unknown)").padEnd(18);
69
- heading = ` ${glyph} ${row.id} ${akuma} ${renderProjectionAge(row)}`.trimEnd();
80
+ heading = ` ${glyph} ${identity} ${akuma} ${renderProjectionAge(row)}${renderProjectionDiagnostics(row)}`.trimEnd();
70
81
  }
71
82
  else if (row.state === "minting") {
72
- heading = ` ${glyph} ${row.id.padEnd(18)} (minting)${renderProjectionDiagnostics(row)}`.trimEnd();
83
+ heading = ` ${glyph} ${identity.padEnd(18)} (minting)${renderProjectionDiagnostics(row)}`.trimEnd();
73
84
  }
74
85
  else {
75
86
  const age = renderProjectionAge(row).padEnd(9);
76
87
  const akuma = (row.akuma ?? "(unknown)").padEnd(18);
77
- heading = ` ${glyph} ${row.id} ${akuma} ${age}${renderProjectionTellCounts(row)}${renderProjectionDiagnostics(row)}`.trimEnd();
88
+ heading = ` ${glyph} ${identity} ${akuma} ${age}${renderProjectionTellCounts(row)}${renderProjectionDiagnostics(row)}`.trimEnd();
78
89
  }
79
90
  const activity = renderProjectionActivityLine(row);
80
91
  return activity ? [heading, activity] : [heading];
@@ -82,7 +93,11 @@ function renderProjectionRow(row) {
82
93
  export function renderProjectionStatusSection(board) {
83
94
  if (!board || board.rows.length === 0)
84
95
  return "";
85
- return `${STATUS_PROJECTION_SECTION_TITLE}\n${board.rows.flatMap(renderProjectionRow).join("\n")}\n`;
96
+ const lines = board.rows.flatMap(renderProjectionRow);
97
+ const omission = renderProjectionOmission(board);
98
+ if (omission)
99
+ lines.push(omission);
100
+ return `${STATUS_PROJECTION_SECTION_TITLE}\n${lines.join("\n")}\n`;
86
101
  }
87
102
  function renderClaimReconciliationSection(recon) {
88
103
  if (!recon)
@@ -3,7 +3,7 @@ import { displayColumns, resolveLineColumns, truncateColumns } from "./line-widt
3
3
  import { projectionRejoinHintLines, textResponse } from "./shared.js";
4
4
  const WAIT_HEADER_MAX_COLUMNS = 60;
5
5
  function shortAddress(projectionId) {
6
- return /-([0-9a-f]{8})$/.exec(projectionId)?.[1] ?? projectionId;
6
+ return /\/([0-9a-f]{8})$/.exec(projectionId)?.[1] ?? projectionId;
7
7
  }
8
8
  function phaseState(phase) {
9
9
  switch (phase) {
@@ -42,7 +42,7 @@ function waitStateText(snapshot) {
42
42
  }
43
43
  }
44
44
  function projectionIdentityParts(projectionId) {
45
- const match = /^(.*)-([0-9a-f]{8})$/.exec(projectionId);
45
+ const match = /^(.*)\/([0-9a-f]{8})$/.exec(projectionId);
46
46
  return match ? { slug: match[1], shortId: match[2] } : { slug: "", shortId: projectionId };
47
47
  }
48
48
  function waitHeader(projectionId, akuma, snapshot, outputColumns) {
@@ -215,7 +215,7 @@ export function buildWaitResponse(projectionId, result, options = {}) {
215
215
  if (rendered.lines.length === 0 && state === "running") {
216
216
  lines.push(" not a word out of it yet — pulse is steady");
217
217
  }
218
- const nextActions = nextActionLines(projectionId, result, state);
218
+ const nextActions = nextActionLines(options.alias ?? projectionId, result, state);
219
219
  if (rendered.hasMutationFiles && nextActions.length > 0)
220
220
  lines.push("");
221
221
  lines.push(...nextActions);
@@ -0,0 +1,39 @@
1
+ const EMPTY_OWNERSHIP = Object.freeze({
2
+ access: Object.freeze([]),
3
+ network: Object.freeze([]),
4
+ webSearch: Object.freeze([]),
5
+ });
6
+ function owns(config, key) {
7
+ return Object.prototype.hasOwnProperty.call(config, key);
8
+ }
9
+ export function nativePolicyOwnership(instanceName, instance) {
10
+ if (instance.kind !== "codex-sdk" && instance.kind !== "codex-app-server") {
11
+ return EMPTY_OWNERSHIP;
12
+ }
13
+ const config = instance.config;
14
+ if (!config)
15
+ return EMPTY_OWNERSHIP;
16
+ const access = [];
17
+ const network = [];
18
+ const webSearch = [];
19
+ const add = (target, key) => {
20
+ target.push({ path: `providers.${instanceName}.config.${key}`, value: config[key] });
21
+ };
22
+ if (owns(config, "sandbox_mode"))
23
+ add(access, "sandbox_mode");
24
+ if (owns(config, "approval_policy"))
25
+ add(access, "approval_policy");
26
+ for (const key of Object.keys(config)) {
27
+ if (key !== "sandbox_workspace_write" && !key.startsWith("sandbox_workspace_write."))
28
+ continue;
29
+ add(access, key);
30
+ add(network, key);
31
+ }
32
+ if (owns(config, "web_search"))
33
+ add(webSearch, "web_search");
34
+ return Object.freeze({
35
+ access: Object.freeze(access),
36
+ network: Object.freeze(network),
37
+ webSearch: Object.freeze(webSearch),
38
+ });
39
+ }
@@ -41,19 +41,9 @@ const CODEX_SDK_FORBIDDEN_THREAD_OPTIONS_KEYS = [
41
41
  const FORBIDDEN_CONFIG_KEYS = [
42
42
  "model",
43
43
  "model_reasoning_effort",
44
- "sandbox_mode",
45
- "sandbox_workspace_write",
46
- "web_search",
47
- "approval_policy",
48
44
  ];
49
45
  function isConfigKeyForbidden(key) {
50
- // Exact match for model, model_reasoning_effort, sandbox_mode, web_search, approval_policy
51
- if (FORBIDDEN_CONFIG_KEYS.includes(key))
52
- return true;
53
- // sandbox_workspace_write also rejects dotted subkeys from adapter-native TOML flattening
54
- if (key.startsWith("sandbox_workspace_write."))
55
- return true;
56
- return false;
46
+ return FORBIDDEN_CONFIG_KEYS.includes(key);
57
47
  }
58
48
  const claudeSettingSourceSchema = z.enum(CLAUDE_SETTING_SOURCES);
59
49
  const writableRootItemSchema = z.string().superRefine((value, ctx) => {