@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
@@ -4,54 +4,18 @@
4
4
  * shared envelope — src/role-runtime.ts (ADR 0018 / #924).
5
5
  */
6
6
  import type { Static } from "typebox";
7
- import { Type } from "typebox";
8
7
 
9
- import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
10
- import { SECRETARIAT_OUTPUT_TOOL_NAME, type SecretariatVerdict } from "./secretariat-contracts.ts";
8
+ import { SECRETARIAT_OUTPUT_TOOL_NAME, secretariatVerdictSchema, type SecretariatVerdict } from "./secretariat-contracts.ts";
9
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
11
10
 
12
11
  export { SECRETARIAT_OUTPUT_TOOL_NAME } from "./secretariat-contracts.ts";
13
12
  export type { SecretariatVerdict };
14
13
 
15
- /**
16
- * 中书省终局回执形状。
17
- * #1134: `secretariatStatus` alone is the trajectory field
18
- * (src/packaged-role-registry.ts:406 receiptStatusKey; the public settlement reads
19
- * converged | escalate to route); its words ride the description. note/evidence/
20
- * decisionGate are declared name + semantic description only — the string type, the
21
- * number type, the nested question/options object shape and the required constraint
22
- * are deleted, because the package declaration IS the host's pre-dispatch validator.
23
- * (ticketNumber keeps its own declaration: it is the declared seat identity the
24
- * activation flag and settlement read, not narrative content.)
25
- */
26
- export const secretariatVerdictSchema = withTerminatingOutputDeclarations(
27
- Type.Object(
28
- {
29
- secretariatStatus: Type.Unknown({
30
- description: "converged | escalate",
31
- }),
32
- ticketNumber: Type.Optional(
33
- Type.Unknown({ description: "本票号;署时指向最终正文所在票" }),
34
- ),
35
- note: Type.Unknown({ description: "附注" }),
36
- evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
37
- decisionGate: Type.Unknown({
38
- description: "需陛下处置的问题与选项;可写 question 与 options,原样留存。",
39
- }),
40
- },
41
- { additionalProperties: true },
42
- ),
43
- );
44
- (secretariatVerdictSchema as unknown as { required: string[] }).required = [];
14
+ export { secretariatVerdictSchema } from "./secretariat-contracts.ts";
45
15
 
46
16
  export type SecretariatVerdictParameters = Static<typeof secretariatVerdictSchema>;
47
17
 
48
- export const SECRETARIAT_OUTPUT_TOOL_SPEC = {
49
- name: SECRETARIAT_OUTPUT_TOOL_NAME,
50
- label: "中书省输出",
51
- description: "中书省终局回执。",
52
- promptSnippet: "中书省终局回执",
53
- parameters: secretariatVerdictSchema,
54
- } as const;
18
+ export const SECRETARIAT_OUTPUT_TOOL_SPEC = roleSubmissionDeclaration("secretariat");
55
19
 
56
20
  export type SecretariatRuntimeDependencies = {
57
21
  loadSoul(): Promise<string>;
@@ -7,6 +7,8 @@ import { join } from "node:path";
7
7
 
8
8
  import type { PublicSummonResult } from "./public-role-summons.ts";
9
9
 
10
+ import { errorText } from "./unknown-value.ts";
11
+
10
12
  export async function readAssistantUsageFromSessionFile(
11
13
  sessionFile: string,
12
14
  ): Promise<Usage | undefined> {
@@ -30,7 +32,7 @@ export async function readAssistantUsageFromSessionFile(
30
32
  });
31
33
  } catch (error) {
32
34
  throw new Error(
33
- `session usage parse failed (${sessionFile}): ${error instanceof Error ? error.message : String(error)}`,
35
+ `session usage parse failed (${sessionFile}): ${errorText(error)}`,
34
36
  { cause: error },
35
37
  );
36
38
  }
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "./unknown-value.ts";
1
2
  /**
2
3
  * 对话事实适配(ADR 0075 `dialogue-only` / `no-tool-output` / `speaker-required`)。
3
4
  *
@@ -20,10 +21,6 @@ export type DialogueEvent = {
20
21
  readonly id?: string;
21
22
  };
22
23
 
23
- function isRecord(value: unknown): value is Record<string, unknown> {
24
- return typeof value === "object" && value !== null && !Array.isArray(value);
25
- }
26
-
27
24
  /**
28
25
  * 原生 id:行级或消息体级主键的单一实现(bound 解析与落盘共用)。
29
26
  * Pi/CC:行上 `uuid`/`id`;Codex:`response_item.payload.id`(或 `message.id`)。
@@ -42,7 +42,6 @@ export function createSessionIdentityAuthority(
42
42
  };
43
43
  }
44
44
 
45
-
46
45
  /**
47
46
  * Native session/thread id already stored for this principal on `host`.
48
47
  * Public explicit resume reads it once and hands it to the host adapter.
@@ -15,9 +15,7 @@ import type {
15
15
  SitianRecord,
16
16
  } from "./sitian-contracts.ts";
17
17
 
18
- function isRecord(value: unknown): value is Record<string, unknown> {
19
- return typeof value === "object" && value !== null && !Array.isArray(value);
20
- }
18
+ import { isRecord, errorText } from "./unknown-value.ts";
21
19
 
22
20
  /** Read a Sitian record volume with full traversal and non-destructive diagnostics. */
23
21
  export async function readSitianRecords(recordFile: string): Promise<SitianReadResult> {
@@ -53,7 +51,7 @@ export async function readSitianRecords(recordFile: string): Promise<SitianReadR
53
51
  kind: "malformed",
54
52
  line: index + 1,
55
53
  raw: line,
56
- error: error instanceof Error ? error.message : String(error),
54
+ error: errorText(error),
57
55
  });
