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
+59
View File
@@ -64,6 +64,8 @@ before(async () => {
"init",
]);
execFileSync("git", ["clone", "-q", "--bare", src, bare]);
// A forge serves a partial clone only when it allows filters.
execFileSync("git", ["-C", bare, "config", "uploadpack.allowFilter", "true"]);
remote = http.createServer((_req, res) => {
res.writeHead(401, { "WWW-Authenticate": 'Basic realm="t"' });
@@ -183,6 +185,61 @@ describe("POST /api/lanes/clone", () => {
assert.equal(r.body.error.code, "ENOREPO");
});
it("makes a blobless clone only when asked, and refuses a non-boolean", async () => {
const r = await request("POST", "/api/lanes/clone", {
url: "https://forge.test/o/r.git",
parent: WORK,
name: "r-partial",
partial: true,
});
assert.equal(r.status, 201, JSON.stringify(r.body));
const dir = path.join(WORK, "r-partial");
assert.equal(fs.readFileSync(path.join(dir, "README.md"), "utf8"), "hi\n");
const cfg = (k) =>
execFileSync("git", ["-C", dir, "config", "--get", k], { encoding: "utf8" }).trim();
assert.equal(cfg("remote.origin.partialclonefilter"), "blob:none");
// The full clone above is not a promisor remote.
assert.throws(() =>
execFileSync("git", ["-C", path.join(WORK, "r"), "config", "--get", "remote.origin.promisor"])
);
const bad = await request("POST", "/api/lanes/clone", {
url: "https://forge.test/o/r.git",
parent: WORK,
name: "r-bad",
partial: "yes",
});
assert.equal(bad.status, 400);
assert.equal(bad.body.error.code, "EBADFIELD");
});
it("reports a clone's progress by its token, and refuses a bad token", async () => {
const token = "progress-test-0000001";
const r = await request("POST", "/api/lanes/clone", {
url: "https://forge.test/o/r.git",
parent: WORK,
name: "r-progress",
progressToken: token,
});
assert.equal(r.status, 201, JSON.stringify(r.body));
const p = await request("GET", `/api/lanes/clone/progress/${token}`);
assert.equal(p.status, 200);
assert.equal(p.body.state, "done");
assert.equal(p.body.position, null);
assert.equal(
(await request("GET", "/api/lanes/clone/progress/nope-nope-nope-nope")).status,
404
);
const bad = await request("POST", "/api/lanes/clone", {
url: "https://forge.test/o/r.git",
parent: WORK,
name: "r-badtoken",
progressToken: "x",
});
assert.equal(bad.status, 400);
assert.equal(bad.body.error.code, "EBADFIELD");
assert.ok(!fs.existsSync(path.join(WORK, "r-badtoken")));
});
it("refuses a cross-origin request", async () => {
const r = await request(
"POST",
@@ -649,6 +706,8 @@ describe("git-identity, member account (S3, stubbed)", () => {
/^core\.sshCommand=ssh -o IdentityFile='.*\/id_ed25519' -o IdentityFile='.*\/id_rsa' -o IdentitiesOnly=yes$/
);
assert.deepEqual(args.slice(-3), ["--", "git@gh.test:o/r.git", "r"]);
// index-pack gets a share of the cores, never persisted into the clone.
assert.match(args.slice(0, i).join(" "), /-c pack\.threads=\d+/);
});
it("gives a member's fetch their own store, and puts a typed login in it only for the call", () => {