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:
2026-10-19 17:19:41 +07:00
parent 1a6b550e89
commit 49f87c4da3
61 changed files with 2824 additions and 114 deletions
+32
View File
@@ -777,6 +777,10 @@ async function cloneInto(req, res, body, before) {
res.status(400).json({ error: { code: "EBADTOOL", message: "unknown tool" } });
return null;
}
if (body.partial !== undefined && typeof body.partial !== "boolean") {
res.status(400).json({ error: { code: "EBADFIELD", message: "partial: boolean" } });
return null;
}
const once = body.credentials;
if (once != null && (typeof once !== "object" || Array.isArray(once))) {
res.status(400).json({ error: { code: "EBADCRED", message: "credentials: object" } });
@@ -813,17 +817,32 @@ async function cloneInto(req, res, body, before) {
return null;
}
const ownerId = scope.ownerFor(req);
// The browser's own name for this clone, so it can poll its progress.
const queue = require("../lib/clone-queue");
let job = null;
if (body.progressToken !== undefined) {
try {
job = queue.track(body.progressToken, ownerId);
} catch (err) {
res.status(400).json({ error: { code: err.code, message: err.message } });
return null;
}
}
try {
await before(target, ownerId);
await identity.cloneRepo({
job,
url,
parent: dir,
name,
once: once
? { username: once.username, password: once.password, remember: once.remember === true }
: null,
partial: body.partial === true,
});
queue.finish(job, true);
} catch (err) {
queue.finish(job, false);
const status = ["EBADCRED", "EBADHOST", "EBADTOOL", "EDISABLED"].includes(err.code)
? 400
: ["ECREDENTIALS", "ESSHAUTH", "ENOREPO", "EOWNERFOLDER", "EMEMBERGONE"].includes(err.code)
@@ -843,6 +862,19 @@ async function cloneInto(req, res, body, before) {
return { target, name, ownerId };
}
/**
* A clone's place in the machine-wide queue, then git's own progress
* (`server/lib/clone-queue.js`), by the `progressToken` its POST carried —
* only to the user who started it (404 otherwise, and once it is long over).
*/
router.get("/clone/progress/:token", (req, res) => {
const view = require("../lib/clone-queue").progress(req.params.token, scope.ownerFor(req));
if (!view) {
return res.status(404).json({ error: { code: "ENOTFOUND", message: "no such clone" } });
}
res.json(view);
});
router.post("/clone", sameOriginGuard, async (req, res) => {
const body = req.body || {};
// Before the clone: a workspace this user may not own there, or a tool
+3
View File
@@ -57,6 +57,9 @@ const PREF_KEYS = [
"agent-monitor-notifications",
"agenthub.defaultTool",
"agenthub.permissionMode",
"agenthub.codex.sandbox",
"agenthub.codex.approval",
"agenthub.codex.bypass",
"agenthub.tour",
"agenthub.chatUi",
"agenthub.workspace.allTabs",