jinzd-ai-cli 0.1.57 → 0.1.59
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/CLAUDE.md
CHANGED
|
@@ -350,6 +350,134 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
350
350
|
- [x] **web_fetch DNS 解析时 SSRF 防护**(v0.1.25):新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名,检查结果 IP 是否为私有地址。初始 URL 和 redirect 目标均校验。
|
|
351
351
|
- [ ] **`persistentCwd` 全局状态**:bash 工具的当前工作目录是模块级全局变量,多 session 并发时可能串扰。现阶段单 session REPL 无影响,GUI 多会话扩展时需重构为 per-session 状态。
|
|
352
352
|
|
|
353
|
+
## 本轮开发完成记录(2026-03-10,v0.1.57 → v0.1.58)
|
|
354
|
+
|
|
355
|
+
### 代码质量修复:19 个低危问题修复 + 代码重构
|
|
356
|
+
|
|
357
|
+
**审查完结篇**:继 v0.1.56 修复高危、v0.1.57 修复中危后,本轮修复全部低危问题,代码审查报告彻底清零。
|
|
358
|
+
|
|
359
|
+
| # | 文件 | 修复内容 |
|
|
360
|
+
|---|------|---------|
|
|
361
|
+
| L1 | `src/tools/truncate.ts` (新) | 提取 `truncateOutput` 为共享模块,消除 executor.ts 与 spawn-agent.ts 代码重复 |
|
|
362
|
+
| L2 | `src/tools/executor.ts` | 改为从共享 `truncate.ts` 导入 |
|
|
363
|
+
| L3 | `src/tools/builtin/spawn-agent.ts` | 15+ 处 chalk→theme 迁移(header/footer/BLOCKED/toolCall/toolResult),移除 chalk 依赖 |
|
|
364
|
+
| L4 | `src/tools/builtin/run-interactive.ts` | timeout 参数限制 1s–300s(与 bash 工具一致) |
|
|
365
|
+
| L5 | `src/tools/builtin/grep-files.ts` | `statSync` 预检文件大小再读取,避免 OOM |
|
|
366
|
+
| L6 | `src/tools/builtin/read-file.ts` | `buf.slice` → `buf.subarray`(消除 Node.js 弃用警告) |
|
|
367
|
+
| L7 | `src/tools/undo-stack.ts` | 提取 `pushEntry()` 统一入栈方法,消除 push/pushNewFile/pushNewDir 三处溢出检查重复 |
|
|
368
|
+
| L8 | `src/tools/builtin/save-memory.ts` | 用 `statSync` 获取文件大小,避免追加后重读整个文件 |
|
|
369
|
+
| L9 | `src/tools/builtin/list-dir.ts` | 不再隐藏 .github/.vscode/.gitignore/.env* 等有用的点文件/目录 |
|
|
370
|
+
| L10 | `src/repl/custom-commands.ts` | 复用 `skills/types.ts` 的 `parseSimpleYaml`,消除函数重复 |
|
|
371
|
+
| L11 | `src/skills/types.ts` | `parseSimpleYaml` 导出为 public + 添加引号剥离 |
|
|
372
|
+
| L12 | `src/tools/git-context.ts` | 移除 `2>/dev/null`(Windows 不兼容,`stdio:pipe` 已捕获 stderr) |
|
|
373
|
+
| L13 | `src/repl/notify.ts` | Windows PowerShell 反引号转义修复 |
|
|
374
|
+
| L14 | `src/tools/hooks.ts` | 模板变量 shell 转义(`shellEscape` 单引号包裹),防止命令注入 |
|
|
375
|
+
| L15 | `src/mcp/client.ts` | 移除 `rejectAllPending` 中冗余的 `clearTimeout`(reject 回调已包含) |
|
|
376
|
+
| L16 | `src/config/config-manager.ts` | 拆分 `toJSON()`→返回对象 + `toFormattedJSON()`→返回字符串,修复 `/config show` API Key 掩码失效 |
|
|
377
|
+
| L17 | `src/tools/builtin/write-file.ts` | append 模式也记录 undo 条目(可撤销追加操作) |
|
|
378
|
+
| L18 | `src/providers/gemini.ts` | 保留空白文本 parts(仅跳过空字符串),避免丢失有意义的空白 |
|
|
379
|
+
| L19 | `src/repl/clipboard.ts` | PowerShell 单引号字符串中的路径转义修复 |
|
|
380
|
+
| L20 | `src/tools/diff-utils.ts` | DP 表内存限制文档化(~2MB 上限) |
|
|
381
|
+
| L21 | `src/tools/permissions.ts` | `pathPattern` 子串匹配行为文档化 |
|
|
382
|
+
|
|
383
|
+
### 变更文件汇总
|
|
384
|
+
|
|
385
|
+
| 文件 | 变更类型 | 说明 |
|
|
386
|
+
|------|---------|------|
|
|
387
|
+
| `src/tools/truncate.ts` | 新增 | 共享 truncateOutput 函数 |
|
|
388
|
+
| `src/tools/executor.ts` | 修改 | 导入共享 truncate.ts |
|
|
389
|
+
| `src/tools/builtin/spawn-agent.ts` | 修改 | chalk→theme 全量迁移 + 共享 truncate |
|
|
390
|
+
| `src/tools/builtin/run-interactive.ts` | 修改 | timeout 限制 |
|
|
391
|
+
| `src/tools/builtin/grep-files.ts` | 修改 | statSync 预检 |
|
|
392
|
+
| `src/tools/builtin/read-file.ts` | 修改 | buf.subarray |
|
|
393
|
+
| `src/tools/undo-stack.ts` | 修改 | pushEntry DRY |
|
|
394
|
+
| `src/tools/builtin/save-memory.ts` | 修改 | statSync 替代 readFile |
|
|
395
|
+
| `src/tools/builtin/list-dir.ts` | 修改 | 有用点文件可见 |
|
|
396
|
+
| `src/repl/custom-commands.ts` | 修改 | 复用 parseSimpleYaml |
|
|
397
|
+
| `src/skills/types.ts` | 修改 | 导出 parseSimpleYaml |
|
|
398
|
+
| `src/tools/git-context.ts` | 修改 | 移除 2>/dev/null |
|
|
399
|
+
| `src/repl/notify.ts` | 修改 | PS 转义修复 |
|
|
400
|
+
| `src/tools/hooks.ts` | 修改 | shell 转义 |
|
|
401
|
+
| `src/mcp/client.ts` | 修改 | 冗余 clearTimeout |
|
|
402
|
+
| `src/config/config-manager.ts` | 修改 | toJSON 拆分 |
|
|
403
|
+
| `src/repl/commands/index.ts` | 修改 | 适配 toJSON 返回类型 |
|
|
404
|
+
| `src/tools/builtin/write-file.ts` | 修改 | append undo |
|
|
405
|
+
| `src/providers/gemini.ts` | 修改 | 空白 text parts |
|
|
406
|
+
| `src/repl/clipboard.ts` | 修改 | 路径转义 |
|
|
407
|
+
| `src/tools/diff-utils.ts` | 修改 | 文档化 |
|
|
408
|
+
| `src/tools/permissions.ts` | 修改 | 文档化 |
|
|
409
|
+
| `src/core/constants.ts` | 修改 | VERSION 0.1.57 → 0.1.58 |
|
|
410
|
+
| `package.json` | 修改 | version 0.1.57 → 0.1.58 |
|
|
411
|
+
|
|
412
|
+
### 版本与收尾
|
|
413
|
+
- `src/core/constants.ts`:VERSION `0.1.57` → `0.1.58`
|
|
414
|
+
- `package.json`:version 同步
|
|
415
|
+
- 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
|
|
416
|
+
|
|
417
|
+
### 代码审查完结状态(v0.1.35+ 增量)
|
|
418
|
+
- **高危 H1–H6**:全部已修复(v0.1.56)
|
|
419
|
+
- **中危 M1–M16**:全部已修复(v0.1.57)
|
|
420
|
+
- **低危 L1–L21**:全部已修复(v0.1.58)
|
|
421
|
+
- **安全债务清零**
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 本轮开发完成记录(2026-03-10,v0.1.56 → v0.1.57)
|
|
426
|
+
|
|
427
|
+
### 代码质量修复:16 个中危问题全部修复
|
|
428
|
+
|
|
429
|
+
**审查续篇**:继 v0.1.56 修复 6 个高危后,本轮修复全部 16 个中危问题。
|
|
430
|
+
|
|
431
|
+
| # | 文件 | 修复内容 |
|
|
432
|
+
|---|------|---------|
|
|
433
|
+
| M1 | `commands/index.ts` | `/config set` 原型污染防护(禁止 `__proto__`/`constructor`/`prototype` 路径) |
|
|
434
|
+
| M2 | `commands/index.ts` | `/config show` API Key 掩码(显示 `sk-d***b0` 格式) |
|
|
435
|
+
| M3 | `repl.ts` | `FREE_ROUND_TOOLS` 连续免费轮次上限(MAX_CONSECUTIVE_FREE_ROUNDS=5),防无限循环 |
|
|
436
|
+
| M4 | `openai-compatible.ts` | 截断 JSON 修复时 stderr 警告(提示参数可能丢失) |
|
|
437
|
+
| M5 | `claude.ts` | `chatWithToolsStream` done 事件兜底(`doneEmitted` 标志 + 流结束后补发) |
|
|
438
|
+
| M6 | `claude.ts` | 非 base64 图片 URL 跳过时 stderr 警告 |
|
|
439
|
+
| M7 | `claude.ts` | AbortError/TimeoutError 不再被 `wrapError` 包装为 ProviderError(正确冒泡) |
|
|
440
|
+
| M8 | `openai-compatible.ts` | HALLUCINATION_PATTERNS 收紧:要求文件扩展名或路径引号上下文,减少误报 |
|
|
441
|
+
| M9 | `openai-compatible.ts` | 非流式降级路径保留 `reasoningContent`(DeepSeek-Reasoner 推理内容) |
|
|
442
|
+
| M10 | `deepseek.ts` + `kimi.ts` | Plan C 重试后二次校验:仍虚假声明时 stderr 警告用户手动检查 |
|
|
443
|
+
| M11 | `renderer.ts` | `printTable` ANSI 感知列对齐(用 `stripAnsi` 计算可见宽度) |
|
|
444
|
+
| M12 | `renderer.ts` | `wrapText` ANSI 样式跨行延续(折行后重发活跃样式序列,行末发 reset) |
|
|
445
|
+
| M13 | `theme.ts` | `resolveColor` 无效颜色名 stderr 警告(不再静默回退) |
|
|
446
|
+
| M14 | `session.ts` | `fork()` 深拷贝 multimodal 消息(嵌套 content 数组不再共享引用) |
|
|
447
|
+
| M15 | `bash.ts` | Undo 追踪限制文档化(仅 CWD 子级、仅常见创建命令、不追踪管道/脚本产生) |
|
|
448
|
+
| M16 | `edit-file.ts` | `findSimilarLines` 500KB 文件大小保护(跳过大文件避免性能问题) |
|
|
449
|
+
|
|
450
|
+
### 变更文件汇总
|
|
451
|
+
|
|
452
|
+
| 文件 | 变更类型 | 说明 |
|
|
453
|
+
|------|---------|------|
|
|
454
|
+
| `src/repl/commands/index.ts` | 修改 | M1 原型污染 + M2 API Key 掩码 |
|
|
455
|
+
| `src/repl/repl.ts` | 修改 | M3 连续免费轮次上限 |
|
|
456
|
+
| `src/providers/openai-compatible.ts` | 修改 | M4 JSON 修复警告 + M8 模式收紧 + M9 reasoningContent |
|
|
457
|
+
| `src/providers/claude.ts` | 修改 | M5 done 兜底 + M6 图片警告 + M7 AbortError |
|
|
458
|
+
| `src/providers/deepseek.ts` | 修改 | M10 重试二次校验 |
|
|
459
|
+
| `src/providers/kimi.ts` | 修改 | M10 重试二次校验 |
|
|
460
|
+
| `src/repl/renderer.ts` | 修改 | M11 表格对齐 + M12 折行样式 |
|
|
461
|
+
| `src/repl/theme.ts` | 修改 | M13 无效颜色警告 |
|
|
462
|
+
| `src/session/session.ts` | 修改 | M14 fork 深拷贝 |
|
|
463
|
+
| `src/tools/builtin/bash.ts` | 修改 | M15 限制文档 |
|
|
464
|
+
| `src/tools/builtin/edit-file.ts` | 修改 | M16 大文件保护 |
|
|
465
|
+
| `src/core/constants.ts` | 修改 | VERSION 0.1.56 → 0.1.57 |
|
|
466
|
+
| `package.json` | 修改 | version 0.1.56 → 0.1.57 |
|
|
467
|
+
|
|
468
|
+
### 版本与收尾
|
|
469
|
+
- `src/core/constants.ts`:VERSION `0.1.56` → `0.1.57`
|
|
470
|
+
- `package.json`:version 同步
|
|
471
|
+
- 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
|
|
472
|
+
- 发布:`npm publish` → `jinzd-ai-cli@0.1.57`
|
|
473
|
+
|
|
474
|
+
### 代码审查完结状态(v0.1.35+ 增量)
|
|
475
|
+
- **高危 H1–H6**:全部已修复(v0.1.56)
|
|
476
|
+
- **中危 M1–M16**:全部已修复(v0.1.57)
|
|
477
|
+
- **低危 L1–L28**:待后续改进(多为代码风格、注释、微优化)
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
353
481
|
## 本轮开发完成记录(2026-03-10,v0.1.55 → v0.1.56)
|
|
354
482
|
|
|
355
483
|
### 安全修复:v0.1.35+ 增量代码审查 — 6 个高危问题全部修复
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
31
31
|
VERSION,
|
|
32
32
|
runTestsTool
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-7RQ6QCDG.js";
|
|
34
34
|
|
|
35
35
|
// src/index.ts
|
|
36
36
|
import { program } from "commander";
|
|
@@ -343,10 +343,14 @@ ${err}`
|
|
|
343
343
|
current[keys[keys.length - 1]] = value;
|
|
344
344
|
this.save();
|
|
345
345
|
}
|
|
346
|
-
/**
|
|
347
|
-
|
|
346
|
+
/** 获取完整配置对象的格式化 JSON 字符串(用于 /config show 等展示) */
|
|
347
|
+
toFormattedJSON() {
|
|
348
348
|
return JSON.stringify(this.config, null, 2);
|
|
349
349
|
}
|
|
350
|
+
/** 获取完整配置对象(JSON 兼容的原始对象) */
|
|
351
|
+
toJSON() {
|
|
352
|
+
return structuredClone(this.config);
|
|
353
|
+
}
|
|
350
354
|
};
|
|
351
355
|
|
|
352
356
|
// src/providers/claude.ts
|
|
@@ -897,7 +901,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
897
901
|
const parts = [];
|
|
898
902
|
for (const p of content) {
|
|
899
903
|
if (p.type === "text") {
|
|
900
|
-
if (p.text.
|
|
904
|
+
if (p.text.length > 0) parts.push({ text: p.text });
|
|
901
905
|
} else if (p.type === "image_url") {
|
|
902
906
|
const match = p.image_url.url.match(/^data:([^;]+);base64,(.+)$/);
|
|
903
907
|
if (match) {
|
|
@@ -2108,6 +2112,15 @@ var Session = class _Session {
|
|
|
2108
2112
|
this.created = /* @__PURE__ */ new Date();
|
|
2109
2113
|
this.updated = /* @__PURE__ */ new Date();
|
|
2110
2114
|
}
|
|
2115
|
+
/**
|
|
2116
|
+
* 更新 session 关联的 provider 和 model(在 /provider 或 /model 切换时调用)。
|
|
2117
|
+
* 保留对话历史和所有状态,仅更新元数据。
|
|
2118
|
+
*/
|
|
2119
|
+
updateProvider(provider, model) {
|
|
2120
|
+
this.provider = provider;
|
|
2121
|
+
this.model = model;
|
|
2122
|
+
this.updated = /* @__PURE__ */ new Date();
|
|
2123
|
+
}
|
|
2111
2124
|
addMessage(message) {
|
|
2112
2125
|
this.messages.push(message);
|
|
2113
2126
|
this.updated = /* @__PURE__ */ new Date();
|
|
@@ -2420,9 +2433,9 @@ var SessionManager = class {
|
|
|
2420
2433
|
|
|
2421
2434
|
// src/repl/repl.ts
|
|
2422
2435
|
import * as readline from "readline";
|
|
2423
|
-
import { existsSync as existsSync19, readFileSync as
|
|
2436
|
+
import { existsSync as existsSync19, readFileSync as readFileSync12, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
|
|
2424
2437
|
import { join as join14, resolve as resolve5, extname as extname4, dirname as dirname6, basename as basename6 } from "path";
|
|
2425
|
-
import
|
|
2438
|
+
import chalk9 from "chalk";
|
|
2426
2439
|
|
|
2427
2440
|
// src/repl/renderer.ts
|
|
2428
2441
|
import chalk2 from "chalk";
|
|
@@ -2967,7 +2980,7 @@ function getGitContext(cwd = process.cwd()) {
|
|
|
2967
2980
|
}
|
|
2968
2981
|
const logOutput = runGit("log --oneline -3", cwd) ?? "";
|
|
2969
2982
|
const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
|
|
2970
|
-
const unpushedOutput = runGit("log @{u}..HEAD --oneline
|
|
2983
|
+
const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
|
|
2971
2984
|
const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
|
|
2972
2985
|
return {
|
|
2973
2986
|
branch,
|
|
@@ -3032,43 +3045,41 @@ var UndoStack = class {
|
|
|
3032
3045
|
return;
|
|
3033
3046
|
}
|
|
3034
3047
|
}
|
|
3035
|
-
this.
|
|
3048
|
+
this.pushEntry({
|
|
3036
3049
|
filePath,
|
|
3037
3050
|
previousContent,
|
|
3038
3051
|
description,
|
|
3039
3052
|
timestamp: /* @__PURE__ */ new Date()
|
|
3040
3053
|
});
|
|
3041
|
-
if (this.stack.length > MAX_UNDO_DEPTH) {
|
|
3042
|
-
this.stack.shift();
|
|
3043
|
-
}
|
|
3044
3054
|
}
|
|
3045
3055
|
/**
|
|
3046
3056
|
* 推入一个新建文件的条目(previousContent=null),undo 时删除该文件。
|
|
3047
3057
|
* 用于 bash 工具执行后检测到的新建文件。
|
|
3048
3058
|
*/
|
|
3049
3059
|
pushNewFile(filePath, description) {
|
|
3050
|
-
this.
|
|
3060
|
+
this.pushEntry({
|
|
3051
3061
|
filePath,
|
|
3052
3062
|
previousContent: null,
|
|
3053
3063
|
description,
|
|
3054
3064
|
timestamp: /* @__PURE__ */ new Date()
|
|
3055
3065
|
});
|
|
3056
|
-
if (this.stack.length > MAX_UNDO_DEPTH) {
|
|
3057
|
-
this.stack.shift();
|
|
3058
|
-
}
|
|
3059
3066
|
}
|
|
3060
3067
|
/**
|
|
3061
3068
|
* 推入一个新建目录的条目(previousContent=null, isDirectory=true),
|
|
3062
3069
|
* undo 时尝试 rmdir(仅空目录可删)。
|
|
3063
3070
|
*/
|
|
3064
3071
|
pushNewDir(dirPath, description) {
|
|
3065
|
-
this.
|
|
3072
|
+
this.pushEntry({
|
|
3066
3073
|
filePath: dirPath,
|
|
3067
3074
|
previousContent: null,
|
|
3068
3075
|
description,
|
|
3069
3076
|
timestamp: /* @__PURE__ */ new Date(),
|
|
3070
3077
|
isDirectory: true
|
|
3071
3078
|
});
|
|
3079
|
+
}
|
|
3080
|
+
/** 内部统一入栈方法,含溢出裁剪 */
|
|
3081
|
+
pushEntry(entry) {
|
|
3082
|
+
this.stack.push(entry);
|
|
3072
3083
|
if (this.stack.length > MAX_UNDO_DEPTH) {
|
|
3073
3084
|
this.stack.shift();
|
|
3074
3085
|
}
|
|
@@ -3310,7 +3321,7 @@ function readClipboardImage() {
|
|
|
3310
3321
|
return outPath;
|
|
3311
3322
|
}
|
|
3312
3323
|
function _readWin32(outPath) {
|
|
3313
|
-
const escapedPath = outPath.replace(
|
|
3324
|
+
const escapedPath = outPath.replace(/'/g, "''");
|
|
3314
3325
|
const script = [
|
|
3315
3326
|
"Add-Type -AssemblyName System.Windows.Forms",
|
|
3316
3327
|
"Add-Type -AssemblyName System.Drawing",
|
|
@@ -3703,9 +3714,16 @@ function createDefaultCommands() {
|
|
|
3703
3714
|
return;
|
|
3704
3715
|
}
|
|
3705
3716
|
await ctx.generateDevStateSnapshot();
|
|
3717
|
+
const session = ctx.sessions.current;
|
|
3718
|
+
const msgCount = session?.messages.length ?? 0;
|
|
3706
3719
|
ctx.setProvider(targetId);
|
|
3707
|
-
|
|
3708
|
-
|
|
3720
|
+
if (session) {
|
|
3721
|
+
session.updateProvider(targetId, ctx.getCurrentModel());
|
|
3722
|
+
await ctx.sessions.save();
|
|
3723
|
+
}
|
|
3724
|
+
ctx.renderer.printSuccess(
|
|
3725
|
+
`Switched to provider: ${targetId}` + (msgCount > 0 ? ` (conversation preserved: ${msgCount} messages)` : "")
|
|
3726
|
+
);
|
|
3709
3727
|
}
|
|
3710
3728
|
},
|
|
3711
3729
|
{
|
|
@@ -3739,9 +3757,16 @@ function createDefaultCommands() {
|
|
|
3739
3757
|
return;
|
|
3740
3758
|
}
|
|
3741
3759
|
await ctx.generateDevStateSnapshot();
|
|
3760
|
+
const session = ctx.sessions.current;
|
|
3761
|
+
const msgCount = session?.messages.length ?? 0;
|
|
3742
3762
|
ctx.setProvider(ctx.getCurrentProvider(), targetModel);
|
|
3743
|
-
|
|
3744
|
-
|
|
3763
|
+
if (session) {
|
|
3764
|
+
session.updateProvider(ctx.getCurrentProvider(), targetModel);
|
|
3765
|
+
await ctx.sessions.save();
|
|
3766
|
+
}
|
|
3767
|
+
ctx.renderer.printSuccess(
|
|
3768
|
+
`Switched to model: ${targetModel}` + (msgCount > 0 ? ` (conversation preserved: ${msgCount} messages)` : "")
|
|
3769
|
+
);
|
|
3745
3770
|
}
|
|
3746
3771
|
},
|
|
3747
3772
|
{
|
|
@@ -4211,17 +4236,17 @@ ${text}
|
|
|
4211
4236
|
return;
|
|
4212
4237
|
}
|
|
4213
4238
|
if (sub === "show") {
|
|
4214
|
-
const
|
|
4215
|
-
const masked = JSON.parse(JSON.stringify(raw));
|
|
4239
|
+
const masked = config.toJSON();
|
|
4216
4240
|
if (masked.apiKeys && typeof masked.apiKeys === "object") {
|
|
4217
|
-
|
|
4218
|
-
|
|
4241
|
+
const keys = masked.apiKeys;
|
|
4242
|
+
for (const key of Object.keys(keys)) {
|
|
4243
|
+
const val = keys[key];
|
|
4219
4244
|
if (typeof val === "string" && val.length > 8) {
|
|
4220
|
-
|
|
4245
|
+
keys[key] = val.slice(0, 4) + "****" + val.slice(-4);
|
|
4221
4246
|
}
|
|
4222
4247
|
}
|
|
4223
4248
|
}
|
|
4224
|
-
console.log(masked);
|
|
4249
|
+
console.log(JSON.stringify(masked, null, 2));
|
|
4225
4250
|
return;
|
|
4226
4251
|
}
|
|
4227
4252
|
if (sub === "get") {
|
|
@@ -4681,7 +4706,7 @@ ${hint}` : "")
|
|
|
4681
4706
|
description: "Run project tests and show structured report",
|
|
4682
4707
|
usage: "/test [command|filter]",
|
|
4683
4708
|
async execute(args, _ctx) {
|
|
4684
|
-
const { executeTests } = await import("./run-tests-
|
|
4709
|
+
const { executeTests } = await import("./run-tests-CUBYICNN.js");
|
|
4685
4710
|
const argStr = args.join(" ").trim();
|
|
4686
4711
|
let testArgs = {};
|
|
4687
4712
|
if (argStr) {
|
|
@@ -5507,7 +5532,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
5507
5532
|
".pyc"
|
|
5508
5533
|
]);
|
|
5509
5534
|
function isBinaryBuffer(buf) {
|
|
5510
|
-
const sample = buf.
|
|
5535
|
+
const sample = buf.subarray(0, 512);
|
|
5511
5536
|
let nullCount = 0;
|
|
5512
5537
|
for (let i = 0; i < sample.length; i++) {
|
|
5513
5538
|
if (sample[i] === 0) nullCount++;
|
|
@@ -5699,9 +5724,7 @@ var writeFileTool = {
|
|
|
5699
5724
|
const encoding = args["encoding"] ?? "utf-8";
|
|
5700
5725
|
const appendMode = String(args["append"] ?? "false").toLowerCase() === "true";
|
|
5701
5726
|
if (!filePath) throw new Error("path is required");
|
|
5702
|
-
|
|
5703
|
-
undoStack.push(filePath, `write_file: ${filePath}`);
|
|
5704
|
-
}
|
|
5727
|
+
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
5705
5728
|
mkdirSync5(dirname4(filePath), { recursive: true });
|
|
5706
5729
|
if (appendMode) {
|
|
5707
5730
|
appendFileSync2(filePath, content, encoding);
|
|
@@ -6004,7 +6027,8 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
6004
6027
|
return a.name.localeCompare(b.name);
|
|
6005
6028
|
});
|
|
6006
6029
|
for (const entry of sorted) {
|
|
6007
|
-
|
|
6030
|
+
const USEFUL_DOT_ENTRIES = /* @__PURE__ */ new Set([".github", ".vscode", ".idea", ".gitignore", ".gitattributes", ".editorconfig", ".eslintrc", ".prettierrc", ".npmrc"]);
|
|
6031
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".") && !USEFUL_DOT_ENTRIES.has(entry.name) && !entry.name.startsWith(".env")) {
|
|
6008
6032
|
if (entry.isDirectory()) {
|
|
6009
6033
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/ (skipped)`);
|
|
6010
6034
|
}
|
|
@@ -6169,13 +6193,18 @@ function collectFiles(dirPath, filePattern, results, regex, contextLines, maxRes
|
|
|
6169
6193
|
}
|
|
6170
6194
|
}
|
|
6171
6195
|
function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, results) {
|
|
6196
|
+
try {
|
|
6197
|
+
const stat = statSync6(fullPath);
|
|
6198
|
+
if (stat.size > 1e6) return;
|
|
6199
|
+
} catch {
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
6172
6202
|
let content;
|
|
6173
6203
|
try {
|
|
6174
6204
|
content = readFileSync7(fullPath, "utf-8");
|
|
6175
6205
|
} catch {
|
|
6176
6206
|
return;
|
|
6177
6207
|
}
|
|
6178
|
-
if (content.length > 1e6) return;
|
|
6179
6208
|
const lines = content.split("\n");
|
|
6180
6209
|
regex.lastIndex = 0;
|
|
6181
6210
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -6384,7 +6413,7 @@ var runInteractiveTool = {
|
|
|
6384
6413
|
process.stderr.write(stdinTypeWarning);
|
|
6385
6414
|
return rawStdin.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6386
6415
|
})() : [];
|
|
6387
|
-
const timeout = Number(args["timeout"] ?? 2e4);
|
|
6416
|
+
const timeout = Math.min(Math.max(Number(args["timeout"] ?? 2e4), 1e3), 3e5);
|
|
6388
6417
|
if (!executable) {
|
|
6389
6418
|
throw new Error("executable is required");
|
|
6390
6419
|
}
|
|
@@ -6706,7 +6735,7 @@ var saveLastResponseTool = {
|
|
|
6706
6735
|
};
|
|
6707
6736
|
|
|
6708
6737
|
// src/tools/builtin/save-memory.ts
|
|
6709
|
-
import { existsSync as existsSync13,
|
|
6738
|
+
import { existsSync as existsSync13, statSync as statSync8, appendFileSync as appendFileSync3, mkdirSync as mkdirSync7 } from "fs";
|
|
6710
6739
|
import { join as join9 } from "path";
|
|
6711
6740
|
import { homedir as homedir3 } from "os";
|
|
6712
6741
|
function getMemoryFilePath() {
|
|
@@ -6744,10 +6773,8 @@ var saveMemoryTool = {
|
|
|
6744
6773
|
${content}
|
|
6745
6774
|
`;
|
|
6746
6775
|
appendFileSync3(memoryPath, entry, "utf-8");
|
|
6747
|
-
const
|
|
6748
|
-
|
|
6749
|
-
const byteSize = Buffer.byteLength(fullContent, "utf-8");
|
|
6750
|
-
return `Memory saved successfully. Total: ${entryCount} entries, ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
6776
|
+
const byteSize = statSync8(memoryPath).size;
|
|
6777
|
+
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
6751
6778
|
}
|
|
6752
6779
|
};
|
|
6753
6780
|
|
|
@@ -7021,21 +7048,12 @@ function formatResults(query, data, requested) {
|
|
|
7021
7048
|
return header + "\n" + results.join("\n\n");
|
|
7022
7049
|
}
|
|
7023
7050
|
|
|
7024
|
-
// src/tools/
|
|
7025
|
-
|
|
7026
|
-
var spawnAgentContext = {
|
|
7027
|
-
provider: null,
|
|
7028
|
-
model: "",
|
|
7029
|
-
systemPrompt: void 0,
|
|
7030
|
-
modelParams: {},
|
|
7031
|
-
configManager: null
|
|
7032
|
-
};
|
|
7033
|
-
var PREFIX = chalk8.dim(" \u2503 ");
|
|
7034
|
-
var MAX_TOOL_OUTPUT_CHARS = 12e3;
|
|
7051
|
+
// src/tools/truncate.ts
|
|
7052
|
+
var MAX_TOOL_OUTPUT_CHARS2 = 12e3;
|
|
7035
7053
|
function truncateOutput(content, toolName) {
|
|
7036
|
-
if (content.length <=
|
|
7037
|
-
const keepHead = Math.floor(
|
|
7038
|
-
const keepTail = Math.floor(
|
|
7054
|
+
if (content.length <= MAX_TOOL_OUTPUT_CHARS2) return content;
|
|
7055
|
+
const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.7);
|
|
7056
|
+
const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.2);
|
|
7039
7057
|
const omitted = content.length - keepHead - keepTail;
|
|
7040
7058
|
const lines = content.split("\n").length;
|
|
7041
7059
|
const head = content.slice(0, keepHead);
|
|
@@ -7046,6 +7064,16 @@ function truncateOutput(content, toolName) {
|
|
|
7046
7064
|
|
|
7047
7065
|
` + tail;
|
|
7048
7066
|
}
|
|
7067
|
+
|
|
7068
|
+
// src/tools/builtin/spawn-agent.ts
|
|
7069
|
+
var spawnAgentContext = {
|
|
7070
|
+
provider: null,
|
|
7071
|
+
model: "",
|
|
7072
|
+
systemPrompt: void 0,
|
|
7073
|
+
modelParams: {},
|
|
7074
|
+
configManager: null
|
|
7075
|
+
};
|
|
7076
|
+
var PREFIX = theme.dim(" \u2503 ");
|
|
7049
7077
|
var SubAgentExecutor = class {
|
|
7050
7078
|
constructor(registry) {
|
|
7051
7079
|
this.registry = registry;
|
|
@@ -7068,7 +7096,7 @@ var SubAgentExecutor = class {
|
|
|
7068
7096
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
7069
7097
|
if (dangerLevel === "destructive") {
|
|
7070
7098
|
this.printPrefixed(
|
|
7071
|
-
|
|
7099
|
+
theme.error("\u26A0 BLOCKED: ") + `Destructive operation ${call.name} not allowed in sub-agent`
|
|
7072
7100
|
);
|
|
7073
7101
|
return {
|
|
7074
7102
|
callId: call.id,
|
|
@@ -7104,9 +7132,9 @@ var SubAgentExecutor = class {
|
|
|
7104
7132
|
}
|
|
7105
7133
|
printToolCall(call, dangerLevel) {
|
|
7106
7134
|
console.log(PREFIX);
|
|
7107
|
-
const icon = dangerLevel === "write" ?
|
|
7108
|
-
const roundBadge = this.totalRounds > 0 ?
|
|
7109
|
-
console.log(PREFIX + icon +
|
|
7135
|
+
const icon = dangerLevel === "write" ? theme.warning("\u270E Tool: ") : theme.toolCall("\u2699 Tool: ");
|
|
7136
|
+
const roundBadge = this.totalRounds > 0 ? theme.dim(` [${this.round}/${this.totalRounds}]`) : "";
|
|
7137
|
+
console.log(PREFIX + icon + call.name + roundBadge);
|
|
7110
7138
|
for (const [key, val] of Object.entries(call.arguments)) {
|
|
7111
7139
|
let valStr;
|
|
7112
7140
|
if (Array.isArray(val)) {
|
|
@@ -7117,22 +7145,22 @@ var SubAgentExecutor = class {
|
|
|
7117
7145
|
} else {
|
|
7118
7146
|
valStr = String(val);
|
|
7119
7147
|
}
|
|
7120
|
-
console.log(PREFIX +
|
|
7148
|
+
console.log(PREFIX + theme.dim(` ${key}: `) + valStr);
|
|
7121
7149
|
}
|
|
7122
7150
|
}
|
|
7123
7151
|
printToolResult(name, content, isError, wasTruncated) {
|
|
7124
7152
|
if (isError) {
|
|
7125
7153
|
console.log(
|
|
7126
|
-
PREFIX +
|
|
7154
|
+
PREFIX + theme.error(`\u26A0 ${name} error: `) + theme.dim(content.slice(0, 300))
|
|
7127
7155
|
);
|
|
7128
7156
|
} else {
|
|
7129
7157
|
const lines = content.split("\n");
|
|
7130
7158
|
const maxLines = name === "run_interactive" ? 40 : 8;
|
|
7131
7159
|
const preview = lines.slice(0, maxLines);
|
|
7132
|
-
const prefixedPreview = preview.map((l) => PREFIX + " " +
|
|
7133
|
-
const moreLines = lines.length > maxLines ? "\n" + PREFIX +
|
|
7134
|
-
const truncNote = wasTruncated ? "\n" + PREFIX +
|
|
7135
|
-
console.log(PREFIX +
|
|
7160
|
+
const prefixedPreview = preview.map((l) => PREFIX + " " + theme.dim(l)).join("\n");
|
|
7161
|
+
const moreLines = lines.length > maxLines ? "\n" + PREFIX + theme.dim(` ... (${lines.length - maxLines} more lines)`) : "";
|
|
7162
|
+
const truncNote = wasTruncated ? "\n" + PREFIX + theme.warning(` \u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD`) : "";
|
|
7163
|
+
console.log(PREFIX + theme.success("\u2713 Result:"));
|
|
7136
7164
|
console.log(prefixedPreview + moreLines + truncNote);
|
|
7137
7165
|
}
|
|
7138
7166
|
}
|
|
@@ -7162,25 +7190,25 @@ ${task}
|
|
|
7162
7190
|
}
|
|
7163
7191
|
function printSubAgentHeader(task, maxRounds) {
|
|
7164
7192
|
console.log();
|
|
7165
|
-
console.log(
|
|
7166
|
-
console.log(PREFIX +
|
|
7193
|
+
console.log(theme.dim(" \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
7194
|
+
console.log(PREFIX + theme.toolCall("\u{1F916} Sub-Agent Spawned"));
|
|
7167
7195
|
console.log(
|
|
7168
|
-
PREFIX +
|
|
7196
|
+
PREFIX + theme.dim("Task: ") + (task.slice(0, 120) + (task.length > 120 ? "..." : ""))
|
|
7169
7197
|
);
|
|
7170
|
-
console.log(PREFIX +
|
|
7171
|
-
console.log(
|
|
7198
|
+
console.log(PREFIX + theme.dim(`Max rounds: ${maxRounds}`));
|
|
7199
|
+
console.log(theme.dim(" \u2503"));
|
|
7172
7200
|
}
|
|
7173
7201
|
function printSubAgentFooter(usage) {
|
|
7174
7202
|
console.log(PREFIX);
|
|
7175
|
-
console.log(PREFIX +
|
|
7203
|
+
console.log(PREFIX + theme.toolCall("Sub-Agent Complete"));
|
|
7176
7204
|
if (usage.inputTokens > 0 || usage.outputTokens > 0) {
|
|
7177
7205
|
console.log(
|
|
7178
|
-
PREFIX +
|
|
7206
|
+
PREFIX + theme.dim(
|
|
7179
7207
|
`Tokens: ${usage.inputTokens} in / ${usage.outputTokens} out`
|
|
7180
7208
|
)
|
|
7181
7209
|
);
|
|
7182
7210
|
}
|
|
7183
|
-
console.log(
|
|
7211
|
+
console.log(theme.dim(" \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
7184
7212
|
console.log();
|
|
7185
7213
|
}
|
|
7186
7214
|
var spawnAgentTool = {
|
|
@@ -7426,18 +7454,21 @@ var ToolRegistry = class {
|
|
|
7426
7454
|
};
|
|
7427
7455
|
|
|
7428
7456
|
// src/tools/executor.ts
|
|
7429
|
-
import
|
|
7430
|
-
import { existsSync as existsSync15, readFileSync as
|
|
7457
|
+
import chalk8 from "chalk";
|
|
7458
|
+
import { existsSync as existsSync15, readFileSync as readFileSync8 } from "fs";
|
|
7431
7459
|
|
|
7432
7460
|
// src/tools/hooks.ts
|
|
7433
7461
|
import { execSync as execSync6 } from "child_process";
|
|
7462
|
+
function shellEscape(value) {
|
|
7463
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
7464
|
+
}
|
|
7434
7465
|
function runHook(template, vars) {
|
|
7435
7466
|
if (!template) return;
|
|
7436
7467
|
let cmd = template;
|
|
7437
|
-
cmd = cmd.replace(/\{tool\}/g, vars.tool);
|
|
7438
|
-
cmd = cmd.replace(/\{dangerLevel\}/g, vars.dangerLevel ?? "");
|
|
7439
|
-
cmd = cmd.replace(/\{args\}/g, vars.args ?? "");
|
|
7440
|
-
cmd = cmd.replace(/\{status\}/g, vars.status ?? "");
|
|
7468
|
+
cmd = cmd.replace(/\{tool\}/g, shellEscape(vars.tool));
|
|
7469
|
+
cmd = cmd.replace(/\{dangerLevel\}/g, shellEscape(vars.dangerLevel ?? ""));
|
|
7470
|
+
cmd = cmd.replace(/\{args\}/g, shellEscape(vars.args ?? ""));
|
|
7471
|
+
cmd = cmd.replace(/\{status\}/g, shellEscape(vars.status ?? ""));
|
|
7441
7472
|
try {
|
|
7442
7473
|
execSync6(cmd, {
|
|
7443
7474
|
timeout: 5e3,
|
|
@@ -7467,21 +7498,6 @@ function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "co
|
|
|
7467
7498
|
}
|
|
7468
7499
|
|
|
7469
7500
|
// src/tools/executor.ts
|
|
7470
|
-
var MAX_TOOL_OUTPUT_CHARS2 = 12e3;
|
|
7471
|
-
function truncateOutput2(content, toolName) {
|
|
7472
|
-
if (content.length <= MAX_TOOL_OUTPUT_CHARS2) return content;
|
|
7473
|
-
const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.7);
|
|
7474
|
-
const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.2);
|
|
7475
|
-
const omitted = content.length - keepHead - keepTail;
|
|
7476
|
-
const lines = content.split("\n").length;
|
|
7477
|
-
const head = content.slice(0, keepHead);
|
|
7478
|
-
const tail = content.slice(content.length - keepTail);
|
|
7479
|
-
return head + `
|
|
7480
|
-
|
|
7481
|
-
... [\u8F93\u51FA\u5DF2\u622A\u65AD\uFF1A\u5171 ${content.length} \u5B57\u7B26 / ${lines} \u884C\uFF0C\u7701\u7565\u4E86\u4E2D\u95F4 ${omitted} \u5B57\u7B26\u3002\u5982\u9700\u67E5\u770B\u5B8C\u6574\u5185\u5BB9\uFF0C\u8BF7\u4F7F\u7528 read_file \u5206\u6BB5\u8BFB\u53D6` + (toolName === "bash" ? "\uFF0C\u6216\u7F29\u5C0F\u547D\u4EE4\u8303\u56F4" : "") + `] ...
|
|
7482
|
-
|
|
7483
|
-
` + tail;
|
|
7484
|
-
}
|
|
7485
7501
|
var ToolExecutor = class {
|
|
7486
7502
|
constructor(registry) {
|
|
7487
7503
|
this.registry = registry;
|
|
@@ -7553,7 +7569,7 @@ var ToolExecutor = class {
|
|
|
7553
7569
|
this.printToolCall(call);
|
|
7554
7570
|
try {
|
|
7555
7571
|
const rawContent = await tool.execute(call.arguments);
|
|
7556
|
-
const content =
|
|
7572
|
+
const content = truncateOutput(rawContent, call.name);
|
|
7557
7573
|
const wasTruncated = content !== rawContent;
|
|
7558
7574
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
7559
7575
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
@@ -7592,7 +7608,7 @@ var ToolExecutor = class {
|
|
|
7592
7608
|
}
|
|
7593
7609
|
try {
|
|
7594
7610
|
const rawContent = await tool.execute(call.arguments);
|
|
7595
|
-
const content =
|
|
7611
|
+
const content = truncateOutput(rawContent, call.name);
|
|
7596
7612
|
const wasTruncated = content !== rawContent;
|
|
7597
7613
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
7598
7614
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
@@ -7650,7 +7666,7 @@ var ToolExecutor = class {
|
|
|
7650
7666
|
for (let i = 0; i < calls.length; i++) {
|
|
7651
7667
|
const call = calls[i];
|
|
7652
7668
|
const filePath = String(call.arguments["path"] ?? "");
|
|
7653
|
-
console.log(theme.warning(` [${i + 1}] `) +
|
|
7669
|
+
console.log(theme.warning(` [${i + 1}] `) + chalk8.white(call.name) + theme.dim(": ") + theme.accent(filePath));
|
|
7654
7670
|
this.printDiffPreview(call);
|
|
7655
7671
|
}
|
|
7656
7672
|
console.log(theme.dim("\u2500".repeat(50)));
|
|
@@ -7667,7 +7683,7 @@ var ToolExecutor = class {
|
|
|
7667
7683
|
}
|
|
7668
7684
|
try {
|
|
7669
7685
|
const rawContent = await tool.execute(call.arguments);
|
|
7670
|
-
const content =
|
|
7686
|
+
const content = truncateOutput(rawContent, call.name);
|
|
7671
7687
|
const wasTruncated = content !== rawContent;
|
|
7672
7688
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
7673
7689
|
results.push({ callId: call.id, content, isError: false });
|
|
@@ -7743,7 +7759,7 @@ var ToolExecutor = class {
|
|
|
7743
7759
|
console.log();
|
|
7744
7760
|
const icon = dangerLevel === "write" ? theme.toolCall("\u270E Tool: ") : theme.heading(theme.accent("\u2699 Tool: "));
|
|
7745
7761
|
const roundBadge = this.totalRounds > 0 ? theme.dim(` [${this.round}/${this.totalRounds}]`) : "";
|
|
7746
|
-
console.log(icon +
|
|
7762
|
+
console.log(icon + chalk8.white(call.name) + roundBadge);
|
|
7747
7763
|
for (const [key, val] of Object.entries(call.arguments)) {
|
|
7748
7764
|
let valStr;
|
|
7749
7765
|
if (Array.isArray(val)) {
|
|
@@ -7754,7 +7770,7 @@ var ToolExecutor = class {
|
|
|
7754
7770
|
} else {
|
|
7755
7771
|
valStr = String(val);
|
|
7756
7772
|
}
|
|
7757
|
-
console.log(theme.dim(` ${key}: `) +
|
|
7773
|
+
console.log(theme.dim(` ${key}: `) + chalk8.white(valStr));
|
|
7758
7774
|
}
|
|
7759
7775
|
}
|
|
7760
7776
|
/**
|
|
@@ -7771,7 +7787,7 @@ var ToolExecutor = class {
|
|
|
7771
7787
|
if (existsSync15(filePath)) {
|
|
7772
7788
|
let oldContent;
|
|
7773
7789
|
try {
|
|
7774
|
-
oldContent =
|
|
7790
|
+
oldContent = readFileSync8(filePath, "utf-8");
|
|
7775
7791
|
} catch {
|
|
7776
7792
|
return;
|
|
7777
7793
|
}
|
|
@@ -7821,7 +7837,7 @@ var ToolExecutor = class {
|
|
|
7821
7837
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
7822
7838
|
let fileContent;
|
|
7823
7839
|
try {
|
|
7824
|
-
fileContent =
|
|
7840
|
+
fileContent = readFileSync8(filePath, "utf-8");
|
|
7825
7841
|
} catch {
|
|
7826
7842
|
return;
|
|
7827
7843
|
}
|
|
@@ -7846,7 +7862,7 @@ var ToolExecutor = class {
|
|
|
7846
7862
|
const moreLines = lines.length > maxLines ? theme.dim(`
|
|
7847
7863
|
... (${lines.length - maxLines} more lines)`) : "";
|
|
7848
7864
|
const truncatedNote = wasTruncated ? theme.warning(`
|
|
7849
|
-
\u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${
|
|
7865
|
+
\u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${MAX_TOOL_OUTPUT_CHARS} \u5B57\u7B26\u540E\u8FD4\u56DE\u7ED9 AI`) : "";
|
|
7850
7866
|
console.log(theme.toolResult("\u2713 Result: ") + theme.dim(preview) + moreLines + truncatedNote);
|
|
7851
7867
|
}
|
|
7852
7868
|
console.log();
|
|
@@ -8139,9 +8155,13 @@ Managing ${displayName} API Key`);
|
|
|
8139
8155
|
import { existsSync as existsSync16, readFileSync as readFileSync10, readdirSync as readdirSync9, mkdirSync as mkdirSync9 } from "fs";
|
|
8140
8156
|
import { join as join11, extname as extname3 } from "path";
|
|
8141
8157
|
import { execSync as execSync7 } from "child_process";
|
|
8142
|
-
|
|
8158
|
+
|
|
8159
|
+
// src/skills/types.ts
|
|
8160
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
8161
|
+
import { basename as basename5 } from "path";
|
|
8162
|
+
function parseSimpleYaml(yaml) {
|
|
8143
8163
|
const result = {};
|
|
8144
|
-
for (const line of
|
|
8164
|
+
for (const line of yaml.split("\n")) {
|
|
8145
8165
|
const match = line.match(/^(\w+)\s*:\s*(.+)$/);
|
|
8146
8166
|
if (match) {
|
|
8147
8167
|
result[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -8149,6 +8169,45 @@ function parseSimpleYaml(text) {
|
|
|
8149
8169
|
}
|
|
8150
8170
|
return result;
|
|
8151
8171
|
}
|
|
8172
|
+
function parseYamlArray(value) {
|
|
8173
|
+
const bracketMatch = value.match(/^\[(.+)]$/);
|
|
8174
|
+
if (bracketMatch) {
|
|
8175
|
+
return bracketMatch[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
|
|
8176
|
+
}
|
|
8177
|
+
return [value.trim()];
|
|
8178
|
+
}
|
|
8179
|
+
function parseSkillFile(filePath) {
|
|
8180
|
+
let raw;
|
|
8181
|
+
try {
|
|
8182
|
+
raw = readFileSync9(filePath, "utf-8");
|
|
8183
|
+
} catch {
|
|
8184
|
+
return null;
|
|
8185
|
+
}
|
|
8186
|
+
const frontmatterMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
8187
|
+
if (!frontmatterMatch) {
|
|
8188
|
+
return {
|
|
8189
|
+
meta: {
|
|
8190
|
+
name: basename5(filePath, ".md"),
|
|
8191
|
+
description: ""
|
|
8192
|
+
},
|
|
8193
|
+
content: raw.trim(),
|
|
8194
|
+
filePath
|
|
8195
|
+
};
|
|
8196
|
+
}
|
|
8197
|
+
const [, yaml, content] = frontmatterMatch;
|
|
8198
|
+
const parsed = parseSimpleYaml(yaml);
|
|
8199
|
+
return {
|
|
8200
|
+
meta: {
|
|
8201
|
+
name: parsed["name"] ?? basename5(filePath, ".md"),
|
|
8202
|
+
description: parsed["description"] ?? "",
|
|
8203
|
+
tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
|
|
8204
|
+
},
|
|
8205
|
+
content: content.trim(),
|
|
8206
|
+
filePath
|
|
8207
|
+
};
|
|
8208
|
+
}
|
|
8209
|
+
|
|
8210
|
+
// src/repl/custom-commands.ts
|
|
8152
8211
|
function parseCommandFile(filePath) {
|
|
8153
8212
|
let content;
|
|
8154
8213
|
try {
|
|
@@ -8228,9 +8287,11 @@ var CustomCommandManager = class {
|
|
|
8228
8287
|
import { existsSync as existsSync17, readFileSync as readFileSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync10 } from "fs";
|
|
8229
8288
|
import { join as join12 } from "path";
|
|
8230
8289
|
import { homedir as homedir4 } from "os";
|
|
8231
|
-
var DEV_STATE_MAX_CHARS =
|
|
8290
|
+
var DEV_STATE_MAX_CHARS = 6e3;
|
|
8232
8291
|
var SNAPSHOT_PROMPT = `You are about to be replaced by a different AI model. Please generate a structured development state snapshot so the next model can continue seamlessly.
|
|
8233
8292
|
|
|
8293
|
+
CRITICAL: Be SPECIFIC and DETAILED. Include exact values, file paths, format requirements, and constraints \u2014 vague summaries are useless to the next model.
|
|
8294
|
+
|
|
8234
8295
|
Output ONLY the snapshot in the following exact format (no preamble, no explanation):
|
|
8235
8296
|
|
|
8236
8297
|
## Development State Snapshot
|
|
@@ -8238,26 +8299,39 @@ Output ONLY the snapshot in the following exact format (no preamble, no explanat
|
|
|
8238
8299
|
### Current Task
|
|
8239
8300
|
[1-2 sentence summary of the primary task/goal the user is working on]
|
|
8240
8301
|
|
|
8302
|
+
### Key Parameters & Constraints
|
|
8303
|
+
- [List ALL specific parameters, values, format requirements discovered during this conversation]
|
|
8304
|
+
- [Include exact numbers, dimensions, scoring rules, naming conventions, etc.]
|
|
8305
|
+
- [Example: "\u8003\u8BD5\u65F6\u95F4 90 \u5206\u949F\uFF0C\u6EE1\u5206 200 \u5206\uFF0C\u5355\u9009 40 \u9898\xD72 \u5206 + \u591A\u9009 20 \u9898\xD74 \u5206 + \u5224\u65AD 20 \u9898\xD72 \u5206"]
|
|
8306
|
+
- [Example: "\u6587\u4EF6\u547D\u540D\u683C\u5F0F YYYYMMDD-NN-\u6A21\u8003-\u96BE\u5EA6.md\uFF0C\u4FDD\u5B58\u5230 exam_papers/ \u76EE\u5F55"]
|
|
8307
|
+
|
|
8241
8308
|
### Completed Steps
|
|
8242
|
-
- [List each completed step
|
|
8309
|
+
- [List each completed step with specific details (file paths, key outcomes)]
|
|
8243
8310
|
|
|
8244
8311
|
### In-Progress Work
|
|
8245
8312
|
- [List any work that was started but not finished]
|
|
8246
8313
|
|
|
8247
|
-
###
|
|
8248
|
-
- [
|
|
8249
|
-
- [
|
|
8314
|
+
### Critical Reference Files
|
|
8315
|
+
- [List file paths that the next model MUST read before doing any work]
|
|
8316
|
+
- [Include brief description of each file's purpose]
|
|
8317
|
+
- [Example: "Exam2025.md \u2014 \u91D1\u6807\u771F\u9898\u683C\u5F0F\u53C2\u8003\uFF0890\u5206\u949F/200\u5206/\u9898\u578B\u5206\u5E03\uFF09"]
|
|
8318
|
+
- [Example: "\u51FA\u9898\u98CE\u683C\u6307\u5357.md \u2014 \u91D1\u6807\u98CE\u683C\u89C4\u8303\uFF08\u4EBA\u7269\u8BBE\u5B9A/\u573A\u666F\u6784\u5EFA/\u65F6\u4E8B\u70ED\u70B9\uFF09"]
|
|
8319
|
+
|
|
8320
|
+
### Modified/Created Files
|
|
8321
|
+
- [List any files that were created or modified, with brief notes on content]
|
|
8250
8322
|
|
|
8251
|
-
###
|
|
8252
|
-
- [
|
|
8323
|
+
### Key Decisions & Context
|
|
8324
|
+
- [Important decisions, user preferences, or constraints established]
|
|
8253
8325
|
|
|
8254
8326
|
### Next Steps
|
|
8255
8327
|
- [What should be done next to continue this work]
|
|
8328
|
+
- [Include specific instructions the next model should follow]
|
|
8256
8329
|
|
|
8257
8330
|
### Important Notes
|
|
8258
8331
|
- [Any warnings, caveats, or critical context the next model needs to know]
|
|
8332
|
+
- [Things that went wrong or should be avoided]
|
|
8259
8333
|
|
|
8260
|
-
If any section has no content, write "(none)" for that section. Be
|
|
8334
|
+
If any section has no content, write "(none)" for that section. Be thorough \u2014 the next model may have access to our conversation messages, but the detailed tool call results (file contents, command outputs) are NOT preserved. This snapshot is the primary source of specific details and context.`;
|
|
8261
8335
|
function sessionHasMeaningfulContent(messages) {
|
|
8262
8336
|
if (messages.length < 2) return false;
|
|
8263
8337
|
const hasUser = messages.some((m) => m.role === "user");
|
|
@@ -8540,10 +8614,9 @@ var McpClient = class {
|
|
|
8540
8614
|
});
|
|
8541
8615
|
});
|
|
8542
8616
|
}
|
|
8543
|
-
/**
|
|
8617
|
+
/** 拒绝所有挂起的请求。pending.reject() 内部的 cleanup() 已包含 clearTimeout。 */
|
|
8544
8618
|
rejectAllPending(error) {
|
|
8545
|
-
for (const [
|
|
8546
|
-
clearTimeout(pending.timer);
|
|
8619
|
+
for (const [, pending] of this.pendingRequests) {
|
|
8547
8620
|
pending.reject(error);
|
|
8548
8621
|
}
|
|
8549
8622
|
this.pendingRequests.clear();
|
|
@@ -8800,59 +8873,6 @@ var McpManager = class {
|
|
|
8800
8873
|
// src/skills/manager.ts
|
|
8801
8874
|
import { existsSync as existsSync18, readdirSync as readdirSync10, mkdirSync as mkdirSync11 } from "fs";
|
|
8802
8875
|
import { join as join13 } from "path";
|
|
8803
|
-
|
|
8804
|
-
// src/skills/types.ts
|
|
8805
|
-
import { readFileSync as readFileSync12 } from "fs";
|
|
8806
|
-
import { basename as basename5 } from "path";
|
|
8807
|
-
function parseSimpleYaml2(yaml) {
|
|
8808
|
-
const result = {};
|
|
8809
|
-
for (const line of yaml.split("\n")) {
|
|
8810
|
-
const match = line.match(/^(\w+)\s*:\s*(.+)$/);
|
|
8811
|
-
if (match) {
|
|
8812
|
-
result[match[1]] = match[2].trim();
|
|
8813
|
-
}
|
|
8814
|
-
}
|
|
8815
|
-
return result;
|
|
8816
|
-
}
|
|
8817
|
-
function parseYamlArray(value) {
|
|
8818
|
-
const bracketMatch = value.match(/^\[(.+)]$/);
|
|
8819
|
-
if (bracketMatch) {
|
|
8820
|
-
return bracketMatch[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
|
|
8821
|
-
}
|
|
8822
|
-
return [value.trim()];
|
|
8823
|
-
}
|
|
8824
|
-
function parseSkillFile(filePath) {
|
|
8825
|
-
let raw;
|
|
8826
|
-
try {
|
|
8827
|
-
raw = readFileSync12(filePath, "utf-8");
|
|
8828
|
-
} catch {
|
|
8829
|
-
return null;
|
|
8830
|
-
}
|
|
8831
|
-
const frontmatterMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
8832
|
-
if (!frontmatterMatch) {
|
|
8833
|
-
return {
|
|
8834
|
-
meta: {
|
|
8835
|
-
name: basename5(filePath, ".md"),
|
|
8836
|
-
description: ""
|
|
8837
|
-
},
|
|
8838
|
-
content: raw.trim(),
|
|
8839
|
-
filePath
|
|
8840
|
-
};
|
|
8841
|
-
}
|
|
8842
|
-
const [, yaml, content] = frontmatterMatch;
|
|
8843
|
-
const parsed = parseSimpleYaml2(yaml);
|
|
8844
|
-
return {
|
|
8845
|
-
meta: {
|
|
8846
|
-
name: parsed["name"] ?? basename5(filePath, ".md"),
|
|
8847
|
-
description: parsed["description"] ?? "",
|
|
8848
|
-
tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
|
|
8849
|
-
},
|
|
8850
|
-
content: content.trim(),
|
|
8851
|
-
filePath
|
|
8852
|
-
};
|
|
8853
|
-
}
|
|
8854
|
-
|
|
8855
|
-
// src/skills/manager.ts
|
|
8856
8876
|
var SKILL_CONTENT_WARN_CHARS = 5e3;
|
|
8857
8877
|
var SkillManager = class {
|
|
8858
8878
|
skills = /* @__PURE__ */ new Map();
|
|
@@ -8942,8 +8962,8 @@ function sendNotification(title, body) {
|
|
|
8942
8962
|
{ detached: true, stdio: "ignore" }
|
|
8943
8963
|
).unref();
|
|
8944
8964
|
} else if (plat === "win32") {
|
|
8945
|
-
const safeTitle = title.replace(/"/g, '`"');
|
|
8946
|
-
const safeBody = body.replace(/"/g, '`"');
|
|
8965
|
+
const safeTitle = title.replace(/`/g, "``").replace(/"/g, '`"');
|
|
8966
|
+
const safeBody = body.replace(/`/g, "``").replace(/"/g, '`"');
|
|
8947
8967
|
const script = [
|
|
8948
8968
|
"Add-Type -AssemblyName System.Windows.Forms",
|
|
8949
8969
|
"$n = New-Object System.Windows.Forms.NotifyIcon",
|
|
@@ -8992,12 +9012,12 @@ function parseAtReferences(input2, cwd) {
|
|
|
8992
9012
|
continue;
|
|
8993
9013
|
}
|
|
8994
9014
|
if (mime) {
|
|
8995
|
-
const fileSize =
|
|
9015
|
+
const fileSize = statSync9(absPath).size;
|
|
8996
9016
|
if (fileSize > MAX_IMAGE_BYTES) {
|
|
8997
9017
|
refs.push({ path: rawPath, type: "toolarge" });
|
|
8998
9018
|
continue;
|
|
8999
9019
|
}
|
|
9000
|
-
const data =
|
|
9020
|
+
const data = readFileSync12(absPath).toString("base64");
|
|
9001
9021
|
imageParts.push({
|
|
9002
9022
|
type: "image_url",
|
|
9003
9023
|
image_url: { url: `data:${mime};base64,${data}` }
|
|
@@ -9005,7 +9025,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
9005
9025
|
refs.push({ path: rawPath, type: "image" });
|
|
9006
9026
|
textBody = textBody.replace(match[0], "").trim();
|
|
9007
9027
|
} else {
|
|
9008
|
-
const content =
|
|
9028
|
+
const content = readFileSync12(absPath, "utf-8");
|
|
9009
9029
|
const inlined = `
|
|
9010
9030
|
|
|
9011
9031
|
[File: ${rawPath}]
|
|
@@ -9203,7 +9223,7 @@ var Repl = class {
|
|
|
9203
9223
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
9204
9224
|
let isDir;
|
|
9205
9225
|
try {
|
|
9206
|
-
isDir =
|
|
9226
|
+
isDir = statSync9(fullPath).isDirectory();
|
|
9207
9227
|
} catch {
|
|
9208
9228
|
continue;
|
|
9209
9229
|
}
|
|
@@ -9235,7 +9255,7 @@ ${treeLines.join("\n")}`
|
|
|
9235
9255
|
const fullPath = join14(dir, name);
|
|
9236
9256
|
let st;
|
|
9237
9257
|
try {
|
|
9238
|
-
st =
|
|
9258
|
+
st = statSync9(fullPath);
|
|
9239
9259
|
} catch {
|
|
9240
9260
|
continue;
|
|
9241
9261
|
}
|
|
@@ -9247,7 +9267,7 @@ ${treeLines.join("\n")}`
|
|
|
9247
9267
|
if (!TEXT_EXTS.has(ext) && !isSpecial) continue;
|
|
9248
9268
|
if (st.size > MAX_FILE_CHARS * 3) continue;
|
|
9249
9269
|
try {
|
|
9250
|
-
let content =
|
|
9270
|
+
let content = readFileSync12(fullPath, "utf-8");
|
|
9251
9271
|
if (content.length > MAX_FILE_CHARS) {
|
|
9252
9272
|
content = content.slice(0, MAX_FILE_CHARS) + `
|
|
9253
9273
|
... (\u622A\u65AD\uFF0C\u5171 ${content.length} \u5B57\u7B26)`;
|
|
@@ -9282,7 +9302,7 @@ ${content}
|
|
|
9282
9302
|
}
|
|
9283
9303
|
let isDir;
|
|
9284
9304
|
try {
|
|
9285
|
-
isDir =
|
|
9305
|
+
isDir = statSync9(absPath).isDirectory();
|
|
9286
9306
|
} catch {
|
|
9287
9307
|
return { success: false, charCount: 0, added: false, error: `Cannot access: ${dirPath}` };
|
|
9288
9308
|
}
|
|
@@ -9313,7 +9333,7 @@ ${content}
|
|
|
9313
9333
|
for (const candidate of candidates) {
|
|
9314
9334
|
const fullPath = join14(dir, candidate);
|
|
9315
9335
|
if (existsSync19(fullPath)) {
|
|
9316
|
-
const content =
|
|
9336
|
+
const content = readFileSync12(fullPath, "utf-8").trim();
|
|
9317
9337
|
if (content) return { filePath: fullPath, content };
|
|
9318
9338
|
}
|
|
9319
9339
|
}
|
|
@@ -9344,7 +9364,7 @@ ${content}
|
|
|
9344
9364
|
const mcpPath = join14(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
9345
9365
|
if (!existsSync19(mcpPath)) return null;
|
|
9346
9366
|
try {
|
|
9347
|
-
const raw = JSON.parse(
|
|
9367
|
+
const raw = JSON.parse(readFileSync12(mcpPath, "utf-8"));
|
|
9348
9368
|
const servers = raw?.mcpServers;
|
|
9349
9369
|
if (!servers || typeof servers !== "object") {
|
|
9350
9370
|
process.stderr.write(
|
|
@@ -9391,7 +9411,7 @@ ${content}
|
|
|
9391
9411
|
return { layers: [], mergedContent: "" };
|
|
9392
9412
|
}
|
|
9393
9413
|
if (existsSync19(fullPath)) {
|
|
9394
|
-
const content =
|
|
9414
|
+
const content = readFileSync12(fullPath, "utf-8").trim();
|
|
9395
9415
|
if (content) {
|
|
9396
9416
|
const layer = {
|
|
9397
9417
|
level: "project",
|
|
@@ -9450,7 +9470,7 @@ ${content}
|
|
|
9450
9470
|
loadMemoryContent() {
|
|
9451
9471
|
const memoryPath = join14(this.config.getConfigDir(), MEMORY_FILE_NAME);
|
|
9452
9472
|
if (!existsSync19(memoryPath)) return null;
|
|
9453
|
-
let content =
|
|
9473
|
+
let content = readFileSync12(memoryPath, "utf-8").trim();
|
|
9454
9474
|
if (!content) return null;
|
|
9455
9475
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
9456
9476
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -9502,6 +9522,7 @@ ${memory.content}`);
|
|
|
9502
9522
|
`# Development State Handoff
|
|
9503
9523
|
|
|
9504
9524
|
\u4EE5\u4E0B\u662F\u524D\u4E00\u4E2A AI \u6A21\u578B\u751F\u6210\u7684\u5F00\u53D1\u72B6\u6001\u5FEB\u7167\uFF0C\u8BF7\u636E\u6B64\u65E0\u7F1D\u7EE7\u7EED\u7528\u6237\u7684\u5DE5\u4F5C\u3002
|
|
9525
|
+
\u91CD\u8981\uFF1A\u5982\u679C\u5FEB\u7167\u4E2D\u63D0\u5230\u4E86\u5173\u952E\u53C2\u8003\u6587\u4EF6\uFF08\u5982\u6A21\u677F\u3001\u683C\u5F0F\u89C4\u8303\u3001\u98CE\u683C\u6307\u5357\uFF09\uFF0C\u5728\u6267\u884C\u4EFB\u4F55\u751F\u6210/\u521B\u5EFA\u64CD\u4F5C\u4E4B\u524D\uFF0C\u8BF7\u5148\u7528 read_file \u5DE5\u5177\u8BFB\u53D6\u8FD9\u4E9B\u53C2\u8003\u6587\u4EF6\uFF0C\u786E\u4FDD\u4F60\u5B8C\u5168\u7406\u89E3\u683C\u5F0F\u8981\u6C42\u548C\u7EA6\u675F\u6761\u4EF6\u3002\u4E0D\u8981\u51ED\u5047\u8BBE\u884C\u4E8B\uFF0C\u5FC5\u987B\u57FA\u4E8E\u5B9E\u9645\u6587\u4EF6\u5185\u5BB9\u3002
|
|
9505
9526
|
|
|
9506
9527
|
` + devState
|
|
9507
9528
|
);
|
|
@@ -9641,7 +9662,7 @@ ${response.content.trim()}
|
|
|
9641
9662
|
systemPrompt: this.buildCurrentSystemPrompt(),
|
|
9642
9663
|
stream: false,
|
|
9643
9664
|
temperature: 0.3,
|
|
9644
|
-
maxTokens: modelParams.maxTokens ??
|
|
9665
|
+
maxTokens: Math.min(modelParams.maxTokens ?? 4096, 4096),
|
|
9645
9666
|
timeout: modelParams.timeout ?? 3e4
|
|
9646
9667
|
});
|
|
9647
9668
|
spinner.stop();
|
|
@@ -9668,7 +9689,7 @@ ${response.content.trim()}
|
|
|
9668
9689
|
const planTag = this.planMode ? theme.warning("[PLAN]") : "";
|
|
9669
9690
|
const modelParams = this.getModelParams();
|
|
9670
9691
|
const thinkTag = modelParams.thinking ? theme.accent("[THINK]") : "";
|
|
9671
|
-
const promptStr = theme.success(`[${this.currentProvider}]`) + skillTag + planTag + thinkTag +
|
|
9692
|
+
const promptStr = theme.success(`[${this.currentProvider}]`) + skillTag + planTag + thinkTag + chalk9.white(" > ");
|
|
9672
9693
|
this.rl.setPrompt(promptStr);
|
|
9673
9694
|
}
|
|
9674
9695
|
showPrompt() {
|
|
@@ -10160,7 +10181,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
10160
10181
|
if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
|
|
10161
10182
|
try {
|
|
10162
10183
|
const fullPath = join14(absDir, entry);
|
|
10163
|
-
const stat =
|
|
10184
|
+
const stat = statSync9(fullPath);
|
|
10164
10185
|
const rel = dir === "." ? entry : `${dir}/${entry}`;
|
|
10165
10186
|
results.push(stat.isDirectory() ? `${rel}/` : rel);
|
|
10166
10187
|
} catch {
|