feat(workspace): split menus start AI tools; drop the Tools menu

A pane's split right/down menus offered only New terminal. They now list
the same AI tools as the tab bar's + menu (newTabTools/newTabBody,
shared from ConsoleTabs), started bare in the first free slot on that
side of the pane.

The tab bar's Tools menu is gone: its readme run hit 409 ERUNLIVE
whenever slot 0 was live (always, in the session view) and depended on
a personal skill; MCP sync and the dev-sync check served the dormant
ship-feature-lane pipeline. The sidebar row menu's readme item goes too.
The routes stay, reached by `agenthub lanes mcp sync` and
`agenthub lanes sync-base`.

Also: the home's Memory header and file tabs no longer shrink (a long
file squeezed the tab row and clipped it).
This commit is contained in:
2026-10-05 10:55:10 +07:00
parent 151dc8a480
commit 138e706dd1
15 changed files with 126 additions and 353 deletions
+20 -15
View File
@@ -61,7 +61,7 @@ import type { Lane, LaneFeature, ProofFeature } from "../lib/types";
import { keepIfSame } from "../lib/keepIfSame";
import { useLanes } from "../hooks/useLanes";
import { LaneConsolePane } from "../components/run/LaneConsolePane";
import { ConsoleTabs } from "../components/run/ConsoleTabs";
import { ConsoleTabs, newTabBody, newTabTools } from "../components/run/ConsoleTabs";
import { type LayoutUpdate, PaneGrid, SplitMenus, usePaneLayout } from "../components/run/PaneGrid";
import {
type Edge,
@@ -77,7 +77,7 @@ import { useAiTools } from "../hooks/useAiTools";
import { freeSlot } from "../components/lanes/WorkspaceHome";
import { LIVE_RUNS_EVENT } from "../components/WorkspaceTree";
import PipelineMap from "../components/lanes/PipelineMap";
import LaneCard, { since, LaneToolsMenu } from "../components/lanes/LaneCard";
import LaneCard, { since } from "../components/lanes/LaneCard";
import { AddLaneModal } from "../components/lanes/AddLaneModal";
import { LaneChangesPanel } from "../components/lanes/LaneChangesPanel";
import { WorkspaceHome } from "../components/lanes/WorkspaceHome";
@@ -285,16 +285,19 @@ function ConsoleArea({
else onNewSession?.();
};
// A split menu's "New terminal": a shell in the first free slot, placed on
// that side of the pane — unless the workspace changed while it started.
// A split menu's "New terminal" or AI tool (bare, as the tab bar's `+`
// starts it) in the first free slot, placed on that side of the pane —
// unless the workspace changed while it started.
const free = lane ? freeSlot(lane.id, tabs) : null;
const newSplit = async (target: number, edge: Edge) => {
const splitTools = lane ? newTabTools(lane, aiTools) : [];
const newSplit = async (target: number, edge: Edge, tool?: string) => {
if (!lane || free === null) return;
const laneId = lane.id;
const slot = free;
setSplitError(null);
try {
const r = await api.lanes.action(laneId, "start", { slot, shell: true });
const body = tool ? newTabBody(tool, lane, aiTools) : { shell: true };
const r = await api.lanes.action(laneId, "start", { slot, ...body });
onRunsChanged();
if (!r.run_id || laneRef.current !== laneId) return;
pending.current.set(slot, activeRuns);
@@ -327,11 +330,10 @@ function ConsoleArea({
}}
onClosed={(runId) => ended(slotOf(runId))}
onHome={onNewSession}
// Per-workspace actions (mcp sync, sync-base check, readme), and
// while one pane is shown its split menus (a split pane has its own).
// While one pane is shown, its split menus (a split pane has its own).
actions={
<div className="flex items-center gap-1">
{shown.length === 1 && shown[0] !== undefined && (
shown.length === 1 && shown[0] !== undefined ? (
<div className="flex items-center gap-1">
<SplitMenus
slot={shown[0]}
shown={shown}
@@ -340,11 +342,12 @@ function ConsoleArea({
// A layout never saved is the one-pane `view`: split that.
onChange={(fn) => change((l) => fn(l.root ? l : view))}
canStart={free !== null}
onNewSplit={(target, edge) => void newSplit(target, edge)}
tools={splitTools}
toolLabel={aiTools.label}
onNewSplit={(target, edge, tool) => void newSplit(target, edge, tool)}
/>
)}
<LaneToolsMenu lane={lane} />
</div>
</div>
) : undefined
}
/>
)}
@@ -360,7 +363,9 @@ function ConsoleArea({
label={label}
onChange={change}
canStart={free !== null}
onNewSplit={(target, edge) => void newSplit(target, edge)}
tools={splitTools}
toolLabel={aiTools.label}
onNewSplit={(target, edge, tool) => void newSplit(target, edge, tool)}
renderPane={(slot) => (
<LaneConsolePane
lanes={lanes}