openmausbot 0.1.59 → 0.1.60
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.
- package/dist/assets/{index-D914UwuG.js → index-BIzf_lw1.js} +1 -1
- package/dist/assets/{index-Cvk0KgFW.js → index-Tha1umgl.js} +137 -137
- package/dist/index.html +1 -1
- package/dist-server/index.js +46 -28
- package/dist-server/server/auto-approve.js +39 -12
- package/dist-server/server/index.js +32 -20
- package/dist-server/shared/approval-mode.js +3 -2
- package/package.json +1 -1
package/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/app-icon.svg" />
|
|
7
7
|
<title>OpenMausBot</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Tha1umgl.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-oPMv6a_k.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist-server/index.js
CHANGED
|
@@ -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", "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
14829
|
-
if (context.source === "sensitive-guard") return "
|
|
14830
|
-
return "
|
|
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:
|
|
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
|
-
|
|
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" ? "
|
|
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
|
-
//
|
|
59145
|
-
//
|
|
59146
|
-
held: approvalHeldReason(
|
|
59147
|
-
|
|
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
|
|
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
|
|
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 "
|
|
226
|
+
return "approval.held.native";
|
|
199
227
|
if (!context.permission)
|
|
200
228
|
return undefined;
|
|
201
|
-
if (context.requiresExplicitApproval)
|
|
202
|
-
return "
|
|
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 "
|
|
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
|
-
|
|
214
|
-
|
|
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 "
|
|
247
|
+
return "approval.held.destructive";
|
|
221
248
|
if (context.source === "sensitive-guard")
|
|
222
|
-
return "
|
|
223
|
-
return "
|
|
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:
|
|
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
|
-
|
|
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
|
-
? "
|
|
2541
|
-
: "
|
|
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
|
-
//
|
|
2584
|
-
//
|
|
2585
|
-
held: approvalHeldReason(
|
|
2586
|
-
|
|
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
|
|
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
|
|
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
|
|
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", "
|
|
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);
|