tmux-ide 2.9.2 → 2.9.3

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 (263) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2071 -625
  3. package/package.json +14 -5
  4. package/packages/contracts/src/desktop-host.ts +3 -0
  5. package/packages/contracts/src/saved-machines.ts +19 -0
  6. package/packages/contracts/src/terminal-replica.ts +1 -1
  7. package/packages/core/package.json +3 -2
  8. package/packages/core/src/terminal-delivery.ts +51 -3
  9. package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
  10. package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
  11. package/packages/core/src/terminal-replica-owned-row.ts +27 -0
  12. package/packages/core/src/terminal-replica.ts +13 -28
  13. package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
  14. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
  15. package/packages/daemon/dist/command-center/ws-events.js +5 -24
  16. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
  17. package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
  18. package/packages/daemon/dist/lib/daemon-embed.js +1 -1
  19. package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
  20. package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
  21. package/packages/daemon/dist/lib/daemon-service.js +259 -0
  22. package/packages/daemon/dist/lib/install-origin.js +2 -0
  23. package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
  24. package/packages/daemon/dist/lib/log.js +6 -10
  25. package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
  26. package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
  27. package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
  28. package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
  29. package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
  30. package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
  31. package/packages/daemon/dist/lib/saved-machines.js +40 -2
  32. package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
  33. package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
  34. package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
  35. package/packages/daemon/dist/lib/tui-binary.js +2 -2
  36. package/packages/daemon/dist/lib/update.js +18 -0
  37. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
  38. package/packages/daemon/dist/machines.js +40 -4
  39. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  40. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  41. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  42. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  43. package/packages/daemon/dist/terminal/attachments/direct-websocket.js +4 -1
  44. package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
  45. package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
  46. package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
  47. package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
  48. package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
  49. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
  50. package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
  51. package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
  52. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
  53. package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
  54. package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
  55. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
  56. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
  57. package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
  58. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
  59. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
  60. package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
  61. package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
  62. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
  63. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
  64. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
  65. package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
  66. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
  67. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
  68. package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
  69. package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
  70. package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
  71. package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
  72. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
  73. package/packages/daemon/src/command-center/ws-events.ts +5 -25
  74. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
  75. package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
  76. package/packages/daemon/src/lib/daemon-embed.ts +1 -0
  77. package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
  78. package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
  79. package/packages/daemon/src/lib/daemon-service.ts +301 -0
  80. package/packages/daemon/src/lib/install-origin.ts +5 -0
  81. package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
  82. package/packages/daemon/src/lib/log.ts +13 -9
  83. package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
  84. package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
  85. package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
  86. package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
  87. package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
  88. package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
  89. package/packages/daemon/src/lib/saved-machines.ts +40 -1
  90. package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
  91. package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
  92. package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
  93. package/packages/daemon/src/lib/tui-binary.ts +2 -2
  94. package/packages/daemon/src/lib/update.ts +24 -0
  95. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
  96. package/packages/daemon/src/machines.ts +53 -5
  97. package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
  98. package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
  99. package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
  100. package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
  101. package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
  102. package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
  103. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
  104. package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
  105. package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
  106. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
  107. package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
  108. package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
  109. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
  110. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
  111. package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
  112. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
  113. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
  114. package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
  115. package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
  116. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
  117. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
  118. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
  119. package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
  120. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
  121. package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
  122. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
  123. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
  124. package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
  125. package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
  126. package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
  127. package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
  128. package/packages/daemon-client/src/workspace-client.ts +4 -1
  129. package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
  130. package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
  131. package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
  132. package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
  133. package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
  134. package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
  135. package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
  136. package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
  137. package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
  138. package/packages/contracts/src/__tests__/commands.test.ts +0 -86
  139. package/packages/contracts/src/__tests__/control.test.ts +0 -154
  140. package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
  141. package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
  142. package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
  143. package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
  144. package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
  145. package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
  146. package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
  147. package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
  148. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
  149. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
  150. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
  151. package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
  152. package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
  153. package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
  154. package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
  155. package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
  156. package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
  157. package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
  158. package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
  159. package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
  160. package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
  161. package/packages/contracts/src/__tests__/missions.test.ts +0 -272
  162. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
  163. package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
  164. package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
  165. package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
  166. package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
  167. package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
  168. package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
  169. package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
  170. package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
  171. package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
  172. package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
  173. package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
  174. package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
  175. package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
  176. package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
  177. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
  178. package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
  179. package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
  180. package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
  181. package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
  182. package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
  183. package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
  184. package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
  185. package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
  186. package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
  187. package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
  188. package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
  189. package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
  190. package/packages/contracts/src/native-operation-identity.test.ts +0 -23
  191. package/packages/contracts/src/workspace-admission.test.ts +0 -21
  192. package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
  193. package/packages/core/src/agent-provisioning.test.ts +0 -42
  194. package/packages/core/src/application-shell-session.test.ts +0 -105
  195. package/packages/core/src/interaction-evidence.test.ts +0 -134
  196. package/packages/core/src/interaction-flow.test.ts +0 -66
  197. package/packages/core/src/interaction-receipts.test.ts +0 -671
  198. package/packages/core/src/navigator.test.ts +0 -34
  199. package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
  200. package/packages/core/src/optimistic-projection.test.ts +0 -118
  201. package/packages/core/src/performance-metrics.test.ts +0 -121
  202. package/packages/core/src/performance-qualification.test.ts +0 -283
  203. package/packages/core/src/saved-machines.test.ts +0 -80
  204. package/packages/core/src/terminal-compact-cold.test.ts +0 -87
  205. package/packages/core/src/terminal-conformance.test.ts +0 -48
  206. package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
  207. package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
  208. package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
  209. package/packages/core/src/terminal-delivery.test.ts +0 -1471
  210. package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
  211. package/packages/core/src/terminal-replica-cold.test.ts +0 -31
  212. package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
  213. package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
  214. package/packages/core/src/terminal-replica.test.ts +0 -347
  215. package/packages/core/src/workspace-selection.test.ts +0 -63
  216. package/packages/daemon/dist/__tests__/support.js +0 -33
  217. package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
  218. package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
  219. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
  220. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
  221. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
  222. package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
  223. package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
  224. package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
  225. package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
  226. package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
  227. package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
  228. package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
  229. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
  230. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
  231. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
  232. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
  233. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
  234. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
  235. package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
  236. package/packages/daemon-client/src/automation-client.test.ts +0 -214
  237. package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
  238. package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
  239. package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
  240. package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
  241. package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
  242. package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
  243. package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
  244. package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
  245. package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
  246. package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
  247. package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
  248. package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
  249. package/packages/daemon-client/src/resource-replica.test.ts +0 -186
  250. package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
  251. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
  252. package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
  253. package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
  254. package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
  255. package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
  256. package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
  257. package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
  258. package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
  259. package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
  260. package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
  261. package/packages/presentation/src/presentation-contract.test.ts +0 -63
  262. package/packages/tmux-bridge/src/runner.test.ts +0 -614
  263. package/packages/tmux-bridge/src/targeting.test.ts +0 -107
