openmausbot 0.1.85 → 0.1.87
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/README.md +2 -2
- 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-BTiuL79F.js +1 -0
- package/dist/assets/arc-C88Blc2v.js +1 -0
- package/dist/assets/architectureDiagram-NJMV4G6O-BZsJIZrb.js +36 -0
- package/dist/assets/blockDiagram-BEXU5L5S-CVeMe9Ah.js +140 -0
- package/dist/assets/c4Diagram-YGBWAQC7-Cedk3Bhx.js +38 -0
- package/dist/assets/channel-taCNRlUU.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-D-VRqzTB.js +1 -0
- package/dist/assets/chunk-3FUC2YCW-BChov01Y.js +2 -0
- package/dist/assets/chunk-5VM5RSS4-C-TfwvHy.js +15 -0
- package/dist/assets/chunk-GTNCS2PH-Db7CGj5Y.js +88 -0
- package/dist/assets/chunk-GWA4HPMP-CNvIfqKT.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-C_bEp5tq.js +1 -0
- package/dist/assets/chunk-NETBCI7D-Coi4BOls.js +1 -0
- package/dist/assets/chunk-WEXAMYUT-CzylEuL3.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-m7p7ztaD.js +1 -0
- package/dist/assets/classDiagram-v2-NBCMYWYE-BMy6o9x2.js +217 -0
- package/dist/assets/cose-bilkent-JH36ORCC-BDQ__0_J.js +1 -0
- package/dist/assets/cynefin-OW5HDTMX-Djv4ghUb.js +166 -0
- package/dist/assets/cynefinDiagram-VND7K2PF-BULrlGYH.js +62 -0
- package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
- package/dist/assets/dagre-6A5THRUB-D-jrT_hU.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-22UHCM2B-p2UhQmfL.js +200 -0
- package/dist/assets/diagram-3UASUU5V-DHvGWZ7q.js +24 -0
- package/dist/assets/diagram-ATOU4E4O-ChhKDot-.js +3 -0
- package/dist/assets/diagram-CDSNMT55-B-T7A43R.js +30 -0
- package/dist/assets/diagram-MLGK6HIB-CJm8ENNY.js +24 -0
- package/dist/assets/diagram-MPIPVDR6-510tlza8.js +41 -0
- package/dist/assets/ebnfDiagram-ZINNZB2B-PkvRPHel.js +1 -0
- package/dist/assets/elk-276RUBZZ-BVBVPoX3.js +27 -0
- package/dist/assets/erDiagram-OPXOYQCR-BgYMVm7L.js +99 -0
- package/dist/assets/flowDiagram-KWPJA3E3-BGqsnc_3.js +213 -0
- package/dist/assets/ganttDiagram-FUAMR5RP-D_-UeReH.js +292 -0
- package/dist/assets/gitGraphDiagram-X574FWY7-D0uooONx.js +106 -0
- package/dist/assets/graph-D8UmR1vK.js +1 -0
- package/dist/assets/index-BwdMAQ6p.css +1 -0
- package/dist/assets/index-ByJennr7.js +581 -0
- package/dist/assets/{index-CTNNoqSJ.js → index-zRsJERGJ.js} +1 -1
- package/dist/assets/infoDiagram-VRGFBTTK-B9e1z77f.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-OU5B5YK6-BB4vNwvD.js +70 -0
- package/dist/assets/journeyDiagram-ZHPQQLJL-DpkosOTB.js +139 -0
- package/dist/assets/kanban-definition-PNTS6WVX-DbDP4kUu.js +89 -0
- package/dist/assets/linear-DBJGPmjT.js +1 -0
- package/dist/assets/mermaid.core-DWPRi-ue.js +311 -0
- package/dist/assets/mindmap-definition-NLK3R4M7-nszBbJmx.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-GJSIUBJH-CWykJWu_.js +1 -0
- package/dist/assets/pieDiagram-5QR66LMP-g3Da-Vnv.js +39 -0
- package/dist/assets/quadrantDiagram-O4NWA36T-Wzrlpzgh.js +7 -0
- package/dist/assets/railroadDiagram-XR7U4H2S-moHTFMC0.js +1 -0
- package/dist/assets/requirementDiagram-PLB6GJNP-CwfXdFH8.js +84 -0
- package/dist/assets/sankeyDiagram-IPEJSGJF-3ZROYvnq.js +40 -0
- package/dist/assets/sequenceDiagram-PO4LG4MO-E1TBebP8.js +169 -0
- package/dist/assets/sizeCapture-INFHLROL-DHKEU5pu.js +1 -0
- package/dist/assets/stateDiagram-v2-GCMORJYK-BguEj09D.js +291 -0
- package/dist/assets/swimlanes-2SLR337P-C7IDYnhd.js +1 -0
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
- package/dist/assets/timeline-definition-EJHVYXUP-DCW-F5PW.js +120 -0
- package/dist/assets/usecaseDiagram-POWQR4AR-xBMFtWYV.js +370 -0
- package/dist/assets/vennDiagram-UO4OBE2U-B5nIkuH8.js +34 -0
- package/dist/assets/wardleyDiagram-VNRHLVJA-DYFAhdbr.js +78 -0
- package/dist/assets/xychartDiagram-PMCCYNJV-DeXwX6Cz.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/browser-proxy.js +2 -1
- package/dist-server/container-mcp.js +1047 -173
- package/dist-server/drivers/agents-proxy.js +16121 -1504
- package/dist-server/drivers/phone-proxy.js +33 -2
- package/dist-server/enterprise/LICENSE +32 -0
- package/dist-server/enterprise/server/index.js +5 -5
- package/dist-server/index.js +16134 -7446
- package/dist-server/local-computer-proxy.js +3 -3
- package/dist-server/local-computer.js +53 -17
- package/dist-server/mcp-server.js +3 -1
- package/dist-server/openmausbot.js +9353 -6124
- package/dist-server/pair-cli.js +9353 -6124
- package/dist-server/permission-proxy.js +29 -9
- package/dist-server/server/admin-activity.js +410 -0
- package/dist-server/server/attachments.js +55 -0
- package/dist-server/server/auto-approve.js +4 -3
- package/dist-server/server/auto-vm-claims.js +1 -0
- package/dist-server/server/bot-package.js +18 -292
- package/dist-server/server/bot-profile.js +5 -17
- package/dist-server/server/bot-visibility.js +462 -0
- package/dist-server/server/box-create-idempotency.js +3 -3
- package/dist-server/server/box.js +59 -56
- package/dist-server/server/browser-proxy.js +3 -2
- package/dist-server/server/browser-runtime.js +38 -2
- package/dist-server/server/channel-queue.js +2 -0
- package/dist-server/server/cli-prompts.js +24 -2
- package/dist-server/server/cli.js +81 -12
- package/dist-server/server/command-allowlist.js +154 -0
- package/dist-server/server/composio.js +49 -11
- package/dist-server/server/computer-wait.js +22 -9
- package/dist-server/server/config.js +133 -17
- package/dist-server/server/connector-verdict.js +141 -0
- package/dist-server/server/container-computer.js +1 -1
- package/dist-server/server/decision-log.js +209 -48
- package/dist-server/server/default-model-selection.js +29 -3
- package/dist-server/server/delegations.js +97 -44
- package/dist-server/server/drivers/acp/core.js +163 -33
- package/dist-server/server/drivers/acp/grok.js +4 -3
- package/dist-server/server/drivers/agents-call.js +1162 -0
- package/dist-server/server/drivers/agents-catalog.js +689 -0
- package/dist-server/server/drivers/agents-client.js +28 -0
- package/dist-server/server/drivers/agents-proxy.js +18 -1722
- package/dist-server/server/drivers/boxagent.js +174 -36
- package/dist-server/server/drivers/builtIn.js +2 -0
- package/dist-server/server/drivers/chat-box-tools.js +90 -0
- package/dist-server/server/drivers/chat-images.js +54 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +55 -10
- package/dist-server/server/drivers/chat-tool-approval.js +70 -18
- package/dist-server/server/drivers/claude.js +28 -8
- package/dist-server/server/drivers/codex-instructions.js +48 -6
- package/dist-server/server/drivers/codex-mcp-names.js +44 -0
- package/dist-server/server/drivers/codex-tool-surface.js +20 -0
- package/dist-server/server/drivers/codex.js +121 -34
- package/dist-server/server/drivers/grok.js +2 -1
- package/dist-server/server/drivers/mistral.js +115 -0
- package/dist-server/server/drivers/openai-chat.js +155 -32
- package/dist-server/server/drivers/openai-compat.js +16 -8
- package/dist-server/server/drivers/permission-command.js +35 -0
- package/dist-server/server/drivers/phone-proxy.js +47 -2
- package/dist-server/server/drivers/pi.js +75 -4
- package/dist-server/server/drivers/prompt-split.js +87 -0
- package/dist-server/server/enterprise.js +97 -16
- package/dist-server/server/external-runtime.js +83 -0
- package/dist-server/server/fleet.js +1 -1
- package/dist-server/server/group-thread-usage.js +103 -0
- package/dist-server/server/guarded-requests.js +60 -0
- package/dist-server/server/harness/http.js +61 -0
- package/dist-server/server/harness/registry.js +1 -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 +3091 -658
- package/dist-server/server/local-computer.js +72 -23
- 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 +6 -6
- package/dist-server/server/message-db.js +28 -0
- package/dist-server/server/model-context-window.js +7 -0
- package/dist-server/server/model-prices.js +121 -0
- package/dist-server/server/new-bot-defaults.js +115 -0
- package/dist-server/server/notify.js +10 -1
- package/dist-server/server/org-library.js +1051 -0
- package/dist-server/server/package-export.js +627 -39
- package/dist-server/server/package-import.js +458 -0
- package/dist-server/server/package-parts.js +87 -0
- package/dist-server/server/peer-approval.js +16 -7
- package/dist-server/server/peer-roster.js +23 -2
- package/dist-server/server/presets.js +492 -0
- package/dist-server/server/provider-key-check.js +3 -2
- package/dist-server/server/published-teams.js +80 -0
- package/dist-server/server/recent-work.js +15 -0
- package/dist-server/server/request-auth.js +96 -0
- package/dist-server/server/room-handoffs.js +44 -14
- package/dist-server/server/routes/bot-presets.js +25 -0
- 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 +6 -3
- package/dist-server/server/routines.js +63 -1
- package/dist-server/server/section-context.js +10 -0
- package/dist-server/server/skills.js +34 -61
- package/dist-server/server/spend.js +111 -16
- package/dist-server/server/steer-queue.js +9 -3
- package/dist-server/server/store.js +550 -35
- package/dist-server/server/surface.js +10 -4
- package/dist-server/server/system-prompt.js +12 -4
- package/dist-server/server/team-backup.js +16 -3
- package/dist-server/server/team-computers.js +10 -0
- package/dist-server/server/team-library.js +6 -2
- package/dist-server/server/team-manifest.js +4 -3
- package/dist-server/server/team-setup-requests.js +16 -5
- package/dist-server/server/thread-events.js +17 -1
- package/dist-server/server/thread-starters.js +53 -0
- package/dist-server/server/turn-context.js +2 -0
- package/dist-server/server/turn-watchdog.js +12 -3
- package/dist-server/server/usage-ledger.js +43 -9
- package/dist-server/server/vps-computer.js +139 -51
- package/dist-server/server/vps-container-mcp.js +7 -0
- package/dist-server/server/vps-ssh.js +36 -7
- package/dist-server/server/workspace-backup-policy.js +21 -2
- package/dist-server/server/workspace-backup.js +7 -4
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/ask-question.js +188 -12
- package/dist-server/shared/bot-profile.js +15 -0
- package/dist-server/shared/command-allowlist.js +1 -0
- package/dist-server/shared/credential-request.js +1 -1
- package/dist-server/shared/mention-boundary.js +15 -0
- package/dist-server/shared/package-format.js +967 -0
- package/dist-server/shared/routines.js +7 -1
- package/dist-server/shared/skill-md.js +64 -0
- package/dist-server/shared/team-backup.js +9 -0
- package/dist-server/shared/wire.js +4 -0
- package/dist-server/src/lib/composer-attachments.js +16 -0
- package/dist-server/vps-container-mcp.js +1147 -179
- package/enterprise/README.md +33 -12
- package/enterprise/server/index.js +5 -5
- package/package.json +1 -1
- package/dist/assets/index-Ba9G44HI.js +0 -310
- package/dist/assets/index-CkRNp7wX.css +0 -1
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// This module only exports; openmausbot.ts is the entry that runs main(), so
|
|
24
24
|
// bundling this file into other entries (pair-cli.ts) never runs it twice.
|
|
25
25
|
import { spawn } from "node:child_process";
|
|
26
|
+
import { randomBytes } from "node:crypto";
|
|
26
27
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync } from "node:fs";
|
|
27
28
|
import { homedir } from "node:os";
|
|
28
29
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -30,6 +31,10 @@ import { createInterface } from "node:readline/promises";
|
|
|
30
31
|
import { fileURLToPath } from "node:url";
|
|
31
32
|
import qrcode from "qrcode-terminal";
|
|
32
33
|
import { parseAllowList } from "./account-signin.js";
|
|
34
|
+
import { appendAdminAction, flushAdminActivity, sharedSignIn } from "./admin-activity.js";
|
|
35
|
+
import { bindDecisionRetention, decisionRetentionDays } from "./decision-log.js";
|
|
36
|
+
import { hostedWorkspaceConfigured } from "./enterprise.js";
|
|
37
|
+
import { resolveLoopbackTrust } from "./request-auth.js";
|
|
33
38
|
import { writeFileAtomic } from "./atomic.js";
|
|
34
39
|
import { ensureCaddy, normalizeDomainOption, startCaddy } from "./caddy.js";
|
|
35
40
|
import { runServiceCommand } from "./service-cli.js";
|
|
@@ -246,7 +251,7 @@ fleet many client workspaces on one Linux server, each its own account,
|
|
|
246
251
|
--dry-run always prints. Install the package permanently first.
|
|
247
252
|
init --operator USER also installs the fleet agent, a root service
|
|
248
253
|
on a Unix socket only USER may open, so the workspace running as
|
|
249
|
-
USER manages the others from Settings →
|
|
254
|
+
USER manages the others from Settings → Installations.
|
|
250
255
|
|
|
251
256
|
--tailscale serve over your tailnet: Tailscale terminates HTTPS and the
|
|
252
257
|
link uses this machine's MagicDNS name (needs Tailscale signed in
|
|
@@ -295,11 +300,23 @@ export function serverVersion(here = HERE) {
|
|
|
295
300
|
}
|
|
296
301
|
const message = (error) => (error instanceof Error ? error.message : String(error));
|
|
297
302
|
// ── talking to a running server (loopback = owner) ────────────────────
|
|
303
|
+
/** Set only inside `openmausbot serve` on a service-trust server: the secret
|
|
304
|
+
* it handed the server it started, which opens that server's pairing route. */
|
|
305
|
+
let serveOwnerToken;
|
|
298
306
|
async function api(port, path, init = {}) {
|
|
299
|
-
|
|
307
|
+
// x-openmausbot-cli names the tool in the admin activity log; on loopback
|
|
308
|
+
// it is the owner either way, so it grants nothing.
|
|
309
|
+
const headers = { "content-type": "application/json", "x-openmausbot-cli": "1", ...(serveOwnerToken ? { "x-openmausbot-cli-owner": serveOwnerToken } : {}) };
|
|
310
|
+
const res = await fetch(`http://127.0.0.1:${port}${path}`, { method: init.method, body: init.body, headers, signal: AbortSignal.timeout(3000) });
|
|
300
311
|
const body = await res.json().catch(() => ({}));
|
|
301
312
|
return { status: res.status, body };
|
|
302
313
|
}
|
|
314
|
+
/** What to do when a server treats this command as a local service rather
|
|
315
|
+
* than its owner (OMB_LOOPBACK_TRUST=service, or a hosted workspace). */
|
|
316
|
+
export const SERVICE_TRUST_HELP = "This server does not treat commands on this computer as its owner (OMB_LOOPBACK_TRUST=service, or a hosted workspace), so it will not pair devices or list sessions for them. Sign in as an admin and use Settings → Remote access, let people sign in with their email (openmausbot access add you@example.com), or restart the server with OMB_LOOPBACK_TRUST=owner.";
|
|
317
|
+
function refusedAsService(status, body) {
|
|
318
|
+
return status === 403 && typeof body?.error === "string" && /shared server|Sign in through the workspace portal/.test(body.error);
|
|
319
|
+
}
|
|
303
320
|
async function serverUp(port, pid) {
|
|
304
321
|
try {
|
|
305
322
|
const { status, body } = await api(port, "/api/health");
|
|
@@ -385,7 +402,7 @@ function startupPreferences(options) {
|
|
|
385
402
|
async function showPhonePairing(options, origin, log) {
|
|
386
403
|
const ready = !!origin && await verifyPhoneEndpoint(options.port, origin);
|
|
387
404
|
if (!ready) {
|
|
388
|
-
log("Phone access is not reachable yet.
|
|
405
|
+
log("Phone access is not reachable yet. OpenMausBot is ready on this computer; no phone pairing code was created.");
|
|
389
406
|
log("Check the HTTPS connection, then run openmausbot pair again with the same --data-dir and --port.");
|
|
390
407
|
return false;
|
|
391
408
|
}
|
|
@@ -472,6 +489,8 @@ async function mintPairing(port, options) {
|
|
|
472
489
|
if (options.client)
|
|
473
490
|
request.scopes = ["client"];
|
|
474
491
|
const { status, body } = await api(port, "/api/auth/pairing", { method: "POST", body: JSON.stringify(request) });
|
|
492
|
+
if (refusedAsService(status, body))
|
|
493
|
+
throw new Error(SERVICE_TRUST_HELP);
|
|
475
494
|
if (status !== 200)
|
|
476
495
|
throw new Error(`server refused to mint a pairing code: ${typeof body?.error === "string" ? body.error : status}`);
|
|
477
496
|
const url = options.publicUrl ? `${options.publicUrl}/pair#code=${body.code}` : typeof body.url === "string" ? body.url : null;
|
|
@@ -547,6 +566,10 @@ export async function runSessions(options) {
|
|
|
547
566
|
}
|
|
548
567
|
if (options.revoke) {
|
|
549
568
|
const { status, body } = await api(options.port, `/api/auth/sessions/${encodeURIComponent(options.revoke)}`, { method: "DELETE" });
|
|
569
|
+
if (refusedAsService(status, body)) {
|
|
570
|
+
console.error(SERVICE_TRUST_HELP);
|
|
571
|
+
return 1;
|
|
572
|
+
}
|
|
550
573
|
if (status !== 200) {
|
|
551
574
|
console.error(`could not revoke: ${typeof body?.error === "string" ? body.error : status}`);
|
|
552
575
|
return 1;
|
|
@@ -554,7 +577,15 @@ export async function runSessions(options) {
|
|
|
554
577
|
console.log(`revoked ${options.revoke}: that device is signed out and its stream is closed`);
|
|
555
578
|
return 0;
|
|
556
579
|
}
|
|
557
|
-
const { body } = await api(options.port, "/api/auth/sessions");
|
|
580
|
+
const { status, body } = await api(options.port, "/api/auth/sessions");
|
|
581
|
+
if (refusedAsService(status, body)) {
|
|
582
|
+
console.error(SERVICE_TRUST_HELP);
|
|
583
|
+
return 1;
|
|
584
|
+
}
|
|
585
|
+
if (status !== 200) {
|
|
586
|
+
console.error(`could not list sessions: ${typeof body?.error === "string" ? body.error : status}`);
|
|
587
|
+
return 1;
|
|
588
|
+
}
|
|
558
589
|
const sessions = Array.isArray(body?.sessions) ? body.sessions : [];
|
|
559
590
|
if (options.json) {
|
|
560
591
|
console.log(JSON.stringify(sessions, null, 2));
|
|
@@ -623,9 +654,24 @@ export async function runAccess(options, io = defaultIo()) {
|
|
|
623
654
|
const admins = list(Reflect.get(current, "admins"));
|
|
624
655
|
const members = list(Reflect.get(current, "members"));
|
|
625
656
|
const overridden = process.env.OMB_SIGNIN_EMAILS !== undefined || process.env.OMB_SIGNIN_MEMBER_EMAILS !== undefined;
|
|
626
|
-
const write = (next) => {
|
|
657
|
+
const write = async (next) => {
|
|
627
658
|
mkdirSync(options.dataDir, { recursive: true, mode: 0o700 });
|
|
628
659
|
writeFileAtomic(file, `${JSON.stringify({ ...raw, signIn: next }, null, 2)}\n`, { mode: 0o600 });
|
|
660
|
+
// The same row Settings → People writes, named for the command line,
|
|
661
|
+
// pruned by the same window the server would use — kept, like the
|
|
662
|
+
// server's, only where more than one person signs in.
|
|
663
|
+
if (!sharedSignIn({ admins, members }) && !sharedSignIn(next) && !hostedWorkspaceConfigured())
|
|
664
|
+
return;
|
|
665
|
+
const decisions = raw.decisions && typeof raw.decisions === "object" ? raw.decisions.retentionDays : undefined;
|
|
666
|
+
bindDecisionRetention(() => decisionRetentionDays(typeof decisions === "number" ? decisions : undefined));
|
|
667
|
+
const changed = ["admins", "members"].filter((key) => next[key].join(",") !== (key === "admins" ? admins : members).join(","));
|
|
668
|
+
appendAdminAction(options.dataDir, {
|
|
669
|
+
category: "people", action: "people.update", target: { kind: "settings" }, actor: { kind: "cli" },
|
|
670
|
+
changed: changed.map((key) => `signIn.${key}`),
|
|
671
|
+
before: Object.fromEntries(changed.map((key) => [`signIn.${key}`, key === "admins" ? admins : members])),
|
|
672
|
+
after: Object.fromEntries(changed.map((key) => [`signIn.${key}`, next[key]])),
|
|
673
|
+
});
|
|
674
|
+
await flushAdminActivity(options.dataDir);
|
|
629
675
|
};
|
|
630
676
|
if (options.accessAction === "list") {
|
|
631
677
|
if (!admins.length && !members.length) {
|
|
@@ -651,11 +697,11 @@ export async function runAccess(options, io = defaultIo()) {
|
|
|
651
697
|
io.error(`${entry} is not on the list`);
|
|
652
698
|
return 1;
|
|
653
699
|
}
|
|
654
|
-
write({ admins: without(admins), members: without(members) });
|
|
700
|
+
await write({ admins: without(admins), members: without(members) });
|
|
655
701
|
io.log(`${entry} can no longer sign in (existing sessions stay until they expire or are revoked with \`openmausbot sessions revoke\`)`);
|
|
656
702
|
return 0;
|
|
657
703
|
}
|
|
658
|
-
write(options.chatOnly ? { admins: without(admins), members: [...without(members), entry] } : { admins: [...without(admins), entry], members: without(members) });
|
|
704
|
+
await write(options.chatOnly ? { admins: without(admins), members: [...without(members), entry] } : { admins: [...without(admins), entry], members: without(members) });
|
|
659
705
|
io.log(`${entry} can sign in at /pair with an emailed code (${options.chatOnly ? "chat and approvals" : "full access"})`);
|
|
660
706
|
if (overridden)
|
|
661
707
|
io.log("note: OMB_SIGNIN_EMAILS / OMB_SIGNIN_MEMBER_EMAILS are set in the environment and win over this list while the server runs");
|
|
@@ -883,6 +929,16 @@ export async function runServe(options, log = console.log) {
|
|
|
883
929
|
};
|
|
884
930
|
if (options.local)
|
|
885
931
|
delete env.OMB_PUBLIC_URL;
|
|
932
|
+
// A service-trust server refuses session-less local admin requests, this
|
|
933
|
+
// CLI's included. Hand the server we start a per-launch secret over its
|
|
934
|
+
// stdin (not its environment, which every engine it starts inherits) so
|
|
935
|
+
// this process alone can still print the pairing code.
|
|
936
|
+
const serviceTrust = resolveLoopbackTrust({ env, desktopManaged: false, hostedWorkspace: hostedWorkspaceConfigured(env) }).trust === "service";
|
|
937
|
+
const ownerToken = serviceTrust ? randomBytes(32).toString("base64url") : undefined;
|
|
938
|
+
if (ownerToken)
|
|
939
|
+
env.OMB_CLI_OWNER_STDIN = "1";
|
|
940
|
+
else
|
|
941
|
+
delete env.OMB_CLI_OWNER_STDIN;
|
|
886
942
|
if (entry.staticDir)
|
|
887
943
|
env.OMB_STATIC_DIR = entry.staticDir;
|
|
888
944
|
if (entry.skillsDir && !process.env.OMB_SKILLS_DIR)
|
|
@@ -925,7 +981,12 @@ export async function runServe(options, log = console.log) {
|
|
|
925
981
|
throw new SetupCancelled();
|
|
926
982
|
if (publicUrl)
|
|
927
983
|
env.OMB_PUBLIC_URL = publicUrl;
|
|
928
|
-
child = spawn(entry.command, entry.args, { env, stdio: ["ignore", logFd ?? "inherit", logFd ?? "inherit"] });
|
|
984
|
+
child = spawn(entry.command, entry.args, { env, stdio: [ownerToken ? "pipe" : "ignore", logFd ?? "inherit", logFd ?? "inherit"] });
|
|
985
|
+
if (ownerToken) {
|
|
986
|
+
child.stdin?.on("error", () => { });
|
|
987
|
+
child.stdin?.end(`${ownerToken}\n`);
|
|
988
|
+
serveOwnerToken = ownerToken;
|
|
989
|
+
}
|
|
929
990
|
}
|
|
930
991
|
catch (error) {
|
|
931
992
|
if ((tailscaleServing || (startupCancelled && tailscaleAttempted)) && tailscale)
|
|
@@ -1048,13 +1109,20 @@ export async function runServe(options, log = console.log) {
|
|
|
1048
1109
|
clearTimeout(timer);
|
|
1049
1110
|
}
|
|
1050
1111
|
if (!stopping && exited === null)
|
|
1051
|
-
await showPhonePairing(options, publicUrl, log);
|
|
1112
|
+
await showPhonePairing(options, publicUrl, log).catch((error) => log(`no pairing code: ${message(error)}`));
|
|
1052
1113
|
}
|
|
1053
1114
|
else if (options.pair && !options.guided) {
|
|
1054
1115
|
log("");
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1116
|
+
// A refused code is no reason to stop a server that is running fine.
|
|
1117
|
+
try {
|
|
1118
|
+
log(await mintPairing(options.port, { label: options.label ? `${options.label} owner` : undefined, client: options.client, publicUrl: publicUrl ?? undefined }));
|
|
1119
|
+
log("");
|
|
1120
|
+
log("another device later: openmausbot pair --label \"Kitchen iPad\"");
|
|
1121
|
+
}
|
|
1122
|
+
catch (error) {
|
|
1123
|
+
log(`no pairing code: ${message(error)}`);
|
|
1124
|
+
log("start without one next time: openmausbot serve --no-pair");
|
|
1125
|
+
}
|
|
1058
1126
|
}
|
|
1059
1127
|
log(options.guided ? "\nKeep this terminal open while using your bots. Ctrl+C stops the server, not your saved work." : "stop with Ctrl+C");
|
|
1060
1128
|
if (options.guided)
|
|
@@ -1062,6 +1130,7 @@ export async function runServe(options, log = console.log) {
|
|
|
1062
1130
|
return await childExit;
|
|
1063
1131
|
}
|
|
1064
1132
|
finally {
|
|
1133
|
+
serveOwnerToken = undefined;
|
|
1065
1134
|
await stop();
|
|
1066
1135
|
process.removeListener("SIGINT", onSignal);
|
|
1067
1136
|
process.removeListener("SIGTERM", onSignal);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { lstatSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, sep } from "node:path";
|
|
4
|
+
import { writeFileAtomic } from "./atomic.js";
|
|
5
|
+
import { redactSecretsInText } from "./redact.js";
|
|
6
|
+
const MAX_COMMAND_BYTES = 16_384;
|
|
7
|
+
const MAX_CWD_BYTES = 4_096;
|
|
8
|
+
const MAX_RULES_PER_BOT = 200;
|
|
9
|
+
const MAX_STORE_BYTES = 8 * 1024 * 1024;
|
|
10
|
+
const BOT_ID = /^[\w-]{1,128}$/;
|
|
11
|
+
const RULE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
12
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
const invalid = (message) => Object.assign(new Error(message), { status: 400 });
|
|
14
|
+
/** Validate without rewriting the command. Resolving a symlink, or folding
|
|
15
|
+
* `..` across one, could make a grant apply in a different directory. */
|
|
16
|
+
export function validateCommandAllowlistCandidate(value) {
|
|
17
|
+
if (!record(value))
|
|
18
|
+
throw invalid("A command, absolute working directory and engine instance are required.");
|
|
19
|
+
const { command, cwd, providerInstanceId } = value;
|
|
20
|
+
if (typeof command !== "string" || !command.trim() || Buffer.byteLength(command) > MAX_COMMAND_BYTES ||
|
|
21
|
+
/[\p{Cc}\p{Cs}]/u.test(command.replace(/[\t\r\n]/g, ""))) {
|
|
22
|
+
throw invalid("The command must be nonempty text of at most 16384 bytes, without invalid control characters.");
|
|
23
|
+
}
|
|
24
|
+
if (redactSecretsInText(command) !== command) {
|
|
25
|
+
throw invalid("Commands containing credentials cannot be saved. Use an environment variable instead.");
|
|
26
|
+
}
|
|
27
|
+
if (typeof cwd !== "string" || !cwd || Buffer.byteLength(cwd) > MAX_CWD_BYTES ||
|
|
28
|
+
/[\p{Cc}\p{Cs}]/u.test(cwd) || !isAbsolute(cwd) || cwd.split(sep === "\\" ? /[\\/]/ : /\//).includes("..")) {
|
|
29
|
+
throw invalid("The working directory must be an absolute path without parent-directory segments.");
|
|
30
|
+
}
|
|
31
|
+
// On Windows, `\work` is rooted but still depends on the current drive.
|
|
32
|
+
if (process.platform === "win32" && !/^(?:[A-Za-z]:[\\/]|[\\/]{2}[^\\/]+[\\/][^\\/]+(?:[\\/]|$))/.test(cwd)) {
|
|
33
|
+
throw invalid("The working directory must include its drive or network share.");
|
|
34
|
+
}
|
|
35
|
+
if (typeof providerInstanceId !== "string" || !providerInstanceId || providerInstanceId.trim() !== providerInstanceId ||
|
|
36
|
+
providerInstanceId.length > 200 || /[\p{Cc}\p{Cs}]/u.test(providerInstanceId)) {
|
|
37
|
+
throw invalid("A valid engine instance is required.");
|
|
38
|
+
}
|
|
39
|
+
// Keep directory spelling exact too. A false negative is preferable to
|
|
40
|
+
// treating two paths as equivalent without knowing the engine's filesystem.
|
|
41
|
+
return { command, cwd, providerInstanceId };
|
|
42
|
+
}
|
|
43
|
+
/** Runtime metadata that cannot identify an exact scope never auto-allows. */
|
|
44
|
+
export function commandAllowlistCandidate(value) {
|
|
45
|
+
try {
|
|
46
|
+
return validateCommandAllowlistCandidate(value);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function validateBotId(botId) {
|
|
53
|
+
if (typeof botId !== "string" || !BOT_ID.test(botId))
|
|
54
|
+
throw invalid("A valid bot id is required.");
|
|
55
|
+
}
|
|
56
|
+
function sameScope(left, right) {
|
|
57
|
+
return left.command === right.command && left.cwd === right.cwd && left.providerInstanceId === right.providerInstanceId;
|
|
58
|
+
}
|
|
59
|
+
/** Installation-local grants, deliberately separate from bot profiles and
|
|
60
|
+
* templates. Callers must also exclude this file from workspace backups.
|
|
61
|
+
* One server owns the store; a successful atomic save precedes publication. */
|
|
62
|
+
export class CommandAllowlistStore {
|
|
63
|
+
rules = [];
|
|
64
|
+
unreadable = false;
|
|
65
|
+
filename;
|
|
66
|
+
constructor(filename) {
|
|
67
|
+
this.filename = filename;
|
|
68
|
+
try {
|
|
69
|
+
const stat = lstatSync(filename);
|
|
70
|
+
if (!stat.isFile() || stat.size > MAX_STORE_BYTES)
|
|
71
|
+
throw new Error("Invalid command rule store.");
|
|
72
|
+
const saved = JSON.parse(readFileSync(filename, "utf8"));
|
|
73
|
+
if (!record(saved) || saved.version !== 1 || !Array.isArray(saved.rules) ||
|
|
74
|
+
Object.keys(saved).some((key) => key !== "version" && key !== "rules"))
|
|
75
|
+
throw new Error("Invalid command rule store.");
|
|
76
|
+
const rules = [];
|
|
77
|
+
const ids = new Set();
|
|
78
|
+
const counts = new Map();
|
|
79
|
+
for (const row of saved.rules) {
|
|
80
|
+
if (!record(row) || typeof row.id !== "string" || !RULE_ID.test(row.id) || ids.has(row.id) ||
|
|
81
|
+
typeof row.botId !== "string" || Object.keys(row).some((key) => !["id", "botId", "command", "cwd", "providerInstanceId"].includes(key))) {
|
|
82
|
+
throw new Error("Invalid command rule store.");
|
|
83
|
+
}
|
|
84
|
+
validateBotId(row.botId);
|
|
85
|
+
const candidate = validateCommandAllowlistCandidate(row);
|
|
86
|
+
const count = (counts.get(row.botId) ?? 0) + 1;
|
|
87
|
+
if (count > MAX_RULES_PER_BOT)
|
|
88
|
+
throw new Error("Too many saved command rules.");
|
|
89
|
+
counts.set(row.botId, count);
|
|
90
|
+
ids.add(row.id);
|
|
91
|
+
rules.push({ ...candidate, id: row.id, botId: row.botId });
|
|
92
|
+
}
|
|
93
|
+
this.rules = rules;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
// Missing means no grants. Any other read/validation failure disables
|
|
97
|
+
// all grants and forbids overwriting evidence of a damaged store.
|
|
98
|
+
if (error.code !== "ENOENT")
|
|
99
|
+
this.unreadable = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
list(botId) {
|
|
103
|
+
validateBotId(botId);
|
|
104
|
+
return this.rules.filter((rule) => rule.botId === botId).map(({ botId: _botId, ...rule }) => rule);
|
|
105
|
+
}
|
|
106
|
+
add(botId, value) {
|
|
107
|
+
this.assertWritable();
|
|
108
|
+
validateBotId(botId);
|
|
109
|
+
const candidate = validateCommandAllowlistCandidate(value);
|
|
110
|
+
const existing = this.list(botId);
|
|
111
|
+
const duplicate = existing.find((rule) => sameScope(rule, candidate));
|
|
112
|
+
if (duplicate)
|
|
113
|
+
return duplicate;
|
|
114
|
+
if (existing.length >= MAX_RULES_PER_BOT)
|
|
115
|
+
throw invalid("A bot may save at most 200 commands.");
|
|
116
|
+
const rule = { ...candidate, id: randomUUID() };
|
|
117
|
+
this.save([...this.rules, { ...rule, botId }]);
|
|
118
|
+
return rule;
|
|
119
|
+
}
|
|
120
|
+
remove(botId, ruleId) {
|
|
121
|
+
this.assertWritable();
|
|
122
|
+
validateBotId(botId);
|
|
123
|
+
const next = this.rules.filter((rule) => rule.botId !== botId || rule.id !== ruleId);
|
|
124
|
+
if (next.length === this.rules.length)
|
|
125
|
+
return false;
|
|
126
|
+
this.save(next);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
clear(botId) {
|
|
130
|
+
this.assertWritable();
|
|
131
|
+
validateBotId(botId);
|
|
132
|
+
const next = this.rules.filter((rule) => rule.botId !== botId);
|
|
133
|
+
if (next.length !== this.rules.length)
|
|
134
|
+
this.save(next);
|
|
135
|
+
}
|
|
136
|
+
matches(botId, value) {
|
|
137
|
+
if (this.unreadable || typeof botId !== "string" || !BOT_ID.test(botId))
|
|
138
|
+
return false;
|
|
139
|
+
const candidate = commandAllowlistCandidate(value);
|
|
140
|
+
return candidate !== null && this.rules.some((rule) => rule.botId === botId && sameScope(rule, candidate));
|
|
141
|
+
}
|
|
142
|
+
assertWritable() {
|
|
143
|
+
if (this.unreadable)
|
|
144
|
+
throw Object.assign(new Error("Saved command rules could not be read. Repair the rule store before changing them."), { status: 503 });
|
|
145
|
+
}
|
|
146
|
+
save(rules) {
|
|
147
|
+
const json = JSON.stringify({ version: 1, rules }, null, 2);
|
|
148
|
+
if (Buffer.byteLength(json) > MAX_STORE_BYTES)
|
|
149
|
+
throw invalid("The saved command rules exceed the storage limit.");
|
|
150
|
+
mkdirSync(dirname(this.filename), { recursive: true, mode: 0o700 });
|
|
151
|
+
writeFileAtomic(this.filename, json, { mode: 0o600 });
|
|
152
|
+
this.rules = rules;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -895,7 +895,7 @@ export async function listToolkits(cfg) {
|
|
|
895
895
|
? backendFingerprint("managed-catalog", broker.url, broker.token)
|
|
896
896
|
: null;
|
|
897
897
|
if (identity && toolkitCache?.identity === identity && Date.now() - toolkitCache.at < 10 * 60_000) {
|
|
898
|
-
return { cards: toolkitCache.cards, source: "api" };
|
|
898
|
+
return { cards: toolkitCache.cards, source: "api", pagination: toolkitCache.pagination };
|
|
899
899
|
}
|
|
900
900
|
if (backendKey || broker) {
|
|
901
901
|
try {
|
|
@@ -907,6 +907,12 @@ export async function listToolkits(cfg) {
|
|
|
907
907
|
const seenCursors = new Set();
|
|
908
908
|
let cursor;
|
|
909
909
|
let lastReportedPage;
|
|
910
|
+
let reportedTotalItems;
|
|
911
|
+
let reportedTotalPages;
|
|
912
|
+
// Why the walk ended. "limit" means the loop ran out of iterations;
|
|
913
|
+
// every early exit names itself so a stalled catalog can be logged
|
|
914
|
+
// instead of silently posing as the complete marketplace.
|
|
915
|
+
let stop = "limit";
|
|
910
916
|
for (let page = 0; page < MAX_CONNECTED_ACCOUNT_PAGES; page += 1) {
|
|
911
917
|
const params = new URLSearchParams({ limit: "500", sort_by: "usage" });
|
|
912
918
|
if (cursor)
|
|
@@ -919,33 +925,51 @@ export async function listToolkits(cfg) {
|
|
|
919
925
|
: await brokerRequest(cursor ? `/v1/catalog?cursor=${encodeURIComponent(cursor)}` : "/v1/catalog", {
|
|
920
926
|
signal: AbortSignal.timeout(15_000),
|
|
921
927
|
});
|
|
922
|
-
if (!res.ok)
|
|
928
|
+
if (!res.ok) {
|
|
929
|
+
stop = "http-error";
|
|
923
930
|
break;
|
|
931
|
+
}
|
|
924
932
|
const json = await res.json();
|
|
925
933
|
const pageItems = json.items ?? json.data ?? [];
|
|
926
|
-
if (!Array.isArray(pageItems))
|
|
934
|
+
if (!Array.isArray(pageItems)) {
|
|
935
|
+
stop = "bad-page";
|
|
927
936
|
break;
|
|
937
|
+
}
|
|
928
938
|
items.push(...pageItems);
|
|
939
|
+
const pageTotalItems = Number(json.total_items);
|
|
940
|
+
if (Number.isFinite(pageTotalItems) && pageTotalItems > 0)
|
|
941
|
+
reportedTotalItems = pageTotalItems;
|
|
929
942
|
// Composio reports current_page and total_pages beside next_cursor.
|
|
930
943
|
// A broker that drops the cursor (or an upstream regression) can replay
|
|
931
944
|
// a page while minting fresh cursors, so trust page movement: once it
|
|
932
945
|
// stops advancing, the catalog is stuck and paging stops cleanly.
|
|
933
946
|
const reportedPage = Number(json.current_page);
|
|
934
947
|
if (Number.isFinite(reportedPage)) {
|
|
935
|
-
if (lastReportedPage !== undefined && reportedPage <= lastReportedPage)
|
|
948
|
+
if (lastReportedPage !== undefined && reportedPage <= lastReportedPage) {
|
|
949
|
+
stop = "page-stuck";
|
|
936
950
|
break;
|
|
951
|
+
}
|
|
937
952
|
lastReportedPage = reportedPage;
|
|
938
953
|
}
|
|
939
|
-
const
|
|
954
|
+
const pageTotalPages = Number(json.total_pages);
|
|
955
|
+
if (Number.isFinite(pageTotalPages) && pageTotalPages > 0)
|
|
956
|
+
reportedTotalPages = pageTotalPages;
|
|
940
957
|
if (lastReportedPage !== undefined &&
|
|
941
|
-
Number.isFinite(
|
|
942
|
-
|
|
943
|
-
lastReportedPage >=
|
|
958
|
+
Number.isFinite(pageTotalPages) &&
|
|
959
|
+
pageTotalPages > 0 &&
|
|
960
|
+
lastReportedPage >= pageTotalPages) {
|
|
961
|
+
stop = "total-reached";
|
|
944
962
|
break;
|
|
945
963
|
}
|
|
946
964
|
const next = typeof json.next_cursor === "string" ? json.next_cursor.trim() : "";
|
|
947
|
-
if (!next
|
|
965
|
+
if (!next) {
|
|
966
|
+
stop = "end";
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
if (seenCursors.has(next)) {
|
|
970
|
+
stop = "cursor-repeated";
|
|
948
971
|
break;
|
|
972
|
+
}
|
|
949
973
|
seenCursors.add(next);
|
|
950
974
|
cursor = next;
|
|
951
975
|
}
|
|
@@ -959,8 +983,22 @@ export async function listToolkits(cfg) {
|
|
|
959
983
|
domain: null,
|
|
960
984
|
}));
|
|
961
985
|
const uniqueCards = cards.filter((card, index) => card.slug && cards.findIndex((candidate) => candidate.slug === card.slug) === index);
|
|
962
|
-
|
|
963
|
-
|
|
986
|
+
const shortOfReportedTotal = reportedTotalItems !== undefined && uniqueCards.length < reportedTotalItems;
|
|
987
|
+
// Trust reported page counts even when the walk ends "cleanly": an
|
|
988
|
+
// endpoint that stops at page 1 of 4 without a cursor is still partial.
|
|
989
|
+
const pagingStoppedEarly = lastReportedPage !== undefined
|
|
990
|
+
&& reportedTotalPages !== undefined
|
|
991
|
+
&& lastReportedPage < reportedTotalPages;
|
|
992
|
+
const pagination = { items: uniqueCards.length, stalled: shortOfReportedTotal || pagingStoppedEarly };
|
|
993
|
+
if (reportedTotalItems !== undefined)
|
|
994
|
+
pagination.totalItems = reportedTotalItems;
|
|
995
|
+
if (pagination.stalled) {
|
|
996
|
+
console.warn(`[composio] marketplace catalog paging stopped early (${stop}) after ${uniqueCards.length} toolkits`
|
|
997
|
+
+ (reportedTotalItems !== undefined ? ` of ~${reportedTotalItems}` : "")
|
|
998
|
+
+ "; serving a partial marketplace");
|
|
999
|
+
}
|
|
1000
|
+
toolkitCache = { at: Date.now(), cards: uniqueCards, identity: identity, pagination };
|
|
1001
|
+
return { cards: uniqueCards, source: "api", pagination };
|
|
964
1002
|
}
|
|
965
1003
|
}
|
|
966
1004
|
catch {
|
|
@@ -7,8 +7,17 @@
|
|
|
7
7
|
// that, name the holder as a bot running a thread, and keep the same words
|
|
8
8
|
// on every surface (chat chip, room chip, the gate's refusal) so a person
|
|
9
9
|
// who has read it once recognises it everywhere.
|
|
10
|
-
/** Whole minutes, or seconds under one minute,
|
|
11
|
-
const minutes = (ms) =>
|
|
10
|
+
/** Whole minutes, or seconds under one minute, singular at exactly one. */
|
|
11
|
+
const minutes = (ms) => {
|
|
12
|
+
const value = ms >= 60_000 ? Math.round(ms / 60_000) : Math.round(ms / 1000);
|
|
13
|
+
const unit = ms >= 60_000 ? "minute" : "second";
|
|
14
|
+
return `${value} ${unit}${value === 1 ? "" : "s"}`;
|
|
15
|
+
};
|
|
16
|
+
/** How long a wait lasted, for the resolution line: waits under a second
|
|
17
|
+
* are honest about being over in a blink instead of claiming "0 seconds". */
|
|
18
|
+
export function computerWaitDuration(ms) {
|
|
19
|
+
return ms < 1_000 ? "under a second" : minutes(ms);
|
|
20
|
+
}
|
|
12
21
|
const holderPhrase = (holder) => holder.task ? `${holder.name} is running ${holder.task}` : `${holder.name} is using it`;
|
|
13
22
|
/** The chip a turn shows while it is queued behind another turn's desktop. */
|
|
14
23
|
export function computerWaitingText(holder) {
|
|
@@ -16,14 +25,18 @@ export function computerWaitingText(holder) {
|
|
|
16
25
|
return "Waiting for its turn on this computer. Starts automatically when it is free.";
|
|
17
26
|
return `Waiting for its turn on this computer — ${holderPhrase(holder)}. Starts automatically when that finishes.`;
|
|
18
27
|
}
|
|
19
|
-
/** The
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
/** The resolution appended once the wait landed and this turn holds the
|
|
29
|
+
* desktop: how long it waited, and who held it. The waiting chip stays as
|
|
30
|
+
* written — this line is the history beside it, not its replacement. */
|
|
31
|
+
export function computerFreeAfterText(holder, waitedMs) {
|
|
32
|
+
const who = holder ? ` (${holder.task ? `${holder.name} · ${holder.task}` : holder.name} held it)` : "";
|
|
33
|
+
return `Computer free — continuing after waiting ${computerWaitDuration(waitedMs)}${who}`;
|
|
22
34
|
}
|
|
23
|
-
/** The
|
|
24
|
-
*
|
|
25
|
-
export function
|
|
26
|
-
|
|
35
|
+
/** The resolution appended when the wait ended because the turn was stopped
|
|
36
|
+
* or its claim was cancelled. */
|
|
37
|
+
export function computerStoppedWaitingText(holder, waitedMs) {
|
|
38
|
+
const who = holder ? ` — ${holderPhrase(holder)}` : "";
|
|
39
|
+
return `Stopped waiting for the computer after ${computerWaitDuration(waitedMs)}${who}.`;
|
|
27
40
|
}
|
|
28
41
|
/** The error after the wait ceiling: still names who holds it, and says
|
|
29
42
|
* what a person can do — stop that turn, or move this one. */
|