From df1348a67525e5a99cff96a7b1f455ca401b4da0 Mon Sep 17 00:00:00 2001 From: qiukai Date: Wed, 6 May 2026 01:19:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20SessionStart=20hook=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20suppressOutput=EF=BC=8C=E9=98=B2=E6=AD=A2=E5=8E=9F=E5=A7=8B?= =?UTF-8?q?=20JSON=20=E6=98=BE=E7=A4=BA=E5=9C=A8=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - context handler 返回值增加 suppressOutput:!0,与 session-init 行为一致 - 回归测试脚本增加 Chroma-MCP 服务连通性、日志健康检查、Embedding 功能验证 --- scripts/regression-test.sh | 69 ++++++++++++++++++++++++++++++++++++-- scripts/worker-service.cjs | 2 +- 2 files changed, 68 insertions(+), 3 deletions(-) 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(`