@renxqoo/renx-code 0.0.4 → 0.0.5

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.
Files changed (209) hide show
  1. package/bin/renx.cjs +16 -0
  2. package/package.json +2 -45
  3. package/src/agent/runtime/runtime.context-usage.test.ts +4 -5
  4. package/src/agent/runtime/runtime.error-handling.test.ts +4 -5
  5. package/src/agent/runtime/runtime.test.ts +7 -4
  6. package/src/agent/runtime/runtime.ts +3 -9
  7. package/src/agent/runtime/runtime.usage-forwarding.test.ts +4 -5
  8. package/src/agent/runtime/source-modules.test.ts +16 -35
  9. package/src/agent/runtime/source-modules.ts +17 -0
  10. package/vendor/agent-root/src/agent/ENTERPRISE_ACCEPTANCE_CHECKLIST.md +95 -0
  11. package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.html +1345 -0
  12. package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.md +1353 -0
  13. package/vendor/agent-root/src/agent/ERROR_CONTRACT.md +60 -0
  14. package/vendor/agent-root/src/agent/TEST_COVERAGE_ANALYSIS.md +278 -0
  15. package/vendor/agent-root/src/agent/__test__/error-contract.test.ts +72 -0
  16. package/vendor/agent-root/src/agent/__test__/types.test.ts +137 -0
  17. package/vendor/agent-root/src/agent/agent/__test__/abort-runtime.test.ts +83 -0
  18. package/vendor/agent-root/src/agent/agent/__test__/callback-safety.test.ts +34 -0
  19. package/vendor/agent-root/src/agent/agent/__test__/compaction.test.ts +323 -0
  20. package/vendor/agent-root/src/agent/agent/__test__/concurrency.test.ts +290 -0
  21. package/vendor/agent-root/src/agent/agent/__test__/error-normalizer.test.ts +377 -0
  22. package/vendor/agent-root/src/agent/agent/__test__/error.test.ts +212 -0
  23. package/vendor/agent-root/src/agent/agent/__test__/fault-injection.test.ts +295 -0
  24. package/vendor/agent-root/src/agent/agent/__test__/index.test.ts +3607 -0
  25. package/vendor/agent-root/src/agent/agent/__test__/logger.test.ts +35 -0
  26. package/vendor/agent-root/src/agent/agent/__test__/message-utils.test.ts +517 -0
  27. package/vendor/agent-root/src/agent/agent/__test__/telemetry.test.ts +97 -0
  28. package/vendor/agent-root/src/agent/agent/__test__/timeout-budget.test.ts +479 -0
  29. package/vendor/agent-root/src/agent/agent/__test__/tool-call-merge.test.ts +80 -0
  30. package/vendor/agent-root/src/agent/agent/__test__/tool-execution-ledger.test.ts +76 -0
  31. package/vendor/agent-root/src/agent/agent/__test__/write-buffer.test.ts +173 -0
  32. package/vendor/agent-root/src/agent/agent/__test__/write-file-session.test.ts +109 -0
  33. package/vendor/agent-root/src/agent/agent/abort-runtime.ts +71 -0
  34. package/vendor/agent-root/src/agent/agent/callback-safety.ts +33 -0
  35. package/vendor/agent-root/src/agent/agent/compaction.ts +291 -0
  36. package/vendor/agent-root/src/agent/agent/concurrency.ts +103 -0
  37. package/vendor/agent-root/src/agent/agent/error-normalizer.ts +190 -0
  38. package/vendor/agent-root/src/agent/agent/error.ts +198 -0
  39. package/vendor/agent-root/src/agent/agent/index.ts +1772 -0
  40. package/vendor/agent-root/src/agent/agent/logger.ts +65 -0
  41. package/vendor/agent-root/src/agent/agent/message-utils.ts +101 -0
  42. package/vendor/agent-root/src/agent/agent/stream-events.ts +61 -0
  43. package/vendor/agent-root/src/agent/agent/telemetry.ts +123 -0
  44. package/vendor/agent-root/src/agent/agent/timeout-budget.ts +227 -0
  45. package/vendor/agent-root/src/agent/agent/tool-call-merge.ts +111 -0
  46. package/vendor/agent-root/src/agent/agent/tool-execution-ledger.ts +164 -0
  47. package/vendor/agent-root/src/agent/agent/write-buffer.ts +188 -0
  48. package/vendor/agent-root/src/agent/agent/write-file-session.ts +238 -0
  49. package/vendor/agent-root/src/agent/app/__test__/agent-app-service.test.ts +1053 -0
  50. package/vendor/agent-root/src/agent/app/__test__/minimal-agent-application.test.ts +158 -0
  51. package/vendor/agent-root/src/agent/app/__test__/sqlite-agent-app-store.test.ts +437 -0
  52. package/vendor/agent-root/src/agent/app/agent-app-service.ts +748 -0
  53. package/vendor/agent-root/src/agent/app/contracts.ts +109 -0
  54. package/vendor/agent-root/src/agent/app/index.ts +5 -0
  55. package/vendor/agent-root/src/agent/app/minimal-agent-application.ts +151 -0
  56. package/vendor/agent-root/src/agent/app/ports.ts +72 -0
  57. package/vendor/agent-root/src/agent/app/sqlite-agent-app-store.ts +1182 -0
  58. package/vendor/agent-root/src/agent/app/sqlite-client.ts +177 -0
  59. package/vendor/agent-root/src/agent/docs/cli-app-layer/00-README.md +36 -0
  60. package/vendor/agent-root/src/agent/docs/cli-app-layer/01-scope-and-goals.md +33 -0
  61. package/vendor/agent-root/src/agent/docs/cli-app-layer/02-architecture-overview.md +40 -0
  62. package/vendor/agent-root/src/agent/docs/cli-app-layer/03-domain-model-and-contracts.md +91 -0
  63. package/vendor/agent-root/src/agent/docs/cli-app-layer/04-ports-and-interfaces.md +116 -0
  64. package/vendor/agent-root/src/agent/docs/cli-app-layer/05-run-orchestration-and-state-machine.md +52 -0
  65. package/vendor/agent-root/src/agent/docs/cli-app-layer/06-cli-commands-and-ux.md +53 -0
  66. package/vendor/agent-root/src/agent/docs/cli-app-layer/07-storage-design-local.md +52 -0
  67. package/vendor/agent-root/src/agent/docs/cli-app-layer/08-error-and-observability.md +40 -0
  68. package/vendor/agent-root/src/agent/docs/cli-app-layer/09-security-and-policy-boundary.md +19 -0
  69. package/vendor/agent-root/src/agent/docs/cli-app-layer/10-test-plan-and-acceptance.md +28 -0
  70. package/vendor/agent-root/src/agent/docs/cli-app-layer/11-implementation-phases.md +26 -0
  71. package/vendor/agent-root/src/agent/docs/cli-app-layer/12-open-questions-and-risks.md +30 -0
  72. package/vendor/agent-root/src/agent/docs/cli-app-layer/13-sqlite-schema-fields-and-rationale.md +567 -0
  73. package/vendor/agent-root/src/agent/docs/cli-app-layer/14-project-flow-mermaid.md +583 -0
  74. package/vendor/agent-root/src/agent/docs/cli-app-layer/15-openclaw-style-project-blueprint.md +972 -0
  75. package/vendor/agent-root/src/agent/error-contract.ts +154 -0
  76. package/vendor/agent-root/src/agent/prompts/system.ts +246 -0
  77. package/vendor/agent-root/src/agent/prompts/system1.ts +208 -0
  78. package/vendor/agent-root/src/agent/storage/__test__/file-history-store.test.ts +98 -0
  79. package/vendor/agent-root/src/agent/storage/file-history-store.ts +313 -0
  80. package/vendor/agent-root/src/agent/storage/file-storage-config.ts +94 -0
  81. package/vendor/agent-root/src/agent/storage/file-system.ts +31 -0
  82. package/vendor/agent-root/src/agent/storage/file-write-service.ts +21 -0
  83. package/vendor/agent-root/src/agent/tool/__test__/base-tool.test.ts +413 -0
  84. package/vendor/agent-root/src/agent/tool/__test__/bash-policy.test.ts +356 -0
  85. package/vendor/agent-root/src/agent/tool/__test__/bash.mocked-coverage.test.ts +375 -0
  86. package/vendor/agent-root/src/agent/tool/__test__/bash.test.ts +372 -0
  87. package/vendor/agent-root/src/agent/tool/__test__/error.test.ts +108 -0
  88. package/vendor/agent-root/src/agent/tool/__test__/file-edit-tool.test.ts +258 -0
  89. package/vendor/agent-root/src/agent/tool/__test__/file-history-tools.test.ts +121 -0
  90. package/vendor/agent-root/src/agent/tool/__test__/file-read-tool.test.ts +210 -0
  91. package/vendor/agent-root/src/agent/tool/__test__/glob.test.ts +139 -0
  92. package/vendor/agent-root/src/agent/tool/__test__/grep.mocked-coverage.test.ts +456 -0
  93. package/vendor/agent-root/src/agent/tool/__test__/grep.test.ts +192 -0
  94. package/vendor/agent-root/src/agent/tool/__test__/lsp.test.ts +300 -0
  95. package/vendor/agent-root/src/agent/tool/__test__/outside-workspace-confirmation.test.ts +214 -0
  96. package/vendor/agent-root/src/agent/tool/__test__/path-security.test.ts +336 -0
  97. package/vendor/agent-root/src/agent/tool/__test__/skill-loader.test.ts +494 -0
  98. package/vendor/agent-root/src/agent/tool/__test__/skill-parser.test.ts +543 -0
  99. package/vendor/agent-root/src/agent/tool/__test__/skill-tool.test.ts +172 -0
  100. package/vendor/agent-root/src/agent/tool/__test__/task-concurrency-and-version.test.ts +116 -0
  101. package/vendor/agent-root/src/agent/tool/__test__/task-create-get-list-update.test.ts +267 -0
  102. package/vendor/agent-root/src/agent/tool/__test__/task-create.test.ts +519 -0
  103. package/vendor/agent-root/src/agent/tool/__test__/task-errors.test.ts +225 -0
  104. package/vendor/agent-root/src/agent/tool/__test__/task-output-blocking.test.ts +223 -0
  105. package/vendor/agent-root/src/agent/tool/__test__/task-output.test.ts +184 -0
  106. package/vendor/agent-root/src/agent/tool/__test__/task-parent-abort.test.ts +287 -0
  107. package/vendor/agent-root/src/agent/tool/__test__/task-real-runner-adapter.test.ts +190 -0
  108. package/vendor/agent-root/src/agent/tool/__test__/task-run-lifecycle.test.ts +352 -0
  109. package/vendor/agent-root/src/agent/tool/__test__/task-store-runner-branches.test.ts +395 -0
  110. package/vendor/agent-root/src/agent/tool/__test__/task-store.test.ts +391 -0
  111. package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config-integration.test.ts +176 -0
  112. package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config.test.ts +68 -0
  113. package/vendor/agent-root/src/agent/tool/__test__/task-tools-core-edges.test.ts +630 -0
  114. package/vendor/agent-root/src/agent/tool/__test__/task-tools-runtime-edges.test.ts +732 -0
  115. package/vendor/agent-root/src/agent/tool/__test__/task-types.test.ts +494 -0
  116. package/vendor/agent-root/src/agent/tool/__test__/task-utils-branches.test.ts +175 -0
  117. package/vendor/agent-root/src/agent/tool/__test__/tool-manager.test.ts +505 -0
  118. package/vendor/agent-root/src/agent/tool/__test__/types.test.ts +55 -0
  119. package/vendor/agent-root/src/agent/tool/__test__/web-fetch.test.ts +244 -0
  120. package/vendor/agent-root/src/agent/tool/__test__/web-search.test.ts +290 -0
  121. package/vendor/agent-root/src/agent/tool/__test__/write-file.test.ts +368 -0
  122. package/vendor/agent-root/src/agent/tool/base-tool.ts +345 -0
  123. package/vendor/agent-root/src/agent/tool/bash-policy.ts +636 -0
  124. package/vendor/agent-root/src/agent/tool/bash.ts +688 -0
  125. package/vendor/agent-root/src/agent/tool/error.ts +131 -0
  126. package/vendor/agent-root/src/agent/tool/file-edit-tool.ts +264 -0
  127. package/vendor/agent-root/src/agent/tool/file-history-list.ts +103 -0
  128. package/vendor/agent-root/src/agent/tool/file-history-restore.ts +149 -0
  129. package/vendor/agent-root/src/agent/tool/file-read-tool.ts +211 -0
  130. package/vendor/agent-root/src/agent/tool/glob.ts +171 -0
  131. package/vendor/agent-root/src/agent/tool/grep.ts +496 -0
  132. package/vendor/agent-root/src/agent/tool/lsp.ts +481 -0
  133. package/vendor/agent-root/src/agent/tool/path-security.ts +117 -0
  134. package/vendor/agent-root/src/agent/tool/search/common.ts +153 -0
  135. package/vendor/agent-root/src/agent/tool/skill/index.ts +13 -0
  136. package/vendor/agent-root/src/agent/tool/skill/loader.ts +229 -0
  137. package/vendor/agent-root/src/agent/tool/skill/parser.ts +124 -0
  138. package/vendor/agent-root/src/agent/tool/skill/types.ts +27 -0
  139. package/vendor/agent-root/src/agent/tool/skill-tool.ts +143 -0
  140. package/vendor/agent-root/src/agent/tool/task-create.ts +186 -0
  141. package/vendor/agent-root/src/agent/tool/task-errors.ts +42 -0
  142. package/vendor/agent-root/src/agent/tool/task-get.ts +116 -0
  143. package/vendor/agent-root/src/agent/tool/task-graph.ts +78 -0
  144. package/vendor/agent-root/src/agent/tool/task-list.ts +141 -0
  145. package/vendor/agent-root/src/agent/tool/task-mock-runner-adapter.ts +232 -0
  146. package/vendor/agent-root/src/agent/tool/task-output.ts +223 -0
  147. package/vendor/agent-root/src/agent/tool/task-parent-abort.ts +115 -0
  148. package/vendor/agent-root/src/agent/tool/task-real-runner-adapter.ts +336 -0
  149. package/vendor/agent-root/src/agent/tool/task-runner-adapter.ts +55 -0
  150. package/vendor/agent-root/src/agent/tool/task-stop.ts +187 -0
  151. package/vendor/agent-root/src/agent/tool/task-store.ts +217 -0
  152. package/vendor/agent-root/src/agent/tool/task-subagent-config.ts +149 -0
  153. package/vendor/agent-root/src/agent/tool/task-types.ts +264 -0
  154. package/vendor/agent-root/src/agent/tool/task-update.ts +315 -0
  155. package/vendor/agent-root/src/agent/tool/task.ts +209 -0
  156. package/vendor/agent-root/src/agent/tool/tool-manager.ts +362 -0
  157. package/vendor/agent-root/src/agent/tool/tool-prompts.ts +242 -0
  158. package/vendor/agent-root/src/agent/tool/types.ts +116 -0
  159. package/vendor/agent-root/src/agent/tool/web-fetch.ts +227 -0
  160. package/vendor/agent-root/src/agent/tool/web-search.ts +208 -0
  161. package/vendor/agent-root/src/agent/tool/write-file.ts +497 -0
  162. package/vendor/agent-root/src/agent/types.ts +232 -0
  163. package/vendor/agent-root/src/agent/utils/__tests__/index.test.ts +18 -0
  164. package/vendor/agent-root/src/agent/utils/__tests__/message-utils.test.ts +610 -0
  165. package/vendor/agent-root/src/agent/utils/__tests__/message.test.ts +223 -0
  166. package/vendor/agent-root/src/agent/utils/__tests__/token.test.ts +42 -0
  167. package/vendor/agent-root/src/agent/utils/index.ts +16 -0
  168. package/vendor/agent-root/src/agent/utils/message.ts +171 -0
  169. package/vendor/agent-root/src/agent/utils/token.ts +28 -0
  170. package/vendor/agent-root/src/config/__tests__/load-config-to-env.test.ts +129 -0
  171. package/vendor/agent-root/src/config/__tests__/loader.test.ts +247 -0
  172. package/vendor/agent-root/src/config/__tests__/runtime.test.ts +88 -0
  173. package/vendor/agent-root/src/config/index.ts +54 -0
  174. package/vendor/agent-root/src/config/loader.ts +431 -0
  175. package/vendor/agent-root/src/config/paths.ts +30 -0
  176. package/vendor/agent-root/src/config/runtime.ts +163 -0
  177. package/vendor/agent-root/src/config/types.ts +70 -0
  178. package/vendor/agent-root/src/logger/index.ts +57 -0
  179. package/vendor/agent-root/src/logger/logger.ts +819 -0
  180. package/vendor/agent-root/src/logger/types.ts +150 -0
  181. package/vendor/agent-root/src/providers/__tests__/errors.test.ts +441 -0
  182. package/vendor/agent-root/src/providers/__tests__/index.test.ts +16 -0
  183. package/vendor/agent-root/src/providers/__tests__/openai-compatible.options.test.ts +318 -0
  184. package/vendor/agent-root/src/providers/__tests__/openai-compatible.test.ts +600 -0
  185. package/vendor/agent-root/src/providers/__tests__/registry.test.ts +449 -0
  186. package/vendor/agent-root/src/providers/__tests__/responses-adapter.test.ts +298 -0
  187. package/vendor/agent-root/src/providers/adapters/__tests__/anthropic.test.ts +354 -0
  188. package/vendor/agent-root/src/providers/adapters/__tests__/kimi.test.ts +58 -0
  189. package/vendor/agent-root/src/providers/adapters/__tests__/standard.test.ts +261 -0
  190. package/vendor/agent-root/src/providers/adapters/anthropic.ts +572 -0
  191. package/vendor/agent-root/src/providers/adapters/base.ts +131 -0
  192. package/vendor/agent-root/src/providers/adapters/kimi.ts +48 -0
  193. package/vendor/agent-root/src/providers/adapters/responses.ts +732 -0
  194. package/vendor/agent-root/src/providers/adapters/standard.ts +120 -0
  195. package/vendor/agent-root/src/providers/http/__tests__/client.timeout.test.ts +313 -0
  196. package/vendor/agent-root/src/providers/http/client.ts +289 -0
  197. package/vendor/agent-root/src/providers/http/stream-parser.ts +109 -0
  198. package/vendor/agent-root/src/providers/index.ts +76 -0
  199. package/vendor/agent-root/src/providers/kimi-headers.ts +177 -0
  200. package/vendor/agent-root/src/providers/openai-compatible.ts +387 -0
  201. package/vendor/agent-root/src/providers/registry/model-config.ts +230 -0
  202. package/vendor/agent-root/src/providers/registry/provider-factory.ts +123 -0
  203. package/vendor/agent-root/src/providers/registry.ts +135 -0
  204. package/vendor/agent-root/src/providers/types/api.ts +284 -0
  205. package/vendor/agent-root/src/providers/types/config.ts +58 -0
  206. package/vendor/agent-root/src/providers/types/errors.ts +323 -0
  207. package/vendor/agent-root/src/providers/types/index.ts +72 -0
  208. package/vendor/agent-root/src/providers/types/provider.ts +45 -0
  209. package/vendor/agent-root/src/providers/types/registry.ts +88 -0
