openmausbot 0.1.94 → 0.1.96

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.
Files changed (188) hide show
  1. package/dist/assets/{DesktopViewer-bxM7NV3N.js → DesktopViewer-DsnA23KM.js} +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-BhMdJhRJ.js → abnfDiagram-O67JEVCF-Dkbd2hcj.js} +1 -1
  3. package/dist/assets/{arc-B5itKDcx.js → arc-Duj5Coy2.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-C5d7UY6U.js → architectureDiagram-NJMV4G6O-uYUsDawP.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-BrZIhMmu.js → blockDiagram-BEXU5L5S-CWDlTpXe.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-7I0ArfbD.js → c4Diagram-YGBWAQC7-B0t15PDc.js} +1 -1
  7. package/dist/assets/channel-BZxwC-Gw.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BnEsyN36.js → chunk-2Q5K7J3B-BJSCL-HV.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BXATQ52s.js → chunk-3FUC2YCW-D3_ILccd.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-B0BpTkEx.js → chunk-5VM5RSS4-D3dmwT_S.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-BtLTz6FR.js → chunk-GTNCS2PH-Br73HFPD.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-D6gcZOYt.js → chunk-GWA4HPMP-uSsjxubC.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DCLrosvf.js → chunk-JWPE2WC7-C40VWFyr.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-B9JsknV9.js → chunk-NETBCI7D-D6WNNYwU.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CsoTxw7z.js → chunk-WEXAMYUT-DOEhq64P.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-0F_frB0c.js → chunk-XXDRQBXY-BqJEi6Pi.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-DkrpnflX.js → classDiagram-v2-NBCMYWYE-ByU_GnxT.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-BNDwYBt9.js → cose-bilkent-JH36ORCC-D-Uglxqq.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-JUkNr1Mk.js → cynefin-OW5HDTMX-CZWO7aYj.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-DKeh9N4M.js → cynefinDiagram-VND7K2PF-C70-w0la.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-ve-6T_wX.js → dagre-6A5THRUB-CnhjhExB.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-BuJ2d8rT.js → diagram-22UHCM2B-bnjWCNb4.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-BgOs_Ni-.js → diagram-3UASUU5V-A4uTWoFV.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-DMK6VmcP.js → diagram-ATOU4E4O-Zi6m3NUl.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-DksASlcY.js → diagram-CDSNMT55-DtCcmd5k.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-Bgn8I5k6.js → diagram-MLGK6HIB-DofuETS3.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-zK0jqO8N.js → diagram-MPIPVDR6-Dw2T9PuU.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-vKrbzTwr.js → ebnfDiagram-ZINNZB2B-DbrwPiem.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-Bzv2mPNf.js → elk-276RUBZZ-BvCt1dCb.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-CvOyYuxu.js → erDiagram-OPXOYQCR-Y-QKqPnU.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-CtbnDbc0.js → flowDiagram-KWPJA3E3-CDkL1B5w.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-c4dMRYyG.js → ganttDiagram-FUAMR5RP-CKV5D2F6.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-B34uaQho.js → gitGraphDiagram-X574FWY7-nL9iFTCv.js} +1 -1
  34. package/dist/assets/{index-CPiqc_DN.js → index-DreDnjOu.js} +1 -1
  35. package/dist/assets/index-Du7o6Dsf.js +610 -0
  36. package/dist/assets/index-KxG4bWSr.css +1 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-o64ma4CI.js → infoDiagram-VRGFBTTK-C7aHjzsf.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-DV4C9BAm.js → ishikawaDiagram-OU5B5YK6-KUZsNBTh.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-Bf0AHk1H.js → journeyDiagram-ZHPQQLJL-CGUuodFl.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-B1UMSKmE.js → kanban-definition-PNTS6WVX-Cy6r868Z.js} +1 -1
  41. package/dist/assets/{linear-BpKhuyLm.js → linear-Bl669148.js} +1 -1
  42. package/dist/assets/{mermaid.core-B_BKNCw5.js → mermaid.core-86Xqz5Mr.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-DeARruT1.js → mindmap-definition-NLK3R4M7-G4DzHLrd.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-BOQEk_1D.js → pegDiagram-GJSIUBJH-K4eruzY0.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-EIfoiTZo.js → pieDiagram-5QR66LMP-DYdR9PPq.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-GgZwT-fi.js → quadrantDiagram-O4NWA36T-BCAh0nEy.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-D9rDQomj.js → railroadDiagram-XR7U4H2S-p0dGRc4g.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DAh1MIeo.js → requirementDiagram-PLB6GJNP-tLeBG0aL.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-CPr6Uz64.js → sankeyDiagram-IPEJSGJF-QRuN16rV.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-BU6VIdcm.js → sequenceDiagram-PO4LG4MO-ueT1nVoa.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-qF2HO41r.js → sizeCapture-INFHLROL-wRS2zWg2.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-DlJ8G4MD.js → stateDiagram-v2-GCMORJYK-CYzA_hE1.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DzYE8I54.js → swimlanes-2SLR337P-kDnv6LMc.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-B7wzEQ0A.js → timeline-definition-EJHVYXUP-D1y2UbbH.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-BvGgrxIP.js → usecaseDiagram-POWQR4AR-IqB4v9HX.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-CEECdgQg.js → vennDiagram-UO4OBE2U-BEi7KYVF.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-XQHA0P_P.js → wardleyDiagram-VNRHLVJA-Dkybo-Yf.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-B5mLcZI2.js → xychartDiagram-PMCCYNJV-BAPFJLpg.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +27 -13
  62. package/dist-server/companion/src/listener.js +39 -18
  63. package/dist-server/companion/src/routes.js +39 -0
  64. package/dist-server/container-mcp.js +16 -29
  65. package/dist-server/drivers/agents-proxy.js +161 -82
  66. package/dist-server/enterprise/server/index.js +2 -1
  67. package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +34 -19
  68. package/dist-server/index.js +4991 -4908
  69. package/dist-server/local-computer.js +2 -1
  70. package/dist-server/openmausbot.js +906 -1785
  71. package/dist-server/pair-cli.js +906 -1785
  72. package/dist-server/proxy-paths.js +2 -1
  73. package/dist-server/server/account-signin.js +0 -2
  74. package/dist-server/server/activity.js +201 -0
  75. package/dist-server/server/admin-activity.js +1 -4
  76. package/dist-server/server/auto-approve.js +25 -4
  77. package/dist-server/server/automatic-recovery.js +1 -1
  78. package/dist-server/server/boat-create-idempotency.js +0 -7
  79. package/dist-server/server/boat-delete-journal.js +0 -4
  80. package/dist-server/server/boat.js +4 -4
  81. package/dist-server/server/bot-package.js +1 -19
  82. package/dist-server/server/browser-runtime.js +1 -2
  83. package/dist-server/server/checkpoints.js +90 -148
  84. package/dist-server/server/chief-of-staff.js +9 -9
  85. package/dist-server/server/cli.js +33 -26
  86. package/dist-server/server/cloud-backend.js +0 -4
  87. package/dist-server/server/cloud-computer-tools.js +110 -0
  88. package/dist-server/server/cloud-home-start.js +12 -15
  89. package/dist-server/server/cloud-home.js +0 -5
  90. package/dist-server/server/cloud-lending.js +0 -3
  91. package/dist-server/server/cloud-move-http.js +44 -30
  92. package/dist-server/server/cloud-move.js +20 -18
  93. package/dist-server/server/composio.js +41 -5
  94. package/dist-server/server/computer-control.js +0 -15
  95. package/dist-server/server/computer-observation.js +0 -74
  96. package/dist-server/server/config.js +121 -65
  97. package/dist-server/server/container-computer.js +23 -24
  98. package/dist-server/server/control-client.js +4 -10
  99. package/dist-server/server/decider/jev.js +1 -4
  100. package/dist-server/server/decider/log.js +1 -3
  101. package/dist-server/server/decision-log.js +1 -4
  102. package/dist-server/server/delegations.js +3 -2
  103. package/dist-server/server/drivers/acp/core.js +11 -8
  104. package/dist-server/server/drivers/acp/opencode-go.js +17 -16
  105. package/dist-server/server/drivers/agents-call.js +88 -92
  106. package/dist-server/server/drivers/agents-catalog.js +90 -11
  107. package/dist-server/server/drivers/agents-proxy.js +10 -31
  108. package/dist-server/server/drivers/antigravity.js +1 -4
  109. package/dist-server/server/drivers/boatagent.js +23 -4
  110. package/dist-server/server/drivers/cerebras.js +3 -3
  111. package/dist-server/server/drivers/chat-mcp-tools.js +4 -7
  112. package/dist-server/server/drivers/claude-login-auth.js +2 -11
  113. package/dist-server/server/drivers/claude.js +16 -14
  114. package/dist-server/server/drivers/codex-device-auth.js +3 -1
  115. package/dist-server/server/drivers/codex.js +5 -4
  116. package/dist-server/server/drivers/grok.js +1 -1
  117. package/dist-server/server/drivers/openai-chat-protocol.js +4 -2
  118. package/dist-server/server/drivers/openai-chat.js +18 -11
  119. package/dist-server/server/drivers/prompt-split.js +4 -4
  120. package/dist-server/server/drivers/retry.js +8 -4
  121. package/dist-server/server/engine-install.js +1 -5
  122. package/dist-server/server/harness/bus.js +49 -0
  123. package/dist-server/server/harness/registry.js +0 -1
  124. package/dist-server/server/{browser-proxy.js → harness-mcp-proxy.js} +40 -20
  125. package/dist-server/server/index.js +1190 -971
  126. package/dist-server/server/lending-memory.js +12 -8
  127. package/dist-server/server/mcp-registry.js +35 -0
  128. package/dist-server/server/memory-entries.js +38 -8
  129. package/dist-server/server/memory-organize.js +1 -8
  130. package/dist-server/server/memory-topics.js +4 -4
  131. package/dist-server/server/memory-upkeep.js +13 -15
  132. package/dist-server/server/message-file.js +9 -13
  133. package/dist-server/server/outbound-counts.js +79 -0
  134. package/dist-server/server/outbound-requests.js +60 -0
  135. package/dist-server/server/package-import.js +1 -1
  136. package/dist-server/server/peer-delivery.js +5 -19
  137. package/dist-server/server/peer-roster.js +30 -4
  138. package/dist-server/server/profile-versions.js +0 -31
  139. package/dist-server/server/provider-key-check.js +2 -1
  140. package/dist-server/server/proxy-paths.js +2 -1
  141. package/dist-server/server/restart.js +40 -0
  142. package/dist-server/server/room-handoffs.js +17 -13
  143. package/dist-server/server/routes/bot-memory.js +0 -24
  144. package/dist-server/server/routine-requests.js +2 -5
  145. package/dist-server/server/routines.js +10 -5
  146. package/dist-server/server/server-launcher.js +53 -0
  147. package/dist-server/server/setup-mode.js +0 -2
  148. package/dist-server/server/skill-learn.js +0 -1
  149. package/dist-server/server/skill-library.js +0 -15
  150. package/dist-server/server/store.js +10 -2
  151. package/dist-server/server/surface.js +69 -28
  152. package/dist-server/server/system-prompt.js +17 -7
  153. package/dist-server/server/team-manifest.js +1 -6
  154. package/dist-server/server/team-memory.js +209 -0
  155. package/dist-server/server/thread-events.js +2 -2
  156. package/dist-server/server/tunnel.js +4 -1
  157. package/dist-server/server/turn-images.js +1 -21
  158. package/dist-server/server/turn-resources.js +19 -150
  159. package/dist-server/server/usage-ledger.js +3 -1
  160. package/dist-server/server/vps-computer.js +4 -14
  161. package/dist-server/server/workspace.js +87 -50
  162. package/dist-server/server-launcher.js +72 -0
  163. package/dist-server/shared/activity.js +3 -0
  164. package/dist-server/shared/ask-question.js +1 -1
  165. package/dist-server/shared/bot-avatar.js +8 -0
  166. package/dist-server/shared/cloud-computer.js +12 -0
  167. package/dist-server/shared/connector-scopes.js +78 -0
  168. package/dist-server/shared/failed-turn.js +25 -0
  169. package/dist-server/shared/live-approval.js +2 -2
  170. package/dist-server/shared/outbound.js +128 -0
  171. package/dist-server/shared/package-format.js +3 -3
  172. package/dist-server/shared/pairing-link.js +144 -0
  173. package/dist-server/shared/tool-surface.js +3 -2
  174. package/dist-server/vps-container-mcp.js +16 -29
  175. package/dist-server/workspace-backup.worker.js +14 -27
  176. package/enterprise/server/index.js +2 -1
  177. package/package.json +1 -1
  178. package/dist/assets/channel-CdJX6i8k.js +0 -1
  179. package/dist/assets/index-CPYrGsv1.js +0 -610
  180. package/dist/assets/index-CzT42GR8.css +0 -1
  181. package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +0 -8
  182. package/dist-server/server/claim-idle.js +0 -33
  183. package/dist-server/server/cloud-overflow.js +0 -167
  184. package/dist-server/server/drivers/chat-boat-tools.js +0 -90
  185. package/dist-server/server/drivers/grokagent.js +0 -4
  186. package/dist-server/server/openmaus-status-capsule.js +0 -329
  187. package/dist-server/server/tightening-requests.js +0 -435
  188. package/dist-server/shared/tightening-request.js +0 -87
@@ -8,7 +8,8 @@ function resolveProxy(relative) {
8
8
  return existsSync(source) ? source : join(SERVER_ROOT, `${relative}.js`);
9
9
  }
10
10
  var SPAWNED_PROXIES = {
11
- browser: resolveProxy("browser-proxy"),
11
+ // The built-in browser and the cloud computer: argv[2] picks which.
12
+ harnessMcp: resolveProxy("harness-mcp-proxy"),
12
13
  localComputer: resolveProxy("local-computer-proxy"),
13
14
  permission: resolveProxy("permission-proxy"),
14
15
  containerMcp: resolveProxy("container-mcp"),
@@ -11,8 +11,6 @@
11
11
  // not expose the routes.
12
12
  import { resolveCompanionControlPlaneURL } from "../electron/companion-account-service.mjs";
13
13
  import { ControlPlaneError, createControlPlaneClient, normalizeAccountEmail } from "../electron/control-plane-client.mjs";
14
- export const ADMIN_EMAILS_ENV = "OMB_SIGNIN_EMAILS";
15
- export const MEMBER_EMAILS_ENV = "OMB_SIGNIN_MEMBER_EMAILS";
16
14
  /** Commas, spaces or newlines between entries; case does not matter. */
17
15
  export function parseAllowList(value) {
18
16
  return (value ?? "")
@@ -0,0 +1,201 @@
1
+ // A bot's activity log: what it did, in plain words, with the outcome.
2
+ //
3
+ // Nothing here is captured for the log's sake. Two records already on disk
4
+ // between them know everything a receipt needs: the per-thread runtime
5
+ // events (harness/bus.ts) say what ran and whether it finished, and the
6
+ // fleet-wide decision log (decision-log.ts) says what was asked, with which
7
+ // arguments, and who allowed it. This module reads both back and folds
8
+ // them into one row per thing that happened.
9
+ //
10
+ // Connector calls pass through the harness relay, whose approval and scope
11
+ // decisions share the decision log. Read those alongside native tool runs.
12
+ import { join } from "node:path";
13
+ import { readDecisions } from "./decision-log.js";
14
+ import { isRuntimeEvent, readRecentLines } from "./thread-events.js";
15
+ /** Composio toolkit slugs whose display name is not just a capitalised word. */
16
+ const TOOLKIT_NAMES = {
17
+ GMAIL: "Gmail",
18
+ GITHUB: "GitHub",
19
+ GOOGLESHEETS: "Google Sheets",
20
+ GOOGLECALENDAR: "Google Calendar",
21
+ GOOGLEDRIVE: "Google Drive",
22
+ GOOGLEDOCS: "Google Docs",
23
+ HUBSPOT: "HubSpot",
24
+ LINKEDIN: "LinkedIn",
25
+ YOUTUBE: "YouTube",
26
+ TWITTER: "X",
27
+ WHATSAPP: "WhatsApp",
28
+ ONEDRIVE: "OneDrive",
29
+ };
30
+ /** The agent's own built-in tools, phrased as what they did. */
31
+ const BUILTIN_LABELS = {
32
+ Bash: "Ran a command",
33
+ Read: "Read a file",
34
+ Edit: "Edited a file",
35
+ MultiEdit: "Edited files",
36
+ Write: "Wrote a file",
37
+ Glob: "Searched files",
38
+ Grep: "Searched files",
39
+ LS: "Listed a folder",
40
+ WebSearch: "Searched the web",
41
+ WebFetch: "Fetched a page",
42
+ Skill: "Used a skill",
43
+ Task: "Ran a subagent",
44
+ Agent: "Ran a subagent",
45
+ AskUserQuestion: "Asked you a question",
46
+ TodoWrite: "Updated its task list",
47
+ };
48
+ /** "save_issue" → "Save issue", "TaskUpdate" → "Task update",
49
+ * "CREATE_AN_ISSUE" → "Create an issue". */
50
+ function humanize(raw) {
51
+ const spaced = raw
52
+ .replace(/_+/g, " ")
53
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
54
+ .replace(/\s+/g, " ")
55
+ .trim()
56
+ .toLowerCase();
57
+ return spaced ? spaced[0].toUpperCase() + spaced.slice(1) : raw;
58
+ }
59
+ function toolkitName(slug) {
60
+ return TOOLKIT_NAMES[slug] ?? slug[0] + slug.slice(1).toLowerCase();
61
+ }
62
+ /** Composio names its tools TOOLKIT_ACTION_WORDS, all upper case. */
63
+ const COMPOSIO_TOOL = /^([A-Z][A-Z0-9]+)_([A-Z0-9_]+)$/;
64
+ /** Turn a raw tool name into the app it touched and the action, in words. */
65
+ export function describeTool(name) {
66
+ const builtin = BUILTIN_LABELS[name];
67
+ if (builtin)
68
+ return { app: null, label: builtin };
69
+ if (name.startsWith("mcp__")) {
70
+ const [, server = "", ...rest] = name.split("__");
71
+ const action = rest.join("__");
72
+ if (server === "composio")
73
+ return describeTool(action);
74
+ if (server === "computer")
75
+ return { app: "Computer", label: humanize(action) };
76
+ if (server === "agents")
77
+ return { app: "Team", label: humanize(action) };
78
+ if (server.startsWith("claude_ai_")) {
79
+ return { app: server.slice("claude_ai_".length).replace(/_/g, " "), label: humanize(action) };
80
+ }
81
+ return { app: humanize(server), label: humanize(action) };
82
+ }
83
+ const composio = COMPOSIO_TOOL.exec(name);
84
+ if (composio)
85
+ return { app: toolkitName(composio[1]), label: humanize(composio[2]) };
86
+ return { app: null, label: humanize(name) };
87
+ }
88
+ /** How many decision rows to read back before filtering to one bot. The
89
+ * file is rotation-capped, so this is a ceiling on parse work, not a
90
+ * window that quietly drops history. */
91
+ const DECISION_SCAN = 20_000;
92
+ /** Runtime events read per thread, newest first; the reader tail-walks. */
93
+ const EVENTS_PER_THREAD = 2_000;
94
+ /** An approval is folded into the tool run it unblocked when the run
95
+ * started inside this window around the decision. A verdict is written
96
+ * as the request opens; the run follows it, occasionally by a clock skew's
97
+ * worth in the other direction. */
98
+ const MATCH_BEFORE_MS = 5_000;
99
+ const MATCH_AFTER_MS = 30_000;
100
+ const OUTCOME_OF_DECISION = {
101
+ "auto-approved": "allowed",
102
+ "auto-denied": "denied",
103
+ "user-approved": "allowed",
104
+ "review-would-approve": "allowed",
105
+ "user-denied": "denied",
106
+ "review-would-deny": "denied",
107
+ "card-shown": "waiting",
108
+ };
109
+ function describeRow(base) {
110
+ const { app, label } = describeTool(base.tool);
111
+ return { ...base, app, label };
112
+ }
113
+ /** One row per request: the decision log writes a row when a card is shown
114
+ * and another when it is answered, and the receipt wants the answer. */
115
+ function foldDecisions(rows) {
116
+ const byRequest = new Map();
117
+ rows.forEach((row, index) => {
118
+ const key = row.requestId ?? `row:${index}`;
119
+ const group = byRequest.get(key);
120
+ if (group)
121
+ group.push(row);
122
+ else
123
+ byRequest.set(key, [row]);
124
+ });
125
+ const folded = [];
126
+ for (const group of byRequest.values()) {
127
+ const opened = group[0];
128
+ const latest = group[group.length - 1];
129
+ const summary = group.find((row) => row.summary)?.summary;
130
+ folded.push(describeRow({
131
+ at: opened.at,
132
+ threadId: opened.threadId,
133
+ requestId: opened.requestId,
134
+ tool: latest.tool ?? opened.tool ?? "unknown",
135
+ ...(summary ? { summary } : {}),
136
+ outcome: OUTCOME_OF_DECISION[latest.decision],
137
+ }));
138
+ }
139
+ return folded;
140
+ }
141
+ function readToolRuns(eventsDir, threadId) {
142
+ const { lines } = readRecentLines(join(eventsDir, `${threadId}.ndjson`), EVENTS_PER_THREAD, isRuntimeEvent);
143
+ const runs = new Map();
144
+ for (const event of lines) {
145
+ if (event.type === "item.started" && event.itemType === "tool") {
146
+ const key = JSON.stringify([event.turnId ?? "", event.itemId ?? event.eventId]);
147
+ runs.set(key, describeRow({
148
+ at: event.createdAt,
149
+ threadId,
150
+ ...(event.turnId ? { turnId: event.turnId } : {}),
151
+ tool: event.title ?? "unknown",
152
+ outcome: "running",
153
+ }));
154
+ }
155
+ else if (event.type === "item.completed" && event.itemType === "tool" && event.itemId) {
156
+ const run = runs.get(JSON.stringify([event.turnId ?? "", event.itemId]));
157
+ if (run)
158
+ run.outcome = event.ok ? "ran" : "failed";
159
+ }
160
+ }
161
+ return [...runs.values()];
162
+ }
163
+ /** The bot's activity, newest first: every tool run in the threads named,
164
+ * and every decision the broker logged for the bot, with an approval folded
165
+ * into the run it unblocked wherever the two can be matched. */
166
+ export function readBotActivity(input) {
167
+ const { dataDir, eventsDir, botId, threadIds, limit } = input;
168
+ const runs = threadIds.flatMap((threadId) => readToolRuns(eventsDir, threadId));
169
+ const decisions = foldDecisions(readDecisions(dataDir, DECISION_SCAN).filter((row) => row.botId === botId));
170
+ const matched = new Set();
171
+ const rows = [...runs];
172
+ for (const decision of decisions) {
173
+ if (decision.outcome !== "allowed") {
174
+ rows.push(decision);
175
+ continue;
176
+ }
177
+ const askedAt = Date.parse(decision.at);
178
+ let best = null;
179
+ for (const run of runs) {
180
+ if (matched.has(run) || run.threadId !== decision.threadId || run.tool !== decision.tool)
181
+ continue;
182
+ const delta = Date.parse(run.at) - askedAt;
183
+ if (delta < -MATCH_BEFORE_MS || delta > MATCH_AFTER_MS)
184
+ continue;
185
+ if (!best || Math.abs(delta) < Math.abs(Date.parse(best.at) - askedAt))
186
+ best = run;
187
+ }
188
+ if (best) {
189
+ matched.add(best);
190
+ if (decision.summary)
191
+ best.summary = decision.summary;
192
+ if (decision.requestId)
193
+ best.requestId = decision.requestId;
194
+ }
195
+ else {
196
+ rows.push(decision);
197
+ }
198
+ }
199
+ rows.sort((a, b) => (a.at < b.at ? 1 : a.at > b.at ? -1 : 0));
200
+ return rows.slice(0, Math.max(1, limit));
201
+ }
@@ -20,16 +20,13 @@ import { appendFile, mkdir } from "node:fs/promises";
20
20
  import { basename, join } from "node:path";
21
21
  import { actorLabel, boundRetentionDays, pruneMonthFiles } from "./decision-log.js";
22
22
  import { redactSecrets } from "./redact.js";
23
- import { csvCell } from "./usage-ledger.js";
23
+ import { csvCell, monthKey } from "./usage-ledger.js";
24
24
  export const ADMIN_ACTIVITY_CATEGORIES = ["config", "people", "session", "webhook", "mcp", "engine", "bot", "budget", "visibility"];
25
25
  const DIR = "admin-activity";
26
26
  const MONTH_FILE = /^(\d{4})-(\d{2})\.ndjson$/;
27
27
  const PRUNE_EVERY_MS = 60 * 60_000;
28
28
  const writeQueues = new Map();
29
29
  const lastPrune = new Map();
30
- function monthKey(at) {
31
- return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
32
- }
33
30
  export function adminActivityFileFor(dataDir, at) {
34
31
  return join(dataDir, DIR, `${monthKey(at)}.ndjson`);
35
32
  }
@@ -3,10 +3,13 @@
3
3
  // Nothing here decides whether an action is safe. Each approval level is a
4
4
  // provider's own permission mode passed straight through (Claude `auto`,
5
5
  // Grok `--permission-mode`, Codex `approvalsReviewer`, …), and a request that
6
- // reaches this process is one the provider left for a person. The only
7
- // grants the app applies are Full access and the person's exact saved commands.
8
- // Questions never come through here: a bot's question always reaches a human.
6
+ // reaches this process is one the provider left for a person. The grants the
7
+ // app applies are Full access and the person's exact saved commands. Approve
8
+ // for me also allows a web search, and that is the one action the app itself
9
+ // grants. Questions never come through here: a bot's question always reaches
10
+ // a human.
9
11
  import { supportsApprovalMode } from "../shared/approval-mode.js";
12
+ import { isOutboundTool } from "../shared/outbound.js";
10
13
  /** A failed delivery is a runtime error, not another permission decision.
11
14
  * An expired ask must never become a fresh Allow/Deny card. */
12
15
  export async function deliverFullAccessApproval(adapter, threadId, requestId, turnId, isCurrent = () => false) {
@@ -77,11 +80,19 @@ export function delegatedApprovalMode(input) {
77
80
  // produce an answer: the CLI runs the tool with none and the model is told
78
81
  // "The user did not answer the questions." — a question silently lost.
79
82
  const ASKS_A_PERSON = new Set(["askuserquestion", "ask_user", "omb-ask"]);
83
+ // A web search is a low-level read. Approve for me allows these names and
84
+ // nothing wider: a bare search, a page fetch, and every other tool stay a
85
+ // prompt. The name is the same shape as a question — one mcp__<server>__
86
+ // prefix removed, then compared case-insensitively.
87
+ const WEB_SEARCH = new Set(["websearch", "web_search", "web-search", "search_web"]);
88
+ function bareToolName(tool) {
89
+ return tool.replace(/^mcp__[^_]+__/, "").toLowerCase();
90
+ }
80
91
  export function autoVerdict(mode, tool, context) {
81
92
  // A question is for a person, whatever channel it arrived on — and
82
93
  // whatever the mode: even Full has no answer to give, only an approval
83
94
  // that would run the tool with none.
84
- if (ASKS_A_PERSON.has(tool.replace(/^mcp__[^_]+__/, "").toLowerCase())) {
95
+ if (ASKS_A_PERSON.has(bareToolName(tool))) {
85
96
  return { approve: null, source: "no-grant" };
86
97
  }
87
98
  // Full's promise is literal: even a sandbox widening is approved. Entering
@@ -91,8 +102,15 @@ export function autoVerdict(mode, tool, context) {
91
102
  return { approve: `approved ${tool} (full access)`, source: "full-access" };
92
103
  if (context?.requiresExplicitApproval)
93
104
  return { approve: null, source: "explicit-approval-block" };
105
+ if (isOutboundTool(tool))
106
+ return { approve: null, source: "outbound-guard" };
94
107
  if (context?.commandAllowed)
95
108
  return { approve: `approved ${tool} (saved command)`, source: "command-allowlist" };
109
+ // The one action the app itself grants. A fetch of an arbitrary URL is not
110
+ // a search and stays a prompt, in this mode and every other.
111
+ if (mode === "auto" && WEB_SEARCH.has(bareToolName(tool))) {
112
+ return { approve: `approved ${tool} (web search)`, source: "web-search" };
113
+ }
96
114
  if (mode === "auto" || mode === "custom")
97
115
  return { approve: null, source: "native-approval" };
98
116
  return { approve: null, source: "no-grant" };
@@ -105,6 +123,7 @@ export function autoVerdict(mode, tool, context) {
105
123
  * the server keeps sending: cards saved before the key existed still render,
106
124
  * and so do the free-text apply errors that have no key at all. */
107
125
  export const HELD_NOTE = {
126
+ "approval.held.outbound": "This sends something on your behalf, so it always asks first.",
108
127
  "approval.held.native": "The provider requires your approval for this action.",
109
128
  "approval.held.sandbox": "This changes the provider sandbox, so only Full access can approve it automatically.",
110
129
  "approval.held.undeliveredFull": "Full access couldn't deliver this approval.",
@@ -115,6 +134,8 @@ export const HELD_NOTE = {
115
134
  export function approvalHeldNote(context) {
116
135
  if (!context.permission)
117
136
  return undefined;
137
+ if (context.source === "outbound-guard")
138
+ return "approval.held.outbound";
118
139
  if (context.source === "explicit-approval-block")
119
140
  return "approval.held.sandbox";
120
141
  if (context.source === "native-approval")
@@ -7,7 +7,7 @@ export function recoveryCapabilityError(from, to) {
7
7
  supportsWorkspaceFiles(from.driverKind) !== supportsWorkspaceFiles(to.driverKind)) {
8
8
  return "The backup cannot use the same workspace.";
9
9
  }
10
- const tools = ["agentsMcp", "computerMcp", "cloudComputerMcp", "composioMcp",
10
+ const tools = ["agentsMcp", "computerMcp", "composioMcp",
11
11
  "phoneMcp", "browserMcp", "localComputerMcp", "customMcp", "images"];
12
12
  if (tools.some(key => from.capabilities[key] === true && to.capabilities[key] !== true)) {
13
13
  return "The backup does not support all of this engine's tools and attachments.";
@@ -388,13 +388,6 @@ export function resolveBoatCreate(request) {
388
388
  return { ...resolved };
389
389
  });
390
390
  }
391
- /** Read-only deletion guard. Both a key-only request with an ambiguous
392
- * provider outcome and a known-but-not-yet-named Boat must keep its bot owner. */
393
- export function hasUnresolvedBoatCreate(botId) {
394
- if (!BOT_ID.test(botId))
395
- throw new Error("invalid bot id for cloud computer creation");
396
- return withJournalLock((requests) => (requests.some((request) => request.botId === botId && request.resolved !== true)));
397
- }
398
391
  /** Sanitized local recovery authority for configuration guards. The fresh,
399
392
  * lock-protected read can prove a remembered provider id even while the
400
393
  * account-wide LIST endpoint is eventually consistent. Provider request
@@ -442,7 +442,6 @@ export function markBoatDeletionBlocked(boxId, value) {
442
442
  export function boatDeletionSnapshot() {
443
443
  return withJournalLock((records) => records.map(clone));
444
444
  }
445
- export const listBoatDeletions = boatDeletionSnapshot;
446
445
  export function getBoatDeletion(boxId) {
447
446
  if (!BOAT_ID.test(boxId))
448
447
  throw new Error("invalid cloud computer id for deletion");
@@ -451,9 +450,6 @@ export function getBoatDeletion(boxId) {
451
450
  return record ? clone(record) : null;
452
451
  });
453
452
  }
454
- export function isBoatDeletionPending(boxId) {
455
- return getBoatDeletion(boxId) !== null;
456
- }
457
453
  export function hasPendingBoatDeletionForBot(botId) {
458
454
  if (!BOT_ID.test(botId))
459
455
  throw new Error("invalid bot id for cloud computer deletion");
@@ -88,9 +88,7 @@ function scopedBoatPrefix() {
88
88
  }
89
89
  /** Decide lifecycle work before a turn mounts Boat. Auto may observe and
90
90
  * attach an already-ready Boat, but only explicit Cloud may create or wake. */
91
- export function boatTurnLifecycleAction({ explicitCloud, canMount, state, }) {
92
- if (!canMount)
93
- return "none";
91
+ export function boatTurnLifecycleAction({ explicitCloud, state, }) {
94
92
  if (state && READY.has(state))
95
93
  return "attach";
96
94
  if (!explicitCloud)
@@ -828,7 +826,9 @@ export async function findBoat(cfg, botId) {
828
826
  if (cachedId) {
829
827
  let direct = null;
830
828
  try {
831
- direct = await boatJson(cfg, `/boxes/${cachedId}`);
829
+ // Bounded like every other Boat read: a relay that accepts the
830
+ // connection and stalls must not hold a turn's setup for minutes.
831
+ direct = await boatJson(cfg, `/boxes/${cachedId}`, { signal: AbortSignal.timeout(20_000) });
832
832
  }
833
833
  catch {
834
834
  // A direct read can fail while the account listing still succeeds.
@@ -1,15 +1,11 @@
1
1
  import { stringify as stringifyYaml } from "yaml";
2
2
  import { cronScheduleLabel } from "../shared/cron-label.js";
3
- import { BOTMRR_MARKDOWN_VERSION, isPackageDocument, PACKAGE_FORMAT, PACKAGE_V1_MAX_SKILLS, PACKAGE_V1_VERSION, parsePackageV1, utf8Bytes, } from "../shared/package-format.js";
3
+ import { BOTMRR_MARKDOWN_VERSION, isPackageDocument, PACKAGE_V1_MAX_SKILLS, parsePackageV1, utf8Bytes, } from "../shared/package-format.js";
4
4
  // The package file format (v1 and v2) lives in shared/package-format.ts: one
5
5
  // validation gate for the server, the renderer and Admin. This module keeps
6
6
  // the v1 names the v1 exporter and its Markdown playbook are written in, and
7
7
  // re-exports the v2 reader so server code has one place to import from.
8
8
  export { canonicalJson, downgradeToV1, PACKAGE_MAX_BYTES, PACKAGE_VERSION, PackageFormatError, packageKeys, packageScanFindings, packageSecretFindings, packageSummary, parsePackageDocument, redactPackageSecrets, upgradeV1, } from "../shared/package-format.js";
9
- export const BOT_PACKAGE_FORMAT = PACKAGE_FORMAT;
10
- export const BOT_PACKAGE_VERSION = PACKAGE_V1_VERSION;
11
- export { BOTMRR_MARKDOWN_VERSION };
12
- export const BOT_PACKAGE_SKILLS_VERSION = 1;
13
9
  export const BOT_PACKAGE_MAX_SKILLS = PACKAGE_V1_MAX_SKILLS;
14
10
  const BOT_PACKAGE_MARKDOWN_MAX_BYTES = 1_000_000;
15
11
  export function isBotPackage(value) {
@@ -98,17 +94,3 @@ export function renderBotPackageMarkdown(document) {
98
94
  throw new Error("The bot playbook is too large");
99
95
  return markdown;
100
96
  }
101
- export function packageAgentAsMember(agent) {
102
- return {
103
- key: agent.key,
104
- name: agent.name,
105
- title: agent.title ?? "",
106
- description: agent.description ?? "",
107
- ...(agent.soul !== undefined ? { soul: agent.soul } : {}),
108
- appearance: {
109
- color: agent.appearance.color,
110
- ...(agent.appearance.mascotExpression ? { mascotExpression: agent.appearance.mascotExpression } : {}),
111
- ...(agent.appearance.mascotBody ? { mascotBody: agent.appearance.mascotBody } : {}),
112
- },
113
- };
114
- }
@@ -216,8 +216,7 @@ export class BrowserRuntime {
216
216
  options;
217
217
  closeBrowser;
218
218
  constructor({ closeBrowser, ...options } = {}) {
219
- const budget = Number(process.env.OMB_BROWSER_RESULT_BUDGET);
220
- this.options = { requestTimeoutMs: 120_000, takeoverTimeoutMs: 15_000, idleMs: 60_000, maxPending: 16, resultBudget: Number.isFinite(budget) && budget > 0 ? budget : DEFAULT_BROWSER_RESULT_BUDGET, ...options };
219
+ this.options = { requestTimeoutMs: 120_000, takeoverTimeoutMs: 15_000, idleMs: 60_000, maxPending: 16, resultBudget: DEFAULT_BROWSER_RESULT_BUDGET, ...options };
221
220
  this.closeBrowser = closeBrowser ?? (async () => false);
222
221
  }
223
222
  gate(session) {