@tencent-ai/agent-sdk 0.3.179 → 0.3.181-dev.fd84926.202606172258

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,20 @@ CodeBuddy Code 的所有重要更新都会记录在这里。
7
7
 
8
8
  ## [未发布]
9
9
 
10
+ ## [2.106.7] - 2026-06-16
11
+
12
+ ### 🔧 改进
13
+
14
+ - **遥测模块维护**:整理遥测相关代码格式,保持发布构建结果稳定,减少后续发版中的无关格式差异。
15
+
16
+ ## [2.106.6] - 2026-06-16
17
+
18
+ ### 🐛 修复
19
+
20
+ - **命令执行兼容性**:修复在 fish 等非 POSIX 登录 shell 下执行命令失败的问题。现在仅当 `$SHELL` 为 POSIX 兼容 shell 时复用该 shell,否则自动回退到 zsh/bash(兜底 `/bin/sh`),不会影响用户当前的登录 shell。
21
+ - **自定义模型工具调用**:修复 Kimi 等模型在流式响应中重复发送 tool call ID 时,工具调用失败的问题。
22
+ - **后台任务输出目录**:将任务输出文件写入按用户和工作目录隔离的临时目录,避免共享环境中的默认目录权限冲突导致读取失败。
23
+
10
24
  ## [2.106.5] - 2026-06-16
11
25
 
12
26
  ### 🐛 修复
@@ -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) {