@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
package/src/submission-gate.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* converged → continue remaining review and settle
|
|
9
9
|
* continue → raw officer receipt; caller resumes the submitted seat for revision
|
|
10
10
|
* escalate → return the officer run so the caller can resume that officer directly
|
|
11
|
-
* unrecognized status → resume officer with plain-language re-ask
|
|
11
|
+
* unrecognized status → resume officer with plain-language re-ask, counted
|
|
12
|
+
* against the configured ceiling; exhaustion keeps that receipt
|
|
12
13
|
* transport / no_receipt → present honestly
|
|
13
14
|
* Four pairs: countersign↔notary, judge↔auditor, worker↔inspector, secretariat↔countersign (#969).
|
|
14
15
|
* Code does not judge content, map next-step for parent, or label unreadable/unusable.
|
|
@@ -28,21 +29,21 @@ import {
|
|
|
28
29
|
} from "./gatekeeper-role.ts";
|
|
29
30
|
import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
30
31
|
import type { TerminalResult } from "./public-cli/terminal.ts";
|
|
32
|
+
import { receivedDiscriminator } from "./submission-errors.ts";
|
|
33
|
+
import type { ReviewQueueWord } from "./review-submission.ts";
|
|
34
|
+
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.ts";
|
|
31
35
|
|
|
32
|
-
/**
|
|
33
|
-
export function latestQueueStatus(terminal: TerminalResult | undefined):
|
|
36
|
+
/** Received discriminator of the latest payload, retained verbatim for re-ask. */
|
|
37
|
+
export function latestQueueStatus(terminal: TerminalResult | undefined): unknown {
|
|
34
38
|
const outcome = terminal?.roleOutcome;
|
|
35
39
|
if (outcome === undefined) return undefined;
|
|
36
40
|
if (outcome.kind === "audit_escalation") return "escalate";
|
|
37
41
|
if (outcome.kind !== "accepted") return undefined;
|
|
38
42
|
const latest = outcome.payloads?.[outcome.payloads.length - 1];
|
|
39
|
-
if (isRecord(latest)) {
|
|
40
|
-
|
|
41
|
-
if (typeof status === "string" && status.trim() !== "") return status;
|
|
43
|
+
if (isRecord(latest) && Object.hasOwn(latest, "status")) {
|
|
44
|
+
return receivedDiscriminator(latest, "status");
|
|
42
45
|
}
|
|
43
|
-
return
|
|
44
|
-
? outcome.status
|
|
45
|
-
: undefined;
|
|
46
|
+
return outcome.status;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export function latestQueuePayload(terminal: TerminalResult | undefined): unknown {
|
|
@@ -98,6 +99,7 @@ function bookDirectOfficerPointer(
|
|
|
98
99
|
officer: GateOfficer,
|
|
99
100
|
result: GatekeeperResult,
|
|
100
101
|
summoned: PublicSummonResult,
|
|
102
|
+
toolCallId: string,
|
|
101
103
|
): void {
|
|
102
104
|
if (
|
|
103
105
|
result.status !== "converged"
|
|
@@ -122,6 +124,7 @@ function bookDirectOfficerPointer(
|
|
|
122
124
|
...(typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== ""
|
|
123
125
|
? { runDirectory: summoned.runDirectory }
|
|
124
126
|
: {}),
|
|
127
|
+
...(toolCallId.trim() === "" ? {} : { submissionToolCallId: toolCallId }),
|
|
125
128
|
});
|
|
126
129
|
}
|
|
127
130
|
|
|
@@ -131,16 +134,20 @@ function bookDirectOfficerPointer(
|
|
|
131
134
|
* public terminal without a second authority.
|
|
132
135
|
*/
|
|
133
136
|
export type SubmissionGateOutcome = {
|
|
134
|
-
readonly status:
|
|
137
|
+
readonly status: ReviewQueueWord | "needs_reask";
|
|
135
138
|
readonly officer: GateOfficer;
|
|
136
139
|
readonly receipt: unknown;
|
|
137
140
|
readonly runId?: string;
|
|
138
141
|
readonly runDirectory?: string;
|
|
142
|
+
/** Officer terminal already returned by the summon. Present when the reply was not a queue word. */
|
|
143
|
+
readonly terminal?: TerminalResult;
|
|
139
144
|
};
|
|
140
145
|
|
|
141
146
|
/**
|
|
142
147
|
* Shared envelope: project gate, book officer pointer, map onto host actions.
|
|
143
|
-
*
|
|
148
|
+
* A real converged / continue / escalate returns immediately. An unreadable
|
|
149
|
+
* conclusion reasks that officer against the configured ceiling; exhaustion
|
|
150
|
+
* returns the receipt actually received.
|
|
144
151
|
* On converged, returns the officer snapshot (receipt + nested runId) for seat projection.
|
|
145
152
|
*/
|
|
146
153
|
export async function requireSubmissionGate(options: {
|
|
@@ -156,9 +163,12 @@ export async function requireSubmissionGate(options: {
|
|
|
156
163
|
readonly submission?: unknown;
|
|
157
164
|
/** Lowest seam: same as runGatekeeper options.summonOfficer — offline tracers only. */
|
|
158
165
|
readonly summonOfficer?: GateOfficerSummon;
|
|
166
|
+
/** This loop's ceiling. Absent uses the package default. */
|
|
167
|
+
readonly autoResumeLimit?: number;
|
|
159
168
|
}): Promise<SubmissionGateOutcome | void> {
|
|
160
169
|
let reask: string | undefined;
|
|
161
|
-
|
|
170
|
+
let reasksSpent = 0;
|
|
171
|
+
const reaskLimit = deliveryLimitFromConfig(options.autoResumeLimit);
|
|
162
172
|
const summonOfficer =
|
|
163
173
|
options.summonOfficer ??
|
|
164
174
|
createDefaultGateOfficerSummon({
|
|
@@ -182,6 +192,7 @@ export async function requireSubmissionGate(options: {
|
|
|
182
192
|
projected.officer,
|
|
183
193
|
gatekeeper,
|
|
184
194
|
projected.summoned,
|
|
195
|
+
options.toolCallId,
|
|
185
196
|
);
|
|
186
197
|
} catch (error) {
|
|
187
198
|
options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
|
|
@@ -208,6 +219,24 @@ export async function requireSubmissionGate(options: {
|
|
|
208
219
|
};
|
|
209
220
|
}
|
|
210
221
|
if (gatekeeper.status === "needs_reask") {
|
|
222
|
+
if (reasksSpent >= reaskLimit) {
|
|
223
|
+
return {
|
|
224
|
+
status: "needs_reask",
|
|
225
|
+
officer: projected.officer,
|
|
226
|
+
receipt: gatekeeper.receipt,
|
|
227
|
+
...(typeof gatekeeper.runId === "string" && gatekeeper.runId.trim() !== ""
|
|
228
|
+
? { runId: gatekeeper.runId }
|
|
229
|
+
: {}),
|
|
230
|
+
...(typeof projected.summoned?.runDirectory === "string"
|
|
231
|
+
&& projected.summoned.runDirectory.trim() !== ""
|
|
232
|
+
? { runDirectory: projected.summoned.runDirectory }
|
|
233
|
+
: {}),
|
|
234
|
+
...(projected.summoned?.terminal === undefined
|
|
235
|
+
? {}
|
|
236
|
+
: { terminal: projected.summoned.terminal }),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
reasksSpent += 1;
|
|
211
240
|
reask = officerConclusionReask(gatekeeper.receivedStatus);
|
|
212
241
|
continue;
|
|
213
242
|
}
|
package/src/worker-role.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import type { RoleHost, HostContext,
|
|
2
|
-
import { Type, type Static } from "typebox";
|
|
3
|
-
import { openToolObject } from "./open-tool-schema.ts";
|
|
4
|
-
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
1
|
+
import type { RoleHost, HostContext, HostGatekeeperActions } from "./host-contracts.ts";
|
|
5
2
|
|
|
6
3
|
import {
|
|
7
|
-
CODER_OUTPUT_TOOL_NAME,
|
|
8
|
-
FIXER_OUTPUT_TOOL_NAME,
|
|
9
4
|
validateAcceptedWorkerDetails,
|
|
10
5
|
type CoderOutput,
|
|
11
6
|
type FixerOutput,
|
|
12
7
|
type WorkerOutput,
|
|
13
8
|
type WorkerRoleLabel,
|
|
14
9
|
} from "./package-contracts/worker-output.ts";
|
|
15
|
-
import {
|
|
10
|
+
import { validateFixerOutput, type FixerPhase } from "./package-contracts/fixer-output.ts";
|
|
11
|
+
import { registerFiledSubmissionTool } from "./filed-submission.ts";
|
|
12
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
16
13
|
import {
|
|
17
14
|
FixerPacketValidationError,
|
|
18
15
|
parseFixerPrerequisites,
|
|
@@ -23,7 +20,9 @@ import {
|
|
|
23
20
|
WorkerCommitReminderError,
|
|
24
21
|
WorkerPrefixReminderError,
|
|
25
22
|
WorkerUnfinishedReasonReminderError,
|
|
23
|
+
type WorkerSubmissionGate,
|
|
26
24
|
} from "./worker-submission-gates.ts";
|
|
25
|
+
import { deliveryLimitFromEnv } from "./receipt-delivery-policy.ts";
|
|
27
26
|
import {
|
|
28
27
|
fixerBashSeatbeltDenyReason,
|
|
29
28
|
matchFixerBashForbiddenLiteral,
|
|
@@ -34,28 +33,10 @@ import { isRecord } from "./unknown-value.ts";
|
|
|
34
33
|
export {
|
|
35
34
|
CODER_OUTPUT_TOOL_NAME,
|
|
36
35
|
FIXER_OUTPUT_TOOL_NAME,
|
|
36
|
+
coderOutputSchema,
|
|
37
37
|
validateAcceptedWorkerDetails,
|
|
38
|
-
};
|
|
39
|
-
export type { WorkerOutput };
|
|
40
|
-
|
|
41
|
-
// status 的合法词写在 description。WORKER_DONE_STATUSES 只让 completed /
|
|
42
|
-
// partially_completed 进入提交闸。unfinished 且未见理由说明时,运行时同 run 催全
|
|
43
|
-
// (ADR 0050);理由在不在不按 JSON 类型判,也不在派发前用长度拒收。
|
|
44
|
-
// report / remainingScope / reason 的声明只留字段名和语义。
|
|
45
|
-
const CODER_STATUS_DESCRIPTION =
|
|
46
|
-
`planned | completed | refused | partially_completed | unfinished。unfinished:缺前置或违宪约束致本局未完成。${PARTIALLY_COMPLETED_DEFINITION}` as const;
|
|
47
|
-
const coderOutputObject = Type.Object({
|
|
48
|
-
status: Type.Unknown({ description: CODER_STATUS_DESCRIPTION }),
|
|
49
|
-
report: Type.Unknown({ description: "如实结果报告" }),
|
|
50
|
-
remainingScope: Type.Unknown({ description: "本局后剩余工作" }),
|
|
51
|
-
reason: Type.Unknown({
|
|
52
|
-
description: "阻断原因:缺前置或违宪约束。缺待决 owner 决定或答复属缺前置。",
|
|
53
|
-
}),
|
|
54
|
-
});
|
|
55
|
-
export const coderOutputSchema = withTerminatingOutputDeclarations(
|
|
56
|
-
openToolObject(coderOutputObject),
|
|
57
|
-
);
|
|
58
|
-
export type { FixerOutput, CoderOutput };
|
|
38
|
+
} from "./package-contracts/worker-output.ts";
|
|
39
|
+
export type { WorkerOutput, FixerOutput, CoderOutput };
|
|
59
40
|
export const FIXER_FLAG_DEFINITIONS = {
|
|
60
41
|
packet: {
|
|
61
42
|
name: "ak-fix-packet",
|
|
@@ -103,7 +84,7 @@ export type CoderRoleDependencies = {
|
|
|
103
84
|
export type WorkerRoleRuntime = {
|
|
104
85
|
activate(ctx?: HostContext): Promise<void>;
|
|
105
86
|
/** Arm gate ① baseline after envelope places the worktree (coder/fixer). Durable parent required (#857). */
|
|
106
|
-
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }): void;
|
|
87
|
+
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }, invocationScopeId?: string): void;
|
|
107
88
|
};
|
|
108
89
|
|
|
109
90
|
/** Read only the field that selects the worker's next package-owned path. */
|
|
@@ -128,9 +109,48 @@ export function validateWorkerOutput(
|
|
|
128
109
|
return validateAcceptedWorkerDetails(output, "Coder") as CoderOutput;
|
|
129
110
|
}
|
|
130
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Coder and fixer share one submission sequence: record via the shared tool,
|
|
114
|
+
* then read status and bounce through the same reminder gate.
|
|
115
|
+
* Seat differences stay in the ready check and the projection.
|
|
116
|
+
*/
|
|
117
|
+
function registerWorkerSubmission(
|
|
118
|
+
pi: RoleHost,
|
|
119
|
+
role: "coder" | "fixer",
|
|
120
|
+
options: {
|
|
121
|
+
readonly notReady: string;
|
|
122
|
+
readonly ready: () => boolean;
|
|
123
|
+
readonly phase: () => WorkerPhase | undefined;
|
|
124
|
+
readonly project: (parameters: unknown, phase: WorkerPhase) => WorkerOutput;
|
|
125
|
+
readonly submissionGate: WorkerSubmissionGate;
|
|
126
|
+
readonly hostActions: WorkerRoleHostActions;
|
|
127
|
+
},
|
|
128
|
+
): void {
|
|
129
|
+
registerFiledSubmissionTool(pi, roleSubmissionDeclaration(role), {
|
|
130
|
+
readyError: () => (options.ready() ? undefined : options.notReady),
|
|
131
|
+
beforeAccept: async ({ toolCallId, parameters, ctx }) => {
|
|
132
|
+
const phase = options.phase();
|
|
133
|
+
if (phase === undefined) throw new Error(options.notReady);
|
|
134
|
+
const output = options.project(parameters, phase);
|
|
135
|
+
const status = workerStatusOf(output);
|
|
136
|
+
if (status !== undefined) {
|
|
137
|
+
assertAcceptableThroughHost(
|
|
138
|
+
options.submissionGate,
|
|
139
|
+
status,
|
|
140
|
+
output,
|
|
141
|
+
options.hostActions,
|
|
142
|
+
ctx,
|
|
143
|
+
toolCallId,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return output;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
131
151
|
/** Reminder bounces stay typed rejects; IO/infrastructure keep identity via host failInfrastructure. */
|
|
132
152
|
function assertAcceptableThroughHost(
|
|
133
|
-
submissionGate:
|
|
153
|
+
submissionGate: WorkerSubmissionGate,
|
|
134
154
|
status: string,
|
|
135
155
|
details: unknown,
|
|
136
156
|
hostActions: WorkerRoleHostActions,
|
|
@@ -155,6 +175,7 @@ export function createFixerRoleRuntime(
|
|
|
155
175
|
pi: RoleHost,
|
|
156
176
|
dependencies: FixerRoleDependencies,
|
|
157
177
|
hostActions: WorkerRoleHostActions,
|
|
178
|
+
options?: { readonly unfinishedReasonBounceLimit?: number },
|
|
158
179
|
): WorkerRoleRuntime {
|
|
159
180
|
let soul: string | undefined;
|
|
160
181
|
let packet: FixerInvocationInput | undefined;
|
|
@@ -162,7 +183,12 @@ export function createFixerRoleRuntime(
|
|
|
162
183
|
let prerequisitesPath: string | undefined;
|
|
163
184
|
let phase: WorkerPhase | undefined;
|
|
164
185
|
let lifecycleRegistered = false;
|
|
165
|
-
|
|
186
|
+
// #1132: ADR 0050 缺理由催全次数. The in-process seam passes the value it
|
|
187
|
+
// already resolved; the Pi child has only the env that seam projected.
|
|
188
|
+
const submissionGate = createWorkerSubmissionGate({
|
|
189
|
+
unfinishedReasonBounceLimit: options?.unfinishedReasonBounceLimit
|
|
190
|
+
?? deliveryLimitFromEnv(process.env),
|
|
191
|
+
});
|
|
166
192
|
|
|
167
193
|
pi.registerFlag(
|
|
168
194
|
FIXER_FLAG_DEFINITIONS.packet.name,
|
|
@@ -212,30 +238,13 @@ export function createFixerRoleRuntime(
|
|
|
212
238
|
|
|
213
239
|
if (!lifecycleRegistered) {
|
|
214
240
|
lifecycleRegistered = true;
|
|
215
|
-
pi
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (packet === undefined || phase === undefined) {
|
|
223
|
-
throw new Error("修内司修理包与阶段未装载");
|
|
224
|
-
}
|
|
225
|
-
const output = deepFreeze(validateFixerOutput(parameters, phase));
|
|
226
|
-
const status = workerStatusOf(output);
|
|
227
|
-
if (status !== undefined) {
|
|
228
|
-
assertAcceptableThroughHost(
|
|
229
|
-
submissionGate,
|
|
230
|
-
status,
|
|
231
|
-
output,
|
|
232
|
-
hostActions,
|
|
233
|
-
ctx,
|
|
234
|
-
toolCallId,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
return { content: [], details: output, terminate: true as const };
|
|
238
|
-
},
|
|
241
|
+
registerWorkerSubmission(pi, "fixer", {
|
|
242
|
+
notReady: "修内司修理包与阶段未装载",
|
|
243
|
+
ready: () => packet !== undefined && phase !== undefined,
|
|
244
|
+
phase: () => phase,
|
|
245
|
+
project: (parameters, current) => deepFreeze(validateFixerOutput(parameters, current)),
|
|
246
|
+
submissionGate,
|
|
247
|
+
hostActions,
|
|
239
248
|
});
|
|
240
249
|
pi.on("tool_call", (event) => {
|
|
241
250
|
if (event.toolName !== "bash") return;
|
|
@@ -263,8 +272,8 @@ export function createFixerRoleRuntime(
|
|
|
263
272
|
});
|
|
264
273
|
}
|
|
265
274
|
},
|
|
266
|
-
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }) {
|
|
267
|
-
submissionGate.arm(cwd, parent);
|
|
275
|
+
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }, invocationScopeId?: string) {
|
|
276
|
+
submissionGate.arm(cwd, parent, invocationScopeId);
|
|
268
277
|
},
|
|
269
278
|
};
|
|
270
279
|
}
|
|
@@ -273,12 +282,17 @@ export function createCoderRoleRuntime(
|
|
|
273
282
|
pi: RoleHost,
|
|
274
283
|
dependencies: CoderRoleDependencies,
|
|
275
284
|
hostActions: WorkerRoleHostActions,
|
|
285
|
+
options?: { readonly unfinishedReasonBounceLimit?: number },
|
|
276
286
|
): WorkerRoleRuntime {
|
|
277
287
|
let soul: string | undefined;
|
|
278
288
|
let task: string | undefined;
|
|
279
289
|
let phase: WorkerPhase | undefined;
|
|
280
290
|
let lifecycleRegistered = false;
|
|
281
|
-
|
|
291
|
+
// #1132: same resolved ceiling as the fixer gate. Absent = Pi child env.
|
|
292
|
+
const submissionGate = createWorkerSubmissionGate({
|
|
293
|
+
unfinishedReasonBounceLimit: options?.unfinishedReasonBounceLimit
|
|
294
|
+
?? deliveryLimitFromEnv(process.env),
|
|
295
|
+
});
|
|
282
296
|
|
|
283
297
|
pi.registerFlag("ak-coder-task", {
|
|
284
298
|
description: "Markdown task assigned to the coder role",
|
|
@@ -310,32 +324,13 @@ export function createCoderRoleRuntime(
|
|
|
310
324
|
|
|
311
325
|
if (!lifecycleRegistered) {
|
|
312
326
|
lifecycleRegistered = true;
|
|
313
|
-
pi
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (task === undefined || phase === undefined) {
|
|
321
|
-
throw new Error("将作监任务与阶段未装载");
|
|
322
|
-
}
|
|
323
|
-
const output = validateWorkerOutput(parameters, phase, "Coder");
|
|
324
|
-
const status = workerStatusOf(output);
|
|
325
|
-
// #836: skill-expansion evidence rejection deleted (陛下「2.4/5 删」).
|
|
326
|
-
// Skill still ships with the package (ADR 0052); code no longer refuses on it.
|
|
327
|
-
if (status !== undefined) {
|
|
328
|
-
assertAcceptableThroughHost(
|
|
329
|
-
submissionGate,
|
|
330
|
-
status,
|
|
331
|
-
output,
|
|
332
|
-
hostActions,
|
|
333
|
-
ctx,
|
|
334
|
-
toolCallId,
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
return { content: [], details: output, terminate: true as const };
|
|
338
|
-
},
|
|
327
|
+
registerWorkerSubmission(pi, "coder", {
|
|
328
|
+
notReady: "将作监任务与阶段未装载",
|
|
329
|
+
ready: () => task !== undefined && phase !== undefined,
|
|
330
|
+
phase: () => phase,
|
|
331
|
+
project: (parameters, current) => validateWorkerOutput(parameters, current, "Coder"),
|
|
332
|
+
submissionGate,
|
|
333
|
+
hostActions,
|
|
339
334
|
});
|
|
340
335
|
pi.on("before_agent_start", (event, ctx) => {
|
|
341
336
|
if (soul === undefined) throw new Error("将作监职分未装载");
|
|
@@ -346,8 +341,8 @@ export function createCoderRoleRuntime(
|
|
|
346
341
|
});
|
|
347
342
|
}
|
|
348
343
|
},
|
|
349
|
-
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }) {
|
|
350
|
-
submissionGate.arm(cwd, parent);
|
|
344
|
+
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }, invocationScopeId?: string) {
|
|
345
|
+
submissionGate.arm(cwd, parent, invocationScopeId);
|
|
351
346
|
},
|
|
352
347
|
};
|
|
353
348
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
WorkerUnfinishedReasonReminderError,
|
|
17
17
|
} from "./submission-errors.ts";
|
|
18
18
|
import { WORKER_DONE_STATUSES } from "./worker-submission-contracts.ts";
|
|
19
|
+
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.ts";
|
|
19
20
|
|
|
20
21
|
import { isRecord } from "./unknown-value.ts";
|
|
21
22
|
|
|
@@ -26,6 +27,7 @@ export const WORKER_SUBMISSION_GATE_RECORD_KIND = WORKER_SUBMISSION_GATE_KIND;
|
|
|
26
27
|
export const WORKER_COMMIT_BASELINE_ENTRY_TYPE = "commit-baseline";
|
|
27
28
|
export const WORKER_COMMIT_REMINDER_BOUNCE_ENTRY_TYPE = "commit-reminder-bounce";
|
|
28
29
|
export const WORKER_PREFIX_REMINDER_BOUNCE_ENTRY_TYPE = "prefix-reminder-bounce";
|
|
30
|
+
export const WORKER_UNFINISHED_REASON_BOUNCE_ENTRY_TYPE = "unfinished-reason-bounce";
|
|
29
31
|
|
|
30
32
|
/** Historical package hook ownership marker — uninstall criterion only. */
|
|
31
33
|
const HOOK_MARKER = "ak-roles: worker-submission-gates reference-transaction";
|
|
@@ -33,7 +35,6 @@ const HOOKS_DIR = "ak-roles-hooks";
|
|
|
33
35
|
const HOOK_FILE = "reference-transaction";
|
|
34
36
|
/** Open platform-prefix domain (constitution #10) — not a closed singleton. */
|
|
35
37
|
const PLATFORM_PREFIX = /^[A-Za-z][A-Za-z0-9_-]*:/;
|
|
36
|
-
const UNFINISHED_REASON_BOUNCE_LIMIT = 2;
|
|
37
38
|
|
|
38
39
|
export type WorkerSubmissionGateParent = RecordSessionParent;
|
|
39
40
|
|
|
@@ -171,14 +172,16 @@ function unfinishedReasonPresent(details?: unknown): boolean {
|
|
|
171
172
|
return containsWrittenReason(details.reason);
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
function readGateState(session: HostRecordSession): {
|
|
175
|
+
function readGateState(session: HostRecordSession, invocationScopeId?: string): {
|
|
175
176
|
baseline: string | null | undefined;
|
|
176
177
|
reminded: boolean;
|
|
177
178
|
prefixReminded: boolean;
|
|
179
|
+
unfinishedReasonBounces: number;
|
|
178
180
|
} {
|
|
179
181
|
let baseline: string | null | undefined;
|
|
180
182
|
let reminded = false;
|
|
181
183
|
let prefixReminded = false;
|
|
184
|
+
let unfinishedReasonBounces = 0;
|
|
182
185
|
for (const entry of session.getEntries()) {
|
|
183
186
|
if (entry.type !== "custom") continue;
|
|
184
187
|
if (entry.customType === WORKER_COMMIT_BASELINE_ENTRY_TYPE) {
|
|
@@ -190,9 +193,13 @@ function readGateState(session: HostRecordSession): {
|
|
|
190
193
|
reminded = true;
|
|
191
194
|
} else if (entry.customType === WORKER_PREFIX_REMINDER_BOUNCE_ENTRY_TYPE) {
|
|
192
195
|
prefixReminded = true;
|
|
196
|
+
} else if (entry.customType === WORKER_UNFINISHED_REASON_BOUNCE_ENTRY_TYPE
|
|
197
|
+
&& invocationScopeId !== undefined && isRecord(entry.data)
|
|
198
|
+
&& entry.data.invocationScopeId === invocationScopeId) {
|
|
199
|
+
unfinishedReasonBounces += 1;
|
|
193
200
|
}
|
|
194
201
|
}
|
|
195
|
-
return { baseline, reminded, prefixReminded };
|
|
202
|
+
return { baseline, reminded, prefixReminded, unfinishedReasonBounces };
|
|
196
203
|
}
|
|
197
204
|
|
|
198
205
|
function isAncestor(cwd: string, ancestor: string, descendant: string): boolean {
|
|
@@ -234,13 +241,20 @@ export type CreateWorkerSubmissionGateOptions = {
|
|
|
234
241
|
* Path still derives from required parent session; home only pins ledger root. #604 Scope 2.
|
|
235
242
|
*/
|
|
236
243
|
readonly home?: string;
|
|
244
|
+
/**
|
|
245
|
+
* #1132: ADR 0050 缺理由催全次数 read from the single configured
|
|
246
|
+
* `autoResumeLimit` value. Absent = package default. Resolved once here; the
|
|
247
|
+
* gate never re-reads it per submission. Exhaustion still accepts (照收) —
|
|
248
|
+
* only the count changes.
|
|
249
|
+
*/
|
|
250
|
+
readonly unfinishedReasonBounceLimit?: number;
|
|
237
251
|
};
|
|
238
252
|
|
|
239
253
|
export function createWorkerSubmissionGate(
|
|
240
254
|
options: CreateWorkerSubmissionGateOptions = {},
|
|
241
255
|
): {
|
|
242
256
|
/** Durable parent is required — ownership must be known at arm (#857 loud failure). */
|
|
243
|
-
arm(cwd: string, parent: WorkerSubmissionGateParent): void;
|
|
257
|
+
arm(cwd: string, parent: WorkerSubmissionGateParent, invocationScopeId?: string): void;
|
|
244
258
|
assertAcceptable(status: string, details?: unknown): void;
|
|
245
259
|
} {
|
|
246
260
|
let baseline: string | null | undefined;
|
|
@@ -248,11 +262,14 @@ export function createWorkerSubmissionGate(
|
|
|
248
262
|
let reminded = false;
|
|
249
263
|
let prefixReminded = false;
|
|
250
264
|
let unfinishedReasonBounces = 0;
|
|
265
|
+
let invocationScopeId: string | undefined;
|
|
251
266
|
let record: HostRecordSession | undefined;
|
|
252
267
|
/** Parent session file retained so every gate sitian write path-derives the same ledger home. */
|
|
253
268
|
let sessionParent: string | undefined;
|
|
254
269
|
const explicitHome =
|
|
255
270
|
typeof options.home === "string" && options.home.length > 0 ? options.home : undefined;
|
|
271
|
+
// #1132: one configured number, resolved once at gate construction.
|
|
272
|
+
const unfinishedReasonBounceLimit = deliveryLimitFromConfig(options.unfinishedReasonBounceLimit);
|
|
256
273
|
const head = (cwd: string): string | null => {
|
|
257
274
|
try {
|
|
258
275
|
return git(cwd, ["rev-parse", "HEAD"]);
|
|
@@ -273,7 +290,8 @@ export function createWorkerSubmissionGate(
|
|
|
273
290
|
});
|
|
274
291
|
};
|
|
275
292
|
return {
|
|
276
|
-
arm(cwd, parent) {
|
|
293
|
+
arm(cwd, parent, scope) {
|
|
294
|
+
invocationScopeId = scope;
|
|
277
295
|
uninstallPackageWorkerHooks(cwd);
|
|
278
296
|
root = cwd;
|
|
279
297
|
sessionParent = parent.getSessionFile();
|
|
@@ -282,7 +300,8 @@ export function createWorkerSubmissionGate(
|
|
|
282
300
|
kind: WORKER_SUBMISSION_GATE_RECORD_KIND,
|
|
283
301
|
parent,
|
|
284
302
|
});
|
|
285
|
-
const prior = readGateState(record);
|
|
303
|
+
const prior = readGateState(record, invocationScopeId);
|
|
304
|
+
unfinishedReasonBounces = prior.unfinishedReasonBounces;
|
|
286
305
|
if (prior.baseline !== undefined) {
|
|
287
306
|
baseline = prior.baseline;
|
|
288
307
|
reminded = prior.reminded;
|
|
@@ -304,7 +323,10 @@ export function createWorkerSubmissionGate(
|
|
|
304
323
|
},
|
|
305
324
|
assertAcceptable(status, details) {
|
|
306
325
|
if (status === "unfinished" && !unfinishedReasonPresent(details)) {
|
|
307
|
-
if (unfinishedReasonBounces <
|
|
326
|
+
if (unfinishedReasonBounces < unfinishedReasonBounceLimit) {
|
|
327
|
+
record?.appendCustomEntry(WORKER_UNFINISHED_REASON_BOUNCE_ENTRY_TYPE, {
|
|
328
|
+
version: 1, invocationScopeId,
|
|
329
|
+
});
|
|
308
330
|
unfinishedReasonBounces += 1;
|
|
309
331
|
throw new WorkerUnfinishedReasonReminderError();
|
|
310
332
|
}
|
|
@@ -345,3 +367,6 @@ export function createWorkerSubmissionGate(
|
|
|
345
367
|
},
|
|
346
368
|
};
|
|
347
369
|
}
|
|
370
|
+
|
|
371
|
+
/** The gate object returned above. Callers reference this instead of restating assertAcceptable. */
|
|
372
|
+
export type WorkerSubmissionGate = ReturnType<typeof createWorkerSubmissionGate>;
|