@ia-ccun/code-agent-cli 0.0.54-beta.1 → 0.0.54-beta.4

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 (144) hide show
  1. package/bin/cli.js +116 -74
  2. package/config/agent/extensions/aicode-advisor.ts +187 -64
  3. package/config/agent/extensions/aicode-banner.ts +101 -0
  4. package/config/agent/extensions/aicode-btw.ts +619 -182
  5. package/config/agent/extensions/aicode-cd.ts +246 -63
  6. package/config/agent/extensions/aicode-pets.ts +0 -11
  7. package/config/agent/extensions/aicode-plan.ts +2 -0
  8. package/config/agent/extensions/aicode-safety-guard.ts +3 -0
  9. package/config/agent/extensions/aicode-tips.ts +1 -16
  10. package/config/agent/extensions/context-monitor.ts +30 -15
  11. package/config/agent/extensions/context.ts +132 -60
  12. package/config/agent/extensions/custom-footer.ts +42 -24
  13. package/config/agent/extensions/custom.ts +41 -29
  14. package/config/agent/extensions/error-monitor.ts +37 -33
  15. package/config/agent/extensions/goal.ts +8 -6
  16. package/config/agent/extensions/msg-debug.ts +103 -112
  17. package/config/agent/extensions/review.ts +365 -164
  18. package/config/agent/extensions/working-msg.ts +2 -4
  19. package/config/agent/settings.json +15 -14
  20. package/config/agent/skills/hello/SKILL.md +1 -1
  21. package/config/agent/skills/refactor/SKILL.md +40 -0
  22. package/dist/banner.d.ts.map +1 -1
  23. package/dist/banner.js +8 -1
  24. package/dist/banner.js.map +1 -1
  25. package/dist/config-loader.d.ts.map +1 -1
  26. package/dist/config-loader.js +9 -2
  27. package/dist/config-loader.js.map +1 -1
  28. package/package.json +6 -7
  29. package/scripts/regression/README.md +224 -0
  30. package/scripts/regression/cases/01-version-and-deps.sh +94 -0
  31. package/scripts/regression/cases/02-extension-loading.sh +130 -0
  32. package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
  33. package/scripts/regression/cases/04-prompt-stream.sh +130 -0
  34. package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
  35. package/scripts/regression/cases/06-tool-execution.sh +115 -0
  36. package/scripts/regression/cases/07-pi-new-features.sh +141 -0
  37. package/scripts/regression/cases/08-cwd-resilience.sh +84 -0
  38. package/scripts/regression/lib/common.sh +287 -0
  39. package/scripts/regression/results/.last_pid +1 -0
  40. package/scripts/regression/results/.last_workdir +1 -0
  41. package/scripts/regression/results/02-extension-loading.log +19 -0
  42. package/scripts/regression/results/03-prompt-stream.log +16 -0
  43. package/scripts/regression/results/03-rpc-protocol.log +28 -0
  44. package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
  45. package/scripts/regression/results/05-tool-execution.log +8 -0
  46. package/scripts/regression/results/06-pi-new-features.log +27 -0
  47. package/scripts/regression/results/08-cwd-resilience.log +1 -0
  48. package/scripts/regression/run-all.sh +187 -0
  49. package/config/agent/bin/fd.exe +0 -0
  50. package/config/agent/bin/rg.exe +0 -0
  51. package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
  52. package/config/agent/extensions/aicode-snake.ts +0 -354
  53. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
  54. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
  55. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
  56. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
  57. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
  58. /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
  59. /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
  60. /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
  61. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
  62. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
  63. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
  64. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
  65. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
  66. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
  67. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
  68. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
  69. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  70. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
  71. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
  72. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
  73. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
  74. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
  75. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
  76. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
  77. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
  78. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
  79. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
  80. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
  81. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
  82. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
  83. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
  84. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
  85. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
  86. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
  87. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
  88. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
  89. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
  90. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
  91. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
  92. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
  93. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
  94. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
  95. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
  96. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
  97. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
  98. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
  99. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
  100. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
  101. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
  102. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
  103. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
  104. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
  105. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
  106. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
  107. /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
  108. /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
  109. /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
  110. /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
  111. /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
  112. /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
  113. /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
  114. /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
  115. /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
  116. /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
  117. /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
  118. /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
  119. /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
  120. /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
  121. /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
  122. /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
  123. /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
  124. /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
  125. /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
  126. /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
  127. /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
  128. /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
  129. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
  130. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
  131. /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
  132. /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
  133. /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
  134. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
  135. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
  136. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
  137. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
  138. /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
  139. /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
  140. /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
  141. /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
  142. /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
  143. /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
  144. /package/config/agent/{skills_bak → skills}/zread/references/stdio-protocol.md +0 -0
