openmausbot 0.1.80 → 0.1.81

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 (56) hide show
  1. package/README.md +6 -5
  2. package/dist/assets/index-CKysBq-V.css +1 -0
  3. package/dist/assets/{index-D6RZKWks.js → index-oLMljotU.js} +1 -1
  4. package/dist/assets/index-peLFiSH8.js +307 -0
  5. package/dist/index.html +2 -2
  6. package/dist-server/container-mcp.js +41 -10
  7. package/dist-server/drivers/agents-proxy.js +50 -11
  8. package/dist-server/index.js +3433 -1278
  9. package/dist-server/openmausbot.js +389 -83
  10. package/dist-server/pair-cli.js +389 -83
  11. package/dist-server/server/box-delete-journal.js +471 -0
  12. package/dist-server/server/box.js +345 -75
  13. package/dist-server/server/cli-phone-setup.js +2 -2
  14. package/dist-server/server/cli.js +84 -12
  15. package/dist-server/server/config.js +48 -18
  16. package/dist-server/server/container-computer.js +7 -0
  17. package/dist-server/server/contracts.js +5 -0
  18. package/dist-server/server/default-model-selection.js +1 -0
  19. package/dist-server/server/drivers/acp/core.js +112 -18
  20. package/dist-server/server/drivers/acp/opencode-go.js +6 -0
  21. package/dist-server/server/drivers/agents-proxy.js +49 -11
  22. package/dist-server/server/drivers/chat-mcp-tools.js +6 -1
  23. package/dist-server/server/drivers/claude.js +33 -2
  24. package/dist-server/server/drivers/codex.js +93 -8
  25. package/dist-server/server/drivers/native.js +6 -1
  26. package/dist-server/server/harness/bus.js +4 -0
  27. package/dist-server/server/harness/registry.js +1 -0
  28. package/dist-server/server/index.js +807 -213
  29. package/dist-server/server/local-vm-lease.js +4 -0
  30. package/dist-server/server/mcp-http.js +337 -0
  31. package/dist-server/server/mcp-probe.js +67 -18
  32. package/dist-server/server/mcp-registry.js +153 -58
  33. package/dist-server/server/member-turn.js +13 -0
  34. package/dist-server/server/message-db.js +74 -8
  35. package/dist-server/server/recall-disclosure.js +4 -0
  36. package/dist-server/server/recent-work.js +142 -0
  37. package/dist-server/server/request-auth.js +3 -1
  38. package/dist-server/server/routines.js +17 -0
  39. package/dist-server/server/screen-frame-gate.js +4 -94
  40. package/dist-server/server/sessions.js +22 -3
  41. package/dist-server/server/store.js +16 -1
  42. package/dist-server/server/surface.js +41 -19
  43. package/dist-server/server/team-setup-requests.js +1 -1
  44. package/dist-server/server/thread-log-rotation.js +104 -0
  45. package/dist-server/server/tts/fish.js +155 -0
  46. package/dist-server/server/tts/index.js +25 -3
  47. package/dist-server/server/vps-computer.js +26 -4
  48. package/dist-server/server/vps-ssh.js +88 -0
  49. package/dist-server/server/workspace-backup.js +11 -0
  50. package/dist-server/server/workspace.js +2 -1
  51. package/dist-server/shared/credential-request.js +10 -0
  52. package/dist-server/shared/tool-surface.js +62 -0
  53. package/dist-server/vps-container-mcp.js +41 -10
  54. package/package.json +1 -1
  55. package/dist/assets/index-Cu8BkIfo.js +0 -305
  56. package/dist/assets/index-tfpeJAIG.css +0 -1
package/dist/index.html CHANGED
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/svg+xml" href="/app-icon.svg" />
7
7
  <title>OpenMausBot</title>
8
- <script type="module" crossorigin src="/assets/index-Cu8BkIfo.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-tfpeJAIG.css">
8
+ <script type="module" crossorigin src="/assets/index-peLFiSH8.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-CKysBq-V.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -14654,18 +14654,31 @@ function normalizeImageGenerationUrl(value) {
14654
14654
 
14655
14655
  // server/contracts.ts
14656
14656
  var EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
14657
+ function isModelVariant(value) {
14658
+ return typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && !new RegExp("\\p{Cc}", "u").test(value);
14659
+ }
14657
14660
 
14658
14661
  // server/mcp-registry.ts
14659
14662
  var MAX_ARGS = 64;