@@ -0,0 +1,688 @@
1
+ import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import * as fs from 'node:fs';
4
+ import * as fsp from 'node:fs/promises';
5
+ import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import stripAnsi from 'strip-ansi';
8
+ import { z } from 'zod';
9
+ import { BaseTool, ToolResult } from './base-tool';
10
+ import { ToolExecutionError } from './error';
11
+ import { evaluateBashPolicy, type BashPolicyEffect, type BashPolicyMode } from './bash-policy';
12
+ import type { ToolExecutionContext } from './types';
13
+ import { BASH_TOOL_DESCRIPTION } from './tool-prompts';
14
+
15
+ const runInBackgroundSchema = z.preprocess((value) => {
16
+ if (typeof value === 'string') {
17
+ const normalized = value.trim().toLowerCase();
18
+ if (normalized === 'true') {
19
+ return true;
20
+ }
21
+ if (normalized === 'false') {
22
+ return false;
23
+ }
24
+ }
25
+ return value;
26
+ }, z.boolean());
27
+
28
+ const schema = z
29
+ .object({
30
+ command: z.string().min(1).describe('The bash command to run'),
31
+ timeout: z
32
+ .number()
33
+ .int()
34
+ .min(0)
35
+ .max(600000)
36
+ .optional()
37
+ .describe('Command timeout in milliseconds'),
38
+ run_in_background: runInBackgroundSchema.optional().describe('Run command in background'),
39
+ })
40
+ .strict();
41
+
42
+ interface PolicyDecision {
43
+ effect: BashPolicyEffect;
44
+ reason?: string;
45
+ }
46
+
47
+ interface CommandExecutionResult {
48
+ exitCode: number;
49
+ output: string;
50
+ timedOut: boolean;
51
+ }
52
+
53
+ interface BackgroundExecutionResult {
54
+ pid?: number;
55
+ logPath: string;
56
+ }
57
+
58
+ type StreamEscapeParseMode = 'text' | 'escape' | 'csi' | 'osc' | 'dcs' | 'pm' | 'apc' | 'sos';
59
+
60
+ interface StreamChunkSanitizerState {
61
+ mode: StreamEscapeParseMode;
62
+ awaitingStringTerminator: boolean;
63
+ }
64
+
65
+ export interface BashToolOptions {
66
+ defaultTimeoutMs?: number;
67
+ backgroundLogDir?: string;
68
+ policyMode?: BashPolicyMode;
69
+ maxOutputLength?: number;
70
+ }
71
+
72
+ const CONFIRMABLE_DENY_REASON_PATTERNS: RegExp[] = [
73
+ /Inline Python execution is blocked for security reasons/i,
74
+ /Inline Node\.js execution is blocked for security reasons/i,
75
+ /eval command is blocked for security reasons/i,
76
+ /exec command is blocked for security reasons/i,
77
+ ];
78
+
79
+ const WINDOWS_GIT_BASH_PATHS = [
80
+ 'C:/Program Files/Git/bin/bash.exe',
81
+ 'C:/Program Files (x86)/Git/bin/bash.exe',
82
+ ];
83
+
84
+ export class BashTool extends BaseTool<typeof schema> {
85
+ name = 'bash';
86
+ description = BASH_TOOL_DESCRIPTION;
87
+ parameters = schema;
88
+
89
+ private readonly defaultTimeoutMs: number;
90
+ private readonly backgroundLogDir: string;
91
+ private readonly policyMode: BashPolicyMode;
92
+ private readonly maxOutputLength: number;
93
+
94
+ constructor(options: BashToolOptions = {}) {
95
+ super();
96
+ this.defaultTimeoutMs =
97
+ options.defaultTimeoutMs && options.defaultTimeoutMs > 0 ? options.defaultTimeoutMs : 60000;
98
+ this.backgroundLogDir = options.backgroundLogDir || os.tmpdir();
99
+ this.policyMode = options.policyMode || this.resolvePolicyModeFromEnv();
100
+ this.maxOutputLength =
101
+ options.maxOutputLength && options.maxOutputLength > 0 ? options.maxOutputLength : 30000;
102
+ }
103
+
104
+ override shouldConfirm(args: z.infer<typeof schema>): boolean {
105
+ const decision = this.validatePolicy(args.command);
106
+ return decision.effect === 'ask';
107
+ }
108
+
109
+ async execute(args: z.infer<typeof schema>, context?: ToolExecutionContext): Promise<ToolResult> {
110
+ const policy = this.validatePolicy(args.command);
111
+ if (policy.effect === 'deny') {
112
+ const message = `COMMAND_BLOCKED_BY_POLICY: ${policy.reason || 'Command not allowed'}`;
113
+ return {
114
+ success: false,
115
+ output: message,
116
+ error: new ToolExecutionError(message),
117
+ metadata: {
118
+ error: 'COMMAND_BLOCKED_BY_POLICY',
119
+ reason: policy.reason,
120
+ },
121
+ };
122
+ }
123
+
124
+ try {
125
+ if (args.run_in_background) {
126
+ const background = await this.executeInBackground(args.command);
127
+ const output = `BACKGROUND_STARTED: pid=${background.pid ?? 'unknown'}, log=${background.logPath}`;
128
+ await context?.onChunk?.({
129
+ type: 'info',
130
+ data: output,
131
+ content: output,
132
+ });
133
+ return {
134
+ success: true,
135
+ output,
136
+ metadata: {
137
+ pid: background.pid,
138
+ logPath: background.logPath,
139
+ run_in_background: true,
140
+ },
141
+ };
142
+ }
143
+
144
+ const timeoutMs = typeof args.timeout === 'number' ? args.timeout : this.defaultTimeoutMs;
145
+ const commandResult = await this.executeForeground(
146
+ args.command,
147
+ timeoutMs,
148
+ context?.toolAbortSignal,
149
+ context
150
+ );
151
+
152
+ if (commandResult.timedOut) {
153
+ const timeoutMessage = `COMMAND_TIMEOUT: exceeded ${timeoutMs}ms`;
154
+ return {
155
+ success: false,
156
+ output: timeoutMessage,
157
+ summary: `Command timed out after ${timeoutMs}ms.`,
158
+ payload: {
159
+ exitCode: commandResult.exitCode,
160
+ timeoutMs,
161
+ timedOut: true,
162
+ },
163
+ error: new ToolExecutionError(timeoutMessage),
164
+ metadata: {
165
+ error: 'COMMAND_TIMEOUT',
166
+ timeoutMs,
167
+ exitCode: commandResult.exitCode,
168
+ },
169
+ };
170
+ }
171
+
172
+ const sanitized = this.sanitizeOutput(commandResult.output);
173
+ const truncated = this.truncateOutput(sanitized);
174
+
175
+ if (commandResult.exitCode === 0) {
176
+ const summary =
177
+ truncated.output.length > 0
178
+ ? truncated.truncated
179
+ ? 'Command completed successfully. Output truncated.'
180
+ : 'Command completed successfully.'
181
+ : 'Command completed successfully with no output.';
182
+ return {
183
+ success: true,
184
+ output: truncated.output,
185
+ summary,
186
+ payload: {
187
+ exitCode: commandResult.exitCode,
188
+ timedOut: false,
189
+ truncated: truncated.truncated,
190
+ hasOutput: truncated.output.length > 0,
191
+ },
192
+ metadata: {
193
+ output: truncated.output,
194
+ exitCode: commandResult.exitCode,
195
+ truncated: truncated.truncated,
196
+ },
197
+ };
198
+ }
199
+
200
+ const failureOutput =
201
+ truncated.output || `Command failed with exit code ${commandResult.exitCode}`;
202
+ return {
203
+ success: false,
204
+ output: failureOutput,
205
+ summary: `Command failed with exit code ${commandResult.exitCode}.`,
206
+ payload: {
207
+ exitCode: commandResult.exitCode,
208
+ timedOut: false,
209
+ truncated: truncated.truncated,
210
+ },
211
+ error: new ToolExecutionError(failureOutput),
212
+ metadata: {
213
+ output: truncated.output,
214
+ exitCode: commandResult.exitCode,
215
+ truncated: truncated.truncated,
216
+ error: `EXIT_CODE_${commandResult.exitCode}`,
217
+ },
218
+ };
219
+ } catch (error) {
220
+ const message = error instanceof Error ? error.message : String(error);
221
+ const output = `EXECUTION_FAILED: ${message}`;
222
+ return {
223
+ success: false,
224
+ output,
225
+ summary: `Command execution failed: ${message}`,
226
+ error: new ToolExecutionError(output),
227
+ metadata: {
228
+ error: 'EXECUTION_FAILED',
229
+ message,
230
+ },
231
+ };
232
+ }
233
+ }
234
+
235
+ private validatePolicy(command: string): PolicyDecision {
236
+ const normalized = command.trim();
237
+ if (!normalized) {
238
+ return { effect: 'deny', reason: 'Command is empty' };
239
+ }
240
+
241
+ const decision = evaluateBashPolicy(normalized, {
242
+ mode: this.policyMode,
243
+ allowlistMissEffect: 'ask',
244
+ allowlistMissReason: (commandName) =>
245
+ `Command "${commandName}" is not in allowed command list and requires user confirmation`,
246
+ });
247
+
248
+ const denyReason = typeof decision.reason === 'string' ? decision.reason : undefined;
249
+ if (
250
+ decision.effect === 'deny' &&
251
+ denyReason &&
252
+ CONFIRMABLE_DENY_REASON_PATTERNS.some((pattern) => pattern.test(denyReason))
253
+ ) {
254
+ return {
255
+ effect: 'ask',
256
+ reason: `${denyReason} (requires explicit confirmation)`,
257
+ };
258
+ }
259
+
260
+ return {
261
+ effect: decision.effect,
262
+ reason: decision.reason,
263
+ };
264
+ }
265
+
266
+ private resolvePolicyModeFromEnv(): BashPolicyMode {
267
+ const raw = (process.env.BASH_TOOL_POLICY || 'guarded').trim().toLowerCase();
268
+ return raw === 'permissive' ? 'permissive' : 'guarded';
269
+ }
270
+
271
+ private async executeForeground(
272
+ command: string,
273
+ timeoutMs: number,
274
+ abortSignal?: AbortSignal,
275
+ context?: ToolExecutionContext
276
+ ): Promise<CommandExecutionResult> {
277
+ const { shellPath, shellArgs } = this.resolveShell(command);
278
+
279
+ return new Promise<CommandExecutionResult>((resolve, reject) => {
280
+ let output = '';
281
+ let timedOut = false;
282
+ let settled = false;
283
+ const stdoutSanitizerState = this.createStreamChunkSanitizerState();
284
+ const stderrSanitizerState = this.createStreamChunkSanitizerState();
285
+
286
+ const child = spawn(shellPath, shellArgs, {
287
+ cwd: process.cwd(),
288
+ env: this.getExecutionEnv(),
289
+ stdio: ['ignore', 'pipe', 'pipe'],
290
+ windowsHide: true,
291
+ detached: process.platform !== 'win32',
292
+ });
293
+
294
+ const cleanup = () => {
295
+ abortSignal?.removeEventListener('abort', abortHandler);
296
+ if (timeout) {
297
+ clearTimeout(timeout);
298
+ }
299
+ };
300
+
301
+ const finish = (result: CommandExecutionResult) => {
302
+ if (settled) {
303
+ return;
304
+ }
305
+ settled = true;
306
+ cleanup();
307
+ resolve(result);
308
+ };
309
+
310
+ const fail = (error: Error) => {
311
+ if (settled) {
312
+ return;
313
+ }
314
+ settled = true;
315
+ cleanup();
316
+ reject(error);
317
+ };
318
+
319
+ const emitChunk = (type: 'stdout' | 'stderr', chunk: Buffer) => {
320
+ const text = chunk.toString('utf8');
321
+ output += text;
322
+ const sanitizerState = type === 'stdout' ? stdoutSanitizerState : stderrSanitizerState;
323
+ const sanitized = this.sanitizeStreamChunk(text, sanitizerState);
324
+ if (!sanitized) {
325
+ return;
326
+ }
327
+ void context?.onChunk?.({
328
+ type,
329
+ data: sanitized,
330
+ content: sanitized,
331
+ });
332
+ };
333
+
334
+ child.stdout?.on('data', (chunk: Buffer) => emitChunk('stdout', chunk));
335
+ child.stderr?.on('data', (chunk: Buffer) => emitChunk('stderr', chunk));
336
+
337
+ child.once('error', fail);
338
+ child.once('close', (code, signal) => {
339
+ const exitCode = code ?? (signal ? 1 : 0);
340
+ finish({
341
+ exitCode: timedOut ? 124 : exitCode,
342
+ output,
343
+ timedOut,
344
+ });
345
+ });
346
+
347
+ const abortHandler = () => {
348
+ timedOut = true;
349
+ this.terminateChildProcess(child);
350
+ };
351
+
352
+ if (abortSignal?.aborted) {
353
+ abortHandler();
354
+ } else if (abortSignal) {
355
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
356
+ }
357
+
358
+ const timeout =
359
+ timeoutMs > 0
360
+ ? setTimeout(() => {
361
+ timedOut = true;
362
+ this.terminateChildProcess(child);
363
+ }, timeoutMs)
364
+ : undefined;
365
+ });
366
+ }
367
+
368
+ private async executeInBackground(command: string): Promise<BackgroundExecutionResult> {
369
+ await fsp.mkdir(this.backgroundLogDir, { recursive: true });
370
+
371
+ const logPath = path.join(
372
+ this.backgroundLogDir,
373
+ `agent-bash-bg-${Date.now()}-${randomUUID().slice(0, 8)}.log`
374
+ );
375
+
376
+ await fsp.writeFile(logPath, '', 'utf8');
377
+
378
+ const quotedLogPath =
379
+ process.platform === 'win32'
380
+ ? `"${logPath.replace(/"/g, '""')}"`
381
+ : `'${logPath.replace(/'/g, `'\\''`)}'`;
382
+ const redirectedCommand = `${command} >> ${quotedLogPath} 2>&1`;
383
+
384
+ const { shellPath, shellArgs } = this.resolveShell(redirectedCommand);
385
+
386
+ const child = spawn(shellPath, shellArgs, {
387
+ cwd: process.cwd(),
388
+ env: this.getExecutionEnv(),
389
+ detached: true,
390
+ stdio: 'ignore',
391
+ windowsHide: true,
392
+ });
393
+
394
+ child.unref();
395
+
396
+ return {
397
+ pid: child.pid,
398
+ logPath,
399
+ };
400
+ }
401
+
402
+ private resolveShell(command: string): { shellPath: string; shellArgs: string[] } {
403
+ if (process.platform === 'win32') {
404
+ const gitBash = this.findGitBashPath();
405
+ if (gitBash) {
406
+ return {
407
+ shellPath: gitBash,
408
+ shellArgs: ['-lc', command],
409
+ };
410
+ }
411
+ return {
412
+ shellPath: process.env.COMSPEC || 'cmd.exe',
413
+ shellArgs: ['/d', '/s', '/c', command],
414
+ };
415
+ }
416
+
417
+ return {
418
+ shellPath: '/bin/bash',
419
+ shellArgs: ['-lc', command],
420
+ };
421
+ }
422
+
423
+ private findGitBashPath(): string | null {
424
+ if (process.platform !== 'win32') {
425
+ return null;
426
+ }
427
+
428
+ const configured = process.env.BASH_TOOL_SHELL;
429
+ if (configured && fs.existsSync(configured)) {
430
+ return configured;
431
+ }
432
+
433
+ for (const candidate of WINDOWS_GIT_BASH_PATHS) {
434
+ if (fs.existsSync(candidate)) {
435
+ return candidate;
436
+ }
437
+ }
438
+
439
+ try {
440
+ const probe = spawnSync('where', ['git'], {
441
+ stdio: 'pipe',
442
+ encoding: 'utf8',
443
+ });
444
+ if (probe.status === 0 && probe.stdout) {
445
+ const gitPath = String(probe.stdout).split(/\r?\n/)[0]?.trim();
446
+ if (gitPath && fs.existsSync(gitPath)) {
447
+ const gitDir = path.dirname(gitPath);
448
+ const gitRoot = path.dirname(gitDir);
449
+ const inferred = path.join(gitRoot, 'bin', 'bash.exe');
450
+ if (fs.existsSync(inferred)) {
451
+ return inferred;
452
+ }
453
+ }
454
+ }
455
+ } catch {
456
+ return null;
457
+ }
458
+
459
+ return null;
460
+ }
461
+
462
+ private getExecutionEnv(): NodeJS.ProcessEnv {
463
+ const env = { ...process.env };
464
+
465
+ if (process.platform === 'win32') {
466
+ env['CHCP'] = '65001';
467
+ env['ANSICON'] = '';
468
+ env['ConEmuANSI'] = 'OFF';
469
+ env['TERM'] = 'dumb';
470
+ }
471
+
472
+ env['LANG'] = 'en_US.UTF-8';
473
+ env['LC_ALL'] = 'en_US.UTF-8';
474
+
475
+ return env;
476
+ }
477
+
478
+ private createStreamChunkSanitizerState(): StreamChunkSanitizerState {
479
+ return {
480
+ mode: 'text',
481
+ awaitingStringTerminator: false,
482
+ };
483
+ }
484
+
485
+ private stripStreamControlSequences(input: string, state: StreamChunkSanitizerState): string {
486
+ if (!input) {
487
+ return input;
488
+ }
489
+
490
+ let plainText = '';
491
+
492
+ for (let index = 0; index < input.length; index += 1) {
493
+ const char = input[index];
494
+ const code = input.charCodeAt(index);
495
+ if (!char || Number.isNaN(code)) {
496
+ continue;
497
+ }
498
+
499
+ if (state.mode === 'text') {
500
+ if (char === '\u001b') {
501
+ state.mode = 'escape';
502
+ state.awaitingStringTerminator = false;
503
+ continue;
504
+ }
505
+ if (code === 0x9b) {
506
+ state.mode = 'csi';
507
+ state.awaitingStringTerminator = false;
508
+ continue;
509
+ }
510
+ plainText += char;
511
+ continue;
512
+ }
513
+
514
+ if (state.mode === 'escape') {
515
+ if (char === '[') {
516
+ state.mode = 'csi';
517
+ } else if (char === ']') {
518
+ state.mode = 'osc';
519
+ state.awaitingStringTerminator = false;
520
+ } else if (char === 'P') {
521
+ state.mode = 'dcs';
522
+ state.awaitingStringTerminator = false;
523
+ } else if (char === '^') {
524
+ state.mode = 'pm';
525
+ state.awaitingStringTerminator = false;
526
+ } else if (char === '_') {
527
+ state.mode = 'apc';
528
+ state.awaitingStringTerminator = false;
529
+ } else if (char === 'X') {
530
+ state.mode = 'sos';
531
+ state.awaitingStringTerminator = false;
532
+ } else {
533
+ state.mode = 'text';
534
+ state.awaitingStringTerminator = false;
535
+ }
536
+ continue;
537
+ }
538
+
539
+ if (state.mode === 'csi') {
540
+ if (code >= 0x40 && code <= 0x7e) {
541
+ state.mode = 'text';
542
+ state.awaitingStringTerminator = false;
543
+ }
544
+ continue;
545
+ }
546
+
547
+ if (state.awaitingStringTerminator) {
548
+ if (char === '\\') {
549
+ state.mode = 'text';
550
+ state.awaitingStringTerminator = false;
551
+ continue;
552
+ }
553
+ state.awaitingStringTerminator = false;
554
+ }
555
+
556
+ if (code === 0x07 || code === 0x9c) {
557
+ state.mode = 'text';
558
+ continue;
559
+ }
560
+
561
+ if (char === '\u001b') {
562
+ state.awaitingStringTerminator = true;
563
+ }
564
+ }
565
+
566
+ return plainText;
567
+ }
568
+
569
+ private sanitizeStreamChunk(output: string, state: StreamChunkSanitizerState): string {
570
+ if (!output) {
571
+ return output;
572
+ }
573
+
574
+ let sanitized = this.stripStreamControlSequences(output, state);
575
+ sanitized = stripAnsi(sanitized);
576
+ sanitized = sanitized.replace(/\uFFFD/g, '');
577
+ sanitized = sanitized.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
578
+ sanitized = this.stripUnsupportedControlChars(sanitized);
579
+
580
+ return sanitized;
581
+ }
582
+
583
+ private stripUnsupportedControlChars(value: string): string {
584
+ if (!value) {
585
+ return value;
586
+ }
587
+
588
+ let normalized = '';
589
+ for (const char of value) {
590
+ const code = char.charCodeAt(0);
591
+ const isAllowedWhitespace = code === 0x09 || code === 0x0a;
592
+ const isPrintableAscii = code >= 0x20 && code <= 0x7e;
593
+ const isNonAscii = code >= 0x80;
594
+ if (isAllowedWhitespace || isPrintableAscii || isNonAscii) {
595
+ normalized += char;
596
+ }
597
+ }
598
+
599
+ return normalized;
600
+ }
601
+
602
+ private sanitizeOutput(output: string): string {
603
+ if (!output) {
604
+ return output;
605
+ }
606
+
607
+ const streamState = this.createStreamChunkSanitizerState();
608
+ let sanitized = this.sanitizeStreamChunk(output, streamState);
609
+ sanitized = sanitized.replace(/\n{3,}/g, '\n\n');
610
+
611
+ return sanitized.trim();
612
+ }
613
+
614
+ private truncateOutput(output: string): { output: string; truncated: boolean } {
615
+ const maxLength = this.maxOutputLength;
616
+ if (output.length <= maxLength) {
617
+ return { output, truncated: false };
618
+ }
619
+
620
+ const marker = '[... Output Truncated for Brevity ...]';
621
+ const separator = '\n\n';
622
+ const reserved = marker.length + separator.length * 2;
623
+ const available = maxLength - reserved;
624
+
625
+ if (available <= 20) {
626
+ return {
627
+ output: output.slice(0, maxLength),
628
+ truncated: true,
629
+ };
630
+ }
631
+
632
+ const headLength = Math.min(3000, Math.floor(available / 2));
633
+ const tailLength = Math.min(3000, available - headLength);
634
+
635
+ return {
636
+ output:
637
+ output.slice(0, headLength) +
638
+ `${separator}${marker}${separator}` +
639
+ output.slice(Math.max(0, output.length - tailLength)),
640
+ truncated: true,
641
+ };
642
+ }
643
+
644
+ private terminateChildProcess(child: ChildProcess): void {
645
+ if (!child.pid) {
646
+ return;
647
+ }
648
+
649
+ if (process.platform === 'win32') {
650
+ try {
651
+ const killer = spawn('taskkill', ['/pid', String(child.pid), '/f', '/t'], {
652
+ stdio: 'ignore',
653
+ windowsHide: true,
654
+ });
655
+ killer.unref();
656
+ } catch {
657
+ // ignore
658
+ }
659
+ return;
660
+ }
661
+
662
+ try {
663
+ process.kill(-child.pid, 'SIGTERM');
664
+ } catch {
665
+ try {
666
+ child.kill('SIGTERM');
667
+ } catch {
668
+ // ignore
669
+ }
670
+ }
671
+
672
+ setTimeout(() => {
673
+ if (!child.killed) {
674
+ try {
675
+ process.kill(-child.pid!, 'SIGKILL');
676
+ } catch {
677
+ try {
678
+ child.kill('SIGKILL');
679
+ } catch {
680
+ // ignore
681
+ }
682
+ }
683
+ }
684
+ }, 200);
685
+ }
686
+ }
687
+
688
+ export default BashTool;