58
56
  }
59
57
  }
@@ -6,6 +6,8 @@ import {
6
6
  WorkerUnfinishedReasonReminderError,
7
7
  } from "./submission-errors.ts";
8
8
 
9
+ import { errorText } from "./unknown-value.ts";
10
+
9
11
  /** Shared, unforgeable identity for submission errors that the same session may correct. */
10
12
  const correctableSubmissionErrorBrand = Symbol("ak-roles.correctable-submission-error");
11
13
 
@@ -49,7 +51,7 @@ export type CorrectableExecuteRejectionProjection = {
49
51
  export function projectCorrectableExecuteRejection(
50
52
  error: unknown,
51
53
  ): CorrectableExecuteRejectionProjection {
52
- const diagnostic = error instanceof Error ? error.message : String(error);
54
+ const diagnostic = errorText(error);
53
55
  if (error instanceof GatekeeperDecisionError) {
54
56
  return { diagnostic, details: { ...(error.result as Record<string, unknown>) } };
55
57
  }
@@ -1,5 +1,6 @@
1
1
  import type { SubmissionGateNonPassResult } from "./gatekeeper-role.ts";
2
2
  import { readableGateItem } from "./readable-gate-item.ts";
3
+ import { isRecord } from "./unknown-value.ts";
3
4
 
4
5
  /**
5
6
  * Tool-result text the parent model sees (#753 / #750 evidence).
@@ -69,7 +70,7 @@ export function unreadableDiscriminatorNotice(field: string, received: unknown):
69
70
 
70
71
  /** The received discriminator only — never the rest of the receipt. */
71
72
  export function receivedDiscriminator(receipt: unknown, field: string): unknown {
72
- if (receipt === null || typeof receipt !== "object" || Array.isArray(receipt)) return undefined;
73
+ if (!isRecord(receipt)) return undefined;
73
74
  if (!Object.hasOwn(receipt, field)) return undefined;
74
75
  return (receipt as Record<string, unknown>)[field];
75
76
  }
@@ -28,21 +28,20 @@ import {
28
28
  } from "./gatekeeper-role.ts";
29
29
  import type { PublicSummonResult } from "./public-role-summons.ts";
30
30
  import type { TerminalResult } from "./public-cli/terminal.ts";
31
+ import { receivedDiscriminator } from "./submission-errors.ts";
32
+ import type { ReviewQueueWord } from "./review-submission.ts";
31
33
 
32
- /** Queue word of the latest this-terminal payload. History does not outrank it. */
33
- export function latestQueueStatus(terminal: TerminalResult | undefined): string | undefined {
34
+ /** Received discriminator of the latest payload, retained verbatim for re-ask. */
35
+ export function latestQueueStatus(terminal: TerminalResult | undefined): unknown {
34
36
  const outcome = terminal?.roleOutcome;
35
37
  if (outcome === undefined) return undefined;
36
38
  if (outcome.kind === "audit_escalation") return "escalate";
37
39
  if (outcome.kind !== "accepted") return undefined;
38
40
  const latest = outcome.payloads?.[outcome.payloads.length - 1];
39
- if (latest !== null && typeof latest === "object" && !Array.isArray(latest)) {
40
- const status = (latest as { status?: unknown }).status;
41
- if (typeof status === "string" && status.trim() !== "") return status;
41
+ if (isRecord(latest) && Object.hasOwn(latest, "status")) {
42
+ return receivedDiscriminator(latest, "status");
42
43
  }
43
- return typeof outcome.status === "string" && outcome.status.trim() !== ""
44
- ? outcome.status
45
- : undefined;
44
+ return outcome.status;
46
45
  }
47
46
 
48
47
  export function latestQueuePayload(terminal: TerminalResult | undefined): unknown {
@@ -54,6 +53,8 @@ export function latestQueuePayload(terminal: TerminalResult | undefined): unknow
54
53
  }
55
54
  import { sessionFileFromPublicSummon } from "./session-assistant-usage.ts";
56
55
 
56
+ import { isRecord } from "./unknown-value.ts";
57
+
57
58
  /**
58
59
  * Shared-envelope default officer summon (ADR 0018).
59
60
  * Role modules must not own this drive seam — only project results.
@@ -129,7 +130,7 @@ function bookDirectOfficerPointer(
129
130
  * public terminal without a second authority.
130
131
  */
131
132
  export type SubmissionGateOutcome = {
132
- readonly status: "converged" | "continue" | "escalate";
133
+ readonly status: ReviewQueueWord;
133
134
  readonly officer: GateOfficer;
134
135
  readonly receipt: unknown;
135
136
  readonly runId?: string;
@@ -12,7 +12,6 @@ import {
12
12
  type RoleHost,
13
13
  } from "./host-contracts.ts";
14
14
 
15
-
16
15
  import { runIdFromRunDirectory } from "./run-terminal-artifacts.ts";
17
16
  import { readSitianRecords, resolveSitianRecordPathInLedger, sitianReport, type RecordPointer } from "./sitian-facade.ts";
18
17
  import type { SitianRecord } from "./sitian-contracts.ts";
@@ -22,6 +21,8 @@ import { isCorrectableExecuteError } from "./submission-correctable-error.ts";
22
21
  import { failOnInfrastructureFailureDeclaration, infrastructureFailureDiagnostic } from "./package-contracts/terminating-infrastructure.ts";
23
22
  import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
24
23
 
24
+ import { isRecord, errorText } from "./unknown-value.ts";
25
+
25
26
  export type SubmissionCall = { readonly id: string; readonly name: string };
26
27
  export type SubmissionOutcomeKind = "correctable-rejection" | "audit-escalation" | "infrastructure";
27
28
  /** Typed correctable-rejection codes — bounce/reminder paths only (#836: no sole/non-terminate reject). */
@@ -662,9 +663,7 @@ export function createSubmissionLedgerHost(
662
663
  // host-failure seam. Tool identity comes from the registry map.
663
664
  const reviewEscalate = tool.name === REVIEW_SUBMISSION_OUTPUT_TOOL_NAME
664
665
  && infrastructureFailureDiagnostic(params) !== undefined
665
- && params !== null
666
- && typeof params === "object"
667
- && !Array.isArray(params)
666
+ && isRecord(params)
668
667
  && (params as Record<string, unknown>).status === "escalate";
669
668
  if (!reviewEscalate) {
670
669
  failOnInfrastructureFailureDeclaration(
@@ -688,7 +687,7 @@ export function createSubmissionLedgerHost(
688
687
  toolCallId,
689
688
  outcome: "correctable-rejection",
690
689
  code: "typed-bounce",
691
- diagnostic: error instanceof Error ? error.message : String(error),
690
+ diagnostic: errorText(error),
692
691
  role,
693
692
  accepted: params,
694
693
  });
@@ -699,7 +698,7 @@ export function createSubmissionLedgerHost(
699
698
  attemptId,
700
699
  toolCallId,
701
700
  outcome: "infrastructure",
702
- diagnostic: error instanceof Error ? error.message : String(error),
701
+ diagnostic: errorText(error),
703
702
  role,
704
703
  accepted: params,
705
704
  });
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "./unknown-value.ts";
1
2
  /**
2
3
  * 起居录(ticket-provenance)typed 形状 —— ADR 0075 / ADR 0081 / #1090。
3
4
  *
@@ -61,10 +62,6 @@ export type TicketProvenanceLine = {
61
62
  readonly text: string;
62
63
  };
63
64
 
64
- function isRecord(value: unknown): value is Record<string, unknown> {
65
- return typeof value === "object" && value !== null && !Array.isArray(value);
66
- }
67
-
68
65
  function positiveInteger(value: unknown): number | undefined {
69
66
  if (typeof value === "number" && Number.isSafeInteger(value) && value >= 1) return value;
70
67
  if (typeof value === "string" && /^[1-9][0-9]*$/.test(value)) {
@@ -32,6 +32,8 @@ import {
32
32
  type TicketProvenanceSession,
33
33
  } from "./ticket-provenance-contracts.ts";
34
34
 
35
+ import { errorText } from "./unknown-value.ts";
36
+
35
37
  /**
36
38
  * Typed input failure for diarist bounds/session path (reask, not infrastructure).
37
39
  * Accept hook discriminates with instanceof — never Error.message prefixes.
@@ -281,7 +283,7 @@ async function projectSessionRanges(input: {
281
283
  if (error instanceof TicketProvenanceInputError) throw error;
282
284
  const code = errnoCode(error);
283
285
  if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
284
- const detail = error instanceof Error ? error.message : String(error);
286
+ const detail = errorText(error);
285
287
  throw new TicketProvenanceInputError(
286
288
  `session unreadable: ${input.session.path} (${detail})`,
287
289
  { cause: error },
@@ -13,6 +13,8 @@
13
13
  */
14
14
  import { createGhApiRunner, type GhApiRunner } from "./gh-api-runner.ts";
15
15
 
16
+ import { isRecord } from "./unknown-value.ts";
17
+
16
18
  export type BookRepoBinding = {
17
19
  bookKey: string;
18
20
  owner: string;
@@ -92,10 +94,6 @@ export class TicketSnapshotApiError extends Error {
92
94
  }
93
95
  }
94
96
 
95
- function isRecord(value: unknown): value is Record<string, unknown> {
96
- return typeof value === "object" && value !== null && !Array.isArray(value);
97
- }
98
-
99
97
  function requireNonEmpty(value: string, label: string): string {
100
98
  const trimmed = value.trim();
101
99
  if (!trimmed) throw new Error(`${label} must be a non-empty string`);
@@ -46,6 +46,7 @@ import {
46
46
  } from "./package-contracts/terminating-tools.ts";
47
47
  import { LEGACY_REVIEW_OUTPUT_ROLES, PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
48
48
 
49
+ import { isRecord, isMissingPathError } from "./unknown-value.ts";
49
50
 
50
51
  /** Declared refresh bound for the same viewing surface (seconds). */
51
52
  export const DEFAULT_REFRESH_BOUNDARY_SECONDS = 30;
@@ -122,14 +123,6 @@ export type TicketTrajectoryRun = {
122
123
 
123
124
  type ParsedRun = TicketTrajectoryRun;
124
125
 
125
- function isRecord(value: unknown): value is Record<string, unknown> {
126
- return typeof value === "object" && value !== null && !Array.isArray(value);
127
- }
128
-
129
- function isMissingPathError(error: unknown): boolean {
130
- return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
131
- }
132
-
133
126
  /** realpath when the node exists; lexical path only for recognized absence — never for other errors. */
134
127
  async function realpathOrLexicalIfMissing(path: string): Promise<string> {
135
128
  try {
@@ -5,6 +5,8 @@
5
5
  import { readFile, unlink, writeFile } from "node:fs/promises";
6
6
  import { join } from "node:path";
7
7
 
8
+ import { isEnoent, isRecord } from "./unknown-value.ts";
9
+
8
10
  const TYPED_HTTP_FILE = "typed-provider-http.json";
9
11
 
10
12
  export type TypedProviderHttpObservation = {
@@ -27,13 +29,7 @@ export async function clearTypedProviderHttpObservation(
27
29
  try {
28
30
  await unlink(typedProviderHttpPath(runDirectory));
29
31
  } catch (error) {
30
- if (
31
- error instanceof Error &&
32
- "code" in error &&
33
- (error as { code?: unknown }).code === "ENOENT"
34
- ) {
35
- return;
36
- }
32
+ if (isEnoent(error)) return;
37
33
  throw error;
38
34
  }
39
35
  }
@@ -75,17 +71,11 @@ export async function readLatestTypedProviderHttpObservation(
75
71
  try {
76
72
  text = await readFile(typedProviderHttpPath(runDirectory), "utf8");
77
73
  } catch (error) {
78
- if (
79
- error instanceof Error &&
80
- "code" in error &&
81
- (error as { code?: unknown }).code === "ENOENT"
82
- ) {
83
- return undefined;
84
- }
74
+ if (isEnoent(error)) return undefined;
85
75
  throw error;
86
76
  }
87
77
  const raw: unknown = JSON.parse(text);
88
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
78
+ if (!isRecord(raw)) {
89
79
  throw new Error("typed provider HTTP observation must be a JSON object");
90
80
  }
91
81
  const record = raw as Record<string, unknown>;
@@ -0,0 +1,34 @@
1
+ import { Guard } from "typebox/guard";
2
+
3
+ /**
4
+ * Sole readers for a plain object, a Node errno, and an Error message.
5
+ * Callers choose which codes count; ENOTDIR is not ENOENT.
6
+ * Plain objects are TypeBox `Guard.IsObjectNotArray`.
7
+ */
8
+
9
+ export function isRecord(value: unknown): value is Record<string, unknown> {
10
+ return Guard.IsObjectNotArray(value);
11
+ }
12
+
13
+ export function errnoCode(error: unknown): string | undefined {
14
+ return error !== null && typeof error === "object" && "code" in error
15
+ && typeof (error as { code: unknown }).code === "string"
16
+ ? (error as { code: string }).code
17
+ : undefined;
18
+ }
19
+
20
+ /** `Error.message`, otherwise `String(value)`. */
21
+ export function errorText(error: unknown): string {
22
+ return error instanceof Error ? error.message : String(error);
23
+ }
24
+
25
+ /** True only for ENOENT on an Error. ENOTDIR and every other code stay distinct. */
26
+ export function isEnoent(error: unknown): boolean {
27
+ return error instanceof Error && errnoCode(error) === "ENOENT";
28
+ }
29
+
30
+ /** ENOENT or ENOTDIR on an Error. */
31
+ export function isMissingPathError(error: unknown): boolean {
32
+ const code = errnoCode(error);
33
+ return error instanceof Error && (code === "ENOENT" || code === "ENOTDIR");
34
+ }
@@ -3,6 +3,8 @@
3
3
  * Pi's piped-stdin reader trims the wrapper; the body field keeps original bytes.
4
4
  * No size cap and no path/pointer substitute — the whole dialogue rides the envelope.
5
5
  */
6
+ import { isRecord } from "./unknown-value.ts";
7
+
6
8
  export const USER_DIALOGUE_STDIN_KIND = "ak-user-dialogue" as const;
7
9
 
8
10
  export type UserDialogueStdinEnvelope = {
@@ -15,7 +17,7 @@ export function encodeUserDialogueStdin(body: string): string {
15
17
  }
16
18
 
17
19
  function isUserDialogueEnvelope(value: unknown): value is UserDialogueStdinEnvelope {
18
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
20
+ if (!isRecord(value)) return false;
19
21
  const record = value as { kind?: unknown; body?: unknown };
20
22
  return record.kind === USER_DIALOGUE_STDIN_KIND && typeof record.body === "string";
21
23
  }
@@ -1,18 +1,15 @@
1
- import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
2
- import { Type, type Static } from "typebox";
3
- import { openToolObject } from "./open-tool-schema.ts";
4
- import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
1
+ import type { RoleHost, HostContext, HostGatekeeperActions } from "./host-contracts.ts";
5
2
 
6
3
  import {
7
- CODER_OUTPUT_TOOL_NAME,
8
- FIXER_OUTPUT_TOOL_NAME,
9
4
  validateAcceptedWorkerDetails,
10
5
  type CoderOutput,
11
6
  type FixerOutput,
12
7
  type WorkerOutput,
13
8
  type WorkerRoleLabel,
14
9
  } from "./package-contracts/worker-output.ts";
15
- import { PARTIALLY_COMPLETED_DEFINITION, fixerOutputSchema, validateFixerOutput, type FixerPhase } from "./package-contracts/fixer-output.ts";
10
+ import { validateFixerOutput, type FixerPhase } from "./package-contracts/fixer-output.ts";
11
+ import { registerFiledSubmissionTool } from "./filed-submission.ts";
12
+ import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
16
13
  import {
17
14
  FixerPacketValidationError,
18
15
  parseFixerPrerequisites,
@@ -23,37 +20,22 @@ import {
23
20
  WorkerCommitReminderError,
24
21
  WorkerPrefixReminderError,
25
22
  WorkerUnfinishedReasonReminderError,
23
+ type WorkerSubmissionGate,
26
24
  } from "./worker-submission-gates.ts";
27
25
  import {
28
26
  fixerBashSeatbeltDenyReason,
29
27
  matchFixerBashForbiddenLiteral,
30
28
  } from "./fixer-bash-seatbelt.ts";
31
29
 
30
+ import { isRecord } from "./unknown-value.ts";
31
+
32
32
  export {
33
33
  CODER_OUTPUT_TOOL_NAME,
34
34
  FIXER_OUTPUT_TOOL_NAME,
35
+ coderOutputSchema,
35
36
  validateAcceptedWorkerDetails,
36
- };
37
- export type { WorkerOutput };
38
-
39
- // status 的合法词写在 description。WORKER_DONE_STATUSES 只让 completed /
40
- // partially_completed 进入提交闸。unfinished 且未见理由说明时,运行时同 run 催全
41
- // (ADR 0050);理由在不在不按 JSON 类型判,也不在派发前用长度拒收。
42
- // report / remainingScope / reason 的声明只留字段名和语义。
43
- const CODER_STATUS_DESCRIPTION =
44
- `planned | completed | refused | partially_completed | unfinished。unfinished:缺前置或违宪约束致本局未完成。${PARTIALLY_COMPLETED_DEFINITION}` as const;
45
- const coderOutputObject = Type.Object({
46
- status: Type.Unknown({ description: CODER_STATUS_DESCRIPTION }),
47
- report: Type.Unknown({ description: "如实结果报告" }),
48
- remainingScope: Type.Unknown({ description: "本局后剩余工作" }),
49
- reason: Type.Unknown({
50
- description: "阻断原因:缺前置或违宪约束。缺待决 owner 决定或答复属缺前置。",
51
- }),
52
- });
53
- export const coderOutputSchema = withTerminatingOutputDeclarations(
54
- openToolObject(coderOutputObject),
55
- );
56
- export type { FixerOutput, CoderOutput };
37
+ } from "./package-contracts/worker-output.ts";
38
+ export type { WorkerOutput, FixerOutput, CoderOutput };
57
39
  export const FIXER_FLAG_DEFINITIONS = {
58
40
  packet: {
59
41
  name: "ak-fix-packet",
@@ -104,10 +86,6 @@ export type WorkerRoleRuntime = {
104
86
  armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }): void;
105
87
  };
106
88
 
107
- function isRecord(value: unknown): value is Record<string, unknown> {
108
- return typeof value === "object" && value !== null && !Array.isArray(value);
109
- }
110
-
111
89
  /** Read only the field that selects the worker's next package-owned path. */
112
90
  function workerStatusOf(output: WorkerOutput): string | undefined {
113
91
  return isRecord(output) && typeof output.status === "string"
@@ -130,10 +108,48 @@ export function validateWorkerOutput(
130
108
  return validateAcceptedWorkerDetails(output, "Coder") as CoderOutput;
131
109
  }
132
110
 
111
+ /**
112
+ * Coder and fixer share one submission sequence: record via the shared tool,
113
+ * then read status and bounce through the same reminder gate.
114
+ * Seat differences stay in the ready check and the projection.
115
+ */
116
+ function registerWorkerSubmission(
117
+ pi: RoleHost,
118
+ role: "coder" | "fixer",
119
+ options: {
120
+ readonly notReady: string;
121
+ readonly ready: () => boolean;
122
+ readonly phase: () => WorkerPhase | undefined;
123
+ readonly project: (parameters: unknown, phase: WorkerPhase) => WorkerOutput;
124
+ readonly submissionGate: WorkerSubmissionGate;
125
+ readonly hostActions: WorkerRoleHostActions;
126
+ },
127
+ ): void {
128
+ registerFiledSubmissionTool(pi, roleSubmissionDeclaration(role), {
129
+ readyError: () => (options.ready() ? undefined : options.notReady),
130
+ beforeAccept: async ({ toolCallId, parameters, ctx }) => {
131
+ const phase = options.phase();
132
+ if (phase === undefined) throw new Error(options.notReady);
133
+ const output = options.project(parameters, phase);
134
+ const status = workerStatusOf(output);
135
+ if (status !== undefined) {
136
+ assertAcceptableThroughHost(
137
+ options.submissionGate,
138
+ status,
139
+ output,
140
+ options.hostActions,
141
+ ctx,
142
+ toolCallId,
143
+ );
144
+ }
145
+ return output;
146
+ },
147
+ });
148
+ }
133
149
 
134
150
  /** Reminder bounces stay typed rejects; IO/infrastructure keep identity via host failInfrastructure. */
135
151
  function assertAcceptableThroughHost(
136
- submissionGate: { assertAcceptable(status: string, details?: unknown): void },
152
+ submissionGate: WorkerSubmissionGate,
137
153
  status: string,
138
154
  details: unknown,
139
155
  hostActions: WorkerRoleHostActions,
@@ -215,30 +231,13 @@ export function createFixerRoleRuntime(
215
231
 
216
232
  if (!lifecycleRegistered) {
217
233
  lifecycleRegistered = true;
218
- pi.registerTool({
219
- name: FIXER_OUTPUT_TOOL_NAME,
220
- label: "修内司输出",
221
- description: "提交修内司终局回执。",
222
- promptSnippet: "提交修内司终局回执",
223
- parameters: fixerOutputSchema,
224
- async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
225
- if (packet === undefined || phase === undefined) {
226
- throw new Error("修内司修理包与阶段未装载");
227
- }
228
- const output = deepFreeze(validateFixerOutput(parameters, phase));
229
- const status = workerStatusOf(output);
230
- if (status !== undefined) {
231
- assertAcceptableThroughHost(
232
- submissionGate,
233
- status,
234
- output,
235
- hostActions,
236
- ctx,
237
- toolCallId,
238
- );
239
- }
240
- return { content: [], details: output, terminate: true as const };
241
- },
234
+ registerWorkerSubmission(pi, "fixer", {
235
+ notReady: "修内司修理包与阶段未装载",
236
+ ready: () => packet !== undefined && phase !== undefined,
237
+ phase: () => phase,
238
+ project: (parameters, current) => deepFreeze(validateFixerOutput(parameters, current)),
239
+ submissionGate,
240
+ hostActions,
242
241
  });
243
242
  pi.on("tool_call", (event) => {
244
243
  if (event.toolName !== "bash") return;
@@ -313,32 +312,13 @@ export function createCoderRoleRuntime(
313
312
 
314
313
  if (!lifecycleRegistered) {
315
314
  lifecycleRegistered = true;
316
- pi.registerTool({
317
- name: CODER_OUTPUT_TOOL_NAME,
318
- label: "将作监输出",
319
- description: "提交将作监终局回执。",
320
- promptSnippet: "提交将作监终局回执",
321
- parameters: coderOutputSchema,
322
- async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
323
- if (task === undefined || phase === undefined) {
324
- throw new Error("将作监任务与阶段未装载");
325
- }
326
- const output = validateWorkerOutput(parameters, phase, "Coder");
327
- const status = workerStatusOf(output);
328
- // #836: skill-expansion evidence rejection deleted (陛下「2.4/5 删」).
329
- // Skill still ships with the package (ADR 0052); code no longer refuses on it.
330
- if (status !== undefined) {
331
- assertAcceptableThroughHost(
332
- submissionGate,
333
- status,
334
- output,
335
- hostActions,
336
- ctx,
337
- toolCallId,
338
- );
339
- }
340
- return { content: [], details: output, terminate: true as const };
341
- },
315
+ registerWorkerSubmission(pi, "coder", {
316
+ notReady: "将作监任务与阶段未装载",
317
+ ready: () => task !== undefined && phase !== undefined,
318
+ phase: () => phase,
319
+ project: (parameters, current) => validateWorkerOutput(parameters, current, "Coder"),
320
+ submissionGate,
321
+ hostActions,
342
322
  });
343
323
  pi.on("before_agent_start", (event, ctx) => {
344
324
  if (soul === undefined) throw new Error("将作监职分未装载");