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.
Files changed (84) hide show
  1. package/bin/cli.js +6036 -3047
  2. package/package.json +1 -1
  3. package/packages/contracts/src/index.ts +6 -0
  4. package/packages/contracts/src/pane-stream.ts +77 -0
  5. package/packages/contracts/src/terminal-delivery-binary-chunk.ts +55 -0
  6. package/packages/contracts/src/terminal-delivery.ts +19 -3
  7. package/packages/contracts/src/terminal-graphics-compressed.ts +66 -0
  8. package/packages/contracts/src/terminal-graphics.ts +73 -0
  9. package/packages/contracts/src/terminal-projection.ts +153 -0
  10. package/packages/contracts/src/terminal-surfaces.ts +137 -0
  11. package/packages/core/src/fixtures/terminal-projection-vectors.json +585 -0
  12. package/packages/core/src/index.ts +2 -0
  13. package/packages/core/src/terminal-delivery-binary.ts +160 -0
  14. package/packages/core/src/terminal-delivery.ts +377 -16
  15. package/packages/core/src/terminal-projection-vectors.mjs +82 -0
  16. package/packages/core/src/terminal-projection.ts +476 -0
  17. package/packages/core/src/terminal-replica.ts +34 -1
  18. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +2 -0
  19. package/packages/daemon/dist/command-center/resources/saved-machine-watch.js +117 -0
  20. package/packages/daemon/dist/command-center/server.js +13 -0
  21. package/packages/daemon/dist/command-center/tmux-servers.js +9 -0
  22. package/packages/daemon/dist/lib/daemon-embed.js +71 -4
  23. package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +1 -0
  24. package/packages/daemon/dist/lib/native-surface-context.js +96 -0
  25. package/packages/daemon/dist/lib/tmux-server-owner.js +10 -0
  26. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  27. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  28. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  29. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  30. package/packages/daemon/dist/server/native-surface-provider-upgrade.js +88 -0
  31. package/packages/daemon/dist/terminal/graphics/compress-chunk.js +40 -0
  32. package/packages/daemon/dist/terminal/graphics/local-image-owner.js +330 -0
  33. package/packages/daemon/dist/terminal/graphics/local-image-store.js +255 -0
  34. package/packages/daemon/dist/terminal/graphics/native-surface-demand.js +99 -0
  35. package/packages/daemon/dist/terminal/graphics/native-surface-provider-channel.js +162 -0
  36. package/packages/daemon/dist/terminal/graphics/native-surface-provider.js +275 -0
  37. package/packages/daemon/dist/terminal/graphics/native-surface-viewer.js +146 -0
  38. package/packages/daemon/dist/terminal/mirror/mirror-service.js +3 -0
  39. package/packages/daemon/dist/terminal/mirror/session-channel.js +32 -9
  40. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +369 -16
  41. package/packages/daemon/dist/terminal/pane-stream/runtime.js +10 -0
  42. package/packages/daemon/dist/terminal/session-runtime/registry.js +2 -0
  43. package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +146 -16
  44. package/packages/daemon/dist/terminal/session-runtime/terminal-projection-delivery.js +32 -0
  45. package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +3 -4
  46. package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +4 -0
  47. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +28 -3
  48. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +13 -1
  49. package/packages/daemon/dist/tui/mirror/runtime/renderer-probe.js +195 -0
  50. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -0
  51. package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +4 -0
  52. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +4 -0
  53. package/packages/daemon/src/command-center/resources/saved-machine-watch.ts +132 -0
  54. package/packages/daemon/src/command-center/server.ts +18 -0
  55. package/packages/daemon/src/command-center/tmux-servers.ts +14 -0
  56. package/packages/daemon/src/lib/daemon-embed.ts +81 -3
  57. package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +3 -0
  58. package/packages/daemon/src/lib/native-surface-context.ts +123 -0
  59. package/packages/daemon/src/lib/tmux-server-owner.ts +12 -0
  60. package/packages/daemon/src/server/native-surface-provider-upgrade.ts +103 -0
  61. package/packages/daemon/src/terminal/graphics/compress-chunk.ts +49 -0
  62. package/packages/daemon/src/terminal/graphics/local-image-owner.ts +346 -0
  63. package/packages/daemon/src/terminal/graphics/local-image-store.ts +286 -0
  64. package/packages/daemon/src/terminal/graphics/native-surface-demand.ts +88 -0
  65. package/packages/daemon/src/terminal/graphics/native-surface-provider-channel.ts +161 -0
  66. package/packages/daemon/src/terminal/graphics/native-surface-provider.ts +308 -0
  67. package/packages/daemon/src/terminal/graphics/native-surface-viewer.ts +154 -0
  68. package/packages/daemon/src/terminal/mirror/mirror-service.ts +5 -0
  69. package/packages/daemon/src/terminal/mirror/session-channel.ts +31 -10
  70. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +446 -15
  71. package/packages/daemon/src/terminal/pane-stream/runtime.ts +14 -1
  72. package/packages/daemon/src/terminal/session-runtime/registry.ts +2 -0
  73. package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +1 -1
  74. package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +216 -22
  75. package/packages/daemon/src/terminal/session-runtime/terminal-delivery-observation-identity.ts +7 -4
  76. package/packages/daemon/src/terminal/session-runtime/terminal-projection-delivery.ts +59 -0
  77. package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +3 -4
  78. package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +4 -0
  79. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +26 -4
  80. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +20 -1
  81. package/packages/daemon/src/tui/mirror/runtime/renderer-probe.ts +257 -0
  82. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +15 -0
  83. package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +4 -0
  84. 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 paneStreamBoundary = attachPaneStreamWebSocket(server, paneStreamRuntime.coordinator, PANE_STREAM_REDEEM_PATH, () => serverOwners.isDefaultCurrent());
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([sessionRuntimeRegistry?.dispose() ?? Promise.resolve()]);
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.11</string>
24
+ <string>2.9.13</string>
25
25
  <key>CFBundleVersion</key>
26
- <string>2.9.11</string>
26
+ <string>2.9.13</string>
27
27
  <key>LSMinimumSystemVersion</key>
28
28
  <string>11.0</string>
29
29
  <key>LSUIElement</key>
@@ -10,7 +10,7 @@
10
10
  </data>
11
11
  <key>Resources/Assets.car</key>
12
12
  <data>
13
- nBfyKmj9/xOSU1E6fMrLP0j34to=
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
- BH5ZBbufHUMGSzMRXcEqBgur+e94BeWVItaM9FDWrFA=
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
+ }