openmausbot 0.1.84 → 0.1.86
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/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-DpUZNnFD.js +1 -0
- package/dist/assets/arc-DW7mrI4p.js +1 -0
- package/dist/assets/architectureDiagram-NJMV4G6O-CyeZ5WLZ.js +36 -0
- package/dist/assets/blockDiagram-BEXU5L5S-CBVaNjO7.js +140 -0
- package/dist/assets/c4Diagram-YGBWAQC7-sf8d41uT.js +38 -0
- package/dist/assets/channel-BXFkgus9.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-BOGvy7Y_.js +1 -0
- package/dist/assets/chunk-3FUC2YCW-BiF9zyiz.js +2 -0
- package/dist/assets/chunk-5VM5RSS4-EFuZ1SBZ.js +15 -0
- package/dist/assets/chunk-GTNCS2PH-pJLNSAT7.js +88 -0
- package/dist/assets/chunk-GWA4HPMP-Cb-3_3IT.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-DAf5aogn.js +1 -0
- package/dist/assets/chunk-NETBCI7D-DR3kiwZz.js +1 -0
- package/dist/assets/chunk-WEXAMYUT-BmU8PeyM.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-Buh8g8OT.js +1 -0
- package/dist/assets/classDiagram-v2-NBCMYWYE-BurfmMWM.js +217 -0
- package/dist/assets/cose-bilkent-JH36ORCC-KkGZUEkU.js +1 -0
- package/dist/assets/cynefin-OW5HDTMX-C3PpV96d.js +166 -0
- package/dist/assets/cynefinDiagram-VND7K2PF-Dt8JwJaI.js +62 -0
- package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
- package/dist/assets/dagre-6A5THRUB-DkjN8266.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-22UHCM2B-DUAJS0JV.js +200 -0
- package/dist/assets/diagram-3UASUU5V-Dw4KhF1a.js +24 -0
- package/dist/assets/diagram-ATOU4E4O-Dd1gmQqf.js +3 -0
- package/dist/assets/diagram-CDSNMT55-CjwGs0Yw.js +30 -0
- package/dist/assets/diagram-MLGK6HIB-BL-69Zrq.js +24 -0
- package/dist/assets/diagram-MPIPVDR6-5Zi2N7wo.js +41 -0
- package/dist/assets/ebnfDiagram-ZINNZB2B-D9NekWUX.js +1 -0
- package/dist/assets/elk-276RUBZZ-CkaSQjxw.js +27 -0
- package/dist/assets/erDiagram-OPXOYQCR-CdAVBV8-.js +99 -0
- package/dist/assets/flowDiagram-KWPJA3E3-Bm_lWWH7.js +213 -0
- package/dist/assets/ganttDiagram-FUAMR5RP-FZ8ejYWJ.js +292 -0
- package/dist/assets/gitGraphDiagram-X574FWY7-Dlv9rizZ.js +106 -0
- package/dist/assets/graph-D8UmR1vK.js +1 -0
- package/dist/assets/index-BGgqlWPJ.css +1 -0
- package/dist/assets/index-BtHo8ZKl.js +577 -0
- package/dist/assets/{index-DNa2umw-.js → index-h9FFNwTu.js} +1 -1
- package/dist/assets/infoDiagram-VRGFBTTK-BdQ_gW5B.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-OU5B5YK6-EwssEp2X.js +70 -0
- package/dist/assets/journeyDiagram-ZHPQQLJL-BX1G1vOd.js +139 -0
- package/dist/assets/kanban-definition-PNTS6WVX-BB4kAsyC.js +89 -0
- package/dist/assets/linear-3su2TG93.js +1 -0
- package/dist/assets/mermaid.core-CDD1E0s9.js +311 -0
- package/dist/assets/mindmap-definition-NLK3R4M7-Ba50MsNK.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-GJSIUBJH-DZLkkZlM.js +1 -0
- package/dist/assets/pieDiagram-5QR66LMP-DaTz77Uq.js +39 -0
- package/dist/assets/quadrantDiagram-O4NWA36T-CpaiidpR.js +7 -0
- package/dist/assets/railroadDiagram-XR7U4H2S-mN4JrMeI.js +1 -0
- package/dist/assets/requirementDiagram-PLB6GJNP-BSNDRpg7.js +84 -0
- package/dist/assets/sankeyDiagram-IPEJSGJF-FngzGNug.js +40 -0
- package/dist/assets/sequenceDiagram-PO4LG4MO-CM4pCC3L.js +169 -0
- package/dist/assets/sizeCapture-INFHLROL-CRICHXkw.js +1 -0
- package/dist/assets/stateDiagram-v2-GCMORJYK-oyTWpxH2.js +291 -0
- package/dist/assets/swimlanes-2SLR337P-D9h9mPBq.js +1 -0
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +8 -0
- package/dist/assets/timeline-definition-EJHVYXUP-BJUlN9bW.js +120 -0
- package/dist/assets/usecaseDiagram-POWQR4AR-3uBNkbAb.js +370 -0
- package/dist/assets/vennDiagram-UO4OBE2U-D2odalzT.js +34 -0
- package/dist/assets/wardleyDiagram-VNRHLVJA-DHgFaB9S.js +78 -0
- package/dist/assets/xychartDiagram-PMCCYNJV-w5GYM2h0.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/companion/src/routes.js +1 -0
- package/dist-server/container-mcp.js +231 -17
- package/dist-server/drivers/agents-proxy.js +16097 -1411
- package/dist-server/enterprise/LICENSE +32 -0
- package/dist-server/enterprise/server/index.js +6 -5
- package/dist-server/hooks/omb-hook.js +74 -0
- package/dist-server/index.js +12495 -7584
- package/dist-server/local-computer-proxy.js +83 -1
- package/dist-server/local-computer.js +14 -2
- package/dist-server/mcp-server.js +3 -1
- package/dist-server/openmausbot.js +5458 -4587
- package/dist-server/pair-cli.js +5458 -4587
- package/dist-server/proxy-paths.js +1 -0
- package/dist-server/server/admin-activity.js +410 -0
- package/dist-server/server/agent-tool-policy.js +1 -0
- package/dist-server/server/bot-package.js +2 -0
- package/dist-server/server/bot-visibility.js +462 -0
- package/dist-server/server/box.js +25 -5
- package/dist-server/server/browser-engine.js +87 -10
- package/dist-server/server/browser-live.js +7 -5
- package/dist-server/server/browser-runtime.js +66 -11
- package/dist-server/server/channel-queue.js +2 -0
- package/dist-server/server/checkpoints.js +62 -18
- package/dist-server/server/cli-prompts.js +26 -2
- package/dist-server/server/cli.js +79 -10
- package/dist-server/server/commands.js +27 -0
- package/dist-server/server/compaction-summary.js +78 -0
- package/dist-server/server/composio.js +49 -11
- package/dist-server/server/config.js +137 -14
- package/dist-server/server/context-budget.js +29 -0
- package/dist-server/server/context-rebuild.js +72 -0
- package/dist-server/server/decision-log.js +209 -48
- package/dist-server/server/default-model-selection.js +20 -3
- package/dist-server/server/delegations.js +11 -9
- package/dist-server/server/digest.js +130 -0
- package/dist-server/server/drivers/acp/core.js +637 -238
- package/dist-server/server/drivers/acp/grok.js +4 -3
- package/dist-server/server/drivers/agents-call.js +1123 -0
- package/dist-server/server/drivers/agents-catalog.js +659 -0
- package/dist-server/server/drivers/agents-client.js +28 -0
- package/dist-server/server/drivers/agents-proxy.js +18 -1683
- package/dist-server/server/drivers/agents-result.js +23 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +17 -2
- package/dist-server/server/drivers/claude.js +83 -16
- package/dist-server/server/drivers/codex-mcp-names.js +44 -0
- package/dist-server/server/drivers/codex.js +32 -4
- package/dist-server/server/drivers/grok.js +2 -1
- package/dist-server/server/drivers/openai-chat.js +66 -45
- package/dist-server/server/drivers/openai-compat.js +16 -8
- package/dist-server/server/enterprise.js +97 -16
- package/dist-server/server/external-runtime.js +83 -0
- package/dist-server/server/guarded-requests.js +60 -0
- package/dist-server/server/harness/http.js +61 -0
- package/dist-server/server/hooks/omb-hook.js +103 -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 +2810 -678
- package/dist-server/server/local-computer.js +21 -2
- 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 +10 -2
- package/dist-server/server/mcp-tool-images.js +37 -0
- package/dist-server/server/mcp-tool-schema.js +108 -0
- package/dist-server/server/message-db.js +108 -21
- package/dist-server/server/message-file.js +3 -2
- package/dist-server/server/model-context-window.js +7 -0
- package/dist-server/server/model-prices.js +121 -0
- package/dist-server/server/notify.js +10 -1
- package/dist-server/server/package-export.js +1 -0
- package/dist-server/server/peer-roster.js +17 -4
- package/dist-server/server/provider-icon.js +20 -0
- package/dist-server/server/proxy-paths.js +1 -0
- package/dist-server/server/recent-work.js +15 -0
- package/dist-server/server/request-auth.js +97 -0
- package/dist-server/server/room-handoffs.js +41 -13
- 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 +39 -1
- package/dist-server/server/routines.js +65 -9
- package/dist-server/server/shared-computer-control.js +31 -4
- package/dist-server/server/spend.js +111 -16
- package/dist-server/server/steer-queue.js +12 -1
- package/dist-server/server/store.js +384 -33
- package/dist-server/server/system-prompt.js +4 -4
- package/dist-server/server/team-backup.js +19 -2
- package/dist-server/server/thread-starters.js +53 -0
- package/dist-server/server/tool-results.js +72 -0
- package/dist-server/server/tts/grok.js +74 -0
- package/dist-server/server/tts/index.js +19 -1
- package/dist-server/server/usage-ledger.js +31 -8
- package/dist-server/server/webhooks.js +28 -0
- package/dist-server/server/workspace-backup-policy.js +9 -2
- package/dist-server/server/workspace-backup.js +7 -4
- package/dist-server/shared/approval-mode.js +7 -1
- package/dist-server/shared/digest.js +1 -0
- package/dist-server/shared/markdown-windows-paths.js +36 -0
- package/dist-server/shared/mention-boundary.js +15 -0
- package/dist-server/shared/provider-icon.js +110 -0
- package/dist-server/shared/routines.js +7 -1
- package/dist-server/shared/team-backup.js +5 -0
- package/dist-server/src/lib/composer-attachments.js +16 -0
- package/dist-server/vps-container-mcp.js +236 -22
- package/enterprise/README.md +33 -12
- package/enterprise/server/index.js +6 -5
- package/package.json +1 -1
- package/dist/assets/index-BbU5REzd.js +0 -310
- package/dist/assets/index-CLGfYlx_.css +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { redactSecretsInText } from "../../shared/redact.js";
|
|
2
|
+
import { TOOL_RESULT_MAX_CHARS, TOOL_RESULT_PREVIEW_CHARS, toolResultPrefix } from "../tool-results.js";
|
|
3
|
+
/** The operation already happened. Saving overflow must never retry it or
|
|
4
|
+
* turn a successful operation into a failed MCP call. Only cache I/O is timed. */
|
|
5
|
+
export async function boundedAgentResult(text, save) {
|
|
6
|
+
if (text.length <= 24_000)
|
|
7
|
+
return text;
|
|
8
|
+
const redacted = redactSecretsInText(text);
|
|
9
|
+
const prefix = toolResultPrefix(redacted, TOOL_RESULT_PREVIEW_CHARS);
|
|
10
|
+
const retained = toolResultPrefix(redacted, TOOL_RESULT_MAX_CHARS);
|
|
11
|
+
const truncated = retained.length < redacted.length;
|
|
12
|
+
try {
|
|
13
|
+
const saved = await save(retained, truncated);
|
|
14
|
+
if (!saved || typeof saved.id !== "string" || !/^r-[0-9a-f-]{36}$/.test(saved.id))
|
|
15
|
+
throw new Error("Invalid saved result");
|
|
16
|
+
return `${prefix}\n\n[Large tool result: showing the first ${prefix.length} characters. ${truncated || saved.truncated
|
|
17
|
+
? "Only a bounded portion was retained; the remaining tail was omitted."
|
|
18
|
+
: "The remaining redacted result is temporarily saved."} If a missing detail is needed, call tool_result_read with id "${saved.id}" and offset ${prefix.length}. Saved results expire after one hour, on app restart, or under cache pressure. Do not repeat an action just to retrieve its output.]`;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return `${prefix}\n\n[Large tool result: showing the first ${prefix.length} characters. The remaining output could not be saved. The original operation was not retried. Do not repeat an action just to retrieve its output.]`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
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";
|
|
7
9
|
/** The transport cannot safely continue this turn. A dispatched operation may
|
|
8
10
|
* already have taken effect, so callers must not retry it through a new round. */
|
|
@@ -21,6 +23,14 @@ function object(value) {
|
|
|
21
23
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
22
24
|
}
|
|
23
25
|
function aborted() { return new Error("MCP operation cancelled"); }
|
|
26
|
+
/** These servers are a chat-runtime bot's tools, the counterpart of an engine
|
|
27
|
+
* CLI's children: the operator's control-plane secrets never ride along. What
|
|
28
|
+
* the server entry itself names is a deliberate grant and is applied last. */
|
|
29
|
+
export function chatMcpEnvironment(serverEnv, source = process.env) {
|
|
30
|
+
const env = { ...source, PATH: augmentedPath() };
|
|
31
|
+
stripControlPlaneEnv(env);
|
|
32
|
+
return { ...env, ...serverEnv };
|
|
33
|
+
}
|
|
24
34
|
class ChatMcpClient {
|
|
25
35
|
child;
|
|
26
36
|
buffer = "";
|
|
@@ -31,8 +41,11 @@ class ChatMcpClient {
|
|
|
31
41
|
pending = new Map();
|
|
32
42
|
constructor(server) {
|
|
33
43
|
try {
|
|
44
|
+
// The desktop shell inherits Finder's bare PATH, where `npx`-style
|
|
45
|
+
// servers cannot find `node` and exit at once. Widen it the way the
|
|
46
|
+
// Claude and Codex drivers do; a PATH the user set on the server wins.
|
|
34
47
|
this.child = spawnCli(server.command, server.args, {
|
|
35
|
-
stdio: ["pipe", "pipe", "pipe"], env:
|
|
48
|
+
stdio: ["pipe", "pipe", "pipe"], env: chatMcpEnvironment(server.env),
|
|
36
49
|
});
|
|
37
50
|
}
|
|
38
51
|
catch {
|
|
@@ -64,7 +77,9 @@ class ChatMcpClient {
|
|
|
64
77
|
entry.reject(new Error("MCP session closed"));
|
|
65
78
|
this.pending.clear();
|
|
66
79
|
this.buffer = "";
|
|
67
|
-
|
|
80
|
+
// Confirm within the codebase-default grace: Windows reaps the tree via
|
|
81
|
+
// taskkill /T, which can exceed shorter budgets on a loaded machine.
|
|
82
|
+
this.closing = killCliTree(this.child, 5_000).then((stopped) => {
|
|
68
83
|
if (!stopped)
|
|
69
84
|
throw new Error("MCP server shutdown could not be confirmed; execution outcome may be uncertain");
|
|
70
85
|
});
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
// - the bot's cloud computer (box.ascii.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
|
-
import { chmodSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { createServer as createNetServer } from "node:net";
|
|
14
14
|
import { homedir, tmpdir } from "node:os";
|
|
15
15
|
import { join, dirname, isAbsolute, normalize } from "node:path";
|
|
16
16
|
import { DATA_DIR, stripWorkspaceCredentialEnv } from "../config.js";
|
|
17
|
+
import { writeFileAtomic } from "../atomic.js";
|
|
17
18
|
import { augmentedPath } from "../env-path.js";
|
|
18
19
|
import { brokerSocketPath, describeSpawnFailure, execCli, killCliTree, spawnCli } from "../procs.js";
|
|
19
20
|
import { classifyResumeFailure, mayReplay, recoveryPromptFor } from "../resume-recovery.js";
|
|
@@ -25,6 +26,7 @@ import { classifyError, computeBackoff, interruptibleDelay, RETRY_MAX_ATTEMPTS }
|
|
|
25
26
|
import { applyClaudeInject, decodeInjectId, mergeLocalInject, probeLocalInjects, resolveInjectId, } from "./local-inject.js";
|
|
26
27
|
import { appendNative } from "./native.js";
|
|
27
28
|
import { SPAWNED_PROXIES } from "../proxy-paths.js";
|
|
29
|
+
import { extractMcpImages } from "../mcp-tool-images.js";
|
|
28
30
|
import { ASK_USER_QUESTION_TOOL, askQuestionSummary, parseAskQuestions, parseChoices, questionChoices, } from "../../shared/ask-question.js";
|
|
29
31
|
/** Whether `claude` has been signed in.
|
|
30
32
|
*
|
|
@@ -320,6 +322,7 @@ export const STATIC_CLAUDE_MODELS = {
|
|
|
320
322
|
options: [
|
|
321
323
|
{ id: "claude-fable-5-1", label: "Claude Fable 5.1" },
|
|
322
324
|
{ id: "claude-fable-5", label: "Claude Fable 5" },
|
|
325
|
+
{ id: "claude-opus-5-5", label: "Claude Opus 5.5", contextWindow: 1_000_000 },
|
|
323
326
|
{ id: "claude-opus-5", label: "Claude Opus 5" },
|
|
324
327
|
{ id: "claude-sonnet-5", label: "Claude Sonnet 5" },
|
|
325
328
|
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5" },
|
|
@@ -392,6 +395,7 @@ export function readClaudeModelCatalog(env = process.env) {
|
|
|
392
395
|
// far. See server/proxy-paths.ts.
|
|
393
396
|
const PERM_PROXY_PATH = SPAWNED_PROXIES.permission;
|
|
394
397
|
const DWEB_PROXY_PATH = SPAWNED_PROXIES.dweb;
|
|
398
|
+
const HOOK_HELPER_PATH = SPAWNED_PROXIES.hook;
|
|
395
399
|
// in the packaged app process.execPath is the Electron binary — this env
|
|
396
400
|
// makes it behave as plain node for the spawned MCP proxies (harmless in dev)
|
|
397
401
|
const NODE_ENV_FLAG = { ELECTRON_RUN_AS_NODE: "1" };
|
|
@@ -430,6 +434,26 @@ function askSummary(ask) {
|
|
|
430
434
|
return askQuestionSummary(questions).slice(0, 300);
|
|
431
435
|
return askInputSummary(ask.input) ?? ask.tool ?? "tool";
|
|
432
436
|
}
|
|
437
|
+
/** Where the hook helper reads this thread's current turn token. Stable per
|
|
438
|
+
* thread (so the CLI's environment can name it once) and private. */
|
|
439
|
+
export function hookTokenFile(threadId, botId) {
|
|
440
|
+
const digest = createHash("sha256").update(`${botId ?? ""}\0${threadId}`).digest("hex").slice(0, 24);
|
|
441
|
+
return join(DATA_DIR, "hook-tokens", `${digest}.token`);
|
|
442
|
+
}
|
|
443
|
+
/** The `hooks` block for the private --settings file: one command for each
|
|
444
|
+
* event the harness observes. Claude Code runs it with the event JSON on
|
|
445
|
+
* stdin and applies any hookSpecificOutput it prints. The command string is
|
|
446
|
+
* a shell line, so both paths are quoted (this repo's own path has a space). */
|
|
447
|
+
export function claudeHookSettings(helperPath) {
|
|
448
|
+
// JSON quoting is not shell quoting: $(), backticks and $names still
|
|
449
|
+
// expand inside double quotes on POSIX. Windows paths come through env
|
|
450
|
+
// variables so their backslashes are not JSON-escaped into the command.
|
|
451
|
+
const command = process.platform === "win32"
|
|
452
|
+
? '"%OMB_HOOK_NODE%" "%OMB_HOOK_HELPER%"'
|
|
453
|
+
: [process.execPath, helperPath].map(path => `'${path.replace(/'/g, "'\\''")}'`).join(" ");
|
|
454
|
+
const entry = [{ matcher: "", hooks: [{ type: "command", command, timeout: 5 }] }];
|
|
455
|
+
return { PostToolUse: entry, PreCompact: entry, SessionStart: entry, Stop: entry };
|
|
456
|
+
}
|
|
433
457
|
export function permissionSocketPath(threadId, botId) {
|
|
434
458
|
// A readable prefix alone is not unique: ids that agree on their first
|
|
435
459
|
// characters ("t-perm-dup-1", "t-perm-dup-2") would share a socket. POSIX
|
|
@@ -701,10 +725,13 @@ function decodeConfig(raw) {
|
|
|
701
725
|
const configDir = typeof o.configDir === "string" ? o.configDir.trim() : undefined;
|
|
702
726
|
if (configDir)
|
|
703
727
|
resolveClaudeConfigDir(configDir);
|
|
728
|
+
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))))
|
|
729
|
+
throw new Error("Invalid hosted Claude models.");
|
|
704
730
|
return {
|
|
705
731
|
cli: typeof o.cli === "string" ? o.cli : "claude",
|
|
706
732
|
...(configDir ? { configDir } : {}),
|
|
707
733
|
...(o.managed === true ? { managed: true } : {}),
|
|
734
|
+
...(o.managedModels ? { managedModels: o.managedModels } : {}),
|
|
708
735
|
permissionMode: mode ?? "acceptEdits",
|
|
709
736
|
...(tools !== undefined ? { tools } : {}),
|
|
710
737
|
...(disallowedTools !== undefined ? { disallowedTools } : {}),
|
|
@@ -798,7 +825,7 @@ export const ClaudeDriver = {
|
|
|
798
825
|
if (inheritsUserConfig(catalogEnv)) {
|
|
799
826
|
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`);
|
|
800
827
|
}
|
|
801
|
-
let models = STATIC_CLAUDE_MODELS;
|
|
828
|
+
let models = config.managedModels ? { default: config.managedModels[0], options: config.managedModels.map(id => ({ id, label: id })) } : STATIC_CLAUDE_MODELS;
|
|
802
829
|
const refreshModels = async () => {
|
|
803
830
|
if (config.managed)
|
|
804
831
|
return;
|
|
@@ -909,6 +936,8 @@ export const ClaudeDriver = {
|
|
|
909
936
|
// is a fresh sendTurn, and the attempt cap must survive across launches
|
|
910
937
|
const retryState = new Map();
|
|
911
938
|
const sendTurn = async (turn, logicalTurnId) => {
|
|
939
|
+
if (config.managedModels && (!turn.model || !config.managedModels.includes(turn.model)))
|
|
940
|
+
throw new Error("This model is not assigned to this workspace.");
|
|
912
941
|
if (config.managed && (!turn.model || turn.model.includes("::") || !config.configDir ||
|
|
913
942
|
!input.environment.ANTHROPIC_API_KEY || !input.environment.ANTHROPIC_BASE_URL)) {
|
|
914
943
|
throw new Error("Company model access is unavailable. Reconnect your organization; personal billing will not be used.");
|
|
@@ -953,7 +982,7 @@ export const ClaudeDriver = {
|
|
|
953
982
|
// a retry relaunches the whole CLI; the backoff is scaled down in tests
|
|
954
983
|
// so a fake's transient failures don't stall real seconds
|
|
955
984
|
const retryScale = Number(process.env.FAKE_CLAUDE_RETRY_SCALE ?? "1");
|
|
956
|
-
const sessionId = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
985
|
+
const sessionId = !turn.sessionReset && typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
957
986
|
const newSessionId = sessionId ? null : newId();
|
|
958
987
|
const args = [
|
|
959
988
|
"-p",
|
|
@@ -1140,7 +1169,28 @@ export const ClaudeDriver = {
|
|
|
1140
1169
|
const env = environment(turnModel);
|
|
1141
1170
|
const authSettings = isolated && !injected.injected
|
|
1142
1171
|
? readClaudeAuthSettings(env, input.environment) : {};
|
|
1143
|
-
|
|
1172
|
+
// Harness hooks (item 0.2): one helper command for the events the
|
|
1173
|
+
// harness observes. The helper reads its bearer from a per-thread file
|
|
1174
|
+
// the harness rewrites every turn, so a long-lived CLI process never
|
|
1175
|
+
// presents a stale token. Registered through the same private
|
|
1176
|
+
// --settings file as the auth override; both are 0600 and per launch.
|
|
1177
|
+
const hooks = turn.integrations?.hooks;
|
|
1178
|
+
const hookTokenPath = hooks ? hookTokenFile(threadId, botId) : null;
|
|
1179
|
+
if (hooks && hookTokenPath) {
|
|
1180
|
+
mkdirSync(dirname(hookTokenPath), { recursive: true, mode: 0o700 });
|
|
1181
|
+
writeFileAtomic(hookTokenPath, hooks.token, { mode: 0o600 });
|
|
1182
|
+
env.OMB_HOOK_URL = hooks.url;
|
|
1183
|
+
env.OMB_HOOK_TOKEN_FILE = hookTokenPath;
|
|
1184
|
+
env.OMB_HOOK_NODE = process.execPath;
|
|
1185
|
+
env.OMB_HOOK_HELPER = HOOK_HELPER_PATH;
|
|
1186
|
+
// in the packaged app process.execPath is Electron — run the helper as node
|
|
1187
|
+
if (process.versions.electron)
|
|
1188
|
+
env.ELECTRON_RUN_AS_NODE = "1";
|
|
1189
|
+
}
|
|
1190
|
+
const settings = { ...authSettings };
|
|
1191
|
+
if (hooks)
|
|
1192
|
+
settings.hooks = claudeHookSettings(HOOK_HELPER_PATH);
|
|
1193
|
+
const authSettingsPath = mcpConfigPath && Object.keys(settings).length
|
|
1144
1194
|
? join(dirname(mcpConfigPath), "auth-settings.json") : null;
|
|
1145
1195
|
if (authSettingsPath)
|
|
1146
1196
|
args.push("--settings", authSettingsPath);
|
|
@@ -1163,6 +1213,8 @@ export const ClaudeDriver = {
|
|
|
1163
1213
|
model: injected.model ?? null,
|
|
1164
1214
|
base: env.ANTHROPIC_BASE_URL ?? null,
|
|
1165
1215
|
configDir: env.CLAUDE_CONFIG_DIR ?? null,
|
|
1216
|
+
// hooks on/off changes the settings file the process was launched with
|
|
1217
|
+
hooks: Boolean(hooks),
|
|
1166
1218
|
// Rotating an account's key/helper must not reuse the old process.
|
|
1167
1219
|
auth: createHash("sha256").update(JSON.stringify({
|
|
1168
1220
|
settings: authSettings,
|
|
@@ -1170,14 +1222,16 @@ export const ClaudeDriver = {
|
|
|
1170
1222
|
})).digest("hex"),
|
|
1171
1223
|
});
|
|
1172
1224
|
// Reuse the live process when it is idle, unchanged, and is the session
|
|
1173
|
-
// the harness wants resumed.
|
|
1174
|
-
//
|
|
1225
|
+
// the harness wants resumed. Clearing a cursor alone does not opt out
|
|
1226
|
+
// of legacy reuse: an explicit rebuild must discard the idle context.
|
|
1175
1227
|
const live = sessions.get(threadId);
|
|
1176
|
-
if (live && !live.turn && !live.closing && live.child.exitCode === null && live.argsKey === argsKey && (!sessionId || sessionId === live.sessionId)) {
|
|
1228
|
+
if (!turn.sessionReset && live && !live.turn && !live.closing && live.child.exitCode === null && live.argsKey === argsKey && (!sessionId || sessionId === live.sessionId)) {
|
|
1177
1229
|
if (live.idleTimer)
|
|
1178
1230
|
clearTimeout(live.idleTimer);
|
|
1179
1231
|
live.turn = { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false };
|
|
1180
1232
|
active.set(threadId, { stop: () => {
|
|
1233
|
+
if (live.turn)
|
|
1234
|
+
live.turn.stopRequested = true;
|
|
1181
1235
|
closeSession(threadId, "interrupted");
|
|
1182
1236
|
retry.cancelled = true;
|
|
1183
1237
|
retryAbort.abort();
|
|
@@ -1189,8 +1243,9 @@ export const ClaudeDriver = {
|
|
|
1189
1243
|
? promptMsg
|
|
1190
1244
|
: claudeUserMessage(withVolatileNote(turn.text, volatile), turn.images);
|
|
1191
1245
|
live.volatile = volatile;
|
|
1246
|
+
const running = live.turn;
|
|
1192
1247
|
const written = await writeUser(live, threadId, message);
|
|
1193
|
-
if (!written) {
|
|
1248
|
+
if (!written && !running?.stopRequested) {
|
|
1194
1249
|
active.delete(threadId);
|
|
1195
1250
|
live.turn = null;
|
|
1196
1251
|
closeSession(threadId, "stdin write failed");
|
|
@@ -1213,7 +1268,7 @@ export const ClaudeDriver = {
|
|
|
1213
1268
|
return { turnId };
|
|
1214
1269
|
}
|
|
1215
1270
|
if (live)
|
|
1216
|
-
closeSession(threadId, "spawn contract changed");
|
|
1271
|
+
closeSession(threadId, turn.sessionReset ? "context reset" : "spawn contract changed");
|
|
1217
1272
|
// Until sessions.set() below, this turn owns every launch resource.
|
|
1218
1273
|
// Any bind, private-config or synchronous spawn failure must release
|
|
1219
1274
|
// them here rather than leave a live listener or credential temp file.
|
|
@@ -1308,7 +1363,7 @@ export const ClaudeDriver = {
|
|
|
1308
1363
|
writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers }), { mode: 0o600 });
|
|
1309
1364
|
}
|
|
1310
1365
|
if (authSettingsPath) {
|
|
1311
|
-
writeFileSync(authSettingsPath, JSON.stringify(
|
|
1366
|
+
writeFileSync(authSettingsPath, JSON.stringify(settings), { mode: 0o600 });
|
|
1312
1367
|
}
|
|
1313
1368
|
if (sessionId)
|
|
1314
1369
|
args.push("--resume", sessionId);
|
|
@@ -1493,6 +1548,9 @@ export const ClaudeDriver = {
|
|
|
1493
1548
|
for (const b of Array.isArray(o.message?.content) ? o.message.content : []) {
|
|
1494
1549
|
if (b.type === "tool_result") {
|
|
1495
1550
|
emit({ ...base(threadId, currentTurnId()), type: "item.completed", itemType: "tool", itemId: b.tool_use_id, ok: !b.is_error, output: toolDetailPreview(b.content) });
|
|
1551
|
+
for (const img of extractMcpImages(b.content)) {
|
|
1552
|
+
emit({ ...base(threadId, currentTurnId()), type: "item.completed", itemType: "assistant_image", data: img.data });
|
|
1553
|
+
}
|
|
1496
1554
|
}
|
|
1497
1555
|
}
|
|
1498
1556
|
break;
|
|
@@ -1561,7 +1619,10 @@ export const ClaudeDriver = {
|
|
|
1561
1619
|
// a turn still running when the process died is a failed turn; a
|
|
1562
1620
|
// process that exited between turns (idle close, contract change)
|
|
1563
1621
|
// is just a session ending
|
|
1564
|
-
if (session.turn && !session.turn.settled) {
|
|
1622
|
+
if (session.turn?.stopRequested && !session.turn.settled) {
|
|
1623
|
+
settle(false, "interrupted");
|
|
1624
|
+
}
|
|
1625
|
+
else if (session.turn && !session.turn.settled) {
|
|
1565
1626
|
// A retained process may be running a later user turn. Its close
|
|
1566
1627
|
// handler must retry that request, not the process's first prompt.
|
|
1567
1628
|
const { turnId, input: turn, retryAbort } = session.turn;
|
|
@@ -1627,7 +1688,9 @@ export const ClaudeDriver = {
|
|
|
1627
1688
|
active.set(threadId, { stop: () => { retry.cancelled = true; retryAbort.abort(); }, turnId });
|
|
1628
1689
|
try {
|
|
1629
1690
|
const cursor = session.sessionId ?? sessionId ?? undefined;
|
|
1630
|
-
|
|
1691
|
+
// The reset was consumed by the initial launch. Retry the
|
|
1692
|
+
// new session, never the context that launch replaced.
|
|
1693
|
+
await sendTurn({ ...turn, sessionReset: false, resumeCursor: cursor }, turnId);
|
|
1631
1694
|
}
|
|
1632
1695
|
catch (e) {
|
|
1633
1696
|
if (active.get(threadId)?.turnId === turnId)
|
|
@@ -1743,6 +1806,8 @@ export const ClaudeDriver = {
|
|
|
1743
1806
|
void session.finishClose();
|
|
1744
1807
|
});
|
|
1745
1808
|
const stop = () => {
|
|
1809
|
+
if (session.turn)
|
|
1810
|
+
session.turn.stopRequested = true;
|
|
1746
1811
|
// taskkill is asynchronous on Windows. Retire steering and approvals
|
|
1747
1812
|
// now, before a still-connected child can submit more work.
|
|
1748
1813
|
closeSession(threadId, "interrupted");
|
|
@@ -1756,7 +1821,8 @@ export const ClaudeDriver = {
|
|
|
1756
1821
|
// stdin stays OPEN: that is what keeps the session alive for a
|
|
1757
1822
|
// mid-turn steer or the next turn; closeSession() ends it.
|
|
1758
1823
|
if (!(await writeUser(session, threadId, promptMsg))) {
|
|
1759
|
-
|
|
1824
|
+
if (!session.turn?.stopRequested)
|
|
1825
|
+
settle(false, "stdin_write_failed");
|
|
1760
1826
|
closeSession(threadId, "stdin write failed");
|
|
1761
1827
|
}
|
|
1762
1828
|
return { turnId };
|
|
@@ -1793,9 +1859,9 @@ export const ClaudeDriver = {
|
|
|
1793
1859
|
* `claude -p "prompt"` shape is not safe for review. No tools or MCP
|
|
1794
1860
|
* servers are mounted in this isolated process. */
|
|
1795
1861
|
const generateReview = (prompt, signal) => new Promise((resolve, reject) => {
|
|
1796
|
-
const child = spawnCli(config.cli, ["-p", "--model", "claude-haiku-4-5", "--output-format", "text"], {
|
|
1862
|
+
const child = spawnCli(config.cli, ["-p", "--model", config.managedModels?.[0] ?? "claude-haiku-4-5", "--output-format", "text"], {
|
|
1797
1863
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1798
|
-
env: environment("claude-haiku-4-5"),
|
|
1864
|
+
env: environment(config.managedModels?.[0] ?? "claude-haiku-4-5"),
|
|
1799
1865
|
});
|
|
1800
1866
|
let stdout = "";
|
|
1801
1867
|
let stderr = "";
|
|
@@ -1887,6 +1953,7 @@ export const ClaudeDriver = {
|
|
|
1887
1953
|
// Harness turns reassert a per-bot mode and restore the broker even
|
|
1888
1954
|
// when an old instance was configured with bypassPermissions.
|
|
1889
1955
|
localComputerMcp: true,
|
|
1956
|
+
hooks: true,
|
|
1890
1957
|
},
|
|
1891
1958
|
sendTurn,
|
|
1892
1959
|
steer,
|
|
@@ -1914,7 +1981,7 @@ export const ClaudeDriver = {
|
|
|
1914
1981
|
return () => listeners.delete(listener);
|
|
1915
1982
|
},
|
|
1916
1983
|
},
|
|
1917
|
-
generateText: (prompt) => generateReview(prompt),
|
|
1984
|
+
generateText: (prompt, options) => generateReview(prompt, options?.signal),
|
|
1918
1985
|
reviewPermission: generateReview,
|
|
1919
1986
|
dispose: async () => {
|
|
1920
1987
|
try {
|
|
@@ -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
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// and preserves that history or reports a failed resume.
|
|
12
12
|
import { existsSync } from "node:fs";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
|
+
import { codexConfigMcpServerNames, mountedMcpServerName } from "./codex-mcp-names.js";
|
|
14
15
|
import { stripWorkspaceCredentialEnv } from "../config.js";
|
|
15
16
|
import { describeSpawnFailure, execCli, killCliTree, spawnCli } from "../procs.js";
|
|
16
17
|
import { isHarnessOwnedMcpEnvName } from "../mcp-registry.js";
|
|
@@ -25,6 +26,7 @@ import { codexDeveloperInstructions, syncCodexInstructions } from "./codex-instr
|
|
|
25
26
|
import { CodexDeviceAuthController } from "./codex-device-auth.js";
|
|
26
27
|
import { codexAccountEmail } from "./codex-identity.js";
|
|
27
28
|
import { classifyResumeFailure, mayReplay, recoveryPromptFor } from "../resume-recovery.js";
|
|
29
|
+
import { extractMcpImages } from "../mcp-tool-images.js";
|
|
28
30
|
export { decodeCodexSelection, readCodexModelCatalog, STATIC_CODEX_MODELS } from "./codex-catalog.js";
|
|
29
31
|
const DRIVER_KIND = "codex";
|
|
30
32
|
const ASTRA_MODEL_ID = "gpt-6-astra";
|
|
@@ -127,6 +129,14 @@ export function managedCodexArgs(config) {
|
|
|
127
129
|
const QUESTION_TIMEOUT_NOTE = "No answer was given — use your best judgment.";
|
|
128
130
|
const DENY_TIMEOUT_NOTE = "OpenMausBot: nobody answered this permission request in time. Skip this action and finish what you can without it.";
|
|
129
131
|
const skippedSseServers = new Set();
|
|
132
|
+
const renamedMcpServers = new Set();
|
|
133
|
+
/** Logged once per name: the rename is deliberate, not a lost server. */
|
|
134
|
+
function noteRenamedMcpServer(name, mountName) {
|
|
135
|
+
if (renamedMcpServers.has(name))
|
|
136
|
+
return;
|
|
137
|
+
renamedMcpServers.add(name);
|
|
138
|
+
console.error(`codex: MCP server ${JSON.stringify(name)} is also declared in Codex's own config.toml — mounted as ${JSON.stringify(mountName)} for this bot so the two do not merge`);
|
|
139
|
+
}
|
|
130
140
|
function noteSkippedSseServer(name) {
|
|
131
141
|
if (skippedSseServers.has(name))
|
|
132
142
|
return;
|
|
@@ -592,6 +602,11 @@ export const CodexDriver = {
|
|
|
592
602
|
if (turn.integrations?.browser) {
|
|
593
603
|
mountMcpServer(appServerArgs, env, "browser", turn.integrations.browser);
|
|
594
604
|
}
|
|
605
|
+
// A custom server named like one in the user's own config.toml would
|
|
606
|
+
// be merged with it by the `-c` override — a stdio command over a
|
|
607
|
+
// remote url is "invalid configuration" and kills the turn before the
|
|
608
|
+
// model is asked. Such a server mounts under a name of its own.
|
|
609
|
+
const declaredInCodexConfig = codexConfigMcpServerNames(env);
|
|
595
610
|
for (const [name, server] of Object.entries(turn.integrations?.custom ?? {})) {
|
|
596
611
|
// codex speaks streamable HTTP to a remote server, not the older
|
|
597
612
|
// SSE transport: such an entry still reaches Claude bots, and is
|
|
@@ -600,7 +615,10 @@ export const CodexDriver = {
|
|
|
600
615
|
noteSkippedSseServer(name);
|
|
601
616
|
continue;
|
|
602
617
|
}
|
|
603
|
-
|
|
618
|
+
const mountName = mountedMcpServerName(name, declaredInCodexConfig);
|
|
619
|
+
if (mountName !== name)
|
|
620
|
+
noteRenamedMcpServer(name, mountName);
|
|
621
|
+
mountMcpServer(appServerArgs, env, mountName, server, false);
|
|
604
622
|
}
|
|
605
623
|
if (turn.integrations?.phone) {
|
|
606
624
|
const bridge = turn.integrations.phone;
|
|
@@ -1039,6 +1057,11 @@ export const CodexDriver = {
|
|
|
1039
1057
|
ok: item.status !== "failed" && item.status !== "declined",
|
|
1040
1058
|
output: toolDetailPreview(item.type === "commandExecution" ? { output: item.aggregatedOutput, exitCode: item.exitCode } : item.type === "mcpToolCall" ? item.error ?? item.result : item.type === "fileChange" ? item.changes : item.action),
|
|
1041
1059
|
});
|
|
1060
|
+
if (item.type === "mcpToolCall") {
|
|
1061
|
+
for (const img of extractMcpImages(item.result)) {
|
|
1062
|
+
emit({ ...base(threadId, turnId), type: "item.completed", itemType: "assistant_image", data: img.data });
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1042
1065
|
}
|
|
1043
1066
|
else if (item.type === "reasoning") {
|
|
1044
1067
|
emit({ ...base(threadId, turnId), type: "item.updated", itemType: "reasoning", tokens: null });
|
|
@@ -1282,10 +1305,15 @@ export const CodexDriver = {
|
|
|
1282
1305
|
});
|
|
1283
1306
|
effectiveConfig = configured?.config;
|
|
1284
1307
|
}
|
|
1285
|
-
catch {
|
|
1308
|
+
catch (error) {
|
|
1286
1309
|
// Do not expose a possibly secret-bearing native config error or
|
|
1287
|
-
// overwrite unknown instructions with an empty fallback.
|
|
1288
|
-
|
|
1310
|
+
// overwrite unknown instructions with an empty fallback. A config
|
|
1311
|
+
// codex itself rejected is the one case worth naming: the person
|
|
1312
|
+
// can act on it, and the message carries no credential.
|
|
1313
|
+
const invalid = error instanceof CodexRpcError && /^invalid configuration\b/i.test(error.message);
|
|
1314
|
+
throw new Error(invalid
|
|
1315
|
+
? "Codex rejected its configuration as invalid. Check ~/.codex/config.toml (or CODEX_HOME) for a broken entry, then retry."
|
|
1316
|
+
: "Could not read Codex configuration; cannot safely update bot instructions. Retry after checking Codex.");
|
|
1289
1317
|
}
|
|
1290
1318
|
const developerInstructions = codexDeveloperInstructions(effectiveConfig, turn.system ?? "");
|
|
1291
1319
|
let approvalParams;
|
|
@@ -2,8 +2,9 @@ import { createOpenAIChatRuntime } from "./openai-chat.js";
|
|
|
2
2
|
const DRIVER_KIND = "grok";
|
|
3
3
|
const DEFAULT_URL = "https://api.x.ai/v1";
|
|
4
4
|
const MODELS = {
|
|
5
|
-
default: "grok-4",
|
|
5
|
+
default: "grok-4.7",
|
|
6
6
|
options: [
|
|
7
|
+
{ id: "grok-4.7", label: "Grok 4.7", contextWindow: 500_000 },
|
|
7
8
|
{ id: "grok-4", label: "Grok 4" },
|
|
8
9
|
{ id: "grok-4-fast", label: "Grok 4 Fast" },
|
|
9
10
|
{ id: "grok-3-mini", label: "Grok 3 Mini" },
|
|
@@ -115,11 +115,67 @@ export function createOpenAIChatRuntime(options) {
|
|
|
115
115
|
const reader = response.body.getReader();
|
|
116
116
|
const decoder = new TextDecoder();
|
|
117
117
|
let buffer = "";
|
|
118
|
+
const consumeDataLine = (line, atEof = false) => {
|
|
119
|
+
if (!line.startsWith("data:"))
|
|
120
|
+
return false;
|
|
121
|
+
const data = line.slice(5).trim();
|
|
122
|
+
if (data === "[DONE]")
|
|
123
|
+
return true;
|
|
124
|
+
let chunk;
|
|
125
|
+
try {
|
|
126
|
+
chunk = JSON.parse(data);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// A tail left in the buffer when the socket closed is an INCOMPLETE
|
|
130
|
+
// frame, not a bad one: a stop, an abort or a dropped connection all
|
|
131
|
+
// end mid-frame. Only a properly newline-terminated frame that will
|
|
132
|
+
// not parse means the provider actually sent something malformed.
|
|
133
|
+
// Counting the tail here turned a stopped turn into a hard,
|
|
134
|
+
// non-retryable failure (`calls.finish(finishReason, malformedFrame)`).
|
|
135
|
+
if (!atEof)
|
|
136
|
+
malformedFrame = true;
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const chunkError = providerError(chunk);
|
|
140
|
+
if (chunkError)
|
|
141
|
+
throw new ChatProtocolError(`provider returned a streaming completion error: ${chunkError.slice(0, 200)}`);
|
|
142
|
+
const choice = chunk.choices?.find((row) => row.index === undefined || row.index === 0);
|
|
143
|
+
const delta = choice?.delta;
|
|
144
|
+
if (object(delta))
|
|
145
|
+
sawChoice = true;
|
|
146
|
+
if (delta?.function_call)
|
|
147
|
+
throw new ChatProtocolError("legacy function_call is unsupported; use structured tool_calls");
|
|
148
|
+
calls.add(delta?.tool_calls, true);
|
|
149
|
+
details.add(delta?.reasoning_details);
|
|
150
|
+
if (choice?.finish_reason)
|
|
151
|
+
finishReason = choice.finish_reason;
|
|
152
|
+
const reasoningPart = delta?.reasoning_content ?? delta?.reasoning;
|
|
153
|
+
if (typeof reasoningPart === "string")
|
|
154
|
+
protocolReasoning += reasoningPart;
|
|
155
|
+
const reasoningDelta = options.reasoning && typeof reasoningPart === "string"
|
|
156
|
+
? reasoningPart
|
|
157
|
+
: "";
|
|
158
|
+
const contentDelta = typeof delta?.content === "string" ? delta.content : "";
|
|
159
|
+
if (reasoningDelta) {
|
|
160
|
+
reasoning += reasoningDelta;
|
|
161
|
+
onDelta?.(reasoningDelta, "reasoning_text");
|
|
162
|
+
}
|
|
163
|
+
if (contentDelta) {
|
|
164
|
+
text += contentDelta;
|
|
165
|
+
onDelta?.(contentDelta, "assistant_text");
|
|
166
|
+
}
|
|
167
|
+
if (chunk.usage)
|
|
168
|
+
usage = usageFrom(chunk.usage);
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
118
171
|
try {
|
|
119
172
|
readLoop: for (;;) {
|
|
120
173
|
const { done, value } = await reader.read();
|
|
121
174
|
if (done) {
|
|
122
175
|
buffer += decoder.decode();
|
|
176
|
+
const line = buffer.trim();
|
|
177
|
+
if (line && line !== "data: [DONE]")
|
|
178
|
+
consumeDataLine(line, true);
|
|
123
179
|
// MiniMax's api.minimax.io/v1 closes the connection after the
|
|
124
180
|
// finish_reason chunk and never sends `[DONE]`.
|
|
125
181
|
if (buffer.trim() === "data: [DONE]" || finishReason)
|
|
@@ -146,49 +202,8 @@ export function createOpenAIChatRuntime(options) {
|
|
|
146
202
|
while ((newline = buffer.indexOf("\n")) !== -1) {
|
|
147
203
|
const line = buffer.slice(0, newline).trim();
|
|
148
204
|
buffer = buffer.slice(newline + 1);
|
|
149
|
-
if (
|
|
150
|
-
continue;
|
|
151
|
-
const data = line.slice(5).trim();
|
|
152
|
-
if (data === "[DONE]")
|
|
205
|
+
if (consumeDataLine(line))
|
|
153
206
|
break readLoop;
|
|
154
|
-
let chunk;
|
|
155
|
-
try {
|
|
156
|
-
chunk = JSON.parse(data);
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
malformedFrame = true;
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
const chunkError = providerError(chunk);
|
|
163
|
-
if (chunkError)
|
|
164
|
-
throw new ChatProtocolError(`provider returned a streaming completion error: ${chunkError.slice(0, 200)}`);
|
|
165
|
-
const choice = chunk.choices?.find((row) => row.index === undefined || row.index === 0);
|
|
166
|
-
const delta = choice?.delta;
|
|
167
|
-
if (object(delta))
|
|
168
|
-
sawChoice = true;
|
|
169
|
-
if (delta?.function_call)
|
|
170
|
-
throw new ChatProtocolError("legacy function_call is unsupported; use structured tool_calls");
|
|
171
|
-
calls.add(delta?.tool_calls, true);
|
|
172
|
-
details.add(delta?.reasoning_details);
|
|
173
|
-
if (choice?.finish_reason)
|
|
174
|
-
finishReason = choice.finish_reason;
|
|
175
|
-
const reasoningPart = delta?.reasoning_content ?? delta?.reasoning;
|
|
176
|
-
if (typeof reasoningPart === "string")
|
|
177
|
-
protocolReasoning += reasoningPart;
|
|
178
|
-
const reasoningDelta = options.reasoning && typeof reasoningPart === "string"
|
|
179
|
-
? reasoningPart
|
|
180
|
-
: "";
|
|
181
|
-
const contentDelta = typeof delta?.content === "string" ? delta.content : "";
|
|
182
|
-
if (reasoningDelta) {
|
|
183
|
-
reasoning += reasoningDelta;
|
|
184
|
-
onDelta?.(reasoningDelta, "reasoning_text");
|
|
185
|
-
}
|
|
186
|
-
if (contentDelta) {
|
|
187
|
-
text += contentDelta;
|
|
188
|
-
onDelta?.(contentDelta, "assistant_text");
|
|
189
|
-
}
|
|
190
|
-
if (chunk.usage)
|
|
191
|
-
usage = usageFrom(chunk.usage);
|
|
192
207
|
}
|
|
193
208
|
}
|
|
194
209
|
}
|
|
@@ -383,7 +398,13 @@ export function createOpenAIChatRuntime(options) {
|
|
|
383
398
|
throw new ChatProtocolError("tool arguments must be a JSON object");
|
|
384
399
|
tools.validate(call.function.name, args);
|
|
385
400
|
const inputPreview = preview(args);
|
|
386
|
-
|
|
401
|
+
// Full access is the person's explicit grant to answer every
|
|
402
|
+
// prompt. This runtime has no provider reviewer to hand it to,
|
|
403
|
+
// so it is honoured here: without it every single tool call on
|
|
404
|
+
// an OpenAI-compatible engine stops for a card, and a Chief's
|
|
405
|
+
// delegated Full access cannot help either.
|
|
406
|
+
const allowed = turn.approvalMode === "full"
|
|
407
|
+
|| await approval.ask(call.function.name, inputPreview ?? "This tool has no arguments.");
|
|
387
408
|
abort.signal.throwIfAborted();
|
|
388
409
|
emit({ ...base(turn.threadId, turnId), type: "item.started", itemType: "tool", itemId: call.id,
|
|
389
410
|
title: call.function.name, ...(inputPreview ? { input: inputPreview } : {}),
|
|
@@ -489,9 +510,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
489
510
|
return () => listeners.delete(listener);
|
|
490
511
|
},
|
|
491
512
|
},
|
|
492
|
-
generateText: async (prompt) => {
|
|
513
|
+
generateText: async (prompt, { signal } = {}) => {
|
|
493
514
|
const model = options.generateModel?.() ?? options.models().default;
|
|
494
|
-
const { text, reasoning, toolCalls } = await complete([{ role: "user", content: prompt }], model, false);
|
|
515
|
+
const { text, reasoning, toolCalls } = await complete([{ role: "user", content: prompt }], model, false, signal);
|
|
495
516
|
if (toolCalls.length)
|
|
496
517
|
throw new ChatProtocolError("provider returned tool calls to a text-only helper");
|
|
497
518
|
return text.trim() ? text : reasoning;
|