openmausbot 0.1.94 → 0.1.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{DesktopViewer-bxM7NV3N.js → DesktopViewer-DsnA23KM.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-BhMdJhRJ.js → abnfDiagram-O67JEVCF-Dkbd2hcj.js} +1 -1
- package/dist/assets/{arc-B5itKDcx.js → arc-Duj5Coy2.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-C5d7UY6U.js → architectureDiagram-NJMV4G6O-uYUsDawP.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-BrZIhMmu.js → blockDiagram-BEXU5L5S-CWDlTpXe.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-7I0ArfbD.js → c4Diagram-YGBWAQC7-B0t15PDc.js} +1 -1
- package/dist/assets/channel-BZxwC-Gw.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BnEsyN36.js → chunk-2Q5K7J3B-BJSCL-HV.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BXATQ52s.js → chunk-3FUC2YCW-D3_ILccd.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-B0BpTkEx.js → chunk-5VM5RSS4-D3dmwT_S.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BtLTz6FR.js → chunk-GTNCS2PH-Br73HFPD.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-D6gcZOYt.js → chunk-GWA4HPMP-uSsjxubC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DCLrosvf.js → chunk-JWPE2WC7-C40VWFyr.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-B9JsknV9.js → chunk-NETBCI7D-D6WNNYwU.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CsoTxw7z.js → chunk-WEXAMYUT-DOEhq64P.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-0F_frB0c.js → chunk-XXDRQBXY-BqJEi6Pi.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DkrpnflX.js → classDiagram-v2-NBCMYWYE-ByU_GnxT.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BNDwYBt9.js → cose-bilkent-JH36ORCC-D-Uglxqq.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-JUkNr1Mk.js → cynefin-OW5HDTMX-CZWO7aYj.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DKeh9N4M.js → cynefinDiagram-VND7K2PF-C70-w0la.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-ve-6T_wX.js → dagre-6A5THRUB-CnhjhExB.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BuJ2d8rT.js → diagram-22UHCM2B-bnjWCNb4.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BgOs_Ni-.js → diagram-3UASUU5V-A4uTWoFV.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-DMK6VmcP.js → diagram-ATOU4E4O-Zi6m3NUl.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DksASlcY.js → diagram-CDSNMT55-DtCcmd5k.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Bgn8I5k6.js → diagram-MLGK6HIB-DofuETS3.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-zK0jqO8N.js → diagram-MPIPVDR6-Dw2T9PuU.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-vKrbzTwr.js → ebnfDiagram-ZINNZB2B-DbrwPiem.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-Bzv2mPNf.js → elk-276RUBZZ-BvCt1dCb.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CvOyYuxu.js → erDiagram-OPXOYQCR-Y-QKqPnU.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CtbnDbc0.js → flowDiagram-KWPJA3E3-CDkL1B5w.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-c4dMRYyG.js → ganttDiagram-FUAMR5RP-CKV5D2F6.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-B34uaQho.js → gitGraphDiagram-X574FWY7-nL9iFTCv.js} +1 -1
- package/dist/assets/{index-CPiqc_DN.js → index-DreDnjOu.js} +1 -1
- package/dist/assets/index-Du7o6Dsf.js +610 -0
- package/dist/assets/index-KxG4bWSr.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-o64ma4CI.js → infoDiagram-VRGFBTTK-C7aHjzsf.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DV4C9BAm.js → ishikawaDiagram-OU5B5YK6-KUZsNBTh.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-Bf0AHk1H.js → journeyDiagram-ZHPQQLJL-CGUuodFl.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-B1UMSKmE.js → kanban-definition-PNTS6WVX-Cy6r868Z.js} +1 -1
- package/dist/assets/{linear-BpKhuyLm.js → linear-Bl669148.js} +1 -1
- package/dist/assets/{mermaid.core-B_BKNCw5.js → mermaid.core-86Xqz5Mr.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DeARruT1.js → mindmap-definition-NLK3R4M7-G4DzHLrd.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BOQEk_1D.js → pegDiagram-GJSIUBJH-K4eruzY0.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-EIfoiTZo.js → pieDiagram-5QR66LMP-DYdR9PPq.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-GgZwT-fi.js → quadrantDiagram-O4NWA36T-BCAh0nEy.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-D9rDQomj.js → railroadDiagram-XR7U4H2S-p0dGRc4g.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DAh1MIeo.js → requirementDiagram-PLB6GJNP-tLeBG0aL.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CPr6Uz64.js → sankeyDiagram-IPEJSGJF-QRuN16rV.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-BU6VIdcm.js → sequenceDiagram-PO4LG4MO-ueT1nVoa.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-qF2HO41r.js → sizeCapture-INFHLROL-wRS2zWg2.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DlJ8G4MD.js → stateDiagram-v2-GCMORJYK-CYzA_hE1.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DzYE8I54.js → swimlanes-2SLR337P-kDnv6LMc.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-B7wzEQ0A.js → timeline-definition-EJHVYXUP-D1y2UbbH.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-BvGgrxIP.js → usecaseDiagram-POWQR4AR-IqB4v9HX.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-CEECdgQg.js → vennDiagram-UO4OBE2U-BEi7KYVF.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-XQHA0P_P.js → wardleyDiagram-VNRHLVJA-Dkybo-Yf.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-B5mLcZI2.js → xychartDiagram-PMCCYNJV-BAPFJLpg.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +27 -13
- package/dist-server/companion/src/listener.js +39 -18
- package/dist-server/companion/src/routes.js +39 -0
- package/dist-server/container-mcp.js +16 -29
- package/dist-server/drivers/agents-proxy.js +161 -82
- package/dist-server/enterprise/server/index.js +2 -1
- package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +34 -19
- package/dist-server/index.js +4991 -4908
- package/dist-server/local-computer.js +2 -1
- package/dist-server/openmausbot.js +906 -1785
- package/dist-server/pair-cli.js +906 -1785
- package/dist-server/proxy-paths.js +2 -1
- package/dist-server/server/account-signin.js +0 -2
- package/dist-server/server/activity.js +201 -0
- package/dist-server/server/admin-activity.js +1 -4
- package/dist-server/server/auto-approve.js +25 -4
- package/dist-server/server/automatic-recovery.js +1 -1
- package/dist-server/server/boat-create-idempotency.js +0 -7
- package/dist-server/server/boat-delete-journal.js +0 -4
- package/dist-server/server/boat.js +4 -4
- package/dist-server/server/bot-package.js +1 -19
- package/dist-server/server/browser-runtime.js +1 -2
- package/dist-server/server/checkpoints.js +90 -148
- package/dist-server/server/chief-of-staff.js +9 -9
- package/dist-server/server/cli.js +33 -26
- package/dist-server/server/cloud-backend.js +0 -4
- package/dist-server/server/cloud-computer-tools.js +110 -0
- package/dist-server/server/cloud-home-start.js +12 -15
- package/dist-server/server/cloud-home.js +0 -5
- package/dist-server/server/cloud-lending.js +0 -3
- package/dist-server/server/cloud-move-http.js +44 -30
- package/dist-server/server/cloud-move.js +20 -18
- package/dist-server/server/composio.js +41 -5
- package/dist-server/server/computer-control.js +0 -15
- package/dist-server/server/computer-observation.js +0 -74
- package/dist-server/server/config.js +121 -65
- package/dist-server/server/container-computer.js +23 -24
- package/dist-server/server/control-client.js +4 -10
- package/dist-server/server/decider/jev.js +1 -4
- package/dist-server/server/decider/log.js +1 -3
- package/dist-server/server/decision-log.js +1 -4
- package/dist-server/server/delegations.js +3 -2
- package/dist-server/server/drivers/acp/core.js +11 -8
- package/dist-server/server/drivers/acp/opencode-go.js +17 -16
- package/dist-server/server/drivers/agents-call.js +88 -92
- package/dist-server/server/drivers/agents-catalog.js +90 -11
- package/dist-server/server/drivers/agents-proxy.js +10 -31
- package/dist-server/server/drivers/antigravity.js +1 -4
- package/dist-server/server/drivers/boatagent.js +23 -4
- package/dist-server/server/drivers/cerebras.js +3 -3
- package/dist-server/server/drivers/chat-mcp-tools.js +4 -7
- package/dist-server/server/drivers/claude-login-auth.js +2 -11
- package/dist-server/server/drivers/claude.js +16 -14
- package/dist-server/server/drivers/codex-device-auth.js +3 -1
- package/dist-server/server/drivers/codex.js +5 -4
- package/dist-server/server/drivers/grok.js +1 -1
- package/dist-server/server/drivers/openai-chat-protocol.js +4 -2
- package/dist-server/server/drivers/openai-chat.js +18 -11
- package/dist-server/server/drivers/prompt-split.js +4 -4
- package/dist-server/server/drivers/retry.js +8 -4
- package/dist-server/server/engine-install.js +1 -5
- package/dist-server/server/harness/bus.js +49 -0
- package/dist-server/server/harness/registry.js +0 -1
- package/dist-server/server/{browser-proxy.js → harness-mcp-proxy.js} +40 -20
- package/dist-server/server/index.js +1190 -971
- package/dist-server/server/lending-memory.js +12 -8
- package/dist-server/server/mcp-registry.js +35 -0
- package/dist-server/server/memory-entries.js +38 -8
- package/dist-server/server/memory-organize.js +1 -8
- package/dist-server/server/memory-topics.js +4 -4
- package/dist-server/server/memory-upkeep.js +13 -15
- package/dist-server/server/message-file.js +9 -13
- package/dist-server/server/outbound-counts.js +79 -0
- package/dist-server/server/outbound-requests.js +60 -0
- package/dist-server/server/package-import.js +1 -1
- package/dist-server/server/peer-delivery.js +5 -19
- package/dist-server/server/peer-roster.js +30 -4
- package/dist-server/server/profile-versions.js +0 -31
- package/dist-server/server/provider-key-check.js +2 -1
- package/dist-server/server/proxy-paths.js +2 -1
- package/dist-server/server/restart.js +40 -0
- package/dist-server/server/room-handoffs.js +17 -13
- package/dist-server/server/routes/bot-memory.js +0 -24
- package/dist-server/server/routine-requests.js +2 -5
- package/dist-server/server/routines.js +10 -5
- package/dist-server/server/server-launcher.js +53 -0
- package/dist-server/server/setup-mode.js +0 -2
- package/dist-server/server/skill-learn.js +0 -1
- package/dist-server/server/skill-library.js +0 -15
- package/dist-server/server/store.js +10 -2
- package/dist-server/server/surface.js +69 -28
- package/dist-server/server/system-prompt.js +17 -7
- package/dist-server/server/team-manifest.js +1 -6
- package/dist-server/server/team-memory.js +209 -0
- package/dist-server/server/thread-events.js +2 -2
- package/dist-server/server/tunnel.js +4 -1
- package/dist-server/server/turn-images.js +1 -21
- package/dist-server/server/turn-resources.js +19 -150
- package/dist-server/server/usage-ledger.js +3 -1
- package/dist-server/server/vps-computer.js +4 -14
- package/dist-server/server/workspace.js +87 -50
- package/dist-server/server-launcher.js +72 -0
- package/dist-server/shared/activity.js +3 -0
- package/dist-server/shared/ask-question.js +1 -1
- package/dist-server/shared/bot-avatar.js +8 -0
- package/dist-server/shared/cloud-computer.js +12 -0
- package/dist-server/shared/connector-scopes.js +78 -0
- package/dist-server/shared/failed-turn.js +25 -0
- package/dist-server/shared/live-approval.js +2 -2
- package/dist-server/shared/outbound.js +128 -0
- package/dist-server/shared/package-format.js +3 -3
- package/dist-server/shared/pairing-link.js +144 -0
- package/dist-server/shared/tool-surface.js +3 -2
- package/dist-server/vps-container-mcp.js +16 -29
- package/dist-server/workspace-backup.worker.js +14 -27
- package/enterprise/server/index.js +2 -1
- package/package.json +1 -1
- package/dist/assets/channel-CdJX6i8k.js +0 -1
- package/dist/assets/index-CPYrGsv1.js +0 -610
- package/dist/assets/index-CzT42GR8.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +0 -8
- package/dist-server/server/claim-idle.js +0 -33
- package/dist-server/server/cloud-overflow.js +0 -167
- package/dist-server/server/drivers/chat-boat-tools.js +0 -90
- package/dist-server/server/drivers/grokagent.js +0 -4
- package/dist-server/server/openmaus-status-capsule.js +0 -329
- package/dist-server/server/tightening-requests.js +0 -435
- package/dist-server/shared/tightening-request.js +0 -87
|
@@ -1,34 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// skill tools routed back through the harness so the harness stays the
|
|
4
|
-
// single owner of turns, permissions, and recursion limits.
|
|
5
|
-
// tools are:
|
|
6
|
-
//
|
|
7
|
-
// list_bots() → the other bots in this section + their status
|
|
8
|
-
// list_rooms() → the shared rooms this bot may post into
|
|
9
|
-
// post_to_room(group_id, message) → put ONE message in a room; nobody's
|
|
10
|
-
// turn starts, so nobody replies
|
|
11
|
-
// ask_bot(bot_id, msg) → send msg to that bot, wait, return its reply
|
|
12
|
-
// delegate_bot(bot_id, msg, reason?) → hand the task to a peer ASYNC: returns
|
|
13
|
-
// immediately, the peer runs after your
|
|
14
|
-
// current turn finishes, the result is
|
|
15
|
-
// delivered to the source conversation
|
|
16
|
-
// start_thread(title, msg, bot_id?) → open a real thread — on yourself for
|
|
17
|
-
// separate work, or on a teammate as a
|
|
18
|
-
// handoff that runs on its own
|
|
19
|
-
// create_bot(name, role, instructions) → Chiefs can add a specialist to
|
|
20
|
-
// their own section
|
|
21
|
-
// create_room / manage_room → Chiefs manage own-section rooms,
|
|
22
|
-
// never move bots or sections
|
|
23
|
-
// request_credential(id, reason?) → show a secure, allowlisted key card
|
|
24
|
-
// list_routines() → inspect this bot's scheduled work
|
|
25
|
-
// propose_routine(...) → apply or request confirmation for a new routine
|
|
26
|
-
// propose_routine_action(...) → apply or request confirmation for a routine change
|
|
27
|
-
// propose_profile(...) → apply or request confirmation for a profile change
|
|
1
|
+
// The "agents" MCP server — spawned inside a bot's agent process (via the
|
|
2
|
+
// "agents" integration). Exposes the teammate, room, routine, memory and
|
|
3
|
+
// skill tools, each routed back through the harness so the harness stays the
|
|
4
|
+
// single owner of turns, permissions, and recursion limits.
|
|
28
5
|
//
|
|
29
6
|
// Speaks raw JSON-RPC 2.0 over stdio (no MCP SDK — house style, matches
|
|
30
|
-
//
|
|
31
|
-
//
|
|
7
|
+
// permission-proxy). All state comes from env, injected by the harness when
|
|
8
|
+
// it builds the integration (catalogProfileFromEnv and
|
|
9
|
+
// toolCallContextFromEnv read it). The main ones:
|
|
32
10
|
// OMB_HARNESS_URL base URL of the harness (http://127.0.0.1:8799)
|
|
33
11
|
// OMB_BOT_ID the calling bot's id (excluded from list_bots; sender)
|
|
34
12
|
// OMB_COMMS_TOKEN shared secret for the localhost-only internal endpoints
|
|
@@ -44,7 +22,8 @@ import readline from "node:readline";
|
|
|
44
22
|
import { availableTools, catalogProfileFromEnv } from "./agents-catalog.js";
|
|
45
23
|
import { callTool, capResult, toolCallContextFromEnv } from "./agents-call.js";
|
|
46
24
|
const AVAILABLE_TOOLS = availableTools(catalogProfileFromEnv(process.env));
|
|
47
|
-
//
|
|
25
|
+
// A warm engine keeps this process across its turns; the harness keeps
|
|
26
|
+
// every per-turn limit, so nothing here counts.
|
|
48
27
|
const CONTEXT = toolCallContextFromEnv(process.env);
|
|
49
28
|
const send = (msg) => process.stdout.write(JSON.stringify(msg) + "\n");
|
|
50
29
|
const ok = (id, result) => send({ jsonrpc: "2.0", id, result });
|
|
@@ -61,7 +40,7 @@ async function handle(msg) {
|
|
|
61
40
|
ok(id, {
|
|
62
41
|
protocolVersion: params.protocolVersion ?? "2024-11-05",
|
|
63
42
|
capabilities: { tools: {} },
|
|
64
|
-
serverInfo: { name: "
|
|
43
|
+
serverInfo: { name: "openmausbot-agents", version: "0.1.0" },
|
|
65
44
|
});
|
|
66
45
|
return;
|
|
67
46
|
case "notifications/initialized":
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAcpDriver } from "./acp/core.js";
|
|
2
|
-
import { AntigravityAuthController, antigravityProfileAuthenticated,
|
|
2
|
+
import { AntigravityAuthController, antigravityProfileAuthenticated, prepareAntigravityProfile, probeAntigravityModels, validateAntigravityRuntime, } from "./antigravity-acp.js";
|
|
3
3
|
import { antigravityManagedInstallAvailable, installAntigravityRuntime, resolveAntigravityRuntime, } from "./antigravity-runtime.js";
|
|
4
4
|
import { resolveAntigravityReleaseAsset } from "./antigravity-release.js";
|
|
5
5
|
import { VERIFICATION_TEMP_KEY, scheduleAntigravityTempSweep } from "./antigravity-temp.js";
|
|
@@ -20,9 +20,6 @@ export function antigravityPermissionMode(fullAuto, approvalMode) {
|
|
|
20
20
|
return "yolo";
|
|
21
21
|
return approvalMode === "edits" ? "auto_edit" : "default";
|
|
22
22
|
}
|
|
23
|
-
export function antigravityModelsFromSession(value) {
|
|
24
|
-
return catalogFromAntigravityConfigOptions(value, STATIC_ANTIGRAVITY_MODELS.default);
|
|
25
|
-
}
|
|
26
23
|
const managedAsset = resolveAntigravityReleaseAsset();
|
|
27
24
|
const support = {
|
|
28
25
|
driverKind: "antigravityAgent",
|
|
@@ -43,6 +43,22 @@ const providerFor = (model) => {
|
|
|
43
43
|
}
|
|
44
44
|
return { provider: model.startsWith("gpt") ? "codex" : "claude-code", model };
|
|
45
45
|
};
|
|
46
|
+
/** Boat answers a prompt it cannot run with 409 provider_not_configured: the
|
|
47
|
+
* Boat has no AI sign-in for the harness the model needs. Say that, and the
|
|
48
|
+
* one thing the person can do about it here. */
|
|
49
|
+
export const PROVIDER_NOT_CONFIGURED_MESSAGE = "The Computer engine has no AI sign-in on its cloud computer. Choose another engine in this bot's settings.";
|
|
50
|
+
/** An OMB Cloud's included Boat account has no agent sign-ins and never
|
|
51
|
+
* will: the operator's account must not run (or pay for) customers' models. */
|
|
52
|
+
export const COMPUTER_ENGINE_CLOUD_UNAVAILABLE = "The Computer engine isn't available on OpenMaus Cloud — choose another engine.";
|
|
53
|
+
/** The words of a Boat error envelope ({code, message, error:{code,
|
|
54
|
+
* message}}), never a bare code when Boat sent a sentence. */
|
|
55
|
+
export function boatErrorMessage(body, status) {
|
|
56
|
+
const code = body?.code ?? body?.error?.code;
|
|
57
|
+
if (code === "provider_not_configured")
|
|
58
|
+
return PROVIDER_NOT_CONFIGURED_MESSAGE;
|
|
59
|
+
const message = body?.message ?? body?.error?.message ?? (typeof body?.error === "string" ? body.error : undefined) ?? code;
|
|
60
|
+
return typeof message === "string" && message.trim() ? message : `box HTTP ${status}`;
|
|
61
|
+
}
|
|
46
62
|
function decodeConfig(raw) {
|
|
47
63
|
const o = (raw ?? {});
|
|
48
64
|
return {
|
|
@@ -91,9 +107,8 @@ export const BoatAgentDriver = {
|
|
|
91
107
|
signal: opts.signal ?? AbortSignal.timeout(30_000),
|
|
92
108
|
});
|
|
93
109
|
const body = await res.json().catch(() => null);
|
|
94
|
-
if (!res.ok || body?.ok === false)
|
|
95
|
-
throw new Error(body
|
|
96
|
-
}
|
|
110
|
+
if (!res.ok || body?.ok === false)
|
|
111
|
+
throw new Error(boatErrorMessage(body, res.status));
|
|
97
112
|
return body;
|
|
98
113
|
};
|
|
99
114
|
const sendTurn = async (turn) => {
|
|
@@ -103,6 +118,8 @@ export const BoatAgentDriver = {
|
|
|
103
118
|
const boxId = computer && (!computer.kind || computer.kind === "box") ? computer.boxId : undefined;
|
|
104
119
|
if (!account())
|
|
105
120
|
throw new Error('box not configured — add {"box":{"token":"…"}} to ~/.openmausbot/config.json');
|
|
121
|
+
if (account()?.included)
|
|
122
|
+
throw new Error(COMPUTER_ENGINE_CLOUD_UNAVAILABLE);
|
|
106
123
|
if (!boxId) {
|
|
107
124
|
throw new Error("this bot has no computer yet — open the Computer panel and provision one");
|
|
108
125
|
}
|
|
@@ -368,6 +385,8 @@ export const BoatAgentDriver = {
|
|
|
368
385
|
if (!account()) {
|
|
369
386
|
return { state: "unavailable", reason: 'no Boat token — add {"box":{"token":"…"}} to ~/.openmausbot/config.json' };
|
|
370
387
|
}
|
|
388
|
+
if (account()?.included)
|
|
389
|
+
return { state: "unavailable", reason: COMPUTER_ENGINE_CLOUD_UNAVAILABLE };
|
|
371
390
|
try {
|
|
372
391
|
await api("/me");
|
|
373
392
|
return { state: "available", authenticated: true, version: null };
|
|
@@ -385,7 +404,7 @@ export const BoatAgentDriver = {
|
|
|
385
404
|
snapshot,
|
|
386
405
|
adapter: {
|
|
387
406
|
provider: DRIVER_KIND,
|
|
388
|
-
capabilities: { sessionModelSwitch: "in-session", remoteAgent: true
|
|
407
|
+
capabilities: { sessionModelSwitch: "in-session", remoteAgent: true },
|
|
389
408
|
sendTurn,
|
|
390
409
|
interruptTurn: async (threadId) => active.get(threadId)?.cancel(),
|
|
391
410
|
respondToRequest: async (threadId, requestId, decision) => {
|
|
@@ -37,7 +37,7 @@ export const CerebrasDriver = {
|
|
|
37
37
|
install: {
|
|
38
38
|
docsUrl: "https://cloud.cerebras.ai/",
|
|
39
39
|
settings: "connections",
|
|
40
|
-
signInCommand: "Save a Cerebras API key in Settings →
|
|
40
|
+
signInCommand: "Save a Cerebras API key in Settings → API keys, or set CEREBRAS_API_KEY on the server.",
|
|
41
41
|
},
|
|
42
42
|
decodeConfig,
|
|
43
43
|
defaultConfig: () => decodeConfig({}),
|
|
@@ -95,8 +95,8 @@ export const CerebrasDriver = {
|
|
|
95
95
|
...(stream ? { stream_options: { include_usage: true } } : {}),
|
|
96
96
|
}),
|
|
97
97
|
httpErrorLabel: "Cerebras",
|
|
98
|
-
missingKeyError: "Save a Cerebras API key in Settings →
|
|
99
|
-
unavailableReason: "No Cerebras API key — open Settings →
|
|
98
|
+
missingKeyError: "Save a Cerebras API key in Settings → API keys, or set CEREBRAS_API_KEY.",
|
|
99
|
+
unavailableReason: "No Cerebras API key — open Settings → API keys.",
|
|
100
100
|
timeoutMs: 180_000, billing: "metered", includeUsageInCompleted: true,
|
|
101
101
|
nativeLog: {
|
|
102
102
|
source: "cerebras.chat.completions",
|
|
@@ -7,7 +7,6 @@ import { stripControlPlaneEnv } from "../config.js";
|
|
|
7
7
|
import { augmentedPath } from "../env-path.js";
|
|
8
8
|
import { killCliTree, spawnCli } from "../procs.js";
|
|
9
9
|
import { chatImage } from "./chat-images.js";
|
|
10
|
-
import { ChatBoatClient } from "./chat-boat-tools.js";
|
|
11
10
|
import { mcpStdioServer } from "../mcp-gate-config.js";
|
|
12
11
|
import { allowsTool, canUseMcpServer, parseToolScope } from "../../shared/tool-scope.js";
|
|
13
12
|
/** The transport cannot safely continue this turn. A dispatched operation may
|
|
@@ -238,7 +237,7 @@ const validatorOptions = {
|
|
|
238
237
|
// or describe an open tuple. Ajv still enforces every constraint.
|
|
239
238
|
strictRequired: false, strictTypes: false, strictTuples: false,
|
|
240
239
|
};
|
|
241
|
-
function
|
|
240
|
+
export function compileToolSchema(schema) {
|
|
242
241
|
const dialect = schema.$schema;
|
|
243
242
|
if (dialect !== undefined && dialect !== "http://json-schema.org/draft-07/schema#" && dialect !== "https://json-schema.org/draft/2020-12/schema") {
|
|
244
243
|
throw new Error("MCP tool schema uses an unsupported dialect; use JSON Schema draft-07 or 2020-12");
|
|
@@ -291,8 +290,6 @@ export async function mountChatTools(integrations, signal, computerUse = false,
|
|
|
291
290
|
const scope = parsed.scope;
|
|
292
291
|
const servers = [];
|
|
293
292
|
const eligible = (server) => scope === undefined || canUseMcpServer(scope, server);
|
|
294
|
-
if (computerUse && integrations?.computer && eligible("computer"))
|
|
295
|
-
servers.push(["computer", integrations.computer]);
|
|
296
293
|
if (computerUse && integrations?.localComputer && eligible("computer"))
|
|
297
294
|
servers.push(["computer", integrations.localComputer]);
|
|
298
295
|
if (computerUse && integrations?.browser && eligible("browser"))
|
|
@@ -339,10 +336,10 @@ export async function mountChatTools(integrations, signal, computerUse = false,
|
|
|
339
336
|
throw aborted();
|
|
340
337
|
// Every mounted MCP server can return images when the caller enables
|
|
341
338
|
// image delivery, including custom servers. Text stays bounded below.
|
|
342
|
-
const client =
|
|
339
|
+
const client = new ChatMcpClient(descriptor, computerUse);
|
|
343
340
|
clients.push(client);
|
|
344
341
|
const include = (tool) => scope === undefined || (object(tool) && typeof tool.name === "string" && allowsTool(scope, { kind: "mcp", server: name, name: tool.name }));
|
|
345
|
-
const tools =
|
|
342
|
+
const tools = await client.tools(signal, include);
|
|
346
343
|
return { name, client, builtInBrowser: descriptor === integrations?.browser, tools };
|
|
347
344
|
}));
|
|
348
345
|
for (const mount of mounts) {
|
|
@@ -360,7 +357,7 @@ export async function mountChatTools(integrations, signal, computerUse = false,
|
|
|
360
357
|
throw new Error("MCP tools require an object input schema");
|
|
361
358
|
if (Buffer.byteLength(JSON.stringify(tool.inputSchema)) > SCHEMA_BYTES)
|
|
362
359
|
throw new Error("MCP tool schema exceeds the 64KB limit");
|
|
363
|
-
const schema =
|
|
360
|
+
const schema = compileToolSchema(tool.inputSchema);
|
|
364
361
|
const parameters = { ...tool.inputSchema };
|
|
365
362
|
const constraints = {};
|
|
366
363
|
if (computerUse) {
|
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { realpathSync } from "node:fs";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
|
-
import {
|
|
3
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
4
|
import { stripVTControlCharacters } from "node:util";
|
|
6
5
|
import { killCliTree, spawnCli } from "../procs.js";
|
|
6
|
+
import { canonicalPath } from "./codex-device-auth.js";
|
|
7
7
|
const MAX_OUTPUT = 32_768;
|
|
8
8
|
/** Where Anthropic's sign-in lives. Anything else in the CLI's output is not a link we show. */
|
|
9
9
|
const SIGN_IN_HOSTS = ["claude.com", "claude.ai", "console.anthropic.com", "platform.claude.com"];
|
|
10
10
|
// One login per credential home at a time; turns are never blocked by this.
|
|
11
11
|
const authenticatingHomes = new Set();
|
|
12
|
-
function canonicalPath(path) {
|
|
13
|
-
try {
|
|
14
|
-
return realpathSync(path);
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
const parent = dirname(path);
|
|
18
|
-
return parent === path ? path : join(canonicalPath(parent), basename(path));
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
12
|
/** The sign-in link, or null for anything that is not Anthropic's own page over https. */
|
|
22
13
|
export function claudeSignInLink(value) {
|
|
23
14
|
if (!value)
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Integrations become MCP servers on the CLI:
|
|
8
8
|
// - Composio Sessions (connected apps → tools) over streamable HTTP
|
|
9
|
-
// -
|
|
10
|
-
//
|
|
9
|
+
// - every computer (this Mac, a Local VM, a VPS, or a Boat cloud
|
|
10
|
+
// computer through server/harness-mcp-proxy.ts computer) as the one
|
|
11
|
+
// stdio `computer` server in turn.integrations.localComputer
|
|
11
12
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
12
13
|
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
13
14
|
import { createServer as createNetServer } from "node:net";
|
|
@@ -339,7 +340,7 @@ export function claudeCliUpdate(version, cli) {
|
|
|
339
340
|
const effects = [
|
|
340
341
|
...(missing.includes("--autocompact") ? ["no compaction window picked by OpenMausBot"] : []),
|
|
341
342
|
...(missing.includes("--setting-sources") ? ["bots still see this machine's own Claude Code setup"] : []),
|
|
342
|
-
...(missing.includes("--system-prompt-snapshot") ? ["
|
|
343
|
+
...(missing.includes("--system-prompt-snapshot") ? ["resumed turns cannot refresh stale system prompts"] : []),
|
|
343
344
|
];
|
|
344
345
|
return {
|
|
345
346
|
title: "Update Claude Code for context controls",
|
|
@@ -1034,10 +1035,11 @@ export const ClaudeDriver = {
|
|
|
1034
1035
|
// harness snapshots every instance whenever it describes them — app
|
|
1035
1036
|
// load, the Engines page, and right after `claude update`, which is
|
|
1036
1037
|
// exactly when the answer changes — so a turn normally finds it filled.
|
|
1037
|
-
//
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1040
|
-
//
|
|
1038
|
+
// A turn that finds it empty reads the version itself first: the
|
|
1039
|
+
// snapshot-refresh flag every turn passes is newer than the other context
|
|
1040
|
+
// controls, and an unknown flag would reject the turn. If that read
|
|
1041
|
+
// fails, most flags assume a current CLI; the snapshot flag needs a
|
|
1042
|
+
// confirmed version and autocompact a confirmed help listing.
|
|
1041
1043
|
let cliVersion = null;
|
|
1042
1044
|
let cliVersionChecked = false;
|
|
1043
1045
|
// Whether `claude --help` lists --autocompact, read once per CLI version
|
|
@@ -1203,7 +1205,7 @@ export const ClaudeDriver = {
|
|
|
1203
1205
|
args.push("--disallowedTools", config.disallowedTools.join(","));
|
|
1204
1206
|
}
|
|
1205
1207
|
const turnEnvironment = environment();
|
|
1206
|
-
if (
|
|
1208
|
+
if (!cliVersionChecked) {
|
|
1207
1209
|
const version = await readCliVersion(turnEnvironment);
|
|
1208
1210
|
if (version) {
|
|
1209
1211
|
cliVersion = parseClaudeCliVersion(version);
|
|
@@ -1246,12 +1248,12 @@ export const ClaudeDriver = {
|
|
|
1246
1248
|
if (compactWindow && cliHasAutocompact === true) {
|
|
1247
1249
|
args.push("--autocompact", compactWindow);
|
|
1248
1250
|
}
|
|
1249
|
-
//
|
|
1250
|
-
// Claude's recorded system prompt
|
|
1251
|
-
//
|
|
1252
|
-
//
|
|
1253
|
-
//
|
|
1254
|
-
if (
|
|
1251
|
+
// A resumed conversation can still carry an earlier assignment, place
|
|
1252
|
+
// or teammate list in Claude's recorded system prompt, so every turn
|
|
1253
|
+
// refreshes the recorded prompt, on --resume too. Gated by the version
|
|
1254
|
+
// floor like every other flag the CLI may predate: an unknown flag is a
|
|
1255
|
+
// hard argument error, not a graceful degrade.
|
|
1256
|
+
if (cliVersionChecked && claudeCliSupports(cliVersion, "--system-prompt-snapshot")) {
|
|
1255
1257
|
args.push("--system-prompt-snapshot", "off");
|
|
1256
1258
|
}
|
|
1257
1259
|
const turnModel = config.managed ? turn.model : await resolveClaudeTurnModel(turn.model, turnEnvironment);
|
|
@@ -9,7 +9,9 @@ const MAX_OUTPUT = 16_384;
|
|
|
9
9
|
// Two configured instances can use the same on-disk login. Only the auth
|
|
10
10
|
// operation is exclusive: normal turns are not blocked by this lock.
|
|
11
11
|
const authenticatingHomes = new Set();
|
|
12
|
-
|
|
12
|
+
/** The real path of a credential home, so two spellings of one home share
|
|
13
|
+
* one sign-in lock. A missing tail keeps its name under its real parent. */
|
|
14
|
+
export function canonicalPath(path) {
|
|
13
15
|
try {
|
|
14
16
|
return realpathSync(path);
|
|
15
17
|
}
|
|
@@ -1567,10 +1567,11 @@ export const CodexDriver = {
|
|
|
1567
1567
|
// Only the stable half of the prompt belongs in the developer slot:
|
|
1568
1568
|
// it is the part that must survive compaction unchanged, and any
|
|
1569
1569
|
// change to it invalidates the provider's cached prefix. The volatile
|
|
1570
|
-
// half (
|
|
1571
|
-
// inside the turn that changed it, after the cached
|
|
1572
|
-
// contract SendTurnInput.systemStable documents.
|
|
1573
|
-
// the driver keeps the previous single-block
|
|
1570
|
+
// half (the sections in VOLATILE_SECTIONS, system-prompt.ts) is
|
|
1571
|
+
// delivered inside the turn that changed it, after the cached
|
|
1572
|
+
// prefix, the same contract SendTurnInput.systemStable documents.
|
|
1573
|
+
// Without the split the driver keeps the previous single-block
|
|
1574
|
+
// behaviour.
|
|
1574
1575
|
const stableInstructions = typeof turn.systemStable === "string" && typeof turn.systemVolatile === "string"
|
|
1575
1576
|
? turn.systemStable
|
|
1576
1577
|
: null;
|
|
@@ -45,7 +45,7 @@ export const GrokDriver = {
|
|
|
45
45
|
generateModel: () => "grok-3-mini",
|
|
46
46
|
nativeLog: {
|
|
47
47
|
source: "xai.chat.completions",
|
|
48
|
-
outgoing: (_turn, messages, model) => ({ model, messages }),
|
|
48
|
+
outgoing: (_turn, messages, model) => ({ model, messageCount: messages.length }),
|
|
49
49
|
incoming: ({ text, usage }) => ({ text, usage }),
|
|
50
50
|
},
|
|
51
51
|
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/** Calls one model reply may carry (its index bound). The turn's own total is
|
|
2
|
+
* MAX_TURN_TOOL_CALLS in openai-chat.ts. */
|
|
3
|
+
const MAX_REPLY_TOOL_CALLS = 32;
|
|
2
4
|
const MAX_ARGUMENT_CHARS = 256_000;
|
|
3
5
|
export class ChatProtocolError extends Error {
|
|
4
6
|
}
|
|
@@ -36,7 +38,7 @@ export class ChatToolCalls {
|
|
|
36
38
|
for (const [position, raw] of value.entries()) {
|
|
37
39
|
const delta = object(raw);
|
|
38
40
|
const index = streaming ? delta?.index : position;
|
|
39
|
-
if (!delta || !Number.isInteger(index) || Number(index) < 0 || Number(index) >=
|
|
41
|
+
if (!delta || !Number.isInteger(index) || Number(index) < 0 || Number(index) >= MAX_REPLY_TOOL_CALLS) {
|
|
40
42
|
throw new ChatProtocolError("provider returned an invalid tool-call index");
|
|
41
43
|
}
|
|
42
44
|
const call = this.calls.get(Number(index)) ?? { id: "", type: "function", function: { name: "", arguments: "" } };
|
|
@@ -7,7 +7,7 @@ import { ChatToolSessionError, mountChatTools } from "./chat-mcp-tools.js";
|
|
|
7
7
|
import { assertImageTransport, chatImageBudget, chatToolImages, chatUserContent } from "./chat-images.js";
|
|
8
8
|
import { promptHalves, volatileContextNote, withContextNote } from "./prompt-split.js";
|
|
9
9
|
import { createChatToolApproval } from "./chat-tool-approval.js";
|
|
10
|
-
import { ChatProtocolError, ChatReasoningDetails, ChatToolCalls,
|
|
10
|
+
import { ChatProtocolError, ChatReasoningDetails, ChatToolCalls, object } from "./openai-chat-protocol.js";
|
|
11
11
|
import { appendNative } from "./native.js";
|
|
12
12
|
import { classifyError, computeBackoff, interruptibleDelay, RETRY_MAX_ATTEMPTS } from "./retry.js";
|
|
13
13
|
/** The message of a JSON error body a provider returned with HTTP 200.
|
|
@@ -31,6 +31,15 @@ const usageFrom = (usage) => usage
|
|
|
31
31
|
const asError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
32
32
|
class UnsupportedChatToolsError extends Error {
|
|
33
33
|
}
|
|
34
|
+
/** One turn's safety stops: model steps, and tool calls across all of them.
|
|
35
|
+
* Tool-heavy models (a memory server, a browser) take dozens of steps, and
|
|
36
|
+
* a stopped turn cannot simply be continued — the next turn is rebuilt from
|
|
37
|
+
* the text transcript, which holds none of these tool results. The calls
|
|
38
|
+
* that ran have taken effect, so the stop asks only for what is left: a
|
|
39
|
+
* retry of the whole task would repeat them. */
|
|
40
|
+
const MAX_CHAT_ROUNDS = 64;
|
|
41
|
+
const MAX_TURN_TOOL_CALLS = 200;
|
|
42
|
+
const stoppedAfter = (count) => `Stopped after ${count} without a final answer. The steps so far already ran, so ask only for what's left.`;
|
|
34
43
|
const NUDGE_ANNOUNCED_ACTION = "You said what you would do next but called no tool. Do it now with your tools, or reply with your final answer if nothing is left to do.";
|
|
35
44
|
/** A short reply that only announces a next step, with nothing to answer. */
|
|
36
45
|
export function announcesAction(text) {
|
|
@@ -325,7 +334,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
325
334
|
throw new Error(options.missingKeyError);
|
|
326
335
|
if (active.has(turn.threadId))
|
|
327
336
|
throw new Error("a turn is already running on this thread");
|
|
328
|
-
if (options.computerUse && (turn.images?.length || turn.integrations?.
|
|
337
|
+
if (options.computerUse && (turn.images?.length || turn.integrations?.localComputer || turn.integrations?.browser))
|
|
329
338
|
assertImageTransport(options.apiUrl);
|
|
330
339
|
const turnId = newId();
|
|
331
340
|
const abort = new AbortController();
|
|
@@ -334,7 +343,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
334
343
|
for (const message of messages)
|
|
335
344
|
retainImages(message.content);
|
|
336
345
|
const model = turn.model || options.models().default;
|
|
337
|
-
const secrets = [options.apiKey
|
|
346
|
+
const secrets = [options.apiKey].filter((value) => Boolean(value));
|
|
338
347
|
for (const integration of Object.values(turn.integrations ?? {})) {
|
|
339
348
|
const entries = object(integration);
|
|
340
349
|
const specs = entries && "command" in entries ? [entries] : Object.values(entries ?? {}).map(object);
|
|
@@ -413,7 +422,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
413
422
|
if (questionAllowed && !tools.definitions.some((definition) => definition.function.name === ASK_USER_TOOL)) {
|
|
414
423
|
tools.definitions.push(ASK_USER_TOOL_DEFINITION);
|
|
415
424
|
}
|
|
416
|
-
for (let round = 0; round <
|
|
425
|
+
for (let round = 0; round < MAX_CHAT_ROUNDS; round++) {
|
|
417
426
|
abort.signal.throwIfAborted();
|
|
418
427
|
// Drain parked mid-turn input here, before the next completion
|
|
419
428
|
// request: the previous round's tool results are complete, so a
|
|
@@ -531,8 +540,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
531
540
|
throw new ChatProtocolError("provider reused a tool-call ID; refusing to repeat an operation");
|
|
532
541
|
seenCalls.add(call.id);
|
|
533
542
|
}
|
|
534
|
-
|
|
535
|
-
|
|
543
|
+
// The whole batch is refused before any of it runs.
|
|
544
|
+
if (seenCalls.size > MAX_TURN_TOOL_CALLS)
|
|
545
|
+
throw new ChatProtocolError(stoppedAfter(`${MAX_TURN_TOOL_CALLS} tool calls`));
|
|
536
546
|
messages.push({ role: "assistant", content: completion.text || null, tool_calls: completion.toolCalls,
|
|
537
547
|
...(completion.protocolReasoning && !reasoningReplayRejected.has(model) ? { [options.reasoningReplayField ?? "reasoning_content"]: completion.protocolReasoning } : {}),
|
|
538
548
|
...(completion.protocolReasoningDetails.length ? { reasoning_details: completion.protocolReasoningDetails } : {}),
|
|
@@ -645,7 +655,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
645
655
|
messages.push({ role: "user", content: screenshotParts });
|
|
646
656
|
}
|
|
647
657
|
if (!ok)
|
|
648
|
-
throw new ChatProtocolError(
|
|
658
|
+
throw new ChatProtocolError(stoppedAfter(`${MAX_CHAT_ROUNDS} steps`));
|
|
649
659
|
}
|
|
650
660
|
catch (value) {
|
|
651
661
|
stopReason = abort.signal.aborted ? "interrupted" : stopReason ?? "error";
|
|
@@ -695,10 +705,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
695
705
|
adapter: {
|
|
696
706
|
provider: options.driverKind,
|
|
697
707
|
capabilities: { ...(options.computerUse ? { computerMcp: options.tools !== false,
|
|
698
|
-
|
|
699
|
-
// mount the leased Boat descriptor either. The fleet invariant test
|
|
700
|
-
// pins usesCloudComputer === (remoteAgent || cloudComputerMcp).
|
|
701
|
-
usesCloudComputer: options.tools !== false, cloudComputerMcp: options.tools !== false, localComputerMcp: options.tools !== false,
|
|
708
|
+
localComputerMcp: options.tools !== false,
|
|
702
709
|
browserMcp: options.tools !== false, nativeImageInput: true, images: true } : {}),
|
|
703
710
|
sessionModelSwitch: "in-session", customMcp: options.tools !== false, agentsMcp: options.tools !== false, composioMcp: options.tools !== false,
|
|
704
711
|
// The runtime owns the whole tool loop, so it can always take a
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// The stable/volatile prompt split, shared by the drivers that deliver it.
|
|
2
2
|
// The stable half is everything that must stay byte-identical for a
|
|
3
3
|
// provider's cached prefix (or a spawned CLI's session contract) to
|
|
4
|
-
// survive; the volatile half (
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// survive; the volatile half (the sections in VOLATILE_SECTIONS,
|
|
5
|
+
// system-prompt.ts) legitimately changes mid-conversation and reaches the
|
|
6
|
+
// model inside the turn that changed it, after the cacheable prefix.
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
@@ -20,7 +20,7 @@ export function promptHalves(turn) {
|
|
|
20
20
|
return { stable: systemStable, volatile: systemVolatile };
|
|
21
21
|
}
|
|
22
22
|
export const VOLATILE_CONTEXT_NOTE_PREFIX = "Context from OpenMausBot updated since this conversation started; it replaces any earlier copy:";
|
|
23
|
-
export const VOLATILE_CONTEXT_CLEARED_NOTE = "The OpenMausBot context notes from earlier in this conversation
|
|
23
|
+
export const VOLATILE_CONTEXT_CLEARED_NOTE = "The OpenMausBot context notes from earlier in this conversation have been cleared; the standing instructions still apply.";
|
|
24
24
|
/** The labelled block that carries a changed volatile half inside a user
|
|
25
25
|
* turn. A half that is empty and always was needs no note; one that was
|
|
26
26
|
* cleared announces the removal so the model stops relying on it. */
|
|
@@ -17,6 +17,10 @@ const TERMINAL_PATTERNS = [
|
|
|
17
17
|
pattern: /\b(?:40[13]|unauthorized|forbidden|invalid api key|missing bearer|authentication required|not logged in|logged out)\b/i,
|
|
18
18
|
reason: "auth",
|
|
19
19
|
},
|
|
20
|
+
// A subscription's usage window is hours away, so its limit is terminal
|
|
21
|
+
// for this engine even when the provider phrases it as a rate limit;
|
|
22
|
+
// checked before the transient 429 pattern for that reason.
|
|
23
|
+
{ pattern: /\busage limit\b|\bhit your (?:usage )?limit\b|\b(?:weekly|daily|monthly|subscription|usage) limit reached\b|\bout of credits\b/i, reason: "quota" },
|
|
20
24
|
{ pattern: /\b402\b|\bquota\b|\bbilling\b|\bsubscription\b/i, reason: "quota" },
|
|
21
25
|
{ pattern: /\bmodel not found\b|\bunknown model\b|\bdoes not exist for model\b|\bunsupported model\b/i, reason: "unknown_model" },
|
|
22
26
|
{ pattern: /\b400\b|\b422\b|\binvalid request\b|\bmalformed\b|\bunexpected status\b/i, reason: "invalid_request" },
|
|
@@ -50,14 +54,14 @@ export function classifyError(err) {
|
|
|
50
54
|
const { exitCode: code } = err;
|
|
51
55
|
if (code !== null && code < 0)
|
|
52
56
|
return { transient: false, reason: "interrupted" };
|
|
53
|
-
for (const { pattern, reason } of TRANSIENT_PATTERNS) {
|
|
54
|
-
if (pattern.test(text))
|
|
55
|
-
return { transient: true, reason };
|
|
56
|
-
}
|
|
57
57
|
for (const { pattern, reason } of TERMINAL_PATTERNS) {
|
|
58
58
|
if (pattern.test(text))
|
|
59
59
|
return { transient: false, reason };
|
|
60
60
|
}
|
|
61
|
+
for (const { pattern, reason } of TRANSIENT_PATTERNS) {
|
|
62
|
+
if (pattern.test(text))
|
|
63
|
+
return { transient: true, reason };
|
|
64
|
+
}
|
|
61
65
|
return { transient: false, reason: "terminal_exit" };
|
|
62
66
|
}
|
|
63
67
|
for (const { pattern, reason } of TERMINAL_PATTERNS) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// driver's own install descriptor and never from a request, npm runs with
|
|
5
5
|
// a fixed argument list, and the binary is found on the engines' PATH
|
|
6
6
|
// afterwards because that directory is registered ahead of everything else.
|
|
7
|
-
import {
|
|
7
|
+
import { mkdirSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { stripVTControlCharacters } from "node:util";
|
|
10
10
|
import { DATA_DIR, stripWorkspaceCredentialEnv } from "./config.js";
|
|
@@ -169,7 +169,3 @@ function runNpm(args, env, cwd, timeoutMs) {
|
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
|
-
/** True once something has been installed here, for status pages. */
|
|
173
|
-
export function enginesInstalled(baseDir = DATA_DIR) {
|
|
174
|
-
return existsSync(enginesBinDir(baseDir));
|
|
175
|
-
}
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
// a per-thread canonical NDJSON log (the debugging trick both upstream and
|
|
5
5
|
// agentcal lean on), and delivered to subscribers (the SSE endpoint and
|
|
6
6
|
// the server-side message folder).
|
|
7
|
+
//
|
|
8
|
+
// Streamed text is merged here, once, for every engine on every kind of
|
|
9
|
+
// install: a provider sends a reply as hundreds of small text deltas, and
|
|
10
|
+
// each would otherwise be its own log line, listener call and SSE frame. The
|
|
11
|
+
// bus holds a thread's text for at most DELTA_MERGE_MS and publishes it as
|
|
12
|
+
// one delta, sooner when anything else happens on that thread, so the order
|
|
13
|
+
// of events never changes.
|
|
7
14
|
import { appendFileSync } from "node:fs";
|
|
8
15
|
import { join } from "node:path";
|
|
9
16
|
import { EVENTS_DIR } from "../config.js";
|
|
@@ -11,10 +18,19 @@ import { redactSecrets } from "../redact.js";
|
|
|
11
18
|
import { capThreadLog, currentThreadLogCap } from "../thread-log-rotation.js";
|
|
12
19
|
import { newId } from "../contracts.js";
|
|
13
20
|
const INCOMPLETE_LOG_MESSAGE = "Canonical event history is incomplete: OpenMausBot could not write one or more events to disk. Live updates will continue.";
|
|
21
|
+
/** How long a thread's streamed text waits to be merged with what follows. */
|
|
22
|
+
const DELTA_MERGE_MS = 50;
|
|
23
|
+
/** Two deltas are one stream when only their text, id and time differ. */
|
|
24
|
+
function sameStream(a, b) {
|
|
25
|
+
return a.provider === b.provider && a.providerInstanceId === b.providerInstanceId && a.turnId === b.turnId &&
|
|
26
|
+
a.itemId === b.itemId && a.streamKind === b.streamKind && a.synthetic === b.synthetic;
|
|
27
|
+
}
|
|
14
28
|
export class EventBus {
|
|
15
29
|
listeners = new Set();
|
|
16
30
|
unsubscribes = new Map();
|
|
17
31
|
pendingLogWarnings = new Map();
|
|
32
|
+
/** Per thread: the streamed text not yet published, and when it goes. */
|
|
33
|
+
pendingText = new Map();
|
|
18
34
|
appendLog;
|
|
19
35
|
constructor(appendLog = appendFileSync) {
|
|
20
36
|
this.appendLog = appendLog;
|
|
@@ -35,6 +51,38 @@ export class EventBus {
|
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
53
|
publish(event) {
|
|
54
|
+
const pending = this.pendingText.get(event.threadId);
|
|
55
|
+
if (event.type === "content.delta") {
|
|
56
|
+
if (pending && sameStream(pending.event, event)) {
|
|
57
|
+
pending.event = { ...pending.event, delta: pending.event.delta + event.delta };
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (pending)
|
|
61
|
+
this.flushThread(event.threadId);
|
|
62
|
+
const timer = setTimeout(() => this.flushThread(event.threadId), DELTA_MERGE_MS);
|
|
63
|
+
timer.unref?.();
|
|
64
|
+
this.pendingText.set(event.threadId, { event, timer });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (pending)
|
|
68
|
+
this.flushThread(event.threadId);
|
|
69
|
+
this.write(event);
|
|
70
|
+
}
|
|
71
|
+
/** Publish every thread's waiting text now: on detach, and as the server exits. */
|
|
72
|
+
flush() {
|
|
73
|
+
for (const threadId of Array.from(this.pendingText.keys()))
|
|
74
|
+
this.flushThread(threadId);
|
|
75
|
+
}
|
|
76
|
+
flushThread(threadId) {
|
|
77
|
+
const pending = this.pendingText.get(threadId);
|
|
78
|
+
if (!pending)
|
|
79
|
+
return;
|
|
80
|
+
// gone from the map before delivery: a listener may publish on this thread
|
|
81
|
+
this.pendingText.delete(threadId);
|
|
82
|
+
clearTimeout(pending.timer);
|
|
83
|
+
this.write(pending.event);
|
|
84
|
+
}
|
|
85
|
+
write(event) {
|
|
38
86
|
const pendingWarning = this.pendingLogWarnings.get(event.threadId);
|
|
39
87
|
const persistedEvents = pendingWarning ? [pendingWarning, redactSecrets(event)] : [redactSecrets(event)];
|
|
40
88
|
try {
|
|
@@ -91,5 +139,6 @@ export class EventBus {
|
|
|
91
139
|
detach(instanceId) {
|
|
92
140
|
this.unsubscribes.get(instanceId)?.();
|
|
93
141
|
this.unsubscribes.delete(instanceId);
|
|
142
|
+
this.flush();
|
|
94
143
|
}
|
|
95
144
|
}
|
|
@@ -228,7 +228,6 @@ export class ProviderRegistry {
|
|
|
228
228
|
modelVariants: inst.adapter.capabilities.modelVariants === true,
|
|
229
229
|
queueing: inst.adapter.capabilities.queueing === true,
|
|
230
230
|
localComputerMcp: inst.adapter.capabilities.localComputerMcp === true,
|
|
231
|
-
cloudComputerMcp: inst.adapter.capabilities.cloudComputerMcp === true,
|
|
232
231
|
approvalReview: inst.reviewPermission !== undefined,
|
|
233
232
|
},
|
|
234
233
|
access: driver?.metadata.access ?? "subscription",
|