@agentdevjs/shell-feature 0.1.0

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/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @agentdevjs/shell-feature
2
+
3
+ Shell execution feature for AgentDev framework.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @agentdevjs/shell-feature
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { BasicAgent } from 'agentdev';
15
+ import { ShellFeature } from '@agentdevjs/shell-feature';
16
+
17
+ const agent = new BasicAgent().use(new ShellFeature());
18
+ ```
19
+
20
+ ## Features
21
+
22
+ - **Bash Execution**: Execute shell commands via Git Bash
23
+ - **Safe Trash**: Delete, list, and restore files safely with trash bin
@@ -0,0 +1,344 @@
1
+ import { ToolTerminationReason, Tool, AgentFeature, FeatureManifestDefinition, FeatureInitContext, PackageInfo } from '@agentdevjs/core';
2
+
3
+ /**
4
+ * Shell 共享核心(ticket 024 / ADR-0005)
5
+ *
6
+ * bash 与 powershell 共用的「spawn + collect + 截断落盘 + 终止收集」管线:
7
+ * - 正常完成:沿用既有语义(成功 stdout/stderr 独立截断落盘;失败合并截断后 reject)。
8
+ * - 终止收集(toolContext.signal aborted,ticket 023 合并 signal 契约):
9
+ * kill(Windows taskkill /PID <pid> /T /F 进程树;POSIX 进程组 SIGKILL)
10
+ * → 继续读 stdout/stderr 到 EOF(上限 1s,孙进程占 pipe 兜底)
11
+ * → resolve(不再 reject),结果文本为部分输出 + 尾部 <shell_metadata> 块。
12
+ *
13
+ * 计时职责归框架 executor(Tool.timeout 声明契约):本模块不设内部 setTimeout
14
+ * race;args.timeout 由 executor 经 fromArg 消费并 clamp。
15
+ */
16
+
17
+ /** 元数据块标记(仅终止态出现在结果尾部)。 */
18
+ declare const SHELL_METADATA_OPEN = "<shell_metadata>";
19
+ declare const SHELL_METADATA_CLOSE = "</shell_metadata>";
20
+ interface ShellRunResult {
21
+ stdout: string;
22
+ stderr: string;
23
+ output: string;
24
+ }
25
+ interface ShellRunContext {
26
+ /** 框架注入的合并 signal(用户打断与框架超时共用,见 ticket 023) */
27
+ signal?: AbortSignal;
28
+ /** 终止原因查询(executor 注入;未走终止协议时恒返回 null) */
29
+ termination?: () => ToolTerminationReason | null;
30
+ /** 当前终止 settle 的绝对截止时间(epoch ms);shell drain 消费同一预算 */
31
+ terminationDeadline?: () => number | null;
32
+ /**
33
+ * 进度发射上下文(工单 025):提供后执行中每 ~300ms 发射一次
34
+ * tool.progress 通知;缺省不发射(纯增量,行为不变)。
35
+ */
36
+ progress?: {
37
+ /** LLM 生成的 call.id */
38
+ callId?: string;
39
+ /** 工具名称 */
40
+ toolName: string;
41
+ /** 本次调用生效超时(毫秒);未声明 timeout 时为 null */
42
+ timeoutMs: number | null;
43
+ };
44
+ }
45
+ /**
46
+ * 截断输出并持久化完整内容到磁盘。
47
+ *
48
+ * 当输出超过 limit 时:
49
+ * 1. 将完整输出写入 workdir/.agentdev/temp/bash-output-<timestamp>-<random>.log
50
+ * 2. 返回截断版本(头 60% + 尾 40%),中间插入截断提示和文件路径引用
51
+ *
52
+ * 如果写盘失败,fallback 到纯截断(不丢失截断提示,但完整内容不可恢复)。
53
+ *
54
+ * @param forcePersist true 时无论长度一律落盘(终止态使用:被杀进程的已积累
55
+ * 输出必须可恢复),并返回 logPath 供元数据引用。
56
+ */
57
+ declare function processOutputWithPersistence(output: string, workdir: string, limit?: number, forcePersist?: boolean): Promise<[string, string | null]>;
58
+ interface ShellMetadataFields {
59
+ terminated: boolean;
60
+ reason: ToolTerminationReason;
61
+ durationMs: number;
62
+ exitCode: number | null;
63
+ outputBytes: number;
64
+ truncated: boolean;
65
+ logPath: string | null;
66
+ }
67
+ declare function formatShellMetadata(meta: ShellMetadataFields): string;
68
+
69
+ /**
70
+ * Shell Feature 工具定义
71
+ *
72
+ * 提供 bash 工具,在 Windows 上通过 Git Bash、在 Linux/macOS 上通过原生 bash 执行 Shell 命令,支持 AbortSignal 中断。
73
+ *
74
+ * 改进点(照搬 Claude Code 的优秀实践):
75
+ * 1. 命令引用:eval + 单引号包裹,彻底解决 syntax error near unexpected token '('
76
+ * 2. 非 -i 模式:去掉 interactive flag,消除 job control 警告
77
+ * 3. stdin redirect:自动添加 < /dev/null 防止命令挂起
78
+ * 4. Windows null rewrite:>nul → >/dev/null
79
+ * 5. 动态 bash 路径检测(Windows: Git Bash; Linux/macOS: $SHELL || /bin/bash)
80
+ * 6. 输出截断:防止大输出撑爆 LLM 上下文
81
+ *
82
+ * 终止语义(ticket 024 / ADR-0005):超时计时归框架 executor(Tool.timeout
83
+ * 声明契约);signal aborted 时 kill → drain 到 EOF → resolve 部分输出 +
84
+ * <shell_metadata> 块(见 shell-core.ts)。
85
+ */
86
+
87
+ interface ShellCommandToolOptions {
88
+ workspaceDir?: string;
89
+ workdir?: string;
90
+ resourceRoot?: string;
91
+ /** Override bash path detection (used when ShellFeature pre-detects the path) */
92
+ bashPath?: string;
93
+ /** 覆盖默认超时(manifest 配置 defaultTimeoutMs);缺省 120000 */
94
+ timeoutMs?: number;
95
+ /** 覆盖超时上限(manifest 配置 maxTimeoutMs);缺省 600000 */
96
+ maxTimeoutMs?: number;
97
+ }
98
+ interface ShellExecutionResult {
99
+ stdout: string;
100
+ stderr: string;
101
+ output: string;
102
+ }
103
+ /**
104
+ * 动态查找 Git Bash 的 bash.exe 路径。
105
+ *
106
+ * 查找顺序:
107
+ * 1. configuredPath 参数(来自 manifest 配置)
108
+ * 2. 环境变量 AGENTDEV_GIT_BASH_PATH
109
+ * 3. 环境变量 SHELL(如果包含 bash)
110
+ * 4. where bash(Windows)
111
+ * 5. 常见安装位置
112
+ *
113
+ * 返回 null 表示未找到(调用方应据此决定是否注册工具)。
114
+ */
115
+ declare function findGitBashPath(configuredPath?: string): string | null;
116
+ /**
117
+ * 运行 Shell 命令(支持 AbortSignal 中断;终止时收集部分输出并附元数据块)
118
+ *
119
+ * 关键改进:
120
+ * - 使用 eval + 单引号引用替代 naive 的双引号转义
121
+ * - 去掉 -i(interactive)flag
122
+ * - 添加 stdin redirect
123
+ * - 重写 Windows null redirect
124
+ */
125
+ declare function runShellCommand(command: string, options?: ShellCommandToolOptions, context?: ShellRunContext): Promise<ShellExecutionResult>;
126
+ declare function createShellCommandTool(description: string, options?: ShellCommandToolOptions): Tool;
127
+
128
+ /**
129
+ * PowerShell 命令执行工具
130
+ *
131
+ * 提供 PowerShell 路径检测、命令执行和工具定义。
132
+ * 与 bash 工具平行,共用 shell-core.ts 的「spawn + collect + 截断落盘 +
133
+ * 终止收集」管线(ticket 024),行为逐项一致(render 模板本就共用 bash.render.ts)。
134
+ */
135
+
136
+ /**
137
+ * 查找 PowerShell 可执行文件路径。
138
+ *
139
+ * 查找顺序:
140
+ * 1. configuredPath 参数(来自 manifest 配置)
141
+ * 2. 环境变量 AGENTDEV_POWERSHELL_PATH
142
+ * 3. pwsh(PowerShell 7+,跨平台)
143
+ * 4. powershell.exe(Windows PowerShell 5.1)
144
+ * 5. Windows 已知系统路径
145
+ *
146
+ * 返回 null 表示未找到。
147
+ */
148
+ declare function findPowerShellPath(configuredPath?: string): string | null;
149
+ interface PowerShellToolOptions {
150
+ workspaceDir?: string;
151
+ workdir?: string;
152
+ resourceRoot?: string;
153
+ /** 已检测到的 PowerShell 路径 */
154
+ psPath?: string;
155
+ /** 覆盖默认超时(manifest 配置 defaultTimeoutMs);缺省 120000 */
156
+ timeoutMs?: number;
157
+ /** 覆盖超时上限(manifest 配置 maxTimeoutMs);缺省 600000 */
158
+ maxTimeoutMs?: number;
159
+ }
160
+ /**
161
+ * 运行 PowerShell 命令(支持 AbortSignal 中断;终止时收集部分输出并附元数据块)
162
+ */
163
+ declare function runPowerShellCommand(command: string, options?: PowerShellToolOptions, context?: ShellRunContext): Promise<{
164
+ stdout: string;
165
+ stderr: string;
166
+ output: string;
167
+ }>;
168
+ declare function createPowerShellTool(description: string, options?: PowerShellToolOptions): Tool;
169
+
170
+ /**
171
+ * Shell command quoting utilities.
172
+ *
173
+ * Ported from Claude Code's shellQuoting.ts + shellQuote.ts.
174
+ *
175
+ * 核心思路:用单引号整体包裹命令字符串,使所有特殊字符(括号、管道符、
176
+ * 分号、$、反引号等)被 bash 视为字面量。然后通过 `eval` 进行二次解析。
177
+ *
178
+ * 这彻底解决了 `syntax error near unexpected token '('` 问题:
179
+ * - 旧方案:command.replace(/"/g, '\\"') 只转义双引号,
180
+ * 导致 `(`, `)`, `;`, `|` 等裸露在 bash 面前
181
+ * - 新方案:单引号包裹整个命令,eval 二次解析
182
+ */
183
+ /**
184
+ * 检测命令是否包含 heredoc 语法(<<EOF, <<'EOF', <<"EOF", <<-EOF 等)。
185
+ * 排除位运算左移(<<)的误判。
186
+ */
187
+ declare function containsHeredoc(command: string): boolean;
188
+ /**
189
+ * 检测命令是否已有 stdin 重定向(如 < file, </path, < /dev/null)。
190
+ * 排除 << (heredoc)、<< (位运算)、<(进程替换)。
191
+ */
192
+ declare function hasStdinRedirect(command: string): boolean;
193
+ /**
194
+ * 判断是否应该为命令添加 `< /dev/null` stdin 重定向。
195
+ *
196
+ * - heredoc 命令不需要(它们有自己的输入)
197
+ * - 已有 stdin 重定向的命令不需要
198
+ */
199
+ declare function shouldAddStdinRedirect(command: string): boolean;
200
+ declare function rewriteWindowsNullRedirect(command: string): string;
201
+ /**
202
+ * 对 shell 命令进行安全引用,保留 heredoc 和多行字符串的完整性。
203
+ *
204
+ * 返回的字符串可直接用于 `eval`,例如:
205
+ * const quoted = quoteShellCommand('echo "hello (world)"');
206
+ * // quoted === "'echo \"hello (world)\"' < /dev/null"
207
+ * // 完整命令: eval 'echo "hello (world)"' < /dev/null
208
+ *
209
+ * @param command 要引用的原始命令
210
+ * @param addStdinRedirect 是否追加 `< /dev/null` stdin 重定向
211
+ * @returns 安全引用后的命令字符串
212
+ */
213
+ declare function quoteShellCommand(command: string, addStdinRedirect?: boolean): string;
214
+
215
+ /**
216
+ * Safe Trash 工具定义
217
+ */
218
+
219
+ declare function createSafeTrashDeleteTool(workspaceDir?: string): Tool;
220
+ declare function createSafeTrashListTool(workspaceDir?: string): Tool;
221
+ declare function createSafeTrashRestoreTool(workspaceDir?: string): Tool;
222
+ declare const safeTrashDeleteTool: Tool;
223
+ declare const safeTrashListTool: Tool;
224
+ declare const safeTrashRestoreTool: Tool;
225
+
226
+ /**
227
+ * 已删除文件的信息
228
+ */
229
+ interface TrashedFileInfo {
230
+ index: number;
231
+ originalPath: string;
232
+ deletionDate: string;
233
+ operator: string | null;
234
+ trashFile: string;
235
+ infoFile: string;
236
+ size: number;
237
+ }
238
+ /**
239
+ * 恢复结果
240
+ */
241
+ interface RestoreResult {
242
+ success: boolean;
243
+ restored: string[];
244
+ failed: Array<{
245
+ path: string;
246
+ error: string;
247
+ }>;
248
+ skipped: string[];
249
+ restoredCount: number;
250
+ failedCount: number;
251
+ skippedCount: number;
252
+ }
253
+ /**
254
+ * 删除操作结果
255
+ */
256
+ interface SafeRmResult {
257
+ success: boolean;
258
+ moved: string[];
259
+ failed: string[];
260
+ movedCount: number;
261
+ failedCount: number;
262
+ }
263
+ /**
264
+ * 列表操作结果
265
+ */
266
+ interface ListResult {
267
+ success: boolean;
268
+ total: number;
269
+ files: TrashedFileInfo[];
270
+ }
271
+ /**
272
+ * 恢复目标类型
273
+ */
274
+ type RestoreTarget = number | string | number[] | string[];
275
+
276
+ /**
277
+ * SafeRm - 安全删除核心逻辑
278
+ */
279
+
280
+ declare function safeRm(workingDir: string | null, rmCommand: string, trashDir: string, operator?: string | null, verbose?: number): SafeRmResult;
281
+
282
+ /**
283
+ * SafeRestore - 安全恢复核心逻辑
284
+ */
285
+
286
+ declare function listTrashed(trashDir: string, operator?: string | null): ListResult;
287
+ declare function restore(trashDir: string, target: RestoreTarget, operator?: string | null, overwrite?: boolean, dryRun?: boolean, parseIndexRanges?: boolean): RestoreResult;
288
+
289
+ /**
290
+ * Shell Feature - 独立 npm 包
291
+ *
292
+ * 支持 Bash(Windows: Git Bash / Linux/macOS: 原生 bash)和 PowerShell 两种 Shell 环境。
293
+ * 根据用户配置和运行时探测结果,条件注册 Bash 和/或 PowerShell 工具。
294
+ *
295
+ * @example
296
+ * ```typescript
297
+ * import { ShellFeature } from '@agentdevjs/shell-feature';
298
+ * import { BasicAgent } from '@agentdevjs/core';
299
+ *
300
+ * const agent = new BasicAgent().use(new ShellFeature());
301
+ * ```
302
+ */
303
+
304
+ interface ShellFeatureConfig {
305
+ workspaceDir?: string;
306
+ workdir?: string;
307
+ resourceRoot?: string;
308
+ }
309
+ /**
310
+ * Shell Feature 实现
311
+ */
312
+ declare class ShellFeature implements AgentFeature {
313
+ readonly name = "shell";
314
+ readonly dependencies: string[];
315
+ readonly source: string;
316
+ readonly description = "\u63D0\u4F9B Bash/PowerShell \u547D\u4EE4\u6267\u884C\u80FD\u529B\uFF0C\u4EE5\u53CA\u5B89\u5168\u5220\u9664\u3001\u6062\u590D\u548C\u67E5\u770B\u5783\u573E\u6876\u5DE5\u5177\u3002";
317
+ private bashDescription?;
318
+ private powershellDescription?;
319
+ private _packageInfo;
320
+ private readonly workspaceDir;
321
+ private readonly workdir;
322
+ private readonly resourceRoot;
323
+ constructor(config?: ShellFeatureConfig);
324
+ /**
325
+ * 获取同步工具(垃圾桶工具)
326
+ */
327
+ getTools(): Tool[];
328
+ getFeatureManifest(): FeatureManifestDefinition;
329
+ private resolveShellConfig;
330
+ /**
331
+ * 获取异步工具(bash/powershell 工具,条件注册)
332
+ */
333
+ getAsyncTools(ctx: FeatureInitContext): Promise<Tool[]>;
334
+ /**
335
+ * 获取包信息(统一打包方案)
336
+ */
337
+ getPackageInfo(): PackageInfo | null;
338
+ /**
339
+ * 获取模板名称列表(统一打包方案)
340
+ */
341
+ getTemplateNames(): string[];
342
+ }
343
+
344
+ export { SHELL_METADATA_CLOSE, SHELL_METADATA_OPEN, type ShellCommandToolOptions, type ShellExecutionResult, ShellFeature, type ShellFeatureConfig, type ShellMetadataFields, type ShellRunResult, containsHeredoc, createPowerShellTool, createSafeTrashDeleteTool, createSafeTrashListTool, createSafeTrashRestoreTool, createShellCommandTool, findGitBashPath, findPowerShellPath, formatShellMetadata, hasStdinRedirect, listTrashed, processOutputWithPersistence, quoteShellCommand, restore, rewriteWindowsNullRedirect, runPowerShellCommand, runShellCommand, safeRm, safeTrashDeleteTool, safeTrashListTool, safeTrashRestoreTool, shouldAddStdinRedirect };