chore: v2.8.13 — Artifacts reinstall/edit/installs, agent steps in chat, pick a local file to attach, Windows paths as links
- Chat: an Agent step shows its task, status and tool calls and opens its conversation in place; Windows paths (E:\…, C:/…) in replies open as files - Prompt: attach a file on this computer by path (native picker), clearer too-large messages - Artifacts: edit title/description and a skill's files (new snapshot), Reinstall, install history, Hub-only rows, skills installed into ~/.claude/skills and ~/.agents/skills - Files: batch upload of small files in one extract - AI Tools: npm install falls back to --prefix ~/.local when the global prefix is not writable
This commit is contained in:
@@ -332,6 +332,69 @@ describe("POST /api/run/drop-image", () => {
|
||||
});
|
||||
assert.equal(r.status, 400);
|
||||
});
|
||||
|
||||
it("refuses a file declared over the limit before its body arrives", async () => {
|
||||
// 4 GB announced, 1 KB sent: the answer must come without the rest.
|
||||
const { status, body } = await new Promise((resolve, reject) => {
|
||||
const url = new URL("/api/run/drop-file?name=big.mp4", BASE);
|
||||
const req = http.request(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "video/mp4", "Content-Length": 4 * 1024 ** 3 },
|
||||
});
|
||||
req.on("response", (res) => {
|
||||
let text = "";
|
||||
res.on("data", (c) => (text += c));
|
||||
res.on("end", () => {
|
||||
req.destroy();
|
||||
resolve({ status: res.statusCode, body: JSON.parse(text) });
|
||||
});
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.write(Buffer.alloc(1024));
|
||||
});
|
||||
assert.equal(status, 413);
|
||||
assert.equal(body.error.code, "ETOOBIG");
|
||||
assert.equal(body.error.max_bytes, 20 * 1024 * 1024); // the UI shows the server's limit
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/run/local-paths", () => {
|
||||
let srv;
|
||||
before(async () => {
|
||||
srv = http.createServer(createApp());
|
||||
await new Promise((r) => srv.listen(0, "127.0.0.1", r));
|
||||
BASE = `http://127.0.0.1:${srv.address().port}`;
|
||||
});
|
||||
after(() => new Promise((r) => srv.close(r)));
|
||||
|
||||
it("answers the size of each regular file, null for anything else", async () => {
|
||||
const file = path.join(TMP, "clip.mp4");
|
||||
fs.writeFileSync(file, "12345");
|
||||
const r = await fetchJson("/api/run/local-paths", {
|
||||
method: "POST",
|
||||
body: { paths: [file, TMP, path.join(TMP, "missing.bin")] },
|
||||
});
|
||||
assert.equal(r.status, 200);
|
||||
assert.deepEqual(r.body.sizes, [5, null, null]);
|
||||
});
|
||||
|
||||
it("refuses a relative path", async () => {
|
||||
const r = await fetchJson("/api/run/local-paths", {
|
||||
method: "POST",
|
||||
body: { paths: ["clip.mp4"] },
|
||||
});
|
||||
assert.equal(r.status, 400);
|
||||
});
|
||||
|
||||
it("refuses a request a proxy forwarded: that browser is elsewhere", async () => {
|
||||
const r = await fetchJson("/api/run/local-paths", {
|
||||
method: "POST",
|
||||
headers: { "X-Forwarded-For": "10.0.0.5" },
|
||||
body: { paths: [TMP] },
|
||||
});
|
||||
assert.equal(r.status, 403);
|
||||
assert.equal(r.body.error.code, "EREMOTE");
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/run/drop/:name", () => {
|
||||
|
||||
Reference in New Issue
Block a user