openmausbot 0.1.66 → 0.1.68

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.
Files changed (53) hide show
  1. package/dist/assets/index-CbaejjQA.js +278 -0
  2. package/dist/assets/{index-kW5L1QeO.js → index-HqooKFTS.js} +1 -1
  3. package/dist/assets/index-aJ5EWZOQ.css +1 -0
  4. package/dist/index.html +2 -2
  5. package/dist-server/computer-proxy.js +14 -3
  6. package/dist-server/container-mcp.js +44 -19
  7. package/dist-server/drivers/agents-proxy.js +59 -3
  8. package/dist-server/enterprise/server/index.js +14587 -0
  9. package/dist-server/index.js +2675 -1055
  10. package/dist-server/local-computer-proxy.js +433 -0
  11. package/dist-server/local-computer.js +48 -5
  12. package/dist-server/mcp-server.js +38 -17
  13. package/dist-server/openmausbot.js +1560 -745
  14. package/dist-server/pair-cli.js +1560 -745
  15. package/dist-server/proxy-paths.js +1 -0
  16. package/dist-server/server/account-signin.js +103 -0
  17. package/dist-server/server/brand.js +7 -0
  18. package/dist-server/server/caddy.js +203 -0
  19. package/dist-server/server/cli.js +185 -22
  20. package/dist-server/server/computer-proxy.js +11 -2
  21. package/dist-server/server/config.js +22 -1
  22. package/dist-server/server/control-client.js +7 -1
  23. package/dist-server/server/delegations.js +56 -11
  24. package/dist-server/server/drivers/agents-proxy.js +75 -5
  25. package/dist-server/server/drivers/claude-login-auth.js +334 -0
  26. package/dist-server/server/drivers/claude.js +8 -0
  27. package/dist-server/server/environment.js +1 -0
  28. package/dist-server/server/harness/registry.js +7 -1
  29. package/dist-server/server/index.js +965 -363
  30. package/dist-server/server/local-computer-proxy.js +28 -0
  31. package/dist-server/server/local-computer.js +17 -0
  32. package/dist-server/server/mcp-bridge.js +27 -11
  33. package/dist-server/server/peer-approval.js +19 -6
  34. package/dist-server/server/proxy-paths.js +1 -0
  35. package/dist-server/server/routines.js +36 -9
  36. package/dist-server/server/service-cli.js +63 -0
  37. package/dist-server/server/service-unit.js +129 -0
  38. package/dist-server/server/sessions.js +42 -1
  39. package/dist-server/server/steer-queue.js +12 -9
  40. package/dist-server/server/store.js +260 -34
  41. package/dist-server/server/system-prompt.js +1 -0
  42. package/dist-server/server/team-backup.js +1 -0
  43. package/dist-server/server/tunnel.js +27 -1
  44. package/dist-server/server/turn-dispatch-guard.js +6 -0
  45. package/dist-server/server/turn-resources.js +50 -0
  46. package/dist-server/server/workspace.js +53 -3
  47. package/dist-server/vps-container-mcp.js +44 -19
  48. package/enterprise/LICENSE +32 -0
  49. package/enterprise/README.md +89 -0
  50. package/enterprise/server/index.js +14587 -0
  51. package/package.json +2 -1
  52. package/dist/assets/index-BftlaHAF.css +0 -1
  53. package/dist/assets/index-DuR3H-Hf.js +0 -278
@@ -25,7 +25,12 @@ function createControlClient(options) {
25
25
  const res = await fetchImpl(url2, { headers, signal: AbortSignal.timeout(2e3) });
26
26
  if (!res.ok) return UNAVAILABLE;
27
27
  const body = await res.json().catch(() => null);
28
- return { held: body?.held === true, helpOpen: body?.helpOpen === true };
28
+ if (typeof body?.held !== "boolean" || typeof body?.helpOpen !== "boolean") return UNAVAILABLE;
29
+ return {
30
+ held: body.held,
31
+ helpOpen: body.helpOpen,
32
+ ...typeof body.blockedReason === "string" && body.blockedReason.trim() ? { blockedReason: body.blockedReason } : {}
33
+ };
29
34
  } catch {
30
35
  return UNAVAILABLE;
31
36
  }
