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,175 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { FirstLatestCoordinator } from "./first-latest-coordinator.ts";
4
-
5
- function deferred() {
6
- let resolve!: () => void;
7
- let reject!: (error: unknown) => void;
8
- const promise = new Promise<void>((accept, deny) => {
9
- resolve = accept;
10
- reject = deny;
11
- });
12
- return { promise, resolve, reject };
13
- }
14
-
15
- async function settle(): Promise<void> {
16
- await Promise.resolve();
17
- await Promise.resolve();
18
- }
19
-
20
- describe("FirstLatestCoordinator", () => {
21
- it("runs the first and latest request while superseding intermediate work", async () => {
22
- const coordinator = new FirstLatestCoordinator();
23
- const first = deferred();
24
- const latest = deferred();
25
- const events: string[] = [];
26
- const request = (key: string, execution: Promise<void>) => ({
27
- key,
28
- execute: () => execution,
29
- onSuccess: () => events.push(`${key}:ok`),
30
- onFailure: () => events.push(`${key}:failed`),
31
- onSuperseded: () => events.push(`${key}:superseded`),
32
- });
33
-
34
- expect(coordinator.request(request("80x24", first.promise))).toBe("started");
35
- expect(coordinator.request(request("90x30", Promise.resolve()))).toBe("queued");
36
- expect(coordinator.request(request("100x40", latest.promise))).toBe("queued");
37
- expect(events).toEqual(["90x30:superseded"]);
38
- expect(coordinator.snapshot()).toEqual({ activeKey: "80x24", pendingKey: "100x40" });
39
-
40
- first.resolve();
41
- await settle();
42
- expect(events).toEqual(["90x30:superseded", "80x24:ok"]);
43
- latest.resolve();
44
- await settle();
45
- expect(events).toEqual(["90x30:superseded", "80x24:ok", "100x40:ok"]);
46
- });
47
-
48
- it("coalesces equal active/pending keys and fences late retirement", async () => {
49
- const coordinator = new FirstLatestCoordinator();
50
- const active = deferred();
51
- const events: string[] = [];
52
- const request = (key: string) => ({
53
- key,
54
- execute: () => active.promise,
55
- onSuccess: () => events.push(`${key}:ok`),
56
- onFailure: () => events.push(`${key}:failed`),
57
- onSuperseded: () => events.push(`${key}:superseded`),
58
- });
59
-
60
- expect(coordinator.request(request("a"))).toBe("started");
61
- expect(coordinator.request(request("b"))).toBe("queued");
62
- expect(coordinator.request(request("b"))).toBe("coalesced-pending");
63
- expect(coordinator.request(request("a"))).toBe("coalesced-active");
64
- expect(events).toEqual(["b:superseded"]);
65
- coordinator.retire();
66
- active.resolve();
67
- await settle();
68
- expect(events).toEqual(["b:superseded"]);
69
- expect(coordinator.snapshot()).toEqual({ activeKey: null, pendingKey: null });
70
- });
71
-
72
- it("settles synchronous throws through the failure callback", () => {
73
- const coordinator = new FirstLatestCoordinator();
74
- const error = new Error("boom");
75
- let observed: unknown = null;
76
- coordinator.request({
77
- key: "bad",
78
- execute: () => {
79
- throw error;
80
- },
81
- onSuccess: () => undefined,
82
- onFailure: (failure) => (observed = failure),
83
- });
84
- expect(observed).toBe(error);
85
- expect(coordinator.snapshot()).toEqual({ activeKey: null, pendingKey: null });
86
- });
87
-
88
- it("drains retained work when success and failure observers throw", async () => {
89
- const coordinator = new FirstLatestCoordinator();
90
- const first = deferred();
91
- const second = deferred();
92
- const third = deferred();
93
- const executions: string[] = [];
94
-
95
- coordinator.request({
96
- key: "first",
97
- execute: () => {
98
- executions.push("first");
99
- return first.promise;
100
- },
101
- onSuccess: () => {
102
- throw new Error("success observer");
103
- },
104
- onFailure: () => undefined,
105
- });
106
- coordinator.request({
107
- key: "second",
108
- execute: () => {
109
- executions.push("second");
110
- return second.promise;
111
- },
112
- onSuccess: () => undefined,
113
- onFailure: () => {
114
- throw new Error("failure observer");
115
- },
116
- });
117
-
118
- first.resolve();
119
- await settle();
120
- expect(executions).toEqual(["first", "second"]);
121
- expect(coordinator.snapshot()).toEqual({ activeKey: "second", pendingKey: null });
122
-
123
- coordinator.request({
124
- key: "third",
125
- execute: () => {
126
- executions.push("third");
127
- return third.promise;
128
- },
129
- onSuccess: () => undefined,
130
- onFailure: () => undefined,
131
- });
132
- second.reject(new Error("execution failed"));
133
- await settle();
134
- expect(executions).toEqual(["first", "second", "third"]);
135
- expect(coordinator.snapshot()).toEqual({ activeKey: "third", pendingKey: null });
136
-
137
- third.resolve();
138
- await settle();
139
- expect(coordinator.snapshot()).toEqual({ activeKey: null, pendingKey: null });
140
- });
141
-
142
- it("retains the latest request when superseded observers throw and retires cleanly", async () => {
143
- const coordinator = new FirstLatestCoordinator();
144
- const active = deferred();
145
- const latest = deferred();
146
- const executions: string[] = [];
147
- const request = (key: string, execution: Promise<void>, onSuperseded?: () => void) => ({
148
- key,
149
- execute: () => {
150
- executions.push(key);
151
- return execution;
152
- },
153
- onSuccess: () => undefined,
154
- onFailure: () => undefined,
155
- onSuperseded,
156
- });
157
-
158
- coordinator.request(request("active", active.promise));
159
- coordinator.request(
160
- request("intermediate", Promise.resolve(), () => {
161
- throw new Error("superseded observer");
162
- }),
163
- );
164
- expect(() => coordinator.request(request("latest", latest.promise))).not.toThrow();
165
- expect(coordinator.snapshot()).toEqual({ activeKey: "active", pendingKey: "latest" });
166
-
167
- active.resolve();
168
- await settle();
169
- expect(executions).toEqual(["active", "latest"]);
170
- coordinator.retire();
171
- latest.resolve();
172
- await settle();
173
- expect(coordinator.snapshot()).toEqual({ activeKey: null, pendingKey: null });
174
- });
175
- });
@@ -1,114 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { createFleetDialScheduler, fleetReconnectBackoff } from "./fleet-dial-scheduler.ts";
3
-
4
- function deferred<T>() {
5
- let resolve!: (value: T) => void;
6
- const promise = new Promise<T>((done) => {
7
- resolve = done;
8
- });
9
- return { promise, resolve };
10
- }
11
- const tick = () => new Promise<void>((done) => setTimeout(done, 0));
12
-
13
- describe("fleet dial scheduling", () => {
14
- it("bounds 64 handshakes and prioritizes the selected queued route", async () => {
15
- const scheduler = createFleetDialScheduler();
16
- const gates = Array.from({ length: 64 }, () => deferred<number>());
17
- const started: number[] = [];
18
- const requests = gates.map((gate, index) =>
19
- scheduler.run(
20
- String(index),
21
- new AbortController().signal,
22
- () => {
23
- started.push(index);
24
- return gate.promise;
25
- },
26
- () => {},
27
- ),
28
- );
29
- expect(started).toEqual([0, 1, 2, 3]);
30
- expect(scheduler.snapshot()).toEqual({ active: 4, queued: 60, limit: 4 });
31
- scheduler.prioritize("63");
32
- gates[0]!.resolve(0);
33
- await tick();
34
- expect(started).toEqual([0, 1, 2, 3, 63]);
35
- for (let i = 0; i < gates.length; i++) gates[i]!.resolve(i);
36
- expect(await Promise.all(requests)).toEqual(gates.map((_, i) => i));
37
- await tick();
38
- expect(scheduler.snapshot()).toEqual({ active: 0, queued: 0, limit: 4 });
39
- });
40
-
41
- it("cancels queued requests without dialing and disposes late active results without oversubscription", async () => {
42
- const scheduler = createFleetDialScheduler(1);
43
- const first = deferred<number>();
44
- const abort = new AbortController();
45
- const queuedAbort = new AbortController();
46
- const disposed: number[] = [];
47
- let queuedStarted = false;
48
- const active = scheduler.run(
49
- "a",
50
- abort.signal,
51
- () => first.promise,
52
- (value) => {
53
- disposed.push(value);
54
- },
55
- );
56
- const queued = scheduler.run(
57
- "b",
58
- queuedAbort.signal,
59
- async () => {
60
- queuedStarted = true;
61
- return 2;
62
- },
63
- () => {},
64
- );
65
- const activeRejected = active.catch((error: Error) => error);
66
- const queuedRejected = queued.catch((error: Error) => error);
67
- abort.abort();
68
- queuedAbort.abort();
69
- expect(await activeRejected).toBeInstanceOf(Error);
70
- expect(await queuedRejected).toBeInstanceOf(Error);
71
- expect(scheduler.snapshot()).toEqual({ active: 1, queued: 0, limit: 1 });
72
- expect(queuedStarted).toBe(false);
73
- first.resolve(1);
74
- await tick();
75
- expect(disposed).toEqual([1]);
76
- expect(scheduler.snapshot().active).toBe(0);
77
- });
78
-
79
- it("releases a failed slot and never dials an already-aborted request", async () => {
80
- const scheduler = createFleetDialScheduler(1);
81
- const abort = new AbortController();
82
- abort.abort();
83
- let calls = 0;
84
- await expect(
85
- scheduler.run(
86
- "a",
87
- abort.signal,
88
- async () => ++calls,
89
- () => {},
90
- ),
91
- ).rejects.toThrow("cancelled");
92
- await expect(
93
- scheduler.run(
94
- "b",
95
- new AbortController().signal,
96
- () => {
97
- throw new Error("dial failed");
98
- },
99
- () => {},
100
- ),
101
- ).rejects.toThrow("dial failed");
102
- await tick();
103
- expect(calls).toBe(0);
104
- expect(scheduler.snapshot().active).toBe(0);
105
- });
106
-
107
- it("uses bounded nonzero jitter even with invalid random sources", () => {
108
- expect(fleetReconnectBackoff(1, () => 0)).toBe(500);
109
- expect(fleetReconnectBackoff(1, () => 1)).toBe(1000);
110
- expect(fleetReconnectBackoff(100, () => 1)).toBe(30000);
111
- expect(fleetReconnectBackoff(100, () => -1)).toBe(15000);
112
- expect(fleetReconnectBackoff(100, () => NaN)).toBe(22500);
113
- });
114
- });
@@ -1,40 +0,0 @@
1
- import { expect, it } from "bun:test";
2
- import { groupFleetEnvironments } from "./fleet-environments.ts";
3
-
4
- const route = (id: string, ready = true) => ({
5
- id,
6
- ready,
7
- environmentId: "verified-env",
8
- generation: "generation-1",
9
- });
10
-
11
- it("joins only authenticated identities and prefers the current healthy route", () => {
12
- expect(groupFleetEnvironments([route("a"), route("b")], "b")).toEqual([
13
- { environmentId: "verified-env", routeIds: ["a", "b"], primaryRouteId: "b", conflict: false },
14
- ]);
15
- expect(
16
- groupFleetEnvironments([route("a"), { ...route("b"), environmentId: null }], "b"),
17
- ).toHaveLength(2);
18
- expect(
19
- groupFleetEnvironments([route("a"), { ...route("b"), generation: null }], "b"),
20
- ).toHaveLength(2);
21
- });
22
-
23
- it("keeps live or cached conflicting generations separate until reverified", () => {
24
- const groups = groupFleetEnvironments(
25
- [route("a"), { ...route("b", false), generation: "generation-2" }],
26
- "a",
27
- );
28
- expect(groups).toHaveLength(2);
29
- expect(groups.every((group) => group.conflict)).toBe(true);
30
- });
31
-
32
- it("offers a healthy display route without changing the selected authority", () => {
33
- const preferred = new Map([["verified-env", "a"]]);
34
- const groups = groupFleetEnvironments([route("a", false), route("b")], "a", preferred);
35
- expect(groups[0]!.primaryRouteId).toBe("b");
36
- expect(preferred.get("verified-env")).toBe("a");
37
- expect(
38
- groupFleetEnvironments([route("a"), route("b")], "local", preferred)[0]!.primaryRouteId,
39
- ).toBe("a");
40
- });
@@ -1,74 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- createGenerationBoundStore,
4
- type GenerationBoundClock,
5
- type GenerationBoundEventHandlers,
6
- } from "./generation-bound-store.ts";
7
- import { runtimeResourceSnapshot } from "./runtime-resource-ledger.ts";
8
-
9
- class Clock implements GenerationBoundClock {
10
- next = 0;
11
- readonly timers = new Map<number, () => void>();
12
- now(): number {
13
- return 0;
14
- }
15
- setTimeout(callback: () => void): unknown {
16
- const id = ++this.next;
17
- this.timers.set(id, callback);
18
- return id;
19
- }
20
- clearTimeout(handle: unknown): void {
21
- this.timers.delete(handle as number);
22
- }
23
- runOne(): void {
24
- const entry = this.timers.entries().next().value as [number, () => void] | undefined;
25
- if (!entry) throw new Error("no timer");
26
- this.timers.delete(entry[0]);
27
- entry[1]();
28
- }
29
- }
30
-
31
- describe("generation-bound timer ownership", () => {
32
- it("retires generation-bound retry timers on disposal", async () => {
33
- const baseline = runtimeResourceSnapshot()["runtime-timer"].active;
34
- const clock = new Clock();
35
- let connects = 0;
36
- const store = createGenerationBoundStore(
37
- {
38
- validateTarget: (value: unknown) => ({
39
- ok: true as const,
40
- target: String(value),
41
- key: String(value),
42
- }),
43
- fetch: async () => ({ status: "failed" as const, failure: "retry" }),
44
- connect: (_target: string, next: GenerationBoundEventHandlers<string>) => {
45
- void next;
46
- connects += 1;
47
- return connects === 1
48
- ? { status: "failed" as const, failure: "retry" }
49
- : { status: "connected" as const, close: () => undefined };
50
- },
51
- disposition: () => "retry" as const,
52
- rejectionFailure: () => "retry",
53
- transportFailure: () => "retry",
54
- eventExhaustedFailure: () => "exhausted",
55
- project: (view) => view.phase.kind,
56
- },
57
- "alpha",
58
- {
59
- clock,
60
- retry: {
61
- initialDelayMs: 10,
62
- maximumDelayMs: 10,
63
- maximumAttempts: 3,
64
- stabilityWindowMs: 10,
65
- },
66
- },
67
- );
68
- await Promise.resolve();
69
- await Promise.resolve();
70
- expect(runtimeResourceSnapshot()["runtime-timer"].active).toBeGreaterThan(baseline);
71
- store.dispose();
72
- expect(runtimeResourceSnapshot()["runtime-timer"].active).toBe(baseline);
73
- });
74
- });
@@ -1,245 +0,0 @@
1
- import { describe, expect, it, mock } from "bun:test";
2
-
3
- import { DaemonActionInvocationError, dispatchOwnerAction } from "./owner-action-client.ts";
4
-
5
- const operationId = "10000000-0000-4000-8000-000000000001";
6
- const daemonInstanceId = "20000000-0000-4000-8000-000000000002";
7
- const mutation = {
8
- operationId,
9
- daemonInstanceId,
10
- outcome: "applied" as const,
11
- workspaceName: "project",
12
- };
13
-
14
- describe("owner action client", () => {
15
- it("sends the owner credential and stable operation id", async () => {
16
- const request = mock(async (_url: string | URL | Request, init?: RequestInit) => {
17
- expect(init?.headers).toEqual({
18
- "Content-Type": "application/json",
19
- Authorization: "Bearer owner-token",
20
- "X-Tmux-Ide-Operation-Id": operationId,
21
- "X-Tmux-Ide-Host-Client-Id": "opentui:42",
22
- });
23
- return Response.json({
24
- ok: true,
25
- result: {
26
- ...mutation,
27
- verb: "workspace.window.split",
28
- direction: "right",
29
- semanticPaneId: "pane.created",
30
- displayTitle: "Terminal",
31
- },
32
- });
33
- });
34
-
35
- await expect(
36
- dispatchOwnerAction({
37
- baseUrl: "http://127.0.0.1:4000",
38
- ownerToken: "owner-token",
39
- name: "workspace.window.split",
40
- input: {
41
- workspaceName: "project",
42
- semanticPaneId: "pane.editor",
43
- direction: "right",
44
- },
45
- operationId,
46
- hostClientId: "opentui:42",
47
- fetch: request as typeof fetch,
48
- }),
49
- ).resolves.toMatchObject({ outcome: "applied", verb: "workspace.window.split" });
50
- expect(request).toHaveBeenCalledTimes(1);
51
- });
52
-
53
- it("retries one ambiguous response when the operation is idempotent", async () => {
54
- const request = mock()
55
- .mockRejectedValueOnce(new Error("connection reset"))
56
- .mockResolvedValueOnce(
57
- Response.json({
58
- ok: true,
59
- result: {
60
- ...mutation,
61
- verb: "workspace.pane.kill",
62
- windowClosed: false,
63
- remainingWindowCount: 1,
64
- },
65
- }),
66
- );
67
-
68
- await expect(
69
- dispatchOwnerAction({
70
- baseUrl: "http://127.0.0.1:4000",
71
- ownerToken: "owner-token",
72
- name: "workspace.pane.kill",
73
- input: { workspaceName: "project", semanticPaneId: "pane.editor" },
74
- operationId,
75
- fetch: request as typeof fetch,
76
- }),
77
- ).resolves.toMatchObject({ outcome: "applied", verb: "workspace.pane.kill" });
78
- expect(request).toHaveBeenCalledTimes(2);
79
- });
80
-
81
- it("keeps one operation id across a bounded delayed retry schedule", async () => {
82
- const request = mock()
83
- .mockRejectedValueOnce(new Error("first deadline"))
84
- .mockRejectedValueOnce(new Error("second deadline"))
85
- .mockResolvedValueOnce(
86
- Response.json({
87
- ok: true,
88
- result: {
89
- ...mutation,
90
- verb: "workspace.pane.kill",
91
- windowClosed: false,
92
- remainingWindowCount: 1,
93
- },
94
- }),
95
- );
96
- const waits: number[] = [];
97
-
98
- await expect(
99
- dispatchOwnerAction({
100
- baseUrl: "http://127.0.0.1:4000",
101
- ownerToken: "owner-token",
102
- name: "workspace.pane.kill",
103
- input: { workspaceName: "project", semanticPaneId: "pane.editor" },
104
- operationId,
105
- fetch: request as typeof fetch,
106
- maximumAttempts: 3,
107
- retryDelayMs: (completedAttempts) => completedAttempts * 25,
108
- wait: async (delayMs) => {
109
- waits.push(delayMs);
110
- },
111
- }),
112
- ).resolves.toMatchObject({ outcome: "applied", verb: "workspace.pane.kill" });
113
-
114
- expect(waits).toEqual([25, 50]);
115
- expect(request).toHaveBeenCalledTimes(3);
116
- for (const [, init] of request.mock.calls) {
117
- expect(init?.headers).toMatchObject({ "X-Tmux-Ide-Operation-Id": operationId });
118
- }
119
- });
120
-
121
- it("recovers a promotion whose first response outlives the client deadline", async () => {
122
- const operationIds: Array<string | undefined> = [];
123
- let attempt = 0;
124
- const request = mock((_url: string | URL | Request, init?: RequestInit) => {
125
- operationIds.push((init?.headers as Record<string, string>)?.["X-Tmux-Ide-Operation-Id"]);
126
- attempt += 1;
127
- if (attempt === 1)
128
- return new Promise<Response>((_resolve, reject) => {
129
- init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), {
130
- once: true,
131
- });
132
- });
133
- return Promise.resolve(
134
- Response.json({
135
- ok: true,
136
- result: {
137
- operationId,
138
- daemonInstanceId,
139
- outcome: "replayed",
140
- resource: { resourceVersion: 1, workspaceName: "workspace.alpha" },
141
- },
142
- }),
143
- );
144
- });
145
-
146
- await expect(
147
- dispatchOwnerAction({
148
- baseUrl: "http://127.0.0.1:4000",
149
- ownerToken: "owner-token",
150
- name: "workspace.promote",
151
- input: { sessionId: "session.aaaaaaaaaaaaaaaaaaaa" },
152
- operationId,
153
- fetch: request as typeof fetch,
154
- timeoutMs: 5,
155
- maximumAttempts: 2,
156
- retryDelayMs: () => 1,
157
- }),
158
- ).resolves.toMatchObject({ operationId, outcome: "replayed" });
159
- expect(operationIds).toEqual([operationId, operationId]);
160
- });
161
-
162
- it("surfaces typed daemon refusals", async () => {
163
- const request = mock(async () =>
164
- Response.json({
165
- ok: false,
166
- error: { code: "bad_request", message: "Cannot close the last pane" },
167
- }),
168
- );
169
-
170
- const result = dispatchOwnerAction({
171
- baseUrl: "http://127.0.0.1:4000",
172
- ownerToken: "owner-token",
173
- name: "workspace.pane.kill",
174
- input: { workspaceName: "project", semanticPaneId: "pane.editor" },
175
- fetch: request as typeof fetch,
176
- });
177
- await expect(result).rejects.toBeInstanceOf(DaemonActionInvocationError);
178
- await expect(result).rejects.toThrow("Cannot close the last pane");
179
- });
180
- });
181
-
182
- describe("owner transport ambiguous responses", () => {
183
- const options = {
184
- baseUrl: "http://localhost:4000/",
185
- ownerToken: "token",
186
- name: "workspace.pane.kill" as const,
187
- input: { workspaceName: "project", semanticPaneId: "pane.editor" },
188
- };
189
- it("does not retry a refusal even with an operation ID", async () => {
190
- const request = mock(async () =>
191
- Response.json({ ok: false, error: { code: "refused", message: "Denied" } }),
192
- );
193
- await expect(
194
- dispatchOwnerAction({ ...options, operationId, fetch: request as typeof fetch }),
195
- ).rejects.toMatchObject({ code: "refused" });
196
- expect(request).toHaveBeenCalledTimes(1);
197
- });
198
- it.each(["invalid json", "invalid result"])(
199
- "retries %s with identical operation and body",
200
- async (kind) => {
201
- const request = mock()
202
- .mockResolvedValueOnce(
203
- kind === "invalid json" ? new Response("{") : Response.json({ ok: true, result: {} }),
204
- )
205
- .mockResolvedValueOnce(
206
- Response.json({
207
- ok: true,
208
- result: {
209
- ...mutation,
210
- verb: "workspace.pane.kill",
211
- windowClosed: false,
212
- remainingWindowCount: 1,
213
- },
214
- }),
215
- );
216
- await expect(
217
- dispatchOwnerAction({ ...options, operationId, fetch: request as typeof fetch }),
218
- ).resolves.toMatchObject({ outcome: "applied" });
219
- expect(request).toHaveBeenCalledTimes(2);
220
- expect(request.mock.calls[0]![1].body).toBe(request.mock.calls[1]![1].body);
221
- expect(request.mock.calls[0]![1].headers).toEqual(request.mock.calls[1]![1].headers);
222
- },
223
- );
224
- it("does not retry ambiguity without an operation ID", async () => {
225
- const request = mock(async () => new Response("{"));
226
- await expect(
227
- dispatchOwnerAction({ ...options, maximumAttempts: 3, fetch: request as typeof fetch }),
228
- ).resolves.toBeNull();
229
- expect(request).toHaveBeenCalledTimes(1);
230
- });
231
- it.each(["http://localhost:4000", "http://localhost:4000/", "http://localhost:4000/prefix/"])(
232
- "normalizes endpoint slash for %s",
233
- async (baseUrl) => {
234
- const request = mock(async () =>
235
- Response.json({ ok: false, error: { code: "refused", message: "Denied" } }),
236
- );
237
- await expect(
238
- dispatchOwnerAction({ ...options, baseUrl, fetch: request as typeof fetch }),
239
- ).rejects.toThrow("Denied");
240
- expect(request.mock.calls[0]![0]).toBe(
241
- baseUrl.replace(/\/$/u, "") + "/api/v2/action/workspace.pane.kill",
242
- );
243
- },
244
- );
245
- });