@akagilnc/pi-workflow-roles 0.1.5365 → 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 (170) hide show
  1. package/dist/acp-host/production-host.js +5585 -5655
  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 +1665 -1737
  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 +4 -9
  45. package/dist/public-cli/cli.js +66 -64
  46. package/dist/public-cli/config.js +12 -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 +69 -43
  50. package/dist/public-cli/invocation.js +141 -136
  51. package/dist/public-cli/main.js +1919 -1968
  52. package/dist/public-cli/post-admission.js +89 -89
  53. package/dist/public-cli/role-seat-runtime.js +29 -0
  54. package/dist/public-cli/run-lifecycle.js +20 -32
  55. package/dist/public-cli/settlement.js +10 -26
  56. package/dist/public-cli/turn-request.js +14 -0
  57. package/dist/public-role-summons.js +23 -35
  58. package/dist/receipt-delivery-policy.js +1 -3
  59. package/dist/role-run-placement.js +7 -5
  60. package/dist/role-run-relocation.js +12 -19
  61. package/dist/run-terminal-artifacts.js +1 -11
  62. package/dist/run-ticket-number.js +2 -4
  63. package/dist/session-assistant-usage.js +2 -1
  64. package/dist/session-dialogue.js +1 -14
  65. package/dist/sitian-reader.js +2 -4
  66. package/dist/submission-correctable-error.js +2 -1
  67. package/dist/submission-errors.js +2 -1
  68. package/dist/submission-gate.js +2 -1
  69. package/dist/submission-ledger.js +4 -5
  70. package/dist/ticket-provenance-contracts.js +1 -3
  71. package/dist/ticket-provenance.js +2 -1
  72. package/dist/typed-provider-http.js +4 -7
  73. package/dist/unknown-value.js +28 -0
  74. package/dist/user-dialogue-stdin.js +2 -1
  75. package/package.json +1 -1
  76. package/src/acp-host/role-turn-host.ts +4 -2
  77. package/src/activation-ledger-git.ts +3 -2
  78. package/src/activation-ledger-topology.ts +4 -12
  79. package/src/analyst-entry.ts +3 -11
  80. package/src/analyst-gate-cycles-read.ts +3 -15
  81. package/src/analyst-index.ts +3 -13
  82. package/src/analyst-ledger.ts +2 -16
  83. package/src/analyst-metric-families/acceptance-success-rework.ts +2 -4
  84. package/src/analyst-metric-families/round-timeline.ts +2 -4
  85. package/src/analyst-page.ts +2 -2
  86. package/src/archivist-record-topology.ts +2 -2
  87. package/src/audit-escalation.ts +2 -1
  88. package/src/auditor-soul.ts +3 -1
  89. package/src/book-topology-migration-placement.ts +8 -21
  90. package/src/book-topology-migrators-t11.ts +16 -15
  91. package/src/book-topology-mixed-volume-migrators.ts +3 -6
  92. package/src/book-topology-record-class-migrators.ts +7 -10
  93. package/src/book-topology-runs-migrator.ts +4 -3
  94. package/src/collector-config.ts +7 -9
  95. package/src/compliance-transport.ts +2 -1
  96. package/src/countersign-contracts.ts +2 -1
  97. package/src/diarist-contracts.ts +3 -2
  98. package/src/doctor-contracts.ts +3 -1
  99. package/src/doctor-evidence.ts +8 -7
  100. package/src/doctor-role.ts +4 -2
  101. package/src/dossier-resolution.ts +2 -4
  102. package/src/engine-detour-tool.ts +0 -2
  103. package/src/engine-detour-usage.ts +2 -4
  104. package/src/external-host-turn-loop.ts +3 -1
  105. package/src/factory-board.ts +1 -8
  106. package/src/gatekeeper-role.ts +2 -10
  107. package/src/gleaner-left-contracts.ts +3 -1
  108. package/src/headless-host/description.ts +15 -18
  109. package/src/headless-host/role-turn-host.ts +10 -9
  110. package/src/host-session-record.ts +4 -2
  111. package/src/inspector-contracts.ts +2 -1
  112. package/src/ledger-session-read.ts +2 -5
  113. package/src/merger-contracts.ts +10 -9
  114. package/src/navigator-attendance.ts +10 -12
  115. package/src/navigator-invocation-identity.ts +3 -2
  116. package/src/navigator-public-session.ts +4 -6
  117. package/src/navigator-session-contracts.ts +8 -11
  118. package/src/navigator-work-base.ts +5 -8
  119. package/src/navigator-work-context.ts +2 -1
  120. package/src/notary-contracts.ts +4 -6
  121. package/src/oauth-keepalive.ts +5 -18
  122. package/src/package-contracts/auditor-output.ts +2 -4
  123. package/src/package-contracts/fixer-packet.ts +4 -6
  124. package/src/package-contracts/gatekeeper-output.ts +2 -4
  125. package/src/package-contracts/judge-output.ts +0 -1
  126. package/src/package-contracts/navigator-output.ts +8 -8
  127. package/src/package-contracts/terminating-infrastructure.ts +6 -20
  128. package/src/package-contracts/terminating-tools.ts +8 -12
  129. package/src/pi/adapter.ts +0 -1
  130. package/src/pi/role-turn-host.ts +0 -1
  131. package/src/public-cli/analyst-run.ts +4 -2
  132. package/src/public-cli/auto-resume.ts +5 -12
  133. package/src/public-cli/cli.ts +77 -83
  134. package/src/public-cli/config.ts +16 -27
  135. package/src/public-cli/countersign-run.ts +2 -4
  136. package/src/public-cli/host-providers.ts +10 -27
  137. package/src/public-cli/instruction-seat-run.ts +87 -58
  138. package/src/public-cli/invocation.ts +159 -172
  139. package/src/public-cli/post-admission.ts +115 -103
  140. package/src/public-cli/role-seat-runtime.ts +72 -0
  141. package/src/public-cli/run-lifecycle.ts +27 -67
  142. package/src/public-cli/settlement.ts +11 -33
  143. package/src/public-cli/terminal.ts +2 -17
  144. package/src/public-cli/turn-request.ts +26 -0
  145. package/src/public-role-summons.ts +24 -48
  146. package/src/receipt-delivery-policy.ts +1 -4
  147. package/src/reviewer-role.ts +0 -1
  148. package/src/role-envelope.ts +5 -3
  149. package/src/role-run-placement.ts +9 -10
  150. package/src/role-run-relocation.ts +13 -23
  151. package/src/role-runtime.ts +9 -15
  152. package/src/run-terminal-artifacts.ts +2 -16
  153. package/src/run-ticket-number.ts +4 -10
  154. package/src/session-assistant-usage.ts +3 -1
  155. package/src/session-dialogue.ts +1 -4
  156. package/src/session-identity.ts +0 -1
  157. package/src/sitian-reader.ts +2 -4
  158. package/src/submission-correctable-error.ts +3 -1
  159. package/src/submission-errors.ts +2 -1
  160. package/src/submission-gate.ts +3 -1
  161. package/src/submission-ledger.ts +5 -6
  162. package/src/ticket-provenance-contracts.ts +1 -4
  163. package/src/ticket-provenance.ts +3 -1
  164. package/src/ticket-snapshot.ts +2 -4
  165. package/src/ticket-trajectory.ts +1 -8
  166. package/src/typed-provider-http.ts +5 -15
  167. package/src/unknown-value.ts +34 -0
  168. package/src/user-dialogue-stdin.ts +3 -1
  169. package/src/worker-role.ts +2 -5
  170. package/src/worker-submission-gates.ts +5 -7
