openmausbot 0.1.78 → 0.1.80

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/dist/assets/index-Cu8BkIfo.js +305 -0
  2. package/dist/assets/{index-BqDJqf2C.js → index-D6RZKWks.js} +1 -1
  3. package/dist/assets/index-tfpeJAIG.css +1 -0
  4. package/dist/index.html +2 -2
  5. package/dist-server/container-mcp.js +11 -5
  6. package/dist-server/drivers/agents-proxy.js +21 -6
  7. package/dist-server/enterprise/server/index.js +0 -1
  8. package/dist-server/index.js +15942 -6859
  9. package/dist-server/local-computer.js +0 -1
  10. package/dist-server/mcp-server.js +8 -1
  11. package/dist-server/openmausbot.js +9303 -786
  12. package/dist-server/pair-cli.js +9303 -786
  13. package/dist-server/proxy-paths.js +0 -1
  14. package/dist-server/server/box.js +98 -41
  15. package/dist-server/server/browser-engine.js +1 -1
  16. package/dist-server/server/browser-runtime.js +11 -3
  17. package/dist-server/server/browser-tool-shape.js +72 -0
  18. package/dist-server/server/claude-accounts.js +1 -0
  19. package/dist-server/server/cli-setup.js +1 -1
  20. package/dist-server/server/composio.js +18 -0
  21. package/dist-server/server/config.js +16 -4
  22. package/dist-server/server/container-computer.js +0 -12
  23. package/dist-server/server/drivers/acp/core.js +4 -19
  24. package/dist-server/server/drivers/agents-proxy.js +27 -2
  25. package/dist-server/server/drivers/boxagent.js +48 -4
  26. package/dist-server/server/drivers/chat-mcp-tools.js +368 -0
  27. package/dist-server/server/drivers/chat-tool-approval.js +42 -0
  28. package/dist-server/server/drivers/claude.js +86 -38
  29. package/dist-server/server/drivers/codex.js +225 -75
  30. package/dist-server/server/drivers/grok.js +4 -0
  31. package/dist-server/server/drivers/minimax.js +11 -3
  32. package/dist-server/server/drivers/openai-chat-protocol.js +92 -0
  33. package/dist-server/server/drivers/openai-chat.js +332 -88
  34. package/dist-server/server/drivers/openai-compat.js +4 -0
  35. package/dist-server/server/drivers/pi.js +1 -9
  36. package/dist-server/server/harness/registry.js +2 -2
  37. package/dist-server/server/index.js +267 -50
  38. package/dist-server/server/managed-desktop.js +205 -0
  39. package/dist-server/server/model-context-window.js +20 -0
  40. package/dist-server/server/proxy-paths.js +0 -1
  41. package/dist-server/server/redact.js +1 -0
  42. package/dist-server/server/room-handoffs.js +57 -9
  43. package/dist-server/server/store.js +13 -1
  44. package/dist-server/server/tts/chatterbox.js +83 -0
  45. package/dist-server/server/tts/index.js +45 -12
  46. package/dist-server/server/workspace.js +12 -2
  47. package/dist-server/shared/ask-question.js +28 -0
  48. package/dist-server/shared/computer-contention.js +17 -0
  49. package/dist-server/vps-container-mcp.js +11 -5
  50. package/enterprise/server/index.js +0 -1
  51. package/package.json +1 -1
  52. package/dist/assets/index-DjwAroIZ.js +0 -305
  53. package/dist/assets/index-Pbb6Ao0s.css +0 -1
  54. package/dist-server/computer-proxy.js +0 -1196
  55. package/dist-server/server/computer-proxy.js +0 -1070
  56. package/dist-server/server/remote-computer.js +0 -170
@@ -18,7 +18,6 @@ import { augmentedPath } from "../env-path.js";
18
18
  import { brokerSocketPath, describeSpawnFailure, execCli, killCliTree, spawnCli } from "../procs.js";
19
19
  import { classifyResumeFailure, mayReplay, recoveryPromptFor } from "../resume-recovery.js";
20
20
  import { ClaudeLoginController } from "./claude-login-auth.js";
21
- import { computerProxyEnv } from "../container-computer.js";
22
21
  import { gateServer, resultBudget } from "../mcp-gate-config.js";
23
22
  import { newEventId, newId } from "../contracts.js";
24
23
  import { askInputSummary, commandSummary, toolDetailPreview } from "../tool-summary.js";
@@ -26,7 +25,7 @@ import { classifyError, computeBackoff, interruptibleDelay, RETRY_MAX_ATTEMPTS }
26
25
  import { applyClaudeInject, decodeInjectId, mergeLocalInject, probeLocalInjects, resolveInjectId, } from "./local-inject.js";
