@astrosheep/keiyaku 1.0.2 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,8 +54,12 @@ export function buildCallTermsV2(snapshot, prompt, cwd, options) {
54
54
  ? { config: snapshot.provider.config }
55
55
  : {}),
56
56
  ...(snapshot.provider.executable !== undefined ? { executable: snapshot.provider.executable } : {}),
57
- sandboxPolicy: snapshot.provider.sandboxPolicy,
58
- approvalPolicy: snapshot.provider.approvalPolicy,
57
+ ...(snapshot.provider.sandboxPolicy !== undefined
58
+ ? { sandboxPolicy: snapshot.provider.sandboxPolicy }
59
+ : {}),
60
+ ...(snapshot.provider.approvalPolicy !== undefined
61
+ ? { approvalPolicy: snapshot.provider.approvalPolicy }
62
+ : {}),
59
63
  developerInstructions: body || undefined,
60
64
  onCheckpoint() { },
61
65
  };
@@ -202,7 +202,11 @@ async function settleProjection(shared) {
202
202
  ? launchTellFence
203
203
  : (() => { throw new FlowError("INTERNAL_STATE", "generation launch has an invalid tellFence"); })();
204
204
  const launchSnapshot = shared.terms.projectionDir && shared.terms.projectionExecutionId
205
- ? snapshotReplayPrompt(shared.terms.projectionDir, shared.terms.prompt, tellFence, shared.terms.projectionExecutionId)
205
+ ? snapshotReplayPrompt(shared.terms.projectionDir, shared.terms.prompt, tellFence, shared.terms.projectionExecutionId,
206
+ // Session resume already holds launch history. Only fenced tells are
207
+ // new operator input; re-blasting the original prompt reasserts stale
208
+ // allowlists and breaks resting continuation.
209
+ { sessionContinuation: shared.reviveTerms !== undefined })
206
210
  : undefined;
207
211
  const launchTerms = launchSnapshot
208
212
  ? shared.terms.provider === "codex-sdk" && launchSnapshot.effort
@@ -1,14 +1,31 @@
1
1
  import { claimAllInbox, claimFencedTells, listTellIds, markTellSubmitted, markTellConsumed, readTellSubmitted, readTellOriginal, isTellCausallyConsumed, appendGenerationEvent, } from "../../core/projection-core.js";
2
2
  import { prepareAgentEventForPersistence, } from "./execution-handle.js";
3
+ const OPERATOR_PROMPT_MARKER = "[Operator prompt]\n";
4
+ function frameTellBodies(projectionDirectory, tellIds) {
5
+ const originals = tellIds.map((tellId) => readTellOriginal(projectionDirectory, "inflight", tellId));
6
+ const frames = originals
7
+ .map((original, index) => `--- tell ${tellIds[index]} ---\n${original.text}`)
8
+ .join("\n\n");
9
+ const effort = originals.reduce((value, original) => original.effort ?? value, undefined);
10
+ return { frames, ...(effort ? { effort } : {}) };
11
+ }
12
+ function operatorBodyForContinuation(prompt, tellFrames, sessionContinuation) {
13
+ if (!sessionContinuation)
14
+ return `${prompt}\n\n${tellFrames}`;
15
+ const markerAt = prompt.indexOf(OPERATOR_PROMPT_MARKER);
16
+ if (markerAt >= 0) {
17
+ return `${prompt.slice(0, markerAt + OPERATOR_PROMPT_MARKER.length)}${tellFrames}`;
18
+ }
19
+ return tellFrames;
20
+ }
3
21
  /** Tier-2 boundary snapshot. No tells leaves prompt byte-identical. */
4
- export function snapshotReplayPrompt(projectionDirectory, prompt, tellFence, executionId) {
22
+ export function snapshotReplayPrompt(projectionDirectory, prompt, tellFence, executionId, options = {}) {
5
23
  const tellIds = claimFencedTells(projectionDirectory, tellFence, executionId);
6
24
  if (tellIds.length === 0)
7
25
  return { prompt, tellIds };
8
- const originals = tellIds.map((tellId) => readTellOriginal(projectionDirectory, "inflight", tellId));
9
- const frames = originals.map((original, index) => `--- tell ${tellIds[index]} ---\n${original.text}`);
10
- const effort = originals.reduce((value, original) => original.effort ?? value, undefined);
11
- return { prompt: `${prompt}\n\n${frames.join("\n\n")}`, tellIds, ...(effort ? { effort } : {}) };
26
+ const { frames, effort } = frameTellBodies(projectionDirectory, tellIds);
27
+ const nextPrompt = operatorBodyForContinuation(prompt, frames, options.sessionContinuation === true);
28
+ return { prompt: nextPrompt, tellIds, ...(effort ? { effort } : {}) };
12
29
  }
13
30
  /** Tier-2 has no steer acknowledgement: actual driver start is its ordinal-zero submission point. */
14
31
  export function markReplayStarted(projectionDirectory, executionId, tellIds) {
@@ -1,6 +1,7 @@
1
1
  import * as path from "node:path";
2
+ import { nativePolicyOwnership } from "../config/provider-policy-ownership.js";
2
3
  import { FlowError } from "../flow-error.js";
3
- import { buildCodexAppServerWorkspaceWriteSandboxPolicy, } from "./providers/codex-app-server.js";
4
+ import { buildCodexAppServerWorkspaceWriteSandboxPolicy, configOwnsApprovalPolicy, configOwnsSandboxPolicy, } from "./providers/codex-app-server.js";
4
5
  const ACCESS_VALUES = new Set(["read", "write", "auto"]);
5
6
  const NETWORK_VALUES = new Set(["disabled", "enabled"]);
6
7
  const WEB_SEARCH_VALUES = new Set(["disabled", "cached", "live"]);
@@ -198,16 +199,15 @@ function parseProvider(value) {
198
199
  }
199
200
  case "codex-app-server": {
200
201
  exactKeys(provider, ["name", "source", "kind", "executable", "profile", "config", "model", "effort", "sandboxPolicy", "approvalPolicy"], "provider");
201
- if (!("sandboxPolicy" in provider))
202
- throw snapshotError("provider.sandboxPolicy", "is required");
203
- if (provider.approvalPolicy !== "never")
202
+ if ("approvalPolicy" in provider && provider.approvalPolicy !== "never")
204
203
  throw snapshotError("provider.approvalPolicy", "must equal 'never'");
205
204
  const executable = optionalString(provider, "executable", "provider");
206
205
  const profile = optionalString(provider, "profile", "provider");
207
206
  const config = optionalJsonObject(provider, "config");
208
207
  const model = optionalString(provider, "model", "provider");
209
208
  const effort = optionalString(provider, "effort", "provider");
210
- return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(profile !== undefined ? { profile } : {}), ...(config !== undefined ? { config } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), sandboxPolicy: parseSandboxPolicy(provider.sandboxPolicy), approvalPolicy: "never" });
209
+ const sandboxPolicy = "sandboxPolicy" in provider ? parseSandboxPolicy(provider.sandboxPolicy) : undefined;
210
+ return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(profile !== undefined ? { profile } : {}), ...(config !== undefined ? { config } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(sandboxPolicy !== undefined ? { sandboxPolicy } : {}), ...(provider.approvalPolicy === "never" ? { approvalPolicy: "never" } : {}) });
211
211
  }
