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
|
@@ -29,7 +29,7 @@ import { SandboxManager } from '@anthropic-ai/sandbox-runtime';
|
|
|
29
29
|
import { Expo } from 'expo-server-sdk';
|
|
30
30
|
|
|
31
31
|
var name = "very-happy-cli";
|
|
32
|
-
var version = "0.2.
|
|
32
|
+
var version = "0.2.101";
|
|
33
33
|
var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
|
|
34
34
|
var author = "Kirill Dubovitskiy";
|
|
35
35
|
var contributors = [
|
|
@@ -142,12 +142,12 @@ var dependencies = {
|
|
|
142
142
|
zod: "^4.0.0"
|
|
143
143
|
};
|
|
144
144
|
var optionalDependencies = {
|
|
145
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
146
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
147
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
148
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
149
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
150
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
145
|
+
"very-happy-tools-arm64-darwin": "0.2.101",
|
|
146
|
+
"very-happy-tools-x64-darwin": "0.2.101",
|
|
147
|
+
"very-happy-tools-arm64-linux": "0.2.101",
|
|
148
|
+
"very-happy-tools-x64-linux": "0.2.101",
|
|
149
|
+
"very-happy-tools-arm64-win32": "0.2.101",
|
|
150
|
+
"very-happy-tools-x64-win32": "0.2.101"
|
|
151
151
|
};
|
|
152
152
|
var devDependencies = {
|
|
153
153
|
"@slopus/happy-wire": "workspace:*",
|
|
@@ -4787,6 +4787,10 @@ function registerTodoHandlers(manager) {
|
|
|
4787
4787
|
});
|
|
4788
4788
|
}
|
|
4789
4789
|
|
|
4790
|
+
function sanitizeGeometry(cols, rows) {
|
|
4791
|
+
const ok = (v) => typeof v === "number" && Number.isInteger(v) && v >= 2 && v <= 1e4;
|
|
4792
|
+
return ok(cols) && ok(rows) ? { cols, rows } : void 0;
|
|
4793
|
+
}
|
|
4790
4794
|
function sanitizeTagList(value) {
|
|
4791
4795
|
if (!Array.isArray(value)) return void 0;
|
|
4792
4796
|
const out = [];
|
|
@@ -4806,7 +4810,7 @@ function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE
|
|
|
4806
4810
|
const rows = [];
|
|
4807
4811
|
for (const [id, value] of Object.entries(raw)) {
|
|
4808
4812
|
if (!id || !value || typeof value !== "object") continue;
|
|
4809
|
-
const { title, cwd, seenAt, tags, manual, attachTmux } = value;
|
|
4813
|
+
const { title, cwd, seenAt, tags, manual, attachTmux, cols: rawCols, rows: rawRows } = value;
|
|
4810
4814
|
if (typeof seenAt !== "number" || !Number.isFinite(seenAt)) continue;
|
|
4811
4815
|
if (now - seenAt >= ttlMs) continue;
|
|
4812
4816
|
const tagList = sanitizeTagList(tags);
|
|
@@ -4816,6 +4820,7 @@ function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE
|
|
|
4816
4820
|
...tagList !== void 0 ? { tags: tagList } : {},
|
|
4817
4821
|
...manual === true ? { manual: true } : {},
|
|
4818
4822
|
...typeof attachTmux === "string" && attachTmux ? { attachTmux } : {},
|
|
4823
|
+
...sanitizeGeometry(rawCols, rawRows) ?? {},
|
|
4819
4824
|
seenAt
|
|
4820
4825
|
}]);
|
|
4821
4826
|
}
|
|
@@ -4838,6 +4843,7 @@ function liveSnapshotChanged(prev, next) {
|
|
|
4838
4843
|
if (!!before.manual !== !!info.manual) return true;
|
|
4839
4844
|
if ((before.attachTmux ?? "") !== (info.attachTmux ?? "")) return true;
|
|
4840
4845
|
if ((before.tags ?? []).join("\0") !== (info.tags ?? []).join("\0")) return true;
|
|
4846
|
+
if (before.cols !== info.cols || before.rows !== info.rows) return true;
|
|
4841
4847
|
}
|
|
4842
4848
|
return false;
|
|
4843
4849
|
}
|
|
@@ -4927,6 +4933,9 @@ function attachStartupCommand(sessionId, socket) {
|
|
|
4927
4933
|
}
|
|
4928
4934
|
|
|
4929
4935
|
const TERMINAL_ID_RE$1 = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
4936
|
+
function recordGeometry(record) {
|
|
4937
|
+
return record.cols !== void 0 && record.rows !== void 0 ? { cols: record.cols, rows: record.rows } : {};
|
|
4938
|
+
}
|
|
4930
4939
|
function planTerminalRestore(record, facts) {
|
|
4931
4940
|
if (facts.tmuxAlive) return { kind: "already-live" };
|
|
4932
4941
|
if (record.attachTmux) {
|
|
@@ -4942,7 +4951,8 @@ function planTerminalRestore(record, facts) {
|
|
|
4942
4951
|
manual: true,
|
|
4943
4952
|
tags: record.tags,
|
|
4944
4953
|
command: attachStartupCommand(matches[0].id, facts.attachSocket),
|
|
4945
|
-
attachTmux: record.attachTmux
|
|
4954
|
+
attachTmux: record.attachTmux,
|
|
4955
|
+
...recordGeometry(record)
|
|
4946
4956
|
};
|
|
4947
4957
|
}
|
|
4948
4958
|
if (!record.cwd || !facts.cwdExists(record.cwd)) return { kind: "error", reason: "missing-cwd" };
|
|
@@ -4954,7 +4964,8 @@ function planTerminalRestore(record, facts) {
|
|
|
4954
4964
|
title: record.title,
|
|
4955
4965
|
manual: record.manual === true,
|
|
4956
4966
|
tags: record.tags,
|
|
4957
|
-
...resumable ? { command: `claude --resume ${record.claudeSessionId}` } : {}
|
|
4967
|
+
...resumable ? { command: `claude --resume ${record.claudeSessionId}` } : {},
|
|
4968
|
+
...recordGeometry(record)
|
|
4958
4969
|
};
|
|
4959
4970
|
}
|
|
4960
4971
|
|
|
@@ -9515,6 +9526,7 @@ const CAPTURE_FULL_BUDGET_BYTES = 1024 * 1024;
|
|
|
9515
9526
|
const ALT_ENTER = Buffer.from("\x1B[?1049h", "ascii");
|
|
9516
9527
|
const CURSOR_HOME = Buffer.from("\x1B[H", "ascii");
|
|
9517
9528
|
const CRLF = Buffer.from("\r\n", "ascii");
|
|
9529
|
+
const SGR_RESET = Buffer.from("\x1B[0m", "ascii");
|
|
9518
9530
|
function normalizeCaptureLines(blob) {
|
|
9519
9531
|
if (blob.length === 0) return blob;
|
|
9520
9532
|
const lines = [];
|
|
@@ -9551,32 +9563,38 @@ function assembleRestore(responses, paneState, budget = CAPTURE_FULL_BUDGET_BYTE
|
|
|
9551
9563
|
const screen = () => {
|
|
9552
9564
|
const raw = get("visible");
|
|
9553
9565
|
const lines = splitLines(raw);
|
|
9566
|
+
let realCount = lines.length;
|
|
9554
9567
|
if (rows !== void 0 && rows > 0) {
|
|
9555
9568
|
while (lines.length > rows) lines.shift();
|
|
9569
|
+
realCount = lines.length;
|
|
9556
9570
|
while (lines.length < rows) lines.push(EMPTY);
|
|
9557
9571
|
}
|
|
9558
|
-
const
|
|
9572
|
+
const realBody = joinCrlf(lines.slice(0, realCount));
|
|
9573
|
+
const padCount = lines.length - realCount;
|
|
9574
|
+
const padBody = padCount > 0 ? Buffer.concat([realCount > 0 ? CRLF : EMPTY, SGR_RESET, joinCrlf(new Array(padCount).fill(EMPTY))]) : EMPTY;
|
|
9559
9575
|
const cursor = paneState ? Buffer.from(`\x1B[${paneState.cursorY + 1};${paneState.cursorX + 1}H`, "ascii") : Buffer.alloc(0);
|
|
9560
|
-
return Buffer.concat([
|
|
9576
|
+
return Buffer.concat([realBody, padBody, SGR_RESET, tail, cursor]);
|
|
9561
9577
|
};
|
|
9578
|
+
const wrapFull = (b) => Buffer.concat([SGR_RESET, b, SGR_RESET]);
|
|
9579
|
+
const wrapSmall = (b) => Buffer.concat([b, SGR_RESET]);
|
|
9562
9580
|
if (!alternateOn) {
|
|
9563
9581
|
const history2 = trimTrailingBlankRows(normalizeCaptureLines(get("history")));
|
|
9564
9582
|
const smallHistory = trimTrailingBlankRows(normalizeCaptureLines(get("smallHistory")));
|
|
9565
9583
|
const withHistory = (h) => Buffer.concat(
|
|
9566
|
-
h.length > 0 ? [truncateToBudget(h, budget), CRLF, screen()] : [screen()]
|
|
9584
|
+
h.length > 0 ? [truncateToBudget(h, budget), SGR_RESET, CRLF, screen()] : [screen()]
|
|
9567
9585
|
);
|
|
9568
|
-
return { full: withHistory(history2), small: withHistory(smallHistory), alternateOn };
|
|
9586
|
+
return { full: wrapFull(withHistory(history2)), small: wrapSmall(withHistory(smallHistory)), alternateOn };
|
|
9569
9587
|
}
|
|
9570
9588
|
const history = trimTrailingBlankRows(normalizeCaptureLines(get("history")));
|
|
9571
9589
|
const saved = trimTrailingBlankRows(normalizeCaptureLines(get("altSaved")));
|
|
9572
9590
|
const scrollback = truncateToBudget(
|
|
9573
|
-
Buffer.concat(history.length > 0 && saved.length > 0 ? [history, CRLF, saved] : [history, saved]),
|
|
9591
|
+
Buffer.concat(history.length > 0 && saved.length > 0 ? [history, SGR_RESET, CRLF, saved] : [history, saved]),
|
|
9574
9592
|
budget
|
|
9575
9593
|
);
|
|
9576
|
-
const altScreen = Buffer.concat([ALT_ENTER, CURSOR_HOME, screen()]);
|
|
9594
|
+
const altScreen = Buffer.concat([ALT_ENTER, CURSOR_HOME, SGR_RESET, screen()]);
|
|
9577
9595
|
return {
|
|
9578
|
-
full: Buffer.concat([scrollback, scrollback.length > 0 ? CRLF : Buffer.alloc(0), altScreen]),
|
|
9579
|
-
small: altScreen,
|
|
9596
|
+
full: wrapFull(Buffer.concat([scrollback, scrollback.length > 0 ? CRLF : Buffer.alloc(0), altScreen])),
|
|
9597
|
+
small: wrapSmall(altScreen),
|
|
9580
9598
|
alternateOn
|
|
9581
9599
|
};
|
|
9582
9600
|
}
|
|
@@ -10074,6 +10092,7 @@ function selectAutoRestore(candidates, opts) {
|
|
|
10074
10092
|
terminalId: c.id,
|
|
10075
10093
|
...c.tags !== void 0 ? { tags: c.tags } : {},
|
|
10076
10094
|
...c.manual ? { manual: true } : {},
|
|
10095
|
+
...c.cols !== void 0 && c.rows !== void 0 ? { cols: c.cols, rows: c.rows } : {},
|
|
10077
10096
|
cwd: c.cwd,
|
|
10078
10097
|
title: c.title,
|
|
10079
10098
|
claudeSessionId: c.claudeSessionId,
|
|
@@ -10261,7 +10280,10 @@ function startupInjectionArgs(tmuxSession, command) {
|
|
|
10261
10280
|
];
|
|
10262
10281
|
}
|
|
10263
10282
|
function seenInfoOf(t) {
|
|
10264
|
-
return { title: t.title, cwd: t.cwd, tags: t.tags, manual: t.manual, attachTmux: t.attachTmux };
|
|
10283
|
+
return { title: t.title, cwd: t.cwd, tags: t.tags, manual: t.manual, attachTmux: t.attachTmux, ...seenGeometry(t.paneCols, t.paneRows) };
|
|
10284
|
+
}
|
|
10285
|
+
function seenGeometry(cols, rows) {
|
|
10286
|
+
return cols !== void 0 && rows !== void 0 ? { cols, rows } : {};
|
|
10265
10287
|
}
|
|
10266
10288
|
function terminalListSignature(items) {
|
|
10267
10289
|
const canon = [...items].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((t) => [
|
|
@@ -10306,6 +10328,12 @@ const LIST_SESSIONS_FORMAT = [
|
|
|
10306
10328
|
// B-273: the user tmux session this terminal was opened to attach (name),
|
|
10307
10329
|
// carried into close records so a restore can re-attach.
|
|
10308
10330
|
"#{@vh_attach}",
|
|
10331
|
+
// B-287: the pane's REAL geometry, persisted with the live snapshot so a
|
|
10332
|
+
// cold restore (daemon boot auto-restore, archive ↻) recreates the session
|
|
10333
|
+
// at the size it last had — claude's one-shot welcome banner then never
|
|
10334
|
+
// gets reflowed by the first web open (the "half-drawn logo").
|
|
10335
|
+
"#{pane_width}",
|
|
10336
|
+
"#{pane_height}",
|
|
10309
10337
|
// B-121: the control-mode client has no `pty.process`, so the agent-state
|
|
10310
10338
|
// fast path lost its live `#{pane_current_command}` equivalent. Carry it in
|
|
10311
10339
|
// the ONE list-sessions call the tracker already makes — the value goes
|
|
@@ -10320,9 +10348,10 @@ const LIST_SESSIONS_FORMAT = [
|
|
|
10320
10348
|
function parseSessionListLine(line) {
|
|
10321
10349
|
if (!line) return void 0;
|
|
10322
10350
|
const parts = line.split(LIST_FIELD_SEP);
|
|
10323
|
-
if (parts.length <
|
|
10324
|
-
const [name, created, activity, cwd, vhTitle, manual, vhTags, vhAttach, paneCommand] = parts;
|
|
10351
|
+
if (parts.length < 12) return void 0;
|
|
10352
|
+
const [name, created, activity, cwd, vhTitle, manual, vhTags, vhAttach, paneW, paneH, paneCommand] = parts;
|
|
10325
10353
|
if (!name) return void 0;
|
|
10354
|
+
const geom = parsePositiveInt(paneW) !== void 0 && parsePositiveInt(paneH) !== void 0 ? { paneCols: parsePositiveInt(paneW), paneRows: parsePositiveInt(paneH) } : {};
|
|
10326
10355
|
return {
|
|
10327
10356
|
name,
|
|
10328
10357
|
created: created ? Number(created) * 1e3 : void 0,
|
|
@@ -10335,11 +10364,17 @@ function parseSessionListLine(line) {
|
|
|
10335
10364
|
// Verbatim (no trim): tmux allows edge spaces in a session name and the
|
|
10336
10365
|
// restore lookup / attach echo compare it exactly.
|
|
10337
10366
|
attachTmux: isSafeTmuxSessionName(vhAttach) ? vhAttach : void 0,
|
|
10338
|
-
|
|
10367
|
+
...geom,
|
|
10368
|
+
// pane_title is last, so anything after field 11 is title content that
|
|
10339
10369
|
// contained the separator — rejoin it rather than dropping it.
|
|
10340
|
-
paneTitle: parts.slice(
|
|
10370
|
+
paneTitle: parts.slice(11).join(LIST_FIELD_SEP) || void 0
|
|
10341
10371
|
};
|
|
10342
10372
|
}
|
|
10373
|
+
function parsePositiveInt(raw) {
|
|
10374
|
+
if (raw === void 0 || !/^[0-9]{1,5}$/.test(raw.trim())) return void 0;
|
|
10375
|
+
const n = Number(raw.trim());
|
|
10376
|
+
return n > 0 ? n : void 0;
|
|
10377
|
+
}
|
|
10343
10378
|
const TERMINAL_TAG_MAX_COUNT = 64;
|
|
10344
10379
|
const TERMINAL_TAG_MAX_LENGTH = 24;
|
|
10345
10380
|
const TERMINAL_TAGS_MAX_BYTES = 4096;
|
|
@@ -10482,6 +10517,11 @@ const VH_SESSION_OPTION_OVERRIDES = [
|
|
|
10482
10517
|
["window", "window-size", "latest"],
|
|
10483
10518
|
["window", "pane-border-status", "off"]
|
|
10484
10519
|
];
|
|
10520
|
+
const COLD_CREATE_DEFAULT_GEOMETRY = { cols: 120, rows: 30 };
|
|
10521
|
+
function coldCreateGeometry(plan) {
|
|
10522
|
+
const ok = (v) => typeof v === "number" && Number.isInteger(v) && v >= 2 && v <= 1e4;
|
|
10523
|
+
return ok(plan.cols) && ok(plan.rows) ? { cols: plan.cols, rows: plan.rows } : { ...COLD_CREATE_DEFAULT_GEOMETRY };
|
|
10524
|
+
}
|
|
10485
10525
|
function tmuxNewSessionArgs(session, cols, rows, cwd, envFlags, shell) {
|
|
10486
10526
|
const args = ["new-session", "-d", ...envFlags, "-s", session, "-x", String(cols), "-y", String(rows), "-c", cwd, shell];
|
|
10487
10527
|
for (const [scope, option, value] of VH_SESSION_OPTION_OVERRIDES) {
|
|
@@ -11028,6 +11068,7 @@ class WebTerminalManager {
|
|
|
11028
11068
|
tags: info.tags,
|
|
11029
11069
|
manual: info.manual,
|
|
11030
11070
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
11071
|
+
...seenGeometry(info.cols, info.rows),
|
|
11031
11072
|
// Live binding first (authoritative while it exists), persisted
|
|
11032
11073
|
// metadata as the fallback once the mirror has been torn down.
|
|
11033
11074
|
mirrorSessionId: this.mirrorResolver?.(id) ?? mirror?.sessionId,
|
|
@@ -11081,6 +11122,7 @@ class WebTerminalManager {
|
|
|
11081
11122
|
tags: info.tags,
|
|
11082
11123
|
manual: info.manual,
|
|
11083
11124
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
11125
|
+
...seenGeometry(info.cols, info.rows),
|
|
11084
11126
|
mirrorSessionId: mirror?.sessionId,
|
|
11085
11127
|
claudeSessionId: mirror?.claudeSessionId,
|
|
11086
11128
|
reason: "daemon-gap",
|
|
@@ -11093,6 +11135,7 @@ class WebTerminalManager {
|
|
|
11093
11135
|
cwd: info.cwd,
|
|
11094
11136
|
tags: info.tags,
|
|
11095
11137
|
manual: info.manual,
|
|
11138
|
+
...seenGeometry(info.cols, info.rows),
|
|
11096
11139
|
seenAt: info.seenAt,
|
|
11097
11140
|
claudeSessionId: mirror?.claudeSessionId
|
|
11098
11141
|
});
|
|
@@ -11234,9 +11277,10 @@ class WebTerminalManager {
|
|
|
11234
11277
|
"-e",
|
|
11235
11278
|
`VH_HAPPY_HOME_DIR=${configuration.happyHomeDir}`
|
|
11236
11279
|
] : [];
|
|
11280
|
+
const { cols, rows } = coldCreateGeometry(plan);
|
|
11237
11281
|
const created = spawnSync(
|
|
11238
11282
|
"tmux",
|
|
11239
|
-
tmuxArgs(tmuxNewSessionArgs(name,
|
|
11283
|
+
tmuxArgs(tmuxNewSessionArgs(name, cols, rows, plan.cwd, envFlags, defaultShell())),
|
|
11240
11284
|
{ stdio: "ignore", timeout: TMUX_CREATE_TIMEOUT_MS, env }
|
|
11241
11285
|
);
|
|
11242
11286
|
const live = created.status === 0 && spawnSync(
|
|
@@ -11289,7 +11333,7 @@ class WebTerminalManager {
|
|
|
11289
11333
|
* would care about changed (see liveSnapshotChanged). */
|
|
11290
11334
|
persistLiveSnapshot(live, now) {
|
|
11291
11335
|
const candidate = /* @__PURE__ */ new Map();
|
|
11292
|
-
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 });
|
|
11336
|
+
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 });
|
|
11293
11337
|
if (!liveSnapshotChanged(this.lastPersistedSnapshot, candidate)) return;
|
|
11294
11338
|
this.lastPersistedSnapshot = candidate;
|
|
11295
11339
|
saveLiveSnapshot(candidate);
|
|
@@ -11400,7 +11444,11 @@ class WebTerminalManager {
|
|
|
11400
11444
|
seq: existing.seq,
|
|
11401
11445
|
mode: "replay",
|
|
11402
11446
|
chunks,
|
|
11403
|
-
|
|
11447
|
+
// B-287: a replay carries no restore, so without this the
|
|
11448
|
+
// adopting client had no authoritative width and fell back
|
|
11449
|
+
// to its own guess — the door through which a stale device
|
|
11450
|
+
// width leaked in. Announced = what we last told clients.
|
|
11451
|
+
...lines ? { streamMode: "lines", paneCols: existing.announcedCols ?? existing.cols, paneRows: existing.announcedRows ?? existing.rows } : {},
|
|
11404
11452
|
...echo2
|
|
11405
11453
|
};
|
|
11406
11454
|
}
|
|
@@ -11482,6 +11530,13 @@ class WebTerminalManager {
|
|
|
11482
11530
|
// the web xterm also sees the OSC, which is inert there.
|
|
11483
11531
|
["set-option", "-t", `=${tmuxSession}:`, "set-titles", "on"],
|
|
11484
11532
|
["set-option", "-t", `=${tmuxSession}:`, "set-titles-string", "#{pane_title}"],
|
|
11533
|
+
// B-287: re-assert on EVERY open, not just at create. A session
|
|
11534
|
+
// made before the create-time override (pre-0.2.96) or under a
|
|
11535
|
+
// user `window-size manual`/`smallest` silently refuses
|
|
11536
|
+
// `refresh-client -C`, so the pane freezes at whatever width the
|
|
11537
|
+
// first viewer set and no later device can widen it. `-w -q` so
|
|
11538
|
+
// an older tmux without the option does not fail the open.
|
|
11539
|
+
["set-option", "-w", "-q", "-t", `=${tmuxSession}:`, "window-size", "latest"],
|
|
11485
11540
|
["set-option", "-g", "set-clipboard", "on"],
|
|
11486
11541
|
["set-option", "-ga", "terminal-features", ",xterm-256color:clipboard"]
|
|
11487
11542
|
];
|
|
@@ -11877,13 +11932,12 @@ class WebTerminalManager {
|
|
|
11877
11932
|
this.applyResize(session, cols, rows);
|
|
11878
11933
|
}
|
|
11879
11934
|
/** Resize the transport AND the authoritative headless screen together, so a
|
|
11880
|
-
* later snapshot matches the real geometry.
|
|
11881
|
-
*
|
|
11882
|
-
*
|
|
11883
|
-
*
|
|
11884
|
-
*
|
|
11885
|
-
*
|
|
11886
|
-
* squeezing the desktop" — not implemented in this batch). */
|
|
11935
|
+
* later snapshot matches the real geometry. One tmux window = one size:
|
|
11936
|
+
* we take the LAST resize we are handed (B-287: the web only sends one on a
|
|
11937
|
+
* human-activity edge, so "last resize" == "most recently used viewer" ==
|
|
11938
|
+
* tmux's own `window-size latest` semantics). The control-client
|
|
11939
|
+
* declaration IS `refresh-client -C`, preceded by a `window-size latest`
|
|
11940
|
+
* re-assert so a manual/smallest session cannot swallow it. */
|
|
11887
11941
|
applyResize(session, cols, rows) {
|
|
11888
11942
|
const c = Math.max(2, Math.floor(cols));
|
|
11889
11943
|
const r = Math.max(2, Math.floor(rows));
|
|
@@ -11894,7 +11948,10 @@ class WebTerminalManager {
|
|
|
11894
11948
|
logger.debug(`[WEB TERMINAL] resize ${session.id} failed: ${e}`);
|
|
11895
11949
|
}
|
|
11896
11950
|
} else if (c !== session.cols || r !== session.rows) {
|
|
11897
|
-
session.transport.client.sendFireAndForget([
|
|
11951
|
+
session.transport.client.sendFireAndForget([
|
|
11952
|
+
`set-option -w -q -t =${session.tmuxSession}: window-size latest`,
|
|
11953
|
+
`refresh-client -C ${c}x${r}`
|
|
11954
|
+
]);
|
|
11898
11955
|
}
|
|
11899
11956
|
session.resizeHeadless(c, r);
|
|
11900
11957
|
}
|
|
@@ -11937,7 +11994,10 @@ class WebTerminalManager {
|
|
|
11937
11994
|
* session (so a local `tmux attach` won't find it either). Used when the
|
|
11938
11995
|
* user deletes the terminal from the sidebar. */
|
|
11939
11996
|
killSession(terminalId, opts) {
|
|
11940
|
-
const info = this.lastSeenInfo.get(terminalId) ??
|
|
11997
|
+
const info = this.lastSeenInfo.get(terminalId) ?? (() => {
|
|
11998
|
+
const t = this.listSessions().find((x) => x.id === terminalId);
|
|
11999
|
+
return t ? seenInfoOf(t) : void 0;
|
|
12000
|
+
})();
|
|
11941
12001
|
const target = `=vh-${terminalId}:`;
|
|
11942
12002
|
const killed = spawnSync("tmux", tmuxArgs(["kill-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
|
|
11943
12003
|
const verified = spawnSync("tmux", tmuxArgs(["has-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
|
|
@@ -11963,6 +12023,7 @@ class WebTerminalManager {
|
|
|
11963
12023
|
tags: info.tags,
|
|
11964
12024
|
manual: info.manual,
|
|
11965
12025
|
...info.attachTmux ? { attachTmux: info.attachTmux } : {},
|
|
12026
|
+
...seenGeometry(info.cols, info.rows),
|
|
11966
12027
|
mirrorSessionId: this.mirrorResolver?.(terminalId) ?? mirror?.sessionId,
|
|
11967
12028
|
claudeSessionId: mirror?.claudeSessionId,
|
|
11968
12029
|
reason: "closed",
|
|
@@ -12052,7 +12113,8 @@ class WebTerminalManager {
|
|
|
12052
12113
|
// the mirror reconciler's stricter "claude is really here" gate.
|
|
12053
12114
|
claudeConfident: probe.claudeConfident,
|
|
12054
12115
|
manual: s.manual,
|
|
12055
|
-
...s.attachTmux ? { attachTmux: s.attachTmux } : {}
|
|
12116
|
+
...s.attachTmux ? { attachTmux: s.attachTmux } : {},
|
|
12117
|
+
...s.paneCols !== void 0 && s.paneRows !== void 0 ? { paneCols: s.paneCols, paneRows: s.paneRows } : {}
|
|
12056
12118
|
});
|
|
12057
12119
|
}
|
|
12058
12120
|
return out;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "very-happy-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.101",
|
|
4
4
|
"description": "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.",
|
|
5
5
|
"author": "Kirill Dubovitskiy",
|
|
6
6
|
"contributors": [
|
|
@@ -113,12 +113,12 @@
|
|
|
113
113
|
"zod": "^4.0.0"
|
|
114
114
|
},
|
|
115
115
|
"optionalDependencies": {
|
|
116
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
117
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
118
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
119
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
120
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
121
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
116
|
+
"very-happy-tools-arm64-darwin": "0.2.101",
|
|
117
|
+
"very-happy-tools-x64-darwin": "0.2.101",
|
|
118
|
+
"very-happy-tools-arm64-linux": "0.2.101",
|
|
119
|
+
"very-happy-tools-x64-linux": "0.2.101",
|
|
120
|
+
"very-happy-tools-arm64-win32": "0.2.101",
|
|
121
|
+
"very-happy-tools-x64-win32": "0.2.101"
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
124
|
"@slopus/happy-wire": "workspace:*",
|