@akagilnc/pi-workflow-roles 0.1.5368 → 0.1.5382

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 (188) hide show
  1. package/dist/acp-host/production-host.js +1192 -1195
  2. package/dist/activation-ledger-git.js +2 -2
  3. package/dist/activation-ledger-topology.js +1 -7
  4. package/dist/analyst-entry.js +4 -8
  5. package/dist/analyst-gate-cycles-read.js +3 -11
  6. package/dist/analyst-index.js +3 -9
  7. package/dist/analyst-ledger.js +1 -11
  8. package/dist/analyst-metric-families/acceptance-success-rework.js +1 -3
  9. package/dist/analyst-metric-families/round-timeline.js +1 -3
  10. package/dist/analyst-page.js +2 -2
  11. package/dist/archivist-record-topology.js +2 -2
  12. package/dist/audit-escalation.js +2 -1
  13. package/dist/auditor-soul.js +2 -1
  14. package/dist/collector-config.js +6 -8
  15. package/dist/collector-tool-schemas.js +18 -0
  16. package/dist/compliance-transport.js +2 -1
  17. package/dist/countersign-contracts.js +2 -1
  18. package/dist/diarist-contracts.js +29 -2
  19. package/dist/doctor-contracts.js +1 -3
  20. package/dist/doctor-evidence.js +7 -8
  21. package/dist/dossier-resolution.js +1 -3
  22. package/dist/engine-detour-usage.js +1 -3
  23. package/dist/filed-submission.js +28 -0
  24. package/dist/gatekeeper-role.js +8 -19
  25. package/dist/gleaner-left-contracts.js +18 -1
  26. package/dist/headless-host/description.js +14 -17
  27. package/dist/headless-host/production-host.js +1210 -1215
  28. package/dist/host-session-record.js +3 -2
  29. package/dist/inspector-contracts.js +2 -1
  30. package/dist/judge-role.js +8 -16
  31. package/dist/ledger-session-read.js +1 -3
  32. package/dist/merger-contracts.js +9 -9
  33. package/dist/migrate-book-topology.js +610 -321
  34. package/dist/navigator-attendance.js +9 -11
  35. package/dist/navigator-invocation-identity.js +3 -2
  36. package/dist/navigator-public-session.js +3 -5
  37. package/dist/navigator-session-contracts.js +8 -10
  38. package/dist/navigator-work-base.js +5 -8
  39. package/dist/notary-contracts.js +3 -5
  40. package/dist/package-contracts/auditor-output.js +1 -3
  41. package/dist/package-contracts/fixer-packet.js +3 -6
  42. package/dist/package-contracts/gatekeeper-output.js +1 -3
  43. package/dist/package-contracts/navigator-output.js +8 -8
  44. package/dist/package-contracts/reviewer-output.js +16 -0
  45. package/dist/package-contracts/terminating-infrastructure.js +5 -9
  46. package/dist/package-contracts/terminating-tools.js +4 -3
  47. package/dist/package-contracts/worker-output.js +18 -2
  48. package/dist/public-cli/analyst-run.js +3 -2
  49. package/dist/public-cli/auto-resume.js +3 -8
  50. package/dist/public-cli/cli.js +9 -8
  51. package/dist/public-cli/config.js +8 -11
  52. package/dist/public-cli/countersign-run.js +1 -3
  53. package/dist/public-cli/host-providers.js +7 -14
  54. package/dist/public-cli/instruction-seat-run.js +12 -14
  55. package/dist/public-cli/invocation.js +9 -8
  56. package/dist/public-cli/main.js +1820 -1599
  57. package/dist/public-cli/post-admission.js +4 -3
  58. package/dist/public-cli/run-lifecycle.js +14 -26
  59. package/dist/public-cli/settlement.js +10 -26
  60. package/dist/public-role-summons.js +3 -2
  61. package/dist/receipt-delivery-policy.js +1 -3
  62. package/dist/role-run-placement.js +3 -5
  63. package/dist/role-run-relocation.js +12 -19
  64. package/dist/role-submission-declarations.js +131 -0
  65. package/dist/run-terminal-artifacts.js +1 -11
  66. package/dist/run-ticket-number.js +2 -4
  67. package/dist/secretariat-contracts.js +23 -1
  68. package/dist/session-assistant-usage.js +2 -1
  69. package/dist/session-dialogue.js +1 -14
  70. package/dist/sitian-reader.js +2 -4
  71. package/dist/submission-correctable-error.js +2 -1
  72. package/dist/submission-errors.js +2 -1
  73. package/dist/submission-gate.js +6 -8
  74. package/dist/submission-ledger.js +4 -5
  75. package/dist/ticket-provenance-contracts.js +1 -3
  76. package/dist/ticket-provenance.js +2 -1
  77. package/dist/typed-provider-http.js +4 -7
  78. package/dist/unknown-value.js +28 -0
  79. package/dist/user-dialogue-stdin.js +2 -1
  80. package/package.json +1 -1
  81. package/src/acp-host/role-turn-host.ts +4 -2
  82. package/src/activation-ledger-git.ts +3 -2
  83. package/src/activation-ledger-topology.ts +4 -12
  84. package/src/analyst-entry.ts +3 -11
  85. package/src/analyst-gate-cycles-read.ts +3 -15
  86. package/src/analyst-index.ts +3 -13
  87. package/src/analyst-ledger.ts +2 -16
  88. package/src/analyst-metric-families/acceptance-success-rework.ts +2 -4
  89. package/src/analyst-metric-families/round-timeline.ts +2 -4
  90. package/src/analyst-page.ts +2 -2
  91. package/src/archivist-record-topology.ts +2 -2
  92. package/src/audit-escalation.ts +2 -1
  93. package/src/auditor-role.ts +2 -8
  94. package/src/auditor-soul.ts +3 -1
  95. package/src/book-topology-migration-placement.ts +5 -16
  96. package/src/book-topology-migrators-t11.ts +16 -15
  97. package/src/book-topology-mixed-volume-migrators.ts +3 -6
  98. package/src/book-topology-record-class-migrators.ts +7 -10
  99. package/src/book-topology-runs-migrator.ts +4 -3
  100. package/src/collector-config.ts +7 -9
  101. package/src/collector-role.ts +5 -28
  102. package/src/compliance-transport.ts +2 -1
  103. package/src/countersign-contracts.ts +2 -1
  104. package/src/countersign-role.ts +2 -7
  105. package/src/diarist-contracts.ts +40 -2
  106. package/src/diarist-role.ts +4 -44
  107. package/src/doctor-contracts.ts +3 -1
  108. package/src/doctor-evidence.ts +8 -7
  109. package/src/doctor-role.ts +20 -13
  110. package/src/dossier-resolution.ts +2 -4
  111. package/src/engine-detour-tool.ts +0 -2
  112. package/src/engine-detour-usage.ts +2 -4
  113. package/src/external-host-turn-loop.ts +3 -1
  114. package/src/factory-board.ts +1 -8
  115. package/src/filed-submission.ts +44 -0
  116. package/src/gatekeeper-role.ts +8 -24
  117. package/src/gleaner-left-contracts.ts +29 -2
  118. package/src/gleaner-left-role.ts +4 -32
  119. package/src/headless-host/description.ts +15 -18
  120. package/src/headless-host/role-turn-host.ts +10 -9
  121. package/src/host-session-record.ts +4 -2
  122. package/src/inspector-contracts.ts +2 -1
  123. package/src/inspector-role.ts +2 -7
  124. package/src/judge-role.ts +12 -24
  125. package/src/ledger-session-read.ts +2 -5
  126. package/src/merger-contracts.ts +10 -9
  127. package/src/merger-role.ts +5 -8
  128. package/src/navigator-attendance.ts +10 -12
  129. package/src/navigator-invocation-identity.ts +3 -2
  130. package/src/navigator-public-session.ts +4 -6
  131. package/src/navigator-role.ts +2 -8
  132. package/src/navigator-session-contracts.ts +8 -11
  133. package/src/navigator-work-base.ts +5 -8
  134. package/src/navigator-work-context.ts +2 -1
  135. package/src/notary-contracts.ts +4 -6
  136. package/src/notary-role.ts +5 -20
  137. package/src/oauth-keepalive.ts +5 -18
  138. package/src/package-contracts/auditor-output.ts +2 -4
  139. package/src/package-contracts/fixer-packet.ts +4 -6
  140. package/src/package-contracts/gatekeeper-output.ts +2 -4
  141. package/src/package-contracts/judge-output.ts +0 -1
  142. package/src/package-contracts/navigator-output.ts +8 -8
  143. package/src/package-contracts/reviewer-output.ts +25 -0
  144. package/src/package-contracts/terminating-infrastructure.ts +6 -20
  145. package/src/package-contracts/terminating-tools.ts +8 -12
  146. package/src/package-contracts/worker-output.ts +43 -5
  147. package/src/pi/adapter.ts +0 -1
  148. package/src/pi/role-turn-host.ts +0 -1
  149. package/src/public-cli/analyst-run.ts +4 -2
  150. package/src/public-cli/auto-resume.ts +4 -11
  151. package/src/public-cli/cli.ts +10 -10
  152. package/src/public-cli/config.ts +8 -27
  153. package/src/public-cli/countersign-run.ts +2 -4
  154. package/src/public-cli/host-providers.ts +10 -27
  155. package/src/public-cli/instruction-seat-run.ts +14 -15
  156. package/src/public-cli/invocation.ts +11 -10
  157. package/src/public-cli/post-admission.ts +5 -3
  158. package/src/public-cli/run-lifecycle.ts +20 -41
  159. package/src/public-cli/settlement.ts +11 -33
  160. package/src/public-role-summons.ts +4 -3
  161. package/src/receipt-delivery-policy.ts +1 -4
  162. package/src/reviewer-role.ts +7 -36
  163. package/src/role-envelope.ts +5 -3
  164. package/src/role-run-placement.ts +4 -10
  165. package/src/role-run-relocation.ts +13 -23
  166. package/src/role-runtime.ts +16 -50
  167. package/src/role-submission-declarations.ts +143 -0
  168. package/src/run-terminal-artifacts.ts +2 -16
  169. package/src/run-ticket-number.ts +4 -10
  170. package/src/secretariat-contracts.ts +35 -0
  171. package/src/secretariat-role.ts +4 -40
  172. package/src/session-assistant-usage.ts +3 -1
  173. package/src/session-dialogue.ts +1 -4
  174. package/src/session-identity.ts +0 -1
  175. package/src/sitian-reader.ts +2 -4
  176. package/src/submission-correctable-error.ts +3 -1
  177. package/src/submission-errors.ts +2 -1
  178. package/src/submission-gate.ts +10 -9
  179. package/src/submission-ledger.ts +5 -6
  180. package/src/ticket-provenance-contracts.ts +1 -4
  181. package/src/ticket-provenance.ts +3 -1
  182. package/src/ticket-snapshot.ts +2 -4
  183. package/src/ticket-trajectory.ts +1 -8
  184. package/src/typed-provider-http.ts +5 -15
  185. package/src/unknown-value.ts +34 -0
  186. package/src/user-dialogue-stdin.ts +3 -1
  187. package/src/worker-role.ts +63 -83
  188. package/src/worker-submission-gates.ts +8 -7