212
212
  case "claude-agent-sdk": {
213
213
  exactKeys(provider, ["name", "source", "kind", "executable", "model", "effort", "settingSources", "permissionMode", "allowDangerouslySkipPermissions", "disallowedTools"], "provider");
@@ -295,9 +295,14 @@ export function resolveAkumaLaunchSnapshot(selected, input) {
295
295
  switch (instance.kind) {
296
296
  case "codex-sdk": {
297
297
  const threadOptions = { ...(instance.threadOptions ?? {}) };
298
- threadOptions.sandboxMode = selected.profile.access === "read" ? "read-only" : selected.profile.access === "auto" ? "danger-full-access" : "workspace-write";
299
- threadOptions.networkAccessEnabled = selected.profile.network === "enabled";
300
- threadOptions.webSearchMode = "disabled";
298
+ const ownership = nativePolicyOwnership(selected.profile.provider, instance);
299
+ if (ownership.access.length === 0) {
300
+ threadOptions.sandboxMode = selected.profile.access === "read" ? "read-only" : selected.profile.access === "auto" ? "danger-full-access" : "workspace-write";
301
+ }
302
+ if (ownership.network.length === 0)
303
+ threadOptions.networkAccessEnabled = selected.profile.network === "enabled";
304
+ if (ownership.webSearch.length === 0)
305
+ threadOptions.webSearchMode = selected.profile.webSearch ?? "disabled";
301
306
  if (model !== undefined)
302
307
  threadOptions.model = model;
303
308
  if (effort !== undefined)
@@ -310,7 +315,7 @@ export function resolveAkumaLaunchSnapshot(selected, input) {
310
315
  case "codex-app-server": {
311
316
  const roots = [path.resolve(input.executionCwd), ...(instance.writableRoots ?? []).map((root) => path.resolve(input.projectRoot, root))];
312
317
  const writableRoots = [...new Set(roots)];
313
- provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(instance.profile !== undefined ? { profile: instance.profile } : {}), ...(instance.config !== undefined ? { config: instance.config } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), sandboxPolicy: buildCodexAppServerWorkspaceWriteSandboxPolicy({ writableRoots, networkAccess: selected.profile.network === "enabled" }), approvalPolicy: "never" };
318
+ provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(instance.profile !== undefined ? { profile: instance.profile } : {}), ...(instance.config !== undefined ? { config: instance.config } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(!configOwnsSandboxPolicy(instance.config) ? { sandboxPolicy: buildCodexAppServerWorkspaceWriteSandboxPolicy({ writableRoots, networkAccess: selected.profile.network === "enabled" }) } : {}), ...(!configOwnsApprovalPolicy(instance.config) ? { approvalPolicy: "never" } : {}) };
314
319
  break;
315
320
  }
316
321
  case "claude-agent-sdk": {
@@ -1,6 +1,7 @@
1
1
  import { getConfig } from "../config/env.js";
2
2
  import { loadAkumaCatalogV2, } from "../config/akuma-loader_v2.js";
3
3
  import { assertSettingsKnobUsable, loadKeiyakuSettings, resolveDefaultAgentName, } from "../config/settings.js";
4
+ import { nativePolicyOwnership } from "../config/provider-policy-ownership.js";
4
5
  import { FlowError } from "../flow-error.js";
5
6
  const ACCESS_VALUES = ["read", "write", "auto"];
6
7
  const NETWORK_VALUES = ["disabled", "enabled"];
@@ -8,7 +9,7 @@ const SUPPORTED_EXPLICIT = {
8
9
  "codex-sdk": {
9
10
  access: new Set(ACCESS_VALUES),
10
11
  network: new Set(NETWORK_VALUES),
11
- webSearch: new Set(["disabled"]),
12
+ webSearch: new Set(["disabled", "cached", "live"]),
12
13
  },
13
14
  "codex-app-server": {
14
15
  access: new Set(["write"]),
@@ -56,6 +57,15 @@ export function assertPortablePolicySupported(adapter, policy) {
56
57
  }
57
58
  }
58
59
  }
60
+ function assertPortablePolicyOwnership(akumaName, instanceName, instance, policy) {
61
+ const ownership = nativePolicyOwnership(instanceName, instance);
62
+ for (const axis of ["access", "network", "webSearch"]) {
63
+ if (policy[axis] === undefined || ownership[axis].length === 0)
64
+ continue;
65
+ const settingsPaths = ownership[axis].map((owner) => `settings.${owner.path}`).join(", ");
66
+ throw new FlowError("INVALID_SETTINGS", `Akuma '${akumaName}' declares frontmatter.${axis}, but provider instance '${instanceName}' also owns that policy at ${settingsPaths}. Remove one declaration.`);
67
+ }
68
+ }
59
69
  function isValidAkumaCandidate(candidate) {
60
70
  return !("reason" in candidate && candidate.reason !== undefined) && candidate.profile !== undefined && candidate.body !== undefined;
61
71
  }
@@ -96,11 +106,13 @@ export async function selectSubagentV2(agentName, cwd) {
96
106
  }
97
107
  const providerInstance = providerCandidate.instance;
98
108
  const adapter = providerInstance.kind;
99
- assertPortablePolicySupported(adapter, {
109
+ const portablePolicy = {
100
110
  ...(candidate.profile.access !== undefined ? { access: candidate.profile.access } : {}),
101
111
  ...(candidate.profile.network !== undefined ? { network: candidate.profile.network } : {}),
102
112
  ...(candidate.profile.webSearch !== undefined ? { webSearch: candidate.profile.webSearch } : {}),
103
- });
113
+ };
114
+ assertPortablePolicyOwnership(selected, instanceName, providerInstance, portablePolicy);
115
+ assertPortablePolicySupported(adapter, portablePolicy);
104
116
  const profile = freezeResolvedProfile({
105
117
  provider: instanceName,
106
118
  adapter,
@@ -1,5 +1,6 @@
1
1
  import { resolveDefaultAgentName, resolveReviewerAgentName, } from "../../config/settings.js";
2
2
  import { FlowError } from "../../flow-error.js";
3
+ import { nativePolicyOwnership } from "../../config/provider-policy-ownership.js";
3
4
  function formatRoleMarkers(settings, name) {
4
5
  const markers = [];
5
6
  if (name === resolveDefaultAgentName(settings))
@@ -81,6 +82,25 @@ function appendPromptPreview(lines, body) {
81
82
  lines.push(...promptLines.slice(0, 8).map((line) => ` ${line}`));
82
83
  lines.push(`(${promptLines.length} lines total)`);
83
84
  }
85
+ function appendPolicyLines(lines, resolved) {
86
+ const ownership = nativePolicyOwnership(resolved.instanceName, resolved.instance);
87
+ const axes = ["access", "network", "webSearch"];
88
+ for (const axis of axes) {
89
+ const value = resolved.profile[axis];
90
+ if (value !== undefined) {
91
+ lines.push(`${axis}: ${value} [source: akuma frontmatter.${axis}]`);
92
+ continue;
93
+ }
94
+ const owners = ownership[axis];
95
+ if (owners.length === 0)
96
+ continue;
97
+ if (axis === "webSearch") {
98
+ lines.push(`webSearch: ${JSON.stringify(owners[0].value)} [source: instance config ${owners[0].path}]`);
99
+ continue;
100
+ }
101
+ lines.push(`${axis}: native [source: instance config ${owners.map((owner) => owner.path).join(", ")}]`);
102
+ }
103
+ }
84
104
  function chooseCallHintName(validNames, settings) {
85
105
  if (validNames.length === 0)
86
106
  return null;
@@ -149,9 +169,7 @@ export function renderAkumaShow(catalog, providerInstances, settings, name) {
149
169
  appendOptionalLine(lines, "model", resolved.profile.model);
150
170
  appendOptionalLine(lines, "effort", resolved.profile.effort);
151
171
  appendOptionalLine(lines, "effortOptions", resolved.profile.effortOptions);
152
- appendOptionalLine(lines, "access", resolved.profile.access);
153
- appendOptionalLine(lines, "network", resolved.profile.network);
154
- appendOptionalLine(lines, "webSearch", resolved.profile.webSearch);
172
+ appendPolicyLines(lines, resolved);
155
173
  appendPromptPreview(lines, resolved.body);
156
174
  return lines.join("\n");
157
175
  }
@@ -35,7 +35,10 @@ export async function renderCompletionCandidates(input) {
35
35
  if (word.startsWith("@")) {
36
36
  return (await contractAddressCandidates(input.cwd, word)).join("\n");
37
37
  }
38
- if (input.previous === "call" || input.previous === "show") {
38
+ if (input.previous === "call") {
39
+ return filterPrefix([...(await akumaCandidates(input.cwd, word)), "--alias"], word).join("\n");
40
+ }
41
+ if (input.previous === "show") {
39
42
  return (await akumaCandidates(input.cwd, word)).join("\n");
40
43
  }
41
44
  if (input.previous === "akuma") {
@@ -34,7 +34,12 @@ export const FLAG_SPECS = {
34
34
  projection: {
35
35
  kind: "value",
36
36
  placeholder: "ID",
37
- description: "Akuma projection id.",
37
+ description: "Akuma projection address.",
38
+ },
39
+ alias: {
40
+ kind: "value",
41
+ placeholder: "WORD",
42
+ description: "Move a projection alias to this newly launched call.",
38
43
  },
39
44
  timeout: {
40
45
  kind: "value",
@@ -108,6 +113,10 @@ export const FLAG_SPECS = {
108
113
  placeholder: "REF",
109
114
  description: "Filter target-keyed status facts by branch name or full refs/heads/<name>.",
110
115
  },
116
+ all: {
117
+ kind: "boolean",
118
+ description: "Show all retained Akuma projections while preserving status priority order.",
119
+ },
111
120
  };
112
121
  export const CONTROL_FLAGS = new Set(Object.keys(FLAG_SPECS));
113
122
  export const VALUE_FLAGS = new Set(Object.entries(FLAG_SPECS)
package/build/cli/help.js CHANGED
@@ -10,9 +10,9 @@ export const CLI_COMMAND_METADATA = {
10
10
  input: ["# <arc title>", "", "## Objective", "<what this arc should accomplish>", "", "## Brief", "<scope, constraints, relevant files, instructions>"],
11
11
  notes: ["An empty seal is valid.", "The command does not create delivery commits."],
12
12
  }),
13
- call: command("call", "Start an Akuma helper session", ["keiyaku call NAME [prompt|-]", "keiyaku call NAME --detach [prompt|-]", "keiyaku @addr call NAME [prompt|-]", "keiyaku call NAME --contract ADDR [prompt|-]"], "none", ["cwd", "repo", "contract", "akuma", "model", "effort", "incognito", "bare", "detach"], "Run a scoped helper task."),
13
+ call: command("call", "Start an Akuma helper session", ["keiyaku call NAME [--alias WORD] [prompt|-]", "keiyaku call NAME --detach [prompt|-]", "keiyaku @addr call NAME [prompt|-]", "keiyaku call NAME --contract ADDR [prompt|-]"], "none", ["cwd", "repo", "contract", "akuma", "model", "effort", "incognito", "bare", "detach", "alias"], "Run a scoped helper task."),
14
14
  revive: command("revive", "Revive an Akuma helper session", ["keiyaku revive ARTIFACT_ID"], "none", ["cwd", "repo", "model", "effort"], "Continue a persisted helper session."),
15
- wait: command("wait", "Wait for an akuma projection", ["keiyaku wait <short-id> [--timeout DURATION]", "keiyaku @addr wait <short-id> [--timeout DURATION]"], "none", ["cwd", "repo", "contract", "timeout"], "Read an akuma projection until its outcome is available, or show a timeout snapshot."),
15
+ wait: command("wait", "Wait for an akuma projection", ["keiyaku wait <projection-address> [--timeout DURATION]", "keiyaku @addr wait <projection-address> [--timeout DURATION]"], "none", ["cwd", "repo", "contract", "timeout"], "Read an akuma projection until its outcome is available, or show a timeout snapshot."),
16
16
  tell: command("tell", "Send intent to a projection mailbox", ["keiyaku tell [--effort LEVEL] PROJECTION MESSAGE", "keiyaku @addr tell [--effort LEVEL] PROJECTION MESSAGE"], "none", ["cwd", "repo", "contract", "effort"], "Write intent to the mailbox and wake only when eligible."),
17
17
  log: command("log", "Render a contract ledger", ["keiyaku log [-C|--cwd DIR] [--repo DIR] [--contract ADDR]", "keiyaku @addr log [-C|--cwd DIR] [--repo DIR]"], "none", ["cwd", "repo", "contract"], "Read a contract ledger."),
18
18
  akuma: command("akuma", "Inspect Akuma profiles", ["keiyaku akuma list|show"], "none", [], "Inspect configured helper profiles.", {
@@ -31,7 +31,7 @@ export const CLI_COMMAND_METADATA = {
31
31
  forfeit: command("forfeit", "Forfeit an addressed contract", ["keiyaku forfeit [-C|--cwd DIR] [--repo DIR] [--contract ADDR] [--reason TEXT]", "keiyaku @addr forfeit [-C|--cwd DIR] [--repo DIR] [--reason TEXT]"], "none", ["cwd", "repo", "contract", "reason"], "Record an abandoned contract without sealing it."),
32
32
  guide: command("guide", "Print the Keiyaku guide", ["keiyaku guide"], "none", [], "Print workflow guidance."),
33
33
  completion: command("completion", "Print shell completion", ["keiyaku completion [--shell bash|zsh]"], "none", ["cwd", "repo", "shell", "word", "previous", "complete"], "Print command and address candidates."),
34
- status: command("status", "Show the Kanshi board", ["keiyaku status [-C|--cwd DIR] [--repo DIR] [--target REF]"], "none", ["cwd", "repo", "target"], "Read contract, queue, and projection facts without mutation."),
34
+ status: command("status", "Show the Kanshi board", ["keiyaku status [-C|--cwd DIR] [--repo DIR] [--target REF] [--all]"], "none", ["cwd", "repo", "target", "all"], "Read contract, queue, and projection facts without mutation."),
35
35
  "dump-env": command("dump-env", "Print the environment template", ["keiyaku dump-env"], "none", [], "Print supported environment keys.", { notes: ["Keiyaku loads <KEIYAKU_HOME>/.env, then .keiyaku/.env; process environment values win."] }),
36
36
  };
37
37
  function indent(lines) {
@@ -6,7 +6,7 @@ import { amendKeiyaku } from "../core/amend.js";
6
6
  import { arcKeiyaku, buildArcInput } from "../core/arc.js";
7
7
  import { bindContract } from "../core/bind.js";
8
8
  import { buildAmendInput, defaultActorClock } from "../core/command-io.js";
9
- import { AddressResolutionError, buildResolutionAttempt, resolveContractAddress, withResolvedAddress, } from "../core/addressing.js";
9
+ import { AddressResolutionError, buildArtifactResolutionAttempt, buildResolutionAttempt, resolveContractAddress, withResolvedAddress, } from "../core/addressing.js";
10
10
  import { forfeitContractCommand } from "../core/forfeit.js";
11
11
  import { readContractLog } from "../core/log.js";
12
12
  import { buildPetitionClaimInput } from "../core/petition.js";
@@ -14,6 +14,7 @@ import { petitionKeiyaku } from "../core/petition-run.js";
14
14
  import { renewKeiyaku } from "../core/renew.js";
15
15
  import { normalizeTargetRef, readKanshiBoard } from "../core/status.js";
16
16
  import { runCallAndPersist } from "../core/call-persist.js";
17
+ import { isResponseArtifactId } from "../core/response-artifact-id.js";
17
18
  import { locateResponseArtifact } from "../core/transcripts.js";
18
19
  import { renderEnvExample } from "../config/env-keys.js";
19
20
  import { assertSettingsKnobUsable, loadKeiyakuSettings } from "../config/settings.js";
@@ -41,6 +42,7 @@ import { prependAddressReceipt, renderResolvedAddressReceipt } from "./render/ad
41
42
  import { installOfficialSkills } from "./skills-install.js";
42
43
  import { isGitRepo } from "../git/branches.js";
43
44
  import { stableRepoRoot } from "../core/worktree-path.js";
45
+ import { assertProjectionAlias } from "../core/projection-alias.js";
44
46
  const CALL_INPUT_HINTS = [
45
47
  "Call input can be a prompt argument, `-` to read stdin, or omitted to read stdin.",
46
48
  "Use `keiyaku call NAME \"prompt\"`, `keiyaku call NAME - < prompt.md`, or `keiyaku revive ARTIFACT_ID`.",
@@ -79,6 +81,7 @@ function buildCallInput(content, flags, mode = "call") {
79
81
  incognito: flags.incognito,
80
82
  bare: flags.bare,
81
83
  detach: flags.detach,
84
+ ...(mode === "call" && flags.alias !== undefined ? { alias: assertProjectionAlias(flags.alias) } : {}),
82
85
  };
83
86
  }
84
87
  function revivePrompt(stdin) {
@@ -96,7 +99,7 @@ async function repositoryDirectory(flags) {
96
99
  if (flags.contractId) {
97
100
  const asTyped = flags.contractAddressSource === "at-address"
98
101
  ? `@${flags.contractId}`
99
- : flags.contractId;
102
+ : `--contract ${flags.contractId}`;
100
103
  throw new AddressResolutionError("EMPTY_PARAM", `${asTyped} is a commission address, but ${candidate} has no repository ledger; contract addressing requires a ledger`, buildResolutionAttempt({
101
104
  repo: { kind: "user" },
102
105
  workdir: effectiveDirectory(flags),
@@ -142,13 +145,13 @@ const CLI_COMMAND_SPECS = {
142
145
  ...(!flags.detach
143
146
  ? {
144
147
  onLaunch: ({ address, projection }) => {
145
- process.stdout.write(`${renderAdoptedLaunchReceipt(address, projection.id)}\n`);
148
+ process.stdout.write(`${renderAdoptedLaunchReceipt(address, projection.id, projection.alias, projection.previousAliasTarget)}\n`);
146
149
  },
147
150
  }
148
151
  : {}),
149
152
  });
150
153
  if (outcome.result.foregroundWait && outcome.result.projection) {
151
- return buildWaitResponse(outcome.result.projection.id, outcome.result.foregroundWait);
154
+ return buildWaitResponse(outcome.result.projection.id, outcome.result.foregroundWait, { alias: outcome.result.projection.alias });
152
155
  }
153
156
  return buildCallResponse(outcome.result, {
154
157
  responsePath: outcome.responsePath,
@@ -160,8 +163,20 @@ const CLI_COMMAND_SPECS = {
160
163
  const cwd = effectiveDirectory(flags);
161
164
  const artifactCwd = await projectDirectory(flags);
162
165
  const artifactId = positional[0];
163
- const responseStoreCwd = await isGitRepo(artifactCwd) ? await stableRepoRoot(artifactCwd) : await fs.realpath(artifactCwd);
164
- const artifact = await locateResponseArtifact(responseStoreCwd, artifactId);
166
+ const isRepo = await isGitRepo(artifactCwd);
167
+ const responseStoreCwd = isRepo ? await stableRepoRoot(artifactCwd) : await fs.realpath(artifactCwd);
168
+ const artifact = await locateResponseArtifact({
169
+ cwd: responseStoreCwd,
170
+ artifactId,
171
+ attempt: buildArtifactResolutionAttempt({
172
+ repo: isRepo
173
+ ? { kind: "repository", stableRoot: responseStoreCwd }
174
+ : { kind: "user" },
175
+ workdir: cwd,
176
+ supplied: artifactId,
177
+ inference: ["response artifact identity lookup"],
178
+ }),
179
+ });
165
180
  let executionCwd;
166
181
  let repo;
167
182
  if (artifact.provenance.authority.kind === "repository") {
@@ -186,20 +201,30 @@ const CLI_COMMAND_SPECS = {
186
201
  const cwd = flags.contractId ? await repositoryDirectory(flags) : await projectDirectory(flags);
187
202
  const address = flags.contractId ? await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource) : undefined;
188
203
  const operation = async () => {
189
- const projectionId = resolveProjectionAddress(cwd, positional[0], address?.binding.commissionId);
190
- return prependAddressReceipt(buildWaitResponse(projectionId, await waitForProjection({ projectionDirectory: projectionDir(cwd, projectionId), timeoutMs: flags.timeoutMs, signal })), address, { projectionId });
204
+ const projection = await resolveProjectionAddress({
205
+ cwd,
206
+ address: positional[0],
207
+ scope: address,
208
+ verb: "wait",
209
+ });
210
+ return prependAddressReceipt(buildWaitResponse(projection.id, await waitForProjection({ projectionDirectory: projectionDir(cwd, projection.id), timeoutMs: flags.timeoutMs, signal }), { alias: projection.alias }), address, { projectionId: projection.id, projectionAlias: projection.alias });
191
211
  };
192
212
  return address ? await withResolvedAddress(address, operation) : await operation();
193
213
  }),
194
214
  tell: commandSpec("tell", async (_stdin, flags, _signal, positional) => {
195
215
  const cwd = flags.contractId ? await repositoryDirectory(flags) : await projectDirectory(flags);
196
- const [prefix, message] = positional;
197
- if (!prefix || !message)
198
- throw new FlowError("EMPTY_PARAM", "tell requires a projection and message");
216
+ const [projectionAddress, message] = positional;
217
+ if (!projectionAddress || !message)
218
+ throw new FlowError("EMPTY_PARAM", "tell requires a projection address and message");
199
219
  const address = flags.contractId ? await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource) : undefined;
200
220
  const operation = async () => {
201
- const projectionId = resolveProjectionAddress(cwd, prefix, address?.binding.commissionId);
202
- const directory = projectionDir(cwd, projectionId);
221
+ const projection = await resolveProjectionAddress({
222
+ cwd,
223
+ address: projectionAddress,
224
+ scope: address,
225
+ verb: "tell",
226
+ });
227
+ const directory = projectionDir(cwd, projection.id);
203
228
  assertTellEffortSupported(directory, flags.effort);
204
229
  // Mailbox first: do not inspect phase before this immutable write.
205
230
  writeInboxTell(directory, message, Date.now(), { effort: flags.effort });
@@ -232,6 +257,9 @@ const CLI_COMMAND_SPECS = {
232
257
  if (!name) {
233
258
  throw new FlowError("EMPTY_PARAM", "akuma show requires an Akuma name");
234
259
  }
260
+ if (isResponseArtifactId(name)) {
261
+ throw new FlowError("EMPTY_PARAM", `artifact ${name} is a response artifact; akuma show expects an Akuma profile name`);
262
+ }
235
263
  const cwd = await projectDirectory(flags);
236
264
  const loaded = await loadKeiyakuSettings(cwd);
237
265
  // Role markers need settings pointers; catalog consumers keep legacy agents disease actionable.
@@ -288,7 +316,10 @@ const CLI_COMMAND_SPECS = {
288
316
  // honors --repo as a repository coordinate when the operator names one.
289
317
  const cwd = await projectDirectory(flags);
290
318
  const target = flags.target === undefined ? undefined : normalizeTargetRef(flags.target);
291
- return textResponse(renderStatusBoard(await readKanshiBoard(cwd, Date.now(), target === undefined ? undefined : { target })));
319
+ return textResponse(renderStatusBoard(await readKanshiBoard(cwd, Date.now(), {
320
+ ...(target === undefined ? {} : { target }),
321
+ showAllProjections: flags.all === true,
322
+ })));
292
323
  }),
293
324
  "dump-env": commandSpec("dump-env", async () => textResponse(renderEnvExample())),
294
325
  };
@@ -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([
@@ -40,7 +41,7 @@ const COMMAND_FLAGS = {
40
41
  // contract is parseable on bind only so the mouth can reject it with bind-specific copy.
41
42
  bind: ["cwd", "repo", "contract", "after", "exclusive", "draft-only"],
42
43
  arc: ["cwd", "repo", "contract", "waive"],
43
- call: ["cwd", "repo", "contract", "akuma", "model", "effort", "incognito", "bare", "detach"],
44
+ call: ["cwd", "repo", "contract", "akuma", "model", "effort", "incognito", "bare", "detach", "alias"],
44
45
  revive: ["cwd", "repo", "model", "effort"],
45
46
  wait: ["cwd", "repo", "contract", "timeout"],
46
47
  tell: ["cwd", "repo", "contract", "effort"],
@@ -57,7 +58,7 @@ const COMMAND_FLAGS = {
57
58
  forfeit: ["cwd", "repo", "contract", "reason"],
58
59
  guide: [],
59
60
  completion: ["cwd", "repo", "shell", "word", "previous", "complete"],
60
- status: ["cwd", "repo", "target"],
61
+ status: ["cwd", "repo", "target", "all"],
61
62
  "dump-env": [],
62
63
  };
63
64
  const STDIN_COMMANDS = new Set(["bind", "arc", "amend"]);
@@ -95,7 +96,7 @@ export function commandPositionalRange(command) {
95
96
  if (command === "revive")
96
97
  return { min: 1, max: 1, label: "<artifact-id>" };
97
98
  if (command === "wait")
98
- return { min: 1, max: 1, label: "<short-id>" };
99
+ return { min: 1, max: 1, label: "<projection-address>" };
99
100
  if (command === "tell")
100
101
  return { min: 2, max: 2, label: "<projection> <message>" };
101
102
  if (isCallCommand(command))
@@ -129,6 +130,9 @@ function assignFlag(flags, name, value) {
129
130
  case "projection":
130
131
  flags.projection = String(value);
131
132
  return;
133
+ case "alias":
134
+ flags.alias = String(value);
135
+ return;
132
136
  case "timeout":
133
137
  flags.timeoutMs = parseWaitTimeout(String(value));
134
138
  return;
@@ -182,6 +186,9 @@ function assignFlag(flags, name, value) {
182
186
  case "complete":
183
187
  flags.complete = Boolean(value);
184
188
  return;
189
+ case "all":
190
+ flags.all = Boolean(value);
191
+ return;
185
192
  case "target":
186
193
  if (flags.target !== undefined) {
187
194
  throw new FlowError("EMPTY_PARAM", "option --target may only be specified once");
@@ -244,21 +251,28 @@ function readValueFlag(name, token, eq, tokens, index) {
244
251
  function commissionConflictMessage(atForm, contractForm) {
245
252
  return `commission address supplied twice: ${atForm} and --contract ${contractForm}; use exactly one form`;
246
253
  }
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;
254
+ function formatSelectorList(items) {
255
+ if (items.length <= 1)
256
+ return items[0] ?? "";
257
+ if (items.length === 2)
258
+ return `${items[0]} and ${items[1]}`;
259
+ return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
260
+ }
261
+ function multiCommissionSelectorMessage(command, spellings) {
262
+ return `${command} accepts one commission address; got ${formatSelectorList(spellings)}`;
263
+ }
264
+ function commissionSelectorSpelling(source, value, rawToken) {
265
+ if (source === "at-address")
266
+ return rawToken ?? `@${value}`;
267
+ return `--contract ${rawToken ?? value}`;
253
268
  }
254
269
  export function parseCliArgs(tokens) {
255
270
  const { head, payload } = splitAtLiteralBoundary(tokens);
256
271
  const flags = {};
257
272
  const free = [];
258
273
  const seenFlagNames = new Set();
259
- let atContractId;
260
- let atToken;
261
- let contractFlagRaw;
274
+ const atTokens = [];
275
+ const contractFlagRaws = [];
262
276
  let helpRequested = false;
263
277
  for (let index = 0; index < head.length; index += 1) {
264
278
  const token = head[index];
@@ -267,11 +281,7 @@ export function parseCliArgs(tokens) {
267
281
  continue;
268
282
  }
269
283
  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);
284
+ atTokens.push(token);
275
285
  continue;
276
286
  }
277
287
  if (/^-[A-Za-z]$/.test(token)) {
@@ -288,7 +298,7 @@ export function parseCliArgs(tokens) {
288
298
  }
289
299
  index += 1;
290
300
  if (name === "contract") {
291
- contractFlagRaw = noteContractFlag(flags, value, contractFlagRaw);
301
+ contractFlagRaws.push(value);
292
302
  }
293
303
  else {
294
304
  assignFlag(flags, name, value);
@@ -307,6 +317,9 @@ export function parseCliArgs(tokens) {
307
317
  }
308
318
  const eq = token.indexOf("=");
309
319
  const name = parseFlagName(eq === -1 ? token : token.slice(0, eq));
320
+ if (name === "place") {
321
+ throw new FlowError("EMPTY_PARAM", "unknown option --place; use @addr or --contract <addr>");
322
+ }
310
323
  if (!CONTROL_FLAGS.has(name)) {
311
324
  throw new FlowError("EMPTY_PARAM", `unknown option: --${name}`);
312
325
  }
@@ -315,7 +328,7 @@ export function parseCliArgs(tokens) {
315
328
  const { value, nextIndex } = readValueFlag(name, token, eq, head, index);
316
329
  index = nextIndex;
317
330
  if (name === "contract") {
318
- contractFlagRaw = noteContractFlag(flags, value, contractFlagRaw);
331
+ contractFlagRaws.push(value);
319
332
  }
320
333
  else {
321
334
  assignFlag(flags, name, value);
@@ -329,14 +342,23 @@ export function parseCliArgs(tokens) {
329
342
  assignFlag(flags, name, true);
330
343
  }
331
344
  }
332
- if (atContractId !== undefined && flags.contractId !== undefined) {
333
- throw new FlowError("EMPTY_PARAM", commissionConflictMessage(atToken ?? `@${atContractId}`, contractFlagRaw ?? flags.contractId));
345
+ const { command, rest } = resolveCommand(free);
346
+ if (atTokens.length > 0 && contractFlagRaws.length > 0) {
347
+ throw new FlowError("EMPTY_PARAM", commissionConflictMessage(atTokens[0], contractFlagRaws[0]));
348
+ }
349
+ if (atTokens.length > 1) {
350
+ throw new FlowError("EMPTY_PARAM", multiCommissionSelectorMessage(command, atTokens));
351
+ }
352
+ if (contractFlagRaws.length > 1) {
353
+ throw new FlowError("EMPTY_PARAM", multiCommissionSelectorMessage(command, contractFlagRaws.map((raw) => `--contract ${raw}`)));
334
354
  }
335
- if (atContractId !== undefined) {
336
- flags.contractId = atContractId;
355
+ if (atTokens.length === 1) {
356
+ flags.contractId = normalizeContractAddress(atTokens[0]);
337
357
  flags.contractAddressSource = "at-address";
338
358
  }
339
- const { command, rest } = resolveCommand(free);
359
+ else if (contractFlagRaws.length === 1) {
360
+ assignFlag(flags, "contract", contractFlagRaws[0]);
361
+ }
340
362
  if (helpRequested) {
341
363
  return {
342
364
  command,
@@ -362,6 +384,17 @@ export function parseCliArgs(tokens) {
362
384
  ? `bind creates a commission and does not accept @${flags.contractId}; use -C/--cwd to select the repository`
363
385
  : `contract address @${flags.contractId} is not valid for ${command}`);
364
386
  }
387
+ if ((command === "wait" || command === "tell") && flags.contractId !== undefined) {
388
+ const selectorSpelling = commissionSelectorSpelling(flags.contractAddressSource === "at-address" ? "at-address" : "explicit-flag", flags.contractId, flags.contractAddressSource === "at-address" ? atTokens[0] : contractFlagRaws[0]);
389
+ const scopeOnlyMessage = `${command} requires a projection id or alias; ${selectorSpelling} only limits projection lookup, so add the projection address`;
390
+ if (command === "wait" && rest.length + payload.length === 0) {
391
+ throw new FlowError("EMPTY_PARAM", scopeOnlyMessage);
392
+ }
393
+ if (command === "tell" && rest.length === 0) {
394
+ // Payload after -- is the message body, never the projection address.
395
+ throw new FlowError("EMPTY_PARAM", scopeOnlyMessage);
396
+ }
397
+ }
365
398
  const positional = [...rest, ...payload];
366
399
  if (isCallCommand(command)) {
367
400
  if (flags.akuma) {
@@ -374,6 +407,9 @@ export function parseCliArgs(tokens) {
374
407
  if (!name) {
375
408
  throw new FlowError("EMPTY_PARAM", `${command} requires an agent name; use \`keiyaku ${command} NAME [prompt|-]\``);
376
409
  }
410
+ if (isResponseArtifactId(name)) {
411
+ throw new FlowError("EMPTY_PARAM", `artifact ${name} is a response artifact; call expects an Akuma profile name`);
412
+ }
377
413
  const parsedName = agentNameSchema.safeParse(name);
378
414
  if (!parsedName.success) {
379
415
  throw new FlowError("EMPTY_PARAM", `${command} requires a valid agent name before the optional prompt`);