openmausbot 0.1.91 → 0.1.93
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-CKNR3SEW.js +5 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-yP-w8pRq.js → abnfDiagram-O67JEVCF-Ckrm1I1X.js} +1 -1
- package/dist/assets/{arc-DsivfZEh.js → arc-BBdBuCgB.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CILw7lzT.js → architectureDiagram-NJMV4G6O-CDdA3Hjs.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-DEQUV3HF.js → blockDiagram-BEXU5L5S-AUrsx1YR.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-lSXCZoKA.js → c4Diagram-YGBWAQC7-BEqQ0Unh.js} +1 -1
- package/dist/assets/channel-BU94VpxE.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-QCD5bgWP.js → chunk-2Q5K7J3B-CEEuvd4C.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-C5-iZJuL.js → chunk-3FUC2YCW-DyANCiHl.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-cxsawMpY.js → chunk-5VM5RSS4-Dx_Slpw9.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-DV8tV8H7.js → chunk-GTNCS2PH-Dl7h7iiD.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-CJFZyfbW.js → chunk-GWA4HPMP-BLx07SPb.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BigbMXco.js → chunk-JWPE2WC7-BMxDj3n7.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-oVxTfpDy.js → chunk-NETBCI7D-CN66PT4H.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CH4BiroW.js → chunk-WEXAMYUT-6HwhsDKw.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-KEDa6gax.js → chunk-XXDRQBXY-gWnLt7SN.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BiPjfqVv.js → classDiagram-v2-NBCMYWYE-CMS5l2ea.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-Caxew3OY.js → cose-bilkent-JH36ORCC-CV7sQwIG.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-B_Pui369.js → cynefin-OW5HDTMX-CSyl-7vl.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-X64ydIhP.js → cynefinDiagram-VND7K2PF-BCLKL6az.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-TqYCI1E1.js → dagre-6A5THRUB-DiaZs6S1.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-TeOt3rAQ.js → diagram-22UHCM2B-BeTtmt3I.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-D70BLhDA.js → diagram-3UASUU5V-CsdBG-QQ.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BbQDLwUC.js → diagram-ATOU4E4O-BczvdbR1.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-ChNJZ1u-.js → diagram-CDSNMT55-DHTTTMhi.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-DMBl2APa.js → diagram-MLGK6HIB-RTjHyd8z.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-CEqDchOp.js → diagram-MPIPVDR6-BDenHEH_.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BVDKRp_-.js → ebnfDiagram-ZINNZB2B-BSiRSbtL.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CRkofO9B.js → elk-276RUBZZ-BCAElss-.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-DruOMh5e.js → erDiagram-OPXOYQCR-C4_1ZPJe.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-8GMQeB7t.js → flowDiagram-KWPJA3E3-Bcb4Z75S.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-ORE9jcsM.js → ganttDiagram-FUAMR5RP-BiPOzxzb.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-BmsedsWh.js → gitGraphDiagram-X574FWY7-B1wtF3ZS.js} +1 -1
- package/dist/assets/index-Biji-bfL.css +1 -0
- package/dist/assets/{index-DKNEM8Q6.js → index-BkYwilkG.js} +1 -1
- package/dist/assets/index-DZlhoES0.js +597 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-E_p7AooJ.js → infoDiagram-VRGFBTTK-A19No71U.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-Y-B2mKda.js → ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-eA8RELcK.js → journeyDiagram-ZHPQQLJL-DAO0pVan.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DqIL2e8e.js → kanban-definition-PNTS6WVX-NepmKEe2.js} +1 -1
- package/dist/assets/{linear-yC_29R6e.js → linear-bq1PTDTW.js} +1 -1
- package/dist/assets/{mermaid.core-VhEMkSND.js → mermaid.core-s4LTbQle.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-oJ7kyQZy.js → mindmap-definition-NLK3R4M7-LtdbgHFH.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BmfYcY0O.js → pegDiagram-GJSIUBJH-tdbaCxiK.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DKvgP7w9.js → pieDiagram-5QR66LMP-DvkbpW8l.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-BEKL3W0L.js → quadrantDiagram-O4NWA36T-Do4KaH-E.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BEsiqR5s.js → railroadDiagram-XR7U4H2S-BQWULOAs.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DWBcWvAP.js → requirementDiagram-PLB6GJNP-w9kei1FU.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-rpZ2_O4s.js → sankeyDiagram-IPEJSGJF-D2gRy0e8.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-B99HQdJR.js → sequenceDiagram-PO4LG4MO-a2sO5_Eo.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BjN0qT5T.js → sizeCapture-INFHLROL-CiPJmFEB.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BbsVgGsC.js → stateDiagram-v2-GCMORJYK-DAkUUlL7.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DG3gKkeR.js → swimlanes-2SLR337P-DANf2R8Y.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BTHOdtF_.js → timeline-definition-EJHVYXUP-BKpQmzrT.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-acLSLFwc.js → usecaseDiagram-POWQR4AR-BtI7qLN-.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-C5JzNX9G.js → vennDiagram-UO4OBE2U-gXJXrAG_.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DL1-PrR_.js → wardleyDiagram-VNRHLVJA-GU8Po5-n.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-Bvr1477p.js → xychartDiagram-PMCCYNJV-BF4J30g6.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/novnc-NOTICE.txt +118 -0
- package/dist-server/cloud-home-start.js +112 -14
- package/dist-server/companion/src/routes.js +24 -1
- package/dist-server/container-mcp.js +201 -16
- package/dist-server/drivers/agents-proxy.js +7 -4
- package/dist-server/enterprise/server/index.js +5 -1
- package/dist-server/index.js +10698 -4737
- package/dist-server/local-computer-proxy.js +133 -8
- package/dist-server/mcp-gate.js +16 -6
- package/dist-server/model-catalog/models-dev.snapshot.json +7299 -0
- package/dist-server/openmausbot.js +4656 -1258
- package/dist-server/pair-cli.js +4656 -1258
- package/dist-server/server/boat.js +5 -5
- package/dist-server/server/browser-engine.js +10 -1
- package/dist-server/server/browser-live.js +46 -2
- package/dist-server/server/browser-runtime.js +74 -8
- package/dist-server/server/browser-tool-shape.js +6 -4
- package/dist-server/server/chief-of-staff.js +1 -1
- package/dist-server/server/cloud-home-start.js +118 -26
- package/dist-server/server/cloud-home.js +39 -3
- package/dist-server/server/cloud-lending.js +74 -10
- package/dist-server/server/cloud-owner.js +196 -0
- package/dist-server/server/cloud-secrets-boot.js +6 -0
- package/dist-server/server/cloud-secrets.js +65 -0
- package/dist-server/server/computer-control.js +6 -0
- package/dist-server/server/config.js +187 -14
- package/dist-server/server/container-computer.js +46 -34
- package/dist-server/server/desktop-viewer-targets.js +26 -0
- package/dist-server/server/digest.js +12 -2
- package/dist-server/server/drivers/acp/core.js +339 -33
- package/dist-server/server/drivers/acp/cursor.js +13 -1
- package/dist-server/server/drivers/acp/opencode-go.js +614 -85
- package/dist-server/server/drivers/acp/quiet-status.js +257 -0
- package/dist-server/server/drivers/acp/qwen.js +18 -0
- package/dist-server/server/drivers/agents-catalog.js +6 -6
- package/dist-server/server/drivers/antigravity-acp.js +69 -11
- package/dist-server/server/drivers/antigravity-temp.js +360 -0
- package/dist-server/server/drivers/antigravity.js +21 -0
- package/dist-server/server/drivers/builtIn.js +2 -0
- package/dist-server/server/drivers/cerebras.js +108 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +20 -3
- package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
- package/dist-server/server/drivers/claude.js +329 -37
- package/dist-server/server/drivers/codex.js +228 -18
- package/dist-server/server/drivers/grok.js +2 -2
- package/dist-server/server/drivers/mistral.js +4 -3
- package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
- package/dist-server/server/drivers/openai-chat.js +66 -4
- package/dist-server/server/drivers/openai-compat.js +73 -23
- package/dist-server/server/drivers/prompt-split.js +2 -0
- package/dist-server/server/engine-install.js +46 -12
- package/dist-server/server/env-path.js +21 -5
- package/dist-server/server/harness/registry.js +2 -2
- package/dist-server/server/index.js +1545 -238
- package/dist-server/server/lending-memory.js +479 -0
- package/dist-server/server/local-vm-idle.js +24 -6
- package/dist-server/server/local-vm-stop-reason.js +26 -0
- package/dist-server/server/managed-desktop.js +3 -0
- package/dist-server/server/mcp-bridge.js +5 -3
- package/dist-server/server/mcp-http.js +6 -3
- package/dist-server/server/mcp-oauth-discovery.js +133 -0
- package/dist-server/server/mcp-oauth-store.js +70 -0
- package/dist-server/server/mcp-oauth.js +550 -0
- package/dist-server/server/mcp-probe.js +14 -1
- package/dist-server/server/mcp-registry.js +43 -0
- package/dist-server/server/memory-upkeep.js +94 -84
- package/dist-server/server/model-catalog/catalog.js +374 -0
- package/dist-server/server/model-catalog/presets.js +176 -0
- package/dist-server/server/model-catalog/trim.js +219 -0
- package/dist-server/server/provider-key-check.js +5 -2
- package/dist-server/server/recent-work.js +1 -1
- package/dist-server/server/request-auth.js +23 -0
- package/dist-server/server/routes/antigravity-leftovers.js +45 -0
- package/dist-server/server/routes/bot-memory.js +31 -6
- package/dist-server/server/routes/desktop-viewer.js +238 -0
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +6 -3
- package/dist-server/server/sessions.js +56 -9
- package/dist-server/server/skills.js +25 -10
- package/dist-server/server/store.js +116 -122
- package/dist-server/server/thread-events.js +2 -0
- package/dist-server/server/thread-starters.js +33 -0
- package/dist-server/server/thread-title.js +46 -0
- package/dist-server/server/tts/fish.js +4 -5
- package/dist-server/server/tts/index.js +5 -3
- package/dist-server/server/vps-computer.js +80 -13
- package/dist-server/server/webhooks.js +18 -1
- package/dist-server/server/workspace-backup-policy.js +3 -0
- package/dist-server/server/workspace-backup.js +25 -0
- package/dist-server/server/workspace.js +66 -16
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/redact.js +1 -0
- package/dist-server/shared/tool-name.js +22 -0
- package/dist-server/shared/workspace-backup-client.js +1 -0
- package/dist-server/src/lib/citations.js +177 -0
- package/dist-server/src/lib/composer-attachments.js +7 -1
- package/dist-server/vps-container-mcp.js +202 -16
- package/dist-server/workspace-backup.worker.js +87 -9
- package/enterprise/server/index.js +5 -1
- package/package.json +1 -1
- package/dist/assets/channel-DrfGMu7X.js +0 -1
- package/dist/assets/index-BlnCEcU6.js +0 -588
- package/dist/assets/index-DVxjOvID.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +0 -8
|
@@ -30,6 +30,17 @@ const usageFrom = (usage) => usage
|
|
|
30
30
|
const asError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
31
31
|
class UnsupportedChatToolsError extends Error {
|
|
32
32
|
}
|
|
33
|
+
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.";
|
|
34
|
+
/** A short reply that only announces a next step, with nothing to answer. */
|
|
35
|
+
export function announcesAction(text) {
|
|
36
|
+
const reply = text.trim();
|
|
37
|
+
if (!reply || reply.length > 400 || reply.includes("?"))
|
|
38
|
+
return false;
|
|
39
|
+
return /(?:[:\u2014\u2013]|\.\.\.|\u2026)$/.test(reply) ||
|
|
40
|
+
/^(?:checking|let me|i'll|i will|i'm going to|i am going to|opening|pulling|looking|searching|navigating|reading|fetching|loading|now (?:i'll|let me|opening|checking|pulling|reading))\b/i.test(reply);
|
|
41
|
+
}
|
|
42
|
+
const TEXT_ONLY_ATTACHMENT_NOTE = "[The person attached image(s), but this model cannot see images. Say so if the request depends on them.]";
|
|
43
|
+
const TEXT_ONLY_SCREENSHOT_NOTE = "[Screenshot not shown: this model cannot see images. Read the page with a snapshot or a text tool instead.]";
|
|
33
44
|
function rejectsToolsParameter(status, body) {
|
|
34
45
|
if (status !== 400 && status !== 422)
|
|
35
46
|
return false;
|
|
@@ -48,11 +59,32 @@ function rejectsToolsParameter(status, body) {
|
|
|
48
59
|
/\b(?:tools|tool calling|function calling) (?:is|are) not supported\b/i.test(message) ||
|
|
49
60
|
/^(?:unsupported|unknown|unrecognized) (?:parameter|field):?\s*['"]?tools['"]?[.!]?$/i.test(message.trim()));
|
|
50
61
|
}
|
|
62
|
+
class UnsupportedReasoningReplayError extends Error {
|
|
63
|
+
}
|
|
64
|
+
/** DeepSeek-style providers need `reasoning_content` echoed on a tool-call
|
|
65
|
+
* assistant message; strict endpoints such as Groq reject the property. */
|
|
66
|
+
function rejectsReasoningReplay(status, body) {
|
|
67
|
+
if (status !== 400 && status !== 422)
|
|
68
|
+
return false;
|
|
69
|
+
let envelope;
|
|
70
|
+
try {
|
|
71
|
+
envelope = object(JSON.parse(body));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const message = object(envelope?.error)?.message ?? envelope?.error;
|
|
77
|
+
return typeof message === "string" && /\breasoning_content\b/.test(message) &&
|
|
78
|
+
/\b(?:unsupported|not supported|unknown|unrecognized|unexpected|not permitted|not allowed)\b/i.test(message);
|
|
79
|
+
}
|
|
51
80
|
/** Shared runtime for the three providers that speak OpenAI chat completions. */
|
|
52
81
|
export function createOpenAIChatRuntime(options) {
|
|
53
82
|
const { input } = options;
|
|
54
83
|
const listeners = new Set();
|
|
55
84
|
const active = new Map();
|
|
85
|
+
/** Models whose endpoint rejected an echoed `reasoning_content`. In memory
|
|
86
|
+
* only: later rounds and turns omit the field instead of failing again. */
|
|
87
|
+
const reasoningReplayRejected = new Set();
|
|
56
88
|
const emit = (event) => {
|
|
57
89
|
for (const listener of Array.from(listeners))
|
|
58
90
|
listener(event);
|
|
@@ -95,6 +127,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
95
127
|
const message = `${options.httpErrorLabel} HTTP ${response.status}${body ? `: ${body.slice(0, 200)}` : ""}`;
|
|
96
128
|
if (rejectsToolsParameter(response.status, body))
|
|
97
129
|
throw new UnsupportedChatToolsError(message);
|
|
130
|
+
if (messages.some((entry) => entry.reasoning_content !== undefined) && rejectsReasoningReplay(response.status, body)) {
|
|
131
|
+
throw new UnsupportedReasoningReplayError(message);
|
|
132
|
+
}
|
|
98
133
|
throw new Error(message);
|
|
99
134
|
}
|
|
100
135
|
if (!stream || response.headers.get("content-type")?.includes("application/json")) {
|
|
@@ -258,6 +293,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
258
293
|
clearTimeout(idleTimer);
|
|
259
294
|
}
|
|
260
295
|
};
|
|
296
|
+
const seesImages = (turn) => options.imageInput?.(turn.model || options.models().default) ?? true;
|
|
261
297
|
const messagesFor = (turn) => {
|
|
262
298
|
// The system message is the head of the resent prefix, so only the
|
|
263
299
|
// stable half belongs there: a volatile edit must not re-price the
|
|
@@ -278,7 +314,9 @@ export function createOpenAIChatRuntime(options) {
|
|
|
278
314
|
role: message.role,
|
|
279
315
|
content: message.text,
|
|
280
316
|
})),
|
|
281
|
-
{ role: "user", content: options.computerUse ?
|
|
317
|
+
{ role: "user", content: !options.computerUse ? userTurn.text
|
|
318
|
+
: seesImages(turn) ? chatUserContent(userTurn)
|
|
319
|
+
: userTurn.images?.length ? `${userTurn.text}\n\n${TEXT_ONLY_ATTACHMENT_NOTE}` : userTurn.text },
|
|
282
320
|
];
|
|
283
321
|
};
|
|
284
322
|
const sendTurn = async (turn) => {
|
|
@@ -357,6 +395,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
357
395
|
let toolFailed = false;
|
|
358
396
|
const denials = [];
|
|
359
397
|
const seenCalls = new Set();
|
|
398
|
+
let nudged = false;
|
|
360
399
|
try {
|
|
361
400
|
tools = await mountChatTools(options.tools === false ? undefined : turn.integrations, abort.signal, options.computerUse);
|
|
362
401
|
let optionalQuestionOnly = options.tools !== false && tools.definitions.length === 0;
|
|
@@ -377,7 +416,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
377
416
|
const parked = turnEntry.asides.splice(0);
|
|
378
417
|
if (parked.length)
|
|
379
418
|
messages.push({ role: "user", content: parked.join("\n\n") });
|
|
380
|
-
native("out", options.nativeLog.outgoing(turn, messages, model));
|
|
419
|
+
native("out", options.nativeLog.outgoing(turn, messages, model, tools.definitions));
|
|
381
420
|
let attempt = 0;
|
|
382
421
|
let completion;
|
|
383
422
|
for (;;) {
|
|
@@ -419,6 +458,16 @@ export function createOpenAIChatRuntime(options) {
|
|
|
419
458
|
catch (value) {
|
|
420
459
|
const error = asError(value);
|
|
421
460
|
const verdict = classifyError(error);
|
|
461
|
+
// A schema rejection of the echoed reasoning executed nothing
|
|
462
|
+
// upstream, so the same transcript without that one property
|
|
463
|
+
// repeats no operation. The error needs the field in the
|
|
464
|
+
// request, so the stripped resend cannot loop.
|
|
465
|
+
if (error instanceof UnsupportedReasoningReplayError && !streamed && !abort.signal.aborted) {
|
|
466
|
+
reasoningReplayRejected.add(model);
|
|
467
|
+
for (const message of messages)
|
|
468
|
+
delete message.reasoning_content;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
422
471
|
// Only our optional question changed a previously plain request.
|
|
423
472
|
// A structured parameter rejection has executed nothing; never
|
|
424
473
|
// downgrade mounted tools, streamed output or a handled call.
|
|
@@ -456,6 +505,11 @@ export function createOpenAIChatRuntime(options) {
|
|
|
456
505
|
if (completion.finishReason && completion.finishReason !== "stop") {
|
|
457
506
|
throw new ChatProtocolError(`provider did not finish the response (${completion.finishReason})`);
|
|
458
507
|
}
|
|
508
|
+
if (options.nudgeAnnouncedAction && !nudged && tools.definitions.length && announcesAction(completion.text)) {
|
|
509
|
+
nudged = true;
|
|
510
|
+
messages.push({ role: "assistant", content: completion.text }, { role: "user", content: NUDGE_ANNOUNCED_ACTION });
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
459
513
|
if (toolFailed) {
|
|
460
514
|
stopReason = "tool_error";
|
|
461
515
|
throw new ChatProtocolError("One or more tool operations failed or were denied. See the tool results; the final response is not an execution receipt.");
|
|
@@ -474,7 +528,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
474
528
|
if (seenCalls.size > MAX_CHAT_TOOL_CALLS)
|
|
475
529
|
throw new ChatProtocolError("tool-call limit reached");
|
|
476
530
|
messages.push({ role: "assistant", content: completion.text || null, tool_calls: completion.toolCalls,
|
|
477
|
-
...(completion.protocolReasoning ? { reasoning_content: completion.protocolReasoning } : {}),
|
|
531
|
+
...(completion.protocolReasoning && !reasoningReplayRejected.has(model) ? { [options.reasoningReplayField ?? "reasoning_content"]: completion.protocolReasoning } : {}),
|
|
478
532
|
...(completion.protocolReasoningDetails.length ? { reasoning_details: completion.protocolReasoningDetails } : {}),
|
|
479
533
|
});
|
|
480
534
|
const screenshotParts = [];
|
|
@@ -539,6 +593,10 @@ export function createOpenAIChatRuntime(options) {
|
|
|
539
593
|
started = true;
|
|
540
594
|
if (allowed) {
|
|
541
595
|
result = await tools.execute(call.function.name, args, abort.signal);
|
|
596
|
+
if (result.images?.length && !seesImages(turn)) {
|
|
597
|
+
const { images: _dropped, ...rest } = result;
|
|
598
|
+
result = { ...rest, text: `${rest.text}\n${TEXT_ONLY_SCREENSHOT_NOTE}` };
|
|
599
|
+
}
|
|
542
600
|
if (result.images?.length) {
|
|
543
601
|
try {
|
|
544
602
|
assertImageTransport(options.apiUrl);
|
|
@@ -637,7 +695,11 @@ export function createOpenAIChatRuntime(options) {
|
|
|
637
695
|
sessionModelSwitch: "in-session", customMcp: options.tools !== false, agentsMcp: options.tools !== false, composioMcp: options.tools !== false,
|
|
638
696
|
// The runtime owns the whole tool loop, so it can always take a
|
|
639
697
|
// user message mid-turn: park it, deliver before the next completion.
|
|
640
|
-
queueing: true
|
|
698
|
+
queueing: true,
|
|
699
|
+
// No shell and no file tool on the host at all: only MCP tools, each
|
|
700
|
+
// call a card in Ask (the Boat's `exec` runs on the Boat). A guest's
|
|
701
|
+
// turn is as confined as it gets.
|
|
702
|
+
guestTurns: "confined" },
|
|
641
703
|
sendTurn,
|
|
642
704
|
interruptTurn: async (threadId, turnId) => {
|
|
643
705
|
const turn = active.get(threadId);
|
|
@@ -15,6 +15,27 @@ const DEFAULT_MODELS = {
|
|
|
15
15
|
{ id: "llama-3.3-70b-versatile", label: "Llama 3.3 70B (Groq)", custom: true },
|
|
16
16
|
],
|
|
17
17
|
};
|
|
18
|
+
const DEFAULT_KEY_ENV = "OPENAI_COMPAT_API_KEY";
|
|
19
|
+
/** A provider's own key (the built-in OpenAI and OpenRouter instances). An
|
|
20
|
+
* instance reading one of these never falls back to the workspace
|
|
21
|
+
* OpenAI-compatible key, URL, model or routing. */
|
|
22
|
+
const OWN_KEY_ENVS = new Set(["OMB_OPENAI_API_KEY", "OMB_OPENROUTER_API_KEY"]);
|
|
23
|
+
/** OpenAI's own API, seeded until its live catalog loads. */
|
|
24
|
+
const OPENAI_MODELS = {
|
|
25
|
+
default: "gpt-5",
|
|
26
|
+
options: [
|
|
27
|
+
{ id: "gpt-5", label: "gpt-5" },
|
|
28
|
+
{ id: "gpt-5-mini", label: "gpt-5-mini" },
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
/** OpenAI's /models lists every model the key can reach: embeddings, speech,
|
|
32
|
+
* images, moderation and Responses-only models too. Only chat models belong
|
|
33
|
+
* in a chat picker. */
|
|
34
|
+
export function isOpenAIChatModel(id) {
|
|
35
|
+
if (!/^(gpt-|chatgpt-|o\d)/i.test(id))
|
|
36
|
+
return false;
|
|
37
|
+
return !/(audio|realtime|tts|transcribe|search|image|embedding|moderation|instruct|codex|computer-use|deep-research|-pro\b)/i.test(id);
|
|
38
|
+
}
|
|
18
39
|
function isOpenRouterUrl(url) {
|
|
19
40
|
try {
|
|
20
41
|
const host = new URL(url).hostname.toLowerCase();
|
|
@@ -30,7 +51,11 @@ function decodeConfig(raw) {
|
|
|
30
51
|
throw new Error("tools must be a boolean");
|
|
31
52
|
if (config.managedModels !== undefined && (!Array.isArray(config.managedModels) || !config.managedModels.length || config.managedModels.some(model => typeof model !== "string" || !model.trim())))
|
|
32
53
|
throw new Error("Invalid managed models.");
|
|
33
|
-
const
|
|
54
|
+
const ownKey = typeof config.apiKeyEnv === "string" && OWN_KEY_ENVS.has(config.apiKeyEnv);
|
|
55
|
+
if (config.catalog !== undefined && config.catalog !== "openai")
|
|
56
|
+
throw new Error("catalog must be \"openai\"");
|
|
57
|
+
// Workspace defaults belong to the shared OpenAI-compatible connection only.
|
|
58
|
+
const envUrl = ownKey ? undefined : process.env.OPENAI_COMPAT_URL;
|
|
34
59
|
return {
|
|
35
60
|
...(config.tools !== undefined ? { tools: config.tools } : {}),
|
|
36
61
|
...(config.managedModels ? { managedModels: config.managedModels } : {}),
|
|
@@ -38,55 +63,65 @@ function decodeConfig(raw) {
|
|
|
38
63
|
.replace(/\/+$/, ""),
|
|
39
64
|
apiKeyEnv: typeof config.apiKeyEnv === "string" && config.apiKeyEnv
|
|
40
65
|
? config.apiKeyEnv
|
|
41
|
-
:
|
|
66
|
+
: DEFAULT_KEY_ENV,
|
|
67
|
+
...(config.catalog === "openai" ? { catalog: "openai" } : {}),
|
|
42
68
|
key: typeof config.key === "string" && config.key ? config.key : undefined,
|
|
43
69
|
model: typeof config.model === "string" && config.model
|
|
44
70
|
? config.model
|
|
45
|
-
: process.env.OPENAI_COMPAT_MODEL || undefined,
|
|
71
|
+
: ownKey ? undefined : process.env.OPENAI_COMPAT_MODEL || undefined,
|
|
46
72
|
// An explicit empty override disables inherited routing for an isolated
|
|
47
73
|
// connection (CLI setup uses this). Absent still inherits the global pin.
|
|
48
74
|
provider: typeof config.provider === "string"
|
|
49
75
|
? config.provider || undefined
|
|
50
|
-
: process.env.OPENAI_COMPAT_PROVIDER || undefined,
|
|
76
|
+
: ownKey ? undefined : process.env.OPENAI_COMPAT_PROVIDER || undefined,
|
|
51
77
|
};
|
|
52
78
|
}
|
|
53
79
|
export const OpenAICompatDriver = {
|
|
54
80
|
driverKind: DRIVER_KIND,
|
|
55
81
|
metadata: {
|
|
56
|
-
displayName: "OpenAI-compatible
|
|
82
|
+
displayName: "Other (OpenAI-compatible)",
|
|
57
83
|
supportsMultipleInstances: true,
|
|
58
|
-
access: "
|
|
84
|
+
access: "api",
|
|
59
85
|
},
|
|
60
86
|
models: DEFAULT_MODELS,
|
|
87
|
+
// Nothing to install: the key and base URL are saved in the app. The old
|
|
88
|
+
// descriptor offered a config.json sentence as an "Open install in
|
|
89
|
+
// Terminal" command.
|
|
61
90
|
install: {
|
|
62
91
|
docsUrl: "https://openrouter.ai/keys",
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
darwin: "Get a free key at https://openrouter.ai/keys (or https://console.groq.com) then add it to ~/.openmausbot/config.json under openaiCompat.key",
|
|
66
|
-
linux: "Get a free key at https://openrouter.ai/keys (or https://console.groq.com) then add it to ~/.openmausbot/config.json under openaiCompat.key",
|
|
67
|
-
win32: "Get a free key at https://openrouter.ai/keys (or https://console.groq.com) then add it to %USERPROFILE%\\.openmausbot\\config.json under openaiCompat.key",
|
|
68
|
-
},
|
|
92
|
+
settings: "connections",
|
|
93
|
+
signInCommand: "Save an OpenAI-compatible API key in Settings → API keys, or set OPENAI_COMPAT_API_KEY on the server.",
|
|
69
94
|
},
|
|
70
95
|
decodeConfig,
|
|
71
96
|
defaultConfig: () => decodeConfig({}),
|
|
72
97
|
async create(input) {
|
|
73
98
|
const { config } = input;
|
|
99
|
+
// An instance that names its own key variable reads only that one: the
|
|
100
|
+
// workspace key (OPENAI_COMPAT_API_KEY) belongs to the workspace's
|
|
101
|
+
// endpoint and must not reach this instance's host.
|
|
102
|
+
const ownKeyVariable = config.apiKeyEnv !== DEFAULT_KEY_ENV;
|
|
74
103
|
const apiKey = config.key ??
|
|
75
104
|
input.environment[config.apiKeyEnv] ??
|
|
76
|
-
input.environment
|
|
105
|
+
(ownKeyVariable ? undefined : input.environment[DEFAULT_KEY_ENV]) ??
|
|
77
106
|
process.env[config.apiKeyEnv] ??
|
|
78
|
-
process.env
|
|
107
|
+
(ownKeyVariable ? undefined : process.env[DEFAULT_KEY_ENV]) ??
|
|
79
108
|
"";
|
|
109
|
+
// The default key and the built-in providers' keys are saved in
|
|
110
|
+
// Settings → API keys; any other variable is configured where it was written.
|
|
111
|
+
const missingKey = !ownKeyVariable || OWN_KEY_ENVS.has(config.apiKeyEnv)
|
|
112
|
+
? "No API key — open Settings → API keys."
|
|
113
|
+
: `no API key — set ${config.apiKeyEnv} or add it to the instance config`;
|
|
114
|
+
const seeded = config.catalog === "openai" ? OPENAI_MODELS : DEFAULT_MODELS;
|
|
80
115
|
let catalog = config.managedModels
|
|
81
116
|
? { default: config.managedModels[0], options: config.managedModels.map(id => ({ id, label: id })) }
|
|
82
117
|
: config.model
|
|
83
118
|
? {
|
|
84
119
|
default: config.model,
|
|
85
|
-
options:
|
|
86
|
-
?
|
|
87
|
-
: [{ id: config.model, label: config.model, custom: true }, ...
|
|
120
|
+
options: seeded.options.some((model) => model.id === config.model)
|
|
121
|
+
? seeded.options
|
|
122
|
+
: [{ id: config.model, label: config.model, custom: true }, ...seeded.options],
|
|
88
123
|
}
|
|
89
|
-
:
|
|
124
|
+
: seeded;
|
|
90
125
|
const fetchModels = async () => {
|
|
91
126
|
if (config.managedModels)
|
|
92
127
|
return;
|
|
@@ -100,7 +135,14 @@ export const OpenAICompatDriver = {
|
|
|
100
135
|
if (!response.ok)
|
|
101
136
|
return;
|
|
102
137
|
const json = await response.json();
|
|
103
|
-
|
|
138
|
+
let rows = Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : [];
|
|
139
|
+
// OpenAI lists its catalog unordered and mixed with non-chat models:
|
|
140
|
+
// keep chat models, newest first.
|
|
141
|
+
if (config.catalog === "openai") {
|
|
142
|
+
rows = rows
|
|
143
|
+
.filter((row) => typeof row.id === "string" && isOpenAIChatModel(row.id))
|
|
144
|
+
.sort((a, b) => (typeof b.created === "number" ? b.created : 0) - (typeof a.created === "number" ? a.created : 0));
|
|
145
|
+
}
|
|
104
146
|
const seen = new Set();
|
|
105
147
|
const options = [];
|
|
106
148
|
for (const row of rows) {
|
|
@@ -111,7 +153,8 @@ export const OpenAICompatDriver = {
|
|
|
111
153
|
options.push({
|
|
112
154
|
id,
|
|
113
155
|
label: typeof row.name === "string" && row.name.trim() ? row.name : id,
|
|
114
|
-
custom
|
|
156
|
+
// A provider's own catalog is its official list, not a custom model.
|
|
157
|
+
...(config.catalog === "openai" ? {} : { custom: true }),
|
|
115
158
|
});
|
|
116
159
|
}
|
|
117
160
|
if (!options.length)
|
|
@@ -146,15 +189,22 @@ export const OpenAICompatDriver = {
|
|
|
146
189
|
: {}),
|
|
147
190
|
}),
|
|
148
191
|
httpErrorLabel: "upstream",
|
|
149
|
-
missingKeyError:
|
|
150
|
-
unavailableReason:
|
|
192
|
+
missingKeyError: missingKey,
|
|
193
|
+
unavailableReason: missingKey,
|
|
151
194
|
timeoutMs: idleTimeoutMs(),
|
|
152
195
|
reasoning: true,
|
|
153
196
|
billing: "metered",
|
|
154
197
|
includeUsageInCompleted: true,
|
|
155
198
|
nativeLog: {
|
|
156
199
|
source: "openai-compat.chat.completions",
|
|
157
|
-
|
|
200
|
+
// Tool names are the answer to "did the harness send them?" — the
|
|
201
|
+
// LiteLLM/proxy hop after this point is what drops tools silently,
|
|
202
|
+
// and until now the tee held no record either side could compare.
|
|
203
|
+
outgoing: (_turn, messages, model, tools) => ({
|
|
204
|
+
model,
|
|
205
|
+
messageCount: messages.length,
|
|
206
|
+
tools: tools.map(tool => tool.function.name),
|
|
207
|
+
}),
|
|
158
208
|
incoming: ({ text, reasoning, usage }) => ({
|
|
159
209
|
textLength: text.length,
|
|
160
210
|
reasoningLength: reasoning.length,
|
|
@@ -57,6 +57,8 @@ export function readPromptSplitReceipt(scope, key) {
|
|
|
57
57
|
stable: record.stable,
|
|
58
58
|
volatile: record.volatile,
|
|
59
59
|
...(typeof record.turnsSinceFull === "number" ? { turnsSinceFull: record.turnsSinceFull } : {}),
|
|
60
|
+
...(typeof record.lastUsed === "number" && record.lastUsed > 0 ? { lastUsed: record.lastUsed } : {}),
|
|
61
|
+
...(typeof record.peakUsed === "number" && record.peakUsed > 0 ? { peakUsed: record.peakUsed } : {}),
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -9,9 +9,10 @@ import { join } from "node:path";
|
|
|
9
9
|
import { stripVTControlCharacters } from "node:util";
|
|
10
10
|
import { DATA_DIR, stripWorkspaceCredentialEnv } from "./config.js";
|
|
11
11
|
import { augmentedPath, findCliCandidates, registerPathDir, resetPathCache } from "./env-path.js";
|
|
12
|
-
import { killCliTree, spawnCli } from "./procs.js";
|
|
12
|
+
import { execCli, killCliTree, spawnCli } from "./procs.js";
|
|
13
13
|
const MAX_OUTPUT = 64 * 1024;
|
|
14
14
|
const DEFAULT_TIMEOUT_MS = 10 * 60_000;
|
|
15
|
+
const PROBE_TIMEOUT_MS = 30_000;
|
|
15
16
|
/** npm's global prefix for engines the app installs itself. */
|
|
16
17
|
export function enginesPrefix(baseDir = DATA_DIR) {
|
|
17
18
|
return join(baseDir, "tools", "npm");
|
|
@@ -68,21 +69,54 @@ async function installOnce(pkg, options) {
|
|
|
68
69
|
stripWorkspaceCredentialEnv(env);
|
|
69
70
|
// npm 11 skips a dependency's install script unless the package is named
|
|
70
71
|
// here; the engines that need one (Claude Code) are exactly these.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
// Engines like Codex ship their native binary as a platform optional
|
|
73
|
+
// dependency, so a user-level `omit=optional` must not leave it out.
|
|
74
|
+
const args = ["install", "-g", "--prefix", prefix, "--loglevel=error", "--include=optional", `--allow-scripts=${pkg}`, `${pkg}@latest`];
|
|
75
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
76
|
+
await runInstall(pkg, args, env, prefix, timeoutMs);
|
|
76
77
|
// Whatever the boot registration did, the directory we just filled must
|
|
77
78
|
// be on the engines' PATH from here on.
|
|
78
|
-
|
|
79
|
+
const binDir = enginesBinDir(options.baseDir);
|
|
80
|
+
registerPathDir(binDir);
|
|
79
81
|
resetPathCache();
|
|
80
|
-
if (options.cli)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
82
|
+
if (!options.cli)
|
|
83
|
+
return;
|
|
84
|
+
const installed = findCliCandidates(options.cli).find((path) => path.startsWith(binDir));
|
|
85
|
+
if (!installed) {
|
|
86
|
+
throw new Error(`${pkg} installed, but it did not provide a \`${options.cli}\` command. Check the package name in this engine's install descriptor.`);
|
|
85
87
|
}
|
|
88
|
+
// npm exits 0 even when it drops an optional dependency it failed to
|
|
89
|
+
// download, which leaves a launcher without its platform binary (#2064).
|
|
90
|
+
// A copy here shadows every other one on PATH, so prove it starts; a
|
|
91
|
+
// second run fetches what the first one dropped.
|
|
92
|
+
let failure = await probeCli(installed, env);
|
|
93
|
+
if (failure === null)
|
|
94
|
+
return;
|
|
95
|
+
await runInstall(pkg, args, env, prefix, timeoutMs);
|
|
96
|
+
failure = await probeCli(installed, env);
|
|
97
|
+
if (failure === null)
|
|
98
|
+
return;
|
|
99
|
+
throw new Error(`npm installed ${pkg}, but \`${options.cli}\` does not start: ${failure}\nnpm can leave out a platform package when its download fails, even after a retry. Check this server's network connection, then install again from Settings.`);
|
|
100
|
+
}
|
|
101
|
+
async function runInstall(pkg, args, env, prefix, timeoutMs) {
|
|
102
|
+
const result = await runNpm(args, env, prefix, timeoutMs);
|
|
103
|
+
if (result.code !== 0) {
|
|
104
|
+
throw new Error(`npm could not install ${pkg} on this server.${tail(result.output)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Null when `<cli> --version` succeeds, else why it did not. A Node
|
|
108
|
+
* launcher that throws prints its source line first, so its `Error:` line
|
|
109
|
+
* is the one worth showing. */
|
|
110
|
+
function probeCli(cli, env) {
|
|
111
|
+
return new Promise((resolveProbe) => {
|
|
112
|
+
execCli(cli, ["--version"], { env: { ...env, PATH: augmentedPath() }, timeout: PROBE_TIMEOUT_MS, killSignal: "SIGKILL", maxBuffer: MAX_OUTPUT }, (error, stdout, stderr) => {
|
|
113
|
+
if (!error)
|
|
114
|
+
return resolveProbe(null);
|
|
115
|
+
const lines = stripVTControlCharacters(`${stderr ?? ""}\n${stdout}`).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
116
|
+
const said = lines.find((line) => /^\w*Error: /.test(line)) ?? lines[0] ?? error.message;
|
|
117
|
+
resolveProbe(said.slice(0, 400));
|
|
118
|
+
});
|
|
119
|
+
});
|
|
86
120
|
}
|
|
87
121
|
function tail(output) {
|
|
88
122
|
const clean = stripVTControlCharacters(output).trim();
|
|
@@ -27,6 +27,15 @@ function nvmBinDirs() {
|
|
|
27
27
|
return [];
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
/** A sealed test fixture finds CLIs only where it was granted them: its own
|
|
31
|
+
* PATH, OMB_EXTRA_PATH, app-managed dirs, and install dirs under its own
|
|
32
|
+
* temporary home. The machine-wide install dirs and the login shell's PATH
|
|
33
|
+
* would hand it this computer's real CLIs — a Homebrew `codex` made the
|
|
34
|
+
* fixture's ChatGPT plan engine "available" on a developer Mac (#2035).
|
|
35
|
+
* Only the verification launcher (scripts/control-omb.ts) sets it. */
|
|
36
|
+
function sealedFixture() {
|
|
37
|
+
return process.env.OMB_TEST_SEALED_PATH === "1";
|
|
38
|
+
}
|
|
30
39
|
function knownDirs() {
|
|
31
40
|
const home = homedir();
|
|
32
41
|
return [
|
|
@@ -36,8 +45,11 @@ function knownDirs() {
|
|
|
36
45
|
join(home, ".grok", "bin"), // x.ai installer
|
|
37
46
|
join(home, ".opencode", "bin"), // opencode installer
|
|
38
47
|
join(home, ".claude", "local"), // claude "local install"
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
// Machine-wide rather than under the (possibly temporary) home.
|
|
49
|
+
...(sealedFixture() ? [] : [
|
|
50
|
+
"/opt/homebrew/bin", // brew, Apple silicon
|
|
51
|
+
"/usr/local/bin", // brew Intel / classic installs
|
|
52
|
+
]),
|
|
41
53
|
join(home, ".volta", "bin"),
|
|
42
54
|
join(home, ".bun", "bin"),
|
|
43
55
|
join(home, ".asdf", "shims"),
|
|
@@ -52,8 +64,9 @@ function knownDirs() {
|
|
|
52
64
|
* invisible until it restarts, because Windows never pushes PATH changes
|
|
53
65
|
* into a live process. Scanning the standard install locations recovers
|
|
54
66
|
* those without a restart — `~/.grok/bin` (the x.ai installer) and
|
|
55
|
-
* `%APPDATA%\npm` (global npm shims), plus `%LOCALAPPDATA%\agy\bin
|
|
56
|
-
* every engine we ship an install
|
|
67
|
+
* `%APPDATA%\npm` (global npm shims), plus `%LOCALAPPDATA%\agy\bin` and
|
|
68
|
+
* `%LOCALAPPDATA%\cursor-agent`, cover every engine we ship an install
|
|
69
|
+
* command for. */
|
|
57
70
|
function windowsKnownDirs() {
|
|
58
71
|
const home = homedir();
|
|
59
72
|
const appData = process.env.APPDATA ?? join(home, "AppData", "Roaming");
|
|
@@ -62,12 +75,15 @@ function windowsKnownDirs() {
|
|
|
62
75
|
join(appData, "npm"), // npm -g shims: claude, codex
|
|
63
76
|
join(home, ".grok", "bin"), // x.ai installer
|
|
64
77
|
join(localAppData, "agy", "bin"), // Antigravity installer
|
|
78
|
+
// Cursor installer: cursor-agent.* and its `agent` copies (MOCA-272)
|
|
79
|
+
join(localAppData, "cursor-agent"),
|
|
65
80
|
join(home, ".local", "bin"), // claude native installer
|
|
66
81
|
join(home, ".claude", "local"),
|
|
67
82
|
join(home, "bin"), // Factory droid installer (%USERPROFILE%\bin)
|
|
68
83
|
join(home, ".bun", "bin"),
|
|
69
84
|
join(home, ".deno", "bin"),
|
|
70
85
|
join(home, "go", "bin"),
|
|
86
|
+
join(process.env.ProgramFiles ?? "C:\\Program Files", "Docker", "Docker", "resources", "bin"), // Docker Desktop installer
|
|
71
87
|
];
|
|
72
88
|
}
|
|
73
89
|
let cached = null;
|
|
@@ -115,7 +131,7 @@ export function augmentedPath() {
|
|
|
115
131
|
// belt-and-braces: fold in the login shell's PATH once, in the
|
|
116
132
|
// background — catches anything the known-dirs list doesn't (custom
|
|
117
133
|
// rc exports). Never blocks a spawn; the next one benefits.
|
|
118
|
-
if (!probed && !process.env.VITEST && process.platform !== "win32") {
|
|
134
|
+
if (!probed && !process.env.VITEST && !sealedFixture() && process.platform !== "win32") {
|
|
119
135
|
probed = true;
|
|
120
136
|
probeLoginShellPath();
|
|
121
137
|
}
|
|
@@ -235,11 +235,11 @@ export class ProviderRegistry {
|
|
|
235
235
|
install: withServerInstall(driver?.install, npmPresent),
|
|
236
236
|
authentication: inst.startAuthentication
|
|
237
237
|
? {
|
|
238
|
-
method: inst.getAuthentication && inst.completeAuthentication
|
|
238
|
+
method: inst.authenticationMethod ?? (inst.getAuthentication && inst.completeAuthentication
|
|
239
239
|
? "paste-code" // a link to open, then a code pasted back (Claude)
|
|
240
240
|
: inst.getAuthentication
|
|
241
241
|
? "device-code" // a code to enter at the provider's page (Codex)
|
|
242
|
-
: "browser", // a link and a callback URL (managed engines)
|
|
242
|
+
: "browser"), // a link and a callback URL (managed engines)
|
|
243
243
|
// the browser may remove the stored sign-in to switch accounts
|
|
244
244
|
signOut: inst.signOut !== undefined,
|
|
245
245
|
}
|