@akagilnc/pi-workflow-roles 0.1.2383 → 0.1.2402
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/audit-escalation.js +0 -13
- package/dist/compliance-transport.js +28 -5
- package/dist/doctor-contracts.js +2 -9
- package/dist/dossier-resolution.js +21 -2
- package/dist/engine-detour-tool.js +10 -35
- package/dist/evidence-child-executor.js +12 -4
- package/dist/merger-contracts.js +2 -8
- package/dist/navigator-attendance.js +1 -9
- package/dist/navigator-invocation-identity.js +16 -4
- package/dist/notary-contracts.js +7 -25
- package/dist/package-contracts/judge-output.js +1 -4
- package/dist/package-contracts/reviewer-output.js +1 -7
- package/dist/package-contracts/terminating-tools.js +6 -11
- package/dist/public-cli/main.js +125 -475
- package/package.json +1 -1
- package/scripts/build-package.mjs +1 -2
- package/src/analyst-gate-cycles-read.ts +5 -8
- package/src/analyst-metric-families/acceptance-success-rework.ts +3 -6
- package/src/audit-escalation.ts +0 -25
- package/src/collector-role.ts +1 -8
- package/src/compliance-transport.ts +38 -6
- package/src/doctor-auditor.ts +3 -3
- package/src/doctor-contracts.ts +3 -18
- package/src/doctor-role.ts +1 -5
- package/src/dossier-resolution.ts +25 -4
- package/src/engine-detour-tool.ts +16 -51
- package/src/evidence-child-executor.ts +12 -4
- package/src/factory-board.ts +2 -4
- package/src/gatekeeper-role.ts +33 -43
- package/src/judge-auditor.ts +3 -3
- package/src/judge-role.ts +1 -8
- package/src/merger-contracts.ts +3 -17
- package/src/merger-role.ts +1 -8
- package/src/navigator-attendance.ts +1 -14
- package/src/navigator-invocation-identity.ts +34 -13
- package/src/notary-contracts.ts +9 -43
- package/src/notary-role.ts +3 -5
- package/src/package-contracts/judge-output.ts +2 -17
- package/src/package-contracts/reviewer-output.ts +2 -21
- package/src/package-contracts/terminating-tools.ts +5 -14
- package/src/package-contracts/worker-output.ts +2 -10
- package/src/public-cli/doctor-run.ts +0 -2
- package/src/public-cli/fixer-run.ts +0 -15
- package/src/public-cli/judge-run.ts +0 -16
- package/src/public-cli/notary-run.ts +1 -1
- package/src/public-cli/one-shot-dispatch.ts +1 -1
- package/src/public-cli/reviewer-run.ts +0 -15
- package/src/public-cli/settlement.ts +110 -368
- package/src/public-cli/terminal.ts +3 -71
- package/src/reviewer-auditor.ts +3 -3
- package/src/reviewer-role.ts +2 -11
- package/src/role-runtime.ts +43 -37
- package/src/worker-role.ts +2 -14
- package/dist/engine-labor-fallback.js +0 -136
- package/src/engine-labor-fallback.ts +0 -270
package/package.json
CHANGED
|
@@ -9,8 +9,7 @@ const entries = [
|
|
|
9
9
|
"work-subject-identity",
|
|
10
10
|
"navigator-invocation-identity",
|
|
11
11
|
"navigator-attendance",
|
|
12
|
-
// Navigator package-graph
|
|
13
|
-
"engine-labor-fallback",
|
|
12
|
+
// Navigator package-graph dependencies used by attendance settlement.
|
|
14
13
|
"activation-ledger-git",
|
|
15
14
|
"activation-ledger-topology",
|
|
16
15
|
"activation-reconciliation",
|
|
@@ -16,10 +16,9 @@
|
|
|
16
16
|
* An accepted gate terminating receipt (isError:false pair on dispatch/officer
|
|
17
17
|
* tool) whose required typed facts are unusable — status, dispatch officer, or
|
|
18
18
|
* first/last span missing/unknown/unparseable/inverted — also fails loudly via
|
|
19
|
-
* the same throw→ledger `auditor-roles` unreadable seam.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* non-gate volumes (soul-audit noise, etc.) stay omitted from pairing.
|
|
19
|
+
* the same throw→ledger `auditor-roles` unreadable seam. Unknown/non-contract
|
|
20
|
+
* dispatch status stays loud (#475 abolished Gatekeeper incomplete special-case).
|
|
21
|
+
* True non-gate volumes (soul-audit noise, etc.) stay omitted from pairing.
|
|
23
22
|
*/
|
|
24
23
|
import { readdir } from "node:fs/promises";
|
|
25
24
|
import { join } from "node:path";
|
|
@@ -36,7 +35,7 @@ export type AnalystGateCycleRound = {
|
|
|
36
35
|
readonly roundIndex: number;
|
|
37
36
|
/** Current English officer face after historical alias fold. */
|
|
38
37
|
readonly officer: "inspector" | "notary";
|
|
39
|
-
/** Typed officer terminal status (pass / bounce /
|
|
38
|
+
/** Typed officer terminal status (pass / bounce / …). */
|
|
40
39
|
readonly status: string;
|
|
41
40
|
/** Officer subsession first→last usable timestamp delta (ms). */
|
|
42
41
|
readonly officerWallMs: number;
|
|
@@ -237,9 +236,7 @@ async function classifyAuditorVolume(
|
|
|
237
236
|
const findingsCount = findings.length;
|
|
238
237
|
|
|
239
238
|
if (DISPATCH_TOOLS.has(call.toolName)) {
|
|
240
|
-
// Gatekeeper contract
|
|
241
|
-
// lawful zero-pair terminal (#434-436 / #458) — never wash the parent leg.
|
|
242
|
-
if (status === "incomplete") return undefined;
|
|
239
|
+
// Gatekeeper contract terminal is dispatch only (#475).
|
|
243
240
|
if (status !== "dispatch") {
|
|
244
241
|
throw new Error(
|
|
245
242
|
`accepted dispatch receipt has non-dispatch status ${JSON.stringify(status)} in ${filePath}`,
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* - success rate den = success-eligible accepted legs (no-receipt out; planned out)
|
|
10
10
|
* - planned = plan-duty acceptance; never success numerator or denominator
|
|
11
11
|
*/
|
|
12
|
-
import { seatFallbackBaseStatus } from "../engine-labor-fallback.ts";
|
|
13
12
|
import type { AnalystReadableRunFacts, AnalystRunTerminalFace } from "../analyst-ledger.ts";
|
|
14
13
|
import { medianNumber } from "../analyst-median.ts";
|
|
15
14
|
import type { AnalystMetricFamilyModule } from "../analyst-metric-family.ts";
|
|
@@ -186,10 +185,8 @@ function mapTerminal(
|
|
|
186
185
|
};
|
|
187
186
|
}
|
|
188
187
|
|
|
189
|
-
// Seat-fallback taint is visible on the label; acceptance/success use base semantics.
|
|
190
|
-
const statusBase = seatFallbackBaseStatus(status);
|
|
191
188
|
const acceptedSet = ACCEPTED_STATUS[role];
|
|
192
|
-
if (acceptedSet === undefined || !acceptedSet.has(
|
|
189
|
+
if (acceptedSet === undefined || !acceptedSet.has(status)) {
|
|
193
190
|
return {
|
|
194
191
|
terminalLabel: status,
|
|
195
192
|
accepted: false,
|
|
@@ -199,9 +196,9 @@ function mapTerminal(
|
|
|
199
196
|
};
|
|
200
197
|
}
|
|
201
198
|
|
|
202
|
-
const plannedDuty = WORKER_ROLES.has(role) &&
|
|
199
|
+
const plannedDuty = WORKER_ROLES.has(role) && status === "planned";
|
|
203
200
|
const successSet = SUCCESS_STATUS[role] ?? new Set<string>();
|
|
204
|
-
const success = !plannedDuty && successSet.has(
|
|
201
|
+
const success = !plannedDuty && successSet.has(status);
|
|
205
202
|
const successEligible = !plannedDuty;
|
|
206
203
|
|
|
207
204
|
return {
|
package/src/audit-escalation.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
ComplianceAuditIncomplete,
|
|
5
4
|
ComplianceDecision,
|
|
6
5
|
} from "./compliance-transport.ts";
|
|
7
6
|
|
|
@@ -35,13 +34,6 @@ export type AuditEscalationToolResult = {
|
|
|
35
34
|
usage?: Usage;
|
|
36
35
|
};
|
|
37
36
|
|
|
38
|
-
export type AuditIncompleteToolResult = {
|
|
39
|
-
content: [{ type: "text"; text: string }];
|
|
40
|
-
details: ComplianceAuditIncomplete;
|
|
41
|
-
terminate: true;
|
|
42
|
-
usage?: Usage;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
37
|
/**
|
|
46
38
|
* Build the escalation delivery face.
|
|
47
39
|
* Role-delivered fields ride under the escalation discriminator (ADR 0055).
|
|
@@ -119,17 +111,6 @@ export function projectAuditEscalation(
|
|
|
119
111
|
};
|
|
120
112
|
}
|
|
121
113
|
|
|
122
|
-
export function projectAuditIncomplete(
|
|
123
|
-
decision: ComplianceAuditIncomplete,
|
|
124
|
-
): AuditIncompleteToolResult {
|
|
125
|
-
return {
|
|
126
|
-
content: [{ type: "text", text: "Compliance audit incomplete; no role receipt was formed." }],
|
|
127
|
-
details: decision,
|
|
128
|
-
terminate: true,
|
|
129
|
-
...(decision.usage === undefined ? {} : { usage: decision.usage }),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
114
|
/**
|
|
134
115
|
* Discriminator-only recognition (ADR 0040). Shape of conflicts/options/gate
|
|
135
116
|
* is not a reject gate — element types and cardinality are delivery content.
|
|
@@ -152,7 +133,6 @@ export type ComplianceDecisionHandlers<T> = {
|
|
|
152
133
|
) => T | PromiseLike<T>;
|
|
153
134
|
revise: (violations: readonly unknown[]) => T | PromiseLike<T>;
|
|
154
135
|
escalate: (result: AuditEscalationToolResult) => T | PromiseLike<T>;
|
|
155
|
-
auditIncomplete?: (result: AuditIncompleteToolResult) => T | PromiseLike<T>;
|
|
156
136
|
};
|
|
157
137
|
|
|
158
138
|
/**
|
|
@@ -183,10 +163,5 @@ export async function disposeComplianceDecision<T>(
|
|
|
183
163
|
return await handlers.escalate(
|
|
184
164
|
projectAuditEscalation(decision, deliveredOutput),
|
|
185
165
|
);
|
|
186
|
-
case "audit-incomplete":
|
|
187
|
-
if (handlers.auditIncomplete === undefined) {
|
|
188
|
-
throw new Error("Compliance audit-incomplete handler is unavailable");
|
|
189
|
-
}
|
|
190
|
-
return await handlers.auditIncomplete(projectAuditIncomplete(decision));
|
|
191
166
|
}
|
|
192
167
|
}
|
package/src/collector-role.ts
CHANGED
|
@@ -4,10 +4,6 @@ import type {
|
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import type { Static } from "typebox";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
readActivationEngineLaborFallbackField,
|
|
9
|
-
withEngineLaborFallbackField,
|
|
10
|
-
} from "./engine-labor-fallback.ts";
|
|
11
7
|
import {
|
|
12
8
|
COLLECTOR_FIXED_KICKOFF,
|
|
13
9
|
emptyCollectorManifest,
|
|
@@ -422,10 +418,7 @@ export function createCollectorRoleRuntime(
|
|
|
422
418
|
);
|
|
423
419
|
activation.ledger.markOutputAccepted();
|
|
424
420
|
activation.ledger.completeOperational(toolCallId);
|
|
425
|
-
const acceptedDetails =
|
|
426
|
-
receipt,
|
|
427
|
-
readActivationEngineLaborFallbackField(),
|
|
428
|
-
);
|
|
421
|
+
const acceptedDetails = receipt;
|
|
429
422
|
return {
|
|
430
423
|
content: [{
|
|
431
424
|
type: "text" as const,
|
|
@@ -15,9 +15,30 @@ export type ComplianceAuditObservation =
|
|
|
15
15
|
| { kind: "non-object-arguments"; type: ComplianceArgumentRootType }
|
|
16
16
|
| { kind: "object-status-unreadable"; status: "missing" | "unknown" }
|
|
17
17
|
| DossierObservation;
|
|
18
|
-
export type ComplianceAuditIncomplete = { status: "audit-incomplete"; observation: ComplianceAuditObservation; candidate: unknown; usage?: Usage };
|
|
19
18
|
export type ComplianceNoReceipt = NoReceiptLifecycleFacts & { status: "no-receipt"; usage?: Usage };
|
|
20
|
-
export type ComplianceDecision = { status: "pass"; usage?: Usage } | { status: "revise"; violations: readonly unknown[]; usage?: Usage } | { status: "escalate"; conflicts?: unknown; decisionGate?: unknown; usage?: Usage } | ComplianceNoReceipt
|
|
19
|
+
export type ComplianceDecision = { status: "pass"; usage?: Usage } | { status: "revise"; violations: readonly unknown[]; usage?: Usage } | { status: "escalate"; conflicts?: unknown; decisionGate?: unknown; usage?: Usage } | ComplianceNoReceipt;
|
|
20
|
+
|
|
21
|
+
/** Unreadable compliance candidate — infrastructure failure, not a judgment status (#475). */
|
|
22
|
+
export class ComplianceCandidateUnreadableError extends Error {
|
|
23
|
+
readonly observation: ComplianceAuditObservation;
|
|
24
|
+
readonly candidate: unknown;
|
|
25
|
+
readonly usage?: Usage;
|
|
26
|
+
constructor(observation: ComplianceAuditObservation, candidate: unknown, usage?: Usage) {
|
|
27
|
+
const detail =
|
|
28
|
+
observation.kind === "non-object-arguments"
|
|
29
|
+
? `${observation.kind}:${observation.type}`
|
|
30
|
+
: observation.kind === "object-status-unreadable"
|
|
31
|
+
? `${observation.kind}:${observation.status}`
|
|
32
|
+
: observation.kind === "missing-subject"
|
|
33
|
+
? `${observation.kind}:${observation.subject}`
|
|
34
|
+
: observation.kind;
|
|
35
|
+
super(`Compliance candidate unreadable: ${detail}`);
|
|
36
|
+
this.name = "ComplianceCandidateUnreadableError";
|
|
37
|
+
this.observation = observation;
|
|
38
|
+
this.candidate = candidate;
|
|
39
|
+
if (usage !== undefined) this.usage = usage;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
21
42
|
export type ComplianceDispatch = { model: Model<Api>; auth: { apiKey?: string; headers?: Record<string, string | null>; env?: Record<string, string> } };
|
|
22
43
|
|
|
23
44
|
/** Zero-projection kickoff — soul already carries dossier-fetch duty; no hand-delivered materials. */
|
|
@@ -25,8 +46,9 @@ export const AUDITOR_DOSSIER_PROMPT = "Audit the current run dossier." as const;
|
|
|
25
46
|
|
|
26
47
|
const nonblank = Type.String({ minLength: 1, pattern: "\\S" });
|
|
27
48
|
const decisionGateSchema = Type.Object({ question: nonblank, options: Type.Array(nonblank, { minItems: 1 }) }, { additionalProperties: false });
|
|
28
|
-
// Transport
|
|
29
|
-
//
|
|
49
|
+
// Transport retains malformed candidates on ComplianceCandidateUnreadableError so
|
|
50
|
+
// the existing failure channel can publish observation + candidate (#475).
|
|
51
|
+
// Status values are guidance, not a schema gate.
|
|
30
52
|
export const complianceDecisionSchema = Type.Object({ status: Type.Unknown({ description: "Auditor decision status." }), violations: Type.Array(nonblank, { description: "Observed compliance violations." }), conflicts: Type.Array(nonblank, { description: "Unresolved authority or execution conflicts." }), decisionGate: Type.Union([decisionGateSchema, Type.Null()], { description: "Escalation question and available options." }) }, { additionalProperties: true, required: [] });
|
|
31
53
|
|
|
32
54
|
export function createComplianceDecisionTool(name: string, description: string) {
|
|
@@ -93,12 +115,22 @@ function retainComplianceResponse(context: ExtensionContext, response: Assistant
|
|
|
93
115
|
}
|
|
94
116
|
function readListField(value: unknown): readonly unknown[] { return Array.isArray(value) ? value : value === undefined ? [] : [value]; }
|
|
95
117
|
export function readComplianceCandidate(arguments_: unknown, usage?: Usage): ComplianceDecision {
|
|
96
|
-
if (typeof arguments_ !== "object" || arguments_ === null || Array.isArray(arguments_))
|
|
118
|
+
if (typeof arguments_ !== "object" || arguments_ === null || Array.isArray(arguments_)) {
|
|
119
|
+
throw new ComplianceCandidateUnreadableError(
|
|
120
|
+
{ kind: "non-object-arguments", type: arguments_ === null ? "null" : Array.isArray(arguments_) ? "array" : typeof arguments_ as ComplianceArgumentRootType },
|
|
121
|
+
arguments_,
|
|
122
|
+
usage,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
97
125
|
const args = arguments_ as Record<string, unknown>; const status = args.status;
|
|
98
126
|
if (status === "pass") return { status, ...(usage === undefined ? {} : { usage }) };
|
|
99
127
|
if (status === "revise") return { status, violations: readListField(args.violations), ...(usage === undefined ? {} : { usage }) };
|
|
100
128
|
if (status === "escalate") return { status, ...(Object.hasOwn(args, "conflicts") ? { conflicts: args.conflicts } : {}), ...(Object.hasOwn(args, "decisionGate") ? { decisionGate: args.decisionGate } : {}), ...(usage === undefined ? {} : { usage }) };
|
|
101
|
-
|
|
129
|
+
throw new ComplianceCandidateUnreadableError(
|
|
130
|
+
{ kind: "object-status-unreadable", status: status === undefined ? "missing" : "unknown" },
|
|
131
|
+
arguments_,
|
|
132
|
+
usage,
|
|
133
|
+
);
|
|
102
134
|
}
|
|
103
135
|
|
|
104
136
|
export type RunComplianceAuditOptions = {
|
package/src/doctor-auditor.ts
CHANGED
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
} from "./compliance-transport.ts";
|
|
11
11
|
import {
|
|
12
12
|
readDoctorAuditSubjects,
|
|
13
|
+
requireAuditMaterials,
|
|
13
14
|
resolveAuditDossier,
|
|
14
|
-
toAuditIncomplete,
|
|
15
15
|
} from "./dossier-resolution.ts";
|
|
16
16
|
|
|
17
17
|
export const DOCTOR_AUDIT_TOOL_NAME = "ak_doctor_audit_decision";
|
|
@@ -35,9 +35,9 @@ export function createPiDoctorAuditor(
|
|
|
35
35
|
): (options: DoctorAuditOptions) => Promise<ComplianceDecision> {
|
|
36
36
|
return async (options) => {
|
|
37
37
|
const dossier = resolveAuditDossier();
|
|
38
|
-
|
|
38
|
+
requireAuditMaterials(dossier);
|
|
39
39
|
const subjects = readDoctorAuditSubjects(options.context);
|
|
40
|
-
|
|
40
|
+
requireAuditMaterials(subjects);
|
|
41
41
|
|
|
42
42
|
return runComplianceAudit({
|
|
43
43
|
tool,
|
package/src/doctor-contracts.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { canonicalJson } from "./canonical-json.ts";
|
|
3
|
-
import {
|
|
4
|
-
seatFallbackBaseStatus,
|
|
5
|
-
seatFallbackStatusHasLawfulEvidence,
|
|
6
|
-
type WithEngineLaborFallback,
|
|
7
|
-
} from "./engine-labor-fallback.ts";
|
|
8
3
|
import { openToolObjectFromUnion } from "./open-tool-schema.ts";
|
|
9
4
|
|
|
10
5
|
export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
|
|
@@ -42,13 +37,9 @@ export type DoctorFinding =
|
|
|
42
37
|
export type DoctorSubmission =
|
|
43
38
|
| { status: "completed"; case: DoctorCaseIdentity; findings: DoctorFinding[] }
|
|
44
39
|
| { status: "refused"; reason: string; missingEvidence: Array<{ need: string; targetKeys: string[] }> };
|
|
45
|
-
type
|
|
40
|
+
export type DoctorOutput =
|
|
46
41
|
| { status: "completed"; case: DoctorCaseIdentity; findings: DoctorFinding[]; cost: DoctorCaseCost }
|
|
47
42
|
| Extract<DoctorSubmission, { status: "refused" }>;
|
|
48
|
-
/** Clean recorded shape or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
49
|
-
export type DoctorOutput =
|
|
50
|
-
| DoctorOutputClean
|
|
51
|
-
| WithEngineLaborFallback<DoctorOutputClean>;
|
|
52
43
|
export type DoctorEvidenceEntry = { id: string; kind: "session" | "stderr"; byteLength: number; contentLength: number; sha256: string; content: string };
|
|
53
44
|
export type DoctorCase = { version: 1; identity: DoctorCaseIdentity; evidence: DoctorEvidenceEntry[]; cost: DoctorCaseCost };
|
|
54
45
|
|
|
@@ -105,19 +96,13 @@ function isRecord(value: unknown): value is Record<string, unknown> { return typ
|
|
|
105
96
|
function read(value: unknown, key: string): unknown { if (!isRecord(value)) return undefined; try { return value[key]; } catch { return undefined; } }
|
|
106
97
|
export function validateDoctorSubmissionShape(value: unknown): DoctorSubmission {
|
|
107
98
|
const status = read(value, "status");
|
|
108
|
-
|
|
109
|
-
if (base !== "completed" && base !== "refused") throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
|
|
110
|
-
// ADR 0071: tainted status requires latch-shaped engineLaborFallback evidence.
|
|
111
|
-
if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, value)) {
|
|
112
|
-
throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
|
|
113
|
-
}
|
|
99
|
+
if (status !== "completed" && status !== "refused") throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
|
|
114
100
|
return value as DoctorSubmission;
|
|
115
101
|
}
|
|
116
102
|
export function validateRecordedDoctorOutput(value: unknown): DoctorOutput {
|
|
117
103
|
const output = validateDoctorSubmissionShape(value);
|
|
118
104
|
const status = read(output, "status");
|
|
119
|
-
|
|
120
|
-
if (base === "completed" && read(output, "cost") === undefined) throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
|
|
105
|
+
if (status === "completed" && read(output, "cost") === undefined) throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
|
|
121
106
|
return output as DoctorOutput;
|
|
122
107
|
}
|
|
123
108
|
|
package/src/doctor-role.ts
CHANGED
|
@@ -2,10 +2,6 @@ import type { AgentToolResult, ExtensionAPI, ExtensionContext } from "@earendil-
|
|
|
2
2
|
import { disposeComplianceDecision } from "./audit-escalation.ts";
|
|
3
3
|
import { appendActiveSessionCustomEntry, type ComplianceDecision } from "./compliance-transport.ts";
|
|
4
4
|
import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
|
|
5
|
-
import {
|
|
6
|
-
readActivationEngineLaborFallbackField,
|
|
7
|
-
withEngineLaborFallbackField,
|
|
8
|
-
} from "./engine-labor-fallback.ts";
|
|
9
5
|
import { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
|
|
10
6
|
|
|
11
7
|
export { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME };
|
|
@@ -16,7 +12,7 @@ export function createDoctorRoleRuntime(pi: ExtensionAPI, dependencies: DoctorRo
|
|
|
16
12
|
let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
|
|
17
13
|
return { async activate() { const path = pi.getFlag(DOCTOR_CASE_FLAG.name); if (typeof path !== "string" || !path.trim()) throw new Error("Doctor requires --ak-doctor-case"); const soul = (await dependencies.loadSoul()).trim(); if (!soul) throw new Error("Doctor soul is empty"); const patient = await dependencies.loadCase(path); activation = { soul, patient, store: new DoctorEvidenceStore(patient) };
|
|
18
14
|
if (!registered) { registered = true; pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "Doctor Evidence", description: "Read retained Pi session bytes with bounded pagination.", parameters: doctorEvidenceReadSchema, async execute(_id, params: { evidenceId: string; offset?: number; limit?: number }) { if (!activation) throw new Error("Doctor is not activated"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details }; } });
|
|
19
|
-
pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "Doctor Output", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id, params, signal, _update, ctx): Promise<AgentToolResult<unknown>> { if (!activation) throw new Error("Doctor is not activated"); singleton(id, ctx); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendActiveSessionCustomEntry(ctx, DOCTOR_CANDIDATE_ENTRY_TYPE, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity }, { unavailable: "doctor candidate retention is unavailable", failed: "doctor candidate retention failed" }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal }); } catch (error) { host.failInfrastructure(error, ctx, id); } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails =
|
|
15
|
+
pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "Doctor Output", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id, params, signal, _update, ctx): Promise<AgentToolResult<unknown>> { if (!activation) throw new Error("Doctor is not activated"); singleton(id, ctx); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendActiveSessionCustomEntry(ctx, DOCTOR_CANDIDATE_ENTRY_TYPE, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity }, { unavailable: "doctor candidate retention is unavailable", failed: "doctor candidate retention failed" }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal }); } catch (error) { host.failInfrastructure(error, ctx, id); } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision<AgentToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: "Doctor output accepted" }], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text" as const, text: "Doctor output accepted; compliance audit produced no receipt" }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true as const, ...usageProjection }), revise: (violations) => { throw new Error(`Doctor output violates its soul: ${violations.join("; ")}`); }, escalate: (result) => result }, acceptedDetails); } });
|
|
20
16
|
pi.on("before_agent_start", (event) => { if (!activation) throw new Error("Doctor is not activated"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { provenance: "runtime-derived from retained session bytes and sealed into the accepted receipt", cost: activation.patient.cost }, lawfulTargetKeys: ["case", ...activation.patient.cost.invocations.sources], evidence: activation.patient.evidence.map(({ id, kind, sha256, byteLength, contentLength }) => ({ id, kind, sha256, byteLength, contentLength })) }; return { systemPrompt: `${event.systemPrompt}\n\n<doctor_soul>\n${activation.soul}\n</doctor_soul>\n\n<doctor_case>\n${JSON.stringify(catalog)}\n</doctor_case>` }; }); }
|
|
21
17
|
const required = [DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME]; const names = pi.getAllTools().map((tool) => tool.name); for (const name of required) if (names.filter((item) => item === name).length !== 1) throw new Error(`Doctor required tool collision or missing: ${name}`); pi.setActiveTools(required); const active = pi.getActiveTools?.() ?? required; if (active.length !== 2 || !required.every((name) => active.includes(name))) throw new Error("Doctor active tool narrowing failed"); } };
|
|
22
18
|
}
|
|
@@ -130,8 +130,29 @@ export function readDoctorAuditSubjects(context: ExtensionContext): SubjectResol
|
|
|
130
130
|
return { status: "incomplete", observation: { kind: "missing-subject", subject: "candidate-testimony" } };
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Missing dossier/subject is infrastructure failure, not a judgment status (#475).
|
|
135
|
+
* Observation + empty candidate ride the existing failInfrastructure → error artifact path.
|
|
136
|
+
*/
|
|
137
|
+
export class AuditMaterialsUnavailableError extends Error {
|
|
138
|
+
readonly observation: DossierObservation;
|
|
139
|
+
readonly candidate: undefined;
|
|
140
|
+
constructor(observation: DossierObservation) {
|
|
141
|
+
const detail =
|
|
142
|
+
observation.kind === "missing-subject"
|
|
143
|
+
? `${observation.kind}:${observation.subject}`
|
|
144
|
+
: observation.kind;
|
|
145
|
+
super(`Audit materials unavailable: ${detail}`);
|
|
146
|
+
this.name = "AuditMaterialsUnavailableError";
|
|
147
|
+
this.observation = observation;
|
|
148
|
+
this.candidate = undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function requireAuditMaterials(
|
|
153
|
+
resolution: DossierResolution | SubjectResolution,
|
|
154
|
+
): asserts resolution is DossierOk | SubjectOk {
|
|
155
|
+
if (resolution.status === "incomplete") {
|
|
156
|
+
throw new AuditMaterialsUnavailableError(resolution.observation);
|
|
157
|
+
}
|
|
137
158
|
}
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Package-owned engine detour tool (#357 T2 / #378 / #380).
|
|
3
3
|
* Registered by shared role-runtime when any role + engine activation signal is present.
|
|
4
4
|
* Evidence-child legs install the same definition via customTools (no spawn in role modules).
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Caller AbortSignal cancel propagates without fallback.
|
|
5
|
+
* Engine process failures stop through the host infrastructure-failure seam.
|
|
6
|
+
* Caller AbortSignal cancellation propagates unchanged.
|
|
8
7
|
*/
|
|
9
8
|
import type {
|
|
10
9
|
AgentToolResult,
|
|
@@ -22,11 +21,6 @@ import {
|
|
|
22
21
|
isEngineDetourFailure,
|
|
23
22
|
runEngineDetourOnce,
|
|
24
23
|
} from "./engine-detour.ts";
|
|
25
|
-
import {
|
|
26
|
-
activationEngineLaborFallbackLatch,
|
|
27
|
-
recordEngineLaborFallback,
|
|
28
|
-
type EngineLaborFallbackField,
|
|
29
|
-
} from "./engine-labor-fallback.ts";
|
|
30
24
|
|
|
31
25
|
const engineDetourArgsSchema = Type.Object(
|
|
32
26
|
{
|
|
@@ -56,27 +50,7 @@ export type EngineDetourToolRegistration = {
|
|
|
56
50
|
|
|
57
51
|
type EngineDetourLatch = { used: boolean };
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
field: EngineLaborFallbackField,
|
|
61
|
-
failure: string,
|
|
62
|
-
): AgentToolResult<unknown> {
|
|
63
|
-
return {
|
|
64
|
-
content: [
|
|
65
|
-
{
|
|
66
|
-
type: "text" as const,
|
|
67
|
-
text:
|
|
68
|
-
`Engine detour failed: ${failure}. Perform the labor in this session (seat main road) and submit via the existing typed path. A mechanical fallback declaration will attach to the typed receipt.`,
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
details: {
|
|
72
|
-
tool: ENGINE_DETOUR_TOOL_NAME,
|
|
73
|
-
detourFailed: true,
|
|
74
|
-
...field,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Caller/upper-layer cancel must propagate; process failure is seat-fallback, not cancel. */
|
|
53
|
+
/** Caller/upper-layer cancellation must propagate unchanged. */
|
|
80
54
|
function isCallerCancellation(
|
|
81
55
|
error: unknown,
|
|
82
56
|
signal: AbortSignal | undefined,
|
|
@@ -96,8 +70,8 @@ function isCallerCancellation(
|
|
|
96
70
|
* Build one once-latch detour tool definition for a configured engine name.
|
|
97
71
|
* `latch` is shared so parent registration can reset between activations.
|
|
98
72
|
* `fail` owns host abort (parent) vs throw (evidence child) for tool misuse only.
|
|
99
|
-
* Engine process
|
|
100
|
-
* Caller AbortSignal
|
|
73
|
+
* Engine process failures (nonzero/empty/spawn) stop via `fail` with their cause.
|
|
74
|
+
* Caller AbortSignal cancellation propagates unchanged.
|
|
101
75
|
*/
|
|
102
76
|
export function createEngineDetourToolDefinition(input: {
|
|
103
77
|
engineName: string;
|
|
@@ -116,7 +90,7 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
116
90
|
`Use ${ENGINE_DETOUR_TOOL_NAME} exactly once for the configured engine (${engineName}). Optional packaged notes are guidance when present; a bare engine name alone is also a valid call path.`,
|
|
117
91
|
"Pass argv for the host CLI of this engine name — first element is the executable name on PATH. Follow optional packaged notes when delivered; otherwise act from the engine name and the host CLI actual interface. Do not invent package flags.",
|
|
118
92
|
"On success, use the returned stdout as labor content for the existing typed submission / report path.",
|
|
119
|
-
"
|
|
93
|
+
"An engine process failure stops this activation; do not continue labor in this session.",
|
|
120
94
|
],
|
|
121
95
|
parameters: engineDetourArgsSchema,
|
|
122
96
|
async execute(
|
|
@@ -145,17 +119,6 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
145
119
|
);
|
|
146
120
|
}
|
|
147
121
|
|
|
148
|
-
const softFail = (failure: string): AgentToolResult<unknown> => {
|
|
149
|
-
// Activation-scoped latch is the sole shared recorder (parent seat + legs).
|
|
150
|
-
const fallbackLatch =
|
|
151
|
-
activationEngineLaborFallbackLatch() ?? { field: undefined };
|
|
152
|
-
const field = recordEngineLaborFallback(fallbackLatch, {
|
|
153
|
-
engine: engineName,
|
|
154
|
-
failure,
|
|
155
|
-
});
|
|
156
|
-
return seatFallbackToolResult(field, failure);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
122
|
let result: Awaited<ReturnType<typeof runEngineDetourOnce>>;
|
|
160
123
|
try {
|
|
161
124
|
result = await runEngineDetourOnce({
|
|
@@ -164,17 +127,19 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
164
127
|
...(signal === undefined ? {} : { signal }),
|
|
165
128
|
});
|
|
166
129
|
} catch (error) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
error instanceof Error ? error.message : String(error);
|
|
173
|
-
return softFail(failure.trim() === "" ? "engine detour spawn failed" : failure);
|
|
130
|
+
if (isCallerCancellation(error, signal)) throw error;
|
|
131
|
+
const cause = error instanceof Error
|
|
132
|
+
? error
|
|
133
|
+
: new Error(String(error).trim() || "engine detour spawn failed");
|
|
134
|
+
input.fail(cause, toolCallId, ctx);
|
|
174
135
|
}
|
|
175
136
|
|
|
176
137
|
if (isEngineDetourFailure(result)) {
|
|
177
|
-
|
|
138
|
+
input.fail(
|
|
139
|
+
new Error(engineDetourFailureDiagnostic(result)),
|
|
140
|
+
toolCallId,
|
|
141
|
+
ctx,
|
|
142
|
+
);
|
|
178
143
|
}
|
|
179
144
|
|
|
180
145
|
return {
|
|
@@ -610,15 +610,17 @@ export async function executeEvidenceChild(
|
|
|
610
610
|
engine: engineName,
|
|
611
611
|
packageRoot: options.packageRoot,
|
|
612
612
|
});
|
|
613
|
-
//
|
|
614
|
-
//
|
|
613
|
+
// Evidence legs use the parent detour tool; retain any engine process cause
|
|
614
|
+
// so the enclosing child boundary, rather than a tool-error result, terminates.
|
|
615
|
+
let engineDetourFailure: Error | undefined;
|
|
615
616
|
const engineDetourTool =
|
|
616
617
|
engineName === undefined
|
|
617
618
|
? undefined
|
|
618
619
|
: createEngineDetourToolDefinition({
|
|
619
620
|
engineName,
|
|
620
621
|
fail(error) {
|
|
621
|
-
|
|
622
|
+
engineDetourFailure ??= error instanceof Error ? error : new Error(String(error));
|
|
623
|
+
throw engineDetourFailure;
|
|
622
624
|
},
|
|
623
625
|
});
|
|
624
626
|
// No tools allowlist — Pi defaults + unrestricted evidence surface (ADR 0064).
|
|
@@ -654,8 +656,14 @@ export async function executeEvidenceChild(
|
|
|
654
656
|
try {
|
|
655
657
|
await session.prompt(delivered);
|
|
656
658
|
} catch (error) {
|
|
659
|
+
if (engineDetourFailure !== undefined) {
|
|
660
|
+
throw classifiedError(engineDetourFailure, "child");
|
|
661
|
+
}
|
|
657
662
|
throw classifiedError(error, "provider");
|
|
658
663
|
}
|
|
664
|
+
if (engineDetourFailure !== undefined) {
|
|
665
|
+
throw classifiedError(engineDetourFailure, "child");
|
|
666
|
+
}
|
|
659
667
|
if (signal?.aborted) throw new Error("Evidence child was cancelled");
|
|
660
668
|
const lastAssistant = [...session.messages]
|
|
661
669
|
.reverse()
|
|
@@ -927,7 +935,7 @@ export async function executeAuditorChild(
|
|
|
927
935
|
decision = part.arguments;
|
|
928
936
|
decisionCallId = part.id;
|
|
929
937
|
// Pi can reject malformed root arguments before invoking execute;
|
|
930
|
-
// that remains the existing
|
|
938
|
+
// that remains the existing unreadable-candidate failure path.
|
|
931
939
|
if (part.arguments === undefined) decisionSubmitted = true;
|
|
932
940
|
}
|
|
933
941
|
}
|
package/src/factory-board.ts
CHANGED
|
@@ -20,7 +20,6 @@ import { randomUUID } from "node:crypto";
|
|
|
20
20
|
import { lstat, mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
21
21
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
22
22
|
|
|
23
|
-
import { seatFallbackBaseStatus } from "./engine-labor-fallback.ts";
|
|
24
23
|
import {
|
|
25
24
|
formatDurationZh,
|
|
26
25
|
formatLocalDateTime,
|
|
@@ -177,8 +176,7 @@ export function decideTicketCurrentState(input: {
|
|
|
177
176
|
const latest = sortRunsByStart(input.runs).at(-1)!;
|
|
178
177
|
if (!latest.hasResult) return unacceptedBand(latest.mtimeMs, input.now.getTime());
|
|
179
178
|
// Escalate is an awaiting overlay: same placement and sort band, distinct state.
|
|
180
|
-
|
|
181
|
-
if (seatFallbackBaseStatus(latest.resultStatus) === "escalate") return "escalate-awaiting";
|
|
179
|
+
if ((latest.resultStatus) === "escalate") return "escalate-awaiting";
|
|
182
180
|
return "accepted-awaiting";
|
|
183
181
|
}
|
|
184
182
|
|
|
@@ -847,7 +845,7 @@ function buildBreadcrumbSteps(runs: readonly TicketTrajectoryRun[]): BreadcrumbS
|
|
|
847
845
|
const isRejected = group.runs.some(
|
|
848
846
|
(run) =>
|
|
849
847
|
run.hasResult &&
|
|
850
|
-
REJECTED_RESULT_STATUSES.has(
|
|
848
|
+
REJECTED_RESULT_STATUSES.has((run.resultStatus)),
|
|
851
849
|
);
|
|
852
850
|
steps.push({
|
|
853
851
|
station: group.station,
|