@akagilnc/pi-workflow-roles 0.1.2091 → 0.1.2101
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 +8 -1
- package/README.zh-CN.md +8 -1
- package/dist/engine-detour-tool.js +57 -4
- package/dist/engine-detour.js +53 -4
- package/dist/engine-labor-fallback.js +121 -0
- package/dist/evidence-child-executor.js +3 -15
- package/dist/package-owned-tool-idle.js +64 -7
- package/dist/public-cli/main.js +56 -22
- package/package.json +1 -1
- package/resources/engines/cursor.md +7 -0
- package/resources/engines/opus.md +12 -5
- package/src/engine-detour-tool.ts +83 -13
- package/src/engine-detour.ts +55 -4
- package/src/engine-labor-fallback.ts +172 -0
- package/src/evidence-child-executor.ts +3 -15
- package/src/judge-role.ts +11 -3
- package/src/package-owned-tool-idle.ts +73 -7
- package/src/public-cli/cli.ts +6 -4
- package/src/public-cli/config.ts +15 -28
- package/src/public-cli/settlement.ts +9 -1
- package/src/reviewer-role.ts +13 -5
- package/src/role-runtime.ts +22 -2
package/src/judge-role.ts
CHANGED
|
@@ -8,6 +8,10 @@ import { Type, type Static } from "typebox";
|
|
|
8
8
|
|
|
9
9
|
import { disposeComplianceDecision } from "./audit-escalation.ts";
|
|
10
10
|
import type { ComplianceDecision } from "./compliance-transport.ts";
|
|
11
|
+
import {
|
|
12
|
+
readActivationEngineLaborFallbackField,
|
|
13
|
+
withEngineLaborFallbackField,
|
|
14
|
+
} from "./engine-labor-fallback.ts";
|
|
11
15
|
|
|
12
16
|
import {
|
|
13
17
|
JUDGE_OUTPUT_TOOL_NAME,
|
|
@@ -126,18 +130,21 @@ export function createJudgeRoleRuntime(
|
|
|
126
130
|
} catch (error) {
|
|
127
131
|
hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
128
132
|
}
|
|
133
|
+
// #380: attach sole-built engineLaborFallback when detour fell back to seat labor.
|
|
134
|
+
const laborFallback = readActivationEngineLaborFallbackField();
|
|
135
|
+
const acceptedDetails = withEngineLaborFallbackField(verdict, laborFallback);
|
|
129
136
|
return disposeComplianceDecision<AgentToolResult<unknown>>(
|
|
130
137
|
audit,
|
|
131
138
|
{
|
|
132
139
|
pass: (usage) => ({
|
|
133
140
|
content: [{ type: "text" as const, text: "Judge verdict accepted" }],
|
|
134
|
-
details:
|
|
141
|
+
details: acceptedDetails,
|
|
135
142
|
terminate: true as const,
|
|
136
143
|
...(usage === undefined ? {} : { usage }),
|
|
137
144
|
}),
|
|
138
145
|
noReceipt: (auditNoReceipt, usageProjection) => ({
|
|
139
146
|
content: [{ type: "text" as const, text: "Judge verdict accepted; compliance audit produced no receipt" }],
|
|
140
|
-
details: { ...
|
|
147
|
+
details: { ...acceptedDetails, auditNoReceipt },
|
|
141
148
|
terminate: true as const,
|
|
142
149
|
...usageProjection,
|
|
143
150
|
}),
|
|
@@ -149,7 +156,8 @@ export function createJudgeRoleRuntime(
|
|
|
149
156
|
escalate: (result) => result,
|
|
150
157
|
auditIncomplete: (result) => result,
|
|
151
158
|
},
|
|
152
|
-
|
|
159
|
+
// #380: escalate deliveredOutput must carry the same mechanical projection.
|
|
160
|
+
acceptedDetails,
|
|
153
161
|
);
|
|
154
162
|
},
|
|
155
163
|
});
|
|
@@ -4,11 +4,16 @@
|
|
|
4
4
|
* Fixed 183000ms silence clock on package-owned tool execute only.
|
|
5
5
|
* Real producing onUpdate resets; final resolve/reject clears; timeout throws so
|
|
6
6
|
* Pi settles the current call as an LLM-visible isError tool result. No retry,
|
|
7
|
-
* role failure, process termination,
|
|
7
|
+
* role failure, process termination, config, or Pi built-in coverage.
|
|
8
8
|
*
|
|
9
9
|
* #339: do not name-exempt whole terminating tools. Outer idle stays armed for
|
|
10
10
|
* pre/post-audit work. Only the real compliance-audit await suspends this single
|
|
11
11
|
* layer (ADR 0059 owns that interval); resume re-arms the same outer backstop.
|
|
12
|
+
*
|
|
13
|
+
* #380: on idle, abort a derived tool signal (not the caller signal) so detour
|
|
14
|
+
* spawn cleanup and softFail can run through the existing AbortSignal path.
|
|
15
|
+
* Cooperative soft-settle may win; non-listeners still hard-reject after a
|
|
16
|
+
* microtask drain (no second timer).
|
|
12
17
|
*/
|
|
13
18
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
19
|
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
@@ -28,6 +33,8 @@ const WRAPPED = Symbol.for("ak.packageOwnedToolIdleWrapped");
|
|
|
28
33
|
type PackageOwnedToolIdleSuspension = {
|
|
29
34
|
suspend(): void;
|
|
30
35
|
resume(): void;
|
|
36
|
+
/** Reset the silence window (byte/activity signal from the active execute). */
|
|
37
|
+
poke(): void;
|
|
31
38
|
};
|
|
32
39
|
|
|
33
40
|
/**
|
|
@@ -46,6 +53,20 @@ export class PackageOwnedToolIdleTimeoutError extends Error {
|
|
|
46
53
|
}
|
|
47
54
|
}
|
|
48
55
|
|
|
56
|
+
export function isPackageOwnedToolIdleTimeoutError(
|
|
57
|
+
value: unknown,
|
|
58
|
+
): value is PackageOwnedToolIdleTimeoutError {
|
|
59
|
+
return (
|
|
60
|
+
value instanceof PackageOwnedToolIdleTimeoutError ||
|
|
61
|
+
(
|
|
62
|
+
typeof value === "object" &&
|
|
63
|
+
value !== null &&
|
|
64
|
+
(value as { name?: unknown }).name === "PackageOwnedToolIdleTimeoutError" &&
|
|
65
|
+
(value as { code?: unknown }).code === PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
49
70
|
/** Minimal executable tool shape accepted by the shared idle wrapper. */
|
|
50
71
|
export type PackageOwnedToolLike = {
|
|
51
72
|
readonly name: string;
|
|
@@ -87,6 +108,15 @@ export async function withPackageOwnedToolIdleSuspended<T>(
|
|
|
87
108
|
}
|
|
88
109
|
}
|
|
89
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Touch the active package-owned execute idle clock (reuse stream-idle-guard poke).
|
|
113
|
+
* No-op outside a wrapped package-owned execute or while that layer is suspended.
|
|
114
|
+
* Used by long-running tools (engine detour) that see real subprocess output bytes.
|
|
115
|
+
*/
|
|
116
|
+
export function pokePackageOwnedToolIdle(): void {
|
|
117
|
+
packageOwnedToolIdleScope.getStore()?.poke();
|
|
118
|
+
}
|
|
119
|
+
|
|
90
120
|
/**
|
|
91
121
|
* Single shared execute wrapper for package-owned tool definitions.
|
|
92
122
|
* Idempotent: wrapping twice returns the same protected definition.
|
|
@@ -103,7 +133,7 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
103
133
|
const wrappedExecute = function packageOwnedToolIdleExecute(
|
|
104
134
|
...args: unknown[]
|
|
105
135
|
): Promise<unknown> {
|
|
106
|
-
const
|
|
136
|
+
const parentSignal = args[2] as AbortSignal | undefined;
|
|
107
137
|
const onUpdate = args[3] as ((partialResult: unknown) => void) | undefined;
|
|
108
138
|
return new Promise((resolve, reject) => {
|
|
109
139
|
let settled = false;
|
|
@@ -112,15 +142,46 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
112
142
|
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
113
143
|
});
|
|
114
144
|
|
|
145
|
+
// Derived tool signal: caller cancel forwards; idle aborts this only (not parent).
|
|
146
|
+
const toolController = new AbortController();
|
|
147
|
+
const onParentAbort = (): void => {
|
|
148
|
+
if (toolController.signal.aborted) return;
|
|
149
|
+
toolController.abort(parentSignal?.reason);
|
|
150
|
+
};
|
|
151
|
+
if (parentSignal !== undefined) {
|
|
152
|
+
if (parentSignal.aborted) {
|
|
153
|
+
toolController.abort(parentSignal.reason);
|
|
154
|
+
} else {
|
|
155
|
+
parentSignal.addEventListener("abort", onParentAbort);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const cleanup = (): void => {
|
|
160
|
+
idle.signal.removeEventListener("abort", onIdle);
|
|
161
|
+
idle.dispose();
|
|
162
|
+
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
163
|
+
};
|
|
164
|
+
|
|
115
165
|
const settle = (deliver: () => void): void => {
|
|
116
166
|
if (settled) return;
|
|
117
167
|
settled = true;
|
|
118
|
-
|
|
119
|
-
idle.dispose();
|
|
168
|
+
cleanup();
|
|
120
169
|
deliver();
|
|
121
170
|
};
|
|
122
171
|
const onIdle = (): void => {
|
|
123
|
-
|
|
172
|
+
const idleError = new PackageOwnedToolIdleTimeoutError();
|
|
173
|
+
if (!toolController.signal.aborted) {
|
|
174
|
+
toolController.abort(idleError);
|
|
175
|
+
}
|
|
176
|
+
// Cooperative tools (detour) soft-settle via the aborted signal in the
|
|
177
|
+
// next microtasks. Non-listeners still hard-reject after a short drain
|
|
178
|
+
// of those microtasks — no second wall-clock timer.
|
|
179
|
+
void Promise.resolve()
|
|
180
|
+
.then(() => {})
|
|
181
|
+
.then(() => {})
|
|
182
|
+
.then(() => {
|
|
183
|
+
settle(() => reject(idleError));
|
|
184
|
+
});
|
|
124
185
|
};
|
|
125
186
|
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
126
187
|
|
|
@@ -141,6 +202,10 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
141
202
|
});
|
|
142
203
|
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
143
204
|
},
|
|
205
|
+
poke(): void {
|
|
206
|
+
if (settled || suspended) return;
|
|
207
|
+
idle.poke();
|
|
208
|
+
},
|
|
144
209
|
};
|
|
145
210
|
|
|
146
211
|
const guardedOnUpdate = onUpdate === undefined
|
|
@@ -152,8 +217,9 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
152
217
|
};
|
|
153
218
|
|
|
154
219
|
const callArgs = args.slice();
|
|
155
|
-
//
|
|
156
|
-
|
|
220
|
+
// Pass derived signal so idle/cancel can clean up subprocesses without
|
|
221
|
+
// aborting the caller/parent agent signal identity.
|
|
222
|
+
callArgs[2] = toolController.signal;
|
|
157
223
|
callArgs[3] = guardedOnUpdate;
|
|
158
224
|
|
|
159
225
|
void packageOwnedToolIdleScope.run(suspension, async () => {
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
formatModelSpec,
|
|
14
14
|
loadCredentialProviders,
|
|
15
15
|
loadPublicCliConfig,
|
|
16
|
-
|
|
16
|
+
parseModelSpec,
|
|
17
17
|
resolveEffectiveSeat,
|
|
18
18
|
savePublicCliConfig,
|
|
19
19
|
isEngineAxisSeat,
|
|
@@ -418,7 +418,7 @@ function renderHelp(): string {
|
|
|
418
418
|
lines.push(
|
|
419
419
|
"",
|
|
420
420
|
"Role options: ak-role help <command>",
|
|
421
|
-
"Persistent config: ak-role config set <seat> <provider/model:thinking>",
|
|
421
|
+
"Persistent config: ak-role config set <seat> <provider/model[:thinking]>",
|
|
422
422
|
"Persistent engine (judge|reviewer): ak-role config set-engine <seat> <name> | unset-engine <seat>",
|
|
423
423
|
"Effective seats: ak-role roles",
|
|
424
424
|
);
|
|
@@ -507,7 +507,7 @@ async function runConfigCommand(
|
|
|
507
507
|
if (args[0] === "set") {
|
|
508
508
|
if (args.length < 3) {
|
|
509
509
|
throw new CliUsageError(
|
|
510
|
-
"usage: ak-role config set <seat> <provider/model:thinking>",
|
|
510
|
+
"usage: ak-role config set <seat> <provider/model[:thinking]>",
|
|
511
511
|
);
|
|
512
512
|
}
|
|
513
513
|
// Bulk: repeated seat spec pairs after `set`
|
|
@@ -524,7 +524,9 @@ async function runConfigCommand(
|
|
|
524
524
|
if (!isPublicConfigurableSeat(seat)) {
|
|
525
525
|
throw new CliUsageError(`unknown configurable seat: ${seat}`);
|
|
526
526
|
}
|
|
527
|
-
|
|
527
|
+
// #384: persistent seat config shares the invocation model grammar.
|
|
528
|
+
// Bare provider/model stores as-is; :thinking suffix still required only when colon present.
|
|
529
|
+
config = setPersistentSeatConfig(config, seat, parseModelSpec(spec));
|
|
528
530
|
}
|
|
529
531
|
await savePublicCliConfig(config, home);
|
|
530
532
|
io.stdout(renderConfig(config));
|
package/src/public-cli/config.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type CredentialProviders = {
|
|
|
23
23
|
|
|
24
24
|
export type SeatModelConfig = ModelRef;
|
|
25
25
|
|
|
26
|
-
/** Persistent seat row:
|
|
26
|
+
/** Persistent seat row: provider/model[:thinking] + optional engine axis (#356/#384). */
|
|
27
27
|
export type PersistentSeatConfig = SeatModelConfig & {
|
|
28
28
|
engine?: string;
|
|
29
29
|
};
|
|
@@ -123,7 +123,7 @@ export function isEngineAxisSeat(seat: string): seat is "judge" | "reviewer" {
|
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* Set or clear persistent engine on Judge or Reviewer (#356 / #378).
|
|
126
|
-
* Engine-only seats are rejected — model
|
|
126
|
+
* Engine-only seats are rejected — provider/model[:thinking] remains required first.
|
|
127
127
|
*/
|
|
128
128
|
export function setPersistentSeatEngine(
|
|
129
129
|
config: PublicCliConfig,
|
|
@@ -136,7 +136,7 @@ export function setPersistentSeatEngine(
|
|
|
136
136
|
const previous = config.seats[seat];
|
|
137
137
|
if (previous === undefined) {
|
|
138
138
|
throw new Error(
|
|
139
|
-
`config seat ${seat} has no persistent model; set provider/model:thinking before engine`,
|
|
139
|
+
`config seat ${seat} has no persistent model; set provider/model[:thinking] before engine`,
|
|
140
140
|
);
|
|
141
141
|
}
|
|
142
142
|
if (engine === undefined) {
|
|
@@ -226,30 +226,12 @@ export function parseModelSpec(
|
|
|
226
226
|
}
|
|
227
227
|
const provider = modelPart.slice(0, slash);
|
|
228
228
|
const model = modelPart.slice(slash + 1);
|
|
229
|
-
// #346: bare provider/model is legal
|
|
230
|
-
// Persistent config set still requires thinking via parsePersistentModelSpec.
|
|
229
|
+
// #346/#384: bare provider/model is legal — do not invent thinking.
|
|
231
230
|
return thinking === undefined
|
|
232
231
|
? { provider, model }
|
|
233
232
|
: { provider, model, thinking };
|
|
234
233
|
}
|
|
235
234
|
|
|
236
|
-
/** Persistent seat config keeps the three-part provider/model:thinking grammar. */
|
|
237
|
-
export function parsePersistentModelSpec(spec: string): SeatModelConfig & {
|
|
238
|
-
thinking: PublicThinkingLevel;
|
|
239
|
-
} {
|
|
240
|
-
const parsed = parseModelSpec(spec);
|
|
241
|
-
if (parsed.thinking === undefined) {
|
|
242
|
-
throw new Error(
|
|
243
|
-
`model specification requires a thinking level (provider/model:thinking), got ${spec}`,
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
return {
|
|
247
|
-
provider: parsed.provider,
|
|
248
|
-
model: parsed.model,
|
|
249
|
-
thinking: parsed.thinking,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
235
|
export function formatModelSpec(selection: SeatModelConfig): string {
|
|
254
236
|
const base = `${selection.provider}/${selection.model}`;
|
|
255
237
|
return selection.thinking === undefined ? base : `${base}:${selection.thinking}`;
|
|
@@ -308,16 +290,21 @@ function parseSeatModelConfig(value: unknown, seat: string): PersistentSeatConfi
|
|
|
308
290
|
if (typeof raw.model !== "string" || raw.model.trim() === "") {
|
|
309
291
|
throw new Error(`config seat ${seat} requires model`);
|
|
310
292
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
293
|
+
// #384: thinking is optional on persistent seats; when present it must be typed.
|
|
294
|
+
if (raw.thinking !== undefined) {
|
|
295
|
+
if (
|
|
296
|
+
typeof raw.thinking !== "string" ||
|
|
297
|
+
!THINKING_LEVELS.has(raw.thinking as PublicThinkingLevel)
|
|
298
|
+
) {
|
|
299
|
+
throw new Error(`config seat ${seat} requires a valid thinking level`);
|
|
300
|
+
}
|
|
316
301
|
}
|
|
317
302
|
const parsed: PersistentSeatConfig = {
|
|
318
303
|
provider: raw.provider,
|
|
319
304
|
model: raw.model,
|
|
320
|
-
|
|
305
|
+
...(raw.thinking === undefined
|
|
306
|
+
? {}
|
|
307
|
+
: { thinking: raw.thinking as PublicThinkingLevel }),
|
|
321
308
|
};
|
|
322
309
|
if (raw.engine !== undefined) {
|
|
323
310
|
// Shape only: engine must be a string field. Path-safety syntax is deferred to
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
COLLECTOR_WAIT_TOOL,
|
|
35
35
|
} from "../collector-ledger.ts";
|
|
36
36
|
import { ENGINE_DETOUR_TOOL_NAME } from "../engine-detour.ts";
|
|
37
|
+
import { readEngineLaborFallbackFieldFrom } from "../engine-labor-fallback.ts";
|
|
37
38
|
import {
|
|
38
39
|
JUDGE_OUTPUT_TOOL_NAME,
|
|
39
40
|
type JudgeVerdict,
|
|
@@ -1060,7 +1061,12 @@ function judgeDecisiveFacts(
|
|
|
1060
1061
|
verdict: object,
|
|
1061
1062
|
judgeStatus: JudgeVerdict["judgeStatus"],
|
|
1062
1063
|
): Record<string, unknown> {
|
|
1063
|
-
const facts: Record<string, unknown> = {
|
|
1064
|
+
const facts: Record<string, unknown> = {
|
|
1065
|
+
judgeStatus,
|
|
1066
|
+
...auditNoReceiptDecisiveFact(verdict),
|
|
1067
|
+
// #380: spread sole-built field; do not re-key here (S1).
|
|
1068
|
+
...readEngineLaborFallbackFieldFrom(verdict),
|
|
1069
|
+
};
|
|
1064
1070
|
if (judgeStatus === "continue") {
|
|
1065
1071
|
const fix = safelyRead(verdict, "fix");
|
|
1066
1072
|
if (fix.readable && isRecord(fix.value)) {
|
|
@@ -1260,6 +1266,8 @@ function reviewerDecisiveFacts(
|
|
|
1260
1266
|
reportAxes,
|
|
1261
1267
|
acceptedBatchPresent: acceptedBatch.readable && acceptedBatch.value !== undefined,
|
|
1262
1268
|
...auditNoReceiptDecisiveFact(candidate),
|
|
1269
|
+
// #380: spread sole-built field; do not re-key here (S1).
|
|
1270
|
+
...readEngineLaborFallbackFieldFrom(candidate),
|
|
1263
1271
|
};
|
|
1264
1272
|
if (status.readable && typeof status.value === "string") facts.reviewerStatus = status.value;
|
|
1265
1273
|
if (
|
package/src/reviewer-role.ts
CHANGED
|
@@ -7,6 +7,10 @@ export type { CanonicalSkillBinding };
|
|
|
7
7
|
import { disposeComplianceDecision } from "./audit-escalation.ts";
|
|
8
8
|
import type { ComplianceDecision } from "./compliance-transport.ts";
|
|
9
9
|
import { appendActiveSessionCustomEntry } from "./compliance-transport.ts";
|
|
10
|
+
import {
|
|
11
|
+
readActivationEngineLaborFallbackField,
|
|
12
|
+
withEngineLaborFallbackField,
|
|
13
|
+
} from "./engine-labor-fallback.ts";
|
|
10
14
|
import { REVIEWER_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
|
|
11
15
|
import { type ReviewerSpecDisposition } from "./reviewer-construction.ts";
|
|
12
16
|
import { createReviewerDispatcher, type AcceptedReviewerDispatch, type AcceptedReviewerExecution, type ReviewerIssueFetcher, type ReviewerPinnedGitReader } from "./reviewer-dispatch.ts";
|
|
@@ -145,11 +149,15 @@ export function createReviewerRoleRuntime(
|
|
|
145
149
|
const output = validateReviewerIntent(parameters);
|
|
146
150
|
let record: ReviewerExecutionRecord;
|
|
147
151
|
try { record = ledger.recordForAudit(output.status); } catch (error) { if ((error as any)?.fatalReviewerInfrastructure) hostActions.failInfrastructure(error, toolCtx, id); throw error; }
|
|
148
|
-
const candidate =
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
const candidate = withEngineLaborFallbackField(
|
|
153
|
+
assembleRuntimeReviewerReceipt({
|
|
154
|
+
intent: output,
|
|
155
|
+
record,
|
|
156
|
+
canonicalSkillText: binding.snapshot.raw,
|
|
157
|
+
}),
|
|
158
|
+
// #380: sole-built declaration when any leg detour fell back to seat labor.
|
|
159
|
+
readActivationEngineLaborFallbackField(),
|
|
160
|
+
);
|
|
153
161
|
// First-record-then-audit: candidate lands on the parent session books
|
|
154
162
|
// before the auditor is spawned (zero hand-delivery).
|
|
155
163
|
try {
|
package/src/role-runtime.ts
CHANGED
|
@@ -21,7 +21,13 @@ import {
|
|
|
21
21
|
writeToolExecutionObservationRecord,
|
|
22
22
|
type ToolExecutionObservationWriter,
|
|
23
23
|
} from "./tool-execution-observation.ts";
|
|
24
|
+
import { ENGINE_DETOUR_TOOL_NAME } from "./engine-detour.ts";
|
|
24
25
|
import { registerEngineDetourTool } from "./engine-detour-tool.ts";
|
|
26
|
+
import {
|
|
27
|
+
createEngineLaborFallbackLatch,
|
|
28
|
+
installActivationEngineLaborFallbackLatch,
|
|
29
|
+
restoreEngineLaborFallbackFromSessionEntries,
|
|
30
|
+
} from "./engine-labor-fallback.ts";
|
|
25
31
|
import { installPackageOwnedToolRegistration } from "./package-owned-tool-idle.ts";
|
|
26
32
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
|
|
27
33
|
import { createOAuthKeepalive, type OAuthKeepaliveOptions } from "./oauth-keepalive.ts";
|
|
@@ -591,8 +597,9 @@ export function createRoleRuntimeExtension(
|
|
|
591
597
|
let pendingNavigatorSettlement: Promise<void> | undefined;
|
|
592
598
|
let navigatorWorkContext: NavigatorWorkContext | undefined;
|
|
593
599
|
const pendingInfrastructureToolCallIds = new Set<string>();
|
|
594
|
-
// #357 T2 / #378: engine detour once-latch +
|
|
600
|
+
// #357 T2 / #378 / #380: engine detour once-latch + seat-fallback latch (Judge|Reviewer+engine).
|
|
595
601
|
let engineDetourRegistration: ReturnType<typeof registerEngineDetourTool> | undefined;
|
|
602
|
+
let engineLaborFallbackLatch = createEngineLaborFallbackLatch();
|
|
596
603
|
// #288 primary-session thin adapter. The policy is the sole budget owner;
|
|
597
604
|
// terminating-tool rejections and mechanical delivery requests share two turns.
|
|
598
605
|
let receiptDelivery = createReceiptDeliveryPolicy();
|
|
@@ -1056,6 +1063,18 @@ export function createRoleRuntimeExtension(
|
|
|
1056
1063
|
pendingNavigatorPresentation = undefined;
|
|
1057
1064
|
pendingNavigatorSettlement = undefined;
|
|
1058
1065
|
pendingInfrastructureToolCallIds.clear();
|
|
1066
|
+
engineLaborFallbackLatch = createEngineLaborFallbackLatch();
|
|
1067
|
+
// #380 resume: restore sole-built fallback from durable same-session detour results.
|
|
1068
|
+
const sessionEntries =
|
|
1069
|
+
typeof ctx.sessionManager?.getEntries === "function"
|
|
1070
|
+
? ctx.sessionManager.getEntries()
|
|
1071
|
+
: [];
|
|
1072
|
+
restoreEngineLaborFallbackFromSessionEntries(
|
|
1073
|
+
engineLaborFallbackLatch,
|
|
1074
|
+
sessionEntries,
|
|
1075
|
+
ENGINE_DETOUR_TOOL_NAME,
|
|
1076
|
+
);
|
|
1077
|
+
installActivationEngineLaborFallbackLatch(engineLaborFallbackLatch);
|
|
1059
1078
|
engineDetourRegistration?.resetLatch();
|
|
1060
1079
|
navigatorWorkContext = undefined;
|
|
1061
1080
|
// #351: OAuth keepalive is orthogonal to --ak-role; start before role early-return
|
|
@@ -1168,8 +1187,9 @@ export function createRoleRuntimeExtension(
|
|
|
1168
1187
|
}
|
|
1169
1188
|
|
|
1170
1189
|
await executeActivationStage(entry.role, activationStage(entry.role, runtime), { clock, writeTrace });
|
|
1171
|
-
// #357 T2 / #378: Judge|Reviewer+engine activation registers the package detour tool once.
|
|
1190
|
+
// #357 T2 / #378 / #380: Judge|Reviewer+engine activation registers the package detour tool once.
|
|
1172
1191
|
// Gate is env presence only — no per-engine execute branch; no role-module spawn.
|
|
1192
|
+
// Seat-fallback latch is activation-scoped (installed at session_start) so legs share it.
|
|
1173
1193
|
if (
|
|
1174
1194
|
(entry.role === "judge" || entry.role === "reviewer") &&
|
|
1175
1195
|
engineDetourRegistration === undefined
|