@gajae-code/coding-agent 0.18.12 → 0.19.0

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 (305) hide show
  1. package/CHANGELOG.md +467 -0
  2. package/dist/types/capability/rule.d.ts +7 -2
  3. package/dist/types/cli/append-system-prompt.d.ts +6 -0
  4. package/dist/types/cli/args.d.ts +7 -2
  5. package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
  6. package/dist/types/cli/customize-doctor.d.ts +4 -3
  7. package/dist/types/cli/public-command-errors.d.ts +1 -0
  8. package/dist/types/cli/root-flags.d.ts +0 -12
  9. package/dist/types/cli/update-cli.d.ts +7 -2
  10. package/dist/types/cli-main.d.ts +0 -6
  11. package/dist/types/commands/launch.d.ts +0 -6
  12. package/dist/types/commands/migrate.d.ts +1 -1
  13. package/dist/types/commands/sdk.d.ts +8 -0
  14. package/dist/types/commit/map-reduce/index.d.ts +4 -4
  15. package/dist/types/config/autorouting-tier-map.d.ts +23 -1
  16. package/dist/types/config/config-hot-reload.d.ts +5 -0
  17. package/dist/types/config/file-lock.d.ts +44 -2
  18. package/dist/types/config/model-profile-activation.d.ts +19 -5
  19. package/dist/types/config/model-registry.d.ts +16 -3
  20. package/dist/types/config/model-resolver.d.ts +5 -4
  21. package/dist/types/config/settings-schema.d.ts +11 -1
  22. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  23. package/dist/types/customization/import.d.ts +2 -0
  24. package/dist/types/customization/types.d.ts +1 -1
  25. package/dist/types/dap/session.d.ts +11 -2
  26. package/dist/types/discovery/helpers.d.ts +7 -2
  27. package/dist/types/edit/index.d.ts +5 -0
  28. package/dist/types/edit/modes/patch.d.ts +9 -0
  29. package/dist/types/eval/py/runtime.d.ts +9 -0
  30. package/dist/types/export/ttsr.d.ts +19 -0
  31. package/dist/types/extensibility/extensions/types.d.ts +5 -1
  32. package/dist/types/extensibility/slash-commands.d.ts +1 -0
  33. package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
  34. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
  36. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
  37. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  38. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  39. package/dist/types/hindsight/content.d.ts +5 -6
  40. package/dist/types/internal-urls/types.d.ts +2 -0
  41. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
  42. package/dist/types/main.d.ts +23 -18
  43. package/dist/types/migrate/action-planner.d.ts +10 -1
  44. package/dist/types/migrate/adapters/codex.d.ts +1 -1
  45. package/dist/types/migrate/adapters/index.d.ts +35 -3
  46. package/dist/types/migrate/hook-module.d.ts +23 -0
  47. package/dist/types/migrate/report.d.ts +2 -1
  48. package/dist/types/migrate/types.d.ts +69 -2
  49. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  50. package/dist/types/modes/components/model-selector.d.ts +43 -0
  51. package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
  52. package/dist/types/modes/utils/host-status.d.ts +15 -11
  53. package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
  54. package/dist/types/sdk/broker/broker-exit.d.ts +1 -1
  55. package/dist/types/sdk/broker/broker.d.ts +22 -1
  56. package/dist/types/sdk/broker/ensure.d.ts +5 -2
  57. package/dist/types/sdk/broker/hop.d.ts +9 -0
  58. package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
  59. package/dist/types/sdk/broker/session-index.d.ts +2 -0
  60. package/dist/types/sdk/broker/transport.d.ts +2 -0
  61. package/dist/types/sdk/bus/index.d.ts +21 -0
  62. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
  63. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  64. package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
  65. package/dist/types/sdk/host/control/operations.d.ts +5 -1
  66. package/dist/types/sdk/host/events.d.ts +4 -0
  67. package/dist/types/sdk/host/host.d.ts +2 -1
  68. package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
  69. package/dist/types/sdk/host/session-runtime.d.ts +8 -8
  70. package/dist/types/sdk/host/surface-policy.d.ts +2 -1
  71. package/dist/types/sdk/lifecycle/service.d.ts +12 -0
  72. package/dist/types/sdk/prompt-status.d.ts +11 -4
  73. package/dist/types/session/agent-session.d.ts +50 -8
  74. package/dist/types/session/session-manager.d.ts +16 -0
  75. package/dist/types/skill-state/workflow-mutation-guard.d.ts +13 -1
  76. package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
  77. package/dist/types/ssh/ssh-executor.d.ts +8 -1
  78. package/dist/types/system-prompt.d.ts +3 -2
  79. package/dist/types/task/role-body.d.ts +11 -0
  80. package/dist/types/tools/awaiting-input.d.ts +28 -0
  81. package/dist/types/tools/bash-allowed-prefixes.d.ts +6 -0
  82. package/dist/types/tools/bash-skill-urls.d.ts +4 -0
  83. package/dist/types/tools/bash.d.ts +7 -0
  84. package/dist/types/tools/descriptors.d.ts +13 -2
  85. package/dist/types/tools/fetch.d.ts +6 -0
  86. package/dist/types/tools/irc.d.ts +5 -3
  87. package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
  88. package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
  89. package/dist/types/tools/write.d.ts +2 -0
  90. package/dist/types/utils/file-mentions.d.ts +5 -0
  91. package/dist/types/utils/image-limits.d.ts +7 -0
  92. package/dist/types/utils/image-loading.d.ts +0 -1
  93. package/dist/types/utils/open.d.ts +0 -1
  94. package/dist/types/utils/pasted-image-loading.d.ts +1 -5
  95. package/dist/types/utils/prompt-suggestion.d.ts +2 -2
  96. package/dist/types/utils/shell-snapshot.d.ts +2 -0
  97. package/dist/types/utils/title-generator.d.ts +13 -3
  98. package/dist/types/web/search/providers/codex.d.ts +6 -7
  99. package/package.json +9 -8
  100. package/scripts/acp-conformance-agent.ts +12 -2
  101. package/scripts/generate-sdk-operation-inventory.ts +16 -0
  102. package/src/capability/fs.ts +31 -3
  103. package/src/capability/rule.ts +11 -2
  104. package/src/cli/append-system-prompt.ts +12 -0
  105. package/src/cli/args.ts +11 -7
  106. package/src/cli/customize-doctor-precedence.ts +31 -0
  107. package/src/cli/customize-doctor.ts +49 -39
  108. package/src/cli/fast-help.ts +0 -2
  109. package/src/cli/migrate-cli.ts +14 -3
  110. package/src/cli/public-command-errors.ts +2 -0
  111. package/src/cli/root-flags.ts +0 -2
  112. package/src/cli/update-cli.ts +247 -6
  113. package/src/commands/harness.ts +25 -15
  114. package/src/commands/migrate.ts +3 -3
  115. package/src/commands/sdk.ts +81 -11
  116. package/src/commit/agentic/agent.ts +6 -6
  117. package/src/commit/analysis/conventional.ts +14 -5
  118. package/src/commit/map-reduce/index.ts +7 -7
  119. package/src/commit/pipeline.ts +12 -12
  120. package/src/config/autorouting-tier-map.ts +20 -1
  121. package/src/config/config-file.ts +106 -3
  122. package/src/config/config-hot-reload.ts +8 -0
  123. package/src/config/file-lock.ts +1431 -304
  124. package/src/config/keybindings.ts +154 -7
  125. package/src/config/model-profile-activation.ts +177 -39
  126. package/src/config/model-registry.ts +380 -55
  127. package/src/config/model-resolver.ts +49 -21
  128. package/src/config/settings-schema.ts +13 -1
  129. package/src/config/settings.ts +2 -0
  130. package/src/coordinator-mcp/server.ts +37 -2
  131. package/src/crash/sanitize.ts +3 -1
  132. package/src/customization/import.ts +276 -7
  133. package/src/customization/inventory.ts +24 -8
  134. package/src/customization/types.ts +1 -0
  135. package/src/dap/client.ts +3 -2
  136. package/src/dap/session.ts +161 -36
  137. package/src/debug/crash-diagnostics.ts +87 -5
  138. package/src/debug/report-bundle.ts +71 -2
  139. package/src/discovery/builtin.ts +4 -3
  140. package/src/discovery/helpers.ts +24 -14
  141. package/src/edit/index.ts +191 -5
  142. package/src/edit/modes/patch.ts +27 -0
  143. package/src/eval/py/runtime.ts +44 -4
  144. package/src/exec/non-interactive-env.ts +0 -1
  145. package/src/export/html/index.ts +60 -20
  146. package/src/export/ttsr.ts +176 -1
  147. package/src/extensibility/extensions/loader.ts +2 -0
  148. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
  149. package/src/extensibility/extensions/types.ts +5 -1
  150. package/src/extensibility/hooks/loader.ts +10 -2
  151. package/src/extensibility/plugins/marketplace/manager.ts +36 -10
  152. package/src/extensibility/slash-commands.ts +5 -0
  153. package/src/gjc-runtime/gc-runtime.ts +4 -1
  154. package/src/gjc-runtime/launch-tmux.ts +23 -13
  155. package/src/gjc-runtime/ralplan-current-final.ts +294 -0
  156. package/src/gjc-runtime/ralplan-runtime.ts +95 -1
  157. package/src/gjc-runtime/state-writer.ts +109 -5
  158. package/src/gjc-runtime/tmux-sessions.ts +93 -13
  159. package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
  160. package/src/gjc-runtime/ultragoal-guard.ts +9 -1
  161. package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
  162. package/src/harness-control-plane/finalize.ts +11 -0
  163. package/src/harness-control-plane/receipt-spool.ts +105 -28
  164. package/src/harness-control-plane/storage.ts +124 -6
  165. package/src/hindsight/content.ts +116 -16
  166. package/src/hindsight/mental-models.ts +22 -10
  167. package/src/hindsight/state.ts +7 -4
  168. package/src/hooks/native-skill-hook.ts +3 -1
  169. package/src/internal-urls/artifact-protocol.ts +3 -1
  170. package/src/internal-urls/docs-index.generated.ts +14 -14
  171. package/src/internal-urls/local-protocol.ts +28 -3
  172. package/src/internal-urls/types.ts +2 -0
  173. package/src/lsp/client.ts +2 -1
  174. package/src/lsp/clients/biome-client.ts +2 -0
  175. package/src/lsp/clients/swiftlint-client.ts +2 -0
  176. package/src/main.ts +330 -92
  177. package/src/memories/index.ts +25 -2
  178. package/src/migrate/action-planner.ts +517 -2
  179. package/src/migrate/adapters/claude-code.ts +141 -3
  180. package/src/migrate/adapters/codex.ts +57 -5
  181. package/src/migrate/adapters/index.ts +240 -5
  182. package/src/migrate/adapters/opencode.ts +4 -0
  183. package/src/migrate/executor.ts +141 -3
  184. package/src/migrate/hook-module.ts +193 -0
  185. package/src/migrate/report.ts +45 -1
  186. package/src/migrate/types.ts +73 -3
  187. package/src/modes/acp/acp-agent.ts +10 -1
  188. package/src/modes/components/agent-dashboard.ts +20 -4
  189. package/src/modes/components/customization/customization-dashboard.ts +7 -0
  190. package/src/modes/components/customization/import-wizard.ts +29 -3
  191. package/src/modes/components/model-selector.ts +157 -103
  192. package/src/modes/components/welcome.ts +3 -1
  193. package/src/modes/controllers/command-controller.ts +271 -16
  194. package/src/modes/controllers/event-controller.ts +1 -1
  195. package/src/modes/controllers/extension-ui-controller.ts +31 -1
  196. package/src/modes/controllers/goal-mode-controller.ts +79 -10
  197. package/src/modes/controllers/input-controller.ts +2 -0
  198. package/src/modes/controllers/plan-mode-controller.ts +49 -4
  199. package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
  200. package/src/modes/controllers/selector-controller.ts +14 -1
  201. package/src/modes/interactive-mode.ts +55 -4
  202. package/src/modes/prompt-suggestion-controller.ts +1 -1
  203. package/src/modes/runtime-init.ts +5 -1
  204. package/src/modes/utils/host-status.ts +21 -10
  205. package/src/prompts/agent-fragments/browser-aside.md +2 -2
  206. package/src/prompts/system/irc-incoming.md +3 -1
  207. package/src/prompts/tools/irc.md +1 -1
  208. package/src/runtime-mcp/manager.ts +8 -3
  209. package/src/runtime-mcp/project-host-values.ts +12 -8
  210. package/src/runtime-mcp/tool-bridge.ts +68 -4
  211. package/src/runtime-mcp/transports/stdio.ts +2 -1
  212. package/src/sdk/broker/broker-exit.ts +2 -0
  213. package/src/sdk/broker/broker.ts +143 -4
  214. package/src/sdk/broker/ensure.ts +74 -6
  215. package/src/sdk/broker/hop.ts +13 -1
  216. package/src/sdk/broker/lifecycle-ledger.ts +3 -0
  217. package/src/sdk/broker/lifecycle.ts +102 -6
  218. package/src/sdk/broker/session-index.ts +29 -4
  219. package/src/sdk/broker/transport.ts +17 -1
  220. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  221. package/src/sdk/bus/index.ts +408 -14
  222. package/src/sdk/bus/session-control.ts +2 -0
  223. package/src/sdk/bus/slack-daemon.ts +105 -9
  224. package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
  225. package/src/sdk/bus/telegram-daemon.ts +10 -2
  226. package/src/sdk/bus/threaded-render.ts +20 -1
  227. package/src/sdk/cli/session-cli.ts +2 -0
  228. package/src/sdk/host/control/dispatch.ts +13 -1
  229. package/src/sdk/host/control/operations.ts +10 -1
  230. package/src/sdk/host/events.ts +67 -4
  231. package/src/sdk/host/host.ts +8 -2
  232. package/src/sdk/host/prompt-image-upload.ts +438 -0
  233. package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
  234. package/src/sdk/host/session-runtime.test.ts +1537 -24
  235. package/src/sdk/host/session-runtime.ts +332 -36
  236. package/src/sdk/host/surface-policy.ts +4 -0
  237. package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
  238. package/src/sdk/host/websocket-transport.ts +19 -7
  239. package/src/sdk/lifecycle/service.ts +14 -1
  240. package/src/sdk/prompt-status.ts +6 -4
  241. package/src/sdk/protocol/adapter-validation.ts +28 -1
  242. package/src/sdk/protocol/operation-inventory.generated.json +160 -0
  243. package/src/sdk/protocol/operation-registry.ts +33 -3
  244. package/src/sdk/session.ts +123 -36
  245. package/src/session/agent-session.ts +1614 -329
  246. package/src/session/history-storage.ts +182 -12
  247. package/src/session/internal/managed-session-scope.ts +47 -18
  248. package/src/session/internal/managed-session-storage.ts +71 -1
  249. package/src/session/session-manager.ts +90 -5
  250. package/src/session-import/redact.ts +53 -2
  251. package/src/skill-state/workflow-mutation-guard.ts +1498 -18
  252. package/src/slash-commands/builtin-registry.ts +6 -3
  253. package/src/slash-commands/helpers/mcp.ts +11 -3
  254. package/src/ssh/ssh-executor.ts +9 -2
  255. package/src/stt/recorder.ts +26 -9
  256. package/src/system-prompt.ts +61 -16
  257. package/src/task/executor.ts +12 -8
  258. package/src/task/role-body.ts +84 -0
  259. package/src/task/token-log-mode.test.ts +162 -0
  260. package/src/task/token-log.ts +91 -2
  261. package/src/tools/archive-reader.ts +650 -37
  262. package/src/tools/ask.ts +1 -0
  263. package/src/tools/ast-grep.ts +7 -2
  264. package/src/tools/auto-generated-guard.ts +12 -3
  265. package/src/tools/awaiting-input.ts +114 -0
  266. package/src/tools/bash-allowed-prefixes.ts +61 -1
  267. package/src/tools/bash-skill-urls.ts +58 -5
  268. package/src/tools/bash.ts +99 -26
  269. package/src/tools/browser/tab-worker.ts +39 -13
  270. package/src/tools/browser.ts +17 -7
  271. package/src/tools/cron.ts +10 -1
  272. package/src/tools/debug.ts +9 -2
  273. package/src/tools/descriptors.test.ts +4 -4
  274. package/src/tools/descriptors.ts +16 -4
  275. package/src/tools/eval.ts +15 -0
  276. package/src/tools/fetch.ts +90 -2
  277. package/src/tools/gh.ts +27 -12
  278. package/src/tools/image-gen.ts +20 -6
  279. package/src/tools/irc.ts +5 -3
  280. package/src/tools/monitor.ts +9 -1
  281. package/src/tools/output-meta.ts +51 -3
  282. package/src/tools/read.ts +9 -2
  283. package/src/tools/recipe/runner.ts +23 -12
  284. package/src/tools/render-utils.ts +2 -2
  285. package/src/tools/search.ts +3 -2
  286. package/src/tools/ssh.ts +274 -8
  287. package/src/tools/subagent.ts +10 -4
  288. package/src/tools/tmux-self-injection-guard.ts +3301 -122
  289. package/src/tools/tool-catalog.generated.ts +1 -1
  290. package/src/tools/ultragoal-ask-guard.ts +13 -1
  291. package/src/tools/write.ts +7 -0
  292. package/src/tui/tree-list.ts +15 -1
  293. package/src/utils/commit-message-generator.ts +3 -2
  294. package/src/utils/external-editor.ts +21 -7
  295. package/src/utils/file-mentions.ts +46 -6
  296. package/src/utils/git.ts +38 -7
  297. package/src/utils/image-limits.ts +8 -0
  298. package/src/utils/image-loading.ts +1 -1
  299. package/src/utils/open.ts +73 -4
  300. package/src/utils/pasted-image-loading.ts +9 -8
  301. package/src/utils/prompt-suggestion.ts +5 -4
  302. package/src/utils/shell-snapshot.ts +9 -1
  303. package/src/utils/title-generator.ts +65 -20
  304. package/src/web/search/index.ts +2 -1
  305. package/src/web/search/providers/codex.ts +29 -32
