tmux-ide 2.9.0-beta.21 → 2.9.0-beta.26

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 (209) hide show
  1. package/bin/cli.js +46791 -43589
  2. package/package.json +2 -2
  3. package/packages/contracts/src/__tests__/daemon-wire.test.ts +2 -2
  4. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +35 -0
  5. package/packages/contracts/src/__tests__/issue-error.test.ts +1 -1
  6. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +3 -1
  7. package/packages/contracts/src/__tests__/pane-stream.test.ts +17 -5
  8. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +166 -0
  9. package/packages/contracts/src/__tests__/window-links.test.ts +243 -0
  10. package/packages/contracts/src/actions-contract.ts +12 -0
  11. package/packages/contracts/src/daemon-resource-request.test.ts +1 -1
  12. package/packages/contracts/src/daemon-wire.ts +10 -5
  13. package/packages/contracts/src/fleet-client-state.ts +3 -0
  14. package/packages/contracts/src/fleet-lifecycle.ts +5 -0
  15. package/packages/contracts/src/index.ts +2 -0
  16. package/packages/contracts/src/interaction-receipts.ts +39 -0
  17. package/packages/contracts/src/issue-error.ts +1 -1
  18. package/packages/contracts/src/multiplexer-verbs.ts +25 -1
  19. package/packages/contracts/src/pane-stream.ts +51 -8
  20. package/packages/contracts/src/tmux-server-scope.ts +172 -0
  21. package/packages/contracts/src/window-links.ts +76 -0
  22. package/packages/contracts/src/workspace-multiplexer.ts +54 -0
  23. package/packages/core/src/interaction-receipts.ts +4 -0
  24. package/packages/daemon/dist/command-center/actions/command-definitions.js +6 -0
  25. package/packages/daemon/dist/command-center/actions/handlers/workspace-multiplexer.js +2 -0
  26. package/packages/daemon/dist/command-center/actions/registry.js +13 -1
  27. package/packages/daemon/dist/command-center/discovery.js +2 -9
  28. package/packages/daemon/dist/command-center/server.js +21 -1
  29. package/packages/daemon/dist/command-center/tmux-server-native-backing.js +37 -0
  30. package/packages/daemon/dist/command-center/tmux-servers.js +351 -0
  31. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +64 -8
  32. package/packages/daemon/dist/lib/canonical-daemon.js +5 -2
  33. package/packages/daemon/dist/lib/daemon-embed.js +198 -30
  34. package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +147 -0
  35. package/packages/daemon/dist/lib/fleet-lifecycle-authority.js +31 -11
  36. package/packages/daemon/dist/lib/headless-daemon.js +10 -1
  37. package/packages/daemon/dist/lib/runtime-namespace.js +17 -3
  38. package/packages/daemon/dist/lib/tmux-client-execution.js +59 -0
  39. package/packages/daemon/dist/lib/tmux-named-socket-fence.js +4 -0
  40. package/packages/daemon/dist/lib/tmux-server-generation-runner.js +62 -0
  41. package/packages/daemon/dist/lib/tmux-server-owner.js +285 -0
  42. package/packages/daemon/dist/lib/tmux-server-owners.js +192 -0
  43. package/packages/daemon/dist/lib/tmux-server-proof.js +82 -0
  44. package/packages/daemon/dist/lib/tmux-server-registration.js +85 -0
  45. package/packages/daemon/dist/lib/tmux-server-session-create.js +43 -0
  46. package/packages/daemon/dist/lib/tmux-server-session-open.js +101 -0
  47. package/packages/daemon/dist/lib/tmux-servers-cli.js +134 -0
  48. package/packages/daemon/dist/lib/tmux-session-mutation-fence.js +55 -0
  49. package/packages/daemon/dist/lib/tmux-window-link-guard.js +88 -0
  50. package/packages/daemon/dist/lib/workspace-config-loader.js +3 -0
  51. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +82 -1
  52. package/packages/daemon/dist/lib/workspace-pane-creation.js +13 -57
  53. package/packages/daemon/dist/lib/workspace-promotion.js +91 -12
  54. package/packages/daemon/dist/lib/workspace-registry.js +4 -0
  55. package/packages/daemon/dist/server/pane-stream-upgrade.js +20 -6
  56. package/packages/daemon/dist/terminal/attachments/native-runtime.js +61 -17
  57. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +9 -1
  58. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +11 -2
  59. package/packages/daemon/dist/terminal/mirror/control-channel.js +65 -6
  60. package/packages/daemon/dist/terminal/mirror/mirror-service.js +44 -0
  61. package/packages/daemon/dist/terminal/mirror/session-channel.js +210 -50
  62. package/packages/daemon/dist/terminal/mirror/window-link-authority.js +160 -0
  63. package/packages/daemon/dist/terminal/pane-stream/lease-manager.js +3 -3
  64. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +40 -6
  65. package/packages/daemon/dist/terminal/protocol/live-session-identity.js +9 -0
  66. package/packages/daemon/dist/terminal/protocol/native-backing-client.js +2 -1
  67. package/packages/daemon/dist/terminal/session-runtime/interaction-receipt-facts.js +9 -0
  68. package/packages/daemon/dist/terminal/session-runtime/registry.js +6 -0
  69. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +1 -1
  70. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-resource-changes.js +1 -0
  71. package/packages/daemon/dist/tui/mirror/application-shell-daemon-connection.js +7 -2
  72. package/packages/daemon/dist/tui/mirror/application-shell-server-connection.js +114 -0
  73. package/packages/daemon/dist/tui/mirror/menu-model.js +1 -1
  74. package/packages/daemon/dist/tui/mirror/open-tui-verified-routing.js +34 -4
  75. package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +29 -8
  76. package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-integration.jsx +119 -0
  77. package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-owner.js +132 -0
  78. package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +21 -9
  79. package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-transport.js +63 -1
  80. package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +8 -2
  81. package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.js +3 -1
  82. package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog-owner.js +11 -5
  83. package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog.js +140 -75
  84. package/packages/daemon/dist/tui/mirror/runtime/application-home-experience.js +40 -0
  85. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +132 -0
  86. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +9 -0
  87. package/packages/daemon/dist/tui/mirror/runtime/application-machine-catalog.js +12 -2
  88. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +211 -31
  89. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +91 -37
  90. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +20 -7
  91. package/packages/daemon/dist/tui/mirror/runtime/application-root-configuration.js +9 -3
  92. package/packages/daemon/dist/tui/mirror/runtime/application-route-connection.js +95 -2
  93. package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +6 -2
  94. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +158 -2
  95. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace-policy.js +43 -0
  96. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +78 -25
  97. package/packages/daemon/dist/tui/mirror/runtime/application-theme-repaint.js +9 -0
  98. package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +44 -1
  99. package/packages/daemon/dist/tui/mirror/runtime/guided-tour-coach.jsx +54 -0
  100. package/packages/daemon/dist/tui/mirror/runtime/guided-tour-storage.js +67 -0
  101. package/packages/daemon/dist/tui/mirror/runtime/guided-tour.js +113 -0
  102. package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +1 -1
  103. package/packages/daemon/dist/tui/mirror/runtime/resize-terminal-pane.js +4 -0
  104. package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +2 -1
  105. package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +12 -4
  106. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +6 -3
  107. package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +11 -2
  108. package/packages/daemon/dist/tui/mirror/workspace/terminal-window-strip.jsx +2 -1
  109. package/packages/daemon/src/command-center/actions/command-definitions.ts +6 -0
  110. package/packages/daemon/src/command-center/actions/errors.ts +4 -0
  111. package/packages/daemon/src/command-center/actions/handlers/workspace-multiplexer.ts +2 -0
  112. package/packages/daemon/src/command-center/actions/registry.ts +14 -0
  113. package/packages/daemon/src/command-center/discovery.ts +2 -10
  114. package/packages/daemon/src/command-center/server.ts +29 -1
  115. package/packages/daemon/src/command-center/tmux-server-native-backing.ts +49 -0
  116. package/packages/daemon/src/command-center/tmux-servers.ts +479 -0
  117. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +111 -4
  118. package/packages/daemon/src/lib/canonical-daemon.ts +15 -3
  119. package/packages/daemon/src/lib/daemon-embed.ts +241 -28
  120. package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +190 -0
  121. package/packages/daemon/src/lib/fleet-lifecycle-authority.ts +33 -9
  122. package/packages/daemon/src/lib/headless-daemon.ts +18 -1
  123. package/packages/daemon/src/lib/runtime-namespace.ts +24 -3
  124. package/packages/daemon/src/lib/tmux-client-execution.ts +56 -0
  125. package/packages/daemon/src/lib/tmux-named-socket-fence.ts +5 -0
  126. package/packages/daemon/src/lib/tmux-server-generation-runner.ts +81 -0
  127. package/packages/daemon/src/lib/tmux-server-owner.ts +336 -0
  128. package/packages/daemon/src/lib/tmux-server-owners.ts +228 -0
  129. package/packages/daemon/src/lib/tmux-server-proof.ts +95 -0
  130. package/packages/daemon/src/lib/tmux-server-registration.ts +105 -0
  131. package/packages/daemon/src/lib/tmux-server-session-create.ts +52 -0
  132. package/packages/daemon/src/lib/tmux-server-session-open.ts +106 -0
  133. package/packages/daemon/src/lib/tmux-servers-cli.ts +186 -0
  134. package/packages/daemon/src/lib/tmux-session-mutation-fence.ts +62 -0
  135. package/packages/daemon/src/lib/tmux-window-link-guard.ts +112 -0
  136. package/packages/daemon/src/lib/workspace-config-loader.ts +3 -0
  137. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +121 -1
  138. package/packages/daemon/src/lib/workspace-pane-creation.ts +16 -55
  139. package/packages/daemon/src/lib/workspace-promotion.ts +98 -12
  140. package/packages/daemon/src/lib/workspace-registry.ts +5 -0
  141. package/packages/daemon/src/server/pane-stream-upgrade.ts +25 -5
  142. package/packages/daemon/src/terminal/attachments/native-runtime.ts +70 -16
  143. package/packages/daemon/src/terminal/mirror/control-channel.ts +64 -7
  144. package/packages/daemon/src/terminal/mirror/events.ts +2 -0
  145. package/packages/daemon/src/terminal/mirror/mirror-service.ts +56 -0
  146. package/packages/daemon/src/terminal/mirror/session-channel.ts +245 -50
  147. package/packages/daemon/src/terminal/mirror/window-link-authority.ts +203 -0
  148. package/packages/daemon/src/terminal/pane-stream/lease-manager.ts +3 -3
  149. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +50 -6
  150. package/packages/daemon/src/terminal/protocol/live-session-identity.ts +14 -0
  151. package/packages/daemon/src/terminal/protocol/native-backing-client.ts +4 -1
  152. package/packages/daemon/src/terminal/session-runtime/interaction-receipt-facts.ts +9 -0
  153. package/packages/daemon/src/terminal/session-runtime/registry.ts +9 -0
  154. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +2 -2
  155. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-resource-changes.ts +1 -0
  156. package/packages/daemon/src/tui/mirror/application-shell-daemon-connection.ts +28 -2
  157. package/packages/daemon/src/tui/mirror/application-shell-server-connection.ts +154 -0
  158. package/packages/daemon/src/tui/mirror/menu-model.ts +1 -1
  159. package/packages/daemon/src/tui/mirror/open-tui-verified-routing.ts +41 -3
  160. package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +47 -6
  161. package/packages/daemon/src/tui/mirror/runtime/application-entry.ts +2 -1
  162. package/packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions.tsx +10 -4
  163. package/packages/daemon/src/tui/mirror/runtime/application-fleet-tabs.ts +2 -0
  164. package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-integration.tsx +199 -0
  165. package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-owner.ts +162 -0
  166. package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +36 -10
  167. package/packages/daemon/src/tui/mirror/runtime/application-home-agent-transport.ts +69 -0
  168. package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +12 -2
  169. package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +13 -2
  170. package/packages/daemon/src/tui/mirror/runtime/application-home-catalog-owner.ts +13 -3
  171. package/packages/daemon/src/tui/mirror/runtime/application-home-catalog.ts +172 -100
  172. package/packages/daemon/src/tui/mirror/runtime/application-home-experience.ts +59 -0
  173. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +175 -0
  174. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +9 -1
  175. package/packages/daemon/src/tui/mirror/runtime/application-machine-catalog.ts +19 -7
  176. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +285 -36
  177. package/packages/daemon/src/tui/mirror/runtime/application-machine-overlays.tsx +1 -1
  178. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +124 -40
  179. package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +3 -0
  180. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +18 -6
  181. package/packages/daemon/src/tui/mirror/runtime/application-root-configuration.ts +12 -3
  182. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +40 -40
  183. package/packages/daemon/src/tui/mirror/runtime/application-route-connection.ts +93 -1
  184. package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +4 -0
  185. package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +20 -1
  186. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +10 -0
  187. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +177 -2
  188. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-policy.ts +62 -0
  189. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +87 -30
  190. package/packages/daemon/src/tui/mirror/runtime/application-theme-repaint.ts +19 -0
  191. package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +55 -0
  192. package/packages/daemon/src/tui/mirror/runtime/guided-tour-coach.tsx +118 -0
  193. package/packages/daemon/src/tui/mirror/runtime/guided-tour-storage.ts +72 -0
  194. package/packages/daemon/src/tui/mirror/runtime/guided-tour.ts +147 -0
  195. package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +1 -1
  196. package/packages/daemon/src/tui/mirror/runtime/resize-terminal-pane.ts +3 -0
  197. package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +3 -0
  198. package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +11 -4
  199. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +15 -4
  200. package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +13 -2
  201. package/packages/daemon/src/tui/mirror/workspace/terminal-window-strip.tsx +5 -1
  202. package/packages/daemon-client/package.json +3 -1
  203. package/packages/daemon-client/src/index.ts +2 -0
  204. package/packages/daemon-client/src/pane-stream-client.test.ts +108 -38
  205. package/packages/daemon-client/src/pane-stream-client.ts +26 -0
  206. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +114 -0
  207. package/packages/daemon-client/src/scoped-tmux-server-transport.ts +385 -0
  208. package/packages/daemon-client/src/tmux-server-client.test.ts +279 -0
  209. package/packages/daemon-client/src/tmux-server-client.ts +287 -0
