MCP polish + SSH host manager Connect button
Three small things bundled from PR-3 verification:
1. Split SSH out of mcp.spawn_pane schema. New McpSpawnSpec enum
(Wsl | Powershell only) used for SpawnPaneArgs, so Claude's
spawn_pane tool description and JSON schema show only the local
shells. SSH must go through connect_host. The internal
pty::SpawnSpec is unchanged — the frontend's manual spawn path
via XtermPane still supports all three variants. Previously
spawn_pane(kind=ssh) was a half-broken path that required `host`
as a separate mandatory field even when hostId was given;
serde-rejected the natural "spawn to a saved host" call shape.
2. Refresh the MCP server's `with_instructions` text and the
module-level header comment. Both still claimed "read-only v1"
long after the v2 write surface landed, which was making Claude
refuse to attempt tools on first contact ("the server has
flagged itself as read-only..."). The instructions now describe
the actual tool set, the SSH-via-connect_host convention, and
the policy/safeguards gates so Claude doesn't have to infer.
3. Add a "Connect" button to the SSH hosts manager. Previously
the dialog only had Edit — users (rightly) expected clicking a
saved host to spawn an SSH pane to it. New onConnect callback
does the splitLeaf + smart-orient dance and closes the manager.
Buttons wrapped in a flex container so the row's
space-between layout doesn't strand the new button mid-row.
This commit is contained in:
parent
bf2810a433
commit
6da7523993
4 changed files with 144 additions and 52 deletions
53
src/App.tsx
53
src/App.tsx
|
|
@ -308,6 +308,42 @@ export default function App() {
|
|||
);
|
||||
}, [activeLeafId, defaultShell, notify]);
|
||||
|
||||
// From the SSH host manager's "Connect" button — splits off the active
|
||||
// pane and opens an SSH session to the picked host. Same smart-orient as
|
||||
// the titlebar "+" path.
|
||||
const connectToHost = useCallback(
|
||||
(hostId: string) => {
|
||||
const container = paneWrapRef.current;
|
||||
const layout = flattenLayout(treeRef.current);
|
||||
const targetId = activeLeafId ?? layout.leaves[0]?.leaf.id ?? null;
|
||||
if (!targetId || !container) {
|
||||
notify("No pane to split off — open a pane first");
|
||||
return;
|
||||
}
|
||||
const slot = layout.leaves.find((s) => s.leaf.id === targetId);
|
||||
if (!slot) return;
|
||||
const rect = container.getBoundingClientRect();
|
||||
const paneW = slot.box.width * rect.width;
|
||||
const paneH = slot.box.height * rect.height;
|
||||
const orientation: Orientation = paneW >= paneH ? "h" : "v";
|
||||
const childW = orientation === "h" ? paneW / 2 : paneW;
|
||||
const childH = orientation === "v" ? paneH / 2 : paneH;
|
||||
if (childW < MIN_PANE_PX || childH < MIN_PANE_PX) {
|
||||
notify(
|
||||
`Pane too small to split — would create ${Math.round(childW)}×${Math.round(childH)}px (min ${MIN_PANE_PX}px)`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
setTree((t) =>
|
||||
splitLeaf(t, targetId, orientation, {
|
||||
shellKind: "ssh",
|
||||
sshHostId: hostId,
|
||||
}),
|
||||
);
|
||||
},
|
||||
[activeLeafId, notify],
|
||||
);
|
||||
|
||||
const close = useCallback(
|
||||
(leafId: NodeId) => {
|
||||
const paneId = paneIdByLeafRef.current.get(leafId);
|
||||
|
|
@ -990,6 +1026,10 @@ export default function App() {
|
|||
};
|
||||
}
|
||||
case "spawn_pane": {
|
||||
// Backend McpSpawnSpec only contains Wsl / Powershell — SSH is
|
||||
// routed through connect_host instead. The cast tolerates the
|
||||
// wider frontend SpawnSpec union; the kind discriminant is what
|
||||
// matters at runtime.
|
||||
const a = args as {
|
||||
spec?: SpawnSpec;
|
||||
parentLeafId?: string;
|
||||
|
|
@ -998,15 +1038,6 @@ export default function App() {
|
|||
if (!a.spec || typeof a.spec !== "object") {
|
||||
throw new Error("missing spec");
|
||||
}
|
||||
// For SSH, our LeafNode only persists sshHostId — we don't store
|
||||
// inline host/user/port on the leaf. Refuse ad-hoc SSH spawns;
|
||||
// use connect_host with a saved host_id instead.
|
||||
if (a.spec.kind === "ssh" && !a.spec.hostId) {
|
||||
throw new Error(
|
||||
"spawn_pane with kind=ssh requires hostId (a saved host). " +
|
||||
"Use connect_host(host_id) or add the host via the SSH host manager first.",
|
||||
);
|
||||
}
|
||||
const newLeafId = await spawnNewLeafFromSpec(
|
||||
a.spec,
|
||||
a.parentLeafId,
|
||||
|
|
@ -1593,6 +1624,10 @@ export default function App() {
|
|||
onSave={saveHosts}
|
||||
onSavePassword={savePassword}
|
||||
onClearPassword={clearPassword}
|
||||
onConnect={(hostId) => {
|
||||
connectToHost(hostId);
|
||||
closeHostManager();
|
||||
}}
|
||||
onClose={closeHostManager}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue