openmausbot 0.1.59 → 0.1.61

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.
@@ -14563,7 +14563,7 @@ function supportsApprovalMode(driverKind, mode) {
14563
14563
  return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo"].includes(driverKind ?? "");
14564
14564
  }
14565
14565
  function requiresNativeApproval(driverKind, mode) {
14566
- return mode === "auto" || mode === "full" && ["claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent"].includes(driverKind);
14566
+ return mode === "auto" || mode === "full" && ["claudeAgent", "cursorAgent", "grokAgent"].includes(driverKind);
14567
14567
  }
14568
14568
  function isApprovalMode(value) {
14569
14569
  return typeof value === "string" && APPROVAL_MODES.includes(value);
@@ -14812,22 +14812,33 @@ function autoVerdict(bot, tool, summary, context) {
14812
14812
  return { approve: null, source: "no-grant" };
14813
14813
  }
14814
14814
  function approvalHeldReason(context) {
14815
- if (context.source === "native-approval") return "The provider requires your approval for this action.";
14815
+ const key3 = approvalHeldNote(context);
14816
+ return key3 && HELD_NOTE[key3];
14817
+ }
14818
+ var HELD_NOTE = {
14819
+ "approval.held.native": "The provider requires your approval for this action.",
14820
+ "approval.held.sandbox": "This changes the provider sandbox, so only Full access can approve it automatically.",
14821
+ "approval.held.localComputer": "Controlling your computer is never covered by Always allow, so this needs you.",
14822
+ "approval.held.unattended": "A webhook or another bot started this turn, so Approve for me is paused and every action asks.",
14823
+ "approval.held.unattendedFullAccess": "A webhook or another bot started this turn, so Approve for me is paused and every action asks. Full access keeps working unattended.",
14824
+ "approval.held.destructive": "This looks destructive, so Approve for me stopped to ask.",
14825
+ "approval.held.sensitive": "This touches credentials, so Approve for me stopped to ask.",
14826
+ "approval.held.needsYou": "This action needs you, so Approve for me stopped to ask.",
14827
+ "approval.held.undeliveredFull": "Full access couldn't deliver this approval.",
14828
+ "approval.held.undelivered": "Approve for me couldn't answer this one."
14829
+ };
14830
+ function approvalHeldNote(context) {
14831
+ if (context.source === "native-approval") return "approval.held.native";
14816
14832
  if (!context.permission) return void 0;
14817
- if (context.requiresExplicitApproval) {
14818
- return "This changes the provider sandbox, so only Full access can approve it automatically.";
14819
- }
14820
- if (context.source === "local-computer-block") {
14821
- return "Controlling your computer is never covered by Always allow, so this needs you.";
14822
- }
14833
+ if (context.requiresExplicitApproval) return "approval.held.sandbox";
14834
+ if (context.source === "local-computer-block") return "approval.held.localComputer";
14823
14835
  if (context.mode !== "auto") return void 0;
14824
14836
  if (context.unattended) {
14825
- const hint = context.fullAccessAvailable ? " Full access keeps working unattended." : "";
14826
- return `A webhook or another bot started this turn, so Approve for me is paused and every action asks.${hint}`;
14837
+ return context.fullAccessAvailable ? "approval.held.unattendedFullAccess" : "approval.held.unattended";
14827
14838
  }
14828
- if (context.source === "destructive-guard") return "This looks destructive, so Approve for me stopped to ask.";
14829
- if (context.source === "sensitive-guard") return "This touches credentials, so Approve for me stopped to ask.";
14830
- return "This action needs you, so Approve for me stopped to ask.";
14839
+ if (context.source === "destructive-guard") return "approval.held.destructive";
14840
+ if (context.source === "sensitive-guard") return "approval.held.sensitive";
14841
+ return "approval.held.needsYou";
14831
14842
  }
14832
14843
 
14833
14844
  // server/schema.ts
@@ -59052,17 +59063,21 @@ bus.subscribe((event) => {
59052
59063
  const permission = event.requestType === "permission";
59053
59064
  const asker = bot ?? (speaker ? store.bot(speaker.botId) : void 0);
59054
59065
  const unattended = permission && asker && event.requestId ? isUnattended(asker.id) : false;
59066
+ const effectiveApprovalMode = asker ? approvalModeForTurn(asker, isInternalTurn(event.threadId)) : "ask";
59055
59067
  const verdict = permission && asker && event.requestId ? autoVerdict({
59056
59068
  // the same origin the dispatch used, so a peer-started turn's
59057
59069
  // residual asks are judged as Approve for me here too
59058
- approvalMode: approvalModeForTurn(asker, isInternalTurn(event.threadId)),
59070
+ approvalMode: effectiveApprovalMode,
59059
59071
  autoApprove: false,
59060
59072
  alwaysAllow: asker.alwaysAllow
59061
59073
  }, event.tool, event.summary, {
59062
59074
  unattended,
59063
59075
  scope: event.approvalScope,
59064
59076
  requiresExplicitApproval: event.requiresExplicitApproval,
59065
- nativeApproval: requiresNativeApproval(event.provider, approvalModeForTurn(asker))
59077
+ // Antigravity's residual asks must use a peer-started turn's
59078
+ // effective safe Auto mode, not its durable Full grant.
59079
+ // Preserve the existing policy of every other provider.
59080
+ nativeApproval: requiresNativeApproval(event.provider, event.provider === "antigravityAgent" ? effectiveApprovalMode : approvalModeForTurn(asker))
59066
59081
  }) : null;
59067
59082
  if (verdict?.approve && asker && event.requestId) {
59068
59083
  const settled = verdict.approve;
@@ -59105,7 +59120,8 @@ bus.subscribe((event) => {
59105
59120
  scope: event.approvalScope,
59106
59121
  requiresExplicitApproval: event.requiresExplicitApproval
59107
59122
  }),
59108
- held: verdict.source === "full-access" ? "Full access couldn't deliver this approval." : "Approve for me couldn't answer this one.",
59123
+ held: HELD_NOTE[verdict.source === "full-access" ? "approval.held.undeliveredFull" : "approval.held.undelivered"],
59124
+ heldCode: verdict.source === "full-access" ? "approval.held.undeliveredFull" : "approval.held.undelivered",
59109
59125
  approvalScope: event.approvalScope
59110
59126
  }
59111
59127
  });
@@ -59125,6 +59141,14 @@ bus.subscribe((event) => {
59125
59141
  })();
59126
59142
  break;
59127
59143
  }
59144
+ const heldContext = {
59145
+ source: verdict?.source,
59146
+ permission,
59147
+ requiresExplicitApproval: event.requiresExplicitApproval,
59148
+ mode: asker ? approvalModeForOrigin(approvalModeFor(asker), { peerInitiated: isInternalTurn(event.threadId) }) : "ask",
59149
+ unattended: Boolean(unattended),
59150
+ fullAccessAvailable: asker && !isInternalTurn(event.threadId) ? supportsApprovalMode(registry2.cliTarget(asker.modelSelection.instanceId)?.driverKind, "full") : false
59151
+ };
59128
59152
  const message3 = pushMessage({
59129
59153
  role: "bot",
59130
59154
  kind: "options",
@@ -59141,16 +59165,10 @@ bus.subscribe((event) => {
59141
59165
  scope: event.approvalScope,
59142
59166
  requiresExplicitApproval: event.requiresExplicitApproval
59143
59167
  }) : void 0,
59144
- // A card can outlive the bot record that raised it. Without one
59145
- // there is no mode to explain, but the sandbox note still applies.
59146
- held: approvalHeldReason({
59147
- source: verdict?.source,
59148
- permission,
59149
- requiresExplicitApproval: event.requiresExplicitApproval,
59150
- mode: asker ? approvalModeForOrigin(approvalModeFor(asker), { peerInitiated: isInternalTurn(event.threadId) }) : "ask",
59151
- unattended: Boolean(unattended),
59152
- fullAccessAvailable: asker && !isInternalTurn(event.threadId) ? supportsApprovalMode(registry2.cliTarget(asker.modelSelection.instanceId)?.driverKind, "full") : false
59153
- }),
59168
+ // The text stays for cards saved before heldCode existed, and for
59169
+ // clients that do not know the key yet.
59170
+ held: approvalHeldReason(heldContext),
59171
+ heldCode: approvalHeldNote(heldContext),
59154
59172
  approvalScope: event.approvalScope
59155
59173
  }
59156
59174
  });
@@ -64544,7 +64562,7 @@ data: ${JSON.stringify({ kind: "ping" })}
64544
64562
  const existingApprovalMode = approvalModeFor(existing);
64545
64563
  if ((existingApprovalMode === "full" || existingApprovalMode === "custom") && (!supportsApprovalMode(registry2.cliTarget(checked.selection.instanceId)?.driverKind, existingApprovalMode) || registry2.cliTarget(checked.selection.instanceId)?.driverKind !== registry2.cliTarget(existing.modelSelection.instanceId)?.driverKind)) {
64546
64564
  return json3(res, 400, {
64547
- error: "Changing providers with elevated permissions requires choosing Ask or Auto first"
64565
+ error: "Changing providers with elevated permissions requires choosing Ask first"
64548
64566
  });
64549
64567
  }
64550
64568
  const bot = store.patchBot(existing.id, { modelSelection: checked.selection });
@@ -64727,7 +64745,7 @@ data: ${JSON.stringify({ kind: "ping" })}
64727
64745
  const targetSelection = normalizedSelection ?? existingBot?.modelSelection;
64728
64746
  if ((requestedApprovalMode === "full" || requestedApprovalMode === "custom") && (body.approvalMode !== void 0 || normalizedSelection !== void 0) && (!targetSelection || !supportsApprovalMode(registry2.cliTarget(targetSelection.instanceId)?.driverKind, requestedApprovalMode) || existingBot && normalizedSelection && registry2.cliTarget(normalizedSelection.instanceId)?.driverKind !== registry2.cliTarget(existingBot.modelSelection.instanceId)?.driverKind)) {
64729
64747
  return json3(res, 400, {
64730
- error: "This provider does not support the selected approval level, or changing providers requires choosing Ask or Auto first"
64748
+ error: "This provider does not support the selected approval level, or changing providers requires choosing Ask first"
64731
64749
  });
64732
64750
  }
64733
64751
  const requiresPrivateApprovalTransition = (requestedApprovalMode === "full" || requestedApprovalMode === "custom") && currentApprovalMode !== requestedApprovalMode || currentApprovalMode === "custom" && requestedApprovalMode !== "custom";
