openmausbot 0.1.68 → 0.1.70

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 (41) hide show
  1. package/dist/assets/index-B2F__IRS.css +1 -0
  2. package/dist/assets/index-CLj3t5Ba.js +280 -0
  3. package/dist/assets/{index-HqooKFTS.js → index-D1RGhLUK.js} +1 -1
  4. package/dist/index.html +2 -2
  5. package/dist-server/container-mcp.js +40 -2
  6. package/dist-server/drivers/agents-proxy.js +188 -5
  7. package/dist-server/index.js +2431 -984
  8. package/dist-server/local-computer-proxy.js +2 -0
  9. package/dist-server/openmausbot.js +923 -334
  10. package/dist-server/pair-cli.js +923 -334
  11. package/dist-server/prepare-cloudflared.js +20 -3
  12. package/dist-server/server/avatar-image.js +86 -34
  13. package/dist-server/server/bot-package.js +60 -14
  14. package/dist-server/server/config.js +23 -2
  15. package/dist-server/server/delegations.js +132 -74
  16. package/dist-server/server/drivers/acp/qwen.js +142 -5
  17. package/dist-server/server/drivers/agents-proxy.js +224 -4
  18. package/dist-server/server/drivers/claude-login-auth.js +102 -9
  19. package/dist-server/server/drivers/claude.js +11 -5
  20. package/dist-server/server/drivers/codex-device-auth.js +102 -3
  21. package/dist-server/server/drivers/codex-identity.js +115 -0
  22. package/dist-server/server/drivers/codex.js +6 -0
  23. package/dist-server/server/engine-install.js +137 -0
  24. package/dist-server/server/env-path.js +12 -0
  25. package/dist-server/server/harness/registry.js +33 -6
  26. package/dist-server/server/index.js +365 -26
  27. package/dist-server/server/package-export.js +7 -0
  28. package/dist-server/server/peer-provenance.js +6 -2
  29. package/dist-server/server/provider-auth-sessions.js +24 -0
  30. package/dist-server/server/routine-requests.js +270 -25
  31. package/dist-server/server/routines.js +297 -69
  32. package/dist-server/server/steer-queue.js +48 -1
  33. package/dist-server/server/store.js +22 -4
  34. package/dist-server/server/system-prompt.js +1 -0
  35. package/dist-server/server/team-backup.js +18 -4
  36. package/dist-server/shared/image-generation.js +28 -0
  37. package/dist-server/shared/team-backup.js +1 -0
  38. package/dist-server/vps-container-mcp.js +40 -2
  39. package/package.json +1 -1
  40. package/dist/assets/index-CbaejjQA.js +0 -278
  41. package/dist/assets/index-aJ5EWZOQ.css +0 -1
