openmausbot 0.1.96 → 0.1.98
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/ActivityPanel-H6D3SD39.js +1 -0
- package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +7 -0
- package/dist/assets/CloudBackendPicker-iezpJFpF.js +1 -0
- package/dist/assets/ComputerPanel-DZPj-IWu.js +1 -0
- package/dist/assets/DesktopViewer-utONms1p.js +5 -0
- package/dist/assets/InspectorPanel-OVaBVrzC.js +5 -0
- package/dist/assets/KeyboardShortcutsModal-gk9mvHj_.js +1 -0
- package/dist/assets/LocalVmWorkspace-DLIvd2kC.js +1 -0
- package/dist/assets/MacLocalControl-bJv8yiPB.js +1 -0
- package/dist/assets/NewBotDialog-1qGOAdUM.js +2 -0
- package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +1 -0
- package/dist/assets/RoutinesPage-BM2D4qGi.js +1 -0
- package/dist/assets/RoutinesSection-Dny9ra-c.js +1 -0
- package/dist/assets/SettingsModal-gC6XJJmt.js +11 -0
- package/dist/assets/TeamMapPage-B6fuu4r_.js +8 -0
- package/dist/assets/TriggersPanel-j0qTP2CO.js +1 -0
- package/dist/assets/VisibilitySection-DT0FqPvu.js +12 -0
- package/dist/assets/WebhooksPanel--YQyCjKO.js +3 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-Dkbd2hcj.js → abnfDiagram-O67JEVCF-BsjcFX0-.js} +1 -1
- package/dist/assets/activity-BxOkayvD.js +1 -0
- package/dist/assets/{arc-Duj5Coy2.js → arc-CJNA94c6.js} +1 -1
- package/dist/assets/architectureDiagram-NJMV4G6O-dz2ud7lP.js +36 -0
- package/dist/assets/{blockDiagram-BEXU5L5S-CWDlTpXe.js → blockDiagram-BEXU5L5S-ChBEALoR.js} +1 -1
- package/dist/assets/brain-CFZ7PEXc.js +1 -0
- package/dist/assets/{c4Diagram-YGBWAQC7-B0t15PDc.js → c4Diagram-YGBWAQC7-KZNRKDQe.js} +1 -1
- package/dist/assets/channel-WhFyujhZ.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BJSCL-HV.js → chunk-2Q5K7J3B-BLLwmCET.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-D3_ILccd.js → chunk-3FUC2YCW-CQxyreBZ.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-D3dmwT_S.js → chunk-5VM5RSS4-D6sFMW0u.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Br73HFPD.js → chunk-GTNCS2PH-BaZ1YgLF.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-uSsjxubC.js → chunk-GWA4HPMP-CcalphmC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-C40VWFyr.js → chunk-JWPE2WC7-CSk8R3ve.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-D6WNNYwU.js → chunk-NETBCI7D-CTmLjA57.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-DOEhq64P.js → chunk-WEXAMYUT-CM9Z77Ne.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-BqJEi6Pi.js → chunk-XXDRQBXY-B1fxpMS2.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-ByU_GnxT.js → classDiagram-v2-NBCMYWYE-DCCyoLpj.js} +1 -1
- package/dist/assets/computer-control-NLov352p.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-KyImBKvE.js +1 -0
- package/dist/assets/{cynefin-OW5HDTMX-CZWO7aYj.js → cynefin-OW5HDTMX-fsZc6MGa.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-C70-w0la.js → cynefinDiagram-VND7K2PF-CGmHsShB.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-CnhjhExB.js → dagre-6A5THRUB-Dah1h-CH.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-bnjWCNb4.js → diagram-22UHCM2B-BIu71OyV.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-A4uTWoFV.js → diagram-3UASUU5V-gxhUB_Mu.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Zi6m3NUl.js → diagram-ATOU4E4O-CBhusUBn.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DtCcmd5k.js → diagram-CDSNMT55-BdPrc86x.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-DofuETS3.js → diagram-MLGK6HIB-Clp110R_.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-Dw2T9PuU.js → diagram-MPIPVDR6-DJKfbugw.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DbrwPiem.js → ebnfDiagram-ZINNZB2B-DnsltoBu.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BvCt1dCb.js → elk-276RUBZZ-jWdq6gqH.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-Y-QKqPnU.js → erDiagram-OPXOYQCR-DArPVf8u.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CDkL1B5w.js → flowDiagram-KWPJA3E3-D2HZ-2f7.js} +1 -1
- package/dist/assets/ganttDiagram-FUAMR5RP--o0KIxEo.js +292 -0
- package/dist/assets/{gitGraphDiagram-X574FWY7-nL9iFTCv.js → gitGraphDiagram-X574FWY7-CsoXr2D7.js} +1 -1
- package/dist/assets/image-plus-BbVjGHin.js +1 -0
- package/dist/assets/index-BrPW5HDO.css +1 -0
- package/dist/assets/index-C3NqfdVU.js +585 -0
- package/dist/assets/{index-DreDnjOu.js → index-CJTp9JEp.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-C7aHjzsf.js → infoDiagram-VRGFBTTK-g-6lNp7r.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-KUZsNBTh.js → ishikawaDiagram-OU5B5YK6-B1d6qVTV.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CGUuodFl.js → journeyDiagram-ZHPQQLJL-BTPuiU6T.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-Cy6r868Z.js → kanban-definition-PNTS6WVX-bDDuRJvP.js} +1 -1
- package/dist/assets/{linear-Bl669148.js → linear-BVVKbEMy.js} +1 -1
- package/dist/assets/{mermaid.core-86Xqz5Mr.js → mermaid.core-JJdoKyKB.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-G4DzHLrd.js → mindmap-definition-NLK3R4M7-B4aE3T0q.js} +1 -1
- package/dist/assets/module-DCUsy2BQ.js +3 -0
- package/dist/assets/page-visible-BBo_Zm8g.js +1 -0
- package/dist/assets/{pegDiagram-GJSIUBJH-K4eruzY0.js → pegDiagram-GJSIUBJH-DFKQWpR7.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DYdR9PPq.js → pieDiagram-5QR66LMP-Iv7OKVJi.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-BCAh0nEy.js → quadrantDiagram-O4NWA36T-U2YnfwBk.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-p0dGRc4g.js → railroadDiagram-XR7U4H2S-CuPy6RqM.js} +1 -1
- package/dist/assets/remote-desktop-panel-BNEj7tUx.js +1 -0
- package/dist/assets/{requirementDiagram-PLB6GJNP-tLeBG0aL.js → requirementDiagram-PLB6GJNP-bMI5AK7T.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-QRuN16rV.js → sankeyDiagram-IPEJSGJF-BKcFE2WQ.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-ueT1nVoa.js → sequenceDiagram-PO4LG4MO-Bn9q88kl.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-wRS2zWg2.js → sizeCapture-INFHLROL-Cvmdq9HD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-CYzA_hE1.js → stateDiagram-v2-GCMORJYK-JJQYw3eQ.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-kDnv6LMc.js → swimlanes-2SLR337P-BkSCBEwp.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +8 -0
- package/dist/assets/table-file.worker-KkMx-SNx.js +10 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-D1y2UbbH.js → timeline-definition-EJHVYXUP-DsaM4GHr.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-IqB4v9HX.js → usecaseDiagram-POWQR4AR-3FOwLq7B.js} +1 -1
- package/dist/assets/user-DcZBnAuv.js +1 -0
- package/dist/assets/{vennDiagram-UO4OBE2U-BEi7KYVF.js → vennDiagram-UO4OBE2U-DyNjJsKt.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-Dkybo-Yf.js → wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BAPFJLpg.js → xychartDiagram-PMCCYNJV-BXZpg955.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +20 -9
- package/dist-server/container-mcp.js +8 -0
- package/dist-server/desktop-entry.mjs +54 -0
- package/dist-server/drivers/agents-proxy.js +26 -9
- package/dist-server/index.js +3734 -2360
- package/dist-server/openmausbot.js +1664 -1553
- package/dist-server/pair-cli.js +1664 -1553
- package/dist-server/permission-proxy.js +2 -4
- package/dist-server/server/atomic.js +70 -2
- package/dist-server/server/auto-approve.js +1 -1
- package/dist-server/server/auto-vm-claims.js +13 -1
- package/dist-server/server/automatic-recovery.js +1 -2
- package/dist-server/server/boat.js +68 -84
- package/dist-server/server/bot-overview.js +1 -1
- package/dist-server/server/browser-engine.js +38 -1
- package/dist-server/server/browser-live.js +112 -25
- package/dist-server/server/browser-runtime.js +31 -1
- package/dist-server/server/checkpoints.js +275 -44
- package/dist-server/server/cli-setup.js +3 -3
- package/dist-server/server/cloud-computer-tools.js +11 -4
- package/dist-server/server/cloud-home-start.js +4 -4
- package/dist-server/server/cloud-home.js +3 -9
- package/dist-server/server/cloud-owner.js +1 -1
- package/dist-server/server/computer-engine-removal.js +70 -0
- package/dist-server/server/computer-selection.js +18 -0
- package/dist-server/server/config.js +29 -12
- package/dist-server/server/connector-card-text.js +15 -0
- package/dist-server/server/connector-scope-hint.js +83 -0
- package/dist-server/server/default-model-selection.js +23 -14
- package/dist-server/server/delegations.js +11 -5
- package/dist-server/server/delta-context.js +6 -1
- package/dist-server/server/drivers/acp/core.js +11 -1
- package/dist-server/server/drivers/acp/grok.js +47 -5
- package/dist-server/server/drivers/acp/opencode-go.js +16 -21
- package/dist-server/server/drivers/agents-call.js +1 -1
- package/dist-server/server/drivers/agents-catalog.js +27 -7
- package/dist-server/server/drivers/agents-proxy.js +1 -0
- package/dist-server/server/drivers/builtIn.js +0 -2
- package/dist-server/server/drivers/chat-mcp-tools.js +5 -3
- package/dist-server/server/drivers/claude-login-auth.js +1 -1
- package/dist-server/server/drivers/claude.js +84 -41
- package/dist-server/server/drivers/codex-device-auth.js +27 -319
- package/dist-server/server/drivers/codex.js +2 -2
- package/dist-server/server/drivers/device-auth.js +342 -0
- package/dist-server/server/drivers/openai-chat.js +110 -10
- package/dist-server/server/drivers/pi.js +184 -49
- package/dist-server/server/fleet-agent.js +18 -5
- package/dist-server/server/harness/http.js +5 -2
- package/dist-server/server/harness/registry.js +19 -0
- package/dist-server/server/harness-capabilities.js +7 -4
- package/dist-server/server/hosted-models.js +3 -1
- package/dist-server/server/index.js +823 -333
- package/dist-server/server/key-rejections.js +57 -0
- package/dist-server/server/live-call.js +11 -6
- package/dist-server/server/managed-policy.js +1 -1
- package/dist-server/server/message-file.js +7 -4
- package/dist-server/server/model-catalog/presets.js +8 -0
- package/dist-server/server/peer-roster.js +10 -2
- package/dist-server/server/permission-proxy.js +4 -0
- package/dist-server/server/provider-key-check.js +5 -1
- package/dist-server/server/request-auth.js +1 -1
- package/dist-server/server/room-turn-end.js +15 -0
- package/dist-server/server/room-turn-timeout.js +28 -0
- package/dist-server/server/routes/live.js +3 -3
- package/dist-server/server/routes/thread-models.js +36 -0
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +3 -3
- package/dist-server/server/screen-frame-gate.js +1 -3
- package/dist-server/server/skills.js +3 -2
- package/dist-server/server/store.js +273 -61
- package/dist-server/server/surface.js +29 -49
- package/dist-server/server/system-prompt.js +10 -10
- package/dist-server/server/team-backup.js +19 -4
- package/dist-server/server/thread-model.js +25 -0
- package/dist-server/server/turn-continuation.js +168 -0
- package/dist-server/server/webhook-ingress.js +8 -3
- package/dist-server/server/webhooks.js +40 -5
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/ask-question.js +0 -70
- package/dist-server/shared/cloud-computer.js +4 -6
- package/dist-server/shared/credential-request.js +1 -1
- package/dist-server/shared/device-sign-in.js +57 -0
- package/dist-server/shared/failed-turn.js +1 -4
- package/dist-server/shared/place-view.js +409 -0
- package/dist-server/shared/team-backup.js +2 -0
- package/dist-server/shared/thread-model.js +13 -0
- package/dist-server/shared/tool-scope-support.js +1 -2
- package/dist-server/vps-container-mcp.js +8 -0
- package/dist-server/workspace-backup.worker.js +41 -22
- package/package.json +1 -1
- package/dist/assets/DesktopViewer-DsnA23KM.js +0 -5
- package/dist/assets/architectureDiagram-NJMV4G6O-uYUsDawP.js +0 -36
- package/dist/assets/channel-BZxwC-Gw.js +0 -1
- package/dist/assets/cose-bilkent-JH36ORCC-D-Uglxqq.js +0 -1
- package/dist/assets/ganttDiagram-FUAMR5RP-CKV5D2F6.js +0 -292
- package/dist/assets/index-Du7o6Dsf.js +0 -610
- package/dist/assets/index-KxG4bWSr.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +0 -8
- package/dist-server/server/drivers/boatagent.js +0 -443
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// A saved key counts as working until the provider says otherwise. When a
|
|
2
|
+
// real use of it is refused as a bad key (a chat request, or Test in
|
|
3
|
+
// Settings → API keys), every engine on that key and endpoint reports it as
|
|
4
|
+
// not signed in, until the key is saved or cleared again or a later use of
|
|
5
|
+
// it succeeds. In memory only: after a restart the key shows as working
|
|
6
|
+
// until its next use fails. The key itself is never kept, only a hash of it
|
|
7
|
+
// beside the provider's base URL.
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
export const KEY_REJECTED_REASON = "The provider rejected this key. Change it in Settings → API keys.";
|
|
10
|
+
const rejected = new Set();
|
|
11
|
+
const listeners = new Set();
|
|
12
|
+
const fingerprint = (key) => createHash("sha256").update(key.trim()).digest("hex").slice(0, 32);
|
|
13
|
+
const markOf = (url, key) => `${url.trim().replace(/\/+$/, "")} ${fingerprint(key)}`;
|
|
14
|
+
const changed = () => { for (const listener of listeners)
|
|
15
|
+
listener(); };
|
|
16
|
+
export function keyRejected(url, key) {
|
|
17
|
+
return rejected.size > 0 && rejected.has(markOf(url, key));
|
|
18
|
+
}
|
|
19
|
+
export function noteKeyRejected(url, key) {
|
|
20
|
+
const mark = markOf(url, key);
|
|
21
|
+
if (rejected.has(mark))
|
|
22
|
+
return;
|
|
23
|
+
rejected.add(mark);
|
|
24
|
+
changed();
|
|
25
|
+
}
|
|
26
|
+
export function noteKeyAccepted(url, key) {
|
|
27
|
+
if (rejected.size > 0 && rejected.delete(markOf(url, key)))
|
|
28
|
+
changed();
|
|
29
|
+
}
|
|
30
|
+
/** Drops every mark on this key, whatever endpoint it was used against. */
|
|
31
|
+
export function forgetKey(key) {
|
|
32
|
+
if (rejected.size === 0)
|
|
33
|
+
return;
|
|
34
|
+
const suffix = ` ${fingerprint(key)}`;
|
|
35
|
+
const before = rejected.size;
|
|
36
|
+
for (const mark of rejected)
|
|
37
|
+
if (mark.endsWith(suffix))
|
|
38
|
+
rejected.delete(mark);
|
|
39
|
+
if (rejected.size !== before)
|
|
40
|
+
changed();
|
|
41
|
+
}
|
|
42
|
+
export function onKeyRejectionChange(listener) {
|
|
43
|
+
listeners.add(listener);
|
|
44
|
+
return () => listeners.delete(listener);
|
|
45
|
+
}
|
|
46
|
+
/** The provider refused the key itself: HTTP 401, or a 400/403 whose body
|
|
47
|
+
* says the key is invalid or revoked (xAI answers a wrong key with a 400).
|
|
48
|
+
* Rate limits, quota, billing and model access are not the key's fault. */
|
|
49
|
+
export function rejectsKey(status, body) {
|
|
50
|
+
if (/\b(?:quota|billing|credits?|rate.?limit|insufficient.?(?:balance|funds))\b/i.test(body))
|
|
51
|
+
return false;
|
|
52
|
+
if (status === 401)
|
|
53
|
+
return true;
|
|
54
|
+
if (status !== 400 && status !== 403)
|
|
55
|
+
return false;
|
|
56
|
+
return /\binvalid_api_key\b|\b(?:invalid|incorrect|revoked|expired|disabled|deactivated)[ _-]?(?:api[ _-]?)?key\b|\bapi[ _-]?key (?:is |was |has been )?(?:invalid|incorrect|revoked|expired|disabled|not valid)\b/i.test(body);
|
|
57
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// This file owns the one server-side step: exchanging the renderer's WebRTC
|
|
12
12
|
// offer for an answer. The OpenAI key never leaves the harness; the renderer
|
|
13
13
|
// only ever holds the SDP answer and the media connection it describes.
|
|
14
|
+
import { CLOUD_HOME_PLACE } from "./system-prompt.js";
|
|
14
15
|
export const LIVE_MODEL = "gpt-live-1";
|
|
15
16
|
const OPENAI_BASE = "https://api.openai.com";
|
|
16
17
|
export const DEFAULT_LIVE_VOICE = "marin";
|
|
@@ -57,14 +58,18 @@ export class LiveSessionError extends Error {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
/** Frontend instructions for the voice. Business rules stay with the bot;
|
|
60
|
-
* this only says who is speaking and when to hand work over.
|
|
61
|
-
* follows the GPT-Live prompting guide's template.
|
|
62
|
-
|
|
61
|
+
* this only says who is speaking, where it runs and when to hand work over.
|
|
62
|
+
* Structure follows the GPT-Live prompting guide's template. `cloudHome`:
|
|
63
|
+
* the harness is the person's My Cloud, named as the bot's own system prompt
|
|
64
|
+
* names it (CLOUD_HOME_PLACE). */
|
|
65
|
+
export function liveInstructions(bot, { cloudHome = false } = {}) {
|
|
63
66
|
const name = oneLine(bot.name) || "the agent";
|
|
64
67
|
const title = oneLine(bot.title ?? "");
|
|
65
68
|
const description = oneLine(bot.description ?? "").slice(0, 400);
|
|
69
|
+
const where = cloudHome ? `runs on ${CLOUD_HOME_PLACE}` : "runs in OpenMausBot on the user's own computer";
|
|
70
|
+
const changes = cloudHome ? "My Cloud" : "the computer";
|
|
66
71
|
return [
|
|
67
|
-
`You are ${name}${title ? `, ${title}` : ""}, an AI agent that
|
|
72
|
+
`You are ${name}${title ? `, ${title}` : ""}, an AI agent that ${where}.${description ? ` ${description}` : ""}`,
|
|
68
73
|
`To the user you are one assistant, ${name}, and you speak in the first person. Your work — looking things up, using your tools, files and memory, researching, deciding, and answering anything that needs facts this conversation does not hold — happens when you delegate. Delegating is how you think and act; it is not someone else.`,
|
|
69
74
|
"Never mention a backend, delegation, a voice layer, or another system or model doing the work, and never say you are only a voice. Say \"I\" about the work.",
|
|
70
75
|
"Never ask the user whether you may look something up or check something. When it needs checking, delegate at once and say briefly that you are checking.",
|
|
@@ -74,7 +79,7 @@ export function liveInstructions(bot) {
|
|
|
74
79
|
"Interruption policy: Stop speaking when the user interrupts. Listen to what they say.",
|
|
75
80
|
"Delegation policy:",
|
|
76
81
|
"Backend tools:",
|
|
77
|
-
`- ${name}: your own files, tools, memory, settings and this conversation's history. It researches, writes, changes things on
|
|
82
|
+
`- ${name}: your own files, tools, memory, settings and this conversation's history. It researches, writes, changes things on ${changes}, and answers questions, including questions about yourself such as which AI model you run on, your settings, your tools and your memory.`,
|
|
78
83
|
"Delegate to the backend when:",
|
|
79
84
|
"- The user asks a question, asks for work, or gives an instruction.",
|
|
80
85
|
"- The user asks something about you that this conversation does not already answer, for example which AI model you run on.",
|
|
@@ -131,7 +136,7 @@ export async function createLiveSession(input) {
|
|
|
131
136
|
const body = {
|
|
132
137
|
session: {
|
|
133
138
|
model: LIVE_MODEL,
|
|
134
|
-
instructions: liveInstructions(input.bot),
|
|
139
|
+
instructions: liveInstructions(input.bot, { cloudHome: input.cloudHome }),
|
|
135
140
|
// client delegation: every request comes back to the harness, which
|
|
136
141
|
// runs it as a normal turn on the bot
|
|
137
142
|
delegation: { type: "client" },
|
|
@@ -16,7 +16,7 @@ const policySchema = z.object({
|
|
|
16
16
|
computers: z.object({ thisComputer: z.boolean(), localVm: z.boolean(), box: z.boolean(), vps: z.boolean() }).strict(),
|
|
17
17
|
remoteAccess: z.boolean(),
|
|
18
18
|
}).strict();
|
|
19
|
-
const computerLabels = { thisComputer: "this computer", localVm: "local virtual machines", box: "
|
|
19
|
+
const computerLabels = { thisComputer: "this computer", localVm: "local virtual machines", box: "cloud computers", vps: "VPS computers" };
|
|
20
20
|
export function parseManagedPolicy(raw, now = Date.now()) {
|
|
21
21
|
if (raw === null)
|
|
22
22
|
return null;
|
|
@@ -17,9 +17,12 @@ export function messageFileRoots(options) {
|
|
|
17
17
|
options.attachments,
|
|
18
18
|
])];
|
|
19
19
|
}
|
|
20
|
-
function statusError(status, message) {
|
|
21
|
-
return Object.assign(new Error(message), { status });
|
|
20
|
+
function statusError(status, message, code) {
|
|
21
|
+
return Object.assign(new Error(message), { status, ...(code ? { code } : {}) });
|
|
22
22
|
}
|
|
23
|
+
// The client offers "Show in folder" for exactly this refusal, so it carries a
|
|
24
|
+
// stable code instead of being recognised by its English text.
|
|
25
|
+
const outsideWorkspace = () => statusError(403, "the linked file is outside this conversation's workspace", "outside_workspace");
|
|
23
26
|
function decodePathWithSuffixRemoved(href) {
|
|
24
27
|
// Split before decoding so an encoded `?` or `#` remains part of the
|
|
25
28
|
// filename, while a real Markdown query/fragment never reaches the file
|
|
@@ -396,7 +399,7 @@ export async function openMessageFile(href, roots) {
|
|
|
396
399
|
}
|
|
397
400
|
const canonicalAfter = await realpath(candidate);
|
|
398
401
|
if (!containedBy(root, canonicalAfter))
|
|
399
|
-
throw
|
|
402
|
+
throw outsideWorkspace();
|
|
400
403
|
const after = await stat(canonicalAfter);
|
|
401
404
|
if (opened.dev !== after.dev || opened.ino !== after.ino) {
|
|
402
405
|
throw statusError(409, "the linked file changed while it was being opened");
|
|
@@ -416,7 +419,7 @@ export async function openMessageFile(href, roots) {
|
|
|
416
419
|
}
|
|
417
420
|
}
|
|
418
421
|
if (sawOutsideRoot)
|
|
419
|
-
throw
|
|
422
|
+
throw outsideWorkspace();
|
|
420
423
|
throw statusError(404, "the linked file is unavailable");
|
|
421
424
|
}
|
|
422
425
|
/** A safe attachment header with a readable ASCII fallback and UTF-8 name. */
|
|
@@ -139,6 +139,14 @@ export const PROVIDER_PRESETS = [
|
|
|
139
139
|
anthropic: { baseUrl: "https://opencode.ai/zen" },
|
|
140
140
|
checkedAt: CHECKED,
|
|
141
141
|
},
|
|
142
|
+
{
|
|
143
|
+
id: "wallaby",
|
|
144
|
+
catalogId: "wallaby",
|
|
145
|
+
label: "Wallaby",
|
|
146
|
+
api: "https://api.wallabytoken.com/v1",
|
|
147
|
+
keyCheck: "/models",
|
|
148
|
+
checkedAt: "2026-10-05",
|
|
149
|
+
},
|
|
142
150
|
];
|
|
143
151
|
const BY_ID = new Map(PROVIDER_PRESETS.map((preset) => [preset.id, preset]));
|
|
144
152
|
export function providerPreset(id) {
|
|
@@ -6,10 +6,18 @@
|
|
|
6
6
|
import { sameAudience } from "./bot-visibility.js";
|
|
7
7
|
const sectionKey = (section) => section?.trim() || "";
|
|
8
8
|
export const PEER_ACCESS_HELP = "Call list_bots for reachable teammates. If the intended Chief is missing, ask the user to check team membership and this bot's allowed peers, or message the Chief directly. A Chief's access to another team does not grant that team's bots access back to the Chief. Do not use computer control to bypass this.";
|
|
9
|
+
/** OMB_OPEN_TEAMS=1 runs the workspace as one team: any bot may reach a bot
|
|
10
|
+
* in any section, so sections only group the sidebar. Off by default. Peer
|
|
11
|
+
* lists, hidden bots and visibility still apply, and it grants no Chief
|
|
12
|
+
* authority (coordinatorSupervises). Read per call, so no caller caches it. */
|
|
13
|
+
const openTeams = () => process.env.OMB_OPEN_TEAMS === "1";
|
|
9
14
|
/** Coordination is scoped to the bot's own team unless the owner explicitly
|
|
10
|
-
* allows its Chief to work with additional teams
|
|
11
|
-
*
|
|
15
|
+
* allows its Chief to work with additional teams, or opens every team to
|
|
16
|
+
* every bot (openTeams). A title, peer id, imported persona or a room
|
|
17
|
+
* membership is not a grant. Invalid saved grants fail closed. */
|
|
12
18
|
export function canAccessTeam(from, section) {
|
|
19
|
+
if (openTeams())
|
|
20
|
+
return true;
|
|
13
21
|
const target = sectionKey(section);
|
|
14
22
|
return target === sectionKey(from.section) || Boolean(from.chiefOfStaff &&
|
|
15
23
|
Array.isArray(from.managedSections) && from.managedSections.some(value => typeof value === "string" && sectionKey(value) === target));
|
|
@@ -36,6 +36,9 @@ const allowQuestion = allowsTool(selection.scope, { kind: "mcp", server: "ogb",
|
|
|
36
36
|
const socketPath = process.argv[2] ?? "";
|
|
37
37
|
const waiting = new Map();
|
|
38
38
|
const conn = connect(socketPath);
|
|
39
|
+
// Both streams below carry JSON lines that can be larger than one read.
|
|
40
|
+
// Decoding as a stream keeps a character split across two reads whole.
|
|
41
|
+
conn.setEncoding("utf8");
|
|
39
42
|
const dead = () => {
|
|
40
43
|
for (const resolve of waiting.values()) {
|
|
41
44
|
resolve({ behavior: "deny", message: "OpenMausBot: permission broker unavailable — skip this action" });
|
|
@@ -227,6 +230,7 @@ async function handle(msg) {
|
|
|
227
230
|
}
|
|
228
231
|
}
|
|
229
232
|
let inBuf = "";
|
|
233
|
+
process.stdin.setEncoding("utf8");
|
|
230
234
|
process.stdin.on("data", (chunk) => {
|
|
231
235
|
inBuf += chunk;
|
|
232
236
|
let nl;
|
|
@@ -14,9 +14,13 @@ export function isLoopback(hostname) {
|
|
|
14
14
|
const host = hostname.replace(/^\[|\]$/g, "");
|
|
15
15
|
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host.startsWith("127.");
|
|
16
16
|
}
|
|
17
|
+
/** A provider's base URL: the configured one, or its default. */
|
|
18
|
+
export function providerBaseUrl(provider, base) {
|
|
19
|
+
return (base?.trim() || DEFAULT_URLS[provider]).replace(/\/+$/, "");
|
|
20
|
+
}
|
|
17
21
|
/** The models endpoint for a provider, from its base URL or the default. */
|
|
18
22
|
export function providerModelsUrl(provider, base) {
|
|
19
|
-
const root = (
|
|
23
|
+
const root = providerBaseUrl(provider, base);
|
|
20
24
|
if (provider === "anthropic")
|
|
21
25
|
return root.endsWith("/v1") ? `${root}/models` : `${root}/v1/models`;
|
|
22
26
|
return `${root}/models`;
|
|
@@ -340,7 +340,7 @@ export function clientBotPatchViolation(body) {
|
|
|
340
340
|
return null;
|
|
341
341
|
}
|
|
342
342
|
/** Same for a room: name and reading state, never its folder or who answers. */
|
|
343
|
-
const CLIENT_GROUP_PATCH_FIELDS = new Set(["name", "bulletin", "unread", "pinnedMessageId", "section"]);
|
|
343
|
+
const CLIENT_GROUP_PATCH_FIELDS = new Set(["name", "bulletin", "unread", "pinnedMessageId", "section", "turnTimeoutMinutes"]);
|
|
344
344
|
export function clientGroupPatchViolation(body) {
|
|
345
345
|
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
346
346
|
return "body";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** How a room member's provider turn ended, read from its turn.completed and
|
|
2
|
+
* from what ended it early: the member's cloud computer, claimed by its first
|
|
3
|
+
* computer call, could not start (a failure with its cause) or parked waiting
|
|
4
|
+
* for its seat. Kept free of index.ts so the rule stays unit-testable. */
|
|
5
|
+
export function roomTurnEnd(event, orchestrated, claim) {
|
|
6
|
+
if (claim?.parked)
|
|
7
|
+
return { outcome: "parked" };
|
|
8
|
+
// The cause, not the "interrupted" the ending interrupt settles as; and a
|
|
9
|
+
// chat round stops here, as it stops on a failure at setup.
|
|
10
|
+
if (claim)
|
|
11
|
+
return { outcome: "provider_failed", stopReason: claim.message };
|
|
12
|
+
if (orchestrated && !event.ok)
|
|
13
|
+
return { outcome: "provider_failed", stopReason: event.stopReason ?? null };
|
|
14
|
+
return { outcome: "settled" };
|
|
15
|
+
}
|
|
@@ -1,6 +1,34 @@
|
|
|
1
|
+
import { MAX_ROOM_TURN_TIMEOUT_MINUTES, MIN_ROOM_TURN_TIMEOUT_MINUTES, } from "./config.js";
|
|
1
2
|
export function roomTurnTimeoutMs(minutes) {
|
|
2
3
|
return minutes * 60_000;
|
|
3
4
|
}
|
|
5
|
+
/** A conversation may replace the global group ceiling. Null clears that
|
|
6
|
+
* choice. Anything else is refused rather than silently clamped. */
|
|
7
|
+
export function parseConversationTurnTimeout(value) {
|
|
8
|
+
if (value === null)
|
|
9
|
+
return { ok: true, minutes: null };
|
|
10
|
+
if (typeof value !== "number" ||
|
|
11
|
+
!Number.isInteger(value) ||
|
|
12
|
+
value < MIN_ROOM_TURN_TIMEOUT_MINUTES ||
|
|
13
|
+
value > MAX_ROOM_TURN_TIMEOUT_MINUTES) {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
error: "Turn limit must be a whole number from 1 to 1,440, or null to use the group default.",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return { ok: true, minutes: value };
|
|
20
|
+
}
|
|
21
|
+
/** The ceiling captured for one turn. An override wins; a missing or
|
|
22
|
+
* unusable one keeps the global group limit. */
|
|
23
|
+
export function effectiveRoomTurnTimeoutMinutes(globalMinutes, override) {
|
|
24
|
+
if (typeof override === "number" &&
|
|
25
|
+
Number.isInteger(override) &&
|
|
26
|
+
override >= MIN_ROOM_TURN_TIMEOUT_MINUTES &&
|
|
27
|
+
override <= MAX_ROOM_TURN_TIMEOUT_MINUTES) {
|
|
28
|
+
return override;
|
|
29
|
+
}
|
|
30
|
+
return globalMinutes;
|
|
31
|
+
}
|
|
4
32
|
/**
|
|
5
33
|
* One room turn's slice of the configured room ceiling.
|
|
6
34
|
*
|
|
@@ -8,11 +8,11 @@ const sessionBody = z.object({
|
|
|
8
8
|
threadId: id.optional(),
|
|
9
9
|
// Not trimmed: the SDP offer goes to OpenAI byte for byte.
|
|
10
10
|
sdp: z.string().min(1).refine((sdp) => Buffer.byteLength(sdp) <= MAX_SDP_BYTES),
|
|
11
|
-
client: z.enum(["desktop", "ios", "android"]),
|
|
11
|
+
client: z.enum(["desktop", "web", "ios", "android"]),
|
|
12
12
|
}).strict();
|
|
13
13
|
const endBody = z.object({ callId: z.string().min(1).max(120) }).strict();
|
|
14
14
|
// Strict, so `key` (or anything else) is refused rather than ignored: the key
|
|
15
|
-
// is
|
|
15
|
+
// is saved through the desktop's credential store or PUT /api/config, never here.
|
|
16
16
|
const settingsBody = z.object({
|
|
17
17
|
voice: z.string().trim().max(40).regex(/^[a-z]*$/).optional(),
|
|
18
18
|
readTypedReplies: z.boolean().optional(),
|
|
@@ -86,7 +86,7 @@ export function createLiveRoutes(deps) {
|
|
|
86
86
|
if (method === "PATCH" && path === "/api/live/settings") {
|
|
87
87
|
const parsed = settingsBody.safeParse(await bodyOf(req, readBody));
|
|
88
88
|
if (!parsed.success) {
|
|
89
|
-
return json(res, 400, { error: "Those Live settings are not valid.
|
|
89
|
+
return json(res, 400, { error: "Those Live settings are not valid." });
|
|
90
90
|
}
|
|
91
91
|
// as PUT /api/config: an empty patch is not saved (or broadcast)
|
|
92
92
|
if (!Object.keys(parsed.data).length)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { sameModelSelection } from "../../shared/thread-model.js";
|
|
2
|
+
import { PASS } from "./table.js";
|
|
3
|
+
const MAX_THREAD_IDS = 10_000;
|
|
4
|
+
export function createThreadModelRoutes(deps) {
|
|
5
|
+
return async ({ req, res, path, method, auth, json, readBody }) => {
|
|
6
|
+
const m = path.match(/^\/api\/bots\/([\w-]+)\/threads\/follow-model$/);
|
|
7
|
+
if (!m || method !== "POST")
|
|
8
|
+
return PASS;
|
|
9
|
+
const bot = deps.bot(m[1]);
|
|
10
|
+
if (!bot)
|
|
11
|
+
return json(res, 404, { error: "no such bot" });
|
|
12
|
+
if (!deps.mayChangeModel(auth))
|
|
13
|
+
return json(res, 403, { error: "On this Cloud only its owner can change a bot's model." });
|
|
14
|
+
const body = await readBody(req);
|
|
15
|
+
if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key) => key !== "threadIds")) {
|
|
16
|
+
return json(res, 400, { error: "send {} or { threadIds }" });
|
|
17
|
+
}
|
|
18
|
+
const raw = body.threadIds;
|
|
19
|
+
if (raw !== undefined && (!Array.isArray(raw) || raw.length > MAX_THREAD_IDS || raw.some((id) => typeof id !== "string"))) {
|
|
20
|
+
return json(res, 400, { error: "threadIds must be a list of this bot's thread ids" });
|
|
21
|
+
}
|
|
22
|
+
const tasks = bot.tasks ?? [];
|
|
23
|
+
const ids = new Set(tasks.map((task) => task.threadId));
|
|
24
|
+
const foreign = raw?.find((id) => !ids.has(id));
|
|
25
|
+
if (foreign !== undefined)
|
|
26
|
+
return json(res, 400, { error: `"${foreign}" is not one of this bot's threads` });
|
|
27
|
+
if (bot.approvalGrant)
|
|
28
|
+
return json(res, 409, { error: "wait for the approval-level change to finish before changing models" });
|
|
29
|
+
const listed = raw === undefined ? null : new Set(raw);
|
|
30
|
+
const own = tasks.filter((task) => (!listed || listed.has(task.threadId)) &&
|
|
31
|
+
task.modelSelection !== undefined && !sameModelSelection(task.modelSelection, bot.modelSelection));
|
|
32
|
+
const busy = own.filter((task) => deps.busy(bot.id, task.threadId));
|
|
33
|
+
const switched = deps.follow(bot.id, own.filter((task) => !busy.includes(task)).map((task) => task.threadId));
|
|
34
|
+
return json(res, 200, { switched: switched.length, busy: busy.length, bot: deps.reply(bot.id) });
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -674,7 +674,7 @@ function cardCopy(operation, manager, timeZone, now) {
|
|
|
674
674
|
const nextRunAt = nextForOperation(operation, manager, now);
|
|
675
675
|
const scheduleTimeZone = definition.schedule.type === "cron" ? definition.schedule.timeZone : timeZone;
|
|
676
676
|
const when = operation.action === "run_now" ? "Now" : scheduleText(definition.schedule, timeZone);
|
|
677
|
-
const destination = definition.runOn === "cloud" ? "Bot’s current model on its
|
|
677
|
+
const destination = definition.runOn === "cloud" ? "Bot’s current model on its cloud computer" : "Bot’s current model and configured computer";
|
|
678
678
|
const current = operation.action === "create"
|
|
679
679
|
? null
|
|
680
680
|
: manager.listRoutines().find((routine) => routine.id === operation.routineId) ?? null;
|
|
@@ -943,7 +943,7 @@ export class RoutineManager {
|
|
|
943
943
|
this.commitMutation(() => {
|
|
944
944
|
run = this.newRun(routine, this.now(), true, allocations, request?.threadId ?? routine.sourceThreadId);
|
|
945
945
|
// Preserve the invoking chat as provenance/fallback for this run.
|
|
946
|
-
//
|
|
946
|
+
// The routine's results destination continues to win.
|
|
947
947
|
if (request)
|
|
948
948
|
run.sourceThreadId = request.threadId;
|
|
949
949
|
if (request)
|
|
@@ -1297,7 +1297,7 @@ export class RoutineManager {
|
|
|
1297
1297
|
? run.groupId
|
|
1298
1298
|
? this.options.createGoalTask?.(run.groupId, title) ?? null
|
|
1299
1299
|
: null
|
|
1300
|
-
: this.options.createTask(run.botId, title, run.triggerSource === "webhook", run
|
|
1300
|
+
: this.options.createTask(run.botId, title, run.triggerSource === "webhook", run);
|
|
1301
1301
|
if (!task) {
|
|
1302
1302
|
this.failRun(run, run.target === "room-goal"
|
|
1303
1303
|
? "Could not create a room task for this goal"
|
|
@@ -1546,7 +1546,7 @@ export class RoutineManager {
|
|
|
1546
1546
|
if (value === null) {
|
|
1547
1547
|
const destination = this.options.resolveResultsThread?.(routine, true);
|
|
1548
1548
|
if (!destination)
|
|
1549
|
-
throw new Error("Could not
|
|
1549
|
+
throw new Error("Could not find a results thread for this routine");
|
|
1550
1550
|
routine.resultsThreadId = destination;
|
|
1551
1551
|
return () => this.options.discardResultsThread?.(routine.botId, destination);
|
|
1552
1552
|
}
|
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
// shell command over computer_exec or a status read does not count,
|
|
8
8
|
// however the driver happens to spell the tool's name;
|
|
9
9
|
// 2. is the end frame different from the one the transcript already
|
|
10
|
-
// shows —
|
|
11
|
-
// is what keeps its shell-only replies from re-picturing the same idle
|
|
12
|
-
// desktop.
|
|
10
|
+
// shows — this keeps a reply from re-picturing the same idle desktop.
|
|
13
11
|
import { createHash } from "node:crypto";
|
|
14
12
|
// Tool-name classification lives in shared/tool-surface.ts so the renderer
|
|
15
13
|
// reads a tool the same way the poller does.
|
|
@@ -29,7 +29,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
29
29
|
import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, symlinkSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
30
30
|
import { dirname, join, relative, resolve } from "node:path";
|
|
31
31
|
import { z } from "zod";
|
|
32
|
-
import { writeFileAtomic } from "./atomic.js";
|
|
32
|
+
import { writeFileAtomic, writeFileAtomicIfChanged } from "./atomic.js";
|
|
33
33
|
import { DATA_DIR } from "./config.js";
|
|
34
34
|
import { redactSecretsInText } from "./redact.js";
|
|
35
35
|
import { LEARN_SOURCE_PREFIX } from "./skill-learn.js";
|
|
@@ -191,9 +191,10 @@ function readManagedLinks(botId) {
|
|
|
191
191
|
return [];
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
+
// Rebuilt on every turn and almost always the same list.
|
|
194
195
|
function writeManagedLinks(botId, names) {
|
|
195
196
|
mkdirSync(skillStateDir(botId), { recursive: true, mode: 0o700 });
|
|
196
|
-
|
|
197
|
+
writeFileAtomicIfChanged(managedLinksPath(botId), `${JSON.stringify([...new Set(names)].sort(), null, 2)}\n`, { mode: 0o600 });
|
|
197
198
|
}
|
|
198
199
|
function manifestFromFile(path) {
|
|
199
200
|
try {
|