@ia-ccun/code-agent-cli 0.0.54-beta.3 → 0.0.54-beta.5
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 +30 -13
- package/config/agent/extensions/custom-footer.ts +42 -19
- package/package.json +1 -1
- package/scripts/regression/cases/08-cwd-resilience.sh +84 -0
- package/scripts/regression/results/.last_pid +1 -1
- package/scripts/regression/results/.last_workdir +1 -1
- package/scripts/regression/results/02-extension-loading.log +14 -16
- package/scripts/regression/results/03-prompt-stream.log +12 -12
- package/scripts/regression/results/03-rpc-protocol.log +16 -18
- package/scripts/regression/results/04-slash-commands-and-skills.log +11 -11
- package/scripts/regression/results/05-tool-execution.log +5 -5
- package/scripts/regression/results/06-pi-new-features.log +23 -14
- package/scripts/regression/results/08-cwd-resilience.log +1 -0
package/bin/cli.js
CHANGED
|
@@ -8,19 +8,28 @@
|
|
|
8
8
|
// 设置终端标题
|
|
9
9
|
process.title = "aicode-cli";
|
|
10
10
|
|
|
11
|
-
// 防御 CWD
|
|
12
|
-
//
|
|
13
|
-
|
|
11
|
+
// 防御 CWD 被删: 在所有 import 之前 chdir (node ESM 解析 import 时会读 cwd)
|
|
12
|
+
// 不 import os.homedir() 因为那需要模块解析, 此时 cwd 还没改
|
|
13
|
+
// 用 process.env.PWD 优先 (用户启动目录) → HOME fallback (防 PWD 缺失 ENOENT)
|
|
14
|
+
// 先 PWD 试 (用户启动目录), 失败 fallback HOME (cwd 被删时 PWD 也指向已删目录, chdir 会抛 ENOENT)
|
|
15
|
+
// catch 后再退一步: HOME 也失败则 chdir 到 / (绝对保证不会 ENOENT)
|
|
14
16
|
try {
|
|
15
|
-
process.chdir(
|
|
17
|
+
process.chdir(process.env.PWD);
|
|
16
18
|
} catch {
|
|
17
|
-
|
|
19
|
+
try {
|
|
20
|
+
process.chdir(process.env.HOME || "/");
|
|
21
|
+
} catch {
|
|
22
|
+
try {
|
|
23
|
+
process.chdir("/");
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
18
26
|
}
|
|
27
|
+
|
|
19
28
|
import { spawn } from "child_process";
|
|
20
29
|
import { fileURLToPath } from "url";
|
|
21
30
|
import { dirname, join } from "path";
|
|
22
31
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
23
|
-
import { getConfig,
|
|
32
|
+
import { getConfig, loadConfig } from "../dist/config-loader.js";
|
|
24
33
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
34
|
const __dirname = dirname(__filename);
|
|
26
35
|
|
|
@@ -30,7 +39,11 @@ function getLocalVersion() {
|
|
|
30
39
|
while (rootDir !== dirname(rootDir)) {
|
|
31
40
|
const pkgPath = join(rootDir, "package.json");
|
|
32
41
|
if (existsSync(pkgPath)) {
|
|
33
|
-
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
34
47
|
}
|
|
35
48
|
rootDir = dirname(rootDir);
|
|
36
49
|
}
|
|
@@ -55,8 +68,12 @@ function parseArgs() {
|
|
|
55
68
|
if (args.includes("--version") || args.includes("-v")) {
|
|
56
69
|
const pkgPath = join(__dirname, "..", "package.json");
|
|
57
70
|
if (existsSync(pkgPath)) {
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
try {
|
|
72
|
+
const { version } = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
73
|
+
console.log(version);
|
|
74
|
+
} catch (_e) {
|
|
75
|
+
// ignore
|
|
76
|
+
}
|
|
60
77
|
}
|
|
61
78
|
process.exit(0);
|
|
62
79
|
}
|
|
@@ -130,7 +147,7 @@ function replaceCLIVersionPlaceholder() {
|
|
|
130
147
|
if (newContent !== content) {
|
|
131
148
|
writeFileSync(modelsPath, newContent, "utf-8");
|
|
132
149
|
}
|
|
133
|
-
} catch
|
|
150
|
+
} catch {
|
|
134
151
|
// 忽略错误
|
|
135
152
|
}
|
|
136
153
|
}
|
|
@@ -138,7 +155,7 @@ function replaceCLIVersionPlaceholder() {
|
|
|
138
155
|
}
|
|
139
156
|
rootDir = dirname(rootDir);
|
|
140
157
|
}
|
|
141
|
-
} catch
|
|
158
|
+
} catch {
|
|
142
159
|
// 忽略错误
|
|
143
160
|
}
|
|
144
161
|
}
|
|
@@ -207,10 +224,10 @@ async function checkForUpdates() {
|
|
|
207
224
|
timestamp: Date.now(),
|
|
208
225
|
};
|
|
209
226
|
writeFileSync(versionCachePath, JSON.stringify(cacheData, null, 2));
|
|
210
|
-
} catch
|
|
227
|
+
} catch {
|
|
211
228
|
// 忽略缓存写入失败
|
|
212
229
|
}
|
|
213
|
-
} catch
|
|
230
|
+
} catch {
|
|
214
231
|
// 忽略检查失败
|
|
215
232
|
}
|
|
216
233
|
}
|
|
@@ -6,10 +6,30 @@
|
|
|
6
6
|
* [gold] [dim] [dim] [color ctx] [accent]
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { basename } from "node:path";
|
|
9
10
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
10
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
12
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* 解析 footer 显示用的项目名.
|
|
16
|
+
* 优先用 process.env.PWD (shell 启动时设进 env, cli.js 入口 chdir(homedir()) 也不改它),
|
|
17
|
+
* 退化到 process.cwd(). /user/xxx 启动显示 xxx, 跟 git 无关.
|
|
18
|
+
* cwd 被删时 fallback 到 'project'.
|
|
19
|
+
*/
|
|
20
|
+
function resolveProjectName(): string {
|
|
21
|
+
const pwd = process.env.PWD;
|
|
22
|
+
if (pwd) {
|
|
23
|
+
const name = basename(pwd);
|
|
24
|
+
if (name) return name;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return basename(process.cwd()) || "project";
|
|
28
|
+
} catch {
|
|
29
|
+
return "project";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
13
33
|
function fmtTokens(n: number): string {
|
|
14
34
|
if (n < 1000) return n.toString();
|
|
15
35
|
if (n < 10000) return `${(n / 1000).toFixed(1)}k`;
|
|
@@ -22,7 +42,7 @@ const gold = (s: string) => `\x1b[38;2;73;188;207m${s}\x1b[0m`;
|
|
|
22
42
|
|
|
23
43
|
// 获取本地版本号
|
|
24
44
|
function getVersion() {
|
|
25
|
-
|
|
45
|
+
return process.env.AICODE_CLI_VERSION || null;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
export default function (pi: ExtensionAPI) {
|
|
@@ -43,7 +63,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
43
63
|
try {
|
|
44
64
|
const { existsSync, readFileSync } = await import("fs");
|
|
45
65
|
if (existsSync(cachePath)) {
|
|
46
|
-
const cache = JSON.parse(readFileSync(cachePath,
|
|
66
|
+
const cache = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
47
67
|
// 缓存有效期 1 小时
|
|
48
68
|
if (cache.needsUpdate && Date.now() - cache.timestamp < 3600000) {
|
|
49
69
|
// 在输入框上方显示版本更新提示
|
|
@@ -68,8 +88,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
68
88
|
const sepWidth = 3; // " │ "
|
|
69
89
|
|
|
70
90
|
// ── Segment 1: project name (branch) ─────────────────────
|
|
71
|
-
|
|
72
|
-
const projectName =
|
|
91
|
+
// /user/xxx 启动显示 xxx (走 PWD env, 跟 chdir 解耦)
|
|
92
|
+
const projectName = resolveProjectName();
|
|
73
93
|
const branch = footerData.getGitBranch();
|
|
74
94
|
const pathRaw = projectName + (branch ? ` (${branch})` : "");
|
|
75
95
|
|
|
@@ -109,8 +129,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
109
129
|
// ── Assemble: figure out what fits ──────────────────────
|
|
110
130
|
// Always show: path | ctx | model. Stats only if room.
|
|
111
131
|
// Available width for path after reserving right segments
|
|
112
|
-
const rightBlockWidth =
|
|
113
|
-
|
|
132
|
+
const rightBlockWidth =
|
|
133
|
+
(statsRaw ? visibleWidth(statsRaw) + sepWidth : 0) +
|
|
134
|
+
visibleWidth(ctxRaw) +
|
|
135
|
+
sepWidth +
|
|
136
|
+
visibleWidth(modelRaw);
|
|
114
137
|
const pathBudget = width - rightBlockWidth - sepWidth; // -sepWidth for first separator
|
|
115
138
|
|
|
116
139
|
let pathDisplay: string;
|
|
@@ -136,12 +159,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
136
159
|
|
|
137
160
|
// Thinking level coloring — semaphore, hardcoded to decouple from border
|
|
138
161
|
const thinkingColors: Record<string, string> = {
|
|
139
|
-
off:
|
|
140
|
-
minimal: "\x1b[38;2;67;61;78m",
|
|
141
|
-
low:
|
|
142
|
-
medium:
|
|
143
|
-
high:
|
|
144
|
-
xhigh:
|
|
162
|
+
off: "\x1b[38;2;51;45;62m", // surface1 — invisible
|
|
163
|
+
minimal: "\x1b[38;2;67;61;78m", // surface2 — barely there
|
|
164
|
+
low: "\x1b[38;2;166;227;161m", // green
|
|
165
|
+
medium: "\x1b[38;2;137;180;250m", // blue
|
|
166
|
+
high: "\x1b[38;2;250;179;135m", // peach
|
|
167
|
+
xhigh: "\x1b[38;2;243;139;168m", // red
|
|
145
168
|
};
|
|
146
169
|
const thinkAnsi = thinkingColors[thinking] ?? thinkingColors.off;
|
|
147
170
|
let modelColored = theme.fg("accent", `⚡ ${modelName}`);
|
|
@@ -155,11 +178,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
155
178
|
|
|
156
179
|
// quots
|
|
157
180
|
const quotes = [
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
181
|
+
"敬畏生产,双人复核,稳妥变更",
|
|
182
|
+
"让代码更有温度",
|
|
183
|
+
"民生所系,支付所向",
|
|
184
|
+
"智慧风控,安全同行",
|
|
185
|
+
"绿色支付,低碳生活",
|
|
163
186
|
"保持热爱,奔赴山海",
|
|
164
187
|
"每一行代码都是故事",
|
|
165
188
|
"技术只是手段,创意才是灵魂",
|
|
@@ -193,8 +216,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
193
216
|
// ── Plan mode segment ────────────────────────────────────
|
|
194
217
|
if (planModeEnabled) {
|
|
195
218
|
segments.push(theme.fg("success", "⏸ 计划模式"));
|
|
196
|
-
}else{
|
|
197
|
-
|
|
219
|
+
} else {
|
|
220
|
+
segments.push(theme.fg("error", "▶ 自动模式"));
|
|
198
221
|
}
|
|
199
222
|
|
|
200
223
|
segments.push(theme.fg("muted", quote));
|
package/package.json
CHANGED
|
@@ -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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
34210
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/Users/jianjiang/.aicode-regression-
|
|
1
|
+
/Users/jianjiang/.aicode-regression-34084-6197000
|