@@ -26,6 +26,7 @@ export function isProjectEmoji(value) {
26
26
  const TASK_PATCH_FIELDS = [
27
27
  "title", "projectId", "modelSelection", "approvalMode", "autoApprove", "alwaysAllow",
28
28
  "unread", "rewound", "pinnedMessageId", "resumeCursors", "lastInstanceId", "cwd",
29
+ "routineRunId",
29
30
  ];
30
31
  /** Everything the BOT authored is scrubbed of content-shaped secrets before
31
32
  * it is stored: its reply text, a tool title (an ACP engine's title can be
@@ -1444,7 +1445,7 @@ export class Store {
1444
1445
  }
1445
1446
  /** A fresh context on the same bot: new thread, new session, same
1446
1447
  * persona/tools/computer. Becomes the active task. */
1447
- createTask(botId, title, activate = true, projectId) {
1448
+ createTask(botId, title, activate = true, projectId, openedBy) {
1448
1449
  const bot = this.bot(botId);
1449
1450
  if (!bot)
1450
1451
  return null;
@@ -1455,6 +1456,7 @@ export class Store {
1455
1456
  title: title?.trim().slice(0, 80) || UNTITLED_THREAD,
1456
1457
  createdAt: Date.now(),
1457
1458
  ...(projectId ? { projectId } : {}),
1459
+ ...(openedBy ? { openedBy: structuredClone(openedBy) } : {}),
1458
1460
  resumeCursors: {},
1459
1461
  modelSelection: structuredClone(bot.modelSelection),
1460
1462
  approvalMode: approvalModeFor(bot),
@@ -1472,6 +1474,20 @@ export class Store {
1472
1474
  this.emit({ type: "bot", botId });
1473
1475
  return task;
1474
1476
  }
1477
+ /** Attach (or complete) the opener record after the thread exists — the
1478
+ * handoff id is only known once the thread it targets has an id, so a
1479
+ * peer-opened thread is created first and stamped second. Never reachable
1480
+ * from the HTTP task PATCH: openedBy is not a TASK_PATCH_FIELD. */
1481
+ setTaskOpenedBy(botId, threadId, openedBy) {
1482
+ const bot = this.bot(botId);
1483
+ const task = this.taskByThread(botId, threadId);
1484
+ if (!bot || !task)
1485
+ return null;
1486
+ task.openedBy = structuredClone(openedBy);
1487
+ this.saveBots();
1488
+ this.emit({ type: "bot", botId });
1489
+ return task;
1490
+ }
1475
1491
  switchTask(botId, threadId) {
1476
1492
  const bot = this.bot(botId);
1477
1493
  const task = bot?.tasks?.find((t) => t.threadId === threadId);
@@ -1503,10 +1519,12 @@ export class Store {
1503
1519
  if (!bot.tasks.some((t) => t.threadId === threadId))
1504
1520
  return null;
1505
1521
  bot.tasks = bot.tasks.filter((t) => t.threadId !== threadId);
1506
- this.deleteThreadRecord(threadId);
1507
- if (bot.threadId === threadId) {
1508
- this.mirrorActiveTask(bot, bot.tasks[0]);
1522
+ const visible = bot.tasks.find((task) => !task.routineRunId)
1523
+ ?? this.createTask(botId, undefined, bot.threadId === threadId);
1524
+ if (bot.threadId === threadId || this.taskByThread(botId, bot.threadId)?.routineRunId) {
1525
+ this.mirrorActiveTask(bot, visible);
1509
1526
  }
1527
+ this.deleteThreadRecord(threadId);
1510
1528
  bot.unread = bot.tasks.some((task) => task.unread);
1511
1529
  this.refreshBotActivity(bot);
1512
1530
  this.saveBots();
@@ -36,6 +36,7 @@ export function computerPrompt(kind) {
36
36
  }
37
37
  export const COMPOSIO_PROMPT = " The user's connected apps (Gmail, Calendar, Slack, Notion, and the rest) are reachable through the composio tools — find the right one with COMPOSIO_SEARCH_TOOLS, read its arguments with COMPOSIO_GET_TOOL_SCHEMAS, then run it with COMPOSIO_MULTI_EXECUTE_TOOL. Reach for them before telling the user you have no access to a service.";
38
38
  export const CREDENTIAL_PROMPT = " If a supported API key is missing, use request_credential to create a secure credential request. A freshly QR-paired mobile app or the desktop app can show the secure entry card. Never claim it opened unless the request succeeded, and never ask the user to paste credentials into chat.";
39
+ export const THREADS_PROMPT = " A thread is one conversation with its own history and its own run; a bot can have several running at once, and the person sees them as rows under that bot. Use start_thread to open one on yourself for separate work, or on a teammate to hand them a job that should run on its own. Use list_threads to see how the ones you opened are going. When you mention a thread to the person, write its title as #Title so it links. Do not use a ticket comment, a note, or a room post as a stand-in for a thread.";
39
40
  export const ROUTINE_PROMPT = " If the user explicitly asks to list or review, schedule, run, or change routines, use list_routines and propose_routine or propose_routine_action. A proposal is not applied until the user confirms its in-app card, so never claim the action completed before that confirmation.";
40
41
  export const ROUTINE_EXECUTION_PROMPT = " Execute this routine now: use available peer tools for required handoffs rather than merely announcing that you will wait; after an accepted delegation, end this turn for automatic resumption, and report a concrete blocker if no handoff is possible.";
41
42
  export const LEARN_PROMPT = " If the user sends /learn or asks you to save a reusable procedure from this work, use skills_list and skill_manage. Create new skills; update an existing learned skill only when the user explicitly asks to revise that exact name. Include source provenance and wait for the review card decision.";
@@ -29,6 +29,11 @@ export function createTeamBackup(store, routines, name) {
29
29
  const tasks = record.tasks?.length ? record.tasks : [{ threadId: record.threadId, title: "Conversation", createdAt: record.createdAt }];
30
30
  return tasks.map((task) => ({
31
31
  key: task.threadId, title: task.title, createdAt: task.createdAt,
32
+ // Who opened the thread travels; the handoff id does not — the
33
+ // delegation ledger is process-local and never part of a backup.
34
+ openedBy: "openedBy" in task && task.openedBy
35
+ ? { botId: task.openedBy.botId, name: task.openedBy.name, at: task.openedBy.at }
36
+ : undefined,
32
37
  activeLeafId: store.activeLeaf(task.threadId),
33
38
  messages: store.messagesFor(task.threadId).map((message) => ({
34
39
  id: message.id, role: message.role, text: messageText(message), at: message.at,
@@ -131,10 +136,19 @@ export function importTeamBackup(store, routines, input, selection) {
131
136
  }
132
137
  for (const source of backup.bots) {
133
138
  const bot = store.bot(botIds.get(source.key));
134
- const tasks = source.tasks.map((task, i) => ({
135
- threadId: i === 0 ? bot.threadId : newId(), title: task.title, createdAt: task.createdAt, resumeCursors: {},
136
- modelSelection: structuredClone(selection), activity: "idle", busy: false, unread: false,
137
- }));
139
+ const tasks = source.tasks.map((task, i) => {
140
+ const record = {
141
+ threadId: i === 0 ? bot.threadId : newId(), title: task.title, createdAt: task.createdAt, resumeCursors: {},
142
+ modelSelection: structuredClone(selection), activity: "idle", busy: false, unread: false,
143
+ };
144
+ // Same rule as a message's `from`: the opener is remapped to its
145
+ // imported twin, and an opener outside this backup leaves no record
146
+ // rather than a bot id that resolves to a stranger.
147
+ const opener = task.openedBy && botIds.get(task.openedBy.botId);
148
+ if (task.openedBy && opener)
149
+ record.openedBy = { botId: opener, name: task.openedBy.name, at: task.openedBy.at };
150
+ return record;
151
+ });
138
152
  // Own the task IDs before writing their transcripts, so rollback also
139
153
  // removes partially imported history if persistence fails midway.
140
154
  store.patchBot(bot.id, { tasks, threadId: tasks[source.tasks.findIndex((task) => task.key === source.activeTask)].threadId });
@@ -0,0 +1,28 @@
1
+ /** A deliberately configured Images API endpoint, not a provider-returned URL.
2
+ * Local HTTP is useful for self-hosted routers; internet endpoints require TLS. */
3
+ export function normalizeImageGenerationUrl(value) {
4
+ const message = "Use an HTTPS API base URL, or HTTP for a local router, without passwords, query parameters or fragments";
5
+ let url;
6
+ try {
7
+ url = new URL(value.trim());
8
+ }
9
+ catch {
10
+ throw new Error(message);
11
+ }
12
+ if (value.length > 2048 || url.username || url.password || url.search || url.hash)
13
+ throw new Error(message);
14
+ const host = url.hostname.toLowerCase();
15
+ const ipv4 = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(host);
16
+ const local = host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")
17
+ || host === "host.docker.internal" || host === "host.containers.internal"
18
+ || host === "[::1]" || /^\[f[cd][0-9a-f]{2}:/.test(host)
19
+ || (ipv4 && (Number(ipv4[1]) === 127 || Number(ipv4[1]) === 10
20
+ || (Number(ipv4[1]) === 192 && Number(ipv4[2]) === 168)
21
+ || (Number(ipv4[1]) === 172 && Number(ipv4[2]) >= 16 && Number(ipv4[2]) <= 31)));
22
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && local))
23
+ throw new Error(message);
24
+ if (ipv4 && (Number(ipv4[1]) === 0 || (Number(ipv4[1]) === 169 && Number(ipv4[2]) === 254)))
25
+ throw new Error(message);
26
+ url.pathname = url.pathname.replace(/\/+$/, "").replace(/\/images\/generations$/, "");
27
+ return url.toString().replace(/\/+$/, "");
28
+ }
@@ -20,6 +20,7 @@ const task = z.object({
20
20
  key,
21
21
  title: z.string().max(2_000),
22
22
  createdAt: timestamp,
23
+ openedBy: z.object({ botId: key, name, at: timestamp }).optional(),
23
24
  activeLeafId: key.nullable(),
24
25
  messages: z.array(message).max(100_000),
25
26
  });
@@ -141,9 +141,11 @@ function windowsKnownDirs() {
141
141
  var cached = null;
142
142
  var probed = false;
143
143
  var loginShellPath = null;
144
+ var registeredDirs = [];
144
145
  function augmentedPath() {
145
146
  if (cached === null) {
146
147
  cached = mergePaths([
148
+ ...registeredDirs.filter((d) => existsSync(d)),
147
149
  ...process.env.OMB_EXTRA_PATH ? process.env.OMB_EXTRA_PATH.split(delimiter) : [],
148
150
  ...process.env.PATH ? process.env.PATH.split(delimiter) : [],
149
151
  // Keep the last successful login-shell result while a rescan starts a
@@ -14933,6 +14935,25 @@ function date4(params) {
14933
14935
  // node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
14934
14936
  config(en_default());
14935
14937
 
14938
+ // shared/image-generation.ts
14939
+ function normalizeImageGenerationUrl(value) {
14940
+ const message = "Use an HTTPS API base URL, or HTTP for a local router, without passwords, query parameters or fragments";
14941
+ let url2;
14942
+ try {
14943
+ url2 = new URL(value.trim());
14944
+ } catch {
14945
+ throw new Error(message);
14946
+ }
14947
+ if (value.length > 2048 || url2.username || url2.password || url2.search || url2.hash) throw new Error(message);
14948
+ const host = url2.hostname.toLowerCase();
14949
+ const ipv43 = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(host);
14950
+ const local = host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host === "host.docker.internal" || host === "host.containers.internal" || host === "[::1]" || /^\[f[cd][0-9a-f]{2}:/.test(host) || ipv43 && (Number(ipv43[1]) === 127 || Number(ipv43[1]) === 10 || Number(ipv43[1]) === 192 && Number(ipv43[2]) === 168 || Number(ipv43[1]) === 172 && Number(ipv43[2]) >= 16 && Number(ipv43[2]) <= 31);
14951
+ if (url2.protocol !== "https:" && !(url2.protocol === "http:" && local)) throw new Error(message);
14952
+ if (ipv43 && (Number(ipv43[1]) === 0 || Number(ipv43[1]) === 169 && Number(ipv43[2]) === 254)) throw new Error(message);
14953
+ url2.pathname = url2.pathname.replace(/\/+$/, "").replace(/\/images\/generations$/, "");
14954
+ return url2.toString().replace(/\/+$/, "");
14955
+ }
14956
+
14936
14957
  // server/contracts.ts
14937
14958
  var EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
14938
14959
 
@@ -15133,8 +15154,25 @@ var appConfigSchema = external_exports.object({
15133
15154
  * engine: "elevenlabs" (default; needs a key) or "system" (the Mac's
15134
15155
  * built-in voices, no key). */
15135
15156
  tts: external_exports.object({ key: optionalText, voice: optionalText, provider: external_exports.enum(["elevenlabs", "system"]).optional() }).optional(),
15136
- /** OpenAI key used only by the in-process avatar image generator. */
15137
- imageGen: external_exports.object({ key: optionalText }).optional(),
15157
+ /** Avatar provider credentials stay separate; choosing a router never reuses a cloud key. */
15158
+ imageGen: external_exports.object({
15159
+ provider: external_exports.enum(["openai", "xai", "custom"]).optional(),
15160
+ key: optionalText,
15161
+ customApiKey: optionalText,
15162
+ customUrl: external_exports.string().trim().max(2048).transform((value, ctx) => {
15163
+ if (!value) return "";
15164
+ try {
15165
+ return normalizeImageGenerationUrl(value);
15166
+ } catch (error51) {
15167
+ ctx.addIssue({ code: "custom", message: error51.message });
15168
+ return external_exports.NEVER;
15169
+ }
15170
+ }).optional(),
15171
+ customModel: external_exports.string().trim().max(200).refine(
15172
+ (value) => !["\r", "\n", "\0"].some((character) => value.includes(character)),
15173
+ "Use a model ID without control characters"
15174
+ ).optional()
15175
+ }).optional(),
15138
15176
  /** Non-secret profile details shown in the sidebar. */
15139
15177
  profile: external_exports.object({ name: optionalText, email: optionalText }).optional(),
15140
15178
  /** UI language override (BCP-47, lowercase). Empty/absent = follow the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmausbot",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
4
4
  "description": "Run the OpenMausBot server anywhere and pair your devices to it",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",