@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
@@ -11,6 +11,8 @@ import { basename, dirname, join } from "node:path";
11
11
 
12
12
  import { roleRunArtifactsDirectory } from "./role-run-placement.ts";
13
13
 
14
+ import { isRecord, errorText, isMissingPathError } from "./unknown-value.ts";
15
+
14
16
  export const RUN_TERMINAL_ARTIFACT_FILES = [
15
17
  "report.json",
16
18
  "error.json",
@@ -49,22 +51,6 @@ export type RunTerminalArtifactRead =
49
51
  readonly reason: string;
50
52
  };
51
53
 
52
- function isMissingPathError(error: unknown): boolean {
53
- return (
54
- error instanceof Error
55
- && "code" in error
56
- && (error.code === "ENOENT" || error.code === "ENOTDIR")
57
- );
58
- }
59
-
60
- function errorText(error: unknown): string {
61
- return error instanceof Error ? error.message : String(error);
62
- }
63
-
64
- function isRecord(value: unknown): value is Record<string, unknown> {
65
- return typeof value === "object" && value !== null && !Array.isArray(value);
66
- }
67
-
68
54
  /**
69
55
  * Minimum producer-owned face shared by settlement terminal artifacts
70
56
  * (report / error / audit-incomplete). Consumer-driven: enough to identify a
@@ -8,18 +8,12 @@
8
8
  import { readFile } from "node:fs/promises";
9
9
  import { join } from "node:path";
10
10
 
11
+ import { isEnoent, isRecord } from "./unknown-value.ts";
12
+
11
13
  /** Sole on-disk page for worktree-basename ticket derivation (#865). */
12
14
  export const MIGRATION_TICKET_DERIVATION_PAGE =
13
15
  "migration-ticket-derivation.json" as const;
14
16
 
15
- function isEnoent(error: unknown): boolean {
16
- return (
17
- error instanceof Error &&
18
- "code" in error &&
19
- (error as NodeJS.ErrnoException).code === "ENOENT"
20
- );
21
- }
22
-
23
17
  /** Sole safe-positive ticket invariant (bind / admission / placement / readers). */
24
18
  export function isSafePositiveTicketNumber(value: unknown): value is number {
25
19
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
@@ -69,10 +63,10 @@ async function readJsonObject(
69
63
  ): Promise<Record<string, unknown> | undefined> {
70
64
  try {
71
65
  const raw: unknown = JSON.parse(await readFile(path, "utf8"));
72
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
66
+ if (!isRecord(raw)) {
73
67
  return undefined;
74
68
  }
75
- return raw as Record<string, unknown>;
69
+ return raw;
76
70
  } catch (error) {
77
71
  if (isEnoent(error)) return undefined;
78
72
  throw error;
@@ -7,6 +7,8 @@ import { join } from "node:path";
7
7
 
8
8
  import type { PublicSummonResult } from "./public-role-summons.ts";
9
9
 
10
+ import { errorText } from "./unknown-value.ts";
11
+
10
12
  export async function readAssistantUsageFromSessionFile(
11
13
  sessionFile: string,
12
14
  ): Promise<Usage | undefined> {
@@ -30,7 +32,7 @@ export async function readAssistantUsageFromSessionFile(
30
32
  });
31
33
  } catch (error) {
32
34
  throw new Error(
33
- `session usage parse failed (${sessionFile}): ${error instanceof Error ? error.message : String(error)}`,
35
+ `session usage parse failed (${sessionFile}): ${errorText(error)}`,
34
36
  { cause: error },
35
37
  );
36
38
  }
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "./unknown-value.ts";
1
2
  /**
2
3
  * 对话事实适配(ADR 0075 `dialogue-only` / `no-tool-output` / `speaker-required`)。
3
4
  *
@@ -20,10 +21,6 @@ export type DialogueEvent = {
20
21
  readonly id?: string;
21
22
  };
22
23
 
23
- function isRecord(value: unknown): value is Record<string, unknown> {
24
- return typeof value === "object" && value !== null && !Array.isArray(value);
25
- }
26
-
27
24
  /**
28
25
  * 原生 id:行级或消息体级主键的单一实现(bound 解析与落盘共用)。
29
26
  * Pi/CC:行上 `uuid`/`id`;Codex:`response_item.payload.id`(或 `message.id`)。
@@ -42,7 +42,6 @@ export function createSessionIdentityAuthority(
42
42
  };
43
43
  }
44
44
 
45
-
46
45
  /**
47
46
  * Native session/thread id already stored for this principal on `host`.
48
47
  * Public explicit resume reads it once and hands it to the host adapter.
@@ -15,9 +15,7 @@ import type {
15
15
  SitianRecord,
16
16
  } from "./sitian-contracts.ts";
17
17
 
18
- function isRecord(value: unknown): value is Record<string, unknown> {
19
- return typeof value === "object" && value !== null && !Array.isArray(value);
20
- }
18
+ import { isRecord, errorText } from "./unknown-value.ts";
21
19
 
22
20
  /** Read a Sitian record volume with full traversal and non-destructive diagnostics. */
23
21
  export async function readSitianRecords(recordFile: string): Promise<SitianReadResult> {
@@ -53,7 +51,7 @@ export async function readSitianRecords(recordFile: string): Promise<SitianReadR
53
51
  kind: "malformed",
54
52
  line: index + 1,
55
53
  raw: line,
56
- error: error instanceof Error ? error.message : String(error),
54
+ error: errorText(error),
57
55
  });
58
56
  }
