openmausbot 0.1.60 → 0.1.62

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 (31) hide show
  1. package/README.md +71 -8
  2. package/dist/assets/index-BSI5e_fY.js +264 -0
  3. package/dist/assets/{index-BIzf_lw1.js → index-DTeYtYoC.js} +1 -1
  4. package/dist/index.html +1 -1
  5. package/dist-server/container-mcp.js +16 -1
  6. package/dist-server/drivers/agents-proxy.js +8 -4
  7. package/dist-server/index.js +463 -134
  8. package/dist-server/openmausbot.js +33585 -14506
  9. package/dist-server/pair-cli.js +33585 -14506
  10. package/dist-server/server/browser-bundle-release.js +56 -0
  11. package/dist-server/server/browser-engine-release.js +13 -5
  12. package/dist-server/server/browser-engine.js +114 -15
  13. package/dist-server/server/cli-api-setup.js +144 -0
  14. package/dist-server/server/cli-phone-setup.js +130 -0
  15. package/dist-server/server/cli-prompts.js +185 -0
  16. package/dist-server/server/cli-setup.js +303 -0
  17. package/dist-server/server/cli.js +375 -70
  18. package/dist-server/server/config.js +25 -1
  19. package/dist-server/server/default-model-selection.js +21 -0
  20. package/dist-server/server/drivers/agents-proxy.js +13 -4
  21. package/dist-server/server/drivers/claude.js +26 -1
  22. package/dist-server/server/drivers/openai-compat.js +4 -2
  23. package/dist-server/server/index.js +117 -80
  24. package/dist-server/server/recall-disclosure.js +45 -0
  25. package/dist-server/server/request-auth.js +6 -0
  26. package/dist-server/server/screen-frame-gate.js +36 -1
  27. package/dist-server/server/screen-frame-source.js +57 -0
  28. package/dist-server/server/sessions.js +52 -4
  29. package/dist-server/vps-container-mcp.js +16 -1
  30. package/package.json +1 -1
  31. package/dist/assets/index-Tha1umgl.js +0 -264
@@ -0,0 +1,57 @@
1
+ import { captureOutsideHumanControl } from "./private-screen-capture.js";
2
+ /** One serialized preview source. A cached or in-flight desktop frame must
3
+ * never become the browser's settled picture after the bot switches surfaces. */
4
+ export function createScreenFrameSource(input) {
5
+ let current = null;
6
+ let lastAt = 0;
7
+ let attemptedSurface;
8
+ let last = null;
9
+ let lastSurface;
10
+ const selectedSurface = () => input.captures[source.surface]
11
+ ? source.surface
12
+ : input.captures.computer ? "computer" : "browser";
13
+ const source = {
14
+ surface: (input.captures.computer ? "computer" : "browser"),
15
+ get last() {
16
+ return lastSurface === selectedSurface() ? last : null;
17
+ },
18
+ async capture(fresh = false) {
19
+ // Wait for the previous source before selecting the next one. A final
20
+ // capture bypasses the preview throttle, even if a poke just finished.
21
+ while (current)
22
+ await current;
23
+ if (input.control().held)
24
+ return;
25
+ const surface = selectedSurface();
26
+ if (!fresh && attemptedSurface === surface && Date.now() - lastAt < input.minGapMs)
27
+ return;
28
+ attemptedSurface = surface;
29
+ current = (async () => {
30
+ try {
31
+ const capture = input.captures[surface];
32
+ if (!capture)
33
+ return;
34
+ const frame = await captureOutsideHumanControl(input.control, capture);
35
+ if (!frame || surface !== selectedSurface())
36
+ return;
37
+ last = frame;
38
+ lastSurface = surface;
39
+ input.onFrame(frame);
40
+ }
41
+ catch {
42
+ // A sleeping or mid-command surface is retried by the next tick.
43
+ }
44
+ finally {
45
+ lastAt = Date.now();
46
+ }
47
+ })();
48
+ try {
49
+ await current;
50
+ }
51
+ finally {
52
+ current = null;
53
+ }
54
+ },
55
+ };
56
+ return source;
57
+ }
@@ -4,8 +4,8 @@
4
4
  //
