openmausbot 0.1.88 → 0.1.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-qd9VD3GV.js → abnfDiagram-O67JEVCF-DUyXd0kL.js} +1 -1
- package/dist/assets/{arc-3RwkUjRe.js → arc-q73qM-At.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CFKSJS7a.js → architectureDiagram-NJMV4G6O-D33O3Wur.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CG1rEfWD.js → blockDiagram-BEXU5L5S-DLsJeoXC.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-CBW9N664.js → c4Diagram-YGBWAQC7-DnaljZnu.js} +1 -1
- package/dist/assets/channel-BCuyHjFV.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-2mRfTksB.js → chunk-2Q5K7J3B-Da0h2i79.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-Cv1dpErj.js → chunk-3FUC2YCW-CfzHi54f.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-B7IACJp-.js → chunk-5VM5RSS4-LjE4d9Jf.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-An0hZtYa.js → chunk-GTNCS2PH-DY-UGmvc.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BcJB2nEm.js → chunk-GWA4HPMP-BqeGhyhP.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CV_W7TN7.js → chunk-JWPE2WC7-BYa9EBAg.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-Bcs_kwnA.js → chunk-NETBCI7D-vTrDtWyb.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-lrx_DxFP.js → chunk-WEXAMYUT-ic5tXadM.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-DabHlLGM.js → chunk-XXDRQBXY-DvRCY-c6.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BnHj6P7d.js → classDiagram-v2-NBCMYWYE-DVbmgz_L.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CfLp6UDi.js → cose-bilkent-JH36ORCC-vdUs2n57.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-Dm4dbVA1.js → cynefin-OW5HDTMX-BvLGlV3U.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-B9mH-PID.js → cynefinDiagram-VND7K2PF-C4GcJQDt.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DDc_NVQJ.js → dagre-6A5THRUB-BMSaHpt_.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-lwr8Ip8-.js → diagram-22UHCM2B-D5b9UBhC.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BLLe6dmI.js → diagram-3UASUU5V-CIKLlB7Y.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-6eUIq4lz.js → diagram-ATOU4E4O-BzXb-7k7.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-O-pRN7VQ.js → diagram-CDSNMT55-HsTvJ7uT.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-BietvGAI.js → diagram-MLGK6HIB-Db9ySNbQ.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-Bd2Cl62X.js → diagram-MPIPVDR6-D6BKbpqY.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-B6iDy69W.js → ebnfDiagram-ZINNZB2B-Bq067L0t.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-DQVHfKe0.js → elk-276RUBZZ-DhaD7fNJ.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BWdVp2tQ.js → erDiagram-OPXOYQCR-fwIhfz36.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-WLE6rKB4.js → flowDiagram-KWPJA3E3-DygIyu0V.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BbjS-4gn.js → ganttDiagram-FUAMR5RP-DPYfSO96.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Dpy2QSGq.js → gitGraphDiagram-X574FWY7-DhkHeCle.js} +1 -1
- package/dist/assets/index-CRvo-DS2.css +1 -0
- package/dist/assets/index-CWYGlA63.js +588 -0
- package/dist/assets/{index-CIaHn5NS.js → index-G1PHKGyh.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-D_jKWlKe.js → infoDiagram-VRGFBTTK-DstBLxME.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DLJohSuK.js → ishikawaDiagram-OU5B5YK6-CrIsGgch.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DPOFj0qN.js → journeyDiagram-ZHPQQLJL-CYypGDbB.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BI7v-xpK.js → kanban-definition-PNTS6WVX-DGjPt8E_.js} +1 -1
- package/dist/assets/{linear-C2pWPSxO.js → linear-Dwc3Hrru.js} +1 -1
- package/dist/assets/{mermaid.core-CR-j5WQU.js → mermaid.core-Dc4OePBL.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-lz9GvXRh.js → mindmap-definition-NLK3R4M7-DV-vepj-.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-Dl4_ia61.js → pegDiagram-GJSIUBJH-lx5Kj-n5.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DWuT3n88.js → pieDiagram-5QR66LMP-Bq7Vmm15.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-DXWe9Jx7.js → quadrantDiagram-O4NWA36T-DeqeqOBj.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-COY1IBVY.js → railroadDiagram-XR7U4H2S-CXwRQzSC.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DnyVuhsQ.js → requirementDiagram-PLB6GJNP-8tAeoB88.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-BSc4NdGK.js → sankeyDiagram-IPEJSGJF-BG5zjGWU.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-kqEU2EGq.js → sequenceDiagram-PO4LG4MO-DKF3iLcw.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BvB4bkWD.js → sizeCapture-INFHLROL-B-y1qUgT.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BIrqNv7y.js → stateDiagram-v2-GCMORJYK-MkzPgxAc.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-CFnalQUE.js → swimlanes-2SLR337P-DnbaOEwH.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CsSWJnSQ.js → timeline-definition-EJHVYXUP-Bb4xG_Bt.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DkAmJ1EE.js → usecaseDiagram-POWQR4AR-JgkR9pFf.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-C3vd9drL.js → vennDiagram-UO4OBE2U-CIPIrAYe.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-j26CM-pi.js → wardleyDiagram-VNRHLVJA-BjCLuUvJ.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BYu1Q_at.js → xychartDiagram-PMCCYNJV-DKgiaYxT.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +14795 -0
- package/dist-server/container-mcp.js +60 -4
- package/dist-server/drivers/agents-proxy.js +20 -10
- package/dist-server/index.js +48924 -46153
- package/dist-server/openmausbot.js +544 -253
- package/dist-server/pair-cli.js +544 -253
- package/dist-server/permission-proxy.js +1 -1
- package/dist-server/server/admission.js +46 -0
- package/dist-server/server/automatic-recovery.js +15 -0
- package/dist-server/server/{box-create-idempotency.js → boat-create-idempotency.js} +32 -31
- package/dist-server/server/{box-delete-journal.js → boat-delete-journal.js} +26 -25
- package/dist-server/server/{box.js → boat.js} +330 -328
- package/dist-server/server/bot-profile.js +7 -1
- package/dist-server/server/channel-queue.js +58 -18
- package/dist-server/server/checkpoints.js +98 -52
- package/dist-server/server/claim-idle.js +33 -0
- package/dist-server/server/cli-api-setup.js +0 -1
- package/dist-server/server/cloud-backend.js +5 -5
- package/dist-server/server/cloud-home-start.js +103 -0
- package/dist-server/server/cloud-home.js +218 -0
- package/dist-server/server/cloud-overflow.js +167 -0
- package/dist-server/server/composio.js +5 -0
- package/dist-server/server/computer-observation.js +2 -2
- package/dist-server/server/config.js +124 -6
- package/dist-server/server/container-computer.js +23 -0
- package/dist-server/server/contracts.js +10 -0
- package/dist-server/server/control-client.js +2 -2
- package/dist-server/server/delegations.js +11 -3
- package/dist-server/server/drivers/acp/core.js +129 -21
- package/dist-server/server/drivers/acp/qwen.js +3 -0
- package/dist-server/server/drivers/agents-call.js +5 -1
- package/dist-server/server/drivers/agents-catalog.js +9 -3
- package/dist-server/server/drivers/{boxagent.js → boatagent.js} +14 -13
- package/dist-server/server/drivers/builtIn.js +2 -2
- package/dist-server/server/drivers/{chat-box-tools.js → chat-boat-tools.js} +3 -3
- package/dist-server/server/drivers/chat-mcp-tools.js +2 -2
- package/dist-server/server/drivers/claude.js +177 -13
- package/dist-server/server/drivers/native.js +1 -1
- package/dist-server/server/drivers/openai-chat.js +19 -4
- package/dist-server/server/drivers/pi.js +3 -3
- package/dist-server/server/harness-capabilities.js +18 -3
- package/dist-server/server/hosted-models.js +6 -1
- package/dist-server/server/index.js +1163 -530
- package/dist-server/server/local-vm-inventory.js +11 -1
- package/dist-server/server/local-vm-seat-pool.js +141 -0
- package/dist-server/server/managed-policy.js +4 -1
- package/dist-server/server/memory-capture.js +194 -0
- package/dist-server/server/memory-entries.js +76 -0
- package/dist-server/server/memory-organize.js +102 -0
- package/dist-server/server/memory-tidy.js +144 -0
- package/dist-server/server/memory-topics.js +169 -0
- package/dist-server/server/memory-upkeep.js +452 -0
- package/dist-server/server/message-db.js +49 -5
- package/dist-server/server/permission-proxy.js +3 -3
- package/dist-server/server/profile-learned.js +98 -0
- package/dist-server/server/profile-versions.js +45 -11
- package/dist-server/server/recall.js +172 -0
- package/dist-server/server/redact.js +1 -1
- package/dist-server/server/room-cwd.js +1 -1
- package/dist-server/server/room-handoffs.js +61 -6
- package/dist-server/server/routes/bot-memory.js +215 -0
- package/dist-server/server/routine-requests.js +27 -16
- package/dist-server/server/routines.js +10 -6
- package/dist-server/server/steer-queue.js +41 -10
- package/dist-server/server/store.js +25 -1
- package/dist-server/server/surface.js +1 -1
- package/dist-server/server/system-prompt.js +16 -5
- package/dist-server/server/team-computers.js +2 -2
- package/dist-server/server/tightening-requests.js +34 -4
- package/dist-server/server/tool-summary.js +2 -2
- package/dist-server/server/tts/elevenlabs.js +1 -1
- package/dist-server/server/turn-resources.js +87 -11
- package/dist-server/server/vps-computer.js +1 -1
- package/dist-server/server/webhooks.js +21 -3
- package/dist-server/server/workspace.js +57 -22
- package/dist-server/shared/ask-question.js +7 -5
- package/dist-server/shared/bot-avatar.js +19 -0
- package/dist-server/shared/credential-request.js +3 -3
- package/dist-server/shared/tool-surface.js +1 -1
- package/dist-server/src/lib/composer-attachments.js +26 -0
- package/dist-server/vps-container-mcp.js +60 -4
- package/dist-server/workspace-backup.worker.js +60 -4
- package/package.json +1 -1
- package/dist/assets/channel-CnRnO3tF.js +0 -1
- package/dist/assets/index-BIMdIbaw.css +0 -1
- package/dist/assets/index-CHnODCr2.js +0 -583
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +0 -8
|
@@ -40,10 +40,10 @@ import { describeSpawnFailure, execCli, killCliTree, spawnCli } from "../../proc
|
|
|
40
40
|
export function skipSubscriptionAuthForLocalInject(model) {
|
|
41
41
|
return Boolean(decodeInjectId(model));
|
|
42
42
|
}
|
|
43
|
-
import { newEventId, newId } from "../../contracts.js";
|
|
43
|
+
import { newEventId, newId, TurnNotStartedError } from "../../contracts.js";
|
|
44
44
|
import { augmentedPath } from "../../env-path.js";
|
|
45
45
|
import { supportsApprovalMode } from "../../../shared/approval-mode.js";
|
|
46
|
-
import { parseAskQuestions, parseChoices, questionAnswersByQuestion } from "../../../shared/ask-question.js";
|
|
46
|
+
import { MAX_QUESTION_TEXT, parseAskQuestions, parseChoices, questionAnswersByQuestion } from "../../../shared/ask-question.js";
|
|
47
47
|
import { appendNative } from "../native.js";
|
|
48
48
|
import { acpPermissionCommand, permissionLaunchCwd } from "../permission-command.js";
|
|
49
49
|
import { commandSummary, toolDetailPreview } from "../../tool-summary.js";
|
|
@@ -51,6 +51,7 @@ import { extractMcpImages } from "../../mcp-tool-images.js";
|
|
|
51
51
|
import { redactSecretsInText } from "../../redact.js";
|
|
52
52
|
import { recoveryPromptFor } from "../../resume-recovery.js";
|
|
53
53
|
import { sessionIdlePolicy } from "../session-idle.js";
|
|
54
|
+
import { classifyError } from "../retry.js";
|
|
54
55
|
/** ACP vendors put the actionable cause in error.data while keeping the
|
|
55
56
|
* JSON-RPC message generic. Only surface known text fields, never a response
|
|
56
57
|
* body/config dump, and redact before bounding the displayed diagnostic. */
|
|
@@ -98,6 +99,20 @@ const promptIdleTimeoutMs = () => {
|
|
|
98
99
|
const ms = Number(raw);
|
|
99
100
|
return Number.isFinite(ms) && ms > 0 ? ms : 0;
|
|
100
101
|
};
|
|
102
|
+
/** Keep a turn's set of running tool calls in step with the agent's
|
|
103
|
+
* `tool_call` / `tool_call_update` notifications: a call is running from
|
|
104
|
+
* the first update that is not terminal until one that is. A `tool_call`
|
|
105
|
+
* without a status is pending (ACP's default); a `tool_call_update` without
|
|
106
|
+
* one leaves the call as it was. */
|
|
107
|
+
function trackRunningTool(current, update, defaultStatus) {
|
|
108
|
+
if (typeof update.toolCallId !== "string" || !update.toolCallId)
|
|
109
|
+
return;
|
|
110
|
+
const status = update.status ?? defaultStatus;
|
|
111
|
+
if (status === "completed" || status === "failed")
|
|
112
|
+
current.runningTools.delete(update.toolCallId);
|
|
113
|
+
else if (status === "pending" || status === "in_progress")
|
|
114
|
+
current.runningTools.add(update.toolCallId);
|
|
115
|
+
}
|
|
101
116
|
const CLIENT_FILE_MAX_BYTES = 8 * 1024 * 1024;
|
|
102
117
|
function acpVariantOption(result) {
|
|
103
118
|
const option = (Array.isArray(result?.configOptions) ? result.configOptions : []).find((entry) => entry?.type === "select" && typeof entry.id === "string"
|
|
@@ -260,7 +275,7 @@ export function createAcpDriver(support) {
|
|
|
260
275
|
};
|
|
261
276
|
const allowedCredentials = new Set(support.credentialEnv ?? []);
|
|
262
277
|
// two lists, one rule: foreign PROVIDER keys must not flip a CLI's
|
|
263
|
-
// billing off its own login, and WORKSPACE credentials (
|
|
278
|
+
// billing off its own login, and WORKSPACE credentials (boat token,
|
|
264
279
|
// voice key, …) are the harness's secrets — riding along in
|
|
265
280
|
// `...process.env` is not a grant. A driver keeps only what its
|
|
266
281
|
// credentialEnv allowlist names.
|
|
@@ -296,6 +311,23 @@ export function createAcpDriver(support) {
|
|
|
296
311
|
// contract prompts the live session instead of paying the handshake
|
|
297
312
|
// again. An idle session closes after SESSION_IDLE_MS of quiet.
|
|
298
313
|
const sessions = new Map();
|
|
314
|
+
// Closing removes a session from the pool, not its native writer lease.
|
|
315
|
+
// Retain every closing Qwen child until its process tree is gone, even
|
|
316
|
+
// when Stop or a failed turn lets another turn start during cleanup.
|
|
317
|
+
const retiring = new Map();
|
|
318
|
+
const retireChild = async (threadId, child) => {
|
|
319
|
+
let children = retiring.get(threadId);
|
|
320
|
+
if (!children)
|
|
321
|
+
retiring.set(threadId, children = new Set());
|
|
322
|
+
children.add(child);
|
|
323
|
+
const stopped = await killCliTree(child);
|
|
324
|
+
if (stopped) {
|
|
325
|
+
children.delete(child);
|
|
326
|
+
if (!children.size && retiring.get(threadId) === children)
|
|
327
|
+
retiring.delete(threadId);
|
|
328
|
+
}
|
|
329
|
+
return stopped;
|
|
330
|
+
};
|
|
299
331
|
const { idleMs: SESSION_IDLE_MS } = sessionIdlePolicy("ACP");
|
|
300
332
|
const closeSession = (threadId, why) => {
|
|
301
333
|
const session = sessions.get(threadId);
|
|
@@ -308,16 +340,20 @@ export function createAcpDriver(support) {
|
|
|
308
340
|
// a new turn must never adopt a closing session
|
|
309
341
|
sessions.delete(threadId);
|
|
310
342
|
session.acp.close();
|
|
311
|
-
//
|
|
312
|
-
//
|
|
343
|
+
// EOF is not a guaranteed exit signal. Qwen must be tracked before
|
|
344
|
+
// another turn can resume its history; other agents keep their grace.
|
|
313
345
|
try {
|
|
314
346
|
session.child.stdin.end();
|
|
315
347
|
}
|
|
316
348
|
catch { }
|
|
317
|
-
|
|
318
|
-
void
|
|
319
|
-
|
|
320
|
-
|
|
349
|
+
if (support.restartOnMcpChange)
|
|
350
|
+
void retireChild(threadId, session.child);
|
|
351
|
+
else {
|
|
352
|
+
const kill = setTimeout(() => {
|
|
353
|
+
void killCliTree(session.child);
|
|
354
|
+
}, 5_000);
|
|
355
|
+
kill.unref?.();
|
|
356
|
+
}
|
|
321
357
|
};
|
|
322
358
|
const armIdle = (threadId) => {
|
|
323
359
|
const session = sessions.get(threadId);
|
|
@@ -403,7 +439,7 @@ export function createAcpDriver(support) {
|
|
|
403
439
|
}
|
|
404
440
|
// The bot's computer, mounted exactly like the Claude driver does:
|
|
405
441
|
// host and sandbox Cua connections expose Cua Driver's own MCP server.
|
|
406
|
-
// (A cloud
|
|
442
|
+
// (A cloud boat is not mounted here at all: a cloud turn runs ON the boat.)
|
|
407
443
|
if (turn.integrations?.localComputer) {
|
|
408
444
|
const local = turn.integrations.localComputer;
|
|
409
445
|
servers.push({
|
|
@@ -436,6 +472,7 @@ export function createAcpDriver(support) {
|
|
|
436
472
|
if (!current || current.state.settled)
|
|
437
473
|
return;
|
|
438
474
|
current.state.settled = true;
|
|
475
|
+
current.acknowledge();
|
|
439
476
|
if (current.interruptTimer)
|
|
440
477
|
clearTimeout(current.interruptTimer);
|
|
441
478
|
for (const finish of current.asks.values())
|
|
@@ -521,8 +558,9 @@ export function createAcpDriver(support) {
|
|
|
521
558
|
if (idleTimer)
|
|
522
559
|
clearTimeout(idleTimer);
|
|
523
560
|
idleTimer = setTimeout(() => {
|
|
524
|
-
// Waiting for a person
|
|
525
|
-
|
|
561
|
+
// Waiting for a person, or for a tool the agent is running,
|
|
562
|
+
// is not an unresponsive agent.
|
|
563
|
+
if (session.current?.asks.size || session.current?.runningTools.size) {
|
|
526
564
|
armIdle();
|
|
527
565
|
return;
|
|
528
566
|
}
|
|
@@ -639,6 +677,7 @@ export function createAcpDriver(support) {
|
|
|
639
677
|
// server→client permission request → canonical request.opened,
|
|
640
678
|
// answered fail-closed for the running turn
|
|
641
679
|
const handleServerRequest = (msg, current) => {
|
|
680
|
+
current.state.startupActivity = true;
|
|
642
681
|
if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
|
|
643
682
|
void handleClientFileRequest(msg);
|
|
644
683
|
return;
|
|
@@ -683,7 +722,8 @@ export function createAcpDriver(support) {
|
|
|
683
722
|
}
|
|
684
723
|
const tool = kind === "execute" ? "shell" : kind === "edit" ? "edit" : kind || "tool";
|
|
685
724
|
const isShellCommand = !isQuestion && kind === "execute" && !/^mcp(?:__|[.:])/i.test(String(toolCall.title ?? ""));
|
|
686
|
-
const
|
|
725
|
+
const rawSummary = String(toolCall.rawInput?.command ?? toolCall.title ?? tool);
|
|
726
|
+
const summary = rawSummary.slice(0, isQuestion ? MAX_QUESTION_TEXT : 200);
|
|
687
727
|
// One structured question beside the flat choices: the richer card
|
|
688
728
|
// renders from it while older clients keep answering through
|
|
689
729
|
// `choices`. Built once here so the emit and the answer path can
|
|
@@ -778,6 +818,9 @@ export function createAcpDriver(support) {
|
|
|
778
818
|
current.receiveModelVariants(p.update);
|
|
779
819
|
return;
|
|
780
820
|
}
|
|
821
|
+
if (current && ["agent_message_chunk", "agent_thought_chunk", "tool_call", "tool_call_update"].includes(p.update?.sessionUpdate)) {
|
|
822
|
+
current.state.startupActivity = true;
|
|
823
|
+
}
|
|
781
824
|
if (!current || !current.state.promptSent)
|
|
782
825
|
return;
|
|
783
826
|
const u = p.update ?? {};
|
|
@@ -811,6 +854,7 @@ export function createAcpDriver(support) {
|
|
|
811
854
|
}
|
|
812
855
|
case "tool_call": {
|
|
813
856
|
current.flushAssistantText();
|
|
857
|
+
trackRunningTool(current, u, "pending");
|
|
814
858
|
emit({
|
|
815
859
|
...base(threadId, current.turnId),
|
|
816
860
|
type: "item.started",
|
|
@@ -823,6 +867,7 @@ export function createAcpDriver(support) {
|
|
|
823
867
|
break;
|
|
824
868
|
}
|
|
825
869
|
case "tool_call_update": {
|
|
870
|
+
trackRunningTool(current, u);
|
|
826
871
|
if (u.status === "completed" || u.status === "failed") {
|
|
827
872
|
current.state.producedItem = true;
|
|
828
873
|
emit({
|
|
@@ -1010,8 +1055,8 @@ export function createAcpDriver(support) {
|
|
|
1010
1055
|
// transformEnv-policy supports (opencode). mcpServers are session
|
|
1011
1056
|
// establishment inputs — they ride session/new and session/load over
|
|
1012
1057
|
// the wire — and the harness mints fresh integration bearer tokens
|
|
1013
|
-
// every turn
|
|
1014
|
-
//
|
|
1058
|
+
// every turn. Most agents apply changes on live load; those that cache
|
|
1059
|
+
// the old MCP clients must resume on a fresh process (see sessionKey).
|
|
1015
1060
|
// The env the spawned child actually receives is part of the
|
|
1016
1061
|
// contract too, and arrives hashed as envFingerprint for the same
|
|
1017
1062
|
// reason.
|
|
@@ -1030,7 +1075,8 @@ export function createAcpDriver(support) {
|
|
|
1030
1075
|
}
|
|
1031
1076
|
const pooled = sessions.get(threadId);
|
|
1032
1077
|
let session;
|
|
1033
|
-
if (pooled && !pooled.dead && !pooled.closing && pooled.contractKey === contractKey
|
|
1078
|
+
if (pooled && !pooled.dead && !pooled.closing && pooled.contractKey === contractKey
|
|
1079
|
+
&& (!support.restartOnMcpChange || pooled.sessionKey === sessionKey)) {
|
|
1034
1080
|
// adoption cancels the idle countdown — a running turn is not quiet
|
|
1035
1081
|
if (pooled.idleTimer)
|
|
1036
1082
|
clearTimeout(pooled.idleTimer);
|
|
@@ -1053,7 +1099,13 @@ export function createAcpDriver(support) {
|
|
|
1053
1099
|
// respawns the child, every wire call must reach the live record, so
|
|
1054
1100
|
// nothing captures the connection off it.
|
|
1055
1101
|
const request = (method, params, timeoutMs, receive, idleMs, idleMessage) => session.acp.request(method, params, timeoutMs, receive, idleMs, idleMessage);
|
|
1056
|
-
const state = { settled: false, promptSent: false, text: "", producedItem: false };
|
|
1102
|
+
const state = { settled: false, promptSent: false, text: "", producedItem: false, startupActivity: false, stopped: false };
|
|
1103
|
+
let acknowledge = () => { };
|
|
1104
|
+
let rejectStartup = (_error) => { };
|
|
1105
|
+
const startupAck = turn.startupRecovery ? new Promise((resolve, reject) => {
|
|
1106
|
+
acknowledge = () => resolve({ turnId });
|
|
1107
|
+
rejectStartup = reject;
|
|
1108
|
+
}) : null;
|
|
1057
1109
|
const asks = new Map();
|
|
1058
1110
|
const modelOf = (result) => {
|
|
1059
1111
|
const option = (Array.isArray(result?.configOptions) ? result.configOptions : []).find((entry) => entry?.id === (support.selectModel?.configId ?? "model"));
|
|
@@ -1101,12 +1153,15 @@ export function createAcpDriver(support) {
|
|
|
1101
1153
|
turnConfig,
|
|
1102
1154
|
controlsHost,
|
|
1103
1155
|
state,
|
|
1156
|
+
acknowledge,
|
|
1104
1157
|
asks,
|
|
1158
|
+
runningTools: new Set(),
|
|
1105
1159
|
interruptTimer: null,
|
|
1106
1160
|
flushAssistantText,
|
|
1107
1161
|
receiveModelVariants,
|
|
1108
1162
|
};
|
|
1109
1163
|
const interrupt = () => {
|
|
1164
|
+
state.stopped = true;
|
|
1110
1165
|
if (session.sessionId) {
|
|
1111
1166
|
session.acp.send({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId: session.sessionId } });
|
|
1112
1167
|
if (current.interruptTimer)
|
|
@@ -1123,7 +1178,7 @@ export function createAcpDriver(support) {
|
|
|
1123
1178
|
closeSession(threadId, "stop");
|
|
1124
1179
|
}
|
|
1125
1180
|
};
|
|
1126
|
-
active.set(threadId, { stop: () => closeSession(threadId, "stop"), interrupt, turnId, asks });
|
|
1181
|
+
active.set(threadId, { stop: () => { state.stopped = true; closeSession(threadId, "stop"); }, interrupt, turnId, asks });
|
|
1127
1182
|
emit({ ...base(threadId, turnId), type: "turn.started" });
|
|
1128
1183
|
session.current = current;
|
|
1129
1184
|
(async () => {
|
|
@@ -1134,6 +1189,19 @@ export function createAcpDriver(support) {
|
|
|
1134
1189
|
// Returns whether this runtime accepts image prompts, for the
|
|
1135
1190
|
// prompt phase below.
|
|
1136
1191
|
const handshake = async () => {
|
|
1192
|
+
// Also covers timeout/reset/re-establishment, not just rotated
|
|
1193
|
+
// MCP credentials. Stop remains wired while cleanup is pending.
|
|
1194
|
+
for (const child of retiring.get(threadId) ?? []) {
|
|
1195
|
+
const stopped = await retireChild(threadId, child);
|
|
1196
|
+
if (state.settled || session.closing)
|
|
1197
|
+
throw new Error("session closed");
|
|
1198
|
+
if (!stopped) {
|
|
1199
|
+
closeSession(threadId, "replace-failed");
|
|
1200
|
+
throw new Error(`${support.displayName} could not close its previous tool session. Try again.`);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (state.settled || session.closing)
|
|
1204
|
+
throw new Error("session closed");
|
|
1137
1205
|
if (!session.initResult) {
|
|
1138
1206
|
session.initResult = await request("initialize", {
|
|
1139
1207
|
protocolVersion: 1,
|
|
@@ -1356,10 +1424,13 @@ export function createAcpDriver(support) {
|
|
|
1356
1424
|
if (turn.variant !== undefined && requestedVariantOption().currentValue !== turn.variant) {
|
|
1357
1425
|
throw new Error(`${support.displayName} changed variant before the prompt`);
|
|
1358
1426
|
}
|
|
1427
|
+
if (state.settled || state.stopped || session.closing)
|
|
1428
|
+
throw new Error("turn stopped");
|
|
1359
1429
|
state.promptSent = true;
|
|
1430
|
+
acknowledge();
|
|
1360
1431
|
const promptIdleMs = promptIdleTimeoutMs();
|
|
1361
|
-
const result = await request("session/prompt", { sessionId, prompt: [{ type: "text", text }, ...imageBlocks] }, undefined, undefined, promptIdleMs, `${DRIVER_KIND}
|
|
1362
|
-
"
|
|
1432
|
+
const result = await request("session/prompt", { sessionId, prompt: [{ type: "text", text }, ...imageBlocks] }, undefined, undefined, promptIdleMs, `${DRIVER_KIND} sent nothing for ${Math.round(promptIdleMs / 1000)} s with no tool running, so the turn was stopped as stuck. ` +
|
|
1433
|
+
"Send the message again to retry. On a self-hosted server, OPENMAUS_ACP_PROMPT_IDLE_TIMEOUT_MS sets this limit (0 turns it off).");
|
|
1363
1434
|
if (pendingSplitReceipt) {
|
|
1364
1435
|
// session/prompt resolving is the acceptance boundary: a
|
|
1365
1436
|
// rejected prompt leaves the receipt unwritten, so the next
|
|
@@ -1405,6 +1476,35 @@ export function createAcpDriver(support) {
|
|
|
1405
1476
|
// fallback for existing ACP supports.
|
|
1406
1477
|
const needsAuth = code === "invalid_credentials" || code === "inactive_subscription"
|
|
1407
1478
|
|| message === support.loginNote;
|
|
1479
|
+
const failure = classifyError({ text: message });
|
|
1480
|
+
const transientStartup = failure.transient || (failure.reason === "unknown" && code === "upstream_outage");
|
|
1481
|
+
const denied = /\b(?:(?:permission|access) denied|(?:approval|permission) (?:required|denied|rejected)|requires? (?:approval|permission)|policy (?:restriction|violation)|(?:blocked|denied|restricted) by (?:the )?policy)\b/i.test(message);
|
|
1482
|
+
if (turn.startupRecovery && transientStartup && !needsAuth && !denied && (!code || code === "upstream_outage")
|
|
1483
|
+
&& ![-32700, -32600, -32601, -32602].includes(e?.code)
|
|
1484
|
+
&& !state.promptSent && !state.startupActivity && !state.producedItem && !state.text
|
|
1485
|
+
&& !state.stopped && !session.closing && !asks.size && !current.runningTools.size) {
|
|
1486
|
+
// Quiesce before killing: the child's close event must not
|
|
1487
|
+
// publish a terminal completion before the harness can recover.
|
|
1488
|
+
state.settled = true;
|
|
1489
|
+
session.current = null;
|
|
1490
|
+
if (current.interruptTimer)
|
|
1491
|
+
clearTimeout(current.interruptTimer);
|
|
1492
|
+
closeSession(threadId, "startup-recovery");
|
|
1493
|
+
const stopped = await killCliTree(session.child).catch(() => false);
|
|
1494
|
+
if (current.interruptTimer)
|
|
1495
|
+
clearTimeout(current.interruptTimer);
|
|
1496
|
+
active.delete(threadId);
|
|
1497
|
+
if (stopped && !state.stopped) {
|
|
1498
|
+
rejectStartup(new TurnNotStartedError(turnId, message));
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
acknowledge();
|
|
1502
|
+
if (!state.stopped)
|
|
1503
|
+
emit({ ...base(threadId, turnId), type: "runtime.error", message });
|
|
1504
|
+
emit({ ...base(threadId, turnId), type: "turn.completed", ok: state.stopped,
|
|
1505
|
+
stopReason: state.stopped ? "cancelled" : "rpc_error", cost: null });
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1408
1508
|
emit({
|
|
1409
1509
|
...base(threadId, turnId),
|
|
1410
1510
|
type: "runtime.error",
|
|
@@ -1424,7 +1524,7 @@ export function createAcpDriver(support) {
|
|
|
1424
1524
|
}
|
|
1425
1525
|
}
|
|
1426
1526
|
})();
|
|
1427
|
-
return { turnId };
|
|
1527
|
+
return startupAck ?? { turnId };
|
|
1428
1528
|
};
|
|
1429
1529
|
const snapshot = async () => {
|
|
1430
1530
|
const env = childEnv();
|
|
@@ -1481,6 +1581,10 @@ export function createAcpDriver(support) {
|
|
|
1481
1581
|
// idle pooled sessions have no running turn — close them too
|
|
1482
1582
|
for (const threadId of Array.from(sessions.keys()))
|
|
1483
1583
|
closeSession(threadId, "stopAll");
|
|
1584
|
+
for (const [threadId, children] of retiring) {
|
|
1585
|
+
for (const child of children)
|
|
1586
|
+
void retireChild(threadId, child);
|
|
1587
|
+
}
|
|
1484
1588
|
},
|
|
1485
1589
|
onEvent: (listener) => {
|
|
1486
1590
|
listeners.add(listener);
|
|
@@ -1492,6 +1596,10 @@ export function createAcpDriver(support) {
|
|
|
1492
1596
|
stop();
|
|
1493
1597
|
for (const threadId of Array.from(sessions.keys()))
|
|
1494
1598
|
closeSession(threadId, "dispose");
|
|
1599
|
+
for (const [threadId, children] of retiring) {
|
|
1600
|
+
for (const child of children)
|
|
1601
|
+
void retireChild(threadId, child);
|
|
1602
|
+
}
|
|
1495
1603
|
listeners.clear();
|
|
1496
1604
|
},
|
|
1497
1605
|
};
|
|
@@ -235,6 +235,9 @@ const support = {
|
|
|
235
235
|
// A raw -m only changes the model within the saved provider. ACP switches
|
|
236
236
|
// the complete route and confirms it before any prompt leaves OMB.
|
|
237
237
|
spawnArgs: (config, turn) => ["--acp", ...qwenApprovalArgs(config.fullAuto, turn.approvalMode)],
|
|
238
|
+
// Qwen's live session/load returns its cached MCP clients, ignoring fresh
|
|
239
|
+
// credentials. Cold-load the same conversation when those inputs change.
|
|
240
|
+
restartOnMcpChange: true,
|
|
238
241
|
selectModel: { configId: "model" },
|
|
239
242
|
pickAuthMethod: () => null,
|
|
240
243
|
authFailure: "continue",
|
|
@@ -278,7 +278,7 @@ function routineFields(args) {
|
|
|
278
278
|
const runOn = destination(args.run_on ?? args.runOn);
|
|
279
279
|
const timeoutMinutes = args.timeout_minutes ?? args.timeoutMinutes;
|
|
280
280
|
if (runOn != null && runOn !== "maus" && runOn !== "cloud") {
|
|
281
|
-
return { fields, error: 'Use run_on="maus" for the bot’s current model and configured computer (including VPS), or run_on="box" only for the
|
|
281
|
+
return { fields, error: 'Use run_on="maus" for the bot’s current model and configured computer (including VPS), or run_on="box" only for the Boat-hosted agent. Legacy "cloud" also means Boat.' };
|
|
282
282
|
}
|
|
283
283
|
if (timeoutMinutes != null && (typeof timeoutMinutes !== "number" || !Number.isInteger(timeoutMinutes) || timeoutMinutes < 5 || timeoutMinutes > 240)) {
|
|
284
284
|
return { fields, error: "timeout_minutes must be a whole number from 5 to 240. Use clear_timeout to remove a limit." };
|
|
@@ -982,11 +982,14 @@ export async function callTool(name, args, context) {
|
|
|
982
982
|
if (!routineId || !action) {
|
|
983
983
|
return { text: "propose_routine_action needs a routine_id and supported action.", isError: true };
|
|
984
984
|
}
|
|
985
|
+
const forBotId = String(args.for_bot_id ?? "").trim();
|
|
985
986
|
const body = {
|
|
986
987
|
fromBotId: BOT_ID,
|
|
987
988
|
fromThreadId: THREAD_ID,
|
|
988
989
|
action,
|
|
989
990
|
routineId,
|
|
991
|
+
// JSON.stringify drops the key entirely when no target was named
|
|
992
|
+
...(forBotId ? { forBotId } : {}),
|
|
990
993
|
};
|
|
991
994
|
if (action === "update") {
|
|
992
995
|
if (!jsonRecord(args.changes)) {
|
|
@@ -1098,6 +1101,7 @@ export async function callTool(name, args, context) {
|
|
|
1098
1101
|
action: args.action,
|
|
1099
1102
|
text: args.text,
|
|
1100
1103
|
oldText: args.old_text,
|
|
1104
|
+
...(typeof args.until === "string" && args.until.trim() ? { until: args.until.trim() } : {}),
|
|
1101
1105
|
}),
|
|
1102
1106
|
});
|
|
1103
1107
|
if (r.error || r.ok !== true) {
|
|
@@ -128,8 +128,9 @@ const ROUTINE_FIELDS_SCHEMA = {
|
|
|
128
128
|
schedule: ROUTINE_SCHEDULE_SCHEMA,
|
|
129
129
|
run_on: {
|
|
130
130
|
type: "string",
|
|
131
|
+
// "box" is Boat's historical run_on destination id (agents wire contract).
|
|
131
132
|
enum: ["maus", "box"],
|
|
132
|
-
description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box explicitly switches the agent to the
|
|
133
|
+
description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box explicitly switches the agent to the Boat-hosted runner; it requires Boat setup and is not the generic cloud/VPS option. Legacy cloud values from list_routines mean box, not VPS.",
|
|
133
134
|
},
|
|
134
135
|
timeout_minutes: {
|
|
135
136
|
type: "integer",
|
|
@@ -293,7 +294,7 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
293
294
|
description: "Choose where this conversation does computer work. Call with no arguments to inspect actual available choices and the current place. For a task needing computer interaction, select the requested place, or auto to choose a suitable configured computer without asking the user to use menus. OpenMausBot reuses an existing computer first; with a configured provider it can start or provision one when needed. Do not provision for ordinary chat or just to inspect availability. A pending result means end this turn immediately: OpenMausBot updates the conversation selector and resumes the original request with that computer's real tools. Do not use the old tools after requesting a switch, repeat the task, or claim the action is done. This cannot change permissions, override Off, or switch a teammate/routine/channel.",
|
|
294
295
|
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
295
296
|
surface: { type: "string", enum: ["auto", "cloud", "vm", "local", "browser"],
|
|
296
|
-
description: "auto = suitable configured computer, cloud = remote
|
|
297
|
+
description: "auto = suitable configured computer, cloud = remote Boat/VPS, vm = isolated Local VM, local = user's own desktop, browser = built-in browser. Omit to list." },
|
|
297
298
|
} },
|
|
298
299
|
},
|
|
299
300
|
{
|
|
@@ -523,6 +524,7 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
523
524
|
action: { type: "string", enum: ["append", "replace", "remove", "supersede"] },
|
|
524
525
|
text: { type: "string", minLength: 1, description: "Non-blank new text for append, replace, or supersede: the fact itself, without a date or bullet. Omit for remove; use remove to delete a passage." },
|
|
525
526
|
old_text: { type: "string", minLength: 1, description: "Exact unique existing passage for replace, supersede, or remove. Omit for append." },
|
|
527
|
+
until: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Optional, for append or supersede: YYYY-MM-DD, the last day a temporary fact holds (an exam this weekend, a trip next week). After that day the entry is hidden from your memory." },
|
|
526
528
|
},
|
|
527
529
|
required: ["action"],
|
|
528
530
|
},
|
|
@@ -614,12 +616,16 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
614
616
|
},
|
|
615
617
|
{
|
|
616
618
|
name: "propose_routine_action",
|
|
617
|
-
description: "Prepare a user-requested change to one of this bot's existing routines. Use list_routines first to get the routine id." + PROPOSAL_OUTCOME,
|
|
619
|
+
description: "Prepare a user-requested change to one of this bot's existing routines. Use list_routines first to get the routine id. If the user asks to change ANOTHER bot's routine and that bot is in your section, call list_bots and pass that bot's id as for_bot_id; the routine keeps its owner and every run keeps that bot's engine and permissions." + PROPOSAL_OUTCOME,
|
|
618
620
|
inputSchema: {
|
|
619
621
|
type: "object",
|
|
620
622
|
additionalProperties: false,
|
|
621
623
|
properties: {
|
|
622
624
|
routine_id: { type: "string", minLength: 1, description: "Routine id from list_routines." },
|
|
625
|
+
for_bot_id: {
|
|
626
|
+
type: "string",
|
|
627
|
+
description: "Only when the requested change targets ANOTHER bot's routine and that bot is in your section: that bot's id from list_bots. Learn the routine id from that bot's own routines (it can run list_routines). Omit to change one of your own routines.",
|
|
628
|
+
},
|
|
623
629
|
action: {
|
|
624
630
|
type: "string",
|
|
625
631
|
enum: ["update", "pause", "resume", "run_now", "delete"],
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { newEventId, newId } from "../contracts.js";
|
|
2
2
|
import { appendNative } from "./native.js";
|
|
3
3
|
import { OMB_ASK_TOOL, answerWithoutPreamble, askQuestionSummary, capAnswerEcho, ombAskProtocolPrompt, parseOmbAskQuestions, questionChoices, stripOmbAskBlock, } from "../../shared/ask-question.js";
|
|
4
|
+
// "boxAgent" is Boat's historical driver kind (catalog and wire); keep it.
|
|
4
5
|
const DRIVER_KIND = "boxAgent";
|
|
5
6
|
// overridable so tests and a dev backend can be pointed at instead of the live provider
|
|
6
|
-
const
|
|
7
|
+
const BOAT_API = process.env.OMB_BOX_API || "https://ascii.dev/api/box/v1";
|
|
7
8
|
const MODELS = {
|
|
8
9
|
default: "claude-fable-5",
|
|
9
10
|
options: [
|
|
@@ -15,14 +16,14 @@ const MODELS = {
|
|
|
15
16
|
/** Any fence whose info string names the ask protocol, even when its body
|
|
16
17
|
* does not parse: the marker for "the model tried to ask and failed". */
|
|
17
18
|
const ASK_FENCE_ANY = /(^|\n)[ \t]{0,3}(`{3,}|~{3,})[ \t]*omb-ask\b/;
|
|
18
|
-
/** The
|
|
19
|
+
/** The boat runs every harness boat.dev ships (claude-code, codex, pi, opencode,
|
|
19
20
|
* prime-agent, kimi). Which one a model id belongs to comes from the public
|
|
20
21
|
* catalog, `GET /api/provider-models` at the API root: an object keyed by
|
|
21
22
|
* harness, each with its `models`. A bot that arrives here from another engine
|
|
22
23
|
* carries that engine's model id, so this is what lets it keep its model. */
|
|
23
24
|
let catalog = null;
|
|
24
25
|
async function loadCatalog() {
|
|
25
|
-
const root =
|
|
26
|
+
const root = BOAT_API.replace(/\/api\/box\/v1\/?$/, "");
|
|
26
27
|
catalog = await fetch(`${root}/api/provider-models`, { signal: AbortSignal.timeout(15_000) })
|
|
27
28
|
.then((res) => (res.ok ? res.json() : null))
|
|
28
29
|
.catch(() => null);
|
|
@@ -44,7 +45,7 @@ function decodeConfig(raw) {
|
|
|
44
45
|
...(typeof o.askTimeoutMs === "number" ? { askTimeoutMs: o.askTimeoutMs } : {}),
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
|
-
export const
|
|
48
|
+
export const BoatAgentDriver = {
|
|
48
49
|
driverKind: DRIVER_KIND,
|
|
49
50
|
metadata: { displayName: "Computer", supportsMultipleInstances: false },
|
|
50
51
|
models: MODELS,
|
|
@@ -74,7 +75,7 @@ export const BoxAgentDriver = {
|
|
|
74
75
|
createdAt: new Date().toISOString(),
|
|
75
76
|
});
|
|
76
77
|
const api = async (path, opts = {}) => {
|
|
77
|
-
const res = await fetch(`${
|
|
78
|
+
const res = await fetch(`${BOAT_API}${path}`, {
|
|
78
79
|
...opts,
|
|
79
80
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json", ...opts.headers },
|
|
80
81
|
signal: opts.signal ?? AbortSignal.timeout(30_000),
|
|
@@ -120,7 +121,7 @@ export const BoxAgentDriver = {
|
|
|
120
121
|
});
|
|
121
122
|
appendNative(threadId, { dir: "out", source: "box.prompt", msg: { model, prompt: promptText, response: started } });
|
|
122
123
|
// real shape (2026-08): {type:"prompt.queued", promptId, promptRun:{id,…},
|
|
123
|
-
// id:<
|
|
124
|
+
// id:<boat id>} — never fall back to the bare id, it's the boat's
|
|
124
125
|
return started?.promptRun?.id ?? started?.prompt?.id ?? started?.promptId ?? null;
|
|
125
126
|
};
|
|
126
127
|
const promptId = await postPrompt(prompt);
|
|
@@ -149,7 +150,7 @@ export const BoxAgentDriver = {
|
|
|
149
150
|
const seen = new Set();
|
|
150
151
|
let lastText = "";
|
|
151
152
|
let pendingText = "";
|
|
152
|
-
/** Why the
|
|
153
|
+
/** Why the boat could not answer (login expired, model refused, …). */
|
|
153
154
|
let problem = null;
|
|
154
155
|
/** Emit unflushed deltas as assistant_text and reset pendingText.
|
|
155
156
|
* The omb-ask block is protocol, not prose: it streamed raw (the
|
|
@@ -229,7 +230,7 @@ export const BoxAgentDriver = {
|
|
|
229
230
|
return { ok: ok && !cancelled, stopReason: cancelled ? "interrupted" : stopReason };
|
|
230
231
|
}
|
|
231
232
|
// The turn is still open, so this prompt continues it: same turnId,
|
|
232
|
-
// same accounting, and the answer reaches the
|
|
233
|
+
// same accounting, and the answer reaches the boat the way the ask
|
|
233
234
|
// contract promised — Q:/A: blocks, capped for echo.
|
|
234
235
|
const continuation = [
|
|
235
236
|
capAnswerEcho(answerWithoutPreamble(reply)),
|
|
@@ -238,7 +239,7 @@ export const BoxAgentDriver = {
|
|
|
238
239
|
].join("\n");
|
|
239
240
|
const nextPromptId = await postPrompt(continuation);
|
|
240
241
|
// Stop can land while the continuation POST is in flight: the
|
|
241
|
-
// interrupt inside cancel() then hits a
|
|
242
|
+
// interrupt inside cancel() then hits a boat with no active run,
|
|
242
243
|
// and the continuation would start after it. Interrupt the run
|
|
243
244
|
// that just started before ending the turn.
|
|
244
245
|
if (cancelled) {
|
|
@@ -247,9 +248,9 @@ export const BoxAgentDriver = {
|
|
|
247
248
|
}
|
|
248
249
|
return await settleRun(nextPromptId);
|
|
249
250
|
};
|
|
250
|
-
/** Poll one
|
|
251
|
+
/** Poll one boat run to its settle. */
|
|
251
252
|
const settleRun = async (runPromptId) => {
|
|
252
|
-
const startedAt = Date.now(); // one 30-min ceiling per
|
|
253
|
+
const startedAt = Date.now(); // one 30-min ceiling per boat run, ask chains included
|
|
253
254
|
lastText = "";
|
|
254
255
|
pendingText = "";
|
|
255
256
|
problem = null;
|
|
@@ -355,7 +356,7 @@ export const BoxAgentDriver = {
|
|
|
355
356
|
};
|
|
356
357
|
const snapshot = async () => {
|
|
357
358
|
if (!token) {
|
|
358
|
-
return { state: "unavailable", reason: 'no
|
|
359
|
+
return { state: "unavailable", reason: 'no Boat token — add {"box":{"token":"…"}} to ~/.openmausbot/config.json' };
|
|
359
360
|
}
|
|
360
361
|
try {
|
|
361
362
|
await api("/me");
|
|
@@ -374,7 +375,7 @@ export const BoxAgentDriver = {
|
|
|
374
375
|
snapshot,
|
|
375
376
|
adapter: {
|
|
376
377
|
provider: DRIVER_KIND,
|
|
377
|
-
capabilities: { sessionModelSwitch: "in-session" },
|
|
378
|
+
capabilities: { sessionModelSwitch: "in-session", remoteAgent: true, usesCloudComputer: true },
|
|
378
379
|
sendTurn,
|
|
379
380
|
interruptTurn: async (threadId) => active.get(threadId)?.cancel(),
|
|
380
381
|
respondToRequest: async (threadId, requestId, decision) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AntigravityDriver } from "./antigravity.js";
|
|
2
|
-
import {
|
|
2
|
+
import { BoatAgentDriver } from "./boatagent.js";
|
|
3
3
|
import { ClaudeDriver } from "./claude.js";
|
|
4
4
|
import { CodexDriver } from "./codex.js";
|
|
5
5
|
import { GrokDriver } from "./grok.js";
|
|
@@ -32,7 +32,7 @@ export const BUILT_IN_DRIVERS = [
|
|
|
32
32
|
ClaudeDriver,
|
|
33
33
|
CodexDriver,
|
|
34
34
|
AntigravityDriver,
|
|
35
|
-
|
|
35
|
+
BoatAgentDriver,
|
|
36
36
|
MinimaxDriver,
|
|
37
37
|
MistralDriver,
|
|
38
38
|
];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isolatedRemoteCommand, runCommand,
|
|
1
|
+
import { isolatedRemoteCommand, runCommand, screenshotBoat } from "../boat.js";
|
|
2
2
|
import { createControlClient, CONTROL_REFUSAL_PLAIN } from "../control-client.js";
|
|
3
3
|
const coordinate = { type: "integer", minimum: 0, maximum: 32767 };
|
|
4
4
|
const tool = (name, description, properties = {}, required = []) => ({ name, description, inputSchema: { type: "object", properties, required, additionalProperties: false } });
|
|
@@ -19,7 +19,7 @@ const buttons = { left: 1, middle: 2, right: 3 };
|
|
|
19
19
|
const scrollButtons = { up: 4, down: 5, left: 6, right: 7 };
|
|
20
20
|
/** Uses only the leased descriptor supplied by the harness. Schema validation
|
|
21
21
|
* and user approval happen in the shared chat tool executor before this call. */
|
|
22
|
-
export class
|
|
22
|
+
export class ChatBoatClient {
|
|
23
23
|
closed = false;
|
|
24
24
|
control;
|
|
25
25
|
descriptor;
|
|
@@ -45,7 +45,7 @@ export class ChatBoxClient {
|
|
|
45
45
|
const config = { box: { token: this.descriptor.token } };
|
|
46
46
|
const id = this.descriptor.boxId;
|
|
47
47
|
if (name === "screenshot") {
|
|
48
|
-
const shot = await
|
|
48
|
+
const shot = await screenshotBoat(config, "", id, { signal, nativeSize: true });
|
|
49
49
|
signal.throwIfAborted();
|
|
50
50
|
return { content: [{ type: "image", mimeType: "image/jpeg", data: shot.png }] };
|
|
51
51
|
}
|
|
@@ -7,7 +7,7 @@ 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 {
|
|
10
|
+
import { ChatBoatClient } from "./chat-boat-tools.js";
|
|
11
11
|
/** The transport cannot safely continue this turn. A dispatched operation may
|
|
12
12
|
* already have taken effect, so callers must not retry it through a new round. */
|
|
13
13
|
export class ChatToolSessionError extends Error {
|
|
@@ -314,7 +314,7 @@ export async function mountChatTools(integrations, signal, computerUse = false)
|
|
|
314
314
|
throw aborted();
|
|
315
315
|
// Every mounted MCP server can return images when the caller enables
|
|
316
316
|
// image delivery, including custom servers. Text stays bounded below.
|
|
317
|
-
const client = "boxId" in descriptor ? new
|
|
317
|
+
const client = "boxId" in descriptor ? new ChatBoatClient(descriptor) : new ChatMcpClient(descriptor, computerUse);
|
|
318
318
|
clients.push(client);
|
|
319
319
|
return { name, client, tools: await client.tools(signal) };
|
|
320
320
|
}));
|