27
26
  import { appendNative } from "./native.js";
28
27
  import { SPAWNED_PROXIES } from "../proxy-paths.js";
29
- import { ASK_USER_QUESTION_TOOL, askQuestionSummary, parseAskQuestions, questionChoices, } from "../../shared/ask-question.js";
28
+ import { ASK_USER_QUESTION_TOOL, askQuestionSummary, parseAskQuestions, parseChoices, questionChoices, } from "../../shared/ask-question.js";
30
29
  /** Whether `claude` has been signed in.
31
30
  *
32
31
  * Credential storage is deliberately not inspected here. Claude Code uses the
@@ -244,11 +243,14 @@ export const CLAUDE_FLAG_FLOORS = {
244
243
  "--strict-mcp-config": [1, 0, 60],
245
244
  "--setting-sources": [1, 0, 122],
246
245
  "--autocompact": [2, 1, 122],
246
+ // 2.1.267 is the first CLI that accepts it; below that the recorded prompt
247
+ // simply is not refreshed, which is the pre-existing behaviour.
248
+ "--system-prompt-snapshot": [2, 1, 267],
247
249
  };
248
250
  /** The newest floor above: a CLI at or past it accepts everything the
249
251
  * harness sends. Below it the engine still works, minus the flags the CLI
250
252
  * predates, and the Engines page suggests an update. */
251
- export const CLAUDE_CONTEXT_CONTROL_MIN_VERSION = CLAUDE_FLAG_FLOORS["--autocompact"];
253
+ export const CLAUDE_CONTEXT_CONTROL_MIN_VERSION = CLAUDE_FLAG_FLOORS["--system-prompt-snapshot"];
252
254
  /** `claude --version` prints "2.1.232 (Claude Code)"; the first dotted triple
253
255
  * is the version. Null when nothing parses, e.g. a wrapper that prints its
254
256
  * own banner first — see claudeCliSupports for how that is treated. */
@@ -274,9 +276,7 @@ export function claudeCliSupports(version, flag) {
274
276
  return version === null || versionAtLeast(version, CLAUDE_FLAG_FLOORS[flag]);
275
277
  }
