@akagilnc/pi-workflow-roles 0.1.3682 → 0.1.3685

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.
@@ -2228,6 +2228,19 @@ var init_reviewer_output = __esm({
2228
2228
  }
2229
2229
  });
2230
2230
 
2231
+ // src/readable-gate-item.ts
2232
+ function readableGateItem(value) {
2233
+ return typeof value === "string" ? value : JSON.stringify(value);
2234
+ }
2235
+ function joinReadableGateItems(items, separator = "; ") {
2236
+ return items.map(readableGateItem).join(separator);
2237
+ }
2238
+ var init_readable_gate_item = __esm({
2239
+ "src/readable-gate-item.ts"() {
2240
+ "use strict";
2241
+ }
2242
+ });
2243
+
2231
2244
  // src/audit-escalation.ts
2232
2245
  function buildAuditEscalationResult(decision, deliveredOutput) {
2233
2246
  const auditOwned = {
@@ -2276,17 +2289,17 @@ function humanDecisionText(decision, result) {
2276
2289
  lines.push(`Reason: ${result.reason}`);
2277
2290
  }
2278
2291
  if (Array.isArray(result.conflicts)) {
2279
- lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${conflict}`));
2292
+ lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${readableGateItem(conflict)}`));
2280
2293
  }
2281
2294
  if (officer !== void 0 && Array.isArray(result.findings) && result.findings.length > 0) {
2282
- lines.push("Findings:", ...result.findings.map((finding2) => `- ${finding2}`));
2295
+ lines.push("Findings:", ...result.findings.map((finding2) => `- ${readableGateItem(finding2)}`));
2283
2296
  }
2284
2297
  const gate = result.auditDecisionGate;
2285
2298
  if (gate !== null && typeof gate === "object" && !Array.isArray(gate)) {
2286
2299
  const record4 = gate;
2287
2300
  if (typeof record4.question === "string") lines.push(`Question: ${record4.question}`);
2288
2301
  if (Array.isArray(record4.options)) {
2289
- lines.push("Options:", ...record4.options.map((option) => `- ${option}`));
2302
+ lines.push("Options:", ...record4.options.map((option) => `- ${readableGateItem(option)}`));
2290
2303
  }
2291
2304
  }
2292
2305
  return lines.join("\n");
