@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
@@ -346,6 +346,8 @@ import {
346
346
  type TerminalRoleOutcome,
347
347
  } from "./terminal.ts";
348
348
 
349
+ import { isEnoent, isMissingPathError, isRecord } from "../unknown-value.ts";
350
+
349
351
  export type { ControlledFailureCause };
350
352
 
351
353
  export {
@@ -467,7 +469,7 @@ export async function inspectJudgeSession(
467
469
  await readFile(sessionFile, "utf8");
468
470
  return { state: "present" };
469
471
  } catch (error) {
470
- if (isMissingPathError(error)) return { state: "missing" };
472
+ if (isEnoent(error)) return { state: "missing" };
471
473
  return {
472
474
  state: "unreadable",
473
475
  diagnostic:
@@ -834,24 +836,6 @@ type SessionEntry = {
834
836
  parentSession?: string;
835
837
  };
836
838
 
837
- function isMissingPathError(error: unknown): boolean {
838
- return (
839
- error instanceof Error &&
840
- "code" in error &&
841
- (error as { code?: unknown }).code === "ENOENT"
842
- );
843
- }
844
-
845
- /** Face clear only: path not enterable as a face (ENOENT or file mid-path). */
846
- function isAbsentFacePathError(error: unknown): boolean {
847
- return (
848
- error instanceof Error &&
849
- "code" in error &&
850
- ((error as { code?: unknown }).code === "ENOENT" ||
851
- (error as { code?: unknown }).code === "ENOTDIR")
852
- );
853
- }
854
-
855
839
  /**
856
840
  * Preserve session-read failure identity as a typed session cause.
857
841
  * SyntaxError keeps its name so durable settlement does not wash malformed JSONL
@@ -1064,7 +1048,7 @@ async function loadBoundAuditorVolumes(
1064
1048
  try {
1065
1049
  parentEntries = await readBoundSessionEntries(sessionFile);
1066
1050
  } catch (error) {
1067
- if (isMissingPathError(error)) return undefined;
1051
+ if (isEnoent(error)) return undefined;
1068
1052
  throw sessionReadFailure(error, "failed to read parent session for auditor binding");
1069
1053
  }
1070
1054
  const parentId = parentEntries.find((entry) => entry.type === "session")?.id;
@@ -1078,7 +1062,7 @@ async function loadBoundAuditorVolumes(
1078
1062
  names = await readdir(childDirectory);
1079
1063
  sawAnyDirectory = true;
1080
1064
  } catch (error) {
1081
- if (isMissingPathError(error)) continue;
1065
+ if (isEnoent(error)) continue;
1082
1066
  throw sessionReadFailure(error, "failed to read bound auditor session directory");
1083
1067
  }
1084
1068
  for (const file of names.filter((name) => name.endsWith(".jsonl")).sort().reverse()) {
@@ -1313,7 +1297,7 @@ export async function resolveAuditedRunnerFailureResolution(input: {
1313
1297
  );
1314
1298
  if (terminatingFailure !== undefined) return resolutionOf(terminatingFailure);
1315
1299
  } catch (error) {
1316
- if (!isMissingPathError(error)) {
1300
+ if (!isEnoent(error)) {
1317
1301
  const failure = sessionReadFailure(error, "failed to recover typed terminating-tool failure");
1318
1302
  return resolutionOf({
1319
1303
  cause: "session",
@@ -1459,10 +1443,6 @@ export function controlledFailureInputFromResolution(
1459
1443
  };
1460
1444
  }
1461
1445
 
1462
- function isRecord(value: unknown): value is Record<string, unknown> {
1463
- return typeof value === "object" && value !== null && !Array.isArray(value);
1464
- }
1465
-
1466
1446
  function safelyRead(object: object, key: string): { readable: true; value: unknown } | { readable: false } {
1467
1447
  try {
1468
1448
  return { readable: true, value: (object as Record<string, unknown>)[key] };
@@ -1478,9 +1458,7 @@ function toolResultText(message: SessionMessage): string {
1478
1458
  return content
1479
1459
  .map((part) => {
1480
1460
  if (
1481
- typeof part === "object" &&
1482
- part !== null &&
1483
- !Array.isArray(part) &&
1461
+ isRecord(part) &&
1484
1462
  typeof (part as { text?: unknown }).text === "string"
1485
1463
  ) {
1486
1464
  return (part as { text: string }).text;
@@ -2234,7 +2212,7 @@ async function extractNavigatorFactFromAdmittedSession(
2234
2212
  const entries = await readBoundSessionEntries(sessionFile);
2235
2213
  return extractNavigatorFact(entries);
2236
2214
  } catch (error) {
2237
- if (isMissingPathError(error)) {
2215
+ if (isEnoent(error)) {
2238
2216
  return {
2239
2217
  disposition: "unavailable",
2240
2218
  source: "unknown",
@@ -2261,7 +2239,7 @@ async function removeFaceIfPresent(path: string): Promise<void> {
2261
2239
  // force:true already ignores ENOENT; ENOTDIR = face path not enterable
2262
2240
  // (artifacts-as-file mid-path) — same absent-face semantics as the reader.
2263
2241
  // Other errno stay loud (失败诚实).
2264
- if (isAbsentFacePathError(error)) return;
2242
+ if (isMissingPathError(error)) return;
2265
2243
  throw error;
2266
2244
  }
2267
2245
  }
@@ -2392,7 +2370,7 @@ async function readLawfulSettlementEntries(
2392
2370
  return await readBoundSessionEntries(sessionFile);
2393
2371
  } catch (error) {
2394
2372
  // Missing path is absence of a lawful outcome; callers classify via session inspect.
2395
- if (isMissingPathError(error)) return undefined;
2373
+ if (isEnoent(error)) return undefined;
2396
2374
  // Malformed JSONL and other read failures keep typed session identity.
2397
2375
  throw error instanceof Error &&
2398
2376
  (error as { knownCause?: unknown }).knownCause === "session"
@@ -2839,7 +2817,7 @@ function countersignTerminalFromEntries(
2839
2817
  if (entry?.type !== "custom") continue;
2840
2818
  if (entry.customType !== SECRETARIAT_GATE_OFFICER_ENTRY_TYPE) continue;
2841
2819
  const data =
2842
- entry.data !== null && typeof entry.data === "object" && !Array.isArray(entry.data)
2820
+ isRecord(entry.data)
2843
2821
  ? (entry.data as Record<string, unknown>)
2844
2822
  : undefined;
2845
2823
  if (data === undefined || !packagedDurableOfficerEntry(data.officer)) continue;
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { NoReceiptLifecycleFacts } from "../receipt-delivery-policy.ts";
9
9
  import type { ControlledFailureCause } from "../host-contracts.ts";
10
+ import type { PackagedRole } from "../packaged-role-registry.ts";
10
11
 
11
12
  export type { ControlledFailureCause } from "../host-contracts.ts";
12
13
 
@@ -22,23 +23,7 @@ export type TerminalArtifactRef = {
22
23
  };
23
24
 
24
25
  /** Public callable roles that currently produce Terminal outcomes. */
25
- export type TerminalRoleName =
26
- | "judge"
27
- | "coder"
28
- | "fixer"
29
- | "collector"
30
- | "doctor"
31
- | "reviewer"
32
- | "merger"
33
- | "notary"
34
- | "countersign"
35
- | "gleaner-left"
36
- | "inspector"
37
- | "gatekeeper"
38
- | "navigator"
39
- | "auditor"
40
- | "diarist"
41
- | "secretariat";
26
+ export type TerminalRoleName = PackagedRole;
42
27
 
43
28
  export type NoReceiptTerminalOutcome = NoReceiptLifecycleFacts & {
44
29
  kind: "no_receipt";
@@ -27,6 +27,32 @@ export type ResumeModelConfig = {
27
27
  readonly thinking?: PublicThinkingLevel;
28
28
  };
29
29
 
30
+ /**
31
+ * Env axes shared by every public turn projection.
32
+ * Callers add correlation, continuation, host, and station-only fields.
33
+ */
34
+ export function projectPublicTurnAxes(env: {
35
+ readonly packageRoot: string;
36
+ readonly home: string;
37
+ readonly agentDir: string;
38
+ readonly model?: SeatModelConfig;
39
+ readonly engine?: string;
40
+ readonly engineModel?: string;
41
+ readonly timeoutMs?: number;
42
+ }): Pick<
43
+ RoleTurnRequestProjectionOptions,
44
+ "packageRoot" | "home" | "agentDir" | "model" | "engine" | "engineModel" | "timeoutMs"
45
+ > {
46
+ return {
47
+ packageRoot: env.packageRoot,
48
+ home: env.home,
49
+ agentDir: env.agentDir,
50
+ ...(env.model === undefined ? {} : { model: env.model }),
51
+ ...pickEngineAxis(env),
52
+ ...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
53
+ };
54
+ }
55
+
30
56
  export type RoleTurnRequestProjectionOptions = {
31
57
  packageRoot: string;
32
58
  home: string;
@@ -25,12 +25,12 @@ import type {
25
25
  } from "./host-contracts.ts";
26
26
  import type { TerminalResult } from "./public-cli/terminal.ts";
27
27
  import type { HostSelectionFailure, NamedRoleTurnHostAdapter } from "./public-cli/role-turn-host-resolution.ts";
28
- import { pickEngineAxis } from "./package-resources/engine-material.ts";
28
+
29
+ import { isRecord, errorText } from "./unknown-value.ts";
29
30
 
30
31
  /** Env published by the parent activation so nested summons never re-derive root. */
31
32
  export const AK_ROLE_PACKAGE_ROOT_ENV = "AK_ROLE_PACKAGE_ROOT" as const;
32
33
 
33
-
34
34
  export type PublicSummonRole =
35
35
  | "inspector"
36
36
  | "notary"
@@ -210,18 +210,6 @@ async function resolveSummonHome(options: PublicSummonRequest): Promise<string>
210
210
  return packageMachineHome();
211
211
  }
212
212
 
213
- /** Seat axes only — no parent-env fallback (#675 / #617 DK-3 / #883). */
214
- function projectSeatEngine(seat: EffectiveSeat): {
215
- engine?: string;
216
- engineModel?: string;
217
- } {
218
- return pickEngineAxis(seat);
219
- }
220
-
221
- function projectSeatHost(seat: EffectiveSeat): { host?: string } {
222
- return seat.host === undefined ? {} : { host: seat.host };
223
- }
224
-
225
213
  function hostSelectionFailureFromUnknown(error: unknown): HostSelectionFailure | undefined {
226
214
  if (typeof error !== "object" || error === null) return undefined;
227
215
  if ((error as { name?: unknown }).name !== "HostSelectionError") return undefined;
@@ -273,18 +261,17 @@ async function createSummonEnv(
273
261
  const [
274
262
  { piDurablePrincipalAuthority },
275
263
  { appendPiSessionCustomEntry },
276
- { resolveRoleTurnHost },
277
- { missingResolvedSeatModelMessage, resolvedSeatWithModel },
264
+ { openRoleSeatRuntime },
265
+ { missingResolvedSeatModelMessage },
278
266
  ] = await Promise.all([
279
267
  import("./pi/durable-principal.ts"),
280
268
  import("./pi/role-turn-host.ts"),
281
- import("./public-cli/role-turn-host-resolution.ts"),
269
+ import("./public-cli/role-seat-runtime.ts"),
282
270
  import("./public-cli/config.ts"),
283
271
  ]);
284
272
  const principalAuthority = options.principalAuthority ?? piDurablePrincipalAuthority;
285
- // Host first → argv (afterHost) → missing-model → provider projection (#617/#178/#840).
286
- const roleTurnHost = resolveRoleTurnHost(
287
- {
273
+ const opened = openRoleSeatRuntime({
274
+ resolution: {
288
275
  packageRoot: options.packageRoot,
289
276
  ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
290
277
  ...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
@@ -293,29 +280,19 @@ async function createSummonEnv(
293
280
  : { extraPiArgs: options.extraPiArgs }),
294
281
  ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
295
282
  },
296
- { role: options.role, seat: options.seat, principalAuthority },
297
- );
298
- afterHost?.();
299
- const seatWithModel = resolvedSeatWithModel(options.seat);
300
- if (seatWithModel === undefined) {
283
+ principalAuthority,
284
+ role: options.role,
285
+ seat: options.seat,
286
+ home: options.home,
287
+ ...(afterHost === undefined ? {} : { afterHost }),
288
+ ...(options.hostFacingModel === undefined ? {} : { hostFacingModel: options.hostFacingModel }),
289
+ });
290
+ if (!opened.ok) {
301
291
  return {
302
292
  ok: false,
303
293
  missingModelMessage: missingResolvedSeatModelMessage(options.role),
304
294
  };
305
295
  }
306
- const hostName = seatWithModel.host ?? "pi";
307
- let hostFacingSelection: RoleTurnModelConfig | undefined = options.hostFacingModel;
308
- if (hostFacingSelection === undefined) {
309
- const { loadHostProvidersTable, projectHostFacingProvider } = await import(
310
- "./public-cli/host-providers.ts"
311
- );
312
- hostFacingSelection = projectHostFacingProvider(
313
- seatWithModel.selection,
314
- hostName,
315
- loadHostProvidersTable(options.home),
316
- options.home,
317
- );
318
- }
319
296
  return {
320
297
  ok: true,
321
298
  env: {
@@ -324,12 +301,13 @@ async function createSummonEnv(
324
301
  agentDir: options.agentDir,
325
302
  sessionAppender: appendPiSessionCustomEntry,
326
303
  packageRoot: options.packageRoot,
327
- roleTurnHost,
304
+ roleTurnHost: opened.roleTurnHost,
328
305
  cwd: options.cwd,
329
306
  credentials: options.credentials,
330
- ...(hostFacingSelection === undefined ? {} : { model: hostFacingSelection }),
331
- ...projectSeatEngine(seatWithModel),
332
- ...projectSeatHost(seatWithModel),
307
+ ...(opened.model === undefined ? {} : { model: opened.model }),
308
+ ...(opened.engine === undefined ? {} : { engine: opened.engine }),
309
+ ...(opened.engineModel === undefined ? {} : { engineModel: opened.engineModel }),
310
+ host: opened.host,
333
311
  ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
334
312
  },
335
313
  };
@@ -344,16 +322,14 @@ export async function summonPublicRole(
344
322
  ): Promise<PublicSummonResult> {
345
323
  const packageRoot = resolveSummonsPackageRoot(options.packageRoot);
346
324
  const home = await resolveSummonHome(options);
347
- const agentDir =
348
- options.agentDir
349
- ?? process.env.PI_CODING_AGENT_DIR
350
- ?? join(home, ".pi", "agent");
351
325
  const {
352
326
  loadCredentialProviders,
353
327
  loadPublicCliConfig,
354
328
  resolveEffectiveSeat,
329
+ resolvePublicAgentDir,
355
330
  validatePublicCliConfigAxes,
356
331
  } = await import("./public-cli/config.ts");
332
+ const agentDir = resolvePublicAgentDir(options.agentDir, home);
357
333
  const credentials =
358
334
  options.credentials ?? (await loadCredentialProviders(agentDir));
359
335
  const config = await loadPublicCliConfig(home);
@@ -616,7 +592,7 @@ export async function summonParallelReviewerLenses(options: {
616
592
  if (error instanceof AggregateError) {
617
593
  return [error.message, ...error.errors.map(describeFailure)].join("\n");
618
594
  }
619
- return error instanceof Error ? error.message : String(error);
595
+ return errorText(error);
620
596
  };
621
597
  const failedResult = (error: unknown): PublicSummonResult => ({
622
598
  exitCode: 1,
@@ -864,7 +840,7 @@ export async function summonGateOfficer(options: {
864
840
  const correlationId = parentRunId;
865
841
  const { isSafePositiveTicketNumber, readBoardTicketNumber } = await import("./run-ticket-number.ts");
866
842
  const parentTicket = await readBoardTicketNumber(options.sourceRunDirectory);
867
- const submittedTicket = options.submission !== null && typeof options.submission === "object" && !Array.isArray(options.submission)
843
+ const submittedTicket = isRecord(options.submission)
868
844
  ? (options.submission as { ticketNumber?: unknown }).ticketNumber
869
845
  : undefined;
870
846
  const courtTicket = isSafePositiveTicketNumber(submittedTicket) ? submittedTicket : parentTicket;
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "./unknown-value.ts";
1
2
  /** Shared accepted-receipt delivery budget for role, auditor, and Navigator sessions (#288). */
2
3
  export const RECEIPT_DELIVERY_TURN_LIMIT = 2 as const;
3
4
 
@@ -14,10 +15,6 @@ export type NoReceiptLifecycleFacts = {
14
15
  acceptedReceipt: false;
15
16
  };
16
17
 
17
- function isRecord(value: unknown): value is Record<string, unknown> {
18
- return typeof value === "object" && value !== null && !Array.isArray(value);
19
- }
20
-
21
18
  /** Read only the facts required by Terminal consumers; persisted extensions are ignored. */
22
19
  export function parseNoReceiptLifecycleFacts(input: unknown): NoReceiptLifecycleFacts {
23
20
  if (!isRecord(input)
@@ -41,7 +41,6 @@ export type ReviewerRoleDependencies = {
41
41
  };
42
42
  export type ReviewerRoleHostActions = { failInfrastructure(error: unknown, ctx: HostContext, toolCallId?: string): never };
43
43
 
44
-
45
44
  export type ReviewerActivation = Readonly<{
46
45
  fixedBaseRevision: string;
47
46
  soul: string;
@@ -35,6 +35,8 @@ import {
35
35
  } from "./navigator-invocation-identity.ts";
36
36
  import { serializeThrownValue } from "./serialize-thrown-value.ts";
37
37
 
38
+ import { isRecord, errorText } from "./unknown-value.ts";
39
+
38
40
  export { projectActivationFlags };
39
41
 
40
42
  type Handler = HostEventRegistration[1];
@@ -158,7 +160,7 @@ export async function prepareRoleEnvelope(options: {
158
160
  try {
159
161
  await appendFile(sessionFile, line, "utf8");
160
162
  } catch (error) {
161
- const diagnostic = error instanceof Error ? error.message : String(error);
163
+ const diagnostic = errorText(error);
162
164
  armDurableWriteFailure?.(customType, diagnostic);
163
165
  }
164
166
  });
@@ -291,7 +293,7 @@ export async function prepareRoleEnvelope(options: {
291
293
  content: ContentPart[],
292
294
  ): void {
293
295
  if (infrastructureRoundFailure !== undefined) return;
294
- const record = typeof details === "object" && details !== null && !Array.isArray(details)
296
+ const record = isRecord(details)
295
297
  ? details as Record<string, unknown>
296
298
  : undefined;
297
299
  const isInfra = record !== undefined && (
@@ -336,7 +338,7 @@ export async function prepareRoleEnvelope(options: {
336
338
  content: ContentPart[];
337
339
  details: Record<string, unknown>;
338
340
  } {
339
- const diagnostic = error instanceof Error ? error.message : String(error);
341
+ const diagnostic = errorText(error);
340
342
  const content: ContentPart[] = [{ type: "text", text: diagnostic }];
341
343
  const errorCode = typeof (error as unknown as { code?: unknown })?.code === "string"
342
344
  ? (error as unknown as { code: string }).code
@@ -7,6 +7,8 @@ import {
7
7
  } from "./activation-ledger-topology.ts";
8
8
  import { requireSafePositiveTicketNumber } from "./run-ticket-number.ts";
9
9
 
10
+ import { isEnoent } from "./unknown-value.ts";
11
+
10
12
  export type RoleRunSubject =
11
13
  | { readonly ticketNumber: number }
12
14
  | { readonly unbound: true };
@@ -19,14 +21,6 @@ export type RoleRunPlacement = {
19
21
  readonly attachmentsDirectory: string;
20
22
  };
21
23
 
22
- function isMissingPathError(error: unknown): boolean {
23
- return (
24
- error instanceof Error &&
25
- "code" in error &&
26
- (error as NodeJS.ErrnoException).code === "ENOENT"
27
- );
28
- }
29
-
30
24
  /**
31
25
  * Sole book-level run directory walk: flat legacy `runs/` plus each
32
26
  * `subject/runs/` child (ticket / unbound). One authority for read-side
@@ -42,7 +36,7 @@ export async function listBookRunDirectories(bookDir: string): Promise<string[]>
42
36
  try {
43
37
  entries = await readdir(runsDir, { withFileTypes: true });
44
38
  } catch (error) {
45
- if (isMissingPathError(error)) return;
39
+ if (isEnoent(error)) return;
46
40
  throw error;
47
41
  }
48
42
  for (const entry of entries) {
@@ -60,7 +54,7 @@ export async function listBookRunDirectories(bookDir: string): Promise<string[]>
60
54
  try {
61
55
  subjects = await readdir(bookDir, { withFileTypes: true });
62
56
  } catch (error) {
63
- if (isMissingPathError(error)) return out.sort();
57
+ if (isEnoent(error)) return out.sort();
64
58
  throw error;
65
59
  }
66
60
  for (const subject of subjects) {
@@ -107,6 +101,11 @@ export function roleRunArtifactsDirectory(runDirectory: string): string {
107
101
  return join(runDirectory, "artifacts");
108
102
  }
109
103
 
104
+ /** Canonical unbound placement: a run directory under `<book>/unbound/runs/`. */
105
+ export function isUnboundRunDirectory(runDirectory: string): boolean {
106
+ return runDirectory.replaceAll("\\", "/").includes("/unbound/runs/");
107
+ }
108
+
110
109
  /** The placement seam owns creation for both new runs and resumed legacy runs. */
111
110
  export function ensureRoleRunDirectory(
112
111
  ledgerHome: string,
@@ -12,6 +12,8 @@ import { join, sep } from "node:path";
12
12
 
13
13
  import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE } from "./compliance-transport.ts";
14
14
 
15
+ import { isRecord, isEnoent } from "./unknown-value.ts";
16
+
15
17
  const ADMITTED_PAGE_FIELDS = [
16
18
  "runDirectory",
17
19
  "admittedRequestPath",
@@ -50,14 +52,6 @@ export type RunDirectoryPathRewrite = {
50
52
  readonly newRunDirectory: string;
51
53
  };
52
54
 
53
- function isEnoent(error: unknown): boolean {
54
- return (
55
- error instanceof Error &&
56
- "code" in error &&
57
- (error as NodeJS.ErrnoException).code === "ENOENT"
58
- );
59
- }
60
-
61
55
  /** Rewrite one path value that points at or under oldRunDirectory. */
62
56
  export function rewriteRunDirectoryPathValue(
63
57
  value: unknown,
@@ -158,16 +152,12 @@ function collectRewrites(input: {
158
152
  return out;
159
153
  }
160
154
 
161
- function isPlainObject(value: unknown): value is Record<string, unknown> {
162
- return value !== null && typeof value === "object" && !Array.isArray(value);
163
- }
164
-
165
155
  /** Typed notary/source-run locator: only runDirectory is a path. */
166
156
  function rewriteSourceRunLocator(
167
157
  value: unknown,
168
158
  rewrites: readonly RunDirectoryPathRewrite[],
169
159
  ): void {
170
- if (!isPlainObject(value)) return;
160
+ if (!isRecord(value)) return;
171
161
  rewriteRunDirectoryPathFieldsAgainstRewrites(
172
162
  value,
173
163
  SOURCE_RUN_LOCATOR_FIELDS,
@@ -180,7 +170,7 @@ function rewriteSummonsMaterials(
180
170
  value: unknown,
181
171
  rewrites: readonly RunDirectoryPathRewrite[],
182
172
  ): void {
183
- if (!isPlainObject(value)) return;
173
+ if (!isRecord(value)) return;
184
174
  rewriteRunDirectoryPathFieldsAgainstRewrites(
185
175
  value,
186
176
  SUMMONS_PATH_FIELDS,
@@ -204,12 +194,12 @@ export function rewriteAdmittedRoleRunPage(
204
194
  rewriteSourceRunLocator(page.sourceRun, rewrites);
205
195
  if (Array.isArray(page.attachments)) {
206
196
  for (const attachment of page.attachments) {
207
- if (isPlainObject(attachment)) {
197
+ if (isRecord(attachment)) {
208
198
  rewriteRunDirectoryPathFieldsAgainstRewrites(attachment, ["frozenPath"], rewrites);
209
199
  }
210
200
  }
211
201
  }
212
- if (isPlainObject(page.principal)) {
202
+ if (isRecord(page.principal)) {
213
203
  rewriteRunDirectoryPathFieldsAgainstRewrites(
214
204
  page.principal,
215
205
  ["sessionDirectory", "sessionFile"],
@@ -224,7 +214,7 @@ async function rewriteOfficerPointerFile(
224
214
  ): Promise<void> {
225
215
  if (!existsSync(path)) return;
226
216
  const page = JSON.parse(await readFile(path, "utf8")) as unknown;
227
- if (!isPlainObject(page)) return;
217
+ if (!isRecord(page)) return;
228
218
  // Only the typed direct-officer pointer shape — never arbitrary .pointer.json.
229
219
  if (page.kind !== "direct-officer-run-pointer") return;
230
220
  const before = JSON.stringify(page);
@@ -267,7 +257,7 @@ async function rewriteSitianRecordsJsonl(
267
257
  out.push(line);
268
258
  continue;
269
259
  }
270
- if (!isPlainObject(parsed)) {
260
+ if (!isRecord(parsed)) {
271
261
  out.push(line);
272
262
  continue;
273
263
  }
@@ -325,7 +315,7 @@ async function rewriteSessionTranscriptBindings(
325
315
  out.push(line);
326
316
  continue;
327
317
  }
328
- if (!isPlainObject(parsed)) {
318
+ if (!isRecord(parsed)) {
329
319
  out.push(line);
330
320
  continue;
331
321
  }
@@ -341,8 +331,8 @@ async function rewriteSessionTranscriptBindings(
341
331
  } else if (
342
332
  parsed.type === "custom" &&
343
333
  parsed.customType === AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE &&
344
- isPlainObject(parsed.data) &&
345
- isPlainObject(parsed.data.parent)
334
+ isRecord(parsed.data) &&
335
+ isRecord(parsed.data.parent)
346
336
  ) {
347
337
  const parent = parsed.data.parent;
348
338
  const before = parent.sessionFile;
@@ -454,7 +444,7 @@ export async function rewriteRoleRunDurablePages(input: {
454
444
  RUN_STATE_PAGE_FIELDS,
455
445
  rewrites,
456
446
  );
457
- if (isPlainObject(page.principal)) {
447
+ if (isRecord(page.principal)) {
458
448
  rewriteRunDirectoryPathFieldsAgainstRewrites(
459
449
  page.principal,
460
450
  ["sessionDirectory", "sessionFile"],
@@ -462,7 +452,7 @@ export async function rewriteRoleRunDurablePages(input: {
462
452
  );
463
453
  }
464
454
  // Open court summons: source-run locators + frozen attachment path pointers.
465
- if (isPlainObject(page.currentCourt)) {
455
+ if (isRecord(page.currentCourt)) {
466
456
  rewriteSummonsMaterials(page.currentCourt.summons, rewrites);
467
457
  }
468
458
  if (JSON.stringify(page) !== before) {
@@ -188,7 +188,7 @@ function decodeReviewerAdmittedInputs(getFlag: (name: string) => unknown): Revie
188
188
  parsed = JSON.parse(rawAuthorityRefs);
189
189
  } catch (error) {
190
190
  throw new Error(
191
- `Reviewer authority refs transport error: JSON decode failed: ${error instanceof Error ? error.message : String(error)}`,
191
+ `Reviewer authority refs transport error: JSON decode failed: ${errorText(error)}`,
192
192
  );
193
193
  }
194
194
  if (!Array.isArray(parsed) || parsed.some((ref) => typeof ref !== "string")) {
@@ -297,6 +297,8 @@ export type { GatekeeperResult, GatekeeperSubject, SubmissionGateNonPassResult,
297
297
  import { ParentQueueReaskError, unreadableDiscriminatorNotice } from "./submission-errors.ts";
298
298
  import { REVIEW_QUEUE_STATUSES } from "./review-submission.ts";
299
299
 
300
+ import { isRecord, errorText, isEnoent } from "./unknown-value.ts";
301
+
300
302
  export {
301
303
  DOCTOR_EVIDENCE_TOOL_NAME,
302
304
  DOCTOR_OUTPUT_TOOL_NAME,
@@ -507,7 +509,7 @@ export function publicNavigatorSettlement(role: string, phase: NavigatorPhase, e
507
509
  return { kind: "role_infrastructure_failure", role, phase };
508
510
  }
509
511
  // accepted/human — project role/phase status; classifier already rejected infra/contradiction.
510
- const details = typeof event.details === "object" && event.details !== null && !Array.isArray(event.details)
512
+ const details = isRecord(event.details)
511
513
  ? event.details as Record<string, unknown>
512
514
  : {};
513
515
  const status = typeof details.status === "string"
@@ -710,7 +712,7 @@ export function createNavigatorRoleRuntime(
710
712
  dependencies.recordRoutePlaybookReadFailure?.(undefined);
711
713
  if (content.trim() !== "") parts.push(content);
712
714
  } catch (error) {
713
- const message = error instanceof Error ? error.message : String(error);
715
+ const message = errorText(error);
714
716
  if (message.trim() !== "") {
715
717
  dependencies.recordRoutePlaybookReadFailure?.(message);
716
718
  parts.push(message);
@@ -854,7 +856,7 @@ export function createDiaristRoleRuntime(
854
856
  soulTag: "diarist",
855
857
  beforeAccept: async ({ parameters, ctx }) => {
856
858
  const submitted =
857
- parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
859
+ isRecord(parameters)
858
860
  ? (parameters as Record<string, unknown>)
859
861
  : undefined;
860
862
  // Routing belongs to the public seam after this tool call has returned.
@@ -1073,7 +1075,7 @@ export function createRoleRuntimeExtension(
1073
1075
  ): void => {
1074
1076
  if (attendance === undefined) return;
1075
1077
  const recordDisposeFailure = (error: unknown): void => {
1076
- const diagnostic = error instanceof Error ? error.message : String(error);
1078
+ const diagnostic = errorText(error);
1077
1079
  try {
1078
1080
  sitianReport({
1079
1081
  level: "event",
@@ -1087,7 +1089,7 @@ export function createRoleRuntimeExtension(
1087
1089
  try {
1088
1090
  envelopeHost.appendEntry?.("ak-navigator-dispose-failure", {
1089
1091
  diagnostic,
1090
- recordFailure: recordError instanceof Error ? recordError.message : String(recordError),
1092
+ recordFailure: errorText(recordError),
1091
1093
  });
1092
1094
  } catch {
1093
1095
  // Failure already diagnosed; recording must not create unhandled rejection (#959).
@@ -1655,15 +1657,7 @@ export function createRoleRuntimeExtension(
1655
1657
  try {
1656
1658
  await recordTypedProviderHttpStatus(runDir, { httpStatus: status, provider });
1657
1659
  } catch (error) {
1658
- if (
1659
- status >= 200 &&
1660
- status < 300 &&
1661
- error instanceof Error &&
1662
- "code" in error &&
1663
- (error as { code?: unknown }).code === "ENOENT"
1664
- ) {
1665
- return;
1666
- }
1660
+ if (status >= 200 && status < 300 && isEnoent(error)) return;
1667
1661
  failInfrastructure(error, ctx);
1668
1662
  }
1669
1663
  };