@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
@@ -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,10 +10,11 @@ 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
+ kill: command("kill", "Stop the current projection generation", ["keiyaku kill <projection-address>", "keiyaku @addr kill <projection-address>"], "none", ["cwd", "repo", "contract"], "Terminate the current generation while keeping the projection identity and tell ledger."),
17
18
  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
19
  akuma: command("akuma", "Inspect Akuma profiles", ["keiyaku akuma list|show"], "none", [], "Inspect configured helper profiles.", {
19
20
  subcommands: ["list", "ls", "show"],
@@ -31,7 +32,7 @@ export const CLI_COMMAND_METADATA = {
31
32
  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
33
  guide: command("guide", "Print the Keiyaku guide", ["keiyaku guide"], "none", [], "Print workflow guidance."),
33
34
  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."),
35
+ 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
36
  "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
37
  };
37
38
  function indent(lines) {
@@ -84,6 +85,7 @@ export function renderCliHelp(version) {
84
85
  " wait Wait for an akuma projection.",
85
86
  " revive Revive an Akuma helper session.",
86
87
  " tell Send intent to an Akuma projection.",
88
+ " kill Stop the current Akuma generation.",
87
89
  " log Render a contract ledger.",
88
90
  " akuma Inspect helper profiles.",
89
91
  " skills Install bundled official skills.",
@@ -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";
@@ -34,6 +35,7 @@ import { buildBindDraftResponse, buildBindResponse, buildForfeitResponse, format
34
35
  import { buildCallResponse, renderAdoptedLaunchReceipt } from "./render/call.js";
35
36
  import { projectionDir, writeInboxTell } from "../core/projection-core.js";
36
37
  import { assertTellEffortSupported, tellProjection } from "../core/projection-wake.js";
38
+ import { killProjectionGeneration } from "../core/projection-kill.js";
37
39
  import { resolveProjectionAddress } from "./projection-address.js";
38
40
  import { waitForProjection } from "../core/projection-wait.js";
39
41
  import { buildWaitResponse } from "./render/wait.js";
@@ -41,6 +43,7 @@ import { prependAddressReceipt, renderResolvedAddressReceipt } from "./render/ad
41
43
  import { installOfficialSkills } from "./skills-install.js";
42
44
  import { isGitRepo } from "../git/branches.js";
43
45
  import { stableRepoRoot } from "../core/worktree-path.js";
46
+ import { assertProjectionAlias } from "../core/projection-alias.js";
44
47
  const CALL_INPUT_HINTS = [
45
48
  "Call input can be a prompt argument, `-` to read stdin, or omitted to read stdin.",
46
49
  "Use `keiyaku call NAME \"prompt\"`, `keiyaku call NAME - < prompt.md`, or `keiyaku revive ARTIFACT_ID`.",
@@ -79,6 +82,7 @@ function buildCallInput(content, flags, mode = "call") {
79
82
  incognito: flags.incognito,
80
83
  bare: flags.bare,
81
84
  detach: flags.detach,
85
+ ...(mode === "call" && flags.alias !== undefined ? { alias: assertProjectionAlias(flags.alias) } : {}),
82
86
  };
83
87
  }
84
88
  function revivePrompt(stdin) {
@@ -96,7 +100,7 @@ async function repositoryDirectory(flags) {
96
100
  if (flags.contractId) {
97
101
  const asTyped = flags.contractAddressSource === "at-address"
98
102
  ? `@${flags.contractId}`
99
- : flags.contractId;
103
+ : `--contract ${flags.contractId}`;
100
104
  throw new AddressResolutionError("EMPTY_PARAM", `${asTyped} is a commission address, but ${candidate} has no repository ledger; contract addressing requires a ledger`, buildResolutionAttempt({
101
105
  repo: { kind: "user" },
102
106
  workdir: effectiveDirectory(flags),
@@ -142,13 +146,13 @@ const CLI_COMMAND_SPECS = {
142
146
  ...(!flags.detach
143
147
  ? {
144
148
  onLaunch: ({ address, projection }) => {
145
- process.stdout.write(`${renderAdoptedLaunchReceipt(address, projection.id)}\n`);
149
+ process.stdout.write(`${renderAdoptedLaunchReceipt(address, projection.id, projection.alias, projection.previousAliasTarget)}\n`);
146
150
  },
147
151
  }
148
152
  : {}),
149
153
  });
150
154
  if (outcome.result.foregroundWait && outcome.result.projection) {
151
- return buildWaitResponse(outcome.result.projection.id, outcome.result.foregroundWait);
155
+ return buildWaitResponse(outcome.result.projection.id, outcome.result.foregroundWait, { alias: outcome.result.projection.alias });
152
156
  }
153
157
  return buildCallResponse(outcome.result, {
154
158
  responsePath: outcome.responsePath,
@@ -160,8 +164,20 @@ const CLI_COMMAND_SPECS = {
160
164
  const cwd = effectiveDirectory(flags);
161
165
  const artifactCwd = await projectDirectory(flags);
162
166
  const artifactId = positional[0];
163
- const responseStoreCwd = await isGitRepo(artifactCwd) ? await stableRepoRoot(artifactCwd) : await fs.realpath(artifactCwd);
164
- const artifact = await locateResponseArtifact(responseStoreCwd, artifactId);
167
+ const isRepo = await isGitRepo(artifactCwd);
168
+ const responseStoreCwd = isRepo ? await stableRepoRoot(artifactCwd) : await fs.realpath(artifactCwd);
169
+ const artifact = await locateResponseArtifact({
170
+ cwd: responseStoreCwd,
171
+ artifactId,
172
+ attempt: buildArtifactResolutionAttempt({
173
+ repo: isRepo
174
+ ? { kind: "repository", stableRoot: responseStoreCwd }
175
+ : { kind: "user" },
176
+ workdir: cwd,
177
+ supplied: artifactId,
178
+ inference: ["response artifact identity lookup"],
179
+ }),
180
+ });
165
181
  let executionCwd;
166
182
  let repo;
167
183
  if (artifact.provenance.authority.kind === "repository") {
@@ -186,20 +202,30 @@ const CLI_COMMAND_SPECS = {
186
202
  const cwd = flags.contractId ? await repositoryDirectory(flags) : await projectDirectory(flags);
187
203
  const address = flags.contractId ? await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource) : undefined;
188
204
  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 });
205
+ const projection = await resolveProjectionAddress({
206
+ cwd,
207
+ address: positional[0],
208
+ scope: address,
209
+ verb: "wait",
210
+ });
211
+ 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
212
  };
192
213
  return address ? await withResolvedAddress(address, operation) : await operation();
193
214
  }),
194
215
  tell: commandSpec("tell", async (_stdin, flags, _signal, positional) => {
195
216
  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");
217
+ const [projectionAddress, message] = positional;
218
+ if (!projectionAddress || !message)
219
+ throw new FlowError("EMPTY_PARAM", "tell requires a projection address and message");
199
220
  const address = flags.contractId ? await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource) : undefined;
200
221
  const operation = async () => {
201
- const projectionId = resolveProjectionAddress(cwd, prefix, address?.binding.commissionId);
202
- const directory = projectionDir(cwd, projectionId);
222
+ const projection = await resolveProjectionAddress({
223
+ cwd,
224
+ address: projectionAddress,
225
+ scope: address,
226
+ verb: "tell",
227
+ });
228
+ const directory = projectionDir(cwd, projection.id);
203
229
  assertTellEffortSupported(directory, flags.effort);
204
230
  // Mailbox first: do not inspect phase before this immutable write.
205
231
  writeInboxTell(directory, message, Date.now(), { effort: flags.effort });
@@ -208,6 +234,27 @@ const CLI_COMMAND_SPECS = {
208
234
  };
209
235
  return address ? await withResolvedAddress(address, operation) : await operation();
210
236
  }),
237
+ kill: commandSpec("kill", async (_stdin, flags, _signal, positional) => {
238
+ const cwd = flags.contractId ? await repositoryDirectory(flags) : await projectDirectory(flags);
239
+ const [projectionAddress] = positional;
240
+ if (!projectionAddress)
241
+ throw new FlowError("EMPTY_PARAM", "kill requires a projection address");
242
+ const address = flags.contractId ? await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource) : undefined;
243
+ const operation = async () => {
244
+ const projection = await resolveProjectionAddress({
245
+ cwd,
246
+ address: projectionAddress,
247
+ scope: address,
248
+ verb: "kill",
249
+ });
250
+ const result = killProjectionGeneration(projectionDir(cwd, projection.id));
251
+ const line = result.status === "killed"
252
+ ? `× killed ${projection.id}`
253
+ : `· already resting ${projection.id}`;
254
+ return textResponse(line);
255
+ };
256
+ return address ? await withResolvedAddress(address, operation) : await operation();
257
+ }),
211
258
  log: commandSpec("log", async (_stdin, flags) => {
212
259
  const cwd = await repositoryDirectory(flags);
213
260
  const address = await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource);
@@ -232,6 +279,9 @@ const CLI_COMMAND_SPECS = {
232
279
  if (!name) {
233
280
  throw new FlowError("EMPTY_PARAM", "akuma show requires an Akuma name");
234
281
  }
282
+ if (isResponseArtifactId(name)) {
283
+ throw new FlowError("EMPTY_PARAM", `artifact ${name} is a response artifact; akuma show expects an Akuma profile name`);
284
+ }
235
285
  const cwd = await projectDirectory(flags);
236
286
  const loaded = await loadKeiyakuSettings(cwd);
237
287
  // Role markers need settings pointers; catalog consumers keep legacy agents disease actionable.
@@ -288,7 +338,10 @@ const CLI_COMMAND_SPECS = {
288
338
  // honors --repo as a repository coordinate when the operator names one.
289
339
  const cwd = await projectDirectory(flags);
290
340
  const target = flags.target === undefined ? undefined : normalizeTargetRef(flags.target);
291
- return textResponse(renderStatusBoard(await readKanshiBoard(cwd, Date.now(), target === undefined ? undefined : { target })));
341
+ return textResponse(renderStatusBoard(await readKanshiBoard(cwd, Date.now(), {
342
+ ...(target === undefined ? {} : { target }),
343
+ showAllProjections: flags.all === true,
344
+ })));
292
345
  }),
293
346
  "dump-env": commandSpec("dump-env", async () => textResponse(renderEnvExample())),
294
347
  };