tmux-ide 2.9.4 → 2.9.5
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 +4354 -3376
- package/package.json +7 -6
- package/packages/contracts/src/session-runtime.ts +2 -0
- package/packages/contracts/src/terminal-delivery.ts +4 -0
- package/packages/core/src/terminal-compact-capability.ts +31 -1
- package/packages/core/src/terminal-delivery.ts +234 -32
- package/packages/core/src/terminal-replica-hash-cache.ts +162 -21
- package/packages/core/src/terminal-replica-owned-row.ts +165 -10
- package/packages/core/src/terminal-replica.ts +43 -21
- package/packages/daemon/dist/lib/daemon-embed.js +19 -34
- package/packages/daemon/dist/lib/runtime-trace-writer.js +133 -0
- package/packages/daemon/dist/lib/tmux-client-execution.js +37 -2
- package/packages/daemon/dist/lib/tmux-server-owner.js +20 -4
- 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/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/darwin-arm64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/darwin-x64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/darwin-x64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/linux-arm64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/linux-arm64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/linux-x64/licenses/libevent_core-2.1.so.7.txt +0 -0
- package/packages/daemon/dist/native/tmux/linux-x64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/linux-x64/tmux +0 -0
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +6 -6
- package/packages/daemon/dist/terminal/mirror/control-channel.js +379 -75
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +43 -3
- package/packages/daemon/dist/terminal/mirror/native-grid-projection.js +20 -11
- package/packages/daemon/dist/terminal/mirror/session-channel.js +639 -811
- package/packages/daemon/dist/terminal/mirror/stock-pane-snapshot.js +177 -0
- package/packages/daemon/dist/terminal/session-runtime/causal-cell-ledger.js +1 -1
- package/packages/daemon/dist/terminal/session-runtime/registry.js +145 -15
- package/packages/daemon/dist/terminal/session-runtime/stock-capture-fidelity.js +12 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +128 -10
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-interpreter.js +21 -4
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +19 -2
- package/packages/daemon/dist/terminal/session-runtime/xterm-terminal-interpreter-backend.js +17 -19
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +64 -4
- package/packages/daemon/dist/tui/mirror/pane-surface.jsx +10 -1
- package/packages/daemon/dist/tui/mirror/reference-performance-trace.js +117 -71
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +27 -3
- package/packages/daemon/dist/tui/mirror/runtime/pane-scoped-terminal-surface.jsx +17 -4
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +19 -0
- package/packages/daemon/dist/tui/mirror/runtime/terminal-input-adapter.js +10 -2
- package/packages/daemon/dist/tui/mirror/testing/styled-frame-oracle.js +156 -0
- package/packages/daemon/src/lib/daemon-embed.ts +22 -34
- package/packages/daemon/src/lib/runtime-trace-writer.ts +132 -0
- package/packages/daemon/src/lib/tmux-client-execution.ts +47 -2
- package/packages/daemon/src/lib/tmux-server-owner.ts +19 -3
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +6 -0
- package/packages/daemon/src/terminal/mirror/control-channel.ts +428 -84
- package/packages/daemon/src/terminal/mirror/events.ts +7 -7
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +49 -3
- package/packages/daemon/src/terminal/mirror/native-grid-projection.ts +21 -11
- package/packages/daemon/src/terminal/mirror/pane-feed.ts +9 -4
- package/packages/daemon/src/terminal/mirror/session-channel.ts +705 -859
- package/packages/daemon/src/terminal/mirror/stock-pane-snapshot.ts +228 -0
- package/packages/daemon/src/terminal/session-runtime/causal-cell-ledger.ts +1 -1
- package/packages/daemon/src/terminal/session-runtime/registry.ts +203 -32
- package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +7 -0
- package/packages/daemon/src/terminal/session-runtime/stock-capture-fidelity.ts +13 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +142 -8
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-interpreter.ts +26 -3
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +25 -1
- package/packages/daemon/src/terminal/session-runtime/xterm-terminal-interpreter-backend.ts +26 -20
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +63 -4
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +9 -1
- package/packages/daemon/src/tui/mirror/reference-performance-trace.ts +141 -90
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +27 -4
- package/packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface.tsx +61 -31
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +31 -3
- package/packages/daemon/src/tui/mirror/runtime/terminal-input-adapter.ts +9 -6
- package/packages/daemon/src/tui/mirror/testing/styled-frame-oracle.ts +209 -0
- package/packages/daemon-client/src/terminal-fast-lane.ts +53 -4
- package/packages/daemon-client/src/workspace-client-types.ts +1 -0
- package/packages/daemon-client/src/workspace-client.ts +78 -5
- package/packages/tmux-bridge/src/index.ts +1 -0
- package/packages/tmux-bridge/src/runner.ts +25 -1
|
@@ -45,6 +45,7 @@ import { startOwnedEmbeddedDaemon } from "./embedded-daemon-lifecycle.ts";
|
|
|
45
45
|
import { execFileSync } from "node:child_process";
|
|
46
46
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
47
47
|
import { createWriteStream } from "node:fs";
|
|
48
|
+
import { createRuntimeTraceWriter } from "./runtime-trace-writer.ts";
|
|
48
49
|
import { createServer, type Server } from "node:http";
|
|
49
50
|
import { createRequire } from "node:module";
|
|
50
51
|
import type { Socket } from "node:net";
|
|
@@ -1404,14 +1405,11 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1404
1405
|
if (serverOwners?.defaultRetired) throw new Error("Default tmux owner is retired");
|
|
1405
1406
|
},
|
|
1406
1407
|
});
|
|
1407
|
-
let
|
|
1408
|
-
const closeRuntimeTraceStream = async (): Promise<void> => {
|
|
1408
|
+
let runtimeTraceWriter: ReturnType<typeof createRuntimeTraceWriter> | null = null;
|
|
1409
|
+
const closeRuntimeTraceStream = async (incomplete = false): Promise<void> => {
|
|
1409
1410
|
externalInteractionObserver.setDiagnostics(null);
|
|
1410
1411
|
setFleetFactsObserverDiagnostics(null);
|
|
1411
|
-
|
|
1412
|
-
runtimeTraceStream = null;
|
|
1413
|
-
if (!stream || stream.closed || stream.destroyed) return;
|
|
1414
|
-
await new Promise<void>((resolve) => stream.end(resolve));
|
|
1412
|
+
await runtimeTraceWriter?.close(incomplete);
|
|
1415
1413
|
};
|
|
1416
1414
|
let startedServer: Awaited<ReturnType<typeof startHttpServer>>;
|
|
1417
1415
|
try {
|
|
@@ -1455,16 +1453,12 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1455
1453
|
);
|
|
1456
1454
|
};
|
|
1457
1455
|
const runtimeTracePath = process.env.TMUX_IDE_SESSION_RUNTIME_TRACE_LOG;
|
|
1458
|
-
|
|
1459
|
-
?
|
|
1456
|
+
runtimeTraceWriter = runtimeTracePath
|
|
1457
|
+
? createRuntimeTraceWriter(
|
|
1458
|
+
createWriteStream(runtimeTracePath, { flags: "a", highWaterMark: 64 * 1024 }),
|
|
1459
|
+
{ processId: `daemon:${process.pid}`, daemonInstanceId: instanceId },
|
|
1460
|
+
)
|
|
1460
1461
|
: null;
|
|
1461
|
-
let runtimeTraceSaturated = false;
|
|
1462
|
-
runtimeTraceStream?.on("error", () => {
|
|
1463
|
-
runtimeTraceSaturated = true;
|
|
1464
|
-
});
|
|
1465
|
-
runtimeTraceStream?.on("drain", () => {
|
|
1466
|
-
runtimeTraceSaturated = false;
|
|
1467
|
-
});
|
|
1468
1462
|
const runtimeObservability = runtimeTracePath
|
|
1469
1463
|
? createSessionRuntimeObservability({
|
|
1470
1464
|
// The JSONL stream is the qualifying record. Keep only a small
|
|
@@ -1473,28 +1467,22 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1473
1467
|
// pauses on the daemon's input/output event loop.
|
|
1474
1468
|
capacity: 1_024,
|
|
1475
1469
|
onSpan: (span) => {
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1470
|
+
runtimeTraceWriter?.append(() => ({
|
|
1471
|
+
version: 1,
|
|
1472
|
+
type: "performance.stage",
|
|
1473
|
+
...span,
|
|
1474
|
+
}));
|
|
1480
1475
|
},
|
|
1481
1476
|
})
|
|
1482
1477
|
: undefined;
|
|
1483
1478
|
if (runtimeTracePath) {
|
|
1484
1479
|
const publishObserverDiagnostic = (event: object): void => {
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
...event,
|
|
1492
|
-
generation: instanceId,
|
|
1493
|
-
})}\n`,
|
|
1494
|
-
);
|
|
1495
|
-
} catch {
|
|
1496
|
-
// Qualification diagnostics never alter daemon observation.
|
|
1497
|
-
}
|
|
1480
|
+
runtimeTraceWriter?.append(() => ({
|
|
1481
|
+
version: 1,
|
|
1482
|
+
type: "performance.daemon-observer",
|
|
1483
|
+
...event,
|
|
1484
|
+
generation: instanceId,
|
|
1485
|
+
}));
|
|
1498
1486
|
};
|
|
1499
1487
|
const diagnostics = {
|
|
1500
1488
|
nowMicros: () => Math.floor(performance.now() * 1_000),
|
|
@@ -1975,7 +1963,7 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1975
1963
|
appWindowMutation.dispose(),
|
|
1976
1964
|
workspaceMultiplexer.dispose(),
|
|
1977
1965
|
disposeInteractionObservation(),
|
|
1978
|
-
closeRuntimeTraceStream(),
|
|
1966
|
+
closeRuntimeTraceStream(true),
|
|
1979
1967
|
]);
|
|
1980
1968
|
// The pane-stream coordinator may still hold runtime consumers while it
|
|
1981
1969
|
// drains. Preserve the normal shutdown order on startup rollback too:
|
|
@@ -2044,7 +2032,7 @@ async function startEmbeddedDaemonGeneration(
|
|
|
2044
2032
|
appWindowMutationDisposal,
|
|
2045
2033
|
workspaceMultiplexerDisposal,
|
|
2046
2034
|
externalInteractionDisposal,
|
|
2047
|
-
closeRuntimeTraceStream(),
|
|
2035
|
+
closeRuntimeTraceStream(true),
|
|
2048
2036
|
Promise.resolve().then(() => closeClients()),
|
|
2049
2037
|
...[...sockets].map((socket) => Promise.resolve().then(() => socket.destroy())),
|
|
2050
2038
|
Promise.race([closePromise, delay(100)]),
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
const MAX_RECORD_BYTES = 64 * 1024;
|
|
4
|
+
|
|
5
|
+
/** Opt-in diagnostics: one stream buffer, no additional queue or producer backpressure. */
|
|
6
|
+
export function createRuntimeTraceWriter(
|
|
7
|
+
stream: Writable,
|
|
8
|
+
identity: { processId: string; daemonInstanceId: string },
|
|
9
|
+
closeTimeoutMs = 1000,
|
|
10
|
+
) {
|
|
11
|
+
const counts = {
|
|
12
|
+
offeredRecords: 0,
|
|
13
|
+
acceptedRecords: 0,
|
|
14
|
+
droppedRecords: 0,
|
|
15
|
+
saturatedDrops: 0,
|
|
16
|
+
failedDrops: 0,
|
|
17
|
+
oversizedRecords: 0,
|
|
18
|
+
constructionFailures: 0,
|
|
19
|
+
rejectedWrites: 0,
|
|
20
|
+
writeFailures: 0,
|
|
21
|
+
};
|
|
22
|
+
let saturated = false;
|
|
23
|
+
let failed = false;
|
|
24
|
+
let closed = false;
|
|
25
|
+
let closing: Promise<void> | undefined;
|
|
26
|
+
let closeFailure: ((error: Error) => void) | undefined;
|
|
27
|
+
const snapshot = () => ({ ...counts, failed, saturated, writableLength: stream.writableLength });
|
|
28
|
+
stream.on("drain", () => {
|
|
29
|
+
if (!failed) saturated = false;
|
|
30
|
+
});
|
|
31
|
+
// Retain the error listener through destroy: a late stream error must not escape cleanup.
|
|
32
|
+
stream.on("error", (error: Error) => {
|
|
33
|
+
counts.writeFailures++;
|
|
34
|
+
failed = true;
|
|
35
|
+
closeFailure?.(error);
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
snapshot,
|
|
39
|
+
append(build: () => unknown): void {
|
|
40
|
+
if (closed) return;
|
|
41
|
+
counts.offeredRecords++;
|
|
42
|
+
if (failed || stream.destroyed) {
|
|
43
|
+
counts.failedDrops++;
|
|
44
|
+
counts.droppedRecords++;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (saturated) {
|
|
48
|
+
counts.saturatedDrops++;
|
|
49
|
+
counts.droppedRecords++;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let line: string;
|
|
53
|
+
try {
|
|
54
|
+
const json = JSON.stringify(build());
|
|
55
|
+
if (json === undefined) throw new Error("Trace record is not JSON");
|
|
56
|
+
line = `${json}\n`;
|
|
57
|
+
if (Buffer.byteLength(line) > MAX_RECORD_BYTES) {
|
|
58
|
+
counts.oversizedRecords++;
|
|
59
|
+
counts.droppedRecords++;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
counts.constructionFailures++;
|
|
64
|
+
counts.droppedRecords++;
|
|
65
|
+
failed = true;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
saturated = !stream.write(line);
|
|
70
|
+
counts.acceptedRecords++;
|
|
71
|
+
} catch {
|
|
72
|
+
counts.rejectedWrites++;
|
|
73
|
+
counts.droppedRecords++;
|
|
74
|
+
failed = true;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
close(incomplete = false): Promise<void> {
|
|
78
|
+
if (closing) return closing;
|
|
79
|
+
closed = true;
|
|
80
|
+
if (incomplete) failed = true;
|
|
81
|
+
closing = new Promise<void>((resolve, reject) => {
|
|
82
|
+
let settled = false;
|
|
83
|
+
const finish = (error?: Error) => {
|
|
84
|
+
if (settled) return;
|
|
85
|
+
settled = true;
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
stream.off("drain", end);
|
|
88
|
+
stream.off("close", prematureClose);
|
|
89
|
+
closeFailure = undefined;
|
|
90
|
+
if (error) {
|
|
91
|
+
failed = true;
|
|
92
|
+
stream.destroy();
|
|
93
|
+
reject(error);
|
|
94
|
+
} else resolve();
|
|
95
|
+
};
|
|
96
|
+
const prematureClose = () =>
|
|
97
|
+
finish(new Error("Runtime trace closed before final summary completion"));
|
|
98
|
+
const end = () => {
|
|
99
|
+
try {
|
|
100
|
+
if (stream.destroyed || !stream.writable)
|
|
101
|
+
throw new Error("Runtime trace stream unavailable");
|
|
102
|
+
const summary = {
|
|
103
|
+
version: 1,
|
|
104
|
+
type: "performance.daemon-trace.summary",
|
|
105
|
+
...identity,
|
|
106
|
+
...snapshot(),
|
|
107
|
+
maxRecordBytes: MAX_RECORD_BYTES,
|
|
108
|
+
acceptedRecordsExcludeSummary: true,
|
|
109
|
+
shutdownKind: incomplete ? "startup-rollback" : "normal",
|
|
110
|
+
ringTailOverwrites: "not-file-loss",
|
|
111
|
+
};
|
|
112
|
+
stream.end(`${JSON.stringify(summary)}\n`, () =>
|
|
113
|
+
finish(failed ? new Error("Runtime trace recorded failures") : undefined),
|
|
114
|
+
);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
finish(error instanceof Error ? error : new Error("Runtime trace end failed"));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const timer = setTimeout(
|
|
120
|
+
() => finish(new Error("Runtime trace close timed out")),
|
|
121
|
+
closeTimeoutMs,
|
|
122
|
+
);
|
|
123
|
+
closeFailure = finish;
|
|
124
|
+
stream.on("close", prematureClose);
|
|
125
|
+
if (stream.destroyed) finish(new Error("Runtime trace stream destroyed"));
|
|
126
|
+
else if (saturated && stream.writableNeedDrain) stream.once("drain", end);
|
|
127
|
+
else end();
|
|
128
|
+
});
|
|
129
|
+
return closing;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
1
2
|
import { accessSync, constants, realpathSync, statSync } from "node:fs";
|
|
2
|
-
import { delimiter, isAbsolute, join } from "node:path";
|
|
3
|
+
import { delimiter, isAbsolute, join, resolve } from "node:path";
|
|
3
4
|
import { resolveRuntimeNamespace } from "./runtime-namespace.ts";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
resolveBundledTmux,
|
|
7
|
+
bundledTmuxResourceEnvironment,
|
|
8
|
+
withBundledTmuxResources,
|
|
9
|
+
} from "./bundled-tmux.ts";
|
|
5
10
|
|
|
6
11
|
export function resolveTmuxExecutable(): string {
|
|
7
12
|
if (resolveRuntimeNamespace().development) return resolveBundledTmux()!;
|
|
@@ -57,3 +62,43 @@ export function tmuxClientEnvironment(
|
|
|
57
62
|
}
|
|
58
63
|
return { ...environment, ...bundledTmuxResourceEnvironment(executable) };
|
|
59
64
|
}
|
|
65
|
+
|
|
66
|
+
/** Ordinary CLI commands retain PATH selection; bundled tmux fills a missing system client. */
|
|
67
|
+
export function resolveOrdinaryTmuxClient(
|
|
68
|
+
environment: NodeJS.ProcessEnv,
|
|
69
|
+
cwd?: string | URL,
|
|
70
|
+
): {
|
|
71
|
+
executable: string;
|
|
72
|
+
environment: NodeJS.ProcessEnv;
|
|
73
|
+
} {
|
|
74
|
+
const configured = environment.TMUX_IDE_TMUX_BIN;
|
|
75
|
+
const candidates = configured
|
|
76
|
+
? [configured]
|
|
77
|
+
: (environment.PATH ?? "/usr/bin:/bin")
|
|
78
|
+
.split(delimiter)
|
|
79
|
+
.map((entry) =>
|
|
80
|
+
resolve(cwd instanceof URL ? fileURLToPath(cwd) : (cwd ?? process.cwd()), entry, "tmux"),
|
|
81
|
+
);
|
|
82
|
+
let executable: string | undefined;
|
|
83
|
+
for (const candidate of candidates) {
|
|
84
|
+
try {
|
|
85
|
+
if (!isAbsolute(candidate)) continue;
|
|
86
|
+
accessSync(candidate, constants.X_OK);
|
|
87
|
+
const canonical = realpathSync(candidate);
|
|
88
|
+
if (statSync(canonical).isFile()) {
|
|
89
|
+
executable = canonical;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
/* Try the next ordinary PATH candidate. */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!executable && !configured)
|
|
97
|
+
executable =
|
|
98
|
+
resolveBundledTmux([
|
|
99
|
+
...(environment.TMUX_IDE_CLI ? [environment.TMUX_IDE_CLI] : []),
|
|
100
|
+
fileURLToPath(import.meta.url),
|
|
101
|
+
]) ?? undefined;
|
|
102
|
+
if (!executable) throw new Error("tmux_executable_unavailable");
|
|
103
|
+
return { executable, environment: withBundledTmuxResources(executable, environment) };
|
|
104
|
+
}
|
|
@@ -19,7 +19,8 @@ import { createNativeTmuxSessionCreator } from "./tmux-server-session-create.ts"
|
|
|
19
19
|
import type { WorkspacePaneCreateMutationRequest } from "@tmux-ide/contracts";
|
|
20
20
|
import { createTmuxSessionMutationFence } from "./tmux-session-mutation-fence.ts";
|
|
21
21
|
import { createNativeTmuxSessionOpener } from "./tmux-server-session-open.ts";
|
|
22
|
-
import { randomUUID } from "node:crypto";
|
|
22
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
23
|
+
import { WorkspaceIdSchemaZ } from "@tmux-ide/contracts";
|
|
23
24
|
import { mkdirSync } from "node:fs";
|
|
24
25
|
import { z } from "zod";
|
|
25
26
|
import type { WorkspaceMultiplexerBackend } from "../command-center/actions/handlers/workspace-multiplexer.ts";
|
|
@@ -61,6 +62,22 @@ export interface NativeTmuxServerOwnerOptions {
|
|
|
61
62
|
readonly nativeServerIdentity?: NativeTmuxServerIdentity;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/** Native display names are not necessarily valid shared-contract record identities. */
|
|
66
|
+
function discoveredWorkspaceName(registry: WorkspaceRegistry, sessionName: string): string {
|
|
67
|
+
if (WorkspaceIdSchemaZ.safeParse(sessionName).success && !registry.has(sessionName))
|
|
68
|
+
return sessionName;
|
|
69
|
+
// Existing intent wins even if it occupies a derived ID. Each candidate is
|
|
70
|
+
// deterministic; repeated refreshes retain the already-adopted session alias.
|
|
71
|
+
for (let suffix = 0; suffix <= registry.list().length; suffix += 1) {
|
|
72
|
+
const digest = createHash("sha256")
|
|
73
|
+
.update(`tmux-ide.discovered-workspace.v1\0${sessionName}\0${suffix}`)
|
|
74
|
+
.digest("hex");
|
|
75
|
+
const name = `session-${digest}`;
|
|
76
|
+
if (!registry.has(name)) return name;
|
|
77
|
+
}
|
|
78
|
+
throw new Error("Unable to allocate discovered workspace identity");
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
/** Refresh discovered live membership without changing durable workspace intent or aliases. */
|
|
65
82
|
export function createNativeTmuxServerCatalog(
|
|
66
83
|
workspaceRegistry: WorkspaceRegistry,
|
|
@@ -114,11 +131,10 @@ export function createNativeTmuxServerCatalog(
|
|
|
114
131
|
workspaceRegistry.remove(workspace.name);
|
|
115
132
|
for (const { summary, dir } of rows.values())
|
|
116
133
|
if (
|
|
117
|
-
!workspaceRegistry.has(summary.sessionName) &&
|
|
118
134
|
!workspaceRegistry.list().some((workspace) => workspace.sessionName === summary.sessionName)
|
|
119
135
|
)
|
|
120
136
|
workspaceRegistry.add({
|
|
121
|
-
name: summary.sessionName,
|
|
137
|
+
name: discoveredWorkspaceName(workspaceRegistry, summary.sessionName),
|
|
122
138
|
sessionName: summary.sessionName,
|
|
123
139
|
projectDir: dir,
|
|
124
140
|
persistence: "volatile",
|
|
@@ -620,6 +620,7 @@ function analyzeInventoryPanes(panes: readonly NativeTerminalInventoryPaneSnapsh
|
|
|
620
620
|
active: _active,
|
|
621
621
|
role: _role,
|
|
622
622
|
name: _name,
|
|
623
|
+
nameSource: _nameSource,
|
|
623
624
|
type: _type,
|
|
624
625
|
missionStamp: _missionStamp,
|
|
625
626
|
dir: _dir,
|
|
@@ -989,6 +990,7 @@ export async function discoverWorkspaceRegistryTerminalInventory(
|
|
|
989
990
|
active: _active,
|
|
990
991
|
role: _role,
|
|
991
992
|
name: _name,
|
|
993
|
+
nameSource: _nameSource,
|
|
992
994
|
type: _type,
|
|
993
995
|
missionStamp: _missionStamp,
|
|
994
996
|
dir: _dir,
|
|
@@ -1018,6 +1020,7 @@ export async function discoverWorkspaceRegistrySemanticPanes(
|
|
|
1018
1020
|
active: _active,
|
|
1019
1021
|
role: _role,
|
|
1020
1022
|
name: _name,
|
|
1023
|
+
nameSource: _nameSource,
|
|
1021
1024
|
type: _type,
|
|
1022
1025
|
missionStamp: _missionStamp,
|
|
1023
1026
|
dir: _dir,
|
|
@@ -1349,6 +1352,7 @@ export class WorkspaceTerminalInventoryRuntime {
|
|
|
1349
1352
|
active: _active,
|
|
1350
1353
|
role: _role,
|
|
1351
1354
|
name: _name,
|
|
1355
|
+
nameSource: _nameSource,
|
|
1352
1356
|
type: _type,
|
|
1353
1357
|
missionStamp: _missionStamp,
|
|
1354
1358
|
dir: _dir,
|
|
@@ -2066,6 +2070,7 @@ export class NativeTerminalAttachmentRuntime {
|
|
|
2066
2070
|
active: _active,
|
|
2067
2071
|
role: _role,
|
|
2068
2072
|
name: _name,
|
|
2073
|
+
nameSource: _nameSource,
|
|
2069
2074
|
type: _type,
|
|
2070
2075
|
missionStamp: _missionStamp,
|
|
2071
2076
|
dir: _dir,
|
|
@@ -2341,6 +2346,7 @@ export class NativeTerminalAttachmentRuntime {
|
|
|
2341
2346
|
active: _active,
|
|
2342
2347
|
role: _role,
|
|
2343
2348
|
name: _name,
|
|
2349
|
+
nameSource: _nameSource,
|
|
2344
2350
|
type: _type,
|
|
2345
2351
|
missionStamp: _missionStamp,
|
|
2346
2352
|
dir: _dir,
|