@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.
- package/CLAUDE.md +1 -1
- package/dist/acp-host/production-host.js +2389 -6018
- package/dist/analyst-gate-cycles-read.js +15 -19
- package/dist/audit-escalation.js +16 -46
- package/dist/auditor-dossier-tool.js +7 -62
- package/dist/collector-identity.js +0 -157
- package/dist/collector-ledger.js +2 -42
- package/dist/compliance-transport.js +40 -14
- package/dist/diarist-contracts.js +2 -40
- package/dist/doctor-contracts.js +39 -108
- package/dist/doctor-evidence.js +1 -5
- package/dist/engine-detour-tool.js +1 -1
- package/dist/gatekeeper-role.js +49 -38
- package/dist/headless-host/production-host.js +4814 -8450
- package/dist/host-contracts.js +0 -5
- package/dist/merger-contracts.js +4 -6
- package/dist/navigator-attendance.js +30 -52
- package/dist/navigator-invocation-identity.js +0 -1
- package/dist/navigator-public-session.js +2 -1
- package/dist/notary-contracts.js +4 -10
- package/dist/package-contracts/auditor-output.js +5 -23
- package/dist/package-contracts/fixer-output.js +25 -49
- package/dist/package-contracts/gatekeeper-output.js +3 -13
- package/dist/package-contracts/navigator-output.js +4 -7
- package/dist/package-contracts/reviewer-output.js +1 -95
- package/dist/package-contracts/terminating-tools.js +33 -144
- package/dist/package-contracts/worker-output.js +1 -3
- package/dist/public-cli/auto-resume.js +30 -68
- package/dist/public-cli/case-dossier-delivery.js +5 -17
- package/dist/public-cli/diarist-run.js +9 -4
- package/dist/public-cli/inspector-run.js +2 -8
- package/dist/public-cli/instruction-seat-run.js +3 -7
- package/dist/public-cli/invocation.js +5 -2
- package/dist/public-cli/main.js +6068 -6964
- package/dist/public-cli/notary-run.js +1 -8
- package/dist/public-cli/post-admission.js +314 -113
- package/dist/public-cli/run-lifecycle.js +14 -38
- package/dist/public-cli/settlement.js +209 -390
- package/dist/public-cli/terminal.js +51 -45
- package/dist/public-role-summons.js +1 -12
- package/dist/reviewer-dispatch.js +12 -258
- package/dist/submission-correctable-error.js +0 -12
- package/dist/submission-errors.js +6 -1
- package/dist/submission-ledger.js +125 -168
- package/dist/ticket-provenance-contracts.js +2 -49
- package/dist/ticket-provenance.js +82 -31
- package/extensions/role-runtime.ts +1 -6
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +18 -0
- package/src/acp-host/role-turn-host.ts +8 -3
- package/src/analyst-gate-cycles-read.ts +17 -29
- package/src/audit-escalation.ts +20 -50
- package/src/auditor-dossier-tool.ts +5 -103
- package/src/canonical-skill-binding.ts +3 -2
- package/src/collector-identity.ts +0 -179
- package/src/collector-ledger.ts +3 -46
- package/src/collector-role.ts +32 -61
- package/src/collector-tool-schemas.ts +20 -14
- package/src/compliance-transport.ts +55 -17
- package/src/countersign-role.ts +17 -8
- package/src/diarist-contracts.ts +3 -36
- package/src/diarist-role.ts +13 -7
- package/src/diarist.ts +4 -16
- package/src/doctor-contracts.ts +73 -94
- package/src/doctor-evidence.ts +1 -1
- package/src/doctor-role.ts +3 -3
- package/src/engine-detour-tool.ts +4 -1
- package/src/external-host-turn-loop.ts +9 -3
- package/src/gatekeeper-pass-envelope.ts +7 -12
- package/src/gatekeeper-role.ts +56 -50
- package/src/gleaner-left-role.ts +4 -2
- package/src/headless-host/role-turn-host.ts +4 -5
- package/src/host-contracts.ts +3 -8
- package/src/judge-role.ts +19 -13
- package/src/merger-contracts.ts +10 -6
- package/src/merger-role.ts +1 -5
- package/src/navigator-attendance.ts +37 -74
- package/src/navigator-invocation-identity.ts +0 -6
- package/src/navigator-public-session.ts +2 -1
- package/src/notary-contracts.ts +7 -10
- package/src/package-contracts/auditor-output.ts +4 -21
- package/src/package-contracts/fixer-output.ts +34 -52
- package/src/package-contracts/gatekeeper-output.ts +5 -22
- package/src/package-contracts/navigator-output.ts +7 -7
- package/src/package-contracts/reviewer-output.ts +2 -150
- package/src/package-contracts/terminating-tools.ts +32 -147
- package/src/package-contracts/worker-output.ts +0 -1
- package/src/pi/adapter.ts +3 -8
- package/src/public-cli/auto-resume.ts +41 -94
- package/src/public-cli/case-dossier-delivery.ts +5 -15
- package/src/public-cli/countersign-run.ts +14 -6
- package/src/public-cli/diarist-run.ts +11 -9
- package/src/public-cli/inspector-run.ts +2 -18
- package/src/public-cli/instruction-seat-run.ts +3 -17
- package/src/public-cli/invocation.ts +5 -2
- package/src/public-cli/merger-run.ts +1 -1
- package/src/public-cli/notary-run.ts +1 -18
- package/src/public-cli/post-admission.ts +385 -115
- package/src/public-cli/run-lifecycle.ts +13 -44
- package/src/public-cli/settlement.ts +314 -562
- package/src/public-cli/terminal.ts +66 -72
- package/src/public-role-summons.ts +5 -30
- package/src/reviewer-dispatch.ts +29 -331
- package/src/reviewer-role.ts +16 -83
- package/src/role-envelope.ts +16 -24
- package/src/role-runtime-dependencies.ts +1 -6
- package/src/role-runtime.ts +65 -201
- package/src/submission-correctable-error.ts +0 -14
- package/src/submission-errors.ts +6 -1
- package/src/submission-ledger.ts +187 -187
- package/src/ticket-provenance-contracts.ts +4 -60
- package/src/ticket-provenance.ts +98 -32
- package/src/worker-role.ts +35 -36
- package/dist/activation-ledger-session.js +0 -105
- package/dist/activation-ledger.js +0 -139
- package/dist/activation-trace.js +0 -33
- package/dist/atomic-write.js +0 -23
- package/dist/auditor-role.js +0 -12
- package/dist/canonical-skill-binding.js +0 -61
- package/dist/collector-handbook.js +0 -142
- package/dist/collector-receipt.js +0 -107
- package/dist/collector-role.js +0 -566
- package/dist/collector-tool-schemas.js +0 -107
- package/dist/countersign-role.js +0 -28
- package/dist/diarist-role.js +0 -44
- package/dist/diarist.js +0 -66
- package/dist/doctor-role.js +0 -75
- package/dist/fixer-bash-seatbelt.js +0 -13
- package/dist/gatekeeper-pass-envelope.js +0 -77
- package/dist/gleaner-left-role.js +0 -26
- package/dist/inspector-role.js +0 -29
- package/dist/judge-role.js +0 -104
- package/dist/merger-role.js +0 -55
- package/dist/navigator-role.js +0 -12
- package/dist/notary-role.js +0 -134
- package/dist/oauth-keepalive.js +0 -158
- package/dist/pi/adapter.js +0 -223
- package/dist/reviewer-agent.js +0 -122
- package/dist/reviewer-child-executor.js +0 -194
- package/dist/reviewer-construction.js +0 -103
- package/dist/reviewer-execution-ledger.js +0 -166
- package/dist/reviewer-failure-diagnostic.js +0 -17
- package/dist/reviewer-role.js +0 -159
- package/dist/reviewer-scope-prompt.js +0 -21
- package/dist/reviewer-settlement.js +0 -66
- package/dist/reviewer-workspace.js +0 -132
- package/dist/role-runtime.js +0 -1604
- package/dist/stderr-jsonl.js +0 -26
- package/dist/tool-execution-observation.js +0 -141
- package/dist/worker-role.js +0 -345
- package/dist/worker-submission-gates.js +0 -305
- package/src/collector-receipt.ts +0 -140
- package/src/diagnostic-tail.ts +0 -18
- package/src/reviewer-agent.ts +0 -135
- package/src/reviewer-child-executor.ts +0 -252
- package/src/reviewer-construction.ts +0 -195
- package/src/reviewer-execution-ledger.ts +0 -212
- package/src/reviewer-failure-diagnostic.ts +0 -18
- package/src/reviewer-scope-prompt.ts +0 -24
- package/src/reviewer-settlement.ts +0 -74
- package/src/reviewer-workspace.ts +0 -105
- package/src/shape-unreadable-failure.ts +0 -49
package/dist/role-runtime.js
DELETED
|
@@ -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
|
-
}
|