openmausbot 0.1.88 → 0.1.89
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-BwaMaZEm.js} +1 -1
- package/dist/assets/{arc-3RwkUjRe.js → arc-Be-8GKxd.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CFKSJS7a.js → architectureDiagram-NJMV4G6O-Dp_U2Kv1.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CG1rEfWD.js → blockDiagram-BEXU5L5S-DQjUjYUk.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-CBW9N664.js → c4Diagram-YGBWAQC7-Bd3aE4Kc.js} +1 -1
- package/dist/assets/channel-DqY_gMLg.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-2mRfTksB.js → chunk-2Q5K7J3B-BijjTBeJ.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-Cv1dpErj.js → chunk-3FUC2YCW-CMGdwvzW.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-B7IACJp-.js → chunk-5VM5RSS4-0G09BfsZ.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-An0hZtYa.js → chunk-GTNCS2PH-FOp3vSHe.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BcJB2nEm.js → chunk-GWA4HPMP-Dv-20VIn.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CV_W7TN7.js → chunk-JWPE2WC7-CRgrdFcz.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-Bcs_kwnA.js → chunk-NETBCI7D-3hP4f7QB.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-lrx_DxFP.js → chunk-WEXAMYUT-CG98IjlG.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-DabHlLGM.js → chunk-XXDRQBXY-BrSTC9xG.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BnHj6P7d.js → classDiagram-v2-NBCMYWYE-rpQQQDFX.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CfLp6UDi.js → cose-bilkent-JH36ORCC-G6RSafHA.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-Dm4dbVA1.js → cynefin-OW5HDTMX-CXjv-IM2.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-B9mH-PID.js → cynefinDiagram-VND7K2PF-Dw07MR9f.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DDc_NVQJ.js → dagre-6A5THRUB-BwPnlTg2.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-lwr8Ip8-.js → diagram-22UHCM2B-Cm6xcMiz.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BLLe6dmI.js → diagram-3UASUU5V-CDpogPbE.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-6eUIq4lz.js → diagram-ATOU4E4O-BV8z3Pnc.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-O-pRN7VQ.js → diagram-CDSNMT55-CKCQhOOC.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-BietvGAI.js → diagram-MLGK6HIB-FGvJg69C.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-Bd2Cl62X.js → diagram-MPIPVDR6-CwATvgnS.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-B6iDy69W.js → ebnfDiagram-ZINNZB2B-CH_c3SSC.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-DQVHfKe0.js → elk-276RUBZZ-3guZi71R.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BWdVp2tQ.js → erDiagram-OPXOYQCR-BMHphR2n.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-WLE6rKB4.js → flowDiagram-KWPJA3E3-Vm5DkW5x.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BbjS-4gn.js → ganttDiagram-FUAMR5RP-DO-1ct07.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Dpy2QSGq.js → gitGraphDiagram-X574FWY7-nxeksEIN.js} +1 -1
- package/dist/assets/{index-CHnODCr2.js → index-BVPdbq2r.js} +142 -142
- package/dist/assets/{index-CIaHn5NS.js → index-Oxk9x45i.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-D_jKWlKe.js → infoDiagram-VRGFBTTK-Dkcvr0p7.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DLJohSuK.js → ishikawaDiagram-OU5B5YK6-BwVibkoO.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DPOFj0qN.js → journeyDiagram-ZHPQQLJL-ByiBZqgG.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BI7v-xpK.js → kanban-definition-PNTS6WVX-qqjgXBF3.js} +1 -1
- package/dist/assets/{linear-C2pWPSxO.js → linear-CtJnKe6A.js} +1 -1
- package/dist/assets/{mermaid.core-CR-j5WQU.js → mermaid.core-BEiDklrY.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-lz9GvXRh.js → mindmap-definition-NLK3R4M7-DfIYHia9.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-Dl4_ia61.js → pegDiagram-GJSIUBJH-CYyRqa1a.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DWuT3n88.js → pieDiagram-5QR66LMP-hOToy_zH.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-DXWe9Jx7.js → quadrantDiagram-O4NWA36T-DKAsNN9I.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-COY1IBVY.js → railroadDiagram-XR7U4H2S-BSA-d8JU.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DnyVuhsQ.js → requirementDiagram-PLB6GJNP-CSmlvb-p.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-BSc4NdGK.js → sankeyDiagram-IPEJSGJF-CD5F6fnG.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-kqEU2EGq.js → sequenceDiagram-PO4LG4MO-JUq5sx6d.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BvB4bkWD.js → sizeCapture-INFHLROL-BjIS-Stk.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BIrqNv7y.js → stateDiagram-v2-GCMORJYK-DklkUxOg.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-CFnalQUE.js → swimlanes-2SLR337P-CsjIU6be.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-Bk14M0_B.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CsSWJnSQ.js → timeline-definition-EJHVYXUP-BkFqK7AB.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DkAmJ1EE.js → usecaseDiagram-POWQR4AR-70gJB1EU.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-C3vd9drL.js → vennDiagram-UO4OBE2U-83Tm62dN.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-j26CM-pi.js → wardleyDiagram-VNRHLVJA-tTmseoX9.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BYu1Q_at.js → xychartDiagram-PMCCYNJV-R1LBA55a.js} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/container-mcp.js +8 -0
- package/dist-server/index.js +44902 -44655
- package/dist-server/openmausbot.js +219 -110
- package/dist-server/pair-cli.js +219 -110
- package/dist-server/server/automatic-recovery.js +15 -0
- package/dist-server/server/cli-api-setup.js +0 -1
- package/dist-server/server/config.js +9 -0
- package/dist-server/server/container-computer.js +10 -0
- package/dist-server/server/contracts.js +10 -0
- package/dist-server/server/drivers/acp/core.js +124 -17
- package/dist-server/server/drivers/acp/qwen.js +3 -0
- package/dist-server/server/drivers/boxagent.js +1 -1
- package/dist-server/server/drivers/openai-chat.js +5 -1
- package/dist-server/server/harness-capabilities.js +18 -3
- package/dist-server/server/index.js +139 -56
- package/dist-server/server/system-prompt.js +11 -0
- package/dist-server/server/webhooks.js +21 -3
- package/dist-server/src/lib/composer-attachments.js +17 -0
- package/dist-server/vps-container-mcp.js +8 -0
- package/dist-server/workspace-backup.worker.js +8 -0
- package/package.json +1 -1
- package/dist/assets/channel-CnRnO3tF.js +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +0 -8
|
@@ -40,7 +40,7 @@ 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
46
|
import { parseAskQuestions, parseChoices, questionAnswersByQuestion } from "../../../shared/ask-question.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"
|
|
@@ -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);
|
|
@@ -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;
|
|
@@ -778,6 +817,9 @@ export function createAcpDriver(support) {
|
|
|
778
817
|
current.receiveModelVariants(p.update);
|
|
779
818
|
return;
|
|
780
819
|
}
|
|
820
|
+
if (current && ["agent_message_chunk", "agent_thought_chunk", "tool_call", "tool_call_update"].includes(p.update?.sessionUpdate)) {
|
|
821
|
+
current.state.startupActivity = true;
|
|
822
|
+
}
|
|
781
823
|
if (!current || !current.state.promptSent)
|
|
782
824
|
return;
|
|
783
825
|
const u = p.update ?? {};
|
|
@@ -811,6 +853,7 @@ export function createAcpDriver(support) {
|
|
|
811
853
|
}
|
|
812
854
|
case "tool_call": {
|
|
813
855
|
current.flushAssistantText();
|
|
856
|
+
trackRunningTool(current, u, "pending");
|
|
814
857
|
emit({
|
|
815
858
|
...base(threadId, current.turnId),
|
|
816
859
|
type: "item.started",
|
|
@@ -823,6 +866,7 @@ export function createAcpDriver(support) {
|
|
|
823
866
|
break;
|
|
824
867
|
}
|
|
825
868
|
case "tool_call_update": {
|
|
869
|
+
trackRunningTool(current, u);
|
|
826
870
|
if (u.status === "completed" || u.status === "failed") {
|
|
827
871
|
current.state.producedItem = true;
|
|
828
872
|
emit({
|
|
@@ -1010,8 +1054,8 @@ export function createAcpDriver(support) {
|
|
|
1010
1054
|
// transformEnv-policy supports (opencode). mcpServers are session
|
|
1011
1055
|
// establishment inputs — they ride session/new and session/load over
|
|
1012
1056
|
// the wire — and the harness mints fresh integration bearer tokens
|
|
1013
|
-
// every turn
|
|
1014
|
-
//
|
|
1057
|
+
// every turn. Most agents apply changes on live load; those that cache
|
|
1058
|
+
// the old MCP clients must resume on a fresh process (see sessionKey).
|
|
1015
1059
|
// The env the spawned child actually receives is part of the
|
|
1016
1060
|
// contract too, and arrives hashed as envFingerprint for the same
|
|
1017
1061
|
// reason.
|
|
@@ -1030,7 +1074,8 @@ export function createAcpDriver(support) {
|
|
|
1030
1074
|
}
|
|
1031
1075
|
const pooled = sessions.get(threadId);
|
|
1032
1076
|
let session;
|
|
1033
|
-
if (pooled && !pooled.dead && !pooled.closing && pooled.contractKey === contractKey
|
|
1077
|
+
if (pooled && !pooled.dead && !pooled.closing && pooled.contractKey === contractKey
|
|
1078
|
+
&& (!support.restartOnMcpChange || pooled.sessionKey === sessionKey)) {
|
|
1034
1079
|
// adoption cancels the idle countdown — a running turn is not quiet
|
|
1035
1080
|
if (pooled.idleTimer)
|
|
1036
1081
|
clearTimeout(pooled.idleTimer);
|
|
@@ -1053,7 +1098,13 @@ export function createAcpDriver(support) {
|
|
|
1053
1098
|
// respawns the child, every wire call must reach the live record, so
|
|
1054
1099
|
// nothing captures the connection off it.
|
|
1055
1100
|
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 };
|
|
1101
|
+
const state = { settled: false, promptSent: false, text: "", producedItem: false, startupActivity: false, stopped: false };
|
|
1102
|
+
let acknowledge = () => { };
|
|
1103
|
+
let rejectStartup = (_error) => { };
|
|
1104
|
+
const startupAck = turn.startupRecovery ? new Promise((resolve, reject) => {
|
|
1105
|
+
acknowledge = () => resolve({ turnId });
|
|
1106
|
+
rejectStartup = reject;
|
|
1107
|
+
}) : null;
|
|
1057
1108
|
const asks = new Map();
|
|
1058
1109
|
const modelOf = (result) => {
|
|
1059
1110
|
const option = (Array.isArray(result?.configOptions) ? result.configOptions : []).find((entry) => entry?.id === (support.selectModel?.configId ?? "model"));
|
|
@@ -1101,12 +1152,15 @@ export function createAcpDriver(support) {
|
|
|
1101
1152
|
turnConfig,
|
|
1102
1153
|
controlsHost,
|
|
1103
1154
|
state,
|
|
1155
|
+
acknowledge,
|
|
1104
1156
|
asks,
|
|
1157
|
+
runningTools: new Set(),
|
|
1105
1158
|
interruptTimer: null,
|
|
1106
1159
|
flushAssistantText,
|
|
1107
1160
|
receiveModelVariants,
|
|
1108
1161
|
};
|
|
1109
1162
|
const interrupt = () => {
|
|
1163
|
+
state.stopped = true;
|
|
1110
1164
|
if (session.sessionId) {
|
|
1111
1165
|
session.acp.send({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId: session.sessionId } });
|
|
1112
1166
|
if (current.interruptTimer)
|
|
@@ -1123,7 +1177,7 @@ export function createAcpDriver(support) {
|
|
|
1123
1177
|
closeSession(threadId, "stop");
|
|
1124
1178
|
}
|
|
1125
1179
|
};
|
|
1126
|
-
active.set(threadId, { stop: () => closeSession(threadId, "stop"), interrupt, turnId, asks });
|
|
1180
|
+
active.set(threadId, { stop: () => { state.stopped = true; closeSession(threadId, "stop"); }, interrupt, turnId, asks });
|
|
1127
1181
|
emit({ ...base(threadId, turnId), type: "turn.started" });
|
|
1128
1182
|
session.current = current;
|
|
1129
1183
|
(async () => {
|
|
@@ -1134,6 +1188,19 @@ export function createAcpDriver(support) {
|
|
|
1134
1188
|
// Returns whether this runtime accepts image prompts, for the
|
|
1135
1189
|
// prompt phase below.
|
|
1136
1190
|
const handshake = async () => {
|
|
1191
|
+
// Also covers timeout/reset/re-establishment, not just rotated
|
|
1192
|
+
// MCP credentials. Stop remains wired while cleanup is pending.
|
|
1193
|
+
for (const child of retiring.get(threadId) ?? []) {
|
|
1194
|
+
const stopped = await retireChild(threadId, child);
|
|
1195
|
+
if (state.settled || session.closing)
|
|
1196
|
+
throw new Error("session closed");
|
|
1197
|
+
if (!stopped) {
|
|
1198
|
+
closeSession(threadId, "replace-failed");
|
|
1199
|
+
throw new Error(`${support.displayName} could not close its previous tool session. Try again.`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (state.settled || session.closing)
|
|
1203
|
+
throw new Error("session closed");
|
|
1137
1204
|
if (!session.initResult) {
|
|
1138
1205
|
session.initResult = await request("initialize", {
|
|
1139
1206
|
protocolVersion: 1,
|
|
@@ -1356,10 +1423,13 @@ export function createAcpDriver(support) {
|
|
|
1356
1423
|
if (turn.variant !== undefined && requestedVariantOption().currentValue !== turn.variant) {
|
|
1357
1424
|
throw new Error(`${support.displayName} changed variant before the prompt`);
|
|
1358
1425
|
}
|
|
1426
|
+
if (state.settled || state.stopped || session.closing)
|
|
1427
|
+
throw new Error("turn stopped");
|
|
1359
1428
|
state.promptSent = true;
|
|
1429
|
+
acknowledge();
|
|
1360
1430
|
const promptIdleMs = promptIdleTimeoutMs();
|
|
1361
|
-
const result = await request("session/prompt", { sessionId, prompt: [{ type: "text", text }, ...imageBlocks] }, undefined, undefined, promptIdleMs, `${DRIVER_KIND}
|
|
1362
|
-
"
|
|
1431
|
+
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. ` +
|
|
1432
|
+
"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
1433
|
if (pendingSplitReceipt) {
|
|
1364
1434
|
// session/prompt resolving is the acceptance boundary: a
|
|
1365
1435
|
// rejected prompt leaves the receipt unwritten, so the next
|
|
@@ -1405,6 +1475,35 @@ export function createAcpDriver(support) {
|
|
|
1405
1475
|
// fallback for existing ACP supports.
|
|
1406
1476
|
const needsAuth = code === "invalid_credentials" || code === "inactive_subscription"
|
|
1407
1477
|
|| message === support.loginNote;
|
|
1478
|
+
const failure = classifyError({ text: message });
|
|
1479
|
+
const transientStartup = failure.transient || (failure.reason === "unknown" && code === "upstream_outage");
|
|
1480
|
+
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);
|
|
1481
|
+
if (turn.startupRecovery && transientStartup && !needsAuth && !denied && (!code || code === "upstream_outage")
|
|
1482
|
+
&& ![-32700, -32600, -32601, -32602].includes(e?.code)
|
|
1483
|
+
&& !state.promptSent && !state.startupActivity && !state.producedItem && !state.text
|
|
1484
|
+
&& !state.stopped && !session.closing && !asks.size && !current.runningTools.size) {
|
|
1485
|
+
// Quiesce before killing: the child's close event must not
|
|
1486
|
+
// publish a terminal completion before the harness can recover.
|
|
1487
|
+
state.settled = true;
|
|
1488
|
+
session.current = null;
|
|
1489
|
+
if (current.interruptTimer)
|
|
1490
|
+
clearTimeout(current.interruptTimer);
|
|
1491
|
+
closeSession(threadId, "startup-recovery");
|
|
1492
|
+
const stopped = await killCliTree(session.child).catch(() => false);
|
|
1493
|
+
if (current.interruptTimer)
|
|
1494
|
+
clearTimeout(current.interruptTimer);
|
|
1495
|
+
active.delete(threadId);
|
|
1496
|
+
if (stopped && !state.stopped) {
|
|
1497
|
+
rejectStartup(new TurnNotStartedError(turnId, message));
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
acknowledge();
|
|
1501
|
+
if (!state.stopped)
|
|
1502
|
+
emit({ ...base(threadId, turnId), type: "runtime.error", message });
|
|
1503
|
+
emit({ ...base(threadId, turnId), type: "turn.completed", ok: state.stopped,
|
|
1504
|
+
stopReason: state.stopped ? "cancelled" : "rpc_error", cost: null });
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1408
1507
|
emit({
|
|
1409
1508
|
...base(threadId, turnId),
|
|
1410
1509
|
type: "runtime.error",
|
|
@@ -1424,7 +1523,7 @@ export function createAcpDriver(support) {
|
|
|
1424
1523
|
}
|
|
1425
1524
|
}
|
|
1426
1525
|
})();
|
|
1427
|
-
return { turnId };
|
|
1526
|
+
return startupAck ?? { turnId };
|
|
1428
1527
|
};
|
|
1429
1528
|
const snapshot = async () => {
|
|
1430
1529
|
const env = childEnv();
|
|
@@ -1481,6 +1580,10 @@ export function createAcpDriver(support) {
|
|
|
1481
1580
|
// idle pooled sessions have no running turn — close them too
|
|
1482
1581
|
for (const threadId of Array.from(sessions.keys()))
|
|
1483
1582
|
closeSession(threadId, "stopAll");
|
|
1583
|
+
for (const [threadId, children] of retiring) {
|
|
1584
|
+
for (const child of children)
|
|
1585
|
+
void retireChild(threadId, child);
|
|
1586
|
+
}
|
|
1484
1587
|
},
|
|
1485
1588
|
onEvent: (listener) => {
|
|
1486
1589
|
listeners.add(listener);
|
|
@@ -1492,6 +1595,10 @@ export function createAcpDriver(support) {
|
|
|
1492
1595
|
stop();
|
|
1493
1596
|
for (const threadId of Array.from(sessions.keys()))
|
|
1494
1597
|
closeSession(threadId, "dispose");
|
|
1598
|
+
for (const [threadId, children] of retiring) {
|
|
1599
|
+
for (const child of children)
|
|
1600
|
+
void retireChild(threadId, child);
|
|
1601
|
+
}
|
|
1495
1602
|
listeners.clear();
|
|
1496
1603
|
},
|
|
1497
1604
|
};
|
|
@@ -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",
|
|
@@ -374,7 +374,7 @@ export const BoxAgentDriver = {
|
|
|
374
374
|
snapshot,
|
|
375
375
|
adapter: {
|
|
376
376
|
provider: DRIVER_KIND,
|
|
377
|
-
capabilities: { sessionModelSwitch: "in-session" },
|
|
377
|
+
capabilities: { sessionModelSwitch: "in-session", remoteAgent: true, usesCloudComputer: true },
|
|
378
378
|
sendTurn,
|
|
379
379
|
interruptTurn: async (threadId) => active.get(threadId)?.cancel(),
|
|
380
380
|
respondToRequest: async (threadId, requestId, decision) => {
|
|
@@ -617,7 +617,11 @@ export function createOpenAIChatRuntime(options) {
|
|
|
617
617
|
: { state: "unavailable", reason: options.unavailableReason },
|
|
618
618
|
adapter: {
|
|
619
619
|
provider: options.driverKind,
|
|
620
|
-
capabilities: { ...(options.computerUse ? { computerMcp: options.tools !== false,
|
|
620
|
+
capabilities: { ...(options.computerUse ? { computerMcp: options.tools !== false,
|
|
621
|
+
// Same gate as cloudComputerMcp: with tools off the runtime cannot
|
|
622
|
+
// mount the leased Box descriptor either. The fleet invariant test
|
|
623
|
+
// pins usesCloudComputer === (remoteAgent || cloudComputerMcp).
|
|
624
|
+
usesCloudComputer: options.tools !== false, cloudComputerMcp: options.tools !== false, localComputerMcp: options.tools !== false,
|
|
621
625
|
browserMcp: options.tools !== false, nativeImageInput: true, images: true } : {}),
|
|
622
626
|
sessionModelSwitch: "in-session", customMcp: options.tools !== false, agentsMcp: options.tools !== false, composioMcp: options.tools !== false,
|
|
623
627
|
// The runtime owns the whole tool loop, so it can always take a
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// uses one envelope profile and lets the per-driver capability decide the
|
|
8
8
|
// delta; the tool names themselves come from the real catalog, so this
|
|
9
9
|
// never drifts from what a turn is actually shown.
|
|
10
|
-
import { APPROVAL_MODES, supportsApprovalMode } from "../shared/approval-mode.js";
|
|
10
|
+
import { APPROVAL_MODES, supportsApprovalMode as driverKindSupportsApprovalMode } from "../shared/approval-mode.js";
|
|
11
11
|
import { availableTools } from "./drivers/agents-catalog.js";
|
|
12
12
|
/** The widest turn the harness can build: every optional integration on and
|
|
13
13
|
* a coordinating (room) turn, where the room-only coordination tools exist.
|
|
@@ -35,8 +35,8 @@ export function harnessCapabilityLines(from, to) {
|
|
|
35
35
|
: "Loses peer coordination and every team tool: " + names.length + " tools including " + coordination.join(", ") + " go away.");
|
|
36
36
|
}
|
|
37
37
|
if (from.driverKind !== to.driverKind) {
|
|
38
|
-
const lost = APPROVAL_MODES.filter((mode) =>
|
|
39
|
-
const gained = APPROVAL_MODES.filter((mode) => !
|
|
38
|
+
const lost = APPROVAL_MODES.filter((mode) => driverKindSupportsApprovalMode(from.driverKind, mode) && !driverKindSupportsApprovalMode(to.driverKind, mode));
|
|
39
|
+
const gained = APPROVAL_MODES.filter((mode) => !driverKindSupportsApprovalMode(from.driverKind, mode) && driverKindSupportsApprovalMode(to.driverKind, mode));
|
|
40
40
|
if (lost.length)
|
|
41
41
|
lines.push("Loses approval levels: " + lost.join(", ") + ".");
|
|
42
42
|
if (gained.length)
|
|
@@ -44,3 +44,18 @@ export function harnessCapabilityLines(from, to) {
|
|
|
44
44
|
}
|
|
45
45
|
return lines;
|
|
46
46
|
}
|
|
47
|
+
/** Binds the registry to the approval-support check so each gate passes its
|
|
48
|
+
* model selection — or a driver view it already holds — and stops
|
|
49
|
+
* unwrapping registry.cliTarget(...)?.driverKind at every call site. The
|
|
50
|
+
* lookup prefers shadow state, so a mid-reconfig instance answers with the
|
|
51
|
+
* driver it would actually run. */
|
|
52
|
+
export function createApprovalModeSupport(registry) {
|
|
53
|
+
return function supportsApprovalMode(target, mode) {
|
|
54
|
+
const driverKind = target === undefined
|
|
55
|
+
? undefined
|
|
56
|
+
: "driverKind" in target
|
|
57
|
+
? target.driverKind
|
|
58
|
+
: registry.cliTarget(target.instanceId)?.driverKind;
|
|
59
|
+
return driverKindSupportsApprovalMode(driverKind, mode);
|
|
60
|
+
};
|
|
61
|
+
}
|