openmausbot 0.1.55 → 0.1.57

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-B433sv8K.css +1 -0
  2. package/dist/assets/index-DSh8AIUy.js +264 -0
  3. package/dist/assets/{index-D-u0LYsV.js → index-jWqt5dmY.js} +1 -1
  4. package/dist/index.html +2 -2
  5. package/dist-server/companion/src/routes.js +218 -0
  6. package/dist-server/connector-proxy.js +31 -12
  7. package/dist-server/container-mcp.js +49 -31
  8. package/dist-server/drivers/agents-proxy.js +56 -2
  9. package/dist-server/index.js +2020 -361
  10. package/dist-server/openmausbot.js +45 -386
  11. package/dist-server/pair-cli.js +45 -386
  12. package/dist-server/server/auto-approve.js +48 -0
  13. package/dist-server/server/bot-folder.js +102 -0
  14. package/dist-server/server/bot-overview.js +206 -0
  15. package/dist-server/server/bot-package.js +5 -0
  16. package/dist-server/server/bot-profile.js +24 -0
  17. package/dist-server/server/channel-queue.js +1 -0
  18. package/dist-server/server/computer-control.js +15 -0
  19. package/dist-server/server/connector-proxy.js +37 -16
  20. package/dist-server/server/container-computer.js +9 -2
  21. package/dist-server/server/drivers/agents-proxy.js +63 -2
  22. package/dist-server/server/drivers/antigravity-acp.js +61 -13
  23. package/dist-server/server/drivers/antigravity-runtime.js +47 -3
  24. package/dist-server/server/drivers/claude.js +7 -1
  25. package/dist-server/server/exit.js +9 -0
  26. package/dist-server/server/index.js +707 -124
  27. package/dist-server/server/mcp-bridge.js +73 -44
  28. package/dist-server/server/openmausbot.js +3 -2
  29. package/dist-server/server/package-export.js +1 -0
  30. package/dist-server/server/pair-cli.js +3 -2
  31. package/dist-server/server/peer-approval.js +27 -0
  32. package/dist-server/server/peer-provenance.js +4 -1
  33. package/dist-server/server/peer-roster.js +15 -0
  34. package/dist-server/server/profile-requests.js +332 -0
  35. package/dist-server/server/profile-revision.js +15 -0
  36. package/dist-server/server/profile-versions.js +154 -0
  37. package/dist-server/server/request-auth.js +10 -0
  38. package/dist-server/server/routine-requests.js +34 -1
  39. package/dist-server/server/routines.js +70 -2
  40. package/dist-server/server/setup-mode.js +63 -0
  41. package/dist-server/server/skill-fetch.js +67 -11
  42. package/dist-server/server/skills.js +1 -1
  43. package/dist-server/server/store.js +87 -7
  44. package/dist-server/server/system-prompt.js +49 -0
  45. package/dist-server/server/team-backup.js +2 -2
  46. package/dist-server/server/team-manifest.js +7 -0
  47. package/dist-server/server/tunnel.js +1 -1
  48. package/dist-server/shared/bot-profile.js +5 -1
  49. package/dist-server/shared/line-diff.js +42 -0
  50. package/dist-server/shared/profile-request.js +9 -0
  51. package/dist-server/shared/team-backup.js +4 -0
  52. package/dist-server/tunnel-guardian.js +6 -19
  53. package/dist-server/vps-container-mcp.js +49 -31
  54. package/package.json +1 -1
  55. package/dist/assets/index-B9rLIY81.js +0 -263
  56. package/dist/assets/index-CJBrtDbC.css +0 -1
