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.
- package/dist/assets/{DesktopViewer-CKNR3SEW.js → DesktopViewer-BwWpc4RK.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-Ckrm1I1X.js → abnfDiagram-O67JEVCF-jsQkIqTn.js} +1 -1
- package/dist/assets/{arc-BBdBuCgB.js → arc-7kei4Hsu.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CDdA3Hjs.js → architectureDiagram-NJMV4G6O-CRWDFDMq.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-AUrsx1YR.js → blockDiagram-BEXU5L5S-CTKW_IKs.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-BEqQ0Unh.js → c4Diagram-YGBWAQC7-BBTW7-6M.js} +1 -1
- package/dist/assets/channel-DS2NbT5x.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-CEEuvd4C.js → chunk-2Q5K7J3B-M_Ox-MkD.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-DyANCiHl.js → chunk-3FUC2YCW-D2YiN-Ud.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-Dx_Slpw9.js → chunk-5VM5RSS4-BmVYJ0f8.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Dl7h7iiD.js → chunk-GTNCS2PH-BtiLVVnZ.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BLx07SPb.js → chunk-GWA4HPMP-DFYSb6J6.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BMxDj3n7.js → chunk-JWPE2WC7-mknKBzgV.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-CN66PT4H.js → chunk-NETBCI7D-DgSYGiOu.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-6HwhsDKw.js → chunk-WEXAMYUT-C7ZvuguH.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-gWnLt7SN.js → chunk-XXDRQBXY-CPqxINpA.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-CMS5l2ea.js → classDiagram-v2-NBCMYWYE-Cd6xfJaC.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CV7sQwIG.js → cose-bilkent-JH36ORCC-BSXY5qRh.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-CSyl-7vl.js → cynefin-OW5HDTMX-D_lhEMj1.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BCLKL6az.js → cynefinDiagram-VND7K2PF-Da9FrFwk.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DiaZs6S1.js → dagre-6A5THRUB-T77jMOXe.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BeTtmt3I.js → diagram-22UHCM2B-Dkj96X5E.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CsdBG-QQ.js → diagram-3UASUU5V-CMi9zlD_.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BczvdbR1.js → diagram-ATOU4E4O-CXK6YUfS.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DHTTTMhi.js → diagram-CDSNMT55-BE7B1u9Q.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-RTjHyd8z.js → diagram-MLGK6HIB-CCMQphKr.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-BDenHEH_.js → diagram-MPIPVDR6-iHJ_-tGH.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BSiRSbtL.js → ebnfDiagram-ZINNZB2B-DONsCWQ7.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BCAElss-.js → elk-276RUBZZ-CU_WOaXk.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-C4_1ZPJe.js → erDiagram-OPXOYQCR-BeYwM04G.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bcb4Z75S.js → flowDiagram-KWPJA3E3-Bdw9DqEl.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BiPOzxzb.js → ganttDiagram-FUAMR5RP-C9iwq3qm.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-B1wtF3ZS.js → gitGraphDiagram-X574FWY7-Dx-VWzUg.js} +1 -1
- package/dist/assets/{index-BkYwilkG.js → index-KygHRE3E.js} +1 -1
- package/dist/assets/index-NF7XCHAy.css +1 -0
- package/dist/assets/index-X_5ydXIE.js +610 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-A19No71U.js → infoDiagram-VRGFBTTK-BG7uaEd4.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js → ishikawaDiagram-OU5B5YK6-D72WAXnM.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DAO0pVan.js → journeyDiagram-ZHPQQLJL-DKH1gjYT.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-NepmKEe2.js → kanban-definition-PNTS6WVX-3upm9JUT.js} +1 -1
- package/dist/assets/{linear-bq1PTDTW.js → linear-CcR4pc0-.js} +1 -1
- package/dist/assets/{mermaid.core-s4LTbQle.js → mermaid.core-C6yU5FSS.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-LtdbgHFH.js → mindmap-definition-NLK3R4M7-DOI2xEuv.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-tdbaCxiK.js → pegDiagram-GJSIUBJH-BVWJSgLd.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DvkbpW8l.js → pieDiagram-5QR66LMP-jtbYezhq.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Do4KaH-E.js → quadrantDiagram-O4NWA36T-g0u8mtPX.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BQWULOAs.js → railroadDiagram-XR7U4H2S-k8689Wny.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-w9kei1FU.js → requirementDiagram-PLB6GJNP-DsJ22PDt.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-D2gRy0e8.js → sankeyDiagram-IPEJSGJF-CneWBRgG.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-a2sO5_Eo.js → sequenceDiagram-PO4LG4MO-DWsFCWap.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CiPJmFEB.js → sizeCapture-INFHLROL-Bvf0YE3S.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DAkUUlL7.js → stateDiagram-v2-GCMORJYK-Bxb3Iei8.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DANf2R8Y.js → swimlanes-2SLR337P-DLkk7W4A.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C6bZtBXk.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BKpQmzrT.js → timeline-definition-EJHVYXUP-KR9bIHYC.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-BtI7qLN-.js → usecaseDiagram-POWQR4AR-DfsOrj8t.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-gXJXrAG_.js → vennDiagram-UO4OBE2U-DRSTzG3h.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-GU8Po5-n.js → wardleyDiagram-VNRHLVJA-DtEdhPgT.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BF4J30g6.js → xychartDiagram-PMCCYNJV-B-XhTxjG.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +3 -1
- package/dist-server/companion/src/routes.js +60 -0
- package/dist-server/container-mcp.js +94 -77
- package/dist-server/drivers/agents-proxy.js +96 -39
- package/dist-server/drivers/pi-mcp-extension.ts +245 -299
- package/dist-server/enterprise/server/index.js +3 -1
- package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +35 -19
- package/dist-server/index.js +18242 -13645
- package/dist-server/local-computer.js +3 -1
- package/dist-server/mcp-gate.js +147 -19
- package/dist-server/mcp-remote-proxy.js +437 -0
- package/dist-server/openmausbot.js +9618 -10058
- package/dist-server/pair-cli.js +9618 -10058
- package/dist-server/permission-proxy.js +65 -1
- package/dist-server/proxy-paths.js +3 -1
- package/dist-server/server/account-signin.js +0 -2
- package/dist-server/server/activity.js +201 -0
- package/dist-server/server/auto-approve.js +39 -11
- package/dist-server/server/automatic-recovery.js +1 -1
- package/dist-server/server/boat-create-idempotency.js +0 -7
- package/dist-server/server/boat-delete-journal.js +0 -4
- package/dist-server/server/bot-package.js +1 -19
- package/dist-server/server/browser-live.js +14 -5
- package/dist-server/server/browser-runtime.js +7 -2
- package/dist-server/server/channel-queue.js +4 -1
- package/dist-server/server/chief-of-staff.js +2 -3
- package/dist-server/server/cli.js +32 -12
- package/dist-server/server/cloud-backend.js +0 -4
- package/dist-server/server/cloud-computer-tools.js +110 -0
- package/dist-server/server/cloud-home.js +0 -1
- package/dist-server/server/cloud-lending.js +0 -3
- package/dist-server/server/cloud-move-http.js +9 -3
- package/dist-server/server/cloud-move.js +18 -0
- package/dist-server/server/composio.js +31 -2
- package/dist-server/server/computer-control.js +0 -15
- package/dist-server/server/computer-observation.js +0 -74
- package/dist-server/server/computer-wait.js +33 -13
- package/dist-server/server/config.js +73 -11
- package/dist-server/server/container-computer.js +7 -19
- package/dist-server/server/contracts.js +9 -0
- package/dist-server/server/control-client.js +4 -10
- package/dist-server/server/decision-log.js +9 -6
- package/dist-server/server/delegations.js +47 -22
- package/dist-server/server/drivers/acp/core.js +64 -13
- package/dist-server/server/drivers/acp/grok.js +208 -3
- package/dist-server/server/drivers/agents-call.js +70 -29
- package/dist-server/server/drivers/agents-catalog.js +34 -12
- package/dist-server/server/drivers/agents-proxy.js +8 -30
- package/dist-server/server/drivers/antigravity.js +1 -4
- package/dist-server/server/drivers/boatagent.js +25 -4
- package/dist-server/server/drivers/cerebras.js +3 -3
- package/dist-server/server/drivers/chat-mcp-tools.js +29 -19
- package/dist-server/server/drivers/claude.js +33 -19
- package/dist-server/server/drivers/codex.js +84 -10
- package/dist-server/server/drivers/openai-chat.js +14 -9
- package/dist-server/server/drivers/pi.js +48 -4
- package/dist-server/server/drivers/retry.js +8 -4
- package/dist-server/server/engine-install.js +1 -5
- package/dist-server/server/environment.js +3 -0
- package/dist-server/server/guarded-requests.js +2 -1
- package/dist-server/server/harness/registry.js +0 -1
- package/dist-server/server/{browser-proxy.js → harness-mcp-proxy.js} +40 -20
- package/dist-server/server/index.js +1929 -507
- package/dist-server/server/lending-memory.js +12 -8
- package/dist-server/server/live-call-controller.js +868 -0
- package/dist-server/server/live-call.js +216 -0
- package/dist-server/server/mcp-gate-config.js +43 -12
- package/dist-server/server/mcp-gate.js +127 -25
- package/dist-server/server/mcp-http.js +46 -8
- package/dist-server/server/mcp-remote-proxy.js +108 -0
- package/dist-server/server/memory-entries.js +38 -8
- package/dist-server/server/memory-organize.js +1 -8
- package/dist-server/server/memory-topics.js +4 -4
- package/dist-server/server/memory-upkeep.js +13 -15
- package/dist-server/server/message-file.js +5 -9
- package/dist-server/server/new-bot-defaults.js +3 -0
- package/dist-server/server/outbound-counts.js +79 -0
- package/dist-server/server/outbound-requests.js +60 -0
- package/dist-server/server/package-import.js +1 -1
- package/dist-server/server/peer-delivery.js +1 -0
- package/dist-server/server/peer-provenance.js +4 -2
- package/dist-server/server/permission-proxy.js +15 -1
- package/dist-server/server/plan-usage.js +797 -0
- package/dist-server/server/proxy-paths.js +3 -1
- package/dist-server/server/redact.js +2 -0
- package/dist-server/server/request-auth.js +3 -2
- package/dist-server/server/room-handoffs.js +5 -13
- package/dist-server/server/routes/live.js +98 -0
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +5 -1
- package/dist-server/server/setup-mode.js +0 -2
- package/dist-server/server/skill-learn.js +0 -1
- package/dist-server/server/skill-library.js +195 -3
- package/dist-server/server/skills-library-migration.js +235 -0
- package/dist-server/server/skills.js +66 -3
- package/dist-server/server/steer-queue.js +13 -1
- package/dist-server/server/store.js +41 -5
- package/dist-server/server/surface.js +74 -23
- package/dist-server/server/system-prompt.js +4 -4
- package/dist-server/server/team-backup.js +2 -1
- package/dist-server/server/team-memory.js +209 -0
- package/dist-server/server/thread-events.js +3 -3
- package/dist-server/server/tunnel.js +20 -1
- package/dist-server/server/turn-log.js +10 -0
- package/dist-server/server/turn-resources.js +124 -0
- package/dist-server/server/vps-computer.js +4 -14
- package/dist-server/server/workspace.js +85 -47
- package/dist-server/shared/activity.js +3 -0
- package/dist-server/shared/ask-question.js +13 -0
- package/dist-server/shared/bot-avatar.js +8 -0
- package/dist-server/shared/call-consent.js +28 -0
- package/dist-server/shared/connector-scopes.js +78 -0
- package/dist-server/shared/live-approval.js +124 -0
- package/dist-server/shared/live-call.js +176 -0
- package/dist-server/shared/outbound.js +128 -0
- package/dist-server/shared/skill-md.js +10 -0
- package/dist-server/shared/team-backup.js +3 -0
- package/dist-server/shared/tool-scope-support.js +25 -0
- package/dist-server/shared/tool-scope.js +136 -0
- package/dist-server/shared/tool-surface.js +3 -2
- package/dist-server/src/lib/thread-refs.js +406 -0
- package/dist-server/vps-container-mcp.js +94 -77
- package/dist-server/workspace-backup.worker.js +89 -100
- package/enterprise/server/index.js +3 -1
- package/package.json +1 -1
- package/dist/assets/channel-BU94VpxE.js +0 -1
- package/dist/assets/index-Biji-bfL.css +0 -1
- package/dist/assets/index-DZlhoES0.js +0 -597
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +0 -8
- package/dist-server/server/drivers/chat-boat-tools.js +0 -90
- package/dist-server/server/drivers/grokagent.js +0 -4
- 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
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
/**
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
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 (
|
|
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
|
|
94
|
-
* per tool: the model needs the same three facts
|
|
95
|
-
* happened, don't retry blindly, and
|
|
96
|
-
|
|
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`,
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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: `${
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 },
|