forked from Selig/openclaw-skill
Merge pull request 'improve(qmd-brain): 強化命令執行安全,降低注入風險' (#5) from tiangong/openclaw-skill:improve/qmd-brain-command-injection-hardening into main
This commit is contained in:
@@ -7,13 +7,15 @@
|
|||||||
* - embed_to_pg.py (Python venv at /home/selig/apps/qmd-pg/)
|
* - embed_to_pg.py (Python venv at /home/selig/apps/qmd-pg/)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execSync, exec } from 'child_process';
|
import { exec, execFile } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
const QMD_CMD = '/home/selig/.nvm/versions/node/v24.13.1/bin/qmd';
|
const QMD_CMD = '/home/selig/.nvm/versions/node/v24.13.1/bin/qmd';
|
||||||
const EMBED_PY = '/home/selig/apps/qmd-pg/venv/bin/python3 /home/selig/apps/qmd-pg/embed_to_pg.py';
|
const EMBED_PY_BIN = '/home/selig/apps/qmd-pg/venv/bin/python3';
|
||||||
|
const EMBED_PY_SCRIPT = '/home/selig/apps/qmd-pg/embed_to_pg.py';
|
||||||
const MAX_SEARCH_LEN = 1500; // 回覆中搜尋結果最大字數
|
const MAX_SEARCH_LEN = 1500; // 回覆中搜尋結果最大字數
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
@@ -23,17 +25,12 @@ interface SearchResult {
|
|||||||
similarity?: number;
|
similarity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QmdResult {
|
|
||||||
path: string;
|
|
||||||
text?: string;
|
|
||||||
score?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 執行 qmd BM25 全文搜尋 */
|
/** 執行 qmd BM25 全文搜尋 */
|
||||||
async function qmdSearch(query: string, topK = 5): Promise<string> {
|
async function qmdSearch(query: string, topK = 5): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execAsync(
|
const { stdout } = await execFileAsync(
|
||||||
`${QMD_CMD} search ${JSON.stringify(query)} --output markdown --limit ${topK}`,
|
QMD_CMD,
|
||||||
|
['search', query, '--output', 'markdown', '--limit', String(topK)],
|
||||||
{ timeout: 15000, env: { ...process.env, HOME: '/home/selig' } }
|
{ timeout: 15000, env: { ...process.env, HOME: '/home/selig' } }
|
||||||
);
|
);
|
||||||
return stdout.trim() || '(無結果)';
|
return stdout.trim() || '(無結果)';
|
||||||
@@ -45,8 +42,9 @@ async function qmdSearch(query: string, topK = 5): Promise<string> {
|
|||||||
/** 執行 PostgreSQL 向量語意搜尋 */
|
/** 執行 PostgreSQL 向量語意搜尋 */
|
||||||
async function pgSearch(query: string, topK = 5): Promise<SearchResult[]> {
|
async function pgSearch(query: string, topK = 5): Promise<SearchResult[]> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execAsync(
|
const { stdout } = await execFileAsync(
|
||||||
`${EMBED_PY} search ${JSON.stringify(query)} --top-k ${topK} --json`,
|
EMBED_PY_BIN,
|
||||||
|
[EMBED_PY_SCRIPT, 'search', query, '--top-k', String(topK), '--json'],
|
||||||
{ timeout: 20000 }
|
{ timeout: 20000 }
|
||||||
);
|
);
|
||||||
return JSON.parse(stdout) as SearchResult[];
|
return JSON.parse(stdout) as SearchResult[];
|
||||||
@@ -71,7 +69,7 @@ async function triggerEmbed(): Promise<string> {
|
|||||||
try {
|
try {
|
||||||
// 背景執行,不等待完成
|
// 背景執行,不等待完成
|
||||||
exec(
|
exec(
|
||||||
`${QMD_CMD} embed 2>&1 >> /tmp/qmd-embed.log & ${EMBED_PY} embed 2>&1 >> /tmp/qmd-embed.log &`,
|
`${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' } }
|
{ env: { ...process.env, HOME: '/home/selig' } }
|
||||||
);
|
);
|
||||||
return '✅ 索引更新已在背景啟動,約需 1-5 分鐘完成。';
|
return '✅ 索引更新已在背景啟動,約需 1-5 分鐘完成。';
|
||||||
@@ -86,8 +84,9 @@ async function getStats(): Promise<string> {
|
|||||||
|
|
||||||
// qmd collection list
|
// qmd collection list
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execAsync(
|
const { stdout } = await execFileAsync(
|
||||||
`${QMD_CMD} collection list`,
|
QMD_CMD,
|
||||||
|
['collection', 'list'],
|
||||||
{ timeout: 5000, env: { ...process.env, HOME: '/home/selig' } }
|
{ timeout: 5000, env: { ...process.env, HOME: '/home/selig' } }
|
||||||
);
|
);
|
||||||
results.push(`**qmd Collections:**\n\`\`\`\n${stdout.trim()}\n\`\`\``);
|
results.push(`**qmd Collections:**\n\`\`\`\n${stdout.trim()}\n\`\`\``);
|
||||||
@@ -97,8 +96,9 @@ async function getStats(): Promise<string> {
|
|||||||
|
|
||||||
// pgvector stats
|
// pgvector stats
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execAsync(
|
const { stdout } = await execFileAsync(
|
||||||
`${EMBED_PY} stats`,
|
EMBED_PY_BIN,
|
||||||
|
[EMBED_PY_SCRIPT, 'stats'],
|
||||||
{ timeout: 10000 }
|
{ timeout: 10000 }
|
||||||
);
|
);
|
||||||
results.push(`**PostgreSQL pgvector:**\n\`\`\`\n${stdout.trim()}\n\`\`\``);
|
results.push(`**PostgreSQL pgvector:**\n\`\`\`\n${stdout.trim()}\n\`\`\``);
|
||||||
|
|||||||
Reference in New Issue
Block a user