feat: complete WorkBuddy parity and Windows release workflow

This commit is contained in:
xiaohan.yuan committed 2026-10-18 12:45:21 +08:00
1 parent 143516b3a8
commit d5512df549
434 files changed
+54977 -1854

No files matched your search

+38 -5
View File
@@ -16,7 +16,8 @@ import { EventEmitter } from 'node:events'
import { existsSync, readFileSync } from 'node:fs'
import { mkdir } from 'node:fs/promises'
import { homedir } from 'node:os'
import { dirname, isAbsolute, join } from 'node:path'
import { dirname, isAbsolute, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
/** OMP 单帧上限(含换行) */
const MAX_FRAME_BYTES = 1024 * 1024
@@ -89,10 +90,37 @@ interface Pending {
timer: NodeJS.Timeout
}
/** 在常见位置探测 OMP 可执行文件 */
/**
* 随包内置的 OMP:打包后位于 resources/omp,开发态位于项目 resources/omp。
* 版本与摘要由 resources/omp/runtime.lock.json 锁定(npm run omp:verify 校验)。
*/
function detectBundledOmpPath(): string | null {
let moduleBundled: string | null = null
if (typeof __dirname === 'string' && __dirname) {
moduleBundled = resolve(__dirname, '..', '..', 'resources', 'omp', 'omp.exe')
} else if (typeof import.meta.url === 'string') {
try {
moduleBundled = fileURLToPath(new URL('../../resources/omp/omp.exe', import.meta.url))
} catch {
// CJS test bundles may expose an empty import.meta object.
}
}
const candidates = [
process.resourcesPath ? join(process.resourcesPath, 'omp', 'omp.exe') : null,
join(process.cwd(), 'resources', 'omp', 'omp.exe'),
moduleBundled
]
for (const candidate of candidates) {
if (candidate && existsSync(candidate)) return candidate
}
return null
}
/** 探测 OMP 可执行文件:显式覆盖 > 随包内置 > 本机 Ygent 安装 */
export function detectOmpPath(): string | null {
const candidates = [
process.env['YCODE_OMP_PATH'],
detectBundledOmpPath(),
process.env['LOCALAPPDATA'] ? `${process.env['LOCALAPPDATA']}\\Ygent\\omp.exe` : null,
'C:\\Program Files\\Ygent\\omp.exe'
].filter((p): p is string => !!p)
@@ -170,6 +198,7 @@ export function getOmpInfo(profile = 'ycode'): OmpInfo {
}
export class OmpKernel extends EventEmitter {
get isStarted(): boolean { return this.readyDone && !this.disposed }
private child: ChildProcessWithoutNullStreams | null = null
private pending = new Map<string, Pending>()
private seq = 0
@@ -227,6 +256,8 @@ export class OmpKernel extends EventEmitter {
}
const args = ['--mode', 'rpc', '--profile', this.profile, `--model=${model}`, '--cwd', cwd, '--no-tools']
if (this.environment?.YCODE_MODEL_CONFIG) args.push('--config', this.environment.YCODE_MODEL_CONFIG)
if (this.environment?.YCODE_CONTEXT_OBSERVER_EXTENSION) args.push('--extension',this.environment.YCODE_CONTEXT_OBSERVER_EXTENSION)
if (this.sessionPath) {
args.push(`--session-dir=${dirname(this.sessionPath)}`, `--resume=${this.sessionPath}`)
} else {
@@ -378,7 +409,7 @@ export class OmpKernel extends EventEmitter {
}
/** Resolve an OMP host_tool_call. The host request id, not toolCallId, correlates the reply. */
async respondToHostTool(requestId: string, content: string, isError = false): Promise<void> {
async respondToHostTool(requestId: string, content: string, isError = false, images?: OmpPromptImage[]): Promise<void> {
await this.start()
if (!requestId.trim()) throw new Error('OMP host tool response requires a request id')
const child = this.child
@@ -386,10 +417,12 @@ export class OmpKernel extends EventEmitter {
const payload = {
type: 'host_tool_result',
id: requestId,
result: { content: [{ type: 'text', text: content }] },
result: { content: [{ type: 'text', text: content }, ...(!isError ? images ?? [] : [])] },
...(isError ? { isError: true } : {})
}
child.stdin.write(`${JSON.stringify(payload)}\n`)
const frame = `${JSON.stringify(payload)}\n`
if (Buffer.byteLength(frame) > MAX_FRAME_BYTES) throw new Error('OMP 工具结果超过单帧大小限制')
child.stdin.write(frame)
}
dispose(): Promise<void> {