Fix Windows bundled Hermes CLI launcher (#1159)

* Fix Windows bundled Hermes CLI launcher

* Update kanban service tests for Hermes process wrapper

---------

Co-authored-by: xingzhi <chuzihao.czh@alibaba-inc.com>
This commit is contained in:
sir1st
2026-05-30 18:57:04 +08:00
committed by GitHub
parent 046c8d4f8f
commit ce04b10eee
10 changed files with 219 additions and 74 deletions
@@ -1,9 +1,6 @@
import { execFile, spawn } from 'child_process'
import type { ChildProcess } from 'child_process'
import { promisify } from 'util'
import { logger } from '../logger'
const execFileAsync = promisify(execFile)
import { execHermes, spawnHermes } from './hermes-process'
const execOpts = { windowsHide: true }
const BOARD_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/
@@ -12,14 +9,6 @@ const NO_WORKER_LOG_PATTERNS = [
/^no worker log(?: for [^\n]+)?$/i,
]
function resolveHermesBin(): string {
const envBin = process.env.HERMES_BIN?.trim()
if (envBin) return envBin
return 'hermes'
}
const HERMES_BIN = resolveHermesBin()
export function normalizeBoardSlug(board?: string | null): string {
if (board === undefined || board === null) return 'default'
const trimmed = board.trim().toLowerCase()
@@ -186,7 +175,7 @@ export async function listBoards(opts?: { includeArchived?: boolean }): Promise<
if (opts?.includeArchived) args.push('--all')
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -213,7 +202,7 @@ export async function createBoard(opts: KanbanBoardCreateOptions): Promise<Kanba
if (opts.switchCurrent) args.push('--switch')
try {
await execFileAsync(HERMES_BIN, args, {
await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -232,7 +221,7 @@ export async function archiveBoard(slugInput: string): Promise<void> {
if (slug === 'default') throw new Error('Cannot archive the default kanban board')
try {
await execFileAsync(HERMES_BIN, ['kanban', 'boards', 'rm', slug], {
await execHermes(['kanban', 'boards', 'rm', slug], {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -297,7 +286,7 @@ function textFromExecValue(value: unknown): string {
async function execKanbanMutation(args: string[], logMessage: string, errorPrefix: string): Promise<string> {
try {
const { stdout, stderr } = await execFileAsync(HERMES_BIN, args, {
const { stdout, stderr } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -318,7 +307,7 @@ export function buildWatchArgs(opts?: KanbanWatchOptions): string[] {
}
export function watchEvents(opts?: KanbanWatchOptions): ChildProcess {
return spawn(HERMES_BIN, buildWatchArgs(opts), {
return spawnHermes(buildWatchArgs(opts), {
stdio: ['ignore', 'pipe', 'pipe'],
...execOpts,
})
@@ -346,7 +335,7 @@ export async function addComment(taskId: string, body: string, opts?: KanbanBoar
const args = [...boardArgs(opts?.board), 'comment', taskId, body]
pushOptional(args, '--author', opts?.author)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -362,7 +351,7 @@ export async function getTaskLog(taskId: string, opts?: KanbanBoardOptions & { t
const args = [...boardArgs(opts?.board), 'log', taskId]
pushOptional(args, '--tail', opts?.tail)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -399,7 +388,7 @@ export async function getDiagnostics(opts?: KanbanBoardOptions & { task?: string
pushOptional(args, '--task', opts?.task)
pushOptional(args, '--severity', opts?.severity)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -415,7 +404,7 @@ export async function reclaimTask(taskId: string, opts?: KanbanBoardOptions & {
const args = [...boardArgs(opts?.board), 'reclaim', taskId]
pushOptional(args, '--reason', opts?.reason)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -432,7 +421,7 @@ export async function reassignTask(taskId: string, profile: string, opts?: Kanba
if (opts?.reclaim) args.push('--reclaim')
pushOptional(args, '--reason', opts?.reason)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -448,7 +437,7 @@ export async function specifyTask(taskId: string, opts?: KanbanBoardOptions & {
const args = [...boardArgs(opts?.board), 'specify', taskId, '--json']
pushOptional(args, '--author', opts?.author)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -466,7 +455,7 @@ export async function dispatch(opts?: KanbanBoardOptions & { dryRun?: boolean; m
pushOptional(args, '--max', opts?.max)
pushOptional(args, '--failure-limit', opts?.failureLimit)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -492,7 +481,7 @@ export async function listTasks(opts?: {
if (opts?.tenant) args.push('--tenant', opts.tenant)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -506,7 +495,7 @@ export async function listTasks(opts?: {
export async function getTask(taskId: string, opts?: KanbanBoardOptions): Promise<KanbanTaskDetail | null> {
try {
const { stdout } = await execFileAsync(HERMES_BIN, [...boardArgs(opts?.board), 'show', taskId, '--json'], {
const { stdout } = await execHermes([...boardArgs(opts?.board), 'show', taskId, '--json'], {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -536,7 +525,7 @@ export async function createTask(
if (opts?.tenant) args.push('--tenant', opts.tenant)
try {
const { stdout } = await execFileAsync(HERMES_BIN, args, {
const { stdout } = await execHermes(args, {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -622,7 +611,7 @@ export async function bulkUpdateTasks(opts: KanbanBulkTaskUpdateOptions): Promis
export async function getStats(opts?: KanbanBoardOptions): Promise<KanbanStats> {
try {
const { stdout } = await execFileAsync(HERMES_BIN, [...boardArgs(opts?.board), 'stats', '--json'], {
const { stdout } = await execHermes([...boardArgs(opts?.board), 'stats', '--json'], {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,
@@ -642,7 +631,7 @@ export async function getStats(opts?: KanbanBoardOptions): Promise<KanbanStats>
export async function getAssignees(opts?: KanbanBoardOptions): Promise<KanbanAssignee[]> {
try {
const { stdout } = await execFileAsync(HERMES_BIN, [...boardArgs(opts?.board), 'assignees', '--json'], {
const { stdout } = await execHermes([...boardArgs(opts?.board), 'assignees', '--json'], {
maxBuffer: 50 * 1024 * 1024,
timeout: 30000,
...execOpts,