tmux-ide 2.9.0-beta.27 → 2.9.0-beta.29
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 +135 -17
- package/package.json +1 -1
- package/packages/daemon/dist/lib/daemon-embed.js +7 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +7 -0
- package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +79 -14
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +11 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +58 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +5 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-appearance-owner.js +13 -4
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +5 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-experience.js +1 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-command-owner.js +18 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-input.js +4 -1
- package/packages/daemon/dist/tui/mirror/ui/dialog.jsx +1 -1
- package/packages/daemon/dist/tui/mirror/ui/overlay-frame.jsx +24 -8
- package/packages/daemon/dist/tui/mirror/ui/overlay-list-row.jsx +34 -5
- package/packages/daemon/dist/tui/mirror/ui/overlay-model.js +7 -0
- package/packages/daemon/dist/tui/mirror/ui/overlay-surface.js +13 -0
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +17 -7
- package/packages/daemon/dist/tui/mirror/workspace/command-palette-surface-view.jsx +22 -16
- package/packages/daemon/dist/tui/mirror/workspace/command-palette-surface.js +9 -14
- package/packages/daemon/dist/tui/mirror/workspace/palette-list-layout.js +47 -0
- package/packages/daemon/src/lib/daemon-embed.ts +9 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +9 -0
- package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +89 -14
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +11 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +63 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +5 -0
- package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +12 -12
- package/packages/daemon/src/tui/mirror/runtime/application-appearance-owner.ts +12 -4
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-switcher.tsx +1 -3
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +6 -1
- package/packages/daemon/src/tui/mirror/runtime/application-home-experience.ts +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-palette-command-owner.ts +19 -0
- package/packages/daemon/src/tui/mirror/runtime/application-palette-input.ts +4 -1
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +2 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +174 -140
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +6 -0
- package/packages/daemon/src/tui/mirror/runtime/palette-production-overlay.tsx +5 -8
- package/packages/daemon/src/tui/mirror/ui/dialog.tsx +2 -0
- package/packages/daemon/src/tui/mirror/ui/overlay-frame.tsx +49 -14
- package/packages/daemon/src/tui/mirror/ui/overlay-list-row.tsx +46 -9
- package/packages/daemon/src/tui/mirror/ui/overlay-model.ts +8 -0
- package/packages/daemon/src/tui/mirror/ui/overlay-search-field.tsx +34 -0
- package/packages/daemon/src/tui/mirror/ui/overlay-surface.ts +19 -0
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +168 -168
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +27 -8
- package/packages/daemon/src/tui/mirror/workspace/command-palette-surface-view.tsx +24 -16
- package/packages/daemon/src/tui/mirror/workspace/command-palette-surface.ts +9 -14
- package/packages/daemon/src/tui/mirror/workspace/palette-list-layout.ts +55 -0
package/bin/cli.js
CHANGED
|
@@ -11279,7 +11279,7 @@ var require_package = __commonJS({
|
|
|
11279
11279
|
"package.json"(exports, module) {
|
|
11280
11280
|
module.exports = {
|
|
11281
11281
|
name: "tmux-ide",
|
|
11282
|
-
version: "2.9.0-beta.
|
|
11282
|
+
version: "2.9.0-beta.29",
|
|
11283
11283
|
description: "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
11284
11284
|
type: "module",
|
|
11285
11285
|
bin: {
|
|
@@ -29081,6 +29081,38 @@ var init_session_channel = __esm({
|
|
|
29081
29081
|
this.windowLinkAuthority = new WindowLinkAuthority(liveSessionId, identity2.runtimeSessionId);
|
|
29082
29082
|
if (this.latestWindowStage) this.windowLinkAuthority.reconcile(this.latestWindowStage.links);
|
|
29083
29083
|
}
|
|
29084
|
+
/** Capture this attached connection once; never resolve a replacement mid-operation. */
|
|
29085
|
+
paneResizeTransport() {
|
|
29086
|
+
const identity2 = this.attachedIdentity;
|
|
29087
|
+
const generation = this.attachedServerGeneration;
|
|
29088
|
+
const assertCurrent = () => {
|
|
29089
|
+
if (this.disposed || !identity2 || !generation || this.attachedIdentity !== identity2 || this.attachedServerGeneration !== generation)
|
|
29090
|
+
throw new Error("Resize control connection retired");
|
|
29091
|
+
};
|
|
29092
|
+
assertCurrent();
|
|
29093
|
+
return async (args) => {
|
|
29094
|
+
assertCurrent();
|
|
29095
|
+
const listing = args.length === 6 && args[0] === "list-panes" && args[1] === "-s" && args[2] === "-t" && args[3] === `=${identity2.sessionName}` && args[4] === "-F";
|
|
29096
|
+
const resize = args.length === 11 && args[0] === "resize-pane" && args[1] === "-t" && /^%[0-9]+$/u.test(args[2]) && (args[3] === "-x" || args[3] === "-y") && /^[1-9][0-9]*$/u.test(args[4]) && args[5] === ";" && args[6] === "display-message" && args[7] === "-p" && args[8] === "-t" && args[9] === args[2] && args[10] === (args[3] === "-x" ? "#{pane_width}" : "#{pane_height}");
|
|
29097
|
+
if (!listing && !resize) throw new Error("Invalid resize control command");
|
|
29098
|
+
const pinned = listing ? [...args.slice(0, 3), identity2.runtimeSessionId, ...args.slice(4)] : args;
|
|
29099
|
+
const command2 = pinned.map((arg, index) => resize && index === 5 ? ";" : tmuxSingleQuote(arg)).join(" ");
|
|
29100
|
+
const lines = listing ? await this.io.request(command2) : await new Promise((resolve41, reject) => {
|
|
29101
|
+
this.io.commandListInline(
|
|
29102
|
+
command2,
|
|
29103
|
+
2,
|
|
29104
|
+
1,
|
|
29105
|
+
(reply) => reply.ok ? resolve41(reply.lines) : reject(new Error("Resize control command failed"))
|
|
29106
|
+
);
|
|
29107
|
+
});
|
|
29108
|
+
assertCurrent();
|
|
29109
|
+
return lines.map((line) => {
|
|
29110
|
+
const decoded = decodeControlReplyUtf8(line);
|
|
29111
|
+
if (decoded === null) throw new Error("Invalid resize control reply");
|
|
29112
|
+
return decoded;
|
|
29113
|
+
}).join("\n");
|
|
29114
|
+
};
|
|
29115
|
+
}
|
|
29084
29116
|
async executeWindowLinkAction(request3) {
|
|
29085
29117
|
const authority = this.windowLinkAuthority;
|
|
29086
29118
|
if (!authority || this.disposed) throw new WindowLinkResolutionError("window_link_stale");
|
|
@@ -31552,6 +31584,16 @@ var init_mirror_service = __esm({
|
|
|
31552
31584
|
}
|
|
31553
31585
|
};
|
|
31554
31586
|
}
|
|
31587
|
+
paneResizeTransport(session) {
|
|
31588
|
+
const entry = this.channels.get(session);
|
|
31589
|
+
if (!entry || entry.retired) throw new Error(`Mirror session ${session} is unavailable`);
|
|
31590
|
+
const run = entry.channel.paneResizeTransport();
|
|
31591
|
+
return (args) => {
|
|
31592
|
+
if (entry.retired || this.channels.get(session) !== entry)
|
|
31593
|
+
return Promise.reject(new Error("Resize mirror session retired"));
|
|
31594
|
+
return run(args);
|
|
31595
|
+
};
|
|
31596
|
+
}
|
|
31555
31597
|
async executeWindowLinkAction(session, request3) {
|
|
31556
31598
|
const entry = this.channels.get(session);
|
|
31557
31599
|
if (!entry || entry.retired) throw new Error(`Mirror session ${session} is unavailable`);
|
|
@@ -51699,6 +51741,10 @@ var init_registry2 = __esm({
|
|
|
51699
51741
|
return (this.#sessions.get(session)?.trustedInventoryQualified() ?? false) && this.#mirror.hasRetainedSession(session);
|
|
51700
51742
|
}
|
|
51701
51743
|
/** Called by the serialized semantic mutation executor, never by a transport directly. */
|
|
51744
|
+
paneResizeTransport(session) {
|
|
51745
|
+
if (this.#disposed) throw new Error("Session runtime disposed");
|
|
51746
|
+
return this.#mirror.paneResizeTransport(session);
|
|
51747
|
+
}
|
|
51702
51748
|
executeWindowLinkAction(session, request3) {
|
|
51703
51749
|
if (this.#disposed) return Promise.reject(new Error("Session runtime disposed"));
|
|
51704
51750
|
return this.#mirror.executeWindowLinkAction(session, request3);
|
|
@@ -62112,7 +62158,7 @@ function parseMultiplexerPaneRows(output) {
|
|
|
62112
62158
|
const rows = [];
|
|
62113
62159
|
for (const line of output.split("\n")) {
|
|
62114
62160
|
const fields = line.split(" ");
|
|
62115
|
-
if (fields.length !== 9) {
|
|
62161
|
+
if (fields.length !== 9 && fields.length !== 11) {
|
|
62116
62162
|
throw new WorkspaceMultiplexerError("workspace_unavailable", {
|
|
62117
62163
|
reason: "pane_listing_shape"
|
|
62118
62164
|
});
|
|
@@ -62135,6 +62181,11 @@ function parseMultiplexerPaneRows(output) {
|
|
|
62135
62181
|
}
|
|
62136
62182
|
const count = Number(paneCount);
|
|
62137
62183
|
const index = Number(paneIndex);
|
|
62184
|
+
const geometry = fields.length === 11 ? { width: Number(fields[9]), height: Number(fields[10]) } : {};
|
|
62185
|
+
if (fields.length === 11 && (!Number.isSafeInteger(geometry.width) || geometry.width < 1 || !Number.isSafeInteger(geometry.height) || geometry.height < 1))
|
|
62186
|
+
throw new WorkspaceMultiplexerError("workspace_unavailable", {
|
|
62187
|
+
reason: "pane_listing_geometry"
|
|
62188
|
+
});
|
|
62138
62189
|
if (!Number.isInteger(count) || count < 1 || !Number.isInteger(index) || index < 0) {
|
|
62139
62190
|
throw new WorkspaceMultiplexerError("workspace_unavailable", {
|
|
62140
62191
|
reason: "pane_listing_shape"
|
|
@@ -62149,7 +62200,8 @@ function parseMultiplexerPaneRows(output) {
|
|
|
62149
62200
|
windowPaneCount: count,
|
|
62150
62201
|
windowZoomed: zoomed === "1",
|
|
62151
62202
|
paneActive: active2 === "1",
|
|
62152
|
-
creationId: creationId === "" ? null : creationId
|
|
62203
|
+
creationId: creationId === "" ? null : creationId,
|
|
62204
|
+
...geometry
|
|
62153
62205
|
});
|
|
62154
62206
|
}
|
|
62155
62207
|
const unique = /* @__PURE__ */ new Map();
|
|
@@ -62296,6 +62348,41 @@ var init_workspace_multiplexer_verbs = __esm({
|
|
|
62296
62348
|
mutate(raw, timing) {
|
|
62297
62349
|
return this.#mutate(raw, timing);
|
|
62298
62350
|
}
|
|
62351
|
+
/** Retained control transport; owned by the same serialized semantic lane. */
|
|
62352
|
+
async mutateResize(raw, transport) {
|
|
62353
|
+
if (this.#disposed) throw new WorkspaceMultiplexerError("workspace_unavailable");
|
|
62354
|
+
const request3 = WorkspaceMultiplexerMutationRequestSchemaZ.parse(raw);
|
|
62355
|
+
if (request3.expectedDaemonInstanceId !== this.#daemonInstanceId)
|
|
62356
|
+
throw new WorkspaceMultiplexerError("daemon_instance_mismatch");
|
|
62357
|
+
const intent = request3.intent;
|
|
62358
|
+
if (intent.verb !== "workspace.pane.resize") throw new TypeError("Expected pane resize");
|
|
62359
|
+
const workspace = this.#registry.get(intent.workspaceName);
|
|
62360
|
+
if (!workspace) throw new WorkspaceMultiplexerError("workspace_not_found");
|
|
62361
|
+
const envelope = {
|
|
62362
|
+
operationId: request3.operationId,
|
|
62363
|
+
daemonInstanceId: this.#daemonInstanceId,
|
|
62364
|
+
workspaceName: intent.workspaceName
|
|
62365
|
+
};
|
|
62366
|
+
try {
|
|
62367
|
+
const run = transport(workspace.sessionName);
|
|
62368
|
+
const steps = this.#resizeSteps(intent, workspace.sessionName, envelope);
|
|
62369
|
+
let next = steps.next();
|
|
62370
|
+
while (!next.done) {
|
|
62371
|
+
if (this.#disposed) throw new WorkspaceMultiplexerError("workspace_unavailable");
|
|
62372
|
+
const observed = await run(next.value);
|
|
62373
|
+
if (this.#disposed) throw new WorkspaceMultiplexerError("workspace_unavailable");
|
|
62374
|
+
next = steps.next(observed);
|
|
62375
|
+
}
|
|
62376
|
+
return WorkspaceMultiplexerMutationResultSchemaZ.parse(next.value);
|
|
62377
|
+
} catch (cause) {
|
|
62378
|
+
if (cause instanceof WorkspaceMultiplexerError) throw cause;
|
|
62379
|
+
throw new WorkspaceMultiplexerError(
|
|
62380
|
+
"mutation_unverified",
|
|
62381
|
+
{ operationId: request3.operationId },
|
|
62382
|
+
cause
|
|
62383
|
+
);
|
|
62384
|
+
}
|
|
62385
|
+
}
|
|
62299
62386
|
/** Runs only inside the existing semantic mutation lane; never rediscovers a stale link. */
|
|
62300
62387
|
async mutateWindowLink(raw, execute2) {
|
|
62301
62388
|
if (this.#disposed) throw new WorkspaceMultiplexerError("workspace_unavailable");
|
|
@@ -63201,14 +63288,7 @@ var init_workspace_multiplexer_verbs = __esm({
|
|
|
63201
63288
|
// resize
|
|
63202
63289
|
// -------------------------------------------------------------------------
|
|
63203
63290
|
/** The pane's own size on one axis, in cells, read straight from tmux. */
|
|
63204
|
-
#paneCells(
|
|
63205
|
-
const observed = this.#io.runTmux([
|
|
63206
|
-
"display-message",
|
|
63207
|
-
"-p",
|
|
63208
|
-
"-t",
|
|
63209
|
-
paneId,
|
|
63210
|
-
axis === "cols" ? "#{pane_width}" : "#{pane_height}"
|
|
63211
|
-
]);
|
|
63291
|
+
#paneCells(observed) {
|
|
63212
63292
|
const cells = Number(observed);
|
|
63213
63293
|
if (!Number.isInteger(cells) || cells < 1) {
|
|
63214
63294
|
throw new WorkspaceMultiplexerError("mutation_unverified", { reason: "pane_size_shape" });
|
|
@@ -63226,7 +63306,21 @@ var init_workspace_multiplexer_verbs = __esm({
|
|
|
63226
63306
|
* whole view exists to remove.
|
|
63227
63307
|
*/
|
|
63228
63308
|
#resize(intent, sessionName, envelope) {
|
|
63229
|
-
const
|
|
63309
|
+
const steps = this.#resizeSteps(intent, sessionName, envelope);
|
|
63310
|
+
let next = steps.next();
|
|
63311
|
+
while (!next.done) next = steps.next(this.#io.runTmux(next.value));
|
|
63312
|
+
return next.value;
|
|
63313
|
+
}
|
|
63314
|
+
*#resizeSteps(intent, sessionName, envelope) {
|
|
63315
|
+
const output = yield [
|
|
63316
|
+
"list-panes",
|
|
63317
|
+
"-s",
|
|
63318
|
+
"-t",
|
|
63319
|
+
`=${sessionName}`,
|
|
63320
|
+
"-F",
|
|
63321
|
+
`${PANE_FIELDS} #{pane_width} #{pane_height}`
|
|
63322
|
+
];
|
|
63323
|
+
const rows = parseMultiplexerPaneRows(output);
|
|
63230
63324
|
const pane = resolvePaneRow(rows, intent.semanticPaneId);
|
|
63231
63325
|
if (pane.windowPaneCount < 2) {
|
|
63232
63326
|
throw new WorkspaceMultiplexerError("single_pane_window", {
|
|
@@ -63238,7 +63332,7 @@ var init_workspace_multiplexer_verbs = __esm({
|
|
|
63238
63332
|
semanticPaneId: intent.semanticPaneId
|
|
63239
63333
|
});
|
|
63240
63334
|
}
|
|
63241
|
-
const before = this.#paneCells(pane.
|
|
63335
|
+
const before = this.#paneCells(intent.axis === "cols" ? pane.width : pane.height);
|
|
63242
63336
|
if (before === intent.cells) {
|
|
63243
63337
|
return {
|
|
63244
63338
|
...envelope,
|
|
@@ -63249,14 +63343,20 @@ var init_workspace_multiplexer_verbs = __esm({
|
|
|
63249
63343
|
cells: before
|
|
63250
63344
|
};
|
|
63251
63345
|
}
|
|
63252
|
-
|
|
63346
|
+
const observed = yield [
|
|
63253
63347
|
"resize-pane",
|
|
63254
63348
|
"-t",
|
|
63255
63349
|
pane.paneId,
|
|
63256
63350
|
intent.axis === "cols" ? "-x" : "-y",
|
|
63257
|
-
String(intent.cells)
|
|
63258
|
-
|
|
63259
|
-
|
|
63351
|
+
String(intent.cells),
|
|
63352
|
+
";",
|
|
63353
|
+
"display-message",
|
|
63354
|
+
"-p",
|
|
63355
|
+
"-t",
|
|
63356
|
+
pane.paneId,
|
|
63357
|
+
intent.axis === "cols" ? "#{pane_width}" : "#{pane_height}"
|
|
63358
|
+
];
|
|
63359
|
+
const after = this.#paneCells(observed);
|
|
63260
63360
|
return {
|
|
63261
63361
|
...envelope,
|
|
63262
63362
|
verb: "workspace.pane.resize",
|
|
@@ -63522,6 +63622,15 @@ async function createNativeTmuxServerOwner(options) {
|
|
|
63522
63622
|
(session, action) => sessionRuntimeRegistry.executeWindowLinkAction(session, action)
|
|
63523
63623
|
);
|
|
63524
63624
|
}
|
|
63625
|
+
if (intent.verb === "workspace.pane.resize") {
|
|
63626
|
+
return multiplexer.mutateResize(
|
|
63627
|
+
{ operationId, expectedDaemonInstanceId: generation, intent },
|
|
63628
|
+
(session) => {
|
|
63629
|
+
if (!sessionRuntimeRegistry) throw new Error("Session runtime unavailable");
|
|
63630
|
+
return sessionRuntimeRegistry.paneResizeTransport(session);
|
|
63631
|
+
}
|
|
63632
|
+
);
|
|
63633
|
+
}
|
|
63525
63634
|
return multiplexer.mutate(
|
|
63526
63635
|
{ operationId, expectedDaemonInstanceId: generation, intent },
|
|
63527
63636
|
timing
|
|
@@ -81352,6 +81461,15 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
81352
81461
|
}
|
|
81353
81462
|
);
|
|
81354
81463
|
}
|
|
81464
|
+
if (intent.verb === "workspace.pane.resize") {
|
|
81465
|
+
return workspaceMultiplexer.mutateResize(
|
|
81466
|
+
{ operationId, expectedDaemonInstanceId: instanceId, intent },
|
|
81467
|
+
(session) => {
|
|
81468
|
+
if (!sessionRuntimeRegistry) throw new Error("Session runtime unavailable");
|
|
81469
|
+
return sessionRuntimeRegistry.paneResizeTransport(session);
|
|
81470
|
+
}
|
|
81471
|
+
);
|
|
81472
|
+
}
|
|
81355
81473
|
return workspaceMultiplexer.mutate(
|
|
81356
81474
|
{ operationId, expectedDaemonInstanceId: instanceId, intent },
|
|
81357
81475
|
timing
|
package/package.json
CHANGED
|
@@ -915,6 +915,13 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
915
915
|
return sessionRuntimeRegistry.executeWindowLinkAction(session, action);
|
|
916
916
|
});
|
|
917
917
|
}
|
|
918
|
+
if (intent.verb === "workspace.pane.resize") {
|
|
919
|
+
return workspaceMultiplexer.mutateResize({ operationId, expectedDaemonInstanceId: instanceId, intent }, (session) => {
|
|
920
|
+
if (!sessionRuntimeRegistry)
|
|
921
|
+
throw new Error("Session runtime unavailable");
|
|
922
|
+
return sessionRuntimeRegistry.paneResizeTransport(session);
|
|
923
|
+
});
|
|
924
|
+
}
|
|
918
925
|
return workspaceMultiplexer.mutate({ operationId, expectedDaemonInstanceId: instanceId, intent }, timing);
|
|
919
926
|
};
|
|
920
927
|
const runtimeTracePath = process.env.TMUX_IDE_SESSION_RUNTIME_TRACE_LOG;
|
|
@@ -122,6 +122,13 @@ export async function createNativeTmuxServerOwner(options) {
|
|
|
122
122
|
intent.verb === "workspace.pane.select") {
|
|
123
123
|
return multiplexer.mutateWindowLink({ operationId, expectedDaemonInstanceId: generation, intent }, (session, action) => sessionRuntimeRegistry.executeWindowLinkAction(session, action));
|
|
124
124
|
}
|
|
125
|
+
if (intent.verb === "workspace.pane.resize") {
|
|
126
|
+
return multiplexer.mutateResize({ operationId, expectedDaemonInstanceId: generation, intent }, (session) => {
|
|
127
|
+
if (!sessionRuntimeRegistry)
|
|
128
|
+
throw new Error("Session runtime unavailable");
|
|
129
|
+
return sessionRuntimeRegistry.paneResizeTransport(session);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
125
132
|
return multiplexer.mutate({ operationId, expectedDaemonInstanceId: generation, intent }, timing);
|
|
126
133
|
},
|
|
127
134
|
publishReceipt: (receipt) => ({
|
|
@@ -96,7 +96,7 @@ export function parseMultiplexerPaneRows(output) {
|
|
|
96
96
|
const rows = [];
|
|
97
97
|
for (const line of output.split("\n")) {
|
|
98
98
|
const fields = line.split("\t");
|
|
99
|
-
if (fields.length !== 9) {
|
|
99
|
+
if (fields.length !== 9 && fields.length !== 11) {
|
|
100
100
|
throw new WorkspaceMultiplexerError("workspace_unavailable", {
|
|
101
101
|
reason: "pane_listing_shape",
|
|
102
102
|
});
|
|
@@ -109,6 +109,15 @@ export function parseMultiplexerPaneRows(output) {
|
|
|
109
109
|
}
|
|
110
110
|
const count = Number(paneCount);
|
|
111
111
|
const index = Number(paneIndex);
|
|
112
|
+
const geometry = fields.length === 11 ? { width: Number(fields[9]), height: Number(fields[10]) } : {};
|
|
113
|
+
if (fields.length === 11 &&
|
|
114
|
+
(!Number.isSafeInteger(geometry.width) ||
|
|
115
|
+
geometry.width < 1 ||
|
|
116
|
+
!Number.isSafeInteger(geometry.height) ||
|
|
117
|
+
geometry.height < 1))
|
|
118
|
+
throw new WorkspaceMultiplexerError("workspace_unavailable", {
|
|
119
|
+
reason: "pane_listing_geometry",
|
|
120
|
+
});
|
|
112
121
|
if (!Number.isInteger(count) || count < 1 || !Number.isInteger(index) || index < 0) {
|
|
113
122
|
throw new WorkspaceMultiplexerError("workspace_unavailable", {
|
|
114
123
|
reason: "pane_listing_shape",
|
|
@@ -124,6 +133,7 @@ export function parseMultiplexerPaneRows(output) {
|
|
|
124
133
|
windowZoomed: zoomed === "1",
|
|
125
134
|
paneActive: active === "1",
|
|
126
135
|
creationId: creationId === "" ? null : creationId,
|
|
136
|
+
...geometry,
|
|
127
137
|
});
|
|
128
138
|
}
|
|
129
139
|
// list-panes -s repeats each backing for its session links. Identical native
|
|
@@ -222,6 +232,45 @@ export class WorkspaceMultiplexerAuthority {
|
|
|
222
232
|
mutate(raw, timing) {
|
|
223
233
|
return this.#mutate(raw, timing);
|
|
224
234
|
}
|
|
235
|
+
/** Retained control transport; owned by the same serialized semantic lane. */
|
|
236
|
+
async mutateResize(raw, transport) {
|
|
237
|
+
if (this.#disposed)
|
|
238
|
+
throw new WorkspaceMultiplexerError("workspace_unavailable");
|
|
239
|
+
const request = WorkspaceMultiplexerMutationRequestSchemaZ.parse(raw);
|
|
240
|
+
if (request.expectedDaemonInstanceId !== this.#daemonInstanceId)
|
|
241
|
+
throw new WorkspaceMultiplexerError("daemon_instance_mismatch");
|
|
242
|
+
const intent = request.intent;
|
|
243
|
+
if (intent.verb !== "workspace.pane.resize")
|
|
244
|
+
throw new TypeError("Expected pane resize");
|
|
245
|
+
const workspace = this.#registry.get(intent.workspaceName);
|
|
246
|
+
if (!workspace)
|
|
247
|
+
throw new WorkspaceMultiplexerError("workspace_not_found");
|
|
248
|
+
const envelope = {
|
|
249
|
+
operationId: request.operationId,
|
|
250
|
+
daemonInstanceId: this.#daemonInstanceId,
|
|
251
|
+
workspaceName: intent.workspaceName,
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
const run = transport(workspace.sessionName);
|
|
255
|
+
const steps = this.#resizeSteps(intent, workspace.sessionName, envelope);
|
|
256
|
+
let next = steps.next();
|
|
257
|
+
while (!next.done) {
|
|
258
|
+
if (this.#disposed)
|
|
259
|
+
throw new WorkspaceMultiplexerError("workspace_unavailable");
|
|
260
|
+
const observed = await run(next.value);
|
|
261
|
+
if (this.#disposed)
|
|
262
|
+
throw new WorkspaceMultiplexerError("workspace_unavailable");
|
|
263
|
+
next = steps.next(observed);
|
|
264
|
+
}
|
|
265
|
+
return WorkspaceMultiplexerMutationResultSchemaZ.parse(next.value);
|
|
266
|
+
}
|
|
267
|
+
catch (cause) {
|
|
268
|
+
if (cause instanceof WorkspaceMultiplexerError)
|
|
269
|
+
throw cause;
|
|
270
|
+
// A missing reply may follow an effect. Never fall back or replay.
|
|
271
|
+
throw new WorkspaceMultiplexerError("mutation_unverified", { operationId: request.operationId }, cause);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
225
274
|
/** Runs only inside the existing semantic mutation lane; never rediscovers a stale link. */
|
|
226
275
|
async mutateWindowLink(raw, execute) {
|
|
227
276
|
if (this.#disposed)
|
|
@@ -1180,14 +1229,7 @@ export class WorkspaceMultiplexerAuthority {
|
|
|
1180
1229
|
// resize
|
|
1181
1230
|
// -------------------------------------------------------------------------
|
|
1182
1231
|
/** The pane's own size on one axis, in cells, read straight from tmux. */
|
|
1183
|
-
#paneCells(
|
|
1184
|
-
const observed = this.#io.runTmux([
|
|
1185
|
-
"display-message",
|
|
1186
|
-
"-p",
|
|
1187
|
-
"-t",
|
|
1188
|
-
paneId,
|
|
1189
|
-
axis === "cols" ? "#{pane_width}" : "#{pane_height}",
|
|
1190
|
-
]);
|
|
1232
|
+
#paneCells(observed) {
|
|
1191
1233
|
const cells = Number(observed);
|
|
1192
1234
|
if (!Number.isInteger(cells) || cells < 1) {
|
|
1193
1235
|
throw new WorkspaceMultiplexerError("mutation_unverified", { reason: "pane_size_shape" });
|
|
@@ -1205,7 +1247,22 @@ export class WorkspaceMultiplexerAuthority {
|
|
|
1205
1247
|
* whole view exists to remove.
|
|
1206
1248
|
*/
|
|
1207
1249
|
#resize(intent, sessionName, envelope) {
|
|
1208
|
-
const
|
|
1250
|
+
const steps = this.#resizeSteps(intent, sessionName, envelope);
|
|
1251
|
+
let next = steps.next();
|
|
1252
|
+
while (!next.done)
|
|
1253
|
+
next = steps.next(this.#io.runTmux(next.value));
|
|
1254
|
+
return next.value;
|
|
1255
|
+
}
|
|
1256
|
+
*#resizeSteps(intent, sessionName, envelope) {
|
|
1257
|
+
const output = yield [
|
|
1258
|
+
"list-panes",
|
|
1259
|
+
"-s",
|
|
1260
|
+
"-t",
|
|
1261
|
+
`=${sessionName}`,
|
|
1262
|
+
"-F",
|
|
1263
|
+
`${PANE_FIELDS}\t#{pane_width}\t#{pane_height}`,
|
|
1264
|
+
];
|
|
1265
|
+
const rows = parseMultiplexerPaneRows(output);
|
|
1209
1266
|
const pane = resolvePaneRow(rows, intent.semanticPaneId);
|
|
1210
1267
|
if (pane.windowPaneCount < 2) {
|
|
1211
1268
|
throw new WorkspaceMultiplexerError("single_pane_window", {
|
|
@@ -1219,7 +1276,7 @@ export class WorkspaceMultiplexerAuthority {
|
|
|
1219
1276
|
semanticPaneId: intent.semanticPaneId,
|
|
1220
1277
|
});
|
|
1221
1278
|
}
|
|
1222
|
-
const before = this.#paneCells(pane.
|
|
1279
|
+
const before = this.#paneCells(intent.axis === "cols" ? pane.width : pane.height);
|
|
1223
1280
|
if (before === intent.cells) {
|
|
1224
1281
|
return {
|
|
1225
1282
|
...envelope,
|
|
@@ -1230,14 +1287,22 @@ export class WorkspaceMultiplexerAuthority {
|
|
|
1230
1287
|
cells: before,
|
|
1231
1288
|
};
|
|
1232
1289
|
}
|
|
1233
|
-
|
|
1290
|
+
// Keep mutation and authoritative readback in one tmux command queue.
|
|
1291
|
+
// Avoid two extra synchronous client processes on every pointer update.
|
|
1292
|
+
const observed = yield [
|
|
1234
1293
|
"resize-pane",
|
|
1235
1294
|
"-t",
|
|
1236
1295
|
pane.paneId,
|
|
1237
1296
|
intent.axis === "cols" ? "-x" : "-y",
|
|
1238
1297
|
String(intent.cells),
|
|
1239
|
-
|
|
1240
|
-
|
|
1298
|
+
";",
|
|
1299
|
+
"display-message",
|
|
1300
|
+
"-p",
|
|
1301
|
+
"-t",
|
|
1302
|
+
pane.paneId,
|
|
1303
|
+
intent.axis === "cols" ? "#{pane_width}" : "#{pane_height}",
|
|
1304
|
+
];
|
|
1305
|
+
const after = this.#paneCells(observed);
|
|
1241
1306
|
return {
|
|
1242
1307
|
...envelope,
|
|
1243
1308
|
verb: "workspace.pane.resize",
|
|
@@ -149,6 +149,17 @@ export class MirrorService {
|
|
|
149
149
|
},
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
|
+
paneResizeTransport(session) {
|
|
153
|
+
const entry = this.channels.get(session);
|
|
154
|
+
if (!entry || entry.retired)
|
|
155
|
+
throw new Error(`Mirror session ${session} is unavailable`);
|
|
156
|
+
const run = entry.channel.paneResizeTransport();
|
|
157
|
+
return (args) => {
|
|
158
|
+
if (entry.retired || this.channels.get(session) !== entry)
|
|
159
|
+
return Promise.reject(new Error("Resize mirror session retired"));
|
|
160
|
+
return run(args);
|
|
161
|
+
};
|
|
162
|
+
}
|
|
152
163
|
async executeWindowLinkAction(session, request) {
|
|
153
164
|
const entry = this.channels.get(session);
|
|
154
165
|
if (!entry || entry.retired)
|
|
@@ -389,6 +389,64 @@ export class SessionChannel {
|
|
|
389
389
|
if (this.latestWindowStage)
|
|
390
390
|
this.windowLinkAuthority.reconcile(this.latestWindowStage.links);
|
|
391
391
|
}
|
|
392
|
+
/** Capture this attached connection once; never resolve a replacement mid-operation. */
|
|
393
|
+
paneResizeTransport() {
|
|
394
|
+
const identity = this.attachedIdentity;
|
|
395
|
+
const generation = this.attachedServerGeneration;
|
|
396
|
+
const assertCurrent = () => {
|
|
397
|
+
if (this.disposed ||
|
|
398
|
+
!identity ||
|
|
399
|
+
!generation ||
|
|
400
|
+
this.attachedIdentity !== identity ||
|
|
401
|
+
this.attachedServerGeneration !== generation)
|
|
402
|
+
throw new Error("Resize control connection retired");
|
|
403
|
+
};
|
|
404
|
+
assertCurrent();
|
|
405
|
+
return async (args) => {
|
|
406
|
+
assertCurrent();
|
|
407
|
+
const listing = args.length === 6 &&
|
|
408
|
+
args[0] === "list-panes" &&
|
|
409
|
+
args[1] === "-s" &&
|
|
410
|
+
args[2] === "-t" &&
|
|
411
|
+
args[3] === `=${identity.sessionName}` &&
|
|
412
|
+
args[4] === "-F";
|
|
413
|
+
const resize = args.length === 11 &&
|
|
414
|
+
args[0] === "resize-pane" &&
|
|
415
|
+
args[1] === "-t" &&
|
|
416
|
+
/^%[0-9]+$/u.test(args[2]) &&
|
|
417
|
+
(args[3] === "-x" || args[3] === "-y") &&
|
|
418
|
+
/^[1-9][0-9]*$/u.test(args[4]) &&
|
|
419
|
+
args[5] === ";" &&
|
|
420
|
+
args[6] === "display-message" &&
|
|
421
|
+
args[7] === "-p" &&
|
|
422
|
+
args[8] === "-t" &&
|
|
423
|
+
args[9] === args[2] &&
|
|
424
|
+
args[10] === (args[3] === "-x" ? "#{pane_width}" : "#{pane_height}");
|
|
425
|
+
if (!listing && !resize)
|
|
426
|
+
throw new Error("Invalid resize control command");
|
|
427
|
+
// A runtime session ID prevents a rename/replacement from redirecting the lookup.
|
|
428
|
+
const pinned = listing
|
|
429
|
+
? [...args.slice(0, 3), identity.runtimeSessionId, ...args.slice(4)]
|
|
430
|
+
: args;
|
|
431
|
+
const command = pinned
|
|
432
|
+
.map((arg, index) => (resize && index === 5 ? ";" : tmuxSingleQuote(arg)))
|
|
433
|
+
.join(" ");
|
|
434
|
+
const lines = listing
|
|
435
|
+
? await this.io.request(command)
|
|
436
|
+
: await new Promise((resolve, reject) => {
|
|
437
|
+
this.io.commandListInline(command, 2, 1, (reply) => reply.ok ? resolve(reply.lines) : reject(new Error("Resize control command failed")));
|
|
438
|
+
});
|
|
439
|
+
assertCurrent();
|
|
440
|
+
return lines
|
|
441
|
+
.map((line) => {
|
|
442
|
+
const decoded = decodeControlReplyUtf8(line);
|
|
443
|
+
if (decoded === null)
|
|
444
|
+
throw new Error("Invalid resize control reply");
|
|
445
|
+
return decoded;
|
|
446
|
+
})
|
|
447
|
+
.join("\n");
|
|
448
|
+
};
|
|
449
|
+
}
|
|
392
450
|
async executeWindowLinkAction(request) {
|
|
393
451
|
const authority = this.windowLinkAuthority;
|
|
394
452
|
if (!authority || this.disposed)
|
|
@@ -259,6 +259,11 @@ export class SessionRuntimeRegistry {
|
|
|
259
259
|
this.#mirror.hasRetainedSession(session));
|
|
260
260
|
}
|
|
261
261
|
/** Called by the serialized semantic mutation executor, never by a transport directly. */
|
|
262
|
+
paneResizeTransport(session) {
|
|
263
|
+
if (this.#disposed)
|
|
264
|
+
throw new Error("Session runtime disposed");
|
|
265
|
+
return this.#mirror.paneResizeTransport(session);
|
|
266
|
+
}
|
|
262
267
|
executeWindowLinkAction(session, request) {
|
|
263
268
|
if (this.#disposed)
|
|
264
269
|
return Promise.reject(new Error("Session runtime disposed"));
|
|
@@ -77,7 +77,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
77
77
|
{ id: "system", name: "System · follow terminal" },
|
|
78
78
|
{ id: "dark", name: "Dark" },
|
|
79
79
|
{ id: "light", name: "Light" },
|
|
80
|
-
...VISUAL_THEME_PRESETS,
|
|
80
|
+
...[...VISUAL_THEME_PRESETS].sort((a, b) => a.name.localeCompare(b.name)),
|
|
81
81
|
];
|
|
82
82
|
const pickerOptions = () => options.filter((p) => `${p.id} ${p.name}`.toLowerCase().includes(pickerQuery().toLowerCase()));
|
|
83
83
|
const apply = (id) => {
|
|
@@ -102,6 +102,14 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
102
102
|
if (options.some((p) => p.id === id))
|
|
103
103
|
apply(id);
|
|
104
104
|
};
|
|
105
|
+
const filterPicker = (query) => {
|
|
106
|
+
setPickerQuery(query);
|
|
107
|
+
const first = pickerOptions()[0];
|
|
108
|
+
if (!query)
|
|
109
|
+
preview(originalSelection);
|
|
110
|
+
else if (first)
|
|
111
|
+
preview(first.id);
|
|
112
|
+
};
|
|
105
113
|
const cancelPicker = () => {
|
|
106
114
|
if (!pickerOpen())
|
|
107
115
|
return;
|
|
@@ -111,7 +119,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
111
119
|
setPickerError(null);
|
|
112
120
|
};
|
|
113
121
|
const savePicker = () => {
|
|
114
|
-
if (!pickerOpen())
|
|
122
|
+
if (!pickerOpen() || !pickerOptions().some((option) => option.id === pickerSelection()))
|
|
115
123
|
return;
|
|
116
124
|
try {
|
|
117
125
|
updateAppConfig({
|
|
@@ -150,6 +158,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
150
158
|
pickerError,
|
|
151
159
|
pickerQuery,
|
|
152
160
|
pickerSelection,
|
|
161
|
+
pickerOriginalSelection: () => originalSelection,
|
|
153
162
|
pickerOptions,
|
|
154
163
|
openPicker() {
|
|
155
164
|
if (pickerOpen())
|
|
@@ -188,12 +197,12 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
188
197
|
}
|
|
189
198
|
}
|
|
190
199
|
else if (name === "backspace") {
|
|
191
|
-
|
|
200
|
+
filterPicker(pickerQuery().slice(0, -1));
|
|
192
201
|
}
|
|
193
202
|
else if (!event.ctrl && !event.meta) {
|
|
194
203
|
const text = event.sequence ?? (name.length === 1 ? name : "");
|
|
195
204
|
if (text.length === 1 && text >= " ")
|
|
196
|
-
|
|
205
|
+
filterPicker((pickerQuery() + text).slice(0, 80));
|
|
197
206
|
}
|
|
198
207
|
return true;
|
|
199
208
|
},
|
|
@@ -203,7 +203,10 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
203
203
|
if (typeof command === "object" ||
|
|
204
204
|
command === "home" ||
|
|
205
205
|
command === "terminals" ||
|
|
206
|
-
command === "appearance"
|
|
206
|
+
command === "appearance" ||
|
|
207
|
+
command === "shortcuts" ||
|
|
208
|
+
command === "whats-new" ||
|
|
209
|
+
command === "switch-session")
|
|
207
210
|
return null;
|
|
208
211
|
if (options.sessionOwner()?.snapshot()?.status !== "live")
|
|
209
212
|
return "Open a live session first";
|
|
@@ -215,6 +218,7 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
215
218
|
setSurface: options.setSurface,
|
|
216
219
|
setNote: options.setNote,
|
|
217
220
|
openAppearance: options.openAppearance,
|
|
221
|
+
openSessions: options.openSessions,
|
|
218
222
|
zoomPane: options.zoomPane,
|
|
219
223
|
newWindow: options.interaction.newWindow,
|
|
220
224
|
splitPane: options.interaction.splitPane,
|
|
@@ -23,6 +23,7 @@ export function createApplicationHomeExperience(options) {
|
|
|
23
23
|
fleetHome: fleetHome.presentation,
|
|
24
24
|
fleetCommands: machines.paletteCommands,
|
|
25
25
|
openFleet: machines.openPalette,
|
|
26
|
+
openSessions: () => machines.showSwitcher(false),
|
|
26
27
|
});
|
|
27
28
|
const tour = createApplicationGuidedTourIntegration({
|
|
28
29
|
...options,
|
|
@@ -2,6 +2,7 @@ import { createSignal } from "solid-js";
|
|
|
2
2
|
import { applicationPaletteCommands } from "./application-palette-input.js";
|
|
3
3
|
import { createApplicationPaletteSearchOwner } from "./application-palette-search-owner.js";
|
|
4
4
|
export function createApplicationPaletteCommandOwner(options) {
|
|
5
|
+
const [referencePage, setReferencePage] = createSignal();
|
|
5
6
|
const [closeArmed, setCloseArmed] = createSignal(false);
|
|
6
7
|
const [busy, setBusy] = createSignal(false);
|
|
7
8
|
let armedTarget;
|
|
@@ -14,6 +15,7 @@ export function createApplicationPaletteCommandOwner(options) {
|
|
|
14
15
|
onChange: () => setCloseArmed(false),
|
|
15
16
|
});
|
|
16
17
|
const setOpen = (open, source) => {
|
|
18
|
+
setReferencePage(undefined);
|
|
17
19
|
openRevision += 1;
|
|
18
20
|
setCloseArmed(false);
|
|
19
21
|
if (open)
|
|
@@ -25,6 +27,20 @@ export function createApplicationPaletteCommandOwner(options) {
|
|
|
25
27
|
const activate = (command, source, confirmed = false) => {
|
|
26
28
|
if (busy())
|
|
27
29
|
return;
|
|
30
|
+
if (command === "shortcuts" || command === "whats-new") {
|
|
31
|
+
setCloseArmed(false);
|
|
32
|
+
setReferencePage(command === "shortcuts" ? "shortcuts" : "changes");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (command === "switch-session") {
|
|
36
|
+
if (!options.openSessions) {
|
|
37
|
+
options.setNote("Session switching is unavailable.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setOpen(false, source);
|
|
41
|
+
options.openSessions();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
28
44
|
if (command === "appearance") {
|
|
29
45
|
setOpen(false, source);
|
|
30
46
|
options.openAppearance?.();
|
|
@@ -124,6 +140,8 @@ export function createApplicationPaletteCommandOwner(options) {
|
|
|
124
140
|
};
|
|
125
141
|
return {
|
|
126
142
|
...search,
|
|
143
|
+
referencePage,
|
|
144
|
+
setReferencePage,
|
|
127
145
|
busy,
|
|
128
146
|
disabledReason: options.disabledReason ?? (() => null),
|
|
129
147
|
closeArmed: () => closeArmed() && armedTarget === options.targetKey?.(),
|