tmux-ide 2.9.2 → 2.9.4

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 (267) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +121 -140
  3. package/bin/cli.js +2457 -727
  4. package/package.json +23 -8
  5. package/packages/contracts/src/desktop-host.ts +3 -0
  6. package/packages/contracts/src/saved-machines.ts +19 -0
  7. package/packages/contracts/src/terminal-replica.ts +1 -1
  8. package/packages/core/package.json +3 -2
  9. package/packages/core/src/terminal-delivery.ts +51 -3
  10. package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
  11. package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
  12. package/packages/core/src/terminal-replica-owned-row.ts +27 -0
  13. package/packages/core/src/terminal-replica.ts +13 -28
  14. package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
  15. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
  16. package/packages/daemon/dist/command-center/ws-events.js +5 -24
  17. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
  18. package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
  19. package/packages/daemon/dist/lib/daemon-embed.js +1 -1
  20. package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
  21. package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
  22. package/packages/daemon/dist/lib/daemon-service.js +259 -0
  23. package/packages/daemon/dist/lib/install-origin.js +2 -0
  24. package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
  25. package/packages/daemon/dist/lib/log.js +6 -10
  26. package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
  27. package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
  28. package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
  29. package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
  30. package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
  31. package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
  32. package/packages/daemon/dist/lib/saved-machines.js +40 -2
  33. package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
  34. package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
  35. package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
  36. package/packages/daemon/dist/lib/tui-binary.js +2 -2
  37. package/packages/daemon/dist/lib/update.js +18 -0
  38. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
  39. package/packages/daemon/dist/machines.js +40 -4
  40. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  41. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  42. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  43. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  44. package/packages/daemon/dist/terminal/attachments/direct-websocket.js +4 -1
  45. package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
  46. package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
  47. package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
  48. package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
  49. package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
  50. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
  51. package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
  52. package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
  53. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
  54. package/packages/daemon/dist/tui/integrations/claude-agent-teams.js +264 -0
  55. package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
  56. package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
  57. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
  58. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
  59. package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
  60. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
  61. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
  62. package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
  63. package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
  64. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
  65. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
  66. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
  67. package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
  68. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
  69. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
  70. package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
  71. package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
  72. package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
  73. package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
  74. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
  75. package/packages/daemon/src/command-center/ws-events.ts +5 -25
  76. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
  77. package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
  78. package/packages/daemon/src/lib/daemon-embed.ts +1 -0
  79. package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
  80. package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
  81. package/packages/daemon/src/lib/daemon-service.ts +301 -0
  82. package/packages/daemon/src/lib/install-origin.ts +5 -0
  83. package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
  84. package/packages/daemon/src/lib/log.ts +13 -9
  85. package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
  86. package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
  87. package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
  88. package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
  89. package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
  90. package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
  91. package/packages/daemon/src/lib/saved-machines.ts +40 -1
  92. package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
  93. package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
  94. package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
  95. package/packages/daemon/src/lib/tui-binary.ts +2 -2
  96. package/packages/daemon/src/lib/update.ts +24 -0
  97. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
  98. package/packages/daemon/src/machines.ts +53 -5
  99. package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
  100. package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
  101. package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
  102. package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
  103. package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
  104. package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
  105. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
  106. package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
  107. package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
  108. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
  109. package/packages/daemon/src/tui/integrations/claude-agent-teams.ts +334 -0
  110. package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
  111. package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
  112. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
  113. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
  114. package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
  115. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
  116. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
  117. package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
  118. package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
  119. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
  120. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
  121. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
  122. package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
  123. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
  124. package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
  125. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
  126. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
  127. package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
  128. package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
  129. package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
  130. package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
  131. package/packages/daemon-client/src/workspace-client.ts +4 -1
  132. package/skill/SKILL.md +30 -30
  133. package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
  134. package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
  135. package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
  136. package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
  137. package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
  138. package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
  139. package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
  140. package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
  141. package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
  142. package/packages/contracts/src/__tests__/commands.test.ts +0 -86
  143. package/packages/contracts/src/__tests__/control.test.ts +0 -154
  144. package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
  145. package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
  146. package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
  147. package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
  148. package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
  149. package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
  150. package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
  151. package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
  152. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
  153. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
  154. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
  155. package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
  156. package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
  157. package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
  158. package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
  159. package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
  160. package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
  161. package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
  162. package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
  163. package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
  164. package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
  165. package/packages/contracts/src/__tests__/missions.test.ts +0 -272
  166. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
  167. package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
  168. package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
  169. package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
  170. package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
  171. package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
  172. package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
  173. package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
  174. package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
  175. package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
  176. package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
  177. package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
  178. package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
  179. package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
  180. package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
  181. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
  182. package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
  183. package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
  184. package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
  185. package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
  186. package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
  187. package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
  188. package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
  189. package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
  190. package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
  191. package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
  192. package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
  193. package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
  194. package/packages/contracts/src/native-operation-identity.test.ts +0 -23
  195. package/packages/contracts/src/workspace-admission.test.ts +0 -21
  196. package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
  197. package/packages/core/src/agent-provisioning.test.ts +0 -42
  198. package/packages/core/src/application-shell-session.test.ts +0 -105
  199. package/packages/core/src/interaction-evidence.test.ts +0 -134
  200. package/packages/core/src/interaction-flow.test.ts +0 -66
  201. package/packages/core/src/interaction-receipts.test.ts +0 -671
  202. package/packages/core/src/navigator.test.ts +0 -34
  203. package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
  204. package/packages/core/src/optimistic-projection.test.ts +0 -118
  205. package/packages/core/src/performance-metrics.test.ts +0 -121
  206. package/packages/core/src/performance-qualification.test.ts +0 -283
  207. package/packages/core/src/saved-machines.test.ts +0 -80
  208. package/packages/core/src/terminal-compact-cold.test.ts +0 -87
  209. package/packages/core/src/terminal-conformance.test.ts +0 -48
  210. package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
  211. package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
  212. package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
  213. package/packages/core/src/terminal-delivery.test.ts +0 -1471
  214. package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
  215. package/packages/core/src/terminal-replica-cold.test.ts +0 -31
  216. package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
  217. package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
  218. package/packages/core/src/terminal-replica.test.ts +0 -347
  219. package/packages/core/src/workspace-selection.test.ts +0 -63
  220. package/packages/daemon/dist/__tests__/support.js +0 -33
  221. package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
  222. package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
  223. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
  224. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
  225. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
  226. package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
  227. package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
  228. package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
  229. package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
  230. package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
  231. package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
  232. package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
  233. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
  234. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
  235. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
  236. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
  237. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
  238. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
  239. package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
  240. package/packages/daemon-client/src/automation-client.test.ts +0 -214
  241. package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
  242. package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
  243. package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
  244. package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
  245. package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
  246. package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
  247. package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
  248. package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
  249. package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
  250. package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
  251. package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
  252. package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
  253. package/packages/daemon-client/src/resource-replica.test.ts +0 -186
  254. package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
  255. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
  256. package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
  257. package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
  258. package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
  259. package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
  260. package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
  261. package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
  262. package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
  263. package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
  264. package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
  265. package/packages/presentation/src/presentation-contract.test.ts +0 -63
  266. package/packages/tmux-bridge/src/runner.test.ts +0 -614
  267. package/packages/tmux-bridge/src/targeting.test.ts +0 -107
