Merge pull request #15 from joey-david/fix/local-agent-deselection
fix: local agent deselection
This commit is contained in:
4 files changed
+59
-7
No files matched your search
+4
-4
@@ -24825,14 +24825,14 @@ Be specific and actionable. Format as structured JSON.`;
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function connect(ids) {
|
async function connect(ids, replaceSelection) {
|
||||||
if (!ids.length) { if(window.toast) toast('Tick at least one ready agent first','warning'); return; }
|
if (!ids.length && !replaceSelection) { if(window.toast) toast('Tick at least one ready agent first','warning'); return; }
|
||||||
const deep = (document.getElementById('localAgentsDeepPing')||{}).checked;
|
const deep = (document.getElementById('localAgentsDeepPing')||{}).checked;
|
||||||
const btn = document.getElementById('localAgentsConnectAllBtn');
|
const btn = document.getElementById('localAgentsConnectAllBtn');
|
||||||
const orig = btn ? btn.innerHTML : '';
|
const orig = btn ? btn.innerHTML : '';
|
||||||
if (btn) { btn.disabled=true; btn.innerHTML='⚡ Connecting…'; btn.style.opacity='0.7'; }
|
if (btn) { btn.disabled=true; btn.innerHTML='⚡ Connecting…'; btn.style.opacity='0.7'; }
|
||||||
try {
|
try {
|
||||||
const data = await api('/api/agents/local/connect', { ids: ids, ping: !!deep });
|
const data = await api('/api/agents/local/connect', { ids: ids, ping: !!deep, replace: !!replaceSelection });
|
||||||
(data.results||[]).forEach(function(r){ if (r.ping) pingStatus[r.id] = { ok:r.ping.ok, latencyMs:r.ping.latencyMs, error:r.ping.error }; });
|
(data.results||[]).forEach(function(r){ if (r.ping) pingStatus[r.id] = { ok:r.ping.ok, latencyMs:r.ping.latencyMs, error:r.ping.error }; });
|
||||||
render(lastDetected, (data.connected||[]).map(function(c){return c.id;}));
|
render(lastDetected, (data.connected||[]).map(function(c){return c.id;}));
|
||||||
persistConnected(connectedIds); // remember for silent auto-reconnect after refresh
|
persistConnected(connectedIds); // remember for silent auto-reconnect after refresh
|
||||||
@@ -24855,7 +24855,7 @@ Be specific and actionable. Format as structured JSON.`;
|
|||||||
|
|
||||||
window.connectSelectedLocalAgents = function() {
|
window.connectSelectedLocalAgents = function() {
|
||||||
const ids = Array.prototype.slice.call(document.querySelectorAll('.la-sel:checked')).map(function(c){return c.dataset.id;});
|
const ids = Array.prototype.slice.call(document.querySelectorAll('.la-sel:checked')).map(function(c){return c.dataset.id;});
|
||||||
connect(ids);
|
connect(ids, true);
|
||||||
};
|
};
|
||||||
window.connectOneLocalAgent = function(id) { connect([id]); };
|
window.connectOneLocalAgent = function(id) { connect([id]); };
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { syncLocalAgentSelection } from '../agent/local-agents.js';
|
||||||
|
|
||||||
|
describe('syncLocalAgentSelection', () => {
|
||||||
|
it('disconnects agents omitted from the bulk selection', () => {
|
||||||
|
const connected = new Map([
|
||||||
|
['codex', { id: 'codex' }],
|
||||||
|
['hermes', { id: 'hermes' }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
syncLocalAgentSelection(connected, ['hermes'], true);
|
||||||
|
|
||||||
|
expect([...connected.keys()]).toEqual(['hermes']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves existing agents for an additive single-agent connect', () => {
|
||||||
|
const connected = new Map([
|
||||||
|
['codex', { id: 'codex' }],
|
||||||
|
['hermes', { id: 'hermes' }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
syncLocalAgentSelection(connected, ['hermes'], false);
|
||||||
|
|
||||||
|
expect([...connected.keys()]).toEqual(['codex', 'hermes']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disconnects every agent when the bulk selection is empty', () => {
|
||||||
|
const connected = new Map([
|
||||||
|
['codex', { id: 'codex' }],
|
||||||
|
['hermes', { id: 'hermes' }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
syncLocalAgentSelection(connected, [], true);
|
||||||
|
|
||||||
|
expect(connected.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -71,6 +71,19 @@ function childEnv(): NodeJS.ProcessEnv {
|
|||||||
|
|
||||||
export type LocalAgentId = 'claude' | 'codex' | 'hermes';
|
export type LocalAgentId = 'claude' | 'codex' | 'hermes';
|
||||||
|
|
||||||
|
/** Apply an authoritative bulk selection; single-agent connects remain additive. */
|
||||||
|
export function syncLocalAgentSelection<T>(
|
||||||
|
connected: Map<string, T>,
|
||||||
|
selectedIds: string[],
|
||||||
|
replace: boolean,
|
||||||
|
): void {
|
||||||
|
if (!replace) return;
|
||||||
|
const selected = new Set(selectedIds);
|
||||||
|
for (const id of connected.keys()) {
|
||||||
|
if (!selected.has(id)) connected.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface AgentSpec {
|
interface AgentSpec {
|
||||||
id: LocalAgentId;
|
id: LocalAgentId;
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
+5
-3
@@ -22,7 +22,7 @@ import { LLMBackbone } from './llm/index.js';
|
|||||||
import { TempestCommand } from './index.js';
|
import { TempestCommand } from './index.js';
|
||||||
import { OpGeneral } from './general/index.js';
|
import { OpGeneral } from './general/index.js';
|
||||||
import type { Directive } from './general/index.js';
|
import type { Directive } from './general/index.js';
|
||||||
import { detectLocalAgents, pingLocalAgent, runLocalAgent } from './agent/local-agents.js';
|
import { detectLocalAgents, pingLocalAgent, runLocalAgent, syncLocalAgentSelection } from './agent/local-agents.js';
|
||||||
import { FRONTIER_ARSENAL_MILESTONE, NETWORK_COMMANDS, SAFE_COMMANDS, TOOL_ADAPTERS, adapterForBinary, adaptersForFamily, summarizeToolCatalog } from './arsenal/catalog.js';
|
import { FRONTIER_ARSENAL_MILESTONE, NETWORK_COMMANDS, SAFE_COMMANDS, TOOL_ADAPTERS, adapterForBinary, adaptersForFamily, summarizeToolCatalog } from './arsenal/catalog.js';
|
||||||
import { AGENT_PROMPT_PACKS, FOREFRONT_PRESSURE_LANES, OPERATOR_RUNBOOKS, RESOURCE_PACKS, WORKFLOW_PRESETS, forefrontPressureForFamily, promptPacksForFamily, resourcesForFamily, runbookForFamily, searchResources, workflowPresetsForFamily } from './resources/index.js';
|
import { AGENT_PROMPT_PACKS, FOREFRONT_PRESSURE_LANES, OPERATOR_RUNBOOKS, RESOURCE_PACKS, WORKFLOW_PRESETS, forefrontPressureForFamily, promptPacksForFamily, resourcesForFamily, runbookForFamily, searchResources, workflowPresetsForFamily } from './resources/index.js';
|
||||||
import { AI_REDTEAM_PLAYBOOK, AI_REDTEAM_TECHNIQUE_IDS, aiRedTeamBriefing } from './resources/ai-redteam-playbook.js';
|
import { AI_REDTEAM_PLAYBOOK, AI_REDTEAM_TECHNIQUE_IDS, aiRedTeamBriefing } from './resources/ai-redteam-playbook.js';
|
||||||
@@ -7363,12 +7363,13 @@ app.get('/api/agents/local/detect', async (_req: Request, res: Response): Promis
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /api/agents/local/connect { ids:[...] | id, ping?:bool } — enlist one or many; optional round-trip
|
// POST /api/agents/local/connect { ids:[...] | id, ping?:bool, replace?:bool } — enlist one or many; optional round-trip
|
||||||
app.post('/api/agents/local/connect', async (req: Request, res: Response): Promise<void> => {
|
app.post('/api/agents/local/connect', async (req: Request, res: Response): Promise<void> => {
|
||||||
const body = req.body || {};
|
const body = req.body || {};
|
||||||
const ids: string[] = Array.isArray(body.ids) ? body.ids : (body.id ? [body.id] : []);
|
const ids: string[] = Array.isArray(body.ids) ? body.ids : (body.id ? [body.id] : []);
|
||||||
const doPing: boolean = body.ping === true;
|
const doPing: boolean = body.ping === true;
|
||||||
if (!ids.length) { res.status(400).json({ error: 'provide ids:[] or id' }); return; }
|
const replace: boolean = body.replace === true;
|
||||||
|
if (!ids.length && !replace) { res.status(400).json({ error: 'provide ids:[] or id' }); return; }
|
||||||
const detected = await detectLocalAgents();
|
const detected = await detectLocalAgents();
|
||||||
const results: Array<Record<string, unknown>> = [];
|
const results: Array<Record<string, unknown>> = [];
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
@@ -7380,6 +7381,7 @@ app.post('/api/agents/local/connect', async (req: Request, res: Response): Promi
|
|||||||
connectedLocalAgents.set(id, { id, label: d.label, version: d.version, connectedAt: Date.now(), lastPing: ping });
|
connectedLocalAgents.set(id, { id, label: d.label, version: d.version, connectedAt: Date.now(), lastPing: ping });
|
||||||
results.push({ id, status: 'active', label: d.label, version: d.version, authMethod: d.authMethod, ping });
|
results.push({ id, status: 'active', label: d.label, version: d.version, authMethod: d.authMethod, ping });
|
||||||
}
|
}
|
||||||
|
syncLocalAgentSelection(connectedLocalAgents, ids, replace);
|
||||||
res.json({ results, connected: Array.from(connectedLocalAgents.values()) });
|
res.json({ results, connected: Array.from(connectedLocalAgents.values()) });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user