Skip to content

Commit 029a64f

Browse files
authored
chore(release): v0.22.1 (#297)
* fix(tui): make private pit tabs predictable * chore(release): v0.22.1
1 parent c9e86f3 commit 029a64f

5 files changed

Lines changed: 63 additions & 9 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ autonomous mode or `/start <engine>` for raw mode. Running `moshcode agents` or
104104
At the mosh prompt, `/new` opens and switches to another independent moshcode
105105
tab. Run `/agents <engine>` in each tab and switch between them with tmux's
106106
`Ctrl-b n`, `Ctrl-b p`, or `Ctrl-b <number>` keys. If moshcode is already inside
107-
tmux, `/new` adds a window to that session. Otherwise the first `/new` opens a
108-
private two-tab workspace with its tab bar at the bottom.
107+
tmux, `/new` adds a window to that session and respects its configured window
108+
keys. Otherwise the first `/new` opens an isolated two-tab workspace with those
109+
default keys and its tab bar at the bottom.
109110

110111
Each tab is a separate moshcode process and provider CLIs still receive an
111112
ordinary inherited terminal. Moshcode does not intercept or reinterpret their

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "moshcode",
3-
"version": "0.22.0",
3+
"version": "0.22.1",
44
"type": "module",
55
"description": "moshcode — a metal wrapper for coding engines and native UGig/CoinPay workflow CLIs, with OpenPRD and moshscript",
66
"bin": {

src/tabs.mjs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,17 @@ export function tabPlan({
5151
session,
5252
socket,
5353
required: [
54-
[...server, "new-session", "-d", "-s", session, "-c", cwd, "-n", "mosh 1", command],
55-
// Do not use -d: selecting the new window avoids assuming whether the
56-
// user's tmux config starts window indexes at 0 or 1.
54+
// This server is only for moshcode. Start it without the user's tmux
55+
// config so the advertised Ctrl-b n/p/number bindings stay true even
56+
// when their normal tmux remaps or unbinds those keys. Existing tmux
57+
// sessions take the branch above and keep the user's configuration.
58+
[...server, "-f", "/dev/null", "new-session", "-d", "-s", session, "-c", cwd, "-n", "mosh 1", command],
59+
// Match the visible names to Ctrl-b 1/2. The clean tmux default starts
60+
// at zero, so change the base and renumber the first window before
61+
// adding its sibling.
62+
[...server, "set-option", "-t", session, "base-index", "1"],
63+
[...server, "move-window", "-r", "-t", session],
64+
// Do not use -d: the new tab should be selected when we attach.
5765
[...server, "new-window", "-t", session, "-c", cwd, "-n", "mosh 2", command],
5866
],
5967
// Presentation is best-effort: an older tmux should still open the tabs.
@@ -124,6 +132,12 @@ export async function openNewTab({
124132
if (!plan.attach) return { ok: true, dedicated: false };
125133
const attached = await runAttached(plan.attach, { spawner, env });
126134
if (!attached.ok) {
135+
// Attaching is the last required step, but the private server and its two
136+
// pit processes already exist by then. Do not strand them in the
137+
// background when the terminal cannot attach (for example TERM=dumb or a
138+
// client-side tmux error). As above, only a server created by this call is
139+
// ever eligible for cleanup.
140+
runner("tmux", ["-L", plan.socket, "kill-server"], { stdio: "ignore", env });
127141
return { ok: false, error: attached.error || new Error(`tmux attach exited ${attached.code ?? attached.signal ?? "unknown"}`) };
128142
}
129143
return { ok: true, dedicated: true, session: plan.session, socket: plan.socket };

src/tui.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ export async function tui() {
512512
}
513513
rl.close();
514514
console.log(info(process.env.TMUX
515-
? "opening a new mosh tab — switch with Ctrl-b n/p or Ctrl-b <number>…"
515+
? "opening a new mosh tab — switch with your tmux window keys…"
516516
: "opening a two-tab mosh workspace — switch with Ctrl-b n/p or Ctrl-b <number>…"));
517517
const result = await openNewTab();
518518
if (!result.ok) {

test/tabs.test.mjs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import assert from "node:assert/strict";
2+
import { EventEmitter } from "node:events";
23
import test from "node:test";
34

4-
import { tabCommand, tabPlan, tabShellQuote } from "../src/tabs.mjs";
5+
import { openNewTab, tabCommand, tabPlan, tabShellQuote } from "../src/tabs.mjs";
56

67
test("tab shell quoting keeps paths as one shell word", () => {
78
assert.equal(tabShellQuote("/tmp/it's here"), "'/tmp/it'\\''s here'");
@@ -34,10 +35,16 @@ test("/new outside tmux builds a private two-tab workspace", () => {
3435
assert.equal(plan.dedicated, true);
3536
assert.equal(plan.socket, "moshcode-42-99");
3637
assert.deepEqual(plan.required[0], [
37-
"-L", "moshcode-42-99", "new-session", "-d", "-s", "moshcode-42-99",
38+
"-L", "moshcode-42-99", "-f", "/dev/null", "new-session", "-d", "-s", "moshcode-42-99",
3839
"-c", "/repo", "-n", "mosh 1", "moshcode-command",
3940
]);
4041
assert.deepEqual(plan.required[1], [
42+
"-L", "moshcode-42-99", "set-option", "-t", "moshcode-42-99", "base-index", "1",
43+
]);
44+
assert.deepEqual(plan.required[2], [
45+
"-L", "moshcode-42-99", "move-window", "-r", "-t", "moshcode-42-99",
46+
]);
47+
assert.deepEqual(plan.required[3], [
4148
"-L", "moshcode-42-99", "new-window", "-t", "moshcode-42-99",
4249
"-c", "/repo", "-n", "mosh 2", "moshcode-command",
4350
]);
@@ -46,3 +53,35 @@ test("/new outside tmux builds a private two-tab workspace", () => {
4653
]);
4754
assert.ok(plan.optional.some((args) => args.includes("bottom")));
4855
});
56+
57+
test("/new cleans up its private server when attaching fails", async () => {
58+
const calls = [];
59+
const runner = (cmd, args) => {
60+
calls.push([cmd, args]);
61+
return { status: 0 };
62+
};
63+
const spawner = () => {
64+
const child = new EventEmitter();
65+
queueMicrotask(() => child.emit("exit", 1, null));
66+
return child;
67+
};
68+
69+
const result = await openNewTab({
70+
cwd: "/repo",
71+
env: { TMUX: "" },
72+
isTTY: true,
73+
runner,
74+
spawner,
75+
execPath: "/node",
76+
entry: "/moshcode.mjs",
77+
pid: 42,
78+
stamp: 99,
79+
});
80+
81+
assert.equal(result.ok, false);
82+
assert.match(result.error.message, /attach exited 1/);
83+
assert.deepEqual(calls.at(-1), [
84+
"tmux",
85+
["-L", "moshcode-42-99", "kill-server"],
86+
]);
87+
});

0 commit comments

Comments
 (0)