feat: proper confirmation-resume flow + per-user tool policy

Fixes the broken confirmation gate where users had no way to approve
or deny a blocked tool call in the web UI.

Changes:
- orchestrator_engine.py: add OrchestrateCheckpoint dataclass, extract
  loop into _run_from_contents(), add resume() function
- openai_orchestrator.py: same treatment — _run_from_messages(), resume()
- routers/orchestrator.py: POST /{job_id}/confirm and /deny endpoints,
  separate _checkpoints store, _resume_job() + _finalize_job() helpers,
  "awaiting_confirmation" job status with pending_confirmation payload
- auth_utils.py: get_tool_policy() and save_tool_policy() helpers reading
  home/{user}/tool_policy.json (allow/deny lists)
- routers/orchestrator.py: loads tool_policy per user and passes
  confirm_allow/confirm_deny to both engines
- app.js: poll loop handles awaiting_confirmation — shows Confirm/Deny
  buttons inline, resumes polling after user action
- settings.html + settings.py: Tool Permissions section with allow/deny
  textareas, POST /settings/tool-policy route
- style.css: .confirm-gate, .confirm-btn, .deny-btn styles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-04-30 19:14:53 -04:00
parent bce7de647c
commit 6405dd338d
8 changed files with 733 additions and 159 deletions

View File

@@ -393,6 +393,35 @@
</form>
</div>
<!-- Tool Permissions -->
<div class="section">
<h2>Tool Permissions</h2>
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.5rem; line-height:1.55;">
Override the default confirmation gate for orchestrator tools.
<strong>Allow list</strong> — tools that run without asking for confirmation.
<strong>Deny list</strong> — tools that are always blocked for your account.
One tool name per line.
</p>
<p style="font-size:0.78rem; color:var(--pg-muted); margin-bottom:0.85rem;">
Tools requiring confirmation by default: <code>{{ confirm_required_tools }}</code>
</p>
<form method="POST" action="/settings/tool-policy">
<div class="form-group">
<label for="allow_list">Allow list (bypass confirmation)</label>
<textarea id="allow_list" name="allow_list" rows="3"
placeholder="reminders_clear&#10;cron_remove"
autocomplete="off" spellcheck="false">{{ tool_allow }}</textarea>
</div>
<div class="form-group">
<label for="deny_list">Deny list (always block)</label>
<textarea id="deny_list" name="deny_list" rows="3"
placeholder="shell_exec&#10;file_write"
autocomplete="off" spellcheck="false">{{ tool_deny }}</textarea>
</div>
<button type="submit">Save tool permissions</button>
</form>
</div>
<!-- Browser cache -->
<div class="section">
<h2>Browser Cache</h2>