openmausbot 0.1.86 → 0.1.88

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 (216) hide show
  1. package/README.md +4 -3
  2. package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-qd9VD3GV.js} +1 -1
  3. package/dist/assets/{arc-DW7mrI4p.js → arc-3RwkUjRe.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-CFKSJS7a.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CG1rEfWD.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-CBW9N664.js} +1 -1
  7. package/dist/assets/channel-CnRnO3tF.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-2mRfTksB.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-Cv1dpErj.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-B7IACJp-.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-An0hZtYa.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-BcJB2nEm.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-CV_W7TN7.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Bcs_kwnA.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-lrx_DxFP.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-DabHlLGM.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BnHj6P7d.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-CfLp6UDi.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Dm4dbVA1.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-B9mH-PID.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-DDc_NVQJ.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-lwr8Ip8-.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-BLLe6dmI.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-6eUIq4lz.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-O-pRN7VQ.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-BietvGAI.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-Bd2Cl62X.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-B6iDy69W.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-DQVHfKe0.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BWdVp2tQ.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-WLE6rKB4.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-BbjS-4gn.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-Dpy2QSGq.js} +1 -1
  34. package/dist/assets/index-BIMdIbaw.css +1 -0
  35. package/dist/assets/index-CHnODCr2.js +583 -0
  36. package/dist/assets/{index-h9FFNwTu.js → index-CIaHn5NS.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-D_jKWlKe.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-DLJohSuK.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DPOFj0qN.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-BI7v-xpK.js} +1 -1
  41. package/dist/assets/{linear-3su2TG93.js → linear-C2pWPSxO.js} +1 -1
  42. package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-CR-j5WQU.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-lz9GvXRh.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-Dl4_ia61.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-DWuT3n88.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-DXWe9Jx7.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-COY1IBVY.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-DnyVuhsQ.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-BSc4NdGK.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-kqEU2EGq.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-BvB4bkWD.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BIrqNv7y.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-CFnalQUE.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-CsSWJnSQ.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-DkAmJ1EE.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-C3vd9drL.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-j26CM-pi.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-BYu1Q_at.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/browser-proxy.js +2 -1
  62. package/dist-server/companion/src/routes.js +8 -1
  63. package/dist-server/connector-proxy.js +105 -1
  64. package/dist-server/container-mcp.js +1032 -165
  65. package/dist-server/drivers/agents-proxy.js +338 -29
  66. package/dist-server/drivers/phone-proxy.js +33 -2
  67. package/dist-server/index.js +40590 -31811
  68. package/dist-server/local-computer-proxy.js +5 -3
  69. package/dist-server/local-computer.js +53 -17
  70. package/dist-server/mcp-gate.js +2 -0
  71. package/dist-server/mcp-server.js +5 -3
  72. package/dist-server/message-search.worker.js +52 -0
  73. package/dist-server/openmausbot.js +4457 -1399
  74. package/dist-server/pair-cli.js +4457 -1399
  75. package/dist-server/permission-proxy.js +29 -9
  76. package/dist-server/server/admission.js +81 -0
  77. package/dist-server/server/aside-queue.js +294 -0
  78. package/dist-server/server/attachments.js +98 -1
  79. package/dist-server/server/auto-approve.js +4 -3
  80. package/dist-server/server/auto-vm-claims.js +1 -0
  81. package/dist-server/server/bot-attachment.js +107 -0
  82. package/dist-server/server/bot-overview.js +19 -0
  83. package/dist-server/server/bot-package.js +18 -292
  84. package/dist-server/server/bot-profile.js +5 -17
  85. package/dist-server/server/box-create-idempotency.js +3 -3
  86. package/dist-server/server/box.js +57 -55
  87. package/dist-server/server/browser-proxy.js +3 -2
  88. package/dist-server/server/browser-runtime.js +37 -1
  89. package/dist-server/server/cli-api-setup.js +1 -0
  90. package/dist-server/server/cli.js +2 -2
  91. package/dist-server/server/command-allowlist.js +154 -0
  92. package/dist-server/server/composio.js +297 -1
  93. package/dist-server/server/computer-wait.js +22 -9
  94. package/dist-server/server/config.js +84 -12
  95. package/dist-server/server/connector-advertisement.js +165 -0
  96. package/dist-server/server/connector-proxy.js +14 -2
  97. package/dist-server/server/connector-verdict.js +168 -0
  98. package/dist-server/server/container-computer.js +61 -1
  99. package/dist-server/server/contracts.js +22 -0
  100. package/dist-server/server/default-model-selection.js +9 -0
  101. package/dist-server/server/delegations.js +85 -34
  102. package/dist-server/server/drivers/acp/core.js +80 -20
  103. package/dist-server/server/drivers/acp/cursor.js +3 -7
  104. package/dist-server/server/drivers/acp/droid.js +4 -7
  105. package/dist-server/server/drivers/acp/gemini.js +4 -4
  106. package/dist-server/server/drivers/acp/grok.js +4 -9
  107. package/dist-server/server/drivers/acp/hermes.js +3 -5
  108. package/dist-server/server/drivers/acp/opencode-go.js +2 -5
  109. package/dist-server/server/drivers/acp/qwen.js +9 -15
  110. package/dist-server/server/drivers/agents-call.js +179 -13
  111. package/dist-server/server/drivers/agents-catalog.js +134 -10
  112. package/dist-server/server/drivers/boxagent.js +160 -36
  113. package/dist-server/server/drivers/builtIn.js +2 -0
  114. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  115. package/dist-server/server/drivers/chat-images.js +54 -0
  116. package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
  117. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  118. package/dist-server/server/drivers/claude.js +38 -19
  119. package/dist-server/server/drivers/codex-catalog.js +14 -6
  120. package/dist-server/server/drivers/codex-identity.js +2 -1
  121. package/dist-server/server/drivers/codex-instructions.js +48 -6
  122. package/dist-server/server/drivers/codex-release.js +48 -0
  123. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  124. package/dist-server/server/drivers/codex.js +102 -55
  125. package/dist-server/server/drivers/mistral.js +115 -0
  126. package/dist-server/server/drivers/openai-chat.js +180 -33
  127. package/dist-server/server/drivers/openai-compat.js +1 -0
  128. package/dist-server/server/drivers/permission-command.js +35 -0
  129. package/dist-server/server/drivers/phone-proxy.js +47 -2
  130. package/dist-server/server/drivers/pi.js +306 -28
  131. package/dist-server/server/drivers/prompt-split.js +110 -0
  132. package/dist-server/server/drivers/session-idle.js +29 -0
  133. package/dist-server/server/env-path.js +15 -0
  134. package/dist-server/server/fleet.js +1 -1
  135. package/dist-server/server/group-thread-usage.js +103 -0
  136. package/dist-server/server/guarded-requests.js +2 -2
  137. package/dist-server/server/harness/registry.js +1 -0
  138. package/dist-server/server/harness-capabilities.js +46 -0
  139. package/dist-server/server/hosted-models.js +1 -1
  140. package/dist-server/server/index.js +2396 -505
  141. package/dist-server/server/local-computer.js +72 -23
  142. package/dist-server/server/managed-desktop.js +1 -1
  143. package/dist-server/server/mcp-bridge.js +6 -6
  144. package/dist-server/server/message-db.js +22 -42
  145. package/dist-server/server/message-file.js +9 -1
  146. package/dist-server/server/message-search-query.js +43 -0
  147. package/dist-server/server/message-search-worker.js +80 -0
  148. package/dist-server/server/message-search.worker.js +21 -0
  149. package/dist-server/server/model-requests.js +248 -0
  150. package/dist-server/server/new-bot-defaults.js +115 -0
  151. package/dist-server/server/options-card.js +21 -0
  152. package/dist-server/server/org-library.js +1065 -0
  153. package/dist-server/server/package-export.js +627 -39
  154. package/dist-server/server/package-import.js +458 -0
  155. package/dist-server/server/package-parts.js +87 -0
  156. package/dist-server/server/peer-approval.js +16 -7
  157. package/dist-server/server/peer-delivery.js +60 -0
  158. package/dist-server/server/peer-roster.js +67 -0
  159. package/dist-server/server/presets.js +492 -0
  160. package/dist-server/server/profile-requests.js +66 -39
  161. package/dist-server/server/profile-revision.js +9 -1
  162. package/dist-server/server/profile-versions.js +6 -3
  163. package/dist-server/server/provider-key-check.js +3 -2
  164. package/dist-server/server/published-teams.js +80 -0
  165. package/dist-server/server/room-handoffs.js +19 -3
  166. package/dist-server/server/routes/bot-presets.js +25 -0
  167. package/dist-server/server/routine-requests.js +35 -10
  168. package/dist-server/server/routines.js +41 -1
  169. package/dist-server/server/section-context.js +10 -0
  170. package/dist-server/server/skill-fetch.js +72 -14
  171. package/dist-server/server/skills.js +35 -61
  172. package/dist-server/server/steer-queue.js +3 -2
  173. package/dist-server/server/store.js +291 -17
  174. package/dist-server/server/surface.js +10 -4
  175. package/dist-server/server/system-prompt.js +38 -6
  176. package/dist-server/server/team-backup.js +6 -2
  177. package/dist-server/server/team-computers.js +10 -0
  178. package/dist-server/server/team-library.js +6 -2
  179. package/dist-server/server/team-manifest.js +4 -3
  180. package/dist-server/server/team-setup-requests.js +139 -27
  181. package/dist-server/server/thread-events.js +20 -2
  182. package/dist-server/server/tightening-requests.js +405 -0
  183. package/dist-server/server/tts/chatterbox.js +27 -5
  184. package/dist-server/server/turn-context.js +2 -0
  185. package/dist-server/server/turn-watchdog.js +12 -3
  186. package/dist-server/server/usage-ledger.js +19 -2
  187. package/dist-server/server/vps-computer.js +139 -51
  188. package/dist-server/server/vps-container-mcp.js +7 -0
  189. package/dist-server/server/vps-ssh.js +36 -7
  190. package/dist-server/server/workspace-backup-policy.js +12 -0
  191. package/dist-server/server/workspace-backup.js +42 -5
  192. package/dist-server/server/workspace-backup.worker.js +10 -0
  193. package/dist-server/server/workspace.js +1 -1
  194. package/dist-server/shared/approval-mode.js +1 -1
  195. package/dist-server/shared/ask-question.js +207 -12
  196. package/dist-server/shared/bot-profile.js +15 -0
  197. package/dist-server/shared/command-allowlist.js +1 -0
  198. package/dist-server/shared/credential-request.js +6 -2
  199. package/dist-server/shared/line-diff.js +23 -0
  200. package/dist-server/shared/model-request.js +1 -0
  201. package/dist-server/shared/options-card.js +51 -0
  202. package/dist-server/shared/package-format.js +967 -0
  203. package/dist-server/shared/profile-request.js +5 -4
  204. package/dist-server/shared/skill-md.js +64 -0
  205. package/dist-server/shared/team-backup.js +7 -0
  206. package/dist-server/shared/tightening-request.js +87 -0
  207. package/dist-server/shared/wire.js +4 -0
  208. package/dist-server/src/lib/composer-attachments.js +25 -3
  209. package/dist-server/vps-container-mcp.js +1132 -171
  210. package/dist-server/workspace-backup.worker.js +19474 -0
  211. package/enterprise/README.md +1 -1
  212. package/package.json +1 -1
  213. package/dist/assets/channel-BXFkgus9.js +0 -1
  214. package/dist/assets/index-BGgqlWPJ.css +0 -1
  215. package/dist/assets/index-BtHo8ZKl.js +0 -577
  216. package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +0 -8
