@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.
Files changed (192) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +104 -0
  3. package/README.zh-CN.md +133 -0
  4. package/THIRD_PARTY_NOTICES.md +60 -0
  5. package/dist/activation-ledger-git.js +68 -0
  6. package/dist/activation-ledger-session.js +120 -0
  7. package/dist/activation-ledger-topology.js +239 -0
  8. package/dist/activation-reconciliation.js +61 -0
  9. package/dist/audit-escalation.js +108 -0
  10. package/dist/auditor-dossier-tool.js +35 -0
  11. package/dist/canonical-json.js +78 -0
  12. package/dist/compliance-transport.js +77 -0
  13. package/dist/doctor-contracts.js +172 -0
  14. package/dist/dossier-resolution.js +103 -0
  15. package/dist/evidence-child-executor.js +661 -0
  16. package/dist/exact-utf8.js +12 -0
  17. package/dist/git-object-id.js +7 -0
  18. package/dist/in-process-session.js +50 -0
  19. package/dist/merger-contracts.js +76 -0
  20. package/dist/navigator-attendance.js +995 -0
  21. package/dist/navigator-invocation-identity.js +220 -0
  22. package/dist/open-tool-schema.js +39 -0
  23. package/dist/package-contracts/collector-output.js +50 -0
  24. package/dist/package-contracts/fixer-output.js +72 -0
  25. package/dist/package-contracts/fixer-packet.js +77 -0
  26. package/dist/package-contracts/judge-output.js +17 -0
  27. package/dist/package-contracts/reviewer-output.js +82 -0
  28. package/dist/package-contracts/terminating-tools.js +173 -0
  29. package/dist/package-contracts/worker-output.js +13 -0
  30. package/dist/package-owned-tool-idle.js +104 -0
  31. package/dist/packaged-role-registry.js +34 -0
  32. package/dist/public-cli/main.js +23867 -0
  33. package/dist/public-command-renderer.js +20 -0
  34. package/dist/reviewer-agent.js +93 -0
  35. package/dist/reviewer-child-executor.js +23 -0
  36. package/dist/reviewer-construction.js +95 -0
  37. package/dist/reviewer-dispatch.js +77 -0
  38. package/dist/reviewer-execution-ledger.js +160 -0
  39. package/dist/reviewer-failure-diagnostic.js +17 -0
  40. package/dist/reviewer-git-snapshot.js +38 -0
  41. package/dist/reviewer-pinned-git.js +146 -0
  42. package/dist/reviewer-preflight-error.js +15 -0
  43. package/dist/reviewer-prompt-identity.js +10 -0
  44. package/dist/reviewer-scope-prompt.js +21 -0
  45. package/dist/reviewer-workspace.js +151 -0
  46. package/dist/sha256.js +5 -0
  47. package/dist/sitian-record-entry.js +33 -0
  48. package/dist/stderr-jsonl.js +26 -0
  49. package/dist/stream-idle-guard.js +75 -0
  50. package/dist/tool-execution-observation.js +141 -0
  51. package/dist/uuidv7.js +21 -0
  52. package/dist/work-subject-identity.js +53 -0
  53. package/extensions/role-runtime.ts +303 -0
  54. package/package.json +69 -0
  55. package/packets/fixer-prerequisites.json +6 -0
  56. package/packets/fixer-repair.md +5 -0
  57. package/packets/judge-apply.md +77 -0
  58. package/packets/judge-authority.md +64 -0
  59. package/packets/judge-plan.md +55 -0
  60. package/packets/judge-review.md +49 -0
  61. package/packets/judge-submission.md +34 -0
  62. package/resources/methods/code-review/SKILL.md +92 -0
  63. package/resources/methods/code-review/agents/openai.yaml +3 -0
  64. package/resources/methods/code-review/provenance.json +26 -0
  65. package/resources/methods/diagnosing-bugs/SKILL.md +134 -0
  66. package/resources/methods/diagnosing-bugs/agents/openai.yaml +3 -0
  67. package/resources/methods/diagnosing-bugs/provenance.json +31 -0
  68. package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  69. package/resources/methods/resolving-merge-conflicts/SKILL.md +14 -0
  70. package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +3 -0
  71. package/resources/methods/resolving-merge-conflicts/provenance.json +26 -0
  72. package/resources/methods/tdd/SKILL.md +38 -0
  73. package/resources/methods/tdd/agents/openai.yaml +3 -0
  74. package/resources/methods/tdd/mocking.md +59 -0
  75. package/resources/methods/tdd/provenance.json +36 -0
  76. package/resources/methods/tdd/tests.md +77 -0
  77. package/resources/navigator-route-playbook.md +32 -0
  78. package/schemas/tool-execution-observation.schema.json +107 -0
  79. package/scripts/build-package.mjs +65 -0
  80. package/scripts/generate-tool-execution-observation-schema.ts +7 -0
  81. package/souls/coder.md +10 -0
  82. package/souls/collector.md +11 -0
  83. package/souls/doctor-auditor.md +23 -0
  84. package/souls/doctor.md +8 -0
  85. package/souls/fixer-auditor.md +33 -0
  86. package/souls/fixer.md +13 -0
  87. package/souls/judge-auditor.md +33 -0
  88. package/souls/judge.md +74 -0
  89. package/souls/merger.md +5 -0
  90. package/souls/navigator.md +5 -0
  91. package/souls/reviewer-auditor.md +25 -0
  92. package/souls/reviewer.md +11 -0
  93. package/src/activation-ledger-git.ts +96 -0
  94. package/src/activation-ledger-session.ts +188 -0
  95. package/src/activation-ledger-topology.ts +301 -0
  96. package/src/activation-ledger.ts +240 -0
  97. package/src/activation-reconciliation.ts +163 -0
  98. package/src/activation-trace.ts +38 -0
  99. package/src/audit-escalation.ts +177 -0
  100. package/src/auditor-dossier-tool.ts +48 -0
  101. package/src/auditor-soul.ts +28 -0
  102. package/src/canonical-json.ts +74 -0
  103. package/src/canonical-skill-binding.ts +107 -0
  104. package/src/collector-config.ts +89 -0
  105. package/src/collector-evidence.ts +461 -0
  106. package/src/collector-github.ts +656 -0
  107. package/src/collector-identity.ts +161 -0
  108. package/src/collector-ledger.ts +827 -0
  109. package/src/collector-receipt.ts +87 -0
  110. package/src/collector-role.ts +592 -0
  111. package/src/collector-tool-schemas.ts +19 -0
  112. package/src/compliance-transport.ts +130 -0
  113. package/src/doctor-auditor.ts +53 -0
  114. package/src/doctor-contracts.ts +166 -0
  115. package/src/doctor-evidence.ts +47 -0
  116. package/src/doctor-role.ts +18 -0
  117. package/src/dossier-resolution.ts +137 -0
  118. package/src/evidence-child-executor.ts +775 -0
  119. package/src/exact-utf8.ts +9 -0
  120. package/src/factory-board.ts +1822 -0
  121. package/src/git-object-id.ts +11 -0
  122. package/src/human-format.ts +65 -0
  123. package/src/in-process-session.ts +78 -0
  124. package/src/judge-auditor.ts +55 -0
  125. package/src/judge-recording-anti-forge.ts +53 -0
  126. package/src/judge-role.ts +160 -0
  127. package/src/merger-contracts.ts +71 -0
  128. package/src/merger-git-state.ts +76 -0
  129. package/src/merger-role.ts +60 -0
  130. package/src/navigator-attendance.ts +1254 -0
  131. package/src/navigator-invocation-identity.ts +446 -0
  132. package/src/open-tool-schema.ts +46 -0
  133. package/src/package-contracts/collector-output.ts +109 -0
  134. package/src/package-contracts/fixer-output.ts +81 -0
  135. package/src/package-contracts/fixer-packet.ts +93 -0
  136. package/src/package-contracts/judge-output.ts +39 -0
  137. package/src/package-contracts/reviewer-output.ts +115 -0
  138. package/src/package-contracts/terminating-tools.ts +259 -0
  139. package/src/package-contracts/worker-output.ts +36 -0
  140. package/src/package-owned-tool-idle.ts +134 -0
  141. package/src/package-resources/method-skill-binding.ts +87 -0
  142. package/src/package-resources/method-skill.ts +358 -0
  143. package/src/packaged-role-registry.ts +36 -0
  144. package/src/public-cli/cli-errors.ts +11 -0
  145. package/src/public-cli/cli-io.ts +4 -0
  146. package/src/public-cli/cli.ts +912 -0
  147. package/src/public-cli/coder-run.ts +575 -0
  148. package/src/public-cli/collector-run.ts +375 -0
  149. package/src/public-cli/command-renderer.ts +8 -0
  150. package/src/public-cli/config.ts +346 -0
  151. package/src/public-cli/doctor-run.ts +355 -0
  152. package/src/public-cli/explicit-internal.ts +274 -0
  153. package/src/public-cli/fixer-run.ts +587 -0
  154. package/src/public-cli/host-pi-runtime.ts +112 -0
  155. package/src/public-cli/invocation.ts +1958 -0
  156. package/src/public-cli/judge-run.ts +507 -0
  157. package/src/public-cli/main.ts +15 -0
  158. package/src/public-cli/merger-run.ts +681 -0
  159. package/src/public-cli/public-run-credentials.ts +71 -0
  160. package/src/public-cli/registry.ts +153 -0
  161. package/src/public-cli/reviewer-run.ts +561 -0
  162. package/src/public-cli/run-lifecycle.ts +884 -0
  163. package/src/public-cli/settlement.ts +3765 -0
  164. package/src/public-cli/terminal.ts +325 -0
  165. package/src/public-command-renderer.ts +43 -0
  166. package/src/reviewer-agent.ts +94 -0
  167. package/src/reviewer-auditor.ts +53 -0
  168. package/src/reviewer-child-executor.ts +31 -0
  169. package/src/reviewer-construction.ts +137 -0
  170. package/src/reviewer-dispatch.ts +94 -0
  171. package/src/reviewer-execution-ledger.ts +206 -0
  172. package/src/reviewer-failure-diagnostic.ts +18 -0
  173. package/src/reviewer-git-snapshot.ts +53 -0
  174. package/src/reviewer-pinned-git.ts +144 -0
  175. package/src/reviewer-preflight-error.ts +14 -0
  176. package/src/reviewer-prompt-identity.ts +17 -0
  177. package/src/reviewer-role.ts +193 -0
  178. package/src/reviewer-scope-prompt.ts +24 -0
  179. package/src/reviewer-settlement.ts +63 -0
  180. package/src/reviewer-workspace.ts +111 -0
  181. package/src/role-runtime.ts +884 -0
  182. package/src/sha256.ts +6 -0
  183. package/src/sitian-record-entry.ts +57 -0
  184. package/src/stderr-jsonl.ts +28 -0
  185. package/src/stream-idle-guard.ts +98 -0
  186. package/src/ticket-snapshot.ts +662 -0
  187. package/src/ticket-trajectory.ts +1000 -0
  188. package/src/tool-execution-observation.ts +168 -0
  189. package/src/uuidv7.ts +1 -0
  190. package/src/work-subject-identity.ts +94 -0
  191. package/src/worker-role.ts +434 -0
  192. 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
+ }