@akagilnc/pi-workflow-roles 0.1.2312 → 0.1.2322
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/evidence-child-executor.js +33 -3
- package/dist/public-cli/config.js +491 -0
- package/dist/public-cli/main.js +103 -21
- package/dist/public-cli/registry.js +121 -0
- package/package.json +1 -1
- package/src/evidence-child-executor.ts +58 -3
- package/src/gatekeeper-role.ts +5 -1
- package/src/public-cli/cli.ts +43 -7
- package/src/public-cli/config.ts +122 -27
- package/src/public-cli/option-definitions.ts +2 -0
- package/src/public-cli/registry.ts +25 -2
package/src/public-cli/config.ts
CHANGED
|
@@ -7,9 +7,10 @@ import { dirname, join } from "node:path";
|
|
|
7
7
|
|
|
8
8
|
import { assertLegalEngineName } from "../package-resources/engine-material.ts";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
AUTOMATIC_CONFIGURABLE_SEATS,
|
|
11
11
|
PUBLIC_CALLABLE_ROLES,
|
|
12
12
|
PUBLIC_CONFIGURABLE_SEATS,
|
|
13
|
+
isAutomaticConfigurableSeat,
|
|
13
14
|
isPublicCallableRole,
|
|
14
15
|
isPublicConfigurableSeat,
|
|
15
16
|
type ModelRef,
|
|
@@ -19,6 +20,19 @@ import {
|
|
|
19
20
|
publicStartupCandidates,
|
|
20
21
|
} from "./registry.ts";
|
|
21
22
|
|
|
23
|
+
/** Province officers that may carry a persistent model override (#453). */
|
|
24
|
+
export const MENXIA_OFFICER_SEATS = [
|
|
25
|
+
"gatekeeper",
|
|
26
|
+
"inspector",
|
|
27
|
+
"notary",
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
export type MenxiaOfficerSeat = (typeof MENXIA_OFFICER_SEATS)[number];
|
|
31
|
+
|
|
32
|
+
export function isMenxiaOfficerSeat(value: string): value is MenxiaOfficerSeat {
|
|
33
|
+
return (MENXIA_OFFICER_SEATS as readonly string[]).includes(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
export type CredentialProviders = {
|
|
23
37
|
"openai-codex": boolean;
|
|
24
38
|
xai: boolean;
|
|
@@ -26,8 +40,16 @@ export type CredentialProviders = {
|
|
|
26
40
|
|
|
27
41
|
export type SeatModelConfig = ModelRef;
|
|
28
42
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Persistent seat row (#356/#384/#453): model fields and engine are independent
|
|
45
|
+
* axes. Engine-only residual is legal only for notary after model clear so direct
|
|
46
|
+
* notary activation keeps its labor engine while province inheritance resumes.
|
|
47
|
+
* All other seats keep the baseline provider/model required contract.
|
|
48
|
+
*/
|
|
49
|
+
export type PersistentSeatConfig = {
|
|
50
|
+
provider?: string;
|
|
51
|
+
model?: string;
|
|
52
|
+
thinking?: PublicThinkingLevel;
|
|
31
53
|
engine?: string;
|
|
32
54
|
};
|
|
33
55
|
|
|
@@ -49,7 +71,7 @@ export type EngineSource = "invocation" | "persistent" | "unconfigured";
|
|
|
49
71
|
|
|
50
72
|
export type EffectiveSeat = {
|
|
51
73
|
seat: PublicConfigurableSeat;
|
|
52
|
-
/** True when the seat is automatic
|
|
74
|
+
/** True when the seat is automatic (no caller command) rather than caller-selected. */
|
|
53
75
|
automatic: boolean;
|
|
54
76
|
source: EffectiveSource;
|
|
55
77
|
selection?: SeatModelConfig;
|
|
@@ -129,6 +151,49 @@ export function setPersistentSeatConfig(
|
|
|
129
151
|
};
|
|
130
152
|
}
|
|
131
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Clear a menxia officer's persistent model override (#453).
|
|
156
|
+
* Scope is MenxiaOfficerSeat only — non-province seats have no destructive clear seam.
|
|
157
|
+
* Only notary may retain an engine-only residual so direct notary activation keeps
|
|
158
|
+
* its labor engine while model resolution returns to startup / province inheritance.
|
|
159
|
+
* gatekeeper/inspector drop the whole row. Already-absent seats are a no-op.
|
|
160
|
+
*/
|
|
161
|
+
export function clearPersistentSeatConfig(
|
|
162
|
+
config: PublicCliConfig,
|
|
163
|
+
seat: MenxiaOfficerSeat,
|
|
164
|
+
): PublicCliConfig {
|
|
165
|
+
const previous = config.seats[seat];
|
|
166
|
+
if (previous === undefined) return config;
|
|
167
|
+
// Engine-only residual ownership is notary-only — never widen to other officers.
|
|
168
|
+
if (seat === "notary" && previous.engine !== undefined) {
|
|
169
|
+
return {
|
|
170
|
+
...config,
|
|
171
|
+
seats: {
|
|
172
|
+
...config.seats,
|
|
173
|
+
notary: { engine: previous.engine },
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const { [seat]: _dropped, ...seats } = config.seats;
|
|
178
|
+
return { ...config, seats };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Province-only model selection (#453): officer persistent > gatekeeper persistent
|
|
183
|
+
* > unset (caller inherits parent session). Never consults startup candidates —
|
|
184
|
+
* direct `ak-role notary` keeps resolveEffectiveSeat; only province reads this.
|
|
185
|
+
* Engine-only residual is not a model override.
|
|
186
|
+
*/
|
|
187
|
+
export function resolveMenxiaOfficerModelSelection(
|
|
188
|
+
config: PublicCliConfig,
|
|
189
|
+
officer: MenxiaOfficerSeat,
|
|
190
|
+
): SeatModelConfig | undefined {
|
|
191
|
+
const ownModel = seatModelOnly(config.seats[officer]);
|
|
192
|
+
if (ownModel !== undefined) return ownModel;
|
|
193
|
+
if (officer === "gatekeeper") return undefined;
|
|
194
|
+
return seatModelOnly(config.seats.gatekeeper);
|
|
195
|
+
}
|
|
196
|
+
|
|
132
197
|
/**
|
|
133
198
|
* Seats that own the labor-engine axis (#391: PUBLIC_CALLABLE_ROLES only).
|
|
134
199
|
* Navigator is configurable for model but has no independent activation path.
|
|
@@ -138,9 +203,11 @@ export function isEngineAxisSeat(seat: string): seat is PublicCallableRole {
|
|
|
138
203
|
}
|
|
139
204
|
|
|
140
205
|
/**
|
|
141
|
-
* Set or clear persistent engine on a callable role seat (#356 / #378 / #391).
|
|
142
|
-
*
|
|
143
|
-
*
|
|
206
|
+
* Set or clear persistent engine on a callable role seat (#356 / #378 / #391 / #453).
|
|
207
|
+
* First engine still requires an existing seat row (model, or notary engine residual).
|
|
208
|
+
* Clearing engine from a notary engine-only residual drops the empty row; clearing
|
|
209
|
+
* engine from a model+engine row leaves model-only. Seat type is PublicCallableRole
|
|
210
|
+
* (navigator excluded at the type boundary).
|
|
144
211
|
*/
|
|
145
212
|
export function setPersistentSeatEngine(
|
|
146
213
|
config: PublicCliConfig,
|
|
@@ -155,6 +222,11 @@ export function setPersistentSeatEngine(
|
|
|
155
222
|
}
|
|
156
223
|
if (engine === undefined) {
|
|
157
224
|
const { engine: _dropped, ...modelOnly } = previous;
|
|
225
|
+
// Engine-only residual with engine cleared → drop the empty row.
|
|
226
|
+
if (seatModelOnly(modelOnly) === undefined) {
|
|
227
|
+
const { [seat]: _row, ...seats } = config.seats;
|
|
228
|
+
return { ...config, seats };
|
|
229
|
+
}
|
|
158
230
|
return {
|
|
159
231
|
...config,
|
|
160
232
|
seats: {
|
|
@@ -200,8 +272,14 @@ export function setAutoResumeLimit(
|
|
|
200
272
|
return { ...config, autoResumeLimit: parseAutoResumeLimit(limit) };
|
|
201
273
|
}
|
|
202
274
|
|
|
203
|
-
/**
|
|
204
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Strip optional engine so activation model argv never sees the engine axis.
|
|
277
|
+
* Engine-only residual (model cleared) yields undefined — not a model override.
|
|
278
|
+
*/
|
|
279
|
+
export function seatModelOnly(
|
|
280
|
+
seat: PersistentSeatConfig | undefined,
|
|
281
|
+
): SeatModelConfig | undefined {
|
|
282
|
+
if (seat?.provider === undefined || seat.model === undefined) return undefined;
|
|
205
283
|
return seat.thinking === undefined
|
|
206
284
|
? { provider: seat.provider, model: seat.model }
|
|
207
285
|
: { provider: seat.provider, model: seat.model, thinking: seat.thinking };
|
|
@@ -339,6 +417,27 @@ function parseSeatModelConfig(value: unknown, seat: string): PersistentSeatConfi
|
|
|
339
417
|
throw new Error(`config seat ${seat} must be an object`);
|
|
340
418
|
}
|
|
341
419
|
const raw = value as Record<string, unknown>;
|
|
420
|
+
const hasProvider = raw.provider !== undefined;
|
|
421
|
+
const hasModel = raw.model !== undefined;
|
|
422
|
+
if (hasProvider !== hasModel) {
|
|
423
|
+
throw new Error(`config seat ${seat} requires both provider and model`);
|
|
424
|
+
}
|
|
425
|
+
if (raw.engine !== undefined && typeof raw.engine !== "string") {
|
|
426
|
+
// Shape only: engine must be a string field. Path-safety syntax is deferred to
|
|
427
|
+
// validatePublicCliConfigEngines → assertLegalEngineName (single authority).
|
|
428
|
+
throw new Error(`config seat ${seat} engine must be a string`);
|
|
429
|
+
}
|
|
430
|
+
// #453: engine-only residual is legal only for notary after model clear.
|
|
431
|
+
// All other seats keep the baseline provider/model required contract.
|
|
432
|
+
if (!hasProvider) {
|
|
433
|
+
if (seat === "notary" && typeof raw.engine === "string") {
|
|
434
|
+
if (raw.thinking !== undefined) {
|
|
435
|
+
throw new Error(`config seat ${seat} thinking requires provider/model`);
|
|
436
|
+
}
|
|
437
|
+
return { engine: raw.engine };
|
|
438
|
+
}
|
|
439
|
+
throw new Error(`config seat ${seat} requires provider`);
|
|
440
|
+
}
|
|
342
441
|
if (typeof raw.provider !== "string" || raw.provider.trim() === "") {
|
|
343
442
|
throw new Error(`config seat ${seat} requires provider`);
|
|
344
443
|
}
|
|
@@ -355,20 +454,13 @@ function parseSeatModelConfig(value: unknown, seat: string): PersistentSeatConfi
|
|
|
355
454
|
}
|
|
356
455
|
}
|
|
357
456
|
const parsed: PersistentSeatConfig = {
|
|
358
|
-
provider: raw.provider,
|
|
359
|
-
model: raw.model,
|
|
457
|
+
provider: raw.provider as string,
|
|
458
|
+
model: raw.model as string,
|
|
360
459
|
...(raw.thinking === undefined
|
|
361
460
|
? {}
|
|
362
461
|
: { thinking: raw.thinking as PublicThinkingLevel }),
|
|
462
|
+
...(raw.engine === undefined ? {} : { engine: raw.engine as string }),
|
|
363
463
|
};
|
|
364
|
-
if (raw.engine !== undefined) {
|
|
365
|
-
// Shape only: engine must be a string field. Path-safety syntax is deferred to
|
|
366
|
-
// validatePublicCliConfigEngines → assertLegalEngineName (single authority).
|
|
367
|
-
if (typeof raw.engine !== "string") {
|
|
368
|
-
throw new Error(`config seat ${seat} engine must be a string`);
|
|
369
|
-
}
|
|
370
|
-
parsed.engine = raw.engine;
|
|
371
|
-
}
|
|
372
464
|
return parsed;
|
|
373
465
|
}
|
|
374
466
|
|
|
@@ -444,14 +536,15 @@ function resolveBaseSeat(
|
|
|
444
536
|
seat: PublicConfigurableSeat,
|
|
445
537
|
credentials: CredentialProviders,
|
|
446
538
|
): EffectiveSeat {
|
|
447
|
-
const automatic = seat
|
|
448
|
-
|
|
449
|
-
|
|
539
|
+
const automatic = isAutomaticConfigurableSeat(seat);
|
|
540
|
+
// Engine-only residual is not a persistent model source (#453).
|
|
541
|
+
const persistentModel = seatModelOnly(config.seats[seat]);
|
|
542
|
+
if (persistentModel !== undefined) {
|
|
450
543
|
return {
|
|
451
544
|
seat,
|
|
452
545
|
automatic,
|
|
453
546
|
source: "persistent",
|
|
454
|
-
selection:
|
|
547
|
+
selection: persistentModel,
|
|
455
548
|
engineSource: "unconfigured",
|
|
456
549
|
};
|
|
457
550
|
}
|
|
@@ -474,7 +567,7 @@ export function resolveEffectiveSeat(
|
|
|
474
567
|
credentials: CredentialProviders,
|
|
475
568
|
invocation?: InvocationModelOverride,
|
|
476
569
|
): EffectiveSeat {
|
|
477
|
-
const automatic = seat
|
|
570
|
+
const automatic = isAutomaticConfigurableSeat(seat);
|
|
478
571
|
const hasModelInvocation =
|
|
479
572
|
invocation !== undefined &&
|
|
480
573
|
(invocation.model !== undefined || invocation.thinking !== undefined);
|
|
@@ -527,7 +620,7 @@ export function effectiveSeatConfigurations(
|
|
|
527
620
|
);
|
|
528
621
|
}
|
|
529
622
|
|
|
530
|
-
/** Callable roles first (package order), then automatic
|
|
623
|
+
/** Callable roles first (package order), then automatic configurable seats. */
|
|
531
624
|
export function listRolesForDisplay(
|
|
532
625
|
config: PublicCliConfig,
|
|
533
626
|
credentials: CredentialProviders,
|
|
@@ -537,8 +630,10 @@ export function listRolesForDisplay(
|
|
|
537
630
|
const callable = PUBLIC_CALLABLE_ROLES.map(
|
|
538
631
|
(role) => all.find((entry) => entry.seat === role)!,
|
|
539
632
|
);
|
|
540
|
-
const
|
|
541
|
-
|
|
633
|
+
const automatic = AUTOMATIC_CONFIGURABLE_SEATS.map(
|
|
634
|
+
(seat) => all.find((entry) => entry.seat === seat)!,
|
|
635
|
+
);
|
|
636
|
+
return [...callable, ...automatic];
|
|
542
637
|
}
|
|
543
638
|
|
|
544
639
|
/**
|
|
@@ -1125,12 +1125,14 @@ const SUPPORT_COMMAND_HELP = {
|
|
|
1125
1125
|
summary: "Persistent seat model, labor-engine, and auto-resume defaults.",
|
|
1126
1126
|
usage: [
|
|
1127
1127
|
"ak-role config set <seat> <provider/model[:thinking]> [<seat> <spec> ...]",
|
|
1128
|
+
"ak-role config unset <gatekeeper|inspector|notary>",
|
|
1128
1129
|
"ak-role config set-engine <seat> <name>",
|
|
1129
1130
|
"ak-role config unset-engine <seat>",
|
|
1130
1131
|
"ak-role config set-auto-resume-limit <N>",
|
|
1131
1132
|
],
|
|
1132
1133
|
examples: [
|
|
1133
1134
|
"ak-role config set judge openai-codex/gpt-5.6-sol:high",
|
|
1135
|
+
"ak-role config unset gatekeeper",
|
|
1134
1136
|
"ak-role config set-engine judge opus",
|
|
1135
1137
|
"ak-role config set-auto-resume-limit 3",
|
|
1136
1138
|
],
|
|
@@ -19,15 +19,35 @@ export type PublicCallableRole = (typeof PUBLIC_CALLABLE_ROLES)[number];
|
|
|
19
19
|
/** Automatic attendance seat — configurable, never a caller-selected command. */
|
|
20
20
|
export const AUTOMATIC_NAVIGATOR_SEAT = "navigator" as const;
|
|
21
21
|
|
|
22
|
+
/** Automatic menxia seats — configurable model only; never caller commands (#453). */
|
|
23
|
+
export const AUTOMATIC_GATEKEEPER_SEAT = "gatekeeper" as const;
|
|
24
|
+
export const AUTOMATIC_INSPECTOR_SEAT = "inspector" as const;
|
|
25
|
+
|
|
26
|
+
/** All automatic configurable seats (no independent public activation command). */
|
|
27
|
+
export const AUTOMATIC_CONFIGURABLE_SEATS = [
|
|
28
|
+
AUTOMATIC_GATEKEEPER_SEAT,
|
|
29
|
+
AUTOMATIC_INSPECTOR_SEAT,
|
|
30
|
+
AUTOMATIC_NAVIGATOR_SEAT,
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export type AutomaticConfigurableSeat =
|
|
34
|
+
(typeof AUTOMATIC_CONFIGURABLE_SEATS)[number];
|
|
35
|
+
|
|
22
36
|
export type PublicConfigurableSeat =
|
|
23
37
|
| PublicCallableRole
|
|
24
|
-
|
|
|
38
|
+
| AutomaticConfigurableSeat;
|
|
25
39
|
|
|
26
40
|
export const PUBLIC_CONFIGURABLE_SEATS = [
|
|
27
41
|
...PUBLIC_CALLABLE_ROLES,
|
|
28
|
-
|
|
42
|
+
...AUTOMATIC_CONFIGURABLE_SEATS,
|
|
29
43
|
] as const satisfies readonly PublicConfigurableSeat[];
|
|
30
44
|
|
|
45
|
+
export function isAutomaticConfigurableSeat(
|
|
46
|
+
value: string,
|
|
47
|
+
): value is AutomaticConfigurableSeat {
|
|
48
|
+
return (AUTOMATIC_CONFIGURABLE_SEATS as readonly string[]).includes(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
31
51
|
export const PUBLIC_CLI_SUPPORT_COMMANDS = [
|
|
32
52
|
"roles",
|
|
33
53
|
"config",
|
|
@@ -90,6 +110,9 @@ const STARTUP_CANDIDATES: Record<PublicConfigurableSeat, readonly ModelRef[]> =
|
|
|
90
110
|
{ provider: "openai-codex", model: "gpt-5.6-luna", thinking: "high" },
|
|
91
111
|
{ provider: "xai", model: "grok-4.5", thinking: "high" },
|
|
92
112
|
],
|
|
113
|
+
// #453: automatic menxia seats have no startup default — unset means inherit parent.
|
|
114
|
+
gatekeeper: [],
|
|
115
|
+
inspector: [],
|
|
93
116
|
navigator: [
|
|
94
117
|
{ provider: "openai-codex", model: "gpt-5.6-luna", thinking: "medium" },
|
|
95
118
|
{ provider: "xai", model: "grok-4.5", thinking: "high" },
|