@@ -4,7 +4,12 @@ import * as crypto from "node:crypto";
4
4
  import * as fs from "node:fs/promises";
5
5
  import * as path from "node:path";
6
6
  import { promisify } from "node:util";
7
- import { isContinuingMidRunMaintenanceOutcome, isNonDispatchedToolEvent, ThinkingLevel } from "@gajae-code/agent-core";
7
+ import {
8
+ isContinuingMidRunMaintenanceOutcome,
9
+ isNonDispatchedToolEvent,
10
+ type RunSettlementProof,
11
+ ThinkingLevel,
12
+ } from "@gajae-code/agent-core";
8
13
  import type { AttemptScope } from "@gajae-code/agent-core/attempt-scope";
9
14
  import type { Api, ImageContent, Model, ProviderDiagnostic } from "@gajae-code/ai/core";
10
15
 
@@ -39,7 +44,7 @@ import { tagSdkLifecycleObserver } from "../../extensibility/extensions/function
39
44
  import type { AgentEndEvent } from "../../extensibility/shared-events";
40
45
  import { normalizeGoal } from "../../goals/state";
41
46
  import { toAgentWireEventPayload } from "../../modes/shared/agent-wire/event-envelope";
42
- import type { AgentSessionEvent } from "../../session/agent-session";
47
+ import type { AgentSessionEvent, QueuedRunStartFailure } from "../../session/agent-session";
43
48
  import type { SdkRunCapability } from "../../session/sdk-run-capability";
