const { test } = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const ts = require('typescript') const vm = require('node:vm') const app = ts.createSourceFile('App.tsx', fs.readFileSync(require.resolve('../src/renderer/src/App.tsx'), 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX) const declarations = new Map() function visit(node) { if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) declarations.set(node.name.text, node.initializer?.getText(app)) ts.forEachChild(node, visit) } visit(app) const plain = value => JSON.parse(JSON.stringify(value)) function harness(remoteWorkspace) { const executions = [] const calls = [] const contexts = [] const events = [] const errors = [] const target = { host: '100.64.0.10', user: 'fixture', workspaceRoot: '/fixture/workspace' } const live = [{ name: 'NAV2-MKMini', folderName: 'vehicle', state: 'running', parameters: ['sim_mode:=false', 'robot:=mkmini'] }] const readTool = backend => async (...args) => { executions.push({ backend, args: plain(args) }); return { status: 'ok', data: '{"content":"remote source"}' } } const exports = {} const globals = { exports, remoteWorkspace, availableModules: [{ ...live[0], state: 'stopped', parameters: ['sim_mode:=true'] }], clonePathPreview: '/local/workspace', runtimeModules: [], agentActionControllerRef: { current: null }, agentApplying: false, getRemoteWorkspaceTarget: () => target, applyModuleParameterOverrides: modules => modules, validateAgentTool: request => request, AGENT_PRACTICE_PROFILES: [], window: { remoteWorkspace: { modules: { list: async () => { events.push('snapshot-read'); return { modules: live } } }, agent: { readTool: readTool('remote') } }, localWorkspace: { modules: { list: async () => { events.push('snapshot-read'); return { modules: live } } }, agent: { readTool: readTool('local') } } }, requestAgentPlan: async (...args) => { calls.push(args) events.push('plan-start') contexts.push(await args[3].loadInitialContext(new AbortController().signal)) }, onTitlebarNotice: (...args) => { errors.push(args) }, console: { error: () => {} }, loadAgentRequestedContext: async () => ({ modules: [], runtimeLogs: [] }) } const names = ['collectAgentWorkspace', 'readAgentModules', 'executeWorkspaceAgentTool', 'submitAgentRequest'] vm.runInNewContext(ts.transpileModule(names.map(name => `const ${name} = ${declarations.get(name)}; exports.${name} = ${name}`).join('\n'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText, globals) return { ...exports, executions, calls, contexts, events, errors } } for (const remote of [false, true]) { test(`${remote ? 'remote' : 'local'}: enters the chat lifecycle immediately and refreshes state before the provider request`, async () => { const h = harness(remote) await h.submitAgentRequest('최대 속도 제한 코드 위치') assert.deepEqual(h.errors, []) assert.equal(h.calls.length, 1) const [, initialModules, , options] = h.calls[0] assert.deepEqual(plain(initialModules), []) assert.deepEqual(h.events, ['plan-start', 'snapshot-read']) const { modules } = h.contexts[0] assert.equal(modules[0].state, 'running') assert.deepEqual(plain(modules[0].parameters), ['sim_mode:=false', 'robot:=mkmini']) assert.deepEqual(plain(modules[0].files), []) assert.equal(options.environment, remote ? 'remote' : 'local') assert.equal(options.workspaceTarget, remote ? 'ssh:100.64.0.10' : 'local-host') }) test(`${remote ? 'remote' : 'local'}: source reads use only the connected workspace transport and module folder`, async () => { const h = harness(remote) const tool = { id: 'read-1', name: 'files.read', moduleName: 'NAV2-MKMini', args: { path: 'src/profiles.py', startLine: 10, lineCount: 20 } } await h.executeWorkspaceAgentTool(tool) assert.equal(h.executions.length, 1) assert.equal(h.executions[0].backend, remote ? 'remote' : 'local') const args = h.executions[0].args assert.deepEqual(args[remote ? 1 : 0], tool) assert.equal(args[2], 'vehicle') assert.equal(remote ? args[0].host : args[1], remote ? '100.64.0.10' : '/local/workspace') }) }