@akagilnc/pi-workflow-roles 0.1.3412 → 0.1.3439

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/archivist-record-entry.js +21 -12
  2. package/dist/archivist-record-topology.js +20 -0
  3. package/dist/auditor-dossier-tool.js +67 -76
  4. package/dist/canonical-json.js +66 -69
  5. package/dist/compliance-transport.js +99 -86
  6. package/dist/countersign-contracts.js +23 -25
  7. package/dist/doctor-contracts.js +169 -145
  8. package/dist/engine-detour-tool.js +83 -87
  9. package/dist/engine-detour.js +148 -183
  10. package/dist/evidence-child-executor.js +10 -1
  11. package/dist/exact-utf8.js +11 -10
  12. package/dist/git-object-id.js +10 -5
  13. package/dist/gleaner-left-contracts.js +33 -35
  14. package/dist/grok/production-host.js +229 -118
  15. package/dist/inspector-contracts.js +28 -31
  16. package/dist/institutional-resolution.js +101 -104
  17. package/dist/ledger-session-read.js +241 -0
  18. package/dist/merger-contracts.js +66 -56
  19. package/dist/notary-contracts.js +91 -100
  20. package/dist/open-tool-schema.js +32 -31
  21. package/dist/package-contracts/collector-output.js +56 -43
  22. package/dist/package-contracts/fixer-output.js +52 -50
  23. package/dist/package-contracts/fixer-packet.js +67 -64
  24. package/dist/package-contracts/gatekeeper-output.js +48 -56
  25. package/dist/package-contracts/judge-output.js +23 -20
  26. package/dist/package-contracts/navigator-output.js +35 -40
  27. package/dist/package-contracts/reviewer-output.js +94 -110
  28. package/dist/package-contracts/terminating-infrastructure.js +62 -90
  29. package/dist/package-contracts/worker-output.js +30 -11
  30. package/dist/package-resources/engine-material.js +58 -94
  31. package/dist/public-cli/config.js +420 -484
  32. package/dist/public-cli/main.js +1219 -524
  33. package/dist/public-cli/registry.js +107 -114
  34. package/dist/receipt-delivery-policy.js +74 -76
  35. package/dist/session-opening-materials.js +44 -47
  36. package/dist/sha256.js +5 -3
  37. package/dist/sitian-appender.js +204 -210
  38. package/dist/sitian-contracts.js +15 -19
  39. package/dist/sitian-facade.js +5 -10
  40. package/dist/sitian-reader.js +37 -46
  41. package/dist/stream-idle-guard.js +63 -71
  42. package/dist/ticket-seat-memory.js +244 -0
  43. package/package.json +1 -1
  44. package/scripts/build-package.mjs +41 -0
  45. package/souls/audit-law.md +4 -0
  46. package/souls/countersign.md +1 -1
  47. package/souls/gleaner-left.md +1 -1
  48. package/souls/inspector.md +1 -1
  49. package/souls/notary.md +1 -1
  50. package/souls/reviewer.md +1 -1
  51. package/src/analyst-gate-cycles-read.ts +152 -83
  52. package/src/analyst-ledger.ts +170 -43
  53. package/src/archivist-record-entry.ts +32 -11
  54. package/src/archivist-record-topology.ts +39 -0
  55. package/src/evidence-child-executor.ts +14 -1
  56. package/src/grok/production-host.ts +44 -9
  57. package/src/host-contracts.ts +18 -0
  58. package/src/host-transition-prior-native.ts +8 -1
  59. package/src/ledger-session-read.ts +56 -0
  60. package/src/pi/durable-principal.ts +6 -0
  61. package/src/public-cli/inspector-run.ts +21 -10
  62. package/src/public-cli/invocation.ts +4 -20
  63. package/src/public-cli/notary-run.ts +21 -4
  64. package/src/public-cli/post-admission.ts +333 -42
  65. package/src/public-cli/settlement.ts +266 -81
  66. package/src/session-opening-materials.ts +4 -2
  67. package/src/ticket-seat-memory.ts +454 -0
@@ -1,532 +1,468 @@
1
- /**
2
- * Persistent and effective per-seat model/thinking configuration for ak-role.
3
- */
4
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
5
2
  import { dirname, join } from "node:path";
6
3
  import { assertLegalEngineName } from "../package-resources/engine-material.js";
7
4
  import { resolveConfiguredProvinceOfficer } from "../institutional-resolution.js";
