openmausbot 0.1.85 → 0.1.87
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/README.md +2 -2
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/abnfDiagram-O67JEVCF-BTiuL79F.js +1 -0
- package/dist/assets/arc-C88Blc2v.js +1 -0
- package/dist/assets/architectureDiagram-NJMV4G6O-BZsJIZrb.js +36 -0
- package/dist/assets/blockDiagram-BEXU5L5S-CVeMe9Ah.js +140 -0
- package/dist/assets/c4Diagram-YGBWAQC7-Cedk3Bhx.js +38 -0
- package/dist/assets/channel-taCNRlUU.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-D-VRqzTB.js +1 -0
- package/dist/assets/chunk-3FUC2YCW-BChov01Y.js +2 -0
- package/dist/assets/chunk-5VM5RSS4-C-TfwvHy.js +15 -0
- package/dist/assets/chunk-GTNCS2PH-Db7CGj5Y.js +88 -0
- package/dist/assets/chunk-GWA4HPMP-CNvIfqKT.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-C_bEp5tq.js +1 -0
- package/dist/assets/chunk-NETBCI7D-Coi4BOls.js +1 -0
- package/dist/assets/chunk-WEXAMYUT-CzylEuL3.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-m7p7ztaD.js +1 -0
- package/dist/assets/classDiagram-v2-NBCMYWYE-BMy6o9x2.js +217 -0
- package/dist/assets/cose-bilkent-JH36ORCC-BDQ__0_J.js +1 -0
- package/dist/assets/cynefin-OW5HDTMX-Djv4ghUb.js +166 -0
- package/dist/assets/cynefinDiagram-VND7K2PF-BULrlGYH.js +62 -0
- package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
- package/dist/assets/dagre-6A5THRUB-D-jrT_hU.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-22UHCM2B-p2UhQmfL.js +200 -0
- package/dist/assets/diagram-3UASUU5V-DHvGWZ7q.js +24 -0
- package/dist/assets/diagram-ATOU4E4O-ChhKDot-.js +3 -0
- package/dist/assets/diagram-CDSNMT55-B-T7A43R.js +30 -0
- package/dist/assets/diagram-MLGK6HIB-CJm8ENNY.js +24 -0
- package/dist/assets/diagram-MPIPVDR6-510tlza8.js +41 -0
- package/dist/assets/ebnfDiagram-ZINNZB2B-PkvRPHel.js +1 -0
- package/dist/assets/elk-276RUBZZ-BVBVPoX3.js +27 -0
- package/dist/assets/erDiagram-OPXOYQCR-BgYMVm7L.js +99 -0
- package/dist/assets/flowDiagram-KWPJA3E3-BGqsnc_3.js +213 -0
- package/dist/assets/ganttDiagram-FUAMR5RP-D_-UeReH.js +292 -0
- package/dist/assets/gitGraphDiagram-X574FWY7-D0uooONx.js +106 -0
- package/dist/assets/graph-D8UmR1vK.js +1 -0
- package/dist/assets/index-BwdMAQ6p.css +1 -0
- package/dist/assets/index-ByJennr7.js +581 -0
- package/dist/assets/{index-CTNNoqSJ.js → index-zRsJERGJ.js} +1 -1
- package/dist/assets/infoDiagram-VRGFBTTK-B9e1z77f.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-OU5B5YK6-BB4vNwvD.js +70 -0
- package/dist/assets/journeyDiagram-ZHPQQLJL-DpkosOTB.js +139 -0
- package/dist/assets/kanban-definition-PNTS6WVX-DbDP4kUu.js +89 -0
- package/dist/assets/linear-DBJGPmjT.js +1 -0
- package/dist/assets/mermaid.core-DWPRi-ue.js +311 -0
- package/dist/assets/mindmap-definition-NLK3R4M7-nszBbJmx.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-GJSIUBJH-CWykJWu_.js +1 -0
- package/dist/assets/pieDiagram-5QR66LMP-g3Da-Vnv.js +39 -0
- package/dist/assets/quadrantDiagram-O4NWA36T-Wzrlpzgh.js +7 -0
- package/dist/assets/railroadDiagram-XR7U4H2S-moHTFMC0.js +1 -0
- package/dist/assets/requirementDiagram-PLB6GJNP-CwfXdFH8.js +84 -0
- package/dist/assets/sankeyDiagram-IPEJSGJF-3ZROYvnq.js +40 -0
- package/dist/assets/sequenceDiagram-PO4LG4MO-E1TBebP8.js +169 -0
- package/dist/assets/sizeCapture-INFHLROL-DHKEU5pu.js +1 -0
- package/dist/assets/stateDiagram-v2-GCMORJYK-BguEj09D.js +291 -0
- package/dist/assets/swimlanes-2SLR337P-C7IDYnhd.js +1 -0
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
- package/dist/assets/timeline-definition-EJHVYXUP-DCW-F5PW.js +120 -0
- package/dist/assets/usecaseDiagram-POWQR4AR-xBMFtWYV.js +370 -0
- package/dist/assets/vennDiagram-UO4OBE2U-B5nIkuH8.js +34 -0
- package/dist/assets/wardleyDiagram-VNRHLVJA-DYFAhdbr.js +78 -0
- package/dist/assets/xychartDiagram-PMCCYNJV-DeXwX6Cz.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/browser-proxy.js +2 -1
- package/dist-server/container-mcp.js +1047 -173
- package/dist-server/drivers/agents-proxy.js +16121 -1504
- package/dist-server/drivers/phone-proxy.js +33 -2
- package/dist-server/enterprise/LICENSE +32 -0
- package/dist-server/enterprise/server/index.js +5 -5
- package/dist-server/index.js +16134 -7446
- package/dist-server/local-computer-proxy.js +3 -3
- package/dist-server/local-computer.js +53 -17
- package/dist-server/mcp-server.js +3 -1
- package/dist-server/openmausbot.js +9353 -6124
- package/dist-server/pair-cli.js +9353 -6124
- package/dist-server/permission-proxy.js +29 -9
- package/dist-server/server/admin-activity.js +410 -0
- package/dist-server/server/attachments.js +55 -0
- package/dist-server/server/auto-approve.js +4 -3
- package/dist-server/server/auto-vm-claims.js +1 -0
- package/dist-server/server/bot-package.js +18 -292
- package/dist-server/server/bot-profile.js +5 -17
- package/dist-server/server/bot-visibility.js +462 -0
- package/dist-server/server/box-create-idempotency.js +3 -3
- package/dist-server/server/box.js +59 -56
- package/dist-server/server/browser-proxy.js +3 -2
- package/dist-server/server/browser-runtime.js +38 -2
- package/dist-server/server/channel-queue.js +2 -0
- package/dist-server/server/cli-prompts.js +24 -2
- package/dist-server/server/cli.js +81 -12
- package/dist-server/server/command-allowlist.js +154 -0
- package/dist-server/server/composio.js +49 -11
- package/dist-server/server/computer-wait.js +22 -9
- package/dist-server/server/config.js +133 -17
- package/dist-server/server/connector-verdict.js +141 -0
- package/dist-server/server/container-computer.js +1 -1
- package/dist-server/server/decision-log.js +209 -48
- package/dist-server/server/default-model-selection.js +29 -3
- package/dist-server/server/delegations.js +97 -44
- package/dist-server/server/drivers/acp/core.js +163 -33
- package/dist-server/server/drivers/acp/grok.js +4 -3
- package/dist-server/server/drivers/agents-call.js +1162 -0
- package/dist-server/server/drivers/agents-catalog.js +689 -0
- package/dist-server/server/drivers/agents-client.js +28 -0
- package/dist-server/server/drivers/agents-proxy.js +18 -1722
- package/dist-server/server/drivers/boxagent.js +174 -36
- package/dist-server/server/drivers/builtIn.js +2 -0
- package/dist-server/server/drivers/chat-box-tools.js +90 -0
- package/dist-server/server/drivers/chat-images.js +54 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +55 -10
- package/dist-server/server/drivers/chat-tool-approval.js +70 -18
- package/dist-server/server/drivers/claude.js +28 -8
- package/dist-server/server/drivers/codex-instructions.js +48 -6
- package/dist-server/server/drivers/codex-mcp-names.js +44 -0
- package/dist-server/server/drivers/codex-tool-surface.js +20 -0
- package/dist-server/server/drivers/codex.js +121 -34
- package/dist-server/server/drivers/grok.js +2 -1
- package/dist-server/server/drivers/mistral.js +115 -0
- package/dist-server/server/drivers/openai-chat.js +155 -32
- package/dist-server/server/drivers/openai-compat.js +16 -8
- package/dist-server/server/drivers/permission-command.js +35 -0
- package/dist-server/server/drivers/phone-proxy.js +47 -2
- package/dist-server/server/drivers/pi.js +75 -4
- package/dist-server/server/drivers/prompt-split.js +87 -0
- package/dist-server/server/enterprise.js +97 -16
- package/dist-server/server/external-runtime.js +83 -0
- package/dist-server/server/fleet.js +1 -1
- package/dist-server/server/group-thread-usage.js +103 -0
- package/dist-server/server/guarded-requests.js +60 -0
- package/dist-server/server/harness/http.js +61 -0
- package/dist-server/server/harness/registry.js +1 -0
- package/dist-server/server/hosted-models.js +151 -0
- package/dist-server/server/hosted-slack.js +11 -0
- package/dist-server/server/index.js +3091 -658
- package/dist-server/server/local-computer.js +72 -23
- package/dist-server/server/managed-desktop.js +79 -14
- package/dist-server/server/managed-policy.js +153 -0
- package/dist-server/server/mcp-bridge.js +6 -6
- package/dist-server/server/message-db.js +28 -0
- package/dist-server/server/model-context-window.js +7 -0
- package/dist-server/server/model-prices.js +121 -0
- package/dist-server/server/new-bot-defaults.js +115 -0
- package/dist-server/server/notify.js +10 -1
- package/dist-server/server/org-library.js +1051 -0
- package/dist-server/server/package-export.js +627 -39
- package/dist-server/server/package-import.js +458 -0
- package/dist-server/server/package-parts.js +87 -0
- package/dist-server/server/peer-approval.js +16 -7
- package/dist-server/server/peer-roster.js +23 -2
- package/dist-server/server/presets.js +492 -0
- package/dist-server/server/provider-key-check.js +3 -2
- package/dist-server/server/published-teams.js +80 -0
- package/dist-server/server/recent-work.js +15 -0
- package/dist-server/server/request-auth.js +96 -0
- package/dist-server/server/room-handoffs.js +44 -14
- package/dist-server/server/routes/bot-presets.js +25 -0
- package/dist-server/server/routes/hosted-slack.js +20 -0
- package/dist-server/server/routes/table.js +14 -0
- package/dist-server/server/routine-requests.js +6 -3
- package/dist-server/server/routines.js +63 -1
- package/dist-server/server/section-context.js +10 -0
- package/dist-server/server/skills.js +34 -61
- package/dist-server/server/spend.js +111 -16
- package/dist-server/server/steer-queue.js +9 -3
- package/dist-server/server/store.js +550 -35
- package/dist-server/server/surface.js +10 -4
- package/dist-server/server/system-prompt.js +12 -4
- package/dist-server/server/team-backup.js +16 -3
- package/dist-server/server/team-computers.js +10 -0
- package/dist-server/server/team-library.js +6 -2
- package/dist-server/server/team-manifest.js +4 -3
- package/dist-server/server/team-setup-requests.js +16 -5
- package/dist-server/server/thread-events.js +17 -1
- package/dist-server/server/thread-starters.js +53 -0
- package/dist-server/server/turn-context.js +2 -0
- package/dist-server/server/turn-watchdog.js +12 -3
- package/dist-server/server/usage-ledger.js +43 -9
- package/dist-server/server/vps-computer.js +139 -51
- package/dist-server/server/vps-container-mcp.js +7 -0
- package/dist-server/server/vps-ssh.js +36 -7
- package/dist-server/server/workspace-backup-policy.js +21 -2
- package/dist-server/server/workspace-backup.js +7 -4
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/ask-question.js +188 -12
- package/dist-server/shared/bot-profile.js +15 -0
- package/dist-server/shared/command-allowlist.js +1 -0
- package/dist-server/shared/credential-request.js +1 -1
- package/dist-server/shared/mention-boundary.js +15 -0
- package/dist-server/shared/package-format.js +967 -0
- package/dist-server/shared/routines.js +7 -1
- package/dist-server/shared/skill-md.js +64 -0
- package/dist-server/shared/team-backup.js +9 -0
- package/dist-server/shared/wire.js +4 -0
- package/dist-server/src/lib/composer-attachments.js +16 -0
- package/dist-server/vps-container-mcp.js +1147 -179
- package/enterprise/README.md +33 -12
- package/enterprise/server/index.js +5 -5
- package/package.json +1 -1
- package/dist/assets/index-Ba9G44HI.js +0 -310
- package/dist/assets/index-CkRNp7wX.css +0 -1
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
import { Ajv } from "ajv";
|
|
4
4
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
5
5
|
import formats from "ajv-formats";
|
|
6
|
+
import { stripControlPlaneEnv } from "../config.js";
|
|
7
|
+
import { augmentedPath } from "../env-path.js";
|
|
6
8
|
import { killCliTree, spawnCli } from "../procs.js";
|
|
9
|
+
import { chatImage } from "./chat-images.js";
|
|
10
|
+
import { ChatBoxClient } from "./chat-box-tools.js";
|
|
7
11
|
/** The transport cannot safely continue this turn. A dispatched operation may
|
|
8
12
|
* already have taken effect, so callers must not retry it through a new round. */
|
|
9
13
|
export class ChatToolSessionError extends Error {
|
|
@@ -21,7 +25,16 @@ function object(value) {
|
|
|
21
25
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
22
26
|
}
|
|
23
27
|
function aborted() { return new Error("MCP operation cancelled"); }
|
|
28
|
+
/** These servers are a chat-runtime bot's tools, the counterpart of an engine
|
|
29
|
+
* CLI's children: the operator's control-plane secrets never ride along. What
|
|
30
|
+
* the server entry itself names is a deliberate grant and is applied last. */
|
|
31
|
+
export function chatMcpEnvironment(serverEnv, source = process.env) {
|
|
32
|
+
const env = { ...source, PATH: augmentedPath() };
|
|
33
|
+
stripControlPlaneEnv(env);
|
|
34
|
+
return { ...env, ...serverEnv };
|
|
35
|
+
}
|
|
24
36
|
class ChatMcpClient {
|
|
37
|
+
frameBytes;
|
|
25
38
|
child;
|
|
26
39
|
buffer = "";
|
|
27
40
|
nextId = 1;
|
|
@@ -29,10 +42,14 @@ class ChatMcpClient {
|
|
|
29
42
|
closed = false;
|
|
30
43
|
closing;
|
|
31
44
|
pending = new Map();
|
|
32
|
-
constructor(server) {
|
|
45
|
+
constructor(server, computerUse = false) {
|
|
46
|
+
this.frameBytes = computerUse ? 32 * 1024 * 1024 : FRAME_BYTES;
|
|
33
47
|
try {
|
|
48
|
+
// The desktop shell inherits Finder's bare PATH, where `npx`-style
|
|
49
|
+
// servers cannot find `node` and exit at once. Widen it the way the
|
|
50
|
+
// Claude and Codex drivers do; a PATH the user set on the server wins.
|
|
34
51
|
this.child = spawnCli(server.command, server.args, {
|
|
35
|
-
stdio: ["pipe", "pipe", "pipe"], env:
|
|
52
|
+
stdio: ["pipe", "pipe", "pipe"], env: chatMcpEnvironment(server.env),
|
|
36
53
|
});
|
|
37
54
|
}
|
|
38
55
|
catch {
|
|
@@ -64,7 +81,9 @@ class ChatMcpClient {
|
|
|
64
81
|
entry.reject(new Error("MCP session closed"));
|
|
65
82
|
this.pending.clear();
|
|
66
83
|
this.buffer = "";
|
|
67
|
-
|
|
84
|
+
// Confirm within the codebase-default grace: Windows reaps the tree via
|
|
85
|
+
// taskkill /T, which can exceed shorter budgets on a loaded machine.
|
|
86
|
+
this.closing = killCliTree(this.child, 5_000).then((stopped) => {
|
|
68
87
|
if (!stopped)
|
|
69
88
|
throw new Error("MCP server shutdown could not be confirmed; execution outcome may be uncertain");
|
|
70
89
|
});
|
|
@@ -74,7 +93,7 @@ class ChatMcpClient {
|
|
|
74
93
|
if (this.closed || !this.child.stdin.writable || this.child.stdin.destroyed)
|
|
75
94
|
throw new Error("MCP session closed");
|
|
76
95
|
const encoded = JSON.stringify(frame) + "\n";
|
|
77
|
-
if (Buffer.byteLength(encoded) >
|
|
96
|
+
if (Buffer.byteLength(encoded) > this.frameBytes)
|
|
78
97
|
throw new Error("MCP request exceeds the frame limit");
|
|
79
98
|
this.child.stdin.write(encoded);
|
|
80
99
|
}
|
|
@@ -88,7 +107,7 @@ class ChatMcpClient {
|
|
|
88
107
|
this.buffer = this.buffer.slice(newline + 1);
|
|
89
108
|
if (++this.frames > MAX_FRAMES)
|
|
90
109
|
return this.fail(new Error("MCP session exceeded the response frame count limit"));
|
|
91
|
-
if (Buffer.byteLength(line) >
|
|
110
|
+
if (Buffer.byteLength(line) > this.frameBytes)
|
|
92
111
|
return this.fail(new Error("MCP response exceeds the frame limit"));
|
|
93
112
|
if (!line.trim())
|
|
94
113
|
continue;
|
|
@@ -125,7 +144,7 @@ class ChatMcpClient {
|
|
|
125
144
|
else
|
|
126
145
|
entry.resolve(message.result);
|
|
127
146
|
}
|
|
128
|
-
if (Buffer.byteLength(this.buffer) >
|
|
147
|
+
if (Buffer.byteLength(this.buffer) > this.frameBytes)
|
|
129
148
|
this.fail(new Error("MCP response exceeds the frame limit"));
|
|
130
149
|
}
|
|
131
150
|
async call(method, params, signal, timeout) {
|
|
@@ -229,6 +248,8 @@ function compileSchema(schema) {
|
|
|
229
248
|
// ajv-formats is CommonJS and exports the plugin as both module.exports
|
|
230
249
|
// and .default; the latter also matches its NodeNext declaration.
|
|
231
250
|
formats.default(compiler);
|
|
251
|
+
compiler.addFormat("uint32", { type: "number", validate: value => Number.isInteger(value) && value >= 0 && value <= 4294967295 });
|
|
252
|
+
compiler.addFormat("uint64", { type: "number", validate: value => Number.isSafeInteger(value) && value >= 0 });
|
|
232
253
|
try {
|
|
233
254
|
return compiler.compile(schema);
|
|
234
255
|
}
|
|
@@ -245,8 +266,14 @@ function boundedText(value) {
|
|
|
245
266
|
end -= 1;
|
|
246
267
|
return `${bytes.subarray(0, end).toString()}\n[MCP result truncated at 50KB; request less output.]`;
|
|
247
268
|
}
|
|
248
|
-
export async function mountChatTools(integrations, signal) {
|
|
269
|
+
export async function mountChatTools(integrations, signal, computerUse = false) {
|
|
249
270
|
const servers = [];
|
|
271
|
+
if (computerUse && integrations?.computer)
|
|
272
|
+
servers.push(["computer", integrations.computer]);
|
|
273
|
+
if (computerUse && integrations?.localComputer)
|
|
274
|
+
servers.push(["computer", integrations.localComputer]);
|
|
275
|
+
if (computerUse && integrations?.browser)
|
|
276
|
+
servers.push(["browser", integrations.browser]);
|
|
250
277
|
if (integrations?.agents)
|
|
251
278
|
servers.push(["agents", integrations.agents]);
|
|
252
279
|
if (integrations?.composio)
|
|
@@ -285,7 +312,9 @@ export async function mountChatTools(integrations, signal) {
|
|
|
285
312
|
const mounts = await Promise.allSettled(servers.map(async ([name, descriptor]) => {
|
|
286
313
|
if (signal.aborted || closed)
|
|
287
314
|
throw aborted();
|
|
288
|
-
|
|
315
|
+
// Every mounted MCP server can return images when the caller enables
|
|
316
|
+
// image delivery, including custom servers. Text stays bounded below.
|
|
317
|
+
const client = "boxId" in descriptor ? new ChatBoxClient(descriptor) : new ChatMcpClient(descriptor, computerUse);
|
|
289
318
|
clients.push(client);
|
|
290
319
|
return { name, client, tools: await client.tools(signal) };
|
|
291
320
|
}));
|
|
@@ -305,6 +334,18 @@ export async function mountChatTools(integrations, signal) {
|
|
|
305
334
|
if (Buffer.byteLength(JSON.stringify(tool.inputSchema)) > SCHEMA_BYTES)
|
|
306
335
|
throw new Error("MCP tool schema exceeds the 64KB limit");
|
|
307
336
|
const schema = compileSchema(tool.inputSchema);
|
|
337
|
+
const parameters = { ...tool.inputSchema };
|
|
338
|
+
const constraints = {};
|
|
339
|
+
if (computerUse) {
|
|
340
|
+
for (const key of ["anyOf", "oneOf", "allOf", "not"]) {
|
|
341
|
+
if (key in parameters) {
|
|
342
|
+
constraints[key] = parameters[key];
|
|
343
|
+
delete parameters[key];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const description = (typeof tool.description === "string" ? tool.description : "Configured MCP tool") +
|
|
348
|
+
(Object.keys(constraints).length ? " Additional argument constraints (validated before execution): " + JSON.stringify(constraints) : "");
|
|
308
349
|
const base = `${server}_${tool.name}`.toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) || "mcp_tool";
|
|
309
350
|
let name = base;
|
|
310
351
|
for (let index = 2; registered.has(name); index += 1) {
|
|
@@ -312,7 +353,7 @@ export async function mountChatTools(integrations, signal) {
|
|
|
312
353
|
name = base.slice(0, 64 - suffix.length) + suffix;
|
|
313
354
|
}
|
|
314
355
|
registered.set(name, { client, name: tool.name, schema });
|
|
315
|
-
definitions.push({ type: "function", function: { name, description
|
|
356
|
+
definitions.push({ type: "function", function: { name, description, parameters } });
|
|
316
357
|
if (Buffer.byteLength(JSON.stringify(definitions)) > CATALOG_BYTES)
|
|
317
358
|
throw new Error("MCP tool catalog exceeds the 1MB limit");
|
|
318
359
|
}
|
|
@@ -349,12 +390,15 @@ export async function mountChatTools(integrations, signal) {
|
|
|
349
390
|
if (!object(result) || !Array.isArray(result.content) || (result.isError !== undefined && typeof result.isError !== "boolean"))
|
|
350
391
|
throw new Error("MCP tool returned an invalid result; execution outcome may be uncertain");
|
|
351
392
|
const parts = [];
|
|
393
|
+
const images = [];
|
|
352
394
|
let unsupported = 0;
|
|
353
395
|
for (const item of result.content) {
|
|
354
396
|
if (!object(item) || typeof item.type !== "string" || (item.type === "text" && typeof item.text !== "string"))
|
|
355
397
|
throw new Error("MCP tool returned invalid content; execution outcome may be uncertain");
|
|
356
398
|
if (item.type === "text")
|
|
357
399
|
parts.push(item.text);
|
|
400
|
+
else if (computerUse && item.type === "image")
|
|
401
|
+
images.push(chatImage(item));
|
|
358
402
|
else
|
|
359
403
|
unsupported += 1;
|
|
360
404
|
}
|
|
@@ -362,7 +406,8 @@ export async function mountChatTools(integrations, signal) {
|
|
|
362
406
|
parts.push(JSON.stringify(result.structuredContent));
|
|
363
407
|
if (unsupported)
|
|
364
408
|
parts.unshift(`[${unsupported} unsupported MCP content item(s) omitted. The operation may have taken effect, but its full result cannot be represented; inspect its state before retrying.]`);
|
|
365
|
-
return { text: boundedText(parts.join("\n") || "(empty result)"), ok: result.isError !== true && unsupported === 0
|
|
409
|
+
return { text: boundedText(parts.join("\n") || (images.length ? "Screenshot captured." : "(empty result)")), ok: result.isError !== true && unsupported === 0,
|
|
410
|
+
...(images.length ? { images } : {}) };
|
|
366
411
|
}
|
|
367
412
|
catch (error) {
|
|
368
413
|
await close();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Per-turn
|
|
1
|
+
// Per-turn ask gate, adapted from the approval lifecycle in #780.
|
|
2
2
|
// Register before publishing: a harness listener may answer synchronously.
|
|
3
3
|
import { newId } from "../contracts.js";
|
|
4
4
|
export function createChatToolApproval(options) {
|
|
@@ -10,33 +10,85 @@ export function createChatToolApproval(options) {
|
|
|
10
10
|
return Promise.resolve(false);
|
|
11
11
|
const ask = { id: newId(), tool, summary };
|
|
12
12
|
return new Promise((resolve) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
let timer;
|
|
14
|
+
const card = {
|
|
15
|
+
kind: "permission",
|
|
16
|
+
ask,
|
|
17
|
+
finish(source, decision) {
|
|
18
|
+
if (!pending.delete(ask.id))
|
|
19
|
+
return;
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
options.signal.removeEventListener("abort", abort);
|
|
22
|
+
const allowed = decision?.allowed === true;
|
|
23
|
+
options.resolved(ask, allowed, source);
|
|
24
|
+
resolve(allowed);
|
|
25
|
+
},
|
|
20
26
|
};
|
|
21
|
-
const abort = () => finish(
|
|
22
|
-
|
|
27
|
+
const abort = () => card.finish("system");
|
|
28
|
+
timer = setTimeout(() => card.finish("timeout"), options.timeoutMs ?? 15 * 60_000);
|
|
23
29
|
timer.unref?.();
|
|
24
|
-
pending.set(ask.id,
|
|
30
|
+
pending.set(ask.id, card);
|
|
25
31
|
options.signal.addEventListener("abort", abort, { once: true });
|
|
26
32
|
options.open(ask);
|
|
27
33
|
});
|
|
28
34
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
question(tool, summary, questions) {
|
|
36
|
+
if (closed || options.signal.aborted)
|
|
37
|
+
return Promise.resolve(null);
|
|
38
|
+
const ask = { id: newId(), tool, summary };
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
let timer;
|
|
41
|
+
const card = {
|
|
42
|
+
kind: "question",
|
|
43
|
+
ask,
|
|
44
|
+
finish(source, decision) {
|
|
45
|
+
if (!pending.delete(ask.id))
|
|
46
|
+
return;
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
options.signal.removeEventListener("abort", abort);
|
|
49
|
+
// Only the person's reply resolves a question; a timeout or
|
|
50
|
+
// abort resolves null so no system note can occupy the answer
|
|
51
|
+
// slot the model reads as the person's words.
|
|
52
|
+
const reply = typeof decision?.message === "string" && decision.message.length > 0 ? decision.message : null;
|
|
53
|
+
options.resolvedQuestion(ask, reply !== null, source);
|
|
54
|
+
resolve(reply);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const abort = () => card.finish("system");
|
|
58
|
+
timer = setTimeout(() => card.finish("timeout"), options.timeoutMs ?? 15 * 60_000);
|
|
59
|
+
timer.unref?.();
|
|
60
|
+
pending.set(ask.id, card);
|
|
61
|
+
options.signal.addEventListener("abort", abort, { once: true });
|
|
62
|
+
options.openQuestion(ask, questions);
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
answer(id, behavior, message) {
|
|
66
|
+
const card = pending.get(id);
|
|
67
|
+
if (!card || options.signal.aborted)
|
|
32
68
|
return "unavailable";
|
|
33
|
-
|
|
34
|
-
|
|
69
|
+
if (behavior === "deny") {
|
|
70
|
+
card.finish("user");
|
|
71
|
+
return "rejected";
|
|
72
|
+
}
|
|
73
|
+
if (card.kind === "permission") {
|
|
74
|
+
// An answer is not a permission: only allow or deny settles it.
|
|
75
|
+
if (behavior !== "allow")
|
|
76
|
+
return "unavailable";
|
|
77
|
+
card.finish("user", { allowed: true });
|
|
78
|
+
return "allowed-once";
|
|
79
|
+
}
|
|
80
|
+
// And an allow is not an answer: a question settles only on a real
|
|
81
|
+
// reply, never a blank the model would have to interpret.
|
|
82
|
+
if (behavior === "answer" && typeof message === "string" && message.trim()) {
|
|
83
|
+
card.finish("user", { message });
|
|
84
|
+
return "answered";
|
|
85
|
+
}
|
|
86
|
+
return "unavailable";
|
|
35
87
|
},
|
|
36
88
|
close() {
|
|
37
89
|
closed = true;
|
|
38
|
-
for (const
|
|
39
|
-
finish(
|
|
90
|
+
for (const card of pending.values())
|
|
91
|
+
card.finish("system");
|
|
40
92
|
},
|
|
41
93
|
};
|
|
42
94
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Integrations become MCP servers on the CLI:
|
|
8
8
|
// - Composio Sessions (connected apps → tools) over streamable HTTP
|
|
9
|
-
// - the bot's cloud computer (
|
|
9
|
+
// - the bot's cloud computer (boat.dev) via server/computer-proxy.ts
|
|
10
10
|
// — screenshot/exec/open_url, the CUA-on-the-box bridge
|
|
11
11
|
import { createHash, randomBytes } from "node:crypto";
|
|
12
12
|
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -25,6 +25,7 @@ import { askInputSummary, commandSummary, toolDetailPreview } from "../tool-summ
|
|
|
25
25
|
import { classifyError, computeBackoff, interruptibleDelay, RETRY_MAX_ATTEMPTS } from "./retry.js";
|
|
26
26
|
import { applyClaudeInject, decodeInjectId, mergeLocalInject, probeLocalInjects, resolveInjectId, } from "./local-inject.js";
|
|
27
27
|
import { appendNative } from "./native.js";
|
|
28
|
+
import { permissionCommand, permissionLaunchCwd } from "./permission-command.js";
|
|
28
29
|
import { SPAWNED_PROXIES } from "../proxy-paths.js";
|
|
29
30
|
import { extractMcpImages } from "../mcp-tool-images.js";
|
|
30
31
|
import { ASK_USER_QUESTION_TOOL, askQuestionSummary, parseAskQuestions, parseChoices, questionChoices, } from "../../shared/ask-question.js";
|
|
@@ -322,6 +323,7 @@ export const STATIC_CLAUDE_MODELS = {
|
|
|
322
323
|
options: [
|
|
323
324
|
{ id: "claude-fable-5-1", label: "Claude Fable 5.1" },
|
|
324
325
|
{ id: "claude-fable-5", label: "Claude Fable 5" },
|
|
326
|
+
{ id: "claude-opus-5-5", label: "Claude Opus 5.5", contextWindow: 1_000_000 },
|
|
325
327
|
{ id: "claude-opus-5", label: "Claude Opus 5" },
|
|
326
328
|
{ id: "claude-sonnet-5", label: "Claude Sonnet 5" },
|
|
327
329
|
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5" },
|
|
@@ -724,10 +726,13 @@ function decodeConfig(raw) {
|
|
|
724
726
|
const configDir = typeof o.configDir === "string" ? o.configDir.trim() : undefined;
|
|
725
727
|
if (configDir)
|
|
726
728
|
resolveClaudeConfigDir(configDir);
|
|
729
|
+
if (o.managedModels !== undefined && (o.managed !== true || !Array.isArray(o.managedModels) || !o.managedModels.length || o.managedModels.some(model => typeof model !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,159}$/.test(model))))
|
|
730
|
+
throw new Error("Invalid hosted Claude models.");
|
|
727
731
|
return {
|
|
728
732
|
cli: typeof o.cli === "string" ? o.cli : "claude",
|
|
729
733
|
...(configDir ? { configDir } : {}),
|
|
730
734
|
...(o.managed === true ? { managed: true } : {}),
|
|
735
|
+
...(o.managedModels ? { managedModels: o.managedModels } : {}),
|
|
731
736
|
permissionMode: mode ?? "acceptEdits",
|
|
732
737
|
...(tools !== undefined ? { tools } : {}),
|
|
733
738
|
...(disallowedTools !== undefined ? { disallowedTools } : {}),
|
|
@@ -821,7 +826,7 @@ export const ClaudeDriver = {
|
|
|
821
826
|
if (inheritsUserConfig(catalogEnv)) {
|
|
822
827
|
console.error(`claude (${instanceId}): OMB_CLAUDE_INHERIT_USER_CONFIG=1 — bots inherit this machine's Claude Code MCP servers, skills, hooks and CLAUDE.md on every turn; remove it unless a bot needs a user-scope server`);
|
|
823
828
|
}
|
|
824
|
-
let models = STATIC_CLAUDE_MODELS;
|
|
829
|
+
let models = config.managedModels ? { default: config.managedModels[0], options: config.managedModels.map(id => ({ id, label: id })) } : STATIC_CLAUDE_MODELS;
|
|
825
830
|
const refreshModels = async () => {
|
|
826
831
|
if (config.managed)
|
|
827
832
|
return;
|
|
@@ -932,6 +937,8 @@ export const ClaudeDriver = {
|
|
|
932
937
|
// is a fresh sendTurn, and the attempt cap must survive across launches
|
|
933
938
|
const retryState = new Map();
|
|
934
939
|
const sendTurn = async (turn, logicalTurnId) => {
|
|
940
|
+
if (config.managedModels && (!turn.model || !config.managedModels.includes(turn.model)))
|
|
941
|
+
throw new Error("This model is not assigned to this workspace.");
|
|
935
942
|
if (config.managed && (!turn.model || turn.model.includes("::") || !config.configDir ||
|
|
936
943
|
!input.environment.ANTHROPIC_API_KEY || !input.environment.ANTHROPIC_BASE_URL)) {
|
|
937
944
|
throw new Error("Company model access is unavailable. Reconnect your organization; personal billing will not be used.");
|
|
@@ -1193,6 +1200,7 @@ export const ClaudeDriver = {
|
|
|
1193
1200
|
// parallel bot work must use the harness's durable delegate_bot path.
|
|
1194
1201
|
env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = "1";
|
|
1195
1202
|
const cwd = turn.cwd ?? homedir();
|
|
1203
|
+
const commandCwd = permissionLaunchCwd(cwd);
|
|
1196
1204
|
// Everything that shapes the process, minus session/turn-specific temp
|
|
1197
1205
|
// paths. Their contents are represented directly in the key instead.
|
|
1198
1206
|
const privateFileFlags = new Set(["--mcp-config", "--settings"]);
|
|
@@ -1224,6 +1232,8 @@ export const ClaudeDriver = {
|
|
|
1224
1232
|
clearTimeout(live.idleTimer);
|
|
1225
1233
|
live.turn = { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false };
|
|
1226
1234
|
active.set(threadId, { stop: () => {
|
|
1235
|
+
if (live.turn)
|
|
1236
|
+
live.turn.stopRequested = true;
|
|
1227
1237
|
closeSession(threadId, "interrupted");
|
|
1228
1238
|
retry.cancelled = true;
|
|
1229
1239
|
retryAbort.abort();
|
|
@@ -1231,12 +1241,13 @@ export const ClaudeDriver = {
|
|
|
1231
1241
|
}, turnId, broker: live.broker });
|
|
1232
1242
|
emit({ ...base(threadId, turnId), type: "turn.started" });
|
|
1233
1243
|
const volatile = turn.systemVolatile ?? "";
|
|
1234
|
-
const message = volatile === live.volatile
|
|
1244
|
+
const message = volatile === live.volatile && !turn.mentionTurn
|
|
1235
1245
|
? promptMsg
|
|
1236
1246
|
: claudeUserMessage(withVolatileNote(turn.text, volatile), turn.images);
|
|
1237
1247
|
live.volatile = volatile;
|
|
1248
|
+
const running = live.turn;
|
|
1238
1249
|
const written = await writeUser(live, threadId, message);
|
|
1239
|
-
if (!written) {
|
|
1250
|
+
if (!written && !running?.stopRequested) {
|
|
1240
1251
|
active.delete(threadId);
|
|
1241
1252
|
live.turn = null;
|
|
1242
1253
|
closeSession(threadId, "stdin write failed");
|
|
@@ -1314,6 +1325,9 @@ export const ClaudeDriver = {
|
|
|
1314
1325
|
requestType: ask.kind,
|
|
1315
1326
|
tool: ask.tool,
|
|
1316
1327
|
summary: askSummary(ask),
|
|
1328
|
+
command: ask.kind === "permission" && ask.tool === "Bash"
|
|
1329
|
+
? permissionCommand(ask.input.command, commandCwd) : undefined,
|
|
1330
|
+
requiresExplicitApproval: ask.kind === "permission" && ask.tool === "Bash" && ask.input.dangerouslyDisableSandbox === true || undefined,
|
|
1317
1331
|
nativeReview,
|
|
1318
1332
|
// the proxy hands Claude its own suggested rules on `always`;
|
|
1319
1333
|
// host control stays one action at a time
|
|
@@ -1610,7 +1624,10 @@ export const ClaudeDriver = {
|
|
|
1610
1624
|
// a turn still running when the process died is a failed turn; a
|
|
1611
1625
|
// process that exited between turns (idle close, contract change)
|
|
1612
1626
|
// is just a session ending
|
|
1613
|
-
if (session.turn && !session.turn.settled) {
|
|
1627
|
+
if (session.turn?.stopRequested && !session.turn.settled) {
|
|
1628
|
+
settle(false, "interrupted");
|
|
1629
|
+
}
|
|
1630
|
+
else if (session.turn && !session.turn.settled) {
|
|
1614
1631
|
// A retained process may be running a later user turn. Its close
|
|
1615
1632
|
// handler must retry that request, not the process's first prompt.
|
|
1616
1633
|
const { turnId, input: turn, retryAbort } = session.turn;
|
|
@@ -1794,6 +1811,8 @@ export const ClaudeDriver = {
|
|
|
1794
1811
|
void session.finishClose();
|
|
1795
1812
|
});
|
|
1796
1813
|
const stop = () => {
|
|
1814
|
+
if (session.turn)
|
|
1815
|
+
session.turn.stopRequested = true;
|
|
1797
1816
|
// taskkill is asynchronous on Windows. Retire steering and approvals
|
|
1798
1817
|
// now, before a still-connected child can submit more work.
|
|
1799
1818
|
closeSession(threadId, "interrupted");
|
|
@@ -1807,7 +1826,8 @@ export const ClaudeDriver = {
|
|
|
1807
1826
|
// stdin stays OPEN: that is what keeps the session alive for a
|
|
1808
1827
|
// mid-turn steer or the next turn; closeSession() ends it.
|
|
1809
1828
|
if (!(await writeUser(session, threadId, promptMsg))) {
|
|
1810
|
-
|
|
1829
|
+
if (!session.turn?.stopRequested)
|
|
1830
|
+
settle(false, "stdin_write_failed");
|
|
1811
1831
|
closeSession(threadId, "stdin write failed");
|
|
1812
1832
|
}
|
|
1813
1833
|
return { turnId };
|
|
@@ -1844,9 +1864,9 @@ export const ClaudeDriver = {
|
|
|
1844
1864
|
* `claude -p "prompt"` shape is not safe for review. No tools or MCP
|
|
1845
1865
|
* servers are mounted in this isolated process. */
|
|
1846
1866
|
const generateReview = (prompt, signal) => new Promise((resolve, reject) => {
|
|
1847
|
-
const child = spawnCli(config.cli, ["-p", "--model", "claude-haiku-4-5", "--output-format", "text"], {
|
|
1867
|
+
const child = spawnCli(config.cli, ["-p", "--model", config.managedModels?.[0] ?? "claude-haiku-4-5", "--output-format", "text"], {
|
|
1848
1868
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1849
|
-
env: environment("claude-haiku-4-5"),
|
|
1869
|
+
env: environment(config.managedModels?.[0] ?? "claude-haiku-4-5"),
|
|
1850
1870
|
});
|
|
1851
1871
|
let stdout = "";
|
|
1852
1872
|
let stderr = "";
|
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
// Codex owns history; this receipt only remembers which bot rules
|
|
1
|
+
// Codex owns history; this receipt only remembers which bot rules and which
|
|
2
|
+
// volatile context digest the native thread last carried.
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
import { mkdirSync, readFileSync } from "node:fs";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { DATA_DIR } from "../config.js";
|
|
6
7
|
import { writeFileAtomic } from "../atomic.js";
|
|
7
8
|
const digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
9
|
+
const isFingerprint = (value) => /^[0-9a-f]{64}$/.test(value);
|
|
10
|
+
function parseReceipt(raw) {
|
|
11
|
+
if (!raw)
|
|
12
|
+
return null;
|
|
13
|
+
const trimmed = raw.trim();
|
|
14
|
+
if (isFingerprint(trimmed))
|
|
15
|
+
return { instructions: trimmed };
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(trimmed);
|
|
18
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
19
|
+
const record = parsed;
|
|
20
|
+
if (typeof record.instructions === "string" && isFingerprint(record.instructions)) {
|
|
21
|
+
return {
|
|
22
|
+
instructions: record.instructions,
|
|
23
|
+
...(typeof record.volatile === "string" && isFingerprint(record.volatile) ? { volatile: record.volatile } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* a corrupt receipt is simply unknown */
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
8
33
|
/** Preserve native configured rules when overriding Codex's developer slot. */
|
|
9
34
|
export function codexDeveloperInstructions(config, botInstructions) {
|
|
10
35
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
@@ -20,19 +45,21 @@ export function codexDeveloperInstructions(config, botInstructions) {
|
|
|
20
45
|
? `${botInstructions || "No OpenMausBot bot-specific instructions remain."}\n\n${configured}`
|
|
21
46
|
: botInstructions;
|
|
22
47
|
}
|
|
23
|
-
export async function syncCodexInstructions(key, nativeThreadId, instructions, resumed, request) {
|
|
48
|
+
export async function syncCodexInstructions(key, nativeThreadId, instructions, volatile, resumed, request, mentionTurn = false) {
|
|
24
49
|
const directory = join(DATA_DIR, "codex-instructions");
|
|
25
50
|
const path = join(directory, `${digest(JSON.stringify([key, nativeThreadId]))}.sha256`);
|
|
26
51
|
const fingerprint = digest(instructions);
|
|
52
|
+
const volatileFingerprint = digest(volatile);
|
|
27
53
|
let previous;
|
|
28
54
|
try {
|
|
29
|
-
previous = readFileSync(path, "utf8");
|
|
55
|
+
previous = parseReceipt(readFileSync(path, "utf8"));
|
|
30
56
|
}
|
|
31
57
|
catch (error) {
|
|
32
58
|
if (error.code !== "ENOENT")
|
|
33
59
|
throw error;
|
|
60
|
+
previous = null;
|
|
34
61
|
}
|
|
35
|
-
if (resumed && previous !== fingerprint) {
|
|
62
|
+
if (resumed && previous?.instructions !== fingerprint) {
|
|
36
63
|
// thread/resume config overrides are used after compaction, but Codex
|
|
37
64
|
// 0.153.4 keeps the old initial developer message until then. Append a
|
|
38
65
|
// developer update ONLY on change (or first adoption of an old session).
|
|
@@ -59,8 +86,23 @@ export async function syncCodexInstructions(key, nativeThreadId, instructions, r
|
|
|
59
86
|
throw error;
|
|
60
87
|
}
|
|
61
88
|
}
|
|
62
|
-
|
|
89
|
+
// A mention describes the turn it rides: deliver it on every tagged turn,
|
|
90
|
+
// even when the volatile text is byte-identical to the previous turn's.
|
|
91
|
+
const deliverVolatile = !resumed || previous?.volatile !== volatileFingerprint || mentionTurn;
|
|
92
|
+
const hadVolatile = typeof previous?.volatile === "string" && previous.volatile !== digest("");
|
|
93
|
+
if (!resumed || previous?.instructions !== fingerprint) {
|
|
94
|
+
// Instructions-only, and only after inject_items acknowledged: the
|
|
95
|
+
// volatile digest stays pending until the driver commits it after the
|
|
96
|
+
// provider accepts the turn, so a submission the provider rejected
|
|
97
|
+
// redelivers its context on the next attempt.
|
|
63
98
|
mkdirSync(directory, { recursive: true });
|
|
64
|
-
writeFileAtomic(path, fingerprint, { mode: 0o600 });
|
|
99
|
+
writeFileAtomic(path, JSON.stringify({ instructions: fingerprint }), { mode: 0o600 });
|
|
65
100
|
}
|
|
101
|
+
const commitVolatile = previous?.instructions === fingerprint && previous?.volatile === volatileFingerprint
|
|
102
|
+
? null
|
|
103
|
+
: () => {
|
|
104
|
+
mkdirSync(directory, { recursive: true });
|
|
105
|
+
writeFileAtomic(path, JSON.stringify({ instructions: fingerprint, volatile: volatileFingerprint }), { mode: 0o600 });
|
|
106
|
+
};
|
|
107
|
+
return { deliverVolatile, hadVolatile, commitVolatile };
|
|
66
108
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// MCP server names the user already declared in their own Codex config.
|
|
2
|
+
//
|
|
3
|
+
// Custom MCP servers reach codex as `-c mcp_servers.<name>.…` overrides, and
|
|
4
|
+
// codex merges an override into any same-named table in config.toml. A stdio
|
|
5
|
+
// command laid over a remote `url` entry is "invalid configuration": codex
|
|
6
|
+
// falls back to defaults, config/read fails, and the whole turn dies before
|
|
7
|
+
// the model is even asked. Mounting a colliding server under a name of its
|
|
8
|
+
// own keeps both definitions usable.
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
const HEADER = /^[ \t]*\[[ \t]*mcp_servers[ \t]*\.[ \t]*(?:"((?:[^"\\]|\\.)*)"|'([^']*)'|([A-Za-z0-9_-]+))[ \t]*[.\]]/gm;
|
|
13
|
+
/** Names declared as `[mcp_servers.<name>]` (or a sub-table of one). Inline
|
|
14
|
+
* tables are not scanned; a collision there still surfaces as codex's own
|
|
15
|
+
* configuration error. */
|
|
16
|
+
export function mcpServerNamesInToml(toml) {
|
|
17
|
+
const names = new Set();
|
|
18
|
+
for (const match of toml.matchAll(HEADER)) {
|
|
19
|
+
const name = match[1] !== undefined ? match[1].replace(/\\(.)/g, "$1") : match[2] ?? match[3];
|
|
20
|
+
if (name)
|
|
21
|
+
names.add(name);
|
|
22
|
+
}
|
|
23
|
+
return names;
|
|
24
|
+
}
|
|
25
|
+
/** Server names in the config.toml of the Codex home this child will use. */
|
|
26
|
+
export function codexConfigMcpServerNames(env) {
|
|
27
|
+
const codexHome = env.CODEX_HOME || join(env.HOME || env.USERPROFILE || homedir(), ".codex");
|
|
28
|
+
try {
|
|
29
|
+
return mcpServerNamesInToml(readFileSync(join(codexHome, "config.toml"), "utf8"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return new Set();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** The name to mount a custom server under: its own, unless the user's Codex
|
|
36
|
+
* config already has a server by that name. */
|
|
37
|
+
export function mountedMcpServerName(name, taken) {
|
|
38
|
+
if (!taken.has(name))
|
|
39
|
+
return name;
|
|
40
|
+
let candidate = `${name}_openmausbot`;
|
|
41
|
+
for (let i = 2; taken.has(candidate); i++)
|
|
42
|
+
candidate = `${name}_openmausbot${i}`;
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// OpenMausBot owns computer selection and mounts its scoped MCP servers.
|
|
2
|
+
// Keep the standalone Codex installation untouched: these overrides apply
|
|
3
|
+
// only to the child process, including resumed conversations and retries.
|
|
4
|
+
// The desktop-app browser is not available in Codex CLI. Enabling its tool
|
|
5
|
+
// without a desktop connection exposes an unusable route; native search is
|
|
6
|
+
// independent and must remain governed by the user's/provider's config.
|
|
7
|
+
// https://developers.openai.com/codex/browser
|
|
8
|
+
export function codexToolSurfaceArgs() {
|
|
9
|
+
return [
|
|
10
|
+
"-c", "features.browser_use=false",
|
|
11
|
+
"-c", "features.browser_use_external=false",
|
|
12
|
+
"-c", "features.computer_use=false",
|
|
13
|
+
// Disable the matching instructions as well as the native tool. Use an
|
|
14
|
+
// inline table: quoted dotted keys are interpreted literally by some CLIs.
|
|
15
|
+
"-c", 'plugins={ "browser@openai-bundled" = { enabled = false }, "computer-use@openai-bundled" = { enabled = false }, "unified-computer-use@openai-bundled" = { enabled = false } }',
|
|
16
|
+
// Native web search does not require the desktop browser connection.
|
|
17
|
+
// Preserve the user's/provider's search mode instead of disabling it for
|
|
18
|
+
// every OpenMausBot process after a failure in one search route.
|
|
19
|
+
];
|
|
20
|
+
}
|