feat: workspace aliases, tab groups of split panes, native path picker (v2.1.0)

- /api/workspaces and `agenthub workspace(s)` / `--workspace` as aliases of
  /api/lanes and `agenthub lanes` / `--lane`; old names print one stderr note.
  REPL `help lanes|workspace|workspaces` resolves through the aliases.
- A workspace holds up to ten sessions (slots 0-9); every console tab is a
  group of 1-4 split panes (PaneGroups, agenthub.workspace.paneLayout);
  parseGroups keeps the stored active tab when an earlier one is dropped.
- Browse button beside path fields: POST /api/lanes/pick-path opens the OS
  picker on the server's desktop — loopback only and no forwarding header
  (EREMOTE), a display failure is ENODIALOG not a Cancel, the dialog is
  killed when the request closes, a folder field falls back to the in-app
  browser on any failure.
- Changes rail counts chip, image viewer (/git/raw), status bar session
  counts and per-day cost (`date=` on /api/pricing/cost).
- /agenthub-open and /agenthub-doctor run from the INSTALLED build
  (supersededBy), doctor offers fixes behind two confirmations; plugin-open
  treats a locked bootstrap as non-fatal.
- New-terminal shell waits for the first attach (issue #55).
- repo-onboarding / debug-live-issue skills removed.
- OpenAPI: pick-path, lane/date on /pricing/cost, tool on /run/history,
  /api/workspaces mount.
This commit is contained in:
2026-10-06 11:31:26 +07:00
parent c067abc68f
commit 6c605849d8
104 changed files with 3478 additions and 1396 deletions
+9 -28
View File
@@ -6,8 +6,8 @@
* session there) and `⋯` (the row menu, `WorkspaceRowMenu.tsx`, also on
* right-click). Dragging it, or Alt+↑/↓, reorders the list — a viewer
* preference kept in localStorage (`laneOrderStorage.ts`), never written to
* the lane row. Only a workspace row's own drag (`LANE_DRAG_TYPE`) is taken —
* a file, a tab or a text dragged over is not a reorder. F2 renames too; the
* the lane row. The row only STARTS a drag (`LANE_DRAG_TYPE`); the list
* (`WorkspaceTree.tsx`) takes the drop anywhere, gaps included. F2 renames too; the
* dot's meaning is screen-reader text, and an idle dot is a ring.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
@@ -20,7 +20,7 @@ import { isSubmitEnter } from "../../lib/keys";
/** What a workspace row's drag carries: its index in the list. */
export const LANE_DRAG_TYPE = "application/x-agenthub-lane";
const isLaneDrag = (e: { dataTransfer: DataTransfer | null }) =>
export const isLaneDrag = (e: { dataTransfer: DataTransfer | null }) =>
!!e.dataTransfer?.types?.includes(LANE_DRAG_TYPE);
/** The row's dot: the lane's liveness, plus a running pulse and needs-you. */
@@ -78,7 +78,6 @@ export function WorkspaceRow({
}) {
const { t } = useTranslation("lanes");
const [draft, setDraft] = useState("");
const [dropTarget, setDropTarget] = useState(false);
const name = workspaceName(lane);
const openMenu = (e: MouseEvent) => {
e.preventDefault();
@@ -92,38 +91,20 @@ export function WorkspaceRow({
return (
<div
data-testid={`workspace-row-${lane.id}`}
data-drop-target={dropTarget ? "true" : undefined}
data-lane-index={index}
draggable={!renaming}
onDragStart={(e) => {
// Firefox starts no drag at all without a setData call.
e.dataTransfer.setData(LANE_DRAG_TYPE, String(index));
e.dataTransfer.effectAllowed = "move";
}}
onDragOver={(e) => {
if (!isLaneDrag(e)) return;
e.preventDefault();
setDropTarget(true);
}}
onDragLeave={() => setDropTarget(false)}
onDragEnd={() => setDropTarget(false)}
onDrop={(e) => {
setDropTarget(false);
if (!isLaneDrag(e)) return;
e.preventDefault();
const raw = e.dataTransfer.getData(LANE_DRAG_TYPE);
// Number("") is 0: an empty payload must not move the first row.
const from = raw === "" ? NaN : Number(raw);
if (Number.isInteger(from)) onReorder(from, index);
}}
onContextMenu={openMenu}
className={`group flex items-center gap-1 rounded-md pl-1 pr-1 transition-colors ${
dropTarget
? "bg-accent/20"
: selected
? "bg-accent/10"
: lane.needs_action
? "bg-status-warning/10 hover:bg-status-warning/20"
: "hover:bg-surface-3"
selected
? "bg-accent/10"
: lane.needs_action
? "bg-status-warning/10 hover:bg-status-warning/20"
: "hover:bg-surface-3"
}`}
>
<button