@ia-ccun/code-agent-cli 0.0.52 → 0.0.53-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/bin/cli.js +97 -69
  2. package/config/agent/bin/README.md +102 -0
  3. package/config/agent/extensions/aicode-advisor.ts +197 -61
  4. package/config/agent/extensions/aicode-banner.ts +101 -0
  5. package/config/agent/extensions/aicode-btw.ts +677 -183
  6. package/config/agent/extensions/aicode-cd.ts +1017 -0
  7. package/config/agent/extensions/aicode-grillMe.ts +198 -0
  8. package/config/agent/extensions/aicode-pets.ts +351 -0
  9. package/config/agent/extensions/aicode-plan.ts +2 -0
  10. package/config/agent/extensions/aicode-safety-guard.ts +555 -0
  11. package/config/agent/extensions/aicode-tips.ts +69 -30
  12. package/config/agent/extensions/aicode-todo-progress.ts +547 -0
  13. package/config/agent/extensions/context-monitor.ts +30 -15
  14. package/config/agent/extensions/context.ts +132 -60
  15. package/config/agent/extensions/custom-footer.ts +0 -5
  16. package/config/agent/extensions/custom.ts +119 -109
  17. package/config/agent/extensions/error-monitor.ts +38 -51
  18. package/config/agent/extensions/goal.ts +8 -6
  19. package/config/agent/extensions/msg-debug.ts +103 -112
  20. package/config/agent/extensions/ponytail.ts +441 -0
  21. package/config/agent/extensions/review.ts +365 -164
  22. package/config/agent/extensions/tps.ts +87 -0
  23. package/config/agent/extensions/working-msg.ts +164 -51
  24. package/config/agent/help.md +0 -7
  25. package/config/agent/models.json +7 -7
  26. package/config/agent/settings.json +2 -1
  27. package/config/agent/skills/chroma/LICENSE +21 -0
  28. package/config/agent/skills/chroma/chroma-local/SKILL.md +165 -0
  29. package/config/agent/skills/chroma/chroma-local/cli.md +48 -0
  30. package/config/agent/skills/chroma/chroma-local/data-model.md +103 -0
  31. package/config/agent/skills/chroma/chroma-local/error-handling/python.md +166 -0
  32. package/config/agent/skills/chroma/chroma-local/error-handling/typescript.md +231 -0
  33. package/config/agent/skills/chroma/chroma-local/local-chroma/python.md +95 -0
  34. package/config/agent/skills/chroma/chroma-local/local-chroma/typescript.md +101 -0
  35. package/config/agent/skills/chroma/chroma-local/metadata/python.md +154 -0
  36. package/config/agent/skills/chroma/chroma-local/metadata/typescript.md +153 -0
  37. package/config/agent/skills/chroma/chroma-local/querying/python.md +147 -0
  38. package/config/agent/skills/chroma/chroma-local/querying/typescript.md +157 -0
  39. package/config/agent/skills/chroma/chroma-local/regex/python.md +69 -0
  40. package/config/agent/skills/chroma/chroma-local/regex/typescript.md +71 -0
  41. package/config/agent/skills/chroma/chroma-local/understanding-a-codebase.md +80 -0
  42. package/config/agent/skills/chroma/chroma-local/updating-deleting/python.md +159 -0
  43. package/config/agent/skills/chroma/chroma-local/updating-deleting/typescript.md +228 -0
  44. package/config/agent/skills/milvus-skill/LICENSE +202 -0
  45. package/config/agent/skills/milvus-skill/README.md +37 -0
  46. package/config/agent/skills/milvus-skill/SKILL.md +259 -0
  47. package/config/agent/skills/milvus-skill/references/collection.md +86 -0
  48. package/config/agent/skills/milvus-skill/references/database.md +24 -0
  49. package/config/agent/skills/milvus-skill/references/index.md +69 -0
  50. package/config/agent/skills/milvus-skill/references/partition.md +29 -0
  51. package/config/agent/skills/milvus-skill/references/patterns.md +150 -0
  52. package/config/agent/skills/milvus-skill/references/user-role.md +90 -0
  53. package/config/agent/skills/milvus-skill/references/vector.md +247 -0
  54. package/config/agent/skills/ponytail/SKILL.md +100 -0
  55. package/config/agent/skills/ponytail-audit/SKILL.md +40 -0
  56. package/config/agent/skills/ponytail-debt/SKILL.md +44 -0
  57. package/config/agent/skills/ponytail-help/SKILL.md +68 -0
  58. package/config/agent/skills/ponytail-review/SKILL.md +56 -0
  59. package/config/agent/skills/zread/README.md +115 -0
  60. package/config/agent/skills/zread/SKILL.md +103 -0
  61. package/config/agent/skills/zread/references/stdio-protocol.md +312 -0
  62. package/dist/banner.d.ts.map +1 -1
  63. package/dist/banner.js +8 -1
  64. package/dist/banner.js.map +1 -1
  65. package/dist/config-loader.d.ts.map +1 -1
  66. package/dist/config-loader.js +9 -2
  67. package/dist/config-loader.js.map +1 -1
  68. package/package.json +7 -7
  69. package/scripts/postinstall.js +58 -1
  70. package/scripts/regression/README.md +224 -0
  71. package/scripts/regression/cases/01-version-and-deps.sh +94 -0
  72. package/scripts/regression/cases/02-extension-loading.sh +130 -0
  73. package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
  74. package/scripts/regression/cases/04-prompt-stream.sh +130 -0
  75. package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
  76. package/scripts/regression/cases/06-tool-execution.sh +115 -0
  77. package/scripts/regression/cases/07-pi-new-features.sh +141 -0
  78. package/scripts/regression/lib/common.sh +287 -0
  79. package/scripts/regression/results/.last_pid +1 -0
  80. package/scripts/regression/results/.last_workdir +1 -0
  81. package/scripts/regression/results/02-extension-loading.log +18 -0
  82. package/scripts/regression/results/03-prompt-stream.log +16 -0
  83. package/scripts/regression/results/03-rpc-protocol.log +27 -0
  84. package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
  85. package/scripts/regression/results/05-tool-execution.log +8 -0
  86. package/scripts/regression/results/06-pi-new-features.log +29 -0
  87. package/scripts/regression/run-all.sh +187 -0
  88. package/config/agent/dangerous-patterns.json +0 -106
  89. package/config/agent/extensions/bash-check.ts +0 -227
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env bash
2
+ # 06-pi-new-features.sh
3
+ # 验证:pi 0.85+ 新功能在 aicode 端可用
4
+ #
5
+ # 重点:
6
+ # - clear_queue RPC(0.84.4+ 新增)
7
+ # - GPT-6 Astra 是否出现在 available_models
8
+ # - thinking 持久化(set_thinking_level 后 turn 间不丢)
9
+ # - usage.cacheWrite1h 字段(0.85+ 新增)
10
+ # - 持久化 Claude thinking effort(0.85+)
11
+ # - /thinking 命令选择器(0.84.3+)
12
+
13
+ set -uo pipefail
14
+ CURRENT_CASE="06-pi-new-features"
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ source "$SCRIPT_DIR/../lib/common.sh"
17
+
18
+ header "$CURRENT_CASE" "pi 0.85+ new features"
19
+
20
+ if ! spawn_rpc 90; then
21
+ fail "spawn aicode RPC" "进程启动失败"
22
+ summary
23
+ exit 1
24
+ fi
25
+ pass "spawn aicode RPC (pid=$REGRESSION_PID)"
26
+
27
+ # ----- Test 1: clear_queue RPC -----
28
+ header "$CURRENT_CASE" "clear_queue RPC (0.84.4+)"
29
+
30
+ send '{"type":"clear_queue","id":"n1"}'
31
+ sleep 0.5
32
+
33
+ if jq -e 'select(.id=="n1" and .type=="response" and .success==true)' "$REGRESSION_LOG" >/dev/null 2>&1; then
34
+ pass "clear_queue RPC 成功"
35
+
36
+ # 验证返回结构
37
+ if jq -e 'select(.id=="n1" and .type=="response") | .data | has("steering") and has("followUp")' "$REGRESSION_LOG" >/dev/null 2>&1; then
38
+ pass "clear_queue 返回 {steering,followUp} 字段"
39
+ else
40
+ skip "clear_queue 返回 {steering,followUp} 字段" "字段名可能不同(followUps/follow_ups)"
41
+ fi
42
+ else
43
+ err=$(jq -c 'select(.id=="n1" and .type=="response") | .error // ""' "$REGRESSION_LOG" 2>/dev/null | head -1)
44
+ if [[ -n "$err" && "$err" != '""' ]]; then
45
+ skip "clear_queue RPC" "pi < 0.84.4 不支持: $err"
46
+ else
47
+ fail "clear_queue RPC" "无 response"
48
+ fi
49
+ fi
50
+
51
+ # ----- Test 2: GPT-6 Astra 在 available_models -----
52
+ header "$CURRENT_CASE" "GPT-6 Astra (0.85.0+)"
53
+
54
+ send '{"type":"get_available_models","id":"n2"}'
55
+ sleep 1
56
+
57
+ models=$(jq -c 'select(.id=="n2" and .type=="response") | .data' "$REGRESSION_LOG" 2>/dev/null | head -1)
58
+ if [[ -n "$models" ]]; then
59
+ if echo "$models" | jq -e '.[] | select(.id | tostring | contains("gpt-6"))' >/dev/null 2>&1; then
60
+ pass "GPT-6 系列模型在 catalog 中"
61
+ gpt6=$(echo "$models" | jq -r '.[] | select(.id | tostring | contains("gpt-6")) | .id' 2>/dev/null | head -3)
62
+ echo " 发现: $(echo "$gpt6" | tr '\n' ' ')"
63
+ else
64
+ skip "GPT-6 Astra 在 catalog 中" "未找到 gpt-6* 模型(取决于 provider 配置)"
65
+ fi
66
+ else
67
+ fail "get_available_models 返回 data" "无响应"
68
+ fi
69
+
70
+ # ----- Test 3: set_thinking_level + 持久化(0.85.0)-----
71
+ header "$CURRENT_CASE" "thinking level persistence (0.85.0)"
72
+
73
+ # 设置 high
74
+ send '{"type":"set_thinking_level","level":"high","id":"n3a"}'
75
+ sleep 0.5
76
+ if jq -e 'select(.id=="n3a" and .type=="response" and .success==true)' "$REGRESSION_LOG" >/dev/null 2>&1; then
77
+ pass "set_thinking_level high 成功"
78
+ else
79
+ fail "set_thinking_level high" "无响应"
80
+ fi
81
+
82
+ # 再发一个 prompt 让它跑
83
+ send '{"type":"prompt","message":"say hi","id":"n3b"}'
84
+ sleep 8
85
+
86
+ # 等 prompt 完成
87
+ waited=0
88
+ while [[ $waited -lt 60 ]]; do
89
+ if jq -e 'select(.id=="n3b" and .type=="response")' "$REGRESSION_LOG" >/dev/null 2>&1; then
90
+ break
91
+ fi
92
+ sleep 1
93
+ waited=$((waited + 1))
94
+ done
95
+
96
+ # 查询 get_state — thinking level 应该还是 high
97
+ send '{"type":"get_state","id":"n3c"}'
98
+ sleep 0.5
99
+
100
+ state_level=$(jq -c 'select(.id=="n3c" and .type=="response") | .data.thinkingLevel // "missing"' "$REGRESSION_LOG" 2>/dev/null | head -1)
101
+ if [[ "$state_level" == '"high"' ]]; then
102
+ pass "thinking level 在 turn 间保持 high(持久化)"
103
+ else
104
+ if [[ "$state_level" == '"medium"' ]]; then
105
+ skip "thinking level 持久化" "回到 medium(0.85 之前行为)"
106
+ else
107
+ fail "thinking level 持久化" "got: $state_level"
108
+ fi
109
+ fi
110
+
111
+ # ----- Test 4: usage.cacheWrite1h 字段(0.85.0+)-----
112
+ header "$CURRENT_CASE" "usage.cacheWrite1h field (0.85.0+)"
113
+
114
+ # 看 turn_end 事件的 usage 是否包含 cacheWrite1h
115
+ has_cw1h=$(jq -c 'select(.type=="turn_end") | .message.usage | has("cacheWrite1h")' "$REGRESSION_LOG" 2>/dev/null | head -1)
116
+ if [[ "$has_cw1h" == "true" ]]; then
117
+ pass "turn_end.usage.cacheWrite1h 字段存在"
118
+ else
119
+ skip "turn_end.usage.cacheWrite1h 字段" "不存在(pi < 0.85.0 或 provider 不报告)"
120
+ fi
121
+
122
+ # ----- Test 5: get_available_thinking_levels -----
123
+ header "$CURRENT_CASE" "thinking levels list"
124
+
125
+ send '{"type":"get_available_thinking_levels","id":"n5"}'
126
+ sleep 0.5
127
+
128
+ if jq -e 'select(.id=="n5" and .type=="response" and .success==true)' "$REGRESSION_LOG" >/dev/null 2>&1; then
129
+ levels=$(jq -c 'select(.id=="n5" and .type=="response") | .data | length' "$REGRESSION_LOG" 2>/dev/null | head -1)
130
+ if [[ -n "$levels" && "$levels" -gt 0 ]]; then
131
+ pass "get_available_thinking_levels 返回 $levels 个级别"
132
+ else
133
+ fail "thinking levels 非空" "got: $levels"
134
+ fi
135
+ else
136
+ fail "get_available_thinking_levels" "无响应"
137
+ fi
138
+
139
+ cleanup_rpc
140
+ summary
141
+ exit $?
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env bash
2
+ # common.sh - 回归测试公共工具
3
+ # 用法:在 case 脚本开头 source "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh"
4
+
5
+ # 严格模式:未定义变量报错,但允许管道部分失败(grep 找不到时 exit 1)
6
+ set -uo pipefail
7
+
8
+ # 默认配置
9
+ : "${AICODE_BIN:=aicode}" # aicode 二进制路径
10
+ : "${AICODE_CONFIG:=$HOME/.aicode-cli}" # 配置目录
11
+ : "${AICODE_TIMEOUT:=30}" # RPC 测试默认超时(秒)
12
+ : "${REGRESSION_QUIET:=0}" # 1 = 减少输出
13
+
14
+ # 路径
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
16
+ RESULTS_DIR="$SCRIPT_DIR/results"
17
+ mkdir -p "$RESULTS_DIR"
18
+
19
+ # 颜色(仅 TTY 启用)
20
+ if [[ -t 1 ]] && [[ "${REGRESSION_QUIET:-0}" != "1" ]]; then
21
+ RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[0;33m'; BLUE=$'\033[0;34m'; CYAN=$'\033[0;36m'; DIM=$'\033[2m'; NC=$'\033[0m'
22
+ else
23
+ RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; DIM=''; NC=''
24
+ fi
25
+
26
+ # 计数器
27
+ PASS_COUNT=0
28
+ FAIL_COUNT=0
29
+ SKIP_COUNT=0
30
+ CURRENT_CASE="${CURRENT_CASE:-$(basename "${BASH_SOURCE[1]:-unknown}" .sh)}"
31
+
32
+ # ----- 输出 -----
33
+ header() {
34
+ [[ "${REGRESSION_QUIET:-0}" == "1" ]] && return
35
+ echo ""
36
+ echo "${CYAN}─── $CURRENT_CASE: $2 ───${NC}"
37
+ }
38
+
39
+ pass() {
40
+ local name="$1"
41
+ echo " ${GREEN}✓${NC} $name"
42
+ PASS_COUNT=$((PASS_COUNT + 1))
43
+ }
44
+
45
+ fail() {
46
+ local name="$1"
47
+ local reason="${2:-no reason}"
48
+ echo " ${RED}✗${NC} $name"
49
+ echo " ${DIM}└─ $reason${NC}"
50
+ FAIL_COUNT=$((FAIL_COUNT + 1))
51
+ }
52
+
53
+ skip() {
54
+ local name="$1"
55
+ local reason="${2:-no reason}"
56
+ echo " ${YELLOW}○${NC} $name ${DIM}(skipped: $reason)${NC}"
57
+ SKIP_COUNT=$((SKIP_COUNT + 1))
58
+ }
59
+
60
+ summary() {
61
+ local exit_code=0
62
+ echo ""
63
+ if [[ $FAIL_COUNT -eq 0 && $SKIP_COUNT -eq 0 ]]; then
64
+ echo "${GREEN}✓ $CURRENT_CASE: ALL PASS${NC} ${DIM}($PASS_COUNT checks)${NC}"
65
+ elif [[ $FAIL_COUNT -eq 0 ]]; then
66
+ echo "${YELLOW}△ $CURRENT_CASE: PASS with skips${NC} ${DIM}($PASS_COUNT pass, $SKIP_COUNT skip)${NC}"
67
+ else
68
+ echo "${RED}✗ $CURRENT_CASE: FAILED${NC} ${DIM}($FAIL_COUNT fail, $PASS_COUNT pass, $SKIP_COUNT skip)${NC}"
69
+ exit_code=1
70
+ fi
71
+ return $exit_code
72
+ }
73
+
74
+ # ----- RPC 进程管理 -----
75
+ #
76
+ # spawn_rpc [timeout_seconds]
77
+ # 启动 aicode -ne --mode rpc --no-session,stdin 从 fd 11 读,stdout/stderr 到 fd 12 写
78
+ # 用法见 cases/02-rpc-handshake.sh
79
+ #
80
+ spawn_rpc() {
81
+ local timeout="${1:-$AICODE_TIMEOUT}"
82
+ local skip_extensions="${2:-true}"
83
+
84
+ if ! command -v "$AICODE_BIN" >/dev/null 2>&1; then
85
+ echo "ERROR: $AICODE_BIN not found. Set AICODE_BIN env var to override." >&2
86
+ return 127
87
+ fi
88
+
89
+ # 临时配置必须在 $HOME 下,aicode-cli 的 config-loader.ts 安全检查
90
+ # 会拒绝非 home 目录并回退到 ~/.aicode-cli
91
+ # 用 home 下的隐藏目录避免污染用户配置
92
+ local workdir="$HOME/.aicode-regression-$$-$(date +%s%N | tail -c 8)"
93
+ mkdir -p "$workdir/agent/extensions"
94
+ local cfg="$workdir/config.json"
95
+ cat >"$cfg" <<EOF
96
+ {
97
+ "name": "aicode-regression",
98
+ "banner": "",
99
+ "themeColor": "#49bccf",
100
+ "configDir": "$(basename "$workdir")"
101
+ }
102
+ EOF
103
+
104
+ # 复用用户全局的 auth.json / models.json / settings.json + prompts / skills / extensions
105
+ # (避免重新登录/配置,且能让 pi 加载预置 prompts 和 skills)
106
+ # ~/.pi 通常是 ~/.aicode-cli 的 symlink
107
+ local user_agent_dir="$HOME/.aicode-cli/agent"
108
+ if [[ -d "$user_agent_dir" ]]; then
109
+ for f in auth.json models.json settings.json; do
110
+ [[ -f "$user_agent_dir/$f" ]] && cp "$user_agent_dir/$f" "$workdir/agent/$f" 2>/dev/null
111
+ done
112
+ # 复制 prompts/skills/extensions 目录(不覆盖本地)
113
+ for d in prompts skills extensions themes; do
114
+ if [[ -d "$user_agent_dir/$d" ]]; then
115
+ cp -R "$user_agent_dir/$d" "$workdir/agent/" 2>/dev/null
116
+ fi
117
+ done
118
+ fi
119
+
120
+ # 覆盖 provider(如果设置了 AICODE_REGRESSION_PROVIDER_*)
121
+ # 用于跨供应商测试:minimax / siliconflow-anthropic / siliconflow-openai 等
122
+ if [[ -n "${AICODE_REGRESSION_BASE_URL:-}" ]] && [[ -n "${AICODE_REGRESSION_API_KEY:-}" ]]; then
123
+ # 在 models.json 里加一个 anthropic 兼容 provider
124
+ local api_kind="${AICODE_REGRESSION_API_KIND:-anthropic-messages}"
125
+ local provider_id="${AICODE_REGRESSION_PROVIDER_ID:-regression}"
126
+ local model_id="${AICODE_REGRESSION_MODEL_ID:-claude-sonnet-4-5}"
127
+ python3 -c "
128
+ import json
129
+ p = '$workdir/agent/models.json'
130
+ try:
131
+ d = json.load(open(p))
132
+ except: d = {'providers': {}}
133
+ prov = d.setdefault('providers', {})
134
+ prov['$provider_id'] = {
135
+ 'baseUrl': '$AICODE_REGRESSION_BASE_URL',
136
+ 'apiKey': '$AICODE_REGRESSION_API_KEY',
137
+ 'api': '$api_kind',
138
+ 'models': [
139
+ {'id': '$model_id', 'name': '$model_id', 'contextWindow': 128000, 'maxTokens': 16384}
140
+ ]
141
+ }
142
+ json.dump(d, open(p, 'w'), indent=2)
143
+ " 2>/dev/null
144
+
145
+ # 修改 settings.json 的 defaultProvider
146
+ [[ -f "$workdir/agent/settings.json" ]] && python3 -c "
147
+ import json
148
+ p = '$workdir/agent/settings.json'
149
+ try:
150
+ d = json.load(open(p))
151
+ except: d = {}
152
+ d['defaultProvider'] = '$provider_id'
153
+ d['defaultModel'] = '$model_id'
154
+ json.dump(d, open(p, 'w'), indent=2)
155
+ " 2>/dev/null
156
+ fi
157
+
158
+ # 创建 stdin fifo(aicode 需要从 stdin 持续读取 RPC 命令)
159
+ mkfifo "$workdir/stdin.fifo" 2>/dev/null || true
160
+
161
+ # fd 11 = stdin (case 脚本可以 printf 到 &11)
162
+ # fd 12 = stdout+stderr 合并的 log(用于后续分析)
163
+ exec 11<>"$workdir/stdin.fifo"
164
+ exec 12>"$workdir/output.log"
165
+
166
+ # spawn 进程(默认 -ne 跳过扩展避免 conflict;要加载扩展传 false)
167
+ local ext_flag=""
168
+ if [[ "$skip_extensions" == "true" ]]; then
169
+ ext_flag="-ne"
170
+ fi
171
+
172
+ "$AICODE_BIN" $ext_flag --config "$workdir" --mode rpc --no-session \
173
+ <&11 >&12 2>&1 &
174
+ local pid=$!
175
+
176
+ echo "$workdir" > "$RESULTS_DIR/.last_workdir"
177
+ echo "$pid" > "$RESULTS_DIR/.last_pid"
178
+
179
+ # 主动发一个 get_state 触发首个 response,验证 RPC 通道已就绪
180
+ printf '{"type":"get_state","id":"ready_probe"}\n' >&11
181
+
182
+ # 等待 response 出现(最多 5 秒)
183
+ local ready=0
184
+ for _ in $(seq 1 50); do
185
+ if grep -q '"id":"ready_probe"' "$workdir/output.log" 2>/dev/null; then
186
+ ready=1; break
187
+ fi
188
+ sleep 0.1
189
+ done
190
+
191
+ if [[ $ready -ne 1 ]]; then
192
+ kill "$pid" 2>/dev/null
193
+ echo "ERROR: aicode RPC 启动超时(未收到 ready_probe 响应)" >&2
194
+ echo " log 前 20 行:$(head -20 "$workdir/output.log" 2>/dev/null | tr '\n' '|' | head -c 300)" >&2
195
+ return 1
196
+ fi
197
+
198
+ REGRESSION_PID="$pid"
199
+ REGRESSION_WORKDIR="$workdir"
200
+ REGRESSION_LOG="$workdir/output.log"
201
+ return 0
202
+ }
203
+
204
+ send() {
205
+ # send <json>
206
+ printf '%s\n' "$1" >&11
207
+ }
208
+
209
+ wait_for() {
210
+ # wait_for <jq-filter> [timeout_seconds]
211
+ local filter="$1"
212
+ local timeout="${2:-$AICODE_TIMEOUT}"
213
+ local jqbin="${JQBIN:-jq}"
214
+
215
+ if ! command -v "$jqbin" >/dev/null 2>&1; then
216
+ echo "ERROR: jq not found. brew install jq" >&2
217
+ return 127
218
+ fi
219
+
220
+ # 等到匹配事件出现
221
+ "$jqbin" -c "$filter" "$REGRESSION_LOG" 2>/dev/null | head -1
222
+ }
223
+
224
+ cleanup_rpc() {
225
+ # 关掉 stdin pipe + 杀进程 + 保留 log
226
+ [[ -n "${REGRESSION_PID:-}" ]] && kill "$REGRESSION_PID" 2>/dev/null
227
+ exec 11<&- 2>/dev/null
228
+ exec 12<&- 2>/dev/null
229
+
230
+ if [[ -n "${REGRESSION_LOG:-}" ]] && [[ -f "$REGRESSION_LOG" ]]; then
231
+ cp "$REGRESSION_LOG" "$RESULTS_DIR/${CURRENT_CASE}.log" 2>/dev/null
232
+ fi
233
+ [[ -n "${REGRESSION_WORKDIR:-}" ]] && rm -rf "$REGRESSION_WORKDIR" 2>/dev/null
234
+ }
235
+
236
+ trap_cleanup() {
237
+ cleanup_rpc
238
+ }
239
+
240
+ # ----- 断言辅助 -----
241
+
242
+ assert_eq() {
243
+ # assert_eq <name> <expected> <actual>
244
+ local name="$1" expected="$2" actual="$3"
245
+ if [[ "$expected" == "$actual" ]]; then
246
+ pass "$name"
247
+ else
248
+ fail "$name" "expected: $expected | got: $actual"
249
+ fi
250
+ }
251
+
252
+ assert_contains() {
253
+ # assert_contains <name> <substring> <haystack>
254
+ local name="$1" needle="$2" hay="$3"
255
+ if [[ "$hay" == *"$needle"* ]]; then
256
+ pass "$name"
257
+ else
258
+ fail "$name" "expected to contain: $needle | got: ${hay:0:200}"
259
+ fi
260
+ }
261
+
262
+ assert_event_field() {
263
+ # assert_event_field <name> <jq-filter> <field-equals-value>
264
+ # 例:assert_event_field "model 是 anthropic" 'select(.type=="message_start")' '.message.api' "anthropic-messages"
265
+ local name="$1" event_filter="$2" field="$3" expected="$4"
266
+ local jqbin="${JQBIN:-jq}"
267
+ local actual
268
+ actual=$("$jqbin" -c "$event_filter | $field // \"\"" "$REGRESSION_LOG" 2>/dev/null | head -1)
269
+ if [[ "$actual" == *"\"$expected\""* ]] || [[ "$actual" == "\"$expected\"" ]]; then
270
+ pass "$name"
271
+ else
272
+ fail "$name" "expected $field == '$expected' | got: $actual"
273
+ fi
274
+ }
275
+
276
+ assert_event_count() {
277
+ # assert_event_count <name> <jq-filter> <min-count>
278
+ local name="$1" filter="$2" min="$3"
279
+ local jqbin="${JQBIN:-jq}"
280
+ local count
281
+ count=$("$jqbin" -c "$filter" "$REGRESSION_LOG" 2>/dev/null | wc -l | tr -d ' ')
282
+ if [[ $count -ge $min ]]; then
283
+ pass "$name ($count ≥ $min)"
284
+ else
285
+ fail "$name" "expected ≥ $min matches | got: $count"
286
+ fi
287
+ }
@@ -0,0 +1 @@
1
+ 85916
@@ -0,0 +1 @@
1
+ /Users/jianjiang/.aicode-regression-85892-3270000