fix(agent): close approval continuation gaps

This commit is contained in:
RaresKeY
2026-08-15 06:14:37 +00:00
parent fd50561af6
commit 58b2a4bfa9
24 changed files with 654 additions and 47 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ import modelsModule from './js/models.js?v=20260715startupcalm2';
import ragModule from './js/rag.js';
import presetsModule from './js/presets.js';
import searchModule from './js/search.js';
import chatModule from './js/chat.js?v=20260815toolapproval2';
import chatModule from './js/chat.js?v=20260815toolapproval3';
import compareModule from './js/compare/index.js?v=20260723compareicon2';
import documentModule from './js/document.js?v=20260722emailfastindex1';
import searchChatModule from './js/search-chat.js';
@@ -22,7 +22,7 @@ import {
settleSessionHydration
} from './js/startupShell.js';
import markdownModule from './js/markdown.js';
import chatRenderer from './js/chatRenderer.js?v=20260815toolapproval2';
import chatRenderer from './js/chatRenderer.js?v=20260815toolapproval3';
import sessionModule from './js/sessions.js';
import memoryModule from './js/memory.js?v=20260722memoryloading1';
import voiceRecorderModule from './js/voiceRecorder.js';
+5 -5
View File
@@ -258,8 +258,8 @@
<link rel="preload" as="font" type="font/woff2" crossorigin href="/static/fonts/FiraCode-Regular.woff2">
<link rel="preload" as="font" type="font/woff2" crossorigin href="/static/fonts/FiraCode-SemiBold.woff2">
<link rel="stylesheet" href="/static/style.css?v=20260808startupshell1">
<link rel="modulepreload" href="/static/app.js?v=20260815toolapproval2">
<link rel="modulepreload" href="/static/js/chat.js?v=20260815toolapproval2">
<link rel="modulepreload" href="/static/app.js?v=20260815toolapproval3">
<link rel="modulepreload" href="/static/js/chat.js?v=20260815toolapproval3">
<link rel="modulepreload" href="/static/js/ui.js">
<link rel="modulepreload" href="/static/js/sessions.js">
<link rel="modulepreload" href="/static/js/markdown.js">
@@ -2534,10 +2534,10 @@
<script type="module" src="/static/js/tts-ai.js"></script>
<script type="module" src="/static/js/document.js?v=20260722emailfastindex1"></script>
<script type="module" src="/static/js/gallery.js?v=20260708match1"></script>
<script type="module" src="/static/js/chatRenderer.js?v=20260815toolapproval2"></script>
<script type="module" src="/static/js/chatRenderer.js?v=20260815toolapproval3"></script>
<script type="module" src="/static/js/codeRunner.js"></script>
<script type="module" src="/static/js/chatStream.js?v=20260722emailfastindex1"></script>
<script type="module" src="/static/js/chat.js?v=20260815toolapproval2"></script>
<script type="module" src="/static/js/chat.js?v=20260815toolapproval3"></script>
<script type="module" src="/static/js/cookbook.js"></script>
<script src="/static/js/cookbookSchedule.js"></script>
<script type="module" src="/static/js/search-chat.js"></script>
@@ -2545,7 +2545,7 @@
<script type="module" src="/static/js/censor.js"></script>
<script type="module" src="/static/js/settings.js?v=20260723compareicon1"></script>
<script type="module" src="/static/js/assistant.js"></script>
<script type="module" src="/static/app.js?v=20260815toolapproval2"></script> <!-- app.js must be LAST -->
<script type="module" src="/static/app.js?v=20260815toolapproval3"></script> <!-- app.js must be LAST -->
<script type="module" src="/static/js/init.js?v=20260715freshroot3"></script>
<script type="module" src="/static/js/a11y.js"></script>
<script nonce="{{CSP_NONCE}}">if('serviceWorker' in navigator){navigator.serviceWorker.register('/static/sw.js').catch(()=>{});}</script>
+1 -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=20260815toolapproval2';
import chatRenderer from './chatRenderer.js?v=20260815toolapproval3';
import chatStream from './chatStream.js';
import { addAITTSButton } from './tts-ai.js';
import markdownModule from './markdown.js';
+1 -1
View File
@@ -2524,7 +2524,7 @@ export function addMessage(role, content, modelName, metadata) {
const toolsByRound = {};
for (const ev of toolEvents) {
const r = ev.round || 1;
const r = ev.round ?? 1;
if (!toolsByRound[r]) toolsByRound[r] = [];
toolsByRound[r].push(ev);
}
+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=20260815toolapproval2';
import chatRenderer from './chatRenderer.js?v=20260815toolapproval3';
import { providerLogo } from './providers.js';
import { initModelPicker, updateModelPicker } from './modelPicker.js?v=20260722ctxheader1';
import themeModule from './theme.js';
+57
View File
@@ -1128,10 +1128,67 @@ function _renderTestLog(logEl, verdictEl, job, card, name) {
else if (ev.type === 'agent_step') add('— round ' + ev.round + ' —', 'skill-test-round');
else if (ev.type === 'tool_start') add('▸ ' + ev.tool + ' ' + String(ev.command || '').slice(0, 200), 'skill-test-tool');
else if (ev.type === 'tool_output') add(String(ev.output || '').slice(0, 500), 'skill-test-out');
else if (ev.type === 'approval_granted' || ev.type === 'approval_denied') add(ev.text || '', 'skill-test-meta');
else if (ev.type === 'say') add(ev.text || '', 'skill-test-say');
else if (ev.type === 'evaluating') add('Evaluating run…', 'skill-test-meta');
else if (ev.type === 'error') add('Error: ' + (ev.error || 'run failed'), 'skill-test-err');
}
if (job.status === 'awaiting_approval' && job.approval) {
const approval = job.approval;
const box = document.createElement('div');
box.className = 'skill-test-approval';
const question = document.createElement('div');
question.className = 'skill-test-meta';
question.textContent = approval.question || 'Allow this exact action once?';
box.appendChild(question);
if (approval.action) {
const action = document.createElement('pre');
action.className = 'skill-test-out';
action.textContent = [
approval.action.tool || 'tool',
approval.action.content || '',
Array.isArray(approval.action.effects)
? `Effects: ${approval.action.effects.join(', ')}`
: '',
approval.action.workspace ? `Workspace: ${approval.action.workspace}` : '',
approval.action.digest ? `Approval fingerprint: ${approval.action.digest}` : '',
].filter(Boolean).join('\n');
box.appendChild(action);
}
const actions = document.createElement('div');
actions.className = 'modal-footer';
const decide = async (decision) => {
actions.querySelectorAll('button').forEach(btn => { btn.disabled = true; });
try {
const response = await fetch(
`${API}/api/skills/${encodeURIComponent(name)}/test-approval`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ approval_id: approval.approval_id, decision }),
},
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
await _testSkill(card, name, false);
} catch (error) {
add(`Approval failed: ${error.message || error}`, 'skill-test-err');
actions.querySelectorAll('button').forEach(btn => { btn.disabled = false; });
}
};
for (const [decision, label, cls] of [
['deny', 'Deny', 'confirm-btn confirm-btn-secondary'],
['approve', 'Allow once', 'confirm-btn confirm-btn-primary'],
]) {
const button = document.createElement('button');
button.type = 'button';
button.className = cls;
button.textContent = label;
button.addEventListener('click', () => decide(decision));
actions.appendChild(button);
}
box.appendChild(actions);
logEl.appendChild(box);
}
if (job.status === 'running') add('…running (you can close this — it keeps going)', 'skill-test-meta');
logEl.scrollTop = logEl.scrollHeight;
if (job.status === 'done' && job.verdict) _renderTestVerdict(verdictEl, job.verdict, card, name);