@@ -1,614 +0,0 @@
1
- import { describe, it, beforeEach, afterEach, mock, expect } from "bun:test";
2
- import {
3
- _setExecutor,
4
- _setSpawner,
5
- TmuxError,
6
- getSessionState,
7
- hasSession,
8
- killSession,
9
- listPanes,
10
- createDetachedSession,
11
- splitPane,
12
- sendLiteral,
13
- getSessionVariable,
14
- setSessionVariable,
15
- startSessionMonitor,
16
- stopSessionMonitor,
17
- setPaneTitle,
18
- setWindowOption,
19
- selectPane,
20
- getPaneCurrentCommand,
21
- setSessionEnvironment,
22
- attachSession,
23
- runSessionCommand,
24
- runTmux,
25
- runTmuxBinary,
26
- runTmuxBinaryAsync,
27
- } from "./index.ts";
28
-
29
- let mockExec;
30
- let restoreExec;
31
-
32
- beforeEach(() => {
33
- mockExec = mock();
34
- restoreExec = _setExecutor(mockExec);
35
- });
36
-
37
- afterEach(() => {
38
- restoreExec();
39
- });
40
-
41
- // --- Helpers ---
42
-
43
- function makeExecError(stderr) {
44
- const err = new Error("Command failed");
45
- err.stderr = stderr;
46
- return err;
47
- }
48
-
49
- // --- Error classification (via public API) ---
50
-
51
- describe("getSessionState", () => {
52
- it("returns running: true when has-session succeeds", () => {
53
- mockExec.mockImplementation(() => "");
54
- const result = getSessionState("my-session");
55
- expect(result).toEqual({ running: true, reason: null });
56
- expect(mockExec.mock.calls[0][1]).toEqual(["has-session", "-t", "my-session"]);
57
- });
58
-
59
- it('returns SESSION_NOT_FOUND for "can\'t find session"', () => {
60
- mockExec.mockImplementation(() => {
61
- throw makeExecError("can't find session: my-session");
62
- });
63
- const result = getSessionState("my-session");
64
- expect(result).toEqual({ running: false, reason: "SESSION_NOT_FOUND" });
65
- });
66
-
67
- it('returns TMUX_UNAVAILABLE for "connection refused"', () => {
68
- mockExec.mockImplementation(() => {
69
- throw makeExecError("error connecting to /tmp/tmux-1000/default (connection refused)");
70
- });
71
- const result = getSessionState("my-session");
72
- expect(result).toEqual({ running: false, reason: "TMUX_UNAVAILABLE" });
73
- });
74
-
75
- it('returns TMUX_UNAVAILABLE for "no server running"', () => {
76
- mockExec.mockImplementation(() => {
77
- throw makeExecError("no server running on /tmp/tmux-1000/default");
78
- });
79
- const result = getSessionState("my-session");
80
- expect(result).toEqual({ running: false, reason: "TMUX_UNAVAILABLE" });
81
- });
82
-
83
- it("throws for unknown errors", () => {
84
- mockExec.mockImplementation(() => {
85
- throw makeExecError("something totally unexpected");
86
- });
87
- expect(() => getSessionState("my-session")).toThrow(TmuxError);
88
- });
89
- });
90
-
91
- describe("hasSession", () => {
92
- it("returns true when session exists", () => {
93
- mockExec.mockImplementation(() => "");
94
- expect(hasSession("proj")).toBe(true);
95
- });
96
-
97
- it("returns false for SESSION_NOT_FOUND", () => {
98
- mockExec.mockImplementation(() => {
99
- throw makeExecError("can't find session: proj");
100
- });
101
- expect(hasSession("proj")).toBe(false);
102
- });
103
-
104
- it("returns false for TMUX_UNAVAILABLE", () => {
105
- mockExec.mockImplementation(() => {
106
- throw makeExecError("no server running on /tmp/tmux-1000/default");
107
- });
108
- expect(hasSession("proj")).toBe(false);
109
- });
110
-
111
- it("throws for unknown errors", () => {
112
- mockExec.mockImplementation(() => {
113
- throw makeExecError("unexpected failure");
114
- });
115
- expect(() => hasSession("proj")).toThrow(TmuxError);
116
- });
117
- });
118
-
119
- describe("killSession", () => {
120
- it("returns stopped: true when kill succeeds", () => {
121
- mockExec.mockImplementation(() => "");
122
- const result = killSession("proj");
123
- expect(result).toEqual({ stopped: true, reason: null });
124
- expect(mockExec.mock.calls[0][1]).toEqual(["kill-session", "-t", "proj"]);
125
- });
126
-
127
- it("returns stopped: false for missing session", () => {
128
- mockExec.mockImplementation(() => {
129
- throw makeExecError("can't find session: proj");
130
- });
131
- const result = killSession("proj");
132
- expect(result).toEqual({ stopped: false, reason: "SESSION_NOT_FOUND" });
133
- });
134
-
135
- it("returns stopped: false for unavailable tmux", () => {
136
- mockExec.mockImplementation(() => {
137
- throw makeExecError("failed to connect to server");
138
- });
139
- const result = killSession("proj");
140
- expect(result).toEqual({ stopped: false, reason: "TMUX_UNAVAILABLE" });
141
- });
142
-
143
- it("throws for unknown errors", () => {
144
- mockExec.mockImplementation(() => {
145
- throw makeExecError("disk error");
146
- });
147
- expect(() => killSession("proj")).toThrow(TmuxError);
148
- });
149
- });
150
-
151
- // --- listPanes ---
152
-
153
- describe("listPanes", () => {
154
- it("parses multi-pane output correctly", () => {
155
- mockExec.mockImplementation(() => "0|Editor|120|40|1\n1|Shell|80|40|0\n");
156
- const panes = listPanes("proj");
157
- expect(panes).toEqual([
158
- { index: 0, title: "Editor", width: 120, height: 40, active: true },
159
- { index: 1, title: "Shell", width: 80, height: 40, active: false },
160
- ]);
161
- });
162
-
163
- it("returns empty array for empty output", () => {
164
- mockExec.mockImplementation(() => " \n");
165
- const panes = listPanes("proj");
166
- expect(panes).toEqual([]);
167
- });
168
-
169
- it("handles pane titles containing pipe characters", () => {
170
- // The split("|") will split on the first pipe in the title, causing misparse.
171
- // This documents the current behavior — titles with pipes are truncated.
172
- mockExec.mockImplementation(() => "0|A|B|120|40|0\n");
173
- const panes = listPanes("proj");
174
- // With pipe in title, index=0, title="A", width=NaN (from "B"), ...
175
- expect(panes[0].index).toBe(0);
176
- expect(panes[0].title).toBe("A");
177
- });
178
- });
179
-
180
- // --- createDetachedSession ---
181
-
182
- describe("createDetachedSession", () => {
183
- it("returns trimmed pane ID", () => {
184
- mockExec.mockImplementation(() => " %0\n");
185
- const id = createDetachedSession("proj", "/workspace");
186
- expect(id).toBe("%0");
187
- expect(mockExec.mock.calls.map((call) => call[1])).toContainEqual([
188
- "set-environment",
189
- "-r",
190
- "-t",
191
- "=proj",
192
- "NO_COLOR",
193
- ]);
194
- });
195
-
196
- it("uses default dimensions when not specified", () => {
197
- mockExec.mockImplementation(() => "%0\n");
198
- createDetachedSession("proj", "/workspace");
199
- const args = mockExec.mock.calls[0][1];
200
- expect(args.includes("200")).toBeTruthy(); // default cols
201
- expect(args.includes("50")).toBeTruthy(); // default lines
202
- expect(args.at(-1)).toBe(
203
- "exec env -u NO_COLOR COLORTERM=truecolor COLORFGBG='15;0' \"${SHELL:-/bin/sh}\" -l",
204
- );
205
- });
206
-
207
- it("passes custom dimensions", () => {
208
- mockExec.mockImplementation(() => "%0\n");
209
- createDetachedSession("proj", "/workspace", { cols: 300, lines: 80 });
210
- const args = mockExec.mock.calls[0][1];
211
- expect(args.includes("300")).toBeTruthy();
212
- expect(args.includes("80")).toBeTruthy();
213
- expect(args.at(-1)).toBe(
214
- "exec env -u NO_COLOR COLORTERM=truecolor COLORFGBG='15;0' \"${SHELL:-/bin/sh}\" -l",
215
- );
216
- });
217
- });
218
-
219
- // --- splitPane ---
220
-
221
- describe("splitPane", () => {
222
- it("uses -v for vertical direction", () => {
223
- mockExec.mockImplementation(() => "%1\n");
224
- splitPane("%0", "vertical", "/workspace", 30);
225
- const args = mockExec.mock.calls[0][1];
226
- expect(args.includes("-v")).toBeTruthy();
227
- expect(!args.includes("-h")).toBeTruthy();
228
- });
229
-
230
- it("uses -h for horizontal direction", () => {
231
- mockExec.mockImplementation(() => "%2\n");
232
- splitPane("%0", "horizontal", "/workspace", 50);
233
- const args = mockExec.mock.calls[0][1];
234
- expect(args.includes("-h")).toBeTruthy();
235
- expect(!args.includes("-v")).toBeTruthy();
236
- });
237
-
238
- it("passes percent correctly", () => {
239
- mockExec.mockImplementation(() => "%3\n");
240
- splitPane("%0", "vertical", "/workspace", 42);
241
- const args = mockExec.mock.calls[0][1];
242
- const pIdx = args.indexOf("-p");
243
- expect(args[pIdx + 1]).toBe("42");
244
- });
245
-
246
- it("returns trimmed pane ID", () => {
247
- mockExec.mockImplementation(() => " %5\n");
248
- const id = splitPane("%0", "vertical", "/workspace", 30);
249
- expect(id).toBe("%5");
250
- });
251
- });
252
-
253
- // --- sendLiteral ---
254
-
255
- describe("sendLiteral", () => {
256
- it("sends text with -l flag then Enter", () => {
257
- mockExec.mockImplementation(() => "");
258
- sendLiteral("%0", "echo hello");
259
- expect(mockExec.mock.calls.length).toBe(2);
260
- // First call: send-keys with -l and the text
261
- const firstArgs = mockExec.mock.calls[0][1];
262
- expect(firstArgs).toEqual(["send-keys", "-t", "%0", "-l", "--", "echo hello"]);
263
- // Second call: send Enter
264
- const secondArgs = mockExec.mock.calls[1][1];
265
- expect(secondArgs).toEqual(["send-keys", "-t", "%0", "Enter"]);
266
- });
267
- });
268
-
269
- // --- getSessionVariable / setSessionVariable ---
270
-
271
- describe("getSessionVariable", () => {
272
- it("returns trimmed value when variable exists", () => {
273
- mockExec.mockImplementation(() => " some-value\n");
274
- const value = getSessionVariable("proj", "@my_var");
275
- expect(value).toBe("some-value");
276
- });
277
-
278
- it("returns null when variable is empty", () => {
279
- mockExec.mockImplementation(() => "\n");
280
- const value = getSessionVariable("proj", "@my_var");
281
- expect(value).toBe(null);
282
- });
283
-
284
- it("returns null when show-option throws", () => {
285
- mockExec.mockImplementation(() => {
286
- throw makeExecError("can't find session: proj");
287
- });
288
- const value = getSessionVariable("proj", "@my_var");
289
- expect(value).toBe(null);
290
- });
291
- });
292
-
293
- describe("setSessionVariable", () => {
294
- it("sets variable with correct args", () => {
295
- mockExec.mockImplementation(() => "");
296
- setSessionVariable("proj", "@my_var", "hello");
297
- expect(mockExec.mock.calls[0][1]).toEqual(["set-option", "-t", "proj", "@my_var", "hello"]);
298
- });
299
- });
300
-
301
- // --- startSessionMonitor / stopSessionMonitor ---
302
-
303
- describe("startSessionMonitor", () => {
304
- it("spawns detached process and stores PID", () => {
305
- const fakeChild = { pid: 12345, unref: mock() };
306
- const restoreSpawn = _setSpawner(mock(() => fakeChild));
307
-
308
- mockExec.mockImplementation(() => "");
309
- startSessionMonitor("proj", "/path/to/monitor.js");
310
-
311
- // Check spawn was called correctly
312
- expect(fakeChild.unref.mock.calls.length).toBe(1);
313
-
314
- // First call checks for existing PID (show-option), then stores new PID (set-option)
315
- const lastCall = mockExec.mock.calls[mockExec.mock.calls.length - 1];
316
- const setArgs = lastCall[1];
317
- expect(setArgs).toEqual(["set-option", "-t", "proj", "@monitor_pid", "12345"]);
318
-
319
- restoreSpawn();
320
- });
321
-
322
- it("spawns the daemon under tsx (never bun — see T085/T087)", () => {
323
- // Pinned to tsx so node-pty's onData callback fires; bun breaks it.
324
- const fakeChild = { pid: 99, unref: mock() };
325
- const mockSpawn = mock(() => fakeChild);
326
- const restoreSpawn = _setSpawner(mockSpawn);
327
-
328
- mockExec.mockImplementation(() => "");
329
- startSessionMonitor("proj", "/path/to/monitor.ts");
330
-
331
- const spawnCall = mockSpawn.mock.calls[0];
332
- expect(spawnCall[0]).toBe("tsx");
333
- const args = spawnCall[1];
334
- expect(args).toEqual(["/path/to/monitor.ts", "proj", "0"]);
335
-
336
- restoreSpawn();
337
- });
338
- });
339
-
340
- describe("stopSessionMonitor", () => {
341
- it("kills process group by stored PID", () => {
342
- mockExec.mockImplementation(() => " 42\n");
343
- const origKill = process.kill;
344
- const killCalls: Array<{ pid: number; signal?: string }> = [];
345
- process.kill = ((pid: number, signal?: string) => {
346
- killCalls.push({ pid, signal });
347
- }) as typeof process.kill;
348
- try {
349
- stopSessionMonitor("proj");
350
- // First tries process group kill (negative PID)
351
- expect(killCalls[0]?.pid).toBe(-42);
352
- expect(killCalls[0]?.signal).toBe("SIGTERM");
353
- } finally {
354
- process.kill = origKill;
355
- }
356
- });
357
-
358
- it("handles missing PID gracefully", () => {
359
- mockExec.mockImplementation(() => {
360
- throw makeExecError("can't find session: proj");
361
- });
362
- // Should not throw
363
- stopSessionMonitor("proj");
364
- });
365
-
366
- it("handles empty PID gracefully", () => {
367
- mockExec.mockImplementation(() => "\n");
368
- // Should not throw (no pid to kill)
369
- stopSessionMonitor("proj");
370
- });
371
- });
372
-
373
- // --- Other public functions ---
374
-
375
- describe("setPaneTitle", () => {
376
- it("calls select-pane with -T flag", () => {
377
- mockExec.mockImplementation(() => "");
378
- setPaneTitle("%0", "My Pane");
379
- expect(mockExec.mock.calls[0][1]).toEqual(["select-pane", "-t", "%0", "-T", "My Pane"]);
380
- });
381
- });
382
-
383
- describe("setWindowOption", () => {
384
- it("sets a quiet window-local option through a pane target", () => {
385
- mockExec.mockImplementation(() => "");
386
- setWindowOption("%0", "@tmux_ide_window_id", "window.launch.abc123");
387
- expect(mockExec.mock.calls[0][1]).toEqual([
388
- "set-option",
389
- "-wqt",
390
- "%0",
391
- "@tmux_ide_window_id",
392
- "window.launch.abc123",
393
- ]);
394
- });
395
- });
396
-
397
- describe("selectPane", () => {
398
- it("calls select-pane with target", () => {
399
- mockExec.mockImplementation(() => "");
400
- selectPane("%2");
401
- expect(mockExec.mock.calls[0][1]).toEqual(["select-pane", "-t", "%2"]);
402
- });
403
- });
404
-
405
- describe("getPaneCurrentCommand", () => {
406
- it("returns trimmed command name", () => {
407
- mockExec.mockImplementation(() => " zsh\n");
408
- expect(getPaneCurrentCommand("%0")).toBe("zsh");
409
- });
410
- });
411
-
412
- describe("setSessionEnvironment", () => {
413
- it("calls set-environment with correct args", () => {
414
- mockExec.mockImplementation(() => "");
415
- setSessionEnvironment("proj", "PORT", 3000);
416
- expect(mockExec.mock.calls[0][1]).toEqual(["set-environment", "-t", "proj", "PORT", "3000"]);
417
- });
418
- });
419
-
420
- describe("attachSession", () => {
421
- it("calls attach with correct args and stdio inherit", () => {
422
- mockExec.mockImplementation(() => "");
423
- attachSession("proj");
424
- expect(mockExec.mock.calls[0][1]).toEqual(["attach", "-t", "proj"]);
425
- expect(mockExec.mock.calls[0][2].stdio).toBe("inherit");
426
- });
427
- });
428
-
429
- describe("runSessionCommand", () => {
430
- it("passes args through to tmux", () => {
431
- mockExec.mockImplementation(() => "");
432
- runSessionCommand(["resize-pane", "-t", "%0", "-x", "100"]);
433
- expect(mockExec.mock.calls[0][1]).toEqual(["resize-pane", "-t", "%0", "-x", "100"]);
434
- });
435
- });
436
-
437
- // --- Debug logging ---
438
-
439
- describe("debug logging", () => {
440
- it("logs to stderr when globalThis.__tmuxIdeVerbose is true", () => {
441
- const origVerbose = globalThis.__tmuxIdeVerbose;
442
- const stderrCalls = [];
443
- const origConsoleError = console.error;
444
- console.error = (...args) => stderrCalls.push(args.join(" "));
445
-
446
- globalThis.__tmuxIdeVerbose = true;
447
- mockExec.mockImplementation(() => "");
448
-
449
- try {
450
- hasSession("test-session");
451
- expect(stderrCalls.some((msg) => msg.includes("[tmux]"))).toBeTruthy();
452
- expect(stderrCalls.some((msg) => msg.includes("has-session"))).toBeTruthy();
453
- } finally {
454
- globalThis.__tmuxIdeVerbose = origVerbose;
455
- console.error = origConsoleError;
456
- }
457
- });
458
-
459
- it("does not log when neither DEBUG nor verbose is set", () => {
460
- const origVerbose = globalThis.__tmuxIdeVerbose;
461
- const stderrCalls = [];
462
- const origConsoleError = console.error;
463
- console.error = (...args) => stderrCalls.push(args.join(" "));
464
-
465
- globalThis.__tmuxIdeVerbose = false;
466
- mockExec.mockImplementation(() => "");
467
-
468
- try {
469
- hasSession("test-session");
470
- expect(stderrCalls.length).toBe(0);
471
- } finally {
472
- globalThis.__tmuxIdeVerbose = origVerbose;
473
- console.error = origConsoleError;
474
- }
475
- });
476
- });
477
-
478
- // --- Error classification edge cases ---
479
-
480
- describe("error classification", () => {
481
- it("executes an explicitly pinned binary without replacing it with ambient tmux", () => {
482
- mockExec.mockImplementation(() => "ok");
483
- expect(runTmuxBinary("/trusted/bin/tmux", ["-S", "/trusted/socket", "list-panes"])).toBe("ok");
484
- expect(mockExec.mock.calls[0][0]).toBe("/trusted/bin/tmux");
485
- expect(mockExec.mock.calls[0][1]).toEqual(["-S", "/trusted/socket", "list-panes"]);
486
- });
487
-
488
- it("sanitizes a hostile client environment before it can cold-start the server", () => {
489
- mockExec.mockImplementation(() => "ok");
490
- runTmux(["list-sessions"], {
491
- env: { PATH: "/trusted/bin", TERM: "dumb", NO_COLOR: "1", COLORTERM: "disabled" },
492
- });
493
- expect(mockExec.mock.calls[0][2].env).toEqual({
494
- PATH: "/trusted/bin",
495
- TERM: "dumb",
496
- COLORTERM: "truecolor",
497
- COLORFGBG: "15;0",
498
- });
499
- });
500
-
501
- it('classifies an exact show-environment "unknown variable" separately', () => {
502
- mockExec.mockImplementation(() => {
503
- throw makeExecError("unknown variable: TMUX_IDE_ATTACHMENT_VIEW");
504
- });
505
-
506
- expect(() => runTmux(["show-environment", "-t", "$12", "TMUX_IDE_ATTACHMENT_VIEW"])).toThrow(
507
- expect.objectContaining({
508
- code: "ENVIRONMENT_VARIABLE_NOT_FOUND",
509
- message: "tmux environment variable was not found",
510
- }),
511
- );
512
- });
513
-
514
- it('classifies "can\'t find window" as SESSION_NOT_FOUND', () => {
515
- mockExec.mockImplementation(() => {
516
- throw makeExecError("can't find window: proj:0");
517
- });
518
- const result = getSessionState("proj");
519
- expect(result).toEqual({ running: false, reason: "SESSION_NOT_FOUND" });
520
- });
521
-
522
- it('classifies "can\'t find pane" as SESSION_NOT_FOUND', () => {
523
- mockExec.mockImplementation(() => {
524
- throw makeExecError("can't find pane: %42");
525
- });
526
- expect(() => runTmux(["list-panes", "-t", "%42"])).toThrow(
527
- expect.objectContaining({ code: "SESSION_NOT_FOUND" }),
528
- );
529
- });
530
-
531
- it('classifies "unknown target" as SESSION_NOT_FOUND', () => {
532
- mockExec.mockImplementation(() => {
533
- throw makeExecError("unknown target: proj");
534
- });
535
- const result = getSessionState("proj");
536
- expect(result).toEqual({ running: false, reason: "SESSION_NOT_FOUND" });
537
- });
538
-
539
- it('classifies "failed to connect to server" as TMUX_UNAVAILABLE', () => {
540
- mockExec.mockImplementation(() => {
541
- throw makeExecError("failed to connect to server: /tmp/tmux-1000/default");
542
- });
543
- const result = getSessionState("proj");
544
- expect(result).toEqual({ running: false, reason: "TMUX_UNAVAILABLE" });
545
- });
546
-
547
- it('classifies "error connecting to" as TMUX_UNAVAILABLE', () => {
548
- mockExec.mockImplementation(() => {
549
- throw makeExecError("error connecting to /tmp/tmux-1000/default");
550
- });
551
- const result = getSessionState("proj");
552
- expect(result).toEqual({ running: false, reason: "TMUX_UNAVAILABLE" });
553
- });
554
-
555
- it("handles Buffer stderr in error", () => {
556
- mockExec.mockImplementation(() => {
557
- const err = new Error("Command failed");
558
- err.stderr = Buffer.from("can't find session: proj");
559
- throw err;
560
- });
561
- const result = getSessionState("proj");
562
- expect(result).toEqual({ running: false, reason: "SESSION_NOT_FOUND" });
563
- });
564
-
565
- it("falls back to error.message when stderr is empty", () => {
566
- mockExec.mockImplementation(() => {
567
- const err = new Error("can't find session: proj");
568
- err.stderr = "";
569
- throw err;
570
- });
571
- const result = getSessionState("proj");
572
- expect(result).toEqual({ running: false, reason: "SESSION_NOT_FOUND" });
573
- });
574
- });
575
-
576
- // --- Async pinned runner (real child processes; no executor mock) ---
577
-
578
- describe("runTmuxBinaryAsync", () => {
579
- it("resolves stdout of a pinned executable off the event loop", async () => {
580
- await expect(runTmuxBinaryAsync("/bin/sh", ["-c", "printf ok"])).resolves.toBe("ok");
581
- });
582
-
583
- it("classifies a missing target exactly like the sync runner, keeping the cause", async () => {
584
- let caught;
585
- try {
586
- await runTmuxBinaryAsync("/bin/sh", ["-c", 'echo "can\'t find session: nope" >&2; exit 1']);
587
- } catch (error) {
588
- caught = error;
589
- }
590
- expect(caught).toBeInstanceOf(TmuxError);
591
- expect(caught.code).toBe("SESSION_NOT_FOUND");
592
- expect(caught.cause).toBeInstanceOf(Error);
593
- });
594
-
595
- it("classifies an unreachable server as TMUX_UNAVAILABLE", async () => {
596
- let caught;
597
- try {
598
- await runTmuxBinaryAsync("/bin/sh", ["-c", "echo 'no server running on /x' >&2; exit 1"]);
599
- } catch (error) {
600
- caught = error;
601
- }
602
- expect(caught).toBeInstanceOf(TmuxError);
603
- expect(caught.code).toBe("TMUX_UNAVAILABLE");
604
- });
605
-
606
- it("sanitizes the client environment it hands the child", async () => {
607
- const output = await runTmuxBinaryAsync(
608
- "/bin/sh",
609
- ["-c", 'printf "%s|%s" "$COLORTERM" "$NO_COLOR"'],
610
- { env: { PATH: "/usr/bin:/bin", NO_COLOR: "1" } },
611
- );
612
- expect(output).toBe("truecolor|");
613
- });
614
- });