f77049c238
Terminal (issue #33): - `convertEol` is gone from xterm. It rewrote every bare LF as CR+LF, so a tmux pane that scrolled (tmux emits a bare LF and keeps drawing at the column it was on) had the rest of that draw land at column 0, stamping a line's first characters into the left margin of the rows below — the torn left edge that only showed up once content scrolled. - The attach carries the client's real size (`?cols=&rows=`, `sizeFromQuery` in `server/websocket.js`, bounded because it reaches a TIOCSWINSZ ioctl). `attach()` spawned `tmux attach-session` at a hardcoded 80x24 on every attach, and tmux sizes the window to the newest client, so `claude` redrew its whole UI at 80 columns until the first `resize` message landed. - The terminal takes the keyboard on mount, so typing reaches tmux without clicking the pane first. - A pane that ends (double Ctrl+C, `exit`, a crash) reports itself through a new `onExit` prop and `LaneConsolePane` runs `newRun()`, so the console returns to the setup form instead of leaving a dead pane behind a "New run" button. Pane environment: - `server/lib/desktop-env.js` forwards the graphical session's DISPLAY / WAYLAND_DISPLAY / XAUTHORITY / XDG_RUNTIME_DIR (from `systemctl --user show-environment`, this process's own values winning) into the pane. The server normally runs as a systemd user service with none of them, so `claude` there could not read the clipboard and answered "no image found in clipboard" on every image paste. Run setup: - `auto` is a permission mode and is now the default everywhere — allowlists, the `--permission-mode` fallback, the select, and the pane's initial state. - The model picker sends the CLI's aliases (`fable`/`opus`/`sonnet`/`haiku`) instead of pinned ids. `claude --model` resolves an alias to the latest model itself, so a release like Fable 5.1 needs no edit here; pricing is unaffected because it keys off the resolved id the transcript reports. - **Create/update readme file** moved from the Add-lane modal into the lane's Tools menu: creating a lane and writing its docs are separate decisions, and the second is worth re-running long after the first. - The Workspace's no-lane state is a real banner; the lane strip (add tile included) renders only once a lane exists. Refs #32, #33. Tests: npm run test:server (1828 pass), client vitest (558 pass).
68 lines
2.3 KiB
JavaScript
68 lines
2.3 KiB
JavaScript
/**
|
|
* @file desktop-env.js
|
|
* @description The graphical session's own environment variables, for panes
|
|
* the dashboard spawns. The server usually runs as a systemd *user* service,
|
|
* which has no `DISPLAY`/`WAYLAND_DISPLAY` of its own — so neither does any
|
|
* tmux pane it starts, and a `claude` running in one cannot reach the X or
|
|
* Wayland clipboard: pasting an image answers "no image found in clipboard"
|
|
* while the same CLI in a normal terminal pastes fine. `systemctl --user
|
|
* show-environment` is the canonical local source for those values (the
|
|
* desktop session imports them there at login), with this process's own
|
|
* environment preferred when it already has them — a checkout started from a
|
|
* terminal inherits the real ones.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const { execFileSync } = require("node:child_process");
|
|
|
|
/** The only names forwarded. A pane gets a clipboard, not the whole session. */
|
|
const FORWARDED = ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR"];
|
|
|
|
let execImpl = (args) =>
|
|
execFileSync("systemctl", args, {
|
|
encoding: "utf8",
|
|
timeout: 2000,
|
|
stdio: ["ignore", "pipe", "ignore"],
|
|
});
|
|
|
|
/** Test seam — same pattern as tmux.js. */
|
|
function __setExecImpl(fn) {
|
|
execImpl = fn;
|
|
}
|
|
|
|
/** `KEY=value` lines from `systemctl --user show-environment`, as a map. */
|
|
function systemdUserEnv() {
|
|
let out;
|
|
try {
|
|
out = execImpl(["--user", "show-environment"]);
|
|
} catch {
|
|
// No systemd user manager (macOS, a container, a plain login shell) — the
|
|
// process environment below is the only source then.
|
|
return {};
|
|
}
|
|
const env = {};
|
|
for (const line of String(out).split("\n")) {
|
|
const eq = line.indexOf("=");
|
|
if (eq > 0) env[line.slice(0, eq)] = line.slice(eq + 1);
|
|
}
|
|
return env;
|
|
}
|
|
|
|
/**
|
|
* The forwarded variables that actually have a usable value right now.
|
|
* A value carrying a newline is dropped rather than passed on: these end up in
|
|
* a `tmux -e NAME=value` argv, and a line break there is not a value tmux can
|
|
* represent.
|
|
*/
|
|
function desktopEnv() {
|
|
const fromSystemd = systemdUserEnv();
|
|
const out = {};
|
|
for (const name of FORWARDED) {
|
|
const value = process.env[name] || fromSystemd[name];
|
|
if (typeof value === "string" && value && !/[\r\n\0]/.test(value)) out[name] = value;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
module.exports = { desktopEnv, FORWARDED, __setExecImpl };
|