Skip to content

Commit 5c99886

Browse files
claudesinelaw
authored andcommitted
Give the custom agent somewhere to type when running in the current workspace
The agent list ends in "custom...", whose whole point is an arbitrary command -- but on a local new workspace that command box lives under the Advanced fold, and both the fold and the inline fallback were gated on "creating". Running in the current workspace therefore rendered no command box at all: picking "custom..." left the form claiming an agent the user had no way to name, and since the preset hands focus to a `cmd` field that wasn't in the focus cycle, focus fell back to the top of the form, where the next arrow key silently flipped "Launch in" over to "New workspace". The box is now inline wherever there is no fold to hold it -- remote backends, and the current-workspace shape -- and `cmd` joins that shape's focus cycle. `submitForm` already read `form.cmd.value` on this path, so nothing downstream changes. Also adds a multi-agent interactive tmux script. Three claude panes go into one workspace holding 2 / 3 / 5 turns, one left running, one quit cleanly, one crashed; after an editor restart each has to come back with its own transcript, its own tab name, and its own session id. The turn counts are the assertion that matters -- they are per-session state on disk, so a resume reporting the wrong number proves the panes were cross-wired even when the screen looks perfectly restored.
1 parent d87d02e commit 5c99886

4 files changed

Lines changed: 408 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Keyboard and mouse input is now parsed by our own `fresh-input-parser` crate ins
2222
* A terminal's exit is **no longer written into the output** as a `[Terminal process exited]` line, which cost a row and scrolled the top of the final screen out of view — often the first, most important line of an agent's last answer. The tab reads `claude (exited)` instead, and the dead terminal is left exactly on its last frame.
2323
* **Terminal tab names now survive an editor restart** — a restored agent tab stays named `claude` instead of falling back to `bash` / `*Terminal 1*`.
2424
* **Run Agent… and New Workspace are now one dialog.** A "Launch in" switch at the top chooses the current workspace or a new one; picking a new one reveals the backend tabs, Project Path, Workspace Name and the Advanced fold, and picking the current one hides them, leaving just the agent controls. Both palette commands open the same form — they only differ in where the switch starts.
25+
* Fixes the **`custom…` agent when running in the current workspace**, which had nowhere to type a command: the Agent Command box lives under the workspace-shaped Advanced fold, which that shape doesn't have, so picking `custom…` left the form claiming an agent you couldn't name — and dropped focus back to the top, where the next arrow key silently switched **Launch in** to *New workspace*. The box is now inline whenever there's no fold to hold it.
2526
* Fixes **Run Agent… → current workspace**, which recorded neither the launch nor the resume argv: that agent used to vanish from the saved workspace entirely, and restarting it produced a bare shell instead of the agent. It now behaves exactly like an agent started in its own new workspace.
2627
* **Classic Mac (CR) line endings** are now fully supported (#2736, requested by @720720).
2728
* **`.editorconfig` support** - `indent_style`/`indent_size`/`tab_width` are picked up automatically (#959, requested by @nyurik).

crates/fresh-editor/plugins/orchestrator.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6003,7 +6003,12 @@ function rebuildFormFocusCycle(): void {
60036003
// non-null narrowing across every call below.
60046004
const f = form;
60056005
if (f.target === "current") {
6006-
cycle.push("agent_dropdown");
6006+
// `cmd` renders inline here (no Advanced fold in this shape), so it is a
6007+
// Tab stop right after the selector that fills it — and, crucially, a
6008+
// *reachable* focus target for the "custom…" preset, which hands focus to
6009+
// it. Without the entry the setFocusKey was dropped and focus fell back to
6010+
// the top of the form, so the next ←/→ silently flipped "Launch in".
6011+
cycle.push("agent_dropdown", "cmd");
60076012
const agent = activeAgentEntry();
60086013
if (agent?.auto) cycle.push("auto_mode");
60096014
if (agent?.prompt) cycle.push("start_prompt");
@@ -7461,9 +7466,15 @@ function buildFormSpec(): WidgetSpec {
74617466
}
74627467
children.push(
74637468
agentPresetRow(),
7464-
// On remote backends the command box stays inline (no Advanced fold to hold
7465-
// it); on local it moves into Advanced (appended in `advancedSection`).
7466-
...(creating && form.backend !== "local" ? [cmdField()] : []),
7469+
// The command box stays inline wherever there's no Advanced fold to hold
7470+
// it: on remote backends, and whenever we're running in the current
7471+
// workspace (the fold is workspace-shaped, so it's gone entirely). Only a
7472+
// local *new workspace* moves it into Advanced (via `advancedSection`).
7473+
//
7474+
// It has to be reachable in current-workspace mode: the agent list ends in
7475+
// "custom…", and picking it with nowhere to type left the form claiming an
7476+
// agent the user could not actually name.
7477+
...(!creating || form.backend !== "local" ? [cmdField()] : []),
74677478
// Agent-specific controls (Auto mode / Start prompt), adaptive to the
74687479
// resolved agent. Empty for a bare terminal / unknown command.
74697480
...agentOptionsFields(),

crates/fresh-editor/tests/e2e/plugins/orchestrator_new_dialog.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,3 +1634,77 @@ fn run_agent_and_new_workspace_are_one_dialog() {
16341634
harness.assert_screen_contains("ORCHESTRATOR :: New Workspace");
16351635
harness.assert_screen_contains("Run in:");
16361636
}
1637+
1638+
/// The agent list ends in "custom…", whose whole purpose is to let the user
1639+
/// type an arbitrary command — so the Agent Command field has to be present,
1640+
/// and focusable, in the current-workspace shape too.
1641+
///
1642+
/// It wasn't. On a local *new workspace* that field lives under the Advanced
1643+
/// fold, and both the fold and the inline fallback were gated on "creating",
1644+
/// so running in the current workspace rendered no command box at all. Picking
1645+
/// "custom…" then left the form claiming an agent the user had no way to name,
1646+
/// and — because the preset hands focus to a `cmd` field that wasn't in the
1647+
/// focus cycle — dropped focus back to the top of the form, where the next
1648+
/// arrow key silently flipped "Launch in" to "New workspace".
1649+
#[test]
1650+
fn custom_agent_is_typable_when_running_in_the_current_workspace() {
1651+
let (_temp, workspace) = set_up_workspace();
1652+
let mut harness = open_form_on(&workspace);
1653+
1654+
// Flip "Launch in" to the current workspace (as `Run Agent…` opens it).
1655+
harness
1656+
.send_key(KeyCode::BackTab, KeyModifiers::NONE)
1657+
.unwrap();
1658+
harness.tick_and_render().unwrap();
1659+
harness
1660+
.send_key(KeyCode::BackTab, KeyModifiers::NONE)
1661+
.unwrap();
1662+
harness.tick_and_render().unwrap();
1663+
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
1664+
harness
1665+
.wait_until(|h| h.screen_to_string().contains("ORCHESTRATOR :: Run Agent"))
1666+
.unwrap();
1667+
1668+
// The command box is here even though the workspace-shaped Advanced fold
1669+
// that normally holds it is not.
1670+
harness.assert_screen_contains("Agent Command");
1671+
harness.assert_screen_not_contains("Advanced");
1672+
1673+
// Walk to the agent selector and step left, which wraps the list around to
1674+
// "custom…" — the shortest route, and the one that used to strand focus.
1675+
let mut guard = 0;
1676+
while !focused_line(&harness.screen_to_string()).contains("Agent:") {
1677+
harness.send_key(KeyCode::Tab, KeyModifiers::NONE).unwrap();
1678+
harness.tick_and_render().unwrap();
1679+
guard += 1;
1680+
assert!(
1681+
guard < 20,
1682+
"Tab never reached the agent selector. Screen:\n{}",
1683+
harness.screen_to_string(),
1684+
);
1685+
}
1686+
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
1687+
harness
1688+
.wait_until(|h| h.screen_to_string().contains("custom"))
1689+
.unwrap();
1690+
1691+
// Focus followed the preset onto the command field, so the user can just
1692+
// type — and, critically, is not sitting on the "Launch in" switch.
1693+
let focused = focused_line(&harness.screen_to_string());
1694+
assert!(
1695+
!focused.contains("Launch in:"),
1696+
"picking 'custom…' must not drop focus onto the Launch-in switch — the \
1697+
next arrow key would change the workspace target. Screen:\n{}",
1698+
harness.screen_to_string(),
1699+
);
1700+
1701+
// And what gets typed lands in the command box rather than nowhere.
1702+
for ch in "zzcustomcmd".chars() {
1703+
harness
1704+
.send_key(KeyCode::Char(ch), KeyModifiers::NONE)
1705+
.unwrap();
1706+
}
1707+
harness
1708+
.wait_until(|h| h.screen_to_string().contains("zzcustomcmd"))
1709+
.unwrap();
1710+
}

0 commit comments

Comments
 (0)