@akagilnc/pi-workflow-roles 0.1.5375 → 0.1.5406

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.
Files changed (79) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +2 -0
  3. package/dist/acp-host/production-host.js +6381 -5684
  4. package/dist/archivist-record-pointer.js +38 -0
  5. package/dist/collector-tool-schemas.js +18 -0
  6. package/dist/compliance-transport.js +5 -0
  7. package/dist/diarist-contracts.js +26 -0
  8. package/dist/doctor-auditor.js +1 -0
  9. package/dist/engine-detour-usage.js +6 -6
  10. package/dist/filed-submission.js +28 -0
  11. package/dist/gatekeeper-role.js +6 -10
  12. package/dist/gleaner-left-contracts.js +16 -0
  13. package/dist/headless-host/production-host.js +1644 -947
  14. package/dist/judge-role.js +21 -16
  15. package/dist/migrate-book-topology.js +452 -81
  16. package/dist/navigator-attendance.js +10 -9
  17. package/dist/navigator-invocation-identity.js +18 -0
  18. package/dist/package-contracts/reviewer-output.js +16 -0
  19. package/dist/package-contracts/worker-output.js +18 -2
  20. package/dist/pi/role-turn-host.js +9 -0
  21. package/dist/public-cli/cli.js +1 -0
  22. package/dist/public-cli/instruction-seat-run.js +258 -55
  23. package/dist/public-cli/main.js +1209 -346
  24. package/dist/public-cli/post-admission.js +427 -244
  25. package/dist/public-cli/settlement.js +111 -48
  26. package/dist/public-cli/terminal.js +12 -1
  27. package/dist/public-cli/turn-request.js +3 -0
  28. package/dist/receipt-delivery-policy.js +152 -17
  29. package/dist/role-submission-declarations.js +131 -0
  30. package/dist/secretariat-contracts.js +23 -1
  31. package/dist/submission-errors.js +11 -1
  32. package/dist/submission-gate.js +32 -12
  33. package/package.json +1 -1
  34. package/src/archivist-record-pointer.ts +48 -0
  35. package/src/auditor-role.ts +2 -8
  36. package/src/collector-role.ts +5 -28
  37. package/src/compliance-transport.ts +10 -2
  38. package/src/countersign-role.ts +2 -7
  39. package/src/diarist-contracts.ts +37 -0
  40. package/src/diarist-role.ts +4 -44
  41. package/src/doctor-auditor.ts +3 -0
  42. package/src/doctor-role.ts +16 -11
  43. package/src/engine-detour-usage.ts +7 -6
  44. package/src/external-host-turn-loop.ts +24 -12
  45. package/src/filed-submission.ts +44 -0
  46. package/src/gatekeeper-role.ts +8 -16
  47. package/src/gleaner-left-contracts.ts +26 -1
  48. package/src/gleaner-left-role.ts +4 -32
  49. package/src/host-contracts.ts +7 -0
  50. package/src/inspector-role.ts +2 -7
  51. package/src/judge-role.ts +31 -24
  52. package/src/merger-role.ts +5 -8
  53. package/src/navigator-attendance.ts +17 -12
  54. package/src/navigator-invocation-identity.ts +31 -0
  55. package/src/navigator-role.ts +2 -8
  56. package/src/notary-role.ts +5 -20
  57. package/src/package-contracts/reviewer-output.ts +25 -0
  58. package/src/package-contracts/worker-output.ts +43 -5
  59. package/src/pi/adapter.ts +33 -8
  60. package/src/pi/role-turn-host.ts +7 -0
  61. package/src/public-cli/cli.ts +1 -0
  62. package/src/public-cli/instruction-seat-run.ts +292 -61
  63. package/src/public-cli/post-admission.ts +556 -324
  64. package/src/public-cli/settlement.ts +112 -50
  65. package/src/public-cli/terminal.ts +16 -4
  66. package/src/public-cli/turn-request.ts +10 -1
  67. package/src/receipt-delivery-policy.ts +209 -17
  68. package/src/reviewer-role.ts +7 -35
  69. package/src/role-envelope.ts +5 -1
  70. package/src/role-runtime-dependencies.ts +1 -0
  71. package/src/role-runtime.ts +72 -56
  72. package/src/role-submission-declarations.ts +143 -0
  73. package/src/secretariat-contracts.ts +35 -0
  74. package/src/secretariat-role.ts +4 -40
  75. package/src/submission-correctable-error.ts +5 -2
  76. package/src/submission-errors.ts +12 -1
  77. package/src/submission-gate.ts +41 -12
  78. package/src/worker-role.ts +81 -86
  79. package/src/worker-submission-gates.ts +32 -7
