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:
2026-10-20 16:44:55 +07:00
parent 49f87c4da3
commit 0547488f13
59 changed files with 4285 additions and 690 deletions
+12 -7
View File
@@ -2,7 +2,8 @@
* @file The one-at-a-time install/update runner behind Settings → AI Tools.
* Every command comes from a catalog file (never a request) and runs as an
* argv with stdin closed and the login shell's PATH first: `npm install -g
* <pkg>@latest`, a tool's own updater, or a vendor script the server
* <pkg>@latest` (with `--prefix ~/.local` when npm's global prefix is not the
* account's to write — a system-wide Node), a tool's own updater, or a vendor script the server
* downloads (https, same site as the tool's homepage, ≤ 1 MB) and runs with
* `bash <file>`. The job lives in memory — a child of the server, so it dies
* with it — and it reports its end only AFTER the PATH is re-read and the tool
@@ -27,7 +28,8 @@ let jobTimeoutMs = 10 * 60 * 1000;
const SCRIPT_MAX = 1024 * 1024;
const EACCES_HINT =
"\n[agenthub] npm could not write its global directory (EACCES): this Node is installed " +
"system-wide. Use a user-level Node (nvm, fnm) or install the tool by hand.\n";
"system-wide and ~/.local is not writable either. Use a user-level Node (nvm, fnm) or install " +
"the tool by hand.\n";
const EXIT_GRACE_MS = 500; // after 'exit', how long to wait for the pipes to close
const KILL_GRACE_MS = 5000; // SIGTERM → SIGKILL
@@ -44,10 +46,11 @@ function makeErr(code, message) {
return err;
}
const npmArgv = (pkg) => {
/** `prefix`: the `--prefix` args (none for npm's own global prefix). */
const npmArgv = (pkg, prefix = []) => {
const npm = versions.npmBin();
if (!npm) throw makeErr("ENONPM", "npm was not found on the login shell's PATH");
return [npm, "install", "-g", `${pkg}@latest`];
return [npm, "install", "-g", ...prefix, `${pkg}@latest`];
};
/** The steps one action on one tool would run — `{argv}` or `{script: url}`. */
@@ -59,20 +62,22 @@ async function stepsFor(id, action) {
if (!tool.install) throw makeErr("ENOINSTALL", `${tool.label} has no install command`);
if (d.installed) throw makeErr("EINSTALLED", `${tool.label} is already installed`);
return tool.install.kind === "npm"
? [{ argv: npmArgv(tool.install.package) }]
? [{ argv: npmArgv(tool.install.package, await versions.installPrefixArgs()) }]
: [{ script: tool.install.url }];
}
if (!tool.update) throw makeErr("ENOUPDATE", `${tool.label} has no update command`);
if (!d.installed) throw makeErr("ENOTINSTALLED", `${tool.label} is not installed`);
if (tool.update.kind === "self") return [{ argv: [d.path, ...tool.update.args] }];
if (tool.update.kind === "script") return [{ script: tool.install.url }];
if (!(await versions.npmManaged(id))) {
const by = await versions.managedBy(id);
if (!by) {
throw makeErr(
"ENOTNPM",
`${tool.label} was installed outside npm; update it the way it was installed`
);
}
return [{ argv: npmArgv(tool.install.package) }];
const prefix = by === "user" ? ["--prefix", versions.userPrefix()] : [];
return [{ argv: npmArgv(tool.install.package, prefix) }];
}
function append(j, text) {