@ia-ccun/code-agent-cli 0.0.52 → 0.0.53-beta.1
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 +6 -2
- package/config/agent/bin/README.md +102 -0
- package/config/agent/extensions/aicode-advisor.ts +13 -0
- package/config/agent/extensions/aicode-btw.ts +66 -14
- package/config/agent/extensions/aicode-cd.ts +834 -0
- package/config/agent/extensions/aicode-grillMe.ts +198 -0
- package/config/agent/extensions/aicode-pets.ts +362 -0
- package/config/agent/extensions/aicode-safety-guard.ts +552 -0
- package/config/agent/extensions/aicode-tips.ts +70 -16
- package/config/agent/extensions/aicode-todo-progress.ts +547 -0
- package/config/agent/extensions/custom.ts +93 -95
- package/config/agent/extensions/error-monitor.ts +1 -18
- package/config/agent/extensions/ponytail.ts +441 -0
- package/config/agent/extensions/tps.ts +87 -0
- package/config/agent/extensions/working-msg.ts +165 -50
- package/config/agent/help.md +0 -7
- package/config/agent/models.json +7 -7
- package/config/agent/settings.json +2 -1
- package/config/agent/skills/chroma/LICENSE +21 -0
- package/config/agent/skills/chroma/chroma-local/SKILL.md +165 -0
- package/config/agent/skills/chroma/chroma-local/cli.md +48 -0
- package/config/agent/skills/chroma/chroma-local/data-model.md +103 -0
- package/config/agent/skills/chroma/chroma-local/error-handling/python.md +166 -0
- package/config/agent/skills/chroma/chroma-local/error-handling/typescript.md +231 -0
- package/config/agent/skills/chroma/chroma-local/local-chroma/python.md +95 -0
- package/config/agent/skills/chroma/chroma-local/local-chroma/typescript.md +101 -0
- package/config/agent/skills/chroma/chroma-local/metadata/python.md +154 -0
- package/config/agent/skills/chroma/chroma-local/metadata/typescript.md +153 -0
- package/config/agent/skills/chroma/chroma-local/querying/python.md +147 -0
- package/config/agent/skills/chroma/chroma-local/querying/typescript.md +157 -0
- package/config/agent/skills/chroma/chroma-local/regex/python.md +69 -0
- package/config/agent/skills/chroma/chroma-local/regex/typescript.md +71 -0
- package/config/agent/skills/chroma/chroma-local/understanding-a-codebase.md +80 -0
- package/config/agent/skills/chroma/chroma-local/updating-deleting/python.md +159 -0
- package/config/agent/skills/chroma/chroma-local/updating-deleting/typescript.md +228 -0
- package/config/agent/skills/milvus-skill/LICENSE +202 -0
- package/config/agent/skills/milvus-skill/README.md +37 -0
- package/config/agent/skills/milvus-skill/SKILL.md +259 -0
- package/config/agent/skills/milvus-skill/references/collection.md +86 -0
- package/config/agent/skills/milvus-skill/references/database.md +24 -0
- package/config/agent/skills/milvus-skill/references/index.md +69 -0
- package/config/agent/skills/milvus-skill/references/partition.md +29 -0
- package/config/agent/skills/milvus-skill/references/patterns.md +150 -0
- package/config/agent/skills/milvus-skill/references/user-role.md +90 -0
- package/config/agent/skills/milvus-skill/references/vector.md +247 -0
- package/config/agent/skills/ponytail/SKILL.md +100 -0
- package/config/agent/skills/ponytail-audit/SKILL.md +40 -0
- package/config/agent/skills/ponytail-debt/SKILL.md +44 -0
- package/config/agent/skills/ponytail-help/SKILL.md +68 -0
- package/config/agent/skills/ponytail-review/SKILL.md +56 -0
- package/config/agent/skills/zread/README.md +115 -0
- package/config/agent/skills/zread/SKILL.md +103 -0
- package/config/agent/skills/zread/references/stdio-protocol.md +312 -0
- package/package.json +7 -6
- package/scripts/postinstall.js +58 -1
- 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 +29 -0
- package/scripts/regression/results/03-prompt-stream.log +25 -0
- package/scripts/regression/results/03-rpc-protocol.log +38 -0
- package/scripts/regression/results/04-slash-commands-and-skills.log +10 -0
- package/scripts/regression/results/05-tool-execution.log +6 -0
- package/scripts/regression/results/06-pi-new-features.log +33 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/dangerous-patterns.json +0 -106
- package/config/agent/extensions/bash-check.ts +0 -227
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 01-version-and-deps.sh
|
|
3
|
+
# 验证:aicode 二进制版本 + 内嵌 pi 包版本
|
|
4
|
+
# 这是回归测试的入口,确认我们用的版本符合预期
|
|
5
|
+
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
CURRENT_CASE="01-version-and-deps"
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
10
|
+
|
|
11
|
+
header "$CURRENT_CASE" "version & pi dependencies"
|
|
12
|
+
|
|
13
|
+
# ----- Test 1: aicode -v 输出 -----
|
|
14
|
+
header "$CURRENT_CASE" "aicode binary"
|
|
15
|
+
if ! command -v "$AICODE_BIN" >/dev/null 2>&1; then
|
|
16
|
+
fail "aicode 二进制存在" "未找到 $AICODE_BIN,设 AICODE_BIN 环境变量"
|
|
17
|
+
summary
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
pass "aicode 二进制存在 ($AICODE_BIN)"
|
|
21
|
+
|
|
22
|
+
VERSION=$("$AICODE_BIN" -v 2>&1 | head -1)
|
|
23
|
+
echo " version: $VERSION"
|
|
24
|
+
|
|
25
|
+
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9.]+)?$ ]]; then
|
|
26
|
+
pass "版本号格式合规 ($VERSION)"
|
|
27
|
+
else
|
|
28
|
+
fail "版本号格式合规" "got: $VERSION"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# ----- Test 2: 内嵌 pi 包版本 -----
|
|
32
|
+
header "$CURRENT_CASE" "pi package dependencies"
|
|
33
|
+
|
|
34
|
+
# 找到 aicode 安装路径里的 package.json
|
|
35
|
+
AICODE_PATH=$(command -v "$AICODE_BIN")
|
|
36
|
+
# 解析 symlink
|
|
37
|
+
if [[ -L "$AICODE_PATH" ]]; then
|
|
38
|
+
AICODE_PATH=$(readlink "$AICODE_PATH")
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# 找 package.json
|
|
42
|
+
RESOLVED_PATH=$(node -e "
|
|
43
|
+
const path = require('path');
|
|
44
|
+
const fs = require('fs');
|
|
45
|
+
let p = '$AICODE_PATH';
|
|
46
|
+
// aicode -> ../lib/node_modules/@ia-ccun/code-agent-cli/bin/cli.js
|
|
47
|
+
if (p.includes('/lib/node_modules/')) {
|
|
48
|
+
p = p.split('/lib/node_modules/')[0] + '/lib/node_modules/@ia-ccun/code-agent-cli';
|
|
49
|
+
} else if (p.includes('/.bin/')) {
|
|
50
|
+
p = path.join(path.dirname(p), '..', 'lib', 'node_modules', '@ia-ccun', 'code-agent-cli');
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(p, 'package.json'), 'utf8'));
|
|
54
|
+
console.log(p);
|
|
55
|
+
console.log(JSON.stringify(pkg.dependencies || {}));
|
|
56
|
+
} catch (e) { console.error(e.message); process.exit(1); }
|
|
57
|
+
" 2>&1) || {
|
|
58
|
+
fail "解析 aicode 安装路径" "node 解析失败"
|
|
59
|
+
summary
|
|
60
|
+
exit 1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
AICODE_ROOT=$(echo "$RESOLVED_PATH" | head -1)
|
|
64
|
+
DEPS_JSON=$(echo "$RESOLVED_PATH" | tail -1)
|
|
65
|
+
echo " install path: $AICODE_ROOT"
|
|
66
|
+
echo " declared deps: $DEPS_JSON"
|
|
67
|
+
|
|
68
|
+
# 检查 4 个核心 pi 包
|
|
69
|
+
for pkg in @earendil-works/pi-agent-core @earendil-works/pi-ai @earendil-works/pi-coding-agent @earendil-works/pi-tui; do
|
|
70
|
+
declared=$(echo "$DEPS_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$pkg','MISSING'))" 2>/dev/null)
|
|
71
|
+
if [[ "$declared" == "MISSING" || -z "$declared" ]]; then
|
|
72
|
+
fail "$pkg 已在 dependencies 声明" "declared: $declared"
|
|
73
|
+
continue
|
|
74
|
+
fi
|
|
75
|
+
pass "$pkg declared as $declared"
|
|
76
|
+
|
|
77
|
+
# 检查实际安装版本
|
|
78
|
+
actual=$("$AICODE_ROOT/node_modules/$pkg/package.json" -e "console.log(require('$AICODE_ROOT/node_modules/$pkg/package.json').version)" 2>/dev/null)
|
|
79
|
+
if [[ -n "$actual" ]]; then
|
|
80
|
+
# 解析 semver 主要版本
|
|
81
|
+
declared_major=$(echo "$declared" | sed -E 's/^[^0-9]*([0-9]+).*/\1/')
|
|
82
|
+
actual_major=$(echo "$actual" | sed -E 's/^[^0-9]*([0-9]+).*/\1/')
|
|
83
|
+
if [[ "$declared_major" == "$actual_major" ]]; then
|
|
84
|
+
pass "$pkg 安装版本 $actual 满足 ^$declared_major"
|
|
85
|
+
else
|
|
86
|
+
fail "$pkg 主版本一致" "declared major=$declared_major | actual=$actual"
|
|
87
|
+
fi
|
|
88
|
+
else
|
|
89
|
+
fail "$pkg 实际安装版本" "无法读取 $AICODE_ROOT/node_modules/$pkg/package.json"
|
|
90
|
+
fi
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
summary
|
|
94
|
+
exit $?
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 02-extension-loading.sh
|
|
3
|
+
# 验证:aicode-cli 自带的 20 个 TypeScript 扩展在 RPC 启动时全部成功加载
|
|
4
|
+
#
|
|
5
|
+
# 这是 aicode-cli 的核心差异化能力:
|
|
6
|
+
# - aicode-btw / aicode-plan / aicode-advisor / aicode-cd / aicode-grillMe
|
|
7
|
+
# - aicode-safety-guard / aicode-todo-progress / aicode-tips / aicode-pets
|
|
8
|
+
# - context-monitor / context / error-monitor / working-msg / tps
|
|
9
|
+
# - goal / review / ponytail / custom / custom-footer / msg-debug
|
|
10
|
+
#
|
|
11
|
+
# 升级 pi 后扩展 API 可能微调,必须验证:
|
|
12
|
+
# 1. 启动时没有任何 "Failed to load extension" 错误
|
|
13
|
+
# 2. 没有任何工具名冲突(conflict error)
|
|
14
|
+
# 3. 至少 N 个 aicode-* 扩展在 .agents.md 或 get_commands 中可见
|
|
15
|
+
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
CURRENT_CASE="02-extension-loading"
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
20
|
+
|
|
21
|
+
header "$CURRENT_CASE" "aicode custom extensions"
|
|
22
|
+
|
|
23
|
+
if ! spawn_rpc; then
|
|
24
|
+
fail "spawn aicode RPC(带扩展)" "进程启动失败"
|
|
25
|
+
summary
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
pass "spawn aicode RPC (pid=$REGRESSION_PID)"
|
|
29
|
+
|
|
30
|
+
# ----- Test 1: 启动日志中无扩展加载失败 -----
|
|
31
|
+
header "$CURRENT_CASE" "extension load errors"
|
|
32
|
+
|
|
33
|
+
# 把 stderr/stdout 全捕获到 log(-ne 关闭扩展 → 我们用普通模式)
|
|
34
|
+
cleanup_rpc
|
|
35
|
+
|
|
36
|
+
# 重启:不用 -ne,让所有扩展都加载
|
|
37
|
+
WORKDIR=$(mktemp -d -t aicode-ext.XXXXXX)
|
|
38
|
+
cat >"$WORKDIR/config.json" <<EOF
|
|
39
|
+
{"name":"aicode-test","banner":"","themeColor":"#49bccf","configDir":"$(basename "$WORKDIR")"}
|
|
40
|
+
EOF
|
|
41
|
+
|
|
42
|
+
LOG="$RESULTS_DIR/${CURRENT_CASE}.log"
|
|
43
|
+
: >"$LOG"
|
|
44
|
+
|
|
45
|
+
"$AICODE_BIN" --config "$WORKDIR" --mode rpc --no-session \
|
|
46
|
+
< /dev/null >"$LOG" 2>&1 &
|
|
47
|
+
EXT_PID=$!
|
|
48
|
+
|
|
49
|
+
# 等启动完成(5s 应该够)
|
|
50
|
+
sleep 5
|
|
51
|
+
kill "$EXT_PID" 2>/dev/null
|
|
52
|
+
wait "$EXT_PID" 2>/dev/null
|
|
53
|
+
|
|
54
|
+
# 错误关键字扫描
|
|
55
|
+
echo " scanning log for extension errors..."
|
|
56
|
+
|
|
57
|
+
fail_count=0
|
|
58
|
+
for pattern in "Failed to load extension" "Tool.*conflicts with" "Cannot find module" "SyntaxError" "TypeError.*Extension"; do
|
|
59
|
+
matches=$(grep -cE "$pattern" "$LOG" 2>/dev/null || echo 0)
|
|
60
|
+
if [[ "$matches" -gt 0 ]]; then
|
|
61
|
+
fail "无 $pattern 错误" "found $matches matches"
|
|
62
|
+
grep -E "$pattern" "$LOG" | head -3 | sed 's/^/ /'
|
|
63
|
+
fail_count=$((fail_count + matches))
|
|
64
|
+
else
|
|
65
|
+
pass "无 '$pattern' 错误"
|
|
66
|
+
fi
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
# ----- Test 2: get_commands 包含 aicode- 自定义命令 -----
|
|
70
|
+
# 不带 -ne 启动,确认扩展注册的 slash commands 也可见
|
|
71
|
+
header "$CURRENT_CASE" "aicode custom slash commands"
|
|
72
|
+
|
|
73
|
+
spawn_rpc 2>/dev/null || {
|
|
74
|
+
fail "second spawn" "进程启动失败"
|
|
75
|
+
summary; exit 1
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
send '{"type":"get_commands","id":"e1"}'
|
|
79
|
+
sleep 1
|
|
80
|
+
|
|
81
|
+
cmds_json=$(jq -c 'select(.id=="e1" and .type=="response") | .data.commands' "$REGRESSION_LOG" 2>/dev/null | head -1)
|
|
82
|
+
cmd_count=$(echo "$cmds_json" | jq -c 'length' 2>/dev/null)
|
|
83
|
+
echo " registered commands: $cmd_count"
|
|
84
|
+
|
|
85
|
+
if [[ -z "$cmd_count" || "$cmd_count" == "null" || "$cmd_count" -lt 5 ]]; then
|
|
86
|
+
fail "至少注册 5 个命令" "got: $cmd_count"
|
|
87
|
+
else
|
|
88
|
+
pass "注册 $cmd_count 个 slash commands"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# 关键 aicode 自定义命令(在 /llama /model /settings 等内置之外的 aicode-*)
|
|
92
|
+
AICODE_CMDS=("/aicode-cd" "/grill-me" "/bugs" "/goal" "/plan" "/workflow" "/git-commit" "/feat" "/init-project" "/code-simplifier")
|
|
93
|
+
for cmd in "${AICODE_CMDS[@]}"; do
|
|
94
|
+
if echo "$cmds_json" | jq -e --arg c "$cmd" '.[] | select(.name == $c)' >/dev/null 2>&1; then
|
|
95
|
+
pass "扩展命令 $cmd 已注册"
|
|
96
|
+
else
|
|
97
|
+
# 可能该命令需要其他条件才注册,标记 skip
|
|
98
|
+
skip "扩展命令 $cmd 已注册" "未在 get_commands 中找到"
|
|
99
|
+
fi
|
|
100
|
+
done
|
|
101
|
+
|
|
102
|
+
# ----- Test 3: get_state 反映扩展注册的工具 -----
|
|
103
|
+
header "$CURRENT_CASE" "tools registered by extensions"
|
|
104
|
+
|
|
105
|
+
send '{"type":"get_state","id":"e2"}'
|
|
106
|
+
sleep 1
|
|
107
|
+
|
|
108
|
+
# 通过发 prompt 让模型思考,看 tool_execution_start 是否包含 aicode 扩展工具
|
|
109
|
+
# (aicode-safety-guard 注册的 safety_check / aicode-plan 注册的 todo_update 等)
|
|
110
|
+
|
|
111
|
+
send '{"type":"prompt","message":"just say hi","id":"e3"}'
|
|
112
|
+
sleep 8
|
|
113
|
+
|
|
114
|
+
# 收集所有出现过的 tool_name
|
|
115
|
+
all_tools=$(jq -r 'select(.type=="tool_execution_start") | .toolName // empty' "$REGRESSION_LOG" 2>/dev/null | sort -u)
|
|
116
|
+
echo " observed tools: $(echo "$all_tools" | tr '\n' ' ')"
|
|
117
|
+
|
|
118
|
+
# 内置工具一定在
|
|
119
|
+
for tool in read bash write edit; do
|
|
120
|
+
if echo "$all_tools" | grep -qx "$tool"; then
|
|
121
|
+
pass "内置工具 $tool 可调用"
|
|
122
|
+
else
|
|
123
|
+
# 单次 prompt 可能不调用
|
|
124
|
+
skip "内置工具 $tool 可调用" "本次 prompt 未触发"
|
|
125
|
+
fi
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
cleanup_rpc
|
|
129
|
+
summary
|
|
130
|
+
exit $?
|
|
@@ -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 $?
|