openmausbot 0.1.65 → 0.1.67
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/index-A3CBTxwl.css +1 -0
- package/dist/assets/{index-BZeH50da.js → index-BwpNqgCm.js} +1 -1
- package/dist/assets/index-CDuyoVPJ.js +278 -0
- package/dist/index.html +2 -2
- package/dist-server/browser-proxy.js +121 -0
- package/dist-server/computer-proxy.js +14 -3
- package/dist-server/container-mcp.js +43 -19
- package/dist-server/drivers/agents-proxy.js +31 -0
- package/dist-server/index.js +18985 -16059
- package/dist-server/local-computer-proxy.js +433 -0
- package/dist-server/local-computer.js +48 -5
- package/dist-server/mcp-server.js +38 -17
- package/dist-server/openmausbot.js +1666 -775
- package/dist-server/pair-cli.js +1666 -775
- package/dist-server/proxy-paths.js +2 -0
- package/dist-server/server/account-signin.js +103 -0
- package/dist-server/server/attachments.js +125 -13
- package/dist-server/server/browser-engine-release.js +10 -8
- package/dist-server/server/browser-engine.js +226 -26
- package/dist-server/server/browser-live.js +567 -0
- package/dist-server/server/browser-proxy.js +129 -0
- package/dist-server/server/browser-runtime.js +370 -0
- package/dist-server/server/caddy.js +203 -0
- package/dist-server/server/claude-accounts.js +103 -0
- package/dist-server/server/cli.js +163 -22
- package/dist-server/server/computer-proxy.js +11 -2
- package/dist-server/server/config.js +41 -19
- package/dist-server/server/control-client.js +7 -1
- package/dist-server/server/custom-domain.js +14 -0
- package/dist-server/server/drivers/agents-proxy.js +34 -0
- package/dist-server/server/drivers/claude-login-auth.js +334 -0
- package/dist-server/server/drivers/claude.js +85 -19
- package/dist-server/server/environment.js +1 -0
- package/dist-server/server/harness/bus.js +9 -4
- package/dist-server/server/harness/registry.js +15 -1
- package/dist-server/server/index.js +1120 -379
- package/dist-server/server/local-computer-proxy.js +28 -0
- package/dist-server/server/local-computer.js +17 -0
- package/dist-server/server/mcp-bridge.js +27 -11
- package/dist-server/server/peer-approval.js +19 -6
- package/dist-server/server/provider-auth-sessions.js +7 -0
- package/dist-server/server/proxy-paths.js +2 -0
- package/dist-server/server/sessions.js +42 -1
- package/dist-server/server/steer-queue.js +10 -8
- package/dist-server/server/store.js +234 -34
- package/dist-server/server/team-backup.js +1 -0
- package/dist-server/server/tunnel.js +27 -1
- package/dist-server/server/turn-resources.js +50 -0
- package/dist-server/server/workspace.js +53 -3
- package/dist-server/src/lib/composer-attachments.js +3 -3
- package/dist-server/vps-container-mcp.js +43 -19
- package/package.json +1 -1
- package/dist/assets/index-BOrNJcfG.js +0 -273
- package/dist/assets/index-Dwzk199I.css +0 -1
|
@@ -12,10 +12,11 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
12
12
|
import { chmodSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { createServer as createNetServer } from "node:net";
|
|
14
14
|
import { homedir, tmpdir } from "node:os";
|
|
15
|
-
import { join, dirname } from "node:path";
|
|
15
|
+
import { join, dirname, isAbsolute, normalize } from "node:path";
|
|
16
16
|
import { DATA_DIR, stripWorkspaceCredentialEnv } from "../config.js";
|
|
17
17
|
import { augmentedPath } from "../env-path.js";
|
|
18
18
|
import { brokerSocketPath, describeSpawnFailure, execCli, killCliTree, spawnCli } from "../procs.js";
|
|
19
|
+
import { ClaudeLoginController } from "./claude-login-auth.js";
|
|
19
20
|
import { computerProxyEnv } from "../container-computer.js";
|
|
20
21
|
import { newEventId, newId } from "../contracts.js";
|
|
21
22
|
import { classifyError, computeBackoff, interruptibleDelay, RETRY_MAX_ATTEMPTS } from "./retry.js";
|
|
@@ -29,19 +30,61 @@ import { SPAWNED_PROXIES } from "../proxy-paths.js";
|
|
|
29
30
|
* backends over time. Presence checks also accept stale credentials. The CLI's
|
|
30
31
|
* own machine-readable auth command is the source of truth for every backend.
|
|
31
32
|
*/
|
|
32
|
-
|
|
33
|
+
function claudeAuthStatus(cli, env, run = execCli) {
|
|
33
34
|
return new Promise((resolve) => {
|
|
34
|
-
run(cli, ["auth", "status", "--json"], { timeout: 8000, env }, (_error, stdout) => {
|
|
35
|
+
run(cli, ["auth", "status", "--json"], { timeout: 8000, maxBuffer: 65_536, env }, (_error, stdout) => {
|
|
35
36
|
try {
|
|
36
37
|
const status = JSON.parse(stdout);
|
|
37
|
-
|
|
38
|
+
if (!status || typeof status !== "object" || !("loggedIn" in status) || status.loggedIn !== true) {
|
|
39
|
+
return resolve({ authenticated: false });
|
|
40
|
+
}
|
|
41
|
+
// Only display identity fields, never the CLI's full auth response.
|
|
42
|
+
const identity = status;
|
|
43
|
+
const boundedText = (value, max) => typeof value === "string" && value.trim().length > 0 && value.length <= max && !/[\p{Cc}\p{Cf}]/u.test(value)
|
|
44
|
+
? value.trim() : undefined;
|
|
45
|
+
const candidateEmail = boundedText(identity.email, 254);
|
|
46
|
+
const email = candidateEmail && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(candidateEmail) ? candidateEmail : undefined;
|
|
47
|
+
const organization = boundedText(identity.orgName, 160);
|
|
48
|
+
resolve({
|
|
49
|
+
authenticated: true,
|
|
50
|
+
...(email || organization ? { account: { ...(email ? { email } : {}), ...(organization ? { organization } : {}) } } : {}),
|
|
51
|
+
});
|
|
38
52
|
}
|
|
39
53
|
catch {
|
|
40
|
-
resolve(false);
|
|
54
|
+
resolve({ authenticated: false });
|
|
41
55
|
}
|
|
42
56
|
});
|
|
43
57
|
});
|
|
44
58
|
}
|
|
59
|
+
export async function claudeSignedIn(cli, env, run = execCli) {
|
|
60
|
+
return (await claudeAuthStatus(cli, env, run)).authenticated;
|
|
61
|
+
}
|
|
62
|
+
/** Parent-session credentials/routing that a named account must not inherit.
|
|
63
|
+
* Shared with terminal sign-in instructions so login and turns select alike. */
|
|
64
|
+
export const CLAUDE_ACCOUNT_ENV_KEYS = [
|
|
65
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
66
|
+
"CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
|
|
67
|
+
"CLAUDE_CODE_OAUTH_SCOPES",
|
|
68
|
+
"CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR",
|
|
69
|
+
"CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR",
|
|
70
|
+
"ANTHROPIC_API_KEY",
|
|
71
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
72
|
+
"ANTHROPIC_BASE_URL",
|
|
73
|
+
"ANTHROPIC_CUSTOM_HEADERS",
|
|
74
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
75
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
76
|
+
"CLAUDE_CODE_USE_FOUNDRY",
|
|
77
|
+
];
|
|
78
|
+
/** Resolve the CLI's config location without changing HOME or Keychain. */
|
|
79
|
+
export function resolveClaudeConfigDir(configDir, env = process.env) {
|
|
80
|
+
const home = env.HOME || env.USERPROFILE || homedir();
|
|
81
|
+
const configured = configDir?.trim() || env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
|
|
82
|
+
const expanded = configured === "~" ? home : configured.startsWith("~/") ? join(home, configured.slice(2)) : configured;
|
|
83
|
+
if (!isAbsolute(expanded) || /[\p{Cc}\p{Cf}]/u.test(expanded)) {
|
|
84
|
+
throw new Error("claude: configDir must be an absolute path or start with ~/");
|
|
85
|
+
}
|
|
86
|
+
return normalize(expanded);
|
|
87
|
+
}
|
|
45
88
|
/** Whether a stream frame is the CLI reporting that it has no login.
|
|
46
89
|
*
|
|
47
90
|
* The CLI flags its own api-error frames (`error`, `is_api_error_message`);
|
|
@@ -63,8 +106,21 @@ export function claudeAuthFailure(frame, text) {
|
|
|
63
106
|
* Keeping the probe and turn environments identical prevents setup from
|
|
64
107
|
* claiming an API-key login that the turn itself would deliberately remove.
|
|
65
108
|
*/
|
|
66
|
-
function claudeEnvironment(model, source = process.env) {
|
|
109
|
+
function claudeEnvironment(model, source = process.env, configDir, instanceEnvironment = {}) {
|
|
67
110
|
const env = { ...source, PATH: augmentedPath(), NPM_CONFIG_LOGLEVEL: "error" };
|
|
111
|
+
if (configDir?.trim()) {
|
|
112
|
+
env.CLAUDE_CONFIG_DIR = resolveClaudeConfigDir(configDir, env);
|
|
113
|
+
for (const key of CLAUDE_ACCOUNT_ENV_KEYS) {
|
|
114
|
+
// Explicit custom endpoint settings still work; subscription OAuth
|
|
115
|
+
// always belongs to this account's CLI-managed login, never its parent.
|
|
116
|
+
if (key.startsWith("CLAUDE_CODE_OAUTH_") || key.endsWith("_FILE_DESCRIPTOR") || !Object.hasOwn(instanceEnvironment, key)) {
|
|
117
|
+
delete env[key];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (env.CLAUDE_CONFIG_DIR) {
|
|
122
|
+
env.CLAUDE_CONFIG_DIR = resolveClaudeConfigDir(undefined, env);
|
|
123
|
+
}
|
|
68
124
|
delete env.CLAUDECODE;
|
|
69
125
|
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
70
126
|
// The harness process may hold workspace credentials (xai/box/voice keys,
|
|
@@ -97,11 +153,6 @@ async function resolveClaudeTurnModel(model, env) {
|
|
|
97
153
|
}
|
|
98
154
|
return resolveInjectId(model, await probeLocalInjects(env)) ?? model;
|
|
99
155
|
}
|
|
100
|
-
function claudeConfigDir(env) {
|
|
101
|
-
if (env.CLAUDE_CONFIG_DIR)
|
|
102
|
-
return env.CLAUDE_CONFIG_DIR;
|
|
103
|
-
return join(env.HOME || env.USERPROFILE || homedir(), ".claude");
|
|
104
|
-
}
|
|
105
156
|
function extrasFromUnknown(value) {
|
|
106
157
|
if (!Array.isArray(value))
|
|
107
158
|
return [];
|
|
@@ -127,7 +178,7 @@ function extrasFromUnknown(value) {
|
|
|
127
178
|
export function readClaudeModelCatalog(env = process.env) {
|
|
128
179
|
let settings = {};
|
|
129
180
|
try {
|
|
130
|
-
settings = JSON.parse(readFileSync(join(
|
|
181
|
+
settings = JSON.parse(readFileSync(join(resolveClaudeConfigDir(undefined, env), "settings.json"), "utf8"));
|
|
131
182
|
}
|
|
132
183
|
catch {
|
|
133
184
|
return STATIC_CLAUDE_MODELS;
|
|
@@ -446,8 +497,14 @@ function decodeConfig(raw) {
|
|
|
446
497
|
}
|
|
447
498
|
const tools = decodeToolList(o.tools, "tools");
|
|
448
499
|
const disallowedTools = decodeToolList(o.disallowedTools, "disallowedTools");
|
|
500
|
+
if (o.configDir !== undefined && typeof o.configDir !== "string")
|
|
501
|
+
throw new Error("claude: configDir must be a string");
|
|
502
|
+
const configDir = typeof o.configDir === "string" ? o.configDir.trim() : undefined;
|
|
503
|
+
if (configDir)
|
|
504
|
+
resolveClaudeConfigDir(configDir);
|
|
449
505
|
return {
|
|
450
506
|
cli: typeof o.cli === "string" ? o.cli : "claude",
|
|
507
|
+
...(configDir ? { configDir } : {}),
|
|
451
508
|
permissionMode: mode ?? "acceptEdits",
|
|
452
509
|
...(tools !== undefined ? { tools } : {}),
|
|
453
510
|
...(disallowedTools !== undefined ? { disallowedTools } : {}),
|
|
@@ -523,7 +580,8 @@ export const ClaudeDriver = {
|
|
|
523
580
|
defaultConfig: () => decodeConfig({}),
|
|
524
581
|
async create(input) {
|
|
525
582
|
const { instanceId, config } = input;
|
|
526
|
-
const
|
|
583
|
+
const environment = (model) => claudeEnvironment(model, { ...process.env, ...input.environment }, config.configDir, input.environment);
|
|
584
|
+
const catalogEnv = environment();
|
|
527
585
|
let models = STATIC_CLAUDE_MODELS;
|
|
528
586
|
const refreshModels = async () => {
|
|
529
587
|
try {
|
|
@@ -659,7 +717,7 @@ export const ClaudeDriver = {
|
|
|
659
717
|
if (config.disallowedTools?.length) {
|
|
660
718
|
args.push("--disallowedTools", config.disallowedTools.join(","));
|
|
661
719
|
}
|
|
662
|
-
const turnEnvironment =
|
|
720
|
+
const turnEnvironment = environment();
|
|
663
721
|
const turnModel = await resolveClaudeTurnModel(turn.model, turnEnvironment);
|
|
664
722
|
const injected = applyClaudeInject({ ...turnEnvironment }, turnModel);
|
|
665
723
|
if (injected.model)
|
|
@@ -762,7 +820,7 @@ export const ClaudeDriver = {
|
|
|
762
820
|
args.push("--mcp-config", mcpConfigPath);
|
|
763
821
|
args.push("--allowedTools", allowed.join(","));
|
|
764
822
|
}
|
|
765
|
-
const env =
|
|
823
|
+
const env = environment(turnModel);
|
|
766
824
|
// Our approvals and browser credentials expire at the user-turn
|
|
767
825
|
// boundary. Native background workers cannot outlive that boundary;
|
|
768
826
|
// parallel bot work must use the harness's durable delegate_bot path.
|
|
@@ -779,6 +837,7 @@ export const ClaudeDriver = {
|
|
|
779
837
|
cwd,
|
|
780
838
|
model: injected.model ?? null,
|
|
781
839
|
base: env.ANTHROPIC_BASE_URL ?? null,
|
|
840
|
+
configDir: env.CLAUDE_CONFIG_DIR ?? null,
|
|
782
841
|
});
|
|
783
842
|
// Reuse the live process when it is idle, unchanged, and is the session
|
|
784
843
|
// the harness wants resumed. Anything else: close it and spawn fresh
|
|
@@ -1229,18 +1288,21 @@ export const ClaudeDriver = {
|
|
|
1229
1288
|
return false;
|
|
1230
1289
|
return writeUser(s, threadId, claudeUserMessage(text, undefined));
|
|
1231
1290
|
};
|
|
1291
|
+
// Sign in from Settings: the unmodified CLI's own login, driven over pipes
|
|
1292
|
+
// (server/drivers/claude-login-auth.ts). Same environment as every turn.
|
|
1293
|
+
const login = new ClaudeLoginController({ cli: config.cli, environment, onAuthenticated: async () => { await refreshModels(); } });
|
|
1232
1294
|
const snapshot = async () => {
|
|
1233
|
-
const env =
|
|
1295
|
+
const env = environment();
|
|
1234
1296
|
const version = await new Promise((resolve) => {
|
|
1235
1297
|
execCli(config.cli, ["--version"], { timeout: 8000, env }, (err, stdout) => resolve(err ? null : stdout.trim()));
|
|
1236
1298
|
});
|
|
1237
1299
|
if (!version)
|
|
1238
1300
|
return { state: "unavailable", reason: `\`${config.cli}\` CLI not found` };
|
|
1239
|
-
const
|
|
1301
|
+
const auth = await claudeAuthStatus(config.cli, env);
|
|
1240
1302
|
// claudeEnvironment strips ANTHROPIC_API_KEY, so turns run on the
|
|
1241
1303
|
// CLI's own login (Pro/Max): the cost it reports is what the call
|
|
1242
1304
|
// WOULD bill, not a charge
|
|
1243
|
-
return { state: "available", version,
|
|
1305
|
+
return { state: "available", version, ...auth, billing: "subscription" };
|
|
1244
1306
|
};
|
|
1245
1307
|
/** One-shot Claude call with the prompt on stdin, never argv. Approval
|
|
1246
1308
|
* summaries can contain paths, commands, or secrets, so the generic
|
|
@@ -1249,7 +1311,7 @@ export const ClaudeDriver = {
|
|
|
1249
1311
|
const generateReview = (prompt, signal) => new Promise((resolve, reject) => {
|
|
1250
1312
|
const child = spawnCli(config.cli, ["-p", "--model", "claude-haiku-4-5", "--output-format", "text"], {
|
|
1251
1313
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1252
|
-
env:
|
|
1314
|
+
env: environment("claude-haiku-4-5"),
|
|
1253
1315
|
});
|
|
1254
1316
|
let stdout = "";
|
|
1255
1317
|
let stderr = "";
|
|
@@ -1310,6 +1372,10 @@ export const ClaudeDriver = {
|
|
|
1310
1372
|
},
|
|
1311
1373
|
refreshModels,
|
|
1312
1374
|
snapshot,
|
|
1375
|
+
startAuthentication: () => login.start(),
|
|
1376
|
+
getAuthentication: (flowId) => login.get(flowId),
|
|
1377
|
+
completeAuthentication: (flowId, code) => login.complete(flowId, code),
|
|
1378
|
+
cancelAuthentication: () => login.cancel(),
|
|
1313
1379
|
adapter: {
|
|
1314
1380
|
provider: DRIVER_KIND,
|
|
1315
1381
|
capabilities: {
|
|
@@ -12,7 +12,7 @@ import { newId } from "../contracts.js";
|
|
|
12
12
|
const INCOMPLETE_LOG_MESSAGE = "Canonical event history is incomplete: OpenMausBot could not write one or more events to disk. Live updates will continue.";
|
|
13
13
|
export class EventBus {
|
|
14
14
|
listeners = new Set();
|
|
15
|
-
unsubscribes =
|
|
15
|
+
unsubscribes = new Map();
|
|
16
16
|
pendingLogWarnings = new Map();
|
|
17
17
|
appendLog;
|
|
18
18
|
constructor(appendLog = appendFileSync) {
|
|
@@ -20,6 +20,7 @@ export class EventBus {
|
|
|
20
20
|
}
|
|
21
21
|
attach(instances) {
|
|
22
22
|
for (const instance of instances) {
|
|
23
|
+
this.detach(instance.instanceId);
|
|
23
24
|
const unsub = instance.adapter.onEvent((event) => {
|
|
24
25
|
// hard invariant borrowed from correlateRuntimeEventWithInstance:
|
|
25
26
|
// an adapter may only emit events for its own driver kind
|
|
@@ -29,7 +30,7 @@ export class EventBus {
|
|
|
29
30
|
}
|
|
30
31
|
this.publish({ ...event, providerInstanceId: instance.instanceId });
|
|
31
32
|
});
|
|
32
|
-
this.unsubscribes.
|
|
33
|
+
this.unsubscribes.set(instance.instanceId, unsub);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
publish(event) {
|
|
@@ -80,7 +81,11 @@ export class EventBus {
|
|
|
80
81
|
return () => this.listeners.delete(listener);
|
|
81
82
|
}
|
|
82
83
|
detachAll() {
|
|
83
|
-
for (const
|
|
84
|
-
|
|
84
|
+
for (const id of this.unsubscribes.keys())
|
|
85
|
+
this.detach(id);
|
|
86
|
+
}
|
|
87
|
+
detach(instanceId) {
|
|
88
|
+
this.unsubscribes.get(instanceId)?.();
|
|
89
|
+
this.unsubscribes.delete(instanceId);
|
|
85
90
|
}
|
|
86
91
|
}
|
|
@@ -35,6 +35,8 @@ export class ProviderRegistry {
|
|
|
35
35
|
}
|
|
36
36
|
async load(configs) {
|
|
37
37
|
for (const [instanceId, entry] of Object.entries(configs)) {
|
|
38
|
+
// Account edits replace only their own process/session state.
|
|
39
|
+
await this.dispose(instanceId);
|
|
38
40
|
const driver = this.driversByKind.get(entry.driver);
|
|
39
41
|
if (!driver) {
|
|
40
42
|
this.byId.set(instanceId, {
|
|
@@ -205,7 +207,13 @@ export class ProviderRegistry {
|
|
|
205
207
|
access: driver?.metadata.access ?? "subscription",
|
|
206
208
|
install: driver?.install,
|
|
207
209
|
authentication: inst.startAuthentication
|
|
208
|
-
? {
|
|
210
|
+
? {
|
|
211
|
+
method: inst.getAuthentication && inst.completeAuthentication
|
|
212
|
+
? "paste-code" // a link to open, then a code pasted back (Claude)
|
|
213
|
+
: inst.getAuthentication
|
|
214
|
+
? "device-code" // a code to enter at the provider's page (Codex)
|
|
215
|
+
: "browser", // a link and a callback URL (managed engines)
|
|
216
|
+
}
|
|
209
217
|
: undefined,
|
|
210
218
|
cli: this.cliByInstance.get(inst.instanceId),
|
|
211
219
|
cliDefault: cliDefaultOf(driver),
|
|
@@ -221,4 +229,10 @@ export class ProviderRegistry {
|
|
|
221
229
|
this.byId.clear();
|
|
222
230
|
this.cliByInstance.clear();
|
|
223
231
|
}
|
|
232
|
+
async dispose(instanceId) {
|
|
233
|
+
const entry = this.byId.get(instanceId);
|
|
234
|
+
this.byId.delete(instanceId);
|
|
235
|
+
this.cliByInstance.delete(instanceId);
|
|
236
|
+
await entry?.live?.dispose();
|
|
237
|
+
}
|
|
224
238
|
}
|