feat: improve agent responses and workspace UI

Add live agent progress, elapsed timing, verified code navigation and rich Markdown rendering. Preserve NanumSquare styling with missing glyph fallback, fix platform availability filtering and unify workspace scrollbar and visualization controls.
This commit is contained in:
2026-10-19 17:06:18 +09:00
parent af21fa0510
commit 8b501266ff
33 changed files with 2231 additions and 98 deletions
+86
View File
@@ -0,0 +1,86 @@
const { test } = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const vm = require('node:vm')
const ts = require('typescript')
const source = ts.createSourceFile('App.tsx', fs.readFileSync(require.resolve('../src/renderer/src/App.tsx'), 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
const names = ['isRemotePlatformStatusPending', 'filteredPlatformOptions', 'platformFilterCounts']
const declarations = new Map()
function visit(node) {
if (ts.isVariableDeclaration(node) && names.includes(node.name.getText(source))) {
declarations.set(node.name.getText(source), node.getText(source))
}
ts.forEachChild(node, visit)
}
visit(source)
for (const name of names) assert.ok(declarations.has(name), `missing ${name}`)
const filterScript = ts.transpileModule(`${names.map(name => `const ${declarations.get(name)}`).join('\n')}
result = { counts: platformFilterCounts, ids: filteredPlatformOptions.map(platform => platform.id) }`, {
compilerOptions: { target: ts.ScriptTarget.ES2022 }
}).outputText
const controlSource = fs.readFileSync(require.resolve('../src/renderer/src/features/platforms/platformControl.ts'), 'utf8')
const control = {}
vm.runInNewContext(ts.transpileModule(controlSource, { compilerOptions: { module: ts.ModuleKind.CommonJS } }).outputText, {
exports: control,
require: name => {
assert.equal(name, '../../config/appConfig')
return { LMS_API_BASE_URL: 'http://unused.invalid' }
}
})
function classify(platformOptions, checkedIds = ['mini'], platformFilter = 'all') {
const context = {
platformOptions, platformFilter,
checkedRemotePlatformStatusIds: new Set(checkedIds),
isPlatformUnavailable: control.isPlatformUnavailable
}
vm.runInNewContext(filterScript, context)
return JSON.parse(JSON.stringify(context.result))
}
const simulation = { id: 'sim', platformKey: 'futuredrive-local-pc', state: 'ready', status: '사용 가능', action: '시작' }
const mini = { id: 'mini', platformKey: 'mkmini', state: 'ready', status: '사용 가능', action: '제어권 요청', connection: { headscaleOnline: true, agentHealthy: true } }
test('disconnected ready platform counts as unavailable and appears only in that tab', () => {
const disconnected = { ...mini, status: '연결 끊김', connection: { headscaleOnline: false, agentHealthy: false } }
const platforms = [simulation, disconnected]
assert.deepEqual(classify(platforms), { counts: { all: 2, available: 1, unavailable: 1 }, ids: ['sim', 'mini'] })
assert.deepEqual(classify(platforms, ['mini'], 'available').ids, ['sim'])
assert.deepEqual(classify(platforms, ['mini'], 'unavailable').ids, ['mini'])
})
test('offline network, unhealthy agent and explicit unavailability all exclude ready platforms', () => {
for (const patch of [
{ connection: { headscaleOnline: false, agentHealthy: true } },
{ connection: { headscaleOnline: true, agentHealthy: false } },
{ connection: undefined, status: '연결 끊김' },
{ connection: undefined, status: '연결 불가' },
{ connection: undefined, action: '연결 불가' }
]) {
const platforms = [simulation, { ...mini, ...patch }]
assert.deepEqual(classify(platforms).counts, { all: 2, available: 1, unavailable: 1 })
assert.deepEqual(classify(platforms, ['mini'], 'unavailable').ids, ['mini'])
}
})
test('pending remote status remains in All without prematurely entering either availability tab', () => {
const platforms = [simulation, mini]
assert.deepEqual(classify(platforms, []).counts, { all: 2, available: 1, unavailable: 0 })
assert.deepEqual(classify(platforms, [], 'available').ids, ['sim'])
assert.deepEqual(classify(platforms, [], 'unavailable').ids, [])
})
test('reconnecting moves a platform back to available without changing All', () => {
assert.deepEqual(classify([simulation, mini]).counts, { all: 2, available: 2, unavailable: 0 })
assert.deepEqual(classify([simulation, mini], ['mini'], 'available').ids, ['sim', 'mini'])
assert.deepEqual(classify([simulation, mini], ['mini'], 'unavailable').ids, [])
})
test('locked, requested and reserved platform classifications are preserved', () => {
for (const state of ['locked', 'request', 'reserved']) {
const platforms = [simulation, { ...mini, state }]
assert.deepEqual(classify(platforms).counts, { all: 2, available: 1, unavailable: 1 })
assert.deepEqual(classify(platforms, ['mini'], 'unavailable').ids, ['mini'])
}
})