openmausbot 0.1.93 → 0.1.95

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 (192) hide show
  1. package/dist/assets/{DesktopViewer-CKNR3SEW.js → DesktopViewer-BwWpc4RK.js} +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-Ckrm1I1X.js → abnfDiagram-O67JEVCF-jsQkIqTn.js} +1 -1
  3. package/dist/assets/{arc-BBdBuCgB.js → arc-7kei4Hsu.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CDdA3Hjs.js → architectureDiagram-NJMV4G6O-CRWDFDMq.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-AUrsx1YR.js → blockDiagram-BEXU5L5S-CTKW_IKs.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-BEqQ0Unh.js → c4Diagram-YGBWAQC7-BBTW7-6M.js} +1 -1
  7. package/dist/assets/channel-DS2NbT5x.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-CEEuvd4C.js → chunk-2Q5K7J3B-M_Ox-MkD.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-DyANCiHl.js → chunk-3FUC2YCW-D2YiN-Ud.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-Dx_Slpw9.js → chunk-5VM5RSS4-BmVYJ0f8.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-Dl7h7iiD.js → chunk-GTNCS2PH-BtiLVVnZ.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-BLx07SPb.js → chunk-GWA4HPMP-DFYSb6J6.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-BMxDj3n7.js → chunk-JWPE2WC7-mknKBzgV.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-CN66PT4H.js → chunk-NETBCI7D-DgSYGiOu.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-6HwhsDKw.js → chunk-WEXAMYUT-C7ZvuguH.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-gWnLt7SN.js → chunk-XXDRQBXY-CPqxINpA.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-CMS5l2ea.js → classDiagram-v2-NBCMYWYE-Cd6xfJaC.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-CV7sQwIG.js → cose-bilkent-JH36ORCC-BSXY5qRh.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-CSyl-7vl.js → cynefin-OW5HDTMX-D_lhEMj1.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-BCLKL6az.js → cynefinDiagram-VND7K2PF-Da9FrFwk.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DiaZs6S1.js → dagre-6A5THRUB-T77jMOXe.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-BeTtmt3I.js → diagram-22UHCM2B-Dkj96X5E.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-CsdBG-QQ.js → diagram-3UASUU5V-CMi9zlD_.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-BczvdbR1.js → diagram-ATOU4E4O-CXK6YUfS.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-DHTTTMhi.js → diagram-CDSNMT55-BE7B1u9Q.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-RTjHyd8z.js → diagram-MLGK6HIB-CCMQphKr.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-BDenHEH_.js → diagram-MPIPVDR6-iHJ_-tGH.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-BSiRSbtL.js → ebnfDiagram-ZINNZB2B-DONsCWQ7.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-BCAElss-.js → elk-276RUBZZ-CU_WOaXk.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-C4_1ZPJe.js → erDiagram-OPXOYQCR-BeYwM04G.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bcb4Z75S.js → flowDiagram-KWPJA3E3-Bdw9DqEl.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-BiPOzxzb.js → ganttDiagram-FUAMR5RP-C9iwq3qm.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-B1wtF3ZS.js → gitGraphDiagram-X574FWY7-Dx-VWzUg.js} +1 -1
  34. package/dist/assets/{index-BkYwilkG.js → index-KygHRE3E.js} +1 -1
  35. package/dist/assets/index-NF7XCHAy.css +1 -0
  36. package/dist/assets/index-X_5ydXIE.js +610 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-A19No71U.js → infoDiagram-VRGFBTTK-BG7uaEd4.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js → ishikawaDiagram-OU5B5YK6-D72WAXnM.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DAO0pVan.js → journeyDiagram-ZHPQQLJL-DKH1gjYT.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-NepmKEe2.js → kanban-definition-PNTS6WVX-3upm9JUT.js} +1 -1
  41. package/dist/assets/{linear-bq1PTDTW.js → linear-CcR4pc0-.js} +1 -1
  42. package/dist/assets/{mermaid.core-s4LTbQle.js → mermaid.core-C6yU5FSS.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-LtdbgHFH.js → mindmap-definition-NLK3R4M7-DOI2xEuv.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-tdbaCxiK.js → pegDiagram-GJSIUBJH-BVWJSgLd.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DvkbpW8l.js → pieDiagram-5QR66LMP-jtbYezhq.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-Do4KaH-E.js → quadrantDiagram-O4NWA36T-g0u8mtPX.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-BQWULOAs.js → railroadDiagram-XR7U4H2S-k8689Wny.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-w9kei1FU.js → requirementDiagram-PLB6GJNP-DsJ22PDt.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-D2gRy0e8.js → sankeyDiagram-IPEJSGJF-CneWBRgG.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-a2sO5_Eo.js → sequenceDiagram-PO4LG4MO-DWsFCWap.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-CiPJmFEB.js → sizeCapture-INFHLROL-Bvf0YE3S.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-DAkUUlL7.js → stateDiagram-v2-GCMORJYK-Bxb3Iei8.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DANf2R8Y.js → swimlanes-2SLR337P-DLkk7W4A.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-C6bZtBXk.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BKpQmzrT.js → timeline-definition-EJHVYXUP-KR9bIHYC.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-BtI7qLN-.js → usecaseDiagram-POWQR4AR-DfsOrj8t.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-gXJXrAG_.js → vennDiagram-UO4OBE2U-DRSTzG3h.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-GU8Po5-n.js → wardleyDiagram-VNRHLVJA-DtEdhPgT.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-BF4J30g6.js → xychartDiagram-PMCCYNJV-B-XhTxjG.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +3 -1
  62. package/dist-server/companion/src/routes.js +60 -0
  63. package/dist-server/container-mcp.js +94 -77
  64. package/dist-server/drivers/agents-proxy.js +96 -39
  65. package/dist-server/drivers/pi-mcp-extension.ts +245 -299
  66. package/dist-server/enterprise/server/index.js +3 -1
  67. package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +35 -19
  68. package/dist-server/index.js +18242 -13645
  69. package/dist-server/local-computer.js +3 -1
  70. package/dist-server/mcp-gate.js +147 -19
  71. package/dist-server/mcp-remote-proxy.js +437 -0
  72. package/dist-server/openmausbot.js +9618 -10058
  73. package/dist-server/pair-cli.js +9618 -10058
  74. package/dist-server/permission-proxy.js +65 -1
  75. package/dist-server/proxy-paths.js +3 -1
  76. package/dist-server/server/account-signin.js +0 -2
  77. package/dist-server/server/activity.js +201 -0
  78. package/dist-server/server/auto-approve.js +39 -11
  79. package/dist-server/server/automatic-recovery.js +1 -1
  80. package/dist-server/server/boat-create-idempotency.js +0 -7
  81. package/dist-server/server/boat-delete-journal.js +0 -4
  82. package/dist-server/server/bot-package.js +1 -19
  83. package/dist-server/server/browser-live.js +14 -5
  84. package/dist-server/server/browser-runtime.js +7 -2
  85. package/dist-server/server/channel-queue.js +4 -1
  86. package/dist-server/server/chief-of-staff.js +2 -3
  87. package/dist-server/server/cli.js +32 -12
  88. package/dist-server/server/cloud-backend.js +0 -4
  89. package/dist-server/server/cloud-computer-tools.js +110 -0
  90. package/dist-server/server/cloud-home.js +0 -1
  91. package/dist-server/server/cloud-lending.js +0 -3
  92. package/dist-server/server/cloud-move-http.js +9 -3
  93. package/dist-server/server/cloud-move.js +18 -0
  94. package/dist-server/server/composio.js +31 -2
  95. package/dist-server/server/computer-control.js +0 -15
  96. package/dist-server/server/computer-observation.js +0 -74
  97. package/dist-server/server/computer-wait.js +33 -13
  98. package/dist-server/server/config.js +73 -11
  99. package/dist-server/server/container-computer.js +7 -19
  100. package/dist-server/server/contracts.js +9 -0
  101. package/dist-server/server/control-client.js +4 -10
  102. package/dist-server/server/decision-log.js +9 -6
  103. package/dist-server/server/delegations.js +47 -22
  104. package/dist-server/server/drivers/acp/core.js +64 -13
  105. package/dist-server/server/drivers/acp/grok.js +208 -3
  106. package/dist-server/server/drivers/agents-call.js +70 -29
  107. package/dist-server/server/drivers/agents-catalog.js +34 -12
  108. package/dist-server/server/drivers/agents-proxy.js +8 -30
  109. package/dist-server/server/drivers/antigravity.js +1 -4
  110. package/dist-server/server/drivers/boatagent.js +25 -4
  111. package/dist-server/server/drivers/cerebras.js +3 -3
  112. package/dist-server/server/drivers/chat-mcp-tools.js +29 -19
  113. package/dist-server/server/drivers/claude.js +33 -19
  114. package/dist-server/server/drivers/codex.js +84 -10
  115. package/dist-server/server/drivers/openai-chat.js +14 -9
  116. package/dist-server/server/drivers/pi.js +48 -4
  117. package/dist-server/server/drivers/retry.js +8 -4
  118. package/dist-server/server/engine-install.js +1 -5
  119. package/dist-server/server/environment.js +3 -0
  120. package/dist-server/server/guarded-requests.js +2 -1
  121. package/dist-server/server/harness/registry.js +0 -1
  122. package/dist-server/server/{browser-proxy.js → harness-mcp-proxy.js} +40 -20
  123. package/dist-server/server/index.js +1929 -507
  124. package/dist-server/server/lending-memory.js +12 -8
  125. package/dist-server/server/live-call-controller.js +868 -0
  126. package/dist-server/server/live-call.js +216 -0
  127. package/dist-server/server/mcp-gate-config.js +43 -12
  128. package/dist-server/server/mcp-gate.js +127 -25
  129. package/dist-server/server/mcp-http.js +46 -8
  130. package/dist-server/server/mcp-remote-proxy.js +108 -0
  131. package/dist-server/server/memory-entries.js +38 -8
  132. package/dist-server/server/memory-organize.js +1 -8
  133. package/dist-server/server/memory-topics.js +4 -4
  134. package/dist-server/server/memory-upkeep.js +13 -15
  135. package/dist-server/server/message-file.js +5 -9
  136. package/dist-server/server/new-bot-defaults.js +3 -0
  137. package/dist-server/server/outbound-counts.js +79 -0
  138. package/dist-server/server/outbound-requests.js +60 -0
  139. package/dist-server/server/package-import.js +1 -1
  140. package/dist-server/server/peer-delivery.js +1 -0
  141. package/dist-server/server/peer-provenance.js +4 -2
  142. package/dist-server/server/permission-proxy.js +15 -1
  143. package/dist-server/server/plan-usage.js +797 -0
  144. package/dist-server/server/proxy-paths.js +3 -1
  145. package/dist-server/server/redact.js +2 -0
  146. package/dist-server/server/request-auth.js +3 -2
  147. package/dist-server/server/room-handoffs.js +5 -13
  148. package/dist-server/server/routes/live.js +98 -0
  149. package/dist-server/server/routine-requests.js +1 -1
  150. package/dist-server/server/routines.js +5 -1
  151. package/dist-server/server/setup-mode.js +0 -2
  152. package/dist-server/server/skill-learn.js +0 -1
  153. package/dist-server/server/skill-library.js +195 -3
  154. package/dist-server/server/skills-library-migration.js +235 -0
  155. package/dist-server/server/skills.js +66 -3
  156. package/dist-server/server/steer-queue.js +13 -1
  157. package/dist-server/server/store.js +41 -5
  158. package/dist-server/server/surface.js +74 -23
  159. package/dist-server/server/system-prompt.js +4 -4
  160. package/dist-server/server/team-backup.js +2 -1
  161. package/dist-server/server/team-memory.js +209 -0
  162. package/dist-server/server/thread-events.js +3 -3
  163. package/dist-server/server/tunnel.js +20 -1
  164. package/dist-server/server/turn-log.js +10 -0
  165. package/dist-server/server/turn-resources.js +124 -0
  166. package/dist-server/server/vps-computer.js +4 -14
  167. package/dist-server/server/workspace.js +85 -47
  168. package/dist-server/shared/activity.js +3 -0
  169. package/dist-server/shared/ask-question.js +13 -0
  170. package/dist-server/shared/bot-avatar.js +8 -0
  171. package/dist-server/shared/call-consent.js +28 -0
  172. package/dist-server/shared/connector-scopes.js +78 -0
  173. package/dist-server/shared/live-approval.js +124 -0
  174. package/dist-server/shared/live-call.js +176 -0
  175. package/dist-server/shared/outbound.js +128 -0
  176. package/dist-server/shared/skill-md.js +10 -0
  177. package/dist-server/shared/team-backup.js +3 -0
  178. package/dist-server/shared/tool-scope-support.js +25 -0
  179. package/dist-server/shared/tool-scope.js +136 -0
  180. package/dist-server/shared/tool-surface.js +3 -2
  181. package/dist-server/src/lib/thread-refs.js +406 -0
  182. package/dist-server/vps-container-mcp.js +94 -77
  183. package/dist-server/workspace-backup.worker.js +89 -100
  184. package/enterprise/server/index.js +3 -1
  185. package/package.json +1 -1
  186. package/dist/assets/channel-BU94VpxE.js +0 -1
  187. package/dist/assets/index-Biji-bfL.css +0 -1
  188. package/dist/assets/index-DZlhoES0.js +0 -597
  189. package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +0 -8
  190. package/dist-server/server/drivers/chat-boat-tools.js +0 -90
  191. package/dist-server/server/drivers/grokagent.js +0 -4
  192. package/dist-server/server/openmaus-status-capsule.js +0 -329
@@ -1,7 +1,7 @@
1
1
  // Config + data dirs. One file, ~/.openmausbot/config.json, env fallbacks:
2
2
  // { "xai": {"key":"xai-…"}, "composio": {"apiKey":"ak_…"}, "box": {"token":"…"},
3
3
  // "instances": { "<instanceId>": {"driver":"grok", …} } }
4
- import { readFileSync, mkdirSync, existsSync, renameSync } from "node:fs";
4
+ import { readFileSync, mkdirSync, existsSync, renameSync, statSync } from "node:fs";
5
5
  import { homedir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  import { z } from "zod";
@@ -14,7 +14,6 @@ import { PROVIDER_ICON_PRESETS, providerIconError } from "../shared/provider-ico
14
14
  import { isRemoteMcpServer, parseStoredMcpServer } from "./mcp-registry.js";
15
15
  import { parseJson, schemaIssue } from "./schema.js";
16
16
  import { CLOUD_SEAT_IDLE_STOP_MS } from "./cloud-overflow.js";
17
- import { cloudHomeConfigured } from "./cloud-home.js";
18
17
  const optionalText = z.string().optional();
19
18
  const SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
20
19
  const LEGACY_BROWSER_PROFILE_ID = /^[A-Za-z0-9_-]{1,40}$/;
@@ -304,6 +303,10 @@ const featureConfigSchema = z.object({
304
303
  * consent card or a configured allowlist — never a silent Auto default.
305
304
  * Off until baked; see cloudOverflowEnabled for how to enable it by hand. */
306
305
  cloudOverflow: z.boolean().optional(),
306
+ /** Opt-in shared skills library: one store at the data dir that bots
307
+ * read by assignment instead of per-workspace copies. Off until
308
+ * explicitly enabled — see skillsLibraryEnabled. */
309
+ skillsLibrary: z.boolean().optional(),
307
310
  });
308
311
  /** First-run progress. Kept in the workspace config rather than a browser so
309
312
  * it survives cleared site data and is shared by every paired client. Hint
@@ -474,6 +477,15 @@ const appConfigSchema = z.object({
474
477
  .optional(),
475
478
  jobs: z.object({ roomRouting: z.boolean().optional() }).optional(),
476
479
  }).optional(),
480
+ /** Live calls: an OpenAI project key for GPT-Live, kept apart from every
481
+ * other OpenAI credential so a Live call never bills an image or engine key
482
+ * the user did not hand to it. `voice` is a GPT-Live built-in voice name. */
483
+ live: z.object({
484
+ key: optionalText,
485
+ voice: z.string().trim().max(40).regex(/^[a-z]*$/, "a Live voice is a lowercase built-in voice name").optional(),
486
+ readTypedReplies: z.boolean().optional(),
487
+ idleMinutes: z.number().int().min(1).max(60).optional(),
488
+ }).optional(),
477
489
  /** Avatar provider credentials stay separate; choosing a router never reuses a cloud key. */
478
490
  imageGen: z.object({
479
491
  provider: z.enum(["openai", "xai", "custom"]).optional(),
@@ -630,6 +642,17 @@ export function browserEngineAttachCdpUrl(cfg) {
630
642
  export function roomTurnTimeoutMinutes(cfg) {
631
643
  return cfg.rooms?.turnTimeoutMinutes ?? DEFAULT_ROOM_TURN_TIMEOUT_MINUTES;
632
644
  }
645
+ export const LIVE_IDLE_MINUTES_DEFAULT = 5;
646
+ /** Non-secret Live settings. The key only shows up as `configured`. */
647
+ export function liveSettingsFor(cfg) {
648
+ const minutes = cfg.live?.idleMinutes;
649
+ return {
650
+ configured: Boolean(cfg.live?.key?.trim()),
651
+ voice: cfg.live?.voice ?? "",
652
+ readTypedReplies: cfg.live?.readTypedReplies ?? true,
653
+ idleMinutes: Number.isInteger(minutes) && minutes >= 1 && minutes <= 60 ? minutes : LIVE_IDLE_MINUTES_DEFAULT,
654
+ };
655
+ }
633
656
  /** Room handoff tree budgets in milliseconds. The tree lifetime pauses
634
657
  * while a node is actively executing; the hard cap is the maximum stall
635
658
  * window, measured wall-clock from the tree's last durable progress. Read
@@ -692,14 +715,11 @@ export function showToolCallsEnabled(cfg) {
692
715
  export function routinesInConversationEnabled(cfg) {
693
716
  return cfg.features?.routinesInConversation === true;
694
717
  }
695
- /** Workspace-level gate for the experimental built-in browser. A bot's own
696
- * switch sits under it, so either can withhold the browser. Off until the
697
- * desktop's first-run welcome turns it on. A Cloud home (cloud-home.ts)
698
- * skips that welcome, ships the browser in its image and has no other screen
699
- * of its own, so there it is on unless the person switched it off. */
700
- export function builtInBrowserEnabled(cfg, env = process.env) {
701
- const chosen = cfg.features?.browser;
702
- return chosen === undefined ? cloudHomeConfigured(env) : chosen === true;
718
+ /** Workspace-level gate for the built-in browser. A bot's own switch sits
719
+ * under it, so either can withhold the browser. On unless the person switched
720
+ * it off: an explicit `false` (Settings, or a bot's computer panel) is kept. */
721
+ export function builtInBrowserEnabled(cfg, _env = process.env) {
722
+ return cfg.features?.browser !== false;
703
723
  }
704
724
  /** Opt-in computer sharing: the routes, the agent tools, the advertised
705
725
  * capability and the desktop connector. Off unless an explicit `true` turns
@@ -766,6 +786,14 @@ export function cloudOverflowIdleStopMs(cfg) {
766
786
  export function cloudOverflowAllowlistedThreads(cfg) {
767
787
  return new Set(cfg.cloudOverflow?.allowlistedThreads ?? []);
768
788
  }
789
+ /** Opt-in shared skills library (skills lane S1): one store at the data dir
790
+ * that bots reference by assignment instead of per-workspace copies. Off
791
+ * unless enabled by hand in ~/.openmausbot/config.json
792
+ * (`{"features": {"skillsLibrary": true}}`); while off, every skills
793
+ * surface keeps today's byte-identical per-bot behavior. */
794
+ export function skillsLibraryEnabled(cfg) {
795
+ return cfg.features?.skillsLibrary === true;
796
+ }
769
797
  /** Config sections no provider driver reads. A write that touches only
770
798
  * these must not rebuild the fleet: rebuilding disposes every engine child
771
799
  * and reloads it, seconds of work that would also interrupt in-flight
@@ -778,6 +806,7 @@ export const FLEET_NEUTRAL_KEYS = new Set([
778
806
  // no engine reads it: the harness asks it before a turn starts
779
807
  "decider",
780
808
  "imageGen",
809
+ "live",
781
810
  "vps",
782
811
  "rooms",
783
812
  "threads",
@@ -937,6 +966,9 @@ export function loadConfig() {
937
966
  cfg.decider = { ...cfg.decider };
938
967
  if (process.env.OMB_JEV_API_KEY !== undefined)
939
968
  cfg.decider.key = process.env.OMB_JEV_API_KEY;
969
+ cfg.live = { ...cfg.live };
970
+ if (process.env.OMB_OPENAI_LIVE_KEY !== undefined)
971
+ cfg.live.key = process.env.OMB_OPENAI_LIVE_KEY;
940
972
  cfg.imageGen = { ...cfg.imageGen };
941
973
  if (process.env.OMB_OPENAI_IMAGE_KEY !== undefined)
942
974
  cfg.imageGen.key = process.env.OMB_OPENAI_IMAGE_KEY;
@@ -954,6 +986,34 @@ export function loadConfig() {
954
986
  }
955
987
  return cfg;
956
988
  }
989
+ /** `derive(loadConfig())`, worked out again only when config.json changes:
990
+ * a new file (every save renames one into place), a new size, a new
991
+ * modification time, or new permissions or owner. For values read on every
992
+ * request or stream frame, where parsing the file each time costs more than
993
+ * the work itself. A file that is missing, saved in the last two seconds, or
994
+ * that loadConfig() could not use (unreadable, a failed read, invalid JSON)
995
+ * is read on every call: a second write inside one clock tick is never
996
+ * missed, and a file that is fixed is used again on the next call. */
997
+ export function cacheUntilConfigChanges(derive) {
998
+ let cached = null;
999
+ return () => {
1000
+ let stamp = null;
1001
+ try {
1002
+ const file = statSync(join(DATA_DIR, "config.json"), { bigint: true });
1003
+ if (Date.now() - Number(file.mtimeMs) >= 2_000)
1004
+ stamp = `${file.ino}:${file.size}:${file.mtimeNs}:${file.ctimeNs}`;
1005
+ }
1006
+ catch {
1007
+ // Missing or out of reach: read it the way loadConfig() always has.
1008
+ }
1009
+ if (stamp !== null && cached?.stamp === stamp)
1010
+ return cached.value;
1011
+ const value = derive(loadConfig());
1012
+ // A warning means loadConfig() ignored the file and ran on defaults.
1013
+ cached = stamp === null || lastIgnoredConfigWarning !== "" ? null : { stamp, value };
1014
+ return value;
1015
+ };
1016
+ }
957
1017
  /** After saveConfig() writes a credential, the running process's env must
958
1018
  * follow the newest value — loadConfig() prefers env, so the secret injected
959
1019
  * at boot would otherwise shadow the save until relaunch: the UI would show
@@ -978,6 +1038,7 @@ export function syncCredentialEnv(patch) {
978
1038
  [patch.decider?.key, "OMB_JEV_API_KEY"],
979
1039
  [patch.imageGen?.key, "OMB_OPENAI_IMAGE_KEY"],
980
1040
  [patch.imageGen?.customApiKey, "OMB_CUSTOM_IMAGE_KEY"],
1041
+ [patch.live?.key, "OMB_OPENAI_LIVE_KEY"],
981
1042
  ];
982
1043
  for (const [value, name] of secrets) {
983
1044
  if (value === undefined)
@@ -1028,6 +1089,7 @@ export const WORKSPACE_CREDENTIAL_ENV = [
1028
1089
  "OMB_JEV_API_KEY",
1029
1090
  "OMB_OPENAI_IMAGE_KEY",
1030
1091
  "OMB_CUSTOM_IMAGE_KEY",
1092
+ "OMB_OPENAI_LIVE_KEY",
1031
1093
  "COMPOSIO_API_KEY",
1032
1094
  "OMB_COMPOSIO_BROKER_TOKEN",
1033
1095
  // Cloud Pro's included Boat, voice and decision relay tokens
@@ -1118,7 +1180,7 @@ export function saveConfig(patch, options = {}) {
1118
1180
  const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
1119
1181
  if (storedProfiles.success)
1120
1182
  disk.browserProfiles = storedProfiles.data;
1121
- for (const key of ["xai", "anthropic", "mistral", "cerebras", "openai", "openrouter", "openaiCompat", "composio", "box", "opencodeGo", "tts", "decider", "imageGen", "profile", "rooms", "threads", "context", "memory", "localVm", "features", "cloudOverflow", "budgets", "billing", "decisions", "onboarding", "browserEngine", "newBots"]) {
1183
+ for (const key of ["xai", "anthropic", "mistral", "cerebras", "openai", "openrouter", "openaiCompat", "composio", "box", "opencodeGo", "tts", "decider", "imageGen", "live", "profile", "rooms", "threads", "context", "memory", "localVm", "features", "cloudOverflow", "budgets", "billing", "decisions", "onboarding", "browserEngine", "newBots"]) {
1122
1184
  const section = checkedPatch[key];
1123
1185
  if (!section)
1124
1186
  continue;
@@ -46,9 +46,7 @@ const RUNTIMES = ["docker", "podman", "container"];
46
46
  const INTERNAL_VIEWER_PORT = 6901;
47
47
  const HOST_VIEWER_PORT = 6080;
48
48
  const MEMORY_BYTES = 4 * 1024 * 1024 * 1024;
49
- const NANO_CPUS = 2_000_000_000;
50
49
  const PIDS_LIMIT = 512;
51
- const SHM_BYTES = 512 * 1024 * 1024;
52
50
  export const SHARED_LOCAL_VM_TARGET = {
53
51
  key: "shared",
54
52
  containerName: CONTAINER,
@@ -604,14 +602,10 @@ function appleWorkspaceMountIsSafe(mounts, platform, expectedWorkspace) {
604
602
  mounts[0]?.destination === VM_WORKSPACE_GUEST &&
605
603
  !options.some((option) => option === "ro" || option === "readonly"));
606
604
  }
607
- /** One hardening contract for both managed containers (Local VM here, the
608
- * BYO-VPS backend in vps-computer.ts): exact resource limits, no privilege,
609
- * no host namespaces or devices, no disabled security profiles. The only
610
- * runtime-specific capability exception is Podman's Firefox sandbox chroot.
611
- * Callers also differ on restart policy — the VPS
612
- * container must survive a reboot nobody is watching ("unless-stopped"),
613
- * while Local VM starts remain controlled by OMB's idle policy and turn
614
- * lifecycle rather than a daemon restart policy. */
605
+ /** Shared isolation contract for Local VM and BYO-VPS containers. Resource
606
+ * budgets are creation defaults, not an isolation requirement. Podman alone
607
+ * needs chroot for Firefox's sandbox. Local VM starts stay controlled by
608
+ * OMB's idle policy; VPS restart policy belongs to the server operator. */
615
609
  export function dockerSecurityIsHardened(config, options = {}) {
616
610
  if (!config)
617
611
  return false;
@@ -621,26 +615,20 @@ export function dockerSecurityIsHardened(config, options = {}) {
621
615
  .sort();
622
616
  const unsafeSecurityOption = (config.SecurityOpt ?? []).some((option) => /(?:^|=)(?:unconfined|disable)$/i.test(option));
623
617
  const restartPolicy = config.RestartPolicy?.Name;
624
- const restartPolicyOk = options.restartPolicy === "unless-stopped"
618
+ const restartPolicyOk = options.restartPolicy === "any" || (options.restartPolicy === "unless-stopped"
625
619
  ? restartPolicy === "unless-stopped"
626
- : restartPolicy === undefined || restartPolicy === "" || restartPolicy === "no";
627
- return (config.Memory === MEMORY_BYTES &&
628
- (config.MemorySwap ?? 0) === MEMORY_BYTES &&
629
- (config.NanoCpus ?? 0) === NANO_CPUS &&
630
- config.PidsLimit === PIDS_LIMIT &&
631
- capDrop.includes("all") &&
620
+ : restartPolicy === undefined || restartPolicy === "" || restartPolicy === "no");
621
+ return (capDrop.includes("all") &&
632
622
  capAdd.join(",") === (options.podmanBrowserSandbox ? "setgid,setuid,sys_chroot" : "setgid,setuid") &&
633
623
  config.Privileged === false &&
634
624
  !config.PidMode &&
635
625
  config.IpcMode === "private" &&
636
626
  !config.UTSMode &&
637
- config.ShmSize === SHM_BYTES &&
638
627
  (!config.Devices || config.Devices.length === 0) &&
639
628
  (!config.DeviceRequests || config.DeviceRequests.length === 0) &&
640
629
  !unsafeSecurityOption &&
641
630
  !config.UsernsMode &&
642
631
  config.CgroupnsMode === "private" &&
643
- config.OomKillDisable !== true &&
644
632
  config.AutoRemove !== true &&
645
633
  restartPolicyOk);
646
634
  }
@@ -49,6 +49,15 @@ export function qualifiedModelLabel(id, qualifier, options = {}) {
49
49
  return id;
50
50
  return `${id}${decorate(qualifier)}`;
51
51
  }
52
+ /** True when this driver's turn can run against a cloud computer — natively
53
+ * (remoteAgent) or through its computer tools (computerMcp), where the cloud
54
+ * computer is one more stdio server. The one rule for Hosted desktop: every
55
+ * cloud attach path refuses an engine without it before anything is
56
+ * provisioned (cloudPlaceDriverError). Derived, so no driver can declare it
57
+ * out of step with the two fields it reads. */
58
+ export function usesCloudComputer(capabilities) {
59
+ return capabilities.remoteAgent === true || capabilities.computerMcp === true;
60
+ }
52
61
  let eventCounter = 0;
53
62
  export const newEventId = () => `ev-${Date.now().toString(36)}-${(eventCounter++).toString(36)}`;
54
63
  export const newId = () => crypto.randomUUID();
@@ -90,16 +90,10 @@ export function createControlClient(options) {
90
90
  },
91
91
  };
92
92
  }
93
- /** The one sentence every refused action gets. Deliberately does not vary
94
- * per tool: the model needs the same three facts every time — nothing
95
- * happened, don't retry blindly, and how to wait properly. */
96
- export const CONTROL_REFUSAL = "A person has taken control of this computer, so this call was NOT performed. " +
97
- "Do not retry it — the screen is changing under their hands. " +
98
- "Call computer_request_help (no reason needed) to wait for them to finish, " +
99
- "then take a fresh screenshot before your next action.";
100
- /** The bridge-gated computers (Local VM, VPS) speak Cua Driver's own tool
101
- * surface, which has no wait tool to point at — so the guidance is to
102
- * pause, not to call anything. */
93
+ /** The one sentence every refused computer action gets, on every computer.
94
+ * Deliberately does not vary per tool: the model needs the same three facts
95
+ * every time — nothing happened, don't retry blindly, and wait for the
96
+ * person. */
103
97
  export const CONTROL_REFUSAL_PLAIN = "A person has taken control of this computer, so this call was NOT performed. " +
104
98
  "Do not retry it — the screen is changing under their hands. " +
105
99
  "Pause this task, tell the person you are waiting for them to hand control back, " +
@@ -65,9 +65,10 @@ export function decisionRetentionDays(configured, env = process.env) {
65
65
  }
66
66
  const actorScope = new AsyncLocalStorage();
67
67
  /** Run `work` as a person's card answer: every `source: "user"` row it
68
- * writes names `actor`, without each resolver having to thread it through. */
69
- export function withDecisionActor(actor, work) {
70
- return actorScope.run(actor, work);
68
+ * writes names `actor` (and `via`, for an answer spoken on a Live call),
69
+ * without each resolver having to thread it through. */
70
+ export function withDecisionActor(actor, work, via) {
71
+ return actorScope.run(via ? { actor, via } : { actor }, work);
71
72
  }
72
73
  function monthKey(at) {
73
74
  return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -88,10 +89,12 @@ async function writeDecision(dataDir, record) {
88
89
  * the fold that calls this is delivering approvals and cards, and a full
89
90
  * disk must not turn into denied tools. */
90
91
  export function appendDecision(dataDir, row) {
91
- const actor = row.actor ?? (row.source === "user" ? actorScope.getStore() : undefined);
92
+ const scope = row.source === "user" ? actorScope.getStore() : undefined;
93
+ const actor = row.actor ?? scope?.actor;
94
+ const via = row.via ?? scope?.via;
92
95
  // Redact now, not when the queue drains: the row is what was true at the
93
96
  // moment of the decision.
94
- const record = redactSecrets({ at: new Date().toISOString(), ...row, ...(actor ? { actor } : {}) });
97
+ const record = redactSecrets({ at: new Date().toISOString(), ...row, ...(actor ? { actor } : {}), ...(via ? { via } : {}) });
95
98
  const previous = writeQueues.get(dataDir) ?? Promise.resolve();
96
99
  // Serialize appends (and the occasional prune) per directory, so two
97
100
  // simultaneous approvals keep their decision order.
@@ -258,7 +261,7 @@ export function decisionsCsv(rows) {
258
261
  row.summary ?? "",
259
262
  row.rule ?? "",
260
263
  row.unattended ? "yes" : "",
261
- actorLabel(row.actor),
264
+ row.via === "call" ? `${actorLabel(row.actor)} (by voice)` : actorLabel(row.actor),
262
265
  row.threadId,
263
266
  row.requestId ?? "",
264
267
  ].map(csvCell).join(","));
@@ -192,6 +192,8 @@ export function _loadPending() {
192
192
  if (typeof item.targetThreadId === "string" && item.targetThreadId) {
193
193
  loaded.targetThreadId = item.targetThreadId;
194
194
  }
195
+ if (item.oneWay === true)
196
+ loaded.oneWay = true;
195
197
  return [loaded];
196
198
  });
197
199
  if (items.length)
@@ -268,7 +270,7 @@ const MAX_QUEUED_PER_THREAD = 6;
268
270
  export function queueDelegation(bus, from, item, maxDepth, sourceThreadId = from.threadId) {
269
271
  if (item.toBotId === from.id)
270
272
  return { result: "self" };
271
- if (item.depth >= maxDepth)
273
+ if (!item.oneWay && item.depth >= maxDepth)
272
274
  return { result: "too_deep" };
273
275
  const target = bus.store.bot(item.toBotId);
274
276
  if (!target)
@@ -347,6 +349,11 @@ onSettled) {
347
349
  const from = bus.store.botByThread(threadId) ??
348
350
  bus.store.bot(item.sourceBotId);
349
351
  if (!from) {
352
+ appendDeliveryMessage(bus, threadId, item, {
353
+ role: "bot",
354
+ kind: "activity",
355
+ tool: { name: "Send failed — the sending bot no longer exists", ok: false },
356
+ });
350
357
  recordDelegationReceipt({
351
358
  id: item.id,
352
359
  sourceThreadId: threadId,
@@ -373,7 +380,7 @@ onSettled) {
373
380
  result: why.slice(0, 200),
374
381
  });
375
382
  try {
376
- bus.store.appendMessage(threadId, {
383
+ appendDeliveryMessage(bus, threadId, item, {
377
384
  role: "bot",
378
385
  kind: "activity",
379
386
  tool: { name: `error: delegation failed — ${why.slice(0, 120)}`, ok: false },
@@ -393,7 +400,7 @@ onSettled) {
393
400
  if (outcome === "settled" && stillQueued) {
394
401
  const receipt = findDelegationReceipt(item.id);
395
402
  try {
396
- if (receipt)
403
+ if (receipt && !item.oneWay)
397
404
  onSettled?.(receipt);
398
405
  }
399
406
  catch (error) {
@@ -428,6 +435,15 @@ function acknowledgeDelegation(threadId, itemId) {
428
435
  pendingDelegations.delete(threadId);
429
436
  savePending();
430
437
  }
438
+ function appendDeliveryMessage(bus, sourceThreadId, item, message) {
439
+ const threadId = item.oneWay && item.targetThreadId && bus.store.taskByThread(item.toBotId, item.targetThreadId)
440
+ ? item.targetThreadId : sourceThreadId;
441
+ // A failed independent send needs a visible receipt, not a source wake.
442
+ // Never recreate a deleted conversation just to post that failure.
443
+ if (item.oneWay && threadId === sourceThreadId && !sourceThreadBelongsToBot(bus.store, item.sourceBotId, sourceThreadId))
444
+ return;
445
+ bus.store.appendMessage(threadId, message);
446
+ }
431
447
  const isExpired = (item, now) => now - item.queuedAt >= DELEGATION_TTL_MS;
432
448
  /** Past the busy-hold cap — the tighter bound that fires while its target is
433
449
  * still busy, long before the 24-hour window. */
@@ -457,9 +473,9 @@ function expireDelegation(bus, sourceThreadId, item, ownerId) {
457
473
  status: "expired",
458
474
  result: busyHold ? `@${name} was still busy after ${busyHoldCapText()}` : `@${name} was not free to take this for 24 hours`,
459
475
  });
460
- if (!sourceThreadBelongsToBot(bus.store, ownerId, sourceThreadId))
476
+ if (!item.oneWay && !sourceThreadBelongsToBot(bus.store, ownerId, sourceThreadId))
461
477
  return;
462
- bus.store.appendMessage(sourceThreadId, {
478
+ appendDeliveryMessage(bus, sourceThreadId, item, {
463
479
  role: "bot",
464
480
  kind: "activity",
465
481
  tool: {
@@ -511,15 +527,16 @@ export function expireStaleDelegations(bus, now, onSettled) {
511
527
  expireDelegation(bus, threadId, item, ownerId ?? item.sourceBotId);
512
528
  const receipt = findDelegationReceipt(item.id);
513
529
  if (receipt)
514
- expired.push(receipt);
530
+ expired.push({ receipt, oneWay: item.oneWay === true });
515
531
  }
516
532
  }
517
533
  if (!expired.length)
518
534
  return 0;
519
535
  savePending();
520
- for (const receipt of expired) {
536
+ for (const { receipt, oneWay } of expired) {
521
537
  try {
522
- onSettled?.(receipt);
538
+ if (!oneWay)
539
+ onSettled?.(receipt);
523
540
  }
524
541
  catch (error) {
525
542
  console.error("delegation expired but its source could not be resumed", error);
@@ -533,9 +550,14 @@ export function discardDelegations(bus, threadId) {
533
550
  const list = pendingDelegations.get(threadId);
534
551
  if (!list?.length)
535
552
  return;
536
- pendingDelegations.delete(threadId);
553
+ const kept = list.filter(item => item.oneWay);
554
+ const dropped = list.filter(item => !item.oneWay);
555
+ if (kept.length)
556
+ pendingDelegations.set(threadId, kept);
557
+ else
558
+ pendingDelegations.delete(threadId);
537
559
  savePending();
538
- for (const item of list) {
560
+ for (const item of dropped) {
539
561
  recordDelegationReceipt({
540
562
  id: item.id,
541
563
  sourceThreadId: threadId,
@@ -545,14 +567,16 @@ export function discardDelegations(bus, threadId) {
545
567
  result: "the delegating turn did not finish",
546
568
  });
547
569
  }
570
+ if (!dropped.length)
571
+ return;
548
572
  const from = bus.store.botByThread(threadId) ??
549
- bus.store.bot(list.find((item) => bus.store.bot(item.sourceBotId))?.sourceBotId ?? list[0].sourceBotId);
573
+ bus.store.bot(dropped.find((item) => bus.store.bot(item.sourceBotId))?.sourceBotId ?? dropped[0].sourceBotId);
550
574
  if (!from)
551
575
  return;
552
576
  bus.store.appendMessage(threadId, {
553
577
  role: "bot",
554
578
  kind: "activity",
555
- tool: { name: `${list.length} queued delegation${list.length > 1 ? "s" : ""} dropped — the turn did not finish`, ok: false },
579
+ tool: { name: `${dropped.length} queued delegation${dropped.length > 1 ? "s" : ""} dropped — the turn did not finish`, ok: false },
556
580
  });
557
581
  }
558
582
  async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarget) {
@@ -560,7 +584,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
560
584
  let target = bus.store.bot(item.toBotId);
561
585
  // Retained approval authorizes the message, not a deleted conversation or
562
586
  // revoked room membership. Check every retry before writing to its source.
563
- if (!sourceThreadBelongsToBot(bus.store, sender.id, sourceThreadId)) {
587
+ if (!item.oneWay && !sourceThreadBelongsToBot(bus.store, sender.id, sourceThreadId)) {
564
588
  recordDelegationReceipt({
565
589
  id: item.id,
566
590
  sourceThreadId,
@@ -580,7 +604,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
580
604
  status: "error",
581
605
  result: "no such bot",
582
606
  });
583
- bus.store.appendMessage(sourceThreadId, {
607
+ appendDeliveryMessage(bus, sourceThreadId, item, {
584
608
  role: "bot",
585
609
  kind: "activity",
586
610
  tool: { name: `error: delegation to ${item.toBotId} failed — no such bot`, ok: false },
@@ -621,7 +645,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
621
645
  // a denial, which otherwise recreates a deleted source transcript.
622
646
  const current = bus.store.bot(item.toBotId);
623
647
  const currentSender = bus.store.bot(from.id);
624
- if (!current || !currentSender || !sourceThreadBelongsToBot(bus.store, currentSender.id, sourceThreadId)) {
648
+ if (!current || !currentSender || (!item.oneWay && !sourceThreadBelongsToBot(bus.store, currentSender.id, sourceThreadId))) {
625
649
  recordDelegationReceipt({
626
650
  id: item.id,
627
651
  sourceThreadId,
@@ -644,7 +668,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
644
668
  approvalOutcome: verdict,
645
669
  result: verdict === "deny" ? "the user denied this handoff" : failure.error,
646
670
  });
647
- bus.store.appendMessage(sourceThreadId, {
671
+ appendDeliveryMessage(bus, sourceThreadId, item, {
648
672
  role: "bot",
649
673
  kind: "activity",
650
674
  tool: { name: verdict === "deny" ? `Delegation to @${target.name} denied by user`
@@ -682,8 +706,9 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
682
706
  // members; otherwise the exchange falls back to the pair DM.
683
707
  const originatingGroup = (item.originatingGroupId ? bus.store.group(item.originatingGroupId) : undefined) ??
684
708
  (sourceThreadId ? bus.store.groupByThread(sourceThreadId) : undefined);
685
- const channel = getOrCreateChannel(bus.store, sender, target, originatingGroup);
686
- mirrorExchange(bus, sender, target, item.message, channel, sourceThreadId);
709
+ const channel = item.oneWay ? undefined : getOrCreateChannel(bus.store, sender, target, originatingGroup);
710
+ if (channel)
711
+ mirrorExchange(bus, sender, target, item.message, channel, sourceThreadId);
687
712
  const reasonLine = item.reason ? `\n\n[Reason: ${item.reason}]` : "";
688
713
  // A fresh thread's first line gets the shared peer-provenance note from
689
714
  // the harness (which knows whether the opener was unattended); the
@@ -691,7 +716,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
691
716
  const prefixed = item.targetThreadId
692
717
  ? item.message
693
718
  : `[Delegated by @${sender.name}, another bot in this OpenMausBot workspace. Do the work and reply directly.]\n\n${item.message}${reasonLine}`;
694
- await runTarget(item.toBotId, prefixed, item.depth + 1, sourceThreadId, channel, item.id, sender.id, item.targetThreadId);
719
+ await runTarget(item.toBotId, prefixed, item.oneWay ? 0 : item.depth + 1, sourceThreadId, channel, item.id, sender.id, item.targetThreadId, item.oneWay === true);
695
720
  return "dispatched";
696
721
  }
697
722
  /** "Is the target free to take this handoff right now?" Both shapes ask for
@@ -723,7 +748,7 @@ function holdWhileTargetBusy(bus, target, sourceThreadId, item, canTakeTurn = ta
723
748
  item.busySince = Date.now();
724
749
  if (!item.waitAnnounced) {
725
750
  item.waitAnnounced = true;
726
- bus.store.appendMessage(sourceThreadId, {
751
+ appendDeliveryMessage(bus, sourceThreadId, item, {
727
752
  role: "bot",
728
753
  kind: "activity",
729
754
  tool: { name: waitingChipText(bus.store, target, item) },
@@ -757,7 +782,7 @@ function dropIfThreadGone(bus, target, sourceThreadId, item) {
757
782
  status: "dropped",
758
783
  result: `the thread opened on @${target.name} was deleted before it could start`,
759
784
  });
760
- bus.store.appendMessage(sourceThreadId, {
785
+ appendDeliveryMessage(bus, sourceThreadId, item, {
761
786
  role: "bot",
762
787
  kind: "activity",
763
788
  tool: { name: `Thread on @${target.name} canceled — it was deleted before it could start`, ok: false },
@@ -795,7 +820,7 @@ function dropIfUnreachable(bus, sender, target, sourceThreadId, item) {
795
820
  status: "dropped",
796
821
  result,
797
822
  });
798
- bus.store.appendMessage(sourceThreadId, {
823
+ appendDeliveryMessage(bus, sourceThreadId, item, {
799
824
  role: "bot",
800
825
  kind: "activity",
801
826
  tool: { name: `Delegation to @${target.name} canceled — ${reason}`, ok: false },