@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
|
@@ -436,12 +436,12 @@ const REVIEWER_OPTIONS = [
|
|
|
436
436
|
canonical: "--lens",
|
|
437
437
|
aliases: [],
|
|
438
438
|
valueMetavar: "completeness|correctness",
|
|
439
|
-
required:
|
|
439
|
+
required: false,
|
|
440
440
|
repeatable: false,
|
|
441
441
|
form: "option",
|
|
442
442
|
description: {
|
|
443
|
-
en: "
|
|
444
|
-
zh: "
|
|
443
|
+
en: "Optional single-lens override: completeness or correctness; omitted runs both in parallel.",
|
|
444
|
+
zh: "可选单 lens 覆盖:completeness 或 correctness;省略时并行运行两轴。",
|
|
445
445
|
},
|
|
446
446
|
},
|
|
447
447
|
{
|
|
@@ -997,9 +997,9 @@ const ROLE_COMMAND_HELP = {
|
|
|
997
997
|
},
|
|
998
998
|
reviewer: {
|
|
999
999
|
command: "reviewer",
|
|
1000
|
-
summary: "Fixed-target
|
|
1000
|
+
summary: "Fixed-target parallel two-lens review, with an optional single-lens override.",
|
|
1001
1001
|
usage: [
|
|
1002
|
-
"ak-role reviewer --base <revision> --lens completeness|correctness --authority-ref <ref> [options] <instruction>",
|
|
1002
|
+
"ak-role reviewer --base <revision> [--lens completeness|correctness] --authority-ref <ref> [options] <instruction>",
|
|
1003
1003
|
],
|
|
1004
1004
|
examples: [
|
|
1005
1005
|
'ak-role reviewer --base main --lens completeness --authority-ref docs/adr/0001.md "Review the branch."',
|
|
@@ -1050,7 +1050,7 @@ const ROLE_COMMAND_HELP = {
|
|
|
1050
1050
|
},
|
|
1051
1051
|
navigator: {
|
|
1052
1052
|
command: "navigator",
|
|
1053
|
-
summary: "Direct Navigator (游奕使) route advice
|
|
1053
|
+
summary: "Direct Navigator (游奕使) free-form prose route advice.",
|
|
1054
1054
|
usage: ["ak-role navigator [options] [instruction]"],
|
|
1055
1055
|
examples: [
|
|
1056
1056
|
'ak-role navigator "刚完成 coder apply 收敛,下一步?"',
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { writeFile } from "node:fs/promises";
|
|
10
10
|
import { isAbsolute, join, resolve } from "node:path";
|
|
11
|
-
import { buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
11
|
+
import { buildAutoResumeContinuationPrompt, buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
12
12
|
import { CliUsageError } from "./cli-errors.js";
|
|
13
13
|
import { bindAdmittedTicketNumber, buildInstructionTransportPrompt, freezeAttachmentsIntoRun, } from "./invocation.js";
|
|
14
14
|
import { readRecordedSubmissionRows } from "../submission-ledger.js";
|
|
@@ -878,6 +878,9 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
878
878
|
}
|
|
879
879
|
adapters = prepared.adapters;
|
|
880
880
|
}
|
|
881
|
+
// Call-local execution env; afterAdmittedPrepare may replace it (Reviewer fresh-copy).
|
|
882
|
+
let env = input.env;
|
|
883
|
+
let preparedCleanup;
|
|
881
884
|
const buildRequestAfterLease = async () => {
|
|
882
885
|
let openCourtAttemptId;
|
|
883
886
|
// Build uses the admitted judged under this lease (rehydrated when open
|
|
@@ -945,8 +948,15 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
945
948
|
// Court recovery / open under lease, then dispatch.
|
|
946
949
|
// Station-child same-ticket/same-parent resume is call-local auto-resume
|
|
947
950
|
// (#840 / #416). Public `ak-role resume` stays one-shot (ADR 0080).
|
|
951
|
+
// afterAdmittedPrepare runs inside this try so mint failure and cleanup share one finally.
|
|
948
952
|
try {
|
|
949
|
-
if (input.
|
|
953
|
+
if (input.afterAdmittedPrepare !== undefined) {
|
|
954
|
+
const prepared = await input.afterAdmittedPrepare(loaded.admitted);
|
|
955
|
+
if (prepared.env !== undefined)
|
|
956
|
+
env = prepared.env;
|
|
957
|
+
preparedCleanup = prepared.cleanup;
|
|
958
|
+
}
|
|
959
|
+
if (env.stationChild === true) {
|
|
950
960
|
let firstTurn;
|
|
951
961
|
const stationAdapters = withOnceSuccessfulBeforeDispatch(adapters);
|
|
952
962
|
// One public call → one detour scope across in-place auto-resume dispatches.
|
|
@@ -957,11 +967,11 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
957
967
|
});
|
|
958
968
|
return await runWithAutoResumeLoop({
|
|
959
969
|
admitted: loaded.admitted,
|
|
960
|
-
principalAuthority:
|
|
961
|
-
isPrincipalAvailable: resolveHostAwareSessionAvailability(
|
|
970
|
+
principalAuthority: env.principalAuthority,
|
|
971
|
+
isPrincipalAvailable: resolveHostAwareSessionAvailability(env.host, env.principalAuthority),
|
|
962
972
|
io: input.io,
|
|
963
|
-
sessionAppender:
|
|
964
|
-
autoResumeLimit:
|
|
973
|
+
sessionAppender: env.sessionAppender,
|
|
974
|
+
autoResumeLimit: env.autoResumeLimit,
|
|
965
975
|
buildInitialPayload: () => ({ resumeTurn: false }),
|
|
966
976
|
buildResumePayload: () => ({ resumeTurn: true }),
|
|
967
977
|
// Same as public manual resume: prior-court sealed acceptance is not a
|
|
@@ -995,7 +1005,7 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
995
1005
|
dispatch: async (turnRequest) => {
|
|
996
1006
|
const result = await dispatchPostAdmissionTurn({
|
|
997
1007
|
admitted: loaded.admitted,
|
|
998
|
-
env
|
|
1008
|
+
env,
|
|
999
1009
|
io: attemptIo,
|
|
1000
1010
|
request: turnRequest,
|
|
1001
1011
|
lease,
|
|
@@ -1005,14 +1015,14 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
1005
1015
|
? {}
|
|
1006
1016
|
: { effectiveEngine: input.effectiveEngine }),
|
|
1007
1017
|
});
|
|
1008
|
-
return settleDeferredPersist(loaded.admitted,
|
|
1018
|
+
return settleDeferredPersist(loaded.admitted, env.principalAuthority, stationAdapters, attemptIo, result);
|
|
1009
1019
|
},
|
|
1010
1020
|
}),
|
|
1011
1021
|
});
|
|
1012
1022
|
}
|
|
1013
1023
|
return await runPostAdmissionManualResume({
|
|
1014
1024
|
admitted: loaded.admitted,
|
|
1015
|
-
env
|
|
1025
|
+
env,
|
|
1016
1026
|
io: input.io,
|
|
1017
1027
|
adapters,
|
|
1018
1028
|
...(input.effectiveEngine === undefined
|
|
@@ -1030,6 +1040,11 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
1030
1040
|
}
|
|
1031
1041
|
throw error;
|
|
1032
1042
|
}
|
|
1043
|
+
finally {
|
|
1044
|
+
if (preparedCleanup !== undefined) {
|
|
1045
|
+
await preparedCleanup();
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1033
1048
|
}
|
|
1034
1049
|
/**
|
|
1035
1050
|
* Shared post-admission one-shot path: folds into runPostAdmissionResumable (#840 / #416).
|
|
@@ -1047,7 +1062,7 @@ export async function runPostAdmissionOneShot(input) {
|
|
|
1047
1062
|
...input.request,
|
|
1048
1063
|
continuation: {
|
|
1049
1064
|
kind: "resume",
|
|
1050
|
-
prompt:
|
|
1065
|
+
prompt: buildAutoResumeContinuationPrompt({
|
|
1051
1066
|
packageRoot: input.env.packageRoot,
|
|
1052
1067
|
...pickEngineAxis({
|
|
1053
1068
|
engine: input.effectiveEngine ?? input.env.engine,
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Reviewer Role run: admit → post-admission coordinator → settle Terminal result (#917 / #517).
|
|
3
|
+
* Package-owned ak-cross-m-review method is forced; users never submit
|
|
4
|
+
* extra packets. Controlled-failure settlement reuses #107.
|
|
5
|
+
* #526: execution via RoleTurnHost; argv is Pi adapter internal.
|
|
6
|
+
*/
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { appendEngineSessionMaterial, engineSessionMaterialFromOptions, pickEngineAxis, } from "../package-resources/engine-material.js";
|
|
9
|
+
import { loadPackagedMethodSkillMaterial, resolvePackagedMethodSkillPath, } from "../package-resources/method-skill.js";
|
|
10
|
+
import { CliUsageError } from "./cli-errors.js";
|
|
11
|
+
import { admitReviewerInvocation, buildReviewerTransportPrompt, } from "./invocation.js";
|
|
12
|
+
import { loadResumableReviewerRun, markRunAdmitted, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
13
|
+
import { presentStructuralRejection, readEngineDetourInfrastructureFailure, trySettleReviewerTerminalResult, } from "./settlement.js";
|
|
14
|
+
import { formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
|
|
15
|
+
import { projectRoleTurnRequest, } from "./turn-request.js";
|
|
16
|
+
import { presentControlledFailure, resolveResumeMethodMaterialAdapters, runPostAdmissionResumable, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
|
|
17
|
+
function reviewerMethods(packageRoot) {
|
|
18
|
+
return [{ kind: "skill", path: resolvePackagedMethodSkillPath(packageRoot, "ak-cross-m-review") }];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* All Reviewer turns (explicit --lens, dual-lens child, resume) execute in a
|
|
22
|
+
* fresh worktree of the source tree's current HEAD (#946 统一新副本). Dual-lens
|
|
23
|
+
* summon already injects executionCwd; only mint when absent. Cleanup failure
|
|
24
|
+
* is diagnostic only and does not flip the exit code. Mint failure propagates
|
|
25
|
+
* so the caller can settle a controlled failure against the admitted run.
|
|
26
|
+
*/
|
|
27
|
+
async function runReviewerTurnInFreshCopy(env, projectRoot, io, body) {
|
|
28
|
+
if (env.executionCwd !== undefined) {
|
|
29
|
+
return body(env);
|
|
30
|
+
}
|
|
31
|
+
const { withEphemeralReviewerWorktree } = await import("../public-role-summons.js");
|
|
32
|
+
return await withEphemeralReviewerWorktree({
|
|
33
|
+
projectRoot,
|
|
34
|
+
onCleanupDiagnostic: (diagnostic) => {
|
|
35
|
+
io.stderr(`${diagnostic}\n`);
|
|
36
|
+
},
|
|
37
|
+
run: (executionCwd) => body({ ...env, executionCwd }),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** Project admitted Reviewer invocation onto the host-neutral turn request. */
|
|
41
|
+
export function buildReviewerTurnRequest(admitted, options) {
|
|
42
|
+
return projectRoleTurnRequest(admitted, {
|
|
43
|
+
activation: {
|
|
44
|
+
role: "reviewer",
|
|
45
|
+
baseRevision: admitted.baseRevision,
|
|
46
|
+
lens: admitted.lens,
|
|
47
|
+
authorityRefs: admitted.authorityRefs,
|
|
48
|
+
...(admitted.ticketNumber === undefined ? {} : { ticketNumber: admitted.ticketNumber }),
|
|
49
|
+
},
|
|
50
|
+
methods: reviewerMethods(options.packageRoot),
|
|
51
|
+
}, options);
|
|
52
|
+
}
|
|
53
|
+
function reviewerAdapters(packageRoot, methodMaterial) {
|
|
54
|
+
return {
|
|
55
|
+
trySettle: (admitted, authority, scope) => methodMaterial === undefined
|
|
56
|
+
? Promise.resolve(undefined)
|
|
57
|
+
: trySettleReviewerTerminalResult(admitted, authority, {
|
|
58
|
+
methodProvenance: methodMaterial.provenance,
|
|
59
|
+
methodSkillPath: methodMaterial.skillPath,
|
|
60
|
+
methodSkillConfiguredPath: resolvePackagedMethodSkillPath(packageRoot, "ak-cross-m-review"),
|
|
61
|
+
}, scope),
|
|
62
|
+
resolveRunnerKnownFailure: async ({ result, sessionFile }) => {
|
|
63
|
+
const infrastructureFailure = await readEngineDetourInfrastructureFailure(sessionFile);
|
|
64
|
+
return infrastructureFailure === undefined
|
|
65
|
+
? result.knownFailure
|
|
66
|
+
: {
|
|
67
|
+
...(infrastructureFailure.cause === undefined
|
|
68
|
+
? {}
|
|
69
|
+
: { cause: infrastructureFailure.cause }),
|
|
70
|
+
diagnostic: infrastructureFailure.diagnostic,
|
|
71
|
+
...(infrastructureFailure.identity === undefined
|
|
72
|
+
? {}
|
|
73
|
+
: { identity: infrastructureFailure.identity }),
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function loadReviewerMethodMaterial(packageRoot) {
|
|
79
|
+
return await loadPackagedMethodSkillMaterial(packageRoot, "ak-cross-m-review");
|
|
80
|
+
}
|
|
81
|
+
/** Continue the existing method turn; frozen axes remain on typed activation fields. */
|
|
82
|
+
function reviewerResumePrompt(env, message) {
|
|
83
|
+
const lines = [RESUME_TRANSPORT_ENVELOPE];
|
|
84
|
+
if (message !== undefined)
|
|
85
|
+
lines.push("", message);
|
|
86
|
+
return appendEngineSessionMaterial(lines, engineSessionMaterialFromOptions({
|
|
87
|
+
...pickEngineAxis(env),
|
|
88
|
+
packageRoot: env.packageRoot,
|
|
89
|
+
})).join("\n");
|
|
90
|
+
}
|
|
91
|
+
export async function runPublicReviewer(argv, env, io, parseReviewerArgv) {
|
|
92
|
+
let parsed;
|
|
93
|
+
try {
|
|
94
|
+
parsed = parseReviewerArgv(argv);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error instanceof CliUsageError) {
|
|
98
|
+
presentStructuralRejection(error, io);
|
|
99
|
+
return { exitCode: 2 };
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
// Omitted public lens is the parallel two-axis branch mark; never admitted as a parent run.
|
|
104
|
+
// Each leg reuses this call's public argv and only adds --lens (#946 / 10a).
|
|
105
|
+
if (parsed.lens === undefined) {
|
|
106
|
+
const { summonParallelReviewerLenses } = await import("../public-role-summons.js");
|
|
107
|
+
const children = await summonParallelReviewerLenses({
|
|
108
|
+
argv,
|
|
109
|
+
// Replay argv under the same cwd the single-axis entry would see (10a).
|
|
110
|
+
// Do not substitute the resolved project path — relative --project must
|
|
111
|
+
// not be re-resolved against a shifted cwd.
|
|
112
|
+
cwd: env.cwd,
|
|
113
|
+
projectRoot: resolve(parsed.project ?? env.cwd),
|
|
114
|
+
// Typed base from the public parse — precheck only; child argv stays verbatim.
|
|
115
|
+
baseRevision: parsed.baseRevision,
|
|
116
|
+
home: env.home,
|
|
117
|
+
agentDir: env.agentDir,
|
|
118
|
+
...(env.credentials === undefined ? {} : { credentials: env.credentials }),
|
|
119
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
120
|
+
...(env.host === undefined ? {} : { host: env.host }),
|
|
121
|
+
...(env.engine === undefined ? {} : { engine: env.engine }),
|
|
122
|
+
...(env.engineModel === undefined ? {} : { engineModel: env.engineModel }),
|
|
123
|
+
packageRoot: env.packageRoot,
|
|
124
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
125
|
+
...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
|
|
126
|
+
roleTurnHost: env.roleTurnHost,
|
|
127
|
+
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
128
|
+
principalAuthority: env.principalAuthority,
|
|
129
|
+
...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
|
|
130
|
+
});
|
|
131
|
+
const childResults = [children.completeness, children.correctness];
|
|
132
|
+
const terminals = childResults
|
|
133
|
+
.map((child) => child.terminal)
|
|
134
|
+
.filter((terminal) => terminal !== undefined);
|
|
135
|
+
// ADR 0052 / terminal.ts: lawful typed child terminals (accepted / no_receipt /
|
|
136
|
+
// audit_escalation) are not batch failures. Child exitCode remains the live
|
|
137
|
+
// surface for seal/infrastructure overlays that keep the original Terminal.
|
|
138
|
+
// failedChildren counts only non-lawful/missing child Terminals; seal overlays
|
|
139
|
+
// that flip exitCode while keeping lawful Terminals are a separate identity.
|
|
140
|
+
const failedChildren = childResults.filter((child) => child.terminal === undefined
|
|
141
|
+
|| !isLawfulTypedTerminalOutcome(child.terminal.roleOutcome)).length;
|
|
142
|
+
const overlayExit = childResults.some((child) => child.exitCode !== 0);
|
|
143
|
+
const failed = failedChildren > 0 || overlayExit;
|
|
144
|
+
const overlayDiagnostics = [...new Set(childResults
|
|
145
|
+
.map((child) => child.stderr)
|
|
146
|
+
.filter((text) => typeof text === "string" && text !== ""))];
|
|
147
|
+
const terminal = {
|
|
148
|
+
batch: "reviewer",
|
|
149
|
+
roleOutcome: failed
|
|
150
|
+
? {
|
|
151
|
+
kind: "failure",
|
|
152
|
+
role: "reviewer",
|
|
153
|
+
diagnostic: failedChildren > 0
|
|
154
|
+
? "Reviewer batch child failure"
|
|
155
|
+
: (overlayDiagnostics[0] ?? "Reviewer batch infrastructure failure"),
|
|
156
|
+
decisiveFacts: { failedChildren },
|
|
157
|
+
payloads: terminals,
|
|
158
|
+
}
|
|
159
|
+
: { kind: "accepted", role: "reviewer", payloads: terminals },
|
|
160
|
+
reviewerChildren: {
|
|
161
|
+
...(children.completeness.terminal === undefined ? {} : { completeness: children.completeness.terminal }),
|
|
162
|
+
...(children.correctness.terminal === undefined ? {} : { correctness: children.correctness.terminal }),
|
|
163
|
+
},
|
|
164
|
+
reviewerChildOutcomes: {
|
|
165
|
+
completeness: { exitCode: children.completeness.exitCode, ...(children.completeness.stderr === undefined ? {} : { stderr: children.completeness.stderr }) },
|
|
166
|
+
correctness: { exitCode: children.correctness.exitCode, ...(children.correctness.stderr === undefined ? {} : { stderr: children.correctness.stderr }) },
|
|
167
|
+
},
|
|
168
|
+
// Batch has no parent run and no own attendance; no-advice is affirmative
|
|
169
|
+
// only (navigator-attendance.ts). Children carry their own navigator facts.
|
|
170
|
+
navigator: {
|
|
171
|
+
disposition: "unavailable",
|
|
172
|
+
source: "unknown",
|
|
173
|
+
reason: "Reviewer batch has no parent-run Navigator attendance",
|
|
174
|
+
},
|
|
175
|
+
artifacts: terminals.flatMap((item) => item.artifacts),
|
|
176
|
+
};
|
|
177
|
+
io.stdout(formatTerminalResult(terminal));
|
|
178
|
+
return { exitCode: failed ? 1 : 0, terminal };
|
|
179
|
+
}
|
|
180
|
+
let admitted;
|
|
181
|
+
try {
|
|
182
|
+
admitted = await admitReviewerInvocation({
|
|
183
|
+
home: env.home,
|
|
184
|
+
principalAuthority: env.principalAuthority,
|
|
185
|
+
cwd: env.cwd,
|
|
186
|
+
instruction: parsed.instruction,
|
|
187
|
+
attachmentPaths: parsed.attachmentPaths,
|
|
188
|
+
baseRevision: parsed.baseRevision,
|
|
189
|
+
lens: parsed.lens,
|
|
190
|
+
authorityRefs: parsed.authorityRefs,
|
|
191
|
+
...(parsed.project === undefined ? {} : { project: parsed.project }),
|
|
192
|
+
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
193
|
+
...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
|
|
194
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (error instanceof CliUsageError) {
|
|
199
|
+
presentStructuralRejection(error, io);
|
|
200
|
+
return { exitCode: 2 };
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
await markRunAdmitted(admitted, env.principalAuthority);
|
|
205
|
+
let methodMaterial;
|
|
206
|
+
try {
|
|
207
|
+
methodMaterial = await loadReviewerMethodMaterial(env.packageRoot);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
return (await presentControlledFailure(admitted, {
|
|
211
|
+
timedOut: false,
|
|
212
|
+
code: null,
|
|
213
|
+
stderr: "",
|
|
214
|
+
thrown: error,
|
|
215
|
+
}, reviewerAdapters(env.packageRoot), env.principalAuthority, io));
|
|
216
|
+
}
|
|
217
|
+
// Fresh copy for this call (explicit --lens or dual-lens child with pre-set cwd).
|
|
218
|
+
// Durable projectRoot stays the caller project; only the host turn uses the sandbox.
|
|
219
|
+
try {
|
|
220
|
+
return await runReviewerTurnInFreshCopy(env, admitted.projectRoot, io, async (sandboxedEnv) => await runPostAdmissionResumable({
|
|
221
|
+
admitted,
|
|
222
|
+
env: sandboxedEnv,
|
|
223
|
+
io,
|
|
224
|
+
buildInitialRequest: () => buildReviewerTurnRequest(admitted, {
|
|
225
|
+
packageRoot: sandboxedEnv.packageRoot,
|
|
226
|
+
home: sandboxedEnv.home,
|
|
227
|
+
agentDir: sandboxedEnv.agentDir,
|
|
228
|
+
...(sandboxedEnv.model === undefined ? {} : { model: sandboxedEnv.model }),
|
|
229
|
+
...pickEngineAxis(sandboxedEnv),
|
|
230
|
+
...(sandboxedEnv.timeoutMs === undefined ? {} : { timeoutMs: sandboxedEnv.timeoutMs }),
|
|
231
|
+
...(admitted.correlationId === undefined && sandboxedEnv.correlationId === undefined
|
|
232
|
+
? {}
|
|
233
|
+
: { correlationId: admitted.correlationId ?? sandboxedEnv.correlationId }),
|
|
234
|
+
// Ephemeral worktree cwd; durable projectRoot stays on admitted caller project.
|
|
235
|
+
...(sandboxedEnv.executionCwd === undefined ? {} : { cwd: sandboxedEnv.executionCwd }),
|
|
236
|
+
continuation: {
|
|
237
|
+
kind: "initial",
|
|
238
|
+
prompt: buildReviewerTransportPrompt(admitted, engineSessionMaterialFromOptions({
|
|
239
|
+
...pickEngineAxis(sandboxedEnv),
|
|
240
|
+
packageRoot: sandboxedEnv.packageRoot,
|
|
241
|
+
})),
|
|
242
|
+
},
|
|
243
|
+
}),
|
|
244
|
+
buildResumeRequest: () => buildReviewerTurnRequest(admitted, {
|
|
245
|
+
packageRoot: sandboxedEnv.packageRoot,
|
|
246
|
+
home: sandboxedEnv.home,
|
|
247
|
+
agentDir: sandboxedEnv.agentDir,
|
|
248
|
+
...(sandboxedEnv.model === undefined ? {} : { model: sandboxedEnv.model }),
|
|
249
|
+
...pickEngineAxis(sandboxedEnv),
|
|
250
|
+
...(sandboxedEnv.timeoutMs === undefined ? {} : { timeoutMs: sandboxedEnv.timeoutMs }),
|
|
251
|
+
...(admitted.correlationId === undefined && sandboxedEnv.correlationId === undefined
|
|
252
|
+
? {}
|
|
253
|
+
: { correlationId: admitted.correlationId ?? sandboxedEnv.correlationId }),
|
|
254
|
+
// In-batch auto-resume keeps the same call's sandbox (dual-lens or single).
|
|
255
|
+
...(sandboxedEnv.executionCwd === undefined ? {} : { cwd: sandboxedEnv.executionCwd }),
|
|
256
|
+
continuation: {
|
|
257
|
+
kind: "resume",
|
|
258
|
+
prompt: reviewerResumePrompt(sandboxedEnv),
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
adapters: reviewerAdapters(sandboxedEnv.packageRoot, methodMaterial),
|
|
262
|
+
...(sandboxedEnv.engine === undefined ? {} : { effectiveEngine: sandboxedEnv.engine }),
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
return (await presentControlledFailure(admitted, {
|
|
267
|
+
timedOut: false,
|
|
268
|
+
code: null,
|
|
269
|
+
stderr: "",
|
|
270
|
+
thrown: error,
|
|
271
|
+
}, reviewerAdapters(env.packageRoot, methodMaterial), env.principalAuthority, io));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Resume a previously admitted Reviewer Role run after a typed HTTP 429.
|
|
276
|
+
* Restores task/base/session identity; model override is temporary.
|
|
277
|
+
* Execution always uses a fresh worktree of the source tree at resume time
|
|
278
|
+
* (#946 统一新副本 / 10a) — no old worktree, no ownership record.
|
|
279
|
+
* Fresh-copy mint reuses the coordinator's single pre-lease load via
|
|
280
|
+
* afterAdmittedPrepare; does not pre-load outside the coordinator.
|
|
281
|
+
*/
|
|
282
|
+
export async function runPublicReviewerResume(request, env, io) {
|
|
283
|
+
// Call-local cell: afterAdmittedPrepare writes executionCwd; buildTurnRequest reads it.
|
|
284
|
+
// Keeps the single-load coordinator contract — no preliminary load outside.
|
|
285
|
+
const sandbox = {
|
|
286
|
+
...(env.executionCwd === undefined ? {} : { executionCwd: env.executionCwd }),
|
|
287
|
+
};
|
|
288
|
+
return await runPostAdmissionSeatResume({
|
|
289
|
+
request,
|
|
290
|
+
env,
|
|
291
|
+
io,
|
|
292
|
+
load: (effective) => loadResumableReviewerRun(env.home, effective.runId, env.principalAuthority),
|
|
293
|
+
buildTurnRequest: (admitted, effective) => {
|
|
294
|
+
const activeEnv = sandbox.executionCwd === undefined
|
|
295
|
+
? env
|
|
296
|
+
: { ...env, executionCwd: sandbox.executionCwd };
|
|
297
|
+
const base = resumeTurnRequestProjectionOptions(admitted, effective, activeEnv);
|
|
298
|
+
return buildReviewerTurnRequest(admitted, {
|
|
299
|
+
...base,
|
|
300
|
+
// Fresh copy at resume time; durable projectRoot unchanged.
|
|
301
|
+
...(sandbox.executionCwd === undefined ? {} : { cwd: sandbox.executionCwd }),
|
|
302
|
+
continuation: {
|
|
303
|
+
kind: "resume",
|
|
304
|
+
prompt: reviewerResumePrompt(activeEnv, effective.message),
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
adapters: reviewerAdapters(env.packageRoot),
|
|
309
|
+
afterAdmittedLoad: async (admitted) => {
|
|
310
|
+
return resolveResumeMethodMaterialAdapters({
|
|
311
|
+
admitted,
|
|
312
|
+
authority: env.principalAuthority,
|
|
313
|
+
io,
|
|
314
|
+
loadMaterial: () => loadReviewerMethodMaterial(env.packageRoot),
|
|
315
|
+
adaptersWith: (material) => reviewerAdapters(env.packageRoot, material),
|
|
316
|
+
emptyAdapters: reviewerAdapters(env.packageRoot),
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
afterAdmittedPrepare: async (admitted) => {
|
|
320
|
+
// Already sandboxed (in-batch auto-resume path) — nothing to mint.
|
|
321
|
+
if (sandbox.executionCwd !== undefined) {
|
|
322
|
+
return { env: { ...env, executionCwd: sandbox.executionCwd } };
|
|
323
|
+
}
|
|
324
|
+
const { openEphemeralReviewerWorktree } = await import("../public-role-summons.js");
|
|
325
|
+
const opened = await openEphemeralReviewerWorktree({
|
|
326
|
+
projectRoot: admitted.projectRoot,
|
|
327
|
+
onCleanupDiagnostic: (diagnostic) => {
|
|
328
|
+
io.stderr(`${diagnostic}\n`);
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
sandbox.executionCwd = opened.executionCwd;
|
|
332
|
+
return {
|
|
333
|
+
env: { ...env, executionCwd: opened.executionCwd },
|
|
334
|
+
cleanup: opened.close,
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
@@ -26,20 +26,31 @@ export const V1_RESUMABLE_PROVIDERS = ["openai-codex", "xai"];
|
|
|
26
26
|
* source: runWithAutoResumeLoop receives the effective value once per call.
|
|
27
27
|
*/
|
|
28
28
|
export const AUTO_RESUME_LIMIT = 2;
|
|
29
|
-
/** Package-owned turn trigger for resume. Not caller instruction and not semantic task content. */
|
|
30
|
-
export const RESUME_TRANSPORT_ENVELOPE = "[ak-role:resume-continue]";
|
|
31
29
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* Package-owned non-empty Chinese neutral resume transport (#959 / ADR 0073).
|
|
31
|
+
* Used by:
|
|
32
|
+
* - auto-resume (all seats via buildAutoResumeContinuationPrompt) — required so
|
|
33
|
+
* hosts that reject empty stdin (codex) still receive a prompt;
|
|
34
|
+
* - reviewer seat manual resume (reviewerResumePrompt) — same non-empty need on
|
|
35
|
+
* that seat's own manual entry.
|
|
36
|
+
* Generic bare `ak-role resume` stays empty-capable via selectResumeContinuationPrompt
|
|
37
|
+
* / buildResumeContinuationPrompt — ADR 0080 keeps auto and generic-manual entries separate.
|
|
38
|
+
*/
|
|
39
|
+
export const RESUME_TRANSPORT_ENVELOPE = "继续。";
|
|
40
|
+
/**
|
|
41
|
+
* Manual resume continuation selector (#471 / #600 / #736 / ADR 0080).
|
|
42
|
+
* Message present → those bytes; absent → engine pointers only (may be empty).
|
|
43
|
+
* Caller message (including blank) still wins verbatim when supplied.
|
|
44
|
+
* Auto-resume must use buildAutoResumeContinuationPrompt — do not fold the
|
|
45
|
+
* non-empty Chinese envelope into this shared manual selector (#959).
|
|
35
46
|
*/
|
|
36
47
|
export function selectResumeContinuationPrompt(message, engineMaterial) {
|
|
37
48
|
const lines = message !== undefined ? [message] : [];
|
|
38
49
|
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
39
50
|
}
|
|
40
51
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
52
|
+
* Manual resume continuation with engine material from the seat env (#600).
|
|
53
|
+
* Bare manual resume stays empty-prompt-capable; auto-resume is a separate entry.
|
|
43
54
|
*/
|
|
44
55
|
export function buildResumeContinuationPrompt(options) {
|
|
45
56
|
return selectResumeContinuationPrompt(options.message, engineSessionMaterialFromOptions({
|
|
@@ -47,6 +58,17 @@ export function buildResumeContinuationPrompt(options) {
|
|
|
47
58
|
...pickEngineAxis(options),
|
|
48
59
|
}));
|
|
49
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Auto-resume continuation only (#959 / ADR 0080).
|
|
63
|
+
* Always non-empty: Chinese neutral envelope plus optional engine pointers.
|
|
64
|
+
* Never call this from manual `ak-role resume`.
|
|
65
|
+
*/
|
|
66
|
+
export function buildAutoResumeContinuationPrompt(options) {
|
|
67
|
+
return selectResumeContinuationPrompt(RESUME_TRANSPORT_ENVELOPE, engineSessionMaterialFromOptions({
|
|
68
|
+
packageRoot: options.packageRoot,
|
|
69
|
+
...pickEngineAxis(options),
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
50
72
|
const RUN_STATE_FILE = "run-state.json";
|
|
51
73
|
const WRITER_LOCK_FILE = "writer.lock";
|
|
52
74
|
/** @deprecated #416: 429-only classification removed; kept for compatibility. */
|
|
@@ -157,7 +157,7 @@ async function closedLedgerOutcome(admitted, role, scope) {
|
|
|
157
157
|
function coordinatesFromAdmitted(authority, admitted) {
|
|
158
158
|
return authority.decode(admitted.principal);
|
|
159
159
|
}
|
|
160
|
-
import { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome,
|
|
160
|
+
import { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, adviceNavigatorFact, } from "./terminal.js";
|
|
161
161
|
export { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, };
|
|
162
162
|
/**
|
|
163
163
|
* Host stderr as recorded — full bytes, no flood filter, no char clip (#836).
|
|
@@ -457,7 +457,13 @@ export function explicitInternalKnownFailureClassificationInput(failure) {
|
|
|
457
457
|
...(failure.details === undefined ? {} : { knownDetails: failure.details }),
|
|
458
458
|
};
|
|
459
459
|
}
|
|
460
|
-
/**
|
|
460
|
+
/**
|
|
461
|
+
* Post-role Navigator delivery grace (Issue #11 / #101 / #106 / #159).
|
|
462
|
+
* After the parent finishes: wait at most this long for navigator output, then
|
|
463
|
+
* stop waiting. Navigator must already be running from parent start (prepare
|
|
464
|
+
* host round in parallel); this window is only the tail after parent end — not
|
|
465
|
+
* the budget to start a cold full host turn (owner 2026-09-17 #959).
|
|
466
|
+
*/
|
|
461
467
|
export const NAVIGATOR_POST_ROLE_GRACE_MS = 10_000;
|
|
462
468
|
function isMissingPathError(error) {
|
|
463
469
|
return (error instanceof Error &&
|
|
@@ -1334,11 +1340,6 @@ export async function appendRunAttemptHistory(source, outcome) {
|
|
|
1334
1340
|
}
|
|
1335
1341
|
catch { }
|
|
1336
1342
|
}
|
|
1337
|
-
function navigatorPhaseValue(value) {
|
|
1338
|
-
if (value === "plan" || value === "apply")
|
|
1339
|
-
return value;
|
|
1340
|
-
return null;
|
|
1341
|
-
}
|
|
1342
1343
|
/**
|
|
1343
1344
|
* Minimal attendance provenance against the bound marker (ADR 0043).
|
|
1344
1345
|
* Keep only invocationId + post-terminal ordering. Runtime-self-produced
|
|
@@ -1357,35 +1358,48 @@ function parseNavigatorAttendanceDetails(details) {
|
|
|
1357
1358
|
const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string"
|
|
1358
1359
|
? { advisoryDiagnostic: details.routePlaybookReadFailure }
|
|
1359
1360
|
: {};
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1361
|
+
// #959: advice prose is presented as-is. Legacy "recommendation" with next/reason/
|
|
1362
|
+
// command is projected into prose so historical sessions still render — never wash
|
|
1363
|
+
// a real recommendation into no-advice when any advice body is recoverable.
|
|
1364
|
+
if (disposition === "advice" || disposition === "recommendation") {
|
|
1365
|
+
let prose;
|
|
1366
|
+
if (typeof details.prose === "string" && details.prose.trim() !== "") {
|
|
1367
|
+
prose = details.prose;
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
const next = isRecord(details.next) && typeof details.next.role === "string"
|
|
1371
|
+
? details.next.role
|
|
1372
|
+
: undefined;
|
|
1373
|
+
const reason = typeof details.reason === "string" && details.reason.trim() !== ""
|
|
1374
|
+
? details.reason
|
|
1375
|
+
: undefined;
|
|
1376
|
+
const command = typeof details.command === "string" && details.command.trim() !== ""
|
|
1377
|
+
? details.command
|
|
1378
|
+
: undefined;
|
|
1379
|
+
if (reason !== undefined && next !== undefined) {
|
|
1380
|
+
prose = `${reason}(下一步:${next})`;
|
|
1381
|
+
}
|
|
1382
|
+
else if (reason !== undefined) {
|
|
1383
|
+
prose = reason;
|
|
1384
|
+
}
|
|
1385
|
+
else if (next !== undefined) {
|
|
1386
|
+
// Historical recommendation with only typed next — still real advice.
|
|
1387
|
+
prose = `下一步:${next}`;
|
|
1388
|
+
}
|
|
1389
|
+
else if (command !== undefined) {
|
|
1390
|
+
prose = command;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (prose === undefined || prose.trim() === "") {
|
|
1394
|
+
// Attended but empty body is affirmative no-advice, not unavailable (#959).
|
|
1363
1395
|
return {
|
|
1364
|
-
disposition: "
|
|
1365
|
-
|
|
1366
|
-
reason: "navigator recommendation missing typed next role",
|
|
1396
|
+
disposition: "no-advice",
|
|
1397
|
+
...advisoryDiagnostic,
|
|
1367
1398
|
};
|
|
1368
1399
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
? details.route
|
|
1372
|
-
.filter(isRecord)
|
|
1373
|
-
.map((target) => ({
|
|
1374
|
-
role: String(target.role),
|
|
1375
|
-
phase: navigatorPhaseValue(target.phase),
|
|
1376
|
-
}))
|
|
1377
|
-
: undefined;
|
|
1378
|
-
return recommendationNavigatorFact({
|
|
1400
|
+
return adviceNavigatorFact({
|
|
1401
|
+
prose,
|
|
1379
1402
|
...advisoryDiagnostic,
|
|
1380
|
-
next: {
|
|
1381
|
-
role: next.role,
|
|
1382
|
-
phase: navigatorPhaseValue(next.phase),
|
|
1383
|
-
},
|
|
1384
|
-
reason,
|
|
1385
|
-
...(route === undefined ? {} : { route }),
|
|
1386
|
-
...(typeof details.command === "string"
|
|
1387
|
-
? { modelCommand: details.command }
|
|
1388
|
-
: {}),
|
|
1389
1403
|
});
|
|
1390
1404
|
}
|
|
1391
1405
|
if (disposition === "unavailable") {
|