Remove the API relay link and the ESP32 device feature

Two pieces of upstream promotion came out of this deployment.

The sidebar "apiRelay" tab only opened a registration page on a third-party
site, so it goes with no replacement.

The circuit badge next to Settings was not what it looked like. It opened an
ESP32 device manager: add a device, show LAN status, remote connect/disconnect,
rename, delete, backed by six REST routes and an mcu_devices table. The purchase
button inside that dialog was the promotional part; the rest was real device
management. Since the hardware is out of scope here, the entire feature is
removed rather than just the link that pointed upstream - UI entry, client API,
routes, controller, store, schema, locale strings, and the one GlobalAgentServer
method that only existed to answer its status column.

Table creation is dropped but no DROP TABLE was added, so an already-provisioned
database keeps its rows as harmless orphans instead of losing data on upgrade.

DevicesView and the hermes.devices route are untouched: that is LAN device
discovery, a separate feature.

AGENTS.md now records the removal so the badge does not come back.
This commit is contained in:
2026-10-19 01:32:51 +08:00
parent 3c963bb1d9
commit 77b1858163
22 changed files with 49 additions and 1405 deletions
+1 -9
View File
@@ -21,7 +21,7 @@ describe('Hermes schema initialization', () => {
})
it('initializes all tables with correct schemas', async () => {
const { initAllHermesTables, USAGE_TABLE, SESSIONS_TABLE, MESSAGES_TABLE, GC_ROOMS_TABLE, USERS_TABLE, USER_PROFILES_TABLE, DEVICES_TABLE, MCU_DEVICES_TABLE } =
const { initAllHermesTables, USAGE_TABLE, SESSIONS_TABLE, MESSAGES_TABLE, GC_ROOMS_TABLE, USERS_TABLE, USER_PROFILES_TABLE, DEVICES_TABLE } =
await import('../../packages/server/src/db/hermes/schemas')
expect(() => initAllHermesTables()).not.toThrow()
@@ -35,7 +35,6 @@ describe('Hermes schema initialization', () => {
expect(tables.map(t => t.name)).toContain(USERS_TABLE)
expect(tables.map(t => t.name)).toContain(USER_PROFILES_TABLE)
expect(tables.map(t => t.name)).toContain(DEVICES_TABLE)
expect(tables.map(t => t.name)).toContain(MCU_DEVICES_TABLE)
// Verify USAGE_TABLE structure
const usageCols = db.prepare(`PRAGMA table_info("${USAGE_TABLE}")`).all() as Array<{ name: string }>
@@ -63,13 +62,6 @@ describe('Hermes schema initialization', () => {
expect(deviceCols.some(c => c.name === 'id')).toBe(true)
expect(deviceCols.some(c => c.name === 'status')).toBe(true)
expect(deviceCols.some(c => c.name === 'device_public_key')).toBe(true)
const mcuDeviceCols = db.prepare(`PRAGMA table_info("${MCU_DEVICES_TABLE}")`).all() as Array<{ name: string }>
expect(mcuDeviceCols.some(c => c.name === 'id')).toBe(true)
expect(mcuDeviceCols.some(c => c.name === 'name')).toBe(true)
expect(mcuDeviceCols.some(c => c.name === 'device_code')).toBe(true)
expect(mcuDeviceCols.some(c => c.name === 'is_official')).toBe(true)
expect(mcuDeviceCols.some(c => c.name === 'created_at')).toBe(true)
})
it('preserves existing data when adding safe schema columns', async () => {
-88
View File
@@ -1,88 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
describe('MCU devices store', () => {
let db: any = null
beforeEach(async () => {
vi.resetModules()
const { DatabaseSync } = await import('node:sqlite')
db = new DatabaseSync(':memory:')
vi.doMock('../../packages/server/src/db/index', () => ({
getDb: () => db,
getStoragePath: () => ':memory:',
}))
const { initAllHermesTables } = await import('../../packages/server/src/db/hermes/schemas')
initAllHermesTables()
})
afterEach(() => {
db?.close()
db = null
vi.doUnmock('../../packages/server/src/db/index')
vi.resetModules()
})
it('stores official and unofficial MCU devices', async () => {
const { createMcuDevice, listMcuDevices } = await import('../../packages/server/src/db/hermes/mcu-devices-store')
const official = createMcuDevice({
name: 'Official Box',
deviceCode: 'official-code',
isOfficial: true,
})
const unofficial = createMcuDevice({
name: 'Unofficial Box',
deviceCode: 'unofficial-code',
isOfficial: false,
})
expect(official.is_official).toBe(true)
expect(unofficial.is_official).toBe(false)
expect(listMcuDevices().map(device => device.device_code).sort()).toEqual(['official-code', 'unofficial-code'])
})
it('rejects duplicate device codes', async () => {
const { createMcuDevice } = await import('../../packages/server/src/db/hermes/mcu-devices-store')
createMcuDevice({
name: 'Box',
deviceCode: 'duplicate-code',
isOfficial: true,
})
expect(() => createMcuDevice({
name: 'Box 2',
deviceCode: 'duplicate-code',
isOfficial: false,
})).toThrow('mcu_device_exists')
})
it('updates device name only', async () => {
const { createMcuDevice, updateMcuDeviceName } = await import('../../packages/server/src/db/hermes/mcu-devices-store')
const created = createMcuDevice({
name: 'Old Name',
deviceCode: 'editable-code',
isOfficial: true,
})
const updated = updateMcuDeviceName(created.id, 'New Name')
expect(updated.name).toBe('New Name')
expect(updated.device_code).toBe('editable-code')
expect(updated.is_official).toBe(true)
})
it('deletes MCU devices', async () => {
const { createMcuDevice, deleteMcuDevice, listMcuDevices } = await import('../../packages/server/src/db/hermes/mcu-devices-store')
const created = createMcuDevice({
name: 'Delete Me',
deviceCode: 'delete-code',
isOfficial: false,
})
expect(deleteMcuDevice(created.id)).toBe(true)
expect(deleteMcuDevice(created.id)).toBe(false)
expect(listMcuDevices()).toEqual([])
})
})
-6
View File
@@ -109,12 +109,6 @@ const ALLOWED_LEGACY_BRAND: AllowEntry[] = [
pattern: /href="https:\/\/hermes-studio\.ai\/"/,
reason: 'sidebar footer links to the upstream product website',
},
{
// 原因:购买徽章指向托管 ESP32 活动的上游文档站
file: 'packages/client/src/components/layout/SettingsCircuitBadge.vue',
pattern: /const purchaseUrl = 'https:\/\/hermes-studio\.ai\/docs\//,
reason: 'purchase badge links to the upstream docs site hosting the ESP32 offer',
},
{
// 原因:断言旧 MCP 条目会从存量磁盘配置里被清掉
file: 'tests/server/studio-mcp-autoinject.test.ts',
-18
View File
@@ -119,8 +119,6 @@ describe('Database Schema Synchronization', () => {
WORKFLOW_RUNS_SCHEMA,
WORKFLOW_RUN_NODE_SESSIONS_TABLE,
WORKFLOW_RUN_NODE_SESSIONS_SCHEMA,
MCU_DEVICES_TABLE,
MCU_DEVICES_SCHEMA,
} =
await import('../../packages/server/src/db/hermes/schemas')
@@ -183,22 +181,6 @@ describe('Database Schema Synchronization', () => {
expect(workflowRunNodeSessionCols.has('session_id')).toBe(true)
expect(workflowRunNodeSessionCols.has('status')).toBe(true)
expect(workflowRunNodeSessionCols.has('agent')).toBe(true)
expect(tableExists(db, MCU_DEVICES_TABLE)).toBe(true)
const mcuDeviceCols = getTableColumns(db, MCU_DEVICES_TABLE)
expect(mcuDeviceCols.size).toBe(Object.keys(MCU_DEVICES_SCHEMA).length)
expect(mcuDeviceCols.has('id')).toBe(true)
expect(mcuDeviceCols.has('name')).toBe(true)
expect(mcuDeviceCols.has('device_code')).toBe(true)
expect(mcuDeviceCols.has('is_official')).toBe(true)
expect(mcuDeviceCols.has('created_at')).toBe(true)
db.prepare(`INSERT INTO "${MCU_DEVICES_TABLE}" (name, device_code, is_official) VALUES (?, ?, ?)`)
.run('MCU 1', 'device-code-1', 1)
expect(() => {
db.prepare(`INSERT INTO "${MCU_DEVICES_TABLE}" (name, device_code, is_official) VALUES (?, ?, ?)`)
.run('MCU Duplicate', 'device-code-1', 0)
}).toThrow()
})
})