@akagilnc/pi-workflow-roles 0.1.2048 → 0.1.2052

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.
@@ -5,6 +5,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
5
5
  import { homedir } from "node:os";
6
6
  import { dirname, join } from "node:path";
7
7
 
8
+ import { assertLegalEngineName } from "../package-resources/engine-material.ts";
8
9
  import {
9
10
  AUTOMATIC_NAVIGATOR_SEAT,
10
11
  PUBLIC_CALLABLE_ROLES,
@@ -22,23 +23,36 @@ export type CredentialProviders = {
22
23
 
23
24
  export type SeatModelConfig = ModelRef;
24
25
 
26
+ /** Persistent seat row: required model triple + optional engine axis (#356). */
27
+ export type PersistentSeatConfig = SeatModelConfig & {
28
+ engine?: string;
29
+ };
30
+
25
31
  export type PublicCliConfig = {
26
- seats: Partial<Record<PublicConfigurableSeat, SeatModelConfig>>;
32
+ seats: Partial<Record<PublicConfigurableSeat, PersistentSeatConfig>>;
27
33
  };
28
34
 
29
35
  export type EffectiveSource = "persistent" | "startup" | "invocation" | "unconfigured";
30
36
 
37
+ /** Engine axis source is independent of model source (#356). */
38
+ export type EngineSource = "invocation" | "persistent" | "unconfigured";
39
+
31
40
  export type EffectiveSeat = {
32
41
  seat: PublicConfigurableSeat;
33
42
  /** True when the seat is automatic Navigator attendance rather than caller-selected. */
34
43
  automatic: boolean;
35
44
  source: EffectiveSource;
36
45
  selection?: SeatModelConfig;
46
+ /** Selected engine name when configured; undefined = no engine (default path). */
47
+ engine?: string;
48
+ engineSource: EngineSource;
37
49
  };
38
50
 
39
51
  export type InvocationModelOverride = {
40
52
  model?: string;
41
53
  thinking?: PublicThinkingLevel;
54
+ /** Optional engine override for this invocation only (#356). */
55
+ engine?: string;
42
56
  };
43
57
 
44
58
  const THINKING_LEVELS = new Set<PublicThinkingLevel>([
@@ -89,14 +103,91 @@ export function setPersistentSeatConfig(
89
103
  seat: PublicConfigurableSeat,
90
104
  selection: SeatModelConfig,
91
105
  ): PublicCliConfig {
106
+ const previous = config.seats[seat];
92
107
  return {
93
108
  seats: {
94
109
  ...config.seats,
95
- [seat]: { ...selection },
110
+ [seat]: {
111
+ ...selection,
112
+ // Model rewrite preserves a previously configured engine axis.
113
+ ...(previous?.engine === undefined ? {} : { engine: previous.engine }),
114
+ },
96
115
  },
97
116
  };
98
117
  }
99
118
 
119
+ /**
120
+ * Set or clear persistent engine on the Judge seat only (#356 MVP).
121
+ * Engine-only seats are rejected — model triple remains required.
122
+ */
123
+ export function setPersistentSeatEngine(
124
+ config: PublicCliConfig,
125
+ seat: PublicConfigurableSeat,
126
+ engine: string | undefined,
127
+ ): PublicCliConfig {
128
+ if (seat !== "judge") {
129
+ throw new Error(`engine axis is judge-only; refused seat ${seat}`);
130
+ }
131
+ const previous = config.seats[seat];
132
+ if (previous === undefined) {
133
+ throw new Error(
134
+ `config seat ${seat} has no persistent model; set provider/model:thinking before engine`,
135
+ );
136
+ }
137
+ if (engine === undefined) {
138
+ const { engine: _dropped, ...modelOnly } = previous;
139
+ return {
140
+ seats: {
141
+ ...config.seats,
142
+ [seat]: modelOnly,
143
+ },
144
+ };
145
+ }
146
+ // Engine-name legality is owned solely by assertLegalEngineName
147
+ // (call-request + config-parse seams). Setter is pure seat mutation.
148
+ return {
149
+ seats: {
150
+ ...config.seats,
151
+ [seat]: { ...previous, engine },
152
+ },
153
+ };
154
+ }
155
+
156
+ /** Strip optional engine so activation model argv never sees the engine axis. */
157
+ export function seatModelOnly(seat: PersistentSeatConfig): SeatModelConfig {
158
+ return seat.thinking === undefined
159
+ ? { provider: seat.provider, model: seat.model }
160
+ : { provider: seat.provider, model: seat.model, thinking: seat.thinking };
161
+ }
162
+
163
+ /**
164
+ * Config-parse seam: engine axis is Judge-only; Judge engine names must exist
165
+ * in package materials. Call with packageRoot after load / before dispatch (#356).
166
+ * Legality authority = assertLegalEngineName (no injected duplicate).
167
+ */
168
+ export function validatePublicCliConfigEngines(
169
+ config: PublicCliConfig,
170
+ packageRoot: string,
171
+ ): void {
172
+ for (const seat of Object.keys(config.seats) as PublicConfigurableSeat[]) {
173
+ const row = config.seats[seat];
174
+ if (row?.engine === undefined) continue;
175
+ if (seat !== "judge") {
176
+ throw new Error(
177
+ `config seat ${seat} engine is not allowed; engine axis is judge-only`,
178
+ );
179
+ }
180
+ try {
181
+ assertLegalEngineName(packageRoot, row.engine);
182
+ } catch (error) {
183
+ throw new Error(
184
+ `config seat ${seat} engine is unknown: ${row.engine}`,
185
+ { cause: error },
186
+ );
187
+ }
188
+ }
189
+ }
190
+
100
191
  export function parseModelSpec(
101
192
  spec: string,
102
193
  fallbackThinking?: PublicThinkingLevel,
@@ -200,7 +291,7 @@ function parsePublicCliConfig(value: unknown): PublicCliConfig {
200
291
  return { seats };
201
292
  }
202
293
 
203
- function parseSeatModelConfig(value: unknown, seat: string): SeatModelConfig {
294
+ function parseSeatModelConfig(value: unknown, seat: string): PersistentSeatConfig {
204
295
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
205
296
  throw new Error(`config seat ${seat} must be an object`);
206
297
  }
@@ -217,11 +308,20 @@ function parseSeatModelConfig(value: unknown, seat: string): SeatModelConfig {
217
308
  ) {
218
309
  throw new Error(`config seat ${seat} requires a valid thinking level`);
219
310
  }
220
- return {
311
+ const parsed: PersistentSeatConfig = {
221
312
  provider: raw.provider,
222
313
  model: raw.model,
223
314
  thinking: raw.thinking as PublicThinkingLevel,
224
315
  };
316
+ if (raw.engine !== undefined) {
317
+ // Shape only: engine must be a string field. Name legality is deferred to
318
+ // validatePublicCliConfigEngines → assertLegalEngineName (single authority).
319
+ if (typeof raw.engine !== "string") {
320
+ throw new Error(`config seat ${seat} engine must be a string`);
321
+ }
322
+ parsed.engine = raw.engine;
323
+ }
324
+ return parsed;
225
325
  }
226
326
 
227
327
  export function providerConfigured(
@@ -258,6 +358,39 @@ function pickStartupCandidate(
258
358
  return undefined;
259
359
  }
260
360
 
361
+ function attachEngineAxis(
362
+ seat: EffectiveSeat,
363
+ config: PublicCliConfig,
364
+ invocation?: InvocationModelOverride,
365
+ ): EffectiveSeat {
366
+ // #356 MVP: engine axis is Judge-only. Other seats stay unconfigured.
367
+ if (seat.seat !== "judge") {
368
+ return {
369
+ ...seat,
370
+ engineSource: "unconfigured",
371
+ };
372
+ }
373
+ const persistentEngine = config.seats.judge?.engine;
374
+ if (invocation?.engine !== undefined) {
375
+ return {
376
+ ...seat,
377
+ engine: invocation.engine,
378
+ engineSource: "invocation",
379
+ };
380
+ }
381
+ if (persistentEngine !== undefined) {
382
+ return {
383
+ ...seat,
384
+ engine: persistentEngine,
385
+ engineSource: "persistent",
386
+ };
387
+ }
388
+ return {
389
+ ...seat,
390
+ engineSource: "unconfigured",
391
+ };
392
+ }
393
+
261
394
  function resolveBaseSeat(
262
395
  config: PublicCliConfig,
263
396
  seat: PublicConfigurableSeat,
@@ -270,7 +403,8 @@ function resolveBaseSeat(
270
403
  seat,
271
404
  automatic,
272
405
  source: "persistent",
273
- selection: { ...persistent },
406
+ selection: seatModelOnly(persistent),
407
+ engineSource: "unconfigured",
274
408
  };
275
409
  }
276
410
  const startup = pickStartupCandidate(seat, credentials);
@@ -280,9 +414,10 @@ function resolveBaseSeat(
280
414
  automatic,
281
415
  source: "startup",
282
416
  selection: startup,
417
+ engineSource: "unconfigured",
283
418
  };
284
419
  }
285
- return { seat, automatic, source: "unconfigured" };
420
+ return { seat, automatic, source: "unconfigured", engineSource: "unconfigured" };
286
421
  }
287
422
 
288
423
  export function resolveEffectiveSeat(
@@ -292,36 +427,46 @@ export function resolveEffectiveSeat(
292
427
  invocation?: InvocationModelOverride,
293
428
  ): EffectiveSeat {
294
429
  const automatic = seat === AUTOMATIC_NAVIGATOR_SEAT;
295
- const hasInvocation =
430
+ const hasModelInvocation =
296
431
  invocation !== undefined &&
297
432
  (invocation.model !== undefined || invocation.thinking !== undefined);
298
- if (!hasInvocation || invocation === undefined) {
299
- return resolveBaseSeat(config, seat, credentials);
300
- }
301
433
 
302
- if (invocation.model !== undefined) {
434
+ let modelSeat: EffectiveSeat;
435
+ if (!hasModelInvocation || invocation === undefined) {
436
+ modelSeat = resolveBaseSeat(config, seat, credentials);
437
+ } else if (invocation.model !== undefined) {
303
438
  const spec =
304
439
  invocation.model.includes(":") || invocation.thinking === undefined
305
440
  ? invocation.model
306
441
  : `${invocation.model}:${invocation.thinking}`;
307
- return {
442
+ modelSeat = {
308
443
  seat,
309
444
  automatic,
310
445
  source: "invocation",
311
446
  selection: parseModelSpec(spec),
447
+ engineSource: "unconfigured",
312
448
  };
449
+ } else {
450
+ const base = resolveBaseSeat(config, seat, credentials);
451
+ if (base.selection === undefined || invocation.thinking === undefined) {
452
+ modelSeat = {
453
+ seat,
454
+ automatic,
455
+ source: "unconfigured",
456
+ engineSource: "unconfigured",
457
+ };
458
+ } else {
459
+ modelSeat = {
460
+ seat,
461
+ automatic,
462
+ source: "invocation",
463
+ selection: { ...base.selection, thinking: invocation.thinking },
464
+ engineSource: "unconfigured",
465
+ };
466
+ }
313
467
  }
314
468
 
315
- const base = resolveBaseSeat(config, seat, credentials);
316
- if (base.selection === undefined || invocation.thinking === undefined) {
317
- return { seat, automatic, source: "unconfigured" };
318
- }
319
- return {
320
- seat,
321
- automatic,
322
- source: "invocation",
323
- selection: { ...base.selection, thinking: invocation.thinking },
324
- };
469
+ return attachEngineAxis(modelSeat, config, invocation);
325
470
  }
326
471
 
327
472
  export function effectiveSeatConfigurations(
@@ -47,6 +47,10 @@ import {
47
47
  } from "../merger-contracts.ts";
48
48
  import { sha256Hex } from "../sha256.ts";
49
49
  import { uuidv7 } from "../uuidv7.ts";
50
+ import {
51
+ appendEngineSessionMaterial,
52
+ type EngineSessionMaterial,
53
+ } from "../package-resources/engine-material.ts";
50
54
  import { CliUsageError } from "./cli-errors.ts";
51
55
  import {
52
56
  REJECTED_PUBLIC_SPELLINGS,
@@ -809,6 +813,7 @@ export async function admitJudgeInvocation(
809
813
  /** Build the Pi prompt transport for an admitted Judge request. */
810
814
  export function buildJudgeTransportPrompt(
811
815
  admitted: AdmittedJudgeInvocation,
816
+ engineMaterial?: EngineSessionMaterial,
812
817
  ): string {
813
818
  const lines: string[] = [admitted.instructionEmpty ? "" : admitted.instruction];
814
819
  if (admitted.attachments.length > 0) {
@@ -818,7 +823,7 @@ export function buildJudgeTransportPrompt(
818
823
  lines.push(`- ${attachment.frozenPath}`);
819
824
  }
820
825
  }
821
- return lines.join("\n");
826
+ return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
822
827
  }
823
828
 
824
829
  /** Load admitted-request.json written at admission (Navigator work-context seam). */
@@ -1404,6 +1409,7 @@ export async function admitCollectorInvocation(
1404
1409
  export function buildCollectorTransportPrompt(
1405
1410
  _admitted: AdmittedCollectorInvocation,
1406
1411
  ): string {
1412
+ // Exact historical fixed kickoff bytes (one-shot observation).
1407
1413
  return COLLECTOR_FIXED_KICKOFF;
1408
1414
  }
1409
1415
 
@@ -6,6 +6,7 @@
6
6
  import { writeFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
8
 
9
+ import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
9
10
  import {
10
11
  runExplicitInternalActivation,
11
12
  type ExplicitInternalKnownFailure,
@@ -76,6 +77,8 @@ export type JudgeRunEnv = {
76
77
  piRunner?: ExplicitInternalPiRunner;
77
78
  /** Effective judge seat model (persistent/startup/invocation). */
78
79
  model?: SeatModelConfig;
80
+ /** Optional labor engine name (config→activation; session material only). */
81
+ engine?: string;
79
82
  /**
80
83
  * Credential presence for public providers (auth.json shape).
81
84
  * Used as the production-owned typed channel when a selected public provider
@@ -100,10 +103,15 @@ export function buildJudgeActivationExtraArgs(
100
103
  admitted: AdmittedJudgeInvocation,
101
104
  options: {
102
105
  model?: SeatModelConfig;
106
+ engine?: string;
107
+ packageRoot?: string;
103
108
  extraPiArgs?: readonly string[];
104
109
  } = {},
105
110
  ): string[] {
106
- const prompt = buildJudgeTransportPrompt(admitted);
111
+ const prompt = buildJudgeTransportPrompt(
112
+ admitted,
113
+ engineSessionMaterialFromOptions(options),
114
+ );
107
115
  return [
108
116
  "--no-skills",
109
117
  "--no-prompt-templates",
@@ -436,7 +444,9 @@ export async function runPublicJudge(
436
444
  }
437
445
 
438
446
  const extraArgs = buildJudgeActivationExtraArgs(admitted, {
447
+ packageRoot: env.packageRoot,
439
448
  ...(env.model === undefined ? {} : { model: env.model }),
449
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
440
450
  ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
441
451
  });
442
452
 
@@ -278,6 +278,20 @@ const GLOBAL_OPTIONS = [
278
278
  zh: "覆盖 thinking 档位:off|minimal|low|medium|high|xhigh|max。",
279
279
  },
280
280
  },
281
+ {
282
+ id: "engine",
283
+ owner: "global",
284
+ canonical: "--engine",
285
+ aliases: [],
286
+ valueMetavar: "name",
287
+ required: false,
288
+ repeatable: false,
289
+ form: "option",
290
+ description: {
291
+ en: "Optional Judge labor engine for this invocation (name must exist in packaged engine materials; judge-only).",
292
+ zh: "本调用可选 Judge 劳动引擎(名字须存在于包内引擎调法材料;仅 Judge)。",
293
+ },
294
+ },
281
295
  {
282
296
  id: "help",
283
297
  owner: "global",