tmux-ide 2.9.0-beta.16 → 2.9.0-beta.17
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 +165 -24
- package/package.json +2 -2
- package/packages/contracts/src/fleet-lifecycle.ts +5 -1
- package/packages/contracts/src/workspace-multiplexer.ts +11 -1
- package/packages/daemon/dist/command-center/actions/handlers/fleet-lifecycle.js +5 -0
- package/packages/daemon/dist/command-center/resources/fleet-preview-route.js +97 -16
- package/packages/daemon/dist/lib/fleet-preview-model.js +1 -0
- package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +35 -4
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +20 -2
- package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +12 -1
- package/packages/daemon/dist/terminal/session-runtime/registry.js +5 -1
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +80 -13
- package/packages/daemon/dist/tui/mirror/runtime/application-fleet-preview.js +163 -8
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +9 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.js +2 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +1 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +80 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +66 -8
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-command-owner.js +10 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +57 -0
- package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +47 -1
- package/packages/daemon/dist/tui/mirror/runtime/fleet-presentation.js +20 -0
- package/packages/daemon/dist/tui/mirror/theme.js +9 -0
- package/packages/daemon/dist/tui/mirror/ui/badge.jsx +3 -1
- package/packages/daemon/dist/tui/mirror/ui/navigation-row.jsx +3 -1
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +23 -6
- package/packages/daemon/src/command-center/actions/handlers/fleet-lifecycle.ts +5 -0
- package/packages/daemon/src/command-center/resources/fleet-preview-route.ts +136 -32
- package/packages/daemon/src/lib/fleet-preview-model.ts +13 -0
- package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +41 -3
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +33 -2
- package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +14 -1
- package/packages/daemon/src/terminal/session-runtime/registry.ts +6 -1
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +4 -1
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +84 -14
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-preview.ts +185 -9
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions.tsx +244 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +17 -1
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +8 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +89 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +84 -8
- package/packages/daemon/src/tui/mirror/runtime/application-palette-command-owner.ts +16 -0
- package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +208 -0
- package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +61 -0
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +25 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +17 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +57 -5
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +21 -1
- package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +66 -1
- package/packages/daemon/src/tui/mirror/runtime/fleet-presentation.ts +33 -0
- package/packages/daemon/src/tui/mirror/shell-chrome-view.tsx +2 -0
- package/packages/daemon/src/tui/mirror/theme.ts +10 -0
- package/packages/daemon/src/tui/mirror/ui/badge.tsx +10 -1
- package/packages/daemon/src/tui/mirror/ui/navigation-row.tsx +10 -1
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +42 -6
- package/packages/daemon/src/tui/mirror/workspace/application-shell-view.tsx +1 -0
package/bin/cli.js
CHANGED
|
@@ -3730,7 +3730,14 @@ var init_workspace_multiplexer = __esm({
|
|
|
3730
3730
|
WorkspacePaneKillArgumentsSchemaZ = /* @__PURE__ */ (() => WorkspaceScopedSchemaZ.extend({
|
|
3731
3731
|
semanticPaneId: TerminalAttachmentSemanticPaneIdSchemaZ
|
|
3732
3732
|
}).strict())();
|
|
3733
|
-
WorkspaceSessionKillArgumentsSchemaZ = /* @__PURE__ */ (() => WorkspaceScopedSchemaZ.
|
|
3733
|
+
WorkspaceSessionKillArgumentsSchemaZ = /* @__PURE__ */ (() => WorkspaceScopedSchemaZ.extend({
|
|
3734
|
+
/** Passive fleet actions fence the daemon and exact live session incarnation. */
|
|
3735
|
+
fleetTarget: z31.object({
|
|
3736
|
+
daemonInstanceId: z31.uuid(),
|
|
3737
|
+
liveSessionId: z31.string().regex(/^live-session\.[a-f0-9]{20}$/),
|
|
3738
|
+
sessionName: WorkspaceMultiplexerNameSchemaZ
|
|
3739
|
+
}).strict().optional()
|
|
3740
|
+
}).strict())();
|
|
3734
3741
|
WorkspaceRenameArgumentsSchemaZ = /* @__PURE__ */ (() => z31.discriminatedUnion("scope", [
|
|
3735
3742
|
WorkspaceScopedSchemaZ.extend({
|
|
3736
3743
|
scope: z31.literal("session"),
|
|
@@ -3938,7 +3945,11 @@ var init_fleet_lifecycle = __esm({
|
|
|
3938
3945
|
"display name contains control bytes"
|
|
3939
3946
|
).refine((value) => !value.startsWith("-"), "display name cannot be parsed as an option"))();
|
|
3940
3947
|
OwnerPathSchemaZ = /* @__PURE__ */ (() => z32.string().min(1).max(4096).refine((value) => !value.includes("\0")))();
|
|
3941
|
-
WorkspaceSessionCreateArgumentsSchemaZ = /* @__PURE__ */ (() => z32.object({
|
|
3948
|
+
WorkspaceSessionCreateArgumentsSchemaZ = /* @__PURE__ */ (() => z32.object({
|
|
3949
|
+
displayName: SafeDisplayNameSchemaZ,
|
|
3950
|
+
cwd: OwnerPathSchemaZ.optional(),
|
|
3951
|
+
expectedDaemonInstanceId: z32.uuid().optional()
|
|
3952
|
+
}).strict())();
|
|
3942
3953
|
WorkspaceSessionCreateResultSchemaZ = /* @__PURE__ */ (() => z32.object({
|
|
3943
3954
|
operationId: z32.uuid(),
|
|
3944
3955
|
daemonInstanceId: z32.uuid(),
|
|
@@ -10261,7 +10272,7 @@ var require_package = __commonJS({
|
|
|
10261
10272
|
"package.json"(exports, module) {
|
|
10262
10273
|
module.exports = {
|
|
10263
10274
|
name: "tmux-ide",
|
|
10264
|
-
version: "2.9.0-beta.
|
|
10275
|
+
version: "2.9.0-beta.17",
|
|
10265
10276
|
description: "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
10266
10277
|
type: "module",
|
|
10267
10278
|
bin: {
|
|
@@ -10323,7 +10334,7 @@ var require_package = __commonJS({
|
|
|
10323
10334
|
postinstall: "node scripts/postinstall.js",
|
|
10324
10335
|
docs: "turbo run dev --filter=@tmux-ide/docs",
|
|
10325
10336
|
"demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
|
|
10326
|
-
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx",
|
|
10337
|
+
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
|
|
10327
10338
|
"test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
|
|
10328
10339
|
"test:tui-live": "node scripts/tui-testdrive.mjs smoke",
|
|
10329
10340
|
"test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
|
|
@@ -18414,7 +18425,7 @@ import { z as z71 } from "zod";
|
|
|
18414
18425
|
import { bodyLimit } from "hono/body-limit";
|
|
18415
18426
|
import { stripVTControlCharacters } from "node:util";
|
|
18416
18427
|
function createFleetPreviewCapture(run) {
|
|
18417
|
-
|
|
18428
|
+
const snapshot = async (liveSessionId2, signal, windowId) => {
|
|
18418
18429
|
const readSessions = async () => {
|
|
18419
18430
|
const raw = await run(
|
|
18420
18431
|
["list-panes", "-a", "-F", "#{pid} #{session_id} #{session_created} #{session_name}"],
|
|
@@ -18424,24 +18435,80 @@ function createFleetPreviewCapture(run) {
|
|
|
18424
18435
|
};
|
|
18425
18436
|
const session = (await readSessions()).find((s) => s.liveSessionId === liveSessionId2);
|
|
18426
18437
|
if (!session) return null;
|
|
18427
|
-
const
|
|
18438
|
+
const readPanes = async () => (await run(
|
|
18428
18439
|
[
|
|
18429
18440
|
"list-panes",
|
|
18430
18441
|
"-s",
|
|
18431
18442
|
"-t",
|
|
18432
18443
|
`=${session.sessionName}`,
|
|
18433
18444
|
"-F",
|
|
18434
|
-
"#{pane_id} #{window_active} #{pane_active}"
|
|
18445
|
+
"#{pane_id} #{window_active} #{pane_active} #{window_id} #{window_index}"
|
|
18435
18446
|
],
|
|
18436
18447
|
signal
|
|
18448
|
+
)).trim().split("\n").map((line) => line.split(" "));
|
|
18449
|
+
const rows = await readPanes();
|
|
18450
|
+
const names = /* @__PURE__ */ new Map();
|
|
18451
|
+
const windowNames = await run(
|
|
18452
|
+
["list-windows", "-t", `=${session.sessionName}`, "-F", "#{window_id} #{window_name}"],
|
|
18453
|
+
signal
|
|
18437
18454
|
);
|
|
18438
|
-
const
|
|
18439
|
-
|
|
18455
|
+
for (const line of windowNames.split("\n")) {
|
|
18456
|
+
const tab = line.indexOf(" ");
|
|
18457
|
+
if (tab > 0)
|
|
18458
|
+
names.set(
|
|
18459
|
+
line.slice(0, tab),
|
|
18460
|
+
cleanText(line.slice(tab + 1)).replace(/[\n\t]/gu, " ").slice(0, 80)
|
|
18461
|
+
);
|
|
18462
|
+
}
|
|
18463
|
+
const windowRows = rows.filter((row) => /^@\d+$/u.test(row[3] ?? ""));
|
|
18464
|
+
const selectedWindowId = windowId ?? windowRows.find((row) => row[1] === "1")?.[3] ?? windowRows[0]?.[3] ?? null;
|
|
18465
|
+
if (windowId && !windowRows.some((row) => row[3] === windowId)) return null;
|
|
18466
|
+
const toWindow = (row) => ({
|
|
18467
|
+
id: row[3],
|
|
18468
|
+
index: Math.max(0, Number.parseInt(row[4] ?? "0", 10) || 0),
|
|
18469
|
+
name: names.get(row[3]) ?? `Window ${row[4]}`,
|
|
18470
|
+
active: row[1] === "1"
|
|
18471
|
+
});
|
|
18472
|
+
const windows = [];
|
|
18473
|
+
for (const row of windowRows) {
|
|
18474
|
+
if (windows.some((w) => w.id === row[3])) continue;
|
|
18475
|
+
if (windows.length >= 64) break;
|
|
18476
|
+
windows.push(toWindow(row));
|
|
18477
|
+
}
|
|
18478
|
+
if (selectedWindowId && !windows.some((w) => w.id === selectedWindowId)) {
|
|
18479
|
+
const selectedRow = windowRows.find((row) => row[3] === selectedWindowId);
|
|
18480
|
+
if (selectedRow) windows.splice(63, 1, toWindow(selectedRow));
|
|
18481
|
+
}
|
|
18482
|
+
let paneBudget = 256;
|
|
18483
|
+
for (const window2 of [...windows].sort(
|
|
18484
|
+
(a, b) => Number(b.id === selectedWindowId) - Number(a.id === selectedWindowId)
|
|
18485
|
+
)) {
|
|
18486
|
+
const ids = [
|
|
18487
|
+
...new Set(
|
|
18488
|
+
windowRows.filter((row) => row[3] === window2.id && /^%\d+$/u.test(row[0] ?? "")).map((row) => row[0])
|
|
18489
|
+
)
|
|
18490
|
+
];
|
|
18491
|
+
if (ids.length <= paneBudget) {
|
|
18492
|
+
window2.paneIds = ids;
|
|
18493
|
+
paneBudget -= ids.length;
|
|
18494
|
+
}
|
|
18495
|
+
}
|
|
18496
|
+
windows.sort((a, b) => a.index - b.index);
|
|
18497
|
+
const candidates = selectedWindowId ? rows.filter((r) => r[3] === selectedWindowId) : rows;
|
|
18498
|
+
const pane = candidates.find((r) => r[2] === "1")?.[0] ?? candidates[0]?.[0];
|
|
18440
18499
|
if (!pane || !/^%\d+$/u.test(pane)) return null;
|
|
18441
18500
|
const captured = await run(["capture-pane", "-p", "-t", pane, "-S", "-24"], signal);
|
|
18442
18501
|
if (!(await readSessions()).some((s) => s.liveSessionId === liveSessionId2)) return null;
|
|
18443
|
-
|
|
18502
|
+
if (selectedWindowId && !(await readPanes()).some((r) => r[3] === selectedWindowId && r[0] === pane))
|
|
18503
|
+
return null;
|
|
18504
|
+
return {
|
|
18505
|
+
windows,
|
|
18506
|
+
selectedWindowId,
|
|
18507
|
+
text: cleanText(captured).split("\n").slice(-24).map((line) => line.slice(0, 180)).join("\n").slice(0, 8192)
|
|
18508
|
+
};
|
|
18444
18509
|
};
|
|
18510
|
+
const capture = async (liveSessionId2, signal) => (await snapshot(liveSessionId2, signal))?.text ?? null;
|
|
18511
|
+
return Object.assign(capture, { snapshot });
|
|
18445
18512
|
}
|
|
18446
18513
|
function mountFleetPreviewRoute(app, options) {
|
|
18447
18514
|
const authorize = ownerAuthorityGate(options.ownerToken, {
|
|
@@ -18476,11 +18543,18 @@ function mountFleetPreviewRoute(app, options) {
|
|
|
18476
18543
|
next = now + 250;
|
|
18477
18544
|
pending = true;
|
|
18478
18545
|
try {
|
|
18479
|
-
const
|
|
18480
|
-
|
|
18481
|
-
|
|
18482
|
-
);
|
|
18483
|
-
|
|
18546
|
+
const signal = AbortSignal.any([c.req.raw.signal, AbortSignal.timeout(1500)]);
|
|
18547
|
+
if (input.data.windowId && !options.capture.snapshot)
|
|
18548
|
+
return c.json({ error: "Window preview unavailable" }, 503);
|
|
18549
|
+
const snapshot = options.capture.snapshot ? await options.capture.snapshot(input.data.liveSessionId, signal, input.data.windowId) : null;
|
|
18550
|
+
const legacy = !options.capture.snapshot ? await options.capture(input.data.liveSessionId, signal) : null;
|
|
18551
|
+
const text = snapshot?.text ?? legacy;
|
|
18552
|
+
return text === null ? c.json({ error: "Session changed" }, 409) : c.json({
|
|
18553
|
+
daemon: options.daemon,
|
|
18554
|
+
liveSessionId: input.data.liveSessionId,
|
|
18555
|
+
text,
|
|
18556
|
+
...snapshot ? { windows: snapshot.windows, selectedWindowId: snapshot.selectedWindowId } : {}
|
|
18557
|
+
});
|
|
18484
18558
|
} catch {
|
|
18485
18559
|
return c.json({ error: "Preview unavailable" }, 503);
|
|
18486
18560
|
} finally {
|
|
@@ -18488,14 +18562,16 @@ function mountFleetPreviewRoute(app, options) {
|
|
|
18488
18562
|
}
|
|
18489
18563
|
});
|
|
18490
18564
|
}
|
|
18491
|
-
var requestSchema;
|
|
18565
|
+
var cleanText, requestSchema;
|
|
18492
18566
|
var init_fleet_preview_route = __esm({
|
|
18493
18567
|
"packages/daemon/src/command-center/resources/fleet-preview-route.ts"() {
|
|
18494
18568
|
"use strict";
|
|
18495
18569
|
init_owner_authority();
|
|
18496
18570
|
init_discovery();
|
|
18571
|
+
cleanText = (value) => stripVTControlCharacters(value).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
18497
18572
|
requestSchema = z71.strictObject({
|
|
18498
18573
|
expectedInstanceId: z71.uuid(),
|
|
18574
|
+
windowId: z71.string().regex(/^@\d+$/u).optional(),
|
|
18499
18575
|
liveSessionId: z71.string().regex(/^live-session\.[a-f0-9]{20}$/u)
|
|
18500
18576
|
});
|
|
18501
18577
|
}
|
|
@@ -33670,6 +33746,7 @@ var CREATION_OPTION2, SEMANTIC_PANE_OPTION4, SEMANTIC_WINDOW_OPTION3, DISPLAY_TI
|
|
|
33670
33746
|
var init_workspace_multiplexer_verbs = __esm({
|
|
33671
33747
|
"packages/daemon/src/lib/workspace-multiplexer-verbs.ts"() {
|
|
33672
33748
|
"use strict";
|
|
33749
|
+
init_discovery();
|
|
33673
33750
|
init_src();
|
|
33674
33751
|
init_src2();
|
|
33675
33752
|
init_workspace_pane_creation2();
|
|
@@ -33888,6 +33965,37 @@ var init_workspace_multiplexer_verbs = __esm({
|
|
|
33888
33965
|
operationId: request.operationId
|
|
33889
33966
|
});
|
|
33890
33967
|
}
|
|
33968
|
+
if (request.intent.verb === "workspace.session.kill" && request.intent.fleetTarget) {
|
|
33969
|
+
const target = request.intent.fleetTarget;
|
|
33970
|
+
if (target.daemonInstanceId !== this.#daemonInstanceId)
|
|
33971
|
+
throw new WorkspaceMultiplexerError("daemon_instance_mismatch", {
|
|
33972
|
+
operationId: request.operationId
|
|
33973
|
+
});
|
|
33974
|
+
const raw2 = this.#io.runTmux([
|
|
33975
|
+
"list-panes",
|
|
33976
|
+
"-a",
|
|
33977
|
+
"-F",
|
|
33978
|
+
"#{pid} #{session_id} #{session_created} #{session_name}"
|
|
33979
|
+
]);
|
|
33980
|
+
const match = raw2.split("\n").find((line) => {
|
|
33981
|
+
const session = discoverLiveSessionSummaries(() => line)[0];
|
|
33982
|
+
return session?.liveSessionId === target.liveSessionId && session.sessionName === target.sessionName;
|
|
33983
|
+
});
|
|
33984
|
+
if (!match)
|
|
33985
|
+
throw new WorkspaceMultiplexerError("workspace_not_found", {
|
|
33986
|
+
reason: "session_incarnation_changed"
|
|
33987
|
+
});
|
|
33988
|
+
const runtimeId = match.split(" ")[1];
|
|
33989
|
+
return this.#killSession(
|
|
33990
|
+
target.sessionName,
|
|
33991
|
+
{
|
|
33992
|
+
operationId: request.operationId,
|
|
33993
|
+
daemonInstanceId: this.#daemonInstanceId,
|
|
33994
|
+
workspaceName: request.intent.workspaceName
|
|
33995
|
+
},
|
|
33996
|
+
runtimeId
|
|
33997
|
+
);
|
|
33998
|
+
}
|
|
33891
33999
|
const workspace = this.#registry.get(request.intent.workspaceName);
|
|
33892
34000
|
if (!workspace) {
|
|
33893
34001
|
throw new WorkspaceMultiplexerError("workspace_not_found", {
|
|
@@ -34128,19 +34236,19 @@ var init_workspace_multiplexer_verbs = __esm({
|
|
|
34128
34236
|
remainingWindowCount: windowIdsOf(after).length
|
|
34129
34237
|
};
|
|
34130
34238
|
}
|
|
34131
|
-
#killSession(sessionName, envelope) {
|
|
34239
|
+
#killSession(sessionName, envelope, exactTarget = `=${sessionName}`) {
|
|
34132
34240
|
let existed = true;
|
|
34133
34241
|
try {
|
|
34134
|
-
this.#io.runTmux(["has-session", "-t",
|
|
34242
|
+
this.#io.runTmux(["has-session", "-t", exactTarget]);
|
|
34135
34243
|
} catch (error) {
|
|
34136
34244
|
if (!this.#io.isMissingTmuxTarget(error)) throw error;
|
|
34137
34245
|
existed = false;
|
|
34138
34246
|
}
|
|
34139
34247
|
if (existed) {
|
|
34140
|
-
this.#io.runTmux(["kill-session", "-t",
|
|
34248
|
+
this.#io.runTmux(["kill-session", "-t", exactTarget]);
|
|
34141
34249
|
let stillPresent = true;
|
|
34142
34250
|
try {
|
|
34143
|
-
this.#io.runTmux(["has-session", "-t",
|
|
34251
|
+
this.#io.runTmux(["has-session", "-t", exactTarget]);
|
|
34144
34252
|
} catch {
|
|
34145
34253
|
stillPresent = false;
|
|
34146
34254
|
}
|
|
@@ -39533,7 +39641,7 @@ var init_semantic_mutation_executor = __esm({
|
|
|
39533
39641
|
intent = { ...intent, origin: authority.origin };
|
|
39534
39642
|
}
|
|
39535
39643
|
const authenticatedSourceSemanticPaneId = authority.authenticatedSourceSemanticPaneId ?? null;
|
|
39536
|
-
const session = this.#options.resolveSession(intent.workspaceName);
|
|
39644
|
+
const session = intent.verb === "workspace.session.kill" && intent.fleetTarget ? intent.fleetTarget.sessionName : this.#options.resolveSession(intent.workspaceName);
|
|
39537
39645
|
const ledger2 = this.#ledger(session ?? MISSING_SESSION_LEDGER);
|
|
39538
39646
|
const origin = authority.origin;
|
|
39539
39647
|
const fingerprint2 = JSON.stringify([intent, authenticatedSourceSemanticPaneId, origin]);
|
|
@@ -56807,7 +56915,7 @@ var init_registry2 = __esm({
|
|
|
56807
56915
|
if (this.#disposed) return Promise.reject(new Error("SessionRuntimeRegistry is disposed"));
|
|
56808
56916
|
runtime.assertController(lease);
|
|
56809
56917
|
let intent = SessionRuntimeSemanticIntentSchemaZ.parse(rawIntent);
|
|
56810
|
-
const resolvedSession = this.#resolveSession?.(intent.workspaceName) ?? null;
|
|
56918
|
+
const resolvedSession = intent.verb === "workspace.session.kill" && intent.fleetTarget ? intent.fleetTarget.daemonInstanceId === this.generation ? intent.fleetTarget.sessionName : null : this.#resolveSession?.(intent.workspaceName) ?? null;
|
|
56811
56919
|
if (resolvedSession !== runtime.session) {
|
|
56812
56920
|
return Promise.reject(
|
|
56813
56921
|
new SessionRuntimeControllerLeaseError(
|
|
@@ -64732,6 +64840,14 @@ var init_pane_stream_websocket = __esm({
|
|
|
64732
64840
|
this.close(1011, "stream-unavailable");
|
|
64733
64841
|
return;
|
|
64734
64842
|
}
|
|
64843
|
+
const visibleAtOpen = new Set(
|
|
64844
|
+
stagedAuthority.layouts.flatMap(
|
|
64845
|
+
(layout) => layout.currentWindow ? layout.panes.map((pane) => pane.semanticPaneId) : []
|
|
64846
|
+
)
|
|
64847
|
+
);
|
|
64848
|
+
channels.sort(
|
|
64849
|
+
(left, right) => Number(visibleAtOpen.has(right.semanticPaneId)) - Number(visibleAtOpen.has(left.semanticPaneId))
|
|
64850
|
+
);
|
|
64735
64851
|
const openings = channels.map(async (channel) => {
|
|
64736
64852
|
const pending = [];
|
|
64737
64853
|
let ready = false;
|
|
@@ -64802,7 +64918,15 @@ var init_pane_stream_websocket = __esm({
|
|
|
64802
64918
|
});
|
|
64803
64919
|
layoutActivated = true;
|
|
64804
64920
|
this.#recordDiagnosticLifecycle("pane-stream-delivery-open");
|
|
64805
|
-
|
|
64921
|
+
const visibleAtDelivery = new Set(
|
|
64922
|
+
authority.layouts.flatMap(
|
|
64923
|
+
(layout) => layout.currentWindow ? layout.panes.map((pane) => pane.semanticPaneId) : []
|
|
64924
|
+
)
|
|
64925
|
+
);
|
|
64926
|
+
opened.sort(
|
|
64927
|
+
(left, right) => Number(visibleAtDelivery.has(right.channel.semanticPaneId)) - Number(visibleAtDelivery.has(left.channel.semanticPaneId))
|
|
64928
|
+
);
|
|
64929
|
+
for (const { channel, delivery } of opened) {
|
|
64806
64930
|
if (this.#closed || channel.closed) {
|
|
64807
64931
|
await delivery.close();
|
|
64808
64932
|
continue;
|
|
@@ -64824,6 +64948,9 @@ var init_pane_stream_websocket = __esm({
|
|
|
64824
64948
|
pane: channel.semanticPaneId,
|
|
64825
64949
|
negotiation: delivery.negotiation
|
|
64826
64950
|
});
|
|
64951
|
+
}
|
|
64952
|
+
for (const { channel, pending, markReady } of opened) {
|
|
64953
|
+
if (this.#closed || channel.closed) continue;
|
|
64827
64954
|
markReady();
|
|
64828
64955
|
for (const message of pending)
|
|
64829
64956
|
await this.#sendTerminalDelivery(channel.semanticPaneId, message);
|
|
@@ -65895,7 +66022,16 @@ function createSessionRuntimeMultiplexerBackend(options) {
|
|
|
65895
66022
|
};
|
|
65896
66023
|
return {
|
|
65897
66024
|
mutate: async (request, authenticatedHostClientId, sourcePaneCredential, ownerAuthorized = false) => {
|
|
65898
|
-
const
|
|
66025
|
+
const fleetTarget = request.intent.verb === "workspace.session.kill" ? request.intent.fleetTarget : void 0;
|
|
66026
|
+
if (fleetTarget && (!ownerAuthorized || authenticatedHostClientId || sourcePaneCredential))
|
|
66027
|
+
throw new WorkspaceMultiplexerError("operation_conflict", {
|
|
66028
|
+
reason: "fleet_close_requires_explicit_owner"
|
|
66029
|
+
});
|
|
66030
|
+
if (fleetTarget && fleetTarget.daemonInstanceId !== options.registry.generation)
|
|
66031
|
+
throw new WorkspaceMultiplexerError("daemon_instance_mismatch", {
|
|
66032
|
+
operationId: request.operationId
|
|
66033
|
+
});
|
|
66034
|
+
const session = fleetTarget && ownerAuthorized && !authenticatedHostClientId && !sourcePaneCredential ? fleetTarget.sessionName : options.resolveSession(request.intent.workspaceName);
|
|
65899
66035
|
if (!session) {
|
|
65900
66036
|
throw new Error(`Workspace ${request.intent.workspaceName} has no live tmux session`);
|
|
65901
66037
|
}
|
|
@@ -68774,6 +68910,11 @@ async function workspaceSessionCreateHandler(input, context) {
|
|
|
68774
68910
|
message: "Fleet lifecycle is unavailable."
|
|
68775
68911
|
});
|
|
68776
68912
|
const [operation, generation] = authorityContext(context);
|
|
68913
|
+
if (input.expectedDaemonInstanceId && input.expectedDaemonInstanceId !== generation)
|
|
68914
|
+
throw new ActionError({
|
|
68915
|
+
code: "bad_request",
|
|
68916
|
+
message: "The selected daemon has been replaced."
|
|
68917
|
+
});
|
|
68777
68918
|
return await mapAuthority(
|
|
68778
68919
|
() => context.fleetLifecycleBackend.createSession(operation, generation, input)
|
|
68779
68920
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-ide",
|
|
3
|
-
"version": "2.9.0-beta.
|
|
3
|
+
"version": "2.9.0-beta.17",
|
|
4
4
|
"description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"postinstall": "node scripts/postinstall.js",
|
|
63
63
|
"docs": "turbo run dev --filter=@tmux-ide/docs",
|
|
64
64
|
"demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
|
|
65
|
-
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx",
|
|
65
|
+
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
|
|
66
66
|
"test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
|
|
67
67
|
"test:tui-live": "node scripts/tui-testdrive.mjs smoke",
|
|
68
68
|
"test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
|
|
@@ -27,7 +27,11 @@ const OwnerPathSchemaZ = z
|
|
|
27
27
|
.max(4_096)
|
|
28
28
|
.refine((value) => !value.includes("\0"));
|
|
29
29
|
export const WorkspaceSessionCreateArgumentsSchemaZ = z
|
|
30
|
-
.object({
|
|
30
|
+
.object({
|
|
31
|
+
displayName: SafeDisplayNameSchemaZ,
|
|
32
|
+
cwd: OwnerPathSchemaZ.optional(),
|
|
33
|
+
expectedDaemonInstanceId: z.uuid().optional(),
|
|
34
|
+
})
|
|
31
35
|
.strict();
|
|
32
36
|
export type WorkspaceSessionCreateArguments = z.infer<
|
|
33
37
|
typeof WorkspaceSessionCreateArgumentsSchemaZ
|
|
@@ -84,7 +84,17 @@ export const WorkspacePaneKillArgumentsSchemaZ = WorkspaceScopedSchemaZ.extend({
|
|
|
84
84
|
}).strict();
|
|
85
85
|
export type WorkspacePaneKillArguments = z.infer<typeof WorkspacePaneKillArgumentsSchemaZ>;
|
|
86
86
|
|
|
87
|
-
export const WorkspaceSessionKillArgumentsSchemaZ = WorkspaceScopedSchemaZ.
|
|
87
|
+
export const WorkspaceSessionKillArgumentsSchemaZ = WorkspaceScopedSchemaZ.extend({
|
|
88
|
+
/** Passive fleet actions fence the daemon and exact live session incarnation. */
|
|
89
|
+
fleetTarget: z
|
|
90
|
+
.object({
|
|
91
|
+
daemonInstanceId: z.uuid(),
|
|
92
|
+
liveSessionId: z.string().regex(/^live-session\.[a-f0-9]{20}$/),
|
|
93
|
+
sessionName: WorkspaceMultiplexerNameSchemaZ,
|
|
94
|
+
})
|
|
95
|
+
.strict()
|
|
96
|
+
.optional(),
|
|
97
|
+
}).strict();
|
|
88
98
|
export type WorkspaceSessionKillArguments = z.infer<typeof WorkspaceSessionKillArgumentsSchemaZ>;
|
|
89
99
|
|
|
90
100
|
export const WorkspaceRenameArgumentsSchemaZ = z.discriminatedUnion("scope", [
|
|
@@ -34,6 +34,11 @@ export async function workspaceSessionCreateHandler(input, context) {
|
|
|
34
34
|
message: "Fleet lifecycle is unavailable.",
|
|
35
35
|
});
|
|
36
36
|
const [operation, generation] = authorityContext(context);
|
|
37
|
+
if (input.expectedDaemonInstanceId && input.expectedDaemonInstanceId !== generation)
|
|
38
|
+
throw new ActionError({
|
|
39
|
+
code: "bad_request",
|
|
40
|
+
message: "The selected daemon has been replaced.",
|
|
41
|
+
});
|
|
37
42
|
return await mapAuthority(() => context.fleetLifecycleBackend.createSession(operation, generation, input));
|
|
38
43
|
}
|
|
39
44
|
export async function fleetAgentMutateHandler(input, context) {
|
|
@@ -3,8 +3,9 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
3
3
|
import { stripVTControlCharacters } from "node:util";
|
|
4
4
|
import { ownerAuthorityGate } from "../owner-authority.js";
|
|
5
5
|
import { discoverLiveSessionSummaries } from "../discovery.js";
|
|
6
|
+
const cleanText = (value) => stripVTControlCharacters(value).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
6
7
|
export function createFleetPreviewCapture(run) {
|
|
7
|
-
|
|
8
|
+
const snapshot = async (liveSessionId, signal, windowId) => {
|
|
8
9
|
const readSessions = async () => {
|
|
9
10
|
const raw = await run(["list-panes", "-a", "-F", "#{pid}\t#{session_id}\t#{session_created}\t#{session_name}"], signal);
|
|
10
11
|
return discoverLiveSessionSummaries(() => raw);
|
|
@@ -12,36 +13,99 @@ export function createFleetPreviewCapture(run) {
|
|
|
12
13
|
const session = (await readSessions()).find((s) => s.liveSessionId === liveSessionId);
|
|
13
14
|
if (!session)
|
|
14
15
|
return null;
|
|
15
|
-
const
|
|
16
|
+
const readPanes = async () => (await run([
|
|
16
17
|
"list-panes",
|
|
17
18
|
"-s",
|
|
18
19
|
"-t",
|
|
19
20
|
`=${session.sessionName}`,
|
|
20
21
|
"-F",
|
|
21
|
-
"#{pane_id}\t#{window_active}\t#{pane_active}",
|
|
22
|
-
], signal)
|
|
23
|
-
const rows = panes
|
|
22
|
+
"#{pane_id}\t#{window_active}\t#{pane_active}\t#{window_id}\t#{window_index}",
|
|
23
|
+
], signal))
|
|
24
24
|
.trim()
|
|
25
25
|
.split("\n")
|
|
26
26
|
.map((line) => line.split("\t"));
|
|
27
|
-
const
|
|
27
|
+
const rows = await readPanes();
|
|
28
|
+
// Names are untrusted display text. Keep them out of the pane membership
|
|
29
|
+
// format so embedded newlines cannot manufacture a capture target.
|
|
30
|
+
const names = new Map();
|
|
31
|
+
const windowNames = await run(["list-windows", "-t", `=${session.sessionName}`, "-F", "#{window_id}\t#{window_name}"], signal);
|
|
32
|
+
for (const line of windowNames.split("\n")) {
|
|
33
|
+
const tab = line.indexOf("\t");
|
|
34
|
+
if (tab > 0)
|
|
35
|
+
names.set(line.slice(0, tab), cleanText(line.slice(tab + 1))
|
|
36
|
+
.replace(/[\n\t]/gu, " ")
|
|
37
|
+
.slice(0, 80));
|
|
38
|
+
}
|
|
39
|
+
const windowRows = rows.filter((row) => /^@\d+$/u.test(row[3] ?? ""));
|
|
40
|
+
const selectedWindowId = windowId ?? windowRows.find((row) => row[1] === "1")?.[3] ?? windowRows[0]?.[3] ?? null;
|
|
41
|
+
if (windowId && !windowRows.some((row) => row[3] === windowId))
|
|
42
|
+
return null;
|
|
43
|
+
const toWindow = (row) => ({
|
|
44
|
+
id: row[3],
|
|
45
|
+
index: Math.max(0, Number.parseInt(row[4] ?? "0", 10) || 0),
|
|
46
|
+
name: names.get(row[3]) ?? `Window ${row[4]}`,
|
|
47
|
+
active: row[1] === "1",
|
|
48
|
+
});
|
|
49
|
+
const windows = [];
|
|
50
|
+
for (const row of windowRows) {
|
|
51
|
+
if (windows.some((w) => w.id === row[3]))
|
|
52
|
+
continue;
|
|
53
|
+
if (windows.length >= 64)
|
|
54
|
+
break;
|
|
55
|
+
windows.push(toWindow(row));
|
|
56
|
+
}
|
|
57
|
+
// Preserve the actual active/requested target even on unusually large sessions.
|
|
58
|
+
if (selectedWindowId && !windows.some((w) => w.id === selectedWindowId)) {
|
|
59
|
+
const selectedRow = windowRows.find((row) => row[3] === selectedWindowId);
|
|
60
|
+
if (selectedRow)
|
|
61
|
+
windows.splice(63, 1, toWindow(selectedRow));
|
|
62
|
+
}
|
|
63
|
+
let paneBudget = 256;
|
|
64
|
+
// The selected window gets the membership budget first; omitted membership
|
|
65
|
+
// remains explicitly unknown instead of reporting incomplete activity counts.
|
|
66
|
+
for (const window of [...windows].sort((a, b) => Number(b.id === selectedWindowId) - Number(a.id === selectedWindowId))) {
|
|
67
|
+
const ids = [
|
|
68
|
+
...new Set(windowRows
|
|
69
|
+
.filter((row) => row[3] === window.id && /^%\d+$/u.test(row[0] ?? ""))
|
|
70
|
+
.map((row) => row[0])),
|
|
71
|
+
];
|
|
72
|
+
if (ids.length <= paneBudget) {
|
|
73
|
+
window.paneIds = ids;
|
|
74
|
+
paneBudget -= ids.length;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
windows.sort((a, b) => a.index - b.index);
|
|
78
|
+
const candidates = selectedWindowId ? rows.filter((r) => r[3] === selectedWindowId) : rows;
|
|
79
|
+
const pane = candidates.find((r) => r[2] === "1")?.[0] ?? candidates[0]?.[0];
|
|
28
80
|
if (!pane || !/^%\d+$/u.test(pane))
|
|
29
81
|
return null;
|
|
30
|
-
//
|
|
82
|
+
// Passive capture never changes active windows, size, input or terminal ownership.
|
|
31
83
|
const captured = await run(["capture-pane", "-p", "-t", pane, "-S", "-24"], signal);
|
|
32
84
|
if (!(await readSessions()).some((s) => s.liveSessionId === liveSessionId))
|
|
33
85
|
return null;
|
|
34
|
-
|
|
35
|
-
.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
86
|
+
if (selectedWindowId &&
|
|
87
|
+
!(await readPanes()).some((r) => r[3] === selectedWindowId && r[0] === pane))
|
|
88
|
+
return null;
|
|
89
|
+
return {
|
|
90
|
+
windows,
|
|
91
|
+
selectedWindowId,
|
|
92
|
+
text: cleanText(captured)
|
|
93
|
+
.split("\n")
|
|
94
|
+
.slice(-24)
|
|
95
|
+
.map((line) => line.slice(0, 180))
|
|
96
|
+
.join("\n")
|
|
97
|
+
.slice(0, 8192),
|
|
98
|
+
};
|
|
41
99
|
};
|
|
100
|
+
const capture = async (liveSessionId, signal) => (await snapshot(liveSessionId, signal))?.text ?? null;
|
|
101
|
+
return Object.assign(capture, { snapshot });
|
|
42
102
|
}
|
|
43
103
|
const requestSchema = z.strictObject({
|
|
44
104
|
expectedInstanceId: z.uuid(),
|
|
105
|
+
windowId: z
|
|
106
|
+
.string()
|
|
107
|
+
.regex(/^@\d+$/u)
|
|
108
|
+
.optional(),
|
|
45
109
|
liveSessionId: z.string().regex(/^live-session\.[a-f0-9]{20}$/u),
|
|
46
110
|
});
|
|
47
111
|
export function mountFleetPreviewRoute(app, options) {
|
|
@@ -83,10 +147,27 @@ export function mountFleetPreviewRoute(app, options) {
|
|
|
83
147
|
next = now + 250;
|
|
84
148
|
pending = true;
|
|
85
149
|
try {
|
|
86
|
-
const
|
|
150
|
+
const signal = AbortSignal.any([c.req.raw.signal, AbortSignal.timeout(1500)]);
|
|
151
|
+
if (input.data.windowId && !options.capture.snapshot)
|
|
152
|
+
return c.json({ error: "Window preview unavailable" }, 503);
|
|
153
|
+
const snapshot = options.capture.snapshot
|
|
154
|
+
? await options.capture.snapshot(input.data.liveSessionId, signal, input.data.windowId)
|
|
155
|
+
: null;
|
|
156
|
+
// Older capture providers remain valid for the original text-only contract.
|
|
157
|
+
const legacy = !options.capture.snapshot
|
|
158
|
+
? await options.capture(input.data.liveSessionId, signal)
|
|
159
|
+
: null;
|
|
160
|
+
const text = snapshot?.text ?? legacy;
|
|
87
161
|
return text === null
|
|
88
162
|
? c.json({ error: "Session changed" }, 409)
|
|
89
|
-
: c.json({
|
|
163
|
+
: c.json({
|
|
164
|
+
daemon: options.daemon,
|
|
165
|
+
liveSessionId: input.data.liveSessionId,
|
|
166
|
+
text,
|
|
167
|
+
...(snapshot
|
|
168
|
+
? { windows: snapshot.windows, selectedWindowId: snapshot.selectedWindowId }
|
|
169
|
+
: {}),
|
|
170
|
+
});
|
|
90
171
|
}
|
|
91
172
|
catch {
|
|
92
173
|
return c.json({ error: "Preview unavailable" }, 503);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|