@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,661 @@
|
|
|
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 { createAssistantMessageEventStream, InMemoryCredentialStore, } from "@earendil-works/pi-ai";
|
|
10
|
+
import { AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, prepareComplianceDispatch, } from "./compliance-transport.js";
|
|
11
|
+
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.js";
|
|
12
|
+
import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.js";
|
|
13
|
+
// ── shared constants / types ──────────────────────────────────────────────
|
|
14
|
+
export const AUDITOR_TURN_LIMIT = 32;
|
|
15
|
+
export const DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES = 2;
|
|
16
|
+
export class AuditorTurnLimitError extends Error {
|
|
17
|
+
limit;
|
|
18
|
+
observedTurns;
|
|
19
|
+
lastResponse;
|
|
20
|
+
constructor(limit, observedTurns, lastResponse) {
|
|
21
|
+
super(observedTurns === undefined
|
|
22
|
+
? `Auditor exceeded ${limit} turns`
|
|
23
|
+
: `Auditor exhausted its ${limit}-turn limit after ${observedTurns} provider turns`);
|
|
24
|
+
this.limit = limit;
|
|
25
|
+
this.observedTurns = observedTurns;
|
|
26
|
+
this.lastResponse = lastResponse;
|
|
27
|
+
this.name = "AuditorTurnLimitError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Shared scratch directory with guaranteed cleanup. */
|
|
31
|
+
export async function withInProcessScratch(options, run) {
|
|
32
|
+
const scratch = await mkdtemp(join(options.parentDirectory ?? tmpdir(), options.prefix));
|
|
33
|
+
let failure;
|
|
34
|
+
try {
|
|
35
|
+
return await run(scratch);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
failure = error;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
try {
|
|
43
|
+
await rm(scratch, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
catch (cleanupFailure) {
|
|
46
|
+
if (failure !== undefined) {
|
|
47
|
+
throw new AggregateError([failure, cleanupFailure], "in-process child scratch cleanup failed", { cause: failure });
|
|
48
|
+
}
|
|
49
|
+
throw cleanupFailure;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build an inherited ModelRuntime + provider. Optional idle-only retry is the
|
|
55
|
+
* ADR 0059 provider-stream seam (not package-owned-tool idle).
|
|
56
|
+
*/
|
|
57
|
+
export async function createInheritedRuntime(options) {
|
|
58
|
+
const { ModelRuntime } = await import("@earendil-works/pi-coding-agent");
|
|
59
|
+
const activeModel = options.context.model;
|
|
60
|
+
if (activeModel === undefined)
|
|
61
|
+
throw new Error(`${options.label} requires an active model`);
|
|
62
|
+
const dispatch = await prepareComplianceDispatch(activeModel, options.context, options.label);
|
|
63
|
+
const parentProvider = options.runCompletion === undefined
|
|
64
|
+
? options.context.modelRegistry.getProvider(activeModel.provider)
|
|
65
|
+
: undefined;
|
|
66
|
+
if (parentProvider === undefined && options.runCompletion === undefined) {
|
|
67
|
+
throw new Error(`${options.label} provider not found: ${activeModel.provider}`);
|
|
68
|
+
}
|
|
69
|
+
const runtime = await ModelRuntime.create({
|
|
70
|
+
credentials: new InMemoryCredentialStore(),
|
|
71
|
+
modelsPath: null,
|
|
72
|
+
});
|
|
73
|
+
// Injected completions historically accepted the minimal model exposed by an
|
|
74
|
+
// ExtensionContext. AgentSession crosses ModelRuntime first, so complete the
|
|
75
|
+
// model metadata required by that runtime without changing provider identity.
|
|
76
|
+
const inheritedModel = options.runCompletion === undefined
|
|
77
|
+
? dispatch.model
|
|
78
|
+
: {
|
|
79
|
+
...dispatch.model,
|
|
80
|
+
name: dispatch.model.name ?? dispatch.model.id,
|
|
81
|
+
baseUrl: dispatch.model.baseUrl ?? "",
|
|
82
|
+
reasoning: dispatch.model.reasoning ?? false,
|
|
83
|
+
input: dispatch.model.input ?? ["text"],
|
|
84
|
+
cost: dispatch.model.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
85
|
+
contextWindow: dispatch.model.contextWindow ?? 1,
|
|
86
|
+
maxTokens: dispatch.model.maxTokens ?? 1,
|
|
87
|
+
};
|
|
88
|
+
const state = {
|
|
89
|
+
runtime,
|
|
90
|
+
model: inheritedModel,
|
|
91
|
+
dispatch,
|
|
92
|
+
streamFailure: undefined,
|
|
93
|
+
};
|
|
94
|
+
const abortReason = (signal) => signal.reason ?? new Error(`${options.label} provider stream aborted`);
|
|
95
|
+
async function waitForStream(promise, signal) {
|
|
96
|
+
if (signal.aborted)
|
|
97
|
+
throw abortReason(signal);
|
|
98
|
+
let onAbort;
|
|
99
|
+
try {
|
|
100
|
+
return await Promise.race([
|
|
101
|
+
promise,
|
|
102
|
+
new Promise((_resolve, reject) => {
|
|
103
|
+
onAbort = () => reject(abortReason(signal));
|
|
104
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
105
|
+
}),
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
if (onAbort !== undefined)
|
|
110
|
+
signal.removeEventListener("abort", onAbort);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const createRetriedStream = (simple, model, context, request) => {
|
|
114
|
+
const wrapped = createAssistantMessageEventStream();
|
|
115
|
+
void (async () => {
|
|
116
|
+
for (let attempt = 0;; attempt += 1) {
|
|
117
|
+
const idle = createStreamIdleGuard(options.signal === undefined ? {} : { parentSignal: options.signal });
|
|
118
|
+
try {
|
|
119
|
+
const requestSignal = request?.signal;
|
|
120
|
+
const streamSignal = requestSignal === undefined
|
|
121
|
+
? idle.signal
|
|
122
|
+
: AbortSignal.any([idle.signal, requestSignal]);
|
|
123
|
+
const inheritedRequest = {
|
|
124
|
+
...(request ?? {}),
|
|
125
|
+
...(dispatch.auth.env === undefined ? {} : { env: dispatch.auth.env }),
|
|
126
|
+
signal: streamSignal,
|
|
127
|
+
};
|
|
128
|
+
if (options.runCompletion !== undefined) {
|
|
129
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
130
|
+
if (streamSignal.aborted)
|
|
131
|
+
throw abortReason(streamSignal);
|
|
132
|
+
const completed = await waitForStream(options.runCompletion(model, options.injectedSystemPrompt === undefined
|
|
133
|
+
? context
|
|
134
|
+
: { ...context, systemPrompt: options.injectedSystemPrompt }, inheritedRequest), streamSignal);
|
|
135
|
+
const response = {
|
|
136
|
+
...completed,
|
|
137
|
+
api: model.api,
|
|
138
|
+
provider: model.provider,
|
|
139
|
+
model: model.id,
|
|
140
|
+
};
|
|
141
|
+
if (response.stopReason === "error" || response.stopReason === "aborted") {
|
|
142
|
+
wrapped.push({ type: "error", reason: response.stopReason, error: response });
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
wrapped.end(response);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const source = simple
|
|
150
|
+
? parentProvider.streamSimple(model, context, inheritedRequest)
|
|
151
|
+
: parentProvider.stream(model, context, inheritedRequest);
|
|
152
|
+
let sawEvent = false;
|
|
153
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
154
|
+
while (true) {
|
|
155
|
+
const next = await waitForStream(iterator.next(), idle.signal);
|
|
156
|
+
if (next.done)
|
|
157
|
+
break;
|
|
158
|
+
sawEvent = true;
|
|
159
|
+
idle.poke();
|
|
160
|
+
wrapped.push(next.value);
|
|
161
|
+
}
|
|
162
|
+
const response = await waitForStream(source.result(), idle.signal);
|
|
163
|
+
if (!sawEvent)
|
|
164
|
+
wrapped.end(response);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
if (request?.signal?.aborted) {
|
|
169
|
+
const response = {
|
|
170
|
+
role: "assistant",
|
|
171
|
+
content: [],
|
|
172
|
+
api: model.api,
|
|
173
|
+
provider: model.provider,
|
|
174
|
+
model: model.id,
|
|
175
|
+
usage: emptyUsage(),
|
|
176
|
+
stopReason: "aborted",
|
|
177
|
+
errorMessage: "Auditor session aborted",
|
|
178
|
+
timestamp: Date.now(),
|
|
179
|
+
};
|
|
180
|
+
wrapped.push({ type: "error", reason: "aborted", error: response });
|
|
181
|
+
wrapped.end(response);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const failure = isStreamIdleTimeoutError(idle.signal.reason) ? idle.signal.reason : error;
|
|
185
|
+
if (options.idleRetry === true
|
|
186
|
+
&& isStreamIdleTimeoutError(failure)
|
|
187
|
+
&& attempt < DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES
|
|
188
|
+
&& options.signal?.aborted !== true) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
state.streamFailure = failure;
|
|
192
|
+
const response = {
|
|
193
|
+
role: "assistant",
|
|
194
|
+
content: [],
|
|
195
|
+
api: model.api,
|
|
196
|
+
provider: model.provider,
|
|
197
|
+
model: model.id,
|
|
198
|
+
usage: emptyUsage(),
|
|
199
|
+
stopReason: "error",
|
|
200
|
+
errorMessage: failure instanceof Error ? failure.message : String(failure),
|
|
201
|
+
timestamp: Date.now(),
|
|
202
|
+
};
|
|
203
|
+
wrapped.push({ type: "error", reason: "error", error: response });
|
|
204
|
+
wrapped.end(response);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
idle.dispose();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
})();
|
|
212
|
+
return wrapped;
|
|
213
|
+
};
|
|
214
|
+
const provider = options.idleRetry === true || options.runCompletion !== undefined
|
|
215
|
+
? {
|
|
216
|
+
id: parentProvider?.id ?? activeModel.provider,
|
|
217
|
+
name: parentProvider?.name ?? options.label,
|
|
218
|
+
auth: {
|
|
219
|
+
apiKey: {
|
|
220
|
+
name: `Inherited ${options.label} authentication`,
|
|
221
|
+
async resolve() {
|
|
222
|
+
const { env, ...auth } = dispatch.auth;
|
|
223
|
+
return {
|
|
224
|
+
auth: {
|
|
225
|
+
...auth,
|
|
226
|
+
...(dispatch.model.baseUrl === undefined ? {} : { baseUrl: dispatch.model.baseUrl }),
|
|
227
|
+
},
|
|
228
|
+
...(env === undefined ? {} : { env }),
|
|
229
|
+
};
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
getModels() { return [inheritedModel]; },
|
|
234
|
+
stream(model, context, request) {
|
|
235
|
+
return createRetriedStream(false, model, context, request);
|
|
236
|
+
},
|
|
237
|
+
streamSimple(model, context, request) {
|
|
238
|
+
return createRetriedStream(true, model, context, request);
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
: {
|
|
242
|
+
id: parentProvider.id,
|
|
243
|
+
name: parentProvider.name,
|
|
244
|
+
...(parentProvider.baseUrl === undefined ? {} : { baseUrl: parentProvider.baseUrl }),
|
|
245
|
+
...(parentProvider.headers === undefined ? {} : { headers: parentProvider.headers }),
|
|
246
|
+
auth: {
|
|
247
|
+
apiKey: {
|
|
248
|
+
name: `Inherited ${options.label} authentication`,
|
|
249
|
+
async resolve() {
|
|
250
|
+
return {
|
|
251
|
+
auth: {
|
|
252
|
+
...(dispatch.auth.apiKey === undefined ? {} : { apiKey: dispatch.auth.apiKey }),
|
|
253
|
+
...(dispatch.auth.headers === undefined ? {} : { headers: dispatch.auth.headers }),
|
|
254
|
+
...(dispatch.model.baseUrl === undefined ? {} : { baseUrl: dispatch.model.baseUrl }),
|
|
255
|
+
},
|
|
256
|
+
...(dispatch.auth.env === undefined ? {} : { env: dispatch.auth.env }),
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
getModels() { return [inheritedModel]; },
|
|
262
|
+
stream(model, childContext, streamOptions) {
|
|
263
|
+
return parentProvider.stream(model, childContext, streamOptions);
|
|
264
|
+
},
|
|
265
|
+
streamSimple(model, childContext, streamOptions) {
|
|
266
|
+
return parentProvider.streamSimple(model, childContext, streamOptions);
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
runtime.registerNativeProvider(provider);
|
|
270
|
+
return state;
|
|
271
|
+
}
|
|
272
|
+
function classifiedError(error, evidenceChildFailure) {
|
|
273
|
+
const diagnostic = typeof error === "object" && error !== null && typeof error.errorMessage === "string"
|
|
274
|
+
? error.errorMessage
|
|
275
|
+
: error === undefined ? "" : String(error);
|
|
276
|
+
const wrapped = error instanceof Error
|
|
277
|
+
? error
|
|
278
|
+
: Object.assign(new Error(diagnostic, { cause: error }), { evidenceChildOriginal: error });
|
|
279
|
+
const classification = "evidenceChildFailure" in wrapped
|
|
280
|
+
? wrapped.evidenceChildFailure
|
|
281
|
+
: evidenceChildFailure;
|
|
282
|
+
return Object.assign(wrapped, { evidenceChildFailure: classification });
|
|
283
|
+
}
|
|
284
|
+
function emptyUsage() {
|
|
285
|
+
return {
|
|
286
|
+
input: 0,
|
|
287
|
+
output: 0,
|
|
288
|
+
cacheRead: 0,
|
|
289
|
+
cacheWrite: 0,
|
|
290
|
+
totalTokens: 0,
|
|
291
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function addUsage(total, next) {
|
|
295
|
+
total.input += next.input;
|
|
296
|
+
total.output += next.output;
|
|
297
|
+
total.cacheRead += next.cacheRead;
|
|
298
|
+
total.cacheWrite += next.cacheWrite;
|
|
299
|
+
total.totalTokens += next.totalTokens;
|
|
300
|
+
total.cost.input += next.cost.input;
|
|
301
|
+
total.cost.output += next.cost.output;
|
|
302
|
+
total.cost.cacheRead += next.cost.cacheRead;
|
|
303
|
+
total.cost.cacheWrite += next.cost.cacheWrite;
|
|
304
|
+
total.cost.total += next.cost.total;
|
|
305
|
+
}
|
|
306
|
+
export async function executeEvidenceChild(workspace, prompt, context, options = {}) {
|
|
307
|
+
const signal = options.signal;
|
|
308
|
+
return withInProcessScratch({
|
|
309
|
+
prefix: "ak-evidence-child-",
|
|
310
|
+
...(options.credentialScratchParent === undefined
|
|
311
|
+
? {}
|
|
312
|
+
: { parentDirectory: options.credentialScratchParent }),
|
|
313
|
+
}, async (childConfigDir) => {
|
|
314
|
+
const { openInProcessAgentSession } = await import("./in-process-session.js");
|
|
315
|
+
const { childSessionManager } = await import("./activation-ledger-session.js");
|
|
316
|
+
let inherited;
|
|
317
|
+
try {
|
|
318
|
+
inherited = await createInheritedRuntime({
|
|
319
|
+
context,
|
|
320
|
+
label: "Evidence child",
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
throw classifiedError(error, "provider");
|
|
325
|
+
}
|
|
326
|
+
// No tools allowlist — Pi defaults + unrestricted evidence surface (ADR 0064).
|
|
327
|
+
// Single createAgentSession owner: in-process-session.ts.
|
|
328
|
+
const { session, dispose } = await openInProcessAgentSession({
|
|
329
|
+
cwd: workspace,
|
|
330
|
+
agentDir: childConfigDir,
|
|
331
|
+
model: inherited.model,
|
|
332
|
+
thinkingLevel: context.thinkingLevel ?? "off",
|
|
333
|
+
modelRuntime: inherited.runtime,
|
|
334
|
+
systemPrompt: [
|
|
335
|
+
"Work only in the supplied workspace.",
|
|
336
|
+
"Use the available evidence tools to investigate. Do not commit, push, or mutate remotes.",
|
|
337
|
+
"Return one substantive non-blank report.",
|
|
338
|
+
].join("\n"),
|
|
339
|
+
sessionManager: childSessionManager(context.sessionManager, workspace, "evidence-children"),
|
|
340
|
+
});
|
|
341
|
+
const usage = emptyUsage();
|
|
342
|
+
const unsubscribe = session.subscribe((event) => {
|
|
343
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
344
|
+
addUsage(usage, event.message.usage);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
const abortChild = () => { void session.abort(); };
|
|
348
|
+
if (signal?.aborted)
|
|
349
|
+
abortChild();
|
|
350
|
+
else
|
|
351
|
+
signal?.addEventListener("abort", abortChild, { once: true });
|
|
352
|
+
let primaryFailure;
|
|
353
|
+
try {
|
|
354
|
+
const delivered = prompt;
|
|
355
|
+
try {
|
|
356
|
+
await session.prompt(delivered);
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
throw classifiedError(error, "provider");
|
|
360
|
+
}
|
|
361
|
+
if (signal?.aborted)
|
|
362
|
+
throw new Error("Evidence child was cancelled");
|
|
363
|
+
const lastAssistant = [...session.messages]
|
|
364
|
+
.reverse()
|
|
365
|
+
.find((message) => message.role === "assistant");
|
|
366
|
+
if (lastAssistant?.role === "assistant" && lastAssistant.stopReason === "error") {
|
|
367
|
+
throw classifiedError(new Error(lastAssistant.errorMessage ?? "", { cause: lastAssistant }), "provider");
|
|
368
|
+
}
|
|
369
|
+
if (lastAssistant?.role !== "assistant" || lastAssistant.stopReason === "aborted") {
|
|
370
|
+
throw classifiedError(new Error("Evidence child child terminated without a report", {
|
|
371
|
+
cause: lastAssistant ?? session.messages,
|
|
372
|
+
}), "child");
|
|
373
|
+
}
|
|
374
|
+
const report = session.getLastAssistantText() ?? "";
|
|
375
|
+
if (report.trim().length === 0) {
|
|
376
|
+
throw new Error("Evidence child returned a blank child report");
|
|
377
|
+
}
|
|
378
|
+
return { report, usage, prompt: delivered };
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
primaryFailure = classifiedError(error, "child");
|
|
382
|
+
throw primaryFailure;
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
signal?.removeEventListener("abort", abortChild);
|
|
386
|
+
let cleanupFailure;
|
|
387
|
+
for (const cleanup of [() => unsubscribe(), () => dispose()]) {
|
|
388
|
+
try {
|
|
389
|
+
cleanup();
|
|
390
|
+
}
|
|
391
|
+
catch (failure) {
|
|
392
|
+
cleanupFailure = cleanupFailure === undefined
|
|
393
|
+
? failure
|
|
394
|
+
: new AggregateError([cleanupFailure, failure], "Reviewer child cleanup failed", {
|
|
395
|
+
cause: cleanupFailure,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (cleanupFailure !== undefined) {
|
|
400
|
+
if (primaryFailure !== undefined) {
|
|
401
|
+
throw new AggregateError([primaryFailure, cleanupFailure], "Reviewer child execution and cleanup failed", { cause: primaryFailure });
|
|
402
|
+
}
|
|
403
|
+
throw new AggregateError([cleanupFailure], "Reviewer child cleanup failed", {
|
|
404
|
+
cause: cleanupFailure,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Auditor lifecycle via the shared in-process helper.
|
|
412
|
+
* Adapter keeps role label / soul / decision tool / result projection only.
|
|
413
|
+
* No tools allowlist (ADR 0064). Provider-stream idle-only retry (ADR 0059).
|
|
414
|
+
* Durable child session via ADR 0065 sitian entry.
|
|
415
|
+
*/
|
|
416
|
+
export async function executeAuditorChild(options) {
|
|
417
|
+
const { createRecordSession } = await import("./sitian-record-entry.js");
|
|
418
|
+
return withInProcessScratch({ prefix: "ak-auditor-role-" }, async (scratch) => {
|
|
419
|
+
const inherited = await createInheritedRuntime({
|
|
420
|
+
context: options.context,
|
|
421
|
+
label: options.roleLabel,
|
|
422
|
+
idleRetry: true,
|
|
423
|
+
...(options.runCompletion === undefined
|
|
424
|
+
? {}
|
|
425
|
+
: { runCompletion: options.runCompletion, injectedSystemPrompt: options.systemPrompt }),
|
|
426
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
427
|
+
});
|
|
428
|
+
const cwd = options.context.cwd ?? process.cwd();
|
|
429
|
+
let decision;
|
|
430
|
+
let decisionSubmitted = false;
|
|
431
|
+
let decisionCallId;
|
|
432
|
+
let decisionToolFailure;
|
|
433
|
+
const tool = wrapPackageOwnedToolDefinition({
|
|
434
|
+
...options.tool,
|
|
435
|
+
label: options.roleLabel,
|
|
436
|
+
async execute(...args) {
|
|
437
|
+
if (decisionSubmitted && decisionCallId !== args[0]) {
|
|
438
|
+
throw new Error("Auditor decision was submitted more than once");
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const result = await options.tool.execute(...args);
|
|
442
|
+
decision = args[1];
|
|
443
|
+
decisionCallId = args[0];
|
|
444
|
+
decisionSubmitted = true;
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
decisionToolFailure = error;
|
|
449
|
+
throw error;
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
const parentSessionManager = options.context.sessionManager;
|
|
454
|
+
const parentHeader = parentSessionManager?.getHeader?.();
|
|
455
|
+
const parentSessionFile = parentSessionManager?.getSessionFile?.();
|
|
456
|
+
const parentAttemptEntryId = parentSessionManager?.getLeafId?.();
|
|
457
|
+
const auditorSessionManager = createRecordSession({
|
|
458
|
+
cwd,
|
|
459
|
+
kind: "auditor-roles",
|
|
460
|
+
...(parentSessionManager === undefined ? {} : { parent: parentSessionManager }),
|
|
461
|
+
});
|
|
462
|
+
// Shared session open — no tools allowlist (ADR 0064).
|
|
463
|
+
const { openInProcessAgentSession: openSession } = await import("./in-process-session.js");
|
|
464
|
+
const { session, dispose } = await openSession({
|
|
465
|
+
cwd,
|
|
466
|
+
agentDir: scratch,
|
|
467
|
+
model: inherited.model,
|
|
468
|
+
thinkingLevel: options.context.thinkingLevel ?? "off",
|
|
469
|
+
modelRuntime: inherited.runtime,
|
|
470
|
+
systemPrompt: options.systemPrompt,
|
|
471
|
+
customTools: [wrapPackageOwnedToolDefinition({ ...options.dossierTool, label: options.roleLabel }), tool],
|
|
472
|
+
sessionManager: auditorSessionManager,
|
|
473
|
+
});
|
|
474
|
+
const binding = {
|
|
475
|
+
version: 1,
|
|
476
|
+
parent: {
|
|
477
|
+
...(parentHeader?.id === undefined ? {} : { sessionId: parentHeader.id }),
|
|
478
|
+
...(parentSessionFile === undefined ? {} : { sessionFile: parentSessionFile }),
|
|
479
|
+
...(parentAttemptEntryId === null || parentAttemptEntryId === undefined
|
|
480
|
+
? {}
|
|
481
|
+
: { attemptEntryId: parentAttemptEntryId }),
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
// Durable binding is a prerequisite: never observe the provider when its
|
|
485
|
+
// response could not later be tied to the current parent attempt.
|
|
486
|
+
auditorSessionManager.appendCustomEntry(AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, binding);
|
|
487
|
+
let turns = 0;
|
|
488
|
+
let boundaryResponse;
|
|
489
|
+
let retentionFailure;
|
|
490
|
+
let retainedResponse;
|
|
491
|
+
const registeredToolNames = new Set(session.getAllTools().map((entry) => entry.name));
|
|
492
|
+
const evidenceToolFailures = new Map();
|
|
493
|
+
for (const name of registeredToolNames) {
|
|
494
|
+
if (name === tool.name)
|
|
495
|
+
continue;
|
|
496
|
+
const definition = session.getToolDefinition(name);
|
|
497
|
+
if (definition === undefined)
|
|
498
|
+
continue;
|
|
499
|
+
const execute = definition.execute.bind(definition);
|
|
500
|
+
definition.execute = async (...args) => {
|
|
501
|
+
try {
|
|
502
|
+
return await execute(...args);
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
evidenceToolFailures.set(args[0], error);
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
const findToolFailure = (response) => {
|
|
511
|
+
const callIds = response.content.flatMap((part) => part.type === "toolCall" && part.name !== tool.name && registeredToolNames.has(part.name) ? [part.id] : []);
|
|
512
|
+
for (const callId of callIds) {
|
|
513
|
+
if (evidenceToolFailures.has(callId))
|
|
514
|
+
return evidenceToolFailures.get(callId);
|
|
515
|
+
}
|
|
516
|
+
const callIdSet = new Set(callIds);
|
|
517
|
+
return [...session.messages].reverse().find((message) => message.role === "toolResult" && callIdSet.has(message.toolCallId) && message.isError);
|
|
518
|
+
};
|
|
519
|
+
const unsubscribe = session.subscribe((event) => {
|
|
520
|
+
if (event.type === "message_end" && event.message.role === "assistant" && boundaryResponse === undefined) {
|
|
521
|
+
turns += 1;
|
|
522
|
+
retainedResponse = event.message;
|
|
523
|
+
try {
|
|
524
|
+
options.retainResponse?.(event.message);
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
retentionFailure = error;
|
|
528
|
+
}
|
|
529
|
+
for (const part of event.message.content) {
|
|
530
|
+
if (part.type !== "toolCall" || part.name !== tool.name)
|
|
531
|
+
continue;
|
|
532
|
+
if (!decisionSubmitted) {
|
|
533
|
+
decision = part.arguments;
|
|
534
|
+
decisionCallId = part.id;
|
|
535
|
+
decisionSubmitted = true;
|
|
536
|
+
}
|
|
537
|
+
else if (decisionCallId !== part.id) {
|
|
538
|
+
decisionToolFailure = new Error("Auditor decision was submitted more than once");
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (turns >= AUDITOR_TURN_LIMIT)
|
|
542
|
+
boundaryResponse = event.message;
|
|
543
|
+
}
|
|
544
|
+
if (event.type === "turn_end" &&
|
|
545
|
+
(decisionSubmitted || boundaryResponse !== undefined || retentionFailure !== undefined)) {
|
|
546
|
+
void session.abort();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
const abort = () => { void session.abort(); };
|
|
550
|
+
if (options.signal?.aborted)
|
|
551
|
+
abort();
|
|
552
|
+
else
|
|
553
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
554
|
+
try {
|
|
555
|
+
try {
|
|
556
|
+
await session.prompt(options.prompt);
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
if (options.signal?.aborted)
|
|
560
|
+
throw options.signal.reason;
|
|
561
|
+
if (inherited.streamFailure !== undefined)
|
|
562
|
+
throw inherited.streamFailure;
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
if (options.signal?.aborted)
|
|
566
|
+
throw options.signal.reason;
|
|
567
|
+
if (inherited.streamFailure !== undefined)
|
|
568
|
+
throw inherited.streamFailure;
|
|
569
|
+
if (decisionToolFailure !== undefined)
|
|
570
|
+
throw decisionToolFailure;
|
|
571
|
+
const relevantResponse = !decisionSubmitted
|
|
572
|
+
? boundaryResponse
|
|
573
|
+
: [...session.messages].reverse().find((message) => message.role === "assistant" && message.content.some((part) => part.type === "toolCall" && part.name === tool.name));
|
|
574
|
+
if (relevantResponse !== undefined) {
|
|
575
|
+
const toolFailure = findToolFailure(relevantResponse);
|
|
576
|
+
if (toolFailure !== undefined)
|
|
577
|
+
throw toolFailure;
|
|
578
|
+
}
|
|
579
|
+
if (retentionFailure !== undefined && retainedResponse?.stopReason !== "error")
|
|
580
|
+
throw retentionFailure;
|
|
581
|
+
if (boundaryResponse !== undefined && !decisionSubmitted) {
|
|
582
|
+
const toolNames = boundaryResponse.content.flatMap((part) => part.type === "toolCall" ? [part.name] : []);
|
|
583
|
+
throw new AuditorTurnLimitError(AUDITOR_TURN_LIMIT, turns, {
|
|
584
|
+
stopReason: boundaryResponse.stopReason,
|
|
585
|
+
toolNames,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
const assistants = [...session.messages]
|
|
589
|
+
.reverse()
|
|
590
|
+
.filter((message) => message.role === "assistant");
|
|
591
|
+
const response = !decisionSubmitted
|
|
592
|
+
? assistants[0]
|
|
593
|
+
: assistants.find((message) => message.content.some((part) => part.type === "toolCall" && part.name === tool.name));
|
|
594
|
+
if (response !== undefined) {
|
|
595
|
+
try {
|
|
596
|
+
if (retainedResponse === undefined)
|
|
597
|
+
options.retainResponse?.(response);
|
|
598
|
+
else if (retentionFailure !== undefined)
|
|
599
|
+
throw retentionFailure;
|
|
600
|
+
}
|
|
601
|
+
catch (retentionFailure) {
|
|
602
|
+
if (response.stopReason !== "error")
|
|
603
|
+
throw retentionFailure;
|
|
604
|
+
const failure = new Error(response.errorMessage?.trim() || "provider failure", { cause: retentionFailure });
|
|
605
|
+
failure.name = response.model || response.provider || "Error";
|
|
606
|
+
failure.knownCause = "provider";
|
|
607
|
+
failure.failureCode = response.provider || response.model;
|
|
608
|
+
const retentionError = retentionFailure instanceof Error ? retentionFailure : undefined;
|
|
609
|
+
const retentionCause = retentionError?.cause;
|
|
610
|
+
failure.details = {
|
|
611
|
+
...(response.provider ? { provider: response.provider } : {}),
|
|
612
|
+
...(response.model ? { model: response.model } : {}),
|
|
613
|
+
retentionFailure: {
|
|
614
|
+
name: retentionError?.name ?? typeof retentionFailure,
|
|
615
|
+
message: retentionError?.message ?? String(retentionFailure),
|
|
616
|
+
...(retentionError?.code !== undefined
|
|
617
|
+
? { code: retentionError.code }
|
|
618
|
+
: {}),
|
|
619
|
+
...(retentionCause === undefined
|
|
620
|
+
? {}
|
|
621
|
+
: {
|
|
622
|
+
cause: retentionCause instanceof Error
|
|
623
|
+
? {
|
|
624
|
+
name: retentionCause.name,
|
|
625
|
+
message: retentionCause.message,
|
|
626
|
+
...(retentionCause.code === undefined
|
|
627
|
+
? {}
|
|
628
|
+
: { code: retentionCause.code }),
|
|
629
|
+
}
|
|
630
|
+
: retentionCause,
|
|
631
|
+
}),
|
|
632
|
+
},
|
|
633
|
+
};
|
|
634
|
+
auditorSessionManager.appendCustomEntry(AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, {
|
|
635
|
+
version: 1,
|
|
636
|
+
parent: binding.parent,
|
|
637
|
+
failure: {
|
|
638
|
+
cause: failure.knownCause,
|
|
639
|
+
identity: { name: failure.name, code: failure.failureCode },
|
|
640
|
+
diagnostic: failure.message,
|
|
641
|
+
details: failure.details,
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
throw failure;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (response === undefined
|
|
648
|
+
|| response.stopReason === "error"
|
|
649
|
+
|| response.stopReason === "aborted"
|
|
650
|
+
|| !decisionSubmitted) {
|
|
651
|
+
throw new Error(`${options.roleLabel} exited without a readable decision receipt`);
|
|
652
|
+
}
|
|
653
|
+
return { decision, response };
|
|
654
|
+
}
|
|
655
|
+
finally {
|
|
656
|
+
options.signal?.removeEventListener("abort", abort);
|
|
657
|
+
unsubscribe();
|
|
658
|
+
dispose();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
2
|
+
/** Decode only byte sequences whose decoded text reproduces the exact input bytes. */
|
|
3
|
+
export function exactUtf8(bytes, label) {
|
|
4
|
+
let text;
|
|
5
|
+
try {
|
|
6
|
+
text = decoder.decode(bytes);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
throw new Error(`${label} is not valid UTF-8`, { cause: error });
|
|
10
|
+
}
|
|
11
|
+
return text;
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const FULL_GIT_OBJECT_ID_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
2
|
+
export function isFullGitObjectId(value) {
|
|
3
|
+
return typeof value === "string" && FULL_GIT_OBJECT_ID_RE.test(value);
|
|
4
|
+
}
|
|
5
|
+
export function gitObjectIdWidth(format) {
|
|
6
|
+
return format === "sha1" ? 40 : 64;
|
|
7
|
+
}
|