diff --git a/docs/index.html b/docs/index.html
index 7ce7abe..77ef5c5 100755
--- a/docs/index.html
+++ b/docs/index.html
@@ -26365,7 +26365,8 @@ Be specific and actionable. Format as structured JSON.`;
+ pill(status, color)
+ (connected ? '' : '')
+ (a.ready ? '' : '')
- + (a.ready ? '' : '')
+ + (connected ? ''
+ : (a.ready ? '' : ''))
+ '';
}
@@ -26495,6 +26496,22 @@ Be specific and actionable. Format as structured JSON.`;
};
window.connectOneLocalAgent = function(id) { connect([id]); };
+ window.disconnectLocalAgent = async function(id) {
+ try {
+ const data = await api('/api/agents/local/disconnect', { id: id });
+ delete pingStatus[id];
+ const ids = Array.isArray(data.connected) ? data.connected : connectedIds.filter(function(x){ return x !== id; });
+ if (getActiveAgent() === id) setActiveAgentStored('');
+ persistConnected(ids);
+ render(lastDetected, ids);
+ if (window.toast) toast(id+' disconnected', 'success');
+ if (typeof window.refreshSystemStatus === 'function') window.refreshSystemStatus();
+ if (typeof updatePreflightChecklist === 'function') { try { updatePreflightChecklist(); } catch(e) {} }
+ } catch(e) {
+ if (window.toast) toast('Disconnect failed: '+e.message, 'error');
+ }
+ };
+
// Pin (or unpin) which connected agent drives missions + analysis as the LLM backbone.
// Clicking the active one again clears the pin (back to the auto codex→hermes→claude order).
window.setActiveLocalAgent = function(id) {
diff --git a/src/__tests__/local-api-hardening-static.test.ts b/src/__tests__/local-api-hardening-static.test.ts
index e256b5a..efee3b1 100644
--- a/src/__tests__/local-api-hardening-static.test.ts
+++ b/src/__tests__/local-api-hardening-static.test.ts
@@ -108,6 +108,14 @@ describe('local API authorization hardening invariants', () => {
expect(uiSource).toMatch(/\['codex', 'mock', 'local', 'local-agent'\]/);
});
+ it('Settings can disconnect a local agent without silently reconnecting it', () => {
+ expect(uiSource).toMatch(/onclick="disconnectLocalAgent\(\\'/);
+ expect(uiSource).toContain("api('/api/agents/local/disconnect', { id: id })");
+ expect(uiSource).toContain("if (getActiveAgent() === id) setActiveAgentStored('')");
+ expect(uiSource).toContain('persistConnected(ids)');
+ expect(uiSource).toContain('delete pingStatus[id]');
+ });
+
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);