@akagilnc/pi-workflow-roles 0.1.4956 → 0.1.4980
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 +4 -4
- package/dist/acp-host/production-host.js +157 -266
- package/dist/audit-escalation.js +19 -3
- package/dist/compliance-transport.js +6 -11
- package/dist/doctor-contracts.js +0 -2
- package/dist/gatekeeper-role.js +5 -3
- package/dist/headless-host/production-host.js +157 -266
- package/dist/migrate-book-topology.js +1 -16
- package/dist/packaged-role-registry.js +0 -24
- package/dist/public-cli/main.js +16 -28
- package/dist/public-cli/settlement.js +8 -1
- package/dist/public-cli/terminal.js +2 -2
- package/dist/secretariat-contracts.js +1 -3
- package/dist/submission-ledger.js +11 -0
- package/package.json +1 -1
- package/src/audit-escalation.ts +26 -5
- package/src/collector-role.ts +1 -5
- package/src/compliance-transport.ts +10 -12
- package/src/doctor-auditor.ts +2 -0
- package/src/doctor-contracts.ts +0 -1
- package/src/doctor-role.ts +4 -4
- package/src/gatekeeper-pass-envelope.ts +7 -10
- package/src/gatekeeper-role.ts +5 -3
- package/src/judge-role.ts +45 -31
- package/src/merger-role.ts +1 -2
- package/src/notary-role.ts +1 -2
- package/src/packaged-role-registry.ts +0 -25
- package/src/public-cli/settlement.ts +10 -1
- package/src/public-cli/terminal.ts +2 -2
- package/src/reviewer-role.ts +1 -2
- package/src/role-runtime.ts +59 -156
- package/src/secretariat-contracts.ts +0 -2
- package/src/secretariat-role.ts +3 -162
- package/src/submission-ledger.ts +18 -0
- package/src/worker-role.ts +42 -24
|
@@ -6,6 +6,7 @@ import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
|
6
6
|
import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
7
7
|
import { OFFICER_CONCLUSION_REASK } from "./gatekeeper-role.ts";
|
|
8
8
|
import { coalesceSubmissionRows } from "./public-cli/terminal.ts";
|
|
9
|
+
import { readableGateItem } from "./readable-gate-item.ts";
|
|
9
10
|
|
|
10
11
|
export type ComplianceNoReceipt = NoReceiptLifecycleFacts & { status: "no-receipt"; usage?: Usage };
|
|
11
12
|
/**
|
|
@@ -34,7 +35,7 @@ export type ComplianceDecision =
|
|
|
34
35
|
| ComplianceNoReceipt
|
|
35
36
|
| ComplianceReceived
|
|
36
37
|
| ComplianceTransportFailure;
|
|
37
|
-
/**
|
|
38
|
+
/** Source-run pointer remains the binding locator; parent body rides the separate instruction. */
|
|
38
39
|
export const AUDITOR_DOSSIER_PROMPT = "卷宗指针:" as const;
|
|
39
40
|
|
|
40
41
|
export const COMPLIANCE_RESPONSE_ENTRY_TYPE = "ak_compliance_response" as const;
|
|
@@ -106,12 +107,15 @@ export type AuditorSummon = (
|
|
|
106
107
|
signal?: AbortSignal,
|
|
107
108
|
/** Plain-language re-ask when the prior reply was not a three-state conclusion. */
|
|
108
109
|
reask?: string,
|
|
110
|
+
/** Parent's verbatim current submission, serialized when structured. */
|
|
111
|
+
submission?: string,
|
|
109
112
|
) => Promise<PublicSummonResult>;
|
|
110
113
|
|
|
111
114
|
export type RunComplianceAuditOptions = {
|
|
112
115
|
/** Who is being audited — selects judge-auditor.md / doctor-auditor.md. */
|
|
113
116
|
readonly subject: AuditorSoulRole;
|
|
114
117
|
context: HostContext;
|
|
118
|
+
submission?: unknown;
|
|
115
119
|
runDirectory?: string | undefined;
|
|
116
120
|
signal?: AbortSignal;
|
|
117
121
|
/** Test seam — production uses summonPublicRole({ role: "auditor", argv: ["--subject", subject, "--source-run", …] }). */
|
|
@@ -169,16 +173,8 @@ async function projectAuditorTerminal(summoned: PublicSummonResult): Promise<Com
|
|
|
169
173
|
if (rows.length === 0) {
|
|
170
174
|
return readComplianceCandidate({}, usage);
|
|
171
175
|
}
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
if (rows.length === 1) return decision;
|
|
175
|
-
if (decision.status === "pass" || decision.status === "bounce" || decision.status === "escalate") {
|
|
176
|
-
return { ...decision, receipt: rows };
|
|
177
|
-
}
|
|
178
|
-
if (decision.status === "received") {
|
|
179
|
-
return { ...decision, reply: rows };
|
|
180
|
-
}
|
|
181
|
-
return decision;
|
|
176
|
+
// The terminal retains history; the decision carries only the current reply.
|
|
177
|
+
return readComplianceCandidate(rows[rows.length - 1], usage);
|
|
182
178
|
}
|
|
183
179
|
// Unknown terminal kind: still not a shape judgment — surface as received reply.
|
|
184
180
|
return {
|
|
@@ -194,6 +190,7 @@ export async function runComplianceAudit(options: RunComplianceAuditOptions): Pr
|
|
|
194
190
|
throw new Error("Compliance audit requires a parent run directory pointer");
|
|
195
191
|
}
|
|
196
192
|
const subject = options.subject;
|
|
193
|
+
const submission = options.submission === undefined ? undefined : readableGateItem(options.submission);
|
|
197
194
|
const summon =
|
|
198
195
|
options.summonAuditor
|
|
199
196
|
?? (async (
|
|
@@ -221,11 +218,12 @@ export async function runComplianceAudit(options: RunComplianceAuditOptions): Pr
|
|
|
221
218
|
home,
|
|
222
219
|
...(auditSignal === undefined ? {} : { signal: auditSignal }),
|
|
223
220
|
...(reask === undefined ? {} : { reviewReask: reask }),
|
|
221
|
+
...(submission === undefined ? {} : { gateReviewInstruction: submission }),
|
|
224
222
|
});
|
|
225
223
|
});
|
|
226
224
|
let reask: string | undefined;
|
|
227
225
|
for (;;) {
|
|
228
|
-
const summoned = await summon(subject, runDirectory, options.signal, reask);
|
|
226
|
+
const summoned = await summon(subject, runDirectory, options.signal, reask, submission);
|
|
229
227
|
const decision = await projectAuditorTerminal(summoned);
|
|
230
228
|
if (decision.status === "received") {
|
|
231
229
|
reask = OFFICER_CONCLUSION_REASK;
|
package/src/doctor-auditor.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { DOCTOR_AUDIT_TOOL_NAME } from "./doctor-contracts.ts";
|
|
|
14
14
|
|
|
15
15
|
export type DoctorAuditOptions = {
|
|
16
16
|
context: HostContext;
|
|
17
|
+
submission: unknown;
|
|
17
18
|
signal?: AbortSignal;
|
|
18
19
|
/** Same seam as runComplianceAudit options — offline tracers only. */
|
|
19
20
|
summonAuditor?: AuditorSummon;
|
|
@@ -33,6 +34,7 @@ export function createPiDoctorAuditor(): (options: DoctorAuditOptions) => Promis
|
|
|
33
34
|
return runComplianceAudit({
|
|
34
35
|
subject: "doctor",
|
|
35
36
|
context: options.context,
|
|
37
|
+
submission: options.submission,
|
|
36
38
|
...(auditorRunDirectory(options.context) === undefined
|
|
37
39
|
? {}
|
|
38
40
|
: { runDirectory: auditorRunDirectory(options.context) }),
|
package/src/doctor-contracts.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { withTerminatingOutputDeclarations } from "./package-contracts/terminati
|
|
|
6
6
|
export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
|
|
7
7
|
export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
|
|
8
8
|
export const DOCTOR_AUDIT_TOOL_NAME = "ak_doctor_audit_decision";
|
|
9
|
-
export const DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT = "太医署回执已接受;审计无回执";
|
|
10
9
|
export const DOCTOR_OUTPUT_TOOL_DESCRIPTION = "提交唯一终局单案证词;completed 允许空 findings。";
|
|
11
10
|
export const DOCTOR_TARGET_KINDS = ["law", "gate", "template", "station", "seat"] as const;
|
|
12
11
|
export type DoctorTargetKind = typeof DOCTOR_TARGET_KINDS[number];
|
package/src/doctor-role.ts
CHANGED
|
@@ -3,14 +3,14 @@ import { disposeComplianceDecision } from "./audit-escalation.ts";
|
|
|
3
3
|
import { ComplianceResponseRetentionError, type ComplianceDecision } from "./compliance-transport.ts";
|
|
4
4
|
import { GatekeeperDecisionError } from "./submission-errors.ts";
|
|
5
5
|
import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { readableGateItem } from "./readable-gate-item.ts";
|
|
7
|
+
import { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
|
|
8
8
|
|
|
9
9
|
import { sitianReport } from "./sitian-facade.ts";
|
|
10
10
|
|
|
11
11
|
export { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME };
|
|
12
12
|
export const DOCTOR_CASE_FLAG = { name: "ak-doctor-case", definition: { description: "Retained .ak-roles/books/<book>/<n>/runs directory", type: "string" as const } } as const;
|
|
13
|
-
export type DoctorRoleDependencies = { loadSoul(): Promise<string>; loadCase(path: string): Promise<DoctorCase>; auditCompliance(options: { context: HostContext; signal?: AbortSignal }): Promise<ComplianceDecision> };
|
|
13
|
+
export type DoctorRoleDependencies = { loadSoul(): Promise<string>; loadCase(path: string): Promise<DoctorCase>; auditCompliance(options: { context: HostContext; signal?: AbortSignal; submission: unknown }): Promise<ComplianceDecision> };
|
|
14
14
|
function appendCandidate(ctx: HostContext, data: unknown): void {
|
|
15
15
|
try {
|
|
16
16
|
sitianReport({
|
|
@@ -37,7 +37,7 @@ export function createDoctorRoleRuntime(pi: RoleHost, dependencies: DoctorRoleDe
|
|
|
37
37
|
let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
|
|
38
38
|
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) };
|
|
39
39
|
if (!registered) { registered = true; pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "太医署证据", description: "分页读取留存的 Pi session 字节。", parameters: doctorEvidenceReadSchema, async execute(_id: string, params: { evidenceId: string; offset?: number; limit?: number }) { if (!activation) throw new Error("太医署未激活"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details }; } });
|
|
40
|
-
pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id: string, params: unknown, signal: AbortSignal | undefined, _update: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> { if (!activation) throw new Error("太医署未激活"); const active = activation; const testimony = validateDoctorOutput(params, active.patient, active.store); try { appendCandidate(ctx, { version: 1, testimony, cost: active.patient.cost, readRecord: active.store.readRecord(), patientIdentity: active.patient.identity }); } 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 acceptedDetails = testimony; return disposeComplianceDecision<HostToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text:
|
|
40
|
+
pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id: string, params: unknown, signal: AbortSignal | undefined, _update: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> { if (!activation) throw new Error("太医署未激活"); const active = activation; const testimony = validateDoctorOutput(params, active.patient, active.store); try { appendCandidate(ctx, { version: 1, testimony, cost: active.patient.cost, readRecord: active.store.readRecord(), patientIdentity: active.patient.identity }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx, submission: testimony } : { context: ctx, signal, submission: testimony }); } catch (error) { host.failInfrastructure(error, ctx, id); } const acceptedDetails = testimony; return disposeComplianceDecision<HostToolResult<unknown>>(audit, { pass: (usage) => ({ content: audit.status === "pass" && audit.receipt !== undefined ? [{ type: "text" as const, text: readableGateItem(audit.receipt) }] : [], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => { try { appendCandidate(ctx, { version: 1, testimony, cost: active.patient.cost, auditNoReceipt, readRecord: active.store.readRecord(), patientIdentity: active.patient.identity }); } catch (error) { host.failInfrastructure(error, ctx, id); } return { content: [], details: acceptedDetails, terminate: true as const, ...usageProjection }; }, received: () => { throw new Error("审刑院非三态应交 runComplianceAudit 重问说话者"); }, bounce: (violations) => { throw new GatekeeperDecisionError({ status: "bounce", officer: "auditor", receipt: audit.status === "bounce" ? audit.receipt ?? violations : violations }); }, escalate: (result) => result, transportFailure: (facts) => { host.failInfrastructure(Object.assign(new Error(facts.diagnostic), { name: "ComplianceTransportFailure", ...(facts.submissions === undefined && facts.terminal === undefined ? {} : { submission: facts.submissions ?? facts.terminal }) }), ctx, id); } }, acceptedDetails); } });
|
|
41
41
|
pi.on("before_agent_start", (event) => { if (!activation) throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { 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>` }; }); }
|
|
42
42
|
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"); } };
|
|
43
43
|
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Queue guarantee only (#753 / #756 / #750):
|
|
7
7
|
* parent submit → summon officer → read conclusion field
|
|
8
8
|
* pass → accept end
|
|
9
|
-
* bounce
|
|
10
|
-
*
|
|
9
|
+
* bounce → raw officer receipt back to parent; escalate → audit_escalation terminal,
|
|
10
|
+
* without binding a correctable parent submission
|
|
11
11
|
* not three-state → resume officer with plain-language re-ask (no round cap)
|
|
12
12
|
* transport / no_receipt → present honestly
|
|
13
13
|
* Four pairs: countersign↔notary, judge↔auditor, worker↔inspector, secretariat↔countersign (#969).
|
|
@@ -127,7 +127,7 @@ export async function requireGatekeeperPass(options: {
|
|
|
127
127
|
readonly summonOfficer?: GateOfficerSummon;
|
|
128
128
|
}): Promise<GatekeeperPassOutcome | void> {
|
|
129
129
|
let reask: string | undefined;
|
|
130
|
-
// No round cap — end only on pass, bounce/escalate
|
|
130
|
+
// No round cap — end only on pass, bounce/escalate, or real failure.
|
|
131
131
|
const summonOfficer =
|
|
132
132
|
options.summonOfficer ??
|
|
133
133
|
createDefaultGateOfficerSummon({
|
|
@@ -176,15 +176,12 @@ export async function requireGatekeeperPass(options: {
|
|
|
176
176
|
});
|
|
177
177
|
options.hostActions.failInfrastructure(failure, options.context, options.toolCallId);
|
|
178
178
|
}
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
if (
|
|
182
|
-
gatekeeper.status === "escalate"
|
|
183
|
-
&& options.subject.kind === "secretariat_verdict"
|
|
184
|
-
) {
|
|
179
|
+
// Escalation ends the parent without a correctable tool error or resubmission.
|
|
180
|
+
// Each caller projects the existing audit_escalation terminal.
|
|
181
|
+
if (gatekeeper.status === "escalate") {
|
|
185
182
|
throw new GatekeeperDecisionError(gatekeeper);
|
|
186
183
|
}
|
|
187
|
-
// bounce |
|
|
184
|
+
// bounce | no_receipt: parent stands and may resubmit. Not a run abort.
|
|
188
185
|
options.hostActions.bindSubmissionNonPass(options.toolCallId, gatekeeper);
|
|
189
186
|
throw new GatekeeperDecisionError(gatekeeper);
|
|
190
187
|
}
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -373,15 +373,17 @@ function projectOfficerTerminal(
|
|
|
373
373
|
status: "escalate",
|
|
374
374
|
officer,
|
|
375
375
|
// This-court receipt only (#879) — historical rows remain on terminal.submissions.
|
|
376
|
-
receipt:
|
|
376
|
+
receipt: outcome.decisiveFacts !== undefined && Object.hasOwn(outcome.decisiveFacts, "auditEscalationReceipt")
|
|
377
|
+
? outcome.decisiveFacts.auditEscalationReceipt
|
|
378
|
+
: thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome),
|
|
377
379
|
},
|
|
378
380
|
summoned,
|
|
379
381
|
);
|
|
380
382
|
}
|
|
381
383
|
if (outcome.kind === "accepted") {
|
|
382
384
|
// outcome.status is the fixture/compat leaf: production settlement leaves
|
|
383
|
-
// it undefined once payloads are recorded
|
|
384
|
-
//
|
|
385
|
+
// it undefined once payloads are recorded. It may interpret a receipt
|
|
386
|
+
// lacking its own status, never substitute for a missing receipt.
|
|
385
387
|
return withOfficerRunId(
|
|
386
388
|
projectOfficerPayloads(officer, thisCourt, outcome.status),
|
|
387
389
|
summoned,
|
package/src/judge-role.ts
CHANGED
|
@@ -2,9 +2,10 @@ import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } fro
|
|
|
2
2
|
import { Type, type Static } from "typebox";
|
|
3
3
|
|
|
4
4
|
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
5
|
-
import { ParentQueueReaskError } from "./submission-errors.ts";
|
|
5
|
+
import { GatekeeperDecisionError, ParentQueueReaskError } from "./submission-errors.ts";
|
|
6
|
+
import { projectGatekeeperEscalation } from "./audit-escalation.ts";
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import { readableGateItem } from "./readable-gate-item.ts";
|
|
8
9
|
import {
|
|
9
10
|
JUDGE_OUTPUT_TOOL_NAME,
|
|
10
11
|
validateAcceptedJudgeDetails,
|
|
@@ -64,7 +65,10 @@ export type JudgeRoleDependencies = {
|
|
|
64
65
|
loadSoul(): Promise<string>;
|
|
65
66
|
};
|
|
66
67
|
|
|
67
|
-
export type JudgeRoleHostActions = HostGatekeeperActions
|
|
68
|
+
export type JudgeRoleHostActions = HostGatekeeperActions & {
|
|
69
|
+
/** Preserve the first officer's pass as a separate model-visible result item if the second gate fails. */
|
|
70
|
+
bindPriorGatePass(toolCallId: string, receipt: unknown): void;
|
|
71
|
+
};
|
|
68
72
|
|
|
69
73
|
export function validateVerdict(verdict: JudgeVerdictParameters): JudgeVerdict {
|
|
70
74
|
return validateAcceptedJudgeDetails(verdict);
|
|
@@ -107,7 +111,7 @@ export function createJudgeRoleRuntime(
|
|
|
107
111
|
// Parent escalate → throw to caller as-is; officers do not attend (#753 / #756).
|
|
108
112
|
const verdict = validateVerdict(parameters);
|
|
109
113
|
return {
|
|
110
|
-
content: [
|
|
114
|
+
content: [],
|
|
111
115
|
details: verdict,
|
|
112
116
|
terminate: true as const,
|
|
113
117
|
};
|
|
@@ -115,33 +119,43 @@ export function createJudgeRoleRuntime(
|
|
|
115
119
|
const verdict = validateVerdict(parameters);
|
|
116
120
|
// Candidate verdict is already on the parent session books as this
|
|
117
121
|
// tool-call leaf (first-record-then-audit; run 019fea05 L61/L62).
|
|
118
|
-
// #753: 符宝郎内闸 —
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
122
|
+
// #753: 符宝郎内闸 — bounce returns raw receipt; escalate pauses.
|
|
123
|
+
let draftPass: { receipt: unknown } | undefined;
|
|
124
|
+
try {
|
|
125
|
+
const obtainedDraftPass = await pi.requireGatekeeperPass!({
|
|
126
|
+
context: ctx,
|
|
127
|
+
subject: { kind: "judge_draft" },
|
|
128
|
+
...(signal === undefined ? {} : { signal }),
|
|
129
|
+
hostActions,
|
|
130
|
+
toolCallId,
|
|
131
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
132
|
+
submission: parameters,
|
|
133
|
+
});
|
|
134
|
+
draftPass = obtainedDraftPass || undefined;
|
|
135
|
+
if (draftPass !== undefined) hostActions.bindPriorGatePass(toolCallId, draftPass.receipt);
|
|
136
|
+
// #756: 审刑院合规路径 — same review-queue law as 符宝郎/台院.
|
|
137
|
+
// pass → accept; bounce → raw auditor receipt; escalate → pause;
|
|
138
|
+
// not three-state → resume auditor; no round cap; no disposeCompliance mapping.
|
|
139
|
+
const compliancePass = await pi.requireGatekeeperPass!({
|
|
140
|
+
context: ctx,
|
|
141
|
+
subject: { kind: "judge_compliance" },
|
|
142
|
+
...(signal === undefined ? {} : { signal }),
|
|
143
|
+
hostActions,
|
|
144
|
+
toolCallId,
|
|
145
|
+
// #879: same parent payload for 审刑院; not recovered from session latest.
|
|
146
|
+
submission: parameters,
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
content: [draftPass, compliancePass].filter((pass) => pass !== undefined).map((pass) => ({ type: "text" as const, text: readableGateItem(pass.receipt) })),
|
|
150
|
+
details: verdict,
|
|
151
|
+
terminate: true as const,
|
|
152
|
+
};
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error instanceof GatekeeperDecisionError && error.result.status === "escalate") {
|
|
155
|
+
return projectGatekeeperEscalation(error.result, verdict);
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
145
159
|
},
|
|
146
160
|
});
|
|
147
161
|
pi.on("before_agent_start", (event) => {
|
package/src/merger-role.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { RoleHost, HostContext } from "./host-contracts.ts";
|
|
2
|
-
import { packagedRoleAcceptedText } from "./packaged-role-registry.ts";
|
|
3
2
|
import {
|
|
4
3
|
MERGER_OUTPUT_TOOL_NAME,
|
|
5
4
|
mergerOutputSchema,
|
|
@@ -40,7 +39,7 @@ export function createMergerRoleRuntime(pi: RoleHost, dependencies: MergerRoleDe
|
|
|
40
39
|
name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate。", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
|
|
41
40
|
async execute(_id: string, params: unknown, _signal: AbortSignal | undefined, _update: unknown, _ctx: HostContext) {
|
|
42
41
|
if (!activation) throw new Error("校书郎未激活");
|
|
43
|
-
return { content: [
|
|
42
|
+
return { content: [], details: params, terminate: true as const };
|
|
44
43
|
},
|
|
45
44
|
});
|
|
46
45
|
pi.on("before_agent_start", (event) => {
|
package/src/notary-role.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts"
|
|
|
6
6
|
* This module keeps evidence assembly + projection only.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { packagedRoleAcceptedText } from "./packaged-role-registry.ts";
|
|
10
9
|
import {
|
|
11
10
|
NOTARY_OUTPUT_TOOL_NAME,
|
|
12
11
|
NOTARY_SOURCE_RUN_FLAG,
|
|
@@ -170,7 +169,7 @@ export function createNotaryRoleRuntime(
|
|
|
170
169
|
// #753: handler only records — params as submitted, no findings/disposition rewrite.
|
|
171
170
|
// #541 infra declaration + sole-final barrier are ledger-owned (#575).
|
|
172
171
|
return {
|
|
173
|
-
content: [
|
|
172
|
+
content: [],
|
|
174
173
|
details: parameters,
|
|
175
174
|
terminate: true as const,
|
|
176
175
|
};
|
|
@@ -87,7 +87,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
87
87
|
auditTool: JUDGE_AUDIT_TOOL_NAME,
|
|
88
88
|
settlement: "sealed",
|
|
89
89
|
runnerFailure: "engine-detour-known-first",
|
|
90
|
-
acceptedText: "大理寺回执已接受",
|
|
91
90
|
/** Factory board: court until another station has started, then marshal. */
|
|
92
91
|
boardPlacement: "judge-history",
|
|
93
92
|
/** Navigator subject is the public admitted instruction when the run is bound. */
|
|
@@ -126,7 +125,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
126
125
|
],
|
|
127
126
|
method: "observed",
|
|
128
127
|
},
|
|
129
|
-
acceptedText: "修内司回执已接受",
|
|
130
128
|
boardPlacement: "marshal",
|
|
131
129
|
activationFlags: [
|
|
132
130
|
{ field: "packetPath", flag: "ak-fix-packet", binds: "input" },
|
|
@@ -165,7 +163,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
165
163
|
],
|
|
166
164
|
method: "optional",
|
|
167
165
|
},
|
|
168
|
-
acceptedText: "将作监回执已接受",
|
|
169
166
|
activationFlags: [
|
|
170
167
|
{ field: "taskPath", flag: "ak-coder-task", binds: "input" },
|
|
171
168
|
{ field: "phase", flag: "ak-coder-phase", binds: "phase" },
|
|
@@ -211,7 +208,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
211
208
|
/** Frozen base/lens/authority become the initial prompt; instruction follows. */
|
|
212
209
|
transportPrompt: "skill-args",
|
|
213
210
|
runnerFailure: "engine-detour-record-first",
|
|
214
|
-
acceptedText: "御史台回执已接受",
|
|
215
211
|
boardPlacement: "marshal",
|
|
216
212
|
activationFlags: [
|
|
217
213
|
{ field: "baseRevision", flag: "ak-review-base" },
|
|
@@ -253,7 +249,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
253
249
|
{ key: "manifestDigest" },
|
|
254
250
|
],
|
|
255
251
|
},
|
|
256
|
-
acceptedText: "通进司回执已接受",
|
|
257
252
|
boardPlacement: "collector",
|
|
258
253
|
/** Taishi acceptance is a typed groups array, not a status leaf. */
|
|
259
254
|
analystTerminal: "groups",
|
|
@@ -287,7 +282,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
287
282
|
],
|
|
288
283
|
doctorReportFacts: true,
|
|
289
284
|
},
|
|
290
|
-
acceptedText: "太医署回执已接受",
|
|
291
285
|
/** Navigator subject is the doctor case, not the case-path file bytes. */
|
|
292
286
|
navigatorSubject: "case",
|
|
293
287
|
activationFlags: [
|
|
@@ -323,7 +317,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
323
317
|
],
|
|
324
318
|
method: "observed",
|
|
325
319
|
},
|
|
326
|
-
acceptedText: "合并回执已接受",
|
|
327
320
|
activationFlags: [
|
|
328
321
|
{ field: "inputPath", from: "mergerInputPath", flag: "ak-merger-input", binds: "input" },
|
|
329
322
|
],
|
|
@@ -356,7 +349,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
356
349
|
/** Court reask replaces the initial prompt. Otherwise the fixed kickoff. */
|
|
357
350
|
reaskPrompt: true,
|
|
358
351
|
transportPrompt: "fixed-kickoff",
|
|
359
|
-
acceptedText: "符宝郎回执已接受",
|
|
360
352
|
gateStageLabel: "符宝郎",
|
|
361
353
|
/** Gate summon binds --source-run and keeps dialogue off the argv. */
|
|
362
354
|
gateSummon: "source-run",
|
|
@@ -381,7 +373,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
381
373
|
phases: [null],
|
|
382
374
|
outputTool: COUNTERSIGN_OUTPUT_TOOL_NAME,
|
|
383
375
|
settlement: "accepted",
|
|
384
|
-
acceptedText: "给事中回执已接受",
|
|
385
376
|
gateStageLabel: "给事中",
|
|
386
377
|
/** Gate queue reads countersignStatus, not the shared three-state status. */
|
|
387
378
|
gateDecision: "countersign-status",
|
|
@@ -407,7 +398,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
407
398
|
settlement: "accepted",
|
|
408
399
|
/** Project the durable 给事中 officer entry onto the shared accepted-tool settlement. */
|
|
409
400
|
projectCountersignTerminal: true,
|
|
410
|
-
acceptedText: "中书省回执已接受",
|
|
411
401
|
activationFlags: [
|
|
412
402
|
{ field: "ticketNumber" },
|
|
413
403
|
],
|
|
@@ -437,7 +427,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
437
427
|
settlement: "accepted",
|
|
438
428
|
/** Bound comparison base is the initial prompt; instruction follows. */
|
|
439
429
|
transportPrompt: "baseline",
|
|
440
|
-
acceptedText: "左拾遗回执已接受",
|
|
441
430
|
activationFlags: [
|
|
442
431
|
{ field: "baseRevision", flag: "ak-gleaner-left-base" },
|
|
443
432
|
],
|
|
@@ -468,7 +457,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
468
457
|
settlement: "accepted",
|
|
469
458
|
/** Court reask replaces the initial prompt. */
|
|
470
459
|
reaskPrompt: true,
|
|
471
|
-
acceptedText: "台院回执已接受",
|
|
472
460
|
gateStageLabel: "台院",
|
|
473
461
|
activationFlags: [
|
|
474
462
|
{
|
|
@@ -500,7 +488,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
500
488
|
outputTool: GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
501
489
|
settlement: "accepted",
|
|
502
490
|
runnerFailure: "engine-detour-record-first",
|
|
503
|
-
acceptedText: "门下省决议已受理",
|
|
504
491
|
activationStage: "load-and-install",
|
|
505
492
|
// Province materials; officers reuse their own public records below.
|
|
506
493
|
sessionMaterials: ["CLAUDE.md", "souls/gatekeeper.md", "souls/quality-law.md", "souls/gate-output-guide.md"],
|
|
@@ -516,7 +503,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
516
503
|
outputTool: NAVIGATOR_OUTPUT_TOOL_NAME,
|
|
517
504
|
settlement: "accepted",
|
|
518
505
|
runnerFailure: "engine-detour-record-first",
|
|
519
|
-
acceptedText: "游奕使建议已受理",
|
|
520
506
|
activationStage: "load-and-install",
|
|
521
507
|
sessionMaterials: ["CLAUDE.md", "souls/navigator.md"],
|
|
522
508
|
},
|
|
@@ -540,7 +526,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
540
526
|
/** Court reask replaces the initial prompt. */
|
|
541
527
|
reaskPrompt: true,
|
|
542
528
|
runnerFailure: "engine-detour-record-first",
|
|
543
|
-
acceptedText: "审刑院回执已接受",
|
|
544
529
|
gateStageLabel: "审刑院",
|
|
545
530
|
/** Gate summon binds --subject judge and --source-run. */
|
|
546
531
|
gateSummon: "subject-source",
|
|
@@ -559,7 +544,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
559
544
|
phases: [null],
|
|
560
545
|
outputTool: DIARIST_OUTPUT_TOOL_NAME,
|
|
561
546
|
settlement: "accepted",
|
|
562
|
-
acceptedText: "起居郎回执已接受",
|
|
563
547
|
activationStage: "load-and-install",
|
|
564
548
|
sessionMaterials: [
|
|
565
549
|
"CLAUDE.md",
|
|
@@ -660,15 +644,6 @@ export function packagedRoleOutputTool(role: string): string | undefined {
|
|
|
660
644
|
return packagedRoleMetadata(role)?.outputTool;
|
|
661
645
|
}
|
|
662
646
|
|
|
663
|
-
/** Accept-face text for a registered seat. One registry leaf; callers do not keep a copy. */
|
|
664
|
-
export function packagedRoleAcceptedText(role: string): string {
|
|
665
|
-
const record = packagedRoleMetadata(role);
|
|
666
|
-
if (record === undefined) {
|
|
667
|
-
throw new Error(`Unsupported workflow role: ${String(role)}`);
|
|
668
|
-
}
|
|
669
|
-
return record.acceptedText;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
647
|
/** Method-material load failure cause declared on the seat. Absent means no typed cause. */
|
|
673
648
|
export function packagedMethodLoadFailureCause(role: string): "activation" | undefined {
|
|
674
649
|
const record = packagedRoleMetadata(role);
|
|
@@ -169,6 +169,8 @@ function roleOutcomeFromRows(
|
|
|
169
169
|
readonly role?: TerminalRoleName;
|
|
170
170
|
readonly kind: "accepted" | "audit-escalation" | "correctable-rejection" | "infrastructure" | "candidate";
|
|
171
171
|
readonly accepted: unknown;
|
|
172
|
+
readonly auditReceipt?: unknown;
|
|
173
|
+
readonly auditOfficer?: unknown;
|
|
172
174
|
}[],
|
|
173
175
|
): Extract<TerminalRoleOutcome, { kind: "accepted" | "audit_escalation" }> | undefined {
|
|
174
176
|
const mine = rows.filter((row) => row.role === role);
|
|
@@ -181,7 +183,14 @@ function roleOutcomeFromRows(
|
|
|
181
183
|
// Role-result block is the full recorded sequence for this seat, not a sole pick.
|
|
182
184
|
const payloads = mine.map((row) => row.accepted);
|
|
183
185
|
if (terminal.some((row) => row.kind === "audit-escalation")) {
|
|
184
|
-
|
|
186
|
+
const audited = terminal.slice().reverse().find((row) => row.kind === "audit-escalation" && Object.hasOwn(row, "auditReceipt"));
|
|
187
|
+
return {
|
|
188
|
+
kind: "audit_escalation", role, status: "audit_escalation", payloads,
|
|
189
|
+
...(audited === undefined ? {} : { decisiveFacts: {
|
|
190
|
+
auditEscalationReceipt: audited.auditReceipt,
|
|
191
|
+
...(Object.hasOwn(audited, "auditOfficer") ? { auditEscalationOfficer: audited.auditOfficer } : {}),
|
|
192
|
+
} }),
|
|
193
|
+
};
|
|
185
194
|
}
|
|
186
195
|
return { kind: "accepted", role, payloads };
|
|
187
196
|
}
|
|
@@ -278,8 +278,8 @@ export function formatTerminalResult(result: TerminalResult): string {
|
|
|
278
278
|
`diagnostic\t${encodeTerminalField(result.roleOutcome.diagnostic)}`,
|
|
279
279
|
);
|
|
280
280
|
}
|
|
281
|
-
if (result.roleOutcome.kind === "failure" || result.roleOutcome.kind === "no_receipt") {
|
|
282
|
-
const facts = result.roleOutcome.decisiveFacts;
|
|
281
|
+
if (result.roleOutcome.kind === "failure" || result.roleOutcome.kind === "no_receipt" || result.roleOutcome.kind === "audit_escalation") {
|
|
282
|
+
const facts = result.roleOutcome.decisiveFacts ?? {};
|
|
283
283
|
for (const [key, value] of Object.entries(facts)) {
|
|
284
284
|
if (value === undefined) continue;
|
|
285
285
|
const rendered =
|
package/src/reviewer-role.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { withTerminatingOutputDeclarations } from "./package-contracts/terminati
|
|
|
5
5
|
|
|
6
6
|
import type { AnyCanonicalSkillBinding, CanonicalSkillBinding } from "./canonical-skill-binding.ts";
|
|
7
7
|
export type { CanonicalSkillBinding };
|
|
8
|
-
import { packagedRoleAcceptedText } from "./packaged-role-registry.ts";
|
|
9
8
|
import { REVIEWER_OUTPUT_TOOL_NAME, type ReviewerIntent } from "./package-contracts/reviewer-output.ts";
|
|
10
9
|
|
|
11
10
|
export { REVIEWER_OUTPUT_TOOL_NAME };
|
|
@@ -104,7 +103,7 @@ export function createReviewerRoleRuntime(
|
|
|
104
103
|
async execute(_id: string, parameters: unknown): Promise<HostToolResult<unknown>> {
|
|
105
104
|
if (!soul || !binding) throw new Error("御史台输入未装载");
|
|
106
105
|
return {
|
|
107
|
-
content: [
|
|
106
|
+
content: [],
|
|
108
107
|
details: parameters,
|
|
109
108
|
terminate: true as const,
|
|
110
109
|
};
|