@akagilnc/pi-workflow-roles 0.1.3262 → 0.1.3280

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +8 -122
  2. package/README.zh-CN.md +9 -123
  3. package/dist/gatekeeper-role.js +22 -16
  4. package/dist/grok/production-host.js +745 -571
  5. package/dist/package-contracts/gatekeeper-output.js +71 -0
  6. package/dist/package-contracts/navigator-output.js +48 -0
  7. package/dist/package-contracts/terminating-tools.js +18 -2
  8. package/dist/packaged-role-registry.js +24 -0
  9. package/dist/public-cli/config.js +7 -21
  10. package/dist/public-cli/main.js +689 -365
  11. package/dist/public-cli/registry.js +2 -14
  12. package/dist/session-opening-materials.js +6 -11
  13. package/extensions/role-runtime.ts +3 -1
  14. package/package.json +1 -1
  15. package/resources/navigator-route-playbook.md +1 -1
  16. package/src/gatekeeper-role.ts +32 -17
  17. package/src/grok/production-host.ts +3 -1
  18. package/src/host-contracts.ts +3 -1
  19. package/src/navigator-role.ts +17 -0
  20. package/src/package-contracts/gatekeeper-output.ts +89 -0
  21. package/src/package-contracts/navigator-output.ts +59 -0
  22. package/src/package-contracts/terminating-tools.ts +24 -2
  23. package/src/packaged-role-registry.ts +25 -2
  24. package/src/pi/role-turn-host.ts +4 -0
  25. package/src/public-cli/cli.ts +37 -10
  26. package/src/public-cli/config.ts +6 -30
  27. package/src/public-cli/instruction-seat-run.ts +147 -0
  28. package/src/public-cli/invocation.ts +47 -3
  29. package/src/public-cli/option-definitions.ts +36 -3
  30. package/src/public-cli/registry.ts +3 -26
  31. package/src/public-cli/run-lifecycle.ts +8 -2
  32. package/src/public-cli/settlement.ts +115 -3
  33. package/src/public-cli/terminal.ts +3 -1
  34. package/src/role-runtime.ts +64 -1
  35. package/src/session-opening-materials.ts +6 -11
@@ -7,10 +7,8 @@ import { dirname, join } from "node:path";
7
7
  import { assertLegalEngineName } from "../package-resources/engine-material.ts";
8
8
  import { resolveConfiguredProvinceOfficer } from "../institutional-resolution.ts";
