openmausbot 0.1.84 → 0.1.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/abnfDiagram-O67JEVCF-DpUZNnFD.js +1 -0
- package/dist/assets/arc-DW7mrI4p.js +1 -0
- package/dist/assets/architectureDiagram-NJMV4G6O-CyeZ5WLZ.js +36 -0
- package/dist/assets/blockDiagram-BEXU5L5S-CBVaNjO7.js +140 -0
- package/dist/assets/c4Diagram-YGBWAQC7-sf8d41uT.js +38 -0
- package/dist/assets/channel-BXFkgus9.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-BOGvy7Y_.js +1 -0
- package/dist/assets/chunk-3FUC2YCW-BiF9zyiz.js +2 -0
- package/dist/assets/chunk-5VM5RSS4-EFuZ1SBZ.js +15 -0
- package/dist/assets/chunk-GTNCS2PH-pJLNSAT7.js +88 -0
- package/dist/assets/chunk-GWA4HPMP-Cb-3_3IT.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-DAf5aogn.js +1 -0
- package/dist/assets/chunk-NETBCI7D-DR3kiwZz.js +1 -0
- package/dist/assets/chunk-WEXAMYUT-BmU8PeyM.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-Buh8g8OT.js +1 -0
- package/dist/assets/classDiagram-v2-NBCMYWYE-BurfmMWM.js +217 -0
- package/dist/assets/cose-bilkent-JH36ORCC-KkGZUEkU.js +1 -0
- package/dist/assets/cynefin-OW5HDTMX-C3PpV96d.js +166 -0
- package/dist/assets/cynefinDiagram-VND7K2PF-Dt8JwJaI.js +62 -0
- package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
- package/dist/assets/dagre-6A5THRUB-DkjN8266.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-22UHCM2B-DUAJS0JV.js +200 -0
- package/dist/assets/diagram-3UASUU5V-Dw4KhF1a.js +24 -0
- package/dist/assets/diagram-ATOU4E4O-Dd1gmQqf.js +3 -0
- package/dist/assets/diagram-CDSNMT55-CjwGs0Yw.js +30 -0
- package/dist/assets/diagram-MLGK6HIB-BL-69Zrq.js +24 -0
- package/dist/assets/diagram-MPIPVDR6-5Zi2N7wo.js +41 -0
- package/dist/assets/ebnfDiagram-ZINNZB2B-D9NekWUX.js +1 -0
- package/dist/assets/elk-276RUBZZ-CkaSQjxw.js +27 -0
- package/dist/assets/erDiagram-OPXOYQCR-CdAVBV8-.js +99 -0
- package/dist/assets/flowDiagram-KWPJA3E3-Bm_lWWH7.js +213 -0
- package/dist/assets/ganttDiagram-FUAMR5RP-FZ8ejYWJ.js +292 -0
- package/dist/assets/gitGraphDiagram-X574FWY7-Dlv9rizZ.js +106 -0
- package/dist/assets/graph-D8UmR1vK.js +1 -0
- package/dist/assets/index-BGgqlWPJ.css +1 -0
- package/dist/assets/index-BtHo8ZKl.js +577 -0
- package/dist/assets/{index-DNa2umw-.js → index-h9FFNwTu.js} +1 -1
- package/dist/assets/infoDiagram-VRGFBTTK-BdQ_gW5B.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-OU5B5YK6-EwssEp2X.js +70 -0
- package/dist/assets/journeyDiagram-ZHPQQLJL-BX1G1vOd.js +139 -0
- package/dist/assets/kanban-definition-PNTS6WVX-BB4kAsyC.js +89 -0
- package/dist/assets/linear-3su2TG93.js +1 -0
- package/dist/assets/mermaid.core-CDD1E0s9.js +311 -0
- package/dist/assets/mindmap-definition-NLK3R4M7-Ba50MsNK.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-GJSIUBJH-DZLkkZlM.js +1 -0
- package/dist/assets/pieDiagram-5QR66LMP-DaTz77Uq.js +39 -0
- package/dist/assets/quadrantDiagram-O4NWA36T-CpaiidpR.js +7 -0
- package/dist/assets/railroadDiagram-XR7U4H2S-mN4JrMeI.js +1 -0
- package/dist/assets/requirementDiagram-PLB6GJNP-BSNDRpg7.js +84 -0
- package/dist/assets/sankeyDiagram-IPEJSGJF-FngzGNug.js +40 -0
- package/dist/assets/sequenceDiagram-PO4LG4MO-CM4pCC3L.js +169 -0
- package/dist/assets/sizeCapture-INFHLROL-CRICHXkw.js +1 -0
- package/dist/assets/stateDiagram-v2-GCMORJYK-oyTWpxH2.js +291 -0
- package/dist/assets/swimlanes-2SLR337P-D9h9mPBq.js +1 -0
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +8 -0
- package/dist/assets/timeline-definition-EJHVYXUP-BJUlN9bW.js +120 -0
- package/dist/assets/usecaseDiagram-POWQR4AR-3uBNkbAb.js +370 -0
- package/dist/assets/vennDiagram-UO4OBE2U-D2odalzT.js +34 -0
- package/dist/assets/wardleyDiagram-VNRHLVJA-DHgFaB9S.js +78 -0
- package/dist/assets/xychartDiagram-PMCCYNJV-w5GYM2h0.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/companion/src/routes.js +1 -0
- package/dist-server/container-mcp.js +231 -17
- package/dist-server/drivers/agents-proxy.js +16097 -1411
- package/dist-server/enterprise/LICENSE +32 -0
- package/dist-server/enterprise/server/index.js +6 -5
- package/dist-server/hooks/omb-hook.js +74 -0
- package/dist-server/index.js +12495 -7584
- package/dist-server/local-computer-proxy.js +83 -1
- package/dist-server/local-computer.js +14 -2
- package/dist-server/mcp-server.js +3 -1
- package/dist-server/openmausbot.js +5458 -4587
- package/dist-server/pair-cli.js +5458 -4587
- package/dist-server/proxy-paths.js +1 -0
- package/dist-server/server/admin-activity.js +410 -0
- package/dist-server/server/agent-tool-policy.js +1 -0
- package/dist-server/server/bot-package.js +2 -0
- package/dist-server/server/bot-visibility.js +462 -0
- package/dist-server/server/box.js +25 -5
- package/dist-server/server/browser-engine.js +87 -10
- package/dist-server/server/browser-live.js +7 -5
- package/dist-server/server/browser-runtime.js +66 -11
- package/dist-server/server/channel-queue.js +2 -0
- package/dist-server/server/checkpoints.js +62 -18
- package/dist-server/server/cli-prompts.js +26 -2
- package/dist-server/server/cli.js +79 -10
- package/dist-server/server/commands.js +27 -0
- package/dist-server/server/compaction-summary.js +78 -0
- package/dist-server/server/composio.js +49 -11
- package/dist-server/server/config.js +137 -14
- package/dist-server/server/context-budget.js +29 -0
- package/dist-server/server/context-rebuild.js +72 -0
- package/dist-server/server/decision-log.js +209 -48
- package/dist-server/server/default-model-selection.js +20 -3
- package/dist-server/server/delegations.js +11 -9
- package/dist-server/server/digest.js +130 -0
- package/dist-server/server/drivers/acp/core.js +637 -238
- package/dist-server/server/drivers/acp/grok.js +4 -3
- package/dist-server/server/drivers/agents-call.js +1123 -0
- package/dist-server/server/drivers/agents-catalog.js +659 -0
- package/dist-server/server/drivers/agents-client.js +28 -0
- package/dist-server/server/drivers/agents-proxy.js +18 -1683
- package/dist-server/server/drivers/agents-result.js +23 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +17 -2
- package/dist-server/server/drivers/claude.js +83 -16
- package/dist-server/server/drivers/codex-mcp-names.js +44 -0
- package/dist-server/server/drivers/codex.js +32 -4
- package/dist-server/server/drivers/grok.js +2 -1
- package/dist-server/server/drivers/openai-chat.js +66 -45
- package/dist-server/server/drivers/openai-compat.js +16 -8
- package/dist-server/server/enterprise.js +97 -16
- package/dist-server/server/external-runtime.js +83 -0
- package/dist-server/server/guarded-requests.js +60 -0
- package/dist-server/server/harness/http.js +61 -0
- package/dist-server/server/hooks/omb-hook.js +103 -0
- package/dist-server/server/hosted-models.js +151 -0
- package/dist-server/server/hosted-slack.js +11 -0
- package/dist-server/server/index.js +2810 -678
- package/dist-server/server/local-computer.js +21 -2
- package/dist-server/server/managed-desktop.js +79 -14
- package/dist-server/server/managed-policy.js +153 -0
- package/dist-server/server/mcp-bridge.js +10 -2
- package/dist-server/server/mcp-tool-images.js +37 -0
- package/dist-server/server/mcp-tool-schema.js +108 -0
- package/dist-server/server/message-db.js +108 -21
- package/dist-server/server/message-file.js +3 -2
- package/dist-server/server/model-context-window.js +7 -0
- package/dist-server/server/model-prices.js +121 -0
- package/dist-server/server/notify.js +10 -1
- package/dist-server/server/package-export.js +1 -0
- package/dist-server/server/peer-roster.js +17 -4
- package/dist-server/server/provider-icon.js +20 -0
- package/dist-server/server/proxy-paths.js +1 -0
- package/dist-server/server/recent-work.js +15 -0
- package/dist-server/server/request-auth.js +97 -0
- package/dist-server/server/room-handoffs.js +41 -13
- package/dist-server/server/routes/hosted-slack.js +20 -0
- package/dist-server/server/routes/table.js +14 -0
- package/dist-server/server/routine-requests.js +39 -1
- package/dist-server/server/routines.js +65 -9
- package/dist-server/server/shared-computer-control.js +31 -4
- package/dist-server/server/spend.js +111 -16
- package/dist-server/server/steer-queue.js +12 -1
- package/dist-server/server/store.js +384 -33
- package/dist-server/server/system-prompt.js +4 -4
- package/dist-server/server/team-backup.js +19 -2
- package/dist-server/server/thread-starters.js +53 -0
- package/dist-server/server/tool-results.js +72 -0
- package/dist-server/server/tts/grok.js +74 -0
- package/dist-server/server/tts/index.js +19 -1
- package/dist-server/server/usage-ledger.js +31 -8
- package/dist-server/server/webhooks.js +28 -0
- package/dist-server/server/workspace-backup-policy.js +9 -2
- package/dist-server/server/workspace-backup.js +7 -4
- package/dist-server/shared/approval-mode.js +7 -1
- package/dist-server/shared/digest.js +1 -0
- package/dist-server/shared/markdown-windows-paths.js +36 -0
- package/dist-server/shared/mention-boundary.js +15 -0
- package/dist-server/shared/provider-icon.js +110 -0
- package/dist-server/shared/routines.js +7 -1
- package/dist-server/shared/team-backup.js +5 -0
- package/dist-server/src/lib/composer-attachments.js +16 -0
- package/dist-server/vps-container-mcp.js +236 -22
- package/enterprise/README.md +33 -12
- package/enterprise/server/index.js +6 -5
- package/package.json +1 -1
- package/dist/assets/index-BbU5REzd.js +0 -310
- package/dist/assets/index-CLGfYlx_.css +0 -1
|
@@ -18,6 +18,7 @@ var SPAWNED_PROXIES = {
|
|
|
18
18
|
connectors: resolveProxy("connector-proxy"),
|
|
19
19
|
mcpGate: resolveProxy("mcp-gate"),
|
|
20
20
|
phone: resolveProxy("drivers/phone-proxy"),
|
|
21
|
+
hook: resolveProxy("hooks/omb-hook"),
|
|
21
22
|
// Loaded by the external `pi` process via `-e`, not by this server — but
|
|
22
23
|
// resolved through the same single source of truth so the packaged layout
|
|
23
24
|
// check can assert it ships.
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// The admin activity log: who changed what on this server — settings,
|
|
2
|
+
// people, sessions, webhooks, MCP servers, engines, bots, budgets and bot
|
|
3
|
+
// visibility — written beside the decision log and kept as long.
|
|
4
|
+
//
|
|
5
|
+
// One file per UTC month under <data>/admin-activity/YYYY-MM.ndjson, 0600,
|
|
6
|
+
// pruned by the decision log's retention window (decision-log.ts
|
|
7
|
+
// `decisions.retentionDays` / OMB_DECISION_RETENTION_DAYS), so one setting
|
|
8
|
+
// keeps both. Each row names who acted (a signed-in session, the owner on
|
|
9
|
+
// this machine, a local service, or the command line), what changed (the
|
|
10
|
+
// setting paths), and the values before and after — redacted: a value under
|
|
11
|
+
// a key that names a credential, and every value in a headers or environment
|
|
12
|
+
// map, is written as "[hidden]", and every other string goes through the
|
|
13
|
+
// same content redaction the decision log uses. Nothing here is sent
|
|
14
|
+
// anywhere; the organisation's cloud Admin keeps its own log.
|
|
15
|
+
//
|
|
16
|
+
// Fire-and-forget, like the decision log: a full disk must not undo a
|
|
17
|
+
// setting that already took effect.
|
|
18
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
19
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
20
|
+
import { basename, join } from "node:path";
|
|
21
|
+
import { actorLabel, boundRetentionDays, pruneMonthFiles } from "./decision-log.js";
|
|
22
|
+
import { redactSecrets } from "./redact.js";
|
|
23
|
+
import { csvCell } from "./usage-ledger.js";
|
|
24
|
+
export const ADMIN_ACTIVITY_CATEGORIES = ["config", "people", "session", "webhook", "mcp", "engine", "bot", "budget", "visibility"];
|
|
25
|
+
const DIR = "admin-activity";
|
|
26
|
+
const MONTH_FILE = /^(\d{4})-(\d{2})\.ndjson$/;
|
|
27
|
+
const PRUNE_EVERY_MS = 60 * 60_000;
|
|
28
|
+
const writeQueues = new Map();
|
|
29
|
+
const lastPrune = new Map();
|
|
30
|
+
function monthKey(at) {
|
|
31
|
+
return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
32
|
+
}
|
|
33
|
+
export function adminActivityFileFor(dataDir, at) {
|
|
34
|
+
return join(dataDir, DIR, `${monthKey(at)}.ndjson`);
|
|
35
|
+
}
|
|
36
|
+
async function writeRow(dataDir, row) {
|
|
37
|
+
const at = new Date(row.at);
|
|
38
|
+
await mkdir(join(dataDir, DIR), { recursive: true, mode: 0o700 });
|
|
39
|
+
await appendFile(adminActivityFileFor(dataDir, at), JSON.stringify(row) + "\n", { mode: 0o600 });
|
|
40
|
+
if (at.getTime() - (lastPrune.get(dataDir) ?? 0) >= PRUNE_EVERY_MS) {
|
|
41
|
+
lastPrune.set(dataDir, at.getTime());
|
|
42
|
+
await pruneMonthFiles(join(dataDir, DIR), boundRetentionDays(), at);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Append one row. Its values are redacted here, at the moment of writing. */
|
|
46
|
+
export function appendAdminAction(dataDir, row) {
|
|
47
|
+
const record = {
|
|
48
|
+
...row,
|
|
49
|
+
at: row.at ?? new Date().toISOString(),
|
|
50
|
+
...(row.before ? { before: auditValues(row.before) } : {}),
|
|
51
|
+
...(row.after ? { after: auditValues(row.after) } : {}),
|
|
52
|
+
};
|
|
53
|
+
const previous = writeQueues.get(dataDir) ?? Promise.resolve();
|
|
54
|
+
const queued = previous.then(() => writeRow(dataDir, record)).catch(() => {
|
|
55
|
+
/* an audit log must never take down the change it records */
|
|
56
|
+
});
|
|
57
|
+
writeQueues.set(dataDir, queued);
|
|
58
|
+
void queued.finally(() => {
|
|
59
|
+
if (writeQueues.get(dataDir) === queued)
|
|
60
|
+
writeQueues.delete(dataDir);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Delete the months the retention window no longer covers. Never throws.
|
|
64
|
+
* Writing prunes too; the server also runs this on a timer for quiet days. */
|
|
65
|
+
export function pruneAdminActivity(dataDir, days = boundRetentionDays(), now = new Date()) {
|
|
66
|
+
return pruneMonthFiles(join(dataDir, DIR), days, now);
|
|
67
|
+
}
|
|
68
|
+
/** Test/shutdown seam: wait for every queued row to reach disk. */
|
|
69
|
+
export async function flushAdminActivity(dataDir) {
|
|
70
|
+
await writeQueues.get(dataDir);
|
|
71
|
+
}
|
|
72
|
+
// ── redaction ───────────────────────────────────────────────────────────
|
|
73
|
+
/** Name parts that mark a credential (case-insensitive substring), as in
|
|
74
|
+
* redact.ts, plus `key` standing alone or as a suffix (apiKey, xai_key). */
|
|
75
|
+
const SECRET_PARTS = ["token", "secret", "password", "passwd", "passphrase", "credential", "authorization", "cookie", "bearer", "apikey", "api_key"];
|
|
76
|
+
/** Maps whose every value may be a credential: keep the names, hide values. */
|
|
77
|
+
const OPAQUE_MAP = /^(?:headers|env|environment)$/i;
|
|
78
|
+
const MAX_VALUE_CHARS = 2_000;
|
|
79
|
+
const HIDDEN = "[hidden]";
|
|
80
|
+
function isSecretName(name) {
|
|
81
|
+
const lower = name.toLowerCase();
|
|
82
|
+
// Counts, not credentials: maxTokens, contextTokens.
|
|
83
|
+
if (lower.endsWith("tokens"))
|
|
84
|
+
return false;
|
|
85
|
+
if (SECRET_PARTS.some((part) => lower.includes(part)))
|
|
86
|
+
return true;
|
|
87
|
+
return /(?:^|[_.-])keys?$/.test(lower) || /[a-z]Keys?$/.test(name);
|
|
88
|
+
}
|
|
89
|
+
/** A command-line flag or URL parameter that names a credential:
|
|
90
|
+
* `--api-key`, `--token=…`, `?key=`, `&access_token=`, `?sig=`. */
|
|
91
|
+
function isSecretParameter(name) {
|
|
92
|
+
// `-k` is the usual short form of --key (curl-style CLIs and many MCP servers).
|
|
93
|
+
if (name === "-k" || name === "-K")
|
|
94
|
+
return true;
|
|
95
|
+
const bare = name.replace(/^-+/, "");
|
|
96
|
+
return isSecretName(bare) || /^(?:auth|sig|signature|pass|pwd|code)$/i.test(bare);
|
|
97
|
+
}
|
|
98
|
+
/** A path segment that looks like a key rather than a name: it holds a run
|
|
99
|
+
* of 16 or more letters and digits, mixed (Zapier-style `/s/<key>/sse`, a
|
|
100
|
+
* Slack hook's last part) — a readable slug like `getting-started-2024` or a
|
|
101
|
+
* UUID does not. */
|
|
102
|
+
function keyLikeSegment(segment) {
|
|
103
|
+
return (segment.match(/[A-Za-z0-9]{16,}/g) ?? []).some((run) => /\d/.test(run) && /[A-Za-z]/.test(run));
|
|
104
|
+
}
|
|
105
|
+
/** In a URL: credentials before the host (`user:pass@`, or a bare token
|
|
106
|
+
* `TOKEN@`), key-like path segments, and query or fragment values whose
|
|
107
|
+
* names mark a credential. */
|
|
108
|
+
function maskUrlSecrets(text) {
|
|
109
|
+
return text.replace(/\b([a-z][\w+.-]*:\/\/)([^\s/?#]*)([^\s?#]*)([^\s]*)/gi, (_all, scheme, authority, path, rest) => {
|
|
110
|
+
const at = authority.lastIndexOf("@");
|
|
111
|
+
const host = at >= 0 ? `${HIDDEN}@${authority.slice(at + 1)}` : authority;
|
|
112
|
+
const maskedPath = path.split("/").map((segment) => (keyLikeSegment(segment) ? HIDDEN : segment)).join("/");
|
|
113
|
+
const maskedRest = rest.replace(/([?&;#])([^=&#\s]+)=([^&#\s]+)/g, (all, separator, name) => (isSecretParameter(name) ? `${separator}${name}=${HIDDEN}` : all));
|
|
114
|
+
return `${scheme}${host}${maskedPath}${maskedRest}`;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** An argument list: the value after a credential flag, or after its `=`. */
|
|
118
|
+
function maskArgs(items) {
|
|
119
|
+
return items.map((item, index) => {
|
|
120
|
+
const previous = items[index - 1];
|
|
121
|
+
if (typeof item !== "string")
|
|
122
|
+
return item;
|
|
123
|
+
if (typeof previous === "string" && /^--?[\w-]+$/.test(previous) && isSecretParameter(previous))
|
|
124
|
+
return HIDDEN;
|
|
125
|
+
const inline = /^(--?[\w-]+)=(.+)$/.exec(item);
|
|
126
|
+
return inline && isSecretParameter(inline[1]) ? `${inline[1]}=${HIDDEN}` : item;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function scrub(value, depth, hideAll) {
|
|
130
|
+
if (value === undefined || value === null || value === "")
|
|
131
|
+
return value;
|
|
132
|
+
if (hideAll && typeof value !== "object")
|
|
133
|
+
return HIDDEN;
|
|
134
|
+
if (typeof value === "string")
|
|
135
|
+
return redactSecrets(maskUrlSecrets(value));
|
|
136
|
+
if (typeof value !== "object")
|
|
137
|
+
return value;
|
|
138
|
+
if (depth > 6)
|
|
139
|
+
return "[…]";
|
|
140
|
+
if (Array.isArray(value))
|
|
141
|
+
return maskArgs(value.slice(0, 100)).map((item) => scrub(item, depth + 1, hideAll));
|
|
142
|
+
const out = {};
|
|
143
|
+
for (const [key, child] of Object.entries(value)) {
|
|
144
|
+
if (isSecretName(key) && child !== undefined && child !== null && child !== "")
|
|
145
|
+
out[key] = HIDDEN;
|
|
146
|
+
else
|
|
147
|
+
out[key] = scrub(child, depth + 1, hideAll || OPAQUE_MAP.test(key));
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/** A path's last segment decides, too: `anthropic.key` is a key. A value
|
|
152
|
+
* too large to be useful in an audit row is summarised, not stored. */
|
|
153
|
+
export function auditValues(values) {
|
|
154
|
+
const out = {};
|
|
155
|
+
for (const [path, value] of Object.entries(values)) {
|
|
156
|
+
const segments = path.split(".");
|
|
157
|
+
const last = segments.at(-1) ?? path;
|
|
158
|
+
const opaque = segments.slice(0, -1).some((segment) => OPAQUE_MAP.test(segment));
|
|
159
|
+
const scrubbed = (isSecretName(last) || opaque) && value !== undefined && value !== null && value !== ""
|
|
160
|
+
? HIDDEN
|
|
161
|
+
: scrub(value, 0, OPAQUE_MAP.test(last));
|
|
162
|
+
const text = JSON.stringify(scrubbed);
|
|
163
|
+
out[path] = text !== undefined && text.length > MAX_VALUE_CHARS ? "[large value]" : scrubbed ?? null;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
// ── diffs ───────────────────────────────────────────────────────────────
|
|
168
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
169
|
+
/** Key-order-insensitive JSON, for comparing two readings of one setting. */
|
|
170
|
+
function stable(value) {
|
|
171
|
+
if (Array.isArray(value))
|
|
172
|
+
return `[${value.map(stable).join(",")}]`;
|
|
173
|
+
if (isPlainObject(value))
|
|
174
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stable(value[key])}`).join(",")}}`;
|
|
175
|
+
return JSON.stringify(value) ?? "undefined";
|
|
176
|
+
}
|
|
177
|
+
/** Dot paths (up to `depth` segments) whose values differ. A section that
|
|
178
|
+
* appears or disappears is followed into, so a new key reads as the key. */
|
|
179
|
+
export function changedPaths(before, after, depth = 3, prefix = "") {
|
|
180
|
+
if (stable(before) === stable(after))
|
|
181
|
+
return [];
|
|
182
|
+
if (before === undefined && isPlainObject(after))
|
|
183
|
+
before = {};
|
|
184
|
+
if (after === undefined && isPlainObject(before))
|
|
185
|
+
after = {};
|
|
186
|
+
if (depth > 0 && isPlainObject(before) && isPlainObject(after)) {
|
|
187
|
+
const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();
|
|
188
|
+
return keys.flatMap((key) => changedPaths(before[key], after[key], depth - 1, prefix ? `${prefix}.${key}` : key));
|
|
189
|
+
}
|
|
190
|
+
return [prefix || "(all)"];
|
|
191
|
+
}
|
|
192
|
+
export function valueAt(source, path) {
|
|
193
|
+
let value = source;
|
|
194
|
+
for (const segment of path.split(".")) {
|
|
195
|
+
if (!isPlainObject(value))
|
|
196
|
+
return undefined;
|
|
197
|
+
value = value[segment];
|
|
198
|
+
}
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
const pick = (source, paths) => Object.fromEntries(paths.map((path) => [path, valueAt(source, path) ?? null]));
|
|
202
|
+
/** Which log category a top-level config.json key belongs to. */
|
|
203
|
+
const CONFIG_CATEGORY = {
|
|
204
|
+
signIn: "people",
|
|
205
|
+
budgets: "budget",
|
|
206
|
+
billing: "budget",
|
|
207
|
+
mcpServers: "mcp",
|
|
208
|
+
instances: "engine",
|
|
209
|
+
anthropic: "engine",
|
|
210
|
+
openaiCompat: "engine",
|
|
211
|
+
xai: "engine",
|
|
212
|
+
opencodeGo: "engine",
|
|
213
|
+
defaultModelSelection: "engine",
|
|
214
|
+
};
|
|
215
|
+
/** The rows one change to config.json makes: one per category it touched,
|
|
216
|
+
* each naming the paths that changed and their redacted values. */
|
|
217
|
+
export function configChangeRows(before, after) {
|
|
218
|
+
const paths = changedPaths(isPlainObject(before) ? before : {}, isPlainObject(after) ? after : {});
|
|
219
|
+
const byCategory = new Map();
|
|
220
|
+
for (const path of paths) {
|
|
221
|
+
const category = CONFIG_CATEGORY[path.split(".")[0]] ?? "config";
|
|
222
|
+
byCategory.set(category, [...(byCategory.get(category) ?? []), path]);
|
|
223
|
+
}
|
|
224
|
+
return [...byCategory].map(([category, changed]) => ({
|
|
225
|
+
category,
|
|
226
|
+
action: `${category}.update`,
|
|
227
|
+
target: { kind: "settings" },
|
|
228
|
+
changed,
|
|
229
|
+
before: pick(before, changed),
|
|
230
|
+
after: pick(after, changed),
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
/** The fields of a bot an audit follows: what it may do, where, and who may
|
|
234
|
+
* see it. Display fields (colour, unread, pins) are not audited. */
|
|
235
|
+
export const BOT_AUDIT_FIELDS = [
|
|
236
|
+
"approvalMode", "autoApprove", "alwaysAllow", "peers", "approvePeerComms", "computer", "cloudBackend", "autoStartVps",
|
|
237
|
+
"cwd", "composio", "browser", "browserProfile", "mcpServers", "chiefOfStaff", "managedSections", "section",
|
|
238
|
+
"parkDirectMessages", "hidden", "modelSelection", "visibility",
|
|
239
|
+
];
|
|
240
|
+
export function botAuditSnapshot(bot) {
|
|
241
|
+
if (!bot)
|
|
242
|
+
return null;
|
|
243
|
+
// "everyone" and no field are one audience.
|
|
244
|
+
const present = (field) => bot[field] !== undefined && !(field === "visibility" && bot[field] === "everyone");
|
|
245
|
+
return Object.fromEntries(BOT_AUDIT_FIELDS.filter(present).map((field) => [field, structuredClone(bot[field])]));
|
|
246
|
+
}
|
|
247
|
+
/** A bot's audited fields that changed: a `visibility.update` row when its
|
|
248
|
+
* audience changed, a `bot.update` row for everything else. `only` limits
|
|
249
|
+
* the comparison to the fields a request named. */
|
|
250
|
+
export function botChangeRows(target, before, after, only) {
|
|
251
|
+
if (!before || !after)
|
|
252
|
+
return [];
|
|
253
|
+
const changed = BOT_AUDIT_FIELDS.filter((field) => (!only || only.includes(field)) && stable(before[field]) !== stable(after[field]));
|
|
254
|
+
const rows = [];
|
|
255
|
+
const bot = { kind: "bot", id: target.id, ...(target.name ? { name: target.name } : {}) };
|
|
256
|
+
if (changed.includes("visibility")) {
|
|
257
|
+
// An absent audience is the default: everyone.
|
|
258
|
+
const audience = (value) => value ?? "everyone";
|
|
259
|
+
rows.push({
|
|
260
|
+
category: "visibility", action: "visibility.update", target: bot, changed: ["visibility"],
|
|
261
|
+
before: { visibility: audience(before.visibility) }, after: { visibility: audience(after.visibility) },
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const rest = changed.filter((field) => field !== "visibility");
|
|
265
|
+
if (rest.length)
|
|
266
|
+
rows.push({ category: "bot", action: "bot.update", target: bot, changed: rest, before: pick(before, rest), after: pick(after, rest) });
|
|
267
|
+
return rows;
|
|
268
|
+
}
|
|
269
|
+
/** An email sign-in list that lets more than one person in: any member, a
|
|
270
|
+
* second admin, or a whole @domain. */
|
|
271
|
+
export function sharedSignIn(lists) {
|
|
272
|
+
return lists.members.length > 0 || lists.admins.length > 1 || [...lists.admins, ...lists.members].some((entry) => entry.trim().startsWith("@"));
|
|
273
|
+
}
|
|
274
|
+
/** The sign-in lists in a config.json reading. */
|
|
275
|
+
export function signInListsOf(config) {
|
|
276
|
+
const signIn = isPlainObject(config) && isPlainObject(config.signIn) ? config.signIn : {};
|
|
277
|
+
const list = (value) => (Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : []);
|
|
278
|
+
return { admins: list(signIn.admins), members: list(signIn.members) };
|
|
279
|
+
}
|
|
280
|
+
// ── reading ─────────────────────────────────────────────────────────────
|
|
281
|
+
const isRow = (value) => isPlainObject(value) &&
|
|
282
|
+
typeof value.at === "string" &&
|
|
283
|
+
typeof value.category === "string" &&
|
|
284
|
+
typeof value.action === "string" &&
|
|
285
|
+
isPlainObject(value.actor);
|
|
286
|
+
function readRows(path) {
|
|
287
|
+
let text;
|
|
288
|
+
try {
|
|
289
|
+
text = readFileSync(path, "utf8");
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
const rows = [];
|
|
295
|
+
for (const line of text.split("\n")) {
|
|
296
|
+
if (!line)
|
|
297
|
+
continue;
|
|
298
|
+
try {
|
|
299
|
+
const value = JSON.parse(line);
|
|
300
|
+
if (isRow(value))
|
|
301
|
+
rows.push(value);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
/* a line torn mid-write */
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return rows;
|
|
308
|
+
}
|
|
309
|
+
/** Every row inside the range, oldest first. */
|
|
310
|
+
export function readAdminActivityRange(dataDir, range) {
|
|
311
|
+
let months = [];
|
|
312
|
+
try {
|
|
313
|
+
months = readdirSync(join(dataDir, DIR)).filter((name) => MONTH_FILE.test(name)).sort();
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
const first = monthKey(range.from);
|
|
319
|
+
const last = monthKey(range.to);
|
|
320
|
+
const from = range.from.getTime();
|
|
321
|
+
const to = range.to.getTime();
|
|
322
|
+
const rows = [];
|
|
323
|
+
for (const name of months) {
|
|
324
|
+
const month = basename(name, ".ndjson");
|
|
325
|
+
if (month < first || month > last)
|
|
326
|
+
continue;
|
|
327
|
+
for (const row of readRows(join(dataDir, DIR, name))) {
|
|
328
|
+
const at = Date.parse(row.at);
|
|
329
|
+
if (at >= from && at <= to)
|
|
330
|
+
rows.push(row);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return rows;
|
|
334
|
+
}
|
|
335
|
+
// ── the Activity view: admin actions and approvals together ─────────────
|
|
336
|
+
export function adminActorLabel(actor) {
|
|
337
|
+
if (actor?.kind === "cli")
|
|
338
|
+
return "Command line";
|
|
339
|
+
return actorLabel(actor);
|
|
340
|
+
}
|
|
341
|
+
export function parseActivityWhat(value) {
|
|
342
|
+
if (!value)
|
|
343
|
+
return "all";
|
|
344
|
+
if (value === "all" || value === "approvals" || value === "decisions")
|
|
345
|
+
return value;
|
|
346
|
+
return ADMIN_ACTIVITY_CATEGORIES.includes(value) ? value : null;
|
|
347
|
+
}
|
|
348
|
+
/** Both logs as one list, newest first, narrowed by who (a case-insensitive
|
|
349
|
+
* part of the name, email or label shown) and what. */
|
|
350
|
+
export function activityEntries(decisions, actions, filter) {
|
|
351
|
+
const who = filter.who?.trim().toLowerCase() ?? "";
|
|
352
|
+
const entries = [];
|
|
353
|
+
const wantDecision = (row) => filter.what === "decisions" || ((filter.what === "all" || filter.what === "approvals") && row.source === "user");
|
|
354
|
+
for (const row of decisions) {
|
|
355
|
+
if (!wantDecision(row))
|
|
356
|
+
continue;
|
|
357
|
+
const clean = redactSecrets(row);
|
|
358
|
+
entries.push({
|
|
359
|
+
type: "approval",
|
|
360
|
+
at: clean.at,
|
|
361
|
+
who: clean.actor ? actorLabel(clean.actor) : "",
|
|
362
|
+
what: clean.decision,
|
|
363
|
+
source: clean.source,
|
|
364
|
+
...(clean.botName || clean.botId ? { bot: clean.botName || clean.botId } : {}),
|
|
365
|
+
...(clean.tool ? { tool: clean.tool } : {}),
|
|
366
|
+
...(clean.summary ? { summary: clean.summary } : {}),
|
|
367
|
+
threadId: clean.threadId,
|
|
368
|
+
...(clean.requestId ? { requestId: clean.requestId } : {}),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (filter.what !== "approvals" && filter.what !== "decisions") {
|
|
372
|
+
for (const row of actions) {
|
|
373
|
+
if (filter.what !== "all" && row.category !== filter.what)
|
|
374
|
+
continue;
|
|
375
|
+
entries.push({
|
|
376
|
+
type: "admin",
|
|
377
|
+
at: row.at,
|
|
378
|
+
who: adminActorLabel(row.actor),
|
|
379
|
+
what: row.category,
|
|
380
|
+
action: row.action,
|
|
381
|
+
...(row.target ? { target: row.target } : {}),
|
|
382
|
+
...(row.changed ? { changed: row.changed } : {}),
|
|
383
|
+
...(row.before ? { before: row.before } : {}),
|
|
384
|
+
...(row.after ? { after: row.after } : {}),
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const matched = who ? entries.filter((entry) => entry.who.toLowerCase().includes(who)) : entries;
|
|
389
|
+
return matched.sort((a, b) => (a.at < b.at ? 1 : a.at > b.at ? -1 : 0));
|
|
390
|
+
}
|
|
391
|
+
/** One line per entry. Cells go through the usage CSV's formula
|
|
392
|
+
* neutralising; values were redacted when the rows were written. */
|
|
393
|
+
export function activityCsv(entries) {
|
|
394
|
+
const header = ["time", "type", "who", "what", "action", "target", "changed", "before", "after", "bot", "tool", "summary", "thread"];
|
|
395
|
+
const lines = [header.join(",")];
|
|
396
|
+
for (const entry of entries) {
|
|
397
|
+
const cells = entry.type === "approval"
|
|
398
|
+
? [entry.at, "approval", entry.who, entry.what, entry.source, "", "", "", "", entry.bot ?? "", entry.tool ?? "", entry.summary ?? "", entry.threadId]
|
|
399
|
+
: [
|
|
400
|
+
entry.at, "admin", entry.who, entry.what, entry.action,
|
|
401
|
+
entry.target ? entry.target.name ?? entry.target.id ?? entry.target.kind : "",
|
|
402
|
+
(entry.changed ?? []).join(" "),
|
|
403
|
+
entry.before ? JSON.stringify(entry.before) : "",
|
|
404
|
+
entry.after ? JSON.stringify(entry.after) : "",
|
|
405
|
+
"", "", "", "",
|
|
406
|
+
];
|
|
407
|
+
lines.push(cells.map(csvCell).join(","));
|
|
408
|
+
}
|
|
409
|
+
return lines.join("\n") + "\n";
|
|
410
|
+
}
|
|
@@ -170,6 +170,7 @@ const packageSchema = z.object({
|
|
|
170
170
|
schedule: packageRoutineScheduleSchema,
|
|
171
171
|
durationMinutes: z.number().int().min(5).max(240),
|
|
172
172
|
timeoutMinutes: z.number().int().min(5).max(240).optional(),
|
|
173
|
+
overlap: z.enum(["skip", "queue"]).optional(),
|
|
173
174
|
enabledAfterInstall: z.literal(false),
|
|
174
175
|
})).max(50).optional(),
|
|
175
176
|
playbooks: z.array(z.object({
|
|
@@ -339,6 +340,7 @@ export function renderBotPackageMarkdown(document) {
|
|
|
339
340
|
? intervalScheduleText(routine.schedule)
|
|
340
341
|
: `once at ${routine.schedule.at}`} `,
|
|
341
342
|
`**Run limit:** ${routine.timeoutMinutes === undefined ? "none" : `${routine.timeoutMinutes} minutes`} `,
|
|
343
|
+
`**While busy:** ${routine.overlap === "queue" ? "queue one scheduled run" : "skip scheduled occurrences"} `,
|
|
342
344
|
"**Initial state:** paused — the user must enable it",
|
|
343
345
|
"",
|
|
344
346
|
routine.prompt,
|