@@ -1,509 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import {
4
- createPushResourceSession,
5
- type PushResourceEventHandlers,
6
- type PushResourceFetchResult,
7
- type PushResourceSessionAdapter,
8
- } from "./push-resource-session.ts";
9
- import { runtimeResourceSnapshot } from "./runtime-resource-ledger.ts";
10
-
11
- type Key = "fleet" | "files" | "changes" | "terminal";
12
- interface Target {
13
- readonly generation: string;
14
- }
15
- interface Failure {
16
- readonly reason: string;
17
- readonly transient: boolean;
18
- }
19
-
20
- class FakeClock {
21
- nowValue = 0;
22
- next = 1;
23
- readonly timers = new Map<number, { at: number; run: () => void }>();
24
-
25
- now(): number {
26
- return this.nowValue;
27
- }
28
- setTimeout(run: () => void, delayMs: number): number {
29
- const id = this.next++;
30
- this.timers.set(id, { at: this.nowValue + delayMs, run });
31
- return id;
32
- }
33
- clearTimeout(handle: unknown): void {
34
- this.timers.delete(handle as number);
35
- }
36
- advanceBy(durationMs: number): void {
37
- const end = this.nowValue + durationMs;
38
- while (true) {
39
- const due = [...this.timers.entries()]
40
- .filter(([, timer]) => timer.at <= end)
41
- .sort((left, right) => left[1].at - right[1].at)[0];
42
- if (!due) break;
43
- this.timers.delete(due[0]);
44
- this.nowValue = due[1].at;
45
- due[1].run();
46
- }
47
- this.nowValue = end;
48
- }
49
- }
50
-
51
- class Microtasks {
52
- readonly pending: Array<() => void> = [];
53
- queue = (run: () => void): void => {
54
- this.pending.push(run);
55
- };
56
- runNext(): void {
57
- const run = this.pending.shift();
58
- if (!run) throw new Error("no pending microtask");
59
- run();
60
- }
61
- flush(): void {
62
- while (this.pending.length > 0) this.runNext();
63
- }
64
- }
65
-
66
- const ok = (resource: string): PushResourceFetchResult<string, Failure> => ({
67
- status: "ok",
68
- resource,
69
- });
70
-
71
- function target(value: unknown) {
72
- if (
73
- typeof value === "object" &&
74
- value !== null &&
75
- "generation" in value &&
76
- typeof value.generation === "string"
77
- ) {
78
- return { ok: true as const, target: value as Target, key: value.generation };
79
- }
80
- return {
81
- ok: false as const,
82
- failure: { reason: "invalid target", transient: false },
83
- };
84
- }
85
-
86
- function adapter(
87
- overrides: Partial<PushResourceSessionAdapter<Target, Key, string, Failure>> = {},
88
- ): PushResourceSessionAdapter<Target, Key, string, Failure> {
89
- return {
90
- validateTarget: target,
91
- fetch: async (current, key) => ok(`${current.generation}:${key}`),
92
- connect: () => ({ status: "connected", close: () => undefined }),
93
- rejectionFailure: () => ({ reason: "request rejected", transient: true }),
94
- retryable: (failure) => failure.transient,
95
- ...overrides,
96
- };
97
- }
98
-
99
- async function turn(): Promise<void> {
100
- await Promise.resolve();
101
- await Promise.resolve();
102
- }
103
-
104
- describe("push resource session", () => {
105
- it("has one initial fetch and zero idle work for ten simulated minutes", async () => {
106
- const clock = new FakeClock();
107
- let fetches = 0;
108
- const session = createPushResourceSession(
109
- adapter({
110
- fetch: async () => {
111
- fetches += 1;
112
- return ok(`files-${fetches}`);
113
- },
114
- }),
115
- { generation: "one" },
116
- { clock },
117
- );
118
- session.activate("files");
119
- await turn();
120
- expect(fetches).toBe(1);
121
- expect(clock.timers.size).toBe(0);
122
-
123
- clock.advanceBy(10 * 60 * 1_000);
124
- await turn();
125
- expect(fetches).toBe(1);
126
- expect(clock.timers.size).toBe(0);
127
- expect(session.getMetrics().idleWakeups).toBe(0);
128
- });
129
-
130
- it("coalesces an invalidation burst into one refetch and ignores terminal traffic", async () => {
131
- const microtasks = new Microtasks();
132
- let events: PushResourceEventHandlers<Key> | null = null;
133
- let fetches = 0;
134
- const session = createPushResourceSession(
135
- adapter({
136
- fetch: async () => ok(`files-${++fetches}`),
137
- connect: (_target, _keys, handlers) => {
138
- events = handlers;
139
- return { status: "connected", close: () => undefined };
140
- },
141
- }),
142
- { generation: "one" },
143
- { queueMicrotask: microtasks.queue },
144
- );
145
- session.activate("files");
146
- await turn();
147
- expect(fetches).toBe(1);
148
-
149
- events!.invalidate();
150
- events!.invalidate();
151
- events!.invalidate();
152
- expect(fetches).toBe(1);
153
- expect(microtasks.pending).toHaveLength(1);
154
- microtasks.flush();
155
- await turn();
156
- expect(fetches).toBe(2);
157
-
158
- events!.invalidate(["terminal"]);
159
- microtasks.flush();
160
- await turn();
161
- expect(fetches).toBe(2);
162
- expect(session.getMetrics()).toMatchObject({
163
- invalidationsObserved: 4,
164
- invalidationsCoalesced: 2,
165
- fetchesStarted: 2,
166
- });
167
- });
168
-
169
- it("updates logical interests and releases each watcher without physical socket churn", async () => {
170
- const connectedWith: Key[][] = [];
171
- const updatedTo: string[][] = [];
172
- let closes = 0;
173
- const session = createPushResourceSession(
174
- adapter({
175
- connect: (_target, keys) => {
176
- connectedWith.push([...keys]);
177
- return {
178
- status: "connected",
179
- close: () => {
180
- closes += 1;
181
- },
182
- updateInterests: (next) => {
183
- updatedTo.push([...next]);
184
- },
185
- };
186
- },
187
- }),
188
- { generation: "one" },
189
- );
190
- const releaseFiles = session.activate("files");
191
- await turn();
192
- const releaseChanges = session.activate("changes");
193
- await turn();
194
- releaseFiles();
195
- await turn();
196
- expect(connectedWith).toEqual([["files"]]);
197
- expect(updatedTo).toEqual([["files", "changes"], ["changes"]]);
198
- expect(closes).toBe(0);
199
- expect(session.getMetrics().activeInterests).toBe(1);
200
-
201
- releaseChanges();
202
- expect(closes).toBe(1);
203
- expect(session.getMetrics()).toMatchObject({
204
- activeInterests: 0,
205
- subscriptionsOpened: 1,
206
- subscriptionsClosed: 1,
207
- });
208
- });
209
-
210
- it("serially converges A to B to C when desired interests mutate during the B ACK", async () => {
211
- const updates: string[][] = [];
212
- const acknowledgements: Array<() => void> = [];
213
- const session = createPushResourceSession(
214
- adapter({
215
- connect: () => ({
216
- status: "connected",
217
- close: () => undefined,
218
- updateInterests: (next) => {
219
- updates.push([...next].sort());
220
- return new Promise<void>((resolve) => acknowledgements.push(resolve));
221
- },
222
- }),
223
- }),
224
- { generation: "one" },
225
- );
226
- const releaseFiles = session.activate("files");
227
- await turn();
228
-
229
- session.activate("changes");
230
- expect(updates).toEqual([["changes", "files"]]);
231
- releaseFiles();
232
- session.activate("terminal");
233
- expect(updates).toEqual([["changes", "files"]]);
234
-
235
- acknowledgements.shift()!();
236
- await turn();
237
- expect(updates).toEqual([
238
- ["changes", "files"],
239
- ["changes", "terminal"],
240
- ]);
241
-
242
- acknowledgements.shift()!();
243
- await turn();
244
- expect(session.getMetrics().activeInterests).toBe(2);
245
- expect(session.getState().slots.get("terminal")?.status).toBe("loaded");
246
- });
247
-
248
- it("lets a replacement generation converge while a retired updater never settles", async () => {
249
- const updates = new Map<string, string[][]>();
250
- const session = createPushResourceSession(
251
- adapter({
252
- connect: (current) => ({
253
- status: "connected",
254
- close: () => undefined,
255
- updateInterests: (next) => {
256
- const generationUpdates = updates.get(current.generation) ?? [];
257
- generationUpdates.push([...next].sort());
258
- updates.set(current.generation, generationUpdates);
259
- if (current.generation === "one") return new Promise<void>(() => undefined);
260
- },
261
- }),
262
- }),
263
- { generation: "one" },
264
- );
265
- session.activate("files");
266
- await turn();
267
-
268
- // Generation one's physical updater never ACKs.
269
- session.activate("changes");
270
- expect(updates.get("one")).toEqual([["changes", "files"]]);
271
-
272
- // Retirement must release the logical mutex without waiting for that ACK.
273
- session.setTarget({ generation: "two" });
274
- await turn();
275
- session.activate("terminal");
276
- await turn();
277
-
278
- expect(updates.get("two")).toEqual([["changes", "files", "terminal"]]);
279
- expect(session.getState().slots.get("terminal")).toMatchObject({
280
- status: "loaded",
281
- resource: "two:terminal",
282
- });
283
- session.dispose();
284
- });
285
-
286
- it("establishes subscription interest before the initial read", async () => {
287
- let events: PushResourceEventHandlers<Key> | null = null;
288
- let finishConnect: (() => void) | null = null;
289
- let revision = 1;
290
- const reads: number[] = [];
291
- const session = createPushResourceSession(
292
- adapter({
293
- fetch: async () => {
294
- reads.push(revision);
295
- return ok(`revision-${revision}`);
296
- },
297
- connect: (_target, _keys, handlers) => {
298
- events = handlers;
299
- return new Promise((resolve) => {
300
- finishConnect = () => resolve({ status: "connected", close: () => undefined } as const);
301
- });
302
- },
303
- }),
304
- { generation: "one" },
305
- );
306
- session.activate("files");
307
- expect(reads).toEqual([]);
308
- revision = 2;
309
- events!.invalidate(["files"]);
310
- await turn();
311
- expect(reads).toEqual([]);
312
-
313
- finishConnect!();
314
- await turn();
315
- expect(reads).toEqual([2]);
316
- expect(session.getState().slots.get("files")).toMatchObject({
317
- status: "loaded",
318
- resource: "revision-2",
319
- });
320
- });
321
-
322
- it("falls back to a demand read when event subscription throws or rejects", async () => {
323
- for (const connect of [
324
- () => {
325
- throw new Error("sync refusal");
326
- },
327
- () => Promise.reject(new Error("async refusal")),
328
- ]) {
329
- let fetches = 0;
330
- const session = createPushResourceSession(
331
- adapter({
332
- connect,
333
- fetch: async () => ok(`fallback-${++fetches}`),
334
- }),
335
- { generation: "one" },
336
- );
337
- session.activate("files");
338
- await turn();
339
- expect(fetches).toBe(1);
340
- expect(session.getState().slots.get("files")).toMatchObject({
341
- status: "loaded",
342
- resource: "fallback-1",
343
- });
344
- session.dispose();
345
- }
346
- });
347
-
348
- it("reports degraded event authority, retries once, and resynchronizes after installation", async () => {
349
- const timerBaseline = runtimeResourceSnapshot()["runtime-timer"].active;
350
- const clock = new FakeClock();
351
- let connects = 0;
352
- let fetches = 0;
353
- let events: PushResourceEventHandlers<Key> | null = null;
354
- const session = createPushResourceSession(
355
- adapter({
356
- connect: (_target, _keys, handlers) => {
357
- events = handlers;
358
- connects += 1;
359
- return connects === 1
360
- ? { status: "unavailable" as const }
361
- : { status: "connected" as const, close: () => undefined };
362
- },
363
- fetch: async () => ok(`snapshot-${++fetches}`),
364
- }),
365
- { generation: "one" },
366
- { clock, retry: { initialDelayMs: 10, maximumDelayMs: 10, maximumAttempts: 2 } },
367
- );
368
- session.activate("files");
369
- await turn();
370
- expect(session.getState().eventPhase).toBe("degraded");
371
- expect(fetches).toBe(1);
372
- expect(clock.timers.size).toBe(1);
373
- expect(runtimeResourceSnapshot()["runtime-timer"].active).toBe(timerBaseline + 1);
374
-
375
- clock.advanceBy(10);
376
- await turn();
377
- expect(session.getState().eventPhase).toBe("live");
378
- expect(fetches).toBe(2);
379
- expect(clock.timers.size).toBe(0);
380
- expect(runtimeResourceSnapshot()["runtime-timer"].active).toBe(timerBaseline);
381
-
382
- events!.invalidate(["files"]);
383
- await turn();
384
- expect(fetches).toBe(3);
385
- clock.advanceBy(10 * 60 * 1_000);
386
- expect(clock.timers.size).toBe(0);
387
- session.dispose();
388
- expect(runtimeResourceSnapshot()["runtime-timer"].active).toBe(timerBaseline);
389
- });
390
-
391
- it("retires an installed event source, reads through degradation, and reconnects", async () => {
392
- const clock = new FakeClock();
393
- const handlers: PushResourceEventHandlers<Key>[] = [];
394
- let connects = 0;
395
- let closes = 0;
396
- let fetches = 0;
397
- const session = createPushResourceSession(
398
- adapter({
399
- connect: (_target, _keys, next) => {
400
- handlers.push(next);
401
- connects += 1;
402
- return { status: "connected", close: () => closes++ };
403
- },
404
- fetch: async () => ok(`snapshot-${++fetches}`),
405
- }),
406
- { generation: "one" },
407
- { clock, retry: { initialDelayMs: 10, maximumDelayMs: 10, maximumAttempts: 2 } },
408
- );
409
- session.activate("files");
410
- await turn();
411
- expect(session.getState().eventPhase).toBe("live");
412
- expect(fetches).toBe(1);
413
-
414
- handlers[0]!.unavailable();
415
- await turn();
416
- expect(closes).toBe(1);
417
- expect(session.getState().eventPhase).toBe("degraded");
418
- expect(fetches).toBe(2);
419
- expect(clock.timers.size).toBe(1);
420
-
421
- clock.advanceBy(10);
422
- await turn();
423
- expect(connects).toBe(2);
424
- expect(session.getState().eventPhase).toBe("live");
425
- expect(fetches).toBe(3);
426
- });
427
-
428
- it("does not let a stale-generation invalidation microtask erase the new generation", async () => {
429
- const microtasks = new Microtasks();
430
- const handlers: PushResourceEventHandlers<Key>[] = [];
431
- const reads: string[] = [];
432
- const session = createPushResourceSession(
433
- adapter({
434
- fetch: async (current) => {
435
- reads.push(current.generation);
436
- return ok(current.generation);
437
- },
438
- connect: (_target, _keys, next) => {
439
- handlers.push(next);
440
- return { status: "connected", close: () => undefined };
441
- },
442
- }),
443
- { generation: "one" },
444
- { queueMicrotask: microtasks.queue },
445
- );
446
- session.activate("files");
447
- await turn();
448
- handlers[0]!.invalidate(["files"]);
449
- session.setTarget({ generation: "two" });
450
- await turn();
451
- handlers[1]!.invalidate(["files"]);
452
- expect(microtasks.pending).toHaveLength(2);
453
- microtasks.runNext();
454
- microtasks.runNext();
455
- await turn();
456
- expect(reads).toEqual(["one", "two", "two"]);
457
- expect(session.getState().slots.get("files")).toMatchObject({ resource: "two" });
458
- });
459
-
460
- it("aborts requests and ignores their late completion after release or disposal", async () => {
461
- let signal: AbortSignal | null = null;
462
- let finish: ((result: PushResourceFetchResult<string, Failure>) => void) | null = null;
463
- const session = createPushResourceSession(
464
- adapter({
465
- fetch: (_target, _key, nextSignal) => {
466
- signal = nextSignal;
467
- return new Promise((resolve) => {
468
- finish = resolve;
469
- });
470
- },
471
- }),
472
- { generation: "one" },
473
- );
474
- session.activate("files");
475
- await turn();
476
- session.dispose();
477
- expect(signal!.aborted).toBe(true);
478
- finish!(ok("late"));
479
- await turn();
480
- expect(session.getState().disposed).toBe(true);
481
- expect(session.getState().slots.size).toBe(0);
482
- expect(session.getMetrics()).toMatchObject({ fetchesAborted: 1, lateResultsIgnored: 1 });
483
- });
484
-
485
- it("aborts a pending event connection on last release and closes a late result", async () => {
486
- let connectSignal: AbortSignal | null = null;
487
- let finishConnect: ((result: { status: "connected"; close: () => void }) => void) | null = null;
488
- let closes = 0;
489
- const session = createPushResourceSession(
490
- adapter({
491
- connect: (_target, _keys, _handlers, signal) => {
492
- connectSignal = signal;
493
- return new Promise((resolve) => {
494
- finishConnect = resolve;
495
- });
496
- },
497
- }),
498
- { generation: "one" },
499
- );
500
- const release = session.activate("files");
501
- expect(connectSignal!.aborted).toBe(false);
502
- release();
503
- expect(connectSignal!.aborted).toBe(true);
504
- finishConnect!({ status: "connected", close: () => closes++ });
505
- await turn();
506
- expect(closes).toBe(1);
507
- expect(session.getMetrics()).toMatchObject({ activeInterests: 0, subscriptionsOpened: 0 });
508
- });
509
- });
@@ -1,186 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- advanceResourceReplica,
4
- initialResourceReplica,
5
- type ResourceReplicaState,
6
- } from "./resource-replica.ts";
7
-
8
- const GENERATION_A = "daemon-a";
9
- const GENERATION_B = "daemon-b";
10
-
11
- function live(value = "layout-a"): ResourceReplicaState<string> {
12
- return {
13
- phase: "live",
14
- daemonInstanceId: GENERATION_A,
15
- sequence: 10,
16
- revision: 4,
17
- value,
18
- reason: null,
19
- };
20
- }
21
-
22
- describe("resource replica", () => {
23
- it("requests one initial snapshot and retains the last value through reconnect", () => {
24
- const connected = advanceResourceReplica(initialResourceReplica<string>(), {
25
- type: "connected",
26
- daemonInstanceId: GENERATION_A,
27
- });
28
- expect(connected.state).toMatchObject({ phase: "syncing", value: null });
29
- expect(connected.effects).toEqual([
30
- { type: "request-snapshot", daemonInstanceId: GENERATION_A },
31
- ]);
32
-
33
- const disconnected = advanceResourceReplica(live(), { type: "disconnected" });
34
- expect(disconnected.state).toMatchObject({
35
- phase: "stale",
36
- value: "layout-a",
37
- reason: "disconnected",
38
- });
39
- const resumed = advanceResourceReplica(disconnected.state, {
40
- type: "connected",
41
- daemonInstanceId: GENERATION_A,
42
- });
43
- expect(resumed.state.value).toBe("layout-a");
44
- expect(resumed.effects).toEqual([{ type: "request-snapshot", daemonInstanceId: GENERATION_A }]);
45
- });
46
-
47
- it("retires generation-scoped authority before syncing a replacement daemon", () => {
48
- const transition = advanceResourceReplica(live(), {
49
- type: "connected",
50
- daemonInstanceId: GENERATION_B,
51
- });
52
- expect(transition.state).toMatchObject({
53
- phase: "syncing",
54
- daemonInstanceId: GENERATION_B,
55
- value: "layout-a",
56
- reason: "generation-changed",
57
- });
58
- expect(transition.effects).toEqual([
59
- { type: "retire-generation", daemonInstanceId: GENERATION_A },
60
- { type: "request-snapshot", daemonInstanceId: GENERATION_B },
61
- ]);
62
- });
63
-
64
- it("refreshes only for a contiguous event carrying a newer resource revision", () => {
65
- const transition = advanceResourceReplica(live(), {
66
- type: "changed",
67
- daemonInstanceId: GENERATION_A,
68
- sequence: 11,
69
- revision: 5,
70
- causeOperationId: "operation-a",
71
- });
72
- expect(transition.state).toMatchObject({ phase: "stale", sequence: 11, reason: "changed" });
73
- expect(transition.effects).toEqual([
74
- {
75
- type: "acknowledge-operation",
76
- daemonInstanceId: GENERATION_A,
77
- operationId: "operation-a",
78
- sequence: 11,
79
- revision: 5,
80
- },
81
- {
82
- type: "refresh-resource",
83
- daemonInstanceId: GENERATION_A,
84
- minimumRevision: 5,
85
- },
86
- ]);
87
-
88
- const duplicate = advanceResourceReplica(live(), {
89
- type: "changed",
90
- daemonInstanceId: GENERATION_A,
91
- sequence: 10,
92
- revision: 5,
93
- });
94
- expect(duplicate).toEqual({ state: live(), effects: [] });
95
- });
96
-
97
- it("acknowledges an observed idempotent mutation even when its resource revision is unchanged", () => {
98
- const transition = advanceResourceReplica(live(), {
99
- type: "changed",
100
- daemonInstanceId: GENERATION_A,
101
- sequence: 11,
102
- revision: 4,
103
- causeOperationId: "operation-unchanged",
104
- });
105
- expect(transition.state).toMatchObject({ phase: "live", sequence: 11, revision: 4 });
106
- expect(transition.effects).toEqual([
107
- {
108
- type: "acknowledge-operation",
109
- daemonInstanceId: GENERATION_A,
110
- operationId: "operation-unchanged",
111
- sequence: 11,
112
- revision: 4,
113
- },
114
- ]);
115
- });
116
-
117
- it("requests a full snapshot when any event sequence is missing", () => {
118
- const transition = advanceResourceReplica(live(), {
119
- type: "changed",
120
- daemonInstanceId: GENERATION_A,
121
- sequence: 13,
122
- revision: 5,
123
- });
124
- expect(transition.state).toMatchObject({ phase: "stale", reason: "event-gap" });
125
- expect(transition.effects).toEqual([
126
- { type: "request-snapshot", daemonInstanceId: GENERATION_A },
127
- ]);
128
- });
129
-
130
- it("advances over a contiguous interaction receipt without refreshing a resource", () => {
131
- const transition = advanceResourceReplica(live(), {
132
- type: "observed",
133
- daemonInstanceId: GENERATION_A,
134
- sequence: 11,
135
- });
136
- expect(transition.state).toMatchObject({ phase: "live", sequence: 11, revision: 4 });
137
- expect(transition.effects).toEqual([]);
138
- });
139
-
140
- it("allows multiple clients to converge independently on one shared snapshot", () => {
141
- let left = initialResourceReplica<{ windows: number }>();
142
- let right = initialResourceReplica<{ windows: number }>();
143
- for (const set of [
144
- (value: typeof left) => (left = value),
145
- (value: typeof right) => (right = value),
146
- ]) {
147
- const connected = advanceResourceReplica(initialResourceReplica<{ windows: number }>(), {
148
- type: "connected",
149
- daemonInstanceId: GENERATION_A,
150
- });
151
- const snapshot = advanceResourceReplica(connected.state, {
152
- type: "snapshot",
153
- daemonInstanceId: GENERATION_A,
154
- sequence: 7,
155
- revision: 3,
156
- value: { windows: 6 },
157
- });
158
- set(snapshot.state);
159
- }
160
- expect(left).toEqual(right);
161
-
162
- left = advanceResourceReplica(left, {
163
- type: "disconnected",
164
- }).state;
165
- expect(left.phase).toBe("stale");
166
- expect(right.phase).toBe("live");
167
- expect(left.value).toEqual(right.value);
168
- });
169
-
170
- it("refuses malformed counters and identities at the host-neutral boundary", () => {
171
- expect(() =>
172
- advanceResourceReplica(initialResourceReplica(), {
173
- type: "connected",
174
- daemonInstanceId: "",
175
- }),
176
- ).toThrow("daemonInstanceId");
177
- expect(() =>
178
- advanceResourceReplica(live(), {
179
- type: "changed",
180
- daemonInstanceId: GENERATION_A,
181
- sequence: -1,
182
- revision: 5,
183
- }),
184
- ).toThrow("sequence");
185
- });
186
- });
@@ -1,18 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { acquireRuntimeResource, runtimeResourceSnapshot } from "./runtime-resource-ledger.ts";
4
-
5
- describe("runtime resource ledger", () => {
6
- it("counts idempotent lifecycle acquisition and release", () => {
7
- const baseline = runtimeResourceSnapshot()["pane-stream-socket"];
8
- const release = acquireRuntimeResource("pane-stream-socket");
9
- expect(runtimeResourceSnapshot()["pane-stream-socket"].active).toBe(baseline.active + 1);
10
- release();
11
- release();
12
- expect(runtimeResourceSnapshot()["pane-stream-socket"]).toEqual({
13
- created: baseline.created + 1,
14
- disposed: baseline.disposed + 1,
15
- active: baseline.active,
16
- });
17
- });
18
- });