Files
goodbuddy/src/main/agent/continue-utility-process-adapter.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

100 lines
2.6 KiB
TypeScript

import type { ContinueHostChild } from './continue-host-adapter'
type UtilityErrorListener = (
type: 'FatalError',
location: string,
report: string
) => void
export type ContinueUtilityProcessSource = {
readonly pid?: number
readonly stdout?: Pick<NodeJS.ReadableStream, 'resume'> | null
readonly stderr?: ContinueHostChild['stderr']
kill(): boolean
onExit(listener: (code: number) => void): void
onceExit(listener: (code: number) => void): void
onceError(listener: UtilityErrorListener): void
removeExitListener(listener: (code: number) => void): void
removeErrorListener(listener: UtilityErrorListener): void
}
export function createContinueUtilityProcessChild(
utility: ContinueUtilityProcessSource
): ContinueHostChild {
// Responses arrive over HTTP, but an unread console pipe can block the host.
utility.stdout?.resume()
let exitCode: number | null = null
let killed = false
const closeListeners = new Map<
(value: Error | number | null) => void,
(code: number) => void
>()
const errorListeners = new Map<
(value: Error | number | null) => void,
UtilityErrorListener
>()
utility.onExit((code) => {
exitCode = code
})
const child: ContinueHostChild = {
get exitCode() {
return exitCode
},
get killed() {
return killed
},
get pid() {
return utility.pid
},
stderr: utility.stderr,
once: (event, listener) => {
if (event === 'close') {
const wrapped = (code: number): void => {
closeListeners.delete(listener)
listener(code)
}
closeListeners.set(listener, wrapped)
utility.onceExit(wrapped)
} else {
const wrapped: UtilityErrorListener = (
_type,
location,
report
): void => {
errorListeners.delete(listener)
listener(
new Error(
`Continue 宿主进程异常(${location}):${report.slice(0, 500)}`
)
)
}
errorListeners.set(listener, wrapped)
utility.onceError(wrapped)
}
return child
},
removeListener: (event, listener) => {
if (event === 'close') {
const wrapped = closeListeners.get(listener)
if (wrapped) {
closeListeners.delete(listener)
utility.removeExitListener(wrapped)
}
} else {
const wrapped = errorListeners.get(listener)
if (wrapped) {
errorListeners.delete(listener)
utility.removeErrorListener(wrapped)
}
}
return child
},
kill: () => {
killed = true
return utility.kill()
}
}
return child
}