openmausbot 0.1.83 → 0.1.85

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 (76) hide show
  1. package/dist/assets/index-Ba9G44HI.js +310 -0
  2. package/dist/assets/{index-nqTFZTxg.js → index-CTNNoqSJ.js} +1 -1
  3. package/dist/assets/index-CkRNp7wX.css +1 -0
  4. package/dist/index.html +2 -2
  5. package/dist-server/companion/src/routes.js +1 -0
  6. package/dist-server/container-mcp.js +212 -7
  7. package/dist-server/drivers/agents-proxy.js +161 -12
  8. package/dist-server/enterprise/server/index.js +1 -0
  9. package/dist-server/hooks/omb-hook.js +74 -0
  10. package/dist-server/index.js +8025 -5661
  11. package/dist-server/local-computer-proxy.js +83 -1
  12. package/dist-server/local-computer.js +14 -2
  13. package/dist-server/openmausbot.js +796 -309
  14. package/dist-server/pair-cli.js +796 -309
  15. package/dist-server/proxy-paths.js +1 -0
  16. package/dist-server/server/agent-tool-policy.js +1 -0
  17. package/dist-server/server/bot-package.js +2 -0
  18. package/dist-server/server/box.js +23 -4
  19. package/dist-server/server/browser-engine.js +87 -10
  20. package/dist-server/server/browser-live.js +7 -5
  21. package/dist-server/server/browser-runtime.js +65 -10
  22. package/dist-server/server/checkpoints.js +71 -23
  23. package/dist-server/server/chief-of-staff.js +3 -0
  24. package/dist-server/server/cli-prompts.js +3 -1
  25. package/dist-server/server/commands.js +27 -0
  26. package/dist-server/server/compaction-summary.js +78 -0
  27. package/dist-server/server/computer-wait.js +33 -0
  28. package/dist-server/server/config.js +84 -3
  29. package/dist-server/server/context-budget.js +29 -0
  30. package/dist-server/server/context-rebuild.js +72 -0
  31. package/dist-server/server/delta-context.js +258 -0
  32. package/dist-server/server/digest.js +130 -0
  33. package/dist-server/server/drivers/acp/core.js +540 -229
  34. package/dist-server/server/drivers/agents-proxy.js +79 -12
  35. package/dist-server/server/drivers/agents-result.js +23 -0
  36. package/dist-server/server/drivers/claude.js +84 -15
  37. package/dist-server/server/drivers/codex.js +19 -5
  38. package/dist-server/server/drivers/openai-chat.js +66 -45
  39. package/dist-server/server/drivers/openai-compat.js +1 -0
  40. package/dist-server/server/hooks/omb-hook.js +103 -0
  41. package/dist-server/server/incidents.js +104 -0
  42. package/dist-server/server/index.js +1382 -318
  43. package/dist-server/server/local-computer.js +21 -2
  44. package/dist-server/server/mcp-bridge.js +10 -2
  45. package/dist-server/server/mcp-tool-images.js +37 -0
  46. package/dist-server/server/mcp-tool-schema.js +108 -0
  47. package/dist-server/server/message-db.js +80 -21
  48. package/dist-server/server/message-file.js +3 -2
  49. package/dist-server/server/notify.js +3 -1
  50. package/dist-server/server/package-export.js +1 -0
  51. package/dist-server/server/peer-roster.js +4 -2
  52. package/dist-server/server/provider-icon.js +20 -0
  53. package/dist-server/server/proxy-paths.js +1 -0
  54. package/dist-server/server/request-auth.js +1 -0
  55. package/dist-server/server/room-handoffs.js +16 -2
  56. package/dist-server/server/routine-requests.js +39 -1
  57. package/dist-server/server/routines.js +43 -9
  58. package/dist-server/server/shared-computer-control.js +31 -4
  59. package/dist-server/server/steer-queue.js +6 -0
  60. package/dist-server/server/store.js +111 -14
  61. package/dist-server/server/system-prompt.js +4 -4
  62. package/dist-server/server/team-backup.js +9 -1
  63. package/dist-server/server/tool-results.js +72 -0
  64. package/dist-server/server/tts/grok.js +74 -0
  65. package/dist-server/server/tts/index.js +19 -1
  66. package/dist-server/server/webhooks.js +28 -0
  67. package/dist-server/shared/approval-mode.js +7 -1
  68. package/dist-server/shared/digest.js +1 -0
  69. package/dist-server/shared/markdown-windows-paths.js +36 -0
  70. package/dist-server/shared/provider-icon.js +110 -0
  71. package/dist-server/shared/team-backup.js +3 -0
  72. package/dist-server/vps-container-mcp.js +217 -12
  73. package/enterprise/server/index.js +1 -0
  74. package/package.json +1 -1
  75. package/dist/assets/index-CKysBq-V.css +0 -1
  76. package/dist/assets/index-DSvqfBOf.js +0 -309
@@ -3157,13 +3157,13 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
3157
3157
  }
3158
3158
  return propValues;
3159
3159
  });
3160
- const isObject2 = isObject;
3160
+ const isObject3 = isObject;
3161
3161
  const catchall = def.catchall;
3162
3162
  let value;
