fix: v2.6.1 — panes start in their folder again after an update (#69), Changes panel stacks every repo, clickable paths in chat

- #69: tmux applies a pane's `-c` only while its server's own cwd exists.
  The server was forked from the dashboard's cwd (the plugin cache dir),
  outlives updates, and the old cache dir is pruned — after that every new
  pane started in a deleted directory, where `claude` died at once ("run not
  found") and a shell printed getcwd errors. tmux calls now run from `/`,
  and the pane's sh wrapper cds into its folder itself, which also heals a
  tmux server that is already stuck.
- Changes panel: a workspace's extra folders are sections stacked under the
  workspace folder (VS Code Source Control style), with Close repository /
  Close other repositories on a header's right-click.
- Chat: URLs and absolute paths are links; Claude Code's `[Image: source:]`
  line folds into its turn; uploads under `<data dir>/drops/` are listed.
- Update check: a checkout holding commits a newer release lacks reports
  `diverged_from_release` instead of offering a fast-forward that can only fail.
This commit is contained in:
2026-10-09 17:24:14 +07:00
parent fa156d6a59
commit 96e4d28f7c
25 changed files with 589 additions and 208 deletions
+21 -12
View File
@@ -81,20 +81,29 @@ const WAIT_FOR_CLIENT =
const CLIENT_WAIT_MS = 2000;
/**
* A pane's tmux command: `argv` behind {@link WAIT_FOR_CLIENT}, or — with no
* `argv` — the login shell behind it. Windows has no `sh` (a WinGet tmux runs
* native programs), and an `sh -c` there died at once and took the session
* with it, so it gets the command itself, or nothing for tmux's own default
* shell.
* `sh` that moves into `$1` itself. tmux applies `-c` only while its server's
* own cwd still exists: the server is forked from the dashboard's cwd — a
* plugin cache dir, deleted by `pruneRetiredPluginRoots` after an update the
* server outlives — and from then on every pane started in that deleted dir,
* where `claude` dies at once and a shell prints `getcwd` errors (issue #69).
*/
const IN_CWD = 'cd -- "$1" 2>/dev/null; shift;';
/**
* A pane's tmux command, run in `cwd`: `argv` behind {@link WAIT_FOR_CLIENT},
* or — with no `argv` — the login shell behind it. Windows has no `sh` (a
* WinGet tmux runs native programs), and an `sh -c` there died at once and
* took the session with it, so it gets the command itself, or nothing for
* tmux's own default shell.
* ponytail: no wait for the attach on Windows, so a pane may first draw at
* 80×24 (issue #55); port the wait to PowerShell if that shows.
*/
function paneArgv(argv, platform = process.platform) {
function paneArgv(argv, cwd, platform = process.platform) {
if (platform === "win32") return argv || [];
// argv rides as sh's positional parameters, never inside the script text.
// cwd and argv ride as sh's positional parameters, never inside the script text.
return argv
? ["sh", "-c", `${WAIT_FOR_CLIENT} exec "$@"`, "sh", ...argv]
: ["sh", "-c", `${WAIT_FOR_CLIENT} exec "$SHELL" -l`];
? ["sh", "-c", `${IN_CWD} ${WAIT_FOR_CLIENT} exec "$@"`, "sh", cwd, ...argv]
: ["sh", "-c", `${IN_CWD} ${WAIT_FOR_CLIENT} exec "$SHELL" -l`, "sh", cwd];
}
const CODEX_SANDBOXES = new Set(["read-only", "workspace-write", "danger-full-access"]);
const CODEX_APPROVALS = new Set(["on-request", "never"]);
@@ -614,7 +623,7 @@ function spawnRun(args) {
if (!tmux.hasSession(id)) {
codexPool?.place();
tmux.newSession({ name: id, cwd, argv: paneArgv(paneCmd), env });
tmux.newSession({ name: id, cwd, argv: paneArgv(paneCmd, cwd), env });
record();
schedulePaste();
} else if (SHELL_COMMANDS.has(tmux.paneCommand(id) || "")) {
@@ -625,7 +634,7 @@ function spawnRun(args) {
// still answered 200. Run the argv in the pane the user already sees
// instead of erroring or opening a second session.
codexPool?.place();
tmux.sendCommand(id, paneCmd, env, cwd);
tmux.sendCommand(id, paneArgv(paneCmd, cwd), env, cwd);
record();
schedulePaste();
}
@@ -677,7 +686,7 @@ function spawnShell({ laneId, slot = 0, cwd }) {
const paneEnv = member ? require("./run-as").memberPaneEnv(member, env) : env;
// Started once the pane has its real size (WAIT_FOR_CLIENT, issue #55), as
// the login shell tmux would have run (tmux sets SHELL in the pane).
tmux.newSession({ name: id, cwd, argv: paneArgv(null), env: paneEnv });
tmux.newSession({ name: id, cwd, argv: paneArgv(null, cwd), env: paneEnv });
dashboardRuns?.recordRun({
id,
sessionId: null,