@akagilnc/pi-workflow-roles 0.1.5079 → 0.1.5096

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.
Files changed (64) hide show
  1. package/dist/acp-host/production-host.js +348 -301
  2. package/dist/atomic-write.js +13 -19
  3. package/dist/auditor-soul.js +2 -1
  4. package/dist/doctor-contracts.js +1 -1
  5. package/dist/gatekeeper-role.js +2 -16
  6. package/dist/headless-host/production-host.js +380 -333
  7. package/dist/merger-contracts.js +1 -1
  8. package/dist/migrate-book-topology.js +18 -14
  9. package/dist/navigator-attendance.js +28 -143
  10. package/dist/navigator-invocation-identity.js +2 -0
  11. package/dist/navigator-public-session.js +13 -1
  12. package/dist/navigator-work-base.js +113 -0
  13. package/dist/notary-contracts.js +0 -2
  14. package/dist/package-contracts/fixer-output.js +1 -1
  15. package/dist/package-contracts/gatekeeper-output.js +1 -1
  16. package/dist/package-contracts/navigator-output.js +1 -1
  17. package/dist/package-contracts/terminating-infrastructure.js +3 -3
  18. package/dist/packaged-role-registry.js +9 -5
  19. package/dist/public-cli/countersign-run.js +1 -1
  20. package/dist/public-cli/invocation.js +6 -3
  21. package/dist/public-cli/main.js +46 -22
  22. package/dist/public-cli/settlement.js +20 -1
  23. package/dist/receipt-delivery-policy.js +9 -2
  24. package/dist/review-submission.js +3 -3
  25. package/docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md +9 -0
  26. package/extensions/role-runtime.ts +0 -27
  27. package/package.json +2 -1
  28. package/resources/836-deleted-machine-instruction-inventory.md +3 -3
  29. package/resources/collector-bot-handbook.md +4 -0
  30. package/resources/navigator-route-playbook.md +4 -6
  31. package/scripts/build-package.mjs +4 -0
  32. package/souls/coder-output-guide.md +1 -0
  33. package/src/auditor-soul.ts +2 -1
  34. package/src/collector-role.ts +6 -7
  35. package/src/collector-tool-schemas.ts +5 -5
  36. package/src/diarist-role.ts +2 -2
  37. package/src/doctor-contracts.ts +1 -1
  38. package/src/gatekeeper-role.ts +1 -18
  39. package/src/gleaner-left-role.ts +2 -2
  40. package/src/inspector-role.ts +1 -1
  41. package/src/judge-role.ts +1 -1
  42. package/src/merger-contracts.ts +1 -1
  43. package/src/navigator-attendance.ts +38 -164
  44. package/src/navigator-invocation-identity.ts +2 -0
  45. package/src/navigator-public-session.ts +17 -1
  46. package/src/navigator-role.ts +4 -0
  47. package/src/navigator-session-contracts.ts +2 -0
  48. package/src/navigator-work-base.ts +123 -0
  49. package/src/notary-contracts.ts +0 -4
  50. package/src/package-contracts/fixer-output.ts +1 -1
  51. package/src/package-contracts/gatekeeper-output.ts +3 -3
  52. package/src/package-contracts/navigator-output.ts +1 -1
  53. package/src/package-contracts/terminating-infrastructure.ts +3 -6
  54. package/src/packaged-role-registry.ts +5 -1
  55. package/src/public-cli/countersign-run.ts +1 -1
  56. package/src/public-cli/invocation.ts +6 -3
  57. package/src/public-cli/settlement.ts +21 -0
  58. package/src/receipt-delivery-policy.ts +9 -1
  59. package/src/review-submission.ts +3 -3
  60. package/src/reviewer-role.ts +3 -3
  61. package/src/role-runtime-dependencies.ts +1 -5
  62. package/src/role-runtime.ts +52 -38
  63. package/src/secretariat-role.ts +3 -3
  64. package/src/worker-role.ts +3 -3
