openmausbot 0.1.82 → 0.1.83

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 (47) hide show
  1. package/dist/assets/{index-BLLsDK2F.js → index-DSvqfBOf.js} +71 -71
  2. package/dist/assets/{index-5HVwp5m2.js → index-nqTFZTxg.js} +1 -1
  3. package/dist/index.html +1 -1
  4. package/dist-server/container-mcp.js +20 -4
  5. package/dist-server/drivers/agents-proxy.js +4 -4
  6. package/dist-server/index.js +1063 -493
  7. package/dist-server/mcp-gate.js +2 -1
  8. package/dist-server/openmausbot.js +187 -55
  9. package/dist-server/pair-cli.js +187 -55
  10. package/dist-server/server/auto-approve.js +15 -1
  11. package/dist-server/server/auto-vm-claims.js +22 -0
  12. package/dist-server/server/browser-bundle-release.js +8 -7
  13. package/dist-server/server/browser-engine.js +1 -1
  14. package/dist-server/server/channel-queue.js +58 -0
  15. package/dist-server/server/chief-of-staff.js +2 -2
  16. package/dist-server/server/config.js +33 -2
  17. package/dist-server/server/contracts.js +0 -7
  18. package/dist-server/server/drivers/acp/gemini.js +11 -1
  19. package/dist-server/server/drivers/acp/qwen.js +16 -1
  20. package/dist-server/server/drivers/agents-proxy.js +4 -4
  21. package/dist-server/server/drivers/claude.js +4 -3
  22. package/dist-server/server/drivers/codex.js +66 -1
  23. package/dist-server/server/drivers/pi.js +2 -1
  24. package/dist-server/server/env-path.js +6 -1
  25. package/dist-server/server/index.js +534 -84
  26. package/dist-server/server/local-routing.js +10 -4
  27. package/dist-server/server/peer-roster.js +37 -1
  28. package/dist-server/server/recent-work.js +4 -1
  29. package/dist-server/server/redact.js +5 -48
  30. package/dist-server/server/room-handoffs.js +79 -16
  31. package/dist-server/server/skill-fetch.js +1 -1
  32. package/dist-server/server/steer-queue.js +29 -0
  33. package/dist-server/server/store.js +26 -3
  34. package/dist-server/server/thread-retention.js +50 -0
  35. package/dist-server/server/turn-context.js +7 -0
  36. package/dist-server/server/turn-resources.js +6 -0
  37. package/dist-server/shared/approval-mode.js +8 -5
  38. package/dist-server/shared/inspector.js +1 -0
  39. package/dist-server/shared/json.js +1 -0
  40. package/dist-server/shared/notification.js +1 -0
  41. package/dist-server/shared/redact.js +61 -0
  42. package/dist-server/shared/routines.js +1 -0
  43. package/dist-server/shared/runtime-events.js +1 -0
  44. package/dist-server/shared/webhooks.js +1 -0
  45. package/dist-server/shared/wire.js +8 -0
  46. package/dist-server/vps-container-mcp.js +20 -4
  47. package/package.json +1 -1
@@ -17568,15 +17568,23 @@ var init_image_generation = __esm({
17568
17568
  }
17569
17569
  });
17570
17570
 
17571
+ // shared/wire.ts
17572
+ var EFFORT_LEVELS;
17573
+ var init_wire = __esm({
17574
+ "shared/wire.ts"() {
17575
+ "use strict";
17576
+ EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
17577
+ }
17578
+ });
17579
+
17571
17580
  // server/contracts.ts
17572
17581
  function isModelVariant(value) {
17573
17582
  return typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && !new RegExp("\\p{Cc}", "u").test(value);
17574
17583
  }
