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
@@ -2,20 +2,25 @@
2
2
  export class BoundedPerformanceRecordWriter {
3
3
  #sink;
4
4
  #criticalLimit;
5
- #pendingCritical = new Map();
5
+ #pendingCritical = new Set();
6
+ #pending = [];
7
+ #pendingByteLimit;
8
+ #pendingBytes = 0;
9
+ #flushPromise = null;
10
+ #resolveFlush = null;
6
11
  #saturated = false;
7
12
  #failed = false;
8
13
  #droppedRecords = 0;
9
- constructor(sink, criticalLimit = 16) {
14
+ constructor(sink, criticalLimit = 16, pendingByteLimit = 64 * 1024) {
10
15
  this.#sink = sink;
11
16
  this.#criticalLimit = criticalLimit;
17
+ this.#pendingByteLimit = pendingByteLimit;
12
18
  }
13
19
  write(record) {
14
20
  if (this.#failed)
15
21
  return false;
16
22
  if (this.#saturated) {
17
- this.#droppedRecords += 1;
18
- return false;
23
+ return this.#enqueue(record);
19
24
  }
20
25
  return this.#writeAccepted(record);
21
26
  }
@@ -30,22 +35,57 @@ export class BoundedPerformanceRecordWriter {
30
35
  this.#droppedRecords += 1;
31
36
  return false;
32
37
  }
33
- this.#pendingCritical.set(key, record);
34
- return true;
38
+ return this.#enqueue(record, key);
35
39
  }
36
40
  drain() {
37
41
  if (this.#failed)
38
42
  return;
39
43
  this.#saturated = false;
40
- for (const [key, record] of this.#pendingCritical) {
41
- this.#pendingCritical.delete(key);
42
- if (!this.#writeAccepted(record))
43
- break;
44
+ while (this.#pending.length > 0 && !this.#saturated && !this.#failed) {
45
+ const next = this.#pending.shift();
46
+ this.#pendingBytes -= next.bytes;
47
+ if (next.key !== undefined)
48
+ this.#pendingCritical.delete(next.key);
49
+ this.#writeAccepted(next.record);
44
50
  }
51
+ if (this.#pending.length === 0)
52
+ this.#finishFlush();
53
+ }
54
+ /** Wait until retained records reach the stream; its owner then flushes/end()s it. */
55
+ flush() {
56
+ if (this.#failed || this.#pending.length === 0)
57
+ return Promise.resolve();
58
+ this.#flushPromise ??= new Promise((resolve) => {
59
+ this.#resolveFlush = resolve;
60
+ });
61
+ return this.#flushPromise;
45
62
  }
46
63
  fail() {
47
64
  this.#failed = true;
48
65
  this.#pendingCritical.clear();
66
+ this.#pending.length = 0;
67
+ this.#pendingBytes = 0;
68
+ this.#finishFlush();
69
+ }
70
+ #enqueue(record, key) {
71
+ const bytes = Buffer.byteLength(record);
72
+ // Ordinary bursts get one extra stream-sized buffer. Critical records have
73
+ // a separate byte reserve as well as their existing key-count limit.
74
+ const limit = this.#pendingByteLimit + (key === undefined ? 0 : 64 * 1024);
75
+ if (this.#pending.length >= 1024 || this.#pendingBytes + bytes > limit) {
76
+ this.#droppedRecords += 1;
77
+ return false;
78
+ }
79
+ if (key !== undefined)
80
+ this.#pendingCritical.add(key);
81
+ this.#pending.push({ record, bytes, ...(key === undefined ? {} : { key }) });
82
+ this.#pendingBytes += bytes;
83
+ return true;
84
+ }
85
+ #finishFlush() {
86
+ this.#resolveFlush?.();
87
+ this.#resolveFlush = null;
88
+ this.#flushPromise = null;
49
89
  }
50
90
  diagnostics() {
51
91
  return Object.freeze({
@@ -271,12 +271,13 @@ export class TerminalFastLaneRendererAdapter {
271
271
  ]);
272
272
  if (seen.has(key))
273
273
  continue;
274
+ // Bound retained de-duplication history, not the renderer's lifetime.
275
+ // An evicted identity may be reported again only after another real paint.
276
+ // Pending identities still have their separate per-frame admission limit.
274
277
  if (seen.size >= 256)
275
- this.#droppedCanonicalHostFrames += 1;
276
- else {
277
- seen.add(key);
278
- identities.push(identity);
279
- }
278
+ seen.delete(seen.values().next().value);
279
+ seen.add(key);
280
+ identities.push(identity);
280
281
  }
281
282
  const dropped = this.#droppedCanonicalHostFrames;
282
283
  this.#droppedCanonicalHostFrames = 0;
@@ -13,6 +13,9 @@ export class TerminalPaneInputRouter {
13
13
  constructor(options) {
14
14
  this.#options = options;
15
15
  }
16
+ get selectionVersion() {
17
+ return this.#selectionToken;
18
+ }
16
19
  get focusedPane() {
17
20
  return this.#focusedPane;
18
21
  }
@@ -1,5 +1,7 @@
1
1
  import { createTerminalFastLane, createWorkspaceClientTerminalSource, } from "@tmux-ide/daemon-client/terminal-fast-lane";
2
2
  import { currentTuiPerformanceEventSink } from "../performance-events.js";
3
+ // Limit outstanding low-water sampling chains, not completed observations over
4
+ // the lifetime of a renderer. Each chain owns at most one pending timeout.
3
5
  const TERMINAL_RESOURCE_SAMPLE_LIMIT = 512;
4
6
  // The ProductRig idle proof reads after 10.1s. Emit just before that boundary
5
7
  // so the operation-scoped sample cannot race the proof read.
@@ -227,7 +229,7 @@ function createOpenTuiTerminalResourceSampler(lane, sink) {
227
229
  heartbeatTimer = setTimeout(heartbeat, TERMINAL_RESOURCE_HEARTBEAT_MS);
228
230
  heartbeatTimer.unref?.();
229
231
  const emit = (operation, identity, expectedAt, sampled) => {
230
- if (disposed || resourceEpochIdentity === null || ordinal >= TERMINAL_RESOURCE_SAMPLE_LIMIT)
232
+ if (disposed || resourceEpochIdentity === null)
231
233
  return;
232
234
  ordinal += 1;
233
235
  const now = readDiagnosticNow();
@@ -373,8 +375,12 @@ function createOpenTuiTerminalResourceSampler(lane, sink) {
373
375
  };
374
376
  return {
375
377
  afterFence(identity) {
376
- if (disposed || ordinal >= TERMINAL_RESOURCE_SAMPLE_LIMIT)
378
+ if (disposed)
379
+ return;
380
+ if (pending.size >= TERMINAL_RESOURCE_SAMPLE_LIMIT) {
381
+ recordSamplingFailure();
377
382
  return;
383
+ }
378
384
  latestIdentity = identity;
379
385
  let now;
380
386
  if (resourceEpochIdentity === null) {
@@ -50,3 +50,38 @@ function graphemes(text) {
50
50
  return [...new Segmenter(undefined, { granularity: "grapheme" }).segment(text)].map((entry) => entry.segment);
51
51
  return [...text];
52
52
  }
53
+ /** PURE — greedy word-wrap for terminal prose (never returns empty for
54
+ * non-empty text; words longer than the width hard-break). */
55
+ export function wrapText(text, width) {
56
+ if (width <= 0)
57
+ return [text];
58
+ const out = [];
59
+ for (const para of text.split("\n")) {
60
+ let line = "";
61
+ for (const word of para.split(/\s+/).filter(Boolean)) {
62
+ if (line.length === 0) {
63
+ let w = word;
64
+ while (w.length > width) {
65
+ out.push(w.slice(0, width));
66
+ w = w.slice(width);
67
+ }
68
+ line = w;
69
+ }
70
+ else if (line.length + 1 + word.length <= width) {
71
+ line += ` ${word}`;
72
+ }
73
+ else {
74
+ out.push(line);
75
+ let w = word;
76
+ while (w.length > width) {
77
+ out.push(w.slice(0, width));
78
+ w = w.slice(width);
79
+ }
80
+ line = w;
81
+ }
82
+ }
83
+ if (line.length > 0 || para.length === 0)
84
+ out.push(line);
85
+ }
86
+ return out.length > 0 ? out : [""];
87
+ }
@@ -156,9 +156,11 @@ export function createApplicationConnectionFeedback(onPublish = () => undefined)
156
156
  failure,
157
157
  ...(inventory
158
158
  ? {
159
- recovery: missing
160
- ? "A registered session may have been recreated. Choose that session to restore its identity, then retry here. It may be a different session."
161
- : "Terminal identities conflict or cannot be verified. Copy connection details for diagnosis; Retry does not repair identity conflicts.",
159
+ recovery: failure.reason === "duplicate-runtime-pane-binding"
160
+ ? "A terminal is shared by multiple workspace entries. Unlink shared tmux windows so each belongs to one session, then select Retry."
161
+ : missing
162
+ ? "A registered session may have been recreated. Choose that session to restore its identity, then retry here. It may be a different session."
163
+ : "Terminal identities conflict or cannot be verified. Copy connection details for diagnosis; Retry does not repair identity conflicts.",
162
164
  }
163
165
  : {}),
164
166
  };
@@ -546,16 +546,7 @@ export function createDefaultFleetFactsReaders(): Pick<
546
546
  return null;
547
547
  }
548
548
  };
549
- return {
550
- readSessions: async (signal) => {
551
- const raw = await execute(SESSION_COMPOSITION_TMUX_ARGS, signal);
552
- return raw === null ? null : parseSessionCompositionFacts(raw);
553
- },
554
- readAgents: async (signal) => {
555
- const raw = await execute(AGENT_STATE_TMUX_ARGS, signal);
556
- return raw === null ? null : parseAgentStateFacts(raw);
557
- },
558
- };
549
+ return createSharedFleetFactsReaders(execute);
559
550
  }
560
551
 
561
552
  export const SESSION_COMPOSITION_TMUX_ARGS = [
@@ -593,3 +584,102 @@ export const AGENT_STATE_TMUX_ARGS = [
593
584
  export async function readAgentStateFacts(signal?: AbortSignal): Promise<AgentStateReading | null> {
594
585
  return createDefaultFleetFactsReaders().readAgents(signal);
595
586
  }
587
+
588
+ /** Agent columns are excluded from the session topology projection. */
589
+ export const FLEET_FACTS_TMUX_ARGS = [
590
+ "list-panes",
591
+ "-a",
592
+ "-F",
593
+ `${SESSION_COMPOSITION_TMUX_ARGS[3]}\t#{@agent_state}\t#{pane_current_command}`,
594
+ ] as const;
595
+
596
+ /** Share only an outstanding read; completed snapshots are never cached. */
597
+ export function createSharedFleetFactsReaders(
598
+ run: (args: readonly string[], signal?: AbortSignal) => string | null | Promise<string | null>,
599
+ isAbsentServer: (error: unknown) => boolean = () => false,
600
+ ): Pick<DaemonFleetFactsObserverOptions, "readSessions" | "readAgents"> {
601
+ interface Pending {
602
+ controller: AbortController;
603
+ result: Promise<string | null>;
604
+ readers: number;
605
+ }
606
+ let pending: Pending | null = null;
607
+ const read = async (signal?: AbortSignal): Promise<string | null> => {
608
+ if (signal?.aborted) return null;
609
+ if (!pending) {
610
+ const controller = new AbortController();
611
+ const entry: Pending = {
612
+ controller,
613
+ readers: 0,
614
+ result: Promise.resolve().then(() => run(FLEET_FACTS_TMUX_ARGS, controller.signal)),
615
+ };
616
+ entry.result = entry.result.finally(() => {
617
+ if (pending === entry) pending = null;
618
+ });
619
+ pending = entry;
620
+ }
621
+ const entry = pending;
622
+ entry.readers += 1;
623
+ let released = false;
624
+ const release = () => {
625
+ if (released) return;
626
+ released = true;
627
+ entry.readers -= 1;
628
+ if (entry.readers === 0) {
629
+ if (pending === entry) pending = null;
630
+ entry.controller.abort();
631
+ }
632
+ };
633
+ signal?.addEventListener("abort", release, { once: true });
634
+ try {
635
+ // Keep the promise outstanding for a noncooperative runner so the
636
+ // observer's per-kind quarantine remains effective after cancellation.
637
+ const raw = await entry.result;
638
+ return signal?.aborted ? null : raw;
639
+ } catch (error) {
640
+ if (signal?.aborted) return null;
641
+ throw error;
642
+ } finally {
643
+ signal?.removeEventListener("abort", release);
644
+ release();
645
+ }
646
+ };
647
+ return {
648
+ readSessions: async (signal) => {
649
+ try {
650
+ const raw = await read(signal);
651
+ return raw === null
652
+ ? null
653
+ : parseSessionCompositionFacts(
654
+ raw
655
+ .split("\n")
656
+ .map((line) => line.split("\t").slice(0, 11).join("\t"))
657
+ .join("\n"),
658
+ );
659
+ } catch (error) {
660
+ // Socket absence is an authoritative empty catalog, but must not
661
+ // manufacture agent turn completions from a failed agent observation.
662
+ return isAbsentServer(error) ? parseSessionCompositionFacts("") : null;
663
+ }
664
+ },
665
+ readAgents: async (signal) => {
666
+ try {
667
+ const raw = await read(signal);
668
+ return raw === null
669
+ ? null
670
+ : parseAgentStateFacts(
671
+ raw
672
+ .split("\n")
673
+ .map((line) => {
674
+ const fields = line.split("\t");
675
+ if (fields.length !== 13) return "";
676
+ return [fields[0], fields[6], fields[9], fields[11], fields[12]].join("\t");
677
+ })
678
+ .join("\n"),
679
+ );
680
+ } catch {
681
+ return null;
682
+ }
683
+ },
684
+ };
685
+ }
@@ -2,13 +2,22 @@ import type { Hono } from "hono";
2
2
  import { bodyLimit } from "hono/body-limit";
3
3
  import { z } from "zod";
4
4
  import { SavedMachineRegistrySchema, type DaemonInstanceIdentity } from "@tmux-ide/contracts";
5
- import { loadSavedMachines, mergeSavedMachines } from "../../lib/saved-machines.ts";
5
+ import { SavedMachineMutationSchema } from "@tmux-ide/contracts/saved-machines";
6
+ import {
7
+ loadSavedMachines,
8
+ mergeSavedMachines,
9
+ updateSavedMachines,
10
+ } from "../../lib/saved-machines.ts";
6
11
  import { ownerAuthorityGate } from "../owner-authority.ts";
7
12
 
8
13
  const Request = z.strictObject({
9
14
  expectedInstanceId: z.uuid(),
10
15
  registry: SavedMachineRegistrySchema,
11
16
  });
17
+ const MutationRequest = z.strictObject({
18
+ expectedInstanceId: z.uuid(),
19
+ change: SavedMachineMutationSchema,
20
+ });
12
21
  export function mountSavedMachineRoute(
13
22
  app: Hono,
14
23
  options: {
@@ -16,6 +25,7 @@ export function mountSavedMachineRoute(
16
25
  ownerToken: string | null;
17
26
  read?: typeof loadSavedMachines;
18
27
  merge?: typeof mergeSavedMachines;
28
+ update?: typeof updateSavedMachines;
19
29
  },
20
30
  ) {
21
31
  const authorize = ownerAuthorityGate(options.ownerToken, {
@@ -56,4 +66,41 @@ export function mountSavedMachineRoute(
56
66
  );
57
67
  }
58
68
  });
69
+ app.patch("/api/resources/saved-machines", bodyLimit({ maxSize: 1024 }), async (c) => {
70
+ const gate = authorize(c);
71
+ if (gate) return gate;
72
+ c.header("Cache-Control", "no-store");
73
+ let parsed;
74
+ try {
75
+ parsed = MutationRequest.safeParse(await c.req.json());
76
+ } catch {
77
+ return c.json({ error: "Invalid machine mutation" }, 400);
78
+ }
79
+ if (!parsed.success) return c.json({ error: "Invalid machine mutation" }, 400);
80
+ if (parsed.data.expectedInstanceId !== options.daemon.instanceId)
81
+ return c.json({ error: "Daemon generation changed" }, 409);
82
+ const change = parsed.data.change;
83
+ const { id, operation } = change;
84
+ try {
85
+ const registry = (options.update ?? updateSavedMachines)(
86
+ operation === "remove"
87
+ ? { type: "remove", id }
88
+ : {
89
+ type: "update",
90
+ id,
91
+ patch:
92
+ change.operation === "edit" ? change.patch : { enabled: operation === "enable" },
93
+ },
94
+ );
95
+ return c.json({ daemon: options.daemon, registry });
96
+ } catch {
97
+ return c.json(
98
+ {
99
+ error:
100
+ "Machine not found, edit conflicts with another profile, or registry unavailable; nothing changed",
101
+ },
102
+ 409,
103
+ );
104
+ }
105
+ });
59
106
  }
@@ -17,11 +17,8 @@ import { discoverSessions, buildOverviews, buildProjectDetail } from "./discover
17
17
  import type { AgentTurnCompletion } from "./agent-status-watch.ts";
18
18
  import {
19
19
  DaemonFleetFactsObserver,
20
- AGENT_STATE_TMUX_ARGS,
21
- parseAgentStateFacts,
22
- parseSessionCompositionFacts,
23
20
  createDefaultFleetFactsReaders,
24
- SESSION_COMPOSITION_TMUX_ARGS,
21
+ createSharedFleetFactsReaders,
25
22
  type DaemonFleetFactsObserverOptions,
26
23
  type DaemonFleetFactsObserverDiagnostic,
27
24
  } from "./daemon-fleet-facts-observer.ts";
@@ -431,28 +428,11 @@ export function setFleetFactsTmuxRunner(
431
428
  runTmux: ((args: readonly string[], signal?: AbortSignal) => string | Promise<string>) | null,
432
429
  ): void {
433
430
  stopFleetFactsObserver();
434
- sessionCompositionReaderOverride = runTmux
435
- ? async (signal) => {
436
- try {
437
- return parseSessionCompositionFacts(await runTmux(SESSION_COMPOSITION_TMUX_ARGS, signal));
438
- } catch (error) {
439
- // A daemon may legitimately precede the first tmux server. Socket
440
- // absence is an authoritative empty fleet baseline, not a failed
441
- // observation: catalog clients must be allowed to render their
442
- // actionable first-run state while the observer waits for tmux.
443
- return isTmuxServerUnavailableError(error) ? parseSessionCompositionFacts("") : null;
444
- }
445
- }
446
- : null;
447
- agentStateReaderOverride = runTmux
448
- ? async (signal) => {
449
- try {
450
- return parseAgentStateFacts(await runTmux(AGENT_STATE_TMUX_ARGS, signal));
451
- } catch {
452
- return null;
453
- }
454
- }
431
+ const readers = runTmux
432
+ ? createSharedFleetFactsReaders(runTmux, isTmuxServerUnavailableError)
455
433
  : null;
434
+ sessionCompositionReaderOverride = readers?.readSessions ?? null;
435
+ agentStateReaderOverride = readers?.readAgents ?? null;
456
436
  }
457
437
 
458
438
  interface ResourceObservationHandle {
@@ -236,6 +236,18 @@ async function replaceOlderCanonicalDaemon(
236
236
  reason: "identity-mismatch",
237
237
  });
238
238
  }
239
+ const supervisor = latest.info.provenance?.supervisor;
240
+ if (!latest.info.supervisionId && (supervisor === "systemd" || supervisor === "launchd")) {
241
+ throw new DaemonBootstrapError(
242
+ "incompatible",
243
+ `The older canonical daemon is managed by ${supervisor} without a supervisor reservation. ` +
244
+ "Stop and disable the old service, reserve the stopped namespace with " +
245
+ "tmux-ide daemon reserve-supervisor <id>, then configure the service to run " +
246
+ "tmux-ide --headless --supervised <id> before restarting it. " +
247
+ "The existing daemon has been left running.",
248
+ { reason: "canonical-record-invalid" },
249
+ );
250
+ }
239
251
  await deps.shutdownOlderOwner(info);
240
252
  const deadline = deps.now() + timeoutMs;
241
253
  while (deps.now() < deadline) {
@@ -115,7 +115,12 @@ export type CanonicalDaemonInfoState =
115
115
  };
116
116
 
117
117
  export function getCanonicalDaemonInfoPath(): string {
118
- return runtimeOwnedPath(join(resolveRuntimeNamespace().daemonInfoDir, DAEMON_INFO_FILE));
118
+ const namespace = resolveRuntimeNamespace();
119
+ const path = join(namespace.daemonInfoDir, DAEMON_INFO_FILE);
120
+ // Resolution already validates ordinary and isolated namespaces. Only
121
+ // development adds a per-path containment check; do not resolve twice on
122
+ // every ordinary daemon read. Each call still resolves the current namespace.
123
+ return namespace.development ? runtimeOwnedPath(path) : path;
119
124
  }
120
125
 
121
126
  export function getCanonicalDaemonClaimPath(): string {
@@ -687,11 +692,48 @@ export function assertCanonicalDaemonSupervision(supervisionId: string): void {
687
692
  }
688
693
 
689
694
  /** Explicit installation only; startup never calls this to recreate a reservation. */
695
+ /** Sanitized reservation guidance; never includes credentials or filesystem errors. */
696
+ export class CanonicalDaemonReservationError extends Error {
697
+ constructor(message: string) {
698
+ super(`Supervisor reservation refused: ${message}`);
699
+ this.name = "CanonicalDaemonReservationError";
700
+ }
701
+ }
702
+
703
+ function reservationRefusal(
704
+ attempt: Exclude<CanonicalDaemonClaimAttempt, { status: "acquired" }>,
705
+ ): CanonicalDaemonReservationError {
706
+ if (attempt.status === "busy")
707
+ return new CanonicalDaemonReservationError(
708
+ `another lifecycle operation holds the namespace (PID ${attempt.owner.pid}). Wait for it to finish, then retry.`,
709
+ );
710
+ const state = inspectCanonicalDaemonInfo();
711
+ if (state.status === "valid")
712
+ return new CanonicalDaemonReservationError(
713
+ `the recorded ${state.info.supervisionId ? "supervised" : "unsupervised"} daemon (PID ${state.info.pid}) has not been released. Inspect it with tmux-ide daemon info --json, then stop it through its owning process or service before reserving.`,
714
+ );
715
+ if (state.status === "reserved")
716
+ return new CanonicalDaemonReservationError(
717
+ `this namespace is reserved for supervisor ${state.reservation.supervisionId}. Remove that service and release its reservation before assigning a different ID.`,
718
+ );
719
+ if (state.status === "invalid")
720
+ return new CanonicalDaemonReservationError(
721
+ `the namespace record cannot be trusted (${state.reason}). Run tmux-ide daemon info --json for diagnosis; repair ownership or permissions before retrying.`,
722
+ );
723
+ return new CanonicalDaemonReservationError(
724
+ "the namespace changed or its lifecycle claim is unavailable. Run tmux-ide daemon info --json, then retry after the owning operation finishes.",
725
+ );
726
+ }
727
+
690
728
  export function reserveCanonicalDaemonSupervision(
691
729
  supervisionId: string,
692
730
  ): CanonicalDaemonReservation {
731
+ if (!DaemonSupervisionIdSchema.safeParse(supervisionId).success)
732
+ throw new CanonicalDaemonReservationError(
733
+ "use a 1–128 character ID starting with a letter or digit and containing only letters, digits, periods, underscores or hyphens.",
734
+ );
693
735
  const attempt = tryAcquireCanonicalDaemonClaim({ kind: "reserve", supervisionId });
694
- if (attempt.status !== "acquired") throw new Error("Cannot reserve supervised daemon namespace");
736
+ if (attempt.status !== "acquired") throw reservationRefusal(attempt);
695
737
  const claim = attempt.claim;
696
738
  try {
697
739
  const before = inspectCanonicalDaemonInfo();
@@ -1436,6 +1436,7 @@ async function startEmbeddedDaemonGeneration(
1436
1436
  if (!sessionRuntimeRegistry) throw new Error("Session runtime unavailable");
1437
1437
  return sessionRuntimeRegistry.executeWindowLinkAction(session, action);
1438
1438
  },
1439
+ timing,
1439
1440
  );
1440
1441
  }
1441
1442
  if (intent.verb === "workspace.pane.resize") {