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
+28 -8
View File
@@ -44,8 +44,8 @@ const gitCredentials = require("./git-credentials");
const runAs = require("./run-as");
const HOST_RE = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:\d{1,5})?$/;
/** A clone may take a while on a big repo; past this it is killed. */
const CLONE_TIMEOUT_MS = 10 * 60_000;
/** A clone may take a while on a big repo (~1 GB); past this it is killed. */
const CLONE_TIMEOUT_MS = 30 * 60_000;
const HELPER = path.join(__dirname, "git-credential-helper.js");
const err = (code, message) => Object.assign(new Error(message), { code });
@@ -841,15 +841,33 @@ function sshFailure(stderr) {
* Clone `url` into `<parent>/<name>` as the account owning `parent`, with
* that account's own identity. `once` is a login typed for this clone
* (`{username, password, remember}`) — https only, for this URL's host only.
* `partial` makes it a blobless clone (`--filter=blob:none`): every commit
* and tree, but a file's contents only when a checkout first needs them —
* far less to download and unpack for a big repo, at the cost of a fetch
* from the remote when an old revision is checked out later.
* Resolves `{dir, account}`; rejects with `ECREDENTIALS` (`{protocol, host}`
* attached) when git wanted a login it did not get, `ESSHAUTH` when the key
* was refused, else the git error.
*/
async function cloneRepo({ url, parent, name, once = null }) {
function cloneRepo({ job = null, ...opts }) {
// Waits for a free clone slot (clone-queue.js) before anything — a typed
// login sits in a member's store only while its own clone runs, and the
// timeout counts from there. `job` (clone-queue `track`) gets the queue
// position and git's progress.
const queue = require("./clone-queue");
return queue.withCloneSlot(
() => cloneNow(opts, queue.threads(), (chunk) => queue.report(job, chunk)),
job
);
}
async function cloneNow({ url, parent, name, once = null, partial = false }, threads, onProgress) {
const { git } = require("./worktree");
const account = accountForDir(parent);
const remote = remoteOf(url);
const pre = [];
// index-pack's delta threads: this clone's share of the cores, not all of
// them (clone-queue.js). Not persisted — a later fetch keeps git's default.
const pre = ["-c", `pack.threads=${threads}`];
const persist = [];
const env = {};
let restore = null;
@@ -911,9 +929,13 @@ async function cloneRepo({ url, parent, name, once = null }) {
let ok = false;
try {
await git(parent, [...pre, "clone", ...persist, "--", url, name], {
const filter = partial ? ["--filter=blob:none"] : [];
// --progress: git prints it to a pipe too, for the UI's progress line.
await git(parent, [...pre, "clone", ...persist, "--progress", ...filter, "--", url, name], {
timeout: CLONE_TIMEOUT_MS,
env,
background: true,
onStderr: onProgress,
});
ok = true;
} catch (e) {
@@ -932,9 +954,7 @@ async function cloneRepo({ url, parent, name, once = null }) {
}
throw err(
"ECLONE",
String(stderr || e.message)
.trim()
.slice(-800)
(require("./clone-queue").stripProgress(stderr) || e.message).trim().slice(-800)
);
} finally {
// A login typed without "remember" — or one that did not work — leaves