@deepseek-harness-tui/dsh-tui 0.10.2 → 0.11.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 (277) hide show
  1. package/README.md +171 -124
  2. package/bin/dsh-tui.js +748 -22
  3. package/cordis.patch.yml +38 -10
  4. package/cordis.yml +2 -0
  5. package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
  6. package/lib/types/adapter/channel/session-projection.js +2 -0
  7. package/lib/types/adapter/channel/ui-policy.d.ts +4 -1
  8. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  9. package/lib/types/adapter/channel/ui-policy.js +5 -1
  10. package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
  11. package/lib/types/adapter/kernel/host-facade.js +1 -0
  12. package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
  13. package/lib/types/adapter/kernel/replay.js +3 -0
  14. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  15. package/lib/types/adapter/kernel/runtime.js +2 -0
  16. package/lib/types/adapter/kernel/slices/workspace.d.ts.map +1 -1
  17. package/lib/types/adapter/kernel/slices/workspace.js +1 -0
  18. package/lib/types/adapter/ports/channel-ui.d.ts +22 -2
  19. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  20. package/lib/types/adapter/ports/channel-view.d.ts +66 -0
  21. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  22. package/lib/types/adapter/ports/channel-workspace.d.ts +23 -0
  23. package/lib/types/adapter/ports/channel-workspace.d.ts.map +1 -1
  24. package/lib/types/adapter/ports/workspace.d.ts +2 -0
  25. package/lib/types/adapter/ports/workspace.d.ts.map +1 -1
  26. package/lib/types/adapter/upstream/workspace-driver.d.ts.map +1 -1
  27. package/lib/types/adapter/upstream/workspace-driver.js +10 -2
  28. package/lib/types/commands.d.ts.map +1 -1
  29. package/lib/types/commands.js +1 -0
  30. package/lib/types/components/ContextBarView.d.ts +15 -7
  31. package/lib/types/components/ContextBarView.d.ts.map +1 -1
  32. package/lib/types/components/ContextBarView.js +26 -22
  33. package/lib/types/components/ImagePreviewOverlay.d.ts.map +1 -1
  34. package/lib/types/components/ImagePreviewOverlay.js +11 -5
  35. package/lib/types/components/Markdown.d.ts +11 -1
  36. package/lib/types/components/Markdown.d.ts.map +1 -1
  37. package/lib/types/components/Markdown.js +20 -2
  38. package/lib/types/components/MermaidDiagram.d.ts +13 -0
  39. package/lib/types/components/MermaidDiagram.d.ts.map +1 -0
  40. package/lib/types/components/MermaidDiagram.js +61 -0
  41. package/lib/types/components/MessageList.d.ts +26 -1
  42. package/lib/types/components/MessageList.d.ts.map +1 -1
  43. package/lib/types/components/MessageList.js +72 -7
  44. package/lib/types/components/PageMargin.d.ts +1 -1
  45. package/lib/types/components/PromptInput.d.ts +37 -1
  46. package/lib/types/components/PromptInput.d.ts.map +1 -1
  47. package/lib/types/components/PromptInput.js +243 -28
  48. package/lib/types/components/ScrollbarGutter.d.ts +20 -5
  49. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
  50. package/lib/types/components/ScrollbarGutter.js +88 -10
  51. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  52. package/lib/types/components/StreamingMarkdown.js +30 -28
  53. package/lib/types/components/Tooltip.d.ts.map +1 -1
  54. package/lib/types/components/Tooltip.js +45 -7
  55. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/UserPromptMessage.js +5 -7
  57. package/lib/types/components/messages/useImageInspection.d.ts.map +1 -1
  58. package/lib/types/components/messages/useImageInspection.js +19 -9
  59. package/lib/types/components/promptDraftCache.d.ts +102 -0
  60. package/lib/types/components/promptDraftCache.d.ts.map +1 -0
  61. package/lib/types/components/promptDraftCache.js +76 -0
  62. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +17 -1
  63. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  64. package/lib/types/components/questions/AskUserQuestionPanel.js +40 -7
  65. package/lib/types/components/questions/QuestionMinimizedBar.d.ts +12 -0
  66. package/lib/types/components/questions/QuestionMinimizedBar.d.ts.map +1 -0
  67. package/lib/types/components/questions/QuestionMinimizedBar.js +43 -0
  68. package/lib/types/components/sessions/SessionListRow.d.ts +23 -1
  69. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  70. package/lib/types/components/sessions/SessionListRow.js +52 -5
  71. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +49 -0
  72. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -0
  73. package/lib/types/components/workspaces/HomeWorkspaceRow.js +50 -0
  74. package/lib/types/dsh-adapter/bundled-presets.d.ts +10 -0
  75. package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -0
  76. package/lib/types/dsh-adapter/bundled-presets.js +80 -0
  77. package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
  78. package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
  79. package/lib/types/dsh-adapter/channel/action-readiness.js +3 -0
  80. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts +1 -1
  81. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts.map +1 -1
  82. package/lib/types/dsh-adapter/channel/agent-view-projection.js +19 -2
  83. package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
  84. package/lib/types/dsh-adapter/channel/background-action.js +52 -29
  85. package/lib/types/dsh-adapter/channel/binding.d.ts +19 -2
  86. package/lib/types/dsh-adapter/channel/binding.d.ts.map +1 -1
  87. package/lib/types/dsh-adapter/channel/binding.js +78 -18
  88. package/lib/types/dsh-adapter/channel/composer-images.d.ts +5 -0
  89. package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
  90. package/lib/types/dsh-adapter/channel/composer-images.js +105 -6
  91. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +8 -0
  92. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
  93. package/lib/types/dsh-adapter/channel/context-bookkeeping.js +3 -2
  94. package/lib/types/dsh-adapter/channel/ide-selection.d.ts +58 -0
  95. package/lib/types/dsh-adapter/channel/ide-selection.d.ts.map +1 -0
  96. package/lib/types/dsh-adapter/channel/ide-selection.js +226 -0
  97. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +6 -1
  98. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  99. package/lib/types/dsh-adapter/channel/input-delivery.js +9 -1
  100. package/lib/types/dsh-adapter/channel/local-actions.d.ts +3 -19
  101. package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
  102. package/lib/types/dsh-adapter/channel/local-actions.js +2 -1
  103. package/lib/types/dsh-adapter/channel/model-actions.d.ts +4 -1
  104. package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
  105. package/lib/types/dsh-adapter/channel/model-actions.js +82 -6
  106. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  107. package/lib/types/dsh-adapter/channel/model-switch.js +49 -31
  108. package/lib/types/dsh-adapter/channel/projection.d.ts +4 -0
  109. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  110. package/lib/types/dsh-adapter/channel/projection.js +29 -19
  111. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  112. package/lib/types/dsh-adapter/channel/reports.js +4 -12
  113. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  114. package/lib/types/dsh-adapter/channel/session-fork.js +26 -2
  115. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +3 -0
  116. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
  117. package/lib/types/dsh-adapter/channel/session-live-adoption.js +1 -0
  118. package/lib/types/dsh-adapter/channel/session-metadata.d.ts +10 -0
  119. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  120. package/lib/types/dsh-adapter/channel/session-metadata.js +2 -2
  121. package/lib/types/dsh-adapter/channel/session-resume.d.ts +11 -2
  122. package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
  123. package/lib/types/dsh-adapter/channel/session-resume.js +291 -94
  124. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  125. package/lib/types/dsh-adapter/channel/session-rewind.js +34 -16
  126. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  127. package/lib/types/dsh-adapter/channel/session-tree-actions.js +19 -3
  128. package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
  129. package/lib/types/dsh-adapter/channel/settings-host.js +4 -3
  130. package/lib/types/dsh-adapter/channel/state.d.ts +1 -1
  131. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  132. package/lib/types/dsh-adapter/channel/state.js +1 -1
  133. package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
  134. package/lib/types/dsh-adapter/channel/transcript.js +6 -12
  135. package/lib/types/dsh-adapter/channel/types.d.ts +6 -1
  136. package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
  137. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts +4 -1
  138. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
  139. package/lib/types/dsh-adapter/channel/workspace-actions.js +41 -1
  140. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  141. package/lib/types/dsh-adapter/channel.js +67 -6
  142. package/lib/types/dsh-adapter/compat/messages.d.ts +15 -0
  143. package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -0
  144. package/lib/types/dsh-adapter/compat/messages.js +15 -0
  145. package/lib/types/dsh-adapter/compat/settings.d.ts +33 -0
  146. package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -0
  147. package/lib/types/dsh-adapter/compat/settings.js +43 -0
  148. package/lib/types/dsh-adapter/compat/shell.d.ts +25 -0
  149. package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -0
  150. package/lib/types/dsh-adapter/compat/shell.js +11 -0
  151. package/lib/types/dsh-adapter/contract.d.ts +4 -4
  152. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  153. package/lib/types/dsh-adapter/contract.js +15 -6
  154. package/lib/types/dsh-adapter/ide-channel.d.ts +224 -0
  155. package/lib/types/dsh-adapter/ide-channel.d.ts.map +1 -0
  156. package/lib/types/dsh-adapter/ide-channel.js +556 -0
  157. package/lib/types/dsh-adapter/index.d.ts +18 -3
  158. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  159. package/lib/types/dsh-adapter/index.js +32 -5
  160. package/lib/types/dsh-adapter/plugin.d.ts +5 -2
  161. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  162. package/lib/types/dsh-adapter/plugin.js +173 -42
  163. package/lib/types/dsh-adapter/preset-resolution.d.ts +8 -0
  164. package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
  165. package/lib/types/dsh-adapter/preset-resolution.js +10 -0
  166. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts +53 -0
  167. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts.map +1 -0
  168. package/lib/types/dsh-adapter/session-mount-heartbeat.js +100 -0
  169. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
  170. package/lib/types/dsh-adapter/sessionTree.js +3 -2
  171. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  172. package/lib/types/dsh-adapter/subagents.js +6 -5
  173. package/lib/types/dsh-adapter/workspaces.d.ts +46 -3
  174. package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
  175. package/lib/types/dsh-adapter/workspaces.js +116 -0
  176. package/lib/types/effortPrefs.d.ts +15 -3
  177. package/lib/types/effortPrefs.d.ts.map +1 -1
  178. package/lib/types/effortPrefs.js +37 -3
  179. package/lib/types/homePrefs.d.ts +15 -0
  180. package/lib/types/homePrefs.d.ts.map +1 -0
  181. package/lib/types/homePrefs.js +53 -0
  182. package/lib/types/i18n.d.ts +257 -296
  183. package/lib/types/i18n.d.ts.map +1 -1
  184. package/lib/types/i18n.js +78 -84
  185. package/lib/types/ink/colorize.d.ts +10 -0
  186. package/lib/types/ink/colorize.d.ts.map +1 -1
  187. package/lib/types/ink/colorize.js +21 -0
  188. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  189. package/lib/types/ink/components/ScrollBox.js +12 -1
  190. package/lib/types/ink/dom.d.ts +1 -0
  191. package/lib/types/ink/dom.d.ts.map +1 -1
  192. package/lib/types/ink/hooks/use-copy-on-select.d.ts +5 -1
  193. package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
  194. package/lib/types/ink/hooks/use-copy-on-select.js +15 -1
  195. package/lib/types/ink/ink.d.ts.map +1 -1
  196. package/lib/types/ink/ink.js +36 -1
  197. package/lib/types/ink/output.d.ts +10 -1
  198. package/lib/types/ink/output.d.ts.map +1 -1
  199. package/lib/types/ink/output.js +109 -17
  200. package/lib/types/ink/render-border.d.ts +6 -1
  201. package/lib/types/ink/render-border.d.ts.map +1 -1
  202. package/lib/types/ink/render-border.js +25 -9
  203. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  204. package/lib/types/ink/render-node-to-output.js +48 -6
  205. package/lib/types/ink/renderer.d.ts +6 -0
  206. package/lib/types/ink/renderer.d.ts.map +1 -1
  207. package/lib/types/ink/renderer.js +2 -1
  208. package/lib/types/ink/selection.d.ts +60 -0
  209. package/lib/types/ink/selection.d.ts.map +1 -1
  210. package/lib/types/ink/selection.js +129 -0
  211. package/lib/types/ink/sixel-graphics.d.ts +19 -1
  212. package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
  213. package/lib/types/ink/sixel-graphics.js +124 -12
  214. package/lib/types/ink/terminal-image.d.ts +18 -1
  215. package/lib/types/ink/terminal-image.d.ts.map +1 -1
  216. package/lib/types/ink/terminal.d.ts +17 -0
  217. package/lib/types/ink/terminal.d.ts.map +1 -1
  218. package/lib/types/ink/terminal.js +20 -1
  219. package/lib/types/screens/Chat.d.ts +17 -1
  220. package/lib/types/screens/Chat.d.ts.map +1 -1
  221. package/lib/types/screens/Chat.js +284 -72
  222. package/lib/types/screens/SessionSupervisor.d.ts +84 -0
  223. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -0
  224. package/lib/types/screens/SessionSupervisor.js +350 -0
  225. package/lib/types/screens/SessionTree.js +2 -2
  226. package/lib/types/screens/Settings.d.ts.map +1 -1
  227. package/lib/types/screens/Settings.js +32 -9
  228. package/lib/types/screens/StatusLine.d.ts +13 -0
  229. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  230. package/lib/types/screens/StatusLine.js +36 -17
  231. package/lib/types/screens/StatusMetrics.d.ts +79 -22
  232. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  233. package/lib/types/screens/StatusMetrics.js +153 -88
  234. package/lib/types/screens/sessionSupervisor/model.d.ts +83 -0
  235. package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -0
  236. package/lib/types/screens/sessionSupervisor/model.js +69 -0
  237. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +120 -0
  238. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -0
  239. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +593 -0
  240. package/lib/types/sessionMounts.d.ts +272 -0
  241. package/lib/types/sessionMounts.d.ts.map +1 -0
  242. package/lib/types/sessionMounts.js +706 -0
  243. package/lib/types/sessions/format.d.ts.map +1 -1
  244. package/lib/types/sessions/format.js +4 -1
  245. package/lib/types/sessions/resumeFailure.d.ts +38 -0
  246. package/lib/types/sessions/resumeFailure.d.ts.map +1 -0
  247. package/lib/types/sessions/resumeFailure.js +58 -0
  248. package/lib/types/terminal-utils/mermaid.d.ts +44 -0
  249. package/lib/types/terminal-utils/mermaid.d.ts.map +1 -0
  250. package/lib/types/terminal-utils/mermaid.js +83 -0
  251. package/lib/types/tips.d.ts.map +1 -1
  252. package/lib/types/tips.js +175 -19
  253. package/lib/types/tuiDisplayPrefs.d.ts +7 -9
  254. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  255. package/lib/types/tuiDisplayPrefs.js +31 -28
  256. package/lib/types/update.d.ts +61 -0
  257. package/lib/types/update.d.ts.map +1 -1
  258. package/lib/types/update.js +194 -0
  259. package/lib/types/utils/imageResize.d.ts +90 -0
  260. package/lib/types/utils/imageResize.d.ts.map +1 -0
  261. package/lib/types/utils/imageResize.js +272 -0
  262. package/lib/types/utils/keymap.d.ts +5 -1
  263. package/lib/types/utils/keymap.d.ts.map +1 -1
  264. package/lib/types/utils/keymap.js +23 -0
  265. package/lib/types/workspaces.d.ts +2 -1
  266. package/lib/types/workspaces.d.ts.map +1 -1
  267. package/lib/types/workspaces.js +1 -1
  268. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +10 -10
  269. package/package.json +120 -103
  270. package/presets/liangshen/.dsh-tui-managed.json +1 -1
  271. package/presets/liangshen/agent.cordis.yml +15 -0
  272. package/lib/types/screens/AgentView.d.ts +0 -23
  273. package/lib/types/screens/AgentView.d.ts.map +0 -1
  274. package/lib/types/screens/AgentView.js +0 -546
  275. package/lib/types/screens/SessionBrowser.d.ts +0 -42
  276. package/lib/types/screens/SessionBrowser.d.ts.map +0 -1
  277. package/lib/types/screens/SessionBrowser.js +0 -911
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { rememberImagePath, transcriptImageFromAttachment } from '../transcript-images.js';
3
+ import { probeImageSize, adaptImageForAdmission, withinDimensionLimits } from '../../utils/imageResize.js';
3
4
  import { mentionAttachments } from './mentions.js';
