@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
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Terminal result: typed semantic regions for one admitted Role run (ADR 0052 / #106).
|
|
3
|
-
* Presentation may rearrange labels/order; only regions and typed facts are stable.
|
|
4
|
-
*
|
|
5
|
-
* Free-text cells are JSON-string encoded so legitimate newlines/tabs cannot forge
|
|
6
|
-
* extra rows or shift column boundaries (note / fix.summary / decision question / reason).
|
|
7
|
-
*/
|
|
8
|
-
import { isPublicCallableRole, renderPublicAkRoleCommand, } from "./command-renderer.js";
|
|
9
1
|
/** Encode one free-text Terminal cell. JSON string form cannot embed raw tab/newline. */
|
|
10
2
|
export function encodeTerminalField(value) {
|
|
11
3
|
return JSON.stringify(value);
|
|
@@ -34,21 +26,13 @@ export function roleResultPayloads(outcome) {
|
|
|
34
26
|
return [];
|
|
35
27
|
}
|
|
36
28
|
/**
|
|
37
|
-
* Build
|
|
38
|
-
*
|
|
29
|
+
* Build an advice navigator fact (#959). Prose is presented as submitted;
|
|
30
|
+
* code does not parse route/next or judge usability.
|
|
39
31
|
*/
|
|
40
|
-
export function
|
|
41
|
-
const command = typeof input.modelCommand === "string" && input.modelCommand.trim() !== ""
|
|
42
|
-
? input.modelCommand
|
|
43
|
-
: isPublicCallableRole(input.next.role)
|
|
44
|
-
? renderPublicAkRoleCommand(input.next)
|
|
45
|
-
: undefined;
|
|
32
|
+
export function adviceNavigatorFact(input) {
|
|
46
33
|
return {
|
|
47
|
-
disposition: "
|
|
48
|
-
|
|
49
|
-
reason: input.reason,
|
|
50
|
-
...(command === undefined ? {} : { command }),
|
|
51
|
-
...(input.route === undefined ? {} : { route: input.route }),
|
|
34
|
+
disposition: "advice",
|
|
35
|
+
prose: input.prose,
|
|
52
36
|
...(input.advisoryDiagnostic === undefined ? {} : { advisoryDiagnostic: input.advisoryDiagnostic }),
|
|
53
37
|
};
|
|
54
38
|
}
|
|
@@ -82,12 +66,9 @@ export function formatTerminalResult(result) {
|
|
|
82
66
|
if (result.navigator.advisoryDiagnostic !== undefined) {
|
|
83
67
|
lines.push(`navigator-advisory\t${encodeTerminalField(result.navigator.advisoryDiagnostic)}`);
|
|
84
68
|
}
|
|
85
|
-
if (result.navigator.disposition === "
|
|
86
|
-
|
|
87
|
-
lines.push(`
|
|
88
|
-
if (result.navigator.command !== undefined) {
|
|
89
|
-
lines.push(`command\t${encodeTerminalField(result.navigator.command)}`);
|
|
90
|
-
}
|
|
69
|
+
if (result.navigator.disposition === "advice") {
|
|
70
|
+
// #959: present navigator prose as-is — no next/reason/command parsing.
|
|
71
|
+
lines.push(`prose\t${encodeTerminalField(result.navigator.prose)}`);
|
|
91
72
|
}
|
|
92
73
|
else if (result.navigator.disposition === "unavailable") {
|
|
93
74
|
lines.push(`unavailable\t${result.navigator.source}\t${encodeTerminalField(result.navigator.reason)}`);
|
|
@@ -107,6 +88,15 @@ export function formatTerminalResult(result) {
|
|
|
107
88
|
lines.push(`gate-round\t${round.roundIndex}\t${round.dispatch.kind}\t${round.dispatch.officer}\t${reason}\t${round.officer.seat}\t${encodeTerminalField(round.officer.status)}\t${encodeTerminalField(JSON.stringify(round.officer.findings))}`);
|
|
108
89
|
}
|
|
109
90
|
}
|
|
91
|
+
if (result.reviewerChildOutcomes !== undefined) {
|
|
92
|
+
for (const axis of ["completeness", "correctness"]) {
|
|
93
|
+
const child = result.reviewerChildOutcomes[axis];
|
|
94
|
+
lines.push(`reviewer-child\t${axis}\t${child.exitCode}`);
|
|
95
|
+
if (child.stderr !== undefined && child.stderr !== "") {
|
|
96
|
+
lines.push(`reviewer-child-diagnostic\t${axis}\t${encodeTerminalField(child.stderr)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
110
100
|
if (result.resume !== undefined) {
|
|
111
101
|
// Resumable failure: run ID is revealed only inside the complete resume command.
|
|
112
102
|
lines.push(`resume\t${encodeTerminalField(result.resume.command)}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { pickEngineAxis } from "../package-resources/engine-material.js";
|
|
2
2
|
export function projectRoleTurnRequest(admitted, roleDetails, options) {
|
|
3
|
-
const cwd = admitted.projectRoot ?? admitted.repoRoot ?? admitted.cwd;
|
|
3
|
+
const cwd = options.cwd ?? admitted.projectRoot ?? admitted.repoRoot ?? admitted.cwd;
|
|
4
4
|
if (cwd === undefined)
|
|
5
5
|
throw new Error("admitted invocation missing working directory");
|
|
6
6
|
if (admitted.principal === undefined)
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
+
import { mkdir, mkdtemp, realpath, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join, relative, sep } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
3
7
|
import { pickEngineAxis } from "./package-resources/engine-material.js";
|
|
4
8
|
const AK_ROLE_PACKAGE_ROOT_ENV = "AK_ROLE_PACKAGE_ROOT";
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
5
10
|
function createCapturingIo() {
|
|
6
11
|
const chunks = [];
|
|
7
12
|
return {
|
|
@@ -89,13 +94,14 @@ async function createSummonEnv(options, afterHost) {
|
|
|
89
94
|
import("./public-cli/role-turn-host-resolution.js"),
|
|
90
95
|
import("./public-cli/config.js")
|
|
91
96
|
]);
|
|
92
|
-
const principalAuthority = piDurablePrincipalAuthority;
|
|
97
|
+
const principalAuthority = options.principalAuthority ?? piDurablePrincipalAuthority;
|
|
93
98
|
const roleTurnHost = resolveRoleTurnHost(
|
|
94
99
|
{
|
|
95
100
|
packageRoot: options.packageRoot,
|
|
96
101
|
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
97
102
|
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
98
|
-
...options.extraPiArgs === void 0 || options.extraPiArgs.length === 0 ? {} : { extraPiArgs: options.extraPiArgs }
|
|
103
|
+
...options.extraPiArgs === void 0 || options.extraPiArgs.length === 0 ? {} : { extraPiArgs: options.extraPiArgs },
|
|
104
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
99
105
|
},
|
|
100
106
|
{ role: options.role, seat: options.seat, principalAuthority }
|
|
101
107
|
);
|
|
@@ -108,13 +114,16 @@ async function createSummonEnv(options, afterHost) {
|
|
|
108
114
|
};
|
|
109
115
|
}
|
|
110
116
|
const hostName = seatWithModel.host ?? "pi";
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
let hostFacingSelection = options.hostFacingModel;
|
|
118
|
+
if (hostFacingSelection === void 0) {
|
|
119
|
+
const { loadHostProvidersTable, projectHostFacingProvider } = await import("./public-cli/host-providers.js");
|
|
120
|
+
hostFacingSelection = projectHostFacingProvider(
|
|
121
|
+
seatWithModel.selection,
|
|
122
|
+
hostName,
|
|
123
|
+
loadHostProvidersTable(options.home),
|
|
124
|
+
options.home
|
|
125
|
+
);
|
|
126
|
+
}
|
|
118
127
|
return {
|
|
119
128
|
ok: true,
|
|
120
129
|
env: {
|
|
@@ -128,7 +137,8 @@ async function createSummonEnv(options, afterHost) {
|
|
|
128
137
|
credentials: options.credentials,
|
|
129
138
|
...hostFacingSelection === void 0 ? {} : { model: hostFacingSelection },
|
|
130
139
|
...projectSeatEngine(seatWithModel),
|
|
131
|
-
...projectSeatHost(seatWithModel)
|
|
140
|
+
...projectSeatHost(seatWithModel),
|
|
141
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
132
142
|
}
|
|
133
143
|
};
|
|
134
144
|
}
|
|
@@ -143,7 +153,19 @@ async function summonPublicRole(options) {
|
|
|
143
153
|
} = await import("./public-cli/config.js");
|
|
144
154
|
const credentials = options.credentials ?? await loadCredentialProviders(agentDir);
|
|
145
155
|
const config = await loadPublicCliConfig(home);
|
|
146
|
-
const
|
|
156
|
+
const resolvedSeat = resolveEffectiveSeat(config, options.role, credentials);
|
|
157
|
+
const inheritedSelection = options.model === void 0 ? void 0 : {
|
|
158
|
+
provider: options.model.provider,
|
|
159
|
+
model: options.model.model,
|
|
160
|
+
...options.model.thinking === void 0 ? {} : { thinking: options.model.thinking }
|
|
161
|
+
};
|
|
162
|
+
const seat = {
|
|
163
|
+
...resolvedSeat,
|
|
164
|
+
...inheritedSelection === void 0 ? {} : { selection: inheritedSelection, source: "invocation" },
|
|
165
|
+
...options.host === void 0 ? {} : { host: options.host, hostSource: "invocation" },
|
|
166
|
+
...options.engine === void 0 ? {} : { engine: options.engine, engineSource: "invocation" },
|
|
167
|
+
...options.engineModel === void 0 ? {} : { engineModel: options.engineModel }
|
|
168
|
+
};
|
|
147
169
|
const captured = options.io === void 0 ? createCapturingIo() : void 0;
|
|
148
170
|
const io = options.io ?? captured.io;
|
|
149
171
|
const summonEnvInput = {
|
|
@@ -156,7 +178,11 @@ async function summonPublicRole(options) {
|
|
|
156
178
|
seat,
|
|
157
179
|
...options.extraPiArgs === void 0 ? {} : { extraPiArgs: options.extraPiArgs },
|
|
158
180
|
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
159
|
-
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters }
|
|
181
|
+
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
182
|
+
...options.principalAuthority === void 0 ? {} : { principalAuthority: options.principalAuthority },
|
|
183
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
184
|
+
// Parent model is already host-facing; child must not project again.
|
|
185
|
+
...options.model === void 0 ? {} : { hostFacingModel: options.model }
|
|
160
186
|
};
|
|
161
187
|
async function prepareSummonEnv(afterHost) {
|
|
162
188
|
try {
|
|
@@ -168,7 +194,9 @@ async function summonPublicRole(options) {
|
|
|
168
194
|
ok: true,
|
|
169
195
|
env: {
|
|
170
196
|
...built.env,
|
|
171
|
-
|
|
197
|
+
// Nested court stations keep station-child semantics; dual-lens
|
|
198
|
+
// ordinary Reviewer axes opt out (#946 / ADR 0082).
|
|
199
|
+
...options.stationChild === false ? {} : { stationChild: true },
|
|
172
200
|
// Forward composition-root adapters so nested court stations (e.g.
|
|
173
201
|
// countersign → diarist) select the same faux/production table (#924).
|
|
174
202
|
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
@@ -178,7 +206,8 @@ async function summonPublicRole(options) {
|
|
|
178
206
|
...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
|
|
179
207
|
...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
|
|
180
208
|
...options.correlationId === void 0 || options.correlationId.trim() === "" ? {} : { correlationId: options.correlationId },
|
|
181
|
-
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
209
|
+
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId },
|
|
210
|
+
...options.executionCwd === void 0 ? {} : { executionCwd: options.executionCwd }
|
|
182
211
|
}
|
|
183
212
|
};
|
|
184
213
|
} catch (error) {
|
|
@@ -240,14 +269,32 @@ async function summonPublicRole(options) {
|
|
|
240
269
|
case "navigator":
|
|
241
270
|
case "gatekeeper": {
|
|
242
271
|
const seat2 = options.role;
|
|
243
|
-
const [{ runPublicInstructionSeat }, invocation] = await Promise.all([
|
|
272
|
+
const [{ runPublicInstructionSeat, runPublicInstructionSeatResume }, invocation] = await Promise.all([
|
|
244
273
|
import("./public-cli/instruction-seat-run.js"),
|
|
245
274
|
import("./public-cli/invocation.js")
|
|
246
275
|
]);
|
|
247
276
|
const parse = seat2 === "auditor" ? invocation.parseAuditorArgv : seat2 === "navigator" ? invocation.parseNavigatorArgv : invocation.parseGatekeeperArgv;
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
250
|
-
|
|
277
|
+
const resumeRunId = typeof options.resumeRunId === "string" && options.resumeRunId.trim() !== "" ? options.resumeRunId.trim() : void 0;
|
|
278
|
+
if (resumeRunId !== void 0) {
|
|
279
|
+
const instruction = options.argv[0] ?? "";
|
|
280
|
+
const stepped = await runPrepared(parse, (env) => runPublicInstructionSeatResume(
|
|
281
|
+
{
|
|
282
|
+
runId: resumeRunId,
|
|
283
|
+
summons: {
|
|
284
|
+
instruction,
|
|
285
|
+
instructionEmpty: instruction.trim() === ""
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
env,
|
|
289
|
+
io
|
|
290
|
+
));
|
|
291
|
+
if ("fail" in stepped) return stepped.fail;
|
|
292
|
+
result = stepped.ok;
|
|
293
|
+
} else {
|
|
294
|
+
const stepped = await runPrepared(parse, (env, once) => runPublicInstructionSeat(options.argv, env, io, seat2, once));
|
|
295
|
+
if ("fail" in stepped) return stepped.fail;
|
|
296
|
+
result = stepped.ok;
|
|
297
|
+
}
|
|
251
298
|
break;
|
|
252
299
|
}
|
|
253
300
|
case "judge": {
|
|
@@ -290,6 +337,16 @@ async function summonPublicRole(options) {
|
|
|
290
337
|
result = stepped.ok;
|
|
291
338
|
break;
|
|
292
339
|
}
|
|
340
|
+
case "reviewer": {
|
|
341
|
+
const [{ runPublicReviewer }, { parseReviewerArgv }] = await Promise.all([
|
|
342
|
+
import("./public-cli/reviewer-run.js"),
|
|
343
|
+
import("./public-cli/invocation.js")
|
|
344
|
+
]);
|
|
345
|
+
const stepped = await runPrepared(parseReviewerArgv, (env, once) => runPublicReviewer(options.argv, env, io, once));
|
|
346
|
+
if ("fail" in stepped) return stepped.fail;
|
|
347
|
+
result = stepped.ok;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
293
350
|
}
|
|
294
351
|
const stderr = captured?.stderrText();
|
|
295
352
|
const runDirectory = result.admitted?.runDirectory;
|
|
@@ -301,6 +358,296 @@ async function summonPublicRole(options) {
|
|
|
301
358
|
...stderr === void 0 || stderr === "" ? {} : { stderr }
|
|
302
359
|
};
|
|
303
360
|
}
|
|
361
|
+
function withReviewerLens(argv, lens) {
|
|
362
|
+
const separator = argv.indexOf("--");
|
|
363
|
+
if (separator === -1) return [...argv, "--lens", lens];
|
|
364
|
+
return [...argv.slice(0, separator), "--lens", lens, ...argv.slice(separator)];
|
|
365
|
+
}
|
|
366
|
+
async function resolveReviewerWorktreeRoots(projectRoot) {
|
|
367
|
+
const callerProjectRoot = await realpath(projectRoot);
|
|
368
|
+
const sourceProjectRoot = await realpath((await execFileAsync(
|
|
369
|
+
"git",
|
|
370
|
+
["rev-parse", "--show-toplevel"],
|
|
371
|
+
{ cwd: callerProjectRoot }
|
|
372
|
+
)).stdout.trim());
|
|
373
|
+
const callerRelative = relative(sourceProjectRoot, callerProjectRoot);
|
|
374
|
+
const projectRelative = callerRelative === "" || callerRelative === "." || callerRelative.startsWith(`..${sep}`) || callerRelative === ".." ? "" : callerRelative;
|
|
375
|
+
return { callerProjectRoot, sourceProjectRoot, projectRelative };
|
|
376
|
+
}
|
|
377
|
+
function reviewerSandboxPath(worktreeAxis, projectRelative) {
|
|
378
|
+
return projectRelative === "" ? worktreeAxis : join(worktreeAxis, projectRelative);
|
|
379
|
+
}
|
|
380
|
+
async function openEphemeralReviewerWorktree(options) {
|
|
381
|
+
const { sourceProjectRoot, projectRelative } = await resolveReviewerWorktreeRoots(
|
|
382
|
+
options.projectRoot
|
|
383
|
+
);
|
|
384
|
+
const { stdout: headStdout } = await execFileAsync(
|
|
385
|
+
"git",
|
|
386
|
+
["rev-parse", "--verify", "HEAD^{commit}"],
|
|
387
|
+
{ cwd: sourceProjectRoot }
|
|
388
|
+
);
|
|
389
|
+
const targetCommit = headStdout.trim();
|
|
390
|
+
const root = await mkdtemp(join(tmpdir(), "ak-reviewer-sandbox-"));
|
|
391
|
+
const worktreeRoot = join(root, "work");
|
|
392
|
+
let registered = false;
|
|
393
|
+
const rollback = async (cause) => {
|
|
394
|
+
const cleanupErrors = [];
|
|
395
|
+
if (registered) {
|
|
396
|
+
try {
|
|
397
|
+
await execFileAsync("git", ["worktree", "remove", "--force", worktreeRoot], {
|
|
398
|
+
cwd: sourceProjectRoot
|
|
399
|
+
});
|
|
400
|
+
} catch (error) {
|
|
401
|
+
cleanupErrors.push(error);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
await rm(root, { recursive: true, force: true });
|
|
406
|
+
} catch (error) {
|
|
407
|
+
cleanupErrors.push(error);
|
|
408
|
+
}
|
|
409
|
+
if (cleanupErrors.length > 0) {
|
|
410
|
+
options.onCleanupDiagnostic?.([
|
|
411
|
+
"reviewer worktree cleanup failed",
|
|
412
|
+
...cleanupErrors.map((error) => error instanceof Error ? error.message : String(error))
|
|
413
|
+
].join("\n"));
|
|
414
|
+
}
|
|
415
|
+
throw cause;
|
|
416
|
+
};
|
|
417
|
+
try {
|
|
418
|
+
await execFileAsync("git", ["worktree", "add", "--detach", worktreeRoot, targetCommit], {
|
|
419
|
+
cwd: sourceProjectRoot
|
|
420
|
+
});
|
|
421
|
+
registered = true;
|
|
422
|
+
if (projectRelative !== "") {
|
|
423
|
+
await mkdir(reviewerSandboxPath(worktreeRoot, projectRelative), { recursive: true });
|
|
424
|
+
}
|
|
425
|
+
} catch (error) {
|
|
426
|
+
await rollback(error);
|
|
427
|
+
}
|
|
428
|
+
let closed = false;
|
|
429
|
+
return {
|
|
430
|
+
executionCwd: reviewerSandboxPath(worktreeRoot, projectRelative),
|
|
431
|
+
close: async () => {
|
|
432
|
+
if (closed) return;
|
|
433
|
+
closed = true;
|
|
434
|
+
const cleanupErrors = [];
|
|
435
|
+
try {
|
|
436
|
+
await execFileAsync("git", ["worktree", "remove", worktreeRoot], {
|
|
437
|
+
cwd: sourceProjectRoot
|
|
438
|
+
});
|
|
439
|
+
} catch (error) {
|
|
440
|
+
cleanupErrors.push(error);
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
await rm(root, { recursive: true, force: true });
|
|
444
|
+
} catch (error) {
|
|
445
|
+
cleanupErrors.push(error);
|
|
446
|
+
}
|
|
447
|
+
if (cleanupErrors.length > 0) {
|
|
448
|
+
options.onCleanupDiagnostic?.([
|
|
449
|
+
"reviewer worktree cleanup failed",
|
|
450
|
+
...cleanupErrors.map((error) => error instanceof Error ? error.message : String(error))
|
|
451
|
+
].join("\n"));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
async function withEphemeralReviewerWorktree(options) {
|
|
457
|
+
const sandbox = await openEphemeralReviewerWorktree({
|
|
458
|
+
projectRoot: options.projectRoot,
|
|
459
|
+
...options.onCleanupDiagnostic === void 0 ? {} : { onCleanupDiagnostic: options.onCleanupDiagnostic }
|
|
460
|
+
});
|
|
461
|
+
try {
|
|
462
|
+
return await options.run(sandbox.executionCwd);
|
|
463
|
+
} finally {
|
|
464
|
+
await sandbox.close();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function summonParallelReviewerLenses(options) {
|
|
468
|
+
const describeFailure = (error) => {
|
|
469
|
+
if (error instanceof AggregateError) {
|
|
470
|
+
return [error.message, ...error.errors.map(describeFailure)].join("\n");
|
|
471
|
+
}
|
|
472
|
+
return error instanceof Error ? error.message : String(error);
|
|
473
|
+
};
|
|
474
|
+
const failedResult = (error) => ({
|
|
475
|
+
exitCode: 1,
|
|
476
|
+
stderr: describeFailure(error)
|
|
477
|
+
});
|
|
478
|
+
const dualFailure = (error) => {
|
|
479
|
+
const failure = failedResult(error);
|
|
480
|
+
return { completeness: failure, correctness: failure };
|
|
481
|
+
};
|
|
482
|
+
let sourceProjectRoot;
|
|
483
|
+
let projectRelative;
|
|
484
|
+
let targetCommit;
|
|
485
|
+
const statusArgs = [
|
|
486
|
+
"status",
|
|
487
|
+
"--porcelain=v1",
|
|
488
|
+
"--untracked-files=all",
|
|
489
|
+
"--",
|
|
490
|
+
":/",
|
|
491
|
+
":(top,exclude).claude/worktrees/**"
|
|
492
|
+
];
|
|
493
|
+
try {
|
|
494
|
+
const roots = await resolveReviewerWorktreeRoots(options.projectRoot);
|
|
495
|
+
sourceProjectRoot = roots.sourceProjectRoot;
|
|
496
|
+
projectRelative = roots.projectRelative;
|
|
497
|
+
const { stdout: statusStdout } = await execFileAsync("git", statusArgs, {
|
|
498
|
+
cwd: sourceProjectRoot
|
|
499
|
+
});
|
|
500
|
+
if (statusStdout !== "") {
|
|
501
|
+
const diagnostic = [
|
|
502
|
+
"Reviewer target status gate failed:",
|
|
503
|
+
"git status --porcelain=v1 --untracked-files=all -- :/ ':(top,exclude).claude/worktrees/**'",
|
|
504
|
+
statusStdout
|
|
505
|
+
].join("\n");
|
|
506
|
+
return dualFailure(new Error(diagnostic));
|
|
507
|
+
}
|
|
508
|
+
const { stdout: targetStdout } = await execFileAsync(
|
|
509
|
+
"git",
|
|
510
|
+
["rev-parse", "--verify", "HEAD^{commit}"],
|
|
511
|
+
{ cwd: sourceProjectRoot }
|
|
512
|
+
);
|
|
513
|
+
targetCommit = targetStdout.trim();
|
|
514
|
+
await execFileAsync(
|
|
515
|
+
"git",
|
|
516
|
+
["rev-parse", "--verify", `${options.baseRevision}^{commit}`],
|
|
517
|
+
{ cwd: sourceProjectRoot }
|
|
518
|
+
);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
return dualFailure(error);
|
|
521
|
+
}
|
|
522
|
+
const root = await mkdtemp(join(tmpdir(), "ak-reviewer-lenses-"));
|
|
523
|
+
const completenessRoot = join(root, "completeness");
|
|
524
|
+
const correctnessRoot = join(root, "correctness");
|
|
525
|
+
const worktrees = [completenessRoot, correctnessRoot];
|
|
526
|
+
const created = /* @__PURE__ */ new Set();
|
|
527
|
+
let results;
|
|
528
|
+
const creation = await Promise.allSettled(
|
|
529
|
+
worktrees.map(async (path) => {
|
|
530
|
+
await execFileAsync("git", ["worktree", "add", "--detach", path, targetCommit], {
|
|
531
|
+
cwd: sourceProjectRoot
|
|
532
|
+
});
|
|
533
|
+
created.add(path);
|
|
534
|
+
if (projectRelative !== "") {
|
|
535
|
+
await mkdir(reviewerSandboxPath(path, projectRelative), { recursive: true });
|
|
536
|
+
}
|
|
537
|
+
})
|
|
538
|
+
);
|
|
539
|
+
const creationFailures = creation.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
540
|
+
if (creationFailures.length > 0) {
|
|
541
|
+
const failure = failedResult(new AggregateError(
|
|
542
|
+
creationFailures,
|
|
543
|
+
"parallel reviewer worktree creation failed"
|
|
544
|
+
));
|
|
545
|
+
results = { completeness: failure, correctness: failure };
|
|
546
|
+
} else {
|
|
547
|
+
const summon = (lens, worktreeAxis) => {
|
|
548
|
+
const sandbox = reviewerSandboxPath(worktreeAxis, projectRelative);
|
|
549
|
+
return summonPublicRole({
|
|
550
|
+
role: "reviewer",
|
|
551
|
+
argv: withReviewerLens(options.argv, lens),
|
|
552
|
+
cwd: options.cwd,
|
|
553
|
+
executionCwd: sandbox,
|
|
554
|
+
// Ordinary single-axis public semantics — not a nested court station.
|
|
555
|
+
stationChild: false,
|
|
556
|
+
home: options.home,
|
|
557
|
+
...options.agentDir === void 0 ? {} : { agentDir: options.agentDir },
|
|
558
|
+
...options.credentials === void 0 ? {} : { credentials: options.credentials },
|
|
559
|
+
...options.model === void 0 ? {} : { model: options.model },
|
|
560
|
+
...options.host === void 0 ? {} : { host: options.host },
|
|
561
|
+
...options.engine === void 0 ? {} : { engine: options.engine },
|
|
562
|
+
...options.engineModel === void 0 ? {} : { engineModel: options.engineModel },
|
|
563
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
564
|
+
...options.correlationId === void 0 ? {} : { correlationId: options.correlationId },
|
|
565
|
+
...options.packageRoot === void 0 ? {} : { packageRoot: options.packageRoot },
|
|
566
|
+
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
567
|
+
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
568
|
+
...options.principalAuthority === void 0 ? {} : { principalAuthority: options.principalAuthority },
|
|
569
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
570
|
+
});
|
|
571
|
+
};
|
|
572
|
+
const settled = await Promise.allSettled([
|
|
573
|
+
summon("completeness", completenessRoot),
|
|
574
|
+
summon("correctness", correctnessRoot)
|
|
575
|
+
]);
|
|
576
|
+
results = {
|
|
577
|
+
completeness: settled[0].status === "fulfilled" ? settled[0].value : failedResult(settled[0].reason),
|
|
578
|
+
correctness: settled[1].status === "fulfilled" ? settled[1].value : failedResult(settled[1].reason)
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
let sealDiagnostic;
|
|
582
|
+
try {
|
|
583
|
+
const { stdout: headBeforeStdout } = await execFileAsync(
|
|
584
|
+
"git",
|
|
585
|
+
["rev-parse", "--verify", "HEAD^{commit}"],
|
|
586
|
+
{ cwd: sourceProjectRoot }
|
|
587
|
+
);
|
|
588
|
+
const { stdout: sealedStatusStdout } = await execFileAsync("git", statusArgs, {
|
|
589
|
+
cwd: sourceProjectRoot
|
|
590
|
+
});
|
|
591
|
+
const { stdout: headAfterStdout } = await execFileAsync(
|
|
592
|
+
"git",
|
|
593
|
+
["rev-parse", "--verify", "HEAD^{commit}"],
|
|
594
|
+
{ cwd: sourceProjectRoot }
|
|
595
|
+
);
|
|
596
|
+
const headBefore = headBeforeStdout.trim();
|
|
597
|
+
const headAfter = headAfterStdout.trim();
|
|
598
|
+
if (headBefore !== targetCommit || headAfter !== targetCommit || sealedStatusStdout !== "") {
|
|
599
|
+
sealDiagnostic = [
|
|
600
|
+
"Reviewer target final seal failed:",
|
|
601
|
+
`HEAD before status => ${headBefore}`,
|
|
602
|
+
`HEAD after status => ${headAfter}`,
|
|
603
|
+
`expected PRE_HEAD ${targetCommit}`,
|
|
604
|
+
"git status --porcelain=v1 --untracked-files=all -- :/ ':(top,exclude).claude/worktrees/**'",
|
|
605
|
+
sealedStatusStdout
|
|
606
|
+
].join("\n");
|
|
607
|
+
}
|
|
608
|
+
} catch (error) {
|
|
609
|
+
sealDiagnostic = `Reviewer target final seal failed:
|
|
610
|
+
${describeFailure(error)}`;
|
|
611
|
+
}
|
|
612
|
+
if (sealDiagnostic !== void 0) {
|
|
613
|
+
const withSealFailure = (result) => ({
|
|
614
|
+
...result,
|
|
615
|
+
exitCode: 1,
|
|
616
|
+
stderr: [result.stderr, sealDiagnostic].filter(
|
|
617
|
+
(text) => typeof text === "string" && text !== ""
|
|
618
|
+
).join("\n")
|
|
619
|
+
});
|
|
620
|
+
results = {
|
|
621
|
+
completeness: withSealFailure(results.completeness),
|
|
622
|
+
correctness: withSealFailure(results.correctness)
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
const cleanup = await Promise.allSettled(
|
|
626
|
+
[...created].map((path) => execFileAsync("git", ["worktree", "remove", path], { cwd: sourceProjectRoot }))
|
|
627
|
+
);
|
|
628
|
+
const cleanupFailures = cleanup.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
629
|
+
if (cleanupFailures.length === 0) {
|
|
630
|
+
const rootCleanup = await Promise.allSettled([rm(root, { recursive: true, force: true })]);
|
|
631
|
+
cleanupFailures.push(...rootCleanup.flatMap((result) => result.status === "rejected" ? [result.reason] : []));
|
|
632
|
+
}
|
|
633
|
+
if (cleanupFailures.length > 0) {
|
|
634
|
+
const diagnostic = [
|
|
635
|
+
"parallel reviewer worktree cleanup failed",
|
|
636
|
+
...cleanupFailures.map((failure) => failure instanceof Error ? failure.message : String(failure))
|
|
637
|
+
].join("\n");
|
|
638
|
+
const withCleanupDiagnostic = (result) => ({
|
|
639
|
+
...result,
|
|
640
|
+
stderr: [result.stderr, diagnostic].filter(
|
|
641
|
+
(text) => typeof text === "string" && text !== ""
|
|
642
|
+
).join("\n")
|
|
643
|
+
});
|
|
644
|
+
results = {
|
|
645
|
+
completeness: withCleanupDiagnostic(results.completeness),
|
|
646
|
+
correctness: withCleanupDiagnostic(results.correctness)
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
return results;
|
|
650
|
+
}
|
|
304
651
|
async function summonGateOfficer(options) {
|
|
305
652
|
let home = options.home;
|
|
306
653
|
if (home === void 0) {
|
|
@@ -352,7 +699,10 @@ async function summonGateOfficer(options) {
|
|
|
352
699
|
}
|
|
353
700
|
export {
|
|
354
701
|
AK_ROLE_PACKAGE_ROOT_ENV,
|
|
702
|
+
openEphemeralReviewerWorktree,
|
|
355
703
|
resolveSummonsPackageRoot,
|
|
356
704
|
summonGateOfficer,
|
|
357
|
-
|
|
705
|
+
summonParallelReviewerLenses,
|
|
706
|
+
summonPublicRole,
|
|
707
|
+
withEphemeralReviewerWorktree
|
|
358
708
|
};
|
|
@@ -338,6 +338,59 @@ async function restoreState(cwd, runId, scope) {
|
|
|
338
338
|
}, 0),
|
|
339
339
|
};
|
|
340
340
|
}
|
|
341
|
+
/** Sole HostContext-derived session parent for ledger restore/append (never process.env). */
|
|
342
|
+
function sessionParentFromHostContext(context) {
|
|
343
|
+
const runDirectory = runDirectoryFromHostContext(context);
|
|
344
|
+
if (runDirectory !== undefined)
|
|
345
|
+
return join(runDirectory, "session", "session.jsonl");
|
|
346
|
+
const sessionFile = context.sessionManager.getSessionFile?.();
|
|
347
|
+
if (typeof sessionFile === "string" && sessionFile.length > 0)
|
|
348
|
+
return sessionFile;
|
|
349
|
+
const sessionDir = context.sessionManager.getSessionDir?.();
|
|
350
|
+
if (typeof sessionDir === "string" && sessionDir.length > 0) {
|
|
351
|
+
return join(sessionDir, "session.jsonl");
|
|
352
|
+
}
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
function homeFromHostContext(context, home) {
|
|
356
|
+
if (home !== undefined)
|
|
357
|
+
return home;
|
|
358
|
+
const sessionParent = sessionParentFromHostContext(context);
|
|
359
|
+
return sessionParent !== undefined ? tryHomeFromAkRolesPath(sessionParent) : undefined;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* #959: seal one accepted submission without a model tool call.
|
|
363
|
+
* Same ledger row shape and priorEventId chain as the terminating-tool wrap.
|
|
364
|
+
* Used when a prose-exit seat (navigator) harvests the final assistant text.
|
|
365
|
+
*/
|
|
366
|
+
export async function sealAcceptedSubmission(options) {
|
|
367
|
+
const runId = runIdentity(options.context);
|
|
368
|
+
const attemptId = attemptIdentity(options.context, runId);
|
|
369
|
+
const sessionParent = sessionParentFromHostContext(options.context);
|
|
370
|
+
const home = homeFromHostContext(options.context, options.home);
|
|
371
|
+
const state = await restoreState(options.context.cwd, runId, {
|
|
372
|
+
...(home === undefined ? {} : { home }),
|
|
373
|
+
...(sessionParent === undefined ? {} : { sessionParent }),
|
|
374
|
+
});
|
|
375
|
+
const pointer = sitianReport({
|
|
376
|
+
level: "event",
|
|
377
|
+
kind: "sealed",
|
|
378
|
+
subject: { runId, attemptId },
|
|
379
|
+
...(state.prior === undefined ? {} : { priorEventId: state.prior.identity }),
|
|
380
|
+
payload: {
|
|
381
|
+
type: "sealed",
|
|
382
|
+
attemptId,
|
|
383
|
+
toolCallId: options.toolCallId,
|
|
384
|
+
role: options.role,
|
|
385
|
+
accepted: options.accepted,
|
|
386
|
+
},
|
|
387
|
+
source: "role-runtime",
|
|
388
|
+
cwd: options.context.cwd,
|
|
389
|
+
...(home !== undefined ? { home } : {}),
|
|
390
|
+
...(sessionParent === undefined ? {} : { sessionParent }),
|
|
391
|
+
});
|
|
392
|
+
state.prior = pointer;
|
|
393
|
+
}
|
|
341
394
|
/**
|
|
342
395
|
* Submission ledger host — record only (#836).
|
|
343
396
|
* Each terminating submission is appended with the role's original payload.
|
|
@@ -346,30 +399,10 @@ async function restoreState(cwd, runId, scope) {
|
|
|
346
399
|
*/
|
|
347
400
|
export function createSubmissionLedgerHost(host, outputTools, failInfrastructure = (error) => { throw error; }, projectClosure = () => undefined, options) {
|
|
348
401
|
const states = new Map();
|
|
349
|
-
|
|
350
|
-
const sessionParentFromContext = (context) => {
|
|
351
|
-
const runDirectory = runDirectoryFromHostContext(context);
|
|
352
|
-
if (runDirectory !== undefined) {
|
|
353
|
-
return join(runDirectory, "session", "session.jsonl");
|
|
354
|
-
}
|
|
355
|
-
const sessionFile = context.sessionManager.getSessionFile?.();
|
|
356
|
-
if (typeof sessionFile === "string" && sessionFile.length > 0)
|
|
357
|
-
return sessionFile;
|
|
358
|
-
const sessionDir = context.sessionManager.getSessionDir?.();
|
|
359
|
-
if (typeof sessionDir === "string" && sessionDir.length > 0) {
|
|
360
|
-
return join(sessionDir, "session.jsonl");
|
|
361
|
-
}
|
|
362
|
-
return undefined;
|
|
363
|
-
};
|
|
364
|
-
const resolveHomeFromContext = (context) => {
|
|
365
|
-
if (options?.home !== undefined)
|
|
366
|
-
return options.home;
|
|
367
|
-
const sessionParent = sessionParentFromContext(context);
|
|
368
|
-
return sessionParent !== undefined ? tryHomeFromAkRolesPath(sessionParent) : undefined;
|
|
369
|
-
};
|
|
402
|
+
const resolveHomeFromContext = (context) => homeFromHostContext(context, options?.home);
|
|
370
403
|
const stateFor = (context, runId) => states.get(runId) ?? (() => {
|
|
371
404
|
const home = resolveHomeFromContext(context);
|
|
372
|
-
const sessionParent =
|
|
405
|
+
const sessionParent = sessionParentFromHostContext(context);
|
|
373
406
|
const pending = restoreState(context.cwd, runId, {
|
|
374
407
|
...(home === undefined ? {} : { home }),
|
|
375
408
|
...(sessionParent === undefined ? {} : { sessionParent }),
|
|
@@ -379,7 +412,7 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
|
|
|
379
412
|
})();
|
|
380
413
|
const appendFor = (state, context, runId, attemptId, event) => {
|
|
381
414
|
const home = resolveHomeFromContext(context);
|
|
382
|
-
const sessionParent =
|
|
415
|
+
const sessionParent = sessionParentFromHostContext(context);
|
|
383
416
|
const pointer = sitianReport({
|
|
384
417
|
level: "event",
|
|
385
418
|
kind: event.type,
|
package/package.json
CHANGED
|
@@ -14,5 +14,7 @@
|
|
|
14
14
|
| `run-lifecycle.ts` A4.2 | `重新读 <path>` | 前轮已删改写。 |
|
|
15
15
|
| `submission-correctable-error.ts` A4.5 | `终局交卷并非本轮唯一工具调用。` | 删。Pi/ACP `deliverSubmissionRejection` 不再注入该句。 |
|
|
16
16
|
| `role-runtime.ts` 催交 | `本会话尚无已接受的 typed 回执。` | **留**(轮次/预算,Q2)。 |
|
|
17
|
+
| `navigator-attendance.ts` early prepare 开场 | `父衙门进行中。请在本宿主会话熟悉下列材料并待命;结算结果送达后,再给出下一步建议。本轮不要提交最终路线建议。` | #959 迁入 `resources/navigator-route-playbook.md`「到场阶段」;代码只留中立指针句「本轮为待命轮。」 |
|
|
18
|
+
| `navigator-attendance.ts` settlement feed 开场 | `父衙门结算已送达。请根据下列材料给出下一步建议。` | #959 迁入同手册;代码只留中立指针句「本轮为结算投喂轮。」 |
|
|
17
19
|
|
|
18
20
|
催交句与 2.1–2.3 打回文、重问三句不在删除列。
|