@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
@@ -0,0 +1,248 @@
1
+ import * as path from "node:path";
2
+ import { z } from "zod";
3
+ import { executableSchema, isConfigKeyForbidden, providerCommonFields, providerEnvSchema, structuredTomlValueSchema, writableRootItemSchema, } from "../../../config/settings/provider-instance-fields.js";
4
+ import { exactKeys, optionalJsonObject, optionalString, optionalStringRecord, parseAbsoluteRootList, snapshotError, } from "../../launch-snapshot/parse-helpers.js";
5
+ import { SUBAGENT_SESSION_VERSION, subagentSessionEnvelopeSchema, } from "../../session.js";
6
+ import { FlowError } from "../../../flow-error.js";
7
+ import { CODEX_APP_SERVER_PROVIDER } from "./identity.js";
8
+ import { buildCodexAppServerWorkspaceWriteSandboxPolicy, configOwnsApprovalPolicy, configOwnsSandboxPolicy, } from "./policy.js";
9
+ import { codexAppServerSessionSchema } from "./session-schema.js";
10
+ const startCodexAppServer = async (prompt, cwd, options) => {
11
+ const { startCodexAppServer: start } = await import("./adapter.js");
12
+ return start(prompt, cwd, options);
13
+ };
14
+ const ACCESS_VALUES = new Set(["write"]);
15
+ const NETWORK_VALUES = new Set(["disabled", "enabled"]);
16
+ function admitResumeShape(schema, session, resumePath) {
17
+ const envelopeResult = subagentSessionEnvelopeSchema.safeParse(session);
18
+ if (!envelopeResult.success) {
19
+ throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session envelope.`);
20
+ }
21
+ if (session.provider !== CODEX_APP_SERVER_PROVIDER) {
22
+ throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' was created with provider '${session.provider}', but the selected subagent resolves to '${CODEX_APP_SERVER_PROVIDER}'. Cross-provider resume is not supported.`);
23
+ }
24
+ if (session.version !== SUBAGENT_SESSION_VERSION) {
25
+ throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' uses unsupported session version '${session.version}' for provider '${session.provider}'.`);
26
+ }
27
+ const shapeResult = schema.safeParse(session.handle);
28
+ if (!shapeResult.success) {
29
+ throw new FlowError("INVALID_RESPONSE_PATH", `Resume path file '${resumePath}' includes an invalid session handle for provider '${session.provider}'.`);
30
+ }
31
+ return shapeResult.data;
32
+ }
33
+ function parseSandboxPolicy(value) {
34
+ const sandbox = value;
35
+ if (sandbox === null || typeof sandbox !== "object" || Array.isArray(sandbox)) {
36
+ throw snapshotError("provider.sandboxPolicy", "must be a plain JSON object");
37
+ }
38
+ const record = sandbox;
39
+ exactKeys(record, ["type", "writableRoots", "networkAccess", "excludeTmpdirEnvVar", "excludeSlashTmp"], "provider.sandboxPolicy");
40
+ if (record.type !== "workspaceWrite")
41
+ throw snapshotError("provider.sandboxPolicy.type", "must equal 'workspaceWrite'");
42
+ const roots = parseAbsoluteRootList(record.writableRoots, "provider.sandboxPolicy.writableRoots");
43
+ for (const key of ["networkAccess", "excludeTmpdirEnvVar", "excludeSlashTmp"]) {
44
+ if (typeof record[key] !== "boolean")
45
+ throw snapshotError(`provider.sandboxPolicy.${key}`, "must be a boolean");
46
+ }
47
+ return Object.freeze({
48
+ type: "workspaceWrite",
49
+ writableRoots: roots,
50
+ networkAccess: record.networkAccess,
51
+ excludeTmpdirEnvVar: record.excludeTmpdirEnvVar,
52
+ excludeSlashTmp: record.excludeSlashTmp,
53
+ });
54
+ }
55
+ export const codexAppServerRegistration = {
56
+ name: CODEX_APP_SERVER_PROVIDER,
57
+ start: startCodexAppServer,
58
+ sessionSchema: codexAppServerSessionSchema,
59
+ rejectsGlobalWritableRoots: true,
60
+ effortField: "effort",
61
+ instanceSchema: z
62
+ .object({
63
+ kind: z.literal(CODEX_APP_SERVER_PROVIDER),
64
+ ...providerCommonFields,
65
+ executable: executableSchema.optional(),
66
+ profile: z.string().optional(),
67
+ config: structuredTomlValueSchema.optional(),
68
+ writableRoots: z.array(writableRootItemSchema).optional(),
69
+ env: providerEnvSchema,
70
+ })
71
+ .strict()
72
+ .superRefine((data, ctx) => {
73
+ if (data.config) {
74
+ for (const key of Object.keys(data.config)) {
75
+ if (isConfigKeyForbidden(key)) {
76
+ ctx.addIssue({
77
+ code: z.ZodIssueCode.custom,
78
+ path: ["config", key],
79
+ message: `"${key}" is owned by the Akuma or Keiyaku execution, not this provider instance`,
80
+ });
81
+ }
82
+ }
83
+ }
84
+ }),
85
+ builtinInstance: {
86
+ name: CODEX_APP_SERVER_PROVIDER,
87
+ source: "builtin",
88
+ instance: { kind: CODEX_APP_SERVER_PROVIDER, executable: "codex" },
89
+ },
90
+ builtinAkumaProfiles: [
91
+ { name: "akuma-codex", description: "Default Codex app-server Akuma." },
92
+ ],
93
+ portablePolicy: {
94
+ access: new Set(ACCESS_VALUES),
95
+ network: new Set(NETWORK_VALUES),
96
+ webSearch: new Set(),
97
+ },
98
+ maxCaptureChars: (config) => config.codex.execMaxCaptureChars,
99
+ buildCallTerms: ({ snapshot, baseTerms }) => {
100
+ const provider = snapshot.provider;
101
+ return {
102
+ ...baseTerms,
103
+ provider: CODEX_APP_SERVER_PROVIDER,
104
+ ...(provider.model !== undefined ? { model: provider.model } : {}),
105
+ ...(provider.effort !== undefined ? { effort: provider.effort } : {}),
106
+ ...(provider.profile !== undefined ? { profile: provider.profile } : {}),
107
+ ...(provider.config !== undefined ? { config: provider.config } : {}),
108
+ ...(provider.executable !== undefined ? { executable: provider.executable } : {}),
109
+ ...(provider.sandboxPolicy !== undefined
110
+ ? { sandboxPolicy: provider.sandboxPolicy }
111
+ : {}),
112
+ ...(provider.approvalPolicy !== undefined ? { approvalPolicy: provider.approvalPolicy } : {}),
113
+ ...(provider.env !== undefined ? { env: provider.env } : {}),
114
+ developerInstructions: snapshot.akuma.body || undefined,
115
+ onCheckpoint() { },
116
+ };
117
+ },
118
+ resolveLaunchProvider: ({ common, instance, model, effort, executionCwd, projectRoot, network, }) => {
119
+ if (instance.kind !== CODEX_APP_SERVER_PROVIDER) {
120
+ throw new Error(`resolveLaunchProvider expected ${CODEX_APP_SERVER_PROVIDER}`);
121
+ }
122
+ const codexInstance = instance;
123
+ const roots = [
124
+ path.resolve(executionCwd),
125
+ ...(codexInstance.writableRoots ?? []).map((root) => path.resolve(projectRoot, root)),
126
+ ];
127
+ const writableRoots = [...new Set(roots)];
128
+ return Object.freeze({
129
+ ...common,
130
+ kind: CODEX_APP_SERVER_PROVIDER,
131
+ ...(codexInstance.executable !== undefined ? { executable: codexInstance.executable } : {}),
132
+ ...(codexInstance.profile !== undefined ? { profile: codexInstance.profile } : {}),
133
+ ...(codexInstance.config !== undefined ? { config: codexInstance.config } : {}),
134
+ ...(model !== undefined ? { model } : {}),
135
+ ...(effort !== undefined ? { effort } : {}),
136
+ ...(!configOwnsSandboxPolicy(codexInstance.config)
137
+ ? {
138
+ sandboxPolicy: buildCodexAppServerWorkspaceWriteSandboxPolicy({
139
+ writableRoots,
140
+ networkAccess: network === "enabled",
141
+ }),
142
+ }
143
+ : {}),
144
+ ...(!configOwnsApprovalPolicy(codexInstance.config) ? { approvalPolicy: "never" } : {}),
145
+ ...(codexInstance.env !== undefined ? { env: codexInstance.env } : {}),
146
+ });
147
+ },
148
+ parseLaunchProvider: (provider, common) => {
149
+ exactKeys(provider, ["name", "source", "kind", "executable", "profile", "config", "model", "effort", "sandboxPolicy", "approvalPolicy", "env"], "provider");
150
+ if ("approvalPolicy" in provider && provider.approvalPolicy !== "never") {
151
+ throw snapshotError("provider.approvalPolicy", "must equal 'never'");
152
+ }
153
+ const executable = optionalString(provider, "executable", "provider");
154
+ const profile = optionalString(provider, "profile", "provider");
155
+ const config = optionalJsonObject(provider, "config");
156
+ const model = optionalString(provider, "model", "provider");
157
+ const effort = optionalString(provider, "effort", "provider");
158
+ const sandboxPolicy = "sandboxPolicy" in provider ? parseSandboxPolicy(provider.sandboxPolicy) : undefined;
159
+ const env = optionalStringRecord(provider, "env");
160
+ return Object.freeze({
161
+ ...common,
162
+ kind: CODEX_APP_SERVER_PROVIDER,
163
+ ...(executable !== undefined ? { executable } : {}),
164
+ ...(profile !== undefined ? { profile } : {}),
165
+ ...(config !== undefined ? { config } : {}),
166
+ ...(model !== undefined ? { model } : {}),
167
+ ...(effort !== undefined ? { effort } : {}),
168
+ ...(sandboxPolicy !== undefined ? { sandboxPolicy } : {}),
169
+ ...(provider.approvalPolicy === "never" ? { approvalPolicy: "never" } : {}),
170
+ ...(env !== undefined ? { env } : {}),
171
+ });
172
+ },
173
+ applyEffort: (provider, effort) => Object.freeze({ ...provider, effort }),
174
+ providerEffortAndModel: (provider) => {
175
+ const resolved = provider;
176
+ return { model: resolved.model, effort: resolved.effort };
177
+ },
178
+ attachLoader: (terms) => terms,
179
+ nativePolicyOwnership: (instanceName, instance) => {
180
+ if (instance.kind !== CODEX_APP_SERVER_PROVIDER)
181
+ return null;
182
+ const config = instance.config;
183
+ if (!config) {
184
+ return Object.freeze({
185
+ access: Object.freeze([]),
186
+ network: Object.freeze([]),
187
+ webSearch: Object.freeze([]),
188
+ });
189
+ }
190
+ const access = [];
191
+ const network = [];
192
+ const webSearch = [];
193
+ const owns = (key) => Object.prototype.hasOwnProperty.call(config, key);
194
+ const add = (target, key) => {
195
+ target.push({ path: `providers.${instanceName}.config.${key}`, value: config[key] });
196
+ };
197
+ if (owns("sandbox_mode"))
198
+ add(access, "sandbox_mode");
199
+ if (owns("approval_policy"))
200
+ add(access, "approval_policy");
201
+ for (const key of Object.keys(config)) {
202
+ if (key !== "sandbox_workspace_write" && !key.startsWith("sandbox_workspace_write."))
203
+ continue;
204
+ add(access, key);
205
+ add(network, key);
206
+ }
207
+ if (owns("web_search"))
208
+ add(webSearch, "web_search");
209
+ return Object.freeze({
210
+ access: Object.freeze(access),
211
+ network: Object.freeze(network),
212
+ webSearch: Object.freeze(webSearch),
213
+ });
214
+ },
215
+ startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onIngress, }) => {
216
+ if (terms.provider !== CODEX_APP_SERVER_PROVIDER) {
217
+ throw new Error(`startLive expected ${CODEX_APP_SERVER_PROVIDER}`);
218
+ }
219
+ const codexTerms = terms;
220
+ const options = {
221
+ model: codexTerms.model,
222
+ effort: codexTerms.effort,
223
+ profile: codexTerms.profile,
224
+ config: codexTerms.config,
225
+ sandboxPolicy: codexTerms.sandboxPolicy,
226
+ approvalPolicy: codexTerms.approvalPolicy,
227
+ executable: codexTerms.executable,
228
+ env: codexTerms.env,
229
+ signal,
230
+ developerInstructions: codexTerms.developerInstructions,
231
+ onSyncCheckpoint,
232
+ onDeliveryOpportunity,
233
+ onEvidence: codexTerms.onEvidence,
234
+ onIngress,
235
+ initialTurn: codexTerms.initialTurn,
236
+ };
237
+ const { startCodexAppServer: start } = await import("./adapter.js");
238
+ if (reviveTerms) {
239
+ const session = admitResumeShape(codexAppServerSessionSchema, reviveTerms.session, reviveTerms.resumePath);
240
+ return start(codexTerms.prompt, codexTerms.workdir, {
241
+ ...options,
242
+ threadId: session.threadId,
243
+ codexHome: session.codexHome,
244
+ });
245
+ }
246
+ return start(codexTerms.prompt, codexTerms.workdir, options);
247
+ },
248
+ };
@@ -0,0 +1,6 @@
1
+ import { z } from "zod";
2
+ /** Session handle for codex-app-server: thread id plus optional CODEX_HOME for cross-process resume. */
3
+ export const codexAppServerSessionSchema = z.object({
4
+ threadId: z.string().trim().min(1),
5
+ codexHome: z.string().trim().min(1).optional(),
6
+ });
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { emitProviderEvidence, sha256Utf8 } from "../../harness/runtime.js";
3
3
  import { coerceString } from "../../harness/runtime.js";