5
5
  // A pairing code is 12 characters from a 32-symbol alphabet with no 0/O/1/I
6
6
  // (60 bits), single use, five minutes. Exchanging it yields an opaque
7
- // session token (`omb_sess_…`, 256 bits) that lives 30 days; only its sha256
8
- // is stored. A stream ticket is a 5-minute single-use credential for the SSE
7
+ // session token (`omb_sess_…`, 256 bits) that lives 30 days, renewed on use
8
+ // up to 180 days from pairing (`renew`); only its sha256 is stored. A stream ticket is a 5-minute single-use credential for the SSE
9
9
  // endpoint, because EventSource cannot set headers. Failed exchanges are
10
10
  // counted per source: five in a minute lock that source out for ten.
11
11
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -16,7 +16,31 @@ export const SCOPES = ["admin", "client"];
16
16
  export const PAIRING_CODE_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
17
17
  export const PAIRING_CODE_LENGTH = 12;
18
18
  export const PAIRING_CODE_TTL_MS = 5 * 60_000;
19
- export const SESSION_TTL_MS = 30 * 24 * 60 * 60_000;
19
+ /** A whole number of days from an environment variable, as milliseconds.
20
+ * Anything that is not a whole number of days between 1 and 3650 falls back
21
+ * to the default rather than being clamped, so a typo is not silently
22
+ * turned into a policy. */
23
+ export function daysMs(value, fallbackDays) {
24
+ const days = Number(value);
25
+ const ok = Number.isInteger(days) && days >= 1 && days <= 3650;
26
+ return (ok ? days : fallbackDays) * 24 * 60 * 60_000;
27
+ }
28
+ /** How long a session lives after its last renewal. A session used with half
29
+ * the term or less left is renewed for the full term again (see `renew`), so
30
+ * a device in regular use keeps working; one that goes quiet lapses.
31
+ * OMB_SESSION_TTL_DAYS overrides the 30-day default. */
32
+ export const SESSION_TTL_MS = daysMs(process.env.OMB_SESSION_TTL_DAYS, 30);
33
+ /** The most a session may live from the day it was paired, however often it
34
+ * is used. Renewal never pushes a session past this, so a stolen cookie has a
35
+ * bounded life and every device re-pairs occasionally. OMB_SESSION_MAX_DAYS
36
+ * overrides the 180-day default. */
37
+ export const SESSION_MAX_AGE_MS = daysMs(process.env.OMB_SESSION_MAX_DAYS, 180);
38
+ /** Renewal is due once half the term or less is left. */
39
+ export const SESSION_RENEW_WHEN_LEFT_MS = SESSION_TTL_MS / 2;
40
+ /** Max-Age for a cookie that should die with its session: whole seconds, at least one. */
41
+ export function cookieMaxAgeSeconds(session, now = Date.now()) {
42
+ return Math.max(1, Math.floor((session.expiresAt - now) / 1000));
43
+ }
20
44
  export const STREAM_TICKET_TTL_MS = 5 * 60_000;
