@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
@@ -27,7 +27,7 @@ import { REVIEWER_OUTPUT_TOOL_NAME, } from "../package-contracts/reviewer-output
27
27
  import { packagedAuditToolName, packagedDurableOfficerEntry, packagedRoleAcceptedOutputTool, packagedRoleMetadata, packagedSkipsGateOnInfrastructureStage, } from "../packaged-role-registry.js";
28
28
  import { SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, } from "../secretariat-contracts.js";
29
29
  import { classifyPackagedRoleTerminalResult, findLatestDurablePackagedRoleTerminal, hasNavigatorInfrastructureFailureBase, isAcceptedPackagedRoleTerminalResult, NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, } from "../navigator-invocation-identity.js";
30
- import { NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_TURN_LIMIT, noReceiptLifecycleFacts, parseNoReceiptLifecycleFacts, } from "../receipt-delivery-policy.js";
30
+ import { NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, noReceiptLifecycleFacts, parseNoReceiptLifecycleFacts, priorReceiptContinuation, receiptAttemptPointer, } from "../receipt-delivery-policy.js";
31
31
  import { roleRunArtifactsDirectory } from "../role-run-placement.js";
32
32
  import { rewriteRunDirectoryPathValue } from "../role-run-relocation.js";
33
33
  import { listSeamOwnedUniqueErrorFacePaths, RUN_TERMINAL_ARTIFACT_FILES, RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS, } from "../run-terminal-artifacts.js";
