@akagilnc/pi-workflow-roles 0.1.4956 → 0.1.4977

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.
@@ -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
- /** Zero-projection kickoff — soul already carries dossier-fetch duty; no hand-delivered materials. */
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
- // Queue the latest conclusion; keep every original row on the receipt/reply face.
173
- const decision = readComplianceCandidate(rows[rows.length - 1], usage);
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;
@@ -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) }),
@@ -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];
@@ -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 { packagedRoleAcceptedText } from "./packaged-role-registry.ts";
7
- import { DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
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: packagedRoleAcceptedText("doctor") }], 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: [{ type: "text" as const, text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: acceptedDetails, terminate: true as const, ...usageProjection }; }, received: () => { throw new Error("审刑院非三态应交 runComplianceAudit 重问说话者"); }, bounce: (violations) => { throw new GatekeeperDecisionError({ status: "bounce", officer: "auditor", receipt: 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); } });
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 | escalate → raw officer receipt as tool result back to parent
10
- * — except secretariat_verdict escalate: throw → parent audit_escalation, not raw receipt (#969)
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-to-parent, or real failure.
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
- // #969: 给事中上呈 ends the secretariat parent — no bind/retry (不回中书省擅改).
180
- // Parent beforeAccept converts this into audit_escalation with the officer receipt.
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 | escalate | no_receipt: parent stands and may resubmit. Not a run abort.
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
  }
@@ -373,7 +373,9 @@ function projectOfficerTerminal(
373
373
  status: "escalate",
374
374
  officer,
375
375
  // This-court receipt only (#879) — historical rows remain on terminal.submissions.
376
- receipt: thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome),
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
  );
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 { projectAuditEscalation } from "./audit-escalation.ts";
6
7
 
7
- import { packagedRoleAcceptedText } from "./packaged-role-registry.ts";
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: [{ type: "text" as const, text: packagedRoleAcceptedText("judge") }],
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: 符宝郎内闸 — queue only, raw receipt on bounce/escalate.
119
- await pi.requireGatekeeperPass!({
120
- context: ctx,
121
- subject: { kind: "judge_draft" },
122
- ...(signal === undefined ? {} : { signal }),
123
- hostActions,
124
- toolCallId,
125
- // #879: this-turn typed payload — identity-bound at submit site.
126
- submission: parameters,
127
- });
128
- // #756: 审刑院合规路径 — same review-queue law as 符宝郎/台院.
129
- // pass → accept; bounce|escalate → raw auditor receipt back to judge;
130
- // not three-state → resume auditor; no round cap; no disposeCompliance mapping.
131
- await pi.requireGatekeeperPass!({
132
- context: ctx,
133
- subject: { kind: "judge_compliance" },
134
- ...(signal === undefined ? {} : { signal }),
135
- hostActions,
136
- toolCallId,
137
- // #879: same parent payload for 审刑院; not recovered from session latest.
138
- submission: parameters,
139
- });
140
- return {
141
- content: [{ type: "text" as const, text: packagedRoleAcceptedText("judge") }],
142
- details: verdict,
143
- terminate: true as const,
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 projectAuditEscalation({ status: "escalate", conflicts: error.result.receipt }, verdict);
156
+ }
157
+ throw error;
158
+ }
145
159
  },
146
160
  });
147
161
  pi.on("before_agent_start", (event) => {
@@ -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: [{ type: "text" as const, text: packagedRoleAcceptedText("merger") }], details: params, terminate: true as const };
42
+ return { content: [], details: params, terminate: true as const };
44
43
  },
45
44
  });
46
45
  pi.on("before_agent_start", (event) => {
@@ -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: [{ type: "text" as const, text: packagedRoleAcceptedText("notary") }],
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,7 @@ 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;
172
173
  }[],
173
174
  ): Extract<TerminalRoleOutcome, { kind: "accepted" | "audit_escalation" }> | undefined {
174
175
  const mine = rows.filter((row) => row.role === role);
@@ -181,7 +182,11 @@ function roleOutcomeFromRows(
181
182
  // Role-result block is the full recorded sequence for this seat, not a sole pick.
182
183
  const payloads = mine.map((row) => row.accepted);
183
184
  if (terminal.some((row) => row.kind === "audit-escalation")) {
184
- return { kind: "audit_escalation", role, status: "audit_escalation", payloads };
185
+ const audited = terminal.slice().reverse().find((row) => row.kind === "audit-escalation" && Object.hasOwn(row, "auditReceipt"));
186
+ return {
187
+ kind: "audit_escalation", role, status: "audit_escalation", payloads,
188
+ ...(audited === undefined ? {} : { decisiveFacts: { auditEscalationReceipt: audited.auditReceipt } }),
189
+ };
185
190
  }
186
191
  return { kind: "accepted", role, payloads };
187
192
  }
@@ -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 =
@@ -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: [{ type: "text" as const, text: packagedRoleAcceptedText("reviewer") }],
106
+ content: [],
108
107
  details: parameters,
109
108
  terminate: true as const,
110
109
  };