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:
@@ -9,7 +9,7 @@
|
||||
* worktree is still the workspace home's prompt box) — plus a link to
|
||||
* Settings → Agents. Four tabs is the server's limit (`MAX_PANE_SLOT`). The
|
||||
* bar is the console's only header: Home (the workspace's home) at its left
|
||||
* end, `actions` (the workspace's Tools menu) at its right end. A click shows a tab in the focused pane, Ctrl/Cmd+click
|
||||
* end, `actions` (the single pane's split menus) at its right end. A click shows a tab in the focused pane, Ctrl/Cmd+click
|
||||
* (on a tab or a `+` item) opens it in a split to the right, and a tab drags
|
||||
* onto the panes (`PaneGrid.tsx`); a tab another pane shows is marked. A
|
||||
* right-click on a tab opens the same actions as a menu (`ContextMenu`).
|
||||
@@ -29,6 +29,31 @@ import { freeSlot } from "../lanes/WorkspaceHome";
|
||||
import { RunIcon, startPaneDrag } from "./PaneGrid";
|
||||
import { ContextMenu, MenuItem, MenuSeparator, useContextMenu } from "../ContextMenu";
|
||||
|
||||
type AiToolsState = ReturnType<typeof useAiTools>;
|
||||
|
||||
/** The tools a new tab or split offers: the workspace's own whatever its
|
||||
* state, then every installed, enabled one. */
|
||||
export function newTabTools(lane: Lane, aiTools: AiToolsState): string[] {
|
||||
return [
|
||||
...new Set([
|
||||
lane.tool,
|
||||
...aiTools.tools.filter((x) => x.installed && x.enabled).map((x) => x.id),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/** The start body of a bare tool on its default model, no prompt. Only a
|
||||
* different tool is named, as the home does: the workspace's own keeps
|
||||
* starting even once disabled. */
|
||||
export function newTabBody(id: string, lane: Lane, aiTools: AiToolsState) {
|
||||
const model = aiTools.byId(id)?.defaultModel;
|
||||
return {
|
||||
prompt: "",
|
||||
...(id !== lane.tool ? { tool: id } : {}),
|
||||
...(model ? { model } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function ConsoleTabs({
|
||||
lane,
|
||||
tabs,
|
||||
@@ -52,7 +77,7 @@ export function ConsoleTabs({
|
||||
onClosed: (runId: string) => void;
|
||||
/** The workspace's home (its prompt box, Recents, context). */
|
||||
onHome?: () => void;
|
||||
/** Right end of the bar (the workspace's Tools menu). */
|
||||
/** Right end of the bar (the single pane's split menus). */
|
||||
actions?: ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation("lanes");
|
||||
@@ -79,13 +104,7 @@ export function ConsoleTabs({
|
||||
|
||||
const slot = freeSlot(lane.id, tabs);
|
||||
const label = (r: RunHandle) => (r.tool === "shell" ? t("tabs.terminal") : aiTools.label(r.tool));
|
||||
// The workspace's own tool whatever its state, then every installed, enabled one.
|
||||
const toolIds = [
|
||||
...new Set([
|
||||
lane.tool,
|
||||
...aiTools.tools.filter((x) => x.installed && x.enabled).map((x) => x.id),
|
||||
]),
|
||||
];
|
||||
const toolIds = newTabTools(lane, aiTools);
|
||||
|
||||
const open = async (body: Record<string, unknown>, split = false) => {
|
||||
setMenuOpen(false);
|
||||
@@ -217,33 +236,19 @@ export function ConsoleTabs({
|
||||
{t("tabs.newTerminal")}
|
||||
</button>
|
||||
<div className="my-1.5 border-t border-border" />
|
||||
{toolIds.map((id) => {
|
||||
const model = aiTools.byId(id)?.defaultModel;
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
role="menuitem"
|
||||
data-testid={`console-tab-new-${id}`}
|
||||
className={item}
|
||||
onClick={(e) =>
|
||||
void open(
|
||||
{
|
||||
prompt: "",
|
||||
// Only a different tool is named, as the home does: the
|
||||
// workspace's own keeps starting even once disabled.
|
||||
...(id !== lane.tool ? { tool: id } : {}),
|
||||
...(model ? { model } : {}),
|
||||
},
|
||||
e.ctrlKey || e.metaKey
|
||||
)
|
||||
}
|
||||
>
|
||||
<AiToolIcon tool={{ id, label: aiTools.label(id) }} className="h-4 w-4 !p-0" />
|
||||
{aiTools.label(id)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{toolIds.map((id) => (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
role="menuitem"
|
||||
data-testid={`console-tab-new-${id}`}
|
||||
className={item}
|
||||
onClick={(e) => void open(newTabBody(id, lane, aiTools), e.ctrlKey || e.metaKey)}
|
||||
>
|
||||
<AiToolIcon tool={{ id, label: aiTools.label(id) }} className="h-4 w-4 !p-0" />
|
||||
{aiTools.label(id)}
|
||||
</button>
|
||||
))}
|
||||
<div className="my-1.5 border-t border-border" />
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user