diff --git a/docs/fr/security-model.md b/docs/fr/security-model.md index 7e39db90b..185d804ae 100644 --- a/docs/fr/security-model.md +++ b/docs/fr/security-model.md @@ -100,7 +100,7 @@ La menace principale : une page malveillante conçoit un contenu qui, lorsqu'il | **Exposition d'outils par niveau** | Les niveaux de fournisseur (`compact | mid | full`) limitent la surface d'agent navigateur normale pour les modèles plus petits. Compact obtient la surface d'action la plus petite ; Mid ajoute des outils de tâches courantes ; Full ajoute des solutions de repli UI/DOM avancées. Dev en Compact est bloqué. | | **Plan avant Act** | Lorsqu'il est activé, les exécutions en mode action produisent d'abord un plan structuré et attendent l'approbation du panneau latéral avant que tout outil navigateur ne s'exécute. Les exécutions planifiées peuvent auto-approuver le plan uniquement via la politique du planificateur. | | **Limite d'import de compétence** | Les compétences peuvent exposer des outils HTTP en lecture seule et des outils de téléchargement via un manifeste `webbrain-tools`. Importer ou garder la compétence activée est la décision de confiance pour le point de terminaison HTTPS déclaré ; les outils de compétence déclarés utilisent `credentials: "omit"` et doivent marquer les résultats tiers `resultPolicy: "untrusted"`. Les outils de compétence de téléchargement nécessitent toujours un mode action et la passerelle de permission Téléchargements normale avant d'enregistrer des fichiers. | -| **`/allow-api`** | Un indicateur `/allow-api` par conversation qui *supprime* la demande de permission pour les sorties réseau avec méthode d'écriture (`fetch_url`/`research_url` avec POST/PUT/PATCH/DELETE). Il ne supprime PAS la sortie GET ni aucune autre capacité. S'efface à la réinitialisation de la conversation. | +| **Dérogation de mutation API** | Un indicateur `/allow-api` par conversation, ou le réglage persistant désactivé par défaut sous Général → Avancé, *supprime* la demande de permission pour les sorties réseau avec méthode d'écriture (`fetch_url`/`research_url` avec POST/PUT/PATCH/DELETE). Aucun des deux ne supprime la sortie GET ni aucune autre capacité. La réinitialisation n'efface que la dérogation de la commande. | | **Blocage `done()`** | Avant d'accepter la complétion, l'agent vérifie la présence de dialogues/formulaires ouverts. Si le résumé prétend "créé"/"sauvegardé" mais qu'une modale est encore ouverte, l'agent est forcé de continuer. | | **Garde anti-soumission en double** | Les clics sur du texte de type soumission (créer/sauvegarder/soumettre/ajouter/poster/publier/envoyer/confirmer/s'inscrire/se connecter/payer/commander/checkout, etc.) sont bloqués pendant une fenêtre de 45 secondes par onglet+URL (Chrome). | | **Test d'occlusion CLICK** | Avant de cliquer, le résolveur appelle `elementFromPoint()`. Si un autre élément est visuellement au-dessus, le clic est refusé. | @@ -121,7 +121,11 @@ La menace principale : une page malveillante conçoit un contenu qui, lorsqu'il ## Indicateur `/allow-api` -Défini par conversation via la commande `/allow-api` dans le panneau latéral. Lorsqu'il est actif, il supprime la demande de permission pour **les sorties réseau avec méthode d'écriture uniquement** : +Défini par conversation via la commande `/allow-api` dans le panneau latéral, +ou de façon persistante avec **Toujours autoriser les mutations API** sous +**Paramètres → Général → Avancé**. Le réglage persistant est désactivé par +défaut. Lorsque l'une des options est active, elle supprime la demande de +permission pour **les sorties réseau avec méthode d'écriture uniquement** : - `fetch_url` / `research_url` avec `method: POST/PUT/PATCH/DELETE` @@ -137,10 +141,11 @@ Le prompt système ajoute un préambule indiquant au modèle de : Les indices de raccourcis API de détection de boucle ne contournent pas cette politique. Ils peuvent exposer la méthode et l'URL exactes que la page appelait déjà, y compris POST/PATCH/etc., mais les appels `fetch_url` / `research_url` avec méthode d'écriture nécessitent toujours -l'état `/allow-api` de la conversation. Les requêtes GET et les capacités non réseau +l'état `/allow-api` de la conversation ou le réglage persistant. Les requêtes GET et les capacités non réseau passent toujours par la passerelle normale capacité × origine. -Effacé à la réinitialisation de la conversation. +La réinitialisation de la conversation efface la dérogation de la commande, mais +ne modifie pas le réglage persistant, qui reste actif jusqu'à sa désactivation. --- diff --git a/docs/fr/slash-commands.md b/docs/fr/slash-commands.md index d3c16a038..63240d74c 100644 --- a/docs/fr/slash-commands.md +++ b/docs/fr/slash-commands.md @@ -84,8 +84,15 @@ leur navigateur est dédié à la tâche. ## `/allow-api` `/allow-api` lève la restriction UI-d'abord pour la conversation en cours, afin -que l'agent puisse utiliser POST/PUT/PATCH/DELETE via `fetch_url` lorsque l'UI -échoue. Un badge apparaît pendant l'activation, et il s'efface au `/reset`. +que l'agent puisse utiliser POST/PUT/PATCH/DELETE via `fetch_url` ou +`research_url` lorsque l'UI échoue. Un badge apparaît pendant l'activation, et +il s'efface au `/reset`. + +Pour conserver la même politique entre les conversations et les redémarrages +du navigateur, activez **Toujours autoriser les mutations API** sous +**Paramètres → Général → Avancé**. Ce réglage est désactivé par défaut et reste +actif jusqu'à sa désactivation. `/reset` efface toujours la dérogation +`/allow-api` de la conversation, mais ne modifie pas ce réglage persistant. La règle UI-d'abord par défaut existe parce que les actions API sont invisibles (vous ne voyez pas ce qui est envoyé), nécessitent souvent des jetons diff --git a/docs/security-model.md b/docs/security-model.md index 9dae4ab17..fc73e4d0d 100644 --- a/docs/security-model.md +++ b/docs/security-model.md @@ -111,7 +111,7 @@ The primary threat: a malicious page crafts content that, when read by the agent | **Plan before Act** | When enabled, action-mode runs first produce a structured plan and wait for side-panel approval before any browser tool executes. In Try mode, planner JSON that remains invalid after repair degrades that turn to Ask/read-only; Strict stops. Scheduled runs can auto-approve the plan only through scheduler policy. | | **Skill import boundary** | Skills can expose read-only HTTP tools and download-job tools through a `webbrain-tools` manifest. Importing or keeping the skill enabled is the trust decision for the declared HTTPS endpoint; declared skill tools use `credentials: "omit"` and should mark third-party results `resultPolicy: "untrusted"`. Download-job skill tools still require an action mode and the normal Downloads permission gate before saving files. | | **WebMCP boundary** | Experimental WebMCP is off by default, so its tools and prompt guidance do not enter ordinary model requests unless the user opts in under Settings → General → Advanced. When enabled, Chrome page-registered names, descriptions, schemas, frame URLs, annotations, outputs, and errors are page-controlled and always use the untrusted-content wrapper. Calls use opaque IDs. Ask may list tools but cannot invoke them. Because a callback can run arbitrary page logic, every invocation requires Act/Dev, fresh per-call confirmation, and a permission grant for the actual registration-frame origin; a page-authored `readOnly` hint never bypasses those gates. Missing/opaque frame identity fails closed, and the frame plus effective HTTP(S) security origin are revalidated immediately before dispatch to prevent navigation races from borrowing an old grant. | -| **`/allow-api`** | A per-conversation `/allow-api` flag that *waives* the permission prompt for write-method network egress (`fetch_url`/`research_url` with POST/PUT/PATCH/DELETE). It does NOT waive GET egress or any other capability. Clears on conversation reset. | +| **API mutation override** | A per-conversation `/allow-api` flag, or the default-off persistent setting under General → Advanced, *waives* the permission prompt for write-method network egress (`fetch_url`/`research_url` with POST/PUT/PATCH/DELETE). Neither option waives GET egress or any other capability. Conversation reset clears only the slash-command override. | | **`done()` blocking** | Before accepting completion, the agent probes for open dialogs/forms. If the summary claims "created"/"saved" but a modal is still open, the agent is forced to continue. | | **Duplicate-submit guard** | Clicks on submit-like text (create/save/submit/add/post/publish/send/confirm/sign up/log in/pay/checkout/order, etc.) are blocked within a 45-second window per tab+URL (Chrome). | | **CLICK occlusion test** | Before clicking, the resolver calls `elementFromPoint()`. If another element is visually on top, the click is refused. | @@ -132,7 +132,10 @@ The primary threat: a malicious page crafts content that, when read by the agent ## `/allow-api` Flag -Set per-conversation via the `/allow-api` slash command in the side panel. When active, it waives the permission prompt for **write-method network egress only**: +Set per-conversation via the `/allow-api` slash command in the side panel, or +persistently with **Always allow API mutations** under **Settings → General → +Advanced**. The persistent setting is off by default. When either option is +active, it waives the permission prompt for **write-method network egress only**: - `fetch_url` / `research_url` with `method: POST/PUT/PATCH/DELETE` @@ -147,11 +150,12 @@ The system prompt adds a preamble telling the model to: Loop-detection API shortcut hints do not bypass this policy. They can expose the exact method and URL the page was already calling, including POST/PATCH/etc., -but write-method `fetch_url` / `research_url` calls still require the -conversation's `/allow-api` state. GET requests and non-network capabilities -still go through the normal capability × origin gate. +but write-method `fetch_url` / `research_url` calls still require either the +conversation's `/allow-api` state or the persistent setting. GET requests and +non-network capabilities still go through the normal capability × origin gate. -Cleared on conversation reset. +Conversation reset clears the slash-command override. It does not change the +persistent setting, which remains active until the user turns it off. --- diff --git a/docs/slash-commands.md b/docs/slash-commands.md index 9d8b242d6..7d75c4901 100644 --- a/docs/slash-commands.md +++ b/docs/slash-commands.md @@ -81,8 +81,14 @@ browser is dedicated to the task. ## `/allow-api` `/allow-api` lifts the UI-first restriction for the current conversation so the -agent may use POST/PUT/PATCH/DELETE via `fetch_url` when the UI is failing. A -badge appears while it is active, and it clears on `/reset`. +agent may use POST/PUT/PATCH/DELETE via `fetch_url` or `research_url` when the UI +is failing. A badge appears while it is active, and it clears on `/reset`. + +To keep the same policy active across conversations and browser restarts, turn +on **Always allow API mutations** under **Settings → General → Advanced**. The +setting is off by default and remains active until you turn it off. `/reset` +still clears the conversation-only `/allow-api` override, but does not change +the persistent setting. The default UI-first rule exists because API actions are invisible (you don't see what's being sent), often require separate auth tokens you may not have diff --git a/docs/zh-CN/security-model.md b/docs/zh-CN/security-model.md index 3a85b3782..5a779e1aa 100644 --- a/docs/zh-CN/security-model.md +++ b/docs/zh-CN/security-model.md @@ -96,7 +96,7 @@ | **分层工具暴露** | 提供商层级(`compact | mid | full`)限制较小模型的普通浏览器智能体操作面。Compact 获得最小的操作面;Mid 添加常见任务工具;Full 添加高级 UI/DOM 回退。Compact Dev 被阻止。 | | **行动前规划** | 启用时,行动模式的运行首先生成结构化计划,并等待侧面板批准后才执行任何浏览器工具。定时运行仅通过调度器策略可自动批准计划。 | | **技能导入边界** | 技能可通过 `webbrain-tools` 清单暴露只读 HTTP 工具和下载任务工具。导入或保持技能启用是对声明的 HTTPS 端点的信任决策;声明的技能工具使用 `credentials: "omit"` 并应将第三方结果标记为 `resultPolicy: "untrusted"`。下载任务技能工具在保存文件前仍需行动模式和正常的下载权限门。 | -| **`/allow-api`** | 每个对话的 `/allow-api` 标记,*免除*写方法网络出口(`fetch_url`/`research_url` 的 POST/PUT/PATCH/DELETE)的权限提示。不免除 GET 出口或任何其他能力。对话重置时清除。 | +| **API 变更覆盖** | 每个对话的 `/allow-api` 标记,或“常规 → 高级”中默认关闭的持久设置,都会*免除*写方法网络出口(`fetch_url`/`research_url` 的 POST/PUT/PATCH/DELETE)的权限提示。两者都不免除 GET 出口或任何其他能力。对话重置仅清除斜杠命令覆盖。 | | **`done()` 阻塞** | 在接受完成前,智能体探测是否有打开的对话框/表单。如果摘要声称"已创建"/"已保存"但模态框仍打开,则强制智能体继续。 | | **重复提交防护** | 在 45 秒窗口内,每个标签页+URL 阻止对类似提交文本(create/save/submit/add/post/publish/send/confirm/sign up/log in/pay/checkout/order 等)的点击(Chrome)。 | | **CLICK 遮挡测试** | 在点击前,解析器调用 `elementFromPoint()`。如果另一个元素视觉上位于上方,则拒绝点击。 | @@ -117,7 +117,9 @@ ## `/allow-api` 标记 -通过侧面板中的 `/allow-api` 斜杠命令为每个对话设置。激活时,它仅免除**写方法网络出口**的权限提示: +可通过侧面板中的 `/allow-api` 斜杠命令为每个对话设置,也可在**设置 → 常规 → 高级**中 +开启**始终允许 API 变更**使其持久生效。持久设置默认关闭。任一选项激活时,都仅免除 +**写方法网络出口**的权限提示: - `fetch_url` / `research_url` 使用 `method: POST/PUT/PATCH/DELETE` @@ -127,9 +129,9 @@ - 在任何破坏性 API 调用前以纯文本说明 URL、方法和载荷 - 默认优先使用 UI,仅在 UI 确实失败时才使用 API -循环检测 API 快捷提示不会绕过此策略。它们可以暴露页面已在调用的确切方法和 URL,包括 POST/PATCH 等,但写方法的 `fetch_url` / `research_url` 调用仍需要对话的 `/allow-api` 状态。GET 请求和非网络能力仍通过正常的能力 × 来源门。 +循环检测 API 快捷提示不会绕过此策略。它们可以暴露页面已在调用的确切方法和 URL,包括 POST/PATCH 等,但写方法的 `fetch_url` / `research_url` 调用仍需要对话的 `/allow-api` 状态或持久设置。GET 请求和非网络能力仍通过正常的能力 × 来源门。 -对话重置时清除。 +对话重置会清除斜杠命令覆盖,但不会更改持久设置;后者会一直生效到用户手动关闭。 --- diff --git a/docs/zh-CN/slash-commands.md b/docs/zh-CN/slash-commands.md index e2cdfe5da..d05981023 100644 --- a/docs/zh-CN/slash-commands.md +++ b/docs/zh-CN/slash-commands.md @@ -70,7 +70,11 @@ CDP 截图,并仅在该次运行期间模拟焦点;Firefox 使用 `tabs.capt ## `/allow-api` `/allow-api` 会为当前对话解除 UI 优先限制,使智能体在 UI 失败时可通过 `fetch_url` -使用 POST/PUT/PATCH/DELETE。激活期间会显示徽章,并在 `/reset` 时清除。 +或 `research_url` 使用 POST/PUT/PATCH/DELETE。激活期间会显示徽章,并在 `/reset` 时清除。 + +若要让相同策略在不同对话和浏览器重启后继续生效,请在**设置 → 常规 → 高级**中开启 +**始终允许 API 变更**。该设置默认关闭,并会一直生效到你手动关闭。`/reset` 仍会清除 +当前对话的 `/allow-api` 覆盖,但不会更改此持久设置。 默认的 UI 优先规则之所以存在,是因为 API 操作是不可见的(你看不到发送了什么内容), 通常需要你可能尚未配置的独立认证令牌,并且其影响范围可能比一次可见的误点击大得多。 diff --git a/src/chrome/src/agent/agent.js b/src/chrome/src/agent/agent.js index 5890a355b..d210d31c5 100644 --- a/src/chrome/src/agent/agent.js +++ b/src/chrome/src/agent/agent.js @@ -459,12 +459,17 @@ export class Agent extends LoopDetector { this.lastAutoScreenshotTs = new Map(); // tabId -> ms — defensive debounce this.lastSeenAdapter = new Map(); // tabId -> adapter name from last enrichment // Per-tab opt-in: when true, the agent is allowed to use API mutations - // (POST/PUT/PATCH/DELETE via fetch_url, mutation fetch() via execute_js) + // (POST/PUT/PATCH/DELETE via fetch_url or research_url) // for steps where it judges API to be more reliable than UI. Set via // the /allow-api slash command in the sidebar; cleared on // clearConversation. Persisted with the conversation so a service // worker restart preserves it. this.apiAllowedTabs = new Set(); + // Global Advanced-setting opt-in. This is deliberately separate from + // apiAllowedTabs so resetting a conversation clears only its slash-command + // override, while disabling the stored setting revokes global permission + // immediately without disturbing explicit per-conversation choices. + this.alwaysAllowApiMutations = false; // Track which tabs have already had the [API ALLOWED] preamble // injected for the current run, so we don't push it on every turn. this.apiAllowedInjected = new Set(); @@ -973,7 +978,7 @@ export class Agent extends LoopDetector { user_memory_enabled: this.userMemoryEnabled === true, // Per-tab authorizations only mean something in a tab's context. ...(tabId != null ? { - api_mutations_allowed: this.apiAllowedTabs.has(tabId), + api_mutations_allowed: this.isApiMutationsAllowed(tabId), selection_grounded: this.selectionGroundingScopes.has(tabId), } : {}), image_detail: this.imageDetail, @@ -1667,6 +1672,33 @@ export class Agent extends LoopDetector { } } + setAlwaysAllowApiMutations(allowed) { + const nextAllowed = allowed === true; + const revoked = this.alwaysAllowApiMutations && !nextAllowed; + this.alwaysAllowApiMutations = nextAllowed; + if (!revoked) return; + + // The allow note lives in trusted user-message history, so refreshing the + // system prompt cannot retract it. Append the live state only for chats + // that actually saw that note and lost their effective authorization. + for (const tabId of [...this.apiAllowedInjected]) { + if (this.apiAllowedTabs.has(tabId)) continue; + const messages = this.conversations.get(tabId); + if (Array.isArray(messages)) { + messages.push({ + role: 'user', + content: '[CURRENT API MUTATION AUTHORIZATION — NOT ALLOWED: The persistent API mutation setting was disabled and this conversation has no /allow-api override. This current state supersedes any earlier [USER OVERRIDE — API MUTATIONS ALLOWED] note. Do not plan or call POST/PUT/PATCH/DELETE requests through fetch_url or research_url unless the user explicitly enables /allow-api for this conversation. Continue through the visible UI or ask for /allow-api.]', + }); + this._persist(tabId); + } + this.apiAllowedInjected.delete(tabId); + } + } + + isApiMutationsAllowed(tabId) { + return this.alwaysAllowApiMutations || this.apiAllowedTabs.has(tabId); + } + // Browser-free loop detection is inherited from LoopDetector. These // methods integrate Agent-owned API replay and page-scoped cleanup. @@ -2602,7 +2634,7 @@ export class Agent extends LoopDetector { replayRequestId: replaySource?.replayRequestId || null, replayHasBody: !!replaySource?.hasBody, replayHeaderNames: replaySource?.headerNames || [], - apiAllowed: this.apiAllowedTabs.has(tabId), + apiAllowed: this.isApiMutationsAllowed(tabId), }; } @@ -3308,11 +3340,11 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d } } catch (e) { /* recorder state unavailable — skip the status note */ } - // API mutation override: prepend a strong note when the user has set - // /allow-api for this tab. Inject only once per "allowed run" to avoid - // bloating every subsequent turn. - if (this.apiAllowedTabs.has(tabId) && !this.apiAllowedInjected.has(tabId)) { - contextLine += `[USER OVERRIDE — /allow-api: For this conversation the user has explicitly authorized you to use API mutations (POST/PUT/PATCH/DELETE via fetch_url) when you judge API to be more reliable than UI for a specific step. The default UI-first rule still applies — reach for the API when UI has failed/is genuinely unworkable, or when WebBrain reports a [BULK API MUTATION PATTERN] for repeated successful same-kind UI actions. Before any destructive API call (anything that creates, deletes, transfers, or charges), state the URL, method, and payload in plain text in your response so the user can see what you're about to do.]\n\n`; + // API mutation override: prepend a strong note when either the persistent + // setting or /allow-api enables it. Inject only once per "allowed run" to + // avoid bloating every subsequent turn. + if (this.isApiMutationsAllowed(tabId) && !this.apiAllowedInjected.has(tabId)) { + contextLine += `[USER OVERRIDE — API MUTATIONS ALLOWED: The user has authorized API mutations (POST/PUT/PATCH/DELETE via fetch_url or research_url). The default UI-first rule still applies — reach for the API when UI has failed/is genuinely unworkable, or when WebBrain reports a [BULK API MUTATION PATTERN] for repeated successful same-kind UI actions. Before any destructive API call (anything that creates, deletes, transfers, or charges), state the URL, method, and payload in plain text in your response so the user can see what you're about to do.]\n\n`; this.apiAllowedInjected.add(tabId); } @@ -4666,7 +4698,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d onUpdate('warning', { message }); continue; } - if (isNetworkMutation(fnName, fnArgs) && !this.apiAllowedTabs.has(tabId)) { + if (isNetworkMutation(fnName, fnArgs) && !this.isApiMutationsAllowed(tabId)) { messages.push({ role: 'tool', tool_call_id: tc.id, @@ -4821,7 +4853,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d for (const capability of capabilities) { if (!requiresMandatoryWebMCPGates && this._skipPermissionGate) { gateDisabled = true; break; } // /allow-api waives ONLY write-method network egress. - if (capability === Capability.NETWORK && isNetworkMutation(fnName, fnArgs) && this.apiAllowedTabs.has(tabId)) continue; + if (capability === Capability.NETWORK && isNetworkMutation(fnName, fnArgs) && this.isApiMutationsAllowed(tabId)) continue; // Every distinct host the call touches must be granted. Usually one, // but download_files takes a urls[] array that can span many hosts. const gateArgs = this._skillPermissionArgsForCapability(skillCallTool, capability, fnArgs); @@ -8907,7 +8939,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d const skillCatalog = this._skillCatalog(conversationMode, tier); const plannerMessages = buildPlannerMessages(enriched, tabUrl, tabTitle, historyDigest, { noThink: this._plannerPrefersNoThinkPrompt(provider), - allowApi: this.apiAllowedTabs.has(tabId), + allowApi: this.isApiMutationsAllowed(tabId), skillCatalog, locale, priorUserTask: followUpContext.priorUserTask, diff --git a/src/chrome/src/agent/planner.js b/src/chrome/src/agent/planner.js index d07736cc2..0a7704575 100644 --- a/src/chrome/src/agent/planner.js +++ b/src/chrome/src/agent/planner.js @@ -9,7 +9,7 @@ import { sanitizeText } from './text-sanitize.js'; const UNTRUSTED_PAGE_CONTENT_TAG_RE = /<\/?untrusted_page_content\b[^>]*>/gi; const REQUEST_KINDS = new Set(['execute', 'respond', 'plan_only', 'clarify']); -export const PLANNER_API_REPLAY_RULE = '- Because /allow-api is enabled for this conversation, repeated same-kind UI mutations may include a conditional API branch: if WebBrain later reports a [BULK API MUTATION PATTERN], sample exactly one fetch_url replay with the provided replayRequestId. If that sample fails with success:false or HTTP 4xx/5xx, stop using API for that request shape and continue through the paced visible-UI loop.'; +export const PLANNER_API_REPLAY_RULE = '- Because API mutations are authorized, repeated same-kind UI mutations may include a conditional API branch: if WebBrain later reports a [BULK API MUTATION PATTERN], sample exactly one fetch_url replay with the provided replayRequestId. If that sample fails with success:false or HTTP 4xx/5xx, stop using API for that request shape and continue through the paced visible-UI loop.'; export const PLANNER_SYSTEM_PROMPT = `You are the planning subsystem for WebBrain, a browser automation agent. Given the user's task and current page context, output ONLY a single JSON object (no markdown fences, no commentary outside the JSON). diff --git a/src/chrome/src/agent/tools.js b/src/chrome/src/agent/tools.js index 6ad69eb55..b1e57d361 100644 --- a/src/chrome/src/agent/tools.js +++ b/src/chrome/src/agent/tools.js @@ -1629,7 +1629,7 @@ UI vs API — read this carefully: - The user wants to see what's happening. They want to verify before clicking the final button. They want the action to look exactly like a human did it through the page, not like a script ran in the background. UI flows also generally Just Work with the user's existing session, while API endpoints often require separate tokens the user hasn't configured. - TWO exceptions where API mutations are allowed: (1) The user explicitly says "use the API" or "call the endpoint directly" or "POST to /foo" in their message — do what they asked. - (2) The conversation has the [USER OVERRIDE — /allow-api] flag set (you'll see it as a context note in the user's message). When that's set, you may use API mutations when UI is genuinely failing/unworkable, or when WebBrain reports a [BULK API MUTATION PATTERN] showing repeated successful same-kind UI actions and matching background API requests. Without /allow-api, mutating fetch_url calls are blocked. Before any destructive API call (anything that creates, deletes, transfers, or charges money), state the URL, method, and payload in plain text in your response so the user can see what you're about to do. + (2) The [USER OVERRIDE — API MUTATIONS ALLOWED] context note is present. It can come from /allow-api for this conversation or the user's persistent setting. When present, you may use API mutations when UI is genuinely failing/unworkable, or when WebBrain reports a [BULK API MUTATION PATTERN] showing repeated successful same-kind UI actions and matching background API requests. Without this authorization, mutating fetch_url/research_url calls are blocked. Before any destructive API call (anything that creates, deletes, transfers, or charges money), state the URL, method, and payload in plain text in your response so the user can see what you're about to do. - For READING data (looking things up, fetching a README, comparing prices across sites, checking a status page, gathering research), \`fetch_url\` and \`research_url\` are the RIGHT tool. Reading is not the same as acting. - Examples of the rule: - "Create a release on GitHub" → navigate to /releases/new, click the button, fill the form, click Publish. Don't POST to api.github.com/repos/.../releases. @@ -1913,7 +1913,7 @@ FORMS & MODALS: IFRAMES & UI-vs-API: - Cross-origin iframes (Stripe, payment widgets, embedded forms) are NOT a blocker. Start with iframe_read to enumerate labels and matchIndex values; iframe_click/type fail closed on ambiguity. If the embed remains hard to target and no fields have been changed, use promote_iframe({urlFilter}) to load it standalone in the current run tab. After iframe form edits, call verify_form({urlFilter}) and compare labels/values before done, even when the user will submit later. -- For anything that creates, modifies, deletes, sends, submits, buys, transfers, or posts: go through the visible UI unless /allow-api is enabled and either UI is failing/unworkable or WebBrain reports a [BULK API MUTATION PATTERN]. Do NOT call REST/GraphQL endpoints via fetch_url with POST/PUT/PATCH/DELETE without /allow-api. Reading data (fetch_url / research_url GET) is fine. +- For anything that creates, modifies, deletes, sends, submits, buys, transfers, or posts: go through the visible UI unless API mutations are authorized and either UI is failing/unworkable or WebBrain reports a [BULK API MUTATION PATTERN]. Do NOT call REST/GraphQL endpoints via fetch_url or research_url with POST/PUT/PATCH/DELETE without that authorization. Reading data (fetch_url / research_url GET) is fine. SCRATCHPAD & DON'T REDO WORK: - On long tasks, scratchpad_write({text}) pins miscellaneous facts (IDs, plans) that survive context summarization; downloads are auto-pinned for you (scan the \`[auto]\` lines for downloadIds). Keep entries short and factual. diff --git a/src/chrome/src/background.js b/src/chrome/src/background.js index f9388b06c..55f80e2a2 100644 --- a/src/chrome/src/background.js +++ b/src/chrome/src/background.js @@ -89,6 +89,15 @@ import { const providerManager = new ProviderManager(); const agent = new Agent(providerManager); +const ALWAYS_ALLOW_API_MUTATIONS_KEY = 'alwaysAllowApiMutations'; +const alwaysAllowApiMutationsReady = chrome.storage.local + .get({ [ALWAYS_ALLOW_API_MUTATIONS_KEY]: false }) + .then((stored) => { + agent.setAlwaysAllowApiMutations(stored[ALWAYS_ALLOW_API_MUTATIONS_KEY] === true); + }) + .catch(() => { + agent.setAlwaysAllowApiMutations(false); + }); agent.setConversationScopeChangeListener((tabId, state) => { chrome.runtime.sendMessage({ target: 'sidepanel', @@ -121,6 +130,7 @@ const scheduler = new ScheduledJobManager({ agent, loadProviders: async () => { await customSkillsReady; + await alwaysAllowApiMutationsReady; if (providerManager.providers.size === 0) await providerManager.load(); }, sendUpdate: (tabId, type, data) => { @@ -159,7 +169,9 @@ const cloudRunController = createCloudRunController({ stopRecording: stopTabRecording, workflowTrace, }); -cloudRunController.syncBridge().catch(() => {}); +alwaysAllowApiMutationsReady + .then(() => cloudRunController.syncBridge()) + .catch(() => {}); const MAX_AGENT_STEPS_DEFAULT = 130; const MAX_AGENT_STEPS_UNLIMITED_SENTINEL = 200; @@ -895,6 +907,10 @@ chrome.storage.onChanged.addListener((changes) => { // already-open conversations so the next turn sees the update — without // wiping the chat history. let refreshPrompts = false; + if (changes[ALWAYS_ALLOW_API_MUTATIONS_KEY]) { + agent.setAlwaysAllowApiMutations(changes[ALWAYS_ALLOW_API_MUTATIONS_KEY].newValue === true); + refreshPrompts = true; + } if (changes.useSiteAdapters) { agent.useSiteAdapters = changes.useSiteAdapters.newValue; refreshPrompts = true; @@ -1983,6 +1999,7 @@ async function handleMessage(msg, sender) { // promises so the first chat can't race ahead of hydration, without a // storage round-trip on every message. await Promise.all([planBeforeActReady, planReviewReady, customSkillsReady, userMemoryReady]); + await alwaysAllowApiMutationsReady; await webMcpEnabledReady; await screenshotRedactionReady; await imageBudgetReady; diff --git a/src/chrome/src/config-transfer.js b/src/chrome/src/config-transfer.js index 6ccaf65d8..2925b6295 100644 --- a/src/chrome/src/config-transfer.js +++ b/src/chrome/src/config-transfer.js @@ -31,6 +31,7 @@ export const DEFAULT_CONFIG_SETTINGS = Object.freeze({ autoScreenshot: 'state_change', useSiteAdapters: true, voiceInputEnabled: true, + alwaysAllowApiMutations: false, apiMutationObserverEnabled: false, webMcpEnabled: false, openaiAskStreamingEnabled: true, @@ -81,6 +82,7 @@ const BOOLEAN_KEYS = new Set([ 'clarifyTimeoutSemanticsV2', 'useSiteAdapters', 'voiceInputEnabled', + 'alwaysAllowApiMutations', 'apiMutationObserverEnabled', 'webMcpEnabled', 'openaiAskStreamingEnabled', diff --git a/src/chrome/src/ui/locales/ar.js b/src/chrome/src/ui/locales/ar.js index 83fec780c..6699462b9 100644 --- a/src/chrome/src/ui/locales/ar.js +++ b/src/chrome/src/ui/locales/ar.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'مدة انتظار الرد على سؤال التوضيح قبل اختيار الخيار الأول تلقائيًا (أو انتهاء المهلة إن لم توجد خيارات). 0 = فوري (اختيار تلقائي دائمًا). أعلى من 1200 ثانية = انتظار بلا حدود (إيقاف). الافتراضي 60 ثانية. لا ينطبق على أذونات أو تأكيدات إرسال النماذج.', 'st.display.clarify_timeout.off': 'إيقاف', 'st.display.clarify_timeout.instant': 'فوري', + 'st.display.always_allow_api_mutations.label': "السماح دائمًا بتعديلات API", + 'st.display.always_allow_api_mutations.desc': "اسمح لـ WebBrain باستخدام POST وPUT وPATCH وDELETE عبر fetch_url أو research_url دون الحاجة إلى /allow-api في كل محادثة. تظل إرشادات أولوية واجهة المستخدم وفحوصات التأكيد سارية. معطّل افتراضيًا.", 'st.display.api_mutation_observer.label': 'مراقب تحولات API', 'st.display.api_mutation_observer.desc': 'مراقبة عناوين URL وطرق طلبات XHR/fetch في نفس التبويب ليتمكن WebBrain من اكتشاف إجراءات الواجهة المتكررة واقتراح أنماط اختصارات API. معطل افتراضياً؛ قم بتفعيله فقط أثناء التحقيق في سلوك الاختصار أو زمن الاستجابة.', 'st.display.openai_ask_streaming.label': "?? ?????? ?? ??? ??????", diff --git a/src/chrome/src/ui/locales/bn.js b/src/chrome/src/ui/locales/bn.js index b7e141191..8532d9d27 100644 --- a/src/chrome/src/ui/locales/bn.js +++ b/src/chrome/src/ui/locales/bn.js @@ -518,6 +518,8 @@ export default { 'st.display.site_adapters.desc': "এজেন্টের প্রথম বার্তায় পরিচিত উচ্চ-ট্রাফিক সাইটগুলির (GitHub, Gmail, Stripe, AWS, ইত্যাদি) জন্য সংক্ষিপ্ত, হাতে-কলমে নির্দেশিকা প্রবেশ করান৷ এজেন্টকে সাধারণ ডেড-এন্ড এড়াতে সাহায্য করে। মিলিত সাইটগুলিতে কথোপকথনের প্রথম পালায় একটি ছোট টোকেন খরচ যোগ করে।", 'st.display.voice_input.label': "ভয়েস ইনপুট", 'st.display.voice_input.desc': "চ্যাট ইনপুটে মাইক বোতামটিকে আপনার ব্রাউজারের স্পিচ রিকগনিশনের মাধ্যমে পাঠ্য নির্দেশ করতে দিন। এটি সমর্থন করে এমন ব্রাউজারগুলিতে ডিফল্টরূপে চালু৷", + 'st.display.always_allow_api_mutations.label': "সর্বদা API পরিবর্তনের অনুমতি দিন", + 'st.display.always_allow_api_mutations.desc': "প্রতিটি কথোপকথনে /allow-api ছাড়াই WebBrain-কে fetch_url বা research_url-এর মাধ্যমে POST, PUT, PATCH ও DELETE ব্যবহার করতে দিন। UI-প্রথম নির্দেশনা ও নিশ্চিতকরণ যাচাই এখনও প্রযোজ্য। ডিফল্টভাবে বন্ধ।", 'st.display.api_mutation_observer.label': "API মিউটেশন পর্যবেক্ষক", 'st.display.api_mutation_observer.desc': "একই-ট্যাব XHR/ফেচ অনুরোধ URL এবং পদ্ধতিগুলি পর্যবেক্ষণ করুন যাতে WebBrain বারবার UI অ্যাকশন সনাক্ত করতে পারে এবং API শর্টকাট প্যাটার্নের পরামর্শ দিতে পারে। ডিফল্টরূপে বন্ধ; শুধুমাত্র শর্টকাট আচরণ বা লেটেন্সি তদন্ত করার সময় সক্ষম করুন।", 'st.display.webmcp.label': "পরীক্ষামূলক WebMCP", diff --git a/src/chrome/src/ui/locales/de.js b/src/chrome/src/ui/locales/de.js index 6ca51d635..c74507aba 100644 --- a/src/chrome/src/ui/locales/de.js +++ b/src/chrome/src/ui/locales/de.js @@ -513,6 +513,8 @@ export default { 'st.display.site_adapters.desc': 'Fügt kurze, handverlesene Anleitungen für bekannte, stark frequentierte Websites in die erste Nachricht des Agents ein.', 'st.display.voice_input.label': 'Spracheingabe', 'st.display.voice_input.desc': 'Ermöglicht dem Mikrofon-Button in der Chat-Eingabe, Text über die Spracherkennung Ihres Browsers zu diktieren.', + 'st.display.always_allow_api_mutations.label': "API-Änderungen immer erlauben", + 'st.display.always_allow_api_mutations.desc': "WebBrain darf POST, PUT, PATCH und DELETE über fetch_url oder research_url verwenden, ohne dass in jeder Unterhaltung /allow-api erforderlich ist. UI-zuerst-Hinweise und Bestätigungsprüfungen gelten weiterhin. Standardmäßig aus.", 'st.display.api_mutation_observer.label': 'API-Mutations-Observer', 'st.display.api_mutation_observer.desc': 'Beobachtet XHR/fetch-Anfragen-URLs und Methoden im selben Tab, damit WebBrain wiederkehrende UI-Aktionen erkennen und API-Verknüpfungsmuster vorschlagen kann.', 'st.display.webmcp.label': 'Experimentelles WebMCP', diff --git a/src/chrome/src/ui/locales/en.js b/src/chrome/src/ui/locales/en.js index fc3bc2c99..589ea70ec 100644 --- a/src/chrome/src/ui/locales/en.js +++ b/src/chrome/src/ui/locales/en.js @@ -518,6 +518,8 @@ export default { 'st.display.site_adapters.desc': 'Inject short, hand-curated guidance for known high-traffic sites (GitHub, Gmail, Stripe, AWS, etc.) into the agent\'s first message. Helps the agent avoid common dead-ends. Adds a small token cost on the first turn of conversations on matched sites.', 'st.display.voice_input.label': 'Voice input', 'st.display.voice_input.desc': 'Let the mic button in the chat input dictate text via your browser\'s speech recognition. On by default in browsers that support it.', + 'st.display.always_allow_api_mutations.label': "Always allow API mutations", + 'st.display.always_allow_api_mutations.desc': "Allow WebBrain to use POST, PUT, PATCH, and DELETE through fetch_url or research_url without requiring /allow-api in each conversation. UI-first guidance and confirmation checks still apply. Off by default.", 'st.display.api_mutation_observer.label': 'API mutation observer', 'st.display.api_mutation_observer.desc': 'Observe same-tab XHR/fetch request URLs and methods so WebBrain can detect repeated UI actions and suggest API shortcut patterns. Off by default; enable only while investigating shortcut behavior or latency.', 'st.display.webmcp.label': 'Experimental WebMCP', diff --git a/src/chrome/src/ui/locales/es.js b/src/chrome/src/ui/locales/es.js index 04cbc8752..d5ba2c1c2 100644 --- a/src/chrome/src/ui/locales/es.js +++ b/src/chrome/src/ui/locales/es.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'Cuánto esperar una respuesta a una pregunta de aclaración antes de elegir automáticamente la primera opción (o agotar el tiempo si no hay opciones). 0 = Instantáneo (autoelegir siempre). Valores por encima de 1200s esperan indefinidamente (Desactivado). Predeterminado 60s. No se aplica a permisos ni confirmaciones de envío de formularios.', 'st.display.clarify_timeout.off': 'Desactivado', 'st.display.clarify_timeout.instant': 'Instantáneo', + 'st.display.always_allow_api_mutations.label': "Permitir siempre mutaciones de API", + 'st.display.always_allow_api_mutations.desc': "Permite que WebBrain use POST, PUT, PATCH y DELETE mediante fetch_url o research_url sin requerir /allow-api en cada conversación. Las directrices de priorizar la interfaz y las comprobaciones de confirmación siguen vigentes. Desactivado de forma predeterminada.", 'st.display.api_mutation_observer.label': 'Observador de mutaciones de API', 'st.display.api_mutation_observer.desc': 'Observa las URLs y métodos de peticiones XHR/fetch en la misma pestaña para que WebBrain pueda detectar acciones repetidas de la interfaz y sugerir patrones de acceso directo por API. Desactivado por defecto; actívalo solo mientras investigas comportamientos de acceso directo o latencia.', 'st.display.openai_ask_streaming.label': "Transmitir respuestas en modo Ask", diff --git a/src/chrome/src/ui/locales/fa.js b/src/chrome/src/ui/locales/fa.js index d8b655886..083d87769 100644 --- a/src/chrome/src/ui/locales/fa.js +++ b/src/chrome/src/ui/locales/fa.js @@ -518,6 +518,8 @@ export default { 'st.display.site_adapters.desc': "راهنمای کوتاه و دستی برای سایت‌های پربازدید شناخته شده (GitHub، Gmail، Stripe، AWS، و غیره) در اولین پیام نماینده وارد کنید. به عامل کمک می کند تا از بن بست های رایج جلوگیری کند. در اولین نوبت مکالمه در سایت های منطبق، هزینه رمز اندکی اضافه می کند.", 'st.display.voice_input.label': "ورودی صوتی", 'st.display.voice_input.desc': "اجازه دهید دکمه میکروفون در ورودی چت، متن را از طریق تشخیص گفتار مرورگر شما دیکته کند. به طور پیش فرض در مرورگرهایی که از آن پشتیبانی می کنند روشن است.", + 'st.display.always_allow_api_mutations.label': "همیشه تغییرات API مجاز باشد", + 'st.display.always_allow_api_mutations.desc': "به WebBrain اجازه دهید بدون نیاز به /allow-api در هر گفتگو، از POST، PUT، PATCH و DELETE از طریق fetch_url یا research_url استفاده کند. راهنمای اولویت رابط کاربری و بررسی‌های تأیید همچنان اعمال می‌شوند. به‌طور پیش‌فرض خاموش است.", 'st.display.api_mutation_observer.label': "مشاهده گر جهش API", 'st.display.api_mutation_observer.desc': "نشانی‌های وب و روش‌های درخواستی XHR/واکشی همان تب را مشاهده کنید تا WebBrain بتواند اقدامات مکرر UI را تشخیص دهد و الگوهای میانبر API را پیشنهاد کند. خاموش به طور پیش فرض؛ فقط هنگام بررسی رفتار میانبر یا تأخیر فعال شود.", 'st.display.webmcp.label': "WebMCP آزمایشی", diff --git a/src/chrome/src/ui/locales/fr.js b/src/chrome/src/ui/locales/fr.js index 90233432c..4f249c091 100644 --- a/src/chrome/src/ui/locales/fr.js +++ b/src/chrome/src/ui/locales/fr.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'Durée d’attente d’une réponse à une question de clarification avant de sélectionner automatiquement la première option (ou d’expirer s’il n’y a pas d’options). 0 = Immédiat (auto-sélection). Au-delà de 1200s = attendre indéfiniment (Désactivé). Par défaut 60s. Ne s’applique pas aux permissions ni aux confirmations d’envoi de formulaire.', 'st.display.clarify_timeout.off': 'Désactivé', 'st.display.clarify_timeout.instant': 'Immédiat', + 'st.display.always_allow_api_mutations.label': "Toujours autoriser les mutations API", + 'st.display.always_allow_api_mutations.desc': "Autoriser WebBrain à utiliser POST, PUT, PATCH et DELETE via fetch_url ou research_url sans exiger /allow-api dans chaque conversation. Les directives UI-d’abord et les confirmations restent applicables. Désactivé par défaut.", 'st.display.api_mutation_observer.label': 'Observateur de mutations API', 'st.display.api_mutation_observer.desc': 'Observer les URLs et méthodes des requêtes XHR/fetch dans le même onglet pour que WebBrain puisse détecter des actions UI répétées et suggérer des modèles de raccourcis API. Désactivé par défaut ; activer seulement lors de l\'investigation de comportements de raccourci ou de latence.', 'st.display.openai_ask_streaming.label': "Diffuser les r?ponses en mode Ask", diff --git a/src/chrome/src/ui/locales/he.js b/src/chrome/src/ui/locales/he.js index 2e94189c2..43494553e 100644 --- a/src/chrome/src/ui/locales/he.js +++ b/src/chrome/src/ui/locales/he.js @@ -488,6 +488,8 @@ export default { "st.display.site_adapters.desc": "הזרקו הדרכה קצרה, שנקבעה ביד לאתרים ידועים בעלי תנועה גבוהה (GitHub, Gmail, Stripe, AWSוכו') בהודעה הראשונה של הסוכן. עוזר לסוכן להימנע ממבוי סתום שכיח. מוסיף עלות סמלית קטנה בסיבוב הראשון של שיחות באתרים תואמים.", "st.display.voice_input.label": "קלט קולי", "st.display.voice_input.desc": "תן ללחצן המיקרופון בקלט הצ'אט להכתיב טקסט באמצעות זיהוי הדיבור של הדפדפן שלך. פועל כברירת מחדל בדפדפנים התומכים בו.", + 'st.display.always_allow_api_mutations.label': "לאפשר תמיד שינויי API", + 'st.display.always_allow_api_mutations.desc': "אפשר ל-WebBrain להשתמש ב-POST, PUT, PATCH ו-DELETE דרך fetch_url או research_url בלי לדרוש /allow-api בכל שיחה. הנחיות להעדפת הממשק ובדיקות אישור עדיין חלות. כבוי כברירת מחדל.", "st.display.api_mutation_observer.label": "מעקב אחר שינויי API", "st.display.api_mutation_observer.desc": "עקוב אחר כתובות URL ושיטות של בקשות XHR/fetch באותה כרטיסייה, כדי ש-WebBrain יוכל לזהות פעולות חוזרות בממשק ולהציע דפוסי קיצור דרך דרך ה-API. כבוי כברירת מחדל; הפעל רק בעת בדיקת קיצורי דרך או זמני תגובה.", 'st.display.openai_ask_streaming.label': "????? ?????? ???? Ask", diff --git a/src/chrome/src/ui/locales/hi.js b/src/chrome/src/ui/locales/hi.js index 3b60f500c..0753133b4 100644 --- a/src/chrome/src/ui/locales/hi.js +++ b/src/chrome/src/ui/locales/hi.js @@ -518,6 +518,8 @@ export default { 'st.display.site_adapters.desc': "एजेंट के पहले संदेश में ज्ञात उच्च-ट्रैफ़िक साइटों (GitHub, Gmail, Stripe, AWS, आदि) के लिए संक्षिप्त, हाथ से क्यूरेटेड मार्गदर्शन शामिल करें। एजेंट को सामान्य गतिरोधों से बचने में मदद करता है। मेल खाने वाली साइटों पर बातचीत के पहले दौर में एक छोटी सी टोकन लागत जुड़ जाती है।", 'st.display.voice_input.label': "ध्वनि इनपुट", 'st.display.voice_input.desc': "चैट इनपुट में माइक बटन को अपने ब्राउज़र की वाक् पहचान के माध्यम से टेक्स्ट निर्देशित करने दें। इसका समर्थन करने वाले ब्राउज़र में डिफ़ॉल्ट रूप से चालू।", + 'st.display.always_allow_api_mutations.label': "API बदलावों को हमेशा अनुमति दें", + 'st.display.always_allow_api_mutations.desc': "WebBrain को हर बातचीत में /allow-api की आवश्यकता के बिना fetch_url या research_url के माध्यम से POST, PUT, PATCH और DELETE का उपयोग करने दें। UI-प्रथम मार्गदर्शन और पुष्टि जाँचें लागू रहेंगी। डिफ़ॉल्ट रूप से बंद।", 'st.display.api_mutation_observer.label': "एपीआई उत्परिवर्तन पर्यवेक्षक", 'st.display.api_mutation_observer.desc': "समान-टैब XHR/फ़ेच अनुरोध URL और विधियों का निरीक्षण करें ताकि WebBrain बार-बार होने वाली UI क्रियाओं का पता लगा सके और API शॉर्टकट पैटर्न सुझा सके। डिफ़ॉल्ट रूप से बंद; शॉर्टकट व्यवहार या विलंबता की जांच करते समय ही सक्षम करें।", 'st.display.webmcp.label': "प्रायोगिक वेबएमसीपी", diff --git a/src/chrome/src/ui/locales/id.js b/src/chrome/src/ui/locales/id.js index df0e3e147..8baa69070 100644 --- a/src/chrome/src/ui/locales/id.js +++ b/src/chrome/src/ui/locales/id.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'Berapa lama menunggu balasan pada prompt klarifikasi sebelum memilih opsi pertama secara otomatis (atau timeout jika tidak ada opsi). 0 = Instan (selalu pilih otomatis). Di atas 1200 detik menunggu tanpa batas (Nonaktif). Default 60 detik. Tidak berlaku untuk izin atau konfirmasi kirim formulir.', 'st.display.clarify_timeout.off': 'Nonaktif', 'st.display.clarify_timeout.instant': 'Instan', + 'st.display.always_allow_api_mutations.label': "Selalu izinkan mutasi API", + 'st.display.always_allow_api_mutations.desc': "Izinkan WebBrain menggunakan POST, PUT, PATCH, dan DELETE melalui fetch_url atau research_url tanpa memerlukan /allow-api di setiap percakapan. Panduan yang mengutamakan UI dan pemeriksaan konfirmasi tetap berlaku. Nonaktif secara default.", 'st.display.api_mutation_observer.label': 'Pengamat mutasi API', 'st.display.api_mutation_observer.desc': 'Amati URL dan metode permintaan XHR/fetch pada tab yang sama sehingga WebBrain dapat mendeteksi tindakan UI berulang dan menyarankan pola pintasan API. Nonaktif secara default; aktifkan hanya saat menyelidiki perilaku pintasan atau latensi.', 'st.display.openai_ask_streaming.label': "Streaming respons dalam mode Ask", diff --git a/src/chrome/src/ui/locales/ja.js b/src/chrome/src/ui/locales/ja.js index 13a929ccd..7ed245e2a 100644 --- a/src/chrome/src/ui/locales/ja.js +++ b/src/chrome/src/ui/locales/ja.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'clarify の返答を待つ時間。経過すると最初の選択肢を自動選択(選択肢がなければタイムアウト)。0 で即時(常に自動選択)。1200 秒超は無制限(オフ)。既定 60 秒。権限やフォーム送信確認には適用されません。', 'st.display.clarify_timeout.off': 'オフ', 'st.display.clarify_timeout.instant': '即時', + 'st.display.always_allow_api_mutations.label': "API変更を常に許可", + 'st.display.always_allow_api_mutations.desc': "会話ごとに /allow-api を必要とせず、WebBrain が fetch_url または research_url で POST、PUT、PATCH、DELETE を使用できるようにします。UI優先の指針と確認チェックは引き続き適用されます。既定ではオフです。", 'st.display.api_mutation_observer.label': 'API変更オブザーバー', 'st.display.api_mutation_observer.desc': '同じタブの XHR/fetch リクエスト URL とメソッドを監視し、WebBrain が繰り返しの UI アクションを検出して API ショートカットパターンを提案できるようにします。デフォルトではオフ。ショートカット動作やレイテンシの調査中のみ有効にしてください。', 'st.display.openai_ask_streaming.label': "Ask ???????????", diff --git a/src/chrome/src/ui/locales/ko.js b/src/chrome/src/ui/locales/ko.js index 1d82f3174..1cda7a4eb 100644 --- a/src/chrome/src/ui/locales/ko.js +++ b/src/chrome/src/ui/locales/ko.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': '명확화 질문에 대한 답변 대기 시간입니다. 시간이 지나면 첫 번째 옵션을 자동 선택합니다(옵션이 없으면 시간 초과). 0은 즉시(항상 자동 선택). 1200초 초과는 무제한(끔). 기본 60초. 권한 또는 양식 제출 확인에는 적용되지 않습니다.', 'st.display.clarify_timeout.off': '끔', 'st.display.clarify_timeout.instant': '즉시', + 'st.display.always_allow_api_mutations.label': "API 변경 항상 허용", + 'st.display.always_allow_api_mutations.desc': "대화마다 /allow-api를 입력하지 않아도 WebBrain이 fetch_url 또는 research_url을 통해 POST, PUT, PATCH, DELETE를 사용하도록 허용합니다. UI 우선 지침과 확인 검사는 계속 적용됩니다. 기본적으로 꺼져 있습니다.", 'st.display.api_mutation_observer.label': 'API 변경 관찰자', 'st.display.api_mutation_observer.desc': '동일한 탭의 XHR/fetch 요청 URL 및 메서드를 관찰하여 WebBrain이 반복되는 UI 작업을 감지하고 API 바로가기 패턴을 제안할 수 있도록 합니다. 기본적으로 꺼져 있습니다. 바로가기 동작이나 지연 시간을 조사할 때만 활성화하세요.', 'st.display.openai_ask_streaming.label': "Ask ?? ????", diff --git a/src/chrome/src/ui/locales/ms.js b/src/chrome/src/ui/locales/ms.js index 1073a5712..940fdbd3f 100644 --- a/src/chrome/src/ui/locales/ms.js +++ b/src/chrome/src/ui/locales/ms.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'Berapa lama menunggu balasan soalan penjelasan sebelum memilih pilihan pertama secara automatik (atau tamat masa jika tiada pilihan). 0 = Segera (sentiasa auto-pilih). Melebihi 1200s tunggu tanpa had (Mati). Lalai 60s. Tidak digunakan untuk kebenaran atau pengesahan hantar borang.', 'st.display.clarify_timeout.off': 'Mati', 'st.display.clarify_timeout.instant': 'Segera', + 'st.display.always_allow_api_mutations.label': "Sentiasa benarkan mutasi API", + 'st.display.always_allow_api_mutations.desc': "Benarkan WebBrain menggunakan POST, PUT, PATCH dan DELETE melalui fetch_url atau research_url tanpa memerlukan /allow-api dalam setiap perbualan. Panduan mengutamakan UI dan semakan pengesahan masih terpakai. Dimatikan secara lalai.", 'st.display.api_mutation_observer.label': 'Pemerhati mutasi API', 'st.display.api_mutation_observer.desc': 'Perhatikan URL dan metode permintaan XHR/fetch tab yang sama supaya WebBrain dapat mengesan tindakan UI berulang dan mencadangkan corak pintasan API. Dimatikan secara lalai; aktifkan hanya semasa menyiasat tingkah laku pintasan atau kependaman.', 'st.display.openai_ask_streaming.label': "Strim respons dalam mod Ask", diff --git a/src/chrome/src/ui/locales/nl.js b/src/chrome/src/ui/locales/nl.js index d680efd02..06589efc3 100644 --- a/src/chrome/src/ui/locales/nl.js +++ b/src/chrome/src/ui/locales/nl.js @@ -495,6 +495,8 @@ export default { 'st.display.site_adapters.desc': 'Injecteer korte, handgeschreven begeleiding voor bekende sites met veel verkeer...', 'st.display.voice_input.label': 'Spraakinvoer', 'st.display.voice_input.desc': 'Laat de microfoonknop in de chatinvoer tekst dicteren via de spraakherkenning van uw browser...', + 'st.display.always_allow_api_mutations.label': "API-mutaties altijd toestaan", + 'st.display.always_allow_api_mutations.desc': "Sta WebBrain toe POST, PUT, PATCH en DELETE via fetch_url of research_url te gebruiken zonder /allow-api in elk gesprek. Richtlijnen die de UI vooropstellen en bevestigingscontroles blijven van toepassing. Standaard uit.", 'st.display.api_mutation_observer.label': 'API-wijzigingsobserver', 'st.display.api_mutation_observer.desc': 'Observeer XHR/fetch-verzoek-URL\'s en -methoden op hetzelfde tabblad...', 'st.display.webmcp.label': 'Experimenteel WebMCP', diff --git a/src/chrome/src/ui/locales/pl.js b/src/chrome/src/ui/locales/pl.js index 26656f6d3..83bbcf06f 100644 --- a/src/chrome/src/ui/locales/pl.js +++ b/src/chrome/src/ui/locales/pl.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'Jak długo czekać na odpowiedź na dopytanie, zanim automatycznie wybrana zostanie pierwsza opcja (lub upłynie limit, gdy brak opcji). 0 = Natychmiast (zawsze auto-wybór). Powyżej 1200s czekaj bez limitu (Wył.). Domyślnie 60s. Nie dotyczy uprawnień ani potwierdzeń wysyłki formularza.', 'st.display.clarify_timeout.off': 'Wył.', 'st.display.clarify_timeout.instant': 'Natychmiast', + 'st.display.always_allow_api_mutations.label': "Zawsze zezwalaj na mutacje API", + 'st.display.always_allow_api_mutations.desc': "Zezwól WebBrain na używanie POST, PUT, PATCH i DELETE przez fetch_url lub research_url bez wymagania /allow-api w każdej rozmowie. Nadal obowiązują wskazówki preferujące interfejs oraz kontrole potwierdzeń. Domyślnie wyłączone.", 'st.display.api_mutation_observer.label': 'Obserwator mutacji API', 'st.display.api_mutation_observer.desc': 'Obserwuj URL-e i metody żądań XHR/fetch w tej samej karcie, aby WebBrain mógł wykrywać powtarzające się akcje UI i sugerować wzorce skrótów API. Wyłączone domyślnie; włącz tylko podczas badania zachowania skrótów lub opóźnień.', 'st.display.openai_ask_streaming.label': "Strumieniuj odpowiedzi w trybie Ask", diff --git a/src/chrome/src/ui/locales/pt.js b/src/chrome/src/ui/locales/pt.js index e3f94b307..e8dd63d3b 100644 --- a/src/chrome/src/ui/locales/pt.js +++ b/src/chrome/src/ui/locales/pt.js @@ -518,6 +518,8 @@ export default { 'st.display.site_adapters.desc': "Injete orientações curtas e selecionadas manualmente para sites conhecidos de alto tráfego (GitHub, Gmail, Stripe, AWS, etc.) na primeira mensagem do agente. Ajuda o agente a evitar becos sem saída comuns. Adiciona um pequeno custo de token na primeira rodada de conversas em sites correspondentes.", 'st.display.voice_input.label': "Entrada de voz", 'st.display.voice_input.desc': "Deixe o botão do microfone na entrada do bate-papo ditar o texto por meio do reconhecimento de fala do seu navegador. Ativado por padrão em navegadores compatíveis.", + 'st.display.always_allow_api_mutations.label': "Sempre permitir mutações de API", + 'st.display.always_allow_api_mutations.desc': "Permita que o WebBrain use POST, PUT, PATCH e DELETE por fetch_url ou research_url sem exigir /allow-api em cada conversa. As orientações de priorizar a interface e as verificações de confirmação continuam válidas. Desativado por padrão.", 'st.display.api_mutation_observer.label': "Observador de mutação de API", 'st.display.api_mutation_observer.desc': "Observe URLs e métodos de solicitação de busca/XHR na mesma guia para que WebBrain possa detectar ações repetidas da interface do usuário e sugerir padrões de atalho de API. Desativado por padrão; habilite apenas ao investigar o comportamento ou a latência do atalho.", 'st.display.webmcp.label': "WebMCP Experimental", diff --git a/src/chrome/src/ui/locales/ru.js b/src/chrome/src/ui/locales/ru.js index 0c9837b00..15bead6b1 100644 --- a/src/chrome/src/ui/locales/ru.js +++ b/src/chrome/src/ui/locales/ru.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'Сколько ждать ответа на уточняющий вопрос, прежде чем автоматически выбрать первый вариант (или зафиксировать таймаут без вариантов). 0 — сразу (всегда автовыбор). Больше 1200 с — ждать бесконечно (Выкл.). По умолчанию 60 с. Не применяется к разрешениям и подтверждениям отправки форм.', 'st.display.clarify_timeout.off': 'Выкл.', 'st.display.clarify_timeout.instant': 'Сразу', + 'st.display.always_allow_api_mutations.label': "Всегда разрешать изменения через API", + 'st.display.always_allow_api_mutations.desc': "Разрешить WebBrain использовать POST, PUT, PATCH и DELETE через fetch_url или research_url без команды /allow-api в каждом разговоре. Правило приоритета интерфейса и проверки подтверждения продолжают действовать. По умолчанию выключено.", 'st.display.api_mutation_observer.label': 'Наблюдатель мутаций API', 'st.display.api_mutation_observer.desc': 'Наблюдайте за URL-адресами и методами запросов XHR/fetch на той же вкладке, чтобы WebBrain мог обнаруживать повторяющиеся действия UI и предлагать шаблоны API-шорткатов. Отключено по умолчанию; включайте только при исследовании поведения шорткатов или задержек.', 'st.display.openai_ask_streaming.label': "????????? ?????? ? ?????? Ask", diff --git a/src/chrome/src/ui/locales/th.js b/src/chrome/src/ui/locales/th.js index 8037424d9..08f0f9994 100644 --- a/src/chrome/src/ui/locales/th.js +++ b/src/chrome/src/ui/locales/th.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'ระยะเวลารอคำตอบ clarify ก่อนเลือกตัวเลือกแรกอัตโนมัติ (หรือหมดเวลาหากไม่มีตัวเลือก) 0 = ทันที (เลือกอัตโนมัติเสมอ) เกิน 1200 วินาที = รอไม่จำกัด (ปิด) ค่าเริ่มต้น 60 วินาที ไม่ใช้กับสิทธิ์หรือการยืนยันส่งฟอร์ม', 'st.display.clarify_timeout.off': 'ปิด', 'st.display.clarify_timeout.instant': 'ทันที', + 'st.display.always_allow_api_mutations.label': "อนุญาตการเปลี่ยนแปลง API เสมอ", + 'st.display.always_allow_api_mutations.desc': "อนุญาตให้ WebBrain ใช้ POST, PUT, PATCH และ DELETE ผ่าน fetch_url หรือ research_url โดยไม่ต้องใช้ /allow-api ในทุกการสนทนา แนวทางที่ให้ความสำคัญกับ UI และการตรวจสอบการยืนยันยังคงมีผล ปิดไว้โดยค่าเริ่มต้น", 'st.display.api_mutation_observer.label': 'ตัวสังเกตการกลายพันธุ์ของ API', 'st.display.api_mutation_observer.desc': 'สังเกต URL และวิธีการของคำขอ XHR/fetch ในแท็บเดียวกัน เพื่อให้ WebBrain ตรวจจับการกระทำ UI ที่ซ้ำและแนะนำรูปแบบทางลัด API ปิดโดยค่าเริ่มต้น เปิดใช้งานเฉพาะเมื่อตรวจสอบพฤติกรรมทางลัดหรือความหน่วง', 'st.display.openai_ask_streaming.label': "???????????????? Ask", diff --git a/src/chrome/src/ui/locales/tl.js b/src/chrome/src/ui/locales/tl.js index f93342f63..0494b3a25 100644 --- a/src/chrome/src/ui/locales/tl.js +++ b/src/chrome/src/ui/locales/tl.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'Gaano katagal maghintay ng sagot sa clarify bago awtomatikong piliin ang unang opsyon (o mag-timeout kung walang opsyon). 0 = Agad (palaging auto-select). Higit sa 1200s ay walang hangganan (Naka-off). Default 60s. Hindi para sa permission o form-submit confirmations.', 'st.display.clarify_timeout.off': 'Naka-off', 'st.display.clarify_timeout.instant': 'Agad', + 'st.display.always_allow_api_mutations.label': "Palaging payagan ang mga pagbabago sa API", + 'st.display.always_allow_api_mutations.desc': "Payagan ang WebBrain na gumamit ng POST, PUT, PATCH, at DELETE sa pamamagitan ng fetch_url o research_url nang hindi kailangan ang /allow-api sa bawat usapan. Nalalapat pa rin ang gabay na unahin ang UI at mga pagsusuri sa kumpirmasyon. Naka-off bilang default.", 'st.display.api_mutation_observer.label': 'Tagamasid ng mutasyon ng API', 'st.display.api_mutation_observer.desc': 'Obserbahan ang mga URL at paraan ng XHR/fetch request sa parehong tab upang matukoy ng WebBrain ang mga paulit-ulit na aksyon sa UI at magmungkahi ng mga pattern ng shortcut sa API. Naka-off bilang default; paganahin lamang habang iniimbestigahan ang pag-uugali ng shortcut o latency.', 'st.display.openai_ask_streaming.label': "I-stream ang mga sagot sa Ask mode", diff --git a/src/chrome/src/ui/locales/tr.js b/src/chrome/src/ui/locales/tr.js index a15b7344a..2ebf3e0d9 100644 --- a/src/chrome/src/ui/locales/tr.js +++ b/src/chrome/src/ui/locales/tr.js @@ -667,6 +667,8 @@ export default { 'st.display.clarify_timeout.desc': 'Açıklama sorusuna yanıt için ne kadar bekleneceği; süre dolunca ilk seçenek otomatik seçilir (seçenek yoksa zaman aşımı). 0 = Anında (her zaman otomatik seç). 1200 sn üzeri = süresiz bekle (Kapalı). Varsayılan 60 sn. İzin ve form gönderim onaylarına uygulanmaz.', 'st.display.clarify_timeout.off': 'Kapalı', 'st.display.clarify_timeout.instant': 'Anında', + 'st.display.always_allow_api_mutations.label': "API değişikliklerine her zaman izin ver", + 'st.display.always_allow_api_mutations.desc': "WebBrain’in her konuşmada /allow-api gerektirmeden fetch_url veya research_url üzerinden POST, PUT, PATCH ve DELETE kullanmasına izin verin. Önce arayüz yaklaşımı ve onay kontrolleri uygulanmaya devam eder. Varsayılan olarak kapalıdır.", 'st.display.api_mutation_observer.label': 'API mutasyon gözlemcisi', 'st.display.api_mutation_observer.desc': 'WebBrain\'in tekrarlanan UI eylemlerini tespit etmesi ve API kısayol kalıpları önermesi için aynı sekmedeki XHR/fetch istek URL\'lerini ve yöntemlerini gözlemle. Varsayılan olarak kapalı; yalnızca kısayol davranışını veya gecikmeyi araştırırken etkinleştir.', 'st.display.openai_ask_streaming.label': 'Ask yanıtlarını akışla', diff --git a/src/chrome/src/ui/locales/uk.js b/src/chrome/src/ui/locales/uk.js index 06df39beb..da9904b2d 100644 --- a/src/chrome/src/ui/locales/uk.js +++ b/src/chrome/src/ui/locales/uk.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': 'Скільки чекати відповіді на уточнювальне запитання, перш ніж автоматично обрати перший варіант (або зафіксувати таймаут без варіантів). 0 — миттєво (завжди автовибір). Понад 1200 с — чекати необмежено (Вимк.). За замовчуванням 60 с. Не застосовується до дозволів і підтверджень надсилання форм.', 'st.display.clarify_timeout.off': 'Вимк.', 'st.display.clarify_timeout.instant': 'Миттєво', + 'st.display.always_allow_api_mutations.label': "Завжди дозволяти зміни через API", + 'st.display.always_allow_api_mutations.desc': "Дозволити WebBrain використовувати POST, PUT, PATCH і DELETE через fetch_url або research_url без команди /allow-api в кожній розмові. Правило пріоритету інтерфейсу та перевірки підтвердження залишаються чинними. Типово вимкнено.", 'st.display.api_mutation_observer.label': 'Спостерігач мутацій API', 'st.display.api_mutation_observer.desc': 'Спостерігайте за URL-адресами та методами запитів XHR/fetch на тій самій вкладці, щоб WebBrain міг виявляти повторювані дії інтерфейсу та пропонувати шаблони API-скорочень. Вимкнено за замовчуванням; вмикайте лише під час дослідження поведінки скорочень або затримки.', 'st.display.openai_ask_streaming.label': "???????? ????????? ? ?????? Ask", diff --git a/src/chrome/src/ui/locales/vi.js b/src/chrome/src/ui/locales/vi.js index c2becb756..b3cd615cf 100644 --- a/src/chrome/src/ui/locales/vi.js +++ b/src/chrome/src/ui/locales/vi.js @@ -518,6 +518,8 @@ export default { 'st.display.site_adapters.desc': "Đưa hướng dẫn ngắn gọn, được tuyển chọn thủ công cho các trang web có lưu lượng truy cập cao đã biết (GitHub, Gmail, Stripe, AWS, v.v.) vào tin nhắn đầu tiên của tổng đài viên. Giúp tác nhân tránh được những ngõ cụt thông thường. Thêm một khoản phí mã thông báo nhỏ vào lượt trò chuyện đầu tiên trên các trang web phù hợp.", 'st.display.voice_input.label': "Nhập bằng giọng nói", 'st.display.voice_input.desc': "Để nút micrô trong đầu vào trò chuyện đọc chính tả văn bản thông qua tính năng nhận dạng giọng nói của trình duyệt. Bật theo mặc định trong các trình duyệt hỗ trợ nó.", + 'st.display.always_allow_api_mutations.label': "Luôn cho phép thay đổi qua API", + 'st.display.always_allow_api_mutations.desc': "Cho phép WebBrain dùng POST, PUT, PATCH và DELETE qua fetch_url hoặc research_url mà không cần /allow-api trong mỗi cuộc trò chuyện. Hướng dẫn ưu tiên giao diện và các bước kiểm tra xác nhận vẫn được áp dụng. Tắt theo mặc định.", 'st.display.api_mutation_observer.label': "Người quan sát đột biến API", 'st.display.api_mutation_observer.desc': "Quan sát các URL và phương thức yêu cầu XHR/tìm nạp cùng một tab để WebBrain có thể phát hiện các hành động giao diện người dùng lặp lại và đề xuất các mẫu phím tắt API. Tắt theo mặc định; chỉ bật trong khi điều tra hành vi hoặc độ trễ của phím tắt.", 'st.display.webmcp.label': "WebMCP thử nghiệm", diff --git a/src/chrome/src/ui/locales/zh.js b/src/chrome/src/ui/locales/zh.js index bff312e38..7d3e99296 100644 --- a/src/chrome/src/ui/locales/zh.js +++ b/src/chrome/src/ui/locales/zh.js @@ -662,6 +662,8 @@ export default { 'st.display.clarify_timeout.desc': '等待澄清问题回复的时长;超时后自动选择第一个选项(若无选项则记为超时)。0 = 立即(始终自动选择)。超过 1200 秒为无限等待(关闭)。默认 60 秒。不适用于权限或表单提交确认。', 'st.display.clarify_timeout.off': '关闭', 'st.display.clarify_timeout.instant': '立即', + 'st.display.always_allow_api_mutations.label': "始终允许 API 变更", + 'st.display.always_allow_api_mutations.desc': "允许 WebBrain 通过 fetch_url 或 research_url 使用 POST、PUT、PATCH 和 DELETE,无需在每个对话中输入 /allow-api。仍会遵循 UI 优先指引和确认检查。默认关闭。", 'st.display.api_mutation_observer.label': 'API 变更观察器', 'st.display.api_mutation_observer.desc': '观察同标签页中的 XHR/fetch 请求 URL 和方法,以便 WebBrain 可以检测重复的界面操作并建议 API 快捷模式。默认关闭;仅在调查快捷行为或延迟时启用。', 'st.display.openai_ask_streaming.label': "? Ask ????????", diff --git a/src/chrome/src/ui/settings.html b/src/chrome/src/ui/settings.html index c4ec6613c..a09361c47 100644 --- a/src/chrome/src/ui/settings.html +++ b/src/chrome/src/ui/settings.html @@ -1333,6 +1333,16 @@

+
+
+
+
+
+ +
diff --git a/src/chrome/src/ui/settings.js b/src/chrome/src/ui/settings.js index 4611682a7..fb60651da 100644 --- a/src/chrome/src/ui/settings.js +++ b/src/chrome/src/ui/settings.js @@ -79,6 +79,7 @@ const maxScreenshotsSelect = document.getElementById('select-max-screenshots'); const maxImageDimensionSelect = document.getElementById('select-max-image-dimension'); const siteAdaptersToggle = document.getElementById('toggle-site-adapters'); const voiceInputToggle = document.getElementById('toggle-voice-input'); +const alwaysAllowApiMutationsToggle = document.getElementById('toggle-always-allow-api-mutations'); const apiMutationObserverToggle = document.getElementById('toggle-api-mutation-observer'); const webMcpToggle = document.getElementById('toggle-webmcp'); const openAIAskStreamingToggle = document.getElementById('toggle-openai-ask-streaming'); @@ -405,7 +406,7 @@ async function init() { chrome.storage.local.remove(['authToken', 'authEmail', 'authDefaultModel']).catch(() => {}); // Load display settings - const stored = await chrome.storage.local.get(['verboseMode', 'selectionShortcutEnabled', 'helpImproveWebBrain', 'screenshotFallback', 'maxAgentSteps', 'autoScreenshot', 'useSiteAdapters', 'voiceInputEnabled', 'apiMutationObserverEnabled', 'webMcpEnabled', 'openaiAskStreamingEnabled', 'planBeforeActMode', 'planBeforeAct', 'planReviewMode', 'planReviewConfidenceThreshold', DOWNLOAD_DIRECTORY_STORAGE_KEY, 'notifySound', 'completionConfetti', 'tracingEnabled', 'strictSecretMode', 'agentAllowLocalNetwork', 'scheduledTasksEnabled', 'scheduledRequireConsequentialConfirmation', 'providerFilter', 'requestTimeoutMs', 'clarifyTimeoutSec', 'clarifyTimeoutSemanticsV2', 'costAllowanceSessionUsd', 'costAllowanceTotalUsd', 'cloudCostSpentUsd', 'screenshotRedaction', 'imageDetail', 'maxScreenshotsPerTurn', 'maxImageDimension']); + const stored = await chrome.storage.local.get(['verboseMode', 'selectionShortcutEnabled', 'helpImproveWebBrain', 'screenshotFallback', 'maxAgentSteps', 'autoScreenshot', 'useSiteAdapters', 'voiceInputEnabled', 'alwaysAllowApiMutations', 'apiMutationObserverEnabled', 'webMcpEnabled', 'openaiAskStreamingEnabled', 'planBeforeActMode', 'planBeforeAct', 'planReviewMode', 'planReviewConfidenceThreshold', DOWNLOAD_DIRECTORY_STORAGE_KEY, 'notifySound', 'completionConfetti', 'tracingEnabled', 'strictSecretMode', 'agentAllowLocalNetwork', 'scheduledTasksEnabled', 'scheduledRequireConsequentialConfirmation', 'providerFilter', 'requestTimeoutMs', 'clarifyTimeoutSec', 'clarifyTimeoutSemanticsV2', 'costAllowanceSessionUsd', 'costAllowanceTotalUsd', 'cloudCostSpentUsd', 'screenshotRedaction', 'imageDetail', 'maxScreenshotsPerTurn', 'maxImageDimension']); if (typeof stored.providerFilter === 'string' && ['all','local','cloud','router'].includes(stored.providerFilter)) { providerFilter = stored.providerFilter; } @@ -455,6 +456,7 @@ async function init() { maxImageDimensionSelect.value = String(stored.maxImageDimension || 1568); siteAdaptersToggle.checked = stored.useSiteAdapters ?? true; if (voiceInputToggle) voiceInputToggle.checked = stored.voiceInputEnabled ?? true; + alwaysAllowApiMutationsToggle.checked = stored.alwaysAllowApiMutations === true; apiMutationObserverToggle.checked = stored.apiMutationObserverEnabled === true; if (webMcpToggle) webMcpToggle.checked = stored.webMcpEnabled === true; // off by default if (openAIAskStreamingToggle) openAIAskStreamingToggle.checked = stored.openaiAskStreamingEnabled !== false; @@ -1096,6 +1098,10 @@ apiMutationObserverToggle.addEventListener('change', async () => { await chrome.storage.local.set({ apiMutationObserverEnabled: apiMutationObserverToggle.checked }).catch(() => {}); }); +alwaysAllowApiMutationsToggle.addEventListener('change', async () => { + await chrome.storage.local.set({ alwaysAllowApiMutations: alwaysAllowApiMutationsToggle.checked }).catch(() => {}); +}); + if (webMcpToggle) { webMcpToggle.addEventListener('change', async () => { await chrome.storage.local.set({ webMcpEnabled: webMcpToggle.checked }).catch(() => {}); diff --git a/src/chrome/src/ui/sidepanel.js b/src/chrome/src/ui/sidepanel.js index cb3b01764..33c0e1b67 100644 --- a/src/chrome/src/ui/sidepanel.js +++ b/src/chrome/src/ui/sidepanel.js @@ -3732,9 +3732,11 @@ async function init() { } }); - // Load verbose setting - const stored = await chrome.storage.local.get('verboseMode'); + // Load settings that affect the composer state. + const stored = await chrome.storage.local.get(['verboseMode', 'alwaysAllowApiMutations']); verboseMode = stored.verboseMode || false; + alwaysAllowApiMutations = stored.alwaysAllowApiMutations === true; + syncApiMutationsAllowedForCurrentTab(); // Restore prior conversation for this tab (if any) — survives close/reopen. const restoreTabId = currentTabId; @@ -3790,6 +3792,10 @@ async function init() { verboseMode = changes.verboseMode.newValue; if (verboseBtn) verboseBtn.classList.toggle('active', verboseMode); } + if (changes.alwaysAllowApiMutations) { + alwaysAllowApiMutations = changes.alwaysAllowApiMutations.newValue === true; + syncApiMutationsAllowedForCurrentTab(); + } if (changes.providers || changes.activeProvider) { void loadProviders(); } @@ -6453,6 +6459,7 @@ function handleInput() { // Per-conversation flag for API mutation override (set via /allow-api). // Reset on clearConversation. Visible to the user in the chat as a system // message and as a sticky badge near the input area. +let alwaysAllowApiMutations = false; let apiMutationsAllowed = false; const apiMutationsAllowedByTab = new Map(); @@ -6471,7 +6478,7 @@ function setApiMutationsAllowedForTab(tabId, allowed) { } function syncApiMutationsAllowedForCurrentTab() { - apiMutationsAllowed = isApiMutationsAllowedForTab(currentTabId); + apiMutationsAllowed = alwaysAllowApiMutations || isApiMutationsAllowedForTab(currentTabId); updateApiBadge(); } diff --git a/src/firefox/src/agent/agent.js b/src/firefox/src/agent/agent.js index 46d9270a4..7f1e7c5b6 100644 --- a/src/firefox/src/agent/agent.js +++ b/src/firefox/src/agent/agent.js @@ -416,6 +416,10 @@ export class Agent extends LoopDetector { this.lastAutoScreenshotTs = new Map(); this.lastSeenAdapter = new Map(); this.apiAllowedTabs = new Set(); + // Global Advanced-setting opt-in. Keep it separate from the tab set so + // /reset clears only the conversation override and live setting changes + // can revoke global permission without erasing explicit /allow-api state. + this.alwaysAllowApiMutations = false; this.apiAllowedInjected = new Set(); this.bulkApiMutationClicks = new Map(); this.bulkApiMutationHints = new Map(); @@ -1109,7 +1113,7 @@ export class Agent extends LoopDetector { user_memory_enabled: this.userMemoryEnabled === true, // Per-tab authorizations only mean something in a tab's context. ...(tabId != null ? { - api_mutations_allowed: this.apiAllowedTabs.has(tabId), + api_mutations_allowed: this.isApiMutationsAllowed(tabId), selection_grounded: this.selectionGroundingScopes.has(tabId), } : {}), image_detail: this.imageDetail, @@ -1741,6 +1745,33 @@ export class Agent extends LoopDetector { } } + setAlwaysAllowApiMutations(allowed) { + const nextAllowed = allowed === true; + const revoked = this.alwaysAllowApiMutations && !nextAllowed; + this.alwaysAllowApiMutations = nextAllowed; + if (!revoked) return; + + // The allow note lives in trusted user-message history, so refreshing the + // system prompt cannot retract it. Append the live state only for chats + // that actually saw that note and lost their effective authorization. + for (const tabId of [...this.apiAllowedInjected]) { + if (this.apiAllowedTabs.has(tabId)) continue; + const messages = this.conversations.get(tabId); + if (Array.isArray(messages)) { + messages.push({ + role: 'user', + content: '[CURRENT API MUTATION AUTHORIZATION — NOT ALLOWED: The persistent API mutation setting was disabled and this conversation has no /allow-api override. This current state supersedes any earlier [USER OVERRIDE — API MUTATIONS ALLOWED] note. Do not plan or call POST/PUT/PATCH/DELETE requests through fetch_url or research_url unless the user explicitly enables /allow-api for this conversation. Continue through the visible UI or ask for /allow-api.]', + }); + this._persist(tabId); + } + this.apiAllowedInjected.delete(tabId); + } + } + + isApiMutationsAllowed(tabId) { + return this.alwaysAllowApiMutations || this.apiAllowedTabs.has(tabId); + } + // Browser-free loop detection is inherited from LoopDetector. These // methods integrate Agent-owned API replay and page-scoped cleanup. @@ -2663,7 +2694,7 @@ export class Agent extends LoopDetector { replayRequestId: replaySource?.replayRequestId || null, replayHasBody: !!replaySource?.hasBody, replayHeaderNames: replaySource?.headerNames || [], - apiAllowed: this.apiAllowedTabs.has(tabId), + apiAllowed: this.isApiMutationsAllowed(tabId), }; } @@ -4282,7 +4313,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d onUpdate('warning', { message }); continue; } - if (isNetworkMutation(fnName, fnArgs) && !this.apiAllowedTabs.has(tabId)) { + if (isNetworkMutation(fnName, fnArgs) && !this.isApiMutationsAllowed(tabId)) { messages.push({ role: 'tool', tool_call_id: tc.id, @@ -4407,7 +4438,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d for (const capability of capabilities) { if (this._skipPermissionGate) { gateDisabled = true; break; } // /allow-api waives ONLY write-method network egress. - if (capability === Capability.NETWORK && isNetworkMutation(fnName, fnArgs) && this.apiAllowedTabs.has(tabId)) continue; + if (capability === Capability.NETWORK && isNetworkMutation(fnName, fnArgs) && this.isApiMutationsAllowed(tabId)) continue; // Every distinct host the call touches must be granted. Usually one, // but download_files takes a urls[] array that can span many hosts. const gateArgs = this._skillPermissionArgsForCapability(skillCallTool, capability, fnArgs); @@ -6565,8 +6596,8 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d contextLine += `[Current page context — applies to this user message and supersedes older page context for phrases like "this page". URL: ${safeField(url)}${title ? ` — Title: ${safeField(title)}` : ''}]\n\n`; } - if (this.apiAllowedTabs.has(tabId) && !this.apiAllowedInjected.has(tabId)) { - contextLine += `[USER OVERRIDE — /allow-api: For this conversation the user has explicitly authorized you to use API mutations (POST/PUT/PATCH/DELETE via fetch_url, or fetch() with mutation methods via execute_js) when you judge API to be more reliable than UI for a specific step. The default UI-first rule still applies — reach for the API when UI has failed/is genuinely unworkable, or when WebBrain reports a [BULK API MUTATION PATTERN] for repeated successful same-kind UI actions. Before any destructive API call, state the URL, method, and payload in plain text in your response so the user can see what you're about to do.]\n\n`; + if (this.isApiMutationsAllowed(tabId) && !this.apiAllowedInjected.has(tabId)) { + contextLine += `[USER OVERRIDE — API MUTATIONS ALLOWED: The user has authorized API mutations (POST/PUT/PATCH/DELETE via fetch_url or research_url). The default UI-first rule still applies — reach for the API when UI has failed/is genuinely unworkable, or when WebBrain reports a [BULK API MUTATION PATTERN] for repeated successful same-kind UI actions. Before any destructive API call, state the URL, method, and payload in plain text in your response so the user can see what you're about to do.]\n\n`; this.apiAllowedInjected.add(tabId); } @@ -7852,7 +7883,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d const skillCatalog = this._skillCatalog(conversationMode, tier); const plannerMessages = buildPlannerMessages(enriched, tabUrl, tabTitle, historyDigest, { noThink: this._plannerPrefersNoThinkPrompt(provider), - allowApi: this.apiAllowedTabs.has(tabId), + allowApi: this.isApiMutationsAllowed(tabId), skillCatalog, locale, priorUserTask: followUpContext.priorUserTask, diff --git a/src/firefox/src/agent/planner.js b/src/firefox/src/agent/planner.js index d07736cc2..0a7704575 100644 --- a/src/firefox/src/agent/planner.js +++ b/src/firefox/src/agent/planner.js @@ -9,7 +9,7 @@ import { sanitizeText } from './text-sanitize.js'; const UNTRUSTED_PAGE_CONTENT_TAG_RE = /<\/?untrusted_page_content\b[^>]*>/gi; const REQUEST_KINDS = new Set(['execute', 'respond', 'plan_only', 'clarify']); -export const PLANNER_API_REPLAY_RULE = '- Because /allow-api is enabled for this conversation, repeated same-kind UI mutations may include a conditional API branch: if WebBrain later reports a [BULK API MUTATION PATTERN], sample exactly one fetch_url replay with the provided replayRequestId. If that sample fails with success:false or HTTP 4xx/5xx, stop using API for that request shape and continue through the paced visible-UI loop.'; +export const PLANNER_API_REPLAY_RULE = '- Because API mutations are authorized, repeated same-kind UI mutations may include a conditional API branch: if WebBrain later reports a [BULK API MUTATION PATTERN], sample exactly one fetch_url replay with the provided replayRequestId. If that sample fails with success:false or HTTP 4xx/5xx, stop using API for that request shape and continue through the paced visible-UI loop.'; export const PLANNER_SYSTEM_PROMPT = `You are the planning subsystem for WebBrain, a browser automation agent. Given the user's task and current page context, output ONLY a single JSON object (no markdown fences, no commentary outside the JSON). diff --git a/src/firefox/src/agent/tools.js b/src/firefox/src/agent/tools.js index 75e36d9d5..10afc4a7e 100644 --- a/src/firefox/src/agent/tools.js +++ b/src/firefox/src/agent/tools.js @@ -1477,7 +1477,7 @@ UI vs API — read this carefully: - The user wants to see what's happening, verify before submission, and have actions look like a human did them through the page. UI flows also work with the user's existing session, while API endpoints often require separate tokens. - TWO exceptions where API mutations are allowed: (1) The user explicitly says "use the API" or "POST to /foo". - (2) The conversation has the [USER OVERRIDE — /allow-api] flag set (you'll see it as a context note). When that's set, you may use API mutations when UI is genuinely failing/unworkable, or when WebBrain reports a [BULK API MUTATION PATTERN] showing repeated successful same-kind UI actions and matching background API requests. Without /allow-api, mutating fetch_url calls are blocked. Before any destructive API call, state the URL, method, and payload in plain text in your response. + (2) The [USER OVERRIDE — API MUTATIONS ALLOWED] context note is present. It can come from /allow-api for this conversation or the user's persistent setting. When present, you may use API mutations when UI is genuinely failing/unworkable, or when WebBrain reports a [BULK API MUTATION PATTERN] showing repeated successful same-kind UI actions and matching background API requests. Without this authorization, mutating fetch_url/research_url calls are blocked. Before any destructive API call, state the URL, method, and payload in plain text in your response. - For READING data (looking things up, fetching a README, comparing prices, checking a status page), \`fetch_url\` and \`research_url\` are the RIGHT tool. Reading is fine. - Examples: - "Create a release on GitHub" → navigate to /releases/new, fill the form, click Publish. NOT a POST to api.github.com. @@ -1671,7 +1671,7 @@ FORMS & MODALS: IFRAMES & UI-vs-API: - Cross-origin iframes (Stripe, payment widgets, embedded forms) are NOT a blocker. Start with iframe_read to enumerate labels and matchIndex values; iframe_click/type fail closed on ambiguity. If the embed remains hard to target and no fields have been changed, use promote_iframe({urlFilter}) to load it standalone in the current run tab. After iframe form edits, call verify_form({urlFilter}) and compare labels/values before done, even when the user will submit later. -- For anything that creates, modifies, deletes, sends, submits, buys, transfers, or posts: go through the visible UI unless /allow-api is enabled and either UI is failing/unworkable or WebBrain reports a [BULK API MUTATION PATTERN]. Do NOT call REST/GraphQL endpoints via fetch_url with POST/PUT/PATCH/DELETE without /allow-api. Reading data (fetch_url / research_url GET) is fine. +- For anything that creates, modifies, deletes, sends, submits, buys, transfers, or posts: go through the visible UI unless API mutations are authorized and either UI is failing/unworkable or WebBrain reports a [BULK API MUTATION PATTERN]. Do NOT call REST/GraphQL endpoints via fetch_url or research_url with POST/PUT/PATCH/DELETE without that authorization. Reading data (fetch_url / research_url GET) is fine. SCRATCHPAD & DON'T REDO WORK: - On long tasks, scratchpad_write({text}) pins miscellaneous facts (IDs, plans) that survive context summarization; downloads are auto-pinned for you (scan the \`[auto]\` lines for downloadIds). Keep entries short and factual. diff --git a/src/firefox/src/background.js b/src/firefox/src/background.js index b1cbbb4b4..ea37696fd 100644 --- a/src/firefox/src/background.js +++ b/src/firefox/src/background.js @@ -79,6 +79,15 @@ import { const providerManager = new ProviderManager(); const agent = new Agent(providerManager); +const ALWAYS_ALLOW_API_MUTATIONS_KEY = 'alwaysAllowApiMutations'; +const alwaysAllowApiMutationsReady = browser.storage.local + .get({ [ALWAYS_ALLOW_API_MUTATIONS_KEY]: false }) + .then((stored) => { + agent.setAlwaysAllowApiMutations(stored[ALWAYS_ALLOW_API_MUTATIONS_KEY] === true); + }) + .catch(() => { + agent.setAlwaysAllowApiMutations(false); + }); agent.setConversationScopeChangeListener((tabId, state) => { browser.runtime.sendMessage({ target: 'sidepanel', @@ -100,6 +109,7 @@ const scheduler = new ScheduledJobManager({ agent, loadProviders: async () => { await customSkillsReady; + await alwaysAllowApiMutationsReady; if (providerManager.providers.size === 0) await providerManager.load(); }, sendUpdate: (tabId, type, data) => { @@ -841,6 +851,10 @@ browser.storage.onChanged.addListener((changes) => { agent.autoScreenshot = changes.autoScreenshot.newValue; } let refreshPrompts = false; + if (changes[ALWAYS_ALLOW_API_MUTATIONS_KEY]) { + agent.setAlwaysAllowApiMutations(changes[ALWAYS_ALLOW_API_MUTATIONS_KEY].newValue === true); + refreshPrompts = true; + } if (changes.useSiteAdapters) { agent.useSiteAdapters = changes.useSiteAdapters.newValue; refreshPrompts = true; @@ -1776,6 +1790,7 @@ async function handleMessage(msg, sender) { // Hydrate agent toggles and prompt add-ons once at boot (not per message); // onChanged keeps them in sync afterward. await Promise.all([planBeforeActReady, planReviewReady, customSkillsReady, userMemoryReady]); + await alwaysAllowApiMutationsReady; await screenshotRedactionReady; await imageBudgetReady; } diff --git a/src/firefox/src/config-transfer.js b/src/firefox/src/config-transfer.js index 6ccaf65d8..2925b6295 100644 --- a/src/firefox/src/config-transfer.js +++ b/src/firefox/src/config-transfer.js @@ -31,6 +31,7 @@ export const DEFAULT_CONFIG_SETTINGS = Object.freeze({ autoScreenshot: 'state_change', useSiteAdapters: true, voiceInputEnabled: true, + alwaysAllowApiMutations: false, apiMutationObserverEnabled: false, webMcpEnabled: false, openaiAskStreamingEnabled: true, @@ -81,6 +82,7 @@ const BOOLEAN_KEYS = new Set([ 'clarifyTimeoutSemanticsV2', 'useSiteAdapters', 'voiceInputEnabled', + 'alwaysAllowApiMutations', 'apiMutationObserverEnabled', 'webMcpEnabled', 'openaiAskStreamingEnabled', diff --git a/src/firefox/src/ui/locales/ar.js b/src/firefox/src/ui/locales/ar.js index 2a21fb218..a161d5a82 100644 --- a/src/firefox/src/ui/locales/ar.js +++ b/src/firefox/src/ui/locales/ar.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'مدة انتظار الرد على سؤال التوضيح قبل اختيار الخيار الأول تلقائيًا (أو انتهاء المهلة إن لم توجد خيارات). 0 = فوري (اختيار تلقائي دائمًا). أعلى من 1200 ثانية = انتظار بلا حدود (إيقاف). الافتراضي 60 ثانية. لا ينطبق على أذونات أو تأكيدات إرسال النماذج.', 'st.display.clarify_timeout.off': 'إيقاف', 'st.display.clarify_timeout.instant': 'فوري', + 'st.display.always_allow_api_mutations.label': "السماح دائمًا بتعديلات API", + 'st.display.always_allow_api_mutations.desc': "اسمح لـ WebBrain باستخدام POST وPUT وPATCH وDELETE عبر fetch_url أو research_url دون الحاجة إلى /allow-api في كل محادثة. تظل إرشادات أولوية واجهة المستخدم وفحوصات التأكيد سارية. معطّل افتراضيًا.", 'st.display.api_mutation_observer.label': 'مراقب تحولات API', 'st.display.api_mutation_observer.desc': 'مراقبة عناوين URL وطرق طلبات XHR/fetch في نفس التبويب ليتمكن WebBrain من اكتشاف إجراءات الواجهة المتكررة واقتراح أنماط اختصارات API. معطل افتراضياً؛ قم بتفعيله فقط أثناء التحقيق في سلوك الاختصار أو زمن الاستجابة.', 'st.display.openai_ask_streaming.label': "?? ?????? ?? ??? ??????", diff --git a/src/firefox/src/ui/locales/bn.js b/src/firefox/src/ui/locales/bn.js index 191fd878f..5711a417c 100644 --- a/src/firefox/src/ui/locales/bn.js +++ b/src/firefox/src/ui/locales/bn.js @@ -515,6 +515,8 @@ export default { 'st.display.site_adapters.desc': "এজেন্টের প্রথম বার্তায় পরিচিত উচ্চ-ট্রাফিক সাইটগুলির (GitHub, Gmail, Stripe, AWS, ইত্যাদি) জন্য সংক্ষিপ্ত, হাতে-কলমে নির্দেশিকা প্রবেশ করান৷ এজেন্টকে সাধারণ ডেড-এন্ড এড়াতে সাহায্য করে। মিলিত সাইটগুলিতে কথোপকথনের প্রথম পালায় একটি ছোট টোকেন খরচ যোগ করে।", 'st.display.voice_input.label': "ভয়েস ইনপুট", 'st.display.voice_input.desc': "চ্যাট ইনপুটে মাইক বোতামটিকে আপনার ব্রাউজারের স্পিচ রিকগনিশনের মাধ্যমে পাঠ্য নির্দেশ করতে দিন। এটি সমর্থন করে এমন ব্রাউজারগুলিতে ডিফল্টরূপে চালু৷", + 'st.display.always_allow_api_mutations.label': "সর্বদা API পরিবর্তনের অনুমতি দিন", + 'st.display.always_allow_api_mutations.desc': "প্রতিটি কথোপকথনে /allow-api ছাড়াই WebBrain-কে fetch_url বা research_url-এর মাধ্যমে POST, PUT, PATCH ও DELETE ব্যবহার করতে দিন। UI-প্রথম নির্দেশনা ও নিশ্চিতকরণ যাচাই এখনও প্রযোজ্য। ডিফল্টভাবে বন্ধ।", 'st.display.api_mutation_observer.label': "API মিউটেশন পর্যবেক্ষক", 'st.display.api_mutation_observer.desc': "একই-ট্যাব XHR/ফেচ অনুরোধ URL এবং পদ্ধতিগুলি পর্যবেক্ষণ করুন যাতে WebBrain বারবার UI অ্যাকশন সনাক্ত করতে পারে এবং API শর্টকাট প্যাটার্নের পরামর্শ দিতে পারে। ডিফল্টরূপে বন্ধ; শুধুমাত্র শর্টকাট আচরণ বা লেটেন্সি তদন্ত করার সময় সক্ষম করুন।", 'st.display.openai_ask_streaming.label': "Ask ????? ????? ??????? ????", diff --git a/src/firefox/src/ui/locales/de.js b/src/firefox/src/ui/locales/de.js index 18137255f..5ef187582 100644 --- a/src/firefox/src/ui/locales/de.js +++ b/src/firefox/src/ui/locales/de.js @@ -510,6 +510,8 @@ export default { 'st.display.site_adapters.desc': 'Fügt kurze, handverlesene Anleitungen für bekannte, stark frequentierte Websites in die erste Nachricht des Agents ein.', 'st.display.voice_input.label': 'Spracheingabe', 'st.display.voice_input.desc': 'Ermöglicht dem Mikrofon-Button in der Chat-Eingabe, Text über die Spracherkennung Ihres Browsers zu diktieren.', + 'st.display.always_allow_api_mutations.label': "API-Änderungen immer erlauben", + 'st.display.always_allow_api_mutations.desc': "WebBrain darf POST, PUT, PATCH und DELETE über fetch_url oder research_url verwenden, ohne dass in jeder Unterhaltung /allow-api erforderlich ist. UI-zuerst-Hinweise und Bestätigungsprüfungen gelten weiterhin. Standardmäßig aus.", 'st.display.api_mutation_observer.label': 'API-Mutations-Observer', 'st.display.api_mutation_observer.desc': 'Beobachtet XHR/fetch-Anfragen-URLs und Methoden im selben Tab, damit WebBrain wiederkehrende UI-Aktionen erkennen und API-Verknüpfungsmuster vorschlagen kann.', 'st.display.openai_ask_streaming.label': 'Ask-Antworten streamen', diff --git a/src/firefox/src/ui/locales/en.js b/src/firefox/src/ui/locales/en.js index d93ba087c..9f2f2ee70 100644 --- a/src/firefox/src/ui/locales/en.js +++ b/src/firefox/src/ui/locales/en.js @@ -515,6 +515,8 @@ export default { 'st.display.site_adapters.desc': 'Inject short, hand-curated guidance for known high-traffic sites (GitHub, Gmail, Stripe, AWS, etc.) into the agent\'s first message. Helps the agent avoid common dead-ends. Adds a small token cost on the first turn of conversations on matched sites.', 'st.display.voice_input.label': 'Voice input', 'st.display.voice_input.desc': 'Let the mic button in the chat input dictate text via your browser\'s speech recognition. On by default in browsers that support it.', + 'st.display.always_allow_api_mutations.label': "Always allow API mutations", + 'st.display.always_allow_api_mutations.desc': "Allow WebBrain to use POST, PUT, PATCH, and DELETE through fetch_url or research_url without requiring /allow-api in each conversation. UI-first guidance and confirmation checks still apply. Off by default.", 'st.display.api_mutation_observer.label': 'API mutation observer', 'st.display.api_mutation_observer.desc': 'Observe same-tab XHR/fetch request URLs and methods so WebBrain can detect repeated UI actions and suggest API shortcut patterns. Off by default; enable only while investigating shortcut behavior or latency.', // TRANSLATORS: Updated for multi-provider Ask streaming and silent transport fallback. diff --git a/src/firefox/src/ui/locales/es.js b/src/firefox/src/ui/locales/es.js index 196d12f93..7ee3798f0 100644 --- a/src/firefox/src/ui/locales/es.js +++ b/src/firefox/src/ui/locales/es.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'Cuánto esperar una respuesta a una pregunta de aclaración antes de elegir automáticamente la primera opción (o agotar el tiempo si no hay opciones). 0 = Instantáneo (autoelegir siempre). Valores por encima de 1200s esperan indefinidamente (Desactivado). Predeterminado 60s. No se aplica a permisos ni confirmaciones de envío de formularios.', 'st.display.clarify_timeout.off': 'Desactivado', 'st.display.clarify_timeout.instant': 'Instantáneo', + 'st.display.always_allow_api_mutations.label': "Permitir siempre mutaciones de API", + 'st.display.always_allow_api_mutations.desc': "Permite que WebBrain use POST, PUT, PATCH y DELETE mediante fetch_url o research_url sin requerir /allow-api en cada conversación. Las directrices de priorizar la interfaz y las comprobaciones de confirmación siguen vigentes. Desactivado de forma predeterminada.", 'st.display.api_mutation_observer.label': 'Observador de mutaciones de API', 'st.display.api_mutation_observer.desc': 'Observa las URLs y métodos de peticiones XHR/fetch en la misma pestaña para que WebBrain pueda detectar acciones repetidas de la interfaz y sugerir patrones de acceso directo por API. Desactivado por defecto; actívalo solo mientras investigas comportamientos de acceso directo o latencia.', 'st.display.openai_ask_streaming.label': "Transmitir respuestas en modo Ask", diff --git a/src/firefox/src/ui/locales/fa.js b/src/firefox/src/ui/locales/fa.js index 733c8524d..8454e538f 100644 --- a/src/firefox/src/ui/locales/fa.js +++ b/src/firefox/src/ui/locales/fa.js @@ -515,6 +515,8 @@ export default { 'st.display.site_adapters.desc': "راهنمای کوتاه و دستی برای سایت‌های پربازدید شناخته شده (GitHub، Gmail، Stripe، AWS، و غیره) در اولین پیام نماینده وارد کنید. به عامل کمک می کند تا از بن بست های رایج جلوگیری کند. در اولین نوبت مکالمه در سایت های منطبق، هزینه رمز اندکی اضافه می کند.", 'st.display.voice_input.label': "ورودی صوتی", 'st.display.voice_input.desc': "اجازه دهید دکمه میکروفون در ورودی چت، متن را از طریق تشخیص گفتار مرورگر شما دیکته کند. به طور پیش فرض در مرورگرهایی که از آن پشتیبانی می کنند روشن است.", + 'st.display.always_allow_api_mutations.label': "همیشه تغییرات API مجاز باشد", + 'st.display.always_allow_api_mutations.desc': "به WebBrain اجازه دهید بدون نیاز به /allow-api در هر گفتگو، از POST، PUT، PATCH و DELETE از طریق fetch_url یا research_url استفاده کند. راهنمای اولویت رابط کاربری و بررسی‌های تأیید همچنان اعمال می‌شوند. به‌طور پیش‌فرض خاموش است.", 'st.display.api_mutation_observer.label': "مشاهده گر جهش API", 'st.display.api_mutation_observer.desc': "نشانی‌های وب و روش‌های درخواستی XHR/واکشی همان تب را مشاهده کنید تا WebBrain بتواند اقدامات مکرر UI را تشخیص دهد و الگوهای میانبر API را پیشنهاد کند. خاموش به طور پیش فرض؛ فقط هنگام بررسی رفتار میانبر یا تأخیر فعال شود.", 'st.display.openai_ask_streaming.label': "??? ??????? ?? ???? Ask", diff --git a/src/firefox/src/ui/locales/fr.js b/src/firefox/src/ui/locales/fr.js index 7cfb9c6d0..2726613fb 100644 --- a/src/firefox/src/ui/locales/fr.js +++ b/src/firefox/src/ui/locales/fr.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'Durée d’attente d’une réponse à une question de clarification avant de sélectionner automatiquement la première option (ou d’expirer s’il n’y a pas d’options). 0 = Immédiat (auto-sélection). Au-delà de 1200s = attendre indéfiniment (Désactivé). Par défaut 60s. Ne s’applique pas aux permissions ni aux confirmations d’envoi de formulaire.', 'st.display.clarify_timeout.off': 'Désactivé', 'st.display.clarify_timeout.instant': 'Immédiat', + 'st.display.always_allow_api_mutations.label': "Toujours autoriser les mutations API", + 'st.display.always_allow_api_mutations.desc': "Autoriser WebBrain à utiliser POST, PUT, PATCH et DELETE via fetch_url ou research_url sans exiger /allow-api dans chaque conversation. Les directives UI-d’abord et les confirmations restent applicables. Désactivé par défaut.", 'st.display.api_mutation_observer.label': 'Observateur de mutations API', 'st.display.api_mutation_observer.desc': 'Observer les URLs et méthodes des requêtes XHR/fetch dans le même onglet pour que WebBrain puisse détecter des actions UI répétées et suggérer des modèles de raccourcis API. Désactivé par défaut ; activer seulement lors de l\'investigation de comportements de raccourci ou de latence.', 'st.display.openai_ask_streaming.label': "Diffuser les r?ponses en mode Ask", diff --git a/src/firefox/src/ui/locales/he.js b/src/firefox/src/ui/locales/he.js index 4ee5d450c..a1b2246fa 100644 --- a/src/firefox/src/ui/locales/he.js +++ b/src/firefox/src/ui/locales/he.js @@ -478,6 +478,8 @@ export default { "st.display.site_adapters.desc": "הזרקו הדרכה קצרה, שנקבעה ביד לאתרים ידועים בעלי תנועה גבוהה (GitHub, Gmail, Stripe, AWSוכו') בהודעה הראשונה של הסוכן. עוזר לסוכן להימנע ממבוי סתום שכיח. מוסיף עלות סמלית קטנה בסיבוב הראשון של שיחות באתרים תואמים.", "st.display.voice_input.label": "קלט קולי", "st.display.voice_input.desc": "תן ללחצן המיקרופון בקלט הצ'אט להכתיב טקסט באמצעות זיהוי הדיבור של הדפדפן שלך. פועל כברירת מחדל בדפדפנים התומכים בו.", + 'st.display.always_allow_api_mutations.label': "לאפשר תמיד שינויי API", + 'st.display.always_allow_api_mutations.desc': "אפשר ל-WebBrain להשתמש ב-POST, PUT, PATCH ו-DELETE דרך fetch_url או research_url בלי לדרוש /allow-api בכל שיחה. הנחיות להעדפת הממשק ובדיקות אישור עדיין חלות. כבוי כברירת מחדל.", "st.display.api_mutation_observer.label": "מעקב אחר שינויי API", "st.display.api_mutation_observer.desc": "עקוב אחר כתובות URL ושיטות של בקשות XHR/fetch באותה כרטיסייה, כדי ש-WebBrain יוכל לזהות פעולות חוזרות בממשק ולהציע דפוסי קיצור דרך דרך ה-API. כבוי כברירת מחדל; הפעל רק בעת בדיקת קיצורי דרך או זמני תגובה.", 'st.display.openai_ask_streaming.label': "????? ?????? ???? Ask", diff --git a/src/firefox/src/ui/locales/hi.js b/src/firefox/src/ui/locales/hi.js index f5bf717c3..5d2fbe8ba 100644 --- a/src/firefox/src/ui/locales/hi.js +++ b/src/firefox/src/ui/locales/hi.js @@ -515,6 +515,8 @@ export default { 'st.display.site_adapters.desc': "एजेंट के पहले संदेश में ज्ञात उच्च-ट्रैफ़िक साइटों (GitHub, Gmail, Stripe, AWS, आदि) के लिए संक्षिप्त, हाथ से क्यूरेटेड मार्गदर्शन शामिल करें। एजेंट को सामान्य गतिरोधों से बचने में मदद करता है। मेल खाने वाली साइटों पर बातचीत के पहले दौर में एक छोटी सी टोकन लागत जुड़ जाती है।", 'st.display.voice_input.label': "ध्वनि इनपुट", 'st.display.voice_input.desc': "चैट इनपुट में माइक बटन को अपने ब्राउज़र की वाक् पहचान के माध्यम से टेक्स्ट निर्देशित करने दें। इसका समर्थन करने वाले ब्राउज़र में डिफ़ॉल्ट रूप से चालू।", + 'st.display.always_allow_api_mutations.label': "API बदलावों को हमेशा अनुमति दें", + 'st.display.always_allow_api_mutations.desc': "WebBrain को हर बातचीत में /allow-api की आवश्यकता के बिना fetch_url या research_url के माध्यम से POST, PUT, PATCH और DELETE का उपयोग करने दें। UI-प्रथम मार्गदर्शन और पुष्टि जाँचें लागू रहेंगी। डिफ़ॉल्ट रूप से बंद।", 'st.display.api_mutation_observer.label': "एपीआई उत्परिवर्तन पर्यवेक्षक", 'st.display.api_mutation_observer.desc': "समान-टैब XHR/फ़ेच अनुरोध URL और विधियों का निरीक्षण करें ताकि WebBrain बार-बार होने वाली UI क्रियाओं का पता लगा सके और API शॉर्टकट पैटर्न सुझा सके। डिफ़ॉल्ट रूप से बंद; शॉर्टकट व्यवहार या विलंबता की जांच करते समय ही सक्षम करें।", 'st.display.openai_ask_streaming.label': "Ask ??? ??? ????? ??????? ????", diff --git a/src/firefox/src/ui/locales/id.js b/src/firefox/src/ui/locales/id.js index 6704e4b06..638421660 100644 --- a/src/firefox/src/ui/locales/id.js +++ b/src/firefox/src/ui/locales/id.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'Berapa lama menunggu balasan pada prompt klarifikasi sebelum memilih opsi pertama secara otomatis (atau timeout jika tidak ada opsi). 0 = Instan (selalu pilih otomatis). Di atas 1200 detik menunggu tanpa batas (Nonaktif). Default 60 detik. Tidak berlaku untuk izin atau konfirmasi kirim formulir.', 'st.display.clarify_timeout.off': 'Nonaktif', 'st.display.clarify_timeout.instant': 'Instan', + 'st.display.always_allow_api_mutations.label': "Selalu izinkan mutasi API", + 'st.display.always_allow_api_mutations.desc': "Izinkan WebBrain menggunakan POST, PUT, PATCH, dan DELETE melalui fetch_url atau research_url tanpa memerlukan /allow-api di setiap percakapan. Panduan yang mengutamakan UI dan pemeriksaan konfirmasi tetap berlaku. Nonaktif secara default.", 'st.display.api_mutation_observer.label': 'Pengamat mutasi API', 'st.display.api_mutation_observer.desc': 'Amati URL dan metode permintaan XHR/fetch pada tab yang sama sehingga WebBrain dapat mendeteksi tindakan UI berulang dan menyarankan pola pintasan API. Nonaktif secara default; aktifkan hanya saat menyelidiki perilaku pintasan atau latensi.', 'st.display.openai_ask_streaming.label': "Streaming respons dalam mode Ask", diff --git a/src/firefox/src/ui/locales/ja.js b/src/firefox/src/ui/locales/ja.js index b98f5948f..6ebaf3cfd 100644 --- a/src/firefox/src/ui/locales/ja.js +++ b/src/firefox/src/ui/locales/ja.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'clarify の返答を待つ時間。経過すると最初の選択肢を自動選択(選択肢がなければタイムアウト)。0 で即時(常に自動選択)。1200 秒超は無制限(オフ)。既定 60 秒。権限やフォーム送信確認には適用されません。', 'st.display.clarify_timeout.off': 'オフ', 'st.display.clarify_timeout.instant': '即時', + 'st.display.always_allow_api_mutations.label': "API変更を常に許可", + 'st.display.always_allow_api_mutations.desc': "会話ごとに /allow-api を必要とせず、WebBrain が fetch_url または research_url で POST、PUT、PATCH、DELETE を使用できるようにします。UI優先の指針と確認チェックは引き続き適用されます。既定ではオフです。", 'st.display.api_mutation_observer.label': 'API変更オブザーバー', 'st.display.api_mutation_observer.desc': '同じタブの XHR/fetch リクエスト URL とメソッドを監視し、WebBrain が繰り返しの UI アクションを検出して API ショートカットパターンを提案できるようにします。デフォルトではオフ。ショートカット動作やレイテンシの調査中のみ有効にしてください。', 'st.display.openai_ask_streaming.label': "Ask ???????????", diff --git a/src/firefox/src/ui/locales/ko.js b/src/firefox/src/ui/locales/ko.js index 94ba1823c..4199c2157 100644 --- a/src/firefox/src/ui/locales/ko.js +++ b/src/firefox/src/ui/locales/ko.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': '명확화 질문에 대한 답변 대기 시간입니다. 시간이 지나면 첫 번째 옵션을 자동 선택합니다(옵션이 없으면 시간 초과). 0은 즉시(항상 자동 선택). 1200초 초과는 무제한(끔). 기본 60초. 권한 또는 양식 제출 확인에는 적용되지 않습니다.', 'st.display.clarify_timeout.off': '끔', 'st.display.clarify_timeout.instant': '즉시', + 'st.display.always_allow_api_mutations.label': "API 변경 항상 허용", + 'st.display.always_allow_api_mutations.desc': "대화마다 /allow-api를 입력하지 않아도 WebBrain이 fetch_url 또는 research_url을 통해 POST, PUT, PATCH, DELETE를 사용하도록 허용합니다. UI 우선 지침과 확인 검사는 계속 적용됩니다. 기본적으로 꺼져 있습니다.", 'st.display.api_mutation_observer.label': 'API 변경 관찰자', 'st.display.api_mutation_observer.desc': '동일한 탭의 XHR/fetch 요청 URL 및 메서드를 관찰하여 WebBrain이 반복되는 UI 작업을 감지하고 API 바로가기 패턴을 제안할 수 있도록 합니다. 기본적으로 꺼져 있습니다. 바로가기 동작이나 지연 시간을 조사할 때만 활성화하세요.', 'st.display.openai_ask_streaming.label': "Ask ?? ????", diff --git a/src/firefox/src/ui/locales/ms.js b/src/firefox/src/ui/locales/ms.js index 759e2b9cf..b1c0d830a 100644 --- a/src/firefox/src/ui/locales/ms.js +++ b/src/firefox/src/ui/locales/ms.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'Berapa lama menunggu balasan soalan penjelasan sebelum memilih pilihan pertama secara automatik (atau tamat masa jika tiada pilihan). 0 = Segera (sentiasa auto-pilih). Melebihi 1200s tunggu tanpa had (Mati). Lalai 60s. Tidak digunakan untuk kebenaran atau pengesahan hantar borang.', 'st.display.clarify_timeout.off': 'Mati', 'st.display.clarify_timeout.instant': 'Segera', + 'st.display.always_allow_api_mutations.label': "Sentiasa benarkan mutasi API", + 'st.display.always_allow_api_mutations.desc': "Benarkan WebBrain menggunakan POST, PUT, PATCH dan DELETE melalui fetch_url atau research_url tanpa memerlukan /allow-api dalam setiap perbualan. Panduan mengutamakan UI dan semakan pengesahan masih terpakai. Dimatikan secara lalai.", 'st.display.api_mutation_observer.label': 'Pemerhati mutasi API', 'st.display.api_mutation_observer.desc': 'Perhatikan URL dan metode permintaan XHR/fetch tab yang sama supaya WebBrain dapat mengesan tindakan UI berulang dan mencadangkan corak pintasan API. Dimatikan secara lalai; aktifkan hanya semasa menyiasat tingkah laku pintasan atau kependaman.', 'st.display.openai_ask_streaming.label': "Strim respons dalam mod Ask", diff --git a/src/firefox/src/ui/locales/nl.js b/src/firefox/src/ui/locales/nl.js index b8d1efa21..99e8b6485 100644 --- a/src/firefox/src/ui/locales/nl.js +++ b/src/firefox/src/ui/locales/nl.js @@ -492,6 +492,8 @@ export default { 'st.display.site_adapters.desc': 'Injecteer korte, handgeschreven begeleiding voor bekende sites met veel verkeer...', 'st.display.voice_input.label': 'Spraakinvoer', 'st.display.voice_input.desc': 'Laat de microfoonknop in de chatinvoer tekst dicteren via de spraakherkenning van uw browser...', + 'st.display.always_allow_api_mutations.label': "API-mutaties altijd toestaan", + 'st.display.always_allow_api_mutations.desc': "Sta WebBrain toe POST, PUT, PATCH en DELETE via fetch_url of research_url te gebruiken zonder /allow-api in elk gesprek. Richtlijnen die de UI vooropstellen en bevestigingscontroles blijven van toepassing. Standaard uit.", 'st.display.api_mutation_observer.label': 'API-wijzigingsobserver', 'st.display.api_mutation_observer.desc': 'Observeer XHR/fetch-verzoek-URL\'s en -methoden op hetzelfde tabblad...', 'st.display.openai_ask_streaming.label': 'Ask-antwoorden streamen', diff --git a/src/firefox/src/ui/locales/pl.js b/src/firefox/src/ui/locales/pl.js index 228c92667..04465f31d 100644 --- a/src/firefox/src/ui/locales/pl.js +++ b/src/firefox/src/ui/locales/pl.js @@ -644,6 +644,8 @@ export default { 'st.display.clarify_timeout.desc': 'Jak długo czekać na odpowiedź na dopytanie, zanim automatycznie wybrana zostanie pierwsza opcja (lub upłynie limit, gdy brak opcji). 0 = Natychmiast (zawsze auto-wybór). Powyżej 1200s czekaj bez limitu (Wył.). Domyślnie 60s. Nie dotyczy uprawnień ani potwierdzeń wysyłki formularza.', 'st.display.clarify_timeout.off': 'Wył.', 'st.display.clarify_timeout.instant': 'Natychmiast', + 'st.display.always_allow_api_mutations.label': "Zawsze zezwalaj na mutacje API", + 'st.display.always_allow_api_mutations.desc': "Zezwól WebBrain na używanie POST, PUT, PATCH i DELETE przez fetch_url lub research_url bez wymagania /allow-api w każdej rozmowie. Nadal obowiązują wskazówki preferujące interfejs oraz kontrole potwierdzeń. Domyślnie wyłączone.", 'st.display.api_mutation_observer.label': 'Obserwator mutacji API', 'st.display.api_mutation_observer.desc': 'Obserwuj URL-e i metody żądań XHR/fetch w tej samej karcie, aby WebBrain mógł wykrywać powtarzające się akcje UI i sugerować wzorce skrótów API. Wyłączone domyślnie; włącz tylko podczas badania zachowania skrótów lub opóźnień.', 'st.display.openai_ask_streaming.label': "Strumieniuj odpowiedzi w trybie Ask", diff --git a/src/firefox/src/ui/locales/pt.js b/src/firefox/src/ui/locales/pt.js index c72fd51d2..9f1d90854 100644 --- a/src/firefox/src/ui/locales/pt.js +++ b/src/firefox/src/ui/locales/pt.js @@ -515,6 +515,8 @@ export default { 'st.display.site_adapters.desc': "Injete orientações curtas e selecionadas manualmente para sites conhecidos de alto tráfego (GitHub, Gmail, Stripe, AWS, etc.) na primeira mensagem do agente. Ajuda o agente a evitar becos sem saída comuns. Adiciona um pequeno custo de token na primeira rodada de conversas em sites correspondentes.", 'st.display.voice_input.label': "Entrada de voz", 'st.display.voice_input.desc': "Deixe o botão do microfone na entrada do bate-papo ditar o texto por meio do reconhecimento de fala do seu navegador. Ativado por padrão em navegadores compatíveis.", + 'st.display.always_allow_api_mutations.label': "Sempre permitir mutações de API", + 'st.display.always_allow_api_mutations.desc': "Permita que o WebBrain use POST, PUT, PATCH e DELETE por fetch_url ou research_url sem exigir /allow-api em cada conversa. As orientações de priorizar a interface e as verificações de confirmação continuam válidas. Desativado por padrão.", 'st.display.api_mutation_observer.label': "Observador de mutação de API", 'st.display.api_mutation_observer.desc': "Observe URLs e métodos de solicitação de busca/XHR na mesma guia para que WebBrain possa detectar ações repetidas da interface do usuário e sugerir padrões de atalho de API. Desativado por padrão; habilite apenas ao investigar o comportamento ou a latência do atalho.", 'st.display.openai_ask_streaming.label': "Transmitir respostas no modo Ask", diff --git a/src/firefox/src/ui/locales/ru.js b/src/firefox/src/ui/locales/ru.js index 504f2eca9..e1a6b5ab3 100644 --- a/src/firefox/src/ui/locales/ru.js +++ b/src/firefox/src/ui/locales/ru.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'Сколько ждать ответа на уточняющий вопрос, прежде чем автоматически выбрать первый вариант (или зафиксировать таймаут без вариантов). 0 — сразу (всегда автовыбор). Больше 1200 с — ждать бесконечно (Выкл.). По умолчанию 60 с. Не применяется к разрешениям и подтверждениям отправки форм.', 'st.display.clarify_timeout.off': 'Выкл.', 'st.display.clarify_timeout.instant': 'Сразу', + 'st.display.always_allow_api_mutations.label': "Всегда разрешать изменения через API", + 'st.display.always_allow_api_mutations.desc': "Разрешить WebBrain использовать POST, PUT, PATCH и DELETE через fetch_url или research_url без команды /allow-api в каждом разговоре. Правило приоритета интерфейса и проверки подтверждения продолжают действовать. По умолчанию выключено.", 'st.display.api_mutation_observer.label': 'Наблюдатель мутаций API', 'st.display.api_mutation_observer.desc': 'Наблюдайте за URL-адресами и методами запросов XHR/fetch на той же вкладке, чтобы WebBrain мог обнаруживать повторяющиеся действия UI и предлагать шаблоны API-шорткатов. Отключено по умолчанию; включайте только при исследовании поведения шорткатов или задержек.', 'st.display.openai_ask_streaming.label': "????????? ?????? ? ?????? Ask", diff --git a/src/firefox/src/ui/locales/th.js b/src/firefox/src/ui/locales/th.js index d95f9d65e..54c4b115b 100644 --- a/src/firefox/src/ui/locales/th.js +++ b/src/firefox/src/ui/locales/th.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'ระยะเวลารอคำตอบ clarify ก่อนเลือกตัวเลือกแรกอัตโนมัติ (หรือหมดเวลาหากไม่มีตัวเลือก) 0 = ทันที (เลือกอัตโนมัติเสมอ) เกิน 1200 วินาที = รอไม่จำกัด (ปิด) ค่าเริ่มต้น 60 วินาที ไม่ใช้กับสิทธิ์หรือการยืนยันส่งฟอร์ม', 'st.display.clarify_timeout.off': 'ปิด', 'st.display.clarify_timeout.instant': 'ทันที', + 'st.display.always_allow_api_mutations.label': "อนุญาตการเปลี่ยนแปลง API เสมอ", + 'st.display.always_allow_api_mutations.desc': "อนุญาตให้ WebBrain ใช้ POST, PUT, PATCH และ DELETE ผ่าน fetch_url หรือ research_url โดยไม่ต้องใช้ /allow-api ในทุกการสนทนา แนวทางที่ให้ความสำคัญกับ UI และการตรวจสอบการยืนยันยังคงมีผล ปิดไว้โดยค่าเริ่มต้น", 'st.display.api_mutation_observer.label': 'ตัวสังเกตการกลายพันธุ์ของ API', 'st.display.api_mutation_observer.desc': 'สังเกต URL และวิธีการของคำขอ XHR/fetch ในแท็บเดียวกัน เพื่อให้ WebBrain ตรวจจับการกระทำ UI ที่ซ้ำและแนะนำรูปแบบทางลัด API ปิดโดยค่าเริ่มต้น เปิดใช้งานเฉพาะเมื่อตรวจสอบพฤติกรรมทางลัดหรือความหน่วง', 'st.display.openai_ask_streaming.label': "???????????????? Ask", diff --git a/src/firefox/src/ui/locales/tl.js b/src/firefox/src/ui/locales/tl.js index c1f83e791..20dbb7c58 100644 --- a/src/firefox/src/ui/locales/tl.js +++ b/src/firefox/src/ui/locales/tl.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'Gaano katagal maghintay ng sagot sa clarify bago awtomatikong piliin ang unang opsyon (o mag-timeout kung walang opsyon). 0 = Agad (palaging auto-select). Higit sa 1200s ay walang hangganan (Naka-off). Default 60s. Hindi para sa permission o form-submit confirmations.', 'st.display.clarify_timeout.off': 'Naka-off', 'st.display.clarify_timeout.instant': 'Agad', + 'st.display.always_allow_api_mutations.label': "Palaging payagan ang mga pagbabago sa API", + 'st.display.always_allow_api_mutations.desc': "Payagan ang WebBrain na gumamit ng POST, PUT, PATCH, at DELETE sa pamamagitan ng fetch_url o research_url nang hindi kailangan ang /allow-api sa bawat usapan. Nalalapat pa rin ang gabay na unahin ang UI at mga pagsusuri sa kumpirmasyon. Naka-off bilang default.", 'st.display.api_mutation_observer.label': 'Tagamasid ng mutasyon ng API', 'st.display.api_mutation_observer.desc': 'Obserbahan ang mga URL at paraan ng XHR/fetch request sa parehong tab upang matukoy ng WebBrain ang mga paulit-ulit na aksyon sa UI at magmungkahi ng mga pattern ng shortcut sa API. Naka-off bilang default; paganahin lamang habang iniimbestigahan ang pag-uugali ng shortcut o latency.', 'st.display.openai_ask_streaming.label': "I-stream ang mga sagot sa Ask mode", diff --git a/src/firefox/src/ui/locales/tr.js b/src/firefox/src/ui/locales/tr.js index efbc7b9e7..453dab641 100644 --- a/src/firefox/src/ui/locales/tr.js +++ b/src/firefox/src/ui/locales/tr.js @@ -658,6 +658,8 @@ export default { 'st.display.clarify_timeout.desc': 'Açıklama sorusuna yanıt için ne kadar bekleneceği; süre dolunca ilk seçenek otomatik seçilir (seçenek yoksa zaman aşımı). 0 = Anında (her zaman otomatik seç). 1200 sn üzeri = süresiz bekle (Kapalı). Varsayılan 60 sn. İzin ve form gönderim onaylarına uygulanmaz.', 'st.display.clarify_timeout.off': 'Kapalı', 'st.display.clarify_timeout.instant': 'Anında', + 'st.display.always_allow_api_mutations.label': "API değişikliklerine her zaman izin ver", + 'st.display.always_allow_api_mutations.desc': "WebBrain’in her konuşmada /allow-api gerektirmeden fetch_url veya research_url üzerinden POST, PUT, PATCH ve DELETE kullanmasına izin verin. Önce arayüz yaklaşımı ve onay kontrolleri uygulanmaya devam eder. Varsayılan olarak kapalıdır.", 'st.display.api_mutation_observer.label': 'API mutasyon gözlemcisi', 'st.display.api_mutation_observer.desc': 'WebBrain\'in tekrarlanan UI eylemlerini tespit etmesi ve API kısayol kalıpları önermesi için aynı sekmedeki XHR/fetch istek URL\'lerini ve yöntemlerini gözlemle. Varsayılan olarak kapalı; yalnızca kısayol davranışını veya gecikmeyi araştırırken etkinleştir.', 'st.display.openai_ask_streaming.label': 'Ask yanıtlarını akışla', diff --git a/src/firefox/src/ui/locales/uk.js b/src/firefox/src/ui/locales/uk.js index 9687a3429..559b7478c 100644 --- a/src/firefox/src/ui/locales/uk.js +++ b/src/firefox/src/ui/locales/uk.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': 'Скільки чекати відповіді на уточнювальне запитання, перш ніж автоматично обрати перший варіант (або зафіксувати таймаут без варіантів). 0 — миттєво (завжди автовибір). Понад 1200 с — чекати необмежено (Вимк.). За замовчуванням 60 с. Не застосовується до дозволів і підтверджень надсилання форм.', 'st.display.clarify_timeout.off': 'Вимк.', 'st.display.clarify_timeout.instant': 'Миттєво', + 'st.display.always_allow_api_mutations.label': "Завжди дозволяти зміни через API", + 'st.display.always_allow_api_mutations.desc': "Дозволити WebBrain використовувати POST, PUT, PATCH і DELETE через fetch_url або research_url без команди /allow-api в кожній розмові. Правило пріоритету інтерфейсу та перевірки підтвердження залишаються чинними. Типово вимкнено.", 'st.display.api_mutation_observer.label': 'Спостерігач мутацій API', 'st.display.api_mutation_observer.desc': 'Спостерігайте за URL-адресами та методами запитів XHR/fetch на тій самій вкладці, щоб WebBrain міг виявляти повторювані дії інтерфейсу та пропонувати шаблони API-скорочень. Вимкнено за замовчуванням; вмикайте лише під час дослідження поведінки скорочень або затримки.', 'st.display.openai_ask_streaming.label': "???????? ????????? ? ?????? Ask", diff --git a/src/firefox/src/ui/locales/vi.js b/src/firefox/src/ui/locales/vi.js index 9dd125327..8493d093b 100644 --- a/src/firefox/src/ui/locales/vi.js +++ b/src/firefox/src/ui/locales/vi.js @@ -515,6 +515,8 @@ export default { 'st.display.site_adapters.desc': "Đưa hướng dẫn ngắn gọn, được tuyển chọn thủ công cho các trang web có lưu lượng truy cập cao đã biết (GitHub, Gmail, Stripe, AWS, v.v.) vào tin nhắn đầu tiên của tổng đài viên. Giúp tác nhân tránh được những ngõ cụt thông thường. Thêm một khoản phí mã thông báo nhỏ vào lượt trò chuyện đầu tiên trên các trang web phù hợp.", 'st.display.voice_input.label': "Nhập bằng giọng nói", 'st.display.voice_input.desc': "Để nút micrô trong đầu vào trò chuyện đọc chính tả văn bản thông qua tính năng nhận dạng giọng nói của trình duyệt. Bật theo mặc định trong các trình duyệt hỗ trợ nó.", + 'st.display.always_allow_api_mutations.label': "Luôn cho phép thay đổi qua API", + 'st.display.always_allow_api_mutations.desc': "Cho phép WebBrain dùng POST, PUT, PATCH và DELETE qua fetch_url hoặc research_url mà không cần /allow-api trong mỗi cuộc trò chuyện. Hướng dẫn ưu tiên giao diện và các bước kiểm tra xác nhận vẫn được áp dụng. Tắt theo mặc định.", 'st.display.api_mutation_observer.label': "Người quan sát đột biến API", 'st.display.api_mutation_observer.desc': "Quan sát các URL và phương thức yêu cầu XHR/tìm nạp cùng một tab để WebBrain có thể phát hiện các hành động giao diện người dùng lặp lại và đề xuất các mẫu phím tắt API. Tắt theo mặc định; chỉ bật trong khi điều tra hành vi hoặc độ trễ của phím tắt.", 'st.display.openai_ask_streaming.label': "Truy?n tr?c tuy?n c?u tr? l?i ? ch? ?? Ask", diff --git a/src/firefox/src/ui/locales/zh.js b/src/firefox/src/ui/locales/zh.js index 741bf94c8..4771ee1e0 100644 --- a/src/firefox/src/ui/locales/zh.js +++ b/src/firefox/src/ui/locales/zh.js @@ -653,6 +653,8 @@ export default { 'st.display.clarify_timeout.desc': '等待澄清问题回复的时长;超时后自动选择第一个选项(若无选项则记为超时)。0 = 立即(始终自动选择)。超过 1200 秒为无限等待(关闭)。默认 60 秒。不适用于权限或表单提交确认。', 'st.display.clarify_timeout.off': '关闭', 'st.display.clarify_timeout.instant': '立即', + 'st.display.always_allow_api_mutations.label': "始终允许 API 变更", + 'st.display.always_allow_api_mutations.desc': "允许 WebBrain 通过 fetch_url 或 research_url 使用 POST、PUT、PATCH 和 DELETE,无需在每个对话中输入 /allow-api。仍会遵循 UI 优先指引和确认检查。默认关闭。", 'st.display.api_mutation_observer.label': 'API 变更观察器', 'st.display.api_mutation_observer.desc': '观察同标签页中的 XHR/fetch 请求 URL 和方法,以便 WebBrain 可以检测重复的界面操作并建议 API 快捷模式。默认关闭;仅在调查快捷行为或延迟时启用。', 'st.display.openai_ask_streaming.label': "? Ask ????????", diff --git a/src/firefox/src/ui/settings.html b/src/firefox/src/ui/settings.html index 26989e962..e23ca4e95 100644 --- a/src/firefox/src/ui/settings.html +++ b/src/firefox/src/ui/settings.html @@ -1324,6 +1324,16 @@

+
+
+
+
+
+ +
diff --git a/src/firefox/src/ui/settings.js b/src/firefox/src/ui/settings.js index ab767fd2d..d4b846aea 100644 --- a/src/firefox/src/ui/settings.js +++ b/src/firefox/src/ui/settings.js @@ -79,6 +79,7 @@ const maxScreenshotsSelect = document.getElementById('select-max-screenshots'); const maxImageDimensionSelect = document.getElementById('select-max-image-dimension'); const siteAdaptersToggle = document.getElementById('toggle-site-adapters'); const voiceInputToggle = document.getElementById('toggle-voice-input'); +const alwaysAllowApiMutationsToggle = document.getElementById('toggle-always-allow-api-mutations'); const apiMutationObserverToggle = document.getElementById('toggle-api-mutation-observer'); const openAIAskStreamingToggle = document.getElementById('toggle-openai-ask-streaming'); const planBeforeActModeSelect = document.getElementById('select-plan-before-act-mode'); @@ -402,7 +403,7 @@ async function init() { browser.storage.local.remove(['authToken', 'authEmail', 'authDefaultModel']).catch(() => {}); // Load display settings - const stored = await browser.storage.local.get(['verboseMode', 'selectionShortcutEnabled', 'helpImproveWebBrain', 'screenshotFallback', 'maxAgentSteps', 'autoScreenshot', 'useSiteAdapters', 'voiceInputEnabled', 'apiMutationObserverEnabled', 'openaiAskStreamingEnabled', 'planBeforeActMode', 'planBeforeAct', 'planReviewMode', 'planReviewConfidenceThreshold', DOWNLOAD_DIRECTORY_STORAGE_KEY, 'notifySound', 'completionConfetti', 'tracingEnabled', 'strictSecretMode', 'agentAllowLocalNetwork', 'scheduledTasksEnabled', 'scheduledRequireConsequentialConfirmation', 'providerFilter', 'requestTimeoutMs', 'clarifyTimeoutSec', 'clarifyTimeoutSemanticsV2', 'costAllowanceSessionUsd', 'costAllowanceTotalUsd', 'cloudCostSpentUsd', 'screenshotRedaction', 'imageDetail', 'maxScreenshotsPerTurn', 'maxImageDimension']); + const stored = await browser.storage.local.get(['verboseMode', 'selectionShortcutEnabled', 'helpImproveWebBrain', 'screenshotFallback', 'maxAgentSteps', 'autoScreenshot', 'useSiteAdapters', 'voiceInputEnabled', 'alwaysAllowApiMutations', 'apiMutationObserverEnabled', 'openaiAskStreamingEnabled', 'planBeforeActMode', 'planBeforeAct', 'planReviewMode', 'planReviewConfidenceThreshold', DOWNLOAD_DIRECTORY_STORAGE_KEY, 'notifySound', 'completionConfetti', 'tracingEnabled', 'strictSecretMode', 'agentAllowLocalNetwork', 'scheduledTasksEnabled', 'scheduledRequireConsequentialConfirmation', 'providerFilter', 'requestTimeoutMs', 'clarifyTimeoutSec', 'clarifyTimeoutSemanticsV2', 'costAllowanceSessionUsd', 'costAllowanceTotalUsd', 'cloudCostSpentUsd', 'screenshotRedaction', 'imageDetail', 'maxScreenshotsPerTurn', 'maxImageDimension']); if (typeof stored.providerFilter === 'string' && ['all','local','cloud','router'].includes(stored.providerFilter)) { providerFilter = stored.providerFilter; } @@ -451,6 +452,7 @@ async function init() { if (maxImageDimensionSelect) maxImageDimensionSelect.value = String(stored.maxImageDimension || 1568); if (siteAdaptersToggle) siteAdaptersToggle.checked = stored.useSiteAdapters ?? true; if (voiceInputToggle) voiceInputToggle.checked = stored.voiceInputEnabled ?? true; + if (alwaysAllowApiMutationsToggle) alwaysAllowApiMutationsToggle.checked = stored.alwaysAllowApiMutations === true; if (apiMutationObserverToggle) apiMutationObserverToggle.checked = stored.apiMutationObserverEnabled === true; if (openAIAskStreamingToggle) openAIAskStreamingToggle.checked = stored.openaiAskStreamingEnabled !== false; if (planBeforeActModeSelect) planBeforeActModeSelect.value = normalizePlanBeforeActMode(stored); @@ -1076,6 +1078,10 @@ apiMutationObserverToggle?.addEventListener('change', async () => { await browser.storage.local.set({ apiMutationObserverEnabled: apiMutationObserverToggle.checked }).catch(() => {}); }); +alwaysAllowApiMutationsToggle?.addEventListener('change', async () => { + await browser.storage.local.set({ alwaysAllowApiMutations: alwaysAllowApiMutationsToggle.checked }).catch(() => {}); +}); + openAIAskStreamingToggle?.addEventListener('change', async () => { await browser.storage.local.set({ openaiAskStreamingEnabled: openAIAskStreamingToggle.checked }).catch(() => {}); }); diff --git a/src/firefox/src/ui/sidepanel.js b/src/firefox/src/ui/sidepanel.js index 4a5d02a2f..a48b21319 100644 --- a/src/firefox/src/ui/sidepanel.js +++ b/src/firefox/src/ui/sidepanel.js @@ -3584,9 +3584,11 @@ async function init() { } }); - // Load verbose setting - const stored = await browser.storage.local.get('verboseMode'); + // Load settings that affect the composer state. + const stored = await browser.storage.local.get(['verboseMode', 'alwaysAllowApiMutations']); verboseMode = stored.verboseMode || false; + alwaysAllowApiMutations = stored.alwaysAllowApiMutations === true; + syncApiMutationsAllowedForCurrentTab(); // Restore prior conversation for this tab (if any) — survives close/reopen. const restoreTabId = currentTabId; @@ -3641,6 +3643,10 @@ async function init() { verboseMode = changes.verboseMode.newValue; if (verboseBtn) verboseBtn.classList.toggle('active', verboseMode); } + if (changes.alwaysAllowApiMutations) { + alwaysAllowApiMutations = changes.alwaysAllowApiMutations.newValue === true; + syncApiMutationsAllowedForCurrentTab(); + } if (changes.providers || changes.activeProvider) { void loadProviders(); } @@ -6293,6 +6299,7 @@ function handleInput() { // --- Message Sending --- // Per-conversation API mutation override (set via /allow-api). +let alwaysAllowApiMutations = false; let apiMutationsAllowed = false; const apiMutationsAllowedByTab = new Map(); @@ -6311,7 +6318,7 @@ function setApiMutationsAllowedForTab(tabId, allowed) { } function syncApiMutationsAllowedForCurrentTab() { - apiMutationsAllowed = isApiMutationsAllowedForTab(currentTabId); + apiMutationsAllowed = alwaysAllowApiMutations || isApiMutationsAllowedForTab(currentTabId); updateApiBadge(); } diff --git a/test/run.js b/test/run.js index dc9573a58..9d40b36e0 100644 --- a/test/run.js +++ b/test/run.js @@ -21482,6 +21482,35 @@ test('API mutation observer setting is opt-in and controls the request observer' } }); +test('persistent API mutation permission is opt-in, portable, and mirrored', () => { + for (const [label, bgRel, settingsRel, htmlRel, panelRel, configRel] of [ + ['chrome', 'src/chrome/src/background.js', 'src/chrome/src/ui/settings.js', 'src/chrome/src/ui/settings.html', 'src/chrome/src/ui/sidepanel.js', 'src/chrome/src/config-transfer.js'], + ['firefox', 'src/firefox/src/background.js', 'src/firefox/src/ui/settings.js', 'src/firefox/src/ui/settings.html', 'src/firefox/src/ui/sidepanel.js', 'src/firefox/src/config-transfer.js'], + ]) { + const bg = fs.readFileSync(path.join(ROOT, bgRel), 'utf8'); + const settings = fs.readFileSync(path.join(ROOT, settingsRel), 'utf8'); + const html = fs.readFileSync(path.join(ROOT, htmlRel), 'utf8'); + const panel = fs.readFileSync(path.join(ROOT, panelRel), 'utf8'); + const config = fs.readFileSync(path.join(ROOT, configRel), 'utf8'); + + assert.match(html, /advanced-settings-body[\s\S]*id="toggle-always-allow-api-mutations"/, `${label}: persistent permission toggle should live under Advanced`); + assert.match(html, /id="always-allow-api-mutations-label"[^>]*data-i18n="st\.display\.always_allow_api_mutations\.label"/, `${label}: persistent permission label should expose a stable accessible-name target`); + assert.match(html, /id="always-allow-api-mutations-desc"[^>]*data-i18n="st\.display\.always_allow_api_mutations\.desc"/, `${label}: persistent permission description should expose a stable accessible-description target`); + assert.match(html, /id="toggle-always-allow-api-mutations"[^>]*aria-labelledby="always-allow-api-mutations-label"[^>]*aria-describedby="always-allow-api-mutations-desc"/, `${label}: persistent permission toggle should reference its translated label and description`); + assert.doesNotMatch(html, /id="toggle-always-allow-api-mutations"\s+checked/, `${label}: persistent permission must default off`); + assert.match(settings, /alwaysAllowApiMutationsToggle\.checked = stored\.alwaysAllowApiMutations === true/, `${label}: settings should load only explicit persistent opt-in`); + assert.match(settings, /alwaysAllowApiMutations:\s*alwaysAllowApiMutationsToggle\.checked/, `${label}: settings should save the persistent permission`); + assert.match(bg, /const ALWAYS_ALLOW_API_MUTATIONS_KEY = 'alwaysAllowApiMutations';/, `${label}: background storage key missing`); + assert.match(bg, /setAlwaysAllowApiMutations\(stored\[ALWAYS_ALLOW_API_MUTATIONS_KEY\] === true\)/, `${label}: background should initialize the Agent from storage`); + assert.match(bg, /await alwaysAllowApiMutationsReady;/, `${label}: first agent runs should wait for persistent permission hydration`); + assert.match(bg, /changes\[ALWAYS_ALLOW_API_MUTATIONS_KEY\][\s\S]*setAlwaysAllowApiMutations\(changes\[ALWAYS_ALLOW_API_MUTATIONS_KEY\]\.newValue === true\)/, `${label}: background should apply live revocation`); + assert.match(panel, /alwaysAllowApiMutations = stored\.alwaysAllowApiMutations === true/, `${label}: side panel should load the persistent badge state`); + assert.match(panel, /alwaysAllowApiMutations \|\| isApiMutationsAllowedForTab\(currentTabId\)/, `${label}: badge should combine persistent and conversation permission`); + assert.match(config, /alwaysAllowApiMutations:\s*false/, `${label}: config export default should remain off`); + assert.match(config, /'alwaysAllowApiMutations'/, `${label}: config import should validate the boolean setting`); + } +}); + test('settings async test controls surface rejected background results', () => { for (const [label, settingsRel, htmlRel] of [ ['chrome', 'src/chrome/src/ui/settings.js', 'src/chrome/src/ui/settings.html'], @@ -22439,7 +22468,7 @@ test('sidepanel scopes allow-api override to the tab conversation', () => { const panel = fs.readFileSync(path.join(ROOT, panelRel), 'utf8'); assert.match(panel, /const apiMutationsAllowedByTab = new Map\(\);/, `${label}: /allow-api should be tracked per tab`); assert.match(panel, /function isApiMutationsAllowedForTab\(tabId\) \{[\s\S]*?apiMutationsAllowedByTab\.get\(tabId\) === true;[\s\S]*?\}/, `${label}: /allow-api per-tab read helper missing`); - assert.match(panel, /function syncApiMutationsAllowedForCurrentTab\(\) \{[\s\S]*?apiMutationsAllowed = isApiMutationsAllowedForTab\(currentTabId\);[\s\S]*?updateApiBadge\(\);[\s\S]*?\}/, `${label}: tab switches should resync /allow-api badge state`); + assert.match(panel, /function syncApiMutationsAllowedForCurrentTab\(\) \{[\s\S]*?apiMutationsAllowed = alwaysAllowApiMutations \|\| isApiMutationsAllowedForTab\(currentTabId\);[\s\S]*?updateApiBadge\(\);[\s\S]*?\}/, `${label}: tab switches should combine persistent and conversation API badge state`); const switchStart = panel.indexOf('function switchToTab(newTabId)'); assert.notEqual(switchStart, -1, `${label}: switchToTab missing`); @@ -39972,6 +40001,68 @@ test('agent clearConversation drops /allow-api override in both builds', () => { } }); +test('persistent API mutation permission is global and independent of /allow-api lifecycle', () => { + for (const AgentClass of [AgentCh, AgentFx]) { + const agent = new AgentClass({}); + const tabId = 4896; + + assert.equal(agent.isApiMutationsAllowed(tabId), false, `${AgentClass.name}: persistent permission should default off`); + + agent.setAlwaysAllowApiMutations(true); + assert.equal(agent.isApiMutationsAllowed(tabId), true, `${AgentClass.name}: persistent permission did not enable a fresh conversation`); + + agent.setApiMutationsAllowed(tabId, true); + agent.setAlwaysAllowApiMutations(false); + assert.equal(agent.isApiMutationsAllowed(tabId), true, `${AgentClass.name}: disabling the global setting cleared the conversation override`); + + agent.clearConversation(tabId); + assert.equal(agent.isApiMutationsAllowed(tabId), false, `${AgentClass.name}: conversation override survived reset after global permission was disabled`); + + agent.setAlwaysAllowApiMutations(true); + agent.clearConversation(tabId); + assert.equal(agent.isApiMutationsAllowed(tabId), true, `${AgentClass.name}: reset cleared the persistent permission`); + } +}); + +test('disabling persistent API permission retracts stale prompt authorization in both builds', () => { + for (const AgentClass of [AgentCh, AgentFx]) { + const agent = new AgentClass({}); + const revokedTabId = 4898; + const stillAllowedTabId = 4899; + const allowedNote = '[USER OVERRIDE — API MUTATIONS ALLOWED: earlier trusted authorization]'; + const revokedMessages = [ + { role: 'system', content: 'sys' }, + { role: 'user', content: allowedNote }, + ]; + const stillAllowedMessages = [ + { role: 'system', content: 'sys' }, + { role: 'user', content: allowedNote }, + ]; + const persisted = []; + agent._persist = (tabId) => persisted.push(tabId); + agent.conversations.set(revokedTabId, revokedMessages); + agent.conversations.set(stillAllowedTabId, stillAllowedMessages); + agent.setAlwaysAllowApiMutations(true); + agent.apiAllowedInjected.add(revokedTabId); + agent.apiAllowedInjected.add(stillAllowedTabId); + agent.setApiMutationsAllowed(stillAllowedTabId, true); + + agent.setAlwaysAllowApiMutations(false); + + assert.equal(agent.isApiMutationsAllowed(revokedTabId), false, `${AgentClass.name}: persistent revocation did not update effective permission`); + assert.equal(revokedMessages.length, 3, `${AgentClass.name}: persistent revocation did not append a current-state note`); + assert.equal(revokedMessages.at(-1)?.role, 'user', `${AgentClass.name}: revocation note should remain in trusted user-message history`); + assert.match(revokedMessages.at(-1)?.content || '', /CURRENT API MUTATION AUTHORIZATION — NOT ALLOWED/, `${AgentClass.name}: revocation note did not state the current denial`); + assert.match(revokedMessages.at(-1)?.content || '', /supersedes any earlier \[USER OVERRIDE — API MUTATIONS ALLOWED\]/, `${AgentClass.name}: revocation note did not supersede stale authorization`); + assert.equal(agent.apiAllowedInjected.has(revokedTabId), false, `${AgentClass.name}: revoked tab retained its injection marker`); + assert.deepEqual(persisted, [revokedTabId], `${AgentClass.name}: revocation note was not persisted exactly once`); + + assert.equal(agent.isApiMutationsAllowed(stillAllowedTabId), true, `${AgentClass.name}: persistent revocation overrode /allow-api`); + assert.equal(stillAllowedMessages.length, 2, `${AgentClass.name}: tab with /allow-api received a contradictory denial`); + assert.equal(agent.apiAllowedInjected.has(stillAllowedTabId), true, `${AgentClass.name}: tab with /allow-api lost its valid injection marker`); + } +}); + test('agent clearConversation drops transient page-run state in both builds', () => { for (const AgentClass of [AgentCh, AgentFx]) { const agent = new AgentClass({}); @@ -43053,36 +43144,42 @@ test('agent blocks captured replay mutations until /allow-api when method is omi } }); -test('agent allows mutating fetch_url after /allow-api', async () => { +test('agent allows mutating fetch_url after conversation or persistent API approval', async () => { for (const AgentClass of [AgentCh, AgentFx]) { - const agent = new AgentClass({ getVisionProvider: async () => null }); - let executed = false; - agent.executeTool = async () => { - executed = true; - return { success: true }; - }; - agent._ensureGateSetting = async () => {}; - agent._skipPermissionGate = true; - agent.setApiMutationsAllowed(4896, true); - const messages = []; + for (const [approval, enable] of [ + ['/allow-api', (agent, tabId) => agent.setApiMutationsAllowed(tabId, true)], + ['persistent setting', (agent) => agent.setAlwaysAllowApiMutations(true)], + ]) { + const agent = new AgentClass({ getVisionProvider: async () => null }); + let executed = false; + agent.executeTool = async () => { + executed = true; + return { success: true }; + }; + agent._ensureGateSetting = async () => {}; + agent._skipPermissionGate = true; + const tabId = 4896; + enable(agent, tabId); + const messages = []; - await agent._executeToolBatch( - 4896, - [{ - id: 'tool_1', - function: { name: 'fetch_url', arguments: '{"url":"https://github.com/users/follow?target=alice","method":"POST"}' }, - }], - messages, - () => {}, - { supportsVision: false }, - '', - new Set(['fetch_url']), - 1, - ); + await agent._executeToolBatch( + tabId, + [{ + id: 'tool_1', + function: { name: 'fetch_url', arguments: '{"url":"https://github.com/users/follow?target=alice","method":"POST"}' }, + }], + messages, + () => {}, + { supportsVision: false }, + '', + new Set(['fetch_url']), + 1, + ); - assert.equal(executed, true, `${AgentClass.name}: mutating fetch_url did not run after /allow-api`); - assert.equal(messages.length, 1, `${AgentClass.name}: expected executed tool result message`); - assert.doesNotMatch(messages[0].content, /requiresApiAllow/, `${AgentClass.name}: allowed mutation was still blocked`); + assert.equal(executed, true, `${AgentClass.name}: mutating fetch_url did not run after ${approval}`); + assert.equal(messages.length, 1, `${AgentClass.name}: expected executed tool result message after ${approval}`); + assert.doesNotMatch(messages[0].content, /requiresApiAllow/, `${AgentClass.name}: ${approval} mutation was still blocked`); + } } });