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
@@ -6,6 +6,7 @@ import { homedir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
8
8
  import { createAcpDriver } from "./core.js";
9
+ import { titleCaseModelId } from "../../contracts.js";
9
10
  import { execCli } from "../../procs.js";
10
11
  const STATIC_MODELS = {
11
12
  default: "opencode/x-preview-f-free",
@@ -21,11 +22,7 @@ let lastSuccessfulCatalog = null;
21
22
  const MODEL_PROBE_TTL_MS = 30_000;
22
23
  const modelProbeCache = new Map();
23
24
  function labelForModel(id) {
24
- return id
25
- .split(/[-_.]+/g)
26
- .filter(Boolean)
27
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
28
- .join(" ");
25
+ return titleCaseModelId(id, /[-_.]+/);
29
26
  }
30
27
  function providerLabel(id) {
31
28
  if (id === "opencode")
@@ -3,30 +3,20 @@
3
3
  // Custom and are written into ~/.qwen/settings.json modelProviders.
4
4
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { createHash } from "node:crypto";
6
- import { homedir } from "node:os";
7
6
  import { join } from "node:path";
7
+ import { qualifiedModelLabel } from "../../contracts.js";
8
+ import { harnessHome } from "../../env-path.js";
8
9
  import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
9
10
  import { createAcpDriver } from "./core.js";
10
11
  const EMPTY = { default: "", options: [] };
11
- function qwenHome(env) {
12
- const home = process.platform === "win32"
13
- ? env.USERPROFILE || env.HOME || homedir()
14
- : env.HOME || env.USERPROFILE || homedir();
15
- return join(home, ".qwen");
16
- }
17
12
  function isRecord(value) {
18
13
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19
14
  }
20
- function qwenModelLabel(id, name) {
21
- if (!name || name.toLocaleLowerCase().endsWith(id.toLocaleLowerCase()))
22
- return id;
23
- return `${id} — ${name}`;
24
- }
25
15
  const PROTOCOLS = new Set(["openai", "anthropic", "gemini", "vertex-ai"]);
26
16
  function readQwenRoutes(env) {
27
17
  let settings;
28
18
  try {
29
- settings = JSON.parse(readFileSync(join(qwenHome(env), "settings.json"), "utf8"));
19
+ settings = JSON.parse(readFileSync(join(harnessHome("qwen", env), "settings.json"), "utf8"));
30
20
  }
31
21
  catch {
32
22
  return [];
@@ -102,7 +92,11 @@ function readQwenRoutes(env) {
102
92
  /** Public metadata only. Use Qwen's provider-qualified ACP selectors, not raw model IDs. */
103
93
  export function readQwenModelCatalog(env = process.env) {
104
94
  const options = readQwenRoutes(env).map(({ id, model, label, provider }) => ({
105
- id, label: qwenModelLabel(model, label), custom: true, provider,
95
+ id,
96
+ label: qualifiedModelLabel(model, label, {
97
+ redundantWhen: (id, name) => name.toLocaleLowerCase().endsWith(id.toLocaleLowerCase()),
98
+ }),
99
+ custom: true, provider,
106
100
  }));
107
101
  return { default: options[0]?.id ?? "", options };
108
102
  }
@@ -117,7 +111,7 @@ export function ensureQwenInjectModel(modelId, env = process.env) {
117
111
  const host = localHost(inject.host);
118
112
  if (!host)
119
113
  return modelId;
120
- const dir = qwenHome(env);
114
+ const dir = harnessHome("qwen", env);
121
115
  mkdirSync(dir, { recursive: true });
122
116
  const path = join(dir, "settings.json");
123
117
  let settings = {};
@@ -7,8 +7,10 @@
7
7
  // Nothing here reads the environment or holds state of its own. The caller
8
8
  // passes a ToolCallContext, and the per-turn counters live on it.
9
9
  import { CREDENTIAL_TARGETS, isCredentialTargetId } from "../../shared/credential-request.js";
10
+ import { parseOptionsCardInput, WATCHER_OPTIONS_CARD_BOT_ID } from "../../shared/options-card.js";
10
11
  import { normalizeCronSchedule } from "../../shared/routine-schedule.js";
11
12
  import { peerName } from "../peer-roster.js";
13
+ import { renderPeerDeliveryReceipts } from "../peer-delivery.js";
12
14
  import { catalogProfileFromEnv, SHARED_COMPUTER_TOOL_NAMES, WEEKDAYS } from "./agents-catalog.js";
13
15
  import { harnessClientFromEnv } from "./agents-client.js";
14
16
  import { boundedAgentResult } from "./agents-result.js";
@@ -352,12 +354,95 @@ function recallSpeaker(hit) {
352
354
  return hit.from;
353
355
  return hit.role === "user" ? "user" : "you";
354
356
  }
357
+ const isPeerDeliveryOutcome = (value) => value === "queued" || value === "injected" || value === "failed";
358
+ /** The delivery receipt a peer-send answer carries, as one prose line.
359
+ * Unvalidated JSON stays out: a malformed receipt renders nothing rather
360
+ * than half a line of somebody else's text. */
361
+ function deliveryNote(r) {
362
+ const receipt = r.receipt;
363
+ if (!receipt || typeof receipt !== "object")
364
+ return "";
365
+ const { botId, botName, outcome, detail, taskId, requestId } = receipt;
366
+ if (typeof botId !== "string" || !botId || typeof detail !== "string" || !detail)
367
+ return "";
368
+ if (!isPeerDeliveryOutcome(outcome))
369
+ return "";
370
+ const parsed = {
371
+ botId,
372
+ ...(typeof botName === "string" && botName ? { botName } : {}),
373
+ outcome,
374
+ detail,
375
+ ...(typeof taskId === "string" && taskId ? { taskId } : {}),
376
+ ...(typeof requestId === "string" && requestId ? { requestId } : {}),
377
+ };
378
+ return renderPeerDeliveryReceipts([parsed]);
379
+ }
380
+ /** When a recalled line was said, on the machine's own clock. `toISOString()`
381
+ * answers in UTC, which disagrees with every other day the bot is shown: a
382
+ * bare `since`/`until` date is read as local midnight (recent-work.ts
383
+ * parseSince), the recent-work brief's times are local (whenLabel), and the
384
+ * daily memory logs are named after the local day. East of UTC a message
385
+ * from this morning was being dated yesterday — so a search for today's work
386
+ * came back stamped with the wrong date. */
387
+ function recallWhen(at, dateOnly) {
388
+ if (typeof at !== "number" || !Number.isFinite(at))
389
+ return "";
390
+ const said = new Date(at);
391
+ if (!Number.isFinite(said.getTime()))
392
+ return "";
393
+ const pad = (value) => String(value).padStart(2, "0");
394
+ const day = `${said.getFullYear()}-${pad(said.getMonth() + 1)}-${pad(said.getDate())}`;
395
+ return dateOnly ? day : `${day} ${pad(said.getHours())}:${pad(said.getMinutes())}`;
396
+ }
355
397
  export async function callTool(name, args, context) {
356
398
  // The names this body has always used, so it reads (and diffs) as it did
357
399
  // when these were the proxy's module-level constants.
358
400
  const { botId: BOT_ID, threadId: THREAD_ID, depth: DEPTH, externalRuntime: EXTERNAL_RUNTIME, coordinating: COORDINATING, turn } = context;
359
401
  const { delegationTaskIdsThisTurn } = turn;
360
402
  const { api, apiResponse } = context.client;
403
+ if (name === "vm_exec") {
404
+ const { ok, body } = await apiResponse("/api/internal/vm-exec", {
405
+ method: "POST",
406
+ body: JSON.stringify({ command: args.command, ...(typeof args.timeout_seconds === "number" ? { timeout_seconds: args.timeout_seconds } : {}) }),
407
+ });
408
+ if (!ok)
409
+ return { text: String(body.error ?? "Could not run that command."), isError: true };
410
+ const exitCode = Number(body.exitCode ?? 0);
411
+ const stdout = String(body.stdout ?? "");
412
+ const stderr = String(body.stderr ?? "");
413
+ const lines = [body.timedOut ? "The command was stopped: it ran past its time limit." : `exit code ${exitCode}`];
414
+ if (stdout)
415
+ lines.push("--- stdout ---", stdout.replace(/\s+$/, ""));
416
+ if (stderr)
417
+ lines.push("--- stderr ---", stderr.replace(/\s+$/, ""));
418
+ if (!stdout && !stderr && !body.timedOut)
419
+ lines.push("(no output)");
420
+ return { text: lines.join("\n"), ...(exitCode !== 0 || body.timedOut ? { isError: true } : {}) };
421
+ }
422
+ if (name === "attach_file") {
423
+ const { ok, body } = await apiResponse("/api/internal/attach-file", {
424
+ method: "POST",
425
+ body: JSON.stringify({ path: args.path, ...(typeof args.name === "string" ? { name: args.name } : {}) }),
426
+ });
427
+ if (!ok)
428
+ return { text: String(body.error ?? "Could not attach that file."), isError: true };
429
+ return { text: `Attached ${String(body.name ?? "the file")} (${Number(body.bytes ?? 0)} bytes). It now appears in the chat with a preview.` };
430
+ }
431
+ if (name === "create_options_card") {
432
+ if (BOT_ID !== WATCHER_OPTIONS_CARD_BOT_ID) {
433
+ return { text: "create_options_card is not enabled for this bot.", isError: true };
434
+ }
435
+ const parsed = parseOptionsCardInput(args);
436
+ if (!parsed.ok)
437
+ return { text: parsed.error, isError: true };
438
+ const result = await api("/api/internal/options-card", {
439
+ method: "POST",
440
+ body: JSON.stringify(parsed.value),
441
+ });
442
+ return {
443
+ text: `Rendered the native options card in this Watcher thread (message ${String(result.messageId ?? "created")}). Wait for the person's click or custom response; the card itself authorizes no external action.`,
444
+ };
445
+ }
361
446
  // Second lock. With sharing off the tool is not in the catalog, so a front
362
447
  // end already refuses the call as an unknown tool — the same answer a build
363
448
  // without the feature gives. This keeps the handler itself refusing if that
@@ -467,6 +552,7 @@ export async function callTool(name, args, context) {
467
552
  if (name === "post_to_room") {
468
553
  const groupId = String(args.group_id ?? "").trim();
469
554
  const message = String(args.message ?? "").trim();
555
+ const attachVoiceNote = args.attach_voice_note === true;
470
556
  if (!groupId || !message) {
471
557
  return { text: "post_to_room needs group_id (from list_rooms) and message.", isError: true };
472
558
  }
@@ -478,13 +564,13 @@ export async function callTool(name, args, context) {
478
564
  }
479
565
  const r = await api("/api/internal/post-to-room", {
480
566
  method: "POST",
481
- body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, groupId, message }),
567
+ body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, groupId, message, attachVoiceNote }),
482
568
  });
483
569
  if (r.error)
484
570
  return { text: String(r.error), isError: true };
485
571
  turn.roomPostsThisTurn += 1;
486
572
  return {
487
- text: `Posted in ${r.roomName ?? "the room"}. Nobody's turn was started, so expect no reply — tell the user it is posted.`,
573
+ text: `Posted in ${r.roomName ?? "the room"}${r.attachedVoiceNote ? " with the voice note attached" : ""}. Nobody's turn was started, so expect no reply — tell the user it is posted.`,
488
574
  };
489
575
  }
490
576
  if (name === "ask_bot") {
@@ -496,6 +582,7 @@ export async function callTool(name, args, context) {
496
582
  method: "POST",
497
583
  body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, toBotId, message, depth: DEPTH }),
498
584
  });
585
+ const note = deliveryNote(r);
499
586
  if (r.timeout) {
500
587
  // The peer's turn outlived the synchronous wait, so the harness
501
588
  // converted the ask into a delegation — the reply is not lost.
@@ -506,10 +593,25 @@ export async function callTool(name, args, context) {
506
593
  const amount = waitedSeconds < 60 ? waitedSeconds : Math.round(waitedSeconds / 60);
507
594
  const unit = waitedSeconds < 60 ? "second" : "minute";
508
595
  return {
509
- text: `${r.toBotName ?? "That bot"} is still working after ${amount} ${unit}${amount === 1 ? "" : "s"} — the ask was converted to a delegation so the reply is not lost. Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status."}`,
596
+ text: `${r.toBotName ?? "That bot"} is still working after ${amount} ${unit}${amount === 1 ? "" : "s"} — the ask was converted to a delegation so the reply is not lost. Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status."}${note ? `\n\n${note}` : ""}`,
510
597
  };
511
598
  }
512
599
  if (r.busy) {
600
+ // Aside lane: the teammate was mid-turn on a seam-capable engine, so
601
+ // the harness folded the message into their running work as peer
602
+ // context. No new turn started and no reply is coming back through
603
+ // this call — the receipt says whether the words are already in the
604
+ // live turn or waiting for it to settle.
605
+ if (r.aside === "injected") {
606
+ return {
607
+ text: `${r.toBotName ?? "That bot"} is mid-turn; your message was handed to them as an aside — peer context folded into their current work, not a request that interrupts or replies. Finish your turn and treat their eventual output as possibly informed by it.`,
608
+ };
609
+ }
610
+ if (r.aside === "queued") {
611
+ return {
612
+ text: `${r.toBotName ?? "That bot"} is mid-turn; your aside is queued and will reach them as context when the turn settles. No reply is expected — finish your turn.`,
613
+ };
614
+ }
513
615
  // The harness queues the message as a delegation when it can; the
514
616
  // task id is the asker's claim ticket for the eventual reply.
515
617
  const taskId = String(r.taskId ?? "").trim();
@@ -517,14 +619,14 @@ export async function callTool(name, args, context) {
517
619
  if (!EXTERNAL_RUNTIME)
518
620
  delegationTaskIdsThisTurn.add(taskId);
519
621
  return {
520
- text: `${r.toBotName ?? "That bot"} is busy right now, so your message was queued as a delegation instead — ${EXTERNAL_RUNTIME ? "it waits for the peer and any required approval" : "it runs after your current turn ends"}. Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status."}`,
622
+ text: `${r.toBotName ?? "That bot"} is busy right now, so your message was queued as a delegation instead — ${EXTERNAL_RUNTIME ? "it waits for the peer and any required approval" : "it runs after your current turn ends"}. Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status."}${note ? `\n\n${note}` : ""}`,
521
623
  };
522
624
  }
523
- return { text: `That bot is busy right now — try again after it finishes.` };
625
+ return { text: `That bot is busy right now — try again after it finishes.${note ? `\n\n${note}` : ""}` };
524
626
  }
525
627
  if (r.error)
526
- return { text: `Couldn't reach that bot: ${r.error}`, isError: true };
527
- return { text: `${r.botName ?? "Bot"} replied:\n${r.text ?? "(no reply)"}` };
628
+ return { text: `Couldn't reach that bot: ${r.error}${note ? `\n\n${note}` : ""}`, isError: true };
629
+ return { text: `${r.botName ?? "Bot"} replied:\n${r.text ?? "(no reply)"}${note ? `\n\n${note}` : ""}` };
528
630
  }
529
631
  if (name === "delegate_bot") {
530
632
  const toBotId = String(args.bot_id ?? "").trim();
@@ -542,8 +644,9 @@ export async function callTool(name, args, context) {
542
644
  if (reason)
543
645
  body.reason = reason;
544
646
  const r = await api(`/api/internal/delegate-bot`, { method: "POST", body: JSON.stringify(body) });
647
+ const delivery = deliveryNote(r);
545
648
  if (r.error)
546
- return { text: `Couldn't queue the delegation: ${r.error}`, isError: true };
649
+ return { text: `Couldn't queue the delegation: ${r.error}${delivery ? `\n\n${delivery}` : ""}`, isError: true };
547
650
  // Fire-and-forget by contract: the harness returns immediately, the
548
651
  // peer turn runs after our current turn finishes. The task id is the
549
652
  // bot's claim ticket for the outcome.
@@ -554,7 +657,7 @@ export async function callTool(name, args, context) {
554
657
  const suffix = taskId
555
658
  ? ` Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Acknowledge the assignment and finish your turn; the result will be delivered to this conversation automatically. Do not check or wait for it in this turn."}`
556
659
  : "";
557
- return { text: `${note}${suffix}` };
660
+ return { text: `${note}${suffix}${delivery ? `\n\n${delivery}` : ""}` };
558
661
  }
559
662
  if (name === "check_delegation" || name === "wait_delegation") {
560
663
  const taskId = String(args.task_id ?? "").trim();
@@ -727,11 +830,13 @@ export async function callTool(name, args, context) {
727
830
  name: botName,
728
831
  role,
729
832
  instructions,
833
+ ...(args.modelSelection !== undefined ? { modelSelection: args.modelSelection } : {}),
834
+ ...(typeof args.cwd === "string" ? { cwd: args.cwd.trim() } : {}),
730
835
  }),
731
836
  });
732
837
  turn.createdThisTurn += 1;
733
838
  return {
734
- text: `Created @${r.name ?? botName} in ${r.section ?? "General"} [id: ${r.id}]. Assign work with ${COORDINATING ? "coordinate_bots" : "delegate_bot"}.`,
839
+ text: `Created @${r.name ?? botName} in ${r.section ?? "General"} [id: ${r.id}].${r.modelSelection ? ` Model: ${JSON.stringify(r.modelSelection)}.` : ""} Assign work with ${COORDINATING ? "coordinate_bots" : "delegate_bot"}.`,
735
840
  };
736
841
  }
737
842
  if (name === "create_room") {
@@ -816,6 +921,27 @@ export async function callTool(name, args, context) {
816
921
  text: `A secure ${r.label ?? CREDENTIAL_TARGETS[credentialId].label} request is ready. The desktop app and a freshly QR-paired mobile app show its secure entry card; older mobile pairings explain how to pair again or finish on the computer. End this turn; OpenMausBot will resume the task after the user saves or declines. Never ask them to paste the key into chat.`,
817
922
  };
818
923
  }
924
+ if (name === "send_voice_note") {
925
+ if (typeof args.text !== "string" || !args.text.trim()) {
926
+ return { text: "send_voice_note needs text: the short speakable note, at most 1000 characters.", isError: true };
927
+ }
928
+ const text = args.text.trim();
929
+ if (text.length > 1000) {
930
+ return { text: `send_voice_note is limited to 1000 characters; this one is ${text.length}. Shorten the note.`, isError: true };
931
+ }
932
+ try {
933
+ await api("/api/internal/voice-note", {
934
+ method: "POST",
935
+ body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, text }),
936
+ });
937
+ return { text: "Voice note recorded. It will be attached to this turn's reply when the turn ends; the note text is also the visible caption." };
938
+ }
939
+ catch (error) {
940
+ // A missing voice setup is the user's to fix, not a failed turn: hand
941
+ // the harness's setup guidance straight to the model.
942
+ return { text: `Voice note not sent: ${error instanceof Error ? error.message : String(error)}`, isError: true };
943
+ }
944
+ }
819
945
  if (name === "list_routines") {
820
946
  const query = new URLSearchParams({ fromBotId: BOT_ID, fromThreadId: THREAD_ID });
821
947
  const r = await api(`/api/internal/routines?${query.toString()}`);
@@ -884,6 +1010,13 @@ export async function callTool(name, args, context) {
884
1010
  return confirmationResult(r, `${action.replace("_", " ")} on routine ${routineId}`);
885
1011
  }
886
1012
  if (name === "propose_profile") {
1013
+ // A non-boolean toggle would be dropped silently while any valid half of
1014
+ // the request went through, applying a partial proposal the model never
1015
+ // described. Reject the whole call instead.
1016
+ if ((args.notifications !== undefined && typeof args.notifications !== "boolean")
1017
+ || (args.speakReplies !== undefined && typeof args.speakReplies !== "boolean")) {
1018
+ return { text: "propose_profile notifications and speakReplies must be true or false.", isError: true };
1019
+ }
887
1020
  const changes = {};
888
1021
  if (typeof args.name === "string")
889
1022
  changes.name = args.name.trim();
@@ -895,8 +1028,12 @@ export async function callTool(name, args, context) {
895
1028
  changes.soul = args.soul;
896
1029
  if (typeof args.cwd === "string")
897
1030
  changes.cwd = args.cwd.trim();
1031
+ if (typeof args.notifications === "boolean")
1032
+ changes.notifications = args.notifications;
1033
+ if (typeof args.speakReplies === "boolean")
1034
+ changes.speakReplies = args.speakReplies;
898
1035
  if (!Object.keys(changes).length) {
899
- return { text: "propose_profile needs at least one of name, title, description, soul, or cwd.", isError: true };
1036
+ return { text: "propose_profile needs at least one of name, title, description, soul, cwd, notifications, or speakReplies.", isError: true };
900
1037
  }
901
1038
  const forBotId = String(args.for_bot_id ?? "").trim();
902
1039
  const r = await api("/api/internal/profile-requests", {
@@ -912,6 +1049,35 @@ export async function callTool(name, args, context) {
912
1049
  });
913
1050
  return confirmationResult(r, "the profile change", "profile");
914
1051
  }
1052
+ if (name === "propose_model") {
1053
+ const raw = args.model_selection;
1054
+ const fields = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : null;
1055
+ if (!fields || typeof fields.instanceId !== "string" || !fields.instanceId.trim() || typeof fields.model !== "string" || !fields.model.trim()) {
1056
+ return { text: "propose_model needs model_selection with instanceId and model.", isError: true };
1057
+ }
1058
+ if ((fields.effort !== undefined && (typeof fields.effort !== "string" || !fields.effort.trim()))
1059
+ || (fields.variant !== undefined && (typeof fields.variant !== "string" || !fields.variant.trim()))) {
1060
+ return { text: "propose_model effort and variant must be non-empty strings when supplied.", isError: true };
1061
+ }
1062
+ const selection = { instanceId: fields.instanceId.trim(), model: fields.model.trim() };
1063
+ if (typeof fields.effort === "string" && fields.effort.trim())
1064
+ selection.effort = fields.effort.trim();
1065
+ if (typeof fields.variant === "string" && fields.variant.trim())
1066
+ selection.variant = fields.variant.trim();
1067
+ const forBotId = String(args.for_bot_id ?? "").trim();
1068
+ const r = await api("/api/internal/model-requests", {
1069
+ method: "POST",
1070
+ body: JSON.stringify({
1071
+ fromBotId: BOT_ID,
1072
+ fromThreadId: THREAD_ID,
1073
+ modelSelection: selection,
1074
+ reason: args.reason,
1075
+ // JSON.stringify drops the key entirely when no target was named
1076
+ forBotId: forBotId || undefined,
1077
+ }),
1078
+ });
1079
+ return confirmationResult(r, "the default model change", "model");
1080
+ }
915
1081
  if (name === "memory_update") {
916
1082
  if (!["append", "replace", "remove", "supersede"].includes(String(args.action))
917
1083
  || (args.action !== "remove" && (typeof args.text !== "string" || !args.text.trim()))
@@ -1008,7 +1174,7 @@ export async function callTool(name, args, context) {
1008
1174
  }
1009
1175
  const lines = hits.map((hit) => {
1010
1176
  // a listing by time shows the time; a search by words keeps the date
1011
- const when = typeof hit.at === "number" ? new Date(hit.at).toISOString().slice(0, q ? 10 : 16).replace("T", " ") : "";
1177
+ const when = recallWhen(hit.at, Boolean(q));
1012
1178
  const task = typeof hit.task === "string" && hit.task ? `task "${hit.task}"` : "an earlier task";
1013
1179
  const where = hit.current
1014
1180
  ? "this conversation"
@@ -1040,7 +1206,7 @@ export async function callTool(name, args, context) {
1040
1206
  catch (error) {
1041
1207
  return { text: `Couldn't read that message: ${error instanceof Error ? error.message : String(error)}. Use ids from a session_search hit.`, isError: true };
1042
1208
  }
1043
- const when = typeof r.at === "number" ? new Date(r.at).toISOString().slice(0, 10) : "";
1209
+ const when = recallWhen(r.at, true);
1044
1210
  const readTask = typeof r.task === "string" && r.task ? `task "${r.task}"` : "an earlier task";
1045
1211
  const where = threadId === THREAD_ID ? "this conversation" : r.crossed ? `${readTask}, private to this user` : readTask;
1046
1212
  const note = r.crossed