21
45
  /** Per-source slow-down only. A 60-bit code cannot be guessed online in
22
46
  * five minutes whatever the rate, so the lock exists to make noise visible,
@@ -248,7 +272,9 @@ export class SessionRegistry {
248
272
  scopes: [...pairing.scopes],
249
273
  createdAt: now,
250
274
  lastSeenAt: now,
251
- expiresAt: now + SESSION_TTL_MS,
275
+ // The absolute cap applies from the first term, so a TTL configured
276
+ // longer than the cap does not hand out a session the cap forbids.
277
+ expiresAt: now + Math.min(SESSION_TTL_MS, SESSION_MAX_AGE_MS),
252
278
  };
253
279
  this.sessions.push(record);
254
280
  this.lastSeenWrites.set(record.id, now); // the exchange itself was the first sighting
@@ -275,6 +301,28 @@ export class SessionRegistry {
275
301
  }
276
302
  return record;
277
303
  }
304
+ /** Sliding expiry. Called by the request gate once a request has passed
305
+ * the origin and scope checks (so a rejected request never extends
306
+ * anything): a still-valid session with half its term or less left is
307
+ * renewed for the full term, capped at SESSION_MAX_AGE_MS from pairing.
308
+ * Nothing expired is revived. Writes at most once per half-term, so it
309
+ * adds nothing to the last-seen traffic. Returns whether it renewed. */
310
+ renew(sessionId) {
311
+ const record = this.sessions.find((s) => s.id === sessionId);
312
+ const now = this.now();
313
+ if (!record || record.expiresAt <= now)
314
+ return false;
315
+ if (record.expiresAt - now > SESSION_RENEW_WHEN_LEFT_MS)
316
+ return false;
317
+ const next = Math.min(now + SESSION_TTL_MS, record.createdAt + SESSION_MAX_AGE_MS);
318
+ if (next <= record.expiresAt)
319
+ return false; // already at the absolute cap
320
+ record.expiresAt = next;
321
+ record.lastSeenAt = now;
322
+ this.lastSeenWrites.set(record.id, now);
323
+ this.persist();
324
+ return true;
325
+ }
278
326
  /** Still valid right now (prunes expiry first). */
279
327
  isLive(sessionId) {
280
328
  this.prune();
@@ -14914,6 +14914,9 @@ function date4(params) {
14914
14914
  // node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
14915
14915
  config(en_default());
14916
14916
 
14917
+ // server/contracts.ts
14918
+ var EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
14919
+
14917
14920
  // server/mcp-registry.ts
14918
14921
  var MAX_ARGS = 64;
14919
14922
  var storedEntrySchema = external_exports.object({
@@ -15077,7 +15080,19 @@ var instanceConfigSchema = external_exports.object({
15077
15080
  config: external_exports.json().optional()
15078
15081
  });
15079
15082
  var instanceConfigMapSchema = external_exports.record(external_exports.string(), instanceConfigSchema);
15083
+ var defaultModelSelectionSchema = external_exports.object({
15084
+ instanceId: external_exports.string().trim().min(1),
15085
+ model: external_exports.string().trim().min(1),
15086
+ effort: external_exports.enum(EFFORT_LEVELS).optional()
15087
+ });
15080
15088
  var appConfigSchema = external_exports.object({
15089
+ defaultModelSelection: defaultModelSelectionSchema.optional(),
15090
+ /** CLI-only launch preferences. Never enable remote access implicitly. */
15091
+ cliStartup: external_exports.object({
15092
+ access: external_exports.enum(["local", "tunnel", "tailscale", "public-url"]),
15093
+ publicUrl: external_exports.string().url().optional(),
15094
+ phone: external_exports.enum(["ios", "android"]).optional()
15095
+ }).optional(),
15081
15096
  xai: external_exports.object({ key: optionalText, url: optionalText }).optional(),
15082
15097
  /** `model` seeds the default selection; `provider` pins an OpenRouter
15083
15098
  * upstream (e.g. "fireworks"). Both are non-secret and optional. */
@@ -15114,7 +15129,7 @@ var appConfigSchema = external_exports.object({
15114
15129
  var storedAppConfigSchema = appConfigSchema.extend({
15115
15130
  browserProfiles: storedBrowserProfilesSchema.optional()
15116
15131
  });
15117
- var appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true });
15132
+ var appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true });
15118
15133
  var jsonObjectSchema = external_exports.record(external_exports.string(), external_exports.json());
15119
15134
  var DATA_DIR = process.env.OMB_DATA_DIR ?? join2(homedir2(), ".openmausbot");
15120
15135
  var LEGACY_DATA_DIR = join2(homedir2(), ".opengrokbot");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmausbot",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
4
4
  "description": "Run the OpenMausBot server anywhere and pair your devices to it",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",