@astrosheep/keiyaku 2.9.2 → 2.9.4

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 (238) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/call-terms.js +13 -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 +25 -222
  6. package/build/agents/harness/index.js +3 -1
  7. package/build/agents/harness/outcome.js +47 -4
  8. package/build/agents/harness/projection.js +42 -59
  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 +50 -53
  21. package/build/agents/providers/codex-app-server/events.js +67 -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/registration.js +145 -0
  36. package/build/agents/providers/pi/session-schema.js +6 -0
  37. package/build/agents/providers/pi/terms.js +1 -0
  38. package/build/agents/providers/registration.js +7 -0
  39. package/build/agents/providers/registry.js +76 -0
  40. package/build/agents/selector.js +2 -29
  41. package/build/agents/types.js +8 -0
  42. package/build/artifact-directories.js +22 -0
  43. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  44. package/build/cli/commands/akuma/view/handler.js +5 -4
  45. package/build/cli/commands/akuma.js +6 -0
  46. package/build/cli/commands/contract/amend/handler.js +8 -2
  47. package/build/cli/commands/contract/arc/handler.js +7 -1
  48. package/build/cli/commands/contract/bind/handler.js +4 -3
  49. package/build/cli/commands/contract/bind/meta.js +34 -7
  50. package/build/cli/commands/contract/forfeit/handler.js +4 -4
  51. package/build/cli/commands/contract/log/handler.js +3 -3
  52. package/build/cli/commands/contract/petition/handler.js +4 -6
  53. package/build/cli/commands/contract/renew/handler.js +20 -6
  54. package/build/cli/commands/contract/renew/meta.js +5 -4
  55. package/build/cli/commands/metadata.js +58 -0
  56. package/build/cli/commands/projection/call/handler.js +6 -5
  57. package/build/cli/commands/projection/kill/handler.js +16 -32
  58. package/build/cli/commands/projection/shared.js +28 -0
  59. package/build/cli/commands/projection/status/handler.js +1 -1
  60. package/build/cli/commands/projection/tell/handler.js +14 -30
  61. package/build/cli/commands/projection/wait/handler.js +45 -22
  62. package/build/cli/commands/projection/wait/meta.js +7 -2
  63. package/build/cli/commands/registry.js +2 -54
  64. package/build/cli/commands/shared.js +10 -7
  65. package/build/cli/commands/skills/install/handler.js +2 -5
  66. package/build/cli/commands/skills/skills/handler.js +1 -1
  67. package/build/cli/commands/task/add/handler.js +5 -13
  68. package/build/cli/commands/task/catalog.js +2 -2
  69. package/build/cli/commands/task/done/handler.js +4 -8
  70. package/build/cli/commands/task/drop/handler.js +3 -6
  71. package/build/cli/commands/task/ls/handler.js +3 -5
  72. package/build/cli/commands/task/shared.js +14 -4
  73. package/build/cli/commands/task/{view → show}/handler.js +7 -8
  74. package/build/cli/commands/task/show/meta.js +8 -0
  75. package/build/cli/commands/task/start/handler.js +3 -6
  76. package/build/cli/commands/task/stop/handler.js +4 -8
  77. package/build/cli/commands/task/task/handler.js +1 -1
  78. package/build/cli/commands/task/update/handler.js +4 -8
  79. package/build/cli/completion.js +1 -1
  80. package/build/cli/flags.js +19 -3
  81. package/build/cli/help.js +4 -11
  82. package/build/cli/index.js +57 -83
  83. package/build/cli/missing-subcommand.js +7 -0
  84. package/build/cli/parse-flags.js +14 -5
  85. package/build/cli/parse-metadata.js +3 -3
  86. package/build/cli/parse-selectors.js +1 -1
  87. package/build/cli/parse.js +21 -0
  88. package/build/cli/projection-address.js +7 -12
  89. package/build/cli/render/address.js +41 -37
  90. package/build/cli/render/arc.js +24 -12
  91. package/build/cli/render/call.js +11 -5
  92. package/build/cli/render/errors.js +36 -1
  93. package/build/cli/render/format.js +4 -0
  94. package/build/cli/render/kanshi.js +6 -12
  95. package/build/cli/render/kill.js +9 -0
  96. package/build/cli/render/misc.js +55 -12
  97. package/build/cli/render/petition.js +20 -15
  98. package/build/cli/render/projection-activity.js +3 -0
  99. package/build/cli/render/shared.js +60 -21
  100. package/build/cli/render/skills.js +10 -0
  101. package/build/cli/render/status.js +37 -14
  102. package/build/cli/render/success-response.js +77 -0
  103. package/build/cli/render/task.js +54 -14
  104. package/build/cli/render/tell.js +5 -0
  105. package/build/cli/render/terminal-failure.js +58 -0
  106. package/build/cli/render/types.js +1 -0
  107. package/build/cli/render/wait.js +38 -36
  108. package/build/cli/types.js +1 -1
  109. package/build/config/akuma-loader.js +0 -5
  110. package/build/config/env-keys.js +1 -1
  111. package/build/config/provider-policy-ownership.js +3 -32
  112. package/build/config/settings/disease.js +8 -1
  113. package/build/config/settings/loader.js +11 -3
  114. package/build/config/settings/provider-instance-fields.js +51 -0
  115. package/build/config/settings/schema.js +6 -176
  116. package/build/core/addressing.js +66 -16
  117. package/build/core/amend.js +135 -18
  118. package/build/core/arc.js +19 -67
  119. package/build/core/bind.js +82 -35
  120. package/build/core/call/call.js +15 -9
  121. package/build/core/call/context.js +6 -51
  122. package/build/core/call/execution.js +21 -22
  123. package/build/core/call/prompt.js +14 -1
  124. package/build/core/command-markdown.js +45 -0
  125. package/build/core/context.js +13 -7
  126. package/build/core/contract-view.js +20 -3
  127. package/build/core/draft.js +123 -16
  128. package/build/core/forfeit.js +5 -2
  129. package/build/core/hints.js +1 -75
  130. package/build/core/identifier-slug.js +66 -0
  131. package/build/core/ids.js +25 -11
  132. package/build/core/ledger.js +20 -68
  133. package/build/core/markdown/parser.js +0 -68
  134. package/build/core/markdown/render.js +68 -0
  135. package/build/core/markdown/sections.js +2 -1
  136. package/build/core/outcome-base.js +4 -0
  137. package/build/core/projection/generation/database.js +2 -38
  138. package/build/core/projection/generation/model.js +42 -1
  139. package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
  140. package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +19 -49
  141. package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
  142. package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +5 -3
  143. package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
  144. package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +18 -3
  145. package/build/core/projection/generation/store.js +2 -2
  146. package/build/core/projection/generation/transitions.js +1 -1
  147. package/build/core/projection/index.js +27 -0
  148. package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
  149. package/build/core/{projection-alias.js → projection/projection-alias.js} +14 -19
  150. package/build/core/projection/projection-coordinate.js +61 -0
  151. package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
  152. package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +2 -57
  153. package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
  154. package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
  155. package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +1 -1
  156. package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
  157. package/build/core/{projection-status.js → projection/projection-status.js} +98 -25
  158. package/build/core/projection/projection-terminal-failure.js +104 -0
  159. package/build/core/projection/projection-wait.js +278 -0
  160. package/build/core/projection/projection-wake.js +203 -0
  161. package/build/core/provider-loader-ports.js +4 -0
  162. package/build/core/ref-log.js +65 -40
  163. package/build/core/registry.js +63 -10
  164. package/build/core/renew-session.js +128 -0
  165. package/build/core/renew.js +426 -99
  166. package/build/core/scope.js +143 -66
  167. package/build/core/seal.js +139 -35
  168. package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +9 -14
  169. package/build/core/{claim.js → settlement/claim.js} +32 -8
  170. package/build/core/settlement/index.js +19 -0
  171. package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +22 -18
  172. package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
  173. package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
  174. package/build/core/settlement/petition-io.js +1 -0
  175. package/build/core/{petition-preview.js → settlement/petition-preview.js} +1 -1
  176. package/build/core/{petition.js → settlement/petition.js} +12 -10
  177. package/build/core/settlement/queue-read-model.js +143 -0
  178. package/build/core/settlement/queue-seat-allocation.js +156 -0
  179. package/build/core/settlement/queue.js +6 -0
  180. package/build/core/settlement/settlement.js +295 -0
  181. package/build/core/{verdict.js → settlement/verdict.js} +9 -37
  182. package/build/core/status/board.js +24 -23
  183. package/build/core/status/ledger-batch.js +158 -0
  184. package/build/core/status/reconciliation.js +10 -4
  185. package/build/core/stored-agent-event.js +3 -0
  186. package/build/core/task/board.js +40 -4
  187. package/build/core/task/commands.js +30 -21
  188. package/build/core/task/index.js +12 -0
  189. package/build/core/task/settlement-git.js +157 -65
  190. package/build/core/task/settlement-policy.js +54 -24
  191. package/build/core/task/task-contract.js +370 -0
  192. package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
  193. package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
  194. package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
  195. package/build/core/{task-git-store.js → task/task-git-store.js} +92 -29
  196. package/build/core/task/task-repository.js +1 -0
  197. package/build/core/{task-store-repository.js → task/task-store-repository.js} +5 -4
  198. package/build/core/task/task-store.js +1 -0
  199. package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
  200. package/build/core/task/task.js +5 -0
  201. package/build/core/task/tree.js +37 -0
  202. package/build/core/task/validation.js +5 -2
  203. package/build/core/transcripts.js +15 -3
  204. package/build/core/worktree-path.js +43 -28
  205. package/build/flow-error.js +85 -39
  206. package/build/fs/durability.js +27 -0
  207. package/build/fs/path-coordinate.js +16 -0
  208. package/build/generated/version.js +3 -2
  209. package/build/git/branches.js +2 -1
  210. package/build/git/core.js +106 -2
  211. package/build/git/path-coordinate.js +17 -0
  212. package/build/git/refs.js +57 -0
  213. package/build/git/streaming-batch.js +301 -0
  214. package/build/git/worktree.js +65 -0
  215. package/build/index.js +3 -3
  216. package/build/keiyaku.js +2 -36
  217. package/package.json +4 -10
  218. package/skills/keiyaku/SKILL.md +3 -2
  219. package/skills/keiyaku-akuma/SKILL.md +10 -6
  220. package/skills/keiyaku-task/SKILL.md +1 -1
  221. package/skills/keiyaku-workflow/SKILL.md +25 -4
  222. package/build/agents/providers/codex-sdk.js +0 -329
  223. package/build/cli/commands/task/view/meta.js +0 -8
  224. package/build/core/command-io.js +0 -174
  225. package/build/core/path-coordinate.js +0 -27
  226. package/build/core/petition-claim.js +0 -117
  227. package/build/core/petition-run.js +0 -83
  228. package/build/core/projection-coordinate.js +0 -63
  229. package/build/core/projection-wait.js +0 -165
  230. package/build/core/projection-wake.js +0 -136
  231. package/build/core/queue.js +0 -343
  232. package/build/core/task-contract.js +0 -237
  233. package/build/core/task.js +0 -4
  234. /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
  235. /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
  236. /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
  237. /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
  238. /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
