@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,4 +1,4 @@
1
- import type { AgentHandle } from '@deepseek-ai/dsh-agent';
1
+ import type { Agent, AgentHandle } from '@deepseek-ai/dsh-agent';
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
3
  import { type SessionEvent } from '@deepseek-ai/dsh-session';
4
4
  import { resetSessionProjection } from './session-reset.js';
@@ -22,7 +22,13 @@ export declare function createSessionResumeActions(ctx: Context, state: ResumeSt
22
22
  model: string;
23
23
  }, deps: {
24
24
  owner: Pick<ChannelOwner, 'current'>;
25
- binding: Pick<Binding, 'agent' | 'capture' | 'isCurrent' | 'prepare' | 'abandon' | 'adopt'>;
25
+ binding: Pick<Binding, 'agent' | 'capture' | 'isCurrent' | 'prepare' | 'abandon' | 'adopt' | 'waitForDisposal'>;
26
+ /**
27
+ * Adopt an agent this process already has live. `/resume` uses it so a
28
+ * target that is already running here is re-attached in place (parking the
29
+ * session left behind) instead of being resumed a second time from its log.
30
+ */
31
+ adoptLive(target: Agent): Promise<ResumeResult>;
26
32
  backgroundHandles: Map<string, AgentHandle>;
27
33
  rowIds: {
28
34
  value: number;
@@ -41,6 +47,9 @@ export declare function createSessionResumeActions(ctx: Context, state: ResumeSt
41
47
  refreshLoadedContext(): Promise<void>;
42
48
  refreshSkillCommands(): Promise<void>;
43
49
  clearStagedImages(): void;
50
+ /** Drop the live IDE selection: the adopted session's cwd differs from
51
+ * the one the selection was made in. */
52
+ resetIdeSelection(): void;
44
53
  settleCompaction(): Promise<void>;
45
54
  sessionSwitchVetoed(kind: 'new' | 'resume' | 'agent-view', targetSessionId?: string): Promise<boolean>;
46
55
  notify: ChannelState['notify'];
@@ -1 +1 @@
1
- {"version":3,"file":"session-resume.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/session-resume.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-resume.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/session-resume.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAsB,MAAM,wBAAwB,CAAA;AAEpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAmBvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAgC,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAC1G,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE5D,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtD,KAAK,WAAW,GAAG,IAAI,CACrB,YAAY,EACV,SAAS,GACT,QAAQ,GACR,SAAS,GACT,KAAK,GACL,YAAY,GACZ,aAAa,GACb,UAAU,GACV,OAAO,GACP,eAAe,GACf,eAAe,GACf,cAAc,GACd,iBAAiB,GACjB,SAAS,GACT,MAAM,CACT,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAA;AAEhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAgBD,8DAA8D;AAC9D,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE;IACP,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,EACD,IAAI,EAAE;IACJ,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACpC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,iBAAiB,CAAC,CAAA;IAC/G;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC/C,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IAC3C,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB,cAAc,IAAI,IAAI,CAAA;IACtB,cAAc,IAAI,IAAI,CAAA;IACtB,SAAS,IAAI,IAAI,CAAA;IACjB,MAAM,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI,CAAA;IAC7C,YAAY,IAAI,IAAI,CAAA;IACpB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxD,gBAAgB,IAAI,IAAI,CAAA;IACxB,SAAS,IAAI,IAAI,CAAA;IACjB,eAAe,IAAI,IAAI,CAAA;IACvB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,iBAAiB,IAAI,IAAI,CAAA;IACzB;6CACyC;IACzC,iBAAiB,IAAI,IAAI,CAAA;IACzB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,mBAAmB,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,YAAY,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACtG,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9B,qBAAqB,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChH,OAAO,EAAE,qBAAqB,CAAA;CAC/B;4BA+LoC,MAAM,QAAQ,QAAQ,GAAG,YAAY,eAAe,OAAO,KAAG,OAAO,CAAC,YAAY,CAAC;0BAiDrF,MAAM,KAAG,OAAO,CAAC,YAAY,CAAC;sBA4K1C,OAAO,CAAC,OAAO,CAAC;oCA7GM,gBAAgB,KAAG,OAAO,CAAC,OAAO,CAAC;EAgHjF"}
@@ -1,11 +1,13 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { SessionId } from '@deepseek-ai/dsh-session';
3
3
  import { explicitModelRoute, recordedModelRoute, resolveModelRoute, validateModelRoute } from '../../modelRoute.js';
4
- import { clearResumeTarget, touchAgentViewSession, touchSession, writeResumeTarget } from '../../sessionHistory.js';
4
+ import { clearResumeTarget, writeResumeTarget, touchAgentViewSession, touchSession } from '../../sessionHistory.js';
5
+ import { mountFailureText } from '../../sessions/resumeFailure.js';
5
6
  import { t } from '../../i18n.js';
6
7
  import { readModelPref } from '../../modelPrefs.js';
7
8
  import { migratePresetPref, readPresetPref } from '../../presetPrefs.js';
8
9
  import { agentViewHasTurns } from '../agent-view.js';
10
+ import { occupancyOf, readSessionOwners, reserveMount, reserveNewSession, } from '../../sessionMounts.js';
9
11
  import { ensureLegacySessionEventTypes } from '../compat/index.js';
10
12
  import { snapshotLiveSessionEvents } from '../compat/liveSession.js';
11
13
  import { composePreset, resolvePersistedPreset, resolvePersistedRoute } from '../presets.js';
@@ -39,96 +41,272 @@ export function createSessionResumeActions(ctx, state, options, deps) {
39
41
  void deps.refreshSkillCommands();
40
42
  };
41
43
  /** Resume a persisted session, optionally parking the attached agent for agent-view attachment. */
42
- const resume = async (sessionId, kind, keepCurrent, adoption, entrySession) => {
43
- const agents = ctx.get('agents');
44
- if (!agents) {
45
- deps.notify(t('resume-unavailable'), { color: 'error' });
46
- return { ok: false, reason: 'unavailable' };
47
- }
48
- ensureLegacySessionEventTypes();
49
- const composed = await composePreset(ctx, await resolvePersistedPreset(ctx, SessionId(sessionId)));
50
- const explicitRoute = explicitModelRoute({ provider: options.configuredProvider, model: options.configuredModel });
51
- const persistedRoute = await resolvePersistedRoute(ctx, SessionId(sessionId));
52
- let handle;
53
- try {
54
- handle = await deps.binding.prepare(adoption, () => agents.resume({
55
- resumeSessionId: SessionId(sessionId),
56
- agentOptions: {
57
- provider: explicitRoute?.provider ?? persistedRoute?.provider,
58
- model: explicitRoute?.model ?? persistedRoute?.model,
59
- },
60
- ...(composed.setup === undefined ? {} : { setup: composed.setup }),
61
- }));
62
- }
63
- catch (error) {
64
- const message = error instanceof Error ? error.message : String(error);
65
- deps.notify(t('resume-failed', { err: message }), { color: 'error', timeoutMs: 8000 });
66
- return { ok: false, reason: 'failed', error: message };
67
- }
68
- if (!deps.binding.isCurrent(adoption)) {
69
- await deps.binding.abandon(handle);
70
- return { ok: false, reason: 'cancelled' };
71
- }
44
+ const resume = async (sessionId, kind, keepCurrent, adoption, entrySession,
45
+ /**
46
+ * The ledger reservation this call holds for `sessionId`; ended by this
47
+ * function on every exit. `settle` on a commit (the agent is in the
48
+ * registry from then on), `abandon` on every path that ends WITHOUT one —
49
+ * including a throwing `binding.adopt()` and a setup failure inside its
50
+ * tail, which revokes the candidate.
51
+ */
52
+ reservation) => {
53
+ /**
54
+ * The reservation is ended on EVERY exit from here, so the attempt owns it
55
+ * for the whole body: `settle` on a commit (the agent is in the registry
56
+ * from then on), `abandon` on everything else — a throwing
57
+ * `binding.adopt()`, a setup failure inside its tail, and also a throw from
58
+ * the preset/route reads below, which used to sit outside any guard and
59
+ * would have left a pin that republishes the session as ours on every beat.
60
+ */
61
+ let committed = false;
72
62
  try {
73
- await attachSessionToWorkspace(ctx, handle.agent.session.header.cwd ?? state.cwd, SessionId(sessionId));
63
+ return await runResume();
74
64
  }
75
- catch (error) {
76
- deps.notify(t('resume-attach-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'warning', timeoutMs: 8000 });
65
+ finally {
66
+ if (committed)
67
+ reservation?.settle();
68
+ else
69
+ reservation?.abandon();
77
70
  }
78
- if (entrySession !== undefined && (!deps.owner.current() || deps.binding.agent.session !== entrySession)) {
79
- await deps.binding.abandon(handle);
80
- deps.notify(t('resume-session-changed'), { color: 'error' });
81
- return { ok: false, reason: 'failed', error: 'live session changed during resume' };
82
- }
83
- return deps.binding.adopt(handle, adoption, (committed, disposePrevious) => {
84
- const previousSessionId = String(committed.agent.session.id);
85
- state.cwd = handle.agent.session.header.cwd ?? state.cwd;
86
- state.displayCwd = deps.describeWorkspace(state.cwd).description ?? state.cwd;
87
- deps.refreshGitBranch();
88
- // Reset the input FIFO and pending-decision indicators BEFORE the first
89
- // emit (main's bind → clear → refresh order); see the /new tail.
90
- deps.clearStagedImages();
91
- resetAndBind(handle, composed.agentPreset, explicitRoute ?? recordedModelRoute(snapshotLiveSessionEvents(handle.agent.session)), true);
92
- writeResumeTarget(sessionId);
93
- touchSession(sessionId);
94
- state.emit();
95
- const keepPrevious = keepCurrent && committed.handle !== undefined
96
- && (committed.handle.agent.status === 'running' || agentViewHasTurns(snapshotLiveSessionEvents(committed.handle.agent.session)));
97
- if (committed.handle !== undefined) {
98
- if (keepPrevious) {
99
- deps.backgroundHandles.set(previousSessionId, committed.handle);
100
- disposePrevious('park');
71
+ async function runResume() {
72
+ const agents = ctx.get('agents');
73
+ if (!agents) {
74
+ deps.notify(t('resume-unavailable'), { color: 'error' });
75
+ return { ok: false, reason: 'unavailable' };
76
+ }
77
+ ensureLegacySessionEventTypes();
78
+ const composed = await composePreset(ctx, await resolvePersistedPreset(ctx, SessionId(sessionId)));
79
+ const explicitRoute = explicitModelRoute({ provider: options.configuredProvider, model: options.configuredModel });
80
+ const persistedRoute = await resolvePersistedRoute(ctx, SessionId(sessionId));
81
+ let handle;
82
+ try {
83
+ handle = await deps.binding.prepare(adoption, () => agents.resume({
84
+ resumeSessionId: SessionId(sessionId),
85
+ agentOptions: {
86
+ provider: explicitRoute?.provider ?? persistedRoute?.provider,
87
+ model: explicitRoute?.model ?? persistedRoute?.model,
88
+ },
89
+ ...(composed.setup === undefined ? {} : { setup: composed.setup }),
90
+ }));
91
+ }
92
+ catch (error) {
93
+ const message = error instanceof Error ? error.message : String(error);
94
+ deps.notify(t('resume-failed', { err: message }), { color: 'error', timeoutMs: 8000 });
95
+ return { ok: false, reason: 'failed', error: message };
96
+ }
97
+ if (!deps.binding.isCurrent(adoption)) {
98
+ await deps.binding.abandon(handle);
99
+ return { ok: false, reason: 'cancelled' };
100
+ }
101
+ try {
102
+ await attachSessionToWorkspace(ctx, handle.agent.session.header.cwd ?? state.cwd, SessionId(sessionId));
103
+ }
104
+ catch (error) {
105
+ deps.notify(t('resume-attach-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'warning', timeoutMs: 8000 });
106
+ }
107
+ if (entrySession !== undefined && (!deps.owner.current() || deps.binding.agent.session !== entrySession)) {
108
+ await deps.binding.abandon(handle);
109
+ deps.notify(t('resume-session-changed'), { color: 'error' });
110
+ return { ok: false, reason: 'failed', error: 'live session changed during resume' };
111
+ }
112
+ // `adopt` is a transaction: it revokes the candidate and disposes it when
113
+ // the tail fails, and it can also refuse before the tail ever runs. Only a
114
+ // normal return is a commit, which is what the outer finally keys on.
115
+ const result = deps.binding.adopt(handle, adoption, (committedBinding, disposePrevious) => {
116
+ const previousSessionId = String(committedBinding.agent.session.id);
117
+ state.cwd = handle.agent.session.header.cwd ?? state.cwd;
118
+ state.displayCwd = deps.describeWorkspace(state.cwd).description ?? state.cwd;
119
+ deps.resetIdeSelection();
120
+ deps.refreshGitBranch();
121
+ // Reset the input FIFO and pending-decision indicators BEFORE the first
122
+ // emit (main's bind → clear → refresh order); see the /new tail.
123
+ deps.clearStagedImages();
124
+ resetAndBind(handle, composed.agentPreset, explicitRoute ?? recordedModelRoute(snapshotLiveSessionEvents(handle.agent.session)), true);
125
+ writeResumeTarget(sessionId);
126
+ touchSession(sessionId);
127
+ state.emit();
128
+ const keepPrevious = keepCurrent && committedBinding.handle !== undefined
129
+ && (committedBinding.handle.agent.status === 'running' || agentViewHasTurns(snapshotLiveSessionEvents(committedBinding.handle.agent.session)));
130
+ if (committedBinding.handle !== undefined) {
131
+ if (keepPrevious) {
132
+ deps.backgroundHandles.set(previousSessionId, committedBinding.handle);
133
+ disposePrevious('park');
134
+ }
135
+ else {
136
+ disposePrevious('dispose');
137
+ }
101
138
  }
102
- else {
103
- disposePrevious('dispose');
139
+ if (kind === 'agent-view') {
140
+ touchAgentViewSession(sessionId);
141
+ touchAgentViewSession(previousSessionId);
104
142
  }
143
+ deps.notifySessionSwitched(kind, sessionId, previousSessionId);
144
+ return { ok: true };
145
+ });
146
+ committed = true;
147
+ return result;
148
+ }
149
+ };
150
+ /**
151
+ * Reserve `sessionId` for a disk resume on THIS terminal.
152
+ *
153
+ * Returning a reservation means the caller owns the session in the ledger
154
+ * until it calls `finish()`. That covers the whole attempt, not just the
155
+ * `agents.resume` call: the awaits in between (veto, compaction, preset and
156
+ * route reads) are exactly the window a publisher beat — or a peer — could
157
+ * otherwise slip into.
158
+ * @param sessionId - Session about to be resumed from disk.
159
+ * @returns The reservation, or the refusal to hand back to the caller.
160
+ */
161
+ const reserveDiskResume = async (sessionId) => {
162
+ // Cheap pre-filter for the friendly toast: the authoritative conflict test
163
+ // runs inside reserveMount, under the ledger lock.
164
+ const occupancy = occupancyOf(sessionId, readSessionOwners());
165
+ if (occupancy.kind === 'occupied') {
166
+ deps.notify(t('resume-session-occupied', { pid: occupancy.pid }), { color: 'error', timeoutMs: 8000 });
167
+ return { ok: false, result: { ok: false, reason: 'occupied', pid: occupancy.pid } };
168
+ }
169
+ const reserved = await reserveMount(sessionId);
170
+ if (!reserved.ok) {
171
+ if (reserved.reason === 'occupied') {
172
+ const pid = reserved.holders[0] ?? 0;
173
+ deps.notify(t('resume-session-occupied', { pid }), { color: 'error', timeoutMs: 8000 });
174
+ return { ok: false, result: { ok: false, reason: 'occupied', pid } };
105
175
  }
106
- if (kind === 'agent-view') {
107
- touchAgentViewSession(sessionId);
108
- touchAgentViewSession(previousSessionId);
109
- }
110
- deps.notifySessionSwitched(kind, sessionId, previousSessionId);
111
- return { ok: true };
112
- });
176
+ // Both of these are refusals, not conflicts, and neither may be dressed
177
+ // up as "held by pid 0". The generic failure branch carries the sentence.
178
+ return { ok: false, result: { ok: false, reason: 'failed', error: mountFailureText(reserved) } };
179
+ }
180
+ return { ok: true, reservation: reserved.reservation };
113
181
  };
114
- const resumeInto = (sessionId, kind, keepCurrent) => resume(sessionId, kind, keepCurrent, deps.binding.capture());
115
- /** `/resume` keeps the entry capture across its veto/compaction awaits, so a rival switch cannot adopt over newer state. */
116
- const resumeTo = async (sessionId) => {
182
+ /**
183
+ * Reserve a freshly minted session id for a create on THIS terminal. See
184
+ * `reserveNewSession`: a new id cannot conflict, so a refusal is a loss of
185
+ * announcement rather than a reason to stop, and it is warned about.
186
+ * @param sessionId - Session id minted for the new agent.
187
+ * @returns The reservation (a no-op when the claim could not be made).
188
+ */
189
+ const reserveCreatedSession = async (sessionId) => {
190
+ const { reservation, failure } = await reserveNewSession(sessionId);
191
+ if (failure !== undefined && failure.reason !== 'occupied') {
192
+ deps.notify(mountFailureText(failure), { color: 'warning', timeoutMs: 8000 });
193
+ }
194
+ return reservation;
195
+ };
196
+ const resumeInto = async (sessionId, kind, keepCurrent) => {
117
197
  const adoption = deps.binding.capture();
118
- if (state.working) {
119
- deps.notify(t('resume-while-working'), { color: 'warning' });
120
- return { ok: false, reason: 'working' };
198
+ const agents = ctx.get('agents');
199
+ if (!agents) {
200
+ deps.notify(t('resume-unavailable'), { color: 'error' });
201
+ return { ok: false, reason: 'unavailable' };
121
202
  }
203
+ // Same reservation as `/resume`: this is the other public door onto the
204
+ // disk path (`ChannelUi.attachToAgent`), and a mount that skips the ledger
205
+ // is a mount a peer can race from the outside.
206
+ const reserved = await reserveDiskResume(sessionId);
207
+ if (!reserved.ok)
208
+ return reserved.result;
209
+ return resume(sessionId, kind, keepCurrent, adoption, undefined, reserved.reservation);
210
+ };
211
+ /**
212
+ * `/resume` — mount a session on THIS terminal, the same non-destructive way
213
+ * `/agentview` does.
214
+ *
215
+ * There used to be two different mount models behind these two commands.
216
+ * `/agentview` parked the session it left behind (it kept running in this
217
+ * process and was reachable again from the view), while `/resume` DISPOSED
218
+ * it, and refused outright while a turn was running. That split was never a
219
+ * decision about resuming; it was an accident of the two commands growing
220
+ * separately, and it is what made `/resume` feel like a different product
221
+ * from the session overview sitting one command away.
222
+ *
223
+ * The unified model is the overview's, because it is the one that matches
224
+ * what a TUI terminal actually is: one process that can host several agent
225
+ * sessions at once. So:
226
+ *
227
+ * - Leaving a session parks it (`keepCurrent`), it does not end it, and the
228
+ * parked handle is reachable from the session screen afterwards.
229
+ * - A running turn is not a refusal. The user is switching what they are
230
+ * LOOKING at, not asking the model to stop; the turn keeps running in the
231
+ * background and its row keeps reporting progress.
232
+ *
233
+ * The one thing that IS refused is a session another TUI process already has
234
+ * mounted: two processes driving one append-only log interleave its events.
235
+ * The occupancy check happens BEFORE the resume awaits (which yield), and
236
+ * the claim is published immediately after it so this process owns the
237
+ * session before any other process can pass the same check.
238
+ *
239
+ * The reservation also outlives those awaits: it is released only when the
240
+ * attempt ends without a commit. The publisher's beat derives its set from
241
+ * the agent registry, which does not list the target yet, so a reservation
242
+ * that was not held across them could be erased by this process's own
243
+ * heartbeat and handed straight to a peer.
244
+ */
245
+ const resumeTo = async (sessionId) => {
246
+ const adoption = deps.binding.capture();
122
247
  const agents = ctx.get('agents');
123
248
  if (!agents) {
124
249
  deps.notify(t('resume-unavailable'), { color: 'error' });
125
250
  return { ok: false, reason: 'unavailable' };
126
251
  }
127
- if (await deps.sessionSwitchVetoed('resume', sessionId))
252
+ // The attached session as of entry, so a rival switch that commits while
253
+ // the awaits below yield cannot be adopted over.
254
+ const entrySession = deps.binding.agent.session;
255
+ // Already attached: this is a no-op, not a switch.
256
+ //
257
+ // The live-adoption path below hands `backgroundHandles.get(targetId)` to
258
+ // `binding.switchTo()`, and a session this terminal is CURRENTLY attached
259
+ // to has no entry there — its handle is the binding's own `currentHandle`.
260
+ // Calling it with `undefined` therefore parks nothing, and the adoption's
261
+ // default disposition disposes the handle that IS running, so a second
262
+ // Enter on the `current` row stopped the live turn while reporting success.
263
+ // `attachToAgent()` has always short-circuited here; this is the same rule
264
+ // for the unified screen's `/resume` path.
265
+ if (String(sessionId) === String(entrySession.id))
266
+ return { ok: true };
267
+ // Switching away starts disposal without blocking the synchronous commit.
268
+ // Do not adopt that closing Agent (or reopen its still-held JSONL writer).
269
+ await deps.binding.waitForDisposal(sessionId);
270
+ if (!deps.binding.isCurrent(adoption))
271
+ return { ok: false, reason: 'cancelled' };
272
+ // A live agent of this process is already mounted here; there is nothing
273
+ // to claim and nothing that can be occupied. Adoption takes the live
274
+ // handle (parking the current one) with no occupancy round-trip.
275
+ const live = agents.get?.(SessionId(sessionId));
276
+ let reservation;
277
+ if (live === undefined) {
278
+ const reserved = await reserveDiskResume(sessionId);
279
+ if (!reserved.ok)
280
+ return reserved.result;
281
+ reservation = reserved.reservation;
282
+ }
283
+ if (await deps.sessionSwitchVetoed('resume', sessionId)) {
284
+ reservation?.abandon();
128
285
  return { ok: false, reason: 'cancelled' };
286
+ }
129
287
  await deps.settleCompaction();
130
- const entrySession = deps.binding.agent.session;
131
- return resume(sessionId, 'resume', false, adoption, entrySession);
288
+ if (!deps.binding.isCurrent(adoption) || deps.binding.agent.session !== entrySession) {
289
+ reservation?.abandon();
290
+ return { ok: false, reason: 'cancelled' };
291
+ }
292
+ // The target was read BEFORE those awaits. Re-read it in BOTH directions:
293
+ // the registry can have replaced or dropped that agent while we yielded
294
+ // (adopting the captured object would hand the screen a session nothing
295
+ // owns any more), and it can also have GROWN one — a peer action mounting
296
+ // this very session here means the disk path below would resume a log this
297
+ // process is already driving. `agent-view-projection.attach` has always made
298
+ // the first check.
299
+ const liveNow = agents.get?.(SessionId(sessionId));
300
+ if (liveNow !== live) {
301
+ reservation?.abandon();
302
+ return { ok: false, reason: 'cancelled' };
303
+ }
304
+ // A live target is adopted in place — the same path `/agentview` uses, so
305
+ // the session being left is parked rather than disposed of. Only a target
306
+ // with no live agent here goes back to the persistence backend.
307
+ if (live !== undefined)
308
+ return deps.adoptLive(live);
309
+ return resume(sessionId, 'agent-view', true, adoption, entrySession, reservation);
132
310
  };
133
311
  const newSessionWithTarget = async (target) => {
134
312
  // The typed workspace target seam still creates a real Agent/session; it
@@ -181,6 +359,10 @@ export function createSessionResumeActions(ctx, state, options, deps) {
181
359
  }
182
360
  return false;
183
361
  }
362
+ // Reserve BEFORE the factory runs: the moment `agents.create` returns, this
363
+ // process holds the only write handle on a log no peer has been told about
364
+ // yet, and the publisher would not name it until its next beat.
365
+ const reservation = await reserveCreatedSession(sessionId);
184
366
  let handle;
185
367
  try {
186
368
  handle = await deps.binding.prepare(adoption, () => agents.create({
@@ -191,6 +373,7 @@ export function createSessionResumeActions(ctx, state, options, deps) {
191
373
  }));
192
374
  }
193
375
  catch (error) {
376
+ reservation.abandon();
194
377
  if (current()) {
195
378
  const message = error instanceof Error ? error.message : String(error);
196
379
  deps.notify(t('new-session-failed', { err: message }), { color: 'error', timeoutMs: 8000 });
@@ -199,6 +382,7 @@ export function createSessionResumeActions(ctx, state, options, deps) {
199
382
  }
200
383
  if (!current()) {
201
384
  await deps.binding.abandon(handle);
385
+ reservation.abandon();
202
386
  return false;
203
387
  }
204
388
  try {
@@ -210,30 +394,43 @@ export function createSessionResumeActions(ctx, state, options, deps) {
210
394
  }
211
395
  if (!current()) {
212
396
  await deps.binding.abandon(handle);
397
+ reservation.abandon();
213
398
  return false;
214
399
  }
215
400
  // Do not catch this synchronous commit tail. A post-commit setup failure
216
401
  // is owned by binding.adopt(), which revokes the new live handle and must
217
402
  // reject its caller rather than masquerade as an ordinary precommit false.
218
- return deps.binding.adopt(handle, adoption, (committed, disposePrevious) => {
219
- const previousSessionId = String(committed.agent.session.id);
220
- // The target only becomes shared channel state inside the successful
221
- // adoption tail. A losing prepared handle therefore cannot publish or
222
- // roll back another workspace's cwd.
223
- state.cwd = targetCwd;
224
- state.displayCwd = targetDisplayCwd ?? deps.describeWorkspace(targetCwd).description ?? targetCwd;
225
- // Reset the input FIFO and the pending-decision indicators BEFORE the
226
- // first emit: a submit enqueued from a session-changed subscriber must
227
- // land on a fresh chain instead of behind the replaced session's parked
228
- // promise (main's bind → clear → refresh order).
229
- deps.clearStagedImages();
230
- resetAndBind(handle, composed.agentPreset, route, false);
231
- clearResumeTarget();
232
- touchSession(handle.agent.id);
233
- disposePrevious('dispose');
234
- deps.notifySessionSwitched('new', String(handle.agent.id), previousSessionId);
235
- return true;
236
- });
403
+ let committed = false;
404
+ try {
405
+ const result = deps.binding.adopt(handle, adoption, (committedBinding, disposePrevious) => {
406
+ const previousSessionId = String(committedBinding.agent.session.id);
407
+ // The target only becomes shared channel state inside the successful
408
+ // adoption tail. A losing prepared handle therefore cannot publish or
409
+ // roll back another workspace's cwd.
410
+ state.cwd = targetCwd;
411
+ state.displayCwd = targetDisplayCwd ?? deps.describeWorkspace(targetCwd).description ?? targetCwd;
412
+ deps.resetIdeSelection();
413
+ // Reset the input FIFO and the pending-decision indicators BEFORE the
414
+ // first emit: a submit enqueued from a session-changed subscriber must
415
+ // land on a fresh chain instead of behind the replaced session's parked
416
+ // promise (main's bind → clear → refresh order).
417
+ deps.clearStagedImages();
418
+ resetAndBind(handle, composed.agentPreset, route, false);
419
+ clearResumeTarget();
420
+ touchSession(handle.agent.id);
421
+ disposePrevious('dispose');
422
+ deps.notifySessionSwitched('new', String(handle.agent.id), previousSessionId);
423
+ return true;
424
+ });
425
+ committed = true;
426
+ return result;
427
+ }
428
+ finally {
429
+ if (committed)
430
+ reservation.settle();
431
+ else
432
+ reservation.abandon();
433
+ }
237
434
  };
238
435
  const newSession = () => newSessionWithTarget();
239
436
  return { resumeInto, resumeTo, newSession, newSessionWithTarget };
@@ -1 +1 @@
1
- {"version":3,"file":"session-rewind.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/session-rewind.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,wBAAwB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAQvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEvD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtD,KAAK,WAAW,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,OAAO,CAAC,CAAA;AAgB/E,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE;IACJ,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACpC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC,CAAA;IACjF,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9B,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,YAAY,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAAA;IAC1K,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1F,IAEa,KAAK,OAAO,EAAE,OAAM,MAAM,GAAG,IAAW,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwFhF"}
1
+ {"version":3,"file":"session-rewind.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/session-rewind.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,wBAAwB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AASvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEvD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtD,KAAK,WAAW,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,OAAO,CAAC,CAAA;AAgB/E,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE;IACJ,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACpC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC,CAAA;IACjF,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9B,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,YAAY,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAAA;IAC1K,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1F,IAEa,KAAK,OAAO,EAAE,OAAM,MAAM,GAAG,IAAW,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsGhF"}
@@ -6,6 +6,7 @@ import { dispatchTuiDecision } from '../extension-events.js';
6
6
  import { normalizeRewindDoneSummary } from './decisions.js';
7
7
  import { composePreset, runningPresetOf } from '../presets.js';
8
8
  import { attachSessionToWorkspace } from '../workspace.js';
9
+ import { reserveNewSession } from '../../sessionMounts.js';
9
10
  async function waitForTurnEnd(session, fromSeq, timeoutMs) {
10
11
  const deadline = Date.now() + timeoutMs;
11
12
  while (Date.now() < deadline) {
@@ -65,6 +66,10 @@ export function createRewindToAction(ctx, state, deps) {
65
66
  return null;
66
67
  }
67
68
  const composed = await composePreset(ctx, runningPresetOf(deps.binding.agent.session));
69
+ // Announce the id before the factory: the rewind creates the child's log
70
+ // here, and the publisher only learns the id from the registry on its next
71
+ // beat.
72
+ const { reservation } = await reserveNewSession(String(childId));
68
73
  let handle;
69
74
  try {
70
75
  handle = await deps.binding.prepare(adoption, () => agents.create(liveSessionCreateOptions({
@@ -87,11 +92,13 @@ export function createRewindToAction(ctx, state, deps) {
87
92
  })));
88
93
  }
89
94
  catch {
95
+ reservation.abandon();
90
96
  deps.notify(t('rewind-create-failed'), { color: 'error' });
91
97
  return null;
92
98
  }
93
99
  if (!deps.binding.isCurrent(adoption)) {
94
100
  await deps.binding.abandon(handle);
101
+ reservation.abandon();
95
102
  return null;
96
103
  }
97
104
  try {
@@ -102,27 +109,38 @@ export function createRewindToAction(ctx, state, deps) {
102
109
  }
103
110
  if (!deps.owner.current()) {
104
111
  await deps.binding.abandon(handle);
112
+ reservation.abandon();
105
113
  return null;
106
114
  }
107
- const sourceSessionId = deps.adoptForkedAgent(handle, adoption, snapshotLiveSessionEvents(handle.agent.session), composed.agentPreset, childId);
115
+ // `adoptForkedAgent` is the commit: the child is the session this process
116
+ // now drives, so the reservation hands over to the registry. It THROWS when
117
+ // the adoption transaction revokes the candidate, so it is guarded.
108
118
  try {
109
- void dispatchTuiDecision(ctx, 'tui/rewind-done', {
110
- text: row.text,
111
- mode,
112
- boundarySeq: boundary,
113
- sourceSessionId,
114
- childSessionId: String(childId),
115
- sessionId: String(childId),
116
- cwd: state.cwd,
117
- }, normalizeRewindDoneSummary).then(summary => {
118
- if (typeof summary === 'string')
119
- deps.notify(summary, { timeoutMs: 6000 });
120
- }).catch((error) => ctx.logger.warn('dsh-tui: tui/rewind-done dispatch failed: %o', error));
119
+ const sourceSessionId = deps.adoptForkedAgent(handle, adoption, snapshotLiveSessionEvents(handle.agent.session), composed.agentPreset, childId);
120
+ reservation.settle();
121
+ try {
122
+ void dispatchTuiDecision(ctx, 'tui/rewind-done', {
123
+ text: row.text,
124
+ mode,
125
+ boundarySeq: boundary,
126
+ sourceSessionId,
127
+ childSessionId: String(childId),
128
+ sessionId: String(childId),
129
+ cwd: state.cwd,
130
+ }, normalizeRewindDoneSummary).then(summary => {
131
+ if (typeof summary === 'string')
132
+ deps.notify(summary, { timeoutMs: 6000 });
133
+ }).catch((error) => ctx.logger.warn('dsh-tui: tui/rewind-done dispatch failed: %o', error));
134
+ }
135
+ catch (error) {
136
+ ctx.logger.warn('dsh-tui: tui/rewind-done dispatch failed: %o', error);
137
+ }
138
+ deps.notifySessionSwitched('rewind', String(childId), sourceSessionId);
139
+ return row.text;
121
140
  }
122
141
  catch (error) {
123
- ctx.logger.warn('dsh-tui: tui/rewind-done dispatch failed: %o', error);
142
+ reservation.abandon();
143
+ throw error;
124
144
  }
125
- deps.notifySessionSwitched('rewind', String(childId), sourceSessionId);
126
- return row.text;
127
145
  };
128
146
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session-tree-actions.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/session-tree-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,wBAAwB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AASvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtD,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,OAAO,CAAC,CAAA;AAYnF,+GAA+G;AAC/G,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE;IACJ,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACpC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC,CAAA;IACjF,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9B,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,YAAY,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAAA;IAC1K,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACnG,IAEa,WAAW,MAAM,EAAE,KAAK,MAAM,EAAE,OAAM,QAAQ,GAAG,MAAiB,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4G1G"}
1
+ {"version":3,"file":"session-tree-actions.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/session-tree-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,wBAAwB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAUvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtD,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,OAAO,CAAC,CAAA;AAYnF,+GAA+G;AAC/G,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE;IACJ,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IACpC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC,CAAA;IACjF,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9B,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,YAAY,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAAA;IAC1K,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACnG,IAEa,WAAW,MAAM,EAAE,KAAK,MAAM,EAAE,OAAM,QAAQ,GAAG,MAAiB,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyH1G"}