14660
- var storedEntrySchema = external_exports.object({
14663
+ var stdioEntrySchema = external_exports.object({
14661
14664
  command: external_exports.string().trim().min(1).max(1024),
14662
14665
  args: external_exports.array(external_exports.string().max(4096)).max(MAX_ARGS).optional(),
14663
14666
  env: external_exports.record(external_exports.string(), external_exports.string().max(16384)).optional(),
14664
14667
  enabled: external_exports.boolean().optional()
14665
14668
  }).strict();
14666
- var mutationEntrySchema = storedEntrySchema.extend({
14669
+ var stdioMutationSchema = stdioEntrySchema.extend({
14667
14670
  env: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().max(16384), external_exports.literal(true)])).optional()
14668
14671
  });
14672
+ var remoteEntrySchema = external_exports.object({
14673
+ /** Streamable HTTP unless the entry says the older SSE transport. */
14674
+ type: external_exports.enum(["http", "sse"]).optional(),
14675
+ url: external_exports.string().trim().min(1).max(2048),
14676
+ headers: external_exports.record(external_exports.string(), external_exports.string().max(16384)).optional(),
14677
+ enabled: external_exports.boolean().optional()
14678
+ }).strict();
14679
+ var remoteMutationSchema = remoteEntrySchema.extend({
14680
+ headers: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().max(16384), external_exports.literal(true)])).optional()
14681
+ });
14669
14682
 
14670
14683
  // server/config.ts
14671
14684
  var optionalText = external_exports.string().optional();
@@ -14675,6 +14688,8 @@ var BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
14675
14688
  var MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
14676
14689
  var MAX_ROOM_TURN_TIMEOUT_MINUTES = 1440;
14677
14690
  var MAX_CONCURRENT_BOT_THREADS = 10;
14691
+ var MIN_THREAD_EVENT_LOG_BYTES = 256 * 1024;
14692
+ var MAX_THREAD_EVENT_LOG_BYTES = 4 * 1024 * 1024 * 1024;
14678
14693
  var MIN_LOCAL_VM_MAX_INSTANCES = 1;
14679
14694
  var MAX_LOCAL_VM_MAX_INSTANCES = 4;
