@akagilnc/pi-workflow-roles 0.1.1751
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/LICENSE +202 -0
- package/README.md +104 -0
- package/README.zh-CN.md +133 -0
- package/THIRD_PARTY_NOTICES.md +60 -0
- package/dist/activation-ledger-git.js +68 -0
- package/dist/activation-ledger-session.js +120 -0
- package/dist/activation-ledger-topology.js +239 -0
- package/dist/activation-reconciliation.js +61 -0
- package/dist/audit-escalation.js +108 -0
- package/dist/auditor-dossier-tool.js +35 -0
- package/dist/canonical-json.js +78 -0
- package/dist/compliance-transport.js +77 -0
- package/dist/doctor-contracts.js +172 -0
- package/dist/dossier-resolution.js +103 -0
- package/dist/evidence-child-executor.js +661 -0
- package/dist/exact-utf8.js +12 -0
- package/dist/git-object-id.js +7 -0
- package/dist/in-process-session.js +50 -0
- package/dist/merger-contracts.js +76 -0
- package/dist/navigator-attendance.js +995 -0
- package/dist/navigator-invocation-identity.js +220 -0
- package/dist/open-tool-schema.js +39 -0
- package/dist/package-contracts/collector-output.js +50 -0
- package/dist/package-contracts/fixer-output.js +72 -0
- package/dist/package-contracts/fixer-packet.js +77 -0
- package/dist/package-contracts/judge-output.js +17 -0
- package/dist/package-contracts/reviewer-output.js +82 -0
- package/dist/package-contracts/terminating-tools.js +173 -0
- package/dist/package-contracts/worker-output.js +13 -0
- package/dist/package-owned-tool-idle.js +104 -0
- package/dist/packaged-role-registry.js +34 -0
- package/dist/public-cli/main.js +23867 -0
- package/dist/public-command-renderer.js +20 -0
- package/dist/reviewer-agent.js +93 -0
- package/dist/reviewer-child-executor.js +23 -0
- package/dist/reviewer-construction.js +95 -0
- package/dist/reviewer-dispatch.js +77 -0
- package/dist/reviewer-execution-ledger.js +160 -0
- package/dist/reviewer-failure-diagnostic.js +17 -0
- package/dist/reviewer-git-snapshot.js +38 -0
- package/dist/reviewer-pinned-git.js +146 -0
- package/dist/reviewer-preflight-error.js +15 -0
- package/dist/reviewer-prompt-identity.js +10 -0
- package/dist/reviewer-scope-prompt.js +21 -0
- package/dist/reviewer-workspace.js +151 -0
- package/dist/sha256.js +5 -0
- package/dist/sitian-record-entry.js +33 -0
- package/dist/stderr-jsonl.js +26 -0
- package/dist/stream-idle-guard.js +75 -0
- package/dist/tool-execution-observation.js +141 -0
- package/dist/uuidv7.js +21 -0
- package/dist/work-subject-identity.js +53 -0
- package/extensions/role-runtime.ts +303 -0
- package/package.json +69 -0
- package/packets/fixer-prerequisites.json +6 -0
- package/packets/fixer-repair.md +5 -0
- package/packets/judge-apply.md +77 -0
- package/packets/judge-authority.md +64 -0
- package/packets/judge-plan.md +55 -0
- package/packets/judge-review.md +49 -0
- package/packets/judge-submission.md +34 -0
- package/resources/methods/code-review/SKILL.md +92 -0
- package/resources/methods/code-review/agents/openai.yaml +3 -0
- package/resources/methods/code-review/provenance.json +26 -0
- package/resources/methods/diagnosing-bugs/SKILL.md +134 -0
- package/resources/methods/diagnosing-bugs/agents/openai.yaml +3 -0
- package/resources/methods/diagnosing-bugs/provenance.json +31 -0
- package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/resources/methods/resolving-merge-conflicts/SKILL.md +14 -0
- package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/resources/methods/resolving-merge-conflicts/provenance.json +26 -0
- package/resources/methods/tdd/SKILL.md +38 -0
- package/resources/methods/tdd/agents/openai.yaml +3 -0
- package/resources/methods/tdd/mocking.md +59 -0
- package/resources/methods/tdd/provenance.json +36 -0
- package/resources/methods/tdd/tests.md +77 -0
- package/resources/navigator-route-playbook.md +32 -0
- package/schemas/tool-execution-observation.schema.json +107 -0
- package/scripts/build-package.mjs +65 -0
- package/scripts/generate-tool-execution-observation-schema.ts +7 -0
- package/souls/coder.md +10 -0
- package/souls/collector.md +11 -0
- package/souls/doctor-auditor.md +23 -0
- package/souls/doctor.md +8 -0
- package/souls/fixer-auditor.md +33 -0
- package/souls/fixer.md +13 -0
- package/souls/judge-auditor.md +33 -0
- package/souls/judge.md +74 -0
- package/souls/merger.md +5 -0
- package/souls/navigator.md +5 -0
- package/souls/reviewer-auditor.md +25 -0
- package/souls/reviewer.md +11 -0
- package/src/activation-ledger-git.ts +96 -0
- package/src/activation-ledger-session.ts +188 -0
- package/src/activation-ledger-topology.ts +301 -0
- package/src/activation-ledger.ts +240 -0
- package/src/activation-reconciliation.ts +163 -0
- package/src/activation-trace.ts +38 -0
- package/src/audit-escalation.ts +177 -0
- package/src/auditor-dossier-tool.ts +48 -0
- package/src/auditor-soul.ts +28 -0
- package/src/canonical-json.ts +74 -0
- package/src/canonical-skill-binding.ts +107 -0
- package/src/collector-config.ts +89 -0
- package/src/collector-evidence.ts +461 -0
- package/src/collector-github.ts +656 -0
- package/src/collector-identity.ts +161 -0
- package/src/collector-ledger.ts +827 -0
- package/src/collector-receipt.ts +87 -0
- package/src/collector-role.ts +592 -0
- package/src/collector-tool-schemas.ts +19 -0
- package/src/compliance-transport.ts +130 -0
- package/src/doctor-auditor.ts +53 -0
- package/src/doctor-contracts.ts +166 -0
- package/src/doctor-evidence.ts +47 -0
- package/src/doctor-role.ts +18 -0
- package/src/dossier-resolution.ts +137 -0
- package/src/evidence-child-executor.ts +775 -0
- package/src/exact-utf8.ts +9 -0
- package/src/factory-board.ts +1822 -0
- package/src/git-object-id.ts +11 -0
- package/src/human-format.ts +65 -0
- package/src/in-process-session.ts +78 -0
- package/src/judge-auditor.ts +55 -0
- package/src/judge-recording-anti-forge.ts +53 -0
- package/src/judge-role.ts +160 -0
- package/src/merger-contracts.ts +71 -0
- package/src/merger-git-state.ts +76 -0
- package/src/merger-role.ts +60 -0
- package/src/navigator-attendance.ts +1254 -0
- package/src/navigator-invocation-identity.ts +446 -0
- package/src/open-tool-schema.ts +46 -0
- package/src/package-contracts/collector-output.ts +109 -0
- package/src/package-contracts/fixer-output.ts +81 -0
- package/src/package-contracts/fixer-packet.ts +93 -0
- package/src/package-contracts/judge-output.ts +39 -0
- package/src/package-contracts/reviewer-output.ts +115 -0
- package/src/package-contracts/terminating-tools.ts +259 -0
- package/src/package-contracts/worker-output.ts +36 -0
- package/src/package-owned-tool-idle.ts +134 -0
- package/src/package-resources/method-skill-binding.ts +87 -0
- package/src/package-resources/method-skill.ts +358 -0
- package/src/packaged-role-registry.ts +36 -0
- package/src/public-cli/cli-errors.ts +11 -0
- package/src/public-cli/cli-io.ts +4 -0
- package/src/public-cli/cli.ts +912 -0
- package/src/public-cli/coder-run.ts +575 -0
- package/src/public-cli/collector-run.ts +375 -0
- package/src/public-cli/command-renderer.ts +8 -0
- package/src/public-cli/config.ts +346 -0
- package/src/public-cli/doctor-run.ts +355 -0
- package/src/public-cli/explicit-internal.ts +274 -0
- package/src/public-cli/fixer-run.ts +587 -0
- package/src/public-cli/host-pi-runtime.ts +112 -0
- package/src/public-cli/invocation.ts +1958 -0
- package/src/public-cli/judge-run.ts +507 -0
- package/src/public-cli/main.ts +15 -0
- package/src/public-cli/merger-run.ts +681 -0
- package/src/public-cli/public-run-credentials.ts +71 -0
- package/src/public-cli/registry.ts +153 -0
- package/src/public-cli/reviewer-run.ts +561 -0
- package/src/public-cli/run-lifecycle.ts +884 -0
- package/src/public-cli/settlement.ts +3765 -0
- package/src/public-cli/terminal.ts +325 -0
- package/src/public-command-renderer.ts +43 -0
- package/src/reviewer-agent.ts +94 -0
- package/src/reviewer-auditor.ts +53 -0
- package/src/reviewer-child-executor.ts +31 -0
- package/src/reviewer-construction.ts +137 -0
- package/src/reviewer-dispatch.ts +94 -0
- package/src/reviewer-execution-ledger.ts +206 -0
- package/src/reviewer-failure-diagnostic.ts +18 -0
- package/src/reviewer-git-snapshot.ts +53 -0
- package/src/reviewer-pinned-git.ts +144 -0
- package/src/reviewer-preflight-error.ts +14 -0
- package/src/reviewer-prompt-identity.ts +17 -0
- package/src/reviewer-role.ts +193 -0
- package/src/reviewer-scope-prompt.ts +24 -0
- package/src/reviewer-settlement.ts +63 -0
- package/src/reviewer-workspace.ts +111 -0
- package/src/role-runtime.ts +884 -0
- package/src/sha256.ts +6 -0
- package/src/sitian-record-entry.ts +57 -0
- package/src/stderr-jsonl.ts +28 -0
- package/src/stream-idle-guard.ts +98 -0
- package/src/ticket-snapshot.ts +662 -0
- package/src/ticket-trajectory.ts +1000 -0
- package/src/tool-execution-observation.ts +168 -0
- package/src/uuidv7.ts +1 -0
- package/src/work-subject-identity.ts +94 -0
- package/src/worker-role.ts +434 -0
- package/src/worker-submission-gates.ts +225 -0
|
@@ -0,0 +1,775 @@
|
|
|
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
|
+
createAssistantMessageEventStream,
|
|
11
|
+
InMemoryCredentialStore,
|
|
12
|
+
type Api,
|
|
13
|
+
type AssistantMessage,
|
|
14
|
+
type Context,
|
|
15
|
+
type Model,
|
|
16
|
+
type Provider,
|
|
17
|
+
type ProviderStreamOptions,
|
|
18
|
+
type Usage,
|
|
19
|
+
} from "@earendil-works/pi-ai";
|
|
20
|
+
import type {
|
|
21
|
+
ExtensionContext,
|
|
22
|
+
ModelRuntime,
|
|
23
|
+
SessionManager,
|
|
24
|
+
} from "@earendil-works/pi-coding-agent";
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE,
|
|
28
|
+
AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE,
|
|
29
|
+
prepareComplianceDispatch,
|
|
30
|
+
type AuditorParentAttemptBinding,
|
|
31
|
+
} from "./compliance-transport.ts";
|
|
32
|
+
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.ts";
|
|
33
|
+
import type { ReviewerPromptText } from "./reviewer-prompt-identity.ts";
|
|
34
|
+
import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.ts";
|
|
35
|
+
|
|
36
|
+
// ── shared constants / types ──────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
export const AUDITOR_TURN_LIMIT = 32;
|
|
39
|
+
export const DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES = 2;
|
|
40
|
+
|
|
41
|
+
export type AuditorLastResponseFacts = {
|
|
42
|
+
readonly stopReason: AssistantMessage["stopReason"];
|
|
43
|
+
readonly toolNames: readonly string[];
|
|
44
|
+
};
|
|
45
|
+
export class AuditorTurnLimitError extends Error {
|
|
46
|
+
constructor(
|
|
47
|
+
readonly limit: number,
|
|
48
|
+
readonly observedTurns?: number,
|
|
49
|
+
readonly lastResponse?: AuditorLastResponseFacts,
|
|
50
|
+
) {
|
|
51
|
+
super(observedTurns === undefined
|
|
52
|
+
? `Auditor exceeded ${limit} turns`
|
|
53
|
+
: `Auditor exhausted its ${limit}-turn limit after ${observedTurns} provider turns`);
|
|
54
|
+
this.name = "AuditorTurnLimitError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export type AuditorCompletion = (
|
|
58
|
+
model: Model<Api>,
|
|
59
|
+
context: Context,
|
|
60
|
+
options: ProviderStreamOptions,
|
|
61
|
+
) => Promise<AssistantMessage>;
|
|
62
|
+
export type AuditorDecisionTool = {
|
|
63
|
+
name: string;
|
|
64
|
+
description: string;
|
|
65
|
+
parameters: object;
|
|
66
|
+
execute(...args: any[]): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>>;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type InProcessScratchOptions = {
|
|
70
|
+
readonly prefix: string;
|
|
71
|
+
readonly parentDirectory?: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Shared scratch directory with guaranteed cleanup. */
|
|
75
|
+
export async function withInProcessScratch<T>(
|
|
76
|
+
options: InProcessScratchOptions,
|
|
77
|
+
run: (scratch: string) => Promise<T>,
|
|
78
|
+
): Promise<T> {
|
|
79
|
+
const scratch = await mkdtemp(join(options.parentDirectory ?? tmpdir(), options.prefix));
|
|
80
|
+
let failure: unknown;
|
|
81
|
+
try {
|
|
82
|
+
return await run(scratch);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
failure = error;
|
|
85
|
+
throw error;
|
|
86
|
+
} finally {
|
|
87
|
+
try {
|
|
88
|
+
await rm(scratch, { recursive: true, force: true });
|
|
89
|
+
} catch (cleanupFailure) {
|
|
90
|
+
if (failure !== undefined) {
|
|
91
|
+
throw new AggregateError([failure, cleanupFailure], "in-process child scratch cleanup failed", { cause: failure });
|
|
92
|
+
}
|
|
93
|
+
throw cleanupFailure;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type InheritedRuntimeOptions = {
|
|
99
|
+
readonly context: ExtensionContext;
|
|
100
|
+
readonly label: string;
|
|
101
|
+
readonly runCompletion?: AuditorCompletion;
|
|
102
|
+
readonly injectedSystemPrompt?: string;
|
|
103
|
+
readonly signal?: AbortSignal;
|
|
104
|
+
/** When true, wrap provider streams with ADR 0059 idle-only retry. */
|
|
105
|
+
readonly idleRetry?: boolean;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type InheritedRuntime = {
|
|
109
|
+
readonly runtime: ModelRuntime;
|
|
110
|
+
readonly model: Model<Api>;
|
|
111
|
+
readonly dispatch: Awaited<ReturnType<typeof prepareComplianceDispatch>>;
|
|
112
|
+
/** Present when a stream failed under idle-retry instrumentation. */
|
|
113
|
+
streamFailure: unknown;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build an inherited ModelRuntime + provider. Optional idle-only retry is the
|
|
118
|
+
* ADR 0059 provider-stream seam (not package-owned-tool idle).
|
|
119
|
+
*/
|
|
120
|
+
export async function createInheritedRuntime(options: InheritedRuntimeOptions): Promise<InheritedRuntime> {
|
|
121
|
+
const { ModelRuntime } = await import("@earendil-works/pi-coding-agent");
|
|
122
|
+
const activeModel = options.context.model;
|
|
123
|
+
if (activeModel === undefined) throw new Error(`${options.label} requires an active model`);
|
|
124
|
+
const dispatch = await prepareComplianceDispatch(activeModel, options.context, options.label);
|
|
125
|
+
const parentProvider = options.runCompletion === undefined
|
|
126
|
+
? options.context.modelRegistry.getProvider(activeModel.provider)
|
|
127
|
+
: undefined;
|
|
128
|
+
if (parentProvider === undefined && options.runCompletion === undefined) {
|
|
129
|
+
throw new Error(`${options.label} provider not found: ${activeModel.provider}`);
|
|
130
|
+
}
|
|
131
|
+
const runtime = await ModelRuntime.create({
|
|
132
|
+
credentials: new InMemoryCredentialStore(),
|
|
133
|
+
modelsPath: null,
|
|
134
|
+
});
|
|
135
|
+
// Injected completions historically accepted the minimal model exposed by an
|
|
136
|
+
// ExtensionContext. AgentSession crosses ModelRuntime first, so complete the
|
|
137
|
+
// model metadata required by that runtime without changing provider identity.
|
|
138
|
+
const inheritedModel: Model<Api> = options.runCompletion === undefined
|
|
139
|
+
? dispatch.model
|
|
140
|
+
: {
|
|
141
|
+
...dispatch.model,
|
|
142
|
+
name: dispatch.model.name ?? dispatch.model.id,
|
|
143
|
+
baseUrl: dispatch.model.baseUrl ?? "",
|
|
144
|
+
reasoning: dispatch.model.reasoning ?? false,
|
|
145
|
+
input: dispatch.model.input ?? ["text"],
|
|
146
|
+
cost: dispatch.model.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
147
|
+
contextWindow: dispatch.model.contextWindow ?? 1,
|
|
148
|
+
maxTokens: dispatch.model.maxTokens ?? 1,
|
|
149
|
+
};
|
|
150
|
+
const state: InheritedRuntime = {
|
|
151
|
+
runtime,
|
|
152
|
+
model: inheritedModel,
|
|
153
|
+
dispatch,
|
|
154
|
+
streamFailure: undefined,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const abortReason = (signal: AbortSignal): unknown =>
|
|
158
|
+
signal.reason ?? new Error(`${options.label} provider stream aborted`);
|
|
159
|
+
|
|
160
|
+
async function waitForStream<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
161
|
+
if (signal.aborted) throw abortReason(signal);
|
|
162
|
+
let onAbort: (() => void) | undefined;
|
|
163
|
+
try {
|
|
164
|
+
return await Promise.race([
|
|
165
|
+
promise,
|
|
166
|
+
new Promise<never>((_resolve, reject) => {
|
|
167
|
+
onAbort = () => reject(abortReason(signal));
|
|
168
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
169
|
+
}),
|
|
170
|
+
]);
|
|
171
|
+
} finally {
|
|
172
|
+
if (onAbort !== undefined) signal.removeEventListener("abort", onAbort);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const createRetriedStream = (
|
|
177
|
+
simple: boolean,
|
|
178
|
+
model: Model<Api>,
|
|
179
|
+
context: Context,
|
|
180
|
+
request?: ProviderStreamOptions,
|
|
181
|
+
): ReturnType<Provider["stream"]> => {
|
|
182
|
+
const wrapped = createAssistantMessageEventStream();
|
|
183
|
+
void (async () => {
|
|
184
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
185
|
+
const idle = createStreamIdleGuard(
|
|
186
|
+
options.signal === undefined ? {} : { parentSignal: options.signal },
|
|
187
|
+
);
|
|
188
|
+
try {
|
|
189
|
+
const requestSignal = request?.signal;
|
|
190
|
+
const streamSignal = requestSignal === undefined
|
|
191
|
+
? idle.signal
|
|
192
|
+
: AbortSignal.any([idle.signal, requestSignal]);
|
|
193
|
+
const inheritedRequest = {
|
|
194
|
+
...(request ?? {}),
|
|
195
|
+
...(dispatch.auth.env === undefined ? {} : { env: dispatch.auth.env }),
|
|
196
|
+
signal: streamSignal,
|
|
197
|
+
} as ProviderStreamOptions;
|
|
198
|
+
if (options.runCompletion !== undefined) {
|
|
199
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
200
|
+
if (streamSignal.aborted) throw abortReason(streamSignal);
|
|
201
|
+
const completed = await waitForStream(
|
|
202
|
+
options.runCompletion(
|
|
203
|
+
model,
|
|
204
|
+
options.injectedSystemPrompt === undefined
|
|
205
|
+
? context
|
|
206
|
+
: { ...context, systemPrompt: options.injectedSystemPrompt },
|
|
207
|
+
inheritedRequest,
|
|
208
|
+
),
|
|
209
|
+
streamSignal,
|
|
210
|
+
);
|
|
211
|
+
const response: AssistantMessage = {
|
|
212
|
+
...completed,
|
|
213
|
+
api: model.api,
|
|
214
|
+
provider: model.provider,
|
|
215
|
+
model: model.id,
|
|
216
|
+
};
|
|
217
|
+
if (response.stopReason === "error" || response.stopReason === "aborted") {
|
|
218
|
+
wrapped.push({ type: "error", reason: response.stopReason, error: response });
|
|
219
|
+
} else {
|
|
220
|
+
wrapped.end(response);
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const source = simple
|
|
225
|
+
? parentProvider!.streamSimple(model, context, inheritedRequest as any)
|
|
226
|
+
: parentProvider!.stream(model, context, inheritedRequest as any);
|
|
227
|
+
let sawEvent = false;
|
|
228
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
229
|
+
while (true) {
|
|
230
|
+
const next = await waitForStream(iterator.next(), idle.signal);
|
|
231
|
+
if (next.done) break;
|
|
232
|
+
sawEvent = true;
|
|
233
|
+
idle.poke();
|
|
234
|
+
wrapped.push(next.value as any);
|
|
235
|
+
}
|
|
236
|
+
const response = await waitForStream(source.result(), idle.signal);
|
|
237
|
+
if (!sawEvent) wrapped.end(response);
|
|
238
|
+
return;
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (request?.signal?.aborted) {
|
|
241
|
+
const response: AssistantMessage = {
|
|
242
|
+
role: "assistant",
|
|
243
|
+
content: [],
|
|
244
|
+
api: model.api,
|
|
245
|
+
provider: model.provider,
|
|
246
|
+
model: model.id,
|
|
247
|
+
usage: emptyUsage(),
|
|
248
|
+
stopReason: "aborted",
|
|
249
|
+
errorMessage: "Auditor session aborted",
|
|
250
|
+
timestamp: Date.now(),
|
|
251
|
+
};
|
|
252
|
+
wrapped.push({ type: "error", reason: "aborted", error: response });
|
|
253
|
+
wrapped.end(response);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const failure = isStreamIdleTimeoutError(idle.signal.reason) ? idle.signal.reason : error;
|
|
257
|
+
if (
|
|
258
|
+
options.idleRetry === true
|
|
259
|
+
&& isStreamIdleTimeoutError(failure)
|
|
260
|
+
&& attempt < DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES
|
|
261
|
+
&& options.signal?.aborted !== true
|
|
262
|
+
) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
state.streamFailure = failure;
|
|
266
|
+
const response: AssistantMessage = {
|
|
267
|
+
role: "assistant",
|
|
268
|
+
content: [],
|
|
269
|
+
api: model.api,
|
|
270
|
+
provider: model.provider,
|
|
271
|
+
model: model.id,
|
|
272
|
+
usage: emptyUsage(),
|
|
273
|
+
stopReason: "error",
|
|
274
|
+
errorMessage: failure instanceof Error ? failure.message : String(failure),
|
|
275
|
+
timestamp: Date.now(),
|
|
276
|
+
};
|
|
277
|
+
wrapped.push({ type: "error", reason: "error", error: response });
|
|
278
|
+
wrapped.end(response);
|
|
279
|
+
return;
|
|
280
|
+
} finally {
|
|
281
|
+
idle.dispose();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
})();
|
|
285
|
+
return wrapped as ReturnType<Provider["stream"]>;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const provider: Provider = options.idleRetry === true || options.runCompletion !== undefined
|
|
289
|
+
? {
|
|
290
|
+
id: parentProvider?.id ?? activeModel.provider,
|
|
291
|
+
name: parentProvider?.name ?? options.label,
|
|
292
|
+
auth: {
|
|
293
|
+
apiKey: {
|
|
294
|
+
name: `Inherited ${options.label} authentication`,
|
|
295
|
+
async resolve() {
|
|
296
|
+
const { env, ...auth } = dispatch.auth;
|
|
297
|
+
return {
|
|
298
|
+
auth: {
|
|
299
|
+
...auth,
|
|
300
|
+
...(dispatch.model.baseUrl === undefined ? {} : { baseUrl: dispatch.model.baseUrl }),
|
|
301
|
+
},
|
|
302
|
+
...(env === undefined ? {} : { env }),
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
getModels() { return [inheritedModel]; },
|
|
308
|
+
stream(model, context, request) {
|
|
309
|
+
return createRetriedStream(false, model, context, request as ProviderStreamOptions | undefined);
|
|
310
|
+
},
|
|
311
|
+
streamSimple(model, context, request) {
|
|
312
|
+
return createRetriedStream(true, model, context, request as ProviderStreamOptions | undefined);
|
|
313
|
+
},
|
|
314
|
+
}
|
|
315
|
+
: {
|
|
316
|
+
id: parentProvider!.id,
|
|
317
|
+
name: parentProvider!.name,
|
|
318
|
+
...(parentProvider!.baseUrl === undefined ? {} : { baseUrl: parentProvider!.baseUrl }),
|
|
319
|
+
...(parentProvider!.headers === undefined ? {} : { headers: parentProvider!.headers }),
|
|
320
|
+
auth: {
|
|
321
|
+
apiKey: {
|
|
322
|
+
name: `Inherited ${options.label} authentication`,
|
|
323
|
+
async resolve() {
|
|
324
|
+
return {
|
|
325
|
+
auth: {
|
|
326
|
+
...(dispatch.auth.apiKey === undefined ? {} : { apiKey: dispatch.auth.apiKey }),
|
|
327
|
+
...(dispatch.auth.headers === undefined ? {} : { headers: dispatch.auth.headers }),
|
|
328
|
+
...(dispatch.model.baseUrl === undefined ? {} : { baseUrl: dispatch.model.baseUrl }),
|
|
329
|
+
},
|
|
330
|
+
...(dispatch.auth.env === undefined ? {} : { env: dispatch.auth.env }),
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
getModels() { return [inheritedModel]; },
|
|
336
|
+
stream(model, childContext, streamOptions) {
|
|
337
|
+
return parentProvider!.stream(model, childContext, streamOptions);
|
|
338
|
+
},
|
|
339
|
+
streamSimple(model, childContext, streamOptions) {
|
|
340
|
+
return parentProvider!.streamSimple(model, childContext, streamOptions);
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
runtime.registerNativeProvider(provider);
|
|
345
|
+
return state;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
type ClassifiedReviewerError = Error & Readonly<{
|
|
349
|
+
evidenceChildFailure: "provider" | "child";
|
|
350
|
+
evidenceChildOriginal?: unknown;
|
|
351
|
+
}>;
|
|
352
|
+
function classifiedError(error: unknown, evidenceChildFailure: "provider" | "child"): ClassifiedReviewerError {
|
|
353
|
+
const diagnostic = typeof error === "object" && error !== null && typeof (error as { errorMessage?: unknown }).errorMessage === "string"
|
|
354
|
+
? (error as { errorMessage: string }).errorMessage
|
|
355
|
+
: error === undefined ? "" : String(error);
|
|
356
|
+
const wrapped = error instanceof Error
|
|
357
|
+
? error
|
|
358
|
+
: Object.assign(new Error(diagnostic, { cause: error }), { evidenceChildOriginal: error });
|
|
359
|
+
const classification = "evidenceChildFailure" in wrapped
|
|
360
|
+
? (wrapped as ClassifiedReviewerError).evidenceChildFailure
|
|
361
|
+
: evidenceChildFailure;
|
|
362
|
+
return Object.assign(wrapped, { evidenceChildFailure: classification });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function emptyUsage(): Usage {
|
|
366
|
+
return {
|
|
367
|
+
input: 0,
|
|
368
|
+
output: 0,
|
|
369
|
+
cacheRead: 0,
|
|
370
|
+
cacheWrite: 0,
|
|
371
|
+
totalTokens: 0,
|
|
372
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function addUsage(total: Usage, next: Usage): void {
|
|
377
|
+
total.input += next.input;
|
|
378
|
+
total.output += next.output;
|
|
379
|
+
total.cacheRead += next.cacheRead;
|
|
380
|
+
total.cacheWrite += next.cacheWrite;
|
|
381
|
+
total.totalTokens += next.totalTokens;
|
|
382
|
+
total.cost.input += next.cost.input;
|
|
383
|
+
total.cost.output += next.cost.output;
|
|
384
|
+
total.cost.cacheRead += next.cost.cacheRead;
|
|
385
|
+
total.cost.cacheWrite += next.cost.cacheWrite;
|
|
386
|
+
total.cost.total += next.cost.total;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── evidence child (reviewer legs) ────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
export type EvidenceChildExecuteOptions = Readonly<{
|
|
392
|
+
signal?: AbortSignal;
|
|
393
|
+
/** Parent directory for credential/config scratch. Defaults to os.tmpdir(). */
|
|
394
|
+
credentialScratchParent?: string;
|
|
395
|
+
}>;
|
|
396
|
+
|
|
397
|
+
export async function executeEvidenceChild(
|
|
398
|
+
workspace: string,
|
|
399
|
+
prompt: ReviewerPromptText,
|
|
400
|
+
context: ExtensionContext,
|
|
401
|
+
options: EvidenceChildExecuteOptions = {},
|
|
402
|
+
): Promise<{ report: string; usage: Usage; prompt: ReviewerPromptText }> {
|
|
403
|
+
const signal = options.signal;
|
|
404
|
+
return withInProcessScratch(
|
|
405
|
+
{
|
|
406
|
+
prefix: "ak-evidence-child-",
|
|
407
|
+
...(options.credentialScratchParent === undefined
|
|
408
|
+
? {}
|
|
409
|
+
: { parentDirectory: options.credentialScratchParent }),
|
|
410
|
+
},
|
|
411
|
+
async (childConfigDir) => {
|
|
412
|
+
const { openInProcessAgentSession } = await import("./in-process-session.ts");
|
|
413
|
+
const { childSessionManager } = await import("./activation-ledger-session.ts");
|
|
414
|
+
let inherited: InheritedRuntime;
|
|
415
|
+
try {
|
|
416
|
+
inherited = await createInheritedRuntime({
|
|
417
|
+
context,
|
|
418
|
+
label: "Evidence child",
|
|
419
|
+
});
|
|
420
|
+
} catch (error) {
|
|
421
|
+
throw classifiedError(error, "provider");
|
|
422
|
+
}
|
|
423
|
+
// No tools allowlist — Pi defaults + unrestricted evidence surface (ADR 0064).
|
|
424
|
+
// Single createAgentSession owner: in-process-session.ts.
|
|
425
|
+
const { session, dispose } = await openInProcessAgentSession({
|
|
426
|
+
cwd: workspace,
|
|
427
|
+
agentDir: childConfigDir,
|
|
428
|
+
model: inherited.model,
|
|
429
|
+
thinkingLevel: context.thinkingLevel ?? "off",
|
|
430
|
+
modelRuntime: inherited.runtime,
|
|
431
|
+
systemPrompt: [
|
|
432
|
+
"Work only in the supplied workspace.",
|
|
433
|
+
"Use the available evidence tools to investigate. Do not commit, push, or mutate remotes.",
|
|
434
|
+
"Return one substantive non-blank report.",
|
|
435
|
+
].join("\n"),
|
|
436
|
+
sessionManager: childSessionManager(context.sessionManager, workspace, "evidence-children"),
|
|
437
|
+
});
|
|
438
|
+
const usage = emptyUsage();
|
|
439
|
+
const unsubscribe = session.subscribe((event) => {
|
|
440
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
441
|
+
addUsage(usage, event.message.usage);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
const abortChild = () => { void session.abort(); };
|
|
445
|
+
if (signal?.aborted) abortChild();
|
|
446
|
+
else signal?.addEventListener("abort", abortChild, { once: true });
|
|
447
|
+
let primaryFailure: unknown;
|
|
448
|
+
try {
|
|
449
|
+
const delivered = prompt;
|
|
450
|
+
try {
|
|
451
|
+
await session.prompt(delivered);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
throw classifiedError(error, "provider");
|
|
454
|
+
}
|
|
455
|
+
if (signal?.aborted) throw new Error("Evidence child was cancelled");
|
|
456
|
+
const lastAssistant = [...session.messages]
|
|
457
|
+
.reverse()
|
|
458
|
+
.find((message) => message.role === "assistant");
|
|
459
|
+
if (lastAssistant?.role === "assistant" && lastAssistant.stopReason === "error") {
|
|
460
|
+
throw classifiedError(
|
|
461
|
+
new Error(lastAssistant.errorMessage ?? "", { cause: lastAssistant }),
|
|
462
|
+
"provider",
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
if (lastAssistant?.role !== "assistant" || lastAssistant.stopReason === "aborted") {
|
|
466
|
+
throw classifiedError(
|
|
467
|
+
new Error("Evidence child child terminated without a report", {
|
|
468
|
+
cause: lastAssistant ?? session.messages,
|
|
469
|
+
}),
|
|
470
|
+
"child",
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const report = session.getLastAssistantText() ?? "";
|
|
474
|
+
if (report.trim().length === 0) {
|
|
475
|
+
throw new Error("Evidence child returned a blank child report");
|
|
476
|
+
}
|
|
477
|
+
return { report, usage, prompt: delivered };
|
|
478
|
+
} catch (error) {
|
|
479
|
+
primaryFailure = classifiedError(error, "child");
|
|
480
|
+
throw primaryFailure;
|
|
481
|
+
} finally {
|
|
482
|
+
signal?.removeEventListener("abort", abortChild);
|
|
483
|
+
let cleanupFailure: unknown;
|
|
484
|
+
for (const cleanup of [() => unsubscribe(), () => dispose()]) {
|
|
485
|
+
try {
|
|
486
|
+
cleanup();
|
|
487
|
+
} catch (failure) {
|
|
488
|
+
cleanupFailure = cleanupFailure === undefined
|
|
489
|
+
? failure
|
|
490
|
+
: new AggregateError([cleanupFailure, failure], "Reviewer child cleanup failed", {
|
|
491
|
+
cause: cleanupFailure,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (cleanupFailure !== undefined) {
|
|
496
|
+
if (primaryFailure !== undefined) {
|
|
497
|
+
throw new AggregateError(
|
|
498
|
+
[primaryFailure, cleanupFailure],
|
|
499
|
+
"Reviewer child execution and cleanup failed",
|
|
500
|
+
{ cause: primaryFailure },
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
throw new AggregateError([cleanupFailure], "Reviewer child cleanup failed", {
|
|
504
|
+
cause: cleanupFailure,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ── auditor child ─────────────────────────────────────────────────────────
|
|
513
|
+
|
|
514
|
+
export type AuditorRoleOptions = {
|
|
515
|
+
systemPrompt: string;
|
|
516
|
+
prompt: string;
|
|
517
|
+
tool: AuditorDecisionTool;
|
|
518
|
+
dossierTool: AuditorDecisionTool;
|
|
519
|
+
roleLabel: string;
|
|
520
|
+
context: ExtensionContext;
|
|
521
|
+
signal?: AbortSignal;
|
|
522
|
+
runCompletion?: AuditorCompletion;
|
|
523
|
+
retainResponse?(response: AssistantMessage): void;
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Auditor lifecycle via the shared in-process helper.
|
|
528
|
+
* Adapter keeps role label / soul / decision tool / result projection only.
|
|
529
|
+
* No tools allowlist (ADR 0064). Provider-stream idle-only retry (ADR 0059).
|
|
530
|
+
* Durable child session via ADR 0065 sitian entry.
|
|
531
|
+
*/
|
|
532
|
+
export async function executeAuditorChild(
|
|
533
|
+
options: AuditorRoleOptions,
|
|
534
|
+
): Promise<{ decision: unknown; response: AssistantMessage }> {
|
|
535
|
+
const { createRecordSession } = await import("./sitian-record-entry.ts");
|
|
536
|
+
|
|
537
|
+
return withInProcessScratch({ prefix: "ak-auditor-role-" }, async (scratch) => {
|
|
538
|
+
const inherited = await createInheritedRuntime({
|
|
539
|
+
context: options.context,
|
|
540
|
+
label: options.roleLabel,
|
|
541
|
+
idleRetry: true,
|
|
542
|
+
...(options.runCompletion === undefined
|
|
543
|
+
? {}
|
|
544
|
+
: { runCompletion: options.runCompletion, injectedSystemPrompt: options.systemPrompt }),
|
|
545
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const cwd = options.context.cwd ?? process.cwd();
|
|
549
|
+
|
|
550
|
+
let decision: unknown;
|
|
551
|
+
let decisionSubmitted = false;
|
|
552
|
+
let decisionCallId: string | undefined;
|
|
553
|
+
let decisionToolFailure: unknown;
|
|
554
|
+
const tool = wrapPackageOwnedToolDefinition({
|
|
555
|
+
...options.tool,
|
|
556
|
+
label: options.roleLabel,
|
|
557
|
+
async execute(...args: any[]) {
|
|
558
|
+
if (decisionSubmitted && decisionCallId !== args[0]) {
|
|
559
|
+
throw new Error("Auditor decision was submitted more than once");
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
const result = await options.tool.execute(...args);
|
|
563
|
+
decision = args[1];
|
|
564
|
+
decisionCallId = args[0];
|
|
565
|
+
decisionSubmitted = true;
|
|
566
|
+
return result;
|
|
567
|
+
} catch (error) {
|
|
568
|
+
decisionToolFailure = error;
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const parentSessionManager = options.context.sessionManager;
|
|
575
|
+
const parentHeader = parentSessionManager?.getHeader?.();
|
|
576
|
+
const parentSessionFile = parentSessionManager?.getSessionFile?.();
|
|
577
|
+
const parentAttemptEntryId = parentSessionManager?.getLeafId?.();
|
|
578
|
+
const auditorSessionManager: SessionManager = createRecordSession({
|
|
579
|
+
cwd,
|
|
580
|
+
kind: "auditor-roles",
|
|
581
|
+
...(parentSessionManager === undefined ? {} : { parent: parentSessionManager }),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Shared session open — no tools allowlist (ADR 0064).
|
|
585
|
+
const { openInProcessAgentSession: openSession } = await import("./in-process-session.ts");
|
|
586
|
+
const { session, dispose } = await openSession({
|
|
587
|
+
cwd,
|
|
588
|
+
agentDir: scratch,
|
|
589
|
+
model: inherited.model,
|
|
590
|
+
thinkingLevel: options.context.thinkingLevel ?? "off",
|
|
591
|
+
modelRuntime: inherited.runtime,
|
|
592
|
+
systemPrompt: options.systemPrompt,
|
|
593
|
+
customTools: [wrapPackageOwnedToolDefinition({ ...options.dossierTool, label: options.roleLabel }), tool],
|
|
594
|
+
sessionManager: auditorSessionManager,
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const binding: AuditorParentAttemptBinding = {
|
|
598
|
+
version: 1,
|
|
599
|
+
parent: {
|
|
600
|
+
...(parentHeader?.id === undefined ? {} : { sessionId: parentHeader.id }),
|
|
601
|
+
...(parentSessionFile === undefined ? {} : { sessionFile: parentSessionFile }),
|
|
602
|
+
...(parentAttemptEntryId === null || parentAttemptEntryId === undefined
|
|
603
|
+
? {}
|
|
604
|
+
: { attemptEntryId: parentAttemptEntryId }),
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
// Durable binding is a prerequisite: never observe the provider when its
|
|
608
|
+
// response could not later be tied to the current parent attempt.
|
|
609
|
+
auditorSessionManager.appendCustomEntry(AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, binding);
|
|
610
|
+
|
|
611
|
+
let turns = 0;
|
|
612
|
+
let boundaryResponse: AssistantMessage | undefined;
|
|
613
|
+
let retentionFailure: unknown;
|
|
614
|
+
let retainedResponse: AssistantMessage | undefined;
|
|
615
|
+
const registeredToolNames = new Set(session.getAllTools().map((entry) => entry.name));
|
|
616
|
+
const evidenceToolFailures = new Map<string, unknown>();
|
|
617
|
+
for (const name of registeredToolNames) {
|
|
618
|
+
if (name === tool.name) continue;
|
|
619
|
+
const definition = session.getToolDefinition(name);
|
|
620
|
+
if (definition === undefined) continue;
|
|
621
|
+
const execute = definition.execute.bind(definition);
|
|
622
|
+
definition.execute = async (...args: any[]) => {
|
|
623
|
+
try {
|
|
624
|
+
return await (execute as (...executeArgs: any[]) => Promise<any>)(...args);
|
|
625
|
+
} catch (error) {
|
|
626
|
+
evidenceToolFailures.set(args[0], error);
|
|
627
|
+
throw error;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
const findToolFailure = (response: AssistantMessage): unknown => {
|
|
632
|
+
const callIds = response.content.flatMap((part) =>
|
|
633
|
+
part.type === "toolCall" && part.name !== tool.name && registeredToolNames.has(part.name) ? [part.id] : []);
|
|
634
|
+
for (const callId of callIds) {
|
|
635
|
+
if (evidenceToolFailures.has(callId)) return evidenceToolFailures.get(callId);
|
|
636
|
+
}
|
|
637
|
+
const callIdSet = new Set(callIds);
|
|
638
|
+
return [...session.messages].reverse().find((message) =>
|
|
639
|
+
message.role === "toolResult" && callIdSet.has(message.toolCallId) && message.isError);
|
|
640
|
+
};
|
|
641
|
+
const unsubscribe = session.subscribe((event) => {
|
|
642
|
+
if (event.type === "message_end" && event.message.role === "assistant" && boundaryResponse === undefined) {
|
|
643
|
+
turns += 1;
|
|
644
|
+
retainedResponse = event.message;
|
|
645
|
+
try { options.retainResponse?.(event.message); } catch (error) { retentionFailure = error; }
|
|
646
|
+
for (const part of event.message.content) {
|
|
647
|
+
if (part.type !== "toolCall" || part.name !== tool.name) continue;
|
|
648
|
+
if (!decisionSubmitted) {
|
|
649
|
+
decision = part.arguments;
|
|
650
|
+
decisionCallId = part.id;
|
|
651
|
+
decisionSubmitted = true;
|
|
652
|
+
} else if (decisionCallId !== part.id) {
|
|
653
|
+
decisionToolFailure = new Error("Auditor decision was submitted more than once");
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (turns >= AUDITOR_TURN_LIMIT) boundaryResponse = event.message;
|
|
657
|
+
}
|
|
658
|
+
if (event.type === "turn_end" &&
|
|
659
|
+
(decisionSubmitted || boundaryResponse !== undefined || retentionFailure !== undefined)) {
|
|
660
|
+
void session.abort();
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
const abort = () => { void session.abort(); };
|
|
664
|
+
if (options.signal?.aborted) abort();
|
|
665
|
+
else options.signal?.addEventListener("abort", abort, { once: true });
|
|
666
|
+
|
|
667
|
+
try {
|
|
668
|
+
try {
|
|
669
|
+
await session.prompt(options.prompt);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
if (options.signal?.aborted) throw options.signal.reason;
|
|
672
|
+
if (inherited.streamFailure !== undefined) throw inherited.streamFailure;
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
if (options.signal?.aborted) throw options.signal.reason;
|
|
676
|
+
if (inherited.streamFailure !== undefined) throw inherited.streamFailure;
|
|
677
|
+
if (decisionToolFailure !== undefined) throw decisionToolFailure;
|
|
678
|
+
const relevantResponse = !decisionSubmitted
|
|
679
|
+
? boundaryResponse
|
|
680
|
+
: [...session.messages].reverse().find((message): message is AssistantMessage =>
|
|
681
|
+
message.role === "assistant" && message.content.some((part) => part.type === "toolCall" && part.name === tool.name));
|
|
682
|
+
if (relevantResponse !== undefined) {
|
|
683
|
+
const toolFailure = findToolFailure(relevantResponse);
|
|
684
|
+
if (toolFailure !== undefined) throw toolFailure;
|
|
685
|
+
}
|
|
686
|
+
if (retentionFailure !== undefined && retainedResponse?.stopReason !== "error") throw retentionFailure;
|
|
687
|
+
if (boundaryResponse !== undefined && !decisionSubmitted) {
|
|
688
|
+
const toolNames = boundaryResponse.content.flatMap((part) => part.type === "toolCall" ? [part.name] : []);
|
|
689
|
+
throw new AuditorTurnLimitError(AUDITOR_TURN_LIMIT, turns, {
|
|
690
|
+
stopReason: boundaryResponse.stopReason,
|
|
691
|
+
toolNames,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const assistants = [...session.messages]
|
|
696
|
+
.reverse()
|
|
697
|
+
.filter((message): message is AssistantMessage => message.role === "assistant");
|
|
698
|
+
const response = !decisionSubmitted
|
|
699
|
+
? assistants[0]
|
|
700
|
+
: assistants.find((message) =>
|
|
701
|
+
message.content.some((part) => part.type === "toolCall" && part.name === tool.name));
|
|
702
|
+
|
|
703
|
+
if (response !== undefined) {
|
|
704
|
+
try {
|
|
705
|
+
if (retainedResponse === undefined) options.retainResponse?.(response);
|
|
706
|
+
else if (retentionFailure !== undefined) throw retentionFailure;
|
|
707
|
+
} catch (retentionFailure) {
|
|
708
|
+
if (response.stopReason !== "error") throw retentionFailure;
|
|
709
|
+
const failure = new Error(
|
|
710
|
+
response.errorMessage?.trim() || "provider failure",
|
|
711
|
+
{ cause: retentionFailure },
|
|
712
|
+
) as Error & {
|
|
713
|
+
knownCause: "provider";
|
|
714
|
+
failureCode?: string;
|
|
715
|
+
details: Record<string, unknown>;
|
|
716
|
+
};
|
|
717
|
+
failure.name = response.model || response.provider || "Error";
|
|
718
|
+
failure.knownCause = "provider";
|
|
719
|
+
failure.failureCode = response.provider || response.model;
|
|
720
|
+
const retentionError = retentionFailure instanceof Error ? retentionFailure : undefined;
|
|
721
|
+
const retentionCause = retentionError?.cause;
|
|
722
|
+
failure.details = {
|
|
723
|
+
...(response.provider ? { provider: response.provider } : {}),
|
|
724
|
+
...(response.model ? { model: response.model } : {}),
|
|
725
|
+
retentionFailure: {
|
|
726
|
+
name: retentionError?.name ?? typeof retentionFailure,
|
|
727
|
+
message: retentionError?.message ?? String(retentionFailure),
|
|
728
|
+
...((retentionError as Error & { code?: unknown } | undefined)?.code !== undefined
|
|
729
|
+
? { code: (retentionError as Error & { code?: unknown }).code }
|
|
730
|
+
: {}),
|
|
731
|
+
...(retentionCause === undefined
|
|
732
|
+
? {}
|
|
733
|
+
: {
|
|
734
|
+
cause: retentionCause instanceof Error
|
|
735
|
+
? {
|
|
736
|
+
name: retentionCause.name,
|
|
737
|
+
message: retentionCause.message,
|
|
738
|
+
...((retentionCause as Error & { code?: unknown }).code === undefined
|
|
739
|
+
? {}
|
|
740
|
+
: { code: (retentionCause as Error & { code?: unknown }).code }),
|
|
741
|
+
}
|
|
742
|
+
: retentionCause,
|
|
743
|
+
}),
|
|
744
|
+
},
|
|
745
|
+
};
|
|
746
|
+
auditorSessionManager.appendCustomEntry(AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, {
|
|
747
|
+
version: 1,
|
|
748
|
+
parent: binding.parent,
|
|
749
|
+
failure: {
|
|
750
|
+
cause: failure.knownCause,
|
|
751
|
+
identity: { name: failure.name, code: failure.failureCode },
|
|
752
|
+
diagnostic: failure.message,
|
|
753
|
+
details: failure.details,
|
|
754
|
+
},
|
|
755
|
+
});
|
|
756
|
+
throw failure;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (
|
|
761
|
+
response === undefined
|
|
762
|
+
|| response.stopReason === "error"
|
|
763
|
+
|| response.stopReason === "aborted"
|
|
764
|
+
|| !decisionSubmitted
|
|
765
|
+
) {
|
|
766
|
+
throw new Error(`${options.roleLabel} exited without a readable decision receipt`);
|
|
767
|
+
}
|
|
768
|
+
return { decision, response };
|
|
769
|
+
} finally {
|
|
770
|
+
options.signal?.removeEventListener("abort", abort);
|
|
771
|
+
unsubscribe();
|
|
772
|
+
dispose();
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
}
|