@akagilnc/pi-workflow-roles 0.1.3565 → 0.1.3621

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 (142) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +18477 -8099
  4. package/dist/analyst-gate-cycles-read.js +387 -0
  5. package/dist/archivist-record-entry.js +22 -1
  6. package/dist/audit-escalation.js +6 -0
  7. package/dist/auditor-soul.js +74 -0
  8. package/dist/collector-config.js +86 -0
  9. package/dist/collector-evidence.js +316 -0
  10. package/dist/collector-github.js +527 -0
  11. package/dist/collector-ledger.js +853 -0
  12. package/dist/collector-tool-schemas.js +33 -0
  13. package/dist/compliance-transport.js +104 -52
  14. package/dist/diarist-contracts.js +72 -0
  15. package/dist/diarist-mechanical.js +411 -0
  16. package/dist/diarist-ticket-resolution.js +177 -0
  17. package/dist/diarist.js +311 -0
  18. package/dist/doctor-auditor.js +25 -0
  19. package/dist/doctor-contracts.js +2 -0
  20. package/dist/doctor-evidence.js +142 -0
  21. package/dist/gatekeeper-role.js +104 -81
  22. package/dist/host-transition-prior-native.js +78 -0
  23. package/dist/institutional-resolution.js +1 -95
  24. package/dist/judge-auditor.js +26 -0
  25. package/dist/ledger-session-read.js +227 -0
  26. package/dist/merger-git-state.js +78 -0
  27. package/dist/navigator-attendance.js +15 -7
  28. package/dist/navigator-public-session.js +170 -0
  29. package/dist/navigator-session-contracts.js +36 -40
  30. package/dist/notary-source-run.js +122 -0
  31. package/dist/package-contracts/auditor-output.js +66 -0
  32. package/dist/package-contracts/evidence-child-output.js +34 -0
  33. package/dist/package-contracts/judge-output.js +2 -0
  34. package/dist/package-contracts/terminating-tools.js +44 -3
  35. package/dist/package-resources/method-skill.js +254 -0
  36. package/dist/packaged-role-registry.js +56 -0
  37. package/dist/pi/durable-principal.js +61 -0
  38. package/dist/pi/in-process-session.js +25 -6
  39. package/dist/pi/known-failure.js +52 -0
  40. package/dist/pi/role-turn-host.js +430 -0
  41. package/dist/public-cli/auto-resume.js +414 -0
  42. package/dist/public-cli/cli-errors.js +8 -0
  43. package/dist/public-cli/cli-io.js +1 -0
  44. package/dist/public-cli/command-renderer.js +5 -0
  45. package/dist/public-cli/doctor-run.js +84 -0
  46. package/dist/public-cli/inspector-run.js +136 -0
  47. package/dist/public-cli/instruction-seat-run.js +256 -0
  48. package/dist/public-cli/invocation.js +2225 -0
  49. package/dist/public-cli/judge-run.js +118 -0
  50. package/dist/public-cli/load-production-acp-host.js +40 -0
  51. package/dist/public-cli/main.js +1511 -1124
  52. package/dist/public-cli/notary-run.js +161 -0
  53. package/dist/public-cli/option-definitions.js +1192 -0
  54. package/dist/public-cli/post-admission.js +667 -0
  55. package/dist/public-cli/public-run-credentials.js +49 -0
  56. package/dist/public-cli/registry.js +8 -2
  57. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  58. package/dist/public-cli/run-lifecycle.js +1344 -0
  59. package/dist/public-cli/seat-ticket-binding.js +113 -0
  60. package/dist/public-cli/settlement.js +3441 -0
  61. package/dist/public-cli/terminal.js +135 -0
  62. package/dist/public-cli/turn-request.js +25 -0
  63. package/dist/public-role-summons.js +301 -0
  64. package/dist/receipt-delivery-policy.js +10 -0
  65. package/dist/reviewer-child-executor.js +80 -8
  66. package/dist/reviewer-execution-ledger.js +2 -1
  67. package/dist/run-terminal-artifacts.js +195 -0
  68. package/dist/run-ticket-number.js +40 -0
  69. package/dist/session-assistant-usage.js +107 -0
  70. package/dist/shape-unreadable-failure.js +34 -0
  71. package/dist/submission-errors.js +3 -0
  72. package/dist/submission-ledger.js +388 -0
  73. package/dist/ticket-provenance-contracts.js +115 -0
  74. package/dist/ticket-provenance.js +340 -0
  75. package/extensions/role-runtime.ts +8 -0
  76. package/package.json +1 -1
  77. package/resources/diarist-collect.md +6 -10
  78. package/scripts/build-package.mjs +2 -1
  79. package/souls/diarist.md +9 -0
  80. package/souls/doctor-auditor.md +1 -0
  81. package/souls/evidence-child.md +1 -0
  82. package/src/acp-host/production-host.ts +4 -0
  83. package/src/acp-host/role-envelope.ts +4 -3
  84. package/src/acp-host/role-turn-host.ts +9 -1
  85. package/src/analyst-gate-cycles-read.ts +37 -2
  86. package/src/archivist-record-entry.ts +45 -1
  87. package/src/audit-escalation.ts +17 -0
  88. package/src/auditor-role.ts +22 -0
  89. package/src/auditor-soul.ts +42 -0
  90. package/src/compliance-transport.ts +177 -66
  91. package/src/diarist-contracts.ts +88 -0
  92. package/src/diarist-role.ts +60 -0
  93. package/src/diarist.ts +253 -179
  94. package/src/doctor-auditor.ts +10 -14
  95. package/src/doctor-contracts.ts +1 -0
  96. package/src/doctor-role.ts +2 -2
  97. package/src/evidence-child-role.ts +22 -0
  98. package/src/gatekeeper-pass-envelope.ts +109 -0
  99. package/src/gatekeeper-role.ts +145 -104
  100. package/src/host-contracts.ts +15 -2
  101. package/src/institutional-resolution.ts +8 -163
  102. package/src/judge-auditor.ts +10 -15
  103. package/src/judge-role.ts +8 -0
  104. package/src/navigator-attendance.ts +25 -9
  105. package/src/navigator-public-session.ts +217 -0
  106. package/src/navigator-session-contracts.ts +61 -54
  107. package/src/notary-source-run.ts +3 -1
  108. package/src/package-contracts/auditor-output.ts +82 -0
  109. package/src/package-contracts/evidence-child-output.ts +51 -0
  110. package/src/package-contracts/judge-output.ts +1 -0
  111. package/src/package-contracts/reviewer-output.ts +2 -2
  112. package/src/package-contracts/terminating-tools.ts +53 -3
  113. package/src/packaged-role-registry.ts +63 -0
  114. package/src/pi/adapter.ts +2 -1
  115. package/src/pi/in-process-session.ts +38 -12
  116. package/src/pi/role-turn-host.ts +42 -0
  117. package/src/public-cli/cli.ts +43 -6
  118. package/src/public-cli/countersign-run.ts +9 -129
  119. package/src/public-cli/diarist-run.ts +312 -0
  120. package/src/public-cli/instruction-seat-run.ts +289 -50
  121. package/src/public-cli/invocation.ts +87 -25
  122. package/src/public-cli/option-definitions.ts +79 -0
  123. package/src/public-cli/post-admission.ts +9 -3
  124. package/src/public-cli/registry.ts +7 -1
  125. package/src/public-cli/run-lifecycle.ts +53 -6
  126. package/src/public-cli/settlement.ts +170 -7
  127. package/src/public-cli/terminal.ts +4 -1
  128. package/src/public-role-summons.ts +431 -0
  129. package/src/receipt-delivery-policy.ts +10 -0
  130. package/src/reviewer-agent.ts +1 -1
  131. package/src/reviewer-child-executor.ts +114 -12
  132. package/src/reviewer-execution-ledger.ts +3 -2
  133. package/src/role-runtime.ts +275 -23
  134. package/src/session-assistant-usage.ts +119 -0
  135. package/src/session-opening-materials.ts +1 -1
  136. package/src/shape-unreadable-failure.ts +49 -0
  137. package/src/submission-errors.ts +3 -0
  138. package/src/ticket-provenance-contracts.ts +1 -0
  139. package/src/ticket-provenance.ts +0 -23
  140. package/dist/evidence-child-executor.js +0 -829
  141. package/src/diarist-llm-collector.ts +0 -324
  142. package/src/evidence-child-executor.ts +0 -1140
