@akagilnc/pi-workflow-roles 0.1.2014 → 0.1.2026

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.
@@ -14387,12 +14387,15 @@ function parseModelSpec(spec, fallbackThinking) {
14387
14387
  const thinkingSplit = trimmed.lastIndexOf(":");
14388
14388
  let modelPart = trimmed;
14389
14389
  let thinking = fallbackThinking;
14390
- if (thinkingSplit > 0) {
14390
+ if (thinkingSplit !== -1) {
14391
14391
  const maybeThinking = trimmed.slice(thinkingSplit + 1);
14392
- if (THINKING_LEVELS.has(maybeThinking)) {
14393
- thinking = maybeThinking;
14394
- modelPart = trimmed.slice(0, thinkingSplit);
14392
+ if (!THINKING_LEVELS.has(maybeThinking)) {
14393
+ throw new Error(
14394
+ `model specification must be provider/model[:thinking], got ${spec}`
14395
+ );
14395
14396
  }
14397
+ thinking = maybeThinking;
14398
+ modelPart = trimmed.slice(0, thinkingSplit);
14396
14399
  }
14397
14400
  const slash = modelPart.indexOf("/");
14398
14401
  if (slash <= 0 || slash === modelPart.length - 1) {
@@ -14402,15 +14405,34 @@ function parseModelSpec(spec, fallbackThinking) {
14402
14405
  }
14403
14406
  const provider = modelPart.slice(0, slash);
14404
14407
  const model = modelPart.slice(slash + 1);
14405
- if (!thinking) {
14408
+ return thinking === void 0 ? { provider, model } : { provider, model, thinking };
14409
+ }
14410
+ function parsePersistentModelSpec(spec) {
14411
+ const parsed = parseModelSpec(spec);
14412
+ if (parsed.thinking === void 0) {
14406
14413
  throw new Error(
14407
14414
  `model specification requires a thinking level (provider/model:thinking), got ${spec}`
14408
14415
  );
14409
14416
  }
14410
- return { provider, model, thinking };
14417
+ return {
14418
+ provider: parsed.provider,
14419
+ model: parsed.model,
14420
+ thinking: parsed.thinking
14421
+ };
14411
14422
  }
14412
14423
  function formatModelSpec(selection) {
14413
- return `${selection.provider}/${selection.model}:${selection.thinking}`;
14424
+ const base = `${selection.provider}/${selection.model}`;
14425
+ return selection.thinking === void 0 ? base : `${base}:${selection.thinking}`;
14426
+ }
14427
+ function buildSeatModelCliArgs(model) {
14428
+ if (model === void 0) return [];
14429
+ return [
14430
+ "--provider",
14431
+ model.provider,
14432
+ "--model",
14433
+ model.model,
14434
+ ...model.thinking === void 0 ? [] : ["--thinking", model.thinking]
14435
+ ];
14414
14436
  }
14415
14437
  function parsePublicCliConfig(value) {
14416
14438
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -15432,7 +15454,15 @@ import {
15432
15454
  writeFile as writeFile2
15433
15455
  } from "node:fs/promises";
15434
15456
  import { basename as basename3, isAbsolute as isAbsolute3, join as join5, resolve as resolve4, sep as sep3 } from "node:path";
15435
- async function writeRoleInvocationLedger(source, role) {
15457
+ function effectiveModelLedgerFields(model) {
15458
+ if (model === void 0) return {};
15459
+ return {
15460
+ provider: model.provider,
15461
+ model: model.model,
15462
+ ...model.thinking === void 0 ? {} : { thinking: model.thinking }
15463
+ };
15464
+ }
15465
+ async function writeRoleInvocationLedger(source, role, effectiveModel) {
15436
15466
  const identity = {
15437
15467
  role,
15438
15468
  runId: source.runId,
@@ -15442,7 +15472,8 @@ async function writeRoleInvocationLedger(source, role) {
15442
15472
  sessionDirectory: source.sessionDirectory,
15443
15473
  sessionFile: source.sessionFile,
15444
15474
  ...source.correlationId === void 0 ? {} : { correlationId: source.correlationId },
15445
- ...source.ticketNumber === void 0 ? {} : { ticketNumber: source.ticketNumber }
15475
+ ...source.ticketNumber === void 0 ? {} : { ticketNumber: source.ticketNumber },
15476
+ ...effectiveModelLedgerFields(effectiveModel)
15446
15477
  };
15447
15478
  await writeFile2(
15448
15479
  join5(source.runDirectory, "invocation.json"),
@@ -15451,6 +15482,26 @@ async function writeRoleInvocationLedger(source, role) {
15451
15482
  "utf8"
15452
15483
  );
15453
15484
  }
15485
+ async function recordEffectiveInvocationModel(runDirectory, model) {
15486
+ const ledgerPath = join5(runDirectory, "invocation.json");
15487
+ const current = JSON.parse(await readFile4(ledgerPath, "utf8"));
15488
+ const next = {
15489
+ ...current,
15490
+ provider: model.provider,
15491
+ model: model.model
15492
+ };
15493
+ if (model.thinking === void 0) {
15494
+ delete next.thinking;
15495
+ } else {
15496
+ next.thinking = model.thinking;
15497
+ }
15498
+ await writeFile2(
15499
+ ledgerPath,
15500
+ `${JSON.stringify(next, null, 2)}
15501
+ `,
15502
+ "utf8"
15503
+ );
15504
+ }
15454
15505
  async function mergeInvocationIdentityPage(runDirectory, fields) {
15455
15506
  const ledgerPath = join5(runDirectory, "invocation.json");
15456
15507
  const current = JSON.parse(await readFile4(ledgerPath, "utf8"));
@@ -15753,7 +15804,7 @@ async function admitJudgeInvocation(options) {
15753
15804
  const admittedRequestPath = join5(runDirectory, "admitted-request.json");
15754
15805
  await writeFile2(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}
15755
15806
  `, "utf8");
15756
- await writeRoleInvocationLedger(admitted, admitted.role);
15807
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
15757
15808
  return {
15758
15809
  role: "judge",
15759
15810
  runId,
@@ -15835,7 +15886,7 @@ async function admitCoderInvocation(options) {
15835
15886
  const admittedRequestPath = join5(runDirectory, "admitted-request.json");
15836
15887
  await writeFile2(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}
15837
15888
  `, "utf8");
15838
- await writeRoleInvocationLedger(admitted, admitted.role);
15889
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
15839
15890
  return {
15840
15891
  role: "coder",
15841
15892
  phase: options.phase,
@@ -15950,7 +16001,7 @@ async function admitFixerInvocation(options) {
15950
16001
  const admittedRequestPath = join5(runDirectory, "admitted-request.json");
15951
16002
  await writeFile2(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}
15952
16003
  `, "utf8");
15953
- await writeRoleInvocationLedger(admitted, admitted.role);
16004
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
15954
16005
  return {
15955
16006
  role: "fixer",
15956
16007
  phase: options.phase,
@@ -16194,7 +16245,7 @@ async function admitCollectorInvocation(options) {
16194
16245
  `,
16195
16246
  "utf8"
16196
16247
  );
16197
- await writeRoleInvocationLedger(admitted, admitted.role);
16248
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
16198
16249
  return {
16199
16250
  role: "collector",
16200
16251
  runId,
@@ -16444,7 +16495,7 @@ async function admitDoctorInvocation(options) {
16444
16495
  `,
16445
16496
  "utf8"
16446
16497
  );
16447
- await writeRoleInvocationLedger(admitted, admitted.role);
16498
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
16448
16499
  return {
16449
16500
  role: "doctor",
16450
16501
  runId,
@@ -16578,7 +16629,7 @@ async function admitReviewerInvocation(options) {
16578
16629
  `,
16579
16630
  "utf8"
16580
16631
  );
16581
- await writeRoleInvocationLedger(admitted, admitted.role);
16632
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
16582
16633
  return {
16583
16634
  role: "reviewer",
16584
16635
  runId,
@@ -16765,7 +16816,7 @@ async function admitMergerInvocation(options) {
16765
16816
  `,
16766
16817
  "utf8"
16767
16818
  );
16768
- await writeRoleInvocationLedger(admitted, admitted.role);
16819
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
16769
16820
  return {
16770
16821
  role: "merger",
16771
16822
  runId,
@@ -17816,7 +17867,10 @@ async function markRunAdmitted(admitted) {
17816
17867
  ...admitted.role === "coder" || admitted.role === "fixer" ? { phase: admitted.phase } : {}
17817
17868
  });
17818
17869
  }
17819
- async function markRunRunning(runDirectory) {
17870
+ async function markRunRunning(runDirectory, effectiveModel) {
17871
+ if (effectiveModel !== void 0) {
17872
+ await recordEffectiveInvocationModel(runDirectory, effectiveModel);
17873
+ }
17820
17874
  const current = await readRoleRunState(runDirectory);
17821
17875
  if (current === void 0) {
17822
17876
  throw new Error("cannot mark running: run state missing");
@@ -21605,17 +21659,6 @@ var init_settlement = __esm({
21605
21659
  // src/public-cli/coder-run.ts
21606
21660
  import { writeFile as writeFile6 } from "node:fs/promises";
21607
21661
  import { join as join11 } from "node:path";
21608
- function buildModelArgs(model) {
21609
- if (model === void 0) return [];
21610
- return [
21611
- "--provider",
21612
- model.provider,
21613
- "--model",
21614
- model.model,
21615
- "--thinking",
21616
- model.thinking
21617
- ];
21618
- }
21619
21662
  function buildCoderActivationExtraArgs(admitted, options) {
21620
21663
  const prompt = buildCoderTransportPrompt(admitted);
21621
21664
  const skillArgs = admitted.phase === "apply" ? [
@@ -21641,7 +21684,7 @@ function buildCoderActivationExtraArgs(admitted, options) {
21641
21684
  admitted.taskPath,
21642
21685
  "--mode",
21643
21686
  "json",
21644
- ...buildModelArgs(options.model),
21687
+ ...buildSeatModelCliArgs(options.model),
21645
21688
  prompt
21646
21689
  ];
21647
21690
  }
@@ -21669,7 +21712,7 @@ function buildCoderResumeActivationExtraArgs(admitted, options) {
21669
21712
  admitted.taskPath,
21670
21713
  "--mode",
21671
21714
  "json",
21672
- ...buildModelArgs(options.model),
21715
+ ...buildSeatModelCliArgs(options.model),
21673
21716
  RESUME_TRANSPORT_ENVELOPE
21674
21717
  ];
21675
21718
  }
@@ -21735,7 +21778,7 @@ async function dispatchAdmittedCoder(input) {
21735
21778
  io
21736
21779
  );
21737
21780
  }
21738
- await markRunRunning(admitted.runDirectory);
21781
+ await markRunRunning(admitted.runDirectory, env.model);
21739
21782
  await clearTypedProviderHttpObservation(admitted.runDirectory);
21740
21783
  const childEnv = {
21741
21784
  ...process.env,
@@ -21841,7 +21884,8 @@ async function runPublicCoder(argv, env, io, parseCoderArgv2) {
21841
21884
  instruction: parsed.instruction,
21842
21885
  attachmentPaths: parsed.attachmentPaths,
21843
21886
  ...parsed.project === void 0 ? {} : { project: parsed.project },
21844
- ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId }
21887
+ ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId },
21888
+ ...env.model === void 0 ? {} : { model: env.model }
21845
21889
  });
21846
21890
  } catch (error) {
21847
21891
  if (error instanceof CliUsageError) {
@@ -21995,17 +22039,6 @@ var init_coder_run = __esm({
21995
22039
  // src/public-cli/collector-run.ts
21996
22040
  import { writeFile as writeFile7 } from "node:fs/promises";
21997
22041
  import { join as join12 } from "node:path";
21998
- function buildModelArgs2(model) {
21999
- if (model === void 0) return [];
22000
- return [
22001
- "--provider",
22002
- model.provider,
22003
- "--model",
22004
- model.model,
22005
- "--thinking",
22006
- model.thinking
22007
- ];
22008
- }
22009
22042
  function buildCollectorActivationExtraArgs(admitted, options = {}) {
22010
22043
  const prompt = buildCollectorTransportPrompt(admitted);
22011
22044
  return [
@@ -22027,7 +22060,7 @@ function buildCollectorActivationExtraArgs(admitted, options = {}) {
22027
22060
  ...admitted.requestManifestPath === void 0 ? [] : ["--ak-collector-request-manifest", admitted.requestManifestPath],
22028
22061
  "--mode",
22029
22062
  "json",
22030
- ...buildModelArgs2(options.model),
22063
+ ...buildSeatModelCliArgs(options.model),
22031
22064
  prompt
22032
22065
  ];
22033
22066
  }
@@ -22068,7 +22101,7 @@ async function dispatchAdmittedCollector(input) {
22068
22101
  io
22069
22102
  );
22070
22103
  }
22071
- await markRunRunning(admitted.runDirectory);
22104
+ await markRunRunning(admitted.runDirectory, env.model);
22072
22105
  await clearTypedProviderHttpObservation(admitted.runDirectory);
22073
22106
  const childEnv = {
22074
22107
  ...process.env,
@@ -22181,7 +22214,8 @@ async function runPublicCollector(argv, env, io, parseCollectorArgv2) {
22181
22214
  ...parsed.project === void 0 ? {} : { project: parsed.project },
22182
22215
  ...parsed.repo === void 0 ? {} : { repo: parsed.repo },
22183
22216
  ...parsed.requestManifestPath === void 0 ? {} : { requestManifestPath: parsed.requestManifestPath },
22184
- ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId }
22217
+ ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId },
22218
+ ...env.model === void 0 ? {} : { model: env.model }
22185
22219
  });
22186
22220
  } catch (error) {
22187
22221
  if (error instanceof CliUsageError) {
@@ -22232,17 +22266,6 @@ var init_collector_run = __esm({
22232
22266
  // src/public-cli/doctor-run.ts
22233
22267
  import { writeFile as writeFile8 } from "node:fs/promises";
22234
22268
  import { join as join13 } from "node:path";
22235
- function buildModelArgs3(model) {
22236
- if (model === void 0) return [];
22237
- return [
22238
- "--provider",
22239
- model.provider,
22240
- "--model",
22241
- model.model,
22242
- "--thinking",
22243
- model.thinking
22244
- ];
22245
- }
22246
22269
  function buildDoctorActivationExtraArgs(admitted, options = {}) {
22247
22270
  const prompt = buildDoctorTransportPrompt(admitted);
22248
22271
  return [
@@ -22261,7 +22284,7 @@ function buildDoctorActivationExtraArgs(admitted, options = {}) {
22261
22284
  admitted.caseRunsPath,
22262
22285
  "--mode",
22263
22286
  "json",
22264
- ...buildModelArgs3(options.model),
22287
+ ...buildSeatModelCliArgs(options.model),
22265
22288
  prompt
22266
22289
  ];
22267
22290
  }
@@ -22299,7 +22322,7 @@ async function dispatchAdmittedDoctor(input) {
22299
22322
  io
22300
22323
  );
22301
22324
  }
22302
- await markRunRunning(admitted.runDirectory);
22325
+ await markRunRunning(admitted.runDirectory, env.model);
22303
22326
  await clearTypedProviderHttpObservation(admitted.runDirectory);
22304
22327
  const childEnv = {
22305
22328
  ...process.env,
@@ -22417,7 +22440,8 @@ async function runPublicDoctor(argv, env, io, parseDoctorArgv2) {
22417
22440
  attachmentPaths: parsed.attachmentPaths,
22418
22441
  ...parsed.project === void 0 ? {} : { project: parsed.project },
22419
22442
  ...parsed.runs === void 0 ? {} : { runs: parsed.runs },
22420
- ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId }
22443
+ ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId },
22444
+ ...env.model === void 0 ? {} : { model: env.model }
22421
22445
  });
22422
22446
  } catch (error) {
22423
22447
  if (error instanceof CliUsageError) {
@@ -22465,17 +22489,6 @@ var init_doctor_run = __esm({
22465
22489
  // src/public-cli/fixer-run.ts
22466
22490
  import { writeFile as writeFile9 } from "node:fs/promises";
22467
22491
  import { join as join14 } from "node:path";
22468
- function buildModelArgs4(model) {
22469
- if (model === void 0) return [];
22470
- return [
22471
- "--provider",
22472
- model.provider,
22473
- "--model",
22474
- model.model,
22475
- "--thinking",
22476
- model.thinking
22477
- ];
22478
- }
22479
22492
  function buildFixerActivationExtraArgs(admitted, options) {
22480
22493
  const prompt = buildFixerTransportPrompt(admitted);
22481
22494
  const diagnosisSkillPath = resolvePackagedMethodSkillPath(
@@ -22507,7 +22520,7 @@ function buildFixerActivationExtraArgs(admitted, options) {
22507
22520
  ...prerequisiteArgs,
22508
22521
  "--mode",
22509
22522
  "json",
22510
- ...buildModelArgs4(options.model),
22523
+ ...buildSeatModelCliArgs(options.model),
22511
22524
  prompt
22512
22525
  ];
22513
22526
  }
@@ -22541,7 +22554,7 @@ function buildFixerResumeActivationExtraArgs(admitted, options) {
22541
22554
  ...prerequisiteArgs,
22542
22555
  "--mode",
22543
22556
  "json",
22544
- ...buildModelArgs4(options.model),
22557
+ ...buildSeatModelCliArgs(options.model),
22545
22558
  RESUME_TRANSPORT_ENVELOPE
22546
22559
  ];
22547
22560
  }
@@ -22604,7 +22617,7 @@ async function dispatchAdmittedFixer(input) {
22604
22617
  io
22605
22618
  );
22606
22619
  }
22607
- await markRunRunning(admitted.runDirectory);
22620
+ await markRunRunning(admitted.runDirectory, env.model);
22608
22621
  await clearTypedProviderHttpObservation(admitted.runDirectory);
22609
22622
  const childEnv = {
22610
22623
  ...process.env,
@@ -22733,7 +22746,8 @@ async function runPublicFixer(argv, env, io, parseFixerArgv2) {
22733
22746
  attachmentPaths: parsed.attachmentPaths,
22734
22747
  ...parsed.prerequisitesPath === void 0 ? {} : { prerequisitesPath: parsed.prerequisitesPath },
22735
22748
  ...parsed.project === void 0 ? {} : { project: parsed.project },
22736
- ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId }
22749
+ ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId },
22750
+ ...env.model === void 0 ? {} : { model: env.model }
22737
22751
  });
22738
22752
  } catch (error) {
22739
22753
  if (error instanceof CliUsageError) {
@@ -22873,17 +22887,6 @@ var init_fixer_run = __esm({
22873
22887
  // src/public-cli/judge-run.ts
22874
22888
  import { writeFile as writeFile10 } from "node:fs/promises";
22875
22889
  import { join as join15 } from "node:path";
22876
- function buildModelArgs5(model) {
22877
- if (model === void 0) return [];
22878
- return [
22879
- "--provider",
22880
- model.provider,
22881
- "--model",
22882
- model.model,
22883
- "--thinking",
22884
- model.thinking
22885
- ];
22886
- }
22887
22890
  function buildJudgeActivationExtraArgs(admitted, options = {}) {
22888
22891
  const prompt = buildJudgeTransportPrompt(admitted);
22889
22892
  return [
@@ -22901,7 +22904,7 @@ function buildJudgeActivationExtraArgs(admitted, options = {}) {
22901
22904
  "judge",
22902
22905
  "--mode",
22903
22906
  "json",
22904
- ...buildModelArgs5(options.model),
22907
+ ...buildSeatModelCliArgs(options.model),
22905
22908
  prompt
22906
22909
  ];
22907
22910
  }
@@ -22920,7 +22923,7 @@ function buildJudgeResumeActivationExtraArgs(admitted, options = {}) {
22920
22923
  "judge",
22921
22924
  "--mode",
22922
22925
  "json",
22923
- ...buildModelArgs5(options.model),
22926
+ ...buildSeatModelCliArgs(options.model),
22924
22927
  RESUME_TRANSPORT_ENVELOPE
22925
22928
  ];
22926
22929
  }
@@ -22983,7 +22986,7 @@ async function dispatchAdmittedJudge(input) {
22983
22986
  io
22984
22987
  );
22985
22988
  }
22986
- await markRunRunning(admitted.runDirectory);
22989
+ await markRunRunning(admitted.runDirectory, env.model);
22987
22990
  await clearTypedProviderHttpObservation(admitted.runDirectory);
22988
22991
  const childEnv = {
22989
22992
  ...process.env,
@@ -23102,7 +23105,8 @@ async function runPublicJudge(argv, env, io, parseJudgeArgv2) {
23102
23105
  instruction: parsed.instruction,
23103
23106
  attachmentPaths: parsed.attachmentPaths,
23104
23107
  ...parsed.project === void 0 ? {} : { project: parsed.project },
23105
- ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId }
23108
+ ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId },
23109
+ ...env.model === void 0 ? {} : { model: env.model }
23106
23110
  });
23107
23111
  } catch (error) {
23108
23112
  if (error instanceof CliUsageError) {
@@ -23205,17 +23209,6 @@ var init_judge_run = __esm({
23205
23209
  // src/public-cli/merger-run.ts
23206
23210
  import { mkdir as mkdir4, writeFile as writeFile11 } from "node:fs/promises";
23207
23211
  import { join as join16, resolve as resolve7 } from "node:path";
23208
- function buildModelArgs6(model) {
23209
- if (model === void 0) return [];
23210
- return [
23211
- "--provider",
23212
- model.provider,
23213
- "--model",
23214
- model.model,
23215
- "--thinking",
23216
- model.thinking
23217
- ];
23218
- }
23219
23212
  function buildMergerActivationExtraArgs(admitted, options) {
23220
23213
  const prompt = buildMergerTransportPrompt(admitted);
23221
23214
  const skillPath = resolvePackagedMethodSkillPath(
@@ -23240,7 +23233,7 @@ function buildMergerActivationExtraArgs(admitted, options) {
23240
23233
  admitted.mergerInputPath,
23241
23234
  "--mode",
23242
23235
  "json",
23243
- ...buildModelArgs6(options.model),
23236
+ ...buildSeatModelCliArgs(options.model),
23244
23237
  prompt
23245
23238
  ];
23246
23239
  }
@@ -23267,7 +23260,7 @@ function buildMergerResumeActivationExtraArgs(admitted, options) {
23267
23260
  admitted.mergerInputPath,
23268
23261
  "--mode",
23269
23262
  "json",
23270
- ...buildModelArgs6(options.model),
23263
+ ...buildSeatModelCliArgs(options.model),
23271
23264
  RESUME_TRANSPORT_ENVELOPE
23272
23265
  ];
23273
23266
  }
@@ -23333,7 +23326,7 @@ async function dispatchAdmittedMerger(input) {
23333
23326
  io
23334
23327
  );
23335
23328
  }
23336
- await markRunRunning(admitted.runDirectory);
23329
+ await markRunRunning(admitted.runDirectory, env.model);
23337
23330
  await clearTypedProviderHttpObservation(admitted.runDirectory);
23338
23331
  const childEnv = {
23339
23332
  ...process.env,
@@ -23509,7 +23502,8 @@ async function runPublicMerger(argv, env, io, parseMergerArgv2) {
23509
23502
  instruction: parsed.instruction,
23510
23503
  attachmentPaths: parsed.attachmentPaths,
23511
23504
  ...parsed.project === void 0 ? {} : { project: parsed.project },
23512
- ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId }
23505
+ ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId },
23506
+ ...env.model === void 0 ? {} : { model: env.model }
23513
23507
  });
23514
23508
  } catch (error) {
23515
23509
  if (error instanceof CliUsageError) {
@@ -23676,17 +23670,6 @@ var init_merger_run = __esm({
23676
23670
  // src/public-cli/reviewer-run.ts
23677
23671
  import { writeFile as writeFile12 } from "node:fs/promises";
23678
23672
  import { join as join17 } from "node:path";
23679
- function buildModelArgs7(model) {
23680
- if (model === void 0) return [];
23681
- return [
23682
- "--provider",
23683
- model.provider,
23684
- "--model",
23685
- model.model,
23686
- "--thinking",
23687
- model.thinking
23688
- ];
23689
- }
23690
23673
  function buildReviewerTicketNumberArgs(ticketNumber) {
23691
23674
  return ticketNumber === void 0 ? [] : ["--ak-review-ticket-number", String(ticketNumber)];
23692
23675
  }
@@ -23718,7 +23701,7 @@ function buildReviewerActivationExtraArgs(admitted, options) {
23718
23701
  ...ticketNumberArgs,
23719
23702
  "--mode",
23720
23703
  "json",
23721
- ...buildModelArgs7(options.model),
23704
+ ...buildSeatModelCliArgs(options.model),
23722
23705
  prompt
23723
23706
  ];
23724
23707
  }
@@ -23749,7 +23732,7 @@ function buildReviewerResumeActivationExtraArgs(admitted, options) {
23749
23732
  ...ticketNumberArgs,
23750
23733
  "--mode",
23751
23734
  "json",
23752
- ...buildModelArgs7(options.model),
23735
+ ...buildSeatModelCliArgs(options.model),
23753
23736
  RESUME_TRANSPORT_ENVELOPE
23754
23737
  ];
23755
23738
  }
@@ -23812,7 +23795,7 @@ async function dispatchAdmittedReviewer(input) {
23812
23795
  io
23813
23796
  );
23814
23797
  }
23815
- await markRunRunning(admitted.runDirectory);
23798
+ await markRunRunning(admitted.runDirectory, env.model);
23816
23799
  await clearTypedProviderHttpObservation(admitted.runDirectory);
23817
23800
  await clearReviewerDispatchRejection(admitted.runDirectory);
23818
23801
  const childEnv = {
@@ -23942,7 +23925,8 @@ async function runPublicReviewer(argv, env, io, parseReviewerArgv2) {
23942
23925
  baseRevision: parsed.baseRevision,
23943
23926
  authorityRefs: parsed.authorityRefs,
23944
23927
  ...parsed.project === void 0 ? {} : { project: parsed.project },
23945
- ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId }
23928
+ ...env.createRunId === void 0 ? {} : { createRunId: env.createRunId },
23929
+ ...env.model === void 0 ? {} : { model: env.model }
23946
23930
  });
23947
23931
  } catch (error) {
23948
23932
  if (error instanceof CliUsageError) {
@@ -26365,7 +26349,7 @@ async function runConfigCommand(args, home, io) {
26365
26349
  if (!isPublicConfigurableSeat(seat)) {
26366
26350
  throw new CliUsageError(`unknown configurable seat: ${seat}`);
26367
26351
  }
26368
- config = setPersistentSeatConfig(config, seat, parseModelSpec(spec));
26352
+ config = setPersistentSeatConfig(config, seat, parsePersistentModelSpec(spec));
26369
26353
  }
26370
26354
  await savePublicCliConfig(config, home);
26371
26355
  io.stdout(renderConfig(config));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.2014",
3
+ "version": "0.1.2026",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -10,7 +10,7 @@ import {
10
10
  formatModelSpec,
11
11
  loadCredentialProviders,
12
12
  loadPublicCliConfig,
13
- parseModelSpec,
13
+ parsePersistentModelSpec,
14
14
  resolveEffectiveSeat,
15
15
  savePublicCliConfig,
16
16
  setPersistentSeatConfig,
@@ -402,7 +402,7 @@ async function runConfigCommand(
402
402
  if (!isPublicConfigurableSeat(seat)) {
403
403
  throw new CliUsageError(`unknown configurable seat: ${seat}`);
404
404
  }
405
- config = setPersistentSeatConfig(config, seat, parseModelSpec(spec));
405
+ config = setPersistentSeatConfig(config, seat, parsePersistentModelSpec(spec));
406
406
  }
407
407
  await savePublicCliConfig(config, home);
408
408
  io.stdout(renderConfig(config));
@@ -24,6 +24,7 @@ import {
24
24
  type AdmittedCoderInvocation,
25
25
  } from "./invocation.ts";
26
26
  import {
27
+ buildSeatModelCliArgs,
27
28
  type CredentialProviders,
28
29
  type SeatModelConfig,
29
30
  } from "./config.ts";
@@ -84,17 +85,6 @@ export type CoderRunEnv = {
84
85
  timeoutMs?: number;
85
86
  };
86
87
 
87
- function buildModelArgs(model: SeatModelConfig | undefined): string[] {
88
- if (model === undefined) return [];
89
- return [
90
- "--provider",
91
- model.provider,
92
- "--model",
93
- model.model,
94
- "--thinking",
95
- model.thinking,
96
- ];
97
- }
98
88
 
99
89
  /**
100
90
  * Build Internal activation extra-args for an admitted Coder run.
@@ -136,7 +126,7 @@ export function buildCoderActivationExtraArgs(
136
126
  admitted.taskPath,
137
127
  "--mode",
138
128
  "json",
139
- ...buildModelArgs(options.model),
129
+ ...buildSeatModelCliArgs(options.model),
140
130
  prompt,
141
131
  ];
142
132
  }
@@ -179,7 +169,7 @@ export function buildCoderResumeActivationExtraArgs(
179
169
  admitted.taskPath,
180
170
  "--mode",
181
171
  "json",
182
- ...buildModelArgs(options.model),
172
+ ...buildSeatModelCliArgs(options.model),
183
173
  RESUME_TRANSPORT_ENVELOPE,
184
174
  ];
185
175
  }
@@ -304,7 +294,7 @@ async function dispatchAdmittedCoder(input: {
304
294
  io,
305
295
  );
306
296
  }
307
- await markRunRunning(admitted.runDirectory);
297
+ await markRunRunning(admitted.runDirectory, env.model);
308
298
  await clearTypedProviderHttpObservation(admitted.runDirectory);
309
299
 
310
300
  const childEnv: NodeJS.ProcessEnv = {
@@ -434,6 +424,7 @@ export async function runPublicCoder(
434
424
  attachmentPaths: parsed.attachmentPaths,
435
425
  ...(parsed.project === undefined ? {} : { project: parsed.project }),
436
426
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
427
+ ...(env.model === undefined ? {} : { model: env.model }),
437
428
  });
438
429
  } catch (error) {
439
430
  if (error instanceof CliUsageError) {
@@ -20,6 +20,7 @@ import {
20
20
  type ParseCollectorArgvResult,
21
21
  } from "./invocation.ts";
22
22
  import {
23
+ buildSeatModelCliArgs,
23
24
  type CredentialProviders,
24
25
  type SeatModelConfig,
25
26
  } from "./config.ts";
@@ -69,17 +70,6 @@ export type CollectorRunEnv = {
69
70
  timeoutMs?: number;
70
71
  };
71
72
 
72
- function buildModelArgs(model: SeatModelConfig | undefined): string[] {
73
- if (model === undefined) return [];
74
- return [
75
- "--provider",
76
- model.provider,
77
- "--model",
78
- model.model,
79
- "--thinking",
80
- model.thinking,
81
- ];
82
- }
83
73
 
84
74
  /**
85
75
  * Build Internal activation extra-args for an admitted Collector run.
@@ -114,7 +104,7 @@ export function buildCollectorActivationExtraArgs(
114
104
  : ["--ak-collector-request-manifest", admitted.requestManifestPath]),
115
105
  "--mode",
116
106
  "json",
117
- ...buildModelArgs(options.model),
107
+ ...buildSeatModelCliArgs(options.model),
118
108
  prompt,
119
109
  ];
120
110
  }
@@ -202,7 +192,7 @@ async function dispatchAdmittedCollector(input: {
202
192
  io,
203
193
  );
204
194
  }
205
- await markRunRunning(admitted.runDirectory);
195
+ await markRunRunning(admitted.runDirectory, env.model);
206
196
  await clearTypedProviderHttpObservation(admitted.runDirectory);
207
197
 
208
198
  const childEnv: NodeJS.ProcessEnv = {
@@ -340,6 +330,7 @@ export async function runPublicCollector(
340
330
  ...(parsed.repo === undefined ? {} : { repo: parsed.repo }),
341
331
  ...(parsed.requestManifestPath === undefined ? {} : { requestManifestPath: parsed.requestManifestPath }),
342
332
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
333
+ ...(env.model === undefined ? {} : { model: env.model }),
343
334
  });
344
335
  } catch (error) {
345
336
  if (error instanceof CliUsageError) {
@@ -108,12 +108,18 @@ export function parseModelSpec(
108
108
  const thinkingSplit = trimmed.lastIndexOf(":");
109
109
  let modelPart = trimmed;
110
110
  let thinking: PublicThinkingLevel | undefined = fallbackThinking;
111
- if (thinkingSplit > 0) {
111
+ // #346: no colon → bare provider/model is legal. Colon present (any index,
112
+ // including 0) → suffix must be a typed PublicThinkingLevel (empty/unknown
113
+ // stay format rejects; never swallow ":…" into the model name).
114
+ if (thinkingSplit !== -1) {
112
115
  const maybeThinking = trimmed.slice(thinkingSplit + 1);
113
- if (THINKING_LEVELS.has(maybeThinking as PublicThinkingLevel)) {
114
- thinking = maybeThinking as PublicThinkingLevel;
115
- modelPart = trimmed.slice(0, thinkingSplit);
116
+ if (!THINKING_LEVELS.has(maybeThinking as PublicThinkingLevel)) {
117
+ throw new Error(
118
+ `model specification must be provider/model[:thinking], got ${spec}`,
119
+ );
116
120
  }
121
+ thinking = maybeThinking as PublicThinkingLevel;
122
+ modelPart = trimmed.slice(0, thinkingSplit);
117
123
  }
118
124
  const slash = modelPart.indexOf("/");
119
125
  if (slash <= 0 || slash === modelPart.length - 1) {
@@ -123,16 +129,48 @@ export function parseModelSpec(
123
129
  }
124
130
  const provider = modelPart.slice(0, slash);
125
131
  const model = modelPart.slice(slash + 1);
126
- if (!thinking) {
132
+ // #346: bare provider/model is legal on invocation — do not invent thinking.
133
+ // Persistent config set still requires thinking via parsePersistentModelSpec.
134
+ return thinking === undefined
135
+ ? { provider, model }
136
+ : { provider, model, thinking };
137
+ }
138
+
139
+ /** Persistent seat config keeps the three-part provider/model:thinking grammar. */
140
+ export function parsePersistentModelSpec(spec: string): SeatModelConfig & {
141
+ thinking: PublicThinkingLevel;
142
+ } {
143
+ const parsed = parseModelSpec(spec);
144
+ if (parsed.thinking === undefined) {
127
145
  throw new Error(
128
146
  `model specification requires a thinking level (provider/model:thinking), got ${spec}`,
129
147
  );
130
148
  }
131
- return { provider, model, thinking };
149
+ return {
150
+ provider: parsed.provider,
151
+ model: parsed.model,
152
+ thinking: parsed.thinking,
153
+ };
132
154
  }
133
155
 
134
156
  export function formatModelSpec(selection: SeatModelConfig): string {
135
- return `${selection.provider}/${selection.model}:${selection.thinking}`;
157
+ const base = `${selection.provider}/${selection.model}`;
158
+ return selection.thinking === undefined ? base : `${base}:${selection.thinking}`;
159
+ }
160
+
161
+ /**
162
+ * Single source: seat model selection → Pi argv at the public CLI execution seam.
163
+ * Bare provider/model omits --thinking so pi/model defaults apply; suffix passes through.
164
+ */
165
+ export function buildSeatModelCliArgs(model: SeatModelConfig | undefined): string[] {
166
+ if (model === undefined) return [];
167
+ return [
168
+ "--provider",
169
+ model.provider,
170
+ "--model",
171
+ model.model,
172
+ ...(model.thinking === undefined ? [] : ["--thinking", model.thinking]),
173
+ ];
136
174
  }
137
175
 
138
176
  function parsePublicCliConfig(value: unknown): PublicCliConfig {
@@ -20,6 +20,7 @@ import {
20
20
  type ParseDoctorArgvResult,
21
21
  } from "./invocation.ts";
22
22
  import {
23
+ buildSeatModelCliArgs,
23
24
  type CredentialProviders,
24
25
  type SeatModelConfig,
25
26
  } from "./config.ts";
@@ -70,17 +71,6 @@ export type DoctorRunEnv = {
70
71
  timeoutMs?: number;
71
72
  };
72
73
 
73
- function buildModelArgs(model: SeatModelConfig | undefined): string[] {
74
- if (model === undefined) return [];
75
- return [
76
- "--provider",
77
- model.provider,
78
- "--model",
79
- model.model,
80
- "--thinking",
81
- model.thinking,
82
- ];
83
- }
84
74
 
85
75
  /**
86
76
  * Build Internal activation extra-args for an admitted Doctor run.
@@ -111,7 +101,7 @@ export function buildDoctorActivationExtraArgs(
111
101
  admitted.caseRunsPath,
112
102
  "--mode",
113
103
  "json",
114
- ...buildModelArgs(options.model),
104
+ ...buildSeatModelCliArgs(options.model),
115
105
  prompt,
116
106
  ];
117
107
  }
@@ -183,7 +173,7 @@ async function dispatchAdmittedDoctor(input: {
183
173
  io,
184
174
  );
185
175
  }
186
- await markRunRunning(admitted.runDirectory);
176
+ await markRunRunning(admitted.runDirectory, env.model);
187
177
  await clearTypedProviderHttpObservation(admitted.runDirectory);
188
178
 
189
179
  const childEnv: NodeJS.ProcessEnv = {
@@ -319,6 +309,7 @@ export async function runPublicDoctor(
319
309
  ...(parsed.project === undefined ? {} : { project: parsed.project }),
320
310
  ...(parsed.runs === undefined ? {} : { runs: parsed.runs }),
321
311
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
312
+ ...(env.model === undefined ? {} : { model: env.model }),
322
313
  });
323
314
  } catch (error) {
324
315
  if (error instanceof CliUsageError) {
@@ -26,6 +26,7 @@ import {
26
26
  type AdmittedFixerInvocation,
27
27
  } from "./invocation.ts";
28
28
  import {
29
+ buildSeatModelCliArgs,
29
30
  type CredentialProviders,
30
31
  type SeatModelConfig,
31
32
  } from "./config.ts";
@@ -87,17 +88,6 @@ export type FixerRunEnv = {
87
88
  timeoutMs?: number;
88
89
  };
89
90
 
90
- function buildModelArgs(model: SeatModelConfig | undefined): string[] {
91
- if (model === undefined) return [];
92
- return [
93
- "--provider",
94
- model.provider,
95
- "--model",
96
- model.model,
97
- "--thinking",
98
- model.thinking,
99
- ];
100
- }
101
91
 
102
92
  /**
103
93
  * Build Internal activation extra-args for an admitted Fixer run.
@@ -145,7 +135,7 @@ export function buildFixerActivationExtraArgs(
145
135
  ...prerequisiteArgs,
146
136
  "--mode",
147
137
  "json",
148
- ...buildModelArgs(options.model),
138
+ ...buildSeatModelCliArgs(options.model),
149
139
  prompt,
150
140
  ];
151
141
  }
@@ -194,7 +184,7 @@ export function buildFixerResumeActivationExtraArgs(
194
184
  ...prerequisiteArgs,
195
185
  "--mode",
196
186
  "json",
197
- ...buildModelArgs(options.model),
187
+ ...buildSeatModelCliArgs(options.model),
198
188
  RESUME_TRANSPORT_ENVELOPE,
199
189
  ];
200
190
  }
@@ -303,7 +293,7 @@ async function dispatchAdmittedFixer(input: {
303
293
  io,
304
294
  );
305
295
  }
306
- await markRunRunning(admitted.runDirectory);
296
+ await markRunRunning(admitted.runDirectory, env.model);
307
297
  await clearTypedProviderHttpObservation(admitted.runDirectory);
308
298
 
309
299
  const childEnv: NodeJS.ProcessEnv = {
@@ -461,6 +451,7 @@ export async function runPublicFixer(
461
451
  : { prerequisitesPath: parsed.prerequisitesPath }),
462
452
  ...(parsed.project === undefined ? {} : { project: parsed.project }),
463
453
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
454
+ ...(env.model === undefined ? {} : { model: env.model }),
464
455
  });
465
456
  } catch (error) {
466
457
  if (error instanceof CliUsageError) {
@@ -172,14 +172,39 @@ type RoleInvocationLedgerSource = Pick<
172
172
  "runId" | "bookKey" | "projectRoot" | "runDirectory" | "sessionDirectory" | "sessionFile" | "correlationId" | "ticketNumber"
173
173
  >;
174
174
 
175
+ /**
176
+ * Effective provider/model selection recorded on the invocation identity page.
177
+ * thinking is present only when the caller/seat supplied it — bare model omits it.
178
+ */
179
+ export type InvocationEffectiveModel = {
180
+ readonly provider: string;
181
+ readonly model: string;
182
+ readonly thinking?: string;
183
+ };
184
+
185
+ /** Project effective model onto ledger fields; absent thinking stays absent. */
186
+ function effectiveModelLedgerFields(
187
+ model: InvocationEffectiveModel | undefined,
188
+ ): Record<string, string> {
189
+ if (model === undefined) return {};
190
+ return {
191
+ provider: model.provider,
192
+ model: model.model,
193
+ ...(model.thinking === undefined ? {} : { thinking: model.thinking }),
194
+ };
195
+ }
196
+
175
197
  /**
176
198
  * Persist one `invocation.json` identity page for the public run.
177
199
  * Admission is the sole source for every field; this is the only identity
178
200
  * projection and callers never provide an independent ledger shape.
201
+ * When an effective model is known at admission, provider/model (and thinking
202
+ * only when supplied) are written onto the same page.
179
203
  */
180
204
  async function writeRoleInvocationLedger(
181
205
  source: RoleInvocationLedgerSource,
182
206
  role: AdmittedRoleInvocation["role"],
207
+ effectiveModel?: InvocationEffectiveModel,
183
208
  ): Promise<void> {
184
209
  const identity = {
185
210
  role,
@@ -191,6 +216,7 @@ async function writeRoleInvocationLedger(
191
216
  sessionFile: source.sessionFile,
192
217
  ...(source.correlationId === undefined ? {} : { correlationId: source.correlationId }),
193
218
  ...(source.ticketNumber === undefined ? {} : { ticketNumber: source.ticketNumber }),
219
+ ...effectiveModelLedgerFields(effectiveModel),
194
220
  };
195
221
  await writeFile(
196
222
  join(source.runDirectory, "invocation.json"),
@@ -199,6 +225,37 @@ async function writeRoleInvocationLedger(
199
225
  );
200
226
  }
201
227
 
228
+ /**
229
+ * Merge the effective launch model onto the existing invocation identity page
230
+ * (resume / temporary override path — same field shape as admission write).
231
+ * Bare model clears any prior thinking key so absence stays honest.
232
+ */
233
+ export async function recordEffectiveInvocationModel(
234
+ runDirectory: string,
235
+ model: InvocationEffectiveModel,
236
+ ): Promise<void> {
237
+ const ledgerPath = join(runDirectory, "invocation.json");
238
+ const current = JSON.parse(await readFile(ledgerPath, "utf8")) as Record<
239
+ string,
240
+ unknown
241
+ >;
242
+ const next: Record<string, unknown> = {
243
+ ...current,
244
+ provider: model.provider,
245
+ model: model.model,
246
+ };
247
+ if (model.thinking === undefined) {
248
+ delete next.thinking;
249
+ } else {
250
+ next.thinking = model.thinking;
251
+ }
252
+ await writeFile(
253
+ ledgerPath,
254
+ `${JSON.stringify(next, null, 2)}\n`,
255
+ "utf8",
256
+ );
257
+ }
258
+
202
259
  /** Merge observed launch-time fields into the single existing invocation.json identity page. */
203
260
  async function mergeInvocationIdentityPage(
204
261
  runDirectory: string,
@@ -686,6 +743,8 @@ export type AdmitJudgeInvocationOptions = {
686
743
  project?: string;
687
744
  /** Injectable clock/id for tests. */
688
745
  createRunId?: () => string;
746
+ /** Effective model for this invocation — written onto invocation.json. */
747
+ model?: InvocationEffectiveModel;
689
748
  };
690
749
 
691
750
  /**
@@ -736,7 +795,7 @@ export async function admitJudgeInvocation(
736
795
  };
737
796
  const admittedRequestPath = join(runDirectory, "admitted-request.json");
738
797
  await writeFile(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}\n`, "utf8");
739
- await writeRoleInvocationLedger(admitted, admitted.role);
798
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
740
799
 
741
800
  return {
742
801
  role: "judge",
@@ -811,6 +870,8 @@ export type AdmitCoderInvocationOptions = {
811
870
  attachmentPaths: readonly string[];
812
871
  project?: string;
813
872
  createRunId?: () => string;
873
+ /** Effective model for this invocation — written onto invocation.json. */
874
+ model?: InvocationEffectiveModel;
814
875
  };
815
876
 
816
877
  /**
@@ -874,7 +935,7 @@ export async function admitCoderInvocation(
874
935
  };
875
936
  const admittedRequestPath = join(runDirectory, "admitted-request.json");
876
937
  await writeFile(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}\n`, "utf8");
877
- await writeRoleInvocationLedger(admitted, admitted.role);
938
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
878
939
 
879
940
  return {
880
941
  role: "coder",
@@ -923,6 +984,8 @@ export type AdmitFixerInvocationOptions = {
923
984
  prerequisitesPath?: string;
924
985
  project?: string;
925
986
  createRunId?: () => string;
987
+ /** Effective model for this invocation — written onto invocation.json. */
988
+ model?: InvocationEffectiveModel;
926
989
  };
927
990
 
928
991
  /**
@@ -1026,7 +1089,7 @@ export async function admitFixerInvocation(
1026
1089
  };
1027
1090
  const admittedRequestPath = join(runDirectory, "admitted-request.json");
1028
1091
  await writeFile(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}\n`, "utf8");
1029
- await writeRoleInvocationLedger(admitted, admitted.role);
1092
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
1030
1093
 
1031
1094
  return {
1032
1095
  role: "fixer",
@@ -1188,6 +1251,8 @@ export type AdmitCollectorInvocationOptions = {
1188
1251
  /** Optional public request configuration; copied into the admitted run. */
1189
1252
  requestManifestPath?: string;
1190
1253
  createRunId?: () => string;
1254
+ /** Effective model for this invocation — written onto invocation.json. */
1255
+ model?: InvocationEffectiveModel;
1191
1256
  };
1192
1257
 
1193
1258
  /**
@@ -1286,7 +1351,7 @@ export async function admitCollectorInvocation(
1286
1351
  `${JSON.stringify(admitted, null, 2)}\n`,
1287
1352
  "utf8",
1288
1353
  );
1289
- await writeRoleInvocationLedger(admitted, admitted.role);
1354
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
1290
1355
 
1291
1356
  return {
1292
1357
  role: "collector",
@@ -1440,6 +1505,8 @@ export type AdmitDoctorInvocationOptions = {
1440
1505
  attachmentPaths?: readonly string[];
1441
1506
  project?: string;
1442
1507
  createRunId?: () => string;
1508
+ /** Effective model for this invocation — written onto invocation.json. */
1509
+ model?: InvocationEffectiveModel;
1443
1510
  };
1444
1511
 
1445
1512
  /**
@@ -1619,7 +1686,7 @@ export async function admitDoctorInvocation(
1619
1686
  `${JSON.stringify(admitted, null, 2)}\n`,
1620
1687
  "utf8",
1621
1688
  );
1622
- await writeRoleInvocationLedger(admitted, admitted.role);
1689
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
1623
1690
 
1624
1691
  return {
1625
1692
  role: "doctor",
@@ -1730,6 +1797,8 @@ export type AdmitReviewerInvocationOptions = {
1730
1797
  authorityRefs?: readonly string[];
1731
1798
  project?: string;
1732
1799
  createRunId?: () => string;
1800
+ /** Effective model for this invocation — written onto invocation.json. */
1801
+ model?: InvocationEffectiveModel;
1733
1802
  };
1734
1803
 
1735
1804
  /**
@@ -1794,7 +1863,7 @@ export async function admitReviewerInvocation(
1794
1863
  `${JSON.stringify(admitted, null, 2)}\n`,
1795
1864
  "utf8",
1796
1865
  );
1797
- await writeRoleInvocationLedger(admitted, admitted.role);
1866
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
1798
1867
 
1799
1868
  return {
1800
1869
  role: "reviewer",
@@ -1945,6 +2014,8 @@ export type AdmitMergerInvocationOptions = {
1945
2014
  createRunId?: () => string;
1946
2015
  /** Test seam; production binds createProductionMergerGitState(projectRoot). */
1947
2016
  gitState?: MergerGitState;
2017
+ /** Effective model for this invocation — written onto invocation.json. */
2018
+ model?: InvocationEffectiveModel;
1948
2019
  };
1949
2020
 
1950
2021
  /**
@@ -2052,7 +2123,7 @@ export async function admitMergerInvocation(
2052
2123
  `${JSON.stringify(admitted, null, 2)}\n`,
2053
2124
  "utf8",
2054
2125
  );
2055
- await writeRoleInvocationLedger(admitted, admitted.role);
2126
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
2056
2127
 
2057
2128
  return {
2058
2129
  role: "merger",
@@ -19,6 +19,7 @@ import {
19
19
  type AdmittedJudgeInvocation,
20
20
  } from "./invocation.ts";
21
21
  import {
22
+ buildSeatModelCliArgs,
22
23
  type CredentialProviders,
23
24
  type SeatModelConfig,
24
25
  } from "./config.ts";
@@ -89,17 +90,6 @@ export type JudgeRunEnv = {
89
90
  };
90
91
 
91
92
 
92
- function buildModelArgs(model: SeatModelConfig | undefined): string[] {
93
- if (model === undefined) return [];
94
- return [
95
- "--provider",
96
- model.provider,
97
- "--model",
98
- model.model,
99
- "--thinking",
100
- model.thinking,
101
- ];
102
- }
103
93
 
104
94
  /**
105
95
  * Build Internal activation extra-args for an admitted Judge run
@@ -129,7 +119,7 @@ export function buildJudgeActivationExtraArgs(
129
119
  "judge",
130
120
  "--mode",
131
121
  "json",
132
- ...buildModelArgs(options.model),
122
+ ...buildSeatModelCliArgs(options.model),
133
123
  prompt,
134
124
  ];
135
125
  }
@@ -160,7 +150,7 @@ export function buildJudgeResumeActivationExtraArgs(
160
150
  "judge",
161
151
  "--mode",
162
152
  "json",
163
- ...buildModelArgs(options.model),
153
+ ...buildSeatModelCliArgs(options.model),
164
154
  RESUME_TRANSPORT_ENVELOPE,
165
155
  ];
166
156
  }
@@ -276,7 +266,7 @@ async function dispatchAdmittedJudge(input: {
276
266
  io,
277
267
  );
278
268
  }
279
- await markRunRunning(admitted.runDirectory);
269
+ await markRunRunning(admitted.runDirectory, env.model);
280
270
  // Attempt-scoped observation: drop any prior dispatch's 429 evidence so only
281
271
  // the current initial/resume attempt can qualify v1 resume.
282
272
  await clearTypedProviderHttpObservation(admitted.runDirectory);
@@ -422,6 +412,7 @@ export async function runPublicJudge(
422
412
  attachmentPaths: parsed.attachmentPaths,
423
413
  ...(parsed.project === undefined ? {} : { project: parsed.project }),
424
414
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
415
+ ...(env.model === undefined ? {} : { model: env.model }),
425
416
  });
426
417
  } catch (error) {
427
418
  if (error instanceof CliUsageError) {
@@ -29,6 +29,7 @@ import {
29
29
  type AdmittedMergerInvocation,
30
30
  } from "./invocation.ts";
31
31
  import {
32
+ buildSeatModelCliArgs,
32
33
  type CredentialProviders,
33
34
  type SeatModelConfig,
34
35
  } from "./config.ts";
@@ -88,17 +89,6 @@ export type MergerRunEnv = {
88
89
  timeoutMs?: number;
89
90
  };
90
91
 
91
- function buildModelArgs(model: SeatModelConfig | undefined): string[] {
92
- if (model === undefined) return [];
93
- return [
94
- "--provider",
95
- model.provider,
96
- "--model",
97
- model.model,
98
- "--thinking",
99
- model.thinking,
100
- ];
101
- }
102
92
 
103
93
  /**
104
94
  * Build Internal activation extra-args for an admitted Merger run.
@@ -136,7 +126,7 @@ export function buildMergerActivationExtraArgs(
136
126
  admitted.mergerInputPath,
137
127
  "--mode",
138
128
  "json",
139
- ...buildModelArgs(options.model),
129
+ ...buildSeatModelCliArgs(options.model),
140
130
  prompt,
141
131
  ];
142
132
  }
@@ -175,7 +165,7 @@ export function buildMergerResumeActivationExtraArgs(
175
165
  admitted.mergerInputPath,
176
166
  "--mode",
177
167
  "json",
178
- ...buildModelArgs(options.model),
168
+ ...buildSeatModelCliArgs(options.model),
179
169
  RESUME_TRANSPORT_ENVELOPE,
180
170
  ];
181
171
  }
@@ -299,7 +289,7 @@ async function dispatchAdmittedMerger(input: {
299
289
  io,
300
290
  );
301
291
  }
302
- await markRunRunning(admitted.runDirectory);
292
+ await markRunRunning(admitted.runDirectory, env.model);
303
293
  await clearTypedProviderHttpObservation(admitted.runDirectory);
304
294
 
305
295
  const childEnv: NodeJS.ProcessEnv = {
@@ -516,6 +506,7 @@ export async function runPublicMerger(
516
506
  attachmentPaths: parsed.attachmentPaths,
517
507
  ...(parsed.project === undefined ? {} : { project: parsed.project }),
518
508
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
509
+ ...(env.model === undefined ? {} : { model: env.model }),
519
510
  });
520
511
  } catch (error) {
521
512
  if (error instanceof CliUsageError) {
@@ -49,7 +49,8 @@ export type PublicThinkingLevel =
49
49
  export type ModelRef = {
50
50
  provider: string;
51
51
  model: string;
52
- thinking: PublicThinkingLevel;
52
+ /** Absent when invocation passed bare provider/model — pi/model default applies. */
53
+ thinking?: PublicThinkingLevel;
53
54
  };
54
55
 
55
56
  /**
@@ -27,6 +27,7 @@ import {
27
27
  type AdmittedReviewerInvocation,
28
28
  } from "./invocation.ts";
29
29
  import {
30
+ buildSeatModelCliArgs,
30
31
  type CredentialProviders,
31
32
  type SeatModelConfig,
32
33
  } from "./config.ts";
@@ -88,17 +89,6 @@ export type ReviewerRunEnv = {
88
89
  timeoutMs?: number;
89
90
  };
90
91
 
91
- function buildModelArgs(model: SeatModelConfig | undefined): string[] {
92
- if (model === undefined) return [];
93
- return [
94
- "--provider",
95
- model.provider,
96
- "--model",
97
- model.model,
98
- "--thinking",
99
- model.thinking,
100
- ];
101
- }
102
92
 
103
93
  /** Encode admitted ticketNumber as CLI argv for activation/resume (no defaults). */
104
94
  function buildReviewerTicketNumberArgs(
@@ -152,7 +142,7 @@ export function buildReviewerActivationExtraArgs(
152
142
  ...ticketNumberArgs,
153
143
  "--mode",
154
144
  "json",
155
- ...buildModelArgs(options.model),
145
+ ...buildSeatModelCliArgs(options.model),
156
146
  prompt,
157
147
  ];
158
148
  }
@@ -198,7 +188,7 @@ export function buildReviewerResumeActivationExtraArgs(
198
188
  ...ticketNumberArgs,
199
189
  "--mode",
200
190
  "json",
201
- ...buildModelArgs(options.model),
191
+ ...buildSeatModelCliArgs(options.model),
202
192
  RESUME_TRANSPORT_ENVELOPE,
203
193
  ];
204
194
  }
@@ -306,7 +296,7 @@ async function dispatchAdmittedReviewer(input: {
306
296
  io,
307
297
  );
308
298
  }
309
- await markRunRunning(admitted.runDirectory);
299
+ await markRunRunning(admitted.runDirectory, env.model);
310
300
  await clearTypedProviderHttpObservation(admitted.runDirectory);
311
301
  await clearReviewerDispatchRejection(admitted.runDirectory);
312
302
 
@@ -463,6 +453,7 @@ export async function runPublicReviewer(
463
453
  authorityRefs: parsed.authorityRefs,
464
454
  ...(parsed.project === undefined ? {} : { project: parsed.project }),
465
455
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
456
+ ...(env.model === undefined ? {} : { model: env.model }),
466
457
  });
467
458
  } catch (error) {
468
459
  if (error instanceof CliUsageError) {
@@ -24,6 +24,7 @@ export {
24
24
  import type { FixerPhase } from "../package-contracts/fixer-output.ts";
25
25
  import type { FixerPrerequisite } from "../package-contracts/fixer-packet.ts";
26
26
  import {
27
+ recordEffectiveInvocationModel,
27
28
  requireAuthorityRef,
28
29
  type AdmittedCoderInvocation,
29
30
  type AdmittedFixerInvocation,
@@ -34,6 +35,7 @@ import {
34
35
  type CoderPhase,
35
36
  type DerivedMergerEnvelope,
36
37
  type FrozenAttachment,
38
+ type InvocationEffectiveModel,
37
39
  } from "./invocation.ts";
38
40
 
39
41
  /** Providers eligible for v1 typed-429 resume (Codex / xAI only). */
@@ -226,7 +228,18 @@ export async function markRunAdmitted(
226
228
  });
227
229
  }
228
230
 
229
- export async function markRunRunning(runDirectory: string): Promise<void> {
231
+ /**
232
+ * Shared dispatch execution seam: record the effective launch model (initial or
233
+ * resume override) onto invocation.json when known, then transition to running.
234
+ * Role runners must not coordinate lifecycle ledger writes themselves.
235
+ */
236
+ export async function markRunRunning(
237
+ runDirectory: string,
238
+ effectiveModel?: InvocationEffectiveModel,
239
+ ): Promise<void> {
240
+ if (effectiveModel !== undefined) {
241
+ await recordEffectiveInvocationModel(runDirectory, effectiveModel);
242
+ }
230
243
  const current = await readRoleRunState(runDirectory);
231
244
  if (current === undefined) {
232
245
  throw new Error("cannot mark running: run state missing");
@@ -288,11 +288,15 @@ async function readInvocation(runDir: string): Promise<InvocationInfo | undefine
288
288
  const info: InvocationInfo = {};
289
289
  if (typeof parsed.role === "string" && parsed.role.trim()) info.role = parsed.role.trim();
290
290
  if (typeof parsed.thinking === "string" && parsed.thinking.trim()) info.thinking = parsed.thinking.trim();
291
+ if (typeof parsed.provider === "string" && parsed.provider.trim()) {
292
+ info.provider = parsed.provider.trim();
293
+ }
291
294
  if (typeof parsed.model === "string" && parsed.model.trim()) {
292
295
  const rawModel = parsed.model.trim();
293
296
  if (rawModel.includes("/")) {
294
297
  const slash = rawModel.indexOf("/");
295
- info.provider = rawModel.slice(0, slash);
298
+ // Combined provider/model form (legacy pages).
299
+ if (info.provider === undefined) info.provider = rawModel.slice(0, slash);
296
300
  info.model = rawModel.slice(slash + 1);
297
301
  } else {
298
302
  info.model = rawModel;