@@ -2338,6 +2351,7 @@ var AUDIT_ESCALATION_KIND, AUDIT_ESCALATION_LIVE_REGISTRY;
2338
2351
  var init_audit_escalation = __esm({
2339
2352
  "src/audit-escalation.ts"() {
2340
2353
  "use strict";
2354
+ init_readable_gate_item();
2341
2355
  AUDIT_ESCALATION_KIND = "audit_escalation";
2342
2356
  AUDIT_ESCALATION_LIVE_REGISTRY = /* @__PURE__ */ new WeakSet();
2343
2357
  }
@@ -2346,7 +2360,7 @@ var init_audit_escalation = __esm({
2346
2360
  // src/submission-errors.ts
2347
2361
  function gatekeeperNonPassMessage(result) {
2348
2362
  if (result.status === "bounce") {
2349
- const findings = result.findings.length === 0 ? "\uFF08\u65E0 findings\uFF09" : result.findings.join("; ");
2363
+ const findings = result.findings.length === 0 ? "\uFF08\u65E0 findings\uFF09" : joinReadableGateItems(result.findings);
2350
2364
  return `\u95E8\u4E0B\u7701\u6253\u56DE\u91CD\u5199\uFF0Cfindings\uFF1A${findings}`;
2351
2365
  }
2352
2366
  if (result.status === "unreadable") {
@@ -2358,6 +2372,7 @@ var GatekeeperDecisionError, WorkerCommitReminderError, WorkerPrefixReminderErro
2358
2372
  var init_submission_errors = __esm({
2359
2373
  "src/submission-errors.ts"() {
2360
2374
  "use strict";
2375
+ init_readable_gate_item();
2361
2376
  GatekeeperDecisionError = class extends Error {
2362
2377
  result;
2363
2378
  constructor(result) {
@@ -9834,7 +9849,7 @@ function failureReason(error) {
9834
9849
  }
9835
9850
  function asStringArray3(value) {
9836
9851
  if (!Array.isArray(value)) return [];
9837
- return value.map((item) => typeof item === "string" ? item : JSON.stringify(item));
9852
+ return value.map(readableGateItem);
9838
9853
  }
9839
9854
  function isRecord13(value) {
9840
9855
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -9973,6 +9988,7 @@ var init_gatekeeper_role = __esm({
9973
9988
  "use strict";
9974
9989
  init_auditor_dossier_tool();
9975
9990
  init_submission_errors();
9991
+ init_readable_gate_item();
9976
9992
  init_inspector_contracts();
9977
9993
  init_gatekeeper_output();
9978
9994
  init_shape_unreadable_failure();
@@ -20071,7 +20087,7 @@ function createDoctorRoleRuntime(pi, dependencies, host) {
20071
20087
  const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony;
20072
20088
  const acceptedDetails = details;
20073
20089
  return disposeComplianceDecision(audit, { pass: (usage) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true, ...usage === void 0 ? {} : { usage } }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true, ...usageProjection }), received: (auditReceived, usageProjection) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_TEXT }], details: { ...acceptedDetails, audit: auditReceived.reply }, terminate: true, ...usageProjection }), bounce: (violations) => {
20074
- throw new Error(`\u592A\u533B\u7F72\u56DE\u6267\u8FDD soul\uFF1A${violations.join("; ")}`);
20090
+ throw new Error(`\u592A\u533B\u7F72\u56DE\u6267\u8FDD soul\uFF1A${joinReadableGateItems(violations)}`);
20075
20091
  }, escalate: (result) => result }, acceptedDetails);
20076
20092
  } });
20077
20093
  pi.on("before_agent_start", (event) => {
@@ -20102,6 +20118,7 @@ var init_doctor_role = __esm({
20102
20118
  "use strict";
20103
20119
  init_audit_escalation();
20104
20120
  init_compliance_transport();
20121
+ init_readable_gate_item();
20105
20122
  init_dossier_resolution();
20106
20123
  init_doctor_contracts();
20107
20124
  init_sitian_facade();
@@ -20780,7 +20797,7 @@ function createJudgeRoleRuntime(pi, dependencies, hostActions) {
20780
20797
  }),
20781
20798
  bounce: (violations) => {
20782
20799
  throw new Error(
20783
- `\u5927\u7406\u5BFA\u56DE\u6267\u8FDD soul\uFF1A${violations.join("; ")}`
20800
+ `\u5927\u7406\u5BFA\u56DE\u6267\u8FDD soul\uFF1A${joinReadableGateItems(violations)}`
20784
20801
  );
20785
20802
  },
20786
20803
  escalate: (result) => result
@@ -20810,6 +20827,7 @@ var init_judge_role = __esm({
20810
20827
  "use strict";
20811
20828
  init_host_contracts();
20812
20829
  init_audit_escalation();
20830
+ init_readable_gate_item();
20813
20831
  init_terminating_infrastructure();
20814
20832
  init_judge_output();
20815
20833
  judgeVerdictSchema = withInfrastructureFailureDeclaration(
@@ -1,3 +1,4 @@
1
+ import { readableGateItem } from "./readable-gate-item.js";
1
2
  export const AUDIT_ESCALATION_KIND = "audit_escalation";
2
3
  // Live Navigator settlement may consume only the projection produced by this
3
4
  // owner. Its private WeakSet cannot be authored by role output; persisted/
@@ -71,10 +72,10 @@ function humanDecisionText(decision, result) {
71
72
  lines.push(`Reason: ${result.reason}`);
72
73
  }
73
74
  if (Array.isArray(result.conflicts)) {
74
- lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${conflict}`));
75
+ lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${readableGateItem(conflict)}`));
75
76
  }
76
77
  if (officer !== undefined && Array.isArray(result.findings) && result.findings.length > 0) {
77
- lines.push("Findings:", ...result.findings.map((finding) => `- ${finding}`));
78
+ lines.push("Findings:", ...result.findings.map((finding) => `- ${readableGateItem(finding)}`));
78
79
  }
79
80
  const gate = result.auditDecisionGate;
80
81
  if (gate !== null && typeof gate === "object" && !Array.isArray(gate)) {
@@ -82,7 +83,7 @@ function humanDecisionText(decision, result) {
82
83
  if (typeof record.question === "string")
83
84
  lines.push(`Question: ${record.question}`);
84
85
  if (Array.isArray(record.options)) {
85
- lines.push("Options:", ...record.options.map((option) => `- ${option}`));
86
+ lines.push("Options:", ...record.options.map((option) => `- ${readableGateItem(option)}`));
86
87
  }
87
88
  }
88
89
  return lines.join("\n");
@@ -1,5 +1,6 @@
1
1
  import { disposeComplianceDecision } from "./audit-escalation.js";
2
2
  import { ComplianceResponseRetentionError } from "./compliance-transport.js";
3
+ import { joinReadableGateItems } from "./readable-gate-item.js";
3
4
  import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.js";
4
5
  import { DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, DOCTOR_ACCEPTED_TEXT, DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput } from "./doctor-contracts.js";
5
6
  import { sitianReport } from "./sitian-facade.js";
@@ -57,7 +58,7 @@ export function createDoctorRoleRuntime(pi, dependencies, host) {
57
58
  }
58
59
  catch (error) {
59
60
  host.failInfrastructure(error, ctx, id);
60
- } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision(audit, { pass: (usage) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true, ...usageProjection }), received: (auditReceived, usageProjection) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_TEXT }], details: { ...acceptedDetails, audit: auditReceived.reply }, terminate: true, ...usageProjection }), bounce: (violations) => { throw new Error(`太医署回执违 soul:${violations.join("; ")}`); }, escalate: (result) => result }, acceptedDetails); } });
61
+ } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision(audit, { pass: (usage) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true, ...usageProjection }), received: (auditReceived, usageProjection) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_TEXT }], details: { ...acceptedDetails, audit: auditReceived.reply }, terminate: true, ...usageProjection }), bounce: (violations) => { throw new Error(`太医署回执违 soul:${joinReadableGateItems(violations)}`); }, escalate: (result) => result }, acceptedDetails); } });
61
62
  pi.on("before_agent_start", (event) => { if (!activation)
62
63
  throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { provenance: "由留存 session 字节推导,封入受理回执。", 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>` }; });
63
64
  }
@@ -1,5 +1,6 @@
1
1
  import { auditorRunDirectory, persistGateSubmissionCandidate, } from "./auditor-dossier-tool.js";
2
2
  import { GatekeeperDecisionError } from "./submission-errors.js";
3
+ import { readableGateItem } from "./readable-gate-item.js";
3
4
  import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
4
5
  import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperDecisionSchema, gatekeeperOutputSchema, } from "./package-contracts/gatekeeper-output.js";
5
6
  import { retainedShapeUnreadable } from "./shape-unreadable-failure.js";
@@ -52,8 +53,8 @@ function asStringArray(value) {
52
53
  if (!Array.isArray(value))
53
54
  return [];
54
55
  // Officer findings may be structured objects (category/law/evidence); the
55
- // parent role must see them verbatim, not an empty list (#750 evidence).
56
- return value.map((item) => (typeof item === "string" ? item : JSON.stringify(item)));
56
+ // parent role must see them verbatim, not an empty list (#750 / #775).
57
+ return value.map(readableGateItem);
57
58
  }
58
59
  /** Serializable stand-in when the child tool call had no arguments object. */
59
60
  export const MISSING_ARGUMENTS_SUBMISSION = Object.freeze({ missing: "arguments" });
@@ -1,6 +1,7 @@
1
1
  import { stringEnum } from "./host-contracts.js";
2
2
  import { Type } from "typebox";
3
3
  import { disposeComplianceDecision } from "./audit-escalation.js";
4
+ import { joinReadableGateItems } from "./readable-gate-item.js";
4
5
  import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
5
6
  import { JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, JUDGE_ACCEPTED_TEXT, JUDGE_OUTPUT_TOOL_NAME, validateAcceptedJudgeDetails, } from "./package-contracts/judge-output.js";
6
7
  export { JUDGE_OUTPUT_TOOL_NAME };
@@ -85,7 +86,7 @@ export function createJudgeRoleRuntime(pi, dependencies, hostActions) {
85
86
  ...usageProjection,
86
87
  }),
87
88
  bounce: (violations) => {
88
- throw new Error(`大理寺回执违 soul:${violations.join("; ")}`);
89
+ throw new Error(`大理寺回执违 soul:${joinReadableGateItems(violations)}`);
89
90
  },
90
91
  escalate: (result) => result,
91
92
  },
@@ -16687,6 +16687,13 @@ var init_run_ticket_number = __esm({
16687
16687
  }
16688
16688
  });
16689
16689
 
16690
+ // src/readable-gate-item.ts
16691
+ var init_readable_gate_item = __esm({
16692
+ "src/readable-gate-item.ts"() {
16693
+ "use strict";
16694
+ }
16695
+ });
16696
+
16690
16697
  // src/audit-escalation.ts
16691
16698
  function isAuditEscalationResult(value) {
16692
16699
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -16698,6 +16705,7 @@ var AUDIT_ESCALATION_KIND;
16698
16705
  var init_audit_escalation = __esm({
16699
16706
  "src/audit-escalation.ts"() {
16700
16707
  "use strict";
16708
+ init_readable_gate_item();
16701
16709
  AUDIT_ESCALATION_KIND = "audit_escalation";
16702
16710
  }
16703
16711
  });
@@ -16706,6 +16714,7 @@ var init_audit_escalation = __esm({
16706
16714
  var init_submission_errors = __esm({
16707
16715
  "src/submission-errors.ts"() {
16708
16716
  "use strict";
16717
+ init_readable_gate_item();
16709
16718
  }
16710
16719
  });
16711
16720
 
@@ -22411,6 +22420,7 @@ var init_gatekeeper_role = __esm({
22411
22420
  "use strict";
22412
22421
  init_auditor_dossier_tool();
22413
22422
  init_submission_errors();
22423
+ init_readable_gate_item();
22414
22424
  init_inspector_contracts();
22415
22425
  init_gatekeeper_output();
22416
22426
  init_shape_unreadable_failure();
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Parent-visible relay of one gate bounce/finding item (#750 / #775).
3
+ *
4
+ * Strings stay as-is. Any other value is JSON-serialized so structured content
5
+ * (article/reason/evidence, category/law/evidence, …) is not lost as
6
+ * `[object Object]` when joined into parent-seat text.
7
+ *
8
+ * Presentation only: does not legislate carrier format for LLM-to-LLM traffic.
9
+ */
10
+ export function readableGateItem(value) {
11
+ return typeof value === "string" ? value : JSON.stringify(value);
12
+ }
13
+ /** Join gate items into one parent-visible line. */
14
+ export function joinReadableGateItems(items, separator = "; ") {
15
+ return items.map(readableGateItem).join(separator);
16
+ }
@@ -1,6 +1,9 @@
1
+ import { joinReadableGateItems } from "./readable-gate-item.js";
1
2
  function gatekeeperNonPassMessage(result) {
2
3
  if (result.status === "bounce") {
3
- const findings = result.findings.length === 0 ? "(无 findings)" : result.findings.join("; ");
4
+ const findings = result.findings.length === 0
5
+ ? "(无 findings)"
6
+ : joinReadableGateItems(result.findings);
4
7
  return `门下省打回重写,findings:${findings}`;
5
8
  }
6
9
  if (result.status === "unreadable") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.3682",
3
+ "version": "0.1.3685",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -3,6 +3,7 @@ import type { Usage } from "@earendil-works/pi-ai";
3
3
  import type {
4
4
  ComplianceDecision,
5
5
  } from "./compliance-transport.ts";
6
+ import { readableGateItem } from "./readable-gate-item.ts";
6
7
 
7
8
  export const AUDIT_ESCALATION_KIND = "audit_escalation" as const;
8
9
 
@@ -126,17 +127,17 @@ function humanDecisionText(
126
127
  lines.push(`Reason: ${result.reason}`);
127
128
  }
128
129
  if (Array.isArray(result.conflicts)) {
129
- lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${conflict}`));
130
+ lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${readableGateItem(conflict)}`));
130
131
  }
131
132
  if (officer !== undefined && Array.isArray(result.findings) && result.findings.length > 0) {
132
- lines.push("Findings:", ...result.findings.map((finding) => `- ${finding}`));
133
+ lines.push("Findings:", ...result.findings.map((finding) => `- ${readableGateItem(finding)}`));
133
134
  }
134
135
  const gate = result.auditDecisionGate;
135
136
  if (gate !== null && typeof gate === "object" && !Array.isArray(gate)) {
136
137
  const record = gate as Record<string, unknown>;
137
138
  if (typeof record.question === "string") lines.push(`Question: ${record.question}`);
138
139
  if (Array.isArray(record.options)) {
139
- lines.push("Options:", ...record.options.map((option) => `- ${option}`));
140
+ lines.push("Options:", ...record.options.map((option) => `- ${readableGateItem(option)}`));
140
141
  }
141
142
  }
142
143
  return lines.join("\n");
@@ -1,6 +1,7 @@
1
1
  import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts";
2
2
  import { disposeComplianceDecision } from "./audit-escalation.ts";
3
3
  import { ComplianceResponseRetentionError, type ComplianceDecision } from "./compliance-transport.ts";
4
+ import { joinReadableGateItems } from "./readable-gate-item.ts";
4
5
  import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
5
6
  import { DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, DOCTOR_ACCEPTED_TEXT, DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
6
7
 
@@ -35,7 +36,7 @@ export function createDoctorRoleRuntime(pi: RoleHost, dependencies: DoctorRoleDe
35
36
  let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
36
37
  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) };
37
38
  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 }; } });
38
- 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 testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendCandidate(ctx, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.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 details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision<HostToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true as const, ...usageProjection }), received: (auditReceived, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_TEXT }], details: { ...acceptedDetails, audit: auditReceived.reply }, terminate: true as const, ...usageProjection }), bounce: (violations) => { throw new Error(`太医署回执违 soul:${violations.join("; ")}`); }, escalate: (result) => result }, acceptedDetails); } });
39
+ 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 testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendCandidate(ctx, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.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 details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision<HostToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true as const, ...usageProjection }), received: (auditReceived, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_TEXT }], details: { ...acceptedDetails, audit: auditReceived.reply }, terminate: true as const, ...usageProjection }), bounce: (violations) => { throw new Error(`太医署回执违 soul:${joinReadableGateItems(violations)}`); }, escalate: (result) => result }, acceptedDetails); } });
39
40
  pi.on("before_agent_start", (event) => { if (!activation) throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { provenance: "由留存 session 字节推导,封入受理回执。", 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>` }; }); }
