@astrosheep/keiyaku 2.9.3 → 2.9.5

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 (282) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/call-terms.js +14 -94
  3. package/build/agents/harness/activity-values.js +16 -0
  4. package/build/agents/harness/event-persistence.js +218 -0
  5. package/build/agents/harness/execution-handle.js +26 -222
  6. package/build/agents/harness/index.js +3 -1
  7. package/build/agents/harness/outcome.js +68 -5
  8. package/build/agents/harness/projection.js +179 -124
  9. package/build/agents/harness/provider-adapter.js +58 -0
  10. package/build/agents/harness/router.js +11 -187
  11. package/build/agents/harness/runtime.js +17 -4
  12. package/build/agents/harness/session.js +15 -0
  13. package/build/agents/launch-snapshot/model.js +15 -236
  14. package/build/agents/launch-snapshot/parse-helpers.js +114 -0
  15. package/build/agents/launch-snapshot/resolve.js +17 -43
  16. package/build/agents/opencode-sdk.js +45 -12
  17. package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
  18. package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
  19. package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
  20. package/build/agents/providers/codex-app-server/adapter.js +82 -53
  21. package/build/agents/providers/codex-app-server/events.js +90 -11
  22. package/build/agents/providers/codex-app-server/identity.js +1 -0
  23. package/build/agents/providers/codex-app-server/policy.js +29 -0
  24. package/build/agents/providers/codex-app-server/registration.js +248 -0
  25. package/build/agents/providers/codex-app-server/session-schema.js +6 -0
  26. package/build/agents/providers/codex-app-server/session.js +23 -17
  27. package/build/agents/providers/codex-app-server/terms.js +1 -0
  28. package/build/agents/providers/opencode-sdk/adapter.js +94 -50
  29. package/build/agents/providers/opencode-sdk/events.js +8 -4
  30. package/build/agents/providers/opencode-sdk/registration.js +135 -0
  31. package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
  32. package/build/agents/providers/opencode-sdk/session.js +21 -14
  33. package/build/agents/providers/opencode-sdk/terms.js +1 -0
  34. package/build/agents/providers/pi/adapter.js +36 -30
  35. package/build/agents/providers/pi/events.js +4 -3
  36. package/build/agents/providers/pi/registration.js +145 -0
  37. package/build/agents/providers/pi/session-schema.js +6 -0
  38. package/build/agents/providers/pi/terms.js +1 -0
  39. package/build/agents/providers/registration.js +7 -0
  40. package/build/agents/providers/registry.js +76 -0
  41. package/build/agents/selector.js +2 -29
  42. package/build/agents/types.js +10 -0
  43. package/build/artifact-directories.js +22 -0
  44. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  45. package/build/cli/commands/akuma/akuma/meta.js +1 -1
  46. package/build/cli/commands/akuma/list/meta-ls.js +1 -1
  47. package/build/cli/commands/akuma/view/handler.js +5 -4
  48. package/build/cli/commands/akuma/view/meta.js +1 -1
  49. package/build/cli/commands/akuma.js +6 -0
  50. package/build/cli/commands/contract/amend/handler.js +8 -2
  51. package/build/cli/commands/contract/amend/meta.js +4 -4
  52. package/build/cli/commands/contract/arc/handler.js +8 -2
  53. package/build/cli/commands/contract/arc/meta.js +4 -4
  54. package/build/cli/commands/contract/audit/handler.js +16 -0
  55. package/build/cli/commands/contract/audit/meta.js +16 -0
  56. package/build/cli/commands/contract/bind/handler.js +4 -3
  57. package/build/cli/commands/contract/bind/meta.js +44 -8
  58. package/build/cli/commands/contract/catalog.js +2 -0
  59. package/build/cli/commands/contract/forfeit/handler.js +4 -4
  60. package/build/cli/commands/contract/forfeit/meta.js +1 -1
  61. package/build/cli/commands/contract/log/handler.js +3 -3
  62. package/build/cli/commands/contract/log/meta.js +1 -1
  63. package/build/cli/commands/contract/petition/handler.js +5 -8
  64. package/build/cli/commands/contract/petition/meta.js +6 -6
  65. package/build/cli/commands/contract/renew/handler.js +20 -6
  66. package/build/cli/commands/contract/renew/meta.js +6 -5
  67. package/build/cli/commands/metadata.js +59 -0
  68. package/build/cli/commands/projection/call/handler.js +6 -5
  69. package/build/cli/commands/projection/call/meta.js +4 -1
  70. package/build/cli/commands/projection/kill/handler.js +16 -32
  71. package/build/cli/commands/projection/kill/meta.js +1 -1
  72. package/build/cli/commands/projection/revive/meta.js +4 -1
  73. package/build/cli/commands/projection/shared.js +28 -0
  74. package/build/cli/commands/projection/status/handler.js +1 -1
  75. package/build/cli/commands/projection/status/meta.js +1 -1
  76. package/build/cli/commands/projection/tell/handler.js +14 -30
  77. package/build/cli/commands/projection/tell/meta.js +4 -1
  78. package/build/cli/commands/projection/wait/handler.js +45 -22
  79. package/build/cli/commands/projection/wait/meta.js +8 -3
  80. package/build/cli/commands/registry.js +2 -54
  81. package/build/cli/commands/shared.js +10 -7
  82. package/build/cli/commands/skills/install/handler.js +2 -5
  83. package/build/cli/commands/skills/install/meta.js +1 -1
  84. package/build/cli/commands/skills/skills/handler.js +1 -1
  85. package/build/cli/commands/skills/skills/meta.js +1 -1
  86. package/build/cli/commands/system/completion/meta.js +1 -1
  87. package/build/cli/commands/system/dump-env/meta.js +1 -1
  88. package/build/cli/commands/system/guide/meta.js +1 -1
  89. package/build/cli/commands/task/add/handler.js +7 -15
  90. package/build/cli/commands/task/add/meta.js +4 -1
  91. package/build/cli/commands/task/catalog.js +2 -2
  92. package/build/cli/commands/task/doctor/meta.js +1 -1
  93. package/build/cli/commands/task/done/handler.js +4 -8
  94. package/build/cli/commands/task/done/meta.js +1 -1
  95. package/build/cli/commands/task/drop/handler.js +3 -6
  96. package/build/cli/commands/task/drop/meta.js +1 -1
  97. package/build/cli/commands/task/ls/handler.js +12 -8
  98. package/build/cli/commands/task/ls/meta.js +3 -3
  99. package/build/cli/commands/task/shared.js +14 -4
  100. package/build/cli/commands/task/{view → show}/handler.js +7 -8
  101. package/build/cli/commands/task/show/meta.js +8 -0
  102. package/build/cli/commands/task/start/handler.js +3 -6
  103. package/build/cli/commands/task/start/meta.js +1 -1
  104. package/build/cli/commands/task/stop/handler.js +4 -8
  105. package/build/cli/commands/task/stop/meta.js +1 -1
  106. package/build/cli/commands/task/task/handler.js +1 -1
  107. package/build/cli/commands/task/task/meta.js +1 -1
  108. package/build/cli/commands/task/update/handler.js +6 -11
  109. package/build/cli/commands/task/update/meta.js +4 -1
  110. package/build/cli/completion.js +1 -1
  111. package/build/cli/flags.js +37 -6
  112. package/build/cli/help.js +7 -14
  113. package/build/cli/index.js +112 -154
  114. package/build/cli/missing-subcommand.js +7 -0
  115. package/build/cli/output-stream-error-policy.js +19 -5
  116. package/build/cli/parse-flags.js +33 -7
  117. package/build/cli/parse-metadata.js +3 -9
  118. package/build/cli/parse-selectors.js +2 -1
  119. package/build/cli/parse.js +35 -5
  120. package/build/cli/projection-address.js +12 -12
  121. package/build/cli/render/address.js +41 -37
  122. package/build/cli/render/arc.js +24 -12
  123. package/build/cli/render/audit.js +121 -0
  124. package/build/cli/render/call.js +11 -5
  125. package/build/cli/render/errors.js +36 -1
  126. package/build/cli/render/format.js +4 -0
  127. package/build/cli/render/kanshi.js +6 -12
  128. package/build/cli/render/kill.js +9 -0
  129. package/build/cli/render/misc.js +58 -13
  130. package/build/cli/render/petition.js +20 -15
  131. package/build/cli/render/projection-activity.js +15 -6
  132. package/build/cli/render/shared.js +60 -21
  133. package/build/cli/render/skills.js +10 -0
  134. package/build/cli/render/status.js +56 -23
  135. package/build/cli/render/success-response.js +78 -0
  136. package/build/cli/render/task.js +66 -57
  137. package/build/cli/render/tell.js +5 -0
  138. package/build/cli/render/terminal-failure.js +63 -0
  139. package/build/cli/render/types.js +1 -0
  140. package/build/cli/render/wait.js +68 -38
  141. package/build/cli/types.js +1 -1
  142. package/build/config/akuma-loader.js +0 -5
  143. package/build/config/env-keys.js +3 -1
  144. package/build/config/env.js +3 -0
  145. package/build/config/provider-policy-ownership.js +3 -32
  146. package/build/config/settings/disease.js +8 -1
  147. package/build/config/settings/loader.js +11 -3
  148. package/build/config/settings/provider-instance-fields.js +51 -0
  149. package/build/config/settings/schema.js +6 -176
  150. package/build/core/addressing.js +66 -16
  151. package/build/core/amend.js +61 -18
  152. package/build/core/arc.js +33 -73
  153. package/build/core/audit.js +650 -0
  154. package/build/core/bind.js +82 -35
  155. package/build/core/call/call.js +15 -9
  156. package/build/core/call/context.js +6 -51
  157. package/build/core/call/execution.js +22 -22
  158. package/build/core/call/prompt.js +14 -1
  159. package/build/core/command-markdown.js +45 -0
  160. package/build/core/context.js +13 -7
  161. package/build/core/contract-view.js +22 -6
  162. package/build/core/draft.js +139 -19
  163. package/build/core/entry.js +3 -4
  164. package/build/core/forfeit.js +5 -2
  165. package/build/core/hints.js +1 -75
  166. package/build/core/identifier-slug.js +66 -0
  167. package/build/core/ids.js +25 -11
  168. package/build/core/ledger.js +20 -68
  169. package/build/core/markdown/lex.js +2 -1
  170. package/build/core/markdown/parser.js +0 -68
  171. package/build/core/markdown/render.js +68 -0
  172. package/build/core/markdown/sections.js +2 -1
  173. package/build/core/outcome-base.js +4 -0
  174. package/build/core/projection/generation/database.js +41 -52
  175. package/build/core/projection/generation/model.js +63 -2
  176. package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
  177. package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +36 -53
  178. package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
  179. package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +9 -7
  180. package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
  181. package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +19 -3
  182. package/build/core/projection/generation/store.js +3 -3
  183. package/build/core/projection/generation/transitions.js +19 -3
  184. package/build/core/projection/index.js +27 -0
  185. package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
  186. package/build/core/{projection-alias.js → projection/projection-alias.js} +13 -13
  187. package/build/core/projection/projection-coordinate.js +61 -0
  188. package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
  189. package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +37 -78
  190. package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
  191. package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
  192. package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +16 -2
  193. package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
  194. package/build/core/{projection-status.js → projection/projection-status.js} +163 -45
  195. package/build/core/projection/projection-terminal-failure.js +106 -0
  196. package/build/core/projection/projection-wait.js +275 -0
  197. package/build/core/projection/projection-wake.js +227 -0
  198. package/build/core/provider-loader-ports.js +4 -0
  199. package/build/core/ref-log.js +65 -40
  200. package/build/core/registry.js +63 -10
  201. package/build/core/renew-session.js +128 -0
  202. package/build/core/renew.js +448 -102
  203. package/build/core/scope.js +272 -68
  204. package/build/core/seal.js +165 -40
  205. package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +8 -8
  206. package/build/core/{claim.js → settlement/claim.js} +32 -8
  207. package/build/core/settlement/index.js +19 -0
  208. package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +24 -49
  209. package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
  210. package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
  211. package/build/core/settlement/petition-io.js +1 -0
  212. package/build/core/{petition-preview.js → settlement/petition-preview.js} +2 -2
  213. package/build/core/{petition.js → settlement/petition.js} +21 -12
  214. package/build/core/settlement/queue-read-model.js +143 -0
  215. package/build/core/settlement/queue-seat-allocation.js +156 -0
  216. package/build/core/settlement/queue.js +6 -0
  217. package/build/core/settlement/settlement.js +288 -0
  218. package/build/core/{verdict.js → settlement/verdict.js} +9 -37
  219. package/build/core/settlement/verification.js +59 -0
  220. package/build/core/status/board.js +62 -41
  221. package/build/core/status/ledger-batch.js +158 -0
  222. package/build/core/status/reconciliation.js +10 -4
  223. package/build/core/stored-agent-event.js +6 -2
  224. package/build/core/task/board.js +41 -4
  225. package/build/core/task/commands.js +45 -28
  226. package/build/core/task/document.js +5 -0
  227. package/build/core/task/index.js +12 -0
  228. package/build/core/task/model.js +8 -1
  229. package/build/core/task/query.js +172 -0
  230. package/build/core/task/settlement-git.js +265 -74
  231. package/build/core/task/settlement-policy.js +67 -29
  232. package/build/core/task/source-board.js +1 -0
  233. package/build/core/task/task-contract.js +444 -0
  234. package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
  235. package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
  236. package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
  237. package/build/core/{task-git-store.js → task/task-git-store.js} +90 -19
  238. package/build/core/task/task-repository.js +1 -0
  239. package/build/core/{task-store-repository.js → task/task-store-repository.js} +6 -4
  240. package/build/core/task/task-store.js +1 -0
  241. package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
  242. package/build/core/task/task.js +7 -0
  243. package/build/core/task/tree.js +37 -0
  244. package/build/core/task/validation.js +5 -2
  245. package/build/core/transcripts.js +15 -3
  246. package/build/core/worktree-path.js +43 -28
  247. package/build/flow-error.js +83 -39
  248. package/build/fs/path-coordinate.js +16 -0
  249. package/build/generated/version.js +3 -2
  250. package/build/git/branches.js +2 -1
  251. package/build/git/core.js +106 -2
  252. package/build/git/diff/parsers.js +23 -13
  253. package/build/git/diff/structured.js +127 -0
  254. package/build/git/path-coordinate.js +17 -0
  255. package/build/git/refs.js +57 -0
  256. package/build/git/streaming-batch.js +301 -0
  257. package/build/git/worktree.js +65 -0
  258. package/build/index.js +9 -9
  259. package/build/keiyaku.js +2 -36
  260. package/build/telemetry/logger.js +4 -2
  261. package/package.json +4 -10
  262. package/skills/keiyaku/SKILL.md +5 -4
  263. package/skills/keiyaku-akuma/SKILL.md +25 -11
  264. package/skills/keiyaku-task/SKILL.md +17 -8
  265. package/skills/keiyaku-workflow/SKILL.md +58 -9
  266. package/build/agents/providers/codex-sdk.js +0 -329
  267. package/build/cli/commands/task/view/meta.js +0 -8
  268. package/build/core/command-io.js +0 -174
  269. package/build/core/path-coordinate.js +0 -27
  270. package/build/core/petition-claim.js +0 -117
  271. package/build/core/petition-run.js +0 -83
  272. package/build/core/projection-coordinate.js +0 -63
  273. package/build/core/projection-wait.js +0 -165
  274. package/build/core/projection-wake.js +0 -136
  275. package/build/core/queue.js +0 -343
  276. package/build/core/task-contract.js +0 -237
  277. package/build/core/task.js +0 -4
  278. /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
  279. /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
  280. /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
  281. /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
  282. /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