@@ -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 $?
@@ -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,84 @@
1
+ #!/usr/bin/env bash
2
+ # 08-cwd-resilience.sh
3
+ # 验证:aicode-cli 在不同 cwd 场景下都能稳定启动, 不因 cwd 状态错位
4
+ #
5
+ # 0.85→1.1.0 升级后引入的 CWD 双重错位:
6
+ # - cli.js 入口 process.chdir(homedir()) 防 ENOENT → 毁了 process.cwd() 语义
7
+ # - custom-footer 用 process.env.PWD 修 footer 显示 → 仍修不了 aicode 进程 cwd
8
+ # - cli.js 改为 process.chdir(process.env.PWD || homedir()) → 全修好
9
+ #
10
+ # 这个 case 锁定以上 3 层修复都不退化:
11
+ # 1. cwd 正常: 启动 aicode 进程不死, stdout 无 ENOENT
12
+ # 2. cwd 被删 (预防 ENOENT): 不崩, cli.js 入口 chdir 兜底生效
13
+ # 3. PWD 未设 (degenerate): fallback 到 homedir, 不崩
14
+
15
+ set -uo pipefail
16
+ CURRENT_CASE="08-cwd-resilience"
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ source "$SCRIPT_DIR/../lib/common.sh"
19
+
20
+ header "$CURRENT_CASE" "aicode CWD resilience across edge cases"
21
+
22
+ # ----- Test 1: 从正常项目目录启动, 不应 ENOENT -----
23
+ PROJECT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
24
+ header "$CURRENT_CASE" "test 1: normal cwd (project root)"
25
+ (
26
+ cd "$PROJECT_DIR"
27
+ if ! spawn_rpc 15 true; then
28
+ echo "FAIL_SPAWN"
29
+ cleanup_rpc 2>/dev/null
30
+ exit 1
31
+ fi
32
+ if grep -q "Error: ENOENT" "$REGRESSION_LOG" 2>/dev/null; then
33
+ echo "FAIL_ENOENT"
34
+ cleanup_rpc
35
+ exit 1
36
+ fi
37
+ cleanup_rpc
38
+ ) && pass "normal cwd startup: no ENOENT" ||
39
+ fail "normal cwd: ENOENT or spawn failed" "cli.js 入口 chdir 防御退化"
40
+
41
+ # ----- Test 2: 从 deleted cwd 启动 (复现 0.85 ENOENT 场景) -----
42
+ header "$CURRENT_CASE" "test 2: cwd deleted (ENOENT resilience)"
43
+ (
44
+ # 创建临时目录, 然后删它, 模拟 cwd 被删
45
+ TESTDIR=$(mktemp -d -t aicode-cwd-test.XXXXXX)
46
+ cd "$TESTDIR"
47
+ rmdir "$TESTDIR"
48
+ # 现在 shell cwd 指向已删目录
49
+ if ! spawn_rpc 15 true; then
50
+ echo "FAIL_SPAWN"
51
+ cleanup_rpc 2>/dev/null
52
+ exit 1
53
+ fi
54
+ # 等 RPC 启动事件
55
+ sleep 2
56
+ if grep -q "Error: ENOENT" "$REGRESSION_LOG" 2>/dev/null; then
57
+ echo "FAIL_ENOENT"
58
+ cleanup_rpc
59
+ exit 1
60
+ fi
61
+ cleanup_rpc
62
+ ) && pass "deleted cwd startup: no ENOENT" ||
63
+ fail "deleted cwd: ENOENT or spawn failed" "cli.js 入口 chdir 防御失效"
64
+
65
+ # ----- Test 3: PWD env 未设 (degenerate shell) -----
66
+ header "$CURRENT_CASE" "test 3: PWD unset, fallback to homedir"
67
+ (
68
+ unset PWD
69
+ if ! spawn_rpc 15 true; then
70
+ echo "FAIL_SPAWN"
71
+ cleanup_rpc 2>/dev/null
72
+ exit 1
73
+ fi
74
+ sleep 2
75
+ if grep -q "Error: ENOENT" "$REGRESSION_LOG" 2>/dev/null; then
76
+ echo "FAIL_ENOENT"
77
+ cleanup_rpc
78
+ exit 1
79
+ fi
80
+ cleanup_rpc
81
+ ) && pass "PWD unset startup: no ENOENT" ||
82
+ fail "PWD unset: ENOENT or spawn failed" "homedir fallback 失效"
83
+
84
+ summary
@@ -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
+ 14590
@@ -0,0 +1 @@
1
+ /Users/jianjiang/.aicode-regression-14462-2262000