fix(server): 修复 csc.exe server 模式下探针进程因 --feature 参数崩溃导致模型列表为空
Bun 将源码编译为独立可执行文件后,process.argv[1] 变为内部虚拟路径 (B:/~BUN/root/cli.js),该路径含 / 被误判为脚本模式,导致 saveChildSpawnPrefix 为子进程生成 --feature / -d 参数。子进程(csc.exe 本身)不识别这些 Bun 运行时参数,以 exit code 1 退出,探针失败,/provider/capabilities 返回空模型。 修复: - getScriptArgsForChild: 识别并跳过 Bun snapshot 虚拟路径前缀 (B:/~BUN/, /snapshot/) - saveChildSpawnPrefix: 仅在 scriptArgs 非空(脚本模式)时才生成 --feature/define 参数 - build.ts: 补充 Step 7 编译独立可执行文件 csc.exe / csc Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ad2d4508c8
commit
40dc2720d0
37
build.ts
37
build.ts
|
|
@ -172,3 +172,40 @@ chmodSync(cliBun, 0o755)
|
||||||
chmodSync(cliNode, 0o755)
|
chmodSync(cliNode, 0o755)
|
||||||
|
|
||||||
console.log(`Generated ${cliBun} (shebang: bun) and ${cliNode} (shebang: node)`)
|
console.log(`Generated ${cliBun} (shebang: bun) and ${cliNode} (shebang: node)`)
|
||||||
|
|
||||||
|
// Step 7: Compile standalone executable (csc.exe on Windows, csc on Unix)
|
||||||
|
// Must use Bun.build({ compile: true, features }) instead of `bun build --compile`
|
||||||
|
// because the CLI command doesn't support the --feature flag.
|
||||||
|
// NOTE: compile mode uses outdir (not outfile) — Bun names the output after the entrypoint.
|
||||||
|
const isWin = process.platform === 'win32'
|
||||||
|
const exeName = isWin ? 'csc.exe' : 'csc'
|
||||||
|
|
||||||
|
const compileResult = await Bun.build({
|
||||||
|
entrypoints: ['src/entrypoints/cli.tsx'],
|
||||||
|
target: 'bun',
|
||||||
|
compile: true,
|
||||||
|
define: getMacroDefines(),
|
||||||
|
features,
|
||||||
|
outdir: import.meta.dir,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!compileResult.success) {
|
||||||
|
console.error('Compile failed:')
|
||||||
|
for (const log of compileResult.logs) {
|
||||||
|
console.error(log)
|
||||||
|
}
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename auto-generated cli.tsx.exe → csc.exe
|
||||||
|
const generated = compileResult.outputs[0]
|
||||||
|
const targetPath = join(import.meta.dir, exeName)
|
||||||
|
if (generated && generated.path !== targetPath) {
|
||||||
|
const { renameSync, unlinkSync, existsSync } = await import('fs')
|
||||||
|
if (existsSync(targetPath)) {
|
||||||
|
try { unlinkSync(targetPath) } catch { /* locked by running process */ }
|
||||||
|
}
|
||||||
|
renameSync(generated.path, targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Compiled standalone executable: ${join(import.meta.dir, exeName)}`)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ export const INIT_TIMEOUT_MS = (() => {
|
||||||
export function getScriptArgsForChild(): string[] {
|
export function getScriptArgsForChild(): string[] {
|
||||||
const argv1 = process.argv[1]
|
const argv1 = process.argv[1]
|
||||||
if (!argv1) return []
|
if (!argv1) return []
|
||||||
|
// Bun standalone executable embeds a virtual snapshot path like "B:/~BUN/root/cli.js".
|
||||||
|
// This is not a real file on disk — skip it so the child process (the same exe)
|
||||||
|
// is not launched with a bogus script argument.
|
||||||
|
if (argv1.startsWith('B:/~BUN/') || argv1.startsWith('/snapshot/')) return []
|
||||||
if (argv1.endsWith('.ts') || argv1.endsWith('.tsx') || argv1.includes('/') || argv1.includes('\\')) {
|
if (argv1.endsWith('.ts') || argv1.endsWith('.tsx') || argv1.includes('/') || argv1.includes('\\')) {
|
||||||
return [argv1]
|
return [argv1]
|
||||||
}
|
}
|
||||||
|
|
@ -23,19 +27,26 @@ export async function saveChildSpawnPrefix(): Promise<void> {
|
||||||
const { execPath, scriptArgs } = getChildSpawnArgs()
|
const { execPath, scriptArgs } = getChildSpawnArgs()
|
||||||
let defineArgs: string[] = []
|
let defineArgs: string[] = []
|
||||||
let featureArgs: string[] = []
|
let featureArgs: string[] = []
|
||||||
try {
|
// Only pass --feature/define flags when running as a script (dev mode).
|
||||||
const definesMod = await import('../../scripts/defines.js') as { getMacroDefines: () => Record<string, string>; DEFAULT_BUILD_FEATURES: readonly string[] }
|
// In compiled standalone executable mode scriptArgs is empty, and feature
|
||||||
const defines = definesMod.getMacroDefines()
|
// flags are already baked in at compile time — passing --feature to the
|
||||||
defineArgs = Object.entries(defines).flatMap(([k, v]) => ['-d', `${k}:${v}`])
|
// child process would cause "unknown option" errors.
|
||||||
const features = definesMod.DEFAULT_BUILD_FEATURES
|
const isScriptMode = scriptArgs.length > 0
|
||||||
featureArgs = features.flatMap((f: string) => ['--feature', f])
|
if (isScriptMode) {
|
||||||
} catch {}
|
try {
|
||||||
const envFeatures = Object.entries(process.env)
|
const definesMod = await import('../../scripts/defines.js') as { getMacroDefines: () => Record<string, string>; DEFAULT_BUILD_FEATURES: readonly string[] }
|
||||||
.filter(([k]) => k.startsWith('FEATURE_') && k.slice(8))
|
const defines = definesMod.getMacroDefines()
|
||||||
.map(([k]) => ['--feature', k.slice(8)] as [string, string])
|
defineArgs = Object.entries(defines).flatMap(([k, v]) => ['-d', `${k}:${v}`])
|
||||||
.flat()
|
const features = definesMod.DEFAULT_BUILD_FEATURES
|
||||||
const allFeatureArgs = [...featureArgs, ...envFeatures]
|
featureArgs = features.flatMap((f: string) => ['--feature', f])
|
||||||
const prefix = JSON.stringify({ execPath, scriptArgs, defineArgs, featureArgs: allFeatureArgs })
|
} catch {}
|
||||||
|
const envFeatures = Object.entries(process.env)
|
||||||
|
.filter(([k]) => k.startsWith('FEATURE_') && k.slice(8))
|
||||||
|
.map(([k]) => ['--feature', k.slice(8)] as [string, string])
|
||||||
|
.flat()
|
||||||
|
featureArgs = [...featureArgs, ...envFeatures]
|
||||||
|
}
|
||||||
|
const prefix = JSON.stringify({ execPath, scriptArgs, defineArgs, featureArgs })
|
||||||
process.env._CSC_CHILD_SPAWN_PREFIX = prefix
|
process.env._CSC_CHILD_SPAWN_PREFIX = prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user