tmux-ide 2.9.0-beta.21 → 2.9.0-beta.22
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.
- package/bin/cli.js +52253 -49070
- package/package.json +1 -1
- package/packages/contracts/src/__tests__/daemon-wire.test.ts +2 -2
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +35 -0
- package/packages/contracts/src/__tests__/issue-error.test.ts +1 -1
- package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +3 -1
- package/packages/contracts/src/__tests__/pane-stream.test.ts +17 -5
- package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +166 -0
- package/packages/contracts/src/__tests__/window-links.test.ts +243 -0
- package/packages/contracts/src/actions-contract.ts +12 -0
- package/packages/contracts/src/daemon-resource-request.test.ts +1 -1
- package/packages/contracts/src/daemon-wire.ts +10 -5
- package/packages/contracts/src/fleet-client-state.ts +3 -0
- package/packages/contracts/src/index.ts +2 -0
- package/packages/contracts/src/interaction-receipts.ts +39 -0
- package/packages/contracts/src/issue-error.ts +1 -1
- package/packages/contracts/src/multiplexer-verbs.ts +25 -1
- package/packages/contracts/src/pane-stream.ts +51 -8
- package/packages/contracts/src/tmux-server-scope.ts +172 -0
- package/packages/contracts/src/window-links.ts +76 -0
- package/packages/contracts/src/workspace-multiplexer.ts +54 -0
- package/packages/core/src/interaction-receipts.ts +4 -0
- package/packages/daemon/dist/command-center/actions/command-definitions.js +6 -0
- package/packages/daemon/dist/command-center/actions/handlers/workspace-multiplexer.js +2 -0
- package/packages/daemon/dist/command-center/actions/registry.js +13 -1
- package/packages/daemon/dist/command-center/discovery.js +2 -9
- package/packages/daemon/dist/command-center/server.js +21 -1
- package/packages/daemon/dist/command-center/tmux-server-native-backing.js +37 -0
- package/packages/daemon/dist/command-center/tmux-servers.js +345 -0
- package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +64 -8
- package/packages/daemon/dist/lib/canonical-daemon.js +5 -2
- package/packages/daemon/dist/lib/daemon-embed.js +198 -30
- package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +147 -0
- package/packages/daemon/dist/lib/fleet-lifecycle-authority.js +16 -10
- package/packages/daemon/dist/lib/headless-daemon.js +10 -1
- package/packages/daemon/dist/lib/runtime-namespace.js +17 -3
- package/packages/daemon/dist/lib/tmux-client-execution.js +59 -0
- package/packages/daemon/dist/lib/tmux-named-socket-fence.js +4 -0
- package/packages/daemon/dist/lib/tmux-server-generation-runner.js +62 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +285 -0
- package/packages/daemon/dist/lib/tmux-server-owners.js +192 -0
- package/packages/daemon/dist/lib/tmux-server-proof.js +82 -0
- package/packages/daemon/dist/lib/tmux-server-registration.js +85 -0
- package/packages/daemon/dist/lib/tmux-server-session-create.js +43 -0
- package/packages/daemon/dist/lib/tmux-server-session-open.js +101 -0
- package/packages/daemon/dist/lib/tmux-servers-cli.js +134 -0
- package/packages/daemon/dist/lib/tmux-session-mutation-fence.js +55 -0
- package/packages/daemon/dist/lib/tmux-window-link-guard.js +88 -0
- package/packages/daemon/dist/lib/workspace-config-loader.js +3 -0
- package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +82 -1
- package/packages/daemon/dist/lib/workspace-pane-creation.js +13 -57
- package/packages/daemon/dist/lib/workspace-promotion.js +91 -12
- package/packages/daemon/dist/lib/workspace-registry.js +4 -0
- package/packages/daemon/dist/server/pane-stream-upgrade.js +20 -6
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +61 -17
- package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +9 -1
- package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +11 -2
- package/packages/daemon/dist/terminal/mirror/control-channel.js +65 -6
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +44 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +210 -50
- package/packages/daemon/dist/terminal/mirror/window-link-authority.js +160 -0
- package/packages/daemon/dist/terminal/pane-stream/lease-manager.js +3 -3
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +40 -6
- package/packages/daemon/dist/terminal/protocol/live-session-identity.js +9 -0
- package/packages/daemon/dist/terminal/protocol/native-backing-client.js +2 -1
- package/packages/daemon/dist/terminal/session-runtime/interaction-receipt-facts.js +9 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +6 -0
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +1 -1
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-resource-changes.js +1 -0
- package/packages/daemon/dist/tui/mirror/application-shell-daemon-connection.js +7 -2
- package/packages/daemon/dist/tui/mirror/application-shell-server-connection.js +114 -0
- package/packages/daemon/dist/tui/mirror/menu-model.js +1 -1
- package/packages/daemon/dist/tui/mirror/open-tui-verified-routing.js +34 -4
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +29 -8
- package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog-owner.js +11 -5
- package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog.js +140 -75
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-catalog.js +12 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +190 -31
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +91 -37
- package/packages/daemon/dist/tui/mirror/runtime/application-root-configuration.js +9 -3
- package/packages/daemon/dist/tui/mirror/runtime/application-route-connection.js +95 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +158 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace-policy.js +43 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +78 -25
- package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +42 -1
- package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +2 -1
- package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +6 -3
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +11 -2
- package/packages/daemon/dist/tui/mirror/workspace/terminal-window-strip.jsx +2 -1
- package/packages/daemon/src/command-center/actions/command-definitions.ts +6 -0
- package/packages/daemon/src/command-center/actions/errors.ts +4 -0
- package/packages/daemon/src/command-center/actions/handlers/workspace-multiplexer.ts +2 -0
- package/packages/daemon/src/command-center/actions/registry.ts +14 -0
- package/packages/daemon/src/command-center/discovery.ts +2 -10
- package/packages/daemon/src/command-center/server.ts +29 -1
- package/packages/daemon/src/command-center/tmux-server-native-backing.ts +49 -0
- package/packages/daemon/src/command-center/tmux-servers.ts +468 -0
- package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +111 -4
- package/packages/daemon/src/lib/canonical-daemon.ts +15 -3
- package/packages/daemon/src/lib/daemon-embed.ts +241 -28
- package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +190 -0
- package/packages/daemon/src/lib/fleet-lifecycle-authority.ts +16 -8
- package/packages/daemon/src/lib/headless-daemon.ts +18 -1
- package/packages/daemon/src/lib/runtime-namespace.ts +24 -3
- package/packages/daemon/src/lib/tmux-client-execution.ts +56 -0
- package/packages/daemon/src/lib/tmux-named-socket-fence.ts +5 -0
- package/packages/daemon/src/lib/tmux-server-generation-runner.ts +81 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +336 -0
- package/packages/daemon/src/lib/tmux-server-owners.ts +228 -0
- package/packages/daemon/src/lib/tmux-server-proof.ts +95 -0
- package/packages/daemon/src/lib/tmux-server-registration.ts +105 -0
- package/packages/daemon/src/lib/tmux-server-session-create.ts +52 -0
- package/packages/daemon/src/lib/tmux-server-session-open.ts +106 -0
- package/packages/daemon/src/lib/tmux-servers-cli.ts +186 -0
- package/packages/daemon/src/lib/tmux-session-mutation-fence.ts +62 -0
- package/packages/daemon/src/lib/tmux-window-link-guard.ts +112 -0
- package/packages/daemon/src/lib/workspace-config-loader.ts +3 -0
- package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +121 -1
- package/packages/daemon/src/lib/workspace-pane-creation.ts +16 -55
- package/packages/daemon/src/lib/workspace-promotion.ts +98 -12
- package/packages/daemon/src/lib/workspace-registry.ts +5 -0
- package/packages/daemon/src/server/pane-stream-upgrade.ts +25 -5
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +70 -16
- package/packages/daemon/src/terminal/mirror/control-channel.ts +64 -7
- package/packages/daemon/src/terminal/mirror/events.ts +2 -0
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +56 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +245 -50
- package/packages/daemon/src/terminal/mirror/window-link-authority.ts +203 -0
- package/packages/daemon/src/terminal/pane-stream/lease-manager.ts +3 -3
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +50 -6
- package/packages/daemon/src/terminal/protocol/live-session-identity.ts +14 -0
- package/packages/daemon/src/terminal/protocol/native-backing-client.ts +4 -1
- package/packages/daemon/src/terminal/session-runtime/interaction-receipt-facts.ts +9 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +9 -0
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +2 -2
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-resource-changes.ts +1 -0
- package/packages/daemon/src/tui/mirror/application-shell-daemon-connection.ts +28 -2
- package/packages/daemon/src/tui/mirror/application-shell-server-connection.ts +154 -0
- package/packages/daemon/src/tui/mirror/menu-model.ts +1 -1
- package/packages/daemon/src/tui/mirror/open-tui-verified-routing.ts +41 -3
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +47 -6
- package/packages/daemon/src/tui/mirror/runtime/application-entry.ts +2 -1
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions.tsx +10 -4
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-tabs.ts +2 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-catalog-owner.ts +13 -3
- package/packages/daemon/src/tui/mirror/runtime/application-home-catalog.ts +172 -100
- package/packages/daemon/src/tui/mirror/runtime/application-machine-catalog.ts +19 -7
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +263 -36
- package/packages/daemon/src/tui/mirror/runtime/application-machine-overlays.tsx +1 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +124 -40
- package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +3 -0
- package/packages/daemon/src/tui/mirror/runtime/application-root-configuration.ts +12 -3
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +24 -24
- package/packages/daemon/src/tui/mirror/runtime/application-route-connection.ts +93 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +177 -2
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-policy.ts +62 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +87 -30
- package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +52 -0
- package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +1 -1
- package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +15 -4
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +13 -2
- package/packages/daemon/src/tui/mirror/workspace/terminal-window-strip.tsx +5 -1
- package/packages/daemon-client/package.json +3 -1
- package/packages/daemon-client/src/index.ts +2 -0
- package/packages/daemon-client/src/pane-stream-client.test.ts +108 -38
- package/packages/daemon-client/src/pane-stream-client.ts +26 -0
- package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +114 -0
- package/packages/daemon-client/src/scoped-tmux-server-transport.ts +385 -0
- package/packages/daemon-client/src/tmux-server-client.test.ts +279 -0
- package/packages/daemon-client/src/tmux-server-client.ts +287 -0
|
@@ -22,6 +22,7 @@ export class FleetLifecycleAuthority {
|
|
|
22
22
|
#startedAt;
|
|
23
23
|
#registry;
|
|
24
24
|
#runTmux;
|
|
25
|
+
#ensureChromeUpdater;
|
|
25
26
|
#readFleet;
|
|
26
27
|
#operations = new Map();
|
|
27
28
|
#tail = Promise.resolve();
|
|
@@ -30,6 +31,7 @@ export class FleetLifecycleAuthority {
|
|
|
30
31
|
this.#productVersion = options.productVersion;
|
|
31
32
|
this.#startedAt = options.startedAt;
|
|
32
33
|
this.#registry = options.registry;
|
|
34
|
+
this.#ensureChromeUpdater = options.ensureChromeUpdater ?? true;
|
|
33
35
|
this.#runTmux = options.runTmux;
|
|
34
36
|
this.#readFleet = options.readFleet ?? (() => readAdoptedFleet(this.#registry));
|
|
35
37
|
}
|
|
@@ -94,11 +96,13 @@ export class FleetLifecycleAuthority {
|
|
|
94
96
|
prepareTmuxTruecolorEnvironment(this.#runTmux, identity.sessionName);
|
|
95
97
|
this.#runTmux(["set-environment", "-t", identity.sessionName, "TMUX_IDE", "1"]);
|
|
96
98
|
this.#runTmux(adoptMarkArgv(identity.sessionName));
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
if (this.#ensureChromeUpdater) {
|
|
100
|
+
try {
|
|
101
|
+
this.#runTmux(updaterProbeArgv());
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
this.#runTmux(updaterSpawnArgv());
|
|
105
|
+
}
|
|
102
106
|
}
|
|
103
107
|
if (!existing)
|
|
104
108
|
this.#registry.add({
|
|
@@ -268,11 +272,13 @@ export class FleetLifecycleAuthority {
|
|
|
268
272
|
this.#runTmux(["set-option", "-p", "-t", paneId, "@agent_launch", input.command]);
|
|
269
273
|
this.#runTmux(["set-option", "-p", "-t", paneId, "@agent_hint", input.harness]);
|
|
270
274
|
this.#runTmux(["select-pane", "-t", paneId, "-T", input.displayTitle]);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
if (this.#ensureChromeUpdater) {
|
|
276
|
+
try {
|
|
277
|
+
this.#runTmux(updaterProbeArgv());
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
this.#runTmux(updaterSpawnArgv());
|
|
281
|
+
}
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
284
|
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.
|
|
242
|
-
? {
|
|
243
|
-
:
|
|
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
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { createNativeTmuxSessionCreator } from "./tmux-server-session-create.js";
|
|
2
|
+
import { createTmuxSessionMutationFence } from "./tmux-session-mutation-fence.js";
|
|
3
|
+
import { createNativeTmuxSessionOpener } from "./tmux-server-session-open.js";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { mkdirSync } from "node:fs";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { isVisibleFleetSession } from "../command-center/discovery.js";
|
|
8
|
+
import { WorkspaceTerminalInventoryRuntime } from "../terminal/attachments/native-runtime.js";
|
|
9
|
+
import { createTmuxAgentStatusProbe } from "../terminal/attachments/agent-status-probe.js";
|
|
10
|
+
import { createPaneStreamRuntime } from "../terminal/pane-stream/runtime.js";
|
|
11
|
+
import { liveSessionIdForNativeIdentity } from "../terminal/protocol/live-session-identity.js";
|
|
12
|
+
import { SessionRuntimeRegistry } from "../terminal/session-runtime/registry.js";
|
|
13
|
+
import { createSessionRuntimeMultiplexerBackend } from "../terminal/session-runtime/multiplexer-backend.js";
|
|
14
|
+
import { TmuxExternalInteractionObserver } from "./tmux-external-interaction-observer.js";
|
|
15
|
+
import { WorkspaceMultiplexerAuthority } from "./workspace-multiplexer-verbs.js";
|
|
16
|
+
import { WorkspacePaneCreationAuthority, } from "./workspace-pane-creation.js";
|
|
17
|
+
import { WorkspaceRegistry } from "./workspace-registry.js";
|
|
18
|
+
import { createServerGenerationFencedTmuxRunner, createServerGenerationFencedTmuxAsyncRunner, } from "./tmux-server-generation-runner.js";
|
|
19
|
+
/** Refresh discovered live membership without changing durable workspace intent or aliases. */
|
|
20
|
+
export function createNativeTmuxServerCatalog(workspaceRegistry, run, assertOpen = () => undefined) {
|
|
21
|
+
return async () => {
|
|
22
|
+
assertOpen();
|
|
23
|
+
const raw = await run([
|
|
24
|
+
"list-panes",
|
|
25
|
+
"-a",
|
|
26
|
+
"-F",
|
|
27
|
+
"#{pid}\t#{session_id}\t#{session_created}\t#{session_name}\t#{pane_id}\t#{session_path}",
|
|
28
|
+
], AbortSignal.timeout(2_000));
|
|
29
|
+
assertOpen();
|
|
30
|
+
const rows = new Map();
|
|
31
|
+
for (const line of raw.trim().split("\n")) {
|
|
32
|
+
const [pid, id, created, name, pane, dir] = line.split("\t");
|
|
33
|
+
if (!pid ||
|
|
34
|
+
!/^\d+$/.test(pid) ||
|
|
35
|
+
!id ||
|
|
36
|
+
!/^\$\d+$/.test(id) ||
|
|
37
|
+
!created ||
|
|
38
|
+
!/^\d+$/.test(created) ||
|
|
39
|
+
!name ||
|
|
40
|
+
!pane ||
|
|
41
|
+
!/^%\d+$/.test(pane) ||
|
|
42
|
+
!dir)
|
|
43
|
+
throw new Error("Invalid native server catalog");
|
|
44
|
+
if (!isVisibleFleetSession(name))
|
|
45
|
+
continue;
|
|
46
|
+
const liveSessionId = liveSessionIdForNativeIdentity(pid, id, created);
|
|
47
|
+
const row = rows.get(liveSessionId) ?? {
|
|
48
|
+
summary: { liveSessionId, sessionName: name, paneCount: 0 },
|
|
49
|
+
panes: new Set(),
|
|
50
|
+
dir,
|
|
51
|
+
};
|
|
52
|
+
row.panes.add(pane);
|
|
53
|
+
rows.set(liveSessionId, row);
|
|
54
|
+
}
|
|
55
|
+
const names = new Set([...rows.values()].map(({ summary }) => summary.sessionName));
|
|
56
|
+
for (const workspace of workspaceRegistry.list())
|
|
57
|
+
if (workspaceRegistry.isVolatile(workspace.name) && !names.has(workspace.sessionName))
|
|
58
|
+
workspaceRegistry.remove(workspace.name);
|
|
59
|
+
for (const { summary, dir } of rows.values())
|
|
60
|
+
if (!workspaceRegistry.has(summary.sessionName) &&
|
|
61
|
+
!workspaceRegistry.list().some((workspace) => workspace.sessionName === summary.sessionName))
|
|
62
|
+
workspaceRegistry.add({
|
|
63
|
+
name: summary.sessionName,
|
|
64
|
+
sessionName: summary.sessionName,
|
|
65
|
+
projectDir: dir,
|
|
66
|
+
persistence: "volatile",
|
|
67
|
+
});
|
|
68
|
+
return [...rows.values()].map(({ summary, panes }) => ({ ...summary, paneCount: panes.size }));
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** One proven server incarnation. Never elects an endpoint or changes ambient tmux authority. */
|
|
72
|
+
export async function createNativeTmuxServerOwner(options) {
|
|
73
|
+
const generation = z.uuid().parse(options.generation);
|
|
74
|
+
const authority = options.tmuxAuthority;
|
|
75
|
+
if (authority.socketSelector.kind !== "path") {
|
|
76
|
+
throw new Error("A server owner requires a proven direct socket authority");
|
|
77
|
+
}
|
|
78
|
+
const socketPath = authority.socketSelector.path;
|
|
79
|
+
mkdirSync(options.stateDirectory, { recursive: true, mode: 0o700 });
|
|
80
|
+
const workspaceRegistry = new WorkspaceRegistry({
|
|
81
|
+
dir: options.stateDirectory,
|
|
82
|
+
listSessions: () => [],
|
|
83
|
+
});
|
|
84
|
+
const sessionMutationFence = createTmuxSessionMutationFence();
|
|
85
|
+
const generationRun = sessionMutationFence.wrap(createServerGenerationFencedTmuxRunner(authority, options.nativeServerIdentity));
|
|
86
|
+
const identityParts = generationRun(["display-message", "-p", "#{pid}\t#{start_time}"]).split("\t");
|
|
87
|
+
const nativeServerIdentity = options.nativeServerIdentity ?? {
|
|
88
|
+
pid: identityParts[0],
|
|
89
|
+
startTime: identityParts[1],
|
|
90
|
+
};
|
|
91
|
+
const generationRunAsync = createServerGenerationFencedTmuxAsyncRunner(authority, nativeServerIdentity);
|
|
92
|
+
const multiplexer = new WorkspaceMultiplexerAuthority({
|
|
93
|
+
daemonInstanceId: generation,
|
|
94
|
+
registry: workspaceRegistry,
|
|
95
|
+
tmuxAuthority: authority,
|
|
96
|
+
io: { runTmux: generationRun },
|
|
97
|
+
});
|
|
98
|
+
const paneCreation = new WorkspacePaneCreationAuthority({
|
|
99
|
+
daemonInstanceId: generation,
|
|
100
|
+
registry: workspaceRegistry,
|
|
101
|
+
tmuxAuthority: authority,
|
|
102
|
+
io: { runTmux: generationRun },
|
|
103
|
+
});
|
|
104
|
+
let disposed = false;
|
|
105
|
+
let disposePromise = null;
|
|
106
|
+
let observerStarted = null;
|
|
107
|
+
let receiptSequence = 0;
|
|
108
|
+
const assertOpen = () => {
|
|
109
|
+
if (disposed)
|
|
110
|
+
throw new Error("Tmux server owner is retired");
|
|
111
|
+
};
|
|
112
|
+
const sessionRuntimeRegistry = new SessionRuntimeRegistry({
|
|
113
|
+
generation,
|
|
114
|
+
semanticMutations: {
|
|
115
|
+
resolveSession: (name) => workspaceRegistry.get(name)?.sessionName ?? null,
|
|
116
|
+
execute: (operationId, intent, timing) => {
|
|
117
|
+
assertOpen();
|
|
118
|
+
if (intent.verb === "workspace.pane.read")
|
|
119
|
+
return multiplexer.readPane(operationId, intent);
|
|
120
|
+
if (intent.verb === "workspace.window.link.select" ||
|
|
121
|
+
intent.verb === "workspace.window.link.unlink" ||
|
|
122
|
+
intent.verb === "workspace.pane.select") {
|
|
123
|
+
return multiplexer.mutateWindowLink({ operationId, expectedDaemonInstanceId: generation, intent }, (session, action) => sessionRuntimeRegistry.executeWindowLinkAction(session, action));
|
|
124
|
+
}
|
|
125
|
+
return multiplexer.mutate({ operationId, expectedDaemonInstanceId: generation, intent }, timing);
|
|
126
|
+
},
|
|
127
|
+
publishReceipt: (receipt) => ({
|
|
128
|
+
...receipt,
|
|
129
|
+
type: "interaction.receipt",
|
|
130
|
+
sequence: ++receiptSequence,
|
|
131
|
+
}),
|
|
132
|
+
traceAuthority: { generation, incarnation: null },
|
|
133
|
+
},
|
|
134
|
+
mirror: {
|
|
135
|
+
executable: authority.executablePath,
|
|
136
|
+
socketPath,
|
|
137
|
+
resolveSocketPath: () => generationRun(["display-message", "-p", "#{socket_path}"]),
|
|
138
|
+
nativeServerIdentity,
|
|
139
|
+
internalReadHookEmission: (pane, marker) => observer.internalReadHookEmission(pane, marker),
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const terminalInventoryRuntime = new WorkspaceTerminalInventoryRuntime({
|
|
143
|
+
registry: workspaceRegistry,
|
|
144
|
+
sessionRuntimeRegistry,
|
|
145
|
+
tmuxAuthority: { ...authority, trustedCwd: options.stateDirectory, nativeServerIdentity },
|
|
146
|
+
agentStatusProbeFactory: ({ run }) => createTmuxAgentStatusProbe({ run }),
|
|
147
|
+
onInventory: (snapshot) => multiplexer.adoptPaneInventory(snapshot.panes),
|
|
148
|
+
onSessionInventory: (session, snapshot) => multiplexer.adoptSessionPaneInventory(session, snapshot?.panes ?? []),
|
|
149
|
+
});
|
|
150
|
+
const observer = new TmuxExternalInteractionObserver({
|
|
151
|
+
daemonInstanceId: generation,
|
|
152
|
+
internalReadOwnerToken: randomUUID(),
|
|
153
|
+
registry: workspaceRegistry,
|
|
154
|
+
tmuxAuthority: authority,
|
|
155
|
+
io: { runTmux: generationRunAsync },
|
|
156
|
+
onGap: () => terminalInventoryRuntime.invalidate(),
|
|
157
|
+
onObserved: ({ workspaceName, semanticPaneId, operationKind, operationId }) => operationId !== null &&
|
|
158
|
+
sessionRuntimeRegistry.observeTmuxInteraction({
|
|
159
|
+
workspaceName,
|
|
160
|
+
semanticPaneId,
|
|
161
|
+
operationKind,
|
|
162
|
+
operationId,
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
const paneStreamRuntime = createPaneStreamRuntime({
|
|
166
|
+
daemonInstanceId: generation,
|
|
167
|
+
webSocketUrl: options.webSocketUrl,
|
|
168
|
+
sessionRuntimeRegistry,
|
|
169
|
+
semanticPaneCatalog: terminalInventoryRuntime.semanticPaneCatalog,
|
|
170
|
+
});
|
|
171
|
+
const backend = createSessionRuntimeMultiplexerBackend({
|
|
172
|
+
registry: sessionRuntimeRegistry,
|
|
173
|
+
resolveSession: (name) => workspaceRegistry.get(name)?.sessionName ?? null,
|
|
174
|
+
});
|
|
175
|
+
const catalog = createNativeTmuxServerCatalog(workspaceRegistry, generationRunAsync, assertOpen);
|
|
176
|
+
const sessionCreator = createNativeTmuxSessionCreator({
|
|
177
|
+
generation,
|
|
178
|
+
registry: workspaceRegistry,
|
|
179
|
+
run: generationRun,
|
|
180
|
+
assertOpen,
|
|
181
|
+
});
|
|
182
|
+
const sessionOpener = createNativeTmuxSessionOpener({
|
|
183
|
+
generation,
|
|
184
|
+
registry: workspaceRegistry,
|
|
185
|
+
run: generationRunAsync,
|
|
186
|
+
assertOpen,
|
|
187
|
+
});
|
|
188
|
+
const openSession = async (liveSessionId) => {
|
|
189
|
+
await catalog();
|
|
190
|
+
const result = await sessionOpener.openSession(liveSessionId);
|
|
191
|
+
terminalInventoryRuntime.invalidate();
|
|
192
|
+
return result;
|
|
193
|
+
};
|
|
194
|
+
const multiplexerBackend = {
|
|
195
|
+
mutate: async (...args) => {
|
|
196
|
+
assertOpen();
|
|
197
|
+
if (args[0].expectedDaemonInstanceId !== generation)
|
|
198
|
+
throw new Error("Tmux server generation mismatch");
|
|
199
|
+
await catalog();
|
|
200
|
+
observerStarted ??= observer.start();
|
|
201
|
+
await observerStarted;
|
|
202
|
+
assertOpen();
|
|
203
|
+
return backend.mutate(...args);
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
const dispose = () => {
|
|
207
|
+
if (disposePromise)
|
|
208
|
+
return disposePromise;
|
|
209
|
+
disposed = true;
|
|
210
|
+
disposePromise = (async () => {
|
|
211
|
+
await sessionOpener.dispose();
|
|
212
|
+
await sessionCreator.dispose();
|
|
213
|
+
await paneCreation.dispose();
|
|
214
|
+
try {
|
|
215
|
+
await paneStreamRuntime.dispose();
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
try {
|
|
219
|
+
await observer.dispose();
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
terminalInventoryRuntime.dispose();
|
|
223
|
+
try {
|
|
224
|
+
await multiplexer.dispose();
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
await sessionRuntimeRegistry.dispose();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
})();
|
|
232
|
+
return disposePromise;
|
|
233
|
+
};
|
|
234
|
+
try {
|
|
235
|
+
await terminalInventoryRuntime.whenReady();
|
|
236
|
+
await catalog();
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
await dispose();
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
serverId: options.serverId,
|
|
244
|
+
generation,
|
|
245
|
+
catalog,
|
|
246
|
+
openSession,
|
|
247
|
+
createSession: sessionCreator.createSession,
|
|
248
|
+
createSessionPane: async (liveSessionId, request) => {
|
|
249
|
+
assertOpen();
|
|
250
|
+
await catalog();
|
|
251
|
+
const sessionName = workspaceRegistry.get(request.intent.workspaceName)?.sessionName;
|
|
252
|
+
if (!sessionName)
|
|
253
|
+
throw new Error("Selected workspace is unavailable");
|
|
254
|
+
const result = await sessionMutationFence.execute({
|
|
255
|
+
liveSessionId,
|
|
256
|
+
sessionName,
|
|
257
|
+
run: generationRunAsync,
|
|
258
|
+
mutate: () => paneCreation.create(request),
|
|
259
|
+
});
|
|
260
|
+
terminalInventoryRuntime.invalidate();
|
|
261
|
+
return result;
|
|
262
|
+
},
|
|
263
|
+
mutateSession: async (liveSessionId, request, authenticatedHostClientId) => {
|
|
264
|
+
assertOpen();
|
|
265
|
+
await catalog();
|
|
266
|
+
const sessionName = workspaceRegistry.get(request.intent.workspaceName)?.sessionName;
|
|
267
|
+
if (!sessionName)
|
|
268
|
+
throw new Error("Selected workspace is unavailable");
|
|
269
|
+
observerStarted ??= observer.start();
|
|
270
|
+
await observerStarted;
|
|
271
|
+
return sessionMutationFence.execute({
|
|
272
|
+
liveSessionId,
|
|
273
|
+
sessionName,
|
|
274
|
+
run: generationRunAsync,
|
|
275
|
+
mutate: () => multiplexerBackend.mutate(request, authenticatedHostClientId, undefined, true),
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
workspaceRegistry,
|
|
279
|
+
multiplexerBackend,
|
|
280
|
+
sessionRuntimeRegistry,
|
|
281
|
+
terminalInventoryRuntime,
|
|
282
|
+
paneStreamRuntime,
|
|
283
|
+
dispose,
|
|
284
|
+
};
|
|
285
|
+
}
|