44
49
  import {
45
50
  boundTerminalRetentionState,
@@ -125,6 +130,7 @@ import {
125
130
  TURN_STREAM_CAPABILITY,
126
131
  } from "./host";
127
132
  import { clearAutoroutingInactive, isAutoroutingInactive, markAutoroutingInactive } from "./internal-autorouting-state";
133
+ import { PromptImageUploadStore, retirePromptImageReservation } from "./prompt-image-upload";
128
134
  import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "./query";
129
135
  import { createSdkRunCapability } from "./sdk-run-capability";
130
136
  import {
@@ -350,6 +356,8 @@ export interface SessionSdkTransport {
350
356
  stop(): Promise<void>;
351
357
  broadcastFrame?(frame: SdkFrame): void;
352
358
  onConnectionClose?(handler: (connectionId: string) => void): undefined | (() => void);
359
+ /** Authoritative live socket membership, not a cache of previously observed frames. */
360
+ isConnectionOpen?(connectionId: string): boolean;
353
361
  onNegotiatedCapabilities?(
354
362
  handler: (connectionId: string, capabilities: readonly string[]) => void,
355
363
  ): undefined | (() => void);
@@ -357,6 +365,7 @@ export interface SessionSdkTransport {
357
365
 
358
366
  export interface SessionSdkRuntimeOptions
359
367
  extends Omit<SessionSdkHostOptions, "sessionId" | "stateRoot" | "token" | "sendFrame" | "onFrame"> {
368
+ onDisconnected?: (connectionId: string) => void;
360
369
  transport: SessionSdkTransport;
361
370
  /** Session settings; enables `config.patch` application on this runtime. */
362
371
  settings?: Settings;
@@ -443,6 +452,8 @@ export interface SdkOnlyReconciliationStore {
443
452
  }
444
453
 
445
454
  export interface SdkOnlyTerminalAbortSeams {
455
+ /** Definitive event-driven observation: never abort, seal or quarantine a run to reclaim images. */
456
+ waitForRunSettlement?: (handle: string) => Promise<RunSettlementProof>;
446
457
  getReconciliationStore?: () => SdkOnlyReconciliationStore | undefined;
447
458
  getTerminalTurnEpoch: () => number | undefined;
448
459
  getActivePromptHandle: () => string | undefined;
@@ -521,6 +532,10 @@ export interface SdkOnlyDeadlineRecoveryCheckpoint {
521
532
  * dispatch, replay/event publication, and reverse-provider lifecycle without
522
533
  * importing any notification adapter or native notification class.
523
534
  */
535
+ function isStringArray(value: unknown): value is string[] {
536
+ return Array.isArray(value) && value.every((item: unknown) => typeof item === "string");
537
+ }
538
+
524
539
  export class SessionSdkSessionRuntime {
525
540
  readonly host: SessionSdkHost;
526
541
  readonly transport: SessionSdkTransport;
@@ -551,18 +566,32 @@ export class SessionSdkSessionRuntime {
551
566
  onFrame: handler =>
552
567
  options.transport.onFrame((connectionId, frame) => {
553
568
  this.#connectionIds.add(connectionId);
569
+ if (
570
+ !options.connectionCapabilities &&
571
+ frame.type === "event_replay" &&
572
+ isStringArray(frame.capabilities)
573
+ ) {
574
+ this.#connectionCapabilities.set(
575
+ connectionId,
576
+ new Set([...(this.#connectionCapabilities.get(connectionId) ?? []), ...frame.capabilities]),
577
+ );
578
+ }
554
579
  handler(connectionId, frame);
555
580
  }),
556
581
  });
557
582
  this.#connectionDisposer = options.transport.onConnectionClose?.(connectionId => {
558
583
  this.#connectionIds.delete(connectionId);
559
584
  this.#connectionCapabilities.delete(connectionId);
585
+ options.onDisconnected?.(connectionId);
560
586
  this.host.handleDisconnect(connectionId);
561
587
  });
562
588
  this.#capabilitiesDisposer = options.transport.onNegotiatedCapabilities?.((connectionId, negotiated) => {
563
589
  if (options.connectionCapabilities && this.#connectionCapabilitiesProvider(connectionId) === undefined) return;
564
590
  this.#connectionIds.add(connectionId);
565
- this.#connectionCapabilities.set(connectionId, new Set(negotiated));
591
+ this.#connectionCapabilities.set(
592
+ connectionId,
593
+ new Set([...(this.#connectionCapabilities.get(connectionId) ?? []), ...negotiated]),
594
+ );
566
595
  });
567
596
  this.#malformedDisposer = options.transport.onMalformedFrame?.((connectionId, message) => {
568
597
  this.host.handleMalformedFrame(connectionId, message);
@@ -2215,6 +2244,8 @@ export interface SdkSurfaceFactoryOptions {
2215
2244
  api: ExtensionAPI;
2216
2245
  /** Startup surface that owns host-level permission and lifecycle authority. */
2217
2246
  primaryControlSurface?: "cli" | "sdk";
2247
+ /** Private staging controls are advertised only with authoritative transport liveness. */
2248
+ imageUploadsAvailable?: boolean;
2218
2249
  policy?: SdkSurfacePolicy;
2219
2250
  getInstalledDefinitions?: (capability: string) => unknown | undefined;
2220
2251
  getLiveState?: () => { isStreaming: boolean; steeringQueueDepth: number; followupQueueDepth: number };
@@ -2298,7 +2329,8 @@ function createQuerySurface(
2298
2329
  };
2299
2330
  const getProfileCredentialSessionId = () => ctx.credentialSessionId ?? id;
2300
2331
  const profileSettings = (options.settings ?? ctx.settings) as Pick<Settings, "get"> | undefined;
2301
- const getProfileAvailableModels = (): Model<Api>[] => ctx.modelRegistry.getAvailable();
2332
+ const getProfileAvailableModels = (): Model<Api>[] =>
2333
+ ctx.modelRegistry.getAvailable(getProfileCredentialSessionId());
2302
2334
  const resolveProfileAvailability = async (
2303
2335
  profile: ModelProfileDefinition,
2304
2336
  authenticatedProviders: ReadonlySet<string>,
@@ -2519,7 +2551,7 @@ function createQuerySurface(
2519
2551
  getDiff,
2520
2552
  getUsage: () => ctx.sessionManager.getUsageStatistics(),
2521
2553
  getModels: async () => {
2522
- const models = ctx.modelRegistry.getAll();
2554
+ const models = ctx.modelRegistry.getAll(getProfileCredentialSessionId());
2523
2555
  const currentModel = ctx.model;
2524
2556
  const currentThinkingLevel = api.getThinkingLevel();
2525
2557
  const activeProfile =
@@ -2642,7 +2674,9 @@ function createQuerySurface(
2642
2674
  getBranchCandidates: () => ctx.getBranchCandidates(),
2643
2675
  getLastAssistant: lastAssistant,
2644
2676
  getCapabilities: () => createSdkCapabilities(policy, hasHostTools(), options.primaryControlSurface),
2645
- getAuthProviders: () => [...new Set(ctx.modelRegistry.getAll().map(model => model.provider))],
2677
+ getAuthProviders: () => [
2678
+ ...new Set(ctx.modelRegistry.getAll(getProfileCredentialSessionId()).map(model => model.provider)),
2679
+ ],
2646
2680
  getActiveProviders: () => ctx.modelRegistry.getActiveProviders(),
2647
2681
  getTools: () => {
2648
2682
  const tools = typeof (ctx as Partial<ExtensionContext>).getAllTools === "function" ? ctx.getAllTools() : [];
@@ -2720,7 +2754,11 @@ export function createSdkSurfaceFactory(
2720
2754
  ): SdkSurfaceFactory {
2721
2755
  const policy =
2722
2756
  options.policy ??
2723
- createSdkSurfacePolicyForContext(options.ctx, hasSdkWorkflowGateCapability(options.ctx.workflowGate));
2757
+ createSdkSurfacePolicyForContext(
2758
+ options.ctx,
2759
+ hasSdkWorkflowGateCapability(options.ctx.workflowGate),
2760
+ options.imageUploadsAvailable,
2761
+ );
2724
2762
  const reconciliation =
2725
2763
  options.reconciliation ??
2726
2764
  createInvocationReconciliation({
@@ -3106,6 +3144,7 @@ function createControlSurface(
3106
3144
  ) => void,
3107
3145
  armPromptDeadline: (correlation: InvocationCorrelation) => void,
3108
3146
  steerReconciliation: KindAwareReconciliation,
3147
+ imageUploads: PromptImageUploadStore | undefined,
3109
3148
  onPromotedTurn?: (
3110
3149
  kind: InvocationKind,
3111
3150
  correlation: InvocationCorrelation,
@@ -3133,7 +3172,15 @@ function createControlSurface(
3133
3172
  trackGateResolution: <T>(resolution: Promise<T>) => Promise<T> = async resolution => await resolution,
3134
3173
  onInvocationCompletionReconciledForTests?: (kind: InvocationKind, correlation: InvocationCorrelation) => void,
3135
3174
  publishLifecycleFrame?: (frame: SdkFrame) => void,
3175
+ retainAcceptedImage?: (correlation: InvocationCorrelation, release: () => void) => void,
3176
+ settleAcceptedImageSubmission?: (correlation: InvocationCorrelation) => void,
3177
+ releaseAcceptedImage?: (correlation: InvocationCorrelation) => void,
3136
3178
  acceptedQueueCancellations: Map<string, AcceptedQueueCancellation> = new Map(),
3179
+ onQueuedRunStartFailed?: (
3180
+ kind: InvocationKind,
3181
+ correlation: InvocationCorrelation,
3182
+ failure: QueuedRunStartFailure,
3183
+ ) => boolean,
3137
3184
  ): ControlSurface {
3138
3185
  const normalizePromptImages = (value: unknown): ImageContent[] => {
3139
3186
  if (!Array.isArray(value)) return [];
@@ -3154,6 +3201,7 @@ function createControlSurface(
3154
3201
  type InternalSendOptions = NonNullable<Parameters<ExtensionAPI["sendUserMessage"]>[1]> & {
3155
3202
  sdkRunCapability?: SdkRunCapability;
3156
3203
  expectedSdkRunToken?: string;
3204
+ onQueuedRunStartFailed?: (failure: QueuedRunStartFailure) => void;
3157
3205
  };
3158
3206
  type InternalSdkApi = Omit<ExtensionAPI, "sendUserMessage"> & {
3159
3207
  sendUserMessage: (
@@ -3164,7 +3212,7 @@ function createControlSurface(
3164
3212
  const internalApi = api as InternalSdkApi;
3165
3213
  const sendSdkUserMessage = (
3166
3214
  content: Parameters<ExtensionAPI["sendUserMessage"]>[0],
3167
- options?: Record<string, unknown>,
3215
+ options?: InternalSendOptions,
3168
3216
  ): Promise<void> => internalApi.sendUserMessage(content, options);
3169
3217
  const surfacePolicy =
3170
3218
  policy ?? createSdkSurfacePolicyForContext(ctx, hasSdkWorkflowGateCapability(ctx.workflowGate));
@@ -3174,8 +3222,14 @@ function createControlSurface(
3174
3222
  const [provider, ...modelId] = id.split("/");
3175
3223
  const model =
3176
3224
  modelId.length > 0
3177
- ? ctx.modelRegistry.find(provider, modelId.join("/"))
3178
- : ctx.modelRegistry.getAll().find(candidate => candidate.id === id);
3225
+ ? ctx.modelRegistry.find(
3226
+ provider,
3227
+ modelId.join("/"),
3228
+ ctx.credentialSessionId ?? ctx.sessionManager.getSessionId(),
3229
+ )
3230
+ : ctx.modelRegistry
3231
+ .getAll(ctx.credentialSessionId ?? ctx.sessionManager.getSessionId())
3232
+ .find(candidate => candidate.id === id);
3179
3233
  if (!model) throw Object.assign(new Error(`Model ${id} was not found.`), { code: "invalid_input" });
3180
3234
  return model;
3181
3235
  };
@@ -3301,11 +3355,13 @@ function createControlSurface(
3301
3355
  onDispatchDisposition: (promotion: { startsOwnRun: boolean }) => void;
3302
3356
  /** Fired when a queued submission (steering or follow-up) is promoted to its own run (SDK ownership correlation). */
3303
3357
  onQueuedPromoted: (promotion?: { startsOwnRun?: boolean; removed?: boolean }) => void;
3358
+ onQueuedRunStartFailed: (failure: QueuedRunStartFailure) => void;
3304
3359
  queuedAtDispatch: boolean;
3305
3360
  }) => Promise<unknown>,
3306
3361
  acceptedFields?: () => Record<string, unknown>,
3307
3362
  allowCompletionFallback = false,
3308
3363
  alwaysQueued = false,
3364
+ onCreated?: (correlation: InvocationCorrelation) => void,
3309
3365
  ): Promise<unknown> => {
3310
3366
  // Capture the REQUESTING connection at admission: a terminal abort from
3311
3367
  // another SDK connection must never stop the prompt this one accepts
@@ -3314,9 +3370,11 @@ function createControlSurface(
3314
3370
  const retainedClientRef = normalizeClientRef(clientRef);
3315
3371
  reconciliation.admit(kind, retainedClientRef);
3316
3372
  const correlation = newCorrelation();
3373
+ onCreated?.(correlation);
3317
3374
  const sdkRunToken = `${correlation.commandId}:${correlation.turnId}`;
3318
3375
  const sdkRunCapability = createSdkRunCapability(sdkRunToken);
3319
3376
  const publishTerminal = (outcome: InvocationOutcome): void => {
3377
+ releaseAcceptedImage?.(correlation);
3320
3378
  retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
3321
3379
  publishLifecycleFrame?.({
3322
3380
  type: "agent_end",
@@ -3350,6 +3408,7 @@ function createControlSurface(
3350
3408
  };
3351
3409
  let accepted = false;
3352
3410
  let settled = false;
3411
+ let submission: Promise<unknown> | undefined;
3353
3412
  const cancelPreflight = () => {
3354
3413
  preflightController.abort();
3355
3414
  if (settled) return;
@@ -3409,14 +3468,16 @@ function createControlSurface(
3409
3468
  if (kind === "prompt")
3410
3469
  acceptedQueueCancellations.set(`${correlation.commandId}:${correlation.turnId}`, queueCancellation);
3411
3470
  let promotionStartsOwnRun: boolean | undefined;
3471
+ let queuedRunStartRefused = false;
3412
3472
  try {
3413
- const submission = Promise.resolve(
3473
+ submission = Promise.resolve(
3414
3474
  run({
3415
3475
  onPreflightAccepted: () => void accept().catch(() => undefined),
3416
3476
  preflightSignal: preflightController.signal,
3417
3477
  sdkRunCapability,
3418
3478
  onPreflightAcceptCommit: accept,
3419
3479
  onDispatchDisposition: promotion => {
3480
+ if (queuedRunStartRefused) return;
3420
3481
  promotionStartsOwnRun = promotion.startsOwnRun;
3421
3482
  if (promotion.startsOwnRun === false) {
3422
3483
  queueCancellation.queueCandidate = true;
@@ -3436,6 +3497,7 @@ function createControlSurface(
3436
3497
  // created at promotion so the submitting connection can
3437
3498
  // terminal-abort that turn (review threads P1/P2).
3438
3499
  onQueuedPromoted: (promotion?: { startsOwnRun?: boolean; removed?: boolean }) => {
3500
+ if (queuedRunStartRefused) return;
3439
3501
  promotionStartsOwnRun = promotion?.startsOwnRun;
3440
3502
  if (promotion?.removed) {
3441
3503
  queueCancellation.disposition = "removed";
@@ -3469,11 +3531,29 @@ function createControlSurface(
3469
3531
  }
3470
3532
  void onPromotedTurn?.(kind, correlation, requesterConnectionId, sdkRunToken, promotion);
3471
3533
  },
3534
+ onQueuedRunStartFailed: failure => {
3535
+ if (
3536
+ queuedRunStartRefused ||
3537
+ !accepted ||
3538
+ !queueCancellation.queueCandidate ||
3539
+ failure.disposition !== "not-started"
3540
+ )
3541
+ return;
3542
+ // Only the private Core decision for this selected queue entry is proof.
3543
+ // The lifecycle owner rejects contradictory decisions after adoption.
3544
+ queuedRunStartRefused = onQueuedRunStartFailed?.(kind, correlation, failure) === true;
3545
+ if (queuedRunStartRefused) {
3546
+ queueCancellation.disposition = "consumed";
3547
+ queueCancellation.resolveDisposition("consumed");
3548
+ }
3549
+ },
3472
3550
  queuedAtDispatch,
3473
3551
  }),
3474
3552
  );
3475
3553
  void submission.then(
3476
3554
  result => {
3555
+ settleAcceptedImageSubmission?.(correlation);
3556
+ if (queuedRunStartRefused) return;
3477
3557
  if (settled) {
3478
3558
  // A resolved submission after preflight acceptance means the work is over
3479
3559
  // for every kind. `noteTransition` ignores an already-terminal record, so
@@ -3484,7 +3564,9 @@ function createControlSurface(
3484
3564
  // a delayed preflight diverted to steering fires onQueuedPromoted
3485
3565
  // with startsOwnRun:false and is now attached to the in-flight run.
3486
3566
  // Do not terminalize from the stale snapshot — the run will.
3487
- if (!queuedAtDispatch && promotionStartsOwnRun !== false) {
3567
+ // Remember actual diversion even after own-run promotion changes
3568
+ // promotionStartsOwnRun: enqueue completion is still not run completion.
3569
+ if (!queueCancellation.queueCandidate && promotionStartsOwnRun !== false) {
3488
3570
  // The accepted work settled without its own run still pending:
3489
3571
  // retire the pending ownership entry (and with it the
3490
3572
  // acceptance-anchored deadline lease, #4668 review) BEFORE
@@ -3553,7 +3635,10 @@ function createControlSurface(
3553
3635
  );
3554
3636
  },
3555
3637
  error => {
3638
+ settleAcceptedImageSubmission?.(correlation);
3639
+ if (queuedRunStartRefused) return;
3556
3640
  if (settled) {
3641
+ releaseAcceptedImage?.(correlation);
3557
3642
  retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
3558
3643
  // The submission promise rejects after preflight acceptance only when the
3559
3644
  // work itself is over (provider stream interrupt, abort, queue failure).
@@ -3675,6 +3760,9 @@ function createControlSurface(
3675
3760
  ...(acceptedFields?.() ?? {}),
3676
3761
  };
3677
3762
  } catch (error) {
3763
+ if (!accepted) preflightController.abort();
3764
+ if (!submission) settleAcceptedImageSubmission?.(correlation);
3765
+ releaseAcceptedImage?.(correlation);
3678
3766
  retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
3679
3767
  if (!accepted) reconciliation.release(kind, retainedClientRef);
3680
3768
  throw error;
@@ -4593,28 +4681,57 @@ function createControlSurface(
4593
4681
  }
4594
4682
  };
4595
4683
  return {
4596
- prompt: async (text, images, clientRef) => {
4597
- const invalid = validateRequiredPromptText("turn.prompt", { text, images });
4684
+ prompt: async (text, images, clientRef, stagedImages) => {
4685
+ const invalid = validateRequiredPromptText("turn.prompt", { text, images, stagedImages });
4598
4686
  if (invalid) throw Object.assign(new Error(invalid.message), { code: invalid.code });
4599
- return await submit("prompt", clientRef, ({ queuedAtDispatch, sdkRunCapability, ...options }) =>
4600
- sendSdkUserMessage(
4601
- typeof images === "undefined"
4602
- ? text
4603
- : ([{ type: "text", text }, ...normalizePromptImages(images)] as [
4604
- { type: "text"; text: string },
4605
- ...ImageContent[],
4606
- ]),
4607
- {
4608
- ...options,
4609
- sdkRunCapability,
4610
- // ACP terminal settlement is owned by the correlated agent_end
4611
- // publication. Post-prompt recovery may include independent
4612
- // subagent work and must not hold that client-facing boundary.
4613
- ...(queuedAtDispatch ? { queuedAtDispatch: true } : {}),
4687
+ if (stagedImages !== undefined && images !== undefined)
4688
+ throw Object.assign(new Error("Direct and staged images cannot be mixed."), { code: "invalid_input" });
4689
+ if (stagedImages !== undefined && !imageUploads)
4690
+ throw Object.assign(new Error("Image uploads are unavailable."), { code: "operation_prohibited" });
4691
+ let reservation: { images: ImageContent[]; release: () => void } | undefined;
4692
+ let imageCorrelation: InvocationCorrelation | undefined;
4693
+ try {
4694
+ return await submit(
4695
+ "prompt",
4696
+ clientRef,
4697
+ ({ queuedAtDispatch, sdkRunCapability, ...options }) => {
4698
+ // submit has reserved reconciliation/clientRef before consuming staged IDs.
4699
+ if (stagedImages !== undefined) {
4700
+ reservation = imageUploads!.redeem(sdkControlRequesterContext.getStore(), stagedImages);
4701
+ if (imageCorrelation && retainAcceptedImage)
4702
+ retainAcceptedImage(imageCorrelation, reservation.release);
4703
+ }
4704
+ return sendSdkUserMessage(
4705
+ typeof images === "undefined" && !reservation
4706
+ ? text
4707
+ : ([{ type: "text", text }, ...(reservation?.images ?? normalizePromptImages(images))] as [
4708
+ { type: "text"; text: string },
4709
+ ...ImageContent[],
4710
+ ]),
4711
+ { ...options, sdkRunCapability, ...(queuedAtDispatch ? { queuedAtDispatch: true } : {}) },
4712
+ );
4614
4713
  },
4615
- ),
4616
- );
4714
+ undefined,
4715
+ false,
4716
+ false,
4717
+ correlation => {
4718
+ if (retainAcceptedImage) imageCorrelation = correlation;
4719
+ },
4720
+ );
4721
+ } catch (error) {
4722
+ if (imageCorrelation) releaseAcceptedImage?.(imageCorrelation);
4723
+ else reservation?.release();
4724
+ throw error;
4725
+ }
4617
4726
  },
4727
+ ...(imageUploads
4728
+ ? {
4729
+ imageBegin: (input: unknown) => imageUploads.begin(sdkControlRequesterContext.getStore(), input),
4730
+ imageAppend: (input: unknown) => imageUploads.append(sdkControlRequesterContext.getStore(), input),
4731
+ imageFinish: (input: unknown) => imageUploads.finish(sdkControlRequesterContext.getStore(), input),
4732
+ imageDiscard: (input: unknown) => imageUploads.discard(sdkControlRequesterContext.getStore(), input),
4733
+ }
4734
+ : {}),
4618
4735
  steer: async (text, clientRef, expectedSdkRunToken) => {
4619
4736
  const invalid = validateRequiredPromptText("turn.steer", { text });
4620
4737
  if (invalid) throw Object.assign(new Error(invalid.message), { code: invalid.code });
@@ -4869,7 +4986,9 @@ function createControlSurface(
4869
4986
  retryLast: () => typed("retry.last"),
4870
4987
  retryNow: () => typed("retry.now"),
4871
4988
  backgroundBash: () => typed("bash.background"),
4872
- installedOperations: surfacePolicy.installedControls,
4989
+ installedOperations: imageUploads
4990
+ ? surfacePolicy.installedControls
4991
+ : new Set([...surfacePolicy.installedControls].filter(operation => !operation.startsWith("turn.image."))),
4873
4992
  revisionProvider: resource => (resource === "config" ? String(configRevision.current) : undefined),
4874
4993
  };
4875
4994
  }
@@ -5019,6 +5138,89 @@ function quiescingFrame(frame: Record<string, unknown>): Record<string, unknown>
5019
5138
 
5020
5139
  /** Install a complete SDK host for a session when notifications are inactive. */
5021
5140
  export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: CreateSdkSessionRuntimeOptions): void {
5141
+ const acceptedImages = new Map<
5142
+ string,
5143
+ {
5144
+ store: PromptImageUploadStore;
5145
+ release: () => void;
5146
+ handle?: string;
5147
+ executionStarted?: boolean;
5148
+ submissionPending?: boolean;
5149
+ awaitingAdoption?: boolean;
5150
+ releaseRequested?: boolean;
5151
+ retiring?: boolean;
5152
+ }
5153
+ >();
5154
+ const imageKey = (correlation: InvocationCorrelation): string => `${correlation.commandId}:${correlation.turnId}`;
5155
+ const retireAcceptedImage = (key: string): void => {
5156
+ const reservation = acceptedImages.get(key);
5157
+ if (!reservation) return;
5158
+ if ((reservation.submissionPending || reservation.awaitingAdoption) && !reservation.executionStarted) {
5159
+ reservation.releaseRequested = true;
5160
+ return;
5161
+ }
5162
+ if (reservation.retiring) return;
5163
+ const release = (): void => {
5164
+ if (acceptedImages.get(key) !== reservation) return;
5165
+ acceptedImages.delete(key);
5166
+ reservation.release();
5167
+ };
5168
+ if (!reservation.handle) {
5169
+ if (reservation.executionStarted) {
5170
+ reservation.retiring = true;
5171
+ logger.warn("SDK image capacity retained for execution without an exact resource handle");
5172
+ return;
5173
+ }
5174
+ release();
5175
+ return;
5176
+ }
5177
+ const waitForSettlement = options.terminalAbortSeams?.waitForRunSettlement;
5178
+ if (!waitForSettlement) return;
5179
+ reservation.retiring = true;
5180
+ retirePromptImageReservation(
5181
+ reservation.handle,
5182
+ waitForSettlement,
5183
+ () => acceptedImages.get(key) === reservation,
5184
+ release,
5185
+ );
5186
+ };
5187
+ const releaseAcceptedImage = (correlation: InvocationCorrelation): void =>
5188
+ retireAcceptedImage(imageKey(correlation));
5189
+ const settleAcceptedImageSubmission = (correlation: InvocationCorrelation): void => {
5190
+ const key = imageKey(correlation);
5191
+ const reservation = acceptedImages.get(key);
5192
+ if (!reservation) return;
5193
+ reservation.submissionPending = false;
5194
+ if (reservation.releaseRequested) {
5195
+ reservation.releaseRequested = false;
5196
+ retireAcceptedImage(key);
5197
+ }
5198
+ };
5199
+ const bindAcceptedImage = (correlation: InvocationCorrelation, handle: string | undefined): void => {
5200
+ const reservation = acceptedImages.get(imageKey(correlation));
5201
+ if (!reservation) return;
5202
+ reservation.awaitingAdoption = false;
5203
+ reservation.executionStarted = true;
5204
+ if (handle && reservation.handle === undefined) reservation.handle = handle;
5205
+ if (reservation.releaseRequested) retireAcceptedImage(imageKey(correlation));
5206
+ };
5207
+ // Promotion commits a queued image to its next own-run before agent_start supplies the exact resource handle.
5208
+ const awaitAcceptedImageAdoption = (correlation: InvocationCorrelation): void => {
5209
+ const reservation = acceptedImages.get(imageKey(correlation));
5210
+ if (reservation) reservation.awaitingAdoption = true;
5211
+ };
5212
+ const releaseImagesForStore = (store: PromptImageUploadStore | undefined): void => {
5213
+ for (const [key, reservation] of acceptedImages) {
5214
+ if (reservation.store !== store) continue;
5215
+ retireAcceptedImage(key);
5216
+ }
5217
+ };
5218
+ const pendingImageCleanups = new Set<PromptImageUploadStore>();
5219
+ const completeImageCleanups = (): void => {
5220
+ for (const store of pendingImageCleanups) releaseImagesForStore(store);
5221
+ pendingImageCleanups.clear();
5222
+ };
5223
+ let currentImageUploads: PromptImageUploadStore | undefined;
5022
5224
  const acceptedQueueCancellations = new Map<string, AcceptedQueueCancellation>();
5023
5225
  const disposeAcceptedQueueCancellations = async (): Promise<void> => {
5024
5226
  const cancellations = [...acceptedQueueCancellations.values()];
@@ -5549,6 +5751,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5549
5751
  ): Promise<void> => {
5550
5752
  const current = lifecycleOwner?.state ?? active;
5551
5753
  if (!current) return;
5754
+ const imageRunHandle =
5755
+ type === "agent_start" && acceptedImages.size > 0
5756
+ ? options.terminalAbortSeams?.getActivePromptHandle()
5757
+ : undefined;
5552
5758
  const failureBatch = lifecycleOwner?.batch;
5553
5759
  const adoptLifecycleBatch = (batch: LifecycleBatch | undefined): void => {
5554
5760
  current.activeInvocation = batch?.invocations[0];
@@ -5666,6 +5872,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5666
5872
  adoptLifecycleBatch(batch);
5667
5873
  for (const entry of drained) {
5668
5874
  if (entry.kind !== "prompt") continue;
5875
+ bindAcceptedImage(entry.correlation, imageRunHandle);
5669
5876
  if (current.deadlineManager.isExpiring(entry.correlation))
5670
5877
  current.deadlineManager.captureExpiringRun(entry.correlation);
5671
5878
  else current.deadlineManager.onRunStarted(entry.correlation);
@@ -6175,8 +6382,13 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6175
6382
  }
6176
6383
  if (type === "agent_end" && isContinuingMidRunMaintenanceOutcome(maintenanceOutcome)) return;
6177
6384
  if (type === "agent_end") {
6178
- for (const invocation of transitions)
6385
+ for (const invocation of transitions) {
6386
+ // A deadline can capture this event while the exact run still owns
6387
+ // pending tools. Its settlement owner, not event capture, releases quota.
6388
+ if (!current.deadlineManager.shouldDeferTerminalTransition(invocation.correlation))
6389
+ releaseAcceptedImage(invocation.correlation);
6179
6390
  retireAcceptedQueueCancellation(acceptedQueueCancellations, invocation.correlation);
6391
+ }
6180
6392
  if (current.lifecycleEpoch !== eventLifecycleEpoch) {
6181
6393
  // A successor agent_start won the lifecycle race while this event's
6182
6394
  // durable transitions were awaiting persistence. Retire the ended
@@ -6596,6 +6808,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6596
6808
  const startRuntime = async (ctx: ExtensionContext): Promise<void> => {
6597
6809
  if (active) return;
6598
6810
  if (acceptedQueueCancellations.size > 0) await disposeAcceptedQueueCancellations();
6811
+ completeImageCleanups();
6599
6812
  if (active) return;
6600
6813
  const sessionId = ctx.sessionManager.getSessionId();
6601
6814
  const stateRoot = path.join(ctx.cwd, ".gjc", "state");
@@ -6947,6 +7160,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6947
7160
  await autosaveWorktreeOnPromptDeadline(ctx.cwd, options.settings, { isCurrent, signal });
6948
7161
  },
6949
7162
  onExpired: correlation => {
7163
+ // The manager invokes retirement only after confirmed settlement and
7164
+ // durable terminalization (including recovered queue removals), never
7165
+ // merely because an inactivity timer fired or execution is uncertain.
7166
+ releaseAcceptedImage(correlation);
6950
7167
  retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation, true);
6951
7168
  const owner = lifecycleOwnerHolder.state;
6952
7169
  if (owner) {
@@ -7011,6 +7228,9 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7011
7228
  ctx,
7012
7229
  id: sessionId,
7013
7230
  api,
7231
+ imageUploadsAvailable:
7232
+ typeof transport.isConnectionOpen === "function" &&
7233
+ typeof options.terminalAbortSeams?.waitForRunSettlement === "function",
7014
7234
  reconciliation,
7015
7235
  turnResultLookup: selector => reconciliation.lookupResult(selector.kind, selector),
7016
7236
  steerStatusLookup: selector => steerReconciliation.lookupSteer(selector),
@@ -7141,7 +7361,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7141
7361
  });
7142
7362
  // 3. Release recovery ownership ONLY after durable terminalization.
7143
7363
  deadlineManager.clear(correlation);
7144
- if (kind === "prompt") retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
7364
+ if (kind === "prompt") {
7365
+ releaseAcceptedImage(correlation);
7366
+ retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
7367
+ }
7145
7368
  return true;
7146
7369
  } catch (transitionError) {
7147
7370
  // Keep prompt recovery leased; skill recovery has no prompt lease.
@@ -7159,6 +7382,14 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7159
7382
  return attempt(3);
7160
7383
  };
7161
7384
 
7385
+ // This is a private SDK transport facility. A frame-observer cache cannot
7386
+ // establish that the authenticated connection still exists at redemption.
7387
+ const imageUploads =
7388
+ typeof transport.isConnectionOpen === "function" &&
7389
+ typeof options.terminalAbortSeams?.waitForRunSettlement === "function"
7390
+ ? new PromptImageUploadStore(connectionId => transport.isConnectionOpen?.(connectionId) === true)
7391
+ : undefined;
7392
+ currentImageUploads = imageUploads;
7162
7393
  const controlSurface = createControlSurface(
7163
7394
  ctx,
7164
7395
  api,
@@ -7195,7 +7426,13 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7195
7426
  },
7196
7427
  correlation => deadlineManager.onAccepted(correlation),
7197
7428
  steerReconciliation,
7429
+ imageUploads,
7198
7430
  (kind, correlation, connectionId, sdkRunToken, promotion) => {
7431
+ if (!promotion?.removed) {
7432
+ if (promotion?.startsOwnRun === false)
7433
+ bindAcceptedImage(correlation, options.terminalAbortSeams?.getActivePromptHandle());
7434
+ else awaitAcceptedImageAdoption(correlation);
7435
+ }
7199
7436
  const bindPromotedToken = (batch?: LifecycleBatch): void => {
7200
7437
  const owner = lifecycleOwnerHolder.state;
7201
7438
  if (sdkRunToken && owner)
@@ -7251,6 +7488,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7251
7488
  // with it.
7252
7489
  const current = active;
7253
7490
  if (current?.lifecycleActive) {
7491
+ bindAcceptedImage(correlation, options.terminalAbortSeams?.getActivePromptHandle());
7254
7492
  // If this correlation was already admitted as own-run via the
7255
7493
  // idle snapshot (onAccepted -> pending) but later diverted to
7256
7494
  // steering and consumed in-run, move it from pending to the
@@ -7411,7 +7649,47 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7411
7649
  throw error;
7412
7650
  }
7413
7651
  },
7652
+ (correlation, release) => {
7653
+ if (imageUploads)
7654
+ acceptedImages.set(imageKey(correlation), {
7655
+ store: imageUploads,
7656
+ release,
7657
+ submissionPending: true,
7658
+ });
7659
+ },
7660
+ settleAcceptedImageSubmission,
7661
+ releaseAcceptedImage,
7414
7662
  acceptedQueueCancellations,
7663
+ (kind, correlation, failure) => {
7664
+ const record = reconciliation.lookup(kind, correlation) as { status?: string };
7665
+ const reservation = acceptedImages.get(imageKey(correlation));
7666
+ const terminal = record.status === "terminal_ok" || record.status === "failed";
7667
+ if (record.status !== "accepted" && !(terminal && reservation)) return false;
7668
+ // A known execution/handle retains exact-ledger authority even if a
7669
+ // contradictory callback arrives later. Never downgrade adoption.
7670
+ if (reservation?.executionStarted || reservation?.handle) return false;
7671
+ const adopted = active?.openLifecycleBatches.some(batch =>
7672
+ [...batch.invocations, ...batch.attachedInvocations].some(
7673
+ entry => entry.kind === kind && imageKey(entry.correlation) === imageKey(correlation),
7674
+ ),
7675
+ );
7676
+ if (adopted) return false;
7677
+ const index = pending.findIndex(entry => entry.kind === kind && entry.correlation === correlation);
7678
+ if (index >= 0) {
7679
+ const [removed] = pending.splice(index, 1);
7680
+ if (removed) lifecycleRunOwners.delete(removed.sdkRunToken);
7681
+ }
7682
+ if (reservation) {
7683
+ reservation.awaitingAdoption = false;
7684
+ // Ordinary retirement still waits for an outstanding enqueue producer.
7685
+ retireAcceptedImage(imageKey(correlation));
7686
+ }
7687
+ // Keep the existing durable-first failure/recovery owner; this is not
7688
+ // queue removal, and cannot publish cancellation or synthetic success.
7689
+ if (!terminal)
7690
+ void terminalizeAbandonedSubmission(kind, correlation, rejectionRecoveryIntent(failure.error));
7691
+ return true;
7692
+ },
7415
7693
  );
7416
7694
  const installProviderDefinitions = (capability: string, definitions: unknown): void => {
7417
7695
  if (capability === "permission") {
@@ -7479,6 +7757,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7479
7757
  };
7480
7758
  runtime = new SessionSdkSessionRuntime({
7481
7759
  transport,
7760
+ onDisconnected: connectionId => imageUploads?.disconnect(connectionId),
7482
7761
  eventRevision: () =>
7483
7762
  typeof (ctx as Partial<ExtensionContext>).getTranscript === "function"
7484
7763
  ? ctx.getTranscript().length
@@ -7929,6 +8208,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7929
8208
  } catch (error) {
7930
8209
  runtimeOwner.quiesceInput();
7931
8210
  runtimeOwner.fenceGateResolutions();
8211
+ imageUploads?.close();
7932
8212
  active = undefined;
7933
8213
  let queueCancellationFailure: unknown;
7934
8214
  try {
@@ -7987,7 +8267,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7987
8267
  }
7988
8268
  cursors.close();
7989
8269
  await revisions.close().catch(() => undefined);
7990
- if (queueCancellationFailure !== undefined)
8270
+ if (queueCancellationFailure !== undefined) {
8271
+ if (imageUploads) pendingImageCleanups.add(imageUploads);
7991
8272
  throw Object.assign(
7992
8273
  new AggregateError(
7993
8274
  [error, queueCancellationFailure],
@@ -7995,14 +8276,25 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
7995
8276
  ),
7996
8277
  { code: "sdk_reconciliation_teardown_failed" },
7997
8278
  );
8279
+ }
8280
+ if (currentImageUploads === imageUploads) currentImageUploads = undefined;
8281
+ releaseImagesForStore(imageUploads);
7998
8282
  throw error;
7999
8283
  }
8000
8284
  };
8001
8285
  const stopActive = async (cancelSkillRecovery = false, unregisterReason?: "detached_idle"): Promise<void> => {
8002
8286
  const current = active;
8003
- if (!current) return;
8287
+ if (!current) {
8288
+ if (pendingImageCleanups.size > 0) {
8289
+ await disposeAcceptedQueueCancellations();
8290
+ completeImageCleanups();
8291
+ }
8292
+ return;
8293
+ }
8004
8294
  current.quiesceInput();
8005
8295
  current.fenceGateResolutions();
8296
+ const imageUploads = currentImageUploads;
8297
+ imageUploads?.close();
8006
8298
  let queueCancellationFailure: unknown;
8007
8299
  try {
8008
8300
  await disposeAcceptedQueueCancellations();
@@ -8068,6 +8360,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
8068
8360
  allowLockContention: cancelSkillRecovery,
8069
8361
  ...(unregisterReason === undefined ? {} : { unregisterReason }),
8070
8362
  });
8363
+ if (queueCancellationFailure === undefined) {
8364
+ if (currentImageUploads === imageUploads) currentImageUploads = undefined;
8365
+ releaseImagesForStore(imageUploads);
8366
+ } else if (imageUploads) pendingImageCleanups.add(imageUploads);
8071
8367
  } catch (error) {
8072
8368
  // Keep the immutable owner available for a retry when transport teardown
8073
8369
  // fails after quiescing. Clearing `active` before stop prevents a second