@akagilnc/pi-workflow-roles 0.1.4526 → 0.1.4586
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/CLAUDE.md +4 -0
- package/README.md +3 -2
- package/README.zh-CN.md +4 -3
- package/dist/acp-host/production-host.js +854 -111
- package/dist/headless-host/production-host.js +850 -107
- package/dist/migrate-book-topology.js +5 -5
- package/dist/public-cli/invocation.js +10 -4
- package/dist/public-cli/main.js +733 -239
- package/dist/public-cli/option-definitions.js +5 -5
- package/dist/public-cli/post-admission.js +23 -8
- package/dist/public-cli/reviewer-run.js +339 -0
- package/dist/public-cli/terminal.js +9 -0
- package/dist/public-cli/turn-request.js +1 -1
- package/dist/public-role-summons.js +348 -16
- package/package.json +1 -1
- package/src/host-contracts.ts +0 -1
- package/src/public-cli/invocation.ts +19 -9
- package/src/public-cli/option-definitions.ts +5 -5
- package/src/public-cli/post-admission.ts +39 -9
- package/src/public-cli/reviewer-run.ts +238 -53
- package/src/public-cli/settlement.ts +0 -1
- package/src/public-cli/terminal.ts +27 -0
- package/src/public-cli/turn-request.ts +7 -1
- package/src/public-role-summons.ts +510 -17
- package/src/reviewer-role.ts +1 -2
- package/src/role-runtime.ts +2 -2
|
@@ -236,6 +236,12 @@ export type PostAdmissionEnv = {
|
|
|
236
236
|
freshSummons?: true;
|
|
237
237
|
/** Station child role run (#840): omit automatic navigator attendance. */
|
|
238
238
|
stationChild?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* Ephemeral host-turn cwd for Reviewer fresh-copy execution (#946 统一新副本).
|
|
241
|
+
* Admission and durable projectRoot stay on the caller project; the host turn
|
|
242
|
+
* (initial and resume) runs in this sandbox when present.
|
|
243
|
+
*/
|
|
244
|
+
executionCwd?: string;
|
|
239
245
|
};
|
|
240
246
|
|
|
241
247
|
/**
|
|
@@ -1352,6 +1358,16 @@ export async function runPostAdmissionSeatResume<
|
|
|
1352
1358
|
afterAdmittedLoad?: (
|
|
1353
1359
|
admitted: A,
|
|
1354
1360
|
) => Promise<AfterAdmittedLoadResult<A, T>>;
|
|
1361
|
+
/**
|
|
1362
|
+
* After the single pre-lease load (and optional afterAdmittedLoad), prepare
|
|
1363
|
+
* call-local execution context. Reviewer fresh-copy (#946) mints an ephemeral
|
|
1364
|
+
* worktree from the admitted projectRoot here — reuses this load, does not
|
|
1365
|
+
* pre-load outside the coordinator. cleanup runs after dispatch settles.
|
|
1366
|
+
*/
|
|
1367
|
+
afterAdmittedPrepare?: (admitted: A) => Promise<{
|
|
1368
|
+
readonly env?: PostAdmissionEnv;
|
|
1369
|
+
readonly cleanup?: () => Promise<void>;
|
|
1370
|
+
}>;
|
|
1355
1371
|
effectiveEngine?: string;
|
|
1356
1372
|
}): Promise<{ exitCode: number; admitted?: A; terminal?: T }> {
|
|
1357
1373
|
let request = input.request;
|
|
@@ -1382,6 +1398,10 @@ export async function runPostAdmissionSeatResume<
|
|
|
1382
1398
|
adapters = prepared.adapters;
|
|
1383
1399
|
}
|
|
1384
1400
|
|
|
1401
|
+
// Call-local execution env; afterAdmittedPrepare may replace it (Reviewer fresh-copy).
|
|
1402
|
+
let env = input.env;
|
|
1403
|
+
let preparedCleanup: (() => Promise<void>) | undefined;
|
|
1404
|
+
|
|
1385
1405
|
const buildRequestAfterLease = async (): Promise<RoleTurnRequest> => {
|
|
1386
1406
|
let openCourtAttemptId: string | undefined;
|
|
1387
1407
|
// Build uses the admitted judged under this lease (rehydrated when open
|
|
@@ -1464,8 +1484,14 @@ export async function runPostAdmissionSeatResume<
|
|
|
1464
1484
|
// Court recovery / open under lease, then dispatch.
|
|
1465
1485
|
// Station-child same-ticket/same-parent resume is call-local auto-resume
|
|
1466
1486
|
// (#840 / #416). Public `ak-role resume` stays one-shot (ADR 0080).
|
|
1487
|
+
// afterAdmittedPrepare runs inside this try so mint failure and cleanup share one finally.
|
|
1467
1488
|
try {
|
|
1468
|
-
if (input.
|
|
1489
|
+
if (input.afterAdmittedPrepare !== undefined) {
|
|
1490
|
+
const prepared = await input.afterAdmittedPrepare(loaded.admitted);
|
|
1491
|
+
if (prepared.env !== undefined) env = prepared.env;
|
|
1492
|
+
preparedCleanup = prepared.cleanup;
|
|
1493
|
+
}
|
|
1494
|
+
if (env.stationChild === true) {
|
|
1469
1495
|
let firstTurn: RoleTurnRequest | undefined;
|
|
1470
1496
|
const stationAdapters = withOnceSuccessfulBeforeDispatch(adapters);
|
|
1471
1497
|
type StationChildAttempt = { readonly resumeTurn: boolean };
|
|
@@ -1477,14 +1503,14 @@ export async function runPostAdmissionSeatResume<
|
|
|
1477
1503
|
});
|
|
1478
1504
|
return await runWithAutoResumeLoop({
|
|
1479
1505
|
admitted: loaded.admitted,
|
|
1480
|
-
principalAuthority:
|
|
1506
|
+
principalAuthority: env.principalAuthority,
|
|
1481
1507
|
isPrincipalAvailable: resolveHostAwareSessionAvailability(
|
|
1482
|
-
|
|
1483
|
-
|
|
1508
|
+
env.host,
|
|
1509
|
+
env.principalAuthority,
|
|
1484
1510
|
),
|
|
1485
1511
|
io: input.io,
|
|
1486
|
-
sessionAppender:
|
|
1487
|
-
autoResumeLimit:
|
|
1512
|
+
sessionAppender: env.sessionAppender,
|
|
1513
|
+
autoResumeLimit: env.autoResumeLimit,
|
|
1488
1514
|
buildInitialPayload: (): StationChildAttempt => ({ resumeTurn: false }),
|
|
1489
1515
|
buildResumePayload: (): StationChildAttempt => ({ resumeTurn: true }),
|
|
1490
1516
|
// Same as public manual resume: prior-court sealed acceptance is not a
|
|
@@ -1522,7 +1548,7 @@ export async function runPostAdmissionSeatResume<
|
|
|
1522
1548
|
dispatch: async (turnRequest) => {
|
|
1523
1549
|
const result = await dispatchPostAdmissionTurn({
|
|
1524
1550
|
admitted: loaded.admitted,
|
|
1525
|
-
env
|
|
1551
|
+
env,
|
|
1526
1552
|
io: attemptIo,
|
|
1527
1553
|
request: turnRequest,
|
|
1528
1554
|
lease,
|
|
@@ -1534,7 +1560,7 @@ export async function runPostAdmissionSeatResume<
|
|
|
1534
1560
|
});
|
|
1535
1561
|
return settleDeferredPersist(
|
|
1536
1562
|
loaded.admitted,
|
|
1537
|
-
|
|
1563
|
+
env.principalAuthority,
|
|
1538
1564
|
stationAdapters,
|
|
1539
1565
|
attemptIo,
|
|
1540
1566
|
result,
|
|
@@ -1545,7 +1571,7 @@ export async function runPostAdmissionSeatResume<
|
|
|
1545
1571
|
}
|
|
1546
1572
|
return await runPostAdmissionManualResume({
|
|
1547
1573
|
admitted: loaded.admitted,
|
|
1548
|
-
env
|
|
1574
|
+
env,
|
|
1549
1575
|
io: input.io,
|
|
1550
1576
|
adapters,
|
|
1551
1577
|
...(input.effectiveEngine === undefined
|
|
@@ -1561,6 +1587,10 @@ export async function runPostAdmissionSeatResume<
|
|
|
1561
1587
|
return { exitCode: 2 };
|
|
1562
1588
|
}
|
|
1563
1589
|
throw error;
|
|
1590
|
+
} finally {
|
|
1591
|
+
if (preparedCleanup !== undefined) {
|
|
1592
|
+
await preparedCleanup();
|
|
1593
|
+
}
|
|
1564
1594
|
}
|
|
1565
1595
|
}
|
|
1566
1596
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* extra packets. Controlled-failure settlement reuses #107.
|
|
5
5
|
* #526: execution via RoleTurnHost; argv is Pi adapter internal.
|
|
6
6
|
*/
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
|
|
7
9
|
import type {
|
|
8
10
|
DurablePrincipalAuthority,
|
|
9
11
|
MethodBinding,
|
|
@@ -40,7 +42,11 @@ import {
|
|
|
40
42
|
trySettleReviewerTerminalResult,
|
|
41
43
|
} from "./settlement.ts";
|
|
42
44
|
import type { CliIo } from "./cli-io.ts";
|
|
43
|
-
import
|
|
45
|
+
import {
|
|
46
|
+
formatTerminalResult,
|
|
47
|
+
isLawfulTypedTerminalOutcome,
|
|
48
|
+
type TerminalResult,
|
|
49
|
+
} from "./terminal.ts";
|
|
44
50
|
import {
|
|
45
51
|
projectRoleTurnRequest,
|
|
46
52
|
type RoleTurnRequestProjectionOptions,
|
|
@@ -59,10 +65,42 @@ export type ReviewerRunEnv = PostAdmissionEnv & {
|
|
|
59
65
|
createRunId?: () => string;
|
|
60
66
|
};
|
|
61
67
|
|
|
68
|
+
type ReviewerRunResult = {
|
|
69
|
+
exitCode: number;
|
|
70
|
+
admitted?: AdmittedReviewerInvocation;
|
|
71
|
+
terminal?: TerminalResult;
|
|
72
|
+
};
|
|
73
|
+
|
|
62
74
|
function reviewerMethods(packageRoot: string): readonly MethodBinding[] {
|
|
63
75
|
return [{ kind: "skill", path: resolvePackagedMethodSkillPath(packageRoot, "ak-cross-m-review") }];
|
|
64
76
|
}
|
|
65
77
|
|
|
78
|
+
/**
|
|
79
|
+
* All Reviewer turns (explicit --lens, dual-lens child, resume) execute in a
|
|
80
|
+
* fresh worktree of the source tree's current HEAD (#946 统一新副本). Dual-lens
|
|
81
|
+
* summon already injects executionCwd; only mint when absent. Cleanup failure
|
|
82
|
+
* is diagnostic only and does not flip the exit code. Mint failure propagates
|
|
83
|
+
* so the caller can settle a controlled failure against the admitted run.
|
|
84
|
+
*/
|
|
85
|
+
async function runReviewerTurnInFreshCopy(
|
|
86
|
+
env: ReviewerRunEnv,
|
|
87
|
+
projectRoot: string,
|
|
88
|
+
io: CliIo,
|
|
89
|
+
body: (sandboxedEnv: ReviewerRunEnv) => Promise<ReviewerRunResult>,
|
|
90
|
+
): Promise<ReviewerRunResult> {
|
|
91
|
+
if (env.executionCwd !== undefined) {
|
|
92
|
+
return body(env);
|
|
93
|
+
}
|
|
94
|
+
const { withEphemeralReviewerWorktree } = await import("../public-role-summons.ts");
|
|
95
|
+
return await withEphemeralReviewerWorktree({
|
|
96
|
+
projectRoot,
|
|
97
|
+
onCleanupDiagnostic: (diagnostic) => {
|
|
98
|
+
io.stderr(`${diagnostic}\n`);
|
|
99
|
+
},
|
|
100
|
+
run: (executionCwd) => body({ ...env, executionCwd }),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
66
104
|
/** Project admitted Reviewer invocation onto the host-neutral turn request. */
|
|
67
105
|
export function buildReviewerTurnRequest(
|
|
68
106
|
admitted: AdmittedReviewerInvocation,
|
|
@@ -149,7 +187,7 @@ export async function runPublicReviewer(
|
|
|
149
187
|
instruction: string;
|
|
150
188
|
attachmentPaths: string[];
|
|
151
189
|
baseRevision: string;
|
|
152
|
-
lens
|
|
190
|
+
lens?: ReviewerLens;
|
|
153
191
|
authorityRefs: string[];
|
|
154
192
|
project?: string;
|
|
155
193
|
},
|
|
@@ -158,9 +196,100 @@ export async function runPublicReviewer(
|
|
|
158
196
|
admitted?: AdmittedReviewerInvocation;
|
|
159
197
|
terminal?: TerminalResult;
|
|
160
198
|
}> {
|
|
199
|
+
let parsed: ReturnType<typeof parseReviewerArgv>;
|
|
200
|
+
try {
|
|
201
|
+
parsed = parseReviewerArgv(argv);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error instanceof CliUsageError) {
|
|
204
|
+
presentStructuralRejection(error, io);
|
|
205
|
+
return { exitCode: 2 };
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Omitted public lens is the parallel two-axis branch mark; never admitted as a parent run.
|
|
211
|
+
// Each leg reuses this call's public argv and only adds --lens (#946 / 10a).
|
|
212
|
+
if (parsed.lens === undefined) {
|
|
213
|
+
const { summonParallelReviewerLenses } = await import("../public-role-summons.ts");
|
|
214
|
+
const children = await summonParallelReviewerLenses({
|
|
215
|
+
argv,
|
|
216
|
+
// Replay argv under the same cwd the single-axis entry would see (10a).
|
|
217
|
+
// Do not substitute the resolved project path — relative --project must
|
|
218
|
+
// not be re-resolved against a shifted cwd.
|
|
219
|
+
cwd: env.cwd,
|
|
220
|
+
projectRoot: resolve(parsed.project ?? env.cwd),
|
|
221
|
+
// Typed base from the public parse — precheck only; child argv stays verbatim.
|
|
222
|
+
baseRevision: parsed.baseRevision,
|
|
223
|
+
home: env.home,
|
|
224
|
+
agentDir: env.agentDir,
|
|
225
|
+
...(env.credentials === undefined ? {} : { credentials: env.credentials }),
|
|
226
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
227
|
+
...(env.host === undefined ? {} : { host: env.host }),
|
|
228
|
+
...(env.engine === undefined ? {} : { engine: env.engine }),
|
|
229
|
+
...(env.engineModel === undefined ? {} : { engineModel: env.engineModel }),
|
|
230
|
+
packageRoot: env.packageRoot,
|
|
231
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
232
|
+
...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
|
|
233
|
+
roleTurnHost: env.roleTurnHost,
|
|
234
|
+
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
235
|
+
principalAuthority: env.principalAuthority,
|
|
236
|
+
...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
|
|
237
|
+
});
|
|
238
|
+
const childResults = [children.completeness, children.correctness] as const;
|
|
239
|
+
const terminals = childResults
|
|
240
|
+
.map((child) => child.terminal)
|
|
241
|
+
.filter((terminal): terminal is TerminalResult => terminal !== undefined);
|
|
242
|
+
// ADR 0052 / terminal.ts: lawful typed child terminals (accepted / no_receipt /
|
|
243
|
+
// audit_escalation) are not batch failures. Child exitCode remains the live
|
|
244
|
+
// surface for seal/infrastructure overlays that keep the original Terminal.
|
|
245
|
+
// failedChildren counts only non-lawful/missing child Terminals; seal overlays
|
|
246
|
+
// that flip exitCode while keeping lawful Terminals are a separate identity.
|
|
247
|
+
const failedChildren = childResults.filter((child) =>
|
|
248
|
+
child.terminal === undefined
|
|
249
|
+
|| !isLawfulTypedTerminalOutcome(child.terminal.roleOutcome)).length;
|
|
250
|
+
const overlayExit = childResults.some((child) => child.exitCode !== 0);
|
|
251
|
+
const failed = failedChildren > 0 || overlayExit;
|
|
252
|
+
const overlayDiagnostics = [...new Set(
|
|
253
|
+
childResults
|
|
254
|
+
.map((child) => child.stderr)
|
|
255
|
+
.filter((text): text is string => typeof text === "string" && text !== ""),
|
|
256
|
+
)];
|
|
257
|
+
const terminal: TerminalResult = {
|
|
258
|
+
batch: "reviewer",
|
|
259
|
+
roleOutcome: failed
|
|
260
|
+
? {
|
|
261
|
+
kind: "failure",
|
|
262
|
+
role: "reviewer",
|
|
263
|
+
diagnostic: failedChildren > 0
|
|
264
|
+
? "Reviewer batch child failure"
|
|
265
|
+
: (overlayDiagnostics[0] ?? "Reviewer batch infrastructure failure"),
|
|
266
|
+
decisiveFacts: { failedChildren },
|
|
267
|
+
payloads: terminals,
|
|
268
|
+
}
|
|
269
|
+
: { kind: "accepted", role: "reviewer", payloads: terminals },
|
|
270
|
+
reviewerChildren: {
|
|
271
|
+
...(children.completeness.terminal === undefined ? {} : { completeness: children.completeness.terminal }),
|
|
272
|
+
...(children.correctness.terminal === undefined ? {} : { correctness: children.correctness.terminal }),
|
|
273
|
+
},
|
|
274
|
+
reviewerChildOutcomes: {
|
|
275
|
+
completeness: { exitCode: children.completeness.exitCode, ...(children.completeness.stderr === undefined ? {} : { stderr: children.completeness.stderr }) },
|
|
276
|
+
correctness: { exitCode: children.correctness.exitCode, ...(children.correctness.stderr === undefined ? {} : { stderr: children.correctness.stderr }) },
|
|
277
|
+
},
|
|
278
|
+
// Batch has no parent run and no own attendance; no-advice is affirmative
|
|
279
|
+
// only (navigator-attendance.ts). Children carry their own navigator facts.
|
|
280
|
+
navigator: {
|
|
281
|
+
disposition: "unavailable",
|
|
282
|
+
source: "unknown",
|
|
283
|
+
reason: "Reviewer batch has no parent-run Navigator attendance",
|
|
284
|
+
},
|
|
285
|
+
artifacts: terminals.flatMap((item) => item.artifacts),
|
|
286
|
+
};
|
|
287
|
+
io.stdout(formatTerminalResult(terminal));
|
|
288
|
+
return { exitCode: failed ? 1 : 0, terminal };
|
|
289
|
+
}
|
|
290
|
+
|
|
161
291
|
let admitted: AdmittedReviewerInvocation;
|
|
162
292
|
try {
|
|
163
|
-
const parsed = parseReviewerArgv(argv);
|
|
164
293
|
admitted = await admitReviewerInvocation({
|
|
165
294
|
home: env.home,
|
|
166
295
|
principalAuthority: env.principalAuthority,
|
|
@@ -172,6 +301,7 @@ export async function runPublicReviewer(
|
|
|
172
301
|
authorityRefs: parsed.authorityRefs,
|
|
173
302
|
...(parsed.project === undefined ? {} : { project: parsed.project }),
|
|
174
303
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
304
|
+
...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
|
|
175
305
|
...(env.model === undefined ? {} : { model: env.model }),
|
|
176
306
|
});
|
|
177
307
|
} catch (error) {
|
|
@@ -202,56 +332,82 @@ export async function runPublicReviewer(
|
|
|
202
332
|
)) as { exitCode: number; admitted: AdmittedReviewerInvocation; terminal: TerminalResult };
|
|
203
333
|
}
|
|
204
334
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
io,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
335
|
+
// Fresh copy for this call (explicit --lens or dual-lens child with pre-set cwd).
|
|
336
|
+
// Durable projectRoot stays the caller project; only the host turn uses the sandbox.
|
|
337
|
+
try {
|
|
338
|
+
return await runReviewerTurnInFreshCopy(env, admitted.projectRoot, io, async (sandboxedEnv) =>
|
|
339
|
+
await runPostAdmissionResumable({
|
|
340
|
+
admitted,
|
|
341
|
+
env: sandboxedEnv,
|
|
342
|
+
io,
|
|
343
|
+
buildInitialRequest: () =>
|
|
344
|
+
buildReviewerTurnRequest(admitted, {
|
|
345
|
+
packageRoot: sandboxedEnv.packageRoot,
|
|
346
|
+
home: sandboxedEnv.home,
|
|
347
|
+
agentDir: sandboxedEnv.agentDir,
|
|
348
|
+
...(sandboxedEnv.model === undefined ? {} : { model: sandboxedEnv.model }),
|
|
349
|
+
...pickEngineAxis(sandboxedEnv),
|
|
350
|
+
...(sandboxedEnv.timeoutMs === undefined ? {} : { timeoutMs: sandboxedEnv.timeoutMs }),
|
|
351
|
+
...(admitted.correlationId === undefined && sandboxedEnv.correlationId === undefined
|
|
352
|
+
? {}
|
|
353
|
+
: { correlationId: admitted.correlationId ?? sandboxedEnv.correlationId }),
|
|
354
|
+
// Ephemeral worktree cwd; durable projectRoot stays on admitted caller project.
|
|
355
|
+
...(sandboxedEnv.executionCwd === undefined ? {} : { cwd: sandboxedEnv.executionCwd }),
|
|
356
|
+
continuation: {
|
|
357
|
+
kind: "initial",
|
|
358
|
+
prompt: buildReviewerTransportPrompt(
|
|
359
|
+
admitted,
|
|
360
|
+
engineSessionMaterialFromOptions({
|
|
361
|
+
...pickEngineAxis(sandboxedEnv),
|
|
362
|
+
packageRoot: sandboxedEnv.packageRoot,
|
|
363
|
+
}),
|
|
364
|
+
),
|
|
365
|
+
},
|
|
366
|
+
}),
|
|
367
|
+
buildResumeRequest: () =>
|
|
368
|
+
buildReviewerTurnRequest(admitted, {
|
|
369
|
+
packageRoot: sandboxedEnv.packageRoot,
|
|
370
|
+
home: sandboxedEnv.home,
|
|
371
|
+
agentDir: sandboxedEnv.agentDir,
|
|
372
|
+
...(sandboxedEnv.model === undefined ? {} : { model: sandboxedEnv.model }),
|
|
373
|
+
...pickEngineAxis(sandboxedEnv),
|
|
374
|
+
...(sandboxedEnv.timeoutMs === undefined ? {} : { timeoutMs: sandboxedEnv.timeoutMs }),
|
|
375
|
+
...(admitted.correlationId === undefined && sandboxedEnv.correlationId === undefined
|
|
376
|
+
? {}
|
|
377
|
+
: { correlationId: admitted.correlationId ?? sandboxedEnv.correlationId }),
|
|
378
|
+
// In-batch auto-resume keeps the same call's sandbox (dual-lens or single).
|
|
379
|
+
...(sandboxedEnv.executionCwd === undefined ? {} : { cwd: sandboxedEnv.executionCwd }),
|
|
380
|
+
continuation: {
|
|
381
|
+
kind: "resume",
|
|
382
|
+
prompt: reviewerResumePrompt(sandboxedEnv),
|
|
383
|
+
},
|
|
384
|
+
}),
|
|
385
|
+
adapters: reviewerAdapters(sandboxedEnv.packageRoot, methodMaterial),
|
|
386
|
+
...(sandboxedEnv.engine === undefined ? {} : { effectiveEngine: sandboxedEnv.engine }),
|
|
387
|
+
}));
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return (await presentControlledFailure(
|
|
390
|
+
admitted,
|
|
391
|
+
{
|
|
392
|
+
timedOut: false,
|
|
393
|
+
code: null,
|
|
394
|
+
stderr: "",
|
|
395
|
+
thrown: error,
|
|
396
|
+
},
|
|
397
|
+
reviewerAdapters(env.packageRoot, methodMaterial),
|
|
398
|
+
env.principalAuthority,
|
|
399
|
+
io,
|
|
400
|
+
)) as ReviewerRunResult;
|
|
401
|
+
}
|
|
250
402
|
}
|
|
251
403
|
|
|
252
404
|
/**
|
|
253
405
|
* Resume a previously admitted Reviewer Role run after a typed HTTP 429.
|
|
254
406
|
* Restores task/base/session identity; model override is temporary.
|
|
407
|
+
* Execution always uses a fresh worktree of the source tree at resume time
|
|
408
|
+
* (#946 统一新副本 / 10a) — no old worktree, no ownership record.
|
|
409
|
+
* Fresh-copy mint reuses the coordinator's single pre-lease load via
|
|
410
|
+
* afterAdmittedPrepare; does not pre-load outside the coordinator.
|
|
255
411
|
*/
|
|
256
412
|
export async function runPublicReviewerResume(
|
|
257
413
|
request: PublicResumeRequest,
|
|
@@ -262,6 +418,11 @@ export async function runPublicReviewerResume(
|
|
|
262
418
|
admitted?: AdmittedReviewerInvocation;
|
|
263
419
|
terminal?: TerminalResult;
|
|
264
420
|
}> {
|
|
421
|
+
// Call-local cell: afterAdmittedPrepare writes executionCwd; buildTurnRequest reads it.
|
|
422
|
+
// Keeps the single-load coordinator contract — no preliminary load outside.
|
|
423
|
+
const sandbox: { executionCwd?: string } = {
|
|
424
|
+
...(env.executionCwd === undefined ? {} : { executionCwd: env.executionCwd }),
|
|
425
|
+
};
|
|
265
426
|
return await runPostAdmissionSeatResume({
|
|
266
427
|
request,
|
|
267
428
|
env,
|
|
@@ -269,25 +430,49 @@ export async function runPublicReviewerResume(
|
|
|
269
430
|
load: (effective) =>
|
|
270
431
|
loadResumableReviewerRun(env.home, effective.runId, env.principalAuthority),
|
|
271
432
|
buildTurnRequest: (admitted, effective) => {
|
|
272
|
-
const
|
|
433
|
+
const activeEnv: ReviewerRunEnv = sandbox.executionCwd === undefined
|
|
434
|
+
? env
|
|
435
|
+
: { ...env, executionCwd: sandbox.executionCwd };
|
|
436
|
+
const base = resumeTurnRequestProjectionOptions(admitted, effective, activeEnv);
|
|
273
437
|
return buildReviewerTurnRequest(admitted, {
|
|
274
438
|
...base,
|
|
439
|
+
// Fresh copy at resume time; durable projectRoot unchanged.
|
|
440
|
+
...(sandbox.executionCwd === undefined ? {} : { cwd: sandbox.executionCwd }),
|
|
275
441
|
continuation: {
|
|
276
442
|
kind: "resume",
|
|
277
|
-
prompt: reviewerResumePrompt(
|
|
443
|
+
prompt: reviewerResumePrompt(activeEnv, effective.message),
|
|
278
444
|
},
|
|
279
445
|
});
|
|
280
446
|
},
|
|
281
447
|
adapters: reviewerAdapters(env.packageRoot),
|
|
282
|
-
afterAdmittedLoad: (admitted) =>
|
|
283
|
-
resolveResumeMethodMaterialAdapters({
|
|
448
|
+
afterAdmittedLoad: async (admitted) => {
|
|
449
|
+
return resolveResumeMethodMaterialAdapters({
|
|
284
450
|
admitted,
|
|
285
451
|
authority: env.principalAuthority,
|
|
286
452
|
io,
|
|
287
453
|
loadMaterial: () => loadReviewerMethodMaterial(env.packageRoot),
|
|
288
454
|
adaptersWith: (material) => reviewerAdapters(env.packageRoot, material),
|
|
289
455
|
emptyAdapters: reviewerAdapters(env.packageRoot),
|
|
290
|
-
})
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
afterAdmittedPrepare: async (admitted) => {
|
|
459
|
+
// Already sandboxed (in-batch auto-resume path) — nothing to mint.
|
|
460
|
+
if (sandbox.executionCwd !== undefined) {
|
|
461
|
+
return { env: { ...env, executionCwd: sandbox.executionCwd } };
|
|
462
|
+
}
|
|
463
|
+
const { openEphemeralReviewerWorktree } = await import("../public-role-summons.ts");
|
|
464
|
+
const opened = await openEphemeralReviewerWorktree({
|
|
465
|
+
projectRoot: admitted.projectRoot,
|
|
466
|
+
onCleanupDiagnostic: (diagnostic) => {
|
|
467
|
+
io.stderr(`${diagnostic}\n`);
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
sandbox.executionCwd = opened.executionCwd;
|
|
471
|
+
return {
|
|
472
|
+
env: { ...env, executionCwd: opened.executionCwd },
|
|
473
|
+
cleanup: opened.close,
|
|
474
|
+
};
|
|
475
|
+
},
|
|
291
476
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
292
477
|
});
|
|
293
478
|
}
|
|
@@ -3634,7 +3634,6 @@ export async function trySettleReviewerTerminalResult(
|
|
|
3634
3634
|
return settleLawfulReviewerTerminalResult(admitted, authority, options, scope);
|
|
3635
3635
|
}
|
|
3636
3636
|
|
|
3637
|
-
|
|
3638
3637
|
/**
|
|
3639
3638
|
* Observe forced Merger resolving-merge-conflicts Skill expansions from the session.
|
|
3640
3639
|
* Expansion evidence is package-path only; ambient home locations never count.
|
|
@@ -182,6 +182,16 @@ export function roleResultPayloads(outcome: TerminalRoleOutcome): readonly unkno
|
|
|
182
182
|
|
|
183
183
|
export type TerminalResult = {
|
|
184
184
|
roleOutcome: TerminalRoleOutcome;
|
|
185
|
+
/** Default Reviewer parent: original child Terminals, keyed by frozen axis. */
|
|
186
|
+
reviewerChildren?: Readonly<{
|
|
187
|
+
completeness?: TerminalResult;
|
|
188
|
+
correctness?: TerminalResult;
|
|
189
|
+
}>;
|
|
190
|
+
/** Per-axis summons facts retained even when no child Terminal exists. */
|
|
191
|
+
reviewerChildOutcomes?: Readonly<{
|
|
192
|
+
completeness: { exitCode: number; stderr?: string };
|
|
193
|
+
correctness: { exitCode: number; stderr?: string };
|
|
194
|
+
}>;
|
|
185
195
|
navigator: TerminalNavigatorFact;
|
|
186
196
|
artifacts: readonly TerminalArtifactRef[];
|
|
187
197
|
/**
|
|
@@ -201,10 +211,18 @@ export type TerminalResult = {
|
|
|
201
211
|
/** Resumable failure: run ID appears only inside resume.command. */
|
|
202
212
|
resume: TerminalResume;
|
|
203
213
|
runId?: undefined;
|
|
214
|
+
batch?: undefined;
|
|
204
215
|
}
|
|
205
216
|
| {
|
|
206
217
|
runId: string;
|
|
207
218
|
resume?: undefined;
|
|
219
|
+
batch?: undefined;
|
|
220
|
+
}
|
|
221
|
+
| {
|
|
222
|
+
/** Deterministic public batch projection; no parent Role run exists. */
|
|
223
|
+
batch: "reviewer";
|
|
224
|
+
runId?: undefined;
|
|
225
|
+
resume?: undefined;
|
|
208
226
|
}
|
|
209
227
|
);
|
|
210
228
|
|
|
@@ -303,6 +321,15 @@ export function formatTerminalResult(result: TerminalResult): string {
|
|
|
303
321
|
);
|
|
304
322
|
}
|
|
305
323
|
}
|
|
324
|
+
if (result.reviewerChildOutcomes !== undefined) {
|
|
325
|
+
for (const axis of ["completeness", "correctness"] as const) {
|
|
326
|
+
const child = result.reviewerChildOutcomes[axis];
|
|
327
|
+
lines.push(`reviewer-child\t${axis}\t${child.exitCode}`);
|
|
328
|
+
if (child.stderr !== undefined && child.stderr !== "") {
|
|
329
|
+
lines.push(`reviewer-child-diagnostic\t${axis}\t${encodeTerminalField(child.stderr)}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
306
333
|
if (result.resume !== undefined) {
|
|
307
334
|
// Resumable failure: run ID is revealed only inside the complete resume command.
|
|
308
335
|
lines.push(`resume\t${encodeTerminalField(result.resume.command)}`);
|
|
@@ -38,6 +38,12 @@ export type RoleTurnRequestProjectionOptions = {
|
|
|
38
38
|
invocationScopeId?: string;
|
|
39
39
|
/** Station child role run (#840): omit automatic navigator attendance. */
|
|
40
40
|
stationChild?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Optional host-turn cwd override. Durable admitted projectRoot stays the
|
|
43
|
+
* identity source; Reviewer fresh-copy sandboxes (explicit --lens, dual-lens
|
|
44
|
+
* legs, resume) inject the ephemeral worktree here (#946 统一新副本).
|
|
45
|
+
*/
|
|
46
|
+
cwd?: string;
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
export type AdmittedTurnInvocation = {
|
|
@@ -58,7 +64,7 @@ export function projectRoleTurnRequest(
|
|
|
58
64
|
},
|
|
59
65
|
options: RoleTurnRequestProjectionOptions,
|
|
60
66
|
): RoleTurnRequest {
|
|
61
|
-
const cwd = admitted.projectRoot ?? admitted.repoRoot ?? admitted.cwd;
|
|
67
|
+
const cwd = options.cwd ?? admitted.projectRoot ?? admitted.repoRoot ?? admitted.cwd;
|
|
62
68
|
if (cwd === undefined) throw new Error("admitted invocation missing working directory");
|
|
63
69
|
if (admitted.principal === undefined) throw new Error("admitted invocation missing principal");
|
|
64
70
|
// #617 DK-3: turn model is the live seat/env model only — never the admitted
|