4
- const CODEX_APP_SERVER_PROVIDER = "codex-app-server";
4
+ import { CODEX_APP_SERVER_PROVIDER } from "./identity.js";
5
5
  export class CodexAppServerProcess {
6
6
  onEvidence;
7
7
  onEvidenceDiagnostic;
@@ -243,24 +243,30 @@ export class CodexAppServerProcess {
243
243
  // Graceful cleanup is best-effort.
244
244
  }
245
245
  }
246
- const forceKill = new Promise((resolve) => {
247
- const timer = setTimeout(() => {
248
- if (!this.exitSettled) {
249
- try {
250
- this.child.kill("SIGKILL");
251
- }
252
- catch {
253
- // Graceful escalation is best-effort.
254
- }
246
+ const forceKill = setTimeout(() => {
247
+ if (!this.exitSettled) {
248
+ try {
249
+ this.child.kill("SIGKILL");
255
250
  }
256
- resolve();
257
- }, 1000);
258
- timer.unref?.();
259
- });
260
- const hardBound = new Promise((resolve) => {
261
- const timer = setTimeout(resolve, 2000);
251
+ catch {
252
+ // The hard bound below reports the missing exit proof.
253
+ }
254
+ }
255
+ }, 1000);
256
+ forceKill.unref?.();
257
+ let hardBoundTimer;
258
+ const hardBound = new Promise((_, reject) => {
259
+ hardBoundTimer = setTimeout(() => reject(new Error(`${CODEX_APP_SERVER_PROVIDER} process exit was not observed`)), 2000);
260
+ const timer = hardBoundTimer;
262
261
  timer.unref?.();
263
262
  });
264
- await Promise.race([this.exitPromise, Promise.all([forceKill, this.exitPromise]).then(() => undefined), hardBound]);
263
+ try {
264
+ await Promise.race([this.exitPromise, hardBound]);
265
+ }
266
+ finally {
267
+ clearTimeout(forceKill);
268
+ if (hardBoundTimer)
269
+ clearTimeout(hardBoundTimer);
270
+ }
265
271
  }
266
272
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,13 @@
1
- import { z } from "zod";
2
1
  import { FlowError } from "../../../flow-error.js";
3
2
  import { appendDebugBlock, appendDebugLog } from "../../../telemetry/debug-log.js";
4
3
  import { SUBAGENT_SESSION_VERSION } from "../../session.js";
5
4
  import { createProjectionDriver } from "../../harness/execution-handle.js";
6
- import { buildSubagentEnv, coerceString, emitProviderEvidence, sha256Utf8 } from "../../harness/runtime.js";
5
+ import { createProviderFailure } from "../../harness/provider-adapter.js";
6
+ import { awaitProviderAbortSettlement, buildSubagentEnv, coerceString, createAbortError, emitProviderEvidence, sha256Utf8, } from "../../harness/runtime.js";
7
7
  import { renderEventTypeSummary } from "../event-type-tail.js";
8
8
  import { createOpencodeEventController } from "./events.js";
9
- import { OPENCODE_DEFAULT_EXECUTABLE, OPENCODE_SDK_PROVIDER, OpencodeIdleMonitor, loadOpencodeSdk, sessionIdFrom, withAbort, setOpencodeSdkLoaderForTests as setLoader, resetOpencodeSdkLoaderForTests as resetLoader, } from "./session.js";
10
- export const opencodeSessionSchema = z.object({ sessionId: z.string().trim().min(1) });
9
+ import { OPENCODE_DEFAULT_EXECUTABLE, OPENCODE_SDK_PROVIDER, OpencodeIdleMonitor, loadOpencodeSdk, sessionIdFrom, withAbort, } from "./session.js";
10
+ export { opencodeSessionSchema } from "./session-schema.js";
11
11
  export function parseOpencodeModelRef(model, effort) {
12
12
  const trimmed = model.trim();
13
13
  const slash = trimmed.indexOf("/");
@@ -20,11 +20,12 @@ export function parseOpencodeModelRef(model, effort) {
20
20
  ...(effort !== undefined ? { variant: effort } : {}),
21
21
  };
22
22
  }
23
- export function setOpencodeSdkLoaderForTests(loader) {
24
- setLoader(loader);
25
- }
26
- export function resetOpencodeSdkLoaderForTests() {
27
- resetLoader();
23
+ export async function settleOpencodeGracefulInterrupt(input) {
24
+ await input.interrupt();
25
+ await input.waitForIdle();
26
+ input.stopStream();
27
+ await input.waitForStream();
28
+ await input.drainHistory();
28
29
  }
29
30
  export async function startOpencode(prompt, cwd, options = {}) {
30
31
  const sessionId = options.sessionId?.trim();
@@ -42,6 +43,9 @@ export async function startOpencode(prompt, cwd, options = {}) {
42
43
  let enqueueFailure;
43
44
  let acceptingEnqueue = true;
44
45
  let builderRef;
46
+ let streamTask;
47
+ let gracefulInterruptRequested = false;
48
+ let gracefulProviderSettlement;
45
49
  let resolveRunSettled;
46
50
  let rejectRunSettled;
47
51
  const runSettled = new Promise((resolve, reject) => {
@@ -50,12 +54,17 @@ export async function startOpencode(prompt, cwd, options = {}) {
50
54
  });
51
55
  runSettled.catch(() => undefined);
52
56
  let eventController;
53
- const asProviderError = (error) => error instanceof Error ? error : new Error(String(error));
57
+ const asProviderError = (error) => error instanceof Error ? error : createProviderFailure(String(error));
54
58
  const rememberEnqueueFailure = (error) => {
55
59
  const failure = asProviderError(error);
56
60
  enqueueFailure ??= failure;
57
61
  return failure;
58
62
  };
63
+ const activeExecutionSignal = () => {
64
+ if (!executionSignal)
65
+ throw new Error(`${OPENCODE_SDK_PROVIDER} cannot continue: no active execution signal`);
66
+ return executionSignal;
67
+ };
59
68
  const promptSteer = async (client, text, tellId) => {
60
69
  if (options.onEvidence) {
61
70
  emitProviderEvidence(options.onEvidence, {
@@ -66,13 +75,16 @@ export async function startOpencode(prompt, cwd, options = {}) {
66
75
  ...sha256Utf8(text),
67
76
  }, (textDiagnostic) => builderRef?.emit({ type: "diagnostic", text: textDiagnostic }));
68
77
  }
69
- const receipt = await client.v2.session.prompt({
78
+ const signal = activeExecutionSignal();
79
+ const receipt = await withAbort(client.v2.session.prompt({
70
80
  sessionID: activeSessionId,
71
81
  // OpenCode documents no client-id idempotence; omit it and retain the
72
82
  // at-least-once retry window until such a contract exists.
73
83
  prompt: { text },
74
84
  delivery: "steer",
75
- }, { throwOnError: true, signal: executionSignal });
85
+ // V2 only schedules an admitted input for execution when resumed.
86
+ resume: true,
87
+ }, { throwOnError: true, signal }), signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
76
88
  const admitted = receipt.data.data;
77
89
  const ordinal = admitted.admittedSeq;
78
90
  if (options.onEvidence) {
@@ -93,7 +105,8 @@ export async function startOpencode(prompt, cwd, options = {}) {
93
105
  const applyBaseModel = async (client) => {
94
106
  if (!baseModelRef)
95
107
  return;
96
- await client.v2.session.switchModel({ sessionID: activeSessionId, model: baseModelRef }, { throwOnError: true, signal: executionSignal });
108
+ const signal = activeExecutionSignal();
109
+ await withAbort(client.v2.session.switchModel({ sessionID: activeSessionId, model: baseModelRef }, { throwOnError: true, signal }), signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
97
110
  };
98
111
  const submitTell = async (tell) => {
99
112
  if (!sdk || !activeSessionId) {
@@ -105,12 +118,12 @@ export async function startOpencode(prompt, cwd, options = {}) {
105
118
  if (!baseModelRef) {
106
119
  throw new FlowError("INVALID_EFFORT_OVERRIDE", `Effort '${tellEffort}' requires an explicit model.`);
107
120
  }
108
- const signal = executionSignal ?? new AbortController().signal;
121
+ const signal = activeExecutionSignal();
109
122
  await waitIdle();
110
- await client.v2.session.switchModel({
123
+ await withAbort(client.v2.session.switchModel({
111
124
  sessionID: activeSessionId,
112
125
  model: { providerID: baseModelRef.providerID, id: baseModelRef.id, variant: tellEffort },
113
- }, { throwOnError: true, signal });
126
+ }, { throwOnError: true, signal }), signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
114
127
  const ack = await promptSteer(client, tell.text, tell.id);
115
128
  return {
116
129
  ack,
@@ -176,29 +189,17 @@ export async function startOpencode(prompt, cwd, options = {}) {
176
189
  onIngress: options.onIngress,
177
190
  async abort(mode) {
178
191
  acceptingEnqueue = false;
179
- streamAbort?.abort();
180
192
  if (mode === "force") {
181
- const failures = [];
193
+ streamAbort?.abort();
194
+ const attempts = [
195
+ awaitProviderAbortSettlement(OPENCODE_SDK_PROVIDER, Promise.resolve().then(() => sdk?.close?.("force")), "forced server close did not settle"),
196
+ awaitProviderAbortSettlement(OPENCODE_SDK_PROVIDER, runSettled, "forced execution teardown did not settle"),
197
+ ];
182
198
  if (sdk && activeSessionId) {
183
- try {
184
- await sdk.client.v2.session.interrupt({ sessionID: activeSessionId }, { throwOnError: true });
185
- }
186
- catch (error) {
187
- failures.push(error);
188
- }
189
- }
190
- try {
191
- await sdk?.close?.("force");
192
- }
193
- catch (error) {
194
- failures.push(error);
195
- }
196
- try {
197
- await runSettled;
198
- }
199
- catch (error) {
200
- failures.push(error);
199
+ attempts.push(awaitProviderAbortSettlement(OPENCODE_SDK_PROVIDER, Promise.resolve().then(() => sdk.client.v2.session.interrupt({ sessionID: activeSessionId }, { throwOnError: true })), "forced native interrupt did not settle"));
201
200
  }
201
+ const failures = (await Promise.allSettled(attempts))
202
+ .flatMap((result) => result.status === "rejected" ? [result.reason] : []);
202
203
  if (failures.length === 1)
203
204
  throw failures[0];
204
205
  if (failures.length > 1) {
@@ -207,18 +208,40 @@ export async function startOpencode(prompt, cwd, options = {}) {
207
208
  return;
208
209
  }
209
210
  if (sdk && activeSessionId) {
210
- try {
211
- await sdk.client.v2.session.interrupt({ sessionID: activeSessionId }, { throwOnError: true });
212
- }
213
- catch {
214
- // idle interrupt may error
215
- }
211
+ gracefulInterruptRequested = true;
212
+ gracefulProviderSettlement ??= awaitProviderAbortSettlement(OPENCODE_SDK_PROVIDER, settleOpencodeGracefulInterrupt({
213
+ interrupt: () => sdk.client.v2.session.interrupt({ sessionID: activeSessionId }, { throwOnError: true }),
214
+ waitForIdle: () => {
215
+ if (!idleMonitor)
216
+ throw new Error(`${OPENCODE_SDK_PROVIDER} graceful abort lacked idle monitor`);
217
+ return idleMonitor.waitForIdle();
218
+ },
219
+ // Native inactivity is terminal authority. Only then stop the
220
+ // local stream and drain all durable history admitted so far.
221
+ stopStream: () => {
222
+ if (!streamAbort)
223
+ throw new Error(`${OPENCODE_SDK_PROVIDER} graceful abort lacked event stream`);
224
+ streamAbort.abort();
225
+ },
226
+ waitForStream: () => {
227
+ if (!streamTask)
228
+ throw new Error(`${OPENCODE_SDK_PROVIDER} graceful abort lacked stream task`);
229
+ return streamTask;
230
+ },
231
+ drainHistory: () => {
232
+ if (!eventController)
233
+ throw new Error(`${OPENCODE_SDK_PROVIDER} graceful abort lacked event history controller`);
234
+ return eventController.drainHistory(eventController.lastSeenSeq());
235
+ },
236
+ }));
237
+ await gracefulProviderSettlement;
216
238
  }
217
239
  },
218
240
  async run(builder) {
219
241
  builderRef = builder;
220
242
  streamAbort = new AbortController();
221
243
  const combined = new AbortController();
244
+ const streamSignal = AbortSignal.any([combined.signal, streamAbort.signal]);
222
245
  executionSignal = combined.signal;
223
246
  const forward = () => combined.abort();
224
247
  builder.signal.addEventListener("abort", forward, { once: true });
@@ -228,7 +251,7 @@ export async function startOpencode(prompt, cwd, options = {}) {
228
251
  const control = { enqueue };
229
252
  try {
230
253
  const env = buildSubagentEnv(options.env);
231
- sdk = await withAbort(loadOpencodeSdk(executable, cwd, env, combined.signal), combined.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
254
+ sdk = await withAbort(loadOpencodeSdk(executable, cwd, env, combined.signal, options.loader), combined.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
232
255
  const sessionResponse = sessionId
233
256
  ? await withAbort(sdk.client.v2.session.get({ sessionID: sessionId }, { throwOnError: true, signal: combined.signal }), combined.signal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`)
234
257
  : await withAbort(sdk.client.v2.session.create({
@@ -239,13 +262,14 @@ export async function startOpencode(prompt, cwd, options = {}) {
239
262
  if (!activeSessionId) {
240
263
  const message = `${OPENCODE_SDK_PROVIDER} exec could not determine session id`;
241
264
  builder.emit({ type: "fail", message });
242
- throw new Error(message);
265
+ throw createProviderFailure(message);
243
266
  }
244
267
  idleMonitor = new OpencodeIdleMonitor(sdk.client.v2.session, activeSessionId, combined.signal);
245
268
  eventController = createOpencodeEventController({
246
269
  builder,
247
270
  control,
248
271
  idleMonitorIngress: () => idleMonitor?.ingress(),
272
+ idleMonitorTerminal: () => idleMonitor?.terminal(),
249
273
  onEvidence: options.onEvidence,
250
274
  onSyncCheckpoint: options.onSyncCheckpoint,
251
275
  onDeliveryOpportunity: options.onDeliveryOpportunity,
@@ -259,23 +283,40 @@ export async function startOpencode(prompt, cwd, options = {}) {
259
283
  if (sessionId && baseModelRef) {
260
284
  await applyBaseModel(sdk.client);
261
285
  }
262
- const eventsResult = await sdk.client.v2.session.events({ sessionID: activeSessionId }, { signal: streamAbort.signal });
263
- const streamTask = (async () => {
286
+ const eventsResultTask = withAbort(sdk.client.v2.session.events({ sessionID: activeSessionId }, { signal: streamSignal }), streamSignal, `${OPENCODE_SDK_PROVIDER} event stream cancelled by client`);
287
+ streamTask = (async () => {
264
288
  try {
265
- for await (const item of eventsResult.stream) {
266
- if (streamAbort.signal.aborted || combined.signal.aborted)
289
+ const eventsResult = await eventsResultTask;
290
+ const iterator = eventsResult.stream[Symbol.asyncIterator]();
291
+ for (;;) {
292
+ const next = await withAbort(iterator.next(), streamSignal, `${OPENCODE_SDK_PROVIDER} event stream cancelled by client`);
293
+ if (next.done || streamSignal.aborted)
267
294
  break;
295
+ const item = next.value;
268
296
  eventController.processUnknownStreamItem(item);
269
297
  }
270
298
  }
271
299
  catch (error) {
272
- if (!streamAbort.signal.aborted && !combined.signal.aborted)
300
+ if (!streamSignal.aborted)
273
301
  throw error;
274
302
  }
275
303
  })();
304
+ streamTask.catch(() => undefined);
305
+ // Preserve provider sequencing: establish the stream before prompting.
306
+ // The acquisition itself lives inside streamTask, so local shutdown can
307
+ // always settle it even when the SDK never resolves events().
308
+ await eventsResultTask;
276
309
  await promptSteer(sdk.client, prompt);
277
310
  await waitIdle();
311
+ if (gracefulInterruptRequested) {
312
+ await gracefulProviderSettlement;
313
+ throw createAbortError(`${OPENCODE_SDK_PROVIDER} session interrupted`);
314
+ }
278
315
  await drainEnqueueTail();
316
+ if (gracefulInterruptRequested) {
317
+ await gracefulProviderSettlement;
318
+ throw createAbortError(`${OPENCODE_SDK_PROVIDER} session interrupted`);
319
+ }
279
320
  streamAbort.abort();
280
321
  await streamTask;
281
322
  await eventController.drainHistory(eventController.lastSeenSeq());
@@ -290,7 +331,7 @@ export async function startOpencode(prompt, cwd, options = {}) {
290
331
  const message = coerceString(finalAssistant.error.message) ?? `${OPENCODE_SDK_PROVIDER} assistant error`;
291
332
  builder.emit({ type: "diagnostic", text: message });
292
333
  builder.emit({ type: "fail", message });
293
- throw new Error(message);
334
+ throw createProviderFailure(message);
294
335
  }
295
336
  if (finalMessage) {
296
337
  builder.emit({
@@ -326,6 +367,7 @@ export async function startOpencode(prompt, cwd, options = {}) {
326
367
  idleMonitor?.dispose();
327
368
  idleMonitor = undefined;
328
369
  executionSignal = undefined;
370
+ streamTask = undefined;
329
371
  try {
330
372
  await sdk?.close?.();
331
373
  }
@@ -342,3 +384,5 @@ export async function startOpencode(prompt, cwd, options = {}) {
342
384
  },
343
385
  });
344
386
  }
387
+ // Compiler-level binding to the named ProviderAdapter function contract.
388
+ startOpencode;
@@ -1,8 +1,9 @@
1
1
  import { activityCallFromNamedTool } from "../../harness/activity-values.js";
2
2
  import { AGENT_THOUGHT_TEXT_MAX_CHARS, } from "../../harness/events.js";
3
+ import { createProviderFailure } from "../../harness/provider-adapter.js";
3
4
  import { coerceString, emitProviderEvidence } from "../../harness/runtime.js";
4
5
  import { appendEventType } from "../event-type-tail.js";
5
- import { OPENCODE_SDK_PROVIDER } from "./session.js";
6
+ import { OPENCODE_SDK_PROVIDER, withAbort } from "./session.js";
6
7
  const OPENCODE_EVENT_PROMPTED = "session.next.prompted";
7
8
  function isSessionDurableEvent(value) {
8
9
  return Boolean(value
@@ -116,7 +117,7 @@ function extractFinalAssistantMessage(messages) {
116
117
  return messages.find((message) => message.type === "assistant");
117
118
  }
118
119
  export function createOpencodeEventController(options) {
119
- const { builder, control, idleMonitorIngress, onEvidence, onSyncCheckpoint, onDeliveryOpportunity, getSessionId, client, } = options;
120
+ const { builder, control, idleMonitorIngress, idleMonitorTerminal, onEvidence, onSyncCheckpoint, onDeliveryOpportunity, getSessionId, client, } = options;
120
121
  const recordedEventTypes = [];
121
122
  let totalEventCount = 0;
122
123
  let currentTurnAdmitted = false;
@@ -186,6 +187,7 @@ export function createOpencodeEventController(options) {
186
187
  });
187
188
  }
188
189
  clearLastTurnFailureOnTerminalEvidence();
190
+ idleMonitorTerminal();
189
191
  break;
190
192
  }
191
193
  case "session.next.reasoning.ended": {
@@ -263,15 +265,17 @@ export function createOpencodeEventController(options) {
263
265
  outputTokens: tokens.output,
264
266
  });
265
267
  clearLastTurnFailureOnTerminalEvidence();
268
+ idleMonitorTerminal();
266
269
  break;
267
270
  }
268
271
  case "session.next.step.failed": {
269
272
  const message = coerceString(event.data.error?.message) ?? `${OPENCODE_SDK_PROVIDER} step failed`;
270
- const failure = new Error(message);
273
+ const failure = createProviderFailure(message);
271
274
  currentTurnFailure = failure;
272
275
  lastTurnFailure = failure;
273
276
  builder.emit({ type: "diagnostic", text: message });
274
277
  builder.emit({ type: "fail", message });
278
+ idleMonitorTerminal();
275
279
  break;
276
280
  }
277
281
  default:
@@ -317,7 +321,7 @@ export function createOpencodeEventController(options) {
317
321
  let cursor = after;
318
322
  for (;;) {
319
323
  const priorCursor = cursor;
320
- const page = await client.v2.session.history({ sessionID: getSessionId(), after: cursor }, { throwOnError: true, signal: options.executionSignal });
324
+ const page = await withAbort(client.v2.session.history({ sessionID: getSessionId(), after: cursor }, { throwOnError: true, signal: options.executionSignal }), options.executionSignal, `${OPENCODE_SDK_PROVIDER} exec cancelled by client`);
321
325
  const body = page.data;
322
326
  const events = body.data ?? [];
323
327
  for (const event of events) {