@@ -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
+ }
@@ -1,6 +1,4 @@
1
- import * as path from "node:path";
2
- import { nativePolicyOwnership } from "../../config/provider-policy-ownership.js";
3
- import { buildCodexAppServerWorkspaceWriteSandboxPolicy, configOwnsApprovalPolicy, configOwnsSandboxPolicy, } from "../providers/codex-app-server/adapter.js";
1
+ import { getProviderRegistration } from "../providers/registry.js";
4
2
  import { assertEffortAllowed, assertEffortRequiresModel, parseResolvedAkumaLaunchSnapshot, } from "./model.js";
5
3
  export function resolveAkumaLaunchSnapshot(selected, input) {
6
4
  const model = input.model ?? selected.profile.model;
@@ -28,45 +26,21 @@ export function resolveAkumaLaunchSnapshot(selected, input) {
28
26
  policy,
29
27
  };
30
28
  const instance = selected.profile.providerInstance;
31
- const common = { name: selected.profile.provider, source: selected.profile.providerSource, kind: instance.kind };
32
- let provider;
33
- switch (instance.kind) {
34
- case "codex-sdk": {
35
- const threadOptions = { ...(instance.threadOptions ?? {}) };
36
- const ownership = nativePolicyOwnership(selected.profile.provider, instance);
37
- if (ownership.access.length === 0) {
38
- threadOptions.sandboxMode = selected.profile.access === "read" ? "read-only" : selected.profile.access === "auto" ? "danger-full-access" : "workspace-write";
39
- }
40
- if (ownership.network.length === 0)
41
- threadOptions.networkAccessEnabled = selected.profile.network === "enabled";
42
- if (ownership.webSearch.length === 0)
43
- threadOptions.webSearchMode = selected.profile.webSearch ?? "disabled";
44
- if (model !== undefined)
45
- threadOptions.model = model;
46
- if (effort !== undefined)
47
- threadOptions.modelReasoningEffort = effort;
48
- threadOptions.workingDirectory = input.executionCwd;
49
- threadOptions.skipGitRepoCheck = true;
50
- provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(instance.profile !== undefined ? { profile: instance.profile } : {}), ...(instance.config !== undefined ? { config: instance.config } : {}), threadOptions };
51
- break;
52
- }
53
- case "codex-app-server": {
54
- const roots = [path.resolve(input.executionCwd), ...(instance.writableRoots ?? []).map((root) => path.resolve(input.projectRoot, root))];
55
- const writableRoots = [...new Set(roots)];
56
- provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(instance.profile !== undefined ? { profile: instance.profile } : {}), ...(instance.config !== undefined ? { config: instance.config } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(!configOwnsSandboxPolicy(instance.config) ? { sandboxPolicy: buildCodexAppServerWorkspaceWriteSandboxPolicy({ writableRoots, networkAccess: selected.profile.network === "enabled" }) } : {}), ...(!configOwnsApprovalPolicy(instance.config) ? { approvalPolicy: "never" } : {}), ...(instance.env !== undefined ? { env: instance.env } : {}) };
57
- break;
58
- }
59
- case "claude-agent-sdk": {
60
- const permissionMode = selected.profile.access === "read" ? "plan" : selected.profile.access === "write" ? "acceptEdits" : selected.profile.access === "auto" ? "bypassPermissions" : "default";
61
- provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(instance.settingSources !== undefined ? { settingSources: instance.settingSources } : {}), permissionMode, ...(permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}), ...(selected.profile.webSearch === "disabled" ? { disallowedTools: ["WebSearch"] } : {}), ...(instance.env !== undefined ? { env: instance.env } : {}) };
62
- break;
63
- }
64
- case "opencode-sdk":
65
- provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(instance.env !== undefined ? { env: instance.env } : {}) };
66
- break;
67
- case "pi":
68
- provider = { ...common, ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { thinkingLevel: effort } : {}), ...(instance.env !== undefined ? { env: instance.env } : {}) };
69
- break;
70
- }
29
+ const common = {
30
+ name: selected.profile.provider,
31
+ source: selected.profile.providerSource,
32
+ };
33
+ const registration = getProviderRegistration(instance.kind);
34
+ const provider = registration.resolveLaunchProvider({
35
+ common,
36
+ instance,
37
+ model,
38
+ effort,
39
+ executionCwd: input.executionCwd,
40
+ projectRoot: input.projectRoot,
41
+ access: selected.profile.access,
42
+ network: selected.profile.network,
43
+ webSearch: selected.profile.webSearch,
44
+ });
71
45
  return parseResolvedAkumaLaunchSnapshot({ version: 1, akuma, provider });