9
9
  import {
10
- AUTOMATIC_CONFIGURABLE_SEATS,
11
10
  PUBLIC_CALLABLE_ROLES,
12
11
  PUBLIC_CONFIGURABLE_SEATS,
13
- isAutomaticConfigurableSeat,
14
12
  isPublicCallableRole,
15
13
  isPublicConfigurableSeat,
16
14
  seatModelOnly,
@@ -87,8 +85,6 @@ export type HostSource = "invocation" | "persistent" | "default";
87
85
 
88
86
  export type EffectiveSeat = {
89
87
  seat: PublicConfigurableSeat;
90
- /** True when the seat is automatic (no caller command) rather than caller-selected. */
91
- automatic: boolean;
92
88
  source: EffectiveSource;
93
89
  selection?: SeatModelConfig;
94
90
  /** Selected engine name when configured; undefined = no engine (default path). */
@@ -240,7 +236,7 @@ export function setPersistentSeatHost(
240
236
  * First engine still requires an existing seat row (model, or residual axes).
241
237
  * Clearing engine from an axis-only residual drops the empty row; clearing
242
238
  * engine from a model+engine row leaves model-only. Seat type is PublicCallableRole
243
- * (navigator excluded at the type boundary).
239
+ * (same as PublicConfigurableSeat; navigator included since #639).
244
240
  */
245
241
  export function setPersistentSeatEngine(
246
242
  config: PublicCliConfig,
@@ -308,8 +304,8 @@ export function setAutoResumeLimit(
308
304
  /**
309
305
  * Config-parse seam: persistent call axes belong to PUBLIC_CALLABLE_ROLES;
310
306
  * engine names need only path-safety syntax (no closed material catalog;
311
- * #376 / #378 / #391 / ADR 0069). Disk-handwritten automatic-seat axes are
312
- * rejected. Syntax authority = assertLegalEngineName (no injected duplicate).
307
+ * #376 / #378 / #391 / ADR 0069). Syntax authority = assertLegalEngineName
308
+ * (no injected duplicate).
313
309
  */
314
310
  export function validatePublicCliConfigAxes(
315
311
  config: PublicCliConfig,
@@ -317,11 +313,6 @@ export function validatePublicCliConfigAxes(
317
313
  ): void {
318
314
  for (const seat of Object.keys(config.seats) as PublicConfigurableSeat[]) {
319
315
  const row = config.seats[seat];
320
- if ((row?.engine !== undefined || row?.host !== undefined) && !isPublicCallableRole(seat)) {
321
- throw new Error(
322
- `config seat ${seat} cannot persist call axes: no independent activation path; storing would be silently ineffective`,
323
- );
324
- }
325
316
  if (row?.engine === undefined) continue;
326
317
  try {
327
318
  assertLegalEngineName(row.engine);
@@ -622,13 +613,11 @@ function resolveBaseSeat(
622
613
  seat: PublicConfigurableSeat,
623
614
  credentials: CredentialProviders,
624
615
  ): UnhostedEffectiveSeat {
625
- const automatic = isAutomaticConfigurableSeat(seat);
626
616
  // #620: subordinate officers consume institutional-resolution authority result.
627
617
  if (seat === "notary" || seat === "inspector") {
628
618
  const resolved = resolveConfiguredProvinceOfficer(config, seat);
629
619
  return {
630
620
  seat,
631
- automatic,
632
621
  source: resolved.source,
633
622
  ...(resolved.selection === undefined ? {} : { selection: resolved.selection }),
634
623
  engineSource: "unconfigured",
@@ -639,7 +628,6 @@ function resolveBaseSeat(
639
628
  if (persistentModel !== undefined) {
640
629
  return {
641
630
  seat,
642
- automatic,
643
631
  source: "persistent",
644
632
  selection: persistentModel,
645
633
  engineSource: "unconfigured",
@@ -649,13 +637,12 @@ function resolveBaseSeat(
649
637
  if (startup !== undefined) {
650
638
  return {
651
639
  seat,
652
- automatic,
653
640
  source: "startup",
654
641
  selection: startup,
655
642
  engineSource: "unconfigured",
656
643
  };
657
644
  }
658
- return { seat, automatic, source: "unconfigured", engineSource: "unconfigured" };
645
+ return { seat, source: "unconfigured", engineSource: "unconfigured" };
659
646
  }
660
647
 
661
648
  export function resolveEffectiveSeat(
@@ -664,7 +651,6 @@ export function resolveEffectiveSeat(
664
651
  credentials: CredentialProviders,
665
652
  invocation?: InvocationModelOverride,
666
653
  ): EffectiveSeat {
667
- const automatic = isAutomaticConfigurableSeat(seat);
668
654
  const hasModelInvocation =
669
655
  invocation !== undefined &&
670
656
  (invocation.model !== undefined || invocation.thinking !== undefined);
@@ -679,7 +665,6 @@ export function resolveEffectiveSeat(
679
665
  : `${invocation.model}:${invocation.thinking}`;
680
666
  modelSeat = {
681
667
  seat,
682
- automatic,
683
668
  source: "invocation",
684
669
  selection: parseModelSpec(spec),
685
670
  engineSource: "unconfigured",
@@ -689,14 +674,12 @@ export function resolveEffectiveSeat(
689
674
  if (base.selection === undefined || invocation.thinking === undefined) {
690
675
  modelSeat = {
691
676
  seat,
692
- automatic,
693
677
  source: "unconfigured",
694
678
  engineSource: "unconfigured",
695
679
  };
696
680
  } else {
697
681
  modelSeat = {
698
682
  seat,
699
- automatic,
700
683
  source: "invocation",
701
684
  selection: { ...base.selection, thinking: invocation.thinking },
702
685
  engineSource: "unconfigured",
@@ -717,20 +700,13 @@ export function effectiveSeatConfigurations(
717
700
  );
718
701
  }
719
702
 
720
- /** Callable roles first (package order), then automatic configurable seats. */
703
+ /** Callable roles in package order. */
721
704
  export function listRolesForDisplay(
722
705
  config: PublicCliConfig,
723
706
  credentials: CredentialProviders,
724
707
  invocation?: InvocationModelOverride,
725
708
  ): EffectiveSeat[] {
726
- const all = effectiveSeatConfigurations(config, credentials, invocation);
727
- const callable = PUBLIC_CALLABLE_ROLES.map(
728
- (role) => all.find((entry) => entry.seat === role)!,
729
- );
730
- const automatic = AUTOMATIC_CONFIGURABLE_SEATS.map(
731
- (seat) => all.find((entry) => entry.seat === seat)!,
732
- );
733
- return [...callable, ...automatic];
709
+ return effectiveSeatConfigurations(config, credentials, invocation);
734
710
  }
735
711
 
736
712
  /**
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Shared instruction-seat run (#639 repair seam): gatekeeper and navigator are
3
+ * the same admit → turn-request → post-admission → settle shape, parameterized
4
+ * by seat. Same direction as the settlement one-shot shared skeleton. cli.ts
5
+ * calls this parameterized module directly; no per-seat wrappers.
6
+ */
7
+ import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
8
+ import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
9
+ import { CliUsageError } from "./cli-errors.ts";
10
+ import {
11
+ admitGatekeeperInvocation,
12
+ admitNavigatorInvocation,
13
+ buildInstructionTransportPrompt,
14
+ type AdmittedGatekeeperInvocation,
15
+ type AdmittedNavigatorInvocation,
16
+ type ParseInstructionArgvResult,
17
+ } from "./invocation.ts";
18
+ import {
19
+ runPostAdmissionOneShot,
20
+ type PostAdmissionEnv,
21
+ } from "./post-admission.ts";
22
+ import {
23
+ markRunAdmitted,
24
+ } from "./run-lifecycle.ts";
25
+ import {
26
+ presentStructuralRejection,
27
+ trySettleGatekeeperTerminalResult,
28
+ trySettleNavigatorTerminalResult,
29
+ } from "./settlement.ts";
30
+ import type { CliIo } from "./cli-io.ts";
31
+ import type { TerminalResult } from "./terminal.ts";
32
+ import {
33
+ projectRoleTurnRequest,
34
+ type RoleTurnRequestProjectionOptions,
35
+ } from "./turn-request.ts";
36
+
37
+ export type InstructionSeatRole = "gatekeeper" | "navigator";
38
+
39
+ export type AdmittedInstructionSeatInvocation =
40
+ | AdmittedGatekeeperInvocation
41
+ | AdmittedNavigatorInvocation;
42
+
43
+ export type InstructionSeatRunEnv = PostAdmissionEnv & {
44
+ principalAuthority: DurablePrincipalAuthority;
45
+ createRunId?: () => string;
46
+ };
47
+
48
+ /** Project an admitted instruction-seat invocation onto the host-neutral turn request. */
49
+ export function buildInstructionSeatTurnRequest(
50
+ admitted: AdmittedInstructionSeatInvocation,
51
+ options: RoleTurnRequestProjectionOptions,
52
+ ): RoleTurnRequest {
53
+ return projectRoleTurnRequest(
54
+ admitted,
55
+ {
56
+ activation: {
57
+ role: admitted.role,
58
+ },
59
+ },
60
+ options,
61
+ );
62
+ }
63
+
64
+ function instructionSeatAdapters() {
65
+ return {
66
+ trySettle: (
67
+ admitted: AdmittedInstructionSeatInvocation,
68
+ authority: DurablePrincipalAuthority,
69
+ ) =>
70
+ admitted.role === "gatekeeper"
71
+ ? trySettleGatekeeperTerminalResult(admitted, authority)
72
+ : trySettleNavigatorTerminalResult(admitted, authority),
73
+ // Accepted receipts and failure terminals both present via shared path.
74
+ shouldPresentSettled: () => true,
75
+ };
76
+ }
77
+
78
+ export async function runPublicInstructionSeat(
79
+ argv: readonly string[],
80
+ env: InstructionSeatRunEnv,
81
+ io: CliIo,
82
+ role: InstructionSeatRole,
83
+ parseArgv: (args: readonly string[]) => ParseInstructionArgvResult,
84
+ ): Promise<{
85
+ exitCode: number;
86
+ admitted?: AdmittedInstructionSeatInvocation;
87
+ terminal?: TerminalResult;
88
+ }> {
89
+ let admitted: AdmittedInstructionSeatInvocation;
90
+ try {
91
+ const parsed = parseArgv(argv);
92
+ const options = {
93
+ home: env.home,
94
+ principalAuthority: env.principalAuthority,
95
+ cwd: env.cwd,
96
+ instruction: parsed.instruction,
97
+ attachmentPaths: parsed.attachmentPaths,
98
+ ...(parsed.project === undefined ? {} : { project: parsed.project }),
99
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
100
+ ...(env.model === undefined ? {} : { model: env.model }),
101
+ ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
102
+ };
103
+ admitted =
104
+ role === "gatekeeper"
105
+ ? await admitGatekeeperInvocation(options)
106
+ : await admitNavigatorInvocation(options);
107
+ } catch (error) {
108
+ if (error instanceof CliUsageError) {
109
+ presentStructuralRejection(error, io);
110
+ return { exitCode: 2 };
111
+ }
112
+ throw error;
113
+ }
114
+
115
+ await markRunAdmitted(admitted, env.principalAuthority);
116
+
117
+ const turnRequest = buildInstructionSeatTurnRequest(admitted, {
118
+ packageRoot: env.packageRoot,
119
+ home: env.home,
120
+ agentDir: env.agentDir,
121
+ ...(env.model === undefined ? {} : { model: env.model }),
122
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
123
+ ...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
124
+ ...(env.correlationId === undefined || env.correlationId.trim() === ""
125
+ ? {}
126
+ : { correlationId: env.correlationId }),
127
+ continuation: {
128
+ kind: "initial",
129
+ prompt: buildInstructionTransportPrompt(
130
+ admitted,
131
+ engineSessionMaterialFromOptions({
132
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
133
+ packageRoot: env.packageRoot,
134
+ }),
135
+ ),
136
+ },
137
+ });
138
+
139
+ return await runPostAdmissionOneShot({
140
+ admitted,
141
+ env,
142
+ io,
143
+ request: turnRequest,
144
+ adapters: instructionSeatAdapters(),
145
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
146
+ });
147
+ }
@@ -136,6 +136,14 @@ export type AdmittedInspectorInvocation = AdmittedRoleInvocationBase & {
136
136
  readonly role: "inspector";
137
137
  };
138
138
 
139
+ export type AdmittedGatekeeperInvocation = AdmittedRoleInvocationBase & {
140
+ readonly role: "gatekeeper";
141
+ };
142
+
143
+ export type AdmittedNavigatorInvocation = AdmittedRoleInvocationBase & {
144
+ readonly role: "navigator";
145
+ };
146
+
139
147
  export type CoderPhase = "plan" | "apply";
140
148
 
141
149
  export type AdmittedCoderInvocation = AdmittedRoleInvocationBase & {
@@ -217,6 +225,8 @@ export type AdmittedRoleInvocation =
217
225
  | AdmittedCountersignInvocation
218
226
  | AdmittedGleanerLeftInvocation
219
227
  | AdmittedInspectorInvocation
228
+ | AdmittedGatekeeperInvocation
229
+ | AdmittedNavigatorInvocation
220
230
  | AdmittedCoderInvocation
221
231
  | AdmittedFixerInvocation
222
232
  | AdmittedCollectorInvocation
@@ -535,6 +545,8 @@ export type ParseInstructionArgvResult = {
535
545
  export type ParseJudgeArgvResult = ParseInstructionArgvResult;
536
546
  export type ParseCountersignArgvResult = ParseInstructionArgvResult;
537
547
  export type ParseInspectorArgvResult = ParseInstructionArgvResult;
548
+ export type ParseGatekeeperArgvResult = ParseInstructionArgvResult;
549
+ export type ParseNavigatorArgvResult = ParseInstructionArgvResult;
538
550
 
539
551
  /** Positive ticket number shared by countersign/notary/analyst faces. */
540
552
  export function parsePositiveTicketNumber(
@@ -555,7 +567,7 @@ export function parsePositiveTicketNumber(
555
567
  /** 共享解析体:同形 owner 的 argv → instruction/attachments/project。 */
556
568
  function parseInstructionArgv(
557
569
  args: readonly string[],
558
- owner: "judge" | "countersign" | "inspector",
570
+ owner: "judge" | "countersign" | "inspector" | "gatekeeper" | "navigator",
559
571
  ): ParseInstructionArgvResult {
560
572
  const attachmentPaths: string[] = [];
561
573
  let project: string | undefined;
@@ -799,6 +811,14 @@ export function parseInspectorArgv(args: readonly string[]): ParseInspectorArgvR
799
811
  return parseInstructionArgv(args, "inspector");
800
812
  }
801
813
 
814
+ export function parseGatekeeperArgv(args: readonly string[]): ParseGatekeeperArgvResult {
815
+ return parseInstructionArgv(args, "gatekeeper");
816
+ }
817
+
818
+ export function parseNavigatorArgv(args: readonly string[]): ParseNavigatorArgvResult {
819
+ return parseInstructionArgv(args, "navigator");
820
+ }
821
+
802
822
  /**
803
823
  * Parse Coder-specific argv after the `coder` token.
804
824
  * Phase defaults to apply; spellings from PUBLIC_OPTION_TABLE.coder (#342).
@@ -988,6 +1008,9 @@ export type AdmitInspectorInvocationOptions = AdmitJudgeInvocationOptions & {
988
1008
  correlationId?: string;
989
1009
  };
990
1010
 
1011
+ export type AdmitGatekeeperInvocationOptions = AdmitInspectorInvocationOptions;
1012
+ export type AdmitNavigatorInvocationOptions = AdmitInspectorInvocationOptions;
1013
+
991
1014
  /**
992
1015
  * Shared instruction-seat admission for Judge and Inspector: project check,
993
1016
  * principal/placement issue, attachment freeze, ticket extract, admitted-request
@@ -995,7 +1018,7 @@ export type AdmitInspectorInvocationOptions = AdmitJudgeInvocationOptions & {
995
1018
  * (Inspector). Countersign keeps its own path for ticket-override specialty.
996
1019
  */
997
1020
  async function admitStandardMaterialInvocation<
998
- R extends "judge" | "inspector",
1021
+ R extends "judge" | "inspector" | "gatekeeper" | "navigator",
999
1022
  >(
1000
1023
  role: R,
1001
1024
  options: AdmitJudgeInvocationOptions & { correlationId?: string },
@@ -1101,8 +1124,28 @@ export async function admitInspectorInvocation(
1101
1124
  return admitStandardMaterialInvocation("inspector", options);
1102
1125
  }
1103
1126
 
1127
+ /**
1128
+ * Admit a direct Gatekeeper (门下省) run (#639): freeze attachments, persist
1129
+ * the request, reserve session placement — same instruction-seat face.
1130
+ */
1131
+ export async function admitGatekeeperInvocation(
1132
+ options: AdmitGatekeeperInvocationOptions,
1133
+ ): Promise<AdmittedGatekeeperInvocation> {
1134
+ return admitStandardMaterialInvocation("gatekeeper", options);
1135
+ }
1136
+
1137
+ /**
1138
+ * Admit a direct Navigator (游奕使) run (#639): freeze attachments, persist
1139
+ * the request, reserve session placement — same instruction-seat face.
1140
+ */
1141
+ export async function admitNavigatorInvocation(
1142
+ options: AdmitNavigatorInvocationOptions,
1143
+ ): Promise<AdmittedNavigatorInvocation> {
1144
+ return admitStandardMaterialInvocation("navigator", options);
1145
+ }
1146
+
1104
1147
  /** Shared prompt transport for instruction-seat roles (judge/countersign/inspector). */
1105
- function buildInstructionTransportPrompt(
1148
+ export function buildInstructionTransportPrompt(
1106
1149
  admitted: { instruction: string; instructionEmpty: boolean; attachments: readonly { frozenPath: string }[] },
1107
1150
  engineMaterial?: EngineSessionMaterial,
1108
1151
  ): string {
@@ -1132,6 +1175,7 @@ export function buildInspectorTransportPrompt(
1132
1175
  return buildInstructionTransportPrompt(admitted, engineMaterial);
1133
1176
  }
1134
1177
 
1178
+
1135
1179
  export type AdmitCountersignInvocationOptions = {
1136
1180
  home: string;
1137
1181
  cwd: string;
@@ -32,6 +32,8 @@ export type OptionOwner =
32
32
  | "notary"
33
33
  | "gleaner-left"
34
34
  | "inspector"
35
+ | "gatekeeper"
36
+ | "navigator"
35
37
  | "analyst";
36
38
 
37
39
  /**
@@ -410,6 +412,16 @@ const INSPECTOR_OPTIONS = [
410
412
  bindOwner("inspector", SHARED_ATTACH_SEMANTICS),
411
413
  ] as const satisfies readonly PublicOptionDefinition[];
412
414
 
415
+ const GATEKEEPER_OPTIONS = [
416
+ bindOwner("gatekeeper", SHARED_PROJECT_SEMANTICS),
417
+ bindOwner("gatekeeper", SHARED_ATTACH_SEMANTICS),
418
+ ] as const satisfies readonly PublicOptionDefinition[];
419
+
420
+ const NAVIGATOR_OPTIONS = [
421
+ bindOwner("navigator", SHARED_PROJECT_SEMANTICS),
422
+ bindOwner("navigator", SHARED_ATTACH_SEMANTICS),
423
+ ] as const satisfies readonly PublicOptionDefinition[];
424
+
413
425
  const COUNTERSIGN_OPTIONS = [
414
426
  bindOwner("countersign", SHARED_PROJECT_SEMANTICS),
415
427
  bindOwner("countersign", SHARED_ATTACH_SEMANTICS),
@@ -798,6 +810,8 @@ export const PUBLIC_OPTION_TABLE = {
798
810
  merger: MERGER_OPTIONS,
799
811
  notary: NOTARY_OPTIONS,
800
812
  inspector: INSPECTOR_OPTIONS,
813
+ gatekeeper: GATEKEEPER_OPTIONS,
814
+ navigator: NAVIGATOR_OPTIONS,
801
815
  analyst: ANALYST_OPTIONS,
802
816
  } as const satisfies Record<OptionOwner, readonly PublicOptionDefinition[]>;
803
817
 
@@ -816,6 +830,8 @@ export const PUBLIC_ROLE_OPTION_OWNERS = [
816
830
  "merger",
817
831
  "notary",
818
832
  "inspector",
833
+ "gatekeeper",
834
+ "navigator",
819
835
  "analyst",
820
836
  ] as const satisfies readonly PublicRoleOptionOwner[];
821
837
 
@@ -1089,11 +1105,12 @@ export type PublicCommandHelpFacts = {
1089
1105
  };
1090
1106
 
1091
1107
  /**
1092
- * Top-level public help short note for automatic Navigator attendance.
1093
- * Not a caller command; configure via `ak-role config set navigator …`.
1108
+ * Top-level public help short note for Navigator automatic attendance (#639:
1109
+ * attendance is orthogonal to callability — the direct command face lives on
1110
+ * the role list like every other role).
1094
1111
  */
1095
1112
  export const PUBLIC_NAVIGATOR_HELP_NOTE =
1096
- "Navigator attends automatically on every run; configure with `ak-role config set navigator <provider/model[:thinking]>` (not a caller command)." as const;
1113
+ "Navigator also attends automatically on every run; that sidecar is unchanged by direct `ak-role navigator` calls." as const;
1097
1114
 
1098
1115
  const TOP_LEVEL_HELP = {
1099
1116
  command: "top",
@@ -1196,6 +1213,22 @@ const ROLE_COMMAND_HELP = {
1196
1213
  'ak-role inspector --attach ./change.patch "Review this material."',
1197
1214
  ],
1198
1215
  },
1216
+ gatekeeper: {
1217
+ command: "gatekeeper",
1218
+ summary: "Direct Gatekeeper (门下省) review: dispatch an officer or pass.",
1219
+ usage: ["ak-role gatekeeper [options] [instruction]"],
1220
+ examples: [
1221
+ 'ak-role gatekeeper --attach ./submission.json "审:这批材料该谁审?"',
1222
+ ],
1223
+ },
1224
+ navigator: {
1225
+ command: "navigator",
1226
+ summary: "Direct Navigator (游奕使) route advice: ordered next-role candidates.",
1227
+ usage: ["ak-role navigator [options] [instruction]"],
1228
+ examples: [
1229
+ 'ak-role navigator "刚完成 coder apply 收敛,下一步?"',
1230
+ ],
1231
+ },
1199
1232
  notary: {
1200
1233
  command: "notary",
1201
1234
  summary: "Direct Notary document check (quote fidelity + ticket alignment); zero prompt/attachment.",
@@ -16,36 +16,12 @@ export const PUBLIC_CALLABLE_ROLES = PACKAGED_ROLE_REGISTRY.map(
16
16
 
17
17
  export type PublicCallableRole = (typeof PUBLIC_CALLABLE_ROLES)[number];
18
18
 
19
- /** Automatic attendance seat — configurable, never a caller-selected command. */
20
- export const AUTOMATIC_NAVIGATOR_SEAT = "navigator" as const;
21
-
22
- /** Automatic province seat — configurable model only; never a caller command. */
23
- export const AUTOMATIC_GATEKEEPER_SEAT = "gatekeeper" as const;
24
-
25
- /** Automatic-only configurable seats. Inspector remains automatic-capable via its callable seat. */
26
- export const AUTOMATIC_CONFIGURABLE_SEATS = [
27
- AUTOMATIC_GATEKEEPER_SEAT,
28
- AUTOMATIC_NAVIGATOR_SEAT,
29
- ] as const;
30
-
31
- export type AutomaticConfigurableSeat =
32
- (typeof AUTOMATIC_CONFIGURABLE_SEATS)[number];
33
-
34
- export type PublicConfigurableSeat =
35
- | PublicCallableRole
36
- | AutomaticConfigurableSeat;
19
+ export type PublicConfigurableSeat = PublicCallableRole;
37
20
 
38
21
  export const PUBLIC_CONFIGURABLE_SEATS = [
39
22
  ...PUBLIC_CALLABLE_ROLES,
40
- ...AUTOMATIC_CONFIGURABLE_SEATS,
41
23
  ] as const satisfies readonly PublicConfigurableSeat[];
42
24
 
43
- export function isAutomaticConfigurableSeat(
44
- value: string,
45
- ): value is AutomaticConfigurableSeat {
46
- return (AUTOMATIC_CONFIGURABLE_SEATS as readonly string[]).includes(value);
47
- }
48
-
49
25
  export const PUBLIC_CLI_SUPPORT_COMMANDS = [
50
26
  "roles",
51
27
  "config",
@@ -127,7 +103,8 @@ const STARTUP_CANDIDATES: Record<PublicConfigurableSeat, readonly ModelRef[]> =
127
103
  ],
128
104
  // #620: subordinate officers inherit gatekeeper; no package startup model.
129
105
  notary: [],
130
- // #453/#620: gatekeeper unset inherits audited session (province path only).
106
+ // #453/#620/#639: gatekeeper callable but no package startup model — caller
107
+ // configures it or the institutional resolution applies on the province path.
131
108
  gatekeeper: [],
132
109
  inspector: [],
133
110
  navigator: [
@@ -84,7 +84,9 @@ export type RoleRunRecord = {
84
84
  | "notary"
85
85
  | "countersign"
86
86
  | "gleaner-left"
87
- | "inspector";
87
+ | "inspector"
88
+ | "gatekeeper"
89
+ | "navigator";
88
90
  readonly state: RoleRunState;
89
91
  readonly bookKey: string;
90
92
  readonly projectRoot: string;
@@ -244,7 +246,9 @@ async function readRoleRunStateDisk(
244
246
  record.role !== "notary" &&
245
247
  record.role !== "countersign" &&
246
248
  record.role !== "gleaner-left" &&
247
- record.role !== "inspector"
249
+ record.role !== "inspector" &&
250
+ record.role !== "gatekeeper" &&
251
+ record.role !== "navigator"
248
252
  ) {
249
253
  return undefined;
250
254
  }
@@ -1475,6 +1479,8 @@ export async function peekRoleRunRole(
1475
1479
  | "countersign"
1476
1480
  | "gleaner-left"
1477
1481
  | "inspector"
1482
+ | "gatekeeper"
1483
+ | "navigator"
1478
1484
  | undefined
1479
1485
  > {
1480
1486
  const runDirectory = await findRunDirectoryById(home, runId);