openmausbot 0.1.97 → 0.1.98
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/ActivityPanel-H6D3SD39.js +1 -0
- package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +7 -0
- package/dist/assets/CloudBackendPicker-iezpJFpF.js +1 -0
- package/dist/assets/ComputerPanel-DZPj-IWu.js +1 -0
- package/dist/assets/DesktopViewer-utONms1p.js +5 -0
- package/dist/assets/InspectorPanel-OVaBVrzC.js +5 -0
- package/dist/assets/KeyboardShortcutsModal-gk9mvHj_.js +1 -0
- package/dist/assets/LocalVmWorkspace-DLIvd2kC.js +1 -0
- package/dist/assets/MacLocalControl-bJv8yiPB.js +1 -0
- package/dist/assets/NewBotDialog-1qGOAdUM.js +2 -0
- package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +1 -0
- package/dist/assets/RoutinesPage-BM2D4qGi.js +1 -0
- package/dist/assets/RoutinesSection-Dny9ra-c.js +1 -0
- package/dist/assets/SettingsModal-gC6XJJmt.js +11 -0
- package/dist/assets/TeamMapPage-B6fuu4r_.js +8 -0
- package/dist/assets/TriggersPanel-j0qTP2CO.js +1 -0
- package/dist/assets/VisibilitySection-DT0FqPvu.js +12 -0
- package/dist/assets/WebhooksPanel--YQyCjKO.js +3 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-k1UCNXb7.js → abnfDiagram-O67JEVCF-BsjcFX0-.js} +1 -1
- package/dist/assets/activity-BxOkayvD.js +1 -0
- package/dist/assets/{arc-BUWGa9UU.js → arc-CJNA94c6.js} +1 -1
- package/dist/assets/architectureDiagram-NJMV4G6O-dz2ud7lP.js +36 -0
- package/dist/assets/{blockDiagram-BEXU5L5S-Ag9nLW4H.js → blockDiagram-BEXU5L5S-ChBEALoR.js} +1 -1
- package/dist/assets/brain-CFZ7PEXc.js +1 -0
- package/dist/assets/{c4Diagram-YGBWAQC7-Bh0sjgUa.js → c4Diagram-YGBWAQC7-KZNRKDQe.js} +1 -1
- package/dist/assets/channel-WhFyujhZ.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BFBcIIqu.js → chunk-2Q5K7J3B-BLLwmCET.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-Dvfpex-f.js → chunk-3FUC2YCW-CQxyreBZ.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-CMjP2YZW.js → chunk-5VM5RSS4-D6sFMW0u.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BryoeTc7.js → chunk-GTNCS2PH-BaZ1YgLF.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BhfvcchO.js → chunk-GWA4HPMP-CcalphmC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-bcCVp8rp.js → chunk-JWPE2WC7-CSk8R3ve.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-yKIjIlkQ.js → chunk-NETBCI7D-CTmLjA57.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-D3Zqbmup.js → chunk-WEXAMYUT-CM9Z77Ne.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-CjVoIMG6.js → chunk-XXDRQBXY-B1fxpMS2.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-mUCWdVyH.js → classDiagram-v2-NBCMYWYE-DCCyoLpj.js} +1 -1
- package/dist/assets/computer-control-NLov352p.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-KyImBKvE.js +1 -0
- package/dist/assets/{cynefin-OW5HDTMX-BpRrxoO4.js → cynefin-OW5HDTMX-fsZc6MGa.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-D3VBWCPK.js → cynefinDiagram-VND7K2PF-CGmHsShB.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DxEIACOO.js → dagre-6A5THRUB-Dah1h-CH.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BANgZEOS.js → diagram-22UHCM2B-BIu71OyV.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-Cnq7nJir.js → diagram-3UASUU5V-gxhUB_Mu.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-4TBDIWnz.js → diagram-ATOU4E4O-CBhusUBn.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CD_B39D4.js → diagram-CDSNMT55-BdPrc86x.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-CzvnFhEC.js → diagram-MLGK6HIB-Clp110R_.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-C4w2SNml.js → diagram-MPIPVDR6-DJKfbugw.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-gkUEBm2s.js → ebnfDiagram-ZINNZB2B-DnsltoBu.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-B_56X6mw.js → elk-276RUBZZ-jWdq6gqH.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-C_oByoDS.js → erDiagram-OPXOYQCR-DArPVf8u.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CR9xXJkC.js → flowDiagram-KWPJA3E3-D2HZ-2f7.js} +1 -1
- package/dist/assets/ganttDiagram-FUAMR5RP--o0KIxEo.js +292 -0
- package/dist/assets/{gitGraphDiagram-X574FWY7-VJ6-Tp_p.js → gitGraphDiagram-X574FWY7-CsoXr2D7.js} +1 -1
- package/dist/assets/image-plus-BbVjGHin.js +1 -0
- package/dist/assets/index-BrPW5HDO.css +1 -0
- package/dist/assets/index-C3NqfdVU.js +585 -0
- package/dist/assets/{index-Cp4S4NOL.js → index-CJTp9JEp.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-2Lu9te4D.js → infoDiagram-VRGFBTTK-g-6lNp7r.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DR49XY-e.js → ishikawaDiagram-OU5B5YK6-B1d6qVTV.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-ZoR0M305.js → journeyDiagram-ZHPQQLJL-BTPuiU6T.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DLKHxrO-.js → kanban-definition-PNTS6WVX-bDDuRJvP.js} +1 -1
- package/dist/assets/{linear-BASsfWly.js → linear-BVVKbEMy.js} +1 -1
- package/dist/assets/{mermaid.core-BVidiPkk.js → mermaid.core-JJdoKyKB.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-g_Qx0U5C.js → mindmap-definition-NLK3R4M7-B4aE3T0q.js} +1 -1
- package/dist/assets/module-DCUsy2BQ.js +3 -0
- package/dist/assets/page-visible-BBo_Zm8g.js +1 -0
- package/dist/assets/{pegDiagram-GJSIUBJH-DI9Cjs8w.js → pegDiagram-GJSIUBJH-DFKQWpR7.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-sRI0Uhr5.js → pieDiagram-5QR66LMP-Iv7OKVJi.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-CamKTby3.js → quadrantDiagram-O4NWA36T-U2YnfwBk.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-DOLlkbf3.js → railroadDiagram-XR7U4H2S-CuPy6RqM.js} +1 -1
- package/dist/assets/remote-desktop-panel-BNEj7tUx.js +1 -0
- package/dist/assets/{requirementDiagram-PLB6GJNP-DrRQ5r97.js → requirementDiagram-PLB6GJNP-bMI5AK7T.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-DGS_lXcX.js → sankeyDiagram-IPEJSGJF-BKcFE2WQ.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-CkjWqwtf.js → sequenceDiagram-PO4LG4MO-Bn9q88kl.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CepRgful.js → sizeCapture-INFHLROL-Cvmdq9HD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-hykr-9RS.js → stateDiagram-v2-GCMORJYK-JJQYw3eQ.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-CKOHEZbI.js → swimlanes-2SLR337P-BkSCBEwp.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +8 -0
- package/dist/assets/table-file.worker-KkMx-SNx.js +10 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CB8pJ6AC.js → timeline-definition-EJHVYXUP-DsaM4GHr.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DrclJHdl.js → usecaseDiagram-POWQR4AR-3FOwLq7B.js} +1 -1
- package/dist/assets/user-DcZBnAuv.js +1 -0
- package/dist/assets/{vennDiagram-UO4OBE2U-zS23qjv8.js → vennDiagram-UO4OBE2U-DyNjJsKt.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-52jgeKxz.js → wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DSdl9rQ8.js → xychartDiagram-PMCCYNJV-BXZpg955.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +19 -8
- package/dist-server/container-mcp.js +8 -0
- package/dist-server/desktop-entry.mjs +54 -0
- package/dist-server/drivers/agents-proxy.js +21 -4
- package/dist-server/index.js +1412 -821
- package/dist-server/openmausbot.js +837 -571
- package/dist-server/pair-cli.js +837 -571
- package/dist-server/permission-proxy.js +2 -0
- package/dist-server/server/atomic.js +70 -2
- package/dist-server/server/cloud-home-start.js +3 -3
- package/dist-server/server/config.js +22 -1
- package/dist-server/server/connector-card-text.js +15 -0
- package/dist-server/server/default-model-selection.js +7 -4
- package/dist-server/server/drivers/acp/core.js +10 -0
- package/dist-server/server/drivers/acp/grok.js +47 -5
- package/dist-server/server/drivers/agents-catalog.js +24 -4
- package/dist-server/server/drivers/agents-proxy.js +1 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +5 -3
- package/dist-server/server/drivers/claude-login-auth.js +1 -1
- package/dist-server/server/drivers/claude.js +35 -11
- package/dist-server/server/drivers/codex-device-auth.js +27 -319
- package/dist-server/server/drivers/device-auth.js +342 -0
- package/dist-server/server/drivers/openai-chat.js +12 -4
- package/dist-server/server/fleet-agent.js +18 -5
- package/dist-server/server/harness/http.js +5 -2
- package/dist-server/server/harness/registry.js +19 -0
- package/dist-server/server/harness-capabilities.js +7 -4
- package/dist-server/server/hosted-models.js +3 -1
- package/dist-server/server/index.js +268 -54
- package/dist-server/server/key-rejections.js +57 -0
- package/dist-server/server/message-file.js +7 -4
- package/dist-server/server/permission-proxy.js +4 -0
- package/dist-server/server/provider-key-check.js +5 -1
- package/dist-server/server/request-auth.js +1 -1
- package/dist-server/server/room-turn-timeout.js +28 -0
- package/dist-server/server/routes/thread-models.js +36 -0
- package/dist-server/server/routines.js +2 -2
- package/dist-server/server/skills.js +3 -2
- package/dist-server/server/store.js +148 -60
- package/dist-server/server/team-backup.js +19 -4
- package/dist-server/server/thread-model.js +25 -0
- package/dist-server/server/turn-continuation.js +8 -2
- package/dist-server/server/webhook-ingress.js +8 -3
- package/dist-server/server/webhooks.js +40 -5
- package/dist-server/shared/device-sign-in.js +57 -0
- package/dist-server/shared/team-backup.js +2 -0
- package/dist-server/shared/thread-model.js +13 -0
- package/dist-server/vps-container-mcp.js +8 -0
- package/dist-server/workspace-backup.worker.js +41 -22
- package/package.json +1 -1
- package/dist/assets/DesktopViewer-CPidMCtt.js +0 -5
- package/dist/assets/architectureDiagram-NJMV4G6O-DiPor3UT.js +0 -36
- package/dist/assets/channel-DR8eD6QW.js +0 -1
- package/dist/assets/cose-bilkent-JH36ORCC-BBmR1bbX.js +0 -1
- package/dist/assets/ganttDiagram-FUAMR5RP-gtYBTu0B.js +0 -292
- package/dist/assets/index-CpaIXRLp.js +0 -614
- package/dist/assets/index-Z81SMsgR.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-8vqZ4-.js +0 -8
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { stripVTControlCharacters } from "node:util";
|
|
6
|
+
import { deviceSignInPrompt } from "../../shared/device-sign-in.js";
|
|
7
|
+
import { killCliTree, spawnCli } from "../procs.js";
|
|
8
|
+
const MAX_OUTPUT = 16_384;
|
|
9
|
+
// Two configured instances can use the same on-disk login. Only the auth
|
|
10
|
+
// operation is exclusive: normal turns are not blocked by this lock.
|
|
11
|
+
const authenticatingHomes = new Set();
|
|
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) {
|
|
15
|
+
try {
|
|
16
|
+
return realpathSync(path);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
const parent = dirname(path);
|
|
20
|
+
return parent === path ? path : join(canonicalPath(parent), basename(path));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** A fixed login command, not a remotely accessible terminal. CLI output
|
|
24
|
+
* stays in bounded private memory; only a device page and code are exposed. */
|
|
25
|
+
export class DeviceAuthController {
|
|
26
|
+
flow = null;
|
|
27
|
+
command = null;
|
|
28
|
+
disposed = false;
|
|
29
|
+
spec;
|
|
30
|
+
options;
|
|
31
|
+
constructor(spec, options) {
|
|
32
|
+
this.spec = spec;
|
|
33
|
+
this.options = options;
|
|
34
|
+
}
|
|
35
|
+
/** The credential home the lock is held on, for this environment. */
|
|
36
|
+
home(env, action) {
|
|
37
|
+
const { homeEnv, homeDir, product } = this.spec;
|
|
38
|
+
const home = env.HOME || env.USERPROFILE || homedir();
|
|
39
|
+
const named = env[homeEnv];
|
|
40
|
+
if (!isAbsolute(home) || (named && !isAbsolute(named))) {
|
|
41
|
+
throw new Error(`Use an absolute HOME and ${homeEnv} path for this server's ${product} provider before ${action}.`);
|
|
42
|
+
}
|
|
43
|
+
return { home, homeKey: canonicalPath(resolve(named || join(home, homeDir))) };
|
|
44
|
+
}
|
|
45
|
+
async start() {
|
|
46
|
+
const { product, account, status } = this.spec;
|
|
47
|
+
if (this.disposed)
|
|
48
|
+
throw new Error("This provider was removed. Refresh Settings before signing in.");
|
|
49
|
+
if (this.flow?.status.phase === "waiting") {
|
|
50
|
+
if (this.flow.ready)
|
|
51
|
+
return { ...this.flow.status, phase: "waiting" };
|
|
52
|
+
throw new Error(`A ${account} sign-in is already starting. Please wait for the code.`);
|
|
53
|
+
}
|
|
54
|
+
await this.flow?.stopping;
|
|
55
|
+
if (this.disposed)
|
|
56
|
+
throw new Error("This provider was removed. Refresh Settings before signing in.");
|
|
57
|
+
const env = this.options.environment();
|
|
58
|
+
const { home, homeKey } = this.home(env, "signing in");
|
|
59
|
+
if (authenticatingHomes.has(homeKey)) {
|
|
60
|
+
throw new Error(`A ${account} sign-in is already running for this server account. Finish or cancel it before starting another.`);
|
|
61
|
+
}
|
|
62
|
+
authenticatingHomes.add(homeKey);
|
|
63
|
+
return new Promise((resolveStart, reject) => {
|
|
64
|
+
const flow = {
|
|
65
|
+
status: {
|
|
66
|
+
phase: "waiting", flowId: randomUUID(), authorizationUrl: null,
|
|
67
|
+
expiresAt: new Date(Date.now() + (this.options.lifetimeMs ?? 15 * 60_000)).toISOString(),
|
|
68
|
+
},
|
|
69
|
+
child: null, ready: false, resolve: resolveStart, reject, homeKey,
|
|
70
|
+
startupTimer: setTimeout(() => this.finish(flow, "failed", `${product} did not provide a sign-in code in time. Check the server connection and update ${product}, then try again.`), this.options.startupTimeoutMs ?? 30_000),
|
|
71
|
+
expiryTimer: setTimeout(() => this.finish(flow, "expired", `The ${account} sign-in code expired. Start sign-in again.`), this.options.lifetimeMs ?? 15 * 60_000),
|
|
72
|
+
};
|
|
73
|
+
flow.startupTimer.unref();
|
|
74
|
+
flow.expiryTimer.unref();
|
|
75
|
+
this.flow = flow;
|
|
76
|
+
const login = () => this.run(flow, [...this.spec.loginArgs], env, home, (loginCode, loginOutput) => {
|
|
77
|
+
if (loginCode !== 0 || !flow.ready) {
|
|
78
|
+
this.finish(flow, "failed", this.spec.failure(loginOutput));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// A successful command is not enough: confirm the sign-in the way
|
|
82
|
+
// this client's bots will find it, with the same executable and
|
|
83
|
+
// environment.
|
|
84
|
+
flow.startupTimer = setTimeout(() => this.finish(flow, "failed", `${product} finished sign-in but could not confirm the account. Refresh Settings and try again.`), this.options.startupTimeoutMs ?? 30_000);
|
|
85
|
+
flow.startupTimer.unref();
|
|
86
|
+
this.confirm(flow, env, home, (signedIn) => this.finish(flow, signedIn ? "succeeded" : "failed", `${product} finished sign-in but did not confirm a ${account} account. Refresh Settings and try again.`));
|
|
87
|
+
}, true);
|
|
88
|
+
// Never overwrite a working login just because Connect was clicked twice.
|
|
89
|
+
if (!status) {
|
|
90
|
+
login();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.run(flow, [...status.args], env, home, (code, output) => {
|
|
94
|
+
const state = status.read(code, output);
|
|
95
|
+
if (state === "signed-in")
|
|
96
|
+
this.finish(flow, "succeeded");
|
|
97
|
+
else if (state === "other")
|
|
98
|
+
this.finish(flow, "failed", `${product} already has a different sign-in method on this server. Ask the server administrator to review it before changing accounts.`);
|
|
99
|
+
else if (state === "signed-out")
|
|
100
|
+
login();
|
|
101
|
+
else
|
|
102
|
+
this.finish(flow, "failed", `${product} could not confirm the existing sign-in on this server. Update ${product} and check its login status before trying again.`);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async get(flowId) {
|
|
107
|
+
if (!flowId || this.flow?.status.flowId !== flowId)
|
|
108
|
+
throw new Error("This sign-in is no longer available. Start sign-in again.");
|
|
109
|
+
return { ...this.flow.status };
|
|
110
|
+
}
|
|
111
|
+
async cancel() {
|
|
112
|
+
if (this.flow?.status.phase === "waiting")
|
|
113
|
+
this.finish(this.flow, "cancelled", `${this.spec.account} sign-in cancelled.`);
|
|
114
|
+
await this.flow?.stopping;
|
|
115
|
+
if (this.flow?.terminationFailed)
|
|
116
|
+
throw new Error(this.flow.status.message);
|
|
117
|
+
if (this.command && !await this.stopChild(this.command)) {
|
|
118
|
+
throw new Error(`${this.spec.product} could not be stopped on this server. Ask the server administrator to stop the account command before trying again.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async dispose() {
|
|
122
|
+
this.disposed = true;
|
|
123
|
+
await this.cancel();
|
|
124
|
+
this.flow = null;
|
|
125
|
+
}
|
|
126
|
+
/** Remove the sign-in the CLI stores for this server account so a
|
|
127
|
+
* different account can connect. A sign-in in progress is never pulled
|
|
128
|
+
* away underneath the browser completing it. */
|
|
129
|
+
async signOut() {
|
|
130
|
+
const { product, account, status, logoutArgs } = this.spec;
|
|
131
|
+
if (!status || !logoutArgs)
|
|
132
|
+
throw new Error(`Signing out of ${account} is not available from here.`);
|
|
133
|
+
if (this.disposed)
|
|
134
|
+
throw new Error("This provider was removed. Refresh Settings before signing out.");
|
|
135
|
+
if (this.flow?.status.phase === "waiting")
|
|
136
|
+
throw new Error(`Finish or cancel the ${account} sign-in in progress before signing out.`);
|
|
137
|
+
await this.flow?.stopping;
|
|
138
|
+
if (this.disposed)
|
|
139
|
+
throw new Error("This provider was removed. Refresh Settings before signing out.");
|
|
140
|
+
const env = this.options.environment();
|
|
141
|
+
const { home, homeKey } = this.home(env, "signing out");
|
|
142
|
+
if (authenticatingHomes.has(homeKey)) {
|
|
143
|
+
throw new Error(`A ${account} sign-in is running for this server account. Finish or cancel it before signing out.`);
|
|
144
|
+
}
|
|
145
|
+
authenticatingHomes.add(homeKey);
|
|
146
|
+
try {
|
|
147
|
+
const before = await this.exec([...status.args], env, home);
|
|
148
|
+
const state = status.read(before.code, before.output);
|
|
149
|
+
if (state === "signed-out")
|
|
150
|
+
return;
|
|
151
|
+
if (state !== "signed-in") {
|
|
152
|
+
throw new Error(`${product} did not confirm a ${account} sign-in. Other authentication methods will not be removed; ask the server administrator to check the account.`);
|
|
153
|
+
}
|
|
154
|
+
const logout = await this.exec([...logoutArgs], env, home);
|
|
155
|
+
if (logout.code !== 0)
|
|
156
|
+
throw new Error(`${product} could not remove the sign-in on this server. Check the server's ${product} installation and try again.`);
|
|
157
|
+
// The command's own report is not enough: confirm with the same status
|
|
158
|
+
// check that decides whether bots may run on this account.
|
|
159
|
+
const after = await this.exec([...status.args], env, home);
|
|
160
|
+
if (status.read(after.code, after.output) !== "signed-out") {
|
|
161
|
+
throw new Error(`${product} still reports a sign-in on this server. Update ${product} and check its login status before trying again.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
// If the OS cannot stop a command, it may still change credentials.
|
|
166
|
+
// Keep the home reserved until that exact child actually exits.
|
|
167
|
+
const child = this.command;
|
|
168
|
+
if (child && child.exitCode === null && child.signalCode === null) {
|
|
169
|
+
child.once("close", () => authenticatingHomes.delete(homeKey));
|
|
170
|
+
}
|
|
171
|
+
else
|
|
172
|
+
authenticatingHomes.delete(homeKey);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** After a login command succeeded: the status command when the CLI has
|
|
176
|
+
* one, else the stored sign-in a turn checks for. */
|
|
177
|
+
confirm(flow, env, home, done) {
|
|
178
|
+
const status = this.spec.status;
|
|
179
|
+
if (status) {
|
|
180
|
+
this.run(flow, [...status.args], env, home, (code, output) => done(status.read(code, output) === "signed-in"));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
let signedIn = false;
|
|
184
|
+
try {
|
|
185
|
+
signedIn = this.spec.signedIn?.(env) === true;
|
|
186
|
+
}
|
|
187
|
+
catch { /* unreadable: not signed in */ }
|
|
188
|
+
done(signedIn);
|
|
189
|
+
}
|
|
190
|
+
startFailure(error) {
|
|
191
|
+
const { product, install } = this.spec;
|
|
192
|
+
return error.code === "ENOENT"
|
|
193
|
+
? `${product} is not installed on this server. ${install}`
|
|
194
|
+
: `${product} could not start on this server. Check the configured CLI path and its executable permissions.`;
|
|
195
|
+
}
|
|
196
|
+
/** One bounded, non-interactive command. Its output stays here; callers
|
|
197
|
+
* see an exit code and a status-line match, never the text. */
|
|
198
|
+
exec(args, env, cwd) {
|
|
199
|
+
const { product } = this.spec;
|
|
200
|
+
return new Promise((resolveExec, rejectExec) => {
|
|
201
|
+
if (this.disposed) {
|
|
202
|
+
rejectExec(new Error("This provider was removed. Refresh Settings before signing out."));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
let child;
|
|
206
|
+
try {
|
|
207
|
+
child = spawnCli(this.options.cli, args, { env: { ...env, NO_COLOR: "1" }, cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
rejectExec(new Error(`${product} could not start on this server. Install or update the configured ${product} CLI, then try again.`));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.command = child;
|
|
214
|
+
child.stdin.end();
|
|
215
|
+
let output = "";
|
|
216
|
+
const receive = (chunk) => {
|
|
217
|
+
if (output.length < MAX_OUTPUT)
|
|
218
|
+
output += chunk.toString("utf8").slice(0, MAX_OUTPUT - output.length);
|
|
219
|
+
};
|
|
220
|
+
child.stdout.on("data", receive);
|
|
221
|
+
child.stderr.on("data", receive);
|
|
222
|
+
// A hung CLI must not hold the credential-home lock forever.
|
|
223
|
+
const timer = setTimeout(() => {
|
|
224
|
+
void this.stopChild(child).catch(() => false).then((stopped) => {
|
|
225
|
+
if (!stopped)
|
|
226
|
+
rejectExec(new Error(`${product} could not be stopped on this server. Ask the server administrator to stop the account command before trying again.`));
|
|
227
|
+
});
|
|
228
|
+
}, this.options.startupTimeoutMs ?? 30_000);
|
|
229
|
+
timer.unref();
|
|
230
|
+
child.once("error", (error) => {
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
rejectExec(new Error(this.startFailure(error)));
|
|
233
|
+
});
|
|
234
|
+
child.once("close", (code) => {
|
|
235
|
+
clearTimeout(timer);
|
|
236
|
+
if (this.command === child)
|
|
237
|
+
this.command = null;
|
|
238
|
+
resolveExec({ code, output: stripVTControlCharacters(output) });
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
run(flow, args, env, cwd, done, parsePrompt = false) {
|
|
243
|
+
const { product, provider } = this.spec;
|
|
244
|
+
if (flow.status.phase !== "waiting")
|
|
245
|
+
return;
|
|
246
|
+
let child;
|
|
247
|
+
try {
|
|
248
|
+
child = spawnCli(this.options.cli, args, { env: { ...env, NO_COLOR: "1" }, cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
this.finish(flow, "failed", `${product} could not start on this server. Install or update the configured ${product} CLI, then try again.`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
flow.child = child;
|
|
255
|
+
child.stdin.end();
|
|
256
|
+
let output = "";
|
|
257
|
+
const receive = (chunk) => {
|
|
258
|
+
if (flow.status.phase !== "waiting")
|
|
259
|
+
return;
|
|
260
|
+
if (output.length + chunk.length > MAX_OUTPUT) {
|
|
261
|
+
this.finish(flow, "failed", `${product} returned an unexpected sign-in response. Update the server's ${product} CLI and try again.`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
output += chunk.toString("utf8");
|
|
265
|
+
const prompt = parsePrompt && !flow.ready ? deviceSignInPrompt(provider, stripVTControlCharacters(output)) : null;
|
|
266
|
+
if (prompt) {
|
|
267
|
+
flow.status = { ...flow.status, ...prompt };
|
|
268
|
+
flow.ready = true;
|
|
269
|
+
clearTimeout(flow.startupTimer);
|
|
270
|
+
flow.resolve({ ...flow.status, phase: "waiting" });
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
child.stdout.on("data", receive);
|
|
274
|
+
child.stderr.on("data", receive);
|
|
275
|
+
child.once("error", (error) => this.finish(flow, "failed", this.startFailure(error)));
|
|
276
|
+
child.once("close", (code) => {
|
|
277
|
+
if (flow.child === child)
|
|
278
|
+
flow.child = null;
|
|
279
|
+
if (flow.status.phase === "waiting")
|
|
280
|
+
done(code, stripVTControlCharacters(output));
|
|
281
|
+
output = "";
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
finish(flow, phase, message) {
|
|
285
|
+
const { product, account } = this.spec;
|
|
286
|
+
if (flow.status.phase !== "waiting")
|
|
287
|
+
return;
|
|
288
|
+
clearTimeout(flow.startupTimer);
|
|
289
|
+
clearTimeout(flow.expiryTimer);
|
|
290
|
+
flow.status = {
|
|
291
|
+
phase, flowId: flow.status.flowId, authorizationUrl: null, expiresAt: null,
|
|
292
|
+
...(phase !== "succeeded" && message ? { message } : {}),
|
|
293
|
+
};
|
|
294
|
+
if (!flow.ready) {
|
|
295
|
+
if (phase === "succeeded")
|
|
296
|
+
flow.resolve({ ...flow.status, phase });
|
|
297
|
+
else
|
|
298
|
+
flow.reject(new Error(message ?? `${account} sign-in did not finish.`));
|
|
299
|
+
}
|
|
300
|
+
flow.stopping = this.stopChild(flow.child).catch(() => false).then((stopped) => {
|
|
301
|
+
const release = () => authenticatingHomes.delete(flow.homeKey);
|
|
302
|
+
if (stopped) {
|
|
303
|
+
release();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
flow.terminationFailed = true;
|
|
307
|
+
flow.status = { ...flow.status, phase: "failed", message: `${product} could not be stopped on this server. Ask the server administrator to stop the login process before trying again.` };
|
|
308
|
+
// Retain the credential-home lock until the OS actually reports exit.
|
|
309
|
+
const child = flow.child;
|
|
310
|
+
if (!child || child.exitCode !== null || child.signalCode !== null)
|
|
311
|
+
release();
|
|
312
|
+
else
|
|
313
|
+
child.once("close", release);
|
|
314
|
+
});
|
|
315
|
+
if (phase === "succeeded")
|
|
316
|
+
void this.options.onAuthenticated?.().catch(() => { });
|
|
317
|
+
}
|
|
318
|
+
async stopChild(child) {
|
|
319
|
+
if (!child || await killCliTree(child, this.options.terminateTimeoutMs ?? 1500))
|
|
320
|
+
return true;
|
|
321
|
+
// A broken CLI may ignore SIGTERM. Do not leave a stale device flow able
|
|
322
|
+
// to write credentials after the user cancelled it.
|
|
323
|
+
if (process.platform !== "win32" && child.pid) {
|
|
324
|
+
try {
|
|
325
|
+
process.kill(-child.pid, "SIGKILL");
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
child.kill("SIGKILL");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else
|
|
332
|
+
child.kill("SIGKILL");
|
|
333
|
+
await new Promise((resolveStop) => {
|
|
334
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
335
|
+
return resolveStop();
|
|
336
|
+
const timer = setTimeout(resolveStop, 1500);
|
|
337
|
+
timer.unref();
|
|
338
|
+
child.once("close", () => { clearTimeout(timer); resolveStop(); });
|
|
339
|
+
});
|
|
340
|
+
return child.exitCode !== null || child.signalCode !== null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -11,6 +11,7 @@ import { ChatProtocolError, ChatReasoningDetails, ChatToolCalls, object } from "
|
|
|
11
11
|
import { appendNative } from "./native.js";
|
|
12
12
|
import { classifyError, computeBackoff, interruptibleDelay, RETRY_MAX_ATTEMPTS } from "./retry.js";
|
|
13
13
|
import { classifyContinuable, writeTurnHandoff } from "../turn-continuation.js";
|
|
14
|
+
import { KEY_REJECTED_REASON, keyRejected, noteKeyAccepted, noteKeyRejected, rejectsKey } from "../key-rejections.js";
|
|
14
15
|
/** The message of a JSON error body a provider returned with HTTP 200.
|
|
15
16
|
* MiniMax reports auth, balance, and parameter failures as `base_resp`. */
|
|
16
17
|
function providerError(json) {
|
|
@@ -182,6 +183,8 @@ export function createOpenAIChatRuntime(options) {
|
|
|
182
183
|
});
|
|
183
184
|
if (!response.ok) {
|
|
184
185
|
const body = await response.text().catch(() => "");
|
|
186
|
+
if (rejectsKey(response.status, body))
|
|
187
|
+
noteKeyRejected(options.apiUrl, options.apiKey);
|
|
185
188
|
const message = `${options.httpErrorLabel} HTTP ${response.status}${body ? `: ${body.slice(0, 200)}` : ""}`;
|
|
186
189
|
if (rejectsToolsParameter(response.status, body))
|
|
187
190
|
throw new UnsupportedChatToolsError(message);
|
|
@@ -193,6 +196,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
193
196
|
}
|
|
194
197
|
throw new Error(message);
|
|
195
198
|
}
|
|
199
|
+
noteKeyAccepted(options.apiUrl, options.apiKey);
|
|
196
200
|
if (!stream || response.headers.get("content-type")?.includes("application/json")) {
|
|
197
201
|
const json = await response.json();
|
|
198
202
|
if (onUsage) {
|
|
@@ -462,7 +466,7 @@ export function createOpenAIChatRuntime(options) {
|
|
|
462
466
|
if (!parsed.ok)
|
|
463
467
|
throw new Error(parsed.error);
|
|
464
468
|
const scope = parsed.scope;
|
|
465
|
-
tools = await mountChatTools(options.tools === false ? undefined : turn.integrations, abort.signal, options.computerUse, scope);
|
|
469
|
+
tools = await mountChatTools(options.tools === false ? undefined : turn.integrations, abort.signal, options.computerUse, scope, turn.mcpCallTimeoutMs);
|
|
466
470
|
const questionAllowed = options.tools !== false && allowsTool(scope, { kind: "native", name: ASK_USER_TOOL });
|
|
467
471
|
let optionalQuestionOnly = questionAllowed && tools.definitions.length === 0;
|
|
468
472
|
// The runtime's one built-in tool rides the same list: ask_user is
|
|
@@ -791,9 +795,13 @@ export function createOpenAIChatRuntime(options) {
|
|
|
791
795
|
return options.models();
|
|
792
796
|
},
|
|
793
797
|
...(options.refreshModels ? { refreshModels: options.refreshModels } : {}),
|
|
794
|
-
snapshot: async () =>
|
|
795
|
-
|
|
796
|
-
|
|
798
|
+
snapshot: async () => {
|
|
799
|
+
if (!options.apiKey)
|
|
800
|
+
return { state: "unavailable", reason: options.unavailableReason };
|
|
801
|
+
if (keyRejected(options.apiUrl, options.apiKey))
|
|
802
|
+
return { state: "available", authenticated: false, reason: KEY_REJECTED_REASON, version: null };
|
|
803
|
+
return { state: "available", authenticated: true, version: null, ...(options.billing ? { billing: options.billing } : {}) };
|
|
804
|
+
},
|
|
797
805
|
adapter: {
|
|
798
806
|
provider: options.driverKind,
|
|
799
807
|
capabilities: { ...(options.computerUse ? { computerMcp: options.tools !== false,
|
|
@@ -9,15 +9,28 @@ import { dirname } from "node:path";
|
|
|
9
9
|
import { defaultFleetDeps, executePlan, loadRegistry, markProvisioningFailed, planFleetAction } from "./fleet-cli.js";
|
|
10
10
|
import { assertSlug, fleetLayout, fleetUser, openRouterModelIds, workspaceDataDir } from "./fleet.js";
|
|
11
11
|
const MAX_BODY = 256 * 1024;
|
|
12
|
-
function readBody(req) {
|
|
12
|
+
export function readBody(req) {
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
14
|
-
|
|
14
|
+
const chunks = [];
|
|
15
|
+
let bytes = 0;
|
|
16
|
+
let tooLarge = false;
|
|
15
17
|
req.on("data", (chunk) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
if (tooLarge)
|
|
19
|
+
return;
|
|
20
|
+
bytes += chunk.length;
|
|
21
|
+
if (bytes > MAX_BODY) {
|
|
22
|
+
// Keep draining so the 413 can still be sent, but stop holding bytes.
|
|
23
|
+
tooLarge = true;
|
|
24
|
+
chunks.length = 0;
|
|
25
|
+
return reject(Object.assign(new Error("request too large"), { status: 413 }));
|
|
26
|
+
}
|
|
27
|
+
chunks.push(chunk);
|
|
19
28
|
});
|
|
20
29
|
req.on("end", () => {
|
|
30
|
+
if (tooLarge)
|
|
31
|
+
return;
|
|
32
|
+
// Decode once: a character split across two chunks stays whole.
|
|
33
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
21
34
|
if (!text.trim())
|
|
22
35
|
return resolve({});
|
|
23
36
|
try {
|
|
@@ -20,7 +20,7 @@ export function json(res, status, body) {
|
|
|
20
20
|
}
|
|
21
21
|
export function readBody(req, limit = 1_000_000) {
|
|
22
22
|
return new Promise((resolve, reject) => {
|
|
23
|
-
|
|
23
|
+
const chunks = [];
|
|
24
24
|
let bytes = 0;
|
|
25
25
|
let done = false;
|
|
26
26
|
const fail = (status, msg) => {
|
|
@@ -40,11 +40,14 @@ export function readBody(req, limit = 1_000_000) {
|
|
|
40
40
|
// receiving the useful 413 response.
|
|
41
41
|
return fail(413, "body too large");
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
chunks.push(typeof c === "string" ? Buffer.from(c) : c);
|
|
44
44
|
});
|
|
45
45
|
req.on("end", () => {
|
|
46
46
|
if (done)
|
|
47
47
|
return;
|
|
48
|
+
// Decode once, after the last chunk: a character split across two
|
|
49
|
+
// chunks turns into U+FFFD when each chunk is decoded on its own.
|
|
50
|
+
const data = Buffer.concat(chunks).toString("utf8");
|
|
48
51
|
let body;
|
|
49
52
|
try {
|
|
50
53
|
body = data ? JSON.parse(data) : {};
|
|
@@ -35,6 +35,9 @@ export class ProviderRegistry {
|
|
|
35
35
|
/** decoded per-instance `cli` overrides, for describe() — drivers spawn
|
|
36
36
|
* from their own config; this map only reports what was configured */
|
|
37
37
|
cliByInstance = new Map();
|
|
38
|
+
/** What describe() last read of each live instance. A turn's start checks
|
|
39
|
+
* a thread's own engine against it without waiting on that engine's CLI. */
|
|
40
|
+
lastSnapshots = new Map();
|
|
38
41
|
driversByKind;
|
|
39
42
|
/** Where Settings-driven npm installs go; the data directory by default. */
|
|
40
43
|
enginesBaseDir;
|
|
@@ -100,6 +103,16 @@ export class ProviderRegistry {
|
|
|
100
103
|
get(instanceId) {
|
|
101
104
|
return this.byId.get(instanceId)?.live ?? null;
|
|
102
105
|
}
|
|
106
|
+
/** The snapshot describe() last read of this live instance; absent before
|
|
107
|
+
* the first read, or for an instance that is not live. */
|
|
108
|
+
lastSnapshot(instanceId) {
|
|
109
|
+
return this.byId.get(instanceId)?.live ? this.lastSnapshots.get(instanceId) : undefined;
|
|
110
|
+
}
|
|
111
|
+
/** How the instance's driver is presented and paid for (EngineAccess). */
|
|
112
|
+
access(instanceId) {
|
|
113
|
+
const entry = this.byId.get(instanceId);
|
|
114
|
+
return entry ? this.driversByKind.get(entry.shadow?.driverKind ?? entry.live.driverKind)?.metadata.access ?? "subscription" : undefined;
|
|
115
|
+
}
|
|
103
116
|
/** The configured executable for instance-scoped maintenance actions.
|
|
104
117
|
* This deliberately comes from the registry/config, never an HTTP body. */
|
|
105
118
|
cliTarget(instanceId) {
|
|
@@ -211,6 +224,10 @@ export class ProviderRegistry {
|
|
|
211
224
|
catch (e) {
|
|
212
225
|
snapshot = { state: "unavailable", reason: e instanceof Error ? e.message : String(e) };
|
|
213
226
|
}
|
|
227
|
+
// Only while this is still the live instance: a reload may have
|
|
228
|
+
// replaced it during the read.
|
|
229
|
+
if (this.byId.get(inst.instanceId)?.live === inst)
|
|
230
|
+
this.lastSnapshots.set(inst.instanceId, snapshot);
|
|
214
231
|
return {
|
|
215
232
|
instanceId: inst.instanceId,
|
|
216
233
|
driverKind: inst.driverKind,
|
|
@@ -256,11 +273,13 @@ export class ProviderRegistry {
|
|
|
256
273
|
await Promise.allSettled(this.instances().map((i) => i.dispose()));
|
|
257
274
|
this.byId.clear();
|
|
258
275
|
this.cliByInstance.clear();
|
|
276
|
+
this.lastSnapshots.clear();
|
|
259
277
|
}
|
|
260
278
|
async dispose(instanceId) {
|
|
261
279
|
const entry = this.byId.get(instanceId);
|
|
262
280
|
this.byId.delete(instanceId);
|
|
263
281
|
this.cliByInstance.delete(instanceId);
|
|
282
|
+
this.lastSnapshots.delete(instanceId);
|
|
264
283
|
await entry?.live?.dispose();
|
|
265
284
|
}
|
|
266
285
|
}
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
// never drifts from what a turn is actually shown.
|
|
10
10
|
import { APPROVAL_MODES, supportsApprovalMode as driverKindSupportsApprovalMode } from "../shared/approval-mode.js";
|
|
11
11
|
import { availableTools } from "./drivers/agents-catalog.js";
|
|
12
|
-
/** The widest turn the harness can build: every optional integration on
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
/** The widest turn the harness can build: every optional integration on, a
|
|
13
|
+
* Chief of Staff (the only role shown the Chief-only tools) and a
|
|
14
|
+
* coordinating (room) turn, where the room-only coordination tools exist.
|
|
15
|
+
* So the count is an upper bound, not one bot's catalog. Both sides of a
|
|
16
|
+
* comparison get the same profile; only the driver's own capability can
|
|
17
|
+
* differ. */
|
|
16
18
|
const ENVELOPE = {
|
|
17
19
|
externalRuntime: false,
|
|
18
20
|
coordinating: true,
|
|
@@ -22,6 +24,7 @@ const ENVELOPE = {
|
|
|
22
24
|
voiceNotes: true,
|
|
23
25
|
// Not a tool it adds: a Cloud home only hides its Local VM shell.
|
|
24
26
|
cloudHome: false,
|
|
27
|
+
chief: true,
|
|
25
28
|
botId: "",
|
|
26
29
|
};
|
|
27
30
|
/** Extra card lines for an engine switch, empty when nothing changes. */
|
|
@@ -138,12 +138,14 @@ export function hostedModelPolicy(dataDirectory, env = process.env) {
|
|
|
138
138
|
const previousDefault = bot.modelSelection;
|
|
139
139
|
const nextDefault = select(previousDefault);
|
|
140
140
|
for (const task of store.tasks(bot.id)) {
|
|
141
|
+
// A thread without a model of its own follows the bot onto its
|
|
142
|
+
// assigned model; a thread's own model is moved onto one itself.
|
|
141
143
|
const current = task.modelSelection ?? previousDefault;
|
|
142
144
|
const next = select(current);
|
|
143
145
|
const changed = JSON.stringify(next) !== JSON.stringify(current);
|
|
144
146
|
if (changed || changedRoute)
|
|
145
147
|
store.patchTask(bot.id, task.threadId, {
|
|
146
|
-
modelSelection: next, resumeCursors: {}, lastInstanceId: undefined, rewound: true,
|
|
148
|
+
...(task.modelSelection ? { modelSelection: next } : {}), resumeCursors: {}, lastInstanceId: undefined, rewound: true,
|
|
147
149
|
lastContextModel: undefined, appliedCompactionId: undefined,
|
|
148
150
|
});
|
|
149
151
|
}
|