zidane-ui 9.1.13 → 9.1.14

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 (309) 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 +5 -16
  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 +24 -25
  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 +3 -3
  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/appearance/AppearanceProvider.js +2 -2
  31. package/dist/appearance/headless/index.js +1 -1
  32. package/dist/approvals/headless/index.js +2 -2
  33. package/dist/chat/SessionFilterMenu.d.ts +1 -1
  34. package/dist/chat/SessionFilterMenu.js +2 -1
  35. package/dist/chat/Transcript.js +1 -1
  36. package/dist/chat/composer/Composer.js +9 -6
  37. package/dist/chat/composer/ComposerEditor.js +1 -1
  38. package/dist/chat/composer/ConversationBadges.js +1 -1
  39. package/dist/chat/composer/ModelChainList.js +17 -8
  40. package/dist/chat/composer/ModelChainTooltip.js +5 -2
  41. package/dist/chat/composer/use-composer-stores.js +1 -1
  42. package/dist/chat/composer/use-draft-target.d.ts +20 -1
  43. package/dist/chat/composer/use-draft-target.js +122 -6
  44. package/dist/chat/composer/use-new-session-defaults.d.ts +5 -1
  45. package/dist/chat/composer/use-new-session-defaults.js +17 -4
  46. package/dist/chat/composer/use-run-meter.js +1 -1
  47. package/dist/chat/composer/use-send-prompt.js +1 -1
  48. package/dist/chat/headless/chip-labels.d.ts +1 -0
  49. package/dist/chat/headless/chip-labels.js +1 -0
  50. package/dist/chat/headless/composer/index.js +2 -2
  51. package/dist/chat/headless/draft-target.d.ts +21 -1
  52. package/dist/chat/headless/fixture-host.js +17 -2
  53. package/dist/chat/headless/host.d.ts +3 -3
  54. package/dist/chat/headless/index.d.ts +9 -9
  55. package/dist/chat/headless/index.js +15 -15
  56. package/dist/chat/headless/labels.d.ts +13 -4
  57. package/dist/chat/headless/labels.js +8 -5
  58. package/dist/chat/headless/session-list.d.ts +20 -6
  59. package/dist/chat/headless/session-list.js +1 -0
  60. package/dist/chat/headless/sidebar/directory.d.ts +56 -14
  61. package/dist/chat/headless/sidebar/directory.js +149 -70
  62. package/dist/chat/headless/sidebar/index.d.ts +3 -3
  63. package/dist/chat/headless/sidebar/index.js +3 -3
  64. package/dist/chat/headless/sidebar/labels.d.ts +15 -6
  65. package/dist/chat/headless/sidebar/types.d.ts +3 -1
  66. package/dist/chat/headless/sidebar/view.d.ts +13 -5
  67. package/dist/chat/headless/sidebar/view.js +31 -11
  68. package/dist/chat/headless/streams/actions.d.ts +5 -3
  69. package/dist/chat/headless/streams/actions.js +3 -2
  70. package/dist/chat/headless/streams/index.d.ts +3 -3
  71. package/dist/chat/headless/streams/merge.d.ts +6 -10
  72. package/dist/chat/headless/streams/merge.js +7 -21
  73. package/dist/chat/headless/streams/session-streams.d.ts +29 -4
  74. package/dist/chat/headless/streams/session-streams.js +86 -0
  75. package/dist/chat/headless/streams/store.d.ts +162 -16
  76. package/dist/chat/headless/streams/store.js +334 -38
  77. package/dist/chat/headless/streams/types.d.ts +10 -7
  78. package/dist/chat/headless/transcript-labels.d.ts +1 -0
  79. package/dist/chat/interactions/PlanCard.js +10 -3
  80. package/dist/chat/interactions/QuestionForm.js +7 -3
  81. package/dist/chat/interactions/card-ui.d.ts +5 -3
  82. package/dist/chat/interactions/card-ui.js +9 -7
  83. package/dist/chat/sidebar/SessionSidebar.js +5 -5
  84. package/dist/chat/sidebar/SidebarFilterMenu.js +3 -1
  85. package/dist/chat/sidebar/SidebarFrame.d.ts +4 -0
  86. package/dist/chat/sidebar/SidebarFrame.js +27 -9
  87. package/dist/chat/sidebar/SidebarGroupHeader.js +1 -1
  88. package/dist/chat/sidebar/SidebarHostProvider.d.ts +8 -5
  89. package/dist/chat/sidebar/SidebarHostProvider.js +13 -9
  90. package/dist/chat/sidebar/SidebarToggleButton.js +7 -3
  91. package/dist/chat/sidebar/index.d.ts +2 -2
  92. package/dist/chat/sidebar/session-menu.js +4 -4
  93. package/dist/chat/sidebar/useSaveViewAsSpace.js +2 -2
  94. package/dist/chat/testing/contract.js +2 -2
  95. package/dist/chat/transcript/AuthFailureCard.js +11 -8
  96. package/dist/chat/transcript/BadgeMenu.js +13 -2
  97. package/dist/chat/transcript/MermaidDiagram.js +7 -3
  98. package/dist/chat/transcript/fold.d.ts +4 -0
  99. package/dist/chat/transcript/fold.js +53 -22
  100. package/dist/chat/transcript/script-input.d.ts +1 -1
  101. package/dist/chat/transcript/shell-block.js +8 -4
  102. package/dist/chat/transcript/useStickToBottom.js +12 -0
  103. package/dist/components/BrowserSignIn.js +23 -21
  104. package/dist/components/Form.d.ts +6 -4
  105. package/dist/components/Form.js +16 -8
  106. package/dist/components/LoadError.d.ts +4 -2
  107. package/dist/components/LoadError.js +13 -9
  108. package/dist/components/Notice.d.ts +29 -0
  109. package/dist/components/Notice.js +63 -0
  110. package/dist/components/PanelEmpty.d.ts +1 -1
  111. package/dist/components/PanelEmpty.js +4 -2
  112. package/dist/components/Restricted.d.ts +1 -1
  113. package/dist/components/palette/PaletteList.js +8 -1
  114. package/dist/components/palette/PalettePanel.js +14 -6
  115. package/dist/core.css +24 -5
  116. package/dist/dist/{index-Bq87OcGy.d.ts → index-C6rzc-tt.d.ts} +28 -3
  117. package/dist/dist/index.d.ts +2 -2
  118. package/dist/dist/{timeout-B5pO5I5a.d.ts → timeout-Cz9dRF8Q.d.ts} +1 -1
  119. package/dist/extensions/ExtensionSettingsPage.js +2 -5
  120. package/dist/extensions/ExtensionSettingsSection.js +5 -4
  121. package/dist/extensions/ExtensionSettingsSidebar.js +3 -7
  122. package/dist/extensions/SettingsWireForm.d.ts +1 -7
  123. package/dist/extensions/SettingsWireForm.js +7 -5
  124. package/dist/extensions/headless/fixture-host.d.ts +1 -5
  125. package/dist/extensions/headless/fixture-host.js +2 -5
  126. package/dist/extensions/headless/host.d.ts +1 -11
  127. package/dist/extensions/headless/index.d.ts +3 -3
  128. package/dist/extensions/headless/index.js +6 -6
  129. package/dist/extensions/headless/select.d.ts +1 -11
  130. package/dist/extensions/headless/select.js +1 -18
  131. package/dist/extensions/hooks.js +1 -1
  132. package/dist/files/EditorColumn.js +13 -2
  133. package/dist/files/MediaDiff.js +1 -1
  134. package/dist/files/TreeColumn.js +13 -20
  135. package/dist/files/useEntryActions.js +1 -1
  136. package/dist/files/useOpenFiles.js +3 -1
  137. package/dist/git/BranchMenu.js +6 -4
  138. package/dist/git/ImportRepoDialog.js +18 -9
  139. package/dist/git/MoveSessionDialog.js +16 -8
  140. package/dist/git/RemoteFolderPicker.js +35 -17
  141. package/dist/git/SyncChip.js +17 -17
  142. package/dist/git/WherePicker.d.ts +16 -1
  143. package/dist/git/WherePicker.js +81 -37
  144. package/dist/git/headless/environment.d.ts +33 -3
  145. package/dist/git/headless/environment.js +33 -5
  146. package/dist/git/headless/index.d.ts +2 -2
  147. package/dist/git/headless/index.js +2 -2
  148. package/dist/git/headless/labels.d.ts +15 -2
  149. package/dist/git/headless/labels.js +17 -4
  150. package/dist/git/useEnvironmentChoices.d.ts +3 -1
  151. package/dist/git/useEnvironmentChoices.js +7 -4
  152. package/dist/index.d.ts +2 -1
  153. package/dist/index.js +2 -1
  154. package/dist/keybindings/KeyCommands.js +1 -1
  155. package/dist/keybindings/KeybindingsProvider.js +1 -1
  156. package/dist/keybindings/headless/index.js +2 -2
  157. package/dist/keybindings/headless/overrides.js +1 -1
  158. package/dist/labels.d.ts +5 -0
  159. package/dist/labels.js +5 -0
  160. package/dist/machines/AddMachineDialog.d.ts +34 -0
  161. package/dist/machines/AddMachineDialog.js +464 -0
  162. package/dist/machines/Banners.d.ts +33 -4
  163. package/dist/machines/Banners.js +64 -3
  164. package/dist/machines/DaemonMenu.js +27 -4
  165. package/dist/machines/HostCapabilities.d.ts +1 -3
  166. package/dist/machines/HostCapabilities.js +24 -45
  167. package/dist/machines/HostSyncPanel.js +124 -130
  168. package/dist/machines/HostsSection.d.ts +4 -1
  169. package/dist/machines/HostsSection.js +30 -7
  170. package/dist/machines/MachinesExplainer.d.ts +17 -0
  171. package/dist/machines/MachinesExplainer.js +71 -0
  172. package/dist/machines/MachinesProvider.d.ts +14 -2
  173. package/dist/machines/MachinesProvider.js +35 -1
  174. package/dist/machines/ServerAddedCard.d.ts +31 -0
  175. package/dist/machines/ServerAddedCard.js +72 -0
  176. package/dist/machines/ServerCard.js +91 -215
  177. package/dist/machines/ServerMachinesPanel.d.ts +2 -2
  178. package/dist/machines/ServerMachinesPanel.js +184 -323
  179. package/dist/machines/ServerUpdatePanel.js +51 -40
  180. package/dist/machines/ShareThisComputerForm.d.ts +19 -0
  181. package/dist/machines/ShareThisComputerForm.js +133 -0
  182. package/dist/machines/SyncOutcomeBadge.js +1 -1
  183. package/dist/machines/TokensPanel.d.ts +5 -3
  184. package/dist/machines/TokensPanel.js +98 -78
  185. package/dist/machines/headless/fixture-host.d.ts +19 -3
  186. package/dist/machines/headless/fixture-host.js +43 -6
  187. package/dist/machines/headless/fixtures.js +4 -6
  188. package/dist/machines/headless/host.d.ts +40 -19
  189. package/dist/machines/headless/index.d.ts +3 -3
  190. package/dist/machines/headless/index.js +3 -3
  191. package/dist/machines/headless/labels.d.ts +63 -20
  192. package/dist/machines/headless/labels.js +88 -25
  193. package/dist/machines/headless/messages.js +8 -0
  194. package/dist/machines/headless/stores.d.ts +9 -2
  195. package/dist/machines/headless/stores.js +28 -1
  196. package/dist/machines/index.d.ts +7 -3
  197. package/dist/machines/index.js +8 -4
  198. package/dist/machines/testing/contract.d.ts +15 -3
  199. package/dist/machines/testing/contract.js +68 -14
  200. package/dist/motion/grow.d.ts +8 -1
  201. package/dist/motion/grow.js +10 -4
  202. package/dist/onboarding/headless/labels.d.ts +13 -0
  203. package/dist/onboarding/headless/labels.js +8 -0
  204. package/dist/onboarding/sources/SourceBrowser.js +11 -7
  205. package/dist/onboarding/steps/ImportStep.js +4 -1
  206. package/dist/onboarding/steps/ReadyStep.js +9 -2
  207. package/dist/onboarding/steps/connect/KeyForm.js +6 -2
  208. package/dist/onboarding/steps/connect/ProviderPanel.js +6 -2
  209. package/dist/onboarding/steps/connect/ProviderReady.js +25 -20
  210. package/dist/onboarding/steps/tasks.js +17 -14
  211. package/dist/palette/app-sources.js +1 -1
  212. package/dist/server/sse-handler.js +141 -19
  213. package/dist/session/ArtifactsPanel.js +40 -18
  214. package/dist/session/PlanTracker.js +13 -8
  215. package/dist/session/ProjectHome.js +13 -11
  216. package/dist/session/RemindersPanel.js +35 -17
  217. package/dist/session/RunningWorkDialog.js +6 -2
  218. package/dist/session/SecretsPanel.js +29 -10
  219. package/dist/session/SessionDetailsDialog.d.ts +1 -1
  220. package/dist/session/SessionDetailsDialog.js +18 -10
  221. package/dist/session/SideChat.js +10 -7
  222. package/dist/session/TurnDetailsDialog.js +19 -5
  223. package/dist/session/UsagePanel.js +10 -4
  224. package/dist/session/headless/host.d.ts +2 -0
  225. package/dist/session/headless/index.js +2 -2
  226. package/dist/session/headless/labels.d.ts +19 -1
  227. package/dist/session/headless/labels.js +15 -1
  228. package/dist/session/headless/queue.js +2 -1
  229. package/dist/session/headless/reminder-labels.d.ts +4 -0
  230. package/dist/session/headless/reminder-labels.js +4 -0
  231. package/dist/settings/SettingsRestricted.d.ts +1 -1
  232. package/dist/settings/SettingsRestricted.js +4 -4
  233. package/dist/settings/SettingsShell.d.ts +7 -0
  234. package/dist/settings/SettingsShell.js +6 -1
  235. package/dist/settings/headless/fixture-performance.d.ts +1 -1
  236. package/dist/settings/headless/host.d.ts +2 -2
  237. package/dist/settings/headless/index.js +1 -1
  238. package/dist/settings/headless/labels.d.ts +3 -3
  239. package/dist/settings/headless/labels.js +3 -3
  240. package/dist/settings/headless/performance.d.ts +3 -4
  241. package/dist/settings/headless/search.d.ts +1 -1
  242. package/dist/settings/index.d.ts +2 -1
  243. package/dist/settings/index.js +2 -1
  244. package/dist/settings/sections/appearance/AppearanceSection.js +2 -2
  245. package/dist/settings/sections/claude-import/ClaudeImportDialog.js +1 -1
  246. package/dist/settings/sections/editor/EditorSection.js +8 -5
  247. package/dist/settings/sections/editor/labels.d.ts +1 -0
  248. package/dist/settings/sections/editor/labels.js +1 -0
  249. package/dist/settings/sections/environments/EnvironmentsSection.js +21 -14
  250. package/dist/settings/sections/environments/labels.d.ts +1 -0
  251. package/dist/settings/sections/environments/labels.js +2 -1
  252. package/dist/settings/sections/extensions/ChangeOriginDialog.js +1 -1
  253. package/dist/settings/sections/extensions/CreateExtensionDialog.js +1 -1
  254. package/dist/settings/sections/extensions/ExtensionConfigRows.d.ts +1 -3
  255. package/dist/settings/sections/extensions/ExtensionConfigRows.js +20 -19
  256. package/dist/settings/sections/extensions/ExtensionOptionsSection.js +2 -1
  257. package/dist/settings/sections/extensions/ExtensionsSection.js +1 -1
  258. package/dist/settings/sections/extensions/options-labels.d.ts +2 -1
  259. package/dist/settings/sections/extensions/options-labels.js +2 -1
  260. package/dist/settings/sections/host-catalog/HostCatalogSection.js +1 -1
  261. package/dist/settings/sections/index.d.ts +2 -1
  262. package/dist/settings/sections/index.js +2 -1
  263. package/dist/settings/sections/instructions/InstructionsSection.js +1 -1
  264. package/dist/settings/sections/logs/LogsSection.d.ts +2 -2
  265. package/dist/settings/sections/logs/LogsSection.js +2 -2
  266. package/dist/settings/sections/mcp/McpSection.d.ts +1 -1
  267. package/dist/settings/sections/mcp/McpSection.js +13 -7
  268. package/dist/settings/sections/mcp/McpServerRow.js +21 -14
  269. package/dist/settings/sections/mcp/labels.d.ts +8 -2
  270. package/dist/settings/sections/mcp/labels.js +8 -2
  271. package/dist/settings/sections/models/ModelRows.js +1 -1
  272. package/dist/settings/sections/models/NewChatPlaceRow.d.ts +26 -0
  273. package/dist/settings/sections/models/NewChatPlaceRow.js +78 -0
  274. package/dist/settings/sections/models/SpacesDefaultsSection.js +15 -3
  275. package/dist/settings/sections/models/labels.d.ts +7 -0
  276. package/dist/settings/sections/models/labels.js +7 -0
  277. package/dist/settings/sections/models/model-test.js +7 -4
  278. package/dist/settings/sections/performance/PerformanceSection.js +16 -6
  279. package/dist/settings/sections/performance/labels.d.ts +1 -1
  280. package/dist/settings/sections/performance/labels.js +1 -1
  281. package/dist/settings/sections/providers/ProvidersSection.d.ts +1 -1
  282. package/dist/settings/sections/providers/ProvidersSection.js +15 -8
  283. package/dist/settings/sections/providers/ServerProvidersSection.d.ts +1 -1
  284. package/dist/settings/sections/providers/ServerProvidersSection.js +12 -6
  285. package/dist/settings/sections/providers/labels.d.ts +2 -2
  286. package/dist/settings/sections/providers/labels.js +2 -2
  287. package/dist/settings/sections/shared/catalog.js +1 -1
  288. package/dist/settings/sections/skills/SkillsSection.js +1 -1
  289. package/dist/settings/testing/performance.contract.js +8 -12
  290. package/dist/styles.css +32 -3
  291. package/dist/terminal/HostTerminalSurface.js +17 -7
  292. package/dist/terminal/TerminalPanel.js +30 -17
  293. package/dist/terminal/TerminalSurface.js +11 -4
  294. package/dist/terminal/headless/labels.d.ts +5 -0
  295. package/dist/terminal/headless/labels.js +5 -0
  296. package/dist/terminal/headless/store.d.ts +1 -1
  297. package/dist/terminal/headless/store.js +6 -3
  298. package/dist/workspace/LayoutBudget.d.ts +5 -1
  299. package/dist/workspace/LayoutBudget.js +14 -1
  300. package/dist/workspace/PanelContentView.d.ts +1 -1
  301. package/dist/workspace/PanelContentView.js +17 -6
  302. package/dist/workspace/WorkspaceProvider.js +4 -3
  303. package/dist/workspace/extension-tabs.js +1 -1
  304. package/dist/workspace/headless/index.js +1 -1
  305. package/dist/workspace/headless/labels.d.ts +1 -0
  306. package/dist/workspace/headless/labels.js +1 -0
  307. package/package.json +3 -3
  308. package/src/styles/motion.css +8 -0
  309. package/src/styles/primitives.css +7 -0
