@akagilnc/pi-workflow-roles 0.1.3733 → 0.1.3741
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/README.md +1 -3
- package/README.zh-CN.md +1 -3
- package/dist/acp-host/production-host.js +278 -201
- package/dist/public-cli/config.js +2 -102
- package/dist/public-cli/host-providers.js +188 -0
- package/dist/public-cli/main.js +350 -324
- package/dist/public-cli/option-definitions.js +1 -4
- package/dist/public-role-summons.js +8 -4
- package/package.json +1 -1
- package/souls/countersign.md +1 -1
- package/souls/judge.md +21 -68
- package/souls/notary.md +1 -1
- package/src/public-cli/cli.ts +38 -71
- package/src/public-cli/config.ts +3 -153
- package/src/public-cli/host-providers.ts +246 -0
- package/src/public-cli/option-definitions.ts +1 -4
- package/src/public-role-summons.ts +14 -7
|
@@ -139,50 +139,6 @@ function parseAutoResumeLimit(value) {
|
|
|
139
139
|
function setAutoResumeLimit(config, limit) {
|
|
140
140
|
return { ...config, autoResumeLimit: parseAutoResumeLimit(limit) };
|
|
141
141
|
}
|
|
142
|
-
function requireNonEmptyToken(value, label) {
|
|
143
|
-
const trimmed = value.trim();
|
|
144
|
-
if (trimmed === "") {
|
|
145
|
-
throw new Error(`${label} must be a non-empty string`);
|
|
146
|
-
}
|
|
147
|
-
return trimmed;
|
|
148
|
-
}
|
|
149
|
-
function setProviderHostAlias(config, provider, host, alias) {
|
|
150
|
-
const from = requireNonEmptyToken(provider, "provider");
|
|
151
|
-
const hostName = requireNonEmptyToken(host, "host");
|
|
152
|
-
const to = requireNonEmptyToken(alias, "alias");
|
|
153
|
-
const previous = config.providerAliases ?? {};
|
|
154
|
-
return {
|
|
155
|
-
...config,
|
|
156
|
-
providerAliases: {
|
|
157
|
-
...previous,
|
|
158
|
-
[from]: {
|
|
159
|
-
...previous[from] ?? {},
|
|
160
|
-
[hostName]: to
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
function unsetProviderHostAlias(config, provider, host) {
|
|
166
|
-
const from = requireNonEmptyToken(provider, "provider");
|
|
167
|
-
const hostName = requireNonEmptyToken(host, "host");
|
|
168
|
-
const previous = config.providerAliases;
|
|
169
|
-
if (previous === void 0 || previous[from] === void 0) return config;
|
|
170
|
-
const { [hostName]: _dropped, ...restHosts } = previous[from];
|
|
171
|
-
const nextForProvider = Object.keys(restHosts).length === 0 ? void 0 : restHosts;
|
|
172
|
-
const { [from]: _provider, ...restProviders } = previous;
|
|
173
|
-
const nextAliases = nextForProvider === void 0 ? restProviders : { ...restProviders, [from]: nextForProvider };
|
|
174
|
-
if (Object.keys(nextAliases).length === 0) {
|
|
175
|
-
const { providerAliases: _gone, ...rest } = config;
|
|
176
|
-
return rest;
|
|
177
|
-
}
|
|
178
|
-
return { ...config, providerAliases: nextAliases };
|
|
179
|
-
}
|
|
180
|
-
function applyProviderHostAlias(selection, host, aliases) {
|
|
181
|
-
if (selection === void 0 || aliases === void 0) return selection;
|
|
182
|
-
const mapped = aliases[selection.provider]?.[host];
|
|
183
|
-
if (mapped === void 0) return selection;
|
|
184
|
-
return { ...selection, provider: mapped };
|
|
185
|
-
}
|
|
186
142
|
function validatePublicCliConfigAxes(config, _packageRoot) {
|
|
187
143
|
for (const seat of Object.keys(config.seats)) {
|
|
188
144
|
const row = config.seats[seat];
|
|
@@ -252,44 +208,9 @@ function serializePublicCliConfig(config) {
|
|
|
252
208
|
...config.unknownSeats ?? {},
|
|
253
209
|
...config.seats
|
|
254
210
|
},
|
|
255
|
-
...config.autoResumeLimit === void 0 ? {} : { autoResumeLimit: config.autoResumeLimit }
|
|
256
|
-
...config.providerAliases === void 0 || Object.keys(config.providerAliases).length === 0 ? {} : { providerAliases: config.providerAliases }
|
|
211
|
+
...config.autoResumeLimit === void 0 ? {} : { autoResumeLimit: config.autoResumeLimit }
|
|
257
212
|
};
|
|
258
213
|
}
|
|
259
|
-
function parseProviderHostAliases(value) {
|
|
260
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
261
|
-
throw new Error("public CLI config.providerAliases must be an object");
|
|
262
|
-
}
|
|
263
|
-
const out = {};
|
|
264
|
-
for (const [provider, byHost] of Object.entries(value)) {
|
|
265
|
-
if (provider.trim() === "") {
|
|
266
|
-
throw new Error("public CLI config.providerAliases provider key must be non-empty");
|
|
267
|
-
}
|
|
268
|
-
if (byHost === null || typeof byHost !== "object" || Array.isArray(byHost)) {
|
|
269
|
-
throw new Error(
|
|
270
|
-
`public CLI config.providerAliases[${provider}] must be an object`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
const hosts = {};
|
|
274
|
-
for (const [host, alias] of Object.entries(byHost)) {
|
|
275
|
-
if (host.trim() === "") {
|
|
276
|
-
throw new Error(
|
|
277
|
-
`public CLI config.providerAliases[${provider}] host key must be non-empty`
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
if (typeof alias !== "string" || alias.trim() === "") {
|
|
281
|
-
throw new Error(
|
|
282
|
-
`public CLI config.providerAliases[${provider}][${host}] must be a non-empty string`
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
hosts[host] = alias;
|
|
286
|
-
}
|
|
287
|
-
if (Object.keys(hosts).length > 0) {
|
|
288
|
-
out[provider] = hosts;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return out;
|
|
292
|
-
}
|
|
293
214
|
function parsePublicCliConfig(value) {
|
|
294
215
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
295
216
|
throw new Error("public CLI config must be an object");
|
|
@@ -299,13 +220,6 @@ function parsePublicCliConfig(value) {
|
|
|
299
220
|
if (record.autoResumeLimit !== void 0) {
|
|
300
221
|
autoResumeLimit = parseAutoResumeLimit(record.autoResumeLimit);
|
|
301
222
|
}
|
|
302
|
-
let providerAliases;
|
|
303
|
-
if (record.providerAliases !== void 0) {
|
|
304
|
-
const parsed = parseProviderHostAliases(record.providerAliases);
|
|
305
|
-
if (Object.keys(parsed).length > 0) {
|
|
306
|
-
providerAliases = parsed;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
223
|
const unknownSeats = {};
|
|
310
224
|
if (record.unknownSeats !== void 0) {
|
|
311
225
|
if (record.unknownSeats === null || typeof record.unknownSeats !== "object" || Array.isArray(record.unknownSeats)) {
|
|
@@ -316,7 +230,6 @@ function parsePublicCliConfig(value) {
|
|
|
316
230
|
const withOpaque = (seats2) => ({
|
|
317
231
|
seats: seats2,
|
|
318
232
|
...autoResumeLimit === void 0 ? {} : { autoResumeLimit },
|
|
319
|
-
...providerAliases === void 0 ? {} : { providerAliases },
|
|
320
233
|
...Object.keys(unknownSeats).length === 0 ? {} : { unknownSeats }
|
|
321
234
|
});
|
|
322
235
|
if (record.seats === void 0) {
|
|
@@ -493,21 +406,11 @@ function resolveEffectiveSeat(config, seat, credentials, invocation) {
|
|
|
493
406
|
};
|
|
494
407
|
}
|
|
495
408
|
}
|
|
496
|
-
|
|
409
|
+
return attachHostAxis(
|
|
497
410
|
attachEngineAxis(modelSeat, config, invocation),
|
|
498
411
|
config,
|
|
499
412
|
invocation
|
|
500
413
|
);
|
|
501
|
-
const selection = applyProviderHostAlias(
|
|
502
|
-
withAxes.selection,
|
|
503
|
-
withAxes.host,
|
|
504
|
-
config.providerAliases
|
|
505
|
-
);
|
|
506
|
-
if (selection === void 0) {
|
|
507
|
-
const { selection: _dropped, ...rest } = withAxes;
|
|
508
|
-
return rest;
|
|
509
|
-
}
|
|
510
|
-
return { ...withAxes, selection };
|
|
511
414
|
}
|
|
512
415
|
function effectiveSeatConfigurations(config, credentials, invocation) {
|
|
513
416
|
return PUBLIC_CONFIGURABLE_SEATS.map(
|
|
@@ -540,7 +443,6 @@ async function loadCredentialProviders(agentDir) {
|
|
|
540
443
|
}
|
|
541
444
|
export {
|
|
542
445
|
GATE_OFFICER_SEATS,
|
|
543
|
-
applyProviderHostAlias,
|
|
544
446
|
buildSeatModelCliArgs,
|
|
545
447
|
clearPersistentSeatConfig,
|
|
546
448
|
credentialProvidersFromAuthData,
|
|
@@ -561,7 +463,5 @@ export {
|
|
|
561
463
|
setPersistentSeatConfig,
|
|
562
464
|
setPersistentSeatEngine,
|
|
563
465
|
setPersistentSeatHost,
|
|
564
|
-
setProviderHostAlias,
|
|
565
|
-
unsetProviderHostAlias,
|
|
566
466
|
validatePublicCliConfigAxes
|
|
567
467
|
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #788: owner-editable host provider map + host-directory resolution.
|
|
3
|
+
*
|
|
4
|
+
* Data file `~/.ak-roles/host-providers.json` is the sole owner-written map.
|
|
5
|
+
* Shape: { "<host>": { "<seat-provider>": "<host-provider>" } }.
|
|
6
|
+
* Code only reads it — no built-in pairs, no registration commands.
|
|
7
|
+
*
|
|
8
|
+
* Priority: table > unique directory match > loud failure.
|
|
9
|
+
* This ticket implements directory query for hermes only.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
export function hostProvidersPath(home) {
|
|
14
|
+
if (typeof home !== "string" || home.trim() === "") {
|
|
15
|
+
throw new Error("home must be explicitly provided");
|
|
16
|
+
}
|
|
17
|
+
return join(home, ".ak-roles", "host-providers.json");
|
|
18
|
+
}
|
|
19
|
+
/** Hermes provider model catalog under the operator home. */
|
|
20
|
+
export function hermesProviderModelsCachePath(home) {
|
|
21
|
+
return join(home, ".hermes", "provider_models_cache.json");
|
|
22
|
+
}
|
|
23
|
+
export function parseHostProvidersTable(value) {
|
|
24
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
25
|
+
throw new Error("host-providers.json must be an object");
|
|
26
|
+
}
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const [host, byProvider] of Object.entries(value)) {
|
|
29
|
+
if (host.trim() === "") {
|
|
30
|
+
throw new Error("host-providers.json host key must be non-empty");
|
|
31
|
+
}
|
|
32
|
+
if (byProvider === null ||
|
|
33
|
+
typeof byProvider !== "object" ||
|
|
34
|
+
Array.isArray(byProvider)) {
|
|
35
|
+
throw new Error(`host-providers.json[${host}] must be an object`);
|
|
36
|
+
}
|
|
37
|
+
const providers = {};
|
|
38
|
+
for (const [seatProvider, hostProvider] of Object.entries(byProvider)) {
|
|
39
|
+
if (seatProvider.trim() === "") {
|
|
40
|
+
throw new Error(`host-providers.json[${host}] seat-provider key must be non-empty`);
|
|
41
|
+
}
|
|
42
|
+
if (typeof hostProvider !== "string" || hostProvider.trim() === "") {
|
|
43
|
+
throw new Error(`host-providers.json[${host}][${seatProvider}] must be a non-empty string`);
|
|
44
|
+
}
|
|
45
|
+
providers[seatProvider] = hostProvider;
|
|
46
|
+
}
|
|
47
|
+
if (Object.keys(providers).length > 0) {
|
|
48
|
+
out[host] = providers;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Load the owner map. Missing file → empty table (directory / pass-through next).
|
|
55
|
+
* Malformed file fails loud — owner edits this by hand.
|
|
56
|
+
*/
|
|
57
|
+
export function loadHostProvidersTable(home) {
|
|
58
|
+
const path = hostProvidersPath(home);
|
|
59
|
+
try {
|
|
60
|
+
const raw = readFileSync(path, "utf8");
|
|
61
|
+
return parseHostProvidersTable(JSON.parse(raw));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof Error &&
|
|
65
|
+
"code" in error &&
|
|
66
|
+
error.code === "ENOENT") {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** True when the host catalog entry names the seat model (bare or slash-suffixed). */
|
|
73
|
+
export function hostCatalogOffersModel(catalogModels, seatModel) {
|
|
74
|
+
return catalogModels.some((entry) => entry === seatModel || entry.endsWith(`/${seatModel}`));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Providers in the hermes catalog that offer `seatModel`.
|
|
78
|
+
* Reads `~/.hermes/provider_models_cache.json` under the given home.
|
|
79
|
+
* Missing file (ENOENT) → empty list (zero → loud failure upstream).
|
|
80
|
+
* Other read/parse failures keep their identity — never washed as "no model".
|
|
81
|
+
*/
|
|
82
|
+
export function listHermesProvidersForModel(home, seatModel) {
|
|
83
|
+
const path = hermesProviderModelsCachePath(home);
|
|
84
|
+
let text;
|
|
85
|
+
try {
|
|
86
|
+
text = readFileSync(path, "utf8");
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof Error &&
|
|
90
|
+
"code" in error &&
|
|
91
|
+
error.code === "ENOENT") {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
const raw = JSON.parse(text);
|
|
97
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
98
|
+
throw new Error(`hermes provider_models_cache.json must be an object: ${path}`);
|
|
99
|
+
}
|
|
100
|
+
const found = [];
|
|
101
|
+
for (const [provider, entry] of Object.entries(raw)) {
|
|
102
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const models = entry.models;
|
|
106
|
+
if (!Array.isArray(models))
|
|
107
|
+
continue;
|
|
108
|
+
const names = models.filter((m) => typeof m === "string");
|
|
109
|
+
if (hostCatalogOffersModel(names, seatModel)) {
|
|
110
|
+
found.push(provider);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return found.sort();
|
|
114
|
+
}
|
|
115
|
+
/** Hosts this build knows how to query a provider-model directory for. */
|
|
116
|
+
function listDirectoryProvidersForModel(home, host, seatModel) {
|
|
117
|
+
if (host === "hermes") {
|
|
118
|
+
return listHermesProvidersForModel(home, seatModel);
|
|
119
|
+
}
|
|
120
|
+
// No directory for this host → caller falls through to pass-through.
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
export class HostProviderResolutionError extends Error {
|
|
124
|
+
host;
|
|
125
|
+
seatProvider;
|
|
126
|
+
seatModel;
|
|
127
|
+
candidates;
|
|
128
|
+
constructor(options) {
|
|
129
|
+
super(options.message);
|
|
130
|
+
this.name = "HostProviderResolutionError";
|
|
131
|
+
this.host = options.host;
|
|
132
|
+
this.seatProvider = options.seatProvider;
|
|
133
|
+
this.seatModel = options.seatModel;
|
|
134
|
+
this.candidates = options.candidates ?? [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Project seat-table provider to the host-facing name.
|
|
139
|
+
* Priority: owner table > unique host-directory match > loud failure.
|
|
140
|
+
* Hosts without a directory and without a table entry pass the seat provider through.
|
|
141
|
+
*/
|
|
142
|
+
export function projectHostFacingProvider(selection, host, table, home) {
|
|
143
|
+
if (selection === undefined)
|
|
144
|
+
return undefined;
|
|
145
|
+
const mapped = table[host]?.[selection.provider];
|
|
146
|
+
if (mapped !== undefined) {
|
|
147
|
+
return mapped === selection.provider
|
|
148
|
+
? selection
|
|
149
|
+
: { ...selection, provider: mapped };
|
|
150
|
+
}
|
|
151
|
+
const directory = listDirectoryProvidersForModel(home, host, selection.model);
|
|
152
|
+
if (directory === undefined) {
|
|
153
|
+
// No directory for this host → pass-through (pi, grok-build, …).
|
|
154
|
+
return selection;
|
|
155
|
+
}
|
|
156
|
+
if (directory.length === 1) {
|
|
157
|
+
const only = directory[0];
|
|
158
|
+
return only === selection.provider
|
|
159
|
+
? selection
|
|
160
|
+
: { ...selection, provider: only };
|
|
161
|
+
}
|
|
162
|
+
if (directory.length === 0) {
|
|
163
|
+
throw new HostProviderResolutionError({
|
|
164
|
+
message: `host ${host} has no provider offering model ${selection.model}; seat provider was ${selection.provider}`,
|
|
165
|
+
host,
|
|
166
|
+
seatProvider: selection.provider,
|
|
167
|
+
seatModel: selection.model,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
throw new HostProviderResolutionError({
|
|
171
|
+
message: `host ${host} has multiple providers for model ${selection.model}: ${directory.join(", ")}; set host-providers.json[${host}][${selection.provider}] to one of them`,
|
|
172
|
+
host,
|
|
173
|
+
seatProvider: selection.provider,
|
|
174
|
+
seatModel: selection.model,
|
|
175
|
+
candidates: directory,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/** Render the owner table for `config show` (disk face, unchanged). */
|
|
179
|
+
export function renderHostProvidersTable(table) {
|
|
180
|
+
const lines = [];
|
|
181
|
+
for (const host of Object.keys(table).sort()) {
|
|
182
|
+
const byProvider = table[host];
|
|
183
|
+
for (const seatProvider of Object.keys(byProvider).sort()) {
|
|
184
|
+
lines.push(`hostProvider\t${host}\t${seatProvider}\t${byProvider[seatProvider]}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return lines.length === 0 ? "" : `${lines.join("\n")}\n`;
|
|
188
|
+
}
|