@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
|
@@ -1,11 +1,98 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { AddressResolutionError, buildObjectResolutionAttempt, } from "../core/addressing.js";
|
|
4
|
+
import { resolveProjectionCoordinate } from "../core/projection-coordinate.js";
|
|
5
|
+
import { isProjectionId, projectionDir } from "../core/projection-core.js";
|
|
6
|
+
import { isProjectionAlias, readProjectionAlias } from "../core/projection-alias.js";
|
|
7
|
+
import { readProjectionIdentity } from "../core/projection-identity.js";
|
|
8
|
+
import { isResponseArtifactId } from "../core/response-artifact-id.js";
|
|
9
|
+
import { isErrnoException } from "../errno.js";
|
|
10
|
+
function isRealDirectory(directory) {
|
|
11
|
+
try {
|
|
12
|
+
return fs.lstatSync(directory).isDirectory();
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (isErrnoException(error) && error.code === "ENOENT")
|
|
16
|
+
return false;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function resolveProjectionAddress(input) {
|
|
21
|
+
const workdir = path.resolve(input.cwd);
|
|
22
|
+
// Scoped lookup reuses the commission carrier's repository root: zero Git probes.
|
|
23
|
+
// Unscoped lookup performs exactly one coordinate probe.
|
|
24
|
+
let repo;
|
|
25
|
+
let physicalRoot;
|
|
26
|
+
let attempt;
|
|
27
|
+
if (input.scope) {
|
|
28
|
+
repo = input.scope.repo;
|
|
29
|
+
physicalRoot = input.scope.repo.stableRoot;
|
|
30
|
+
attempt = buildObjectResolutionAttempt({
|
|
31
|
+
repo,
|
|
32
|
+
workdir,
|
|
33
|
+
expected: "projection",
|
|
34
|
+
supplied: input.address,
|
|
35
|
+
scope: {
|
|
36
|
+
commissionId: input.scope.binding.commissionId,
|
|
37
|
+
asTyped: input.scope.spelling.asTyped,
|
|
38
|
+
},
|
|
39
|
+
inference: ["commission-scoped projection lookup"],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const coordinate = resolveProjectionCoordinate(input.cwd);
|
|
44
|
+
repo = coordinate.authority.kind === "repository"
|
|
45
|
+
? { kind: "repository", stableRoot: coordinate.authority.stableRoot }
|
|
46
|
+
: { kind: "user" };
|
|
47
|
+
physicalRoot = coordinate.physicalRoot;
|
|
48
|
+
attempt = buildObjectResolutionAttempt({
|
|
49
|
+
repo,
|
|
50
|
+
workdir,
|
|
51
|
+
expected: "projection",
|
|
52
|
+
supplied: input.address,
|
|
53
|
+
inference: ["projection directory lookup"],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (isResponseArtifactId(input.address)) {
|
|
57
|
+
throw new AddressResolutionError("EMPTY_PARAM", `artifact ${input.address} is a response artifact; ${input.verb} expects a projection id or alias`, { ...attempt, actual: "artifact" });
|
|
58
|
+
}
|
|
59
|
+
const suffix = input.scope
|
|
60
|
+
? `within ${input.scope.spelling.asTyped}`
|
|
61
|
+
: repo.kind === "repository" ? `in repository ${repo.stableRoot}` : `in workdir ${workdir}`;
|
|
62
|
+
let id;
|
|
63
|
+
let alias;
|
|
64
|
+
if (input.address.includes("/")) {
|
|
65
|
+
if (!isProjectionId(input.address))
|
|
66
|
+
throw new AddressResolutionError("EMPTY_PARAM", `projection ${input.address} has no match ${suffix}`, attempt);
|
|
67
|
+
id = input.address;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (!isProjectionAlias(input.address))
|
|
71
|
+
throw new AddressResolutionError("EMPTY_PARAM", `projection alias ${input.address} has no match ${suffix}`, attempt);
|
|
72
|
+
const target = readProjectionAlias(physicalRoot, input.address);
|
|
73
|
+
if (!target)
|
|
74
|
+
throw new AddressResolutionError("EMPTY_PARAM", `projection alias ${input.address} has no match ${suffix}`, attempt);
|
|
75
|
+
id = target;
|
|
76
|
+
alias = input.address;
|
|
77
|
+
}
|
|
78
|
+
const directory = projectionDir(physicalRoot, id);
|
|
79
|
+
if (!isRealDirectory(directory)) {
|
|
80
|
+
const subject = alias ? `projection alias ${alias} points to missing projection ${id}` : `projection ${id}`;
|
|
81
|
+
throw new AddressResolutionError("EMPTY_PARAM", `${subject} has no match ${suffix}`, attempt);
|
|
82
|
+
}
|
|
83
|
+
let identity;
|
|
84
|
+
try {
|
|
85
|
+
identity = readProjectionIdentity(directory);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error instanceof Error && /ENOENT|no such file/i.test(error.message)) {
|
|
89
|
+
const subject = alias ? `projection alias ${alias} points to missing projection ${id}` : `projection ${id}`;
|
|
90
|
+
throw new AddressResolutionError("EMPTY_PARAM", `${subject} has no match ${suffix}`, attempt);
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
if (input.scope && (identity.binding.kind !== "commission" || identity.binding.commissionId !== input.scope.binding.commissionId)) {
|
|
95
|
+
throw new AddressResolutionError("EMPTY_PARAM", `projection ${id} has no match ${suffix}`, attempt);
|
|
96
|
+
}
|
|
97
|
+
return { id, ...(alias ? { alias } : {}) };
|
|
11
98
|
}
|
|
@@ -25,7 +25,7 @@ export function renderResolvedAddressReceipt(address, facts = {}) {
|
|
|
25
25
|
`repo ${address.repo.stableRoot}`,
|
|
26
26
|
`workdir ${address.workdir}`,
|
|
27
27
|
facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
|
|
28
|
-
facts.projectionId ? `projection ${facts.projectionId}` : undefined,
|
|
28
|
+
facts.projectionId ? `projection ${facts.projectionId}${facts.projectionAlias ? ` (${facts.projectionAlias})` : ""}` : undefined,
|
|
29
29
|
].filter((part) => part !== undefined).join(" · ");
|
|
30
30
|
}
|
|
31
31
|
const repository = address.repo.kind === "repository"
|
|
@@ -37,7 +37,7 @@ export function renderResolvedAddressReceipt(address, facts = {}) {
|
|
|
37
37
|
repository,
|
|
38
38
|
`workdir ${address.workdir}`,
|
|
39
39
|
facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
|
|
40
|
-
facts.projectionId ? `projection ${facts.projectionId}` : undefined,
|
|
40
|
+
facts.projectionId ? `projection ${facts.projectionId}${facts.projectionAlias ? ` (${facts.projectionAlias})` : ""}` : undefined,
|
|
41
41
|
].filter((part) => part !== undefined).join(" · ");
|
|
42
42
|
}
|
|
43
43
|
return [
|
|
@@ -46,7 +46,7 @@ export function renderResolvedAddressReceipt(address, facts = {}) {
|
|
|
46
46
|
`repo ${address.repo.stableRoot}`,
|
|
47
47
|
`target ${address.target.branch}`,
|
|
48
48
|
facts.artifactId ? `artifact ${facts.artifactId}` : undefined,
|
|
49
|
-
facts.projectionId ? `projection ${facts.projectionId}` : undefined,
|
|
49
|
+
facts.projectionId ? `projection ${facts.projectionId}${facts.projectionAlias ? ` (${facts.projectionAlias})` : ""}` : undefined,
|
|
50
50
|
`via ${sourceLabel(address)}`,
|
|
51
51
|
].filter((part) => part !== undefined).join(" · ");
|
|
52
52
|
}
|
|
@@ -61,7 +61,16 @@ function renderAttemptCandidate(candidate) {
|
|
|
61
61
|
candidate.matchedBy ? `matched by ${candidate.matchedBy}` : undefined,
|
|
62
62
|
].filter((part) => part !== undefined).join(" · ");
|
|
63
63
|
}
|
|
64
|
+
function isCommissionResolutionAttempt(attempt) {
|
|
65
|
+
return attempt.expected === "commission";
|
|
66
|
+
}
|
|
64
67
|
export function renderResolutionAttemptPreamble(attempt) {
|
|
68
|
+
if (!isCommissionResolutionAttempt(attempt)) {
|
|
69
|
+
return [
|
|
70
|
+
attempt.repo.kind === "repository" ? `At repo ${attempt.repo.stableRoot}` : "At no repository",
|
|
71
|
+
attempt.scope ? `scope ${attempt.scope.asTyped}` : undefined,
|
|
72
|
+
].filter((part) => part !== undefined).join(" · ");
|
|
73
|
+
}
|
|
65
74
|
const parts = [
|
|
66
75
|
attempt.repo.kind === "repository" ? `At repo ${attempt.repo.stableRoot}` : "At no repository",
|
|
67
76
|
attempt.spelling ? `address ${attempt.spelling.asTyped}` : undefined,
|
package/build/cli/render/call.js
CHANGED
|
@@ -2,10 +2,12 @@ import { assembleResponse, buildPayload, buildSection, formatMaybe, formatWarnin
|
|
|
2
2
|
import { WARNING_SECTION_TITLE } from "./response-style.js";
|
|
3
3
|
import { CONTENT_SECTION_TITLE, formatPersistenceSummary, formatCallCompletionHeading, formatCallNextAction, INFO_AKUMA_MAX_CHARS, projectionRejoinHintLines, } from "./shared.js";
|
|
4
4
|
import { carryAddress, prependAddressReceipt, renderResolvedAddressReceipt } from "./address.js";
|
|
5
|
-
export function renderAdoptedLaunchReceipt(address, projectionId) {
|
|
5
|
+
export function renderAdoptedLaunchReceipt(address, projectionId, alias, previousAliasTarget) {
|
|
6
6
|
return [
|
|
7
|
-
renderResolvedAddressReceipt(address, { projectionId }),
|
|
8
|
-
...
|
|
7
|
+
renderResolvedAddressReceipt(address, { projectionId, ...(alias ? { projectionAlias: alias } : {}) }),
|
|
8
|
+
...(alias ? [`alias ${alias} → ${projectionId}`] : []),
|
|
9
|
+
...(alias && previousAliasTarget ? [`(moved from ${previousAliasTarget}, still running)`] : []),
|
|
10
|
+
...projectionRejoinHintLines(alias ?? projectionId),
|
|
9
11
|
].join("\n");
|
|
10
12
|
}
|
|
11
13
|
export function buildCallResponse(result, input) {
|
|
@@ -15,8 +17,11 @@ export function buildCallResponse(result, input) {
|
|
|
15
17
|
const { session: _session, evidenceCwd: _evidenceCwd, address: _address, foregroundWait: _foregroundWait, ...responseData } = result;
|
|
16
18
|
const output = {
|
|
17
19
|
text: input.addressPlacement === "stdout"
|
|
18
|
-
? renderAdoptedLaunchReceipt(result.address, result.projection.id)
|
|
19
|
-
:
|
|
20
|
+
? renderAdoptedLaunchReceipt(result.address, result.projection.id, result.projection.alias, result.projection.previousAliasTarget)
|
|
21
|
+
: [
|
|
22
|
+
...(result.projection.alias ? [`alias ${result.projection.alias} → ${result.projection.id}`] : []),
|
|
23
|
+
...projectionRejoinHintLines(result.projection.alias ?? result.projection.id),
|
|
24
|
+
].join("\n"),
|
|
20
25
|
payload: buildPayload(responseData),
|
|
21
26
|
};
|
|
22
27
|
switch (input.addressPlacement) {
|
|
@@ -135,14 +135,14 @@ export function projectionRejoinHintLines(projectionId) {
|
|
|
135
135
|
}
|
|
136
136
|
export function formatCallNextAction(input) {
|
|
137
137
|
if (input.projection?.phase === "active") {
|
|
138
|
-
return projectionRejoinHintLines(input.projection.id).join("\n");
|
|
138
|
+
return projectionRejoinHintLines(input.projection.alias ?? input.projection.id).join("\n");
|
|
139
139
|
}
|
|
140
140
|
if (input.projection?.phase === "done") {
|
|
141
141
|
return [
|
|
142
142
|
"The akuma is back.",
|
|
143
143
|
...(input.artifactId ? [`record: ${input.artifactId}`] : []),
|
|
144
144
|
"talk to it:",
|
|
145
|
-
` » keiyaku tell ${input.projection.id} "your words"`,
|
|
145
|
+
` » keiyaku tell ${input.projection.alias ?? input.projection.id} "your words"`,
|
|
146
146
|
].join("\n");
|
|
147
147
|
}
|
|
148
148
|
return "⟶ keiyaku status";
|
|
@@ -15,6 +15,16 @@ function renderProjectionDiagnostics(row) {
|
|
|
15
15
|
return "";
|
|
16
16
|
return ` · diag ${row.diagnostics.map((diagnostic) => diagnostic.detail).join("; ")}`;
|
|
17
17
|
}
|
|
18
|
+
function renderProjectionOmission(board) {
|
|
19
|
+
if (!board.omitted)
|
|
20
|
+
return undefined;
|
|
21
|
+
const counts = ["failed", "dead", "done", "dismissed"]
|
|
22
|
+
.flatMap((state) => {
|
|
23
|
+
const count = board.omitted?.byState[state] ?? 0;
|
|
24
|
+
return count > 0 ? [`${count} ${state}`] : [];
|
|
25
|
+
});
|
|
26
|
+
return ` · +${board.omitted.total} not shown (${counts.join(" · ")}) — keiyaku status --all`;
|
|
27
|
+
}
|
|
18
28
|
function renderProjectionAge(row) {
|
|
19
29
|
if (row.state === "done")
|
|
20
30
|
return "done";
|
|
@@ -63,18 +73,19 @@ function renderProjectionRow(row) {
|
|
|
63
73
|
: row.state === "startup-timeout"
|
|
64
74
|
? "minting"
|
|
65
75
|
: row.state);
|
|
76
|
+
const identity = row.alias ? `${row.id} (${row.alias})` : row.id;
|
|
66
77
|
let heading;
|
|
67
78
|
if (row.state === "startup-timeout" || row.state === "unknown") {
|
|
68
79
|
const akuma = (row.akuma ?? "(unknown)").padEnd(18);
|
|
69
|
-
heading = ` ${glyph} ${
|
|
80
|
+
heading = ` ${glyph} ${identity} ${akuma} ${renderProjectionAge(row)}${renderProjectionDiagnostics(row)}`.trimEnd();
|
|
70
81
|
}
|
|
71
82
|
else if (row.state === "minting") {
|
|
72
|
-
heading = ` ${glyph} ${
|
|
83
|
+
heading = ` ${glyph} ${identity.padEnd(18)} (minting)${renderProjectionDiagnostics(row)}`.trimEnd();
|
|
73
84
|
}
|
|
74
85
|
else {
|
|
75
86
|
const age = renderProjectionAge(row).padEnd(9);
|
|
76
87
|
const akuma = (row.akuma ?? "(unknown)").padEnd(18);
|
|
77
|
-
heading = ` ${glyph} ${
|
|
88
|
+
heading = ` ${glyph} ${identity} ${akuma} ${age}${renderProjectionTellCounts(row)}${renderProjectionDiagnostics(row)}`.trimEnd();
|
|
78
89
|
}
|
|
79
90
|
const activity = renderProjectionActivityLine(row);
|
|
80
91
|
return activity ? [heading, activity] : [heading];
|
|
@@ -82,7 +93,11 @@ function renderProjectionRow(row) {
|
|
|
82
93
|
export function renderProjectionStatusSection(board) {
|
|
83
94
|
if (!board || board.rows.length === 0)
|
|
84
95
|
return "";
|
|
85
|
-
|
|
96
|
+
const lines = board.rows.flatMap(renderProjectionRow);
|
|
97
|
+
const omission = renderProjectionOmission(board);
|
|
98
|
+
if (omission)
|
|
99
|
+
lines.push(omission);
|
|
100
|
+
return `${STATUS_PROJECTION_SECTION_TITLE}\n${lines.join("\n")}\n`;
|
|
86
101
|
}
|
|
87
102
|
function renderClaimReconciliationSection(recon) {
|
|
88
103
|
if (!recon)
|
package/build/cli/render/wait.js
CHANGED
|
@@ -3,7 +3,7 @@ import { displayColumns, resolveLineColumns, truncateColumns } from "./line-widt
|
|
|
3
3
|
import { projectionRejoinHintLines, textResponse } from "./shared.js";
|
|
4
4
|
const WAIT_HEADER_MAX_COLUMNS = 60;
|
|
5
5
|
function shortAddress(projectionId) {
|
|
6
|
-
return
|
|
6
|
+
return /\/([0-9a-f]{8})$/.exec(projectionId)?.[1] ?? projectionId;
|
|
7
7
|
}
|
|
8
8
|
function phaseState(phase) {
|
|
9
9
|
switch (phase) {
|
|
@@ -42,7 +42,7 @@ function waitStateText(snapshot) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
function projectionIdentityParts(projectionId) {
|
|
45
|
-
const match = /^(.*)
|
|
45
|
+
const match = /^(.*)\/([0-9a-f]{8})$/.exec(projectionId);
|
|
46
46
|
return match ? { slug: match[1], shortId: match[2] } : { slug: "", shortId: projectionId };
|
|
47
47
|
}
|
|
48
48
|
function waitHeader(projectionId, akuma, snapshot, outputColumns) {
|
|
@@ -215,7 +215,7 @@ export function buildWaitResponse(projectionId, result, options = {}) {
|
|
|
215
215
|
if (rendered.lines.length === 0 && state === "running") {
|
|
216
216
|
lines.push(" not a word out of it yet — pulse is steady");
|
|
217
217
|
}
|
|
218
|
-
const nextActions = nextActionLines(projectionId, result, state);
|
|
218
|
+
const nextActions = nextActionLines(options.alias ?? projectionId, result, state);
|
|
219
219
|
if (rendered.hasMutationFiles && nextActions.length > 0)
|
|
220
220
|
lines.push("");
|
|
221
221
|
lines.push(...nextActions);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const EMPTY_OWNERSHIP = Object.freeze({
|
|
2
|
+
access: Object.freeze([]),
|
|
3
|
+
network: Object.freeze([]),
|
|
4
|
+
webSearch: Object.freeze([]),
|
|
5
|
+
});
|
|
6
|
+
function owns(config, key) {
|
|
7
|
+
return Object.prototype.hasOwnProperty.call(config, key);
|
|
8
|
+
}
|
|
9
|
+
export function nativePolicyOwnership(instanceName, instance) {
|
|
10
|
+
if (instance.kind !== "codex-sdk" && instance.kind !== "codex-app-server") {
|
|
11
|
+
return EMPTY_OWNERSHIP;
|
|
12
|
+
}
|
|
13
|
+
const config = instance.config;
|
|
14
|
+
if (!config)
|
|
15
|
+
return EMPTY_OWNERSHIP;
|
|
16
|
+
const access = [];
|
|
17
|
+
const network = [];
|
|
18
|
+
const webSearch = [];
|
|
19
|
+
const add = (target, key) => {
|
|
20
|
+
target.push({ path: `providers.${instanceName}.config.${key}`, value: config[key] });
|
|
21
|
+
};
|
|
22
|
+
if (owns(config, "sandbox_mode"))
|
|
23
|
+
add(access, "sandbox_mode");
|
|
24
|
+
if (owns(config, "approval_policy"))
|
|
25
|
+
add(access, "approval_policy");
|
|
26
|
+
for (const key of Object.keys(config)) {
|
|
27
|
+
if (key !== "sandbox_workspace_write" && !key.startsWith("sandbox_workspace_write."))
|
|
28
|
+
continue;
|
|
29
|
+
add(access, key);
|
|
30
|
+
add(network, key);
|
|
31
|
+
}
|
|
32
|
+
if (owns(config, "web_search"))
|
|
33
|
+
add(webSearch, "web_search");
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
access: Object.freeze(access),
|
|
36
|
+
network: Object.freeze(network),
|
|
37
|
+
webSearch: Object.freeze(webSearch),
|
|
38
|
+
});
|
|
39
|
+
}
|
package/build/config/settings.js
CHANGED
|
@@ -41,19 +41,9 @@ const CODEX_SDK_FORBIDDEN_THREAD_OPTIONS_KEYS = [
|
|
|
41
41
|
const FORBIDDEN_CONFIG_KEYS = [
|
|
42
42
|
"model",
|
|
43
43
|
"model_reasoning_effort",
|
|
44
|
-
"sandbox_mode",
|
|
45
|
-
"sandbox_workspace_write",
|
|
46
|
-
"web_search",
|
|
47
|
-
"approval_policy",
|
|
48
44
|
];
|
|
49
45
|
function isConfigKeyForbidden(key) {
|
|
50
|
-
|
|
51
|
-
if (FORBIDDEN_CONFIG_KEYS.includes(key))
|
|
52
|
-
return true;
|
|
53
|
-
// sandbox_workspace_write also rejects dotted subkeys from adapter-native TOML flattening
|
|
54
|
-
if (key.startsWith("sandbox_workspace_write."))
|
|
55
|
-
return true;
|
|
56
|
-
return false;
|
|
46
|
+
return FORBIDDEN_CONFIG_KEYS.includes(key);
|
|
57
47
|
}
|
|
58
48
|
const claudeSettingSourceSchema = z.enum(CLAUDE_SETTING_SOURCES);
|
|
59
49
|
const writableRootItemSchema = z.string().superRefine((value, ctx) => {
|
package/build/core/addressing.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { FlowError } from "../flow-error.js";
|
|
3
3
|
import { listContractIds, readLedger } from "./ledger.js";
|
|
4
|
+
import { isResponseArtifactId } from "./response-artifact-id.js";
|
|
4
5
|
import { stableRepoRoot } from "./worktree-path.js";
|
|
5
6
|
import { deriveContractState, isTerminalState, } from "./status.js";
|
|
6
7
|
function normalizeAddressQuery(raw) {
|
|
@@ -66,10 +67,39 @@ export function buildResolutionAttempt(input) {
|
|
|
66
67
|
workdir: path.resolve(input.workdir),
|
|
67
68
|
expected: "commission",
|
|
68
69
|
...(input.spelling ? { spelling: input.spelling } : {}),
|
|
70
|
+
...(input.actual !== undefined ? { actual: input.actual } : {}),
|
|
69
71
|
candidates: [...(input.candidates ?? [])],
|
|
70
72
|
inference: [...(input.inference ?? [])],
|
|
71
73
|
};
|
|
72
74
|
}
|
|
75
|
+
export function buildObjectResolutionAttempt(input) {
|
|
76
|
+
return {
|
|
77
|
+
repo: input.repo,
|
|
78
|
+
workdir: path.resolve(input.workdir),
|
|
79
|
+
expected: input.expected,
|
|
80
|
+
supplied: input.supplied,
|
|
81
|
+
...(input.actual !== undefined ? { actual: input.actual } : {}),
|
|
82
|
+
...(input.scope ? { scope: input.scope } : {}),
|
|
83
|
+
candidates: [...(input.candidates ?? [])],
|
|
84
|
+
inference: [...(input.inference ?? [])],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function buildArtifactResolutionAttempt(input) {
|
|
88
|
+
return {
|
|
89
|
+
...buildObjectResolutionAttempt({
|
|
90
|
+
...input,
|
|
91
|
+
expected: "artifact",
|
|
92
|
+
}),
|
|
93
|
+
expected: "artifact",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function commissionSelectorAsTyped(query, source) {
|
|
97
|
+
if (source === "at-address")
|
|
98
|
+
return `@${query}`;
|
|
99
|
+
if (source === "explicit-flag")
|
|
100
|
+
return `--contract ${query}`;
|
|
101
|
+
return query;
|
|
102
|
+
}
|
|
73
103
|
export class AddressResolutionError extends FlowError {
|
|
74
104
|
attempt;
|
|
75
105
|
constructor(code, message, attempt, options) {
|
|
@@ -162,63 +192,77 @@ export async function resolveUncommissionedRepositoryAddress(input) {
|
|
|
162
192
|
export async function resolveContractAddress(cwd, raw, source = raw?.startsWith("@") ? "at-address" : "explicit-flag") {
|
|
163
193
|
const workdir = path.resolve(cwd);
|
|
164
194
|
const repo = await resolvedRepository(cwd);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
195
|
+
if (raw !== undefined) {
|
|
196
|
+
const query = normalizeAddressQuery(raw);
|
|
197
|
+
const spelling = { asTyped: commissionSelectorAsTyped(query, source), source };
|
|
198
|
+
// Lexical artifact fence: no ledger enumeration for rsp_<ULID>.
|
|
199
|
+
if (isResponseArtifactId(query)) {
|
|
200
|
+
throw new AddressResolutionError("EMPTY_PARAM", `artifact ${query} is a response artifact; expected a commission address`, {
|
|
201
|
+
repo,
|
|
202
|
+
workdir,
|
|
203
|
+
expected: "commission",
|
|
204
|
+
spelling,
|
|
205
|
+
candidates: [],
|
|
206
|
+
inference: [],
|
|
207
|
+
actual: "artifact",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const candidates = await commissionAddressCandidates(repo.stableRoot);
|
|
211
|
+
const matches = matchCommissionAddress(query, candidates);
|
|
168
212
|
const attempt = {
|
|
169
213
|
repo,
|
|
170
214
|
workdir,
|
|
171
215
|
expected: "commission",
|
|
172
|
-
|
|
173
|
-
|
|
216
|
+
spelling,
|
|
217
|
+
candidates: matches.map((candidate) => ({
|
|
218
|
+
commissionId: candidate.contractId,
|
|
219
|
+
matchedBy: candidate.spelling,
|
|
220
|
+
place: candidate.bind.data.place ?? null,
|
|
221
|
+
name: candidate.bind.data.name,
|
|
222
|
+
objective: candidate.bind.data.objective,
|
|
223
|
+
lifecycle: candidate.lifecycle,
|
|
224
|
+
target: { branch: candidate.bind.data.target },
|
|
225
|
+
})),
|
|
226
|
+
inference: [],
|
|
174
227
|
};
|
|
175
|
-
if (
|
|
176
|
-
const candidate = operable[0];
|
|
228
|
+
if (matches.length === 1) {
|
|
177
229
|
return buildResolvedAddress({
|
|
178
230
|
repo,
|
|
179
231
|
workdir,
|
|
180
|
-
candidate,
|
|
181
|
-
matchedBy:
|
|
182
|
-
asTyped:
|
|
183
|
-
source
|
|
184
|
-
inference:
|
|
232
|
+
candidate: matches[0],
|
|
233
|
+
matchedBy: matches[0].spelling,
|
|
234
|
+
asTyped: attempt.spelling.asTyped,
|
|
235
|
+
source,
|
|
236
|
+
inference: [],
|
|
185
237
|
});
|
|
186
238
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
239
|
+
if (matches.length === 0) {
|
|
240
|
+
throw new AddressResolutionError("EMPTY_PARAM", `unknown contract address @${query}`, attempt);
|
|
241
|
+
}
|
|
242
|
+
throw new AddressResolutionError("CONTRACT_AMBIGUOUS", `contract address @${query} is ambiguous: ${attempt.candidates.map((candidate) => formatAmbiguousCandidate(query, candidate)).join("; ")}; use a full @commission-id`, attempt);
|
|
190
243
|
}
|
|
191
|
-
const
|
|
192
|
-
const
|
|
244
|
+
const candidates = await commissionAddressCandidates(repo.stableRoot);
|
|
245
|
+
const operable = candidates.filter((candidate) => candidate.active);
|
|
193
246
|
const attempt = {
|
|
194
247
|
repo,
|
|
195
248
|
workdir,
|
|
196
249
|
expected: "commission",
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
commissionId: candidate.contractId,
|
|
200
|
-
matchedBy: candidate.spelling,
|
|
201
|
-
place: candidate.bind.data.place ?? null,
|
|
202
|
-
name: candidate.bind.data.name,
|
|
203
|
-
objective: candidate.bind.data.objective,
|
|
204
|
-
lifecycle: candidate.lifecycle,
|
|
205
|
-
target: { branch: candidate.bind.data.target },
|
|
206
|
-
})),
|
|
207
|
-
inference: [],
|
|
250
|
+
candidates: attemptCandidates(operable),
|
|
251
|
+
inference: ["consulted active commission ledgers"],
|
|
208
252
|
};
|
|
209
|
-
if (
|
|
253
|
+
if (operable.length === 1) {
|
|
254
|
+
const candidate = operable[0];
|
|
210
255
|
return buildResolvedAddress({
|
|
211
256
|
repo,
|
|
212
257
|
workdir,
|
|
213
|
-
candidate
|
|
214
|
-
matchedBy:
|
|
215
|
-
asTyped:
|
|
216
|
-
source,
|
|
217
|
-
inference:
|
|
258
|
+
candidate,
|
|
259
|
+
matchedBy: "id",
|
|
260
|
+
asTyped: `@${candidate.contractId}`,
|
|
261
|
+
source: "sole-active",
|
|
262
|
+
inference: attempt.inference,
|
|
218
263
|
});
|
|
219
264
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
throw new AddressResolutionError("CONTRACT_AMBIGUOUS", `contract address @${query} is ambiguous: ${attempt.candidates.map((candidate) => formatAmbiguousCandidate(query, candidate)).join("; ")}; use a full @commission-id`, attempt);
|
|
265
|
+
throw new AddressResolutionError(operable.length === 0 ? "EMPTY_PARAM" : "CONTRACT_AMBIGUOUS", operable.length === 0
|
|
266
|
+
? "no active commission found; pass @place, @slug, full @commission-id, or --contract <addr>"
|
|
267
|
+
: "multiple active commissions found; pass @place, @slug, full @commission-id, or --contract <addr>", attempt);
|
|
224
268
|
}
|
package/build/core/call.js
CHANGED
|
@@ -11,6 +11,8 @@ import { buildCallTermsV2 } from "../agents/call-terms_v2.js";
|
|
|
11
11
|
import { applySnapshotEffort, resolveAkumaLaunchSnapshot, } from "../agents/launch-snapshot_v2.js";
|
|
12
12
|
import { restoreOutcome } from "../agents/harness/index.js";
|
|
13
13
|
import { mintProjection, mintRepositoryProjection } from "./projection-mint.js";
|
|
14
|
+
import { assertProjectionAlias, moveProjectionAlias } from "./projection-alias.js";
|
|
15
|
+
import { projectionDir } from "./projection-core.js";
|
|
14
16
|
import { waitForProjection } from "./projection-wait.js";
|
|
15
17
|
import { requireResumableArtifact } from "./transcripts.js";
|
|
16
18
|
import { appendDebugBlock } from "../telemetry/debug-log.js";
|
|
@@ -125,7 +127,7 @@ async function readCallKeiyakuContext(executionCwd, input) {
|
|
|
125
127
|
if (input.bare && input.contractId) {
|
|
126
128
|
const asTyped = input.contractAddressSource === "at-address"
|
|
127
129
|
? `@${input.contractId}`
|
|
128
|
-
: input.contractId
|
|
130
|
+
: `--contract ${input.contractId}`;
|
|
129
131
|
throw new AddressResolutionError("EMPTY_PARAM", `commission address ${asTyped} cannot be combined with --bare`, buildResolutionAttempt({
|
|
130
132
|
repo: { kind: "user" },
|
|
131
133
|
workdir: executionCwd,
|
|
@@ -138,7 +140,7 @@ async function readCallKeiyakuContext(executionCwd, input) {
|
|
|
138
140
|
if (input.contractId) {
|
|
139
141
|
const asTyped = input.contractAddressSource === "at-address"
|
|
140
142
|
? `@${input.contractId}`
|
|
141
|
-
: input.contractId
|
|
143
|
+
: `--contract ${input.contractId}`;
|
|
142
144
|
throw new AddressResolutionError("EMPTY_PARAM", `${asTyped} is a commission address, but ${ledgerCandidate} has no repository ledger; contract addressing requires a ledger`, buildResolutionAttempt({
|
|
143
145
|
repo: { kind: "user" },
|
|
144
146
|
workdir: executionCwd,
|
|
@@ -190,7 +192,7 @@ async function readCallKeiyakuContext(executionCwd, input) {
|
|
|
190
192
|
});
|
|
191
193
|
const asTyped = input.contractAddressSource === "at-address"
|
|
192
194
|
? `@${input.contractId}`
|
|
193
|
-
: input.contractId
|
|
195
|
+
: `--contract ${input.contractId}`;
|
|
194
196
|
throw new AddressResolutionError("EMPTY_PARAM", `commission address ${asTyped} cannot be combined with --bare`, buildResolutionAttempt({
|
|
195
197
|
repo: repositoryAddress.repo,
|
|
196
198
|
workdir: repositoryAddress.workdir,
|
|
@@ -240,6 +242,7 @@ async function readCallKeiyakuContext(executionCwd, input) {
|
|
|
240
242
|
catch (error) {
|
|
241
243
|
if (error instanceof AddressResolutionError
|
|
242
244
|
&& error.code === "EMPTY_PARAM"
|
|
245
|
+
&& error.attempt.expected === "commission"
|
|
243
246
|
&& error.attempt.candidates.length === 0) {
|
|
244
247
|
return {
|
|
245
248
|
activeKeiyaku: false,
|
|
@@ -414,7 +417,7 @@ async function buildCallConfig(agentName, prompt, cwd, options) {
|
|
|
414
417
|
idleTimeoutMs: options.idleTimeoutMs,
|
|
415
418
|
});
|
|
416
419
|
// Mint projection directory for supervisor pump.
|
|
417
|
-
const slug = launchSnapshot.akuma.name
|
|
420
|
+
const slug = launchSnapshot.akuma.name;
|
|
418
421
|
const publishProjection = async () => {
|
|
419
422
|
const mintInput = {
|
|
420
423
|
cwd: options.projectionCwd ?? cwd,
|
|
@@ -462,7 +465,29 @@ async function buildCallConfig(agentName, prompt, cwd, options) {
|
|
|
462
465
|
finally {
|
|
463
466
|
store.close();
|
|
464
467
|
}
|
|
465
|
-
|
|
468
|
+
const previousAliasTarget = options.alias === undefined
|
|
469
|
+
? undefined
|
|
470
|
+
: moveProjectionAlias(options.projectionCwd ?? cwd, assertProjectionAlias(options.alias), minted.id);
|
|
471
|
+
const previousAliasTargetStillRunning = previousAliasTarget === undefined
|
|
472
|
+
? undefined
|
|
473
|
+
: (() => {
|
|
474
|
+
try {
|
|
475
|
+
const phase = observeProjectionLife(projectionDir(options.projectionCwd ?? cwd, previousAliasTarget), { nowMs: Date.now(), startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS }).phase;
|
|
476
|
+
return phase === "minting" || phase === "active" || phase === "quiescent"
|
|
477
|
+
? previousAliasTarget
|
|
478
|
+
: undefined;
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
})();
|
|
484
|
+
return {
|
|
485
|
+
id: minted.id,
|
|
486
|
+
dir: minted.dir,
|
|
487
|
+
executionId,
|
|
488
|
+
...(options.alias ? { alias: options.alias } : {}),
|
|
489
|
+
...(previousAliasTargetStillRunning ? { previousAliasTarget: previousAliasTargetStillRunning } : {}),
|
|
490
|
+
};
|
|
466
491
|
};
|
|
467
492
|
// Every public launch has one durable projection object. Repository-free
|
|
468
493
|
// authority changes the storage owner, not whether the object exists; mint
|
|
@@ -565,6 +590,7 @@ export async function runCall(input) {
|
|
|
565
590
|
binding: keiyakuContext.address.binding,
|
|
566
591
|
title,
|
|
567
592
|
context: input.context,
|
|
593
|
+
...(input.mode === "call" && input.alias !== undefined ? { alias: assertProjectionAlias(input.alias) } : {}),
|
|
568
594
|
};
|
|
569
595
|
if ((input.mode ?? "call") === "call") {
|
|
570
596
|
const launched = await launchSubagentGeneration(launchSnapshot.akuma.name, prompt, executionCwd, executionOptions);
|
|
@@ -574,6 +600,8 @@ export async function runCall(input) {
|
|
|
574
600
|
nowMs: Date.now(),
|
|
575
601
|
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
576
602
|
}).phase,
|
|
603
|
+
...(launched.projection.alias ? { alias: launched.projection.alias } : {}),
|
|
604
|
+
...(launched.projection.previousAliasTarget ? { previousAliasTarget: launched.projection.previousAliasTarget } : {}),
|
|
577
605
|
};
|
|
578
606
|
input.onLaunch?.({ address: keiyakuContext.address, projection: adoptedProjection });
|
|
579
607
|
if (input.detach) {
|