@@ -276,7 +281,7 @@ function createGateInterceptor(options) {
276
281
  options.forward(line);
277
282
  return;
278
283
  }
279
- const held = await options.isHeld().catch(() => false);
284
+ const held = await options.isHeld().catch(() => true);
280
285
  if (!held) {
281
286
  options.forward(line);
282
287
  return;
@@ -285,10 +290,11 @@ function createGateInterceptor(options) {
285
290
  JSON.stringify({
286
291
  jsonrpc: "2.0",
287
292
  id: frame.id ?? null,
288
- result: { content: [{ type: "text", text: refusalText }], isError: true }
293
+ result: { content: [{ type: "text", text: options.getRefusalReason?.() ?? refusalText }], isError: true }
289
294
  })
290
295
  );
291
296
  });
297
+ return queue;
292
298
  };
293
299
  }
294
300
  function createMcpBridgeInterceptor(options) {
@@ -296,8 +302,11 @@ function createMcpBridgeInterceptor(options) {
296
302
  isHeld: options.gate.isHeld,
297
303
  forward: options.forward,
298
304
  refuse: options.answer,
299
- refusalText: options.gate.refusalText
300
- }) : options.forward;
305
+ refusalText: options.gate.refusalText,
306
+ getRefusalReason: options.gate.getRefusalReason
307
+ }) : (line) => {
308
+ options.forward(line);
309
+ };
301
310
  return (line) => {
302
311
  let frame = null;
303
312
  try {
@@ -310,37 +319,47 @@ function createMcpBridgeInterceptor(options) {
310
319
  }
311
320
  return;
312
321
  }
313
- afterPing(line);
322
+ return afterPing(line);
314
323
  };
315
324
  }