40
41
  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"); } };
41
42
  }
@@ -7,6 +7,7 @@ import {
7
7
  } from "./auditor-dossier-tool.ts";
8
8
  import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
9
9
  import { GatekeeperDecisionError } from "./submission-errors.ts";
10
+ import { readableGateItem } from "./readable-gate-item.ts";
10
11
  import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
11
12
  import {
12
13
  GATEKEEPER_OUTPUT_TOOL_NAME,
@@ -147,8 +148,8 @@ function failureReason(error: unknown): string {
147
148
  function asStringArray(value: unknown): readonly string[] {
148
149
  if (!Array.isArray(value)) return [];
149
150
  // Officer findings may be structured objects (category/law/evidence); the
150
- // parent role must see them verbatim, not an empty list (#750 evidence).
151
- return value.map((item) => (typeof item === "string" ? item : JSON.stringify(item)));
151
+ // parent role must see them verbatim, not an empty list (#750 / #775).
152
+ return value.map(readableGateItem);
152
153
  }
153
154
 
154
155
  /** Serializable stand-in when the child tool call had no arguments object. */
package/src/judge-role.ts CHANGED
@@ -4,6 +4,7 @@ import { Type, type Static } from "typebox";
4
4
 
5
5
  import { disposeComplianceDecision } from "./audit-escalation.ts";
6
6
  import type { ComplianceDecision } from "./compliance-transport.ts";
7
+ import { joinReadableGateItems } from "./readable-gate-item.ts";
7
8
  import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
8
9
 
9
10
  import {
@@ -136,7 +137,7 @@ export function createJudgeRoleRuntime(
136
137
  }),
137
138
  bounce: (violations) => {
138
139
  throw new Error(
139
- `大理寺回执违 soul:${violations.join("; ")}`,
140
+ `大理寺回执违 soul:${joinReadableGateItems(violations)}`,
140
141
  );
141
142
  },
142
143
  escalate: (result) => result,
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Parent-visible relay of one gate bounce/finding item (#750 / #775).
3
+ *
4
+ * Strings stay as-is. Any other value is JSON-serialized so structured content
5
+ * (article/reason/evidence, category/law/evidence, …) is not lost as
6
+ * `[object Object]` when joined into parent-seat text.
7
+ *
8
+ * Presentation only: does not legislate carrier format for LLM-to-LLM traffic.
9
+ */
10
+ export function readableGateItem(value: unknown): string {
11
+ return typeof value === "string" ? value : JSON.stringify(value);
12
+ }
13
+
14
+ /** Join gate items into one parent-visible line. */
15
+ export function joinReadableGateItems(
16
+ items: readonly unknown[],
17
+ separator = "; ",
18
+ ): string {
19
+ return items.map(readableGateItem).join(separator);
20
+ }
@@ -1,8 +1,11 @@
1
1
  import type { GatekeeperNonPassResult } from "./gatekeeper-role.ts";
2
+ import { joinReadableGateItems } from "./readable-gate-item.ts";
2
3
 
3
4
  function gatekeeperNonPassMessage(result: GatekeeperNonPassResult): string {
4
5
  if (result.status === "bounce") {
5
- const findings = result.findings.length === 0 ? "(无 findings)" : result.findings.join("; ");
6
+ const findings = result.findings.length === 0
7
+ ? "(无 findings)"
8
+ : joinReadableGateItems(result.findings);
6
9
  return `门下省打回重写,findings:${findings}`;
7
10
  }
8
11
  if (result.status === "unreadable") {