@@ -194,31 +194,58 @@ export function autoDecision(bot, tool, summary, context) {
194
194
  * delegating between bots, every card looks like this until someone answers.
195
195
  * Say that plainly, and name the mode that keeps running. */
196
196
  export function approvalHeldReason(context) {
197
+ const key = approvalHeldNote(context);
198
+ return key && HELD_NOTE[key];
199
+ }
200
+ /** Every fixed note a held card can show, by catalog key.
201
+ *
202
+ * The card is the last thing between a bot and someone's filesystem, so the
203
+ * one line explaining why it stopped should not be the one line still in
204
+ * English. The client translates by key and falls back to this text, which
205
+ * the server keeps sending: cards saved before the key existed still render,
206
+ * and so do the free-text apply errors that have no key at all.
207
+ *
208
+ * The unattended hint is a whole second sentence rather than a suffix. A
209
+ * translator needs the sentence, not two halves to reassemble. */
210
+ export const HELD_NOTE = {
211
+ "approval.held.native": "The provider requires your approval for this action.",
212
+ "approval.held.sandbox": "This changes the provider sandbox, so only Full access can approve it automatically.",
213
+ "approval.held.localComputer": "Controlling your computer is never covered by Always allow, so this needs you.",
214
+ "approval.held.unattended": "A webhook or another bot started this turn, so Approve for me is paused and every action asks.",
215
+ "approval.held.unattendedFullAccess": "A webhook or another bot started this turn, so Approve for me is paused and every action asks. Full access keeps working unattended.",
216
+ "approval.held.destructive": "This looks destructive, so Approve for me stopped to ask.",
217
+ "approval.held.sensitive": "This touches credentials, so Approve for me stopped to ask.",
218
+ "approval.held.needsYou": "This action needs you, so Approve for me stopped to ask.",
219
+ "approval.held.undeliveredFull": "Full access couldn't deliver this approval.",
220
+ "approval.held.undelivered": "Approve for me couldn't answer this one.",
221
+ };
222
+ /** Which note, as a key. approvalHeldReason is this plus the English text, so
223
+ * the branching that decides the note lives in exactly one place. */
224
+ export function approvalHeldNote(context) {
197
225
  if (context.source === "native-approval")
198
- return "The provider requires your approval for this action.";
226
+ return "approval.held.native";
199
227
  if (!context.permission)
200
228
  return undefined;
201
- if (context.requiresExplicitApproval) {
202
- return "This changes the provider sandbox, so only Full access can approve it automatically.";
203
- }
229
+ if (context.requiresExplicitApproval)
230
+ return "approval.held.sandbox";
204
231
  // Host control reaches this only over a grant that would otherwise have
205
232
  // fired, in a mode that explains nothing else. "I pressed Always allow and
206
233
  // it asked anyway" is the whole confusion, so answer that and not the mode.
207
- if (context.source === "local-computer-block") {
208
- return "Controlling your computer is never covered by Always allow, so this needs you.";
209
- }
234
+ if (context.source === "local-computer-block")
235
+ return "approval.held.localComputer";
210
236
  if (context.mode !== "auto")
211
237
  return undefined;
212
238
  if (context.unattended) {
213
- const hint = context.fullAccessAvailable ? " Full access keeps working unattended." : "";
214
- return `A webhook or another bot started this turn, so Approve for me is paused and every action asks.${hint}`;
239
+ return context.fullAccessAvailable
240
+ ? "approval.held.unattendedFullAccess"
241
+ : "approval.held.unattended";
215
242
  }
216
243
  // A guard names itself. Both stop the same mode, but one is about damage
217
244
  // and the other about secrets, and a read-only .env card that says
218
245
  // "destructive" teaches people to stop reading these.
219
246
  if (context.source === "destructive-guard")
220
- return "This looks destructive, so Approve for me stopped to ask.";
247
+ return "approval.held.destructive";
221
248
  if (context.source === "sensitive-guard")
222
- return "This touches credentials, so Approve for me stopped to ask.";
223
- return "This action needs you, so Approve for me stopped to ask.";
249
+ return "approval.held.sensitive";
250
+ return "approval.held.needsYou";
224
251
  }
@@ -11,7 +11,7 @@ import { BOT_PROFILE_LIMITS } from "../shared/bot-profile.js";
11
11
  import { approvalModeFor, supportsApprovalMode, requiresNativeApproval, isEmergencyApprovalDowngrade, isApprovalMode, } from "../shared/approval-mode.js";
12
12
  import { escapeAttribute } from "../src/lib/composer-attachments.js";
13
13
  import { CREDENTIAL_TARGETS, credentialResumeOutcome, credentialIsConfigured, isReusableCredentialRequest, isCredentialTargetId, } from "../shared/credential-request.js";
14
- import { approvalHeldReason, approvalModeForOrigin, autoVerdict, rememberableApprovalKey } from "./auto-approve.js";
14
+ import { HELD_NOTE, approvalHeldNote, approvalHeldReason, approvalModeForOrigin, autoVerdict, rememberableApprovalKey } from "./auto-approve.js";
15
15
  import { requestReview, resolveAutoReviewMode, shouldReview } from "./auto-review.js";
16
16
  import { updateClaudeCli } from "./claude-update.js";
17
17
  import { BrowserCleanupCoordinator, finalizeBrowserCleanupMutation, requireBrowserCleanupAcknowledged, } from "./browser-lifecycle-cleanup.js";
@@ -2467,18 +2467,23 @@ bus.subscribe((event) => {
2467
2467
  // the guards in auto-approve.ts; explicitly acknowledged Full does not.
2468
2468
  const asker = bot ?? (speaker ? store.bot(speaker.botId) : undefined);
2469
2469
  const unattended = permission && asker && event.requestId ? isUnattended(asker.id) : false;
2470
+ const effectiveApprovalMode = asker ? approvalModeForTurn(asker, isInternalTurn(event.threadId)) : "ask";
2470
2471
  const verdict = permission && asker && event.requestId
2471
2472
  ? autoVerdict({
2472
2473
  // the same origin the dispatch used, so a peer-started turn's
2473
2474
  // residual asks are judged as Approve for me here too
2474
- approvalMode: approvalModeForTurn(asker, isInternalTurn(event.threadId)),
2475
+ approvalMode: effectiveApprovalMode,
2475
2476
  autoApprove: false,
2476
2477
  alwaysAllow: asker.alwaysAllow,
2477
2478
  }, event.tool, event.summary, {
2478
2479
  unattended,
2479
2480
  scope: event.approvalScope,
2480
2481
  requiresExplicitApproval: event.requiresExplicitApproval,
2481
- nativeApproval: requiresNativeApproval(event.provider, approvalModeForTurn(asker)),
2482
+ // Antigravity's residual asks must use a peer-started turn's
2483
+ // effective safe Auto mode, not its durable Full grant.
2484
+ // Preserve the existing policy of every other provider.
2485
+ nativeApproval: requiresNativeApproval(event.provider, event.provider === "antigravityAgent"
2486
+ ? effectiveApprovalMode : approvalModeForTurn(asker)),
2482
2487
  })
2483
2488
  : null;
2484
2489
  if (verdict?.approve && asker && event.requestId) {
@@ -2536,9 +2541,12 @@ bus.subscribe((event) => {
2536
2541
  scope: event.approvalScope,
2537
2542
  requiresExplicitApproval: event.requiresExplicitApproval,
2538
2543
  }),
2539
- held: verdict.source === "full-access"
2540
- ? "Full access couldn't deliver this approval."
2541
- : "Approve for me couldn't answer this one.",
2544
+ held: HELD_NOTE[verdict.source === "full-access"
2545
+ ? "approval.held.undeliveredFull"
2546
+ : "approval.held.undelivered"],
2547
+ heldCode: verdict.source === "full-access"
2548
+ ? "approval.held.undeliveredFull"
2549
+ : "approval.held.undelivered",
2542
2550
  approvalScope: event.approvalScope,
2543
2551
  },
2544
2552
  });
@@ -2558,6 +2566,18 @@ bus.subscribe((event) => {
2558
2566
  })();
2559
2567
  break;
2560
2568
  }
