@renxqoo/renx-code 0.0.4 → 0.0.6
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 +82 -51
- package/bin/renx.cjs +16 -0
- package/package.json +2 -45
- package/src/agent/runtime/runtime.context-usage.test.ts +4 -5
- package/src/agent/runtime/runtime.error-handling.test.ts +4 -5
- package/src/agent/runtime/runtime.test.ts +7 -4
- package/src/agent/runtime/runtime.ts +3 -9
- package/src/agent/runtime/runtime.usage-forwarding.test.ts +4 -5
- package/src/agent/runtime/source-modules.test.ts +16 -35
- package/src/agent/runtime/source-modules.ts +17 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_ACCEPTANCE_CHECKLIST.md +95 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.html +1345 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.md +1353 -0
- package/vendor/agent-root/src/agent/ERROR_CONTRACT.md +60 -0
- package/vendor/agent-root/src/agent/TEST_COVERAGE_ANALYSIS.md +278 -0
- package/vendor/agent-root/src/agent/__test__/error-contract.test.ts +72 -0
- package/vendor/agent-root/src/agent/__test__/types.test.ts +137 -0
- package/vendor/agent-root/src/agent/agent/__test__/abort-runtime.test.ts +83 -0
- package/vendor/agent-root/src/agent/agent/__test__/callback-safety.test.ts +34 -0
- package/vendor/agent-root/src/agent/agent/__test__/compaction.test.ts +323 -0
- package/vendor/agent-root/src/agent/agent/__test__/concurrency.test.ts +290 -0
- package/vendor/agent-root/src/agent/agent/__test__/error-normalizer.test.ts +377 -0
- package/vendor/agent-root/src/agent/agent/__test__/error.test.ts +212 -0
- package/vendor/agent-root/src/agent/agent/__test__/fault-injection.test.ts +295 -0
- package/vendor/agent-root/src/agent/agent/__test__/index.test.ts +3607 -0
- package/vendor/agent-root/src/agent/agent/__test__/logger.test.ts +35 -0
- package/vendor/agent-root/src/agent/agent/__test__/message-utils.test.ts +517 -0
- package/vendor/agent-root/src/agent/agent/__test__/telemetry.test.ts +97 -0
- package/vendor/agent-root/src/agent/agent/__test__/timeout-budget.test.ts +479 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-call-merge.test.ts +80 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-execution-ledger.test.ts +76 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-buffer.test.ts +173 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-file-session.test.ts +109 -0
- package/vendor/agent-root/src/agent/agent/abort-runtime.ts +71 -0
- package/vendor/agent-root/src/agent/agent/callback-safety.ts +33 -0
- package/vendor/agent-root/src/agent/agent/compaction.ts +291 -0
- package/vendor/agent-root/src/agent/agent/concurrency.ts +103 -0
- package/vendor/agent-root/src/agent/agent/error-normalizer.ts +190 -0
- package/vendor/agent-root/src/agent/agent/error.ts +198 -0
- package/vendor/agent-root/src/agent/agent/index.ts +1772 -0
- package/vendor/agent-root/src/agent/agent/logger.ts +65 -0
- package/vendor/agent-root/src/agent/agent/message-utils.ts +101 -0
- package/vendor/agent-root/src/agent/agent/stream-events.ts +61 -0
- package/vendor/agent-root/src/agent/agent/telemetry.ts +123 -0
- package/vendor/agent-root/src/agent/agent/timeout-budget.ts +227 -0
- package/vendor/agent-root/src/agent/agent/tool-call-merge.ts +111 -0
- package/vendor/agent-root/src/agent/agent/tool-execution-ledger.ts +164 -0
- package/vendor/agent-root/src/agent/agent/write-buffer.ts +188 -0
- package/vendor/agent-root/src/agent/agent/write-file-session.ts +238 -0
- package/vendor/agent-root/src/agent/app/__test__/agent-app-service.test.ts +1053 -0
- package/vendor/agent-root/src/agent/app/__test__/minimal-agent-application.test.ts +158 -0
- package/vendor/agent-root/src/agent/app/__test__/sqlite-agent-app-store.test.ts +437 -0
- package/vendor/agent-root/src/agent/app/agent-app-service.ts +748 -0
- package/vendor/agent-root/src/agent/app/contracts.ts +109 -0
- package/vendor/agent-root/src/agent/app/index.ts +5 -0
- package/vendor/agent-root/src/agent/app/minimal-agent-application.ts +151 -0
- package/vendor/agent-root/src/agent/app/ports.ts +72 -0
- package/vendor/agent-root/src/agent/app/sqlite-agent-app-store.ts +1182 -0
- package/vendor/agent-root/src/agent/app/sqlite-client.ts +177 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/00-README.md +36 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/01-scope-and-goals.md +33 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/02-architecture-overview.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/03-domain-model-and-contracts.md +91 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/04-ports-and-interfaces.md +116 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/05-run-orchestration-and-state-machine.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/06-cli-commands-and-ux.md +53 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/07-storage-design-local.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/08-error-and-observability.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/09-security-and-policy-boundary.md +19 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/10-test-plan-and-acceptance.md +28 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/11-implementation-phases.md +26 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/12-open-questions-and-risks.md +30 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/13-sqlite-schema-fields-and-rationale.md +567 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/14-project-flow-mermaid.md +583 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/15-openclaw-style-project-blueprint.md +972 -0
- package/vendor/agent-root/src/agent/error-contract.ts +154 -0
- package/vendor/agent-root/src/agent/prompts/system.ts +246 -0
- package/vendor/agent-root/src/agent/prompts/system1.ts +208 -0
- package/vendor/agent-root/src/agent/storage/__test__/file-history-store.test.ts +98 -0
- package/vendor/agent-root/src/agent/storage/file-history-store.ts +313 -0
- package/vendor/agent-root/src/agent/storage/file-storage-config.ts +94 -0
- package/vendor/agent-root/src/agent/storage/file-system.ts +31 -0
- package/vendor/agent-root/src/agent/storage/file-write-service.ts +21 -0
- package/vendor/agent-root/src/agent/tool/__test__/base-tool.test.ts +413 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash-policy.test.ts +356 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.mocked-coverage.test.ts +375 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.test.ts +372 -0
- package/vendor/agent-root/src/agent/tool/__test__/error.test.ts +108 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-edit-tool.test.ts +258 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-history-tools.test.ts +121 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-read-tool.test.ts +210 -0
- package/vendor/agent-root/src/agent/tool/__test__/glob.test.ts +139 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.mocked-coverage.test.ts +456 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.test.ts +192 -0
- package/vendor/agent-root/src/agent/tool/__test__/lsp.test.ts +300 -0
- package/vendor/agent-root/src/agent/tool/__test__/outside-workspace-confirmation.test.ts +214 -0
- package/vendor/agent-root/src/agent/tool/__test__/path-security.test.ts +336 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-loader.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-parser.test.ts +543 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-tool.test.ts +172 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-concurrency-and-version.test.ts +116 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create-get-list-update.test.ts +267 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create.test.ts +519 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-errors.test.ts +225 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output-blocking.test.ts +223 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output.test.ts +184 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-parent-abort.test.ts +287 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-real-runner-adapter.test.ts +190 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-run-lifecycle.test.ts +352 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store-runner-branches.test.ts +395 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store.test.ts +391 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config-integration.test.ts +176 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config.test.ts +68 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-core-edges.test.ts +630 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-runtime-edges.test.ts +732 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-types.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-utils-branches.test.ts +175 -0
- package/vendor/agent-root/src/agent/tool/__test__/tool-manager.test.ts +505 -0
- package/vendor/agent-root/src/agent/tool/__test__/types.test.ts +55 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-fetch.test.ts +244 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-search.test.ts +290 -0
- package/vendor/agent-root/src/agent/tool/__test__/write-file.test.ts +368 -0
- package/vendor/agent-root/src/agent/tool/base-tool.ts +345 -0
- package/vendor/agent-root/src/agent/tool/bash-policy.ts +636 -0
- package/vendor/agent-root/src/agent/tool/bash.ts +688 -0
- package/vendor/agent-root/src/agent/tool/error.ts +131 -0
- package/vendor/agent-root/src/agent/tool/file-edit-tool.ts +264 -0
- package/vendor/agent-root/src/agent/tool/file-history-list.ts +103 -0
- package/vendor/agent-root/src/agent/tool/file-history-restore.ts +149 -0
- package/vendor/agent-root/src/agent/tool/file-read-tool.ts +211 -0
- package/vendor/agent-root/src/agent/tool/glob.ts +171 -0
- package/vendor/agent-root/src/agent/tool/grep.ts +496 -0
- package/vendor/agent-root/src/agent/tool/lsp.ts +481 -0
- package/vendor/agent-root/src/agent/tool/path-security.ts +117 -0
- package/vendor/agent-root/src/agent/tool/search/common.ts +153 -0
- package/vendor/agent-root/src/agent/tool/skill/index.ts +13 -0
- package/vendor/agent-root/src/agent/tool/skill/loader.ts +229 -0
- package/vendor/agent-root/src/agent/tool/skill/parser.ts +124 -0
- package/vendor/agent-root/src/agent/tool/skill/types.ts +27 -0
- package/vendor/agent-root/src/agent/tool/skill-tool.ts +143 -0
- package/vendor/agent-root/src/agent/tool/task-create.ts +186 -0
- package/vendor/agent-root/src/agent/tool/task-errors.ts +42 -0
- package/vendor/agent-root/src/agent/tool/task-get.ts +116 -0
- package/vendor/agent-root/src/agent/tool/task-graph.ts +78 -0
- package/vendor/agent-root/src/agent/tool/task-list.ts +141 -0
- package/vendor/agent-root/src/agent/tool/task-mock-runner-adapter.ts +232 -0
- package/vendor/agent-root/src/agent/tool/task-output.ts +223 -0
- package/vendor/agent-root/src/agent/tool/task-parent-abort.ts +115 -0
- package/vendor/agent-root/src/agent/tool/task-real-runner-adapter.ts +336 -0
- package/vendor/agent-root/src/agent/tool/task-runner-adapter.ts +55 -0
- package/vendor/agent-root/src/agent/tool/task-stop.ts +187 -0
- package/vendor/agent-root/src/agent/tool/task-store.ts +217 -0
- package/vendor/agent-root/src/agent/tool/task-subagent-config.ts +149 -0
- package/vendor/agent-root/src/agent/tool/task-types.ts +264 -0
- package/vendor/agent-root/src/agent/tool/task-update.ts +315 -0
- package/vendor/agent-root/src/agent/tool/task.ts +209 -0
- package/vendor/agent-root/src/agent/tool/tool-manager.ts +362 -0
- package/vendor/agent-root/src/agent/tool/tool-prompts.ts +242 -0
- package/vendor/agent-root/src/agent/tool/types.ts +116 -0
- package/vendor/agent-root/src/agent/tool/web-fetch.ts +227 -0
- package/vendor/agent-root/src/agent/tool/web-search.ts +208 -0
- package/vendor/agent-root/src/agent/tool/write-file.ts +497 -0
- package/vendor/agent-root/src/agent/types.ts +232 -0
- package/vendor/agent-root/src/agent/utils/__tests__/index.test.ts +18 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message-utils.test.ts +610 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message.test.ts +223 -0
- package/vendor/agent-root/src/agent/utils/__tests__/token.test.ts +42 -0
- package/vendor/agent-root/src/agent/utils/index.ts +16 -0
- package/vendor/agent-root/src/agent/utils/message.ts +171 -0
- package/vendor/agent-root/src/agent/utils/token.ts +28 -0
- package/vendor/agent-root/src/config/__tests__/load-config-to-env.test.ts +129 -0
- package/vendor/agent-root/src/config/__tests__/loader.test.ts +247 -0
- package/vendor/agent-root/src/config/__tests__/runtime.test.ts +88 -0
- package/vendor/agent-root/src/config/index.ts +54 -0
- package/vendor/agent-root/src/config/loader.ts +431 -0
- package/vendor/agent-root/src/config/paths.ts +30 -0
- package/vendor/agent-root/src/config/runtime.ts +163 -0
- package/vendor/agent-root/src/config/types.ts +70 -0
- package/vendor/agent-root/src/logger/index.ts +57 -0
- package/vendor/agent-root/src/logger/logger.ts +819 -0
- package/vendor/agent-root/src/logger/types.ts +150 -0
- package/vendor/agent-root/src/providers/__tests__/errors.test.ts +441 -0
- package/vendor/agent-root/src/providers/__tests__/index.test.ts +16 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.options.test.ts +318 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.test.ts +600 -0
- package/vendor/agent-root/src/providers/__tests__/registry.test.ts +449 -0
- package/vendor/agent-root/src/providers/__tests__/responses-adapter.test.ts +298 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/anthropic.test.ts +354 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/kimi.test.ts +58 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/standard.test.ts +261 -0
- package/vendor/agent-root/src/providers/adapters/anthropic.ts +572 -0
- package/vendor/agent-root/src/providers/adapters/base.ts +131 -0
- package/vendor/agent-root/src/providers/adapters/kimi.ts +48 -0
- package/vendor/agent-root/src/providers/adapters/responses.ts +732 -0
- package/vendor/agent-root/src/providers/adapters/standard.ts +120 -0
- package/vendor/agent-root/src/providers/http/__tests__/client.timeout.test.ts +313 -0
- package/vendor/agent-root/src/providers/http/client.ts +289 -0
- package/vendor/agent-root/src/providers/http/stream-parser.ts +109 -0
- package/vendor/agent-root/src/providers/index.ts +76 -0
- package/vendor/agent-root/src/providers/kimi-headers.ts +177 -0
- package/vendor/agent-root/src/providers/openai-compatible.ts +387 -0
- package/vendor/agent-root/src/providers/registry/model-config.ts +230 -0
- package/vendor/agent-root/src/providers/registry/provider-factory.ts +123 -0
- package/vendor/agent-root/src/providers/registry.ts +135 -0
- package/vendor/agent-root/src/providers/types/api.ts +284 -0
- package/vendor/agent-root/src/providers/types/config.ts +58 -0
- package/vendor/agent-root/src/providers/types/errors.ts +323 -0
- package/vendor/agent-root/src/providers/types/index.ts +72 -0
- package/vendor/agent-root/src/providers/types/provider.ts +45 -0
- package/vendor/agent-root/src/providers/types/registry.ts +88 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { PassThrough } from 'node:stream';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { BashTool } from '../bash';
|
|
8
|
+
import {
|
|
9
|
+
evaluateBashPolicy,
|
|
10
|
+
extractSegmentCommands,
|
|
11
|
+
getBashAllowedCommands,
|
|
12
|
+
getBashDangerousCommands,
|
|
13
|
+
getBashDangerousPatterns,
|
|
14
|
+
} from '../bash-policy';
|
|
15
|
+
|
|
16
|
+
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
|
17
|
+
|
|
18
|
+
function setPlatform(value: NodeJS.Platform): void {
|
|
19
|
+
Object.defineProperty(process, 'platform', {
|
|
20
|
+
configurable: true,
|
|
21
|
+
value,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function restorePlatform(): void {
|
|
26
|
+
if (originalPlatformDescriptor) {
|
|
27
|
+
Object.defineProperty(process, 'platform', originalPlatformDescriptor);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
vi.restoreAllMocks();
|
|
33
|
+
restorePlatform();
|
|
34
|
+
delete process.env.BASH_TOOL_POLICY;
|
|
35
|
+
delete process.env.BASH_TOOL_SHELL;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function createMockChildProcess(): NodeJS.EventEmitter & {
|
|
39
|
+
stdout: PassThrough;
|
|
40
|
+
stderr: PassThrough;
|
|
41
|
+
killed: boolean;
|
|
42
|
+
pid?: number;
|
|
43
|
+
kill: (signal?: string) => boolean;
|
|
44
|
+
} {
|
|
45
|
+
const child = new EventEmitter() as NodeJS.EventEmitter & {
|
|
46
|
+
stdout: PassThrough;
|
|
47
|
+
stderr: PassThrough;
|
|
48
|
+
killed: boolean;
|
|
49
|
+
pid?: number;
|
|
50
|
+
kill: (signal?: string) => boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
child.stdout = new PassThrough();
|
|
54
|
+
child.stderr = new PassThrough();
|
|
55
|
+
child.killed = false;
|
|
56
|
+
child.pid = 12345;
|
|
57
|
+
child.kill = vi.fn((): boolean => {
|
|
58
|
+
child.killed = true;
|
|
59
|
+
return true;
|
|
60
|
+
}) as unknown as (signal?: string) => boolean;
|
|
61
|
+
|
|
62
|
+
return child;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe('BashTool', () => {
|
|
66
|
+
it('executes a simple command', async () => {
|
|
67
|
+
const tool = new BashTool();
|
|
68
|
+
const result = await tool.execute({ command: 'echo hello' });
|
|
69
|
+
|
|
70
|
+
expect(result.success).toBe(true);
|
|
71
|
+
expect((result.output || '').toLowerCase()).toContain('hello');
|
|
72
|
+
expect((result.metadata as { exitCode: number }).exitCode).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('fails blocked command by policy', async () => {
|
|
76
|
+
const tool = new BashTool();
|
|
77
|
+
const result = await tool.execute({ command: 'rm -rf /' });
|
|
78
|
+
|
|
79
|
+
expect(result.success).toBe(false);
|
|
80
|
+
expect(result.output).toContain('COMMAND_BLOCKED_BY_POLICY');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('supports background execution', async () => {
|
|
84
|
+
const tool = new BashTool();
|
|
85
|
+
const result = await tool.execute({
|
|
86
|
+
command: 'echo background-test',
|
|
87
|
+
run_in_background: true,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(result.success).toBe(true);
|
|
91
|
+
expect(result.output).toContain('BACKGROUND_STARTED');
|
|
92
|
+
|
|
93
|
+
const metadata = result.metadata as { logPath: string; run_in_background: boolean };
|
|
94
|
+
expect(metadata.run_in_background).toBe(true);
|
|
95
|
+
expect(metadata.logPath).toContain('agent-bash-bg-');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('requires confirmation for eval command', () => {
|
|
99
|
+
const tool = new BashTool();
|
|
100
|
+
expect(tool.shouldConfirm({ command: 'eval "ls"' })).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('parses run_in_background boolean strings via schema preprocess', () => {
|
|
104
|
+
const tool = new BashTool();
|
|
105
|
+
const parsedTrue = tool.parameters.safeParse({
|
|
106
|
+
command: 'echo test',
|
|
107
|
+
run_in_background: 'true',
|
|
108
|
+
});
|
|
109
|
+
const parsedFalse = tool.parameters.safeParse({
|
|
110
|
+
command: 'echo test',
|
|
111
|
+
run_in_background: 'false',
|
|
112
|
+
});
|
|
113
|
+
const parsedRaw = tool.parameters.safeParse({
|
|
114
|
+
command: 'echo test',
|
|
115
|
+
run_in_background: 'foo',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(parsedTrue.success && parsedTrue.data.run_in_background).toBe(true);
|
|
119
|
+
expect(parsedFalse.success && parsedFalse.data.run_in_background).toBe(false);
|
|
120
|
+
expect(parsedRaw.success).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns timeout envelope when command exceeds timeout', async () => {
|
|
124
|
+
const tool = new BashTool();
|
|
125
|
+
const helper = tool as unknown as {
|
|
126
|
+
executeForeground: (
|
|
127
|
+
command: string,
|
|
128
|
+
timeoutMs: number
|
|
129
|
+
) => Promise<{ exitCode: number; output: string; timedOut: boolean }>;
|
|
130
|
+
};
|
|
131
|
+
vi.spyOn(helper, 'executeForeground').mockResolvedValueOnce({
|
|
132
|
+
exitCode: 124,
|
|
133
|
+
output: '',
|
|
134
|
+
timedOut: true,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = await tool.execute({
|
|
138
|
+
command: 'echo timeout-test',
|
|
139
|
+
timeout: 1,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result.success).toBe(false);
|
|
143
|
+
expect(result.output).toContain('COMMAND_TIMEOUT');
|
|
144
|
+
expect((result.metadata as { error: string }).error).toBe('COMMAND_TIMEOUT');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('builds fallback failure message when command has no output', async () => {
|
|
148
|
+
const tool = new BashTool();
|
|
149
|
+
const result = await tool.execute({
|
|
150
|
+
command: 'false',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(result.success).toBe(false);
|
|
154
|
+
expect(result.output).toContain('Command failed with exit code');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('maps unexpected runtime errors to EXECUTION_FAILED', async () => {
|
|
158
|
+
const tool = new BashTool({
|
|
159
|
+
backgroundLogDir: '\u0000bad',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = await tool.execute({
|
|
163
|
+
command: 'echo test',
|
|
164
|
+
run_in_background: true,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(result.success).toBe(false);
|
|
168
|
+
expect(result.output).toContain('EXECUTION_FAILED');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('streams foreground stdout/stderr chunks through onChunk callback', async () => {
|
|
172
|
+
const tool = new BashTool();
|
|
173
|
+
const chunks: string[] = [];
|
|
174
|
+
|
|
175
|
+
const result = await tool.execute(
|
|
176
|
+
{
|
|
177
|
+
command: 'echo stdout-line && echo stderr-line 1>&2',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
toolCallId: 't1',
|
|
181
|
+
loopIndex: 1,
|
|
182
|
+
agent: {},
|
|
183
|
+
onChunk: async (chunk) => {
|
|
184
|
+
if (typeof chunk.content === 'string') {
|
|
185
|
+
chunks.push(`${chunk.type}:${chunk.content.trim()}`);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(result.success).toBe(true);
|
|
192
|
+
expect(chunks.some((chunk) => chunk.startsWith('stdout:stdout-line'))).toBe(true);
|
|
193
|
+
expect(chunks.some((chunk) => chunk.startsWith('stderr:stderr-line'))).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('streams background info chunk through onChunk callback', async () => {
|
|
197
|
+
const tool = new BashTool();
|
|
198
|
+
const infos: string[] = [];
|
|
199
|
+
|
|
200
|
+
const result = await tool.execute(
|
|
201
|
+
{
|
|
202
|
+
command: 'echo bg',
|
|
203
|
+
run_in_background: true,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
toolCallId: 't2',
|
|
207
|
+
loopIndex: 1,
|
|
208
|
+
agent: {},
|
|
209
|
+
onChunk: async (chunk) => {
|
|
210
|
+
if (chunk.type === 'info' && typeof chunk.content === 'string') {
|
|
211
|
+
infos.push(chunk.content);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(result.success).toBe(true);
|
|
218
|
+
expect(infos.some((line) => line.includes('BACKGROUND_STARTED'))).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('respects permissive policy mode from env helper', () => {
|
|
222
|
+
process.env.BASH_TOOL_POLICY = 'permissive';
|
|
223
|
+
const tool = new BashTool();
|
|
224
|
+
const mode = (
|
|
225
|
+
tool as unknown as { resolvePolicyModeFromEnv: () => string }
|
|
226
|
+
).resolvePolicyModeFromEnv();
|
|
227
|
+
expect(mode).toBe('permissive');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('returns ask confirm for unknown guarded command', () => {
|
|
231
|
+
const tool = new BashTool({ policyMode: 'guarded' });
|
|
232
|
+
expect(tool.shouldConfirm({ command: 'unknown_command_xyz' })).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('sanitizes ANSI and normalizes empty output helper', () => {
|
|
236
|
+
const tool = new BashTool();
|
|
237
|
+
const helper = tool as unknown as {
|
|
238
|
+
sanitizeOutput: (output: string) => string;
|
|
239
|
+
truncateOutput: (output: string) => { output: string; truncated: boolean };
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const sanitized = helper.sanitizeOutput('\u001b[31mred\u001b[0m\r\n\r\n\r\n');
|
|
243
|
+
const sanitizedEmpty = helper.sanitizeOutput('');
|
|
244
|
+
const long = helper.truncateOutput('x'.repeat(50000));
|
|
245
|
+
|
|
246
|
+
expect(sanitized).toBe('red');
|
|
247
|
+
expect(sanitizedEmpty).toBe('');
|
|
248
|
+
expect(long.truncated).toBe(true);
|
|
249
|
+
expect(long.output).toContain('[... Output Truncated for Brevity ...]');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('sanitizes streamed chunks with split ANSI control sequences', () => {
|
|
253
|
+
const tool = new BashTool();
|
|
254
|
+
const helper = tool as unknown as {
|
|
255
|
+
createStreamChunkSanitizerState: () => {
|
|
256
|
+
mode: string;
|
|
257
|
+
awaitingStringTerminator: boolean;
|
|
258
|
+
};
|
|
259
|
+
sanitizeStreamChunk: (
|
|
260
|
+
output: string,
|
|
261
|
+
state: { mode: string; awaitingStringTerminator: boolean }
|
|
262
|
+
) => string;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const state = helper.createStreamChunkSanitizerState();
|
|
266
|
+
const first = helper.sanitizeStreamChunk('\u001b[38;', state);
|
|
267
|
+
const second = helper.sanitizeStreamChunk('5;240mskills\u001b[0m\rline', state);
|
|
268
|
+
const third = helper.sanitizeStreamChunk('\u001b[999', state);
|
|
269
|
+
const fourth = helper.sanitizeStreamChunk('D Cloning repository', state);
|
|
270
|
+
const fifth = helper.sanitizeStreamChunk('\u001b]0;title\u0007done', state);
|
|
271
|
+
|
|
272
|
+
expect(first).toBe('');
|
|
273
|
+
expect(second).toBe('skills\nline');
|
|
274
|
+
expect(third).toBe('');
|
|
275
|
+
expect(fourth).toBe(' Cloning repository');
|
|
276
|
+
expect(fifth).toBe('done');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('resolves shell for linux and windows branches', () => {
|
|
280
|
+
const tool = new BashTool();
|
|
281
|
+
const toolAny = tool as unknown as {
|
|
282
|
+
resolveShell: (command: string) => { shellPath: string; shellArgs: string[] };
|
|
283
|
+
findGitBashPath: () => string | null;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
setPlatform('linux');
|
|
287
|
+
const linuxShell = toolAny.resolveShell('echo a');
|
|
288
|
+
expect(linuxShell.shellPath).toBe('/bin/bash');
|
|
289
|
+
|
|
290
|
+
setPlatform('win32');
|
|
291
|
+
vi.spyOn(toolAny, 'findGitBashPath').mockReturnValue(null);
|
|
292
|
+
const windowsShell = toolAny.resolveShell('echo a');
|
|
293
|
+
expect(windowsShell.shellPath).toBe(process.env.COMSPEC || 'cmd.exe');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('covers findGitBashPath fallbacks and catch branch', async () => {
|
|
297
|
+
const tmp = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'bash-tool-test-'));
|
|
298
|
+
const fakeGitBash = path.join(tmp, 'bash.exe');
|
|
299
|
+
await fs.promises.writeFile(fakeGitBash, '');
|
|
300
|
+
|
|
301
|
+
const tool = new BashTool();
|
|
302
|
+
const toolAny = tool as unknown as { findGitBashPath: () => string | null };
|
|
303
|
+
|
|
304
|
+
setPlatform('win32');
|
|
305
|
+
process.env.BASH_TOOL_SHELL = fakeGitBash;
|
|
306
|
+
expect(toolAny.findGitBashPath()).toBe(fakeGitBash);
|
|
307
|
+
|
|
308
|
+
delete process.env.BASH_TOOL_SHELL;
|
|
309
|
+
const maybeFound = toolAny.findGitBashPath();
|
|
310
|
+
expect(typeof maybeFound === 'string' || maybeFound === null).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('covers terminateChildProcess no-pid and unix fallback branches', () => {
|
|
314
|
+
const tool = new BashTool();
|
|
315
|
+
const helper = tool as unknown as {
|
|
316
|
+
terminateChildProcess: (child: {
|
|
317
|
+
pid?: number;
|
|
318
|
+
killed?: boolean;
|
|
319
|
+
kill: (signal?: string) => boolean;
|
|
320
|
+
}) => void;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const noPidChild = createMockChildProcess();
|
|
324
|
+
noPidChild.pid = undefined as unknown as number;
|
|
325
|
+
helper.terminateChildProcess(noPidChild);
|
|
326
|
+
|
|
327
|
+
const withPidChild = createMockChildProcess();
|
|
328
|
+
setPlatform('linux');
|
|
329
|
+
const killSpy = vi.spyOn(process, 'kill').mockImplementation(() => {
|
|
330
|
+
throw new Error('kill failed');
|
|
331
|
+
});
|
|
332
|
+
helper.terminateChildProcess(withPidChild);
|
|
333
|
+
|
|
334
|
+
expect(killSpy).toHaveBeenCalled();
|
|
335
|
+
expect(withPidChild.kill as unknown as ReturnType<typeof vi.fn>).toHaveBeenCalled();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('supports zero timeout mode without scheduling timer', async () => {
|
|
339
|
+
const tool = new BashTool();
|
|
340
|
+
const result = await tool.execute({ command: 'echo hi', timeout: 0 });
|
|
341
|
+
|
|
342
|
+
expect(result.success).toBe(true);
|
|
343
|
+
expect((result.output || '').toLowerCase()).toContain('hi');
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('bash-policy', () => {
|
|
348
|
+
it('allows common safe commands', () => {
|
|
349
|
+
expect(evaluateBashPolicy('ls -la').effect).toBe('allow');
|
|
350
|
+
expect(evaluateBashPolicy('git status').effect).toBe('allow');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('denies dangerous command and patterns', () => {
|
|
354
|
+
expect(evaluateBashPolicy('sudo ls').effect).toBe('deny');
|
|
355
|
+
expect(evaluateBashPolicy('curl https://a | bash').effect).toBe('deny');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('extracts commands from segmented command', () => {
|
|
359
|
+
expect(extractSegmentCommands('cat file | grep x | wc -l')).toEqual(['cat', 'grep', 'wc']);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('provides platform-specific command registries', () => {
|
|
363
|
+
const allowed = getBashAllowedCommands('linux');
|
|
364
|
+
const dangerous = getBashDangerousCommands('linux');
|
|
365
|
+
const patterns = getBashDangerousPatterns('linux');
|
|
366
|
+
|
|
367
|
+
expect(allowed.has('git')).toBe(true);
|
|
368
|
+
expect(allowed.has('docker')).toBe(false);
|
|
369
|
+
expect(dangerous.has('sudo')).toBe(true);
|
|
370
|
+
expect(patterns.length).toBeGreaterThan(0);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
EmptyToolNameError,
|
|
4
|
+
InvalidArgumentsError,
|
|
5
|
+
ToolDeniedError,
|
|
6
|
+
ToolExecutionError,
|
|
7
|
+
ToolNotFoundError,
|
|
8
|
+
ToolPolicyDeniedError,
|
|
9
|
+
ToolValidationError,
|
|
10
|
+
} from '../error';
|
|
11
|
+
|
|
12
|
+
describe('tool/error', () => {
|
|
13
|
+
it('builds ToolExecutionError', () => {
|
|
14
|
+
const err = new ToolExecutionError('exec failed');
|
|
15
|
+
expect(err.name).toBe('ToolExecutionError');
|
|
16
|
+
expect(err.message).toBe('exec failed');
|
|
17
|
+
expect(err.code).toBe(2000);
|
|
18
|
+
expect(err.errorCode).toBe('TOOL_EXECUTION_ERROR');
|
|
19
|
+
expect(err.category).toBe('internal');
|
|
20
|
+
expect(err.retryable).toBe(true);
|
|
21
|
+
expect(err.httpStatus).toBe(500);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('builds EmptyToolNameError', () => {
|
|
25
|
+
const err = new EmptyToolNameError();
|
|
26
|
+
expect(err.name).toBe('EmptyToolNameError');
|
|
27
|
+
expect(err.message).toBe('Tool name is empty');
|
|
28
|
+
expect(err.code).toBe(2001);
|
|
29
|
+
expect(err.errorCode).toBe('TOOL_NAME_EMPTY');
|
|
30
|
+
expect(err.category).toBe('validation');
|
|
31
|
+
expect(err.retryable).toBe(false);
|
|
32
|
+
expect(err.httpStatus).toBe(400);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('builds InvalidArgumentsError', () => {
|
|
36
|
+
const err = new InvalidArgumentsError('bash', 'bad json');
|
|
37
|
+
expect(err.name).toBe('InvalidArgumentsError');
|
|
38
|
+
expect(err.code).toBe(2002);
|
|
39
|
+
expect(err.errorCode).toBe('TOOL_INVALID_ARGUMENTS');
|
|
40
|
+
expect(err.category).toBe('validation');
|
|
41
|
+
expect(err.retryable).toBe(false);
|
|
42
|
+
expect(err.httpStatus).toBe(400);
|
|
43
|
+
expect(err.toolName).toBe('bash');
|
|
44
|
+
expect(err.message).toContain('Invalid arguments format for tool bash');
|
|
45
|
+
expect(err.message).toContain('bad json');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('builds ToolNotFoundError', () => {
|
|
49
|
+
const err = new ToolNotFoundError('missing_tool');
|
|
50
|
+
expect(err.name).toBe('ToolNotFoundError');
|
|
51
|
+
expect(err.code).toBe(2003);
|
|
52
|
+
expect(err.errorCode).toBe('TOOL_NOT_FOUND');
|
|
53
|
+
expect(err.category).toBe('not_found');
|
|
54
|
+
expect(err.retryable).toBe(false);
|
|
55
|
+
expect(err.httpStatus).toBe(404);
|
|
56
|
+
expect(err.toolName).toBe('missing_tool');
|
|
57
|
+
expect(err.message).toBe('Tool missing_tool not found');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('builds ToolValidationError', () => {
|
|
61
|
+
const issues = [{ message: 'x is required' }, { message: 'y must be int' }];
|
|
62
|
+
const err = new ToolValidationError('demo_tool', issues);
|
|
63
|
+
expect(err.name).toBe('ToolValidationError');
|
|
64
|
+
expect(err.code).toBe(2004);
|
|
65
|
+
expect(err.errorCode).toBe('TOOL_VALIDATION_FAILED');
|
|
66
|
+
expect(err.category).toBe('validation');
|
|
67
|
+
expect(err.retryable).toBe(false);
|
|
68
|
+
expect(err.httpStatus).toBe(400);
|
|
69
|
+
expect(err.toolName).toBe('demo_tool');
|
|
70
|
+
expect(err.issues).toEqual(issues);
|
|
71
|
+
expect(err.message).toBe('x is required, y must be int');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('builds ToolDeniedError with default reason and custom reason', () => {
|
|
75
|
+
const defaultErr = new ToolDeniedError('danger');
|
|
76
|
+
const customErr = new ToolDeniedError('danger', 'policy denied');
|
|
77
|
+
|
|
78
|
+
expect(defaultErr.name).toBe('ToolDeniedError');
|
|
79
|
+
expect(defaultErr.code).toBe(2005);
|
|
80
|
+
expect(defaultErr.errorCode).toBe('TOOL_DENIED');
|
|
81
|
+
expect(defaultErr.category).toBe('permission');
|
|
82
|
+
expect(defaultErr.retryable).toBe(false);
|
|
83
|
+
expect(defaultErr.httpStatus).toBe(403);
|
|
84
|
+
expect(defaultErr.toolName).toBe('danger');
|
|
85
|
+
expect(defaultErr.reason).toBeUndefined();
|
|
86
|
+
expect(defaultErr.message).toBe('Tool danger denied: User rejected');
|
|
87
|
+
|
|
88
|
+
expect(customErr.reason).toBe('policy denied');
|
|
89
|
+
expect(customErr.message).toBe('Tool danger denied: policy denied');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('builds ToolPolicyDeniedError with standard reason code envelope', () => {
|
|
93
|
+
const err = new ToolPolicyDeniedError('write_file', 'PATH_NOT_ALLOWED', 'outside workspace');
|
|
94
|
+
|
|
95
|
+
expect(err.name).toBe('ToolPolicyDeniedError');
|
|
96
|
+
expect(err.code).toBe(2006);
|
|
97
|
+
expect(err.errorCode).toBe('TOOL_POLICY_DENIED');
|
|
98
|
+
expect(err.category).toBe('permission');
|
|
99
|
+
expect(err.retryable).toBe(false);
|
|
100
|
+
expect(err.httpStatus).toBe(403);
|
|
101
|
+
expect(err.toolName).toBe('write_file');
|
|
102
|
+
expect(err.reasonCode).toBe('PATH_NOT_ALLOWED');
|
|
103
|
+
expect(err.reason).toBe('outside workspace');
|
|
104
|
+
expect(err.message).toBe(
|
|
105
|
+
'Tool write_file blocked by policy [PATH_NOT_ALLOWED]: outside workspace'
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
});
|