3163
3163
  inst._zod.parse = (payload, ctx) => {
3164
3164
  value ?? (value = _normalized.value);
3165
3165
  const input = payload.value;
3166
- if (!isObject2(input)) {
3166
+ if (!isObject3(input)) {
3167
3167
  payload.issues.push({
3168
3168
  expected: "object",
3169
3169
  code: "invalid_type",
@@ -3290,7 +3290,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
3290
3290
  return (payload, ctx) => fn(shape, payload, ctx);
3291
3291
  };
3292
3292
  let fastpass;
3293
- const isObject2 = isObject;
3293
+ const isObject3 = isObject;
3294
3294
  const jit = !globalConfig.jitless;
3295
3295
  const allowsEval2 = allowsEval;
3296
3296
  const fastEnabled = jit && allowsEval2.value;
@@ -3299,7 +3299,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
3299
3299
  inst._zod.parse = (payload, ctx) => {
3300
3300
  value ?? (value = _normalized.value);
3301
3301
  const input = payload.value;
3302
- if (!isObject2(input)) {
3302
+ if (!isObject3(input)) {
3303
3303
  payload.issues.push({
3304
3304
  expected: "object",
3305
3305
  code: "invalid_type",
@@ -14660,6 +14660,97 @@ function isModelVariant(value) {
14660
14660
  return typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && !new RegExp("\\p{Cc}", "u").test(value);
14661
14661
  }
14662
14662
 
14663
+ // shared/provider-icon.ts
14664
+ var PROVIDER_ICON_PRESETS = [
14665
+ "openai",
14666
+ "anthropic",
14667
+ "google",
14668
+ "azure",
14669
+ "aws",
14670
+ "xai",
14671
+ "deepseek",
14672
+ "meta",
14673
+ "mistral",
14674
+ "qwen",
14675
+ "moonshot",
14676
+ "cohere",
14677
+ "openrouter"
14678
+ ];
14679
+ var PROVIDER_ICON_MAX_BYTES = 128 * 1024;
14680
+ var PROVIDER_ICON_MAX_DIMENSION = 1024;
14681
+ function decodedBase64Bytes(value) {
14682
+ const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
14683
+ return Math.floor(value.length * 3 / 4) - padding;
14684
+ }
14685
+ function imageDimensions(bytes, mime) {
14686
+ const u16be = (offset) => bytes[offset] * 256 + bytes[offset + 1];
14687
+ const u16le = (offset) => bytes[offset] + bytes[offset + 1] * 256;
14688
+ const u24le = (offset) => bytes[offset] + bytes[offset + 1] * 256 + bytes[offset + 2] * 65536;
14689
+ const u32le = (offset) => bytes[offset] + bytes[offset + 1] * 256 + bytes[offset + 2] * 65536 + bytes[offset + 3] * 16777216;
14690
+ const u32be = (offset) => bytes[offset] * 16777216 + bytes[offset + 1] * 65536 + bytes[offset + 2] * 256 + bytes[offset + 3];
14691
+ if (mime === "image/png") {
14692
+ if (bytes.length < 33 || bytes.slice(0, 8).join(",") !== "137,80,78,71,13,10,26,10" || String.fromCharCode(...bytes.slice(12, 16)) !== "IHDR" || bytes.slice(-8).join(",") !== "73,69,78,68,174,66,96,130") return null;
14693
+ return { width: u32be(16), height: u32be(20) };
14694
+ }
14695
+ if (mime === "image/jpeg") {
14696
+ if (bytes.length < 12 || bytes[0] !== 255 || bytes[1] !== 216 || bytes.at(-2) !== 255 || bytes.at(-1) !== 217) return null;
14697
+ for (let offset = 2; offset + 8 < bytes.length; ) {
14698
+ if (bytes[offset] !== 255) {
14699
+ offset += 1;
14700
+ continue;
14701
+ }
14702
+ const marker = bytes[offset + 1];
14703
+ if ([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207].includes(marker)) {
14704
+ return { height: u16be(offset + 5), width: u16be(offset + 7) };
14705
+ }
14706
+ if (marker === 216 || marker === 217 || marker === 1 || marker >= 208 && marker <= 215) {
14707
+ offset += 2;
14708
+ continue;
14709
+ }
14710
+ const length = u16be(offset + 2);
14711
+ if (length < 2) return null;
14712
+ offset += length + 2;
14713
+ }
14714
+ return null;
14715
+ }
14716
+ if (bytes.length < 30 || String.fromCharCode(...bytes.slice(0, 4)) !== "RIFF" || String.fromCharCode(...bytes.slice(8, 12)) !== "WEBP" || u32le(4) + 8 !== bytes.length) return null;
14717
+ const format = String.fromCharCode(...bytes.slice(12, 16));
14718
+ if (format === "VP8X") return { width: u24le(24) + 1, height: u24le(27) + 1 };
14719
+ if (format === "VP8L" && bytes[20] === 47) {
14720
+ const bits = bytes[21] | bytes[22] << 8 | bytes[23] << 16 | bytes[24] << 24;
14721
+ return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
14722
+ }
14723
+ if (format === "VP8 " && bytes[23] === 157 && bytes[24] === 1 && bytes[25] === 42) {
14724
+ return { width: u16le(26) & 16383, height: u16le(28) & 16383 };
14725
+ }
14726
+ return null;
14727
+ }
14728
+ function providerIconError(icon) {
14729
+ if (icon.kind === "preset") {
14730
+ return PROVIDER_ICON_PRESETS.includes(icon.preset) ? null : "Choose a supported provider icon.";
14731
+ }
14732
+ const match = /^data:(image\/(?:png|jpeg|webp));base64,([A-Za-z0-9+/]+={0,2})$/.exec(icon.dataUrl);
14733
+ if (!match) return "Upload a PNG, JPEG, or WebP image.";
14734
+ const byteLength = decodedBase64Bytes(match[2]);
14735
+ if (byteLength > PROVIDER_ICON_MAX_BYTES) {
14736
+ return "Provider icons must be 128 KB or smaller.";
14737
+ }
14738
+ let decoded;
14739
+ try {
14740
+ decoded = atob(match[2]);
14741
+ } catch {
14742
+ return "The uploaded image is not valid base64.";
14743
+ }
14744
+ if (decoded.length !== byteLength) return "The uploaded image is not valid base64.";
14745
+ const bytes = Uint8Array.from(decoded, (value) => value.charCodeAt(0));
14746
+ const dimensions = imageDimensions(bytes, match[1]);
14747
+ if (!dimensions) return "The uploaded image is incomplete or does not match its file type.";
14748
+ if (dimensions.width < 1 || dimensions.height < 1 || dimensions.width > PROVIDER_ICON_MAX_DIMENSION || dimensions.height > PROVIDER_ICON_MAX_DIMENSION || dimensions.width * dimensions.height > PROVIDER_ICON_MAX_DIMENSION ** 2) {
14749
+ return `Provider icons must be at most ${PROVIDER_ICON_MAX_DIMENSION} \xD7 ${PROVIDER_ICON_MAX_DIMENSION} pixels.`;
14750
+ }
14751
+ return null;
14752
+ }
14753
+
14663
14754
  // server/mcp-registry.ts
14664
14755
  var MAX_ARGS = 64;
14665
14756
  var stdioEntrySchema = external_exports.object({
@@ -14700,11 +14791,26 @@ var MAX_LOCAL_VM_MAX_INSTANCES = 4;
14700
14791
  function isValidSshAlias(value) {
14701
14792
  return typeof value === "string" && SSH_ALIAS.test(value);
14702
14793
  }
14794
+ var CDP_PORT = /^[0-9]{1,5}$/;
14795
+ var CDP_URL = /^(https?|wss?):\/\/\S+$/i;
14796
+ function isValidCdpTarget(value) {
14797
+ if (typeof value !== "string" || value === "") return false;
14798
+ if (CDP_PORT.test(value)) {
14799
+ const port = Number(value);
14800
+ return port >= 1 && port <= 65535;
14801
+ }
14802
+ return CDP_URL.test(value);
14803
+ }
14703
14804
  var vpsConfigSchema = external_exports.object({
14704
14805
  sshAlias: external_exports.string().refine((value) => value === "" || isValidSshAlias(value), {
14705
14806
  message: "must be a simple SSH config alias"
14706
14807
  }).optional()
14707
14808
  });
14809
+ var browserEngineConfigSchema = external_exports.object({
14810
+ attachCdpUrl: external_exports.string().trim().max(2048).refine((value) => value === "" || isValidCdpTarget(value), {
14811
+ message: "browserEngine.attachCdpUrl must be a CDP port (1-65535) or an http(s)/ws(s) URL"
14812
+ }).optional()
14813
+ });
14708
14814
  var roomConfigSchema = external_exports.object({
14709
14815
  turnTimeoutMinutes: external_exports.number().int().min(MIN_ROOM_TURN_TIMEOUT_MINUTES).max(MAX_ROOM_TURN_TIMEOUT_MINUTES),
14710
14816
  /** Room handoff tree lifetime. Active execution pauses this clock; the
@@ -14846,7 +14952,11 @@ var featureConfigSchema = external_exports.object({
14846
14952
  /** Claude bots also load the MCP servers and connectors from this
14847
14953
  * machine's own Claude Code setup (Plugins → MCP servers switch). Off by
14848
14954
  * default: each extra tool costs tokens on every model call. */
14849
- claudeUserMcp: external_exports.boolean().optional()
14955
+ claudeUserMcp: external_exports.boolean().optional(),
14956
+ /** LLM-generated titles for new bot threads. Off until explicitly
14957
+ * enabled; a one-shot that fails or answers junk leaves the first-message
14958
+ * snippet in place — see llmThreadTitlesEnabled. */
14959
+ llmThreadTitles: external_exports.boolean().optional()
14850
14960
  });
14851
14961
  var onboardingConfigSchema = external_exports.object({
14852
14962
  /** ISO timestamp of finishing (or skipping to the end of) the welcome flow. */
@@ -14860,6 +14970,10 @@ var instanceConfigSchema = external_exports.object({
14860
14970
  driver: external_exports.string().min(1),
14861
14971
  displayName: optionalText,
14862
14972
  accentColor: optionalText,
14973
+ icon: external_exports.discriminatedUnion("kind", [
14974
+ external_exports.object({ kind: external_exports.literal("preset"), preset: external_exports.enum(PROVIDER_ICON_PRESETS) }).strict(),
14975
+ external_exports.object({ kind: external_exports.literal("custom"), dataUrl: external_exports.string() }).strict().refine((icon) => providerIconError(icon) === null, { message: "Invalid provider icon" })
14976
+ ]).optional(),
14863
14977
  environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
14864
14978
  enabled: external_exports.boolean().optional(),
14865
14979
  config: external_exports.json().optional()
@@ -14924,6 +15038,7 @@ var appConfigSchema = external_exports.object({
14924
15038
  /** Voice settings and the selected voice id. `provider` picks the
14925
15039
  * engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
14926
15040
  * `fishKey`), "system" (the Mac's built-in voices, no key), or
15041
+ * "xai" (Grok TTS, reusing `xai.key`), or
14927
15042
  * "chatterbox" (a local OpenAI-compatible Chatterbox server; `baseUrl`
14928
15043
  * and `model` are settings, not secrets). Cloud keys stay separate so
14929
15044
  * switching providers never overwrites or misuses the other key. */
@@ -14931,7 +15046,7 @@ var appConfigSchema = external_exports.object({
14931
15046
  key: optionalText,
14932
15047
  fishKey: optionalText,
14933
15048
  voice: optionalText,
14934
- provider: external_exports.enum(["elevenlabs", "fish", "system", "chatterbox"]).optional(),
15049
+ provider: external_exports.enum(["elevenlabs", "fish", "system", "chatterbox", "xai"]).optional(),
14935
15050
  baseUrl: external_exports.string().trim().max(2048).refine((value) => !value || /^https?:\/\//i.test(value), "the Chatterbox server address must start with http:// or https://").optional(),
14936
15051
  model: optionalText
14937
15052
  }).optional(),
@@ -14960,6 +15075,11 @@ var appConfigSchema = external_exports.object({
14960
15075
  * system language. Unknown tags degrade to English in the renderer. */
14961
15076
  language: optionalText,
14962
15077
  rooms: roomConfigSchema.optional(),
15078
+ context: external_exports.object({
15079
+ rebuildBytes: external_exports.number().int().min(1024).max(1e6).optional(),
15080
+ compactAt: external_exports.number().positive().max(1e7).optional(),
15081
+ autoCompact: external_exports.boolean().optional()
15082
+ }).optional(),
14963
15083
  threads: external_exports.object({
14964
15084
  maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
14965
15085
  /** Cap each per-thread events/ and native/ NDJSON log at this many
@@ -14972,6 +15092,8 @@ var appConfigSchema = external_exports.object({
14972
15092
  localVm: localVmConfigSchema.optional(),
14973
15093
  features: featureConfigSchema.optional(),
14974
15094
  onboarding: onboardingConfigSchema.optional(),
15095
+ /** CDP attach target for a bot's browser; see browserEngineConfigSchema. */
15096
+ browserEngine: browserEngineConfigSchema.optional(),
14975
15097
  browserProfiles: browserProfilesSchema.optional(),
14976
15098
  instances: instanceConfigMapSchema.optional(),
14977
15099
  /** User-configured MCP servers, mounted into every capable engine. Kept
@@ -15010,6 +15132,7 @@ var SPAWNED_PROXIES = {
15010
15132
  connectors: resolveProxy("connector-proxy"),
15011
15133
  mcpGate: resolveProxy("mcp-gate"),
15012
15134
  phone: resolveProxy("drivers/phone-proxy"),
15135
+ hook: resolveProxy("hooks/omb-hook"),
15013
15136
  // Loaded by the external `pi` process via `-e`, not by this server — but
15014
15137
  // resolved through the same single source of truth so the packaged layout
15015
15138
  // check can assert it ships.
@@ -15125,6 +15248,86 @@ function createControlClient(options) {
15125
15248
  }
15126
15249
  var CONTROL_REFUSAL_PLAIN = "A person has taken control of this computer, so this call was NOT performed. Do not retry it \u2014 the screen is changing under their hands. Pause this task, tell the person you are waiting for them to hand control back, and take a fresh screenshot before your next action once they have.";
15127
15250
 
15251
+ // server/mcp-tool-schema.ts
15252
+ var ROOT_COMBINATORS = ["anyOf", "oneOf", "allOf", "not"];
15253
+ function isObject2(value) {
15254
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
15255
+ }
15256
+ function branches(value) {
15257
+ return Array.isArray(value) ? value.filter(isObject2) : [];
15258
+ }
15259
+ function objectShape(schema) {
15260
+ const properties = isObject2(schema.properties) ? { ...schema.properties } : {};
15261
+ const required2 = new Set(Array.isArray(schema.required) ? schema.required.filter((key) => typeof key === "string") : []);
15262
+ const adopt = (shape) => {
15263
+ for (const [key, value] of Object.entries(shape.properties)) if (!(key in properties)) properties[key] = value;
15264
+ };
15265
+ for (const branch of branches(schema.allOf)) {
15266
+ const shape = objectShape(branch);
15267
+ adopt(shape);
15268
+ for (const key of shape.required) required2.add(key);
15269
+ }
15270
+ for (const keyword of ["anyOf", "oneOf"]) {
15271
+ const choices = branches(schema[keyword]).map(objectShape);
15272
+ if (choices.length === 0) continue;
15273
+ for (const choice of choices) adopt(choice);
15274
+ for (const key of choices[0].required) {
15275
+ if (choices.every((choice) => choice.required.includes(key))) required2.add(key);
15276
+ }
15277
+ }
15278
+ return { properties, required: [...required2] };
15279
+ }
15280
+ function describesObject(type) {
15281
+ return type === void 0 || type === "object" || Array.isArray(type) && type.includes("object");
15282
+ }
15283
+ function providerSafeInputSchema(schema) {
15284
+ if (!isObject2(schema) || !describesObject(schema.type)) return { type: "object", properties: {} };
15285
+ if (schema.type === "object" && ROOT_COMBINATORS.every((keyword) => !(keyword in schema))) return schema;
15286
+ const { properties, required: required2 } = objectShape(schema);
15287
+ const safe = { ...schema, type: "object", properties };
15288
+ for (const keyword of ROOT_COMBINATORS) delete safe[keyword];
15289
+ if (required2.length > 0) safe.required = required2;
15290
+ else delete safe.required;
15291
+ return safe;
15292
+ }
15293
+ function createToolListNormalizer() {
15294
+ const pending = /* @__PURE__ */ new Set();
15295
+ const idKey = (id) => `${typeof id}:${String(id)}`;
15296
+ return {
15297
+ observeRequest(line) {
15298
+ let frame;
15299
+ try {
15300
+ frame = JSON.parse(line);
15301
+ } catch {
15302
+ return;
15303
+ }
15304
+ if (isObject2(frame) && frame.method === "tools/list" && (typeof frame.id === "number" || typeof frame.id === "string")) {
15305
+ pending.add(idKey(frame.id));
15306
+ }
15307
+ },
15308
+ rewriteResponse(line) {
15309
+ if (pending.size === 0) return line;
15310
+ let frame;
15311
+ try {
15312
+ frame = JSON.parse(line);
15313
+ } catch {
15314
+ return line;
15315
+ }
15316
+ if (!isObject2(frame) || "method" in frame || !pending.delete(idKey(frame.id))) return line;
15317
+ if (!isObject2(frame.result) || !Array.isArray(frame.result.tools)) return line;
15318
+ let changed = false;
15319
+ const tools = frame.result.tools.map((tool) => {
15320
+ if (!isObject2(tool)) return tool;
15321
+ const inputSchema = providerSafeInputSchema(tool.inputSchema);
15322
+ if (inputSchema === tool.inputSchema) return tool;
15323
+ changed = true;
15324
+ return { ...tool, inputSchema };
15325
+ });
15326
+ return changed ? JSON.stringify({ ...frame, result: { ...frame.result, tools } }) : line;
15327
+ }
15328
+ };
15329
+ }
15330
+
15128
15331
  // server/mcp-bridge.ts
15129
15332
  var BRIDGE_INACTIVITY_MS = 45e3;
15130
15333
  var PROBE_TIMEOUT_MS = 1e4;
@@ -15294,8 +15497,10 @@ function runMcpBridge(options) {
15294
15497
  }
15295
15498
  } : {}
15296
15499
  });
15500
+ const toolLists = createToolListNormalizer();
15297
15501
  let pendingInput = Promise.resolve();
15298
15502
  const inbound = createLineSplitter((line) => {
15503
+ toolLists.observeRequest(line);
15299
15504
  const completion = intercept(line);
15300
15505
  if (completion) pendingInput = completion;
15301
15506
  });
@@ -15305,7 +15510,7 @@ function runMcpBridge(options) {
15305
15510
  inbound.flush();
15306
15511
  void pendingInput.finally(() => child.stdin.end());
15307
15512
  });
15308
- const outbound = createLineSplitter((line) => process.stdout.write(line + "\n"));
15513
+ const outbound = createLineSplitter((line) => process.stdout.write(toolLists.rewriteResponse(line) + "\n"));
15309
15514
  child.stdout.on("data", (chunk) => outbound.push(chunk));
15310
15515
  child.stdout.on("end", () => outbound.flush());
15311
15516
  const detach = () => {
@@ -763,6 +763,7 @@ var READ_ONLY_AGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
763
763
  "wait_delegation",
764
764
  "session_search",
765
765
  "session_read",
766
+ "tool_result_read",
766
767
  "list_routines",
767
768
  "skills_list"
768
769
  ]);
@@ -798,6 +799,88 @@ function peerName(value) {
798
799
  return clip(value.replace(/[[\]]/g, " "), ROSTER_NAME_MAX);
799
800
  }
800
801
 
802
+ // shared/redact.ts
803
+ var REDACTION_MARKER = /^«redacted \d+ chars»$/;
804
+ var mask = (value) => REDACTION_MARKER.test(value) ? value : `\xABredacted ${value.length} chars\xBB`;
805
+ var KEY_PREFIXES = [
806
+ /\bom[dg]_[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])/g,
807
+ // desktop device and model-only credentials
808
+ /\bsk-(?:ant-|proj-|live-|test-)?[A-Za-z0-9_-]{16,}/g,
809
+ // anthropic / openai / stripe
810
+ /\bxai-[A-Za-z0-9_-]{20,}/g,
811
+ // xai (grok)
812
+ /\bgsk_[A-Za-z0-9]{40,}/g,
813
+ // groq
814
+ /\bhf_[A-Za-z0-9]{30,}/g,
815
+ // hugging face
816
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/g,
817
+ // github classic
818
+ /\bgithub_pat_[A-Za-z0-9_]{20,}/g,
819
+ // github fine-grained
820
+ /\bxox[abposr]-[A-Za-z0-9-]{20,}/g,
821
+ // slack
822
+ /\bAKIA[0-9A-Z]{16}\b/g,
823
+ // aws access key id
824
+ /\bAIza[0-9A-Za-z_-]{30,}/g,
825
+ // google api key
826
+ /\bnpm_[A-Za-z0-9]{20,}/g,
827
+ // npm
828
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
829
+ // jwt
830
+ ];
831
+ var BEARER = /(\bBearer\s+)([A-Za-z0-9._~+/=-]{12,})/g;
832
+ var PEM_BLOCK = /(-----BEGIN [A-Z ]*PRIVATE KEY-----)([\s\S]*?)(-----END [A-Z ]*PRIVATE KEY-----)/g;
833
+ var KEY_VALUE = /\b((?:[A-Za-z0-9_-]*_)?(?:api[_-]?key|apikey|secret|token|password|passwd|authorization|auth[_-]?token|access[_-]?key|private[_-]?key)s?)(["']?\s*[=:]\s*)(["']?)([A-Za-z0-9._~+/=-]{8,})\3/gi;
834
+ var KEY_SUFFIX_ASSIGNMENT = /\b([A-Za-z][A-Za-z0-9_-]*[_-]key)s?(=)(["']?)([A-Za-z0-9._~+/=-]+)\3/gi;
835
+ var SECRET_FLAG = /(--(?:token|password|passwd|api-key|apikey|secret|access-key|auth-token)(?:=|\s+))(["']?)(?!-)([A-Za-z0-9._~+/=-]+)\2/gi;
836
+ var URL_USERINFO = /(\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@'"]+:)([^\s/@'"«»]+)(@)/gi;
837
+ function redactSecretsInText(text) {
838
+ if (!text || text.length < 8) return text;
839
+ let out = text;
840
+ out = out.replace(PEM_BLOCK, (_m, open, body, close) => `${open}
841
+ ${mask(body.trim())}
842
+ ${close}`);
843
+ for (const re of KEY_PREFIXES) out = out.replace(re, (m2) => mask(m2));
844
+ out = out.replace(BEARER, (_m, lead, tok) => `${lead}${mask(tok)}`);
845
+ out = out.replace(KEY_VALUE, (_m, key, sep, quote, value) => `${key}${sep}${quote}${mask(value)}${quote}`);
846
+ out = out.replace(KEY_SUFFIX_ASSIGNMENT, (_m, key, sep, quote, value) => `${key}${sep}${quote}${mask(value)}${quote}`);
847
+ out = out.replace(SECRET_FLAG, (_m, flag, quote, value) => `${flag}${quote}${mask(value)}${quote}`);
848
+ out = out.replace(URL_USERINFO, (_m, lead, secret, at) => `${lead}${mask(secret)}${at}`);
849
+ return out;
850
+ }
851
+
852
+ // server/tool-results.ts
853
+ var TOOL_RESULT_PREVIEW_CHARS = 16e3;
854
+ var TOOL_RESULT_MAX_CHARS = 128 * 1024;
855
+ var TOOL_RESULT_TTL_MS = 60 * 6e4;
856
+ var MAX_BYTES = 16 * 1024 * 1024;
857
+ var MAX_OWNER_BYTES = 2 * 1024 * 1024;
858
+ function toolResultPrefix(text, chars) {
859
+ const prefix = text.slice(0, chars);
860
+ const last = prefix.charCodeAt(prefix.length - 1);
861
+ return last >= 55296 && last <= 56319 ? prefix.slice(0, -1) : prefix;
862
+ }
863
+
864
+ // server/drivers/agents-result.ts
865
+ async function boundedAgentResult(text, save) {
866
+ if (text.length <= 24e3) return text;
867
+ const redacted = redactSecretsInText(text);
868
+ const prefix = toolResultPrefix(redacted, TOOL_RESULT_PREVIEW_CHARS);
869
+ const retained = toolResultPrefix(redacted, TOOL_RESULT_MAX_CHARS);
870
+ const truncated = retained.length < redacted.length;
871
+ try {
872
+ const saved = await save(retained, truncated);
873
+ if (!saved || typeof saved.id !== "string" || !/^r-[0-9a-f-]{36}$/.test(saved.id)) throw new Error("Invalid saved result");
874
+ return `${prefix}
875
+
876
+ [Large tool result: showing the first ${prefix.length} characters. ${truncated || saved.truncated ? "Only a bounded portion was retained; the remaining tail was omitted." : "The remaining redacted result is temporarily saved."} If a missing detail is needed, call tool_result_read with id "${saved.id}" and offset ${prefix.length}. Saved results expire after one hour, on app restart, or under cache pressure. Do not repeat an action just to retrieve its output.]`;
877
+ } catch {
878
+ return `${prefix}
879
+
880
+ [Large tool result: showing the first ${prefix.length} characters. The remaining output could not be saved. The original operation was not retried. Do not repeat an action just to retrieve its output.]`;
881
+ }
882
+ }
883
+
801
884
  // server/drivers/agents-proxy.ts
802
885
  var HARNESS = process.env.OMB_HARNESS_URL ?? "http://127.0.0.1:8799";
803
886
  var BOT_ID = process.env.OMB_BOT_ID ?? "";
@@ -1047,8 +1130,8 @@ var ROUTINE_FIELDS_SCHEMA = {
1047
1130
  schedule: ROUTINE_SCHEDULE_SCHEMA,
1048
1131
  run_on: {
1049
1132
  type: "string",
1050
- enum: ["maus", "cloud"],
1051
- description: "Where the routine runs. Defaults to maus (this OpenMausBot setup)."
1133
+ enum: ["maus", "box"],
1134
+ description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box explicitly switches the agent to the Box-hosted runner; it requires Box setup and is not the generic cloud/VPS option. Legacy cloud values from list_routines mean box, not VPS."
1052
1135
  },
1053
1136
  timeout_minutes: {
1054
1137
  type: "integer",
@@ -1063,10 +1146,28 @@ var ROUTINE_FIELDS_SCHEMA = {
1063
1146
  continuity: {
1064
1147
  type: "boolean",
1065
1148
  description: "Opt in to using the latest completed run's bounded report as historical context. Defaults to false; set false in an update to start fresh again. Included in the applied result or pending confirmation."
1149
+ },
1150
+ overlap: {
1151
+ type: "string",
1152
+ enum: ["skip", "queue"],
1153
+ description: "While this routine is still working, skip scheduled occurrences (default) or queue at most one run. Queue skips further occurrences until the pending run starts; it never builds an unlimited backlog. Manual and webhook requests are separate."
1066
1154
  }
1067
1155
  };
1068
1156
  var PROPOSAL_OUTCOME = " Read the result: granted Full Access may apply the change immediately. If applied, continue the requested work without another confirmation. Only a pending result requires ending the turn and waiting for the in-app decision. Never claim success from the permission mode alone; report failed or cancelled results honestly. This does not elevate another bot's execution permissions.";
1069
1157
  var TOOLS = [
1158
+ {
1159
+ name: "tool_result_read",
1160
+ description: "Read a missing portion of an oversized agents-tool result using the saved id and next offset from its notice. Returns at most 16,000 characters, only from this bot in this conversation. Use only when the preview is insufficient; do not load every page by default. Results expire after one hour, on app restart, or under cache pressure. This never reruns the original action.",
1161
+ inputSchema: {
1162
+ type: "object",
1163
+ additionalProperties: false,
1164
+ properties: {
1165
+ id: { type: "string", description: "Saved result id copied from the truncation notice." },
1166
+ offset: { type: "integer", minimum: 0, description: "Character offset copied from the previous result's notice. Defaults to 0." }
1167
+ },
1168
+ required: ["id"]
1169
+ }
1170
+ },
1070
1171
  {
1071
1172
  name: "list_shared_computers",
1072
1173
  description: "List online desktop computers explicitly shared with this workspace, and their allowed folders/capabilities. These are the user's computers, not this server. An offline or unshared computer cannot be accessed. Folder paths use opaque folder IDs and relative paths.",
@@ -1117,7 +1218,7 @@ var TOOLS = [
1117
1218
  },
1118
1219
  {
1119
1220
  name: "ask_bot",
1120
- description: "SYNCHRONOUS consultation: send a short question to another bot and stay blocked until its reply is returned inline. Use only when that reply is required to write your current response. Do not use for assigning work, background tasks, or potentially long work; use delegate_bot for those. Returns promptly with a note if that bot is busy.",
1221
+ description: "Brief synchronous consultation: send a short question to another bot. Quick replies return inline; slow replies become asynchronous delegations and return automatically after you finish your turn. Use only when that reply is required to write your current response. Do not use for assigning work, background tasks, or potentially long work; use delegate_bot for those. Returns promptly with a note if that bot is busy.",
1121
1222
  inputSchema: {
1122
1223
  type: "object",
1123
1224
  properties: {
@@ -1359,6 +1460,20 @@ var TOOLS = [
1359
1460
  required: ["action"]
1360
1461
  }
1361
1462
  },
1463
+ {
1464
+ name: "retry_thread",
1465
+ description: "Chief of Staff only. Resume a teammate's thread whose last run failed, stalled or could not start \u2014 the one an incident report named \u2014 exactly where it stopped, keeping its conversation and files. The teammate gets a line saying you asked for the retry and why. Use it when the cause looks transient (a crash, a timeout, a busy service). Use delegate_bot with a corrected brief instead when the request itself needs to change, and tell the person instead when only they can fix the cause (a sign-in, a missing credential, an unanswered question). Never retry the same thread more than twice.",
1466
+ inputSchema: {
1467
+ type: "object",
1468
+ additionalProperties: false,
1469
+ properties: {
1470
+ bot_id: { type: "string", description: "The teammate's id, from the incident report or list_bots." },
1471
+ thread_id: { type: "string", description: "The failed thread's id, from the incident report." },
1472
+ note: { type: "string", description: "Optional: one sentence for the teammate about what to watch for this time." }
1473
+ },
1474
+ required: ["bot_id", "thread_id"]
1475
+ }
1476
+ },
1362
1477
  {
1363
1478
  name: "memory_log",
1364
1479
  description: "Write one line to today's log file, memory/log/YYYY-MM-DD.md, stamped with the time and this conversation: what happened, not what is true. Use it for events worth a trace \u2014 a deploy went out, a person decided something, a check failed \u2014 that should not shape future sessions. Logs are never loaded into your prompt; the person can read them, and session_search finds them later. A fact that should hold in every session goes to memory_update instead.",
@@ -1544,6 +1659,11 @@ async function api(path, init) {
1544
1659
  if (!ok2) throw new Error(String(body.error ?? `HTTP ${status}`));
1545
1660
  return body;
1546
1661
  }
1662
+ var capResult = (text) => boundedAgentResult(text, (retained, truncated) => api("/api/internal/tool-result", {
1663
+ method: "POST",
1664
+ signal: AbortSignal.timeout(3e3),
1665
+ body: JSON.stringify({ text: retained, truncated })
1666
+ }));
1547
1667
  async function apiResponse(path, init) {
1548
1668
  const res = await fetch(HARNESS + path, {
1549
1669
  ...init,
@@ -1566,16 +1686,17 @@ function routineAction(value) {
1566
1686
  }
1567
1687
  function routineFields(args) {
1568
1688
  const fields = {};
1569
- if (args.run_on != null && args.runOn != null && args.run_on !== args.runOn) {
1689
+ const destination = (value) => value === "box" ? "cloud" : value;
1690
+ if (args.run_on != null && args.runOn != null && destination(args.run_on) !== destination(args.runOn)) {
1570
1691
  return { fields, error: "Choose one run_on destination; run_on and runOn disagree." };
1571
1692
  }
1572
1693
  if (args.timeout_minutes != null && args.timeoutMinutes != null && args.timeout_minutes !== args.timeoutMinutes) {
1573
1694
  return { fields, error: "Choose one timeout_minutes limit; timeout_minutes and timeoutMinutes disagree." };
1574
1695
  }
1575
- const runOn = args.run_on ?? args.runOn;
1696
+ const runOn = destination(args.run_on ?? args.runOn);
1576
1697
  const timeoutMinutes = args.timeout_minutes ?? args.timeoutMinutes;
1577
1698
  if (runOn != null && runOn !== "maus" && runOn !== "cloud") {
1578
- return { fields, error: 'run_on must be "maus" or "cloud".' };
1699
+ return { fields, error: 'Use run_on="maus" for the bot\u2019s current model and configured computer (including VPS), or run_on="box" only for the Box-hosted agent. Legacy "cloud" also means Box.' };
1579
1700
  }
1580
1701
  if (timeoutMinutes != null && (typeof timeoutMinutes !== "number" || !Number.isInteger(timeoutMinutes) || timeoutMinutes < 5 || timeoutMinutes > 240)) {
1581
1702
  return { fields, error: "timeout_minutes must be a whole number from 5 to 240. Use clear_timeout to remove a limit." };
@@ -1583,6 +1704,9 @@ function routineFields(args) {
1583
1704
  if (args.continuity != null && typeof args.continuity !== "boolean") {
1584
1705
  return { fields, error: "continuity must be true or false." };
1585
1706
  }
1707
+ if (args.overlap !== void 0 && args.overlap !== "skip" && args.overlap !== "queue") {
1708
+ return { fields, error: "overlap must be skip or queue." };
1709
+ }
1586
1710
  if (args.clear_timeout != null && typeof args.clear_timeout !== "boolean") {
1587
1711
  return { fields, error: "clear_timeout must be true or false." };
1588
1712
  }
@@ -1600,6 +1724,7 @@ function routineFields(args) {
1600
1724
  if (args.clear_timeout === true) fields.timeoutMinutes = null;
1601
1725
  else if (timeoutMinutes != null) fields.timeoutMinutes = timeoutMinutes;
1602
1726
  if (typeof args.continuity === "boolean") fields.continuity = args.continuity;
1727
+ if (args.overlap !== void 0) fields.overlap = args.overlap;
1603
1728
  return { fields };
1604
1729
  }
1605
1730
  function completedProposalResult(r, subject) {
@@ -1643,6 +1768,16 @@ function recallSpeaker(hit) {
1643
1768
  return hit.role === "user" ? "user" : "you";
1644
1769
  }
1645
1770
  async function callTool(name, args) {
1771
+ if (name === "tool_result_read") {
1772
+ if (typeof args.id !== "string" || !/^r-[0-9a-f-]{36}$/.test(args.id) || args.offset !== void 0 && (!Number.isSafeInteger(args.offset) || Number(args.offset) < 0)) {
1773
+ return { text: "Use the saved result id and a non-negative integer offset from its notice.", isError: true };
1774
+ }
1775
+ const r = await api(`/api/internal/tool-result?id=${encodeURIComponent(args.id)}&offset=${args.offset ?? 0}`, { signal: AbortSignal.timeout(3e3) });
1776
+ const text = String(r.text ?? "");
1777
+ return { text: `${text}
1778
+
1779
+ [${Number(r.nextOffset) < Number(r.length) ? `Read more with tool_result_read id "${args.id}" and offset ${r.nextOffset}.` : `End of retained result.${r.truncated ? " The original tail exceeded the storage limit and was omitted." : ""}`}]` };
1780
+ }
1646
1781
  if (name === "list_room_targets") {
1647
1782
  const r = await api("/api/internal/room-targets");
1648
1783
  return { text: JSON.stringify(r), ...r.error ? { isError: true } : {} };
@@ -1747,9 +1882,11 @@ Use post_to_room with one of these ids. A post adds one message to the room; it
1747
1882
  if (r.timeout) {
1748
1883
  const taskId = String(r.taskId ?? "").trim();
1749
1884
  if (taskId) delegationTaskIdsThisTurn.add(taskId);
1750
- const waitedMinutes = Math.max(1, Math.round((Number(r.waitedMs) || 0) / 6e4));
1885
+ const waitedSeconds = Math.max(1, Math.round((Number(r.waitedMs) || 0) / 1e3));
1886
+ const amount = waitedSeconds < 60 ? waitedSeconds : Math.round(waitedSeconds / 60);
1887
+ const unit = waitedSeconds < 60 ? "second" : "minute";
1751
1888
  return {
1752
- text: `${r.toBotName ?? "That bot"} is still working after ${waitedMinutes} minute${waitedMinutes === 1 ? "" : "s"} \u2014 the ask was converted to a delegation so the reply is not lost. Task id: ${taskId}. Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status.`
1889
+ text: `${r.toBotName ?? "That bot"} is still working after ${amount} ${unit}${amount === 1 ? "" : "s"} \u2014 the ask was converted to a delegation so the reply is not lost. Task id: ${taskId}. Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status.`
1753
1890
  };
1754
1891
  }
1755
1892
  if (r.busy) {
@@ -2141,6 +2278,18 @@ ${recent.join("\n")}` : "";
2141
2278
  const entry = typeof r.entry === "string" && r.entry ? ` Entry: ${r.entry}` : "";
2142
2279
  return { text: `Memory updated.${entry}${r.truncated ? " MEMORY.md exceeds the prompt load budget; keep it short and curated." : ""}` };
2143
2280
  }
2281
+ if (name === "retry_thread") {
2282
+ const botId = String(args.bot_id ?? "").trim();
2283
+ const threadId = String(args.thread_id ?? "").trim();
2284
+ const note = typeof args.note === "string" ? args.note.trim() : "";
2285
+ if (!botId || !threadId) return { text: "retry_thread needs bot_id and thread_id \u2014 both are in the incident report.", isError: true };
2286
+ const r = await api("/api/internal/retry-thread", {
2287
+ method: "POST",
2288
+ body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, toBotId: botId, toThreadId: threadId, ...note ? { note } : {} })
2289
+ });
2290
+ if (r.error) return { text: `Couldn't retry that thread: ${String(r.error)}`, isError: true };
2291
+ return { text: typeof r.message === "string" ? r.message : "The thread is running again. Its result stays in that thread; you are not woken for it \u2014 check it later with session_search or list_threads if you need to." };
2292
+ }
2144
2293
  if (name === "memory_log") {
2145
2294
  if (typeof args.text !== "string" || !args.text.trim()) {
2146
2295
  return { text: "memory_log needs text: one line about what happened.", isError: true };
@@ -2316,20 +2465,20 @@ async function handle(msg) {
2316
2465
  return;
2317
2466
  }
2318
2467
  if (name === "list_shared_computers") {
2319
- textResult(id, JSON.stringify(await api("/api/internal/shared-computers")));
2468
+ textResult(id, await capResult(JSON.stringify(await api("/api/internal/shared-computers"))));
2320
2469
  return;
2321
2470
  }
2322
2471
  if (name === "shared_computer") {
2323
2472
  const response = await api("/api/internal/shared-computers", { method: "POST", body: JSON.stringify(params.arguments ?? {}) });
2324
2473
  const result = response.result;
2325
2474
  if (Array.isArray(result?.content)) ok(id, result);
2326
- else textResult(id, JSON.stringify(result));
2475
+ else textResult(id, await capResult(JSON.stringify(result)));
2327
2476
  return;
2328
2477
  }
2329
2478
  const { text, isError } = await callTool(name, params.arguments ?? {});
2330
- textResult(id, text, isError);
2479
+ textResult(id, name === "tool_result_read" ? text : await capResult(text), isError);
2331
2480
  } catch (e) {
2332
- textResult(id, e.message, true);
2481
+ textResult(id, await capResult(e.message), true);
2333
2482
  }
2334
2483
  return;
2335
2484
  }
@@ -14602,6 +14602,7 @@ var SPAWNED_PROXIES = {
14602
14602
  connectors: resolveProxy("connector-proxy"),
14603
14603
  mcpGate: resolveProxy("mcp-gate"),
14604
14604
  phone: resolveProxy("drivers/phone-proxy"),
14605
+ hook: resolveProxy("hooks/omb-hook"),
14605
14606
  // Loaded by the external `pi` process via `-e`, not by this server — but
14606
14607
  // resolved through the same single source of truth so the packaged layout
14607
14608
  // check can assert it ships.