@akagilnc/pi-workflow-roles 0.1.4259 → 0.1.4274

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.
@@ -23,8 +23,10 @@ import {
23
23
  isGateOfficerSeat,
24
24
  loadCredentialProviders,
25
25
  loadPublicCliConfig,
26
+ missingResolvedSeatModelMessage,
26
27
  parseModelSpec,
27
28
  resolveEffectiveSeat,
29
+ resolvedSeatWithModel,
28
30
  savePublicCliConfig,
29
31
  setAutoResumeLimit,
30
32
  setPersistentSeatConfig,
@@ -316,6 +318,8 @@ type RoleEnvironmentOptions = {
316
318
  function createRoleEnvironment(
317
319
  env: CliEnv,
318
320
  options: RoleEnvironmentOptions,
321
+ /** #178: after host selection, before missing-model — structural argv parse once. */
322
+ afterHost?: () => void,
319
323
  ) {
320
324
  const role = options.role;
321
325
  const extraPiArgs =
@@ -355,13 +359,7 @@ function createRoleEnvironment(
355
359
  ? env.notaryTimeoutMs
356
360
  : undefined;
357
361
 
358
- // #617 DK-3: resume and new legs share one seat resolution — model/host/engine
359
- // come from the live seat table (invocation flag → persistent → default).
360
- // #788 expectation 2: select a registered host first; only then project the
361
- // host-facing provider (table > unique directory > fail). Bad host never
362
- // reaches model resolution.
363
- // Nested summons reuse this table and select by the child seat — never the
364
- // already-selected parent adapter (#840 / ADR 0082: --host 旗标>席位配置>缺省 pi).
362
+ // #617/#178/#788/#840: host first, then model; nested child seat selects own host.
365
363
  const hostAdapters = composeRoleTurnHostAdapters(
366
364
  {
367
365
  packageRoot: env.packageRoot,
@@ -376,15 +374,21 @@ function createRoleEnvironment(
376
374
  role,
377
375
  seat: options.seat,
378
376
  });
379
- const hostFacingSelection =
380
- options.seat.selection === undefined
381
- ? undefined
382
- : projectHostFacingProvider(
383
- options.seat.selection,
384
- options.seat.host,
385
- loadHostProvidersTable(options.home),
386
- options.home,
387
- );
377
+ // #178: host → argv (afterHost) → missing-model → provider projection.
378
+ afterHost?.();
379
+ const seatWithModel = resolvedSeatWithModel(options.seat);
380
+ if (seatWithModel === undefined) {
381
+ // Direct CLI has --model; dual remediation is intentional here (#178 / #916).
382
+ throw new CliUsageError(
383
+ missingResolvedSeatModelMessage(options.seat.seat, "invocation-or-config"),
384
+ );
385
+ }
386
+ const hostFacingSelection = projectHostFacingProvider(
387
+ seatWithModel.selection,
388
+ seatWithModel.host,
389
+ loadHostProvidersTable(options.home),
390
+ options.home,
391
+ );
388
392
  return {
389
393
  home: options.home,
390
394
  principalAuthority: env.principalAuthority!,
@@ -408,6 +412,52 @@ function createRoleEnvironment(
408
412
  };
409
413
  }
410
414
 
415
+ /**
416
+ * #178 single spine: resolve seat → host select → argv parse once → missing-model → run.
417
+ * Keeps host-unregistered ahead of bad argv and missing-model.
418
+ */
419
+ async function dispatchPublicRoleCommand<TParsed>(
420
+ env: CliEnv,
421
+ home: string,
422
+ io: CliIo,
423
+ parsed: ParsedGlobal,
424
+ role: PublicCallableRole,
425
+ parse: (args: readonly string[]) => TParsed,
426
+ run: (
427
+ args: readonly string[],
428
+ roleEnv: ReturnType<typeof createRoleEnvironment>,
429
+ parseOnce: () => TParsed,
430
+ ) => Promise<{ exitCode: number; terminal?: TerminalResult }>,
431
+ ): Promise<CliResult> {
432
+ const agentDir = resolveAgentDir(env, home);
433
+ const cwd = env.cwd ?? process.cwd();
434
+ const config = await loadAndValidateConfig(home, env.packageRoot);
435
+ const credentials =
436
+ env.credentials ?? (await loadCredentialProviders(agentDir));
437
+ const seat = resolveEffectiveSeat(
438
+ config,
439
+ role,
440
+ credentials,
441
+ invocationFromParsed(parsed),
442
+ );
443
+ let parsedRoleArgv!: TParsed;
444
+ const result = await run(
445
+ parsed.args,
446
+ createRoleEnvironment(
447
+ env,
448
+ { role, home, agentDir, cwd, credentials, seat, config },
449
+ () => {
450
+ parsedRoleArgv = parse(parsed.args);
451
+ },
452
+ ),
453
+ () => parsedRoleArgv,
454
+ );
455
+ return {
456
+ exitCode: result.exitCode,
457
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
458
+ };
459
+ }
460
+
411
461
  export type CliResult = {
412
462
  exitCode: number;
413
463
  /** Settled Terminal when an admitted Role run produced one (programmatic/tests). */
@@ -744,6 +794,7 @@ function renderHelp(): string {
744
794
  "Persistent engine (callable roles): ak-role config set-engine <seat> <name> [model] | unset-engine <seat> | set-engine-model <seat> <model> | unset-engine-model <seat>",
745
795
  "Persistent host (callable roles): ak-role config set-host <seat> <name> | unset-host <seat>",
746
796
  "Host providers: ~/.ak-roles/host-providers.json (owner-edited; table > unique host directory > fail)",
797
+ "Model resolution: --model → persistent seat → officer inherit (#620); still none → error (#178)",
747
798
  "Host resolution: --host → persistent seat host → pi (resume uses the same order; #617)",
748
799
  "Resume flag position: --model/--thinking/--host/--engine go before <runId> (before `resume` or between `resume` and <runId>); the one argv after <runId> is always the opaque message, never a flag (#471)",
749
800
  "Effective seats: ak-role roles",
@@ -788,7 +839,7 @@ function renderRoles(seats: readonly EffectiveSeat[]): string {
788
839
 
789
840
  /**
790
841
  * #620 typed config display row. Non-subordinate seats stay on the persistent
791
- * disk face; notary/inspector may surface inherit-gatekeeper without startup.
842
+ * disk face; notary/inspector may surface inherit-gatekeeper.
792
843
  * Presentation formats these fields; tests assert the projection, not TSV.
793
844
  * source reuses the institutional authority domain — no parallel union.
794
845
  */
@@ -814,8 +865,8 @@ function diskAxes(disk: PublicCliConfig["seats"][PublicConfigurableSeat]): {
814
865
 
815
866
  /**
816
867
  * Single-seat config projection (#620).
817
- * - notary/inspector: institutional authority result (own > gatekeeper), never startup
818
- * - all other seats: disk model only (persistent face; no startup fill-in)
868
+ * - notary/inspector: institutional authority result (own > gatekeeper)
869
+ * - all other seats: disk model only (persistent face; no package fill-in, #178)
819
870
  */
820
871
  export function projectConfigSeatDisplay(
821
872
  config: PublicCliConfig,
@@ -1240,9 +1291,10 @@ export async function runAkRole(
1240
1291
  resumeRole === undefined
1241
1292
  ? RESUME_SEAT_DISPATCH.judge
1242
1293
  : RESUME_SEAT_DISPATCH[resumeRole];
1243
- // #617 DK-4: resume resolves model/host/engine from the live seat table
1244
- // exactly as a new leg would (flag → persistent → default). Cross-host
1245
- // resume delivers prior native records as context to the target host.
1294
+ // #617 DK-4 / #178: resume resolves model/host/engine from the live seat
1295
+ // table exactly as a new leg would (flag → persistent → officer inherit;
1296
+ // model still none → error). Cross-host resume delivers prior native
1297
+ // records as context to the target host.
1246
1298
  const seat = resolveEffectiveSeat(
1247
1299
  config,
1248
1300
  dispatch.seat,
@@ -1272,333 +1324,89 @@ export async function runAkRole(
1272
1324
  throw new CliUsageError(`unhandled support command: ${parsed.command}`);
1273
1325
  }
1274
1326
 
1275
- // Judge is the first complete public Role run path (#106).
1327
+ // Public LLM role commands share one #178 spine (host → argv once → missing-model → run).
1276
1328
  if (parsed.command === "judge") {
1277
- const agentDir = resolveAgentDir(env, home);
1278
- const cwd = env.cwd ?? process.cwd();
1279
- const config = await loadAndValidateConfig(home, env.packageRoot);
1280
- const credentials =
1281
- env.credentials ?? (await loadCredentialProviders(agentDir));
1282
- const seat = resolveEffectiveSeat(
1283
- config,
1284
- "judge",
1285
- credentials,
1286
- invocationFromParsed(parsed),
1329
+ return await dispatchPublicRoleCommand(
1330
+ env, home, io, parsed, "judge", PUBLIC_ROLE_ARGV.judge.parse,
1331
+ (args, roleEnv, once) => runPublicJudge(args, roleEnv, io, once),
1287
1332
  );
1288
- const result = await runPublicJudge(
1289
- parsed.args,
1290
- createRoleEnvironment(env, { role: "judge", home, agentDir, cwd, credentials, seat, config }),
1291
- io,
1292
- PUBLIC_ROLE_ARGV.judge.parse,
1293
- );
1294
- return {
1295
- exitCode: result.exitCode,
1296
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1297
- };
1298
1333
  }
1299
-
1300
- // Countersign ticket-court run path (#572 / ADR 0074); #599 resume allowed.
1301
1334
  if (parsed.command === "countersign") {
1302
- const agentDir = resolveAgentDir(env, home);
1303
- const cwd = env.cwd ?? process.cwd();
1304
- const config = await loadAndValidateConfig(home, env.packageRoot);
1305
- const credentials =
1306
- env.credentials ?? (await loadCredentialProviders(agentDir));
1307
- const seat = resolveEffectiveSeat(
1308
- config,
1309
- "countersign",
1310
- credentials,
1311
- invocationFromParsed(parsed),
1335
+ return await dispatchPublicRoleCommand(
1336
+ env, home, io, parsed, "countersign", PUBLIC_ROLE_ARGV.countersign.parse,
1337
+ (args, roleEnv, once) => runPublicCountersign(args, roleEnv, io, once),
1312
1338
  );
1313
- const result = await runPublicCountersign(
1314
- parsed.args,
1315
- createRoleEnvironment(env, { role: "countersign", home, agentDir, cwd, credentials, seat, config }),
1316
- io,
1317
- PUBLIC_ROLE_ARGV.countersign.parse,
1318
- );
1319
- return {
1320
- exitCode: result.exitCode,
1321
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1322
- };
1323
1339
  }
1324
-
1325
- // Gleaner-Left pre-merge memorial run path (#502 / ADR 0067); #599 resume allowed.
1326
1340
  if (parsed.command === "gleaner-left") {
1327
- const agentDir = resolveAgentDir(env, home);
1328
- const cwd = env.cwd ?? process.cwd();
1329
- const config = await loadAndValidateConfig(home, env.packageRoot);
1330
- const credentials =
1331
- env.credentials ?? (await loadCredentialProviders(agentDir));
1332
- const seat = resolveEffectiveSeat(
1333
- config,
1334
- "gleaner-left",
1335
- credentials,
1336
- invocationFromParsed(parsed),
1337
- );
1338
- const result = await runPublicGleanerLeft(
1339
- parsed.args,
1340
- createRoleEnvironment(env, { role: "gleaner-left", home, agentDir, cwd, credentials, seat, config }),
1341
- io,
1342
- PUBLIC_ROLE_ARGV["gleaner-left"].parse,
1341
+ return await dispatchPublicRoleCommand(
1342
+ env, home, io, parsed, "gleaner-left", PUBLIC_ROLE_ARGV["gleaner-left"].parse,
1343
+ (args, roleEnv, once) => runPublicGleanerLeft(args, roleEnv, io, once),
1343
1344
  );
1344
- return {
1345
- exitCode: result.exitCode,
1346
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1347
- };
1348
1345
  }
1349
-
1350
- // Diarist public run path (#708): 起居郎 is summoned like any other seat.
1351
1346
  if (parsed.command === "diarist") {
1352
- const agentDir = resolveAgentDir(env, home);
1353
- const cwd = env.cwd ?? process.cwd();
1354
- const config = await loadAndValidateConfig(home, env.packageRoot);
1355
- const credentials =
1356
- env.credentials ?? (await loadCredentialProviders(agentDir));
1357
- const seat = resolveEffectiveSeat(
1358
- config,
1359
- "diarist",
1360
- credentials,
1361
- invocationFromParsed(parsed),
1347
+ return await dispatchPublicRoleCommand(
1348
+ env, home, io, parsed, "diarist", PUBLIC_ROLE_ARGV.diarist.parse,
1349
+ (args, roleEnv, once) => runPublicDiarist(args, roleEnv, io, once),
1362
1350
  );
1363
- const result = await runPublicDiarist(
1364
- parsed.args,
1365
- createRoleEnvironment(env, { role: "diarist", home, agentDir, cwd, credentials, seat, config }),
1366
- io,
1367
- PUBLIC_ROLE_ARGV.diarist.parse,
1368
- );
1369
- return {
1370
- exitCode: result.exitCode,
1371
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1372
- };
1373
1351
  }
1374
-
1375
- // Coder public run path with package-owned TDD method (#109).
1376
1352
  if (parsed.command === "coder") {
1377
- const agentDir = resolveAgentDir(env, home);
1378
- const cwd = env.cwd ?? process.cwd();
1379
- const config = await loadAndValidateConfig(home, env.packageRoot);
1380
- const credentials =
1381
- env.credentials ?? (await loadCredentialProviders(agentDir));
1382
- const seat = resolveEffectiveSeat(
1383
- config,
1384
- "coder",
1385
- credentials,
1386
- invocationFromParsed(parsed),
1387
- );
1388
- const result = await runPublicCoder(
1389
- parsed.args,
1390
- createRoleEnvironment(env, { role: "coder", home, agentDir, cwd, credentials, seat, config }),
1391
- io,
1392
- PUBLIC_ROLE_ARGV.coder.parse,
1353
+ return await dispatchPublicRoleCommand(
1354
+ env, home, io, parsed, "coder", PUBLIC_ROLE_ARGV.coder.parse,
1355
+ (args, roleEnv, once) => runPublicCoder(args, roleEnv, io, once),
1393
1356
  );
1394
- return {
1395
- exitCode: result.exitCode,
1396
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1397
- };
1398
1357
  }
1399
-
1400
- // Fixer public run path with optional package-owned diagnosing-bugs (#110).
1401
1358
  if (parsed.command === "fixer") {
1402
- const agentDir = resolveAgentDir(env, home);
1403
- const cwd = env.cwd ?? process.cwd();
1404
- const config = await loadAndValidateConfig(home, env.packageRoot);
1405
- const credentials =
1406
- env.credentials ?? (await loadCredentialProviders(agentDir));
1407
- const seat = resolveEffectiveSeat(
1408
- config,
1409
- "fixer",
1410
- credentials,
1411
- invocationFromParsed(parsed),
1412
- );
1413
- const result = await runPublicFixer(
1414
- parsed.args,
1415
- createRoleEnvironment(env, { role: "fixer", home, agentDir, cwd, credentials, seat, config }),
1416
- io,
1417
- PUBLIC_ROLE_ARGV.fixer.parse,
1359
+ return await dispatchPublicRoleCommand(
1360
+ env, home, io, parsed, "fixer", PUBLIC_ROLE_ARGV.fixer.parse,
1361
+ (args, roleEnv, once) => runPublicFixer(args, roleEnv, io, once),
1418
1362
  );
1419
- return {
1420
- exitCode: result.exitCode,
1421
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1422
- };
1423
1363
  }
1424
-
1425
- // Collector public run path from explicit PR + leg declarations (#112).
1426
1364
  if (parsed.command === "collector") {
1427
- const agentDir = resolveAgentDir(env, home);
1428
- const cwd = env.cwd ?? process.cwd();
1429
- const config = await loadAndValidateConfig(home, env.packageRoot);
1430
- const credentials =
1431
- env.credentials ?? (await loadCredentialProviders(agentDir));
1432
- const seat = resolveEffectiveSeat(
1433
- config,
1434
- "collector",
1435
- credentials,
1436
- invocationFromParsed(parsed),
1365
+ return await dispatchPublicRoleCommand(
1366
+ env, home, io, parsed, "collector", PUBLIC_ROLE_ARGV.collector.parse,
1367
+ (args, roleEnv, once) => runPublicCollector(args, roleEnv, io, once),
1437
1368
  );
1438
- const result = await runPublicCollector(
1439
- parsed.args,
1440
- createRoleEnvironment(env, { role: "collector", home, agentDir, cwd, credentials, seat, config }),
1441
- io,
1442
- PUBLIC_ROLE_ARGV.collector.parse,
1443
- );
1444
- return {
1445
- exitCode: result.exitCode,
1446
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1447
- };
1448
1369
  }
1449
-
1450
- // Reviewer public run path with package-owned adapted code-review (#111).
1451
1370
  if (parsed.command === "reviewer") {
1452
- const agentDir = resolveAgentDir(env, home);
1453
- const cwd = env.cwd ?? process.cwd();
1454
- const config = await loadAndValidateConfig(home, env.packageRoot);
1455
- const credentials =
1456
- env.credentials ?? (await loadCredentialProviders(agentDir));
1457
- const seat = resolveEffectiveSeat(
1458
- config,
1459
- "reviewer",
1460
- credentials,
1461
- invocationFromParsed(parsed),
1371
+ return await dispatchPublicRoleCommand(
1372
+ env, home, io, parsed, "reviewer", PUBLIC_ROLE_ARGV.reviewer.parse,
1373
+ (args, roleEnv, once) => runPublicReviewer(args, roleEnv, io, once),
1462
1374
  );
1463
- const result = await runPublicReviewer(
1464
- parsed.args,
1465
- createRoleEnvironment(env, { role: "reviewer", home, agentDir, cwd, credentials, seat, config }),
1466
- io,
1467
- PUBLIC_ROLE_ARGV.reviewer.parse,
1468
- );
1469
- return {
1470
- exitCode: result.exitCode,
1471
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1472
- };
1473
1375
  }
1474
-
1475
- // Doctor public run path from Issue identity + optional confined runs root (#113).
1476
1376
  if (parsed.command === "doctor") {
1477
- const agentDir = resolveAgentDir(env, home);
1478
- const cwd = env.cwd ?? process.cwd();
1479
- const config = await loadAndValidateConfig(home, env.packageRoot);
1480
- const credentials =
1481
- env.credentials ?? (await loadCredentialProviders(agentDir));
1482
- const seat = resolveEffectiveSeat(
1483
- config,
1484
- "doctor",
1485
- credentials,
1486
- invocationFromParsed(parsed),
1487
- );
1488
- const result = await runPublicDoctor(
1489
- parsed.args,
1490
- createRoleEnvironment(env, { role: "doctor", home, agentDir, cwd, credentials, seat, config }),
1491
- io,
1492
- PUBLIC_ROLE_ARGV.doctor.parse,
1377
+ return await dispatchPublicRoleCommand(
1378
+ env, home, io, parsed, "doctor", PUBLIC_ROLE_ARGV.doctor.parse,
1379
+ (args, roleEnv, once) => runPublicDoctor(args, roleEnv, io, once),
1493
1380
  );
1494
- return {
1495
- exitCode: result.exitCode,
1496
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1497
- };
1498
1381
  }
1499
-
1500
- // Notary public run path: source-run locator only, direct officer seat (#448).
1501
1382
  if (parsed.command === "notary") {
1502
- const agentDir = resolveAgentDir(env, home);
1503
- const cwd = env.cwd ?? process.cwd();
1504
- const config = await loadAndValidateConfig(home, env.packageRoot);
1505
- const credentials =
1506
- env.credentials ?? (await loadCredentialProviders(agentDir));
1507
- const seat = resolveEffectiveSeat(
1508
- config,
1509
- "notary",
1510
- credentials,
1511
- invocationFromParsed(parsed),
1383
+ return await dispatchPublicRoleCommand(
1384
+ env, home, io, parsed, "notary", PUBLIC_ROLE_ARGV.notary.parse,
1385
+ (args, roleEnv, once) => runPublicNotary(args, roleEnv, io, once),
1512
1386
  );
1513
- const result = await runPublicNotary(
1514
- parsed.args,
1515
- createRoleEnvironment(env, { role: "notary", home, agentDir, cwd, credentials, seat, config }),
1516
- io,
1517
- PUBLIC_ROLE_ARGV.notary.parse,
1518
- );
1519
- return {
1520
- exitCode: result.exitCode,
1521
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1522
- };
1523
1387
  }
1524
-
1525
1388
  if (parsed.command === "inspector") {
1526
- const agentDir = resolveAgentDir(env, home);
1527
- const cwd = env.cwd ?? process.cwd();
1528
- const config = await loadAndValidateConfig(home, env.packageRoot);
1529
- const credentials =
1530
- env.credentials ?? (await loadCredentialProviders(agentDir));
1531
- const seat = resolveEffectiveSeat(
1532
- config,
1533
- "inspector",
1534
- credentials,
1535
- invocationFromParsed(parsed),
1536
- );
1537
- const result = await runPublicInspector(
1538
- parsed.args,
1539
- createRoleEnvironment(env, { role: "inspector", home, agentDir, cwd, credentials, seat, config }),
1540
- io,
1541
- PUBLIC_ROLE_ARGV.inspector.parse,
1389
+ return await dispatchPublicRoleCommand(
1390
+ env, home, io, parsed, "inspector", PUBLIC_ROLE_ARGV.inspector.parse,
1391
+ (args, roleEnv, once) => runPublicInspector(args, roleEnv, io, once),
1542
1392
  );
1543
- return {
1544
- exitCode: result.exitCode,
1545
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1546
- };
1547
1393
  }
1548
-
1549
- // Merger public run path: derive active-merge envelope + forced merge-only method (#114).
1550
1394
  if (parsed.command === "merger") {
1551
- const agentDir = resolveAgentDir(env, home);
1552
- const cwd = env.cwd ?? process.cwd();
1553
- const config = await loadAndValidateConfig(home, env.packageRoot);
1554
- const credentials =
1555
- env.credentials ?? (await loadCredentialProviders(agentDir));
1556
- const seat = resolveEffectiveSeat(
1557
- config,
1558
- "merger",
1559
- credentials,
1560
- invocationFromParsed(parsed),
1395
+ return await dispatchPublicRoleCommand(
1396
+ env, home, io, parsed, "merger", PUBLIC_ROLE_ARGV.merger.parse,
1397
+ (args, roleEnv, once) => runPublicMerger(args, roleEnv, io, once),
1561
1398
  );
1562
- const result = await runPublicMerger(
1563
- parsed.args,
1564
- createRoleEnvironment(env, { role: "merger", home, agentDir, cwd, credentials, seat, config }),
1565
- io,
1566
- PUBLIC_ROLE_ARGV.merger.parse,
1567
- );
1568
- return {
1569
- exitCode: result.exitCode,
1570
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1571
- };
1572
1399
  }
1573
-
1574
- // Instruction seats (#639 / #675): gatekeeper / navigator / auditor.
1575
1400
  if (
1576
1401
  parsed.command === "gatekeeper"
1577
1402
  || parsed.command === "navigator"
1578
1403
  || parsed.command === "auditor"
1579
1404
  ) {
1580
- const agentDir = resolveAgentDir(env, home);
1581
- const cwd = env.cwd ?? process.cwd();
1582
- const config = await loadAndValidateConfig(home, env.packageRoot);
1583
- const credentials =
1584
- env.credentials ?? (await loadCredentialProviders(agentDir));
1585
- const seat = resolveEffectiveSeat(
1586
- config,
1587
- parsed.command,
1588
- credentials,
1589
- invocationFromParsed(parsed),
1590
- );
1591
- const result = await runPublicInstructionSeat(
1592
- parsed.args,
1593
- createRoleEnvironment(env, { role: parsed.command, home, agentDir, cwd, credentials, seat, config }),
1594
- io,
1595
- parsed.command,
1596
- PUBLIC_ROLE_ARGV[parsed.command].parse,
1405
+ const seat = parsed.command;
1406
+ return await dispatchPublicRoleCommand(
1407
+ env, home, io, parsed, seat, PUBLIC_ROLE_ARGV[seat].parse,
1408
+ (args, roleEnv, once) => runPublicInstructionSeat(args, roleEnv, io, seat, once),
1597
1409
  );
1598
- return {
1599
- exitCode: result.exitCode,
1600
- ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1601
- };
1602
1410
  }
1603
1411
 
1604
1412
  // Analyst public run path: deterministic analysis seat (#336 issue / #337 sweep).
@@ -21,7 +21,6 @@ import {
21
21
  type PublicCallableRole,
22
22
  type PublicConfigurableSeat,
23
23
  type PublicThinkingLevel,
24
- publicStartupCandidates,
25
24
  } from "./registry.ts";
26
25
 
27
26
  /** Province officers that may carry a persistent model override (#453). */
@@ -81,7 +80,6 @@ export type PublicCliConfig = {
81
80
 
82
81
  export type EffectiveSource =
83
82
  | "persistent"
84
- | "startup"
85
83
  | "invocation"
86
84
  | "inherit-gatekeeper"
87
85
  | "unconfigured";
@@ -625,16 +623,36 @@ export function missingPublicProviderCredential(
625
623
  return !providerConfigured(credentials, provider);
626
624
  }
627
625
 
628
- function pickStartupCandidate(
626
+ /** Effective seat with model axis present (#178). */
627
+ export type EffectiveSeatWithModel = EffectiveSeat & {
628
+ selection: NonNullable<EffectiveSeat["selection"]>;
629
+ };
630
+
631
+ /** #178: narrow seat with model, or undefined — callers own presentation. */
632
+ export function resolvedSeatWithModel(
633
+ seat: EffectiveSeat,
634
+ ): EffectiveSeatWithModel | undefined {
635
+ if (seat.selection === undefined) return undefined;
636
+ return seat as EffectiveSeatWithModel;
637
+ }
638
+
639
+ /**
640
+ * #178 shared missing-model wording; throw site stays per entry.
641
+ * Recommend `--model` only when the entry actually has an invocation-model channel
642
+ * (direct public CLI). Nested summons / navigator seat reads do not — config set only.
643
+ */
644
+ export function missingResolvedSeatModelMessage(
629
645
  seat: PublicConfigurableSeat,
630
- credentials: CredentialProviders,
631
- ): SeatModelConfig | undefined {
632
- for (const candidate of publicStartupCandidates(seat)) {
633
- if (providerConfigured(credentials, candidate.provider)) {
634
- return { ...candidate };
635
- }
646
+ remediation: "config" | "invocation-or-config" = "config",
647
+ ): string {
648
+ const configSet = `ak-role config set ${seat} <provider/model[:thinking]>`;
649
+ if (remediation === "invocation-or-config") {
650
+ return (
651
+ `seat ${seat} has no model configured; set with --model <provider/model[:thinking]>` +
652
+ ` or ${configSet}`
653
+ );
636
654
  }
637
- return undefined;
655
+ return `seat ${seat} has no model configured; set with ${configSet}`;
638
656
  }
639
657
 
640
658
  type UnhostedEffectiveSeat = Omit<EffectiveSeat, "host" | "hostSource">;
@@ -695,7 +713,6 @@ function attachEngineAxis(
695
713
  function resolveBaseSeat(
696
714
  config: PublicCliConfig,
697
715
  seat: PublicConfigurableSeat,
698
- credentials: CredentialProviders,
699
716
  ): UnhostedEffectiveSeat {
700
717
  // #620: subordinate officers consume institutional-resolution authority result.
701
718
  if (seat === "notary" || seat === "inspector") {
@@ -708,6 +725,7 @@ function resolveBaseSeat(
708
725
  };
709
726
  }
710
727
  // Engine-only residual is not a persistent model source (#453).
728
+ // #178: no package startup candidates — caller specifies via seat table or --model.
711
729
  const persistentModel = seatModelOnly(config.seats[seat]);
712
730
  if (persistentModel !== undefined) {
713
731
  return {
@@ -717,15 +735,6 @@ function resolveBaseSeat(
717
735
  engineSource: "unconfigured",
718
736
  };
719
737
  }
720
- const startup = pickStartupCandidate(seat, credentials);
721
- if (startup !== undefined) {
722
- return {
723
- seat,
724
- source: "startup",
725
- selection: startup,
726
- engineSource: "unconfigured",
727
- };
728
- }
729
738
  return { seat, source: "unconfigured", engineSource: "unconfigured" };
730
739
  }
731
740
 
@@ -739,9 +748,12 @@ export function resolveEffectiveSeat(
739
748
  invocation !== undefined &&
740
749
  (invocation.model !== undefined || invocation.thinking !== undefined);
741
750
 
751
+ // credentials retained on the public resolve surface for call-site stability;
752
+ // model axis no longer consults them (#178 deleted package startup fallback).
753
+ void credentials;
742
754
  let modelSeat: UnhostedEffectiveSeat;
743
755
  if (!hasModelInvocation || invocation === undefined) {
744
- modelSeat = resolveBaseSeat(config, seat, credentials);
756
+ modelSeat = resolveBaseSeat(config, seat);
745
757
  } else if (invocation.model !== undefined) {
746
758
  const spec =
747
759
  invocation.model.includes(":") || invocation.thinking === undefined
@@ -754,7 +766,7 @@ export function resolveEffectiveSeat(
754
766
  engineSource: "unconfigured",
755
767
  };
756
768
  } else {
757
- const base = resolveBaseSeat(config, seat, credentials);
769
+ const base = resolveBaseSeat(config, seat);
758
770
  if (base.selection === undefined || invocation.thinking === undefined) {
759
771
  modelSeat = {
760
772
  seat,
@@ -801,7 +813,7 @@ export function listRolesForDisplay(
801
813
 
802
814
  /**
803
815
  * Read configured credential presence from a Pi auth.json document.
804
- * Only presence of a provider key matters for startup selection (#11).
816
+ * Presence drives the selected-provider fail-closed seam (public-run-credentials).
805
817
  */
806
818
  export function credentialProvidersFromAuthData(
807
819
  data: unknown,