fix(agent): authorize exact actions after untrusted context

This commit is contained in:
RaresKeY
2026-08-15 05:37:47 +00:00
parent 2811c7e815
commit 1b09c568d8
16 changed files with 1510 additions and 52 deletions
+23 -1
View File
@@ -8,7 +8,7 @@
import Storage from './storage.js';
import uiModule from './ui.js';
import sessionModule from './sessions.js';
import chatRenderer from './chatRenderer.js?v=20260722emailfastindex1';
import chatRenderer from './chatRenderer.js?v=20260815toolapproval1';
import chatStream from './chatStream.js';
import { addAITTSButton } from './tts-ai.js';
import markdownModule from './markdown.js';
@@ -59,6 +59,23 @@ import { createTerminalStreamError, isRecoverableStreamError } from './chatStrea
let _contextHeaderSeq = 0;
let _contextHeaderData = null;
let _contextHeaderBound = false;
let _pendingToolApproval = null;
document.addEventListener('odysseus:tool-approval', (event) => {
const detail = event && event.detail ? event.detail : {};
const decision = String(detail.decision || '').toLowerCase();
if (!detail.approval_id || !['approve', 'deny'].includes(decision)) return;
_pendingToolApproval = {
approval_id: String(detail.approval_id),
decision,
};
const input = document.getElementById('message');
if (input) {
input.value = detail.label || (decision === 'approve' ? 'Allow once' : 'Deny');
}
const sendButton = document.querySelector('.send-btn');
if (sendButton) sendButton.click();
});
function _fmtContextNumber(n) {
const v = Number(n || 0);
@@ -1756,6 +1773,11 @@ import { createTerminalStreamError, isRecoverableStreamError } from './chatStrea
const fd = new FormData();
fd.append('message', _finalMsgWithInject);
fd.append('session', streamSessionId);
if (_pendingToolApproval) {
fd.append('tool_approval_id', _pendingToolApproval.approval_id);
fd.append('tool_approval_decision', _pendingToolApproval.decision);
_pendingToolApproval = null;
}
if (selectedRouteForSend.model) fd.append('selected_model', selectedRouteForSend.model);
if (selectedRouteForSend.endpoint_url) fd.append('selected_endpoint_url', selectedRouteForSend.endpoint_url);
if (selectedRouteForSend.endpoint_id) fd.append('selected_endpoint_id', selectedRouteForSend.endpoint_id);
+37 -2
View File
@@ -2342,6 +2342,7 @@ export function renderAskUserCard(payload, options) {
card.setAttribute('role', 'group');
card.tabIndex = -1;
const multi = !!aq.multi;
const isToolApproval = aq.kind === 'tool_approval' && !!aq.approval_id;
const emojiText = (value) => svgifyEmoji(uiModule.esc(String(value)));
const head = document.createElement('div');
@@ -2366,6 +2367,27 @@ export function renderAskUserCard(payload, options) {
card.appendChild(question);
card.setAttribute('aria-labelledby', question.id);
if (isToolApproval && aq.action) {
const action = document.createElement('div');
action.className = 'ask-user-option-desc';
const effects = Array.isArray(aq.action.effects)
? aq.action.effects.join(', ')
: '';
action.textContent = [
aq.action.tool || 'tool',
aq.action.content || '',
effects ? `Effects: ${effects}` : '',
aq.action.workspace ? `Workspace: ${aq.action.workspace}` : '',
aq.action.document_id ? `Document: ${aq.action.document_id}` : '',
aq.action.document_version != null
? `Document version: ${aq.action.document_version}`
: '',
aq.action.digest ? `Approval fingerprint: ${aq.action.digest}` : '',
].filter(Boolean).join('\n');
action.style.whiteSpace = 'pre-wrap';
card.appendChild(action);
}
const list = document.createElement('div');
list.className = 'ask-user-options';
card.appendChild(list);
@@ -2403,7 +2425,20 @@ export function renderAskUserCard(payload, options) {
}
if (!multi) {
row.type = 'button';
row.addEventListener('click', () => send(label));
row.addEventListener('click', () => {
if (isToolApproval) {
card.remove();
document.dispatchEvent(new CustomEvent('odysseus:tool-approval', {
detail: {
approval_id: aq.approval_id,
decision: String((opt && opt.value) || '').toLowerCase(),
label,
},
}));
} else {
send(label);
}
});
}
list.appendChild(row);
});
@@ -2439,7 +2474,7 @@ export function renderAskUserCard(payload, options) {
});
other.appendChild(otherInput);
other.appendChild(otherSend);
card.appendChild(other);
if (!isToolApproval) card.appendChild(other);
chatBox.appendChild(card);
if (renderOptions.scroll !== false) {
+1 -1
View File
@@ -3,7 +3,7 @@
import Storage from './storage.js';
import uiModule, { autoResize, styledPrompt } from './ui.js';
import chatRenderer from './chatRenderer.js?v=20260722ctxheader1';
import chatRenderer from './chatRenderer.js?v=20260815toolapproval1';
import { providerLogo } from './providers.js';
import { initModelPicker, updateModelPicker } from './modelPicker.js?v=20260722ctxheader1';
import themeModule from './theme.js';