@@ -70,19 +70,21 @@ function sameDriverFileIdentity(expected, actual) {
70
70
  }
71
71
  function decodeLegacyDescriptor(value, platform) {
72
72
  const supportedPlatform = legacyPlatform(platform);
73
- if (!supportedPlatform || !value || value.mode === "unavailable" || typeof value.mcpCommand !== "string") {
73
+ if (!supportedPlatform || !value ||
74
+ !(value.mode === "embedded" || (supportedPlatform === "darwin" && value.mode === "standalone")) ||
75
+ (Object.hasOwn(value, "status") && value.status !== "ready") ||
76
+ typeof value.socketPath !== "string" || !value.socketPath ||
77
+ typeof value.mcpCommand !== "string" || !value.mcpCommand.trim()) {
74
78
  return null;
75
79
  }
76
- if (value.mcpArgs !== undefined && !Array.isArray(value.mcpArgs))
80
+ if (!Array.isArray(value.mcpArgs) || value.mcpArgs[0] !== "mcp")
77
81
  return null;
78
- if (value.mcpEnv !== undefined &&
79
- (!value.mcpEnv || typeof value.mcpEnv !== "object" || Array.isArray(value.mcpEnv))) {
82
+ if (!value.mcpEnv || typeof value.mcpEnv !== "object" || Array.isArray(value.mcpEnv))
80
83
  return null;
81
- }
82
- const args = value.mcpArgs ?? ["mcp"];
84
+ const args = value.mcpArgs;
83
85
  if (!args.every((arg) => typeof arg === "string"))
84
86
  return null;
85
- const env = value.mcpEnv ?? {};
87
+ const env = value.mcpEnv;
86
88
  if (!Object.values(env).every((entry) => typeof entry === "string"))
87
89
  return null;
88
90
  return {
@@ -288,31 +290,78 @@ function validateLegacyDescriptorRuntime(descriptorFile, platform, { uid = proce
288
290
  return false;
289
291
  }
290
292
  }
293
+ /** An older fallback cannot override a present but unavailable, malformed, or
294
+ * unreadable descriptor at a more specific location. */
295
+ function firstPresentCuaDescriptor(candidates) {
296
+ for (const file of new Set(candidates)) {
297
+ try {
298
+ lstatSync(file);
299
+ return file;
300
+ }
301
+ catch (error) {
302
+ if (error.code !== "ENOENT")
303
+ return file;
304
+ }
305
+ }
306
+ return null;
307
+ }
291
308
  export function readCuaConnection({ platform = process.platform, userData = process.env.OMB_USER_DATA, home = homedir(), validateLinuxRuntime = validateLinuxDescriptorRuntime, validateLegacyRuntime = validateLegacyDescriptorRuntime, } = {}) {
292
309
  const candidates = userData ? [join(userData, "cua-connection.json")] : [];
293
- if (platform === "darwin") {
294
- // Legacy/dev fallback. Packaged Electron passes its exact userData path.
310
+ if (platform === "darwin" && !userData) {
311
+ // Legacy/dev fallback only when Electron did not provide its exact path.
295
312
  for (const directory of ["OpenMausBot", "openmausbot", "OpenGrokBot", "opengrokbot"]) {
296
313
  candidates.push(join(home, "Library", "Application Support", directory, "cua-connection.json"));
297
314
  }
298
315
  }
299
- for (const file of new Set(candidates)) {
300
- try {
316
+ const file = firstPresentCuaDescriptor(candidates);
317
+ if (!file)
318
+ return null;
319
+ try {
320
+ const raw = JSON.parse(readFileSync(file, "utf8"));
321
+ if (platform === "linux") {
322
+ const decoded = decodeLinuxDescriptor(raw);
323
+ if (decoded && validateLinuxRuntime(file, raw))
324
+ return decoded;
325
+ }
326
+ else {
327
+ const decoded = decodeLegacyDescriptor(raw, platform);
328
+ if (decoded && validateLegacyRuntime(file, platform))
329
+ return decoded;
330
+ }
331
+ }
332
+ catch {
333
+ // Missing, invalid, tampered, or stale descriptors are unavailable.
334
+ }
335
+ return null;
336
+ }
337
+ /** An unavailable descriptor is diagnostic only. It never becomes a connection
338
+ * and only a private, well-formed macOS/Windows descriptor may supply text. */
339
+ export function readCuaUnavailableReason({ platform = process.platform, userData = process.env.OMB_USER_DATA, home = homedir(), } = {}) {
340
+ if (!legacyPlatform(platform))
341
+ return null;
342
+ const candidates = userData ? [join(userData, "cua-connection.json")] : [];
343
+ if (platform === "darwin" && !userData) {
344
+ for (const directory of ["OpenMausBot", "openmausbot", "OpenGrokBot", "opengrokbot"]) {
345
+ candidates.push(join(home, "Library", "Application Support", directory, "cua-connection.json"));
346
+ }
347
+ }
348
+ const file = firstPresentCuaDescriptor(candidates);
349
+ if (!file)
350
+ return null;
351
+ try {
352
+ if (validateLegacyDescriptorRuntime(file, platform)) {
301
353
  const raw = JSON.parse(readFileSync(file, "utf8"));
302
- if (platform === "linux") {
303
- const decoded = decodeLinuxDescriptor(raw);
304
- if (decoded && validateLinuxRuntime(file, raw))
305
- return decoded;
306
- }
307
- else {
308
- const decoded = decodeLegacyDescriptor(raw, platform);
309
- if (decoded && validateLegacyRuntime(file, platform))
310
- return decoded;
354
+ if (raw && typeof raw === "object" && !Array.isArray(raw) &&
355
+ Object.keys(raw).length === 2 &&
356
+ raw.mode === "unavailable") {
357
+ const reason = raw.reason;
358
+ if (typeof reason === "string" && reason.trim() && reason.length <= 2_000)
359
+ return reason.trim();
311
360
  }
312
361
  }
313
- catch {
314
- // Missing, invalid, tampered, or stale descriptors are unavailable.
315
- }
362
+ }
363
+ catch {
364
+ // No usable diagnostic; the caller still refuses computer control.
316
365
  }
317
366
  return null;
318
367
  }
@@ -20,7 +20,7 @@ const identitySchema = z.object({ portalOrigin: z.string().max(2048), organizati
20
20
  const PROVIDER_IDS = ["anthropic", "openai", "openrouter"];
21
21
  const signature = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
22
22
  const expiredMessage = "Company access has ended. Reconnect your organization or explicitly choose a personal model; personal billing will not be used automatically.";
23
- export const LICENSE_EXPIRED_MESSAGE = "Your organisation's OpenMaus Admin licence has expired. Contact your admin.";
23
+ export const LICENSE_EXPIRED_MESSAGE = "Your organization's OpenMaus Admin license has expired. Contact your admin.";
24
24
  /** Validate only messages received over Electron's private utility-parent port.
25
25
  * Never expose this input to renderer HTTP or merge it into saved AppConfig.
26
26
  */
@@ -36,14 +36,14 @@ import { createToolListNormalizer } from "./mcp-tool-schema.js";
36
36
  // stdin/stdout/stderr resets the window.
37
37
  export const BRIDGE_INACTIVITY_MS = 45_000;
38
38
  const PROBE_TIMEOUT_MS = 10_000;
39
- /** Run the liveness command; alive means "exited 0 within the timeout". The
40
- * probe is its own short-lived process, so it cannot inherit the wedged
41
- * connection it is diagnosing. */
42
- export function runLivenessProbe(probe, timeoutMs = PROBE_TIMEOUT_MS) {
39
+ /** Run the liveness command; alive means "exited 0 within the timeout". A
40
+ * separate bounded command checks the transport without waiting for an
41
+ * answer on the possibly wedged MCP stream. */
42
+ export function runLivenessProbe(probe, timeoutMs = PROBE_TIMEOUT_MS, env) {
43
43
  return new Promise((resolve) => {
44
44
  const child = spawn(probe.command, probe.args, {
45
45
  shell: false,
46
- env: { ...process.env, PATH: augmentedPath() },
46
+ env: env ?? { ...process.env, PATH: augmentedPath() },
47
47
  stdio: ["ignore", "ignore", "ignore"],
48
48
  });
49
49
  const timer = setTimeout(() => {
@@ -265,7 +265,7 @@ export function runMcpBridge(options) {
265
265
  const liveness = options.liveness;
266
266
  watchdog = createInactivityWatchdog({
267
267
  inactivityMs: BRIDGE_INACTIVITY_MS,
268
- probe: () => runLivenessProbe(liveness),
268
+ probe: () => runLivenessProbe(liveness, PROBE_TIMEOUT_MS, options.env),
269
269
  onDead: () => {
270
270
  process.stderr.write(`${options.label} transport went silent and stopped answering liveness probes; ending the bridge\n`);
271
271
  process.exitCode = 1;
@@ -15,9 +15,13 @@ import { join } from "node:path";
15
15
  import { DatabaseSync } from "node:sqlite";
16
16
  import { DATA_DIR } from "./config.js";
17
17
  import { peerProvenanceAuthor } from "./peer-provenance.js";
18
+ import { MessageSearchWorker } from "./message-search-worker.js";
19
+ import { searchMessagesInDatabase } from "./message-search-query.js";
18
20
  const DB_FILE = () => join(DATA_DIR, "messages.db");
19
21
  let handle = null;
20
22
  let handlePath = null;
23
+ let searchWorker = null;
24
+ let closingSearch = Promise.resolve();
21
25
  function open() {
22
26
  mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 });
23
27
  const file = DB_FILE();
@@ -444,49 +448,16 @@ export function threadsReferencing(fragment) {
444
448
  return rows.map((row) => row.thread_id);
445
449
  }
446
450
  /** Case-insensitive substring search over text messages, newest first.
447
- * A LIKE scan, deliberately: local transcripts are megabytes at most, a
448
- * scan is milliseconds, and it needs no FTS extension to exist. */
451
+ * Keep literal substring semantics; HTTP callers run the scan off-thread. */
449
452
  export function searchMessages(query, limit = 40, threadId) {
450
- const needle = query.trim().toLowerCase();
451
- if (!needle)
452
- return [];
453
- // escape LIKE wildcards so a literal % or _ in the query stays literal
454
- const pattern = `%${needle.replace(/([\\%_])/g, "\\$1")}%`;
455
- // text messages by their text; activity chips by the tool name — "which
456
- // bot ran that migration" is a tool-name question. The chip's name lives
457
- // in the row's json; a JSON1 extract keeps this one query.
458
- const scope = threadId ? "thread_id = ? AND " : "";
459
- const statement = db().prepare("SELECT thread_id, id, at, role, kind, text, json_extract(json, '$.tool.name') AS tool_name, json_extract(json, '$.from.name') AS from_name FROM messages " +
460
- `WHERE ${scope}((kind = 'text' AND text IS NOT NULL AND lower(text) LIKE ? ESCAPE '\\') ` +
461
- " OR (kind = 'activity' AND tool_name IS NOT NULL AND lower(tool_name) LIKE ? ESCAPE '\\')) " +
462
- "ORDER BY at DESC LIMIT ?");
463
- const rows = (threadId
464
- ? statement.all(threadId, pattern, pattern, limit)
465
- : statement.all(pattern, pattern, limit));
466
- return rows.map((row) => {
467
- const haystack = row.kind === "activity" ? (row.tool_name ?? "") : (row.text ?? "");
468
- const hitAt = Math.max(0, haystack.toLowerCase().indexOf(needle));
469
- const start = Math.max(0, hitAt - 60);
470
- const end = Math.min(haystack.length, hitAt + needle.length + 90);
471
- const head = start > 0 ? "…" : "";
472
- const body = haystack.slice(start, end).replace(/\s+/g, " ").trim();
473
- const snippet = head + body + (end < haystack.length ? "…" : "");
474
- // whitespace folding can shift the offset; find the match again inside
475
- const folded = needle.replace(/\s+/g, " ");
476
- const matchStart = snippet.toLowerCase().indexOf(folded);
477
- return {
478
- threadId: row.thread_id,
479
- messageId: row.id,
480
- at: row.at,
481
- role: row.role,
482
- kind: row.kind,
483
- snippet,
484
- matchStart: matchStart < 0 ? head.length : matchStart,
485
- // A defensive fallback must not mark arbitrary snippet text as the hit.
486
- matchLength: matchStart < 0 ? 0 : folded.length,
487
- ...(row.from_name ? { from: row.from_name } : {}),
488
- };
489
- });
453
+ return searchMessagesInDatabase(db(), query, limit, threadId);
454
+ }
455
+ export function searchMessagesAsync(query, limit = 40, threadId) {
456
+ if (!query.trim())
457
+ return Promise.resolve([]);
458
+ db(); // The owner initializes/migrates the schema before any read-only scan.
459
+ searchWorker ??= new MessageSearchWorker(DB_FILE());
460
+ return searchWorker.search(query, limit, threadId);
490
461
  }
491
462
  /** Who wrote a user-role line that reads as the user's. The structural
492
463
  * field wins; rows stored before it existed still open with the note. */
@@ -675,8 +646,17 @@ export function recallMemory(query, botId, limit = 12) {
675
646
  .all(match, botId, limit);
676
647
  return rows.map((row) => ({ file: row.path, at: row.mtime_ms, snippet: row.snippet.replace(/\s+/g, " ").trim() }));
677
648
  }
649
+ /** Await before maintenance replaces the database or shutdown releases its lease. */
650
+ export async function closeMessageSearch() {
651
+ const worker = searchWorker;
652
+ searchWorker = null;
653
+ if (worker)
654
+ closingSearch = Promise.all([closingSearch, worker.close()]).then(() => { });
655
+ await closingSearch;
656
+ }
678
657
  /** Test/shutdown hook — closes the handle so a wiped DATA_DIR starts clean. */
679
658
  export function closeMessageDb() {
659
+ void closeMessageSearch();
680
660
  try {
681
661
  handle?.close();
682
662
  }
@@ -313,7 +313,7 @@ function containedBy(root, candidate) {
313
313
  const suffix = relative(root, candidate);
314
314
  return suffix === "" || (suffix !== ".." && !suffix.startsWith(`..${sep}`) && !isAbsolute(suffix));
315
315
  }
316
- function mimeFor(path) {
316
+ export function mimeFor(path) {
317
317
  switch (extname(path).toLowerCase()) {
318
318
  case ".md": return "text/markdown; charset=utf-8";
319
319
  case ".txt": return "text/plain; charset=utf-8";
@@ -331,6 +331,14 @@ function mimeFor(path) {
331
331
  case ".m4v": return "video/x-m4v";
332
332
  case ".webm": return "video/webm";
333
333
  case ".mov": return "video/quicktime";
334
+ case ".mp3": return "audio/mpeg";
335
+ case ".m4a": return "audio/mp4";
336
+ case ".aac": return "audio/aac";
337
+ case ".wav": return "audio/wav";
338
+ case ".ogg":
339
+ case ".oga": return "audio/ogg";
340
+ case ".opus": return "audio/opus";
341
+ case ".flac": return "audio/flac";
334
342
  case ".doc": return "application/msword";
335
343
  case ".docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
336
344
  case ".xls": return "application/vnd.ms-excel";
@@ -0,0 +1,43 @@
1
+ /** Shared query and snippet logic; the worker never calls the write-side open(). */
2
+ export function searchMessagesInDatabase(database, query, limit = 40, threadId) {
3
+ const needle = query.trim().toLowerCase();
4
+ if (!needle)
5
+ return [];
6
+ // escape LIKE wildcards so a literal % or _ in the query stays literal
7
+ const pattern = `%${needle.replace(/([\\%_])/g, "\\$1")}%`;
8
+ // text messages by their text; activity chips by the tool name — "which
9
+ // bot ran that migration" is a tool-name question. The chip's name lives
10
+ // in the row's json; a JSON1 extract keeps this one query.
11
+ const scope = threadId ? "thread_id = ? AND " : "";
12
+ const statement = database.prepare("SELECT thread_id, id, at, role, kind, text, json_extract(json, '$.tool.name') AS tool_name, json_extract(json, '$.from.name') AS from_name FROM messages " +
13
+ `WHERE ${scope}((kind = 'text' AND text IS NOT NULL AND lower(text) LIKE ? ESCAPE '\\') ` +
14
+ " OR (kind = 'activity' AND tool_name IS NOT NULL AND lower(tool_name) LIKE ? ESCAPE '\\')) " +
15
+ "ORDER BY at DESC LIMIT ?");
16
+ const rows = (threadId
17
+ ? statement.all(threadId, pattern, pattern, limit)
18
+ : statement.all(pattern, pattern, limit));
19
+ return rows.map((row) => {
20
+ const haystack = row.kind === "activity" ? (row.tool_name ?? "") : (row.text ?? "");
21
+ const hitAt = Math.max(0, haystack.toLowerCase().indexOf(needle));
22
+ const start = Math.max(0, hitAt - 60);
23
+ const end = Math.min(haystack.length, hitAt + needle.length + 90);
24
+ const head = start > 0 ? "…" : "";
25
+ const body = haystack.slice(start, end).replace(/\s+/g, " ").trim();
26
+ const snippet = head + body + (end < haystack.length ? "…" : "");
27
+ // whitespace folding can shift the offset; find the match again inside
28
+ const folded = needle.replace(/\s+/g, " ");
29
+ const matchStart = snippet.toLowerCase().indexOf(folded);
30
+ return {
31
+ threadId: row.thread_id,
32
+ messageId: row.id,
33
+ at: row.at,
34
+ role: row.role,
35
+ kind: row.kind,
36
+ snippet,
37
+ matchStart: matchStart < 0 ? head.length : matchStart,
38
+ // A defensive fallback must not mark arbitrary snippet text as the hit.
39
+ matchLength: matchStart < 0 ? 0 : folded.length,
40
+ ...(row.from_name ? { from: row.from_name } : {}),
41
+ };
42
+ });
43
+ }
@@ -0,0 +1,80 @@
1
+ import { existsSync } from "node:fs";
2
+ import { Worker } from "node:worker_threads";
3
+ export class SearchUnavailable extends Error {
4
+ status = 503;
5
+ }
6
+ /** One read-only worker, not a worker per keystroke. Bound outstanding scans
7
+ * so a burst of searches cannot build an unbounded queue behind the database. */
8
+ export class MessageSearchWorker {
9
+ worker;
10
+ stopping = Promise.resolve();
11
+ sequence = 0;
12
+ pending = new Map();
13
+ file;
14
+ timeoutMs;
15
+ constructor(file, timeoutMs = 30_000) {
16
+ this.file = file;
17
+ this.timeoutMs = timeoutMs;
18
+ }
19
+ search(query, limit, threadId) {
20
+ if (this.pending.size >= 8)
21
+ return Promise.reject(new SearchUnavailable("Search is busy. Try again shortly."));
22
+ if (!this.worker) {
23
+ const source = new URL("./message-search.worker.ts", import.meta.url);
24
+ const worker = new Worker(existsSync(source) ? source : new URL("./message-search.worker.js", import.meta.url), {
25
+ workerData: { file: this.file },
26
+ // Do not inherit test loaders or inspector ports into the worker.
27
+ execArgv: [],
28
+ });
29
+ this.worker = worker;
30
+ worker.on("message", (reply) => {
31
+ if (this.worker !== worker)
32
+ return;
33
+ const request = this.pending.get(reply.id);
34
+ if (!request)
35
+ return;
36
+ clearTimeout(request.timer);
37
+ this.pending.delete(reply.id);
38
+ if (reply.failed)
39
+ request.reject(new SearchUnavailable("Search could not finish. Try again shortly."));
40
+ else
41
+ request.resolve(reply.hits ?? []);
42
+ if (!this.pending.size)
43
+ worker.unref();
44
+ });
45
+ worker.on("error", () => {
46
+ if (this.worker === worker)
47
+ void this.close();
48
+ });
49
+ worker.on("exit", () => {
50
+ if (this.worker === worker)
51
+ void this.close();
52
+ });
53
+ }
54
+ const worker = this.worker;
55
+ worker.ref();
56
+ const id = ++this.sequence;
57
+ return new Promise((resolve, reject) => {
58
+ const timer = setTimeout(() => { void this.close(); }, this.timeoutMs);
59
+ this.pending.set(id, { resolve, reject, timer });
60
+ try {
61
+ worker.postMessage({ id, query, limit, threadId });
62
+ }
63
+ catch {
64
+ void this.close();
65
+ }
66
+ });
67
+ }
68
+ async close() {
69
+ const worker = this.worker;
70
+ this.worker = undefined;
71
+ for (const request of this.pending.values()) {
72
+ clearTimeout(request.timer);
73
+ request.reject(new SearchUnavailable("Search was interrupted. Try again shortly."));
74
+ }
75
+ this.pending.clear();
76
+ if (worker)
77
+ this.stopping = Promise.all([this.stopping, worker.terminate()]).then(() => { });
78
+ await this.stopping;
79
+ }
80
+ }
@@ -0,0 +1,21 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ import { searchMessagesInDatabase } from "./message-search-query.js";
4
+ // Each request reads the latest committed WAL state. Closing after the scan
5
+ // avoids keeping an old database inode open across a workspace restore.
6
+ parentPort.on("message", (request) => {
7
+ let db;
8
+ try {
9
+ db = new DatabaseSync(workerData.file, { readOnly: true });
10
+ const hits = searchMessagesInDatabase(db, request.query, request.limit, request.threadId);
11
+ parentPort.postMessage({ id: request.id, hits });
12
+ }
13
+ catch {
14
+ // Database errors can contain private paths or query text. The server
15
+ // returns a generic retryable error, never a synchronous fallback scan.
16
+ parentPort.postMessage({ id: request.id, failed: true });
17
+ }
18
+ finally {
19
+ db?.close();
20
+ }
21
+ });