@@ -139,13 +139,100 @@ export async function attachRecordedSubmissions(admitted, terminal, scope) {
139
139
  * Does not invent an output failure for an empty ledger.
140
140
  * #953: empty public terminal face — clear every reader-adoptable prior face.
141
141
  */
142
- export async function settleHostEndedNoReceipt(admitted, authority, scope) {
142
+ /**
143
+ * Lifecycle fact the in-process runtime already wrote for this run and attempt.
144
+ * Absent, malformed, or a previous attempt's bytes are not a count.
145
+ */
146
+ function lifecycleFactsFromEntry(entry) {
147
+ const customType = entry.customType ?? entry.message?.customType;
148
+ if (customType !== NO_RECEIPT_LIFECYCLE_ENTRY_TYPE)
149
+ return undefined;
150
+ return entry.data ?? entry.message?.details;
151
+ }
152
+ async function readCurrentAttemptNoReceiptFacts(admitted, authority, scope) {
153
+ const { sessionFile } = coordinatesFromAdmitted(authority, admitted);
154
+ const entries = await readBoundSessionEntries(sessionFile).catch(() => undefined);
155
+ if (entries === undefined)
156
+ return undefined;
157
+ const scopeId = scope?.invocationScopeId?.trim() ?? "";
158
+ if (scopeId.length > 0) {
159
+ const pointer = receiptAttemptPointer(admitted.runDirectory, scopeId);
160
+ let selected;
161
+ for (const entry of entries) {
162
+ const raw = lifecycleFactsFromEntry(entry);
163
+ if (raw === undefined)
164
+ continue;
165
+ try {
166
+ const facts = parseNoReceiptLifecycleFacts(raw);
167
+ if (facts.runPointer !== admitted.runDirectory)
168
+ continue;
169
+ if (facts.attemptPointer !== pointer)
170
+ continue;
171
+ if (selected === undefined || facts.deliveryTurns >= selected.deliveryTurns) {
172
+ selected = facts;
173
+ }
174
+ }
175
+ catch {
176
+ // A malformed historical entry is not this call's count.
177
+ }
178
+ }
179
+ return selected;
180
+ }
181
+ let attemptStart = 0;
182
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
183
+ if (entries[index]?.type === "message" && entries[index]?.message?.role === "user") {
184
+ attemptStart = index;
185
+ break;
186
+ }
187
+ }
188
+ const lifecycleEntry = entries.slice(attemptStart).reverse().find((entry) => lifecycleFactsFromEntry(entry) !== undefined);
189
+ const raw = lifecycleEntry === undefined ? undefined : lifecycleFactsFromEntry(lifecycleEntry);
190
+ if (raw === undefined)
191
+ return undefined;
192
+ let facts;
193
+ try {
194
+ facts = parseNoReceiptLifecycleFacts(raw);
195
+ }
196
+ catch {
197
+ return undefined;
198
+ }
199
+ if (facts.runPointer !== admitted.runDirectory)
200
+ return undefined;
201
+ if (facts.attemptPointer !== receiptAttemptPointer(admitted.runDirectory))
202
+ return undefined;
203
+ return facts;
204
+ }
205
+ export async function settleHostEndedNoReceipt(admitted, authority, scope,
206
+ /**
207
+ * #1132: delivery requests this run actually issued. Zero stays zero when
208
+ * nothing was sent and this attempt has no lifecycle fact. A fact already
209
+ * written for this run and attempt supplies the count when it is larger.
210
+ * The budget is never the count, and another loop's resumes are not催交.
211
+ */
212
+ issuedDeliveryRequests = 0) {
213
+ const scopeId = scope?.invocationScopeId?.trim() ?? "";
214
+ const persisted = scopeId.length > 0
215
+ ? undefined
216
+ : await readCurrentAttemptNoReceiptFacts(admitted, authority, scope);
217
+ let terminalToolCalled = persisted?.terminalToolCalled ?? false;
218
+ let rejectedReceipts = persisted?.rejectedReceipts ?? [];
219
+ let recordedTurns = persisted?.deliveryTurns ?? 0;
220
+ if (scopeId.length > 0) {
221
+ const { sessionFile } = coordinatesFromAdmitted(authority, admitted);
222
+ const entries = await readBoundSessionEntries(sessionFile).catch(() => undefined);
223
+ if (entries !== undefined) {
224
+ const prior = priorReceiptContinuation(entries, scopeId);
225
+ terminalToolCalled = prior.terminalToolCalled;
226
+ rejectedReceipts = prior.rejectedReceipts;
227
+ recordedTurns = prior.deliveryTurns;
228
+ }
229
+ }
143
230
  const facts = noReceiptLifecycleFacts({
144
- terminalToolCalled: false,
145
- rejectedReceipts: [],
146
- deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
231
+ terminalToolCalled,
232
+ rejectedReceipts,
233
+ deliveryTurns: Math.max(issuedDeliveryRequests, recordedTurns),
147
234
  runPointer: admitted.runDirectory,
148
- attemptPointer: `current:${admitted.runDirectory}`,
235
+ attemptPointer: receiptAttemptPointer(admitted.runDirectory, scope?.invocationScopeId),
149
236
  });
150
237
  const coordinates = coordinatesFromAdmitted(authority, admitted);
151
238
  await clearOppositeTerminalArtifactFace(admitted.runDirectory);
@@ -2286,48 +2373,24 @@ export async function settleFailureTerminalResult(admitted, failure, authority,
2286
2373
  // current-attempt fact. Transcript reconstruction must not turn arbitrary output
2287
2374
  // failures (or bytes retained from a prior resume attempt) into exit zero.
2288
2375
  if (failure.cause === "output") {
2289
- const entries = await readBoundSessionEntries(sessionFile).catch(() => undefined);
2290
- if (entries !== undefined) {
2291
- let attemptStart = 0;
2292
- for (let index = entries.length - 1; index >= 0; index -= 1) {
2293
- if (entries[index]?.type === "message" && entries[index]?.message?.role === "user") {
2294
- attemptStart = index;
2295
- break;
2296
- }
2297
- }
2298
- const lifecycleEntry = entries.slice(attemptStart).reverse().find((entry) => entry.customType === NO_RECEIPT_LIFECYCLE_ENTRY_TYPE || entry.message?.customType === NO_RECEIPT_LIFECYCLE_ENTRY_TYPE);
2299
- const raw = lifecycleEntry?.data ?? lifecycleEntry?.message?.details;
2300
- if (raw !== undefined) {
2301
- // Catch only covers lifecycle-byte parse. Clear / navigator / gate I/O
2302
- // after a valid parse must keep their real failure identity (#953).
2303
- let facts;
2304
- try {
2305
- facts = parseNoReceiptLifecycleFacts(raw);
2306
- }
2307
- catch {
2308
- /* malformed lifecycle bytes remain the existing nonzero output failure */
2309
- }
2310
- if (facts !== undefined &&
2311
- facts.runPointer === admitted.runDirectory &&
2312
- facts.attemptPointer === `current:${admitted.runDirectory}`) {
2313
- let decisiveFacts = facts;
2314
- // #478: no_receipt is still a public Terminal — project accepted gate facts.
2315
- // #953: empty public terminal face — clear every reader-adoptable prior face.
2316
- await clearOppositeTerminalArtifactFace(admitted.runDirectory);
2317
- return withOptionalGateProjection({
2318
- roleOutcome: {
2319
- kind: "no_receipt",
2320
- role: admitted.role,
2321
- status: "no-accepted-receipt",
2322
- ...facts,
2323
- decisiveFacts,
2324
- },
2325
- navigator: await extractNavigatorFactFromAdmittedSession(sessionFile),
2326
- artifacts: [],
2327
- runId: admitted.runId,
2328
- }, sessionDirectory, detourGateContext(admitted, options));
2329
- }
2330
- }
2376
+ const facts = await readCurrentAttemptNoReceiptFacts(admitted, authority, options);
2377
+ if (facts !== undefined) {
2378
+ const decisiveFacts = facts;
2379
+ // #478: no_receipt is still a public Terminal — project accepted gate facts.
2380
+ // #953: empty public terminal face — clear every reader-adoptable prior face.
2381
+ await clearOppositeTerminalArtifactFace(admitted.runDirectory);
2382
+ return withOptionalGateProjection({
2383
+ roleOutcome: {
2384
+ kind: "no_receipt",
2385
+ role: admitted.role,
2386
+ status: "no-accepted-receipt",
2387
+ ...facts,
2388
+ decisiveFacts,
2389
+ },
2390
+ navigator: await extractNavigatorFactFromAdmittedSession(sessionFile),
2391
+ artifacts: [],
2392
+ runId: admitted.runId,
2393
+ }, sessionDirectory, detourGateContext(admitted, options));
2331
2394
  }
2332
2395
  }
2333
2396
  // Exact-session attendance only — never infer no-advice from caller omission.
@@ -69,6 +69,17 @@ export function formatTerminalResult(result) {
69
69
  if (result.roleOutcome.kind === "failure") {
70
70
  lines.push(`diagnostic\t${encodeTerminalField(result.roleOutcome.diagnostic)}`);
71
71
  }
72
+ // Accepted volumes do not dump decisiveFacts unless a failed attempt is
73
+ // recorded. The unsettled-direction fact still has to be visible on its own.
74
+ // audit_escalation already dumps every decisiveFact below.
75
+ if (result.roleOutcome.kind === "accepted"
76
+ && result.roleOutcome.decisiveFacts?.directionUnsettled === true) {
77
+ lines.push("fact\tdirectionUnsettled\ttrue");
78
+ const subsequent = result.roleOutcome.decisiveFacts.subsequentAudit;
79
+ if (typeof subsequent === "string") {
80
+ lines.push(`fact\tsubsequentAudit\t${encodeTerminalField(subsequent)}`);
81
+ }
82
+ }
72
83
  if (result.roleOutcome.kind === "failure" || result.roleOutcome.kind === "no_receipt" || result.roleOutcome.kind === "audit_escalation" || result.roleOutcome.decisiveFacts?.failedAttempts !== undefined) {
73
84
  const facts = result.roleOutcome.kind === "accepted"
74
85
  ? { failedAttempts: result.roleOutcome.decisiveFacts?.failedAttempts }
@@ -140,7 +151,7 @@ export function formatTerminalResult(result) {
140
151
  else {
141
152
  const payloads = result.roleOutcome.kind === "accepted" ||
142
153
  result.roleOutcome.kind === "audit_escalation"
143
- ? coalesceSubmissionRows(result.roleOutcome.payloads, result.submissions)
154
+ ? coalesceSubmissionRows(result.submissions, result.roleOutcome.payloads)
144
155
  : result.submissions ?? [];
145
156
  for (let i = payloads.length - 1; i >= 0; i -= 1) {
146
157
  const payload = payloads[i];
@@ -46,6 +46,9 @@ export function projectRoleTurnRequest(admitted, roleDetails, options) {
46
46
  ? {}
47
47
  : { invocationScopeId: options.invocationScopeId }),
48
48
  ...(options.stationChild === undefined ? {} : { stationChild: options.stationChild }),
49
+ ...(options.deliveryRequestLimit === undefined
50
+ ? {}
51
+ : { deliveryRequestLimit: options.deliveryRequestLimit }),
49
52
  };
50
53
  }
51
54
  /**
@@ -1,8 +1,33 @@
1
+ import { AUTO_RESUME_LIMIT } from "./public-cli/run-lifecycle.js";
2
+ import { parseAutoResumeLimit } from "./public-cli/config.js";
1
3
  import { isRecord } from "./unknown-value.js";
2
- const RECEIPT_DELIVERY_TURN_LIMIT = 2;
3
4
  const NO_RECEIPT_LIFECYCLE_ENTRY_TYPE = "ak-no-receipt-lifecycle";
5
+ const RECEIPT_DELIVERY_REQUEST_ENTRY = "ak-receipt-delivery-request";
6
+ const RECEIPT_REJECTION_ENTRY = "ak-receipt-rejection";
7
+ function receiptAttemptPointer(runPointer, invocationScopeId) {
8
+ const scope = invocationScopeId?.trim() ?? "";
9
+ if (scope.length > 0) return `invocation:${scope}`;
10
+ return `current:${runPointer}`;
11
+ }
12
+ const RECEIPT_DELIVERY_LIMIT_ENV = "AK_ROLE_RECEIPT_DELIVERY_LIMIT";
13
+ function deliveryLimitFromConfig(value) {
14
+ return parseAutoResumeLimit(value ?? AUTO_RESUME_LIMIT);
15
+ }
16
+ function deliveryLimitFromEnv(env) {
17
+ const raw = env[RECEIPT_DELIVERY_LIMIT_ENV];
18
+ if (raw === void 0 || raw.trim() === "") return deliveryLimitFromConfig(void 0);
19
+ const parsed = Number(raw);
20
+ if (!Number.isInteger(parsed)) return deliveryLimitFromConfig(void 0);
21
+ return deliveryLimitFromConfig(parsed);
22
+ }
23
+ function deliveryTurnsFact(value) {
24
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
25
+ throw new TypeError("deliveryTurns must be a non-negative integer");
26
+ }
27
+ return value;
28
+ }
4
29
  function parseNoReceiptLifecycleFacts(input) {
5
- if (!isRecord(input) || typeof input.terminalToolCalled !== "boolean" || input.deliveryTurns !== RECEIPT_DELIVERY_TURN_LIMIT || input.sessionCompletion !== "settled-without-accepted-receipt" || input.acceptedReceipt !== false || typeof input.runPointer !== "string" || input.runPointer.trim() === "" || typeof input.attemptPointer !== "string" || input.attemptPointer.trim() === "" || !Array.isArray(input.rejectedReceipts) || !input.rejectedReceipts.every((item) => isRecord(item) && typeof item.reason === "string")) {
30
+ if (!isRecord(input) || typeof input.terminalToolCalled !== "boolean" || input.sessionCompletion !== "settled-without-accepted-receipt" || input.acceptedReceipt !== false || typeof input.runPointer !== "string" || input.runPointer.trim() === "" || typeof input.attemptPointer !== "string" || input.attemptPointer.trim() === "" || !Array.isArray(input.rejectedReceipts) || !input.rejectedReceipts.every((item) => isRecord(item) && typeof item.reason === "string")) {
6
31
  throw new TypeError("malformed no-receipt lifecycle facts");
7
32
  }
8
33
  return {
@@ -11,7 +36,7 @@ function parseNoReceiptLifecycleFacts(input) {
11
36
  reason: item.reason,
12
37
  diagnosticAvailable: item.reason.trim() !== ""
13
38
  })),
14
- deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
39
+ deliveryTurns: deliveryTurnsFact(input.deliveryTurns),
15
40
  sessionCompletion: "settled-without-accepted-receipt",
16
41
  runPointer: input.runPointer,
17
42
  attemptPointer: input.attemptPointer,
@@ -19,28 +44,33 @@ function parseNoReceiptLifecycleFacts(input) {
19
44
  };
20
45
  }
21
46
  function noReceiptLifecycleFacts(input) {
22
- if (input.deliveryTurns !== RECEIPT_DELIVERY_TURN_LIMIT) {
23
- throw new TypeError("no-receipt lifecycle requires an exhausted delivery budget");
24
- }
25
47
  return {
26
48
  terminalToolCalled: input.terminalToolCalled,
27
49
  rejectedReceipts: input.rejectedReceipts.map(({ reason }) => ({
28
50
  reason,
29
51
  diagnosticAvailable: reason.trim() !== ""
30
52
  })),
31
- deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
53
+ deliveryTurns: deliveryTurnsFact(input.deliveryTurns),
32
54
  sessionCompletion: "settled-without-accepted-receipt",
33
55
  runPointer: input.runPointer,
34
56
  attemptPointer: input.attemptPointer,
35
57
  acceptedReceipt: false
36
58
  };
37
59
  }
38
- function createReceiptDeliveryPolicy() {
60
+ function createReceiptDeliveryPolicy(limit) {
61
+ const deliveryLimit = deliveryLimitFromConfig(limit);
39
62
  let accepted = false;
63
+ let closed = false;
40
64
  let terminalToolCalled = false;
41
65
  let deliveryTurns = 0;
66
+ let budgetSpent = 0;
42
67
  const rejectedReceipts = [];
68
+ const spend = () => {
69
+ if (budgetSpent < deliveryLimit) budgetSpent += 1;
70
+ };
43
71
  return {
72
+ /** The effective ceiling this budget spends (#1132 — never re-read). */
73
+ limit: deliveryLimit,
44
74
  recordAccepted() {
45
75
  accepted = true;
46
76
  terminalToolCalled = true;
@@ -49,27 +79,46 @@ function createReceiptDeliveryPolicy() {
49
79
  stopForInfrastructure() {
50
80
  accepted = true;
51
81
  },
82
+ /**
83
+ * Record the rejection this turn already produced.
84
+ * The fact does not spend a budget. The seam that sends the next prompt
85
+ * owns that count.
86
+ */
52
87
  recordRejected(reason) {
53
88
  terminalToolCalled = true;
54
89
  rejectedReceipts.push({ reason, diagnosticAvailable: reason.trim() !== "" });
55
- deliveryTurns = Math.min(RECEIPT_DELIVERY_TURN_LIMIT, deliveryTurns + 1);
56
90
  },
57
91
  recordDeliveryRequest() {
58
- deliveryTurns = Math.min(RECEIPT_DELIVERY_TURN_LIMIT, deliveryTurns + 1);
92
+ deliveryTurns += 1;
93
+ spend();
59
94
  },
60
95
  /**
61
- * A nested session settled without an accepted receipt after spending its own
62
- * delivery budget. Adopt its facts and close this budget: another prompt would
63
- * open an independent session, not a delivery request on the settled one.
96
+ * Stop asking. The issued count stays whatever was actually sent (#1132 —
97
+ * do not fill the remainder up to the ceiling).
98
+ */
99
+ closeBudget() {
100
+ closed = true;
101
+ },
102
+ /**
103
+ * A nested session settled without an accepted receipt. Close so this layer
104
+ * does not open another prompt on that session. The issued count stays the
105
+ * larger of the two seams: a nested zero must not wipe prompts this layer
106
+ * already sent, and adding the two counts would bill one send twice.
64
107
  */
65
108
  recordNestedNoReceipt(facts) {
66
109
  terminalToolCalled = terminalToolCalled || facts.terminalToolCalled;
67
110
  rejectedReceipts.push(...facts.rejectedReceipts);
68
- deliveryTurns = RECEIPT_DELIVERY_TURN_LIMIT;
111
+ deliveryTurns = Math.max(deliveryTurns, facts.deliveryTurns);
112
+ closed = true;
69
113
  },
70
114
  nextAction() {
71
115
  if (accepted) return "accepted";
72
- return deliveryTurns < RECEIPT_DELIVERY_TURN_LIMIT ? "request-delivery" : "no-receipt";
116
+ if (closed) return "no-receipt";
117
+ return budgetSpent < deliveryLimit ? "request-delivery" : "no-receipt";
118
+ },
119
+ /** Delivery requests actually issued so far (#1132). */
120
+ issuedDeliveryRequests() {
121
+ return deliveryTurns;
73
122
  },
74
123
  /** Current typed delivery facts. No prose. */
75
124
  deliveryState() {
@@ -82,13 +131,99 @@ function createReceiptDeliveryPolicy() {
82
131
  },
83
132
  facts(binding) {
84
133
  return noReceiptLifecycleFacts({ terminalToolCalled, rejectedReceipts: [...rejectedReceipts], deliveryTurns, ...binding });
134
+ },
135
+ /**
136
+ * Continue a public call after the in-memory policy was rebuilt.
137
+ * Sends already issued spend the same budget; copied rejections keep
138
+ * the fact and do not spend again.
139
+ */
140
+ continueIssued(prior) {
141
+ for (const receipt of prior.rejectedReceipts) {
142
+ this.recordRejected(receipt.reason);
143
+ }
144
+ if (prior.terminalToolCalled === true) terminalToolCalled = true;
145
+ for (let index = 0; index < prior.deliveryTurns; index += 1) {
146
+ this.recordDeliveryRequest();
147
+ }
85
148
  }
86
149
  };
87
150
  }
151
+ function entryRecord(entry) {
152
+ return isRecord(entry) ? entry : void 0;
153
+ }
154
+ function customTypeOf(record, message) {
155
+ return record.customType ?? message?.customType;
156
+ }
157
+ function dataOf(record, message) {
158
+ return record.data ?? message?.details;
159
+ }
160
+ function scopeTag(record) {
161
+ const message = isRecord(record.message) ? record.message : void 0;
162
+ const customType = customTypeOf(record, message);
163
+ const data = dataOf(record, message);
164
+ if ((customType === RECEIPT_DELIVERY_REQUEST_ENTRY || customType === RECEIPT_REJECTION_ENTRY) && isRecord(data) && typeof data.invocationScopeId === "string" && data.invocationScopeId.trim() !== "") {
165
+ return data.invocationScopeId;
166
+ }
167
+ if (customType !== NO_RECEIPT_LIFECYCLE_ENTRY_TYPE) return void 0;
168
+ try {
169
+ const facts = parseNoReceiptLifecycleFacts(data);
170
+ const prefix = "invocation:";
171
+ if (!facts.attemptPointer.startsWith(prefix)) return void 0;
172
+ const scope = facts.attemptPointer.slice(prefix.length);
173
+ return scope.trim() === "" ? void 0 : scope;
174
+ } catch {
175
+ return void 0;
176
+ }
177
+ }
178
+ function priorReceiptContinuation(entries, invocationScopeId) {
179
+ const pointer = receiptAttemptPointer("", invocationScopeId);
180
+ let entryTurns = 0;
181
+ let factTurns = 0;
182
+ let rejectedReceipts = [];
183
+ let snapshotTerminal = false;
184
+ for (const entry of entries) {
185
+ const record = entryRecord(entry);
186
+ if (record === void 0) continue;
187
+ const tagged = scopeTag(record);
188
+ if (tagged !== void 0) {
189
+ const message = isRecord(record.message) ? record.message : void 0;
190
+ const customType = customTypeOf(record, message);
191
+ if (tagged !== invocationScopeId) continue;
192
+ if (customType === RECEIPT_DELIVERY_REQUEST_ENTRY) entryTurns += 1;
193
+ if (customType === RECEIPT_REJECTION_ENTRY) {
194
+ const data = dataOf(record, message);
195
+ if (isRecord(data) && typeof data.reason === "string") rejectedReceipts.push({ reason: data.reason });
196
+ }
197
+ if (customType !== NO_RECEIPT_LIFECYCLE_ENTRY_TYPE) continue;
198
+ try {
199
+ const facts = parseNoReceiptLifecycleFacts(dataOf(record, message));
200
+ if (facts.attemptPointer !== pointer) continue;
201
+ factTurns = Math.max(factTurns, facts.deliveryTurns);
202
+ if (facts.rejectedReceipts.length >= rejectedReceipts.length) {
203
+ rejectedReceipts = facts.rejectedReceipts.map(({ reason }) => ({ reason }));
204
+ }
205
+ snapshotTerminal ||= facts.terminalToolCalled;
206
+ } catch {
207
+ }
208
+ continue;
209
+ }
210
+ }
211
+ return {
212
+ deliveryTurns: Math.max(entryTurns, factTurns),
213
+ rejectedReceipts,
214
+ terminalToolCalled: snapshotTerminal || rejectedReceipts.length > 0
215
+ };
216
+ }
88
217
  export {
89
218
  NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
90
- RECEIPT_DELIVERY_TURN_LIMIT,
219
+ RECEIPT_DELIVERY_LIMIT_ENV,
220
+ RECEIPT_DELIVERY_REQUEST_ENTRY,
221
+ RECEIPT_REJECTION_ENTRY,
91
222
  createReceiptDeliveryPolicy,
223
+ deliveryLimitFromConfig,
224
+ deliveryLimitFromEnv,
92
225
  noReceiptLifecycleFacts,
93
- parseNoReceiptLifecycleFacts
226
+ parseNoReceiptLifecycleFacts,
227
+ priorReceiptContinuation,
228
+ receiptAttemptPointer
94
229
  };
@@ -0,0 +1,131 @@
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 } from "./packaged-role-registry.js";
8
+ import { reviewSubmissionSchema } from "./review-submission.js";
9
+ import { collectorOutputArgsSchema } from "./collector-tool-schemas.js";
10
+ import { diaristOutputSchema } from "./diarist-contracts.js";
11
+ import { doctorSubmissionSchema, DOCTOR_OUTPUT_TOOL_DESCRIPTION } from "./doctor-contracts.js";
12
+ import { fixerOutputSchema } from "./package-contracts/fixer-output.js";
13
+ import { gatekeeperOutputSchema } from "./package-contracts/gatekeeper-output.js";
14
+ import { gleanerLeftOutputSchema } from "./gleaner-left-contracts.js";
15
+ import { mergerOutputSchema } from "./merger-contracts.js";
16
+ import { navigatorOutputSchema } from "./package-contracts/navigator-output.js";
17
+ import { reviewerOutputSchema } from "./package-contracts/reviewer-output.js";
18
+ import { secretariatVerdictSchema } from "./secretariat-contracts.js";
19
+ import { coderOutputSchema } from "./package-contracts/worker-output.js";
20
+ const SUBMISSION_FACE = {
21
+ judge: {
22
+ label: "大理寺输出",
23
+ description: "提交大理寺终局判词;交卷调用结束后,由公开调用接缝按判词状态执行适用审核。",
24
+ promptSnippet: "提交大理寺终局判词",
25
+ parameters: reviewSubmissionSchema,
26
+ },
27
+ fixer: {
28
+ label: "修内司输出",
29
+ description: "提交修内司终局回执。",
30
+ promptSnippet: "提交修内司终局回执",
31
+ parameters: fixerOutputSchema,
32
+ },
33
+ coder: {
34
+ label: "将作监输出",
35
+ description: "提交将作监终局回执。",
36
+ promptSnippet: "提交将作监终局回执",
37
+ parameters: coderOutputSchema,
38
+ },
39
+ reviewer: {
40
+ label: "御史台输出",
41
+ description: "提交御史台终局回执。",
42
+ promptSnippet: "提交御史台终局回执",
43
+ parameters: reviewerOutputSchema,
44
+ },
45
+ collector: {
46
+ label: "通进司输出",
47
+ description: "提交通进司回执。",
48
+ promptSnippet: "提交通进司回执",
49
+ parameters: collectorOutputArgsSchema,
50
+ },
51
+ doctor: {
52
+ label: "太医署输出",
53
+ description: DOCTOR_OUTPUT_TOOL_DESCRIPTION,
54
+ parameters: doctorSubmissionSchema,
55
+ },
56
+ merger: {
57
+ label: "合并输出",
58
+ description: "提交合并结果;输出分支为 completed 与 escalate。",
59
+ promptSnippet: "提交合并结果",
60
+ parameters: mergerOutputSchema,
61
+ },
62
+ notary: {
63
+ label: "符宝郎输出",
64
+ description: "提交引文保真与票面对齐的 converged/continue/escalate 决议。",
65
+ promptSnippet: "提交符宝郎决议",
66
+ parameters: reviewSubmissionSchema,
67
+ },
68
+ countersign: {
69
+ label: "给事中输出",
70
+ description: "给事中决议。",
71
+ promptSnippet: "给事中决议",
72
+ parameters: reviewSubmissionSchema,
73
+ },
74
+ secretariat: {
75
+ label: "中书省输出",
76
+ description: "中书省终局回执。",
77
+ promptSnippet: "中书省终局回执",
78
+ parameters: secretariatVerdictSchema,
79
+ },
80
+ "gleaner-left": {
81
+ label: "左拾遗输出",
82
+ description: "左拾遗弹章。",
83
+ promptSnippet: "左拾遗弹章",
84
+ parameters: gleanerLeftOutputSchema,
85
+ },
86
+ inspector: {
87
+ label: "台院输出",
88
+ description: "台院终局回执,状态为 converged、continue 或 escalate。",
89
+ promptSnippet: "台院终局回执",
90
+ parameters: reviewSubmissionSchema,
91
+ },
92
+ gatekeeper: {
93
+ label: "门下省决议",
94
+ description: "门下省终局决议。status 为 dispatch 或 pass。",
95
+ promptSnippet: "门下省决议",
96
+ parameters: gatekeeperOutputSchema,
97
+ },
98
+ navigator: {
99
+ label: "游奕使建议",
100
+ description: "游奕使终局回执:散文建议,原样呈现。",
101
+ promptSnippet: "游奕使建议",
102
+ parameters: navigatorOutputSchema,
103
+ },
104
+ auditor: {
105
+ label: "审刑院输出",
106
+ description: "审刑院终局回执,status 为 converged、continue 或 escalate。",
107
+ promptSnippet: "审刑院终局回执",
108
+ parameters: reviewSubmissionSchema,
109
+ },
110
+ diarist: {
111
+ label: "起居郎输出",
112
+ description: "起居郎交单票 sessions 或多票逐票 ticketSessions 边界;认不出本庭对象则 escalate。",
113
+ promptSnippet: "起居郎交边界",
114
+ parameters: diaristOutputSchema,
115
+ },
116
+ };
117
+ /** Tool name comes from the registry record. Label and schema stay on this face. */
118
+ export function roleSubmissionDeclaration(role) {
119
+ const name = packagedRoleOutputTool(role);
120
+ if (name === undefined)
121
+ throw new Error(`${role} output tool is not declared`);
122
+ const face = SUBMISSION_FACE[role];
123
+ return {
124
+ role,
125
+ name,
126
+ label: face.label,
127
+ description: face.description,
128
+ ...("promptSnippet" in face ? { promptSnippet: face.promptSnippet } : {}),
129
+ parameters: face.parameters,
130
+ };
131
+ }
@@ -1,8 +1,30 @@
1
+ import { Type } from "typebox";
2
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.js";
1
3
  const SECRETARIAT_OUTPUT_TOOL_NAME = "ak_secretariat_output";
4
+ const secretariatVerdictSchema = withTerminatingOutputDeclarations(
5
+ Type.Object(
6
+ {
7
+ secretariatStatus: Type.Unknown({
8
+ description: "converged | escalate"
9
+ }),
10
+ ticketNumber: Type.Optional(
11
+ Type.Unknown({ description: "\u672C\u7968\u53F7\uFF1B\u7F72\u65F6\u6307\u5411\u6700\u7EC8\u6B63\u6587\u6240\u5728\u7968" })
12
+ ),
13
+ note: Type.Unknown({ description: "\u9644\u6CE8" }),
14
+ evidence: Type.Optional(Type.Unknown({ description: "\u7559\u5B58\u8BC1\u636E" })),
15
+ decisionGate: Type.Unknown({
16
+ description: "\u9700\u965B\u4E0B\u5904\u7F6E\u7684\u95EE\u9898\u4E0E\u9009\u9879\uFF1B\u53EF\u5199 question \u4E0E options\uFF0C\u539F\u6837\u7559\u5B58\u3002"
17
+ })
18
+ },
19
+ { additionalProperties: true }
20
+ )
21
+ );
22
+ secretariatVerdictSchema.required = [];
2
23
  const SECRETARIAT_GATE_OFFICER_ENTRY_TYPE = "ak-secretariat-gate-officer";
3
24
  const SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY = "countersignTerminal";
4
25
  export {
5
26
  SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY,
6
27
  SECRETARIAT_GATE_OFFICER_ENTRY_TYPE,
7
- SECRETARIAT_OUTPUT_TOOL_NAME
28
+ SECRETARIAT_OUTPUT_TOOL_NAME,
29
+ secretariatVerdictSchema
8
30
  };
@@ -44,10 +44,20 @@ export class WorkerPrefixReminderError extends Error {
44
44
  this.name = "WorkerPrefixReminderError";
45
45
  }
46
46
  }
47
+ /**
48
+ * ADR 0066/0070 one-shot commit and prefix reminders.
49
+ * They stay a correction the same session may make, and they do not occupy
50
+ * the configured re-ask or 催交 budget (#1132).
51
+ */
52
+ export function isOneShotWorkerReminderCode(code) {
53
+ return code === "worker_commit_reminder" || code === "worker_prefix_reminder";
54
+ }
47
55
  export class WorkerUnfinishedReasonReminderError extends Error {
48
56
  code = "worker_unfinished_reason_reminder";
49
57
  constructor() {
50
- super("本次 unfinished 回执未含 reason;本接缝缺由至多打回两次。");
58
+ // The ceiling is the configured unfinished-reason limit. This text reaches
59
+ // the model and does not restate a count.
60
+ super("本次 unfinished 回执未含 reason;请补上 reason 后再交。");
51
61
  this.name = "WorkerUnfinishedReasonReminderError";
52
62
  }
53
63
  }