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
@@ -49,6 +49,8 @@ var MAX_CREATED_PER_TURN = 4;
49
49
  var createdThisTurn = 0;
50
50
  var MAX_ROOM_POSTS_PER_TURN = 3;
51
51
  var roomPostsThisTurn = 0;
52
+ var MAX_THREADS_PER_TURN = 5;
53
+ var threadsOpenedThisTurn = 0;
52
54
  var delegationTaskIdsThisTurn = /* @__PURE__ */ new Set();
53
55
  var WEEKDAYS = [
54
56
  "monday",
@@ -62,7 +64,7 @@ var WEEKDAYS = [
62
64
  var ROUTINE_SCHEDULE_SCHEMA = {
63
65
  type: "object",
64
66
  additionalProperties: false,
65
- description: 'Either {"type":"once","at":RFC3339} for one future run, {"type":"weekly","time":"HH:MM","weekdays":[...]} for chosen days, {"type":"daily","time":"HH:MM"} for every day, or {"type":"interval","every_minutes":15,"starts_at":RFC3339} to repeat from an optional starting point.',
67
+ description: 'Either {"type":"once","at":RFC3339} for one future run, {"type":"weekly","time":"HH:MM","weekdays":[...]} for chosen days, {"type":"daily","time":"HH:MM"} for every day, or {"type":"interval","every_minutes":15} to repeat. Intervals can optionally be limited with weekdays, window_start + window_end, and ends_at.',
66
68
  properties: {
67
69
  type: {
68
70
  type: "string",
@@ -80,7 +82,7 @@ var ROUTINE_SCHEDULE_SCHEMA = {
80
82
  weekdays: {
81
83
  type: "array",
82
84
  items: { type: "string", enum: WEEKDAYS },
83
- description: "Only for type weekly: which days the routine runs, in the computer's local timezone."
85
+ description: "For type weekly: required run days. For type interval: optional allowed days. Values use the computer's local timezone."
84
86
  },
85
87
  every_minutes: {
86
88
  type: "integer",
@@ -91,6 +93,30 @@ var ROUTINE_SCHEDULE_SCHEMA = {
91
93
  starts_at: {
92
94
  type: "string",
93
95
  description: "Optional for type interval: RFC3339 date-time with an explicit timezone offset that anchors the cadence. Omit to start one interval after confirmation."
96
+ },
97
+ window_start: {
98
+ type: "string",
99
+ description: "Optional for type interval, together with window_end: local 24-hour HH:MM when runs may begin, inclusive."
100
+ },
101
+ window_end: {
102
+ type: "string",
103
+ description: "Optional for type interval, together with window_start: local 24-hour HH:MM when the allowed window ends, exclusive. It must be later on the same day."
104
+ },
105
+ ends_at: {
106
+ type: "string",
107
+ description: "Optional for type interval: inclusive RFC3339 date-time cutoff with an explicit timezone offset."
108
+ },
109
+ every_day: {
110
+ type: "boolean",
111
+ description: "Only for an interval update: true removes an existing weekday restriction."
112
+ },
113
+ all_day: {
114
+ type: "boolean",
115
+ description: "Only for an interval update: true removes an existing time-window restriction."
116
+ },
117
+ never_ends: {
118
+ type: "boolean",
119
+ description: "Only for an interval update: true removes an existing end cutoff."
94
120
  }
95
121
  },
96
122
  required: ["type"]
@@ -107,7 +133,7 @@ var SHORT_WEEKDAYS = {
107
133
  sat: "saturday",
108
134
  sun: "sunday"
109
135
  };
110
- var SUPPORTED_SCHEDULES = 'Supported schedules: {"type":"once","at":"2026-09-01T09:00:00+05:30"} (future RFC3339 with explicit offset), {"type":"weekly","time":"09:00","weekdays":["monday","friday"]}, {"type":"daily","time":"09:00"}, or {"type":"interval","every_minutes":15}.';
136
+ var SUPPORTED_SCHEDULES = 'Supported schedules: {"type":"once","at":"2026-09-01T09:00:00+05:30"} (future RFC3339 with explicit offset), {"type":"weekly","time":"09:00","weekdays":["monday","friday"]}, {"type":"daily","time":"09:00"}, or {"type":"interval","every_minutes":15,"weekdays":["monday","friday"],"window_start":"09:00","window_end":"17:00"}.';
111
137
  function normalizeScheduleInput(args) {
112
138
  let raw = args.schedule;
113
139
  if (typeof raw === "string") {
@@ -119,7 +145,7 @@ function normalizeScheduleInput(args) {
119
145
  }
120
146
  if (!jsonRecord(raw)) return { error: `The schedule must be a JSON object. ${SUPPORTED_SCHEDULES}` };
121
147
  const type = typeof raw.type === "string" ? raw.type.trim().toLowerCase() : "";
122
- const fields = type === "once" ? ["type", "at"] : type === "weekly" || type === "daily" ? ["type", "time", "weekdays"] : type === "interval" ? ["type", "every_minutes", "everyMinutes", "starts_at", "anchorAt"] : null;
148
+ const fields = type === "once" ? ["type", "at"] : type === "weekly" || type === "daily" ? ["type", "time", "weekdays"] : type === "interval" ? ["type", "every_minutes", "everyMinutes", "starts_at", "anchorAt", "weekdays", "every_day", "window_start", "window_end", "window", "all_day", "ends_at", "endsAt", "never_ends"] : null;
123
149
  const unsupported = fields && Object.keys(raw).find((key) => raw[key] != null && !fields.includes(key));
124
150
  if (unsupported) {
125
151
  return { error: `Unsupported ${type} schedule field "${unsupported}". Weekly and daily times use the computer's timezone from list_routines. ${SUPPORTED_SCHEDULES}` };
@@ -152,6 +178,14 @@ function normalizeScheduleInput(args) {
152
178
  return { schedule: { type: "weekly", time, weekdays: normalized } };
153
179
  }
154
180
  if (type === "interval") {
181
+ for (const flag of ["every_day", "all_day", "never_ends"]) {
182
+ if (raw[flag] != null && typeof raw[flag] !== "boolean") {
183
+ return { error: `"${flag}" must be true or false.` };
184
+ }
185
+ }
186
+ if (raw.window != null && (!jsonRecord(raw.window) || Object.keys(raw.window).some((key) => key !== "start" && key !== "end") || typeof raw.window.start !== "string" || typeof raw.window.end !== "string")) {
187
+ return { error: '"window" must contain "start" and "end" in HH:MM, for example {"start":"09:00","end":"17:00"}.' };
188
+ }
155
189
  const rawMinutes = raw.every_minutes ?? raw.everyMinutes;
156
190
  const everyMinutes = Number(rawMinutes);
157
191
  if (!Number.isInteger(everyMinutes) || everyMinutes < 5 || everyMinutes > 1440) {
@@ -161,11 +195,59 @@ function normalizeScheduleInput(args) {
161
195
  if (rawStart !== void 0 && (typeof rawStart !== "string" || !rawStart.trim())) {
162
196
  return { error: '"starts_at" must be an RFC3339 date-time with an explicit timezone offset.' };
163
197
  }
198
+ if (raw.every_day === true && Array.isArray(raw.weekdays) && raw.weekdays.length > 0) {
199
+ return { error: 'Choose interval "weekdays" or "every_day", not both.' };
200
+ }
201
+ let intervalWeekdays;
202
+ if (raw.every_day === true) {
203
+ intervalWeekdays = null;
204
+ } else if (raw.weekdays !== void 0) {
205
+ if (!Array.isArray(raw.weekdays) || raw.weekdays.length === 0) {
206
+ return { error: 'Interval "weekdays" must contain at least one full weekday name.' };
207
+ }
208
+ intervalWeekdays = [];
209
+ for (const day of raw.weekdays) {
210
+ const lower = String(day).trim().toLowerCase();
211
+ const full = WEEKDAYS.includes(lower) ? lower : Object.hasOwn(SHORT_WEEKDAYS, lower) ? SHORT_WEEKDAYS[lower] : void 0;
212
+ if (!full) return { error: `Unsupported weekday "${String(day)}". Use full names: ${WEEKDAYS.join(", ")}.` };
213
+ if (!intervalWeekdays.includes(full)) intervalWeekdays.push(full);
214
+ }
215
+ }
216
+ const rawWindow = jsonRecord(raw.window) ? raw.window : void 0;
217
+ const windowStart = raw.window_start ?? rawWindow?.start;
218
+ const windowEnd = raw.window_end ?? rawWindow?.end;
219
+ if (raw.all_day === true && (windowStart !== void 0 || windowEnd !== void 0)) {
220
+ return { error: 'Choose window_start + window_end or "all_day", not both.' };
221
+ }
222
+ let window;
223
+ if (raw.all_day === true) {
224
+ window = null;
225
+ } else if (windowStart !== void 0 || windowEnd !== void 0) {
226
+ if (typeof windowStart !== "string" || !windowStart.trim() || typeof windowEnd !== "string" || !windowEnd.trim()) {
227
+ return { error: 'An interval time window needs both "window_start" and "window_end" in 24-hour HH:MM.' };
228
+ }
229
+ window = { start: windowStart.trim(), end: windowEnd.trim() };
230
+ }
231
+ const rawEnd = raw.ends_at ?? raw.endsAt;
232
+ if (raw.never_ends === true && rawEnd !== void 0) {
233
+ return { error: 'Choose "ends_at" or "never_ends", not both.' };
234
+ }
235
+ let endsAt;
236
+ if (raw.never_ends === true) endsAt = null;
237
+ else if (rawEnd !== void 0) {
238
+ if (typeof rawEnd !== "string" || !rawEnd.trim()) {
239
+ return { error: '"ends_at" must be an RFC3339 date-time with an explicit timezone offset.' };
240
+ }
241
+ endsAt = rawEnd.trim();
242
+ }
164
243
  return {
165
244
  schedule: {
166
245
  type: "interval",
167
246
  everyMinutes,
168
- ...typeof rawStart === "string" ? { anchorAt: rawStart.trim() } : {}
247
+ ...typeof rawStart === "string" ? { anchorAt: rawStart.trim() } : {},
248
+ ...intervalWeekdays !== void 0 ? { weekdays: intervalWeekdays } : {},
249
+ ...window !== void 0 ? { window } : {},
250
+ ...endsAt !== void 0 ? { endsAt } : {}
169
251
  }
170
252
  };
171
253
  }
@@ -262,6 +344,36 @@ var TOOLS = [
262
344
  required: ["task_id"]
263
345
  }
264
346
  },
347
+ {
348
+ name: "list_threads",
349
+ 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, or idle), 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.",
350
+ inputSchema: { type: "object", additionalProperties: false, properties: {} }
351
+ },
352
+ {
353
+ name: "close_thread",
354
+ description: "Mark a thread you opened (or one of your own) as finished once you have read its result: it goes idle in the person's sidebar with a note saying you closed it. Nothing is deleted \u2014 deleting stays the person's decision \u2014 and a thread that is still running cannot be closed; wait for it or leave it. Use the thread id from list_threads or from the start_thread result. If a close is refused, do not retry it.",
355
+ inputSchema: {
356
+ type: "object",
357
+ additionalProperties: false,
358
+ properties: { thread_id: { type: "string", description: "The thread id from list_threads or start_thread." } },
359
+ required: ["thread_id"]
360
+ }
361
+ },
362
+ {
363
+ name: "start_thread",
364
+ description: `Open a new thread: one conversation with its own history and its own run, shown to the person as a row under the bot it belongs to. Leave bot_id out to open it on yourself, for a separate job that should run on its own ("review each pull request" \u2014 one thread per pull request) instead of inside this conversation. Give bot_id (from list_bots) to open it on a teammate: that is a handoff into a fresh thread, which starts after your current turn ends and whose result is delivered here, like delegate_bot. The title becomes the row's name, so make it short and specific; write it as #Title when you mention it to the person. Do not use it for a question you need answered right now (ask_bot), for one task where the teammate's usual conversation is fine (delegate_bot), or for a note nobody has to act on. If a call is refused, do not retry it: say what you still wanted opened.`,
365
+ inputSchema: {
366
+ type: "object",
367
+ additionalProperties: false,
368
+ properties: {
369
+ title: { type: "string", description: `The thread's name: one short line, at most 80 characters, specific enough to tell it apart from the others (for example "QA: PR #412 login fix").` },
370
+ message: { type: "string", description: "The complete first message of the thread \u2014 everything the run needs, since it will not see this conversation." },
371
+ bot_id: { type: "string", description: "Optional: the teammate's id from list_bots. Leave out to open the thread on yourself." },
372
+ folder: { type: "string", description: "Optional: the name of one of that bot's existing folders to file the thread under. Leave out unless the person named one." }
373
+ },
374
+ required: ["title", "message"]
375
+ }
376
+ },
265
377
  {
266
378
  name: "post_to_room",
267
379
  description: "Put one message into a shared room you belong to, for example when the user asks you to tell the team something. Get group_id from list_rooms. This posts and returns: no room member's turn starts, nobody replies, and nothing comes back except confirmation \u2014 so never use it to ask a question or hand out work (use ask_bot or delegate_bot for those). Post once, say it in full, and tell the user what you posted. If a post is refused, do not retry it: say what you wanted to post in your reply instead.",
@@ -472,6 +584,12 @@ async function api(path, init) {
472
584
  if (!res.ok) throw new Error(String(body.error ?? `HTTP ${res.status}`));
473
585
  return body;
474
586
  }
587
+ function ordinal(n) {
588
+ const rem100 = n % 100;
589
+ if (rem100 >= 11 && rem100 <= 13) return `${n}th`;
590
+ const rem10 = n % 10;
591
+ return `${n}${rem10 === 1 ? "st" : rem10 === 2 ? "nd" : rem10 === 3 ? "rd" : "th"}`;
592
+ }
475
593
  function jsonRecord(value) {
476
594
  return value !== null && typeof value === "object" && !Array.isArray(value);
477
595
  }
@@ -680,6 +798,71 @@ Judge progress by this activity, not by waiting: real work keeps producing lines
680
798
  }
681
799
  return { text: `Task ${taskId} ended without a reply \u2014 ${String(r.status ?? "unknown")}${r.result ? `: ${String(r.result)}` : ""}.`, isError: true };
682
800
  }
801
+ if (name === "list_threads") {
802
+ const query = new URLSearchParams({ fromBotId: BOT_ID, fromThreadId: THREAD_ID });
803
+ const r = await api(`/api/internal/threads?${query.toString()}`);
804
+ if (r.error) return { text: `Couldn't list threads: ${String(r.error)}`, isError: true };
805
+ const threads = Array.isArray(r.threads) ? r.threads.filter(jsonRecord) : [];
806
+ if (!threads.length) return { text: "No threads yet: you have none of your own beyond this one, and you have not opened any on a teammate." };
807
+ const stateWord = { running: "running", "waiting-on-you": "waiting on the person", queued: "queued", idle: "idle" };
808
+ const lines = threads.map((thread) => {
809
+ const where = thread.own === true ? "yours" : `on @${String(thread.botName)}`;
810
+ const state = stateWord[String(thread.state)] ?? String(thread.state);
811
+ const unread = thread.unread === true ? ", unread for the person" : "";
812
+ const handoff = typeof thread.delegationId === "string" && thread.delegationId ? ` [delegation id: ${thread.delegationId}]` : "";
813
+ return `- #${String(thread.title)} (${where}, ${state}${unread}) [thread id: ${String(thread.threadId)}]${handoff}`;
814
+ });
815
+ return { text: `Threads, newest first:
816
+ ${lines.join("\n")}` };
817
+ }
818
+ if (name === "close_thread") {
819
+ const threadId = String(args.thread_id ?? "").trim();
820
+ if (!threadId) return { text: "close_thread needs the thread_id from list_threads or start_thread.", isError: true };
821
+ const r = await api(`/api/internal/threads/${encodeURIComponent(threadId)}/close`, { method: "POST", body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID }) });
822
+ if (r.error) return { text: `Couldn't close that thread: ${String(r.error)}`, isError: true };
823
+ return { text: `Closed #${String(r.title)}${r.botName ? ` on @${String(r.botName)}` : ""}. It stays in the person's sidebar, idle, with a note that you closed it.` };
824
+ }
825
+ if (name === "start_thread") {
826
+ const title = String(args.title ?? "").trim();
827
+ const message = String(args.message ?? "").trim();
828
+ if (!title || !message) return { text: "start_thread needs title (one short line) and message (the complete first message).", isError: true };
829
+ if (threadsOpenedThisTurn >= MAX_THREADS_PER_TURN) {
830
+ return {
831
+ text: `You have already opened ${MAX_THREADS_PER_TURN} threads this turn, which is the limit. Do not retry \u2014 finish your turn and tell the person which threads you still wanted to open, so they can open them or ask you again.`,
832
+ isError: true
833
+ };
834
+ }
835
+ const toBotId = typeof args.bot_id === "string" ? args.bot_id.trim() : "";
836
+ const folder = typeof args.folder === "string" ? args.folder.trim() : "";
837
+ const body = { fromBotId: BOT_ID, fromThreadId: THREAD_ID, title, message, depth: DEPTH };
838
+ if (toBotId) body.toBotId = toBotId;
839
+ if (folder) body.folder = folder;
840
+ const r = await api("/api/internal/threads", { method: "POST", body: JSON.stringify(body) });
841
+ if (r.error && r.state !== "failed") return { text: `Couldn't open that thread: ${String(r.error)}`, isError: true };
842
+ threadsOpenedThisTurn += 1;
843
+ const threadTitle = String(r.title ?? title);
844
+ const threadId = String(r.threadId ?? "");
845
+ const where = r.self === true ? "on yourself" : `on @${String(r.botName ?? "that bot")}`;
846
+ const opened = `Opened thread #${threadTitle} ${where} [thread id: ${threadId}].`;
847
+ if (r.self === true) {
848
+ if (r.state === "running") {
849
+ return { text: `${opened} It is running now, in parallel with this conversation, and its result stays in that thread \u2014 it will not be delivered here. Mention it to the person as #${threadTitle}; use list_threads in a later turn to see how it is going.` };
850
+ }
851
+ if (r.state === "queued") {
852
+ const position = Number(r.position) || 1;
853
+ const limit = Number(r.limit) || 0;
854
+ return { text: `${opened} You are at your limit of ${limit} threads running at once, so it is ${ordinal(position)} in line and starts as soon as one of them finishes \u2014 nothing more to do. Mention it to the person as #${threadTitle}.` };
855
+ }
856
+ return { text: `${opened} It could not start: ${String(r.error ?? "unknown reason")}. The thread exists but nothing is running in it; tell the person.`, isError: true };
857
+ }
858
+ const delegationId = typeof r.delegationId === "string" ? r.delegationId.trim() : "";
859
+ if (delegationId) delegationTaskIdsThisTurn.add(delegationId);
860
+ const approval = r.approvalRequired === true ? " The person must approve this handoff first; their card appears after your turn ends." : "";
861
+ const timing = r.state === "queued" ? ` @${String(r.botName ?? "that bot")} can run ${Number(r.limit) || 0} threads at once and they are all spoken for, so it waits ${ordinal(Number(r.position) || 1)} in line for a free slot after this turn ends.` : " It starts when this turn ends, like any handoff.";
862
+ return {
863
+ text: `${opened}${timing}${approval} Its result will be delivered to this conversation automatically (delegation id: ${delegationId || "unknown"}). Acknowledge it, mention it to the person as #${threadTitle}, and finish your turn; do not check or wait for it in this turn.`
864
+ };
865
+ }
683
866
  if (name === "create_bot") {
684
867
  const botName = String(args.name ?? "").trim();
685
868
  const role = String(args.role ?? "").trim();