@@ -351,8 +351,7 @@ export function createCollectorRoleRuntime(
351
351
  pi.registerTool({
352
352
  name: COLLECTOR_BIND_TARGET_TOOL,
353
353
  label: "通进司认票绑定",
354
- description:
355
- "角色判定任务材料后绑定本仓唯一 PR 目标。可提交 prNumber 或 issueNumber(线上关联唯一 PR);显式 --pr 已绑定时无需再调。多义或无法确定时会正确驳回,要求调用方明确 --pr。",
354
+ description: "绑定本仓唯一 PR 目标。",
356
355
  promptSnippet: "绑定角色判定的 issue/PR 目标",
357
356
  parameters: bindSchema,
358
357
  async execute(toolCallId: string, params: BindParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -433,7 +432,7 @@ export function createCollectorRoleRuntime(
433
432
  pi.registerTool({
434
433
  name: COLLECTOR_OBSERVE_TOOL,
435
434
  label: "通进司观察",
436
- description: "抓取配置目标的完整 GitHub PR 证据,存不可变快照入卷。正文在上下文中只给头部摘录加指针;需要头部之外的正文时,用 ak_collector_read 按 evidenceId 开卷;findings 的拆分与归类由你在交件时完成。目标未绑定前须先 ak_collector_bind_target。",
435
+ description: "抓取配置目标的 GitHub PR 证据。",
437
436
  promptSnippet: "抓取配置目标 PR 证据",
438
437
  parameters: observeSchema,
439
438
  async execute(toolCallId: string, _params: unknown, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -464,7 +463,7 @@ export function createCollectorRoleRuntime(
464
463
  pi.registerTool({
465
464
  name: COLLECTOR_READ_TOOL,
466
465
  label: "通进司开卷",
467
- description: "按 evidenceId 开卷读取一条已观测材料的全量正文与指针;只在观察头部摘录不足以判读时调用。",
466
+ description: "按 evidenceId 开卷读取一条已观测材料的全量正文与指针。",
468
467
  promptSnippet: "按指针开卷读材料",
469
468
  parameters: readSchema,
470
469
  async execute(toolCallId: string, params: ReadParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -499,7 +498,7 @@ export function createCollectorRoleRuntime(
499
498
  pi.registerTool({
500
499
  name: COLLECTOR_REQUEST_TOOL,
501
500
  label: "通进司请求",
502
- description: "在所引最新快照 HEAD 发一次请求。requestId 可取配置清单,或角色依手册/现场判定的稳定 id;后者须同时提供 body。",
501
+ description: "发一次请求。",
503
502
  promptSnippet: "发一次评审请求",
504
503
  parameters: requestSchema,
505
504
  async execute(toolCallId: string, params: RequestParams, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -625,7 +624,7 @@ export function createCollectorRoleRuntime(
625
624
  pi.registerTool({
626
625
  name: COLLECTOR_WAIT_TOOL,
627
626
  label: "通进司等待",
628
- description: "再观察前等待;实际睡眠不超过剩余等待窗。",
627
+ description: "等待。实际睡眠不超过剩余等待窗。",
629
628
  promptSnippet: "等待窗内等待",
630
629
  parameters: waitSchema,
631
630
  async execute(toolCallId: string, params: WaitParams, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -656,7 +655,7 @@ export function createCollectorRoleRuntime(
656
655
  pi.registerTool({
657
656
  name: COLLECTOR_OUTPUT_TOOL,
658
657
  label: "通进司输出",
659
- description: "观察完成后提交原交卷。正常完工提交空对象 {}(如需报 finding,填 findings 指针数组);仅在基础设施真实失败时才可填 infrastructureFailure,无失败时必须省略该字段。",
658
+ description: "提交通进司回执。",
660
659
  promptSnippet: "提交通进司回执",
661
660
  parameters: outputSchema,
662
661
  async execute(toolCallId: string, params: OutputParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -66,10 +66,10 @@ export const collectorOpenWaitWindowArgsSchema = Type.Object({
66
66
  */
67
67
  export const collectorBindTargetArgsSchema = Type.Object({
68
68
  prNumber: Type.Optional(Type.Unknown({
69
- description: "角色判定的本仓 PR 号(正整数)。与 issueNumber 二选一或同指唯一目标。形状指引,非 schema 闸。",
69
+ description: "本仓 PR 号(正整数)。",
70
70
  })),
71
71
  issueNumber: Type.Optional(Type.Unknown({
72
- description: "角色判定的本仓 issue 号(正整数);runtime 经线上关联解析唯一 PR。形状指引,非 schema 闸。",
72
+ description: "本仓 issue 号(正整数)。",
73
73
  })),
74
74
  }, { additionalProperties: true });
75
75
 
@@ -96,7 +96,7 @@ const collectorFindingItemDeclaration = (() => {
96
96
  {
97
97
  additionalProperties: true,
98
98
  description:
99
- "单条 finding 指针:evidenceId + 可选 category/summary。形状指引,非 schema 闸。",
99
+ "单条 finding 指针:evidenceId,以及可选 category、summary。",
100
100
  },
101
101
  );
102
102
  (item as unknown as { required: string[] }).required = [];
@@ -113,12 +113,12 @@ export const collectorOutputBaseSchema = openToolObject(
113
113
  // Nested item declarations ride `items` for registration preservation (ADR 0057).
114
114
  findings: Type.Unsafe({
115
115
  description:
116
- "本次收集到的逐条 findings(指针数组为规范形)。零 finding 的模板通知不得进入;正常完工无 finding 时省略。形状指引,非 schema 闸。",
116
+ "本次收集到的逐条 findings 指针。",
117
117
  items: collectorFindingItemDeclaration,
118
118
  }),
119
119
  unfinishedReasons: Type.Unknown({
120
120
  description:
121
- "未完成原因字符串数组(额度/故障/等待届满等现场依据);不得把未完成表述为无问题。无可报告时省略。形状指引,非 schema 闸。",
121
+ "未完成原因。",
122
122
  }),
123
123
  }),
124
124
  );
@@ -16,14 +16,14 @@ export type { DiaristOutput };
16
16
  export { validateRecordedDiaristOutput };
17
17
 
18
18
  /**
19
- * 起居郎交卷形状;形状指引,非 schema 闸。
19
+ * 起居郎交卷形状。
20
20
  * #901:交边界(sessions);正文与不可解析原字节由机械投影。
21
21
  */
22
22
  export const diaristOutputSchema = withTerminatingOutputDeclarations(
23
23
  openToolObject(
24
24
  Type.Object({
25
25
  status: Type.Unknown({
26
- description: "completed | escalate — 形状指引,非 schema 闸",
26
+ description: "completed | escalate",
27
27
  }),
28
28
  ticketNumber: Type.Optional(
29
29
  Type.Unknown({
@@ -73,7 +73,7 @@ const caseIdentity = Type.Object({ issueNumber: Type.Optional(Type.Integer()), r
73
73
  // (src/countersign-role.ts) — one shared description across both variants
74
74
  // so openToolObjectFromUnion's identical-declaration collapse drops none of it.
75
75
  const DOCTOR_STATUS_DESCRIPTION =
76
- "completed | refused — 形状指引,非 schema 闸;completed 允许空 findings;refused 仅当证据不足以支撑如实案证词" as const;
76
+ "completed | refused。completed 允许空 findings;refused 仅当证据不足以支撑如实案证词" as const;
77
77
  const doctorSubmissionVariants = Type.Union([
78
78
  Type.Object({
79
79
  status: Type.Unknown({ description: DOCTOR_STATUS_DESCRIPTION }),
@@ -9,7 +9,6 @@ import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
9
9
  import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
10
10
  import {
11
11
  GATEKEEPER_OUTPUT_TOOL_NAME,
12
- gatekeeperDecisionSchema,
13
12
  gatekeeperOutputSchema,
14
13
  } from "./package-contracts/gatekeeper-output.ts";
15
14
  import type { PublicSummonResult } from "./public-role-summons.ts";
@@ -161,7 +160,7 @@ export type SubmissionGateHostActions = {
161
160
  export const GATEKEEPER_TOOL_SPEC = {
162
161
  name: GATEKEEPER_OUTPUT_TOOL_NAME,
163
162
  label: "门下省决议",
164
- description: "门下省终局决议,状态为 dispatch 或 pass。",
163
+ description: "门下省终局决议。status 为 dispatch 或 pass。",
165
164
  promptSnippet: "门下省决议",
166
165
  parameters: gatekeeperOutputSchema,
167
166
  } as const;
@@ -170,22 +169,6 @@ export type GatekeeperRuntimeDependencies = {
170
169
  loadSoul(): Promise<string>;
171
170
  };
172
171
 
173
- function result(content: string, details: unknown) {
174
- return { content: [{ type: "text" as const, text: content }], details };
175
- }
176
-
177
- /** Gatekeeper province decision tool — open transport; package-contract projection owns legality. */
178
- export function createGatekeeperOutputTool() {
179
- return {
180
- name: GATEKEEPER_OUTPUT_TOOL_NAME,
181
- description: "提交门下省派官决定。",
182
- parameters: gatekeeperDecisionSchema,
183
- async execute(_id: string, args: unknown) {
184
- return result(`已收 ${String((args as { status?: unknown })?.status)}`, args);
185
- },
186
- };
187
- }
188
-
189
172
  function failureReason(error: unknown): string {
190
173
  if (error instanceof AggregateError) return error.errors.map(failureReason).join("; ");
191
174
  return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
@@ -17,12 +17,12 @@ export { validateRecordedGleanerLeftOutput };
17
17
 
18
18
  // #836 r16 class 1: pointer/statement are LLM/human-read narrative content — no
19
19
  // code branches on their presence.
20
- /** 左拾遗弹章交卷形状;形状指引,非 schema 闸。 */
20
+ /** 左拾遗弹章交卷形状。 */
21
21
  export const gleanerLeftOutputSchema = withTerminatingOutputDeclarations(
22
22
  openToolObject(
23
23
  Type.Object({
24
24
  status: Type.Unknown({
25
- description: "completed — 形状指引,非 schema 闸",
25
+ description: "completed",
26
26
  }),
27
27
  findings: Type.Array(
28
28
  Type.Object(
@@ -13,7 +13,7 @@ export { INSPECTOR_OUTPUT_TOOL_NAME as INSPECTOR_OUTPUT_TOOL };
13
13
  export type { InspectorOutput };
14
14
  export { validateRecordedInspectorOutput };
15
15
 
16
- /** 台院事后察举交卷形状;形状指引,非 schema 闸。 */
16
+ /** 台院事后察举交卷形状。 */
17
17
  export const inspectorOutputSchema = reviewSubmissionSchema;
18
18
 
19
19
  export type InspectorOutputParameters = Static<typeof inspectorOutputSchema>;
package/src/judge-role.ts CHANGED
@@ -48,7 +48,7 @@ export function createJudgeRoleRuntime(
48
48
  pi.registerTool({
49
49
  name: JUDGE_OUTPUT_TOOL_NAME,
50
50
  label: "大理寺输出",
51
- description: "提交大理寺终局判词;受理前经符宝郎内闸与审刑院合规审核。",
51
+ description: "提交大理寺终局判词。",
52
52
  promptSnippet: "提交大理寺终局判词",
53
53
  parameters: judgeVerdictSchema,
54
54
  async execute(toolCallId: string, parameters: Static<typeof judgeVerdictSchema>, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
@@ -20,7 +20,7 @@ export const mergerInputSchema = Type.Object({
20
20
  // #836 (ADR 0003 Amendment): status kept open like countersignStatus
21
21
  // (src/countersign-role.ts) — one shared description across both variants
22
22
  // so openToolObjectFromUnion's identical-declaration collapse drops none of it.
23
- const MERGER_STATUS_DESCRIPTION = "completed | escalate — 形状指引,非 schema 闸" as const;
23
+ const MERGER_STATUS_DESCRIPTION = "completed | escalate" as const;
24
24
  const mergerOutputVariants = Type.Union([
25
25
  Type.Object({ status: Type.Unknown({ description: MERGER_STATUS_DESCRIPTION }), attemptId: Type.String({ description: "已受理合并 attempt 身份" }), report: Type.String({ description: "如实结果报告" }), mergeCommitId: Type.String({ description: "完成合并 commit object ID" }) }, { additionalProperties: false }),
26
26
  Type.Object({ status: Type.Unknown({ description: MERGER_STATUS_DESCRIPTION }), attemptId: Type.String({ description: "已受理合并 attempt 身份" }), diagnosis: Type.String({ description: "合并无法或不应由本席完成的原因(含无进行中合并、无活可干、需新的产品/权力决定)" }), report: Type.String({ description: "如实结果报告" }) }, { additionalProperties: false }),
@@ -8,6 +8,7 @@ import { Type, type Static } from "typebox";
8
8
 
9
9
  import {
10
10
  NAVIGATOR_INVOCATION_ENTRY,
11
+ NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY,
11
12
  mintNavigatorInvocationId,
12
13
  } from "./navigator-invocation-identity.ts";
13
14
  import { PACKAGED_ROLE_REGISTRY, type PackagedRole } from "./packaged-role-registry.ts";
@@ -59,11 +60,12 @@ export {
59
60
  export { createNativeNavigatorSessionFactory };
60
61
  export { resolveNavigatorSeatSelection };
61
62
  import { issueRoot, subjectPath } from "./work-subject-identity.ts";
62
- import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
63
+ import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE } from "./receipt-delivery-policy.ts";
63
64
  import { navigatorProseFromUnknown } from "./package-contracts/navigator-output.ts";
65
+ import { persistNavigatorWorkBase } from "./navigator-work-base.ts";
64
66
 
65
67
  export const NAVIGATOR_EVENT_TYPE = "ak-navigator-attendance" as const;
66
- export const NAVIGATOR_PREPARE_ACCEPTED_TEXT = "游奕使准备已接受";
68
+ export { NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY };
67
69
 
68
70
  /**
69
71
  * Role-input document bytes win verbatim over work-root file authority when non-empty.
@@ -131,23 +133,11 @@ export type NavigatorEvent = {
131
133
  arrivalMessage?: string;
132
134
  };
133
135
 
134
- export type NavigatorSettlementFact = NavigatorSettlement & { invocationId: string; subjectKey: string };
135
- export type NavigatorContextProjection = {
136
- subjectKey: string;
137
- subject: string;
138
- authority: string;
139
- currentRole: { role: string; phase: NavigatorPhase };
140
- /** Present on the unique settlement-bound advice prompt. */
141
- currentSettlement?: NavigatorSettlement;
142
- publicSettlementHistory: NavigatorSettlementFact[];
143
- liveRoleHelp: Array<{ role: NavigatorTargetRole; help: string }>;
144
- };
145
-
146
136
  // #959: prepare is a prose vehicle. Object root only (ADR 0060); nested shape is
147
137
  // never a gate — every object root reaches execute exactly once (Rule 0).
148
138
  const prepareSchema = Type.Object({
149
139
  prose: Type.Optional(Type.Unknown({
150
- description: "游奕使散文建议,原样呈现。不要求 candidates/next 结构。非受理闸",
140
+ description: "游奕使散文建议,原样呈现。不要求 candidates/next 结构。",
151
141
  })),
152
142
  }, { additionalProperties: true });
153
143
  type PrepareOutput = Static<typeof prepareSchema>;
@@ -157,9 +147,6 @@ export type NavigatorAttendanceOptions = {
157
147
  role: string;
158
148
  phase: NavigatorPhase;
159
149
  subjectKey: string;
160
- loadSoul: () => Promise<string>;
161
- loadRoutePlaybook?: () => Promise<string>;
162
- loadRoleHelp: (role: NavigatorTargetRole) => Promise<string>;
163
150
  createSession: NavigatorSessionFactory;
164
151
  modelSettingPath?: string;
165
152
  subject: string;
@@ -170,7 +157,6 @@ export type NavigatorAttendanceOptions = {
170
157
  onEvent: (event: NavigatorEvent, report: NavigatorReport) => void | Promise<void>;
171
158
  };
172
159
 
173
- const CONTEXT_ENTRY = "ak-navigator-context";
174
160
  const INVOCATION_ENTRY = NAVIGATOR_INVOCATION_ENTRY;
175
161
  const SETTLEMENT_ENTRY = "ak-navigator-settlement";
176
162
  const unavailableKeys = new Set<NavigatorUnavailableKey>(["context", "session", "model", "thinking", "auth", "quota", "transport", "unknown"]);
@@ -263,7 +249,7 @@ export function createNavigatorPrepareTool(onOutput: (value: PrepareOutput) => v
263
249
  async execute(_id, value) {
264
250
  // Rule 0: the unique prepare submission is accepted once. Shape is never a gate.
265
251
  onOutput(value as PrepareOutput);
266
- return { content: [{ type: "text" as const, text: NAVIGATOR_PREPARE_ACCEPTED_TEXT }], details: value, terminate: true as const };
252
+ return { content: [], details: value, terminate: true as const };
267
253
  },
268
254
  };
269
255
  }
@@ -271,7 +257,7 @@ export function createNavigatorPrepareTool(onOutput: (value: PrepareOutput) => v
271
257
  export function formatNavigatorReport(report: NavigatorReport): string {
272
258
  const playbookFailure = report.routePlaybookReadFailure === undefined
273
259
  ? []
274
- : [`路书读取失败:${report.routePlaybookReadFailure}`];
260
+ : [report.routePlaybookReadFailure];
275
261
  if (report.disposition === "no-advice") return playbookFailure.join("\n");
276
262
  if (report.disposition === "unavailable") {
277
263
  return [...playbookFailure, ...(report.unavailableReason ? [report.unavailableReason] : [])].join("\n");
@@ -300,16 +286,13 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
300
286
  let settlementTail: Promise<void> = Promise.resolve();
301
287
  let settlementFailure: unknown;
302
288
  let preparationFailure: unknown;
303
- let routePlaybookReadFailure: string | undefined;
289
+ let observedRoutePlaybookFailure: string | undefined;
304
290
  let disposed = false;
305
291
  /** In-flight session teardown; repeat dispose returns the same promise (#959 mutation proof). */
306
292
  let closing: Promise<void> | undefined;
307
293
  // Shared attendance seam owns nested summon cancel (ADR 0018 / #959).
308
294
  // Role session factory only forwards HostContext.signal — never its own controller.
309
295
  const nestCancel = new AbortController();
310
- /** One-shot live-help warm; consumed by the next prepare so later prepares reread live help. */
311
- let warmedHelp: Promise<Array<{ role: NavigatorTargetRole; help: string }>> | undefined;
312
-
313
296
  const sessionHostContext = (): HostContext => {
314
297
  const parentSignal = options.context.signal;
315
298
  const signal = parentSignal === undefined
@@ -318,19 +301,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
318
301
  return { ...options.context, signal };
319
302
  };
320
303
 
321
- const loadLiveHelp = async (): Promise<Array<{ role: NavigatorTargetRole; help: string }>> => {
322
- try {
323
- return await Promise.all(
324
- NAVIGATOR_TARGETS.map(async ({ role }) => ({
325
- role,
326
- help: await options.loadRoleHelp(role),
327
- })),
328
- );
329
- } catch (error) {
330
- throw navigatorUnavailableError("transport", error);
331
- }
332
- };
333
-
334
304
  const unavailable = (invocationId: string, reason: unknown): NavigatorReport => {
335
305
  const failure = reason instanceof NavigatorUnavailableError
336
306
  ? reason
@@ -342,24 +312,13 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
342
312
  unavailableCause: failure.unavailableCause,
343
313
  };
344
314
  };
345
- let routePlaybookSettlement: Promise<void> | undefined;
346
315
  /**
347
- * Settlement fed for the output prompt. Early prepare() runs the host round
348
- * from parent start (ready and wait); settleOnce feeds currentSettlement and
349
- * lets navigator speak. No candidates/next parse (owner 2026-09-17 #959).
316
+ * Settlement is the only model round. Standby records attendance and does not prompt.
317
+ * Soul and route playbook stay on the navigator system prompt, not this user turn.
350
318
  */
351
319
  let prepareBoundSettlement: NavigatorSettlement | undefined;
352
- type MaterialsBundle = {
353
- soul: string;
354
- routePlaybook: string;
355
- helpContext: string;
356
- modelSetting: string;
357
- model: ReturnType<typeof parseNavigatorModelSetting>;
358
- help: Array<{ role: NavigatorTargetRole; help: string }>;
359
- activeSession: NavigatorPreparationSession;
360
- };
361
- /** Materials + session only — no host prompt (cold settle book-before-feed). */
362
- const loadMaterialsAndSession = async (invocationId: string): Promise<MaterialsBundle> => {
320
+ /** Session only — no host prompt (standby attendance, or cold settle book-before-feed). */
321
+ const loadMaterialsAndSession = async (invocationId: string): Promise<NavigatorPreparationSession> => {
363
322
  if (contextError !== undefined) throw navigatorUnavailableError("context", contextError);
364
323
  if (typeof authority !== "string" || authority.trim() === "") {
365
324
  throw navigatorUnavailableError(
@@ -367,30 +326,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
367
326
  new Error("controlling authority content was not supplied as typed work context"),
368
327
  );
369
328
  }
370
- let soul: string;
371
- let modelSetting: string;
372
- let help: Array<{ role: NavigatorTargetRole; help: string }>;
373
- let routePlaybook = "";
374
- routePlaybookReadFailure = undefined;
375
- const soulPromise = (async () => {
376
- try {
377
- const text = (await options.loadSoul()).trim();
378
- if (!text) throw new Error("Navigator soul is empty");
379
- return text;
380
- } catch (error) {
381
- throw navigatorUnavailableError("context", error);
382
- }
383
- })();
384
- const routePlaybookPromise = (async () => {
385
- if (options.loadRoutePlaybook === undefined) return "";
386
- try {
387
- return await options.loadRoutePlaybook();
388
- } catch (error) {
389
- routePlaybookReadFailure = error instanceof Error ? error.message : String(error);
390
- return "";
391
- }
392
- })();
393
- routePlaybookSettlement = routePlaybookPromise.then(() => undefined);
394
329
  const modelPromise = (async () => {
395
330
  try {
396
331
  const resolved = await resolveNavigatorSeatSelection(options.context);
@@ -400,21 +335,13 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
400
335
  throw navigatorUnavailableError("model", error);
401
336
  }
402
337
  })();
403
- const helpPromise = warmedHelp ?? loadLiveHelp();
404
- warmedHelp = undefined;
405
- [soul, routePlaybook, modelSetting, help] = await Promise.all([
406
- soulPromise,
407
- routePlaybookPromise,
408
- modelPromise,
409
- helpPromise,
410
- ]);
338
+ const modelSetting = await modelPromise;
411
339
  let model: ReturnType<typeof parseNavigatorModelSetting>;
412
340
  try {
413
341
  model = parseNavigatorModelSetting(modelSetting);
414
342
  } catch (error) {
415
343
  throw navigatorUnavailableError("model", error);
416
344
  }
417
- const helpContext = help.map(({ role, help: text }) => `<role_help role="${role}">\n${text}\n</role_help>`).join("\n");
418
345
  const tool = createNavigatorPrepareTool((value) => { outputSink?.(value); });
419
346
  if (session === undefined) {
420
347
  sessionReady = (async () => {
@@ -457,9 +384,9 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
457
384
  }
458
385
  if (disposed) throw navigatorUnavailableError("session", new Error("Navigator attendance was disposed"));
459
386
  if (session === undefined) throw new Error("Navigator session was not created");
460
- return { soul, routePlaybook, helpContext, modelSetting, model, help, activeSession: session };
387
+ return session;
461
388
  };
462
- const prepare = async (materialsBundle?: MaterialsBundle): Promise<string | undefined> => {
389
+ const prepare = async (existingSession?: NavigatorPreparationSession): Promise<string | undefined> => {
463
390
  // Exact principal is owned by shared lifecycle (or one mint per attendance).
464
391
  // Model/tool/advice paths cannot override it; role-session persistence is
465
392
  // pi.appendEntry at lifecycle start — not optional sessionManager probing.
@@ -480,53 +407,14 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
480
407
  const merged = [prior, next].filter((part) => part.trim() !== "").join("\n\n");
481
408
  output = { prose: merged };
482
409
  };
483
- // Cold settle passes its first-load bundle; early/warm prepare loads here.
484
- const { soul, routePlaybook, helpContext, help, activeSession } =
485
- materialsBundle ?? await loadMaterialsAndSession(invocationId);
486
- const publicSettlementHistory = activeSession.entries()
487
- .filter((entry): entry is { type: "custom"; customType: string; data?: unknown } => exactRecord(entry) && entry.type === "custom" && entry.customType === SETTLEMENT_ENTRY && exactRecord(entry.data))
488
- .map((entry) => entry.data as NavigatorSettlementFact);
489
- // Prior advice is the host session's own history (CLI resume), not a package ledger.
490
- const projection: NavigatorContextProjection = {
410
+ const activeSession = existingSession ?? await loadMaterialsAndSession(invocationId);
411
+ // Standby records the invocation entry and does not call the model.
412
+ if (boundSettlement === undefined) return undefined;
413
+ const request = JSON.stringify({
414
+ ...boundSettlement,
415
+ invocationId,
491
416
  subjectKey,
492
- subject,
493
- authority,
494
- currentRole: { role: options.role, phase: options.phase },
495
- ...(boundSettlement === undefined ? {} : { currentSettlement: boundSettlement }),
496
- publicSettlementHistory,
497
- liveRoleHelp: help,
498
- };
499
- activeSession.appendEntry(CONTEXT_ENTRY, projection);
500
- // Early (parent start): full host round — ready and wait. Settlement-bound:
501
- // feed the result and let navigator speak (owner 2026-09-17 #959).
502
- // ADR 0073: phase behavior text lives in resources/navigator-route-playbook.md;
503
- // code only binds a neutral phase pointer + typed materials.
504
- const request = boundSettlement === undefined
505
- ? [
506
- "本轮为待命轮。",
507
- `<navigator_soul>\n${soul}\n</navigator_soul>`,
508
- ...(routePlaybookReadFailure === undefined
509
- ? [`<route_playbook>\n${routePlaybook}\n</route_playbook>`]
510
- : ["可选路线手册未能读取。"]),
511
- `<work_subject>\n${subject}\n</work_subject>`,
512
- `<controlling_authority>\n${authority}\n</controlling_authority>`,
513
- `<current_role>\n${JSON.stringify({ role: options.role, phase: options.phase })}\n</current_role>`,
514
- `<public_settlement_history>\n${JSON.stringify(projection.publicSettlementHistory)}\n</public_settlement_history>`,
515
- `<live_role_help>\n${helpContext}\n</live_role_help>`,
516
- ].join("\n\n")
517
- : [
518
- "本轮为结算投喂轮。",
519
- `<navigator_soul>\n${soul}\n</navigator_soul>`,
520
- ...(routePlaybookReadFailure === undefined
521
- ? [`<route_playbook>\n${routePlaybook}\n</route_playbook>`]
522
- : ["可选路线手册未能读取。"]),
523
- `<work_subject>\n${subject}\n</work_subject>`,
524
- `<controlling_authority>\n${authority}\n</controlling_authority>`,
525
- `<current_role>\n${JSON.stringify({ role: options.role, phase: options.phase })}\n</current_role>`,
526
- `<current_settlement>\n${JSON.stringify(boundSettlement)}\n</current_settlement>`,
527
- `<public_settlement_history>\n${JSON.stringify(projection.publicSettlementHistory)}\n</public_settlement_history>`,
528
- `<live_role_help>\n${helpContext}\n</live_role_help>`,
529
- ].join("\n\n");
417
+ });
530
418
  try {
531
419
  try {
532
420
  if (disposed) throw navigatorUnavailableError("session", new Error("Navigator attendance was disposed"));
@@ -535,6 +423,7 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
535
423
  // (navigator-public-session). No assistant-entry harvest — entries() is
536
424
  // archivist custom-only on the wired factory (#959).
537
425
  const promptAllowingRejectedPrepare = async (text: string, deliveryRequest: boolean) => {
426
+ await persistNavigatorWorkBase(activeSession.recordPointer(), { subject, authority });
538
427
  const entryStart = activeSession.entries().length;
539
428
  prepareBatchRejected = false;
540
429
  let promptFailure: unknown;
@@ -572,7 +461,7 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
572
461
  // does not 催交 final advice (owner: prepare then wait for settlement feed).
573
462
  if (boundSettlement !== undefined) {
574
463
  while (output === undefined && prepareBatchRejected && delivery.nextAction() === "request-delivery") {
575
- await promptAllowingRejectedPrepare(RECEIPT_DELIVERY_PROMPT, true);
464
+ await promptAllowingRejectedPrepare(JSON.stringify(delivery.deliveryState()), true);
576
465
  }
577
466
  if (output === undefined && delivery.nextAction() === "request-delivery") {
578
467
  while (delivery.nextAction() === "request-delivery") {
@@ -618,6 +507,10 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
618
507
  preparedProse = normalizePrepareProse(output);
619
508
  return preparedProse;
620
509
  } finally {
510
+ const playbookFailure = activeSession.routePlaybookReadFailure?.();
511
+ observedRoutePlaybookFailure = typeof playbookFailure === "string" && playbookFailure.trim() !== ""
512
+ ? playbookFailure
513
+ : undefined;
621
514
  outputSink = undefined;
622
515
  }
623
516
  };
@@ -636,17 +529,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
636
529
  contextError = next.contextError;
637
530
  return closing;
638
531
  },
639
- /**
640
- * Start live-help subprocesses during activation without beginning full
641
- * preparation. Next prepare() consumes the warm result; a later prepare
642
- * reloads so live help edits remain visible.
643
- */
644
- warmHelp(): void {
645
- if (disposed || warmedHelp !== undefined || preparation !== undefined) return;
646
- warmedHelp = loadLiveHelp();
647
- // Prevent unhandled rejection if attendance is disposed before prepare.
648
- void warmedHelp.catch(() => undefined);
649
- },
650
532
  prepare(): void {
651
533
  if (disposed || preparation !== undefined) return;
652
534
  preparationFailure = undefined;
@@ -662,9 +544,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
662
544
  isPreparing(): boolean {
663
545
  return preparationInFlight;
664
546
  },
665
- knownRoutePlaybookReadFailure(): string | undefined {
666
- return routePlaybookReadFailure;
667
- },
668
547
  settle(settlement: NavigatorSettlement): Promise<void> {
669
548
  const next = settlementTail.then(() => settleOnce(settlement));
670
549
  // Contract: README.md#Navigator-attendance — rejected attendance settlements are drained only to serialize later attendance; retain the exact rejection for the caller/audit path.
@@ -698,10 +577,11 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
698
577
  async function settleOnce(settlement: NavigatorSettlement): Promise<void> {
699
578
  // Dispose during post-role grace must ignore late completion entirely (#675).
700
579
  if (disposed) return;
580
+ observedRoutePlaybookFailure = undefined;
701
581
  const invocationId = activeInvocationId ?? invocationPrincipal;
702
582
  let report: NavigatorReport;
703
- // Drain in-flight early prepare (parent-start host round: ready and wait).
704
- // Then feed settlement for the output prompt on the same host session.
583
+ // Drain in-flight standby attendance (record only). Then feed the typed
584
+ // settlement as the only model round on the same host session.
705
585
  if (sessionReady !== undefined) {
706
586
  try { await sessionReady; } catch (error) { preparationFailure ??= error; }
707
587
  }
@@ -740,12 +620,12 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
740
620
  // session already exists so the output prompt can run (owner: 准备好了就等着,
741
621
  // 结果出来喂给它让它自己输出).
742
622
  if (session !== undefined) preparationFailure = undefined;
743
- // Cold settle: materials+session only (no unbound host prompt), then book + feed.
744
- // Pass the bundle into prepare so loadLiveHelp/setModel/INVOCATION run once.
745
- let coldMaterials: MaterialsBundle | undefined;
623
+ // Cold settle: session only (no unbound host prompt), then book + feed.
624
+ // Pass the session into prepare so setModel/INVOCATION run once.
625
+ let coldSession: NavigatorPreparationSession | undefined;
746
626
  if (session === undefined && preparationFailure === undefined) {
747
627
  try {
748
- coldMaterials = await loadMaterialsAndSession(invocationId);
628
+ coldSession = await loadMaterialsAndSession(invocationId);
749
629
  } catch (error) {
750
630
  preparationFailure = error;
751
631
  }
@@ -753,7 +633,7 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
753
633
  session?.appendEntry(SETTLEMENT_ENTRY, settlementFact);
754
634
  if (preparationFailure === undefined) {
755
635
  prepareBoundSettlement = settlement;
756
- preparation = prepare(coldMaterials);
636
+ preparation = prepare(coldSession);
757
637
  void preparation.catch((error) => { preparationFailure = error; });
758
638
  try {
759
639
  drainedProse = await preparation;
@@ -777,12 +657,8 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
777
657
  report = { disposition: "no-advice" };
778
658
  }
779
659
  }
780
- // A primary preparation failure may reject Promise.all before the optional
781
- // routebook read finishes. Preserve that primary unavailable cause while
782
- // waiting for, and independently attaching, the routebook diagnostic.
783
- await routePlaybookSettlement;
784
- if (routePlaybookReadFailure !== undefined) {
785
- report = { ...report, routePlaybookReadFailure };
660
+ if (observedRoutePlaybookFailure !== undefined) {
661
+ report = { ...report, routePlaybookReadFailure: observedRoutePlaybookFailure };
786
662
  }
787
663
  const event: NavigatorEvent = {
788
664
  version: 1,
@@ -813,8 +689,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
813
689
  sessionReady = undefined;
814
690
  preparedProse = undefined;
815
691
  preparationFailure = undefined;
816
- routePlaybookSettlement = undefined;
817
- routePlaybookReadFailure = undefined;
818
692
  }
819
693
  }
820
694
 
@@ -29,6 +29,8 @@ import { isUuidV7, uuidv7 } from "./uuidv7.ts";
29
29
  import { workSubjectKeysEqual } from "./work-subject-identity.ts";
30
30
 
31
31
  export const NAVIGATOR_INVOCATION_ENTRY = "ak-navigator-invocation" as const;
32
+ /** Native playbook read failure recorded by the navigator process that read the file. */
33
+ export const NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY = "ak-navigator-route-playbook-failure" as const;
32
34
 
33
35
  /** Typed durable infrastructure-failure fact on a packaged role output toolResult. */
34
36
  export const NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND = "role_infrastructure_failure" as const;