@ia-ccun/code-agent-cli 0.0.53 → 0.0.54-beta.3
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.
- package/bin/cli.js +97 -69
- package/config/agent/extensions/aicode-advisor.ts +187 -64
- package/config/agent/extensions/aicode-banner.ts +101 -0
- package/config/agent/extensions/aicode-btw.ts +619 -182
- package/config/agent/extensions/aicode-cd.ts +246 -63
- package/config/agent/extensions/aicode-pets.ts +0 -11
- package/config/agent/extensions/aicode-plan.ts +2 -0
- package/config/agent/extensions/aicode-safety-guard.ts +3 -0
- package/config/agent/extensions/aicode-tips.ts +1 -16
- package/config/agent/extensions/context-monitor.ts +30 -15
- package/config/agent/extensions/context.ts +132 -60
- package/config/agent/extensions/custom-footer.ts +0 -5
- package/config/agent/extensions/custom.ts +41 -29
- package/config/agent/extensions/error-monitor.ts +37 -33
- package/config/agent/extensions/goal.ts +8 -6
- package/config/agent/extensions/msg-debug.ts +103 -112
- package/config/agent/extensions/review.ts +365 -164
- package/config/agent/extensions/working-msg.ts +2 -4
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +8 -1
- package/dist/banner.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +9 -2
- package/dist/config-loader.js.map +1 -1
- package/package.json +6 -7
- package/scripts/regression/README.md +224 -0
- package/scripts/regression/cases/01-version-and-deps.sh +94 -0
- package/scripts/regression/cases/02-extension-loading.sh +130 -0
- package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
- package/scripts/regression/cases/04-prompt-stream.sh +130 -0
- package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
- package/scripts/regression/cases/06-tool-execution.sh +115 -0
- package/scripts/regression/cases/07-pi-new-features.sh +141 -0
- package/scripts/regression/lib/common.sh +287 -0
- package/scripts/regression/results/.last_pid +1 -0
- package/scripts/regression/results/.last_workdir +1 -0
- package/scripts/regression/results/02-extension-loading.log +21 -0
- package/scripts/regression/results/03-prompt-stream.log +16 -0
- package/scripts/regression/results/03-rpc-protocol.log +27 -0
- package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
- package/scripts/regression/results/05-tool-execution.log +8 -0
- package/scripts/regression/results/06-pi-new-features.log +27 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/bin/fd.exe +0 -0
- package/config/agent/bin/rg.exe +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 03-rpc-protocol.sh
|
|
3
|
+
# 验证:AicodeRpcClient.kt 实际用到的所有 RPC 命令 + 事件类型兼容性矩阵
|
|
4
|
+
#
|
|
5
|
+
# 这是 RPC 协议层的回归保护伞 — 任何 RPC 命令的字段格式变化都会在这里失败
|
|
6
|
+
#
|
|
7
|
+
# RPC 命令清单(来源:aicode-idea-plugin-swing/src/main/kotlin/com/nu/aicode/rpc/AicodeRpcClient.kt)
|
|
8
|
+
# 状态控制: prompt / steer / follow_up / abort / compact
|
|
9
|
+
# 查询: get_state / get_session_stats / get_commands
|
|
10
|
+
# get_available_models / get_available_thinking_levels
|
|
11
|
+
# get_messages / get_entries / get_tree
|
|
12
|
+
# 模型控制: set_model / cycle_model / set_model_provider
|
|
13
|
+
# set_thinking_level / cycle_thinking_level
|
|
14
|
+
# 工具: set_tools / bash
|
|
15
|
+
# 会话: new_session / switch_session / fork
|
|
16
|
+
# 扩展钩子: extension_ui_response
|
|
17
|
+
# 新增 (0.84.4+): clear_queue
|
|
18
|
+
#
|
|
19
|
+
# 事件类型清单(同上文件 AicodeStreamItem sealed class)
|
|
20
|
+
# message_update(assistantMessageEvent: text_delta/thinking_delta/thinking)
|
|
21
|
+
# turn_start / turn_end / message_start / message_end
|
|
22
|
+
# agent_start / agent_end / agent_settled
|
|
23
|
+
# tool_execution_start / tool_execution_update / tool_execution_end
|
|
24
|
+
# queue_update / auto_retry_end
|
|
25
|
+
|
|
26
|
+
set -uo pipefail
|
|
27
|
+
CURRENT_CASE="03-rpc-protocol"
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
30
|
+
|
|
31
|
+
header "$CURRENT_CASE" "RPC protocol compatibility matrix"
|
|
32
|
+
|
|
33
|
+
if ! spawn_rpc 90; then
|
|
34
|
+
fail "spawn aicode RPC" "进程启动失败"
|
|
35
|
+
summary
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
pass "spawn aicode RPC (pid=$REGRESSION_PID)"
|
|
39
|
+
|
|
40
|
+
# ----- Part 1: 所有 RPC 命令的兼容性 -----
|
|
41
|
+
header "$CURRENT_CASE" "part 1: RPC command compatibility"
|
|
42
|
+
|
|
43
|
+
# 每个命令: "type|payload|必需 response.success 字段|期望字段"
|
|
44
|
+
# 简化版:只验证 type 字段能被识别(success=true 即视为兼容)
|
|
45
|
+
# 失败类型:响应错误、字段缺失、命令不存在
|
|
46
|
+
# RPC 命令兼容性测试 — 格式: "type|extra_fields_json_object"
|
|
47
|
+
# extra_fields 是 type 之外的字段,会拼到 {"type":"X","id":"rpcN", extra_fields}
|
|
48
|
+
#
|
|
49
|
+
# 注意:只测 pi 核心协议命令。aicode-cli 自定义命令(set_model_provider、set_tools)
|
|
50
|
+
# 是在 extensions 里实现的,跳过 -ne 时不生效,单独在 case 02 中测试。
|
|
51
|
+
#
|
|
52
|
+
# set_thinking_level / cycle_thinking_level 在并发发送场景下响应时序不稳定
|
|
53
|
+
# (0.85.1 表现),交给 case 07 单独验证。
|
|
54
|
+
RPC_COMMANDS=(
|
|
55
|
+
# 类型查询(pi 核心,必现)
|
|
56
|
+
"get_state|"
|
|
57
|
+
"get_session_stats|"
|
|
58
|
+
"get_commands|"
|
|
59
|
+
"get_available_models|"
|
|
60
|
+
"get_available_thinking_levels|"
|
|
61
|
+
|
|
62
|
+
# 会话管理
|
|
63
|
+
"new_session|"
|
|
64
|
+
"get_messages|"
|
|
65
|
+
"get_entries|"
|
|
66
|
+
"get_tree|"
|
|
67
|
+
|
|
68
|
+
# 新功能(0.84.4+)
|
|
69
|
+
"clear_queue|"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
cmd_pass=0
|
|
73
|
+
cmd_fail=0
|
|
74
|
+
cmd_skip=0
|
|
75
|
+
idx=0
|
|
76
|
+
for spec in "${RPC_COMMANDS[@]}"; do
|
|
77
|
+
IFS='|' read -r cmd_type payload <<<"$spec"
|
|
78
|
+
idx=$((idx + 1))
|
|
79
|
+
|
|
80
|
+
req_id="rpc${idx}"
|
|
81
|
+
# 拼接合法 JSON:{"type":"X","id":"rpcN",<extra>}
|
|
82
|
+
if [[ -n "$payload" ]]; then
|
|
83
|
+
send "{\"type\":\"$cmd_type\",\"id\":\"$req_id\",$payload}"
|
|
84
|
+
else
|
|
85
|
+
send "{\"type\":\"$cmd_type\",\"id\":\"$req_id\"}"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# 等最多 10 秒响应(set_tools/set_thinking_level 可能需要时间)
|
|
89
|
+
local_log="$REGRESSION_LOG"
|
|
90
|
+
waited=0
|
|
91
|
+
while [[ $waited -lt 20 ]]; do
|
|
92
|
+
if jq -e "select(.id==\"$req_id\" and .type==\"response\")" "$local_log" >/dev/null 2>&1; then
|
|
93
|
+
break
|
|
94
|
+
fi
|
|
95
|
+
sleep 0.5
|
|
96
|
+
waited=$((waited + 1))
|
|
97
|
+
done
|
|
98
|
+
|
|
99
|
+
resp=$(jq -c "select(.id==\"$req_id\" and .type==\"response\")" "$local_log" 2>/dev/null | head -1)
|
|
100
|
+
if [[ -z "$resp" ]]; then
|
|
101
|
+
fail "RPC $cmd_type 返回 response" "10s 内无响应"
|
|
102
|
+
cmd_fail=$((cmd_fail + 1))
|
|
103
|
+
continue
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
success=$(echo "$resp" | jq -c '.success // false' 2>/dev/null)
|
|
107
|
+
if [[ "$success" == "true" ]]; then
|
|
108
|
+
pass "RPC $cmd_type success=true"
|
|
109
|
+
cmd_pass=$((cmd_pass + 1))
|
|
110
|
+
else
|
|
111
|
+
err=$(echo "$resp" | jq -c '.error // ""' 2>/dev/null)
|
|
112
|
+
# 区分"协议错误"和"业务错误"
|
|
113
|
+
if [[ -n "$err" ]] && echo "$err" | grep -qiE "Unknown command|not implemented|unsupported"; then
|
|
114
|
+
skip "RPC $cmd_type success=true" "pi 不支持: $err"
|
|
115
|
+
cmd_skip=$((cmd_skip + 1))
|
|
116
|
+
else
|
|
117
|
+
# 业务错误(如 fork 没 entryId)但协议 OK
|
|
118
|
+
if [[ "$success" == "false" ]] && [[ -n "$err" ]]; then
|
|
119
|
+
pass "RPC $cmd_type 协议 OK(业务错误: $err)"
|
|
120
|
+
cmd_pass=$((cmd_pass + 1))
|
|
121
|
+
else
|
|
122
|
+
fail "RPC $cmd_type 协议响应异常" "resp: $resp"
|
|
123
|
+
cmd_fail=$((cmd_fail + 1))
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
echo ""
|
|
130
|
+
echo " RPC 命令小计: $cmd_pass pass / $cmd_fail fail / $cmd_skip skip"
|
|
131
|
+
|
|
132
|
+
# ----- Part 2: 事件类型 + 字段格式(通过实际 prompt 触发)-----
|
|
133
|
+
header "$CURRENT_CASE" "part 2: event type + field schema"
|
|
134
|
+
|
|
135
|
+
# 发 prompt 触发完整事件流
|
|
136
|
+
send '{"type":"prompt","message":"just say hi","id":"ev1"}'
|
|
137
|
+
|
|
138
|
+
# 等 LLM 完成(以 agent_end 为信号,最长 120s)
|
|
139
|
+
waited=0
|
|
140
|
+
while [[ $waited -lt 120 ]]; do
|
|
141
|
+
# 检查两种结束信号:agent_end 或 agent_settled
|
|
142
|
+
if jq -e 'select(.type=="agent_end" or .type=="agent_settled")' "$REGRESSION_LOG" >/dev/null 2>&1; then
|
|
143
|
+
break
|
|
144
|
+
fi
|
|
145
|
+
sleep 1
|
|
146
|
+
waited=$((waited + 1))
|
|
147
|
+
done
|
|
148
|
+
|
|
149
|
+
if [[ $waited -ge 120 ]]; then
|
|
150
|
+
fail "LLM 在 120s 内完成" "超时"
|
|
151
|
+
else
|
|
152
|
+
echo " LLM 在 ${waited}s 内完成"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# 事件类型清单(每个都要出现至少 1 次)
|
|
156
|
+
EVENT_TYPES=(
|
|
157
|
+
"agent_start"
|
|
158
|
+
"turn_start"
|
|
159
|
+
"message_start"
|
|
160
|
+
"message_end"
|
|
161
|
+
"message_update"
|
|
162
|
+
"turn_end"
|
|
163
|
+
"agent_end"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
ev_pass=0
|
|
167
|
+
ev_fail=0
|
|
168
|
+
for ev in "${EVENT_TYPES[@]}"; do
|
|
169
|
+
if jq -e "select(.type==\"$ev\")" "$REGRESSION_LOG" >/dev/null 2>&1; then
|
|
170
|
+
pass "事件 $ev 出现"
|
|
171
|
+
ev_pass=$((ev_pass + 1))
|
|
172
|
+
else
|
|
173
|
+
fail "事件 $ev 出现" "60s 内未观察到"
|
|
174
|
+
ev_fail=$((ev_fail + 1))
|
|
175
|
+
fi
|
|
176
|
+
done
|
|
177
|
+
|
|
178
|
+
echo ""
|
|
179
|
+
echo " 事件类型小计: $ev_pass pass / $ev_fail fail"
|
|
180
|
+
|
|
181
|
+
# ----- Part 3: 关键字段 schema 兼容性 -----
|
|
182
|
+
header "$CURRENT_CASE" "part 3: event field schema"
|
|
183
|
+
|
|
184
|
+
# message_start.message.role 必须是 "user" 或 "assistant"
|
|
185
|
+
role_check=$(jq -c 'select(.type=="message_start") | .message.role // "missing"' "$REGRESSION_LOG" 2>/dev/null | sort -u | tr '\n' ' ')
|
|
186
|
+
echo " 观察到的 roles: $role_check"
|
|
187
|
+
if [[ "$role_check" == *"user"* && "$role_check" == *"assistant"* ]]; then
|
|
188
|
+
pass "message_start.message.role 包含 user + assistant"
|
|
189
|
+
else
|
|
190
|
+
fail "message_start.message.role 字段" "got: $role_check"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# turn_end.message.usage 字段
|
|
194
|
+
usage_keys=$(jq -c 'select(.type=="turn_end") | .message.usage | keys | join(",")' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
195
|
+
echo " turn_end.message.usage keys: $usage_keys"
|
|
196
|
+
for key in input output cost; do
|
|
197
|
+
if [[ "$usage_keys" == *"$key"* ]]; then
|
|
198
|
+
pass "turn_end.usage.$key 存在"
|
|
199
|
+
else
|
|
200
|
+
fail "turn_end.usage.$key 存在" "got: $usage_keys"
|
|
201
|
+
fi
|
|
202
|
+
done
|
|
203
|
+
|
|
204
|
+
# message_update.assistantMessageEvent 子类型必须出现
|
|
205
|
+
ame_subtypes=$(jq -r 'select(.type=="message_update") | .assistantMessageEvent.type // ""' "$REGRESSION_LOG" 2>/dev/null | sort -u | grep -v '^$' | tr '\n' ' ')
|
|
206
|
+
echo " assistantMessageEvent 子类型: $ame_subtypes"
|
|
207
|
+
for subtype in thinking_delta text_delta; do
|
|
208
|
+
if [[ "$ame_subtypes" == *"$subtype"* ]]; then
|
|
209
|
+
pass "assistantMessageEvent.$subtype 出现"
|
|
210
|
+
else
|
|
211
|
+
fail "assistantMessageEvent.$subtype 出现" "got: $ame_subtypes"
|
|
212
|
+
fi
|
|
213
|
+
done
|
|
214
|
+
|
|
215
|
+
# message_update.assistantMessageEvent.delta 字段
|
|
216
|
+
delta_check=$(jq -c 'select(.type=="message_update" and .assistantMessageEvent.type=="text_delta") | .assistantMessageEvent.delta // "missing"' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
217
|
+
if [[ "$delta_check" != '"missing"' && -n "$delta_check" ]]; then
|
|
218
|
+
pass "assistantMessageEvent.delta 字段存在"
|
|
219
|
+
else
|
|
220
|
+
fail "assistantMessageEvent.delta 字段存在" "got: $delta_check"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# turn_end 必须有 message 字段(不是 top-level usage)
|
|
224
|
+
turn_end_schema=$(jq -c 'select(.type=="turn_end") | has("message") and (.message | has("usage"))' "$REGRESSION_LOG" 2>/dev/null | sort -u | head -1)
|
|
225
|
+
if [[ "$turn_end_schema" == "true" ]]; then
|
|
226
|
+
pass "turn_end.message.usage 嵌套结构"
|
|
227
|
+
else
|
|
228
|
+
fail "turn_end.message.usage 嵌套结构" "可能 schema 漂移:$turn_end_schema"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# ----- Part 4: 计时关键字段(与 aicode-plugin-swing 状态计算相关)-----
|
|
232
|
+
header "$CURRENT_CASE" "part 4: timing fields"
|
|
233
|
+
|
|
234
|
+
# agent_end 可能含 willRetry 字段(aicode-plugin-swing 用它判断是否重试中)
|
|
235
|
+
agent_end_keys=$(jq -c 'select(.type=="agent_end") | keys | join(",")' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
236
|
+
echo " agent_end keys: $agent_end_keys"
|
|
237
|
+
if [[ "$agent_end_keys" == *"willRetry"* ]]; then
|
|
238
|
+
pass "agent_end.willRetry 字段存在"
|
|
239
|
+
else
|
|
240
|
+
skip "agent_end.willRetry 字段存在" "未出现(可能本轮不重试)"
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
# message_end.stopReason 必须是已知值(0.85+ 新增 "stop" 通用值 + rawStopReason 保留 provider 原值)
|
|
244
|
+
stop_reason=$(jq -c 'select(.type=="message_end" and .message.role=="assistant") | .message.stopReason // "missing"' "$REGRESSION_LOG" 2>/dev/null | sort -u | head -1)
|
|
245
|
+
case "$stop_reason" in
|
|
246
|
+
'"end_turn"'|'"tool_use"'|'"error"'|'"max_tokens"'|'"stop_sequence"'|'"aborted"'|'"stop"')
|
|
247
|
+
pass "stopReason 合法 ($stop_reason)"
|
|
248
|
+
;;
|
|
249
|
+
'"missing"')
|
|
250
|
+
fail "stopReason 字段" "未出现"
|
|
251
|
+
;;
|
|
252
|
+
*)
|
|
253
|
+
skip "stopReason 已知值" "新值: $stop_reason(需人工确认是否要更新 AicodeRpcClient.kt)"
|
|
254
|
+
;;
|
|
255
|
+
esac
|
|
256
|
+
|
|
257
|
+
cleanup_rpc
|
|
258
|
+
summary
|
|
259
|
+
exit $?
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 03-prompt-stream.sh
|
|
3
|
+
# 验证:prompt RPC 触发完整 message_update 事件流
|
|
4
|
+
# 核心回归项 — AicodeRpcClient.kt 的 streamPrompt() 完全依赖这套事件序列
|
|
5
|
+
#
|
|
6
|
+
# 关键事件序列(与 aicode-plugin-swing AicodeStreamItem 映射):
|
|
7
|
+
# 1. {"type":"response","command":"prompt","success":true} <- RPC 响应
|
|
8
|
+
# 2. {"type":"agent_start"}
|
|
9
|
+
# 3. {"type":"turn_start"}
|
|
10
|
+
# 4. {"type":"message_start",message:{role:"user",...}}
|
|
11
|
+
# 5. {"type":"message_end",message:{role:"user",...}}
|
|
12
|
+
# 6. {"type":"message_start",message:{role:"assistant",api,provider,...}}
|
|
13
|
+
# 7. N × {"type":"message_update",assistantMessageEvent:{type:"thinking_delta",delta:"..."}}
|
|
14
|
+
# 8. {"type":"message_update",assistantMessageEvent:{type:"thinking_end",content:"..."}}
|
|
15
|
+
# 9. {"type":"message_update",assistantMessageEvent:{type:"text_start",contentIndex:N}}
|
|
16
|
+
# 10. N × {"type":"message_update",assistantMessageEvent:{type:"text_delta",delta:"..."}}
|
|
17
|
+
# 11. {"type":"message_update",assistantMessageEvent:{type:"text_end",...}} (可能)
|
|
18
|
+
# 12. {"type":"message_end",message:{role:"assistant",stopReason:"end_turn"|"tool_use"|...}}
|
|
19
|
+
# 13. {"type":"turn_end",message:{...usage:{input,output,cost,...}}}
|
|
20
|
+
# 14. {"type":"agent_end"}
|
|
21
|
+
|
|
22
|
+
set -uo pipefail
|
|
23
|
+
CURRENT_CASE="03-prompt-stream"
|
|
24
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
26
|
+
|
|
27
|
+
header "$CURRENT_CASE" "prompt → message_update stream"
|
|
28
|
+
|
|
29
|
+
if ! spawn_rpc 60; then
|
|
30
|
+
fail "spawn aicode RPC" "进程启动失败"
|
|
31
|
+
summary
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
pass "spawn aicode RPC (pid=$REGRESSION_PID)"
|
|
35
|
+
|
|
36
|
+
# 发 prompt
|
|
37
|
+
send '{"type":"prompt","message":"reply with just: ok","id":"p1"}'
|
|
38
|
+
|
|
39
|
+
# 等 LLM 完成(agent_end 作为信号,最长 120s)
|
|
40
|
+
waited=0
|
|
41
|
+
while [[ $waited -lt 120 ]]; do
|
|
42
|
+
if jq -e 'select(.type=="agent_end" or .type=="agent_settled")' "$REGRESSION_LOG" >/dev/null 2>&1; then
|
|
43
|
+
break
|
|
44
|
+
fi
|
|
45
|
+
sleep 0.5
|
|
46
|
+
waited=$((waited + 1))
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
if [[ $waited -ge 120 ]]; then
|
|
50
|
+
fail "LLM 在 120s 内完成" "超时"
|
|
51
|
+
else
|
|
52
|
+
echo " LLM 在 ${waited}.0s 内完成"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# 验证事件序列
|
|
56
|
+
header "$CURRENT_CASE" "event sequence"
|
|
57
|
+
|
|
58
|
+
assert_event_count "agent_start 事件出现" \
|
|
59
|
+
'select(.type=="agent_start")' 1
|
|
60
|
+
|
|
61
|
+
assert_event_count "turn_start 事件出现" \
|
|
62
|
+
'select(.type=="turn_start")' 1
|
|
63
|
+
|
|
64
|
+
# user message_start / message_end 配对
|
|
65
|
+
assert_event_count "user message_start 出现" \
|
|
66
|
+
'select(.type=="message_start" and .message.role=="user")' 1
|
|
67
|
+
assert_event_count "user message_end 出现" \
|
|
68
|
+
'select(.type=="message_end" and .message.role=="user")' 1
|
|
69
|
+
|
|
70
|
+
# assistant message_start
|
|
71
|
+
assert_event_count "assistant message_start 出现" \
|
|
72
|
+
'select(.type=="message_start" and .message.role=="assistant")' 1
|
|
73
|
+
|
|
74
|
+
# assistantMessageEvent 子类型
|
|
75
|
+
header "$CURRENT_CASE" "assistantMessageEvent subtypes"
|
|
76
|
+
|
|
77
|
+
# 至少 1 个 thinking_start
|
|
78
|
+
assert_event_count "thinking_start 出现" \
|
|
79
|
+
'select(.type=="message_update" and .assistantMessageEvent.type=="thinking_start")' 1
|
|
80
|
+
|
|
81
|
+
# 至少 1 个 thinking_delta (流式)
|
|
82
|
+
assert_event_count "thinking_delta 至少 1 个" \
|
|
83
|
+
'select(.type=="message_update" and .assistantMessageEvent.type=="thinking_delta")' 1
|
|
84
|
+
|
|
85
|
+
# 至少 1 个 text_start
|
|
86
|
+
assert_event_count "text_start 出现" \
|
|
87
|
+
'select(.type=="message_update" and .assistantMessageEvent.type=="text_start")' 1
|
|
88
|
+
|
|
89
|
+
# 至少 1 个 text_delta
|
|
90
|
+
assert_event_count "text_delta 至少 1 个" \
|
|
91
|
+
'select(.type=="message_update" and .assistantMessageEvent.type=="text_delta")' 1
|
|
92
|
+
|
|
93
|
+
# assistant message_end
|
|
94
|
+
header "$CURRENT_CASE" "completion events"
|
|
95
|
+
assert_event_count "assistant message_end 出现" \
|
|
96
|
+
'select(.type=="message_end" and .message.role=="assistant")' 1
|
|
97
|
+
|
|
98
|
+
# stopReason 字段(end_turn / tool_use / error / stop 0.85+)
|
|
99
|
+
stop_reason=$(jq -c 'select(.type=="message_end" and .message.role=="assistant") | .message.stopReason // "missing"' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
100
|
+
case "$stop_reason" in
|
|
101
|
+
'"end_turn"'|'"tool_use"'|'"error"'|'"max_tokens"'|'"stop_sequence"'|'"aborted"'|'"stop"')
|
|
102
|
+
pass "assistant stopReason 是 $stop_reason"
|
|
103
|
+
;;
|
|
104
|
+
'"missing"')
|
|
105
|
+
fail "assistant stopReason 字段" "未出现"
|
|
106
|
+
;;
|
|
107
|
+
*)
|
|
108
|
+
fail "assistant stopReason 合法" "未知值: $stop_reason"
|
|
109
|
+
;;
|
|
110
|
+
esac
|
|
111
|
+
|
|
112
|
+
# turn_end 带 usage
|
|
113
|
+
assert_event_count "turn_end 事件出现" \
|
|
114
|
+
'select(.type=="turn_end")' 1
|
|
115
|
+
|
|
116
|
+
# usage 字段
|
|
117
|
+
usage_input=$(jq -c 'select(.type=="turn_end") | .message.usage.input // "missing"' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
118
|
+
if [[ "$usage_input" != '"missing"' && "$usage_input" =~ ^[0-9]+$ ]]; then
|
|
119
|
+
pass "turn_end.usage.input 是数字 ($usage_input)"
|
|
120
|
+
else
|
|
121
|
+
fail "turn_end.usage.input 是数字" "got: $usage_input"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# agent_end 收尾
|
|
125
|
+
assert_event_count "agent_end 事件出现" \
|
|
126
|
+
'select(.type=="agent_end")' 1
|
|
127
|
+
|
|
128
|
+
cleanup_rpc
|
|
129
|
+
summary
|
|
130
|
+
exit $?
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 04-slash-commands-and-skills.sh
|
|
3
|
+
# 验证:aicode-cli 预置的 Prompts 与 Skills 全部能通过 get_commands 拿到
|
|
4
|
+
#
|
|
5
|
+
# Prompts (config/agent/prompts/):
|
|
6
|
+
# /code-simplifier /feat /git-commit /init-project /workflow
|
|
7
|
+
# Skills (config/agent/skills/) — 通过 /skill: 触发:
|
|
8
|
+
# /skill:code-review /skill:commit /skill:pr /skill:refactor
|
|
9
|
+
# /skill:frontend-design /skill:brainstorming /skill:hello
|
|
10
|
+
# /skill:ponytail /skill:ponytail-audit /skill:ponytail-debt 等
|
|
11
|
+
#
|
|
12
|
+
# 这是 aicode-cli 的"开箱即用"卖点 — 升级 pi 后不能丢命令
|
|
13
|
+
|
|
14
|
+
set -uo pipefail
|
|
15
|
+
CURRENT_CASE="04-slash-commands-and-skills"
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
18
|
+
|
|
19
|
+
header "$CURRENT_CASE" "prompts & skills registration"
|
|
20
|
+
|
|
21
|
+
# pi 从 cwd 发现 prompts/skills/extensions。cd 到 aicode-cli 仓库根
|
|
22
|
+
# 让 pi 能加载 config/agent/{prompts,skills,extensions}
|
|
23
|
+
AICODE_CLI_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
24
|
+
cd "$AICODE_CLI_ROOT" || { fail "cd aicode root" "$AICODE_CLI_ROOT"; summary; exit 1; }
|
|
25
|
+
echo " cwd: $AICODE_CLI_ROOT"
|
|
26
|
+
|
|
27
|
+
if ! spawn_rpc 30 false; then
|
|
28
|
+
fail "spawn aicode RPC(带扩展)" "进程启动失败"
|
|
29
|
+
summary
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
pass "spawn aicode RPC(带扩展) (pid=$REGRESSION_PID)"
|
|
33
|
+
|
|
34
|
+
# ----- Test 1: 总命令数 -----
|
|
35
|
+
send '{"type":"get_commands","id":"c1"}'
|
|
36
|
+
sleep 1
|
|
37
|
+
|
|
38
|
+
cmds_json=$(jq -c 'select(.id=="c1" and .type=="response") | .data.commands' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
39
|
+
cmd_count=$(echo "$cmds_json" | jq -c 'length' 2>/dev/null)
|
|
40
|
+
|
|
41
|
+
if [[ -z "$cmd_count" || "$cmd_count" == "null" ]]; then
|
|
42
|
+
fail "get_commands 返回 commands 数组" "got: $cmds_json"
|
|
43
|
+
cleanup_rpc; summary; exit 1
|
|
44
|
+
fi
|
|
45
|
+
echo " total commands: $cmd_count"
|
|
46
|
+
|
|
47
|
+
if [[ "$cmd_count" -lt 20 ]]; then
|
|
48
|
+
fail "至少注册 20 个命令(5 prompt + 20+ skill + 内置)" "got: $cmd_count"
|
|
49
|
+
else
|
|
50
|
+
pass "注册 $cmd_count 个命令"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# ----- Test 2: 5 个预置 Prompts -----
|
|
54
|
+
header "$CURRENT_CASE" "predefined prompts (5)"
|
|
55
|
+
|
|
56
|
+
PROMPTS=("git-commit" "workflow" "feat" "code-simplifier" "init-project")
|
|
57
|
+
prompt_ok=0
|
|
58
|
+
for p in "${PROMPTS[@]}"; do
|
|
59
|
+
# pi 0.85+ 的 commands.name 不带 / 前缀(如 "git-commit"),用户调用时才加 /
|
|
60
|
+
if echo "$cmds_json" | jq -e --arg c "$p" '.[] | select(.name == $c)' >/dev/null 2>&1; then
|
|
61
|
+
pass "Prompt /$p 注册"
|
|
62
|
+
prompt_ok=$((prompt_ok + 1))
|
|
63
|
+
else
|
|
64
|
+
fail "Prompt /$p 注册" "未在 commands 中"
|
|
65
|
+
fi
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
# ----- Test 3: 关键 Skills -----
|
|
69
|
+
header "$CURRENT_CASE" "predefined skills (sample)"
|
|
70
|
+
|
|
71
|
+
# 测试核心 skills(不强求全部,验证机制)
|
|
72
|
+
SKILLS=("skill:code-review" "skill:commit" "skill:pr" "skill:refactor" "skill:frontend-design" "skill:brainstorming" "skill:hello")
|
|
73
|
+
skill_ok=0
|
|
74
|
+
for s in "${SKILLS[@]}"; do
|
|
75
|
+
# pi 0.85+ skills 用 "skill:xxx" 作为 name(含 skill: 前缀)
|
|
76
|
+
if echo "$cmds_json" | jq -e --arg c "$s" '.[] | select(.name == $c)' >/dev/null 2>&1; then
|
|
77
|
+
pass "Skill /$s 注册"
|
|
78
|
+
skill_ok=$((skill_ok + 1))
|
|
79
|
+
else
|
|
80
|
+
fail "Skill /$s 注册" "未在 commands 中"
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
# 至少 60% 核心 skills 要在
|
|
85
|
+
min_required=$(( ${#SKILLS[@]} * 60 / 100 ))
|
|
86
|
+
if [[ $skill_ok -ge $min_required ]]; then
|
|
87
|
+
pass "核心 skills 注册率 ≥ 60% ($skill_ok/${#SKILLS[@]})"
|
|
88
|
+
else
|
|
89
|
+
fail "核心 skills 注册率 ≥ 60%" "got: $skill_ok/${#SKILLS[@]}"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# ----- Test 4: 每条 command 必须有 name + description -----
|
|
93
|
+
header "$CURRENT_CASE" "command schema"
|
|
94
|
+
|
|
95
|
+
# 抽 3 条 command 看字段
|
|
96
|
+
sample=$(echo "$cmds_json" | jq -c '.[0:3]' 2>/dev/null)
|
|
97
|
+
if [[ -n "$sample" ]]; then
|
|
98
|
+
echo " sample commands:"
|
|
99
|
+
echo "$sample" | jq -r '.[] | " - \(.name // "?") | \(.description // "(no description)")"' 2>/dev/null | head -3
|
|
100
|
+
|
|
101
|
+
# 每条都有 name
|
|
102
|
+
if echo "$cmds_json" | jq -e 'all(.[]; has("name"))' >/dev/null 2>&1; then
|
|
103
|
+
pass "所有 command 都有 name 字段"
|
|
104
|
+
else
|
|
105
|
+
fail "所有 command 都有 name 字段" "存在缺失 name 的 command"
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# ----- Test 5: 内置命令(/model /settings /clear 等)也在 -----
|
|
110
|
+
header "$CURRENT_CASE" "built-in commands"
|
|
111
|
+
|
|
112
|
+
# RPC 模式下,pi 0.85.1 的内置命令(/model /settings 等)不通过 get_commands 返回
|
|
113
|
+
# 它们是内置到 TUI 的,只在交互模式里出现。RPC 只返回 prompts/skills/extensions。
|
|
114
|
+
# 跳过此检查,备注到 README。
|
|
115
|
+
skip "内置命令 ≥ 5 个" "RPC 模式不返回内置命令(仅交互模式可见)"
|
|
116
|
+
|
|
117
|
+
cleanup_rpc
|
|
118
|
+
summary
|
|
119
|
+
exit $?
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 05-tool-execution.sh
|
|
3
|
+
# 验证:tool_execution_* 事件流(AicodeRpcClient.kt 的 ToolStart/ToolUpdate/ToolEnd 映射)
|
|
4
|
+
#
|
|
5
|
+
# 关键事件序列:
|
|
6
|
+
# 1. {"type":"tool_execution_start",toolCallId,toolName,args}
|
|
7
|
+
# 2. N × {"type":"tool_execution_update",toolCallId,partialResult:{content:[{text:"..."}]}}
|
|
8
|
+
# 3. {"type":"tool_execution_end",toolCallId,result:{content:[{text:"..."}]},isError:"false"|"true"}
|
|
9
|
+
#
|
|
10
|
+
# 0.84.3 修复了 toolcall_start 缺 id/name 的 bug,所以 toolCallId 字段必须存在
|
|
11
|
+
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
CURRENT_CASE="05-tool-execution"
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
16
|
+
|
|
17
|
+
header "$CURRENT_CASE" "tool_execution event flow"
|
|
18
|
+
|
|
19
|
+
if ! spawn_rpc 120; then
|
|
20
|
+
fail "spawn aicode RPC" "进程启动失败"
|
|
21
|
+
summary
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
pass "spawn aicode RPC (pid=$REGRESSION_PID)"
|
|
25
|
+
|
|
26
|
+
# 触发 read:让模型读取一个已知文件
|
|
27
|
+
TEST_FILE="/etc/hostname"
|
|
28
|
+
send "{\"type\":\"prompt\",\"message\":\"use read tool to read /etc/hostname\",\"id\":\"t1\"}"
|
|
29
|
+
|
|
30
|
+
# 等 turn 完成(最长 90s)
|
|
31
|
+
header "$CURRENT_CASE" "waiting for turn"
|
|
32
|
+
|
|
33
|
+
waited=0
|
|
34
|
+
while [[ $waited -lt 90 ]]; do
|
|
35
|
+
if jq -e 'select(.id=="t1" and .type=="response")' "$REGRESSION_LOG" >/dev/null 2>&1; then
|
|
36
|
+
break
|
|
37
|
+
fi
|
|
38
|
+
sleep 1
|
|
39
|
+
waited=$((waited + 1))
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
if ! jq -e 'select(.id=="t1" and .type=="response")' "$REGRESSION_LOG" >/dev/null 2>&1; then
|
|
43
|
+
fail "prompt 在 90s 内完成" "超时"
|
|
44
|
+
cleanup_rpc; summary; exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# 验证 tool 事件
|
|
48
|
+
header "$CURRENT_CASE" "tool_execution events"
|
|
49
|
+
|
|
50
|
+
start_count=$(jq -c 'select(.type=="tool_execution_start")' "$REGRESSION_LOG" 2>/dev/null | wc -l | tr -d ' ')
|
|
51
|
+
end_count=$(jq -c 'select(.type=="tool_execution_end")' "$REGRESSION_LOG" 2>/dev/null | wc -l | tr -d ' ')
|
|
52
|
+
|
|
53
|
+
echo " tool_execution_start: $start_count"
|
|
54
|
+
echo " tool_execution_end: $end_count"
|
|
55
|
+
|
|
56
|
+
if [[ $start_count -gt 0 ]]; then
|
|
57
|
+
pass "tool_execution_start 出现 ($start_count 个)"
|
|
58
|
+
|
|
59
|
+
# 0.84.3 修复:toolCallId 必现
|
|
60
|
+
tool_call_id=$(jq -c 'select(.type=="tool_execution_start") | .toolCallId // "missing"' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
61
|
+
if [[ "$tool_call_id" != '"missing"' && "$tool_call_id" != '""' && -n "$tool_call_id" ]]; then
|
|
62
|
+
pass "tool_execution_start.toolCallId 存在 (0.84.3 修复点)"
|
|
63
|
+
else
|
|
64
|
+
fail "tool_execution_start.toolCallId 存在" "got: $tool_call_id"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# 0.84.3 修复:toolName 必现
|
|
68
|
+
tool_name=$(jq -c 'select(.type=="tool_execution_start") | .toolName // "missing"' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
69
|
+
if [[ "$tool_name" != '"missing"' && "$tool_name" != '""' && -n "$tool_name" ]]; then
|
|
70
|
+
pass "tool_execution_start.toolName 存在 (0.84.3 修复点) = $tool_name"
|
|
71
|
+
else
|
|
72
|
+
fail "tool_execution_start.toolName 存在" "got: $tool_name"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# args 字段
|
|
76
|
+
args=$(jq -c 'select(.type=="tool_execution_start") | .args // "missing" | type' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
77
|
+
if [[ "$args" == '"object"' ]]; then
|
|
78
|
+
pass "tool_execution_start.args 是 object"
|
|
79
|
+
else
|
|
80
|
+
fail "tool_execution_start.args 是 object" "got type: $args"
|
|
81
|
+
fi
|
|
82
|
+
else
|
|
83
|
+
skip "tool_execution_start 出现" "模型没调用工具"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
if [[ $end_count -gt 0 ]]; then
|
|
87
|
+
pass "tool_execution_end 出现 ($end_count 个)"
|
|
88
|
+
|
|
89
|
+
# isError 字段
|
|
90
|
+
is_error=$(jq -c 'select(.type=="tool_execution_end") | .isError // "missing"' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
91
|
+
if [[ "$is_error" == '"false"' || "$is_error" == '"true"' ]]; then
|
|
92
|
+
pass "tool_execution_end.isError 是 \"false\"|\"true\""
|
|
93
|
+
else
|
|
94
|
+
fail "tool_execution_end.isError 格式" "got: $is_error"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# result.content 数组
|
|
98
|
+
result_type=$(jq -c 'select(.type=="tool_execution_end") | .result.content | type' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
99
|
+
if [[ "$result_type" == '"array"' ]]; then
|
|
100
|
+
pass "tool_execution_end.result.content 是数组"
|
|
101
|
+
else
|
|
102
|
+
fail "tool_execution_end.result.content 是数组" "got: $result_type"
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# start 与 end 配对
|
|
107
|
+
if [[ $start_count -gt 0 && $start_count -eq $end_count ]]; then
|
|
108
|
+
pass "tool_execution_start 与 tool_execution_end 配对 ($start_count == $end_count)"
|
|
109
|
+
elif [[ $start_count -gt 0 ]]; then
|
|
110
|
+
fail "tool_execution_start 与 tool_execution_end 配对" "$start_count start vs $end_count end"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
cleanup_rpc
|
|
114
|
+
summary
|
|
115
|
+
exit $?
|