@@ -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>();
@@ -210,7 +203,7 @@ function jsonSafeReplacer(): (key: string, value: unknown) => unknown {
210
203
  * + O_NOFOLLOW where the platform provides it (a planted symlink is never
211
204
  * followed) — mirrors settlement.ts's own hardened artifact writers.
212
205
  */
213
- async function writeHardenedArtifactFile(
206
+ export async function writeHardenedArtifactFile(
214
207
  artifactsDir: string,
215
208
  namePrefix: string,
216
209
  payload: Record<string, unknown>,
@@ -4,7 +4,6 @@ import { piDurablePrincipalAuthority } from "../pi/durable-principal.ts";
4
4
  * Public ak-role CLI dispatcher (roles / config / layered help / Judge run).
5
5
  */
6
6
  import { realpath } from "node:fs/promises";
7
- import { join } from "node:path";
8
7
 
9
8
  import { packageMachineHome } from "../activation-ledger-topology.ts";
10
9
  import {
@@ -26,13 +25,13 @@ import {
26
25
  missingResolvedSeatModelMessage,
27
26
  parseModelSpec,
28
27
  resolveEffectiveSeat,
29
- resolvedSeatWithModel,
30
28
  savePublicCliConfig,
31
29
  setAutoResumeLimit,
32
30
  setPersistentSeatConfig,
33
31
  setPersistentSeatEngine,
34
32
  setPersistentSeatEngineModel,
35
33
  setPersistentSeatHost,
34
+ resolvePublicAgentDir,
36
35
  validatePublicCliConfigAxes,
37
36
  type CredentialProviders,
38
37
  type EffectiveSeat,
@@ -41,7 +40,6 @@ import {
41
40
  } from "./config.ts";
42
41
  import {
43
42
  loadHostProvidersTable,
44
- projectHostFacingProvider,
45
43
  renderHostProvidersTable,
46
44
  } from "./host-providers.ts";
47
45
  import { packagedModelParent } from "../packaged-role-registry.ts";
@@ -54,11 +52,10 @@ import {
54
52
  type PostAdmissionEnv,
55
53
  } from "./post-admission.ts";
56
54
  import { appendPiSessionCustomEntry } from "../pi/role-turn-host.ts";
55
+ import { openRoleSeatRuntime } from "./role-seat-runtime.ts";
57
56
  import {
58
- composeRoleTurnHostAdapters,
59
57
  formatHostSelectionFailure,
60
58
  HostSelectionError,
61
- selectRoleTurnHost,
62
59
  type HostSelectionFailure,
63
60
  type NamedRoleTurnHostAdapter,
64
61
  } from "./role-turn-host-resolution.ts";
@@ -108,6 +105,8 @@ import {
108
105
  } from "./settlement.ts";
109
106
  import type { TerminalResult } from "./terminal.ts";
110
107
 
108
+ import { errorText } from "../unknown-value.ts";
109
+
111
110
  export {
112
111
  buildExplicitInternalActivationArgs,
113
112
  resolveInternalRoleEntrypoint,
@@ -240,8 +239,6 @@ export type CliEnv = {
240
239
  signal?: AbortSignal;
241
240
  };
242
241
 
243
-
244
-
245
242
  type RoleEnvironmentOptions = {
246
243
  role: PublicCallableRole;
247
244
  home: string;
@@ -258,54 +255,43 @@ function createRoleEnvironment(
258
255
  /** #178: after host selection, before missing-model — structural argv parse once. */
259
256
  afterHost?: () => void,
260
257
  ) {
261
- const role = options.role;
262
258
  const extraPiArgs = env.extraPiArgs;
263
259
  const timeoutMs = env.timeoutMs;
264
-
265
- // #617/#178/#788/#840: host first, then model; nested child seat selects own host.
266
- const hostAdapters = composeRoleTurnHostAdapters(
267
- {
260
+ const opened = openRoleSeatRuntime({
261
+ resolution: {
268
262
  packageRoot: env.packageRoot,
269
263
  ...(env.roleTurnHost === undefined ? {} : { roleTurnHost: env.roleTurnHost }),
270
264
  ...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
271
265
  ...(extraPiArgs === undefined ? {} : { extraPiArgs }),
272
266
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
273
267
  },
274
- env.principalAuthority!,
275
- );
276
- const roleTurnHost = selectRoleTurnHost(hostAdapters, {
277
- role,
268
+ principalAuthority: env.principalAuthority!,
269
+ role: options.role,
278
270
  seat: options.seat,
271
+ home: options.home,
272
+ ...(afterHost === undefined ? {} : { afterHost }),
279
273
  });
280
- // #178: host → argv (afterHost) → missing-model → provider projection.
281
- afterHost?.();
282
- const seatWithModel = resolvedSeatWithModel(options.seat);
283
- if (seatWithModel === undefined) {
274
+ if (!opened.ok) {
284
275
  // Direct CLI has --model; dual remediation is intentional here (#178 / #916).
285
276
  throw new CliUsageError(
286
277
  missingResolvedSeatModelMessage(options.seat.seat, "invocation-or-config"),
287
278
  );
288
279
  }
289
- const hostFacingSelection = projectHostFacingProvider(
290
- seatWithModel.selection,
291
- seatWithModel.host,
292
- loadHostProvidersTable(options.home),
293
- options.home,
294
- );
295
280
  return {
296
281
  home: options.home,
297
282
  principalAuthority: env.principalAuthority!,
298
283
  agentDir: options.agentDir,
299
284
  sessionAppender: appendPiSessionCustomEntry,
300
285
  packageRoot: env.packageRoot,
301
- roleTurnHost,
302
- hostAdapters,
286
+ roleTurnHost: opened.roleTurnHost,
287
+ hostAdapters: opened.hostAdapters,
303
288
  cwd: options.cwd,
304
289
  ...(options.credentials === undefined ? {} : { credentials: options.credentials }),
305
290
  ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
306
- ...(hostFacingSelection === undefined ? {} : { model: hostFacingSelection }),
307
- ...projectSeatEngine(options.seat),
308
- ...projectSeatHost(options.seat),
291
+ ...(opened.model === undefined ? {} : { model: opened.model }),
292
+ ...(opened.engine === undefined ? {} : { engine: opened.engine }),
293
+ ...(opened.engineModel === undefined ? {} : { engineModel: opened.engineModel }),
294
+ host: opened.host,
309
295
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
310
296
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
311
297
  ...(options.config?.autoResumeLimit === undefined
@@ -319,6 +305,25 @@ function createRoleEnvironment(
319
305
  };
320
306
  }
321
307
 
308
+ type PublicRoleDispatchParts = {
309
+ readonly agentDir: string;
310
+ readonly cwd: string;
311
+ readonly config: PublicCliConfig;
312
+ readonly credentials: CredentialProviders;
313
+ };
314
+
315
+ /** Shared preamble for a new public role command and an explicit resume. */
316
+ async function loadPublicRoleDispatchParts(
317
+ env: CliEnv,
318
+ home: string,
319
+ ): Promise<PublicRoleDispatchParts> {
320
+ const agentDir = resolvePublicAgentDir(env.agentDir, home);
321
+ const cwd = env.cwd ?? process.cwd();
322
+ const config = await loadAndValidateConfig(home, env.packageRoot);
323
+ const credentials = env.credentials ?? (await loadCredentialProviders(agentDir));
324
+ return { agentDir, cwd, config, credentials };
325
+ }
326
+
322
327
  /**
323
328
  * #178 single spine: resolve seat → host select → argv parse once → missing-model → run.
324
329
  * Keeps host-unregistered ahead of bad argv and missing-model.
@@ -336,15 +341,11 @@ async function dispatchPublicRoleCommand<TParsed>(
336
341
  parseOnce: () => TParsed,
337
342
  ) => Promise<{ exitCode: number; terminal?: TerminalResult }>,
338
343
  ): Promise<CliResult> {
339
- const agentDir = resolveAgentDir(env, home);
340
- const cwd = env.cwd ?? process.cwd();
341
- const config = await loadAndValidateConfig(home, env.packageRoot);
342
- const credentials =
343
- env.credentials ?? (await loadCredentialProviders(agentDir));
344
+ const parts = await loadPublicRoleDispatchParts(env, home);
344
345
  const seat = resolveEffectiveSeat(
345
- config,
346
+ parts.config,
346
347
  role,
347
- credentials,
348
+ parts.credentials,
348
349
  invocationFromParsed(parsed),
349
350
  );
350
351
  let parsedRoleArgv!: TParsed;
@@ -352,7 +353,15 @@ async function dispatchPublicRoleCommand<TParsed>(
352
353
  parsed.args,
353
354
  createRoleEnvironment(
354
355
  env,
355
- { role, home, agentDir, cwd, credentials, seat, config },
356
+ {
357
+ role,
358
+ home,
359
+ agentDir: parts.agentDir,
360
+ cwd: parts.cwd,
361
+ credentials: parts.credentials,
362
+ seat,
363
+ config: parts.config,
364
+ },
356
365
  () => {
357
366
  parsedRoleArgv = parse(parsed.args);
358
367
  },
@@ -397,14 +406,6 @@ function resolveHome(env: CliEnv): string {
397
406
  return env.home ?? packageMachineHome();
398
407
  }
399
408
 
400
- function resolveAgentDir(env: CliEnv, home: string): string {
401
- return (
402
- env.agentDir ??
403
- process.env.PI_CODING_AGENT_DIR ??
404
- join(home, ".pi", "agent")
405
- );
406
- }
407
-
408
409
  type ParsedGlobal = {
409
410
  command?: string;
410
411
  args: string[];
@@ -541,7 +542,7 @@ function requireLegalEngineModel(model: string): string {
541
542
  return assertLegalEngineModel(model);
542
543
  } catch (error) {
543
544
  throw new CliUsageError(
544
- error instanceof Error ? error.message : String(error),
545
+ errorText(error),
545
546
  { cause: error },
546
547
  );
547
548
  }
@@ -552,7 +553,7 @@ function requireLegalEngineName(name: string): string {
552
553
  return assertLegalEngineName(name);
553
554
  } catch (error) {
554
555
  throw new CliUsageError(
555
- error instanceof Error ? error.message : String(error),
556
+ errorText(error),
556
557
  { cause: error },
557
558
  );
558
559
  }
@@ -575,20 +576,6 @@ function requireCallableSeat(
575
576
  }
576
577
  }
577
578
 
578
- /** Single seat engine axis → run-options projection (#391 E2 / #883). */
579
- function projectSeatEngine(
580
- seat: Readonly<{ engine?: string; engineModel?: string }>,
581
- ): { engine?: string; engineModel?: string } {
582
- return pickEngineAxis(seat);
583
- }
584
-
585
- /** Single seat.host → run-options projection (#595 / #617). */
586
- function projectSeatHost(
587
- seat: Readonly<{ host: string }>,
588
- ): { host: string } {
589
- return { host: seat.host };
590
- }
591
-
592
579
  function loadAndValidateConfig(
593
580
  home: string,
594
581
  packageRoot: string,
@@ -598,7 +585,7 @@ function loadAndValidateConfig(
598
585
  validatePublicCliConfigAxes(config, packageRoot);
599
586
  } catch (error) {
600
587
  throw new CliUsageError(
601
- error instanceof Error ? error.message : String(error),
588
+ errorText(error),
602
589
  { cause: error },
603
590
  );
604
591
  }
@@ -930,7 +917,7 @@ async function runConfigCommand(
930
917
  try {
931
918
  config = setPersistentSeatHost(config, seat, unset ? undefined : args[2]!);
932
919
  } catch (error) {
933
- throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
920
+ throw new CliUsageError(errorText(error), { cause: error });
934
921
  }
935
922
  await savePublicCliConfig(config, home);
936
923
  io.stdout(renderConfig(config, home));
@@ -954,7 +941,7 @@ async function runConfigCommand(
954
941
  config = setPersistentSeatEngine(config, seat, name, engineModel);
955
942
  } catch (error) {
956
943
  throw new CliUsageError(
957
- error instanceof Error ? error.message : String(error),
944
+ errorText(error),
958
945
  { cause: error },
959
946
  );
960
947
  }
@@ -976,7 +963,7 @@ async function runConfigCommand(
976
963
  config = setPersistentSeatEngine(config, seat, undefined);
977
964
  } catch (error) {
978
965
  throw new CliUsageError(
979
- error instanceof Error ? error.message : String(error),
966
+ errorText(error),
980
967
  { cause: error },
981
968
  );
982
969
  }
@@ -1000,7 +987,7 @@ async function runConfigCommand(
1000
987
  config = setPersistentSeatEngineModel(config, seat, model);
1001
988
  } catch (error) {
1002
989
  throw new CliUsageError(
1003
- error instanceof Error ? error.message : String(error),
990
+ errorText(error),
1004
991
  { cause: error },
1005
992
  );
1006
993
  }
@@ -1022,7 +1009,7 @@ async function runConfigCommand(
1022
1009
  config = setPersistentSeatEngineModel(config, seat, undefined);
1023
1010
  } catch (error) {
1024
1011
  throw new CliUsageError(
1025
- error instanceof Error ? error.message : String(error),
1012
+ errorText(error),
1026
1013
  { cause: error },
1027
1014
  );
1028
1015
  }
@@ -1170,7 +1157,7 @@ export async function runAkRole(
1170
1157
  const config = await loadAndValidateConfig(home, env.packageRoot);
1171
1158
  const credentials =
1172
1159
  env.credentials ??
1173
- (await loadCredentialProviders(resolveAgentDir(env, home)));
1160
+ (await loadCredentialProviders(resolvePublicAgentDir(env.agentDir, home)));
1174
1161
  const seats = effectiveSeatConfigurations(
1175
1162
  config,
1176
1163
  credentials,
@@ -1219,19 +1206,15 @@ export async function runAkRole(
1219
1206
  runId: resumeRequest.runId,
1220
1207
  authority: env.principalAuthority ?? piDurablePrincipalAuthority,
1221
1208
  };
1222
- const agentDir = resolveAgentDir(env, home);
1223
- const cwd = env.cwd ?? process.cwd();
1224
- const config = await loadAndValidateConfig(home, env.packageRoot);
1225
- const credentials =
1226
- env.credentials ?? (await loadCredentialProviders(agentDir));
1209
+ const parts = await loadPublicRoleDispatchParts(env, home);
1227
1210
  const resumeRole = await peekRoleRunRole(home, resumeRequest.runId);
1228
1211
  // Missing durable role keeps the judge seat table. The resume entry
1229
1212
  // itself is one function; it reads the stored run.
1230
1213
  const seatRole = resumeRole ?? "judge";
1231
1214
  const seat = resolveEffectiveSeat(
1232
- config,
1215
+ parts.config,
1233
1216
  seatRole,
1234
- credentials,
1217
+ parts.credentials,
1235
1218
  invocationFromParsed(parsed),
1236
1219
  );
1237
1220
  const result = await runPublicInstructionSeatResume(
@@ -1239,11 +1222,11 @@ export async function runAkRole(
1239
1222
  createRoleEnvironment(env, {
1240
1223
  role: seatRole,
1241
1224
  home,
1242
- agentDir,
1243
- cwd,
1244
- credentials,
1225
+ agentDir: parts.agentDir,
1226
+ cwd: parts.cwd,
1227
+ credentials: parts.credentials,
1245
1228
  seat,
1246
- config,
1229
+ config: parts.config,
1247
1230
  }),
1248
1231
  io,
1249
1232
  );
@@ -1261,9 +1244,20 @@ export async function runAkRole(
1261
1244
  };
1262
1245
  const parentRole = await peekRoleRunRole(home, parentRunId);
1263
1246
  if (parentRole === undefined) break;
1264
- const parentSeat = resolveEffectiveSeat(config, parentRole, credentials, invocationFromParsed(parsed));
1247
+ const parentSeat = resolveEffectiveSeat(
1248
+ parts.config,
1249
+ parentRole,
1250
+ parts.credentials,
1251
+ invocationFromParsed(parsed),
1252
+ );
1265
1253
  const parentEnv = createRoleEnvironment(env, {
1266
- role: parentRole, home, agentDir, cwd, credentials, seat: parentSeat, config,
1254
+ role: parentRole,
1255
+ home,
1256
+ agentDir: parts.agentDir,
1257
+ cwd: parts.cwd,
1258
+ credentials: parts.credentials,
1259
+ seat: parentSeat,
1260
+ config: parts.config,
1267
1261
  });
1268
1262
  current = await continueParentAfterChild(parentRunId, current.admitted, parentEnv, io);
1269
1263
  }
@@ -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>;
@@ -835,6 +822,14 @@ export function credentialProvidersFromAuthData(
835
822
  };
836
823
  }
837
824
 
825
+ /**
826
+ * Public entry agent directory: explicit value, else PI_CODING_AGENT_DIR,
827
+ * else `<home>/.pi/agent`. CLI and nested summons share this rule.
828
+ */
829
+ export function resolvePublicAgentDir(explicit: string | undefined, home: string): string {
830
+ return explicit ?? process.env.PI_CODING_AGENT_DIR ?? join(home, ".pi", "agent");
831
+ }
832
+
838
833
  export async function loadCredentialProviders(
839
834
  agentDir: string,
840
835
  ): Promise<CredentialProviders> {
@@ -842,13 +837,7 @@ export async function loadCredentialProviders(
842
837
  const raw = await readFile(join(agentDir, "auth.json"), "utf8");
843
838
  return credentialProvidersFromAuthData(JSON.parse(raw));
844
839
  } catch (error) {
845
- if (
846
- error instanceof Error &&
847
- "code" in error &&
848
- (error as NodeJS.ErrnoException).code === "ENOENT"
849
- ) {
850
- return { "openai-codex": false, xai: false };
851
- }
840
+ if (isEnoent(error)) return { "openai-codex": false, xai: false };
852
841
  throw error;
853
842
  }
854
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;