chore: bootstrap hermes-ui skeleton from 0.6.29 snapshot
Build / build (push) Canceled after 0s
NPM Lockfile Check / npm ci --ignore-scripts (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

Import the client, server, skills, tests, and build scripts as the starting
point for the rebuild, then trim the surfaces this project does not ship:
Electron desktop packaging, the marketing website, and the ESP32 firmware
tree. CI keeps the web build, e2e, lockfile, release, and Docker workflows.

Rename the package and CLI entry points to hermes-ui, drop the prepare hook so
npm ci no longer triggers a build, and drop the removed tsconfig and nodemon
references. Rewrite AGENTS.md, ARCHITECTURE.md, DEVELOPMENT.md, and the READMEs
so the documented package map and hard rules match this repository.

reference_code/ is ignored: it is a read-only snapshot, not an input to the build.
This commit is contained in:
2026-10-16 10:34:35 +08:00
commit 1e9ea89d86
912 changed files with 287328 additions and 0 deletions
@@ -0,0 +1,320 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const testState = vi.hoisted(() => {
class TestEmitter {
private readonly handlers = new Map<string, Array<(...args: any[]) => void>>()
on(event: string, handler: (...args: any[]) => void) {
const handlers = this.handlers.get(event) || []
handlers.push(handler)
this.handlers.set(event, handlers)
return this
}
emit(event: string, ...args: any[]) {
for (const handler of this.handlers.get(event) || []) handler(...args)
return true
}
}
return {
spawnCalls: [] as Array<{ command: string; args: string[]; options: any; child: any }>,
TestEmitter,
}
})
vi.mock('child_process', () => ({
spawn: vi.fn((command: string, args: string[], options: any) => {
const child = new testState.TestEmitter() as any
child.stdout = new testState.TestEmitter()
child.stderr = new testState.TestEmitter()
child.pid = 1234
child.exitCode = null
child.signalCode = null
child.killed = false
child.kill = vi.fn(() => {
child.killed = true
})
testState.spawnCalls.push({ command, args, options, child })
return child
}),
}))
import { CodingAgentRunManager } from '../../packages/server/src/services/agent-runner/coding-agent-run-manager'
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
function setPlatform(platform: NodeJS.Platform) {
Object.defineProperty(process, 'platform', { value: platform })
}
beforeEach(() => {
testState.spawnCalls.length = 0
setPlatform('win32')
})
afterEach(() => {
if (originalPlatform) Object.defineProperty(process, 'platform', originalPlatform)
})
describe('coding agent Windows process launch', () => {
it('runs npm .cmd shims through cmd.exe for hidden Claude Code chat turns', () => {
const manager = new CodingAgentRunManager()
;(manager as any).ensureDbSession = () => {}
;(manager as any).addUserMessage = () => {}
;(manager as any).emitToChat = () => {}
;(manager as any).markChatRunCompleted = () => {}
manager.start({
agentSessionId: 'agent-session-1',
agentId: 'claude-code',
mode: 'scoped',
profile: 'default',
provider: 'test-provider',
model: 'claude-test',
sessionId: 'chat-session-1',
command: 'C:\\Users\\Administrator\\AppData\\Roaming\\npm\\claude.cmd',
args: ['--settings', 'C:\\Users\\Administrator\\.hermes-web-ui\\settings.json'],
shellCommand: 'claude',
workspaceDir: process.cwd(),
state: { messages: [], isWorking: false, events: [], queue: [] },
})
manager.send('chat-session-1', 'test', { systemPrompt: 'system prompt\nsecond line' })
expect(testState.spawnCalls[0]).toMatchObject({
command: 'cmd.exe',
args: expect.arrayContaining(['/d', '/s', '/c']),
})
expect(testState.spawnCalls[0].args[3]).toContain('C:\\Users\\Administrator\\AppData\\Roaming\\npm\\claude.cmd')
expect(testState.spawnCalls[0].args[3]).toContain('^"--settings^"')
expect(testState.spawnCalls[0].args[3]).toContain('^"--append-system-prompt^"')
expect(testState.spawnCalls[0].args[3]).toContain('^"system^ prompt^ /^ second^ line^"')
expect(testState.spawnCalls[0].args[3]).not.toContain('\n')
expect(testState.spawnCalls[0].args[3]).not.toContain('\r')
expect(testState.spawnCalls[0].args[3]).toContain('^"test^"')
expect(testState.spawnCalls[0].options).toMatchObject({
stdio: ['ignore', 'pipe', 'pipe'],
windowsVerbatimArguments: true,
windowsHide: true,
})
const run = (manager as any).runs.get('agent-session-1')
if (run?.idleTimer) clearTimeout(run.idleTimer)
;(manager as any).runs.clear()
;(manager as any).sessionIndex.clear()
})
it('runs npm .cmd shims through cmd.exe for hidden Codex chat turns', () => {
const manager = new CodingAgentRunManager()
;(manager as any).ensureDbSession = () => {}
;(manager as any).addUserMessage = () => {}
;(manager as any).emitToChat = () => {}
;(manager as any).markChatRunCompleted = () => {}
manager.start({
agentSessionId: 'agent-session-codex-1',
agentId: 'codex',
mode: 'scoped',
profile: 'default',
provider: 'test-provider',
model: 'gpt-test',
sessionId: 'chat-session-codex-1',
command: 'C:\\Users\\Administrator\\AppData\\Roaming\\npm\\codex.cmd',
args: ['--model', 'gpt-test'],
shellCommand: 'codex',
workspaceDir: process.cwd(),
state: { messages: [], isWorking: false, events: [], queue: [] },
})
manager.send('chat-session-codex-1', 'test', { systemPrompt: 'system prompt\nsecond line' })
expect(testState.spawnCalls[0]).toMatchObject({
command: 'cmd.exe',
args: expect.arrayContaining(['/d', '/s', '/c']),
})
expect(testState.spawnCalls[0].args[3]).toContain('C:\\Users\\Administrator\\AppData\\Roaming\\npm\\codex.cmd')
expect(testState.spawnCalls[0].args[3]).toContain('^"exec^"')
expect(testState.spawnCalls[0].args[3]).toContain('^"-c^"')
expect(testState.spawnCalls[0].args[3]).toContain('model_reasoning_summary=\\^"auto\\^"')
expect(testState.spawnCalls[0].args[3]).not.toContain('developer_instructions=')
expect(testState.spawnCalls[0].args[3]).not.toContain('system^ prompt^ /^ second^ line')
expect(testState.spawnCalls[0].args[3]).not.toContain('\n')
expect(testState.spawnCalls[0].args[3]).not.toContain('\r')
expect(testState.spawnCalls[0].args[3]).toContain('^"--model^"')
expect(testState.spawnCalls[0].args[3]).toContain('^"test^"')
expect(testState.spawnCalls[0].args[3]).not.toContain('system^ prompt\r\n\r\ntest')
expect(testState.spawnCalls[0].options).toMatchObject({
stdio: ['ignore', 'pipe', 'pipe'],
windowsVerbatimArguments: true,
windowsHide: true,
})
const run = (manager as any).runs.get('agent-session-codex-1')
if (run?.idleTimer) clearTimeout(run.idleTimer)
;(manager as any).runs.clear()
;(manager as any).sessionIndex.clear()
})
it('preserves non-ASCII Windows .cmd paths when launching hidden chat turns', () => {
const manager = new CodingAgentRunManager()
;(manager as any).ensureDbSession = () => {}
;(manager as any).addUserMessage = () => {}
;(manager as any).emitToChat = () => {}
;(manager as any).markChatRunCompleted = () => {}
manager.start({
agentSessionId: 'agent-session-codex-unicode-1',
agentId: 'codex',
mode: 'scoped',
profile: 'default',
provider: 'test-provider',
model: 'gpt-test',
sessionId: 'chat-session-codex-unicode-1',
command: 'C:\\用户\\管理员\\AppData\\Roaming\\npm\\codex.cmd',
args: ['--model', 'gpt-test'],
shellCommand: 'codex',
workspaceDir: process.cwd(),
state: { messages: [], isWorking: false, events: [], queue: [] },
})
manager.send('chat-session-codex-unicode-1', 'test')
expect(testState.spawnCalls[0]).toMatchObject({
command: 'cmd.exe',
args: expect.arrayContaining(['/d', '/s', '/c']),
})
expect(testState.spawnCalls[0].args[3]).toContain('C:\\用户\\管理员\\AppData\\Roaming\\npm\\codex.cmd')
const run = (manager as any).runs.get('agent-session-codex-unicode-1')
if (run?.idleTimer) clearTimeout(run.idleTimer)
;(manager as any).runs.clear()
;(manager as any).sessionIndex.clear()
})
it('normalizes already quoted Windows .cmd paths before launching hidden chat turns', () => {
const manager = new CodingAgentRunManager()
;(manager as any).ensureDbSession = () => {}
;(manager as any).addUserMessage = () => {}
;(manager as any).emitToChat = () => {}
;(manager as any).markChatRunCompleted = () => {}
manager.start({
agentSessionId: 'agent-session-codex-quoted-1',
agentId: 'codex',
mode: 'scoped',
profile: 'default',
provider: 'test-provider',
model: 'gpt-test',
sessionId: 'chat-session-codex-quoted-1',
command: '"C:\\nvm4w\\nodejs\\codex.cmd"',
args: ['--model', 'gpt-test'],
shellCommand: 'codex',
workspaceDir: process.cwd(),
state: { messages: [], isWorking: false, events: [], queue: [] },
})
manager.send('chat-session-codex-quoted-1', 'test')
expect(testState.spawnCalls[0]).toMatchObject({
command: 'cmd.exe',
args: expect.arrayContaining(['/d', '/s', '/c']),
})
expect(testState.spawnCalls[0].args[3]).toContain('C:\\nvm4w\\nodejs\\codex.cmd')
expect(testState.spawnCalls[0].args[3]).not.toContain('"C:\\nvm4w\\nodejs\\codex.cmd"')
const run = (manager as any).runs.get('agent-session-codex-quoted-1')
if (run?.idleTimer) clearTimeout(run.idleTimer)
;(manager as any).runs.clear()
;(manager as any).sessionIndex.clear()
})
it('emits a readable failed run when a hidden Claude Code process cannot start', async () => {
const manager = new CodingAgentRunManager()
const emitted: Array<{ event: string; payload: any }> = []
;(manager as any).ensureDbSession = () => {}
;(manager as any).addUserMessage = () => {}
;(manager as any).markChatRunCompleted = (_sessionId: string, event: string) => {
emitted.push({ event: 'marked', payload: { event } })
}
;(manager as any).emitToChat = (_sessionId: string, event: string, payload: any) => {
emitted.push({ event, payload })
}
manager.start({
agentSessionId: 'agent-session-error-1',
agentId: 'claude-code',
mode: 'scoped',
profile: 'default',
provider: 'test-provider',
model: 'claude-test',
sessionId: 'chat-session-error-1',
command: 'claude',
args: [],
shellCommand: 'claude',
workspaceDir: process.cwd(),
state: { messages: [], isWorking: false, events: [], queue: [] },
})
manager.send('chat-session-error-1', 'test')
testState.spawnCalls[0].child.emit('error', Object.assign(new Error('spawn claude ENOENT'), { code: 'ENOENT' }))
await new Promise(resolve => setTimeout(resolve, 0))
expect(emitted).toContainEqual(expect.objectContaining({
event: 'run.failed',
payload: expect.objectContaining({
error: 'spawn claude ENOENT',
}),
}))
const run = (manager as any).runs.get('agent-session-error-1')
if (run?.idleTimer) clearTimeout(run.idleTimer)
;(manager as any).runs.clear()
;(manager as any).sessionIndex.clear()
})
it('includes decoded stderr detail when a hidden Codex process exits non-zero', async () => {
const manager = new CodingAgentRunManager()
const emitted: Array<{ event: string; payload: any }> = []
;(manager as any).ensureDbSession = () => {}
;(manager as any).addUserMessage = () => {}
;(manager as any).markChatRunCompleted = (_sessionId: string, event: string) => {
emitted.push({ event: 'marked', payload: { event } })
}
;(manager as any).emitToChat = (_sessionId: string, event: string, payload: any) => {
emitted.push({ event, payload })
}
manager.start({
agentSessionId: 'agent-session-codex-error-1',
agentId: 'codex',
mode: 'scoped',
profile: 'default',
provider: 'test-provider',
model: 'gpt-test',
sessionId: 'chat-session-codex-error-1',
command: 'C:\\Users\\Administrator\\AppData\\Roaming\\npm\\codex.cmd',
args: ['--model', 'gpt-test'],
shellCommand: 'codex',
workspaceDir: process.cwd(),
state: { messages: [], isWorking: false, events: [], queue: [] },
})
manager.send('chat-session-codex-error-1', 'test')
testState.spawnCalls[0].child.stderr.emit('data', Buffer.from([0xb2, 0xbb, 0xca, 0xc7]))
testState.spawnCalls[0].child.emit('exit', 1)
await new Promise(resolve => setTimeout(resolve, 0))
expect(emitted).toContainEqual(expect.objectContaining({
event: 'run.failed',
payload: expect.objectContaining({
error: 'Codex exited with code 1: 不是',
}),
}))
const run = (manager as any).runs.get('agent-session-codex-error-1')
if (run?.idleTimer) clearTimeout(run.idleTimer)
;(manager as any).runs.clear()
;(manager as any).sessionIndex.clear()
})
})