@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
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Navigator attendance session factory (#675 r3).
|
|
3
3
|
* Each prepare turn uses the public navigator activation path (summonPublicRole) —
|
|
4
4
|
* same seat table and shared envelope as `ak-role navigator`.
|
|
5
|
-
* Archivist createRecordSession
|
|
6
|
-
*
|
|
5
|
+
* Archivist createRecordSession books the attendance nest under the parent;
|
|
6
|
+
* host conversation continuity uses CLI resume (`resumeRunId`), not a package
|
|
7
|
+
* advice ledger (development-closure: do not invent package-level memory).
|
|
7
8
|
*/
|
|
9
|
+
import { basename } from "node:path";
|
|
10
|
+
|
|
8
11
|
import { sitianReport } from "./sitian-facade.ts";
|
|
9
12
|
import {
|
|
10
13
|
NavigatorUnavailableError,
|
|
@@ -17,6 +20,8 @@ import {
|
|
|
17
20
|
} from "./navigator-session-contracts.ts";
|
|
18
21
|
import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
19
22
|
import { runDirectoryFromHostContext, type HostContext } from "./host-contracts.ts";
|
|
23
|
+
import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
24
|
+
import { CliUsageError } from "./public-cli/cli-errors.ts";
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
27
|
* Ledger process home for navigator attendance: admitted HostContext.runDirectory
|
|
@@ -40,13 +45,73 @@ async function resolveNavigatorLedgerHome(context: HostContext): Promise<string
|
|
|
40
45
|
return undefined;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
/** Resume key only — points at a host run principal, never stores advice prose. */
|
|
49
|
+
export const NAVIGATOR_HOST_RUN_POINTER_ENTRY = "ak-navigator-host-run";
|
|
50
|
+
|
|
51
|
+
function exactRecord(value: unknown): value is Record<string, unknown> {
|
|
52
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function runIdFromNavigatorDirectory(runDirectory: string): string | undefined {
|
|
56
|
+
const entry = basename(runDirectory);
|
|
57
|
+
const suffix = "@navigator";
|
|
58
|
+
if (!entry.endsWith(suffix)) return undefined;
|
|
59
|
+
const runId = entry.slice(0, entry.length - suffix.length);
|
|
60
|
+
return runId.length > 0 ? runId : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function readNavigatorHostRunPointer(entries: readonly unknown[]): string | undefined {
|
|
64
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
65
|
+
const entry = entries[index];
|
|
66
|
+
if (!exactRecord(entry)) continue;
|
|
67
|
+
if (entry.type === "custom" && entry.customType === NAVIGATOR_HOST_RUN_POINTER_ENTRY) {
|
|
68
|
+
const data = entry.data;
|
|
69
|
+
if (exactRecord(data) && typeof data.runId === "string" && data.runId.trim() !== "") {
|
|
70
|
+
return data.runId.trim();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Typed preflight via public CLI load path: can this run reopen as navigator?
|
|
79
|
+
* CliUsageError (code AK_ROLE_USAGE) from loadResumableInstructionSeatRun means
|
|
80
|
+
* unknown id / principal unavailable / non-instruction seat — not resumable.
|
|
81
|
+
* Other throws propagate. Never reads stderr prose.
|
|
82
|
+
*/
|
|
83
|
+
export async function navigatorHostRunResumable(home: string, runId: string): Promise<boolean> {
|
|
84
|
+
const { piDurablePrincipalAuthority } = await import("./pi/durable-principal.ts");
|
|
85
|
+
const { loadResumableInstructionSeatRun } = await import("./public-cli/run-lifecycle.ts");
|
|
86
|
+
try {
|
|
87
|
+
const loaded = await loadResumableInstructionSeatRun(home, runId, piDurablePrincipalAuthority);
|
|
88
|
+
return loaded.admitted.role === "navigator";
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error instanceof CliUsageError) return false;
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type NavigatorPublicSummon = (options: {
|
|
96
|
+
readonly role: "navigator";
|
|
97
|
+
readonly argv: readonly string[];
|
|
98
|
+
readonly cwd: string;
|
|
99
|
+
readonly home?: string;
|
|
100
|
+
readonly resumeRunId?: string;
|
|
101
|
+
}) => Promise<PublicSummonResult>;
|
|
102
|
+
|
|
103
|
+
export function createNativeNavigatorSessionFactory(deps?: {
|
|
104
|
+
/** Test/composition inject — production leaves unset and uses public-role-summons. */
|
|
105
|
+
readonly summonPublicRole?: NavigatorPublicSummon;
|
|
106
|
+
/** Test inject — production uses navigatorHostRunResumable (typed CLI load). */
|
|
107
|
+
readonly hostRunResumable?: (home: string, runId: string) => Promise<boolean>;
|
|
108
|
+
}): NavigatorSessionFactory {
|
|
44
109
|
return async ({ context, subject, tool }) => {
|
|
45
110
|
// Model is enforced at prepare (attendance seat resolve) and at prompt (summon).
|
|
46
111
|
// Factory open only books the archivist nest — no package default, no eager seat read.
|
|
47
112
|
let thinkingLevel: string | undefined;
|
|
48
113
|
|
|
49
|
-
// Archivist nest for attendance
|
|
114
|
+
// Archivist nest for attendance bookkeeping (ADR 0018 / 0065) — not a host session open.
|
|
50
115
|
// #852: navigator/<work-subject> is the sole book-top exception; always pass subject so
|
|
51
116
|
// unmaterialized/missing parent still gets a durable nest instead of silent in-memory.
|
|
52
117
|
// Home comes from HostContext.runDirectory (or ledger parent path) — never context.home.
|
|
@@ -64,6 +129,14 @@ export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
|
|
|
64
129
|
let noReceipt: NoReceiptLifecycleFacts | undefined;
|
|
65
130
|
let disposed = false;
|
|
66
131
|
let inFlightPrompt: Promise<unknown> | undefined;
|
|
132
|
+
/** In-factory host run id for CLI resume; durable pointer also lives on the nest. */
|
|
133
|
+
let hostRunId = readNavigatorHostRunPointer(sessionManager.getEntries() as readonly unknown[]);
|
|
134
|
+
|
|
135
|
+
const summon: NavigatorPublicSummon = deps?.summonPublicRole
|
|
136
|
+
?? (async (options) => {
|
|
137
|
+
const { summonPublicRole } = await import("./public-role-summons.ts");
|
|
138
|
+
return summonPublicRole(options);
|
|
139
|
+
});
|
|
67
140
|
|
|
68
141
|
return {
|
|
69
142
|
prompt: async (text) => {
|
|
@@ -74,15 +147,28 @@ export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
|
|
|
74
147
|
noReceipt = undefined;
|
|
75
148
|
const run = (async () => {
|
|
76
149
|
try {
|
|
77
|
-
const { summonPublicRole } = await import("./public-role-summons.ts");
|
|
78
150
|
const summonHome = await resolveNavigatorLedgerHome(context);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
151
|
+
const resumeRunId = hostRunId;
|
|
152
|
+
|
|
153
|
+
const baseSummon = {
|
|
154
|
+
role: "navigator" as const,
|
|
155
|
+
argv: [text] as const,
|
|
83
156
|
cwd: context.cwd,
|
|
84
157
|
...(summonHome === undefined ? {} : { home: summonHome }),
|
|
85
|
-
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Prefer host CLI resume so prior advice stays on the host session.
|
|
161
|
+
// Fresh mint only when typed load says the principal cannot reopen.
|
|
162
|
+
const resumable = deps?.hostRunResumable ?? navigatorHostRunResumable;
|
|
163
|
+
let summoned: PublicSummonResult;
|
|
164
|
+
if (resumeRunId === undefined || summonHome === undefined) {
|
|
165
|
+
summoned = await summon(baseSummon);
|
|
166
|
+
} else if (await resumable(summonHome, resumeRunId)) {
|
|
167
|
+
summoned = await summon({ ...baseSummon, resumeRunId });
|
|
168
|
+
} else {
|
|
169
|
+
hostRunId = undefined;
|
|
170
|
+
summoned = await summon(baseSummon);
|
|
171
|
+
}
|
|
86
172
|
|
|
87
173
|
const outcome = summoned.terminal?.roleOutcome;
|
|
88
174
|
if (outcome === undefined) {
|
|
@@ -109,23 +195,40 @@ export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
|
|
|
109
195
|
noReceipt = outcome;
|
|
110
196
|
return;
|
|
111
197
|
}
|
|
198
|
+
|
|
199
|
+
// Pin host run for the next attendance prompt on this subject (CLI resume key).
|
|
200
|
+
const runDirectory = summoned.runDirectory
|
|
201
|
+
?? (typeof summoned.admitted?.runDirectory === "string"
|
|
202
|
+
? summoned.admitted.runDirectory
|
|
203
|
+
: undefined);
|
|
204
|
+
if (typeof runDirectory === "string" && runDirectory.trim() !== "") {
|
|
205
|
+
const nextRunId = runIdFromNavigatorDirectory(runDirectory);
|
|
206
|
+
if (nextRunId !== undefined) {
|
|
207
|
+
hostRunId = nextRunId;
|
|
208
|
+
sessionManager.appendCustomEntry(NAVIGATOR_HOST_RUN_POINTER_ENTRY, { runId: nextRunId });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
112
212
|
if (outcome.kind !== "accepted") {
|
|
113
|
-
// Non-accepted kinds (e.g. audit_escalation): no
|
|
213
|
+
// Non-accepted kinds (e.g. audit_escalation): no prose advice this turn.
|
|
114
214
|
// Not a shape-unusable judgment on the navigator reply (#757).
|
|
115
215
|
return;
|
|
116
216
|
}
|
|
217
|
+
// #959: present navigator words as prose. No candidates/next parsing.
|
|
218
|
+
const { navigatorProseFromUnknown } = await import("./package-contracts/navigator-output.ts");
|
|
219
|
+
const proseParts: string[] = [];
|
|
117
220
|
for (const payload of outcome.payloads ?? []) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!Array.isArray(candidates)) continue;
|
|
121
|
-
await tool.execute(
|
|
122
|
-
"navigator-public-prepare",
|
|
123
|
-
{ candidates },
|
|
124
|
-
undefined,
|
|
125
|
-
undefined,
|
|
126
|
-
context as never,
|
|
127
|
-
);
|
|
221
|
+
const prose = navigatorProseFromUnknown(payload);
|
|
222
|
+
if (prose !== undefined) proseParts.push(prose);
|
|
128
223
|
}
|
|
224
|
+
if (proseParts.length === 0) return;
|
|
225
|
+
await tool.execute(
|
|
226
|
+
"navigator-public-prepare",
|
|
227
|
+
{ prose: proseParts.join("\n\n") },
|
|
228
|
+
undefined,
|
|
229
|
+
undefined,
|
|
230
|
+
context as never,
|
|
231
|
+
);
|
|
129
232
|
} catch (error) {
|
|
130
233
|
if (error instanceof NavigatorUnavailableError) throw error;
|
|
131
234
|
const fact = navigatorProviderFailureFromError(error);
|
|
@@ -166,10 +269,8 @@ export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
|
|
|
166
269
|
} catch (error) {
|
|
167
270
|
throw navigatorUnavailableError("model", error);
|
|
168
271
|
}
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
// / #617 DK-3). A seat edit between prepares therefore applies on the next
|
|
172
|
-
// summon — it is never an unavailable session.
|
|
272
|
+
// Resume and fresh mint both read the live seat table (#675 / #617 DK-3).
|
|
273
|
+
// A seat edit between prepares applies on the next host turn.
|
|
173
274
|
thinkingLevel = nextThinking ?? nextParsed.thinkingLevel;
|
|
174
275
|
},
|
|
175
276
|
getThinkingLevel: () => thinkingLevel,
|
package/src/navigator-role.ts
CHANGED
|
@@ -2,12 +2,12 @@ import { NAVIGATOR_OUTPUT_TOOL_NAME, navigatorOutputSchema } from "./package-con
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
|
|
5
|
-
*
|
|
5
|
+
* #959: navigator speaks free-form prose; tool is a lifecycle vehicle only.
|
|
6
6
|
*/
|
|
7
7
|
export const NAVIGATOR_TOOL_SPEC = {
|
|
8
8
|
name: NAVIGATOR_OUTPUT_TOOL_NAME,
|
|
9
9
|
label: "游奕使建议",
|
|
10
|
-
description: "
|
|
10
|
+
description: "游奕使终局回执:散文建议,原样呈现。",
|
|
11
11
|
promptSnippet: "游奕使建议",
|
|
12
12
|
parameters: navigatorOutputSchema,
|
|
13
13
|
} as const;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Public Navigator (游奕使) terminating receipt contracts (#639).
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Public Navigator (游奕使) terminating receipt contracts (#639 / #959).
|
|
3
|
+
* Navigator speaks free-form prose. The tool is only a lifecycle vehicle so the
|
|
4
|
+
* host can seal a turn; the model-facing content is prose, not structured route
|
|
5
|
+
* candidates. Code does not parse, rank, or judge the advice.
|
|
5
6
|
* No usable result is infrastructure failure via public settlement, not a judgment status (#475).
|
|
6
7
|
*/
|
|
7
8
|
import { Type } from "typebox";
|
|
@@ -15,42 +16,57 @@ export const NAVIGATOR_ACCEPTED_TEXT = "游奕使建议已受理";
|
|
|
15
16
|
export const navigatorOutputSchema = withTerminatingOutputDeclarations(
|
|
16
17
|
openToolObject(
|
|
17
18
|
Type.Object({
|
|
18
|
-
|
|
19
|
-
description: "
|
|
20
|
-
}),
|
|
21
|
-
candidates: Type.Unknown({
|
|
22
|
-
description: "排好序的路线建议数组,元素含 next/phase/reason — 形状指引,非 schema 闸",
|
|
19
|
+
prose: Type.Unknown({
|
|
20
|
+
description: "游奕使散文建议,原样呈现。不要求 candidates/next 等结构化字段。形状指引,非 schema 闸",
|
|
23
21
|
}),
|
|
24
22
|
}),
|
|
25
23
|
),
|
|
26
24
|
);
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type NavigatorAdvice = { readonly status: "advice"; readonly candidates: readonly unknown[] };
|
|
26
|
+
export type NavigatorAdvice = { readonly prose: string };
|
|
33
27
|
|
|
34
28
|
/**
|
|
35
|
-
* Project one
|
|
36
|
-
*
|
|
37
|
-
*
|
|
29
|
+
* Project one Navigator advice receipt as prose.
|
|
30
|
+
* Accepts { prose }, a bare string, or any object — objects stringify as the
|
|
31
|
+
* prose body so historical / free-form submissions still present. Shape is not
|
|
32
|
+
* an admission gate (ADR 0055 / 第 0 条 / #959).
|
|
38
33
|
*/
|
|
39
34
|
export function projectLawfulNavigatorOutput(value: unknown): NavigatorAdvice | undefined {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
if (value === undefined || value === null) return undefined;
|
|
36
|
+
if (typeof value === "string") return { prose: value };
|
|
37
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
38
|
+
const record = value as Record<string, unknown>;
|
|
39
|
+
if (typeof record.prose === "string") return { prose: record.prose };
|
|
40
|
+
if (record.prose !== undefined && record.prose !== null) {
|
|
41
|
+
return { prose: typeof record.prose === "string" ? record.prose : String(record.prose) };
|
|
42
|
+
}
|
|
43
|
+
// Free-form object submission: present the whole body as prose when it has content.
|
|
44
|
+
const keys = Object.keys(record);
|
|
45
|
+
if (keys.length === 0) return { prose: "" };
|
|
46
|
+
try {
|
|
47
|
+
return { prose: JSON.stringify(record) };
|
|
48
|
+
} catch {
|
|
49
|
+
return { prose: String(record) };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { prose: String(value) };
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
/**
|
|
47
|
-
* Settlement/recording path:
|
|
48
|
-
* Does not gate role admission — callers must not use this to reject a submission.
|
|
56
|
+
* Settlement/recording path: project prose. Does not gate role admission.
|
|
49
57
|
*/
|
|
50
58
|
export function validateRecordedNavigatorOutput(value: unknown): NavigatorAdvice {
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
const projected = projectLawfulNavigatorOutput(value);
|
|
60
|
+
if (projected === undefined) {
|
|
61
|
+
throw new Error("Navigator output is empty");
|
|
53
62
|
}
|
|
54
|
-
return
|
|
63
|
+
return projected;
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
/** Extract display prose from any navigator payload / attendance body. */
|
|
67
|
+
export function navigatorProseFromUnknown(value: unknown): string | undefined {
|
|
68
|
+
const projected = projectLawfulNavigatorOutput(value);
|
|
69
|
+
if (projected === undefined) return undefined;
|
|
70
|
+
const trimmed = projected.prose.trim();
|
|
71
|
+
return trimmed === "" ? undefined : projected.prose;
|
|
72
|
+
}
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type AdmittedCoderInvocation,
|
|
23
23
|
} from "./invocation.ts";
|
|
24
24
|
import {
|
|
25
|
-
|
|
25
|
+
buildAutoResumeContinuationPrompt,
|
|
26
26
|
loadResumableCoderRun,
|
|
27
27
|
markRunAdmitted,
|
|
28
28
|
type PublicResumeRequest,
|
|
@@ -198,7 +198,7 @@ export async function runPublicCoder(
|
|
|
198
198
|
: { correlationId: admitted.correlationId ?? env.correlationId }),
|
|
199
199
|
continuation: {
|
|
200
200
|
kind: "resume",
|
|
201
|
-
prompt:
|
|
201
|
+
prompt: buildAutoResumeContinuationPrompt({
|
|
202
202
|
packageRoot: env.packageRoot,
|
|
203
203
|
...pickEngineAxis(env),
|
|
204
204
|
}),
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
type AdmittedFixerInvocation,
|
|
26
26
|
} from "./invocation.ts";
|
|
27
27
|
import {
|
|
28
|
-
|
|
28
|
+
buildAutoResumeContinuationPrompt,
|
|
29
29
|
loadResumableFixerRun,
|
|
30
30
|
markRunAdmitted,
|
|
31
31
|
type PublicResumeRequest,
|
|
@@ -213,7 +213,7 @@ export async function runPublicFixer(
|
|
|
213
213
|
: { correlationId: admitted.correlationId ?? env.correlationId }),
|
|
214
214
|
continuation: {
|
|
215
215
|
kind: "resume",
|
|
216
|
-
prompt:
|
|
216
|
+
prompt: buildAutoResumeContinuationPrompt({
|
|
217
217
|
packageRoot: env.packageRoot,
|
|
218
218
|
...pickEngineAxis(env),
|
|
219
219
|
}),
|
|
@@ -239,14 +239,14 @@ export type AdmittedNotaryInvocation = AdmittedRoleInvocationBase & {
|
|
|
239
239
|
readonly sourceRun: NotarySourceRunLocator;
|
|
240
240
|
};
|
|
241
241
|
|
|
242
|
-
/**
|
|
242
|
+
/** Durable single-axis Reviewer lens. Parallel default is parse-only omission, never admitted. */
|
|
243
243
|
export type ReviewerLens = "completeness" | "correctness";
|
|
244
244
|
|
|
245
245
|
export type AdmittedReviewerInvocation = AdmittedRoleInvocationBase & {
|
|
246
246
|
readonly role: "reviewer";
|
|
247
247
|
/** Required fixed base revision for the pinned review target (ADR 0037). */
|
|
248
248
|
readonly baseRevision: string;
|
|
249
|
-
/**
|
|
249
|
+
/** Frozen single-axis shape for an ordinary Reviewer run; reused on resume. */
|
|
250
250
|
readonly lens: ReviewerLens;
|
|
251
251
|
/**
|
|
252
252
|
* Required durable authority references/URLs frozen at admission.
|
|
@@ -929,8 +929,11 @@ export type ParseReviewerArgvResult = {
|
|
|
929
929
|
attachmentPaths: string[];
|
|
930
930
|
/** Required fixed base revision for the pinned review target. */
|
|
931
931
|
baseRevision: string;
|
|
932
|
-
/**
|
|
933
|
-
|
|
932
|
+
/**
|
|
933
|
+
* Explicit single-axis override. Omitted public `--lens` is the internal
|
|
934
|
+
* parallel two-axis branch mark only — never persisted on an admitted run.
|
|
935
|
+
*/
|
|
936
|
+
lens?: ReviewerLens;
|
|
934
937
|
/** Repeatable durable authority references/URLs (exact order preserved; at least one). */
|
|
935
938
|
authorityRefs: string[];
|
|
936
939
|
project?: string;
|
|
@@ -1048,7 +1051,7 @@ export function isReviewerLens(value: unknown): value is ReviewerLens {
|
|
|
1048
1051
|
return value === "completeness" || value === "correctness";
|
|
1049
1052
|
}
|
|
1050
1053
|
|
|
1051
|
-
/**
|
|
1054
|
+
/** Admitted single-axis lens enum; public `--lens all` and bare omission are not admitted values. */
|
|
1052
1055
|
export function requireReviewerLens(value: string | undefined): ReviewerLens {
|
|
1053
1056
|
const trimmed = (value ?? "").trim();
|
|
1054
1057
|
if (!isReviewerLens(trimmed)) {
|
|
@@ -3073,7 +3076,7 @@ export function parseReviewerArgv(
|
|
|
3073
3076
|
continue;
|
|
3074
3077
|
}
|
|
3075
3078
|
if (taken.def.id === "lens") {
|
|
3076
|
-
//
|
|
3079
|
+
// Public override is single-axis only; omission stays undefined for the parallel branch.
|
|
3077
3080
|
lens = requireReviewerLens(taken.value);
|
|
3078
3081
|
continue;
|
|
3079
3082
|
}
|
|
@@ -3090,13 +3093,13 @@ export function parseReviewerArgv(
|
|
|
3090
3093
|
positional.push(token);
|
|
3091
3094
|
}
|
|
3092
3095
|
|
|
3093
|
-
//
|
|
3096
|
+
// Base and authority are required; omitted lens selects the parallel two-axis mode.
|
|
3094
3097
|
options.assertRequired();
|
|
3095
3098
|
return {
|
|
3096
3099
|
instruction: positional.join(" "),
|
|
3097
3100
|
attachmentPaths,
|
|
3098
3101
|
baseRevision: baseRevision!,
|
|
3099
|
-
lens: lens
|
|
3102
|
+
...(lens === undefined ? {} : { lens }),
|
|
3100
3103
|
authorityRefs,
|
|
3101
3104
|
...(project === undefined ? {} : { project }),
|
|
3102
3105
|
};
|
|
@@ -3110,12 +3113,13 @@ export type AdmitReviewerInvocationOptions = {
|
|
|
3110
3113
|
instruction: string;
|
|
3111
3114
|
attachmentPaths: readonly string[];
|
|
3112
3115
|
baseRevision: string;
|
|
3113
|
-
/**
|
|
3116
|
+
/** Explicit single-axis shape, frozen unchanged at admission and resume. */
|
|
3114
3117
|
lens: ReviewerLens;
|
|
3115
3118
|
/** Required durable authority references/URLs; frozen unchanged at admission. */
|
|
3116
3119
|
authorityRefs: readonly string[];
|
|
3117
3120
|
project?: string;
|
|
3118
3121
|
createRunId?: () => string;
|
|
3122
|
+
correlationId?: string;
|
|
3119
3123
|
/** Effective model for this invocation — written onto invocation.json. */
|
|
3120
3124
|
model?: InvocationEffectiveModel;
|
|
3121
3125
|
};
|
|
@@ -3175,6 +3179,9 @@ export async function admitReviewerInvocation(
|
|
|
3175
3179
|
baseRevision,
|
|
3176
3180
|
lens,
|
|
3177
3181
|
authorityRefs: [...authorityRefs],
|
|
3182
|
+
...(options.correlationId === undefined
|
|
3183
|
+
? {}
|
|
3184
|
+
: { correlationId: options.correlationId }),
|
|
3178
3185
|
attachments: attachments.map((a) => ({
|
|
3179
3186
|
provenancePath: a.provenancePath,
|
|
3180
3187
|
frozenPath: a.frozenPath,
|
|
@@ -3204,6 +3211,9 @@ export async function admitReviewerInvocation(
|
|
|
3204
3211
|
baseRevision,
|
|
3205
3212
|
lens,
|
|
3206
3213
|
authorityRefs,
|
|
3214
|
+
...(options.correlationId === undefined
|
|
3215
|
+
? {}
|
|
3216
|
+
: { correlationId: options.correlationId }),
|
|
3207
3217
|
};
|
|
3208
3218
|
}
|
|
3209
3219
|
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
loadResumableJudgeRun,
|
|
20
20
|
markRunAdmitted,
|
|
21
|
-
|
|
21
|
+
buildAutoResumeContinuationPrompt,
|
|
22
22
|
type PublicResumeRequest,
|
|
23
23
|
} from "./run-lifecycle.ts";
|
|
24
24
|
import {
|
|
@@ -152,7 +152,7 @@ export async function runPublicJudge(
|
|
|
152
152
|
: { correlationId: admitted.correlationId ?? env.correlationId }),
|
|
153
153
|
continuation: {
|
|
154
154
|
kind: "resume",
|
|
155
|
-
prompt:
|
|
155
|
+
prompt: buildAutoResumeContinuationPrompt({
|
|
156
156
|
packageRoot: env.packageRoot,
|
|
157
157
|
...pickEngineAxis(env),
|
|
158
158
|
}),
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
import {
|
|
25
25
|
loadResumableMergerRun,
|
|
26
26
|
markRunAdmitted,
|
|
27
|
-
|
|
27
|
+
buildAutoResumeContinuationPrompt,
|
|
28
28
|
type PublicResumeRequest,
|
|
29
29
|
} from "./run-lifecycle.ts";
|
|
30
30
|
import {
|
|
@@ -241,7 +241,7 @@ export async function runPublicMerger(
|
|
|
241
241
|
: { correlationId: admitted.correlationId ?? env.correlationId }),
|
|
242
242
|
continuation: {
|
|
243
243
|
kind: "resume",
|
|
244
|
-
prompt:
|
|
244
|
+
prompt: buildAutoResumeContinuationPrompt({
|
|
245
245
|
packageRoot: env.packageRoot,
|
|
246
246
|
...pickEngineAxis(env),
|
|
247
247
|
}),
|
|
@@ -552,12 +552,12 @@ const REVIEWER_OPTIONS = [
|
|
|
552
552
|
canonical: "--lens",
|
|
553
553
|
aliases: [],
|
|
554
554
|
valueMetavar: "completeness|correctness",
|
|
555
|
-
required:
|
|
555
|
+
required: false,
|
|
556
556
|
repeatable: false,
|
|
557
557
|
form: "option",
|
|
558
558
|
description: {
|
|
559
|
-
en: "
|
|
560
|
-
zh: "
|
|
559
|
+
en: "Optional single-lens override: completeness or correctness; omitted runs both in parallel.",
|
|
560
|
+
zh: "可选单 lens 覆盖:completeness 或 correctness;省略时并行运行两轴。",
|
|
561
561
|
},
|
|
562
562
|
},
|
|
563
563
|
{
|
|
@@ -1227,9 +1227,9 @@ const ROLE_COMMAND_HELP = {
|
|
|
1227
1227
|
},
|
|
1228
1228
|
reviewer: {
|
|
1229
1229
|
command: "reviewer",
|
|
1230
|
-
summary: "Fixed-target
|
|
1230
|
+
summary: "Fixed-target parallel two-lens review, with an optional single-lens override.",
|
|
1231
1231
|
usage: [
|
|
1232
|
-
"ak-role reviewer --base <revision> --lens completeness|correctness --authority-ref <ref> [options] <instruction>",
|
|
1232
|
+
"ak-role reviewer --base <revision> [--lens completeness|correctness] --authority-ref <ref> [options] <instruction>",
|
|
1233
1233
|
],
|
|
1234
1234
|
examples: [
|
|
1235
1235
|
'ak-role reviewer --base main --lens completeness --authority-ref docs/adr/0001.md "Review the branch."',
|
|
@@ -1280,7 +1280,7 @@ const ROLE_COMMAND_HELP = {
|
|
|
1280
1280
|
},
|
|
1281
1281
|
navigator: {
|
|
1282
1282
|
command: "navigator",
|
|
1283
|
-
summary: "Direct Navigator (游奕使) route advice
|
|
1283
|
+
summary: "Direct Navigator (游奕使) free-form prose route advice.",
|
|
1284
1284
|
usage: ["ak-role navigator [options] [instruction]"],
|
|
1285
1285
|
examples: [
|
|
1286
1286
|
'ak-role navigator "刚完成 coder apply 收敛,下一步?"',
|
|
@@ -10,6 +10,7 @@ import { writeFile } from "node:fs/promises";
|
|
|
10
10
|
import { isAbsolute, join, resolve } from "node:path";
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
+
buildAutoResumeContinuationPrompt,
|
|
13
14
|
buildResumeContinuationPrompt,
|
|
14
15
|
RESUME_TRANSPORT_ENVELOPE,
|
|
15
16
|
type PublicResumeRequest,
|
|
@@ -236,6 +237,12 @@ export type PostAdmissionEnv = {
|
|
|
236
237
|
freshSummons?: true;
|
|
237
238
|
/** Station child role run (#840): omit automatic navigator attendance. */
|
|
238
239
|
stationChild?: boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Ephemeral host-turn cwd for Reviewer fresh-copy execution (#946 统一新副本).
|
|
242
|
+
* Admission and durable projectRoot stay on the caller project; the host turn
|
|
243
|
+
* (initial and resume) runs in this sandbox when present.
|
|
244
|
+
*/
|
|
245
|
+
executionCwd?: string;
|
|
239
246
|
};
|
|
240
247
|
|
|
241
248
|
/**
|
|
@@ -1352,6 +1359,16 @@ export async function runPostAdmissionSeatResume<
|
|
|
1352
1359
|
afterAdmittedLoad?: (
|
|
1353
1360
|
admitted: A,
|
|
1354
1361
|
) => Promise<AfterAdmittedLoadResult<A, T>>;
|
|
1362
|
+
/**
|
|
1363
|
+
* After the single pre-lease load (and optional afterAdmittedLoad), prepare
|
|
1364
|
+
* call-local execution context. Reviewer fresh-copy (#946) mints an ephemeral
|
|
1365
|
+
* worktree from the admitted projectRoot here — reuses this load, does not
|
|
1366
|
+
* pre-load outside the coordinator. cleanup runs after dispatch settles.
|
|
1367
|
+
*/
|
|
1368
|
+
afterAdmittedPrepare?: (admitted: A) => Promise<{
|
|
1369
|
+
readonly env?: PostAdmissionEnv;
|
|
1370
|
+
readonly cleanup?: () => Promise<void>;
|
|
1371
|
+
}>;
|
|
1355
1372
|
effectiveEngine?: string;
|
|
1356
1373
|
}): Promise<{ exitCode: number; admitted?: A; terminal?: T }> {
|
|
1357
1374
|
let request = input.request;
|
|
@@ -1382,6 +1399,10 @@ export async function runPostAdmissionSeatResume<
|
|
|
1382
1399
|
adapters = prepared.adapters;
|
|
1383
1400
|
}
|
|
1384
1401
|
|
|
1402
|
+
// Call-local execution env; afterAdmittedPrepare may replace it (Reviewer fresh-copy).
|
|
1403
|
+
let env = input.env;
|
|
1404
|
+
let preparedCleanup: (() => Promise<void>) | undefined;
|
|
1405
|
+
|
|
1385
1406
|
const buildRequestAfterLease = async (): Promise<RoleTurnRequest> => {
|
|
1386
1407
|
let openCourtAttemptId: string | undefined;
|
|
1387
1408
|
// Build uses the admitted judged under this lease (rehydrated when open
|
|
@@ -1464,8 +1485,14 @@ export async function runPostAdmissionSeatResume<
|
|
|
1464
1485
|
// Court recovery / open under lease, then dispatch.
|
|
1465
1486
|
// Station-child same-ticket/same-parent resume is call-local auto-resume
|
|
1466
1487
|
// (#840 / #416). Public `ak-role resume` stays one-shot (ADR 0080).
|
|
1488
|
+
// afterAdmittedPrepare runs inside this try so mint failure and cleanup share one finally.
|
|
1467
1489
|
try {
|
|
1468
|
-
if (input.
|
|
1490
|
+
if (input.afterAdmittedPrepare !== undefined) {
|
|
1491
|
+
const prepared = await input.afterAdmittedPrepare(loaded.admitted);
|
|
1492
|
+
if (prepared.env !== undefined) env = prepared.env;
|
|
1493
|
+
preparedCleanup = prepared.cleanup;
|
|
1494
|
+
}
|
|
1495
|
+
if (env.stationChild === true) {
|
|
1469
1496
|
let firstTurn: RoleTurnRequest | undefined;
|
|
1470
1497
|
const stationAdapters = withOnceSuccessfulBeforeDispatch(adapters);
|
|
1471
1498
|
type StationChildAttempt = { readonly resumeTurn: boolean };
|
|
@@ -1477,14 +1504,14 @@ export async function runPostAdmissionSeatResume<
|
|
|
1477
1504
|
});
|
|
1478
1505
|
return await runWithAutoResumeLoop({
|
|
1479
1506
|
admitted: loaded.admitted,
|
|
1480
|
-
principalAuthority:
|
|
1507
|
+
principalAuthority: env.principalAuthority,
|
|
1481
1508
|
isPrincipalAvailable: resolveHostAwareSessionAvailability(
|
|
1482
|
-
|
|
1483
|
-
|
|
1509
|
+
env.host,
|
|
1510
|
+
env.principalAuthority,
|
|
1484
1511
|
),
|
|
1485
1512
|
io: input.io,
|
|
1486
|
-
sessionAppender:
|
|
1487
|
-
autoResumeLimit:
|
|
1513
|
+
sessionAppender: env.sessionAppender,
|
|
1514
|
+
autoResumeLimit: env.autoResumeLimit,
|
|
1488
1515
|
buildInitialPayload: (): StationChildAttempt => ({ resumeTurn: false }),
|
|
1489
1516
|
buildResumePayload: (): StationChildAttempt => ({ resumeTurn: true }),
|
|
1490
1517
|
// Same as public manual resume: prior-court sealed acceptance is not a
|
|
@@ -1522,7 +1549,7 @@ export async function runPostAdmissionSeatResume<
|
|
|
1522
1549
|
dispatch: async (turnRequest) => {
|
|
1523
1550
|
const result = await dispatchPostAdmissionTurn({
|
|
1524
1551
|
admitted: loaded.admitted,
|
|
1525
|
-
env
|
|
1552
|
+
env,
|
|
1526
1553
|
io: attemptIo,
|
|
1527
1554
|
request: turnRequest,
|
|
1528
1555
|
lease,
|
|
@@ -1534,7 +1561,7 @@ export async function runPostAdmissionSeatResume<
|
|
|
1534
1561
|
});
|
|
1535
1562
|
return settleDeferredPersist(
|
|
1536
1563
|
loaded.admitted,
|
|
1537
|
-
|
|
1564
|
+
env.principalAuthority,
|
|
1538
1565
|
stationAdapters,
|
|
1539
1566
|
attemptIo,
|
|
1540
1567
|
result,
|
|
@@ -1545,7 +1572,7 @@ export async function runPostAdmissionSeatResume<
|
|
|
1545
1572
|
}
|
|
1546
1573
|
return await runPostAdmissionManualResume({
|
|
1547
1574
|
admitted: loaded.admitted,
|
|
1548
|
-
env
|
|
1575
|
+
env,
|
|
1549
1576
|
io: input.io,
|
|
1550
1577
|
adapters,
|
|
1551
1578
|
...(input.effectiveEngine === undefined
|
|
@@ -1561,6 +1588,10 @@ export async function runPostAdmissionSeatResume<
|
|
|
1561
1588
|
return { exitCode: 2 };
|
|
1562
1589
|
}
|
|
1563
1590
|
throw error;
|
|
1591
|
+
} finally {
|
|
1592
|
+
if (preparedCleanup !== undefined) {
|
|
1593
|
+
await preparedCleanup();
|
|
1594
|
+
}
|
|
1564
1595
|
}
|
|
1565
1596
|
}
|
|
1566
1597
|
|
|
@@ -1594,7 +1625,7 @@ export async function runPostAdmissionOneShot<
|
|
|
1594
1625
|
...input.request,
|
|
1595
1626
|
continuation: {
|
|
1596
1627
|
kind: "resume",
|
|
1597
|
-
prompt:
|
|
1628
|
+
prompt: buildAutoResumeContinuationPrompt({
|
|
1598
1629
|
packageRoot: input.env.packageRoot,
|
|
1599
1630
|
...pickEngineAxis({
|
|
1600
1631
|
engine: input.effectiveEngine ?? input.env.engine,
|