tmux-ide 2.9.11 → 2.9.13
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 +6036 -3047
- package/package.json +1 -1
- package/packages/contracts/src/index.ts +6 -0
- package/packages/contracts/src/pane-stream.ts +77 -0
- package/packages/contracts/src/terminal-delivery-binary-chunk.ts +55 -0
- package/packages/contracts/src/terminal-delivery.ts +19 -3
- package/packages/contracts/src/terminal-graphics-compressed.ts +66 -0
- package/packages/contracts/src/terminal-graphics.ts +73 -0
- package/packages/contracts/src/terminal-projection.ts +153 -0
- package/packages/contracts/src/terminal-surfaces.ts +137 -0
- package/packages/core/src/fixtures/terminal-projection-vectors.json +585 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/terminal-delivery-binary.ts +160 -0
- package/packages/core/src/terminal-delivery.ts +377 -16
- package/packages/core/src/terminal-projection-vectors.mjs +82 -0
- package/packages/core/src/terminal-projection.ts +476 -0
- package/packages/core/src/terminal-replica.ts +34 -1
- package/packages/daemon/dist/command-center/resources/saved-machine-route.js +2 -0
- package/packages/daemon/dist/command-center/resources/saved-machine-watch.js +117 -0
- package/packages/daemon/dist/command-center/server.js +13 -0
- package/packages/daemon/dist/command-center/tmux-servers.js +9 -0
- package/packages/daemon/dist/lib/daemon-embed.js +71 -4
- package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +1 -0
- package/packages/daemon/dist/lib/native-surface-context.js +96 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +10 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
- package/packages/daemon/dist/server/native-surface-provider-upgrade.js +88 -0
- package/packages/daemon/dist/terminal/graphics/compress-chunk.js +40 -0
- package/packages/daemon/dist/terminal/graphics/local-image-owner.js +330 -0
- package/packages/daemon/dist/terminal/graphics/local-image-store.js +255 -0
- package/packages/daemon/dist/terminal/graphics/native-surface-demand.js +99 -0
- package/packages/daemon/dist/terminal/graphics/native-surface-provider-channel.js +162 -0
- package/packages/daemon/dist/terminal/graphics/native-surface-provider.js +275 -0
- package/packages/daemon/dist/terminal/graphics/native-surface-viewer.js +146 -0
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +3 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +32 -9
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +369 -16
- package/packages/daemon/dist/terminal/pane-stream/runtime.js +10 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +2 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +146 -16
- package/packages/daemon/dist/terminal/session-runtime/terminal-projection-delivery.js +32 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +3 -4
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +4 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +28 -3
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +13 -1
- package/packages/daemon/dist/tui/mirror/runtime/renderer-probe.js +195 -0
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -0
- package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +4 -0
- package/packages/daemon/src/command-center/resources/saved-machine-route.ts +4 -0
- package/packages/daemon/src/command-center/resources/saved-machine-watch.ts +132 -0
- package/packages/daemon/src/command-center/server.ts +18 -0
- package/packages/daemon/src/command-center/tmux-servers.ts +14 -0
- package/packages/daemon/src/lib/daemon-embed.ts +81 -3
- package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +3 -0
- package/packages/daemon/src/lib/native-surface-context.ts +123 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +12 -0
- package/packages/daemon/src/server/native-surface-provider-upgrade.ts +103 -0
- package/packages/daemon/src/terminal/graphics/compress-chunk.ts +49 -0
- package/packages/daemon/src/terminal/graphics/local-image-owner.ts +346 -0
- package/packages/daemon/src/terminal/graphics/local-image-store.ts +286 -0
- package/packages/daemon/src/terminal/graphics/native-surface-demand.ts +88 -0
- package/packages/daemon/src/terminal/graphics/native-surface-provider-channel.ts +161 -0
- package/packages/daemon/src/terminal/graphics/native-surface-provider.ts +308 -0
- package/packages/daemon/src/terminal/graphics/native-surface-viewer.ts +154 -0
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +5 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +31 -10
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +446 -15
- package/packages/daemon/src/terminal/pane-stream/runtime.ts +14 -1
- package/packages/daemon/src/terminal/session-runtime/registry.ts +2 -0
- package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +1 -1
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +216 -22
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-observation-identity.ts +7 -4
- package/packages/daemon/src/terminal/session-runtime/terminal-projection-delivery.ts +59 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +3 -4
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +26 -4
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +20 -1
- package/packages/daemon/src/tui/mirror/runtime/renderer-probe.ts +257 -0
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +15 -0
- package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +4 -0
- package/packages/daemon-client/src/pane-stream-client.ts +75 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NativeSurfaceLocationSchema } from "../lib/native-surface-context.js";
|
|
1
2
|
import { mountTmuxServerHistoryAnchorRoute } from "./tmux-server-history-anchor.js";
|
|
2
3
|
import { mountTmuxServerDiagnosticsRoute } from "./tmux-server-diagnostics.js";
|
|
3
4
|
import { WindowSplitLayoutUnavailable } from "../terminal/mirror/window-split-authority.js";
|
|
@@ -191,6 +192,14 @@ export function mountTmuxServerRoutes(app, options) {
|
|
|
191
192
|
}),
|
|
192
193
|
}));
|
|
193
194
|
}));
|
|
195
|
+
app.post(`${scoped}/native-surface-context`, route(async (c) => {
|
|
196
|
+
const server = scope(c);
|
|
197
|
+
const target = NativeSurfaceLocationSchema.parse(await boundedJson(c.req.raw, 8192));
|
|
198
|
+
c.req.raw.signal.throwIfAborted();
|
|
199
|
+
const context = await options.owners.withOwner(server, (owner) => owner.resolveNativeSurfaceContext(target, c.req.raw.signal));
|
|
200
|
+
c.req.raw.signal.throwIfAborted();
|
|
201
|
+
return c.json({ version: 1, server, context });
|
|
202
|
+
}));
|
|
194
203
|
app.get(`${scoped}/inventory/:workspaceName`, route(async (c) => {
|
|
195
204
|
const server = scope(c);
|
|
196
205
|
const workspaceName = workspaceSchema.parse(c.req.param("workspaceName"));
|
|
@@ -6,6 +6,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
+
import { createNativeSurfaceContextResolver } from "./native-surface-context.js";
|
|
10
|
+
import { NativeSurfaceProviderRegistry } from "../terminal/graphics/native-surface-provider.js";
|
|
9
11
|
import { TmuxServerVersionSchemaZ } from "@tmux-ide/contracts";
|
|
10
12
|
import { createNativeSplitCapabilityProbe, createNativeSplitReadiness, } from "./native-split-capability.js";
|
|
11
13
|
import { executeCanonicalSplitMutation } from "./canonical-split-mutation.js";
|
|
@@ -13,7 +15,8 @@ import { createGuardedNativeSplitResize } from "./guarded-native-split-resize.js
|
|
|
13
15
|
import { WindowLinkTargetSchemaZ } from "@tmux-ide/contracts";
|
|
14
16
|
import { WindowSplitLayoutUnavailable } from "../terminal/mirror/window-split-authority.js";
|
|
15
17
|
import { requireSupportedTmuxVersion } from "./tmux-version.js";
|
|
16
|
-
import { join } from "node:path";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
import { LocalImageOwner, cleanupLocalImageOwner } from "../terminal/graphics/local-image-owner.js";
|
|
17
20
|
import { createClaudeTeamMembershipReader } from "../terminal/attachments/claude-team-names.js";
|
|
18
21
|
import { PaneSourceDiscovery } from "./pane-source-discovery.js";
|
|
19
22
|
import { createBackgroundNativeCapture } from "./background-native-capture.js";
|
|
@@ -79,6 +82,7 @@ import { InteractionEvidenceAuthority } from "./interaction-evidence-authority.j
|
|
|
79
82
|
import { createNativeTerminalAttachmentRuntime, WorkspaceTerminalInventoryRuntime, } from "../terminal/attachments/native-runtime.js";
|
|
80
83
|
import { createTmuxAgentStatusProbe } from "../terminal/attachments/agent-status-probe.js";
|
|
81
84
|
import { attachTerminalAttachmentWebSocket, } from "../server/terminal-attachment-upgrade.js";
|
|
85
|
+
import { attachNativeSurfaceProvider } from "../server/native-surface-provider-upgrade.js";
|
|
82
86
|
import { attachPaneStreamWebSocket, } from "../server/pane-stream-upgrade.js";
|
|
83
87
|
import { createPaneStreamRuntime, } from "../terminal/pane-stream/runtime.js";
|
|
84
88
|
import { SessionRuntimeRegistry } from "../terminal/session-runtime/registry.js";
|
|
@@ -89,7 +93,7 @@ import { setActivationBackend } from "./active-projects.js";
|
|
|
89
93
|
import { readOrMintEnvironmentId } from "./environment-identity.js";
|
|
90
94
|
import { captureDaemonProvenance } from "./daemon-provenance.js";
|
|
91
95
|
import { registerLogSecret, setLogIdentity } from "./log.js";
|
|
92
|
-
import { canonicalDaemonUrl, clearCanonicalDaemonInfoIfOwned, clearCanonicalDaemonInfoIfUnchanged, inspectCanonicalDaemonInfo, isCanonicalDaemonAlive, isCanonicalDaemonRecordOwnerProvenDead, probeCanonicalDaemonHealth, probeCanonicalDaemonIdentity, releaseCanonicalDaemonClaim, tryAcquireCanonicalDaemonClaim, writeCanonicalDaemonInfo, matchesCanonicalDaemonPredecessor, } from "./canonical-daemon.js";
|
|
96
|
+
import { canonicalDaemonUrl, getCanonicalDaemonInfoPath, clearCanonicalDaemonInfoIfOwned, clearCanonicalDaemonInfoIfUnchanged, inspectCanonicalDaemonInfo, isCanonicalDaemonAlive, isCanonicalDaemonRecordOwnerProvenDead, probeCanonicalDaemonHealth, probeCanonicalDaemonIdentity, releaseCanonicalDaemonClaim, tryAcquireCanonicalDaemonClaim, writeCanonicalDaemonInfo, matchesCanonicalDaemonPredecessor, } from "./canonical-daemon.js";
|
|
93
97
|
const requireFromHere = createRequire(import.meta.url);
|
|
94
98
|
const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
95
99
|
const DEFAULT_GRACEFUL_MS = 2000;
|
|
@@ -542,6 +546,8 @@ async function startHttpServer({ sessionName, requestedPort, bindHostname, dir,
|
|
|
542
546
|
issue: (request, context) => getTerminalAttachmentRuntime().admission.issue(request, context),
|
|
543
547
|
},
|
|
544
548
|
paneStreamIssueBackend: paneStreamRuntime.coordinator,
|
|
549
|
+
nativeSurfaceProviderSupported: () => serverOwners.isDefaultCurrent() && paneStreamRuntime.nativeSurfaces !== undefined,
|
|
550
|
+
nativeSurfaceProviderAvailable: () => serverOwners.isDefaultCurrent() && paneStreamRuntime.nativeSurfaces?.available === true,
|
|
545
551
|
applicationShellInventoryBackend: terminalInventoryRuntime,
|
|
546
552
|
startupReadinessAttachmentBackend: terminalInventoryRuntime,
|
|
547
553
|
catalogLiveSessions,
|
|
@@ -585,7 +591,16 @@ async function startHttpServer({ sessionName, requestedPort, bindHostname, dir,
|
|
|
585
591
|
});
|
|
586
592
|
const terminalAttachmentBoundary = attachTerminalAttachmentWebSocket(server, (create) => (create ? getTerminalAttachmentRuntime() : peekTerminalAttachmentRuntime())?.admission ??
|
|
587
593
|
null);
|
|
588
|
-
const
|
|
594
|
+
const paneStreamSocketBoundary = attachPaneStreamWebSocket(server, paneStreamRuntime.coordinator, PANE_STREAM_REDEEM_PATH, () => serverOwners.isDefaultCurrent());
|
|
595
|
+
const nativeProviderBoundary = paneStreamRuntime.nativeSurfaces
|
|
596
|
+
? attachNativeSurfaceProvider(server, paneStreamRuntime.nativeSurfaces, localBypassToken ?? null, () => serverOwners.isDefaultCurrent())
|
|
597
|
+
: null;
|
|
598
|
+
const paneStreamBoundary = {
|
|
599
|
+
async close() {
|
|
600
|
+
nativeProviderBoundary?.close();
|
|
601
|
+
await paneStreamSocketBoundary.close();
|
|
602
|
+
},
|
|
603
|
+
};
|
|
589
604
|
serverOwners.attach(server);
|
|
590
605
|
try {
|
|
591
606
|
await new Promise((resolve, reject) => {
|
|
@@ -955,6 +970,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
955
970
|
};
|
|
956
971
|
let terminalAttachmentRuntime = null;
|
|
957
972
|
let paneStreamRuntime = null;
|
|
973
|
+
let localImages;
|
|
958
974
|
let serverOwners = null;
|
|
959
975
|
let defaultEditorContextRetired = false;
|
|
960
976
|
const defaultDiagnosticsLifetime = new AbortController();
|
|
@@ -1057,6 +1073,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1057
1073
|
externalInteractionObserver.setDiagnostics(diagnostics);
|
|
1058
1074
|
setFleetFactsObserverDiagnostics(diagnostics);
|
|
1059
1075
|
}
|
|
1076
|
+
const nativeSurfaces = process.platform === "darwin" ? new NativeSurfaceProviderRegistry(instanceId) : undefined;
|
|
1060
1077
|
sessionRuntimeRegistry = new SessionRuntimeRegistry({
|
|
1061
1078
|
generation: instanceId,
|
|
1062
1079
|
...(runtimeObservability ? { observability: runtimeObservability } : {}),
|
|
@@ -1083,6 +1100,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1083
1100
|
publishResourceChange: (change) => broadcastResourceChanged(change, instanceId),
|
|
1084
1101
|
},
|
|
1085
1102
|
mirror: {
|
|
1103
|
+
onNativeClientPresence: (session, present) => nativeSurfaces?.demand.presence(session, present),
|
|
1086
1104
|
splitLayoutEpoch: () => observationSelector?.nativeServerEpoch ?? null,
|
|
1087
1105
|
splitLayoutPrepare: createNativeSplitReadiness({
|
|
1088
1106
|
observation: () => observationSelector ?? null,
|
|
@@ -1219,7 +1237,36 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1219
1237
|
return terminalAttachmentRuntime;
|
|
1220
1238
|
};
|
|
1221
1239
|
const peekTerminalAttachmentRuntime = () => terminalAttachmentRuntime;
|
|
1240
|
+
// The held canonical claim has already proven and hardened this private root.
|
|
1241
|
+
// Local images are optional: unavailable storage retains inline graphics.
|
|
1242
|
+
if (process.platform !== "win32") {
|
|
1243
|
+
const root = dirname(getCanonicalDaemonInfoPath());
|
|
1244
|
+
try {
|
|
1245
|
+
if (existingCanonical.status === "valid") {
|
|
1246
|
+
await cleanupLocalImageOwner({
|
|
1247
|
+
root,
|
|
1248
|
+
owner: {
|
|
1249
|
+
generation: existingCanonical.info.instanceId,
|
|
1250
|
+
pid: existingCanonical.info.pid,
|
|
1251
|
+
startedAt: existingCanonical.info.startedAt,
|
|
1252
|
+
},
|
|
1253
|
+
proveDead: () => isCanonicalDaemonRecordOwnerProvenDead(existingCanonical),
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
localImages = await LocalImageOwner.create({
|
|
1257
|
+
root,
|
|
1258
|
+
generation: instanceId,
|
|
1259
|
+
pid: process.pid,
|
|
1260
|
+
startedAt,
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
catch {
|
|
1264
|
+
localImages = undefined;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1222
1267
|
paneStreamRuntime = createPaneStreamRuntime({
|
|
1268
|
+
nativeSurfaces,
|
|
1269
|
+
localImages,
|
|
1223
1270
|
daemonInstanceId: instanceId,
|
|
1224
1271
|
webSocketUrl: paneStreamWebSocketUrl(bindHostname, port),
|
|
1225
1272
|
sessionRuntimeRegistry,
|
|
@@ -1239,6 +1286,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1239
1286
|
});
|
|
1240
1287
|
await externalInteractionObserver.start();
|
|
1241
1288
|
serverOwners = await createEmbeddedTmuxServerOwners({
|
|
1289
|
+
localImages,
|
|
1242
1290
|
environmentId,
|
|
1243
1291
|
onDefaultScope: async (scope) => {
|
|
1244
1292
|
interactionEvidence = new InteractionEvidenceAuthority(environmentId, scope);
|
|
@@ -1322,6 +1370,20 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1322
1370
|
return grant ? interactionEvidence.captureSourceBinding(grant) : null;
|
|
1323
1371
|
},
|
|
1324
1372
|
interactionReceipts,
|
|
1373
|
+
resolveNativeSurfaceContext: createNativeSurfaceContextResolver({
|
|
1374
|
+
generation: instanceId,
|
|
1375
|
+
registry: workspaceRegistry,
|
|
1376
|
+
catalog: createNativeTmuxServerCatalog(workspaceRegistry, fleetFactsTmuxRunner),
|
|
1377
|
+
inventory: terminalInventoryRuntime,
|
|
1378
|
+
run: (args, signal) => fleetFactsTmuxRunner(args, signal),
|
|
1379
|
+
assertOpen: () => {
|
|
1380
|
+
if (defaultEditorContextRetired ||
|
|
1381
|
+
serverOwners?.defaultRetired ||
|
|
1382
|
+
!initialNativeServerIdentity ||
|
|
1383
|
+
terminalInventoryRuntime?.lifecycleState() !== "ready")
|
|
1384
|
+
throw new Error("Default native surface context owner is unavailable");
|
|
1385
|
+
},
|
|
1386
|
+
}),
|
|
1325
1387
|
resolvePaneEditorContext: createPaneEditorContextResolver({
|
|
1326
1388
|
generation: instanceId,
|
|
1327
1389
|
registry: workspaceRegistry,
|
|
@@ -1514,7 +1576,10 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1514
1576
|
// The pane-stream coordinator may still hold runtime consumers while it
|
|
1515
1577
|
// drains. Preserve the normal shutdown order on startup rollback too:
|
|
1516
1578
|
// transports first, then the one session/control authority.
|
|
1517
|
-
await Promise.allSettled([
|
|
1579
|
+
await Promise.allSettled([
|
|
1580
|
+
sessionRuntimeRegistry?.dispose() ?? Promise.resolve(),
|
|
1581
|
+
localImages?.dispose() ?? Promise.resolve(),
|
|
1582
|
+
]);
|
|
1518
1583
|
throw error;
|
|
1519
1584
|
}
|
|
1520
1585
|
const { server, sockets, closeClients, closeWsServers, terminalAttachmentBoundary, paneStreamBoundary, } = startedServer;
|
|
@@ -1535,6 +1600,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1535
1600
|
...(await retireTerminalAttachmentTransport(terminalAttachmentRuntime, terminalAttachmentBoundary)),
|
|
1536
1601
|
...(await retirePaneStreamTransport()),
|
|
1537
1602
|
];
|
|
1603
|
+
await localImages?.dispose();
|
|
1538
1604
|
terminalInventoryRuntime.dispose();
|
|
1539
1605
|
const paneDisposal = Promise.resolve().then(() => workspacePaneCreation.dispose());
|
|
1540
1606
|
const workspaceOpenDisposal = Promise.resolve().then(() => workspaceOpen.dispose());
|
|
@@ -1785,6 +1851,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1785
1851
|
// listener before touching the legacy WS or HTTP surfaces.
|
|
1786
1852
|
failures.push(...(await retireTerminalAttachmentTransport(terminalAttachmentRuntime, terminalAttachmentBoundary)));
|
|
1787
1853
|
failures.push(...(await retirePaneStreamTransport()));
|
|
1854
|
+
await capture(() => localImages?.dispose());
|
|
1788
1855
|
await capture(() => terminalInventoryRuntime.dispose());
|
|
1789
1856
|
await capture(() => workspacePaneCreation.dispose());
|
|
1790
1857
|
await capture(() => workspaceOpen.dispose());
|
|
@@ -58,6 +58,7 @@ export async function createEmbeddedTmuxServerOwners(options) {
|
|
|
58
58
|
await retireDefault();
|
|
59
59
|
const route = tmuxServerPaneStreamPath(scope);
|
|
60
60
|
const owner = await createNativeTmuxServerOwner({
|
|
61
|
+
localImages: options.localImages,
|
|
61
62
|
environmentId: options.environmentId,
|
|
62
63
|
...scope,
|
|
63
64
|
tmuxAuthority: observation.authority,
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { TerminalAttachmentSemanticPaneIdSchemaZ, WorkspaceCatalogLiveSessionIdSchemaZ, } from "@tmux-ide/contracts";
|
|
4
|
+
export const NativeSurfaceLocationSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
socketPath: z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.max(4096)
|
|
10
|
+
.refine((p) => isAbsolute(p) && !/[\0\r\n\t]/u.test(p)),
|
|
11
|
+
serverPid: z
|
|
12
|
+
.string()
|
|
13
|
+
.max(16)
|
|
14
|
+
.regex(/^[1-9][0-9]*$/u),
|
|
15
|
+
sessionId: z
|
|
16
|
+
.string()
|
|
17
|
+
.max(32)
|
|
18
|
+
.regex(/^\$(?:0|[1-9][0-9]*)$/u),
|
|
19
|
+
runtimePaneId: z
|
|
20
|
+
.string()
|
|
21
|
+
.max(32)
|
|
22
|
+
.regex(/^%(?:0|[1-9][0-9]*)$/u),
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
/** Resolve inherited tmux location only through the current generation's pinned owner. */
|
|
26
|
+
export function createNativeSurfaceContextResolver(options) {
|
|
27
|
+
const generation = z.uuid().parse(options.generation);
|
|
28
|
+
return async (input, cancellation) => {
|
|
29
|
+
const target = NativeSurfaceLocationSchema.parse(input);
|
|
30
|
+
const signal = AbortSignal.any([
|
|
31
|
+
AbortSignal.timeout(5000),
|
|
32
|
+
...(cancellation ? [cancellation] : []),
|
|
33
|
+
]);
|
|
34
|
+
const assertCurrent = () => {
|
|
35
|
+
options.assertOpen();
|
|
36
|
+
signal.throwIfAborted();
|
|
37
|
+
};
|
|
38
|
+
const unavailable = () => {
|
|
39
|
+
throw new Error("Native surface source unavailable");
|
|
40
|
+
};
|
|
41
|
+
const observe = async () => {
|
|
42
|
+
assertCurrent();
|
|
43
|
+
const proof = await options.run([
|
|
44
|
+
"display-message",
|
|
45
|
+
"-p",
|
|
46
|
+
"-t",
|
|
47
|
+
target.runtimePaneId,
|
|
48
|
+
"#{socket_path}\t#{pid}\t#{session_id}\t#{pane_id}",
|
|
49
|
+
], signal);
|
|
50
|
+
assertCurrent();
|
|
51
|
+
if (proof.trimEnd() !==
|
|
52
|
+
[target.socketPath, target.serverPid, target.sessionId, target.runtimePaneId].join("\t"))
|
|
53
|
+
unavailable();
|
|
54
|
+
const snapshot = await options.inventory.discoverTerminalInventory(signal);
|
|
55
|
+
assertCurrent();
|
|
56
|
+
if (snapshot.catalog.invalidRuntimeProof ||
|
|
57
|
+
snapshot.catalog.duplicateSemanticStamp ||
|
|
58
|
+
snapshot.catalog.duplicateRuntimePaneBinding)
|
|
59
|
+
unavailable();
|
|
60
|
+
const matches = snapshot.panes.filter((pane) => pane.runtimePaneId === target.runtimePaneId && pane.sessionId === target.sessionId);
|
|
61
|
+
if (matches.length !== 1)
|
|
62
|
+
return unavailable();
|
|
63
|
+
const pane = { ...matches[0] };
|
|
64
|
+
const semantic = TerminalAttachmentSemanticPaneIdSchemaZ.parse(pane.semanticPaneId);
|
|
65
|
+
const workspace = options.registry.get(pane.workspaceName);
|
|
66
|
+
if (!workspace || workspace.sessionName !== pane.sessionName)
|
|
67
|
+
return unavailable();
|
|
68
|
+
const sessions = (await options.catalog()).filter((row) => row.sessionName === pane.sessionName);
|
|
69
|
+
assertCurrent();
|
|
70
|
+
if (sessions.length !== 1 || options.registry.get(pane.workspaceName) !== workspace)
|
|
71
|
+
return unavailable();
|
|
72
|
+
const live = WorkspaceCatalogLiveSessionIdSchemaZ.parse(sessions[0].liveSessionId);
|
|
73
|
+
return {
|
|
74
|
+
context: {
|
|
75
|
+
generation,
|
|
76
|
+
workspaceName: pane.workspaceName,
|
|
77
|
+
liveSessionId: live,
|
|
78
|
+
pane: semantic,
|
|
79
|
+
},
|
|
80
|
+
identity: [
|
|
81
|
+
pane.sessionId,
|
|
82
|
+
pane.windowId,
|
|
83
|
+
pane.runtimePaneId,
|
|
84
|
+
pane.windowStamp ?? null,
|
|
85
|
+
pane.nativePaneBirthId ?? null,
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const before = await observe();
|
|
90
|
+
const after = await observe();
|
|
91
|
+
if (JSON.stringify(before) !== JSON.stringify(after))
|
|
92
|
+
return unavailable();
|
|
93
|
+
assertCurrent();
|
|
94
|
+
return Object.freeze(after.context);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createNativeSurfaceContextResolver } from "./native-surface-context.js";
|
|
1
2
|
import { shellEscape } from "./shell.js";
|
|
2
3
|
import { createNativeSplitCapabilityProbe, createNativeSplitReadiness, } from "./native-split-capability.js";
|
|
3
4
|
import { executeCanonicalSplitMutation } from "./canonical-split-mutation.js";
|
|
@@ -369,6 +370,7 @@ export async function createNativeTmuxServerOwner(options) {
|
|
|
369
370
|
}),
|
|
370
371
|
});
|
|
371
372
|
const paneStreamRuntime = createPaneStreamRuntime({
|
|
373
|
+
localImages: options.localImages,
|
|
372
374
|
daemonInstanceId: generation,
|
|
373
375
|
webSocketUrl: options.webSocketUrl,
|
|
374
376
|
sessionRuntimeRegistry,
|
|
@@ -494,6 +496,14 @@ export async function createNativeTmuxServerOwner(options) {
|
|
|
494
496
|
},
|
|
495
497
|
serverId: options.serverId,
|
|
496
498
|
generation,
|
|
499
|
+
resolveNativeSurfaceContext: createNativeSurfaceContextResolver({
|
|
500
|
+
generation,
|
|
501
|
+
assertOpen,
|
|
502
|
+
registry: workspaceRegistry,
|
|
503
|
+
catalog,
|
|
504
|
+
inventory: terminalInventoryRuntime,
|
|
505
|
+
run: (args, signal) => generationRunAsync(args, signal),
|
|
506
|
+
}),
|
|
497
507
|
resolvePaneEditorContext,
|
|
498
508
|
catalog,
|
|
499
509
|
readWindowSplitLayout: async (workspaceName, input) => {
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
<key>CFBundlePackageType</key>
|
|
22
22
|
<string>APPL</string>
|
|
23
23
|
<key>CFBundleShortVersionString</key>
|
|
24
|
-
<string>2.9.
|
|
24
|
+
<string>2.9.13</string>
|
|
25
25
|
<key>CFBundleVersion</key>
|
|
26
|
-
<string>2.9.
|
|
26
|
+
<string>2.9.13</string>
|
|
27
27
|
<key>LSMinimumSystemVersion</key>
|
|
28
28
|
<string>11.0</string>
|
|
29
29
|
<key>LSUIElement</key>
|
|
Binary file
|
|
Binary file
|
package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</data>
|
|
11
11
|
<key>Resources/Assets.car</key>
|
|
12
12
|
<data>
|
|
13
|
-
|
|
13
|
+
6Tstp1dls1WRUQD+/DDuIhts8/g=
|
|
14
14
|
</data>
|
|
15
15
|
</dict>
|
|
16
16
|
<key>files2</key>
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<dict>
|
|
27
27
|
<key>hash2</key>
|
|
28
28
|
<data>
|
|
29
|
-
|
|
29
|
+
5CnAgYS5jeHfuf40WhEyRn9BbniU54houDrGdNEkNyI=
|
|
30
30
|
</data>
|
|
31
31
|
</dict>
|
|
32
32
|
</dict>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
2
|
+
import { TERMINAL_SURFACE_MAX_CONTROL_BYTES } from "@tmux-ide/contracts";
|
|
3
|
+
import { ownerBearerMatches } from "../command-center/owner-authority.js";
|
|
4
|
+
import { NativeSurfaceProviderChannel } from "../terminal/graphics/native-surface-provider-channel.js";
|
|
5
|
+
export const NATIVE_SURFACE_PROVIDER_PATH = "/v1/terminal/native-surface-provider";
|
|
6
|
+
const PROTOCOL = "tmux-ide-native-surface-provider-v1";
|
|
7
|
+
/** Separate owner-authenticated producer authority. Loopback, PID and Origin do
|
|
8
|
+
* not grant access. At most one connection (including registration) is admitted.
|
|
9
|
+
* Native kernel proof remains required independently of this control channel. */
|
|
10
|
+
export function attachNativeSurfaceProvider(server, registry, ownerToken, isCurrent) {
|
|
11
|
+
const wss = new WebSocketServer({
|
|
12
|
+
noServer: true,
|
|
13
|
+
clientTracking: false,
|
|
14
|
+
perMessageDeflate: false,
|
|
15
|
+
maxPayload: TERMINAL_SURFACE_MAX_CONTROL_BYTES,
|
|
16
|
+
handleProtocols: (values) => (values.size === 1 && values.has(PROTOCOL) ? PROTOCOL : false),
|
|
17
|
+
});
|
|
18
|
+
let active = null;
|
|
19
|
+
let channel = null;
|
|
20
|
+
let closed = false;
|
|
21
|
+
const upgrade = (request, socket, head) => {
|
|
22
|
+
if (request.url?.split("?", 1)[0] !== NATIVE_SURFACE_PROVIDER_PATH)
|
|
23
|
+
return;
|
|
24
|
+
const headers = (name) => request.rawHeaders.filter((_, i) => i % 2 === 0).filter((key) => key.toLowerCase() === name)
|
|
25
|
+
.length;
|
|
26
|
+
const authorized = headers("authorization") === 1 &&
|
|
27
|
+
ownerBearerMatches(request.headers.authorization, ownerToken);
|
|
28
|
+
const status = !authorized
|
|
29
|
+
? 403
|
|
30
|
+
: closed || !isCurrent()
|
|
31
|
+
? 410
|
|
32
|
+
: active || registry.available
|
|
33
|
+
? 409
|
|
34
|
+
: request.url !== NATIVE_SURFACE_PROVIDER_PATH ||
|
|
35
|
+
headers("sec-websocket-protocol") !== 1 ||
|
|
36
|
+
request.headers["sec-websocket-protocol"] !== PROTOCOL
|
|
37
|
+
? 400
|
|
38
|
+
: 0;
|
|
39
|
+
if (status) {
|
|
40
|
+
socket.end(`HTTP/1.1 ${status} Rejected\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
44
|
+
active = ws;
|
|
45
|
+
const owner = new NativeSurfaceProviderChannel(registry, (message) => {
|
|
46
|
+
if (!isCurrent() ||
|
|
47
|
+
ws.readyState !== WebSocket.OPEN ||
|
|
48
|
+
ws.bufferedAmount + Buffer.byteLength(message) > 64 * 1024)
|
|
49
|
+
return false;
|
|
50
|
+
ws.send(message, (error) => {
|
|
51
|
+
if (error)
|
|
52
|
+
owner.close();
|
|
53
|
+
});
|
|
54
|
+
return true;
|
|
55
|
+
}, () => ws.terminate());
|
|
56
|
+
channel = owner;
|
|
57
|
+
const timer = setTimeout(() => owner.close(), 5000);
|
|
58
|
+
timer.unref();
|
|
59
|
+
ws.on("message", (data, binary) => {
|
|
60
|
+
if (binary || !isCurrent()) {
|
|
61
|
+
owner.close();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
owner.receive(data.toString());
|
|
65
|
+
if (registry.available)
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
});
|
|
68
|
+
ws.on("error", () => owner.close());
|
|
69
|
+
ws.on("close", () => {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
owner.close();
|
|
72
|
+
if (active === ws) {
|
|
73
|
+
active = null;
|
|
74
|
+
channel = null;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
server.on("upgrade", upgrade);
|
|
80
|
+
return {
|
|
81
|
+
close: () => {
|
|
82
|
+
closed = true;
|
|
83
|
+
server.off("upgrade", upgrade);
|
|
84
|
+
channel?.close();
|
|
85
|
+
wss.close();
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { deflate } from "node:zlib";
|
|
2
|
+
import { encodeTerminalGraphicsCompressed, TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES, } from "@tmux-ide/contracts";
|
|
3
|
+
// Across panes, windows and connections: no queued compression backlog and at
|
|
4
|
+
// most two chunk inputs/outputs retained by libuv. Never compress on the JS loop.
|
|
5
|
+
let active = 0;
|
|
6
|
+
// Prefer flat-color UI chunks. A bounded, spread-out sample avoids spending a
|
|
7
|
+
// libuv job on noisy frames. False negatives only choose the raw fallback.
|
|
8
|
+
function likelyCompressible(bytes) {
|
|
9
|
+
const counts = new Uint16Array(256);
|
|
10
|
+
const samples = 4096;
|
|
11
|
+
for (let i = 0; i < samples; i++) {
|
|
12
|
+
const value = bytes[Math.floor((i * (bytes.length - 1)) / (samples - 1))];
|
|
13
|
+
if (++counts[value] >= samples / 32)
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
export async function compressGraphicsChunk(header, bytes) {
|
|
19
|
+
if (active >= 2 ||
|
|
20
|
+
bytes.length < 4096 ||
|
|
21
|
+
bytes.length > TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES ||
|
|
22
|
+
(header.blob.format !== "rgb" && header.blob.format !== "rgba"))
|
|
23
|
+
return null;
|
|
24
|
+
if (!likelyCompressible(bytes))
|
|
25
|
+
return null;
|
|
26
|
+
active++;
|
|
27
|
+
try {
|
|
28
|
+
const encoded = await new Promise((resolve) => {
|
|
29
|
+
deflate(bytes, { level: 1 }, (error, result) => resolve(error ? null : result));
|
|
30
|
+
});
|
|
31
|
+
// Savings must cover metadata and be material; incompressible images retain
|
|
32
|
+
// the original binary path. PNG/producer-zlib are never recompressed.
|
|
33
|
+
if (!encoded || encoded.length + 128 > bytes.length * 0.875)
|
|
34
|
+
return null;
|
|
35
|
+
return encodeTerminalGraphicsCompressed({ ...header, compression: "zlib", decodedBytes: bytes.length }, encoded);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
active--;
|
|
39
|
+
}
|
|
40
|
+
}
|