316
325
  function runMcpBridge(options) {
317
326
  const child = spawn(options.command, options.args, {
318
327
  shell: false,
319
- env: { ...process.env, PATH: augmentedPath() },
328
+ env: options.env ?? { ...process.env, PATH: augmentedPath() },
320
329
  stdio: ["pipe", "pipe", "pipe"]
321
330
  });
322
331
  child.stdin.on("error", () => {
323
332
  });
324
333
  child.stderr.pipe(process.stderr);
325
334
  const client = options.gate ? createControlClient({ url: options.gate.url, token: options.gate.token }) : null;
335
+ let refusalReason;
326
336
  const answer = (line) => process.stdout.write(line + "\n");
327
337
  const forward = (line) => child.stdin.write(line + "\n");
328
- const inbound = createLineSplitter(
329
- createMcpBridgeInterceptor({
330
- answer,
331
- forward,
332
- ...options.gate ? {
333
- gate: {
334
- isHeld: async () => (await client.state(true)).held
335
- }
336
- } : {}
337
- })
338
- );
338
+ const intercept = createMcpBridgeInterceptor({
339
+ answer,
340
+ forward,
341
+ ...options.gate ? {
342
+ gate: {
343
+ isHeld: async () => {
344
+ refusalReason = void 0;
345
+ const state = await client.state(true);
346
+ refusalReason = state.blockedReason;
347
+ return state.held;
348
+ },
349
+ getRefusalReason: () => refusalReason
350
+ }
351
+ } : {}
352
+ });
353
+ let pendingInput = Promise.resolve();
354
+ const inbound = createLineSplitter((line) => {
355
+ const completion = intercept(line);
356
+ if (completion) pendingInput = completion;
357
+ });
339
358
  const onStdin = (chunk) => inbound.push(chunk);
340
359
  process.stdin.on("data", onStdin);
341
360
  process.stdin.on("end", () => {
342
361
  inbound.flush();
343
- child.stdin.end();
362
+ void pendingInput.finally(() => child.stdin.end());
344
363
  });
345
364
  const outbound = createLineSplitter((line) => process.stdout.write(line + "\n"));
346
365
  child.stdout.on("data", (chunk) => outbound.push(chunk));
@@ -14936,6 +14955,7 @@ var LEGACY_BROWSER_PROFILE_ID = /^[A-Za-z0-9_-]{1,40}$/;
14936
14955
  var BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
14937
14956
  var MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
14938
14957
  var MAX_ROOM_TURN_TIMEOUT_MINUTES = 1440;
14958
+ var MAX_CONCURRENT_BOT_THREADS = 10;
14939
14959
  var MIN_LOCAL_VM_MAX_INSTANCES = 1;
14940
14960
  var MAX_LOCAL_VM_MAX_INSTANCES = 4;
14941
14961
  function isValidSshAlias(value) {
@@ -15088,6 +15108,9 @@ var defaultModelSelectionSchema = external_exports.object({
15088
15108
  var appConfigSchema = external_exports.object({
15089
15109
  /** Verified by the dedicated domain endpoint, never a generic config patch. */
15090
15110
  customDomain: external_exports.string().optional(),
15111
+ /** Who may sign in with an emailed code (server/account-signin.ts):
15112
+ * addresses or `@domain` entries; admins get every scope, members chat only. */
15113
+ signIn: external_exports.object({ admins: external_exports.array(external_exports.string().max(320)).max(500).optional(), members: external_exports.array(external_exports.string().max(320)).max(5e3).optional() }).optional(),
15091
15114
  defaultModelSelection: defaultModelSelectionSchema.optional(),
15092
15115
  /** CLI-only launch preferences. Never enable remote access implicitly. */
15093
15116
  cliStartup: external_exports.object({
@@ -15118,6 +15141,7 @@ var appConfigSchema = external_exports.object({
15118
15141
  * system language. Unknown tags degrade to English in the renderer. */
15119
15142
  language: optionalText,
15120
15143
  rooms: roomConfigSchema.optional(),
15144
+ threads: external_exports.object({ maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS) }).strict().optional(),
15121
15145
  localVm: localVmConfigSchema.optional(),
15122
15146
  features: featureConfigSchema.optional(),
15123
15147
  browserProfiles: browserProfilesSchema.optional(),
@@ -15150,6 +15174,7 @@ function resolveProxy(relative) {
15150
15174
  var SPAWNED_PROXIES = {
15151
15175
  browser: resolveProxy("browser-proxy"),
15152
15176
  computer: resolveProxy("computer-proxy"),
15177
+ localComputer: resolveProxy("local-computer-proxy"),
15153
15178
  permission: resolveProxy("permission-proxy"),
15154
15179
  containerMcp: resolveProxy("container-mcp"),
15155
15180
  vpsContainerMcp: resolveProxy("vps-container-mcp"),
@@ -0,0 +1,32 @@
1
+ OpenMausBot Enterprise License
2
+
3
+ Copyright (c) 2026 Milind Soni. All rights reserved.
4
+
5
+ This directory ("enterprise") and everything in it is NOT covered by the
6
+ Apache License 2.0 that applies to the rest of this repository.
7
+
8
+ 1. Source-available. You may read, build, and evaluate this code, and you may
9
+ modify it for your own evaluation, so that you know exactly what runs.
10
+ Development, test, and evaluation use is free of charge.
11
+
12
+ 2. Use requires a license key. Running this code with enterprise features
13
+ enabled in production requires a valid, unexpired license key issued by
14
+ the copyright holder for your organization, under a separate agreement
15
+ that sets the licensed features, seats, and term.
16
+
17
+ 3. No redistribution. You may not redistribute, sublicense, sell, host for
18
+ third parties, or offer as a service this directory or any modified
19
+ version of it, and you may not remove, disable, or circumvent the license
20
+ check. Hosting for third parties, reselling, and white-labelling the
21
+ product for third parties are permitted only under a written partner
22
+ agreement with the copyright holder.
23
+
24
+ 4. Everything else is open source. Delete this directory and the remainder
25
+ of the repository builds and runs as the open-source edition under
26
+ Apache 2.0, with no obligation under this license.
27
+
28
+ 5. No warranty. This code is provided "as is", without warranty of any kind.
29
+ To the extent permitted by law, the copyright holder is not liable for
30
+ any damages arising from its use.
31
+
32
+ Questions and license keys: soni.mil2001@gmail.com
@@ -0,0 +1,89 @@
1
+ # OpenMausBot Enterprise
2
+
3
+ Source-available features for hosted and white-labelled deployments. This
4
+ folder has its own [LICENSE](./LICENSE); everything outside it is Apache 2.0.
5
+
6
+ **Delete this folder and you have the open-source edition.** Core only
7
+ reaches the layer through one hook point, `server/enterprise.ts`, which
8
+ loads `enterprise/server/index.ts` if it exists and otherwise reports the
9
+ open-source edition. No core file imports anything from here.
10
+
11
+ ## How a deployment turns enterprise
12
+
13
+ Set `OMB_LICENSE_KEY` on the server. The key is `omb1.<claims>.<signature>`:
14
+ the claims are visible JSON (who it is for, which entitlements, when it
15
+ expires), signed with an Ed25519 key whose public half is baked into
16
+ `server/license.ts`. Verification is offline, and one build serves every
17
+ customer, because the key decides the feature set rather than the code.
18
+
19
+ `GET /api/edition` reports the outcome:
20
+
21
+ ```json
22
+ { "edition": "enterprise", "customer": "Acme", "features": ["sso", "whitelabel"], "expiresAt": "2027-09-02" }
23
+ { "edition": "oss", "features": [], "notice": "OMB_LICENSE_KEY expired on 2027-09-02; renew it to keep enterprise features" }
24
+ ```
25
+
26
+ A missing, altered, or expired key never stops the server: it runs the
27
+ open-source edition and the notice says what to fix.
28
+
29
+ ## Entitlement ids
30
+
31
+ | id | grants |
32
+ |---|---|
33
+ | `whitelabel` | product name, tagline, accent colour, logo, favicon and support link from `brand.json` (below) |
34
+ | `sso` | identity-header trust behind an OIDC proxy |
35
+ | `admin` | the admin panel routes |
36
+ | `budgets` | per-bot and per-section spend limits |
37
+
38
+ Core gates a feature with `entitled("id")` from `server/enterprise.ts`.
39
+ Unknown ids are carried in the key but grant nothing, so keys can be issued
40
+ ahead of a feature landing.
41
+
42
+ ## Issuing keys
43
+
44
+ ```sh
45
+ node enterprise/scripts/issue-license.mjs keygen # once; prints the public key to add to license.ts
46
+ node enterprise/scripts/issue-license.mjs issue --customer "Acme" --features whitelabel,sso --expires 2027-09-02
47
+ ```
48
+
49
+ The signing key lives outside the repo (default
50
+ `~/.config/openmausbot-enterprise/signing-key.json`). Rotate by generating a
51
+ new pair and appending its public key: keys signed by older pairs keep
52
+ working until they expire.
53
+
54
+ ## What goes where
55
+
56
+ - Could any open-source user want it? It goes in core, as a public PR.
57
+ - Org-, admin- or tier-flavoured, or something the next enterprise lead
58
+ would be shown? It lives here, behind an entitlement.
59
+ - Customer-specific brand, skills, packages, connectors? The customer's own
60
+ repo: data and config, never a fork.
61
+
62
+ ## White-label (`whitelabel`)
63
+
64
+ Put a `brand.json` in the server's data dir (`OMB_DATA_DIR`, the `/data`
65
+ volume in Docker) or point `OMB_BRAND_FILE` at one:
66
+
67
+ ```json
68
+ {
69
+ "name": "Reliable Platform",
70
+ "tagline": "Back office, on autopilot",
71
+ "accent": "#1D4ED8",
72
+ "logo": "data:image/svg+xml;base64,…",
73
+ "favicon": "data:image/png;base64,…",
74
+ "supportUrl": "https://help.example.com"
75
+ }
76
+ ```
77
+
78
+ Only `name` is required. `logo` is an inline `data:image/…` URI or an
79
+ `https://` URL; `accent` is a 6-digit hex colour, and the text colour on it
80
+ is derived for contrast. The server reads the file on every `GET /api/brand`,
81
+ so edits show on the next reload; the app fetches it before the first paint,
82
+ so the window never flashes the default name. An unlicensed server, or a
83
+ file with a mistake, keeps the default brand and says why in `/api/brand`
84
+ and the startup log.
85
+
86
+ What `brand.json` cannot change, because it is baked at packaging time: the
87
+ desktop app's bundle and menu-bar name, installer names, the macOS
88
+ permission prompts, the iOS app's name, and the helper apps' paths. A fully
89
+ rebranded desktop build is a per-customer packaging job, not config.