@tencent-ai/agent-sdk 0.3.181 → 0.3.183

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/cli/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ CodeBuddy Code 的所有重要更新都会记录在这里。
7
7
 
8
8
  ## [未发布]
9
9
 
10
+ ## [2.108.0] - 2026-06-18
11
+
12
+ ### 🎉 新功能
13
+
14
+ - **模型切换提示**:当项目配置已手动设置模型时,用户通过 `/model` 持久切换后会明确提示:重启后项目配置仍会覆盖当前选择,并建议移除项目配置中的模型设置以持久化新选择。
15
+
16
+ ### 🐛 修复
17
+
18
+ - **请求追踪标识唯一性**:修复多轮对话中多个模型请求复用同一组 `X-Request-ID` 和 `X-Trace-ID` 的问题,确保每个 HTTP 请求都有独立标识,便于排障与链路追踪。
19
+ - **自定义 Agent frontmatter 生成**:修复 AI 生成自定义 Agent 配置时,在多行描述或继承全部工具场景下可能写出不可解析或不符合加载约定的 frontmatter 的问题,确保生成结果可被稳定加载。
20
+
21
+ ## [2.107.0] - 2026-06-17
22
+
23
+ ### 🎉 新功能
24
+
25
+ - **预热进程(prewarm)**:新增 `--prewarm` 模式(默认关闭),进程可先完成启动初始化后待命,通过本地 IPC 在需要时快速唤醒并绑定工作目录;配套提供 `cbc-prewarm`(`list` / `ping` / `status` / `activate`)管理命令,用于加速新会话启动。
26
+
27
+ ### 🔧 改进
28
+
29
+ - **预热参数入口统一**:预热相关配置统一收口为 `--prewarm` / `--prewarm-id` / `--prewarm-force` CLI 参数,不再支持等价环境变量,降低排障时的配置歧义。
30
+ - **工作目录切换缓存刷新**:工作目录改为统一可订阅的变更源,切换目录时会自动重建文件监听并失效项目级缓存,避免设置、记忆、技能、插件和产品配置继续指向旧目录。
31
+ - **预热进程发现与接管**:预热进程待命时会登记到会话注册表,`cbc-prewarm list` 能正确发现可用实例;重复使用同一 `prewarmId` 时默认拒绝抢占,显式传入 `--prewarm-force` 后才会终止旧进程并接管。
32
+ - **ZIP marketplace 临时文件清理**:ZIP marketplace 安装或更新成功后,会自动清理 `~/.codebuddy/plugins/marketplaces/` 下超过 1 小时的同命名孤儿临时 zip 与解压目录,减少失败下载残留堆积。
33
+
34
+ ### 🐛 修复
35
+
36
+ - **空闲通知误报**:修复 agent 仍在流式输出或执行工具时,仍每 60 秒误发一次“CodeBuddy is waiting for your input”通知的问题;现在仅在会话真正进入 IDLE 状态时才发送。
37
+ - **沙箱权限确认超时语义**:将沙箱命令审批超时从 2 分钟延长至 30 分钟,并在超时后按“用户取消”而非“用户拒绝”处理,同时清理残留审批弹框,减少多步任务被误判中断。
38
+ - **apiKeyHelper 误报红字**:修复信任工作区后仍残留 `apiKeyHelper blocked` 红色提示的问题;现在交互式且尚未确认信任时会静默跳过本次执行,待用户完成信任后再正常重新同步。
39
+ - **任务编号顺序**:修复同一轮并发创建多个任务时,任务编号可能与创建顺序错位的问题,确保任务列表展示顺序稳定。
40
+ - **safe-delete 在 fish shell 报语法错误**:修复 safe-delete 路径恢复逻辑在 fish 等非 POSIX shell 下因拼接空 bootstrap 导致命令执行前直接报语法错误的问题。
41
+
10
42
  ## [2.106.7] - 2026-06-16
11
43
 
