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.
- package/bin/cli.js +46791 -43589
- package/package.json +2 -2
- 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/fleet-lifecycle.ts +5 -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 +351 -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 +31 -11
- 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-guided-tour-integration.jsx +119 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-owner.js +132 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +21 -9
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-transport.js +63 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +8 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.js +3 -1
- 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-home-experience.js +40 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +132 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +9 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-catalog.js +12 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +211 -31
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +91 -37
- package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +20 -7
- 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-shell-home.jsx +6 -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/application-theme-repaint.js +9 -0
- package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +44 -1
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour-coach.jsx +54 -0
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour-storage.js +67 -0
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour.js +113 -0
- package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/resize-terminal-pane.js +4 -0
- package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +2 -1
- package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +12 -4
- 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 +479 -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 +33 -9
- 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-guided-tour-integration.tsx +199 -0
- package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-owner.ts +162 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +36 -10
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-transport.ts +69 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +12 -2
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +13 -2
- 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-home-experience.ts +59 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +175 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +9 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-catalog.ts +19 -7
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +285 -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-performance-log.ts +18 -6
- package/packages/daemon/src/tui/mirror/runtime/application-root-configuration.ts +12 -3
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +40 -40
- package/packages/daemon/src/tui/mirror/runtime/application-route-connection.ts +93 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +20 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +10 -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/application-theme-repaint.ts +19 -0
- package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +55 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour-coach.tsx +118 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour-storage.ts +72 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour.ts +147 -0
- package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +1 -1
- package/packages/daemon/src/tui/mirror/runtime/resize-terminal-pane.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +11 -4
- 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WorkspaceCatalogLiveSessionIdSchemaZ } from "./workspace-catalog-resource.ts";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import {
|
|
3
4
|
FleetAgentIdSchemaZ,
|
|
@@ -29,6 +30,8 @@ const OwnerPathSchemaZ = z
|
|
|
29
30
|
export const WorkspaceSessionCreateArgumentsSchemaZ = z
|
|
30
31
|
.object({
|
|
31
32
|
displayName: SafeDisplayNameSchemaZ,
|
|
33
|
+
/** Opt in to an incarnation-bearing receipt; legacy clients keep their strict result shape. */
|
|
34
|
+
includeLiveSessionId: z.boolean().optional(),
|
|
32
35
|
cwd: OwnerPathSchemaZ.optional(),
|
|
33
36
|
expectedDaemonInstanceId: z.uuid().optional(),
|
|
34
37
|
})
|
|
@@ -42,6 +45,8 @@ export const WorkspaceSessionCreateResultSchemaZ = z
|
|
|
42
45
|
operationId: z.uuid(),
|
|
43
46
|
daemonInstanceId: z.uuid(),
|
|
44
47
|
outcome: z.enum(["created", "adopted", "replayed"]),
|
|
48
|
+
/** Captured atomically by new-session; absent from older daemons and adopted routes. */
|
|
49
|
+
liveSessionId: WorkspaceCatalogLiveSessionIdSchemaZ.optional(),
|
|
45
50
|
fleetSessionId: FleetSessionIdSchemaZ,
|
|
46
51
|
/** Canonical registered route; displayName is presentation only. */
|
|
47
52
|
workspaceName: DesktopWorkspaceNameSchemaZ,
|
|
@@ -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";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WindowLinkTargetSchemaZ } from "./window-links.ts";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
|
|
3
4
|
import { DesktopWorkspaceNameSchemaZ } from "./desktop-workspace-name.ts";
|
|
@@ -14,6 +15,8 @@ export type AuthoredInteractionOrigin = z.infer<typeof AuthoredInteractionOrigin
|
|
|
14
15
|
|
|
15
16
|
/** Every semantic session-runtime verb; raw tmux addresses never enter this vocabulary. */
|
|
16
17
|
export const InteractionOperationKindSchemaZ = z.enum([
|
|
18
|
+
"workspace.window.link.select",
|
|
19
|
+
"workspace.window.link.unlink",
|
|
17
20
|
"workspace.window.split",
|
|
18
21
|
"workspace.window.kill",
|
|
19
22
|
"workspace.pane.kill",
|
|
@@ -46,6 +49,7 @@ const InteractionWindowReferenceSchemaZ = z.discriminatedUnion("by", [
|
|
|
46
49
|
|
|
47
50
|
/** Stable semantic target retained even when the operation removes or renames it. */
|
|
48
51
|
export const InteractionTargetSchemaZ = z.discriminatedUnion("kind", [
|
|
52
|
+
z.object({ kind: z.literal("window-link"), target: WindowLinkTargetSchemaZ }).strict(),
|
|
49
53
|
z.object({ kind: z.literal("session") }).strict(),
|
|
50
54
|
z.object({ kind: z.literal("window"), target: InteractionWindowReferenceSchemaZ }).strict(),
|
|
51
55
|
z
|
|
@@ -63,6 +67,8 @@ const MutationOutcomeSchemaZ = z.enum(["applied", "unchanged", "replayed"]);
|
|
|
63
67
|
* different verb.
|
|
64
68
|
*/
|
|
65
69
|
export const InteractionSafeSummarySchemaZ = z.union([
|
|
70
|
+
z.object({ operationKind: z.literal("workspace.window.link.unlink") }).strict(),
|
|
71
|
+
z.object({ operationKind: z.literal("workspace.window.link.select") }).strict(),
|
|
66
72
|
z
|
|
67
73
|
.object({
|
|
68
74
|
operationKind: z.literal("workspace.window.split"),
|
|
@@ -129,6 +135,20 @@ export type PaneReadSafeSummary = Extract<
|
|
|
129
135
|
* terminal bytes never enter the replay journal.
|
|
130
136
|
*/
|
|
131
137
|
export const InteractionProofSchemaZ = z.discriminatedUnion("operationKind", [
|
|
138
|
+
z
|
|
139
|
+
.object({
|
|
140
|
+
operationKind: z.literal("workspace.window.link.unlink"),
|
|
141
|
+
outcome: MutationOutcomeSchemaZ,
|
|
142
|
+
target: WindowLinkTargetSchemaZ,
|
|
143
|
+
})
|
|
144
|
+
.strict(),
|
|
145
|
+
z
|
|
146
|
+
.object({
|
|
147
|
+
operationKind: z.literal("workspace.window.link.select"),
|
|
148
|
+
outcome: MutationOutcomeSchemaZ,
|
|
149
|
+
target: WindowLinkTargetSchemaZ,
|
|
150
|
+
})
|
|
151
|
+
.strict(),
|
|
132
152
|
z
|
|
133
153
|
.object({
|
|
134
154
|
operationKind: z.literal("workspace.window.split"),
|
|
@@ -280,6 +300,25 @@ export const InteractionReceiptSchemaZ = z
|
|
|
280
300
|
message: "external observations require a presence-only summary",
|
|
281
301
|
});
|
|
282
302
|
}
|
|
303
|
+
if (receipt.operationKind.startsWith("workspace.window.link.")) {
|
|
304
|
+
if (receipt.target.kind !== "window-link") {
|
|
305
|
+
context.addIssue({
|
|
306
|
+
code: "custom",
|
|
307
|
+
path: ["target"],
|
|
308
|
+
message: "Window link verbs require a link target",
|
|
309
|
+
});
|
|
310
|
+
} else if (
|
|
311
|
+
receipt.proof &&
|
|
312
|
+
"target" in receipt.proof &&
|
|
313
|
+
JSON.stringify(receipt.proof.target) !== JSON.stringify(receipt.target.target)
|
|
314
|
+
) {
|
|
315
|
+
context.addIssue({
|
|
316
|
+
code: "custom",
|
|
317
|
+
path: ["proof"],
|
|
318
|
+
message: "Window link proof must match its target",
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
283
322
|
const paneVerb = receipt.operationKind.startsWith("workspace.pane.");
|
|
284
323
|
if (paneVerb && receipt.target.kind !== "pane") {
|
|
285
324
|
context.addIssue({
|
|
@@ -84,7 +84,7 @@ export const RendererSafeIssueReasonSchemaZ = z
|
|
|
84
84
|
.max(240)
|
|
85
85
|
.refine(
|
|
86
86
|
(reason) =>
|
|
87
|
-
!/(?:authorization|bearer\s+|owner.?token|redemptionticket|
|
|
87
|
+
!/(?:authorization|bearer\s+|owner.?token|redemptionticket|ps[12]_|ta1_)/iu.test(reason),
|
|
88
88
|
"issue error reason must be credential-redacted",
|
|
89
89
|
);
|
|
90
90
|
|
|
@@ -118,6 +118,29 @@ function deepFreeze<Value>(value: Value): Value {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
const ENTRIES: readonly MultiplexerVerbEntry[] = [
|
|
121
|
+
{
|
|
122
|
+
version: MULTIPLEXER_VERB_TABLE_VERSION,
|
|
123
|
+
id: "window.link.select",
|
|
124
|
+
label: "Select window link",
|
|
125
|
+
description: "Select this link without changing the shared window's active pane.",
|
|
126
|
+
scope: "window",
|
|
127
|
+
execution: { kind: "daemon-action", action: "workspace.window.link.select" },
|
|
128
|
+
availabilityInputs: ["workspaceConnected"],
|
|
129
|
+
destructive: false,
|
|
130
|
+
tmuxKeyHint: null,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
version: MULTIPLEXER_VERB_TABLE_VERSION,
|
|
134
|
+
id: "window.link.unlink",
|
|
135
|
+
label: "Unlink window",
|
|
136
|
+
description:
|
|
137
|
+
"Remove this session link; preserve other links and their panes. Native last-link restrictions apply.",
|
|
138
|
+
scope: "window",
|
|
139
|
+
execution: { kind: "daemon-action", action: "workspace.window.link.unlink" },
|
|
140
|
+
availabilityInputs: ["workspaceConnected"],
|
|
141
|
+
destructive: true,
|
|
142
|
+
tmuxKeyHint: null,
|
|
143
|
+
},
|
|
121
144
|
{
|
|
122
145
|
version: MULTIPLEXER_VERB_TABLE_VERSION,
|
|
123
146
|
id: "session.new",
|
|
@@ -179,7 +202,8 @@ const ENTRIES: readonly MultiplexerVerbEntry[] = [
|
|
|
179
202
|
version: MULTIPLEXER_VERB_TABLE_VERSION,
|
|
180
203
|
id: "window.kill",
|
|
181
204
|
label: "Close window",
|
|
182
|
-
description:
|
|
205
|
+
description:
|
|
206
|
+
"Kill this shared tmux window and its panes in every linked session. Refused for a session's last window.",
|
|
183
207
|
scope: "window",
|
|
184
208
|
execution: { kind: "daemon-action", action: "workspace.window.kill" },
|
|
185
209
|
availabilityInputs: ["workspaceConnected", "sessionWindowCount"],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { isTmuxServerPaneStreamPath } from "./tmux-server-scope.ts";
|
|
2
3
|
import {
|
|
3
4
|
CausalCellCapabilitySchemaZ,
|
|
4
5
|
CausalCellFailureV1SchemaZ,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
SessionRuntimeTerminalTextInputSchemaZ,
|
|
42
43
|
} from "./session-runtime.ts";
|
|
43
44
|
import { WorkspaceMultiplexerMutationResultSchemaZ } from "./workspace-multiplexer.ts";
|
|
45
|
+
import { WindowLinkTopologySchemaZ } from "./window-links.ts";
|
|
44
46
|
|
|
45
47
|
export const PANE_STREAM_CLOCK_BOUNDS_CAPABILITY_V1 = "clock-bounds-v1" as const;
|
|
46
48
|
export const PaneStreamDiagnosticCapabilitySchemaZ = z.union([
|
|
@@ -55,7 +57,7 @@ export type PaneStreamDiagnosticCapability = z.infer<typeof PaneStreamDiagnostic
|
|
|
55
57
|
*
|
|
56
58
|
* The discipline is the terminal-attachment one, applied to mirror streams:
|
|
57
59
|
* semantic identity only (runtime `%N`/`@N`/`$N` addresses never cross this
|
|
58
|
-
* boundary), a one-time `
|
|
60
|
+
* boundary), a one-time `ps2_` redemption ticket bound to one daemon
|
|
59
61
|
* generation, and bounded shapes everywhere. Two deliberate differences,
|
|
60
62
|
* both recorded product decisions:
|
|
61
63
|
*
|
|
@@ -65,11 +67,11 @@ export type PaneStreamDiagnosticCapability = z.infer<typeof PaneStreamDiagnostic
|
|
|
65
67
|
* (the control client casts no size vote), so a renderer has no geometry
|
|
66
68
|
* to declare.
|
|
67
69
|
*/
|
|
68
|
-
export const PANE_STREAM_PROTOCOL_VERSION =
|
|
70
|
+
export const PANE_STREAM_PROTOCOL_VERSION = 2 as const;
|
|
69
71
|
|
|
70
|
-
export const PANE_STREAM_ISSUE_PATH = "/api/
|
|
71
|
-
export const PANE_STREAM_REDEEM_PATH = "/
|
|
72
|
-
export const PANE_STREAM_WEBSOCKET_SUBPROTOCOL = "tmux-ide-pane-stream.
|
|
72
|
+
export const PANE_STREAM_ISSUE_PATH = "/api/v2/terminal/pane-streams/issue" as const;
|
|
73
|
+
export const PANE_STREAM_REDEEM_PATH = "/v2/terminal/pane-streams/redeem" as const;
|
|
74
|
+
export const PANE_STREAM_WEBSOCKET_SUBPROTOCOL = "tmux-ide-pane-stream.v2" as const;
|
|
73
75
|
|
|
74
76
|
/** Perf-plan ceiling (~16-24 live nodes) with headroom; enforced at issue. */
|
|
75
77
|
export const PANE_STREAM_MAX_PANES = 24;
|
|
@@ -115,7 +117,7 @@ export const PaneStreamLeaseRequestSchemaZ = z
|
|
|
115
117
|
.strict();
|
|
116
118
|
export type PaneStreamLeaseRequest = z.infer<typeof PaneStreamLeaseRequestSchemaZ>;
|
|
117
119
|
|
|
118
|
-
export const PaneStreamRedemptionTicketSchemaZ = z.string().regex(/^
|
|
120
|
+
export const PaneStreamRedemptionTicketSchemaZ = z.string().regex(/^ps2_[A-Za-z0-9_-]{43}$/u);
|
|
119
121
|
|
|
120
122
|
/** Same canonical loopback discipline as the terminal-attachment endpoint. */
|
|
121
123
|
export const PaneStreamLoopbackWebSocketUrlSchemaZ = z
|
|
@@ -129,7 +131,7 @@ export const PaneStreamLoopbackWebSocketUrlSchemaZ = z
|
|
|
129
131
|
url.port.length > 0 &&
|
|
130
132
|
url.username.length === 0 &&
|
|
131
133
|
url.password.length === 0 &&
|
|
132
|
-
url.pathname === PANE_STREAM_REDEEM_PATH &&
|
|
134
|
+
(url.pathname === PANE_STREAM_REDEEM_PATH || isTmuxServerPaneStreamPath(url.pathname)) &&
|
|
133
135
|
url.search.length === 0 &&
|
|
134
136
|
url.hash.length === 0 &&
|
|
135
137
|
url.toString() === value
|
|
@@ -471,7 +473,7 @@ export const PaneStreamLayoutFrameSchemaZ = z
|
|
|
471
473
|
})
|
|
472
474
|
.strict();
|
|
473
475
|
|
|
474
|
-
export const
|
|
476
|
+
export const PaneStreamLayoutSnapshotV1FrameSchemaZ = z
|
|
475
477
|
.object({
|
|
476
478
|
type: z.literal("layout-snapshot"),
|
|
477
479
|
topologyEpoch: z.number().int().nonnegative(),
|
|
@@ -526,6 +528,47 @@ export const PaneStreamLayoutSnapshotFrameSchemaZ = z
|
|
|
526
528
|
}
|
|
527
529
|
});
|
|
528
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Atomic v2 grammar: backing layouts and session links share one snapshot.
|
|
533
|
+
*/
|
|
534
|
+
export const PaneStreamLayoutSnapshotV2FrameSchemaZ =
|
|
535
|
+
PaneStreamLayoutSnapshotV1FrameSchemaZ.safeExtend({
|
|
536
|
+
windowLinks: WindowLinkTopologySchemaZ,
|
|
537
|
+
}).superRefine((snapshot, context) => {
|
|
538
|
+
const backings = new Map(snapshot.layouts.map((layout) => [layout.semanticWindowId, layout]));
|
|
539
|
+
const linkedBackings = new Set<string>();
|
|
540
|
+
for (const [index, link] of snapshot.windowLinks.links.entries()) {
|
|
541
|
+
linkedBackings.add(link.semanticWindowId);
|
|
542
|
+
const backing = backings.get(link.semanticWindowId);
|
|
543
|
+
if (!backing) {
|
|
544
|
+
context.addIssue({
|
|
545
|
+
code: "custom",
|
|
546
|
+
message: "Window links require a known backing layout",
|
|
547
|
+
path: ["windowLinks", "links", index, "semanticWindowId"],
|
|
548
|
+
});
|
|
549
|
+
} else if (link.linkId === snapshot.windowLinks.activeLinkId && !backing.currentWindow) {
|
|
550
|
+
context.addIssue({
|
|
551
|
+
code: "custom",
|
|
552
|
+
message: "The current backing must own the active window link",
|
|
553
|
+
path: ["windowLinks", "activeLinkId"],
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
for (const [index, layout] of snapshot.layouts.entries()) {
|
|
558
|
+
if (layout.semanticWindowId !== null && !linkedBackings.has(layout.semanticWindowId)) {
|
|
559
|
+
context.addIssue({
|
|
560
|
+
code: "custom",
|
|
561
|
+
message: "Every backing layout requires a window link",
|
|
562
|
+
path: ["layouts", index, "semanticWindowId"],
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
export const PaneStreamLayoutSnapshotFrameSchemaZ = PaneStreamLayoutSnapshotV2FrameSchemaZ;
|
|
568
|
+
export type PaneStreamLayoutSnapshotV2Frame = z.infer<
|
|
569
|
+
typeof PaneStreamLayoutSnapshotV2FrameSchemaZ
|
|
570
|
+
>;
|
|
571
|
+
|
|
529
572
|
export const PaneStreamFlowFrameSchemaZ = z
|
|
530
573
|
.object({
|
|
531
574
|
type: z.literal("flow"),
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
TerminalAttachmentSemanticPaneIdSchemaZ,
|
|
4
|
+
TerminalAttachmentSemanticWindowIdSchemaZ,
|
|
5
|
+
} from "./semantic-identity.ts";
|
|
6
|
+
import { WorkspaceCatalogLiveSessionIdSchemaZ } from "./workspace-catalog-resource.ts";
|
|
7
|
+
|
|
8
|
+
/** Stable registration identity. Never derive it from a socket path or native ID. */
|
|
9
|
+
export const TmuxServerIdSchemaZ = z.string().regex(/^tmux-server\.[a-f0-9]{32}$/u);
|
|
10
|
+
/** Fresh live authority incarnation, independently retired for each server owner. */
|
|
11
|
+
export const TmuxServerGenerationSchemaZ = z.uuid();
|
|
12
|
+
export const TmuxServerScopeSchemaZ = z
|
|
13
|
+
.object({ serverId: TmuxServerIdSchemaZ, generation: TmuxServerGenerationSchemaZ })
|
|
14
|
+
.strict();
|
|
15
|
+
|
|
16
|
+
const descriptorShape = {
|
|
17
|
+
serverId: TmuxServerIdSchemaZ,
|
|
18
|
+
label: z
|
|
19
|
+
.string()
|
|
20
|
+
.trim()
|
|
21
|
+
.min(1)
|
|
22
|
+
.max(160)
|
|
23
|
+
.refine(
|
|
24
|
+
(value) =>
|
|
25
|
+
[...value].every(
|
|
26
|
+
(character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127,
|
|
27
|
+
),
|
|
28
|
+
"server label contains control characters",
|
|
29
|
+
),
|
|
30
|
+
};
|
|
31
|
+
export const TmuxServerDescriptorSchemaZ = z.discriminatedUnion("state", [
|
|
32
|
+
z
|
|
33
|
+
.object({
|
|
34
|
+
...descriptorShape,
|
|
35
|
+
state: z.literal("online"),
|
|
36
|
+
generation: TmuxServerGenerationSchemaZ,
|
|
37
|
+
})
|
|
38
|
+
.strict(),
|
|
39
|
+
z.object({ ...descriptorShape, state: z.literal("offline"), generation: z.null() }).strict(),
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
/** Explicitly versioned endpoint; this is not an additive field on legacy wire resources. */
|
|
43
|
+
export const TMUX_SERVERS_API_PATH = "/api/v1/tmux-servers" as const;
|
|
44
|
+
export const TmuxServersResourceSchemaZ = z
|
|
45
|
+
.object({
|
|
46
|
+
version: z.literal(1),
|
|
47
|
+
servers: z.array(TmuxServerDescriptorSchemaZ),
|
|
48
|
+
})
|
|
49
|
+
.strict()
|
|
50
|
+
.superRefine((resource, context) => {
|
|
51
|
+
const seen = new Set<string>();
|
|
52
|
+
for (const [index, server] of resource.servers.entries()) {
|
|
53
|
+
if (seen.has(server.serverId))
|
|
54
|
+
context.addIssue({
|
|
55
|
+
code: "custom",
|
|
56
|
+
path: ["servers", index, "serverId"],
|
|
57
|
+
message: "Server registrations must be unique",
|
|
58
|
+
});
|
|
59
|
+
seen.add(server.serverId);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
export const TmuxServerSessionTargetSchemaZ = z
|
|
63
|
+
.object({
|
|
64
|
+
server: TmuxServerScopeSchemaZ,
|
|
65
|
+
liveSessionId: WorkspaceCatalogLiveSessionIdSchemaZ,
|
|
66
|
+
})
|
|
67
|
+
.strict();
|
|
68
|
+
export const TmuxServerWindowTargetSchemaZ = TmuxServerSessionTargetSchemaZ.extend({
|
|
69
|
+
semanticWindowId: TerminalAttachmentSemanticWindowIdSchemaZ,
|
|
70
|
+
});
|
|
71
|
+
export const TmuxServerPaneTargetSchemaZ = TmuxServerSessionTargetSchemaZ.extend({
|
|
72
|
+
semanticPaneId: TerminalAttachmentSemanticPaneIdSchemaZ,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export type TmuxServerId = z.infer<typeof TmuxServerIdSchemaZ>;
|
|
76
|
+
export type TmuxServerGeneration = z.infer<typeof TmuxServerGenerationSchemaZ>;
|
|
77
|
+
export type TmuxServerScope = z.infer<typeof TmuxServerScopeSchemaZ>;
|
|
78
|
+
export type TmuxServerDescriptor = z.infer<typeof TmuxServerDescriptorSchemaZ>;
|
|
79
|
+
export type TmuxServersResource = z.infer<typeof TmuxServersResourceSchemaZ>;
|
|
80
|
+
export type TmuxServerSessionTarget = z.infer<typeof TmuxServerSessionTargetSchemaZ>;
|
|
81
|
+
export type TmuxServerWindowTarget = z.infer<typeof TmuxServerWindowTargetSchemaZ>;
|
|
82
|
+
export type TmuxServerPaneTarget = z.infer<typeof TmuxServerPaneTargetSchemaZ>;
|
|
83
|
+
|
|
84
|
+
export type TmuxServerScopeResolution =
|
|
85
|
+
| { status: "matched"; scope: TmuxServerScope }
|
|
86
|
+
| { status: "not-found" | "offline" | "stale-generation" | "ambiguous" };
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Legacy adaptation considers every registration, including offline ones. Losing
|
|
90
|
+
* one connection must not redirect an old unscoped request to a different server.
|
|
91
|
+
* Explicit callers never fall back when their registration or incarnation is gone.
|
|
92
|
+
*/
|
|
93
|
+
export function resolveTmuxServerScope(
|
|
94
|
+
registrations: readonly TmuxServerDescriptor[],
|
|
95
|
+
requested?: TmuxServerScope,
|
|
96
|
+
): TmuxServerScopeResolution {
|
|
97
|
+
const candidates = requested
|
|
98
|
+
? registrations.filter((entry) => entry.serverId === requested.serverId)
|
|
99
|
+
: registrations;
|
|
100
|
+
if (candidates.length === 0) return { status: "not-found" };
|
|
101
|
+
if (candidates.length !== 1) return { status: "ambiguous" };
|
|
102
|
+
const candidate = candidates[0]!;
|
|
103
|
+
if (candidate.state === "offline") return { status: "offline" };
|
|
104
|
+
if (requested && requested.generation !== candidate.generation) {
|
|
105
|
+
return { status: "stale-generation" };
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
status: "matched",
|
|
109
|
+
scope: { serverId: candidate.serverId, generation: candidate.generation },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Namespaces copied stamps/native IDs and retires keys on authority replacement. */
|
|
114
|
+
export function tmuxServerScopedResourceKey(
|
|
115
|
+
scope: TmuxServerScope,
|
|
116
|
+
kind: string,
|
|
117
|
+
resourceId: string,
|
|
118
|
+
): string {
|
|
119
|
+
return JSON.stringify([scope.serverId, scope.generation, kind, resourceId]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Server and generation are part of the upgrade address, never query authority. */
|
|
123
|
+
export function tmuxServerPaneStreamPath(scope: TmuxServerScope): string {
|
|
124
|
+
const parsed = TmuxServerScopeSchemaZ.parse(scope);
|
|
125
|
+
return `/v2/tmux-servers/${parsed.serverId}/${parsed.generation}/pane-streams/redeem`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function isTmuxServerPaneStreamPath(path: string): boolean {
|
|
129
|
+
const match = /^\/v2\/tmux-servers\/([^/]+)\/([^/]+)\/pane-streams\/redeem$/u.exec(path);
|
|
130
|
+
if (!match) return false;
|
|
131
|
+
return TmuxServerScopeSchemaZ.safeParse({ serverId: match[1], generation: match[2] }).success;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const TmuxServerSessionsResourceSchemaZ = z
|
|
135
|
+
.object({
|
|
136
|
+
version: z.literal(1),
|
|
137
|
+
server: TmuxServerScopeSchemaZ,
|
|
138
|
+
sessions: z.array(
|
|
139
|
+
z
|
|
140
|
+
.object({
|
|
141
|
+
liveSessionId: WorkspaceCatalogLiveSessionIdSchemaZ,
|
|
142
|
+
sessionName: z.string().min(1).max(160),
|
|
143
|
+
workspaceName: z.string().min(1).max(160).nullable(),
|
|
144
|
+
paneCount: z.number().int().nonnegative(),
|
|
145
|
+
})
|
|
146
|
+
.strict(),
|
|
147
|
+
),
|
|
148
|
+
})
|
|
149
|
+
.strict();
|
|
150
|
+
|
|
151
|
+
/** Selector intent is interpreted only by the daemon on its own host. */
|
|
152
|
+
export const TmuxServerRegistrationRequestSchemaZ = z
|
|
153
|
+
.object({
|
|
154
|
+
label: descriptorShape.label.max(128),
|
|
155
|
+
selector: z.discriminatedUnion("kind", [
|
|
156
|
+
z
|
|
157
|
+
.object({
|
|
158
|
+
kind: z.literal("path"),
|
|
159
|
+
path: z
|
|
160
|
+
.string()
|
|
161
|
+
.min(1)
|
|
162
|
+
.max(4096)
|
|
163
|
+
.refine((path) => path.startsWith("/") && !/[\0\r\n]/u.test(path)),
|
|
164
|
+
})
|
|
165
|
+
.strict(),
|
|
166
|
+
z
|
|
167
|
+
.object({ kind: z.literal("name"), name: z.string().regex(/^[A-Za-z0-9_.-]{1,128}$/u) })
|
|
168
|
+
.strict(),
|
|
169
|
+
]),
|
|
170
|
+
})
|
|
171
|
+
.strict();
|
|
172
|
+
export type TmuxServerRegistrationRequest = z.infer<typeof TmuxServerRegistrationRequestSchemaZ>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { TerminalAttachmentSemanticWindowIdSchemaZ } from "./semantic-identity.ts";
|
|
3
|
+
import { WorkspaceCatalogLiveSessionIdSchemaZ } from "./workspace-catalog-resource.ts";
|
|
4
|
+
|
|
5
|
+
/** Schema bound, not a measured runtime resource budget. */
|
|
6
|
+
export const WINDOW_LINK_MAX_LINKS = 256;
|
|
7
|
+
|
|
8
|
+
/** Random daemon/session-generation-scoped handle; never a native index or ID. */
|
|
9
|
+
export const WindowLinkIdSchemaZ = z.string().regex(/^window-link\.[a-f0-9]{32}$/u);
|
|
10
|
+
export const WindowLinkRevisionSchemaZ = z
|
|
11
|
+
.number()
|
|
12
|
+
.int()
|
|
13
|
+
.nonnegative()
|
|
14
|
+
.max(Number.MAX_SAFE_INTEGER);
|
|
15
|
+
|
|
16
|
+
/** Shape validation only: the authority must validate session, revision and backing. */
|
|
17
|
+
export const WindowLinkTargetSchemaZ = z
|
|
18
|
+
.object({
|
|
19
|
+
liveSessionId: WorkspaceCatalogLiveSessionIdSchemaZ,
|
|
20
|
+
linkId: WindowLinkIdSchemaZ,
|
|
21
|
+
expectedSemanticWindowId: TerminalAttachmentSemanticWindowIdSchemaZ,
|
|
22
|
+
linkRevision: WindowLinkRevisionSchemaZ,
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
|
|
26
|
+
export const WindowLinkObservationSchemaZ = z
|
|
27
|
+
.object({
|
|
28
|
+
linkId: WindowLinkIdSchemaZ,
|
|
29
|
+
semanticWindowId: TerminalAttachmentSemanticWindowIdSchemaZ,
|
|
30
|
+
/** Display only. Mutations resolve the opaque handle under live authority. */
|
|
31
|
+
displayIndex: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
32
|
+
})
|
|
33
|
+
.strict();
|
|
34
|
+
|
|
35
|
+
export const WindowLinkTopologySchemaZ = z
|
|
36
|
+
.object({
|
|
37
|
+
liveSessionId: WorkspaceCatalogLiveSessionIdSchemaZ,
|
|
38
|
+
linkRevision: WindowLinkRevisionSchemaZ,
|
|
39
|
+
activeLinkId: WindowLinkIdSchemaZ,
|
|
40
|
+
links: z.array(WindowLinkObservationSchemaZ).min(1).max(WINDOW_LINK_MAX_LINKS),
|
|
41
|
+
})
|
|
42
|
+
.strict()
|
|
43
|
+
.superRefine((topology, context) => {
|
|
44
|
+
const ids = new Set<string>();
|
|
45
|
+
const indexes = new Set<number>();
|
|
46
|
+
for (const [index, link] of topology.links.entries()) {
|
|
47
|
+
if (ids.has(link.linkId)) {
|
|
48
|
+
context.addIssue({
|
|
49
|
+
code: "custom",
|
|
50
|
+
message: "Window link handles must be unique",
|
|
51
|
+
path: ["links", index, "linkId"],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (indexes.has(link.displayIndex)) {
|
|
55
|
+
context.addIssue({
|
|
56
|
+
code: "custom",
|
|
57
|
+
message: "Window link display indexes must be unique",
|
|
58
|
+
path: ["links", index, "displayIndex"],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
ids.add(link.linkId);
|
|
62
|
+
indexes.add(link.displayIndex);
|
|
63
|
+
}
|
|
64
|
+
if (!ids.has(topology.activeLinkId)) {
|
|
65
|
+
context.addIssue({
|
|
66
|
+
code: "custom",
|
|
67
|
+
message: "The active window link must be present",
|
|
68
|
+
path: ["activeLinkId"],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export type WindowLinkId = z.infer<typeof WindowLinkIdSchemaZ>;
|
|
74
|
+
export type WindowLinkTarget = z.infer<typeof WindowLinkTargetSchemaZ>;
|
|
75
|
+
export type WindowLinkObservation = z.infer<typeof WindowLinkObservationSchemaZ>;
|
|
76
|
+
export type WindowLinkTopology = z.infer<typeof WindowLinkTopologySchemaZ>;
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
WorkspacePaneDisplayTitleSchemaZ,
|
|
28
28
|
} from "./workspace-pane-creation.ts";
|
|
29
29
|
import { AuthoredInteractionOriginSchemaZ } from "./interaction-receipts.ts";
|
|
30
|
+
import { WindowLinkTargetSchemaZ } from "./window-links.ts";
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* A tmux window named either by its own durable stamp or by a pane inside it.
|
|
@@ -130,10 +131,40 @@ export type WorkspacePaneZoomToggleArguments = z.infer<
|
|
|
130
131
|
>;
|
|
131
132
|
|
|
132
133
|
export const WorkspacePaneSelectArgumentsSchemaZ = WorkspaceScopedSchemaZ.extend({
|
|
134
|
+
windowLink: WindowLinkTargetSchemaZ.optional(),
|
|
133
135
|
semanticPaneId: TerminalAttachmentSemanticPaneIdSchemaZ,
|
|
134
136
|
}).strict();
|
|
135
137
|
export type WorkspacePaneSelectArguments = z.infer<typeof WorkspacePaneSelectArgumentsSchemaZ>;
|
|
136
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Link-aware arguments use opaque observed authority.
|
|
141
|
+
* Execution requires daemon/session generation fencing and
|
|
142
|
+
* execution-time native link guards. These shapes carry no executable target.
|
|
143
|
+
*/
|
|
144
|
+
export const WorkspaceWindowLinkSelectArgumentsSchemaZ = WorkspaceScopedSchemaZ.extend({
|
|
145
|
+
target: WindowLinkTargetSchemaZ,
|
|
146
|
+
}).strict();
|
|
147
|
+
export type WorkspaceWindowLinkSelectArguments = z.infer<
|
|
148
|
+
typeof WorkspaceWindowLinkSelectArgumentsSchemaZ
|
|
149
|
+
>;
|
|
150
|
+
|
|
151
|
+
/** Ordinary unlink semantics; no forced unlink or implicit backing destruction. */
|
|
152
|
+
export const WorkspaceWindowLinkUnlinkArgumentsSchemaZ = WorkspaceScopedSchemaZ.extend({
|
|
153
|
+
target: WindowLinkTargetSchemaZ,
|
|
154
|
+
}).strict();
|
|
155
|
+
export type WorkspaceWindowLinkUnlinkArguments = z.infer<
|
|
156
|
+
typeof WorkspaceWindowLinkUnlinkArgumentsSchemaZ
|
|
157
|
+
>;
|
|
158
|
+
|
|
159
|
+
/** Future explicit-link pane selection; runtime must verify pane/backing membership. */
|
|
160
|
+
export const WorkspacePaneSelectWithWindowLinkArgumentsSchemaZ =
|
|
161
|
+
WorkspacePaneSelectArgumentsSchemaZ.extend({
|
|
162
|
+
windowLink: WindowLinkTargetSchemaZ,
|
|
163
|
+
}).strict();
|
|
164
|
+
export type WorkspacePaneSelectWithWindowLinkArguments = z.infer<
|
|
165
|
+
typeof WorkspacePaneSelectWithWindowLinkArgumentsSchemaZ
|
|
166
|
+
>;
|
|
167
|
+
|
|
137
168
|
/**
|
|
138
169
|
* Deliver literal terminal input through daemon authority. The text exists only
|
|
139
170
|
* on the mutation request; neither the result nor the interaction receipt can
|
|
@@ -197,6 +228,12 @@ export type WorkspacePaneResizeArguments = z.infer<typeof WorkspacePaneResizeArg
|
|
|
197
228
|
|
|
198
229
|
/** Every multiplexer intent, discriminated by the route that carries it. */
|
|
199
230
|
export const WorkspaceMultiplexerIntentSchemaZ = z.discriminatedUnion("verb", [
|
|
231
|
+
WorkspaceWindowLinkSelectArgumentsSchemaZ.extend({
|
|
232
|
+
verb: z.literal("workspace.window.link.select"),
|
|
233
|
+
}).strict(),
|
|
234
|
+
WorkspaceWindowLinkUnlinkArgumentsSchemaZ.extend({
|
|
235
|
+
verb: z.literal("workspace.window.link.unlink"),
|
|
236
|
+
}).strict(),
|
|
200
237
|
WorkspaceWindowSplitArgumentsSchemaZ.extend({
|
|
201
238
|
verb: z.literal("workspace.window.split"),
|
|
202
239
|
}).strict(),
|
|
@@ -273,6 +310,21 @@ const MutationEnvelopeSchemaZ = z.object({
|
|
|
273
310
|
workspaceName: WorkspacePaneCreationWorkspaceNameSchemaZ,
|
|
274
311
|
});
|
|
275
312
|
|
|
313
|
+
export const WorkspaceWindowLinkSelectResultSchemaZ = MutationEnvelopeSchemaZ.extend({
|
|
314
|
+
verb: z.literal("workspace.window.link.select"),
|
|
315
|
+
target: WindowLinkTargetSchemaZ,
|
|
316
|
+
}).strict();
|
|
317
|
+
export type WorkspaceWindowLinkSelectResult = z.infer<
|
|
318
|
+
typeof WorkspaceWindowLinkSelectResultSchemaZ
|
|
319
|
+
>;
|
|
320
|
+
export const WorkspaceWindowLinkUnlinkResultSchemaZ = MutationEnvelopeSchemaZ.extend({
|
|
321
|
+
verb: z.literal("workspace.window.link.unlink"),
|
|
322
|
+
target: WindowLinkTargetSchemaZ,
|
|
323
|
+
}).strict();
|
|
324
|
+
export type WorkspaceWindowLinkUnlinkResult = z.infer<
|
|
325
|
+
typeof WorkspaceWindowLinkUnlinkResultSchemaZ
|
|
326
|
+
>;
|
|
327
|
+
|
|
276
328
|
export const WorkspaceWindowSplitResultSchemaZ = MutationEnvelopeSchemaZ.extend({
|
|
277
329
|
verb: z.literal("workspace.window.split"),
|
|
278
330
|
direction: WorkspaceSplitDirectionSchemaZ,
|
|
@@ -355,6 +407,8 @@ export const WorkspacePaneResizeResultSchemaZ = MutationEnvelopeSchemaZ.extend({
|
|
|
355
407
|
export type WorkspacePaneResizeResult = z.infer<typeof WorkspacePaneResizeResultSchemaZ>;
|
|
356
408
|
|
|
357
409
|
export const WorkspaceMultiplexerMutationResultSchemaZ = z.discriminatedUnion("verb", [
|
|
410
|
+
WorkspaceWindowLinkSelectResultSchemaZ,
|
|
411
|
+
WorkspaceWindowLinkUnlinkResultSchemaZ,
|
|
358
412
|
WorkspaceWindowSplitResultSchemaZ,
|
|
359
413
|
WorkspaceWindowKillResultSchemaZ,
|
|
360
414
|
WorkspacePaneKillResultSchemaZ,
|
|
@@ -121,6 +121,10 @@ export function interactionSummaryLabel(
|
|
|
121
121
|
): string {
|
|
122
122
|
const observed = phase === "observed";
|
|
123
123
|
switch (operationKind) {
|
|
124
|
+
case "workspace.window.link.select":
|
|
125
|
+
return observed ? "window link selected" : "select window link";
|
|
126
|
+
case "workspace.window.link.unlink":
|
|
127
|
+
return observed ? "window unlinked" : "unlink window";
|
|
124
128
|
case "workspace.window.split":
|
|
125
129
|
return `split ${summary.operationKind === operationKind ? summary.direction : "window"}`;
|
|
126
130
|
case "workspace.window.kill":
|
|
@@ -34,6 +34,12 @@ const ACTION_COMMAND_METADATA = {
|
|
|
34
34
|
"workspace.open.cancel": { label: "Cancel workspace handoff", category: "workspace" },
|
|
35
35
|
"workspace.promote": { label: "Promote live session to workspace", category: "workspace" },
|
|
36
36
|
"workspace.app-window.mutate": { label: "Mutate application window", category: "workspace" },
|
|
37
|
+
"workspace.window.link.select": { label: "Focus window link", category: "workspace" },
|
|
38
|
+
"workspace.window.link.unlink": {
|
|
39
|
+
label: "Unlink window",
|
|
40
|
+
category: "workspace",
|
|
41
|
+
dangerous: true,
|
|
42
|
+
},
|
|
37
43
|
"workspace.window.split": { label: "Split pane", category: "workspace" },
|
|
38
44
|
"workspace.window.kill": { label: "Close window", category: "workspace", dangerous: true },
|
|
39
45
|
"workspace.pane.kill": { label: "Close pane", category: "workspace", dangerous: true },
|
|
@@ -43,6 +43,8 @@ async function runVerb(verb, input, context, deps) {
|
|
|
43
43
|
function verbHandler(verb) {
|
|
44
44
|
return async (input, context = {}, deps = {}) => (await runVerb(verb, input, context, deps));
|
|
45
45
|
}
|
|
46
|
+
export const workspaceWindowLinkSelectHandler = verbHandler("workspace.window.link.select");
|
|
47
|
+
export const workspaceWindowLinkUnlinkHandler = verbHandler("workspace.window.link.unlink");
|
|
46
48
|
export const workspaceWindowSplitHandler = verbHandler("workspace.window.split");
|
|
47
49
|
export const workspaceWindowKillHandler = verbHandler("workspace.window.kill");
|
|
48
50
|
export const workspacePaneKillHandler = verbHandler("workspace.pane.kill");
|