chore: v2.8.71 — remote clients (company hub), artifacts shared with chosen people, folders and viewable copies, fixes
Remote clients (docs/plans/remote-clients/README.md, B1–B3 for the Claude pool): - server/hub/ holds the hub-only code (devices, remote sessions, upload ingest, LAN gateway), required only in server mode; hub-boundary.test.js keeps local code from importing it. - A local install enrolls with `agenthub remote login` or Settings → AI tools → Company hub; the "Company account" AI Proxy (reserved name agenthub.company) sends a session through the hub's gateway, which adds a pool account's token — no credential reaches the machine. Only those sessions are uploaded, hourly and at their end; secret-shaped strings are redacted on the client. - Gateway refuses: unknown device (401), a message without a session id, an unregistered session under AGENTHUB_REMOTE_GATE=block, a user over quota (EQUOTA), a purged session (410). Device tokens 90 days, conversations 30. - New alert rule type remote_unmonitored: a company-account session its device never registered; a late registration clears the flag instead. - The hub imports a session once per upload round (chunks carry `more`), not once per chunk. Artifacts: - Share an artifact with chosen people (artifact_shares, PATCH sharedWith, GET /api/artifacts/people) besides everyone; a "Shared with you" tab. Only the owner or an admin sees or edits who it is shared with; audited. - Folders: each account's own arrangement (agenthub.artifacts.folders pref). - A claude.ai page keeps a copy viewable without the publishing account: its HTML file (and supporting files) snapshotted at publish, or the copy Claude saves for a Doc / Slides / Design in artifacts-export/ (a synchronous PostToolUse hint hook, scripts/artifact-export-hint.js, admin switch), attached to the page at Stop. Viewed in HtmlFrame's sandbox, Markdown, or pdf.js (pdfjs-dist 6.4.299, legacy build, loaded only for a PDF). Fixes: - tmux: a cached /proc/<pid>/exe of an exited server falls back to PATH's tmux (stop the last pane, start again within 10 s failed with ENOENT). - shelves: a shelf restores beside changes in other files. - chat: a finished reply ends the turn even with no hook at all. - Settings → AI tools: profile list display tweaks.
This commit is contained in:
+16
-11
@@ -10,7 +10,9 @@
|
||||
* not booked twice; Codex has no hourly rows, so its session counts whole
|
||||
* once a run of the account started in the window, as before. With `ownerId`
|
||||
* only the runs of that user's workspaces count — one user's share of the
|
||||
* account, for the Administration page's user detail.
|
||||
* account, for the Administration page's user detail. A remote client's
|
||||
* company-account session (`remote_sessions`, server/hub/) holds its account
|
||||
* like a run does, from its registration to its end.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -34,25 +36,28 @@ function accountUsage(
|
||||
const since7d = new Date(now - WINDOWS["7d"] * 3600e3).toISOString();
|
||||
const owned = ownerId == null ? "" : "AND lane_id IN (SELECT id FROM lanes WHERE owner_id = ?)";
|
||||
const ownedArgs = ownerId == null ? [] : [ownerId];
|
||||
const remoteOwned = ownerId == null ? "" : "AND user_id = ?";
|
||||
// Every holder of the account: the dashboard's runs, then remote clients'
|
||||
// sessions (a run's columns under the same names).
|
||||
const HOLDERS = `SELECT session_id, started_at, ended_at FROM dashboard_runs
|
||||
WHERE tool = ? AND account = ? AND session_id IS NOT NULL
|
||||
AND COALESCE(ended_at, '9999') >= ? ${owned}
|
||||
UNION ALL
|
||||
SELECT session_id, registered_at, ended_at FROM remote_sessions
|
||||
WHERE tool = ? AND account = ? AND COALESCE(ended_at, '9999') >= ? ${remoteOwned}`;
|
||||
const holderArgs = [tool, account, since7d, ...ownedArgs, tool, account, since7d, ...ownedArgs];
|
||||
const runs = db
|
||||
.prepare(
|
||||
`SELECT session_id, substr(started_at, 1, 13) AS start_hour, started_at,
|
||||
substr(COALESCE(ended_at, '9999'), 1, 13) AS end_hour
|
||||
FROM dashboard_runs
|
||||
WHERE tool = ? AND account = ? AND session_id IS NOT NULL
|
||||
AND COALESCE(ended_at, '9999') >= ? ${owned}`
|
||||
FROM (${HOLDERS})`
|
||||
)
|
||||
.all(tool, account, since7d, ...ownedArgs);
|
||||
.all(...holderArgs);
|
||||
const out = { spend5h: 0, spend7d: 0, tokens5h: 0, tokens7d: 0 };
|
||||
if (runs.length === 0) return out;
|
||||
const runsOf = new Map();
|
||||
for (const r of runs) runsOf.set(r.session_id, [...(runsOf.get(r.session_id) || []), r]);
|
||||
const rows = hourlyTokenRows(
|
||||
`WHERE s.id IN (SELECT session_id FROM dashboard_runs
|
||||
WHERE tool = ? AND account = ? AND session_id IS NOT NULL
|
||||
AND COALESCE(ended_at, '9999') >= ? ${owned})`,
|
||||
[tool, account, since7d, ...ownedArgs]
|
||||
);
|
||||
const rows = hourlyTokenRows(`WHERE s.id IN (SELECT session_id FROM (${HOLDERS}))`, holderArgs);
|
||||
const rules = stmts.listPricing.all();
|
||||
for (const [label, hours] of Object.entries(WINDOWS)) {
|
||||
const sinceHour = hourAgo(hours, now);
|
||||
|
||||
Reference in New Issue
Block a user