perf: faster plugin updates, half-size first bundle, images from the chat attach on Windows

- Chat view: an attached image's path is pasted on its own after the text
  (promptForPane). claude turns a paste that is only an image path into
  [Image #n], but left a Windows path inside a paste of text as a plain path.
  Other files and @-mentions stay in the text as before.
- Plugin bootstrap: depsHash covers only what npm installs (no release
  version), so a version bump no longer reinstalls runtime deps; the client's
  installed tree is kept while its manifests are unchanged (no npm ci per
  update); the user-side build runs vite only (tsc -b cost ~25 s / ~900 MB);
  clientBuild is recorded only after a build that did not fail, so a failed
  one is retried; npm output goes to runtime/npm-install.log.
- Server: a port held by another program ends with a clear message and exit 1
  instead of an uncaught 'error' stack trace.
- Client: the onboarding wizard is lazy and rendered only before onboarding;
  usePrefersReducedMotion moved to hooks/ so PetSettings no longer pulls the
  Sagi chat/pane stack into the entry chunk (884 KB -> 431 KB, gzip 268 -> 133 KB).
This commit is contained in:
2026-10-16 10:05:03 +07:00
parent a58e95a957
commit ecbbb4dd8f
17 changed files with 313 additions and 89 deletions
+30 -7
View File
@@ -33,6 +33,20 @@ if (process.env.GIT_EDITOR === "true") delete process.env.GIT_EDITOR;
// server/lib/settings-env.js. Only when this
// file IS the process: the test suite requires it dozens of times, and a
// developer's real settings file pins DASHBOARD_DATA_DIR at their live database.
/** What to tell the user when the dashboard cannot bind its port. The health
* probe above already turned away a port held by a running dashboard, so a
* port in use here belongs to some other program. */
function listenFailure(err, port) {
if (err?.code === "EADDRINUSE")
return (
`Port ${port} is already in use by another program, so AgentHub cannot start there. ` +
`Stop that program, or run AgentHub on another port with DASHBOARD_PORT (for example DASHBOARD_PORT=4821).`
);
if (err?.code === "EACCES")
return `Not allowed to listen on port ${port}. Pick a port above 1024 with DASHBOARD_PORT.`;
return `AgentHub could not start its server: ${err?.stack || err}`;
}
if (require.main === module) {
const settingsEnv = require("./lib/settings-env");
settingsEnv.loadEnvFiles({
@@ -337,8 +351,12 @@ function startServer(app, port) {
const host = resolveHost();
const boundLoopback = isLoopbackHostname(host);
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
// A port another program holds fails here, not as an uncaught 'error'
// event: the caller says what happened instead of a bare stack trace.
server.once("error", reject);
server.listen(port, host, () => {
server.off("error", reject);
// Publish the live port so the Claude Code hook handler can find this
// server even when it bound a non-default port (the desktop app falls
// back off 4820 when that port is already taken).
@@ -1210,12 +1228,17 @@ if (require.main === module) {
return;
}
const app = createApp();
startServer(app, PORT).then((server) => {
httpServer = server;
startBackgroundServices();
// The status-area icon (server/lib/tray.js) — production servers only.
require("./lib/tray").startTrayWatch();
});
startServer(app, PORT)
.then((server) => {
httpServer = server;
startBackgroundServices();
// The status-area icon (server/lib/tray.js) — production servers only.
require("./lib/tray").startTrayWatch();
})
.catch((err) => {
console.error(listenFailure(err, PORT));
process.exit(1);
});
});
// Graceful shutdown — close connections and DB cleanly