@@ -11,6 +11,12 @@ import { DEFAULT_SPACE_ID } from "zidane/chat/pure";
11
11
  * too (`ChatHost.sessions.views`, the desktop app's database), it is read
12
12
  * once and wins for every space this run has not changed yet; a space only
13
13
  * this device knows is written back to it.
14
+ *
15
+ * In format 2 (`SIDEBAR_VIEW_VERSION`), the default sort is creation time.
16
+ * Format 1 saved the whole view, with `sortBy: 'recency'` even when nobody
17
+ * picked it. So a record without the mark (Storage's v1 key, the host's copy,
18
+ * an older app's write) reads that sort as unset, on each read. Every record
19
+ * written in format 2 carries the mark.
14
20
  */
15
21
  /** A view nobody changed yet. */
16
22
  const DEFAULT_SIDEBAR_VIEW = {
@@ -18,10 +24,14 @@ const DEFAULT_SIDEBAR_VIEW = {
18
24
  project: null,
19
25
  activity: "all",
20
26
  groupBy: "project",
21
- sortBy: "recency"
27
+ sortBy: "createdTime"
22
28
  };
29
+ /** The format a view record carries (`v`). Format 1 had none, and its `sortBy: 'recency'` reads as the default. */
30
+ const SIDEBAR_VIEW_VERSION = 2;
23
31
  /** Where the views persist: one map of space id → view. */
24
- const SIDEBAR_VIEW_KEY = "zg-sidebar-view:v1";
32
+ const SIDEBAR_VIEW_KEY = "zg-sidebar-view:v2";
33
+ /** Where format 1 kept them: read when the current key holds nothing, then removed. */
34
+ const SIDEBAR_VIEW_KEY_V1 = "zg-sidebar-view:v1";
25
35
  /** Where the shown space persists. */
26
36
  const SIDEBAR_SPACE_KEY = "zg-sidebar-space";
27
37
  const oneOf = (members) => (raw) => members.includes(raw) ? raw : void 0;
@@ -48,6 +58,7 @@ const parseGroupBy = oneOf([
48
58
  const parseSortBy = oneOf([
49
59
  "recency",
50
60
  "createdTime",
61
+ "lastUserMessage",
51
62
  "alphabetical"
52
63
  ]);
53
64
  function parseExtensionView(raw) {
@@ -71,13 +82,15 @@ function parseExtensionView(raw) {
71
82
  };
72
83
  }
73
84
  /**
74
- * A stored view, every axis checked; what is missing or unknown takes the default.
85
+ * A stored view, every axis checked. What is missing or unknown takes the default. A record
86
+ * without the format mark (format 1) reads `sortBy: 'recency'` as unset, because it was format 1's default.
75
87
  *
76
88
  * @param raw - The stored value.
77
89
  */
78
90
  function parseSidebarView(raw) {
79
91
  const o = raw && typeof raw === "object" ? raw : {};
80
92
  const str = (v) => typeof v === "string" ? v : "";
93
+ const formerDefault = o.v !== 2 && o.sortBy === "recency";
81
94
  const d = DEFAULT_SIDEBAR_VIEW;
82
95
  const agent = typeof o.agent === "string" && o.agent.length > 0 ? o.agent : null;
83
96
  const origin = o.origin === "scheduled" || o.origin === "unscheduled" ? o.origin : null;
@@ -87,7 +100,7 @@ function parseSidebarView(raw) {
87
100
  project: typeof o.project === "string" && o.project.length > 0 ? o.project : null,
88
101
  activity: parseActivity(str(o.activity)) ?? d.activity,
89
102
  groupBy: parseGroupBy(str(o.groupBy)) ?? d.groupBy,
90
- sortBy: parseSortBy(str(o.sortBy)) ?? d.sortBy,
103
+ sortBy: (formerDefault ? void 0 : parseSortBy(str(o.sortBy))) ?? d.sortBy,
91
104
  ...agent ? { agent } : {},
92
105
  ...origin ? { origin } : {},
93
106
  ...extension ? { extension } : {}
@@ -154,13 +167,18 @@ function write(storage, key, value) {
154
167
  else storage?.setItem(key, value);
155
168
  } catch {}
156
169
  }
170
+ /** The stored per-space records: the current key, else format 1's. */
171
+ function readRecords(storage) {
172
+ return parseJson(read(storage, "zg-sidebar-view:v2") ?? read(storage, SIDEBAR_VIEW_KEY_V1));
173
+ }
157
174
  /**
158
- * The views the Storage holds: the per-space map, else the legacy per-axis keys as the default space's view.
175
+ * The views the Storage holds: the per-space map (format 1's when the current key holds none),
176
+ * else the legacy per-axis keys as the default space's view.
159
177
  *
160
178
  * @param storage - Where the views persist; absent reads nothing.
161
179
  */
162
180
  function readSidebarViews(storage) {
163
- const stored = viewsFrom(parseJson(read(storage, SIDEBAR_VIEW_KEY)));
181
+ const stored = viewsFrom(readRecords(storage));
164
182
  if (stored) return stored;
165
183
  const legacy = Object.fromEntries(Object.entries(LEGACY_KEYS).map(([field, key]) => [field, read(storage, key)]));
166
184
  return Object.values(legacy).some((v) => v !== void 0) ? { [DEFAULT_SPACE_ID]: parseSidebarView(legacy) } : {};
@@ -173,7 +191,7 @@ function readSidebarViews(storage) {
173
191
  function createSidebarViews(options = {}) {
174
192
  const { storage, host, onError } = options;
175
193
  const views = writable(readSidebarViews(storage));
176
- let lastSessions = sessionsFrom(parseJson(read(storage, SIDEBAR_VIEW_KEY)));
194
+ let lastSessions = sessionsFrom(readRecords(storage));
177
195
  const savedSpace = read(storage, SIDEBAR_SPACE_KEY);
178
196
  const space = writable(savedSpace || (options.fallbackSpace ?? DEFAULT_SPACE_ID));
179
197
  /** Spaces whose view changed this run: the host's older copy never overwrites them. */
@@ -185,15 +203,17 @@ function createSidebarViews(options = {}) {
185
203
  const switcher = writable(parseSwitcherPrefs(parseJson(read(storage, SIDEBAR_SWITCHER_KEY))));
186
204
  const recordOf = (spaceId, view = views.get()[spaceId] ?? DEFAULT_SIDEBAR_VIEW) => {
187
205
  const session = lastSessions[spaceId];
188
- return session ? {
206
+ return {
189
207
  ...view,
190
- session
191
- } : view;
208
+ ...session ? { session } : {},
209
+ v: 2
210
+ };
192
211
  };
193
212
  const persistViews = (next) => {
194
213
  const ids = /* @__PURE__ */ new Set([...Object.keys(next), ...Object.keys(lastSessions)]);
195
214
  const records = Object.fromEntries([...ids].map((id) => [id, recordOf(id, next[id] ?? DEFAULT_SIDEBAR_VIEW)]));
196
215
  write(storage, SIDEBAR_VIEW_KEY, JSON.stringify(records));
216
+ write(storage, SIDEBAR_VIEW_KEY_V1, null);
197
217
  for (const key of Object.values(LEGACY_KEYS)) write(storage, key, null);
198
218
  };
199
219
  const saveView = (spaceId, view) => {
@@ -295,4 +315,4 @@ function createSidebarViews(options = {}) {
295
315
  };
296
316
  }
297
317
  //#endregion
298
- export { DEFAULT_SIDEBAR_VIEW, DEFAULT_SWITCHER_PREFS, SIDEBAR_SPACE_KEY, SIDEBAR_SWITCHER_KEY, SIDEBAR_VIEW_KEY, createSidebarViews, parseSidebarView, parseSwitcherPrefs, readSidebarViews };
318
+ export { DEFAULT_SIDEBAR_VIEW, DEFAULT_SWITCHER_PREFS, SIDEBAR_SPACE_KEY, SIDEBAR_SWITCHER_KEY, SIDEBAR_VIEW_KEY, SIDEBAR_VIEW_VERSION, createSidebarViews, parseSidebarView, parseSwitcherPrefs, readSidebarViews };
@@ -10,8 +10,8 @@ interface SessionStreamActions<K extends string = string> {
10
10
  reset(sessionId: K): void;
11
11
  /** Forgets the session (a deleted one). */
12
12
  drop(sessionId: K): void;
13
- /** Shows a sent prompt at once; the host's echo of it is then dropped. */
14
- appendUserPrompt(sessionId: K, text: string, refs?: readonly PromptSegmentRef[], attachments?: readonly ChatAttachment[]): void;
13
+ /** Shows a sent prompt at once; the host's echo of it (matched by `clientMessageId` when given, else by text) is then dropped. */
14
+ appendUserPrompt(sessionId: K, text: string, refs?: readonly PromptSegmentRef[], attachments?: readonly ChatAttachment[], clientMessageId?: string): void;
15
15
  /** Unwinds one pending echo after a failed send. */
16
16
  dropPendingEcho(sessionId: K, text: string): void;
17
17
  /** Keeps a queued prompt's previews until the host sends it. */
@@ -32,8 +32,10 @@ interface SessionStreamActions<K extends string = string> {
32
32
  evictExcept(keep: ReadonlySet<K>): void;
33
33
  /** Forgets every session. */
34
34
  dropAll(): void;
35
- /** Hydrates a tail page (events and scrollback bookkeeping, one notify). */
35
+ /** Hydrates a tail page (events and scrollback bookkeeping, one notify); a page older than a write applied since is read again. */
36
36
  setEventsPage(sessionId: K, page: SessionStreamPage): void;
37
+ /** Brings the session's window back in step after its signals may have gone missing (the reconnect ladder's last steps). */
38
+ resync(sessionId: K): void;
37
39
  /** Seeds the session's `SYSTEM.md` override. */
38
40
  seedSystemMd(sessionId: K, file: SystemMdFile | null): void;
39
41
  /** Names the session the user reads: its run-end compaction waits until it is backgrounded. */
@@ -11,10 +11,11 @@ function createSessionStreamActions(streams) {
11
11
  drop: (sessionId) => streams.drop(sessionId),
12
12
  dropAll: () => streams.dropAll(),
13
13
  evictExcept: (keep) => streams.evictExcept(keep),
14
- setEventsPage: (sessionId, page) => store.setEventsPage(sessionId, page),
14
+ setEventsPage: (sessionId, page) => streams.setEventsPage(sessionId, page),
15
+ resync: (sessionId) => streams.resync(sessionId),
15
16
  seedSystemMd: (sessionId, file) => store.seedSystemMd(sessionId, file),
16
17
  loadEarlier: (sessionId, options) => streams.loadEarlier(sessionId, options),
17
- appendUserPrompt: (sessionId, text, refs, attachments) => store.appendUserPrompt(sessionId, text, refs, attachments),
18
+ appendUserPrompt: (sessionId, text, refs, attachments, clientMessageId) => store.appendUserPrompt(sessionId, text, refs, attachments, clientMessageId),
18
19
  dropPendingEcho: (sessionId, text) => store.dropPendingEcho(sessionId, text),
19
20
  rememberPromptPreviews: (sessionId, text, attachments) => store.rememberPromptPreviews(sessionId, text, attachments),
20
21
  dropPromptPreviews: (sessionId, text) => store.dropPromptPreviews(sessionId, text),
@@ -1,10 +1,10 @@
1
- import { FrameScheduler, defaultFrameScheduler } from "./scheduler.js";
2
1
  import { OtherStreamSignal, SessionFallbackModel, SessionPageState, SessionRetryState, SessionStreamPage, SessionStreamSignal } from "./types.js";
3
2
  import { DrainPlan } from "./pacer.js";
4
- import { SessionStreamStore, SessionStreamStoreOptions, SessionStreamWrite } from "./store.js";
3
+ import { FrameScheduler, defaultFrameScheduler } from "./scheduler.js";
4
+ import { PendingEcho, SessionStreamStore, SessionStreamStoreOptions, SessionStreamWrite } from "./store.js";
5
5
  import { LoadEarlierOptions, SessionStreams, SessionStreamsOptions, createSessionStreams } from "./session-streams.js";
6
6
  import { mergeLiveTail } from "./merge.js";
7
7
  import { mergePromptPreviews, missingPreviewData } from "./prompt-previews.js";
8
8
  import { SessionStreamActions, createSessionStreamActions } from "./actions.js";
9
9
  import { selectEditEvents, stableSelection } from "./selectors.js";
10
- export { type DrainPlan, type FrameScheduler, type LoadEarlierOptions, type OtherStreamSignal, type SessionFallbackModel, type SessionPageState, type SessionRetryState, type SessionStreamActions, type SessionStreamPage, type SessionStreamSignal, SessionStreamStore, type SessionStreamStoreOptions, type SessionStreamWrite, type SessionStreams, type SessionStreamsOptions, createSessionStreamActions, createSessionStreams, defaultFrameScheduler, mergeLiveTail, mergePromptPreviews, missingPreviewData, selectEditEvents, stableSelection };
10
+ export { type DrainPlan, type FrameScheduler, type LoadEarlierOptions, type OtherStreamSignal, type PendingEcho, type SessionFallbackModel, type SessionPageState, type SessionRetryState, type SessionStreamActions, type SessionStreamPage, type SessionStreamSignal, SessionStreamStore, type SessionStreamStoreOptions, type SessionStreamWrite, type SessionStreams, type SessionStreamsOptions, createSessionStreamActions, createSessionStreams, defaultFrameScheduler, mergeLiveTail, mergePromptPreviews, missingPreviewData, selectEditEvents, stableSelection };
@@ -3,16 +3,12 @@ import { ChatEvent } from "../host.js";
3
3
  /**
4
4
  * Hydrate a live-only window: the disk page followed by the live events
5
5
  * that disk does not have yet. Persisted turns and their live events share
6
- * a `turnId` (hooks pre-mint it), so the boundary is the LAST live event
7
- * whose turn is on the page — everything after it (the in-flight turn's
8
- * streaming markdown, a display-only error row) is kept; everything before
9
- * it is superseded by its persisted form. No live turn on the page (the
10
- * run's first turn is still streaming) keeps the whole live lane.
11
- *
12
- * Untagged live events in the kept suffix (a `user-prompt` echo is
13
- * broadcast bare and only retagged by `prompt-turns`, `steer:inject` echoes
14
- * likewise) are dropped when the page's trailing part already holds the
15
- * same `kind` + `text` — otherwise the disk copy and the echo both render.
6
+ * a `turnId` (the host mints it before it streams). So the boundary is the
7
+ * LAST live event whose turn is on the page. Everything after it (the
8
+ * in-flight turn's streaming markdown, a display-only error row) is kept.
9
+ * Everything up to it is superseded by its persisted form. If no live turn
10
+ * is on the page (the run's first turn is still streaming), the whole live
11
+ * lane is kept.
16
12
  *
17
13
  * @param live - The window's live events (never hydrated from disk).
18
14
  * @param page - The persisted page to hydrate it with.
@@ -3,16 +3,12 @@ import { reconcileEvents } from "zidane/chat/pure";
3
3
  /**
4
4
  * Hydrate a live-only window: the disk page followed by the live events
5
5
  * that disk does not have yet. Persisted turns and their live events share
6
- * a `turnId` (hooks pre-mint it), so the boundary is the LAST live event
7
- * whose turn is on the page — everything after it (the in-flight turn's
8
- * streaming markdown, a display-only error row) is kept; everything before
9
- * it is superseded by its persisted form. No live turn on the page (the
10
- * run's first turn is still streaming) keeps the whole live lane.
11
- *
12
- * Untagged live events in the kept suffix (a `user-prompt` echo is
13
- * broadcast bare and only retagged by `prompt-turns`, `steer:inject` echoes
14
- * likewise) are dropped when the page's trailing part already holds the
15
- * same `kind` + `text` — otherwise the disk copy and the echo both render.
6
+ * a `turnId` (the host mints it before it streams). So the boundary is the
7
+ * LAST live event whose turn is on the page. Everything after it (the
8
+ * in-flight turn's streaming markdown, a display-only error row) is kept.
9
+ * Everything up to it is superseded by its persisted form. If no live turn
10
+ * is on the page (the run's first turn is still streaming), the whole live
11
+ * lane is kept.
16
12
  *
17
13
  * @param live - The window's live events (never hydrated from disk).
18
14
  * @param page - The persisted page to hydrate it with.
@@ -29,17 +25,7 @@ function mergeLiveTail(live, page) {
29
25
  break;
30
26
  }
31
27
  }
32
- const boundaryTurn = lastPersisted === -1 ? void 0 : live[lastPersisted].turnId;
33
- let trailingFrom = 0;
34
- if (boundaryTurn) {
35
- for (let i = page.length - 1; i >= 0; i--) if (page[i].turnId === boundaryTurn) {
36
- trailingFrom = i + 1;
37
- break;
38
- }
39
- }
40
- const trailing = page.slice(trailingFrom);
41
- const suffix = live.slice(lastPersisted + 1).filter((event) => event.turnId || !trailing.some((p) => p.kind === event.kind && p.text === event.text));
42
- return [...reconcileEvents(live, page), ...suffix];
28
+ return [...reconcileEvents(live, page), ...live.slice(lastPersisted + 1)];
43
29
  }
44
30
  //#endregion
45
31
  export { mergeLiveTail };
@@ -1,8 +1,8 @@
1
- import { Transcript } from "../host.js";
2
- import { FrameScheduler } from "./scheduler.js";
1
+ import { ChatEvent, Transcript } from "../host.js";
3
2
  import { OtherStreamSignal, SessionStreamPage, SessionStreamSignal } from "./types.js";
3
+ import { FrameScheduler } from "./scheduler.js";
4
4
  import { SessionStreamStore } from "./store.js";
5
- import { MarkdownDeltaOptions } from "zidane/chat/pure";
5
+ import { HistoryChange, HistorySince, MarkdownDeltaOptions } from "zidane/chat/pure";
6
6
  //#region src/chat/headless/streams/session-streams.d.ts
7
7
  /** Options for {@link createSessionStreams}. */
8
8
  interface SessionStreamsOptions<K extends string = string> {
@@ -12,10 +12,17 @@ interface SessionStreamsOptions<K extends string = string> {
12
12
  scheduler?: FrameScheduler;
13
13
  /** How streamed markdown merges. Default: one event per message. */
14
14
  markdown?: MarkdownDeltaOptions;
15
- /** Loads a session's persisted tail page. Run-end compaction needs it; without it windows are never compacted. */
15
+ /** Loads a session's persisted tail page. Run-end compaction and a history rewrite's fetch need it; without it neither happens. */
16
16
  loadTail?(sessionId: K): Promise<SessionStreamPage>;
17
17
  /** Loads the page before `beforeSeq`. {@link SessionStreams.loadEarlier} needs it. */
18
18
  loadBefore?(sessionId: K, beforeSeq: number): Promise<SessionStreamPage>;
19
+ /**
20
+ * The reconnect ladder's second step (`sessions.historySince`). Reads the rows
21
+ * of the turns appended after `afterPos` of numbering `historyId`. Returns
22
+ * `null` when the host cannot say (a rewrite since, another history).
23
+ * {@link SessionStreams.resync} needs it to skip the tail read.
24
+ */
25
+ loadSince?(sessionId: K, afterPos: number, historyId: string): Promise<HistorySince<ChatEvent> | null>;
19
26
  /** Event count past which a background window is compacted at run end. Default: 2000. */
20
27
  compactAbove?: number;
21
28
  }
@@ -34,6 +41,24 @@ interface SessionStreams<K extends string = string> {
34
41
  * follows, so the transcript stays in causal order.
35
42
  */
36
43
  apply(signal: SessionStreamSignal<K> | OtherStreamSignal): void;
44
+ /**
45
+ * Applies one history write (`history:changed`) to a session's window
46
+ * (`SessionStreamStore.applyHistory`). A rewrite, a missed write or an
47
+ * append too large to carry its rows fetches the tail and replaces the
48
+ * window with it. The fetch happens at once, or at the end of a streaming run.
49
+ */
50
+ applyHistory(sessionId: K, change: HistoryChange): void;
51
+ /**
52
+ * Brings a session's window back in step after its live signals may have
53
+ * gone missing (the connection came back, and the signals could not be
54
+ * replayed). It climbs the reconnect ladder's last two steps
55
+ * (`climbReconnectLadder`). It applies the rows appended since its history
56
+ * position (`loadSince`), or else reads its tail again. A window that is not
57
+ * hydrated (dropped by a runtime switch) reads its tail.
58
+ */
59
+ resync(sessionId: K): void;
60
+ /** Hydrates a tail page into the store (`SessionStreamStore.setEventsPage`); a page cut before a write the window applied since is read again. */
61
+ setEventsPage(sessionId: K, page: SessionStreamPage): void;
37
62
  /** A session's transcript as a {@link ChatHost} hands it to the components (cached per session). */
38
63
  transcript(sessionId: K): Transcript;
39
64
  /** Empties a session's window and run state (and its smoothing bucket). */
@@ -3,8 +3,17 @@ import { SessionStreamStore } from "./store.js";
3
3
  import { storeTranscript } from "./transcript.js";
4
4
  import "../smooth-stream.js";
5
5
  import { allBucketsEmpty, getOrCreateBucket, planDrainBeforeSignal, planFullDrain, planTickDrain } from "./pacer.js";
6
+ import { climbReconnectLadder, cursorValid } from "zidane/chat/pure";
6
7
  //#region src/chat/headless/streams/session-streams.ts
7
8
  /**
9
+ * `createSessionStreams`: the session stream store with everything a live
10
+ * client wraps around it. Smooth streaming paces streamed markdown per
11
+ * (session, owner) on a once-per-frame ticker; lazy scrollback fetches the
12
+ * page before a window; run-end compaction swaps an oversized background
13
+ * window back to its disk tail page. React-free: frames and fetches are
14
+ * injected.
15
+ */
16
+ /**
8
17
  * Event count past which a session's live window is compacted back to the
9
18
  * on-disk tail page at the next run-end boundary. Sized well above a tail
10
19
  * page (at least 200 turns) so compaction only ever fires after genuinely
@@ -68,9 +77,51 @@ function createSessionStreams(options = {}) {
68
77
  if (store.getEventsSnapshot(sessionId) === snapshot) store.setEventsPage(sessionId, page);
69
78
  }).catch(() => {});
70
79
  };
80
+ /** One read per session is in flight. A read asked for meanwhile runs after it, if the first did not bring the window up to date. */
81
+ const reading = /* @__PURE__ */ new Map();
82
+ const refetch = (sessionId, attempts = 3) => {
83
+ if (!options.loadTail) return;
84
+ const inFlight = reading.get(sessionId);
85
+ if (inFlight) {
86
+ inFlight.again = true;
87
+ return;
88
+ }
89
+ const entry = { again: false };
90
+ reading.set(sessionId, entry);
91
+ const settle = () => {
92
+ if (reading.get(sessionId) === entry) reading.delete(sessionId);
93
+ };
94
+ const followUp = () => {
95
+ if (entry.again && !reading.has(sessionId) && store.isTailDue(sessionId) && !store.getRunningSnapshot(sessionId)) refetch(sessionId);
96
+ };
97
+ const runSeq = store.getRunSeqSnapshot(sessionId);
98
+ const hydrated = store.isHydrated(sessionId);
99
+ const again = () => {
100
+ if (attempts > 1) refetch(sessionId, attempts - 1);
101
+ else store.markRefetchDue(sessionId);
102
+ };
103
+ options.loadTail(sessionId).then((page) => {
104
+ settle();
105
+ if (store.isHydrated(sessionId) !== hydrated) return;
106
+ if (!hydrated && store.getHistoryPosSnapshot(sessionId) === void 0) return;
107
+ if (hydrated && store.getRunningSnapshot(sessionId)) {
108
+ store.markRefetchDue(sessionId);
109
+ return;
110
+ }
111
+ if (hydrated && store.getRunSeqSnapshot(sessionId) !== runSeq) return again();
112
+ if (!store.setEventsPage(sessionId, page)) again();
113
+ }).catch(() => {
114
+ settle();
115
+ }).finally(followUp);
116
+ };
71
117
  const compactAfterRunEnd = (signal) => {
72
118
  if (signal.kind !== "run:end") return;
73
119
  const { sessionId } = signal;
120
+ if (store.takeRefetchDue(sessionId)) {
121
+ deferredCompact.delete(sessionId);
122
+ refetch(sessionId);
123
+ return;
124
+ }
74
125
  if (!signal.ok) {
75
126
  deferredCompact.delete(sessionId);
76
127
  return;
@@ -110,6 +161,34 @@ function createSessionStreams(options = {}) {
110
161
  store.applySignal(signal);
111
162
  compactAfterRunEnd(signal);
112
163
  },
164
+ applyHistory(sessionId, change) {
165
+ if (store.applyHistory(sessionId, change)) refetch(sessionId);
166
+ },
167
+ setEventsPage(sessionId, page) {
168
+ if (!store.setEventsPage(sessionId, page)) refetch(sessionId);
169
+ },
170
+ resync(sessionId) {
171
+ if (!store.isHydrated(sessionId)) {
172
+ options.loadTail?.(sessionId).then((page) => {
173
+ if (!store.isHydrated(sessionId)) streams.setEventsPage(sessionId, page);
174
+ }).catch(() => void 0);
175
+ return;
176
+ }
177
+ if (store.isTailDue(sessionId)) return refetch(sessionId);
178
+ const pos = store.getHistoryPosSnapshot(sessionId);
179
+ const historyId = store.getHistoryIdSnapshot(sessionId);
180
+ const loadSince = options.loadSince;
181
+ climbReconnectLadder({
182
+ sessionId,
183
+ ...pos !== void 0 && historyId !== void 0 ? {
184
+ pos,
185
+ historyId
186
+ } : {}
187
+ }, loadSince ? { since: (afterPos, id) => loadSince(sessionId, afterPos, id) } : {}).then((outcome) => {
188
+ if (outcome.via !== "since") return refetch(sessionId);
189
+ if (store.applyHistorySince(sessionId, outcome.since)) refetch(sessionId);
190
+ }).catch(() => refetch(sessionId));
191
+ },
113
192
  transcript(sessionId) {
114
193
  let transcript = transcripts.get(sessionId);
115
194
  if (!transcript) {
@@ -148,6 +227,7 @@ function createSessionStreams(options = {}) {
148
227
  };
149
228
  store.applyDrainPlan(planDrainBeforeSignal(buckets, signal));
150
229
  store.applySignal(signal);
230
+ if (store.takeRefetchDue(sessionId)) refetch(sessionId);
151
231
  },
152
232
  loadEarlier(sessionId, opts) {
153
233
  const page = store.getPageSnapshot(sessionId);
@@ -161,6 +241,12 @@ function createSessionStreams(options = {}) {
161
241
  opts?.onSettled?.(false);
162
242
  return;
163
243
  }
244
+ if (!cursorValid(page.pos, older, page.historyId)) {
245
+ store.setLoadingEarlier(sessionId, false);
246
+ refetch(sessionId);
247
+ opts?.onSettled?.(false);
248
+ return;
249
+ }
164
250
  store.prependEventsPage(sessionId, older);
165
251
  opts?.onSettled?.(true);
166
252
  }).catch(() => {
@@ -1,10 +1,19 @@
1
1
  import { ChatAttachment, ChatEvent } from "../host.js";
2
- import { FrameScheduler } from "./scheduler.js";
3
2
  import { OtherStreamSignal, SessionFallbackModel, SessionPageState, SessionRetryState, SessionStreamPage, SessionStreamSignal } from "./types.js";
4
3
  import { DrainPlan } from "./pacer.js";
5
- import { MarkdownDeltaOptions, PromptSegmentRef } from "zidane/chat/pure";
4
+ import { FrameScheduler } from "./scheduler.js";
5
+ import { HistoryChange, HistorySince, MarkdownDeltaOptions, PromptSegmentRef } from "zidane/chat/pure";
6
6
  import { SystemMdFile } from "zidane/chat";
7
7
  //#region src/chat/headless/streams/store.d.ts
8
+ /** A prompt this client appended optimistically, awaiting its host echo (`SessionStreamStore.pendingEchoes`). */
9
+ interface PendingEcho {
10
+ /** The prompt text. */
11
+ text: string;
12
+ /** The id the send gave the message, when it gave one. */
13
+ clientMessageId?: string;
14
+ /** The row the client appended. The echo gives it its turn id. */
15
+ event: ChatEvent;
16
+ }
8
17
  /** Options for {@link SessionStreamStore}. */
9
18
  interface SessionStreamStoreOptions {
10
19
  /** Frames and timers for batched notifications. Default: {@link defaultFrameScheduler}. */
@@ -86,15 +95,17 @@ declare class SessionStreamStore<K extends string = string> {
86
95
  private _runSeq;
87
96
  private _pages;
88
97
  /**
89
- * Texts of user prompts THIS window appended optimistically that still
90
- * await their host echo. The host stamps `echo: true` on the broadcast
91
- * of the sender's own prompt (send / steer paths) for EVERY peer — it
92
- * cannot know which window originated the send — so the dedupe lives
93
- * here: one pending entry consumes one echo (keeping the local copy,
94
- * which carries refs + attachments the canonical event doesn't).
98
+ * User prompts THIS window appended optimistically that still await their
99
+ * host echo. The host stamps `echo: true` on the broadcast of the sender's
100
+ * own prompt (send / steer paths) for EVERY peer. It cannot know which
101
+ * window sent the prompt, so the dedupe lives here. One pending entry
102
+ * consumes one echo and keeps the local copy, which carries refs and
103
+ * attachments that the canonical event does not.
95
104
  * Echo-tagged prompts with NO pending match are genuine remote sends
96
105
  * (another window, a daemon-drained queued prompt) and must render.
97
- * Leftovers (failed sends) are cleared at `run:end`.
106
+ * The echo matches by the sender's message id when both carry one, else
107
+ * by text, and gives the local row its turn id. Leftovers (failed sends)
108
+ * are cleared at `run:end`.
98
109
  */
99
110
  private _pendingEcho;
100
111
  /**
@@ -102,6 +113,39 @@ declare class SessionStreamStore<K extends string = string> {
102
113
  * the prompt shows up in the transcript (see `prompt-previews.ts`).
103
114
  */
104
115
  private _promptPreviews;
116
+ /** The history position each window applied last (`applyHistory`). It is the window's bookmark in the session's history. */
117
+ private _historyPos;
118
+ /** The numbering of each window's `_historyPos` (`HistoryPosition.historyId`), when known. */
119
+ private _historyId;
120
+ /** Windows to fetch again when their run ends (`takeRefetchDue`, `markRefetchDue`). */
121
+ private _refetchDue;
122
+ /**
123
+ * Windows whose rows are behind their history position. A write asked for a
124
+ * tail read that has not landed yet (in flight, failed, or waiting for the
125
+ * run's end). The position stays the newest write seen, so a read older than
126
+ * it is stale. But it is not a bookmark. The reconnect ladder reads the tail,
127
+ * and a later write also reads the tail instead of applying in place.
128
+ */
129
+ private _tailDue;
130
+ /** Per window, the numberings it left for another numbering. A read in one of them is older than the window. */
131
+ private _retiredHistoryIds;
132
+ /**
133
+ * Windows that saw their current run start (`run:start`, live or replayed).
134
+ * If a run ends in a hydrated window that never saw it start, the window
135
+ * missed the run's first rows. This happens after a drop that main could not
136
+ * replay, or when the window opened mid-run. The rows it read meanwhile also
137
+ * sit below the rows it streamed since. So it reads its tail at the run's
138
+ * end (`takeRefetchDue`).
139
+ */
140
+ private _runStarted;
141
+ /**
142
+ * Windows that streamed rows since their last run boundary (a run's start or
143
+ * end, a page read). With `_running`, it tells a run whose start went by
144
+ * apart from a send that the host refused before it started. A refused send
145
+ * streams nothing, and its error row lives only in this store. Reading the
146
+ * tail drops that row.
147
+ */
148
+ private _runActivity;
105
149
  private _listenersBySession;
106
150
  private _listenersForHas;
107
151
  /**
@@ -194,14 +238,98 @@ declare class SessionStreamStore<K extends string = string> {
194
238
  */
195
239
  getPageSnapshot(sessionId: K | null): SessionPageState;
196
240
  /**
197
- * Hydrate one replay page as the session's full window — the
198
- * `loadEvents` (tail page) path. One notify covers both the events
199
- * swap and the page-state swap.
241
+ * Hydrate one replay page as the session's full window (the
242
+ * `loadEvents` tail page path). One notify covers both the events
243
+ * swap and the page-state swap. Returns `false` and changes nothing
244
+ * when the page is older than a write the window applied since.
200
245
  *
201
246
  * @param sessionId - The session.
202
247
  * @param page - The persisted page.
203
248
  */
204
- setEventsPage(sessionId: K, page: SessionStreamPage): void;
249
+ setEventsPage(sessionId: K, page: SessionStreamPage): boolean;
250
+ /**
251
+ * Applies one history write (`history:changed`) to the session's window.
252
+ * It uses the shared rule (`applyHistoryOp`, `zidane/chat/pure`). A
253
+ * streamed append changes nothing. An append that never streamed adds the
254
+ * rows it carries. A rewrite or a missed write asks for a fetch.
255
+ *
256
+ * Returns `true` when the caller must fetch the session's tail now and
257
+ * replace the window with it (`setEventsPage`). A window whose run streams
258
+ * fetches at the run's end instead (`takeRefetchDue`), and a window never
259
+ * hydrated is left to its first read.
260
+ *
261
+ * @param sessionId - The session.
262
+ * @param change - The write, as the signal carries it.
263
+ */
264
+ applyHistory(sessionId: K, change: HistoryChange): boolean;
265
+ /**
266
+ * Applies a shared history rule's answer to the window. Its position moves
267
+ * first. Then its rows change, or it asks for a tail read. It returns `true`
268
+ * for a read now. While the run streams, the read waits for the run's end.
269
+ * A window whose rows are behind (`_tailDue`) reads its tail on any write
270
+ * that moves it. It does not apply the write to rows that miss the earlier one.
271
+ */
272
+ private _applyHistoryResult;
273
+ /**
274
+ * Applies the reconnect ladder's second step to the session's window. That
275
+ * step brings the rows of the turns appended after its position
276
+ * (`sessions.historySince`, the shared `applyHistorySince`). Returns `true`
277
+ * when the caller must fetch the tail now (as {@link applyHistory} does).
278
+ *
279
+ * @param sessionId - The session.
280
+ * @param since - The rows and the position they bring the window to.
281
+ */
282
+ applyHistorySince(sessionId: K, since: HistorySince<ChatEvent>): boolean;
283
+ /**
284
+ * Moves the window to `pos` after an adapter's own hydrate replaced it with
285
+ * a tail read at `pos`. Returns `stale` if the read is older than a write
286
+ * the window applied since. The adapter then reads it again.
287
+ *
288
+ * @param sessionId - The session.
289
+ * @param pos - The read's position, when the host named one.
290
+ * @param historyId - The numbering `pos` counts in, when the host named one.
291
+ */
292
+ adoptHistoryPos(sessionId: K, pos: number | undefined, historyId?: string): 'fresh' | 'stale';
293
+ /** `readFreshness` against what the window applied, and the numberings it left. */
294
+ private _freshness;
295
+ /** What the window applied last, for the shared history rules. */
296
+ private _historyContext;
297
+ /** Sets the window at `pos` of numbering `historyId`. If the numbering is unknown, none is kept. */
298
+ private _setHistoryPos;
299
+ /**
300
+ * Whether the session's window must be fetched again now that its run ended
301
+ * (see `applyHistory`, `markRefetchDue`). Clears the flag.
302
+ *
303
+ * @param sessionId - The session.
304
+ */
305
+ takeRefetchDue(sessionId: K): boolean;
306
+ /**
307
+ * Asks for the session's window to be read again at its run's end (`takeRefetchDue`). It is for a read dropped
308
+ * because a run streamed when it came back, or for a read that kept coming back older than the window.
309
+ *
310
+ * @param sessionId - The session.
311
+ */
312
+ markRefetchDue(sessionId: K): void;
313
+ /**
314
+ * Whether the session's rows are behind its history position. This is so
315
+ * when a write asked for a tail read that has not landed (in flight, failed,
316
+ * or waiting for the run's end). The reconnect ladder then reads the tail.
317
+ *
318
+ * @param sessionId - The session.
319
+ */
320
+ isTailDue(sessionId: K): boolean;
321
+ /**
322
+ * The history position the session's window applied last. It is `undefined` before its first `history:changed` or positioned page.
323
+ *
324
+ * @param sessionId - The session.
325
+ */
326
+ getHistoryPosSnapshot(sessionId: K): number | undefined;
327
+ /**
328
+ * The numbering of {@link getHistoryPosSnapshot}'s position, when known.
329
+ *
330
+ * @param sessionId - The session.
331
+ */
332
+ getHistoryIdSnapshot(sessionId: K): string | undefined;
205
333
  /**
206
334
  * Splice an OLDER page in front of the hydrated window (lazy scrollback).
207
335
  *
@@ -306,8 +434,9 @@ declare class SessionStreamStore<K extends string = string> {
306
434
  * @param text - The prompt text.
307
435
  * @param refs - The completions the prompt used (skills, files), for its chips.
308
436
  * @param rawAttachments - The prompt's attachments. Omitted for a pushed queued prompt, which takes the previews this client remembered for it.
437
+ * @param clientMessageId - The id the send gave the message (`SendOptions.clientMessageId`). The store matches the echo by it.
309
438
  */
310
- appendUserPrompt(sessionId: K, text: string, refs?: readonly PromptSegmentRef[], rawAttachments?: readonly ChatAttachment[]): void;
439
+ appendUserPrompt(sessionId: K, text: string, refs?: readonly PromptSegmentRef[], rawAttachments?: readonly ChatAttachment[], clientMessageId?: string): void;
311
440
  /**
312
441
  * Unwind ONE pending echo after a FAILED send — a refused/errored send
313
442
  * produces no run, so the `run:end` sweep never fires and the stale
@@ -324,7 +453,7 @@ declare class SessionStreamStore<K extends string = string> {
324
453
  *
325
454
  * @param sessionId - The session.
326
455
  */
327
- pendingEchoes(sessionId: K): string[];
456
+ pendingEchoes(sessionId: K): PendingEcho[];
328
457
  /**
329
458
  * Low-level write: sets any of a session's events, run flag and page state
330
459
  * in one notification, with none of the signal rules (no finalize, no
@@ -366,6 +495,23 @@ declare class SessionStreamStore<K extends string = string> {
366
495
  private _notifySoon;
367
496
  /** Deliver notifications `_notifySoon` held back (also called before anything that must be immediate). */
368
497
  flushNotifications(): void;
498
+ /**
499
+ * Returns `live` without the prompts that this client appended and that the
500
+ * page already holds. These prompts still await their echo. They match by the
501
+ * send's message id, which the saved prompt carries (`origin.messageId`).
502
+ * Their pending entries stay, so the echo is still consumed. It binds
503
+ * nothing, because the row is gone.
504
+ */
505
+ private _withoutSavedLocalPrompts;
506
+ /**
507
+ * Puts the host's exchange separator before the oldest prompt that this
508
+ * client appended and whose echo has not come yet (`appendUserPrompt`).
509
+ * Returns false when no such prompt is on show. The separator then appends
510
+ * like any event.
511
+ */
512
+ private _separateLocalPrompt;
513
+ /** Gives this client's optimistic prompt row the turn id its echo carries. A row that a reload replaced is left alone. */
514
+ private _bindLocalPrompt;
369
515
  /** Drop a stalled attempt's reasoning lane: the retry (or next model) streams its own, and the saved turn never holds it. */
370
516
  private _discardThinking;
371
517
  /**
@@ -382,4 +528,4 @@ declare class SessionStreamStore<K extends string = string> {
382
528
  private _notifyHas;
383
529
  }
384
530
  //#endregion
385
- export { SessionStreamStore, SessionStreamStoreOptions, SessionStreamWrite };
531
+ export { PendingEcho, SessionStreamStore, SessionStreamStoreOptions, SessionStreamWrite };