import test from 'node:test'; import assert from 'node:assert/strict'; import { extensionConfigsMatch, extensionsNeedingRefresh, reconcileAgentSession, } from './session-reconcile.mjs'; function harnessMemoryResponse(pathname) { if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') { return { ok: true, text: async () => JSON.stringify({ remembered: true, ok: true }), }; } return null; } test('extensionConfigsMatch compares available_tools', () => { const sessionExt = { name: 'developer', available_tools: ['write', 'edit'] }; const desired = { name: 'developer', available_tools: ['write', 'edit', 'shell', 'tree'] }; assert.equal(extensionConfigsMatch(sessionExt, desired), false); assert.equal( extensionConfigsMatch( { name: 'developer', available_tools: ['shell', 'tree', 'write', 'edit'] }, desired, ), true, ); }); test('extensionsNeedingRefresh upgrades stale developer tools', () => { const current = [ { name: 'developer', available_tools: ['write', 'edit'] }, { name: 'skills', available_tools: [] }, ]; const desired = [ { name: 'developer', available_tools: ['write', 'edit', 'shell', 'tree'] }, { name: 'skills', available_tools: [] }, ]; const { toRemove, toAdd } = extensionsNeedingRefresh(current, desired); assert.deepEqual(toRemove, ['developer']); assert.equal(toAdd.length, 1); assert.equal(toAdd[0].name, 'developer'); assert.deepEqual(toAdd[0].available_tools, ['write', 'edit', 'shell', 'tree']); }); test('extensionsNeedingRefresh adds missing extensions', () => { const { toRemove, toAdd } = extensionsNeedingRefresh( [{ name: 'developer', available_tools: ['write', 'edit'] }], [ { name: 'developer', available_tools: ['write', 'edit'] }, { name: 'summon', available_tools: ['load_skill'] }, ], ); assert.deepEqual(toRemove, []); assert.equal(toAdd.length, 1); assert.equal(toAdd[0].name, 'summon'); }); test('reconcileAgentSession tolerates invalid working directory during resume sync without restart', async () => { const calls = []; const apiFetch = async (pathname) => { calls.push(pathname); if (pathname === '/sessions/session-1') { return { ok: true, text: async () => JSON.stringify({ working_dir: '/stale/workspace' }), }; } if (pathname === '/agent/update_working_dir') { return { ok: false, status: 400, text: async () => 'Invalid directory path', }; } if (pathname === '/sessions/session-1/extensions') { return { ok: true, text: async () => JSON.stringify({ extensions: [] }), }; } if (pathname === '/agent/restart') { return { ok: true, text: async () => JSON.stringify({ ok: true }), }; } const harness = harnessMemoryResponse(pathname); if (harness) return harness; throw new Error(`unexpected path: ${pathname}`); }; await reconcileAgentSession(apiFetch, 'session-1', { workingDir: '/valid/workspace', sessionPolicy: { extensionOverrides: [] }, tolerateInvalidWorkingDir: true, }); assert.deepEqual(calls, [ '/sessions/session-1', '/agent/update_working_dir', '/sessions/session-1/extensions', '/agent/harness_remember', '/agent/harness_bootstrap', ]); }); test('reconcileAgentSession skips restart when extensions already match', async () => { const calls = []; const apiFetch = async (pathname) => { calls.push(pathname); if (pathname === '/sessions/session-1') { return { ok: true, text: async () => JSON.stringify({ working_dir: '/valid/workspace' }), }; } if (pathname === '/sessions/session-1/extensions') { return { ok: true, text: async () => JSON.stringify({ extensions: [{ name: 'skills', available_tools: [] }], }), }; } if (pathname === '/agent/restart') { return { ok: true, text: async () => JSON.stringify({ ok: true }), }; } const harness = harnessMemoryResponse(pathname); if (harness) return harness; throw new Error(`unexpected path: ${pathname}`); }; await reconcileAgentSession(apiFetch, 'session-1', { workingDir: '/valid/workspace', sessionPolicy: { extensionOverrides: [{ name: 'skills', available_tools: [] }] }, }); assert.deepEqual(calls, [ '/sessions/session-1', '/sessions/session-1/extensions', '/agent/harness_remember', '/agent/harness_bootstrap', ]); }); test('reconcileAgentSession restarts after adding missing extensions', async () => { const calls = []; const apiFetch = async (pathname) => { calls.push(pathname); if (pathname === '/sessions/session-1') { return { ok: true, text: async () => JSON.stringify({ working_dir: '/valid/workspace' }), }; } if (pathname === '/sessions/session-1/extensions') { return { ok: true, text: async () => JSON.stringify({ extensions: [] }), }; } if (pathname === '/agent/add_extension' || pathname === '/agent/restart') { return { ok: true, text: async () => JSON.stringify({ ok: true }), }; } const harness = harnessMemoryResponse(pathname); if (harness) return harness; throw new Error(`unexpected path: ${pathname}`); }; await reconcileAgentSession(apiFetch, 'session-1', { workingDir: '/valid/workspace', sessionPolicy: { extensionOverrides: [{ name: 'skills', available_tools: [] }] }, }); assert.deepEqual(calls, [ '/sessions/session-1', '/sessions/session-1/extensions', '/agent/add_extension', '/agent/restart', '/agent/harness_remember', '/agent/harness_bootstrap', ]); });