mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-10 18:22:20 +02:00
fix(ui): complete exact approval continuation
This commit is contained in:
+2
-2
@@ -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=20260815toolapproval1';
|
||||
import chatModule from './js/chat.js?v=20260815toolapproval2';
|
||||
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=20260815toolapproval1';
|
||||
import chatRenderer from './js/chatRenderer.js?v=20260815toolapproval2';
|
||||
import sessionModule from './js/sessions.js';
|
||||
import memoryModule from './js/memory.js?v=20260722memoryloading1';
|
||||
import voiceRecorderModule from './js/voiceRecorder.js';
|
||||
|
||||
+5
-5
@@ -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=20260808startupshell1">
|
||||
<link rel="modulepreload" href="/static/js/chat.js?v=20260815toolapproval1">
|
||||
<link rel="modulepreload" href="/static/app.js?v=20260815toolapproval2">
|
||||
<link rel="modulepreload" href="/static/js/chat.js?v=20260815toolapproval2">
|
||||
<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=20260815toolapproval1"></script>
|
||||
<script type="module" src="/static/js/chatRenderer.js?v=20260815toolapproval2"></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=20260815toolapproval1"></script>
|
||||
<script type="module" src="/static/js/chat.js?v=20260815toolapproval2"></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=20260808startupshell1"></script> <!-- app.js must be LAST -->
|
||||
<script type="module" src="/static/app.js?v=20260815toolapproval2"></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>
|
||||
|
||||
+23
-7
@@ -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=20260815toolapproval1';
|
||||
import chatRenderer from './chatRenderer.js?v=20260815toolapproval2';
|
||||
import chatStream from './chatStream.js';
|
||||
import { addAITTSButton } from './tts-ai.js';
|
||||
import markdownModule from './markdown.js';
|
||||
@@ -61,6 +61,24 @@ import { createTerminalStreamError, isRecoverableStreamError } from './chatStrea
|
||||
let _contextHeaderBound = false;
|
||||
let _pendingToolApproval = null;
|
||||
|
||||
function _submitToolApprovalWhenIdle(approvalId, label) {
|
||||
if (
|
||||
!_pendingToolApproval
|
||||
|| _pendingToolApproval.approval_id !== approvalId
|
||||
) return;
|
||||
if (isStreaming || _sendInFlight) {
|
||||
setTimeout(() => _submitToolApprovalWhenIdle(approvalId, label), 120);
|
||||
return;
|
||||
}
|
||||
const input = document.getElementById('message');
|
||||
if (input) {
|
||||
input.value = label;
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
const sendButton = document.querySelector('.send-btn');
|
||||
if (sendButton) sendButton.click();
|
||||
}
|
||||
|
||||
document.addEventListener('odysseus:tool-approval', (event) => {
|
||||
const detail = event && event.detail ? event.detail : {};
|
||||
const decision = String(detail.decision || '').toLowerCase();
|
||||
@@ -69,12 +87,10 @@ import { createTerminalStreamError, isRecoverableStreamError } from './chatStrea
|
||||
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();
|
||||
_submitToolApprovalWhenIdle(
|
||||
_pendingToolApproval.approval_id,
|
||||
detail.label || (decision === 'approve' ? 'Allow once' : 'Deny'),
|
||||
);
|
||||
});
|
||||
|
||||
function _fmtContextNumber(n) {
|
||||
|
||||
@@ -2532,9 +2532,12 @@ export function addMessage(role, content, modelName, metadata) {
|
||||
const toolRounds = Object.keys(toolsByRound).map(Number);
|
||||
const maxRound = Math.max(toolRounds.length ? Math.max(...toolRounds) : 0, roundTexts.length);
|
||||
|
||||
for (let r = 0; r < maxRound; r++) {
|
||||
const roundNum = r + 1;
|
||||
const txt = resolveDocumentPlaceholderLinks((roundTexts[r] || '').trim(), metadata);
|
||||
const firstRound = (toolsByRound[0] || []).length ? 0 : 1;
|
||||
for (let roundNum = firstRound; roundNum <= maxRound; roundNum++) {
|
||||
const r = roundNum - 1;
|
||||
const txt = r >= 0
|
||||
? resolveDocumentPlaceholderLinks((roundTexts[r] || '').trim(), metadata)
|
||||
: '';
|
||||
|
||||
if (txt) {
|
||||
const wrap = document.createElement('div');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import Storage from './storage.js';
|
||||
import uiModule, { autoResize, styledPrompt } from './ui.js';
|
||||
import chatRenderer from './chatRenderer.js?v=20260815toolapproval1';
|
||||
import chatRenderer from './chatRenderer.js?v=20260815toolapproval2';
|
||||
import { providerLogo } from './providers.js';
|
||||
import { initModelPicker, updateModelPicker } from './modelPicker.js?v=20260722ctxheader1';
|
||||
import themeModule from './theme.js';
|
||||
|
||||
@@ -37,7 +37,7 @@ def _patch_common(monkeypatch):
|
||||
monkeypatch.setattr(al, "estimate_tokens", lambda *a, **k: 10, raising=False)
|
||||
|
||||
async def _fake_exec(block, *a, **k):
|
||||
return ("bash", {"output": "ok", "exit_code": 0})
|
||||
return (block.tool_type, {"output": "ok", "exit_code": 0})
|
||||
monkeypatch.setattr(al, "execute_tool_block", _fake_exec, raising=False)
|
||||
|
||||
|
||||
@@ -58,8 +58,14 @@ def _run_loop(monkeypatch, round_text, max_rounds=2):
|
||||
|
||||
def test_emits_rounds_exhausted_when_cap_hit_mid_task(monkeypatch):
|
||||
_patch_common(monkeypatch)
|
||||
# Every round returns a tool block -> never "done" -> loop exhausts the cap.
|
||||
events = _run_loop(monkeypatch, "```bash\necho hi\n```", max_rounds=2)
|
||||
# Use a system-owned interaction result so this remains a loop-control test:
|
||||
# Bash output is workspace-derived and now correctly pauses for exact user
|
||||
# approval before a later Bash call.
|
||||
events = _run_loop(
|
||||
monkeypatch,
|
||||
'```update_plan\n{"plan":"- [ ] keep going"}\n```',
|
||||
max_rounds=2,
|
||||
)
|
||||
assert any(e.get("type") == "rounds_exhausted" for e in events), events
|
||||
|
||||
|
||||
@@ -84,7 +90,11 @@ def test_emits_intent_nudge_exhausted_when_cap_is_exhausted(monkeypatch):
|
||||
def test_emits_loop_breaker_triggered_when_loop_breaker_trips(monkeypatch):
|
||||
_patch_common(monkeypatch)
|
||||
|
||||
events = _run_loop(monkeypatch, "```bash\necho hi\n```", max_rounds=6)
|
||||
events = _run_loop(
|
||||
monkeypatch,
|
||||
'```update_plan\n{"plan":"- [ ] keep going"}\n```',
|
||||
max_rounds=6,
|
||||
)
|
||||
|
||||
guard = next((e for e in events if e.get("type") == "loop_breaker_triggered"), None)
|
||||
assert guard is not None, events
|
||||
|
||||
@@ -854,6 +854,7 @@ def test_frontend_tool_approval_uses_opaque_id_and_fixed_decisions():
|
||||
root = Path(__file__).parents[1]
|
||||
chat = (root / "static/js/chat.js").read_text()
|
||||
renderer = (root / "static/js/chatRenderer.js").read_text()
|
||||
index = (root / "static/index.html").read_text()
|
||||
|
||||
assert "fd.append('tool_approval_id'" in chat
|
||||
assert "fd.append('tool_approval_decision'" in chat
|
||||
@@ -861,6 +862,12 @@ def test_frontend_tool_approval_uses_opaque_id_and_fixed_decisions():
|
||||
assert "aq.kind === 'tool_approval'" in renderer
|
||||
assert "aq.action.content" in renderer
|
||||
assert "decision: String((opt && opt.value)" in renderer
|
||||
assert "if (isStreaming || _sendInFlight)" in chat
|
||||
assert "_submitToolApprovalWhenIdle" in chat
|
||||
assert "input.dispatchEvent(new Event('input'" in chat
|
||||
assert "const firstRound = (toolsByRound[0] || []).length ? 0 : 1" in renderer
|
||||
assert index.count("app.js?v=20260815toolapproval2") == 2
|
||||
assert "app.js?v=20260808startupshell1" not in index
|
||||
|
||||
|
||||
def test_frontend_raw_fences_do_not_call_document_mutators():
|
||||
|
||||
@@ -23,7 +23,7 @@ _IMPORT_REWRITES = {
|
||||
"import uiModule, { autoResize, styledPrompt } from './ui.js';": (
|
||||
"import uiModule, { autoResize, styledPrompt } from './ui.mjs';"
|
||||
),
|
||||
"import chatRenderer from './chatRenderer.js?v=20260722ctxheader1';": (
|
||||
"import chatRenderer from './chatRenderer.js?v=20260815toolapproval2';": (
|
||||
"import chatRenderer from './chatRenderer.mjs';"
|
||||
),
|
||||
"import { providerLogo } from './providers.js';": (
|
||||
|
||||
Reference in New Issue
Block a user