chore: v2.8.15 — Oh My Pi login sharing, omp --allow-home, Markdown Preview default, first-login password

- omp-share.js: an admin's switch (S3) runs omp's own auth broker + gateway as
  the host on loopback; a member's omp pane reaches it as a scoped client keyed
  by its pane token (clients file = live omp panes only), runs on
  --model agenthub/<model>, never holds a credential.
- omp catalog: --allow-home, so a workspace at the account's home keeps its
  sessions there (they were recorded under a temp dir and dropped).
- Settings → Appearance → Open Markdown as: Preview (default) or Raw, for
  Files/Changes, the config viewer and a chat's file panel.
- First-login password change and password rules, avatar URLs, Artifacts
  "Mine" includes installed shared skills/plugins, a member's own hook pages.
This commit is contained in:
2026-10-22 10:04:30 +07:00
parent 8a6dc6a29e
commit 750d74a2af
66 changed files with 3021 additions and 696 deletions
+40 -10
View File
@@ -8,11 +8,47 @@
* for a member (who can only read their own). An admin's account change on
* the Users page is written here too (`recordAccountChange`), and so is an
* admin's look into another user's home (`recordHomeRead`, File Explorer).
* Entries are kept `AGENTHUB_AUDIT_DAYS` days (default 180; `0` keeps them
* all): older ones are deleted as new ones are written, at most once an hour.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const sql = (q) => require("../db").db.prepare(q);
const DEFAULT_KEEP_DAYS = 180;
const PRUNE_EVERY_MS = 60 * 60 * 1000;
let lastPrune = 0;
/** Days of audit history kept; 0 = forever. */
function keepDays() {
const v = process.env.AGENTHUB_AUDIT_DAYS;
if (v === undefined || v === "") return DEFAULT_KEEP_DAYS;
const n = Number(v);
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : DEFAULT_KEEP_DAYS;
}
/** Deletes entries older than `keepDays()`; returns how many. `force` skips the hourly gate. */
function pruneAudit({ force = false } = {}) {
const days = keepDays();
if (!days || (!force && Date.now() - lastPrune < PRUNE_EVERY_MS)) return 0;
lastPrune = Date.now();
const cutoff = new Date(Date.now() - days * 864e5).toISOString();
try {
return sql("DELETE FROM audit_log WHERE at < ?").run(cutoff).changes;
} catch (err) {
console.warn("[audit] could not prune:", err.message);
return 0;
}
}
/** Writes one entry, then prunes when it is time. */
function insertEntry(userId, ownerId, sessionId, method, path) {
sql(
"INSERT INTO audit_log (user_id, owner_id, session_id, method, path) VALUES (?, ?, ?, ?, ?)"
).run(userId, ownerId, sessionId, method, path);
pruneAudit();
}
// Paths that name one session's content.
const SESSION_PATHS = [
/^\/sessions\/(?!facets\/?$)([^/]+)/i,
@@ -35,9 +71,7 @@ function auditGuard(req, res, next) {
if (owner === u.id) continue;
const ownerRole = sql("SELECT role FROM users WHERE id = ?").get(owner)?.role;
if (ownerRole !== "developer") continue;
sql(
"INSERT INTO audit_log (user_id, owner_id, session_id, method, path) VALUES (?, ?, ?, ?, ?)"
).run(u.id, owner, id, req.method, req.originalUrl.slice(0, 500));
insertEntry(u.id, owner, id, req.method, req.originalUrl.slice(0, 500));
}
} catch (err) {
console.warn("[audit] could not record:", err.message);
@@ -64,20 +98,16 @@ function listAudit(limit = 100) {
/** An admin's account change from the Users page: who, on whom, what (never
* a password). `owner_id` is the account changed; `path` carries the action. */
function recordAccountChange(actorId, targetId, method, action) {
sql(
"INSERT INTO audit_log (user_id, owner_id, session_id, method, path) VALUES (?, ?, NULL, ?, ?)"
).run(actorId, targetId, method, String(action).slice(0, 500));
insertEntry(actorId, targetId, null, method, String(action).slice(0, 500));
}
/** An admin opening another user's home in the File Explorer (read-only). */
function recordHomeRead(actorId, ownerId, method, what) {
try {
sql(
"INSERT INTO audit_log (user_id, owner_id, session_id, method, path) VALUES (?, ?, NULL, ?, ?)"
).run(actorId, ownerId, method, String(what).slice(0, 500));
insertEntry(actorId, ownerId, null, method, String(what).slice(0, 500));
} catch (err) {
console.warn("[audit] could not record:", err.message);
}
}
module.exports = { auditGuard, listAudit, recordAccountChange, recordHomeRead };
module.exports = { auditGuard, listAudit, recordAccountChange, recordHomeRead, pruneAudit };