From 3ca29993f20b11023c1ce8b5939db7538b3bc390 Mon Sep 17 00:00:00 2001 From: Selig Date: Mon, 16 Mar 2026 15:03:28 +0800 Subject: [PATCH] improve(qmd-brain): avoid shell execution in embed trigger --- skills/qmd-brain/handler.ts | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/skills/qmd-brain/handler.ts b/skills/qmd-brain/handler.ts index f0dda98..c265988 100644 --- a/skills/qmd-brain/handler.ts +++ b/skills/qmd-brain/handler.ts @@ -7,10 +7,10 @@ * - embed_to_pg.py (Python venv at /home/selig/apps/qmd-pg/) */ -import { exec, execFile } from 'child_process'; +import { spawn, execFile } from 'child_process'; import { promisify } from 'util'; +import { openSync, closeSync } from 'fs'; -const execAsync = promisify(exec); const execFileAsync = promisify(execFile); const QMD_CMD = '/home/selig/.nvm/versions/node/v24.13.1/bin/qmd'; @@ -66,12 +66,28 @@ function formatPgResults(results: SearchResult[]): string { /** 觸發向量索引更新 */ async function triggerEmbed(): Promise { + const env = { ...process.env, HOME: '/home/selig' }; + const logPath = '/tmp/qmd-embed.log'; + try { - // 背景執行,不等待完成 - exec( - `${QMD_CMD} embed 2>&1 >> /tmp/qmd-embed.log & ${EMBED_PY_BIN} ${EMBED_PY_SCRIPT} embed 2>&1 >> /tmp/qmd-embed.log &`, - { env: { ...process.env, HOME: '/home/selig' } } - ); + const fdQmd = openSync(logPath, 'a'); + const qmdProc = spawn(QMD_CMD, ['embed'], { + env, + detached: true, + stdio: ['ignore', fdQmd, fdQmd], + }); + qmdProc.unref(); + closeSync(fdQmd); + + const fdPg = openSync(logPath, 'a'); + const pgProc = spawn(EMBED_PY_BIN, [EMBED_PY_SCRIPT, 'embed'], { + env, + detached: true, + stdio: ['ignore', fdPg, fdPg], + }); + pgProc.unref(); + closeSync(fdPg); + return '✅ 索引更新已在背景啟動,約需 1-5 分鐘完成。'; } catch (e: any) { return `❌ 索引啟動失敗: ${e.message}`;