fix: SessionStart hook 添加 suppressOutput,防止原始 JSON 显示在对话中

- context handler 返回值增加 suppressOutput:!0,与 session-init 行为一致
- 回归测试脚本增加 Chroma-MCP 服务连通性、日志健康检查、Embedding 功能验证
This commit is contained in:
2026-05-06 01:19:47 +08:00
parent b84d163af1
commit df1348a675
2 changed files with 68 additions and 3 deletions

View File

@@ -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. 配置文件"

View File

@@ -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(`