chatccc 0.2.296 → 0.2.297
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/deepccc-agent/README.md +8 -4
- package/deepccc-agent/os-prompts/win32.md +8 -8
- package/deepccc-agent/package.json +1 -1
- package/dist/deepccc-agent/src/file-tools.js +244 -15
- package/dist/deepccc-agent/src/index.js +3 -3
- package/dist/deepccc-agent/src/permissions.js +1 -1
- package/dist/deepccc-agent/src/progress/cards-helpers.js +2 -0
- package/dist/deepccc-agent/src/web-tool-presentation.js +2 -0
- package/dist/src/cards.js +2 -0
- package/package.json +1 -1
package/deepccc-agent/README.md
CHANGED
|
@@ -165,7 +165,7 @@ Web UI 支持新建、恢复、重命名和删除多会话,多个会话可以
|
|
|
165
165
|
|
|
166
166
|
API 设置采用单一 Provider 配置,支持 OpenAI-compatible 与 Anthropic Messages。完整 API Key 只保存在本机 `~/.deepccc/config.json`,浏览器读取设置时仅返回掩码。危险命令会在会话中暂停并请求“拒绝、允许一次、本会话允许、永久允许”,浏览器断开或审批超时默认拒绝。
|
|
167
167
|
|
|
168
|
-
`git.coAuthor.enabled` 默认开启。DeepCCC 通过 `run_command` 创建 Git 提交时会保留用户为
|
|
168
|
+
`git.coAuthor.enabled` 默认开启。DeepCCC 通过 `run_process` 或 `run_command` 创建 Git 提交时会保留用户为
|
|
169
169
|
主 Author,并追加 `Co-authored-by: DeepCCC <20184052+wzj998@users.noreply.github.com>`。
|
|
170
170
|
可设为 `false` 或用 `DEEPCCC_GIT_COAUTHOR=false` 全局关闭。ChatCCC 的
|
|
171
171
|
`ccc.gitCoAuthor` 是三态 override:`null`/缺失跟随这里,`true` 强制开启,`false` 强制关闭。
|
|
@@ -276,7 +276,7 @@ deepccc-cli --max-output-tokens 8192
|
|
|
276
276
|
|
|
277
277
|
## 权限机制
|
|
278
278
|
|
|
279
|
-
`deepccc` 内置轻量权限机制,对标主流 agent 的审批体验:**只拦截有副作用的操作**(`run_command` 与文件写操作),只读工具(`read_file` / `list_dir` / `search_code`)永不拦截,常规文件编辑默认放行不打断工作流。
|
|
279
|
+
`deepccc` 内置轻量权限机制,对标主流 agent 的审批体验:**只拦截有副作用的操作**(`run_process` / `run_script` / `run_command` 与文件写操作),只读工具(`read_file` / `list_dir` / `search_code`)永不拦截,常规文件编辑默认放行不打断工作流。
|
|
280
280
|
|
|
281
281
|
默认模式(`ask`)下,只有**命中内置危险命令库**的高危命令才会询问,例如:
|
|
282
282
|
|
|
@@ -307,11 +307,13 @@ deepccc-cli --max-output-tokens 8192
|
|
|
307
307
|
```json
|
|
308
308
|
{
|
|
309
309
|
"allow": [
|
|
310
|
+
"run_process:git status*",
|
|
310
311
|
"run_command:git status*",
|
|
311
312
|
"run_command:git push --force origin release*"
|
|
312
313
|
],
|
|
313
314
|
"deny": [
|
|
314
315
|
"edit_file:node_modules/**",
|
|
316
|
+
"run_process:npm publish*",
|
|
315
317
|
"run_command:npm publish*"
|
|
316
318
|
]
|
|
317
319
|
}
|
|
@@ -433,11 +435,13 @@ deepccc 的内核主战场在 ChatCCC 仓库的 `deepccc-agent/` 子目录;本
|
|
|
433
435
|
- 用 ripgrep 搜索代码
|
|
434
436
|
- 编辑、创建、删除、移动文件
|
|
435
437
|
- 应用 unified diff patch
|
|
436
|
-
-
|
|
438
|
+
- 用 `run_process` 以结构化 argv、`shell:false` 运行单个程序,避免参数被 shell 二次解析
|
|
439
|
+
- 用 `run_script` 通过 stdin 运行多行 Python/Node 代码,避免复杂 `python -c` / `node -e` 引号
|
|
440
|
+
- 仅在需要 `&&`、管道或重定向时用 `run_command` 运行非交互式 shell 命令
|
|
437
441
|
- 联网搜索(`websearch`:DuckDuckGo,免 API key,返回标题 + URL + 摘要)
|
|
438
442
|
- 抓取网页并转纯文本(`webfetch`:仅 http/https,自动去 HTML 标签、控制长度与超时)
|
|
439
443
|
|
|
440
|
-
|
|
444
|
+
三个命令工具都支持独立 `cwd`、超时、取消、输出限长和运行中消息让位;返回非零退出码时不会直接被当成工具异常,模型可以读取结构化结果继续判断。Windows 下 `run_command` 会拒绝多行或嵌套同类引号的复杂 Python/Node 内联代码,并提示改用 `run_script`。
|
|
441
445
|
|
|
442
446
|
## License
|
|
443
447
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
## Windows 命令行提示
|
|
2
2
|
|
|
3
|
-
你运行在 Windows
|
|
3
|
+
你运行在 Windows 上。优先绕过 shell,只有需要 shell 运算符时才进入 cmd.exe:
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- npm 11.x 在 Windows 上忽略 `--prefix` 对 `npm publish`
|
|
5
|
+
- 单个程序优先用 `run_process`,把可执行文件和每个参数分别放进 `executable` / `args`。它使用 `shell:false`,不会让 cmd.exe 再解释引号、反斜杠、`&`、`%VAR%` 等内容。
|
|
6
|
+
- 多行或引号密集的 Python/Node 代码必须用 `run_script`。不要塞进 `python -c` / `node -e`;Windows 下复杂内联代码会被 `run_command` 拒绝,避免命令返回 0 却实际执行了不同代码。
|
|
7
|
+
- `run_command` 仅用于 `&&`、管道、重定向等必须由 shell 解析的场景;它通过 cmd.exe 执行,不是 bash,也不是 PowerShell。
|
|
8
|
+
- 双引号在 cmd.exe、Node 的 shell 封装和目标程序的 argv 解析之间会经历多层处理;是否保留或剥离取决于具体调用链,不能假设会“原样到达”。单引号在 cmd.exe 中不是可靠的引用字符。
|
|
9
|
+
- 使用各命令工具的 `cwd` 参数切换工作目录,不要前置 `cd /d ... &&`。只有手工编写纯 cmd 命令时,跨盘 `cd` 才需要 `/d`。
|
|
10
|
+
- PowerShell 专用语法(Get-Item、2>$null、Select-Object)不能直接写进 `run_command`;确需使用时,用 `run_process` 显式执行 `powershell.exe` 并逐项传参。
|
|
11
|
+
- npm 11.x 在 Windows 上忽略 `--prefix` 对 `npm publish` 的作用。发布时把工具的 `cwd` 参数设为目标包目录,再执行 `npm publish`。
|
|
@@ -4,7 +4,7 @@ import { createReadStream } from "node:fs";
|
|
|
4
4
|
import { copyFile, mkdir, open, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
9
|
import { jsonSchema, tool } from "ai";
|
|
10
10
|
import { isDangerousCommand } from "./permissions.js";
|
|
@@ -25,6 +25,7 @@ const SEARCH_TIMEOUT_MS = 15_000;
|
|
|
25
25
|
const MAX_COMMAND_OUTPUT_BYTES = 256 * 1024;
|
|
26
26
|
const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
27
27
|
const MAX_COMMAND_TIMEOUT_MS = 900_000;
|
|
28
|
+
const MAX_SCRIPT_BYTES = 2 * 1024 * 1024;
|
|
28
29
|
/**
|
|
29
30
|
* 让位注入的轮询间隔:run_command 执行期以此频率检查是否有待注入的用户消息。
|
|
30
31
|
* 取 1s 而非更小值:轮询本身不花 token(只读内存队列,无模型调用),但更密的
|
|
@@ -54,6 +55,22 @@ export function withGitCoAuthor(command, coAuthor) {
|
|
|
54
55
|
const trailer = ` --trailer "Co-authored-by: ${coAuthor.name} <${coAuthor.email}>"`;
|
|
55
56
|
return command.replace(/(^|(?:&&|\|\||;|\|)\s*)(git\s+commit)\b/g, (_match, prefix, gitCommit) => `${prefix}${gitCommit}${trailer}`);
|
|
56
57
|
}
|
|
58
|
+
/** Structured equivalent of withGitCoAuthor for run_process({ executable: "git", args: [...] }). */
|
|
59
|
+
export function withGitCoAuthorArgs(executable, args, coAuthor) {
|
|
60
|
+
const executableName = basename(executable).replace(/\.(?:exe|cmd|bat)$/i, "").toLowerCase();
|
|
61
|
+
if (!coAuthor?.enabled
|
|
62
|
+
|| executableName !== "git"
|
|
63
|
+
|| args[0]?.toLowerCase() !== "commit"
|
|
64
|
+
|| args.some((arg) => arg.includes(coAuthor.email))) {
|
|
65
|
+
return [...args];
|
|
66
|
+
}
|
|
67
|
+
return [
|
|
68
|
+
args[0],
|
|
69
|
+
"--trailer",
|
|
70
|
+
`Co-authored-by: ${coAuthor.name} <${coAuthor.email}>`,
|
|
71
|
+
...args.slice(1),
|
|
72
|
+
];
|
|
73
|
+
}
|
|
57
74
|
/**
|
|
58
75
|
* 把 `~` / `~/x`(含反斜杠 `~\\x`)展开为用户主目录绝对路径。
|
|
59
76
|
* `~user/x` 形式不展开(Node 无内置支持),保持原样交给后续解析。
|
|
@@ -775,10 +792,91 @@ function pruneBackgroundCommands() {
|
|
|
775
792
|
return;
|
|
776
793
|
}
|
|
777
794
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
795
|
+
const WINDOWS_INLINE_SCRIPT = /\b(?:python(?:\d+(?:\.\d+)?)?|py|node)(?:\.exe)?\s+(?:-c|-e|--eval)\s+/gi;
|
|
796
|
+
/**
|
|
797
|
+
* cmd.exe cannot reliably preserve nested quotes/backslashes in complex inline
|
|
798
|
+
* Python/Node programs. Keep simple one-line snippets working, but reject the
|
|
799
|
+
* cases for which a zero exit code can still mean that different code ran.
|
|
800
|
+
*/
|
|
801
|
+
export function isUnsafeWindowsInlineScript(command) {
|
|
802
|
+
WINDOWS_INLINE_SCRIPT.lastIndex = 0;
|
|
803
|
+
let match;
|
|
804
|
+
while ((match = WINDOWS_INLINE_SCRIPT.exec(command)) !== null) {
|
|
805
|
+
const tail = command.slice(WINDOWS_INLINE_SCRIPT.lastIndex);
|
|
806
|
+
if (/[\r\n]/.test(tail))
|
|
807
|
+
return true;
|
|
808
|
+
const shellBoundary = tail.search(/\s(?:&&|\|\||[|>])/);
|
|
809
|
+
const argument = (shellBoundary >= 0 ? tail.slice(0, shellBoundary) : tail).trim();
|
|
810
|
+
const quote = argument[0];
|
|
811
|
+
if (quote !== "\"" && quote !== "'")
|
|
812
|
+
continue;
|
|
813
|
+
// cmd.exe does not treat single quotes as argument delimiters.
|
|
814
|
+
if (quote === "'")
|
|
815
|
+
return true;
|
|
816
|
+
const closingQuote = argument.lastIndexOf(quote);
|
|
817
|
+
if (closingQuote <= 0)
|
|
818
|
+
return true;
|
|
819
|
+
const body = argument.slice(1, closingQuote);
|
|
820
|
+
if (body.includes(quote))
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
function assertStringArgs(args) {
|
|
826
|
+
if (args === undefined)
|
|
827
|
+
return [];
|
|
828
|
+
if (!Array.isArray(args) || args.some((arg) => typeof arg !== "string")) {
|
|
829
|
+
throw new Error("args must be an array of strings");
|
|
830
|
+
}
|
|
831
|
+
return [...args];
|
|
832
|
+
}
|
|
833
|
+
function resolveProcessExecutable(commandCwd, value) {
|
|
834
|
+
const executable = value.trim();
|
|
835
|
+
if (!executable)
|
|
836
|
+
throw new Error("executable is required");
|
|
837
|
+
const expanded = expandHomePath(executable);
|
|
838
|
+
if (isAbsolute(expanded))
|
|
839
|
+
return resolve(expanded);
|
|
840
|
+
if (expanded.includes("/") || expanded.includes("\\") || expanded.startsWith(".")) {
|
|
841
|
+
return resolve(commandCwd, expanded);
|
|
842
|
+
}
|
|
843
|
+
return executable;
|
|
844
|
+
}
|
|
845
|
+
function formatProcessCommand(executable, args) {
|
|
846
|
+
return [executable, ...args].map((part) => JSON.stringify(part)).join(" ");
|
|
847
|
+
}
|
|
848
|
+
async function resolveWindowsNodeShim(executable, args) {
|
|
849
|
+
if (process.platform !== "win32" || extname(executable).toLowerCase() === ".exe") {
|
|
850
|
+
return { executable, args };
|
|
851
|
+
}
|
|
852
|
+
const toolName = basename(executable).replace(/\.(?:cmd|bat)$/i, "").toLowerCase();
|
|
853
|
+
if (toolName !== "npm" && toolName !== "npx")
|
|
854
|
+
return { executable, args };
|
|
855
|
+
const cliName = toolName === "npm" ? "npm-cli.js" : "npx-cli.js";
|
|
856
|
+
const candidates = new Set();
|
|
857
|
+
const npmExecPath = process.env.npm_execpath?.trim();
|
|
858
|
+
if (npmExecPath) {
|
|
859
|
+
candidates.add(toolName === "npm" ? npmExecPath : join(dirname(npmExecPath), cliName));
|
|
860
|
+
}
|
|
861
|
+
const addInstallRoot = (root) => {
|
|
862
|
+
if (root)
|
|
863
|
+
candidates.add(join(root, "node_modules", "npm", "bin", cliName));
|
|
864
|
+
};
|
|
865
|
+
addInstallRoot(dirname(process.execPath));
|
|
866
|
+
if (executable.includes("/") || executable.includes("\\"))
|
|
867
|
+
addInstallRoot(dirname(executable));
|
|
868
|
+
for (const pathEntry of (process.env.PATH ?? "").split(delimiter)) {
|
|
869
|
+
addInstallRoot(pathEntry.replace(/^"(.*)"$/, "$1"));
|
|
870
|
+
}
|
|
871
|
+
for (const candidate of candidates) {
|
|
872
|
+
if (await pathExists(candidate)) {
|
|
873
|
+
return { executable: process.execPath, args: [candidate, ...args] };
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
throw new Error(`无法在 shell:false 下定位 Windows ${toolName} CLI;请用 run_command 执行 ${toolName},`
|
|
877
|
+
+ "或用 run_process 显式调用 node 并把 npm CLI JS 路径放入 args。");
|
|
878
|
+
}
|
|
879
|
+
async function spawnForTool(cwd, input, abortSignal, shouldYield) {
|
|
782
880
|
const commandCwd = resolveToolPath(cwd, input.cwd);
|
|
783
881
|
const cwdInfo = await stat(commandCwd);
|
|
784
882
|
if (!cwdInfo.isDirectory()) {
|
|
@@ -788,6 +886,7 @@ export async function runCommandForTool(cwd, input, abortSignal, coAuthor, shoul
|
|
|
788
886
|
const startedAt = Date.now();
|
|
789
887
|
const stdout = { chunks: [], bytes: 0, truncated: false };
|
|
790
888
|
const stderr = { chunks: [], bytes: 0, truncated: false };
|
|
889
|
+
const command = input.command;
|
|
791
890
|
return new Promise((resolvePromise, reject) => {
|
|
792
891
|
let settled = false;
|
|
793
892
|
let timedOut = false;
|
|
@@ -795,11 +894,11 @@ export async function runCommandForTool(cwd, input, abortSignal, coAuthor, shoul
|
|
|
795
894
|
let fallbackTimer;
|
|
796
895
|
let yieldPoll;
|
|
797
896
|
let backgroundEntry;
|
|
798
|
-
const child = spawn(
|
|
897
|
+
const child = spawn(input.executable, input.args, {
|
|
799
898
|
cwd: commandCwd,
|
|
800
|
-
shell:
|
|
899
|
+
shell: input.shell,
|
|
801
900
|
windowsHide: true,
|
|
802
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
901
|
+
stdio: [input.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
803
902
|
detached: process.platform !== "win32",
|
|
804
903
|
});
|
|
805
904
|
const cleanup = () => {
|
|
@@ -934,6 +1033,10 @@ export async function runCommandForTool(cwd, input, abortSignal, coAuthor, shoul
|
|
|
934
1033
|
child.stderr?.on("data", (chunk) => {
|
|
935
1034
|
appendLimitedOutput(stderr, chunk);
|
|
936
1035
|
});
|
|
1036
|
+
child.stdin?.on("error", () => {
|
|
1037
|
+
// The child error/close event is authoritative; ignore a secondary EPIPE
|
|
1038
|
+
// when an interpreter exits before consuming all script input.
|
|
1039
|
+
});
|
|
937
1040
|
child.once("error", (err) => {
|
|
938
1041
|
if (backgroundEntry) {
|
|
939
1042
|
settle(null, "error");
|
|
@@ -948,8 +1051,76 @@ export async function runCommandForTool(cwd, input, abortSignal, coAuthor, shoul
|
|
|
948
1051
|
child.once("close", (code, signal) => {
|
|
949
1052
|
settle(code, signal);
|
|
950
1053
|
});
|
|
1054
|
+
if (input.stdin !== undefined) {
|
|
1055
|
+
child.stdin?.end(input.stdin, "utf8");
|
|
1056
|
+
}
|
|
951
1057
|
});
|
|
952
1058
|
}
|
|
1059
|
+
export async function runCommandForTool(cwd, input, abortSignal, coAuthor, shouldYield) {
|
|
1060
|
+
const command = withGitCoAuthor(input.command?.trim(), coAuthor);
|
|
1061
|
+
if (!command)
|
|
1062
|
+
throw new Error("command is required");
|
|
1063
|
+
if (process.platform === "win32" && isUnsafeWindowsInlineScript(command)) {
|
|
1064
|
+
throw new Error("Windows CMD 无法可靠传递复杂的 python -c/node -e 内联代码;请改用 run_script,"
|
|
1065
|
+
+ "单程序参数请用 run_process。run_command 仅保留给 &&、管道和重定向等 shell 操作。");
|
|
1066
|
+
}
|
|
1067
|
+
return spawnForTool(cwd, {
|
|
1068
|
+
executable: command,
|
|
1069
|
+
args: [],
|
|
1070
|
+
command,
|
|
1071
|
+
cwd: input.cwd,
|
|
1072
|
+
timeoutMs: input.timeoutMs,
|
|
1073
|
+
shell: true,
|
|
1074
|
+
}, abortSignal, shouldYield);
|
|
1075
|
+
}
|
|
1076
|
+
export async function runProcessForTool(cwd, input, abortSignal, coAuthor, shouldYield) {
|
|
1077
|
+
const commandCwd = resolveToolPath(cwd, input.cwd);
|
|
1078
|
+
const requestedExecutable = resolveProcessExecutable(commandCwd, input.executable ?? "");
|
|
1079
|
+
const requestedArgs = withGitCoAuthorArgs(requestedExecutable, assertStringArgs(input.args), coAuthor);
|
|
1080
|
+
const invocation = await resolveWindowsNodeShim(requestedExecutable, requestedArgs);
|
|
1081
|
+
return spawnForTool(cwd, {
|
|
1082
|
+
executable: invocation.executable,
|
|
1083
|
+
args: invocation.args,
|
|
1084
|
+
command: formatProcessCommand(requestedExecutable, requestedArgs),
|
|
1085
|
+
cwd: commandCwd,
|
|
1086
|
+
timeoutMs: input.timeoutMs,
|
|
1087
|
+
shell: false,
|
|
1088
|
+
}, abortSignal, shouldYield);
|
|
1089
|
+
}
|
|
1090
|
+
async function defaultScriptExecutable(commandCwd, language) {
|
|
1091
|
+
if (language === "node")
|
|
1092
|
+
return process.execPath;
|
|
1093
|
+
const venvPython = process.platform === "win32"
|
|
1094
|
+
? join(commandCwd, ".venv", "Scripts", "python.exe")
|
|
1095
|
+
: join(commandCwd, ".venv", "bin", "python");
|
|
1096
|
+
if (await pathExists(venvPython))
|
|
1097
|
+
return venvPython;
|
|
1098
|
+
return process.platform === "win32" ? "python" : "python3";
|
|
1099
|
+
}
|
|
1100
|
+
export async function runScriptForTool(cwd, input, abortSignal, shouldYield) {
|
|
1101
|
+
if (input.language !== "node" && input.language !== "python") {
|
|
1102
|
+
throw new Error("language must be node or python");
|
|
1103
|
+
}
|
|
1104
|
+
if (typeof input.code !== "string" || !input.code.trim()) {
|
|
1105
|
+
throw new Error("code is required");
|
|
1106
|
+
}
|
|
1107
|
+
if (Buffer.byteLength(input.code, "utf8") > MAX_SCRIPT_BYTES) {
|
|
1108
|
+
throw new Error(`script exceeds ${MAX_SCRIPT_BYTES} bytes`);
|
|
1109
|
+
}
|
|
1110
|
+
const commandCwd = resolveToolPath(cwd, input.cwd);
|
|
1111
|
+
const configuredExecutable = input.executable?.trim();
|
|
1112
|
+
const executable = resolveProcessExecutable(commandCwd, configuredExecutable || await defaultScriptExecutable(commandCwd, input.language));
|
|
1113
|
+
const args = ["-", ...assertStringArgs(input.args)];
|
|
1114
|
+
return spawnForTool(cwd, {
|
|
1115
|
+
executable,
|
|
1116
|
+
args,
|
|
1117
|
+
command: `${formatProcessCommand(executable, args)} <${input.language} script via stdin>`,
|
|
1118
|
+
cwd: commandCwd,
|
|
1119
|
+
timeoutMs: input.timeoutMs,
|
|
1120
|
+
shell: false,
|
|
1121
|
+
stdin: input.code,
|
|
1122
|
+
}, abortSignal, shouldYield);
|
|
1123
|
+
}
|
|
953
1124
|
/** 读取后台命令的最新输出;waitMs > 0 时最多等待该时长(等待期间仍可让位)。 */
|
|
954
1125
|
export async function commandOutputForTool(taskId, input, shouldYield) {
|
|
955
1126
|
const entry = backgroundCommands.get(taskId);
|
|
@@ -1407,14 +1578,72 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1407
1578
|
}),
|
|
1408
1579
|
execute: (input) => presentFileForTool(cwd, input),
|
|
1409
1580
|
}),
|
|
1581
|
+
run_process: tool({
|
|
1582
|
+
description: "优先使用:以结构化 executable + args 直接运行单个非交互式程序(shell:false),参数不会经过平台 shell 展开,适合测试、git、npm 和含空格/引号的参数。需要 &&、管道或重定向时才用 run_command。",
|
|
1583
|
+
inputSchema: jsonSchema({
|
|
1584
|
+
type: "object",
|
|
1585
|
+
additionalProperties: false,
|
|
1586
|
+
properties: {
|
|
1587
|
+
executable: { type: "string", description: "可执行文件名或路径,例如 git、npm、node、.venv/Scripts/python.exe。" },
|
|
1588
|
+
args: {
|
|
1589
|
+
type: "array",
|
|
1590
|
+
items: { type: "string" },
|
|
1591
|
+
description: "逐项传递给程序的参数;不要自行拼接或 shell 转义。",
|
|
1592
|
+
},
|
|
1593
|
+
cwd: { type: "string", description: "可选工作目录。请用此字段切换目录,不要在命令前拼接 cd /d。" },
|
|
1594
|
+
timeoutMs: { type: "number", description: `可选超时(毫秒),上限为 ${MAX_COMMAND_TIMEOUT_MS}。` },
|
|
1595
|
+
},
|
|
1596
|
+
required: ["executable"],
|
|
1597
|
+
}),
|
|
1598
|
+
execute: async (input, execOptions) => {
|
|
1599
|
+
const action = [input.executable, ...(input.args ?? [])].join(" ");
|
|
1600
|
+
await guard({
|
|
1601
|
+
tool: "run_process",
|
|
1602
|
+
action,
|
|
1603
|
+
reason: isDangerousCommand(action) ? "high-risk" : "rule",
|
|
1604
|
+
detail: `运行程序: ${action}`,
|
|
1605
|
+
});
|
|
1606
|
+
return runProcessForTool(cwd, input, execOptions.abortSignal, options.gitCoAuthor, options.shouldYieldToInjection);
|
|
1607
|
+
},
|
|
1608
|
+
}),
|
|
1609
|
+
run_script: tool({
|
|
1610
|
+
description: "运行多行 Node.js 或 Python 脚本,代码通过 stdin 传入解释器(shell:false),避免 python -c/node -e 的多层引号问题。Python 默认优先使用 cwd 下的 .venv,也可显式指定 executable。",
|
|
1611
|
+
inputSchema: jsonSchema({
|
|
1612
|
+
type: "object",
|
|
1613
|
+
additionalProperties: false,
|
|
1614
|
+
properties: {
|
|
1615
|
+
language: { type: "string", enum: ["node", "python"], description: "脚本语言。" },
|
|
1616
|
+
code: { type: "string", description: "要执行的完整多行代码,不需要 shell 引号。" },
|
|
1617
|
+
args: {
|
|
1618
|
+
type: "array",
|
|
1619
|
+
items: { type: "string" },
|
|
1620
|
+
description: "可选脚本参数,逐项原样传递。",
|
|
1621
|
+
},
|
|
1622
|
+
executable: { type: "string", description: "可选解释器路径,例如 .venv/Scripts/python.exe。" },
|
|
1623
|
+
cwd: { type: "string", description: "可选工作目录。请用此字段切换目录,不要在命令前拼接 cd /d。" },
|
|
1624
|
+
timeoutMs: { type: "number", description: `可选超时(毫秒),上限为 ${MAX_COMMAND_TIMEOUT_MS}。` },
|
|
1625
|
+
},
|
|
1626
|
+
required: ["language", "code"],
|
|
1627
|
+
}),
|
|
1628
|
+
execute: async (input, execOptions) => {
|
|
1629
|
+
const runtime = input.executable?.trim() || input.language;
|
|
1630
|
+
await guard({
|
|
1631
|
+
tool: "run_script",
|
|
1632
|
+
action: `${runtime} script`,
|
|
1633
|
+
reason: "rule",
|
|
1634
|
+
detail: `运行 ${input.language} 脚本`,
|
|
1635
|
+
});
|
|
1636
|
+
return runScriptForTool(cwd, input, execOptions.abortSignal, options.shouldYieldToInjection);
|
|
1637
|
+
},
|
|
1638
|
+
}),
|
|
1410
1639
|
run_command: tool({
|
|
1411
|
-
description: "
|
|
1640
|
+
description: "仅在需要 &&、管道、重定向或其他 shell 运算符时运行非交互式 shell 命令;单程序优先用 run_process,多行 Python/Node 优先用 run_script。请用 cwd 字段切换目录,不要前置 cd /d。返回 stdout/stderr 和 exitCode;非零退出码不是工具错误。运行期间若有新消息会转入后台并返回 taskId。",
|
|
1412
1641
|
inputSchema: jsonSchema({
|
|
1413
1642
|
type: "object",
|
|
1414
1643
|
additionalProperties: false,
|
|
1415
1644
|
properties: {
|
|
1416
|
-
command: { type: "string", description: "
|
|
1417
|
-
cwd: { type: "string", description: "
|
|
1645
|
+
command: { type: "string", description: "确实需要 shell 解析的命令行。Windows 下复杂 python -c/node -e 会被拒绝。" },
|
|
1646
|
+
cwd: { type: "string", description: "可选工作目录。请用此字段切换目录,不要在命令前拼接 cd /d。" },
|
|
1418
1647
|
timeoutMs: { type: "number", description: `可选超时(毫秒),上限为 ${MAX_COMMAND_TIMEOUT_MS}。` },
|
|
1419
1648
|
},
|
|
1420
1649
|
required: ["command"],
|
|
@@ -1430,12 +1659,12 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1430
1659
|
},
|
|
1431
1660
|
}),
|
|
1432
1661
|
command_output: tool({
|
|
1433
|
-
description: "
|
|
1662
|
+
description: "读取 run_process、run_script 或 run_command 让位到后台后的最新输出。立即返回当前状态;waitMs 可最多等待 30 秒。",
|
|
1434
1663
|
inputSchema: jsonSchema({
|
|
1435
1664
|
type: "object",
|
|
1436
1665
|
additionalProperties: false,
|
|
1437
1666
|
properties: {
|
|
1438
|
-
taskId: { type: "string", description: "
|
|
1667
|
+
taskId: { type: "string", description: "命令工具返回的 taskId(backgrounded:true 时)。" },
|
|
1439
1668
|
waitMs: { type: "number", description: `可选等待毫秒数;0 或省略立即返回,上限 ${MAX_COMMAND_OUTPUT_WAIT_MS}。` },
|
|
1440
1669
|
},
|
|
1441
1670
|
required: ["taskId"],
|
|
@@ -1443,12 +1672,12 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1443
1672
|
execute: (input) => commandOutputForTool(input.taskId, input, options.shouldYieldToInjection),
|
|
1444
1673
|
}),
|
|
1445
1674
|
command_kill: tool({
|
|
1446
|
-
description: "
|
|
1675
|
+
description: "终止一个由 run_process、run_script 或 run_command 转入后台的进程。",
|
|
1447
1676
|
inputSchema: jsonSchema({
|
|
1448
1677
|
type: "object",
|
|
1449
1678
|
additionalProperties: false,
|
|
1450
1679
|
properties: {
|
|
1451
|
-
taskId: { type: "string", description: "
|
|
1680
|
+
taskId: { type: "string", description: "命令工具返回的 taskId。" },
|
|
1452
1681
|
},
|
|
1453
1682
|
required: ["taskId"],
|
|
1454
1683
|
}),
|
|
@@ -153,8 +153,8 @@ function readProjectInstructionFiles(cwd) {
|
|
|
153
153
|
function buildRuntimeWorkspacePrompt(cwd) {
|
|
154
154
|
return [
|
|
155
155
|
`当前工作目录:${cwd}`,
|
|
156
|
-
"需要理解代码、配置、项目结构、测试或 git 状态时,主动使用 read_file、list_dir、search_code
|
|
157
|
-
"
|
|
156
|
+
"需要理解代码、配置、项目结构、测试或 git 状态时,主动使用 read_file、list_dir、search_code 和命令执行工具。",
|
|
157
|
+
"单个程序优先使用 run_process 的结构化 executable/args;多行 Python/Node 使用 run_script;只有 &&、管道、重定向等 shell 场景才使用 run_command。用 cwd 参数切换目录,不要前置 cd。先检查 exitCode、stdout 和 stderr 再决定下一步。",
|
|
158
158
|
"编辑前先阅读相关文件范围。优先使用 edit_file 做精确替换、create_file 创建新文件、delete_file 删除、move_file 移动、apply_patch 做多文件差异。",
|
|
159
159
|
"文件工具通过 DeepCCC 在本地执行。可行时优先使用带 SHA-256 前置条件的受保护编辑,避免覆盖并发用户修改。",
|
|
160
160
|
].join("\n");
|
|
@@ -288,7 +288,7 @@ export class ChatSession {
|
|
|
288
288
|
context;
|
|
289
289
|
compactionTimeoutMs;
|
|
290
290
|
maxSteps;
|
|
291
|
-
/**
|
|
291
|
+
/** 透传给命令工具的让位注入判定(见 ChatSessionOptions.shouldYieldToInjection)。 */
|
|
292
292
|
shouldYieldToInjection;
|
|
293
293
|
effort;
|
|
294
294
|
maxOutputTokens;
|
|
@@ -146,7 +146,7 @@ export class PermissionGate {
|
|
|
146
146
|
return "deny";
|
|
147
147
|
if (rules.allow.some((r) => keys.some((k) => matchRule(r, k))))
|
|
148
148
|
return "allow";
|
|
149
|
-
//
|
|
149
|
+
// 只有命令执行工具标记为 high-risk 才询问;文件编辑等常规操作默认放行
|
|
150
150
|
if (request.reason !== "high-risk")
|
|
151
151
|
return "allow";
|
|
152
152
|
// 高危且无交互能力(非 TTY / JSONL / 程序化调用)→ 安全默认拒绝
|
|
@@ -50,6 +50,8 @@ const TOOL_EMOJI_MAP = {
|
|
|
50
50
|
read_file: "\u{1F4D6}", // 📖
|
|
51
51
|
list_dir: "\u{1F4C2}", // 📂
|
|
52
52
|
search_code: "\u{1F50E}", // 🔎
|
|
53
|
+
run_process: "\u{1F5A5}\u{FE0F}", // 🖥️
|
|
54
|
+
run_script: "\u{1F5A5}\u{FE0F}", // 🖥️
|
|
53
55
|
run_command: "\u{1F5A5}\u{FE0F}", // 🖥️
|
|
54
56
|
edit_file: "\u{270F}\u{FE0F}", // ✏️
|
|
55
57
|
create_file: "\u{270D}\u{FE0F}", // ✍️
|
|
@@ -2,6 +2,8 @@ export const WEB_TOOL_SUMMARY_RULES = {
|
|
|
2
2
|
read_file: { emoji: "📖", inputFields: ["path"] },
|
|
3
3
|
list_dir: { emoji: "📂", inputFields: ["path"] },
|
|
4
4
|
search_code: { emoji: "🔎", inputFields: ["query", "path"] },
|
|
5
|
+
run_process: { emoji: "🖥️", inputFields: ["executable", "args", "cwd"] },
|
|
6
|
+
run_script: { emoji: "🖥️", inputFields: ["language", "cwd"] },
|
|
5
7
|
run_command: { emoji: "🖥️", inputFields: ["command"] },
|
|
6
8
|
edit_file: { emoji: "✏️", inputFields: ["path"] },
|
|
7
9
|
create_file: { emoji: "✍️", inputFields: ["path"] },
|
package/dist/src/cards.js
CHANGED
|
@@ -61,6 +61,8 @@ const TOOL_EMOJI_MAP = {
|
|
|
61
61
|
read_file: "\u{1F4D6}", // 📖
|
|
62
62
|
list_dir: "\u{1F4C2}", // 📂
|
|
63
63
|
search_code: "\u{1F50E}", // 🔎
|
|
64
|
+
run_process: "\u{1F5A5}\u{FE0F}", // 🖥️
|
|
65
|
+
run_script: "\u{1F5A5}\u{FE0F}", // 🖥️
|
|
64
66
|
run_command: "\u{1F5A5}\u{FE0F}", // 🖥️
|
|
65
67
|
edit_file: "\u{270F}\u{FE0F}", // ✏️
|
|
66
68
|
create_file: "\u{270D}\u{FE0F}", // ✍️
|