@@ -6,7 +6,6 @@ import {
6
6
 
7
7
  runDirectoryFromHostContext,
8
8
  type HostContext,
9
- type HostToolResult,
10
9
  type RoleEnvelopeHost,
11
10
  type RoleHost,
12
11
  } from "./host-contracts.ts";
@@ -14,6 +13,8 @@ import {
14
13
  import { Value } from "typebox/value";
15
14
  import { sitianReport } from "./sitian-facade.ts";
16
15
  import { createSubmissionLedgerHost, sealAcceptedSubmission } from "./submission-ledger.ts";
16
+ import { registerFiledSubmissionTool, type FiledSubmissionBeforeAccept } from "./filed-submission.ts";
17
+ import type { RoleSubmissionDeclaration } from "./role-submission-declarations.ts";
17
18
 
18
19
  import { activationTraceRecordSchema, namedActivationCause, type ActivationTraceRecord, type ActivationTraceWriter } from "./activation-trace.ts";
19
20
  import { homeFromRunDirectory } from "./activation-ledger-topology.ts";
@@ -31,7 +32,16 @@ import {
31
32
  } from "./engine-detour.ts";
32
33
  import { engineSessionMaterialFromOptions } from "./package-resources/engine-material.ts";
33
34
  import { registerEngineDetourTool } from "./engine-detour-tool.ts";
34
- import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE } from "./receipt-delivery-policy.ts";
35
+ import {
36
+ createReceiptDeliveryPolicy,
37
+ deliveryLimitFromConfig,
38
+ deliveryLimitFromEnv,
39
+ NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
40
+ priorReceiptContinuation,
41
+ RECEIPT_DELIVERY_REQUEST_ENTRY,
42
+ RECEIPT_REJECTION_ENTRY,
43
+ receiptAttemptPointer,
44
+ } from "./receipt-delivery-policy.ts";
35
45
  import {
36
46
  COLLECTOR_CONSTRUCTION_TOOLS,
37
47
  COLLECTOR_REQUIRED_TOOLS,
@@ -96,6 +106,7 @@ import { loadNavigatorWorkBaseSuffix } from "./navigator-work-base.ts";
96
106
  import {
97
107
  buildNavigatorInfrastructureFailureFact,
98
108
  classifyPackagedRoleTerminalResult,
109
+ packagedRoleOutputRejectionReason,
99
110
  extractInfrastructureFailureEvidence,
100
111
  NAVIGATOR_INVOCATION_ENTRY,
101
112
  resolveLifecycleInvocationPrincipal,
@@ -445,7 +456,7 @@ export type RoleRuntimeDependencies = {
445
456
  loadNotarySourceRun?(path: string): Promise<import("./notary-contracts.ts").NotarySourceRunLocator>;
446
457
  loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
447
458
  loadMergerInput?(path: string): Promise<unknown>;
448
- createNavigatorAttendance?(options: { context: HostContext; role: string; phase: NavigatorPhase; subjectKey: string; subject: string; authority: string; contextError?: unknown; invocationId: string; onEvent: (event: import("./navigator-attendance.ts").NavigatorEvent, report: import("./navigator-attendance.ts").NavigatorReport) => void | Promise<void> }): NavigatorAttendanceDependency | Promise<NavigatorAttendanceDependency>;
459
+ createNavigatorAttendance?(options: { context: HostContext; role: string; phase: NavigatorPhase; subjectKey: string; subject: string; authority: string; contextError?: unknown; invocationId: string; deliveryRequestLimit?: number; onEvent: (event: import("./navigator-attendance.ts").NavigatorEvent, report: import("./navigator-attendance.ts").NavigatorReport) => void | Promise<void> }): NavigatorAttendanceDependency | Promise<NavigatorAttendanceDependency>;
449
460
  loadNavigatorWorkContext?(options: { context: HostContext; role: string; phase: NavigatorPhase; getFlag?: (name: string) => unknown }): Promise<NavigatorWorkContext>;
450
461
  activationClock?(): string;
451
462
  activationTraceWriter?: (record: ActivationTraceRecord) => void | Promise<void>;
@@ -547,18 +558,6 @@ export async function projectClosedSubmissionLifecycle(
547
558
  }
548
559
  }
549
560
 
550
- /**
551
- * Optional pre-accept hook on the shared filed-officer envelope (ADR 0075).
552
- * May return a details projection (envelope-owned machine facts recorded next to
553
- * the submitted parameters); undefined keeps the parameters as submitted.
554
- */
555
- type FiledOfficerBeforeAccept = (input: {
556
- readonly toolCallId: string;
557
- readonly parameters: unknown;
558
- readonly signal: AbortSignal | undefined;
559
- readonly ctx: HostContext;
560
- }) => Promise<unknown>;
561
-
562
561
  /**
563
562
  * Shared registration envelope for filed officers (ADR 0018 / #572):
564
563
  * activate, tool register, before_agent_start prompt, inventory check.
@@ -569,9 +568,9 @@ function createFiledOfficerRuntime(
569
568
  roleHost: RoleHost,
570
569
  spec: {
571
570
  role: PackagedRole;
572
- tool: { name: string; label: string; description: string; promptSnippet: string; parameters: unknown };
571
+ tool: RoleSubmissionDeclaration;
573
572
  soulTag: string;
574
- beforeAccept?: FiledOfficerBeforeAccept;
573
+ beforeAccept?: FiledSubmissionBeforeAccept;
575
574
  },
576
575
  dependencies: { loadSoul(): Promise<string> },
577
576
  ) {
@@ -584,26 +583,9 @@ function createFiledOfficerRuntime(
584
583
  soul = loaded;
585
584
  if (!registered) {
586
585
  registered = true;
587
- roleHost.registerTool({
588
- name: spec.tool.name,
589
- label: spec.tool.label,
590
- description: spec.tool.description,
591
- promptSnippet: spec.tool.promptSnippet,
592
- parameters: spec.tool.parameters as never,
593
- async execute(toolCallId, parameters, signal, _onUpdate, ctx): Promise<HostToolResult<unknown>> {
594
- if (soul === undefined) throw new Error(`${spec.role} 职分未装载`);
595
- const projected =
596
- spec.beforeAccept === undefined
597
- ? undefined
598
- : await spec.beforeAccept({ toolCallId, parameters, signal, ctx });
599
- // Accept-as-is + terminate only. Shape is not an admission gate
600
- // (第 0 条 / ADR 0055); sole-final barrier is ledger-owned (#575).
601
- return {
602
- content: [],
603
- details: projected === undefined ? parameters : projected,
604
- terminate: true as const,
605
- };
606
- },
586
+ registerFiledSubmissionTool(roleHost, spec.tool, {
587
+ readyError: () => (soul === undefined ? `${spec.role} 职分未装载` : undefined),
588
+ ...(spec.beforeAccept === undefined ? {} : { beforeAccept: spec.beforeAccept }),
607
589
  });
608
590
  roleHost.on("before_agent_start", (event) => {
609
591
  if (soul === undefined) throw new Error(`${spec.role} 职分未装载`);
@@ -995,7 +977,15 @@ function lastAssistantProse(
995
977
 
996
978
  export function createRoleRuntimeExtension(
997
979
  dependencies: RoleRuntimeDependencies,
980
+ /**
981
+ * Effective ceiling already resolved for this turn (#1132). Absent only on
982
+ * the Pi child, whose env is that same resolved value projected by the host.
983
+ */
984
+ deliveryRequestLimit?: number,
998
985
  ): (envelopeHost: RoleEnvelopeHost) => void {
986
+ const deliveryLimit = deliveryRequestLimit === undefined
987
+ ? deliveryLimitFromEnv(process.env)
988
+ : deliveryLimitFromConfig(deliveryRequestLimit);
999
989
  return (envelopeHost) => {
1000
990
  let projectClosedSubmission: (closed: import("./submission-ledger.ts").ClosedSubmission, context: HostContext) => Promise<void> = async () => {
1001
991
  throw new Error("角色终局投射接缝尚未初始化");
@@ -1062,9 +1052,9 @@ export function createRoleRuntimeExtension(
1062
1052
  // Envelope-owned execute→tool_result bridge for submission non-pass (ADR 0018 / #525).
1063
1053
  const pendingSubmissionNonPassByToolCallId = new Map<string, SubmissionGateNonPassResult>();
1064
1054
  let engineDetourRegistered = false;
1065
- // #288 primary-session thin adapter. The policy is the sole budget owner;
1066
- // terminating-tool rejections and mechanical delivery requests share two turns.
1067
- let receiptDelivery = createReceiptDeliveryPolicy();
1055
+ // #288 primary-session thin adapter. The policy is the sole budget owner.
1056
+ // #1132: one ceiling for this turn, closed over from the execution seam.
1057
+ let receiptDelivery = createReceiptDeliveryPolicy(deliveryLimit);
1068
1058
  let noReceiptRecorded = false;
1069
1059
  // Public-run fetch observation.
1070
1060
  let priorFetch: typeof globalThis.fetch | undefined;
@@ -1261,7 +1251,7 @@ export function createRoleRuntimeExtension(
1261
1251
  },
1262
1252
  };
1263
1253
  });
1264
- roleHost.on("tool_result", async (event) => {
1254
+ roleHost.on("tool_result", async (event, ctx) => {
1265
1255
  const role = selectedRole;
1266
1256
  if (role === undefined) return;
1267
1257
  const pendingInfra = pendingInfrastructureFailures.get(event.toolCallId);
@@ -1276,12 +1266,19 @@ export function createRoleRuntimeExtension(
1276
1266
  const outputClassification = isOutputTool ? classifyPackagedRoleTerminalResult(classified) : undefined;
1277
1267
  if (isRoleInfrastructureFailure || outputClassification?.kind === "infrastructure") {
1278
1268
  receiptDelivery.stopForInfrastructure();
1279
- } else if (isOutputTool && outputClassification?.kind === "nonterminal" && event.isError) {
1280
- const reason = (event.content ?? [])
1281
- .map((part) => part.type === "text" && "text" in part ? part.text : "")
1282
- .join("")
1283
- .trim();
1284
- receiptDelivery.recordRejected(reason);
1269
+ } else if (isOutputTool) {
1270
+ const reason = packagedRoleOutputRejectionReason(classified);
1271
+ if (reason !== undefined) {
1272
+ receiptDelivery.recordRejected(reason);
1273
+ // Bind the observed rejection now, before agent_end or a host failure.
1274
+ // This observation is not an exhausted lifecycle or a budget spend.
1275
+ if (ctx.invocationScopeId !== undefined) {
1276
+ envelopeHost.appendEntry(RECEIPT_REJECTION_ENTRY, {
1277
+ invocationScopeId: ctx.invocationScopeId,
1278
+ reason,
1279
+ });
1280
+ }
1281
+ }
1285
1282
  }
1286
1283
  // Accepted/human terminal projection belongs exclusively to typed ledger
1287
1284
  // closure. tool_result retains only infrastructure settlement.
@@ -1340,15 +1337,16 @@ export function createRoleRuntimeExtension(
1340
1337
  return;
1341
1338
  }
1342
1339
  // Attended with neither tool nor prose → honest no_receipt (not typed 催交).
1343
- // Exhaust delivery budget without sending the typed prompt so facts() stays lawful.
1340
+ // Close the budget without sending prompts; the fact stays the real count.
1344
1341
  if (!noReceiptRecorded) {
1345
1342
  const runPointer = runDirectoryFromHostContext(ctx);
1346
1343
  if (runPointer !== undefined) {
1347
1344
  noReceiptRecorded = true;
1348
- while (receiptDelivery.nextAction() === "request-delivery") {
1349
- receiptDelivery.recordDeliveryRequest();
1350
- }
1351
- const facts = receiptDelivery.facts({ runPointer, attemptPointer: `current:${runPointer}` });
1345
+ receiptDelivery.closeBudget();
1346
+ const facts = receiptDelivery.facts({
1347
+ runPointer,
1348
+ attemptPointer: receiptAttemptPointer(runPointer, ctx.invocationScopeId),
1349
+ });
1352
1350
  envelopeHost.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
1353
1351
  try {
1354
1352
  sitianReport({
@@ -1368,7 +1366,11 @@ export function createRoleRuntimeExtension(
1368
1366
  receiptDelivery.recordDeliveryRequest();
1369
1367
  // Keep the package-owned continuation off the public input lifecycle:
1370
1368
  // receipt delivery must not be mistaken for later caller input.
1371
- envelopeHost.appendEntry("ak-receipt-delivery-request");
1369
+ const scopeId = ctx.invocationScopeId?.trim() ?? "";
1370
+ envelopeHost.appendEntry(
1371
+ RECEIPT_DELIVERY_REQUEST_ENTRY,
1372
+ scopeId.length > 0 ? { invocationScopeId: scopeId } : undefined,
1373
+ );
1372
1374
  try {
1373
1375
  sitianReport({
1374
1376
  level: "event",
@@ -1388,7 +1390,10 @@ export function createRoleRuntimeExtension(
1388
1390
  const runPointer = runDirectoryFromHostContext(ctx);
1389
1391
  if (runPointer !== undefined) {
1390
1392
  noReceiptRecorded = true;
1391
- const facts = receiptDelivery.facts({ runPointer, attemptPointer: `current:${runPointer}` });
1393
+ const facts = receiptDelivery.facts({
1394
+ runPointer,
1395
+ attemptPointer: receiptAttemptPointer(runPointer, ctx.invocationScopeId),
1396
+ });
1392
1397
  envelopeHost.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
1393
1398
  try {
1394
1399
  sitianReport({
@@ -1489,6 +1494,7 @@ export function createRoleRuntimeExtension(
1489
1494
  },
1490
1495
  },
1491
1496
  hostActions,
1497
+ { unfinishedReasonBounceLimit: deliveryLimit },
1492
1498
  );
1493
1499
  const coder = createCoderRoleRuntime(
1494
1500
  roleHost,
@@ -1502,6 +1508,7 @@ export function createRoleRuntimeExtension(
1502
1508
  },
1503
1509
  },
1504
1510
  hostActions,
1511
+ { unfinishedReasonBounceLimit: deliveryLimit },
1505
1512
  );
1506
1513
  const reviewer = createReviewerRoleRuntime(
1507
1514
  roleHost,
@@ -1707,7 +1714,7 @@ export function createRoleRuntimeExtension(
1707
1714
  roleReferenceMaterials = "";
1708
1715
  activeReviewerParent = undefined;
1709
1716
  activeCollector = undefined;
1710
- receiptDelivery = createReceiptDeliveryPolicy();
1717
+ receiptDelivery = createReceiptDeliveryPolicy(deliveryLimit);
1711
1718
  noReceiptRecorded = false;
1712
1719
  pendingNavigatorPresentation = undefined;
1713
1720
  navigatorActivation += 1;
@@ -1761,6 +1768,14 @@ export function createRoleRuntimeExtension(
1761
1768
  // #855: two-face waiting.jsonl write removed — fail-closed book-key + session only.
1762
1769
  resolveBookKeyFromGit(ctx.cwd);
1763
1770
  durableSessionPointer(ctx.sessionManager);
1771
+ const scopeId = ctx.invocationScopeId?.trim() ?? "";
1772
+ if (scopeId.length > 0) {
1773
+ // Same public call, new process: keep sends already issued and the
1774
+ // rejection facts. A different manual call has a different scope.
1775
+ receiptDelivery.continueIssued(
1776
+ priorReceiptContinuation(ctx.sessionManager.getEntries(), scopeId),
1777
+ );
1778
+ }
1764
1779
 
1765
1780
  // Station children (court diarist, inner-gate summons) omit navigator sidecar (#840).
1766
1781
  // Top-level public entry legs still attend automatically.
@@ -1837,6 +1852,7 @@ export function createRoleRuntimeExtension(
1837
1852
  subject: work.subject,
1838
1853
  authority: work.authority,
1839
1854
  invocationId,
1855
+ deliveryRequestLimit: deliveryLimit,
1840
1856
  ...(contextError === undefined ? {} : { contextError }),
1841
1857
  onEvent: (navigatorEvent, report) => {
1842
1858
  if (activation === navigatorActivation && !navigatorDeliveryClosed) {
@@ -1873,7 +1889,7 @@ export function createRoleRuntimeExtension(
1873
1889
  // Parent session feeds #216 createRecordSession so baseline/bounce survive resume.
1874
1890
  if ("worker" in entry && entry.worker === true) {
1875
1891
  const workerArms = { coder, fixer } as const;
1876
- workerArms[entry.role].armSubmissionGate(ctx.cwd, ctx.sessionManager);
1892
+ workerArms[entry.role].armSubmissionGate(ctx.cwd, ctx.sessionManager, ctx.invocationScopeId);
1877
1893
  }
1878
1894
  admitted = true;
1879
1895
  } catch (error) {
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Field guidance for each public seat's submission tool (#1031).
3
+ * The tool name is the role registry's outputTool. This module does not restate it.
4
+ * Review seats keep the #1028 schema. Other seats keep their own field guidance.
5
+ * Execute lives in filed-submission.ts.
6
+ */
7
+ import { packagedRoleOutputTool, type PackagedRole } from "./packaged-role-registry.ts";
8
+ import { reviewSubmissionSchema } from "./review-submission.ts";
9
+ import { collectorOutputArgsSchema } from "./collector-tool-schemas.ts";
10
+ import { diaristOutputSchema } from "./diarist-contracts.ts";
11
+ import { doctorSubmissionSchema, DOCTOR_OUTPUT_TOOL_DESCRIPTION } from "./doctor-contracts.ts";
12
+ import { fixerOutputSchema } from "./package-contracts/fixer-output.ts";
13
+ import { gatekeeperOutputSchema } from "./package-contracts/gatekeeper-output.ts";
14
+ import { gleanerLeftOutputSchema } from "./gleaner-left-contracts.ts";
15
+ import { mergerOutputSchema } from "./merger-contracts.ts";
16
+ import { navigatorOutputSchema } from "./package-contracts/navigator-output.ts";
17
+ import { reviewerOutputSchema } from "./package-contracts/reviewer-output.ts";
18
+ import { secretariatVerdictSchema } from "./secretariat-contracts.ts";
19
+ import { coderOutputSchema } from "./package-contracts/worker-output.ts";
20
+
21
+ export type RoleSubmissionDeclaration = {
22
+ readonly role: PackagedRole;
23
+ readonly name: string;
24
+ readonly label: string;
25
+ readonly description: string;
26
+ readonly promptSnippet?: string;
27
+ readonly parameters: unknown;
28
+ };
29
+
30
+ type SubmissionFace = Omit<RoleSubmissionDeclaration, "role" | "name">;
31
+
32
+ const SUBMISSION_FACE = {
33
+ judge: {
34
+ label: "大理寺输出",
35
+ description: "提交大理寺终局判词;交卷调用结束后,由公开调用接缝按判词状态执行适用审核。",
36
+ promptSnippet: "提交大理寺终局判词",
37
+ parameters: reviewSubmissionSchema,
38
+ },
39
+ fixer: {
40
+ label: "修内司输出",
41
+ description: "提交修内司终局回执。",
42
+ promptSnippet: "提交修内司终局回执",
43
+ parameters: fixerOutputSchema,
44
+ },
45
+ coder: {
46
+ label: "将作监输出",
47
+ description: "提交将作监终局回执。",
48
+ promptSnippet: "提交将作监终局回执",
49
+ parameters: coderOutputSchema,
50
+ },
51
+ reviewer: {
52
+ label: "御史台输出",
53
+ description: "提交御史台终局回执。",
54
+ promptSnippet: "提交御史台终局回执",
55
+ parameters: reviewerOutputSchema,
56
+ },
57
+ collector: {
58
+ label: "通进司输出",
59
+ description: "提交通进司回执。",
60
+ promptSnippet: "提交通进司回执",
61
+ parameters: collectorOutputArgsSchema,
62
+ },
63
+ doctor: {
64
+ label: "太医署输出",
65
+ description: DOCTOR_OUTPUT_TOOL_DESCRIPTION,
66
+ parameters: doctorSubmissionSchema,
67
+ },
68
+ merger: {
69
+ label: "合并输出",
70
+ description: "提交合并结果;输出分支为 completed 与 escalate。",
71
+ promptSnippet: "提交合并结果",
72
+ parameters: mergerOutputSchema,
73
+ },
74
+ notary: {
75
+ label: "符宝郎输出",
76
+ description: "提交引文保真与票面对齐的 converged/continue/escalate 决议。",
77
+ promptSnippet: "提交符宝郎决议",
78
+ parameters: reviewSubmissionSchema,
79
+ },
80
+ countersign: {
81
+ label: "给事中输出",
82
+ description: "给事中决议。",
83
+ promptSnippet: "给事中决议",
84
+ parameters: reviewSubmissionSchema,
85
+ },
86
+ secretariat: {
87
+ label: "中书省输出",
88
+ description: "中书省终局回执。",
89
+ promptSnippet: "中书省终局回执",
90
+ parameters: secretariatVerdictSchema,
91
+ },
92
+ "gleaner-left": {
93
+ label: "左拾遗输出",
94
+ description: "左拾遗弹章。",
95
+ promptSnippet: "左拾遗弹章",
96
+ parameters: gleanerLeftOutputSchema,
97
+ },
98
+ inspector: {
99
+ label: "台院输出",
100
+ description: "台院终局回执,状态为 converged、continue 或 escalate。",
101
+ promptSnippet: "台院终局回执",
102
+ parameters: reviewSubmissionSchema,
103
+ },
104
+ gatekeeper: {
105
+ label: "门下省决议",
106
+ description: "门下省终局决议。status 为 dispatch 或 pass。",
107
+ promptSnippet: "门下省决议",
108
+ parameters: gatekeeperOutputSchema,
109
+ },
110
+ navigator: {
111
+ label: "游奕使建议",
112
+ description: "游奕使终局回执:散文建议,原样呈现。",
113
+ promptSnippet: "游奕使建议",
114
+ parameters: navigatorOutputSchema,
115
+ },
116
+ auditor: {
117
+ label: "审刑院输出",
118
+ description: "审刑院终局回执,status 为 converged、continue 或 escalate。",
119
+ promptSnippet: "审刑院终局回执",
120
+ parameters: reviewSubmissionSchema,
121
+ },
122
+ diarist: {
123
+ label: "起居郎输出",
124
+ description: "起居郎交单票 sessions 或多票逐票 ticketSessions 边界;认不出本庭对象则 escalate。",
125
+ promptSnippet: "起居郎交边界",
126
+ parameters: diaristOutputSchema,
127
+ },
128
+ } as const satisfies Record<PackagedRole, SubmissionFace>;
129
+
130
+ /** Tool name comes from the registry record. Label and schema stay on this face. */
131
+ export function roleSubmissionDeclaration(role: PackagedRole): RoleSubmissionDeclaration {
132
+ const name = packagedRoleOutputTool(role);
133
+ if (name === undefined) throw new Error(`${role} output tool is not declared`);
134
+ const face = SUBMISSION_FACE[role];
135
+ return {
136
+ role,
137
+ name,
138
+ label: face.label,
139
+ description: face.description,
140
+ ...("promptSnippet" in face ? { promptSnippet: face.promptSnippet } : {}),
141
+ parameters: face.parameters,
142
+ };
143
+ }
@@ -7,7 +7,42 @@
7
7
  * is defaulted, rewritten, or dropped (ADR 0055).
8
8
  */
9
9
 
10
+ import { Type } from "typebox";
11
+
12
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
13
+
10
14
  export const SECRETARIAT_OUTPUT_TOOL_NAME = "ak_secretariat_output";
15
+
16
+ /**
17
+ * 中书省终局回执形状。
18
+ * #1134: `secretariatStatus` alone is the trajectory field
19
+ * (src/packaged-role-registry.ts receiptStatusKey; the public settlement reads
20
+ * converged | escalate to route); its words ride the description. note/evidence/
21
+ * decisionGate are declared name + semantic description only — the string type, the
22
+ * number type, the nested question/options object shape and the required constraint
23
+ * are deleted, because the package declaration IS the host's pre-dispatch validator.
24
+ * (ticketNumber keeps its own declaration: it is the declared seat identity the
25
+ * activation flag and settlement read, not narrative content.)
26
+ */
27
+ export const secretariatVerdictSchema = withTerminatingOutputDeclarations(
28
+ Type.Object(
29
+ {
30
+ secretariatStatus: Type.Unknown({
31
+ description: "converged | escalate",
32
+ }),
33
+ ticketNumber: Type.Optional(
34
+ Type.Unknown({ description: "本票号;署时指向最终正文所在票" }),
35
+ ),
36
+ note: Type.Unknown({ description: "附注" }),
37
+ evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
38
+ decisionGate: Type.Unknown({
39
+ description: "需陛下处置的问题与选项;可写 question 与 options,原样留存。",
40
+ }),
41
+ },
42
+ { additionalProperties: true },
43
+ ),
44
+ );
45
+ (secretariatVerdictSchema as unknown as { required: string[] }).required = [];
11
46
  /**
12
47
  * #969 durable custom entry: 给事中 terminal (署|上呈) for seat settlement projection.
13
48
  * Carries original receipt bytes + nested runId. Envelope persists custom entries
@@ -4,54 +4,18 @@
4
4
  * shared envelope — src/role-runtime.ts (ADR 0018 / #924).
5
5
  */
6
6
  import type { Static } from "typebox";
7
- import { Type } from "typebox";
8
7
 
9
- import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
10
- import { SECRETARIAT_OUTPUT_TOOL_NAME, type SecretariatVerdict } from "./secretariat-contracts.ts";
8
+ import { SECRETARIAT_OUTPUT_TOOL_NAME, secretariatVerdictSchema, type SecretariatVerdict } from "./secretariat-contracts.ts";
9
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
11
10
 
12
11
  export { SECRETARIAT_OUTPUT_TOOL_NAME } from "./secretariat-contracts.ts";
13
12
  export type { SecretariatVerdict };
14
13
 
15
- /**
16
- * 中书省终局回执形状。
17
- * #1134: `secretariatStatus` alone is the trajectory field
18
- * (src/packaged-role-registry.ts:406 receiptStatusKey; the public settlement reads
19
- * converged | escalate to route); its words ride the description. note/evidence/
20
- * decisionGate are declared name + semantic description only — the string type, the
21
- * number type, the nested question/options object shape and the required constraint
22
- * are deleted, because the package declaration IS the host's pre-dispatch validator.
23
- * (ticketNumber keeps its own declaration: it is the declared seat identity the
24
- * activation flag and settlement read, not narrative content.)
25
- */
26
- export const secretariatVerdictSchema = withTerminatingOutputDeclarations(
27
- Type.Object(
28
- {
29
- secretariatStatus: Type.Unknown({
30
- description: "converged | escalate",
31
- }),
32
- ticketNumber: Type.Optional(
33
- Type.Unknown({ description: "本票号;署时指向最终正文所在票" }),
34
- ),
35
- note: Type.Unknown({ description: "附注" }),
36
- evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
37
- decisionGate: Type.Unknown({
38
- description: "需陛下处置的问题与选项;可写 question 与 options,原样留存。",
39
- }),
40
- },
41
- { additionalProperties: true },
42
- ),
43
- );
44
- (secretariatVerdictSchema as unknown as { required: string[] }).required = [];
14
+ export { secretariatVerdictSchema } from "./secretariat-contracts.ts";
45
15
 
46
16
  export type SecretariatVerdictParameters = Static<typeof secretariatVerdictSchema>;
47
17
 
48
- export const SECRETARIAT_OUTPUT_TOOL_SPEC = {
49
- name: SECRETARIAT_OUTPUT_TOOL_NAME,
50
- label: "中书省输出",
51
- description: "中书省终局回执。",
52
- promptSnippet: "中书省终局回执",
53
- parameters: secretariatVerdictSchema,
54
- } as const;
18
+ export const SECRETARIAT_OUTPUT_TOOL_SPEC = roleSubmissionDeclaration("secretariat");
55
19
 
56
20
  export type SecretariatRuntimeDependencies = {
57
21
  loadSoul(): Promise<string>;
@@ -36,8 +36,11 @@ export function isCorrectableExecuteError(error: unknown): boolean {
36
36
  }
37
37
 
38
38
  /**
39
- * Durable projection for ACP envelope tool catches only.
40
- * Pi path keeps native throw → isError toolResult (no shared projection consumer).
39
+ * Durable projection for correctable execute errors.
40
+ * The ACP envelope consumes it on every correctable catch. The Pi adapter
41
+ * consumes it for the one-shot commit and prefix reminders and the
42
+ * unfinished-reason reminder, returned as an isError tool result. Other Pi
43
+ * correctable errors stay native throws.
41
44
  */
42
45
  export type CorrectableExecuteRejectionProjection = {
43
46
  readonly diagnostic: string;
@@ -51,10 +51,21 @@ export class WorkerPrefixReminderError extends Error {
51
51
  }
52
52
  }
53
53
 
54
+ /**
55
+ * ADR 0066/0070 one-shot commit and prefix reminders.
56
+ * They stay a correction the same session may make, and they do not occupy
57
+ * the configured re-ask or 催交 budget (#1132).
58
+ */
59
+ export function isOneShotWorkerReminderCode(code: unknown): boolean {
60
+ return code === "worker_commit_reminder" || code === "worker_prefix_reminder";
61
+ }
62
+
54
63
  export class WorkerUnfinishedReasonReminderError extends Error {
55
64
  readonly code = "worker_unfinished_reason_reminder" as const;
56
65
  constructor() {
57
- super("本次 unfinished 回执未含 reason;本接缝缺由至多打回两次。");
66
+ // The ceiling is the configured unfinished-reason limit. This text reaches
67
+ // the model and does not restate a count.
68
+ super("本次 unfinished 回执未含 reason;请补上 reason 后再交。");
58
69
  this.name = "WorkerUnfinishedReasonReminderError";
59
70
  }
60
71
  }