@@ -0,0 +1,147 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { tmuxServerPaneStreamPath } from "@tmux-ide/contracts";
4
+ import { TmuxServerOwners, MAX_TMUX_SERVER_OWNERS, } from "./tmux-server-owners.js";
5
+ import { createTmuxServerProbe, readTmuxServerRegistrations, writeTmuxServerRegistrations, } from "./tmux-server-registration.js";
6
+ import { createNativeTmuxServerOwner } from "./tmux-server-owner.js";
7
+ import { attachPaneStreamWebSocket, } from "../server/pane-stream-upgrade.js";
8
+ /** Adapts the established default owner without duplicating its control clients. */
9
+ export async function createEmbeddedTmuxServerOwners(options) {
10
+ const path = join(options.stateDirectory, "tmux-servers.json");
11
+ const persisted = readTmuxServerRegistrations(path);
12
+ const probe = createTmuxServerProbe(options.defaultAuthority.executablePath);
13
+ const defaultObservation = await probe(options.defaultAuthority.socketSelector);
14
+ let established = persisted.find((entry) => JSON.stringify(entry.selector) === JSON.stringify(options.defaultAuthority.socketSelector));
15
+ if (!established && defaultObservation) {
16
+ for (const entry of persisted) {
17
+ const observation = await probe(entry.selector);
18
+ if (observation?.fingerprint === defaultObservation.fingerprint) {
19
+ established = entry;
20
+ break;
21
+ }
22
+ }
23
+ }
24
+ const defaultRegistration = established ?? {
25
+ serverId: `tmux-server.${randomUUID().replaceAll("-", "")}`,
26
+ label: "Default",
27
+ selector: options.defaultAuthority.socketSelector,
28
+ };
29
+ let httpServer = null;
30
+ const boundaries = new Map();
31
+ const pending = new Map();
32
+ const isCurrent = (owner) => {
33
+ try {
34
+ return (owners.current({ serverId: owner.serverId, generation: owner.generation })
35
+ .paneStreamRuntime === owner.paneStreamRuntime);
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ };
41
+ const attachOwner = (route, owner) => {
42
+ pending.set(route, owner);
43
+ if (httpServer)
44
+ boundaries.set(route, attachPaneStreamWebSocket(httpServer, owner.paneStreamRuntime.coordinator, route, () => isCurrent(owner)));
45
+ };
46
+ let defaultRetired = false;
47
+ const retireDefault = async () => {
48
+ if (defaultRetired)
49
+ return;
50
+ defaultRetired = true;
51
+ await options.defaultOwner.dispose();
52
+ };
53
+ const owners = new TmuxServerOwners({
54
+ probe,
55
+ persist: (registrations) => writeTmuxServerRegistrations(path, registrations),
56
+ create: async (registration, scope, observation) => {
57
+ if (registration.serverId === defaultRegistration.serverId)
58
+ await retireDefault();
59
+ const route = tmuxServerPaneStreamPath(scope);
60
+ const owner = await createNativeTmuxServerOwner({
61
+ ...scope,
62
+ tmuxAuthority: observation.authority,
63
+ nativeServerIdentity: observation.nativeServerIdentity,
64
+ stateDirectory: join(options.stateDirectory, "server-owners", registration.serverId),
65
+ webSocketUrl: new URL(route, options.webSocketBaseUrl).href,
66
+ });
67
+ attachOwner(route, owner);
68
+ return {
69
+ ...owner,
70
+ dispose: async () => {
71
+ const boundary = boundaries.get(route);
72
+ boundaries.delete(route);
73
+ pending.delete(route);
74
+ try {
75
+ await boundary?.close();
76
+ }
77
+ finally {
78
+ await owner.dispose();
79
+ }
80
+ },
81
+ };
82
+ },
83
+ });
84
+ if (defaultObservation && defaultObservation.fingerprint === options.expectedDefaultProofDigest)
85
+ owners.adopt(defaultRegistration, { serverId: defaultRegistration.serverId, generation: options.defaultGeneration }, defaultObservation, {
86
+ ...options.defaultOwner,
87
+ serverId: defaultRegistration.serverId,
88
+ generation: options.defaultGeneration,
89
+ dispose: retireDefault,
90
+ });
91
+ try {
92
+ if (!defaultObservation ||
93
+ defaultObservation.fingerprint !== options.expectedDefaultProofDigest)
94
+ await owners.register(defaultRegistration);
95
+ for (const registration of persisted) {
96
+ if (registration.serverId !== defaultRegistration.serverId)
97
+ await owners.register(registration);
98
+ }
99
+ writeTmuxServerRegistrations(path, owners.registrations());
100
+ }
101
+ catch (error) {
102
+ await owners.dispose();
103
+ throw error;
104
+ }
105
+ const refuseUnknownScope = (request, socket) => {
106
+ const route = (request.url ?? "").split("?", 1)[0];
107
+ if (route.startsWith("/v2/tmux-servers/") && !pending.has(route))
108
+ socket.end("HTTP/1.1 410 Gone\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
109
+ };
110
+ return {
111
+ owners,
112
+ isDefaultCurrent() {
113
+ if (defaultRetired)
114
+ return false;
115
+ if (!defaultObservation ||
116
+ defaultObservation.fingerprint !== options.expectedDefaultProofDigest)
117
+ return true;
118
+ try {
119
+ owners.current({
120
+ serverId: defaultRegistration.serverId,
121
+ generation: options.defaultGeneration,
122
+ });
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ },
129
+ attach(server) {
130
+ if (httpServer)
131
+ throw new Error("Server owner transport already attached");
132
+ httpServer = server;
133
+ server.on("upgrade", refuseUnknownScope);
134
+ server.setMaxListeners(Math.max(server.getMaxListeners(), MAX_TMUX_SERVER_OWNERS + server.listenerCount("upgrade") + 4));
135
+ for (const [route, owner] of pending)
136
+ boundaries.set(route, attachPaneStreamWebSocket(server, owner.paneStreamRuntime.coordinator, route, () => isCurrent(owner)));
137
+ },
138
+ async dispose() {
139
+ httpServer?.off("upgrade", refuseUnknownScope);
140
+ await owners.dispose();
141
+ },
142
+ /** Legacy default routes remain bound to their original authority; never retarget. */
143
+ get defaultRetired() {
144
+ return defaultRetired;
145
+ },
146
+ };
147
+ }
@@ -1,3 +1,4 @@
1
+ import { liveSessionIdForNativeIdentity } from "../terminal/protocol/live-session-identity.js";
1
2
  import { createHash, randomUUID } from "node:crypto";
2
3
  import { isAbsolute, resolve } from "node:path";
3
4
  import { realpath, stat } from "node:fs/promises";
@@ -22,6 +23,7 @@ export class FleetLifecycleAuthority {
22
23
  #startedAt;
23
24
  #registry;
24
25
  #runTmux;
26
+ #ensureChromeUpdater;
25
27
  #readFleet;
26
28
  #operations = new Map();
27
29
  #tail = Promise.resolve();
@@ -30,6 +32,7 @@ export class FleetLifecycleAuthority {
30
32
  this.#productVersion = options.productVersion;
31
33
  this.#startedAt = options.startedAt;
32
34
  this.#registry = options.registry;
35
+ this.#ensureChromeUpdater = options.ensureChromeUpdater ?? true;
33
36
  this.#runTmux = options.runTmux;
34
37
  this.#readFleet = options.readFleet ?? (() => readAdoptedFleet(this.#registry));
35
38
  }
@@ -77,10 +80,14 @@ export class FleetLifecycleAuthority {
77
80
  }
78
81
  }
79
82
  let created = false;
83
+ let liveSessionId;
80
84
  try {
81
- this.#runTmux([
85
+ const createdId = this.#runTmux([
82
86
  "new-session",
83
87
  "-d",
88
+ "-P",
89
+ "-F",
90
+ "#{pid}\t#{session_id}\t#{session_created}",
84
91
  ...TMUX_TRUECOLOR_ENVIRONMENT_ARGS,
85
92
  "-s",
86
93
  identity.sessionName,
@@ -90,15 +97,25 @@ export class FleetLifecycleAuthority {
90
97
  cwd,
91
98
  TMUX_TRUECOLOR_INTERACTIVE_SHELL_COMMAND,
92
99
  ]);
100
+ const [serverPid, sessionId, sessionCreated] = createdId.trim().split("\t");
101
+ if (serverPid &&
102
+ /^\d+$/u.test(serverPid) &&
103
+ sessionId &&
104
+ /^\$\d+$/u.test(sessionId) &&
105
+ sessionCreated &&
106
+ /^\d+$/u.test(sessionCreated))
107
+ liveSessionId = liveSessionIdForNativeIdentity(serverPid, sessionId, sessionCreated);
93
108
  created = true;
94
109
  prepareTmuxTruecolorEnvironment(this.#runTmux, identity.sessionName);
95
110
  this.#runTmux(["set-environment", "-t", identity.sessionName, "TMUX_IDE", "1"]);
96
111
  this.#runTmux(adoptMarkArgv(identity.sessionName));
97
- try {
98
- this.#runTmux(updaterProbeArgv());
99
- }
100
- catch {
101
- this.#runTmux(updaterSpawnArgv());
112
+ if (this.#ensureChromeUpdater) {
113
+ try {
114
+ this.#runTmux(updaterProbeArgv());
115
+ }
116
+ catch {
117
+ this.#runTmux(updaterSpawnArgv());
118
+ }
102
119
  }
103
120
  if (!existing)
104
121
  this.#registry.add({
@@ -125,6 +142,7 @@ export class FleetLifecycleAuthority {
125
142
  operationId,
126
143
  daemonInstanceId: this.#daemonInstanceId,
127
144
  outcome: "created",
145
+ ...(input.includeLiveSessionId && liveSessionId ? { liveSessionId } : {}),
128
146
  fleetSessionId: fleetSessionIdForName(identity.sessionName),
129
147
  workspaceName: identity.workspaceName,
130
148
  displayName: input.displayName,
@@ -268,11 +286,13 @@ export class FleetLifecycleAuthority {
268
286
  this.#runTmux(["set-option", "-p", "-t", paneId, "@agent_launch", input.command]);
269
287
  this.#runTmux(["set-option", "-p", "-t", paneId, "@agent_hint", input.harness]);
270
288
  this.#runTmux(["select-pane", "-t", paneId, "-T", input.displayTitle]);
271
- try {
272
- this.#runTmux(updaterProbeArgv());
273
- }
274
- catch {
275
- this.#runTmux(updaterSpawnArgv());
289
+ if (this.#ensureChromeUpdater) {
290
+ try {
291
+ this.#runTmux(updaterProbeArgv());
292
+ }
293
+ catch {
294
+ this.#runTmux(updaterSpawnArgv());
295
+ }
276
296
  }
277
297
  }
278
298
  catch (error) {
@@ -1,3 +1,5 @@
1
+ import { assertCanonicalDaemonServerIntent } from "./canonical-daemon-bootstrap.js";
2
+ import { resolveTmuxServerIntent } from "./runtime-namespace.js";
1
3
  import { canonicalDaemonUrl, matchesCanonicalDaemonPredecessor, getCanonicalDaemonInfoPath, prepareCanonicalDaemonInfoForBootstrap, isCanonicalDaemonAlive, isCanonicalDaemonRecordOwnerProvenDead, probeCanonicalDaemonHealth, probeCanonicalDaemonIdentity, warnOnDaemonVersionSkew, } from "./canonical-daemon.js";
2
4
  import { DAEMON_WIRE_PROTOCOL_VERSION, isDaemonWireProtocolCompatible, } from "@tmux-ide/contracts";
3
5
  import { startEmbeddedDaemon, } from "./daemon-embed.js";
@@ -63,12 +65,15 @@ function assertIdentityMatches(info, identity) {
63
65
  }
64
66
  }
65
67
  async function assertAttachableDaemon(deps, info, options) {
66
- const identity = await deps.probeCanonicalDaemonIdentity(info);
68
+ const identity = await deps.probeCanonicalDaemonIdentity(info, undefined, Boolean(options.tmuxServerIntent));
67
69
  if (!identity) {
68
70
  throw new IdeError(`Canonical daemon PID ${info.pid} is alive but its identity endpoint is unavailable. ` +
69
71
  "Refusing takeover.", { code: "DAEMON_IDENTITY_UNAVAILABLE", exitCode: 1 });
70
72
  }
71
73
  assertIdentityMatches(info, identity);
74
+ await assertCanonicalDaemonServerIntent(info, options.tmuxServerIntent ?? null, {
75
+ identity: async () => identity,
76
+ });
72
77
  const health = await deps.probeCanonicalDaemonHealth(info);
73
78
  if (!health) {
74
79
  throw new IdeError(`Canonical daemon PID ${info.pid} is alive but its health endpoint is unavailable. ` +
@@ -180,6 +185,10 @@ async function waitForCanonicalWinner(deps, options, timeoutMs = 15_000) {
180
185
  * action converge on the same idempotent EmbeddedDaemonHandle.stop() path.
181
186
  */
182
187
  export async function runHeadlessDaemon(options = {}, deps = defaultDependencies) {
188
+ options = {
189
+ ...options,
190
+ tmuxServerIntent: options.tmuxServerIntent === undefined ? resolveTmuxServerIntent() : options.tmuxServerIntent,
191
+ };
183
192
  let restoreTmuxWorkspaces = false;
184
193
  const lifecycle = {};
185
194
  while (true) {
@@ -212,6 +212,7 @@ export function resolveRuntimeNamespace(options = {}) {
212
212
  controlSocketPath: join(runtimeDir, "control.sock"),
213
213
  eventLogPath: join(stateHome, "events.jsonl"),
214
214
  tmuxSocket,
215
+ tmuxSocketExplicit: Boolean(tmuxSocketName || tmuxSocketPath),
215
216
  cleanupToken,
216
217
  namespaceId: development?.id ?? (isolated ? cleanupToken : "canonical"),
217
218
  persistence: isolated && !development ? "ephemeral" : "durable",
@@ -238,9 +239,11 @@ export function runtimeNamespaceEnvironment(namespace) {
238
239
  [STATE_HOME_ENV]: namespace.stateHome,
239
240
  [REGISTRY_DIR_ENV]: namespace.registryDir,
240
241
  [DAEMON_INFO_DIR_ENV]: namespace.daemonInfoDir,
241
- ...(namespace.tmuxSocket.kind === "name"
242
- ? { [TMUX_SOCKET_NAME_ENV]: namespace.tmuxSocket.name }
243
- : { [TMUX_SOCKET_PATH_ENV]: namespace.tmuxSocket.path }),
242
+ ...(namespace.tmuxSocketExplicit === false
243
+ ? {}
244
+ : namespace.tmuxSocket.kind === "name"
245
+ ? { [TMUX_SOCKET_NAME_ENV]: namespace.tmuxSocket.name }
246
+ : { [TMUX_SOCKET_PATH_ENV]: namespace.tmuxSocket.path }),
244
247
  ...(namespace.cleanupToken ? { [CLEANUP_TOKEN_ENV]: namespace.cleanupToken } : {}),
245
248
  });
246
249
  }
@@ -294,3 +297,14 @@ export function assertQualifiedDevelopmentLaunch() {
294
297
  if (resolveRuntimeNamespace().development)
295
298
  throw new Error("Development launch requires exact build artifacts (D03); installed CLI fallback is disabled");
296
299
  }
300
+ /** Caller intent differs from the default used when starting a new owner. */
301
+ export function resolveTmuxServerIntent(options = {}) {
302
+ const env = options.env ?? process.env;
303
+ const namespace = resolveRuntimeNamespace(options);
304
+ if (namespace.isolated ||
305
+ nonEmpty(env, TMUX_SOCKET_NAME_ENV) ||
306
+ nonEmpty(env, TMUX_SOCKET_PATH_ENV))
307
+ return { source: "explicit", selector: namespace.tmuxSocket };
308
+ const current = /^(.*),[0-9]+,[0-9]+$/u.exec(env.TMUX ?? "")?.[1];
309
+ return current ? { source: "current", selector: { kind: "path", path: current } } : null;
310
+ }
@@ -0,0 +1,59 @@
1
+ import { accessSync, constants, realpathSync, statSync } from "node:fs";
2
+ import { delimiter, isAbsolute, join } from "node:path";
3
+ import { resolveRuntimeNamespace } from "./runtime-namespace.js";
4
+ import { resolveBundledTmux } from "./bundled-tmux.js";
5
+ export function resolveTmuxExecutable() {
6
+ if (resolveRuntimeNamespace().development)
7
+ return resolveBundledTmux();
8
+ const configured = process.env.TMUX_IDE_TMUX_BIN;
9
+ if (!configured) {
10
+ const bundled = resolveBundledTmux();
11
+ if (bundled)
12
+ return bundled;
13
+ }
14
+ const candidates = configured
15
+ ? [configured]
16
+ : (process.env.PATH ?? "")
17
+ .split(delimiter)
18
+ // Empty and relative PATH entries mean the daemon/project cwd. They
19
+ // are never executable authority for a privileged tmux mutation.
20
+ .filter((entry) => entry.length > 0 && isAbsolute(entry))
21
+ .map((entry) => join(entry, "tmux"));
22
+ for (const candidate of candidates) {
23
+ try {
24
+ if (!isAbsolute(candidate))
25
+ continue;
26
+ accessSync(candidate, constants.X_OK);
27
+ const canonical = realpathSync(candidate);
28
+ if (statSync(canonical).isFile())
29
+ return canonical;
30
+ }
31
+ catch {
32
+ // Continue to the next daemon-start candidate.
33
+ }
34
+ }
35
+ throw new Error("tmux_executable_unavailable");
36
+ }
37
+ const SAFE_TERMINAL_VALUE = /^(?:xterm|screen|tmux|rxvt|vt100|ansi)[A-Za-z0-9+._-]{0,58}$/u;
38
+ const SAFE_LOCALE_VALUE = /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,127}$/u;
39
+ /**
40
+ * The pinned tmux client receives only presentation metadata. Everything else
41
+ * is denied by omission: PATH/HOME/SHELL, TMUX/TMUX_PANE/TMUX_TMPDIR, shell
42
+ * startup hooks, dynamic-loader variables, Node options, and project secrets
43
+ * cannot redirect authority or inject code into this daemon-owned execution.
44
+ */
45
+ export function tmuxClientEnvironment(source) {
46
+ const environment = {
47
+ TERM: SAFE_TERMINAL_VALUE.test(source.TERM ?? "") ? source.TERM : "xterm-256color",
48
+ // A pinned runner may be the first tmux client and therefore create the
49
+ // server. Never let a headless parent (`TERM=dumb`, `NO_COLOR=1`) become
50
+ // the global environment inherited by every later interactive child.
51
+ COLORTERM: "truecolor",
52
+ };
53
+ for (const name of ["LANG", "LC_ALL", "LC_CTYPE"]) {
54
+ const value = source[name];
55
+ if (value && SAFE_LOCALE_VALUE.test(value))
56
+ environment[name] = value;
57
+ }
58
+ return environment;
59
+ }
@@ -63,3 +63,7 @@ export function createNamedSocketFence(authority, executable, environment) {
63
63
  isPinned: () => shared.identity !== null,
64
64
  };
65
65
  }
66
+ /** True only before this exact authority has observed any live socket. */
67
+ export function isNamedSocketAuthorityUnbound(authority) {
68
+ return authority.socketSelector.kind === "name" && !states.get(authority)?.identity;
69
+ }
@@ -0,0 +1,62 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createPinnedWorkspaceTmuxRunner, createPinnedWorkspaceTmuxAsyncRunner, } from "./workspace-pane-creation.js";
3
+ import { shellEscape } from "./shell.js";
4
+ /** Guard each command on the server which actually accepted this connection. */
5
+ export function fenceNativeTmuxCommand(args, expected) {
6
+ const { pid, startTime } = expected;
7
+ if (!/^[1-9][0-9]*$/u.test(pid) || !/^[1-9][0-9]*$/u.test(startTime))
8
+ throw new Error("Invalid native tmux server identity");
9
+ if (args.length === 0 || args.some((arg) => arg.includes("\0")))
10
+ throw new Error("Invalid tmux command");
11
+ const globalFlags = [];
12
+ let firstCommand = 0;
13
+ while (args[firstCommand]?.startsWith("-")) {
14
+ const flag = args[firstCommand];
15
+ if (flag !== "-N" && flag !== "-u")
16
+ throw new Error("Unsupported tmux global option in generation-fenced command");
17
+ globalFlags.push(flag);
18
+ firstCommand += 1;
19
+ }
20
+ if (firstCommand === args.length)
21
+ throw new Error("Missing tmux command");
22
+ const condition = `#{&&:#{==:#{pid},${pid}},#{==:#{start_time},${startTime}}}`;
23
+ // Preserve arbitrary literal payloads; a standalone ; is the native argv
24
+ // command separator already used by the existing runners.
25
+ const command = args
26
+ .slice(firstCommand)
27
+ .map((arg) => (arg === ";" ? ";" : shellEscape(arg)))
28
+ .join(" ");
29
+ const refusal = `tmux-server-stale.${randomUUID()}`;
30
+ return {
31
+ argv: [...globalFlags, "if-shell", "-F", condition, command, `display-message -p '${refusal}'`],
32
+ verify(output) {
33
+ if (output.trimEnd() === refusal)
34
+ throw new Error("Tmux server generation changed before command execution");
35
+ return output;
36
+ },
37
+ };
38
+ }
39
+ /** Socket validation alone leaves a validation-to-connect replacement race. */
40
+ export function createServerGenerationFencedTmuxRunner(authority, expected) {
41
+ const run = createPinnedWorkspaceTmuxRunner(authority, { timeoutMs: 5_000 });
42
+ const [pid, startTime] = expected
43
+ ? [expected.pid, expected.startTime]
44
+ : run(["display-message", "-p", "#{pid}\t#{start_time}"]).split("\t");
45
+ if (!pid || !startTime)
46
+ throw new Error("Invalid native tmux server identity");
47
+ const identity = { pid, startTime };
48
+ // Validate once before publishing an owner; each command rechecks natively.
49
+ fenceNativeTmuxCommand(["display-message", "-p", ""], identity);
50
+ return (args) => {
51
+ const command = fenceNativeTmuxCommand(args, identity);
52
+ return command.verify(run(command.argv));
53
+ };
54
+ }
55
+ export function createServerGenerationFencedTmuxAsyncRunner(authority, expected) {
56
+ const run = createPinnedWorkspaceTmuxAsyncRunner(authority);
57
+ fenceNativeTmuxCommand(["display-message", "-p", ""], expected);
58
+ return async (args, signal) => {
59
+ const command = fenceNativeTmuxCommand(args, expected);
60
+ return command.verify(await run(command.argv, signal));
61
+ };
62
+ }