2569
+ // A card can outlive the bot record that raised it. Without one there is
2570
+ // no mode to explain, but the sandbox note still applies.
2571
+ const heldContext = {
2572
+ source: verdict?.source,
2573
+ permission,
2574
+ requiresExplicitApproval: event.requiresExplicitApproval,
2575
+ mode: asker ? approvalModeForOrigin(approvalModeFor(asker), { peerInitiated: isInternalTurn(event.threadId) }) : "ask",
2576
+ unattended: Boolean(unattended),
2577
+ fullAccessAvailable: asker && !isInternalTurn(event.threadId)
2578
+ ? supportsApprovalMode(registry.cliTarget(asker.modelSelection.instanceId)?.driverKind, "full")
2579
+ : false,
2580
+ };
2561
2581
  const message = pushMessage({
2562
2582
  role: "bot",
2563
2583
  kind: "options",
@@ -2580,18 +2600,10 @@ bus.subscribe((event) => {
2580
2600
  requiresExplicitApproval: event.requiresExplicitApproval,
2581
2601
  })
2582
2602
  : undefined,
2583
- // A card can outlive the bot record that raised it. Without one
2584
- // there is no mode to explain, but the sandbox note still applies.
2585
- held: approvalHeldReason({
2586
- source: verdict?.source,
2587
- permission,
2588
- requiresExplicitApproval: event.requiresExplicitApproval,
2589
- mode: asker ? approvalModeForOrigin(approvalModeFor(asker), { peerInitiated: isInternalTurn(event.threadId) }) : "ask",
2590
- unattended: Boolean(unattended),
2591
- fullAccessAvailable: asker && !isInternalTurn(event.threadId)
2592
- ? supportsApprovalMode(registry.cliTarget(asker.modelSelection.instanceId)?.driverKind, "full")
2593
- : false,
2594
- }),
2603
+ // The text stays for cards saved before heldCode existed, and for
2604
+ // clients that do not know the key yet.
2605
+ held: approvalHeldReason(heldContext),
2606
+ heldCode: approvalHeldNote(heldContext),
2595
2607
  approvalScope: event.approvalScope,
2596
2608
  },
2597
2609
  });
