mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 01:21:59 +00:00
eddeded2a4
- 第一批(安全加固与生命周期止血):隔离测试宿主机密钥与 .env 泄漏;修复 SQLite 取消查询挂死、:memory: 连接池报错与 EngineCache 世代竞态;补齐运行与评测删除时的 Checkpointer 线程、Artifact 磁盘文件与无主附件回收;加固三套沙箱路径越界、进程组清理与 Seatbelt 数据目录隔离,封堵 SSRF IPv4-mapped IPv6 元数据绕过与响应体 OOM;修复推理模型多轮 reasoning_content、结构化输出 Token 漏计与缓存折扣计费 - 第二批(数据库/集群/问数提速与首屏分包):补齐核心表索引与 SQLite WAL 调优,列表接口按需 defer 大列,收敛启动扫描并打通跨进程 slot 槽位唤醒;优化跨源全局选表、哈希桶数字绑定、流式 CSV 导入及多方言分析算子;剥离首屏静态引入的 @xyflow/react 与全局样式(首屏主包降至 461.59 kB),补充流式 rAF 攒批、Store 精细订阅与表单无障碍绑定 - 第三批(架构演进与工程自动化闭环):记录库超挂接阈值历史版本自动增量归档至单库多表(解除 SQLite 10 库 ATTACH 硬上限);重构知识库为词面 BM25 与全集合归一化向量矩阵真·双路独立召回并支持游标分批重建;升级多方言感知 SQL 模板词法分析(E'...' / $$...$$ / q'[...]' / # / 嵌套注释)与 Decimal / bool / NaN 参数校验,消除 ledger 层对 api 路由层的反向依赖;拆分 3101 行 CaliberDrafts 页面为 7 个领域子模块,消除检查脚本本机路径硬编码并新增 GitHub Actions CI 门禁与三批回归测试套件
143 lines
5.6 KiB
JavaScript
143 lines
5.6 KiB
JavaScript
import { spawnSync } from 'node:child_process'
|
||
import { existsSync, readFileSync } from 'node:fs'
|
||
import { homedir } from 'node:os'
|
||
import { dirname, extname, join, resolve } from 'node:path'
|
||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||
|
||
export const ROOT = dirname(dirname( dirname(fileURLToPath(import.meta.url)) ))
|
||
const FRONTEND = join(ROOT, 'frontend')
|
||
|
||
/**
|
||
* 跨平台解析可用的 Python 解释器:
|
||
* 优先尊重 CHECK_PYTHON、当前激活的虚拟环境 / Conda 环境、仓库内 .venv,
|
||
* 再回退到常见开发路径与系统 PATH 中的 python3 / python,并实际试跑确认可执行。
|
||
*/
|
||
export function resolvePython({ requireBackend = false } = {}) {
|
||
const isWin = process.platform === 'win32'
|
||
const venvBin = (dir) => (isWin ? join(dir, 'Scripts', 'python.exe') : join(dir, 'bin', 'python'))
|
||
const candidates = [
|
||
process.env.CHECK_PYTHON,
|
||
process.env.VIRTUAL_ENV && venvBin(process.env.VIRTUAL_ENV),
|
||
process.env.CONDA_PREFIX && (isWin ? join(process.env.CONDA_PREFIX, 'python.exe') : venvBin(process.env.CONDA_PREFIX)),
|
||
venvBin(join(ROOT, '.venv')),
|
||
venvBin(join(ROOT, 'backend', '.venv')),
|
||
join(homedir(), 'miniforge3', 'envs', 'agentlab', 'bin', 'python'),
|
||
join(homedir(), 'miniconda3', 'envs', 'agentlab', 'bin', 'python'),
|
||
'/opt/homebrew/bin/python3',
|
||
'/usr/local/bin/python3',
|
||
'python3',
|
||
'python',
|
||
].filter(Boolean)
|
||
|
||
const probe = requireBackend
|
||
? 'import sys, app; assert sys.version_info >= (3, 12)'
|
||
: 'import sys; assert sys.version_info >= (3, 12)'
|
||
const cwd = requireBackend ? join(ROOT, 'backend') : ROOT
|
||
|
||
const seen = new Set()
|
||
for (const bin of candidates) {
|
||
if (seen.has(bin)) continue
|
||
seen.add(bin)
|
||
if ((bin.includes('/') || bin.includes('\\')) && !existsSync(bin)) continue
|
||
const res = spawnSync(bin, ['-c', probe], { cwd, stdio: 'ignore' })
|
||
if (res.status === 0) return bin
|
||
}
|
||
return process.env.CHECK_PYTHON ?? 'python3'
|
||
}
|
||
|
||
/** 跨平台解析 Chrome / Chromium 可执行文件路径 */
|
||
export function resolveChrome() {
|
||
if (process.env.CHROME_PATH) return process.env.CHROME_PATH
|
||
const candidates = [
|
||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||
'/usr/bin/google-chrome',
|
||
'/usr/bin/google-chrome-stable',
|
||
'/usr/bin/chromium',
|
||
'/usr/bin/chromium-browser',
|
||
'/snap/bin/chromium',
|
||
]
|
||
for (const p of candidates) {
|
||
if (existsSync(p)) return p
|
||
}
|
||
return candidates[0]
|
||
}
|
||
|
||
function resolveTsFile(fromDir, spec) {
|
||
const base = spec.startsWith('/src/')
|
||
? join(FRONTEND, spec.slice(1))
|
||
: resolve(fromDir, spec)
|
||
const tries = extname(base)
|
||
? [base]
|
||
: [base, `${base}.ts`, `${base}.tsx`, join(base, 'index.ts'), join(base, 'index.tsx')]
|
||
for (const candidate of tries) {
|
||
if (existsSync(candidate)) return candidate
|
||
}
|
||
throw new Error(`找不到模块 ${spec}(自 ${fromDir})`)
|
||
}
|
||
|
||
/**
|
||
* 为 check-trace / check-decode 提供前端 TS 模块加载器:
|
||
* - 若本地或指定的 AGENTLAB_WEB 开发服务器可达,直接拉取其转译结果;
|
||
* - 若开发服务器未启动(如 CI、离线环境或单跑脚本),自动回退到 Vite 内置的 Oxc 内存转译,无需启服务或绑端口。
|
||
*/
|
||
export async function createTsLoader(webUrl = process.env.AGENTLAB_WEB ?? 'http://localhost:5273') {
|
||
let serverOnline = false
|
||
if (process.env.CHECK_OFFLINE !== '1') {
|
||
try {
|
||
const r = await fetch(`${webUrl}/`, { signal: AbortSignal.timeout(400) })
|
||
serverOnline = r.ok
|
||
} catch {
|
||
serverOnline = false
|
||
}
|
||
}
|
||
|
||
if (serverOnline) {
|
||
const loaded = new Map()
|
||
const loadFromWeb = async (modPath) => {
|
||
if (loaded.has(modPath)) return loaded.get(modPath)
|
||
const res = await fetch(`${webUrl}${modPath}?t=${Date.now()}`)
|
||
if (!res.ok) throw new Error(`${modPath} → HTTP ${res.status}`)
|
||
let code = await res.text()
|
||
const deps = new Set([...code.matchAll(/from\s+["'](\/src\/[^"'?]+)(?:\?[^"']*)?["']/g)].map((m) => m[1]))
|
||
for (const dep of deps) {
|
||
const url = await loadFromWeb(dep)
|
||
const pattern = new RegExp(`(["'])${dep.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\?[^"']*)?\\1`, 'g')
|
||
code = code.replace(pattern, JSON.stringify(url))
|
||
}
|
||
const url = 'data:text/javascript;base64,' + Buffer.from(code).toString('base64')
|
||
loaded.set(modPath, url)
|
||
return url
|
||
}
|
||
return loadFromWeb
|
||
}
|
||
|
||
const viteEntry = pathToFileURL(join(FRONTEND, 'node_modules', 'vite', 'dist', 'node', 'index.js')).href
|
||
const { transformWithOxc } = await import(viteEntry)
|
||
const loaded = new Map()
|
||
|
||
const loadLocalFile = async (absPath) => {
|
||
if (loaded.has(absPath)) return loaded.get(absPath)
|
||
const raw = readFileSync(absPath, 'utf8')
|
||
const lang = absPath.endsWith('.tsx') ? 'tsx' : 'ts'
|
||
const transformed = await transformWithOxc(raw, absPath, { lang, sourcemap: false })
|
||
let code = transformed.code
|
||
const dir = dirname(absPath)
|
||
const specs = new Set(
|
||
[...code.matchAll(/(?:import|export)\s+(?:[^"']*?\s+from\s+)?["']((?:\.\.?\/|\/src\/)[^"'?]+)(?:\?[^"']*)?["']/g)]
|
||
.map((m) => m[1]),
|
||
)
|
||
for (const spec of specs) {
|
||
const depAbs = resolveTsFile(dir, spec)
|
||
const depUrl = await loadLocalFile(depAbs)
|
||
const escaped = spec.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||
const pattern = new RegExp(`(["'])${escaped}(\\?[^"']*)?\\1`, 'g')
|
||
code = code.replace(pattern, JSON.stringify(depUrl))
|
||
}
|
||
const url = 'data:text/javascript;base64,' + Buffer.from(code).toString('base64')
|
||
loaded.set(absPath, url)
|
||
return url
|
||
}
|
||
|
||
return async (modPath) => loadLocalFile(resolveTsFile(FRONTEND, modPath))
|
||
}
|