zidane-ui 9.1.13 → 9.1.16

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 (290) hide show
  1. package/dist/adapters/index.d.ts +2 -2
  2. package/dist/adapters/index.js +2 -2
  3. package/dist/adapters/live.d.ts +29 -3
  4. package/dist/adapters/live.js +9 -1
  5. package/dist/adapters/server-composer-host.js +17 -2
  6. package/dist/adapters/server-extensions-host.d.ts +0 -1
  7. package/dist/adapters/server-extensions-host.js +4 -15
  8. package/dist/adapters/server-git-host.js +7 -16
  9. package/dist/adapters/server-host.js +181 -43
  10. package/dist/adapters/server-machines-host.d.ts +2 -0
  11. package/dist/adapters/server-machines-host.js +22 -23
  12. package/dist/adapters/server-onboarding-host.d.ts +1 -1
  13. package/dist/adapters/server-onboarding-host.js +3 -4
  14. package/dist/adapters/server-session-host.js +22 -3
  15. package/dist/adapters/server-settings-accounts.js +2 -2
  16. package/dist/adapters/server-settings-host.js +2 -2
  17. package/dist/adapters/server-settings-performance.js +7 -9
  18. package/dist/adapters/server-settings-sections.js +13 -14
  19. package/dist/adapters/server-sidebar-host.js +2 -2
  20. package/dist/adapters/sse-host.js +68 -3
  21. package/dist/adapters/sse-protocol.d.ts +12 -2
  22. package/dist/adapters/sse-protocol.js +4 -2
  23. package/dist/app/AppHostProvider.js +4 -3
  24. package/dist/app/ModalProvider.d.ts +13 -3
  25. package/dist/app/ModalProvider.js +103 -24
  26. package/dist/app/NoticeStack.d.ts +33 -0
  27. package/dist/app/NoticeStack.js +90 -0
  28. package/dist/app/index.d.ts +3 -2
  29. package/dist/app/index.js +3 -2
  30. package/dist/chat/SessionFilterMenu.d.ts +1 -1
  31. package/dist/chat/SessionFilterMenu.js +2 -1
  32. package/dist/chat/composer/Composer.js +10 -8
  33. package/dist/chat/composer/ModelChainList.js +17 -8
  34. package/dist/chat/composer/ModelChainTooltip.js +5 -2
  35. package/dist/chat/composer/use-draft-target.d.ts +20 -1
  36. package/dist/chat/composer/use-draft-target.js +121 -5
  37. package/dist/chat/composer/use-new-session-defaults.d.ts +5 -1
  38. package/dist/chat/composer/use-new-session-defaults.js +17 -4
  39. package/dist/chat/headless/chip-labels.d.ts +1 -0
  40. package/dist/chat/headless/chip-labels.js +1 -0
  41. package/dist/chat/headless/composer/labels-editor.d.ts +0 -2
  42. package/dist/chat/headless/composer/labels-editor.js +0 -2
  43. package/dist/chat/headless/draft-target.d.ts +21 -1
  44. package/dist/chat/headless/fixture-host.js +17 -2
  45. package/dist/chat/headless/host.d.ts +3 -3
  46. package/dist/chat/headless/index.d.ts +9 -9
  47. package/dist/chat/headless/index.js +2 -2
  48. package/dist/chat/headless/labels.d.ts +13 -6
  49. package/dist/chat/headless/labels.js +10 -9
  50. package/dist/chat/headless/session-list.d.ts +20 -8
  51. package/dist/chat/headless/session-list.js +1 -0
  52. package/dist/chat/headless/sidebar/directory.d.ts +56 -14
  53. package/dist/chat/headless/sidebar/directory.js +149 -70
  54. package/dist/chat/headless/sidebar/index.d.ts +3 -3
  55. package/dist/chat/headless/sidebar/index.js +2 -2
  56. package/dist/chat/headless/sidebar/labels.d.ts +15 -8
  57. package/dist/chat/headless/sidebar/types.d.ts +3 -1
  58. package/dist/chat/headless/sidebar/view.d.ts +13 -5
  59. package/dist/chat/headless/sidebar/view.js +31 -11
  60. package/dist/chat/headless/streams/actions.d.ts +5 -3
  61. package/dist/chat/headless/streams/actions.js +3 -2
  62. package/dist/chat/headless/streams/index.d.ts +3 -3
  63. package/dist/chat/headless/streams/merge.d.ts +6 -10
  64. package/dist/chat/headless/streams/merge.js +7 -21
  65. package/dist/chat/headless/streams/session-streams.d.ts +29 -4
  66. package/dist/chat/headless/streams/session-streams.js +86 -0
  67. package/dist/chat/headless/streams/store.d.ts +162 -16
  68. package/dist/chat/headless/streams/store.js +334 -38
  69. package/dist/chat/headless/streams/types.d.ts +10 -7
  70. package/dist/chat/headless/transcript-labels.d.ts +1 -0
  71. package/dist/chat/interactions/PlanCard.js +10 -3
  72. package/dist/chat/interactions/QuestionForm.js +7 -3
  73. package/dist/chat/interactions/card-ui.d.ts +5 -3
  74. package/dist/chat/interactions/card-ui.js +9 -7
  75. package/dist/chat/rich-composer/RichComposer.js +1 -7
  76. package/dist/chat/rich-composer/composer-extensions.d.ts +0 -2
  77. package/dist/chat/rich-composer/composer-extensions.js +2 -2
  78. package/dist/chat/rich-composer/use-composer-editor.d.ts +0 -2
  79. package/dist/chat/rich-composer/use-composer-editor.js +1 -2
  80. package/dist/chat/sidebar/SessionSidebar.js +5 -5
  81. package/dist/chat/sidebar/SidebarFilterMenu.js +3 -1
  82. package/dist/chat/sidebar/SidebarFrame.d.ts +4 -0
  83. package/dist/chat/sidebar/SidebarFrame.js +27 -9
  84. package/dist/chat/sidebar/SidebarGroupHeader.js +1 -1
  85. package/dist/chat/sidebar/SidebarHostProvider.d.ts +8 -5
  86. package/dist/chat/sidebar/SidebarHostProvider.js +13 -9
  87. package/dist/chat/sidebar/SidebarToggleButton.js +7 -3
  88. package/dist/chat/sidebar/index.d.ts +2 -2
  89. package/dist/chat/sidebar/session-menu.js +4 -4
  90. package/dist/chat/sidebar/useSaveViewAsSpace.js +2 -2
  91. package/dist/chat/testing/contract.js +2 -2
  92. package/dist/chat/transcript/AuthFailureCard.js +11 -8
  93. package/dist/chat/transcript/BadgeMenu.js +13 -2
  94. package/dist/chat/transcript/MermaidDiagram.js +7 -3
  95. package/dist/chat/transcript/fold.d.ts +4 -0
  96. package/dist/chat/transcript/fold.js +53 -22
  97. package/dist/chat/transcript/script-input.d.ts +1 -1
  98. package/dist/chat/transcript/shell-block.js +7 -3
  99. package/dist/chat/transcript/useStickToBottom.js +12 -0
  100. package/dist/components/BrowserSignIn.js +23 -21
  101. package/dist/components/Form.d.ts +6 -4
  102. package/dist/components/Form.js +16 -8
  103. package/dist/components/LoadError.d.ts +4 -2
  104. package/dist/components/LoadError.js +13 -9
  105. package/dist/components/Notice.d.ts +29 -0
  106. package/dist/components/Notice.js +63 -0
  107. package/dist/components/PanelEmpty.d.ts +1 -1
  108. package/dist/components/PanelEmpty.js +4 -2
  109. package/dist/components/Restricted.d.ts +1 -1
  110. package/dist/components/palette/PaletteList.js +8 -1
  111. package/dist/components/palette/PalettePanel.js +14 -6
  112. package/dist/core.css +24 -5
  113. package/dist/dist/{index-Bq87OcGy.d.ts → index-BZM8tu4L.d.ts} +28 -3
  114. package/dist/dist/index.d.ts +2 -2
  115. package/dist/dist/{timeout-B5pO5I5a.d.ts → timeout-Cyx3VGPz.d.ts} +1 -1
  116. package/dist/extensions/ExtensionSettingsPage.js +1 -4
  117. package/dist/extensions/ExtensionSettingsSection.js +4 -3
  118. package/dist/extensions/ExtensionSettingsSidebar.js +2 -6
  119. package/dist/extensions/SettingsWireForm.d.ts +1 -7
  120. package/dist/extensions/SettingsWireForm.js +7 -5
  121. package/dist/extensions/headless/fixture-host.d.ts +1 -5
  122. package/dist/extensions/headless/fixture-host.js +1 -4
  123. package/dist/extensions/headless/host.d.ts +1 -11
  124. package/dist/extensions/headless/index.d.ts +3 -3
  125. package/dist/extensions/headless/index.js +2 -2
  126. package/dist/extensions/headless/select.d.ts +1 -11
  127. package/dist/extensions/headless/select.js +1 -18
  128. package/dist/files/EditorColumn.js +12 -1
  129. package/dist/files/TreeColumn.js +13 -20
  130. package/dist/files/useOpenFiles.js +2 -0
  131. package/dist/git/BranchMenu.js +5 -3
  132. package/dist/git/ImportRepoDialog.js +18 -9
  133. package/dist/git/MoveSessionDialog.js +15 -7
  134. package/dist/git/RemoteFolderPicker.js +34 -16
  135. package/dist/git/SyncChip.js +17 -17
  136. package/dist/git/WherePicker.d.ts +16 -1
  137. package/dist/git/WherePicker.js +80 -36
  138. package/dist/git/headless/environment.d.ts +33 -3
  139. package/dist/git/headless/environment.js +33 -5
  140. package/dist/git/headless/index.d.ts +2 -2
  141. package/dist/git/headless/index.js +2 -2
  142. package/dist/git/headless/labels.d.ts +15 -2
  143. package/dist/git/headless/labels.js +17 -4
  144. package/dist/git/useEnvironmentChoices.d.ts +3 -1
  145. package/dist/git/useEnvironmentChoices.js +6 -3
  146. package/dist/index.d.ts +2 -1
  147. package/dist/index.js +2 -1
  148. package/dist/labels.d.ts +5 -0
  149. package/dist/labels.js +5 -0
  150. package/dist/machines/AddMachineDialog.d.ts +34 -0
  151. package/dist/machines/AddMachineDialog.js +464 -0
  152. package/dist/machines/Banners.d.ts +33 -4
  153. package/dist/machines/Banners.js +64 -3
  154. package/dist/machines/DaemonMenu.js +27 -4
  155. package/dist/machines/HostCapabilities.d.ts +1 -3
  156. package/dist/machines/HostCapabilities.js +24 -45
  157. package/dist/machines/HostSyncPanel.js +123 -129
  158. package/dist/machines/HostsSection.d.ts +4 -1
  159. package/dist/machines/HostsSection.js +30 -7
  160. package/dist/machines/MachinesExplainer.d.ts +17 -0
  161. package/dist/machines/MachinesExplainer.js +71 -0
  162. package/dist/machines/MachinesProvider.d.ts +14 -2
  163. package/dist/machines/MachinesProvider.js +35 -1
  164. package/dist/machines/ServerAddedCard.d.ts +31 -0
  165. package/dist/machines/ServerAddedCard.js +72 -0
  166. package/dist/machines/ServerCard.js +91 -215
  167. package/dist/machines/ServerMachinesPanel.d.ts +2 -2
  168. package/dist/machines/ServerMachinesPanel.js +184 -323
  169. package/dist/machines/ServerUpdatePanel.js +51 -40
  170. package/dist/machines/ShareThisComputerForm.d.ts +19 -0
  171. package/dist/machines/ShareThisComputerForm.js +133 -0
  172. package/dist/machines/TokensPanel.d.ts +5 -3
  173. package/dist/machines/TokensPanel.js +98 -78
  174. package/dist/machines/headless/fixture-host.d.ts +19 -3
  175. package/dist/machines/headless/fixture-host.js +42 -5
  176. package/dist/machines/headless/fixtures.js +4 -6
  177. package/dist/machines/headless/host.d.ts +40 -19
  178. package/dist/machines/headless/index.d.ts +3 -3
  179. package/dist/machines/headless/index.js +2 -2
  180. package/dist/machines/headless/labels.d.ts +63 -20
  181. package/dist/machines/headless/labels.js +88 -25
  182. package/dist/machines/headless/messages.js +8 -0
  183. package/dist/machines/headless/stores.d.ts +9 -2
  184. package/dist/machines/headless/stores.js +28 -1
  185. package/dist/machines/index.d.ts +7 -3
  186. package/dist/machines/index.js +8 -4
  187. package/dist/machines/testing/contract.d.ts +15 -3
  188. package/dist/machines/testing/contract.js +68 -14
  189. package/dist/motion/grow.d.ts +8 -1
  190. package/dist/motion/grow.js +10 -4
  191. package/dist/onboarding/demos/index.js +1 -1
  192. package/dist/onboarding/headless/labels.d.ts +13 -0
  193. package/dist/onboarding/headless/labels.js +8 -0
  194. package/dist/onboarding/index.js +3 -3
  195. package/dist/onboarding/sources/SourceBrowser.js +11 -7
  196. package/dist/onboarding/steps/ConnectorsStep.js +1 -1
  197. package/dist/onboarding/steps/ImportStep.js +4 -1
  198. package/dist/onboarding/steps/ReadyStep.js +9 -2
  199. package/dist/onboarding/steps/SkillsStep.js +1 -1
  200. package/dist/onboarding/steps/connect/KeyForm.js +6 -2
  201. package/dist/onboarding/steps/connect/ProviderPanel.js +6 -2
  202. package/dist/onboarding/steps/connect/ProviderReady.js +25 -20
  203. package/dist/onboarding/steps/tasks.js +17 -14
  204. package/dist/server/sse-handler.js +141 -19
  205. package/dist/session/ArtifactsPanel.js +39 -17
  206. package/dist/session/PlanTracker.js +12 -7
  207. package/dist/session/ProjectHome.js +13 -11
  208. package/dist/session/RemindersPanel.js +34 -16
  209. package/dist/session/RunningWorkDialog.js +6 -2
  210. package/dist/session/SecretsPanel.js +29 -10
  211. package/dist/session/SessionDetailsDialog.d.ts +1 -1
  212. package/dist/session/SessionDetailsDialog.js +18 -10
  213. package/dist/session/SideChat.js +10 -7
  214. package/dist/session/TurnDetailsDialog.js +19 -5
  215. package/dist/session/UsagePanel.js +10 -4
  216. package/dist/session/headless/host.d.ts +2 -0
  217. package/dist/session/headless/labels.d.ts +19 -1
  218. package/dist/session/headless/labels.js +15 -1
  219. package/dist/session/headless/queue.js +2 -1
  220. package/dist/session/headless/reminder-labels.d.ts +4 -0
  221. package/dist/session/headless/reminder-labels.js +4 -0
  222. package/dist/settings/SettingsRestricted.d.ts +1 -1
  223. package/dist/settings/SettingsRestricted.js +4 -4
  224. package/dist/settings/SettingsShell.d.ts +7 -0
  225. package/dist/settings/SettingsShell.js +6 -1
  226. package/dist/settings/headless/fixture-performance.d.ts +1 -1
  227. package/dist/settings/headless/host.d.ts +2 -2
  228. package/dist/settings/headless/labels.d.ts +3 -3
  229. package/dist/settings/headless/labels.js +3 -3
  230. package/dist/settings/headless/performance.d.ts +3 -4
  231. package/dist/settings/headless/search.d.ts +1 -1
  232. package/dist/settings/index.d.ts +2 -1
  233. package/dist/settings/index.js +2 -1
  234. package/dist/settings/sections/editor/EditorSection.js +7 -4
  235. package/dist/settings/sections/editor/labels.d.ts +1 -0
  236. package/dist/settings/sections/editor/labels.js +1 -0
  237. package/dist/settings/sections/environments/EnvironmentsSection.js +19 -12
  238. package/dist/settings/sections/environments/labels.d.ts +1 -0
  239. package/dist/settings/sections/environments/labels.js +2 -1
  240. package/dist/settings/sections/extensions/ExtensionConfigRows.d.ts +1 -3
  241. package/dist/settings/sections/extensions/ExtensionConfigRows.js +20 -19
  242. package/dist/settings/sections/extensions/ExtensionOptionsSection.js +2 -1
  243. package/dist/settings/sections/extensions/options-labels.d.ts +2 -1
  244. package/dist/settings/sections/extensions/options-labels.js +2 -1
  245. package/dist/settings/sections/index.d.ts +2 -1
  246. package/dist/settings/sections/index.js +2 -1
  247. package/dist/settings/sections/logs/LogsSection.d.ts +2 -2
  248. package/dist/settings/sections/logs/LogsSection.js +2 -2
  249. package/dist/settings/sections/mcp/McpSection.d.ts +1 -1
  250. package/dist/settings/sections/mcp/McpSection.js +12 -6
  251. package/dist/settings/sections/mcp/McpServerRow.js +20 -13
  252. package/dist/settings/sections/mcp/labels.d.ts +8 -2
  253. package/dist/settings/sections/mcp/labels.js +8 -2
  254. package/dist/settings/sections/models/ModelRows.js +1 -1
  255. package/dist/settings/sections/models/NewChatPlaceRow.d.ts +26 -0
  256. package/dist/settings/sections/models/NewChatPlaceRow.js +78 -0
  257. package/dist/settings/sections/models/SpacesDefaultsSection.js +15 -3
  258. package/dist/settings/sections/models/labels.d.ts +7 -0
  259. package/dist/settings/sections/models/labels.js +7 -0
  260. package/dist/settings/sections/models/model-test.js +7 -4
  261. package/dist/settings/sections/performance/PerformanceSection.js +16 -6
  262. package/dist/settings/sections/performance/labels.d.ts +1 -1
  263. package/dist/settings/sections/performance/labels.js +1 -1
  264. package/dist/settings/sections/providers/ProvidersSection.d.ts +1 -1
  265. package/dist/settings/sections/providers/ProvidersSection.js +14 -7
  266. package/dist/settings/sections/providers/ServerProvidersSection.d.ts +1 -1
  267. package/dist/settings/sections/providers/ServerProvidersSection.js +11 -5
  268. package/dist/settings/sections/providers/labels.d.ts +2 -2
  269. package/dist/settings/sections/providers/labels.js +2 -2
  270. package/dist/settings/sections/skills/SkillList.js +1 -1
  271. package/dist/settings/sections/skills/SkillsSection.js +1 -1
  272. package/dist/settings/testing/performance.contract.js +8 -12
  273. package/dist/styles.css +32 -3
  274. package/dist/terminal/HostTerminalSurface.js +15 -5
  275. package/dist/terminal/TerminalPanel.js +30 -17
  276. package/dist/terminal/TerminalSurface.js +11 -4
  277. package/dist/terminal/headless/labels.d.ts +5 -0
  278. package/dist/terminal/headless/labels.js +5 -0
  279. package/dist/terminal/headless/store.d.ts +1 -1
  280. package/dist/terminal/headless/store.js +6 -3
  281. package/dist/workspace/LayoutBudget.d.ts +5 -1
  282. package/dist/workspace/LayoutBudget.js +14 -1
  283. package/dist/workspace/PanelContentView.d.ts +1 -1
  284. package/dist/workspace/PanelContentView.js +17 -6
  285. package/dist/workspace/WorkspaceProvider.js +3 -2
  286. package/dist/workspace/headless/labels.d.ts +1 -0
  287. package/dist/workspace/headless/labels.js +1 -0
  288. package/package.json +3 -3
  289. package/src/styles/motion.css +8 -0
  290. package/src/styles/primitives.css +7 -0