@@ -1,30 +1,4 @@
1
- import { FlowError } from "../../flow-error.js";
2
- import { SUBAGENT_SESSION_VERSION, subagentSessionEnvelopeSchema, } from "../session.js";
3
- import { startClaudeAgentSdk, claudeSessionSchema, } from "../providers/claude-agent-sdk/adapter.js";
4
- import { startCodexAppServer, codexAppServerSessionSchema, } from "../providers/codex-app-server/adapter.js";
5
- import { startCodexSdk, codexThreadSchema, } from "../providers/codex-sdk.js";
6
- import { startOpencode, opencodeSessionSchema, } from "../providers/opencode-sdk/adapter.js";
7
- import { startPi, piSessionSchema, } from "../providers/pi/adapter.js";
8
- function admitResumeShape(provider, schema, session, resumePath) {
9
- const envelopeResult = subagentSessionEnvelopeSchema.safeParse(session);
10
- if (!envelopeResult.success) {
11
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session envelope.`);
12
- }
13
- if (session.provider !== provider) {
14
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' was created with provider '${session.provider}', but the selected subagent resolves to '${provider}'. Cross-provider resume is not supported.`);
15
- }
16
- if (session.version !== SUBAGENT_SESSION_VERSION) {
17
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' uses unsupported session version '${session.version}' for provider '${session.provider}'.`);
18
- }
19
- const shapeResult = schema.safeParse(session.handle);
20
- if (!shapeResult.success) {
21
- throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session handle for provider '${session.provider}'.`);
22
- }
23
- return shapeResult.data;
24
- }
25
- function assertNever(_value) {
26
- throw new Error("Unhandled provider");
27
- }
1
+ import { getProviderRegistration } from "../providers/registry.js";
28
2
  export async function startLiveProjection(terms, signal, reviveTerms, onIngress, observer) {
29
3
  const onSyncCheckpoint = observer
30
4
  ? (control, checkpoint) => {
@@ -38,164 +12,14 @@ export async function startLiveProjection(terms, signal, reviveTerms, onIngress,
38
12
  observer.onDeliveryOpportunity(control);
39
13
  }
40
14
  : undefined;
41
- switch (terms.provider) {
42
- case "pi": {
43
- if (reviveTerms) {
44
- const session = admitResumeShape("pi", piSessionSchema, reviveTerms.session, reviveTerms.resumePath);
45
- return startPi(terms.prompt, terms.workdir, {
46
- model: terms.model,
47
- thinkingLevel: terms.thinkingLevel,
48
- signal,
49
- sessionFile: session.sessionFile,
50
- appendSystemPromptOverride: terms.appendSystemPromptOverride,
51
- onSyncCheckpoint,
52
- onEvidence: terms.onEvidence,
53
- onIngress,
54
- initialTurn: terms.initialTurn,
55
- loader: terms.loader,
56
- });
57
- }
58
- return startPi(terms.prompt, terms.workdir, {
59
- model: terms.model,
60
- thinkingLevel: terms.thinkingLevel,
61
- signal,
62
- appendSystemPromptOverride: terms.appendSystemPromptOverride,
63
- onSyncCheckpoint,
64
- onEvidence: terms.onEvidence,
65
- onIngress,
66
- initialTurn: terms.initialTurn,
67
- loader: terms.loader,
68
- });
69
- }
70
- case "claude-agent-sdk": {
71
- const claudeOptions = {
72
- model: terms.model,
73
- executable: terms.executable,
74
- effort: terms.effort,
75
- env: terms.env,
76
- signal,
77
- permissionMode: terms.permissionMode,
78
- settingSources: terms.settingSources,
79
- allowDangerouslySkipPermissions: terms.allowDangerouslySkipPermissions,
80
- disallowedTools: terms.disallowedTools,
81
- systemPrompt: terms.systemPrompt,
82
- onSyncCheckpoint,
83
- onEvidence: terms.onEvidence,
84
- onIngress,
85
- initialTurn: terms.initialTurn,
86
- loader: terms.loader,
87
- };
88
- if (reviveTerms) {
89
- const session = admitResumeShape("claude-agent-sdk", claudeSessionSchema, reviveTerms.session, reviveTerms.resumePath);
90
- return startClaudeAgentSdk(terms.prompt, terms.workdir, {
91
- ...claudeOptions,
92
- sessionId: session.sessionId,
93
- });
94
- }
95
- return startClaudeAgentSdk(terms.prompt, terms.workdir, claudeOptions);
96
- }
97
- case "codex-app-server": {
98
- if (reviveTerms) {
99
- const session = admitResumeShape("codex-app-server", codexAppServerSessionSchema, reviveTerms.session, reviveTerms.resumePath);
100
- return startCodexAppServer(terms.prompt, terms.workdir, {
101
- model: terms.model,
102
- effort: terms.effort,
103
- profile: terms.profile,
104
- config: terms.config,
105
- sandboxPolicy: terms.sandboxPolicy,
106
- approvalPolicy: terms.approvalPolicy,
107
- executable: terms.executable,
108
- env: terms.env,
109
- signal,
110
- threadId: session.threadId,
111
- codexHome: session.codexHome,
112
- developerInstructions: terms.developerInstructions,
113
- onSyncCheckpoint,
114
- onDeliveryOpportunity,
115
- onEvidence: terms.onEvidence,
116
- onIngress,
117
- initialTurn: terms.initialTurn,
118
- });
119
- }
120
- return startCodexAppServer(terms.prompt, terms.workdir, {
121
- model: terms.model,
122
- effort: terms.effort,
123
- profile: terms.profile,
124
- config: terms.config,
125
- sandboxPolicy: terms.sandboxPolicy,
126
- approvalPolicy: terms.approvalPolicy,
127
- executable: terms.executable,
128
- env: terms.env,
129
- signal,
130
- developerInstructions: terms.developerInstructions,
131
- onSyncCheckpoint,
132
- onDeliveryOpportunity,
133
- onEvidence: terms.onEvidence,
134
- onIngress,
135
- initialTurn: terms.initialTurn,
136
- });
137
- }
138
- case "codex-sdk": {
139
- if (reviveTerms) {
140
- const session = admitResumeShape("codex-sdk", codexThreadSchema, reviveTerms.session, reviveTerms.resumePath);
141
- return startCodexSdk(terms.prompt, terms.workdir, {
142
- model: terms.model,
143
- profile: terms.profile,
144
- config: terms.config,
145
- threadOptions: terms.threadOptions,
146
- executable: terms.executable,
147
- signal,
148
- threadId: session.threadId,
149
- onEvidence: terms.onEvidence,
150
- onIngress,
151
- initialTurn: terms.initialTurn,
152
- codexConstructor: terms.codexConstructor,
153
- });
154
- }
155
- return startCodexSdk(terms.prompt, terms.workdir, {
156
- model: terms.model,
157
- profile: terms.profile,
158
- config: terms.config,
159
- threadOptions: terms.threadOptions,
160
- executable: terms.executable,
161
- signal,
162
- onEvidence: terms.onEvidence,
163
- onIngress,
164
- initialTurn: terms.initialTurn,
165
- codexConstructor: terms.codexConstructor,
166
- });
167
- }
168
- case "opencode-sdk": {
169
- if (reviveTerms) {
170
- const session = admitResumeShape("opencode-sdk", opencodeSessionSchema, reviveTerms.session, reviveTerms.resumePath);
171
- return startOpencode(terms.prompt, terms.workdir, {
172
- model: terms.model,
173
- effort: terms.effort,
174
- executable: terms.executable,
175
- env: terms.env,
176
- signal,
177
- sessionId: session.sessionId,
178
- onSyncCheckpoint,
179
- onDeliveryOpportunity,
180
- onEvidence: terms.onEvidence,
181
- onIngress,
182
- initialTurn: terms.initialTurn,
183
- });
184
- }
185
- return startOpencode(terms.prompt, terms.workdir, {
186
- model: terms.model,
187
- effort: terms.effort,
188
- executable: terms.executable,
189
- env: terms.env,
190
- signal,
191
- onSyncCheckpoint,
192
- onDeliveryOpportunity,
193
- onEvidence: terms.onEvidence,
194
- onIngress,
195
- initialTurn: terms.initialTurn,
196
- });
197
- }
198
- default:
199
- return assertNever(terms);
200
- }
15
+ const registration = getProviderRegistration(terms.provider);
16
+ return registration.startLive({
17
+ terms,
18
+ signal,
19
+ reviveTerms,
20
+ onSyncCheckpoint,
21
+ onDeliveryOpportunity,
22
+ onIngress,
23
+ observer,
24
+ });
201
25
  }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { getConfig } from "../../config/env.js";
3
3
  import { DISABLE_SUBAGENT_SPAWN_ENV_KEY } from "../../config/env-keys.js";
4
+ export const PROVIDER_ABORT_SETTLEMENT_TIMEOUT_MS = 5_000;
4
5
  export function sha256Utf8(value) {
5
6
  const bytes = Buffer.from(value, "utf8");
6
7
  return { sha256: createHash("sha256").update(bytes).digest("hex"), byteLength: bytes.byteLength };
@@ -33,10 +34,22 @@ export function emitProviderEvidence(callback, evidence, diagnostic) {
33
34
  reportFailure();
34
35
  }
35
36
  }
36
- export function createAbortError(message) {
37
- const error = new Error(message);
38
- error.name = "AbortError";
39
- return error;
37
+ export { createAbortError } from "./provider-adapter.js";
38
+ export async function awaitProviderAbortSettlement(provider, settlement, operation = "graceful abort could not prove provider terminal settlement") {
39
+ let timer;
40
+ const deadline = new Promise((_, reject) => {
41
+ timer = setTimeout(() => {
42
+ reject(new Error(`${provider} ${operation} within ${PROVIDER_ABORT_SETTLEMENT_TIMEOUT_MS}ms`));
43
+ }, PROVIDER_ABORT_SETTLEMENT_TIMEOUT_MS);
44
+ timer.unref?.();
45
+ });
46
+ try {
47
+ return await Promise.race([settlement, deadline]);
48
+ }
49
+ finally {
50
+ if (timer)
51
+ clearTimeout(timer);
52
+ }
40
53
  }
41
54
  export function coerceString(value) {
42
55
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
@@ -0,0 +1,15 @@
1
+ import { isSubagentProvider } from "../../keiyaku.js";
2
+ import { SUBAGENT_SESSION_VERSION, subagentSessionEnvelopeSchema, } from "../session.js";
3
+ import { getProviderRegistration } from "../providers/registry.js";
4
+ /** Validate a continuation envelope through its owning provider schema. */
5
+ export function isRecoverableSessionEnvelope(provider, value) {
6
+ if (!isSubagentProvider(provider))
7
+ return false;
8
+ const envelope = subagentSessionEnvelopeSchema.safeParse(value);
9
+ if (!envelope.success)
10
+ return false;
11
+ if (envelope.data.provider !== provider || envelope.data.version !== SUBAGENT_SESSION_VERSION) {
12
+ return false;
13
+ }
14
+ return getProviderRegistration(provider).sessionSchema.safeParse(envelope.data.handle).success;
15
+ }
@@ -1,91 +1,11 @@
1
- import * as path from "node:path";
2
1
  import { FlowError } from "../../flow-error.js";
2
+ import { getProviderRegistration, isSubagentProvider, } from "../providers/registry.js";
3
+ import { exactKeys, objectAt, optionalString, requiredEnum, requiredString, snapshotError, } from "./parse-helpers.js";
3
4
  const ACCESS_VALUES = new Set(["read", "write", "auto"]);
4
5
  const NETWORK_VALUES = new Set(["disabled", "enabled"]);
5
6
  const WEB_SEARCH_VALUES = new Set(["disabled", "cached", "live"]);
6
7
  const AKUMA_SOURCES = new Set(["builtin", "user", "project"]);
7
8
  const PROVIDER_SOURCES = new Set(["builtin", "global", "project"]);
8
- const CLAUDE_SETTING_SOURCES = new Set(["project", "user", "local"]);
9
- function snapshotError(fieldPath, message) {
10
- return new Error(`Invalid resolved Akuma launch snapshot at ${fieldPath}: ${message}`);
11
- }
12
- function isPlainObject(value) {
13
- if (value === null || typeof value !== "object" || Array.isArray(value))
14
- return false;
15
- const prototype = Object.getPrototypeOf(value);
16
- return prototype === Object.prototype || prototype === null;
17
- }
18
- function objectAt(value, fieldPath) {
19
- if (!isPlainObject(value))
20
- throw snapshotError(fieldPath, "must be a plain JSON object");
21
- return value;
22
- }
23
- function exactKeys(value, allowed, fieldPath) {
24
- const allowedSet = new Set(allowed);
25
- for (const key of Object.keys(value)) {
26
- if (!allowedSet.has(key))
27
- throw snapshotError(`${fieldPath}.${key}`, "is an unknown field");
28
- }
29
- }
30
- function requiredString(value, key, fieldPath, allowBlank = false) {
31
- const item = value[key];
32
- if (typeof item !== "string" || (!allowBlank && item.trim().length === 0)) {
33
- throw snapshotError(`${fieldPath}.${key}`, allowBlank ? "must be a string" : "must be a nonblank string");
34
- }
35
- return item;
36
- }
37
- function optionalString(value, key, fieldPath) {
38
- if (!(key in value))
39
- return undefined;
40
- return requiredString(value, key, fieldPath);
41
- }
42
- function requiredEnum(value, key, fieldPath, values) {
43
- const item = requiredString(value, key, fieldPath);
44
- if (!values.has(item))
45
- throw snapshotError(`${fieldPath}.${key}`, `has invalid value '${item}'`);
46
- return item;
47
- }
48
- function cloneJson(value, fieldPath, ancestors = new Set()) {
49
- if (value === null || typeof value === "string" || typeof value === "boolean")
50
- return value;
51
- if (typeof value === "number") {
52
- if (!Number.isFinite(value))
53
- throw snapshotError(fieldPath, "contains a non-finite number");
54
- return value;
55
- }
56
- if (typeof value !== "object")
57
- throw snapshotError(fieldPath, "contains a non-JSON value");
58
- if (ancestors.has(value))
59
- throw snapshotError(fieldPath, "contains a cycle");
60
- ancestors.add(value);
61
- try {
62
- if (Array.isArray(value)) {
63
- return Object.freeze(value.map((item, index) => cloneJson(item, `${fieldPath}[${index}]`, ancestors)));
64
- }
65
- if (!isPlainObject(value))
66
- throw snapshotError(fieldPath, "contains a non-plain JSON object");
67
- const result = {};
68
- for (const [key, item] of Object.entries(value)) {
69
- Object.defineProperty(result, key, {
70
- value: cloneJson(item, `${fieldPath}.${key}`, ancestors),
71
- enumerable: true,
72
- configurable: true,
73
- writable: true,
74
- });
75
- }
76
- return Object.freeze(result);
77
- }
78
- finally {
79
- ancestors.delete(value);
80
- }
81
- }
82
- function cloneJsonObject(value, fieldPath) {
83
- const cloned = cloneJson(value, fieldPath);
84
- if (!isPlainObject(cloned)) {
85
- throw snapshotError(fieldPath, "must be a plain JSON object");
86
- }
87
- return cloned;
88
- }
89
9
  function effortOptionsFrom(akuma) {
90
10
  if (!("effortOptions" in akuma))
91
11
  return undefined;
@@ -118,21 +38,7 @@ function assertEffortRequiresModel(model, effort, kind, akumaName) {
118
38
  throw new FlowError("INVALID_EFFORT_OVERRIDE", `Effort '${effort}' requires an explicit model.`);
119
39
  }
120
40
  function providerEffortAndModel(provider) {
121
- switch (provider.kind) {
122
- case "codex-sdk": {
123
- const model = typeof provider.threadOptions.model === "string" ? provider.threadOptions.model : undefined;
124
- const effort = typeof provider.threadOptions.modelReasoningEffort === "string"
125
- ? provider.threadOptions.modelReasoningEffort
126
- : undefined;
127
- return { model, effort };
128
- }
129
- case "codex-app-server":
130
- case "claude-agent-sdk":
131
- case "opencode-sdk":
132
- return { model: provider.model, effort: provider.effort };
133
- case "pi":
134
- return { model: provider.model, effort: provider.thinkingLevel };
135
- }
41
+ return getProviderRegistration(provider.kind).providerEffortAndModel(provider);
136
42
  }
137
43
  function assertSnapshotEffortRequiresModel(snapshot) {
138
44
  const fromAkuma = { model: snapshot.akuma.model, effort: snapshot.akuma.effort };
@@ -141,7 +47,8 @@ function assertSnapshotEffortRequiresModel(snapshot) {
141
47
  }
142
48
  const fromProvider = providerEffortAndModel(snapshot.provider);
143
49
  if (fromProvider.effort !== undefined && fromProvider.model === undefined) {
144
- throw snapshotError(`provider.${snapshot.provider.kind === "codex-sdk" ? "threadOptions.modelReasoningEffort" : snapshot.provider.kind === "pi" ? "thinkingLevel" : "effort"}`, "requires an explicit model");
50
+ const effortField = getProviderRegistration(snapshot.provider.kind).effortField;
51
+ throw snapshotError(`provider.${effortField}`, "requires an explicit model");
145
52
  }
146
53
  }
147
54
  function parsePolicy(value) {
@@ -184,133 +91,14 @@ function parseProviderCommon(provider) {
184
91
  source: requiredEnum(provider, "source", "provider", PROVIDER_SOURCES),
185
92
  };
186
93
  }
187
- function optionalJsonObject(provider, key) {
188
- return key in provider ? cloneJsonObject(provider[key], `provider.${key}`) : undefined;
189
- }
190
- function optionalStringRecord(provider, key) {
191
- if (!(key in provider))
192
- return undefined;
193
- const value = provider[key];
194
- if (!isPlainObject(value))
195
- throw snapshotError(`provider.${key}`, "must be a plain JSON object");
196
- const result = {};
197
- for (const [entryKey, entryValue] of Object.entries(value)) {
198
- if (typeof entryValue !== "string") {
199
- throw snapshotError(`provider.${key}.${entryKey}`, "must be a string");
200
- }
201
- result[entryKey] = entryValue;
202
- }
203
- return Object.freeze(result);
204
- }
205
- function parseSandboxPolicy(value) {
206
- const sandbox = objectAt(value, "provider.sandboxPolicy");
207
- exactKeys(sandbox, ["type", "writableRoots", "networkAccess", "excludeTmpdirEnvVar", "excludeSlashTmp"], "provider.sandboxPolicy");
208
- if (sandbox.type !== "workspaceWrite")
209
- throw snapshotError("provider.sandboxPolicy.type", "must equal 'workspaceWrite'");
210
- if (!Array.isArray(sandbox.writableRoots) || sandbox.writableRoots.length === 0) {
211
- throw snapshotError("provider.sandboxPolicy.writableRoots", "must be a nonempty array");
212
- }
213
- const roots = sandbox.writableRoots.map((root, index) => {
214
- if (typeof root !== "string" || !path.isAbsolute(root) || path.resolve(root) !== root) {
215
- throw snapshotError(`provider.sandboxPolicy.writableRoots[${index}]`, "must be a lexically normalized absolute path");
216
- }
217
- return root;
218
- });
219
- if (new Set(roots).size !== roots.length) {
220
- throw snapshotError("provider.sandboxPolicy.writableRoots", "must not contain duplicates");
221
- }
222
- for (const key of ["networkAccess", "excludeTmpdirEnvVar", "excludeSlashTmp"]) {
223
- if (typeof sandbox[key] !== "boolean")
224
- throw snapshotError(`provider.sandboxPolicy.${key}`, "must be a boolean");
225
- }
226
- Object.freeze(roots);
227
- return Object.freeze({
228
- type: "workspaceWrite",
229
- writableRoots: roots,
230
- networkAccess: sandbox.networkAccess,
231
- excludeTmpdirEnvVar: sandbox.excludeTmpdirEnvVar,
232
- excludeSlashTmp: sandbox.excludeSlashTmp,
233
- });
234
- }
235
94
  function parseProvider(value) {
236
95
  const provider = objectAt(value, "provider");
237
96
  const kind = requiredString(provider, "kind", "provider");
238
- const common = parseProviderCommon(provider);
239
- switch (kind) {
240
- case "codex-sdk": {
241
- exactKeys(provider, ["name", "source", "kind", "executable", "profile", "config", "threadOptions"], "provider");
242
- if (!("threadOptions" in provider))
243
- throw snapshotError("provider.threadOptions", "is required");
244
- const executable = optionalString(provider, "executable", "provider");
245
- const profile = optionalString(provider, "profile", "provider");
246
- const config = optionalJsonObject(provider, "config");
247
- return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(profile !== undefined ? { profile } : {}), ...(config !== undefined ? { config } : {}), threadOptions: cloneJsonObject(provider.threadOptions, "provider.threadOptions") });
248
- }
249
- case "codex-app-server": {
250
- exactKeys(provider, ["name", "source", "kind", "executable", "profile", "config", "model", "effort", "sandboxPolicy", "approvalPolicy", "env"], "provider");
251
- if ("approvalPolicy" in provider && provider.approvalPolicy !== "never")
252
- throw snapshotError("provider.approvalPolicy", "must equal 'never'");
253
- const executable = optionalString(provider, "executable", "provider");
254
- const profile = optionalString(provider, "profile", "provider");
255
- const config = optionalJsonObject(provider, "config");
256
- const model = optionalString(provider, "model", "provider");
257
- const effort = optionalString(provider, "effort", "provider");
258
- const sandboxPolicy = "sandboxPolicy" in provider ? parseSandboxPolicy(provider.sandboxPolicy) : undefined;
259
- const env = optionalStringRecord(provider, "env");
260
- return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(profile !== undefined ? { profile } : {}), ...(config !== undefined ? { config } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(sandboxPolicy !== undefined ? { sandboxPolicy } : {}), ...(provider.approvalPolicy === "never" ? { approvalPolicy: "never" } : {}), ...(env !== undefined ? { env } : {}) });
261
- }
262
- case "claude-agent-sdk": {
263
- exactKeys(provider, ["name", "source", "kind", "executable", "model", "effort", "settingSources", "permissionMode", "allowDangerouslySkipPermissions", "disallowedTools", "env"], "provider");
264
- const modes = new Set(["default", "plan", "acceptEdits", "bypassPermissions"]);
265
- const permissionMode = requiredEnum(provider, "permissionMode", "provider", modes);
266
- if (permissionMode === "bypassPermissions") {
267
- if (provider.allowDangerouslySkipPermissions !== true)
268
- throw snapshotError("provider.allowDangerouslySkipPermissions", "must be true for bypassPermissions");
269
- }
270
- else if ("allowDangerouslySkipPermissions" in provider) {
271
- throw snapshotError("provider.allowDangerouslySkipPermissions", "is only valid for bypassPermissions");
272
- }
273
- let settingSources;
274
- if ("settingSources" in provider) {
275
- if (!Array.isArray(provider.settingSources))
276
- throw snapshotError("provider.settingSources", "must be an array");
277
- settingSources = Object.freeze(provider.settingSources.map((source, index) => {
278
- if (typeof source !== "string" || !CLAUDE_SETTING_SOURCES.has(source))
279
- throw snapshotError(`provider.settingSources[${index}]`, "has an invalid setting source");
280
- return source;
281
- }));
282
- }
283
- let disallowedTools;
284
- if ("disallowedTools" in provider) {
285
- if (!Array.isArray(provider.disallowedTools) || provider.disallowedTools.length !== 1 || provider.disallowedTools[0] !== "WebSearch") {
286
- throw snapshotError("provider.disallowedTools", "must equal ['WebSearch']");
287
- }
288
- disallowedTools = Object.freeze(["WebSearch"]);
289
- }
290
- const executable = optionalString(provider, "executable", "provider");
291
- const model = optionalString(provider, "model", "provider");
292
- const effort = optionalString(provider, "effort", "provider");
293
- const env = optionalStringRecord(provider, "env");
294
- return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(settingSources !== undefined ? { settingSources } : {}), permissionMode, ...(permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}), ...(disallowedTools !== undefined ? { disallowedTools } : {}), ...(env !== undefined ? { env } : {}) });
295
- }
296
- case "opencode-sdk": {
297
- exactKeys(provider, ["name", "source", "kind", "executable", "model", "effort", "env"], "provider");
298
- const executable = optionalString(provider, "executable", "provider");
299
- const model = optionalString(provider, "model", "provider");
300
- const effort = optionalString(provider, "effort", "provider");
301
- const env = optionalStringRecord(provider, "env");
302
- return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(env !== undefined ? { env } : {}) });
303
- }
304
- case "pi": {
305
- exactKeys(provider, ["name", "source", "kind", "model", "thinkingLevel", "env"], "provider");
306
- const model = optionalString(provider, "model", "provider");
307
- const thinkingLevel = optionalString(provider, "thinkingLevel", "provider");
308
- const env = optionalStringRecord(provider, "env");
309
- return Object.freeze({ ...common, kind, ...(model !== undefined ? { model } : {}), ...(thinkingLevel !== undefined ? { thinkingLevel } : {}), ...(env !== undefined ? { env } : {}) });
310
- }
311
- default:
312
- throw snapshotError("provider.kind", `has invalid value '${kind}'`);
97
+ if (!isSubagentProvider(kind)) {
98
+ throw snapshotError("provider.kind", `has invalid value '${kind}'`);
313
99
  }
100
+ const common = parseProviderCommon(provider);
101
+ return getProviderRegistration(kind).parseLaunchProvider(provider, common);
314
102
  }
315
103
  export function parseResolvedAkumaLaunchSnapshot(value) {
316
104
  const snapshot = objectAt(value, "resolved Akuma launch snapshot");
@@ -321,7 +109,11 @@ export function parseResolvedAkumaLaunchSnapshot(value) {
321
109
  throw snapshotError("akuma", "is required");
322
110
  if (!("provider" in snapshot))
323
111
  throw snapshotError("provider", "is required");
324
- const parsed = Object.freeze({ version: 1, akuma: parseAkuma(snapshot.akuma), provider: parseProvider(snapshot.provider) });
112
+ const parsed = Object.freeze({
113
+ version: 1,
114
+ akuma: parseAkuma(snapshot.akuma),
115
+ provider: parseProvider(snapshot.provider),
116
+ });
325
117
  assertSnapshotEffortRequiresModel(parsed);
326
118
  return parsed;
327
119
  }
@@ -329,20 +121,7 @@ export function applySnapshotEffort(snapshot, effort) {
329
121
  assertEffortAllowed(effort, snapshot.akuma.effortOptions);
330
122
  assertEffortRequiresModel(snapshot.akuma.model, effort, "override");
331
123
  const akuma = { ...snapshot.akuma, effort };
332
- let provider;
333
- switch (snapshot.provider.kind) {
334
- case "codex-sdk":
335
- provider = { ...snapshot.provider, threadOptions: { ...snapshot.provider.threadOptions, modelReasoningEffort: effort } };
336
- break;
337
- case "codex-app-server":
338
- case "claude-agent-sdk":
339
- case "opencode-sdk":
340
- provider = { ...snapshot.provider, effort };
341
- break;
342
- case "pi":
343
- provider = { ...snapshot.provider, thinkingLevel: effort };
344
- break;
345
- }
124
+ const provider = getProviderRegistration(snapshot.provider.kind).applyEffort(snapshot.provider, effort);
346
125
  return parseResolvedAkumaLaunchSnapshot({ version: 1, akuma, provider });
347
126
  }
348
127
  export { assertEffortAllowed, assertEffortRequiresModel };
@@ -0,0 +1,114 @@
1
+ import * as path from "node:path";
2
+ export function snapshotError(fieldPath, message) {
3
+ return new Error(`Invalid resolved Akuma launch snapshot at ${fieldPath}: ${message}`);
4
+ }
5
+ export function isPlainObject(value) {
6
+ if (value === null || typeof value !== "object" || Array.isArray(value))
7
+ return false;
8
+ const prototype = Object.getPrototypeOf(value);
9
+ return prototype === Object.prototype || prototype === null;
10
+ }
11
+ export function objectAt(value, fieldPath) {
12
+ if (!isPlainObject(value))
13
+ throw snapshotError(fieldPath, "must be a plain JSON object");
14
+ return value;
15
+ }
16
+ export function exactKeys(value, allowed, fieldPath) {
17
+ const allowedSet = new Set(allowed);
18
+ for (const key of Object.keys(value)) {
19
+ if (!allowedSet.has(key))
20
+ throw snapshotError(`${fieldPath}.${key}`, "is an unknown field");
21
+ }
22
+ }
23
+ export function requiredString(value, key, fieldPath, allowBlank = false) {
24
+ const item = value[key];
25
+ if (typeof item !== "string" || (!allowBlank && item.trim().length === 0)) {
26
+ throw snapshotError(`${fieldPath}.${key}`, allowBlank ? "must be a string" : "must be a nonblank string");
27
+ }
28
+ return item;
29
+ }
30
+ export function optionalString(value, key, fieldPath) {
31
+ if (!(key in value))
32
+ return undefined;
33
+ return requiredString(value, key, fieldPath);
34
+ }
35
+ export function requiredEnum(value, key, fieldPath, values) {
36
+ const item = requiredString(value, key, fieldPath);
37
+ if (!values.has(item))
38
+ throw snapshotError(`${fieldPath}.${key}`, `has invalid value '${item}'`);
39
+ return item;
40
+ }
41
+ function cloneJson(value, fieldPath, ancestors = new Set()) {
42
+ if (value === null || typeof value === "string" || typeof value === "boolean")
43
+ return value;
44
+ if (typeof value === "number") {
45
+ if (!Number.isFinite(value))
46
+ throw snapshotError(fieldPath, "contains a non-finite number");
47
+ return value;
48
+ }
49
+ if (typeof value !== "object")
50
+ throw snapshotError(fieldPath, "contains a non-JSON value");
51
+ if (ancestors.has(value))
52
+ throw snapshotError(fieldPath, "contains a cycle");
53
+ ancestors.add(value);
54
+ try {
55
+ if (Array.isArray(value)) {
56
+ return Object.freeze(value.map((item, index) => cloneJson(item, `${fieldPath}[${index}]`, ancestors)));
57
+ }
58
+ if (!isPlainObject(value))
59
+ throw snapshotError(fieldPath, "contains a non-plain JSON object");
60
+ const result = {};
61
+ for (const [key, item] of Object.entries(value)) {
62
+ Object.defineProperty(result, key, {
63
+ value: cloneJson(item, `${fieldPath}.${key}`, ancestors),
64
+ enumerable: true,
65
+ configurable: true,
66
+ writable: true,
67
+ });
68
+ }
69
+ return Object.freeze(result);
70
+ }
71
+ finally {
72
+ ancestors.delete(value);
73
+ }
74
+ }
75
+ export function cloneJsonObject(value, fieldPath) {
76
+ const cloned = cloneJson(value, fieldPath);
77
+ if (!isPlainObject(cloned)) {
78
+ throw snapshotError(fieldPath, "must be a plain JSON object");
79
+ }
80
+ return cloned;
81
+ }
82
+ export function optionalJsonObject(provider, key) {
83
+ return key in provider ? cloneJsonObject(provider[key], `provider.${key}`) : undefined;
84
+ }
85
+ export function optionalStringRecord(provider, key) {
86
+ if (!(key in provider))
87
+ return undefined;
88
+ const value = provider[key];
89
+ if (!isPlainObject(value))
90
+ throw snapshotError(`provider.${key}`, "must be a plain JSON object");
91
+ const result = {};
92
+ for (const [entryKey, entryValue] of Object.entries(value)) {
93
+ if (typeof entryValue !== "string") {
94
+ throw snapshotError(`provider.${key}.${entryKey}`, "must be a string");
95
+ }
96
+ result[entryKey] = entryValue;
97
+ }
98
+ return Object.freeze(result);
99
+ }
100
+ export function parseAbsoluteRootList(value, fieldPath) {
101
+ if (!Array.isArray(value) || value.length === 0) {
102
+ throw snapshotError(fieldPath, "must be a nonempty array");
103
+ }
104
+ const roots = value.map((root, index) => {
105
+ if (typeof root !== "string" || !path.isAbsolute(root) || path.resolve(root) !== root) {
106
+ throw snapshotError(`${fieldPath}[${index}]`, "must be a lexically normalized absolute path");
107
+ }
108
+ return root;
109
+ });
110
+ if (new Set(roots).size !== roots.length) {
111
+ throw snapshotError(fieldPath, "must not contain duplicates");
112
+ }
113
+ return Object.freeze(roots);
114
+ }