Files
goodbuddy/src/main/agent/continue-utility-process-adapter.test.ts
T
mesalogo 5a77dfdb93
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
fix: prevent managed runtime stalls during file changes
Automatic native Git snapshot diffs could block concurrent OpenCode sessions
after broad rewrites or line-ending conversions. Managed desktop and Agent
profiles now skip snapshots that GoodBuddy does not use, while preserving
file tools, native subagents, workspace Git operations, and Ask/Execute rules.

Drain otherwise-unused Continue utility-host stdout to prevent console output
from filling the pipe and blocking requests. Correct the lifecycle regression's
body observer to watch the underlying reader rather than a buffered wrapper.

Prepare Desktop 0.12.10 and Agent 0.11.22 with aligned bilingual notes and
feature documentation. Existing runtimes require restart or Agent update.
Full desktop UI before/after and Linux Agent production-path checks passed;
3626 tests passed, with 61 existing platform/manual skips.

Release note: 修复批量文件改写或换行转换后的托管 OpenCode 并行停顿,以及 Continue 控制台输出阻塞;本机升级重启后生效,远程项目请配套更新 Agent。
2026-09-09 03:07:55 +08:00

102 lines
3.1 KiB
TypeScript

import { EventEmitter } from 'node:events'
import { PassThrough } from 'node:stream'
import { describe, expect, it, vi } from 'vitest'
import {
createContinueUtilityProcessChild,
type ContinueUtilityProcessSource
} from './continue-utility-process-adapter'
import { waitForProcessExit } from './child-process-termination'
function createSource(stdout?: PassThrough): ContinueUtilityProcessSource & EventEmitter {
const emitter =
new EventEmitter() as ContinueUtilityProcessSource & EventEmitter
Object.defineProperties(emitter, {
pid: { value: 42 },
stdout: { value: stdout },
stderr: { value: undefined }
})
emitter.kill = vi.fn(() => true)
emitter.onExit = (listener) => {
emitter.on('exit', listener)
}
emitter.onceExit = (listener) => {
emitter.once('exit', listener)
}
emitter.onceError = (listener) => {
emitter.once('utility-error', listener)
}
emitter.removeExitListener = (listener) => {
emitter.removeListener('exit', listener)
}
emitter.removeErrorListener = (listener) => {
emitter.removeListener('utility-error', listener)
}
return emitter
}
describe('Continue utility process adapter', () => {
it('drains unused stdout so native output cannot block the host', async () => {
const stdout = new PassThrough({ highWaterMark: 1024 })
createContinueUtilityProcessChild(createSource(stdout))
expect(stdout.readableFlowing).toBe(true)
await new Promise<void>((resolve) => {
stdout.end(Buffer.alloc(128 * 1024), resolve)
})
expect(stdout.readableLength).toBe(0)
})
it('maps Electron exit to close and completes helper waits immediately', async () => {
const source = createSource()
const child = createContinueUtilityProcessChild(source)
const close = vi.fn()
child.once('close', close)
const waiting = waitForProcessExit(child)
source.emit('exit', 0)
expect(close).toHaveBeenCalledWith(0)
expect(child.exitCode).toBe(0)
await expect(waiting).resolves.toBeUndefined()
})
it('maps utility errors and removes both listener types', () => {
const source = createSource()
const child = createContinueUtilityProcessChild(source)
const close = vi.fn()
const error = vi.fn()
child.once('close', close)
child.once('error', error)
child.removeListener?.('close', close)
child.removeListener?.('error', error)
source.emit('exit', 0)
source.emit('utility-error', 'FatalError', 'worker.js:1', 'report')
expect(close).not.toHaveBeenCalled()
expect(error).not.toHaveBeenCalled()
})
it('converts utility error details to a bounded Error', () => {
const source = createSource()
const child = createContinueUtilityProcessChild(source)
const error = vi.fn()
child.once('error', error)
source.emit(
'utility-error',
'FatalError',
'worker.js:1',
'x'.repeat(1_000)
)
expect(error).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringMatching(
/^Continue 宿主进程异常(worker\.js:1):x{500}$/u
)
})
)
})
})