@akagilnc/pi-workflow-roles 0.1.5406 → 0.1.5475
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/CLAUDE.md +1 -1
- package/README.md +42 -55
- package/README.zh-CN.md +59 -73
- package/THIRD_PARTY_NOTICES.md +4 -7
- package/dist/acp-host/production-host.js +8261 -9073
- package/dist/collector-tool-schemas.js +1 -1
- package/dist/compliance-transport.js +2 -5
- package/dist/engine-detour-usage.js +3 -30
- package/dist/gatekeeper-role.js +4 -18
- package/dist/headless-host/description.js +0 -209
- package/dist/headless-host/production-host.js +6973 -8035
- package/dist/host-contracts.js +8 -0
- package/dist/migrate-book-topology.js +30 -27
- package/dist/packaged-role-registry.js +1 -9
- package/dist/pi/role-turn-host.js +37 -19
- package/dist/public-cli/auto-resume.js +29 -66
- package/dist/public-cli/countersign-run.js +4 -18
- package/dist/public-cli/instruction-seat-run.js +59 -76
- package/dist/public-cli/main.js +9844 -10720
- package/dist/public-cli/option-definitions.js +1 -1
- package/dist/public-cli/post-admission.js +204 -285
- package/dist/public-cli/process-cancel.js +8 -0
- package/dist/public-cli/run-lifecycle.js +24 -94
- package/dist/public-cli/settlement.js +614 -1524
- package/dist/public-cli/terminal.js +23 -53
- package/dist/public-role-summons.js +3 -8
- package/dist/receipt-delivery-policy.js +11 -12
- package/dist/run-terminal-artifacts.js +19 -14
- package/dist/serialize-thrown-value.js +7 -1
- package/dist/submission-ledger.js +21 -89
- package/package.json +1 -1
- package/packets/fixer-repair.md +3 -1
- package/packets/judge-apply.md +7 -16
- package/packets/judge-authority.md +7 -17
- package/packets/judge-plan.md +7 -18
- package/packets/judge-review.md +8 -22
- package/packets/judge-submission.md +4 -7
- package/resources/collector-bot-handbook.md +2 -2
- package/resources/diarist-collect.md +2 -2
- package/resources/engine-dispatch.md +2 -2
- package/resources/navigator-route-playbook.md +1 -1
- package/resources/secretariat-ticket-identity.md +1 -1
- package/scripts/build-package.mjs +0 -2
- package/souls/coder-output-guide.md +3 -3
- package/souls/coder.md +2 -3
- package/souls/doctor-auditor.md +0 -3
- package/souls/fixer-auditor.md +0 -2
- package/souls/fixer-output-guide.md +3 -3
- package/souls/fixer.md +2 -2
- package/souls/gate-output-guide.md +1 -1
- package/souls/quality-law.md +2 -0
- package/src/acp-host/role-turn-host.ts +168 -75
- package/src/collector-tool-schemas.ts +1 -1
- package/src/compliance-transport.ts +3 -6
- package/src/engine-detour-usage.ts +3 -33
- package/src/external-host-turn-loop.ts +81 -36
- package/src/gatekeeper-role.ts +5 -20
- package/src/headless-host/description.ts +4 -214
- package/src/headless-host/production-host.ts +2 -1
- package/src/headless-host/role-turn-host.ts +153 -186
- package/src/host-contracts.ts +13 -10
- package/src/navigator-public-session.ts +1 -1
- package/src/navigator-session-contracts.ts +1 -1
- package/src/package-contracts/gatekeeper-output.ts +1 -1
- package/src/package-contracts/navigator-output.ts +1 -1
- package/src/package-contracts/terminating-infrastructure.ts +1 -1
- package/src/packaged-role-registry.ts +2 -10
- package/src/pi/adapter.ts +0 -3
- package/src/pi/role-turn-host.ts +42 -28
- package/src/prepared-role-turn.ts +2 -0
- package/src/public-cli/auto-resume.ts +27 -66
- package/src/public-cli/countersign-run.ts +4 -22
- package/src/public-cli/instruction-seat-run.ts +79 -80
- package/src/public-cli/option-definitions.ts +1 -1
- package/src/public-cli/post-admission.ts +264 -340
- package/src/public-cli/process-cancel.ts +9 -0
- package/src/public-cli/run-lifecycle.ts +25 -126
- package/src/public-cli/settlement.ts +792 -1912
- package/src/public-cli/terminal.ts +29 -69
- package/src/public-role-summons.ts +3 -8
- package/src/receipt-delivery-policy.ts +11 -12
- package/src/role-envelope.ts +16 -9
- package/src/role-runtime.ts +14 -81
- package/src/run-terminal-artifacts.ts +21 -17
- package/src/serialize-thrown-value.ts +7 -1
- package/src/submission-ledger.ts +30 -116
- package/dist/audit-escalation.js +0 -7
- package/dist/pi/known-failure.js +0 -51
- package/dist/typed-provider-http.js +0 -57
- package/dist/upstream-error-testimony.js +0 -19
- package/src/pi/known-failure.ts +0 -76
- package/src/typed-provider-http.ts +0 -89
- package/src/upstream-error-testimony.ts +0 -45
package/src/submission-ledger.ts
CHANGED
|
@@ -194,15 +194,6 @@ function resolveReadScope(
|
|
|
194
194
|
return homeOrScope;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
function recordsForAttempt<T extends { subject?: unknown; payload?: unknown }>(
|
|
198
|
-
owned: readonly T[],
|
|
199
|
-
attemptId: string | undefined,
|
|
200
|
-
): readonly T[] {
|
|
201
|
-
// #836: courtAttempt is a recording tag for new courts, not a visibility gate.
|
|
202
|
-
void attemptId;
|
|
203
|
-
return owned;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
197
|
/** Recording tag a row was written under — subject first, historical payload fallback. */
|
|
207
198
|
function recordAttemptId(record: { subject?: unknown; payload?: unknown }): string | undefined {
|
|
208
199
|
if (typeof record.subject === "object" && record.subject !== null) {
|
|
@@ -216,34 +207,6 @@ function recordAttemptId(record: { subject?: unknown; payload?: unknown }): stri
|
|
|
216
207
|
return undefined;
|
|
217
208
|
}
|
|
218
209
|
|
|
219
|
-
/**
|
|
220
|
-
* True when the given attemptId itself already produced a sealed or
|
|
221
|
-
* audit-escalation submission (#836 r12 class 2). This is a separate
|
|
222
|
-
* freshness signal, not a presentation filter — `recordsForAttempt` above
|
|
223
|
-
* stays a pass-through so every original payload keeps presenting honestly
|
|
224
|
-
* (#836: attemptId is a recording tag, not a visibility gate). Settlement
|
|
225
|
-
* consumes this only to stop a prior attempt's stale acceptance from
|
|
226
|
-
* outranking the current attempt's own real host-turn failure signal
|
|
227
|
-
* (#637 original intent, restored narrowly).
|
|
228
|
-
*/
|
|
229
|
-
export async function hasFreshAttemptSubmission(
|
|
230
|
-
cwd: string,
|
|
231
|
-
runId: string,
|
|
232
|
-
attemptId: string,
|
|
233
|
-
homeOrScope?: string | SubmissionLedgerReadScope,
|
|
234
|
-
): Promise<boolean> {
|
|
235
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId, resolveReadScope(homeOrScope));
|
|
236
|
-
return owned.some((record) => {
|
|
237
|
-
if (recordAttemptId(record) !== attemptId) return false;
|
|
238
|
-
if (record.kind === "sealed") return true;
|
|
239
|
-
if (record.kind === "outcome") {
|
|
240
|
-
const payload = record.payload as { type?: string; outcome?: string } | undefined;
|
|
241
|
-
return payload?.type === "outcome" && payload.outcome === "audit-escalation";
|
|
242
|
-
}
|
|
243
|
-
return false;
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
210
|
function isTerminalRoleName(value: unknown): value is TerminalRoleName {
|
|
248
211
|
return typeof value === "string" && value.length > 0;
|
|
249
212
|
}
|
|
@@ -359,55 +322,38 @@ function mapOwnedToSubmissionRows(
|
|
|
359
322
|
};
|
|
360
323
|
|
|
361
324
|
for (const record of scoped) {
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
projection?: { role?: unknown };
|
|
366
|
-
} | undefined;
|
|
367
|
-
if (payload?.type !== "candidate" || payload.params === undefined) continue;
|
|
368
|
-
const callKey =
|
|
369
|
-
typeof payload.toolCallId === "string"
|
|
370
|
-
? submissionCallKey(attemptId, payload.toolCallId)
|
|
371
|
-
: undefined;
|
|
372
|
-
const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
|
|
373
|
-
take(rowFromPayload("candidate", payload, payload.params, fallback), callKey);
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
if (record.kind === "sealed") {
|
|
377
|
-
const payload = record.payload as Partial<Extract<SubmissionLedgerEvent, { type: "sealed" }>> & {
|
|
378
|
-
projection?: { role?: unknown };
|
|
379
|
-
} | undefined;
|
|
380
|
-
if (payload?.type !== "sealed" || payload.accepted === undefined) continue;
|
|
381
|
-
const callKey =
|
|
382
|
-
typeof payload.toolCallId === "string"
|
|
383
|
-
? submissionCallKey(attemptId, payload.toolCallId)
|
|
384
|
-
: undefined;
|
|
385
|
-
const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
|
|
386
|
-
take(rowFromPayload("accepted", payload, payload.accepted, fallback), callKey);
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
if (record.kind !== "outcome") continue;
|
|
390
|
-
const payload = record.payload as Partial<Extract<SubmissionLedgerEvent, { type: "outcome" }>> & {
|
|
325
|
+
const payload = record.payload as {
|
|
326
|
+
type?: unknown;
|
|
327
|
+
role?: unknown;
|
|
391
328
|
projection?: { role?: unknown };
|
|
329
|
+
toolCallId?: unknown;
|
|
330
|
+
params?: unknown;
|
|
331
|
+
accepted?: unknown;
|
|
332
|
+
outcome?: unknown;
|
|
333
|
+
auditReceipt?: unknown;
|
|
334
|
+
auditOfficer?: unknown;
|
|
392
335
|
} | undefined;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const callKey =
|
|
406
|
-
typeof payload.toolCallId === "string"
|
|
407
|
-
? submissionCallKey(attemptId, payload.toolCallId)
|
|
336
|
+
let kind: RecordedSubmissionRow["kind"] | undefined;
|
|
337
|
+
let original: unknown;
|
|
338
|
+
if (record.kind === "candidate" && payload?.type === "candidate") {
|
|
339
|
+
kind = "candidate";
|
|
340
|
+
original = payload.params;
|
|
341
|
+
} else if (record.kind === "sealed" && payload?.type === "sealed") {
|
|
342
|
+
kind = "accepted";
|
|
343
|
+
original = payload.accepted;
|
|
344
|
+
} else if (record.kind === "outcome" && payload?.type === "outcome") {
|
|
345
|
+
kind = payload.outcome === "audit-escalation" ? "audit-escalation"
|
|
346
|
+
: payload.outcome === "correctable-rejection" ? "correctable-rejection"
|
|
347
|
+
: payload.outcome === "infrastructure" ? "infrastructure"
|
|
408
348
|
: undefined;
|
|
409
|
-
|
|
410
|
-
|
|
349
|
+
original = payload.accepted;
|
|
350
|
+
}
|
|
351
|
+
if (kind === undefined || original === undefined || payload === undefined) continue;
|
|
352
|
+
const callKey = typeof payload.toolCallId === "string"
|
|
353
|
+
? submissionCallKey(recordAttemptId(record), payload.toolCallId)
|
|
354
|
+
: undefined;
|
|
355
|
+
const fallback = callKey === undefined ? undefined : roleByCall.get(callKey);
|
|
356
|
+
take(rowFromPayload(kind, payload, original, fallback), callKey);
|
|
411
357
|
}
|
|
412
358
|
return out;
|
|
413
359
|
}
|
|
@@ -425,8 +371,7 @@ export async function readRecordedSubmissionRows(
|
|
|
425
371
|
): Promise<readonly RecordedSubmissionRow[]> {
|
|
426
372
|
const scope = resolveReadScope(homeOrScope);
|
|
427
373
|
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope);
|
|
428
|
-
|
|
429
|
-
return mapOwnedToSubmissionRows(scoped);
|
|
374
|
+
return mapOwnedToSubmissionRows(owned);
|
|
430
375
|
}
|
|
431
376
|
|
|
432
377
|
/**
|
|
@@ -461,37 +406,6 @@ export async function readRecordedSubmissions(
|
|
|
461
406
|
return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).map((row) => row.accepted);
|
|
462
407
|
}
|
|
463
408
|
|
|
464
|
-
/** True when the run has at least one recorded original payload (any outcome class). */
|
|
465
|
-
export async function hasRecordedSubmission(
|
|
466
|
-
cwd: string,
|
|
467
|
-
runId: string,
|
|
468
|
-
homeOrScope?: string | SubmissionLedgerReadScope,
|
|
469
|
-
): Promise<boolean> {
|
|
470
|
-
return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).length > 0;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
export type LatestSubmissionOutcome = Extract<SubmissionLedgerEvent, { type: "outcome" }>;
|
|
474
|
-
|
|
475
|
-
/** Latest non-final outcome on the run ledger (settlement residual precedence). */
|
|
476
|
-
export async function readLatestSubmissionOutcome(
|
|
477
|
-
cwd: string,
|
|
478
|
-
runId: string,
|
|
479
|
-
homeOrScope?: string | SubmissionLedgerReadScope,
|
|
480
|
-
): Promise<LatestSubmissionOutcome | undefined> {
|
|
481
|
-
const scope = resolveReadScope(homeOrScope);
|
|
482
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope);
|
|
483
|
-
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
484
|
-
for (let index = scoped.length - 1; index >= 0; index -= 1) {
|
|
485
|
-
const record = scoped[index];
|
|
486
|
-
if (record?.kind !== "outcome") continue;
|
|
487
|
-
const payload = record.payload as Partial<LatestSubmissionOutcome> | undefined;
|
|
488
|
-
if (payload?.type === "outcome" && typeof payload.outcome === "string") {
|
|
489
|
-
return payload as LatestSubmissionOutcome;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
return undefined;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
409
|
type LedgerState = { sequence: number };
|
|
496
410
|
|
|
497
411
|
async function restoreState(cwd: string, runId: string, scope: SubmissionLedgerReadScope): Promise<LedgerState> {
|
package/dist/audit-escalation.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { isRecord } from "./unknown-value.js";
|
|
2
|
-
/** Historical audit-escalation discriminator retained for reading pre-#1057 run artifacts. */
|
|
3
|
-
export const AUDIT_ESCALATION_KIND = "audit_escalation";
|
|
4
|
-
export function isAuditEscalationResult(value) {
|
|
5
|
-
return isRecord(value)
|
|
6
|
-
&& value.kind === AUDIT_ESCALATION_KIND;
|
|
7
|
-
}
|
package/dist/pi/known-failure.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { hasUpstreamErrorTestimony, projectConfirmedRemotePayload, } from "../upstream-error-testimony.js";
|
|
2
|
-
export { hasUpstreamErrorTestimony } from "../upstream-error-testimony.js";
|
|
3
|
-
function nonEmptyString(value) {
|
|
4
|
-
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
5
|
-
}
|
|
6
|
-
function sessionStopDetails(input) {
|
|
7
|
-
const details = {};
|
|
8
|
-
const errorMessage = nonEmptyString(input.errorMessage);
|
|
9
|
-
if (errorMessage !== undefined)
|
|
10
|
-
details.errorMessage = errorMessage;
|
|
11
|
-
const api = nonEmptyString(input.api);
|
|
12
|
-
if (api !== undefined)
|
|
13
|
-
details.api = api;
|
|
14
|
-
const rawStopReason = nonEmptyString(input.rawStopReason);
|
|
15
|
-
if (rawStopReason !== undefined)
|
|
16
|
-
details.rawStopReason = rawStopReason;
|
|
17
|
-
const testimony = hasUpstreamErrorTestimony(input);
|
|
18
|
-
if (testimony) {
|
|
19
|
-
const provider = nonEmptyString(input.provider);
|
|
20
|
-
if (provider !== undefined)
|
|
21
|
-
details.provider = provider;
|
|
22
|
-
const model = nonEmptyString(input.model);
|
|
23
|
-
if (model !== undefined)
|
|
24
|
-
details.model = model;
|
|
25
|
-
}
|
|
26
|
-
if (typeof input.httpStatus === "number")
|
|
27
|
-
details.httpStatus = input.httpStatus;
|
|
28
|
-
if (input.diagnostics !== undefined)
|
|
29
|
-
details.diagnostics = input.diagnostics;
|
|
30
|
-
// SDK structured payload fields: project only when held on a confirmed-remote node.
|
|
31
|
-
if (testimony)
|
|
32
|
-
Object.assign(details, projectConfirmedRemotePayload(input));
|
|
33
|
-
return details;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Project a native session assistant stop onto the existing knownFailure chain.
|
|
37
|
-
* Classification follows two-way testimony: typed HTTP status or SDK structure
|
|
38
|
-
* keeps provider; stopReason / errorMessage prose alone never invents a class (#881).
|
|
39
|
-
* Present upstream payload is preserved in details without rewriting; missing fields are omitted.
|
|
40
|
-
*/
|
|
41
|
-
export function knownFailureFromProviderStop(input) {
|
|
42
|
-
if (input.stopReason !== "error" && input.stopReason !== "aborted")
|
|
43
|
-
return undefined;
|
|
44
|
-
const diagnostic = nonEmptyString(input.errorMessage);
|
|
45
|
-
const details = sessionStopDetails(input);
|
|
46
|
-
return {
|
|
47
|
-
...(hasUpstreamErrorTestimony(input) ? { cause: "provider" } : {}),
|
|
48
|
-
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
49
|
-
...(Object.keys(details).length === 0 ? {} : { details }),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { isEnoent, isRecord } from "./unknown-value.js";
|
|
4
|
-
const TYPED_HTTP_FILE = "typed-provider-http.json";
|
|
5
|
-
function typedProviderHttpPath(runDirectory) {
|
|
6
|
-
return join(runDirectory, TYPED_HTTP_FILE);
|
|
7
|
-
}
|
|
8
|
-
async function clearTypedProviderHttpObservation(runDirectory) {
|
|
9
|
-
try {
|
|
10
|
-
await unlink(typedProviderHttpPath(runDirectory));
|
|
11
|
-
} catch (error) {
|
|
12
|
-
if (isEnoent(error)) return;
|
|
13
|
-
throw error;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
async function recordTypedProviderHttpStatus(runDirectory, observation) {
|
|
17
|
-
if (observation.httpStatus >= 200 && observation.httpStatus < 300) {
|
|
18
|
-
await clearTypedProviderHttpObservation(runDirectory);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const body = {
|
|
22
|
-
httpStatus: observation.httpStatus,
|
|
23
|
-
provider: observation.provider
|
|
24
|
-
};
|
|
25
|
-
await writeFile(
|
|
26
|
-
typedProviderHttpPath(runDirectory),
|
|
27
|
-
`${JSON.stringify(body)}
|
|
28
|
-
`,
|
|
29
|
-
"utf8"
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
async function readLatestTypedProviderHttpObservation(runDirectory) {
|
|
33
|
-
let text;
|
|
34
|
-
try {
|
|
35
|
-
text = await readFile(typedProviderHttpPath(runDirectory), "utf8");
|
|
36
|
-
} catch (error) {
|
|
37
|
-
if (isEnoent(error)) return void 0;
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
const raw = JSON.parse(text);
|
|
41
|
-
if (!isRecord(raw)) {
|
|
42
|
-
throw new Error("typed provider HTTP observation must be a JSON object");
|
|
43
|
-
}
|
|
44
|
-
const record = raw;
|
|
45
|
-
if (typeof record.httpStatus !== "number") {
|
|
46
|
-
throw new Error("typed provider HTTP observation missing numeric httpStatus");
|
|
47
|
-
}
|
|
48
|
-
if (typeof record.provider !== "string" || record.provider.trim() === "") {
|
|
49
|
-
throw new Error("typed provider HTTP observation missing non-empty provider");
|
|
50
|
-
}
|
|
51
|
-
return { httpStatus: record.httpStatus, provider: record.provider };
|
|
52
|
-
}
|
|
53
|
-
export {
|
|
54
|
-
clearTypedProviderHttpObservation,
|
|
55
|
-
readLatestTypedProviderHttpObservation,
|
|
56
|
-
recordTypedProviderHttpStatus
|
|
57
|
-
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
function isNonSuccessHttpStatus(status) {
|
|
2
|
-
return typeof status === "number" && (status < 200 || status >= 300);
|
|
3
|
-
}
|
|
4
|
-
function hasUpstreamErrorTestimony(input) {
|
|
5
|
-
if (isNonSuccessHttpStatus(input.httpStatus)) return true;
|
|
6
|
-
return Array.isArray(input.diagnostics) && input.diagnostics.length > 0;
|
|
7
|
-
}
|
|
8
|
-
function projectConfirmedRemotePayload(input) {
|
|
9
|
-
return {
|
|
10
|
-
...input.body === void 0 ? {} : { body: input.body },
|
|
11
|
-
...input.code === void 0 ? {} : { code: input.code },
|
|
12
|
-
...input.errno === void 0 ? {} : { errno: input.errno }
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
export {
|
|
16
|
-
hasUpstreamErrorTestimony,
|
|
17
|
-
isNonSuccessHttpStatus,
|
|
18
|
-
projectConfirmedRemotePayload
|
|
19
|
-
};
|
package/src/pi/known-failure.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project native session assistant stops onto the host-contract knownFailure shape.
|
|
3
|
-
*/
|
|
4
|
-
import type { RoleTurnKnownFailure } from "../host-contracts.ts";
|
|
5
|
-
import {
|
|
6
|
-
hasUpstreamErrorTestimony,
|
|
7
|
-
projectConfirmedRemotePayload,
|
|
8
|
-
} from "../upstream-error-testimony.ts";
|
|
9
|
-
|
|
10
|
-
export { hasUpstreamErrorTestimony } from "../upstream-error-testimony.ts";
|
|
11
|
-
|
|
12
|
-
function nonEmptyString(value: unknown): string | undefined {
|
|
13
|
-
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function sessionStopDetails(input: {
|
|
17
|
-
readonly errorMessage?: string | null;
|
|
18
|
-
readonly provider?: string;
|
|
19
|
-
readonly model?: string;
|
|
20
|
-
readonly api?: string;
|
|
21
|
-
readonly rawStopReason?: string;
|
|
22
|
-
readonly httpStatus?: number;
|
|
23
|
-
readonly diagnostics?: unknown;
|
|
24
|
-
readonly body?: unknown;
|
|
25
|
-
readonly code?: unknown;
|
|
26
|
-
readonly errno?: unknown;
|
|
27
|
-
}): Record<string, unknown> {
|
|
28
|
-
const details: Record<string, unknown> = {};
|
|
29
|
-
const errorMessage = nonEmptyString(input.errorMessage);
|
|
30
|
-
if (errorMessage !== undefined) details.errorMessage = errorMessage;
|
|
31
|
-
const api = nonEmptyString(input.api);
|
|
32
|
-
if (api !== undefined) details.api = api;
|
|
33
|
-
const rawStopReason = nonEmptyString(input.rawStopReason);
|
|
34
|
-
if (rawStopReason !== undefined) details.rawStopReason = rawStopReason;
|
|
35
|
-
const testimony = hasUpstreamErrorTestimony(input);
|
|
36
|
-
if (testimony) {
|
|
37
|
-
const provider = nonEmptyString(input.provider);
|
|
38
|
-
if (provider !== undefined) details.provider = provider;
|
|
39
|
-
const model = nonEmptyString(input.model);
|
|
40
|
-
if (model !== undefined) details.model = model;
|
|
41
|
-
}
|
|
42
|
-
if (typeof input.httpStatus === "number") details.httpStatus = input.httpStatus;
|
|
43
|
-
if (input.diagnostics !== undefined) details.diagnostics = input.diagnostics;
|
|
44
|
-
// SDK structured payload fields: project only when held on a confirmed-remote node.
|
|
45
|
-
if (testimony) Object.assign(details, projectConfirmedRemotePayload(input));
|
|
46
|
-
return details;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Project a native session assistant stop onto the existing knownFailure chain.
|
|
51
|
-
* Classification follows two-way testimony: typed HTTP status or SDK structure
|
|
52
|
-
* keeps provider; stopReason / errorMessage prose alone never invents a class (#881).
|
|
53
|
-
* Present upstream payload is preserved in details without rewriting; missing fields are omitted.
|
|
54
|
-
*/
|
|
55
|
-
export function knownFailureFromProviderStop(input: {
|
|
56
|
-
readonly stopReason?: string;
|
|
57
|
-
readonly errorMessage?: string | null;
|
|
58
|
-
readonly provider?: string;
|
|
59
|
-
readonly model?: string;
|
|
60
|
-
readonly api?: string;
|
|
61
|
-
readonly rawStopReason?: string;
|
|
62
|
-
readonly httpStatus?: number;
|
|
63
|
-
readonly diagnostics?: unknown;
|
|
64
|
-
readonly body?: unknown;
|
|
65
|
-
readonly code?: unknown;
|
|
66
|
-
readonly errno?: unknown;
|
|
67
|
-
}): RoleTurnKnownFailure | undefined {
|
|
68
|
-
if (input.stopReason !== "error" && input.stopReason !== "aborted") return undefined;
|
|
69
|
-
const diagnostic = nonEmptyString(input.errorMessage);
|
|
70
|
-
const details = sessionStopDetails(input);
|
|
71
|
-
return {
|
|
72
|
-
...(hasUpstreamErrorTestimony(input) ? { cause: "provider" as const } : {}),
|
|
73
|
-
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
74
|
-
...(Object.keys(details).length === 0 ? {} : { details }),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared durable typed-provider HTTP observation owner.
|
|
3
|
-
* Published with both public-cli and navigator-attendance entries.
|
|
4
|
-
*/
|
|
5
|
-
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
|
|
8
|
-
import { isEnoent, isRecord } from "./unknown-value.ts";
|
|
9
|
-
|
|
10
|
-
const TYPED_HTTP_FILE = "typed-provider-http.json";
|
|
11
|
-
|
|
12
|
-
export type TypedProviderHttpObservation = {
|
|
13
|
-
readonly httpStatus: number;
|
|
14
|
-
readonly provider: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
function typedProviderHttpPath(runDirectory: string): string {
|
|
18
|
-
return join(runDirectory, TYPED_HTTP_FILE);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Clear any prior attempt's typed provider HTTP observation.
|
|
23
|
-
* Each initial/resume dispatch must start without inherited 429 evidence so
|
|
24
|
-
* only the current attempt can qualify v1 resume.
|
|
25
|
-
*/
|
|
26
|
-
export async function clearTypedProviderHttpObservation(
|
|
27
|
-
runDirectory: string,
|
|
28
|
-
): Promise<void> {
|
|
29
|
-
try {
|
|
30
|
-
await unlink(typedProviderHttpPath(runDirectory));
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (isEnoent(error)) return;
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Record a typed provider HTTP status observation for the admitted run.
|
|
39
|
-
* Only non-success HTTP statuses are persisted (authorized error evidence).
|
|
40
|
-
* A 2xx response clears any prior observation so success cannot leave stale
|
|
41
|
-
* error evidence. Never inspects diagnostic prose.
|
|
42
|
-
*/
|
|
43
|
-
export async function recordTypedProviderHttpStatus(
|
|
44
|
-
runDirectory: string,
|
|
45
|
-
observation: { readonly httpStatus: number; readonly provider: string },
|
|
46
|
-
): Promise<void> {
|
|
47
|
-
if (observation.httpStatus >= 200 && observation.httpStatus < 300) {
|
|
48
|
-
await clearTypedProviderHttpObservation(runDirectory);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const body: TypedProviderHttpObservation = {
|
|
52
|
-
httpStatus: observation.httpStatus,
|
|
53
|
-
provider: observation.provider,
|
|
54
|
-
};
|
|
55
|
-
await writeFile(
|
|
56
|
-
typedProviderHttpPath(runDirectory),
|
|
57
|
-
`${JSON.stringify(body)}\n`,
|
|
58
|
-
"utf8",
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Read the latest durable typed provider HTTP observation, if any.
|
|
64
|
-
* Only ENOENT means absence (undefined). Read/parse/shape failures preserve the
|
|
65
|
-
* real cause for the existing controlled-failure chain — never laundered as absence.
|
|
66
|
-
*/
|
|
67
|
-
export async function readLatestTypedProviderHttpObservation(
|
|
68
|
-
runDirectory: string,
|
|
69
|
-
): Promise<TypedProviderHttpObservation | undefined> {
|
|
70
|
-
let text: string;
|
|
71
|
-
try {
|
|
72
|
-
text = await readFile(typedProviderHttpPath(runDirectory), "utf8");
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (isEnoent(error)) return undefined;
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
const raw: unknown = JSON.parse(text);
|
|
78
|
-
if (!isRecord(raw)) {
|
|
79
|
-
throw new Error("typed provider HTTP observation must be a JSON object");
|
|
80
|
-
}
|
|
81
|
-
const record = raw as Record<string, unknown>;
|
|
82
|
-
if (typeof record.httpStatus !== "number") {
|
|
83
|
-
throw new Error("typed provider HTTP observation missing numeric httpStatus");
|
|
84
|
-
}
|
|
85
|
-
if (typeof record.provider !== "string" || record.provider.trim() === "") {
|
|
86
|
-
throw new Error("typed provider HTTP observation missing non-empty provider");
|
|
87
|
-
}
|
|
88
|
-
return { httpStatus: record.httpStatus, provider: record.provider };
|
|
89
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single authority for upstream-error testimony (#307).
|
|
3
|
-
* Non-2xx HTTP or non-empty SDK diagnostics = upstream testimony.
|
|
4
|
-
* body/code/errno project only from a confirmed-remote node.
|
|
5
|
-
* Seams keep their own shape/cause-chain reading and wrap via these primitives.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** True when value is a numeric HTTP status outside 2xx. */
|
|
9
|
-
export function isNonSuccessHttpStatus(status: unknown): status is number {
|
|
10
|
-
return typeof status === "number" && (status < 200 || status >= 300);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Upstream testimony is a directly observed HTTP response or SDK-structured
|
|
15
|
-
* remote error. Pi stopReason, selected provider/model names, silent stream
|
|
16
|
-
* death, errorMessage prose (e.g. "500: …"), and locally synthesized errors
|
|
17
|
-
* are not testimony.
|
|
18
|
-
*/
|
|
19
|
-
export function hasUpstreamErrorTestimony(input: {
|
|
20
|
-
readonly httpStatus?: number;
|
|
21
|
-
readonly diagnostics?: unknown;
|
|
22
|
-
}): boolean {
|
|
23
|
-
if (isNonSuccessHttpStatus(input.httpStatus)) return true;
|
|
24
|
-
return Array.isArray(input.diagnostics) && input.diagnostics.length > 0;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Project body/code/errno only when the caller already confirmed remote testimony
|
|
29
|
-
* on the same node. Plain local Error.code is never provider testimony.
|
|
30
|
-
*/
|
|
31
|
-
export function projectConfirmedRemotePayload(input: {
|
|
32
|
-
readonly body?: unknown;
|
|
33
|
-
readonly code?: unknown;
|
|
34
|
-
readonly errno?: unknown;
|
|
35
|
-
}): {
|
|
36
|
-
readonly body?: unknown;
|
|
37
|
-
readonly code?: unknown;
|
|
38
|
-
readonly errno?: unknown;
|
|
39
|
-
} {
|
|
40
|
-
return {
|
|
41
|
-
...(input.body === undefined ? {} : { body: input.body }),
|
|
42
|
-
...(input.code === undefined ? {} : { code: input.code }),
|
|
43
|
-
...(input.errno === undefined ? {} : { errno: input.errno }),
|
|
44
|
-
};
|
|
45
|
-
}
|