@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2653-next.71811ca
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/README.md +129 -39
- package/README.zh-CN.md +128 -39
- package/dist/activation-ledger-topology.js +1 -1
- package/dist/archivist-record-entry.js +47 -26
- package/dist/auditor-dossier-tool.js +3 -1
- package/dist/compliance-transport.js +17 -26
- package/dist/doctor-contracts.js +1 -2
- package/dist/dossier-resolution.js +4 -8
- package/dist/engine-detour-tool.js +33 -8
- package/dist/engine-detour.js +1 -0
- package/dist/evidence-child-executor.js +277 -424
- package/dist/host-contracts.js +22 -0
- package/dist/institutional-resolution.js +88 -0
- package/dist/merger-contracts.js +1 -2
- package/dist/navigator-attendance.js +16 -0
- package/dist/notary-contracts.js +2 -3
- package/dist/package-contracts/fixer-output.js +1 -2
- package/dist/packaged-role-registry.js +102 -9
- package/dist/pi/in-process-session.js +671 -0
- package/dist/public-cli/config.js +7 -5
- package/dist/public-cli/main.js +3767 -4113
- package/dist/reviewer-child-executor.js +6 -1
- package/dist/session-opening-materials.js +10 -38
- package/dist/sitian-appender.js +146 -0
- package/dist/sitian-contracts.js +21 -0
- package/dist/sitian-facade.js +15 -0
- package/dist/sitian-reader.js +52 -0
- package/extensions/role-runtime.ts +9 -7
- package/package.json +5 -5
- package/resources/engines/cursor.md +12 -26
- package/resources/engines/hermes.md +2 -23
- package/resources/engines/opus.md +1 -16
- package/resources/navigator-route-playbook.md +2 -2
- package/scripts/build-package.mjs +0 -1
- package/souls/quality-law.md +1 -1
- package/src/activation-ledger-topology.ts +1 -1
- package/src/archivist-record-entry.ts +57 -45
- package/src/auditor-dossier-tool.ts +4 -2
- package/src/canonical-skill-binding.ts +37 -27
- package/src/collector-role.ts +11 -20
- package/src/collector-tool-schemas.ts +1 -4
- package/src/compliance-transport.ts +19 -44
- package/src/doctor-auditor.ts +1 -5
- package/src/doctor-contracts.ts +1 -4
- package/src/doctor-role.ts +31 -8
- package/src/dossier-resolution.ts +8 -8
- package/src/engine-detour-tool.ts +48 -19
- package/src/engine-detour.ts +3 -0
- package/src/evidence-child-executor.ts +277 -485
- package/src/factory-board.ts +3 -3
- package/src/gatekeeper-role.ts +21 -10
- package/src/host-contracts.ts +386 -0
- package/src/in-process-session.ts +1 -115
- package/src/institutional-resolution.ts +135 -0
- package/src/judge-auditor.ts +1 -5
- package/src/judge-role.ts +37 -51
- package/src/merger-contracts.ts +1 -4
- package/src/merger-role.ts +7 -11
- package/src/navigator-attendance.ts +20 -1
- package/src/notary-contracts.ts +8 -11
- package/src/notary-role.ts +7 -23
- package/src/notary-source-run.ts +4 -3
- package/src/package-contracts/fixer-output.ts +1 -4
- package/src/package-resources/method-skill-binding.ts +10 -34
- package/src/packaged-role-registry.ts +122 -12
- package/src/pi/adapter.ts +192 -0
- package/src/pi/durable-principal.ts +98 -0
- package/src/pi/in-process-session.ts +819 -0
- package/src/pi/known-failure.ts +77 -0
- package/src/pi/pi-normalization.ts +284 -0
- package/src/pi/role-turn-host.ts +459 -0
- package/src/public-cli/auto-resume.ts +42 -44
- package/src/public-cli/cli.ts +171 -257
- package/src/public-cli/coder-run.ts +115 -433
- package/src/public-cli/collector-run.ts +62 -314
- package/src/public-cli/config.ts +7 -5
- package/src/public-cli/doctor-run.ts +41 -52
- package/src/public-cli/fixer-run.ts +122 -427
- package/src/public-cli/invocation.ts +287 -96
- package/src/public-cli/judge-run.ts +92 -422
- package/src/public-cli/merger-run.ts +167 -429
- package/src/public-cli/notary-run.ts +41 -47
- package/src/public-cli/option-definitions.ts +136 -3
- package/src/public-cli/post-admission.ts +497 -0
- package/src/public-cli/public-run-credentials.ts +4 -4
- package/src/public-cli/reviewer-dispatch-rejection.ts +106 -0
- package/src/public-cli/reviewer-run.ts +132 -449
- package/src/public-cli/run-lifecycle.ts +339 -327
- package/src/public-cli/settlement.ts +251 -134
- package/src/public-cli/terminal.ts +3 -9
- package/src/public-cli/turn-request.ts +87 -0
- package/src/reviewer-child-executor.ts +9 -1
- package/src/reviewer-role.ts +11 -20
- package/src/role-runtime.ts +129 -78
- package/src/session-opening-materials.ts +25 -39
- package/src/sitian-appender.ts +171 -0
- package/src/sitian-contracts.ts +106 -0
- package/src/sitian-facade.ts +18 -0
- package/src/sitian-reader.ts +62 -0
- package/src/worker-role.ts +42 -40
- package/src/worker-submission-gates.ts +33 -0
- package/dist/archivist-role-run-coordinates.js +0 -28
- package/dist/package-contracts/terminating-infrastructure.js +0 -90
- package/resources/engines/sonnet.md +0 -27
- package/resources/engines/zcode.md +0 -63
- package/src/archivist-role-run-coordinates.ts +0 -45
- package/src/package-contracts/terminating-infrastructure.ts +0 -132
- package/src/public-cli/explicit-internal.ts +0 -427
- package/src/public-cli/one-shot-dispatch.ts +0 -300
|
@@ -7,28 +7,25 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import {
|
|
10
|
-
createAssistantMessageEventStream,
|
|
11
|
-
InMemoryCredentialStore,
|
|
12
10
|
type Api,
|
|
13
11
|
type AssistantMessage,
|
|
14
12
|
type Context,
|
|
15
13
|
type Model,
|
|
16
|
-
type Provider,
|
|
17
14
|
type ProviderStreamOptions,
|
|
18
15
|
type Usage,
|
|
19
16
|
} from "@earendil-works/pi-ai";
|
|
20
17
|
import type {
|
|
21
18
|
ExtensionContext,
|
|
22
|
-
ModelRuntime,
|
|
23
19
|
SessionManager,
|
|
24
20
|
} from "@earendil-works/pi-coding-agent";
|
|
25
21
|
|
|
26
22
|
import {
|
|
27
23
|
AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE,
|
|
28
24
|
AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE,
|
|
29
|
-
prepareComplianceDispatch,
|
|
30
25
|
type AuditorParentAttemptBinding,
|
|
31
26
|
} from "./compliance-transport.ts";
|
|
27
|
+
import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
|
|
28
|
+
import { sitianReport } from "./sitian-facade.ts";
|
|
32
29
|
import { createEngineDetourToolDefinition } from "./engine-detour-tool.ts";
|
|
33
30
|
import { engineNameFromEnv } from "./engine-detour.ts";
|
|
34
31
|
import {
|
|
@@ -38,15 +35,16 @@ import {
|
|
|
38
35
|
} from "./package-resources/engine-material.ts";
|
|
39
36
|
import { readPackageMaterial } from "./session-opening-materials.ts";
|
|
40
37
|
import {
|
|
41
|
-
formatModelSpec,
|
|
42
|
-
loadPublicCliConfig,
|
|
43
|
-
resolveGateOfficerModelSelection,
|
|
44
38
|
type GateOfficerSeat,
|
|
45
39
|
} from "./public-cli/config.ts";
|
|
46
|
-
import
|
|
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";
|
|
47
46
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT, type NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
48
47
|
import type { ReviewerPromptText } from "./reviewer-prompt-identity.ts";
|
|
49
|
-
import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.ts";
|
|
50
48
|
import {
|
|
51
49
|
hasUpstreamErrorTestimony,
|
|
52
50
|
isNonSuccessHttpStatus,
|
|
@@ -95,11 +93,6 @@ export class AuditorTurnLimitError extends Error {
|
|
|
95
93
|
this.name = "AuditorTurnLimitError";
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
|
-
export type AuditorCompletion = (
|
|
99
|
-
model: Model<Api>,
|
|
100
|
-
context: Context,
|
|
101
|
-
options: ProviderStreamOptions,
|
|
102
|
-
) => Promise<AssistantMessage>;
|
|
103
96
|
export type AuditorDecisionTool = {
|
|
104
97
|
name: string;
|
|
105
98
|
description: string;
|
|
@@ -136,288 +129,40 @@ export async function withInProcessScratch<T>(
|
|
|
136
129
|
}
|
|
137
130
|
}
|
|
138
131
|
|
|
139
|
-
export type InheritedRuntimeOptions = {
|
|
140
|
-
readonly context: ExtensionContext;
|
|
141
|
-
readonly label: string;
|
|
142
|
-
readonly runCompletion?: AuditorCompletion;
|
|
143
|
-
readonly injectedSystemPrompt?: string;
|
|
144
|
-
readonly signal?: AbortSignal;
|
|
145
|
-
/** When true, wrap provider streams with ADR 0059 idle-only retry. */
|
|
146
|
-
readonly idleRetry?: boolean;
|
|
147
|
-
/**
|
|
148
|
-
* Explicit child model. When set, auth/availability failures do not fall back
|
|
149
|
-
* to the parent session model (#453 gate overrides).
|
|
150
|
-
*/
|
|
151
|
-
readonly model?: Model<Api>;
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
export type InheritedRuntime = {
|
|
155
|
-
readonly runtime: ModelRuntime;
|
|
156
|
-
readonly model: Model<Api>;
|
|
157
|
-
readonly dispatch: Awaited<ReturnType<typeof prepareComplianceDispatch>>;
|
|
158
|
-
/** Present when a stream failed under idle-retry instrumentation. */
|
|
159
|
-
streamFailure: unknown;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
132
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
133
|
+
* Run every child cleanup, aggregating failures so one throwing cleanup never
|
|
134
|
+
* skips the rest (e.g. handle.close must still run when unsubscribe throws).
|
|
135
|
+
* If a primary failure is supplied and cleanup also failed, the two are combined
|
|
136
|
+
* into an AggregateError; otherwise only the failing branch is surfaced.
|
|
165
137
|
*/
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
? options.context.modelRegistry.getProvider(activeModel.provider)
|
|
174
|
-
: undefined;
|
|
175
|
-
if (parentProvider === undefined && options.runCompletion === undefined) {
|
|
176
|
-
throw new Error(`${options.label} provider not found: ${activeModel.provider}`);
|
|
177
|
-
}
|
|
178
|
-
const runtime = await ModelRuntime.create({
|
|
179
|
-
credentials: new InMemoryCredentialStore(),
|
|
180
|
-
modelsPath: null,
|
|
181
|
-
});
|
|
182
|
-
// Injected completions historically accepted the minimal model exposed by an
|
|
183
|
-
// ExtensionContext. AgentSession crosses ModelRuntime first, so complete the
|
|
184
|
-
// model metadata required by that runtime without changing provider identity.
|
|
185
|
-
const inheritedModel: Model<Api> = options.runCompletion === undefined
|
|
186
|
-
? dispatch.model
|
|
187
|
-
: {
|
|
188
|
-
...dispatch.model,
|
|
189
|
-
name: dispatch.model.name ?? dispatch.model.id,
|
|
190
|
-
baseUrl: dispatch.model.baseUrl ?? "",
|
|
191
|
-
reasoning: dispatch.model.reasoning ?? false,
|
|
192
|
-
input: dispatch.model.input ?? ["text"],
|
|
193
|
-
cost: dispatch.model.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
194
|
-
contextWindow: dispatch.model.contextWindow ?? 1,
|
|
195
|
-
maxTokens: dispatch.model.maxTokens ?? 1,
|
|
196
|
-
};
|
|
197
|
-
const state: InheritedRuntime = {
|
|
198
|
-
runtime,
|
|
199
|
-
model: inheritedModel,
|
|
200
|
-
dispatch,
|
|
201
|
-
streamFailure: undefined,
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const abortReason = (signal: AbortSignal): unknown =>
|
|
205
|
-
signal.reason ?? new Error(`${options.label} provider stream aborted`);
|
|
206
|
-
|
|
207
|
-
async function waitForStream<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
208
|
-
if (signal.aborted) throw abortReason(signal);
|
|
209
|
-
let onAbort: (() => void) | undefined;
|
|
138
|
+
async function runChildCleanup(
|
|
139
|
+
cleanups: ReadonlyArray<() => void | Promise<void>>,
|
|
140
|
+
primaryFailure: unknown,
|
|
141
|
+
label: string,
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
let cleanupFailure: unknown;
|
|
144
|
+
for (const cleanup of cleanups) {
|
|
210
145
|
try {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} finally {
|
|
219
|
-
if (onAbort !== undefined) signal.removeEventListener("abort", onAbort);
|
|
146
|
+
await cleanup();
|
|
147
|
+
} catch (failure) {
|
|
148
|
+
cleanupFailure = cleanupFailure === undefined
|
|
149
|
+
? failure
|
|
150
|
+
: new AggregateError([cleanupFailure, failure], `${label} cleanup failed`, {
|
|
151
|
+
cause: cleanupFailure,
|
|
152
|
+
});
|
|
220
153
|
}
|
|
221
154
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
options.signal === undefined ? {} : { parentSignal: options.signal },
|
|
234
|
-
);
|
|
235
|
-
// Typed HTTP status observed via onResponse — never inferred from prose.
|
|
236
|
-
let observedHttpStatus: number | undefined;
|
|
237
|
-
try {
|
|
238
|
-
const requestSignal = request?.signal;
|
|
239
|
-
const streamSignal = requestSignal === undefined
|
|
240
|
-
? idle.signal
|
|
241
|
-
: AbortSignal.any([idle.signal, requestSignal]);
|
|
242
|
-
const priorOnResponse = request?.onResponse;
|
|
243
|
-
const inheritedRequest = {
|
|
244
|
-
...(request ?? {}),
|
|
245
|
-
...(dispatch.auth.env === undefined ? {} : { env: dispatch.auth.env }),
|
|
246
|
-
signal: streamSignal,
|
|
247
|
-
onResponse: async (
|
|
248
|
-
response: { status: number; headers: Record<string, string> },
|
|
249
|
-
model: Model<Api>,
|
|
250
|
-
) => {
|
|
251
|
-
if (typeof response?.status === "number") observedHttpStatus = response.status;
|
|
252
|
-
await priorOnResponse?.(response, model);
|
|
253
|
-
},
|
|
254
|
-
} as ProviderStreamOptions;
|
|
255
|
-
if (options.runCompletion !== undefined) {
|
|
256
|
-
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
257
|
-
if (streamSignal.aborted) throw abortReason(streamSignal);
|
|
258
|
-
const completed = await waitForStream(
|
|
259
|
-
options.runCompletion(
|
|
260
|
-
model,
|
|
261
|
-
options.injectedSystemPrompt === undefined
|
|
262
|
-
? context
|
|
263
|
-
: { ...context, systemPrompt: options.injectedSystemPrompt },
|
|
264
|
-
inheritedRequest,
|
|
265
|
-
),
|
|
266
|
-
streamSignal,
|
|
267
|
-
);
|
|
268
|
-
const response: AssistantMessage = attachObservedHttpStatus({
|
|
269
|
-
...completed,
|
|
270
|
-
api: model.api,
|
|
271
|
-
provider: model.provider,
|
|
272
|
-
model: model.id,
|
|
273
|
-
}, observedHttpStatus);
|
|
274
|
-
if (response.stopReason === "error" || response.stopReason === "aborted") {
|
|
275
|
-
wrapped.push({ type: "error", reason: response.stopReason, error: response });
|
|
276
|
-
} else {
|
|
277
|
-
wrapped.end(response);
|
|
278
|
-
}
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
const source = simple
|
|
282
|
-
? parentProvider!.streamSimple(model, context, inheritedRequest as any)
|
|
283
|
-
: parentProvider!.stream(model, context, inheritedRequest as any);
|
|
284
|
-
let sawEvent = false;
|
|
285
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
286
|
-
while (true) {
|
|
287
|
-
const next = await waitForStream(iterator.next(), idle.signal);
|
|
288
|
-
if (next.done) break;
|
|
289
|
-
sawEvent = true;
|
|
290
|
-
idle.poke();
|
|
291
|
-
wrapped.push(enrichStreamEvent(next.value, observedHttpStatus) as any);
|
|
292
|
-
}
|
|
293
|
-
const response = attachObservedHttpStatus(
|
|
294
|
-
await waitForStream(source.result(), idle.signal),
|
|
295
|
-
observedHttpStatus,
|
|
296
|
-
);
|
|
297
|
-
if (!sawEvent) wrapped.end(response);
|
|
298
|
-
return;
|
|
299
|
-
} catch (error) {
|
|
300
|
-
if (request?.signal?.aborted) {
|
|
301
|
-
const response: AssistantMessage = {
|
|
302
|
-
role: "assistant",
|
|
303
|
-
content: [],
|
|
304
|
-
api: model.api,
|
|
305
|
-
provider: model.provider,
|
|
306
|
-
model: model.id,
|
|
307
|
-
usage: emptyUsage(),
|
|
308
|
-
stopReason: "aborted",
|
|
309
|
-
errorMessage: "Auditor session aborted",
|
|
310
|
-
timestamp: Date.now(),
|
|
311
|
-
};
|
|
312
|
-
wrapped.push({ type: "error", reason: "aborted", error: response });
|
|
313
|
-
wrapped.end(response);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const failure = isStreamIdleTimeoutError(idle.signal.reason) ? idle.signal.reason : error;
|
|
317
|
-
if (
|
|
318
|
-
options.idleRetry === true
|
|
319
|
-
&& isStreamIdleTimeoutError(failure)
|
|
320
|
-
&& attempt < DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES
|
|
321
|
-
&& options.signal?.aborted !== true
|
|
322
|
-
) {
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
state.streamFailure = failure;
|
|
326
|
-
const projected = projectStructuredRemote(failure);
|
|
327
|
-
// Prefer structured fields on the thrown failure; fall back to onResponse observation.
|
|
328
|
-
const httpStatus = projected.httpStatus ?? numericHttpStatus(observedHttpStatus);
|
|
329
|
-
const response = {
|
|
330
|
-
role: "assistant" as const,
|
|
331
|
-
content: [] as [],
|
|
332
|
-
api: model.api,
|
|
333
|
-
provider: model.provider,
|
|
334
|
-
model: model.id,
|
|
335
|
-
usage: emptyUsage(),
|
|
336
|
-
stopReason: "error" as const,
|
|
337
|
-
// Preserve the held failure message bytes — do not rewrite.
|
|
338
|
-
errorMessage: failure instanceof Error ? failure.message : String(failure),
|
|
339
|
-
timestamp: Date.now(),
|
|
340
|
-
...(projected.diagnostics === undefined ? {} : { diagnostics: projected.diagnostics }),
|
|
341
|
-
...(httpStatus === undefined ? {} : { status: httpStatus, statusCode: httpStatus }),
|
|
342
|
-
...(projected.body === undefined ? {} : { body: projected.body }),
|
|
343
|
-
...(projected.code === undefined ? {} : { code: projected.code }),
|
|
344
|
-
...(projected.errno === undefined ? {} : { errno: projected.errno }),
|
|
345
|
-
} as unknown as AssistantMessage;
|
|
346
|
-
wrapped.push({ type: "error", reason: "error", error: response });
|
|
347
|
-
wrapped.end(response);
|
|
348
|
-
return;
|
|
349
|
-
} finally {
|
|
350
|
-
idle.dispose();
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
})();
|
|
354
|
-
return wrapped as ReturnType<Provider["stream"]>;
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
// Provider id must match activeModel.provider so ModelRuntime auth lookup
|
|
358
|
-
// finds this registration when a gate override changes provider (#453).
|
|
359
|
-
const provider: Provider = options.idleRetry === true || options.runCompletion !== undefined
|
|
360
|
-
? {
|
|
361
|
-
id: activeModel.provider,
|
|
362
|
-
name: parentProvider?.name ?? options.label,
|
|
363
|
-
auth: {
|
|
364
|
-
apiKey: {
|
|
365
|
-
name: `Inherited ${options.label} authentication`,
|
|
366
|
-
async resolve() {
|
|
367
|
-
const { env, ...auth } = dispatch.auth;
|
|
368
|
-
return {
|
|
369
|
-
auth: {
|
|
370
|
-
...auth,
|
|
371
|
-
...(dispatch.model.baseUrl === undefined ? {} : { baseUrl: dispatch.model.baseUrl }),
|
|
372
|
-
},
|
|
373
|
-
...(env === undefined ? {} : { env }),
|
|
374
|
-
};
|
|
375
|
-
},
|
|
376
|
-
},
|
|
377
|
-
},
|
|
378
|
-
getModels() { return [inheritedModel]; },
|
|
379
|
-
stream(model, context, request) {
|
|
380
|
-
return createRetriedStream(false, model, context, request as ProviderStreamOptions | undefined);
|
|
381
|
-
},
|
|
382
|
-
streamSimple(model, context, request) {
|
|
383
|
-
return createRetriedStream(true, model, context, request as ProviderStreamOptions | undefined);
|
|
384
|
-
},
|
|
385
|
-
}
|
|
386
|
-
: {
|
|
387
|
-
id: parentProvider!.id,
|
|
388
|
-
name: parentProvider!.name,
|
|
389
|
-
...(parentProvider!.baseUrl === undefined ? {} : { baseUrl: parentProvider!.baseUrl }),
|
|
390
|
-
...(parentProvider!.headers === undefined ? {} : { headers: parentProvider!.headers }),
|
|
391
|
-
auth: {
|
|
392
|
-
apiKey: {
|
|
393
|
-
name: `Inherited ${options.label} authentication`,
|
|
394
|
-
async resolve() {
|
|
395
|
-
return {
|
|
396
|
-
auth: {
|
|
397
|
-
...(dispatch.auth.apiKey === undefined ? {} : { apiKey: dispatch.auth.apiKey }),
|
|
398
|
-
...(dispatch.auth.headers === undefined ? {} : { headers: dispatch.auth.headers }),
|
|
399
|
-
...(dispatch.model.baseUrl === undefined ? {} : { baseUrl: dispatch.model.baseUrl }),
|
|
400
|
-
},
|
|
401
|
-
...(dispatch.auth.env === undefined ? {} : { env: dispatch.auth.env }),
|
|
402
|
-
};
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
getModels() { return [inheritedModel]; },
|
|
407
|
-
stream(model, childContext, streamOptions) {
|
|
408
|
-
return parentProvider!.stream(model, childContext, streamOptions);
|
|
409
|
-
},
|
|
410
|
-
streamSimple(model, childContext, streamOptions) {
|
|
411
|
-
return parentProvider!.streamSimple(model, childContext, streamOptions);
|
|
412
|
-
},
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
runtime.registerNativeProvider(provider);
|
|
416
|
-
// registerNativeProvider fires a background refresh; await it so child
|
|
417
|
-
// AgentSession.prompt sees configured auth without racing void refresh
|
|
418
|
-
// (mock timers / slow CI otherwise stall before the compliance stream).
|
|
419
|
-
await runtime.refresh({ allowNetwork: false });
|
|
420
|
-
return state;
|
|
155
|
+
if (cleanupFailure === undefined) return;
|
|
156
|
+
if (primaryFailure !== undefined) {
|
|
157
|
+
throw new AggregateError(
|
|
158
|
+
[primaryFailure, cleanupFailure],
|
|
159
|
+
`${label} execution and cleanup failed`,
|
|
160
|
+
{ cause: primaryFailure },
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
throw new AggregateError([cleanupFailure], `${label} cleanup failed`, {
|
|
164
|
+
cause: cleanupFailure,
|
|
165
|
+
});
|
|
421
166
|
}
|
|
422
167
|
|
|
423
168
|
type EvidenceChildFailureClassification = "provider" | "child" | "unknown";
|
|
@@ -542,6 +287,22 @@ function classifiedError(error: unknown, evidenceChildFailure: EvidenceChildFail
|
|
|
542
287
|
return Object.assign(wrapped, { evidenceChildFailure: classification });
|
|
543
288
|
}
|
|
544
289
|
|
|
290
|
+
/** Extract the first text diagnostic from a flattened toolResult error `details`. */
|
|
291
|
+
function extractToolResultText(details: unknown): string | undefined {
|
|
292
|
+
if (typeof details !== "object" || details === null) return undefined;
|
|
293
|
+
const record = details as Record<string, unknown>;
|
|
294
|
+
const content = record.content;
|
|
295
|
+
if (Array.isArray(content)) {
|
|
296
|
+
for (const part of content) {
|
|
297
|
+
if (typeof part === "object" && part !== null) {
|
|
298
|
+
const text = (part as Record<string, unknown>).text;
|
|
299
|
+
if (typeof text === "string" && text.trim() !== "") return text;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
545
306
|
function emptyUsage(): Usage {
|
|
546
307
|
return {
|
|
547
308
|
input: 0,
|
|
@@ -568,10 +329,14 @@ function addUsage(total: Usage, next: Usage): void {
|
|
|
568
329
|
|
|
569
330
|
// ── evidence child (reviewer legs) ────────────────────────────────────────
|
|
570
331
|
|
|
332
|
+
/** Evidence child execution options. */
|
|
571
333
|
export type EvidenceChildExecuteOptions = Readonly<{
|
|
572
334
|
signal?: AbortSignal;
|
|
573
335
|
/** Parent directory for credential/config scratch. Defaults to os.tmpdir(). */
|
|
574
336
|
credentialScratchParent?: string;
|
|
337
|
+
/** Run directory carrying the institutional resolution page (#518). Derives
|
|
338
|
+
* from context when absent. */
|
|
339
|
+
runDirectory?: string;
|
|
575
340
|
/**
|
|
576
341
|
* Package root for optional engine method-material resolution (#378).
|
|
577
342
|
* Required only when AK_ROLE_ENGINE is set and packaged notes should attach.
|
|
@@ -586,6 +351,11 @@ export async function executeEvidenceChild(
|
|
|
586
351
|
options: EvidenceChildExecuteOptions = {},
|
|
587
352
|
): Promise<{ report: string; usage: Usage; prompt: ReviewerPromptText }> {
|
|
588
353
|
const signal = options.signal;
|
|
354
|
+
const runDirectory = options.runDirectory ?? auditorRunDirectory(context);
|
|
355
|
+
if (runDirectory === undefined) {
|
|
356
|
+
throw new Error("Evidence child requires a run directory carrying the institutional resolution page");
|
|
357
|
+
}
|
|
358
|
+
const selection = await readInstitutionalSeatSelection(runDirectory, "evidenceChild");
|
|
589
359
|
return withInProcessScratch(
|
|
590
360
|
{
|
|
591
361
|
prefix: "ak-evidence-child-",
|
|
@@ -594,17 +364,8 @@ export async function executeEvidenceChild(
|
|
|
594
364
|
: { parentDirectory: options.credentialScratchParent }),
|
|
595
365
|
},
|
|
596
366
|
async (childConfigDir) => {
|
|
597
|
-
const {
|
|
367
|
+
const { openPiInstitutionalSession } = await import("./pi/in-process-session.ts");
|
|
598
368
|
const { createRecordSession } = await import("./archivist-record-entry.ts");
|
|
599
|
-
let inherited: InheritedRuntime;
|
|
600
|
-
try {
|
|
601
|
-
inherited = await createInheritedRuntime({
|
|
602
|
-
context,
|
|
603
|
-
label: "Evidence child",
|
|
604
|
-
});
|
|
605
|
-
} catch (error) {
|
|
606
|
-
throw classifiedError(error, "provider");
|
|
607
|
-
}
|
|
608
369
|
// #378: when labor engine is configured, legs get the same detour tool + material
|
|
609
370
|
// dual-path as the parent seat (ADR 0069 detour-rejoins-main-road).
|
|
610
371
|
const engineName = engineNameFromEnv();
|
|
@@ -632,37 +393,44 @@ export async function executeEvidenceChild(
|
|
|
632
393
|
},
|
|
633
394
|
});
|
|
634
395
|
// No tools allowlist — Pi defaults + unrestricted evidence surface (ADR 0064).
|
|
635
|
-
// Single
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
model: inherited.model,
|
|
640
|
-
thinkingLevel: context.thinkingLevel ?? "off",
|
|
641
|
-
modelRuntime: inherited.runtime,
|
|
642
|
-
systemPrompt: await buildEvidenceChildSystemPrompt(engineMaterial),
|
|
643
|
-
...(engineDetourTool === undefined
|
|
644
|
-
? {}
|
|
645
|
-
: { customTools: [engineDetourTool] }),
|
|
646
|
-
sessionManager: createRecordSession({
|
|
396
|
+
// Single open seam owner: pi/in-process-session.ts.
|
|
397
|
+
let opened: Awaited<ReturnType<typeof openPiInstitutionalSession>>;
|
|
398
|
+
try {
|
|
399
|
+
opened = await openPiInstitutionalSession({
|
|
647
400
|
cwd: workspace,
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
401
|
+
agentDir: childConfigDir,
|
|
402
|
+
selection,
|
|
403
|
+
systemPrompt: await buildEvidenceChildSystemPrompt(engineMaterial),
|
|
404
|
+
...(engineDetourTool === undefined
|
|
405
|
+
? {}
|
|
406
|
+
: { customTools: [engineDetourTool] }),
|
|
407
|
+
sessionManager: createRecordSession({
|
|
408
|
+
cwd: workspace,
|
|
409
|
+
kind: "evidence-children",
|
|
410
|
+
...(context.sessionManager === undefined ? {} : { parent: context.sessionManager }),
|
|
411
|
+
}),
|
|
412
|
+
...(signal === undefined ? {} : { signal }),
|
|
413
|
+
label: "Evidence child",
|
|
414
|
+
});
|
|
415
|
+
} catch (error) {
|
|
416
|
+
throw classifiedError(error, "provider");
|
|
417
|
+
}
|
|
418
|
+
const { handle } = opened;
|
|
652
419
|
const usage = emptyUsage();
|
|
653
|
-
const unsubscribe =
|
|
654
|
-
if (event.type === "message_end" && event.
|
|
655
|
-
addUsage(usage, event.
|
|
420
|
+
const unsubscribe = handle.subscribe((event) => {
|
|
421
|
+
if (event.type === "message_end" && event.role === "assistant") {
|
|
422
|
+
if (event.usage) addUsage(usage, event.usage);
|
|
656
423
|
}
|
|
657
424
|
});
|
|
658
|
-
const abortChild = () => {
|
|
425
|
+
const abortChild = () => { handle.abort(); };
|
|
659
426
|
if (signal?.aborted) abortChild();
|
|
660
427
|
else signal?.addEventListener("abort", abortChild, { once: true });
|
|
661
428
|
let primaryFailure: unknown;
|
|
662
429
|
try {
|
|
663
430
|
const delivered = prompt;
|
|
431
|
+
let turnResult: HostAssistantTurnResult;
|
|
664
432
|
try {
|
|
665
|
-
await
|
|
433
|
+
turnResult = await handle.prompt(delivered);
|
|
666
434
|
} catch (error) {
|
|
667
435
|
if (engineDetourFailure !== undefined) {
|
|
668
436
|
throw classifiedError(engineDetourFailure, "child");
|
|
@@ -673,30 +441,31 @@ export async function executeEvidenceChild(
|
|
|
673
441
|
throw classifiedError(engineDetourFailure, "child");
|
|
674
442
|
}
|
|
675
443
|
if (signal?.aborted) throw new Error("Evidence child was cancelled");
|
|
676
|
-
const lastAssistant =
|
|
677
|
-
.reverse()
|
|
678
|
-
|
|
444
|
+
const lastAssistant = turnResult.messages !== undefined
|
|
445
|
+
? [...turnResult.messages].reverse().find((message: any) => message?.role === "assistant") as AssistantMessage | undefined
|
|
446
|
+
: undefined;
|
|
679
447
|
// error|aborted assistant stops share the upstream-testimony rule: provider only
|
|
680
448
|
// with direct HTTP/SDK testimony, otherwise existing unknown. child is reserved
|
|
681
449
|
// for real local child/report failures (no assistant / blank report / cleanup).
|
|
682
450
|
if (
|
|
683
|
-
|
|
684
|
-
&& (lastAssistant.stopReason === "error" || lastAssistant.stopReason === "aborted")
|
|
451
|
+
turnResult.stopReason === "error" || turnResult.stopReason === "aborted"
|
|
452
|
+
|| (lastAssistant?.role === "assistant" && (lastAssistant.stopReason === "error" || lastAssistant.stopReason === "aborted"))
|
|
685
453
|
) {
|
|
454
|
+
const errMsg = turnResult.errorMessage ?? lastAssistant?.errorMessage ?? "";
|
|
686
455
|
throw classifiedError(
|
|
687
|
-
new Error(
|
|
688
|
-
projectStructuredRemote(lastAssistant).hasTestimony ? "provider" : "unknown",
|
|
456
|
+
new Error(errMsg, { cause: lastAssistant }),
|
|
457
|
+
lastAssistant && projectStructuredRemote(lastAssistant).hasTestimony ? "provider" : "unknown",
|
|
689
458
|
);
|
|
690
459
|
}
|
|
691
|
-
if (lastAssistant
|
|
460
|
+
if (lastAssistant !== undefined && lastAssistant.role !== "assistant") {
|
|
692
461
|
throw classifiedError(
|
|
693
462
|
new Error("Evidence child child terminated without a report", {
|
|
694
|
-
cause: lastAssistant ??
|
|
463
|
+
cause: lastAssistant ?? turnResult.messages,
|
|
695
464
|
}),
|
|
696
465
|
"child",
|
|
697
466
|
);
|
|
698
467
|
}
|
|
699
|
-
const report =
|
|
468
|
+
const report = turnResult.text;
|
|
700
469
|
if (report.trim().length === 0) {
|
|
701
470
|
throw new Error("Evidence child returned a blank child report");
|
|
702
471
|
}
|
|
@@ -706,30 +475,7 @@ export async function executeEvidenceChild(
|
|
|
706
475
|
throw primaryFailure;
|
|
707
476
|
} finally {
|
|
708
477
|
signal?.removeEventListener("abort", abortChild);
|
|
709
|
-
|
|
710
|
-
for (const cleanup of [() => unsubscribe(), () => dispose()]) {
|
|
711
|
-
try {
|
|
712
|
-
cleanup();
|
|
713
|
-
} catch (failure) {
|
|
714
|
-
cleanupFailure = cleanupFailure === undefined
|
|
715
|
-
? failure
|
|
716
|
-
: new AggregateError([cleanupFailure, failure], "Reviewer child cleanup failed", {
|
|
717
|
-
cause: cleanupFailure,
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
if (cleanupFailure !== undefined) {
|
|
722
|
-
if (primaryFailure !== undefined) {
|
|
723
|
-
throw new AggregateError(
|
|
724
|
-
[primaryFailure, cleanupFailure],
|
|
725
|
-
"Reviewer child execution and cleanup failed",
|
|
726
|
-
{ cause: primaryFailure },
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
throw new AggregateError([cleanupFailure], "Reviewer child cleanup failed", {
|
|
730
|
-
cause: cleanupFailure,
|
|
731
|
-
});
|
|
732
|
-
}
|
|
478
|
+
await runChildCleanup([() => unsubscribe(), () => handle.close()], primaryFailure, "Reviewer child");
|
|
733
479
|
}
|
|
734
480
|
},
|
|
735
481
|
);
|
|
@@ -743,45 +489,25 @@ export type AuditorRoleOptions = {
|
|
|
743
489
|
tool: AuditorDecisionTool;
|
|
744
490
|
dossierTool: AuditorDecisionTool;
|
|
745
491
|
roleLabel: string;
|
|
746
|
-
context: ExtensionContext;
|
|
492
|
+
context: ExtensionContext | HostContext;
|
|
747
493
|
signal?: AbortSignal;
|
|
748
|
-
runCompletion?: AuditorCompletion;
|
|
749
494
|
retainResponse?(response: AssistantMessage): void;
|
|
750
|
-
/**
|
|
751
|
-
*
|
|
752
|
-
* resolve, and loud auth/availability failure (#453 / ADR 0018).
|
|
753
|
-
*/
|
|
495
|
+
/** Province seat identity only — shared executor owns seat resolution, and
|
|
496
|
+
* loud auth/availability failure (#453 / ADR 0018). */
|
|
754
497
|
gateSeat?: GateOfficerSeat;
|
|
498
|
+
/** Run directory carrying the institutional resolution page (#518). Derives
|
|
499
|
+
* from context when absent. */
|
|
500
|
+
runDirectory?: string;
|
|
755
501
|
};
|
|
756
502
|
|
|
757
|
-
/**
|
|
758
|
-
*
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
*/
|
|
762
|
-
async function resolveGateSeatModelOptions(
|
|
763
|
-
context: ExtensionContext,
|
|
764
|
-
seat: GateOfficerSeat,
|
|
765
|
-
roleLabel: string,
|
|
766
|
-
): Promise<{ model?: Model<Api>; thinkingLevel?: PublicThinkingLevel }> {
|
|
767
|
-
const selection = resolveGateOfficerModelSelection(await loadPublicCliConfig(), seat);
|
|
768
|
-
if (selection === undefined) return {};
|
|
769
|
-
const find = context.modelRegistry.find?.bind(context.modelRegistry);
|
|
770
|
-
if (typeof find !== "function") {
|
|
771
|
-
throw new Error(`${roleLabel} model registry cannot resolve models`);
|
|
772
|
-
}
|
|
773
|
-
const model = find(selection.provider, selection.model);
|
|
774
|
-
if (model === undefined) {
|
|
775
|
-
throw new Error(`${roleLabel} model is unavailable: ${formatModelSpec(selection)}`);
|
|
776
|
-
}
|
|
777
|
-
return {
|
|
778
|
-
model,
|
|
779
|
-
...(selection.thinking === undefined ? {} : { thinkingLevel: selection.thinking }),
|
|
780
|
-
};
|
|
503
|
+
/** Resolution-page seat key for an auditor invocation: province gate seats map
|
|
504
|
+
* to their own page seats; doctor/judge compliance audits use the auditor seat. */
|
|
505
|
+
function auditorSeatKey(gateSeat: GateOfficerSeat | undefined): string {
|
|
506
|
+
return gateSeat ?? "auditor";
|
|
781
507
|
}
|
|
782
508
|
|
|
783
509
|
/**
|
|
784
|
-
* Auditor lifecycle via the shared
|
|
510
|
+
* Auditor lifecycle via the shared institutional sub-session adapter.
|
|
785
511
|
* Adapter keeps role label / soul / decision tool / result projection only.
|
|
786
512
|
* No tools allowlist (ADR 0064). Provider-stream idle-only retry (ADR 0059).
|
|
787
513
|
* Durable child session via ADR 0065 archivist entry.
|
|
@@ -790,27 +516,14 @@ export async function executeAuditorChild(
|
|
|
790
516
|
options: AuditorRoleOptions,
|
|
791
517
|
): Promise<{ decision: unknown; response: AssistantMessage; noReceiptLifecycle?: NoReceiptLifecycleFacts }> {
|
|
792
518
|
const { createRecordSession } = await import("./archivist-record-entry.ts");
|
|
519
|
+
const runDirectory = options.runDirectory ?? auditorRunDirectory(options.context);
|
|
520
|
+
if (runDirectory === undefined) {
|
|
521
|
+
throw new Error(`${options.roleLabel} requires a run directory carrying the institutional resolution page`);
|
|
522
|
+
}
|
|
523
|
+
const seat = auditorSeatKey(options.gateSeat);
|
|
524
|
+
const selection = await readInstitutionalSeatSelection(runDirectory, seat);
|
|
793
525
|
|
|
794
526
|
return withInProcessScratch({ prefix: "ak-auditor-role-" }, async (scratch) => {
|
|
795
|
-
const gate =
|
|
796
|
-
options.gateSeat === undefined
|
|
797
|
-
? {}
|
|
798
|
-
: await resolveGateSeatModelOptions(
|
|
799
|
-
options.context,
|
|
800
|
-
options.gateSeat,
|
|
801
|
-
options.roleLabel,
|
|
802
|
-
);
|
|
803
|
-
const inherited = await createInheritedRuntime({
|
|
804
|
-
context: options.context,
|
|
805
|
-
label: options.roleLabel,
|
|
806
|
-
idleRetry: true,
|
|
807
|
-
...(gate.model === undefined ? {} : { model: gate.model }),
|
|
808
|
-
...(options.runCompletion === undefined
|
|
809
|
-
? {}
|
|
810
|
-
: { runCompletion: options.runCompletion, injectedSystemPrompt: options.systemPrompt }),
|
|
811
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
812
|
-
});
|
|
813
|
-
|
|
814
527
|
const cwd = options.context.cwd ?? process.cwd();
|
|
815
528
|
|
|
816
529
|
let decision: unknown;
|
|
@@ -833,12 +546,15 @@ export async function executeAuditorChild(
|
|
|
833
546
|
try {
|
|
834
547
|
const result = await options.tool.execute(...args);
|
|
835
548
|
delivery.recordAccepted();
|
|
836
|
-
|
|
549
|
+
const rawDecision = args[1];
|
|
550
|
+
const isMissingArgs = rawDecision === undefined
|
|
551
|
+
|| (typeof rawDecision === "object" && rawDecision !== null && !Array.isArray(rawDecision) && Object.keys(rawDecision).length === 0);
|
|
552
|
+
decision = isMissingArgs ? undefined : rawDecision;
|
|
837
553
|
decisionCallId = args[0];
|
|
838
554
|
decisionToolFailure = undefined;
|
|
839
555
|
decisionToolFailures.delete(args[0]);
|
|
840
556
|
decisionSubmitted = true;
|
|
841
|
-
return result;
|
|
557
|
+
return { ...result, terminate: true };
|
|
842
558
|
} catch (error) {
|
|
843
559
|
decisionToolFailure = error;
|
|
844
560
|
decisionToolFailures.set(args[0], error);
|
|
@@ -857,18 +573,34 @@ export async function executeAuditorChild(
|
|
|
857
573
|
...(parentSessionManager === undefined ? {} : { parent: parentSessionManager }),
|
|
858
574
|
});
|
|
859
575
|
|
|
860
|
-
// Shared session open — no tools allowlist (ADR 0064).
|
|
861
|
-
|
|
862
|
-
const {
|
|
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({
|
|
863
593
|
cwd,
|
|
864
594
|
agentDir: scratch,
|
|
865
|
-
|
|
866
|
-
thinkingLevel: gate.thinkingLevel ?? options.context.thinkingLevel ?? "off",
|
|
867
|
-
modelRuntime: inherited.runtime,
|
|
595
|
+
selection,
|
|
868
596
|
systemPrompt: options.systemPrompt,
|
|
869
|
-
customTools: [
|
|
597
|
+
customTools: [wrappedDossierTool, tool],
|
|
870
598
|
sessionManager: auditorSessionManager,
|
|
599
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
600
|
+
idleRetry: true,
|
|
601
|
+
label: options.roleLabel,
|
|
871
602
|
});
|
|
603
|
+
const { handle } = opened;
|
|
872
604
|
|
|
873
605
|
const binding: AuditorParentAttemptBinding = {
|
|
874
606
|
version: 1,
|
|
@@ -883,6 +615,16 @@ export async function executeAuditorChild(
|
|
|
883
615
|
// Durable binding is a prerequisite: never observe the provider when its
|
|
884
616
|
// response could not later be tied to the current parent attempt.
|
|
885
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 {}
|
|
886
628
|
|
|
887
629
|
let turns = 0;
|
|
888
630
|
const sessionUsage = emptyUsage();
|
|
@@ -892,31 +634,13 @@ export async function executeAuditorChild(
|
|
|
892
634
|
let rejectedDecisionResponse: AssistantMessage | undefined;
|
|
893
635
|
let promptNeighboringFailure: unknown;
|
|
894
636
|
let promptDecisionFailures: unknown[] = [];
|
|
895
|
-
const registeredToolNames = new Set(session.getAllTools().map((entry) => entry.name));
|
|
896
|
-
const evidenceToolFailures = new Map<string, unknown>();
|
|
897
|
-
for (const name of registeredToolNames) {
|
|
898
|
-
if (name === tool.name) continue;
|
|
899
|
-
const definition = session.getToolDefinition(name);
|
|
900
|
-
if (definition === undefined) continue;
|
|
901
|
-
const execute = definition.execute.bind(definition);
|
|
902
|
-
definition.execute = async (...args: any[]) => {
|
|
903
|
-
try {
|
|
904
|
-
return await (execute as (...executeArgs: any[]) => Promise<any>)(...args);
|
|
905
|
-
} catch (error) {
|
|
906
|
-
evidenceToolFailures.set(args[0], error);
|
|
907
|
-
throw error;
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
637
|
const findToolFailure = (response: AssistantMessage): unknown => {
|
|
912
638
|
const callIds = response.content.flatMap((part) =>
|
|
913
|
-
part.type === "toolCall" && part.name !== tool.name
|
|
639
|
+
part.type === "toolCall" && part.name !== tool.name ? [part.id] : []);
|
|
914
640
|
for (const callId of callIds) {
|
|
915
641
|
if (evidenceToolFailures.has(callId)) return evidenceToolFailures.get(callId);
|
|
916
642
|
}
|
|
917
|
-
|
|
918
|
-
return [...session.messages].reverse().find((message) =>
|
|
919
|
-
message.role === "toolResult" && callIdSet.has(message.toolCallId) && message.isError);
|
|
643
|
+
return undefined;
|
|
920
644
|
};
|
|
921
645
|
const drainRejectedDecisionFailures = (response: AssistantMessage) => {
|
|
922
646
|
for (const part of response.content) {
|
|
@@ -926,29 +650,74 @@ export async function executeAuditorChild(
|
|
|
926
650
|
decisionToolFailures.delete(part.id);
|
|
927
651
|
}
|
|
928
652
|
};
|
|
929
|
-
const
|
|
930
|
-
|
|
653
|
+
const retainedAssistants: AssistantMessage[] = [];
|
|
654
|
+
const unsubscribe = handle.subscribe((event) => {
|
|
655
|
+
// Capture evidence-tool (non-decision) failures from the handle's real
|
|
656
|
+
// tool_result events. The old runCompletion path wrapped every child tool's
|
|
657
|
+
// execute to observe failures; through the real handle the adapter forwards
|
|
658
|
+
// tool_execution_end isError. Record the errored tool call id so an adjacent
|
|
659
|
+
// evidence failure can outrank a settled/correctable decision feedback.
|
|
660
|
+
if (event.type === "tool_result" && event.isError === true && event.toolName !== tool.name) {
|
|
661
|
+
// Through the real handle the child flattens a throwing evidence tool
|
|
662
|
+
// into a toolResult error whose content text carries the diagnostic
|
|
663
|
+
// (e.g. "ENOENT: no such file or directory..."). Recover that text so
|
|
664
|
+
// the adjacent evidence failure outranks a settled decision feedback
|
|
665
|
+
// with its original diagnostic, mirroring the pre-migration wrapped
|
|
666
|
+
// execute capture (which held the raw error object).
|
|
667
|
+
const detailText = extractToolResultText(event.details);
|
|
668
|
+
// A native unknown-tool receipt ("Tool <name> not found") is not an
|
|
669
|
+
// evidence-tool failure: the child session has no such tool registered,
|
|
670
|
+
// so its errored toolResult must not short-circuit the auditor (it keeps
|
|
671
|
+
// prompting to the turn limit, mirroring the pre-migration
|
|
672
|
+
// registeredToolNames exclusion in findToolFailure).
|
|
673
|
+
if (detailText !== undefined && /^Tool\s+.+ not found$/.test(detailText.trim())) return;
|
|
674
|
+
const failure = detailText === undefined
|
|
675
|
+
? new Error(event.toolName ?? "evidence tool failed")
|
|
676
|
+
: new Error(detailText);
|
|
677
|
+
// Recover the errno code from the flattened diagnostic so an evidence
|
|
678
|
+
// failure's identity (e.g. code "ENOENT") is preserved across the real
|
|
679
|
+
// HTTP round-trip, mirroring the pre-migration raw error object.
|
|
680
|
+
const errno = /^([A-Z_]+):/.exec(detailText ?? "");
|
|
681
|
+
if (errno !== null && errno[1] !== undefined) (failure as NodeJS.ErrnoException).code = errno[1];
|
|
682
|
+
evidenceToolFailures.set(event.toolCallId, failure);
|
|
683
|
+
}
|
|
684
|
+
if (event.type === "message_end" && event.role === "assistant" && boundaryResponse === undefined) {
|
|
931
685
|
turns += 1;
|
|
932
|
-
addUsage(sessionUsage, event.
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
686
|
+
if (event.usage) addUsage(sessionUsage, event.usage);
|
|
687
|
+
const msg = event.message as AssistantMessage | undefined;
|
|
688
|
+
retainedResponse = msg;
|
|
689
|
+
if (msg) {
|
|
690
|
+
retainedAssistants.push(msg);
|
|
691
|
+
try { options.retainResponse?.(msg); } catch (error) { retentionFailure = error; }
|
|
692
|
+
// A tool call in assistant output is only an observation. Preserve its
|
|
693
|
+
// candidate for typed malformed-decision settlement, but the wrapped
|
|
694
|
+
// execute path above is the sole owner of accepted-receipt state; a
|
|
695
|
+
// rejected execution must remain retryable in this same session.
|
|
696
|
+
for (const part of msg.content) {
|
|
697
|
+
if (part.type === "toolCall" && part.name === tool.name) {
|
|
698
|
+
rejectedDecisionResponse = msg;
|
|
699
|
+
if (decision === undefined) {
|
|
700
|
+
decision = (part.arguments === undefined
|
|
701
|
+
|| (typeof part.arguments === "object" && part.arguments !== null
|
|
702
|
+
&& !Array.isArray(part.arguments) && Object.keys(part.arguments).length === 0))
|
|
703
|
+
? undefined
|
|
704
|
+
: part.arguments;
|
|
705
|
+
decisionCallId = part.id;
|
|
706
|
+
// Pi can reject malformed root arguments before invoking execute;
|
|
707
|
+
// that remains the existing unreadable-candidate failure path.
|
|
708
|
+
// A missing root argument reaches the real provider adapter as an
|
|
709
|
+
// empty object after serialization — treat it as missing too so a
|
|
710
|
+
// one-shot typed missing-args settlement does not solicit another turn.
|
|
711
|
+
if (part.arguments === undefined
|
|
712
|
+
|| (typeof part.arguments === "object" && part.arguments !== null
|
|
713
|
+
&& !Array.isArray(part.arguments) && Object.keys(part.arguments).length === 0)) {
|
|
714
|
+
decisionSubmitted = true;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
948
717
|
}
|
|
949
718
|
}
|
|
719
|
+
if (turns >= AUDITOR_TURN_LIMIT || msg.stopReason === "error") boundaryResponse = msg;
|
|
950
720
|
}
|
|
951
|
-
if (turns >= AUDITOR_TURN_LIMIT) boundaryResponse = event.message;
|
|
952
721
|
}
|
|
953
722
|
if (event.type === "turn_end") {
|
|
954
723
|
if (rejectedDecisionResponse !== undefined) {
|
|
@@ -958,14 +727,15 @@ export async function executeAuditorChild(
|
|
|
958
727
|
if (decisionSubmitted || promptNeighboringFailure !== undefined
|
|
959
728
|
|| (boundaryResponse !== undefined && rejectedDecisionResponse === undefined)
|
|
960
729
|
|| retentionFailure !== undefined) {
|
|
961
|
-
|
|
730
|
+
handle.abort();
|
|
962
731
|
}
|
|
963
732
|
}
|
|
964
733
|
});
|
|
965
|
-
const abort = () => {
|
|
734
|
+
const abort = () => { handle.abort(); };
|
|
966
735
|
if (options.signal?.aborted) abort();
|
|
967
736
|
else options.signal?.addEventListener("abort", abort, { once: true });
|
|
968
737
|
|
|
738
|
+
let auditorFailure: unknown;
|
|
969
739
|
try {
|
|
970
740
|
try {
|
|
971
741
|
const promptAllowingRejectedDecision = async (prompt: string) => {
|
|
@@ -975,7 +745,7 @@ export async function executeAuditorChild(
|
|
|
975
745
|
promptDecisionFailures = [];
|
|
976
746
|
let promptFailure: unknown;
|
|
977
747
|
try {
|
|
978
|
-
await
|
|
748
|
+
await handle.prompt(prompt);
|
|
979
749
|
} catch (error) {
|
|
980
750
|
promptFailure = error;
|
|
981
751
|
}
|
|
@@ -997,6 +767,8 @@ export async function executeAuditorChild(
|
|
|
997
767
|
return;
|
|
998
768
|
}
|
|
999
769
|
if (decisionToolFailure !== undefined) return;
|
|
770
|
+
if (retentionFailure !== undefined) return;
|
|
771
|
+
if (opened.streamFailure !== undefined) throw opened.streamFailure;
|
|
1000
772
|
if (promptFailure !== undefined) throw promptFailure;
|
|
1001
773
|
};
|
|
1002
774
|
const chargeAndClearRejectedDecisionFailures = (failures: unknown[]) => {
|
|
@@ -1007,8 +779,8 @@ export async function executeAuditorChild(
|
|
|
1007
779
|
promptDecisionFailures = [];
|
|
1008
780
|
};
|
|
1009
781
|
await promptAllowingRejectedDecision(options.prompt);
|
|
1010
|
-
while (!decisionSubmitted && (boundaryResponse === undefined || decisionToolFailure !== undefined)
|
|
1011
|
-
&&
|
|
782
|
+
while (!decisionSubmitted && retentionFailure === undefined && (boundaryResponse === undefined || decisionToolFailure !== undefined)
|
|
783
|
+
&& opened.streamFailure === undefined && delivery.nextAction() === "request-delivery") {
|
|
1012
784
|
if (decisionToolFailure !== undefined) {
|
|
1013
785
|
const failures = promptDecisionFailures.length === 0
|
|
1014
786
|
? [decisionToolFailure]
|
|
@@ -1028,7 +800,7 @@ export async function executeAuditorChild(
|
|
|
1028
800
|
await promptAllowingRejectedDecision(RECEIPT_DELIVERY_PROMPT);
|
|
1029
801
|
}
|
|
1030
802
|
}
|
|
1031
|
-
if (!decisionSubmitted &&
|
|
803
|
+
if (!decisionSubmitted && retentionFailure === undefined && opened.streamFailure === undefined
|
|
1032
804
|
&& delivery.nextAction() === "no-receipt") {
|
|
1033
805
|
const runPointer = options.context.sessionManager.getSessionFile() ?? options.context.cwd ?? process.cwd();
|
|
1034
806
|
const attemptPointer = binding.parent.attemptEntryId ?? binding.parent.sessionId ?? `current:${runPointer}`;
|
|
@@ -1038,28 +810,44 @@ export async function executeAuditorChild(
|
|
|
1038
810
|
// charged this prompt to the exhausted shared budget.
|
|
1039
811
|
decisionToolFailure = undefined;
|
|
1040
812
|
auditorSessionManager.appendCustomEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
|
|
813
|
+
try {
|
|
814
|
+
sitianReport({
|
|
815
|
+
level: "event",
|
|
816
|
+
kind: "auditor",
|
|
817
|
+
cwd,
|
|
818
|
+
sessionParent: parentSessionFile,
|
|
819
|
+
payload: { type: NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, ...facts },
|
|
820
|
+
source: "evidence-child-executor",
|
|
821
|
+
});
|
|
822
|
+
} catch {}
|
|
1041
823
|
// Provenance is granted only after the lifecycle owner persisted the
|
|
1042
824
|
// current child record; accepted model arguments can never set it.
|
|
1043
825
|
noReceiptLifecycle = facts;
|
|
1044
826
|
}
|
|
1045
827
|
} catch (error) {
|
|
1046
828
|
if (options.signal?.aborted) throw options.signal.reason;
|
|
1047
|
-
if (
|
|
1048
|
-
throw error;
|
|
829
|
+
if (retentionFailure === undefined && opened.streamFailure !== undefined) throw opened.streamFailure;
|
|
830
|
+
if (retentionFailure === undefined) throw error;
|
|
1049
831
|
}
|
|
1050
832
|
if (options.signal?.aborted) throw options.signal.reason;
|
|
1051
|
-
if (
|
|
833
|
+
if (retentionFailure === undefined && opened.streamFailure !== undefined) throw opened.streamFailure;
|
|
1052
834
|
if (!decisionSubmitted && decisionToolFailure !== undefined) throw decisionToolFailure;
|
|
1053
835
|
const relevantResponse = !decisionSubmitted
|
|
1054
836
|
? boundaryResponse
|
|
1055
|
-
:
|
|
1056
|
-
|
|
837
|
+
: (retainedResponse && retainedResponse.role === "assistant" && retainedResponse.content.some((part) => part.type === "toolCall" && part.name === tool.name)
|
|
838
|
+
? retainedResponse
|
|
839
|
+
: undefined);
|
|
1057
840
|
if (relevantResponse !== undefined) {
|
|
1058
841
|
const toolFailure = findToolFailure(relevantResponse);
|
|
1059
842
|
if (toolFailure !== undefined) throw toolFailure;
|
|
1060
843
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
844
|
+
const assistants = [...retainedAssistants].reverse();
|
|
845
|
+
const response = !decisionSubmitted
|
|
846
|
+
? assistants[0]
|
|
847
|
+
: assistants.find((message) =>
|
|
848
|
+
message.content.some((part) => part.type === "toolCall" && part.name === tool.name));
|
|
849
|
+
|
|
850
|
+
if (boundaryResponse !== undefined && boundaryResponse.stopReason !== "error" && !decisionSubmitted && noReceiptLifecycle === undefined) {
|
|
1063
851
|
const toolNames = boundaryResponse.content.flatMap((part) => part.type === "toolCall" ? [part.name] : []);
|
|
1064
852
|
throw new AuditorTurnLimitError(AUDITOR_TURN_LIMIT, turns, {
|
|
1065
853
|
stopReason: boundaryResponse.stopReason,
|
|
@@ -1067,18 +855,10 @@ export async function executeAuditorChild(
|
|
|
1067
855
|
});
|
|
1068
856
|
}
|
|
1069
857
|
|
|
1070
|
-
const assistants = [...session.messages]
|
|
1071
|
-
.reverse()
|
|
1072
|
-
.filter((message): message is AssistantMessage => message.role === "assistant");
|
|
1073
|
-
const response = !decisionSubmitted
|
|
1074
|
-
? assistants[0]
|
|
1075
|
-
: assistants.find((message) =>
|
|
1076
|
-
message.content.some((part) => part.type === "toolCall" && part.name === tool.name));
|
|
1077
|
-
|
|
1078
858
|
if (response !== undefined) {
|
|
1079
859
|
try {
|
|
860
|
+
if (retentionFailure !== undefined) throw retentionFailure;
|
|
1080
861
|
if (retainedResponse === undefined) options.retainResponse?.(response);
|
|
1081
|
-
else if (retentionFailure !== undefined) throw retentionFailure;
|
|
1082
862
|
} catch (retentionFailure) {
|
|
1083
863
|
if (response.stopReason !== "error") throw retentionFailure;
|
|
1084
864
|
// Do not trim/rewrite the held errorMessage bytes.
|
|
@@ -1133,8 +913,8 @@ export async function executeAuditorChild(
|
|
|
1133
913
|
}),
|
|
1134
914
|
},
|
|
1135
915
|
};
|
|
1136
|
-
|
|
1137
|
-
version: 1,
|
|
916
|
+
const failureData = {
|
|
917
|
+
version: 1 as const,
|
|
1138
918
|
parent: binding.parent,
|
|
1139
919
|
failure: {
|
|
1140
920
|
cause: failure.knownCause,
|
|
@@ -1142,7 +922,18 @@ export async function executeAuditorChild(
|
|
|
1142
922
|
...(failure.message === "" ? {} : { diagnostic: failure.message }),
|
|
1143
923
|
details: failure.details,
|
|
1144
924
|
},
|
|
1145
|
-
}
|
|
925
|
+
};
|
|
926
|
+
auditorSessionManager.appendCustomEntry(AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, failureData);
|
|
927
|
+
try {
|
|
928
|
+
sitianReport({
|
|
929
|
+
level: "event",
|
|
930
|
+
kind: "auditor",
|
|
931
|
+
cwd,
|
|
932
|
+
sessionParent: parentSessionFile,
|
|
933
|
+
payload: { type: AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, ...failureData },
|
|
934
|
+
source: "evidence-child-executor",
|
|
935
|
+
});
|
|
936
|
+
} catch {}
|
|
1146
937
|
throw failure;
|
|
1147
938
|
}
|
|
1148
939
|
}
|
|
@@ -1150,20 +941,21 @@ export async function executeAuditorChild(
|
|
|
1150
941
|
if (
|
|
1151
942
|
response === undefined
|
|
1152
943
|
|| response.stopReason === "error"
|
|
1153
|
-
|| response.stopReason === "aborted"
|
|
1154
|
-
|| (!decisionSubmitted && decision === undefined)
|
|
944
|
+
|| (!decisionSubmitted && (response.stopReason === "aborted" || decision === undefined))
|
|
1155
945
|
) {
|
|
1156
|
-
throw new Error(`${options.roleLabel} exited without a readable decision receipt`);
|
|
946
|
+
throw new Error(response?.errorMessage ?? `${options.roleLabel} exited without a readable decision receipt`);
|
|
1157
947
|
}
|
|
1158
948
|
return {
|
|
1159
949
|
decision,
|
|
1160
950
|
response: { ...response, usage: sessionUsage },
|
|
1161
951
|
...(noReceiptLifecycle === undefined ? {} : { noReceiptLifecycle }),
|
|
1162
952
|
};
|
|
953
|
+
} catch (error) {
|
|
954
|
+
auditorFailure = error;
|
|
955
|
+
throw error;
|
|
1163
956
|
} finally {
|
|
1164
957
|
options.signal?.removeEventListener("abort", abort);
|
|
1165
|
-
unsubscribe();
|
|
1166
|
-
dispose();
|
|
958
|
+
await runChildCleanup([() => unsubscribe(), () => handle.close()], auditorFailure, options.roleLabel);
|
|
1167
959
|
}
|
|
1168
960
|
});
|
|
1169
961
|
}
|