@akagilnc/pi-workflow-roles 0.1.5382 → 0.1.5462
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 +4 -2
- package/README.zh-CN.md +4 -2
- package/dist/acp-host/production-host.js +8843 -9012
- package/dist/archivist-record-pointer.js +38 -0
- package/dist/compliance-transport.js +7 -5
- package/dist/doctor-auditor.js +1 -0
- package/dist/engine-detour-usage.js +9 -36
- package/dist/gatekeeper-role.js +4 -18
- package/dist/headless-host/description.js +0 -209
- package/dist/headless-host/production-host.js +7828 -8247
- package/dist/host-contracts.js +8 -0
- package/dist/judge-role.js +14 -1
- package/dist/migrate-book-topology.js +215 -96
- package/dist/navigator-attendance.js +10 -9
- package/dist/navigator-invocation-identity.js +18 -0
- package/dist/packaged-role-registry.js +1 -9
- package/dist/pi/role-turn-host.js +46 -19
- package/dist/public-cli/auto-resume.js +29 -66
- package/dist/public-cli/cli.js +1 -0
- package/dist/public-cli/countersign-run.js +4 -18
- package/dist/public-cli/instruction-seat-run.js +258 -69
- package/dist/public-cli/main.js +7488 -7758
- package/dist/public-cli/post-admission.js +451 -349
- package/dist/public-cli/process-cancel.js +8 -0
- package/dist/public-cli/run-lifecycle.js +24 -94
- package/dist/public-cli/settlement.js +624 -1471
- package/dist/public-cli/terminal.js +34 -53
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/public-role-summons.js +3 -8
- package/dist/receipt-delivery-policy.js +162 -28
- package/dist/run-terminal-artifacts.js +19 -14
- package/dist/serialize-thrown-value.js +7 -1
- package/dist/submission-errors.js +11 -1
- package/dist/submission-gate.js +27 -4
- package/dist/submission-ledger.js +21 -89
- package/package.json +1 -1
- package/scripts/build-package.mjs +0 -2
- package/src/acp-host/role-turn-host.ts +168 -75
- package/src/archivist-record-pointer.ts +48 -0
- package/src/compliance-transport.ts +13 -8
- package/src/doctor-auditor.ts +3 -0
- package/src/engine-detour-usage.ts +10 -39
- package/src/external-host-turn-loop.ts +100 -43
- package/src/gatekeeper-role.ts +7 -22
- package/src/headless-host/description.ts +4 -214
- package/src/headless-host/production-host.ts +2 -1
- package/src/headless-host/role-turn-host.ts +153 -186
- package/src/host-contracts.ts +20 -10
- package/src/judge-role.ts +24 -5
- package/src/navigator-attendance.ts +17 -12
- package/src/navigator-invocation-identity.ts +31 -0
- package/src/navigator-public-session.ts +1 -1
- package/src/navigator-session-contracts.ts +1 -1
- package/src/packaged-role-registry.ts +2 -10
- package/src/pi/adapter.ts +33 -11
- package/src/pi/role-turn-host.ts +49 -28
- package/src/prepared-role-turn.ts +2 -0
- package/src/public-cli/auto-resume.ts +27 -66
- package/src/public-cli/cli.ts +1 -0
- package/src/public-cli/countersign-run.ts +4 -22
- package/src/public-cli/instruction-seat-run.ts +307 -74
- package/src/public-cli/post-admission.ts +615 -459
- package/src/public-cli/process-cancel.ts +9 -0
- package/src/public-cli/run-lifecycle.ts +25 -126
- package/src/public-cli/settlement.ts +801 -1859
- package/src/public-cli/terminal.ts +42 -70
- package/src/public-cli/turn-request.ts +10 -1
- package/src/public-role-summons.ts +3 -8
- package/src/receipt-delivery-policy.ts +219 -28
- package/src/role-envelope.ts +21 -10
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +79 -102
- package/src/run-terminal-artifacts.ts +21 -17
- package/src/serialize-thrown-value.ts +7 -1
- package/src/submission-correctable-error.ts +5 -2
- package/src/submission-errors.ts +12 -1
- package/src/submission-gate.ts +34 -4
- package/src/submission-ledger.ts +30 -116
- package/src/worker-role.ts +19 -7
- package/src/worker-submission-gates.ts +29 -7
- package/dist/audit-escalation.js +0 -7
- package/dist/pi/known-failure.js +0 -51
- package/dist/typed-provider-http.js +0 -57
- package/dist/upstream-error-testimony.js +0 -19
- package/src/pi/known-failure.ts +0 -76
- package/src/typed-provider-http.ts +0 -89
- package/src/upstream-error-testimony.ts +0 -45
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serializeThrownValue } from "../serialize-thrown-value.js";
|
|
1
2
|
/** Encode one free-text Terminal cell. JSON string form cannot embed raw tab/newline. */
|
|
2
3
|
export function encodeTerminalField(value) {
|
|
3
4
|
return JSON.stringify(value);
|
|
@@ -10,36 +11,15 @@ export function exitCodeForTerminalOutcome(outcome) {
|
|
|
10
11
|
return isLawfulTypedTerminalOutcome(outcome) ? 0 : 1;
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* #416: autoResumeCount (0..2) is a call-local observation of how many in-place
|
|
17
|
-
* auto-resumes occurred during this single LLM call; it is not persisted to
|
|
18
|
-
* run-state.json and does not participate in limit decisions.
|
|
14
|
+
* Parent-facing current reply: a court's own accepted/audit payloads only.
|
|
15
|
+
* Run-scoped history stays on TerminalResult.submissions (#879 / #836), and a
|
|
16
|
+
* failure face never answers as this court's reply (#953).
|
|
19
17
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export function roleResultPayloads(outcome) {
|
|
26
|
-
if (outcome.kind === "accepted" || outcome.kind === "audit_escalation")
|
|
27
|
-
return outcome.payloads ?? [];
|
|
28
|
-
if (outcome.kind === "failure")
|
|
29
|
-
return outcome.payloads ?? [];
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Prefer non-empty current-result payloads; an empty array must not shadow
|
|
34
|
-
* top-level submissions (#953 / #836). Callers that need failure history must
|
|
35
|
-
* read TerminalResult.submissions directly — do not coalesce into failure.payloads.
|
|
36
|
-
*/
|
|
37
|
-
export function coalesceSubmissionRows(payloads, submissions) {
|
|
38
|
-
if (payloads !== undefined && payloads.length > 0)
|
|
39
|
-
return payloads;
|
|
40
|
-
if (submissions !== undefined && submissions.length > 0)
|
|
41
|
-
return submissions;
|
|
42
|
-
return payloads ?? submissions ?? [];
|
|
18
|
+
export function currentReplyRows(terminal) {
|
|
19
|
+
const outcome = terminal?.roleOutcome;
|
|
20
|
+
return outcome?.kind === "accepted" || outcome?.kind === "audit_escalation"
|
|
21
|
+
? outcome.payloads ?? []
|
|
22
|
+
: [];
|
|
43
23
|
}
|
|
44
24
|
/**
|
|
45
25
|
* Build an advice navigator fact (#959). Prose is presented as submitted;
|
|
@@ -69,6 +49,17 @@ export function formatTerminalResult(result) {
|
|
|
69
49
|
if (result.roleOutcome.kind === "failure") {
|
|
70
50
|
lines.push(`diagnostic\t${encodeTerminalField(result.roleOutcome.diagnostic)}`);
|
|
71
51
|
}
|
|
52
|
+
// Accepted volumes do not dump decisiveFacts unless a failed attempt is
|
|
53
|
+
// recorded. The unsettled-direction fact still has to be visible on its own.
|
|
54
|
+
// audit_escalation already dumps every decisiveFact below.
|
|
55
|
+
if (result.roleOutcome.kind === "accepted"
|
|
56
|
+
&& result.roleOutcome.decisiveFacts?.directionUnsettled === true) {
|
|
57
|
+
lines.push("fact\tdirectionUnsettled\ttrue");
|
|
58
|
+
const subsequent = result.roleOutcome.decisiveFacts.subsequentAudit;
|
|
59
|
+
if (typeof subsequent === "string") {
|
|
60
|
+
lines.push(`fact\tsubsequentAudit\t${encodeTerminalField(subsequent)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
72
63
|
if (result.roleOutcome.kind === "failure" || result.roleOutcome.kind === "no_receipt" || result.roleOutcome.kind === "audit_escalation" || result.roleOutcome.decisiveFacts?.failedAttempts !== undefined) {
|
|
73
64
|
const facts = result.roleOutcome.kind === "accepted"
|
|
74
65
|
? { failedAttempts: result.roleOutcome.decisiveFacts?.failedAttempts }
|
|
@@ -76,7 +67,7 @@ export function formatTerminalResult(result) {
|
|
|
76
67
|
for (const [key, value] of Object.entries(facts)) {
|
|
77
68
|
if (value === undefined)
|
|
78
69
|
continue;
|
|
79
|
-
const rendered = typeof value === "string" ? value :
|
|
70
|
+
const rendered = typeof value === "string" ? value : serializeThrownValue(value);
|
|
80
71
|
lines.push(`fact\t${encodeTerminalField(key)}\t${encodeTerminalField(rendered)}`);
|
|
81
72
|
}
|
|
82
73
|
}
|
|
@@ -115,11 +106,7 @@ export function formatTerminalResult(result) {
|
|
|
115
106
|
}
|
|
116
107
|
}
|
|
117
108
|
}
|
|
118
|
-
if (result.
|
|
119
|
-
// Resumable failure: run ID is revealed only inside the complete resume command.
|
|
120
|
-
lines.push(`resume\t${encodeTerminalField(result.resume.command)}`);
|
|
121
|
-
}
|
|
122
|
-
else if (result.runId !== undefined) {
|
|
109
|
+
if (result.runId !== undefined) {
|
|
123
110
|
lines.push(`run\t${encodeTerminalField(result.runId)}`);
|
|
124
111
|
}
|
|
125
112
|
if (result.autoResumeCount !== undefined) {
|
|
@@ -127,25 +114,19 @@ export function formatTerminalResult(result) {
|
|
|
127
114
|
}
|
|
128
115
|
// Role-result block: original payloads, newest first for humans (#961).
|
|
129
116
|
// Typed payloads/submissions stay ledger order; only this presentation reverses.
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const payloads = result.roleOutcome.kind === "accepted" ||
|
|
142
|
-
result.roleOutcome.kind === "audit_escalation"
|
|
143
|
-
? coalesceSubmissionRows(result.roleOutcome.payloads, result.submissions)
|
|
144
|
-
: result.submissions ?? [];
|
|
145
|
-
for (let i = payloads.length - 1; i >= 0; i -= 1) {
|
|
146
|
-
const payload = payloads[i];
|
|
117
|
+
// Present the current result and the run history on distinct faces (#879 / #836).
|
|
118
|
+
// Neither a prior court's submission nor a failed turn's history is this reply.
|
|
119
|
+
const current = result.roleOutcome.kind === "failure"
|
|
120
|
+
? result.roleOutcome.payloads ?? []
|
|
121
|
+
: currentReplyRows(result);
|
|
122
|
+
for (const [label, rows] of [
|
|
123
|
+
["submission", current],
|
|
124
|
+
["recorded-submission", result.submissions ?? []],
|
|
125
|
+
]) {
|
|
126
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
127
|
+
const payload = rows[i];
|
|
147
128
|
const rendered = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
148
|
-
lines.push(
|
|
129
|
+
lines.push(`${label}\t${encodeTerminalField(rendered)}`);
|
|
149
130
|
}
|
|
150
131
|
}
|
|
151
132
|
return `${lines.join("\n")}\n`;
|
|
@@ -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
|
/**
|
|
@@ -4,6 +4,7 @@ import { realpath } from "node:fs/promises";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { isRecord, errorText } from "./unknown-value.js";
|
|
7
|
+
import { serializeThrownValue } from "./serialize-thrown-value.js";
|
|
7
8
|
const AK_ROLE_PACKAGE_ROOT_ENV = "AK_ROLE_PACKAGE_ROOT";
|
|
8
9
|
const execFileAsync = promisify(execFile);
|
|
9
10
|
function createCapturingIo() {
|
|
@@ -288,15 +289,9 @@ async function resolveReviewerGitToplevel(projectRoot) {
|
|
|
288
289
|
)).stdout.trim());
|
|
289
290
|
}
|
|
290
291
|
async function summonParallelReviewerLenses(options) {
|
|
291
|
-
const describeFailure = (error) => {
|
|
292
|
-
if (error instanceof AggregateError) {
|
|
293
|
-
return [error.message, ...error.errors.map(describeFailure)].join("\n");
|
|
294
|
-
}
|
|
295
|
-
return errorText(error);
|
|
296
|
-
};
|
|
297
292
|
const failedResult = (error) => ({
|
|
298
293
|
exitCode: 1,
|
|
299
|
-
stderr:
|
|
294
|
+
stderr: serializeThrownValue(error)
|
|
300
295
|
});
|
|
301
296
|
const dualFailure = (error) => {
|
|
302
297
|
const failure = failedResult(error);
|
|
@@ -399,7 +394,7 @@ async function summonParallelReviewerLenses(options) {
|
|
|
399
394
|
}
|
|
400
395
|
} catch (error) {
|
|
401
396
|
sealDiagnostic = `Reviewer target final seal failed:
|
|
402
|
-
${
|
|
397
|
+
${serializeThrownValue(error)}`;
|
|
403
398
|
}
|
|
404
399
|
if (sealDiagnostic !== void 0) {
|
|
405
400
|
const withSealFailure = (result) => ({
|
|
@@ -1,46 +1,71 @@
|
|
|
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
|
-
return {
|
|
33
|
+
return noReceiptLifecycleFacts({
|
|
9
34
|
terminalToolCalled: input.terminalToolCalled,
|
|
10
|
-
rejectedReceipts: input.rejectedReceipts
|
|
11
|
-
|
|
12
|
-
diagnosticAvailable: item.reason.trim() !== ""
|
|
13
|
-
})),
|
|
14
|
-
deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
|
|
15
|
-
sessionCompletion: "settled-without-accepted-receipt",
|
|
35
|
+
rejectedReceipts: input.rejectedReceipts,
|
|
36
|
+
deliveryTurns: deliveryTurnsFact(input.deliveryTurns),
|
|
16
37
|
runPointer: input.runPointer,
|
|
17
|
-
attemptPointer: input.attemptPointer
|
|
18
|
-
|
|
19
|
-
};
|
|
38
|
+
attemptPointer: input.attemptPointer
|
|
39
|
+
});
|
|
20
40
|
}
|
|
21
41
|
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
42
|
return {
|
|
26
43
|
terminalToolCalled: input.terminalToolCalled,
|
|
27
44
|
rejectedReceipts: input.rejectedReceipts.map(({ reason }) => ({
|
|
28
45
|
reason,
|
|
29
46
|
diagnosticAvailable: reason.trim() !== ""
|
|
30
47
|
})),
|
|
31
|
-
deliveryTurns:
|
|
48
|
+
deliveryTurns: deliveryTurnsFact(input.deliveryTurns),
|
|
32
49
|
sessionCompletion: "settled-without-accepted-receipt",
|
|
33
50
|
runPointer: input.runPointer,
|
|
34
51
|
attemptPointer: input.attemptPointer,
|
|
35
52
|
acceptedReceipt: false
|
|
36
53
|
};
|
|
37
54
|
}
|
|
38
|
-
function createReceiptDeliveryPolicy() {
|
|
55
|
+
function createReceiptDeliveryPolicy(limit) {
|
|
56
|
+
const deliveryLimit = deliveryLimitFromConfig(limit);
|
|
39
57
|
let accepted = false;
|
|
58
|
+
let closed = false;
|
|
40
59
|
let terminalToolCalled = false;
|
|
41
60
|
let deliveryTurns = 0;
|
|
61
|
+
let budgetSpent = 0;
|
|
42
62
|
const rejectedReceipts = [];
|
|
63
|
+
const spend = () => {
|
|
64
|
+
if (budgetSpent < deliveryLimit) budgetSpent += 1;
|
|
65
|
+
};
|
|
43
66
|
return {
|
|
67
|
+
/** The effective ceiling this budget spends (#1132 — never re-read). */
|
|
68
|
+
limit: deliveryLimit,
|
|
44
69
|
recordAccepted() {
|
|
45
70
|
accepted = true;
|
|
46
71
|
terminalToolCalled = true;
|
|
@@ -49,27 +74,50 @@ function createReceiptDeliveryPolicy() {
|
|
|
49
74
|
stopForInfrastructure() {
|
|
50
75
|
accepted = true;
|
|
51
76
|
},
|
|
77
|
+
/**
|
|
78
|
+
* Record the rejection this turn already produced.
|
|
79
|
+
* The fact does not spend a budget. The seam that sends the next prompt
|
|
80
|
+
* owns that count.
|
|
81
|
+
*/
|
|
52
82
|
recordRejected(reason) {
|
|
53
83
|
terminalToolCalled = true;
|
|
54
84
|
rejectedReceipts.push({ reason, diagnosticAvailable: reason.trim() !== "" });
|
|
55
|
-
deliveryTurns = Math.min(RECEIPT_DELIVERY_TURN_LIMIT, deliveryTurns + 1);
|
|
56
85
|
},
|
|
57
86
|
recordDeliveryRequest() {
|
|
58
|
-
deliveryTurns
|
|
87
|
+
deliveryTurns += 1;
|
|
88
|
+
spend();
|
|
59
89
|
},
|
|
60
90
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
91
|
+
* Stop asking. The issued count stays whatever was actually sent (#1132 —
|
|
92
|
+
* do not fill the remainder up to the ceiling).
|
|
93
|
+
*/
|
|
94
|
+
closeBudget() {
|
|
95
|
+
closed = true;
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* A nested session settled without an accepted receipt. Close so this layer
|
|
99
|
+
* does not open another prompt on that session. The issued count stays the
|
|
100
|
+
* larger of the two seams: a nested zero must not wipe prompts this layer
|
|
101
|
+
* already sent, and adding the two counts would bill one send twice.
|
|
64
102
|
*/
|
|
65
103
|
recordNestedNoReceipt(facts) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
104
|
+
if (typeof facts.terminalToolCalled === "boolean") {
|
|
105
|
+
terminalToolCalled = terminalToolCalled || facts.terminalToolCalled;
|
|
106
|
+
}
|
|
107
|
+
if (facts.rejectedReceipts !== void 0) {
|
|
108
|
+
rejectedReceipts.push(...facts.rejectedReceipts);
|
|
109
|
+
}
|
|
110
|
+
if (facts.deliveryTurns !== void 0) deliveryTurns = Math.max(deliveryTurns, facts.deliveryTurns);
|
|
111
|
+
closed = true;
|
|
69
112
|
},
|
|
70
113
|
nextAction() {
|
|
71
114
|
if (accepted) return "accepted";
|
|
72
|
-
|
|
115
|
+
if (closed) return "no-receipt";
|
|
116
|
+
return budgetSpent < deliveryLimit ? "request-delivery" : "no-receipt";
|
|
117
|
+
},
|
|
118
|
+
/** Delivery requests actually issued so far (#1132). */
|
|
119
|
+
issuedDeliveryRequests() {
|
|
120
|
+
return deliveryTurns;
|
|
73
121
|
},
|
|
74
122
|
/** Current typed delivery facts. No prose. */
|
|
75
123
|
deliveryState() {
|
|
@@ -82,13 +130,99 @@ function createReceiptDeliveryPolicy() {
|
|
|
82
130
|
},
|
|
83
131
|
facts(binding) {
|
|
84
132
|
return noReceiptLifecycleFacts({ terminalToolCalled, rejectedReceipts: [...rejectedReceipts], deliveryTurns, ...binding });
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* Continue a public call after the in-memory policy was rebuilt.
|
|
136
|
+
* Sends already issued spend the same budget; copied rejections keep
|
|
137
|
+
* the fact and do not spend again.
|
|
138
|
+
*/
|
|
139
|
+
continueIssued(prior) {
|
|
140
|
+
for (const receipt of prior.rejectedReceipts) {
|
|
141
|
+
this.recordRejected(receipt.reason);
|
|
142
|
+
}
|
|
143
|
+
if (prior.terminalToolCalled === true) terminalToolCalled = true;
|
|
144
|
+
for (let index = 0; index < prior.deliveryTurns; index += 1) {
|
|
145
|
+
this.recordDeliveryRequest();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function entryRecord(entry) {
|
|
151
|
+
return isRecord(entry) ? entry : void 0;
|
|
152
|
+
}
|
|
153
|
+
function customTypeOf(record, message) {
|
|
154
|
+
return record.customType ?? message?.customType;
|
|
155
|
+
}
|
|
156
|
+
function dataOf(record, message) {
|
|
157
|
+
return record.data ?? message?.details;
|
|
158
|
+
}
|
|
159
|
+
function scopeTag(record) {
|
|
160
|
+
const message = isRecord(record.message) ? record.message : void 0;
|
|
161
|
+
const customType = customTypeOf(record, message);
|
|
162
|
+
const data = dataOf(record, message);
|
|
163
|
+
if ((customType === RECEIPT_DELIVERY_REQUEST_ENTRY || customType === RECEIPT_REJECTION_ENTRY) && isRecord(data) && typeof data.invocationScopeId === "string" && data.invocationScopeId.trim() !== "") {
|
|
164
|
+
return data.invocationScopeId;
|
|
165
|
+
}
|
|
166
|
+
if (customType !== NO_RECEIPT_LIFECYCLE_ENTRY_TYPE) return void 0;
|
|
167
|
+
try {
|
|
168
|
+
const facts = parseNoReceiptLifecycleFacts(data);
|
|
169
|
+
const prefix = "invocation:";
|
|
170
|
+
if (!facts.attemptPointer.startsWith(prefix)) return void 0;
|
|
171
|
+
const scope = facts.attemptPointer.slice(prefix.length);
|
|
172
|
+
return scope.trim() === "" ? void 0 : scope;
|
|
173
|
+
} catch {
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function priorReceiptContinuation(entries, invocationScopeId) {
|
|
178
|
+
const pointer = receiptAttemptPointer("", invocationScopeId);
|
|
179
|
+
let entryTurns = 0;
|
|
180
|
+
let factTurns = 0;
|
|
181
|
+
let rejectedReceipts = [];
|
|
182
|
+
let snapshotTerminal = false;
|
|
183
|
+
for (const entry of entries) {
|
|
184
|
+
const record = entryRecord(entry);
|
|
185
|
+
if (record === void 0) continue;
|
|
186
|
+
const tagged = scopeTag(record);
|
|
187
|
+
if (tagged !== void 0) {
|
|
188
|
+
const message = isRecord(record.message) ? record.message : void 0;
|
|
189
|
+
const customType = customTypeOf(record, message);
|
|
190
|
+
if (tagged !== invocationScopeId) continue;
|
|
191
|
+
if (customType === RECEIPT_DELIVERY_REQUEST_ENTRY) entryTurns += 1;
|
|
192
|
+
if (customType === RECEIPT_REJECTION_ENTRY) {
|
|
193
|
+
const data = dataOf(record, message);
|
|
194
|
+
if (isRecord(data) && typeof data.reason === "string") rejectedReceipts.push({ reason: data.reason });
|
|
195
|
+
}
|
|
196
|
+
if (customType !== NO_RECEIPT_LIFECYCLE_ENTRY_TYPE) continue;
|
|
197
|
+
try {
|
|
198
|
+
const facts = parseNoReceiptLifecycleFacts(dataOf(record, message));
|
|
199
|
+
if (facts.attemptPointer !== pointer) continue;
|
|
200
|
+
factTurns = Math.max(factTurns, facts.deliveryTurns);
|
|
201
|
+
if (facts.rejectedReceipts.length >= rejectedReceipts.length) {
|
|
202
|
+
rejectedReceipts = facts.rejectedReceipts.map(({ reason }) => ({ reason }));
|
|
203
|
+
}
|
|
204
|
+
snapshotTerminal ||= facts.terminalToolCalled;
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
85
208
|
}
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
deliveryTurns: Math.max(entryTurns, factTurns),
|
|
212
|
+
rejectedReceipts,
|
|
213
|
+
terminalToolCalled: snapshotTerminal || rejectedReceipts.length > 0
|
|
86
214
|
};
|
|
87
215
|
}
|
|
88
216
|
export {
|
|
89
217
|
NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
|
|
90
|
-
|
|
218
|
+
RECEIPT_DELIVERY_LIMIT_ENV,
|
|
219
|
+
RECEIPT_DELIVERY_REQUEST_ENTRY,
|
|
220
|
+
RECEIPT_REJECTION_ENTRY,
|
|
91
221
|
createReceiptDeliveryPolicy,
|
|
222
|
+
deliveryLimitFromConfig,
|
|
223
|
+
deliveryLimitFromEnv,
|
|
92
224
|
noReceiptLifecycleFacts,
|
|
93
|
-
parseNoReceiptLifecycleFacts
|
|
225
|
+
parseNoReceiptLifecycleFacts,
|
|
226
|
+
priorReceiptContinuation,
|
|
227
|
+
receiptAttemptPointer
|
|
94
228
|
};
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical reader for run-directory typed terminal artifacts.
|
|
3
|
-
*
|
|
4
|
-
* audit-incomplete.json under artifacts/, plus
|
|
5
|
-
*
|
|
2
|
+
* Canonical layout and reader for run-directory typed terminal artifacts.
|
|
3
|
+
* Settlement publish*Artifacts uses these names (report.json / error.json /
|
|
4
|
+
* audit-incomplete.json under artifacts/, plus durable failure fallbacks).
|
|
5
|
+
* The reader only checks presence and structural
|
|
6
6
|
* readability — it does not re-derive role outcomes or invent a second
|
|
7
7
|
* candidate algorithm.
|
|
8
8
|
*/
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
9
10
|
import { readdir, readFile } from "node:fs/promises";
|
|
10
11
|
import { basename, dirname, join } from "node:path";
|
|
11
12
|
import { roleRunArtifactsDirectory } from "./role-run-placement.js";
|
|
12
13
|
import { isRecord, errorText, isMissingPathError } from "./unknown-value.js";
|
|
14
|
+
export const RUN_TERMINAL_REPORT_FILE = "report.json";
|
|
15
|
+
export const RUN_TERMINAL_ERROR_FILE = "error.json";
|
|
16
|
+
export const RUN_TERMINAL_EVIDENCE_FILE = "evidence.json";
|
|
17
|
+
export const RUN_TERMINAL_ERROR_SETTLEMENT_FILE = "error.settlement.json";
|
|
13
18
|
export const RUN_TERMINAL_ARTIFACT_FILES = [
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
RUN_TERMINAL_REPORT_FILE,
|
|
20
|
+
RUN_TERMINAL_ERROR_FILE,
|
|
16
21
|
"audit-incomplete.json",
|
|
17
22
|
];
|
|
18
23
|
/**
|
|
@@ -22,11 +27,15 @@ export const RUN_TERMINAL_ARTIFACT_FILES = [
|
|
|
22
27
|
* Relative to the run directory.
|
|
23
28
|
*/
|
|
24
29
|
export const RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS = [
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
`artifacts/${RUN_TERMINAL_ERROR_SETTLEMENT_FILE}`,
|
|
31
|
+
RUN_TERMINAL_ERROR_SETTLEMENT_FILE,
|
|
27
32
|
];
|
|
28
|
-
/**
|
|
29
|
-
const
|
|
33
|
+
/** Publisher and reader share the open-ended error face stem. */
|
|
34
|
+
export const UNIQUE_ERROR_FALLBACK_STEM = "error";
|
|
35
|
+
export function uniqueErrorFallbackName() {
|
|
36
|
+
return `${UNIQUE_ERROR_FALLBACK_STEM}.${randomUUID()}.json`;
|
|
37
|
+
}
|
|
38
|
+
const UNIQUE_ERROR_FALLBACK_NAME = new RegExp(`^${UNIQUE_ERROR_FALLBACK_STEM}\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.json$`, "i");
|
|
30
39
|
/**
|
|
31
40
|
* Minimum producer-owned face shared by settlement terminal artifacts
|
|
32
41
|
* (report / error / audit-incomplete). Consumer-driven: enough to identify a
|
|
@@ -219,7 +228,3 @@ export async function readRunTerminalArtifact(runDirectory) {
|
|
|
219
228
|
}
|
|
220
229
|
return { status: "absent" };
|
|
221
230
|
}
|
|
222
|
-
/** Test/helper: basename face of a unique fallback path, if any. */
|
|
223
|
-
export function isUniqueErrorFallbackName(name) {
|
|
224
|
-
return UNIQUE_ERROR_FALLBACK_NAME.test(basename(name));
|
|
225
|
-
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { inspect } from "node:util";
|
|
2
2
|
/** Record the whole thrown value without invoking its getters or custom formatter. */
|
|
3
3
|
export function serializeThrownValue(value) {
|
|
4
|
-
return inspect(value, {
|
|
4
|
+
return inspect(value, {
|
|
5
|
+
depth: null,
|
|
6
|
+
maxArrayLength: null,
|
|
7
|
+
maxStringLength: null,
|
|
8
|
+
showHidden: true,
|
|
9
|
+
customInspect: false,
|
|
10
|
+
});
|
|
5
11
|
}
|
|
@@ -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
|
}
|
package/dist/submission-gate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { bookDirectOfficerRunPointer } from "./archivist-record-pointer.js";
|
|
2
2
|
import { GatekeeperDecisionError, officerConclusionReask, projectGatekeeperRun, } from "./gatekeeper-role.js";
|
|
3
3
|
import { receivedDiscriminator } from "./submission-errors.js";
|
|
4
|
+
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.js";
|
|
4
5
|
/** Received discriminator of the latest payload, retained verbatim for re-ask. */
|
|
5
6
|
export function latestQueueStatus(terminal) {
|
|
6
7
|
const outcome = terminal?.roleOutcome;
|
|
@@ -54,7 +55,7 @@ export function createDefaultGateOfficerSummon(options) {
|
|
|
54
55
|
* Book a typed pointer under parent session/auditor-roles (archivist-owned write).
|
|
55
56
|
* Offline mocks without a real session leave no nested volume (lawful zero).
|
|
56
57
|
*/
|
|
57
|
-
function bookDirectOfficerPointer(context, officer, result, summoned) {
|
|
58
|
+
function bookDirectOfficerPointer(context, officer, result, summoned, toolCallId) {
|
|
58
59
|
if (result.status !== "converged"
|
|
59
60
|
&& result.status !== "continue"
|
|
60
61
|
&& result.status !== "escalate"
|
|
@@ -77,16 +78,20 @@ function bookDirectOfficerPointer(context, officer, result, summoned) {
|
|
|
77
78
|
...(typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== ""
|
|
78
79
|
? { runDirectory: summoned.runDirectory }
|
|
79
80
|
: {}),
|
|
81
|
+
...(toolCallId.trim() === "" ? {} : { submissionToolCallId: toolCallId }),
|
|
80
82
|
});
|
|
81
83
|
}
|
|
82
84
|
/**
|
|
83
85
|
* Shared envelope: project gate, book officer pointer, map onto host actions.
|
|
84
|
-
*
|
|
86
|
+
* A real converged / continue / escalate returns immediately. An unreadable
|
|
87
|
+
* conclusion reasks that officer against the configured ceiling; exhaustion
|
|
88
|
+
* returns the receipt actually received.
|
|
85
89
|
* On converged, returns the officer snapshot (receipt + nested runId) for seat projection.
|
|
86
90
|
*/
|
|
87
91
|
export async function requireSubmissionGate(options) {
|
|
88
92
|
let reask;
|
|
89
|
-
|
|
93
|
+
let reasksSpent = 0;
|
|
94
|
+
const reaskLimit = deliveryLimitFromConfig(options.autoResumeLimit);
|
|
90
95
|
const summonOfficer = options.summonOfficer ??
|
|
91
96
|
createDefaultGateOfficerSummon({
|
|
92
97
|
cwd: options.context.cwd ?? process.cwd(),
|
|
@@ -104,7 +109,7 @@ export async function requireSubmissionGate(options) {
|
|
|
104
109
|
// Envelope-owned pointer book. Failure is host infrastructure — single face.
|
|
105
110
|
if (projected.summoned !== undefined) {
|
|
106
111
|
try {
|
|
107
|
-
bookDirectOfficerPointer(options.context, projected.officer, gatekeeper, projected.summoned);
|
|
112
|
+
bookDirectOfficerPointer(options.context, projected.officer, gatekeeper, projected.summoned, options.toolCallId);
|
|
108
113
|
}
|
|
109
114
|
catch (error) {
|
|
110
115
|
options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
|
|
@@ -131,6 +136,24 @@ export async function requireSubmissionGate(options) {
|
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
138
|
if (gatekeeper.status === "needs_reask") {
|
|
139
|
+
if (reasksSpent >= reaskLimit) {
|
|
140
|
+
return {
|
|
141
|
+
status: "needs_reask",
|
|
142
|
+
officer: projected.officer,
|
|
143
|
+
receipt: gatekeeper.receipt,
|
|
144
|
+
...(typeof gatekeeper.runId === "string" && gatekeeper.runId.trim() !== ""
|
|
145
|
+
? { runId: gatekeeper.runId }
|
|
146
|
+
: {}),
|
|
147
|
+
...(typeof projected.summoned?.runDirectory === "string"
|
|
148
|
+
&& projected.summoned.runDirectory.trim() !== ""
|
|
149
|
+
? { runDirectory: projected.summoned.runDirectory }
|
|
150
|
+
: {}),
|
|
151
|
+
...(projected.summoned?.terminal === undefined
|
|
152
|
+
? {}
|
|
153
|
+
: { terminal: projected.summoned.terminal }),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
reasksSpent += 1;
|
|
134
157
|
reask = officerConclusionReask(gatekeeper.receivedStatus);
|
|
135
158
|
continue;
|
|
136
159
|
}
|