@@ -127,6 +127,10 @@ export class AntigravityAcpClient {
127
127
  diagnosticBuffer = "";
128
128
  closed = false;
129
129
  onAuthorizationUrl;
130
+ /** Settles once the runtime process is gone. On Windows a running
131
+ * executable pins its file and its directory, so an installer must not
132
+ * rename or delete either until this settles. */
133
+ exited;
130
134
  constructor(runtime, profile, cwd, onAuthorizationUrl) {
131
135
  this.onAuthorizationUrl = onAuthorizationUrl;
132
136
  this.child = spawnCli(runtime.executablePath, process.platform === "linux" ? ["--uid="] : [], { cwd, env: profile.environment, stdio: ["pipe", "pipe", "pipe"] });
@@ -138,9 +142,14 @@ export class AntigravityAcpClient {
138
142
  this.child.stderr.setEncoding("utf8");
139
143
  this.child.stderr.on("data", (chunk) => this.consumeDiagnostics(chunk));
140
144
  this.child.once("error", (error) => this.failAll(error));
141
- this.child.once("close", (code) => {
142
- if (!this.closed)
143
- this.failAll(new Error(`Antigravity ACP exited ${code ?? "unexpectedly"}.`));
145
+ this.exited = new Promise((resolve) => {
146
+ this.child.once("close", (code) => {
147
+ if (!this.closed)
148
+ this.failAll(new Error(`Antigravity ACP exited ${code ?? "unexpectedly"}.`));
149
+ resolve();
150
+ });
151
+ // Failed spawns also emit `close`. An `error` alone can instead mean
152
+ // a failed kill, and is not evidence that the runtime stopped.
144
153
  });
145
154
  }
146
155
  consume(chunk) {
@@ -239,6 +248,22 @@ export class AntigravityAcpClient {
239
248
  this.failAll(new Error("Antigravity ACP was closed."));
240
249
  killCliTree(this.child);
241
250
  }
251
+ /** Close, then wait (bounded) for the process to be gone. `killCliTree`
252
+ * on Windows asks taskkill and returns at once; the exit lands later. */
253
+ async closeAndWait(timeoutMs = 5_000) {
254
+ this.close();
255
+ let timer;
256
+ const timedOut = new Promise((resolve) => {
257
+ timer = setTimeout(() => resolve(false), timeoutMs);
258
+ timer.unref?.();
259
+ });
260
+ try {
261
+ return await Promise.race([this.exited.then(() => true), timedOut]);
262
+ }
263
+ finally {
264
+ clearTimeout(timer);
265
+ }
266
+ }
242
267
  }
243
268
  export function parseAntigravityAuthorizationUrl(authorizationUrl) {
244
269
  if (authorizationUrl.length > 16_384 || /\s/u.test(authorizationUrl)) {
@@ -407,26 +432,49 @@ export function isValidAntigravityInitializeResult(initialized, expectedVersion)
407
432
  */
408
433
  export async function validateAntigravityRuntime(runtime, expectedVersion) {
409
434
  const profileDirectory = await mkdtemp(join(tmpdir(), "openmaus-antigravity-verify-"));
435
+ let client;
436
+ let failed = false;
437
+ let failure;
410
438
  try {
411
439
  const profile = await prepareAntigravityProfile({
412
440
  instanceId: `verify-${randomUUID()}`,
413
441
  runtime,
414
442
  profileDirectory,
415
443
  });
416
- const client = new AntigravityAcpClient(runtime, profile, profileDirectory);
417
- try {
418
- const initialized = await client.initialize();
419
- if (!isValidAntigravityInitializeResult(initialized, expectedVersion)) {
420
- throw new Error("The download did not identify as the expected Google Antigravity ACP release.");
421
- }
444
+ client = new AntigravityAcpClient(runtime, profile, profileDirectory);
445
+ const initialized = await client.initialize();
446
+ if (!isValidAntigravityInitializeResult(initialized, expectedVersion)) {
447
+ throw new Error("The download did not identify as the expected Google Antigravity ACP release.");
422
448
  }
423
- finally {
424
- client.close();
449
+ }
450
+ catch (error) {
451
+ failed = true;
452
+ failure = error;
453
+ }
454
+ // The caller is about to rename the directory this executable runs from.
455
+ // Neither it nor the profile is safe to touch before confirmed close.
456
+ let stopped = !client;
457
+ try {
458
+ if (client) {
459
+ stopped = await client.closeAndWait();
460
+ if (!stopped)
461
+ throw new Error("Antigravity did not shut down after runtime verification.");
425
462
  }
426
463
  }
427
- finally {
428
- await rm(profileDirectory, { recursive: true, force: true });
464
+ catch (error) {
465
+ if (!failed) {
466
+ failed = true;
467
+ failure = error;
468
+ }
469
+ }
470
+ if (stopped) {
471
+ await rm(profileDirectory, { recursive: true, force: true, maxRetries: 4, retryDelay: 250 })
472
+ .catch((error) => {
473
+ console.warn(`antigravity: could not remove verification profile ${profileDirectory}: ${error instanceof Error ? error.message : String(error)}`);
474
+ });
429
475
  }
476
+ if (failed)
477
+ throw failure;
430
478
  }
431
479
  /** One in-flight personal Google login per provider instance. */
432
480
  export class AntigravityAuthController {
@@ -11,6 +11,33 @@ import { augmentedPath } from "../env-path.js";
11
11
  import { resolveAntigravityReleaseAsset, } from "./antigravity-release.js";
12
12
  const FREE_SPACE_MARGIN = 256 * 1024 * 1024;
13
13
  const DOWNLOAD_TIMEOUT_MS = 45 * 60_000;
14
+ const STAGING_PREFIX = ".install-";
15
+ /** Windows refuses to rename or delete a file something still holds open,
16
+ * with EPERM/EBUSY/EACCES rather than a clear "in use": the runtime the
17
+ * validator just stopped (taskkill is asynchronous) or an antivirus scan of
18
+ * a brand-new executable. Retry briefly; persistent refusals still fail. */
19
+ export function transientWindowsFileError(error) {
20
+ const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : undefined;
21
+ return code === "EPERM" || code === "EBUSY" || code === "EACCES" || code === "ENOTEMPTY";
22
+ }
23
+ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
24
+ /** Retry a filesystem step on the transient Windows refusals above, with
25
+ * growing delays (default: 8 tries, 0.25 s → 2 s, about 10 s in total).
26
+ * Anything else, and the final failure, surface unchanged. */
27
+ export async function retryOnWindowsFileLock(step, options = {}) {
28
+ const attempts = options.attempts ?? 8;
29
+ const sleep = options.sleep ?? wait;
30
+ for (let attempt = 1;; attempt += 1) {
31
+ try {
32
+ return await step();
33
+ }
34
+ catch (error) {
35
+ if (attempt >= attempts || !transientWindowsFileError(error))
36
+ throw error;
37
+ await sleep(Math.min(2_000, (options.delayMs ?? 250) * 2 ** (attempt - 1)));
38
+ }
39
+ }
40
+ }
14
41
  const INSTALL_RECORD = ".install-complete.json";
15
42
  export class AntigravitySetupError extends Error {
16
43
  status = 409;
@@ -293,7 +320,10 @@ async function installOnce(options) {
293
320
  // statfs is unavailable on a few filesystems. The bounded download and
294
321
  // extraction checks still fail safely if the disk fills.
295
322
  }
296
- const staging = await mkdtemp(join(versions, `.install-${randomUUID()}-`));
323
+ // Another app/server process may be installing into this versions folder.
324
+ // The in-memory coalescing map cannot protect its staging directory.
325
+ const staging = await mkdtemp(join(versions, `${STAGING_PREFIX}${randomUUID()}-`));
326
+ let failed = false;
297
327
  try {
298
328
  const archivePath = join(staging, "download.zip");
299
329
  const runtimeDirectory = join(staging, "runtime");
@@ -364,7 +394,7 @@ async function installOnce(options) {
364
394
  };
365
395
  await writeFile(join(runtimeDirectory, INSTALL_RECORD), `${JSON.stringify(record)}\n`, { flag: "wx", mode: 0o600 });
366
396
  try {
367
- await rename(runtimeDirectory, destination);
397
+ await retryOnWindowsFileLock(() => rename(runtimeDirectory, destination));
368
398
  }
369
399
  catch (error) {
370
400
  const winner = await completedRelease(destination, asset);
@@ -376,8 +406,22 @@ async function installOnce(options) {
376
406
  throw new Error("The installed Antigravity runtime is incomplete.");
377
407
  return installed;
378
408
  }
409
+ catch (error) {
410
+ failed = true;
411
+ throw error;
412
+ }
379
413
  finally {
380
- await rm(staging, { recursive: true, force: true });
414
+ // Cleanup must never replace the real error with its own: a user who
415
+ // saw "EPERM: unlink agy_acp_server.exe" was looking at this line, not
416
+ // at why the install failed. Only clean up this attempt's directory;
417
+ // if it stays locked, log and leave it without touching other attempts.
418
+ try {
419
+ await retryOnWindowsFileLock(() => rm(staging, { recursive: true, force: true }));
420
+ }
421
+ catch (cleanupError) {
422
+ const detail = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
423
+ console.warn(`antigravity: could not remove the install folder ${staging} (${detail})${failed ? "" : "; the install itself succeeded"}`);
424
+ }
381
425
  }
382
426
  }
383
427
  /** Coalesce concurrent clicks so only one large Google artifact is fetched. */
@@ -774,7 +774,10 @@ export const ClaudeDriver = {
774
774
  if (live.idleTimer)
775
775
  clearTimeout(live.idleTimer);
776
776
  live.turn = { turnId, settled: false, sawStreamDelta: false };
777
- active.set(threadId, { stop: () => killCliTree(live.child), turnId, broker: live.broker });
777
+ active.set(threadId, { stop: () => {
778
+ closeSession(threadId, "interrupted");
779
+ killCliTree(live.child);
780
+ }, turnId, broker: live.broker });
778
781
  emit({ ...base(threadId, turnId), type: "turn.started" });
779
782
  const written = await writeUser(live, threadId, promptMsg);
780
783
  if (!written) {
@@ -1175,6 +1178,9 @@ export const ClaudeDriver = {
1175
1178
  sessions.delete(threadId);
1176
1179
  });
1177
1180
  const stop = () => {
1181
+ // taskkill is asynchronous on Windows. Retire steering and approvals
1182
+ // now, before a still-connected child can submit more work.
1183
+ closeSession(threadId, "interrupted");
1178
1184
  retry.cancelled = true;
1179
1185
  retryAbort.abort();
1180
1186
  killCliTree(child);
@@ -0,0 +1,9 @@
1
+ // End the process with a code once stdout and stderr have flushed. On Linux a
2
+ // pipe (CI's `| grep`, a journal) takes writes asynchronously, so
3
+ // `process.exit` right after `console.error` can drop the very line that
4
+ // says what went wrong. A write callback fires only once its data is out.
5
+ export function exitAfterFlush(code) {
6
+ process.exitCode = code;
7
+ const flushed = (stream) => new Promise((done) => stream.write("", () => done()));
8
+ void Promise.all([flushed(process.stdout), flushed(process.stderr)]).then(() => process.exit(code));
9
+ }