@akagilnc/pi-workflow-roles 0.1.5368 → 0.1.5375

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 (165) hide show
  1. package/dist/acp-host/production-host.js +719 -776
  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/compliance-transport.js +2 -1
  16. package/dist/countersign-contracts.js +2 -1
  17. package/dist/diarist-contracts.js +3 -2
  18. package/dist/doctor-contracts.js +1 -3
  19. package/dist/doctor-evidence.js +7 -8
  20. package/dist/dossier-resolution.js +1 -3
  21. package/dist/engine-detour-usage.js +1 -3
  22. package/dist/gatekeeper-role.js +2 -9
  23. package/dist/gleaner-left-contracts.js +2 -1
  24. package/dist/headless-host/description.js +14 -17
  25. package/dist/headless-host/production-host.js +721 -780
  26. package/dist/host-session-record.js +3 -2
  27. package/dist/inspector-contracts.js +2 -1
  28. package/dist/ledger-session-read.js +1 -3
  29. package/dist/merger-contracts.js +9 -9
  30. package/dist/migrate-book-topology.js +348 -314
  31. package/dist/navigator-attendance.js +9 -11
  32. package/dist/navigator-invocation-identity.js +3 -2
  33. package/dist/navigator-public-session.js +3 -5
  34. package/dist/navigator-session-contracts.js +8 -10
  35. package/dist/navigator-work-base.js +5 -8
  36. package/dist/notary-contracts.js +3 -5
  37. package/dist/package-contracts/auditor-output.js +1 -3
  38. package/dist/package-contracts/fixer-packet.js +3 -6
  39. package/dist/package-contracts/gatekeeper-output.js +1 -3
  40. package/dist/package-contracts/navigator-output.js +8 -8
  41. package/dist/package-contracts/terminating-infrastructure.js +5 -9
  42. package/dist/package-contracts/terminating-tools.js +4 -3
  43. package/dist/public-cli/analyst-run.js +3 -2
  44. package/dist/public-cli/auto-resume.js +3 -8
  45. package/dist/public-cli/cli.js +9 -8
  46. package/dist/public-cli/config.js +8 -11
  47. package/dist/public-cli/countersign-run.js +1 -3
  48. package/dist/public-cli/host-providers.js +7 -14
  49. package/dist/public-cli/instruction-seat-run.js +4 -3
  50. package/dist/public-cli/invocation.js +9 -8
  51. package/dist/public-cli/main.js +1531 -1567
  52. package/dist/public-cli/post-admission.js +4 -3
  53. package/dist/public-cli/run-lifecycle.js +14 -26
  54. package/dist/public-cli/settlement.js +10 -26
  55. package/dist/public-role-summons.js +3 -2
  56. package/dist/receipt-delivery-policy.js +1 -3
  57. package/dist/role-run-placement.js +3 -5
  58. package/dist/role-run-relocation.js +12 -19
  59. package/dist/run-terminal-artifacts.js +1 -11
  60. package/dist/run-ticket-number.js +2 -4
  61. package/dist/session-assistant-usage.js +2 -1
  62. package/dist/session-dialogue.js +1 -14
  63. package/dist/sitian-reader.js +2 -4
  64. package/dist/submission-correctable-error.js +2 -1
  65. package/dist/submission-errors.js +2 -1
  66. package/dist/submission-gate.js +2 -1
  67. package/dist/submission-ledger.js +4 -5
  68. package/dist/ticket-provenance-contracts.js +1 -3
  69. package/dist/ticket-provenance.js +2 -1
  70. package/dist/typed-provider-http.js +4 -7
  71. package/dist/unknown-value.js +28 -0
  72. package/dist/user-dialogue-stdin.js +2 -1
  73. package/package.json +1 -1
  74. package/src/acp-host/role-turn-host.ts +4 -2
  75. package/src/activation-ledger-git.ts +3 -2
  76. package/src/activation-ledger-topology.ts +4 -12
  77. package/src/analyst-entry.ts +3 -11
  78. package/src/analyst-gate-cycles-read.ts +3 -15
  79. package/src/analyst-index.ts +3 -13
  80. package/src/analyst-ledger.ts +2 -16
  81. package/src/analyst-metric-families/acceptance-success-rework.ts +2 -4
  82. package/src/analyst-metric-families/round-timeline.ts +2 -4
  83. package/src/analyst-page.ts +2 -2
  84. package/src/archivist-record-topology.ts +2 -2
  85. package/src/audit-escalation.ts +2 -1
  86. package/src/auditor-soul.ts +3 -1
  87. package/src/book-topology-migration-placement.ts +5 -16
  88. package/src/book-topology-migrators-t11.ts +16 -15
  89. package/src/book-topology-mixed-volume-migrators.ts +3 -6
  90. package/src/book-topology-record-class-migrators.ts +7 -10
  91. package/src/book-topology-runs-migrator.ts +4 -3
  92. package/src/collector-config.ts +7 -9
  93. package/src/compliance-transport.ts +2 -1
  94. package/src/countersign-contracts.ts +2 -1
  95. package/src/diarist-contracts.ts +3 -2
  96. package/src/doctor-contracts.ts +3 -1
  97. package/src/doctor-evidence.ts +8 -7
  98. package/src/doctor-role.ts +4 -2
  99. package/src/dossier-resolution.ts +2 -4
  100. package/src/engine-detour-tool.ts +0 -2
  101. package/src/engine-detour-usage.ts +2 -4
  102. package/src/external-host-turn-loop.ts +3 -1
  103. package/src/factory-board.ts +1 -8
  104. package/src/gatekeeper-role.ts +2 -10
  105. package/src/gleaner-left-contracts.ts +3 -1
  106. package/src/headless-host/description.ts +15 -18
  107. package/src/headless-host/role-turn-host.ts +10 -9
  108. package/src/host-session-record.ts +4 -2
  109. package/src/inspector-contracts.ts +2 -1
  110. package/src/ledger-session-read.ts +2 -5
  111. package/src/merger-contracts.ts +10 -9
  112. package/src/navigator-attendance.ts +10 -12
  113. package/src/navigator-invocation-identity.ts +3 -2
  114. package/src/navigator-public-session.ts +4 -6
  115. package/src/navigator-session-contracts.ts +8 -11
  116. package/src/navigator-work-base.ts +5 -8
  117. package/src/navigator-work-context.ts +2 -1
  118. package/src/notary-contracts.ts +4 -6
  119. package/src/oauth-keepalive.ts +5 -18
  120. package/src/package-contracts/auditor-output.ts +2 -4
  121. package/src/package-contracts/fixer-packet.ts +4 -6
  122. package/src/package-contracts/gatekeeper-output.ts +2 -4
  123. package/src/package-contracts/judge-output.ts +0 -1
  124. package/src/package-contracts/navigator-output.ts +8 -8
  125. package/src/package-contracts/terminating-infrastructure.ts +6 -20
  126. package/src/package-contracts/terminating-tools.ts +8 -12
  127. package/src/pi/adapter.ts +0 -1
  128. package/src/pi/role-turn-host.ts +0 -1
  129. package/src/public-cli/analyst-run.ts +4 -2
  130. package/src/public-cli/auto-resume.ts +4 -11
  131. package/src/public-cli/cli.ts +10 -10
  132. package/src/public-cli/config.ts +8 -27
  133. package/src/public-cli/countersign-run.ts +2 -4
  134. package/src/public-cli/host-providers.ts +10 -27
  135. package/src/public-cli/instruction-seat-run.ts +5 -3
  136. package/src/public-cli/invocation.ts +11 -10
  137. package/src/public-cli/post-admission.ts +5 -3
  138. package/src/public-cli/run-lifecycle.ts +20 -41
  139. package/src/public-cli/settlement.ts +11 -33
  140. package/src/public-role-summons.ts +4 -3
  141. package/src/receipt-delivery-policy.ts +1 -4
  142. package/src/reviewer-role.ts +0 -1
  143. package/src/role-envelope.ts +5 -3
  144. package/src/role-run-placement.ts +4 -10
  145. package/src/role-run-relocation.ts +13 -23
  146. package/src/role-runtime.ts +9 -15
  147. package/src/run-terminal-artifacts.ts +2 -16
  148. package/src/run-ticket-number.ts +4 -10
  149. package/src/session-assistant-usage.ts +3 -1
  150. package/src/session-dialogue.ts +1 -4
  151. package/src/session-identity.ts +0 -1
  152. package/src/sitian-reader.ts +2 -4
  153. package/src/submission-correctable-error.ts +3 -1
  154. package/src/submission-errors.ts +2 -1
  155. package/src/submission-gate.ts +3 -1
  156. package/src/submission-ledger.ts +5 -6
  157. package/src/ticket-provenance-contracts.ts +1 -4
  158. package/src/ticket-provenance.ts +3 -1
  159. package/src/ticket-snapshot.ts +2 -4
  160. package/src/ticket-trajectory.ts +1 -8
  161. package/src/typed-provider-http.ts +5 -15
  162. package/src/unknown-value.ts +34 -0
  163. package/src/user-dialogue-stdin.ts +3 -1
  164. package/src/worker-role.ts +2 -5
  165. package/src/worker-submission-gates.ts +5 -7
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { Type } from "typebox";
9
9
 
