very-happy-cli 0.2.100 → 0.2.102
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/dist/{AcpBackend-CGnE_dbe.mjs → AcpBackend-C1nF06ZR.mjs} +1 -1
- package/dist/{AcpBackend-oDDlCB78.cjs → AcpBackend-CpKkdwIL.cjs} +1 -1
- package/dist/{AcpSessionManager-XGnQqnaJ.mjs → AcpSessionManager-BDad8lK3.mjs} +1 -1
- package/dist/{AcpSessionManager-BTXK7Wn2.cjs → AcpSessionManager-DjWIwGTR.cjs} +1 -1
- package/dist/{config-bUqA35dk.mjs → config-BlTfR0KA.mjs} +2 -2
- package/dist/{config-CYGeJRgN.cjs → config-DSF29tTS.cjs} +2 -2
- package/dist/{index-BXhSRb9N.cjs → index-C8HuYyZq.cjs} +2 -2
- package/dist/{index-CPUJfdIJ.mjs → index-CF3rNFlZ.mjs} +13 -13
- package/dist/{index-j_bZV63O.mjs → index-CPC-a5lH.mjs} +5 -5
- package/dist/{index-BFQ_XDUX.cjs → index-D_2eCeep.cjs} +17 -17
- package/dist/{index-DlpWmQ1I.mjs → index-DjYlt3fw.mjs} +2 -2
- package/dist/{index-olwBiKj6.cjs → index-yLIv8JPD.cjs} +4 -4
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-ZNRl9Qcl.cjs → installTerminalHooks-BVMhXRZO.cjs} +2 -2
- package/dist/{installTerminalHooks-DmC0SrNo.mjs → installTerminalHooks-DAR3YEo3.mjs} +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +6 -0
- package/dist/lib.d.mts +6 -0
- package/dist/lib.mjs +1 -1
- package/dist/{mcp-CryukOBj.mjs → mcp--I-Uz_wH.mjs} +2 -2
- package/dist/{mcp-7xQMC2Az.cjs → mcp-LCZtdhF0.cjs} +2 -2
- package/dist/{runGemini-12hI-_Ng.cjs → runGemini-Crna_6uM.cjs} +4 -4
- package/dist/{runGemini-BbTfrych.mjs → runGemini-cLR7rfj6.mjs} +4 -4
- package/dist/{runOpenClaw-Cr8boJJ9.mjs → runOpenClaw-C1u9xEH7.mjs} +3 -3
- package/dist/{runOpenClaw-CYrNNJ7P.cjs → runOpenClaw-CWjOCdWg.cjs} +3 -3
- package/dist/{send-DMuE8f-q.mjs → send-BtQ9qf_z.mjs} +2 -2
- package/dist/{send-DQjba9jZ.cjs → send-CufsYMXq.cjs} +2 -2
- package/dist/{spawn-d7F98wtf.cjs → spawn-DPllkoUN.cjs} +2 -2
- package/dist/{spawn-F6qqH4Vw.mjs → spawn-rdb0uM9R.mjs} +2 -2
- package/dist/{types-DTVA1Hp-.cjs → types-7-mfp-j5.cjs} +108 -42
- package/dist/{types-Dt0OQ0gL.mjs → types-B3Dnzv_q.mjs} +107 -41
- package/package.json +7 -7
|
@@ -51,7 +51,7 @@ var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
|
51
51
|
var pty__namespace = /*#__PURE__*/_interopNamespaceDefault(pty);
|
|
52
52
|
|
|
53
53
|
var name = "very-happy-cli";
|
|
54
|
-
var version = "0.2.
|
|
54
|
+
var version = "0.2.102";
|
|
55
55
|
var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
|
|
56
56
|
var author = "Kirill Dubovitskiy";
|
|
57
57
|
var contributors = [
|
|
@@ -164,12 +164,12 @@ var dependencies = {
|
|
|
164
164
|
zod: "^4.0.0"
|
|
165
165
|
};
|
|
166
166
|
var optionalDependencies = {
|
|
167
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
168
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
169
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
170
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
171
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
172
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
167
|
+
"very-happy-tools-arm64-darwin": "0.2.102",
|
|
168
|
+
"very-happy-tools-x64-darwin": "0.2.102",
|
|
169
|
+
"very-happy-tools-arm64-linux": "0.2.102",
|
|
170
|
+
"very-happy-tools-x64-linux": "0.2.102",
|
|
171
|
+
"very-happy-tools-arm64-win32": "0.2.102",
|
|
172
|
+
"very-happy-tools-x64-win32": "0.2.102"
|
|
173
173
|
};
|
|
174
174
|
var devDependencies = {
|
|
175
175
|
"@slopus/happy-wire": "workspace:*",
|
|
@@ -1585,7 +1585,7 @@ class RpcHandlerManager {
|
|
|
1585
1585
|
}
|
|
1586
1586
|
}
|
|
1587
1587
|
|
|
1588
|
-
const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-
|
|
1588
|
+
const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-7-mfp-j5.cjs', document.baseURI).href))));
|
|
1589
1589
|
function projectPath() {
|
|
1590
1590
|
const path = path$1.resolve(__dirname$1, "..");
|
|
1591
1591
|
return path;
|
|
@@ -4809,6 +4809,10 @@ function registerTodoHandlers(manager) {
|
|
|
4809
4809
|
});
|
|
4810
4810
|
}
|
|
4811
4811
|
|
|
4812
|
+
function sanitizeGeometry(cols, rows) {
|
|
4813
|
+
const ok = (v) => typeof v === "number" && Number.isInteger(v) && v >= 2 && v <= 1e4;
|
|
4814
|
+
return ok(cols) && ok(rows) ? { cols, rows } : void 0;
|
|
4815
|
+
}
|
|
4812
4816
|
function sanitizeTagList(value) {
|
|
4813
4817
|
if (!Array.isArray(value)) return void 0;
|
|
4814
4818
|
const out = [];
|
|
@@ -4828,7 +4832,7 @@ function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE
|
|
|
4828
4832
|
const rows = [];
|
|
4829
4833
|
for (const [id, value] of Object.entries(raw)) {
|
|
4830
4834
|
if (!id || !value || typeof value !== "object") continue;
|
|
4831
|
-
const { title, cwd, seenAt, tags, manual, attachTmux } = value;
|
|
4835
|
+
const { title, cwd, seenAt, tags, manual, attachTmux, cols: rawCols, rows: rawRows } = value;
|
|
4832
4836
|
if (typeof seenAt !== "number" || !Number.isFinite(seenAt)) continue;
|
|
4833
4837
|
if (now - seenAt >= ttlMs) continue;
|
|
4834
4838
|
const tagList = sanitizeTagList(tags);
|
|
@@ -4838,6 +4842,7 @@ function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE
|
|
|
4838
4842
|
...tagList !== void 0 ? { tags: tagList } : {},
|
|
4839
4843
|
...manual === true ? { manual: true } : {},
|
|
4840
4844
|
...typeof attachTmux === "string" && attachTmux ? { attachTmux } : {},
|
|
4845
|
+
...sanitizeGeometry(rawCols, rawRows) ?? {},
|
|
4841
4846
|
seenAt
|
|
4842
4847
|
}]);
|
|
4843
4848
|
}
|
|
@@ -4860,6 +4865,7 @@ function liveSnapshotChanged(prev, next) {
|
|
|
4860
4865
|
if (!!before.manual !== !!info.manual) return true;
|
|
4861
4866
|
if ((before.attachTmux ?? "") !== (info.attachTmux ?? "")) return true;
|
|
4862
4867
|
if ((before.tags ?? []).join("\0") !== (info.tags ?? []).join("\0")) return true;
|
|
4868
|
+
if (before.cols !== info.cols || before.rows !== info.rows) return true;
|
|
4863
4869
|
}
|
|
4864
4870
|
return false;
|
|
4865
4871
|
}
|
|
@@ -4949,6 +4955,9 @@ function attachStartupCommand(sessionId, socket) {
|
|
|
4949
4955
|
}
|
|
4950
4956
|
|
|
4951
4957
|
const TERMINAL_ID_RE$1 = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
4958
|
+
function recordGeometry(record) {
|
|
4959
|
+
return record.cols !== void 0 && record.rows !== void 0 ? { cols: record.cols, rows: record.rows } : {};
|
|
4960
|
+
}
|
|
4952
4961
|
function planTerminalRestore(record, facts) {
|
|
4953
4962
|
if (facts.tmuxAlive) return { kind: "already-live" };
|
|
4954
4963
|
if (record.attachTmux) {
|
|
@@ -4964,7 +4973,8 @@ function planTerminalRestore(record, facts) {
|
|
|
4964
4973
|
manual: true,
|
|
4965
4974
|
tags: record.tags,
|
|
4966
4975
|
command: attachStartupCommand(matches[0].id, facts.attachSocket),
|
|
4967
|
-
attachTmux: record.attachTmux
|
|
4976
|
+
attachTmux: record.attachTmux,
|
|
4977
|
+
...recordGeometry(record)
|
|
4968
4978
|
};
|
|
4969
4979
|
}
|
|
4970
4980
|
if (!record.cwd || !facts.cwdExists(record.cwd)) return { kind: "error", reason: "missing-cwd" };
|
|
@@ -4976,7 +4986,8 @@ function planTerminalRestore(record, facts) {
|
|
|
4976
4986
|
title: record.title,
|
|
4977
4987
|
manual: record.manual === true,
|
|
4978
4988
|
tags: record.tags,
|
|
4979
|
-
...resumable ? { command: `claude --resume ${record.claudeSessionId}` } : {}
|
|
4989
|
+
...resumable ? { command: `claude --resume ${record.claudeSessionId}` } : {},
|
|
4990
|
+
...recordGeometry(record)
|
|
4980
4991
|
};
|
|
4981
4992
|
}
|
|
4982
4993
|
|
|
@@ -9537,6 +9548,7 @@ const CAPTURE_FULL_BUDGET_BYTES = 1024 * 1024;
|
|
|
9537
9548
|
const ALT_ENTER = Buffer.from("\x1B[?1049h", "ascii");
|
|
9538
9549
|
const CURSOR_HOME = Buffer.from("\x1B[H", "ascii");
|
|
9539
9550
|
const CRLF = Buffer.from("\r\n", "ascii");
|
|
9551
|
+
const SGR_RESET = Buffer.from("\x1B[0m", "ascii");
|
|
9540
9552
|
function normalizeCaptureLines(blob) {
|
|
9541
9553
|
if (blob.length === 0) return blob;
|
|
9542
9554
|
const lines = [];
|
|
@@ -9573,32 +9585,38 @@ function assembleRestore(responses, paneState, budget = CAPTURE_FULL_BUDGET_BYTE
|
|
|
9573
9585
|
const screen = () => {
|
|
9574
9586
|
const raw = get("visible");
|
|
9575
9587
|
const lines = splitLines(raw);
|
|
9588
|
+
let realCount = lines.length;
|
|
9576
9589
|
if (rows !== void 0 && rows > 0) {
|
|
9577
9590
|
while (lines.length > rows) lines.shift();
|
|
9591
|
+
realCount = lines.length;
|
|
9578
9592
|
while (lines.length < rows) lines.push(EMPTY);
|
|
9579
9593
|
}
|
|
9580
|
-
const
|
|
9594
|
+
const realBody = joinCrlf(lines.slice(0, realCount));
|
|
9595
|
+
const padCount = lines.length - realCount;
|
|
9596
|
+
const padBody = padCount > 0 ? Buffer.concat([realCount > 0 ? CRLF : EMPTY, SGR_RESET, joinCrlf(new Array(padCount).fill(EMPTY))]) : EMPTY;
|
|
9581
9597
|
const cursor = paneState ? Buffer.from(`\x1B[${paneState.cursorY + 1};${paneState.cursorX + 1}H`, "ascii") : Buffer.alloc(0);
|
|
9582
|
-
return Buffer.concat([
|
|
9598
|
+
return Buffer.concat([realBody, padBody, SGR_RESET, tail, cursor]);
|
|
9583
9599
|
};
|
|
9600
|
+
const wrapFull = (b) => Buffer.concat([SGR_RESET, b, SGR_RESET]);
|
|
9601
|
+
const wrapSmall = (b) => Buffer.concat([b, SGR_RESET]);
|
|
9584
9602
|
if (!alternateOn) {
|
|
9585
9603
|
const history2 = trimTrailingBlankRows(normalizeCaptureLines(get("history")));
|
|
9586
9604
|
const smallHistory = trimTrailingBlankRows(normalizeCaptureLines(get("smallHistory")));
|
|
9587
9605
|
const withHistory = (h) => Buffer.concat(
|
|
9588
|
-
h.length > 0 ? [truncateToBudget(h, budget), CRLF, screen()] : [screen()]
|
|
9606
|
+
h.length > 0 ? [truncateToBudget(h, budget), SGR_RESET, CRLF, screen()] : [screen()]
|
|
9589
9607
|
);
|
|
9590
|
-
return { full: withHistory(history2), small: withHistory(smallHistory), alternateOn };
|
|
9608
|
+
return { full: wrapFull(withHistory(history2)), small: wrapSmall(withHistory(smallHistory)), alternateOn };
|
|
9591
9609
|
}
|
|
9592
9610
|
const history = trimTrailingBlankRows(normalizeCaptureLines(get("history")));
|
|
9593
9611
|
const saved = trimTrailingBlankRows(normalizeCaptureLines(get("altSaved")));
|
|
9594
9612
|
const scrollback = truncateToBudget(
|
|
9595
|
-
Buffer.concat(history.length > 0 && saved.length > 0 ? [history, CRLF, saved] : [history, saved]),
|
|
9613
|
+
Buffer.concat(history.length > 0 && saved.length > 0 ? [history, SGR_RESET, CRLF, saved] : [history, saved]),
|
|
9596
9614
|
budget
|
|
9597
9615
|
);
|
|
9598
|
-
const altScreen = Buffer.concat([ALT_ENTER, CURSOR_HOME, screen()]);
|
|
9616
|
+
const altScreen = Buffer.concat([ALT_ENTER, CURSOR_HOME, SGR_RESET, screen()]);
|
|
9599
9617
|
return {
|
|
9600
|
-
full: Buffer.concat([scrollback, scrollback.length > 0 ? CRLF : Buffer.alloc(0), altScreen]),
|
|
9601
|
-
small: altScreen,
|
|
9618
|
+
full: wrapFull(Buffer.concat([scrollback, scrollback.length > 0 ? CRLF : Buffer.alloc(0), altScreen])),
|
|
9619
|
+
small: wrapSmall(altScreen),
|
|
9602
9620
|
alternateOn
|
|
9603
9621
|
};
|
|
9604
9622
|
}
|
|
@@ -10096,6 +10114,7 @@ function selectAutoRestore(candidates, opts) {
|
|
|
10096
10114
|
terminalId: c.id,
|
|
10097
10115
|
...c.tags !== void 0 ? { tags: c.tags } : {},
|
|
10098
10116
|
...c.manual ? { manual: true } : {},
|
|
10117
|
+
...c.cols !== void 0 && c.rows !== void 0 ? { cols: c.cols, rows: c.rows } : {},
|
|
10099
10118
|
cwd: c.cwd,
|
|
10100
10119
|
title: c.title,
|
|
10101
10120
|
claudeSessionId: c.claudeSessionId,
|
|
@@ -10283,7 +10302,10 @@ function startupInjectionArgs(tmuxSession, command) {
|
|
|
10283
10302
|
];
|
|
10284
10303
|
}
|
|
10285
10304
|
function seenInfoOf(t) {
|
|
10286
|
-
return { title: t.title, cwd: t.cwd, tags: t.tags, manual: t.manual, attachTmux: t.attachTmux };
|
|
10305
|
+
return { title: t.title, cwd: t.cwd, tags: t.tags, manual: t.manual, attachTmux: t.attachTmux, ...seenGeometry(t.paneCols, t.paneRows) };
|
|
10306
|
+
}
|
|
10307
|
+
function seenGeometry(cols, rows) {
|
|
10308
|
+
return cols !== void 0 && rows !== void 0 ? { cols, rows } : {};
|
|
10287
10309
|
}
|
|
10288
10310
|
function terminalListSignature(items) {
|
|
10289
10311
|
const canon = [...items].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((t) => [
|
|
@@ -10328,6 +10350,12 @@ const LIST_SESSIONS_FORMAT = [
|
|
|
10328
10350
|
// B-273: the user tmux session this terminal was opened to attach (name),
|
|
10329
10351
|
// carried into close records so a restore can re-attach.
|
|
10330
10352
|
"#{@vh_attach}",
|
|
10353
|
+
// B-287: the pane's REAL geometry, persisted with the live snapshot so a
|
|
10354
|
+
// cold restore (daemon boot auto-restore, archive ↻) recreates the session
|
|
10355
|
+
// at the size it last had — claude's one-shot welcome banner then never
|
|
10356
|
+
// gets reflowed by the first web open (the "half-drawn logo").
|
|
10357
|
+
"#{pane_width}",
|
|
10358
|
+
"#{pane_height}",
|
|
10331
10359
|
// B-121: the control-mode client has no `pty.process`, so the agent-state
|
|
10332
10360
|
// fast path lost its live `#{pane_current_command}` equivalent. Carry it in
|
|
10333
10361
|
// the ONE list-sessions call the tracker already makes — the value goes
|
|
@@ -10342,9 +10370,10 @@ const LIST_SESSIONS_FORMAT = [
|
|
|
10342
10370
|
function parseSessionListLine(line) {
|
|
10343
10371
|
if (!line) return void 0;
|
|
10344
10372
|
const parts = line.split(LIST_FIELD_SEP);
|
|
10345
|
-
if (parts.length <
|
|
10346
|
-
const [name, created, activity, cwd, vhTitle, manual, vhTags, vhAttach, paneCommand] = parts;
|
|
10373
|
+
if (parts.length < 12) return void 0;
|
|
10374
|
+
const [name, created, activity, cwd, vhTitle, manual, vhTags, vhAttach, paneW, paneH, paneCommand] = parts;
|
|
10347
10375
|
if (!name) return void 0;
|
|
10376
|
+
const geom = parsePositiveInt(paneW) !== void 0 && parsePositiveInt(paneH) !== void 0 ? { paneCols: parsePositiveInt(paneW), paneRows: parsePositiveInt(paneH) } : {};
|
|
10348
10377
|
return {
|
|
10349
10378
|
name,
|
|
10350
10379
|
created: created ? Number(created) * 1e3 : void 0,
|
|
@@ -10357,11 +10386,17 @@ function parseSessionListLine(line) {
|
|
|
10357
10386
|
// Verbatim (no trim): tmux allows edge spaces in a session name and the
|
|
10358
10387
|
// restore lookup / attach echo compare it exactly.
|
|
10359
10388
|
attachTmux: isSafeTmuxSessionName(vhAttach) ? vhAttach : void 0,
|
|
10360
|
-
|
|
10389
|
+
...geom,
|
|
10390
|
+
// pane_title is last, so anything after field 11 is title content that
|
|
10361
10391
|
// contained the separator — rejoin it rather than dropping it.
|
|
10362
|
-
paneTitle: parts.slice(
|
|
10392
|
+
paneTitle: parts.slice(11).join(LIST_FIELD_SEP) || void 0
|
|
10363
10393
|
};
|
|
10364
10394
|
}
|
|
10395
|
+
function parsePositiveInt(raw) {
|
|
10396
|
+
if (raw === void 0 || !/^[0-9]{1,5}$/.test(raw.trim())) return void 0;
|
|
10397
|
+
const n = Number(raw.trim());
|
|
10398
|
+
return n > 0 ? n : void 0;
|
|
10399
|
+
}
|
|
10365
10400
|
const TERMINAL_TAG_MAX_COUNT = 64;
|
|
10366
10401
|
const TERMINAL_TAG_MAX_LENGTH = 24;
|
|
10367
10402
|
const TERMINAL_TAGS_MAX_BYTES = 4096;
|
|
@@ -10504,6 +10539,11 @@ const VH_SESSION_OPTION_OVERRIDES = [
|
|
|
10504
10539
|
["window", "window-size", "latest"],
|
|
10505
10540
|
["window", "pane-border-status", "off"]
|
|
10506
10541
|
];
|
|
10542
|
+
const COLD_CREATE_DEFAULT_GEOMETRY = { cols: 120, rows: 30 };
|
|
10543
|
+
function coldCreateGeometry(plan) {
|
|
10544
|
+
const ok = (v) => typeof v === "number" && Number.isInteger(v) && v >= 2 && v <= 1e4;
|
|
10545
|
+
return ok(plan.cols) && ok(plan.rows) ? { cols: plan.cols, rows: plan.rows } : { ...COLD_CREATE_DEFAULT_GEOMETRY };
|
|
10546
|
+
}
|
|
10507
10547
|
function tmuxNewSessionArgs(session, cols, rows, cwd, envFlags, shell) {
|
|
10508
10548
|
const args = ["new-session", "-d", ...envFlags, "-s", session, "-x", String(cols), "-y", String(rows), "-c", cwd, shell];
|
|
10509
10549
|
for (const [scope, option, value] of VH_SESSION_OPTION_OVERRIDES) {
|
|
@@ -10525,13 +10565,17 @@ function ptyEnv() {
|
|
|
10525
10565
|
env.PATH = `${local}:${env.PATH || ""}`;
|
|
10526
10566
|
}
|
|
10527
10567
|
env.TERM = "xterm-256color";
|
|
10528
|
-
|
|
10529
|
-
if (!isUtf8(env.LC_ALL) && !isUtf8(env.LANG) && !isUtf8(env.LC_CTYPE)) {
|
|
10530
|
-
env.LANG = "en_US.UTF-8";
|
|
10531
|
-
env.LC_CTYPE = "en_US.UTF-8";
|
|
10532
|
-
}
|
|
10568
|
+
Object.assign(env, utf8LocaleEnv(env, process.platform));
|
|
10533
10569
|
return env;
|
|
10534
10570
|
}
|
|
10571
|
+
function utf8LocaleEnv(cur, platform) {
|
|
10572
|
+
const isUtf8 = (v) => !!v && /utf-?8/i.test(v);
|
|
10573
|
+
if (isUtf8(cur.LC_ALL) || isUtf8(cur.LANG) || isUtf8(cur.LC_CTYPE)) return {};
|
|
10574
|
+
const locale = platform === "darwin" ? "en_US.UTF-8" : "C.UTF-8";
|
|
10575
|
+
const patch = { LANG: locale, LC_CTYPE: locale };
|
|
10576
|
+
if (cur.LC_ALL) patch.LC_ALL = locale;
|
|
10577
|
+
return patch;
|
|
10578
|
+
}
|
|
10535
10579
|
class TerminalSession {
|
|
10536
10580
|
id;
|
|
10537
10581
|
tmuxSession;
|
|
@@ -11050,6 +11094,7 @@ class WebTerminalManager {
|
|
|
11050
11094
|
tags: info.tags,
|
|
11051
11095
|
manual: info.manual,
|
|
11052
11096
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
11097
|
+
...seenGeometry(info.cols, info.rows),
|
|
11053
11098
|
// Live binding first (authoritative while it exists), persisted
|
|
11054
11099
|
// metadata as the fallback once the mirror has been torn down.
|
|
11055
11100
|
mirrorSessionId: this.mirrorResolver?.(id) ?? mirror?.sessionId,
|
|
@@ -11103,6 +11148,7 @@ class WebTerminalManager {
|
|
|
11103
11148
|
tags: info.tags,
|
|
11104
11149
|
manual: info.manual,
|
|
11105
11150
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
11151
|
+
...seenGeometry(info.cols, info.rows),
|
|
11106
11152
|
mirrorSessionId: mirror?.sessionId,
|
|
11107
11153
|
claudeSessionId: mirror?.claudeSessionId,
|
|
11108
11154
|
reason: "daemon-gap",
|
|
@@ -11115,6 +11161,7 @@ class WebTerminalManager {
|
|
|
11115
11161
|
cwd: info.cwd,
|
|
11116
11162
|
tags: info.tags,
|
|
11117
11163
|
manual: info.manual,
|
|
11164
|
+
...seenGeometry(info.cols, info.rows),
|
|
11118
11165
|
seenAt: info.seenAt,
|
|
11119
11166
|
claudeSessionId: mirror?.claudeSessionId
|
|
11120
11167
|
});
|
|
@@ -11256,9 +11303,10 @@ class WebTerminalManager {
|
|
|
11256
11303
|
"-e",
|
|
11257
11304
|
`VH_HAPPY_HOME_DIR=${configuration.happyHomeDir}`
|
|
11258
11305
|
] : [];
|
|
11306
|
+
const { cols, rows } = coldCreateGeometry(plan);
|
|
11259
11307
|
const created = node_child_process.spawnSync(
|
|
11260
11308
|
"tmux",
|
|
11261
|
-
tmuxArgs(tmuxNewSessionArgs(name,
|
|
11309
|
+
tmuxArgs(tmuxNewSessionArgs(name, cols, rows, plan.cwd, envFlags, defaultShell())),
|
|
11262
11310
|
{ stdio: "ignore", timeout: TMUX_CREATE_TIMEOUT_MS, env }
|
|
11263
11311
|
);
|
|
11264
11312
|
const live = created.status === 0 && node_child_process.spawnSync(
|
|
@@ -11311,7 +11359,7 @@ class WebTerminalManager {
|
|
|
11311
11359
|
* would care about changed (see liveSnapshotChanged). */
|
|
11312
11360
|
persistLiveSnapshot(live, now) {
|
|
11313
11361
|
const candidate = /* @__PURE__ */ new Map();
|
|
11314
|
-
for (const [id, info] of live) candidate.set(id, { title: info.title, cwd: info.cwd, tags: info.tags, manual: info.manual, ...info.attachTmux ? { attachTmux: info.attachTmux } : {}, seenAt: now });
|
|
11362
|
+
for (const [id, info] of live) candidate.set(id, { title: info.title, cwd: info.cwd, tags: info.tags, manual: info.manual, ...info.attachTmux ? { attachTmux: info.attachTmux } : {}, ...seenGeometry(info.cols, info.rows), seenAt: now });
|
|
11315
11363
|
if (!liveSnapshotChanged(this.lastPersistedSnapshot, candidate)) return;
|
|
11316
11364
|
this.lastPersistedSnapshot = candidate;
|
|
11317
11365
|
saveLiveSnapshot(candidate);
|
|
@@ -11422,7 +11470,11 @@ class WebTerminalManager {
|
|
|
11422
11470
|
seq: existing.seq,
|
|
11423
11471
|
mode: "replay",
|
|
11424
11472
|
chunks,
|
|
11425
|
-
|
|
11473
|
+
// B-287: a replay carries no restore, so without this the
|
|
11474
|
+
// adopting client had no authoritative width and fell back
|
|
11475
|
+
// to its own guess — the door through which a stale device
|
|
11476
|
+
// width leaked in. Announced = what we last told clients.
|
|
11477
|
+
...lines ? { streamMode: "lines", paneCols: existing.announcedCols ?? existing.cols, paneRows: existing.announcedRows ?? existing.rows } : {},
|
|
11426
11478
|
...echo2
|
|
11427
11479
|
};
|
|
11428
11480
|
}
|
|
@@ -11504,6 +11556,13 @@ class WebTerminalManager {
|
|
|
11504
11556
|
// the web xterm also sees the OSC, which is inert there.
|
|
11505
11557
|
["set-option", "-t", `=${tmuxSession}:`, "set-titles", "on"],
|
|
11506
11558
|
["set-option", "-t", `=${tmuxSession}:`, "set-titles-string", "#{pane_title}"],
|
|
11559
|
+
// B-287: re-assert on EVERY open, not just at create. A session
|
|
11560
|
+
// made before the create-time override (pre-0.2.96) or under a
|
|
11561
|
+
// user `window-size manual`/`smallest` silently refuses
|
|
11562
|
+
// `refresh-client -C`, so the pane freezes at whatever width the
|
|
11563
|
+
// first viewer set and no later device can widen it. `-w -q` so
|
|
11564
|
+
// an older tmux without the option does not fail the open.
|
|
11565
|
+
["set-option", "-w", "-q", "-t", `=${tmuxSession}:`, "window-size", "latest"],
|
|
11507
11566
|
["set-option", "-g", "set-clipboard", "on"],
|
|
11508
11567
|
["set-option", "-ga", "terminal-features", ",xterm-256color:clipboard"]
|
|
11509
11568
|
];
|
|
@@ -11899,13 +11958,12 @@ class WebTerminalManager {
|
|
|
11899
11958
|
this.applyResize(session, cols, rows);
|
|
11900
11959
|
}
|
|
11901
11960
|
/** Resize the transport AND the authoritative headless screen together, so a
|
|
11902
|
-
* later snapshot matches the real geometry.
|
|
11903
|
-
*
|
|
11904
|
-
*
|
|
11905
|
-
*
|
|
11906
|
-
*
|
|
11907
|
-
*
|
|
11908
|
-
* squeezing the desktop" — not implemented in this batch). */
|
|
11961
|
+
* later snapshot matches the real geometry. One tmux window = one size:
|
|
11962
|
+
* we take the LAST resize we are handed (B-287: the web only sends one on a
|
|
11963
|
+
* human-activity edge, so "last resize" == "most recently used viewer" ==
|
|
11964
|
+
* tmux's own `window-size latest` semantics). The control-client
|
|
11965
|
+
* declaration IS `refresh-client -C`, preceded by a `window-size latest`
|
|
11966
|
+
* re-assert so a manual/smallest session cannot swallow it. */
|
|
11909
11967
|
applyResize(session, cols, rows) {
|
|
11910
11968
|
const c = Math.max(2, Math.floor(cols));
|
|
11911
11969
|
const r = Math.max(2, Math.floor(rows));
|
|
@@ -11916,7 +11974,10 @@ class WebTerminalManager {
|
|
|
11916
11974
|
logger.debug(`[WEB TERMINAL] resize ${session.id} failed: ${e}`);
|
|
11917
11975
|
}
|
|
11918
11976
|
} else if (c !== session.cols || r !== session.rows) {
|
|
11919
|
-
session.transport.client.sendFireAndForget([
|
|
11977
|
+
session.transport.client.sendFireAndForget([
|
|
11978
|
+
`set-option -w -q -t =${session.tmuxSession}: window-size latest`,
|
|
11979
|
+
`refresh-client -C ${c}x${r}`
|
|
11980
|
+
]);
|
|
11920
11981
|
}
|
|
11921
11982
|
session.resizeHeadless(c, r);
|
|
11922
11983
|
}
|
|
@@ -11959,7 +12020,10 @@ class WebTerminalManager {
|
|
|
11959
12020
|
* session (so a local `tmux attach` won't find it either). Used when the
|
|
11960
12021
|
* user deletes the terminal from the sidebar. */
|
|
11961
12022
|
killSession(terminalId, opts) {
|
|
11962
|
-
const info = this.lastSeenInfo.get(terminalId) ??
|
|
12023
|
+
const info = this.lastSeenInfo.get(terminalId) ?? (() => {
|
|
12024
|
+
const t = this.listSessions().find((x) => x.id === terminalId);
|
|
12025
|
+
return t ? seenInfoOf(t) : void 0;
|
|
12026
|
+
})();
|
|
11963
12027
|
const target = `=vh-${terminalId}:`;
|
|
11964
12028
|
const killed = node_child_process.spawnSync("tmux", tmuxArgs(["kill-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
|
|
11965
12029
|
const verified = node_child_process.spawnSync("tmux", tmuxArgs(["has-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
|
|
@@ -11985,6 +12049,7 @@ class WebTerminalManager {
|
|
|
11985
12049
|
tags: info.tags,
|
|
11986
12050
|
manual: info.manual,
|
|
11987
12051
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
12052
|
+
...seenGeometry(info.cols, info.rows),
|
|
11988
12053
|
mirrorSessionId: this.mirrorResolver?.(terminalId) ?? mirror?.sessionId,
|
|
11989
12054
|
claudeSessionId: mirror?.claudeSessionId,
|
|
11990
12055
|
reason: "closed",
|
|
@@ -12074,7 +12139,8 @@ class WebTerminalManager {
|
|
|
12074
12139
|
// the mirror reconciler's stricter "claude is really here" gate.
|
|
12075
12140
|
claudeConfident: probe.claudeConfident,
|
|
12076
12141
|
manual: s.manual,
|
|
12077
|
-
...s.attachTmux ? { attachTmux: s.attachTmux } : {}
|
|
12142
|
+
...s.attachTmux ? { attachTmux: s.attachTmux } : {},
|
|
12143
|
+
...s.paneCols !== void 0 && s.paneRows !== void 0 ? { paneCols: s.paneCols, paneRows: s.paneRows } : {}
|
|
12078
12144
|
});
|
|
12079
12145
|
}
|
|
12080
12146
|
return out;
|