@@ -26,6 +26,8 @@ import type { RoleTurnContinuation } from "./host-contracts.ts";
26
26
  import { sitianReport } from "./sitian-facade.ts";
27
27
  import type { SitianRecordInput } from "./sitian-contracts.ts";
28
28
 
29
+ import { errorText } from "./unknown-value.ts";
30
+
29
31
  /** Volume category under `<run>/session/<kind>/records.jsonl`. */
30
32
  export const HOST_SESSION_RECORD_KIND = "host-session" as const;
31
33
 
@@ -269,7 +271,7 @@ export function copyAndRecordHostDossier(options: {
269
271
  payload: {
270
272
  type: "native-session-warning",
271
273
  sessionId: options.sessionId,
272
- error: error instanceof Error ? error.message : String(error),
274
+ error: errorText(error),
273
275
  },
274
276
  });
275
277
  return;
@@ -334,7 +336,7 @@ export function copyAndRecordHostDossier(options: {
334
336
  landingPath,
335
337
  ordinal,
336
338
  sessionId: options.sessionId,
337
- error: lastError instanceof Error ? lastError.message : String(lastError),
339
+ error: errorText(lastError),
338
340
  },
339
341
  });
340
342
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
8
+ import { isRecord } from "./unknown-value.ts";
8
9
 
