@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
@@ -71,7 +71,7 @@ import {
71
71
  type WorkflowGateTerminalController,
72
72
  type WorkflowGateTerminalProof,
73
73
  } from "../../modes/shared/agent-wire/workflow-gate-broker";
74
- import type { AgentSessionEvent } from "../../session/agent-session";
74
+ import type { AgentSessionEvent, QueuedRunStartFailure } from "../../session/agent-session";
75
75
  import type { ClientBridge } from "../../session/client-bridge";
76
76
  import {
77
77
  createSessionWorkLease,
@@ -102,6 +102,7 @@ import {
102
102
  registerAskAnswerSource,
103
103
  registerWorkflowGateEmitterListener,
104
104
  } from "../../tools/ask-answer-registry";
105
+ import type { AwaitingInputKind } from "../../tools/awaiting-input";
105
106
  import { acpFinalTextFromMessage } from "../acp/final-text";
106
107
  import { ensureBroker } from "../broker/ensure";
107
108
  import { publishSessionHostRuntimeEvidence, type SessionHostRuntimePublication } from "../broker/lifecycle";
@@ -117,11 +118,13 @@ import {
117
118
  type SessionSdkHost,
118
119
  SessionSdkSessionRuntime,
119
120
  shouldHostSdk,
121
+ TURN_STREAM_CAPABILITY,
120
122
  verifyMasterCapabilityFrame,
121
123
  } from "../host";
122
124
  import { type AbortScope, type ControlSurface, dispatchControl, TypedControlError } from "../host/control";
123
125
  import { BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD } from "../host/control/runtime-gate";
124
126
  import { isAutoroutingInactive, markAutoroutingInactive } from "../host/internal-autorouting-state";
127
+ import { PromptImageUploadStore, retirePromptImageReservation } from "../host/prompt-image-upload";
125
128
  import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "../host/query";
126
129
  import { RESPONSE_CEILING_BYTES } from "../host/query/handlers";
127
130
  import type { SdkFrame } from "../host/types";
@@ -1235,6 +1238,10 @@ export class PresentationArbiter {
1235
1238
  interface SessionRuntime {
1236
1239
  server: NotificationServer;
1237
1240
  host: SessionSdkHost;
1241
+ imageUploads: PromptImageUploadStore | undefined;
1242
+ releaseAcceptedImage: (correlation: { commandId: string; turnId: string }) => void;
1243
+ releaseAcceptedImagesForRun: (owner: AgentTerminalOwnerContext) => void;
1244
+ releaseAcceptedImages: () => void;
1238
1245
  getJoinedPromptCorrelations: (owner: AgentTerminalOwnerContext) => Array<{ commandId: string; turnId: string }>;
1239
1246
  isPromptRunOwner: (correlation: { commandId: string; turnId: string }, owner: AgentTerminalOwnerContext) => boolean;
1240
1247
  retireJoinedPromptOwners: () => void;
@@ -1421,6 +1428,12 @@ interface SessionRuntime {
1421
1428
  string,
1422
1429
  { toolName: string; args?: unknown; pendingPhase?: "completed" | "failed" | "cancelled" }
1423
1430
  >;
1431
+ /**
1432
+ * Running tool calls that reported an awaiting-input signal, keyed by tool call
1433
+ * id. An entry stays undelivered while notifications are inactive or policy is
1434
+ * provisional, and is dropped when the call ends; one notification per call.
1435
+ */
1436
+ awaitingInput: Map<string, { toolName: string; kind: AwaitingInputKind; delivered: boolean }>;
1424
1437
  /** Cancels the postmortem cleanup that emits `session_closed` on process teardown. */
1425
1438
  cancelPostmortemCleanup: () => void;
1426
1439
  /** Disposes side-turn resources when their owning logical session becomes unavailable. */
@@ -1431,6 +1444,48 @@ const SENSITIVE_MODEL_LABEL =
1431
1444
  /(?:\b(?:https?|wss?):\/\/|\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b|\b(?:api[-_ ]?key|access[-_ ]?token|bearer|secret|password|account(?:\s*id)?|email|exception|stack trace)\b|\b(?:sk|pk|rk)-[A-Za-z0-9_-]{12,}\b)/i;
1432
1445
  const TOOL_SUMMARY_MAX = 280;
1433
1446
 
1447
+ /** The `awaitingInput` signal a running bash update carries, validated at the extension boundary. */
1448
+ function readAwaitingInput(partialResult: unknown): { kind: AwaitingInputKind } | undefined {
1449
+ if (!partialResult || typeof partialResult !== "object") return undefined;
1450
+ const details = (partialResult as { details?: unknown }).details;
1451
+ if (!details || typeof details !== "object") return undefined;
1452
+ const signal = (details as { awaitingInput?: unknown }).awaitingInput;
1453
+ if (!signal || typeof signal !== "object") return undefined;
1454
+ const kind = (signal as { kind?: unknown }).kind;
1455
+ return kind === "browser_auth" || kind === "device_code" ? { kind } : undefined;
1456
+ }
1457
+
1458
+ /**
1459
+ * Deliver every undelivered awaiting-input notice once stable policy admits it.
1460
+ * The notice describes the present ("a command is waiting now"), so it goes out
1461
+ * on the live native leg only and never enters the replay ring: a client that
1462
+ * reconnects after the command exited must not receive it. The native server
1463
+ * still withholds it from connections without `awaiting_input_v1`.
1464
+ */
1465
+ function deliverAwaitingInput(
1466
+ runtime: Pick<SessionRuntime, "server" | "awaitingInput" | "notificationsActive" | "policySuspended">,
1467
+ sessionId: string,
1468
+ ): void {
1469
+ if (!runtime.notificationsActive || runtime.policySuspended) return;
1470
+ for (const [toolCallId, entry] of runtime.awaitingInput) {
1471
+ if (entry.delivered) continue;
1472
+ entry.delivered = true;
1473
+ try {
1474
+ runtime.server.pushFrame(
1475
+ JSON.stringify({
1476
+ type: "awaiting_input",
1477
+ sessionId,
1478
+ toolCallId,
1479
+ toolName: entry.toolName,
1480
+ kind: entry.kind,
1481
+ }),
1482
+ );
1483
+ } catch (e) {
1484
+ logger.warn(`notifications: awaiting_input failed: ${String(e)}`);
1485
+ }
1486
+ }
1487
+ }
1488
+
1434
1489
  /** Stable projection of the tool-owned safe-display seam (never the full Tool surface). */
1435
1490
  type SafeSummaryTool = Pick<Tool, "safeSummary" | "safeSummaryFields">;
1436
1491
 
@@ -2459,11 +2514,13 @@ function sdkQuerySurface(
2459
2514
  }),
2460
2515
  getRuntimeHost: () => SessionSdkHost | undefined = () => undefined,
2461
2516
  primaryControlSurface: "cli" | "sdk" = "sdk",
2517
+ imageUploadsAvailable = false,
2462
2518
  ): SessionSurface {
2463
2519
  return createSdkSurfaceFactory({
2464
2520
  ctx,
2465
2521
  id,
2466
2522
  api,
2523
+ imageUploadsAvailable,
2467
2524
  getInstalledDefinitions,
2468
2525
  getLiveState,
2469
2526
  configOverrides,
@@ -2612,6 +2669,19 @@ function sdkControlSurface(
2612
2669
  pendingInteractive: Map<string, PendingInteractiveAsk>,
2613
2670
  gatePresentations: PresentationArbiter | undefined,
2614
2671
  api: ExtensionAPI,
2672
+ imageUploads: PromptImageUploadStore | undefined,
2673
+ imageUploadsAvailable: boolean,
2674
+ retainAcceptedImage: (
2675
+ correlation: { commandId: string; turnId: string },
2676
+ release: () => void,
2677
+ preflightSignal: AbortSignal,
2678
+ submissionSettled: Promise<void>,
2679
+ ) => {
2680
+ accepted: () => void;
2681
+ promoted: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
2682
+ refused: () => boolean;
2683
+ },
2684
+ releaseAcceptedImage: (correlation: { commandId: string; turnId: string }) => void,
2615
2685
  isBusy: () => boolean,
2616
2686
  onPromptAccepted: (
2617
2687
  correlation: { commandId: string; turnId: string },
@@ -2626,6 +2696,12 @@ function sdkControlSurface(
2626
2696
  correlation: { commandId: string; turnId: string },
2627
2697
  error: unknown,
2628
2698
  ) => void | Promise<void> = () => {},
2699
+ onQueuedRunStartFailed: (
2700
+ correlation: { commandId: string; turnId: string },
2701
+ failure: QueuedRunStartFailure,
2702
+ sdkRunToken: string | undefined,
2703
+ refuseImage: (() => boolean) | undefined,
2704
+ ) => boolean,
2629
2705
  onPromptAcceptFailed: (correlation: { commandId: string; turnId: string }) => void = () => {},
2630
2706
  onPromptDiverted: (correlation: { commandId: string; turnId: string }) => void,
2631
2707
  onPromptPromoted: (
@@ -2727,6 +2803,7 @@ function sdkControlSurface(
2727
2803
  ctx,
2728
2804
  id: ctx.sessionManager.getSessionId(),
2729
2805
  api,
2806
+ imageUploadsAvailable,
2730
2807
  }).policy;
2731
2808
  const missingExpectedSessionAudits = new Set<"workflow.gate_answer" | "workflow.plan_approve">();
2732
2809
  const auditMissingExpectedSessionId = (operation: "workflow.gate_answer" | "workflow.plan_approve") => {
@@ -2912,6 +2989,18 @@ function sdkControlSurface(
2912
2989
  requesterConnectionId?: string,
2913
2990
  clientRef?: string,
2914
2991
  trackReconciliation = false,
2992
+ onCorrelation?: (
2993
+ correlation: { commandId: string; turnId: string },
2994
+ preflightSignal: AbortSignal,
2995
+ submissionSettled: Promise<void>,
2996
+ ) =>
2997
+ | {
2998
+ accepted: () => void;
2999
+ promoted: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
3000
+ refused: () => boolean;
3001
+ }
3002
+ | undefined,
3003
+ resolveImages?: () => unknown,
2915
3004
  ) => {
2916
3005
  const trimmedClientRef = typeof clientRef === "string" ? clientRef.trim() : undefined;
2917
3006
  if (clientRef !== undefined && (!trimmedClientRef || trimmedClientRef.length > PROMPT_CLIENT_REF_MAX_LENGTH))
@@ -2957,6 +3046,8 @@ function sdkControlSurface(
2957
3046
  code: "busy",
2958
3047
  },
2959
3048
  );
3049
+ // Staged bytes are one-shot: reserve reconciliation before consuming them.
3050
+ if (resolveImages) images = resolveImages();
2960
3051
  } catch (error) {
2961
3052
  admission.abandon();
2962
3053
  if (trackReconciliation) releasePromptAdmission(trimmedClientRef);
@@ -2988,6 +3079,8 @@ function sdkControlSurface(
2988
3079
  const submissionSettled = Promise.withResolvers<void>();
2989
3080
  let cancellation: Promise<void> | undefined;
2990
3081
  const correlation = { commandId, turnId };
3082
+ let queuedRunRefused = false;
3083
+ const imageLifecycle = onCorrelation?.(correlation, preflightController.signal, submissionSettled.promise);
2991
3084
  const key = preflightKey(requesterConnectionId, correlation);
2992
3085
  const settlePreflight = (result: PreflightTerminalResult) => {
2993
3086
  if (preflightSettled) return;
@@ -3038,6 +3131,8 @@ function sdkControlSurface(
3038
3131
  admission.accepted();
3039
3132
  accepting = false;
3040
3133
  accepted = true;
3134
+ // A late durable acceptance after direct abort cannot end image retention.
3135
+ if (!preflightController.signal.aborted) imageLifecycle?.accepted();
3041
3136
  if (effectiveDeliverAs !== undefined) onPromptDiverted(correlation);
3042
3137
  // #4743: an accepted run owns a durable terminal publication when it
3043
3138
  // settles; teardown joins it via this latch (never-accepted preflights
@@ -3065,13 +3160,22 @@ function sdkControlSurface(
3065
3160
  onPreflightAcceptCommit,
3066
3161
  onPreflightAccepted,
3067
3162
  onQueuedPromoted: promotion => {
3163
+ if (queuedRunRefused) return;
3164
+ if (promotion.startsOwnRun === true) imageLifecycle?.promoted(promotion);
3068
3165
  admission.hooks.onQueuedPromoted(promotion);
3069
3166
  onPromptPromoted(correlation, promotion, ctx.getActivePromptHandle());
3167
+ if (promotion.startsOwnRun !== true) imageLifecycle?.promoted(promotion);
3168
+ if (promotion.removed) releaseAcceptedImage(correlation);
3070
3169
  },
3071
3170
  onDispatchDisposition: disposition => {
3171
+ if (queuedRunRefused) return;
3072
3172
  admission.hooks.onDispatchDisposition(disposition);
3073
3173
  if (!disposition.startsOwnRun) onPromptDiverted(correlation);
3074
3174
  },
3175
+ onQueuedRunStartFailed: failure => {
3176
+ if (queuedRunRefused) return;
3177
+ queuedRunRefused = onQueuedRunStartFailed(correlation, failure, sdkRunToken, imageLifecycle?.refused);
3178
+ },
3075
3179
  preflightSignal: preflightController.signal,
3076
3180
  ...(sdkRunToken ? { sdkRunToken } : {}),
3077
3181
  }),
@@ -3079,7 +3183,7 @@ function sdkControlSurface(
3079
3183
  } catch (error) {
3080
3184
  submissionSettled.resolve();
3081
3185
  admission.abandon();
3082
- if (accepted && !preflightController.signal.aborted)
3186
+ if (accepted && !queuedRunRefused && !preflightController.signal.aborted)
3083
3187
  trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
3084
3188
  else settlePreflight({ status: "rejected", error });
3085
3189
  }
@@ -3101,7 +3205,7 @@ function sdkControlSurface(
3101
3205
  },
3102
3206
  error => {
3103
3207
  admission.abandon();
3104
- if (accepted && !preflightController.signal.aborted)
3208
+ if (accepted && !queuedRunRefused && !preflightController.signal.aborted)
3105
3209
  trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
3106
3210
  else settlePreflight({ status: "rejected", error });
3107
3211
  submissionSettled.resolve();
@@ -3124,8 +3228,52 @@ function sdkControlSurface(
3124
3228
  cancelPendingPreflights(): Promise<void>;
3125
3229
  cancelPendingPreflightsForConnection(connectionId: string): Promise<void>;
3126
3230
  } = {
3127
- prompt: (text, images, clientRef) =>
3128
- submitPrompt(text, images, false, undefined, true, controlRequesterContext.getStore(), clientRef, true),
3231
+ prompt: async (text, images, clientRef, stagedImages) => {
3232
+ if (stagedImages !== undefined && images !== undefined)
3233
+ throw Object.assign(new Error("Direct and staged images cannot be mixed."), { code: "invalid_input" });
3234
+ const requesterConnectionId = controlRequesterContext.getStore();
3235
+ let reservation: { images: ImageContent[]; release: () => void } | undefined;
3236
+ let correlation: { commandId: string; turnId: string } | undefined;
3237
+ try {
3238
+ return await submitPrompt(
3239
+ text,
3240
+ images,
3241
+ false,
3242
+ undefined,
3243
+ true,
3244
+ requesterConnectionId,
3245
+ clientRef,
3246
+ true,
3247
+ (current, preflightSignal, submissionSettled) => {
3248
+ correlation = current;
3249
+ if (reservation)
3250
+ return retainAcceptedImage(current, reservation.release, preflightSignal, submissionSettled);
3251
+ },
3252
+ stagedImages === undefined
3253
+ ? undefined
3254
+ : () => {
3255
+ if (!imageUploads)
3256
+ throw Object.assign(new Error("Staged images are unavailable."), {
3257
+ code: "image_upload_unavailable",
3258
+ });
3259
+ reservation = imageUploads.redeem(requesterConnectionId, stagedImages);
3260
+ return reservation.images;
3261
+ },
3262
+ );
3263
+ } catch (error) {
3264
+ if (correlation) releaseAcceptedImage(correlation);
3265
+ else reservation?.release();
3266
+ throw error;
3267
+ }
3268
+ },
3269
+ ...(imageUploads
3270
+ ? {
3271
+ imageBegin: (input: unknown) => imageUploads.begin(controlRequesterContext.getStore(), input),
3272
+ imageAppend: (input: unknown) => imageUploads.append(controlRequesterContext.getStore(), input),
3273
+ imageFinish: (input: unknown) => imageUploads.finish(controlRequesterContext.getStore(), input),
3274
+ imageDiscard: (input: unknown) => imageUploads.discard(controlRequesterContext.getStore(), input),
3275
+ }
3276
+ : {}),
3129
3277
  steer: (text, clientRef) => sendSteer(text, clientRef),
3130
3278
  followUp: text => submitPrompt(text, undefined, false, "followUp", false, controlRequesterContext.getStore()),
3131
3279
  abort: async () => {
@@ -4348,6 +4496,8 @@ export function createNotificationsExtension(
4348
4496
  * preflight outside the durable admission path (review thread P2).
4349
4497
  */
4350
4498
  terminalAbortSeams?: {
4499
+ /** Definitive event-driven observation; never mutates the owning run. */
4500
+ waitForRunSettlement?: (handle: string) => Promise<RunSettlementProof>;
4351
4501
  getTerminalRunOwnerForEvent?: (event: object) => AgentTerminalOwnerContext | undefined;
4352
4502
  getRunOwnerDomain?: (handle: string) => RunCancellationDomain | undefined;
4353
4503
  getTerminalTurnEpoch: () => number | undefined;
@@ -4637,6 +4787,7 @@ export function createNotificationsExtension(
4637
4787
  if (reason === "session" && requestedRuntime) {
4638
4788
  requestedRuntime.inboundFenced = true;
4639
4789
  requestedRuntime.stopping = true;
4790
+ requestedRuntime.imageUploads?.close();
4640
4791
  requestedRuntime.retireJoinedPromptOwners();
4641
4792
  requestedRuntime.abortEphemeralTurns();
4642
4793
  }
@@ -4815,6 +4966,7 @@ export function createNotificationsExtension(
4815
4966
  cleanupRetries.set(id, rt);
4816
4967
  throw new AggregateError(ownerReleaseFailures, `SDK notification runtime ${id} owner release failed.`);
4817
4968
  }
4969
+ rt.releaseAcceptedImages();
4818
4970
  if (cleanupRetries.get(id) === rt) cleanupRetries.delete(id);
4819
4971
  return true;
4820
4972
  }
@@ -5089,7 +5241,11 @@ export function createNotificationsExtension(
5089
5241
  };
5090
5242
 
5091
5243
  type HostConnectionIncarnation = { generation: number; closed: boolean };
5092
- type HostCapabilityCacheEntry = { generation: number; capabilities: ReadonlySet<string> };
5244
+ type HostCapabilityCacheEntry = {
5245
+ generation: number;
5246
+ capabilities: ReadonlySet<string>;
5247
+ nativeConfirmed: ReadonlySet<string>;
5248
+ };
5093
5249
  const hostCapCache = new Map<string, HostCapabilityCacheEntry>();
5094
5250
  const hostConnectionIncarnations = new Map<string, HostConnectionIncarnation>();
5095
5251
  const hostAttachedConnections = new Set<string>();
@@ -5103,12 +5259,28 @@ export function createNotificationsExtension(
5103
5259
  hostConnectionIncarnations.set(connectionId, incarnation);
5104
5260
  return incarnation;
5105
5261
  };
5106
- const rememberHostCapabilities = (connectionId: string, capabilities: readonly string[]): void => {
5262
+ const rememberHostCapabilities = (
5263
+ connectionId: string,
5264
+ capabilities: readonly string[],
5265
+ source: "replay" | "native",
5266
+ ): void => {
5107
5267
  const incarnation = liveHostConnection(connectionId);
5108
5268
  if (!incarnation) return;
5269
+ const previous = hostCapCache.get(connectionId);
5270
+ const merged =
5271
+ previous?.generation === incarnation.generation ? new Set(previous.capabilities) : new Set<string>();
5272
+ const nativeConfirmed =
5273
+ previous?.generation === incarnation.generation ? new Set(previous.nativeConfirmed) : new Set<string>();
5274
+ for (const capability of capabilities) merged.add(capability);
5275
+ // Replay claims enable stream routing immediately, but only native snapshots
5276
+ // can remove a socket from host demand. Merge delayed snapshots monotonically.
5277
+ if (source === "native") {
5278
+ for (const capability of capabilities) nativeConfirmed.add(capability);
5279
+ }
5109
5280
  hostCapCache.set(connectionId, {
5110
5281
  generation: incarnation.generation,
5111
- capabilities: new Set(capabilities),
5282
+ capabilities: merged,
5283
+ nativeConfirmed,
5112
5284
  });
5113
5285
  };
5114
5286
  const liveHostCapabilities = (connectionId: string): ReadonlySet<string> | undefined => {
@@ -5130,6 +5302,17 @@ export function createNotificationsExtension(
5130
5302
  hostCapCache.delete(connectionId);
5131
5303
  hostAttachedConnections.delete(connectionId);
5132
5304
  };
5305
+ const imageUploadsAvailable =
5306
+ typeof terminalAbortSeams?.waitForRunSettlement === "function" &&
5307
+ typeof terminalAbortSeams.getRunOwnerDomain === "function" &&
5308
+ typeof terminalAbortSeams.getTerminalRunOwnerForEvent === "function" &&
5309
+ typeof ctx.getActivePromptHandle === "function";
5310
+ const imageUploads = imageUploadsAvailable
5311
+ ? new PromptImageUploadStore(connectionId => {
5312
+ const incarnation = hostConnectionIncarnations.get(connectionId);
5313
+ return incarnation !== undefined && !incarnation.closed;
5314
+ })
5315
+ : undefined;
5133
5316
 
5134
5317
  const configOverrides = new Map<string, unknown>();
5135
5318
  const configRevision = { current: 0 };
@@ -5145,6 +5328,115 @@ export function createNotificationsExtension(
5145
5328
  const OWNED_SETTLEMENT_GRACE_MS = 500;
5146
5329
  const promptSubmissionKey = (correlation: { commandId: string; turnId: string }) =>
5147
5330
  `${correlation.commandId}:${correlation.turnId}`;
5331
+ type AcceptedImageReservation = {
5332
+ release: () => void;
5333
+ preflightSignal: AbortSignal;
5334
+ /** The producer can still retain ImageContent until settlement or exact run adoption. */
5335
+ submissionPending: boolean;
5336
+ /** A promoted queue entry needs its own run's proof before release authority transfers. */
5337
+ awaitingAdoption: boolean;
5338
+ accepted: boolean;
5339
+ releaseRequested: boolean;
5340
+ };
5341
+ const acceptedImages = new Map<string, AcceptedImageReservation>();
5342
+ // Absence means never consumed; a present undefined owner means execution
5343
+ // started without exact ownership evidence and must remain charged.
5344
+ const acceptedImageRunOwners = new Map<string, AgentTerminalOwnerContext | undefined>();
5345
+ const retiringImages = new Set<string>();
5346
+ const retireAcceptedImage = (key: string): void => {
5347
+ const reservation = acceptedImages.get(key);
5348
+ if (!reservation) return;
5349
+ if (reservation.submissionPending || reservation.awaitingAdoption) {
5350
+ reservation.releaseRequested = true;
5351
+ return;
5352
+ }
5353
+ if (retiringImages.has(key)) return;
5354
+ const discard = (): void => {
5355
+ if (acceptedImages.get(key) !== reservation) return;
5356
+ acceptedImages.delete(key);
5357
+ acceptedImageRunOwners.delete(key);
5358
+ retiringImages.delete(key);
5359
+ reservation.release();
5360
+ };
5361
+ const owner = acceptedImageRunOwners.get(key);
5362
+ if (!owner) {
5363
+ if (acceptedImageRunOwners.has(key)) {
5364
+ retiringImages.add(key);
5365
+ logger.warn("SDK image capacity retained for execution without an exact resource owner");
5366
+ return;
5367
+ }
5368
+ discard();
5369
+ return;
5370
+ }
5371
+ const waitForSettlement = terminalAbortSeams?.waitForRunSettlement;
5372
+ if (!waitForSettlement) return;
5373
+ retiringImages.add(key);
5374
+ retirePromptImageReservation(
5375
+ owner.resourceRunId,
5376
+ waitForSettlement,
5377
+ () => acceptedImages.get(key) === reservation,
5378
+ discard,
5379
+ );
5380
+ };
5381
+ const settleAcceptedImageSubmission = (key: string, reservation: AcceptedImageReservation): void => {
5382
+ if (acceptedImages.get(key) !== reservation || !reservation.submissionPending) return;
5383
+ reservation.submissionPending = false;
5384
+ if (reservation.releaseRequested) retireAcceptedImage(key);
5385
+ };
5386
+ const retainAcceptedImage = (
5387
+ correlation: { commandId: string; turnId: string },
5388
+ release: () => void,
5389
+ preflightSignal: AbortSignal,
5390
+ submissionSettled: Promise<void>,
5391
+ ): {
5392
+ accepted: () => void;
5393
+ promoted: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
5394
+ refused: () => boolean;
5395
+ } => {
5396
+ const key = promptSubmissionKey(correlation);
5397
+ const reservation: AcceptedImageReservation = {
5398
+ release,
5399
+ preflightSignal,
5400
+ submissionPending: true,
5401
+ awaitingAdoption: false,
5402
+ accepted: false,
5403
+ releaseRequested: false,
5404
+ };
5405
+ acceptedImages.set(key, reservation);
5406
+ void submissionSettled.then(() => settleAcceptedImageSubmission(key, reservation));
5407
+ return {
5408
+ accepted: () => {
5409
+ reservation.accepted = true;
5410
+ if (reservation.preflightSignal.aborted) return;
5411
+ if (acceptedImageRunOwners.get(key) && !reservation.awaitingAdoption) {
5412
+ settleAcceptedImageSubmission(key, reservation);
5413
+ if (reservation.releaseRequested) retireAcceptedImage(key);
5414
+ }
5415
+ },
5416
+ promoted: promotion => {
5417
+ reservation.awaitingAdoption = promotion.startsOwnRun === true && promotion.removed !== true;
5418
+ if (!reservation.awaitingAdoption && reservation.releaseRequested) retireAcceptedImage(key);
5419
+ },
5420
+ refused: () => {
5421
+ if (acceptedImages.get(key) !== reservation || acceptedImageRunOwners.has(key)) return false;
5422
+ reservation.awaitingAdoption = false;
5423
+ retireAcceptedImage(key);
5424
+ return true;
5425
+ },
5426
+ };
5427
+ };
5428
+ const releaseAcceptedImage = (correlation: { commandId: string; turnId: string }): void =>
5429
+ retireAcceptedImage(promptSubmissionKey(correlation));
5430
+ const releaseAcceptedImagesForRun = (owner: AgentTerminalOwnerContext): void => {
5431
+ for (const [key, runOwner] of acceptedImageRunOwners) {
5432
+ if (!runOwner || runOwner.resourceRunId !== owner.resourceRunId || runOwner.domain !== owner.domain)
5433
+ continue;
5434
+ retireAcceptedImage(key);
5435
+ }
5436
+ };
5437
+ const releaseAcceptedImages = (): void => {
5438
+ for (const key of acceptedImages.keys()) retireAcceptedImage(key);
5439
+ };
5148
5440
  type PromptLifecycleFrame =
5149
5441
  | {
5150
5442
  type: "agent_start" | "agent_end";
@@ -5850,7 +6142,7 @@ export function createNotificationsExtension(
5850
6142
  );
5851
6143
  };
5852
6144
  const emitPromptEvent = (event: AgentSessionEvent) => {
5853
- if (!runtime) return;
6145
+ if (!runtime || runtime.stopping || runtime.serverStopped || runtimes.get(id) !== runtime) return;
5854
6146
  cleanupPromptRecords();
5855
6147
  const handle = ctx.getActivePromptHandle();
5856
6148
  const domain = handle ? terminalAbortSeams?.getRunOwnerDomain?.(handle) : undefined;
@@ -5863,12 +6155,14 @@ export function createNotificationsExtension(
5863
6155
  )
5864
6156
  )
5865
6157
  correlations.unshift(root);
6158
+ const owners = new Set<string>();
5866
6159
  for (const correlation of correlations) {
5867
6160
  // Renewal is attributed exactly like the correlated delivery below.
5868
6161
  renewPromptDeadline(correlation, event);
5869
6162
  noteToolDispatchBoundary(correlation, event);
5870
6163
  const submission = promptSubmissions.get(promptSubmissionKey(correlation));
5871
6164
  if (!submission || submission.abandoned) continue;
6165
+ owners.add(submission.connectionId);
5872
6166
  const frame = {
5873
6167
  type: "event",
5874
6168
  kind: event.type,
@@ -5881,6 +6175,25 @@ export function createNotificationsExtension(
5881
6175
  }
5882
6176
  deliverCorrelatedFrame(submission, frame);
5883
6177
  }
6178
+ // Broker-created hosts use this bus, not the SDK-only extension. Observers
6179
+ // must opt into BOTH capabilities and receive no other client's correlation.
6180
+ // Keep cumulative partialResult snapshots and authoritative final result
6181
+ // intact, and off the durable lifecycle replay ring, like owner content.
6182
+ for (const connectionId of sdkRuntime?.connectionIdsWithCapabilities([
6183
+ TURN_STREAM_CAPABILITY,
6184
+ SESSION_HOST_OBSERVER_CAPABILITY,
6185
+ ]) ?? []) {
6186
+ if (owners.has(connectionId)) continue;
6187
+ try {
6188
+ sdkRuntime?.sendFrameTo(connectionId, {
6189
+ type: "event",
6190
+ kind: event.type,
6191
+ payload: toAgentWireEventPayload(event),
6192
+ });
6193
+ } catch {
6194
+ // Content delivery is best-effort and must not interrupt execution.
6195
+ }
6196
+ }
5884
6197
  };
5885
6198
  /**
5886
6199
  * Status of a registered deadline expiry attempt after an awaited
@@ -6200,6 +6513,7 @@ export function createNotificationsExtension(
6200
6513
  logger.warn(`sdk: failed to cancel fenced preflight: ${String(error)}`),
6201
6514
  );
6202
6515
  fencedConnections.add(submission.connectionId);
6516
+ imageUploads?.disconnect(submission.connectionId);
6203
6517
  host?.handleDisconnect(submission.connectionId);
6204
6518
  for (const [key, owned] of promptSubmissions)
6205
6519
  if (owned.connectionId === submission.connectionId) {
@@ -6232,6 +6546,21 @@ export function createNotificationsExtension(
6232
6546
  submission.executionHandle = handle;
6233
6547
  submission.preflightAbort = undefined;
6234
6548
  }
6549
+ if (acceptedImages.has(key) && !acceptedImageRunOwners.has(key)) {
6550
+ acceptedImageRunOwners.set(key, undefined);
6551
+ const domain = handle ? terminalAbortSeams?.getRunOwnerDomain?.(handle) : undefined;
6552
+ if (handle && domain) acceptedImageRunOwners.set(key, { resourceRunId: handle, domain });
6553
+ }
6554
+ const imageReservation = acceptedImages.get(key);
6555
+ if (
6556
+ imageReservation?.accepted &&
6557
+ !imageReservation.preflightSignal.aborted &&
6558
+ acceptedImageRunOwners.get(key)
6559
+ ) {
6560
+ imageReservation.awaitingAdoption = false;
6561
+ settleAcceptedImageSubmission(key, imageReservation);
6562
+ if (imageReservation.releaseRequested) retireAcceptedImage(key);
6563
+ }
6235
6564
  };
6236
6565
  const onPromptDiverted = (correlation: { commandId: string; turnId: string }) => {
6237
6566
  const submission = promptSubmissions.get(promptSubmissionKey(correlation));
@@ -6419,6 +6748,7 @@ export function createNotificationsExtension(
6419
6748
  return;
6420
6749
  }
6421
6750
  if (capture) capture.proof = proof;
6751
+ releaseAcceptedImage(correlation);
6422
6752
  }
6423
6753
  if (deadlineAttempt) {
6424
6754
  // Durability BEFORE the durable terminal (#5583, #5623 review round 4).
@@ -6637,7 +6967,8 @@ export function createNotificationsExtension(
6637
6967
  };
6638
6968
  const recordPromptFailure = async (correlation: { commandId: string; turnId: string }, error: unknown) => {
6639
6969
  const submission = promptSubmissions.get(promptSubmissionKey(correlation));
6640
- if (!submission) return;
6970
+ if (!submission || submission.terminal || submission.failed) return;
6971
+ releaseAcceptedImage(correlation);
6641
6972
  submission.failed = true;
6642
6973
  removePendingPromptCorrelation(correlation);
6643
6974
  if (
@@ -6647,6 +6978,29 @@ export function createNotificationsExtension(
6647
6978
  runtime.activePromptCorrelation = undefined;
6648
6979
  await emitPromptFailure(correlation, error);
6649
6980
  };
6981
+ const recordQueuedRunStartFailure = (
6982
+ correlation: { commandId: string; turnId: string },
6983
+ failure: QueuedRunStartFailure,
6984
+ sdkRunToken: string | undefined,
6985
+ refuseImage: (() => boolean) | undefined,
6986
+ ): boolean => {
6987
+ const key = promptSubmissionKey(correlation);
6988
+ const submission = promptSubmissions.get(key);
6989
+ // A present undefined owner is already-started execution, not no-start evidence.
6990
+ if (
6991
+ failure.disposition !== "not-started" ||
6992
+ (submission && submission.sdkRunToken !== sdkRunToken) ||
6993
+ submission?.executionHandle !== undefined ||
6994
+ acceptedImageRunOwners.has(key)
6995
+ )
6996
+ return false;
6997
+ // Reservation proof outlives the mutable receipt. The captured lifecycle
6998
+ // checks reservation identity and keeps the pending producer fenced.
6999
+ if (refuseImage ? !refuseImage() : !submission) return false;
7000
+ if (submission && !submission.terminal && !submission.failed)
7001
+ trackReconciliationProducer(recordPromptFailure(correlation, failure.error));
7002
+ return true;
7003
+ };
6650
7004
  const acknowledgePrompt = (connectionId: string, correlation: { commandId: string; turnId: string }) => {
6651
7005
  const key = promptSubmissionKey(correlation);
6652
7006
  const submission = promptSubmissions.get(key);
@@ -6679,6 +7033,7 @@ export function createNotificationsExtension(
6679
7033
  selector => kindReconciliation.lookupSteer(selector),
6680
7034
  () => host,
6681
7035
  options.primaryControlSurface ?? "sdk",
7036
+ imageUploadsAvailable,
6682
7037
  ),
6683
7038
  id,
6684
7039
  revisions,
@@ -6689,12 +7044,17 @@ export function createNotificationsExtension(
6689
7044
  pendingInteractive,
6690
7045
  gatePresentations,
6691
7046
  api,
7047
+ imageUploads,
7048
+ imageUploadsAvailable,
7049
+ retainAcceptedImage,
7050
+ releaseAcceptedImage,
6692
7051
  () =>
6693
7052
  runtime?.busy === true ||
6694
7053
  pendingPromptCorrelations.length > 0 ||
6695
7054
  pendingPromptCorrelationsBySdkRunToken.size > 0,
6696
7055
  recordPromptAccepted,
6697
7056
  recordPromptFailure,
7057
+ recordQueuedRunStartFailure,
6698
7058
  discardPromptAcceptance,
6699
7059
  onPromptDiverted,
6700
7060
  onPromptPromoted,
@@ -6758,6 +7118,7 @@ export function createNotificationsExtension(
6758
7118
  await terminalizePrompt({ commandId, turnId }, cancellationOutcome, {
6759
7119
  fence: hasExecutionHandle || !submission.preflightAbort,
6760
7120
  });
7121
+ if (!hasExecutionHandle && submission.preflightAbort) releaseAcceptedImage({ commandId, turnId });
6761
7122
  }
6762
7123
  }
6763
7124
  return { aborted: true, disposition: "cancelled" as const };
@@ -8188,6 +8549,10 @@ export function createNotificationsExtension(
8188
8549
  runtime = {
8189
8550
  server,
8190
8551
  host,
8552
+ imageUploads,
8553
+ releaseAcceptedImage,
8554
+ releaseAcceptedImagesForRun,
8555
+ releaseAcceptedImages,
8191
8556
  getJoinedPromptCorrelations,
8192
8557
  isPromptRunOwner: (correlation, owner) => {
8193
8558
  const handle = promptSubmissions.get(promptSubmissionKey(correlation))?.executionHandle;
@@ -8327,6 +8692,7 @@ export function createNotificationsExtension(
8327
8692
  string,
8328
8693
  { toolName: string; args?: unknown; pendingPhase?: "completed" | "failed" | "cancelled" }
8329
8694
  >(),
8695
+ awaitingInput: new Map(),
8330
8696
  deferredInboundControls: [],
8331
8697
  };
8332
8698
  const initializedRuntime = runtime;
@@ -8446,7 +8812,18 @@ export function createNotificationsExtension(
8446
8812
  sendEndpointStale(inbound.connectionId, typedFrame);
8447
8813
  return;
8448
8814
  }
8815
+ if (typedFrame.type === "control_request" && !liveHostConnection(inbound.connectionId)) {
8816
+ sendEndpointStale(inbound.connectionId, typedFrame);
8817
+ return;
8818
+ }
8449
8819
  if (typedFrame.type === "event_replay") {
8820
+ const capabilities = typedFrame.capabilities;
8821
+ if (
8822
+ Array.isArray(capabilities) &&
8823
+ capabilities.every((capability: unknown) => typeof capability === "string")
8824
+ ) {
8825
+ rememberHostCapabilities(inbound.connectionId, capabilities as string[], "replay");
8826
+ }
8450
8827
  if (liveHostConnection(inbound.connectionId)) hostAttachedConnections.add(inbound.connectionId);
8451
8828
  }
8452
8829
  if (typedFrame.type === "ephemeral_turn" || typedFrame.type === "ephemeral_turn_cancel") return;
@@ -8491,7 +8868,7 @@ export function createNotificationsExtension(
8491
8868
  const negotiated = tuple?.[1] ?? capabilities;
8492
8869
  if (typeof id !== "string" || !Array.isArray(negotiated)) return;
8493
8870
  const normalized = negotiated.filter((capability): capability is string => typeof capability === "string");
8494
- rememberHostCapabilities(id, normalized);
8871
+ rememberHostCapabilities(id, normalized, "native");
8495
8872
  if (liveHostCapabilities(id) === undefined) return;
8496
8873
  hostAttachedConnections.add(id);
8497
8874
  negotiatedCapabilitiesHandler?.(id, normalized);
@@ -8499,6 +8876,7 @@ export function createNotificationsExtension(
8499
8876
  server.onConnectionClose((_err, connectionId) => {
8500
8877
  if (!connectionId) return;
8501
8878
  closeHostConnection(connectionId);
8879
+ imageUploads?.disconnect(connectionId);
8502
8880
  connectionCloseHandler?.(connectionId);
8503
8881
  void controlSurface
8504
8882
  .cancelPendingPreflightsForConnection(connectionId)
@@ -9034,8 +9412,10 @@ export function createNotificationsExtension(
9034
9412
  attachedClients: () => server.clientCount(),
9035
9413
  observerClients: () => {
9036
9414
  if (workLease.isHeld()) return 0;
9037
- const observers = [...hostCapCache.keys()].filter(connectionId =>
9038
- liveHostCapabilities(connectionId)?.has(SESSION_HOST_OBSERVER_CAPABILITY),
9415
+ const observers = [...hostCapCache.entries()].filter(
9416
+ ([connectionId, entry]) =>
9417
+ liveHostCapabilities(connectionId)?.has(SESSION_HOST_OBSERVER_CAPABILITY) &&
9418
+ entry.nativeConfirmed.has(SESSION_HOST_OBSERVER_CAPABILITY),
9039
9419
  ).length;
9040
9420
  return Math.min(server.clientCount(), observers);
9041
9421
  },
@@ -9490,6 +9870,7 @@ export function createNotificationsExtension(
9490
9870
  runtime.gatePresentations?.activateDeferred(runtime.workflowGatePublicationEpoch);
9491
9871
  flushPendingFinal(runtime, runtime.id);
9492
9872
  flushPendingSettled(runtime, runtime.id);
9873
+ deliverAwaitingInput(runtime, runtime.id);
9493
9874
  for (const processControl of runtime.deferredInboundControls.splice(0)) processControl();
9494
9875
  },
9495
9876
  reproveOwnership: async binding => {
@@ -10100,6 +10481,7 @@ export function createNotificationsExtension(
10100
10481
  // The Agent's terminal owner is independent of delivery correlation. Never
10101
10482
  // borrow a successor's run handle to settle a predecessor's SDK prompts.
10102
10483
  const terminalOwner = terminalAbortSeams?.getTerminalRunOwnerForEvent?.(event);
10484
+ if (terminalOwner) rt.releaseAcceptedImagesForRun(terminalOwner);
10103
10485
  const rootCorrelation = rt.activePromptCorrelation;
10104
10486
  const correlations = terminalOwner ? rt.getJoinedPromptCorrelations(terminalOwner) : [];
10105
10487
  if (
@@ -10116,6 +10498,7 @@ export function createNotificationsExtension(
10116
10498
  // subsequent durable terminal claim fails. A fatal transport closure
10117
10499
  // clears attribution, so an unrelated later agent_end cannot settle a
10118
10500
  // predecessor's prompt correlation.
10501
+ if (terminalOwner) rt.releaseAcceptedImage(correlation);
10119
10502
  const assistants = (Array.isArray(event.messages) ? [...event.messages].reverse() : []).filter(
10120
10503
  message => message && typeof message === "object" && (message as { role?: unknown }).role === "assistant",
10121
10504
  ) as Array<{ stopReason?: unknown; errorKind?: unknown; errorCode?: unknown }>;
@@ -10391,8 +10774,18 @@ export function createNotificationsExtension(
10391
10774
  });
10392
10775
 
10393
10776
  api.on("tool_execution_update", (event, ctx) => {
10394
- const rt = runtimes.get(sessionId(ctx));
10777
+ const id = sessionId(ctx);
10778
+ const rt = runtimes.get(id);
10395
10779
  rt?.emitPromptEvent(event);
10780
+ if (!rt) return;
10781
+ const awaitingInput = readAwaitingInput(event.partialResult);
10782
+ if (!awaitingInput || rt.awaitingInput.has(event.toolCallId)) return;
10783
+ // Notify-only and content-free (prompt kind only, never the URL or code), so
10784
+ // it is delivered under redaction exactly like the idle marker. While policy
10785
+ // is provisional it stays pending; activation delivers it if the call is
10786
+ // still running.
10787
+ rt.awaitingInput.set(event.toolCallId, { toolName: event.toolName, kind: awaitingInput.kind, delivered: false });
10788
+ deliverAwaitingInput(rt, id);
10396
10789
  });
10397
10790
 
10398
10791
  api.on("tool_execution_end", (event, ctx) => {
@@ -10400,6 +10793,7 @@ export function createNotificationsExtension(
10400
10793
  const rt = runtimes.get(id);
10401
10794
  if (!rt) return;
10402
10795
  rt.emitPromptEvent(event);
10796
+ rt.awaitingInput.delete(event.toolCallId);
10403
10797
  const inFlight = rt.inFlightTools.get(event.toolCallId);
10404
10798
  if (!inFlight) return;
10405
10799
  if (!rt.notificationsActive) {