@akagilnc/pi-workflow-roles 0.1.3932 → 0.1.3975

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 (162) hide show
  1. package/CLAUDE.md +1 -1
  2. package/dist/acp-host/production-host.js +2389 -6018
  3. package/dist/analyst-gate-cycles-read.js +15 -19
  4. package/dist/audit-escalation.js +16 -46
  5. package/dist/auditor-dossier-tool.js +7 -62
  6. package/dist/collector-identity.js +0 -157
  7. package/dist/collector-ledger.js +2 -42
  8. package/dist/compliance-transport.js +40 -14
  9. package/dist/diarist-contracts.js +2 -40
  10. package/dist/doctor-contracts.js +39 -108
  11. package/dist/doctor-evidence.js +1 -5
  12. package/dist/engine-detour-tool.js +1 -1
  13. package/dist/gatekeeper-role.js +49 -38
  14. package/dist/headless-host/production-host.js +4814 -8450
  15. package/dist/host-contracts.js +0 -5
  16. package/dist/merger-contracts.js +4 -6
  17. package/dist/navigator-attendance.js +30 -52
  18. package/dist/navigator-invocation-identity.js +0 -1
  19. package/dist/navigator-public-session.js +2 -1
  20. package/dist/notary-contracts.js +4 -10
  21. package/dist/package-contracts/auditor-output.js +5 -23
  22. package/dist/package-contracts/fixer-output.js +25 -49
  23. package/dist/package-contracts/gatekeeper-output.js +3 -13
  24. package/dist/package-contracts/navigator-output.js +4 -7
  25. package/dist/package-contracts/reviewer-output.js +1 -95
  26. package/dist/package-contracts/terminating-tools.js +33 -144
  27. package/dist/package-contracts/worker-output.js +1 -3
  28. package/dist/public-cli/auto-resume.js +30 -68
  29. package/dist/public-cli/case-dossier-delivery.js +5 -17
  30. package/dist/public-cli/diarist-run.js +9 -4
  31. package/dist/public-cli/inspector-run.js +2 -8
  32. package/dist/public-cli/instruction-seat-run.js +3 -7
  33. package/dist/public-cli/invocation.js +5 -2
  34. package/dist/public-cli/main.js +6068 -6964
  35. package/dist/public-cli/notary-run.js +1 -8
  36. package/dist/public-cli/post-admission.js +314 -113
  37. package/dist/public-cli/run-lifecycle.js +14 -38
  38. package/dist/public-cli/settlement.js +209 -390
  39. package/dist/public-cli/terminal.js +51 -45
  40. package/dist/public-role-summons.js +1 -12
  41. package/dist/reviewer-dispatch.js +12 -258
  42. package/dist/submission-correctable-error.js +0 -12
  43. package/dist/submission-errors.js +6 -1
  44. package/dist/submission-ledger.js +125 -168
  45. package/dist/ticket-provenance-contracts.js +2 -49
  46. package/dist/ticket-provenance.js +82 -31
  47. package/extensions/role-runtime.ts +1 -6
  48. package/package.json +1 -1
  49. package/resources/836-deleted-machine-instruction-inventory.md +18 -0
  50. package/src/acp-host/role-turn-host.ts +8 -3
  51. package/src/analyst-gate-cycles-read.ts +17 -29
  52. package/src/audit-escalation.ts +20 -50
  53. package/src/auditor-dossier-tool.ts +5 -103
  54. package/src/canonical-skill-binding.ts +3 -2
  55. package/src/collector-identity.ts +0 -179
  56. package/src/collector-ledger.ts +3 -46
  57. package/src/collector-role.ts +32 -61
  58. package/src/collector-tool-schemas.ts +20 -14
  59. package/src/compliance-transport.ts +55 -17
  60. package/src/countersign-role.ts +17 -8
  61. package/src/diarist-contracts.ts +3 -36
  62. package/src/diarist-role.ts +13 -7
  63. package/src/diarist.ts +4 -16
  64. package/src/doctor-contracts.ts +73 -94
  65. package/src/doctor-evidence.ts +1 -1
  66. package/src/doctor-role.ts +3 -3
  67. package/src/engine-detour-tool.ts +4 -1
  68. package/src/external-host-turn-loop.ts +9 -3
  69. package/src/gatekeeper-pass-envelope.ts +7 -12
  70. package/src/gatekeeper-role.ts +56 -50
  71. package/src/gleaner-left-role.ts +4 -2
  72. package/src/headless-host/role-turn-host.ts +4 -5
  73. package/src/host-contracts.ts +3 -8
  74. package/src/judge-role.ts +19 -13
  75. package/src/merger-contracts.ts +10 -6
  76. package/src/merger-role.ts +1 -5
  77. package/src/navigator-attendance.ts +37 -74
  78. package/src/navigator-invocation-identity.ts +0 -6
  79. package/src/navigator-public-session.ts +2 -1
  80. package/src/notary-contracts.ts +7 -10
  81. package/src/package-contracts/auditor-output.ts +4 -21
  82. package/src/package-contracts/fixer-output.ts +34 -52
  83. package/src/package-contracts/gatekeeper-output.ts +5 -22
  84. package/src/package-contracts/navigator-output.ts +7 -7
  85. package/src/package-contracts/reviewer-output.ts +2 -150
  86. package/src/package-contracts/terminating-tools.ts +32 -147
  87. package/src/package-contracts/worker-output.ts +0 -1
  88. package/src/pi/adapter.ts +3 -8
  89. package/src/public-cli/auto-resume.ts +41 -94
  90. package/src/public-cli/case-dossier-delivery.ts +5 -15
  91. package/src/public-cli/countersign-run.ts +14 -6
  92. package/src/public-cli/diarist-run.ts +11 -9
  93. package/src/public-cli/inspector-run.ts +2 -18
  94. package/src/public-cli/instruction-seat-run.ts +3 -17
  95. package/src/public-cli/invocation.ts +5 -2
  96. package/src/public-cli/merger-run.ts +1 -1
  97. package/src/public-cli/notary-run.ts +1 -18
  98. package/src/public-cli/post-admission.ts +385 -115
  99. package/src/public-cli/run-lifecycle.ts +13 -44
  100. package/src/public-cli/settlement.ts +314 -562
  101. package/src/public-cli/terminal.ts +66 -72
  102. package/src/public-role-summons.ts +5 -30
  103. package/src/reviewer-dispatch.ts +29 -331
  104. package/src/reviewer-role.ts +16 -83
  105. package/src/role-envelope.ts +16 -24
  106. package/src/role-runtime-dependencies.ts +1 -6
  107. package/src/role-runtime.ts +65 -201
  108. package/src/submission-correctable-error.ts +0 -14
  109. package/src/submission-errors.ts +6 -1
  110. package/src/submission-ledger.ts +187 -187
  111. package/src/ticket-provenance-contracts.ts +4 -60
  112. package/src/ticket-provenance.ts +98 -32
  113. package/src/worker-role.ts +35 -36
  114. package/dist/activation-ledger-session.js +0 -105
  115. package/dist/activation-ledger.js +0 -139
  116. package/dist/activation-trace.js +0 -33
  117. package/dist/atomic-write.js +0 -23
  118. package/dist/auditor-role.js +0 -12
  119. package/dist/canonical-skill-binding.js +0 -61
  120. package/dist/collector-handbook.js +0 -142
  121. package/dist/collector-receipt.js +0 -107
  122. package/dist/collector-role.js +0 -566
  123. package/dist/collector-tool-schemas.js +0 -107
  124. package/dist/countersign-role.js +0 -28
  125. package/dist/diarist-role.js +0 -44
  126. package/dist/diarist.js +0 -66
  127. package/dist/doctor-role.js +0 -75
  128. package/dist/fixer-bash-seatbelt.js +0 -13
  129. package/dist/gatekeeper-pass-envelope.js +0 -77
  130. package/dist/gleaner-left-role.js +0 -26
  131. package/dist/inspector-role.js +0 -29
  132. package/dist/judge-role.js +0 -104
  133. package/dist/merger-role.js +0 -55
  134. package/dist/navigator-role.js +0 -12
  135. package/dist/notary-role.js +0 -134
  136. package/dist/oauth-keepalive.js +0 -158
  137. package/dist/pi/adapter.js +0 -223
  138. package/dist/reviewer-agent.js +0 -122
  139. package/dist/reviewer-child-executor.js +0 -194
  140. package/dist/reviewer-construction.js +0 -103
  141. package/dist/reviewer-execution-ledger.js +0 -166
  142. package/dist/reviewer-failure-diagnostic.js +0 -17
  143. package/dist/reviewer-role.js +0 -159
  144. package/dist/reviewer-scope-prompt.js +0 -21
  145. package/dist/reviewer-settlement.js +0 -66
  146. package/dist/reviewer-workspace.js +0 -132
  147. package/dist/role-runtime.js +0 -1604
  148. package/dist/stderr-jsonl.js +0 -26
  149. package/dist/tool-execution-observation.js +0 -141
  150. package/dist/worker-role.js +0 -345
  151. package/dist/worker-submission-gates.js +0 -305
  152. package/src/collector-receipt.ts +0 -140
  153. package/src/diagnostic-tail.ts +0 -18
  154. package/src/reviewer-agent.ts +0 -135
  155. package/src/reviewer-child-executor.ts +0 -252
  156. package/src/reviewer-construction.ts +0 -195
  157. package/src/reviewer-execution-ledger.ts +0 -212
  158. package/src/reviewer-failure-diagnostic.ts +0 -18
  159. package/src/reviewer-scope-prompt.ts +0 -24
  160. package/src/reviewer-settlement.ts +0 -74
  161. package/src/reviewer-workspace.ts +0 -105
  162. package/src/shape-unreadable-failure.ts +0 -49
