Commit Graph
5 Commits
Author SHA1 Message Date
Anx 83f7246aa3 fix(env): translate PEBREL_HOOK_EXE across the WSL boundary (#290)
Forward the current and legacy hook executable variables through WSLENV with /p path translation. Document the boundary contract and cover the primary hook variable in the existing environment test.
2026-09-26 13:49:36 +08:00
AnxForever 25cac197d5 fix(gpui): stop screen silence from toasting "turn complete"
The screen watchdog downgrades `Working -> Done` after the pane's screen has
been static for 5 ticks (it samples at 1 Hz), and that transition used to
notify unconditionally. But a static screen is not evidence that the agent
stopped: Claude Code prints nothing while it thinks, and a long command such
as a full build writes nothing for minutes. The result was a "回合完成" toast
while the task was still running.

The screen is only a fallback for when the hook never arrives. When hooks are
live, the agent CLI's own `TurnDone` is the authoritative terminal state, so
keep the screen-derived transition (the sidebar dot still updates) but stay
silent about it. Clients without hooks keep the old behaviour, where the
screen really is the only evidence.

This is the notification half of the problem. The other half is that
`active_background_tasks` never fires for Claude Code — its `Stop` payload
carries no `background_tasks` field — so the guard at the top of the
`TurnDone` arm is dead code for that CLI. Left as-is for now.
2026-09-17 12:19:36 +08:00
AnxForever 70b90f1880 feat(gpui): open a folder in a chosen shell from the Explorer menu
Right-clicking a folder has always opened the configured default shell —
usually PowerShell — with no way to ask for a WSL distribution instead.

- `--shell <id>` takes the same ids the `shell` setting uses (`pwsh`, `cmd`,
  `wsl:Ubuntu`, or an imported profile's settings id) and applies to the
  first tab this process creates. It carries an id rather than a path so
  every launch re-resolves the executable.
- The id travels both startup paths: the cold start (`run_shell` →
  `WorkspaceStartup::NewTerminal`), and the resident hand-off, which is the
  common case on Windows. `tab.new`/`window.create` gained an optional
  `shell` field, so clicking the menu while Pebrel is already running adds a
  tab to the existing window instead of starting a second process.
- An explicit request never silently degrades. An unresolvable id fails
  (`invalid_shell`, or a startup error before any window opens), and a
  `wsl:<distro>` id that the registry no longer lists still launches
  `wsl.exe -d <distro>`, leaving the complaint to the distribution itself.
  The `shell` setting keeps its documented fallback to the platform default.
- The winit shell rejects a shell request instead of ignoring it, so the
  caller falls back to a cold GPUI start and still gets the requested shell.

The resolution lives in a new `workspace::shell_launch` module: `main.rs` has
to validate `--shell` before it opens a window, and none of it needs
`NebulaWorkspace` state — `workspace.rs` is also at its line budget, which
this keeps from growing (`architecture/file-budgets.txt`).

The installer registers one context-menu entry per registered WSL
distribution (`PebrelWsl<n>`, skipping `docker-desktop*`) and the uninstaller
reclaims them by key prefix plus the command line it wrote. `--shell` is
passed before `--working-directory`: a drive-root `%V` ends in `\"`, which
would swallow the rest of the line if the directory came first. Both the
string and the order are pinned by installer assertions.

Docs (runtime API schema + control API) and shell completions are updated.
2026-09-14 23:07:38 +08:00
AnxForever be5fd1eb67 fix(windows): stop console children from popping terminal windows
Pebrel is a `windows_subsystem = "windows"` process, so it has no console to
hand to a child, and Windows allocates a new one for every console program we
spawn. When the default terminal application is Windows Terminal that is not
a hidden flash — it is a whole window on screen. Measured on the test binary,
which has no console for the same reason: one full `cargo test` run popped 86
windows, and the `runtime_api` module alone popped 7.

Most spawn sites already carried `CREATE_NO_WINDOW`, but each spelled it
itself — `0x0800_0000` in some places, the named constant in others, a
private `hidden_command` in one module. Three sites had no flag at all:
`pane.exec` (`runtime_exec::configure_process_group` was a no-op on Windows),
the WSL path of drag-and-drop (`path_drop::wsl_paths`), and the `ssh -G`
probe behind the `pebrel ssh` entry point.

Route every console child through one helper, `platform::process`:

- `hidden_command` for the common case;
- `hidden_command_with` when the caller needs extra creation flags (the
  daemon's `CREATE_NEW_PROCESS_GROUP`) — `creation_flags` replaces rather
  than ORs, so the helper owns the `CREATE_NO_WINDOW` bit;
- a `CREATE_NO_WINDOW` constant for `Command` types that are not
  `std::process::Command` (the tokio proxy command). No raw literals left.

Test helpers are included: the test binary is console-less too, so its git
calls popped windows as well (7 during one `runtime_api` run, 0 after).

Deliberately left alone, and now documented as such: the interactive
`pebrel ssh <host>` session (it inherits the parent console through
`AttachConsole`), the update installer wizard, and `notepad`/`explorer`/
`open`, which exist to be visible.
2026-09-14 22:57:27 +08:00
AnxForever 2b4eadcdee fix(ai-hook): keep the turn-done notification off background work
`Stop` fires at the end of every turn, not at task completion, so the guard
that suppresses the completion toast while work is still in flight is what
keeps that notification honest. It reads `background_tasks`, but the counter
only accepted entries whose type was `subagent` — and Claude Code reports a
backgrounded Bash command as `local_bash`. The most common shape (start a
long command, turn ends, command still running) therefore counted zero: the
guard stayed off and the toast claimed the turn was done while the command
was still running.

Count every entry the field carries. It is documented as in-flight work
("Empty array when nothing is in flight"), so the filter belongs on the
status, not on the type. Idle `in_process_teammate` entries stay excluded —
they keep reporting `status: "running"` after they finish
(anthropics/claude-code#85955) — matching Claude Code's own
`status === "running" && !isIdle` predicate.

Tests: three cases pin the counting (every in-flight type, an empty array
leaving the turn done, an idle teammate not holding it open). The first one
counted zero before this change.
2026-09-14 22:54:29 +08:00