@@ -1,6 +1,9 @@
1
- import { readFile, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import type { RoleTurnModelConfig } from "./host-contracts.ts";
1
+ /**
2
+ * Province-officer model authority (#453/#620) and shared seatModelOnly projection.
3
+ * #675 deleted the independent institutional-resolution.json seat page and the
4
+ * child-session open path that consumed it. Seat resolution for every activation
5
+ * is the live public seat table (model/host/engine).
6
+ */
4
7
  import type {
5
8
  GateOfficerSeat,
6
9
  PublicCliConfig,
@@ -8,70 +11,20 @@ import type {
8
11
  } from "./public-cli/config.ts";
9
12
  import { seatModelOnly } from "./public-cli/registry.ts";
10
13
 
11
- export const INSTITUTIONAL_RESOLUTION_FILE = "institutional-resolution.json" as const;
12
-
13
- /** Non-secret per-seat model selection — alias of the single host-neutral RoleTurnModelConfig. */
14
- export type InstitutionalSeatSelection = RoleTurnModelConfig;
15
-
16
14
  /**
17
15
  * Configured province-officer resolution (#453/#620 authority seam):
18
16
  * officer persistent > gatekeeper persistent > unconfigured.
19
- * No startup candidates and no parent-session fallback — parent compose stays
20
- * in resolveInstitutionalSeatSelections; direct call and config display consume
21
- * this typed result as-is.
17
+ * No startup candidates and no parent-session fallback — direct call and config
18
+ * display consume this typed result as-is.
22
19
  */
23
20
  export type ConfiguredProvinceOfficerResolution = {
24
21
  readonly selection?: SeatModelConfig;
25
22
  readonly source: "persistent" | "inherit-gatekeeper" | "unconfigured";
26
23
  };
27
24
 
28
- export type InstitutionalResolutionPage = {
29
- readonly version: 1;
30
- readonly seats: {
31
- readonly gatekeeper?: InstitutionalSeatSelection;
32
- readonly inspector?: InstitutionalSeatSelection;
33
- readonly notary?: InstitutionalSeatSelection;
34
- readonly auditor?: InstitutionalSeatSelection;
35
- readonly evidenceChild?: InstitutionalSeatSelection;
36
- readonly [seat: string]: InstitutionalSeatSelection | undefined;
37
- };
38
- };
39
-
40
- /** Why institutional seat read failed — structured, not free-text. */
41
- export type InstitutionalResolutionFailureReason =
42
- | "missing-page"
43
- | "missing-seat"
44
- | "corrupted"
45
- | "invalid-format";
46
-
47
- export class InstitutionalResolutionError extends Error {
48
- readonly reason: InstitutionalResolutionFailureReason;
49
- constructor(
50
- message: string,
51
- reason: InstitutionalResolutionFailureReason,
52
- options?: ErrorOptions,
53
- ) {
54
- super(message, options);
55
- this.name = "InstitutionalResolutionError";
56
- this.reason = reason;
57
- }
58
- }
59
-
60
- function cleanSelection(model?: { provider?: string; model?: string; thinking?: string }): InstitutionalSeatSelection | undefined {
61
- if (model === undefined) return undefined;
62
- if (typeof model.provider !== "string" || typeof model.model !== "string") return undefined;
63
- return {
64
- provider: model.provider,
65
- model: model.model,
66
- ...(model.thinking === undefined ? {} : { thinking: model.thinking }),
67
- };
68
- }
69
-
70
25
  /**
71
26
  * Authority for configured province-officer model+source (#453/#620):
72
27
  * own persistent > gatekeeper persistent > unconfigured.
73
- * Shared by resolveInstitutionalSeatSelections (plus parent fallback),
74
- * resolveEffectiveSeat subordinate branch, and roles/config display.
75
28
  * Model-axis projection stays seatModelOnly (single implementation in registry).
76
29
  */
77
30
  export function resolveConfiguredProvinceOfficer(
@@ -91,111 +44,3 @@ export function resolveConfiguredProvinceOfficer(
91
44
  }
92
45
  return { source: "unconfigured" };
93
46
  }
94
-
95
- /**
96
- * Resolve effective per-seat institutional selections (#518 §2 Hop 1; #620):
97
- * - gatekeeper / inspector / notary: resolveConfiguredProvinceOfficer then
98
- * parent effective on the province path only
99
- * - auditor / evidenceChild: parent effective
100
- * - navigator: explicit config seat only. Navigator model authority stays
101
- * `navigator-model.json`; the page never carries a parent-inherited navigator
102
- * seat, so host-neutral transport cannot shadow that setting (#590 Out of Scope:
103
- * 席位表/模型路由变更归 owner 域).
104
- */
105
- export function resolveInstitutionalSeatSelections(
106
- config: PublicCliConfig,
107
- parentEffectiveModel?: { provider: string; model: string; thinking?: string },
108
- ): InstitutionalResolutionPage {
109
- const parentSelection = cleanSelection(parentEffectiveModel);
110
-
111
- const gatekeeper =
112
- resolveConfiguredProvinceOfficer(config, "gatekeeper").selection ?? parentSelection;
113
- const inspector =
114
- resolveConfiguredProvinceOfficer(config, "inspector").selection ?? parentSelection;
115
- const notary =
116
- resolveConfiguredProvinceOfficer(config, "notary").selection ?? parentSelection;
117
- const auditor = parentSelection;
118
- const evidenceChild = parentSelection;
119
- const navigator = cleanSelection(seatModelOnly(config.seats?.navigator));
120
-
121
- return {
122
- version: 1,
123
- seats: {
124
- ...(gatekeeper === undefined ? {} : { gatekeeper }),
125
- ...(inspector === undefined ? {} : { inspector }),
126
- ...(notary === undefined ? {} : { notary }),
127
- ...(auditor === undefined ? {} : { auditor }),
128
- ...(evidenceChild === undefined ? {} : { evidenceChild }),
129
- ...(navigator === undefined ? {} : { navigator }),
130
- },
131
- };
132
- }
133
-
134
- /**
135
- * Persist institutional resolution page to run directory (#518 §2 Hop 2).
136
- * Rewritten on each dispatch and resume. Zero secrets inside page.
137
- */
138
- export async function writeInstitutionalResolutionPage(
139
- runDirectory: string,
140
- page: InstitutionalResolutionPage,
141
- ): Promise<void> {
142
- const filePath = join(runDirectory, INSTITUTIONAL_RESOLUTION_FILE);
143
- await writeFile(filePath, `${JSON.stringify(page, null, 2)}\n`, "utf8");
144
- }
145
-
146
- /**
147
- * Read institutional seat selection from run directory (#518 §2 Hop 3).
148
- * Fails loud if page is missing, corrupted, or does not contain a resolution for the seat.
149
- */
150
- export async function readInstitutionalSeatSelection(
151
- runDirectory: string,
152
- seat: string,
153
- ): Promise<InstitutionalSeatSelection> {
154
- const filePath = join(runDirectory, INSTITUTIONAL_RESOLUTION_FILE);
155
- let raw: string;
156
- try {
157
- raw = await readFile(filePath, "utf8");
158
- } catch (error) {
159
- throw new InstitutionalResolutionError(
160
- `institutional resolution page is missing at ${filePath}`,
161
- "missing-page",
162
- { cause: error },
163
- );
164
- }
165
-
166
- let page: unknown;
167
- try {
168
- page = JSON.parse(raw);
169
- } catch (error) {
170
- throw new InstitutionalResolutionError(
171
- `institutional resolution page is corrupted at ${filePath}`,
172
- "corrupted",
173
- { cause: error },
174
- );
175
- }
176
-
177
- if (typeof page !== "object" || page === null || (page as { version?: unknown }).version !== 1) {
178
- throw new InstitutionalResolutionError(
179
- `institutional resolution page format is invalid at ${filePath}`,
180
- "invalid-format",
181
- );
182
- }
183
-
184
- const seats = (page as { seats?: Record<string, unknown> }).seats;
185
- if (typeof seats !== "object" || seats === null) {
186
- throw new InstitutionalResolutionError(
187
- `institutional resolution page missing seats object at ${filePath}`,
188
- "invalid-format",
189
- );
190
- }
191
-
192
- const selection = cleanSelection(seats[seat] as InstitutionalSeatSelection | undefined);
193
- if (selection === undefined) {
194
- throw new InstitutionalResolutionError(
195
- `institutional resolution page has no resolution for seat "${seat}" at ${filePath}`,
196
- "missing-seat",
197
- );
198
- }
199
-
200
- return selection;
201
- }
@@ -1,10 +1,9 @@
1
1
  import {
2
- createComplianceDecisionTool,
3
2
  runComplianceAudit,
3
+ type AuditorSummon,
4
4
  type ComplianceDecision,
5
5
  } from "./compliance-transport.ts";
6
6
  import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
7
- import { loadAuditorSoul } from "./auditor-soul.ts";
8
7
  import {
9
8
  readJudgeAuditSubjects,
10
9
  requireAuditMaterials,
@@ -18,17 +17,13 @@ export const SOUL_AUDIT_TOOL_NAME = JUDGE_AUDIT_TOOL_NAME;
18
17
  export type JudgeAuditOptions = {
19
18
  context: HostContext;
20
19
  signal?: AbortSignal;
20
+ /** Same seam as runComplianceAudit options — offline tracers only. */
21
+ summonAuditor?: AuditorSummon;
21
22
  };
22
23
 
23
- const auditDecisionTool = createComplianceDecisionTool(
24
- JUDGE_AUDIT_TOOL_NAME,
25
- "提交 typed pass/revise/escalate 决议(大理寺审刑)。",
26
- );
27
-
28
24
  /**
29
- * Judge auditor: zero hand-delivered materials. Subjects recovered from the
30
- * parent-session books. Public CLI injects AK_ROLE_RUN_DIR; bare Pi (ADR 0052)
31
- * proceeds without that pointer and self-locates per soul.
25
+ * Judge compliance via public auditor activation (#675 / ADR 0062 / owner r11).
26
+ * 审刑院 is an independent role; subject=judge selects souls/judge-auditor.md.
32
27
  */
33
28
  export function createPiJudgeAuditor(): (options: JudgeAuditOptions) => Promise<ComplianceDecision> {
34
29
  return async (options) => {
@@ -38,13 +33,13 @@ export function createPiJudgeAuditor(): (options: JudgeAuditOptions) => Promise<
38
33
  requireAuditMaterials(subjects);
39
34
 
40
35
  return runComplianceAudit({
41
- tool: auditDecisionTool,
42
- systemPrompt: await loadAuditorSoul("judge"),
43
- roleLabel: "Judge compliance audit",
44
- invalidDecisionLabel: "invalid judge audit decision",
36
+ subject: "judge",
45
37
  context: options.context,
46
- ...(auditorRunDirectory(options.context) === undefined ? {} : { runDirectory: auditorRunDirectory(options.context) }),
38
+ ...(auditorRunDirectory(options.context) === undefined
39
+ ? {}
40
+ : { runDirectory: auditorRunDirectory(options.context) }),
47
41
  ...(options.signal === undefined ? {} : { signal: options.signal }),
42
+ ...(options.summonAuditor === undefined ? {} : { summonAuditor: options.summonAuditor }),
48
43
  });
49
44
  };
50
45
  }
package/src/judge-role.ts CHANGED
@@ -8,6 +8,7 @@ import { withInfrastructureFailureDeclaration } from "./package-contracts/termin
8
8
 
9
9
  import {
10
10
  JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT,
11
+ JUDGE_ACCEPTED_AUDIT_UNREADABLE_TEXT,
11
12
  JUDGE_ACCEPTED_TEXT,
12
13
  JUDGE_OUTPUT_TOOL_NAME,
13
14
  validateAcceptedJudgeDetails,
@@ -127,6 +128,13 @@ export function createJudgeRoleRuntime(
127
128
  terminate: true as const,
128
129
  ...usageProjection,
129
130
  }),
131
+ // ADR 0055: parent stands with typed unreadable audit fact — not forged pass.
132
+ unreadable: (auditUnreadable, usageProjection) => ({
133
+ content: [{ type: "text" as const, text: JUDGE_ACCEPTED_AUDIT_UNREADABLE_TEXT }],
134
+ details: { ...acceptedDetails, auditUnreadable },
135
+ terminate: true as const,
136
+ ...usageProjection,
137
+ }),
130
138
  revise: (violations) => {
131
139
  throw new Error(
132
140
  `大理寺回执违 soul:${violations.join("; ")}`,
@@ -16,7 +16,7 @@ import {
16
16
  resolveActivationLedgerHome,
17
17
  } from "./activation-ledger-topology.ts";
18
18
  import type { HostContext } from "./host-contracts.ts";
19
- import { createNativeNavigatorSessionFactory } from "./evidence-child-executor.ts";
19
+ import { createNativeNavigatorSessionFactory } from "./navigator-public-session.ts";
20
20
  import {
21
21
  NAVIGATOR_DEFAULT_MODEL,
22
22
  NAVIGATOR_PREPARE_TOOL_NAME,
@@ -78,7 +78,9 @@ export function resolveNavigatorAuthorityMaterial(
78
78
  return undefined;
79
79
  }
80
80
 
81
- export const NAVIGATOR_TARGETS = PACKAGED_ROLE_REGISTRY.map(({ role, phases }) => ({ role, phases }));
81
+ /** Every public role is a lawful navigator route target (#675 — no nested-only seats). */
82
+ export const NAVIGATOR_TARGETS = PACKAGED_ROLE_REGISTRY
83
+ .map(({ role, phases }) => ({ role, phases }));
82
84
 
83
85
  export type NavigatorTargetRole = PackagedRole;
84
86
  export type NavigatorPhase = "plan" | "apply" | null;
@@ -591,11 +593,7 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
591
593
  const modelPromise = (async () => {
592
594
  try {
593
595
  // Same resolution authority as createNativeNavigatorSessionFactory (#590).
594
- const resolved = await resolveNavigatorSeatSelection(
595
- options.context,
596
- options.modelSettingPath,
597
- options.modelSettingPath ?? navigatorModelSettingPath(),
598
- );
596
+ const resolved = await resolveNavigatorSeatSelection(options.context);
599
597
  return resolved.configuredLabel;
600
598
  } catch (error) {
601
599
  if (error instanceof NavigatorUnavailableError) throw error;
@@ -735,6 +733,14 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
735
733
  return;
736
734
  }
737
735
  if (promptFailure !== undefined) throw promptFailure;
736
+ const sessionNoReceipt = activeSession.noReceipt?.();
737
+ if (sessionNoReceipt !== undefined) {
738
+ // This session already settled without an accepted receipt on its own
739
+ // budget; one more prompt opens an independent summon, not a delivery
740
+ // request on the settled session (#675).
741
+ delivery.recordNestedNoReceipt(sessionNoReceipt);
742
+ return;
743
+ }
738
744
  if (deliveryRequest && output === undefined) delivery.recordDeliveryRequest();
739
745
  };
740
746
  await promptAllowingRejectedPrepare(request, false);
@@ -831,6 +837,8 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
831
837
  };
832
838
 
833
839
  async function settleOnce(settlement: NavigatorSettlement): Promise<void> {
840
+ // Dispose during post-role grace must ignore late completion entirely (#675).
841
+ if (disposed) return;
834
842
  const invocationId = activeInvocationId ?? invocationPrincipal;
835
843
  let report: NavigatorReport;
836
844
  if (settlement.kind === "human_decision" || settlement.kind === "role_infrastructure_failure") {
@@ -876,8 +884,10 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
876
884
  // table. After selection (speculative or rebound), advice is passed
877
885
  // through as-is (ADR 0010 / ADR 0061: caller may ignore).
878
886
  if (selected?.candidate.next !== undefined && !selected.matchedToSettlement) {
887
+ if (disposed) return;
879
888
  prepareBoundSettlement = settlement;
880
889
  prepared = await prepare();
890
+ if (disposed) return;
881
891
  selected = selectNavigatorCandidate(prepared, settlement);
882
892
  }
883
893
  // Budget exhaustion is affirmative typed no-advice; malformed submitted
@@ -933,10 +943,16 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
933
943
  ...(report.routePlaybookReadFailure === undefined ? {} : { routePlaybookReadFailure: report.routePlaybookReadFailure }),
934
944
  ...(report.arrivalMessage === undefined ? {} : { arrivalMessage: report.arrivalMessage }),
935
945
  };
936
- // Dispose during post-role grace must ignore late completion (ADR 0052 / #106).
946
+ // Dispose during post-role grace must ignore late completion (ADR 0052 / #106 / #675).
937
947
  // Every settled disposition (recommendation | no-advice | unavailable | arrival) is affirmative.
938
- if (!disposed) {
948
+ // Only the disposed bit is typed authority to drop — never match free-text Error.message.
949
+ if (disposed) return;
950
+ try {
939
951
  await options.onEvent(event, report);
952
+ } catch (error) {
953
+ // Race: dispose landed between the check and onEvent; drop only then.
954
+ if (disposed) return;
955
+ throw error;
940
956
  }
941
957
  preparation = undefined;
942
958
  sessionReady = undefined;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Navigator attendance session factory (#675 r3).
3
+ * Each prepare turn uses the public navigator activation path (summonPublicRole) —
4
+ * same seat table and shared envelope as `ak-role navigator`.
5
+ * Archivist createRecordSession only books route-memory nest under the parent;
6
+ * no openPiInProcessSession second lifecycle / no agentDir patch on the old path.
7
+ */
8
+ import { sitianReport } from "./sitian-facade.ts";
9
+ import {
10
+ NavigatorUnavailableError,
11
+ navigatorProviderFailureFromDiagnostics,
12
+ navigatorProviderFailureFromError,
13
+ navigatorProviderFailureFromStatus,
14
+ navigatorUnavailableError,
15
+ parseNavigatorModelSetting,
16
+ resolveNavigatorSeatSelection,
17
+ type NavigatorProviderFailureFact,
18
+ type NavigatorSessionFactory,
19
+ } from "./navigator-session-contracts.ts";
20
+ import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
21
+
22
+ /**
23
+ * Classify public-navigator failure terminal from structured decisiveFacts only
24
+ * (httpStatus / diagnostics / secondaryEvidence on the terminal) — same fields
25
+ * settlement already stamps from typed HTTP observation / provider stop.
26
+ */
27
+ function providerFailureFromPublicTerminal(outcome: {
28
+ readonly cause: string;
29
+ readonly diagnostic: string;
30
+ readonly decisiveFacts: Readonly<Record<string, unknown>>;
31
+ }): NavigatorProviderFailureFact {
32
+ const facts = outcome.decisiveFacts;
33
+ const secondary =
34
+ typeof facts.secondaryEvidence === "object" && facts.secondaryEvidence !== null
35
+ ? (facts.secondaryEvidence as Record<string, unknown>)
36
+ : undefined;
37
+ const httpStatus =
38
+ typeof secondary?.httpStatus === "number"
39
+ ? secondary.httpStatus
40
+ : typeof facts.httpStatus === "number"
41
+ ? facts.httpStatus
42
+ : typeof facts.errorCode === "number"
43
+ ? facts.errorCode
44
+ : undefined;
45
+ const fromStatus = navigatorProviderFailureFromStatus(httpStatus);
46
+ if (fromStatus !== undefined) return fromStatus;
47
+ const diagnostics = secondary?.diagnostics ?? facts.diagnostics;
48
+ const fromDiagnostics = navigatorProviderFailureFromDiagnostics(diagnostics);
49
+ if (fromDiagnostics !== undefined) return fromDiagnostics;
50
+ const fromCode = navigatorProviderFailureFromError({
51
+ code: secondary?.code ?? facts.errorCode,
52
+ });
53
+ if (fromCode !== undefined) return fromCode;
54
+ if (outcome.cause === "provider") return { source: "transport", cause: "transport" };
55
+ return { source: "session", cause: "session" };
56
+ }
57
+
58
+ export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
59
+ return async ({ context, subject, tool }) => {
60
+ const resolved = await resolveNavigatorSeatSelection(context);
61
+ let thinkingLevel = resolved.thinkingLevel;
62
+
63
+ // Archivist nest for attendance route memory only (ADR 0018 / 0065) — not a session open.
64
+ const { createRecordSession } = await import("./archivist-record-entry.ts");
65
+ const sessionManager = createRecordSession({
66
+ cwd: context.cwd,
67
+ kind: "navigator",
68
+ subject,
69
+ parent: context.sessionManager,
70
+ });
71
+
72
+ let providerFailure: NavigatorProviderFailureFact | undefined;
73
+ let noReceipt: NoReceiptLifecycleFacts | undefined;
74
+ let disposed = false;
75
+ let inFlightPrompt: Promise<unknown> | undefined;
76
+
77
+ const resolveHome = async (): Promise<string | undefined> => {
78
+ const parentFile = context.sessionManager?.getSessionFile?.();
79
+ if (typeof parentFile === "string" && parentFile.trim() !== "") {
80
+ try {
81
+ const { tryHomeFromAkRolesPath, homeFromRunDirectory } = await import(
82
+ "./activation-ledger-topology.ts"
83
+ );
84
+ const fromParent = tryHomeFromAkRolesPath(parentFile);
85
+ if (fromParent !== undefined && fromParent.length > 0) return fromParent;
86
+ const runDir = parentFile.replace(/\/session\/session\.jsonl$/, "");
87
+ if (runDir !== parentFile) {
88
+ try {
89
+ return homeFromRunDirectory(runDir);
90
+ } catch {
91
+ // fall through
92
+ }
93
+ }
94
+ } catch {
95
+ // fall through
96
+ }
97
+ }
98
+ const envHome = process.env.HOME;
99
+ return typeof envHome === "string" && envHome.trim() !== "" ? envHome : undefined;
100
+ };
101
+
102
+ return {
103
+ prompt: async (text) => {
104
+ if (disposed) {
105
+ throw navigatorUnavailableError("session", new Error("Navigator attendance was disposed"));
106
+ }
107
+ providerFailure = undefined;
108
+ noReceipt = undefined;
109
+ const run = (async () => {
110
+ try {
111
+ const { summonPublicRole } = await import("./public-role-summons.ts");
112
+ const home = await resolveHome();
113
+ // Public activation — same face as `ak-role navigator <instruction>` (#675).
114
+ const summoned = await summonPublicRole({
115
+ role: "navigator",
116
+ argv: [text],
117
+ cwd: context.cwd,
118
+ ...(home === undefined ? {} : { home }),
119
+ });
120
+
121
+ const outcome = summoned.terminal?.roleOutcome;
122
+ if (outcome === undefined) {
123
+ const detail = summoned.stderr?.trim() || `exit ${summoned.exitCode}`;
124
+ providerFailure = { source: "transport", cause: "transport" };
125
+ throw navigatorUnavailableError(
126
+ "transport",
127
+ new Error(`Navigator public summon produced no terminal (${detail})`),
128
+ );
129
+ }
130
+ if (outcome.kind === "failure") {
131
+ providerFailure = providerFailureFromPublicTerminal(outcome);
132
+ throw navigatorUnavailableError(
133
+ providerFailure.source,
134
+ new Error(outcome.diagnostic),
135
+ providerFailure.cause,
136
+ );
137
+ }
138
+ if (outcome.kind === "no_receipt") {
139
+ // The nested session spent its own delivery budget; attendance settles
140
+ // no-receipt on these facts instead of opening another summon (#675).
141
+ noReceipt = outcome;
142
+ return;
143
+ }
144
+ if (outcome.kind !== "accepted") {
145
+ providerFailure = { source: "session", cause: "session" };
146
+ throw navigatorUnavailableError(
147
+ "session",
148
+ new Error("Navigator public summon returned unusable terminal"),
149
+ );
150
+ }
151
+ const candidates = outcome.decisiveFacts.candidates;
152
+ if (!Array.isArray(candidates)) {
153
+ return;
154
+ }
155
+ // Rejoin attendance prepare tool sink (same candidate shape as public advice).
156
+ await tool.execute(
157
+ "navigator-public-prepare",
158
+ { candidates },
159
+ undefined,
160
+ undefined,
161
+ context as never,
162
+ );
163
+ } catch (error) {
164
+ if (error instanceof NavigatorUnavailableError) throw error;
165
+ const fact = navigatorProviderFailureFromError(error);
166
+ providerFailure = fact ?? { source: "transport", cause: "transport" };
167
+ throw navigatorUnavailableError(providerFailure.source, error, providerFailure.cause);
168
+ }
169
+ })();
170
+ inFlightPrompt = run;
171
+ try {
172
+ await run;
173
+ } finally {
174
+ if (inFlightPrompt === run) inFlightPrompt = undefined;
175
+ }
176
+ },
177
+ providerFailure: () => providerFailure,
178
+ noReceipt: () => noReceipt,
179
+ appendEntry: (customType, data) => {
180
+ sessionManager.appendCustomEntry(customType, data);
181
+ try {
182
+ sitianReport({
183
+ level: "event",
184
+ kind: "attendance",
185
+ cwd: context.cwd,
186
+ sessionParent: sessionManager.getSessionFile(),
187
+ payload: { customType, data },
188
+ source: "navigator-public-session",
189
+ });
190
+ } catch {
191
+ // best-effort
192
+ }
193
+ },
194
+ entries: () => sessionManager.getEntries(),
195
+ setModel: async (next, nextThinking) => {
196
+ let nextParsed: ReturnType<typeof parseNavigatorModelSetting>;
197
+ try {
198
+ nextParsed = parseNavigatorModelSetting(next);
199
+ } catch (error) {
200
+ throw navigatorUnavailableError("model", error);
201
+ }
202
+ // No live provider session is pinned here: every prompt is an independent
203
+ // public summon whose nested CLI reads the current seat table (#675 验收②
204
+ // / #617 DK-3). A seat edit between prepares therefore applies on the next
205
+ // summon — it is never an unavailable session.
206
+ thinkingLevel = nextThinking ?? nextParsed.thinkingLevel;
207
+ },
208
+ getThinkingLevel: () => thinkingLevel,
209
+ recordPointer: () => sessionManager.getSessionDir(),
210
+ dispose: async () => {
211
+ disposed = true;
212
+ const pending = inFlightPrompt;
213
+ if (pending !== undefined) await pending.catch(() => undefined);
214
+ },
215
+ };
216
+ };
217
+ }