4
5
  /** Every visible composer placeholder token, in source order. */
5
6
  export const COMPOSER_IMAGE_TOKEN = /\[Image #\d+\]/gu;
@@ -45,6 +46,24 @@ export function firstStaleComposerToken(text, ordered) {
45
46
  * content-addressed and durable; this map only connects editable
46
47
  * placeholders to them. */
47
48
  const STAGED_IMAGE_LIMIT = 128;
49
+ /** Failure text for a refusal the ingress gate can state before the store:
50
+ * every reason except a missing sharp describes an image the deployment
51
+ * would reject anyway, so the paste is refused with the cause named. */
52
+ function admissionFailure(outcome, context) {
53
+ switch (outcome.reason) {
54
+ case 'sharp-missing':
55
+ return context.probe !== null
56
+ ? `image is ${context.probe.width}×${context.probe.height} (over ${context.limits.maxImageDimension}px) and sharp is unavailable to resample it`
57
+ : `${context.mediaType} is not accepted by this profile and sharp is unavailable to convert it`;
58
+ case 'animated-unsupported':
59
+ return `resizing or converting this animated image cannot keep its frames (${outcome.detail})`;
60
+ default:
61
+ return `image could not be decoded to resize or convert it (${outcome.detail})`;
62
+ }
63
+ }
64
+ /** A store-reported pixel dimension, or the gate's own value when the store
65
+ * reported none (lean attachment fakes in the verify scripts). */
66
+ const positiveOr = (value, fallback) => typeof value === 'number' && value > 0 ? value : fallback;
48
67
  /**
49
68
  * Owns the editable-composer image capabilities: the session epoch, the
50
69
  * capability map, its preview facades and the `[Image #N]` compatibility
@@ -121,16 +140,78 @@ export function createComposerImages(ctx, owner, deps) {
121
140
  if (generation !== stagedImageEpoch) {
122
141
  throw new Error('the session changed while the image was being staged');
123
142
  }
124
- if (!attachments.imageLimits.mediaTypes.includes(input.mediaType)) {
125
- throw new Error(`${input.mediaType} images are not accepted by this profile`);
126
- }
127
143
  if (input.data.byteLength > attachments.imageLimits.maxImageBytes) {
128
144
  throw new Error(`image exceeds this profile's per-image size limit`);
129
145
  }
146
+ // Ingress adaptation (issue #938): the store admits by media type, byte
147
+ // budget and the per-side / total pixel caps, and refuses on all of them
148
+ // when the bytes reach it. Measure and re-encode HERE instead, while the
149
+ // paste can still be explained to the user. Admission is judged against
150
+ // the pair this gate HANDS OVER (the store may normalize further on its
151
+ // own — that step is outside this feature), and both triggers are
152
+ // size-independent:
153
+ // * a source media type the profile does not accept is converted, so a
154
+ // small PNG is never refused while a large one succeeds;
155
+ // * a probe-recognized image inside the caps skips sharp entirely;
156
+ // * an unrecognized format is decoded for measurement (never trusted by
157
+ // byte budget alone — the #432 gap);
158
+ // * with sharp absent the image goes to the store unchanged ONLY while
159
+ // it is still admissible as-is — an accepted format whose size the
160
+ // probe could not prove fits (upstream stays the backstop); a measured
161
+ // oversize, a decode failure, an unaccepted format or a multi-frame
162
+ // image this gate would have to re-encode is reported now instead of
163
+ // becoming a token that dies at save time.
164
+ // The runtime guards keep older attachment fakes (whose imageLimits
165
+ // predate the dimension fields) on the legacy synchronous path.
166
+ const dimensionCap = attachments.imageLimits.maxImageDimension;
167
+ const pixelCap = attachments.imageLimits.maxImagePixels;
168
+ let mediaType = input.mediaType;
169
+ let data = input.data;
170
+ let adapted;
171
+ if (typeof dimensionCap === 'number' && typeof pixelCap === 'number'
172
+ && Number.isFinite(dimensionCap) && Number.isFinite(pixelCap)) {
173
+ const dimensionLimits = { maxImageDimension: dimensionCap, maxImagePixels: pixelCap };
174
+ const probe = probeImageSize(data);
175
+ const sourceAccepted = attachments.imageLimits.mediaTypes.includes(mediaType);
176
+ // Only a probe-measured oversize is *known* to be refused by the store;
177
+ // an unmeasurable image may well be admissible, which is what keeps the
178
+ // sharp-less degradation a degradation rather than a guess.
179
+ const knownOversize = probe !== null && !withinDimensionLimits(probe, dimensionLimits);
180
+ if (!sourceAccepted || probe === null || knownOversize) {
181
+ const outcome = await adaptImageForAdmission(data, mediaType, dimensionLimits, attachments.imageLimits.mediaTypes);
182
+ if (outcome.kind === 'adapted') {
183
+ // outcome.mediaType comes from the accepted list inside the gate, so
184
+ // it is one of the profile's media types at runtime — narrowing,
185
+ // not trusting.
186
+ mediaType = outcome.mediaType;
187
+ data = outcome.data;
188
+ adapted = outcome;
189
+ if (data.byteLength > attachments.imageLimits.maxImageBytes) {
190
+ throw new Error(`image still exceeds this profile's per-image size limit after resampling`);
191
+ }
192
+ }
193
+ else if (outcome.kind === 'unavailable') {
194
+ // Degrade ONLY when the paste is still admissible as-is: an accepted
195
+ // format whose size the probe could not prove fits. Everything else
196
+ // (measured oversize, unaccepted format, decode failure, animation
197
+ // that cannot survive) is a refusal we can state now.
198
+ const degraded = outcome.reason === 'sharp-missing' && sourceAccepted && !knownOversize;
199
+ if (!degraded)
200
+ throw new Error(admissionFailure(outcome, { probe, mediaType, limits: dimensionLimits }));
201
+ }
202
+ }
203
+ }
204
+ // What the store will actually see: an accepted media type, inside the
205
+ // byte cap. A media type can still be unaccepted here only when the
206
+ // adaptation did not run (legacy limits) or could not produce one.
207
+ if (!attachments.imageLimits.mediaTypes.includes(mediaType)) {
208
+ throw new Error(`${mediaType} images are not accepted by this profile`);
209
+ }
130
210
  // The source path is TUI-side display metadata; the store only sees the
131
- // fields its contract names.
211
+ // fields its contract names. mediaType/data may be the adapted pair from
212
+ // the ingress gate above.
132
213
  const { path, ...stored } = input;
133
- const attachment = await attachments.saveImage(stored);
214
+ const attachment = await attachments.saveImage({ ...stored, mediaType, data });
134
215
  // A session change (/new, resume, rewind, model switch, background)
135
216
  // cleared the maps while the save was in flight: the durable object is
136
217
  // harmless, but the OLD session's capability must not reach the new one.
@@ -151,7 +232,23 @@ export function createComposerImages(ctx, owner, deps) {
151
232
  break;
152
233
  deleteStagedImage(oldest);
153
234
  }
154
- return { stageId };
235
+ // The adjustment travels with the capability so the composer can say what
236
+ // happened instead of letting a rewritten image reach the store silently.
237
+ // Its numbers come from the STORE's own report, not from what this gate
238
+ // handed over: the store normalizes further (dimensions, and the media type
239
+ // too), and a notice quoting the gate's numbers would contradict the
240
+ // preview card the user is looking at.
241
+ const adjustment = adapted === undefined ? undefined : {
242
+ sourceMediaType: input.mediaType,
243
+ mediaType: attachment.mediaType,
244
+ width: positiveOr(attachment.width, adapted.width),
245
+ height: positiveOr(attachment.height, adapted.height),
246
+ resized: adapted.resized
247
+ || positiveOr(attachment.width, adapted.width) !== adapted.width
248
+ || positiveOr(attachment.height, adapted.height) !== adapted.height,
249
+ flattened: adapted.flattened,
250
+ };
251
+ return adjustment === undefined ? { stageId } : { stageId, adjustment };
155
252
  };
156
253
  return {
157
254
  stagedImageGeneration() {
@@ -195,6 +292,8 @@ export function createComposerImages(ctx, owner, deps) {
195
292
  return {
196
293
  maxImageBytes: limits.maxImageBytes,
197
294
  maxImagesPerMessage: limits.maxImagesPerMessage,
295
+ maxImageDimension: limits.maxImageDimension,
296
+ maxImagePixels: limits.maxImagePixels,
198
297
  };
199
298
  },
200
299
  clearStagedImages() {
@@ -5,6 +5,14 @@ export declare function createContextBookkeeping(state: () => {
5
5
  tokens: {
6
6
  input: number;
7
7
  };
8
+ /** Last turn's billed token shape; the honest numerator for the
9
+ * context-low warning (input alone misses cached reads/writes, which
10
+ * resumed sessions replay back at full size). */
11
+ lastUsage: {
12
+ input: number;
13
+ cacheRead: number;
14
+ cacheWrite: number;
15
+ } | undefined;
8
16
  pending: PendingMessage[];
9
17
  emit(): void;
10
18
  }, notify: (text: string, options: {
@@ -1 +1 @@
1
- {"version":3,"file":"context-bookkeeping.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/context-bookkeeping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAElE,+EAA+E;AAC/E,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM;IACX,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,IAAI,IAAI,IAAI,CAAA;CACb,EACD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,EACnF,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EAC3C,mBAAmB,EAAE,MAAM;;;;+BAwBK,IAAI;+BArBJ,IAAI;4BAUL;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;KAAE,aAAa,cAAc,CAAC,WAAW,CAAC,KAAG,IAAI;gCAK7G,MAAM,KAAG,IAAI;EAQjD"}
1
+ {"version":3,"file":"context-bookkeeping.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/context-bookkeeping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAElE,+EAA+E;AAC/E,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM;IACX,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB;;qDAEiD;IACjD,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;IAC/E,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,IAAI,IAAI,IAAI,CAAA;CACb,EACD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,EACnF,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EAC3C,mBAAmB,EAAE,MAAM;;;;+BAyBK,IAAI;+BAtBJ,IAAI;4BAWL;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;KAAE,aAAa,cAAc,CAAC,WAAW,CAAC,KAAG,IAAI;gCAK7G,MAAM,KAAG,IAAI;EAQjD"}
@@ -3,9 +3,10 @@ export function createContextBookkeeping(state, notify, lowContextText, warningB
3
3
  const warning = { value: false };
4
4
  const checkContextWarning = () => {
5
5
  const channel = state();
6
- if (warning.value || channel.contextWindow === undefined)
6
+ if (warning.value || channel.contextWindow === undefined || channel.lastUsage === undefined)
7
7
  return;
8
- const remaining = channel.contextWindow - channel.tokens.input;
8
+ const used = channel.lastUsage.input + channel.lastUsage.cacheRead + channel.lastUsage.cacheWrite;
9
+ const remaining = channel.contextWindow - used;
9
10
  if (remaining >= warningBufferTokens)
10
11
  return;
11
12
  warning.value = true;
@@ -0,0 +1,58 @@
1
+ import type { ChannelSelection, SelectionAttachment } from '../../adapter/ports/channel-view.js';
2
+ import type { MentionExpansion, MentionFs } from './types.js';
3
+ /**
4
+ * Escape a path for reuse inside a quoted `<attached-file path="…">` attribute:
5
+ * POSIX filenames are legal with `"`, `&`, `<`, `>` — a raw interpolated path
6
+ * could break out of the attribute or smuggle markup into the model-facing
7
+ * block. `&` first so the replacements themselves are not re-escaped.
8
+ */
9
+ export declare function escapeSnippetAttr(value: string): string;
10
+ /**
11
+ * Build the `<attached-file path="…" selection>` block for an IDE selection:
12
+ * coordinates arrive 0-based inclusive from the extension and convert to
13
+ * sliceLines' 1-based; an endLine past EOF clamps to the last line (the model
14
+ * receives every remaining line), while a startLine past EOF yields undefined
15
+ * — the caller silently skips instead of attaching an empty or stale block.
16
+ * An empty selection is rejected here too so the guard cannot be bypassed.
17
+ */
18
+ export declare function buildSelectionBlock(selection: ChannelSelection, content: string): {
19
+ text: string;
20
+ lines: number;
21
+ } | undefined;
22
+ /**
23
+ * Append the selection's attached-file block to a message's content. Returns
24
+ * what was attached ({lines, path}) or undefined when there is nothing to
25
+ * attach — no selection, or an unresolvable/unreadable file. The caller
26
+ * passes the ENQUEUE-time snapshot (and its fs handle) so a selection made
27
+ * after submit can never attach to the wrong message.
28
+ *
29
+ * Protocol 2 carries the editor buffer's own `text` (unsaved edits
30
+ * included): when present it is attached VERBATIM and the filesystem is not
31
+ * touched — reading the disk copy would hand the model a version the user
32
+ * never saw (maintainer review round 3). The disk read remains only as the
33
+ * protocol-1 fallback.
34
+ */
35
+ export declare function attachIdeSelection(blocks: MentionExpansion['blocks'], cwd: string, selection: ChannelSelection | undefined, fs: MentionFs | undefined): Promise<SelectionAttachment | undefined>;
36
+ /**
37
+ * Derive the transcript indicator from the DURABLE user-message content:
38
+ * the `<attached-file path="…" selection count="N">` block the submit path
39
+ * appended is part of the persisted event, so a replayed session can rebuild
40
+ * the "Selected N lines from <file>" line even though the in-memory
41
+ * message-id → attachment map starts empty (maintainer review round 3: the
42
+ * session log is the source of truth — the indicator must not depend on
43
+ * process-local state).
44
+ */
45
+ export declare function replaySelectionAttachment(content: readonly unknown[] | undefined): SelectionAttachment | undefined;
46
+ /**
47
+ * Bounded map from a submitted message id → what its selection attached.
48
+ * The delivery path remembers at submit; the transcript projection reads it
49
+ * when the durable `user/message` event lands (the event carries the same
50
+ * message id) to stamp the row's indicator. FIFO-capped like the other
51
+ * row-keyed caches — ids grow monotonically and a miss only loses the
52
+ * indicator, never the attachment itself.
53
+ */
54
+ export declare function createSelectionAttachments(): {
55
+ remember(messageId: string, info: SelectionAttachment): void;
56
+ take(messageId: string): SelectionAttachment | undefined;
57
+ };
58
+ //# sourceMappingURL=ide-selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ide-selection.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/ide-selection.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AAEhG,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAuB7D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAyCD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAc7C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAClC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,gBAAgB,GAAG,SAAS,EACvC,EAAE,EAAE,SAAS,GAAG,SAAS,GACxB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAoC1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GACtC,mBAAmB,GAAG,SAAS,CA8BjC;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,IAAI;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAA;IAC5D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAA;CACzD,CAeA"}
@@ -0,0 +1,226 @@
1
+ /**
2
+ * IDE selection consumption: turn a live editor-selection snapshot into the
3
+ * model-facing `<attached-file … selection>` block. Protocol 2 pushes carry
4
+ * the editor buffer's own selection text — that is attached verbatim; the
5
+ * disk read below remains only as the protocol-1 fallback, and every failure
6
+ * mode is silent (an IDE-side extra must never block a send).
7
+ *
8
+ * The block builder never goes through text parsing (unlike `@`-mentions):
9
+ * selection paths may contain spaces no tokenizer could survive. It reuses
10
+ * the same line-slice semantics and size-cap policy as mention attachments.
11
+ */
12
+ import { isAbsolute, join } from 'node:path';
13
+ import { MENTION_MAX_FILE_CHARS } from './mentions.js';
14
+ /**
15
+ * Slice `content` into the 1-based inclusive [`startLine`, `endLine`] range.
16
+ * Returns undefined for malformed ranges (non-safe integers, zero/negative
17
+ * start, inverted) and for a start past EOF — counted against REAL lines: a
18
+ * trailing newline splits into a phantom final element that is not a line.
19
+ */
20
+ function sliceLines(content, startLine, endLine) {
21
+ const end = endLine ?? startLine;
22
+ if (!Number.isSafeInteger(startLine) || startLine < 1)
23
+ return undefined;
24
+ if (!Number.isSafeInteger(end) || end < startLine)
25
+ return undefined;
26
+ const lines = content.split('\n');
27
+ // A trailing newline splits into a phantom final element that is not a line.
28
+ if (lines.length > 1 && lines[lines.length - 1] === '')
29
+ lines.pop();
30
+ if (startLine > lines.length)
31
+ return undefined;
32
+ return lines.slice(startLine - 1, end).join('\n');
33
+ }
34
+ /**
35
+ * Escape a path for reuse inside a quoted `<attached-file path="…">` attribute:
36
+ * POSIX filenames are legal with `"`, `&`, `<`, `>` — a raw interpolated path
37
+ * could break out of the attribute or smuggle markup into the model-facing
38
+ * block. `&` first so the replacements themselves are not re-escaped.
39
+ */
40
+ export function escapeSnippetAttr(value) {
41
+ return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
42
+ }
43
+ /**
44
+ * Format the attached-file block from an ALREADY-SLICED body: cap it like
45
+ * @-mention attachments (same MENTION_MAX_FILE_CHARS policy, same visible
46
+ * truncation marker) and count the lines the model gets — the truncated body,
47
+ * not the request (review round 3).
48
+ *
49
+ * `allowEmpty` keeps a selection that covers ONE blank line: the body is
50
+ * legitimately empty there while the badge still says "1 line selected", and
51
+ * dropping the block made the footer and the transcript disagree (review
52
+ * round 7). Callers pass it only for a single-line range that really exists.
53
+ */
54
+ function cappedSelectionBlock(path, sliced, allowEmpty = false) {
55
+ if (sliced === '' && !allowEmpty)
56
+ return undefined;
57
+ let body = sliced;
58
+ let attached = sliced;
59
+ if (body.length > MENTION_MAX_FILE_CHARS) {
60
+ attached = body.slice(0, MENTION_MAX_FILE_CHARS);
61
+ body = `${attached}\n[… truncated]`;
62
+ }
63
+ // `count` is the number of lines in the attached BODY — the truncation
64
+ // marker line is not counted (it rides on top of the cap and the live
65
+ // indicator reports the same number). It exists so the replay path never has
66
+ // to GUESS that number from the body tail: a selection whose last line is
67
+ // literally `[… truncated]` used to lose one line when the indicator was
68
+ // rebuilt from the persisted event (fuzz rounds found 46/400 such bodies).
69
+ // Deliberately NOT `lines`: @-mention attachments already use `lines="2-4"`
70
+ // for the REQUESTED range, and overloading the key with a count would make
71
+ // the two block flavours read alike.
72
+ const lines = attached.split('\n').length;
73
+ return {
74
+ text: `<attached-file path="${escapeSnippetAttr(path)}" selection count="${lines}">\n${body}\n</attached-file>`,
75
+ lines,
76
+ };
77
+ }
78
+ /**
79
+ * Build the `<attached-file path="…" selection>` block for an IDE selection:
80
+ * coordinates arrive 0-based inclusive from the extension and convert to
81
+ * sliceLines' 1-based; an endLine past EOF clamps to the last line (the model
82
+ * receives every remaining line), while a startLine past EOF yields undefined
83
+ * — the caller silently skips instead of attaching an empty or stale block.
84
+ * An empty selection is rejected here too so the guard cannot be bypassed.
85
+ */
86
+ export function buildSelectionBlock(selection, content) {
87
+ if (selection.isEmpty)
88
+ return undefined;
89
+ // 0-based inclusive [start, end] → 1-based inclusive [start+1, end+1].
90
+ // sliceLines clamps an oversized end itself (Array#slice bounds); a start
91
+ // past EOF returns undefined and the whole attach is silently dropped.
92
+ const sliced = sliceLines(content, selection.startLine + 1, selection.endLine + 1);
93
+ if (sliced === undefined)
94
+ return undefined;
95
+ // sliceLines returning '' (not undefined) means the requested line EXISTS and
96
+ // is blank — a single blank line is a real selection the badge reports.
97
+ return cappedSelectionBlock(selection.path, sliced, selection.startLine === selection.endLine);
98
+ }
99
+ /**
100
+ * Append the selection's attached-file block to a message's content. Returns
101
+ * what was attached ({lines, path}) or undefined when there is nothing to
102
+ * attach — no selection, or an unresolvable/unreadable file. The caller
103
+ * passes the ENQUEUE-time snapshot (and its fs handle) so a selection made
104
+ * after submit can never attach to the wrong message.
105
+ *
106
+ * Protocol 2 carries the editor buffer's own `text` (unsaved edits
107
+ * included): when present it is attached VERBATIM and the filesystem is not
108
+ * touched — reading the disk copy would hand the model a version the user
109
+ * never saw (maintainer review round 3). The disk read remains only as the
110
+ * protocol-1 fallback.
111
+ */
112
+ export async function attachIdeSelection(blocks, cwd, selection, fs) {
113
+ if (selection === undefined || selection.isEmpty)
114
+ return undefined;
115
+ if (selection.text !== undefined) {
116
+ // Protocol 2: `text` is the editor's OWN text for the selection
117
+ // (document.getText(selection)) — the ABSOLUTE document coordinates ride
118
+ // along for the badge/indicator, but they must NOT slice this string:
119
+ // it already contains exactly the selected lines, so anything not
120
+ // starting at line 0 would be mis-cut or dropped entirely (review
121
+ // round 4). Attach it verbatim with the shared size cap. A full-line
122
+ // selection ends at column 0 of the NEXT line, so getText hands back a
123
+ // trailing '\n' — strip that one terminator or the body grows a phantom
124
+ // blank line and the indicator over-counts by one (review round 5).
125
+ let text = selection.text;
126
+ if (text.endsWith('\n'))
127
+ text = text.slice(0, -1);
128
+ // Triple-clicking ONE blank line hands back "\n": after the strip the body
129
+ // is empty but a line WAS selected (the footer badge says so) — keep the
130
+ // block so the indicator and the badge agree instead of attaching nothing.
131
+ const block = cappedSelectionBlock(selection.path, text, selection.text !== '');
132
+ if (block === undefined)
133
+ return undefined;
134
+ blocks.push({ type: 'text', text: block.text });
135
+ return { lines: block.lines, path: selection.path };
136
+ }
137
+ if (fs === undefined)
138
+ return undefined;
139
+ try {
140
+ const absolute = isAbsolute(selection.path) ? selection.path : join(cwd, selection.path);
141
+ const target = await fs.resolve(absolute);
142
+ const info = await fs.stat(target);
143
+ if (info?.type !== 'file')
144
+ return undefined;
145
+ const content = await fs.readText(target);
146
+ const block = buildSelectionBlock(selection, content);
147
+ if (block === undefined)
148
+ return undefined;
149
+ blocks.push({ type: 'text', text: block.text });
150
+ return { lines: block.lines, path: selection.path };
151
+ }
152
+ catch {
153
+ return undefined;
154
+ }
155
+ }
156
+ /**
157
+ * Derive the transcript indicator from the DURABLE user-message content:
158
+ * the `<attached-file path="…" selection count="N">` block the submit path
159
+ * appended is part of the persisted event, so a replayed session can rebuild
160
+ * the "Selected N lines from <file>" line even though the in-memory
161
+ * message-id → attachment map starts empty (maintainer review round 3: the
162
+ * session log is the source of truth — the indicator must not depend on
163
+ * process-local state).
164
+ */
165
+ export function replaySelectionAttachment(content) {
166
+ if (content === undefined)
167
+ return undefined;
168
+ for (const block of content) {
169
+ if (block === null || typeof block !== 'object')
170
+ continue;
171
+ const text = block.text;
172
+ if (block.type !== 'text' || typeof text !== 'string')
173
+ continue;
174
+ const opened = /^<attached-file path="([^"]+)" selection(?: count="(\d+)")?>\n/.exec(text);
175
+ if (opened === null)
176
+ continue;
177
+ let closed = text.slice(opened[0].length);
178
+ if (closed.endsWith('</attached-file>'))
179
+ closed = closed.slice(0, -'</attached-file>'.length);
180
+ // The builder writes `body\n</attached-file>` — strip the newline the
181
+ // close tag rode on, or every body counts one phantom line.
182
+ if (closed.endsWith('\n'))
183
+ closed = closed.slice(0, -1);
184
+ const lines = closed.split('\n').length;
185
+ // `count` is authoritative when present (the builder always writes it).
186
+ // Blocks persisted before it existed fall back to the old tail heuristic,
187
+ // which misreads a body whose last line is literally the truncation marker.
188
+ const counted = opened[2] === undefined ? undefined : Number(opened[2]);
189
+ return {
190
+ lines: counted ?? (closed.endsWith('\n[… truncated]') ? lines - 1 : lines),
191
+ // Reverse escapeSnippetAttr so the replayed indicator shows the path
192
+ // exactly as the live one did (`&` first so entities are not re-baked).
193
+ path: opened[1]
194
+ .replace(/&lt;/g, '<')
195
+ .replace(/&gt;/g, '>')
196
+ .replace(/&quot;/g, '"')
197
+ .replace(/&amp;/g, '&'),
198
+ };
199
+ }
200
+ return undefined;
201
+ }
202
+ /**
203
+ * Bounded map from a submitted message id → what its selection attached.
204
+ * The delivery path remembers at submit; the transcript projection reads it
205
+ * when the durable `user/message` event lands (the event carries the same
206
+ * message id) to stamp the row's indicator. FIFO-capped like the other
207
+ * row-keyed caches — ids grow monotonically and a miss only loses the
208
+ * indicator, never the attachment itself.
209
+ */
210
+ export function createSelectionAttachments() {
211
+ const byMessageId = new Map();
212
+ return {
213
+ remember(messageId, info) {
214
+ byMessageId.set(messageId, info);
215
+ while (byMessageId.size > 128) {
216
+ const oldest = byMessageId.keys().next().value;
217
+ if (oldest === undefined)
218
+ break;
219
+ byMessageId.delete(oldest);
220
+ }
221
+ },
222
+ take(messageId) {
223
+ return byMessageId.get(messageId);
224
+ },
225
+ };
226
+ }
@@ -3,6 +3,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent';
3
3
  import { type UserMessage } from '@deepseek-ai/dsh-llm';
4
4
  import { type ComposerImages } from './composer-images.js';
5
5
  import type { ChannelOwner } from './owner.js';
6
+ import type { ChannelSelection, SelectionAttachment } from '../../adapter/ports/channel-view.js';
6
7
  import type { ChannelImageBlock, ChannelState, ComposerImageRef, MentionAttachments, MentionFs, PendingMessage, StagedImageInput } from './types.js';
7
8
  /** One submission's enqueue-time world: the session it was typed in, the
8
9
  * services that resolve its references, and the capabilities live then. */
@@ -14,6 +15,10 @@ interface UserTextOrigin {
14
15
  readonly fs: MentionFs | undefined;
15
16
  readonly attachments: MentionAttachments | undefined;
16
17
  readonly stagedImages: ReadonlyMap<string, ChannelImageBlock['attachment']>;
18
+ /** Live editor selection AT ENQUEUE (undefined = none). Captured here so a
19
+ * selection made while the FIFO or mention expansion parks the delivery
20
+ * can never attach to this message. */
21
+ readonly selection: ChannelSelection | undefined;
17
22
  }
18
23
  /** Input FIFO, staged attachments and decision notice timers share one lifetime. */
19
24
  export declare function createInputDelivery(ctx: Context, owner: ChannelOwner, binding: {
@@ -22,7 +27,7 @@ export declare function createInputDelivery(ctx: Context, owner: ChannelOwner, b
22
27
  id: string;
23
28
  text: string;
24
29
  images?: readonly ComposerImageRef[];
25
- }, placement: PendingMessage['placement']) => void, untrackPending: (id: string) => void, composer: ComposerImages): {
30
+ }, placement: PendingMessage['placement']) => void, untrackPending: (id: string) => void, composer: ComposerImages, selection: () => ChannelSelection | undefined, rememberSelection: (messageId: string, info: SelectionAttachment) => void): {
26
31
  dispatchUserText: (text: string, placement: PendingMessage["placement"], images?: readonly ComposerImageRef[], attach?: UserMessage) => void;
27
32
  deliverUserText: (text: string, placement: PendingMessage["placement"], images?: readonly ComposerImageRef[], attach?: UserMessage) => void;
28
33
  claimAttachments: (messages: readonly UserMessage[]) => UserMessage[];
@@ -1 +1 @@
1
- {"version":3,"file":"input-delivery.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/input-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAI1E,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAA;AAI7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAEnB;4EAC4E;AAC5E,UAAU,cAAc;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACpD,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAA;CAC5E;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAClC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,EACrE,KAAK,EAAE,MAAM,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,SAAS,GAAG,wBAAwB,CAAC,EAC7E,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC9B,YAAY,EAAE,CAAC,OAAO,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE,EAAE,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,IAAI,EAC3I,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACpC,QAAQ,EAAE,cAAc;6BA4Qf,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,WAC1B,WAAW,KACnB,IAAI;4BAcC,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,WAC1B,WAAW,KACnB,IAAI;iCA5Q6B,SAAS,WAAW,EAAE,KAAG,WAAW,EAAE;kCAarC,MAAM,KAAG,IAAI;0BAiIrB,CAAC,QAAQ,MAAM,WAAW,OAAO,CAAC,CAAC,CAAC,WAAW,cAAc,KAAG,OAAO,CAAC,CAAC,CAAC;6BAkIzE,IAAI;wBAaZ,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC;wBACpC,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;;EAGzE"}
1
+ {"version":3,"file":"input-delivery.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/input-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAI1E,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAA;AAK7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AAChG,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAEnB;4EAC4E;AAC5E,UAAU,cAAc;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACpD,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAA;IAC3E;;4CAEwC;IACxC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAA;CACjD;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAClC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,EACrE,KAAK,EAAE,MAAM,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,SAAS,GAAG,wBAAwB,CAAC,EAC7E,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC9B,YAAY,EAAE,CAAC,OAAO,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE,EAAE,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,IAAI,EAC3I,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACpC,QAAQ,EAAE,cAAc,EACxB,SAAS,EAAE,MAAM,gBAAgB,GAAG,SAAS,EAC7C,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,KAAK,IAAI;6BAkRhE,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,WAC1B,WAAW,KACnB,IAAI;4BAcC,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,WAC1B,WAAW,KACnB,IAAI;iCAlR6B,SAAS,WAAW,EAAE,KAAG,WAAW,EAAE;kCAarC,MAAM,KAAG,IAAI;0BAuIrB,CAAC,QAAQ,MAAM,WAAW,OAAO,CAAC,CAAC,CAAC,WAAW,cAAc,KAAG,OAAO,CAAC,CAAC,CAAC;6BAkIzE,IAAI;wBAaZ,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC;wBACpC,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;;EAGzE"}
@@ -5,9 +5,10 @@ import { dispatchTuiDecision } from '../extension-events.js';
5
5
  import { firstStaleComposerToken, formatMissingReference, orderedComposerImages, } from './composer-images.js';
6
6
  import { expandComposerMentions } from './composer-mentions.js';
7
7
  import { normalizeInputDecision } from './decisions.js';
8
+ import { attachIdeSelection } from './ide-selection.js';
8
9
  import { mentionAttachments, mentionFs } from './mentions.js';
9
10
  /** Input FIFO, staged attachments and decision notice timers share one lifetime. */
10
- export function createInputDelivery(ctx, owner, binding, state, notify, trackPending, untrackPending, composer) {
11
+ export function createInputDelivery(ctx, owner, binding, state, notify, trackPending, untrackPending, composer, selection, rememberSelection) {
11
12
  /**
12
13
  * `@` file mentions (issue #15): expansion reads files asynchronously, so
13
14
  * every user-text delivery (submit / steer / interrupt-requeue) funnels
@@ -82,6 +83,7 @@ export function createInputDelivery(ctx, owner, binding, state, notify, trackPen
82
83
  fs: mentionFs(ctx),
83
84
  attachments: mentionAttachments(ctx),
84
85
  stagedImages: composer.snapshot(),
86
+ selection: selection(),
85
87
  });
86
88
  /**
87
89
  * Expand the text's `@` mentions and deliver ONE user message: the typed
@@ -107,10 +109,16 @@ export function createInputDelivery(ctx, owner, binding, state, notify, trackPen
107
109
  notify(t('ext-stale-dropped'), { color: 'warning', timeoutMs: 4000 });
108
110
  return;
109
111
  }
112
+ // IDE selection consumption: append the ENQUEUE-time selection as its own
113
+ // attached-file block — direct construction, never text parsing, failures
114
+ // silently skipped (an IDE-side extra must never block a send).
115
+ const selectionAttached = await attachIdeSelection(expansion.blocks, origin.cwd, origin.selection, origin.fs);
110
116
  const message = createUserMessage({
111
117
  content: expansion.blocks,
112
118
  source: { kind: 'user' },
113
119
  });
120
+ if (selectionAttached !== undefined)
121
+ rememberSelection(message.id, selectionAttached);
114
122
  // The message is real from here on: remember its attached context BEFORE
115
123
  // the agent call so the pre-step listener can find it (D6). A throwing
116
124
  // followup/steer rolls both the pending preview and this entry back.
@@ -1,21 +1,6 @@
1
+ import { type ForegroundShell } from '../compat/shell.js';
1
2
  import { type foldBack as FoldBack } from './transcript.js';
2
3
  import type { ChannelState, ToolViewPresenter } from './types.js';
3
- type Shell = {
4
- resolve(request: {
5
- command: string;
6
- workdir?: string;
7
- timeoutMs: number;
8
- }): unknown;
9
- run(spec: unknown): Promise<{
10
- stdout: {
11
- text: string;
12
- };
13
- stderr: {
14
- text: string;
15
- };
16
- timedOut: boolean;
17
- }>;
18
- };
19
4
  /** Owns local-only transcript mutations, shell output, and live subagent queries. */
20
5
  export declare function createLocalActions(deps: {
21
6
  ctx: {
@@ -54,9 +39,9 @@ export declare function createLocalActions(deps: {
54
39
  badge: string;
55
40
  label: string;
56
41
  };
57
- commandShell(cwd: string): Promise<Shell | undefined>;
42
+ commandShell(cwd: string): Promise<ForegroundShell | undefined>;
58
43
  };
59
- shell?: Shell;
44
+ shell?: ForegroundShell;
60
45
  notify: ChannelState['notify'];
61
46
  }): {
62
47
  loadOlder(): number;
@@ -66,5 +51,4 @@ export declare function createLocalActions(deps: {
66
51
  listSubagents(): Promise<string[]>;
67
52
  runLocalCommand(command: string, includeInContext: boolean): Promise<void>;
68
53
  };
69
- export {};
70
54
  //# sourceMappingURL=local-actions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"local-actions.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/local-actions.ts"],"names":[],"mappings":"AAOA,OAAO,EAA+B,KAAK,QAAQ,IAAI,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEjE,KAAK,KAAK,GAAG;IACX,OAAO,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAA;IACnF,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CACvG,CAAA;AAED,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,GAAG,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IACnC,KAAK,EAAE;QAAE,OAAO,IAAI,OAAO,CAAA;KAAE,CAAA;IAC7B,OAAO,EAAE;QACP,OAAO,IAAI,OAAO,CAAA;QAClB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;QACpC,QAAQ,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;SAAE,CAAA;KACvE,CAAA;IACD,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB,SAAS,EAAE;QAAE,KAAK,IAAI,IAAI,CAAC;QAAC,eAAe,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,iBAAiB,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;IAClJ,SAAS,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAA;IAC/B,IAAI,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAA;IAC1B,QAAQ,EAAE,OAAO,QAAQ,CAAA;IACzB,SAAS,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAA;KAAE,CAAA;IAC3I,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;CAC/B;iBAIgB,MAAM;aAKV,IAAI;qBAYI,MAAM,SAAS,SAAS,MAAM,EAAE,GAAG,IAAI;4BAKhC,MAAM,GAAG,OAAO;qBAajB,OAAO,CAAC,MAAM,EAAE,CAAC;6BAmBT,MAAM,oBAAoB,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;EAqBnF"}
1
+ {"version":3,"file":"local-actions.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/local-actions.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAA+B,KAAK,QAAQ,IAAI,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEjE,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,GAAG,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IACnC,KAAK,EAAE;QAAE,OAAO,IAAI,OAAO,CAAA;KAAE,CAAA;IAC7B,OAAO,EAAE;QACP,OAAO,IAAI,OAAO,CAAA;QAClB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;QACpC,QAAQ,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;SAAE,CAAA;KACvE,CAAA;IACD,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB,SAAS,EAAE;QAAE,KAAK,IAAI,IAAI,CAAC;QAAC,eAAe,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,iBAAiB,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;IAClJ,SAAS,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAA;IAC/B,IAAI,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAA;IAC1B,QAAQ,EAAE,OAAO,QAAQ,CAAA;IACzB,SAAS,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAA;KAAE,CAAA;IACrJ,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;CAC/B;iBAIgB,MAAM;aAKV,IAAI;qBAYI,MAAM,SAAS,SAAS,MAAM,EAAE,GAAG,IAAI;4BAKhC,MAAM,GAAG,OAAO;qBAajB,OAAO,CAAC,MAAM,EAAE,CAAC;6BAmBT,MAAM,oBAAoB,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;EAqBnF"}
@@ -4,6 +4,7 @@ import { writeActivityFrames } from '../../activityPrefs.js';
4
4
  import { isPresetName, normalizeActivityPreset } from '../../components/activityFrames.js';
5
5
  import { t } from '../../i18n.js';
6
6
  import { snapshotLiveSessionEvents } from '../compat/liveSession.js';
7
+ import { runForegroundShell } from '../compat/shell.js';
7
8
  import { LOCAL_OUTPUT_LIMIT, preview } from './transcript.js';
8
9
  /** Owns local-only transcript mutations, shell output, and live subagent queries. */
9
10
  export function createLocalActions(deps) {
@@ -90,7 +91,7 @@ export function createLocalActions(deps) {
90
91
  return;
91
92
  if (executor) {
92
93
  try {
93
- const result = await executor.run(executor.resolve({ command, workdir: cwd, timeoutMs: 30000 }));
94
+ const result = await runForegroundShell(executor, { command, workdir: cwd, timeoutMs: 30000 });
94
95
  output = result.stdout.text.trim() || result.stderr.text.trim() || (result.timedOut ? '(timed out)' : '(no output)');
95
96
  }
96
97
  catch (error) {
@@ -11,13 +11,16 @@ type Binding = ReturnType<typeof createChannelBinding>;
11
11
  * This owns the async catalog caches and their generation fences; callers only
12
12
  * compose its surface into ChannelState after the state has been constructed.
13
13
  */
14
- export declare function createModelActions(ctx: Context, state: Pick<ChannelState, 'provider' | 'model' | 'reasoningEffort' | 'effortLevels' | 'agentPreset' | 'working' | 'emit'>, deps: {
14
+ export declare function createModelActions(ctx: Context, state: Pick<ChannelState, 'provider' | 'model' | 'reasoningEffort' | 'effortLevels' | 'agentPreset' | 'working' | 'contextWindow' | 'emit'>, deps: {
15
15
  owner: Pick<ChannelOwner, 'current'>;
16
16
  binding: Pick<Binding, 'capture' | 'isCurrent'>;
17
17
  selection: ModelSelectionRef;
18
18
  initialEffort?: string;
19
19
  agent(): Agent;
20
20
  notify: ChannelState['notify'];
21
+ /** Re-check the context-low warning after a refreshed route metadata
22
+ * answer carries a newer contextWindow than the session started with. */
23
+ checkContextWarning(): void;
21
24
  }): {
22
25
  selection: ModelSelectionRef;
23
26
  applyPreferredEffort: () => Promise<void>;