14680
14695
  function isValidSshAlias(value) {
@@ -14814,7 +14829,11 @@ var featureConfigSchema = external_exports.object({
14814
14829
  /** Opt-in computer sharing (a desktop lending folders, a terminal or
14815
14830
  * computer control to a workspace). Off until explicitly enabled; there is
14816
14831
  * no Settings toggle — see sharedComputersEnabled. */
14817
- sharedComputers: external_exports.boolean().optional()
14832
+ sharedComputers: external_exports.boolean().optional(),
14833
+ /** Claude bots also load the MCP servers and connectors from this
14834
+ * machine's own Claude Code setup (Plugins → MCP servers switch). Off by
14835
+ * default: each extra tool costs tokens on every model call. */
14836
+ claudeUserMcp: external_exports.boolean().optional()
14818
14837
  });
14819
14838
  var onboardingConfigSchema = external_exports.object({
14820
14839
  /** ISO timestamp of finishing (or skipping to the end of) the welcome flow. */
@@ -14836,8 +14855,12 @@ var instanceConfigMapSchema = external_exports.record(external_exports.string(),
14836
14855
  var defaultModelSelectionSchema = external_exports.object({
14837
14856
  instanceId: external_exports.string().trim().min(1),
14838
14857
  model: external_exports.string().trim().min(1),
14839
- effort: external_exports.enum(EFFORT_LEVELS).optional()
14840
- });
14858
+ effort: external_exports.enum(EFFORT_LEVELS).optional(),
14859
+ variant: external_exports.string().refine(isModelVariant, "invalid model variant").optional()
14860
+ }).refine(
14861
+ (selection) => selection.variant === void 0 || selection.effort === void 0,
14862
+ "choose either a model variant or an effort level"
14863
+ );
14841
14864
  var appConfigSchema = external_exports.object({
14842
14865
  /** Verified by the dedicated domain endpoint, never a generic config patch. */
14843
14866
  customDomain: external_exports.string().optional(),
@@ -14886,13 +14909,16 @@ var appConfigSchema = external_exports.object({
14886
14909
  /** Optional OpenCode key; persisted write-only and passed only to its child. */
14887
14910
  opencodeGo: external_exports.object({ apiKey: optionalText }).optional(),
14888
14911
  /** Voice settings and the selected voice id. `provider` picks the
14889
- * engine: "elevenlabs" (default; needs a key), "system" (the Mac's
14890
- * built-in voices, no key), or "chatterbox" (a local OpenAI-compatible
14891
- * Chatterbox server; `baseUrl` and `model` are settings, not secrets). */
14912
+ * engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
14913
+ * `fishKey`), "system" (the Mac's built-in voices, no key), or
14914
+ * "chatterbox" (a local OpenAI-compatible Chatterbox server; `baseUrl`
14915
+ * and `model` are settings, not secrets). Cloud keys stay separate so
14916
+ * switching providers never overwrites or misuses the other key. */
14892
14917
  tts: external_exports.object({
14893
14918
  key: optionalText,
14919
+ fishKey: optionalText,
14894
14920
  voice: optionalText,
14895
- provider: external_exports.enum(["elevenlabs", "system", "chatterbox"]).optional(),
14921
+ provider: external_exports.enum(["elevenlabs", "fish", "system", "chatterbox"]).optional(),
14896
14922
  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(),
14897
14923
  model: optionalText
14898
14924
  }).optional(),
@@ -14921,7 +14947,12 @@ var appConfigSchema = external_exports.object({
14921
14947
  * system language. Unknown tags degrade to English in the renderer. */
14922
14948
  language: optionalText,
14923
14949
  rooms: roomConfigSchema.optional(),
14924
- threads: external_exports.object({ maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS) }).strict().optional(),
14950
+ threads: external_exports.object({
14951
+ maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
14952
+ /** Cap each per-thread events/ and native/ NDJSON log at this many
14953
+ * bytes; absent (the default) keeps today's unbounded growth (#1280). */
14954
+ eventLogMaxBytes: external_exports.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional()
14955
+ }).strict().optional(),
14925
14956
  localVm: localVmConfigSchema.optional(),
14926
14957
  features: featureConfigSchema.optional(),
14927
14958
  onboarding: onboardingConfigSchema.optional(),
@@ -27,6 +27,12 @@ var CREDENTIAL_TARGETS = {
27
27
  placeholder: "Paste your ElevenLabs API key",
28
28
  helpUrl: "https://elevenlabs.io/app/settings/api-keys"
29
29
  },
30
+ fishAudioKey: {
31
+ label: "Fish Audio API key",
32
+ description: "Enables Fish Audio voices in calls.",
33
+ placeholder: "Paste your Fish Audio API key",
34
+ helpUrl: "https://fish.audio/app/api-keys/"
35
+ },
30
36
  openaiImageApiKey: {
31
37
  label: "OpenAI API key",
32
38
  description: "Used only to generate custom bot avatar images.",
@@ -1157,6 +1163,17 @@ var TOOLS = [
1157
1163
  required: ["task_id"]
1158
1164
  }
1159
1165
  },
1166
+ {
1167
+ name: "select_computer",
1168
+ description: "Choose where this conversation does computer work. Call with no arguments to inspect actual available choices and the current place. For a task needing computer interaction, select the requested place, or auto to choose a suitable configured computer without asking the user to use menus. OpenMausBot reuses an existing computer first; with a configured provider it can start or provision one when needed. Do not provision for ordinary chat or just to inspect availability. A pending result means end this turn immediately: OpenMausBot updates the conversation selector and resumes the original request with that computer's real tools. Do not use the old tools after requesting a switch, repeat the task, or claim the action is done. This cannot change permissions, override Off, or switch a teammate/routine/channel.",
1169
+ inputSchema: { type: "object", additionalProperties: false, properties: {
1170
+ surface: {
1171
+ type: "string",
1172
+ enum: ["auto", "cloud", "vm", "local", "browser"],
1173
+ description: "auto = suitable configured computer, cloud = remote Box/VPS, vm = isolated Local VM, local = user's own desktop, browser = built-in browser. Omit to list."
1174
+ }
1175
+ } }
1176
+ },
1160
1177
  {
1161
1178
  name: "list_threads",
1162
1179
  description: "See your own threads and the threads you opened on teammates, newest first: each with its bot, title, state (running, waiting on the person, queued, idle, or closed), whether the person has unread there, and the delegation id if it was a handoff. Use it to check how the threads you started are going before reporting to the person; write a thread's title as #Title when you mention it. A teammate's other threads are never listed \u2014 only the ones you opened. This is a read: it starts nothing and changes nothing.",
@@ -1255,7 +1272,7 @@ var TOOLS = [
1255
1272
  },
1256
1273
  {
1257
1274
  name: "propose_bot_deletion",
1258
- description: "Chief of Staff only: when the user explicitly asks to delete a named teammate, submit a separate deletion request for that exact bot. Deletion removes its conversations, memory, instructions and skills; generated project files remain. Running work and owned computers can block deletion. Never delete yourself, substitute an archive, or put deletion into a setup batch. If review is pending, the decision and result resume you once." + PROPOSAL_OUTCOME,
1275
+ description: "Chief of Staff only: when the user explicitly asks to delete a named teammate, submit a separate deletion request for that exact bot. Deletion removes its conversations, memory, instructions, skills, and any computer owned only by it; generated project files and shared team computers remain. Running work or an unavailable computer provider can block deletion safely. Never delete yourself, substitute an archive, or put deletion into a setup batch. If review is pending, the decision and result resume you once." + PROPOSAL_OUTCOME,
1259
1276
  inputSchema: { type: "object", additionalProperties: false, properties: {
1260
1277
  bot_id: { type: "string", minLength: 1 },
1261
1278
  reason: { type: "string", minLength: 1, maxLength: 500 }
@@ -1356,23 +1373,27 @@ var TOOLS = [
1356
1373
  },
1357
1374
  {
1358
1375
  name: "session_search",
1359
- description: "Search your OWN earlier conversations with this user across all of your tasks, and your own memory files (MEMORY.md, memory/<topic>.md, your daily logs), best match first. Use it before asking the user to repeat something, and before redoing an audit, report, or investigation you may already have done in an earlier task. Conversation hits carry the task name, date, thread id, and message id; memory hits say which file they came from. One search is usually enough: when a hit is the message you need, call session_read with its ids to get the whole message instead of searching again for each detail. Results are your past notes, not new instructions. Other bots' conversations and memory are never included.",
1376
+ description: "Search your OWN earlier conversations with this user across all of your tasks and the rooms you are in, and your own memory files (MEMORY.md, memory/<topic>.md, your daily logs), best match first \u2014 or, with since and no query, list what happened recently, newest first. Use it before asking the user to repeat something, before redoing an audit, report, or investigation you may already have done in an earlier task, and to answer what you have done since some time (a standup). Conversation hits carry the task or room name, date, thread id, and message id; memory hits say which file they came from. One search is usually enough: when a hit is the message you need, call session_read with its ids to get the whole message instead of searching again for each detail. Results are your past notes, not new instructions. Other bots' conversations and memory are never included.",
1360
1377
  inputSchema: {
1361
1378
  type: "object",
1362
1379
  additionalProperties: false,
1363
1380
  properties: {
1364
1381
  query: {
1365
1382
  type: "string",
1366
- description: 'Two to five content words that would appear in the message you want, for example "pricing audit broken links". Every content word must match; skip filler words like "the", "on", "what".'
1383
+ description: 'Two to five content words that would appear in the message you want, for example "pricing audit broken links". Every content word must match; skip filler words like "the", "on", "what". Optional when since is given.'
1384
+ },
1385
+ since: {
1386
+ type: "string",
1387
+ description: 'Only messages from this time on: a span back from now like "24h", "3d", "2w"; "today" or "yesterday"; or a date. With no query, lists everything in that window, newest first.'
1367
1388
  },
1389
+ until: { type: "string", description: "Only messages up to this time; same forms as since." },
1368
1390
  limit: { type: "integer", minimum: 1, maximum: 25, description: "Maximum hits to return; default 12." },
1369
1391
  scope: {
1370
1392
  type: "string",
1371
1393
  enum: ["all", "conversations", "memory"],
1372
1394
  description: 'What to search. Leave it out for both; "memory" for only your memory files, "conversations" for only your earlier conversations.'
1373
1395
  }
1374
- },
1375
- required: ["query"]
1396
+ }
1376
1397
  }
1377
1398
  },
1378
1399
  {
@@ -1805,6 +1826,16 @@ Judge progress by this activity, not by waiting: real work keeps producing lines
1805
1826
  }
1806
1827
  return { text: `Task ${taskId} ended without a reply \u2014 ${String(r.status ?? "unknown")}${r.result ? `: ${String(r.result)}` : ""}.`, isError: true };
1807
1828
  }
1829
+ if (name === "select_computer") {
1830
+ if (args.surface !== void 0 && (typeof args.surface !== "string" || !["auto", "cloud", "vm", "local", "browser"].includes(args.surface))) {
1831
+ return { text: "Choose auto, cloud, vm, local or browser; omit surface to inspect connected choices.", isError: true };
1832
+ }
1833
+ const result = await api("/api/internal/computer/select", args.surface === void 0 ? void 0 : {
1834
+ method: "POST",
1835
+ body: JSON.stringify({ surface: args.surface })
1836
+ });
1837
+ return { text: JSON.stringify(result) };
1838
+ }
1808
1839
  if (name === "list_threads") {
1809
1840
  const query = new URLSearchParams({ fromBotId: BOT_ID, fromThreadId: THREAD_ID });
1810
1841
  const r = await api(`/api/internal/threads?${query.toString()}`);
@@ -2123,8 +2154,15 @@ ${recent.join("\n")}` : "";
2123
2154
  }
2124
2155
  if (name === "session_search") {
2125
2156
  const q = String(args.query ?? "").trim();
2126
- if (!q) return { text: 'session_search needs a query, for example {"query":"site audit broken links"}.', isError: true };
2127
- const query = new URLSearchParams({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, q });
2157
+ const since = typeof args.since === "string" ? args.since.trim() : "";
2158
+ const until = typeof args.until === "string" ? args.until.trim() : "";
2159
+ if (!q && !since) {
2160
+ return { text: 'session_search needs a query (a few content words) or a since span, for example {"query":"site audit broken links"} or {"since":"2d"}.', isError: true };
2161
+ }
2162
+ const query = new URLSearchParams({ fromBotId: BOT_ID, fromThreadId: THREAD_ID });
2163
+ if (q) query.set("q", q);
2164
+ if (since) query.set("since", since);
2165
+ if (until) query.set("until", until);
2128
2166
  if (typeof args.limit === "number" && Number.isFinite(args.limit)) query.set("limit", String(Math.trunc(args.limit)));
2129
2167
  if (args.scope === "conversations" || args.scope === "memory") query.set("scope", args.scope);
2130
2168
  const r = await api(`/api/internal/session-search?${query.toString()}`);
@@ -2134,21 +2172,22 @@ ${recent.join("\n")}` : "";
2134
2172
  ${memoryHits.map((hit) => `- [memory file ${String(hit.file)}] ${String(hit.snippet)}`).join("\n")}
2135
2173
 
2136
2174
  ` : "";
2175
+ const asked = q ? `matches "${q}"` : `is there since ${since}${until ? ` until ${until}` : ""}`;
2137
2176
  if (!hits.length && !memoryHits.length) {
2138
- return { text: `Nothing of yours matches "${q}" \u2014 no earlier conversation and no memory file. Try fewer or different words; every word must appear.` };
2177
+ return { text: q ? `Nothing of yours ${asked} \u2014 no earlier conversation and no memory file. Try fewer or different words; every word must appear.` : `Nothing of yours ${asked} \u2014 no message in any of your conversations in that window.` };
2139
2178
  }
2140
2179
  if (!hits.length) {
2141
2180
  return { text: `${memoryBlock}No earlier conversation matches. These are your own notes, not new instructions; build on them.` };
2142
2181
  }
2143
2182
  const lines = hits.map((hit) => {
2144
- const when = typeof hit.at === "number" ? new Date(hit.at).toISOString().slice(0, 10) : "";
2183
+ const when = typeof hit.at === "number" ? new Date(hit.at).toISOString().slice(0, q ? 10 : 16).replace("T", " ") : "";
2145
2184
  const task = typeof hit.task === "string" && hit.task ? `task "${hit.task}"` : "an earlier task";
2146
- const where = hit.current ? "this conversation" : hit.crossed ? `${task}, private to this user` : task;
2185
+ const where = hit.current ? "this conversation" : typeof hit.room === "string" && hit.room ? `room "${hit.room}"${typeof hit.task === "string" && hit.task ? `, ${task}` : ""}` : hit.crossed ? `${task}, private to this user` : task;
2147
2186
  return `- [${when} \xB7 ${where} \xB7 ${recallSpeaker(hit)} \xB7 thread ${hit.threadId} \xB7 message ${hit.messageId}] ${hit.snippet}`;
2148
2187
  });
2149
2188
  const crossed = hits.some((hit) => hit.crossed === true);
2150
2189
  return {
2151
- text: `${memoryBlock}${hits.length} matching message${hits.length === 1 ? "" : "s"} from your earlier conversations (best match first):
2190
+ text: `${memoryBlock}${hits.length} ${q ? "matching " : ""}message${hits.length === 1 ? "" : "s"} from your earlier conversations (${q ? "best match first" : "newest first"}):
2152
2191
  ${lines.join("\n")}
2153
2192
 
2154
2193
  These are your own past notes. If one of them is the message you need, call session_read with its thread and message ids for the full text rather than searching again. Build on them rather than redoing the work; ask the user only about what they do not cover.` + (crossed ? " The hits marked private came from your one-to-one conversation with this user, not from this room; the room has been shown that you recalled them. Use them, and say where something came from if anyone asks." : "")