openmausbot 0.1.94 → 0.1.96
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-bxM7NV3N.js → DesktopViewer-DsnA23KM.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-BhMdJhRJ.js → abnfDiagram-O67JEVCF-Dkbd2hcj.js} +1 -1
- package/dist/assets/{arc-B5itKDcx.js → arc-Duj5Coy2.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-C5d7UY6U.js → architectureDiagram-NJMV4G6O-uYUsDawP.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-BrZIhMmu.js → blockDiagram-BEXU5L5S-CWDlTpXe.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-7I0ArfbD.js → c4Diagram-YGBWAQC7-B0t15PDc.js} +1 -1
- package/dist/assets/channel-BZxwC-Gw.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BnEsyN36.js → chunk-2Q5K7J3B-BJSCL-HV.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BXATQ52s.js → chunk-3FUC2YCW-D3_ILccd.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-B0BpTkEx.js → chunk-5VM5RSS4-D3dmwT_S.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BtLTz6FR.js → chunk-GTNCS2PH-Br73HFPD.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-D6gcZOYt.js → chunk-GWA4HPMP-uSsjxubC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DCLrosvf.js → chunk-JWPE2WC7-C40VWFyr.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-B9JsknV9.js → chunk-NETBCI7D-D6WNNYwU.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CsoTxw7z.js → chunk-WEXAMYUT-DOEhq64P.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-0F_frB0c.js → chunk-XXDRQBXY-BqJEi6Pi.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DkrpnflX.js → classDiagram-v2-NBCMYWYE-ByU_GnxT.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BNDwYBt9.js → cose-bilkent-JH36ORCC-D-Uglxqq.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-JUkNr1Mk.js → cynefin-OW5HDTMX-CZWO7aYj.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DKeh9N4M.js → cynefinDiagram-VND7K2PF-C70-w0la.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-ve-6T_wX.js → dagre-6A5THRUB-CnhjhExB.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BuJ2d8rT.js → diagram-22UHCM2B-bnjWCNb4.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BgOs_Ni-.js → diagram-3UASUU5V-A4uTWoFV.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-DMK6VmcP.js → diagram-ATOU4E4O-Zi6m3NUl.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DksASlcY.js → diagram-CDSNMT55-DtCcmd5k.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Bgn8I5k6.js → diagram-MLGK6HIB-DofuETS3.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-zK0jqO8N.js → diagram-MPIPVDR6-Dw2T9PuU.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-vKrbzTwr.js → ebnfDiagram-ZINNZB2B-DbrwPiem.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-Bzv2mPNf.js → elk-276RUBZZ-BvCt1dCb.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CvOyYuxu.js → erDiagram-OPXOYQCR-Y-QKqPnU.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CtbnDbc0.js → flowDiagram-KWPJA3E3-CDkL1B5w.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-c4dMRYyG.js → ganttDiagram-FUAMR5RP-CKV5D2F6.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-B34uaQho.js → gitGraphDiagram-X574FWY7-nL9iFTCv.js} +1 -1
- package/dist/assets/{index-CPiqc_DN.js → index-DreDnjOu.js} +1 -1
- package/dist/assets/index-Du7o6Dsf.js +610 -0
- package/dist/assets/index-KxG4bWSr.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-o64ma4CI.js → infoDiagram-VRGFBTTK-C7aHjzsf.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DV4C9BAm.js → ishikawaDiagram-OU5B5YK6-KUZsNBTh.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-Bf0AHk1H.js → journeyDiagram-ZHPQQLJL-CGUuodFl.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-B1UMSKmE.js → kanban-definition-PNTS6WVX-Cy6r868Z.js} +1 -1
- package/dist/assets/{linear-BpKhuyLm.js → linear-Bl669148.js} +1 -1
- package/dist/assets/{mermaid.core-B_BKNCw5.js → mermaid.core-86Xqz5Mr.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DeARruT1.js → mindmap-definition-NLK3R4M7-G4DzHLrd.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BOQEk_1D.js → pegDiagram-GJSIUBJH-K4eruzY0.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-EIfoiTZo.js → pieDiagram-5QR66LMP-DYdR9PPq.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-GgZwT-fi.js → quadrantDiagram-O4NWA36T-BCAh0nEy.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-D9rDQomj.js → railroadDiagram-XR7U4H2S-p0dGRc4g.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DAh1MIeo.js → requirementDiagram-PLB6GJNP-tLeBG0aL.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CPr6Uz64.js → sankeyDiagram-IPEJSGJF-QRuN16rV.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-BU6VIdcm.js → sequenceDiagram-PO4LG4MO-ueT1nVoa.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-qF2HO41r.js → sizeCapture-INFHLROL-wRS2zWg2.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DlJ8G4MD.js → stateDiagram-v2-GCMORJYK-CYzA_hE1.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DzYE8I54.js → swimlanes-2SLR337P-kDnv6LMc.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-B7wzEQ0A.js → timeline-definition-EJHVYXUP-D1y2UbbH.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-BvGgrxIP.js → usecaseDiagram-POWQR4AR-IqB4v9HX.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-CEECdgQg.js → vennDiagram-UO4OBE2U-BEi7KYVF.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-XQHA0P_P.js → wardleyDiagram-VNRHLVJA-Dkybo-Yf.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-B5mLcZI2.js → xychartDiagram-PMCCYNJV-BAPFJLpg.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +27 -13
- package/dist-server/companion/src/listener.js +39 -18
- package/dist-server/companion/src/routes.js +39 -0
- package/dist-server/container-mcp.js +16 -29
- package/dist-server/drivers/agents-proxy.js +161 -82
- package/dist-server/enterprise/server/index.js +2 -1
- package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +34 -19
- package/dist-server/index.js +4991 -4908
- package/dist-server/local-computer.js +2 -1
- package/dist-server/openmausbot.js +906 -1785
- package/dist-server/pair-cli.js +906 -1785
- package/dist-server/proxy-paths.js +2 -1
- package/dist-server/server/account-signin.js +0 -2
- package/dist-server/server/activity.js +201 -0
- package/dist-server/server/admin-activity.js +1 -4
- package/dist-server/server/auto-approve.js +25 -4
- 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/boat.js +4 -4
- package/dist-server/server/bot-package.js +1 -19
- package/dist-server/server/browser-runtime.js +1 -2
- package/dist-server/server/checkpoints.js +90 -148
- package/dist-server/server/chief-of-staff.js +9 -9
- package/dist-server/server/cli.js +33 -26
- 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-start.js +12 -15
- package/dist-server/server/cloud-home.js +0 -5
- package/dist-server/server/cloud-lending.js +0 -3
- package/dist-server/server/cloud-move-http.js +44 -30
- package/dist-server/server/cloud-move.js +20 -18
- package/dist-server/server/composio.js +41 -5
- package/dist-server/server/computer-control.js +0 -15
- package/dist-server/server/computer-observation.js +0 -74
- package/dist-server/server/config.js +121 -65
- package/dist-server/server/container-computer.js +23 -24
- package/dist-server/server/control-client.js +4 -10
- package/dist-server/server/decider/jev.js +1 -4
- package/dist-server/server/decider/log.js +1 -3
- package/dist-server/server/decision-log.js +1 -4
- package/dist-server/server/delegations.js +3 -2
- package/dist-server/server/drivers/acp/core.js +11 -8
- package/dist-server/server/drivers/acp/opencode-go.js +17 -16
- package/dist-server/server/drivers/agents-call.js +88 -92
- package/dist-server/server/drivers/agents-catalog.js +90 -11
- package/dist-server/server/drivers/agents-proxy.js +10 -31
- package/dist-server/server/drivers/antigravity.js +1 -4
- package/dist-server/server/drivers/boatagent.js +23 -4
- package/dist-server/server/drivers/cerebras.js +3 -3
- package/dist-server/server/drivers/chat-mcp-tools.js +4 -7
- package/dist-server/server/drivers/claude-login-auth.js +2 -11
- package/dist-server/server/drivers/claude.js +16 -14
- package/dist-server/server/drivers/codex-device-auth.js +3 -1
- package/dist-server/server/drivers/codex.js +5 -4
- package/dist-server/server/drivers/grok.js +1 -1
- package/dist-server/server/drivers/openai-chat-protocol.js +4 -2
- package/dist-server/server/drivers/openai-chat.js +18 -11
- package/dist-server/server/drivers/prompt-split.js +4 -4
- package/dist-server/server/drivers/retry.js +8 -4
- package/dist-server/server/engine-install.js +1 -5
- package/dist-server/server/harness/bus.js +49 -0
- 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 +1190 -971
- package/dist-server/server/lending-memory.js +12 -8
- package/dist-server/server/mcp-registry.js +35 -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 +9 -13
- 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 +5 -19
- package/dist-server/server/peer-roster.js +30 -4
- package/dist-server/server/profile-versions.js +0 -31
- package/dist-server/server/provider-key-check.js +2 -1
- package/dist-server/server/proxy-paths.js +2 -1
- package/dist-server/server/restart.js +40 -0
- package/dist-server/server/room-handoffs.js +17 -13
- package/dist-server/server/routes/bot-memory.js +0 -24
- package/dist-server/server/routine-requests.js +2 -5
- package/dist-server/server/routines.js +10 -5
- package/dist-server/server/server-launcher.js +53 -0
- 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 +0 -15
- package/dist-server/server/store.js +10 -2
- package/dist-server/server/surface.js +69 -28
- package/dist-server/server/system-prompt.js +17 -7
- package/dist-server/server/team-manifest.js +1 -6
- package/dist-server/server/team-memory.js +209 -0
- package/dist-server/server/thread-events.js +2 -2
- package/dist-server/server/tunnel.js +4 -1
- package/dist-server/server/turn-images.js +1 -21
- package/dist-server/server/turn-resources.js +19 -150
- package/dist-server/server/usage-ledger.js +3 -1
- package/dist-server/server/vps-computer.js +4 -14
- package/dist-server/server/workspace.js +87 -50
- package/dist-server/server-launcher.js +72 -0
- package/dist-server/shared/activity.js +3 -0
- package/dist-server/shared/ask-question.js +1 -1
- package/dist-server/shared/bot-avatar.js +8 -0
- package/dist-server/shared/cloud-computer.js +12 -0
- package/dist-server/shared/connector-scopes.js +78 -0
- package/dist-server/shared/failed-turn.js +25 -0
- package/dist-server/shared/live-approval.js +2 -2
- package/dist-server/shared/outbound.js +128 -0
- package/dist-server/shared/package-format.js +3 -3
- package/dist-server/shared/pairing-link.js +144 -0
- package/dist-server/shared/tool-surface.js +3 -2
- package/dist-server/vps-container-mcp.js +16 -29
- package/dist-server/workspace-backup.worker.js +14 -27
- package/enterprise/server/index.js +2 -1
- package/package.json +1 -1
- package/dist/assets/channel-CdJX6i8k.js +0 -1
- package/dist/assets/index-CPYrGsv1.js +0 -610
- package/dist/assets/index-CzT42GR8.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +0 -8
- package/dist-server/server/claim-idle.js +0 -33
- package/dist-server/server/cloud-overflow.js +0 -167
- 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
- package/dist-server/server/tightening-requests.js +0 -435
- package/dist-server/shared/tightening-request.js +0 -87
|
@@ -10,80 +10,6 @@ export const emptyObservationMetrics = () => ({
|
|
|
10
10
|
verificationSuccesses: 0,
|
|
11
11
|
verificationFailures: 0,
|
|
12
12
|
});
|
|
13
|
-
export function normalizeCrop(raw, maxWidth, maxHeight) {
|
|
14
|
-
if (!raw || typeof raw !== "object")
|
|
15
|
-
return null;
|
|
16
|
-
const value = raw;
|
|
17
|
-
const x = Math.round(Number(value.x));
|
|
18
|
-
const y = Math.round(Number(value.y));
|
|
19
|
-
const width = Math.round(Number(value.width));
|
|
20
|
-
const height = Math.round(Number(value.height));
|
|
21
|
-
if (![x, y, width, height, maxWidth, maxHeight].every(Number.isFinite) ||
|
|
22
|
-
maxWidth <= 0 ||
|
|
23
|
-
maxHeight <= 0 ||
|
|
24
|
-
x < 0 ||
|
|
25
|
-
y < 0 ||
|
|
26
|
-
width < 32 ||
|
|
27
|
-
height < 32) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
if (x + width > maxWidth || y + height > maxHeight)
|
|
31
|
-
return null;
|
|
32
|
-
return { x, y, width, height };
|
|
33
|
-
}
|
|
34
|
-
/** Canonical value for internal navigation checks. Credentials are never
|
|
35
|
-
* needed for equality and are removed here; query and fragment remain so
|
|
36
|
-
* two distinct application states cannot verify as the same destination. */
|
|
37
|
-
export function normalizeBrowserUrl(value) {
|
|
38
|
-
if (typeof value !== "string" || !value || value.length > 8_192)
|
|
39
|
-
return null;
|
|
40
|
-
try {
|
|
41
|
-
const url = new URL(value);
|
|
42
|
-
if (!/^https?:$/.test(url.protocol))
|
|
43
|
-
return null;
|
|
44
|
-
url.username = "";
|
|
45
|
-
url.password = "";
|
|
46
|
-
return url.toString();
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/** Removes credentials, query, and fragment before browser state reaches a model or log. */
|
|
53
|
-
export function safeBrowserUrl(value) {
|
|
54
|
-
const normalized = normalizeBrowserUrl(value);
|
|
55
|
-
if (!normalized)
|
|
56
|
-
return null;
|
|
57
|
-
const url = new URL(normalized);
|
|
58
|
-
url.search = "";
|
|
59
|
-
url.hash = "";
|
|
60
|
-
const safe = url.toString();
|
|
61
|
-
return safe.length <= 2_048 ? safe : null;
|
|
62
|
-
}
|
|
63
|
-
/** Parses Chrome's /json/list response into a small, safe structured observation. */
|
|
64
|
-
export function parseBrowserTargets(raw) {
|
|
65
|
-
if (raw.length > 1_000_000)
|
|
66
|
-
return [];
|
|
67
|
-
try {
|
|
68
|
-
const parsed = JSON.parse(raw);
|
|
69
|
-
if (!Array.isArray(parsed))
|
|
70
|
-
return [];
|
|
71
|
-
return parsed.slice(0, 20).flatMap((item) => {
|
|
72
|
-
if (!item || typeof item !== "object")
|
|
73
|
-
return [];
|
|
74
|
-
const value = item;
|
|
75
|
-
const comparisonUrl = normalizeBrowserUrl(value.url);
|
|
76
|
-
const url = safeBrowserUrl(value.url);
|
|
77
|
-
if (value.type !== "page" || !url || !comparisonUrl || typeof value.id !== "string")
|
|
78
|
-
return [];
|
|
79
|
-
const title = typeof value.title === "string" ? value.title.replace(/\s+/g, " ").trim().slice(0, 200) : "";
|
|
80
|
-
return [{ id: value.id.slice(0, 100), title, url, comparisonUrl }];
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
13
|
/**
|
|
88
14
|
* Keeps observations cheap without claiming the screen is immutable. Every
|
|
89
15
|
* requested observation still captures fresh pixels (pages can change without
|
|
@@ -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";
|
|
@@ -13,7 +13,6 @@ import { isModelVariant } from "./contracts.js";
|
|
|
13
13
|
import { PROVIDER_ICON_PRESETS, providerIconError } from "../shared/provider-icon.js";
|
|
14
14
|
import { isRemoteMcpServer, parseStoredMcpServer } from "./mcp-registry.js";
|
|
15
15
|
import { parseJson, schemaIssue } from "./schema.js";
|
|
16
|
-
import { CLOUD_SEAT_IDLE_STOP_MS } from "./cloud-overflow.js";
|
|
17
16
|
const optionalText = z.string().optional();
|
|
18
17
|
const SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
|
|
19
18
|
const LEGACY_BROWSER_PROFILE_ID = /^[A-Za-z0-9_-]{1,40}$/;
|
|
@@ -292,17 +291,6 @@ const featureConfigSchema = z.object({
|
|
|
292
291
|
* conversations that share words with the message ride into the turn.
|
|
293
292
|
* Read-only; on unless explicitly switched off — see autoRecallEnabled. */
|
|
294
293
|
autoRecall: z.boolean().optional(),
|
|
295
|
-
/** Idle release for computer claims (#1653): a desktop seat that stays
|
|
296
|
-
* screen-quiet for 90 seconds is released to waiting turns while its
|
|
297
|
-
* holder's turn still lives; the previous holder re-claims directly
|
|
298
|
-
* for 10 minutes and yields to an occupied seat. Off until baked; see
|
|
299
|
-
* computerClaimIdleReleaseEnabled for how to enable it by hand. */
|
|
300
|
-
computerClaimIdleRelease: z.boolean().optional(),
|
|
301
|
-
/** Consented cloud overflow for local computer waits (#1655): a wait
|
|
302
|
-
* may offer a per-second-billed cloud seat, but only through an explicit
|
|
303
|
-
* consent card or a configured allowlist — never a silent Auto default.
|
|
304
|
-
* Off until baked; see cloudOverflowEnabled for how to enable it by hand. */
|
|
305
|
-
cloudOverflow: z.boolean().optional(),
|
|
306
294
|
/** Opt-in shared skills library: one store at the data dir that bots
|
|
307
295
|
* read by assignment instead of per-workspace copies. Off until
|
|
308
296
|
* explicitly enabled — see skillsLibraryEnabled. */
|
|
@@ -436,8 +424,12 @@ const appConfigSchema = z.object({
|
|
|
436
424
|
* provider is Boat now and the key is kept for compatibility. */
|
|
437
425
|
box: z.object({ token: optionalText }).optional(),
|
|
438
426
|
vps: vpsConfigSchema.optional(),
|
|
439
|
-
/** Optional OpenCode key; persisted write-only and passed only to its child.
|
|
440
|
-
|
|
427
|
+
/** Optional OpenCode key; persisted write-only and passed only to its child.
|
|
428
|
+
* `providerKeys`: keys for OpenCode's other providers (Venice, Groq…) by the
|
|
429
|
+
* environment name OpenCode reads, also write-only and only for its child.
|
|
430
|
+
* Settings changes go through mergeOpenCodeProviderKeys; the stored copy
|
|
431
|
+
* is read loosely (storedAppConfigSchema). */
|
|
432
|
+
opencodeGo: z.object({ apiKey: optionalText, providerKeys: z.record(z.string(), z.string()).optional() }).optional(),
|
|
441
433
|
/** Voice settings and the selected voice id. `provider` picks the
|
|
442
434
|
* engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
|
|
443
435
|
* `fishKey`; `fishModel` picks its speech model), "system" (the Mac's
|
|
@@ -526,15 +518,6 @@ const appConfigSchema = z.object({
|
|
|
526
518
|
/** The authorization decision log (server/decision-log.ts): days of month
|
|
527
519
|
* files kept, at least; OMB_DECISION_RETENTION_DAYS wins when set. */
|
|
528
520
|
decisions: z.object({ retentionDays: z.number().int().min(1).max(3650).optional() }).strict().optional(),
|
|
529
|
-
/** #1655 cloud-overflow settings. perSecondCostUsd is the operator's own
|
|
530
|
-
* verified rate: with no price configured the feature stays inert rather
|
|
531
|
-
* than show an invented one. allowlistedThreads carries standing consent
|
|
532
|
-
* for exact thread ids. */
|
|
533
|
-
cloudOverflow: z.object({
|
|
534
|
-
perSecondCostUsd: z.number().positive().max(10).optional(),
|
|
535
|
-
idleStopMs: z.number().int().positive().max(24 * 60 * 60_000).optional(),
|
|
536
|
-
allowlistedThreads: z.array(z.string().min(1)).max(1000).optional(),
|
|
537
|
-
}).strict().optional(),
|
|
538
521
|
localVm: localVmConfigSchema.optional(),
|
|
539
522
|
features: featureConfigSchema.optional(),
|
|
540
523
|
onboarding: onboardingConfigSchema.optional(),
|
|
@@ -550,6 +533,15 @@ const appConfigSchema = z.object({
|
|
|
550
533
|
});
|
|
551
534
|
const storedAppConfigSchema = appConfigSchema.extend({
|
|
552
535
|
browserProfiles: storedBrowserProfilesSchema.optional(),
|
|
536
|
+
/** Read loosely, like mcpServers: a hand-edited provider key that isn't
|
|
537
|
+
* text is dropped here, and a bad name or key later
|
|
538
|
+
* (openCodeProviderKeys), never the whole file. */
|
|
539
|
+
opencodeGo: z.object({
|
|
540
|
+
apiKey: optionalText,
|
|
541
|
+
providerKeys: z.record(z.string(), z.unknown()).catch({})
|
|
542
|
+
.transform((keys) => Object.fromEntries(Object.entries(keys).filter((entry) => typeof entry[1] === "string")))
|
|
543
|
+
.optional(),
|
|
544
|
+
}).optional(),
|
|
553
545
|
});
|
|
554
546
|
const appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true })
|
|
555
547
|
.extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() });
|
|
@@ -749,43 +741,6 @@ export function claudeUserMcpEnabled(cfg) {
|
|
|
749
741
|
export function llmThreadTitlesEnabled(cfg) {
|
|
750
742
|
return cfg.features?.llmThreadTitles === true;
|
|
751
743
|
}
|
|
752
|
-
/** Idle release for computer claims (#1653): a whole-turn desktop hold
|
|
753
|
-
* ends after a screen-quiet window (default 90 seconds, screen-poller
|
|
754
|
-
* frames excluded) instead of at turn settle, and the previous holder
|
|
755
|
-
* re-claims directly inside a reclaim window (default 10 minutes) while
|
|
756
|
-
* yielding to a seat another turn already holds. Off unless an explicit
|
|
757
|
-
* `true` — bake it as a maintainer-only flag first, exactly like
|
|
758
|
-
* sharedComputers: enable by hand in ~/.openmausbot/config.json
|
|
759
|
-
* (`{"features": {"computerClaimIdleRelease": true}}`) and restart. */
|
|
760
|
-
export function computerClaimIdleReleaseEnabled(cfg) {
|
|
761
|
-
return cfg.features?.computerClaimIdleRelease === true;
|
|
762
|
-
}
|
|
763
|
-
/** Consented cloud overflow for local computer waits (#1655): while a turn
|
|
764
|
-
* waits for a local desktop, it may offer overflow to a per-second-billed
|
|
765
|
-
* cloud seat — never silently: the card names the cost, and only an
|
|
766
|
-
* explicit per-conversation consent or a configured allowlist thread may
|
|
767
|
-
* start the machine, which then hard-stops after an idle window. Off
|
|
768
|
-
* unless an explicit `true`, like computerClaimIdleRelease: enable by
|
|
769
|
-
* hand in ~/.openmausbot/config.json
|
|
770
|
-
* (`{"features": {"cloudOverflow": true}, "cloudOverflow": {"perSecondCostUsd": 0.0004}}`)
|
|
771
|
-
* and restart. */
|
|
772
|
-
export function cloudOverflowEnabled(cfg) {
|
|
773
|
-
return cfg.features?.cloudOverflow === true;
|
|
774
|
-
}
|
|
775
|
-
/** The per-second rate shown on the card, or null when the operator has
|
|
776
|
-
* not set one: the offer fails closed rather than guess a price. */
|
|
777
|
-
export function cloudOverflowPerSecondCostUsd(cfg) {
|
|
778
|
-
const cost = cfg.cloudOverflow?.perSecondCostUsd;
|
|
779
|
-
return typeof cost === "number" && Number.isFinite(cost) && cost > 0 ? cost : null;
|
|
780
|
-
}
|
|
781
|
-
/** How long a cloud seat may stay screen-quiet before it is stopped. */
|
|
782
|
-
export function cloudOverflowIdleStopMs(cfg) {
|
|
783
|
-
return cfg.cloudOverflow?.idleStopMs ?? CLOUD_SEAT_IDLE_STOP_MS;
|
|
784
|
-
}
|
|
785
|
-
/** Threads with standing consent, by exact thread id. */
|
|
786
|
-
export function cloudOverflowAllowlistedThreads(cfg) {
|
|
787
|
-
return new Set(cfg.cloudOverflow?.allowlistedThreads ?? []);
|
|
788
|
-
}
|
|
789
744
|
/** Opt-in shared skills library (skills lane S1): one store at the data dir
|
|
790
745
|
* that bots reference by assignment instead of per-workspace copies. Off
|
|
791
746
|
* unless enabled by hand in ~/.openmausbot/config.json
|
|
@@ -815,7 +770,6 @@ export const FLEET_NEUTRAL_KEYS = new Set([
|
|
|
815
770
|
"memory",
|
|
816
771
|
"localVm",
|
|
817
772
|
"features",
|
|
818
|
-
"cloudOverflow",
|
|
819
773
|
"browserProfiles",
|
|
820
774
|
"onboarding",
|
|
821
775
|
]);
|
|
@@ -986,6 +940,34 @@ export function loadConfig() {
|
|
|
986
940
|
}
|
|
987
941
|
return cfg;
|
|
988
942
|
}
|
|
943
|
+
/** `derive(loadConfig())`, worked out again only when config.json changes:
|
|
944
|
+
* a new file (every save renames one into place), a new size, a new
|
|
945
|
+
* modification time, or new permissions or owner. For values read on every
|
|
946
|
+
* request or stream frame, where parsing the file each time costs more than
|
|
947
|
+
* the work itself. A file that is missing, saved in the last two seconds, or
|
|
948
|
+
* that loadConfig() could not use (unreadable, a failed read, invalid JSON)
|
|
949
|
+
* is read on every call: a second write inside one clock tick is never
|
|
950
|
+
* missed, and a file that is fixed is used again on the next call. */
|
|
951
|
+
export function cacheUntilConfigChanges(derive) {
|
|
952
|
+
let cached = null;
|
|
953
|
+
return () => {
|
|
954
|
+
let stamp = null;
|
|
955
|
+
try {
|
|
956
|
+
const file = statSync(join(DATA_DIR, "config.json"), { bigint: true });
|
|
957
|
+
if (Date.now() - Number(file.mtimeMs) >= 2_000)
|
|
958
|
+
stamp = `${file.ino}:${file.size}:${file.mtimeNs}:${file.ctimeNs}`;
|
|
959
|
+
}
|
|
960
|
+
catch {
|
|
961
|
+
// Missing or out of reach: read it the way loadConfig() always has.
|
|
962
|
+
}
|
|
963
|
+
if (stamp !== null && cached?.stamp === stamp)
|
|
964
|
+
return cached.value;
|
|
965
|
+
const value = derive(loadConfig());
|
|
966
|
+
// A warning means loadConfig() ignored the file and ran on defaults.
|
|
967
|
+
cached = stamp === null || lastIgnoredConfigWarning !== "" ? null : { stamp, value };
|
|
968
|
+
return value;
|
|
969
|
+
};
|
|
970
|
+
}
|
|
989
971
|
/** After saveConfig() writes a credential, the running process's env must
|
|
990
972
|
* follow the newest value — loadConfig() prefers env, so the secret injected
|
|
991
973
|
* at boot would otherwise shadow the save until relaunch: the UI would show
|
|
@@ -1123,6 +1105,75 @@ export const PROVIDER_CREDENTIAL_ENV = [
|
|
|
1123
1105
|
"CURSOR_API_KEY",
|
|
1124
1106
|
"CURSOR_AUTH_TOKEN",
|
|
1125
1107
|
];
|
|
1108
|
+
/** Provider keys OpenCode reads from its environment, as it does in a
|
|
1109
|
+
* terminal: with ANTHROPIC_API_KEY set, `opencode` lists Anthropic's models.
|
|
1110
|
+
* Keys OpenMaus saves for another engine (xAI, Mistral, the workspace
|
|
1111
|
+
* Anthropic key) are workspace credentials under other names and never
|
|
1112
|
+
* ride along. */
|
|
1113
|
+
export const OPENCODE_PROVIDER_ENV = [
|
|
1114
|
+
"ANTHROPIC_API_KEY",
|
|
1115
|
+
"OPENAI_API_KEY",
|
|
1116
|
+
"GEMINI_API_KEY",
|
|
1117
|
+
"GOOGLE_API_KEY",
|
|
1118
|
+
"KIMI_API_KEY",
|
|
1119
|
+
"MOONSHOT_API_KEY",
|
|
1120
|
+
"MINIMAX_API_KEY",
|
|
1121
|
+
];
|
|
1122
|
+
/** Keys the owner saves in Settings for OpenCode's other providers (Venice,
|
|
1123
|
+
* Groq, DeepSeek…), each under the environment name OpenCode reads. A Cloud
|
|
1124
|
+
* has no terminal to export them in. They go to OpenCode's process only,
|
|
1125
|
+
* never to this server's own environment. */
|
|
1126
|
+
export const OPENCODE_PROVIDER_KEY_LIMIT = 20;
|
|
1127
|
+
const OPENCODE_PROVIDER_KEY_NAME = /^[A-Z][A-Z0-9_]{0,59}_(?:API_KEY|KEY|TOKEN)$/;
|
|
1128
|
+
/** Printable, no spaces: no provider key has any, and a pasted sentence is not a key. */
|
|
1129
|
+
const OPENCODE_PROVIDER_KEY_VALUE = /^[\x21-\x7e]{1,4096}$/;
|
|
1130
|
+
/** Why OpenCode can't be given a key under this name, or undefined. A name
|
|
1131
|
+
* OpenMaus keeps for itself or another engine would never reach OpenCode:
|
|
1132
|
+
* every engine keeps only the credential names it reads (credentialEnv). */
|
|
1133
|
+
function openCodeProviderKeyNameRefusal(name) {
|
|
1134
|
+
if (!OPENCODE_PROVIDER_KEY_NAME.test(name)) {
|
|
1135
|
+
return "Use the name the provider reads its key from: capitals, ending in _API_KEY, _KEY or _TOKEN, such as VENICE_API_KEY.";
|
|
1136
|
+
}
|
|
1137
|
+
if (name === "OPENCODE_API_KEY")
|
|
1138
|
+
return "Save the OpenCode key in the OpenCode API key box instead.";
|
|
1139
|
+
const reserved = name.startsWith("OPENCODE_") || name.startsWith("OMB_")
|
|
1140
|
+
|| WORKSPACE_CREDENTIAL_ENV.includes(name)
|
|
1141
|
+
|| (PROVIDER_CREDENTIAL_ENV.includes(name) && !OPENCODE_PROVIDER_ENV.includes(name));
|
|
1142
|
+
return reserved ? `OpenMaus keeps ${name} for itself, so OpenCode can't be given it here. Put this key in opencode.json instead.` : undefined;
|
|
1143
|
+
}
|
|
1144
|
+
/** The saved OpenCode provider keys that can be used, by name. A hand-edited
|
|
1145
|
+
* entry OpenCode could not be given, or whose value is not a key, is
|
|
1146
|
+
* skipped; so is anything past the limit. */
|
|
1147
|
+
export function openCodeProviderKeys(cfg) {
|
|
1148
|
+
const saved = cfg.opencodeGo?.providerKeys ?? {};
|
|
1149
|
+
const usable = Object.keys(saved).sort()
|
|
1150
|
+
.filter((name) => !openCodeProviderKeyNameRefusal(name) && OPENCODE_PROVIDER_KEY_VALUE.test(saved[name] ?? ""))
|
|
1151
|
+
.slice(0, OPENCODE_PROVIDER_KEY_LIMIT);
|
|
1152
|
+
return Object.fromEntries(usable.map((name) => [name, saved[name]]));
|
|
1153
|
+
}
|
|
1154
|
+
/** A Settings change to the saved OpenCode provider keys: a key saves or
|
|
1155
|
+
* replaces its name, "" removes it, and every other saved key stays. Returns
|
|
1156
|
+
* the whole new set to store, or why it was refused. */
|
|
1157
|
+
export function mergeOpenCodeProviderKeys(cfg, change) {
|
|
1158
|
+
const keys = new Map(Object.entries(openCodeProviderKeys(cfg)));
|
|
1159
|
+
for (const [name, raw] of Object.entries(change)) {
|
|
1160
|
+
const value = raw.trim();
|
|
1161
|
+
if (!value) {
|
|
1162
|
+
keys.delete(name);
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
const refusal = openCodeProviderKeyNameRefusal(name);
|
|
1166
|
+
if (refusal)
|
|
1167
|
+
return { ok: false, error: refusal };
|
|
1168
|
+
if (!OPENCODE_PROVIDER_KEY_VALUE.test(value))
|
|
1169
|
+
return { ok: false, error: "That doesn't look like a key. Paste only the key, with no spaces." };
|
|
1170
|
+
keys.set(name, value);
|
|
1171
|
+
}
|
|
1172
|
+
if (keys.size > OPENCODE_PROVIDER_KEY_LIMIT) {
|
|
1173
|
+
return { ok: false, error: `OpenCode can hold up to ${OPENCODE_PROVIDER_KEY_LIMIT} provider keys. Remove one you no longer use, then add this one.` };
|
|
1174
|
+
}
|
|
1175
|
+
return { ok: true, keys: Object.fromEntries([...keys].sort(([a], [b]) => (a < b ? -1 : 1))) };
|
|
1176
|
+
}
|
|
1126
1177
|
const configSaveListeners = new Set();
|
|
1127
1178
|
/** Told, synchronously, what each saveConfig wrote: the file before and
|
|
1128
1179
|
* after. The admin activity log (server/admin-activity.ts) records the
|
|
@@ -1152,7 +1203,7 @@ export function saveConfig(patch, options = {}) {
|
|
|
1152
1203
|
const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
|
|
1153
1204
|
if (storedProfiles.success)
|
|
1154
1205
|
disk.browserProfiles = storedProfiles.data;
|
|
1155
|
-
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", "
|
|
1206
|
+
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", "budgets", "billing", "decisions", "onboarding", "browserEngine", "newBots"]) {
|
|
1156
1207
|
const section = checkedPatch[key];
|
|
1157
1208
|
if (!section)
|
|
1158
1209
|
continue;
|
|
@@ -1439,8 +1490,13 @@ function injectedEnvironment(cfg, instanceId, driver) {
|
|
|
1439
1490
|
// to Cloud Pro's included token, which never enters an environment map.
|
|
1440
1491
|
if (driver === "boxAgent" && cfg.box?.token)
|
|
1441
1492
|
environment.set("BOX_TOKEN", cfg.box.token);
|
|
1442
|
-
if (driver === "opencodeGo"
|
|
1443
|
-
|
|
1493
|
+
if (driver === "opencodeGo") {
|
|
1494
|
+
// Keys for OpenCode's other providers, under the names OpenCode reads.
|
|
1495
|
+
for (const [name, key] of Object.entries(openCodeProviderKeys(cfg)))
|
|
1496
|
+
environment.set(name, key);
|
|
1497
|
+
if (cfg.opencodeGo?.apiKey)
|
|
1498
|
+
environment.set("OPENCODE_API_KEY", cfg.opencodeGo.apiKey);
|
|
1499
|
+
}
|
|
1444
1500
|
return environment;
|
|
1445
1501
|
}
|
|
1446
1502
|
/** One instance per pasted provider key, each running only on its own key.
|
|
@@ -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,
|
|
@@ -374,11 +372,19 @@ function containerOwnershipLabelsMatch(labels, target) {
|
|
|
374
372
|
function normalizeImageId(id) {
|
|
375
373
|
return id?.trim().replace(/^sha256:/, "") || null;
|
|
376
374
|
}
|
|
377
|
-
function inspectedImage(stdout) {
|
|
375
|
+
function inspectedImage(stdout, runtime) {
|
|
378
376
|
const parsed = JSON.parse(stdout);
|
|
379
377
|
const image = parsed[0];
|
|
378
|
+
// Apple container runs on Apple Silicon and puts image labels inside each
|
|
379
|
+
// platform variant. Never accept another platform's labels or guess between
|
|
380
|
+
// multiple matching variants. Docker/Podman keep their existing inspect paths.
|
|
381
|
+
const variants = runtime === "container" && Array.isArray(image?.variants)
|
|
382
|
+
? image.variants.filter(variant => variant?.platform?.os === "linux" && variant.platform.architecture === "arm64")
|
|
383
|
+
: [];
|
|
380
384
|
return {
|
|
381
|
-
labels:
|
|
385
|
+
labels: runtime === "container"
|
|
386
|
+
? (variants.length === 1 ? variants[0]?.config?.config?.Labels : undefined)
|
|
387
|
+
: image?.Config?.Labels ?? image?.config?.Labels ?? image?.config?.labels ?? image?.configuration?.labels,
|
|
382
388
|
id: normalizeImageId(image?.Id ?? image?.id ?? image?.configuration?.descriptor?.digest),
|
|
383
389
|
};
|
|
384
390
|
}
|
|
@@ -432,7 +438,7 @@ export async function containerComputerStatus(runner = sh, platform = process.pl
|
|
|
432
438
|
}
|
|
433
439
|
try {
|
|
434
440
|
const { stdout } = await runner(status.runtime, ["image", "inspect", IMAGE]);
|
|
435
|
-
const image = inspectedImage(stdout);
|
|
441
|
+
const image = inspectedImage(stdout, status.runtime);
|
|
436
442
|
status.image = imageLabelsMatch(image.labels);
|
|
437
443
|
status.image_id = image.id;
|
|
438
444
|
}
|
|
@@ -604,14 +610,10 @@ function appleWorkspaceMountIsSafe(mounts, platform, expectedWorkspace) {
|
|
|
604
610
|
mounts[0]?.destination === VM_WORKSPACE_GUEST &&
|
|
605
611
|
!options.some((option) => option === "ro" || option === "readonly"));
|
|
606
612
|
}
|
|
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. */
|
|
613
|
+
/** Shared isolation contract for Local VM and BYO-VPS containers. Resource
|
|
614
|
+
* budgets are creation defaults, not an isolation requirement. Podman alone
|
|
615
|
+
* needs chroot for Firefox's sandbox. Local VM starts stay controlled by
|
|
616
|
+
* OMB's idle policy; VPS restart policy belongs to the server operator. */
|
|
615
617
|
export function dockerSecurityIsHardened(config, options = {}) {
|
|
616
618
|
if (!config)
|
|
617
619
|
return false;
|
|
@@ -621,26 +623,20 @@ export function dockerSecurityIsHardened(config, options = {}) {
|
|
|
621
623
|
.sort();
|
|
622
624
|
const unsafeSecurityOption = (config.SecurityOpt ?? []).some((option) => /(?:^|=)(?:unconfined|disable)$/i.test(option));
|
|
623
625
|
const restartPolicy = config.RestartPolicy?.Name;
|
|
624
|
-
const restartPolicyOk = options.restartPolicy === "unless-stopped"
|
|
626
|
+
const restartPolicyOk = options.restartPolicy === "any" || (options.restartPolicy === "unless-stopped"
|
|
625
627
|
? 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") &&
|
|
628
|
+
: restartPolicy === undefined || restartPolicy === "" || restartPolicy === "no");
|
|
629
|
+
return (capDrop.includes("all") &&
|
|
632
630
|
capAdd.join(",") === (options.podmanBrowserSandbox ? "setgid,setuid,sys_chroot" : "setgid,setuid") &&
|
|
633
631
|
config.Privileged === false &&
|
|
634
632
|
!config.PidMode &&
|
|
635
633
|
config.IpcMode === "private" &&
|
|
636
634
|
!config.UTSMode &&
|
|
637
|
-
config.ShmSize === SHM_BYTES &&
|
|
638
635
|
(!config.Devices || config.Devices.length === 0) &&
|
|
639
636
|
(!config.DeviceRequests || config.DeviceRequests.length === 0) &&
|
|
640
637
|
!unsafeSecurityOption &&
|
|
641
638
|
!config.UsernsMode &&
|
|
642
639
|
config.CgroupnsMode === "private" &&
|
|
643
|
-
config.OomKillDisable !== true &&
|
|
644
640
|
config.AutoRemove !== true &&
|
|
645
641
|
restartPolicyOk);
|
|
646
642
|
}
|
|
@@ -711,8 +707,11 @@ async function ensureVmWorkspace(platform, target) {
|
|
|
711
707
|
if (platform !== "win32")
|
|
712
708
|
await chmod(target.workspaceDir, 0o700);
|
|
713
709
|
}
|
|
710
|
+
function baseImagePullArgs(runtime) {
|
|
711
|
+
return runtime === "container" ? ["image", "pull", BASE_IMAGE] : ["pull", BASE_IMAGE];
|
|
712
|
+
}
|
|
714
713
|
async function prepareManagedImage(runtime, runner) {
|
|
715
|
-
await runner(runtime,
|
|
714
|
+
await runner(runtime, baseImagePullArgs(runtime), 10 * 60_000);
|
|
716
715
|
const context = await mkdtemp(join(tmpdir(), "openmausbot-cua-image-"));
|
|
717
716
|
try {
|
|
718
717
|
await writeFile(join(context, "Dockerfile"), managedImageDockerfile(), { mode: 0o600 });
|
|
@@ -950,7 +949,7 @@ export function setupCommands(runtime, platform = process.platform, target = SHA
|
|
|
950
949
|
runtimeStart,
|
|
951
950
|
// This is the inspectable base download. The normal Prepare button also
|
|
952
951
|
// builds the checksum-pinned 0.20.0 derivative automatically.
|
|
953
|
-
pull: command(
|
|
952
|
+
pull: command(baseImagePullArgs(runtime)),
|
|
954
953
|
run: runtime === "container" && target.key !== SHARED_LOCAL_VM_TARGET.key
|
|
955
954
|
? null
|
|
956
955
|
: command(containerRunArgs(runtime, "CHANGE_ME", target)),
|
|
@@ -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, " +
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
+
import { isLoopback } from "../provider-key-check.js";
|
|
1
2
|
export const JEV_DEFAULT_BASE_URL = "https://api.typesafe.ai";
|
|
2
3
|
export const JEV_MODEL = "jev-latest";
|
|
3
4
|
export const JEV_MAX_OPTIONS = 255;
|
|
4
5
|
export const JEV_MIN_LEVELS = 2;
|
|
5
6
|
export const JEV_MAX_LEVELS = 10;
|
|
6
|
-
function isLoopback(hostname) {
|
|
7
|
-
const host = hostname.replace(/^\[|\]$/g, "");
|
|
8
|
-
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host.startsWith("127.");
|
|
9
|
-
}
|
|
10
7
|
/** The endpoint for a base URL, or null when the key must not be sent there:
|
|
11
8
|
* https anywhere, plain http only to this machine (a local Jev-compatible
|
|
12
9
|
* server or a test double). */
|
|
@@ -14,6 +14,7 @@ import { createHash } from "node:crypto";
|
|
|
14
14
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { boundRetentionDays, pruneMonthFiles } from "../decision-log.js";
|
|
17
|
+
import { monthKey } from "../usage-ledger.js";
|
|
17
18
|
export const DECIDER_LOG_DIR = "decider-log";
|
|
18
19
|
const PRUNE_EVERY_MS = 60 * 60_000;
|
|
19
20
|
const writeQueues = new Map();
|
|
@@ -28,9 +29,6 @@ export function stateHash(state) {
|
|
|
28
29
|
}
|
|
29
30
|
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
30
31
|
}
|
|
31
|
-
function monthKey(at) {
|
|
32
|
-
return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
33
|
-
}
|
|
34
32
|
export function deciderLogFileFor(dataDir, at) {
|
|
35
33
|
return join(dataDir, DECIDER_LOG_DIR, `${monthKey(at)}.ndjson`);
|
|
36
34
|
}
|
|
@@ -32,7 +32,7 @@ import { readdirSync, readFileSync } from "node:fs";
|
|
|
32
32
|
import { appendFile, mkdir, readdir, stat, unlink } from "node:fs/promises";
|
|
33
33
|
import { basename, join } from "node:path";
|
|
34
34
|
import { redactSecrets } from "./redact.js";
|
|
35
|
-
import { csvCell } from "./usage-ledger.js";
|
|
35
|
+
import { csvCell, monthKey } from "./usage-ledger.js";
|
|
36
36
|
const DIR = "decisions";
|
|
37
37
|
const LEGACY_FILE = "decisions.ndjson";
|
|
38
38
|
const MONTH_FILE = /^(\d{4})-(\d{2})\.ndjson$/;
|
|
@@ -70,9 +70,6 @@ const actorScope = new AsyncLocalStorage();
|
|
|
70
70
|
export function withDecisionActor(actor, work, via) {
|
|
71
71
|
return actorScope.run(via ? { actor, via } : { actor }, work);
|
|
72
72
|
}
|
|
73
|
-
function monthKey(at) {
|
|
74
|
-
return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
75
|
-
}
|
|
76
73
|
export function decisionFileFor(dataDir, at) {
|
|
77
74
|
return join(dataDir, DIR, `${monthKey(at)}.ndjson`);
|
|
78
75
|
}
|
|
@@ -15,6 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
import { writeFileAtomic } from "./atomic.js";
|
|
16
16
|
import { getOrCreateChannel, mirrorExchange } from "./comms-visibility.js";
|
|
17
17
|
import { DATA_DIR } from "./config.js";
|
|
18
|
+
import { failedTurnTool } from "../shared/failed-turn.js";
|
|
18
19
|
import { newId } from "./contracts.js";
|
|
19
20
|
import { peerApprovalFailure, requestPeerApproval } from "./peer-approval.js";
|
|
20
21
|
import { canAccessTeam, peerAllowed } from "./peer-roster.js";
|
|
@@ -383,7 +384,7 @@ onSettled) {
|
|
|
383
384
|
appendDeliveryMessage(bus, threadId, item, {
|
|
384
385
|
role: "bot",
|
|
385
386
|
kind: "activity",
|
|
386
|
-
tool:
|
|
387
|
+
tool: failedTurnTool(`delegation failed — ${why}`),
|
|
387
388
|
});
|
|
388
389
|
}
|
|
389
390
|
catch (reportError) {
|
|
@@ -607,7 +608,7 @@ async function processOne(bus, approvalBus, from, sourceThreadId, item, runTarge
|
|
|
607
608
|
appendDeliveryMessage(bus, sourceThreadId, item, {
|
|
608
609
|
role: "bot",
|
|
609
610
|
kind: "activity",
|
|
610
|
-
tool:
|
|
611
|
+
tool: failedTurnTool(`delegation to ${item.toBotId} failed — no such bot`),
|
|
611
612
|
});
|
|
612
613
|
return "settled";
|
|
613
614
|
}
|
|
@@ -91,10 +91,12 @@ function acpRpcError(value, method) {
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
const envOr = (key, fallback) => Number(process.env[key] ?? fallback);
|
|
94
|
-
const INIT_TIMEOUT =
|
|
95
|
-
const SESSION_CONFIG_TIMEOUT =
|
|
96
|
-
const NEW_SESSION_TIMEOUT =
|
|
97
|
-
const LOAD_SESSION_TIMEOUT =
|
|
94
|
+
const INIT_TIMEOUT = 300_000;
|
|
95
|
+
const SESSION_CONFIG_TIMEOUT = 300_000; // configureSession's per-request default
|
|
96
|
+
const NEW_SESSION_TIMEOUT = 300_000;
|
|
97
|
+
const LOAD_SESSION_TIMEOUT = 120_000; // history replay on a long thread is slow
|
|
98
|
+
/** How long a quiet agent shows no sign of life before it is called stuck. */
|
|
99
|
+
const ACP_STUCK_AFTER_MS = 180_000;
|
|
98
100
|
/** ACP agents may compact their own history without telling the client;
|
|
99
101
|
* re-send the full prompt after this many bare turns as a backstop to
|
|
100
102
|
* compaction detection. */
|
|
@@ -614,7 +616,7 @@ export function createAcpDriver(support) {
|
|
|
614
616
|
// "no sign of life" is called only once that has lasted minutes.
|
|
615
617
|
if (next.kind === "no-signs") {
|
|
616
618
|
noSignsSince ??= now;
|
|
617
|
-
if (now - noSignsSince <
|
|
619
|
+
if (now - noSignsSince < ACP_STUCK_AFTER_MS)
|
|
618
620
|
next = { kind: "between-requests" };
|
|
619
621
|
}
|
|
620
622
|
else {
|
|
@@ -1262,9 +1264,10 @@ export function createAcpDriver(support) {
|
|
|
1262
1264
|
// session, so the model is not a separate axis; fullAuto covers the
|
|
1263
1265
|
// transformEnv-policy supports (opencode). mcpServers are session
|
|
1264
1266
|
// establishment inputs — they ride session/new and session/load over
|
|
1265
|
-
// the wire — and
|
|
1266
|
-
//
|
|
1267
|
-
//
|
|
1267
|
+
// the wire — and change when a turn's integrations or their grants do
|
|
1268
|
+
// (a thread keeps its integration credentials across turns). Most
|
|
1269
|
+
// agents apply changes on live load; those that cache the old MCP
|
|
1270
|
+
// clients must resume on a fresh process (see sessionKey).
|
|
1268
1271
|
// The env the spawned child actually receives is part of the
|
|
1269
1272
|
// contract too, and arrives hashed as envFingerprint for the same
|
|
1270
1273
|
// reason.
|