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
package/package.json
CHANGED
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
} from "../daemon-wire.ts";
|
|
14
14
|
import { WorkspaceCatalogResourceV1SchemaZ } from "../workspace-catalog-resource.ts";
|
|
15
15
|
|
|
16
|
-
const fixturePath = fileURLToPath(new URL("./fixtures/daemon-wire-
|
|
16
|
+
const fixturePath = fileURLToPath(new URL("./fixtures/daemon-wire-v3.json", import.meta.url));
|
|
17
17
|
|
|
18
18
|
describe("daemon wire protocol", () => {
|
|
19
|
-
it("validates the shared desktop/TypeScript
|
|
19
|
+
it("validates the shared desktop/TypeScript v3 fixture", () => {
|
|
20
20
|
const fixture = JSON.parse(readFileSync(fixturePath, "utf8")) as Record<string, unknown>;
|
|
21
21
|
|
|
22
22
|
expect(CanonicalDaemonInfoSchema.parse(fixture.canonical).protocolVersion).toBe(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"canonical": {
|
|
3
|
+
"pid": 4312,
|
|
4
|
+
"port": 4010,
|
|
5
|
+
"protocolVersion": 3,
|
|
6
|
+
"productVersion": "0.0.1",
|
|
7
|
+
"instanceId": "9bcf33b0-c837-4a94-b5e8-c0977f54464f",
|
|
8
|
+
"startedAt": "2026-07-20T12:34:56.123Z",
|
|
9
|
+
"bindHostname": "127.0.0.1",
|
|
10
|
+
"authToken": "fixture-token"
|
|
11
|
+
},
|
|
12
|
+
"health": {
|
|
13
|
+
"ok": true,
|
|
14
|
+
"protocolVersion": 3,
|
|
15
|
+
"productVersion": "0.0.1",
|
|
16
|
+
"uptime": 42
|
|
17
|
+
},
|
|
18
|
+
"healthz": {
|
|
19
|
+
"ok": true,
|
|
20
|
+
"protocolVersion": 3,
|
|
21
|
+
"productVersion": "0.0.1",
|
|
22
|
+
"uptimeMs": 42000
|
|
23
|
+
},
|
|
24
|
+
"identity": {
|
|
25
|
+
"ok": true,
|
|
26
|
+
"pid": 4312,
|
|
27
|
+
"protocolVersion": 3,
|
|
28
|
+
"productVersion": "0.0.1",
|
|
29
|
+
"instanceId": "9bcf33b0-c837-4a94-b5e8-c0977f54464f",
|
|
30
|
+
"startedAt": "2026-07-20T12:34:56.123Z"
|
|
31
|
+
},
|
|
32
|
+
"sessions": {
|
|
33
|
+
"sessions": [{ "name": "fixture-project", "dir": "/work/fixture-project" }]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -65,7 +65,7 @@ describe("the shared issue-error vocabulary", () => {
|
|
|
65
65
|
for (const reason of [
|
|
66
66
|
"Authorization was Bearer owner-secret",
|
|
67
67
|
`The redemptionTicket was ta1_${"A".repeat(43)}`,
|
|
68
|
-
`The redemptionTicket was
|
|
68
|
+
`The redemptionTicket was ps2_${"A".repeat(43)}`,
|
|
69
69
|
"ownerToken=secret",
|
|
70
70
|
]) {
|
|
71
71
|
for (const schema of [TerminalAttachmentIssueErrorSchemaZ, PaneStreamIssueErrorSchemaZ]) {
|
|
@@ -22,6 +22,8 @@ const verb = (id: string): MultiplexerVerbEntry => {
|
|
|
22
22
|
describe("the multiplexer verb table", () => {
|
|
23
23
|
it("carries every verb the milestone declares, and no others", () => {
|
|
24
24
|
expect([...MULTIPLEXER_VERB_IDS]).toEqual([
|
|
25
|
+
"window.link.select",
|
|
26
|
+
"window.link.unlink",
|
|
25
27
|
"session.new",
|
|
26
28
|
"session.kill",
|
|
27
29
|
"session.rename",
|
|
@@ -80,7 +82,7 @@ describe("the multiplexer verb table", () => {
|
|
|
80
82
|
const destructive = MULTIPLEXER_VERB_TABLE.filter((entry) => entry.destructive).map(
|
|
81
83
|
(entry) => entry.id,
|
|
82
84
|
);
|
|
83
|
-
expect(destructive).toEqual(["session.kill", "window.kill", "pane.kill"]);
|
|
85
|
+
expect(destructive).toEqual(["window.link.unlink", "session.kill", "window.kill", "pane.kill"]);
|
|
84
86
|
});
|
|
85
87
|
|
|
86
88
|
it("leaves every tmux key hint unfilled until the keybinding bridge exists", () => {
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
PaneStreamServerFrameSchemaZ,
|
|
26
26
|
} from "../pane-stream.ts";
|
|
27
27
|
|
|
28
|
-
const TICKET = `
|
|
28
|
+
const TICKET = `ps2_${"A".repeat(43)}`;
|
|
29
29
|
const REQUEST_ID = "7f0f9a7e-9be0-4b6e-a3a3-0a15c9a7e0d1";
|
|
30
30
|
const INSTANCE_ID = "3d3f8a52-77aa-4b3e-9a44-2f2f7b1c9d10";
|
|
31
31
|
const WS_URL = `ws://127.0.0.1:6070${PANE_STREAM_REDEEM_PATH}`;
|
|
@@ -55,9 +55,9 @@ function descriptor() {
|
|
|
55
55
|
|
|
56
56
|
describe("pane-stream lease contracts", () => {
|
|
57
57
|
it("publishes one shared issue and redemption endpoint authority", () => {
|
|
58
|
-
expect(PANE_STREAM_ISSUE_PATH).toBe("/api/
|
|
59
|
-
expect(PANE_STREAM_REDEEM_PATH).toBe("/
|
|
60
|
-
expect(PANE_STREAM_WEBSOCKET_SUBPROTOCOL).toBe("tmux-ide-pane-stream.
|
|
58
|
+
expect(PANE_STREAM_ISSUE_PATH).toBe("/api/v2/terminal/pane-streams/issue");
|
|
59
|
+
expect(PANE_STREAM_REDEEM_PATH).toBe("/v2/terminal/pane-streams/redeem");
|
|
60
|
+
expect(PANE_STREAM_WEBSOCKET_SUBPROTOCOL).toBe("tmux-ide-pane-stream.v2");
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it("accepts a viewport-free enumerated pane set", () => {
|
|
@@ -115,7 +115,7 @@ describe("pane-stream lease contracts", () => {
|
|
|
115
115
|
|
|
116
116
|
it("accepts only ps1 tickets", () => {
|
|
117
117
|
expect(PaneStreamRedemptionTicketSchemaZ.safeParse(TICKET).success).toBe(true);
|
|
118
|
-
for (const invalid of [`ta1_${"A".repeat(43)}`, `
|
|
118
|
+
for (const invalid of [`ta1_${"A".repeat(43)}`, `ps2_${"A".repeat(42)}`, "ps2_", "ps1"]) {
|
|
119
119
|
expect(PaneStreamRedemptionTicketSchemaZ.safeParse(invalid).success).toBe(false);
|
|
120
120
|
}
|
|
121
121
|
});
|
|
@@ -184,6 +184,18 @@ describe("pane-stream lease contracts", () => {
|
|
|
184
184
|
type: "layout-snapshot" as const,
|
|
185
185
|
topologyEpoch: 1,
|
|
186
186
|
layouts: [layout("window.one", "pane.editor", true)],
|
|
187
|
+
windowLinks: {
|
|
188
|
+
liveSessionId: `live-session.${"a".repeat(20)}`,
|
|
189
|
+
linkRevision: 1,
|
|
190
|
+
activeLinkId: `window-link.${"a".repeat(32)}`,
|
|
191
|
+
links: [
|
|
192
|
+
{
|
|
193
|
+
linkId: `window-link.${"a".repeat(32)}`,
|
|
194
|
+
semanticWindowId: "window.one",
|
|
195
|
+
displayIndex: 0,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
},
|
|
187
199
|
};
|
|
188
200
|
expect(PaneStreamServerFrameSchemaZ.safeParse(snapshot).success).toBe(true);
|
|
189
201
|
for (const layouts of [
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
TmuxServerScopeSchemaZ,
|
|
4
|
+
tmuxServerPaneStreamPath,
|
|
5
|
+
isTmuxServerPaneStreamPath,
|
|
6
|
+
TmuxServerDescriptorSchemaZ,
|
|
7
|
+
TmuxServersResourceSchemaZ,
|
|
8
|
+
TmuxServerPaneTargetSchemaZ,
|
|
9
|
+
resolveTmuxServerScope,
|
|
10
|
+
tmuxServerScopedResourceKey,
|
|
11
|
+
type TmuxServerScope,
|
|
12
|
+
type TmuxServerDescriptor,
|
|
13
|
+
} from "../tmux-server-scope.ts";
|
|
14
|
+
|
|
15
|
+
import { PaneStreamLoopbackWebSocketUrlSchemaZ, PANE_STREAM_REDEEM_PATH } from "../pane-stream.ts";
|
|
16
|
+
|
|
17
|
+
const a: TmuxServerScope = {
|
|
18
|
+
serverId: `tmux-server.${"a".repeat(32)}`,
|
|
19
|
+
generation: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
20
|
+
};
|
|
21
|
+
const b: TmuxServerScope = { ...a, serverId: `tmux-server.${"b".repeat(32)}` };
|
|
22
|
+
const replacement: TmuxServerScope = { ...a, generation: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" };
|
|
23
|
+
const online = (scope: TmuxServerScope): TmuxServerDescriptor => ({
|
|
24
|
+
...scope,
|
|
25
|
+
label: "default",
|
|
26
|
+
state: "online",
|
|
27
|
+
});
|
|
28
|
+
const offline = (scope: TmuxServerScope): TmuxServerDescriptor => ({
|
|
29
|
+
serverId: scope.serverId,
|
|
30
|
+
label: "default",
|
|
31
|
+
state: "offline",
|
|
32
|
+
generation: null,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("server-scoped identities", () => {
|
|
36
|
+
it("keeps registration identity separate from each live incarnation", () => {
|
|
37
|
+
expect(TmuxServerScopeSchemaZ.parse(a)).toEqual(a);
|
|
38
|
+
expect(TmuxServerScopeSchemaZ.parse(replacement).serverId).toBe(a.serverId);
|
|
39
|
+
for (const serverId of ["/tmp/tmux/socket", "default", "%0", "tmux-server.short"]) {
|
|
40
|
+
expect(TmuxServerScopeSchemaZ.safeParse({ ...a, serverId }).success).toBe(false);
|
|
41
|
+
}
|
|
42
|
+
expect(TmuxServerScopeSchemaZ.safeParse({ ...a, socketPath: "/tmp/socket" }).success).toBe(
|
|
43
|
+
false,
|
|
44
|
+
);
|
|
45
|
+
expect(TmuxServerScopeSchemaZ.safeParse({ ...a, generation: "1234" }).success).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("publishes no live generation for offline registrations", () => {
|
|
49
|
+
expect(TmuxServerDescriptorSchemaZ.parse(online(a))).toEqual(online(a));
|
|
50
|
+
expect(TmuxServerDescriptorSchemaZ.parse(offline(a))).toEqual(offline(a));
|
|
51
|
+
expect(TmuxServerDescriptorSchemaZ.safeParse({ ...online(a), generation: null }).success).toBe(
|
|
52
|
+
false,
|
|
53
|
+
);
|
|
54
|
+
expect(
|
|
55
|
+
TmuxServerDescriptorSchemaZ.safeParse({ ...offline(a), generation: a.generation }).success,
|
|
56
|
+
).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("versions the new resource explicitly and refuses duplicate registrations", () => {
|
|
60
|
+
expect(
|
|
61
|
+
TmuxServersResourceSchemaZ.safeParse({ version: 1, servers: [online(a), online(b)] }).success,
|
|
62
|
+
).toBe(true);
|
|
63
|
+
expect(TmuxServersResourceSchemaZ.safeParse({ version: 2, servers: [] }).success).toBe(false);
|
|
64
|
+
expect(
|
|
65
|
+
TmuxServersResourceSchemaZ.safeParse({ version: 1, servers: [online(a), offline(a)] })
|
|
66
|
+
.success,
|
|
67
|
+
).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("requires complete server scope for copied pane stamps", () => {
|
|
71
|
+
const target = {
|
|
72
|
+
server: a,
|
|
73
|
+
liveSessionId: `live-session.${"a".repeat(20)}`,
|
|
74
|
+
semanticPaneId: "pane.copied",
|
|
75
|
+
};
|
|
76
|
+
expect(TmuxServerPaneTargetSchemaZ.safeParse(target).success).toBe(true);
|
|
77
|
+
expect(TmuxServerPaneTargetSchemaZ.safeParse({ ...target, server: undefined }).success).toBe(
|
|
78
|
+
false,
|
|
79
|
+
);
|
|
80
|
+
expect(TmuxServerPaneTargetSchemaZ.safeParse({ ...target, runtimePaneId: "%0" }).success).toBe(
|
|
81
|
+
false,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("keys copied stamps, native IDs and generations without delimiter collisions", () => {
|
|
86
|
+
const keys = [
|
|
87
|
+
tmuxServerScopedResourceKey(a, "pane", "copied"),
|
|
88
|
+
tmuxServerScopedResourceKey(b, "pane", "copied"),
|
|
89
|
+
tmuxServerScopedResourceKey(replacement, "pane", "copied"),
|
|
90
|
+
tmuxServerScopedResourceKey(a, "window", "copied"),
|
|
91
|
+
tmuxServerScopedResourceKey(a, "pane:copied", ""),
|
|
92
|
+
tmuxServerScopedResourceKey(a, "pane", ":copied"),
|
|
93
|
+
];
|
|
94
|
+
expect(new Set(keys).size).toBe(keys.length);
|
|
95
|
+
expect(tmuxServerScopedResourceKey(a, "pane", "%0")).not.toBe(
|
|
96
|
+
tmuxServerScopedResourceKey(b, "pane", "%0"),
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("server selection authority", () => {
|
|
102
|
+
it("routes exact scope despite identical display labels", () => {
|
|
103
|
+
expect(resolveTmuxServerScope([online(a), online(b)], b)).toEqual({
|
|
104
|
+
status: "matched",
|
|
105
|
+
scope: b,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("rejects old generations without falling back to another live server", () => {
|
|
110
|
+
expect(resolveTmuxServerScope([online(replacement), online(b)], a)).toEqual({
|
|
111
|
+
status: "stale-generation",
|
|
112
|
+
});
|
|
113
|
+
expect(resolveTmuxServerScope([online(b)], a)).toEqual({ status: "not-found" });
|
|
114
|
+
expect(resolveTmuxServerScope([offline(a), online(b)], a)).toEqual({ status: "offline" });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("allows legacy selection only with one registered live owner", () => {
|
|
118
|
+
expect(resolveTmuxServerScope([online(a)])).toEqual({ status: "matched", scope: a });
|
|
119
|
+
expect(resolveTmuxServerScope([])).toEqual({ status: "not-found" });
|
|
120
|
+
expect(resolveTmuxServerScope([offline(a)])).toEqual({ status: "offline" });
|
|
121
|
+
expect(resolveTmuxServerScope([online(a), online(b)])).toEqual({ status: "ambiguous" });
|
|
122
|
+
expect(resolveTmuxServerScope([offline(a), online(b)])).toEqual({ status: "ambiguous" });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("fails closed if malformed registry repeats an identity", () => {
|
|
126
|
+
expect(resolveTmuxServerScope([online(a), online(a)], a)).toEqual({ status: "ambiguous" });
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("server-scoped pane stream addresses", () => {
|
|
131
|
+
it("separates owners and generations and accepts canonical loopback URLs", () => {
|
|
132
|
+
const paths = [a, b, replacement].map(tmuxServerPaneStreamPath);
|
|
133
|
+
expect(new Set(paths).size).toBe(3);
|
|
134
|
+
for (const path of [...paths, PANE_STREAM_REDEEM_PATH]) {
|
|
135
|
+
expect(
|
|
136
|
+
PaneStreamLoopbackWebSocketUrlSchemaZ.safeParse(`ws://127.0.0.1:1234${path}`).success,
|
|
137
|
+
).toBe(true);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("refuses unscoped, malformed, encoded and credentialed paths", () => {
|
|
142
|
+
const path = tmuxServerPaneStreamPath(a);
|
|
143
|
+
for (const badPath of [
|
|
144
|
+
path + "/",
|
|
145
|
+
path + "/other",
|
|
146
|
+
path.replace(a.serverId, "default"),
|
|
147
|
+
path.replace(a.generation, "old"),
|
|
148
|
+
path.replace("tmux-server.", "tmux-server%2e"),
|
|
149
|
+
"/v2/tmux-servers/pane-streams/redeem",
|
|
150
|
+
]) {
|
|
151
|
+
expect(isTmuxServerPaneStreamPath(badPath)).toBe(false);
|
|
152
|
+
expect(
|
|
153
|
+
PaneStreamLoopbackWebSocketUrlSchemaZ.safeParse(`ws://127.0.0.1:1234${badPath}`).success,
|
|
154
|
+
).toBe(false);
|
|
155
|
+
}
|
|
156
|
+
for (const url of [
|
|
157
|
+
`ws://127.0.0.1:1234${path}?generation=${b.generation}`,
|
|
158
|
+
`ws://127.0.0.1:1234${path}#fragment`,
|
|
159
|
+
`ws://user@127.0.0.1:1234${path}`,
|
|
160
|
+
`ws://example.com:1234${path}`,
|
|
161
|
+
`wss://127.0.0.1:1234${path}`,
|
|
162
|
+
`ws://127.0.0.1${path}`,
|
|
163
|
+
])
|
|
164
|
+
expect(PaneStreamLoopbackWebSocketUrlSchemaZ.safeParse(url).success).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WorkspaceWindowLinkSelectArgumentsSchemaZ,
|
|
3
|
+
WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
|
|
4
|
+
WorkspacePaneSelectWithWindowLinkArgumentsSchemaZ,
|
|
5
|
+
WorkspacePaneSelectArgumentsSchemaZ,
|
|
6
|
+
WorkspaceMultiplexerIntentSchemaZ,
|
|
7
|
+
} from "../workspace-multiplexer.ts";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
WINDOW_LINK_MAX_LINKS,
|
|
11
|
+
WindowLinkTargetSchemaZ,
|
|
12
|
+
WindowLinkTopologySchemaZ,
|
|
13
|
+
} from "../window-links.ts";
|
|
14
|
+
import {
|
|
15
|
+
PaneStreamLayoutSnapshotV2FrameSchemaZ,
|
|
16
|
+
PaneStreamLayoutSnapshotFrameSchemaZ,
|
|
17
|
+
PaneStreamLayoutSnapshotV1FrameSchemaZ,
|
|
18
|
+
PaneStreamServerFrameSchemaZ,
|
|
19
|
+
PANE_STREAM_PROTOCOL_VERSION,
|
|
20
|
+
} from "../pane-stream.ts";
|
|
21
|
+
|
|
22
|
+
const linkId = (index: number) => `window-link.${index.toString(16).padStart(32, "0")}`;
|
|
23
|
+
const topology = () => ({
|
|
24
|
+
liveSessionId: `live-session.${"a".repeat(20)}`,
|
|
25
|
+
linkRevision: 1,
|
|
26
|
+
activeLinkId: linkId(1),
|
|
27
|
+
links: [
|
|
28
|
+
{ linkId: linkId(1), semanticWindowId: "window.one", displayIndex: 0 },
|
|
29
|
+
{ linkId: linkId(2), semanticWindowId: "window.one", displayIndex: 1 },
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
const layout = (window = "window.one", pane: string | null = "pane.one", currentWindow = true) => ({
|
|
33
|
+
type: "layout" as const,
|
|
34
|
+
semanticWindowId: window,
|
|
35
|
+
windowName: "shell",
|
|
36
|
+
currentWindow,
|
|
37
|
+
cols: 80,
|
|
38
|
+
rows: 24,
|
|
39
|
+
zoomed: false,
|
|
40
|
+
panes: [{ pane, left: 0, top: 0, width: 80, height: 24, active: true }],
|
|
41
|
+
});
|
|
42
|
+
const snapshot = () => ({
|
|
43
|
+
type: "layout-snapshot" as const,
|
|
44
|
+
topologyEpoch: 1,
|
|
45
|
+
layouts: [layout()],
|
|
46
|
+
windowLinks: topology(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("window link contracts", () => {
|
|
50
|
+
it("allows multiple links sharing one unique backing and pane", () => {
|
|
51
|
+
expect(WindowLinkTopologySchemaZ.safeParse(topology()).success).toBe(true);
|
|
52
|
+
expect(PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse(snapshot()).success).toBe(true);
|
|
53
|
+
expect(
|
|
54
|
+
PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse({
|
|
55
|
+
...snapshot(),
|
|
56
|
+
windowLinks: { ...topology(), activeLinkId: linkId(2) },
|
|
57
|
+
}).success,
|
|
58
|
+
).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("rejects duplicate handles/indexes and missing active links", () => {
|
|
62
|
+
const original = topology();
|
|
63
|
+
for (const invalid of [
|
|
64
|
+
{ ...original, links: [original.links[0], { ...original.links[1], linkId: linkId(1) }] },
|
|
65
|
+
{ ...original, links: [original.links[0], { ...original.links[1], displayIndex: 0 }] },
|
|
66
|
+
{ ...original, activeLinkId: linkId(3) },
|
|
67
|
+
{ ...original, links: [] },
|
|
68
|
+
])
|
|
69
|
+
expect(WindowLinkTopologySchemaZ.safeParse(invalid).success).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("bounds topology size and integer revisions/indexes", () => {
|
|
73
|
+
const links = Array.from({ length: WINDOW_LINK_MAX_LINKS }, (_, index) => ({
|
|
74
|
+
linkId: linkId(index + 1),
|
|
75
|
+
semanticWindowId: "window.one",
|
|
76
|
+
displayIndex: index,
|
|
77
|
+
}));
|
|
78
|
+
expect(WindowLinkTopologySchemaZ.safeParse({ ...topology(), links }).success).toBe(true);
|
|
79
|
+
expect(
|
|
80
|
+
WindowLinkTopologySchemaZ.safeParse({
|
|
81
|
+
...topology(),
|
|
82
|
+
links: [
|
|
83
|
+
...links,
|
|
84
|
+
{
|
|
85
|
+
linkId: linkId(WINDOW_LINK_MAX_LINKS + 1),
|
|
86
|
+
semanticWindowId: "window.one",
|
|
87
|
+
displayIndex: WINDOW_LINK_MAX_LINKS,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
}).success,
|
|
91
|
+
).toBe(false);
|
|
92
|
+
for (const value of [-1, 0.5, Number.MAX_SAFE_INTEGER + 1, Infinity]) {
|
|
93
|
+
expect(
|
|
94
|
+
WindowLinkTopologySchemaZ.safeParse({ ...topology(), linkRevision: value }).success,
|
|
95
|
+
).toBe(false);
|
|
96
|
+
expect(
|
|
97
|
+
WindowLinkTopologySchemaZ.safeParse({
|
|
98
|
+
...topology(),
|
|
99
|
+
links: [{ ...links[0], displayIndex: value }],
|
|
100
|
+
}).success,
|
|
101
|
+
).toBe(false);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("accepts only opaque targets and rejects native or display action addresses", () => {
|
|
106
|
+
const target = {
|
|
107
|
+
liveSessionId: topology().liveSessionId,
|
|
108
|
+
linkId: linkId(1),
|
|
109
|
+
expectedSemanticWindowId: "window.one",
|
|
110
|
+
linkRevision: 0,
|
|
111
|
+
};
|
|
112
|
+
expect(WindowLinkTargetSchemaZ.safeParse(target).success).toBe(true);
|
|
113
|
+
for (const invalid of [
|
|
114
|
+
{ ...target, linkId: "@0" },
|
|
115
|
+
{ ...target, linkId: "/tmp/tmux/socket" },
|
|
116
|
+
{ ...target, linkId: "window-link.abc" },
|
|
117
|
+
{ ...target, liveSessionId: "$0" },
|
|
118
|
+
{ ...target, expectedSemanticWindowId: "@0" },
|
|
119
|
+
{ ...target, displayIndex: 0 },
|
|
120
|
+
{ ...target, linkRevision: -1 },
|
|
121
|
+
{ ...target, linkRevision: Number.MAX_SAFE_INTEGER + 1 },
|
|
122
|
+
])
|
|
123
|
+
expect(WindowLinkTargetSchemaZ.safeParse(invalid).success).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("requires a complete join and a matching active backing", () => {
|
|
127
|
+
const second = layout("window.two", "pane.two", false);
|
|
128
|
+
for (const invalid of [
|
|
129
|
+
{ ...snapshot(), layouts: [layout("window.two")] },
|
|
130
|
+
{ ...snapshot(), layouts: [layout(), second] },
|
|
131
|
+
{
|
|
132
|
+
...snapshot(),
|
|
133
|
+
layouts: [layout("window.one", "pane.one", false), { ...second, currentWindow: true }],
|
|
134
|
+
windowLinks: {
|
|
135
|
+
...topology(),
|
|
136
|
+
links: [
|
|
137
|
+
...topology().links,
|
|
138
|
+
{ linkId: linkId(3), semanticWindowId: "window.two", displayIndex: 2 },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
])
|
|
143
|
+
expect(PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse(invalid).success).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("preserves unique backing/pane and exactly-one-current invariants", () => {
|
|
147
|
+
for (const layouts of [
|
|
148
|
+
[layout(), layout()],
|
|
149
|
+
[layout(), layout("window.two", "pane.one", false)],
|
|
150
|
+
[layout("window.one", null)],
|
|
151
|
+
[{ ...layout(), semanticWindowId: null }],
|
|
152
|
+
[layout("window.one", "pane.one", false)],
|
|
153
|
+
[layout(), layout("window.two", "pane.two", true)],
|
|
154
|
+
])
|
|
155
|
+
expect(
|
|
156
|
+
PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse({ ...snapshot(), layouts }).success,
|
|
157
|
+
).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("requires v2 topology and refuses the old v1 shape", () => {
|
|
161
|
+
const v1 = { type: "layout-snapshot", topologyEpoch: 1, layouts: [layout()] };
|
|
162
|
+
expect(PANE_STREAM_PROTOCOL_VERSION).toBe(2);
|
|
163
|
+
expect(PaneStreamLayoutSnapshotV1FrameSchemaZ.safeParse(v1).success).toBe(true);
|
|
164
|
+
expect(PaneStreamLayoutSnapshotFrameSchemaZ.safeParse(v1).success).toBe(false);
|
|
165
|
+
expect(PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse(v1).success).toBe(false);
|
|
166
|
+
expect(PaneStreamLayoutSnapshotFrameSchemaZ.safeParse(snapshot()).success).toBe(true);
|
|
167
|
+
expect(PaneStreamServerFrameSchemaZ.safeParse(snapshot()).success).toBe(true);
|
|
168
|
+
expect(
|
|
169
|
+
PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse({ ...snapshot(), unexpected: true }).success,
|
|
170
|
+
).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("window link action arguments", () => {
|
|
175
|
+
const target = () => ({
|
|
176
|
+
liveSessionId: topology().liveSessionId,
|
|
177
|
+
linkId: linkId(1),
|
|
178
|
+
expectedSemanticWindowId: "window.one",
|
|
179
|
+
linkRevision: 1,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("accepts opaque select/unlink targets and explicit-link pane selection", () => {
|
|
183
|
+
for (const schema of [
|
|
184
|
+
WorkspaceWindowLinkSelectArgumentsSchemaZ,
|
|
185
|
+
WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
|
|
186
|
+
]) {
|
|
187
|
+
expect(schema.safeParse({ workspaceName: "project", target: target() }).success).toBe(true);
|
|
188
|
+
}
|
|
189
|
+
expect(
|
|
190
|
+
WorkspacePaneSelectWithWindowLinkArgumentsSchemaZ.safeParse({
|
|
191
|
+
workspaceName: "project",
|
|
192
|
+
semanticPaneId: "pane.one",
|
|
193
|
+
windowLink: target(),
|
|
194
|
+
}).success,
|
|
195
|
+
).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("rejects native addresses, display indexes, command/force flags and unknown fields", () => {
|
|
199
|
+
for (const schema of [
|
|
200
|
+
WorkspaceWindowLinkSelectArgumentsSchemaZ,
|
|
201
|
+
WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
|
|
202
|
+
]) {
|
|
203
|
+
const args = { workspaceName: "project", target: target() };
|
|
204
|
+
for (const invalid of [
|
|
205
|
+
{ ...args, target: "@0" },
|
|
206
|
+
{ ...args, target: { ...target(), linkId: "@0" } },
|
|
207
|
+
{ ...args, target: { ...target(), expectedSemanticWindowId: "@0" } },
|
|
208
|
+
{ ...args, target: { ...target(), displayIndex: 0 } },
|
|
209
|
+
{ ...args, target: { ...target(), runtimeSessionId: "$0" } },
|
|
210
|
+
{ ...args, displayIndex: 0 },
|
|
211
|
+
{ ...args, command: "kill-server" },
|
|
212
|
+
{ ...args, force: true },
|
|
213
|
+
{ ...args, socketPath: "/tmp/tmux/socket" },
|
|
214
|
+
{ ...args, workspaceName: "" },
|
|
215
|
+
])
|
|
216
|
+
expect(schema.safeParse(invalid).success).toBe(false);
|
|
217
|
+
}
|
|
218
|
+
const args = { workspaceName: "project", semanticPaneId: "pane.one", windowLink: target() };
|
|
219
|
+
for (const invalid of [
|
|
220
|
+
{ ...args, semanticPaneId: "%0" },
|
|
221
|
+
{ ...args, command: "select-pane -t %0" },
|
|
222
|
+
{ ...args, windowLink: { ...target(), linkId: "project:0" } },
|
|
223
|
+
{ workspaceName: "project", semanticPaneId: "pane.one" },
|
|
224
|
+
])
|
|
225
|
+
expect(WorkspacePaneSelectWithWindowLinkArgumentsSchemaZ.safeParse(invalid).success).toBe(
|
|
226
|
+
false,
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("accepts registered link verbs and explicit-link pane selection", () => {
|
|
231
|
+
const args = { workspaceName: "project", target: target() };
|
|
232
|
+
for (const verb of ["workspace.window.link.select", "workspace.window.link.unlink"]) {
|
|
233
|
+
expect(WorkspaceMultiplexerIntentSchemaZ.safeParse({ verb, ...args }).success).toBe(true);
|
|
234
|
+
}
|
|
235
|
+
expect(
|
|
236
|
+
WorkspacePaneSelectArgumentsSchemaZ.safeParse({
|
|
237
|
+
workspaceName: "project",
|
|
238
|
+
semanticPaneId: "pane.one",
|
|
239
|
+
windowLink: target(),
|
|
240
|
+
}).success,
|
|
241
|
+
).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -42,6 +42,10 @@ import {
|
|
|
42
42
|
AppWindowMutationResultSchemaZ,
|
|
43
43
|
} from "./app-window-mutation.ts";
|
|
44
44
|
import {
|
|
45
|
+
WorkspaceWindowLinkSelectArgumentsSchemaZ,
|
|
46
|
+
WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
|
|
47
|
+
WorkspaceWindowLinkSelectResultSchemaZ,
|
|
48
|
+
WorkspaceWindowLinkUnlinkResultSchemaZ,
|
|
45
49
|
WorkspacePaneKillArgumentsSchemaZ,
|
|
46
50
|
WorkspacePaneKillResultSchemaZ,
|
|
47
51
|
WorkspacePaneResizeArgumentsSchemaZ,
|
|
@@ -427,6 +431,14 @@ export const ActionContractsZ = {
|
|
|
427
431
|
input: AppWindowMutationInputZ,
|
|
428
432
|
result: AppWindowMutationResultZ,
|
|
429
433
|
},
|
|
434
|
+
"workspace.window.link.select": {
|
|
435
|
+
input: WorkspaceWindowLinkSelectArgumentsSchemaZ,
|
|
436
|
+
result: WorkspaceWindowLinkSelectResultSchemaZ,
|
|
437
|
+
},
|
|
438
|
+
"workspace.window.link.unlink": {
|
|
439
|
+
input: WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
|
|
440
|
+
result: WorkspaceWindowLinkUnlinkResultSchemaZ,
|
|
441
|
+
},
|
|
430
442
|
"workspace.window.split": {
|
|
431
443
|
input: WorkspaceWindowSplitInputZ,
|
|
432
444
|
result: WorkspaceWindowSplitResultZ,
|
|
@@ -5,11 +5,8 @@ import { z } from "zod";
|
|
|
5
5
|
* health responses, REST resources, and WebSocket transports. This is
|
|
6
6
|
* intentionally independent from npm/package marketing versions.
|
|
7
7
|
*/
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// then fail a current client at the first catalog parse, so this is a real wire
|
|
11
|
-
// boundary rather than an additive package-version change.
|
|
12
|
-
export const DAEMON_WIRE_PROTOCOL_VERSION = 2 as const;
|
|
8
|
+
// v3 requires link-aware terminal topology and actions; v2 peers must upgrade.
|
|
9
|
+
export const DAEMON_WIRE_PROTOCOL_VERSION = 3 as const;
|
|
13
10
|
|
|
14
11
|
/**
|
|
15
12
|
* Discovery must retain unknown positive versions so a client can report an
|
|
@@ -96,7 +93,14 @@ export const DaemonProvenanceSchema = z.object({
|
|
|
96
93
|
});
|
|
97
94
|
export type DaemonProvenance = z.infer<typeof DaemonProvenanceSchema>;
|
|
98
95
|
|
|
96
|
+
export const TmuxServerProofSchema = z.object({
|
|
97
|
+
version: z.literal(1),
|
|
98
|
+
kind: z.enum(["live", "unbound-name"]),
|
|
99
|
+
digest: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
100
|
+
});
|
|
101
|
+
|
|
99
102
|
export const CanonicalDaemonInfoSchema = z.object({
|
|
103
|
+
tmuxServerProofVersion: z.literal(1).optional(),
|
|
100
104
|
supervisionId: DaemonSupervisionIdSchema.optional(),
|
|
101
105
|
pid: z.number().int().positive(),
|
|
102
106
|
port: z.number().int().min(1).max(65_535),
|
|
@@ -133,6 +137,7 @@ export type DaemonHealthz = z.infer<typeof DaemonHealthzSchema>;
|
|
|
133
137
|
* daemon.json record is the process instance which published that record.
|
|
134
138
|
*/
|
|
135
139
|
export const DaemonIdentitySchema = z.object({
|
|
140
|
+
tmuxServerProof: TmuxServerProofSchema.nullable().optional(),
|
|
136
141
|
ok: z.literal(true),
|
|
137
142
|
pid: z.number().int().positive(),
|
|
138
143
|
protocolVersion: DaemonWireProtocolVersionSchema,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TmuxServerScopeSchemaZ } from "./tmux-server-scope.ts";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { SavedMachineIdSchema } from "./saved-machines.ts";
|
|
3
4
|
import { DaemonInstanceIdentitySchemaZ } from "./daemon-wire.ts";
|
|
@@ -16,6 +17,8 @@ export const FleetCacheRouteIdSchema = z.union([z.literal("local"), SavedMachine
|
|
|
16
17
|
export const FleetCachedSessionSchema = z.strictObject({
|
|
17
18
|
id: key,
|
|
18
19
|
liveSessionId: key.optional(),
|
|
20
|
+
server: TmuxServerScopeSchemaZ.optional(),
|
|
21
|
+
serverLabel: label.optional(),
|
|
19
22
|
name: label,
|
|
20
23
|
paneCount: z.number().int().min(0).max(4096),
|
|
21
24
|
});
|
|
@@ -65,6 +65,7 @@ export * from "./workspace-open.ts";
|
|
|
65
65
|
export * from "./workspace-open-handoff.ts";
|
|
66
66
|
export * from "./workspace-promotion.ts";
|
|
67
67
|
export * from "./workspace-multiplexer.ts";
|
|
68
|
+
export * from "./window-links.ts";
|
|
68
69
|
export * from "./interaction-receipts.ts";
|
|
69
70
|
export * from "./multiplexer-verbs.ts";
|
|
70
71
|
export * from "./visual-tokens.ts";
|
|
@@ -97,3 +98,4 @@ export * from "./saved-machines.ts";
|
|
|
97
98
|
export * from "./semantic-icons.ts";
|
|
98
99
|
|
|
99
100
|
export * from "./workspace-admission.ts";
|
|
101
|
+
export * from "./tmux-server-scope.ts";
|