openmausbot 0.1.95 → 0.1.97
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-BwWpc4RK.js → DesktopViewer-CPidMCtt.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-jsQkIqTn.js → abnfDiagram-O67JEVCF-k1UCNXb7.js} +1 -1
- package/dist/assets/{arc-7kei4Hsu.js → arc-BUWGa9UU.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CRWDFDMq.js → architectureDiagram-NJMV4G6O-DiPor3UT.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CTKW_IKs.js → blockDiagram-BEXU5L5S-Ag9nLW4H.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-BBTW7-6M.js → c4Diagram-YGBWAQC7-Bh0sjgUa.js} +1 -1
- package/dist/assets/channel-DR8eD6QW.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-M_Ox-MkD.js → chunk-2Q5K7J3B-BFBcIIqu.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-D2YiN-Ud.js → chunk-3FUC2YCW-Dvfpex-f.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-BmVYJ0f8.js → chunk-5VM5RSS4-CMjP2YZW.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BtiLVVnZ.js → chunk-GTNCS2PH-BryoeTc7.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-DFYSb6J6.js → chunk-GWA4HPMP-BhfvcchO.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-mknKBzgV.js → chunk-JWPE2WC7-bcCVp8rp.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-DgSYGiOu.js → chunk-NETBCI7D-yKIjIlkQ.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-C7ZvuguH.js → chunk-WEXAMYUT-D3Zqbmup.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-CPqxINpA.js → chunk-XXDRQBXY-CjVoIMG6.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-Cd6xfJaC.js → classDiagram-v2-NBCMYWYE-mUCWdVyH.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BSXY5qRh.js → cose-bilkent-JH36ORCC-BBmR1bbX.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-D_lhEMj1.js → cynefin-OW5HDTMX-BpRrxoO4.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-Da9FrFwk.js → cynefinDiagram-VND7K2PF-D3VBWCPK.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-T77jMOXe.js → dagre-6A5THRUB-DxEIACOO.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-Dkj96X5E.js → diagram-22UHCM2B-BANgZEOS.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CMi9zlD_.js → diagram-3UASUU5V-Cnq7nJir.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-CXK6YUfS.js → diagram-ATOU4E4O-4TBDIWnz.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-BE7B1u9Q.js → diagram-CDSNMT55-CD_B39D4.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-CCMQphKr.js → diagram-MLGK6HIB-CzvnFhEC.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-iHJ_-tGH.js → diagram-MPIPVDR6-C4w2SNml.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DONsCWQ7.js → ebnfDiagram-ZINNZB2B-gkUEBm2s.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CU_WOaXk.js → elk-276RUBZZ-B_56X6mw.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BeYwM04G.js → erDiagram-OPXOYQCR-C_oByoDS.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bdw9DqEl.js → flowDiagram-KWPJA3E3-CR9xXJkC.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-C9iwq3qm.js → ganttDiagram-FUAMR5RP-gtYBTu0B.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Dx-VWzUg.js → gitGraphDiagram-X574FWY7-VJ6-Tp_p.js} +1 -1
- package/dist/assets/{index-KygHRE3E.js → index-Cp4S4NOL.js} +1 -1
- package/dist/assets/index-CpaIXRLp.js +614 -0
- package/dist/assets/index-Z81SMsgR.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-BG7uaEd4.js → infoDiagram-VRGFBTTK-2Lu9te4D.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-D72WAXnM.js → ishikawaDiagram-OU5B5YK6-DR49XY-e.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DKH1gjYT.js → journeyDiagram-ZHPQQLJL-ZoR0M305.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-3upm9JUT.js → kanban-definition-PNTS6WVX-DLKHxrO-.js} +1 -1
- package/dist/assets/{linear-CcR4pc0-.js → linear-BASsfWly.js} +1 -1
- package/dist/assets/{mermaid.core-C6yU5FSS.js → mermaid.core-BVidiPkk.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DOI2xEuv.js → mindmap-definition-NLK3R4M7-g_Qx0U5C.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BVWJSgLd.js → pegDiagram-GJSIUBJH-DI9Cjs8w.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-jtbYezhq.js → pieDiagram-5QR66LMP-sRI0Uhr5.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-g0u8mtPX.js → quadrantDiagram-O4NWA36T-CamKTby3.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-k8689Wny.js → railroadDiagram-XR7U4H2S-DOLlkbf3.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DsJ22PDt.js → requirementDiagram-PLB6GJNP-DrRQ5r97.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CneWBRgG.js → sankeyDiagram-IPEJSGJF-DGS_lXcX.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-DWsFCWap.js → sequenceDiagram-PO4LG4MO-CkjWqwtf.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-Bvf0YE3S.js → sizeCapture-INFHLROL-CepRgful.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-Bxb3Iei8.js → stateDiagram-v2-GCMORJYK-hykr-9RS.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DLkk7W4A.js → swimlanes-2SLR337P-CKOHEZbI.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-8vqZ4-.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-KR9bIHYC.js → timeline-definition-EJHVYXUP-CB8pJ6AC.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DfsOrj8t.js → usecaseDiagram-POWQR4AR-DrclJHdl.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-DRSTzG3h.js → vennDiagram-UO4OBE2U-zS23qjv8.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DtEdhPgT.js → wardleyDiagram-VNRHLVJA-52jgeKxz.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-B-XhTxjG.js → xychartDiagram-PMCCYNJV-DSdl9rQ8.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +26 -13
- package/dist-server/companion/src/listener.js +39 -18
- package/dist-server/container-mcp.js +14 -27
- package/dist-server/drivers/agents-proxy.js +108 -49
- package/dist-server/harness-mcp-proxy.js +1 -2
- package/dist-server/index.js +4664 -4644
- package/dist-server/openmausbot.js +1425 -1437
- package/dist-server/pair-cli.js +1425 -1437
- package/dist-server/permission-proxy.js +0 -4
- package/dist-server/server/admin-activity.js +1 -4
- package/dist-server/server/auto-approve.js +20 -5
- package/dist-server/server/auto-vm-claims.js +13 -1
- package/dist-server/server/automatic-recovery.js +1 -2
- package/dist-server/server/boat.js +71 -87
- package/dist-server/server/bot-overview.js +1 -1
- package/dist-server/server/browser-engine.js +38 -1
- package/dist-server/server/browser-live.js +112 -25
- package/dist-server/server/browser-runtime.js +31 -1
- package/dist-server/server/checkpoints.js +340 -167
- package/dist-server/server/chief-of-staff.js +7 -6
- package/dist-server/server/cli-setup.js +3 -3
- package/dist-server/server/cli.js +33 -26
- package/dist-server/server/cloud-computer-tools.js +11 -4
- package/dist-server/server/cloud-home-start.js +13 -16
- package/dist-server/server/cloud-home.js +3 -13
- package/dist-server/server/cloud-move-http.js +44 -30
- package/dist-server/server/cloud-move.js +20 -18
- package/dist-server/server/cloud-owner.js +1 -1
- package/dist-server/server/composio.js +10 -3
- package/dist-server/server/computer-engine-removal.js +70 -0
- package/dist-server/server/computer-selection.js +18 -0
- package/dist-server/server/config.js +99 -75
- package/dist-server/server/connector-scope-hint.js +83 -0
- package/dist-server/server/container-computer.js +16 -5
- package/dist-server/server/contracts.js +0 -9
- 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/default-model-selection.js +20 -14
- package/dist-server/server/delegations.js +14 -7
- package/dist-server/server/delta-context.js +6 -1
- package/dist-server/server/drivers/acp/core.js +5 -4
- package/dist-server/server/drivers/acp/opencode-go.js +19 -23
- package/dist-server/server/drivers/agents-call.js +47 -64
- package/dist-server/server/drivers/agents-catalog.js +69 -3
- package/dist-server/server/drivers/agents-proxy.js +2 -1
- package/dist-server/server/drivers/builtIn.js +0 -2
- package/dist-server/server/drivers/claude-login-auth.js +2 -11
- package/dist-server/server/drivers/claude.js +49 -30
- package/dist-server/server/drivers/codex-device-auth.js +3 -1
- package/dist-server/server/drivers/codex.js +7 -6
- 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 +113 -11
- package/dist-server/server/drivers/pi.js +184 -49
- package/dist-server/server/drivers/prompt-split.js +4 -4
- package/dist-server/server/harness/bus.js +49 -0
- package/dist-server/server/harness-mcp-proxy.js +1 -1
- package/dist-server/server/index.js +942 -980
- package/dist-server/server/live-call.js +11 -6
- package/dist-server/server/managed-policy.js +1 -1
- package/dist-server/server/mcp-registry.js +35 -0
- package/dist-server/server/message-file.js +4 -4
- package/dist-server/server/model-catalog/presets.js +8 -0
- package/dist-server/server/peer-delivery.js +4 -19
- package/dist-server/server/peer-roster.js +40 -6
- package/dist-server/server/profile-versions.js +0 -31
- package/dist-server/server/provider-key-check.js +2 -1
- package/dist-server/server/restart.js +40 -0
- package/dist-server/server/room-handoffs.js +12 -0
- package/dist-server/server/room-turn-end.js +15 -0
- package/dist-server/server/routes/bot-memory.js +0 -24
- package/dist-server/server/routes/live.js +3 -3
- package/dist-server/server/routine-requests.js +2 -5
- package/dist-server/server/routines.js +6 -5
- package/dist-server/server/screen-frame-gate.js +1 -3
- package/dist-server/server/server-launcher.js +53 -0
- package/dist-server/server/store.js +126 -2
- package/dist-server/server/surface.js +40 -64
- package/dist-server/server/system-prompt.js +23 -13
- package/dist-server/server/team-manifest.js +1 -6
- package/dist-server/server/tunnel.js +4 -1
- package/dist-server/server/turn-continuation.js +162 -0
- 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/workspace.js +3 -4
- package/dist-server/server-launcher.js +72 -0
- package/dist-server/shared/ask-question.js +1 -71
- package/dist-server/shared/cloud-computer.js +10 -0
- package/dist-server/shared/credential-request.js +1 -1
- package/dist-server/shared/failed-turn.js +22 -0
- package/dist-server/shared/live-approval.js +2 -2
- package/dist-server/shared/package-format.js +3 -3
- package/dist-server/shared/pairing-link.js +144 -0
- package/dist-server/shared/place-view.js +409 -0
- package/dist-server/shared/tool-scope-support.js +1 -2
- package/dist-server/vps-container-mcp.js +14 -27
- package/dist-server/workspace-backup.worker.js +14 -27
- package/package.json +1 -1
- package/dist/assets/channel-DS2NbT5x.js +0 -1
- package/dist/assets/index-NF7XCHAy.css +0 -1
- package/dist/assets/index-X_5ydXIE.js +0 -610
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C6bZtBXk.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/boatagent.js +0 -443
- package/dist-server/server/tightening-requests.js +0 -435
- package/dist-server/shared/tightening-request.js +0 -87
|
@@ -1145,8 +1145,20 @@ export const ClaudeDriver = {
|
|
|
1145
1145
|
// logical turn's stop handle in `active` while it sets up, so Stop is
|
|
1146
1146
|
// never a silent no-op between two CLI processes of the same turn.
|
|
1147
1147
|
const relaunch = logicalTurnId !== undefined;
|
|
1148
|
-
if (
|
|
1149
|
-
|
|
1148
|
+
if (!relaunch) {
|
|
1149
|
+
// Stop is acknowledged at once and the process tree reaped after it
|
|
1150
|
+
// (taskkill is asynchronous on Windows); the stopped turn leaves
|
|
1151
|
+
// `active` only when its close is handled. A Stop that lands while
|
|
1152
|
+
// a turn is still starting lets the harness send the next message
|
|
1153
|
+
// before then. That turn waits for the stopped process to be gone,
|
|
1154
|
+
// never overlapping its helpers, instead of being refused.
|
|
1155
|
+
const stopped = sessions.get(threadId);
|
|
1156
|
+
if (stopped?.turn?.stopRequested && active.get(threadId)?.turnId === stopped.turn.turnId &&
|
|
1157
|
+
await killCliTree(stopped.child))
|
|
1158
|
+
await stopped.closed;
|
|
1159
|
+
if (active.has(threadId))
|
|
1160
|
+
throw new Error("a turn is already running on this thread");
|
|
1161
|
+
}
|
|
1150
1162
|
// A bot-level mode is authoritative for this turn. In particular, an
|
|
1151
1163
|
// old provider instance may still be configured with
|
|
1152
1164
|
// `bypassPermissions`; Ask/Auto must restore Claude's interactive
|
|
@@ -1459,7 +1471,7 @@ export const ClaudeDriver = {
|
|
|
1459
1471
|
if (!turn.sessionReset && live && !live.turn && !live.closing && live.child.exitCode === null && live.argsKey === argsKey && (!sessionId || sessionId === live.sessionId)) {
|
|
1460
1472
|
if (live.idleTimer)
|
|
1461
1473
|
clearTimeout(live.idleTimer);
|
|
1462
|
-
live.turn = { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false, pendingSteers: new Set(), deferred: null, continuationGrace: null, continuationSilence: null };
|
|
1474
|
+
live.turn = { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false, awaitingInit: true, pendingSteers: new Set(), deferred: null, continuationGrace: null, continuationSilence: null };
|
|
1463
1475
|
active.set(threadId, { stop: () => {
|
|
1464
1476
|
if (live.turn)
|
|
1465
1477
|
live.turn.stopRequested = true;
|
|
@@ -1475,20 +1487,13 @@ export const ClaudeDriver = {
|
|
|
1475
1487
|
: claudeUserMessage(withVolatileNote(turn.text, volatile), turn.images);
|
|
1476
1488
|
live.volatile = volatile;
|
|
1477
1489
|
const running = live.turn;
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1490
|
+
// A warm process can end between turns just as the next one is
|
|
1491
|
+
// written: this driver learns of an exit only when the event loop
|
|
1492
|
+
// gets to it (later still on Windows), so the check above can find it
|
|
1493
|
+
// live and the write then fail. Nothing was submitted; its close
|
|
1494
|
+
// resumes this same turn on a fresh process (see `awaitingInit`).
|
|
1495
|
+
if (!(await writeUser(live, threadId, message)) && !running?.stopRequested)
|
|
1482
1496
|
closeSession(threadId, "stdin write failed");
|
|
1483
|
-
retryState.delete(threadId);
|
|
1484
|
-
if (mcpConfigPath) {
|
|
1485
|
-
try {
|
|
1486
|
-
rmSync(dirname(mcpConfigPath), { recursive: true, force: true });
|
|
1487
|
-
}
|
|
1488
|
-
catch { }
|
|
1489
|
-
}
|
|
1490
|
-
throw new Error("claude session stdin is not writable");
|
|
1491
|
-
}
|
|
1492
1497
|
// the MCP config was for the first spawn; nothing to clean here
|
|
1493
1498
|
if (mcpConfigPath) {
|
|
1494
1499
|
try {
|
|
@@ -1630,7 +1635,9 @@ export const ClaudeDriver = {
|
|
|
1630
1635
|
cleanupUnownedLaunch();
|
|
1631
1636
|
throw error;
|
|
1632
1637
|
}
|
|
1638
|
+
let markClosed;
|
|
1633
1639
|
const session = {
|
|
1640
|
+
closed: new Promise((resolve) => { markClosed = resolve; }),
|
|
1634
1641
|
child,
|
|
1635
1642
|
broker,
|
|
1636
1643
|
mcpConfigPath,
|
|
@@ -1749,6 +1756,8 @@ export const ClaudeDriver = {
|
|
|
1749
1756
|
break;
|
|
1750
1757
|
}
|
|
1751
1758
|
session.sawInit = true;
|
|
1759
|
+
if (session.turn)
|
|
1760
|
+
session.turn.awaitingInit = false;
|
|
1752
1761
|
session.nativePermissionMode = typeof o.permissionMode === "string" ? o.permissionMode : null;
|
|
1753
1762
|
if (typeof o.session_id === "string")
|
|
1754
1763
|
session.sessionId = o.session_id;
|
|
@@ -2051,11 +2060,18 @@ export const ClaudeDriver = {
|
|
|
2051
2060
|
const retry = retryState.get(threadId) ?? { attempt: 0, cancelled: false };
|
|
2052
2061
|
const message = `claude exited ${code} before result${session.stderr ? `: ${session.stderr.trim().slice(-300)}` : ""}`;
|
|
2053
2062
|
const verdict = classifyError({ exitCode: code, stderr: message });
|
|
2063
|
+
// A process kept warm from an earlier turn ended before it
|
|
2064
|
+
// announced this one: it never took the prompt. That is a session
|
|
2065
|
+
// ending between turns, not a failed turn, so the same turn
|
|
2066
|
+
// resumes the session on a fresh process at once — no retry row,
|
|
2067
|
+
// no retry budget spent. (A fresh process is never retained, so
|
|
2068
|
+
// this happens at most once per turn.)
|
|
2069
|
+
const endedBeforeTurn = session.turn.awaitingInit === true;
|
|
2054
2070
|
if (!retry.cancelled &&
|
|
2055
|
-
code !== 0 &&
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2071
|
+
(endedBeforeTurn || (code !== 0 &&
|
|
2072
|
+
verdict.transient &&
|
|
2073
|
+
!session.turn.sawStreamDelta &&
|
|
2074
|
+
retry.attempt < RETRY_MAX_ATTEMPTS - 1))) {
|
|
2059
2075
|
// the CLI is gone but the TURN continues: keep the thread busy,
|
|
2060
2076
|
// emit no terminal event, and relaunch after the backoff. The
|
|
2061
2077
|
// `active` entry STAYS — it is what makes an interrupt during
|
|
@@ -2077,15 +2093,18 @@ export const ClaudeDriver = {
|
|
|
2077
2093
|
}
|
|
2078
2094
|
sessions.delete(threadId);
|
|
2079
2095
|
session.turn = null;
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2096
|
+
let delayMs = 0;
|
|
2097
|
+
if (!endedBeforeTurn) {
|
|
2098
|
+
retry.attempt++;
|
|
2099
|
+
delayMs = computeBackoff(retry.attempt - 1);
|
|
2100
|
+
emit({
|
|
2101
|
+
...base(threadId, turnId),
|
|
2102
|
+
type: "turn.retrying",
|
|
2103
|
+
attempt: retry.attempt,
|
|
2104
|
+
delayMs,
|
|
2105
|
+
reason: verdict.reason,
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2089
2108
|
void (async () => {
|
|
2090
2109
|
const wait = interruptibleDelay(delayMs * retryScale, retryAbort.signal);
|
|
2091
2110
|
await wait.promise;
|
|
@@ -2225,7 +2244,7 @@ export const ClaudeDriver = {
|
|
|
2225
2244
|
};
|
|
2226
2245
|
child.on("close", (code) => {
|
|
2227
2246
|
session.finishClose = () => finalizeClose(code);
|
|
2228
|
-
void session.finishClose();
|
|
2247
|
+
void session.finishClose().finally(markClosed);
|
|
2229
2248
|
});
|
|
2230
2249
|
const stop = () => {
|
|
2231
2250
|
if (session.turn)
|
|
@@ -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;
|
|
@@ -1710,8 +1711,8 @@ export const CodexDriver = {
|
|
|
1710
1711
|
// back. The app-server offers no way to clear a level either:
|
|
1711
1712
|
// "" is rejected outright and thread/start takes no effort at
|
|
1712
1713
|
// all. So a thread keeps the last level it was sent until it is
|
|
1713
|
-
// sent another
|
|
1714
|
-
// thread
|
|
1714
|
+
// sent another; back on Default, the harness gives a thread that
|
|
1715
|
+
// holds a level a new thread instead of resuming it (server/index.ts).
|
|
1715
1716
|
...(turn.effort ? { effort: turn.effort } : {}),
|
|
1716
1717
|
});
|
|
1717
1718
|
};
|
|
@@ -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,9 +7,10 @@ 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
|
+
import { classifyContinuable, writeTurnHandoff } from "../turn-continuation.js";
|
|
13
14
|
/** The message of a JSON error body a provider returned with HTTP 200.
|
|
14
15
|
* MiniMax reports auth, balance, and parameter failures as `base_resp`. */
|
|
15
16
|
function providerError(json) {
|
|
@@ -31,6 +32,15 @@ const usageFrom = (usage) => usage
|
|
|
31
32
|
const asError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
32
33
|
class UnsupportedChatToolsError extends Error {
|
|
33
34
|
}
|
|
35
|
+
/** One turn's safety stops: model steps, and tool calls across all of them.
|
|
36
|
+
* Tool-heavy models (a memory server, a browser) take dozens of steps, and
|
|
37
|
+
* a stopped turn cannot simply be continued — the next turn is rebuilt from
|
|
38
|
+
* the text transcript, which holds none of these tool results. The calls
|
|
39
|
+
* that ran have taken effect, so the stop asks only for what is left: a
|
|
40
|
+
* retry of the whole task would repeat them. */
|
|
41
|
+
const MAX_CHAT_ROUNDS = 64;
|
|
42
|
+
const MAX_TURN_TOOL_CALLS = 200;
|
|
43
|
+
const stoppedAfter = (count) => `Stopped after ${count} without a final answer. The steps so far already ran, so ask only for what's left.`;
|
|
34
44
|
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
45
|
/** A short reply that only announces a next step, with nothing to answer. */
|
|
36
46
|
export function announcesAction(text) {
|
|
@@ -78,6 +88,53 @@ function rejectsReasoningReplay(status, body) {
|
|
|
78
88
|
return typeof message === "string" && /\breasoning_content\b/.test(message) &&
|
|
79
89
|
/\b(?:unsupported|not supported|unknown|unrecognized|unexpected|not permitted|not allowed)\b/i.test(message);
|
|
80
90
|
}
|
|
91
|
+
/** Groq checks each generated tool call against the request and fails the
|
|
92
|
+
* completion with `tool_use_failed` when the call is invalid: most often a
|
|
93
|
+
* tool it was not offered (gpt-oss inventing a "json" tool to answer in
|
|
94
|
+
* JSON), sometimes arguments that miss the schema. `refusal` is the
|
|
95
|
+
* provider's whole error object, `failed_generation` included. */
|
|
96
|
+
class RejectedToolCallError extends ChatProtocolError {
|
|
97
|
+
refusal;
|
|
98
|
+
constructor(message, refusal) {
|
|
99
|
+
super(message);
|
|
100
|
+
this.refusal = refusal;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const toolCallRefusal = (error) => {
|
|
104
|
+
const body = object(error);
|
|
105
|
+
return body?.code === "tool_use_failed" ? body : undefined;
|
|
106
|
+
};
|
|
107
|
+
function refusedToolCall(status, body) {
|
|
108
|
+
if (status !== 400)
|
|
109
|
+
return undefined;
|
|
110
|
+
try {
|
|
111
|
+
return toolCallRefusal(object(JSON.parse(body))?.error);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function completionError(json, label) {
|
|
118
|
+
const message = providerError(json);
|
|
119
|
+
if (!message)
|
|
120
|
+
return null;
|
|
121
|
+
const text = `provider returned a ${label}: ${message.slice(0, 200)}`;
|
|
122
|
+
const refusal = toolCallRefusal(json.error);
|
|
123
|
+
return refusal ? new RejectedToolCallError(text, refusal) : new ChatProtocolError(text);
|
|
124
|
+
}
|
|
125
|
+
/** What a failed turn says once the provider refused every attempt: what
|
|
126
|
+
* happened, the next step, then the provider's words. The refused call ran
|
|
127
|
+
* nothing, but tool calls from earlier steps of the turn did, and a Retry
|
|
128
|
+
* of the whole request would repeat them. */
|
|
129
|
+
function refusedToolCallFailure(error, earlierSteps) {
|
|
130
|
+
const words = typeof error.refusal.message === "string" ? error.refusal.message : error.message;
|
|
131
|
+
const tool = /attempted to call tool '([^']+)'/.exec(words)?.[1];
|
|
132
|
+
const what = tool ? `tried to use a tool it was not given ("${tool}")` : "made a tool call the provider rejected";
|
|
133
|
+
const next = earlierSteps
|
|
134
|
+
? "The steps before it already ran, so ask only for what's left."
|
|
135
|
+
: "Nothing ran. Retry; if it keeps happening, rephrase the request or choose another model.";
|
|
136
|
+
return `The model ${what}. ${next} Provider: ${words.slice(0, 200)}`;
|
|
137
|
+
}
|
|
81
138
|
/** Shared runtime for the three providers that speak OpenAI chat completions. */
|
|
82
139
|
export function createOpenAIChatRuntime(options) {
|
|
83
140
|
const { input } = options;
|
|
@@ -128,6 +185,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
128
185
|
const message = `${options.httpErrorLabel} HTTP ${response.status}${body ? `: ${body.slice(0, 200)}` : ""}`;
|
|
129
186
|
if (rejectsToolsParameter(response.status, body))
|
|
130
187
|
throw new UnsupportedChatToolsError(message);
|
|
188
|
+
const refusal = refusedToolCall(response.status, body);
|
|
189
|
+
if (refusal)
|
|
190
|
+
throw new RejectedToolCallError(message, refusal);
|
|
131
191
|
if (messages.some((entry) => entry.reasoning_content !== undefined) && rejectsReasoningReplay(response.status, body)) {
|
|
132
192
|
throw new UnsupportedReasoningReplayError(message);
|
|
133
193
|
}
|
|
@@ -146,9 +206,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
146
206
|
costUsd: count(json.usage?.cost),
|
|
147
207
|
});
|
|
148
208
|
}
|
|
149
|
-
const bodyError =
|
|
209
|
+
const bodyError = completionError(json, "completion error");
|
|
150
210
|
if (bodyError)
|
|
151
|
-
throw
|
|
211
|
+
throw bodyError;
|
|
152
212
|
const message = json.choices?.[0]?.message;
|
|
153
213
|
if (!message || !object(message) || !["content", "reasoning_content", "reasoning", "reasoning_details", "tool_calls", "function_call"].some((key) => key in message)) {
|
|
154
214
|
throw new ChatProtocolError("provider returned no completion message");
|
|
@@ -210,9 +270,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
210
270
|
malformedFrame = true;
|
|
211
271
|
return false;
|
|
212
272
|
}
|
|
213
|
-
const chunkError =
|
|
273
|
+
const chunkError = completionError(chunk, "streaming completion error");
|
|
214
274
|
if (chunkError)
|
|
215
|
-
throw
|
|
275
|
+
throw chunkError;
|
|
216
276
|
const choice = chunk.choices?.find((row) => row.index === undefined || row.index === 0);
|
|
217
277
|
const delta = choice?.delta;
|
|
218
278
|
if (object(delta))
|
|
@@ -262,9 +322,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
262
322
|
catch {
|
|
263
323
|
body = undefined;
|
|
264
324
|
}
|
|
265
|
-
const bodyError = body ?
|
|
325
|
+
const bodyError = body ? completionError(body, "completion error") : null;
|
|
266
326
|
if (bodyError)
|
|
267
|
-
throw
|
|
327
|
+
throw bodyError;
|
|
268
328
|
}
|
|
269
329
|
throw new ChatProtocolError("Stream ended before completion");
|
|
270
330
|
}
|
|
@@ -413,7 +473,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
413
473
|
if (questionAllowed && !tools.definitions.some((definition) => definition.function.name === ASK_USER_TOOL)) {
|
|
414
474
|
tools.definitions.push(ASK_USER_TOOL_DEFINITION);
|
|
415
475
|
}
|
|
416
|
-
for (let round = 0; round <
|
|
476
|
+
for (let round = 0; round < MAX_CHAT_ROUNDS; round++) {
|
|
417
477
|
abort.signal.throwIfAborted();
|
|
418
478
|
// Drain parked mid-turn input here, before the next completion
|
|
419
479
|
// request: the previous round's tool results are complete, so a
|
|
@@ -427,6 +487,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
427
487
|
let completion;
|
|
428
488
|
for (;;) {
|
|
429
489
|
let streamed = false;
|
|
490
|
+
let answerStreamed = false;
|
|
430
491
|
const pending = { assistant_text: "", reasoning_text: "" };
|
|
431
492
|
const delta = (text, streamKind, flush = false) => {
|
|
432
493
|
let combined = pending[streamKind] + text;
|
|
@@ -455,6 +516,8 @@ export function createOpenAIChatRuntime(options) {
|
|
|
455
516
|
try {
|
|
456
517
|
completion = await complete(messages, model, true, abort.signal, (text, streamKind) => {
|
|
457
518
|
streamed = true;
|
|
519
|
+
if (streamKind === "assistant_text")
|
|
520
|
+
answerStreamed = true;
|
|
458
521
|
delta(text, streamKind);
|
|
459
522
|
}, tools.definitions);
|
|
460
523
|
delta("", "assistant_text", true);
|
|
@@ -474,6 +537,20 @@ export function createOpenAIChatRuntime(options) {
|
|
|
474
537
|
delete message.reasoning_content;
|
|
475
538
|
continue;
|
|
476
539
|
}
|
|
540
|
+
// A refused tool call ran nothing, so the same request goes
|
|
541
|
+
// again; the model usually answers properly next time. Not once
|
|
542
|
+
// answer text streamed: the phones' live bubble would join it to
|
|
543
|
+
// the next attempt's. Streamed thinking stays thinking. Each
|
|
544
|
+
// refusal, failed_generation included, goes to the native log.
|
|
545
|
+
if (error instanceof RejectedToolCallError) {
|
|
546
|
+
native("in", { refused: "tool_use_failed", attempt: attempt + 1, error: error.refusal });
|
|
547
|
+
if (!answerStreamed && !abort.signal.aborted && attempt < RETRY_MAX_ATTEMPTS - 1) {
|
|
548
|
+
emit({ ...base(turn.threadId, turnId), type: "turn.retrying", attempt: ++attempt, delayMs: 0, reason: "tool_use_failed" });
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (!abort.signal.aborted)
|
|
552
|
+
throw new ChatProtocolError(refusedToolCallFailure(error, seenCalls.size > 0));
|
|
553
|
+
}
|
|
477
554
|
// Only our optional question changed a previously plain request.
|
|
478
555
|
// A structured parameter rejection has executed nothing; never
|
|
479
556
|
// downgrade mounted tools, streamed output or a handled call.
|
|
@@ -517,6 +594,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
517
594
|
continue;
|
|
518
595
|
}
|
|
519
596
|
if (toolFailed) {
|
|
597
|
+
// A failed or denied tool op ends the turn: the final answer is
|
|
598
|
+
// not an execution receipt, and a person's "no" must never be
|
|
599
|
+
// answered with a prompt to run the same thing again.
|
|
520
600
|
stopReason = "tool_error";
|
|
521
601
|
throw new ChatProtocolError("One or more tool operations failed or were denied. See the tool results; the final response is not an execution receipt.");
|
|
522
602
|
}
|
|
@@ -531,8 +611,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
531
611
|
throw new ChatProtocolError("provider reused a tool-call ID; refusing to repeat an operation");
|
|
532
612
|
seenCalls.add(call.id);
|
|
533
613
|
}
|
|
534
|
-
|
|
535
|
-
|
|
614
|
+
// The whole batch is refused before any of it runs.
|
|
615
|
+
if (seenCalls.size > MAX_TURN_TOOL_CALLS)
|
|
616
|
+
throw new ChatProtocolError(stoppedAfter(`${MAX_TURN_TOOL_CALLS} tool calls`));
|
|
536
617
|
messages.push({ role: "assistant", content: completion.text || null, tool_calls: completion.toolCalls,
|
|
537
618
|
...(completion.protocolReasoning && !reasoningReplayRejected.has(model) ? { [options.reasoningReplayField ?? "reasoning_content"]: completion.protocolReasoning } : {}),
|
|
538
619
|
...(completion.protocolReasoningDetails.length ? { reasoning_details: completion.protocolReasoningDetails } : {}),
|
|
@@ -645,7 +726,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
645
726
|
messages.push({ role: "user", content: screenshotParts });
|
|
646
727
|
}
|
|
647
728
|
if (!ok)
|
|
648
|
-
throw new ChatProtocolError(
|
|
729
|
+
throw new ChatProtocolError(stoppedAfter(`${MAX_CHAT_ROUNDS} steps`));
|
|
649
730
|
}
|
|
650
731
|
catch (value) {
|
|
651
732
|
stopReason = abort.signal.aborted ? "interrupted" : stopReason ?? "error";
|
|
@@ -670,6 +751,27 @@ export function createOpenAIChatRuntime(options) {
|
|
|
670
751
|
if (failure && (!abort.signal.aborted || cleanupFailed)) {
|
|
671
752
|
emit({ ...base(turn.threadId, turnId), type: "runtime.error", message: failure, terminal: !abort.signal.aborted });
|
|
672
753
|
}
|
|
754
|
+
// Automatic continuity: a resumable terminal (budget cap or tool
|
|
755
|
+
// errors) persists a handoff and raises cap.exhausted so the harness
|
|
756
|
+
// can start a `Continue:` thread. Interruptions and provider config
|
|
757
|
+
// errors are excluded by classifyContinuable.
|
|
758
|
+
if (!ok && !abort.signal.aborted) {
|
|
759
|
+
const continuable = classifyContinuable(stopReason, failure);
|
|
760
|
+
if (continuable) {
|
|
761
|
+
const handoffPath = writeTurnHandoff({
|
|
762
|
+
threadId: turn.threadId,
|
|
763
|
+
turnId,
|
|
764
|
+
model,
|
|
765
|
+
usage,
|
|
766
|
+
hasUsage,
|
|
767
|
+
failure,
|
|
768
|
+
toolCalls: seenCalls.size,
|
|
769
|
+
messages,
|
|
770
|
+
});
|
|
771
|
+
if (handoffPath)
|
|
772
|
+
emit({ ...base(turn.threadId, turnId), type: "cap.exhausted", handoffPath, reason: continuable });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
673
775
|
active.delete(turn.threadId);
|
|
674
776
|
emit({ ...base(turn.threadId, turnId), type: "turn.completed", ok, stopReason, cost: null,
|
|
675
777
|
...(hasUsage && (options.includeUsageInCompleted || seenCalls.size) ? { usage } : {}),
|