@akagilnc/pi-workflow-roles 0.1.4802 → 0.1.4870
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/dist/acp-host/production-host.js +19434 -18218
- package/dist/analyst-book-key.js +63 -0
- package/dist/analyst-cohort.js +167 -0
- package/dist/analyst-entry.js +320 -0
- package/dist/analyst-index.js +247 -0
- package/dist/analyst-ledger.js +561 -0
- package/dist/analyst-median.js +16 -0
- package/dist/analyst-metric-families/acceptance-success-rework.js +249 -0
- package/dist/analyst-metric-families/b2-frame-buckets-actions.js +189 -0
- package/dist/analyst-metric-families/gate-cycles.js +77 -0
- package/dist/analyst-metric-families/leg-wall-clock.js +56 -0
- package/dist/analyst-metric-families/round-timeline.js +126 -0
- package/dist/analyst-metric-families.js +23 -0
- package/dist/analyst-metric-family.js +11 -0
- package/dist/analyst-model-groups.js +135 -0
- package/dist/analyst-page.js +182 -0
- package/dist/atomic-write.js +23 -0
- package/dist/auditor-soul.js +1 -1
- package/dist/countersign-contracts.js +0 -2
- package/dist/diarist-contracts.js +0 -1
- package/dist/doctor-contracts.js +2 -2
- package/dist/gatekeeper-role.js +15 -16
- package/dist/gleaner-left-contracts.js +0 -2
- package/dist/headless-host/production-host.js +16967 -15749
- package/dist/host-contracts.js +0 -4
- package/dist/inspector-contracts.js +0 -2
- package/dist/institutional-resolution.js +4 -2
- package/dist/merger-contracts.js +0 -2
- package/dist/migrate-book-topology.js +3908 -3089
- package/dist/navigator-public-session.js +4 -3
- package/dist/notary-contracts.js +0 -2
- package/dist/package-contracts/auditor-output.js +0 -1
- package/dist/package-contracts/collector-output.js +0 -2
- package/dist/package-contracts/fixer-output.js +0 -2
- package/dist/package-contracts/gatekeeper-output.js +0 -2
- package/dist/package-contracts/judge-output.js +0 -2
- package/dist/package-contracts/navigator-output.js +0 -2
- package/dist/package-contracts/reviewer-output.js +0 -2
- package/dist/package-contracts/terminating-tools.js +32 -92
- package/dist/package-contracts/worker-output.js +0 -4
- package/dist/packaged-role-registry.js +518 -36
- package/dist/pi/role-turn-host.js +2 -1
- package/dist/public-cli/analyst-run.js +185 -0
- package/dist/public-cli/cli.js +885 -0
- package/dist/public-cli/config.js +12 -9
- package/dist/public-cli/countersign-run.js +4 -311
- package/dist/public-cli/instruction-seat-run.js +628 -158
- package/dist/public-cli/invocation.js +806 -1550
- package/dist/public-cli/main.js +6962 -9882
- package/dist/public-cli/option-definitions.js +51 -99
- package/dist/public-cli/post-admission.js +30 -2
- package/dist/public-cli/run-lifecycle.js +234 -353
- package/dist/public-cli/seat-ticket-binding.js +6 -0
- package/dist/public-cli/settlement.js +261 -675
- package/dist/public-cli/turn-request.js +76 -0
- package/dist/public-role-summons.js +38 -112
- package/dist/role-activation-flags.js +23 -45
- package/dist/secretariat-contracts.js +0 -2
- package/dist/session-identity.js +17 -8
- package/dist/session-opening-materials.js +3 -2
- package/extensions/role-runtime.ts +6 -80
- package/package.json +1 -1
- package/src/acp-host/role-turn-host.ts +4 -1
- package/src/analyst-metric-families/acceptance-success-rework.ts +3 -2
- package/src/auditor-role.ts +1 -2
- package/src/auditor-soul.ts +1 -1
- package/src/collector-role.ts +2 -2
- package/src/countersign-contracts.ts +0 -1
- package/src/countersign-role.ts +1 -1
- package/src/diarist-contracts.ts +0 -1
- package/src/diarist-role.ts +0 -1
- package/src/doctor-auditor.ts +1 -2
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-role.ts +3 -2
- package/src/external-host-turn-loop.ts +1 -1
- package/src/factory-board.ts +10 -7
- package/src/gatekeeper-role.ts +16 -10
- package/src/gleaner-left-contracts.ts +0 -1
- package/src/gleaner-left-role.ts +0 -1
- package/src/headless-host/role-turn-host.ts +7 -1
- package/src/host-contracts.ts +10 -6
- package/src/inspector-contracts.ts +0 -1
- package/src/inspector-role.ts +0 -2
- package/src/institutional-resolution.ts +6 -4
- package/src/judge-role.ts +3 -3
- package/src/merger-contracts.ts +0 -1
- package/src/merger-role.ts +2 -2
- package/src/navigator-public-session.ts +7 -6
- package/src/navigator-work-context.ts +6 -8
- package/src/notary-contracts.ts +0 -1
- package/src/notary-role.ts +2 -3
- package/src/package-contracts/auditor-output.ts +0 -1
- package/src/package-contracts/collector-output.ts +0 -1
- package/src/package-contracts/fixer-output.ts +0 -1
- package/src/package-contracts/gatekeeper-output.ts +0 -1
- package/src/package-contracts/judge-output.ts +0 -1
- package/src/package-contracts/navigator-output.ts +0 -1
- package/src/package-contracts/reviewer-output.ts +0 -1
- package/src/package-contracts/terminating-tools.ts +27 -99
- package/src/package-contracts/worker-output.ts +0 -2
- package/src/packaged-role-registry.ts +604 -35
- package/src/pi/role-turn-host.ts +2 -1
- package/src/public-cli/cli.ts +64 -259
- package/src/public-cli/config.ts +18 -10
- package/src/public-cli/countersign-run.ts +14 -449
- package/src/public-cli/instruction-seat-run.ts +758 -277
- package/src/public-cli/invocation.ts +1097 -2051
- package/src/public-cli/option-definitions.ts +62 -127
- package/src/public-cli/post-admission.ts +47 -2
- package/src/public-cli/run-lifecycle.ts +303 -613
- package/src/public-cli/seat-ticket-binding.ts +6 -0
- package/src/public-cli/settlement.ts +399 -1185
- package/src/public-cli/turn-request.ts +92 -1
- package/src/public-role-summons.ts +44 -128
- package/src/reviewer-role.ts +3 -2
- package/src/role-activation-flags.ts +20 -41
- package/src/role-runtime-dependencies.ts +25 -26
- package/src/role-runtime.ts +121 -232
- package/src/secretariat-contracts.ts +0 -1
- package/src/secretariat-role.ts +0 -2
- package/src/session-identity.ts +23 -8
- package/src/session-opening-materials.ts +2 -1
- package/src/worker-role.ts +3 -4
- package/dist/doctor-auditor.js +0 -25
- package/dist/public-cli/diarist-run.js +0 -130
- package/dist/public-cli/doctor-run.js +0 -87
- package/dist/public-cli/inspector-run.js +0 -151
- package/dist/public-cli/judge-run.js +0 -114
- package/dist/public-cli/notary-run.js +0 -174
- package/dist/public-cli/reviewer-run.js +0 -339
- package/src/public-cli/coder-run.ts +0 -255
- package/src/public-cli/collector-run.ts +0 -189
- package/src/public-cli/diarist-run.ts +0 -233
- package/src/public-cli/doctor-run.ts +0 -167
- package/src/public-cli/fixer-run.ts +0 -265
- package/src/public-cli/gleaner-left-run.ts +0 -167
- package/src/public-cli/inspector-run.ts +0 -261
- package/src/public-cli/judge-run.ts +0 -196
- package/src/public-cli/merger-run.ts +0 -294
- package/src/public-cli/notary-run.ts +0 -279
- package/src/public-cli/reviewer-run.ts +0 -480
- package/src/public-cli/secretariat-run.ts +0 -265
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
import { piDurablePrincipalAuthority } from "../pi/durable-principal.js";
|
|
2
|
+
/**
|
|
3
|
+
* Public ak-role CLI dispatcher (roles / config / layered help / Judge run).
|
|
4
|
+
*/
|
|
5
|
+
import { realpath } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { packageMachineHome } from "../activation-ledger-topology.js";
|
|
8
|
+
import { assertLegalEngineModel, assertLegalEngineName, pickEngineAxis, } from "../package-resources/engine-material.js";
|
|
9
|
+
import { resolveConfiguredProvinceOfficer, } from "../institutional-resolution.js";
|
|
10
|
+
import { clearPersistentSeatConfig, effectiveSeatConfigurations, formatModelSpec, isGateOfficerSeat, loadCredentialProviders, loadPublicCliConfig, missingResolvedSeatModelMessage, parseModelSpec, resolveEffectiveSeat, resolvedSeatWithModel, savePublicCliConfig, setAutoResumeLimit, setPersistentSeatConfig, setPersistentSeatEngine, setPersistentSeatEngineModel, setPersistentSeatHost, validatePublicCliConfigAxes, } from "./config.js";
|
|
11
|
+
import { loadHostProvidersTable, projectHostFacingProvider, renderHostProvidersTable, } from "./host-providers.js";
|
|
12
|
+
import { packagedModelParent } from "../packaged-role-registry.js";
|
|
13
|
+
import { seatModelOnly } from "./registry.js";
|
|
14
|
+
import { CliUsageError } from "./cli-errors.js";
|
|
15
|
+
import { appendPiSessionCustomEntry } from "../pi/role-turn-host.js";
|
|
16
|
+
import { composeRoleTurnHostAdapters, formatHostSelectionFailure, HostSelectionError, selectRoleTurnHost, } from "./role-turn-host-resolution.js";
|
|
17
|
+
import { parseAnalystArgv, parsePublicSeatArgv, } from "./invocation.js";
|
|
18
|
+
import { createTypedOptionConsumer, optionsForOwner, projectCommandHelp, projectOwnerOptions, PUBLIC_NAVIGATOR_HELP_NOTE, PUBLIC_ROLE_OPTION_OWNERS, renderHumanOwnerOptionLines, } from "./option-definitions.js";
|
|
19
|
+
import { runPublicInstructionSeat, runPublicInstructionSeatResume } from "./instruction-seat-run.js";
|
|
20
|
+
import { runPublicAnalyst } from "./analyst-run.js";
|
|
21
|
+
import { AUTO_RESUME_LIMIT, peekRoleRunRole, } from "./run-lifecycle.js";
|
|
22
|
+
import { INTERNAL_ROLE_ENTRYPOINT_RELATIVE, PUBLIC_CALLABLE_ROLES, isPublicCallableRole, isPublicCliSupportCommand, isPublicConfigurableSeat, listHelpCapabilities, } from "./registry.js";
|
|
23
|
+
import { formatCliDiagnostic, formatErrorCauseDetail, presentStructuralRejection, } from "./settlement.js";
|
|
24
|
+
export { buildExplicitInternalActivationArgs, resolveInternalRoleEntrypoint, } from "../pi/role-turn-host.js";
|
|
25
|
+
export { CliUsageError } from "./cli-errors.js";
|
|
26
|
+
/**
|
|
27
|
+
* Sole production map: public role command → one argv parse + that owner's option row.
|
|
28
|
+
* Seat option values are assigned in parsePublicSeatArgv. Analyst stays deterministic.
|
|
29
|
+
*/
|
|
30
|
+
function publicSeatArgv(owner) {
|
|
31
|
+
return {
|
|
32
|
+
parse: (args) => parsePublicSeatArgv(owner, args),
|
|
33
|
+
options: optionsForOwner(owner),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One argv row per public owner. Seats come from PUBLIC_ROLE_OPTION_OWNERS
|
|
38
|
+
* (composition-root roles plus the deterministic analyst command).
|
|
39
|
+
*/
|
|
40
|
+
export const PUBLIC_ROLE_ARGV = Object.fromEntries(PUBLIC_ROLE_OPTION_OWNERS.map((owner) => [
|
|
41
|
+
owner,
|
|
42
|
+
owner === "analyst"
|
|
43
|
+
? { parse: parseAnalystArgv, options: optionsForOwner("analyst") }
|
|
44
|
+
: publicSeatArgv(owner),
|
|
45
|
+
]));
|
|
46
|
+
/** Global public options — same typed table as role rows (#342). */
|
|
47
|
+
export const PUBLIC_GLOBAL_OPTIONS = optionsForOwner("global");
|
|
48
|
+
/**
|
|
49
|
+
* If `argv[index]` is a public global flag, describe its span and payload.
|
|
50
|
+
* Spellings + repeatable come solely from PUBLIC_OPTION_TABLE.global via the
|
|
51
|
+
* shared typed consumer (#342).
|
|
52
|
+
*/
|
|
53
|
+
function takePublicGlobalFlag(argv, index, options) {
|
|
54
|
+
const tokens = argv.slice(index);
|
|
55
|
+
const taken = options.takeDashed(tokens);
|
|
56
|
+
if (taken === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
const consumed = argv.length - index - tokens.length;
|
|
59
|
+
if (taken.def.id === "help") {
|
|
60
|
+
return { flag: "help", consume: consumed };
|
|
61
|
+
}
|
|
62
|
+
if (taken.def.id === "model") {
|
|
63
|
+
return {
|
|
64
|
+
flag: "model",
|
|
65
|
+
consume: consumed,
|
|
66
|
+
value: taken.value,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (taken.def.id === "thinking") {
|
|
70
|
+
return {
|
|
71
|
+
flag: "thinking",
|
|
72
|
+
consume: consumed,
|
|
73
|
+
raw: taken.value,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (taken.def.id === "engine" || taken.def.id === "host") {
|
|
77
|
+
return {
|
|
78
|
+
flag: taken.def.id,
|
|
79
|
+
consume: consumed,
|
|
80
|
+
value: taken.value,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
function createRoleEnvironment(env, options,
|
|
86
|
+
/** #178: after host selection, before missing-model — structural argv parse once. */
|
|
87
|
+
afterHost) {
|
|
88
|
+
const role = options.role;
|
|
89
|
+
const extraPiArgs = env.extraPiArgs;
|
|
90
|
+
const timeoutMs = env.timeoutMs;
|
|
91
|
+
// #617/#178/#788/#840: host first, then model; nested child seat selects own host.
|
|
92
|
+
const hostAdapters = composeRoleTurnHostAdapters({
|
|
93
|
+
packageRoot: env.packageRoot,
|
|
94
|
+
...(env.roleTurnHost === undefined ? {} : { roleTurnHost: env.roleTurnHost }),
|
|
95
|
+
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
96
|
+
...(extraPiArgs === undefined ? {} : { extraPiArgs }),
|
|
97
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
98
|
+
}, env.principalAuthority);
|
|
99
|
+
const roleTurnHost = selectRoleTurnHost(hostAdapters, {
|
|
100
|
+
role,
|
|
101
|
+
seat: options.seat,
|
|
102
|
+
});
|
|
103
|
+
// #178: host → argv (afterHost) → missing-model → provider projection.
|
|
104
|
+
afterHost?.();
|
|
105
|
+
const seatWithModel = resolvedSeatWithModel(options.seat);
|
|
106
|
+
if (seatWithModel === undefined) {
|
|
107
|
+
// Direct CLI has --model; dual remediation is intentional here (#178 / #916).
|
|
108
|
+
throw new CliUsageError(missingResolvedSeatModelMessage(options.seat.seat, "invocation-or-config"));
|
|
109
|
+
}
|
|
110
|
+
const hostFacingSelection = projectHostFacingProvider(seatWithModel.selection, seatWithModel.host, loadHostProvidersTable(options.home), options.home);
|
|
111
|
+
return {
|
|
112
|
+
home: options.home,
|
|
113
|
+
principalAuthority: env.principalAuthority,
|
|
114
|
+
agentDir: options.agentDir,
|
|
115
|
+
sessionAppender: appendPiSessionCustomEntry,
|
|
116
|
+
packageRoot: env.packageRoot,
|
|
117
|
+
roleTurnHost,
|
|
118
|
+
hostAdapters,
|
|
119
|
+
cwd: options.cwd,
|
|
120
|
+
...(options.credentials === undefined ? {} : { credentials: options.credentials }),
|
|
121
|
+
...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
|
|
122
|
+
...(hostFacingSelection === undefined ? {} : { model: hostFacingSelection }),
|
|
123
|
+
...projectSeatEngine(options.seat),
|
|
124
|
+
...projectSeatHost(options.seat),
|
|
125
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
126
|
+
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
127
|
+
...(options.config?.autoResumeLimit === undefined
|
|
128
|
+
? {}
|
|
129
|
+
: { autoResumeLimit: options.config.autoResumeLimit }),
|
|
130
|
+
...(env.freshSummons === true ? { freshSummons: true } : {}),
|
|
131
|
+
...(env.boundTicketNumber === undefined
|
|
132
|
+
? {}
|
|
133
|
+
: { boundTicketNumber: env.boundTicketNumber }),
|
|
134
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* #178 single spine: resolve seat → host select → argv parse once → missing-model → run.
|
|
139
|
+
* Keeps host-unregistered ahead of bad argv and missing-model.
|
|
140
|
+
*/
|
|
141
|
+
async function dispatchPublicRoleCommand(env, home, io, parsed, role, parse, run) {
|
|
142
|
+
const agentDir = resolveAgentDir(env, home);
|
|
143
|
+
const cwd = env.cwd ?? process.cwd();
|
|
144
|
+
const config = await loadAndValidateConfig(home, env.packageRoot);
|
|
145
|
+
const credentials = env.credentials ?? (await loadCredentialProviders(agentDir));
|
|
146
|
+
const seat = resolveEffectiveSeat(config, role, credentials, invocationFromParsed(parsed));
|
|
147
|
+
let parsedRoleArgv;
|
|
148
|
+
const result = await run(parsed.args, createRoleEnvironment(env, { role, home, agentDir, cwd, credentials, seat, config }, () => {
|
|
149
|
+
parsedRoleArgv = parse(parsed.args);
|
|
150
|
+
}), () => parsedRoleArgv);
|
|
151
|
+
return {
|
|
152
|
+
exitCode: result.exitCode,
|
|
153
|
+
...(result.terminal === undefined ? {} : { terminal: result.terminal }),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function cliResultFromRoleRun(result) {
|
|
157
|
+
return {
|
|
158
|
+
exitCode: result.exitCode,
|
|
159
|
+
...(result.terminal === undefined ? {} : { terminal: result.terminal }),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function defaultIo() {
|
|
163
|
+
return {
|
|
164
|
+
stdout: (text) => {
|
|
165
|
+
process.stdout.write(text);
|
|
166
|
+
},
|
|
167
|
+
stderr: (text) => {
|
|
168
|
+
process.stderr.write(text);
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function resolveHome(env) {
|
|
173
|
+
return env.home ?? packageMachineHome();
|
|
174
|
+
}
|
|
175
|
+
function resolveAgentDir(env, home) {
|
|
176
|
+
return (env.agentDir ??
|
|
177
|
+
process.env.PI_CODING_AGENT_DIR ??
|
|
178
|
+
join(home, ".pi", "agent"));
|
|
179
|
+
}
|
|
180
|
+
function parseThinking(value) {
|
|
181
|
+
// #683: opaque pass-through — no local whitelist.
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
function parseArgv(argv) {
|
|
185
|
+
const args = [...argv];
|
|
186
|
+
let model;
|
|
187
|
+
let thinking;
|
|
188
|
+
let engine;
|
|
189
|
+
let host;
|
|
190
|
+
let help = false;
|
|
191
|
+
const positional = [];
|
|
192
|
+
const globalOptions = createTypedOptionConsumer(PUBLIC_GLOBAL_OPTIONS);
|
|
193
|
+
// Global flags may appear before or after the subcommand
|
|
194
|
+
// (`ak-role --model x roles` and `ak-role roles --model x`).
|
|
195
|
+
// Grammar authority: shared typed consumer over PUBLIC_OPTION_TABLE.global.
|
|
196
|
+
// #471: after `resume <runId>`, remaining argv is the opaque message segment
|
|
197
|
+
// and must not re-enter the global-option consumer — including bare `--`,
|
|
198
|
+
// which is a legal opaque message token, not an argv delimiter here.
|
|
199
|
+
while (args.length > 0) {
|
|
200
|
+
if (positional[0] === "resume" && positional.length >= 2) {
|
|
201
|
+
positional.push(...args);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (args[0] === "--") {
|
|
205
|
+
args.shift();
|
|
206
|
+
positional.push(...args);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
const taken = takePublicGlobalFlag(args, 0, globalOptions);
|
|
210
|
+
if (taken !== undefined) {
|
|
211
|
+
if (taken.flag === "help") {
|
|
212
|
+
help = true;
|
|
213
|
+
args.splice(0, taken.consume);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (taken.flag === "model") {
|
|
217
|
+
if (taken.value === undefined) {
|
|
218
|
+
throw new CliUsageError("--model requires a value");
|
|
219
|
+
}
|
|
220
|
+
model = taken.value;
|
|
221
|
+
args.splice(0, taken.consume);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (taken.flag === "thinking") {
|
|
225
|
+
if (taken.raw === undefined) {
|
|
226
|
+
throw new CliUsageError("--thinking requires a value");
|
|
227
|
+
}
|
|
228
|
+
thinking = parseThinking(taken.raw);
|
|
229
|
+
args.splice(0, taken.consume);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (taken.flag === "engine" || taken.flag === "host") {
|
|
233
|
+
if (taken.value === undefined || taken.value.trim() === "") {
|
|
234
|
+
throw new CliUsageError(`--${taken.flag} requires a value`);
|
|
235
|
+
}
|
|
236
|
+
if (taken.flag === "engine")
|
|
237
|
+
engine = taken.value;
|
|
238
|
+
else
|
|
239
|
+
host = taken.value;
|
|
240
|
+
args.splice(0, taken.consume);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
// Exhaustive for known global flags; unknown id is a table bug.
|
|
244
|
+
throw new CliUsageError(`unhandled global option: ${String(taken.flag)}`);
|
|
245
|
+
}
|
|
246
|
+
// Subcommands may own additional flags later; unknown dashed tokens stay
|
|
247
|
+
// positional here (same as pre-unification parseArgv).
|
|
248
|
+
positional.push(args.shift());
|
|
249
|
+
}
|
|
250
|
+
const [command, ...rest] = positional;
|
|
251
|
+
return {
|
|
252
|
+
...(command === undefined ? {} : { command }),
|
|
253
|
+
args: rest,
|
|
254
|
+
...(model === undefined ? {} : { model }),
|
|
255
|
+
...(thinking === undefined ? {} : { thinking }),
|
|
256
|
+
...(engine === undefined ? {} : { engine }),
|
|
257
|
+
...(host === undefined ? {} : { host }),
|
|
258
|
+
help,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Unique public resume request parser (#471).
|
|
263
|
+
* One optional argv after runId is the opaque message; no further positionals.
|
|
264
|
+
*/
|
|
265
|
+
const RESUME_USAGE_ERROR = "usage: ak-role resume <runId> [message] (put --model/--thinking/--host/--engine before <runId>; the one argv after <runId> is the opaque message, #471)";
|
|
266
|
+
function parseResumeRequest(args) {
|
|
267
|
+
const runId = args[0];
|
|
268
|
+
if (runId === undefined || runId.trim() === "" || runId.startsWith("-")) {
|
|
269
|
+
throw new CliUsageError(RESUME_USAGE_ERROR);
|
|
270
|
+
}
|
|
271
|
+
if (args.length > 2) {
|
|
272
|
+
throw new CliUsageError(RESUME_USAGE_ERROR);
|
|
273
|
+
}
|
|
274
|
+
if (args.length === 2) {
|
|
275
|
+
return { runId, message: args[1] };
|
|
276
|
+
}
|
|
277
|
+
return { runId };
|
|
278
|
+
}
|
|
279
|
+
function invocationFromParsed(parsed) {
|
|
280
|
+
if (parsed.model === undefined &&
|
|
281
|
+
parsed.thinking === undefined &&
|
|
282
|
+
parsed.engine === undefined &&
|
|
283
|
+
parsed.host === undefined) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
...(parsed.model === undefined ? {} : { model: parsed.model }),
|
|
288
|
+
...(parsed.thinking === undefined ? {} : { thinking: parsed.thinking }),
|
|
289
|
+
...(parsed.engine === undefined ? {} : { engine: parsed.engine }),
|
|
290
|
+
...(parsed.host === undefined ? {} : { host: parsed.host }),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function requireLegalEngineModel(model) {
|
|
294
|
+
try {
|
|
295
|
+
return assertLegalEngineModel(model);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function requireLegalEngineName(name) {
|
|
302
|
+
try {
|
|
303
|
+
return assertLegalEngineName(name);
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/** Callable seats own persistent call axes (checked against the callable predicate). */
|
|
310
|
+
function requireCallableSeat(seat, axis, verb) {
|
|
311
|
+
if (!isPublicCallableRole(seat)) {
|
|
312
|
+
throw new CliUsageError(`unknown ${axis}-axis seat: ${seat}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** Single seat engine axis → run-options projection (#391 E2 / #883). */
|
|
316
|
+
function projectSeatEngine(seat) {
|
|
317
|
+
return pickEngineAxis(seat);
|
|
318
|
+
}
|
|
319
|
+
/** Single seat.host → run-options projection (#595 / #617). */
|
|
320
|
+
function projectSeatHost(seat) {
|
|
321
|
+
return { host: seat.host };
|
|
322
|
+
}
|
|
323
|
+
function loadAndValidateConfig(home, packageRoot) {
|
|
324
|
+
return loadPublicCliConfig(home).then((config) => {
|
|
325
|
+
try {
|
|
326
|
+
validatePublicCliConfigAxes(config, packageRoot);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
330
|
+
}
|
|
331
|
+
return config;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
/** Typed facts used by help presentation — not layout. */
|
|
335
|
+
export function helpDocument() {
|
|
336
|
+
return {
|
|
337
|
+
executable: "ak-role",
|
|
338
|
+
capabilities: listHelpCapabilities(),
|
|
339
|
+
internalEntrypoint: INTERNAL_ROLE_ENTRYPOINT_RELATIVE,
|
|
340
|
+
/** #342 structured global options from the sole option table. */
|
|
341
|
+
globalOptions: projectOwnerOptions("global"),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/** Structured help facts for one public command/role (#342). */
|
|
345
|
+
export function helpDocumentForCommand(command) {
|
|
346
|
+
if (command === "global") {
|
|
347
|
+
return {
|
|
348
|
+
command: "global",
|
|
349
|
+
kind: "global",
|
|
350
|
+
options: projectOwnerOptions("global"),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
if (command in PUBLIC_ROLE_ARGV) {
|
|
354
|
+
const owner = command;
|
|
355
|
+
return {
|
|
356
|
+
command: owner,
|
|
357
|
+
kind: owner === "analyst" ? "deterministic" : "role",
|
|
358
|
+
options: projectOwnerOptions(owner),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
/** Append USAGE + EXAMPLES blocks from the sole public help-copy owner. */
|
|
364
|
+
function appendUsageAndExamples(lines, topic) {
|
|
365
|
+
const facts = projectCommandHelp(topic);
|
|
366
|
+
if (facts === undefined)
|
|
367
|
+
return;
|
|
368
|
+
lines.push("", "USAGE");
|
|
369
|
+
for (const line of facts.usage) {
|
|
370
|
+
lines.push(` ${line}`);
|
|
371
|
+
}
|
|
372
|
+
if (facts.examples.length > 0) {
|
|
373
|
+
lines.push("", "EXAMPLES");
|
|
374
|
+
for (const example of facts.examples) {
|
|
375
|
+
lines.push(` ${example}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function renderHelp() {
|
|
380
|
+
const doc = helpDocument();
|
|
381
|
+
// "top" is a required PUBLIC_COMMAND_HELP topic — no fallback prose (#412/397-F3).
|
|
382
|
+
const top = projectCommandHelp("top");
|
|
383
|
+
const lines = [
|
|
384
|
+
`ak-role — ${top.summary}`,
|
|
385
|
+
];
|
|
386
|
+
appendUsageAndExamples(lines, "top");
|
|
387
|
+
lines.push("", PUBLIC_NAVIGATOR_HELP_NOTE);
|
|
388
|
+
lines.push("", "Support commands:");
|
|
389
|
+
for (const cap of doc.capabilities) {
|
|
390
|
+
if (cap.kind === "support") {
|
|
391
|
+
lines.push(` ${cap.name}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
lines.push("", "Callable roles:");
|
|
395
|
+
for (const cap of doc.capabilities) {
|
|
396
|
+
if (cap.kind === "role") {
|
|
397
|
+
const phaseText = cap.phases.length === 1 && cap.phases[0] === null
|
|
398
|
+
? "no phase"
|
|
399
|
+
: `phases ${cap.phases.filter((p) => p !== null).join("|")}` +
|
|
400
|
+
(cap.defaultPhase ? ` (default ${cap.defaultPhase})` : "");
|
|
401
|
+
lines.push(` ${cap.name} — ${phaseText}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
lines.push("", "Deterministic commands:");
|
|
405
|
+
for (const cap of doc.capabilities) {
|
|
406
|
+
if (cap.kind === "deterministic") {
|
|
407
|
+
lines.push(` ${cap.name}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
lines.push("", "OPTIONS");
|
|
411
|
+
lines.push(...renderHumanOwnerOptionLines("global"));
|
|
412
|
+
lines.push("", "Role options: ak-role help <command>", "Persistent config: ak-role config set <seat> <provider/model[:thinking]> | unset <gatekeeper|inspector|notary>", "Persistent engine (callable roles): ak-role config set-engine <seat> <name> [model] | unset-engine <seat> | set-engine-model <seat> <model> | unset-engine-model <seat>", "Persistent host (callable roles): ak-role config set-host <seat> <name> | unset-host <seat>", "Host providers: ~/.ak-roles/host-providers.json (owner-edited; table > unique host directory > fail)", "Model resolution: --model → persistent seat → officer inherit (#620); still none → error (#178)", "Host resolution: --host → persistent seat host → pi (resume uses the same order; #617)", "Resume flag position: --model/--thinking/--host/--engine go before <runId> (before `resume` or between `resume` and <runId>); the one argv after <runId> is always the opaque message, never a flag (#471)", "Effective seats: ak-role roles");
|
|
413
|
+
return `${lines.join("\n")}\n`;
|
|
414
|
+
}
|
|
415
|
+
function renderCommandHelp(command) {
|
|
416
|
+
const caps = listHelpCapabilities();
|
|
417
|
+
const match = caps.find((cap) => cap.name === command);
|
|
418
|
+
if (match === undefined)
|
|
419
|
+
return undefined;
|
|
420
|
+
const facts = projectCommandHelp(command);
|
|
421
|
+
const lines = [];
|
|
422
|
+
if (facts !== undefined) {
|
|
423
|
+
lines.push(`ak-role ${facts.command} — ${facts.summary}`);
|
|
424
|
+
}
|
|
425
|
+
else if (match.kind === "support") {
|
|
426
|
+
lines.push(`ak-role ${match.name}`);
|
|
427
|
+
}
|
|
428
|
+
else if (match.kind === "deterministic") {
|
|
429
|
+
lines.push(`ak-role ${match.name}`);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
lines.push(`ak-role ${match.name}`);
|
|
433
|
+
}
|
|
434
|
+
appendUsageAndExamples(lines, command);
|
|
435
|
+
// listHelpCapabilities never yields "global"; only role owners carry OPTIONS (#412/397-F3).
|
|
436
|
+
if (command in PUBLIC_ROLE_ARGV) {
|
|
437
|
+
const owner = command;
|
|
438
|
+
lines.push("", "OPTIONS");
|
|
439
|
+
lines.push(...renderHumanOwnerOptionLines(owner));
|
|
440
|
+
}
|
|
441
|
+
return `${lines.join("\n")}\n`;
|
|
442
|
+
}
|
|
443
|
+
function renderRoles(seats) {
|
|
444
|
+
const lines = ["seat\tsource\tmodel"];
|
|
445
|
+
for (const seat of seats) {
|
|
446
|
+
const model = seat.selection === undefined ? "-" : formatModelSpec(seat.selection);
|
|
447
|
+
lines.push(`${seat.seat}\t${seat.source}\t${model}`);
|
|
448
|
+
}
|
|
449
|
+
return `${lines.join("\n")}\n`;
|
|
450
|
+
}
|
|
451
|
+
function diskAxes(disk) {
|
|
452
|
+
return {
|
|
453
|
+
...pickEngineAxis(disk ?? {}),
|
|
454
|
+
...(disk?.host === undefined ? {} : { host: disk.host }),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Single-seat config projection (#620).
|
|
459
|
+
* - notary/inspector: institutional authority result (own > gatekeeper)
|
|
460
|
+
* - all other seats: disk model only (persistent face; no package fill-in, #178)
|
|
461
|
+
*/
|
|
462
|
+
export function projectConfigSeatDisplay(config, seat) {
|
|
463
|
+
const disk = config.seats[seat];
|
|
464
|
+
if (packagedModelParent(seat) !== undefined) {
|
|
465
|
+
const resolved = resolveConfiguredProvinceOfficer(config, seat);
|
|
466
|
+
return {
|
|
467
|
+
seat,
|
|
468
|
+
source: resolved.source,
|
|
469
|
+
...(resolved.selection === undefined ? {} : { selection: resolved.selection }),
|
|
470
|
+
...diskAxes(disk),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
const own = seatModelOnly(disk);
|
|
474
|
+
if (own !== undefined) {
|
|
475
|
+
return { seat, source: "persistent", selection: own, ...diskAxes(disk) };
|
|
476
|
+
}
|
|
477
|
+
if (disk === undefined) {
|
|
478
|
+
return { seat, source: "unconfigured" };
|
|
479
|
+
}
|
|
480
|
+
return { seat, source: "unconfigured", ...diskAxes(disk) };
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Bulk config show projection (#620).
|
|
484
|
+
* Disk seats only, plus notary/inspector inherit rows when gatekeeper supplies a
|
|
485
|
+
* model and the subordinate has no own pin — never invent province rows from an
|
|
486
|
+
* unrelated seat like coder.
|
|
487
|
+
*/
|
|
488
|
+
export function projectConfigDisplaySeats(config) {
|
|
489
|
+
const diskSeats = Object.keys(config.seats).filter((seat) => isPublicConfigurableSeat(seat));
|
|
490
|
+
const rows = new Map();
|
|
491
|
+
for (const seat of diskSeats) {
|
|
492
|
+
rows.set(seat, projectConfigSeatDisplay(config, seat));
|
|
493
|
+
}
|
|
494
|
+
for (const seat of PUBLIC_CALLABLE_ROLES) {
|
|
495
|
+
if (packagedModelParent(seat) === undefined || rows.has(seat))
|
|
496
|
+
continue;
|
|
497
|
+
const projected = projectConfigSeatDisplay(config, seat);
|
|
498
|
+
if (projected.source === "inherit-gatekeeper") {
|
|
499
|
+
rows.set(seat, projected);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return [...rows.keys()].sort().map((seat) => rows.get(seat));
|
|
503
|
+
}
|
|
504
|
+
function renderConfigDisplaySeat(row) {
|
|
505
|
+
const model = row.selection === undefined ? "-" : formatModelSpec(row.selection);
|
|
506
|
+
const engine = row.engine === undefined ? "-" : row.engine;
|
|
507
|
+
const engineModel = row.engineModel === undefined ? "-" : row.engineModel;
|
|
508
|
+
const host = row.host === undefined ? "-" : row.host;
|
|
509
|
+
return `${row.seat}\t${row.source}\t${model}\t${engine}\t${engineModel}\t${host}`;
|
|
510
|
+
}
|
|
511
|
+
function renderConfig(config, home) {
|
|
512
|
+
const lines = ["seat\tsource\tmodel\tengine\tengineModel\thost"];
|
|
513
|
+
const rows = projectConfigDisplaySeats(config);
|
|
514
|
+
if (rows.length === 0) {
|
|
515
|
+
lines.push("(empty)");
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
for (const row of rows) {
|
|
519
|
+
lines.push(renderConfigDisplaySeat(row));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// #422: show the effective auto-resume ceiling (configured value or default).
|
|
523
|
+
lines.push(`autoResumeLimit\t${config.autoResumeLimit ?? AUTO_RESUME_LIMIT}`);
|
|
524
|
+
// #788: owner host-providers table as written on disk (separate file).
|
|
525
|
+
const hostProvidersBlock = renderHostProvidersTable(loadHostProvidersTable(home));
|
|
526
|
+
return `${lines.join("\n")}\n${hostProvidersBlock}`;
|
|
527
|
+
}
|
|
528
|
+
async function runConfigCommand(args, home, packageRoot, io) {
|
|
529
|
+
if (args.length === 0 || args[0] === "get" || args[0] === "list" || args[0] === "show") {
|
|
530
|
+
const config = await loadAndValidateConfig(home, packageRoot);
|
|
531
|
+
if (args[0] === "get" && args[1] !== undefined) {
|
|
532
|
+
if (!isPublicConfigurableSeat(args[1])) {
|
|
533
|
+
throw new CliUsageError(`unknown configurable seat: ${args[1]}`);
|
|
534
|
+
}
|
|
535
|
+
io.stdout(`${renderConfigDisplaySeat(projectConfigSeatDisplay(config, args[1]))}\n`);
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
io.stdout(renderConfig(config, home));
|
|
539
|
+
return 0;
|
|
540
|
+
}
|
|
541
|
+
if (args[0] === "set") {
|
|
542
|
+
if (args.length < 3) {
|
|
543
|
+
throw new CliUsageError("usage: ak-role config set <seat> <provider/model[:thinking]>");
|
|
544
|
+
}
|
|
545
|
+
// Bulk: repeated seat spec pairs after `set`
|
|
546
|
+
const pairs = args.slice(1);
|
|
547
|
+
if (pairs.length % 2 !== 0) {
|
|
548
|
+
throw new CliUsageError("config set requires seat/spec pairs: ak-role config set <seat> <spec> [<seat> <spec> ...]");
|
|
549
|
+
}
|
|
550
|
+
let config = await loadAndValidateConfig(home, packageRoot);
|
|
551
|
+
for (let i = 0; i < pairs.length; i += 2) {
|
|
552
|
+
const seat = pairs[i];
|
|
553
|
+
const spec = pairs[i + 1];
|
|
554
|
+
if (!isPublicConfigurableSeat(seat)) {
|
|
555
|
+
throw new CliUsageError(`unknown configurable seat: ${seat}`);
|
|
556
|
+
}
|
|
557
|
+
// #384: persistent seat config shares the invocation model grammar.
|
|
558
|
+
// Bare provider/model stores as-is; :thinking suffix still required only when colon present.
|
|
559
|
+
config = setPersistentSeatConfig(config, seat, parseModelSpec(spec));
|
|
560
|
+
}
|
|
561
|
+
await savePublicCliConfig(config, home);
|
|
562
|
+
io.stdout(renderConfig(config, home));
|
|
563
|
+
return 0;
|
|
564
|
+
}
|
|
565
|
+
// #453: clear gate officer model override only (engine axis preserved).
|
|
566
|
+
if (args[0] === "unset") {
|
|
567
|
+
if (args.length !== 2) {
|
|
568
|
+
throw new CliUsageError("usage: ak-role config unset <gatekeeper|inspector|notary>");
|
|
569
|
+
}
|
|
570
|
+
const seat = args[1];
|
|
571
|
+
if (!isGateOfficerSeat(seat)) {
|
|
572
|
+
throw new CliUsageError(`config unset serves gate officer overrides only (gatekeeper|inspector|notary); got ${seat}`);
|
|
573
|
+
}
|
|
574
|
+
const config = clearPersistentSeatConfig(await loadAndValidateConfig(home, packageRoot), seat);
|
|
575
|
+
await savePublicCliConfig(config, home);
|
|
576
|
+
io.stdout(renderConfig(config, home));
|
|
577
|
+
return 0;
|
|
578
|
+
}
|
|
579
|
+
if (args[0] === "set-host" || args[0] === "unset-host") {
|
|
580
|
+
const unset = args[0] === "unset-host";
|
|
581
|
+
if (args.length !== (unset ? 2 : 3)) {
|
|
582
|
+
throw new CliUsageError(`usage: ak-role config ${args[0]} <seat>${unset ? "" : " <name>"}`);
|
|
583
|
+
}
|
|
584
|
+
const seat = args[1];
|
|
585
|
+
requireCallableSeat(seat, "host", unset ? "unset-host" : "set-host");
|
|
586
|
+
let config = await loadAndValidateConfig(home, packageRoot);
|
|
587
|
+
try {
|
|
588
|
+
config = setPersistentSeatHost(config, seat, unset ? undefined : args[2]);
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
592
|
+
}
|
|
593
|
+
await savePublicCliConfig(config, home);
|
|
594
|
+
io.stdout(renderConfig(config, home));
|
|
595
|
+
return 0;
|
|
596
|
+
}
|
|
597
|
+
if (args[0] === "set-engine") {
|
|
598
|
+
if (args.length !== 3 && args.length !== 4) {
|
|
599
|
+
throw new CliUsageError("usage: ak-role config set-engine <seat> <name> [model]");
|
|
600
|
+
}
|
|
601
|
+
const seat = args[1];
|
|
602
|
+
const name = args[2];
|
|
603
|
+
const engineModel = args[3];
|
|
604
|
+
requireCallableSeat(seat, "engine", "set-engine");
|
|
605
|
+
requireLegalEngineName(name);
|
|
606
|
+
if (engineModel !== undefined)
|
|
607
|
+
requireLegalEngineModel(engineModel);
|
|
608
|
+
let config = await loadAndValidateConfig(home, packageRoot);
|
|
609
|
+
try {
|
|
610
|
+
config = setPersistentSeatEngine(config, seat, name, engineModel);
|
|
611
|
+
}
|
|
612
|
+
catch (error) {
|
|
613
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
614
|
+
}
|
|
615
|
+
await savePublicCliConfig(config, home);
|
|
616
|
+
io.stdout(renderConfig(config, home));
|
|
617
|
+
return 0;
|
|
618
|
+
}
|
|
619
|
+
if (args[0] === "unset-engine") {
|
|
620
|
+
if (args.length !== 2) {
|
|
621
|
+
throw new CliUsageError("usage: ak-role config unset-engine <seat>");
|
|
622
|
+
}
|
|
623
|
+
const seat = args[1];
|
|
624
|
+
requireCallableSeat(seat, "engine", "unset-engine");
|
|
625
|
+
let config = await loadAndValidateConfig(home, packageRoot);
|
|
626
|
+
try {
|
|
627
|
+
config = setPersistentSeatEngine(config, seat, undefined);
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
631
|
+
}
|
|
632
|
+
await savePublicCliConfig(config, home);
|
|
633
|
+
io.stdout(renderConfig(config, home));
|
|
634
|
+
return 0;
|
|
635
|
+
}
|
|
636
|
+
if (args[0] === "set-engine-model") {
|
|
637
|
+
if (args.length !== 3) {
|
|
638
|
+
throw new CliUsageError("usage: ak-role config set-engine-model <seat> <model>");
|
|
639
|
+
}
|
|
640
|
+
const seat = args[1];
|
|
641
|
+
const model = args[2];
|
|
642
|
+
requireCallableSeat(seat, "engine", "set-engine-model");
|
|
643
|
+
requireLegalEngineModel(model);
|
|
644
|
+
let config = await loadAndValidateConfig(home, packageRoot);
|
|
645
|
+
try {
|
|
646
|
+
config = setPersistentSeatEngineModel(config, seat, model);
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
650
|
+
}
|
|
651
|
+
await savePublicCliConfig(config, home);
|
|
652
|
+
io.stdout(renderConfig(config, home));
|
|
653
|
+
return 0;
|
|
654
|
+
}
|
|
655
|
+
if (args[0] === "unset-engine-model") {
|
|
656
|
+
if (args.length !== 2) {
|
|
657
|
+
throw new CliUsageError("usage: ak-role config unset-engine-model <seat>");
|
|
658
|
+
}
|
|
659
|
+
const seat = args[1];
|
|
660
|
+
requireCallableSeat(seat, "engine", "unset-engine-model");
|
|
661
|
+
let config = await loadAndValidateConfig(home, packageRoot);
|
|
662
|
+
try {
|
|
663
|
+
config = setPersistentSeatEngineModel(config, seat, undefined);
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
667
|
+
}
|
|
668
|
+
await savePublicCliConfig(config, home);
|
|
669
|
+
io.stdout(renderConfig(config, home));
|
|
670
|
+
return 0;
|
|
671
|
+
}
|
|
672
|
+
// #422: standalone verb per the set-engine/unset-engine precedent — the
|
|
673
|
+
// existing `config set` grammar stays strictly even-position seat/spec pairs.
|
|
674
|
+
if (args[0] === "set-auto-resume-limit") {
|
|
675
|
+
if (args.length !== 2) {
|
|
676
|
+
throw new CliUsageError("usage: ak-role config set-auto-resume-limit <N>");
|
|
677
|
+
}
|
|
678
|
+
const raw = args[1];
|
|
679
|
+
if (!/^[0-9]+$/.test(raw)) {
|
|
680
|
+
throw new CliUsageError(`auto-resume limit must be a non-negative integer, got ${raw}`);
|
|
681
|
+
}
|
|
682
|
+
// #422 fidelity boundary (not an upper bound — ADR 0035 stays intact): the
|
|
683
|
+
// persisted representation is a JS number, and Number() silently rounds
|
|
684
|
+
// integers beyond 2^53-1 (e.g. 9007199254740993 → 9007199254740992). Refuse
|
|
685
|
+
// loudly instead of persisting a different N; every exactly-representable
|
|
686
|
+
// non-negative integer remains legal. The regex above guarantees pure
|
|
687
|
+
// digits, so BigInt(raw) has no leading-zero ambiguity.
|
|
688
|
+
const converted = Number(raw);
|
|
689
|
+
if (!Number.isFinite(converted) || BigInt(converted) !== BigInt(raw)) {
|
|
690
|
+
throw new CliUsageError(`auto-resume limit ${raw} is not exactly representable as a number; refusing to silently round the value`);
|
|
691
|
+
}
|
|
692
|
+
let config = await loadAndValidateConfig(home, packageRoot);
|
|
693
|
+
config = setAutoResumeLimit(config, converted);
|
|
694
|
+
await savePublicCliConfig(config, home);
|
|
695
|
+
io.stdout(renderConfig(config, home));
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
698
|
+
throw new CliUsageError(`unknown config subcommand: ${args[0]}`);
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* #855: process-cancel handlers only for commands that run a role turn
|
|
702
|
+
* (callable roles / resume / new). analyst/roles/config/help keep Node's
|
|
703
|
+
* default SIGINT/SIGTERM termination — installing handlers would swallow Ctrl+C.
|
|
704
|
+
*/
|
|
705
|
+
export function commandNeedsProcessCancel(argv) {
|
|
706
|
+
// Decision-time parse must not escape the CLI usage boundary. Malformed
|
|
707
|
+
// global flags throw CliUsageError here; return false and let runAkRole
|
|
708
|
+
// present the same structured usage reject (exit 2). Handlers are moot
|
|
709
|
+
// when parse fails — install-or-not has no subsequent effect.
|
|
710
|
+
let parsed;
|
|
711
|
+
try {
|
|
712
|
+
parsed = parseArgv(argv);
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
if (error instanceof CliUsageError)
|
|
716
|
+
return false;
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
719
|
+
if (parsed.help || parsed.command === undefined || parsed.command === "help") {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
if (parsed.command === "resume" || parsed.command === "new")
|
|
723
|
+
return true;
|
|
724
|
+
return isPublicCallableRole(parsed.command);
|
|
725
|
+
}
|
|
726
|
+
export async function runAkRole(argv, env) {
|
|
727
|
+
const io = env.io ?? defaultIo();
|
|
728
|
+
try {
|
|
729
|
+
// Select the installed package identity once, before any role-owned Skill,
|
|
730
|
+
// runtime entry, activation argv, or invocation provenance is derived.
|
|
731
|
+
env = {
|
|
732
|
+
...env,
|
|
733
|
+
packageRoot: await realpath(env.packageRoot),
|
|
734
|
+
principalAuthority: env.principalAuthority ?? piDurablePrincipalAuthority,
|
|
735
|
+
};
|
|
736
|
+
let parsed = parseArgv(argv);
|
|
737
|
+
// Host/engine axes: callable roles + resume + new (#617 DK-3; #724 new shares seat axes).
|
|
738
|
+
// Support commands (roles/config/…) still refuse both flags.
|
|
739
|
+
const acceptsSeatAxes = parsed.command !== undefined &&
|
|
740
|
+
(isPublicCallableRole(parsed.command) ||
|
|
741
|
+
parsed.command === "resume" ||
|
|
742
|
+
parsed.command === "new");
|
|
743
|
+
if (parsed.host !== undefined &&
|
|
744
|
+
!parsed.help &&
|
|
745
|
+
parsed.command !== undefined &&
|
|
746
|
+
parsed.command !== "help" &&
|
|
747
|
+
!acceptsSeatAxes) {
|
|
748
|
+
throw new CliUsageError(`host axis is role commands only; refused command ${parsed.command}`);
|
|
749
|
+
}
|
|
750
|
+
if (parsed.engine !== undefined) {
|
|
751
|
+
requireLegalEngineName(parsed.engine);
|
|
752
|
+
if (!parsed.help &&
|
|
753
|
+
parsed.command !== undefined &&
|
|
754
|
+
parsed.command !== "help" &&
|
|
755
|
+
!acceptsSeatAxes) {
|
|
756
|
+
throw new CliUsageError(`engine axis is role commands only; refused command ${parsed.command}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (parsed.help ||
|
|
760
|
+
parsed.command === undefined ||
|
|
761
|
+
parsed.command === "help") {
|
|
762
|
+
// Layered help: `help <topic>` derives from the typed registry + option table (#342).
|
|
763
|
+
// Home-free path: never touch passwd/user profile for help/bare/--help.
|
|
764
|
+
if (parsed.command === "help" && parsed.args[0] !== undefined) {
|
|
765
|
+
const topic = parsed.args[0];
|
|
766
|
+
const rendered = renderCommandHelp(topic);
|
|
767
|
+
if (rendered === undefined) {
|
|
768
|
+
throw new CliUsageError(`unknown help topic: ${topic}`);
|
|
769
|
+
}
|
|
770
|
+
io.stdout(rendered);
|
|
771
|
+
return { exitCode: 0 };
|
|
772
|
+
}
|
|
773
|
+
io.stdout(renderHelp());
|
|
774
|
+
return { exitCode: 0 };
|
|
775
|
+
}
|
|
776
|
+
// Profile home only after home-free paths return. Failures keep real identity
|
|
777
|
+
// and settle through the outer catch — no $HOME fallback (#604).
|
|
778
|
+
const home = resolveHome(env);
|
|
779
|
+
if (parsed.command === "roles") {
|
|
780
|
+
if (parsed.args.length > 0) {
|
|
781
|
+
throw new CliUsageError("roles takes no arguments");
|
|
782
|
+
}
|
|
783
|
+
const config = await loadAndValidateConfig(home, env.packageRoot);
|
|
784
|
+
const credentials = env.credentials ??
|
|
785
|
+
(await loadCredentialProviders(resolveAgentDir(env, home)));
|
|
786
|
+
const seats = effectiveSeatConfigurations(config, credentials, invocationFromParsed(parsed));
|
|
787
|
+
io.stdout(renderRoles(seats));
|
|
788
|
+
return { exitCode: 0 };
|
|
789
|
+
}
|
|
790
|
+
if (parsed.command === "config") {
|
|
791
|
+
return {
|
|
792
|
+
exitCode: await runConfigCommand(parsed.args, home, env.packageRoot, io),
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
// #724 explicit fresh summons: `ak-role new <role> …` — same role argv, always mint.
|
|
796
|
+
// Rewrites onto the role command with freshSummons; auto-resume and resume stay intact.
|
|
797
|
+
if (parsed.command === "new") {
|
|
798
|
+
const role = parsed.args[0];
|
|
799
|
+
if (role === undefined) {
|
|
800
|
+
throw new CliUsageError("usage: ak-role new <role> …");
|
|
801
|
+
}
|
|
802
|
+
if (!isPublicCallableRole(role)) {
|
|
803
|
+
throw new CliUsageError(`usage: ak-role new <role> …; unknown role: ${role}`);
|
|
804
|
+
}
|
|
805
|
+
parsed = {
|
|
806
|
+
...parsed,
|
|
807
|
+
command: role,
|
|
808
|
+
args: parsed.args.slice(1),
|
|
809
|
+
};
|
|
810
|
+
env = { ...env, freshSummons: true };
|
|
811
|
+
}
|
|
812
|
+
// Resume reopens an exact Role run (#416): caller decides; session principal
|
|
813
|
+
// must still exist. Seat and dispatch follow the durable admitted role.
|
|
814
|
+
// #471: unique parser owns {runId, message?}; five role paths only consume it.
|
|
815
|
+
if (parsed.command === "resume") {
|
|
816
|
+
const agentDir = resolveAgentDir(env, home);
|
|
817
|
+
const cwd = env.cwd ?? process.cwd();
|
|
818
|
+
const config = await loadAndValidateConfig(home, env.packageRoot);
|
|
819
|
+
const credentials = env.credentials ?? (await loadCredentialProviders(agentDir));
|
|
820
|
+
const resumeRequest = parseResumeRequest(parsed.args);
|
|
821
|
+
const resumeRole = await peekRoleRunRole(home, resumeRequest.runId);
|
|
822
|
+
// Missing durable role keeps the judge seat table. The resume entry
|
|
823
|
+
// itself is one function; it reads the stored run.
|
|
824
|
+
const seatRole = resumeRole ?? "judge";
|
|
825
|
+
const seat = resolveEffectiveSeat(config, seatRole, credentials, invocationFromParsed(parsed));
|
|
826
|
+
const result = await runPublicInstructionSeatResume(resumeRequest, createRoleEnvironment(env, {
|
|
827
|
+
role: seatRole,
|
|
828
|
+
home,
|
|
829
|
+
agentDir,
|
|
830
|
+
cwd,
|
|
831
|
+
credentials,
|
|
832
|
+
seat,
|
|
833
|
+
config,
|
|
834
|
+
}), io);
|
|
835
|
+
return cliResultFromRoleRun(result);
|
|
836
|
+
}
|
|
837
|
+
if (parsed.command !== undefined &&
|
|
838
|
+
isPublicCliSupportCommand(parsed.command)) {
|
|
839
|
+
throw new CliUsageError(`unhandled support command: ${parsed.command}`);
|
|
840
|
+
}
|
|
841
|
+
// Public LLM role commands share one spine: registry parser → one runner.
|
|
842
|
+
if (parsed.command !== undefined && isPublicCallableRole(parsed.command)) {
|
|
843
|
+
const role = parsed.command;
|
|
844
|
+
return await dispatchPublicRoleCommand(env, home, io, parsed, role, (args) => PUBLIC_ROLE_ARGV[role].parse(args), (args, roleEnv, once) => runPublicInstructionSeat(args, roleEnv, io, role, once));
|
|
845
|
+
}
|
|
846
|
+
// Analyst public run path: deterministic analysis seat (#336 issue / #337 sweep).
|
|
847
|
+
// Not an LLM PUBLIC_CALLABLE_ROLE — registered only on PUBLIC_ROLE_ARGV (#176).
|
|
848
|
+
if (parsed.command === "analyst") {
|
|
849
|
+
const result = await runPublicAnalyst(parsed.args, { home }, io, PUBLIC_ROLE_ARGV.analyst.parse);
|
|
850
|
+
return { exitCode: result.exitCode };
|
|
851
|
+
}
|
|
852
|
+
// #115: every PUBLIC_CALLABLE_ROLE has a completed handler above. Unknown
|
|
853
|
+
// tokens (including misspelled role names) are structural rejects.
|
|
854
|
+
throw new CliUsageError(`unknown command: ${parsed.command}`);
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
if (error instanceof HostSelectionError) {
|
|
858
|
+
io.stderr(formatCliDiagnostic(formatHostSelectionFailure(error.failure)));
|
|
859
|
+
return { exitCode: 1, hostFailure: error.failure };
|
|
860
|
+
}
|
|
861
|
+
if (error instanceof CliUsageError) {
|
|
862
|
+
// Non-judge structural paths share the same rejection presenter as Judge.
|
|
863
|
+
presentStructuralRejection(error, io);
|
|
864
|
+
return { exitCode: 2 };
|
|
865
|
+
}
|
|
866
|
+
// Unrecognized outer failure: retain actual name/message identity (no wash).
|
|
867
|
+
// #676 B: cause (HTTP body/headers, git stderr, nested Error) must reach the
|
|
868
|
+
// caller via the same formatErrorCauseDetail face as structural rejection.
|
|
869
|
+
if (error instanceof Error) {
|
|
870
|
+
let label = error.name !== "" && error.name !== "Error"
|
|
871
|
+
? `${error.name}: ${error.message}`
|
|
872
|
+
: error.message;
|
|
873
|
+
if (error.cause !== undefined) {
|
|
874
|
+
const detail = formatErrorCauseDetail(error.cause);
|
|
875
|
+
if (detail.trim().length > 0) {
|
|
876
|
+
label = `${label || error.name || "exception"}; cause: ${detail}`;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
io.stderr(formatCliDiagnostic(label || error.name || "exception"));
|
|
880
|
+
return { exitCode: 1 };
|
|
881
|
+
}
|
|
882
|
+
io.stderr(formatCliDiagnostic(String(error)));
|
|
883
|
+
return { exitCode: 1 };
|
|
884
|
+
}
|
|
885
|
+
}
|