@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.
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/acp-host/production-host.js +6381 -5684
- package/dist/archivist-record-pointer.js +38 -0
- package/dist/collector-tool-schemas.js +18 -0
- package/dist/compliance-transport.js +5 -0
- package/dist/diarist-contracts.js +26 -0
- package/dist/doctor-auditor.js +1 -0
- package/dist/engine-detour-usage.js +6 -6
- package/dist/filed-submission.js +28 -0
- package/dist/gatekeeper-role.js +6 -10
- package/dist/gleaner-left-contracts.js +16 -0
- package/dist/headless-host/production-host.js +1644 -947
- package/dist/judge-role.js +21 -16
- package/dist/migrate-book-topology.js +452 -81
- package/dist/navigator-attendance.js +10 -9
- package/dist/navigator-invocation-identity.js +18 -0
- package/dist/package-contracts/reviewer-output.js +16 -0
- package/dist/package-contracts/worker-output.js +18 -2
- package/dist/pi/role-turn-host.js +9 -0
- package/dist/public-cli/cli.js +1 -0
- package/dist/public-cli/instruction-seat-run.js +258 -55
- package/dist/public-cli/main.js +1209 -346
- package/dist/public-cli/post-admission.js +427 -244
- package/dist/public-cli/settlement.js +111 -48
- package/dist/public-cli/terminal.js +12 -1
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/receipt-delivery-policy.js +152 -17
- package/dist/role-submission-declarations.js +131 -0
- package/dist/secretariat-contracts.js +23 -1
- package/dist/submission-errors.js +11 -1
- package/dist/submission-gate.js +32 -12
- package/package.json +1 -1
- package/src/archivist-record-pointer.ts +48 -0
- package/src/auditor-role.ts +2 -8
- package/src/collector-role.ts +5 -28
- package/src/compliance-transport.ts +10 -2
- package/src/countersign-role.ts +2 -7
- package/src/diarist-contracts.ts +37 -0
- package/src/diarist-role.ts +4 -44
- package/src/doctor-auditor.ts +3 -0
- package/src/doctor-role.ts +16 -11
- package/src/engine-detour-usage.ts +7 -6
- package/src/external-host-turn-loop.ts +24 -12
- package/src/filed-submission.ts +44 -0
- package/src/gatekeeper-role.ts +8 -16
- package/src/gleaner-left-contracts.ts +26 -1
- package/src/gleaner-left-role.ts +4 -32
- package/src/host-contracts.ts +7 -0
- package/src/inspector-role.ts +2 -7
- package/src/judge-role.ts +31 -24
- package/src/merger-role.ts +5 -8
- package/src/navigator-attendance.ts +17 -12
- package/src/navigator-invocation-identity.ts +31 -0
- package/src/navigator-role.ts +2 -8
- package/src/notary-role.ts +5 -20
- package/src/package-contracts/reviewer-output.ts +25 -0
- package/src/package-contracts/worker-output.ts +43 -5
- package/src/pi/adapter.ts +33 -8
- package/src/pi/role-turn-host.ts +7 -0
- package/src/public-cli/cli.ts +1 -0
- package/src/public-cli/instruction-seat-run.ts +292 -61
- package/src/public-cli/post-admission.ts +556 -324
- package/src/public-cli/settlement.ts +112 -50
- package/src/public-cli/terminal.ts +16 -4
- package/src/public-cli/turn-request.ts +10 -1
- package/src/receipt-delivery-policy.ts +209 -17
- package/src/reviewer-role.ts +7 -35
- package/src/role-envelope.ts +5 -1
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +72 -56
- package/src/role-submission-declarations.ts +143 -0
- package/src/secretariat-contracts.ts +35 -0
- package/src/secretariat-role.ts +4 -40
- package/src/submission-correctable-error.ts +5 -2
- package/src/submission-errors.ts +12 -1
- package/src/submission-gate.ts +41 -12
- package/src/worker-role.ts +81 -86
- 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,
|
|
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
|
-
|
|
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
|
|
145
|
-
rejectedReceipts
|
|
146
|
-
deliveryTurns:
|
|
231
|
+
terminalToolCalled,
|
|
232
|
+
rejectedReceipts,
|
|
233
|
+
deliveryTurns: Math.max(issuedDeliveryRequests, recordedTurns),
|
|
147
234
|
runPointer: admitted.runDirectory,
|
|
148
|
-
attemptPointer:
|
|
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
|
|
2290
|
-
if (
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
|
92
|
+
deliveryTurns += 1;
|
|
93
|
+
spend();
|
|
59
94
|
},
|
|
60
95
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
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 =
|
|
111
|
+
deliveryTurns = Math.max(deliveryTurns, facts.deliveryTurns);
|
|
112
|
+
closed = true;
|
|
69
113
|
},
|
|
70
114
|
nextAction() {
|
|
71
115
|
if (accepted) return "accepted";
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|