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