tmux-ide 2.9.6 → 2.9.8
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 +2881 -1336
- package/package.json +2 -2
- package/packages/contracts/src/index.ts +5 -0
- package/packages/contracts/src/pane-stream.ts +54 -0
- package/packages/contracts/src/terminal-graphics.ts +118 -0
- package/packages/contracts/src/terminal-history-anchor.ts +61 -0
- package/packages/contracts/src/tmux-server-diagnostics.ts +16 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/kitty-placeholders.ts +208 -0
- package/packages/daemon/dist/command-center/tmux-server-diagnostics.js +24 -0
- package/packages/daemon/dist/command-center/tmux-server-history-anchor.js +48 -0
- package/packages/daemon/dist/command-center/tmux-servers.js +11 -0
- package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +23 -1
- package/packages/daemon/dist/lib/daemon-embed.js +21 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +10 -1
- package/packages/daemon/dist/lib/tmux-session-mutation-fence.js +2 -1
- 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/native/tmux/darwin-arm64/lib/libevent_core-2.1.7.dylib +0 -0
- package/packages/daemon/dist/native/tmux/darwin-arm64/lib/libutf8proc.3.3.0.dylib +0 -0
- package/packages/daemon/dist/native/tmux/darwin-arm64/manifest.json +12 -4
- package/packages/daemon/dist/native/tmux/darwin-arm64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/linux-arm64/manifest.json +9 -1
- package/packages/daemon/dist/native/tmux/linux-arm64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/linux-x64/manifest.json +9 -1
- package/packages/daemon/dist/native/tmux/linux-x64/tmux +0 -0
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +25 -12
- package/packages/daemon/dist/terminal/graphics/kitty-graphics.js +699 -0
- package/packages/daemon/dist/terminal/mirror/control-channel.js +17 -1
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +7 -0
- package/packages/daemon/dist/terminal/mirror/native-grid-capture.js +8 -0
- package/packages/daemon/dist/terminal/mirror/native-grid-reader.js +6 -1
- package/packages/daemon/dist/terminal/mirror/session-channel.js +56 -4
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +180 -1
- package/packages/daemon/dist/terminal/protocol/session-descriptor-discovery.js +3 -2
- package/packages/daemon/dist/terminal/session-runtime/history-anchor-owner.js +171 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +29 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +20 -1
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-interpreter.js +85 -1
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +30 -0
- package/packages/daemon/dist/terminal/session-runtime/transport-binding.js +53 -21
- package/packages/daemon/dist/terminal/session-runtime/xterm-terminal-interpreter-backend.js +11 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +7 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +35 -5
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-paste.js +2 -2
- package/packages/daemon/dist/tui/mirror/runtime/terminal-input-adapter.js +2 -2
- package/packages/daemon/src/command-center/tmux-server-diagnostics.ts +38 -0
- package/packages/daemon/src/command-center/tmux-server-history-anchor.ts +69 -0
- package/packages/daemon/src/command-center/tmux-servers.ts +11 -0
- package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +20 -2
- package/packages/daemon/src/lib/daemon-embed.ts +26 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +10 -0
- package/packages/daemon/src/lib/tmux-session-mutation-fence.ts +2 -1
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
- package/packages/daemon/src/terminal/graphics/__fixtures__/full-frame-client-recorded.ctl +87 -0
- package/packages/daemon/src/terminal/graphics/__fixtures__/full-frame-client.ctl +10 -0
- package/packages/daemon/src/terminal/graphics/kitty-graphics.ts +756 -0
- package/packages/daemon/src/terminal/mirror/control-channel.ts +18 -1
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +14 -0
- package/packages/daemon/src/terminal/mirror/native-grid-capture.ts +11 -0
- package/packages/daemon/src/terminal/mirror/native-grid-reader.ts +10 -1
- package/packages/daemon/src/terminal/mirror/session-channel.ts +75 -3
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +199 -0
- package/packages/daemon/src/terminal/protocol/session-descriptor-discovery.ts +3 -2
- package/packages/daemon/src/terminal/session-runtime/history-anchor-owner.ts +190 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +38 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +38 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-interpreter-backend.ts +4 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-interpreter.ts +99 -1
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +39 -0
- package/packages/daemon/src/terminal/session-runtime/transport-binding.ts +58 -24
- package/packages/daemon/src/terminal/session-runtime/xterm-terminal-interpreter-backend.ts +13 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +6 -1
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +44 -9
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-paste.ts +2 -1
- package/packages/daemon/src/tui/mirror/runtime/terminal-input-adapter.ts +5 -2
- package/packages/daemon-client/src/fleet-connection-status.ts +1 -1
- package/packages/daemon-client/src/pane-creation-refusal.ts +48 -0
- package/packages/daemon-client/src/pane-stream-client.ts +37 -0
- package/packages/daemon-client/src/tmux-server-client.ts +133 -6
- package/packages/daemon/dist/native/tmux/darwin-arm64/lib/libutf8proc.3.2.3.dylib +0 -0
- /package/packages/daemon/dist/native/tmux/darwin-arm64/licenses/{libutf8proc.3.2.3.dylib.txt → libutf8proc.3.3.0.dylib.txt} +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mountTmuxServerHistoryAnchorRoute } from "./tmux-server-history-anchor.ts";
|
|
2
|
+
import { mountTmuxServerDiagnosticsRoute } from "./tmux-server-diagnostics.ts";
|
|
1
3
|
import { WindowSplitLayoutUnavailable } from "../terminal/mirror/window-split-authority.ts";
|
|
2
4
|
import { streamTmuxInteractions } from "./tmux-server-interaction-events.ts";
|
|
3
5
|
import { mountTmuxServerNativeBackingRoute } from "./tmux-server-native-backing.ts";
|
|
@@ -30,6 +32,7 @@ import {
|
|
|
30
32
|
} from "../lib/tmux-server-owners.ts";
|
|
31
33
|
import type { NativeTmuxServerOwner } from "../lib/tmux-server-owner.ts";
|
|
32
34
|
import { WorkspaceMultiplexerError } from "../lib/workspace-multiplexer-verbs.ts";
|
|
35
|
+
import { WorkspacePaneCreationError } from "../lib/workspace-pane-creation.ts";
|
|
33
36
|
import { ownerBearerMatches } from "./owner-authority.ts";
|
|
34
37
|
import { mountPaneStreamIssueRoute } from "./pane-stream-issue.ts";
|
|
35
38
|
import { projectTerminalRuntimeInventory } from "./resources/terminal-runtime-inventory.ts";
|
|
@@ -79,6 +82,8 @@ function failure(c: Context, error: unknown): Response {
|
|
|
79
82
|
{ error: { code: error.code } },
|
|
80
83
|
error.code === "not-found" ? 404 : error.code === "capacity" ? 429 : 409,
|
|
81
84
|
);
|
|
85
|
+
if (error instanceof WorkspacePaneCreationError && error.code === "harness_unavailable")
|
|
86
|
+
return c.json({ error: { code: "harness_unavailable" } }, 409);
|
|
82
87
|
if (error instanceof WorkspaceMultiplexerError)
|
|
83
88
|
return c.json({ error: { code: error.code } }, 409);
|
|
84
89
|
if (error instanceof z.ZodError || error instanceof SyntaxError || error instanceof TypeError)
|
|
@@ -133,6 +138,12 @@ export function mountTmuxServerRoutes(app: Hono, options: TmuxServerRoutesOption
|
|
|
133
138
|
}),
|
|
134
139
|
);
|
|
135
140
|
const scoped = `${base}/:serverId/:generation`;
|
|
141
|
+
mountTmuxServerHistoryAnchorRoute(app, {
|
|
142
|
+
scopedPath: scoped,
|
|
143
|
+
owners: options.owners,
|
|
144
|
+
readBody: (request) => boundedJson(request, 4096),
|
|
145
|
+
});
|
|
146
|
+
mountTmuxServerDiagnosticsRoute(app, { scopedPath: scoped, owners: options.owners });
|
|
136
147
|
mountTmuxServerEditorContextRoute(app, {
|
|
137
148
|
scopedPath: scoped,
|
|
138
149
|
owners: options.owners,
|
|
@@ -100,6 +100,12 @@ function spawnOwner(entryPath: string, cwd: string): Promise<void> {
|
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
class ShutdownTransportError extends Error {
|
|
104
|
+
constructor(readonly original: unknown) {
|
|
105
|
+
super("Canonical daemon shutdown transport failed.", { cause: original });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
103
109
|
async function shutdownOlderOwner(info: CanonicalDaemonInfo): Promise<void> {
|
|
104
110
|
const headers: Record<string, string> = { "content-type": "application/json" };
|
|
105
111
|
if (info.authToken) headers.authorization = `Bearer ${info.authToken}`;
|
|
@@ -115,7 +121,9 @@ async function shutdownOlderOwner(info: CanonicalDaemonInfo): Promise<void> {
|
|
|
115
121
|
}),
|
|
116
122
|
signal: AbortSignal.timeout(2_000),
|
|
117
123
|
},
|
|
118
|
-
)
|
|
124
|
+
).catch((error: unknown) => {
|
|
125
|
+
throw new ShutdownTransportError(error);
|
|
126
|
+
});
|
|
119
127
|
const envelope = (await response.json().catch(() => null)) as {
|
|
120
128
|
ok?: unknown;
|
|
121
129
|
result?: { stopping?: unknown };
|
|
@@ -248,7 +256,16 @@ async function replaceOlderCanonicalDaemon(
|
|
|
248
256
|
{ reason: "canonical-record-invalid" },
|
|
249
257
|
);
|
|
250
258
|
}
|
|
251
|
-
|
|
259
|
+
let transportFailure: ShutdownTransportError | undefined;
|
|
260
|
+
try {
|
|
261
|
+
await deps.shutdownOlderOwner(info);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (!(error instanceof ShutdownTransportError)) throw error;
|
|
264
|
+
transportFailure = error;
|
|
265
|
+
}
|
|
266
|
+
// A competing upgrader may close the authenticated owner's listener before
|
|
267
|
+
// removing its record. Observe retirement, without repeating the mutation.
|
|
268
|
+
// A transport failure alone never authorizes starting a replacement.
|
|
252
269
|
const deadline = deps.now() + timeoutMs;
|
|
253
270
|
while (deps.now() < deadline) {
|
|
254
271
|
const state = deps.inspect();
|
|
@@ -257,6 +274,7 @@ async function replaceOlderCanonicalDaemon(
|
|
|
257
274
|
if (!(await deps.alive(info))) return;
|
|
258
275
|
await deps.sleep(25);
|
|
259
276
|
}
|
|
277
|
+
if (transportFailure) throw transportFailure.original;
|
|
260
278
|
throw new DaemonBootstrapError(
|
|
261
279
|
"control-timeout",
|
|
262
280
|
"The older canonical daemon did not retire after accepting the version upgrade.",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TmuxServerVersionSchemaZ } from "@tmux-ide/contracts";
|
|
1
2
|
import {
|
|
2
3
|
createNativeSplitCapabilityProbe,
|
|
3
4
|
createNativeSplitReadiness,
|
|
@@ -1400,6 +1401,7 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1400
1401
|
let paneStreamRuntime: PaneStreamRuntime | null = null;
|
|
1401
1402
|
let serverOwners: Awaited<ReturnType<typeof createEmbeddedTmuxServerOwners>> | null = null;
|
|
1402
1403
|
let defaultEditorContextRetired = false;
|
|
1404
|
+
const defaultDiagnosticsLifetime = new AbortController();
|
|
1403
1405
|
const defaultSessionCreator = createNativeTmuxSessionCreator({
|
|
1404
1406
|
generation: instanceId,
|
|
1405
1407
|
registry: workspaceRegistry,
|
|
@@ -1805,6 +1807,29 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1805
1807
|
stateDirectory: resolveRuntimeNamespace().runtimeDir,
|
|
1806
1808
|
webSocketBaseUrl: canonicalDaemonUrl("ws", bindHostname, port),
|
|
1807
1809
|
defaultOwner: {
|
|
1810
|
+
readServerVersion: async (signal?: AbortSignal) => {
|
|
1811
|
+
const assertCurrent = () => {
|
|
1812
|
+
if (
|
|
1813
|
+
defaultEditorContextRetired ||
|
|
1814
|
+
serverOwners?.defaultRetired ||
|
|
1815
|
+
!initialNativeServerIdentity
|
|
1816
|
+
)
|
|
1817
|
+
throw new Error("Default tmux diagnostics owner is unavailable");
|
|
1818
|
+
};
|
|
1819
|
+
assertCurrent();
|
|
1820
|
+
const combined = AbortSignal.any([
|
|
1821
|
+
defaultDiagnosticsLifetime.signal,
|
|
1822
|
+
...(signal ? [signal] : []),
|
|
1823
|
+
]);
|
|
1824
|
+
combined.throwIfAborted();
|
|
1825
|
+
const output = await fleetFactsTmuxRunner(
|
|
1826
|
+
["display-message", "-p", "#{version}"],
|
|
1827
|
+
combined,
|
|
1828
|
+
);
|
|
1829
|
+
combined.throwIfAborted();
|
|
1830
|
+
assertCurrent();
|
|
1831
|
+
return TmuxServerVersionSchemaZ.parse(output.trim());
|
|
1832
|
+
},
|
|
1808
1833
|
get interactionObservation() {
|
|
1809
1834
|
return interactionObservation;
|
|
1810
1835
|
},
|
|
@@ -1913,6 +1938,7 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1913
1938
|
paneStreamRuntime,
|
|
1914
1939
|
dispose: async () => {
|
|
1915
1940
|
defaultEditorContextRetired = true;
|
|
1941
|
+
defaultDiagnosticsLifetime.abort();
|
|
1916
1942
|
sessionMonitor?.stop();
|
|
1917
1943
|
paneSourceCredentials.dispose();
|
|
1918
1944
|
const transportResults = await Promise.allSettled([
|
|
@@ -30,6 +30,7 @@ import { observedAgentSchema } from "./tmux-server-agent-admission.ts";
|
|
|
30
30
|
import { createPaneEditorContextResolver } from "./tmux-pane-editor-context.ts";
|
|
31
31
|
import { createHash, randomUUID } from "node:crypto";
|
|
32
32
|
import {
|
|
33
|
+
TmuxServerVersionSchemaZ,
|
|
33
34
|
WorkspaceIdSchemaZ,
|
|
34
35
|
WindowLinkTargetSchemaZ,
|
|
35
36
|
type WindowLinkTarget,
|
|
@@ -594,6 +595,15 @@ export async function createNativeTmuxServerOwner(options: NativeTmuxServerOwner
|
|
|
594
595
|
throw error;
|
|
595
596
|
}
|
|
596
597
|
return {
|
|
598
|
+
readServerVersion: async (signal?: AbortSignal) => {
|
|
599
|
+
assertOpen();
|
|
600
|
+
const combined = AbortSignal.any([credentialLifetime.signal, ...(signal ? [signal] : [])]);
|
|
601
|
+
combined.throwIfAborted();
|
|
602
|
+
const output = await generationRunAsync(["display-message", "-p", "#{version}"], combined);
|
|
603
|
+
combined.throwIfAborted();
|
|
604
|
+
assertOpen();
|
|
605
|
+
return TmuxServerVersionSchemaZ.parse(output.trim());
|
|
606
|
+
},
|
|
597
607
|
serverId: options.serverId,
|
|
598
608
|
generation,
|
|
599
609
|
resolvePaneEditorContext,
|
|
@@ -23,7 +23,8 @@ export function createTmuxSessionMutationFence() {
|
|
|
23
23
|
const output = run([
|
|
24
24
|
"if-shell",
|
|
25
25
|
"-t",
|
|
26
|
-
|
|
26
|
+
// if-shell resolves a pane target: the colon selects this exact session.
|
|
27
|
+
`=${selected.name}:`,
|
|
27
28
|
"-F",
|
|
28
29
|
`#{&&:#{==:#{session_id},${selected.id}},#{==:#{session_created},${selected.created}}}`,
|
|
29
30
|
args.map((arg) => (arg === ";" ? ";" : shellEscape(arg))).join(" "),
|
|
@@ -918,6 +918,11 @@ function parsePaneSnapshot(
|
|
|
918
918
|
* application-shell inventory. Names are resolved exactly to `$session_id`
|
|
919
919
|
* before any pane query, and a second identity/topology snapshot closes races.
|
|
920
920
|
*/
|
|
921
|
+
const INVENTORY_FENCE_ATTEMPTS = 4;
|
|
922
|
+
/** 25, 50, 100 ms: enough for a mutation's identity stamps, bounded for every reader. */
|
|
923
|
+
const settleDelay = (attempt: number) =>
|
|
924
|
+
new Promise<void>((resolve) => setTimeout(resolve, 25 * 2 ** (attempt - 1)));
|
|
925
|
+
|
|
921
926
|
export async function discoverWorkspaceRegistryTerminalInventory(
|
|
922
927
|
registry: WorkspaceRegistry,
|
|
923
928
|
runner: NativeTerminalInventoryReadRunner,
|
|
@@ -941,12 +946,6 @@ export async function discoverWorkspaceRegistryTerminalInventory(
|
|
|
941
946
|
const argv = ["list-panes", "-s", "-t", identity.id, "-F", PANE_FORMAT] as const;
|
|
942
947
|
const before = await requiredTmuxResult(runner, argv, signal);
|
|
943
948
|
if (before === null) continue;
|
|
944
|
-
const first = parsePaneSnapshot(before, identity);
|
|
945
|
-
const panes = first.panes;
|
|
946
|
-
const after = await requiredTmuxResult(runner, argv, signal);
|
|
947
|
-
if (after === null) throw new NativeTerminalAttachmentRuntimeError("discovery-failed");
|
|
948
|
-
const second = parsePaneSnapshot(after, identity);
|
|
949
|
-
const latest = second.panes;
|
|
950
949
|
// Foreground commands, titles, focus and paths can change while the same
|
|
951
950
|
// pane remains valid. Fence binding/topology only, then publish one complete
|
|
952
951
|
// latest snapshot rather than mixing presentation values across reads.
|
|
@@ -961,14 +960,30 @@ export async function discoverWorkspaceRegistryTerminalInventory(
|
|
|
961
960
|
"windowStamp",
|
|
962
961
|
"index",
|
|
963
962
|
] as const;
|
|
964
|
-
|
|
965
|
-
first
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
963
|
+
const matches = (
|
|
964
|
+
first: ReturnType<typeof parsePaneSnapshot>,
|
|
965
|
+
second: ReturnType<typeof parsePaneSnapshot>,
|
|
966
|
+
) =>
|
|
967
|
+
first.linksProof === second.linksProof &&
|
|
968
|
+
first.panes.length === second.panes.length &&
|
|
969
|
+
first.panes.every((pane, index) =>
|
|
970
|
+
proofKeys.every((key) => pane[key] === second.panes[index]![key]),
|
|
971
|
+
);
|
|
972
|
+
// A just-created window or split is still being stamped, so topology can
|
|
973
|
+
// move between two reads. Let it settle for a bounded number of re-reads;
|
|
974
|
+
// only two consecutive matching reads are ever published.
|
|
975
|
+
let previous = parsePaneSnapshot(before, identity);
|
|
976
|
+
let stable: ReturnType<typeof parsePaneSnapshot> | null = null;
|
|
977
|
+
for (let attempt = 0; attempt < INVENTORY_FENCE_ATTEMPTS && !stable; attempt += 1) {
|
|
978
|
+
if (attempt > 0) await settleDelay(attempt);
|
|
979
|
+
const after = await requiredTmuxResult(runner, argv, signal);
|
|
980
|
+
if (after === null) throw new NativeTerminalAttachmentRuntimeError("discovery-failed");
|
|
981
|
+
const next = parsePaneSnapshot(after, identity);
|
|
982
|
+
if (matches(previous, next)) stable = next;
|
|
983
|
+
else previous = next;
|
|
970
984
|
}
|
|
971
|
-
|
|
985
|
+
if (!stable) throw new NativeTerminalAttachmentRuntimeError("discovery-failed");
|
|
986
|
+
bySessionName.set(sessionName, stable.panes);
|
|
972
987
|
}
|
|
973
988
|
|
|
974
989
|
const panes: NativeTerminalInventoryPaneSnapshot[] = [];
|