12
44
  ### 🔧 改进
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * cbc-prewarm —— 预热进程轻量管理 CLI
5
+ *
6
+ * 设计要点:这是一个**纯 Node 脚本**,只使用内置模块(net/fs/os/path),
7
+ * 故意**不** require 主 bundle(dist/codebuddy*.js ~16MB)、不启动 CellJS 容器、
8
+ * 不做产品配置加载。它的唯一职责是作为 prewarm IPC 的客户端 + 发现工具,
9
+ * 让外部(脚本 / 调度器 / daemon)能以毫秒级开销 list / ping / status / activate
10
+ * 预热进程。
11
+ *
12
+ * 地址约定与进程侧 `src/node/prewarm/prewarm-protocol.ts::resolvePrewarmIpcPath`
13
+ * 保持同构(codebuddy-prewarm-<id>,unix=/tmp/*.sock,win=\\.\pipe\*)。
14
+ * 发现目录复用 daemon 的 ~/.codebuddy/sessions/(PidFileEntry.kind==='prewarm')。
15
+ *
16
+ * 用法:
17
+ * cbc-prewarm list [--all|-a]
18
+ * cbc-prewarm ping <id>
19
+ * cbc-prewarm status <id>
20
+ * cbc-prewarm activate <id> [--cwd <dir>] [-- <cbc args...>]
21
+ * cbc-prewarm kill <id>
22
+ * cbc-prewarm -v | --version
23
+ */
24
+
25
+ 'use strict';
26
+
27
+ const net = require('net');
28
+ const fs = require('fs');
29
+ const os = require('os');
30
+ const path = require('path');
31
+
32
+ const PREWARM_ADDRESS_PREFIX = 'codebuddy-prewarm-';
33
+ const CONNECT_TIMEOUT_MS = 3000;
34
+ /** kill 后等待进程消失的轮询窗口(优雅退出宽限) */
35
+ const KILL_GRACEFUL_TIMEOUT_MS = 5000;
36
+ const KILL_POLL_INTERVAL_MS = 100;
37
+
38
+ // ── 版本号解析(与主 bin/codebuddy 取值逻辑保持一致) ───────────────
39
+
40
+ function resolveVersion() {
41
+ try {
42
+ const pkg = require('../package.json');
43
+ return (pkg && pkg.publishConfig && pkg.publishConfig.customPackage && pkg.publishConfig.customPackage.version)
44
+ || (pkg && pkg.version)
45
+ || 'unknown';
46
+ } catch {
47
+ return 'unknown';
48
+ }
49
+ }
50
+
51
+ // ── 地址 / 目录解析(与进程侧约定保持一致) ─────────────────────────
52
+
53
+ function resolveIpcPath(id) {
54
+ const address = `${PREWARM_ADDRESS_PREFIX}${id}`;
55
+ if (process.platform === 'win32') {
56
+ return `\\\\.\\pipe\\${address}`;
57
+ }
58
+ return `/tmp/${address}.sock`;
59
+ }
60
+
61
+ function getSessionsDir() {
62
+ const configDir = process.env.CODEBUDDY_CONFIG_DIR;
63
+ const root = configDir && configDir.trim() !== ''
64
+ ? configDir
65
+ : path.join(os.homedir(), '.codebuddy');
66
+ return path.join(root, 'sessions');
67
+ }
68
+
69
+ // ── 进程存活检测(kill -0) ─────────────────────────────────────────
70
+
71
+ function isProcessRunning(pid) {
72
+ if (!pid || typeof pid !== 'number') return false;
73
+ try {
74
+ process.kill(pid, 0);
75
+ return true;
76
+ } catch (e) {
77
+ return e && e.code === 'EPERM'; // 存在但无权限也算活着
78
+ }
79
+ }
80
+
81
+ // ── 发现:扫 sessions 目录里 kind==='prewarm' 的条目 ────────────────
82
+
83
+ function discoverPrewarms() {
84
+ const dir = getSessionsDir();
85
+ let files;
86
+ try {
87
+ files = fs.readdirSync(dir);
88
+ } catch {
89
+ return [];
90
+ }
91
+ const result = [];
92
+ for (const f of files) {
93
+ if (!f.endsWith('.json')) continue;
94
+ try {
95
+ const raw = fs.readFileSync(path.join(dir, f), 'utf-8');
96
+ const entry = JSON.parse(raw);
97
+ if (entry && entry.kind === 'prewarm') {
98
+ result.push(entry);
99
+ }
100
+ } catch {
101
+ // 跳过损坏 / 半写入的文件
102
+ }
103
+ }
104
+ return result;
105
+ }
106
+
107
+ // ── IPC 单次请求:连接 → 发一行 JSON → 读一行 JSON → 关闭 ──────────
108
+
109
+ function sendRequest(id, payload) {
110
+ return new Promise((resolve, reject) => {
111
+ const socketPath = resolveIpcPath(id);
112
+ const sock = net.connect(socketPath);
113
+ let buffer = '';
114
+ let settled = false;
115
+
116
+ const done = (err, res) => {
117
+ if (settled) return;
118
+ settled = true;
119
+ try { sock.destroy(); } catch { /* noop */ }
120
+ err ? reject(err) : resolve(res);
121
+ };
122
+
123
+ const timer = setTimeout(() => done(new Error(`timeout after ${CONNECT_TIMEOUT_MS}ms`)), CONNECT_TIMEOUT_MS);
124
+ timer.unref && timer.unref();
125
+
126
+ sock.on('connect', () => {
127
+ sock.write(JSON.stringify(payload) + '\n');
128
+ });
129
+ sock.setEncoding('utf-8');
130
+ sock.on('data', (chunk) => {
131
+ buffer += chunk;
132
+ const nl = buffer.indexOf('\n');
133
+ if (nl >= 0) {
134
+ clearTimeout(timer);
135
+ const line = buffer.slice(0, nl).trim();
136
+ try {
137
+ done(null, JSON.parse(line));
138
+ } catch (e) {
139
+ done(new Error(`invalid response: ${line}`));
140
+ }
141
+ }
142
+ });
143
+ sock.on('error', (err) => { clearTimeout(timer); done(err); });
144
+ sock.on('close', () => { clearTimeout(timer); done(new Error('connection closed before response')); });
145
+ });
146
+ }
147
+
148
+ // ── 命令实现 ────────────────────────────────────────────────────────
149
+
150
+ function cmdList(opts) {
151
+ const showAll = !!(opts && opts.all);
152
+ const entries = discoverPrewarms();
153
+ // 默认只展示活进程:dead 条目(含其残留 socket)会误导人,且会随下次同 id
154
+ // 启动被自愈清理,故默认隐藏;--all/-a 显式查看全部用于排障。
155
+ const rows = entries.map((e) => {
156
+ const meta = e.meta || {};
157
+ const alive = isProcessRunning(e.pid);
158
+ return {
159
+ id: meta.prewarmId || '(unknown)',
160
+ pid: e.pid,
161
+ status: meta.status || e.status || '(unknown)',
162
+ proc: alive ? 'alive' : 'dead',
163
+ socket: meta.socketPath || resolveIpcPath(meta.prewarmId || String(e.pid)),
164
+ alive,
165
+ };
166
+ });
167
+ const visible = showAll ? rows : rows.filter((r) => r.alive);
168
+ const hiddenDead = rows.length - visible.length;
169
+
170
+ if (visible.length === 0) {
171
+ console.log('No prewarm processes found.');
172
+ if (hiddenDead > 0) {
173
+ console.log(`(${hiddenDead} dead hidden; use --all to show)`);
174
+ }
175
+ return;
176
+ }
177
+
178
+ // 简洁表格输出
179
+ console.log(['ID', 'PID', 'STATUS', 'PROC', 'SOCKET'].join('\t'));
180
+ for (const r of visible) {
181
+ console.log([r.id, r.pid, r.status, r.proc, r.socket].join('\t'));
182
+ }
183
+ if (hiddenDead > 0) {
184
+ console.log(`(${hiddenDead} dead hidden; use --all to show)`);
185
+ }
186
+ }
187
+
188
+ async function cmdPing(id) {
189
+ requireId(id, 'ping');
190
+ const res = await sendRequest(id, { cmd: 'ping' });
191
+ console.log(JSON.stringify(res, null, 2));
192
+ }
193
+
194
+ async function cmdStatus(id) {
195
+ requireId(id, 'status');
196
+ const res = await sendRequest(id, { cmd: 'status' });
197
+ console.log(JSON.stringify(res, null, 2));
198
+ }
199
+
200
+ async function cmdActivate(id, opts) {
201
+ requireId(id, 'activate');
202
+ // --cwd 可选:省略时预热进程保持其冷启动 cwd(不 chdir)。
203
+ const payload = { cmd: 'activate', args: opts.args || [] };
204
+ if (opts.cwd) payload.cwd = path.resolve(opts.cwd);
205
+ if (opts.sessionId) payload.sessionId = opts.sessionId;
206
+ if (opts.env && Object.keys(opts.env).length > 0) payload.env = opts.env;
207
+ const res = await sendRequest(id, payload);
208
+ console.log(JSON.stringify(res, null, 2));
209
+ if (!res.ok) process.exitCode = 1;
210
+ }
211
+
212
+ /** 从发现的 prewarm 条目里按 id 找 pid。 */
213
+ function findPrewarmById(id) {
214
+ return discoverPrewarms().find((e) => (e.meta && e.meta.prewarmId) === id);
215
+ }
216
+
217
+ function sleep(ms) {
218
+ return new Promise((resolve) => setTimeout(resolve, ms));
219
+ }
220
+
221
+ async function cmdKill(id) {
222
+ requireId(id, 'kill');
223
+
224
+ const entry = findPrewarmById(id);
225
+ if (!entry || !entry.pid) {
226
+ fail(`kill: prewarm not found: ${id}`);
227
+ }
228
+ const pid = entry.pid;
229
+
230
+ if (!isProcessRunning(pid)) {
231
+ console.log(`Prewarm ${id} (pid ${pid}) is already dead.`);
232
+ return;
233
+ }
234
+
235
+ // 优雅终止:先发 SIGTERM(进程侧 SIGTERM handler 会 close IPC + 清理 socket),
236
+ // 在宽限窗口内轮询存活;超时仍在则升级为 SIGKILL。
237
+ try {
238
+ process.kill(pid, 'SIGTERM');
239
+ } catch (e) {
240
+ fail(`kill: failed to signal pid ${pid}: ${e && e.message ? e.message : e}`);
241
+ }
242
+
243
+ const deadline = Date.now() + KILL_GRACEFUL_TIMEOUT_MS;
244
+ while (Date.now() < deadline) {
245
+ if (!isProcessRunning(pid)) {
246
+ console.log(`Prewarm ${id} (pid ${pid}) terminated.`);
247
+ return;
248
+ }
249
+ await sleep(KILL_POLL_INTERVAL_MS);
250
+ }
251
+
252
+ // 优雅超时,强制 kill。
253
+ try {
254
+ process.kill(pid, 'SIGKILL');
255
+ console.log(`Prewarm ${id} (pid ${pid}) force-killed (SIGKILL after ${KILL_GRACEFUL_TIMEOUT_MS}ms).`);
256
+ } catch (e) {
257
+ fail(`kill: SIGKILL failed for pid ${pid}: ${e && e.message ? e.message : e}`);
258
+ }
259
+ }
260
+
261
+ // ── 参数解析(极简,避免引入 commander 等依赖) ─────────────────────
262
+
263
+ function parseActivateOpts(rest) {
264
+ const opts = { args: [], env: {} };
265
+ const addEnv = (kv) => {
266
+ // KEY=VALUE 形式;VALUE 中含 = 时按首个 = 切。
267
+ const idx = kv.indexOf('=');
268
+ if (idx <= 0) {
269
+ fail(`--env value must be KEY=VALUE, got: ${kv}`);
270
+ }
271
+ opts.env[kv.slice(0, idx)] = kv.slice(idx + 1);
272
+ };
273
+ for (let i = 0; i < rest.length; i++) {
274
+ const a = rest[i];
275
+ if (a === '--cwd') { opts.cwd = rest[++i]; }
276
+ else if (a.startsWith('--cwd=')) { opts.cwd = a.slice('--cwd='.length); }
277
+ else if (a === '--session-id') { opts.sessionId = rest[++i]; }
278
+ else if (a.startsWith('--session-id=')) { opts.sessionId = a.slice('--session-id='.length); }
279
+ else if (a === '--env') { addEnv(rest[++i]); }
280
+ else if (a.startsWith('--env=')) { addEnv(a.slice('--env='.length)); }
281
+ else if (a === '--') { opts.args = rest.slice(i + 1); break; }
282
+ else { /* 忽略未知 flag,留给 -- 之后 */ }
283
+ }
284
+ return opts;
285
+ }
286
+
287
+ function requireId(id, cmd) {
288
+ if (!id) fail(`${cmd} requires <id>`);
289
+ }
290
+
291
+ function fail(msg) {
292
+ console.error(`cbc-prewarm: ${msg}`);
293
+ printUsage();
294
+ process.exit(1);
295
+ }
296
+
297
+ function printUsage() {
298
+ console.error([
299
+ '',
300
+ 'Usage:',
301
+ ' cbc-prewarm list [--all|-a] List prewarm processes (alive only; --all includes dead)',
302
+ ' cbc-prewarm ping <id> Liveness probe',
303
+ ' cbc-prewarm status <id> Query status (idle/activating/active)',
304
+ ' cbc-prewarm activate <id> [--cwd <dir>] [--env KEY=VAL]... [-- <args>] Bind cwd (optional) and run',
305
+ ' cbc-prewarm kill <id> Terminate a prewarm process (SIGTERM, then SIGKILL)',
306
+ ' cbc-prewarm -v | --version Print version',
307
+ '',
308
+ ].join('\n'));
309
+ }
310
+
311
+ // ── 入口 ────────────────────────────────────────────────────────────
312
+
313
+ async function main() {
314
+ const [cmd, id, ...rest] = process.argv.slice(2);
315
+
316
+ switch (cmd) {
317
+ case 'list':
318
+ case 'ls': {
319
+ const all = [id, ...rest].some((a) => a === '--all' || a === '-a');
320
+ cmdList({ all });
321
+ break;
322
+ }
323
+ case 'ping':
324
+ await cmdPing(id);
325
+ break;
326
+ case 'status':
327
+ await cmdStatus(id);
328
+ break;
329
+ case 'activate':
330
+ await cmdActivate(id, parseActivateOpts(rest));
331
+ break;
332
+ case 'kill':
333
+ await cmdKill(id);
334
+ break;
335
+ case '-v':
336
+ case '--version':
337
+ console.log(resolveVersion());
338
+ break;
339
+ case undefined:
340
+ case '-h':
341
+ case '--help':
342
+ case 'help':
343
+ printUsage();
344
+ break;
345
+ default:
346
+ fail(`unknown command: ${cmd}`);
347
+ }
348
+ }
349
+
350
+ main().catch((err) => {
351
+ console.error(`cbc-prewarm: ${err && err.message ? err.message : err}`);
352
+ process.exit(1);
353
+ });
package/cli/bin/codebuddy CHANGED
@@ -61,7 +61,7 @@ require('events').EventEmitter.defaultMaxListeners = 50;
61
61
  if (process.env.CODEBUDDY_DISABLE_COMPILE_CACHE !== '1') {
62
62
  try {
63
63
  const { enableCompileCache, getCompileCacheDir, constants } = require('module');
64
-
64
+
65
65
  if (typeof enableCompileCache === 'function') {
66
66
  // No directory argument => Node picks a per-user temp location keyed by the
67
67
  // module's mtime + Node version, so a stale cache after an upgrade is invalidated.
@@ -144,7 +144,7 @@ const isHeadless = args.includes('--print') || args.includes('-p')
144
144
  // Daemon and background session commands
145
145
  || args[0] === 'daemon'
146
146
  || ['ps', 'logs', 'attach', 'kill'].includes(args[0])
147
- || args.includes('--bg') || args.includes('--background');
147
+ || args.includes('--bg') || args.includes('--background')
148
148
 
149
149
  try {
150
150
  if (isHeadless) {