import test from 'node:test'; import assert from 'node:assert/strict'; import { extensionConfigsMatch, extensionPolicyViolations, extensionsNeedingRefresh, parseGooseTimestampMs, reconcileAgentSession, sessionMcpEnvRequiresFreshSession, SESSION_MCP_ENV_STALE, } from './session-reconcile.mjs'; import { mintMindSpaceMcpScopedToken } from './mindspace-mcp-scoped-token.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('extensionConfigsMatch compares stdio runtime roots and ignores env key order', () => { const stale = { type: 'stdio', name: 'sandbox-fs', cmd: '/usr/local/bin/node', args: ['/opt/portal/mindspace-sandbox-mcp.mjs', '/private/tmp/stale/MindSpace/user-1'], envs: { SANDBOX_ROOT: '/private/tmp/stale/MindSpace/user-1', PRIVATE_DATA_USER_ID: 'user-1', }, available_tools: ['write_file', 'private_data_bind_workspace_page'], }; const desired = { type: 'stdio', name: 'sandbox-fs', cmd: '/usr/local/bin/node', args: ['/opt/portal/mindspace-sandbox-mcp.mjs', '/Users/john/Project/Memind/MindSpace/user-1'], envs: { PRIVATE_DATA_USER_ID: 'user-1', SANDBOX_ROOT: '/Users/john/Project/Memind/MindSpace/user-1', }, available_tools: ['private_data_bind_workspace_page', 'write_file'], }; assert.equal(extensionConfigsMatch(stale, desired), false); assert.equal( extensionConfigsMatch( desired, { ...desired, envs: { SANDBOX_ROOT: desired.envs.SANDBOX_ROOT, PRIVATE_DATA_USER_ID: desired.envs.PRIVATE_DATA_USER_ID, }, }, ), true, ); }); test('extensionsNeedingRefresh replaces stdio extension when its sandbox root changed', () => { const current = [{ type: 'stdio', name: 'sandbox-fs', cmd: 'node', args: ['mindspace-sandbox-mcp.mjs', '/stale/user-1'], envs: { SANDBOX_ROOT: '/stale/user-1' }, available_tools: ['write_file'], }]; const desired = [{ type: 'stdio', name: 'sandbox-fs', cmd: 'node', args: ['mindspace-sandbox-mcp.mjs', '/current/user-1'], envs: { SANDBOX_ROOT: '/current/user-1' }, available_tools: ['write_file'], }]; const { toRemove, toAdd } = extensionsNeedingRefresh(current, desired); assert.deepEqual(toRemove, ['sandbox-fs']); assert.deepEqual(toAdd, desired); }); 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('extensionsNeedingRefresh re-adds missing stdio extensions after quiesce', () => { const desiredSandbox = { type: 'stdio', name: 'sandbox-fs', cmd: '/usr/local/bin/node', args: ['/opt/portal/mindspace-sandbox-mcp.mjs', '/tmp/user-1'], available_tools: ['read_file', 'generate_image'], }; const { toRemove, toAdd } = extensionsNeedingRefresh( [{ name: 'developer', available_tools: ['read_image'] }], [ { name: 'developer', available_tools: ['read_image'] }, desiredSandbox, ], ); assert.deepEqual(toRemove, []); assert.equal(toAdd.length, 1); assert.equal(toAdd[0].name, 'sandbox-fs'); }); test('extensionPolicyViolations reports unexpected, duplicate, and mismatched extensions', () => { assert.deepEqual( extensionPolicyViolations( [ { name: 'developer', available_tools: ['write'] }, { name: 'skills', available_tools: [] }, { name: 'skills', available_tools: [] }, ], [ { name: 'skills', available_tools: [] }, { name: 'sandbox-fs', available_tools: ['write_file'] }, ], ), { unexpected: ['developer'], duplicate: ['skills'], missingOrMismatched: ['sandbox-fs', 'skills'], }, ); }); test('extensionPolicyViolations flags stdio extensions missing from goosed listing', () => { assert.deepEqual( extensionPolicyViolations( [{ name: 'developer', available_tools: ['read_image'] }], [ { name: 'developer', available_tools: ['read_image'] }, { type: 'stdio', name: 'sandbox-fs', cmd: '/usr/local/bin/node', args: ['/opt/portal/mindspace-sandbox-mcp.mjs', '/tmp/user-1'], available_tools: ['read_file'], }, { type: 'stdio', name: 'tkmind-search', cmd: '/usr/local/bin/node', args: ['/opt/portal/tkmind-search-mcp.mjs'], available_tools: ['search_external'], }, ], ), { unexpected: [], duplicate: [], missingOrMismatched: ['sandbox-fs', 'tkmind-search'], }, ); }); 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 = []; let extensionReads = 0; 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') { extensionReads += 1; return { ok: true, text: async () => JSON.stringify({ extensions: extensionReads === 1 ? [] : [{ name: 'skills', available_tools: [] }], }), }; } 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', '/sessions/session-1/extensions', '/agent/harness_remember', '/agent/harness_bootstrap', ]); }); test('reconcileAgentSession restarts after re-adding quiesced stdio extensions', async () => { const calls = []; let extensionReads = 0; const desiredSandbox = { type: 'stdio', name: 'sandbox-fs', cmd: '/usr/local/bin/node', args: ['/opt/portal/mindspace-sandbox-mcp.mjs', '/tmp/user-1'], available_tools: ['write_file', 'generate_image'], }; 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') { extensionReads += 1; return { ok: true, text: async () => JSON.stringify({ extensions: extensionReads === 1 ? [{ name: 'developer', available_tools: ['read_image'] }] : [desiredSandbox, { name: 'developer', available_tools: ['read_image'] }], }), }; } 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: 'developer', available_tools: ['read_image'] }, desiredSandbox, ], }, }); assert.deepEqual(calls, [ '/sessions/session-1', '/sessions/session-1/extensions', '/agent/add_extension', '/agent/restart', '/sessions/session-1/extensions', '/agent/harness_remember', '/agent/harness_bootstrap', ]); }); test('reconcileAgentSession fails closed when restart does not apply the requested policy', async () => { const apiFetch = async (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: 'developer', available_tools: ['shell'] }], }), }; } if ( pathname === '/agent/remove_extension' || pathname === '/agent/add_extension' || pathname === '/agent/restart' ) { return { ok: true, text: async () => JSON.stringify({ ok: true }), }; } throw new Error(`unexpected path: ${pathname}`); }; await assert.rejects( reconcileAgentSession(apiFetch, 'session-1', { workingDir: '/valid/workspace', sessionPolicy: { extensionOverrides: [{ name: 'skills', available_tools: [] }] }, }), (err) => err?.code === 'SESSION_EXTENSION_POLICY_MISMATCH' && err?.retryable === false, ); }); test('reconcileAgentSession skips unrestricted sessions without MindSpace extension overrides', async () => { const calls = []; const apiFetch = async (pathname) => { calls.push(pathname); throw new Error(`unexpected path: ${pathname}`); }; await reconcileAgentSession(apiFetch, 'session-1', { sessionPolicy: { unrestricted: true, extensionOverrides: null }, }); assert.deepEqual(calls, []); }); test('reconcileAgentSession applies sandbox-fs for unrestricted MindSpace sessions', async () => { const calls = []; let extensionReads = 0; const desiredSandbox = { type: 'stdio', name: 'sandbox-fs', cmd: '/usr/local/bin/node', args: ['/opt/portal/mindspace-sandbox-mcp.mjs', '/tmp/user-1'], available_tools: ['write_file', 'generate_image'], }; 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') { extensionReads += 1; return { ok: true, text: async () => JSON.stringify({ extensions: extensionReads === 1 ? [] : [desiredSandbox], }), }; } 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: { unrestricted: true, extensionOverrides: [desiredSandbox], }, }); assert.ok(calls.includes('/agent/add_extension')); }); test('parseGooseTimestampMs accepts unix seconds, millis, and ISO strings', () => { assert.equal(parseGooseTimestampMs(1_746_000_000), 1_746_000_000_000); assert.equal(parseGooseTimestampMs(1_746_000_000_000), 1_746_000_000_000); assert.equal(parseGooseTimestampMs('2026-09-11T10:20:00.000Z'), Date.parse('2026-09-11T10:20:00.000Z')); assert.equal(parseGooseTimestampMs(''), null); }); test('sessionMcpEnvRequiresFreshSession rotates after Goose session outlives MCP token TTL', () => { const now = Date.parse('2026-09-11T10:20:00.000Z'); const freshToken = mintMindSpaceMcpScopedToken({ secret: 'mindspace-test-secret-1234', userId: 'user-1', sessionId: 'session-1', packageId: 'cp_session-1', workspaceRef: 'mindspace://users/user-1/workspace', tools: ['write_file'], now, tokenId: 'token-fresh', }); const desired = [{ type: 'stdio', name: 'sandbox-fs', cmd: 'node', args: ['mcp.mjs'], envs: { MINDSPACE_MCP_SCOPED_TOKEN: freshToken }, }]; assert.equal( sessionMcpEnvRequiresFreshSession( { created_at: new Date(now - 3 * 60 * 60 * 1000).toISOString() }, desired, desired, { now }, ), true, ); assert.equal( sessionMcpEnvRequiresFreshSession( { created_at: new Date(now - 10 * 60 * 1000).toISOString() }, desired, desired, { now }, ), false, ); assert.equal( sessionMcpEnvRequiresFreshSession( { created_at: new Date(now - 3 * 60 * 60 * 1000).toISOString() }, [], [{ name: 'skills', available_tools: [] }], { now }, ), false, ); }); test('sessionMcpEnvRequiresFreshSession rotates when listed MCP token is expired', () => { const mintedAt = 1_000_000; const expired = mintMindSpaceMcpScopedToken({ secret: 'mindspace-test-secret-1234', userId: 'user-1', sessionId: 'session-1', packageId: 'cp_session-1', workspaceRef: 'mindspace://users/user-1/workspace', tools: ['write_file'], ttlSeconds: 60, now: mintedAt, tokenId: 'token-1', }); const desired = [{ type: 'stdio', name: 'sandbox-fs', envs: { MINDSPACE_MCP_SCOPED_TOKEN: expired }, }]; assert.equal( sessionMcpEnvRequiresFreshSession( {}, desired, desired, { now: mintedAt + 5 * 60 * 1000 }, ), true, ); }); test('reconcileAgentSession fails closed when MCP token cannot be refreshed', async () => { const createdAt = new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(); const token = mintMindSpaceMcpScopedToken({ secret: 'mindspace-test-secret-1234', userId: 'user-1', sessionId: 'session-1', packageId: 'cp_session-1', workspaceRef: 'mindspace://users/user-1/workspace', tools: ['write_file'], }); const desiredSandbox = { type: 'stdio', name: 'sandbox-fs', cmd: 'node', args: ['mcp.mjs'], envs: { MINDSPACE_MCP_SCOPED_TOKEN: token }, available_tools: ['write_file'], }; const apiFetch = async (pathname) => { if (pathname === '/sessions/session-1') { return { ok: true, text: async () => JSON.stringify({ working_dir: '/valid/workspace', created_at: createdAt, }), }; } if (pathname === '/sessions/session-1/extensions') { return { ok: true, text: async () => JSON.stringify({ extensions: [desiredSandbox] }), }; } throw new Error(`unexpected path: ${pathname}`); }; await assert.rejects( () => reconcileAgentSession(apiFetch, 'session-1', { workingDir: '/valid/workspace', sessionPolicy: { extensionOverrides: [desiredSandbox] }, }), (error) => error?.code === SESSION_MCP_ENV_STALE, ); });