chore: v2.8.12 — clone queue and progress, session defaults, chat agents, drops as @ mentions
Whole tree from parallel sessions, plus the version bump: - Clone: a queue with live progress (Add workspace and extra folders), clones run under nice/ionice so the machine stays responsive. - Settings → AI tools → Session defaults: a new session's default permission mode / bypass per account. - Chat lists the background subagents that have not reported back yet. - A file pasted or dropped into a claude pane goes in as an @ mention. - Changes panel: say why amend/undo is refused on a pushed commit. - openapi.yaml regenerated (Artifacts, Host, resumeById).
This commit is contained in:
+24
-4
@@ -110,7 +110,7 @@ An admin (server mode) adds an account with `POST /api/accounts` `{tool: "claude
|
||||
| `PUT /api/me/avatar` (raw image bytes) | the account's picture (PNG, JPEG, GIF or WebP by magic bytes — never SVG), stored as `<data dir>/avatars/u<id>.<ext>`; the `/api/me/account` shape back with `user.avatarAt` set. `400 EBADIMAGE`, `413 ETOOBIG` over 1 MB, `403 ELOCALMODE`. Login cookie only, audited |
|
||||
| `DELETE /api/me/avatar` | removes it (`avatarAt` back to `null`); deleting the user removes it too |
|
||||
|
||||
The keys are the browser's own localStorage names: `theme`, `agenthub.theme.dark`, `agenthub.theme.light`, `i18nextLng`, `agenthub.terminal.font`, `agenthub.terminal.fontSize`, `agenthub.ui.fontSize`, `agenthub.pet`, `agenthub.pet.size`, `agenthub.pet.names`, `agent-dashboard-sagi-enabled`, `agent-dashboard-sagi-muted`, `agent-monitor-notifications`, `agenthub.defaultTool`, `agenthub.permissionMode` (never `bypassPermissions` — bypass stays the per-session switch), `agenthub.tour`, and `agenthub.chatUi` (`"1"` = Claude/Codex sessions open as a chat; read and written on the server only, by `client/src/lib/chatUi.ts`). The request never names an account, and writes are same-origin. An account's save writes only their own row, and under S3 a `theme` of `dark` or `light` also becomes the `theme` in their own `~/.claude/settings.json`, written as them.
|
||||
The keys are the browser's own localStorage names: `theme`, `agenthub.theme.dark`, `agenthub.theme.light`, `i18nextLng`, `agenthub.terminal.font`, `agenthub.terminal.fontSize`, `agenthub.ui.fontSize`, `agenthub.pet`, `agenthub.pet.size`, `agenthub.pet.names`, `agent-dashboard-sagi-enabled`, `agent-dashboard-sagi-muted`, `agent-monitor-notifications`, `agenthub.defaultTool`, `agenthub.permissionMode` (`bypassPermissions` allowed — set only from Settings → AI tools → Session defaults, behind a confirm), `agenthub.codex.sandbox`, `agenthub.codex.approval`, `agenthub.codex.bypass` (`"true"`/`"false"`: a new Codex session's defaults), `agenthub.tour`, and `agenthub.chatUi` (`"1"` = Claude/Codex sessions open as a chat; read and written on the server only, by `client/src/lib/chatUi.ts`). The request never names an account, and writes are same-origin. An account's save writes only their own row, and under S3 a `theme` of `dark` or `light` also becomes the `theme` in their own `~/.claude/settings.json`, written as them.
|
||||
|
||||
`GET /api/settings/info` includes `server.version` (the running dashboard release). Pair with `agenthub version` or Settings → System to confirm client and server builds match after deploy.
|
||||
|
||||
@@ -351,11 +351,31 @@ check of that route applies, and opens a workspace on the clone.
|
||||
#### Clone a repository into a new workspace
|
||||
|
||||
```
|
||||
POST /api/lanes/clone Body: { url, parent, name?, title?, tool?, credentials?: { username, password, remember? } }
|
||||
POST /api/lanes/clone Body: { url, parent, name?, title?, tool?, credentials?: { username, password, remember? }, partial?, progressToken? }
|
||||
→ 201 { lane, created: true, path }
|
||||
GET /api/lanes/clone/progress/:token
|
||||
→ 200 { state: queued|running|done|failed, position, running, waiting, limit,
|
||||
phase: remote|receiving|resolving|checkout|other|null, label, percent, detail }
|
||||
```
|
||||
|
||||
Add workspace → **Clone**. `url` passes the same allowlist as the git bar's
|
||||
Add workspace → **Clone**. Clones are queued machine-wide
|
||||
(`server/lib/clone-queue.js`): at most `AGENTHUB_CLONE_CONCURRENCY` run at
|
||||
once (default half the CPU cores, at least 1), the rest wait in arrival order
|
||||
with the request held open. Each runs under `nice -n 10` / `ionice -c2 -n7`
|
||||
with `pack.threads` = its share of the cores (not persisted), and is killed
|
||||
after 30 minutes of running — so several ~1 GB clones no longer starve the
|
||||
dashboard and the panes. `partial: true` (the form's **Partial clone** box;
|
||||
`400 EBADFIELD` when not a boolean) makes it blobless (`--filter=blob:none`):
|
||||
every commit and tree, but a file's contents only when a checkout needs them —
|
||||
far less to download and unpack for a big repo; checking out an old revision
|
||||
later fetches from the remote. `progressToken` (16–64 of `[A-Za-z0-9_-]`, unused;
|
||||
else `400 EBADFIELD`) is the browser's own name for the clone: while the POST is
|
||||
held open, `GET /api/lanes/clone/progress/:token` answers its 1-based
|
||||
`position` among the waiting clones, then git's `--progress` line parsed into
|
||||
`phase`, `percent` and `detail` (`"450/1000, 120.50 MiB | 5.20 MiB/s"`) — the
|
||||
form polls it every second (`CloneProgress.tsx`). Only the user who started
|
||||
the clone sees it (404 otherwise), and it is dropped a minute after the clone
|
||||
ends. `url` passes the same allowlist as the git bar's
|
||||
`publish` (`https://` with no userinfo, `ssh://`, scp-like `user@host:path` —
|
||||
else `400 EBADURL`); `name` (default: the URL's last segment without `.git`)
|
||||
is one plain folder name that must not exist (`400 EBADNAME`, `409 EEXISTS`);
|
||||
@@ -1600,7 +1620,7 @@ What a workspace's home page edits (`server/lib/lane-sources.js`, `server/routes
|
||||
|
||||
- **Instructions** — the `instructions` key is required (`400 EBADFIELD` without it), a string or `null`; blank clears; over 16 000 characters is `413 ETOOBIG`. The lane is re-broadcast (`lane_update`). The text is stored in `lanes.instructions` but is NOT part of the lane payload (`GET /api/lanes`, `lane_update`) — read it here.
|
||||
- **Folder** — must be absolute, exist and be a directory (`400 EBADPATH`), must not be or contain the OS home (`400 EBADHOME`), and must not be the workspace's own folder or one it already has (`400 EDUP`). Stored by real path. Removing it only drops the row.
|
||||
- **Clone** — clones a related repo BESIDE the workspace folder (into its parent; no path is taken from the request) and adds it as a Folder. The clone is `POST /api/lanes/clone`'s own: the same URL allowlist (`400 EBADURL`), one plain `name` (default: the repo's), `409 EEXISTS` for a taken name, run as the folder's account with that account's git identity, `409 ECREDENTIALS` (with `auth: {protocol, host}`) / `ESSHAUTH` / `ENOREPO`; a tool that takes no folders is `400 EUNSUPPORTED`. Answers the view plus `path`. Removing the folder later never deletes the clone. The workspace home's **Add folder** form takes several URLs, one per line, and clones them in turn. Before cloning it checks each URL against `GET /api/me/git`: an SSH link (`ssh://…`, `user@host:path`) while the account has no SSH key is not sent at all and points to Settings → Git (make a key, add its public key to the host), and an HTTPS host with no saved login is flagged with an optional login row — a public repo needs none. The server's own `ECREDENTIALS`/`ESSHAUTH` answers still decide.
|
||||
- **Clone** — clones a related repo BESIDE the workspace folder (into its parent; no path is taken from the request) and adds it as a Folder. The clone is `POST /api/lanes/clone`'s own: the same URL allowlist (`400 EBADURL`), one plain `name` (default: the repo's), the same `partial` and `progressToken`, `409 EEXISTS` for a taken name, run as the folder's account with that account's git identity, `409 ECREDENTIALS` (with `auth: {protocol, host}`) / `ESSHAUTH` / `ENOREPO`; a tool that takes no folders is `400 EUNSUPPORTED`. Answers the view plus `path`. Removing the folder later never deletes the clone. The workspace home's **Add folder** form takes several URLs, one per line, and clones them in turn. Before cloning it checks each URL against `GET /api/me/git`: an SSH link (`ssh://…`, `user@host:path`) while the account has no SSH key is not sent at all and points to Settings → Git (make a key, add its public key to the host), and an HTTPS host with no saved login is flagged with an optional login row — a public repo needs none. The server's own `ECREDENTIALS`/`ESSHAUTH` answers still decide.
|
||||
- **File** — up to 10 MB (`413 ETOOBIG`), not empty (`400 EEMPTY`). AgentHub stores it under `<data dir>/workspace-context/<lane id>/` with a name it derives from `X-File-Name` (directory parts and path characters dropped, `name (2).ext` on a clash), so a request never names a path. It is a COPY taken at upload: later edits to the original are not seen — remove and upload it again, or add its directory as a Folder (read live from disk). Removing it deletes that copy. At most 50 sources per workspace (`400 ETOOMANY`).
|
||||
- **Memory** — read-only: the folder's `CLAUDE.md`, `.claude/CLAUDE.md`, `CLAUDE.local.md`, `AGENTS.md`, and for a Claude workspace `<CLAUDE_HOME>/projects/<encoded cwd>/memory/*.md` — `{files: [{label, path, size, mtime, truncated, content}]}`, each at most 256 KB of content.
|
||||
- Every write is same-origin only (`403 EBADORIGIN`); an unknown workspace is `404 ENOLANE`, an unknown source `404 ENOSOURCE`.
|
||||
|
||||
Reference in New Issue
Block a user