@@ -1,1604 +0,0 @@
1
- import { readFileSync, writeSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { ExplicitInternalActivationError, } from "./host-contracts.js";
4
- import { recordReviewerDispatchRejectionSync } from "./public-cli/reviewer-dispatch-rejection.js";
5
- import { Value } from "typebox/value";
6
- import { sitianReport } from "./sitian-facade.js";
7
- import { createSubmissionLedgerHost } from "./submission-ledger.js";
8
- import { createCollectorLedger } from "./collector-ledger.js";
9
- import { activationTraceRecordSchema, namedActivationCause } from "./activation-trace.js";
10
- import { homeFromRunDirectory, resolveActivationLedgerHomeForPath, } from "./activation-ledger-topology.js";
11
- import { appendAcceptedActivationToBook, buildAcceptedActivationFact, correlationIdentityFromEnv, durableSessionPointer, resolveActivationLedgerHome, resolveBookKeyFromGit, } from "./activation-ledger.js";
12
- import { writeStderrJsonlRecord } from "./stderr-jsonl.js";
13
- import { createToolExecutionObservationFace, systemToolExecutionObservationMonoNow, writeToolExecutionObservationRecord, } from "./tool-execution-observation.js";
14
- import { ENGINE_DETOUR_TOOL_NAME } from "./engine-detour.js";
15
- import { registerEngineDetourTool } from "./engine-detour-tool.js";
16
- import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.js";
17
- import { COLLECTOR_REQUIRED_TOOLS, COLLECTOR_TRANSPORT_FLAGS, createCollectorRoleRuntime, } from "./collector-role.js";
18
- import { createDoctorRoleRuntime } from "./doctor-role.js";
19
- import { createNotaryRoleRuntime, NOTARY_SESSION_BOUND_ENTRY, projectNotaryBoundFromFlags, readNotaryTicketFlag, } from "./notary-role.js";
20
- import { NOTARY_TICKET_FLAG } from "./notary-contracts.js";
21
- import { COUNTERSIGN_TOOL_SPEC, } from "./countersign-role.js";
22
- import { COUNTERSIGN_ACCEPTED_TEXT } from "./countersign-contracts.js";
23
- import { GLEANER_LEFT_TOOL_SPEC, } from "./gleaner-left-role.js";
24
- import { GLEANER_LEFT_ACCEPTED_TEXT, GLEANER_LEFT_BASE_FLAG } from "./gleaner-left-contracts.js";
25
- import { INSPECTOR_TOOL_SPEC, } from "./inspector-role.js";
26
- import { INSPECTOR_ACCEPTED_TEXT } from "./inspector-contracts.js";
27
- import { DIARIST_TOOL_SPEC, } from "./diarist-role.js";
28
- import { DIARIST_ACCEPTED_TEXT, projectDiaristEntries, } from "./diarist-contracts.js";
29
- import { commitDiaristEntries } from "./diarist.js";
30
- import { bindTicketNumberOnRunDirectory } from "./public-cli/invocation.js";
31
- import { GATEKEEPER_TOOL_SPEC, } from "./gatekeeper-role.js";
32
- import { NAVIGATOR_TOOL_SPEC, } from "./navigator-role.js";
33
- import { AUDITOR_TOOL_SPEC, } from "./auditor-role.js";
34
- import { AUDITOR_ACCEPTED_TEXT } from "./package-contracts/auditor-output.js";
35
- import { GATEKEEPER_ACCEPTED_TEXT } from "./package-contracts/gatekeeper-output.js";
36
- import { NAVIGATOR_ACCEPTED_TEXT } from "./package-contracts/navigator-output.js";
37
- import { decorateSettlementWithNavigation, formatNavigatorReport, NAVIGATOR_EVENT_TYPE, navigatorSubjectKey, navigatorUnavailableError, subjectPath } from "./navigator-attendance.js";
38
- import { buildNavigatorInfrastructureFailureFact, classifyPackagedRoleTerminalResult, extractInfrastructureFailureEvidence, NAVIGATOR_INVOCATION_ENTRY, resolveLifecycleInvocationPrincipal, } from "./navigator-invocation-identity.js";
39
- import { recordTypedProviderHttpStatus } from "./typed-provider-http.js";
40
- import { NAVIGATOR_POST_ROLE_GRACE_MS, raceNavigatorGrace } from "./public-cli/settlement.js";
41
- import { PACKAGED_ROLE_REGISTRY, packagedRoleMetadata, packagedRoleOutputTool, packagedRolePhaseFlag } from "./packaged-role-registry.js";
42
- import { isAuditEscalationProjection } from "./audit-escalation.js";
43
- import { createJudgeRoleRuntime, } from "./judge-role.js";
44
- import { createReviewerRoleRuntime, } from "./reviewer-role.js";
45
- import {} from "./reviewer-construction.js";
46
- /**
47
- * Private transport flag names/definitions for Reviewer admitted inputs.
48
- * Shared activation envelope owns registration and decoding (ADR 0018).
49
- */
50
- const REVIEWER_TRANSPORT_FLAGS = Object.freeze([
51
- Object.freeze({
52
- name: "ak-review-base",
53
- definition: Object.freeze({
54
- description: "Fixed base revision for the pinned review target",
55
- type: "string",
56
- }),
57
- }),
58
- Object.freeze({
59
- name: "ak-review-scope-keys",
60
- definition: Object.freeze({
61
- description: "Optional comma-separated exact class keys limiting Reviewer scope",
62
- type: "string",
63
- }),
64
- }),
65
- Object.freeze({
66
- name: "ak-review-authority-refs",
67
- definition: Object.freeze({
68
- description: "JSON array of durable authority references for Spec-axis material only",
69
- type: "string",
70
- }),
71
- }),
72
- Object.freeze({
73
- name: "ak-review-ticket-number",
74
- definition: Object.freeze({
75
- description: "Typed ticketNumber for Spec self-fetch primary path",
76
- type: "string",
77
- }),
78
- }),
79
- ]);
80
- /** Notary private transport: optional court-diary ticket (ADR 0018 / 0075 — envelope-owned). */
81
- const NOTARY_TRANSPORT_FLAGS = Object.freeze([
82
- Object.freeze({
83
- name: NOTARY_TICKET_FLAG.name,
84
- definition: NOTARY_TICKET_FLAG.definition,
85
- }),
86
- ]);
87
- /** Gleaner-left private transport: comparison-base revision (ADR 0018 / #502). */
88
- const GLEANER_LEFT_TRANSPORT_FLAGS = Object.freeze([
89
- Object.freeze({
90
- name: GLEANER_LEFT_BASE_FLAG.name,
91
- definition: GLEANER_LEFT_BASE_FLAG.definition,
92
- }),
93
- ]);
94
- export const STATION_CHILD_FLAG = Object.freeze({
95
- name: "ak-station-child",
96
- definition: Object.freeze({
97
- description: "Station-child role run (omit automatic navigator attendance; #840)",
98
- type: "boolean",
99
- }),
100
- });
101
- /**
102
- * Decode private transport flags into frozen admitted inputs.
103
- * Envelope-owned; necessary JSON decode only (public --authority-ref owns grammar).
104
- */
105
- function decodeReviewerAdmittedInputs(getFlag) {
106
- let reviewScopeKeys;
107
- const rawScopeKeys = getFlag("ak-review-scope-keys");
108
- if (rawScopeKeys !== undefined) {
109
- if (typeof rawScopeKeys !== "string" || rawScopeKeys.length === 0) {
110
- throw new Error("Reviewer scope keys must be a nonempty comma-separated string");
111
- }
112
- const parsed = rawScopeKeys.split(",");
113
- if (parsed.some((key) => key.trim().length === 0) || new Set(parsed).size !== parsed.length) {
114
- throw new Error("Reviewer scope keys contain a blank or exact duplicate key");
115
- }
116
- reviewScopeKeys = Object.freeze(parsed);
117
- }
118
- let authorityRefs;
119
- const rawAuthorityRefs = getFlag("ak-review-authority-refs");
120
- if (rawAuthorityRefs !== undefined) {
121
- if (typeof rawAuthorityRefs !== "string") {
122
- throw new Error("Reviewer authority refs transport error: flag value must be a string");
123
- }
124
- let parsed;
125
- try {
126
- parsed = JSON.parse(rawAuthorityRefs);
127
- }
128
- catch (error) {
129
- throw new Error(`Reviewer authority refs transport error: JSON decode failed: ${error instanceof Error ? error.message : String(error)}`);
130
- }
131
- if (!Array.isArray(parsed) || parsed.some((ref) => typeof ref !== "string")) {
132
- throw new Error("Reviewer authority refs transport error: expected a JSON array of strings");
133
- }
134
- authorityRefs = Object.freeze(parsed);
135
- }
136
- let ticketNumber;
137
- const rawTicketNumber = getFlag("ak-review-ticket-number");
138
- // Shape-invalid flag values do not abort: omit typed candidate; branch→commit→degrade continues.
139
- if (typeof rawTicketNumber === "string" && /^[1-9]\d*$/.test(rawTicketNumber)) {
140
- ticketNumber = Number(rawTicketNumber);
141
- }
142
- const baseRevision = getFlag("ak-review-base");
143
- if (typeof baseRevision !== "string" || !baseRevision.trim()) {
144
- throw new Error("Reviewer role requires --ak-review-base");
145
- }
146
- return Object.freeze({
147
- baseRevision,
148
- ...(reviewScopeKeys === undefined ? {} : { reviewScopeKeys }),
149
- ...(authorityRefs === undefined ? {} : { authorityRefs }),
150
- ...(ticketNumber === undefined ? {} : { ticketNumber }),
151
- });
152
- }
153
- /**
154
- * Parent system-prompt assembly for the shared activation envelope.
155
- * Verification cadence lives in soul/quality-law only (ADR 0073: no machine copy).
156
- */
157
- function assembleReviewerParentSystemPrompt(input) {
158
- const specDispositionNote = input.specDisposition === "skipped-missing"
159
- ? [
160
- "",
161
- "<reviewer_spec_disposition>",
162
- "权威 Spec 不存在;未启动 Spec 取证腿。",
163
- "</reviewer_spec_disposition>",
164
- ]
165
- : [];
166
- return [
167
- input.baseSystemPrompt,
168
- "",
169
- "<reviewer_soul>",
170
- input.soul,
171
- "</reviewer_soul>",
172
- ...specDispositionNote,
173
- ].join("\n");
174
- }
175
- import { CODER_OUTPUT_TOOL_NAME, createCoderRoleRuntime, createFixerRoleRuntime, FIXER_FLAG_DEFINITIONS, FIXER_OUTPUT_TOOL_NAME, FIXER_PHASES, } from "./worker-role.js";
176
- import { JUDGE_OUTPUT_TOOL_NAME } from "./package-contracts/judge-output.js";
177
- import { REVIEWER_OUTPUT_TOOL_NAME } from "./package-contracts/reviewer-output.js";
178
- import { DOCTOR_OUTPUT_TOOL_NAME } from "./doctor-contracts.js";
179
- import { MERGER_OUTPUT_TOOL_NAME } from "./merger-contracts.js";
180
- import { createMergerRoleRuntime } from "./merger-role.js";
181
- export { buildNavigatorInfrastructureFailureFact, classifyPackagedRoleTerminalResult, extractInfrastructureFailureEvidence, hasNavigatorInfrastructureFailureBase, isAcceptedPackagedRoleTerminalResult, isDurablePackagedRoleTerminalResult, isNavigatorInfrastructureFailureFact, NAVIGATOR_INFRASTRUCTURE_FAILURE_EVIDENCE_KEYS, NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND, } from "./navigator-invocation-identity.js";
182
- export { activationTraceRecordSchema, namedActivationCause } from "./activation-trace.js";
183
- export { ACCEPTED_ACTIVATION_EVENT, ACCEPTED_ACTIVATION_FACT_KEYS, ActivationGitRepositoryRequiredError, ActivationLedgerError, ActivationSessionFileMissingError, activationBookDirectory, activationWaitingLedgerPath, appendAcceptedActivationFact, appendAcceptedActivationToBook, buildAcceptedActivationFact, correlationIdentityFromEnv, durableSessionPointer, resolveActivationLedgerHome, resolveBookKeyFromGit, serializeAcceptedActivationFact, } from "./activation-ledger.js";
184
- export { DISPATCH_STUB_EVENT, buildDispatchStubFact, reconcileInvocation, } from "./activation-reconciliation.js";
185
- export { TOOL_EXECUTION_UPDATE_HEARTBEAT, TOOL_EXECUTION_UPDATE_THROTTLE_MS, createToolExecutionObservationFace, isProducingToolUpdate, systemToolExecutionObservationMonoNow, toolExecutionObservationRecordSchema, validateToolExecutionObservationRecord, writeToolExecutionObservationRecord, } from "./tool-execution-observation.js";
186
- export { NOTARY_OUTPUT_TOOL, INSPECTOR_OUTPUT_TOOL, GatekeeperDecisionError, createGatekeeperOutputTool, runGatekeeper, } from "./gatekeeper-role.js";
187
- export { gateOfficerForSubject } from "./gatekeeper-role.js";
188
- import { ParentQueueReaskError } from "./submission-errors.js";
189
- export { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME, } from "./doctor-role.js";
190
- export { validateDoctorSubmissionShape, validateDoctorOutput, DoctorEvidenceStore } from "./doctor-contracts.js";
191
- export { loadDoctorCase } from "./doctor-evidence.js";
192
- export { JUDGE_OUTPUT_TOOL_NAME, } from "./judge-role.js";
193
- export { ENGINE_DETOUR_TOOL_NAME, AK_ROLE_ENGINE_ENV } from "./engine-detour.js";
194
- export { AGENT_TOOL_NAME, REVIEWER_OUTPUT_TOOL_NAME, } from "./reviewer-role.js";
195
- export { CODER_OUTPUT_TOOL_NAME, CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE, CoderSkillExpansionEvidenceMissingError, FIXER_FLAG_DEFINITIONS, FIXER_OUTPUT_TOOL_NAME, FIXER_PHASES, } from "./worker-role.js";
196
- export { fixerOutputSchema, validateFixerOutput, validateFixerOutputForPacket } from "./package-contracts/fixer-output.js";
197
- export { fixerPrerequisiteSchema, fixerPrerequisitesSchema, parseFixerPrerequisites, validateFixerPrerequisites } from "./package-contracts/fixer-packet.js";
198
- export { AUDIT_ESCALATION_KIND, buildAuditEscalationResult, disposeComplianceDecision, isAuditEscalationResult, projectAuditEscalation } from "./audit-escalation.js";
199
- export { AUDITOR_SOUL_ROLES, AK_ROLE_AUDITOR_SUBJECT_ENV, loadAuditorSoul, loadAuditorSoulFromSubjectInput, resolveAuditorSubject, } from "./auditor-soul.js";
200
- export { JUDGE_AUDIT_TOOL_NAME, SOUL_AUDIT_TOOL_NAME } from "./judge-auditor.js";
201
- export { DOCTOR_AUDIT_TOOL_NAME, createPiDoctorAuditor } from "./doctor-auditor.js";
202
- export { COLLECTOR_OBSERVE_TOOL, COLLECTOR_OUTPUT_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, } from "./collector-role.js";
203
- export * from "./navigator-attendance.js";
204
- export { MERGER_INPUT_FLAG, createMergerRoleRuntime } from "./merger-role.js";
205
- export { MERGER_OUTPUT_TOOL_NAME, mergerInputSchema, mergerOutputSchema, validateMergerInput, validateMergerOutput } from "./merger-contracts.js";
206
- export { createProductionMergerGitState } from "./merger-git-state.js";
207
- function activationStage(role, runtime) {
208
- switch (role) {
209
- case "judge": return { id: "load-and-install", run: async () => runtime.judge.activate() };
210
- case "fixer": return { id: "load-and-install", run: async () => runtime.fixer.activate() };
211
- case "coder": return { id: "load-and-install", run: async () => runtime.coder.activate(runtime.context) };
212
- case "reviewer": return { id: "load-install-and-dispatch", run: async () => {
213
- // Envelope owns flag decode inside the activation stage (trace + fail-closed path).
214
- const admitted = runtime.decodeReviewerAdmitted();
215
- const activation = await runtime.reviewer.activate(runtime.context, admitted);
216
- runtime.bindReviewerParent(activation);
217
- const result = await activation.dispatcher.dispatch(activation.fixedBaseRevision, { context: runtime.context });
218
- if (result.status !== "accepted") {
219
- const rejection = new ExplicitInternalActivationError(`Fixed Reviewer dispatch was not accepted: ${result.status}: ${result.diagnostic}`, {
220
- knownCause: "activation",
221
- name: "ReviewerDispatchRejectionError",
222
- });
223
- // Pi stderr cannot carry the structured violations. Do not mask a durable
224
- // write failure: its infrastructure cause is truer than the unwritten page.
225
- const runDir = process.env.AK_ROLE_RUN_DIR;
226
- if (typeof runDir === "string" && runDir.trim() !== "") {
227
- recordReviewerDispatchRejectionSync(runDir, {
228
- diagnostic: rejection.message,
229
- violations: result.violations,
230
- });
231
- }
232
- throw rejection;
233
- }
234
- } };
235
- case "collector": return { id: "load-and-install", run: async () => runtime.collector.activate(runtime.context, runtime.event) };
236
- case "doctor": return { id: "load-and-install", run: async () => runtime.doctor.activate() };
237
- case "notary": return {
238
- id: "load-and-install",
239
- run: async () => {
240
- // Envelope owns ticket flag read (ADR 0018); role receives admitted value only.
241
- await runtime.notary.activate(runtime.decodeNotaryAdmitted());
242
- },
243
- };
244
- case "countersign": return { id: "load-and-install", run: async () => runtime.countersign.activate() };
245
- case "gleaner-left": return { id: "load-and-install", run: async () => runtime.gleanerLeft.activate() };
246
- case "inspector": return { id: "load-and-install", run: async () => runtime.inspector.activate() };
247
- case "gatekeeper": return { id: "load-and-install", run: async () => runtime.gatekeeper.activate() };
248
- case "navigator": return { id: "load-and-install", run: async () => runtime.navigator.activate() };
249
- case "auditor": return { id: "load-and-install", run: async () => runtime.auditor.activate() };
250
- case "diarist": return { id: "load-and-install", run: async () => runtime.diarist.activate() };
251
- case "merger": return { id: "prepare-git-and-install", run: async () => runtime.merger() };
252
- }
253
- }
254
- function validateActivationTraceRecord(record) {
255
- if (!Value.Check(activationTraceRecordSchema, record)) {
256
- throw new TypeError("Activation trace record does not match its closed contract");
257
- }
258
- return record;
259
- }
260
- async function emitActivationTrace(writeTrace, record) {
261
- await writeTrace(validateActivationTraceRecord(record));
262
- }
263
- async function executeActivationStage(role, stage, infrastructure) {
264
- try {
265
- await stage.run();
266
- }
267
- catch (activationError) {
268
- try {
269
- await emitActivationTrace(infrastructure.writeTrace, {
270
- role,
271
- stageId: stage.id,
272
- status: "failed",
273
- timestamp: infrastructure.clock(),
274
- cause: namedActivationCause(activationError),
275
- });
276
- }
277
- catch (traceError) {
278
- throw new AggregateError([activationError, traceError], `Activation stage ${stage.id} failed and its failure trace could not be emitted`);
279
- }
280
- throw activationError;
281
- }
282
- }
283
- export function writeActivationTraceRecord(record, write = writeSync) {
284
- writeStderrJsonlRecord(record, write);
285
- }
286
- export class ActivationBarrierError extends Error {
287
- code = "AK_ACTIVATION_NOT_ADMITTED";
288
- constructor(role) {
289
- super(`Workflow role ${String(role)} activation did not complete`);
290
- this.name = "ActivationBarrierError";
291
- }
292
- }
293
- export const WORKFLOW_ROLES = PACKAGED_ROLE_REGISTRY.map(({ role }) => role);
294
- export const ROLE_FLAG = {
295
- name: "ak-role",
296
- definition: {
297
- description: `Activate a packaged workflow role: ${WORKFLOW_ROLES.slice(0, -1).join(", ")}, or ${WORKFLOW_ROLES.at(-1)}`,
298
- type: "string",
299
- },
300
- };
301
- /** Host-neutral in-process role help for Navigator prepare (Pi and Grok share this). */
302
- export function formatNavigatorRoleHelp(role) {
303
- const metadata = packagedRoleMetadata(role);
304
- const lines = [
305
- `Usage: ak-role ${role}`,
306
- ROLE_FLAG.definition.description,
307
- ];
308
- if (metadata?.inputFlag !== undefined) {
309
- lines.push(` --${metadata.inputFlag} <value> ${role} input material`);
310
- }
311
- if (metadata?.phaseFlag !== undefined) {
312
- lines.push(` --${metadata.phaseFlag} <value> ${role} phase: ${metadata.phases.filter((p) => p !== null).join(" | ")}`);
313
- }
314
- lines.push(`Public next-command form: ak-role ${role}`);
315
- return lines.join("\n");
316
- }
317
- function abortContext(ctx) {
318
- ctx.abort();
319
- }
320
- function failInfrastructure(error, ctx) {
321
- abortContext(ctx);
322
- if (ctx.mode === "print" || ctx.mode === "json")
323
- process.exitCode = 1;
324
- throw error;
325
- }
326
- function buildPendingInfrastructureFailure(error) {
327
- return {
328
- details: {
329
- ...buildNavigatorInfrastructureFailureFact(),
330
- ...extractInfrastructureFailureEvidence(error),
331
- },
332
- };
333
- }
334
- function navigatorPhase(roleHost, role) {
335
- const metadata = packagedRoleMetadata(role);
336
- if (metadata === undefined || metadata.phases[0] === null)
337
- return null;
338
- const phaseFlag = packagedRolePhaseFlag(role);
339
- const requested = phaseFlag === undefined ? undefined : roleHost.getFlag(phaseFlag);
340
- return requested === "apply" ? "apply" : "plan";
341
- }
342
- function navigatorOutputTool(role) {
343
- return packagedRoleOutputTool(role);
344
- }
345
- export function publicNavigatorSettlement(role, phase, event) {
346
- // One shared classifier owns terminal discriminant (lifecycle + settlement + extractors).
347
- if (event.toolName !== navigatorOutputTool(role))
348
- return undefined;
349
- const classification = classifyPackagedRoleTerminalResult(event);
350
- if (classification.kind === "nonterminal")
351
- return undefined;
352
- if (classification.kind === "infrastructure") {
353
- return { kind: "role_infrastructure_failure", role, phase };
354
- }
355
- // accepted/human — project role/phase status; classifier already rejected infra/contradiction.
356
- const details = typeof event.details === "object" && event.details !== null && !Array.isArray(event.details)
357
- ? event.details
358
- : {};
359
- // Live Navigator consumes only the audit-owned projection; persisted/replayed
360
- // records are re-authenticated by settlement against retained audit evidence.
361
- if (isAuditEscalationProjection(event.details)) {
362
- return { kind: "human_decision", role, phase, status: "audit_escalation" };
363
- }
364
- const status = typeof details.status === "string"
365
- ? details.status
366
- : typeof details.judgeStatus === "string" ? details.judgeStatus
367
- : typeof details.countersignStatus === "string" ? details.countersignStatus : undefined;
368
- if (status !== undefined && status === "escalate") {
369
- return { kind: "human_decision", role, phase, status };
370
- }
371
- return { kind: "accepted", role, phase, ...(status === undefined ? {} : { status }) };
372
- }
373
- export async function projectClosedSubmissionLifecycle(projection, context, phase, recordAccepted, settle) {
374
- recordAccepted();
375
- const closure = {
376
- toolName: navigatorOutputTool(projection.role),
377
- isError: false,
378
- details: projection.decisiveFacts,
379
- };
380
- context.sessionManager.appendCustomEntry?.("ak-role-submission-closure", closure);
381
- await settle(publicNavigatorSettlement(projection.role, phase, closure));
382
- }
383
- /**
384
- * Shared registration envelope for filed officers (ADR 0018 / #572):
385
- * activate, tool register, before_agent_start prompt, inventory check.
386
- * Role module keeps label/soul/spec shape only; sole-final barrier is ledger-owned.
387
- * Optional beforeAccept is the sole extension seam (e.g. countersign Notary gate).
388
- */
389
- function createFiledOfficerRuntime(roleHost, spec, dependencies) {
390
- let soul;
391
- let registered = false;
392
- return {
393
- async activate() {
394
- const loaded = (await dependencies.loadSoul()).trim();
395
- if (loaded.length === 0)
396
- throw new Error(`${spec.role} soul is empty`);
397
- soul = loaded;
398
- if (!registered) {
399
- registered = true;
400
- roleHost.registerTool({
401
- name: spec.tool.name,
402
- label: spec.tool.label,
403
- description: spec.tool.description,
404
- promptSnippet: spec.tool.promptSnippet,
405
- parameters: spec.tool.parameters,
406
- async execute(toolCallId, parameters, signal, _onUpdate, ctx) {
407
- if (soul === undefined)
408
- throw new Error(`${spec.role} 职分未装载`);
409
- const projected = spec.beforeAccept === undefined
410
- ? undefined
411
- : await spec.beforeAccept({ toolCallId, parameters, signal, ctx });
412
- // Accept-as-is + terminate only. Shape is not an admission gate
413
- // (第 0 条 / ADR 0055); sole-final barrier is ledger-owned (#575).
414
- return {
415
- content: [{ type: "text", text: spec.acceptedText }],
416
- details: projected === undefined ? parameters : projected,
417
- terminate: true,
418
- };
419
- },
420
- });
421
- roleHost.on("before_agent_start", (event) => {
422
- if (soul === undefined)
423
- throw new Error(`${spec.role} 职分未装载`);
424
- const tail = `\n\n<${spec.soulTag}_soul>\n${soul}\n</${spec.soulTag}_soul>`;
425
- return { systemPrompt: `${event.systemPrompt}${tail}` };
426
- });
427
- }
428
- const all = roleHost.getAllTools().map((tool) => tool.name);
429
- if (all.filter((name) => name === spec.tool.name).length !== 1) {
430
- throw new Error(`${spec.role} required tool collision or missing: ${spec.tool.name}`);
431
- }
432
- },
433
- };
434
- }
435
- export function createGleanerLeftRoleRuntime(roleHost, dependencies) {
436
- return createFiledOfficerRuntime(roleHost, {
437
- role: "gleaner-left",
438
- tool: GLEANER_LEFT_TOOL_SPEC,
439
- acceptedText: GLEANER_LEFT_ACCEPTED_TEXT,
440
- soulTag: "gleaner-left",
441
- }, dependencies);
442
- }
443
- function decodeGleanerLeftBase(getFlag) {
444
- const baseRevision = getFlag(GLEANER_LEFT_BASE_FLAG.name);
445
- if (typeof baseRevision !== "string" || !baseRevision.trim()) {
446
- throw new Error("Gleaner-left role requires --ak-gleaner-left-base");
447
- }
448
- return baseRevision;
449
- }
450
- export function createInspectorRoleRuntime(roleHost, dependencies) {
451
- return createFiledOfficerRuntime(roleHost, {
452
- role: "inspector",
453
- tool: INSPECTOR_TOOL_SPEC,
454
- acceptedText: INSPECTOR_ACCEPTED_TEXT,
455
- soulTag: "inspector",
456
- }, dependencies);
457
- }
458
- /** #639: direct Gatekeeper public seat on the shared filed-officer envelope. */
459
- export function createGatekeeperRoleRuntime(roleHost, dependencies) {
460
- return createFiledOfficerRuntime(roleHost, {
461
- role: "gatekeeper",
462
- tool: GATEKEEPER_TOOL_SPEC,
463
- acceptedText: GATEKEEPER_ACCEPTED_TEXT,
464
- soulTag: "gatekeeper",
465
- }, dependencies);
466
- }
467
- /** #639: direct Navigator public seat on the shared filed-officer envelope. */
468
- export function createNavigatorRoleRuntime(roleHost, dependencies) {
469
- return createFiledOfficerRuntime(roleHost, {
470
- role: "navigator",
471
- tool: NAVIGATOR_TOOL_SPEC,
472
- acceptedText: NAVIGATOR_ACCEPTED_TEXT,
473
- soulTag: "navigator",
474
- }, dependencies);
475
- }
476
- /** #675: public 审刑院 seat on the shared filed-officer envelope. */
477
- export function createAuditorRoleRuntime(roleHost, dependencies) {
478
- const base = createFiledOfficerRuntime(roleHost, {
479
- role: "auditor",
480
- tool: AUDITOR_TOOL_SPEC,
481
- acceptedText: AUDITOR_ACCEPTED_TEXT,
482
- soulTag: "auditor",
483
- }, dependencies);
484
- return {
485
- async activate() {
486
- await base.activate();
487
- // Same tools whether nested or direct (#675): dossier tool always registered.
488
- // Source run: only the shared --source-run input face (never own-run fallback).
489
- const { createAuditorDossierTool, AUDITOR_DOSSIER_TOOL_NAME } = await import("./auditor-dossier-tool.js");
490
- const { AK_ROLE_AUDITOR_SOURCE_RUN_ENV } = await import("./auditor-soul.js");
491
- const already = roleHost.getAllTools().some((tool) => tool.name === AUDITOR_DOSSIER_TOOL_NAME);
492
- if (already)
493
- return;
494
- const sourceRun = typeof process.env[AK_ROLE_AUDITOR_SOURCE_RUN_ENV] === "string"
495
- && process.env[AK_ROLE_AUDITOR_SOURCE_RUN_ENV].trim() !== ""
496
- ? process.env[AK_ROLE_AUDITOR_SOURCE_RUN_ENV].trim()
497
- : undefined;
498
- roleHost.registerTool(createAuditorDossierTool(sourceRun));
499
- },
500
- };
501
- }
502
- /**
503
- * LLM typed court-target assertion from diarist output (ADR 0075 / #779).
504
- * null/absent = true-unbound; positive safe integer = ticket N.
505
- * Shape-only read of the typed field — not content judgment of free text.
506
- * Any other shape fails honestly — never washes into unbound.
507
- */
508
- function readDiaristTicketAssertion(submitted) {
509
- if (submitted === undefined || !("ticketNumber" in submitted)) {
510
- return { kind: "true-unbound" };
511
- }
512
- const raw = submitted.ticketNumber;
513
- if (raw === null)
514
- return { kind: "true-unbound" };
515
- if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) {
516
- return { kind: "ticket", ticketNumber: raw };
517
- }
518
- if (typeof raw === "string" && /^[1-9]\d*$/.test(raw)) {
519
- const n = Number(raw);
520
- if (Number.isSafeInteger(n) && n >= 1) {
521
- return { kind: "ticket", ticketNumber: n };
522
- }
523
- }
524
- throw new Error("diarist ticketNumber must be a safe integer >= 1, null, or absent");
525
- }
526
- /** Run coordinates + optional pre-bound ticket from durable pages (#779). */
527
- function readDiaristRunCoordinates() {
528
- const runDirectory = process.env.AK_ROLE_RUN_DIR;
529
- if (typeof runDirectory !== "string" || runDirectory.trim() === "") {
530
- throw new Error("diarist accept requires AK_ROLE_RUN_DIR");
531
- }
532
- const admittedPath = join(runDirectory, "admitted-request.json");
533
- const admitted = JSON.parse(readFileSync(admittedPath, "utf8"));
534
- if (typeof admitted.projectRoot !== "string" || admitted.projectRoot.trim() === "") {
535
- throw new Error(`diarist admitted-request missing projectRoot (${admittedPath})`);
536
- }
537
- const bound = typeof admitted.ticketNumber === "number" &&
538
- Number.isSafeInteger(admitted.ticketNumber) &&
539
- admitted.ticketNumber >= 1
540
- ? admitted.ticketNumber
541
- : undefined;
542
- return {
543
- runDirectory,
544
- projectRoot: admitted.projectRoot,
545
- home: homeFromRunDirectory(runDirectory),
546
- ...(bound === undefined ? {} : { boundTicketNumber: bound }),
547
- };
548
- }
549
- /**
550
- * #708 / #779: 起居郎 public seat on the shared filed-officer envelope.
551
- * Semantic collection happened in this role's own turn (LLM finds materials).
552
- * Accept hook: bind typed ticket assertion → idempotent sitian append of whole
553
- * blocks the LLM submitted. No frozen catalog, no quote/ticket reverse-verify
554
- * of LLM output. Machine facts never come from model self-report (锚定宪法).
555
- */
556
- export function createDiaristRoleRuntime(roleHost, dependencies) {
557
- return createFiledOfficerRuntime(roleHost, {
558
- role: "diarist",
559
- tool: DIARIST_TOOL_SPEC,
560
- acceptedText: DIARIST_ACCEPTED_TEXT,
561
- soulTag: "diarist",
562
- beforeAccept: async ({ parameters }) => {
563
- const submitted = parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
564
- ? parameters
565
- : undefined;
566
- // LLM cannot identify the court target — escalate without machine facts.
567
- // Strip sitian (锚定宪法) and ticketNumber (must not leak into
568
- // caller-visible admitted typed key via decisiveFacts mirror).
569
- if (submitted?.status === "escalate") {
570
- if (!("sitian" in submitted) && !("ticketNumber" in submitted)) {
571
- return submitted;
572
- }
573
- const stripped = { ...submitted };
574
- delete stripped.sitian;
575
- delete stripped.ticketNumber;
576
- return stripped;
577
- }
578
- const assertion = readDiaristTicketAssertion(submitted);
579
- const coords = readDiaristRunCoordinates();
580
- // Already bound before the turn (typed handoff / resume): skip re-recognition
581
- // and commit under that identity (ADR 0075 已绑定 ticket 优先).
582
- const ticketNumber = coords.boundTicketNumber !== undefined
583
- ? coords.boundTicketNumber
584
- : assertion.kind === "ticket"
585
- ? assertion.ticketNumber
586
- : undefined;
587
- // true-unbound → 无录 (no volume). Cannot-identify must arrive as
588
- // status=escalate above — never as silent unbound.
589
- if (ticketNumber === undefined) {
590
- if (submitted === undefined || !("sitian" in submitted)) {
591
- return { ...(submitted ?? { receipt: parameters }), ticketNumber: null };
592
- }
593
- const stripped = {
594
- ...submitted,
595
- ticketNumber: null,
596
- };
597
- delete stripped.sitian;
598
- return stripped;
599
- }
600
- if (coords.boundTicketNumber === undefined) {
601
- await bindTicketNumberOnRunDirectory(coords.runDirectory, ticketNumber);
602
- }
603
- const facts = await commitDiaristEntries({
604
- ticketNumber,
605
- cwd: coords.projectRoot,
606
- home: coords.home,
607
- entries: projectDiaristEntries(parameters),
608
- });
609
- return {
610
- ...(submitted ?? { receipt: parameters }),
611
- ticketNumber,
612
- sitian: facts,
613
- };
614
- },
615
- }, dependencies);
616
- }
617
- /** Countersign status words the queue reads (#753). */
618
- const COUNTERSIGN_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
619
- /**
620
- * Plain-language re-ask when countersignStatus is not a known queue word.
621
- * Back to countersign itself — notary is not summoned (#753).
622
- */
623
- const COUNTERSIGN_STATUS_REASK = "countersignStatus 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。";
624
- export function createCountersignRoleRuntime(roleHost, dependencies, hostActions) {
625
- // Notary inner gate difference only — lifecycle stays on the shared envelope.
626
- // Pointer-only summons: officer self-fetches from run dossier (#632 / ADR 0079).
627
- // #753 queue: read countersignStatus only — escalate skips gate (thrown to caller);
628
- // unreadable status returns to countersign; else notary inner gate.
629
- const beforeAccept = hostActions !== undefined && roleHost.requireGatekeeperPass !== undefined
630
- ? async ({ toolCallId, parameters, signal, ctx }) => {
631
- const record = parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
632
- ? parameters
633
- : undefined;
634
- const status = record !== undefined && typeof record.countersignStatus === "string"
635
- ? record.countersignStatus
636
- : undefined;
637
- if (status === undefined || !COUNTERSIGN_QUEUE_STATUSES.has(status)) {
638
- // Parent status unreadable → back to countersign itself; do not summon notary,
639
- // do not forge an officer bounce face (#753).
640
- throw new ParentQueueReaskError(COUNTERSIGN_STATUS_REASK);
641
- }
642
- if (status === "escalate") {
643
- // Parent escalate → throw to caller as-is; notary does not attend (#753).
644
- return undefined;
645
- }
646
- await roleHost.requireGatekeeperPass({
647
- context: ctx,
648
- subject: { kind: "countersign_verdict" },
649
- ...(signal === undefined ? {} : { signal }),
650
- hostActions,
651
- toolCallId,
652
- });
653
- }
654
- : undefined;
655
- return createFiledOfficerRuntime(roleHost, {
656
- role: "countersign",
657
- tool: COUNTERSIGN_TOOL_SPEC,
658
- acceptedText: COUNTERSIGN_ACCEPTED_TEXT,
659
- soulTag: "countersign",
660
- ...(beforeAccept === undefined ? {} : { beforeAccept }),
661
- }, dependencies);
662
- }
663
- export function createRoleRuntimeExtension(dependencies) {
664
- return (envelopeHost) => {
665
- let projectClosedSubmission = async () => {
666
- throw new Error("角色终局投射接缝尚未初始化");
667
- };
668
- const roleHost = createSubmissionLedgerHost(envelopeHost.host, new Map(PACKAGED_ROLE_REGISTRY.map(({ role, outputTool }) => [outputTool, role])), failInfrastructure, async (projection, context) => projectClosedSubmission(projection, context));
669
- roleHost.registerFlag(ROLE_FLAG.name, ROLE_FLAG.definition);
670
- // Reviewer transport flags: shared envelope owns registration (ADR 0018).
671
- for (const flag of REVIEWER_TRANSPORT_FLAGS) {
672
- roleHost.registerFlag(flag.name, flag.definition);
673
- }
674
- for (const flag of NOTARY_TRANSPORT_FLAGS) {
675
- roleHost.registerFlag(flag.name, flag.definition);
676
- }
677
- for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
678
- roleHost.registerFlag(flag.name, flag.definition);
679
- }
680
- // Collector transport flags: shared envelope owns registration (ADR 0018 / #676 E).
681
- for (const flag of COLLECTOR_TRANSPORT_FLAGS) {
682
- roleHost.registerFlag(flag.name, flag.definition);
683
- }
684
- // Station-child identity (#840): omit navigator attendance. One flag.
685
- roleHost.registerFlag(STATION_CHILD_FLAG.name, STATION_CHILD_FLAG.definition);
686
- let admitted = false;
687
- let selectedRole;
688
- /** Live Reviewer parent activation for envelope agent_start prompt assembly. */
689
- let activeReviewerParent;
690
- /** Envelope-owned Reviewer Skill expansion state (ADR 0018 — not a role-module facade). */
691
- let reviewerOriginalRequest;
692
- let reviewerExpansionCaptured = false;
693
- let navigatorAttendance;
694
- // #351: session-lifecycle owner for periodic OAuth refresh (orthogonal to role admission).
695
- let pendingNavigatorPresentation;
696
- let pendingNavigatorSettlement;
697
- let navigatorWorkContext;
698
- /** toolCallId → fact+evidence; one-shot projected onto durable tool_result (#475). */
699
- const pendingInfrastructureFailures = new Map();
700
- // Envelope-owned execute→tool_result bridge for submission non-pass (ADR 0018 / #525).
701
- const pendingSubmissionNonPassByToolCallId = new Map();
702
- let engineDetourRegistered = false;
703
- // #288 primary-session thin adapter. The policy is the sole budget owner;
704
- // terminating-tool rejections and mechanical delivery requests share two turns.
705
- let receiptDelivery = createReceiptDeliveryPolicy();
706
- let noReceiptRecorded = false;
707
- // Public-run fetch observation (in-process-session statusAwareFetch face).
708
- let priorFetch;
709
- let fetchWrapped = false;
710
- const settleNavigatorProjection = async (settlement) => {
711
- const attendance = navigatorAttendance;
712
- if (settlement === undefined || attendance === undefined)
713
- return;
714
- const workContext = navigatorWorkContext;
715
- const pending = (async () => {
716
- if (settlement.kind !== "accepted") {
717
- await attendance.settle(settlement);
718
- return;
719
- }
720
- const settlePromise = attendance.settle(settlement);
721
- // Attach catch immediately so a late rejection after grace timeout cannot
722
- // surface as unhandledRejection / stale-ctx after session dispose (#675).
723
- void settlePromise.catch(() => undefined);
724
- const raced = await raceNavigatorGrace(settlePromise, NAVIGATOR_POST_ROLE_GRACE_MS);
725
- if (raced.status !== "timeout")
726
- return;
727
- if (pendingNavigatorPresentation === undefined) {
728
- const routePlaybookReadFailure = attendance.knownRoutePlaybookReadFailure?.();
729
- const report = {
730
- disposition: "unavailable",
731
- unavailableReason: "Navigator exceeded post-role delivery grace",
732
- unavailableSource: "unknown",
733
- unavailableCause: "unknown",
734
- ...(routePlaybookReadFailure === undefined ? {} : { routePlaybookReadFailure }),
735
- };
736
- const event = {
737
- version: 1,
738
- disposition: "unavailable",
739
- invocationId: "post-role-grace-timeout",
740
- role: settlement.role,
741
- phase: settlement.phase,
742
- subjectKey: workContext?.subjectKey ?? "",
743
- unavailableReason: "Navigator exceeded post-role delivery grace",
744
- unavailableSource: "unknown",
745
- unavailableCause: "unknown",
746
- ...(routePlaybookReadFailure === undefined ? {} : { routePlaybookReadFailure }),
747
- };
748
- pendingNavigatorPresentation = { event, report };
749
- }
750
- await attendance.dispose();
751
- })();
752
- pendingNavigatorSettlement = pending;
753
- await pending;
754
- };
755
- projectClosedSubmission = async (projection, context) => projectClosedSubmissionLifecycle(projection, context, navigatorPhase(roleHost, projection.role), () => receiptDelivery.recordAccepted(), settleNavigatorProjection);
756
- roleHost.on("input", (event) => {
757
- const role = roleHost.getFlag(ROLE_FLAG.name);
758
- if (role !== undefined && !admitted)
759
- return { action: "handled" };
760
- // Reviewer: recover original request; Pi argv may already carry native form.
761
- if (role === "reviewer"
762
- && admitted
763
- && activeReviewerParent !== undefined
764
- && reviewerOriginalRequest === undefined) {
765
- reviewerOriginalRequest =
766
- roleHost.capabilities?.skillOriginalRequest?.(activeReviewerParent.skillBinding.name, event.text) ?? event.text;
767
- return { action: "continue" };
768
- }
769
- return { action: "continue" };
770
- });
771
- roleHost.on("before_agent_start", async (event, ctx) => {
772
- const role = roleHost.getFlag(ROLE_FLAG.name);
773
- if (role === undefined)
774
- return;
775
- if (!admitted || selectedRole !== role) {
776
- failInfrastructure(new ActivationBarrierError(role), ctx);
777
- }
778
- // Notary session bound: envelope-owned lifecycle write (ADR 0018 / #582).
779
- // Ticket flag register/read + session entry live here; role projects admitted bound only.
780
- if (role === "notary") {
781
- const bound = projectNotaryBoundFromFlags((name) => roleHost.getFlag(name));
782
- if (bound !== undefined) {
783
- ctx.sessionManager.appendCustomEntry?.(NOTARY_SESSION_BOUND_ENTRY, bound);
784
- }
785
- }
786
- if (navigatorAttendance !== undefined && navigatorWorkContext !== undefined && navigatorWorkContext.contextError === undefined) {
787
- // Flagged roles already have a concrete packet/task/case/review input.
788
- // A bare Judge (and other bare packaged entrypoint) gets its concrete
789
- // user task at this seam; do not copy the assembled system prompt.
790
- // Replacement is keyed by typed subject provenance, never prose prefixes.
791
- // Soft session_start placeholders (no materials yet) recover here; hard
792
- // contextError from true loader failures stays poisoned and honest.
793
- if (navigatorWorkContext.subjectProvenance === "placeholder") {
794
- const subject = event.prompt.trim();
795
- if (subject !== "") {
796
- const root = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
797
- const subjectProvenance = "user_prompt";
798
- const priorAuthority = navigatorWorkContext.authority;
799
- const authority = typeof priorAuthority === "string" && priorAuthority.trim() !== ""
800
- ? priorAuthority
801
- : subject;
802
- navigatorWorkContext = {
803
- subjectKey: navigatorSubjectKey(root, subject, subjectProvenance),
804
- subject,
805
- authority,
806
- subjectProvenance,
807
- };
808
- await navigatorAttendance.setWorkContext(navigatorWorkContext);
809
- }
810
- }
811
- }
812
- navigatorAttendance?.prepare();
813
- // Envelope-owned Reviewer expansion capture + parent prompt assembly (no role-module callback).
814
- if (role === "reviewer" && activeReviewerParent !== undefined) {
815
- if (!reviewerExpansionCaptured) {
816
- if (reviewerOriginalRequest === undefined
817
- || activeReviewerParent.skillBinding.captureExpansion(roleHost.capabilities?.skillExpansion(event.prompt), reviewerOriginalRequest) === undefined) {
818
- failInfrastructure(new Error("Canonical code-review Skill expansion did not match the captured request"), ctx);
819
- }
820
- reviewerExpansionCaptured = true;
821
- }
822
- const specDisposition = activeReviewerParent.getSpecDisposition();
823
- return {
824
- systemPrompt: assembleReviewerParentSystemPrompt({
825
- baseSystemPrompt: event.systemPrompt,
826
- soul: activeReviewerParent.soul,
827
- ...(specDisposition === undefined ? {} : { specDisposition }),
828
- }),
829
- };
830
- }
831
- // #676 E / J1: collector materials + drift gates share this envelope hook (no parallel register).
832
- if (role === "collector" && activeCollector !== undefined) {
833
- const options = event.systemPromptOptions;
834
- if (options.skills && options.skills.length > 0) {
835
- failInfrastructure(activeCollector.ledger.latchFatal("通进司检测到系统提示中的环境 skills"), ctx);
836
- }
837
- if (options.contextFiles && options.contextFiles.length > 0) {
838
- failInfrastructure(activeCollector.ledger.latchFatal("通进司检测到系统提示中的环境 context files"), ctx);
839
- }
840
- if (typeof options.appendSystemPrompt === "string"
841
- && options.appendSystemPrompt.trim().length > 0) {
842
- failInfrastructure(activeCollector.ledger.latchFatal("通进司检测到 appendSystemPrompt 漂移"), ctx);
843
- }
844
- if (!collectorFirstDispatchDone) {
845
- collectorFirstDispatchDone = true;
846
- activeCollector.ledger.recordActivation(activeCollector.clock);
847
- }
848
- return {
849
- systemPrompt: collectorBusiness.assembleMaterials(activeCollector, event.systemPrompt),
850
- };
851
- }
852
- });
853
- roleHost.on("tool_result", async (event) => {
854
- const role = selectedRole;
855
- if (role === undefined)
856
- return;
857
- // #676 E / J1: collector operational bookkeeping on the shared tool_result seam.
858
- if (role === "collector" && activeCollector !== undefined) {
859
- collectorBusiness.onToolResult(activeCollector, event);
860
- }
861
- const pendingInfra = pendingInfrastructureFailures.get(event.toolCallId);
862
- const isRoleInfrastructureFailure = pendingInfra !== undefined;
863
- if (pendingInfra !== undefined)
864
- pendingInfrastructureFailures.delete(event.toolCallId);
865
- // One-shot project fact + typed evidence so live settlement and durable session agree.
866
- const infrastructureDetails = pendingInfra?.details;
867
- const classified = infrastructureDetails === undefined
868
- ? event
869
- : { ...event, details: infrastructureDetails };
870
- const isOutputTool = event.toolName === navigatorOutputTool(role);
871
- const outputClassification = isOutputTool ? classifyPackagedRoleTerminalResult(classified) : undefined;
872
- if (isRoleInfrastructureFailure || outputClassification?.kind === "infrastructure") {
873
- receiptDelivery.stopForInfrastructure();
874
- }
875
- else if (isOutputTool && outputClassification?.kind === "nonterminal" && event.isError) {
876
- const reason = (event.content ?? [])
877
- .map((part) => part.type === "text" && "text" in part ? part.text : "")
878
- .join("")
879
- .trim() || "terminating tool rejected";
880
- receiptDelivery.recordRejected(reason);
881
- }
882
- // Accepted/human terminal projection belongs exclusively to typed ledger
883
- // closure. tool_result retains only infrastructure settlement.
884
- const settlement = isRoleInfrastructureFailure || outputClassification?.kind === "infrastructure"
885
- ? publicNavigatorSettlement(role, navigatorPhase(roleHost, role), classified)
886
- : undefined;
887
- await settleNavigatorProjection(settlement);
888
- // Persist typed infrastructure-failure fact onto the role session toolResult so
889
- // exact-session restart shares the same durable completion classification.
890
- if (infrastructureDetails !== undefined) {
891
- return { details: infrastructureDetails, isError: true };
892
- }
893
- // Submission non-pass: throw kept message text for the model; project the
894
- // envelope-bound structured result onto session details at this tool_result seam.
895
- const submissionNonPass = pendingSubmissionNonPassByToolCallId.get(event.toolCallId);
896
- if (submissionNonPass !== undefined) {
897
- pendingSubmissionNonPassByToolCallId.delete(event.toolCallId);
898
- return { details: submissionNonPass, isError: true };
899
- }
900
- // Recommendation rides the accepted settlement record's content so the one
901
- // mandatory last-ak_*_output extraction surfaces route/next/reason without
902
- // a second file, grep, or nesting step. Receipt details stay contract-pure;
903
- // unavailable/no-advice leave the settlement untouched.
904
- if (event.isError)
905
- return;
906
- const decorated = decorateSettlementWithNavigation(event, pendingNavigatorPresentation);
907
- if (decorated === undefined)
908
- return;
909
- return {
910
- content: decorated.content,
911
- };
912
- });
913
- // Queue receipt delivery before `agent_settled`: that event means Pi has
914
- // already decided no queued continuation will run, so a triggerTurn there is
915
- // too late for print/json sessions. `agent_end` is the last production seam
916
- // whose queued next turn is consumed before settlement.
917
- roleHost.on("agent_end", (event, ctx) => {
918
- const lastMessage = event.messages.at(-1);
919
- if (lastMessage?.role === "assistant"
920
- && (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted")) {
921
- // Provider failure/abort has no tool_result event; classify it here so the
922
- // receipt policy cannot turn infrastructure death into an exit-0 lifecycle.
923
- receiptDelivery.stopForInfrastructure();
924
- return;
925
- }
926
- if (receiptDelivery.nextAction() === "request-delivery") {
927
- receiptDelivery.recordDeliveryRequest();
928
- // Keep the package-owned continuation off the public input lifecycle:
929
- // receipt delivery must not be mistaken for later caller input.
930
- envelopeHost.appendEntry("ak-receipt-delivery-request");
931
- try {
932
- sitianReport({
933
- level: "event",
934
- kind: "receipt-delivery",
935
- cwd: ctx.cwd,
936
- sessionParent: ctx.sessionManager.getSessionFile(),
937
- payload: { type: "ak-receipt-delivery-request" },
938
- source: "role-runtime",
939
- });
940
- }
941
- catch { }
942
- envelopeHost.sendMessage({
943
- customType: "ak-receipt-delivery-prompt",
944
- content: RECEIPT_DELIVERY_PROMPT,
945
- display: false,
946
- }, { triggerTurn: true, deliverAs: "followUp" });
947
- }
948
- else if (receiptDelivery.nextAction() === "no-receipt" && !noReceiptRecorded) {
949
- const runPointer = process.env.AK_ROLE_RUN_DIR;
950
- if (runPointer !== undefined) {
951
- noReceiptRecorded = true;
952
- const facts = receiptDelivery.facts({ runPointer, attemptPointer: `current:${runPointer}` });
953
- envelopeHost.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
954
- try {
955
- sitianReport({
956
- level: "event",
957
- kind: "no-receipt-lifecycle",
958
- cwd: ctx.cwd,
959
- sessionParent: ctx.sessionManager.getSessionFile(),
960
- payload: facts,
961
- source: "role-runtime",
962
- });
963
- }
964
- catch { }
965
- }
966
- }
967
- });
968
- roleHost.on("agent_settled", async () => {
969
- if (pendingNavigatorSettlement !== undefined) {
970
- await pendingNavigatorSettlement;
971
- }
972
- // Do not auto-drain Navigator on ordinary mid-turn agent_settled.
973
- // Multi-turn roles (#162 coder) fire agent_settled after every tool turn;
974
- // discarding a healthy/in-flight prepare there forces a cold prepare on the
975
- // final accepted terminal and races the post-role grace. Keep preparation
976
- // until an accepted/human/infrastructure tool_result settlement (or dispose).
977
- // Provider death without any role tool_result leaves preparation held; the
978
- // next explicit settlement or session end owns it — not mid-turn churn.
979
- pendingNavigatorSettlement = undefined;
980
- const presentation = pendingNavigatorPresentation;
981
- pendingNavigatorPresentation = undefined;
982
- if (presentation === undefined)
983
- return;
984
- await envelopeHost.sendMessage({
985
- customType: NAVIGATOR_EVENT_TYPE,
986
- content: formatNavigatorReport(presentation.report),
987
- display: true,
988
- details: presentation.event,
989
- }, { triggerTurn: false });
990
- });
991
- roleHost.on("session_shutdown", async () => {
992
- // #351: stop OAuth keepalive first so shutdown yields zero further ticks.
993
- envelopeHost.stopKeepalive();
994
- if (fetchWrapped && priorFetch !== undefined) {
995
- globalThis.fetch = priorFetch;
996
- priorFetch = undefined;
997
- fetchWrapped = false;
998
- }
999
- // #676 J4: collector fatal latch must surface nonzero exit on shutdown (envelope-owned).
1000
- if (selectedRole === "collector"
1001
- && activeCollector !== undefined
1002
- && activeCollector.ledger.fatal) {
1003
- if (process.exitCode === undefined || process.exitCode === 0) {
1004
- process.exitCode = 1;
1005
- }
1006
- }
1007
- // Flush any still-pending affirmative attendance before teardown. Accepted
1008
- // grace-timeout paths normally emit on agent_settled; abort can skip that hook.
1009
- const presentation = pendingNavigatorPresentation;
1010
- pendingNavigatorPresentation = undefined;
1011
- if (presentation !== undefined) {
1012
- try {
1013
- await envelopeHost.sendMessage({
1014
- customType: NAVIGATOR_EVENT_TYPE,
1015
- content: formatNavigatorReport(presentation.report),
1016
- display: true,
1017
- details: presentation.event,
1018
- }, { triggerTurn: false });
1019
- }
1020
- catch {
1021
- // Teardown must not mask the original role failure cause.
1022
- }
1023
- }
1024
- await navigatorAttendance?.dispose();
1025
- navigatorAttendance = undefined;
1026
- pendingNavigatorSettlement = undefined;
1027
- pendingInfrastructureFailures.clear();
1028
- pendingSubmissionNonPassByToolCallId.clear();
1029
- observationFace.reset();
1030
- });
1031
- const hostActions = {
1032
- failInfrastructure(error, ctx, toolCallId) {
1033
- if (toolCallId !== undefined) {
1034
- pendingInfrastructureFailures.set(toolCallId, buildPendingInfrastructureFailure(error));
1035
- }
1036
- failInfrastructure(error, ctx);
1037
- },
1038
- bindSubmissionNonPass(toolCallId, result) {
1039
- pendingSubmissionNonPassByToolCallId.set(toolCallId, result);
1040
- },
1041
- };
1042
- const judge = createJudgeRoleRuntime(roleHost, {
1043
- loadSoul: dependencies.loadJudgeSoul,
1044
- }, hostActions);
1045
- const fixer = createFixerRoleRuntime(roleHost, {
1046
- async loadSoul() {
1047
- if (dependencies.loadFixerSoul === undefined) {
1048
- throw new Error("fixer soul loader is not configured");
1049
- }
1050
- return dependencies.loadFixerSoul();
1051
- },
1052
- async loadPacket(path) {
1053
- if (dependencies.loadFixPacket === undefined) {
1054
- throw new Error("Fixer packet loader is not configured");
1055
- }
1056
- return dependencies.loadFixPacket(path);
1057
- },
1058
- }, hostActions);
1059
- const coder = createCoderRoleRuntime(roleHost, {
1060
- async loadSoul() {
1061
- if (dependencies.loadCoderSoul === undefined) {
1062
- throw new Error("coder soul loader is not configured");
1063
- }
1064
- return dependencies.loadCoderSoul();
1065
- },
1066
- async loadTask(path) {
1067
- if (dependencies.loadCoderTask === undefined) {
1068
- throw new Error("Coder task loader is not configured");
1069
- }
1070
- return dependencies.loadCoderTask(path);
1071
- },
1072
- ...(dependencies.loadCanonicalSkillBinding === undefined
1073
- ? {}
1074
- : {
1075
- loadCanonicalSkillBinding: (name) => dependencies.loadCanonicalSkillBinding(name),
1076
- }),
1077
- }, hostActions);
1078
- const reviewer = createReviewerRoleRuntime(roleHost, {
1079
- async loadSoul() {
1080
- if (dependencies.loadReviewerSoul === undefined) {
1081
- throw new Error("reviewer soul loader is not configured");
1082
- }
1083
- return dependencies.loadReviewerSoul();
1084
- },
1085
- async createPinnedGitReader() {
1086
- if (dependencies.createReviewerPinnedGitReader === undefined)
1087
- throw new Error("Reviewer runtime dependencies are not configured");
1088
- return dependencies.createReviewerPinnedGitReader();
1089
- },
1090
- async loadCanonicalSkillBinding(name) {
1091
- if (dependencies.loadCanonicalSkillBinding === undefined) {
1092
- throw new Error("Reviewer runtime dependencies are not configured");
1093
- }
1094
- return dependencies.loadCanonicalSkillBinding(name);
1095
- },
1096
- ...(dependencies.createReviewerIssueFetcher === undefined
1097
- ? {}
1098
- : { fetchIssue: dependencies.createReviewerIssueFetcher() }),
1099
- async runDispatch(dispatch, options) {
1100
- if (dependencies.runReviewerDispatch === undefined)
1101
- throw new Error("Reviewer runtime dependencies are not configured");
1102
- // #818: request-scoped engine flag reaches axis sub-session via same RoleHost
1103
- // that parent registerEngineDetourTool reads — never ambient process.env.
1104
- return dependencies.runReviewerDispatch(dispatch, {
1105
- ...options,
1106
- getFlag: (name) => roleHost.getFlag(name),
1107
- });
1108
- },
1109
- ...(dependencies.shutdownReviewerAgent === undefined
1110
- ? {}
1111
- : { shutdownAgent: dependencies.shutdownReviewerAgent }),
1112
- }, hostActions);
1113
- const doctor = createDoctorRoleRuntime(roleHost, {
1114
- async loadSoul() { if (!dependencies.loadDoctorSoul)
1115
- throw new Error("Doctor runtime dependencies are not configured"); return dependencies.loadDoctorSoul(); },
1116
- async loadCase(path) { if (!dependencies.loadDoctorCase)
1117
- throw new Error("Doctor runtime dependencies are not configured"); return dependencies.loadDoctorCase(path); },
1118
- async auditCompliance(options) { if (!dependencies.auditDoctorCompliance)
1119
- throw new Error("Doctor runtime dependencies are not configured"); return dependencies.auditDoctorCompliance(options); },
1120
- }, hostActions);
1121
- const notary = createNotaryRoleRuntime(roleHost, {
1122
- async loadSoul() {
1123
- if (!dependencies.loadNotarySoul)
1124
- throw new Error("Notary runtime dependencies are not configured");
1125
- return dependencies.loadNotarySoul();
1126
- },
1127
- async loadSourceRunLocator(path) {
1128
- if (!dependencies.loadNotarySourceRun)
1129
- throw new Error("Notary runtime dependencies are not configured");
1130
- return dependencies.loadNotarySourceRun(path);
1131
- },
1132
- }, hostActions);
1133
- const countersign = createCountersignRoleRuntime(roleHost, {
1134
- async loadSoul() {
1135
- if (!dependencies.loadCountersignSoul)
1136
- throw new Error("Countersign runtime dependencies are not configured");
1137
- return dependencies.loadCountersignSoul();
1138
- },
1139
- }, hostActions);
1140
- const gleanerLeftRuntime = createGleanerLeftRoleRuntime(roleHost, {
1141
- async loadSoul() {
1142
- if (!dependencies.loadGleanerLeftSoul)
1143
- throw new Error("Gleaner-left runtime dependencies are not configured");
1144
- return dependencies.loadGleanerLeftSoul();
1145
- },
1146
- });
1147
- const gleanerLeft = {
1148
- async activate() {
1149
- decodeGleanerLeftBase((name) => envelopeHost.host.getFlag(name));
1150
- return gleanerLeftRuntime.activate();
1151
- },
1152
- };
1153
- const inspector = createInspectorRoleRuntime(roleHost, {
1154
- async loadSoul() {
1155
- if (!dependencies.loadInspectorSoul)
1156
- throw new Error("Inspector runtime dependencies are not configured");
1157
- return dependencies.loadInspectorSoul();
1158
- },
1159
- });
1160
- const gatekeeper = createGatekeeperRoleRuntime(roleHost, {
1161
- async loadSoul() {
1162
- if (!dependencies.loadGatekeeperSoul)
1163
- throw new Error("Gatekeeper runtime dependencies are not configured");
1164
- return dependencies.loadGatekeeperSoul();
1165
- },
1166
- });
1167
- const navigator = createNavigatorRoleRuntime(roleHost, {
1168
- async loadSoul() {
1169
- if (!dependencies.loadNavigatorSoul)
1170
- throw new Error("Navigator runtime dependencies are not configured");
1171
- return dependencies.loadNavigatorSoul();
1172
- },
1173
- });
1174
- const auditor = createAuditorRoleRuntime(roleHost, {
1175
- async loadSoul() {
1176
- if (!dependencies.loadAuditorSoul)
1177
- throw new Error("Auditor runtime dependencies are not configured");
1178
- return dependencies.loadAuditorSoul();
1179
- },
1180
- });
1181
- const diarist = createDiaristRoleRuntime(roleHost, {
1182
- async loadSoul() {
1183
- if (!dependencies.loadDiaristSoul)
1184
- throw new Error("Diarist runtime dependencies are not configured");
1185
- return dependencies.loadDiaristSoul();
1186
- },
1187
- });
1188
- const merger = createMergerRoleRuntime(roleHost, {
1189
- async loadSoul() { if (!dependencies.loadMergerSoul)
1190
- throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerSoul(); },
1191
- async loadInput(path) { if (!dependencies.loadMergerInput)
1192
- throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerInput(path); },
1193
- });
1194
- // #676 E: shared envelope owns collector lifecycle (mode/fork, tool surface,
1195
- // event gates). Role module supplies business activate/tools/materials only.
1196
- let activeCollector;
1197
- let collectorFirstDispatchDone = false;
1198
- const collectorBusiness = createCollectorRoleRuntime(roleHost, {
1199
- async loadSoul() {
1200
- if (dependencies.loadCollectorSoul === undefined) {
1201
- throw new Error("collector soul loader is not configured");
1202
- }
1203
- return dependencies.loadCollectorSoul();
1204
- },
1205
- createTransport() {
1206
- if (dependencies.createCollectorTransport === undefined) {
1207
- throw new Error("Collector GitHub transport is not configured");
1208
- }
1209
- return dependencies.createCollectorTransport();
1210
- },
1211
- createLedger(config, collectorClock, context) {
1212
- const append = context.sessionManager?.appendCustomEntry;
1213
- return createCollectorLedger(config, {
1214
- clock: collectorClock,
1215
- ...(append === undefined
1216
- ? {}
1217
- : {
1218
- journal: {
1219
- append(customType, data) {
1220
- context.sessionManager?.appendCustomEntry?.(customType, data);
1221
- },
1222
- },
1223
- }),
1224
- dossierEntries: context.sessionManager?.getEntries?.() ?? [],
1225
- });
1226
- },
1227
- ...(dependencies.createCollectorClock === undefined
1228
- ? {}
1229
- : { createClock: dependencies.createCollectorClock }),
1230
- ...(dependencies.loadCollectorHandbookSeed === undefined
1231
- ? {}
1232
- : { loadHandbookSeed: dependencies.loadCollectorHandbookSeed }),
1233
- }, hostActions);
1234
- // #676 J1: business tools + tool_call gate register only behind admission/activation.
1235
- // before_agent_start / tool_result collector branches live on the shared envelope hooks above.
1236
- //
1237
- // #676 J4 dispositions for guards removed from the role-private collector module:
1238
- // - skills/contextFiles/appendSystemPrompt fail-closed → shared before_agent_start (above).
1239
- // - ambient skill/prompt/template commands → activate (below).
1240
- // - session_shutdown fatal exitCode → shared session_shutdown (above).
1241
- // - subsequent-input latchFatal + fixed-kickoff rewrite → intentionally not migrated:
1242
- // #676 materials-first multi-turn abolished the single-shot fixed kickoff; multi-turn
1243
- // observe/request/wait requires later inputs. Judge r1: fixed-kickoff equality delete is authorized.
1244
- // - tool sourceInfo path override check → intentionally not migrated: depended on
1245
- // packageExtensionPath deleted under ADR 0018 / #676 E envelope ownership; uniqueness
1246
- // + setActiveTools inventory checks remain on activate.
1247
- let collectorToolCallRegistered = false;
1248
- const collector = {
1249
- async activate(context, event) {
1250
- activeCollector = undefined;
1251
- collectorFirstDispatchDone = false;
1252
- // Envelope-owned mode / fork-reload gates (not role-private lifecycle).
1253
- if (context.mode !== "print" && context.mode !== "json") {
1254
- throw new Error(`Collector supports only print or json mode (got ${context.mode})`);
1255
- }
1256
- if (event.reason === "fork" || event.reason === "reload") {
1257
- throw new Error(`Collector does not support session_start reason ${event.reason}`);
1258
- }
1259
- // #676 J4: ambient skill/prompt/template commands fail closed at activation.
1260
- const commands = roleHost.getCommands?.() ?? [];
1261
- const ambientCommands = commands.filter((command) => {
1262
- const name = command.name.toLowerCase();
1263
- return (name.includes("skill")
1264
- || name.includes("prompt")
1265
- || name.startsWith("template"));
1266
- });
1267
- if (ambientCommands.length > 0) {
1268
- throw new Error(`Collector detected ambient instruction commands: ${ambientCommands.map((c) => c.name).join(", ")}`);
1269
- }
1270
- // Business tools behind admission barrier (inert-without-role invariant).
1271
- // First activation: fail closed if a required name is already occupied.
1272
- // Later activations reuse the once-registered tools (registerBusinessTools is idempotent).
1273
- const preExisting = roleHost.getAllTools();
1274
- const alreadyRegistered = COLLECTOR_REQUIRED_TOOLS.every((required) => preExisting.some((tool) => tool.name === required));
1275
- if (!alreadyRegistered) {
1276
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
1277
- const prior = preExisting.filter((tool) => tool.name === required);
1278
- if (prior.length > 0) {
1279
- throw new Error(`Collector required tool name collision: ${required}`);
1280
- }
1281
- }
1282
- }
1283
- collectorBusiness.registerBusinessTools(() => activeCollector);
1284
- const allTools = roleHost.getAllTools();
1285
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
1286
- const matches = allTools.filter((tool) => tool.name === required);
1287
- if (matches.length === 0) {
1288
- throw new Error(`Collector required tool missing: ${required}`);
1289
- }
1290
- if (matches.length > 1) {
1291
- throw new Error(`Collector required tool name collision: ${required}`);
1292
- }
1293
- }
1294
- roleHost.setActiveTools([...COLLECTOR_REQUIRED_TOOLS]);
1295
- const active = new Set(roleHost.getActiveTools());
1296
- for (const required of COLLECTOR_REQUIRED_TOOLS) {
1297
- if (!active.has(required)) {
1298
- throw new Error(`Collector failed to activate required tool ${required}`);
1299
- }
1300
- }
1301
- for (const name of active) {
1302
- if (!COLLECTOR_REQUIRED_TOOLS.includes(name)) {
1303
- throw new Error(`Collector active tool surface includes unexpected ${name}`);
1304
- }
1305
- }
1306
- // Seat-scoped tool_call gate — registered only after collector admission (no install-time tool_call).
1307
- if (!collectorToolCallRegistered) {
1308
- collectorToolCallRegistered = true;
1309
- roleHost.on("tool_call", (toolEvent) => {
1310
- if (activeCollector === undefined || selectedRole !== "collector")
1311
- return;
1312
- if (!COLLECTOR_REQUIRED_TOOLS.includes(toolEvent.toolName)) {
1313
- return {
1314
- block: true,
1315
- reason: `通进司禁用工具 ${toolEvent.toolName}`,
1316
- };
1317
- }
1318
- return collectorBusiness.onToolCall(activeCollector, toolEvent);
1319
- });
1320
- }
1321
- const activation = await collectorBusiness.activate(context);
1322
- if (activation.ledger.activationRecorded) {
1323
- collectorFirstDispatchDone = true;
1324
- }
1325
- activeCollector = activation;
1326
- },
1327
- };
1328
- const clock = dependencies.activationClock ?? (() => new Date().toISOString());
1329
- const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
1330
- const observationFace = createToolExecutionObservationFace({
1331
- role: () => selectedRole,
1332
- admitted: () => admitted,
1333
- clock: dependencies.toolExecutionObservationClock ?? clock,
1334
- monoNow: dependencies.toolExecutionObservationMonoNow ?? systemToolExecutionObservationMonoNow,
1335
- write: dependencies.toolExecutionObservationWriter ?? writeToolExecutionObservationRecord,
1336
- });
1337
- // ExtensionRunner.emit catches ordinary handler throws, emits extension error, and continues.
1338
- // Observation plane failures must still hit the shared infrastructure termination path
1339
- // (abort + nonzero print/json exit) with the original cause before that swallow.
1340
- const observe = async (run, ctx) => {
1341
- try {
1342
- await run();
1343
- }
1344
- catch (error) {
1345
- failInfrastructure(error, ctx);
1346
- }
1347
- };
1348
- roleHost.on("tool_execution_start", async (event, ctx) => {
1349
- await observe(() => observationFace.onStart(event), ctx);
1350
- });
1351
- roleHost.on("tool_execution_update", async (event, ctx) => {
1352
- await observe(() => observationFace.onUpdate(event), ctx);
1353
- });
1354
- roleHost.on("tool_execution_end", async (event, ctx) => {
1355
- await observe(() => observationFace.onEnd(event), ctx);
1356
- });
1357
- // Public Role run: record typed non-success HTTP for error evidence + v1 resume.
1358
- // Same observation owner as in-process-session statusAwareFetch → typed-provider-http
1359
- // sidecar (settlement already merges observation.httpStatus into knownFailure).
1360
- // after_provider_response covers the success-path onResponse face; fetch wrap covers
1361
- // non-2xx Responses where openai-completions throws before onResponse (#675).
1362
- const recordHttpObservation = async (status, provider, ctx) => {
1363
- const runDir = process.env.AK_ROLE_RUN_DIR;
1364
- if (typeof runDir !== "string" || runDir.trim() === "")
1365
- return;
1366
- try {
1367
- await recordTypedProviderHttpStatus(runDir, { httpStatus: status, provider });
1368
- }
1369
- catch (error) {
1370
- if (status >= 200 &&
1371
- status < 300 &&
1372
- error instanceof Error &&
1373
- "code" in error &&
1374
- error.code === "ENOENT") {
1375
- return;
1376
- }
1377
- failInfrastructure(error, ctx);
1378
- }
1379
- };
1380
- roleHost.on("after_provider_response", async (event, ctx) => {
1381
- const status = event.status;
1382
- if (typeof status !== "number")
1383
- return;
1384
- const fromCtx = ctx.model?.provider;
1385
- const provider = typeof fromCtx === "string" && fromCtx.trim() !== ""
1386
- ? fromCtx
1387
- : "unknown";
1388
- await recordHttpObservation(status, provider, ctx);
1389
- });
1390
- roleHost.on("session_start", async (event, ctx) => {
1391
- // Scope fetch observation to this public run (in-process-session statusAwareFetch face).
1392
- if (!fetchWrapped && typeof globalThis.fetch === "function") {
1393
- priorFetch = globalThis.fetch.bind(globalThis);
1394
- const underlying = priorFetch;
1395
- globalThis.fetch = (async (input, init) => {
1396
- const response = await underlying(input, init);
1397
- const runDir = process.env.AK_ROLE_RUN_DIR;
1398
- if (typeof runDir === "string"
1399
- && runDir.trim() !== ""
1400
- && typeof response?.status === "number"
1401
- && (response.status < 200 || response.status >= 300)) {
1402
- const provider = typeof ctx.model?.provider === "string" && ctx.model.provider.trim() !== ""
1403
- ? ctx.model.provider
1404
- : "unknown";
1405
- try {
1406
- await recordTypedProviderHttpStatus(runDir, {
1407
- httpStatus: response.status,
1408
- provider,
1409
- });
1410
- }
1411
- catch {
1412
- // Observation must not break the provider stream (same as in-process-session).
1413
- }
1414
- }
1415
- return response;
1416
- });
1417
- fetchWrapped = true;
1418
- }
1419
- admitted = false;
1420
- selectedRole = undefined;
1421
- activeReviewerParent = undefined;
1422
- reviewerOriginalRequest = undefined;
1423
- reviewerExpansionCaptured = false;
1424
- receiptDelivery = createReceiptDeliveryPolicy();
1425
- noReceiptRecorded = false;
1426
- observationFace.reset();
1427
- pendingNavigatorPresentation = undefined;
1428
- pendingNavigatorSettlement = undefined;
1429
- pendingInfrastructureFailures.clear();
1430
- pendingSubmissionNonPassByToolCallId.clear();
1431
- navigatorWorkContext = undefined;
1432
- // #351: OAuth keepalive is orthogonal to --ak-role; start before role early-return
1433
- // so role-less sessions (and reload after shutdown stop) still keep tokens alive.
1434
- envelopeHost.startKeepalive(ctx);
1435
- const rawRole = roleHost.getFlag(ROLE_FLAG.name);
1436
- if (rawRole === undefined)
1437
- return;
1438
- const entry = PACKAGED_ROLE_REGISTRY.find(({ role }) => role === rawRole);
1439
- if (entry === undefined) {
1440
- failInfrastructure(new Error(`Unsupported workflow role: ${String(rawRole)}`), ctx);
1441
- }
1442
- selectedRole = entry.role;
1443
- await navigatorAttendance?.dispose();
1444
- navigatorAttendance = undefined;
1445
- const runtime = {
1446
- event,
1447
- context: ctx,
1448
- judge,
1449
- fixer,
1450
- coder,
1451
- reviewer,
1452
- decodeReviewerAdmitted() {
1453
- return decodeReviewerAdmittedInputs((name) => roleHost.getFlag(name));
1454
- },
1455
- bindReviewerParent(activation) {
1456
- activeReviewerParent = activation;
1457
- },
1458
- decodeNotaryAdmitted() {
1459
- const ticketNumber = readNotaryTicketFlag(roleHost.getFlag(NOTARY_TICKET_FLAG.name));
1460
- return ticketNumber === undefined ? undefined : { ticketNumber };
1461
- },
1462
- collector,
1463
- doctor,
1464
- notary,
1465
- countersign,
1466
- gleanerLeft,
1467
- inspector,
1468
- gatekeeper,
1469
- navigator,
1470
- auditor,
1471
- diarist,
1472
- merger: async () => {
1473
- await merger.activate();
1474
- },
1475
- };
1476
- try {
1477
- // Production topology only (ADR 0048/0049): no test-only ledger hooks.
1478
- // Admit durable session file first so lifecycle getEntries/appendEntry are truthful:
1479
- // deferred SM materialization must not wipe an in-memory principal marker.
1480
- const bookKey = resolveBookKeyFromGit(ctx.cwd);
1481
- const correlation = correlationIdentityFromEnv();
1482
- const sessionFile = ctx.sessionManager.getSessionFile?.() || ctx.sessionManager.getSessionDir?.();
1483
- const ledgerHome = resolveActivationLedgerHomeForPath(sessionFile);
1484
- const session = durableSessionPointer(ctx.sessionManager);
1485
- // Station children (court diarist, inner-gate summons) omit navigator sidecar (#840).
1486
- // Top-level public entry legs still attend automatically.
1487
- // Navigator seat never re-attaches itself — prepare turns already ARE the navigator
1488
- // public activation (prevents summonPublicRole navigator ↔ attendance recursion).
1489
- const isStationChild = roleHost.getFlag(STATION_CHILD_FLAG.name) === true;
1490
- if (dependencies.createNavigatorAttendance !== undefined
1491
- && entry.role !== "navigator"
1492
- && !isStationChild) {
1493
- let work;
1494
- let contextError;
1495
- if (dependencies.loadNavigatorWorkContext === undefined) {
1496
- const fallbackSubjectKey = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
1497
- contextError = new Error("Navigator work context loader is not configured");
1498
- work = { subjectKey: fallbackSubjectKey, subject: `work subject: ${fallbackSubjectKey}`, authority: "", subjectProvenance: "placeholder" };
1499
- }
1500
- else {
1501
- try {
1502
- work = await dependencies.loadNavigatorWorkContext({
1503
- context: ctx,
1504
- role: entry.role,
1505
- phase: navigatorPhase(roleHost, entry.role),
1506
- getFlag: (name) => roleHost.getFlag(name),
1507
- });
1508
- contextError = work.contextError;
1509
- }
1510
- catch (error) {
1511
- // Contract: README.md#Navigator-attendance — a failed context load continues with a typed placeholder work context; the original cause is retained in contextError for the typed unavailable report.
1512
- contextError = navigatorUnavailableError("context", error);
1513
- const fallbackSubjectKey = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
1514
- work = { subjectKey: fallbackSubjectKey, subject: `work subject: ${fallbackSubjectKey}`, authority: "", subjectProvenance: "placeholder" };
1515
- }
1516
- }
1517
- navigatorWorkContext = { ...work, ...(contextError === undefined ? {} : { contextError }) };
1518
- // Shared envelope owns exact invocation principal from admitted session lifecycle.
1519
- // session_start is process activation: resume unfinished principal only when marker
1520
- // role/phase/subjectKey still match; mint for contradictory marker, malformed nearest,
1521
- // missing marker, or terminal already completed.
1522
- const sessionEntries = [...ctx.sessionManager.getEntries()];
1523
- const invocationPhase = navigatorPhase(roleHost, entry.role);
1524
- const lifecyclePrincipal = resolveLifecycleInvocationPrincipal(sessionEntries, {
1525
- role: entry.role,
1526
- phase: invocationPhase,
1527
- subjectKey: work.subjectKey,
1528
- });
1529
- const invocationId = lifecyclePrincipal.invocationId;
1530
- if (!lifecyclePrincipal.resume) {
1531
- const data = {
1532
- invocationId,
1533
- role: entry.role,
1534
- phase: invocationPhase,
1535
- subjectKey: work.subjectKey,
1536
- };
1537
- envelopeHost.appendEntry(NAVIGATOR_INVOCATION_ENTRY, data);
1538
- try {
1539
- sitianReport({
1540
- level: "event",
1541
- kind: "attendance",
1542
- cwd: ctx.cwd,
1543
- sessionParent: ctx.sessionManager.getSessionFile(),
1544
- payload: { type: NAVIGATOR_INVOCATION_ENTRY, ...data },
1545
- source: "role-runtime",
1546
- });
1547
- }
1548
- catch { }
1549
- }
1550
- navigatorAttendance = await dependencies.createNavigatorAttendance({
1551
- context: ctx,
1552
- role: entry.role,
1553
- phase: invocationPhase,
1554
- subjectKey: work.subjectKey,
1555
- subject: work.subject,
1556
- authority: work.authority,
1557
- invocationId,
1558
- ...(contextError === undefined ? {} : { contextError }),
1559
- onEvent: (navigatorEvent, report) => {
1560
- pendingNavigatorPresentation = { event: navigatorEvent, report };
1561
- },
1562
- });
1563
- // Warm live help during activation so prepare is not help-bound under load.
1564
- // Concrete work context also starts full preparation so session create
1565
- // overlaps the role run. Placeholder subjects wait for before_agent_start
1566
- // (user prompt may replace the subject key) but still inherit warm help.
1567
- navigatorAttendance.warmHelp?.();
1568
- if (navigatorWorkContext.contextError === undefined &&
1569
- navigatorWorkContext.subjectProvenance !== "placeholder") {
1570
- navigatorAttendance.prepare();
1571
- }
1572
- }
1573
- await executeActivationStage(entry.role, activationStage(entry.role, runtime), { clock, writeTrace });
1574
- // #357 T2 / #378 / #380 / #391 / #818: any role+engine activation registers the package detour tool once.
1575
- // Gate is resolveEngineName (RoleHost flag → env fallback) — no per-engine execute branch; no role-module spawn.
1576
- if (!engineDetourRegistered) {
1577
- engineDetourRegistered = registerEngineDetourTool(roleHost, hostActions);
1578
- }
1579
- // Worker gates ①②: arm records baseline and runs private one-shot hook uninstall (ADR 0070).
1580
- // Parent session feeds #216 createRecordSession so baseline/bounce survive resume.
1581
- if (entry.role === "coder" || entry.role === "fixer") {
1582
- if (entry.role === "coder")
1583
- coder.armSubmissionGate(ctx.cwd, ctx.sessionManager);
1584
- else
1585
- fixer.armSubmissionGate(ctx.cwd, ctx.sessionManager);
1586
- }
1587
- appendAcceptedActivationToBook({
1588
- ledgerHome,
1589
- fact: buildAcceptedActivationFact({
1590
- role: entry.role,
1591
- observedAt: clock(),
1592
- bookKey,
1593
- session,
1594
- correlation,
1595
- }),
1596
- });
1597
- admitted = true;
1598
- }
1599
- catch (error) {
1600
- failInfrastructure(error, ctx);
1601
- }
1602
- });
1603
- };
1604
- }