17575
- var EFFORT_LEVELS, eventCounter, newEventId, newId;
17584
+ var eventCounter, newEventId, newId;
17576
17585
  var init_contracts = __esm({
17577
17586
  "server/contracts.ts"() {
17578
17587
  "use strict";
17579
- EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
17580
17588
  eventCounter = 0;
17581
17589
  newEventId = () => `ev-${Date.now().toString(36)}-${(eventCounter++).toString(36)}`;
17582
17590
  newId = () => crypto.randomUUID();
@@ -17937,13 +17945,14 @@ function instanceConfigs(cfg) {
17937
17945
  }
17938
17946
  return map3;
17939
17947
  }
17940
- var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, MAX_CONCURRENT_BOT_THREADS, MIN_THREAD_EVENT_LOG_BYTES, MAX_THREAD_EVENT_LOG_BYTES, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, onboardingConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
17948
+ var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES, DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES, DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES, MAX_CONCURRENT_BOT_THREADS, MIN_THREAD_EVENT_LOG_BYTES, MAX_THREAD_EVENT_LOG_BYTES, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, onboardingConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
17941
17949
  var init_config = __esm({
17942
17950
  "server/config.ts"() {
17943
17951
  "use strict";
17944
17952
  init_zod();
17945
17953
  init_image_generation();
17946
17954
  init_atomic();
17955
+ init_wire();
17947
17956
  init_contracts();
17948
17957
  init_mcp_registry();
17949
17958
  init_schema();
@@ -17953,6 +17962,9 @@ var init_config = __esm({
17953
17962
  BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
17954
17963
  MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
17955
17964
  MAX_ROOM_TURN_TIMEOUT_MINUTES = 1440;
17965
+ DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES = 30;
17966
+ DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES = 10;
17967
+ DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES = 240;
17956
17968
  MAX_CONCURRENT_BOT_THREADS = 10;
17957
17969
  MIN_THREAD_EVENT_LOG_BYTES = 256 * 1024;
17958
17970
  MAX_THREAD_EVENT_LOG_BYTES = 4 * 1024 * 1024 * 1024;
@@ -17964,8 +17976,16 @@ var init_config = __esm({
17964
17976
  }).optional()
17965
17977
  });
17966
17978
  roomConfigSchema = external_exports.object({
17967
- turnTimeoutMinutes: external_exports.number().int().min(MIN_ROOM_TURN_TIMEOUT_MINUTES).max(MAX_ROOM_TURN_TIMEOUT_MINUTES)
17968
- });
17979
+ turnTimeoutMinutes: external_exports.number().int().min(MIN_ROOM_TURN_TIMEOUT_MINUTES).max(MAX_ROOM_TURN_TIMEOUT_MINUTES),
17980
+ /** Room handoff tree lifetime. Active execution pauses this clock; the
17981
+ * hard cap is wall-clock and bounds trees that never stop executing. */
17982
+ handoffLifetimeMinutes: external_exports.number().int().min(1).max(MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
17983
+ handoffMinRunwayMinutes: external_exports.number().int().min(1).max(MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
17984
+ handoffHardCapMinutes: external_exports.number().int().min(1).max(7 * MAX_ROOM_TURN_TIMEOUT_MINUTES).optional()
17985
+ }).refine(
17986
+ (rooms) => (rooms.handoffMinRunwayMinutes ?? DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES) <= (rooms.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) && (rooms.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) <= (rooms.handoffHardCapMinutes ?? DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES),
17987
+ { message: "rooms handoff bounds must satisfy handoffMinRunwayMinutes <= handoffLifetimeMinutes <= handoffHardCapMinutes" }
17988
+ );
17969
17989
  localVmConfigSchema = external_exports.object({
17970
17990
  mode: external_exports.enum(["shared", "per-bot"]).optional(),
17971
17991
  maxInstances: external_exports.number().int().min(MIN_LOCAL_VM_MAX_INSTANCES).max(MAX_LOCAL_VM_MAX_INSTANCES).optional()
@@ -18137,7 +18157,10 @@ var init_config = __esm({
18137
18157
  maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
18138
18158
  /** Cap each per-thread events/ and native/ NDJSON log at this many
18139
18159
  * bytes; absent (the default) keeps today's unbounded growth (#1280). */
18140
- eventLogMaxBytes: external_exports.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional()
18160
+ eventLogMaxBytes: external_exports.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional(),
18161
+ /** Days a closed or archived thread's event logs survive (#1280).
18162
+ * Absent keeps them forever. */
18163
+ eventLogRetentionDays: external_exports.number().int().min(1).max(3650).optional()
18141
18164
  }).strict().optional(),
18142
18165
  localVm: localVmConfigSchema.optional(),
18143
18166
  features: featureConfigSchema.optional(),
@@ -18246,7 +18269,7 @@ function ownerPredatesThisBoot(owner) {
18246
18269
  return false;
18247
18270
  }
18248
18271
  function ownerIsAlive(owner) {
18249
- return !ownerPredatesThisBoot(owner) && processIsAlive2(owner.pid);
18272
+ return !ownerPredatesThisBoot(owner) && processIsAlive2(owner.pid) && ownerIdentityMatches(owner);
18250
18273
  }
18251
18274
  function isLeaseOwner(value) {
18252
18275
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -18296,6 +18319,51 @@ function processIsAlive2(pid) {
18296
18319
  throw leaseError("OpenMausBot could not verify the data-directory lease owner; refusing to start.", error51);
18297
18320
  }
18298
18321
  }
18322
+ function parseWmiDateTime(value) {
18323
+ const raw = String(value).trim();
18324
+ const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-]\d{3})$/.exec(raw);
18325
+ if (!match) return null;
18326
+ const [_, year, month, day, hour, minute, second, micro, offset] = match;
18327
+ const ms = Date.UTC(
18328
+ Number(year),
18329
+ Number(month) - 1,
18330
+ Number(day),
18331
+ Number(hour),
18332
+ Number(minute),
18333
+ Number(second),
18334
+ Math.floor(Number(micro) / 1e3)
18335
+ );
18336
+ const offsetMinutes = Number(offset);
18337
+ return ms - offsetMinutes * 60 * 1e3;
18338
+ }
18339
+ function processCreationTimeMs(pid) {
18340
+ if (process.platform !== "win32") return null;
18341
+ try {
18342
+ const output = execFileSync2(
18343
+ "wmic",
18344
+ ["process", "where", `ProcessId=${pid}`, "get", "CreationDate", "/format:csv"],
18345
+ { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
18346
+ );
18347
+ for (const line of output.split(/\r?\n/)) {
18348
+ const trimmed = line.trim();
18349
+ if (!trimmed) continue;
18350
+ const lower = trimmed.toLowerCase();
18351
+ if (lower === "node,creationdate" || lower.startsWith("node,")) continue;
18352
+ const parts = trimmed.split(",");
18353
+ const raw = parts[parts.length - 1].trim();
18354
+ const parsed = parseWmiDateTime(raw);
18355
+ if (parsed !== null) return parsed;
18356
+ }
18357
+ } catch {
18358
+ }
18359
+ return null;
18360
+ }
18361
+ function ownerIdentityMatches(owner) {
18362
+ if (process.platform !== "win32") return true;
18363
+ const created = processCreationTimeMs(owner.pid);
18364
+ if (created === null) return true;
18365
+ return created < owner.createdAt;
18366
+ }
18299
18367
  function unlinkExact(path3, message2) {
18300
18368
  try {
18301
18369
  unlinkSync2(path3);
@@ -18701,7 +18769,8 @@ function parseCmdShim(shim, env) {
18701
18769
  return null;
18702
18770
  }
18703
18771
  const dir = dirname4(shim);
18704
- const targets = [...text4.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m) => join8(dir, m[1])).filter((p) => isFile(p) && basename2(p).toLowerCase() !== "node.exe");
18772
+ const npmEntry = /^SET "NP[MX]_CLI_JS=%~dp0\\([^"]+)"/im.exec(text4);
18773
+ const targets = [...npmEntry ? [npmEntry] : [], ...text4.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m) => join8(dir, m[1])).filter((p) => isFile(p) && basename2(p).toLowerCase() !== "node.exe");
18705
18774
  const script = targets.find((p) => /\.[cm]?js$/i.test(p));
18706
18775
  if (script) {
18707
18776
  const node = nodeExe(dir, env);
@@ -19574,9 +19643,9 @@ var init_local_inject = __esm({
19574
19643
  // shared/approval-mode.ts
19575
19644
  function supportsApprovalMode(driverKind, mode) {
19576
19645
  if (mode === "custom") return driverKind === "codex";
19577
- if (mode === "edits") return ["claudeAgent", "grokAgent", "antigravityAgent"].includes(driverKind ?? "");
19646
+ if (mode === "edits") return ["claudeAgent", "grokAgent", "antigravityAgent", "qwenAgent", "geminiAgent"].includes(driverKind ?? "");
19578
19647
  if (mode !== "full") return true;
19579
- return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo"].includes(driverKind ?? "");
19648
+ return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo", "qwenAgent", "geminiAgent"].includes(driverKind ?? "");
19580
19649
  }
19581
19650
  var init_approval_mode = __esm({
19582
19651
  "shared/approval-mode.ts"() {
@@ -19584,12 +19653,7 @@ var init_approval_mode = __esm({
19584
19653
  }
19585
19654
  });
19586
19655
 
19587
- // server/redact.ts
19588
- function isSecretName(name) {
19589
- const lower = name.toLowerCase();
19590
- if (SECRET_KEY_PARTS.some((part) => lower.includes(part))) return true;
19591
- return /(^|[_.-])keys?$/.test(lower);
19592
- }
19656
+ // shared/redact.ts
19593
19657
  function redactSecretsInText(text4) {
19594
19658
  if (!text4 || text4.length < 8) return text4;
19595
19659
  let out = text4;
@@ -19604,33 +19668,10 @@ ${close}`);
19604
19668
  out = out.replace(URL_USERINFO, (_m, lead, secret, at) => `${lead}${mask(secret)}${at}`);
19605
19669
  return out;
19606
19670
  }
19607
- function redactSecrets(input, depth = 0) {
19608
- if (typeof input === "string") return redactSecretsInText(input);
19609
- if (depth > 12 || input === null || typeof input !== "object") return input;
19610
- if (Array.isArray(input)) {
19611
- return input.map((item) => {
19612
- if (item !== null && typeof item === "object" && !Array.isArray(item) && typeof item.name === "string" && typeof item.value === "string") {
19613
- const entry = item;
19614
- return isSecretName(entry.name) ? { ...entry, value: mask(entry.value) } : { ...entry, value: redactSecretsInText(entry.value) };
19615
- }
19616
- return redactSecrets(item, depth + 1);
19617
- });
19618
- }
19619
- const out = {};
19620
- for (const [key, value] of Object.entries(input)) {
19621
- if (typeof value === "string" && isSecretName(key)) {
19622
- out[key] = mask(value);
19623
- continue;
19624
- }
19625
- out[key] = redactSecrets(value, depth + 1);
19626
- }
19627
- return out;
19628
- }
19629
- var SECRET_KEY_PARTS, REDACTION_MARKER, mask, KEY_PREFIXES, BEARER, PEM_BLOCK, KEY_VALUE, KEY_SUFFIX_ASSIGNMENT, SECRET_FLAG, URL_USERINFO;
19671
+ var REDACTION_MARKER, mask, KEY_PREFIXES, BEARER, PEM_BLOCK, KEY_VALUE, KEY_SUFFIX_ASSIGNMENT, SECRET_FLAG, URL_USERINFO;
19630
19672
  var init_redact = __esm({
19631
- "server/redact.ts"() {
19673
+ "shared/redact.ts"() {
19632
19674
  "use strict";
19633
- SECRET_KEY_PARTS = ["token", "secret", "password", "passwd", "apikey", "api_key", "authorization", "auth_token"];
19634
19675
  REDACTION_MARKER = /^«redacted \d+ chars»$/;
19635
19676
  mask = (value) => REDACTION_MARKER.test(value) ? value : `\xABredacted ${value.length} chars\xBB`;
19636
19677
  KEY_PREFIXES = [
@@ -19668,6 +19709,44 @@ var init_redact = __esm({
19668
19709
  }
19669
19710
  });
19670
19711
 
19712
+ // server/redact.ts
19713
+ function isSecretName(name) {
19714
+ const lower = name.toLowerCase();
19715
+ if (SECRET_KEY_PARTS.some((part) => lower.includes(part))) return true;
19716
+ return /(^|[_.-])keys?$/.test(lower);
19717
+ }
19718
+ function redactSecrets(input, depth = 0) {
19719
+ if (typeof input === "string") return redactSecretsInText(input);
19720
+ if (depth > 12 || input === null || typeof input !== "object") return input;
19721
+ if (Array.isArray(input)) {
19722
+ return input.map((item) => {
19723
+ if (item !== null && typeof item === "object" && !Array.isArray(item) && typeof item.name === "string" && typeof item.value === "string") {
19724
+ const entry = item;
19725
+ return isSecretName(entry.name) ? { ...entry, value: mask(entry.value) } : { ...entry, value: redactSecretsInText(entry.value) };
19726
+ }
19727
+ return redactSecrets(item, depth + 1);
19728
+ });
19729
+ }
19730
+ const out = {};
19731
+ for (const [key, value] of Object.entries(input)) {
19732
+ if (typeof value === "string" && isSecretName(key)) {
19733
+ out[key] = mask(value);
19734
+ continue;
19735
+ }
19736
+ out[key] = redactSecrets(value, depth + 1);
19737
+ }
19738
+ return out;
19739
+ }
19740
+ var SECRET_KEY_PARTS;
19741
+ var init_redact2 = __esm({
19742
+ "server/redact.ts"() {
19743
+ "use strict";
19744
+ init_redact();
19745
+ init_redact();
19746
+ SECRET_KEY_PARTS = ["token", "secret", "password", "passwd", "apikey", "api_key", "authorization", "auth_token"];
19747
+ }
19748
+ });
19749
+
19671
19750
  // server/thread-log-rotation.ts
19672
19751
  import { closeSync as closeSync4, fstatSync, openSync as openSync4, readSync as readSync2, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync3, writeSync } from "node:fs";
19673
19752
  function currentThreadLogCap() {
@@ -19753,7 +19832,7 @@ var init_native = __esm({
19753
19832
  "server/drivers/native.ts"() {
19754
19833
  "use strict";
19755
19834
  init_config();
19756
- init_redact();
19835
+ init_redact2();
19757
19836
  init_thread_log_rotation();
19758
19837
  }
19759
19838
  });
@@ -19816,7 +19895,7 @@ var QUESTION_LIMIT, LIMIT, cut;
19816
19895
  var init_tool_summary = __esm({
19817
19896
  "server/tool-summary.ts"() {
19818
19897
  "use strict";
19819
- init_redact();
19898
+ init_redact2();
19820
19899
  QUESTION_LIMIT = 300;
19821
19900
  LIMIT = 200;
19822
19901
  cut = (text4, limit) => redactSecretsInText(text4).trim().slice(0, limit);
@@ -23968,8 +24047,8 @@ var init_claude = __esm({
23968
24047
  };
23969
24048
  const steer = async (threadId, text4) => {
23970
24049
  const s = sessions.get(threadId);
23971
- if (!s || !s.turn || s.turn.settled || s.closing || s.child.exitCode !== null) return false;
23972
- return writeUser(s, threadId, claudeUserMessage(text4, void 0));
24050
+ if (!s || !s.turn || s.turn.settled || s.closing || s.child.exitCode !== null) return "refused";
24051
+ return await writeUser(s, threadId, claudeUserMessage(text4, void 0)) ? "steered" : "refused";
23973
24052
  };
23974
24053
  const login = new ClaudeLoginController({ cli: config2.cli, environment, onAuthenticated: async () => {
23975
24054
  await refreshModels();
@@ -25482,9 +25561,23 @@ var init_codex = __esm({
25482
25561
  return stopped;
25483
25562
  });
25484
25563
  let completeStoppedTurn;
25564
+ let interruptRequested = false;
25485
25565
  const stop = async () => {
25486
25566
  stopRequested = true;
25487
25567
  stopSignal.abort();
25568
+ if (!state.settled && !interruptRequested && codexThreadId && codexTurnId && child.exitCode === null && child.signalCode === null) {
25569
+ interruptRequested = true;
25570
+ const graceMs = Math.max(1, Number(process.env.FAKE_CODEX_INTERRUPT_GRACE_MS ?? 750) || 750);
25571
+ try {
25572
+ await request("turn/interrupt", { threadId: codexThreadId, turnId: codexTurnId }, graceMs);
25573
+ } catch {
25574
+ }
25575
+ const deadline = Date.now() + graceMs;
25576
+ while (!state.settled && Date.now() < deadline) {
25577
+ await new Promise((wake) => setTimeout(wake, 15));
25578
+ }
25579
+ if (state.settled) return true;
25580
+ }
25488
25581
  const stopped = await terminate();
25489
25582
  if (stopped) completeStoppedTurn?.();
25490
25583
  return stopped;
@@ -25505,6 +25598,23 @@ var init_codex = __esm({
25505
25598
  emit({ ...base2(threadId, turnId), type: "runtime.error", message: "codex did not shut down after termination was requested" });
25506
25599
  }
25507
25600
  };
25601
+ const steerActiveTurn = async (text4) => {
25602
+ if (state.settled || abandoned || stopRequested || !codexThreadId || !codexTurnId) return "refused";
25603
+ if (child.exitCode !== null || child.signalCode !== null) return "refused";
25604
+ try {
25605
+ const steerTimeoutMs = Math.max(1, Number(process.env.FAKE_CODEX_STEER_TIMEOUT_MS ?? 1e4) || 1e4);
25606
+ await request("turn/steer", {
25607
+ threadId: codexThreadId,
25608
+ input: [{ type: "text", text: text4 }],
25609
+ expectedTurnId: codexTurnId
25610
+ }, steerTimeoutMs);
25611
+ return "steered";
25612
+ } catch (error51) {
25613
+ const message2 = error51 instanceof Error ? error51.message : String(error51);
25614
+ if (state.settled || message2.includes("timed out")) return "indeterminate";
25615
+ return "refused";
25616
+ }
25617
+ };
25508
25618
  const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
25509
25619
  const handleServerRequest = (msg) => {
25510
25620
  const method = msg.method;
@@ -25794,6 +25904,10 @@ var init_codex = __esm({
25794
25904
  void stop();
25795
25905
  return;
25796
25906
  }
25907
+ if (stopRequested) {
25908
+ void settle(false, "interrupted");
25909
+ return;
25910
+ }
25797
25911
  const recentStderr = stderrSinceOutput.trim();
25798
25912
  const hadStreamedOutput = codexTurnId !== null || state.sawStreamDelta;
25799
25913
  const verdict = signal !== null ? { transient: false, reason: "interrupted" } : classifyError({ exitCode: code, stderr: recentStderr });
@@ -25832,7 +25946,7 @@ var init_codex = __esm({
25832
25946
  });
25833
25947
  void settle(false, "exit_before_result");
25834
25948
  });
25835
- active.set(threadId, { stop, turnId, asks });
25949
+ active.set(threadId, { stop, turnId, asks, steer: steerActiveTurn });
25836
25950
  if (attempt === 0) emit({ ...base2(threadId, turnId), type: "turn.started" });
25837
25951
  try {
25838
25952
  await request("initialize", {
@@ -26041,6 +26155,7 @@ ${stderr ?? ""}`))
26041
26155
  provider: DRIVER_KIND3,
26042
26156
  capabilities: {
26043
26157
  sessionModelSwitch: "unsupported",
26158
+ queueing: true,
26044
26159
  computerMcp: true,
26045
26160
  localComputerMcp: true,
26046
26161
  composioMcp: true,
@@ -26056,6 +26171,10 @@ ${stderr ?? ""}`))
26056
26171
  interruptTurn: async (threadId) => {
26057
26172
  await active.get(threadId)?.stop();
26058
26173
  },
26174
+ steer: async (threadId, text4) => {
26175
+ const turn = active.get(threadId);
26176
+ return turn?.steer ? await turn.steer(text4) : "refused";
26177
+ },
26059
26178
  respondToRequest: async (threadId, requestId, decision) => {
26060
26179
  const turn = active.get(threadId);
26061
26180
  const finish = turn?.asks.get(requestId);
@@ -34674,7 +34793,7 @@ var init_openai_chat = __esm({
34674
34793
  "server/drivers/openai-chat.ts"() {
34675
34794
  "use strict";
34676
34795
  init_contracts();
34677
- init_redact();
34796
+ init_redact2();
34678
34797
  init_tool_summary();
34679
34798
  init_chat_mcp_tools();
34680
34799
  init_chat_tool_approval();
@@ -35013,6 +35132,11 @@ function liveOauthCredential() {
35013
35132
  function geminiIsAuthenticated(env) {
35014
35133
  return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync12(join24(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
35015
35134
  }
35135
+ function geminiApprovalArgs(fullAuto, approvalMode) {
35136
+ if (fullAuto) return ["--yolo"];
35137
+ if (approvalMode === "edits") return ["--approval-mode", "auto_edit"];
35138
+ return [];
35139
+ }
35016
35140
  var AUTH_PREFERENCE, nonBlank, support3, GeminiAgentDriver;
35017
35141
  var init_gemini = __esm({
35018
35142
  "server/drivers/acp/gemini.ts"() {
@@ -35035,7 +35159,7 @@ var init_gemini = __esm({
35035
35159
  loginNote: "Gemini CLI needs a GEMINI_API_KEY, a Vertex AI setup, or an enterprise Code Assist login \u2014 consumer Google logins stopped working on 2026-06-18 (use the Antigravity engine for those accounts)",
35036
35160
  // --acp is the stable Gemini CLI surface. --experimental-acp remains an
35037
35161
  // alias for older releases, but using it now emits a deprecation warning.
35038
- spawnArgs: (_config, turn) => ["--acp", ...turn.model ? ["-m", turn.model] : []],
35162
+ spawnArgs: (config2, turn) => ["--acp", ...geminiApprovalArgs(config2.fullAuto, turn.approvalMode), ...turn.model ? ["-m", turn.model] : []],
35039
35163
  credentialEnv: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
35040
35164
  pickAuthMethod: (methods) => {
35041
35165
  const ids = methods.map((m) => m.id).filter((id) => typeof id === "string");
@@ -36468,6 +36592,12 @@ function resolveQwenTurnModel(model, env) {
36468
36592
  if (!matches.length) throw new Error("This Qwen model is no longer configured. Refresh models and select it again.");
36469
36593
  return matches[0].id;
36470
36594
  }
36595
+ function qwenApprovalArgs(fullAuto, approvalMode) {
36596
+ if (fullAuto) return ["--yolo"];
36597
+ if (approvalMode === "auto") return ["--approval-mode", "auto"];
36598
+ if (approvalMode === "edits") return ["--approval-mode", "auto-edit"];
36599
+ return [];
36600
+ }
36471
36601
  var EMPTY, PROTOCOLS, support8, QwenAgentDriver;
36472
36602
  var init_qwen = __esm({
36473
36603
  "server/drivers/acp/qwen.ts"() {
@@ -36496,7 +36626,7 @@ var init_qwen = __esm({
36496
36626
  },
36497
36627
  // A raw -m only changes the model within the saved provider. ACP switches
36498
36628
  // the complete route and confirms it before any prompt leaves OMB.
36499
- spawnArgs: () => ["--acp"],
36629
+ spawnArgs: (config2, turn) => ["--acp", ...qwenApprovalArgs(config2.fullAuto, turn.approvalMode)],
36500
36630
  selectModel: { configId: "model" },
36501
36631
  pickAuthMethod: () => null,
36502
36632
  authFailure: "continue",
@@ -44122,6 +44252,7 @@ var init_pi = __esm({
44122
44252
  init_procs();
44123
44253
  init_proxy_paths();
44124
44254
  init_tool_summary();
44255
+ init_wire();
44125
44256
  init_contracts();
44126
44257
  init_local_inject();
44127
44258
  init_native();
@@ -45205,12 +45336,13 @@ var init_browser_bundle_release = __esm({
45205
45336
  "server/browser-bundle-release.ts"() {
45206
45337
  "use strict";
45207
45338
  init_browser_engine_release();
45208
- CHROME_VERSION = "152.0.7977.82";
45339
+ CHROME_VERSION = "153.0.8010.47";
45209
45340
  CHROME_ASSETS = {
45210
- "darwin-arm64": { platform: "mac-arm64", bytes: 97760996, sha256: "1615f063c894aa824fd55c89f8f05e9904f63cce0c95d0cbce7394d77884fba5", executableSha256: "ac18537fb2bc2b72b0440893b6ea5d4ef3bacc6220b1e9a1693c73f8b1045fbe" },
45211
- "darwin-x64": { platform: "mac-x64", bytes: 102895824, sha256: "c903707292c6aaed7c6e572c0bb5e6645454e45d9e0e6db27e876b18e9165f31", executableSha256: "2faefd55d4e47764956e31e338d362a1e794037fa772a426ffd510f122f33f19" },
45212
- "linux-x64": { platform: "linux64", bytes: 119454769, sha256: "0ca12ea26b502a83e32db334a17883c315348845efc071d908de7a6d94a97eff", executableSha256: "1301a5024fef1d58c4cfbde5d44794cb18c5b2cbbe4f3c64f81cc4eaa683c45f" },
45213
- "win32-x64": { platform: "win64", bytes: 119768220, sha256: "86fb1fa7fbbda65f6f1572062c358803e61a6e6d9489e6619b37a61d609fa845", executableSha256: "ec0744f041cb6c35c439c85a5ac42d77b507838cd59cac005e25114025336c98" }
45341
+ "darwin-arm64": { platform: "mac-arm64", bytes: 98668949, sha256: "6d28839675b6f22dbd7ba8775dbdabcae7a5be37b482380b27b12f05b748b955", executableSha256: "111ee425ea5b0a01dcb19fda16452a9be0319749cba374b1c987d2437d8dcedb" },
45342
+ "darwin-x64": { platform: "mac-x64", bytes: 103712919, sha256: "aa178547f9751fbcf413e0f57915ddb72e83064d4d1c4a29dc1719b169f9cc2f", executableSha256: "33452695b1b6f18db2f796a422eca69b69ca8aacce36d1ff8c6f5d69eac1a635" },
45343
+ "linux-arm64": { platform: "linux-arm64", bytes: 120245582, sha256: "af0931a58d6bab688112d5ca1f7abd6d95c0b8a296ef34637f272795787774d7", executableSha256: "b6a9f0483c976fddc72c34ce1c983a1275163cdfb95576874bfed5ff07c53807" },
45344
+ "linux-x64": { platform: "linux64", bytes: 119695587, sha256: "7728775cf4a35464cd81c8eea2d44d6d32ccc0bd1edfa75aea7f32d146963d63", executableSha256: "ea90dee9cbd7b17f197eb6c386bd9f1ff636734ee99753cd2408658c45358bf2" },
45345
+ "win32-x64": { platform: "win64", bytes: 120466147, sha256: "9f405cfaf7bc08bf9e046e653cd3086c0faa1d4e25907de857f7e7f093a20122", executableSha256: "01176f6928faeb37ff018bc8884bbca8a0197f51b659d467f2501ab2c64f9fcf" }
45214
45346
  };
45215
45347
  }
45216
45348
  });
@@ -45592,7 +45724,7 @@ function agentBrowserIntegration(input) {
45592
45724
  for (const name of ["PATH", "AGENT_BROWSER_EXECUTABLE_PATH"]) {
45593
45725
  if (sourceEnv[name]) env[name] = sourceEnv[name];
45594
45726
  }
45595
- const bundle = packagedBrowser({ env: sourceEnv });
45727
+ const bundle = packagedBrowser({ env: sourceEnv, platform: input.platform, arch: input.arch });
45596
45728
  if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve10(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync18)) {
45597
45729
  env.AGENT_BROWSER_EXECUTABLE_PATH = bundle.chrome;
45598
45730
  }
@@ -7,6 +7,7 @@
7
7
  // verdict the app synthesizes is Full access, because that level is the
8
8
  // person's explicit, separately confirmed grant to answer every prompt.
9
9
  // Questions never come through here: a bot's question always reaches a human.
10
+ import { supportsApprovalMode } from "../shared/approval-mode.js";
10
11
  /** A failed delivery is a runtime error, not another permission decision.
11
12
  * An expired ask must never become a fresh Allow/Deny card. */
12
13
  export async function deliverFullAccessApproval(adapter, threadId, requestId, turnId, isCurrent = () => false) {
@@ -24,7 +25,9 @@ export async function deliverFullAccessApproval(adapter, threadId, requestId, tu
24
25
  }
25
26
  }
26
27
  /** Full access is the person's explicit grant to this receiving bot, including
27
- * delegated work. It never inherits the sender's mode or elevates another bot.
28
+ * delegated work. It never inherits the sender's mode or elevates another bot
29
+ * — with the one exception below (delegationInheritsFullAccess), applied
30
+ * where a Chief's delegated thread is created rather than here.
28
31
  * Custom is a provider-config choice rather than an app Full-access grant, so
29
32
  * peer-started Custom turns use Auto. Provider support and grant confirmation
30
33
  * are checked by the caller. */
@@ -33,6 +36,17 @@ export function approvalModeForOrigin(mode, origin) {
33
36
  return "auto";
34
37
  return mode;
35
38
  }
39
+ /** Whether work a bot hands to a teammate runs with Full access. Only a Chief
40
+ * of Staff passes access on, and only the Full access the person gave it for
41
+ * the conversation it is delegating from: the Chief exists to get the team's
42
+ * work done without the person answering every card, and a teammate stopping
43
+ * that work to ask defeats the grant. The recipient's engine has to implement
44
+ * Full, or the work keeps the recipient's own level. A bot never elevates
45
+ * itself this way. */
46
+ export function delegationInheritsFullAccess(input) {
47
+ return input.senderIsChief && input.senderHasFullAccess && !input.sameBot
48
+ && supportsApprovalMode(input.recipientDriverKind, "full");
49
+ }
36
50
  // Tools that ask a PERSON something. A question exists so that a human
37
51
  // decides; any mode answering one on their behalf defeats the only reason
38
52
  // it was asked. They normally arrive typed as questions and never reach a
@@ -0,0 +1,22 @@
1
+ /** Lazy exclusive claims for auto-resolved Local VM attaches (issue #1361).
2
+ * The table is keyed by thread and owned by the dispatch generation; the
3
+ * claim closure itself lives in index.ts next to the turn state it needs.
4
+ * Kept free of index.ts imports so the fire-once semantics stay unit-testable. */
5
+ /** Fire a thread's lazy claim exactly once, fenced by the dispatch
6
+ * generation. A rejected claim KEEPS the slot and marks it failed: the
7
+ * computer-control gate then keeps answering held for that generation, so
8
+ * the bridge refuses every later screen call instead of forwarding it onto
9
+ * a VM this turn never claimed. `begin` staying set means the claim never
10
+ * re-fires; turn settle (releaseTurnResources / releaseLocalVmThread in
11
+ * index.ts) clears the slot, fenced by owner generation. A rejection
12
+ * mutates only the slot it fired from, so a stale claim can never remove
13
+ * or mark a newer generation's slot on the same thread. */
14
+ export function startAutoVmClaim(table, threadId, generation) {
15
+ const slot = table.get(threadId);
16
+ if (!slot || slot.owner.threadId !== threadId || slot.owner.generation !== generation || slot.begin)
17
+ return;
18
+ slot.begin = slot.claim().then(() => { slot.claimed = true; }, (error) => {
19
+ slot.failed = true;
20
+ slot.failure = error instanceof Error ? error.message : String(error);
21
+ });
22
+ }
@@ -1,15 +1,16 @@
1
- // Reviewed vendor assets, downloaded and hashed on 2026-09-07. Update all
1
+ // Reviewed vendor assets, downloaded and hashed on 2026-09-16. Update all
2
2
  // pins together and run each platform's packaged, offline browser smoke test.
3
3
  // This is Chromium's headless shell, not full Chrome (which includes Widevine).
4
4
  import { join } from "node:path";
5
5
  import { agentBrowserReleaseUrl, agentBrowserReleaseVersion, resolveAgentBrowserReleaseAsset, } from "./browser-engine-release.js";
6
- export const CHROME_VERSION = "152.0.7977.82";
7
- export const SUPPORTED_BROWSER_TARGETS = ["darwin-arm64", "darwin-x64", "linux-x64", "win32-x64"];
6
+ export const CHROME_VERSION = "153.0.8010.47";
7
+ export const SUPPORTED_BROWSER_TARGETS = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-x64"];
8
8
  const CHROME_ASSETS = {
9
- "darwin-arm64": { platform: "mac-arm64", bytes: 97760996, sha256: "1615f063c894aa824fd55c89f8f05e9904f63cce0c95d0cbce7394d77884fba5", executableSha256: "ac18537fb2bc2b72b0440893b6ea5d4ef3bacc6220b1e9a1693c73f8b1045fbe" },
10
- "darwin-x64": { platform: "mac-x64", bytes: 102895824, sha256: "c903707292c6aaed7c6e572c0bb5e6645454e45d9e0e6db27e876b18e9165f31", executableSha256: "2faefd55d4e47764956e31e338d362a1e794037fa772a426ffd510f122f33f19" },
11
- "linux-x64": { platform: "linux64", bytes: 119454769, sha256: "0ca12ea26b502a83e32db334a17883c315348845efc071d908de7a6d94a97eff", executableSha256: "1301a5024fef1d58c4cfbde5d44794cb18c5b2cbbe4f3c64f81cc4eaa683c45f" },
12
- "win32-x64": { platform: "win64", bytes: 119768220, sha256: "86fb1fa7fbbda65f6f1572062c358803e61a6e6d9489e6619b37a61d609fa845", executableSha256: "ec0744f041cb6c35c439c85a5ac42d77b507838cd59cac005e25114025336c98" },
9
+ "darwin-arm64": { platform: "mac-arm64", bytes: 98668949, sha256: "6d28839675b6f22dbd7ba8775dbdabcae7a5be37b482380b27b12f05b748b955", executableSha256: "111ee425ea5b0a01dcb19fda16452a9be0319749cba374b1c987d2437d8dcedb" },
10
+ "darwin-x64": { platform: "mac-x64", bytes: 103712919, sha256: "aa178547f9751fbcf413e0f57915ddb72e83064d4d1c4a29dc1719b169f9cc2f", executableSha256: "33452695b1b6f18db2f796a422eca69b69ca8aacce36d1ff8c6f5d69eac1a635" },
11
+ "linux-arm64": { platform: "linux-arm64", bytes: 120245582, sha256: "af0931a58d6bab688112d5ca1f7abd6d95c0b8a296ef34637f272795787774d7", executableSha256: "b6a9f0483c976fddc72c34ce1c983a1275163cdfb95576874bfed5ff07c53807" },
12
+ "linux-x64": { platform: "linux64", bytes: 119695587, sha256: "7728775cf4a35464cd81c8eea2d44d6d32ccc0bd1edfa75aea7f32d146963d63", executableSha256: "ea90dee9cbd7b17f197eb6c386bd9f1ff636734ee99753cd2408658c45358bf2" },
13
+ "win32-x64": { platform: "win64", bytes: 120466147, sha256: "9f405cfaf7bc08bf9e046e653cd3086c0faa1d4e25907de857f7e7f093a20122", executableSha256: "01176f6928faeb37ff018bc8884bbca8a0197f51b659d467f2501ab2c64f9fcf" },
13
14
  };
14
15
  export function browserBundleSpec(target) {
15
16
  if (!Object.hasOwn(CHROME_ASSETS, target))
@@ -375,7 +375,7 @@ export function agentBrowserIntegration(input) {
375
375
  if (sourceEnv[name])
376
376
  env[name] = sourceEnv[name];
377
377
  }
378
- const bundle = packagedBrowser({ env: sourceEnv });
378
+ const bundle = packagedBrowser({ env: sourceEnv, platform: input.platform, arch: input.arch });
379
379
  if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync)) {
380
380
  env.AGENT_BROWSER_EXECUTABLE_PATH = bundle.chrome;
381
381
  }
@@ -61,6 +61,64 @@ export function cancelChannelMessage(groupId, queueId) {
61
61
  }
62
62
  return false;
63
63
  }
64
+ /** Atomically lift a channel thread's whole queue out for a live steer. The
65
+ * entry leaves first so a room that settles while the adapter is still
66
+ * thinking can never also drain the same words as a follow-up turn. Only
67
+ * the HEAD can be lifted: the success path steers and settles items[0], so
68
+ * a request naming a later item must not lift the queue at all (it would
69
+ * steer and delete a different message's words). The caller must either
70
+ * restore the held queue or settle its head. */
71
+ export function holdChannelQueue(groupId, threadId, queueId) {
72
+ const entry = queues.get(threadId);
73
+ if (!entry || entry.groupId !== groupId || entry.items[0]?.id !== queueId)
74
+ return null;
75
+ queues.delete(threadId);
76
+ return { groupId, threadId, items: entry.items };
77
+ }
78
+ /** Put a held queue back after the steer was refused. Words queued while the
79
+ * hold was open keep their place behind the restored items. */
80
+ export function restoreHeldChannelQueue(held) {
81
+ const existing = queues.get(held.threadId);
82
+ if (existing && existing.groupId !== held.groupId)
83
+ throw new Error("queued task belongs to another channel");
84
+ queues.set(held.threadId, {
85
+ groupId: held.groupId,
86
+ items: existing ? [...held.items, ...existing.items] : held.items,
87
+ });
88
+ }
89
+ /** Resolve the held head's reply target through a caller-supplied resolver.
90
+ * The queue is already lifted out of the map here, so a target that cannot
91
+ * be resolved (missing, non-text, empty — state drift between queueing and
92
+ * the steer) must not strand the held words outside it until restart:
93
+ * restore first, then let the error propagate to the request. */
94
+ export function resolveHeldReplyTarget(held, resolve) {
95
+ const head = held.items[0];
96
+ if (!head?.replyToId)
97
+ return undefined;
98
+ try {
99
+ return resolve(held.threadId, head.replyToId);
100
+ }
101
+ catch (error) {
102
+ restoreHeldChannelQueue(held);
103
+ throw error;
104
+ }
105
+ }
106
+ /** Mark a held queue's head delivered — its words were folded into the
107
+ * running turn — and re-queue the rest for the room's normal one-at-a-time
108
+ * drain. A restart must not replay the steered head as a fresh follow-up. */
109
+ export function settleHeldChannelQueueHead(held) {
110
+ const [head, ...rest] = held.items;
111
+ settleChatFollowups([head.id], null);
112
+ if (rest.length === 0)
113
+ return;
114
+ const existing = queues.get(held.threadId);
115
+ if (existing && existing.groupId !== held.groupId)
116
+ throw new Error("queued task belongs to another channel");
117
+ queues.set(held.threadId, {
118
+ groupId: held.groupId,
119
+ items: existing ? [...rest, ...existing.items] : rest,
120
+ });
121
+ }
64
122
  /**
65
123
  * Start at most one follow-up per idle channel. Starting it synchronously
66
124
  * marks the channel working again; its completion calls this drain for the
@@ -29,12 +29,12 @@ export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, trustedOpen
29
29
  });
30
30
  const delegation = canDelegate
31
31
  ? boundedCoordination
32
- ? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room, everything you send a teammate continues your one standing conversation with them, using their own model and permissions, so they still have the context of your earlier assignments. Busy teammates queue. Give self-contained briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran."
32
+ ? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room, everything you send a teammate continues your one standing conversation with them, using their own model and permissions, so they still have the context of your earlier assignments. Busy teammates queue. Give self-contained briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran. A refusal from coordinate_bots means nothing was sent: fix what it names (usually the id — copy it from list_bots or your roster; a unique teammate name also works) and retry, and never describe a handoff the tool did not accept."
33
33
  : [
34
34
  "Use list_bots to confirm the live roster and IDs. When assigning work to a teammate, use delegate_bot: it returns immediately, keeps you available to the user, and delivers the teammate's outcome back into this conversation automatically — success or failure. When the result arrives you are woken with it: report it to the user and act. If the teammate fails or stalls, tell the user plainly and decide the next step yourself.",
35
35
  "After delegate_bot accepts the task, acknowledge the handoff and continue with any independent work or end your turn. Do not call wait_delegation or repeatedly poll check_delegation in the same turn.",
36
36
  "Use ask_bot only for a brief consultation whose answer you must have before writing your current response. Never use ask_bot for an assigned task, background work, or anything potentially long-running.",
37
- "Delegate with a clear, self-contained brief. Say that the task is assigned, not completed; only claim completion after the teammate's result has actually arrived.",
37
+ "Delegate with a clear, self-contained brief. Say that the task is assigned, not completed; only claim completion after the teammate's result has actually arrived. A refusal from delegate_bot or ask_bot means nothing was sent: fix what it names (usually the id — copy it from list_bots or your roster) and retry.",
38
38
  "You may assign work to more than one teammate when the request genuinely benefits. Stay responsive while they work, then combine their returned results when the user asks for a synthesis.",
39
39
  ].join(" ")
40
40
  : "Your current engine cannot contact teammates. Be honest about that limitation and ask the user to choose a delegation-compatible engine before promising coordinated work.";