@akagilnc/pi-workflow-roles 0.1.4528 → 0.1.4620
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 +5 -4
- package/README.zh-CN.md +6 -5
- package/dist/acp-host/production-host.js +1834 -637
- package/dist/archivist-record-topology.js +27 -35
- package/dist/headless-host/description.js +7 -4
- package/dist/headless-host/production-host.js +1652 -519
- package/dist/migrate-book-topology.js +8 -11
- package/dist/navigator-attendance.js +146 -241
- package/dist/navigator-public-session.js +81 -15
- package/dist/package-contracts/navigator-output.js +30 -12
- package/dist/public-cli/invocation.js +10 -4
- package/dist/public-cli/judge-run.js +2 -2
- package/dist/public-cli/main.js +879 -410
- package/dist/public-cli/option-definitions.js +6 -6
- package/dist/public-cli/post-admission.js +25 -10
- package/dist/public-cli/reviewer-run.js +339 -0
- package/dist/public-cli/run-lifecycle.js +29 -7
- package/dist/public-cli/settlement.js +46 -32
- package/dist/public-cli/terminal.js +17 -27
- package/dist/public-cli/turn-request.js +1 -1
- package/dist/public-role-summons.js +370 -20
- package/dist/submission-ledger.js +56 -23
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +2 -0
- package/resources/navigator-route-playbook.md +9 -0
- package/scripts/build-package.mjs +6 -1
- package/src/acp-host/role-turn-host.ts +129 -13
- package/src/headless-host/description.ts +17 -7
- package/src/headless-host/role-turn-host.ts +42 -11
- package/src/host-contracts.ts +0 -1
- package/src/navigator-attendance.ts +290 -425
- package/src/navigator-public-session.ts +126 -25
- package/src/navigator-role.ts +2 -2
- package/src/package-contracts/navigator-output.ts +41 -25
- package/src/public-cli/coder-run.ts +2 -2
- package/src/public-cli/fixer-run.ts +2 -2
- package/src/public-cli/invocation.ts +19 -9
- package/src/public-cli/judge-run.ts +2 -2
- package/src/public-cli/merger-run.ts +2 -2
- package/src/public-cli/option-definitions.ts +6 -6
- package/src/public-cli/post-admission.ts +41 -10
- package/src/public-cli/reviewer-run.ts +238 -53
- package/src/public-cli/run-lifecycle.ts +37 -7
- package/src/public-cli/settlement.ts +42 -34
- package/src/public-cli/terminal.ts +39 -37
- package/src/public-cli/turn-request.ts +7 -1
- package/src/public-role-summons.ts +544 -22
- package/src/reviewer-role.ts +1 -2
- package/src/role-envelope.ts +89 -4
- package/src/role-runtime.ts +76 -16
- package/src/submission-ledger.ts +63 -21
- package/dist/public-cli/command-renderer.js +0 -5
- package/dist/public-command-renderer.js +0 -29
- package/src/public-cli/command-renderer.ts +0 -9
- package/src/public-command-renderer.ts +0 -55
|
@@ -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
|
}
|
|
@@ -127,8 +127,17 @@ export type RoleRunRecord = {
|
|
|
127
127
|
readonly resumable?: TypedHttp429Observation;
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
-
/**
|
|
131
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Package-owned non-empty Chinese neutral resume transport (#959 / ADR 0073).
|
|
132
|
+
* Used by:
|
|
133
|
+
* - auto-resume (all seats via buildAutoResumeContinuationPrompt) — required so
|
|
134
|
+
* hosts that reject empty stdin (codex) still receive a prompt;
|
|
135
|
+
* - reviewer seat manual resume (reviewerResumePrompt) — same non-empty need on
|
|
136
|
+
* that seat's own manual entry.
|
|
137
|
+
* Generic bare `ak-role resume` stays empty-capable via selectResumeContinuationPrompt
|
|
138
|
+
* / buildResumeContinuationPrompt — ADR 0080 keeps auto and generic-manual entries separate.
|
|
139
|
+
*/
|
|
140
|
+
export const RESUME_TRANSPORT_ENVELOPE = "继续。" as const;
|
|
132
141
|
|
|
133
142
|
/** Public manual resume request after the unique CLI parser owns runId + optional message. */
|
|
134
143
|
export type PublicResumeRequest = {
|
|
@@ -169,9 +178,11 @@ export type SameTicketSummonsMaterials = {
|
|
|
169
178
|
};
|
|
170
179
|
|
|
171
180
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
181
|
+
* Manual resume continuation selector (#471 / #600 / #736 / ADR 0080).
|
|
182
|
+
* Message present → those bytes; absent → engine pointers only (may be empty).
|
|
183
|
+
* Caller message (including blank) still wins verbatim when supplied.
|
|
184
|
+
* Auto-resume must use buildAutoResumeContinuationPrompt — do not fold the
|
|
185
|
+
* non-empty Chinese envelope into this shared manual selector (#959).
|
|
175
186
|
*/
|
|
176
187
|
export function selectResumeContinuationPrompt(
|
|
177
188
|
message?: string,
|
|
@@ -182,8 +193,8 @@ export function selectResumeContinuationPrompt(
|
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
196
|
+
* Manual resume continuation with engine material from the seat env (#600).
|
|
197
|
+
* Bare manual resume stays empty-prompt-capable; auto-resume is a separate entry.
|
|
187
198
|
*/
|
|
188
199
|
export function buildResumeContinuationPrompt(options: {
|
|
189
200
|
packageRoot: string;
|
|
@@ -200,6 +211,25 @@ export function buildResumeContinuationPrompt(options: {
|
|
|
200
211
|
);
|
|
201
212
|
}
|
|
202
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Auto-resume continuation only (#959 / ADR 0080).
|
|
216
|
+
* Always non-empty: Chinese neutral envelope plus optional engine pointers.
|
|
217
|
+
* Never call this from manual `ak-role resume`.
|
|
218
|
+
*/
|
|
219
|
+
export function buildAutoResumeContinuationPrompt(options: {
|
|
220
|
+
packageRoot: string;
|
|
221
|
+
engine?: string;
|
|
222
|
+
engineModel?: string;
|
|
223
|
+
}): string {
|
|
224
|
+
return selectResumeContinuationPrompt(
|
|
225
|
+
RESUME_TRANSPORT_ENVELOPE,
|
|
226
|
+
engineSessionMaterialFromOptions({
|
|
227
|
+
packageRoot: options.packageRoot,
|
|
228
|
+
...pickEngineAxis(options),
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
203
233
|
const RUN_STATE_FILE = "run-state.json";
|
|
204
234
|
const WRITER_LOCK_FILE = "writer.lock";
|
|
205
235
|
|
|
@@ -123,7 +123,6 @@ import {
|
|
|
123
123
|
parseInvocationMarkerIdentity,
|
|
124
124
|
type InvocationMarkerIdentity,
|
|
125
125
|
} from "../navigator-invocation-identity.ts";
|
|
126
|
-
import type { NavigatorPhase } from "../navigator-attendance.ts";
|
|
127
126
|
import {
|
|
128
127
|
NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
|
|
129
128
|
RECEIPT_DELIVERY_TURN_LIMIT,
|
|
@@ -360,7 +359,7 @@ import {
|
|
|
360
359
|
exitCodeForTerminalOutcome,
|
|
361
360
|
formatTerminalResult,
|
|
362
361
|
isLawfulTypedTerminalOutcome,
|
|
363
|
-
|
|
362
|
+
adviceNavigatorFact,
|
|
364
363
|
type ControlledFailureCause,
|
|
365
364
|
type TerminalArtifactRef,
|
|
366
365
|
type TerminalGateFact,
|
|
@@ -782,7 +781,13 @@ export function explicitInternalKnownFailureClassificationInput(
|
|
|
782
781
|
};
|
|
783
782
|
}
|
|
784
783
|
|
|
785
|
-
/**
|
|
784
|
+
/**
|
|
785
|
+
* Post-role Navigator delivery grace (Issue #11 / #101 / #106 / #159).
|
|
786
|
+
* After the parent finishes: wait at most this long for navigator output, then
|
|
787
|
+
* stop waiting. Navigator must already be running from parent start (prepare
|
|
788
|
+
* host round in parallel); this window is only the tail after parent end — not
|
|
789
|
+
* the budget to start a cold full host turn (owner 2026-09-17 #959).
|
|
790
|
+
*/
|
|
786
791
|
export const NAVIGATOR_POST_ROLE_GRACE_MS = 10_000;
|
|
787
792
|
|
|
788
793
|
type SessionMessage = {
|
|
@@ -1931,11 +1936,6 @@ export type LawfulJudgeRoleOutcome = Extract<
|
|
|
1931
1936
|
TerminalRoleOutcome,
|
|
1932
1937
|
{ kind: "accepted" } | { kind: "audit_escalation" }
|
|
1933
1938
|
>;
|
|
1934
|
-
function navigatorPhaseValue(value: unknown): NavigatorPhase {
|
|
1935
|
-
if (value === "plan" || value === "apply") return value;
|
|
1936
|
-
return null;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
1939
|
/**
|
|
1940
1940
|
* Minimal attendance provenance against the bound marker (ADR 0043).
|
|
1941
1941
|
* Keep only invocationId + post-terminal ordering. Runtime-self-produced
|
|
@@ -1960,35 +1960,44 @@ function parseNavigatorAttendanceDetails(
|
|
|
1960
1960
|
const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string"
|
|
1961
1961
|
? { advisoryDiagnostic: details.routePlaybookReadFailure }
|
|
1962
1962
|
: {};
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1963
|
+
// #959: advice prose is presented as-is. Legacy "recommendation" with next/reason/
|
|
1964
|
+
// command is projected into prose so historical sessions still render — never wash
|
|
1965
|
+
// a real recommendation into no-advice when any advice body is recoverable.
|
|
1966
|
+
if (disposition === "advice" || disposition === "recommendation") {
|
|
1967
|
+
let prose: string | undefined;
|
|
1968
|
+
if (typeof details.prose === "string" && details.prose.trim() !== "") {
|
|
1969
|
+
prose = details.prose;
|
|
1970
|
+
} else {
|
|
1971
|
+
const next = isRecord(details.next) && typeof details.next.role === "string"
|
|
1972
|
+
? details.next.role
|
|
1973
|
+
: undefined;
|
|
1974
|
+
const reason = typeof details.reason === "string" && details.reason.trim() !== ""
|
|
1975
|
+
? details.reason
|
|
1976
|
+
: undefined;
|
|
1977
|
+
const command = typeof details.command === "string" && details.command.trim() !== ""
|
|
1978
|
+
? details.command
|
|
1979
|
+
: undefined;
|
|
1980
|
+
if (reason !== undefined && next !== undefined) {
|
|
1981
|
+
prose = `${reason}(下一步:${next})`;
|
|
1982
|
+
} else if (reason !== undefined) {
|
|
1983
|
+
prose = reason;
|
|
1984
|
+
} else if (next !== undefined) {
|
|
1985
|
+
// Historical recommendation with only typed next — still real advice.
|
|
1986
|
+
prose = `下一步:${next}`;
|
|
1987
|
+
} else if (command !== undefined) {
|
|
1988
|
+
prose = command;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
if (prose === undefined || prose.trim() === "") {
|
|
1992
|
+
// Attended but empty body is affirmative no-advice, not unavailable (#959).
|
|
1966
1993
|
return {
|
|
1967
|
-
disposition: "
|
|
1968
|
-
|
|
1969
|
-
reason: "navigator recommendation missing typed next role",
|
|
1994
|
+
disposition: "no-advice",
|
|
1995
|
+
...advisoryDiagnostic,
|
|
1970
1996
|
};
|
|
1971
1997
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
? details.route
|
|
1975
|
-
.filter(isRecord)
|
|
1976
|
-
.map((target) => ({
|
|
1977
|
-
role: String(target.role),
|
|
1978
|
-
phase: navigatorPhaseValue(target.phase),
|
|
1979
|
-
}))
|
|
1980
|
-
: undefined;
|
|
1981
|
-
return recommendationNavigatorFact({
|
|
1998
|
+
return adviceNavigatorFact({
|
|
1999
|
+
prose,
|
|
1982
2000
|
...advisoryDiagnostic,
|
|
1983
|
-
next: {
|
|
1984
|
-
role: next.role,
|
|
1985
|
-
phase: navigatorPhaseValue(next.phase),
|
|
1986
|
-
},
|
|
1987
|
-
reason,
|
|
1988
|
-
...(route === undefined ? {} : { route }),
|
|
1989
|
-
...(typeof details.command === "string"
|
|
1990
|
-
? { modelCommand: details.command }
|
|
1991
|
-
: {}),
|
|
1992
2001
|
});
|
|
1993
2002
|
}
|
|
1994
2003
|
if (disposition === "unavailable") {
|
|
@@ -3634,7 +3643,6 @@ export async function trySettleReviewerTerminalResult(
|
|
|
3634
3643
|
return settleLawfulReviewerTerminalResult(admitted, authority, options, scope);
|
|
3635
3644
|
}
|
|
3636
3645
|
|
|
3637
|
-
|
|
3638
3646
|
/**
|
|
3639
3647
|
* Observe forced Merger resolving-merge-conflicts Skill expansions from the session.
|
|
3640
3648
|
* Expansion evidence is package-path only; ambient home locations never count.
|