@@ -133,7 +133,7 @@ const DEFAULT_CHAT_LABELS = {
133
133
  authSignedInRetrying: "Signed in · retrying",
134
134
  authFixed: "fixed",
135
135
  authTryAgain: "Try again",
136
- authFailed: (message) => `Sign-in failed: ${message}`,
136
+ authSignInFailed: "Sign-in didn't work.",
137
137
  sessionEventLabel: (title, verb, needsAttention) => `${title} ${verb}${needsAttention ? ". Click to answer" : ""}`,
138
138
  clickToAnswer: "Answer",
139
139
  openSession: "open →",
@@ -194,7 +194,7 @@ const DEFAULT_CHAT_LABELS = {
194
194
  /** Copy button of a diagram frame. */
195
195
  copyDiagramSource: "Copy diagram source",
196
196
  /** Under a fence that could not be drawn; `at` is `line 3, column 7` or empty. */
197
- diagramError: (message, at) => at ? `Diagram not drawn (${at}): ${message}` : `Diagram not drawn: ${message}`,
197
+ diagramNotDrawn: (at) => at ? `Diagram not drawn (${at}).` : "Diagram not drawn.",
198
198
  /** Where a diagram error is. */
199
199
  diagramErrorAt: (line, column) => column ? `line ${line}, column ${column}` : `line ${line}`,
200
200
  stopCommand: "Stop command",
@@ -224,7 +224,7 @@ const DEFAULT_CHAT_LABELS = {
224
224
  /** The output kept only its end. */
225
225
  blockRunTruncated: "Earlier output was cut.",
226
226
  /** The panel when the block could not run. */
227
- blockRunFailed: (reason) => `Could not run: ${reason}`,
227
+ blockRunError: "Couldn't run this block.",
228
228
  /** After a failed run: it may have wanted input, which a terminal gives. */
229
229
  blockRunNeedsTerminal: "Needs input? Run it in a terminal.",
230
230
  working: "Working",
@@ -252,16 +252,15 @@ const DEFAULT_CHAT_LABELS = {
252
252
  loadingEarlierHistory: "Loading earlier history…",
253
253
  composerLabel: "Message",
254
254
  composerPlaceholder: "Message the agent…",
255
- composerPlaceholderSteer: "Enter adds it to the run. Alt+Enter sends it when the run ends.",
256
- composerPlaceholderQueue: "Enter sends it when the run ends. Alt+Enter adds it to the run.",
257
255
  sendMessage: "Send message",
256
+ composerSendFailed: "Couldn't send your message.",
258
257
  sendHint: "Send (Enter)",
259
258
  stopRun: "Stop",
260
259
  stopHint: "Stop the run (Esc)",
261
260
  steerMessage: "Steer the run",
262
- steerHint: "Add to the run (Enter). Alt+Enter sends it when the run ends.",
261
+ steerHint: "Add to the running turn (Enter)",
263
262
  queueMessage: "Queue message",
264
- queueHint: "Send when the run ends (Enter). Alt+Enter adds it to the run.",
263
+ queueHint: "Send when the run ends (Enter)",
265
264
  attachFile: "Attach file",
266
265
  removeAttachment: (name) => `Remove ${name}`,
267
266
  attachments: "Attachments",
@@ -299,7 +298,7 @@ const DEFAULT_CHAT_LABELS = {
299
298
  modelsJumpToProvider: "Jump to provider",
300
299
  modelsJumpHint: (provider, digit) => digit ? `${provider} (⌘${digit})` : provider,
301
300
  modelsLoading: "Loading…",
302
- modelsLoadFailed: (error) => `Failed to load models: ${error}`,
301
+ modelsLoadError: "Couldn't load the models.",
303
302
  modelsNoProviders: "No provider is signed in. Sign in under Settings → Accounts.",
304
303
  modelsNoMatch: (query) => `No model matches "${query}".`,
305
304
  modelsTypeAnId: "Type any model id to use it. Put a provider first to pick one, for example \"xai grok-9\".",
@@ -356,6 +355,7 @@ const DEFAULT_CHAT_LABELS = {
356
355
  inboxRecent: "Recent",
357
356
  inboxPreviousSession: "previous session",
358
357
  unsupportedInteraction: (tool) => `The agent is waiting on ${tool}, which this view cannot answer.`,
358
+ answerSendFailed: "Couldn't send your answer.",
359
359
  planRejectNote: "Rejecting stops the run. Add a note so the agent knows why.",
360
360
  planReviseNote: "Say what should change. The agent sends back a revised plan.",
361
361
  planRejectPlaceholder: "Why you reject this plan (optional)",
@@ -419,7 +419,8 @@ const DEFAULT_CHAT_LABELS = {
419
419
  },
420
420
  filterSorts: {
421
421
  recency: "Recency",
422
- createdTime: "Created time",
422
+ createdTime: "Creation time",
423
+ lastUserMessage: "Last user message",
423
424
  alphabetical: "Alphabetically"
424
425
  },
425
426
  filterAllProjects: "All",
@@ -13,11 +13,16 @@ interface ListableSession {
13
13
  createdAt: number;
14
14
  /** Epoch milliseconds of the last activity. Used by the `recency` sort and date grouping. */
15
15
  updatedAt?: number;
16
+ /** Epoch milliseconds of the user's last typed message. Used by the `lastUserMessage` sort. If absent, `createdAt` stands in. */
17
+ lastUserMessageAt?: number;
16
18
  }
17
19
  /** How the list groups sessions: `none` (flat), `date`, `state`, or `project`. */
18
20
  type SessionGroupBy = 'none' | 'date' | 'state' | 'project';
19
- /** How sessions are ordered inside a group: `recency` (last activity, newest first), `createdTime` (newest first), or `alphabetical` by title. */
20
- type SessionSortBy = 'recency' | 'createdTime' | 'alphabetical';
21
+ /**
22
+ * How sessions are ordered inside a group: `recency` (last activity, newest first), `createdTime` (newest first),
23
+ * `lastUserMessage` (the user's last typed message, newest first, else creation), or `alphabetical` by title.
24
+ */
25
+ type SessionSortBy = 'recency' | 'createdTime' | 'lastUserMessage' | 'alphabetical';
21
26
  /** One row of the flat list {@link buildSessionRows} returns: a group `header`, a `session`, or a `showMore` row that ends a capped group. */
22
27
  type SessionListRow<S> = {
23
28
  kind: 'header';
@@ -202,7 +207,7 @@ declare function dateBucket(at: number, now: number, labels?: {
202
207
  authSignedInRetrying: string;
203
208
  authFixed: string;
204
209
  authTryAgain: string;
205
- authFailed: (message: string) => string;
210
+ authSignInFailed: string;
206
211
  sessionEventLabel: (title: string, verb: string, needsAttention: boolean) => string;
207
212
  clickToAnswer: string;
208
213
  openSession: string;
@@ -245,7 +250,7 @@ declare function dateBucket(at: number, now: number, labels?: {
245
250
  zoomOutDiagram: string;
246
251
  fitDiagram: string;
247
252
  copyDiagramSource: string;
248
- diagramError: (message: string, at: string) => string;
253
+ diagramNotDrawn: (at: string) => string;
249
254
  diagramErrorAt: (line: number, column: number) => string;
250
255
  stopCommand: string;
251
256
  shellCancelled: string;
@@ -266,7 +271,7 @@ declare function dateBucket(at: number, now: number, labels?: {
266
271
  blockRunStopped: string;
267
272
  blockRunTimedOut: string;
268
273
  blockRunTruncated: string;
269
- blockRunFailed: (reason: string) => string;
274
+ blockRunError: string;
270
275
  blockRunNeedsTerminal: string;
271
276
  working: string;
272
277
  foldSteps: (count: number) => string;
@@ -293,9 +298,8 @@ declare function dateBucket(at: number, now: number, labels?: {
293
298
  loadingEarlierHistory: string;
294
299
  composerLabel: string;
295
300
  composerPlaceholder: string;
296
- composerPlaceholderSteer: string;
297
- composerPlaceholderQueue: string;
298
301
  sendMessage: string;
302
+ composerSendFailed: string;
299
303
  sendHint: string;
300
304
  stopRun: string;
301
305
  stopHint: string;
@@ -340,7 +344,7 @@ declare function dateBucket(at: number, now: number, labels?: {
340
344
  modelsJumpToProvider: string;
341
345
  modelsJumpHint: (provider: string, digit: number | null) => string;
342
346
  modelsLoading: string;
343
- modelsLoadFailed: (error: string) => string;
347
+ modelsLoadError: string;
344
348
  modelsNoProviders: string;
345
349
  modelsNoMatch: (query: string) => string;
346
350
  modelsTypeAnId: string;
@@ -397,6 +401,7 @@ declare function dateBucket(at: number, now: number, labels?: {
397
401
  inboxRecent: string;
398
402
  inboxPreviousSession: string;
399
403
  unsupportedInteraction: (tool: string) => string;
404
+ answerSendFailed: string;
400
405
  planRejectNote: string;
401
406
  planReviseNote: string;
402
407
  planRejectPlaceholder: string;
@@ -461,6 +466,7 @@ declare function dateBucket(at: number, now: number, labels?: {
461
466
  filterSorts: {
462
467
  recency: string;
463
468
  createdTime: string;
469
+ lastUserMessage: string;
464
470
  alphabetical: string;
465
471
  };
466
472
  filterAllProjects: string;
@@ -548,6 +554,7 @@ declare function dateBucket(at: number, now: number, labels?: {
548
554
  interactionAnsweredBy: (who: string) => string;
549
555
  badgePathUnavailable: string;
550
556
  badgeNoComposer: string;
557
+ badgeActionFailed: string;
551
558
  badgeCopied: string;
552
559
  badgeOpenIn: (app: string) => string;
553
560
  badgeOpenInBrowserPane: string;
@@ -583,6 +590,9 @@ declare function dateBucket(at: number, now: number, labels?: {
583
590
  previous: string;
584
591
  next: string;
585
592
  loading: string;
593
+ noticeDetails: string;
594
+ noticeStack: string;
595
+ noticeDismiss: string;
586
596
  filePath: (path: string) => string;
587
597
  rename: string;
588
598
  current: string;
@@ -600,6 +610,7 @@ declare function dateBucket(at: number, now: number, labels?: {
600
610
  modalBack: string;
601
611
  screenBack: (screen: string) => string;
602
612
  screenBackKey: string;
613
+ screenNavBack: (screen: string) => string;
603
614
  paletteLabel: string;
604
615
  paletteFilter: string;
605
616
  paletteBack: string;
@@ -663,6 +674,7 @@ declare function dateBucket(at: number, now: number, labels?: {
663
674
  openInNewTab: string;
664
675
  terminal: string;
665
676
  terminalLoadFailed: (message: string) => string;
677
+ terminalStartFailed: string;
666
678
  terminalExited: (code: number) => string;
667
679
  }, calendar?: CalendarOptions): string;
668
680
  /** From this many days {@link relativeTime} shows a date (`Mar 4`), not a duration: pass it to core `ageStep` to know whether the label takes "ago". */
@@ -125,6 +125,7 @@ function compareSessions(a, b, sort) {
125
125
  switch (sort) {
126
126
  case "alphabetical": return (a.title || "").localeCompare(b.title || "");
127
127
  case "createdTime": return b.createdAt - a.createdAt;
128
+ case "lastUserMessage": return (b.lastUserMessageAt ?? b.createdAt) - (a.lastUserMessageAt ?? a.createdAt);
128
129
  default: return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
129
130
  }
130
131
  }
@@ -15,6 +15,21 @@ interface ProjectInSpace {
15
15
  /** Its repository, when its group also lists the repository's sessions on other machines (`isProjectMember`). */
16
16
  repoKey?: string;
17
17
  }
18
+ /**
19
+ * One session's row changed on its host. It was created or updated, with the
20
+ * row the host lists now, or it was deleted. Create and update are applied
21
+ * alike. If `row` is absent, no list shows the session and the change is ignored.
22
+ */
23
+ type SessionRowChange<S extends {
24
+ id: string;
25
+ }> = {
26
+ op: 'create' | 'update';
27
+ sessionId: S['id'];
28
+ row?: S;
29
+ } | {
30
+ op: 'delete';
31
+ sessionId: S['id'];
32
+ };
18
33
  /** What deleting a session did with its environment. */
19
34
  type SessionRemoval = {
20
35
  kept: false;
@@ -41,8 +56,8 @@ interface SessionDirectorySource<S extends SidebarListSession, C = unknown> {
41
56
  setSpace?(sessionId: S['id'], spaceId: string): Promise<void>;
42
57
  /** Resolves to `{ kept: true }` when the session's environment stayed on disk. */
43
58
  remove?(sessionId: S['id']): Promise<void | SessionRemoval>;
44
- /** Fires (possibly in bursts) with a session whose row changed. */
45
- onChanged?(listener: (sessionId: S['id']) => void): () => void;
59
+ /** Fires with each session row change, its new row included. */
60
+ onChanged?(listener: (change: SessionRowChange<S>) => void): () => void;
46
61
  /**
47
62
  * Fires with a session's facets when the extensions computed new ones (`sessions:facets`): every
48
63
  * value, keyed `<extensionId>.<key>`. Absent: facets change only with a re-read.
@@ -65,6 +80,8 @@ interface SessionDirectoryStreams<K extends string, P> {
65
80
  hasEvents(sessionId: K): boolean;
66
81
  /** Cache a loaded transcript page. */
67
82
  setEventsPage(sessionId: K, page: P): void;
83
+ /** Bring a session's window back in step after its live signals may have gone missing (a reconnect). It reads the rows appended since its history position, else its tail. */
84
+ resync(sessionId: K): void;
68
85
  /** Drop every cached transcript except these. */
69
86
  evictExcept(keep: ReadonlySet<K>): void;
70
87
  /** Drop every stream (a runtime switch). */
@@ -72,6 +89,22 @@ interface SessionDirectoryStreams<K extends string, P> {
72
89
  /** Drop one session's stream (deleted). */
73
90
  reset(sessionId: K): void;
74
91
  }
92
+ /** What a transport knows of the runtime process behind a connection change (`signals.onRuntime`). */
93
+ interface RuntimeOrigin {
94
+ /** When connected, the runtime process the live signals come from (a daemon's `signalEpoch`). */
95
+ epoch?: string;
96
+ /** When down, whether the runtime is being reconnected. It is resumed if it comes back as the same process. */
97
+ resuming?: boolean;
98
+ /** When down, whether an attempt to reconnect is in flight. A down notice after one, with no comeback between, means the attempt failed. */
99
+ connecting?: boolean;
100
+ }
101
+ /** The runtime's connection as a transport knows it now (`signals.currentRuntime`). */
102
+ interface RuntimeNow {
103
+ /** Whether the transport reaches the runtime now. */
104
+ connected: boolean;
105
+ /** When connected, the runtime process the live signals come from. */
106
+ origin?: RuntimeOrigin;
107
+ }
75
108
  /** A remote host as the directory follows it. */
76
109
  interface DirectoryServer {
77
110
  /** The host id rows carry as `hostId`. */
@@ -112,8 +145,19 @@ interface SessionDirectoryOptions<S extends SidebarListSession, P, C = unknown>
112
145
  signals?: {
113
146
  /** A run ended. */
114
147
  onRunEnd?(listener: (sessionId: S['id'], ok: boolean) => void): () => void;
115
- /** The runtime connected or disconnected (a daemon, a server connection). */
116
- onRuntime?(listener: (connected: boolean) => void): () => void;
148
+ /**
149
+ * The runtime connected or disconnected (a daemon, a server connection). The transport passes `origin`
150
+ * when it knows it. When connected, it names the runtime process the signals come from (`epoch`). When
151
+ * down, it says whether the runtime is being reconnected and resumed (`resuming`). A reconnect to the
152
+ * same process keeps the windows, and what they missed is replayed. Any other change drops them.
153
+ */
154
+ onRuntime?(listener: (connected: boolean, origin?: RuntimeOrigin) => void): () => void;
155
+ /**
156
+ * The runtime's connection now, read once at start. A window that mounts after the last `onRuntime`
157
+ * (a second window, a reload) then knows which process its streams come from. A reconnect to that
158
+ * process keeps them. `null` means unknown.
159
+ */
160
+ currentRuntime?(): Promise<RuntimeNow | null>;
117
161
  /** A remote host was removed, with the sessions it held and the words to show. */
118
162
  onHostRemoved?(listener: (removed: {
119
163
  hostId: string;
@@ -132,9 +176,7 @@ interface SessionDirectoryOptions<S extends SidebarListSession, P, C = unknown>
132
176
  request(run: () => void): number;
133
177
  cancel(handle: number): void;
134
178
  };
135
- /** Debounce of change signals. Defaults to 300 ms. */
136
- changedDebounceMs?: number;
137
- /** A refresh is about to list these rows (an app's per-row bookkeeping, before they render). */
179
+ /** A refresh or a change is about to list these rows (an app's per-row bookkeeping, before they render). */
138
180
  onListed?(rows: readonly S[]): void;
139
181
  /** A session was deleted: drop what the app keeps for it (panels, layouts). */
140
182
  onRemoved?(sessionId: S['id']): void;
@@ -202,17 +244,17 @@ interface SessionDirectory<S extends SidebarListSession, C = unknown> {
202
244
  create(input: C & {
203
245
  spaceId?: string;
204
246
  }): Promise<S>;
205
- /** Renames a session, then re-reads. */
247
+ /** Renames a session (its row comes back as a change). */
206
248
  rename(sessionId: S['id'], title: string): Promise<void>;
207
249
  /** Stars or unstars a session at once; a failed write puts it back. */
208
250
  setFavorite(sessionId: S['id'], favorite: boolean): Promise<void>;
209
- /** Archives or restores a session, then re-reads. */
251
+ /** Archives or restores a session. */
210
252
  setArchived(sessionId: S['id'], archived: boolean): Promise<void>;
211
- /** Archives sessions, then re-reads once. */
253
+ /** Archives sessions. */
212
254
  archiveMany(sessionIds: readonly S['id'][]): Promise<void>;
213
- /** Delete one session without re-reading; the caller refreshes. */
255
+ /** Delete one session. */
214
256
  drop(sessionId: S['id']): Promise<SessionRemoval>;
215
- /** Delete sessions, then re-read; resolves to how many kept their environment. */
257
+ /** Delete sessions. Resolves to how many kept their environment. */
216
258
  removeMany(sessionIds: readonly S['id'][]): Promise<{
217
259
  kept: number;
218
260
  reason?: string;
@@ -224,7 +266,7 @@ interface SessionDirectory<S extends SidebarListSession, C = unknown> {
224
266
  setActiveSpace(spaceId: string): void;
225
267
  /** Step to the next or previous space, wrapping at either end; false with spaces off or only one space. */
226
268
  stepSpace(delta: 1 | -1): boolean;
227
- /** Moves a session to a space, then re-reads. */
269
+ /** Moves a session to a space. */
228
270
  setSessionSpace(sessionId: S['id'], spaceId: string): Promise<void>;
229
271
  /** Hides a space from the switcher or shows it again; hiding the shown space shows the default one. */
230
272
  setSpaceHidden(spaceId: string, hidden: boolean): void;
@@ -293,4 +335,4 @@ declare function filterDirectorySessions<S extends SidebarListSession>(sessions:
293
335
  now?: number;
294
336
  }): readonly S[];
295
337
  //#endregion
296
- export { DirectoryServer, DirectorySettings, ProjectInSpace, SessionDirectory, SessionDirectoryOptions, SessionDirectorySource, SessionDirectoryState, SessionDirectoryStreams, SessionRef, SessionRemoval, availableProjectsOf, createSessionDirectory, filterDirectorySessions, projectKeyOf, sessionRefsOf };
338
+ export { DirectoryServer, DirectorySettings, ProjectInSpace, RuntimeNow, RuntimeOrigin, SessionDirectory, SessionDirectoryOptions, SessionDirectorySource, SessionDirectoryState, SessionDirectoryStreams, SessionRef, SessionRemoval, SessionRowChange, availableProjectsOf, createSessionDirectory, filterDirectorySessions, projectKeyOf, sessionRefsOf };
@@ -4,23 +4,29 @@ import { isProjectMember } from "./rows.js";
4
4
  import { DEFAULT_SIDEBAR_VIEW } from "./view.js";
5
5
  import { createNotificationStore } from "./notifications.js";
6
6
  import { NO_SIDEBAR_CONTRIBUTIONS, filterQueryOf, filtersForSpace, mergeSidebarSpaces, resolveGrouping, resolveSort, spaceMembership, switcherSpaces } from "./query.js";
7
- import { DEFAULT_SPACE_ID, SessionQueryError, activeSpaceOf, normalizeSpaces, projectNameOf, resolveSessionSpace, sessionDirLabel, viewQuery } from "zidane/chat/pure";
7
+ import { DEFAULT_SPACE_ID, SessionQueryError, activeSpaceOf, createRecentChanges, normalizeSpaces, projectNameOf, resolveSessionSpace, sessionDirLabel, viewQuery } from "zidane/chat/pure";
8
8
  //#region src/chat/headless/sidebar/directory.ts
9
9
  /**
10
10
  * The session directory: every host's sessions as the sidebar lists them,
11
11
  * kept current outside React.
12
12
  *
13
13
  * - Listing: one read per host (this machine plus each remote host) under
14
- * the shown archive filter. Reads are versioned: only the newest refresh
15
- * lands, and a host's own older answer never overwrites a newer one.
14
+ * the shown archive filter, at the start, on a reconnect, a host or filter
15
+ * change. Reads are versioned: only the newest refresh lands, and a host's
16
+ * own older answer never overwrites a newer one.
17
+ * - Live rows: each change signal carries the session's row as its host
18
+ * lists it (`SessionRowChange`). The directory applies it at once in
19
+ * place, with no read. Create and update upsert, and delete removes. A
20
+ * change that arrives while a read of its host is in flight is applied on
21
+ * top of that read's answer.
16
22
  * - Run state: a refresh reconciles the rows' status with the stream store
17
23
  * (a running row marks the session running, an idle one confirmed by the
18
24
  * host clears a run this client missed the end of) unless a stream signal
19
25
  * moved the run state while the refresh was in flight.
20
- * - Live: a debounced change signal re-reads the hosts it touched; a run end
21
- * re-reads its host and leaves a notification on a session not open; a
22
- * runtime switch drops every stream; a reconnect (runtime or the open
23
- * session's server) re-reads the open transcript when no run streams it.
26
+ * - Live: a run end leaves a notification on a session not open. A runtime
27
+ * switch drops every stream, unless the runtime comes back as the same
28
+ * process. After a reconnect (runtime or the open session's server), the
29
+ * windows on screen climb the reconnect ladder (`streams.resync`).
24
30
  * - Prewarm: the transcripts around the open session load in idle time.
25
31
  * - Spaces: the shown space (kept by the views store), the slide direction of
26
32
  * the last switch, following the open session to its space, and reopening
@@ -56,7 +62,6 @@ const defaultIdle = {
56
62
  function createSessionDirectory(options) {
57
63
  const { sessions, streams, views } = options;
58
64
  const idle = options.idle ?? defaultIdle;
59
- const debounceMs = options.changedDebounceMs ?? 300;
60
65
  const pinnedSession = options.pinnedSession ?? null;
61
66
  const notifications = createNotificationStore();
62
67
  let contributionsStore = options.contributions ?? constant(NO_SIDEBAR_CONTRIBUTIONS);
@@ -117,6 +122,22 @@ function createSessionDirectory(options) {
117
122
  let refreshVersion = 0;
118
123
  const hostLists = /* @__PURE__ */ new Map();
119
124
  const hostReads = /* @__PURE__ */ new Map();
125
+ /** Per host, the changes received while its list is read. A read's answer takes the ones after the read started (`createRecentChanges`). */
126
+ const recentChanges = /* @__PURE__ */ new Map();
127
+ const recentFor = (host) => {
128
+ let recent = recentChanges.get(host);
129
+ if (!recent) recentChanges.set(host, recent = createRecentChanges());
130
+ return recent;
131
+ };
132
+ /** The row as the shown archive filter lists it, `null` when it does not. */
133
+ const listed = (row) => row && (archive === "active" ? !row.archived : archive === "archived" ? Boolean(row.archived) : true) ? row : null;
134
+ /** `rows` with `id`'s row replaced, added (`row`) or removed (`null`), newest first as a host lists them. */
135
+ const withRow = (rows, id, row) => {
136
+ const at = rows.findIndex((s) => s.id === id);
137
+ if (at === -1 && !row) return rows;
138
+ const next = at === -1 ? [...rows, row] : row ? rows.map((s, i) => i === at ? row : s) : rows.filter((_, i) => i !== at);
139
+ return row ? next.map((s, i) => [s, i]).sort((a, b) => b[0].updatedAt - a[0].updatedAt || a[1] - b[1]).map(([s]) => s) : next;
140
+ };
120
141
  const refreshHosts = async (only) => {
121
142
  const version = ++refreshVersion;
122
143
  const unchanged = streams.captureRunState();
@@ -126,11 +147,17 @@ function createSessionDirectory(options) {
126
147
  await Promise.all(hosts.filter((host) => !only || only.has(host) || !hostLists.has(host)).map(async (host) => {
127
148
  const read = (hostReads.get(host) ?? 0) + 1;
128
149
  hostReads.set(host, read);
129
- const rows = host ? await sessions.query({
130
- archive: filter,
131
- hostId: host
132
- }).catch(() => []) : await sessions.query({ archive: filter });
133
- if (hostReads.get(host) === read) hostLists.set(host, rows);
150
+ const recent = recentFor(host);
151
+ const reading = recent.begin();
152
+ try {
153
+ const rows = host ? await sessions.query({
154
+ archive: filter,
155
+ hostId: host
156
+ }).catch(() => []) : await sessions.query({ archive: filter });
157
+ if (hostReads.get(host) === read) hostLists.set(host, recent.end(reading, rows, (list, row, id) => withRow(list, id, listed(row))));
158
+ } finally {
159
+ recent.drop(reading);
160
+ }
134
161
  }));
135
162
  if (version !== refreshVersion) return;
136
163
  for (const host of hostLists.keys()) if (!hosts.includes(host)) hostLists.delete(host);
@@ -157,13 +184,52 @@ function createSessionDirectory(options) {
157
184
  }
158
185
  };
159
186
  const refresh = () => refreshHosts();
160
- const reconvergeActiveTranscript = () => {
187
+ /** Applies a change signal. The row lands in its host's list at once. */
188
+ const applyChange = (change) => {
189
+ const row = change.op === "delete" ? null : change.row ?? void 0;
190
+ if (row === void 0) return;
191
+ const id = change.sessionId;
192
+ const host = row?.hostId ?? options.serverOf(id) ?? [...hostLists].find(([, rows]) => rows.some((s) => s.id === id))?.[0] ?? "";
193
+ recentFor(host).note(id, row);
194
+ const rows = hostLists.get(host);
195
+ if (!rows) return;
196
+ const shown = listed(row);
197
+ const next = withRow(rows, id, shown);
198
+ if (next === rows) return;
199
+ hostLists.set(host, next);
200
+ if (shown) {
201
+ options.onListed?.([shown]);
202
+ if (shown.status === "running") streams.markRunning(shown.id);
203
+ }
204
+ const hosts = ["", ...options.servers.get().map((h) => h.id)];
205
+ patch({ allSessions: hosts.flatMap((h) => hostLists.get(h) ?? []) });
206
+ };
207
+ const comebacks = /* @__PURE__ */ new Map();
208
+ const caughtUp = /* @__PURE__ */ new Map();
209
+ const comebacksOf = (sessionId) => comebacks.get(options.serverOf(sessionId) ?? "") ?? 0;
210
+ const comeBack = (host) => comebacks.set(host, (comebacks.get(host) ?? 0) + 1);
211
+ const catchUp = (sessionId) => {
212
+ const seen = comebacksOf(sessionId);
213
+ if (seen === 0 || caughtUp.get(sessionId) === seen) return;
214
+ caughtUp.set(sessionId, seen);
215
+ if (streams.hasEvents(sessionId)) streams.resync(sessionId);
216
+ };
217
+ /** When the open session's connection comes back, its window climbs the reconnect ladder's last steps (`streams.resync`). */
218
+ const resyncActiveTranscript = () => {
161
219
  const active = state.get().activeSessionId;
162
- if (!active || streams.peekRunning(active) || state.get().allSessions.some((s) => s.id === active && s.status === "running")) return;
163
- const runSeqAtKickoff = streams.peekRunSeq(active);
164
- options.loadEvents(active).then((events) => {
165
- if (state.get().activeSessionId === active && !streams.peekRunning(active) && streams.peekRunSeq(active) === runSeqAtKickoff) streams.setEventsPage(active, events);
166
- }).catch(() => void 0);
220
+ if (!active) return;
221
+ caughtUp.set(active, comebacksOf(active));
222
+ streams.resync(active);
223
+ };
224
+ /** When this machine's runtime comes back, every window on screen (the open session, a side chat) climbs the ladder. */
225
+ const resyncVisibleTranscripts = () => {
226
+ resyncActiveTranscript();
227
+ const active = state.get().activeSessionId;
228
+ for (const id of options.pinned?.() ?? []) {
229
+ if (id === active || options.serverOf(id) !== null || !streams.hasEvents(id)) continue;
230
+ caughtUp.set(id, comebacksOf(id));
231
+ streams.resync(id);
232
+ }
167
233
  };
168
234
  const setActive = (next) => {
169
235
  const current = state.get().activeSessionId;
@@ -349,11 +415,19 @@ function createSessionDirectory(options) {
349
415
  id,
350
416
  status
351
417
  };
352
- if (id !== null && last.id === id && last.status !== void 0 && last.status !== "connected" && status === "connected") reconvergeActiveTranscript();
418
+ if (id !== null && last.id === id && last.status !== void 0 && last.status !== "connected" && status === "connected") resyncActiveTranscript();
419
+ if (active) catchUp(active);
353
420
  };
421
+ let statuses = new Map(options.servers.get().map((host) => [host.id, host.status]));
354
422
  followActiveServer();
355
423
  offs.push(state.subscribe(followActiveServer));
356
424
  offs.push(options.servers.subscribe(() => {
425
+ const next = new Map(options.servers.get().map((host) => [host.id, host.status]));
426
+ for (const [host, status] of next) {
427
+ const was = statuses.get(host);
428
+ if (was !== void 0 && was !== "connected" && status === "connected") comeBack(host);
429
+ }
430
+ statuses = next;
357
431
  followActiveServer();
358
432
  const key = options.servers.get().map((host) => `${host.id}:${host.status}`).join("|");
359
433
  if (key === serversKey) return;
@@ -361,24 +435,65 @@ function createSessionDirectory(options) {
361
435
  refresh();
362
436
  }));
363
437
  let runtime = null;
438
+ /** The process the windows' streams come from, when the transport names it. */
439
+ let runtimeEpoch;
440
+ /** Set when the runtime went down while being resumed. The windows wait for the comeback to know whether to go. */
441
+ let dropPending = false;
442
+ /** The last down notice said an attempt to reconnect was in flight. */
443
+ let attempting = false;
444
+ /** The runtime's connection went down since it was last up, so its next `connected` is a comeback. */
445
+ let runtimeDropped = false;
364
446
  const onRuntime = options.signals?.onRuntime;
365
447
  if (onRuntime) {
366
- offs.push(onRuntime((connected) => {
448
+ offs.push(onRuntime((connected, origin) => {
367
449
  const previous = runtime;
368
450
  runtime = connected;
369
- if (previous === null || previous === connected) return;
370
- streams.dropAll();
371
- refresh();
451
+ const drop = () => {
452
+ dropPending = false;
453
+ streams.dropAll();
454
+ refresh();
455
+ };
456
+ const wasAttempting = attempting;
457
+ attempting = !connected && origin?.connecting === true;
458
+ if (previous === null) {
459
+ if (connected) runtimeEpoch = origin?.epoch;
460
+ return;
461
+ }
462
+ if (!connected) {
463
+ if (previous) {
464
+ if (origin?.resuming && runtimeEpoch !== void 0) dropPending = true;
465
+ else drop();
466
+ return;
467
+ }
468
+ if (dropPending && (!origin?.resuming || wasAttempting && !attempting)) drop();
469
+ return;
470
+ }
471
+ if (previous) return;
472
+ const same = dropPending && origin?.epoch !== void 0 && origin.epoch === runtimeEpoch;
473
+ runtimeEpoch = origin?.epoch;
474
+ if (same) dropPending = false;
475
+ else drop();
372
476
  }));
373
477
  offs.push(onRuntime((connected) => {
374
- if (!connected) return;
478
+ if (!connected) {
479
+ runtimeDropped = true;
480
+ return;
481
+ }
482
+ if (runtimeDropped) comeBack("");
483
+ runtimeDropped = false;
375
484
  refresh();
376
- reconvergeActiveTranscript();
485
+ resyncVisibleTranscripts();
377
486
  }));
487
+ let seeding = true;
488
+ offs.push(() => seeding = false);
489
+ options.signals?.currentRuntime?.().then((now) => {
490
+ if (!seeding || !now || runtime !== null) return;
491
+ runtime = now.connected;
492
+ if (now.connected) runtimeEpoch = now.origin?.epoch;
493
+ }, () => void 0);
378
494
  }
379
495
  const onRunEnd = options.signals?.onRunEnd;
380
496
  if (onRunEnd) offs.push(onRunEnd((sessionId, ok) => {
381
- refreshHosts(/* @__PURE__ */ new Set([options.serverOf(sessionId) ?? ""]));
382
497
  if (sessionId === state.get().activeSessionId) return;
383
498
  notifications.set(sessionId, ok ? "completed" : "error");
384
499
  }));
@@ -391,30 +506,7 @@ function createSessionDirectory(options) {
391
506
  }
392
507
  refresh();
393
508
  }));
394
- if (sessions.onChanged) {
395
- let timer = null;
396
- let activeTouched = false;
397
- let touched = /* @__PURE__ */ new Set();
398
- const off = sessions.onChanged((sessionId) => {
399
- activeTouched ||= sessionId === state.get().activeSessionId;
400
- touched.add(options.serverOf(sessionId) ?? "");
401
- if (timer !== null) clearTimeout(timer);
402
- timer = setTimeout(() => {
403
- timer = null;
404
- const hosts = touched;
405
- touched = /* @__PURE__ */ new Set();
406
- refreshHosts(hosts);
407
- if (activeTouched) {
408
- activeTouched = false;
409
- reconvergeActiveTranscript();
410
- }
411
- }, debounceMs);
412
- });
413
- offs.push(() => {
414
- off();
415
- if (timer !== null) clearTimeout(timer);
416
- });
417
- }
509
+ if (sessions.onChanged) offs.push(sessions.onChanged(applyChange));
418
510
  refresh();
419
511
  return () => {
420
512
  for (const off of offs.splice(0).reverse()) off();
@@ -443,7 +535,6 @@ function createSessionDirectory(options) {
443
535
  },
444
536
  async rename(sessionId, title) {
445
537
  await sessions.rename?.(sessionId, title);
446
- await refresh();
447
538
  },
448
539
  async setFavorite(sessionId, favorite) {
449
540
  const flip = (value) => {
@@ -460,33 +551,23 @@ function createSessionDirectory(options) {
460
551
  flip(!favorite);
461
552
  throw err;
462
553
  }
463
- await refresh();
464
554
  },
465
555
  async setArchived(sessionId, archived) {
466
556
  await sessions.setArchived?.(sessionId, archived);
467
- await refresh();
468
557
  },
469
558
  async archiveMany(sessionIds) {
470
- try {
471
- for (const id of sessionIds) await sessions.setArchived?.(id, true);
472
- } finally {
473
- await refresh();
474
- }
559
+ for (const id of sessionIds) await sessions.setArchived?.(id, true);
475
560
  },
476
561
  drop,
477
562
  async removeMany(sessionIds) {
478
563
  let kept = 0;
479
564
  let reason;
480
- try {
481
- for (const id of sessionIds) {
482
- const result = await drop(id);
483
- if (result.kept) {
484
- kept += 1;
485
- reason ??= result.reason;
486
- }
565
+ for (const id of sessionIds) {
566
+ const result = await drop(id);
567
+ if (result.kept) {
568
+ kept += 1;
569
+ reason ??= result.reason;
487
570
  }
488
- } finally {
489
- await refresh();
490
571
  }
491
572
  return reason ? {
492
573
  kept,
@@ -504,7 +585,6 @@ function createSessionDirectory(options) {
504
585
  async setSessionSpace(sessionId, spaceId) {
505
586
  await sessions.setSpace?.(sessionId, spaceId);
506
587
  views.forgetSession(sessionId, spaceId);
507
- await refresh();
508
588
  },
509
589
  setSpaceHidden(spaceId, hidden) {
510
590
  if (spaceId === DEFAULT_SPACE_ID) return;
@@ -604,7 +684,6 @@ function createSessionDirectory(options) {
604
684
  await sessions.setSpace?.(member.id, spaceId);
605
685
  views.forgetSession(member.id, spaceId);
606
686
  }
607
- await refresh();
608
687
  return members.length;
609
688
  }
610
689
  };