72
46
  }
@@ -7,6 +7,7 @@ const DEFAULT_HOSTNAME = "127.0.0.1";
7
7
  const DEFAULT_STARTUP_TIMEOUT_MS = 10_000;
8
8
  const SERVER_READY_POLL_INTERVAL_MS = 100;
9
9
  const SERVER_SHUTDOWN_FORCE_KILL_MS = 1000;
10
+ const SERVER_SHUTDOWN_HARD_TIMEOUT_MS = 2000;
10
11
  const OPENCODE_SERVE_SUBCOMMAND = "serve";
11
12
  const OPENCODE_HOSTNAME_FLAG = "--hostname";
12
13
  const OPENCODE_PORT_FLAG = "--port";
@@ -33,7 +34,10 @@ async function waitForServerReady(client, directory, timeoutMs, signal) {
33
34
  if (signal?.aborted)
34
35
  throw createAbortError("OpenCode server startup aborted");
35
36
  try {
36
- await client.config.get({ directory }, { throwOnError: true });
37
+ // Legacy config endpoints become available before the runtime model
38
+ // catalog. Prompting in that window is admitted, then fails asynchronously
39
+ // with ModelUnavailableError and no terminal event.
40
+ await client.v2.model.list({ location: { directory } }, { throwOnError: true });
37
41
  return;
38
42
  }
39
43
  catch {
@@ -67,13 +71,23 @@ export async function createOpencodeServer(options = {}) {
67
71
  return;
68
72
  if (mode === "force") {
69
73
  forceStop ??= new Promise((resolve, reject) => {
70
- child.once("close", resolve);
74
+ let timer;
75
+ const finish = (error) => {
76
+ if (timer)
77
+ clearTimeout(timer);
78
+ error === undefined ? resolve() : reject(error);
79
+ };
80
+ child.once("close", () => finish());
81
+ timer = setTimeout(() => {
82
+ finish(new Error("OpenCode server forced process exit was not observed"));
83
+ }, SERVER_SHUTDOWN_HARD_TIMEOUT_MS);
84
+ timer.unref?.();
71
85
  try {
72
86
  if (!child.kill("SIGKILL") && !exited)
73
- reject(new Error("OpenCode server force kill failed"));
87
+ finish(new Error("OpenCode server force kill failed"));
74
88
  }
75
89
  catch (error) {
76
- reject(error);
90
+ finish(error);
77
91
  }
78
92
  });
79
93
  await forceStop;
@@ -83,24 +97,43 @@ export async function createOpencodeServer(options = {}) {
83
97
  await forceStop;
84
98
  return;
85
99
  }
86
- gracefulStop ??= new Promise((resolve) => {
100
+ gracefulStop ??= new Promise((resolve, reject) => {
87
101
  let settled = false;
88
102
  let forceKill;
89
- const finish = () => {
103
+ let hardTimeout;
104
+ const finish = (error) => {
90
105
  if (settled)
91
106
  return;
92
107
  settled = true;
93
108
  if (forceKill)
94
109
  clearTimeout(forceKill);
95
- resolve();
110
+ if (hardTimeout)
111
+ clearTimeout(hardTimeout);
112
+ error === undefined ? resolve() : reject(error);
96
113
  };
97
- child.once("close", finish);
98
- child.kill("SIGTERM");
114
+ child.once("close", () => finish());
115
+ try {
116
+ child.kill("SIGTERM");
117
+ }
118
+ catch (error) {
119
+ finish(error);
120
+ return;
121
+ }
99
122
  forceKill = setTimeout(() => {
100
- if (!exited)
101
- child.kill("SIGKILL");
123
+ if (!exited) {
124
+ try {
125
+ child.kill("SIGKILL");
126
+ }
127
+ catch (error) {
128
+ finish(error);
129
+ }
130
+ }
102
131
  }, SERVER_SHUTDOWN_FORCE_KILL_MS);
103
- forceKill.unref();
132
+ forceKill.unref?.();
133
+ hardTimeout = setTimeout(() => {
134
+ finish(new Error("OpenCode server process exit was not observed"));
135
+ }, SERVER_SHUTDOWN_HARD_TIMEOUT_MS);
136
+ hardTimeout.unref?.();
104
137
  });
105
138
  await gracefulStop;
106
139
  };
@@ -1,33 +1,20 @@
1
- import { z } from "zod";
2
1
  import { appendDebugLog } from "../../../telemetry/debug-log.js";
3
2
  import { SUBAGENT_SESSION_VERSION } from "../../session.js";
4
3
  import { createProjectionDriver } from "../../harness/execution-handle.js";
4
+ import { createProviderFailure } from "../../harness/provider-adapter.js";
5
5
  import { buildSubagentEnv, createAbortError, emitProviderEvidence, linkAbortSignal, sha256Utf8 } from "../../harness/runtime.js";
6
- import { SubagentFailure } from "../../types.js";
7
6
  import { claudeIngressDisposition, emitClaudeThoughts, emitClaudeToolResults, emitClaudeToolStarts, emitClaudeUsage, extractAssistantMessage, extractAssistantMessageId, extractAssistantMessageModel, extractOverloadFallbackWarning, extractResultText, extractResumeToken, isClaudeApiRetryEvent, isClaudeModelRefusalFallbackEvent, isClaudeModelRefusalNoFallbackEvent, mapClaudeApiRetryWarning, } from "./events.js";
8
7
  import { CLAUDE_AGENT_SDK_PROVIDER, createPushableUserMessageStream, toClaudeUserMessage, } from "./session.js";
9
8
  function toClaudeSdkEffort(effort) {
10
9
  return effort; // opacity boundary
11
10
  }
12
- export const claudeSessionSchema = z.object({ sessionId: z.string().trim().min(1) });
13
- let claudeAgentSdkLoader;
11
+ export { claudeSessionSchema } from "./session-schema.js";
14
12
  async function loadClaudeAgentSdk(loader) {
15
13
  if (loader) {
16
14
  return loader();
17
15
  }
18
- if (claudeAgentSdkLoader) {
19
- return claudeAgentSdkLoader();
20
- }
21
16
  return (await import("@anthropic-ai/claude-agent-sdk"));
22
17
  }
23
- /** @deprecated Use ClaudeProviderOptions.loader instead. Kept for pipeline-based tests pending loader channel. */
24
- export function setClaudeAgentSdkLoaderForTests(loader) {
25
- claudeAgentSdkLoader = loader;
26
- }
27
- /** @deprecated Use ClaudeProviderOptions.loader instead. */
28
- export function resetClaudeAgentSdkLoaderForTests() {
29
- claudeAgentSdkLoader = undefined;
30
- }
31
18
  export async function startClaudeAgentSdk(prompt, cwd, options) {
32
19
  const sessionId = options.sessionId?.trim();
33
20
  let liveQuery;
@@ -314,7 +301,7 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
314
301
  const failCode = unavailable ? "SUBAGENT_PROVIDER_UNAVAILABLE" : "SUBAGENT_EXEC_ERROR";
315
302
  builder.emit({ type: "fail", message, code: failCode });
316
303
  inputStream?.end();
317
- throw new SubagentFailure(failCode, message);
304
+ throw createProviderFailure(message, { code: failCode });
318
305
  }
319
306
  default:
320
307
  break;
@@ -327,7 +314,7 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
327
314
  if (!resumedSessionId) {
328
315
  const message = `${CLAUDE_AGENT_SDK_PROVIDER} exec could not determine session id`;
329
316
  builder.emit({ type: "fail", message });
330
- throw new Error(message);
317
+ throw createProviderFailure(message);
331
318
  }
332
319
  const session = {
333
320
  provider: "claude-agent-sdk",
@@ -366,3 +353,5 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
366
353
  },
367
354
  });
368
355
  }
356
+ // Compiler-level binding to the named ProviderAdapter function contract.
357
+ startClaudeAgentSdk;