@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.
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms_v2.js +6 -2
- package/build/agents/harness/index.js +5 -1
- package/build/agents/harness/pump.js +22 -5
- package/build/agents/launch-snapshot_v2.js +14 -9
- package/build/agents/selector_v2.js +15 -3
- package/build/cli/commands/akuma.js +21 -3
- package/build/cli/completion.js +4 -1
- package/build/cli/flags.js +10 -1
- package/build/cli/help.js +3 -3
- package/build/cli/index.js +45 -14
- package/build/cli/parse.js +60 -24
- package/build/cli/projection-address.js +97 -10
- package/build/cli/render/address.js +12 -3
- package/build/cli/render/call.js +10 -5
- package/build/cli/render/shared.js +2 -2
- package/build/cli/render/status.js +19 -4
- package/build/cli/render/wait.js +3 -3
- package/build/config/provider-policy-ownership.js +39 -0
- package/build/config/settings.js +1 -11
- package/build/core/addressing.js +82 -38
- package/build/core/call.js +33 -5
- package/build/core/projection/mint.js +23 -3
- package/build/core/projection-alias.js +123 -0
- package/build/core/projection-coordinate.js +23 -7
- package/build/core/projection-core.js +8 -47
- package/build/core/projection-mint.js +24 -3
- package/build/core/projection-runner-lock.js +1 -1
- package/build/core/projection-status.js +111 -12
- package/build/core/response-artifact-id.js +5 -0
- package/build/core/status.js +1 -1
- package/build/core/transcripts.js +42 -15
- package/build/generated/version.js +1 -1
- 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
|
-
|
|
58
|
-
|
|
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
|
|
9
|
-
const
|
|
10
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/build/cli/completion.js
CHANGED
|
@@ -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"
|
|
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") {
|
package/build/cli/flags.js
CHANGED
|
@@ -34,7 +34,12 @@ export const FLAG_SPECS = {
|
|
|
34
34
|
projection: {
|
|
35
35
|
kind: "value",
|
|
36
36
|
placeholder: "ID",
|
|
37
|
-
description: "Akuma projection
|
|
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 <
|
|
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) {
|
package/build/cli/index.js
CHANGED
|
@@ -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
|
|
164
|
-
const
|
|
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
|
|
190
|
-
|
|
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 [
|
|
197
|
-
if (!
|
|
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
|
|
202
|
-
|
|
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(),
|
|
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
|
};
|
package/build/cli/parse.js
CHANGED
|
@@ -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: "<
|
|
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
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
333
|
-
|
|
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 (
|
|
336
|
-
flags.contractId =
|
|
355
|
+
if (atTokens.length === 1) {
|
|
356
|
+
flags.contractId = normalizeContractAddress(atTokens[0]);
|
|
337
357
|
flags.contractAddressSource = "at-address";
|
|
338
358
|
}
|
|
339
|
-
|
|
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`);
|