@akagilnc/pi-workflow-roles 0.1.3572 → 0.1.3621
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-host/production-host.js +20239 -10556
- package/dist/analyst-gate-cycles-read.js +387 -0
- package/dist/archivist-record-entry.js +22 -1
- package/dist/audit-escalation.js +6 -0
- package/dist/auditor-soul.js +74 -0
- package/dist/collector-config.js +86 -0
- package/dist/collector-evidence.js +316 -0
- package/dist/collector-github.js +527 -0
- package/dist/collector-ledger.js +853 -0
- package/dist/collector-tool-schemas.js +33 -0
- package/dist/compliance-transport.js +104 -52
- package/dist/diarist-mechanical.js +411 -0
- package/dist/diarist-ticket-resolution.js +177 -0
- package/dist/diarist.js +311 -0
- package/dist/doctor-auditor.js +25 -0
- package/dist/doctor-contracts.js +2 -0
- package/dist/doctor-evidence.js +142 -0
- package/dist/gatekeeper-role.js +104 -81
- package/dist/host-transition-prior-native.js +78 -0
- package/dist/institutional-resolution.js +1 -95
- package/dist/judge-auditor.js +26 -0
- package/dist/ledger-session-read.js +227 -0
- package/dist/merger-git-state.js +78 -0
- package/dist/navigator-attendance.js +15 -7
- package/dist/navigator-public-session.js +170 -0
- package/dist/navigator-session-contracts.js +36 -40
- package/dist/notary-source-run.js +122 -0
- package/dist/package-contracts/auditor-output.js +66 -0
- package/dist/package-contracts/evidence-child-output.js +34 -0
- package/dist/package-contracts/judge-output.js +2 -0
- package/dist/package-contracts/terminating-tools.js +25 -2
- package/dist/package-resources/method-skill.js +254 -0
- package/dist/packaged-role-registry.js +36 -0
- package/dist/pi/durable-principal.js +61 -0
- package/dist/pi/in-process-session.js +25 -6
- package/dist/pi/known-failure.js +52 -0
- package/dist/pi/role-turn-host.js +430 -0
- package/dist/public-cli/auto-resume.js +414 -0
- package/dist/public-cli/cli-errors.js +8 -0
- package/dist/public-cli/cli-io.js +1 -0
- package/dist/public-cli/command-renderer.js +5 -0
- package/dist/public-cli/doctor-run.js +84 -0
- package/dist/public-cli/inspector-run.js +136 -0
- package/dist/public-cli/instruction-seat-run.js +256 -0
- package/dist/public-cli/invocation.js +2225 -0
- package/dist/public-cli/judge-run.js +118 -0
- package/dist/public-cli/load-production-acp-host.js +40 -0
- package/dist/public-cli/main.js +1140 -652
- package/dist/public-cli/notary-run.js +161 -0
- package/dist/public-cli/option-definitions.js +1192 -0
- package/dist/public-cli/post-admission.js +667 -0
- package/dist/public-cli/public-run-credentials.js +49 -0
- package/dist/public-cli/registry.js +4 -1
- package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
- package/dist/public-cli/run-lifecycle.js +1344 -0
- package/dist/public-cli/seat-ticket-binding.js +113 -0
- package/dist/public-cli/settlement.js +3441 -0
- package/dist/public-cli/terminal.js +135 -0
- package/dist/public-cli/turn-request.js +25 -0
- package/dist/public-role-summons.js +301 -0
- package/dist/receipt-delivery-policy.js +10 -0
- package/dist/reviewer-child-executor.js +80 -8
- package/dist/reviewer-execution-ledger.js +2 -1
- package/dist/run-terminal-artifacts.js +195 -0
- package/dist/run-ticket-number.js +40 -0
- package/dist/session-assistant-usage.js +107 -0
- package/dist/shape-unreadable-failure.js +34 -0
- package/dist/submission-errors.js +3 -0
- package/dist/submission-ledger.js +388 -0
- package/dist/ticket-provenance-contracts.js +115 -0
- package/dist/ticket-provenance.js +340 -0
- package/extensions/role-runtime.ts +7 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -1
- package/souls/doctor-auditor.md +1 -0
- package/souls/evidence-child.md +1 -0
- package/src/acp-host/production-host.ts +3 -0
- package/src/acp-host/role-envelope.ts +2 -2
- package/src/acp-host/role-turn-host.ts +9 -1
- package/src/analyst-gate-cycles-read.ts +37 -2
- package/src/archivist-record-entry.ts +45 -1
- package/src/audit-escalation.ts +17 -0
- package/src/auditor-role.ts +22 -0
- package/src/auditor-soul.ts +42 -0
- package/src/compliance-transport.ts +177 -66
- package/src/doctor-auditor.ts +10 -14
- package/src/doctor-contracts.ts +1 -0
- package/src/doctor-role.ts +2 -2
- package/src/evidence-child-role.ts +22 -0
- package/src/gatekeeper-pass-envelope.ts +109 -0
- package/src/gatekeeper-role.ts +145 -104
- package/src/host-contracts.ts +9 -1
- package/src/institutional-resolution.ts +8 -163
- package/src/judge-auditor.ts +10 -15
- package/src/judge-role.ts +8 -0
- package/src/navigator-attendance.ts +25 -9
- package/src/navigator-public-session.ts +217 -0
- package/src/navigator-session-contracts.ts +61 -54
- package/src/notary-source-run.ts +3 -1
- package/src/package-contracts/auditor-output.ts +82 -0
- package/src/package-contracts/evidence-child-output.ts +51 -0
- package/src/package-contracts/judge-output.ts +1 -0
- package/src/package-contracts/reviewer-output.ts +2 -2
- package/src/package-contracts/terminating-tools.ts +31 -1
- package/src/packaged-role-registry.ts +43 -0
- package/src/pi/adapter.ts +2 -1
- package/src/pi/in-process-session.ts +38 -12
- package/src/pi/role-turn-host.ts +34 -0
- package/src/public-cli/cli.ts +14 -6
- package/src/public-cli/instruction-seat-run.ts +289 -50
- package/src/public-cli/invocation.ts +66 -25
- package/src/public-cli/option-definitions.ts +61 -0
- package/src/public-cli/post-admission.ts +7 -1
- package/src/public-cli/registry.ts +4 -1
- package/src/public-cli/run-lifecycle.ts +22 -3
- package/src/public-cli/settlement.ts +120 -5
- package/src/public-cli/terminal.ts +2 -0
- package/src/public-role-summons.ts +431 -0
- package/src/receipt-delivery-policy.ts +10 -0
- package/src/reviewer-agent.ts +1 -1
- package/src/reviewer-child-executor.ts +114 -12
- package/src/reviewer-execution-ledger.ts +3 -2
- package/src/role-runtime.ts +158 -17
- package/src/session-assistant-usage.ts +119 -0
- package/src/session-opening-materials.ts +1 -1
- package/src/shape-unreadable-failure.ts +49 -0
- package/src/submission-errors.ts +3 -0
- package/dist/evidence-child-executor.js +0 -829
- package/src/evidence-child-executor.ts +0 -1140
|
@@ -1,1140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unique in-process child lifecycle helper (#236 established; #233 sinks auditor + navigator).
|
|
3
|
-
* Owns scratch, inherited provider runtime, AgentSession, abort/dispose.
|
|
4
|
-
* Not a subprocess RPC.
|
|
5
|
-
*/
|
|
6
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
7
|
-
import { tmpdir } from "node:os";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import {
|
|
10
|
-
type Api,
|
|
11
|
-
type AssistantMessage,
|
|
12
|
-
type Context,
|
|
13
|
-
type Model,
|
|
14
|
-
type ProviderStreamOptions,
|
|
15
|
-
type Usage,
|
|
16
|
-
} from "@earendil-works/pi-ai";
|
|
17
|
-
import type {
|
|
18
|
-
ExtensionContext,
|
|
19
|
-
SessionManager,
|
|
20
|
-
} from "@earendil-works/pi-coding-agent";
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE,
|
|
24
|
-
AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE,
|
|
25
|
-
type AuditorParentAttemptBinding,
|
|
26
|
-
} from "./compliance-transport.ts";
|
|
27
|
-
import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
|
|
28
|
-
import { sitianReport } from "./sitian-facade.ts";
|
|
29
|
-
import { createEngineDetourToolDefinition } from "./engine-detour-tool.ts";
|
|
30
|
-
import { engineNameFromEnv } from "./engine-detour.ts";
|
|
31
|
-
import {
|
|
32
|
-
appendEngineSessionMaterial,
|
|
33
|
-
engineSessionMaterialFromOptions,
|
|
34
|
-
type EngineSessionMaterial,
|
|
35
|
-
} from "./package-resources/engine-material.ts";
|
|
36
|
-
import { readPackageMaterial } from "./session-opening-materials.ts";
|
|
37
|
-
import {
|
|
38
|
-
type GateOfficerSeat,
|
|
39
|
-
} from "./public-cli/config.ts";
|
|
40
|
-
import { readInstitutionalSeatSelection } from "./institutional-resolution.ts";
|
|
41
|
-
import type {
|
|
42
|
-
OpenPiInstitutionalSessionOptions,
|
|
43
|
-
OpenPiInstitutionalSessionResult,
|
|
44
|
-
} from "./pi/in-process-session.ts";
|
|
45
|
-
import type { HostAssistantTurnResult, HostContext } from "./host-contracts.ts";
|
|
46
|
-
import {
|
|
47
|
-
NAVIGATOR_PREPARE_TOOL_NAME,
|
|
48
|
-
NavigatorUnavailableError,
|
|
49
|
-
navigatorModelSettingPath,
|
|
50
|
-
navigatorProviderFailureFromDiagnostics,
|
|
51
|
-
navigatorProviderFailureFromError,
|
|
52
|
-
navigatorProviderFailureFromStatus,
|
|
53
|
-
navigatorUnavailableError,
|
|
54
|
-
parseNavigatorModelSetting,
|
|
55
|
-
resolveNavigatorSeatSelection,
|
|
56
|
-
type NavigatorProviderFailureFact,
|
|
57
|
-
type NavigatorSessionFactory,
|
|
58
|
-
} from "./navigator-session-contracts.ts";
|
|
59
|
-
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT, type NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
60
|
-
import type { ReviewerPromptText } from "./reviewer-prompt-identity.ts";
|
|
61
|
-
import { recordTypedProviderHttpStatus } from "./typed-provider-http.ts";
|
|
62
|
-
import {
|
|
63
|
-
hasUpstreamErrorTestimony,
|
|
64
|
-
isNonSuccessHttpStatus,
|
|
65
|
-
projectConfirmedRemotePayload,
|
|
66
|
-
} from "./upstream-error-testimony.ts";
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Shared Standards/Spec evidence-child system materials — path roster only.
|
|
70
|
-
* Builder consumes this unique roster; cadence prose stays in owner material (ADR 0073).
|
|
71
|
-
* Not exported — tests must not mirror internal roster structure.
|
|
72
|
-
*/
|
|
73
|
-
const EVIDENCE_CHILD_SESSION_MATERIALS = [
|
|
74
|
-
"souls/quality-law.md",
|
|
75
|
-
] as const;
|
|
76
|
-
|
|
77
|
-
/** Package-owned system prompt for Reviewer Standards/Spec evidence children (private carrier). */
|
|
78
|
-
async function buildEvidenceChildSystemPrompt(
|
|
79
|
-
engineMaterial?: EngineSessionMaterial,
|
|
80
|
-
): Promise<string> {
|
|
81
|
-
// ADR 0073: verification cadence lives in owner material only; no machine prose copy.
|
|
82
|
-
const materials: string[] = [];
|
|
83
|
-
for (const relativePath of EVIDENCE_CHILD_SESSION_MATERIALS) {
|
|
84
|
-
materials.push(await readPackageMaterial(relativePath));
|
|
85
|
-
}
|
|
86
|
-
return appendEngineSessionMaterial(materials, engineMaterial).join("\n");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ── shared constants / types ──────────────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
export const DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES = 2;
|
|
92
|
-
|
|
93
|
-
export type AuditorDecisionTool = {
|
|
94
|
-
name: string;
|
|
95
|
-
description: string;
|
|
96
|
-
parameters: object;
|
|
97
|
-
execute(...args: any[]): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>>;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export type InProcessScratchOptions = {
|
|
101
|
-
readonly prefix: string;
|
|
102
|
-
readonly parentDirectory?: string;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/** Shared scratch directory with guaranteed cleanup. */
|
|
106
|
-
export async function withInProcessScratch<T>(
|
|
107
|
-
options: InProcessScratchOptions,
|
|
108
|
-
run: (scratch: string) => Promise<T>,
|
|
109
|
-
): Promise<T> {
|
|
110
|
-
const scratch = await mkdtemp(join(options.parentDirectory ?? tmpdir(), options.prefix));
|
|
111
|
-
let failure: unknown;
|
|
112
|
-
try {
|
|
113
|
-
return await run(scratch);
|
|
114
|
-
} catch (error) {
|
|
115
|
-
failure = error;
|
|
116
|
-
throw error;
|
|
117
|
-
} finally {
|
|
118
|
-
try {
|
|
119
|
-
await rm(scratch, { recursive: true, force: true });
|
|
120
|
-
} catch (cleanupFailure) {
|
|
121
|
-
if (failure !== undefined) {
|
|
122
|
-
throw new AggregateError([failure, cleanupFailure], "in-process child scratch cleanup failed", { cause: failure });
|
|
123
|
-
}
|
|
124
|
-
throw cleanupFailure;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Run every child cleanup, aggregating failures so one throwing cleanup never
|
|
131
|
-
* skips the rest (e.g. handle.close must still run when unsubscribe throws).
|
|
132
|
-
* If a primary failure is supplied and cleanup also failed, the two are combined
|
|
133
|
-
* into an AggregateError; otherwise only the failing branch is surfaced.
|
|
134
|
-
*/
|
|
135
|
-
async function runChildCleanup(
|
|
136
|
-
cleanups: ReadonlyArray<() => void | Promise<void>>,
|
|
137
|
-
primaryFailure: unknown,
|
|
138
|
-
label: string,
|
|
139
|
-
): Promise<void> {
|
|
140
|
-
let cleanupFailure: unknown;
|
|
141
|
-
for (const cleanup of cleanups) {
|
|
142
|
-
try {
|
|
143
|
-
await cleanup();
|
|
144
|
-
} catch (failure) {
|
|
145
|
-
cleanupFailure = cleanupFailure === undefined
|
|
146
|
-
? failure
|
|
147
|
-
: new AggregateError([cleanupFailure, failure], `${label} cleanup failed`, {
|
|
148
|
-
cause: cleanupFailure,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (cleanupFailure === undefined) return;
|
|
153
|
-
if (primaryFailure !== undefined) {
|
|
154
|
-
throw new AggregateError(
|
|
155
|
-
[primaryFailure, cleanupFailure],
|
|
156
|
-
`${label} execution and cleanup failed`,
|
|
157
|
-
{ cause: primaryFailure },
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
throw new AggregateError([cleanupFailure], `${label} cleanup failed`, {
|
|
161
|
-
cause: cleanupFailure,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
type EvidenceChildFailureClassification = "provider" | "child" | "unknown";
|
|
166
|
-
type ClassifiedReviewerError = Error & Readonly<{
|
|
167
|
-
evidenceChildFailure: EvidenceChildFailureClassification;
|
|
168
|
-
evidenceChildOriginal?: unknown;
|
|
169
|
-
}>;
|
|
170
|
-
function numericHttpStatus(value: unknown): number | undefined {
|
|
171
|
-
return isNonSuccessHttpStatus(value) ? value : undefined;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
type StructuredRemoteProjection = {
|
|
175
|
-
readonly hasTestimony: boolean;
|
|
176
|
-
readonly httpStatus?: number;
|
|
177
|
-
readonly diagnostics?: unknown;
|
|
178
|
-
readonly body?: unknown;
|
|
179
|
-
readonly code?: unknown;
|
|
180
|
-
readonly errno?: unknown;
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Cause-chain reader over the shared upstream-testimony authority.
|
|
185
|
-
* Shape walking stays here; testimony + confirmed-remote payload rules are shared.
|
|
186
|
-
*/
|
|
187
|
-
function projectStructuredRemote(error: unknown): StructuredRemoteProjection {
|
|
188
|
-
let httpStatus: number | undefined;
|
|
189
|
-
let diagnostics: unknown;
|
|
190
|
-
let body: unknown;
|
|
191
|
-
let code: unknown;
|
|
192
|
-
let errno: unknown;
|
|
193
|
-
let cursor: unknown = error;
|
|
194
|
-
const seen = new Set<unknown>();
|
|
195
|
-
while (typeof cursor === "object" && cursor !== null && !seen.has(cursor)) {
|
|
196
|
-
seen.add(cursor);
|
|
197
|
-
const record = cursor as Record<string, unknown>;
|
|
198
|
-
const nodeStatus = numericHttpStatus(record.statusCode)
|
|
199
|
-
?? numericHttpStatus(record.status)
|
|
200
|
-
?? numericHttpStatus(record.httpStatus);
|
|
201
|
-
const nodeDiagnostics = Array.isArray(record.diagnostics) && record.diagnostics.length > 0
|
|
202
|
-
? record.diagnostics
|
|
203
|
-
: undefined;
|
|
204
|
-
const nodeHasTestimony = hasUpstreamErrorTestimony({
|
|
205
|
-
...(nodeStatus === undefined ? {} : { httpStatus: nodeStatus }),
|
|
206
|
-
...(nodeDiagnostics === undefined ? {} : { diagnostics: nodeDiagnostics }),
|
|
207
|
-
});
|
|
208
|
-
if (httpStatus === undefined && nodeStatus !== undefined) httpStatus = nodeStatus;
|
|
209
|
-
if (diagnostics === undefined && nodeDiagnostics !== undefined) diagnostics = nodeDiagnostics;
|
|
210
|
-
// Payload only from confirmed-remote nodes — never arbitrary local Error.code.
|
|
211
|
-
if (nodeHasTestimony) {
|
|
212
|
-
const payload = projectConfirmedRemotePayload(record);
|
|
213
|
-
if (body === undefined && payload.body !== undefined) body = payload.body;
|
|
214
|
-
if (code === undefined && payload.code !== undefined) code = payload.code;
|
|
215
|
-
if (errno === undefined && payload.errno !== undefined) errno = payload.errno;
|
|
216
|
-
}
|
|
217
|
-
cursor = record.cause;
|
|
218
|
-
}
|
|
219
|
-
return {
|
|
220
|
-
hasTestimony: hasUpstreamErrorTestimony({
|
|
221
|
-
...(httpStatus === undefined ? {} : { httpStatus }),
|
|
222
|
-
...(diagnostics === undefined ? {} : { diagnostics }),
|
|
223
|
-
}),
|
|
224
|
-
...(httpStatus === undefined ? {} : { httpStatus }),
|
|
225
|
-
...(diagnostics === undefined ? {} : { diagnostics }),
|
|
226
|
-
...(body === undefined ? {} : { body }),
|
|
227
|
-
...(code === undefined ? {} : { code }),
|
|
228
|
-
...(errno === undefined ? {} : { errno }),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Attach a directly observed HTTP status onto an error/aborted assistant message.
|
|
234
|
-
* Does not invent status from errorMessage prose; skips when already held.
|
|
235
|
-
*/
|
|
236
|
-
function attachObservedHttpStatus<T extends AssistantMessage>(
|
|
237
|
-
message: T,
|
|
238
|
-
observedHttpStatus: number | undefined,
|
|
239
|
-
): T {
|
|
240
|
-
if (observedHttpStatus === undefined) return message;
|
|
241
|
-
if (message.stopReason !== "error" && message.stopReason !== "aborted") return message;
|
|
242
|
-
if (numericHttpStatus(observedHttpStatus) === undefined) return message;
|
|
243
|
-
if (projectStructuredRemote(message).httpStatus !== undefined) return message;
|
|
244
|
-
return Object.assign(message, {
|
|
245
|
-
status: observedHttpStatus,
|
|
246
|
-
statusCode: observedHttpStatus,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
function enrichStreamEvent(event: unknown, observedHttpStatus: number | undefined): unknown {
|
|
250
|
-
if (observedHttpStatus === undefined || event === null || typeof event !== "object") return event;
|
|
251
|
-
const record = event as Record<string, unknown>;
|
|
252
|
-
if (record.type === "error" && record.error !== null && typeof record.error === "object") {
|
|
253
|
-
return {
|
|
254
|
-
...record,
|
|
255
|
-
error: attachObservedHttpStatus(record.error as AssistantMessage, observedHttpStatus),
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
if (record.type === "done" && record.message !== null && typeof record.message === "object") {
|
|
259
|
-
return {
|
|
260
|
-
...record,
|
|
261
|
-
message: attachObservedHttpStatus(record.message as AssistantMessage, observedHttpStatus),
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
if (record.partial !== null && typeof record.partial === "object") {
|
|
265
|
-
return {
|
|
266
|
-
...record,
|
|
267
|
-
partial: attachObservedHttpStatus(record.partial as AssistantMessage, observedHttpStatus),
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
return event;
|
|
271
|
-
}
|
|
272
|
-
function classifiedError(error: unknown, evidenceChildFailure: EvidenceChildFailureClassification): ClassifiedReviewerError {
|
|
273
|
-
const diagnostic = typeof error === "object" && error !== null && typeof (error as { errorMessage?: unknown }).errorMessage === "string"
|
|
274
|
-
? (error as { errorMessage: string }).errorMessage
|
|
275
|
-
: error === undefined ? "" : String(error);
|
|
276
|
-
const wrapped = error instanceof Error
|
|
277
|
-
? error
|
|
278
|
-
: Object.assign(new Error(diagnostic, { cause: error }), { evidenceChildOriginal: error });
|
|
279
|
-
const classification = "evidenceChildFailure" in wrapped
|
|
280
|
-
? (wrapped as ClassifiedReviewerError).evidenceChildFailure
|
|
281
|
-
: evidenceChildFailure === "provider" && !projectStructuredRemote(error).hasTestimony
|
|
282
|
-
? "unknown"
|
|
283
|
-
: evidenceChildFailure;
|
|
284
|
-
return Object.assign(wrapped, { evidenceChildFailure: classification });
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/** Extract the first text diagnostic from a flattened toolResult error `details`. */
|
|
288
|
-
function extractToolResultText(details: unknown): string | undefined {
|
|
289
|
-
if (typeof details !== "object" || details === null) return undefined;
|
|
290
|
-
const record = details as Record<string, unknown>;
|
|
291
|
-
const content = record.content;
|
|
292
|
-
if (Array.isArray(content)) {
|
|
293
|
-
for (const part of content) {
|
|
294
|
-
if (typeof part === "object" && part !== null) {
|
|
295
|
-
const text = (part as Record<string, unknown>).text;
|
|
296
|
-
if (typeof text === "string" && text.trim() !== "") return text;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return undefined;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function emptyUsage(): Usage {
|
|
304
|
-
return {
|
|
305
|
-
input: 0,
|
|
306
|
-
output: 0,
|
|
307
|
-
cacheRead: 0,
|
|
308
|
-
cacheWrite: 0,
|
|
309
|
-
totalTokens: 0,
|
|
310
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function addUsage(total: Usage, next: Usage): void {
|
|
315
|
-
total.input += next.input;
|
|
316
|
-
total.output += next.output;
|
|
317
|
-
total.cacheRead += next.cacheRead;
|
|
318
|
-
total.cacheWrite += next.cacheWrite;
|
|
319
|
-
total.totalTokens += next.totalTokens;
|
|
320
|
-
total.cost.input += next.cost.input;
|
|
321
|
-
total.cost.output += next.cost.output;
|
|
322
|
-
total.cost.cacheRead += next.cost.cacheRead;
|
|
323
|
-
total.cost.cacheWrite += next.cost.cacheWrite;
|
|
324
|
-
total.cost.total += next.cost.total;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// ── evidence child (reviewer legs) ────────────────────────────────────────
|
|
328
|
-
|
|
329
|
-
/** Evidence child execution options. */
|
|
330
|
-
export type EvidenceChildExecuteOptions = Readonly<{
|
|
331
|
-
signal?: AbortSignal;
|
|
332
|
-
/** Parent directory for credential/config scratch. Defaults to os.tmpdir(). */
|
|
333
|
-
credentialScratchParent?: string;
|
|
334
|
-
/** Run directory carrying the institutional resolution page (#518). Derives
|
|
335
|
-
* from context when absent. */
|
|
336
|
-
runDirectory?: string;
|
|
337
|
-
/**
|
|
338
|
-
* Package root for optional engine method-material resolution (#378).
|
|
339
|
-
* Required only when AK_ROLE_ENGINE is set and packaged notes should attach.
|
|
340
|
-
*/
|
|
341
|
-
packageRoot?: string;
|
|
342
|
-
}>;
|
|
343
|
-
|
|
344
|
-
export async function executeEvidenceChild(
|
|
345
|
-
workspace: string,
|
|
346
|
-
prompt: ReviewerPromptText,
|
|
347
|
-
context: ExtensionContext | HostContext,
|
|
348
|
-
options: EvidenceChildExecuteOptions = {},
|
|
349
|
-
): Promise<{ report: string; usage: Usage; prompt: ReviewerPromptText }> {
|
|
350
|
-
const signal = options.signal;
|
|
351
|
-
const runDirectory = options.runDirectory ?? auditorRunDirectory(context);
|
|
352
|
-
if (runDirectory === undefined) {
|
|
353
|
-
throw new Error("Evidence child requires a run directory carrying the institutional resolution page");
|
|
354
|
-
}
|
|
355
|
-
const selection = await readInstitutionalSeatSelection(runDirectory, "evidenceChild");
|
|
356
|
-
return withInProcessScratch(
|
|
357
|
-
{
|
|
358
|
-
prefix: "ak-evidence-child-",
|
|
359
|
-
...(options.credentialScratchParent === undefined
|
|
360
|
-
? {}
|
|
361
|
-
: { parentDirectory: options.credentialScratchParent }),
|
|
362
|
-
},
|
|
363
|
-
async (childConfigDir) => {
|
|
364
|
-
const { openPiInstitutionalSession } = await import("./pi/in-process-session.ts");
|
|
365
|
-
const { createRecordSession } = await import("./archivist-record-entry.ts");
|
|
366
|
-
// #378: when labor engine is configured, legs get the same detour tool + material
|
|
367
|
-
// dual-path as the parent seat (ADR 0069 detour-rejoins-main-road).
|
|
368
|
-
const engineName = engineNameFromEnv();
|
|
369
|
-
const engineMaterial =
|
|
370
|
-
engineName === undefined
|
|
371
|
-
? undefined
|
|
372
|
-
: options.packageRoot === undefined || options.packageRoot.trim() === ""
|
|
373
|
-
// Name-only when package root is unavailable (still a valid #376 path).
|
|
374
|
-
? Object.freeze({ name: engineName })
|
|
375
|
-
: engineSessionMaterialFromOptions({
|
|
376
|
-
engine: engineName,
|
|
377
|
-
packageRoot: options.packageRoot,
|
|
378
|
-
});
|
|
379
|
-
// Evidence legs use the parent detour tool; retain any engine process cause
|
|
380
|
-
// so the enclosing child boundary, rather than a tool-error result, terminates.
|
|
381
|
-
let engineDetourFailure: Error | undefined;
|
|
382
|
-
const engineDetourTool =
|
|
383
|
-
engineName === undefined
|
|
384
|
-
? undefined
|
|
385
|
-
: createEngineDetourToolDefinition({
|
|
386
|
-
engineName,
|
|
387
|
-
fail(error) {
|
|
388
|
-
engineDetourFailure ??= error instanceof Error ? error : new Error(String(error));
|
|
389
|
-
throw engineDetourFailure;
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
// No tools allowlist — Pi defaults + unrestricted evidence surface (ADR 0064).
|
|
393
|
-
// Single open seam owner: pi/in-process-session.ts.
|
|
394
|
-
let opened: Awaited<ReturnType<typeof openPiInstitutionalSession>>;
|
|
395
|
-
try {
|
|
396
|
-
opened = await openPiInstitutionalSession({
|
|
397
|
-
cwd: workspace,
|
|
398
|
-
agentDir: childConfigDir,
|
|
399
|
-
selection,
|
|
400
|
-
systemPrompt: await buildEvidenceChildSystemPrompt(engineMaterial),
|
|
401
|
-
...(engineDetourTool === undefined
|
|
402
|
-
? {}
|
|
403
|
-
: { customTools: [engineDetourTool] }),
|
|
404
|
-
sessionManager: createRecordSession({
|
|
405
|
-
cwd: workspace,
|
|
406
|
-
kind: "evidence-children",
|
|
407
|
-
...(context.sessionManager === undefined ? {} : { parent: context.sessionManager }),
|
|
408
|
-
}),
|
|
409
|
-
...(signal === undefined ? {} : { signal }),
|
|
410
|
-
label: "Evidence child",
|
|
411
|
-
});
|
|
412
|
-
} catch (error) {
|
|
413
|
-
throw classifiedError(error, "provider");
|
|
414
|
-
}
|
|
415
|
-
const { handle } = opened;
|
|
416
|
-
const usage = emptyUsage();
|
|
417
|
-
const unsubscribe = handle.subscribe((event) => {
|
|
418
|
-
if (event.type === "message_end" && event.role === "assistant") {
|
|
419
|
-
if (event.usage) addUsage(usage, event.usage);
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
const abortChild = () => { handle.abort(); };
|
|
423
|
-
if (signal?.aborted) abortChild();
|
|
424
|
-
else signal?.addEventListener("abort", abortChild, { once: true });
|
|
425
|
-
let primaryFailure: unknown;
|
|
426
|
-
try {
|
|
427
|
-
const delivered = prompt;
|
|
428
|
-
let turnResult: HostAssistantTurnResult;
|
|
429
|
-
try {
|
|
430
|
-
turnResult = await handle.prompt(delivered);
|
|
431
|
-
} catch (error) {
|
|
432
|
-
if (engineDetourFailure !== undefined) {
|
|
433
|
-
throw classifiedError(engineDetourFailure, "child");
|
|
434
|
-
}
|
|
435
|
-
throw classifiedError(error, "provider");
|
|
436
|
-
}
|
|
437
|
-
if (engineDetourFailure !== undefined) {
|
|
438
|
-
throw classifiedError(engineDetourFailure, "child");
|
|
439
|
-
}
|
|
440
|
-
if (signal?.aborted) throw new Error("Evidence child was cancelled");
|
|
441
|
-
const lastAssistant = turnResult.messages !== undefined
|
|
442
|
-
? [...turnResult.messages].reverse().find((message: any) => message?.role === "assistant") as AssistantMessage | undefined
|
|
443
|
-
: undefined;
|
|
444
|
-
// error|aborted assistant stops share the upstream-testimony rule: provider only
|
|
445
|
-
// with direct HTTP/SDK testimony, otherwise existing unknown. child is reserved
|
|
446
|
-
// for real local child/report failures (no assistant / blank report / cleanup).
|
|
447
|
-
if (
|
|
448
|
-
turnResult.stopReason === "error" || turnResult.stopReason === "aborted"
|
|
449
|
-
|| (lastAssistant?.role === "assistant" && (lastAssistant.stopReason === "error" || lastAssistant.stopReason === "aborted"))
|
|
450
|
-
) {
|
|
451
|
-
const errMsg = turnResult.errorMessage ?? lastAssistant?.errorMessage ?? "";
|
|
452
|
-
throw classifiedError(
|
|
453
|
-
new Error(errMsg, { cause: lastAssistant }),
|
|
454
|
-
lastAssistant && projectStructuredRemote(lastAssistant).hasTestimony ? "provider" : "unknown",
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
if (lastAssistant !== undefined && lastAssistant.role !== "assistant") {
|
|
458
|
-
throw classifiedError(
|
|
459
|
-
new Error("Evidence child child terminated without a report", {
|
|
460
|
-
cause: lastAssistant ?? turnResult.messages,
|
|
461
|
-
}),
|
|
462
|
-
"child",
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
const report = turnResult.text;
|
|
466
|
-
if (report.trim().length === 0) {
|
|
467
|
-
throw new Error("Evidence child returned a blank child report");
|
|
468
|
-
}
|
|
469
|
-
return { report, usage, prompt: delivered };
|
|
470
|
-
} catch (error) {
|
|
471
|
-
primaryFailure = classifiedError(error, "child");
|
|
472
|
-
throw primaryFailure;
|
|
473
|
-
} finally {
|
|
474
|
-
signal?.removeEventListener("abort", abortChild);
|
|
475
|
-
await runChildCleanup([() => unsubscribe(), () => handle.close()], primaryFailure, "Reviewer child");
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// ── auditor child ─────────────────────────────────────────────────────────
|
|
482
|
-
|
|
483
|
-
export type AuditorRoleOptions = {
|
|
484
|
-
systemPrompt: string;
|
|
485
|
-
prompt: string;
|
|
486
|
-
tool: AuditorDecisionTool;
|
|
487
|
-
dossierTool: AuditorDecisionTool;
|
|
488
|
-
roleLabel: string;
|
|
489
|
-
context: ExtensionContext | HostContext;
|
|
490
|
-
signal?: AbortSignal;
|
|
491
|
-
retainResponse?(response: AssistantMessage): void;
|
|
492
|
-
/** Province seat identity only — shared executor owns seat resolution, and
|
|
493
|
-
* loud auth/availability failure (#453 / ADR 0018). */
|
|
494
|
-
gateSeat?: GateOfficerSeat;
|
|
495
|
-
/** Run directory carrying the institutional resolution page (#518). Derives
|
|
496
|
-
* from context when absent. */
|
|
497
|
-
runDirectory?: string;
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
/** Resolution-page seat key for an auditor invocation: province gate seats map
|
|
501
|
-
* to their own page seats; doctor/judge compliance audits use the auditor seat. */
|
|
502
|
-
function auditorSeatKey(gateSeat: GateOfficerSeat | undefined): string {
|
|
503
|
-
return gateSeat ?? "auditor";
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Auditor lifecycle via the shared institutional sub-session adapter.
|
|
508
|
-
* Adapter keeps role label / soul / decision tool / result projection only.
|
|
509
|
-
* No tools allowlist (ADR 0064). Provider-stream idle-only retry (ADR 0059).
|
|
510
|
-
* Durable child session via ADR 0065 archivist entry.
|
|
511
|
-
*/
|
|
512
|
-
export async function executeAuditorChild(
|
|
513
|
-
options: AuditorRoleOptions,
|
|
514
|
-
): Promise<{ decision: unknown; response: AssistantMessage; noReceiptLifecycle?: NoReceiptLifecycleFacts }> {
|
|
515
|
-
const runDirectory = options.runDirectory ?? auditorRunDirectory(options.context);
|
|
516
|
-
if (runDirectory === undefined) {
|
|
517
|
-
throw new Error(`${options.roleLabel} requires a run directory carrying the institutional resolution page`);
|
|
518
|
-
}
|
|
519
|
-
const seat = auditorSeatKey(options.gateSeat);
|
|
520
|
-
const selection = await readInstitutionalSeatSelection(runDirectory, seat);
|
|
521
|
-
|
|
522
|
-
return withInProcessScratch({ prefix: "ak-auditor-role-" }, async (scratch) => {
|
|
523
|
-
const cwd = options.context.cwd ?? process.cwd();
|
|
524
|
-
|
|
525
|
-
let decision: unknown;
|
|
526
|
-
let noReceiptLifecycle: NoReceiptLifecycleFacts | undefined;
|
|
527
|
-
let decisionSubmitted = false;
|
|
528
|
-
let decisionCallId: string | undefined;
|
|
529
|
-
let decisionToolFailure: unknown;
|
|
530
|
-
const decisionToolFailures = new Map<string, unknown>();
|
|
531
|
-
const delivery = createReceiptDeliveryPolicy();
|
|
532
|
-
const tool = {
|
|
533
|
-
...options.tool,
|
|
534
|
-
label: options.roleLabel,
|
|
535
|
-
async execute(...args: any[]) {
|
|
536
|
-
if (decisionSubmitted && decisionCallId !== args[0]) {
|
|
537
|
-
throw new Error("Auditor decision was submitted more than once");
|
|
538
|
-
}
|
|
539
|
-
// Pi may already have issued several decision calls in one assistant
|
|
540
|
-
// response. Execute every issued call: the budget limits future
|
|
541
|
-
// solicitations, not terminal calls already in flight.
|
|
542
|
-
try {
|
|
543
|
-
const result = await options.tool.execute(...args);
|
|
544
|
-
delivery.recordAccepted();
|
|
545
|
-
const rawDecision = args[1];
|
|
546
|
-
const isMissingArgs = rawDecision === undefined
|
|
547
|
-
|| (typeof rawDecision === "object" && rawDecision !== null && !Array.isArray(rawDecision) && Object.keys(rawDecision).length === 0);
|
|
548
|
-
decision = isMissingArgs ? undefined : rawDecision;
|
|
549
|
-
decisionCallId = args[0];
|
|
550
|
-
decisionToolFailure = undefined;
|
|
551
|
-
decisionToolFailures.delete(args[0]);
|
|
552
|
-
decisionSubmitted = true;
|
|
553
|
-
return { ...result, terminate: true };
|
|
554
|
-
} catch (error) {
|
|
555
|
-
decisionToolFailure = error;
|
|
556
|
-
decisionToolFailures.set(args[0], error);
|
|
557
|
-
throw error;
|
|
558
|
-
}
|
|
559
|
-
},
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const parentSessionManager = options.context.sessionManager;
|
|
563
|
-
const parentHeader = parentSessionManager?.getHeader?.();
|
|
564
|
-
const parentSessionFile = parentSessionManager?.getSessionFile?.();
|
|
565
|
-
const parentAttemptEntryId = parentSessionManager?.getLeafId?.();
|
|
566
|
-
// Institutional child under parent placement for gate/settlement discovery.
|
|
567
|
-
// No ticket:seat subject nest (#636 deleted / ADR 0079). Public officer seats
|
|
568
|
-
// resume via tryResumeSameTicketSeatRun on the seat's own run — not this nest.
|
|
569
|
-
const { createRecordSession } = await import("./archivist-record-entry.ts");
|
|
570
|
-
const auditorSessionManager: SessionManager = createRecordSession({
|
|
571
|
-
cwd,
|
|
572
|
-
kind: "auditor-roles",
|
|
573
|
-
...(parentSessionManager === undefined ? {} : { parent: parentSessionManager }),
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
// Shared session open — no tools allowlist (ADR 0064). Auth resolved
|
|
577
|
-
// child-locally from the explicit seat selection; adapter owns runtime/provider.
|
|
578
|
-
const { openPiInstitutionalSession } = await import("./pi/in-process-session.ts");
|
|
579
|
-
const evidenceToolFailures = new Map<string, unknown>();
|
|
580
|
-
const wrappedDossierTool = {
|
|
581
|
-
...options.dossierTool,
|
|
582
|
-
label: options.roleLabel,
|
|
583
|
-
async execute(...args: any[]) {
|
|
584
|
-
try {
|
|
585
|
-
return await options.dossierTool.execute(...args);
|
|
586
|
-
} catch (error) {
|
|
587
|
-
evidenceToolFailures.set(args[0], error);
|
|
588
|
-
throw error;
|
|
589
|
-
}
|
|
590
|
-
},
|
|
591
|
-
};
|
|
592
|
-
const opened = await openPiInstitutionalSession({
|
|
593
|
-
cwd,
|
|
594
|
-
agentDir: scratch,
|
|
595
|
-
selection,
|
|
596
|
-
systemPrompt: options.systemPrompt,
|
|
597
|
-
customTools: [wrappedDossierTool, tool],
|
|
598
|
-
sessionManager: auditorSessionManager,
|
|
599
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
600
|
-
idleRetry: true,
|
|
601
|
-
label: options.roleLabel,
|
|
602
|
-
});
|
|
603
|
-
const { handle } = opened;
|
|
604
|
-
|
|
605
|
-
const binding: AuditorParentAttemptBinding = {
|
|
606
|
-
version: 1,
|
|
607
|
-
parent: {
|
|
608
|
-
...(parentHeader?.id === undefined ? {} : { sessionId: parentHeader.id }),
|
|
609
|
-
...(parentSessionFile === undefined ? {} : { sessionFile: parentSessionFile }),
|
|
610
|
-
...(parentAttemptEntryId === null || parentAttemptEntryId === undefined
|
|
611
|
-
? {}
|
|
612
|
-
: { attemptEntryId: parentAttemptEntryId }),
|
|
613
|
-
},
|
|
614
|
-
};
|
|
615
|
-
// Durable binding is a prerequisite: never observe the provider when its
|
|
616
|
-
// response could not later be tied to the current parent attempt.
|
|
617
|
-
auditorSessionManager.appendCustomEntry(AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, binding);
|
|
618
|
-
try {
|
|
619
|
-
sitianReport({
|
|
620
|
-
level: "event",
|
|
621
|
-
kind: "auditor",
|
|
622
|
-
cwd,
|
|
623
|
-
sessionParent: parentSessionFile,
|
|
624
|
-
payload: { type: AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, ...binding },
|
|
625
|
-
source: "evidence-child-executor",
|
|
626
|
-
});
|
|
627
|
-
} catch {}
|
|
628
|
-
|
|
629
|
-
const sessionUsage = emptyUsage();
|
|
630
|
-
let boundaryResponse: AssistantMessage | undefined;
|
|
631
|
-
let retentionFailure: unknown;
|
|
632
|
-
let retainedResponse: AssistantMessage | undefined;
|
|
633
|
-
let rejectedDecisionResponse: AssistantMessage | undefined;
|
|
634
|
-
let promptNeighboringFailure: unknown;
|
|
635
|
-
let promptDecisionFailures: unknown[] = [];
|
|
636
|
-
const findToolFailure = (response: AssistantMessage): unknown => {
|
|
637
|
-
const callIds = response.content.flatMap((part) =>
|
|
638
|
-
part.type === "toolCall" && part.name !== tool.name ? [part.id] : []);
|
|
639
|
-
for (const callId of callIds) {
|
|
640
|
-
if (evidenceToolFailures.has(callId)) return evidenceToolFailures.get(callId);
|
|
641
|
-
}
|
|
642
|
-
return undefined;
|
|
643
|
-
};
|
|
644
|
-
const drainRejectedDecisionFailures = (response: AssistantMessage) => {
|
|
645
|
-
for (const part of response.content) {
|
|
646
|
-
if (part.type !== "toolCall" || part.name !== tool.name || !decisionToolFailures.has(part.id)) continue;
|
|
647
|
-
decisionToolFailure = decisionToolFailures.get(part.id);
|
|
648
|
-
promptDecisionFailures.push(decisionToolFailure);
|
|
649
|
-
decisionToolFailures.delete(part.id);
|
|
650
|
-
}
|
|
651
|
-
};
|
|
652
|
-
const retainedAssistants: AssistantMessage[] = [];
|
|
653
|
-
const unsubscribe = handle.subscribe((event) => {
|
|
654
|
-
// Capture evidence-tool (non-decision) failures from the handle's real
|
|
655
|
-
// tool_result events. The old runCompletion path wrapped every child tool's
|
|
656
|
-
// execute to observe failures; through the real handle the adapter forwards
|
|
657
|
-
// tool_execution_end isError. Record the errored tool call id so an adjacent
|
|
658
|
-
// evidence failure can outrank a settled/correctable decision feedback.
|
|
659
|
-
if (event.type === "tool_result" && event.isError === true && event.toolName !== tool.name) {
|
|
660
|
-
// Through the real handle the child flattens a throwing evidence tool
|
|
661
|
-
// into a toolResult error whose content text carries the diagnostic
|
|
662
|
-
// (e.g. "ENOENT: no such file or directory..."). Recover that text so
|
|
663
|
-
// the adjacent evidence failure outranks a settled decision feedback
|
|
664
|
-
// with its original diagnostic, mirroring the pre-migration wrapped
|
|
665
|
-
// execute capture (which held the raw error object).
|
|
666
|
-
const detailText = extractToolResultText(event.details);
|
|
667
|
-
// A native unknown-tool receipt ("Tool <name> not found") is not an
|
|
668
|
-
// evidence-tool failure: the child session has no such tool registered,
|
|
669
|
-
// so its errored toolResult must not short-circuit the auditor (it keeps
|
|
670
|
-
// prompting, mirroring the pre-migration registeredToolNames exclusion
|
|
671
|
-
// in findToolFailure).
|
|
672
|
-
if (detailText !== undefined && /^Tool\s+.+ not found$/.test(detailText.trim())) return;
|
|
673
|
-
const failure = detailText === undefined
|
|
674
|
-
? new Error(event.toolName ?? "evidence tool failed")
|
|
675
|
-
: new Error(detailText);
|
|
676
|
-
// Recover the errno code from the flattened diagnostic so an evidence
|
|
677
|
-
// failure's identity (e.g. code "ENOENT") is preserved across the real
|
|
678
|
-
// HTTP round-trip, mirroring the pre-migration raw error object.
|
|
679
|
-
const errno = /^([A-Z_]+):/.exec(detailText ?? "");
|
|
680
|
-
if (errno !== null && errno[1] !== undefined) (failure as NodeJS.ErrnoException).code = errno[1];
|
|
681
|
-
evidenceToolFailures.set(event.toolCallId, failure);
|
|
682
|
-
}
|
|
683
|
-
if (event.type === "message_end" && event.role === "assistant" && boundaryResponse === undefined) {
|
|
684
|
-
if (event.usage) addUsage(sessionUsage, event.usage);
|
|
685
|
-
const msg = event.message as AssistantMessage | undefined;
|
|
686
|
-
retainedResponse = msg;
|
|
687
|
-
if (msg) {
|
|
688
|
-
retainedAssistants.push(msg);
|
|
689
|
-
try { options.retainResponse?.(msg); } catch (error) { retentionFailure = error; }
|
|
690
|
-
// A tool call in assistant output is only an observation. Preserve its
|
|
691
|
-
// candidate for typed malformed-decision settlement, but the wrapped
|
|
692
|
-
// execute path above is the sole owner of accepted-receipt state; a
|
|
693
|
-
// rejected execution must remain retryable in this same session.
|
|
694
|
-
for (const part of msg.content) {
|
|
695
|
-
if (part.type === "toolCall" && part.name === tool.name) {
|
|
696
|
-
rejectedDecisionResponse = msg;
|
|
697
|
-
if (decision === undefined) {
|
|
698
|
-
decision = (part.arguments === undefined
|
|
699
|
-
|| (typeof part.arguments === "object" && part.arguments !== null
|
|
700
|
-
&& !Array.isArray(part.arguments) && Object.keys(part.arguments).length === 0))
|
|
701
|
-
? undefined
|
|
702
|
-
: part.arguments;
|
|
703
|
-
decisionCallId = part.id;
|
|
704
|
-
// Pi can reject malformed root arguments before invoking execute;
|
|
705
|
-
// that remains the existing unreadable-candidate failure path.
|
|
706
|
-
// A missing root argument reaches the real provider adapter as an
|
|
707
|
-
// empty object after serialization — treat it as missing too so a
|
|
708
|
-
// one-shot typed missing-args settlement does not solicit another turn.
|
|
709
|
-
if (part.arguments === undefined
|
|
710
|
-
|| (typeof part.arguments === "object" && part.arguments !== null
|
|
711
|
-
&& !Array.isArray(part.arguments) && Object.keys(part.arguments).length === 0)) {
|
|
712
|
-
decisionSubmitted = true;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
if (msg.stopReason === "error") boundaryResponse = msg;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
if (event.type === "turn_end") {
|
|
721
|
-
if (rejectedDecisionResponse !== undefined) {
|
|
722
|
-
promptNeighboringFailure = findToolFailure(rejectedDecisionResponse);
|
|
723
|
-
drainRejectedDecisionFailures(rejectedDecisionResponse);
|
|
724
|
-
}
|
|
725
|
-
if (decisionSubmitted || promptNeighboringFailure !== undefined
|
|
726
|
-
|| (boundaryResponse !== undefined && rejectedDecisionResponse === undefined)
|
|
727
|
-
|| retentionFailure !== undefined) {
|
|
728
|
-
handle.abort();
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
|
-
const abort = () => { handle.abort(); };
|
|
733
|
-
if (options.signal?.aborted) abort();
|
|
734
|
-
else options.signal?.addEventListener("abort", abort, { once: true });
|
|
735
|
-
|
|
736
|
-
let auditorFailure: unknown;
|
|
737
|
-
try {
|
|
738
|
-
try {
|
|
739
|
-
const promptAllowingRejectedDecision = async (prompt: string) => {
|
|
740
|
-
rejectedDecisionResponse = undefined;
|
|
741
|
-
promptNeighboringFailure = undefined;
|
|
742
|
-
decisionToolFailure = undefined;
|
|
743
|
-
promptDecisionFailures = [];
|
|
744
|
-
let promptFailure: unknown;
|
|
745
|
-
try {
|
|
746
|
-
await handle.prompt(prompt);
|
|
747
|
-
} catch (error) {
|
|
748
|
-
promptFailure = error;
|
|
749
|
-
}
|
|
750
|
-
// Prefer turn_end correlation, but Pi may reject prompt() before that
|
|
751
|
-
// event. In that case correlate against this prompt's captured decision
|
|
752
|
-
// response and call-id maps at the catch boundary.
|
|
753
|
-
const correlatedResponse = rejectedDecisionResponse as AssistantMessage | undefined;
|
|
754
|
-
if (correlatedResponse !== undefined) {
|
|
755
|
-
promptNeighboringFailure ??= findToolFailure(correlatedResponse);
|
|
756
|
-
drainRejectedDecisionFailures(correlatedResponse);
|
|
757
|
-
}
|
|
758
|
-
// An adjacent failure outranks correctable decision feedback.
|
|
759
|
-
if (promptNeighboringFailure !== undefined) throw promptNeighboringFailure;
|
|
760
|
-
// An accepted correction in the same response owns the terminal
|
|
761
|
-
// outcome; correlated rejected siblings remain observations, not a
|
|
762
|
-
// stale failure capable of replacing that accepted receipt.
|
|
763
|
-
if (decisionSubmitted) {
|
|
764
|
-
decisionToolFailure = undefined;
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
if (decisionToolFailure !== undefined) return;
|
|
768
|
-
if (retentionFailure !== undefined) return;
|
|
769
|
-
if (opened.streamFailure !== undefined) throw opened.streamFailure;
|
|
770
|
-
if (promptFailure !== undefined) throw promptFailure;
|
|
771
|
-
};
|
|
772
|
-
const chargeAndClearRejectedDecisionFailures = (failures: unknown[]) => {
|
|
773
|
-
for (const failure of failures) {
|
|
774
|
-
delivery.recordRejected(failure instanceof Error ? failure.message : String(failure));
|
|
775
|
-
}
|
|
776
|
-
decisionToolFailure = undefined;
|
|
777
|
-
promptDecisionFailures = [];
|
|
778
|
-
};
|
|
779
|
-
await promptAllowingRejectedDecision(options.prompt);
|
|
780
|
-
while (!decisionSubmitted && retentionFailure === undefined && (boundaryResponse === undefined || decisionToolFailure !== undefined)
|
|
781
|
-
&& opened.streamFailure === undefined && delivery.nextAction() === "request-delivery") {
|
|
782
|
-
if (decisionToolFailure !== undefined) {
|
|
783
|
-
const failures = promptDecisionFailures.length === 0
|
|
784
|
-
? [decisionToolFailure]
|
|
785
|
-
: promptDecisionFailures;
|
|
786
|
-
chargeAndClearRejectedDecisionFailures(failures);
|
|
787
|
-
if (delivery.nextAction() === "no-receipt") boundaryResponse = undefined;
|
|
788
|
-
if (delivery.nextAction() === "request-delivery") {
|
|
789
|
-
// A rejection and its correction solicitation are one budget unit;
|
|
790
|
-
// recordRejected already charged it.
|
|
791
|
-
if (retainedResponse === rejectedDecisionResponse) {
|
|
792
|
-
await promptAllowingRejectedDecision(RECEIPT_DELIVERY_PROMPT);
|
|
793
|
-
chargeAndClearRejectedDecisionFailures(promptDecisionFailures);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
} else {
|
|
797
|
-
delivery.recordDeliveryRequest();
|
|
798
|
-
await promptAllowingRejectedDecision(RECEIPT_DELIVERY_PROMPT);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
if (!decisionSubmitted && retentionFailure === undefined && opened.streamFailure === undefined
|
|
802
|
-
&& delivery.nextAction() === "no-receipt") {
|
|
803
|
-
const runPointer = options.context.sessionManager.getSessionFile() ?? options.context.cwd ?? process.cwd();
|
|
804
|
-
const attemptPointer = binding.parent.attemptEntryId ?? binding.parent.sessionId ?? `current:${runPointer}`;
|
|
805
|
-
const facts = delivery.facts({ runPointer, attemptPointer });
|
|
806
|
-
decision = facts;
|
|
807
|
-
// Late turn_end feedback cannot overturn a lifecycle that has already
|
|
808
|
-
// charged this prompt to the exhausted shared budget.
|
|
809
|
-
decisionToolFailure = undefined;
|
|
810
|
-
auditorSessionManager.appendCustomEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
|
|
811
|
-
try {
|
|
812
|
-
sitianReport({
|
|
813
|
-
level: "event",
|
|
814
|
-
kind: "auditor",
|
|
815
|
-
cwd,
|
|
816
|
-
sessionParent: parentSessionFile,
|
|
817
|
-
payload: { type: NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, ...facts },
|
|
818
|
-
source: "evidence-child-executor",
|
|
819
|
-
});
|
|
820
|
-
} catch {}
|
|
821
|
-
// Provenance is granted only after the lifecycle owner persisted the
|
|
822
|
-
// current child record; accepted model arguments can never set it.
|
|
823
|
-
noReceiptLifecycle = facts;
|
|
824
|
-
}
|
|
825
|
-
} catch (error) {
|
|
826
|
-
if (options.signal?.aborted) throw options.signal.reason;
|
|
827
|
-
if (retentionFailure === undefined && opened.streamFailure !== undefined) throw opened.streamFailure;
|
|
828
|
-
if (retentionFailure === undefined) throw error;
|
|
829
|
-
}
|
|
830
|
-
if (options.signal?.aborted) throw options.signal.reason;
|
|
831
|
-
if (retentionFailure === undefined && opened.streamFailure !== undefined) throw opened.streamFailure;
|
|
832
|
-
if (!decisionSubmitted && decisionToolFailure !== undefined) throw decisionToolFailure;
|
|
833
|
-
const relevantResponse = !decisionSubmitted
|
|
834
|
-
? boundaryResponse
|
|
835
|
-
: (retainedResponse && retainedResponse.role === "assistant" && retainedResponse.content.some((part) => part.type === "toolCall" && part.name === tool.name)
|
|
836
|
-
? retainedResponse
|
|
837
|
-
: undefined);
|
|
838
|
-
if (relevantResponse !== undefined) {
|
|
839
|
-
const toolFailure = findToolFailure(relevantResponse);
|
|
840
|
-
if (toolFailure !== undefined) throw toolFailure;
|
|
841
|
-
}
|
|
842
|
-
const assistants = [...retainedAssistants].reverse();
|
|
843
|
-
const response = !decisionSubmitted
|
|
844
|
-
? assistants[0]
|
|
845
|
-
: assistants.find((message) =>
|
|
846
|
-
message.content.some((part) => part.type === "toolCall" && part.name === tool.name));
|
|
847
|
-
|
|
848
|
-
if (response !== undefined) {
|
|
849
|
-
try {
|
|
850
|
-
if (retentionFailure !== undefined) throw retentionFailure;
|
|
851
|
-
if (retainedResponse === undefined) options.retainResponse?.(response);
|
|
852
|
-
} catch (retentionFailure) {
|
|
853
|
-
if (response.stopReason !== "error") throw retentionFailure;
|
|
854
|
-
// Do not trim/rewrite the held errorMessage bytes.
|
|
855
|
-
const diagnostic = typeof response.errorMessage === "string" && response.errorMessage.trim() !== ""
|
|
856
|
-
? response.errorMessage
|
|
857
|
-
: undefined;
|
|
858
|
-
const projected = projectStructuredRemote(response);
|
|
859
|
-
const failure = new Error(
|
|
860
|
-
diagnostic ?? "",
|
|
861
|
-
{ cause: retentionFailure },
|
|
862
|
-
) as Error & {
|
|
863
|
-
knownCause: "provider" | "unrecognized";
|
|
864
|
-
failureCode?: string;
|
|
865
|
-
details: Record<string, unknown>;
|
|
866
|
-
};
|
|
867
|
-
if (projected.hasTestimony && (response.model || response.provider)) {
|
|
868
|
-
failure.name = response.model || response.provider || "Error";
|
|
869
|
-
failure.failureCode = response.provider || response.model;
|
|
870
|
-
}
|
|
871
|
-
failure.knownCause = projected.hasTestimony ? "provider" : "unrecognized";
|
|
872
|
-
const retentionError = retentionFailure instanceof Error ? retentionFailure : undefined;
|
|
873
|
-
const retentionCause = retentionError?.cause;
|
|
874
|
-
failure.details = {
|
|
875
|
-
...(diagnostic === undefined ? {} : { errorMessage: diagnostic }),
|
|
876
|
-
...(projected.hasTestimony && response.provider ? { provider: response.provider } : {}),
|
|
877
|
-
...(projected.hasTestimony && response.model ? { model: response.model } : {}),
|
|
878
|
-
...(response.api ? { api: response.api } : {}),
|
|
879
|
-
...(response.rawStopReason ? { rawStopReason: response.rawStopReason } : {}),
|
|
880
|
-
...(projected.httpStatus === undefined ? {} : { httpStatus: projected.httpStatus }),
|
|
881
|
-
...(projected.diagnostics === undefined ? {} : { diagnostics: projected.diagnostics }),
|
|
882
|
-
...(projected.body === undefined ? {} : { body: projected.body }),
|
|
883
|
-
...(projected.code === undefined ? {} : { code: projected.code }),
|
|
884
|
-
...(projected.errno === undefined ? {} : { errno: projected.errno }),
|
|
885
|
-
retentionFailure: {
|
|
886
|
-
name: retentionError?.name ?? typeof retentionFailure,
|
|
887
|
-
message: retentionError?.message ?? String(retentionFailure),
|
|
888
|
-
...((retentionError as Error & { code?: unknown } | undefined)?.code !== undefined
|
|
889
|
-
? { code: (retentionError as Error & { code?: unknown }).code }
|
|
890
|
-
: {}),
|
|
891
|
-
...(retentionCause === undefined
|
|
892
|
-
? {}
|
|
893
|
-
: {
|
|
894
|
-
cause: retentionCause instanceof Error
|
|
895
|
-
? {
|
|
896
|
-
name: retentionCause.name,
|
|
897
|
-
message: retentionCause.message,
|
|
898
|
-
...((retentionCause as Error & { code?: unknown }).code === undefined
|
|
899
|
-
? {}
|
|
900
|
-
: { code: (retentionCause as Error & { code?: unknown }).code }),
|
|
901
|
-
}
|
|
902
|
-
: retentionCause,
|
|
903
|
-
}),
|
|
904
|
-
},
|
|
905
|
-
};
|
|
906
|
-
const failureData = {
|
|
907
|
-
version: 1 as const,
|
|
908
|
-
parent: binding.parent,
|
|
909
|
-
failure: {
|
|
910
|
-
cause: failure.knownCause,
|
|
911
|
-
...(failure.failureCode === undefined ? {} : { identity: { name: failure.name, code: failure.failureCode } }),
|
|
912
|
-
...(failure.message === "" ? {} : { diagnostic: failure.message }),
|
|
913
|
-
details: failure.details,
|
|
914
|
-
},
|
|
915
|
-
};
|
|
916
|
-
auditorSessionManager.appendCustomEntry(AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, failureData);
|
|
917
|
-
try {
|
|
918
|
-
sitianReport({
|
|
919
|
-
level: "event",
|
|
920
|
-
kind: "auditor",
|
|
921
|
-
cwd,
|
|
922
|
-
sessionParent: parentSessionFile,
|
|
923
|
-
payload: { type: AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, ...failureData },
|
|
924
|
-
source: "evidence-child-executor",
|
|
925
|
-
});
|
|
926
|
-
} catch {}
|
|
927
|
-
throw failure;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (
|
|
932
|
-
response === undefined
|
|
933
|
-
|| response.stopReason === "error"
|
|
934
|
-
|| (!decisionSubmitted && (response.stopReason === "aborted" || decision === undefined))
|
|
935
|
-
) {
|
|
936
|
-
throw new Error(response?.errorMessage ?? `${options.roleLabel} exited without a readable decision receipt`);
|
|
937
|
-
}
|
|
938
|
-
return {
|
|
939
|
-
decision,
|
|
940
|
-
response: { ...response, usage: sessionUsage },
|
|
941
|
-
...(noReceiptLifecycle === undefined ? {} : { noReceiptLifecycle }),
|
|
942
|
-
};
|
|
943
|
-
} catch (error) {
|
|
944
|
-
auditorFailure = error;
|
|
945
|
-
throw error;
|
|
946
|
-
} finally {
|
|
947
|
-
options.signal?.removeEventListener("abort", abort);
|
|
948
|
-
await runChildCleanup([() => unsubscribe(), () => handle.close()], auditorFailure, options.roleLabel);
|
|
949
|
-
}
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
function exactRecord(value: unknown): value is Record<string, unknown> {
|
|
954
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
/**
|
|
958
|
-
* Navigator institutional child on this unique lifecycle seam (#590 / ADR 0018).
|
|
959
|
-
* Owns record-session / open / subscribe / close. Caller keeps tool, label, and
|
|
960
|
-
* failure projection only.
|
|
961
|
-
*/
|
|
962
|
-
export function createNativeNavigatorSessionFactory(
|
|
963
|
-
defaultModelSettingPath = navigatorModelSettingPath(),
|
|
964
|
-
): NavigatorSessionFactory {
|
|
965
|
-
return async ({ context, subject, modelSettingPath, tool }) => {
|
|
966
|
-
const resolved = await resolveNavigatorSeatSelection(context, modelSettingPath, defaultModelSettingPath);
|
|
967
|
-
let selection = resolved.selection;
|
|
968
|
-
let thinkingLevel: string | undefined = resolved.thinkingLevel;
|
|
969
|
-
let configuredLabel = resolved.configuredLabel;
|
|
970
|
-
|
|
971
|
-
const { createRecordSession } = await import("./archivist-record-entry.ts");
|
|
972
|
-
const sessionManager = createRecordSession({
|
|
973
|
-
cwd: context.cwd,
|
|
974
|
-
kind: "navigator",
|
|
975
|
-
subject,
|
|
976
|
-
parent: context.sessionManager,
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
let providerFailure: NavigatorProviderFailureFact | undefined;
|
|
980
|
-
let observationWrite: Promise<void> = Promise.resolve();
|
|
981
|
-
const assignProviderFailure = (fact: NavigatorProviderFailureFact | undefined): void => {
|
|
982
|
-
if (fact !== undefined) providerFailure = fact;
|
|
983
|
-
};
|
|
984
|
-
const classifyTerminalMessage = (message: unknown): void => {
|
|
985
|
-
if (!exactRecord(message)) {
|
|
986
|
-
assignProviderFailure(navigatorProviderFailureFromError(message));
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
assignProviderFailure(navigatorProviderFailureFromDiagnostics(message.diagnostics));
|
|
990
|
-
if (providerFailure !== undefined) return;
|
|
991
|
-
if (message.role !== "assistant") assignProviderFailure(navigatorProviderFailureFromError(message));
|
|
992
|
-
if (providerFailure !== undefined) return;
|
|
993
|
-
const status = typeof message.statusCode === "number"
|
|
994
|
-
? message.statusCode
|
|
995
|
-
: typeof message.status === "number"
|
|
996
|
-
? message.status
|
|
997
|
-
: typeof message.httpStatus === "number"
|
|
998
|
-
? message.httpStatus
|
|
999
|
-
: undefined;
|
|
1000
|
-
if (typeof status === "number") {
|
|
1001
|
-
assignProviderFailure(navigatorProviderFailureFromStatus(status));
|
|
1002
|
-
if (providerFailure === undefined && status >= 400 && status < 600) {
|
|
1003
|
-
assignProviderFailure({ source: "transport", cause: "transport" });
|
|
1004
|
-
}
|
|
1005
|
-
const runDir = process.env.AK_ROLE_RUN_DIR;
|
|
1006
|
-
const provider = typeof message.provider === "string" && message.provider.trim() !== ""
|
|
1007
|
-
? message.provider
|
|
1008
|
-
: selection.provider;
|
|
1009
|
-
if (typeof runDir === "string" && runDir.trim() !== "" && provider.trim() !== "") {
|
|
1010
|
-
observationWrite = observationWrite.then(() =>
|
|
1011
|
-
recordTypedProviderHttpStatus(runDir, { httpStatus: status, provider }),
|
|
1012
|
-
);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
};
|
|
1016
|
-
|
|
1017
|
-
const { openPiInstitutionalSession } = await import("./pi/in-process-session.ts");
|
|
1018
|
-
let opened: Awaited<ReturnType<typeof openPiInstitutionalSession>>;
|
|
1019
|
-
try {
|
|
1020
|
-
opened = await openPiInstitutionalSession({
|
|
1021
|
-
cwd: context.cwd,
|
|
1022
|
-
selection,
|
|
1023
|
-
systemPrompt: "",
|
|
1024
|
-
noTools: "all",
|
|
1025
|
-
toolsAllowlist: [NAVIGATOR_PREPARE_TOOL_NAME],
|
|
1026
|
-
customTools: [tool],
|
|
1027
|
-
sessionManager,
|
|
1028
|
-
label: "Navigator",
|
|
1029
|
-
});
|
|
1030
|
-
} catch (error) {
|
|
1031
|
-
const fact = navigatorProviderFailureFromError(error);
|
|
1032
|
-
throw navigatorUnavailableError(fact?.source ?? "session", error, fact?.cause ?? "session");
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
const unsubscribe = opened.handle.subscribe((event) => {
|
|
1036
|
-
if (event.type === "message_end" && event.message !== undefined) {
|
|
1037
|
-
const message = event.message;
|
|
1038
|
-
if (exactRecord(message) && (message.stopReason === "error" || message.stopReason === "aborted")) {
|
|
1039
|
-
classifyTerminalMessage(message);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
|
|
1044
|
-
let disposal: Promise<void> | undefined;
|
|
1045
|
-
const dispose = (): Promise<void> => {
|
|
1046
|
-
if (disposal === undefined) {
|
|
1047
|
-
disposal = runChildCleanup(
|
|
1048
|
-
[() => unsubscribe(), () => opened.handle.close()],
|
|
1049
|
-
undefined,
|
|
1050
|
-
"Navigator",
|
|
1051
|
-
);
|
|
1052
|
-
}
|
|
1053
|
-
return disposal;
|
|
1054
|
-
};
|
|
1055
|
-
|
|
1056
|
-
return {
|
|
1057
|
-
prompt: async (text) => {
|
|
1058
|
-
providerFailure = undefined;
|
|
1059
|
-
observationWrite = Promise.resolve();
|
|
1060
|
-
const failFrom = (error: unknown): never => {
|
|
1061
|
-
if (providerFailure === undefined) {
|
|
1062
|
-
assignProviderFailure({ source: "transport", cause: "transport" });
|
|
1063
|
-
}
|
|
1064
|
-
const fact = providerFailure!;
|
|
1065
|
-
throw navigatorUnavailableError(fact.source, error, fact.cause);
|
|
1066
|
-
};
|
|
1067
|
-
let terminal: unknown;
|
|
1068
|
-
try {
|
|
1069
|
-
const turn = await opened.handle.prompt(text);
|
|
1070
|
-
if (turn.stopReason === "error" || turn.stopReason === "aborted") {
|
|
1071
|
-
const cause = turn.errorMessage ?? opened.streamFailure ?? "Navigator provider failure";
|
|
1072
|
-
if (providerFailure === undefined && opened.streamFailure !== undefined) {
|
|
1073
|
-
classifyTerminalMessage(opened.streamFailure);
|
|
1074
|
-
}
|
|
1075
|
-
if (providerFailure === undefined) {
|
|
1076
|
-
assignProviderFailure(navigatorProviderFailureFromError(
|
|
1077
|
-
typeof cause === "object" && cause !== null ? cause : new Error(String(cause)),
|
|
1078
|
-
));
|
|
1079
|
-
}
|
|
1080
|
-
terminal = cause;
|
|
1081
|
-
} else if (opened.streamFailure !== undefined) {
|
|
1082
|
-
if (providerFailure === undefined) classifyTerminalMessage(opened.streamFailure);
|
|
1083
|
-
terminal = opened.streamFailure;
|
|
1084
|
-
}
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
if (error instanceof NavigatorUnavailableError) throw error;
|
|
1087
|
-
if (providerFailure === undefined) classifyTerminalMessage(error);
|
|
1088
|
-
terminal = error;
|
|
1089
|
-
}
|
|
1090
|
-
await observationWrite;
|
|
1091
|
-
if (terminal !== undefined) failFrom(terminal);
|
|
1092
|
-
},
|
|
1093
|
-
providerFailure: () => providerFailure,
|
|
1094
|
-
appendEntry: (customType, data) => {
|
|
1095
|
-
sessionManager.appendCustomEntry(customType, data);
|
|
1096
|
-
try {
|
|
1097
|
-
sitianReport({
|
|
1098
|
-
level: "event",
|
|
1099
|
-
kind: "attendance",
|
|
1100
|
-
cwd: context.cwd,
|
|
1101
|
-
sessionParent: sessionManager.getSessionFile(),
|
|
1102
|
-
payload: { customType, data },
|
|
1103
|
-
source: "evidence-child-executor",
|
|
1104
|
-
});
|
|
1105
|
-
} catch {
|
|
1106
|
-
// best-effort
|
|
1107
|
-
}
|
|
1108
|
-
},
|
|
1109
|
-
entries: () => sessionManager.getEntries(),
|
|
1110
|
-
setModel: async (next) => {
|
|
1111
|
-
let nextParsed: ReturnType<typeof parseNavigatorModelSetting>;
|
|
1112
|
-
try {
|
|
1113
|
-
nextParsed = parseNavigatorModelSetting(next);
|
|
1114
|
-
} catch (error) {
|
|
1115
|
-
throw navigatorUnavailableError("model", error);
|
|
1116
|
-
}
|
|
1117
|
-
if (
|
|
1118
|
-
nextParsed.provider !== selection.provider
|
|
1119
|
-
|| nextParsed.model !== selection.model
|
|
1120
|
-
) {
|
|
1121
|
-
throw new NavigatorUnavailableError(
|
|
1122
|
-
"model",
|
|
1123
|
-
`Navigator model switch requires a new session: ${configuredLabel} → ${next}`,
|
|
1124
|
-
);
|
|
1125
|
-
}
|
|
1126
|
-
// Thinking is opaque pass-through; no stick/availability re-check.
|
|
1127
|
-
selection = {
|
|
1128
|
-
provider: nextParsed.provider,
|
|
1129
|
-
model: nextParsed.model,
|
|
1130
|
-
...(nextParsed.thinkingLevel === undefined ? {} : { thinking: nextParsed.thinkingLevel }),
|
|
1131
|
-
};
|
|
1132
|
-
thinkingLevel = nextParsed.thinkingLevel;
|
|
1133
|
-
configuredLabel = next;
|
|
1134
|
-
},
|
|
1135
|
-
getThinkingLevel: () => thinkingLevel,
|
|
1136
|
-
recordPointer: () => sessionManager.getSessionDir(),
|
|
1137
|
-
dispose,
|
|
1138
|
-
};
|
|
1139
|
-
};
|
|
1140
|
-
}
|