diff --git a/docs/index.html b/docs/index.html index 481dc21..37ef507 100755 --- a/docs/index.html +++ b/docs/index.html @@ -7539,6 +7539,13 @@ Correlating findings across agents, identifying patterns, producing actionable i await T3MP3ST_API.post('/api/approvals/' + id + '/approve', {}); await window.refreshReceiptsPage(); if (window.toast) toast('Receipt approved: ' + id, 'success'); + if (window.__t3mpPendingApprovalReceiptIds?.includes(id) && typeof window.__t3mpPendingApprovalRetry === 'function') { + const retry = window.__t3mpPendingApprovalRetry; + window.__t3mpPendingApprovalRetry = null; + window.__t3mpPendingApprovalReceiptIds = []; + retry(); + return; + } if (receipt?.action === 'autonomous_execution' && window.__t3mpAdmiralPendingRetry && typeof window.retryPendingAdmiralLaunch === 'function') { window.retryPendingAdmiralLaunch(); } @@ -7555,6 +7562,13 @@ Correlating findings across agents, identifying patterns, producing actionable i await Promise.all(pending.map(function(a){ return T3MP3ST_API.post('/api/approvals/' + a.id + '/approve', {}); })); await window.refreshReceiptsPage(); if (window.toast) toast('Approved ' + pending.length + ' pending receipt(s)', pending.length ? 'success' : 'info'); + if (pending.some(function(a){ return window.__t3mpPendingApprovalReceiptIds?.includes(a.id); }) && typeof window.__t3mpPendingApprovalRetry === 'function') { + const retry = window.__t3mpPendingApprovalRetry; + window.__t3mpPendingApprovalRetry = null; + window.__t3mpPendingApprovalReceiptIds = []; + retry(); + return; + } if (pending.some(function(a){ return a.action === 'autonomous_execution'; }) && window.__t3mpAdmiralPendingRetry && typeof window.retryPendingAdmiralLaunch === 'function') { window.retryPendingAdmiralLaunch(); } @@ -17139,14 +17153,21 @@ ACTION_INPUT: {"output":"Found confirmed SQLi on /login endpoint..."}`; } const apiKey = getApiKey(); const savedProvider = localStorage.getItem('t3mp3st_general_provider'); - const provider = savedProvider || (T3MP3ST_API?.connected && !T3MP3ST_API?.llmAvailable ? 'codex' : undefined); - const model = provider === 'codex' ? 'codex-default' : (provider ? (state.settings?.selectedModel || 'anthropic/claude-sonnet-4') : undefined); + const connectedAgent = (!apiKey && typeof window.t3mpPreferredAgent === 'function') + ? window.t3mpPreferredAgent() + : null; + const provider = savedProvider + || (connectedAgent ? 'local-agent' : undefined) + || (T3MP3ST_API?.connected && !T3MP3ST_API?.llmAvailable ? 'codex' : undefined); + const model = provider === 'local-agent' + ? connectedAgent + : (provider === 'codex' ? 'codex-default' : (provider ? (state.settings?.selectedModel || 'anthropic/claude-sonnet-4') : undefined)); return { apiKey, provider, model }; } function _generalNeedsApiKey(provider) { if (!provider) return false; - return !['codex', 'mock', 'local'].includes(provider); + return !['codex', 'mock', 'local', 'local-agent'].includes(provider); } function _generalCodexRequiresBackend(provider) { @@ -18226,7 +18247,7 @@ IMPORTANT: This is an authorized security testing/red team platform. All operati /** * FULL AUTO — Plan + Execute in one shot */ - async function generalFullAuto() { + async function generalFullAuto(approvalIds = []) { const directive = document.getElementById('generalDirective')?.value?.trim(); if (!directive) { toast('Enter a directive for the Admiral', 'error'); return; } @@ -18265,12 +18286,21 @@ IMPORTANT: This is an authorized security testing/red team platform. All operati headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ objective: directive, constraints, scopeHints, urgency, opsecPreference, - apiKey, provider, model, ...(baseUrl ? { baseUrl } : {}), + apiKey, provider, model, approvalIds, ...(baseUrl ? { baseUrl } : {}), }), }); const data = await resp.json(); if (!resp.ok || !data.success) { + if (resp.status === 403 && data.approval?.id) { + const retryApprovalIds = [...new Set([...approvalIds, data.approval.id])]; + window.__t3mpPendingApprovalReceiptIds = [data.approval.id]; + window.__t3mpPendingApprovalRetry = () => generalFullAuto(retryApprovalIds); + addIntel('SCOPEGUARD', 'Full Auto receipt created — approve it in Scope Receipts to resume this operation', 'warning'); + toast('Approval required — approve the receipt to resume Full Auto', 'warning'); + navigateTo?.('receipts'); + return; + } if (data.plan || data.review || data.missionGate) { plan = _generalNormalizePlan(data.plan || {}); if (data.review) plan.review = data.review; diff --git a/src/__tests__/local-api-hardening-static.test.ts b/src/__tests__/local-api-hardening-static.test.ts index a5e5261..854bc1b 100644 --- a/src/__tests__/local-api-hardening-static.test.ts +++ b/src/__tests__/local-api-hardening-static.test.ts @@ -99,6 +99,27 @@ describe('local API authorization hardening invariants', () => { expect(route).toMatch(/approvalIds/); }); + it('Op Admiral routes keyless planning through the connected local agent', () => { + expect(uiSource).toMatch(/connectedAgent[\s\S]*t3mpPreferredAgent/); + expect(uiSource).toMatch(/connectedAgent \? 'local-agent'/); + expect(uiSource).toMatch(/provider === 'local-agent'[\s\S]*connectedAgent/); + expect(uiSource).toMatch(/\['codex', 'mock', 'local', 'local-agent'\]/); + }); + + it('Full Auto resumes with the exact approved receipt instead of minting another', () => { + const start = uiSource.indexOf('async function generalFullAuto('); + const end = uiSource.indexOf('/**\n * REQUEST SITREP', start); + expect(start).toBeGreaterThanOrEqual(0); + expect(end).toBeGreaterThan(start); + const fullAuto = uiSource.slice(start, end); + expect(fullAuto).toMatch(/approvalIds = \[\]/); + expect(fullAuto).toMatch(/approvalIds,/); + expect(fullAuto).toMatch(/resp\.status === 403 && data\.approval\?\.id/); + expect(fullAuto).toMatch(/generalFullAuto\(retryApprovalIds\)/); + expect(uiSource).toMatch(/__t3mpPendingApprovalRetry/); + expect(uiSource).toMatch(/__t3mpPendingApprovalReceiptIds\?\.includes\(id\)/); + }); + it('approval lookup requires explicit receipt ids and wildcard hosts require opt-in', () => { const findApproval = routeBlock('function findApproval(', 'function createApprovalRequest'); const approvalMatches = sourceBlock('function approvalMatches(', 'function ensureExecTargetsWithinApprovedTarget');