10
+ import { isRecord } from "../unknown-value.ts";
10
11
  import { openToolObject } from "../open-tool-schema.ts";
11
12
  import { withTerminatingOutputDeclarations } from "./terminating-infrastructure.ts";
12
13
 
@@ -33,19 +34,18 @@ export type NavigatorAdvice = { readonly prose: string };
33
34
  export function projectLawfulNavigatorOutput(value: unknown): NavigatorAdvice | undefined {
34
35
  if (value === undefined || value === null) return undefined;
35
36
  if (typeof value === "string") return { prose: value };
36
- if (typeof value === "object" && !Array.isArray(value)) {
37
- const record = value as Record<string, unknown>;
38
- if (typeof record.prose === "string") return { prose: record.prose };
39
- if (record.prose !== undefined && record.prose !== null) {
40
- return { prose: typeof record.prose === "string" ? record.prose : String(record.prose) };
37
+ if (isRecord(value)) {
38
+ if (typeof value.prose === "string") return { prose: value.prose };
39
+ if (value.prose !== undefined && value.prose !== null) {
40
+ return { prose: typeof value.prose === "string" ? value.prose : String(value.prose) };
41
41
  }
42
42
  // Free-form object submission: present the whole body as prose when it has content.
43
- const keys = Object.keys(record);
43
+ const keys = Object.keys(value);
44
44
  if (keys.length === 0) return { prose: "" };
45
45
  try {
46
- return { prose: JSON.stringify(record) };
46
+ return { prose: JSON.stringify(value) };
47
47
  } catch {
48
- return { prose: String(record) };
48
+ return { prose: String(value) };
49
49
  }
50
50
  }
51
51
  return { prose: String(value) };
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { Type, type TSchema } from "typebox";
15
15
  import type { CorrectableSubmissionError } from "../submission-correctable-error.ts";
16
+ import { isRecord } from "../unknown-value.ts";
16
17
 
17
18
  export const INFRASTRUCTURE_FAILURE_DECLARATION_KEY =
18
19
  "infrastructureFailure" as const;
@@ -94,26 +95,11 @@ type TerminatingInfrastructureHostActions<C> = {
94
95
  function isInfrastructureFailureDeclaration(
95
96
  parameters: unknown,
96
97
  ): boolean {
97
- if (
98
- parameters === null ||
99
- typeof parameters !== "object" ||
100
- Array.isArray(parameters)
101
- ) {
102
- return false;
103
- }
104
- const record = parameters as Record<string, unknown>;
105
- if (!Object.hasOwn(record, INFRASTRUCTURE_FAILURE_DECLARATION_KEY)) return false;
106
- const declaration = record[INFRASTRUCTURE_FAILURE_DECLARATION_KEY];
107
- if (
108
- declaration === null ||
109
- typeof declaration !== "object" ||
110
- Array.isArray(declaration)
111
- ) {
112
- return false;
113
- }
114
- const diagnostic = (declaration as Record<string, unknown>)[
115
- INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY
116
- ];
98
+ if (!isRecord(parameters)) return false;
99
+ if (!Object.hasOwn(parameters, INFRASTRUCTURE_FAILURE_DECLARATION_KEY)) return false;
100
+ const declaration = parameters[INFRASTRUCTURE_FAILURE_DECLARATION_KEY];
101
+ if (!isRecord(declaration)) return false;
102
+ const diagnostic = declaration[INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY];
117
103
  return typeof diagnostic === "string" && diagnostic.trim().length > 0;
118
104
  }
119
105
 
@@ -36,6 +36,8 @@ import {
36
36
  type WorkerOutput,
37
37
  } from "./worker-output.ts";
38
38
 
39
+ import { isRecord } from "../unknown-value.ts";
40
+
39
41
  export {
40
42
  CODER_OUTPUT_TOOL_NAME,
41
43
  COLLECTOR_OUTPUT_TOOL,
@@ -114,7 +116,6 @@ export class AcceptedDetailsContractError extends CorrectableSubmissionError {
114
116
  }
115
117
  }
116
118
 
117
-
118
119
  /**
119
120
  * #836: status allowlist rejection deleted. Original object is the receipt.
120
121
  * Callers that need a typed view may still cast; code must not bounce on status.
@@ -123,7 +124,7 @@ export function validateAcceptedDetails(
123
124
  _toolName: TerminatingToolName,
124
125
  details: unknown,
125
126
  ): AcceptedDetails {
126
- if (details !== null && typeof details === "object" && !Array.isArray(details)) {
127
+ if (isRecord(details)) {
127
128
  return details as AcceptedDetails;
128
129
  }
129
130
  // Non-object payload still records as empty object rather than rejecting.
@@ -178,18 +179,13 @@ export function deepEqual(a: unknown, b: unknown): boolean {
178
179
  if (!Array.isArray(b) || a.length !== b.length) return false;
179
180
  return a.every((item, index) => deepEqual(item, b[index]));
180
181
  }
181
- if (typeof a === "object") {
182
- if (typeof b !== "object" || b === null || Array.isArray(b)) return false;
183
- const aKeys = Object.keys(a as object).sort();
184
- const bKeys = Object.keys(b as object).sort();
182
+ if (isRecord(a)) {
183
+ if (!isRecord(b)) return false;
184
+ const aKeys = Object.keys(a).sort();
185
+ const bKeys = Object.keys(b).sort();
185
186
  if (aKeys.length !== bKeys.length) return false;
186
187
  if (!aKeys.every((key, index) => key === bKeys[index])) return false;
187
- return aKeys.every((key) =>
188
- deepEqual(
189
- (a as Record<string, unknown>)[key],
190
- (b as Record<string, unknown>)[key],
191
- )
192
- );
188
+ return aKeys.every((key) => deepEqual(a[key], b[key]));
193
189
  }
194
190
  return false;
195
191
  }
package/src/pi/adapter.ts CHANGED
@@ -20,7 +20,6 @@ import { createRoleRuntimeExtension, type RoleRuntimeDependencies } from "../rol
20
20
  import { renderAgentStartMaterials } from "../agent-start-materials.ts";
21
21
  import { readUserDialogueStdin } from "../user-dialogue-stdin.ts";
22
22
 
23
-
24
23
  export type PiRoleHostAdapter = RoleEnvelopeHost;
25
24
 
26
25
  const piContexts = new WeakMap<HostContext, ExtensionContext>();
@@ -26,7 +26,6 @@ import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from
26
26
  import { projectActivationFlags } from "../role-activation-flags.ts";
27
27
  import { encodeUserDialogueStdin } from "../user-dialogue-stdin.ts";
28
28
 
29
-
30
29
  /** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
31
30
  const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
32
31
 
@@ -41,6 +41,8 @@ import type {
41
41
  } from "./invocation.ts";
42
42
  import { presentControlledFailure, presentStructuralRejection } from "./settlement.ts";
43
43
 
44
+ import { errorText } from "../unknown-value.ts";
45
+
44
46
  export type AnalystRunEnv = {
45
47
  readonly home: string;
46
48
  };
@@ -97,7 +99,7 @@ export async function buildAnalystIssueModeInputFromPublicArgv(
97
99
 
98
100
  /**
99
101
  * Attachment JSON → library AnalystSweepModeInput via the sole schema (#337).
100
- * No parallel hand shape; rejects missing/extra/wrong-type fields only.
102
+ * No parallel hand shape; rejects missing or wrong-type fields only.
101
103
  */
102
104
  export function parseAnalystSweepModeInputFromJsonValue(
103
105
  value: unknown,
@@ -141,7 +143,7 @@ export async function buildAnalystSweepModeInputFromAttachmentPaths(
141
143
  try {
142
144
  text = exactUtf8(bytes, "analyst sweep attachment");
143
145
  } catch (error) {
144
- const detail = error instanceof Error ? error.message : String(error);
146
+ const detail = errorText(error);
145
147
  throw new CliUsageError(detail, { cause: error });
146
148
  }
147
149
 
@@ -42,6 +42,8 @@ import {
42
42
  import type { CliIo } from "./cli-io.ts";
43
43
  import { serializeThrownValue } from "../serialize-thrown-value.ts";
44
44
 
45
+ import { errorText, isEnoent } from "../unknown-value.ts";
46
+
45
47
  const dummyIo: CliIo = { stdout: () => {}, stderr: () => {} };
46
48
 
47
49
  /**
@@ -132,7 +134,7 @@ export class TurnDispatchedFailure extends Error {
132
134
  constructor(cause: unknown) {
133
135
  super(
134
136
  `settlement failed after the host turn genuinely started: ${
135
- cause instanceof Error ? cause.message : String(cause)
137
+ errorText(cause)
136
138
  }`,
137
139
  { cause },
138
140
  );
@@ -164,7 +166,7 @@ export async function ensureRealArtifactsDirectory(runDirectory: string): Promis
164
166
  throw new Error("run artifact retention: artifacts path is not a real directory");
165
167
  }
166
168
  } catch (error) {
167
- if (!isMissingPathError(error)) throw error;
169
+ if (!isEnoent(error)) throw error;
168
170
  await mkdir(artifactsDir, { recursive: true });
169
171
  const created = await lstat(artifactsDir);
170
172
  if (created.isSymbolicLink() || !created.isDirectory()) {
@@ -174,15 +176,6 @@ export async function ensureRealArtifactsDirectory(runDirectory: string): Promis
174
176
  return artifactsDir;
175
177
  }
176
178
 
177
- /** ENOENT identity shared with settlement.ts's hardened audit-artifact path. */
178
- function isMissingPathError(error: unknown): boolean {
179
- return (
180
- error instanceof Error &&
181
- "code" in error &&
182
- (error as { code?: unknown }).code === "ENOENT"
183
- );
184
- }
185
-
186
179
  /** Cycle- and bigint-safe JSON replacer so serialization itself cannot drop data. */
187
180
  function jsonSafeReplacer(): (key: string, value: unknown) => unknown {
188
181
  const seen = new WeakSet<object>();
@@ -105,6 +105,8 @@ import {
105
105
  } from "./settlement.ts";
106
106
  import type { TerminalResult } from "./terminal.ts";
107
107
 
108
+ import { errorText } from "../unknown-value.ts";
109
+
108
110
  export {
109
111
  buildExplicitInternalActivationArgs,
110
112
  resolveInternalRoleEntrypoint,
@@ -237,8 +239,6 @@ export type CliEnv = {
237
239
  signal?: AbortSignal;
238
240
  };
239
241
 
240
-
241
-
242
242
  type RoleEnvironmentOptions = {
243
243
  role: PublicCallableRole;
244
244
  home: string;
@@ -542,7 +542,7 @@ function requireLegalEngineModel(model: string): string {
542
542
  return assertLegalEngineModel(model);
543
543
  } catch (error) {
544
544
  throw new CliUsageError(
545
- error instanceof Error ? error.message : String(error),
545
+ errorText(error),
546
546
  { cause: error },
547
547
  );
548
548
  }
@@ -553,7 +553,7 @@ function requireLegalEngineName(name: string): string {
553
553
  return assertLegalEngineName(name);
554
554
  } catch (error) {
555
555
  throw new CliUsageError(
556
- error instanceof Error ? error.message : String(error),
556
+ errorText(error),
557
557
  { cause: error },
558
558
  );
559
559
  }
@@ -585,7 +585,7 @@ function loadAndValidateConfig(
585
585
  validatePublicCliConfigAxes(config, packageRoot);
586
586
  } catch (error) {
587
587
  throw new CliUsageError(
588
- error instanceof Error ? error.message : String(error),
588
+ errorText(error),
589
589
  { cause: error },
590
590
  );
591
591
  }
@@ -917,7 +917,7 @@ async function runConfigCommand(
917
917
  try {
918
918
  config = setPersistentSeatHost(config, seat, unset ? undefined : args[2]!);
919
919
  } catch (error) {
920
- throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
920
+ throw new CliUsageError(errorText(error), { cause: error });
921
921
  }
922
922
  await savePublicCliConfig(config, home);
923
923
  io.stdout(renderConfig(config, home));
@@ -941,7 +941,7 @@ async function runConfigCommand(
941
941
  config = setPersistentSeatEngine(config, seat, name, engineModel);
942
942
  } catch (error) {
943
943
  throw new CliUsageError(
944
- error instanceof Error ? error.message : String(error),
944
+ errorText(error),
945
945
  { cause: error },
946
946
  );
947
947
  }
@@ -963,7 +963,7 @@ async function runConfigCommand(
963
963
  config = setPersistentSeatEngine(config, seat, undefined);
964
964
  } catch (error) {
965
965
  throw new CliUsageError(
966
- error instanceof Error ? error.message : String(error),
966
+ errorText(error),
967
967
  { cause: error },
968
968
  );
969
969
  }
@@ -987,7 +987,7 @@ async function runConfigCommand(
987
987
  config = setPersistentSeatEngineModel(config, seat, model);
988
988
  } catch (error) {
989
989
  throw new CliUsageError(
990
- error instanceof Error ? error.message : String(error),
990
+ errorText(error),
991
991
  { cause: error },
992
992
  );
993
993
  }
@@ -1009,7 +1009,7 @@ async function runConfigCommand(
1009
1009
  config = setPersistentSeatEngineModel(config, seat, undefined);
1010
1010
  } catch (error) {
1011
1011
  throw new CliUsageError(
1012
- error instanceof Error ? error.message : String(error),
1012
+ errorText(error),
1013
1013
  { cause: error },
1014
1014
  );
1015
1015
  }
@@ -27,6 +27,7 @@ import {
27
27
  type PublicConfigurableSeat,
28
28
  type PublicThinkingLevel,
29
29
  } from "./registry.ts";
30
+ import { isEnoent, isRecord } from "../unknown-value.ts";
30
31
 
31
32
  /** Province officers that may carry a persistent model override (#453). Registry order. */
32
33
  type ProvinceConfigRole = Extract<
@@ -134,13 +135,7 @@ export async function loadPublicCliConfig(
134
135
  const raw = await readFile(path, "utf8");
135
136
  return parsePublicCliConfig(JSON.parse(raw));
136
137
  } catch (error) {
137
- if (
138
- error instanceof Error &&
139
- "code" in error &&
140
- (error as NodeJS.ErrnoException).code === "ENOENT"
141
- ) {
142
- return { seats: {} };
143
- }
138
+ if (isEnoent(error)) return { seats: {} };
144
139
  throw error;
145
140
  }
146
141
  }
@@ -481,7 +476,7 @@ function serializePublicCliConfig(config: PublicCliConfig): {
481
476
  }
482
477
 
483
478
  function parsePublicCliConfig(value: unknown): PublicCliConfig {
484
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
479
+ if (!isRecord(value)) {
485
480
  throw new Error("public CLI config must be an object");
486
481
  }
487
482
  const record = value as {
@@ -500,11 +495,7 @@ function parsePublicCliConfig(value: unknown): PublicCliConfig {
500
495
  // disk-shaped seats map and a memory-shaped bucket both survive.
501
496
  const unknownSeats: Record<string, unknown> = {};
502
497
  if (record.unknownSeats !== undefined) {
503
- if (
504
- record.unknownSeats === null ||
505
- typeof record.unknownSeats !== "object" ||
506
- Array.isArray(record.unknownSeats)
507
- ) {
498
+ if (!isRecord(record.unknownSeats)) {
508
499
  throw new Error("public CLI config.unknownSeats must be an object");
509
500
  }
510
501
  Object.assign(unknownSeats, record.unknownSeats as Record<string, unknown>);
@@ -517,11 +508,7 @@ function parsePublicCliConfig(value: unknown): PublicCliConfig {
517
508
  if (record.seats === undefined) {
518
509
  return withOpaque({});
519
510
  }
520
- if (
521
- record.seats === null ||
522
- typeof record.seats !== "object" ||
523
- Array.isArray(record.seats)
524
- ) {
511
+ if (!isRecord(record.seats)) {
525
512
  throw new Error("public CLI config.seats must be an object");
526
513
  }
527
514
  const seats: PublicCliConfig["seats"] = {};
@@ -542,7 +529,7 @@ function parsePublicCliConfig(value: unknown): PublicCliConfig {
542
529
  }
543
530
 
544
531
  function parseSeatModelConfig(value: unknown, seat: string): PersistentSeatConfig {
545
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
532
+ if (!isRecord(value)) {
546
533
  throw new Error(`config seat ${seat} must be an object`);
547
534
  }
548
535
  const raw = value as Record<string, unknown>;
@@ -825,7 +812,7 @@ export function listRolesForDisplay(
825
812
  export function credentialProvidersFromAuthData(
826
813
  data: unknown,
827
814
  ): CredentialProviders {
828
- if (data === null || typeof data !== "object" || Array.isArray(data)) {
815
+ if (!isRecord(data)) {
829
816
  return { "openai-codex": false, xai: false };
830
817
  }
831
818
  const record = data as Record<string, unknown>;
@@ -850,13 +837,7 @@ export async function loadCredentialProviders(
850
837
  const raw = await readFile(join(agentDir, "auth.json"), "utf8");
851
838
  return credentialProvidersFromAuthData(JSON.parse(raw));
852
839
  } catch (error) {
853
- if (
854
- error instanceof Error &&
855
- "code" in error &&
856
- (error as NodeJS.ErrnoException).code === "ENOENT"
857
- ) {
858
- return { "openai-codex": false, xai: false };
859
- }
840
+ if (isEnoent(error)) return { "openai-codex": false, xai: false };
860
841
  throw error;
861
842
  }
862
843
  }
@@ -9,6 +9,8 @@ import type { PostAdmissionEnv } from "./post-admission.ts";
9
9
  import type { CliIo } from "./cli-io.ts";
10
10
  import type { TerminalRoleOutcome } from "./terminal.ts";
11
11
 
12
+ import { isRecord } from "../unknown-value.ts";
13
+
12
14
  export type CountersignRunEnv = PostAdmissionEnv & {
13
15
  principalAuthority: DurablePrincipalAuthority;
14
16
  createRunId?: () => string;
@@ -51,10 +53,6 @@ export type CourtDiaristInvocationResult = {
51
53
  readonly failedWithoutEscalate?: { readonly diagnostic: string };
52
54
  };
53
55
 
54
- function isRecord(value: unknown): value is Record<string, unknown> {
55
- return typeof value === "object" && value !== null && !Array.isArray(value);
56
- }
57
-
58
56
  function isEscalatePayload(payload: unknown): boolean {
59
57
  if (!isRecord(payload)) return false;
60
58
  return (
@@ -12,6 +12,7 @@ import { readFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
 
14
14
  import type { SeatModelConfig } from "./config.ts";
15
+ import { isEnoent, isRecord } from "../unknown-value.ts";
15
16
 
16
17
  /** host → seat-provider → host-facing provider. */
17
18
  export type HostProvidersTable = Readonly<
@@ -31,25 +32,19 @@ export function hermesProviderModelsCachePath(home: string): string {
31
32
  }
32
33
 
33
34
  export function parseHostProvidersTable(value: unknown): HostProvidersTable {
34
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
35
+ if (!isRecord(value)) {
35
36
  throw new Error("host-providers.json must be an object");
36
37
  }
37
38
  const out: Record<string, Record<string, string>> = {};
38
- for (const [host, byProvider] of Object.entries(value as Record<string, unknown>)) {
39
+ for (const [host, byProvider] of Object.entries(value)) {
39
40
  if (host.trim() === "") {
40
41
  throw new Error("host-providers.json host key must be non-empty");
41
42
  }
42
- if (
43
- byProvider === null ||
44
- typeof byProvider !== "object" ||
45
- Array.isArray(byProvider)
46
- ) {
43
+ if (!isRecord(byProvider)) {
47
44
  throw new Error(`host-providers.json[${host}] must be an object`);
48
45
  }
49
46
  const providers: Record<string, string> = {};
50
- for (const [seatProvider, hostProvider] of Object.entries(
51
- byProvider as Record<string, unknown>,
52
- )) {
47
+ for (const [seatProvider, hostProvider] of Object.entries(byProvider)) {
53
48
  if (seatProvider.trim() === "") {
54
49
  throw new Error(
55
50
  `host-providers.json[${host}] seat-provider key must be non-empty`,
@@ -79,13 +74,7 @@ export function loadHostProvidersTable(home: string): HostProvidersTable {
79
74
  const raw = readFileSync(path, "utf8");
80
75
  return parseHostProvidersTable(JSON.parse(raw) as unknown);
81
76
  } catch (error) {
82
- if (
83
- error instanceof Error &&
84
- "code" in error &&
85
- (error as NodeJS.ErrnoException).code === "ENOENT"
86
- ) {
87
- return {};
88
- }
77
+ if (isEnoent(error)) return {};
89
78
  throw error;
90
79
  }
91
80
  }
@@ -115,24 +104,18 @@ export function listHermesProvidersForModel(
115
104
  try {
116
105
  text = readFileSync(path, "utf8");
117
106
  } catch (error) {
118
- if (
119
- error instanceof Error &&
120
- "code" in error &&
121
- (error as NodeJS.ErrnoException).code === "ENOENT"
122
- ) {
123
- return [];
124
- }
107
+ if (isEnoent(error)) return [];
125
108
  throw error;
126
109
  }
127
110
  const raw: unknown = JSON.parse(text);
128
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
111
+ if (!isRecord(raw)) {
129
112
  throw new Error(
130
113
  `hermes provider_models_cache.json must be an object: ${path}`,
131
114
  );
132
115
  }
133
116
  const found: string[] = [];
134
- for (const [provider, entry] of Object.entries(raw as Record<string, unknown>)) {
135
- if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
117
+ for (const [provider, entry] of Object.entries(raw)) {
118
+ if (!isRecord(entry)) {
136
119
  continue;
137
120
  }
138
121
  const models = (entry as { models?: unknown }).models;
@@ -101,6 +101,8 @@ import {
101
101
  latestPayloadEscalated,
102
102
  type CountersignRunEnv,
103
103
  } from "./countersign-run.ts";
104
+ import { isRecord, errorText } from "../unknown-value.ts";
105
+
104
106
  export type InstructionSeatRunEnv = PostAdmissionEnv & Pick<
105
107
  CountersignRunEnv,
106
108
  "reviewReask" | "gateReviewInstruction" | "parentRunPath"
@@ -159,7 +161,7 @@ function unreadablePostSubmissionStatus(
159
161
  const route = POST_SUBMISSION_ROUTING[admitted.role];
160
162
  if (route === undefined) return undefined;
161
163
  const payload = terminal.roleOutcome.payloads?.at(-1);
162
- const status = payload !== null && typeof payload === "object" && !Array.isArray(payload)
164
+ const status = isRecord(payload)
163
165
  ? (payload as Record<string, unknown>).status
164
166
  : undefined;
165
167
  return typeof status === "string" && route.statuses.has(status)
@@ -659,7 +661,7 @@ export async function runPublicInstructionSeat(
659
661
  auditorSource = resolved.runDirectory;
660
662
  auditorTicket = await readBoardTicketNumber(resolved.runDirectory);
661
663
  } catch (error) {
662
- presentStructuralRejection(new CliUsageError(error instanceof Error ? error.message : String(error)), io);
664
+ presentStructuralRejection(new CliUsageError(errorText(error)), io);
663
665
  return { exitCode: 2 };
664
666
  }
665
667
  if (auditorSource === undefined) return { exitCode: 2 };
@@ -997,7 +999,7 @@ async function auditSubmittedRole(
997
999
  || turn.terminal?.roleOutcome.kind !== "accepted") return turn;
998
1000
  const accepted = turn.terminal.roleOutcome.payloads?.at(-1);
999
1001
  if (accepted === undefined) throw new Error("accepted submission has no payload");
1000
- const record = accepted !== null && typeof accepted === "object" && !Array.isArray(accepted)
1002
+ const record = isRecord(accepted)
1001
1003
  ? accepted as Record<string, unknown> : undefined;
1002
1004
  const status = admitted.role === "secretariat" ? record?.secretariatStatus : record?.status;
1003
1005
  if (admitted.role === "secretariat" && status !== "converged" && status !== "escalate") {
@@ -110,6 +110,8 @@ import {
110
110
  } from "./option-definitions.ts";
111
111
  import type { PublicThinkingLevel } from "./registry.ts";
112
112
 
113
+ import { errorText, isRecord } from "../unknown-value.ts";
114
+
113
115
  export type FrozenAttachment = {
114
116
  /** Original caller path retained only as provenance. */
115
117
  readonly provenancePath: string;
@@ -1571,7 +1573,7 @@ export async function admitPublicRole(
1571
1573
  try {
1572
1574
  repository = parseCollectorRepository(parsed.repo);
1573
1575
  } catch (error) {
1574
- const detail = error instanceof Error ? error.message : String(error);
1576
+ const detail = errorText(error);
1575
1577
  throw new CliUsageError(detail, { cause: error });
1576
1578
  }
1577
1579
  } else {
@@ -1585,7 +1587,7 @@ export async function admitPublicRole(
1585
1587
  manifestCanonicalJson = manifest.canonicalJson;
1586
1588
  } catch (error) {
1587
1589
  throw new CliUsageError(
1588
- error instanceof Error ? error.message : String(error),
1590
+ errorText(error),
1589
1591
  { cause: error },
1590
1592
  );
1591
1593
  }
@@ -1640,7 +1642,7 @@ export async function admitPublicRole(
1640
1642
  });
1641
1643
  } catch (error) {
1642
1644
  if (error instanceof CliUsageError) throw error;
1643
- const detail = error instanceof Error ? error.message : String(error);
1645
+ const detail = errorText(error);
1644
1646
  throw new CliUsageError(detail, { cause: error });
1645
1647
  }
1646
1648
  if (parsed.runs === undefined) {
@@ -1658,7 +1660,7 @@ export async function admitPublicRole(
1658
1660
  caseRunsPath = await realpath(caseRunsPath);
1659
1661
  } catch (error) {
1660
1662
  if (error instanceof CliUsageError) throw error;
1661
- const detail = error instanceof Error ? error.message : String(error);
1663
+ const detail = errorText(error);
1662
1664
  throw new CliUsageError(
1663
1665
  `doctor case could not be constructed from retained evidence: ${detail}`,
1664
1666
  { cause: error },
@@ -2203,8 +2205,8 @@ export async function loadAdmittedJudgeRequest(
2203
2205
  const raw = JSON.parse(
2204
2206
  await readFile(join(runDirectory, "admitted-request.json"), "utf8"),
2205
2207
  ) as unknown;
2206
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return undefined;
2207
- const record = raw as Record<string, unknown>;
2208
+ if (!isRecord(raw)) return undefined;
2209
+ const record = raw;
2208
2210
  if (!packagedPublicInstructionSubject(record.role)) return undefined;
2209
2211
  if (typeof record.instruction !== "string") return undefined;
2210
2212
  if (typeof record.instructionEmpty !== "boolean") return undefined;
@@ -2246,7 +2248,7 @@ export type AdmitFixerInvocationOptions = {
2246
2248
 
2247
2249
  function parsePositivePrOption(raw: string | undefined): number {
2248
2250
  if (raw === undefined || raw.trim() === "") throw new CliUsageError("--pr requires a positive pull request number");
2249
- try { return parseCollectorPrNumber(raw); } catch (error) { throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error }); }
2251
+ try { return parseCollectorPrNumber(raw); } catch (error) { throw new CliUsageError(errorText(error), { cause: error }); }
2250
2252
  }
2251
2253
  function parseRepoOption(raw: string | undefined): string {
2252
2254
  if (raw === undefined || raw.trim() === "") throw new CliUsageError("--repo requires owner/repo");
@@ -2294,7 +2296,7 @@ export function resolveGitHubRemoteRepository(
2294
2296
  try {
2295
2297
  return parseCollectorRepository(ownerRepo);
2296
2298
  } catch (error) {
2297
- const detail = error instanceof Error ? error.message : String(error);
2299
+ const detail = errorText(error);
2298
2300
  throw new CliUsageError(detail, { cause: error });
2299
2301
  }
2300
2302
  }
@@ -2379,7 +2381,7 @@ export async function resolveDoctorCaseRunsPath(options: {
2379
2381
  try {
2380
2382
  real = await realpath(resolved);
2381
2383
  } catch (error) {
2382
- const detail = error instanceof Error ? error.message : String(error);
2384
+ const detail = errorText(error);
2383
2385
  throw new CliUsageError(
2384
2386
  `doctor --runs is not a readable retained runs root: ${detail}`,
2385
2387
  { cause: error },
@@ -2401,7 +2403,6 @@ export async function resolveDoctorCaseRunsPath(options: {
2401
2403
  return real;
2402
2404
  }
2403
2405
 
2404
-
2405
2406
  export type AdmitReviewerInvocationOptions = {
2406
2407
  home: string;
2407
2408
  principalAuthority: DurablePrincipalAuthority;
@@ -122,6 +122,8 @@ import {
122
122
  writeHardenedArtifactFile,
123
123
  } from "./auto-resume.ts";
124
124
 
125
+ import { isRecord, errorText } from "../unknown-value.ts";
126
+
125
127
  function projectRelocatedTurnIdentity(
126
128
  request: RoleTurnRequest,
127
129
  result: { readonly terminal?: TerminalResult },
@@ -137,7 +139,7 @@ function projectRelocatedTurnIdentity(
137
139
  mutableRequest.runDirectory = admitted.runDirectory;
138
140
  mutableRequest.principal = admitted.principal;
139
141
  const activation = mutableRequest.activation;
140
- if (activation !== null && typeof activation === "object" && !Array.isArray(activation)) {
142
+ if (isRecord(activation)) {
141
143
  for (const field of ["taskPath", "packetPath", "prerequisitesPath", "inputPath", "requestManifestPath"] as const) {
142
144
  const record = activation as Record<string, unknown>;
143
145
  if (field in record) record[field] = rewrite(record[field]);
@@ -733,7 +735,7 @@ export async function dispatchPostAdmissionTurn<
733
735
  continue;
734
736
  }
735
737
  const payload = row.accepted;
736
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) continue;
738
+ if (!isRecord(payload)) continue;
737
739
  ticketNumber = readDeclaredTicketNumber(
738
740
  (payload as { ticketNumber?: unknown }).ticketNumber,
739
741
  );
@@ -1802,7 +1804,7 @@ async function presentResumeFailurePointer(
1802
1804
  cause.thrownCause = formatErrorCauseDetail(thrown.cause);
1803
1805
  }
1804
1806
  presentStructuralRejection({
1805
- message: thrown instanceof Error ? thrown.message : String(thrown),
1807
+ message: errorText(thrown),
1806
1808
  cause,
1807
1809
  }, io);
1808
1810
  }