very-happy-cli 0.2.99 → 0.2.101
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-DkvV2nhl.cjs → AcpBackend-BHst4DAF.cjs} +1 -1
- package/dist/{AcpBackend-C9LtzYSx.mjs → AcpBackend-mbMlyZnj.mjs} +1 -1
- package/dist/{AcpSessionManager-B5rJcE82.mjs → AcpSessionManager-CwdvDD_z.mjs} +1 -1
- package/dist/{AcpSessionManager-gsp_-PGy.cjs → AcpSessionManager-Nyxm1kCR.cjs} +1 -1
- package/dist/{config-BcCVR-Wx.mjs → config-C1Z39_GK.mjs} +2 -2
- package/dist/{config-Bidi0Yeq.cjs → config-DjtLOWRc.cjs} +2 -2
- package/dist/{index-B87z2DOo.mjs → index-3ssSAknz.mjs} +241 -15
- package/dist/{index-Wfds4scC.mjs → index-BL9LWtAR.mjs} +5 -5
- package/dist/index-BnVk_E3V.mjs +51 -0
- package/dist/{index-Dp1EYEyF.cjs → index-DemE8OgZ.cjs} +245 -18
- package/dist/index-DqA3-Xxl.cjs +57 -0
- package/dist/{index-CxdGnaov.cjs → index-tQZwyC3N.cjs} +4 -4
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-jaCruGfR.cjs → installTerminalHooks-BC9thC5_.cjs} +2 -2
- package/dist/{installTerminalHooks-DkOh088o.mjs → installTerminalHooks-BvYqvibn.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-ZQbu8tL1.cjs → mcp-ChjpeHtZ.cjs} +2 -2
- package/dist/{mcp-DhftF_XE.mjs → mcp-D6qSHTLO.mjs} +2 -2
- package/dist/{runGemini-3-xWWBRA.mjs → runGemini-D1SFLnxb.mjs} +4 -4
- package/dist/{runGemini-Ce7Maf-D.cjs → runGemini-Dy7X7eDh.cjs} +4 -4
- package/dist/{runOpenClaw-DZhGhihh.mjs → runOpenClaw-AsjRz-Q3.mjs} +3 -3
- package/dist/{runOpenClaw-q3inEsDr.cjs → runOpenClaw-BpyEPSp5.cjs} +3 -3
- package/dist/{send-BdEJeVye.cjs → send-CvArl8UY.cjs} +2 -2
- package/dist/{send-B3NOqi9-.mjs → send-D1arw2mU.mjs} +2 -2
- package/dist/{spawn-DEVUh4iW.cjs → spawn-BYby4e76.cjs} +2 -2
- package/dist/{spawn-CCYAeBfN.mjs → spawn-D3k-ywKX.mjs} +2 -2
- package/dist/{types-DhxxJFD0.cjs → types-BEwKkfnX.cjs} +99 -37
- package/dist/{types-CrJj-daH.mjs → types-CkxTp9iI.mjs} +98 -36
- package/package.json +7 -7
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
4
|
var node_fs = require('node:fs');
|
|
5
5
|
var path = require('node:path');
|
|
6
|
-
var persistence = require('./types-
|
|
7
|
-
var index = require('./index-
|
|
6
|
+
var persistence = require('./types-BEwKkfnX.cjs');
|
|
7
|
+
var index = require('./index-DemE8OgZ.cjs');
|
|
8
8
|
require('axios');
|
|
9
9
|
require('node:util');
|
|
10
10
|
require('node:os');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
-
import { r as readPersistedSessions, c as configuration } from './types-
|
|
5
|
-
import { b as sendUserMessage, a as sessionWebUrl } from './index-
|
|
4
|
+
import { r as readPersistedSessions, c as configuration } from './types-CkxTp9iI.mjs';
|
|
5
|
+
import { b as sendUserMessage, a as sessionWebUrl } from './index-3ssSAknz.mjs';
|
|
6
6
|
import 'axios';
|
|
7
7
|
import 'node:util';
|
|
8
8
|
import 'node:os';
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
4
|
var node_fs = require('node:fs');
|
|
5
5
|
var path = require('node:path');
|
|
6
|
-
var index = require('./index-
|
|
7
|
-
var persistence = require('./types-
|
|
6
|
+
var index = require('./index-DemE8OgZ.cjs');
|
|
7
|
+
var persistence = require('./types-BEwKkfnX.cjs');
|
|
8
8
|
require('node:os');
|
|
9
9
|
require('node:crypto');
|
|
10
10
|
require('cross-spawn');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
-
import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, w as waitForSessionKey, b as sendUserMessage } from './index-
|
|
5
|
-
import { l as logger } from './types-
|
|
4
|
+
import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, w as waitForSessionKey, b as sendUserMessage } from './index-3ssSAknz.mjs';
|
|
5
|
+
import { l as logger } from './types-CkxTp9iI.mjs';
|
|
6
6
|
import 'node:os';
|
|
7
7
|
import 'node:crypto';
|
|
8
8
|
import 'cross-spawn';
|
|
@@ -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.101";
|
|
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.101",
|
|
168
|
+
"very-happy-tools-x64-darwin": "0.2.101",
|
|
169
|
+
"very-happy-tools-arm64-linux": "0.2.101",
|
|
170
|
+
"very-happy-tools-x64-linux": "0.2.101",
|
|
171
|
+
"very-happy-tools-arm64-win32": "0.2.101",
|
|
172
|
+
"very-happy-tools-x64-win32": "0.2.101"
|
|
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-BEwKkfnX.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) {
|
|
@@ -11050,6 +11090,7 @@ class WebTerminalManager {
|
|
|
11050
11090
|
tags: info.tags,
|
|
11051
11091
|
manual: info.manual,
|
|
11052
11092
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
11093
|
+
...seenGeometry(info.cols, info.rows),
|
|
11053
11094
|
// Live binding first (authoritative while it exists), persisted
|
|
11054
11095
|
// metadata as the fallback once the mirror has been torn down.
|
|
11055
11096
|
mirrorSessionId: this.mirrorResolver?.(id) ?? mirror?.sessionId,
|
|
@@ -11103,6 +11144,7 @@ class WebTerminalManager {
|
|
|
11103
11144
|
tags: info.tags,
|
|
11104
11145
|
manual: info.manual,
|
|
11105
11146
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
11147
|
+
...seenGeometry(info.cols, info.rows),
|
|
11106
11148
|
mirrorSessionId: mirror?.sessionId,
|
|
11107
11149
|
claudeSessionId: mirror?.claudeSessionId,
|
|
11108
11150
|
reason: "daemon-gap",
|
|
@@ -11115,6 +11157,7 @@ class WebTerminalManager {
|
|
|
11115
11157
|
cwd: info.cwd,
|
|
11116
11158
|
tags: info.tags,
|
|
11117
11159
|
manual: info.manual,
|
|
11160
|
+
...seenGeometry(info.cols, info.rows),
|
|
11118
11161
|
seenAt: info.seenAt,
|
|
11119
11162
|
claudeSessionId: mirror?.claudeSessionId
|
|
11120
11163
|
});
|
|
@@ -11256,9 +11299,10 @@ class WebTerminalManager {
|
|
|
11256
11299
|
"-e",
|
|
11257
11300
|
`VH_HAPPY_HOME_DIR=${configuration.happyHomeDir}`
|
|
11258
11301
|
] : [];
|
|
11302
|
+
const { cols, rows } = coldCreateGeometry(plan);
|
|
11259
11303
|
const created = node_child_process.spawnSync(
|
|
11260
11304
|
"tmux",
|
|
11261
|
-
tmuxArgs(tmuxNewSessionArgs(name,
|
|
11305
|
+
tmuxArgs(tmuxNewSessionArgs(name, cols, rows, plan.cwd, envFlags, defaultShell())),
|
|
11262
11306
|
{ stdio: "ignore", timeout: TMUX_CREATE_TIMEOUT_MS, env }
|
|
11263
11307
|
);
|
|
11264
11308
|
const live = created.status === 0 && node_child_process.spawnSync(
|
|
@@ -11311,7 +11355,7 @@ class WebTerminalManager {
|
|
|
11311
11355
|
* would care about changed (see liveSnapshotChanged). */
|
|
11312
11356
|
persistLiveSnapshot(live, now) {
|
|
11313
11357
|
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 });
|
|
11358
|
+
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
11359
|
if (!liveSnapshotChanged(this.lastPersistedSnapshot, candidate)) return;
|
|
11316
11360
|
this.lastPersistedSnapshot = candidate;
|
|
11317
11361
|
saveLiveSnapshot(candidate);
|
|
@@ -11422,7 +11466,11 @@ class WebTerminalManager {
|
|
|
11422
11466
|
seq: existing.seq,
|
|
11423
11467
|
mode: "replay",
|
|
11424
11468
|
chunks,
|
|
11425
|
-
|
|
11469
|
+
// B-287: a replay carries no restore, so without this the
|
|
11470
|
+
// adopting client had no authoritative width and fell back
|
|
11471
|
+
// to its own guess — the door through which a stale device
|
|
11472
|
+
// width leaked in. Announced = what we last told clients.
|
|
11473
|
+
...lines ? { streamMode: "lines", paneCols: existing.announcedCols ?? existing.cols, paneRows: existing.announcedRows ?? existing.rows } : {},
|
|
11426
11474
|
...echo2
|
|
11427
11475
|
};
|
|
11428
11476
|
}
|
|
@@ -11504,6 +11552,13 @@ class WebTerminalManager {
|
|
|
11504
11552
|
// the web xterm also sees the OSC, which is inert there.
|
|
11505
11553
|
["set-option", "-t", `=${tmuxSession}:`, "set-titles", "on"],
|
|
11506
11554
|
["set-option", "-t", `=${tmuxSession}:`, "set-titles-string", "#{pane_title}"],
|
|
11555
|
+
// B-287: re-assert on EVERY open, not just at create. A session
|
|
11556
|
+
// made before the create-time override (pre-0.2.96) or under a
|
|
11557
|
+
// user `window-size manual`/`smallest` silently refuses
|
|
11558
|
+
// `refresh-client -C`, so the pane freezes at whatever width the
|
|
11559
|
+
// first viewer set and no later device can widen it. `-w -q` so
|
|
11560
|
+
// an older tmux without the option does not fail the open.
|
|
11561
|
+
["set-option", "-w", "-q", "-t", `=${tmuxSession}:`, "window-size", "latest"],
|
|
11507
11562
|
["set-option", "-g", "set-clipboard", "on"],
|
|
11508
11563
|
["set-option", "-ga", "terminal-features", ",xterm-256color:clipboard"]
|
|
11509
11564
|
];
|
|
@@ -11899,13 +11954,12 @@ class WebTerminalManager {
|
|
|
11899
11954
|
this.applyResize(session, cols, rows);
|
|
11900
11955
|
}
|
|
11901
11956
|
/** 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). */
|
|
11957
|
+
* later snapshot matches the real geometry. One tmux window = one size:
|
|
11958
|
+
* we take the LAST resize we are handed (B-287: the web only sends one on a
|
|
11959
|
+
* human-activity edge, so "last resize" == "most recently used viewer" ==
|
|
11960
|
+
* tmux's own `window-size latest` semantics). The control-client
|
|
11961
|
+
* declaration IS `refresh-client -C`, preceded by a `window-size latest`
|
|
11962
|
+
* re-assert so a manual/smallest session cannot swallow it. */
|
|
11909
11963
|
applyResize(session, cols, rows) {
|
|
11910
11964
|
const c = Math.max(2, Math.floor(cols));
|
|
11911
11965
|
const r = Math.max(2, Math.floor(rows));
|
|
@@ -11916,7 +11970,10 @@ class WebTerminalManager {
|
|
|
11916
11970
|
logger.debug(`[WEB TERMINAL] resize ${session.id} failed: ${e}`);
|
|
11917
11971
|
}
|
|
11918
11972
|
} else if (c !== session.cols || r !== session.rows) {
|
|
11919
|
-
session.transport.client.sendFireAndForget([
|
|
11973
|
+
session.transport.client.sendFireAndForget([
|
|
11974
|
+
`set-option -w -q -t =${session.tmuxSession}: window-size latest`,
|
|
11975
|
+
`refresh-client -C ${c}x${r}`
|
|
11976
|
+
]);
|
|
11920
11977
|
}
|
|
11921
11978
|
session.resizeHeadless(c, r);
|
|
11922
11979
|
}
|
|
@@ -11959,7 +12016,10 @@ class WebTerminalManager {
|
|
|
11959
12016
|
* session (so a local `tmux attach` won't find it either). Used when the
|
|
11960
12017
|
* user deletes the terminal from the sidebar. */
|
|
11961
12018
|
killSession(terminalId, opts) {
|
|
11962
|
-
const info = this.lastSeenInfo.get(terminalId) ??
|
|
12019
|
+
const info = this.lastSeenInfo.get(terminalId) ?? (() => {
|
|
12020
|
+
const t = this.listSessions().find((x) => x.id === terminalId);
|
|
12021
|
+
return t ? seenInfoOf(t) : void 0;
|
|
12022
|
+
})();
|
|
11963
12023
|
const target = `=vh-${terminalId}:`;
|
|
11964
12024
|
const killed = node_child_process.spawnSync("tmux", tmuxArgs(["kill-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
|
|
11965
12025
|
const verified = node_child_process.spawnSync("tmux", tmuxArgs(["has-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
|
|
@@ -11985,6 +12045,7 @@ class WebTerminalManager {
|
|
|
11985
12045
|
tags: info.tags,
|
|
11986
12046
|
manual: info.manual,
|
|
11987
12047
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
12048
|
+
...seenGeometry(info.cols, info.rows),
|
|
11988
12049
|
mirrorSessionId: this.mirrorResolver?.(terminalId) ?? mirror?.sessionId,
|
|
11989
12050
|
claudeSessionId: mirror?.claudeSessionId,
|
|
11990
12051
|
reason: "closed",
|
|
@@ -12074,7 +12135,8 @@ class WebTerminalManager {
|
|
|
12074
12135
|
// the mirror reconciler's stricter "claude is really here" gate.
|
|
12075
12136
|
claudeConfident: probe.claudeConfident,
|
|
12076
12137
|
manual: s.manual,
|
|
12077
|
-
...s.attachTmux ? { attachTmux: s.attachTmux } : {}
|
|
12138
|
+
...s.attachTmux ? { attachTmux: s.attachTmux } : {},
|
|
12139
|
+
...s.paneCols !== void 0 && s.paneRows !== void 0 ? { paneCols: s.paneCols, paneRows: s.paneRows } : {}
|
|
12078
12140
|
});
|
|
12079
12141
|
}
|
|
12080
12142
|
return out;
|