openmausbot 0.1.90 → 0.1.92
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-Do6lBa4C.js +5 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-DUyXd0kL.js → abnfDiagram-O67JEVCF-Cd3X7Hxc.js} +1 -1
- package/dist/assets/{arc-q73qM-At.js → arc-DIYc7Ymx.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-D33O3Wur.js → architectureDiagram-NJMV4G6O-DBmRGVyP.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-DLsJeoXC.js → blockDiagram-BEXU5L5S-CPFePSEE.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-DnaljZnu.js → c4Diagram-YGBWAQC7-DD_DO1qy.js} +1 -1
- package/dist/assets/channel-Dtk5Mtic.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-Da0h2i79.js → chunk-2Q5K7J3B-BNEeNSjX.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-CfzHi54f.js → chunk-3FUC2YCW-BbinLaqH.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-LjE4d9Jf.js → chunk-5VM5RSS4-BBgYrgDR.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-DY-UGmvc.js → chunk-GTNCS2PH-ezXj-kA-.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BqeGhyhP.js → chunk-GWA4HPMP-9FxZ3diV.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BYa9EBAg.js → chunk-JWPE2WC7-CO-40JpI.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-vTrDtWyb.js → chunk-NETBCI7D-BEhjslYE.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-ic5tXadM.js → chunk-WEXAMYUT-CNeq4n7_.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-DvRCY-c6.js → chunk-XXDRQBXY-OsCY7KSi.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DVbmgz_L.js → classDiagram-v2-NBCMYWYE-CloRUEtk.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-vdUs2n57.js → cose-bilkent-JH36ORCC-D1BGZhp4.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-BvLGlV3U.js → cynefin-OW5HDTMX-CUf4T2CN.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-C4GcJQDt.js → cynefinDiagram-VND7K2PF-DPEZrFGA.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-BMSaHpt_.js → dagre-6A5THRUB-bDe6GIv5.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-D5b9UBhC.js → diagram-22UHCM2B-CdcUQz55.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CIKLlB7Y.js → diagram-3UASUU5V-BZpqGg7c.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BzXb-7k7.js → diagram-ATOU4E4O-DvGM6ugp.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-HsTvJ7uT.js → diagram-CDSNMT55-DSTmMm0s.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Db9ySNbQ.js → diagram-MLGK6HIB-15cucnMn.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-D6BKbpqY.js → diagram-MPIPVDR6-tMbuaE6n.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-Bq067L0t.js → ebnfDiagram-ZINNZB2B-BYxYzjHI.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-DhaD7fNJ.js → elk-276RUBZZ-v6bj852W.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-fwIhfz36.js → erDiagram-OPXOYQCR-5lBBlnd2.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-DygIyu0V.js → flowDiagram-KWPJA3E3-CZyl1zIj.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-DPYfSO96.js → ganttDiagram-FUAMR5RP-BiAI-ltZ.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-DhkHeCle.js → gitGraphDiagram-X574FWY7-BAZISkj3.js} +1 -1
- package/dist/assets/index-Cdmjrl28.css +1 -0
- package/dist/assets/{index-G1PHKGyh.js → index-DGBaEXrM.js} +1 -1
- package/dist/assets/index-DR7YPMkW.js +597 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-DstBLxME.js → infoDiagram-VRGFBTTK-B6ooOy-Z.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-CrIsGgch.js → ishikawaDiagram-OU5B5YK6-BZ9ksi-B.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CYypGDbB.js → journeyDiagram-ZHPQQLJL-DWqlzIEe.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DGjPt8E_.js → kanban-definition-PNTS6WVX-JX6piDwL.js} +1 -1
- package/dist/assets/{linear-Dwc3Hrru.js → linear-CmOWzX0P.js} +1 -1
- package/dist/assets/{mermaid.core-Dc4OePBL.js → mermaid.core-CF1zwoQ-.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DV-vepj-.js → mindmap-definition-NLK3R4M7-CGh2K4fW.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-lx5Kj-n5.js → pegDiagram-GJSIUBJH-CDwYN0s7.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-Bq7Vmm15.js → pieDiagram-5QR66LMP-1rfjJocB.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-DeqeqOBj.js → quadrantDiagram-O4NWA36T-tKPvPiok.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-CXwRQzSC.js → railroadDiagram-XR7U4H2S-BHLm5vXR.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-8tAeoB88.js → requirementDiagram-PLB6GJNP-CB45STp7.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-BG5zjGWU.js → sankeyDiagram-IPEJSGJF-55QA_huC.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-DKF3iLcw.js → sequenceDiagram-PO4LG4MO-BckFmQHn.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-B-y1qUgT.js → sizeCapture-INFHLROL-BFCOJF11.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-MkzPgxAc.js → stateDiagram-v2-GCMORJYK-CZvmiVVN.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DnbaOEwH.js → swimlanes-2SLR337P-BpDUCEKy.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-M0GYW9Y3.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-Bb4xG_Bt.js → timeline-definition-EJHVYXUP-BpxHq6IK.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-JgkR9pFf.js → usecaseDiagram-POWQR4AR-Cj66vm02.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-CIPIrAYe.js → vennDiagram-UO4OBE2U-C9nRkWE_.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-BjCLuUvJ.js → wardleyDiagram-VNRHLVJA-D-me_nWj.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DKgiaYxT.js → xychartDiagram-PMCCYNJV-Dc_fL4mk.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/novnc-NOTICE.txt +118 -0
- package/dist-server/cloud-home-start.js +128 -15
- package/dist-server/container-mcp.js +277 -45
- package/dist-server/drivers/agents-proxy.js +16 -4
- package/dist-server/enterprise/server/index.js +5 -1
- package/dist-server/index.js +46703 -39955
- package/dist-server/local-computer-proxy.js +128 -7
- package/dist-server/mcp-gate.js +11 -5
- package/dist-server/model-catalog/models-dev.snapshot.json +7299 -0
- package/dist-server/openmausbot.js +4099 -1217
- package/dist-server/pair-cli.js +4099 -1217
- package/dist-server/server/boat.js +67 -22
- package/dist-server/server/bot-overview.js +7 -4
- package/dist-server/server/browser-engine.js +10 -1
- package/dist-server/server/browser-live.js +46 -2
- package/dist-server/server/browser-runtime.js +74 -8
- package/dist-server/server/browser-tool-shape.js +6 -4
- package/dist-server/server/chief-of-staff.js +1 -1
- package/dist-server/server/cloud-home-start.js +136 -26
- package/dist-server/server/cloud-home.js +64 -5
- package/dist-server/server/cloud-lending.js +167 -0
- package/dist-server/server/cloud-move-http.js +268 -0
- package/dist-server/server/cloud-move.js +418 -0
- package/dist-server/server/cloud-owner.js +196 -0
- package/dist-server/server/cloud-secrets-boot.js +6 -0
- package/dist-server/server/cloud-secrets.js +65 -0
- package/dist-server/server/config.js +215 -17
- package/dist-server/server/container-computer.js +3 -2
- package/dist-server/server/decider/index.js +238 -0
- package/dist-server/server/decider/jev.js +183 -0
- package/dist-server/server/decider/log.js +62 -0
- package/dist-server/server/decider/relay.js +52 -0
- package/dist-server/server/decider/room-routing.js +88 -0
- package/dist-server/server/decider/types.js +5 -0
- package/dist-server/server/desktop-viewer-targets.js +26 -0
- package/dist-server/server/drivers/acp/core.js +167 -21
- package/dist-server/server/drivers/acp/opencode-go.js +614 -85
- package/dist-server/server/drivers/agents-catalog.js +22 -6
- package/dist-server/server/drivers/antigravity-acp.js +69 -11
- package/dist-server/server/drivers/antigravity-temp.js +360 -0
- package/dist-server/server/drivers/antigravity.js +21 -0
- package/dist-server/server/drivers/boatagent.js +20 -12
- package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
- package/dist-server/server/drivers/claude.js +313 -34
- package/dist-server/server/drivers/codex.js +228 -18
- package/dist-server/server/drivers/grok.js +2 -2
- package/dist-server/server/drivers/mistral.js +4 -3
- package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
- package/dist-server/server/drivers/openai-chat.js +41 -3
- package/dist-server/server/drivers/openai-compat.js +73 -23
- package/dist-server/server/engine-install.js +46 -12
- package/dist-server/server/env-path.js +15 -3
- package/dist-server/server/group-goal-run.js +6 -4
- package/dist-server/server/harness/registry.js +2 -2
- package/dist-server/server/harness-capabilities.js +2 -0
- package/dist-server/server/included-services.js +70 -0
- package/dist-server/server/index.js +1753 -263
- package/dist-server/server/lending-memory.js +479 -0
- package/dist-server/server/local-vm-idle.js +24 -6
- package/dist-server/server/managed-desktop.js +3 -0
- package/dist-server/server/mcp-bridge.js +5 -3
- package/dist-server/server/mcp-http.js +6 -3
- package/dist-server/server/mcp-oauth-discovery.js +133 -0
- package/dist-server/server/mcp-oauth-store.js +70 -0
- package/dist-server/server/mcp-oauth.js +550 -0
- package/dist-server/server/mcp-probe.js +14 -1
- package/dist-server/server/mcp-registry.js +43 -0
- package/dist-server/server/memory-upkeep.js +94 -84
- package/dist-server/server/model-catalog/catalog.js +374 -0
- package/dist-server/server/model-catalog/presets.js +176 -0
- package/dist-server/server/model-catalog/trim.js +219 -0
- package/dist-server/server/model-context-window.js +4 -4
- package/dist-server/server/package-export.js +17 -10
- package/dist-server/server/package-import.js +6 -3
- package/dist-server/server/package-parts.js +3 -1
- package/dist-server/server/provider-key-check.js +4 -2
- package/dist-server/server/recent-work.js +1 -1
- package/dist-server/server/request-auth.js +25 -0
- package/dist-server/server/routes/antigravity-leftovers.js +45 -0
- package/dist-server/server/routes/bot-memory.js +31 -6
- package/dist-server/server/routes/decider.js +19 -0
- package/dist-server/server/routes/desktop-viewer.js +194 -0
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +1 -1
- package/dist-server/server/sessions.js +56 -9
- package/dist-server/server/shared-computers.js +116 -30
- package/dist-server/server/skills.js +25 -10
- package/dist-server/server/store.js +79 -124
- package/dist-server/server/surface.js +13 -0
- package/dist-server/server/system-prompt.js +11 -0
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-manifest.js +11 -3
- package/dist-server/server/thread-events.js +2 -0
- package/dist-server/server/thread-starters.js +33 -0
- package/dist-server/server/thread-title.js +46 -0
- package/dist-server/server/tts/elevenlabs.js +11 -6
- package/dist-server/server/tts/fish.js +4 -5
- package/dist-server/server/tts/index.js +20 -11
- package/dist-server/server/vps-computer.js +79 -12
- package/dist-server/server/webhooks.js +18 -1
- package/dist-server/server/workspace-backup-policy.js +25 -3
- package/dist-server/server/workspace-backup.js +28 -8
- package/dist-server/server/workspace.js +65 -15
- package/dist-server/shared/cloud-home.js +4 -0
- package/dist-server/shared/package-format.js +30 -4
- package/dist-server/shared/team-backup.js +20 -3
- package/dist-server/shared/workspace-backup-client.js +1 -0
- package/dist-server/src/lib/citations.js +177 -0
- package/dist-server/src/lib/composer-attachments.js +7 -1
- package/dist-server/vps-container-mcp.js +278 -45
- package/dist-server/workspace-backup.worker.js +184 -38
- package/enterprise/server/index.js +5 -1
- package/package.json +1 -1
- package/dist/assets/channel-BCuyHjFV.js +0 -1
- package/dist/assets/index-CRvo-DS2.css +0 -1
- package/dist/assets/index-CWYGlA63.js +0 -588
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +0 -8
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
import { DATA_DIR } from "./config.js";
|
|
15
15
|
import { loadEnvironmentId } from "./environment.js";
|
|
16
|
+
import { boatCredential, boatProviderApi } from "./included-services.js";
|
|
16
17
|
import { adoptResolvedBoat, beginBoatCreate, boatCreateRecoverySnapshot, discardBoatCreate, rememberCreatedBoat, resolveBoatCreate, retireDeletedBoatCreate, } from "./boat-create-idempotency.js";
|
|
17
18
|
import { boatDeletionSnapshot, getBoatDeletion, hasPendingBoatDeletionForBot, markBoatDeletionAccepted, markBoatDeletionBlocked, prepareBoatDeletion, retireBoatDeletion, } from "./boat-delete-journal.js";
|
|
18
19
|
const shellQuote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
@@ -34,10 +35,8 @@ export function isolatedRemoteCommand(command) {
|
|
|
34
35
|
shellQuote(command),
|
|
35
36
|
].join(" ");
|
|
36
37
|
}
|
|
37
|
-
// overridable so tests can point at a stub instead of the live provider.
|
|
38
38
|
// Boat's provider surface keeps its historical Box-era names: env OMB_BOX_API,
|
|
39
39
|
// base path /api/box/v1, REST paths /boxes/*, and the box_ token prefix.
|
|
40
|
-
const BOAT_API = process.env.OMB_BOX_API || "https://ascii.dev/api/box/v1";
|
|
41
40
|
const READY = new Set(["idle", "ready", "running"]);
|
|
42
41
|
const SLEEPING = new Set(["archived", "archiving", "stopped", "stopping"]);
|
|
43
42
|
const DEFAULT_BOAT_TTL_SECONDS = 8 * 60 * 60;
|
|
@@ -105,11 +104,15 @@ export function boatTurnLifecycleAction({ explicitCloud, canMount, state, }) {
|
|
|
105
104
|
function snapshotBoatConfig(cfg) {
|
|
106
105
|
return { box: cfg.box ? { token: cfg.box.token } : undefined };
|
|
107
106
|
}
|
|
107
|
+
/** The base URL follows the credential in use (included-services.ts): an own
|
|
108
|
+
* token goes to Boat (OMB_BOX_API points it at a stub in tests), Cloud Pro's
|
|
109
|
+
* included token only to its relay. */
|
|
108
110
|
function boatFetch(cfg, path, opts = {}) {
|
|
109
|
-
|
|
111
|
+
const account = boatAccount(cfg);
|
|
112
|
+
return fetch(`${account?.api ?? boatProviderApi()}${path}`, {
|
|
110
113
|
...opts,
|
|
111
114
|
headers: {
|
|
112
|
-
authorization: `Bearer ${
|
|
115
|
+
authorization: `Bearer ${account?.token}`,
|
|
113
116
|
"content-type": "application/json",
|
|
114
117
|
...opts.headers,
|
|
115
118
|
},
|
|
@@ -280,7 +283,7 @@ async function requestRecordedBoatDeletion(cfg, identity, pollDelaysMs = BOAT_DE
|
|
|
280
283
|
}
|
|
281
284
|
if (!removed.ok) {
|
|
282
285
|
markBoatDeletionBlocked(identity.boxId);
|
|
283
|
-
throw Object.assign(new Error(boatErrorMessage(removed.status, "boat delete", removed.body)), { status: removed.status });
|
|
286
|
+
throw Object.assign(new Error(boatErrorMessage(removed.status, "boat delete", removed.body, usesIncludedBoat(cfg))), { status: removed.status });
|
|
284
287
|
}
|
|
285
288
|
const confirmation = await confirmAcceptedBoatDeletion(cfg, deletion, removed.body, pollDelaysMs);
|
|
286
289
|
if (confirmation === "blocked")
|
|
@@ -346,6 +349,8 @@ async function mintDesktopUrl(cfg, boxId, { vncBudgetMs = 60_000 } = {}) {
|
|
|
346
349
|
async function waitReady(cfg, boxId, budgetMs = 90_000) {
|
|
347
350
|
assertBoatNotDeleting(boxId);
|
|
348
351
|
const t0 = Date.now();
|
|
352
|
+
// Boat's words for the last failed resume, if the wait runs out on them.
|
|
353
|
+
let resumeFailure = null;
|
|
349
354
|
while (Date.now() - t0 < budgetMs) {
|
|
350
355
|
assertBoatNotDeleting(boxId);
|
|
351
356
|
const { body } = await boatJson(cfg, `/boxes/${boxId}`);
|
|
@@ -354,11 +359,25 @@ async function waitReady(cfg, boxId, budgetMs = 90_000) {
|
|
|
354
359
|
return body.box;
|
|
355
360
|
if (state === "error")
|
|
356
361
|
return null;
|
|
357
|
-
// an archiving boat can't resume until the snapshot lands — nudge after
|
|
358
|
-
|
|
359
|
-
|
|
362
|
+
// an archiving boat can't resume until the snapshot lands — nudge after.
|
|
363
|
+
// A refusal (a plan limit, say) is final: report it now. A server error
|
|
364
|
+
// is retried on the next poll, as Boat asks; 409 is a state race with a
|
|
365
|
+
// wake already under way.
|
|
366
|
+
if (state === "archived") {
|
|
367
|
+
const resumed = await boatJson(cfg, `/boxes/${boxId}/resume`, { method: "POST" });
|
|
368
|
+
if (resumed.ok)
|
|
369
|
+
resumeFailure = null;
|
|
370
|
+
else if (resumed.status !== 409) {
|
|
371
|
+
const message = boatErrorMessage(resumed.status, "waking the cloud computer", resumed.body, usesIncludedBoat(cfg));
|
|
372
|
+
if (resumed.status < 500)
|
|
373
|
+
throw new Error(message);
|
|
374
|
+
resumeFailure = message;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
360
377
|
await new Promise((r) => setTimeout(r, 2500));
|
|
361
378
|
}
|
|
379
|
+
if (resumeFailure)
|
|
380
|
+
throw new Error(resumeFailure);
|
|
362
381
|
return null;
|
|
363
382
|
}
|
|
364
383
|
// Resolving a bot's boat means LISTing every boat in the account, so it is
|
|
@@ -367,9 +386,9 @@ async function waitReady(cfg, boxId, budgetMs = 90_000) {
|
|
|
367
386
|
// whenever the direct read fails (deleted/renamed boat) and always carries
|
|
368
387
|
// the live state so callers can still see "archived".
|
|
369
388
|
const boatIdCache = new Map();
|
|
370
|
-
function boatInventoryProblem(status, body) {
|
|
389
|
+
function boatInventoryProblem(status, body, included = false) {
|
|
371
390
|
if (status === 401 || status === 403) {
|
|
372
|
-
return "boat.dev rejected the Boat API key — update it in Settings →
|
|
391
|
+
return included ? INCLUDED_BOAT_UNAVAILABLE : "boat.dev rejected the Boat API key — update it in Settings → API keys";
|
|
373
392
|
}
|
|
374
393
|
if (status === 429)
|
|
375
394
|
return "boat.dev is rate-limiting this account — wait a minute and refresh";
|
|
@@ -396,7 +415,11 @@ async function listBoatPages(cfg) {
|
|
|
396
415
|
return { ok: false, problem: "Could not reach boat.dev to list cloud computers — check your connection and refresh" };
|
|
397
416
|
}
|
|
398
417
|
if (!listed.ok || !Array.isArray(listed.body?.boxes)) {
|
|
399
|
-
return {
|
|
418
|
+
return {
|
|
419
|
+
ok: false,
|
|
420
|
+
problem: boatInventoryProblem(listed.status, listed.body, usesIncludedBoat(cfg)),
|
|
421
|
+
credentialRejected: listed.status === 401 || listed.status === 403,
|
|
422
|
+
};
|
|
400
423
|
}
|
|
401
424
|
boats.push(...listed.body.boxes);
|
|
402
425
|
const next = listed.body?.pageInfo?.nextCursor;
|
|
@@ -672,7 +695,7 @@ export async function inspectBoatIdentity(cfg, boxId) {
|
|
|
672
695
|
return { available: true, identity: null, problem: null };
|
|
673
696
|
}
|
|
674
697
|
if (!inspected.ok) {
|
|
675
|
-
return { available: false, identity: null, problem: boatInventoryProblem(inspected.status, inspected.body) };
|
|
698
|
+
return { available: false, identity: null, problem: boatInventoryProblem(inspected.status, inspected.body, usesIncludedBoat(cfg)) };
|
|
676
699
|
}
|
|
677
700
|
const candidate = inspected.body?.box;
|
|
678
701
|
const returnedId = typeof candidate?.id === "string" ? candidate.id : "";
|
|
@@ -685,7 +708,7 @@ export async function inspectBoatIdentity(cfg, boxId) {
|
|
|
685
708
|
function inventoryFailure(inventory) {
|
|
686
709
|
const error = new Error(inventory.configured
|
|
687
710
|
? (inventory.problem ?? "Cloud computer inventory is unavailable")
|
|
688
|
-
: "Boat is not configured — add its API key in Settings →
|
|
711
|
+
: "Boat is not configured — add its API key in Settings → API keys");
|
|
689
712
|
error.status = inventory.configured ? 503 : 409;
|
|
690
713
|
return error;
|
|
691
714
|
}
|
|
@@ -722,8 +745,9 @@ async function stopBoat(cfg, boxId) {
|
|
|
722
745
|
// must never say a computer is sleeping when boat.dev rejected the action.
|
|
723
746
|
await runCommand(cfg, boxId, QUIESCE_BROWSER, { timeoutMs: 5_000 }).catch(() => null);
|
|
724
747
|
const stopped = await boatJson(cfg, `/boxes/${boxId}/stop`, { method: "POST" });
|
|
725
|
-
if (!stopped.ok)
|
|
726
|
-
throw Object.assign(new Error(boatErrorMessage(stopped.status, "boat sleep", stopped.body)), { status: stopped.status });
|
|
748
|
+
if (!stopped.ok) {
|
|
749
|
+
throw Object.assign(new Error(boatErrorMessage(stopped.status, "boat sleep", stopped.body, usesIncludedBoat(cfg))), { status: stopped.status });
|
|
750
|
+
}
|
|
727
751
|
}
|
|
728
752
|
function forgetBoatId(boxId) {
|
|
729
753
|
for (const [botId, cachedId] of boatIdCache) {
|
|
@@ -854,15 +878,30 @@ export async function readyBoat(cfg, botId, budgetMs = 60_000) {
|
|
|
854
878
|
return boat;
|
|
855
879
|
return waitReady(cfg, boat.id, budgetMs);
|
|
856
880
|
}
|
|
881
|
+
/** The Boat credential a request uses: the person's own token, else Cloud
|
|
882
|
+
* Pro's included one. Settings' own-key flows read cfg.box.token instead. */
|
|
883
|
+
export function boatAccount(cfg) {
|
|
884
|
+
return boatCredential(cfg.box?.token);
|
|
885
|
+
}
|
|
857
886
|
export function boatConfigured(cfg) {
|
|
858
|
-
return Boolean(cfg
|
|
887
|
+
return Boolean(boatAccount(cfg));
|
|
888
|
+
}
|
|
889
|
+
function usesIncludedBoat(cfg) {
|
|
890
|
+
return boatAccount(cfg)?.included === true;
|
|
891
|
+
}
|
|
892
|
+
/** What Settings shows: configured-or-not, and whether that is Cloud Pro's
|
|
893
|
+
* included account rather than a saved key. Never the token. */
|
|
894
|
+
export function describeBoatAccount(cfg) {
|
|
895
|
+
const account = boatAccount(cfg);
|
|
896
|
+
return { configured: Boolean(account), ...(account?.included ? { included: true } : {}) };
|
|
859
897
|
}
|
|
860
898
|
/** Ask the provider whether a token is real, before we let someone save
|
|
861
899
|
* it. Without this the paste "succeeds", and the first sign of trouble is
|
|
862
|
-
* a 401 in a different panel minutes later, with nothing to act on.
|
|
900
|
+
* a 401 in a different panel minutes later, with nothing to act on. Only
|
|
901
|
+
* ever an own token, so only ever Boat itself. */
|
|
863
902
|
export async function verifyToken(token) {
|
|
864
903
|
try {
|
|
865
|
-
const res = await fetch(`${
|
|
904
|
+
const res = await fetch(`${boatProviderApi()}/boxes`, {
|
|
866
905
|
headers: { authorization: `Bearer ${token}` },
|
|
867
906
|
signal: AbortSignal.timeout(20_000),
|
|
868
907
|
});
|
|
@@ -884,11 +923,15 @@ export async function verifyToken(token) {
|
|
|
884
923
|
return { ok: false, message: "Couldn't reach boat.dev to check that token — check your connection and retry." };
|
|
885
924
|
}
|
|
886
925
|
}
|
|
926
|
+
/** A rejected Cloud Pro relay token: nothing the person pasted, so nothing
|
|
927
|
+
* for them to fix in Settings. */
|
|
928
|
+
const INCLUDED_BOAT_UNAVAILABLE = "Cloud Pro's included cloud computers aren't available right now. Try again later.";
|
|
887
929
|
/** Turn a provider refusal into something a person can act on. The
|
|
888
930
|
* provider's own message is better than anything we can invent — it knows
|
|
889
931
|
* the plan, the limit and the link — so prefer it and only fall back to
|
|
890
|
-
* our own wording when it says nothing useful.
|
|
891
|
-
|
|
932
|
+
* our own wording when it says nothing useful. `included`: the request used
|
|
933
|
+
* Cloud Pro's included token, not the person's own. */
|
|
934
|
+
export function boatErrorMessage(status, what, body, included = false) {
|
|
892
935
|
const theirs = typeof body?.message === "string" ? body.message.trim() : "";
|
|
893
936
|
const link = typeof body?.error?.details?.billingUrl === "string" ? body.error.details.billingUrl : "";
|
|
894
937
|
if (status === 402) {
|
|
@@ -896,6 +939,8 @@ export function boatErrorMessage(status, what, body) {
|
|
|
896
939
|
return [theirs || "boat.dev needs a paid Boat plan before it will create a computer.", link].filter(Boolean).join(" ");
|
|
897
940
|
}
|
|
898
941
|
if (status === 401 || status === 403) {
|
|
942
|
+
if (included)
|
|
943
|
+
return INCLUDED_BOAT_UNAVAILABLE;
|
|
899
944
|
return "your box token was rejected by boat.dev — open App Settings and paste a current token (it starts with box_)";
|
|
900
945
|
}
|
|
901
946
|
if (status === 429) {
|
|
@@ -1089,7 +1134,7 @@ export async function provisionBoat(cfg, botId, _botName) {
|
|
|
1089
1134
|
// retry when boat.dev reports their shorter TTL ceiling.
|
|
1090
1135
|
const createRes = await createBoat(cfg, botId, credentialEnv);
|
|
1091
1136
|
if (!createRes.ok || !createRes.body?.box?.id) {
|
|
1092
|
-
throw new Error(boatErrorMessage(createRes.status, "boat create", createRes.body));
|
|
1137
|
+
throw new Error(boatErrorMessage(createRes.status, "boat create", createRes.body, usesIncludedBoat(cfg)));
|
|
1093
1138
|
}
|
|
1094
1139
|
boat = createRes.body.box;
|
|
1095
1140
|
createRequest = createRes.request;
|
|
@@ -1099,7 +1144,7 @@ export async function provisionBoat(cfg, botId, _botName) {
|
|
|
1099
1144
|
body: JSON.stringify({ name: vmName }),
|
|
1100
1145
|
});
|
|
1101
1146
|
if (!rename.ok)
|
|
1102
|
-
throw new Error(boatErrorMessage(rename.status, "box naming", rename.body));
|
|
1147
|
+
throw new Error(boatErrorMessage(rename.status, "box naming", rename.body, usesIncludedBoat(cfg)));
|
|
1103
1148
|
if (createRequest)
|
|
1104
1149
|
createRequest = resolveBoatCreate(createRequest);
|
|
1105
1150
|
}
|
|
@@ -144,14 +144,17 @@ export function grantsSummary(connectorTools) {
|
|
|
144
144
|
})
|
|
145
145
|
.sort((a, b) => (a.slug < b.slug ? -1 : 1));
|
|
146
146
|
}
|
|
147
|
-
function computerReach(computer) {
|
|
147
|
+
function computerReach(computer, cloudHome = false) {
|
|
148
|
+
// A Cloud home never offers either; say so rather than list a preference
|
|
149
|
+
// that every task there refuses.
|
|
150
|
+
const unavailable = cloudHome ? ", which isn't available on OMB Cloud" : "";
|
|
148
151
|
switch (computer) {
|
|
149
152
|
case "cloud":
|
|
150
153
|
return "Computer preference: cloud computer.";
|
|
151
154
|
case "vm":
|
|
152
|
-
return
|
|
155
|
+
return `Computer preference: Local VM${unavailable}.`;
|
|
153
156
|
case "local":
|
|
154
|
-
return
|
|
157
|
+
return `Computer preference: this computer${unavailable}.`;
|
|
155
158
|
case "browser":
|
|
156
159
|
return "Computer preference: browser only.";
|
|
157
160
|
case "off":
|
|
@@ -162,7 +165,7 @@ function computerReach(computer) {
|
|
|
162
165
|
}
|
|
163
166
|
function reachesLines(facts) {
|
|
164
167
|
const lines = [];
|
|
165
|
-
const computer = computerReach(facts.bot.computer);
|
|
168
|
+
const computer = computerReach(facts.bot.computer, facts.cloudHome);
|
|
166
169
|
if (computer)
|
|
167
170
|
lines.push(computer);
|
|
168
171
|
lines.push(facts.bot.cwd ? `Works in ${facts.bot.cwd}.` : "Works in its private workspace.");
|
|
@@ -442,8 +442,17 @@ export function agentBrowserIntegration(input) {
|
|
|
442
442
|
...(sourceEnv.USERPROFILE ? { USERPROFILE: sourceEnv.USERPROFILE } : {}),
|
|
443
443
|
})),
|
|
444
444
|
};
|
|
445
|
-
|
|
445
|
+
// A Linux host with no display cannot run headed Chrome, and OMB's managed
|
|
446
|
+
// browser is headless automation (docs/browser-packaging.md). Pin headless
|
|
447
|
+
// there even for a headed-capable caller. The engine's own flag is
|
|
448
|
+
// AGENT_BROWSER_HEADED; an explicit "0" pins the default so a daemon that
|
|
449
|
+
// kept a headed launch config still comes back headless instead of failing
|
|
450
|
+
// every launch until it is killed by hand (#1383).
|
|
451
|
+
const displayless = (input.platform ?? process.platform) === "linux" && !sourceEnv.DISPLAY && !sourceEnv.WAYLAND_DISPLAY;
|
|
452
|
+
if (input.headless !== false || displayless) {
|
|
446
453
|
env.AGENT_BROWSER_HEADLESS = "1";
|
|
454
|
+
env.AGENT_BROWSER_HEADED = "0";
|
|
455
|
+
}
|
|
447
456
|
if (input.attachCdpUrl)
|
|
448
457
|
env.AGENT_BROWSER_CDP = input.attachCdpUrl;
|
|
449
458
|
// MCP clients may filter the parent environment. Carry the configured
|
|
@@ -39,6 +39,19 @@ function displayUrl(value) {
|
|
|
39
39
|
return "";
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
/** Chrome's own launch-failure wording: the engine is installed and its
|
|
43
|
+
* daemon answers, but no browser can start (no display, an early exit).
|
|
44
|
+
* Distinct from a missing engine — the fix is a fresh session, not an
|
|
45
|
+
* install, so the generic "check the engine is installed" guidance would
|
|
46
|
+
* send the operator the wrong way (#1383). */
|
|
47
|
+
function browserLaunchFailure(error) {
|
|
48
|
+
const detail = [
|
|
49
|
+
error?.stderr,
|
|
50
|
+
error?.stdout,
|
|
51
|
+
error instanceof Error ? error.message : String(error),
|
|
52
|
+
].filter((part) => typeof part === "string").join("\n");
|
|
53
|
+
return /missing x server|\$DISPLAY|devtoolsactiveport|chrome exited|could not launch|failed to launch|browser launch failed/iu.test(detail);
|
|
54
|
+
}
|
|
42
55
|
/** Project only the viewer protocol. Never relay engine commands, results or console output. */
|
|
43
56
|
export function normalizeBrowserLiveMessage(value) {
|
|
44
57
|
const message = object(value);
|
|
@@ -150,6 +163,7 @@ export function parseBrowserLiveAction(value) {
|
|
|
150
163
|
export class BrowserLive {
|
|
151
164
|
runtime;
|
|
152
165
|
viewers = new Map();
|
|
166
|
+
sessionResets = new Map();
|
|
153
167
|
constructor({ runtime }) { this.runtime = runtime; }
|
|
154
168
|
current(viewer) {
|
|
155
169
|
try {
|
|
@@ -298,12 +312,37 @@ export class BrowserLive {
|
|
|
298
312
|
throw new Error("stale viewer");
|
|
299
313
|
const result = object(JSON.parse(stdout));
|
|
300
314
|
const data = object(result?.data);
|
|
301
|
-
if (result?.success !== true || !data)
|
|
302
|
-
|
|
315
|
+
if (result?.success !== true || !data) {
|
|
316
|
+
// Keep the engine's own error internal (logged, never sent) so a
|
|
317
|
+
// launch failure is recognized for what it is below.
|
|
318
|
+
const detail = [result?.error, result?.message].filter((part) => typeof part === "string").join(" ");
|
|
319
|
+
throw new Error(detail ? `browser command failed: ${detail}` : "browser command failed");
|
|
320
|
+
}
|
|
303
321
|
return data;
|
|
304
322
|
}
|
|
305
323
|
catch (error) {
|
|
306
324
|
console.warn("browser-live:", error);
|
|
325
|
+
if (browserLaunchFailure(error)) {
|
|
326
|
+
// The daemon is alive but holding a launch configuration Chrome
|
|
327
|
+
// cannot satisfy on this host (a headed launch with no display). It
|
|
328
|
+
// fails every command the same way until the daemon is killed —
|
|
329
|
+
// discard the session so the next connect starts a fresh one. One
|
|
330
|
+
// tracked reset per session: a reconnect or repeated command joins
|
|
331
|
+
// the in-flight teardown instead of spawning another (#1383).
|
|
332
|
+
if (!this.sessionResets.has(viewer.session)) {
|
|
333
|
+
const reset = closeBrowserSession(viewer.spec.command, env).then((closed) => {
|
|
334
|
+
if (closed)
|
|
335
|
+
console.warn(`browser-live: reset ${viewer.session} after a failed browser launch; reconnecting starts a fresh session`);
|
|
336
|
+
else
|
|
337
|
+
console.warn(`browser-live: reset of ${viewer.session} after a failed browser launch did not finish; the next command retries it`);
|
|
338
|
+
return closed;
|
|
339
|
+
}, () => false);
|
|
340
|
+
this.sessionResets.set(viewer.session, reset);
|
|
341
|
+
void reset.finally(() => { if (this.sessionResets.get(viewer.session) === reset)
|
|
342
|
+
this.sessionResets.delete(viewer.session); });
|
|
343
|
+
}
|
|
344
|
+
throw new BrowserLiveError("The browser could not start on this server. Reconnect to retry with a fresh browser session.", 503);
|
|
345
|
+
}
|
|
307
346
|
throw new BrowserLiveError("The browser could not complete this action. Check that the browser engine is installed, then reconnect.", 503);
|
|
308
347
|
}
|
|
309
348
|
}
|
|
@@ -380,6 +419,11 @@ export class BrowserLive {
|
|
|
380
419
|
options.res.once("close", () => this.close(viewer));
|
|
381
420
|
options.res.once("error", () => this.close(viewer));
|
|
382
421
|
try {
|
|
422
|
+
// A failed launch already started this session's daemon teardown; wait
|
|
423
|
+
// for it so the status probe meets a fresh daemon, not the stuck one.
|
|
424
|
+
const resetting = this.sessionResets.get(viewer.session);
|
|
425
|
+
if (resetting)
|
|
426
|
+
await resetting;
|
|
383
427
|
let status = await this.command(viewer, ["stream", "status"]);
|
|
384
428
|
if (status.enabled !== true) {
|
|
385
429
|
try {
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { killCliTree, spawnCli } from "./procs.js";
|
|
2
2
|
import { DEFAULT_BROWSER_RESULT_BUDGET, shapeBrowserToolResult, slimBrowserToolList, stripHarnessOwnedArguments } from "./browser-tool-shape.js";
|
|
3
3
|
export const BROWSER_CONTROL_REFUSAL = "Browser tools are paused while a person controls this browser. Wait for them to hand control back; do not try another browser or execution tool.";
|
|
4
|
+
/** Harness-owned recovery tool, advertised beside the engine's own tools. A
|
|
5
|
+
* phone or chat-only user has no Browser panel Restart button; without this
|
|
6
|
+
* an interrupted action refuses the bot's browser until the app restarts. */
|
|
7
|
+
export const BROWSER_RESTART_TOOL = "restart_browser";
|
|
8
|
+
const BROWSER_RESTART_TOOL_SPEC = {
|
|
9
|
+
name: BROWSER_RESTART_TOOL,
|
|
10
|
+
description: "Close and restart your browser. Use this only when a browser tool says a browser action was interrupted and the browser must be restarted. Open pages are closed; afterwards open the page you need again and check whether the interrupted action already happened before repeating it.",
|
|
11
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
12
|
+
};
|
|
13
|
+
const BROWSER_INTERRUPTED = `A browser action was interrupted. Restart this browser with the ${BROWSER_RESTART_TOOL} tool before continuing.`;
|
|
4
14
|
const MAX_REQUEST_BYTES = 1_048_576;
|
|
5
15
|
const MAX_RESPONSE_BYTES = 16_777_216;
|
|
6
16
|
/** Startup, not per-request work: a cold engine spawn can exceed a tight
|
|
@@ -200,10 +210,15 @@ class BrowserClient {
|
|
|
200
210
|
export class BrowserRuntime {
|
|
201
211
|
gates = new Map();
|
|
202
212
|
clients = new Map();
|
|
213
|
+
/** Last advertised tools per session, so a turn that starts while the
|
|
214
|
+
* browser is uncertain still sees the tools it can use after recovering. */
|
|
215
|
+
toolLists = new Map();
|
|
203
216
|
options;
|
|
204
|
-
|
|
217
|
+
closeBrowser;
|
|
218
|
+
constructor({ closeBrowser, ...options } = {}) {
|
|
205
219
|
const budget = Number(process.env.OMB_BROWSER_RESULT_BUDGET);
|
|
206
220
|
this.options = { requestTimeoutMs: 120_000, takeoverTimeoutMs: 15_000, idleMs: 60_000, maxPending: 16, resultBudget: Number.isFinite(budget) && budget > 0 ? budget : DEFAULT_BROWSER_RESULT_BUDGET, ...options };
|
|
221
|
+
this.closeBrowser = closeBrowser ?? (async () => false);
|
|
207
222
|
}
|
|
208
223
|
gate(session) {
|
|
209
224
|
let gate = this.gates.get(session);
|
|
@@ -227,7 +242,7 @@ export class BrowserRuntime {
|
|
|
227
242
|
if (gate.owner !== null)
|
|
228
243
|
throw new Error(BROWSER_CONTROL_REFUSAL);
|
|
229
244
|
if (gate.uncertain)
|
|
230
|
-
throw new Error(
|
|
245
|
+
throw new Error(BROWSER_INTERRUPTED);
|
|
231
246
|
if (gate.closing)
|
|
232
247
|
throw new Error("The browser is closing. Try again shortly.");
|
|
233
248
|
gate.agents++;
|
|
@@ -250,10 +265,21 @@ export class BrowserRuntime {
|
|
|
250
265
|
// must refuse the closing window itself or its client outlives restart().
|
|
251
266
|
if (method !== "tools/call" && this.gate(session).closing)
|
|
252
267
|
throw new Error("The browser is closing. Try again shortly.");
|
|
268
|
+
if (method === "tools/call" && params && typeof params === "object" && params.name === BROWSER_RESTART_TOOL) {
|
|
269
|
+
beforeDispatch?.();
|
|
270
|
+
await this.agentRestart(session, () => this.closeBrowser(session, spec));
|
|
271
|
+
beforeDispatch?.(); // A turn revoked while the browser closed receives no result.
|
|
272
|
+
return { content: [{ type: "text", text: "Browser restarted. Open pages were closed; open the page you need again and check whether the interrupted action already happened before repeating it." }] };
|
|
273
|
+
}
|
|
253
274
|
// Uncertainty survives client replacement and never self-resolves: refuse
|
|
254
|
-
// every new browser request until an explicit restart clears it.
|
|
255
|
-
|
|
256
|
-
|
|
275
|
+
// every new browser request until an explicit restart clears it. Listing
|
|
276
|
+
// never reaches the engine; it answers from the last list so the restart
|
|
277
|
+
// tool is reachable and the engine's tools are there once it succeeds.
|
|
278
|
+
if (this.gate(session).uncertain) {
|
|
279
|
+
if (method === "tools/list")
|
|
280
|
+
return withRestartTool(this.toolLists.get(session) ?? { tools: [] });
|
|
281
|
+
throw new Error(BROWSER_INTERRUPTED);
|
|
282
|
+
}
|
|
257
283
|
const invoke = async () => {
|
|
258
284
|
const key = JSON.stringify([spec.command, spec.args, Object.entries(spec.env).sort(([a], [b]) => a.localeCompare(b))]);
|
|
259
285
|
let entry = this.clients.get(session);
|
|
@@ -281,8 +307,11 @@ export class BrowserRuntime {
|
|
|
281
307
|
const request = method === "tools/call" ? stripHarnessOwnedArguments(params) : params;
|
|
282
308
|
let result = await entry.client.rpc(method, request);
|
|
283
309
|
beforeDispatch?.(); // A turn revoked while the tool ran receives no result.
|
|
284
|
-
if (method === "tools/list")
|
|
285
|
-
|
|
310
|
+
if (method === "tools/list") {
|
|
311
|
+
const tools = withRestartTool(slimBrowserToolList(result));
|
|
312
|
+
this.toolLists.set(session, tools);
|
|
313
|
+
return tools;
|
|
314
|
+
}
|
|
286
315
|
const toolName = request && typeof request === "object" && typeof request.name === "string" ? request.name : undefined;
|
|
287
316
|
if (toolName === "agent_browser_open" && result && typeof result === "object" &&
|
|
288
317
|
result.isError !== true) {
|
|
@@ -448,6 +477,35 @@ export class BrowserRuntime {
|
|
|
448
477
|
this.changed(gate);
|
|
449
478
|
}
|
|
450
479
|
}
|
|
480
|
+
/** The agent's own recovery, for a turn with no Browser panel to press
|
|
481
|
+
* Restart in. The same barrier as restart() — the native browser must
|
|
482
|
+
* close — but it never takes a person's hold, and a failed close leaves no
|
|
483
|
+
* owner behind that would lock a person out of the panel. */
|
|
484
|
+
async agentRestart(session, closeBrowser) {
|
|
485
|
+
const gate = this.gate(session);
|
|
486
|
+
if (gate.owner !== null)
|
|
487
|
+
throw new Error(BROWSER_CONTROL_REFUSAL);
|
|
488
|
+
if (gate.closing || gate.releasing || gate.agents || gate.humans)
|
|
489
|
+
throw new Error("The browser is busy. Wait for current work to finish before restarting.");
|
|
490
|
+
gate.ready = false;
|
|
491
|
+
gate.closing = true;
|
|
492
|
+
this.changed(gate);
|
|
493
|
+
try {
|
|
494
|
+
if (!await closeBrowser())
|
|
495
|
+
throw new Error("The browser could not be closed. Ask the person to press Restart in the Browser panel of OpenMausBot on their computer, or to restart OpenMausBot.");
|
|
496
|
+
await this.clients.get(session)?.client.stop();
|
|
497
|
+
await this.clients.get(session)?.client.stop(); // see restart()
|
|
498
|
+
gate.uncertain = false;
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
gate.uncertain = true;
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
finally {
|
|
505
|
+
gate.closing = false;
|
|
506
|
+
this.changed(gate);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
451
509
|
/** Call after the underlying browser is closed when recovering an uncertain
|
|
452
510
|
* action. This closes the MCP transport, not saved logins or profile files. */
|
|
453
511
|
async close(session) {
|
|
@@ -460,10 +518,18 @@ export class BrowserRuntime {
|
|
|
460
518
|
await this.clients.get(session)?.client.stop();
|
|
461
519
|
gate.closing = false;
|
|
462
520
|
this.changed(gate);
|
|
463
|
-
if (!gate.owner && !gate.agents && !gate.humans)
|
|
521
|
+
if (!gate.owner && !gate.agents && !gate.humans) {
|
|
464
522
|
this.gates.delete(session);
|
|
523
|
+
this.toolLists.delete(session);
|
|
524
|
+
}
|
|
465
525
|
}
|
|
466
526
|
async closeAll() {
|
|
467
527
|
await Promise.all([...new Set([...this.clients.keys(), ...this.gates.keys()])].map((session) => this.close(session)));
|
|
468
528
|
}
|
|
469
529
|
}
|
|
530
|
+
function withRestartTool(result) {
|
|
531
|
+
if (!result || typeof result !== "object" || !Array.isArray(result.tools))
|
|
532
|
+
return result;
|
|
533
|
+
const tools = (result.tools).filter((tool) => tool?.name !== BROWSER_RESTART_TOOL);
|
|
534
|
+
return { ...result, tools: [...tools, BROWSER_RESTART_TOOL_SPEC] };
|
|
535
|
+
}
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
import { trimResultText } from "./mcp-trim.js";
|
|
13
13
|
/** Launch, session and network settings OpenMausBot owns through the
|
|
14
14
|
* environment (see browser-engine.ts). A model has no business setting them
|
|
15
|
-
* per call — `session` would reach another bot's browser, `extraArgs
|
|
16
|
-
* `caCert` change the launch — and each one cost more schema
|
|
17
|
-
* own description.
|
|
15
|
+
* per call — `session` would reach another bot's browser, `extraArgs`,
|
|
16
|
+
* `caCert` and `headed` change the launch — and each one cost more schema
|
|
17
|
+
* than the tool's own description. `headed` in particular overrides
|
|
18
|
+
* AGENT_BROWSER_HEADED and the managed config, so a single call can pin a
|
|
19
|
+
* daemon to a launch mode this host cannot satisfy (#1383). */
|
|
18
20
|
export const HARNESS_OWNED_BROWSER_PARAMS = new Set([
|
|
19
|
-
"allowedDomains", "caCert", "clearCaCert", "extraArgs", "idleTimeout", "namespace",
|
|
21
|
+
"allowedDomains", "caCert", "clearCaCert", "extraArgs", "headed", "idleTimeout", "namespace",
|
|
20
22
|
"restore", "restoreCheckFn", "restoreCheckText", "restoreCheckUrl", "restoreSave", "session", "timeoutMs",
|
|
21
23
|
]);
|
|
22
24
|
/** Characters of one browser result that may enter the model's context.
|
|
@@ -29,7 +29,7 @@ export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, trustedOpen
|
|
|
29
29
|
});
|
|
30
30
|
const delegation = canDelegate
|
|
31
31
|
? boundedCoordination
|
|
32
|
-
? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room,
|
|
32
|
+
? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room, each distinct assignment starts a fresh thread for that teammate, using their own model and permissions. Include the context they need in each brief. Busy teammates queue. Give self-contained briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran. A refusal from coordinate_bots means nothing was sent: fix what it names (usually the id — copy it from list_bots or your roster; a unique teammate name also works) and retry, and never describe a handoff the tool did not accept."
|
|
33
33
|
: [
|
|
34
34
|
"Use list_bots to confirm the live roster and IDs. When assigning work to a teammate, use delegate_bot: it returns immediately, keeps you available to the user, and delivers the teammate's outcome back into this conversation automatically — success or failure. When the result arrives you are woken with it: report it to the user and act. If the teammate fails or stalls, tell the user plainly and decide the next step yourself.",
|
|
35
35
|
"After delegate_bot accepts the task, acknowledge the handoff and continue with any independent work or end your turn. Do not call wait_delegation or repeatedly poll check_delegation in the same turn.",
|