@akagilnc/pi-workflow-roles 0.1.1878 → 0.1.1893
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activation-ledger-session.js +99 -107
- package/dist/evidence-child-executor.js +153 -16
- package/dist/navigator-attendance.js +64 -29
- package/dist/public-cli/main.js +418 -204
- package/dist/reviewer-child-executor.js +1 -1
- package/dist/sitian-record-entry.js +45 -3
- package/dist/typed-provider-http.js +60 -0
- package/dist/upstream-error-testimony.js +19 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -0
- package/src/activation-ledger-session.ts +18 -42
- package/src/evidence-child-executor.ts +177 -24
- package/src/navigator-attendance.ts +86 -33
- package/src/public-cli/coder-run.ts +25 -7
- package/src/public-cli/collector-run.ts +1 -0
- package/src/public-cli/doctor-run.ts +1 -0
- package/src/public-cli/explicit-internal.ts +61 -19
- package/src/public-cli/fixer-run.ts +25 -7
- package/src/public-cli/judge-run.ts +28 -7
- package/src/public-cli/merger-run.ts +25 -7
- package/src/public-cli/reviewer-run.ts +24 -7
- package/src/public-cli/run-lifecycle.ts +14 -76
- package/src/public-cli/settlement.ts +256 -66
- package/src/reviewer-child-executor.ts +1 -1
- package/src/role-runtime.ts +19 -9
- package/src/sitian-record-entry.ts +75 -3
- package/src/typed-provider-http.ts +99 -0
- package/src/upstream-error-testimony.ts +45 -0
|
@@ -1,113 +1,105 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import {
|
|
2
|
+
lstatSync,
|
|
3
|
+
realpathSync,
|
|
4
|
+
statSync,
|
|
5
|
+
writeFileSync
|
|
6
|
+
} from "node:fs";
|
|
7
|
+
import { isAbsolute, resolve } from "node:path";
|
|
8
|
+
import {
|
|
9
|
+
errnoCode,
|
|
10
|
+
errorText
|
|
11
|
+
} from "./activation-ledger-topology.js";
|
|
12
|
+
class ActivationSessionFileMissingError extends Error {
|
|
13
|
+
code = "AK_ACTIVATION_SESSION_FILE_MISSING";
|
|
14
|
+
path;
|
|
15
|
+
constructor(path, options) {
|
|
16
|
+
super(
|
|
17
|
+
`Workflow role activation durable session file does not exist: ${path}`,
|
|
18
|
+
options?.cause === void 0 ? void 0 : { cause: options.cause }
|
|
19
|
+
);
|
|
20
|
+
this.name = "ActivationSessionFileMissingError";
|
|
21
|
+
this.path = path;
|
|
22
|
+
}
|
|
16
23
|
}
|
|
17
|
-
function materializeDeferredSessionFile(sessionManager, resolvedFile
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
24
|
+
function materializeDeferredSessionFile(sessionManager, resolvedFile) {
|
|
25
|
+
const header = sessionManager.getHeader?.();
|
|
26
|
+
if (header === null || header === void 0 || header.type !== "session") {
|
|
27
|
+
throw new ActivationSessionFileMissingError(resolvedFile);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
writeFileSync(resolvedFile, `${JSON.stringify(header)}
|
|
31
|
+
`, { flag: "wx" });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (errnoCode(error) !== "EEXIST") {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Workflow role activation failed to materialize durable session file (${resolvedFile}): ${errorText(error)}`,
|
|
36
|
+
{ cause: error }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (typeof sessionManager.setSessionFile === "function") {
|
|
41
|
+
sessionManager.setSessionFile(resolvedFile);
|
|
42
|
+
}
|
|
37
43
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const bookRoot = resolve(activationBookDirectory(options.ledgerHome, options.bookKey));
|
|
60
|
-
if (!pathContainedIn(bookRoot, resolvedFile)) {
|
|
61
|
-
throw new Error(`Workflow role activation requires the durable session file principal under the machine ledger book (${bookRoot}); got: ${resolvedFile}`);
|
|
62
|
-
}
|
|
63
|
-
try {
|
|
64
|
-
lstatSync(resolvedFile);
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
if (errnoCode(error) !== "ENOENT") {
|
|
68
|
-
throw new Error(`Workflow role activation failed to stat durable session file (${resolvedFile}): ${errorText(error)}`, { cause: error });
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
materializeDeferredSessionFile(sessionManager, resolvedFile, options.ledgerHome);
|
|
72
|
-
}
|
|
73
|
-
catch (materializeError) {
|
|
74
|
-
// Missing principal: rethrow typed missing-file identity with the original ENOENT cause.
|
|
75
|
-
// Other materialize failures keep their own typed/native identity.
|
|
76
|
-
if (materializeError instanceof ActivationSessionFileMissingError) {
|
|
77
|
-
throw new ActivationSessionFileMissingError(resolvedFile, { cause: error });
|
|
78
|
-
}
|
|
79
|
-
throw materializeError;
|
|
80
|
-
}
|
|
44
|
+
function durableSessionPointer(sessionManager) {
|
|
45
|
+
const file = sessionManager.getSessionFile?.();
|
|
46
|
+
if (typeof file !== "string" || file.length === 0) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"Workflow role activation requires a durable Pi session file principal (getSessionFile); directory-only or --no-session invocations are rejected"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (!isAbsolute(file)) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Workflow role activation requires an absolute durable session file path; got relative path: ${file}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const resolvedFile = resolve(file);
|
|
57
|
+
try {
|
|
58
|
+
lstatSync(resolvedFile);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (errnoCode(error) !== "ENOENT") {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Workflow role activation failed to stat durable session file (${resolvedFile}): ${errorText(error)}`,
|
|
63
|
+
{ cause: error }
|
|
64
|
+
);
|
|
81
65
|
}
|
|
82
|
-
let realBook;
|
|
83
66
|
try {
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
throw new Error(`Workflow role activation machine ledger book is not resolvable (${bookRoot}): ${errorText(error)}`, { cause: error });
|
|
88
|
-
}
|
|
89
|
-
let realFile;
|
|
90
|
-
try {
|
|
91
|
-
realFile = realpathSync(resolvedFile);
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
67
|
+
materializeDeferredSessionFile(sessionManager, resolvedFile);
|
|
68
|
+
} catch (materializeError) {
|
|
69
|
+
if (materializeError instanceof ActivationSessionFileMissingError) {
|
|
94
70
|
throw new ActivationSessionFileMissingError(resolvedFile, { cause: error });
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
}
|
|
72
|
+
throw materializeError;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
let realFile;
|
|
76
|
+
try {
|
|
77
|
+
realFile = realpathSync(resolvedFile);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new ActivationSessionFileMissingError(resolvedFile, { cause: error });
|
|
80
|
+
}
|
|
81
|
+
let info;
|
|
82
|
+
try {
|
|
83
|
+
info = statSync(realFile);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Workflow role activation failed to stat durable session file (${realFile}): ${errorText(error)}`,
|
|
87
|
+
{ cause: error }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (info.isDirectory()) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Workflow role activation durable session principal must be a file, not a directory: ${realFile}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (!info.isFile()) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Workflow role activation durable session principal is not a regular file: ${realFile}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return { kind: "session-file", path: realFile };
|
|
113
101
|
}
|
|
102
|
+
export {
|
|
103
|
+
ActivationSessionFileMissingError,
|
|
104
|
+
durableSessionPointer
|
|
105
|
+
};
|
|
@@ -11,6 +11,7 @@ import { AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, AUDITOR_PARENT_ATTEMPT_BINDING_E
|
|
|
11
11
|
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.js";
|
|
12
12
|
import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.js";
|
|
13
13
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.js";
|
|
14
|
+
import { hasUpstreamErrorTestimony, isNonSuccessHttpStatus, projectConfirmedRemotePayload, } from "./upstream-error-testimony.js";
|
|
14
15
|
// ── shared constants / types ──────────────────────────────────────────────
|
|
15
16
|
export const AUDITOR_TURN_LIMIT = 32;
|
|
16
17
|
export const DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES = 2;
|
|
@@ -116,15 +117,23 @@ export async function createInheritedRuntime(options) {
|
|
|
116
117
|
void (async () => {
|
|
117
118
|
for (let attempt = 0;; attempt += 1) {
|
|
118
119
|
const idle = createStreamIdleGuard(options.signal === undefined ? {} : { parentSignal: options.signal });
|
|
120
|
+
// Typed HTTP status observed via onResponse — never inferred from prose.
|
|
121
|
+
let observedHttpStatus;
|
|
119
122
|
try {
|
|
120
123
|
const requestSignal = request?.signal;
|
|
121
124
|
const streamSignal = requestSignal === undefined
|
|
122
125
|
? idle.signal
|
|
123
126
|
: AbortSignal.any([idle.signal, requestSignal]);
|
|
127
|
+
const priorOnResponse = request?.onResponse;
|
|
124
128
|
const inheritedRequest = {
|
|
125
129
|
...(request ?? {}),
|
|
126
130
|
...(dispatch.auth.env === undefined ? {} : { env: dispatch.auth.env }),
|
|
127
131
|
signal: streamSignal,
|
|
132
|
+
onResponse: async (response, model) => {
|
|
133
|
+
if (typeof response?.status === "number")
|
|
134
|
+
observedHttpStatus = response.status;
|
|
135
|
+
await priorOnResponse?.(response, model);
|
|
136
|
+
},
|
|
128
137
|
};
|
|
129
138
|
if (options.runCompletion !== undefined) {
|
|
130
139
|
await new Promise((resolve) => setImmediate(resolve));
|
|
@@ -133,12 +142,12 @@ export async function createInheritedRuntime(options) {
|
|
|
133
142
|
const completed = await waitForStream(options.runCompletion(model, options.injectedSystemPrompt === undefined
|
|
134
143
|
? context
|
|
135
144
|
: { ...context, systemPrompt: options.injectedSystemPrompt }, inheritedRequest), streamSignal);
|
|
136
|
-
const response = {
|
|
145
|
+
const response = attachObservedHttpStatus({
|
|
137
146
|
...completed,
|
|
138
147
|
api: model.api,
|
|
139
148
|
provider: model.provider,
|
|
140
149
|
model: model.id,
|
|
141
|
-
};
|
|
150
|
+
}, observedHttpStatus);
|
|
142
151
|
if (response.stopReason === "error" || response.stopReason === "aborted") {
|
|
143
152
|
wrapped.push({ type: "error", reason: response.stopReason, error: response });
|
|
144
153
|
}
|
|
@@ -158,9 +167,9 @@ export async function createInheritedRuntime(options) {
|
|
|
158
167
|
break;
|
|
159
168
|
sawEvent = true;
|
|
160
169
|
idle.poke();
|
|
161
|
-
wrapped.push(next.value);
|
|
170
|
+
wrapped.push(enrichStreamEvent(next.value, observedHttpStatus));
|
|
162
171
|
}
|
|
163
|
-
const response = await waitForStream(source.result(), idle.signal);
|
|
172
|
+
const response = attachObservedHttpStatus(await waitForStream(source.result(), idle.signal), observedHttpStatus);
|
|
164
173
|
if (!sawEvent)
|
|
165
174
|
wrapped.end(response);
|
|
166
175
|
return;
|
|
@@ -190,6 +199,9 @@ export async function createInheritedRuntime(options) {
|
|
|
190
199
|
continue;
|
|
191
200
|
}
|
|
192
201
|
state.streamFailure = failure;
|
|
202
|
+
const projected = projectStructuredRemote(failure);
|
|
203
|
+
// Prefer structured fields on the thrown failure; fall back to onResponse observation.
|
|
204
|
+
const httpStatus = projected.httpStatus ?? numericHttpStatus(observedHttpStatus);
|
|
193
205
|
const response = {
|
|
194
206
|
role: "assistant",
|
|
195
207
|
content: [],
|
|
@@ -198,8 +210,14 @@ export async function createInheritedRuntime(options) {
|
|
|
198
210
|
model: model.id,
|
|
199
211
|
usage: emptyUsage(),
|
|
200
212
|
stopReason: "error",
|
|
213
|
+
// Preserve the held failure message bytes — do not rewrite.
|
|
201
214
|
errorMessage: failure instanceof Error ? failure.message : String(failure),
|
|
202
215
|
timestamp: Date.now(),
|
|
216
|
+
...(projected.diagnostics === undefined ? {} : { diagnostics: projected.diagnostics }),
|
|
217
|
+
...(httpStatus === undefined ? {} : { status: httpStatus, statusCode: httpStatus }),
|
|
218
|
+
...(projected.body === undefined ? {} : { body: projected.body }),
|
|
219
|
+
...(projected.code === undefined ? {} : { code: projected.code }),
|
|
220
|
+
...(projected.errno === undefined ? {} : { errno: projected.errno }),
|
|
203
221
|
};
|
|
204
222
|
wrapped.push({ type: "error", reason: "error", error: response });
|
|
205
223
|
wrapped.end(response);
|
|
@@ -270,6 +288,104 @@ export async function createInheritedRuntime(options) {
|
|
|
270
288
|
runtime.registerNativeProvider(provider);
|
|
271
289
|
return state;
|
|
272
290
|
}
|
|
291
|
+
function numericHttpStatus(value) {
|
|
292
|
+
return isNonSuccessHttpStatus(value) ? value : undefined;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Cause-chain reader over the shared upstream-testimony authority.
|
|
296
|
+
* Shape walking stays here; testimony + confirmed-remote payload rules are shared.
|
|
297
|
+
*/
|
|
298
|
+
function projectStructuredRemote(error) {
|
|
299
|
+
let httpStatus;
|
|
300
|
+
let diagnostics;
|
|
301
|
+
let body;
|
|
302
|
+
let code;
|
|
303
|
+
let errno;
|
|
304
|
+
let cursor = error;
|
|
305
|
+
const seen = new Set();
|
|
306
|
+
while (typeof cursor === "object" && cursor !== null && !seen.has(cursor)) {
|
|
307
|
+
seen.add(cursor);
|
|
308
|
+
const record = cursor;
|
|
309
|
+
const nodeStatus = numericHttpStatus(record.statusCode)
|
|
310
|
+
?? numericHttpStatus(record.status)
|
|
311
|
+
?? numericHttpStatus(record.httpStatus);
|
|
312
|
+
const nodeDiagnostics = Array.isArray(record.diagnostics) && record.diagnostics.length > 0
|
|
313
|
+
? record.diagnostics
|
|
314
|
+
: undefined;
|
|
315
|
+
const nodeHasTestimony = hasUpstreamErrorTestimony({
|
|
316
|
+
...(nodeStatus === undefined ? {} : { httpStatus: nodeStatus }),
|
|
317
|
+
...(nodeDiagnostics === undefined ? {} : { diagnostics: nodeDiagnostics }),
|
|
318
|
+
});
|
|
319
|
+
if (httpStatus === undefined && nodeStatus !== undefined)
|
|
320
|
+
httpStatus = nodeStatus;
|
|
321
|
+
if (diagnostics === undefined && nodeDiagnostics !== undefined)
|
|
322
|
+
diagnostics = nodeDiagnostics;
|
|
323
|
+
// Payload only from confirmed-remote nodes — never arbitrary local Error.code.
|
|
324
|
+
if (nodeHasTestimony) {
|
|
325
|
+
const payload = projectConfirmedRemotePayload(record);
|
|
326
|
+
if (body === undefined && payload.body !== undefined)
|
|
327
|
+
body = payload.body;
|
|
328
|
+
if (code === undefined && payload.code !== undefined)
|
|
329
|
+
code = payload.code;
|
|
330
|
+
if (errno === undefined && payload.errno !== undefined)
|
|
331
|
+
errno = payload.errno;
|
|
332
|
+
}
|
|
333
|
+
cursor = record.cause;
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
hasTestimony: hasUpstreamErrorTestimony({
|
|
337
|
+
...(httpStatus === undefined ? {} : { httpStatus }),
|
|
338
|
+
...(diagnostics === undefined ? {} : { diagnostics }),
|
|
339
|
+
}),
|
|
340
|
+
...(httpStatus === undefined ? {} : { httpStatus }),
|
|
341
|
+
...(diagnostics === undefined ? {} : { diagnostics }),
|
|
342
|
+
...(body === undefined ? {} : { body }),
|
|
343
|
+
...(code === undefined ? {} : { code }),
|
|
344
|
+
...(errno === undefined ? {} : { errno }),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Attach a directly observed HTTP status onto an error/aborted assistant message.
|
|
349
|
+
* Does not invent status from errorMessage prose; skips when already held.
|
|
350
|
+
*/
|
|
351
|
+
function attachObservedHttpStatus(message, observedHttpStatus) {
|
|
352
|
+
if (observedHttpStatus === undefined)
|
|
353
|
+
return message;
|
|
354
|
+
if (message.stopReason !== "error" && message.stopReason !== "aborted")
|
|
355
|
+
return message;
|
|
356
|
+
if (numericHttpStatus(observedHttpStatus) === undefined)
|
|
357
|
+
return message;
|
|
358
|
+
if (projectStructuredRemote(message).httpStatus !== undefined)
|
|
359
|
+
return message;
|
|
360
|
+
return Object.assign(message, {
|
|
361
|
+
status: observedHttpStatus,
|
|
362
|
+
statusCode: observedHttpStatus,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function enrichStreamEvent(event, observedHttpStatus) {
|
|
366
|
+
if (observedHttpStatus === undefined || event === null || typeof event !== "object")
|
|
367
|
+
return event;
|
|
368
|
+
const record = event;
|
|
369
|
+
if (record.type === "error" && record.error !== null && typeof record.error === "object") {
|
|
370
|
+
return {
|
|
371
|
+
...record,
|
|
372
|
+
error: attachObservedHttpStatus(record.error, observedHttpStatus),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (record.type === "done" && record.message !== null && typeof record.message === "object") {
|
|
376
|
+
return {
|
|
377
|
+
...record,
|
|
378
|
+
message: attachObservedHttpStatus(record.message, observedHttpStatus),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
if (record.partial !== null && typeof record.partial === "object") {
|
|
382
|
+
return {
|
|
383
|
+
...record,
|
|
384
|
+
partial: attachObservedHttpStatus(record.partial, observedHttpStatus),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return event;
|
|
388
|
+
}
|
|
273
389
|
function classifiedError(error, evidenceChildFailure) {
|
|
274
390
|
const diagnostic = typeof error === "object" && error !== null && typeof error.errorMessage === "string"
|
|
275
391
|
? error.errorMessage
|
|
@@ -279,7 +395,9 @@ function classifiedError(error, evidenceChildFailure) {
|
|
|
279
395
|
: Object.assign(new Error(diagnostic, { cause: error }), { evidenceChildOriginal: error });
|
|
280
396
|
const classification = "evidenceChildFailure" in wrapped
|
|
281
397
|
? wrapped.evidenceChildFailure
|
|
282
|
-
: evidenceChildFailure
|
|
398
|
+
: evidenceChildFailure === "provider" && !projectStructuredRemote(error).hasTestimony
|
|
399
|
+
? "unknown"
|
|
400
|
+
: evidenceChildFailure;
|
|
283
401
|
return Object.assign(wrapped, { evidenceChildFailure: classification });
|
|
284
402
|
}
|
|
285
403
|
function emptyUsage() {
|
|
@@ -368,10 +486,14 @@ export async function executeEvidenceChild(workspace, prompt, context, options =
|
|
|
368
486
|
const lastAssistant = [...session.messages]
|
|
369
487
|
.reverse()
|
|
370
488
|
.find((message) => message.role === "assistant");
|
|
371
|
-
|
|
372
|
-
|
|
489
|
+
// error|aborted assistant stops share the upstream-testimony rule: provider only
|
|
490
|
+
// with direct HTTP/SDK testimony, otherwise existing unknown. child is reserved
|
|
491
|
+
// for real local child/report failures (no assistant / blank report / cleanup).
|
|
492
|
+
if (lastAssistant?.role === "assistant"
|
|
493
|
+
&& (lastAssistant.stopReason === "error" || lastAssistant.stopReason === "aborted")) {
|
|
494
|
+
throw classifiedError(new Error(lastAssistant.errorMessage ?? "", { cause: lastAssistant }), projectStructuredRemote(lastAssistant).hasTestimony ? "provider" : "unknown");
|
|
373
495
|
}
|
|
374
|
-
if (lastAssistant?.role !== "assistant"
|
|
496
|
+
if (lastAssistant?.role !== "assistant") {
|
|
375
497
|
throw classifiedError(new Error("Evidence child child terminated without a report", {
|
|
376
498
|
cause: lastAssistant ?? session.messages,
|
|
377
499
|
}), "child");
|
|
@@ -721,15 +843,30 @@ export async function executeAuditorChild(options) {
|
|
|
721
843
|
catch (retentionFailure) {
|
|
722
844
|
if (response.stopReason !== "error")
|
|
723
845
|
throw retentionFailure;
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
846
|
+
// Do not trim/rewrite the held errorMessage bytes.
|
|
847
|
+
const diagnostic = typeof response.errorMessage === "string" && response.errorMessage.trim() !== ""
|
|
848
|
+
? response.errorMessage
|
|
849
|
+
: undefined;
|
|
850
|
+
const projected = projectStructuredRemote(response);
|
|
851
|
+
const failure = new Error(diagnostic ?? "", { cause: retentionFailure });
|
|
852
|
+
if (projected.hasTestimony && (response.model || response.provider)) {
|
|
853
|
+
failure.name = response.model || response.provider || "Error";
|
|
854
|
+
failure.failureCode = response.provider || response.model;
|
|
855
|
+
}
|
|
856
|
+
failure.knownCause = projected.hasTestimony ? "provider" : "unrecognized";
|
|
728
857
|
const retentionError = retentionFailure instanceof Error ? retentionFailure : undefined;
|
|
729
858
|
const retentionCause = retentionError?.cause;
|
|
730
859
|
failure.details = {
|
|
731
|
-
...(
|
|
732
|
-
...(response.
|
|
860
|
+
...(diagnostic === undefined ? {} : { errorMessage: diagnostic }),
|
|
861
|
+
...(projected.hasTestimony && response.provider ? { provider: response.provider } : {}),
|
|
862
|
+
...(projected.hasTestimony && response.model ? { model: response.model } : {}),
|
|
863
|
+
...(response.api ? { api: response.api } : {}),
|
|
864
|
+
...(response.rawStopReason ? { rawStopReason: response.rawStopReason } : {}),
|
|
865
|
+
...(projected.httpStatus === undefined ? {} : { httpStatus: projected.httpStatus }),
|
|
866
|
+
...(projected.diagnostics === undefined ? {} : { diagnostics: projected.diagnostics }),
|
|
867
|
+
...(projected.body === undefined ? {} : { body: projected.body }),
|
|
868
|
+
...(projected.code === undefined ? {} : { code: projected.code }),
|
|
869
|
+
...(projected.errno === undefined ? {} : { errno: projected.errno }),
|
|
733
870
|
retentionFailure: {
|
|
734
871
|
name: retentionError?.name ?? typeof retentionFailure,
|
|
735
872
|
message: retentionError?.message ?? String(retentionFailure),
|
|
@@ -756,8 +893,8 @@ export async function executeAuditorChild(options) {
|
|
|
756
893
|
parent: binding.parent,
|
|
757
894
|
failure: {
|
|
758
895
|
cause: failure.knownCause,
|
|
759
|
-
identity: { name: failure.name, code: failure.failureCode },
|
|
760
|
-
diagnostic: failure.message,
|
|
896
|
+
...(failure.failureCode === undefined ? {} : { identity: { name: failure.name, code: failure.failureCode } }),
|
|
897
|
+
...(failure.message === "" ? {} : { diagnostic: failure.message }),
|
|
761
898
|
details: failure.details,
|
|
762
899
|
},
|
|
763
900
|
});
|