diff --git a/scripts/regression-test.sh b/scripts/regression-test.sh index 9b6f4d4..dd8d5c9 100644 --- a/scripts/regression-test.sh +++ b/scripts/regression-test.sh @@ -1,7 +1,7 @@ #!/bin/bash # ============================================================ # codebuddy-mem 全功能回归测试 -# 测试范围: 基础设施 / 数据库 / MCP API / Chroma / 配置 +# 测试范围: 基础设施 / 数据库 / MCP API / Chroma / Chroma-MCP 服务 / 配置 # 使用方式: bash scripts/regression-test.sh # 或对话中说 "执行 codebuddy-mem 回归测试" # ============================================================ @@ -50,7 +50,7 @@ check "test -f $DATA_DIR/worker.pid" \ "worker.pid 存在" check "[ -n \"\$(cat $DATA_DIR/worker.pid 2>/dev/null | python3 -c 'import sys,json; print(json.load(sys.stdin)[\"pid\"])' 2>/dev/null)\" ] && kill -0 \$(cat $DATA_DIR/worker.pid 2>/dev/null | python3 -c 'import sys,json; print(json.load(sys.stdin)[\"pid\"])' 2>/dev/null) 2>/dev/null" \ "Worker 进程运行中" -check "pgrep -f 'chroma-mcp.*codebuddy-mem' > /dev/null" \ +check "pgrep -f 'chroma-mcp' > /dev/null" \ "Chroma-MCP 进程运行中" # ============================================================ @@ -157,6 +157,71 @@ check "test -f $DATA_DIR/chroma/chroma.sqlite3 && sqlite3 $DATA_DIR/chroma/chrom check "sqlite3 $DATA_DIR/chroma/chroma.sqlite3 'SELECT count(*) FROM collections;' | grep -q '[1-9]'" \ "Chroma collections 有数据" +# Chroma-MCP 服务连通性 +check "pgrep -f 'uvx.*chroma-mcp' > /dev/null || pgrep -f 'chroma-mcp.*persistent' > /dev/null || pgrep -f 'chroma-mcp.*data-dir' > /dev/null" \ + "chroma-mcp 进程运行中 (uvx 模式)" + +# 日志健康: 最近 1 小时内无 chroma-mcp 连接失败 +LATEST_LOG=$(ls -t $DATA_DIR/logs/*.log 2>/dev/null | head -1) +if [ -n "$LATEST_LOG" ]; then + check "python3 -c \" +import time, sys +cutoff = time.time() - 3600 +with open('$LATEST_LOG') as f: + for line in f: + if 'CHROMA_MCP' in line and ('Connection failed' in line or 'Connection attempt failed' in line): + try: + ts = line[1:24] + t = time.mktime(time.strptime(ts, '%Y-%m-%d %H:%M:%S.%f')) + if t > cutoff: + sys.exit(1) + except SystemExit: + raise + except: + pass +\"" \ + "chroma-mcp 最近 1 小时无连接失败" +else + _warn "无日志文件,跳过 chroma-mcp 日志检查" +fi + +# 日志健康: 无 chroma-mcp 重试退避 (backoff) +if [ -n "$LATEST_LOG" ]; then + check "python3 -c \" +import time, sys +cutoff = time.time() - 3600 +with open('$LATEST_LOG') as f: + for line in f: + if 'chroma-mcp connection in backoff' in line: + try: + ts = line[1:24] + t = time.mktime(time.strptime(ts, '%Y-%m-%d %H:%M:%S.%f')) + if t > cutoff: + sys.exit(1) + except SystemExit: + raise + except: + pass +\"" \ + "chroma-mcp 最近 1 小时无重试退避" +fi + +# Embedding 功能: 验证向量生成能力 +CHROMA_PYTHON=$(ps aux | grep "chroma-mcp" | grep -v grep | grep -o '/[^ ]*bin/python[0-9.]*' | head -1) +if [ -n "$CHROMA_PYTHON" ]; then + check "$CHROMA_PYTHON -c \" +import chromadb +from chromadb.utils import embedding_functions +ef = embedding_functions.DefaultEmbeddingFunction() +result = ef(['codebuddy-mem regression test embedding']) +assert len(result) == 1 and len(result[0]) == 384, f'Expected (1,384) got {len(result)}x{len(result[0]) if result else 0}' +print('OK') +\"" \ + "Embedding 函数可用 (生成 384 维向量)" +else + _warn "无法定位 chroma-mcp Python 环境,跳过 Embedding 测试" +fi + # ============================================================ _header "7. 配置文件" diff --git a/scripts/worker-service.cjs b/scripts/worker-service.cjs index 14a8cd2..12dfd78 100755 --- a/scripts/worker-service.cjs +++ b/scripts/worker-service.cjs @@ -896,7 +896,7 @@ Please re-login via Claude Desktop to refresh the token.`;d=d?`${y} ${d}`:y}let m="";if(s){let y=await nn(c,"GET");!rn(y)&&typeof y=="string"&&(m=y.trim())}let f=t.platform,h=m||(f==="gemini-cli"||f==="gemini"?d:""),g=s&&h?`${h} -View Observations Live @ http://localhost:${i}`:void 0;return{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:d},systemMessage:g}}}});var yD,bD=fe(()=>{"use strict";yn();te();ri();ql();zs();yD={async execute(t){let{sessionId:e,cwd:r,toolName:i,toolInput:n,toolResponse:s}=t,o=Lt(t.platform);if(!i)return{continue:!0,suppressOutput:!0,exitCode:lt.SUCCESS};let a=v.formatTool(i,n);if(v.dataIn("HOOK",`PostToolUse: ${a}`,{}),!r)throw new Error(`Missing cwd in PostToolUse hook input for session ${e}, tool ${i}`);if(!ba(r))return v.debug("HOOK","Project excluded from tracking, skipping observation",{cwd:r,toolName:i}),{continue:!0,suppressOutput:!0};let c=await nn("/api/sessions/observations","POST",{contentSessionId:e,platformSource:o,tool_name:i,tool_input:n,tool_response:s,cwd:r,agentId:t.agentId,agentType:t.agentType});return rn(c)?{continue:!0,suppressOutput:!0,exitCode:lt.SUCCESS}:(v.debug("HOOK","Observation sent successfully",{toolName:i}),{continue:!0,suppressOutput:!0})}}});function LMe(t){try{let e=JSON.parse(t);if(e&&Array.isArray(e.messages))return{isGemini:!0,messages:e.messages}}catch{}return{isGemini:!1}}function Dee(t,e,r=!1){if(!t||!(0,gw.existsSync)(t))return v.warn("PARSER",`Transcript path missing or file does not exist: ${t}`),"";let i=(0,gw.readFileSync)(t,"utf-8").trim();if(!i)return v.warn("PARSER",`Transcript file exists but is empty: ${t}`),"";let n=LMe(i);return n.isGemini?FMe(n.messages,e,r):qMe(i,e,r)}function FMe(t,e,r){let i=e==="assistant"?"gemini":"user";for(let n=t.length-1;n>=0;n--){let s=t[n];if(s?.type===i&&typeof s.content=="string"){let o=s.content;return r&&(o=o.replace(cf,""),o=o.replace(/\n{3,}/g,` +View Observations Live @ http://localhost:${i}`:void 0;return{suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:d},systemMessage:g}}}});var yD,bD=fe(()=>{"use strict";yn();te();ri();ql();zs();yD={async execute(t){let{sessionId:e,cwd:r,toolName:i,toolInput:n,toolResponse:s}=t,o=Lt(t.platform);if(!i)return{continue:!0,suppressOutput:!0,exitCode:lt.SUCCESS};let a=v.formatTool(i,n);if(v.dataIn("HOOK",`PostToolUse: ${a}`,{}),!r)throw new Error(`Missing cwd in PostToolUse hook input for session ${e}, tool ${i}`);if(!ba(r))return v.debug("HOOK","Project excluded from tracking, skipping observation",{cwd:r,toolName:i}),{continue:!0,suppressOutput:!0};let c=await nn("/api/sessions/observations","POST",{contentSessionId:e,platformSource:o,tool_name:i,tool_input:n,tool_response:s,cwd:r,agentId:t.agentId,agentType:t.agentType});return rn(c)?{continue:!0,suppressOutput:!0,exitCode:lt.SUCCESS}:(v.debug("HOOK","Observation sent successfully",{toolName:i}),{continue:!0,suppressOutput:!0})}}});function LMe(t){try{let e=JSON.parse(t);if(e&&Array.isArray(e.messages))return{isGemini:!0,messages:e.messages}}catch{}return{isGemini:!1}}function Dee(t,e,r=!1){if(!t||!(0,gw.existsSync)(t))return v.warn("PARSER",`Transcript path missing or file does not exist: ${t}`),"";let i=(0,gw.readFileSync)(t,"utf-8").trim();if(!i)return v.warn("PARSER",`Transcript file exists but is empty: ${t}`),"";let n=LMe(i);return n.isGemini?FMe(n.messages,e,r):qMe(i,e,r)}function FMe(t,e,r){let i=e==="assistant"?"gemini":"user";for(let n=t.length-1;n>=0;n--){let s=t[n];if(s?.type===i&&typeof s.content=="string"){let o=s.content;return r&&(o=o.replace(cf,""),o=o.replace(/\n{3,}/g,` `).trim()),o}}return""}function qMe(t,e,r){let i=t.split(` `),n=!1,s=null;for(let o=i.length-1;o>=0;o--){let a=i[o];if(!a)continue;let c;try{c=JSON.parse(a)}catch{continue}if((c.type??c.role)!==e||(n=!0,!c.message?.content))continue;let l="",d=c.message.content;if(typeof d=="string")l=d;else if(Array.isArray(d))l=d.filter(p=>!!p&&typeof p=="object"&&p.type==="text"&&typeof p.text=="string").map(p=>p.text).join(`