9
10
  export const INSPECTOR_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
10
11
  export const INSPECTOR_SOURCE_RUN_FLAG = {
@@ -21,7 +22,7 @@ export type InspectorOutput =
21
22
  | { readonly status: "escalate"; readonly reason?: unknown; readonly findings?: unknown };
22
23
 
23
24
  export function validateRecordedInspectorOutput(value: unknown): InspectorOutput {
24
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
25
+ if (!isRecord(value)) {
25
26
  throw new Error("Inspector output has no execution discriminator");
26
27
  }
27
28
  let status: unknown;
@@ -1,5 +1,6 @@
1
1
  import type { Static } from "typebox";
2
2
  import { reviewSubmissionSchema } from "./review-submission.ts";
3
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
3
4
  import {
4
5
  INSPECTOR_OUTPUT_TOOL_NAME,
5
6
  validateRecordedInspectorOutput,
@@ -25,10 +26,4 @@ export type InspectorRuntimeDependencies = {
25
26
  /**
26
27
  * 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
27
28
  */
28
- export const INSPECTOR_TOOL_SPEC = {
29
- name: INSPECTOR_OUTPUT_TOOL_NAME,
30
- label: "台院输出",
31
- description: "台院终局回执,状态为 converged、continue 或 escalate。",
32
- promptSnippet: "台院终局回执",
33
- parameters: inspectorOutputSchema,
34
- } as const;
29
+ export const INSPECTOR_TOOL_SPEC = roleSubmissionDeclaration("inspector");
package/src/judge-role.ts CHANGED
@@ -1,10 +1,7 @@
1
- import {
2
- type RoleHost,
3
- type HostContext,
4
- type HostToolResult,
5
- } from "./host-contracts.ts";
6
- import type { Static } from "typebox";
7
- import { reviewSubmissionSchema } from "./review-submission.ts";
1
+ import { type RoleHost } from "./host-contracts.ts";
2
+ import { REVIEW_QUEUE_STATUSES, reviewSubmissionSchema, type ReviewQueueWord } from "./review-submission.ts";
3
+ import { registerFiledSubmissionTool } from "./filed-submission.ts";
4
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
8
5
  import { type GatekeeperSubject } from "./gatekeeper-role.ts";
9
6
  import {
10
7
  JUDGE_OUTPUT_TOOL_NAME,
@@ -31,10 +28,10 @@ export async function runJudgeGates(input: {
31
28
  readonly runDirectory?: string;
32
29
  } | void>;
33
30
  }): Promise<{
34
- readonly status: "converged" | "continue" | "escalate";
31
+ readonly status: ReviewQueueWord;
35
32
  readonly passes: readonly {
36
33
  readonly subject: GatekeeperSubject;
37
- readonly status: "converged" | "continue" | "escalate";
34
+ readonly status: ReviewQueueWord;
38
35
  readonly receipt: unknown;
39
36
  readonly runId?: string;
40
37
  readonly runDirectory?: string;
@@ -42,7 +39,7 @@ export async function runJudgeGates(input: {
42
39
  }> {
43
40
  const passes: {
44
41
  subject: GatekeeperSubject;
45
- status: "converged" | "continue" | "escalate";
42
+ status: ReviewQueueWord;
46
43
  receipt: unknown;
47
44
  runId?: string;
48
45
  runDirectory?: string;
@@ -50,10 +47,11 @@ export async function runJudgeGates(input: {
50
47
  for (const subject of JUDGE_GATES) {
51
48
  if (await input.gateAlreadyConverged(subject)) continue;
52
49
  const pass = await input.runGate(subject);
53
- const status = pass?.status;
54
- if (pass === undefined || (status !== "converged" && status !== "continue" && status !== "escalate")) {
50
+ const received = pass?.status;
51
+ if (pass === undefined || typeof received !== "string" || !REVIEW_QUEUE_STATUSES.has(received)) {
55
52
  throw new Error("judge gate returned no conclusion");
56
53
  }
54
+ const status = received as ReviewQueueWord;
57
55
  passes.push({
58
56
  subject,
59
57
  status,
@@ -90,18 +88,8 @@ export function createJudgeRoleRuntime(
90
88
  if (soul.length === 0) throw new Error("Judge soul is empty");
91
89
  if (!lifecycleRegistered) {
92
90
  lifecycleRegistered = true;
93
- pi.registerTool({
94
- name: JUDGE_OUTPUT_TOOL_NAME,
95
- label: "大理寺输出",
96
- description: "提交大理寺终局判词;交卷调用结束后,由公开调用接缝按判词状态执行适用审核。",
97
- promptSnippet: "提交大理寺终局判词",
98
- parameters: judgeVerdictSchema,
99
- async execute(toolCallId: string, parameters: Static<typeof judgeVerdictSchema>, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
100
- if (soul === undefined) throw new Error("大理寺职分未装载");
101
- // ADR 0003: record the original receipt and end the tool call first.
102
- // The public seam reads status and chooses the next route.
103
- return { content: [], details: parameters, terminate: true };
104
- },
91
+ registerFiledSubmissionTool(pi, roleSubmissionDeclaration("judge"), {
92
+ readyError: () => (soul === undefined ? "大理寺职分未装载" : undefined),
105
93
  });
106
94
  pi.on("before_agent_start", (event) => {
107
95
  if (soul === undefined) throw new Error("大理寺职分未装载");
@@ -4,6 +4,8 @@
4
4
  */
5
5
  import { readFile } from "node:fs/promises";
6
6
 
7
+ import { isRecord } from "./unknown-value.ts";
8
+
7
9
  export type LedgerSessionRow = Record<string, unknown>;
8
10
 
9
11
  /**
@@ -32,10 +34,6 @@ export class LedgerSessionJsonlError extends Error {
32
34
  }
33
35
  }
34
36
 
35
- function isRecord(value: unknown): value is Record<string, unknown> {
36
- return typeof value === "object" && value !== null && !Array.isArray(value);
37
- }
38
-
39
37
  /**
40
38
  * One physical JSONL line as the single kernel saw it.
41
39
  * `row` present = syntactically complete session object at that 1-based line.
@@ -339,4 +337,3 @@ export function intervalRowsAroundAnchor(
339
337
  return { rows: rows.slice(start, end), closed: end < rows.length };
340
338
  }
341
339
 
342
-
@@ -3,17 +3,19 @@ import { exactUtf8 } from "./exact-utf8.ts";
3
3
  import { openToolObject } from "./open-tool-schema.ts";
4
4
  import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
5
5
 
6
- const materialSchema = Type.Object({ bytesBase64: Type.String(), sha256: Type.String() }, { additionalProperties: false });
7
- const checkSchema = Type.Object({ name: Type.String({ minLength: 1 }), argv: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }) }, { additionalProperties: false });
6
+ import { isRecord } from "./unknown-value.ts";
7
+
8
+ const materialSchema = Type.Object({ bytesBase64: Type.String(), sha256: Type.String() });
9
+ const checkSchema = Type.Object({ name: Type.String({ minLength: 1 }), argv: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }) });
8
10
  export const mergerInputSchema = Type.Object({
9
11
  attemptId: Type.String({ minLength: 1, description: "对账 attempt 身份(transport)" }),
10
12
  targetObjectId: Type.String({ description: "材料:target parent OID;可空" }),
11
13
  sourceObjectId: Type.String({ description: "材料:source parent OID;可空" }),
12
- materials: Type.Object({ task: materialSchema, authority: materialSchema, targetIntent: materialSchema, sourceIntent: materialSchema }, { additionalProperties: false }),
14
+ materials: Type.Object({ task: materialSchema, authority: materialSchema, targetIntent: materialSchema, sourceIntent: materialSchema }),
13
15
  expectedConflictPaths: Type.Array(Type.String(), { description: "材料:当前未合并路径;可空" }),
14
16
  resolutionScope: Type.Array(Type.String(), { description: "材料:解析范围提示;可空" }),
15
17
  authorizedChecks: Type.Array(checkSchema),
16
- }, { additionalProperties: false });
18
+ });
17
19
  // #836 r16 class 1: attemptId/report/diagnosis/mergeCommitId are LLM/human-read
18
20
  // narrative content (candidate stored as submitted) — no code branches on their
19
21
  // length. mergerInputSchema (host-authored material, not role output) is out of scope.
@@ -42,7 +44,6 @@ export type MergerOutput =
42
44
  | { status: "escalate"; attemptId: string; diagnosis: string; report: string };
43
45
  export const MERGER_OUTPUT_TOOL_NAME = "ak_merger_output";
44
46
 
45
- const record = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);
46
47
  const blank = (v: unknown) => typeof v !== "string" || v.trim().length === 0;
47
48
  export class MergerInputContractError extends Error {
48
49
  constructor(message = "Merger input violates its exact contract") { super(message); this.name = "MergerInputContractError"; }
@@ -53,16 +54,16 @@ const asString = (v: unknown): string => typeof v === "string" ? v : "";
53
54
  const asStringArray = (v: unknown): string[] => Array.isArray(v) ? v.filter((item): item is string => typeof item === "string") : [];
54
55
 
55
56
  function readMaterial(value: unknown, label: string): MergerMaterial {
56
- if (!record(value) || typeof value.bytesBase64 !== "string") fail(`Merger ${label} material is malformed`);
57
+ if (!isRecord(value) || typeof value.bytesBase64 !== "string") fail(`Merger ${label} material is malformed`);
57
58
  exactUtf8(Buffer.from(value.bytesBase64, "base64"), `Merger ${label} material`);
58
59
  return { bytesBase64: value.bytesBase64, sha256: typeof value.sha256 === "string" ? value.sha256 : "" };
59
60
  }
60
61
 
61
62
  export function validateMergerInput(value: unknown): MergerInput {
62
- if (!record(value) || blank(value.attemptId)) fail("Merger input requires a non-empty attemptId for accounting");
63
- if (!record(value.materials)) fail("Merger materials are missing");
63
+ if (!isRecord(value) || blank(value.attemptId)) fail("Merger input requires a non-empty attemptId for accounting");
64
+ if (!isRecord(value.materials)) fail("Merger materials are missing");
64
65
  const materials = { task: readMaterial(value.materials.task, "task"), authority: readMaterial(value.materials.authority, "authority"), targetIntent: readMaterial(value.materials.targetIntent, "targetIntent"), sourceIntent: readMaterial(value.materials.sourceIntent, "sourceIntent") };
65
- const authorizedChecks = Array.isArray(value.authorizedChecks) ? value.authorizedChecks.filter((check): check is Record<string, unknown> => record(check)).map(c => ({ name: asString(c.name), argv: asStringArray(c.argv).filter(a => a.trim().length > 0) })).filter(c => c.name.trim().length > 0 && c.argv.length > 0) : [];
66
+ const authorizedChecks = Array.isArray(value.authorizedChecks) ? value.authorizedChecks.filter((check): check is Record<string, unknown> => isRecord(check)).map(c => ({ name: asString(c.name), argv: asStringArray(c.argv).filter(a => a.trim().length > 0) })).filter(c => c.name.trim().length > 0 && c.argv.length > 0) : [];
66
67
  return deepFreeze(structuredClone({ attemptId: (value.attemptId as string).trim(), targetObjectId: asString(value.targetObjectId), sourceObjectId: asString(value.sourceObjectId), materials, expectedConflictPaths: asStringArray(value.expectedConflictPaths), resolutionScope: asStringArray(value.resolutionScope), authorizedChecks }) as MergerInput);
67
68
  }
68
69
 
@@ -1,10 +1,11 @@
1
- import type { RoleHost, HostContext } from "./host-contracts.ts";
1
+ import type { RoleHost } from "./host-contracts.ts";
2
2
  import {
3
3
  MERGER_OUTPUT_TOOL_NAME,
4
- mergerOutputSchema,
5
4
  validateMergerInput,
6
5
  type MergerInput,
7
6
  } from "./merger-contracts.ts";
7
+ import { registerFiledSubmissionTool } from "./filed-submission.ts";
8
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
8
9
  import { exactUtf8 } from "./exact-utf8.ts";
9
10
 
10
11
  export { MERGER_OUTPUT_TOOL_NAME };
@@ -35,12 +36,8 @@ export function createMergerRoleRuntime(pi: RoleHost, dependencies: MergerRoleDe
35
36
  activation = { soul, input };
36
37
  if (!registered) {
37
38
  registered = true;
38
- pi.registerTool({
39
- name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate。", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
40
- async execute(_id: string, params: unknown, _signal: AbortSignal | undefined, _update: unknown, _ctx: HostContext) {
41
- if (!activation) throw new Error("校书郎未激活");
42
- return { content: [], details: params, terminate: true as const };
43
- },
39
+ registerFiledSubmissionTool(pi, roleSubmissionDeclaration("merger"), {
40
+ readyError: () => (activation === undefined ? "校书郎未激活" : undefined),
44
41
  });
45
42
  pi.on("before_agent_start", (event) => {
46
43
  if (!activation) throw new Error("校书郎未激活");
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
2
  import { homedir } from "node:os";
4
3
  import { dirname, join, resolve } from "node:path";
@@ -64,6 +63,9 @@ import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE } from "./
64
63
  import { navigatorProseFromUnknown } from "./package-contracts/navigator-output.ts";
65
64
  import { persistNavigatorWorkBase } from "./navigator-work-base.ts";
66
65
 
66
+ import { sha256Hex } from "./sha256.ts";
67
+ import { isRecord } from "./unknown-value.ts";
68
+
67
69
  export const NAVIGATOR_EVENT_TYPE = "ak-navigator-attendance" as const;
68
70
  export { NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY };
69
71
 
@@ -167,25 +169,21 @@ function unavailableKey(value: unknown): NavigatorUnavailableKey | undefined {
167
169
  : undefined;
168
170
  }
169
171
 
170
- function exactRecord(value: unknown): value is Record<string, unknown> {
171
- return typeof value === "object" && value !== null && !Array.isArray(value);
172
- }
173
-
174
172
  /** Correlate one rejected prepare call/result inside the just-finished prompt. */
175
173
  function rejectedPrepareReason(entries: readonly unknown[], start: number): string | undefined {
176
174
  const recent = entries.slice(start);
177
175
  const prepareCalls = new Set<string>();
178
176
  for (const entry of recent) {
179
- if (!exactRecord(entry) || entry.type !== "message" || !exactRecord(entry.message)
177
+ if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message)
180
178
  || entry.message.role !== "assistant" || !Array.isArray(entry.message.content)) continue;
181
179
  for (const part of entry.message.content) {
182
- if (exactRecord(part) && part.type === "toolCall" && part.name === NAVIGATOR_PREPARE_TOOL_NAME
180
+ if (isRecord(part) && part.type === "toolCall" && part.name === NAVIGATOR_PREPARE_TOOL_NAME
183
181
  && typeof part.id === "string") prepareCalls.add(part.id);
184
182
  }
185
183
  }
186
184
  let reason: string | undefined;
187
185
  for (const entry of recent) {
188
- if (!exactRecord(entry) || entry.type !== "message" || !exactRecord(entry.message)
186
+ if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message)
189
187
  || entry.message.role !== "toolResult" || entry.message.isError !== true) continue;
190
188
  const callId = entry.message.toolCallId;
191
189
  if (entry.message.toolName !== NAVIGATOR_PREPARE_TOOL_NAME
@@ -194,7 +192,7 @@ function rejectedPrepareReason(entries: readonly unknown[], start: number): stri
194
192
  }
195
193
  const content = entry.message.content;
196
194
  const text = Array.isArray(content)
197
- ? content.flatMap((part) => exactRecord(part) && typeof part.text === "string" ? [part.text] : []).join("")
195
+ ? content.flatMap((part) => isRecord(part) && typeof part.text === "string" ? [part.text] : []).join("")
198
196
  : typeof content === "string" ? content : "";
199
197
  if (text.trim() !== "") reason = text.trim();
200
198
  }
@@ -220,7 +218,7 @@ export function navigatorSubjectKey(
220
218
  if (provenance === "placeholder") return subjectRoot;
221
219
  const normalized = subject.trim().replace(/\s+/g, " ");
222
220
  if (normalized === "") return subjectRoot;
223
- return `${subjectRoot}#${createHash("sha256").update(normalized).digest("hex").slice(0, 32)}`;
221
+ return `${subjectRoot}#${sha256Hex(normalized).slice(0, 32)}`;
224
222
  }
225
223
 
226
224
  /**
@@ -485,10 +483,10 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
485
483
  return undefined;
486
484
  }
487
485
  const nativeFailure = [...activeSession.entries()].reverse().find((entry: unknown) => {
488
- if (!exactRecord(entry) || entry.type !== "message" || !exactRecord(entry.message)) return false;
486
+ if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message)) return false;
489
487
  return entry.message.role === "assistant" && typeof entry.message.errorMessage === "string" && entry.message.errorMessage.trim() !== "";
490
488
  });
491
- const nativeMessage = exactRecord(nativeFailure) && exactRecord(nativeFailure.message) ? nativeFailure.message : undefined;
489
+ const nativeMessage = isRecord(nativeFailure) && isRecord(nativeFailure.message) ? nativeFailure.message : undefined;
492
490
  const errorMessage = nativeMessage !== undefined && typeof nativeMessage.errorMessage === "string"
493
491
  ? nativeMessage.errorMessage
494
492
  : "Navigator did not submit direction advice";
@@ -19,6 +19,7 @@
19
19
 
20
20
  import { LEGACY_REVIEW_OUTPUT_ROLES, PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
21
21
  import { isUuidV7, uuidv7 } from "./uuidv7.ts";
22
+ import { isRecord } from "./unknown-value.ts";
22
23
  import { workSubjectKeysEqual } from "./work-subject-identity.ts";
23
24
 
24
25
  export const NAVIGATOR_INVOCATION_ENTRY = "ak-navigator-invocation" as const;
@@ -83,7 +84,7 @@ export function extractInfrastructureFailureEvidence(error: unknown): Record<str
83
84
  * (ADR 0040 — discriminators select the branch, they do not ban extras).
84
85
  */
85
86
  export function hasNavigatorInfrastructureFailureBase(value: unknown): boolean {
86
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
87
+ if (!isRecord(value)) return false;
87
88
  const record = value as Record<string, unknown>;
88
89
  for (const key of NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS) {
89
90
  if (!Object.hasOwn(record, key)) return false;
@@ -160,7 +161,7 @@ function invocationPhaseFromUnknown(value: unknown): InvocationPhase | undefined
160
161
  export function parseInvocationMarkerIdentity(
161
162
  data: unknown,
162
163
  ): InvocationMarkerIdentity | undefined {
163
- if (data === null || typeof data !== "object" || Array.isArray(data)) return undefined;
164
+ if (!isRecord(data)) return undefined;
164
165
  const record = data as Record<string, unknown>;
165
166
  const invocationId = record.invocationId;
166
167
  if (typeof invocationId !== "string") return undefined;
@@ -29,6 +29,8 @@ import {
29
29
  navigatorWorkContextFile,
30
30
  } from "./navigator-work-base.ts";
31
31
 
32
+ import { isRecord } from "./unknown-value.ts";
33
+
32
34
  /**
33
35
  * Ledger process home for navigator attendance: admitted HostContext.runDirectory
34
36
  * first, else a parent session file under .ak-roles. Never context.home / env HOME /
@@ -54,10 +56,6 @@ async function resolveNavigatorLedgerHome(context: HostContext): Promise<string
54
56
  /** Resume key only — points at a host run principal, never stores advice prose. */
55
57
  export const NAVIGATOR_HOST_RUN_POINTER_ENTRY = "ak-navigator-host-run";
56
58
 
57
- function exactRecord(value: unknown): value is Record<string, unknown> {
58
- return typeof value === "object" && value !== null && !Array.isArray(value);
59
- }
60
-
61
59
  export function runIdFromNavigatorDirectory(runDirectory: string): string | undefined {
62
60
  const entry = basename(runDirectory);
63
61
  const suffix = "@navigator";
@@ -69,10 +67,10 @@ export function runIdFromNavigatorDirectory(runDirectory: string): string | unde
69
67
  export function readNavigatorHostRunPointer(entries: readonly unknown[]): string | undefined {
70
68
  for (let index = entries.length - 1; index >= 0; index -= 1) {
71
69
  const entry = entries[index];
72
- if (!exactRecord(entry)) continue;
70
+ if (!isRecord(entry)) continue;
73
71
  if (entry.type === "custom" && entry.customType === NAVIGATOR_HOST_RUN_POINTER_ENTRY) {
74
72
  const data = entry.data;
75
- if (exactRecord(data) && typeof data.runId === "string" && data.runId.trim() !== "") {
73
+ if (isRecord(data) && typeof data.runId === "string" && data.runId.trim() !== "") {
76
74
  return data.runId.trim();
77
75
  }
78
76
  }
@@ -1,16 +1,10 @@
1
- import { NAVIGATOR_OUTPUT_TOOL_NAME, navigatorOutputSchema } from "./package-contracts/navigator-output.ts";
1
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
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
- export const NAVIGATOR_TOOL_SPEC = {
8
- name: NAVIGATOR_OUTPUT_TOOL_NAME,
9
- label: "游奕使建议",
10
- description: "游奕使终局回执:散文建议,原样呈现。",
11
- promptSnippet: "游奕使建议",
12
- parameters: navigatorOutputSchema,
13
- } as const;
7
+ export const NAVIGATOR_TOOL_SPEC = roleSubmissionDeclaration("navigator");
14
8
 
15
9
  export type NavigatorRuntimeDependencies = {
16
10
  loadSoul(): Promise<string>;
@@ -13,6 +13,7 @@ import { Value } from "typebox/value";
13
13
  import type { HostContext, HostInstitutionalModelSelection } from "./host-contracts.ts";
14
14
  import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
15
15
 
16
+ import { isRecord, errorText, isEnoent } from "./unknown-value.ts";
16
17
 
17
18
  export const NAVIGATOR_PREPARE_TOOL_NAME = "ak_navigator_prepare" as const;
18
19
 
@@ -53,16 +54,12 @@ export function navigatorUnavailableError(
53
54
  error: unknown,
54
55
  cause: NavigatorUnavailableKey = source,
55
56
  ): NavigatorUnavailableError {
56
- const message = error instanceof Error ? error.message : String(error);
57
+ const message = errorText(error);
57
58
  return error instanceof NavigatorUnavailableError
58
59
  ? error
59
60
  : new NavigatorUnavailableError(source, message, cause, error);
60
61
  }
61
62
 
62
- function exactRecord(value: unknown): value is Record<string, unknown> {
63
- return typeof value === "object" && value !== null && !Array.isArray(value);
64
- }
65
-
66
63
  export function navigatorProviderFailureFromStatus(status: number | undefined): NavigatorProviderFailureFact | undefined {
67
64
  if (status === 401 || status === 403) return { source: "auth", cause: "auth" };
68
65
  if (status === 429) return { source: "quota", cause: "quota" };
@@ -86,7 +83,7 @@ export function navigatorProviderFailureFromError(error: unknown): NavigatorProv
86
83
  if (cursor instanceof NavigatorUnavailableError) {
87
84
  return { source: cursor.unavailableSource, cause: cursor.unavailableCause };
88
85
  }
89
- if (!exactRecord(cursor)) {
86
+ if (!isRecord(cursor)) {
90
87
  cursor = cursor instanceof Error ? cursor.cause : undefined;
91
88
  continue;
92
89
  }
@@ -111,13 +108,13 @@ export function navigatorProviderFailureFromError(error: unknown): NavigatorProv
111
108
  export function navigatorProviderFailureFromDiagnostics(diagnostics: unknown): NavigatorProviderFailureFact | undefined {
112
109
  if (!Array.isArray(diagnostics)) return undefined;
113
110
  for (const diagnostic of diagnostics) {
114
- if (!exactRecord(diagnostic)) continue;
111
+ if (!isRecord(diagnostic)) continue;
115
112
  if (diagnostic.type === "provider_transport_failure") return { source: "transport", cause: "transport" };
116
- if (exactRecord(diagnostic.error)) {
113
+ if (isRecord(diagnostic.error)) {
117
114
  const fromCode = navigatorProviderFailureFromCode(diagnostic.error.code);
118
115
  if (fromCode !== undefined) return fromCode;
119
116
  }
120
- if (exactRecord(diagnostic.details)) {
117
+ if (isRecord(diagnostic.details)) {
121
118
  const status = typeof diagnostic.details.status === "number"
122
119
  ? diagnostic.details.status
123
120
  : typeof diagnostic.details.statusCode === "number"
@@ -225,13 +222,13 @@ export function navigatorModelSettingPath(): string {
225
222
  export async function readNavigatorModelSetting(path = navigatorModelSettingPath()): Promise<string> {
226
223
  try {
227
224
  const raw = JSON.parse(await readFile(path, "utf8")) as unknown;
228
- if (!exactRecord(raw) || typeof raw.model !== "string" || raw.model.trim() === "") {
225
+ if (!isRecord(raw) || typeof raw.model !== "string" || raw.model.trim() === "") {
229
226
  throw new Error("Navigator model setting is malformed");
230
227
  }
231
228
  return raw.model;
232
229
  } catch (error) {
233
230
  // #178: missing file is absence (no package default).
234
- if (error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT") {
231
+ if (isEnoent(error)) {
235
232
  throw new Error("Navigator model setting is missing");
236
233
  }
237
234
  throw error;
@@ -8,6 +8,7 @@ import { readFile } from "node:fs/promises";
8
8
  import { basename, dirname, join, resolve } from "node:path";
9
9
 
10
10
  import { writeFileAtomically } from "./atomic-write.ts";
11
+ import { isEnoent, isRecord } from "./unknown-value.ts";
11
12
 
12
13
  export const NAVIGATOR_WORK_CONTEXT_BASENAME = "work-context.json";
13
14
 
@@ -51,9 +52,7 @@ export async function persistNavigatorWorkBase(
51
52
  const current = await readFile(path, "utf8");
52
53
  if (current === next) return path;
53
54
  } catch (error) {
54
- if (!(error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT")) {
55
- throw error;
56
- }
55
+ if (!isEnoent(error)) throw error;
57
56
  }
58
57
  await writeFileAtomically(path, next);
59
58
  return path;
@@ -66,9 +65,7 @@ export async function readNavigatorWorkBase(path: string): Promise<NavigatorWork
66
65
  try {
67
66
  text = await readFile(canonical, "utf8");
68
67
  } catch (error) {
69
- if (error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT") {
70
- return undefined;
71
- }
68
+ if (isEnoent(error)) return undefined;
72
69
  throw error;
73
70
  }
74
71
  let parsed: unknown;
@@ -77,7 +74,7 @@ export async function readNavigatorWorkBase(path: string): Promise<NavigatorWork
77
74
  } catch {
78
75
  return undefined;
79
76
  }
80
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return undefined;
77
+ if (!isRecord(parsed)) return undefined;
81
78
  const record = parsed as Record<string, unknown>;
82
79
  if (typeof record.subject !== "string" || typeof record.authority !== "string") return undefined;
83
80
  if (record.authority.trim() === "") return undefined;
@@ -89,7 +86,7 @@ function jsonObjectFromPrompt(prompt: string): Record<string, unknown> | undefin
89
86
  if (!head.startsWith("{")) return undefined;
90
87
  try {
91
88
  const parsed: unknown = JSON.parse(head);
92
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return undefined;
89
+ if (!isRecord(parsed)) return undefined;
93
90
  return parsed as Record<string, unknown>;
94
91
  } catch {
95
92
  return undefined;
@@ -6,6 +6,7 @@ import { readFile } from "node:fs/promises";
6
6
  import { resolve } from "node:path";
7
7
 
8
8
  import { loadDoctorCase } from "./doctor-evidence.ts";
9
+ import { isEnoent } from "./unknown-value.ts";
9
10
  import { runDirectoryFromHostContext, type HostContext } from "./host-contracts.ts";
10
11
  import {
11
12
  navigatorSubjectKey,
@@ -114,7 +115,7 @@ export async function loadNavigatorWorkContext(
114
115
  break;
115
116
  }
116
117
  } catch (error) {
117
- if (!(error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT")) throw error;
118
+ if (!isEnoent(error)) throw error;
118
119
  }
119
120
  }
120
121
  const authority = resolveNavigatorAuthorityMaterial(input, authorityMaterial);
@@ -5,6 +5,8 @@
5
5
  */
6
6
  import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME, reviewSubmissionSchema } from "./review-submission.ts";
7
7
 
8
+ import { isRecord } from "./unknown-value.ts";
9
+
8
10
  export const NOTARY_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
9
11
  export const NOTARY_SOURCE_RUN_FLAG = {
10
12
  name: "ak-notary-source-run",
@@ -41,10 +43,6 @@ export type NotaryOutput =
41
43
  readonly findings?: unknown;
42
44
  };
43
45
 
44
- function isRecord(value: unknown): value is Record<string, unknown> {
45
- return typeof value === "object" && value !== null && !Array.isArray(value);
46
- }
47
-
48
46
  /**
49
47
  * Recognize one lawful explicit Notary release (converged | continue | escalate).
50
48
  * #753: no field rewrite — submitted params retained as-is (no disposition forge,
@@ -52,7 +50,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
52
50
  */
53
51
  export function projectLawfulNotaryOutput(value: unknown): NotaryOutput | undefined {
54
52
  // #836: no field drop
55
- return (typeof value === "object" && value !== null && !Array.isArray(value))
53
+ return (isRecord(value))
56
54
  ? (value as NotaryOutput)
57
55
  : undefined;
58
56
  }
@@ -72,7 +70,7 @@ export function retainNotarySubmission(value: unknown): unknown {
72
70
  * Does not gate role admission — callers must not use this to reject a submission.
73
71
  */
74
72
  export function validateRecordedNotaryOutput(value: unknown): NotaryOutput {
75
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
73
+ if (!isRecord(value)) {
76
74
  throw new Error("Notary output is not an object");
77
75
  }
78
76
  return value as NotaryOutput;
@@ -1,4 +1,6 @@
1
- import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts";
1
+ import type { RoleHost, HostContext } from "./host-contracts.ts";
2
+ import { registerFiledSubmissionTool } from "./filed-submission.ts";
3
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
2
4
  /**
3
5
  * Public Notary role runtime — direct officer seat (not through Gatekeeper province).
4
6
  * Caller supplies only a source-run locator; Notary self-fetches authoritative materials.
@@ -10,7 +12,6 @@ import {
10
12
  NOTARY_OUTPUT_TOOL_NAME,
11
13
  NOTARY_SOURCE_RUN_FLAG,
12
14
  NOTARY_TICKET_FLAG,
13
- notaryOutputSchema,
14
15
  type NotarySourceRunLocator,
15
16
  } from "./notary-contracts.ts";
16
17
 
@@ -156,24 +157,8 @@ export function createNotaryRoleRuntime(
156
157
 
157
158
  if (!registered) {
158
159
  registered = true;
159
- pi.registerTool({
160
- name: NOTARY_OUTPUT_TOOL_NAME,
161
- label: "符宝郎输出",
162
- description: "提交引文保真与票面对齐的 converged/continue/escalate 决议。",
163
- promptSnippet: "提交符宝郎决议",
164
- parameters: notaryOutputSchema,
165
- async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext, ): Promise<HostToolResult<unknown>> {
166
- if (activation === undefined) {
167
- throw new Error("符宝郎未激活");
168
- }
169
- // #753: handler only records — params as submitted, no findings/disposition rewrite.
170
- // #541 infra declaration + sole-final barrier are ledger-owned (#575).
171
- return {
172
- content: [],
173
- details: parameters,
174
- terminate: true as const,
175
- };
176
- },
160
+ registerFiledSubmissionTool(pi, roleSubmissionDeclaration("notary"), {
161
+ readyError: () => (activation === undefined ? "符宝郎未激活" : undefined),
177
162
  });
178
163
  // Evidence assembly only (ADR 0018): soul + bound projection.
179
164
  // Ticket value is envelope-admitted at activate — not re-read from flags here.