276
278
  /** The Engines-page notice for a CLI older than the newest floor. The engine
277
- * keeps working: turns run without the flags the CLI predates, which means
278
- * no harness-picked compaction window and, on a very old CLI, no isolation
279
- * from this machine's own Claude Code setup. */
279
+ * keeps working without the flags its CLI predates. */
280
280
  export function claudeCliUpdate(version, cli) {
281
281
  const parsed = parseClaudeCliVersion(version);
282
282
  if (!parsed || versionAtLeast(parsed, CLAUDE_CONTEXT_CONTROL_MIN_VERSION))
@@ -284,12 +284,15 @@ export function claudeCliUpdate(version, cli) {
284
284
  const floor = CLAUDE_CONTEXT_CONTROL_MIN_VERSION.join(".");
285
285
  const missing = Object.keys(CLAUDE_FLAG_FLOORS)
286
286
  .filter((flag) => !claudeCliSupports(parsed, flag));
287
+ const effects = [
288
+ ...(missing.includes("--autocompact") ? ["no compaction window picked by OpenMausBot"] : []),
289
+ ...(missing.includes("--setting-sources") ? ["bots still see this machine's own Claude Code setup"] : []),
290
+ ...(missing.includes("--system-prompt-snapshot") ? ["coordinated resumed turns cannot refresh stale system prompts"] : []),
291
+ ];
287
292
  return {
288
293
  title: "Update Claude Code for context controls",
289
294
  message: `Claude Code ${parsed.join(".")} predates ${floor}, so bots run without ${missing.join(", ")}: ` +
290
- "no compaction window picked by OpenMausBot" +
291
- (missing.includes("--setting-sources") ? ", and bots still see this machine's own Claude Code setup" : "") +
292
- ". Update it, then refresh Engines.",
295
+ `${effects.join("; ")}. Update it, then refresh Engines.`,
293
296
  command: cli === "claude" ? "claude update" : `${cli} update`,
294
297
  };
295
298
  }
@@ -338,12 +341,15 @@ function extrasFromUnknown(value) {
338
341
  * ANTHROPIC_API_KEY ("Not logged in · Please run /login"). Live injects
339
342
  * come from mergeLocalInject. */
340
343
  export function readClaudeModelCatalog(env = process.env) {
344
+ // A missing or unreadable settings.json is not fatal: an instance whose
345
+ // environment sets ANTHROPIC_MODEL (a Claude Code install pointed at an
346
+ // Anthropic-compatible host) still lists that model as Custom.
341
347
  let settings = {};
342
348
  try {
343
349
  settings = JSON.parse(readFileSync(join(resolveClaudeConfigDir(undefined, env), "settings.json"), "utf8"));
344
350
  }
345
351
  catch {
346
- return STATIC_CLAUDE_MODELS;
352
+ settings = {};
347
353
  }
348
354
  const extras = [
349
355
  ...extrasFromUnknown(settings.availableModels),
@@ -367,7 +373,6 @@ export function readClaudeModelCatalog(env = process.env) {
367
373
  // Resolved from the server root, never relative to this file: bundling inlines
368
374
  // this module into an entry one directory up, so a `".."` here would climb too
369
375
  // far. See server/proxy-paths.ts.
370
- const PROXY_PATH = SPAWNED_PROXIES.computer;
371
376
  const PERM_PROXY_PATH = SPAWNED_PROXIES.permission;
372
377
  const DWEB_PROXY_PATH = SPAWNED_PROXIES.dweb;
373
378
  // in the packaged app process.execPath is the Electron binary — this env
@@ -682,6 +687,7 @@ function decodeConfig(raw) {
682
687
  return {
683
688
  cli: typeof o.cli === "string" ? o.cli : "claude",
684
689
  ...(configDir ? { configDir } : {}),
690
+ ...(o.managed === true ? { managed: true } : {}),
685
691
  permissionMode: mode ?? "acceptEdits",
686
692
  ...(tools !== undefined ? { tools } : {}),
687
693
  ...(disallowedTools !== undefined ? { disallowedTools } : {}),
@@ -768,10 +774,12 @@ export const ClaudeDriver = {
768
774
  defaultConfig: () => decodeConfig({}),
769
775
  async create(input) {
770
776
  const { instanceId, config } = input;
771
- const environment = (model) => claudeEnvironment(model, { ...process.env, ...input.environment }, config.configDir, input.environment);
777
+ const environment = (model) => claudeEnvironment(config.managed ? undefined : model, { ...process.env, ...input.environment }, config.configDir, input.environment);
772
778
  const catalogEnv = environment();
773
779
  let models = STATIC_CLAUDE_MODELS;
774
780
  const refreshModels = async () => {
781
+ if (config.managed)
782
+ return;
775
783
  try {
776
784
  const resolved = await mergeLocalInject(readClaudeModelCatalog(catalogEnv), catalogEnv);
777
785
  if (resolved.options.length)
@@ -787,10 +795,14 @@ export const ClaudeDriver = {
787
795
  // harness snapshots every instance whenever it describes them — app
788
796
  // load, the Engines page, and right after `claude update`, which is
789
797
  // exactly when the answer changes — so a turn normally finds it filled.
790
- // A turn before any snapshot assumes a current CLI rather than paying a
791
- // CLI start-up of its own: the flags are the default, the exception is
792
- // the older install, and the next snapshot corrects it.
798
+ // Most turns before any snapshot assume a current CLI. A coordinated
799
+ // turn checks first because the snapshot-refresh flag is newer than the
800
+ // other context controls and an unknown flag would reject that request.
793
801
  let cliVersion = null;
802
+ let cliVersionChecked = false;
803
+ const readCliVersion = (env) => new Promise((resolve) => {
804
+ execCli(config.cli, ["--version"], { timeout: 8000, env }, (err, stdout) => resolve(err ? null : stdout.trim() || null));
805
+ });
794
806
  const listeners = new Set();
795
807
  // one active turn per thread; a second send while busy is a caller bug
796
808
  const active = new Map();
@@ -875,8 +887,16 @@ export const ClaudeDriver = {
875
887
  // is a fresh sendTurn, and the attempt cap must survive across launches
876
888
  const retryState = new Map();
877
889
  const sendTurn = async (turn, logicalTurnId) => {
890
+ if (config.managed && (!turn.model || turn.model.includes("::") || !config.configDir ||
891
+ !input.environment.ANTHROPIC_API_KEY || !input.environment.ANTHROPIC_BASE_URL)) {
892
+ throw new Error("Company model access is unavailable. Reconnect your organization; personal billing will not be used.");
893
+ }
878
894
  const { threadId, botId } = turn;
879
- if (active.has(threadId))
895
+ // An internal relaunch (transient failure, rejected resume) keeps the
896
+ // logical turn's stop handle in `active` while it sets up, so Stop is
897
+ // never a silent no-op between two CLI processes of the same turn.
898
+ const relaunch = logicalTurnId !== undefined;
899
+ if (active.has(threadId) && !relaunch)
880
900
  throw new Error("a turn is already running on this thread");
881
901
  // A bot-level mode is authoritative for this turn. In particular, an
882
902
  // old provider instance may still be configured with
@@ -901,7 +921,10 @@ export const ClaudeDriver = {
901
921
  const turnId = logicalTurnId ?? newId();
902
922
  const retryAbort = new AbortController();
903
923
  const retry = retryState.get(threadId) ?? { attempt: 0, cancelled: false };
904
- retry.cancelled = false;
924
+ // A fresh user turn starts un-cancelled. A relaunch must keep a Stop
925
+ // that landed while it was being scheduled.
926
+ if (!relaunch)
927
+ retry.cancelled = false;
905
928
  retryState.set(threadId, retry);
906
929
  // a retry relaunches the whole CLI; the backoff is scaled down in tests
907
930
  // so a fake's transient failures don't stall real seconds
@@ -924,6 +947,13 @@ export const ClaudeDriver = {
924
947
  args.push("--disallowedTools", config.disallowedTools.join(","));
925
948
  }
926
949
  const turnEnvironment = environment();
950
+ if (turn.refreshSystemPrompt && !cliVersionChecked) {
951
+ const version = await readCliVersion(turnEnvironment);
952
+ if (version) {
953
+ cliVersion = parseClaudeCliVersion(version);
954
+ cliVersionChecked = true;
955
+ }
956
+ }
927
957
  const isolated = !inheritsUserConfig(turnEnvironment);
928
958
  if (isolated) {
929
959
  // A bot gets the tools and instructions its owner gave it, not
@@ -944,8 +974,16 @@ export const ClaudeDriver = {
944
974
  if (compactWindow && claudeCliSupports(cliVersion, "--autocompact")) {
945
975
  args.push("--autocompact", compactWindow);
946
976
  }
947
- const turnModel = await resolveClaudeTurnModel(turn.model, turnEnvironment);
948
- const injected = applyClaudeInject({ ...turnEnvironment }, turnModel);
977
+ // An old pair conversation can still carry its first assignment in
978
+ // Claude's recorded system prompt. The current brief rides in the user
979
+ // turn, so refresh the recorded prompt on --resume too. Gated by the
980
+ // version floor like every other flag the CLI may predate: an unknown
981
+ // flag is a hard argument error, not a graceful degrade.
982
+ if (turn.refreshSystemPrompt && cliVersionChecked && claudeCliSupports(cliVersion, "--system-prompt-snapshot")) {
983
+ args.push("--system-prompt-snapshot", "off");
984
+ }
985
+ const turnModel = config.managed ? turn.model : await resolveClaudeTurnModel(turn.model, turnEnvironment);
986
+ const injected = config.managed ? { model: turnModel ?? null, injected: false } : applyClaudeInject({ ...turnEnvironment }, turnModel);
949
987
  if (injected.model)
950
988
  args.push("--model", injected.model);
951
989
  if (turn.effort)
@@ -964,15 +1002,7 @@ export const ClaudeDriver = {
964
1002
  mcpServers.composio = { ...turn.integrations.composio };
965
1003
  allowed.push("mcp__composio");
966
1004
  }
967
- if (turn.integrations?.computer) {
968
- mcpServers.computer = {
969
- command: process.execPath,
970
- args: [PROXY_PATH],
971
- env: { ...NODE_ENV_FLAG, ...computerProxyEnv(turn.integrations.computer) },
972
- };
973
- allowed.push("mcp__computer");
974
- }
975
- else if (turn.integrations?.localComputer) {
1005
+ if (turn.integrations?.localComputer) {
976
1006
  const local = turn.integrations.localComputer;
977
1007
  mcpServers.computer = {
978
1008
  command: local.command,
@@ -1216,9 +1246,7 @@ export const ClaudeDriver = {
1216
1246
  questions: questions ?? undefined,
1217
1247
  // A structured ask still offers flat labels, for the phone
1218
1248
  // companions and any client that predates the question card.
1219
- choices: questions
1220
- ? questionChoices(questions)
1221
- : Array.isArray(ask.input?.choices) ? ask.input.choices.slice(0, 5) : undefined,
1249
+ choices: questions ? questionChoices(questions) : parseChoices(ask.input?.choices),
1222
1250
  });
1223
1251
  },
1224
1252
  onResolve: (resolved) => {
@@ -1259,6 +1287,16 @@ export const ClaudeDriver = {
1259
1287
  cleanupUnownedLaunch();
1260
1288
  throw error;
1261
1289
  }
1290
+ // Stop reached the relaunch handle while this attempt was still setting
1291
+ // up (model probe, broker). Settle the logical turn as interrupted
1292
+ // instead of spawning a process nobody wants.
1293
+ if (relaunch && retry.cancelled) {
1294
+ cleanupUnownedLaunch();
1295
+ if (active.get(threadId)?.turnId === turnId)
1296
+ active.delete(threadId);
1297
+ emit({ ...base(threadId, turnId), type: "turn.completed", ok: false, stopReason: "interrupted", cost: null });
1298
+ return { turnId };
1299
+ }
1262
1300
  let child;
1263
1301
  try {
1264
1302
  child = spawnCli(config.cli, args, {
@@ -1409,6 +1447,9 @@ export const ClaudeDriver = {
1409
1447
  ...(typeof msg.usage.cache_read_input_tokens === "number"
1410
1448
  ? { cachedInput: msg.usage.cache_read_input_tokens }
1411
1449
  : {}),
1450
+ // one assistant message = one model call, and its prompt is
1451
+ // everything in the window: fresh text, cache reads and writes
1452
+ contextTokens: (msg.usage.input_tokens || 0) + (msg.usage.cache_read_input_tokens || 0) + (msg.usage.cache_creation_input_tokens || 0),
1412
1453
  });
1413
1454
  }
1414
1455
  break;
@@ -1544,14 +1585,18 @@ export const ClaudeDriver = {
1544
1585
  });
1545
1586
  return;
1546
1587
  }
1547
- // hand the thread back before recursing — the relaunch's own
1548
- // guard would otherwise reject it as "already running"
1549
- active.delete(threadId);
1588
+ // Keep Stop reachable while the relaunch sets up: there is no
1589
+ // process yet, so this handle only records the cancellation and
1590
+ // the relaunched sendTurn honors it before spawning.
1591
+ retryState.set(threadId, retry);
1592
+ active.set(threadId, { stop: () => { retry.cancelled = true; retryAbort.abort(); }, turnId });
1550
1593
  try {
1551
1594
  const cursor = session.sessionId ?? sessionId ?? undefined;
1552
1595
  await sendTurn({ ...turn, resumeCursor: cursor }, turnId);
1553
1596
  }
1554
1597
  catch (e) {
1598
+ if (active.get(threadId)?.turnId === turnId)
1599
+ active.delete(threadId);
1555
1600
  retryState.delete(threadId);
1556
1601
  emit({
1557
1602
  ...base(threadId, turnId),
@@ -1605,7 +1650,9 @@ export const ClaudeDriver = {
1605
1650
  }
1606
1651
  sessions.delete(threadId);
1607
1652
  session.turn = null;
1608
- active.delete(threadId);
1653
+ // Same relaunch handle as the transient-retry path above.
1654
+ retryState.set(threadId, retry);
1655
+ active.set(threadId, { stop: () => { retry.cancelled = true; retryAbort.abort(); }, turnId });
1609
1656
  emit({
1610
1657
  ...base(threadId, turnId),
1611
1658
  type: "turn.retrying",
@@ -1619,6 +1666,8 @@ export const ClaudeDriver = {
1619
1666
  await sendTurn({ ...turn, resumeCursor: undefined, recoveryText: undefined, text: recovery.text }, turnId);
1620
1667
  }
1621
1668
  catch (e) {
1669
+ if (active.get(threadId)?.turnId === turnId)
1670
+ active.delete(threadId);
1622
1671
  retryState.delete(threadId);
1623
1672
  emit({
1624
1673
  ...base(threadId, turnId),
@@ -1687,12 +1736,11 @@ export const ClaudeDriver = {
1687
1736
  const login = new ClaudeLoginController({ cli: config.cli, environment, onAuthenticated: async () => { await refreshModels(); } });
1688
1737
  const snapshot = async () => {
1689
1738
  const env = environment();
1690
- const version = await new Promise((resolve) => {
1691
- execCli(config.cli, ["--version"], { timeout: 8000, env }, (err, stdout) => resolve(err ? null : stdout.trim()));
1692
- });
1739
+ const version = await readCliVersion(env);
1693
1740
  if (!version)
1694
1741
  return { state: "unavailable", reason: `\`${config.cli}\` CLI not found` };
1695
1742
  cliVersion = parseClaudeCliVersion(version);
1743
+ cliVersionChecked = true;
1696
1744
  const auth = await claudeAuthStatus(config.cli, env);
1697
1745
  // claudeEnvironment strips ANTHROPIC_API_KEY, so turns run on the
1698
1746
  // CLI's own login (Pro/Max): the cost it reports is what the call