59
57
  }
@@ -6,6 +6,8 @@ import {
6
6
  WorkerUnfinishedReasonReminderError,
7
7
  } from "./submission-errors.ts";
8
8
 
9
+ import { errorText } from "./unknown-value.ts";
10
+
9
11
  /** Shared, unforgeable identity for submission errors that the same session may correct. */
10
12
  const correctableSubmissionErrorBrand = Symbol("ak-roles.correctable-submission-error");
11
13
 
@@ -49,7 +51,7 @@ export type CorrectableExecuteRejectionProjection = {
49
51
  export function projectCorrectableExecuteRejection(
50
52
  error: unknown,
51
53
  ): CorrectableExecuteRejectionProjection {
52
- const diagnostic = error instanceof Error ? error.message : String(error);
54
+ const diagnostic = errorText(error);
53
55
  if (error instanceof GatekeeperDecisionError) {
54
56
  return { diagnostic, details: { ...(error.result as Record<string, unknown>) } };
55
57
  }
@@ -1,5 +1,6 @@
1
1
  import type { SubmissionGateNonPassResult } from "./gatekeeper-role.ts";
2
2
  import { readableGateItem } from "./readable-gate-item.ts";
3
+ import { isRecord } from "./unknown-value.ts";
3
4
 
4
5
  /**
5
6
  * Tool-result text the parent model sees (#753 / #750 evidence).
@@ -69,7 +70,7 @@ export function unreadableDiscriminatorNotice(field: string, received: unknown):
69
70
 
70
71
  /** The received discriminator only — never the rest of the receipt. */
71
72
  export function receivedDiscriminator(receipt: unknown, field: string): unknown {
72
- if (receipt === null || typeof receipt !== "object" || Array.isArray(receipt)) return undefined;
73
+ if (!isRecord(receipt)) return undefined;
73
74
  if (!Object.hasOwn(receipt, field)) return undefined;
74
75
  return (receipt as Record<string, unknown>)[field];
75
76
  }
@@ -36,7 +36,7 @@ export function latestQueueStatus(terminal: TerminalResult | undefined): string
36
36
  if (outcome.kind === "audit_escalation") return "escalate";
37
37
  if (outcome.kind !== "accepted") return undefined;
38
38
  const latest = outcome.payloads?.[outcome.payloads.length - 1];
39
- if (latest !== null && typeof latest === "object" && !Array.isArray(latest)) {
39
+ if (isRecord(latest)) {
40
40
  const status = (latest as { status?: unknown }).status;
41
41
  if (typeof status === "string" && status.trim() !== "") return status;
42
42
  }
@@ -54,6 +54,8 @@ export function latestQueuePayload(terminal: TerminalResult | undefined): unknow
54
54
  }
55
55
  import { sessionFileFromPublicSummon } from "./session-assistant-usage.ts";
56
56
 
57
+ import { isRecord } from "./unknown-value.ts";
58
+
57
59
  /**
58
60
  * Shared-envelope default officer summon (ADR 0018).
59
61
  * Role modules must not own this drive seam — only project results.
@@ -12,7 +12,6 @@ import {
12
12
  type RoleHost,
13
13
  } from "./host-contracts.ts";
14
14
 
15
-
16
15
  import { runIdFromRunDirectory } from "./run-terminal-artifacts.ts";
17
16
  import { readSitianRecords, resolveSitianRecordPathInLedger, sitianReport, type RecordPointer } from "./sitian-facade.ts";
18
17
  import type { SitianRecord } from "./sitian-contracts.ts";
@@ -22,6 +21,8 @@ import { isCorrectableExecuteError } from "./submission-correctable-error.ts";
22
21
  import { failOnInfrastructureFailureDeclaration, infrastructureFailureDiagnostic } from "./package-contracts/terminating-infrastructure.ts";
23
22
  import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
24
23
 
24
+ import { isRecord, errorText } from "./unknown-value.ts";
25
+
25
26
  export type SubmissionCall = { readonly id: string; readonly name: string };
26
27
  export type SubmissionOutcomeKind = "correctable-rejection" | "audit-escalation" | "infrastructure";
27
28
  /** Typed correctable-rejection codes — bounce/reminder paths only (#836: no sole/non-terminate reject). */
@@ -662,9 +663,7 @@ export function createSubmissionLedgerHost(
662
663
  // host-failure seam. Tool identity comes from the registry map.
663
664
  const reviewEscalate = tool.name === REVIEW_SUBMISSION_OUTPUT_TOOL_NAME
664
665
  && infrastructureFailureDiagnostic(params) !== undefined
665
- && params !== null
666
- && typeof params === "object"
667
- && !Array.isArray(params)
666
+ && isRecord(params)
668
667
  && (params as Record<string, unknown>).status === "escalate";
669
668
  if (!reviewEscalate) {
670
669
  failOnInfrastructureFailureDeclaration(
@@ -688,7 +687,7 @@ export function createSubmissionLedgerHost(
688
687
  toolCallId,
689
688
  outcome: "correctable-rejection",
690
689
  code: "typed-bounce",
691
- diagnostic: error instanceof Error ? error.message : String(error),
690
+ diagnostic: errorText(error),
692
691
  role,
693
692
  accepted: params,
694
693
  });
@@ -699,7 +698,7 @@ export function createSubmissionLedgerHost(
699
698
  attemptId,
700
699
  toolCallId,
701
700
  outcome: "infrastructure",
702
- diagnostic: error instanceof Error ? error.message : String(error),
701
+ diagnostic: errorText(error),
703
702
  role,
704
703
  accepted: params,
705
704
  });
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "./unknown-value.ts";
1
2
  /**
2
3
  * 起居录(ticket-provenance)typed 形状 —— ADR 0075 / ADR 0081 / #1090。
3
4
  *
@@ -61,10 +62,6 @@ export type TicketProvenanceLine = {
61
62
  readonly text: string;
62
63
  };
63
64
 
64
- function isRecord(value: unknown): value is Record<string, unknown> {
65
- return typeof value === "object" && value !== null && !Array.isArray(value);
66
- }
67
-
68
65
  function positiveInteger(value: unknown): number | undefined {
69
66
  if (typeof value === "number" && Number.isSafeInteger(value) && value >= 1) return value;
70
67
  if (typeof value === "string" && /^[1-9][0-9]*$/.test(value)) {
@@ -32,6 +32,8 @@ import {
32
32
  type TicketProvenanceSession,
33
33
  } from "./ticket-provenance-contracts.ts";
34
34
 
35
+ import { errorText } from "./unknown-value.ts";
36
+
35
37
  /**
36
38
  * Typed input failure for diarist bounds/session path (reask, not infrastructure).
37
39
  * Accept hook discriminates with instanceof — never Error.message prefixes.
@@ -281,7 +283,7 @@ async function projectSessionRanges(input: {
281
283
  if (error instanceof TicketProvenanceInputError) throw error;
282
284
  const code = errnoCode(error);
283
285
  if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
284
- const detail = error instanceof Error ? error.message : String(error);
286
+ const detail = errorText(error);
285
287
  throw new TicketProvenanceInputError(
286
288
  `session unreadable: ${input.session.path} (${detail})`,
287
289
  { cause: error },
@@ -13,6 +13,8 @@
13
13
  */
14
14
  import { createGhApiRunner, type GhApiRunner } from "./gh-api-runner.ts";
15
15
 
16
+ import { isRecord } from "./unknown-value.ts";
17
+
16
18
  export type BookRepoBinding = {
17
19
  bookKey: string;
18
20
  owner: string;
@@ -92,10 +94,6 @@ export class TicketSnapshotApiError extends Error {
92
94
  }
93
95
  }
94
96
 
95
- function isRecord(value: unknown): value is Record<string, unknown> {
96
- return typeof value === "object" && value !== null && !Array.isArray(value);
97
- }
98
-
99
97
  function requireNonEmpty(value: string, label: string): string {
100
98
  const trimmed = value.trim();
101
99
  if (!trimmed) throw new Error(`${label} must be a non-empty string`);
@@ -46,6 +46,7 @@ import {
46
46
  } from "./package-contracts/terminating-tools.ts";
47
47
  import { LEGACY_REVIEW_OUTPUT_ROLES, PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
48
48
 
49
+ import { isRecord, isMissingPathError } from "./unknown-value.ts";
49
50
 
50
51
  /** Declared refresh bound for the same viewing surface (seconds). */
51
52
  export const DEFAULT_REFRESH_BOUNDARY_SECONDS = 30;
@@ -122,14 +123,6 @@ export type TicketTrajectoryRun = {
122
123
 
123
124
  type ParsedRun = TicketTrajectoryRun;
124
125
 
125
- function isRecord(value: unknown): value is Record<string, unknown> {
126
- return typeof value === "object" && value !== null && !Array.isArray(value);
127
- }
128
-
129
- function isMissingPathError(error: unknown): boolean {
130
- return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
131
- }
132
-
133
126
  /** realpath when the node exists; lexical path only for recognized absence — never for other errors. */
134
127
  async function realpathOrLexicalIfMissing(path: string): Promise<string> {
135
128
  try {
@@ -5,6 +5,8 @@
5
5
  import { readFile, unlink, writeFile } from "node:fs/promises";
6
6
  import { join } from "node:path";
7
7
 
8
+ import { isEnoent, isRecord } from "./unknown-value.ts";
9
+
8
10
  const TYPED_HTTP_FILE = "typed-provider-http.json";
9
11
 
10
12
  export type TypedProviderHttpObservation = {
@@ -27,13 +29,7 @@ export async function clearTypedProviderHttpObservation(
27
29
  try {
28
30
  await unlink(typedProviderHttpPath(runDirectory));
29
31
  } catch (error) {
30
- if (
31
- error instanceof Error &&
32
- "code" in error &&
33
- (error as { code?: unknown }).code === "ENOENT"
34
- ) {
35
- return;
36
- }
32
+ if (isEnoent(error)) return;
37
33
  throw error;
38
34
  }
39
35
  }
@@ -75,17 +71,11 @@ export async function readLatestTypedProviderHttpObservation(
75
71
  try {
76
72
  text = await readFile(typedProviderHttpPath(runDirectory), "utf8");
77
73
  } catch (error) {
78
- if (
79
- error instanceof Error &&
80
- "code" in error &&
81
- (error as { code?: unknown }).code === "ENOENT"
82
- ) {
83
- return undefined;
84
- }
74
+ if (isEnoent(error)) return undefined;
85
75
  throw error;
86
76
  }
87
77
  const raw: unknown = JSON.parse(text);
88
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
78
+ if (!isRecord(raw)) {
89
79
  throw new Error("typed provider HTTP observation must be a JSON object");
90
80
  }
91
81
  const record = raw as Record<string, unknown>;
@@ -0,0 +1,34 @@
1
+ import { Guard } from "typebox/guard";
2
+
3
+ /**
4
+ * Sole readers for a plain object, a Node errno, and an Error message.
5
+ * Callers choose which codes count; ENOTDIR is not ENOENT.
6
+ * Plain objects are TypeBox `Guard.IsObjectNotArray`.
7
+ */
8
+
9
+ export function isRecord(value: unknown): value is Record<string, unknown> {
10
+ return Guard.IsObjectNotArray(value);
11
+ }
12
+
13
+ export function errnoCode(error: unknown): string | undefined {
14
+ return error !== null && typeof error === "object" && "code" in error
15
+ && typeof (error as { code: unknown }).code === "string"
16
+ ? (error as { code: string }).code
17
+ : undefined;
18
+ }
19
+
20
+ /** `Error.message`, otherwise `String(value)`. */
21
+ export function errorText(error: unknown): string {
22
+ return error instanceof Error ? error.message : String(error);
23
+ }
24
+
25
+ /** True only for ENOENT on an Error. ENOTDIR and every other code stay distinct. */
26
+ export function isEnoent(error: unknown): boolean {
27
+ return error instanceof Error && errnoCode(error) === "ENOENT";
28
+ }
29
+
30
+ /** ENOENT or ENOTDIR on an Error. */
31
+ export function isMissingPathError(error: unknown): boolean {
32
+ const code = errnoCode(error);
33
+ return error instanceof Error && (code === "ENOENT" || code === "ENOTDIR");
34
+ }
@@ -3,6 +3,8 @@
3
3
  * Pi's piped-stdin reader trims the wrapper; the body field keeps original bytes.
4
4
  * No size cap and no path/pointer substitute — the whole dialogue rides the envelope.
5
5
  */
6
+ import { isRecord } from "./unknown-value.ts";
7
+
6
8
  export const USER_DIALOGUE_STDIN_KIND = "ak-user-dialogue" as const;
7
9
 
8
10
  export type UserDialogueStdinEnvelope = {
@@ -15,7 +17,7 @@ export function encodeUserDialogueStdin(body: string): string {
15
17
  }
16
18
 
17
19
  function isUserDialogueEnvelope(value: unknown): value is UserDialogueStdinEnvelope {
18
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
20
+ if (!isRecord(value)) return false;
19
21
  const record = value as { kind?: unknown; body?: unknown };
20
22
  return record.kind === USER_DIALOGUE_STDIN_KIND && typeof record.body === "string";
21
23
  }
@@ -29,6 +29,8 @@ import {
29
29
  matchFixerBashForbiddenLiteral,
30
30
  } from "./fixer-bash-seatbelt.ts";
31
31
 
32
+ import { isRecord } from "./unknown-value.ts";
33
+
32
34
  export {
33
35
  CODER_OUTPUT_TOOL_NAME,
34
36
  FIXER_OUTPUT_TOOL_NAME,
@@ -104,10 +106,6 @@ export type WorkerRoleRuntime = {
104
106
  armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }): void;
105
107
  };
106
108
 
107
- function isRecord(value: unknown): value is Record<string, unknown> {
108
- return typeof value === "object" && value !== null && !Array.isArray(value);
109
- }
110
-
111
109
  /** Read only the field that selects the worker's next package-owned path. */
112
110
  function workerStatusOf(output: WorkerOutput): string | undefined {
113
111
  return isRecord(output) && typeof output.status === "string"
@@ -130,7 +128,6 @@ export function validateWorkerOutput(
130
128
  return validateAcceptedWorkerDetails(output, "Coder") as CoderOutput;
131
129
  }
132
130
 
133
-
134
131
  /** Reminder bounces stay typed rejects; IO/infrastructure keep identity via host failInfrastructure. */
135
132
  function assertAcceptableThroughHost(
136
133
  submissionGate: { assertAcceptable(status: string, details?: unknown): void },
@@ -17,6 +17,8 @@ import {
17
17
  } from "./submission-errors.ts";
18
18
  import { WORKER_DONE_STATUSES } from "./worker-submission-contracts.ts";
19
19
 
20
+ import { isRecord } from "./unknown-value.ts";
21
+
20
22
  export { WorkerCommitReminderError, WorkerPrefixReminderError, WorkerUnfinishedReasonReminderError } from "./submission-errors.ts";
21
23
  export { WORKER_DONE_STATUSES } from "./worker-submission-contracts.ts";
22
24
 
@@ -152,15 +154,11 @@ function uninstallPackageWorkerHooks(cwd: string): void {
152
154
  }
153
155
  }
154
156
 
155
- function isRecord(value: unknown): value is Record<string, unknown> {
156
- return typeof value === "object" && value !== null && !Array.isArray(value);
157
- }
158
-
159
157
  /** ADR 0050: a written explanation, including text carried by an object key. */
160
158
  function containsWrittenReason(value: unknown): boolean {
161
159
  if (typeof value === "string") return value.trim().length > 0;
162
160
  if (Array.isArray(value)) return value.some(containsWrittenReason);
163
- if (typeof value === "object" && value !== null) {
161
+ if (isRecord(value)) {
164
162
  return Object.entries(value).some(
165
163
  ([key, item]) => containsWrittenReason(key) || containsWrittenReason(item),
166
164
  );
@@ -169,8 +167,8 @@ function containsWrittenReason(value: unknown): boolean {
169
167
  }
170
168
 
171
169
  function unfinishedReasonPresent(details?: unknown): boolean {
172
- if (typeof details !== "object" || details === null || Array.isArray(details)) return false;
173
- return containsWrittenReason((details as { reason?: unknown }).reason);
170
+ if (!isRecord(details)) return false;
171
+ return containsWrittenReason(details.reason);
174
172
  }
175
173
 
176
174
  function readGateState(session: HostRecordSession): {