@@ -9395,7 +9407,7 @@ const handleRequest = async (req, res) => {
9395
9407
  (!supportsApprovalMode(registry.cliTarget(checked.selection.instanceId)?.driverKind, existingApprovalMode) ||
9396
9408
  registry.cliTarget(checked.selection.instanceId)?.driverKind !== registry.cliTarget(existing.modelSelection.instanceId)?.driverKind)) {
9397
9409
  return json(res, 400, {
9398
- error: "Changing providers with elevated permissions requires choosing Ask or Auto first",
9410
+ error: "Changing providers with elevated permissions requires choosing Ask first",
9399
9411
  });
9400
9412
  }
9401
9413
  // patchBot persists first and emits the canonical bot change, which the
@@ -9647,7 +9659,7 @@ const handleRequest = async (req, res) => {
9647
9659
  (!targetSelection || !supportsApprovalMode(registry.cliTarget(targetSelection.instanceId)?.driverKind, requestedApprovalMode) ||
9648
9660
  (existingBot && normalizedSelection && registry.cliTarget(normalizedSelection.instanceId)?.driverKind !== registry.cliTarget(existingBot.modelSelection.instanceId)?.driverKind))) {
9649
9661
  return json(res, 400, {
9650
- error: "This provider does not support the selected approval level, or changing providers requires choosing Ask or Auto first",
9662
+ error: "This provider does not support the selected approval level, or changing providers requires choosing Ask first",
9651
9663
  });
9652
9664
  }
9653
9665
  const requiresPrivateApprovalTransition = ((requestedApprovalMode === "full" || requestedApprovalMode === "custom") &&
@@ -13,9 +13,10 @@ export function hasNativeAutoReview(driverKind) {
13
13
  }
14
14
  /** A native reviewer has already declined to decide, or Auto has no native
15
15
  * equivalent. Never second-guess that request with the app's heuristic rules.
16
- * OpenCode's Full mode is implemented by approving individual ACP requests. */
16
+ * Antigravity and OpenCode Full approve residual ACP tool-permission requests
17
+ * through the app's explicit Full-access grant. Questions remain interactive. */
17
18
  export function requiresNativeApproval(driverKind, mode) {
18
- return mode === "auto" || (mode === "full" && ["claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent"].includes(driverKind));
19
+ return mode === "auto" || (mode === "full" && ["claudeAgent", "cursorAgent", "grokAgent"].includes(driverKind));
19
20
  }
20
21
  export function isApprovalMode(value) {
21
22
  return typeof value === "string" && APPROVAL_MODES.includes(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmausbot",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "description": "Run the OpenMausBot server anywhere and pair your devices to it",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",