fix: per-user Today, native paste and drop on a local machine

- Today's spend in the status bar and the Claude Code statusline is the
  caller's own (`GET /api/pricing/cost?mine=1`, scope.ownSessionWhere), not
  every user's sum for an admin or analyst.
- A screenshot pasted into a local claude pane is sent as Claude Code's own
  image-paste key: Alt+V on Windows (Ctrl+V did nothing there), ^V elsewhere,
  and only when the machine can read its clipboard (wl-paste/xclip + a
  display on Linux), else it is uploaded (GET /api/run/native-paste {key}).
- A file manager's files pasted or dropped on a local browser go in by their
  own paths, read from the OS clipboard or the file manager's selection
  (server/lib/file-manager.js, POST /api/run/clipboard-files and
  /selected-files): no upload, no size limit. A copied image file is pasted
  by path, text that comes with a picture of it (spreadsheet cells) is
  pasted as text, Windows paths go in as `C:/…` mentions.
- Tab titles for Snapshot, Artifacts, Files and Administration.
- An npm tool AgentHub installed under ~/.local is updated there, not by its
  own updater into npm's global prefix.
This commit is contained in:
2026-10-26 11:18:05 +07:00
parent c59e196e41
commit f2f1cd9b6c
33 changed files with 1046 additions and 60 deletions
+10 -2
View File
@@ -576,7 +576,14 @@ function costScope(req, res) {
res.status(400).json(BAD_TOOL);
return null;
}
return costWhere({ scope: parseScope(req), lane });
// `mine=1`: the caller's own sessions even for an admin/analyst, who would
// otherwise get every user's sum (the status bar's Today is a personal figure).
const extra = [];
if (req.query.mine === "1") {
const own = require("../lib/scope").ownSessionWhere(req, "s.id", "s.lane_id");
if (own.where !== "1") extra.push({ clause: own.where, params: own.params });
}
return costWhere({ scope: parseScope(req), lane, extra });
}
/**
@@ -625,7 +632,8 @@ function computeCost(
// / analytics endpoints so the Dashboard "total cost" tracks the selector, plus
// `?lane=<id>` (the sessions that workspace owns), `?from=&to=` (local
// dates, see computeCost) and `?date=` (one local day, its `daily_costs` too —
// the status bar's Today). `by_tool` splits the total.
// the status bar's Today) and `?mine=1` (only the caller's own sessions, even
// for an admin or analyst — server mode; a no-op locally). `by_tool` splits the total.
router.get("/cost", (req, res) => {
const rawOffset = parseInt(req.query.tz_offset, 10);
const tzModifier = Number.isFinite(rawOffset) ? `${-rawOffset} minutes` : "+0 minutes";
+44 -1
View File
@@ -116,7 +116,9 @@ const DROP_TYPES = {
const DROP_MAX_BYTES = 20 * 1024 * 1024;
// A browser on this machine (loopback, never server mode) crosses no network:
// its drop is streamed to disk, so a video or a dump is no reason to refuse.
// Chrome hands a dropped file no `file://` path, so it cannot attach by path.
// Chrome hands a dropped file no `file://` path; the client first asks for
// the file manager's own (`/selected-files`, `/clipboard-files`) and only
// uploads what that cannot place.
const DROP_LOCAL_MAX_BYTES = 2 * 1024 ** 3;
const DROP_TTL_MS = 24 * 60 * 60_000;
const localDrop = (req) => !require("../lib/auth").isServerMode() && isLocalRequest(req);
@@ -314,6 +316,47 @@ router.post("/local-paths", (req, res) => {
});
});
// The files this machine's file manager is handing the browser, by their own
// paths (server/lib/file-manager.js): a browser gives a page a pasted or
// dropped file as bytes only — Chrome never with its path — so a big file
// could not be attached. A paste reads the clipboard (`clipboard-files`), a
// drop the file manager's selection, which is what a drag from it carries
// (`selected-files`). Same rule as /local-paths: local mode and a loopback
// request only; the client uses a path only when name and size match.
function fileManagerRoute(read) {
return async (req, res) => {
if (require("../lib/auth").isServerMode() || !isLocalRequest(req)) {
return res.status(403).json({
error: { code: "EREMOTE", message: "the file manager is read only for a local browser" },
});
}
res.json({ files: await read() });
};
}
router.post(
"/clipboard-files",
fileManagerRoute(() => require("../lib/file-manager").clipboardFiles())
);
router.post(
"/selected-files",
fileManagerRoute(() => require("../lib/file-manager").selectedFiles())
);
// Whether a `claude` pane can take an image paste natively (its image-paste
// key — `^V`, Alt+V on Windows: it reads
// this machine's clipboard itself) — only for a browser on this machine, in
// local mode, and only when a clipboard reader is installed and a display is
// reachable. Otherwise the browser uploads the image, which always works.
router.get("/native-paste", (req, res) => {
const local = !require("../lib/auth").isServerMode() && isLocalRequest(req);
// `key`: the chord Claude Code binds to its image paste here — Alt+V on
// Windows (Ctrl+V there is the terminal's text paste), Ctrl+V elsewhere.
res.json({
native: local && require("../lib/desktop-env").canReadClipboard(),
key: process.platform === "win32" ? "alt+v" : "ctrl+v",
});
});
router.get("/history", (req, res) => {
let dr = null;
try {