Files
Hermes-ui/packages/server/src/controllers/upload.ts
T
ekkoandClaude Opus 4.7 b4a80aceeb fix: Windows/Termux compatibility, comic theme fonts, and UI fixes (#630)
* fix: comprehensive Windows compatibility and gateway management improvements

This commit addresses multiple Windows compatibility issues and improves
gateway management across all platforms.

## Windows Compatibility Fixes
- Add hermes-path.ts with cross-platform Hermes home/bin detection
- Fix Windows native installation paths (%LOCALAPPDATA%\hermes)
- Update terminal.ts to use PowerShell instead of /bin/bash on Windows
- Fix upload.ts path construction to use path.join() for cross-platform paths
- Fix download.ts to use isAbsolute() for Windows absolute path detection
- Update auth.ts to skip file mode 0o600 on Windows (unsupported)
- Add nodemon.json for cross-platform environment variable handling

## Gateway Management Improvements
- Simplify gateway startup: all platforms use 'run' mode uniformly
- Remove complex init system detection and platform-specific code paths
- Improve PID file validation: use health check instead of port detection
- Remove getPortByPid() method (too complex and error-prone)
- Remove checkPortAvailable() TCP bind test (TIME_WAIT false positives)
- Trust gateway --replace flag to handle real port conflicts
- Add smart PID validation: check if stale process via health check
- Fix port allocation to avoid incrementing when gateway restarts
- Add allocatedPorts.clear() on each startAll() call
- Add clearPidFile() method to clean up stale PID files

## Process Management
- Remove detached:true and unref() from gateway spawn
- Gateway processes now follow parent process lifecycle
- Add process reference storage in ManagedGateway interface
- Improve shutdown logic: call gatewayManager.stopAll() before exit
- Fix Windows process killing: use process.kill(pid) for Windows
- Remove PowerShell command for lock file cleanup (use Node.js fs.unlinkSync)

## Frontend Theme Fixes
- Fix main.ts localStorage key mismatch (hermes_theme → hermes_brightness)
- Add inline script in index.html to prevent FOUC (Flash of Unstyled Content)
- Apply theme classes before Vue mount to avoid visual glitches

## Developer Experience
- Fix nodemon windows-kill popup on Windows by removing signal config
- Add delay and environment variables to nodemon.json
- Add windowsHide: true to all child process spawns

## Breaking Changes
- Gateway management now exclusively uses 'run' mode on all platforms
- systemd/launchd integration removed (use --replace flag instead)

This fix ensures hermes-web-ui works correctly on Windows native
installations while maintaining compatibility with Linux/macOS/WSL2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix gateway lifecycle port handling

* fix: comprehensive Windows compatibility and gateway management improvements

- Simplified hermes CLI binary resolution logic
- Fixed Windows line ending compatibility in profile list parsing
- Migrated gateway restart logic from CLI to GatewayManager
- Added gateway restart to updateCredentials method
- Removed unnecessary gateway restarts from provider operations
- Fixed configuration preservation when switching profiles
- Added nodemon quiet mode and legacy watch to reduce Windows popups

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* revert: change back to nodemon due to tsx compatibility issues

- tsx has compatibility issues with Koa generator functions
- Restored nodemon with simplified configuration
- Added cross-env package for future Windows environment variable needs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace nodemon with ts-node-dev to eliminate Windows popup windows

- Installed ts-node-dev as nodemon replacement
- ts-node-dev has better Windows compatibility without console popups
- Supports respawning, inspector debugging, and TypeScript compilation
- Uses cross-env for Windows environment variable support
- Removed nodemon.json configuration file (no longer needed)

Benefits:
- No more Windows console popup windows during development
- Faster restart times compared to nodemon
- Built-in TypeScript compilation without ts-node overhead

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: improve log parsing and Windows compatibility for agent/error logs

- Fixed Pino JSON log parsing bug where logger field incorrectly used obj.msg
- Changed logger field to use obj.name to properly display log source
- Added Windows line ending support (\r\n) for log file listing
- Added support for 'error' log type in addition to 'errors'
- Improved error message extraction from obj.err when available

This fixes the missing agent and error logs issue on Windows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix gateway health checks and shutdown ownership

* Refine auth lock window and dev shutdown

* fix: improve Hermes plugin discovery on Windows by fixing Python path resolution

- Added support for Windows venv Scripts directory structure
- Fixed Python executable path detection for hermes.exe in venv/Scripts/
- Added Windows LOCALAPPDATA hermes-agent directory to search paths
- Improved cross-platform compatibility for plugin discovery

This fixes the "No module named 'hermes_cli'" error on Windows by correctly
locating the Python virtual environment that contains the Hermes modules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: improve cross-platform compatibility for Hermes plugin discovery

- Added platform detection to only add Windows-specific paths on Windows
- Prevents potential issues on Unix/Linux/macOS systems
- Ensures LOCALAPPDATA path is only used when available on Windows
- Maintains existing behavior for all platforms

This makes the Windows plugin discovery fix safer for cross-platform usage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove unused development dependencies

- Removed nodemon (replaced by ts-node-dev)
- Removed tsx (had compatibility issues with Koa)
- Removed nodemon.json configuration file
- Cleaned up development tools to only what's actually used

This reduces dependency size and eliminates the windows-kill popup
source that was part of nodemon.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove memory system files

- Removed MEMORY.md index file
- Removed memory/ directory and windows-compatibility.md
- Cleaned up unused memory persistence system

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve TypeScript compilation error in plugins.ts

- Added type assertion 'as string[]' after filter(Boolean)
- Fixes TS2769 error: No overload matches this call
- Ensures type compatibility with hasHermesPluginModule function

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: comprehensive Windows compatibility and gateway management improvements

- Fix gateway detection after nodemon restart by adding health check-based detection
- Prevent port conflicts by detecting already-running gateways without PID files
- Switch to serial gateway startup to avoid lock file race conditions
- Return to nodemon from ts-node-dev for development stability
- Always stop gateways on shutdown to prevent orphan processes
- Prevent project root config files from being committed to git
- Fix syntax issues in plugins.ts

Resolves issues where default profile gateway failed to start after
nodemon restart and gateways were incorrectly marked as stopped
despite running on correct ports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: comic theme multilingual fonts, sidebar collapse fix, plugin discovery for Termux, and cron history

- Add Chinese (ZCOOL KuaiLe), Japanese (Zen Maru Gothic), Korean (Gaegu) handwritten fonts for Comic theme
- Fix collapsed sidebar: hide language switch, stack theme icons vertically
- Add hermes shebang parsing as fallback Python discovery for Termux
- Remove cron source filter from history sessions
- Add 0.5.17 changelog entries for all 8 locales

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: tolerate duplicate YAML keys in config parsing (closes #628)

Add `{ json: true }` to all 7 `yaml.load()` calls so duplicated mapping
keys (e.g. multiple `mcp_servers:` blocks) no longer crash the API.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: gateway ownership check requires PID file to prevent cross-profile port hijacking

Remove fallback that assumed ownership of healthy gateways without PID
verification. Now only claims a gateway if PID file exists and process
is alive, preventing one profile from hijacking another's port.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 20:08:13 +08:00

64 lines
2.3 KiB
TypeScript

import { randomBytes } from 'crypto'
import { writeFile } from 'fs/promises'
import { join } from 'path'
import { config } from '../config'
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024 // 50MB
export async function handleUpload(ctx: any) {
const contentType = ctx.get('content-type') || ''
if (!contentType.startsWith('multipart/form-data')) {
ctx.status = 400; ctx.body = { error: 'Expected multipart/form-data' }; return
}
const boundary = '--' + contentType.split('boundary=')[1]
if (!boundary || boundary === '--undefined') {
ctx.status = 400; ctx.body = { error: 'Missing boundary' }; return
}
const chunks: Buffer[] = []
let totalSize = 0
for await (const chunk of ctx.req) {
totalSize += chunk.length
if (totalSize > MAX_UPLOAD_SIZE) {
ctx.status = 413; ctx.body = { error: `File too large (max ${MAX_UPLOAD_SIZE / 1024 / 1024}MB)` }; return
}
chunks.push(chunk)
}
const raw = Buffer.concat(chunks)
const boundaryBuf = Buffer.from(boundary)
const parts = splitMultipart(raw, boundaryBuf)
const results: { name: string; path: string }[] = []
for (const part of parts) {
const headerEnd = part.indexOf(Buffer.from('\r\n\r\n'))
if (headerEnd === -1) continue
const headerBuf = part.subarray(0, headerEnd)
const header = headerBuf.toString('utf-8')
const data = part.subarray(headerEnd + 4, part.length - 2)
let filename = ''
const filenameStarMatch = header.match(/filename\*=UTF-8''(.+)/i)
if (filenameStarMatch) { filename = decodeURIComponent(filenameStarMatch[1]) }
else {
const filenameMatch = header.match(/filename="([^"]+)"/)
if (!filenameMatch) continue
filename = filenameMatch[1]
}
const ext = filename.includes('.') ? '.' + filename.split('.').pop() : ''
const savedName = randomBytes(8).toString('hex') + ext
const savedPath = join(config.uploadDir, savedName)
await writeFile(savedPath, data)
results.push({ name: filename, path: savedPath })
}
ctx.body = { files: results }
}
function splitMultipart(raw: Buffer, boundary: Buffer): Buffer[] {
const parts: Buffer[] = []
let start = 0
while (true) {
const idx = raw.indexOf(boundary, start)
if (idx === -1) break
if (start > 0) { parts.push(raw.subarray(start + 2, idx)) }
start = idx + boundary.length
}
return parts
}