feat(ui): a Claude-Desktop-style home for every workspace

/workspace?lane=<id> opens on the workspace's home (WorkspaceHome): a prompt
box whose Start opens a new session in the first free pane slot, Recents (a
live session attaches its pane, any other opens its transcript, nothing
resumes), and Instructions / Memory / Context / Folder. Instructions live in
lanes.instructions, extra folders and uploaded files in the new lane_sources
table (copies under <data dir>/workspace-context/<lane id>/); spawnRun reads
them from the lane's rows and hands Claude --add-dir + an appended system
prompt file, Codex --add-dir + developer_instructions. Memory is read-only.

Every dashboard pane carries AGENTHUB_RUN_ID; hooks forward it and the run
records which session the pane holds (a Claude pane follows /clear and
/resume but not a nested claude -p), so the sidebar and notifications open
the right pane (&run=). Settings loses its Projects group; Worktrees now
decides which worktrees Add folder suggests.
This commit is contained in:
2026-10-02 01:10:58 +07:00
parent 2e051e23b6
commit d0535af626
44 changed files with 2515 additions and 466 deletions
+63
View File
@@ -2739,6 +2739,37 @@ export const api = {
* @param patch Subset of lane fields to update.
* @returns `{ lane }` — the updated {@link Lane}.
*/
/** GET /api/lanes/:id/sources — the workspace's instructions, extra
* folders and context files, and whether its tool can take them. */
sources: (id: number) => request<LaneSourcesView>(`/lanes/${id}/sources`),
/** PUT /api/lanes/:id/instructions — appended to every new pane's system prompt. */
setInstructions: (id: number, instructions: string | null) =>
request<LaneSourcesView>(`/lanes/${id}/instructions`, {
method: "PUT",
body: JSON.stringify({ instructions }),
}),
/** POST /api/lanes/:id/sources/folder — an extra folder the agent may use. */
addFolder: (id: number, path: string) =>
request<LaneSourcesView>(`/lanes/${id}/sources/folder`, {
method: "POST",
body: JSON.stringify({ path }),
}),
/** POST /api/lanes/:id/sources/file — upload one context file (≤ 10 MB). */
addFile: (id: number, file: File) =>
request<LaneSourcesView>(`/lanes/${id}/sources/file`, {
method: "POST",
body: file,
headers: {
"Content-Type": "application/octet-stream",
"X-File-Name": encodeURIComponent(file.name),
},
}),
/** DELETE /api/lanes/:id/sources/:sourceId — a folder stays on disk; an
* uploaded file's AgentHub copy is deleted. */
removeSource: (id: number, sourceId: number) =>
request<LaneSourcesView>(`/lanes/${id}/sources/${sourceId}`, { method: "DELETE" }),
/** GET /api/lanes/:id/memory — CLAUDE.md/AGENTS.md and Claude's auto memory, read-only. */
memory: (id: number) => request<{ files: LaneMemoryFile[] }>(`/lanes/${id}/memory`),
update: (id: number, patch: Partial<Lane>) =>
request<{ lane: Lane }>(`/lanes/${id}`, { method: "PATCH", body: JSON.stringify(patch) }),
/**
@@ -3879,6 +3910,38 @@ export interface RunHandle {
tool: LaneTool;
}
/** One extra folder or uploaded context file of a workspace. */
export interface LaneSource {
id: number;
kind: "folder" | "file";
path: string;
name: string;
size: number | null;
added_at: string;
/** The folder or file is gone from disk; it is skipped at spawn. */
missing: boolean;
}
/** Response shape of GET /api/lanes/:id/sources (and of every sources write). */
export interface LaneSourcesView {
instructions: string;
/** The workspace's tool can take folders and instructions (claude, codex). */
supported: boolean;
maxFileBytes: number;
maxInstructions: number;
sources: LaneSource[];
}
/** One read-only memory file from GET /api/lanes/:id/memory. */
export interface LaneMemoryFile {
label: string;
path: string;
size: number;
mtime: string;
truncated: boolean;
content: string;
}
/** Response shape of GET /api/run. */
export interface RunListResponse {
items: RunHandle[];