8
- import { PUBLIC_CALLABLE_ROLES, PUBLIC_CONFIGURABLE_SEATS, isPublicCallableRole, isPublicConfigurableSeat, seatModelOnly, publicStartupCandidates, } from "./registry.js";
9
- /** Province officers that may carry a persistent model override (#453). */
10
- export const GATE_OFFICER_SEATS = [
11
- "gatekeeper",
12
- "inspector",
13
- "notary",
5
+ import {
6
+ PUBLIC_CALLABLE_ROLES,
7
+ PUBLIC_CONFIGURABLE_SEATS,
8
+ isPublicCallableRole,
9
+ isPublicConfigurableSeat,
10
+ seatModelOnly,
11
+ publicStartupCandidates
12
+ } from "./registry.js";
13
+ const GATE_OFFICER_SEATS = [
14
+ "gatekeeper",
15
+ "inspector",
16
+ "notary"
14
17
  ];
15
- export function isGateOfficerSeat(value) {
16
- return GATE_OFFICER_SEATS.includes(value);
18
+ function isGateOfficerSeat(value) {
19
+ return GATE_OFFICER_SEATS.includes(value);
17
20
  }
18
- export const THINKING_LEVELS = new Set([
19
- "off",
20
- "minimal",
21
- "low",
22
- "medium",
23
- "high",
24
- "xhigh",
25
- "max",
21
+ const THINKING_LEVELS = /* @__PURE__ */ new Set([
22
+ "off",
23
+ "minimal",
24
+ "low",
25
+ "medium",
26
+ "high",
27
+ "xhigh",
28
+ "max"
26
29
  ]);
27
- export function publicCliConfigPath(home) {
28
- if (typeof home !== "string" || home.trim() === "") {
29
- throw new Error("home must be explicitly provided");
30
- }
31
- return join(home, ".ak-roles", "public-cli.json");
30
+ function publicCliConfigPath(home) {
31
+ if (typeof home !== "string" || home.trim() === "") {
32
+ throw new Error("home must be explicitly provided");
33
+ }
34
+ return join(home, ".ak-roles", "public-cli.json");
32
35
  }
33
- export async function loadPublicCliConfig(home) {
34
- const path = publicCliConfigPath(home);
35
- try {
36
- const raw = await readFile(path, "utf8");
37
- return parsePublicCliConfig(JSON.parse(raw));
38
- }
39
- catch (error) {
40
- if (error instanceof Error &&
41
- "code" in error &&
42
- error.code === "ENOENT") {
43
- return { seats: {} };
44
- }
45
- throw error;
46
- }
36
+ async function loadPublicCliConfig(home) {
37
+ const path = publicCliConfigPath(home);
38
+ try {
39
+ const raw = await readFile(path, "utf8");
40
+ return parsePublicCliConfig(JSON.parse(raw));
41
+ } catch (error) {
42
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
43
+ return { seats: {} };
44
+ }
45
+ throw error;
46
+ }
47
47
  }
48
- export async function savePublicCliConfig(config, home) {
49
- const path = publicCliConfigPath(home);
50
- await mkdir(dirname(path), { recursive: true });
51
- const normalized = parsePublicCliConfig(config);
52
- // Fold the opaque bucket back under seats for the on-disk shape. Do not
53
- // emit unknownSeats as a top-level key — neighboring lines read seats only.
54
- await writeFile(path, `${JSON.stringify(serializePublicCliConfig(normalized), null, 2)}\n`, "utf8");
48
+ async function savePublicCliConfig(config, home) {
49
+ const path = publicCliConfigPath(home);
50
+ await mkdir(dirname(path), { recursive: true });
51
+ const normalized = parsePublicCliConfig(config);
52
+ await writeFile(
53
+ path,
54
+ `${JSON.stringify(serializePublicCliConfig(normalized), null, 2)}
55
+ `,
56
+ "utf8"
57
+ );
55
58
  }
56
- export function setPersistentSeatConfig(config, seat, selection) {
57
- const previous = config.seats[seat];
59
+ function setPersistentSeatConfig(config, seat, selection) {
60
+ const previous = config.seats[seat];
61
+ return {
62
+ // Spread preserves sibling top-level keys such as autoResumeLimit (#422):
63
+ // a seat write must never silently drop them.
64
+ ...config,
65
+ seats: {
66
+ ...config.seats,
67
+ [seat]: {
68
+ ...selection,
69
+ // Model rewrite preserves a previously configured engine axis.
70
+ ...previous?.engine === void 0 ? {} : { engine: previous.engine },
71
+ ...previous?.host === void 0 ? {} : { host: previous.host }
72
+ }
73
+ }
74
+ };
75
+ }
76
+ function clearPersistentSeatConfig(config, seat) {
77
+ const previous = config.seats[seat];
78
+ if (previous === void 0) return config;
79
+ if ((seat === "notary" || seat === "inspector") && (previous.engine !== void 0 || previous.host !== void 0)) {
58
80
  return {
59
- // Spread preserves sibling top-level keys such as autoResumeLimit (#422):
60
- // a seat write must never silently drop them.
61
- ...config,
62
- seats: {
63
- ...config.seats,
64
- [seat]: {
65
- ...selection,
66
- // Model rewrite preserves a previously configured engine axis.
67
- ...(previous?.engine === undefined ? {} : { engine: previous.engine }),
68
- ...(previous?.host === undefined ? {} : { host: previous.host }),
69
- },
70
- },
81
+ ...config,
82
+ seats: {
83
+ ...config.seats,
84
+ [seat]: {
85
+ ...previous.engine === void 0 ? {} : { engine: previous.engine },
86
+ ...previous.host === void 0 ? {} : { host: previous.host }
87
+ }
88
+ }
71
89
  };
90
+ }
91
+ const { [seat]: _dropped, ...seats } = config.seats;
92
+ return { ...config, seats };
72
93
  }
73
- /**
74
- * Clear a gate officer's persistent model override (#453).
75
- * Scope is GateOfficerSeat only — non-province seats have no destructive clear seam.
76
- * Notary and direct Inspector may retain host/engine residual axes while model
77
- * resolution returns to gatekeeper inheritance (#522 two independent
78
- * axes; #568 public Inspector; #620). Gatekeeper drops the whole row.
79
- * Already-absent seats are a no-op.
80
- */
81
- export function clearPersistentSeatConfig(config, seat) {
82
- const previous = config.seats[seat];
83
- if (previous === undefined)
84
- return config;
85
- // Callable province seats keep independent host/engine residuals after model clear.
86
- if ((seat === "notary" || seat === "inspector") &&
87
- (previous.engine !== undefined || previous.host !== undefined)) {
88
- return {
89
- ...config,
90
- seats: {
91
- ...config.seats,
92
- [seat]: {
93
- ...(previous.engine === undefined ? {} : { engine: previous.engine }),
94
- ...(previous.host === undefined ? {} : { host: previous.host }),
95
- },
96
- },
97
- };
98
- }
99
- const { [seat]: _dropped, ...seats } = config.seats;
100
- return { ...config, seats };
94
+ function setPersistentSeatHost(config, seat, host) {
95
+ const previous = config.seats[seat];
96
+ if (previous === void 0) {
97
+ throw new Error(`config seat ${seat} has no persistent model; set provider/model[:thinking] before host`);
98
+ }
99
+ if (host === void 0) {
100
+ const { host: _dropped, ...rest } = previous;
101
+ if (seatModelOnly(rest) === void 0 && rest.engine === void 0) {
102
+ const { [seat]: _row, ...seats } = config.seats;
103
+ return { ...config, seats };
104
+ }
105
+ return { ...config, seats: { ...config.seats, [seat]: rest } };
106
+ }
107
+ return { ...config, seats: { ...config.seats, [seat]: { ...previous, host } } };
101
108
  }
102
- /** Set or clear the persistent main-session host on a callable role seat. */
103
- export function setPersistentSeatHost(config, seat, host) {
104
- const previous = config.seats[seat];
105
- if (previous === undefined) {
106
- throw new Error(`config seat ${seat} has no persistent model; set provider/model[:thinking] before host`);
107
- }
108
- if (host === undefined) {
109
- const { host: _dropped, ...rest } = previous;
110
- if (seatModelOnly(rest) === undefined && rest.engine === undefined) {
111
- const { [seat]: _row, ...seats } = config.seats;
112
- return { ...config, seats };
113
- }
114
- return { ...config, seats: { ...config.seats, [seat]: rest } };
109
+ function setPersistentSeatEngine(config, seat, engine) {
110
+ const previous = config.seats[seat];
111
+ if (previous === void 0) {
112
+ throw new Error(
113
+ `config seat ${seat} has no persistent model; set provider/model[:thinking] before engine`
114
+ );
115
+ }
116
+ if (engine === void 0) {
117
+ const { engine: _dropped, ...modelOnly } = previous;
118
+ if (seatModelOnly(modelOnly) === void 0 && modelOnly.host === void 0) {
119
+ const { [seat]: _row, ...seats } = config.seats;
120
+ return { ...config, seats };
115
121
  }
116
- return { ...config, seats: { ...config.seats, [seat]: { ...previous, host } } };
117
- }
118
- /**
119
- * Set or clear persistent engine on a callable role seat (#356 / #378 / #391 / #453).
120
- * First engine still requires an existing seat row (model, or residual axes).
121
- * Clearing engine from an axis-only residual drops the empty row; clearing
122
- * engine from a model+engine row leaves model-only. Seat type is PublicCallableRole
123
- * (same as PublicConfigurableSeat; navigator included since #639).
124
- */
125
- export function setPersistentSeatEngine(config, seat, engine) {
126
- const previous = config.seats[seat];
127
- if (previous === undefined) {
128
- throw new Error(`config seat ${seat} has no persistent model; set provider/model[:thinking] before engine`);
129
- }
130
- if (engine === undefined) {
131
- const { engine: _dropped, ...modelOnly } = previous;
132
- // Drop the row only after the final independent axis is cleared.
133
- if (seatModelOnly(modelOnly) === undefined && modelOnly.host === undefined) {
134
- const { [seat]: _row, ...seats } = config.seats;
135
- return { ...config, seats };
136
- }
137
- return {
138
- ...config,
139
- seats: {
140
- ...config.seats,
141
- [seat]: modelOnly,
142
- },
143
- };
144
- }
145
- // Engine-name path-safety syntax is owned solely by assertLegalEngineName
146
- // (call-request + config-parse seams). Setter is pure seat mutation.
147
122
  return {
148
- ...config,
149
- seats: {
150
- ...config.seats,
151
- [seat]: { ...previous, engine },
152
- },
123
+ ...config,
124
+ seats: {
125
+ ...config.seats,
126
+ [seat]: modelOnly
127
+ }
153
128
  };
129
+ }
130
+ return {
131
+ ...config,
132
+ seats: {
133
+ ...config.seats,
134
+ [seat]: { ...previous, engine }
135
+ }
136
+ };
154
137
  }
155
- /**
156
- * #422 value domain authority for the auto-resume ceiling: non-negative integer,
157
- * no package-local upper bound (ADR 0035). `0` is legal and means auto-resume is
158
- * disabled (a single dispatch, no in-place retry). Negative numbers, fractions,
159
- * NaN, Infinity and non-number types are rejected loudly — never silently coerced.
160
- */
161
- export function parseAutoResumeLimit(value) {
162
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
163
- throw new Error(`auto-resume limit must be a non-negative integer, got ${JSON.stringify(value)}`);
164
- }
165
- return value;
138
+ function parseAutoResumeLimit(value) {
139
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
140
+ throw new Error(
141
+ `auto-resume limit must be a non-negative integer, got ${JSON.stringify(value)}`
142
+ );
143
+ }
144
+ return value;
166
145
  }
167
- /**
168
- * #422: set the persistent autoResumeLimit top-level key. Pure mutation that
169
- * preserves all sibling keys (seats included).
170
- */
171
- export function setAutoResumeLimit(config, limit) {
172
- return { ...config, autoResumeLimit: parseAutoResumeLimit(limit) };
146
+ function setAutoResumeLimit(config, limit) {
147
+ return { ...config, autoResumeLimit: parseAutoResumeLimit(limit) };
173
148
  }
174
- /**
175
- * Config-parse seam: persistent call axes belong to PUBLIC_CALLABLE_ROLES;
176
- * engine names need only path-safety syntax (no closed material catalog;
177
- * #376 / #378 / #391 / ADR 0069). Syntax authority = assertLegalEngineName
178
- * (no injected duplicate).
179
- */
180
- export function validatePublicCliConfigAxes(config, _packageRoot) {
181
- for (const seat of Object.keys(config.seats)) {
182
- const row = config.seats[seat];
183
- if (row?.engine === undefined)
184
- continue;
185
- try {
186
- assertLegalEngineName(row.engine);
187
- }
188
- catch (error) {
189
- throw new Error(`config seat ${seat} engine is illegal: ${row.engine}`, { cause: error });
190
- }
191
- }
149
+ function validatePublicCliConfigAxes(config, _packageRoot) {
150
+ for (const seat of Object.keys(config.seats)) {
151
+ const row = config.seats[seat];
152
+ if (row?.engine === void 0) continue;
153
+ try {
154
+ assertLegalEngineName(row.engine);
155
+ } catch (error) {
156
+ throw new Error(
157
+ `config seat ${seat} engine is illegal: ${row.engine}`,
158
+ { cause: error }
159
+ );
160
+ }
161
+ }
192
162
  }
193
- export function parseModelSpec(spec, fallbackThinking) {
194
- const trimmed = spec.trim();
195
- if (!trimmed) {
196
- throw new Error("model specification must be non-empty");
197
- }
198
- const thinkingSplit = trimmed.lastIndexOf(":");
199
- let modelPart = trimmed;
200
- let thinking = fallbackThinking;
201
- // #346: no colon → bare provider/model is legal. Colon present (any index,
202
- // including 0) → suffix must be a typed PublicThinkingLevel (empty/unknown
203
- // stay format rejects; never swallow ":…" into the model name).
204
- if (thinkingSplit !== -1) {
205
- const maybeThinking = trimmed.slice(thinkingSplit + 1);
206
- if (!THINKING_LEVELS.has(maybeThinking)) {
207
- throw new Error(`model specification must be provider/model[:thinking], got ${spec}`);
208
- }
209
- thinking = maybeThinking;
210
- modelPart = trimmed.slice(0, thinkingSplit);
211
- }
212
- const slash = modelPart.indexOf("/");
213
- if (slash <= 0 || slash === modelPart.length - 1) {
214
- throw new Error(`model specification must be provider/model[:thinking], got ${spec}`);
215
- }
216
- const provider = modelPart.slice(0, slash);
217
- const model = modelPart.slice(slash + 1);
218
- // #346/#384: bare provider/model is legal — do not invent thinking.
219
- return thinking === undefined
220
- ? { provider, model }
221
- : { provider, model, thinking };
163
+ function parseModelSpec(spec, fallbackThinking) {
164
+ const trimmed = spec.trim();
165
+ if (!trimmed) {
166
+ throw new Error("model specification must be non-empty");
167
+ }
168
+ const thinkingSplit = trimmed.lastIndexOf(":");
169
+ let modelPart = trimmed;
170
+ let thinking = fallbackThinking;
171
+ if (thinkingSplit !== -1) {
172
+ const maybeThinking = trimmed.slice(thinkingSplit + 1);
173
+ if (!THINKING_LEVELS.has(maybeThinking)) {
174
+ throw new Error(
175
+ `model specification must be provider/model[:thinking], got ${spec}`
176
+ );
177
+ }
178
+ thinking = maybeThinking;
179
+ modelPart = trimmed.slice(0, thinkingSplit);
180
+ }
181
+ const slash = modelPart.indexOf("/");
182
+ if (slash <= 0 || slash === modelPart.length - 1) {
183
+ throw new Error(
184
+ `model specification must be provider/model[:thinking], got ${spec}`
185
+ );
186
+ }
187
+ const provider = modelPart.slice(0, slash);
188
+ const model = modelPart.slice(slash + 1);
189
+ return thinking === void 0 ? { provider, model } : { provider, model, thinking };
222
190
  }
223
- export function formatModelSpec(selection) {
224
- const base = `${selection.provider}/${selection.model}`;
225
- return selection.thinking === undefined ? base : `${base}:${selection.thinking}`;
191
+ function formatModelSpec(selection) {
192
+ const base = `${selection.provider}/${selection.model}`;
193
+ return selection.thinking === void 0 ? base : `${base}:${selection.thinking}`;
226
194
  }
227
- /**
228
- * Single source: seat model selection → Pi argv at the public CLI execution seam.
229
- * Bare provider/model omits --thinking so pi/model defaults apply; suffix passes through.
230
- */
231
- export function buildSeatModelCliArgs(model) {
232
- if (model === undefined)
233
- return [];
234
- return [
235
- "--provider",
236
- model.provider,
237
- "--model",
238
- model.model,
239
- ...(model.thinking === undefined ? [] : ["--thinking", model.thinking]),
240
- ];
195
+ function buildSeatModelCliArgs(model) {
196
+ if (model === void 0) return [];
197
+ return [
198
+ "--provider",
199
+ model.provider,
200
+ "--model",
201
+ model.model,
202
+ ...model.thinking === void 0 ? [] : ["--thinking", model.thinking]
203
+ ];
241
204
  }
242
- /**
243
- * Disk document shape for public-cli.json. Unknown seat rows live under
244
- * `seats` (not a parallel top-level key) so every build — old or new — sees
245
- * one seats map.
246
- */
247
205
  function serializePublicCliConfig(config) {
248
- return {
249
- // Known seats win on any key clash; by construction the two maps are
250
- // disjoint after parse, but prefer owned rows if a caller stuffed both.
251
- seats: {
252
- ...(config.unknownSeats ?? {}),
253
- ...config.seats,
254
- },
255
- ...(config.autoResumeLimit === undefined
256
- ? {}
257
- : { autoResumeLimit: config.autoResumeLimit }),
258
- };
206
+ return {
207
+ // Known seats win on any key clash; by construction the two maps are
208
+ // disjoint after parse, but prefer owned rows if a caller stuffed both.
209
+ seats: {
210
+ ...config.unknownSeats ?? {},
211
+ ...config.seats
212
+ },
213
+ ...config.autoResumeLimit === void 0 ? {} : { autoResumeLimit: config.autoResumeLimit }
214
+ };
259
215
  }
260
216
  function parsePublicCliConfig(value) {
261
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
262
- throw new Error("public CLI config must be an object");
263
- }
264
- const record = value;
265
- // #422 round-trip preservation: the sibling top-level key must survive every
266
- // parse→save cycle; an unknown-key drop would silently erase it on any write.
267
- let autoResumeLimit;
268
- if (record.autoResumeLimit !== undefined) {
269
- autoResumeLimit = parseAutoResumeLimit(record.autoResumeLimit);
270
- }
271
- // #592: opaque bucket may already be present on an in-memory round-trip
272
- // (load→set→save calls parse again). Carry it before seats iteration so a
273
- // disk-shaped seats map and a memory-shaped bucket both survive.
274
- const unknownSeats = {};
275
- if (record.unknownSeats !== undefined) {
276
- if (record.unknownSeats === null ||
277
- typeof record.unknownSeats !== "object" ||
278
- Array.isArray(record.unknownSeats)) {
279
- throw new Error("public CLI config.unknownSeats must be an object");
280
- }
281
- Object.assign(unknownSeats, record.unknownSeats);
282
- }
283
- const withOpaque = (seats) => ({
284
- seats,
285
- ...(autoResumeLimit === undefined ? {} : { autoResumeLimit }),
286
- ...(Object.keys(unknownSeats).length === 0 ? {} : { unknownSeats }),
287
- });
288
- if (record.seats === undefined) {
289
- return withOpaque({});
290
- }
291
- if (record.seats === null ||
292
- typeof record.seats !== "object" ||
293
- Array.isArray(record.seats)) {
294
- throw new Error("public CLI config.seats must be an object");
295
- }
296
- const seats = {};
297
- for (const [key, raw] of Object.entries(record.seats)) {
298
- // #592: shared machine-wide public-cli.json may hold seat rows a newer CLI
299
- // wrote. Do not consume them (resolve/enum stay owned-seat only), but keep
300
- // the raw value in the opaque bucket so save can put them back under seats.
301
- // Unknown field-level keys on known seats keep their existing tolerance.
302
- if (!isPublicConfigurableSeat(key)) {
303
- unknownSeats[key] = raw;
304
- continue;
305
- }
306
- seats[key] = parseSeatModelConfig(raw, key);
307
- }
308
- return withOpaque(seats);
217
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
218
+ throw new Error("public CLI config must be an object");
219
+ }
220
+ const record = value;
221
+ let autoResumeLimit;
222
+ if (record.autoResumeLimit !== void 0) {
223
+ autoResumeLimit = parseAutoResumeLimit(record.autoResumeLimit);
224
+ }
225
+ const unknownSeats = {};
226
+ if (record.unknownSeats !== void 0) {
227
+ if (record.unknownSeats === null || typeof record.unknownSeats !== "object" || Array.isArray(record.unknownSeats)) {
228
+ throw new Error("public CLI config.unknownSeats must be an object");
229
+ }
230
+ Object.assign(unknownSeats, record.unknownSeats);
231
+ }
232
+ const withOpaque = (seats2) => ({
233
+ seats: seats2,
234
+ ...autoResumeLimit === void 0 ? {} : { autoResumeLimit },
235
+ ...Object.keys(unknownSeats).length === 0 ? {} : { unknownSeats }
236
+ });
237
+ if (record.seats === void 0) {
238
+ return withOpaque({});
239
+ }
240
+ if (record.seats === null || typeof record.seats !== "object" || Array.isArray(record.seats)) {
241
+ throw new Error("public CLI config.seats must be an object");
242
+ }
243
+ const seats = {};
244
+ for (const [key, raw] of Object.entries(
245
+ record.seats
246
+ )) {
247
+ if (!isPublicConfigurableSeat(key)) {
248
+ unknownSeats[key] = raw;
249
+ continue;
250
+ }
251
+ seats[key] = parseSeatModelConfig(raw, key);
252
+ }
253
+ return withOpaque(seats);
309
254
  }
310
255
  function parseSeatModelConfig(value, seat) {
311
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
312
- throw new Error(`config seat ${seat} must be an object`);
313
- }
314
- const raw = value;
315
- const hasProvider = raw.provider !== undefined;
316
- const hasModel = raw.model !== undefined;
317
- if (hasProvider !== hasModel) {
318
- throw new Error(`config seat ${seat} requires both provider and model`);
319
- }
320
- if (raw.host !== undefined && (typeof raw.host !== "string" || raw.host.trim() === "")) {
321
- throw new Error(`config seat ${seat} host must be a non-empty string`);
322
- }
323
- if (raw.engine !== undefined && typeof raw.engine !== "string") {
324
- // Shape only: engine must be a string field. Path-safety syntax is deferred to
325
- // validatePublicCliConfigAxes → assertLegalEngineName (single authority).
326
- throw new Error(`config seat ${seat} engine must be a string`);
327
- }
328
- // #453/#522/#568: notary/inspector host/engine residuals remain legal after
329
- // model clear. All other seats keep provider/model.
330
- if (!hasProvider) {
331
- if ((seat === "notary" || seat === "inspector") &&
332
- (typeof raw.engine === "string" || typeof raw.host === "string")) {
333
- if (raw.thinking !== undefined) {
334
- throw new Error(`config seat ${seat} thinking requires provider/model`);
335
- }
336
- return {
337
- ...(raw.engine === undefined ? {} : { engine: raw.engine }),
338
- ...(raw.host === undefined ? {} : { host: raw.host }),
339
- };
340
- }
341
- throw new Error(`config seat ${seat} requires provider`);
342
- }
343
- if (typeof raw.provider !== "string" || raw.provider.trim() === "") {
344
- throw new Error(`config seat ${seat} requires provider`);
345
- }
346
- if (typeof raw.model !== "string" || raw.model.trim() === "") {
347
- throw new Error(`config seat ${seat} requires model`);
348
- }
349
- // #384: thinking is optional on persistent seats; when present it must be typed.
350
- if (raw.thinking !== undefined) {
351
- if (typeof raw.thinking !== "string" ||
352
- !THINKING_LEVELS.has(raw.thinking)) {
353
- throw new Error(`config seat ${seat} requires a valid thinking level`);
354
- }
355
- }
356
- const parsed = {
357
- provider: raw.provider,
358
- model: raw.model,
359
- ...(raw.thinking === undefined
360
- ? {}
361
- : { thinking: raw.thinking }),
362
- ...(raw.engine === undefined ? {} : { engine: raw.engine }),
363
- ...(raw.host === undefined ? {} : { host: raw.host }),
364
- };
365
- return parsed;
256
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
257
+ throw new Error(`config seat ${seat} must be an object`);
258
+ }
259
+ const raw = value;
260
+ const hasProvider = raw.provider !== void 0;
261
+ const hasModel = raw.model !== void 0;
262
+ if (hasProvider !== hasModel) {
263
+ throw new Error(`config seat ${seat} requires both provider and model`);
264
+ }
265
+ if (raw.host !== void 0 && (typeof raw.host !== "string" || raw.host.trim() === "")) {
266
+ throw new Error(`config seat ${seat} host must be a non-empty string`);
267
+ }
268
+ if (raw.engine !== void 0 && typeof raw.engine !== "string") {
269
+ throw new Error(`config seat ${seat} engine must be a string`);
270
+ }
271
+ if (!hasProvider) {
272
+ if ((seat === "notary" || seat === "inspector") && (typeof raw.engine === "string" || typeof raw.host === "string")) {
273
+ if (raw.thinking !== void 0) {
274
+ throw new Error(`config seat ${seat} thinking requires provider/model`);
275
+ }
276
+ return {
277
+ ...raw.engine === void 0 ? {} : { engine: raw.engine },
278
+ ...raw.host === void 0 ? {} : { host: raw.host }
279
+ };
280
+ }
281
+ throw new Error(`config seat ${seat} requires provider`);
282
+ }
283
+ if (typeof raw.provider !== "string" || raw.provider.trim() === "") {
284
+ throw new Error(`config seat ${seat} requires provider`);
285
+ }
286
+ if (typeof raw.model !== "string" || raw.model.trim() === "") {
287
+ throw new Error(`config seat ${seat} requires model`);
288
+ }
289
+ if (raw.thinking !== void 0) {
290
+ if (typeof raw.thinking !== "string" || !THINKING_LEVELS.has(raw.thinking)) {
291
+ throw new Error(`config seat ${seat} requires a valid thinking level`);
292
+ }
293
+ }
294
+ const parsed = {
295
+ provider: raw.provider,
296
+ model: raw.model,
297
+ ...raw.thinking === void 0 ? {} : { thinking: raw.thinking },
298
+ ...raw.engine === void 0 ? {} : { engine: raw.engine },
299
+ ...raw.host === void 0 ? {} : { host: raw.host }
300
+ };
301
+ return parsed;
366
302
  }
367
- export function providerConfigured(credentials, provider) {
368
- if (provider === "openai-codex")
369
- return credentials["openai-codex"] === true;
370
- if (provider === "xai")
371
- return credentials.xai === true;
372
- return false;
303
+ function providerConfigured(credentials, provider) {
304
+ if (provider === "openai-codex") return credentials["openai-codex"] === true;
305
+ if (provider === "xai") return credentials.xai === true;
306
+ return false;
373
307
  }
374
- /**
375
- * Typed provider-credential absence for the public seat providers ak-role owns.
376
- * Presence is read from auth.json shape (CredentialProviders), never from stderr prose.
377
- * Returns undefined for unknown/custom providers (not this package's credential map).
378
- */
379
- export function missingPublicProviderCredential(provider, credentials) {
380
- if (provider !== "openai-codex" && provider !== "xai")
381
- return false;
382
- return !providerConfigured(credentials, provider);
308
+ function missingPublicProviderCredential(provider, credentials) {
309
+ if (provider !== "openai-codex" && provider !== "xai") return false;
310
+ return !providerConfigured(credentials, provider);
383
311
  }
384
312
  function pickStartupCandidate(seat, credentials) {
385
- for (const candidate of publicStartupCandidates(seat)) {
386
- if (providerConfigured(credentials, candidate.provider)) {
387
- return { ...candidate };
388
- }
313
+ for (const candidate of publicStartupCandidates(seat)) {
314
+ if (providerConfigured(credentials, candidate.provider)) {
315
+ return { ...candidate };
389
316
  }
390
- return undefined;
317
+ }
318
+ return void 0;
391
319
  }
392
320
  function attachHostAxis(seat, config, invocation) {
393
- if (invocation?.host !== undefined)
394
- return { ...seat, host: invocation.host, hostSource: "invocation" };
395
- const persistent = config.seats[seat.seat]?.host;
396
- if (persistent !== undefined)
397
- return { ...seat, host: persistent, hostSource: "persistent" };
398
- return { ...seat, host: "pi", hostSource: "default" };
321
+ if (invocation?.host !== void 0) return { ...seat, host: invocation.host, hostSource: "invocation" };
322
+ const persistent = config.seats[seat.seat]?.host;
323
+ if (persistent !== void 0) return { ...seat, host: persistent, hostSource: "persistent" };
324
+ return { ...seat, host: "pi", hostSource: "default" };
399
325
  }
400
326
  function attachEngineAxis(seat, config, invocation) {
401
- // #391: engine axis is PUBLIC_CALLABLE_ROLES only (single callable-seat predicate).
402
- if (!isPublicCallableRole(seat.seat)) {
403
- return {
404
- ...seat,
405
- engineSource: "unconfigured",
406
- };
407
- }
408
- const persistentEngine = config.seats[seat.seat]?.engine;
409
- if (invocation?.engine !== undefined) {
410
- return {
411
- ...seat,
412
- engine: invocation.engine,
413
- engineSource: "invocation",
414
- };
415
- }
416
- if (persistentEngine !== undefined) {
417
- return {
418
- ...seat,
419
- engine: persistentEngine,
420
- engineSource: "persistent",
421
- };
422
- }
327
+ if (!isPublicCallableRole(seat.seat)) {
328
+ return {
329
+ ...seat,
330
+ engineSource: "unconfigured"
331
+ };
332
+ }
333
+ const persistentEngine = config.seats[seat.seat]?.engine;
334
+ if (invocation?.engine !== void 0) {
335
+ return {
336
+ ...seat,
337
+ engine: invocation.engine,
338
+ engineSource: "invocation"
339
+ };
340
+ }
341
+ if (persistentEngine !== void 0) {
423
342
  return {
424
- ...seat,
425
- engineSource: "unconfigured",
343
+ ...seat,
344
+ engine: persistentEngine,
345
+ engineSource: "persistent"
426
346
  };
347
+ }
348
+ return {
349
+ ...seat,
350
+ engineSource: "unconfigured"
351
+ };
427
352
  }
428
353
  function resolveBaseSeat(config, seat, credentials) {
429
- // #620: subordinate officers consume institutional-resolution authority result.
430
- if (seat === "notary" || seat === "inspector") {
431
- const resolved = resolveConfiguredProvinceOfficer(config, seat);
432
- return {
433
- seat,
434
- source: resolved.source,
435
- ...(resolved.selection === undefined ? {} : { selection: resolved.selection }),
436
- engineSource: "unconfigured",
437
- };
438
- }
439
- // Engine-only residual is not a persistent model source (#453).
440
- const persistentModel = seatModelOnly(config.seats[seat]);
441
- if (persistentModel !== undefined) {
442
- return {
443
- seat,
444
- source: "persistent",
445
- selection: persistentModel,
446
- engineSource: "unconfigured",
447
- };
448
- }
449
- const startup = pickStartupCandidate(seat, credentials);
450
- if (startup !== undefined) {
451
- return {
452
- seat,
453
- source: "startup",
454
- selection: startup,
455
- engineSource: "unconfigured",
456
- };
457
- }
458
- return { seat, source: "unconfigured", engineSource: "unconfigured" };
354
+ if (seat === "notary" || seat === "inspector") {
355
+ const resolved = resolveConfiguredProvinceOfficer(config, seat);
356
+ return {
357
+ seat,
358
+ source: resolved.source,
359
+ ...resolved.selection === void 0 ? {} : { selection: resolved.selection },
360
+ engineSource: "unconfigured"
361
+ };
362
+ }
363
+ const persistentModel = seatModelOnly(config.seats[seat]);
364
+ if (persistentModel !== void 0) {
365
+ return {
366
+ seat,
367
+ source: "persistent",
368
+ selection: persistentModel,
369
+ engineSource: "unconfigured"
370
+ };
371
+ }
372
+ const startup = pickStartupCandidate(seat, credentials);
373
+ if (startup !== void 0) {
374
+ return {
375
+ seat,
376
+ source: "startup",
377
+ selection: startup,
378
+ engineSource: "unconfigured"
379
+ };
380
+ }
381
+ return { seat, source: "unconfigured", engineSource: "unconfigured" };
459
382
  }
460
- export function resolveEffectiveSeat(config, seat, credentials, invocation) {
461
- const hasModelInvocation = invocation !== undefined &&
462
- (invocation.model !== undefined || invocation.thinking !== undefined);
463
- let modelSeat;
464
- if (!hasModelInvocation || invocation === undefined) {
465
- modelSeat = resolveBaseSeat(config, seat, credentials);
466
- }
467
- else if (invocation.model !== undefined) {
468
- const spec = invocation.model.includes(":") || invocation.thinking === undefined
469
- ? invocation.model
470
- : `${invocation.model}:${invocation.thinking}`;
471
- modelSeat = {
472
- seat,
473
- source: "invocation",
474
- selection: parseModelSpec(spec),
475
- engineSource: "unconfigured",
476
- };
477
- }
478
- else {
479
- const base = resolveBaseSeat(config, seat, credentials);
480
- if (base.selection === undefined || invocation.thinking === undefined) {
481
- modelSeat = {
482
- seat,
483
- source: "unconfigured",
484
- engineSource: "unconfigured",
485
- };
486
- }
487
- else {
488
- modelSeat = {
489
- seat,
490
- source: "invocation",
491
- selection: { ...base.selection, thinking: invocation.thinking },
492
- engineSource: "unconfigured",
493
- };
494
- }
495
- }
496
- return attachHostAxis(attachEngineAxis(modelSeat, config, invocation), config, invocation);
383
+ function resolveEffectiveSeat(config, seat, credentials, invocation) {
384
+ const hasModelInvocation = invocation !== void 0 && (invocation.model !== void 0 || invocation.thinking !== void 0);
385
+ let modelSeat;
386
+ if (!hasModelInvocation || invocation === void 0) {
387
+ modelSeat = resolveBaseSeat(config, seat, credentials);
388
+ } else if (invocation.model !== void 0) {
389
+ const spec = invocation.model.includes(":") || invocation.thinking === void 0 ? invocation.model : `${invocation.model}:${invocation.thinking}`;
390
+ modelSeat = {
391
+ seat,
392
+ source: "invocation",
393
+ selection: parseModelSpec(spec),
394
+ engineSource: "unconfigured"
395
+ };
396
+ } else {
397
+ const base = resolveBaseSeat(config, seat, credentials);
398
+ if (base.selection === void 0 || invocation.thinking === void 0) {
399
+ modelSeat = {
400
+ seat,
401
+ source: "unconfigured",
402
+ engineSource: "unconfigured"
403
+ };
404
+ } else {
405
+ modelSeat = {
406
+ seat,
407
+ source: "invocation",
408
+ selection: { ...base.selection, thinking: invocation.thinking },
409
+ engineSource: "unconfigured"
410
+ };
411
+ }
412
+ }
413
+ return attachHostAxis(attachEngineAxis(modelSeat, config, invocation), config, invocation);
497
414
  }
498
- export function effectiveSeatConfigurations(config, credentials, invocation) {
499
- return PUBLIC_CONFIGURABLE_SEATS.map((seat) => resolveEffectiveSeat(config, seat, credentials, invocation));
415
+ function effectiveSeatConfigurations(config, credentials, invocation) {
416
+ return PUBLIC_CONFIGURABLE_SEATS.map(
417
+ (seat) => resolveEffectiveSeat(config, seat, credentials, invocation)
418
+ );
500
419
  }
501
- /** Callable roles in package order. */
502
- export function listRolesForDisplay(config, credentials, invocation) {
503
- return effectiveSeatConfigurations(config, credentials, invocation);
420
+ function listRolesForDisplay(config, credentials, invocation) {
421
+ return effectiveSeatConfigurations(config, credentials, invocation);
504
422
  }
505
- /**
506
- * Read configured credential presence from a Pi auth.json document.
507
- * Only presence of a provider key matters for startup selection (#11).
508
- */
509
- export function credentialProvidersFromAuthData(data) {
510
- if (data === null || typeof data !== "object" || Array.isArray(data)) {
511
- return { "openai-codex": false, xai: false };
512
- }
513
- const record = data;
514
- return {
515
- "openai-codex": Object.prototype.hasOwnProperty.call(record, "openai-codex"),
516
- xai: Object.prototype.hasOwnProperty.call(record, "xai"),
517
- };
423
+ function credentialProvidersFromAuthData(data) {
424
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
425
+ return { "openai-codex": false, xai: false };
426
+ }
427
+ const record = data;
428
+ return {
429
+ "openai-codex": Object.prototype.hasOwnProperty.call(record, "openai-codex"),
430
+ xai: Object.prototype.hasOwnProperty.call(record, "xai")
431
+ };
518
432
  }
519
- export async function loadCredentialProviders(agentDir) {
520
- try {
521
- const raw = await readFile(join(agentDir, "auth.json"), "utf8");
522
- return credentialProvidersFromAuthData(JSON.parse(raw));
523
- }
524
- catch (error) {
525
- if (error instanceof Error &&
526
- "code" in error &&
527
- error.code === "ENOENT") {
528
- return { "openai-codex": false, xai: false };
529
- }
530
- throw error;
531
- }
433
+ async function loadCredentialProviders(agentDir) {
434
+ try {
435
+ const raw = await readFile(join(agentDir, "auth.json"), "utf8");
436
+ return credentialProvidersFromAuthData(JSON.parse(raw));
437
+ } catch (error) {
438
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
439
+ return { "openai-codex": false, xai: false };
440
+ }
441
+ throw error;
442
+ }
532
443
  }
444
+ export {
445
+ GATE_OFFICER_SEATS,
446
+ THINKING_LEVELS,
447
+ buildSeatModelCliArgs,
448
+ clearPersistentSeatConfig,
449
+ credentialProvidersFromAuthData,
450
+ effectiveSeatConfigurations,
451
+ formatModelSpec,
452
+ isGateOfficerSeat,
453
+ listRolesForDisplay,
454
+ loadCredentialProviders,
455
+ loadPublicCliConfig,
456
+ missingPublicProviderCredential,
457
+ parseAutoResumeLimit,
458
+ parseModelSpec,
459
+ providerConfigured,
460
+ publicCliConfigPath,
461
+ resolveEffectiveSeat,
462
+ savePublicCliConfig,
463
+ setAutoResumeLimit,
464
+ setPersistentSeatConfig,
465
+ setPersistentSeatEngine,
466
+ setPersistentSeatHost,
467
+ validatePublicCliConfigAxes
468
+ };