joonecli 0.1.1 → 0.2.1
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/dist/__tests__/config.test.js +1 -0
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/installHostDeps.test.js +45 -0
- package/dist/__tests__/installHostDeps.test.js.map +1 -0
- package/dist/__tests__/whitelistedBackend.test.js +18 -0
- package/dist/__tests__/whitelistedBackend.test.js.map +1 -0
- package/dist/cli/config.d.ts +2 -0
- package/dist/cli/config.js +1 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/index.js +84 -97
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/builtinCommands.js +6 -6
- package/dist/commands/builtinCommands.js.map +1 -1
- package/dist/commands/commandRegistry.d.ts +3 -1
- package/dist/commands/commandRegistry.js.map +1 -1
- package/dist/core/agentLoop.d.ts +11 -28
- package/dist/core/agentLoop.js +68 -229
- package/dist/core/agentLoop.js.map +1 -1
- package/dist/core/compactor.js +2 -2
- package/dist/core/compactor.js.map +1 -1
- package/dist/core/contextGuard.d.ts +5 -0
- package/dist/core/contextGuard.js +30 -3
- package/dist/core/contextGuard.js.map +1 -1
- package/dist/core/events.d.ts +45 -0
- package/dist/core/events.js +8 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/promptBuilder.js.map +1 -1
- package/dist/core/sessionStore.js +3 -2
- package/dist/core/sessionStore.js.map +1 -1
- package/dist/core/tokenCounter.d.ts +8 -1
- package/dist/core/tokenCounter.js +28 -0
- package/dist/core/tokenCounter.js.map +1 -1
- package/dist/hitl/bridge.js +1 -27
- package/dist/hitl/bridge.js.map +1 -1
- package/dist/middleware/loopDetection.d.ts +7 -23
- package/dist/middleware/loopDetection.js +38 -42
- package/dist/middleware/loopDetection.js.map +1 -1
- package/dist/sandbox/whitelistedBackend.d.ts +5 -0
- package/dist/sandbox/whitelistedBackend.js +27 -0
- package/dist/sandbox/whitelistedBackend.js.map +1 -0
- package/dist/tools/askUser.d.ts +12 -3
- package/dist/tools/askUser.js +16 -28
- package/dist/tools/askUser.js.map +1 -1
- package/dist/tools/bashTool.d.ts +11 -0
- package/dist/tools/bashTool.js +51 -0
- package/dist/tools/bashTool.js.map +1 -0
- package/dist/tools/index.d.ts +15 -27
- package/dist/tools/index.js +9 -181
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/installHostDeps.d.ts +8 -0
- package/dist/tools/installHostDeps.js +44 -0
- package/dist/tools/installHostDeps.js.map +1 -0
- package/dist/tracing/sessionTracer.d.ts +1 -0
- package/dist/tracing/sessionTracer.js +4 -1
- package/dist/tracing/sessionTracer.js.map +1 -1
- package/dist/ui/App.js +116 -55
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActionLog.d.ts +7 -0
- package/dist/ui/components/ActionLog.js +63 -0
- package/dist/ui/components/ActionLog.js.map +1 -0
- package/dist/ui/components/FileBrowser.d.ts +2 -0
- package/dist/ui/components/FileBrowser.js +41 -0
- package/dist/ui/components/FileBrowser.js.map +1 -0
- package/dist/ui/components/MessageBubble.js +1 -1
- package/dist/ui/components/MessageBubble.js.map +1 -1
- package/package.json +8 -5
- package/AGENTS.md +0 -56
- package/Handover.md +0 -115
- package/PROGRESS.md +0 -160
- package/dist/__tests__/m55.test.js +0 -160
- package/dist/__tests__/m55.test.js.map +0 -1
- package/dist/__tests__/middleware.test.js +0 -169
- package/dist/__tests__/middleware.test.js.map +0 -1
- package/dist/__tests__/optimizations.test.d.ts +0 -1
- package/dist/__tests__/optimizations.test.js +0 -136
- package/dist/__tests__/optimizations.test.js.map +0 -1
- package/dist/__tests__/security.test.d.ts +0 -1
- package/dist/__tests__/security.test.js +0 -86
- package/dist/__tests__/security.test.js.map +0 -1
- package/dist/__tests__/streaming.test.d.ts +0 -1
- package/dist/__tests__/streaming.test.js +0 -71
- package/dist/__tests__/streaming.test.js.map +0 -1
- package/dist/__tests__/toolRouter.test.d.ts +0 -1
- package/dist/__tests__/toolRouter.test.js +0 -37
- package/dist/__tests__/toolRouter.test.js.map +0 -1
- package/dist/__tests__/tools.test.d.ts +0 -1
- package/dist/__tests__/tools.test.js +0 -112
- package/dist/__tests__/tools.test.js.map +0 -1
- package/dist/core/subAgent.d.ts +0 -56
- package/dist/core/subAgent.js +0 -240
- package/dist/core/subAgent.js.map +0 -1
- package/dist/debug_google.d.ts +0 -1
- package/dist/debug_google.js +0 -23
- package/dist/debug_google.js.map +0 -1
- package/dist/middleware/commandSanitizer.d.ts +0 -18
- package/dist/middleware/commandSanitizer.js +0 -50
- package/dist/middleware/commandSanitizer.js.map +0 -1
- package/dist/middleware/permission.d.ts +0 -17
- package/dist/middleware/permission.js +0 -59
- package/dist/middleware/permission.js.map +0 -1
- package/dist/middleware/pipeline.d.ts +0 -31
- package/dist/middleware/pipeline.js +0 -62
- package/dist/middleware/pipeline.js.map +0 -1
- package/dist/middleware/preCompletion.d.ts +0 -29
- package/dist/middleware/preCompletion.js +0 -82
- package/dist/middleware/preCompletion.js.map +0 -1
- package/dist/middleware/types.d.ts +0 -40
- package/dist/middleware/types.js +0 -8
- package/dist/middleware/types.js.map +0 -1
- package/dist/skills/loader.d.ts +0 -55
- package/dist/skills/loader.js +0 -132
- package/dist/skills/loader.js.map +0 -1
- package/dist/skills/tools.d.ts +0 -5
- package/dist/skills/tools.js +0 -78
- package/dist/skills/tools.js.map +0 -1
- package/dist/test_cache.d.ts +0 -1
- package/dist/test_cache.js +0 -55
- package/dist/test_cache.js.map +0 -1
- package/dist/test_google.d.ts +0 -1
- package/dist/test_google.js +0 -36
- package/dist/test_google.js.map +0 -1
- package/dist/tools/browser.d.ts +0 -19
- package/dist/tools/browser.js +0 -111
- package/dist/tools/browser.js.map +0 -1
- package/dist/tools/registry.d.ts +0 -31
- package/dist/tools/registry.js +0 -168
- package/dist/tools/registry.js.map +0 -1
- package/dist/tools/router.d.ts +0 -34
- package/dist/tools/router.js +0 -75
- package/dist/tools/router.js.map +0 -1
- package/dist/tools/security.d.ts +0 -28
- package/dist/tools/security.js +0 -183
- package/dist/tools/security.js.map +0 -1
- package/dist/tools/spawnAgent.d.ts +0 -19
- package/dist/tools/spawnAgent.js +0 -130
- package/dist/tools/spawnAgent.js.map +0 -1
- package/dist/tools/webSearch.d.ts +0 -6
- package/dist/tools/webSearch.js +0 -120
- package/dist/tools/webSearch.js.map +0 -1
- package/docs/01_insights_and_patterns.md +0 -27
- package/docs/02_edge_cases_and_mitigations.md +0 -143
- package/docs/03_initial_implementation_plan.md +0 -66
- package/docs/04_tech_stack_proposal.md +0 -20
- package/docs/05_prd.md +0 -87
- package/docs/06_user_stories.md +0 -72
- package/docs/07_system_architecture.md +0 -138
- package/docs/08_roadmap.md +0 -200
- package/e2b/Dockerfile +0 -26
- package/src/__tests__/bootstrap.test.ts +0 -111
- package/src/__tests__/config.test.ts +0 -97
- package/src/__tests__/m55.test.ts +0 -238
- package/src/__tests__/middleware.test.ts +0 -219
- package/src/__tests__/modelFactory.test.ts +0 -63
- package/src/__tests__/optimizations.test.ts +0 -201
- package/src/__tests__/promptBuilder.test.ts +0 -141
- package/src/__tests__/sandbox.test.ts +0 -102
- package/src/__tests__/security.test.ts +0 -122
- package/src/__tests__/streaming.test.ts +0 -82
- package/src/__tests__/toolRouter.test.ts +0 -52
- package/src/__tests__/tools.test.ts +0 -146
- package/src/__tests__/tracing.test.ts +0 -196
- package/src/agents/agentRegistry.ts +0 -69
- package/src/agents/agentSpec.ts +0 -67
- package/src/agents/builtinAgents.ts +0 -142
- package/src/cli/config.ts +0 -124
- package/src/cli/index.ts +0 -742
- package/src/cli/modelFactory.ts +0 -174
- package/src/cli/postinstall.ts +0 -28
- package/src/cli/providers.ts +0 -107
- package/src/commands/builtinCommands.ts +0 -293
- package/src/commands/commandRegistry.ts +0 -194
- package/src/core/agentLoop.d.ts.map +0 -1
- package/src/core/agentLoop.ts +0 -312
- package/src/core/autoSave.ts +0 -95
- package/src/core/compactor.ts +0 -252
- package/src/core/contextGuard.ts +0 -129
- package/src/core/errors.ts +0 -202
- package/src/core/promptBuilder.d.ts.map +0 -1
- package/src/core/promptBuilder.ts +0 -139
- package/src/core/reasoningRouter.ts +0 -121
- package/src/core/retry.ts +0 -75
- package/src/core/sessionResumer.ts +0 -90
- package/src/core/sessionStore.ts +0 -216
- package/src/core/subAgent.ts +0 -339
- package/src/core/tokenCounter.ts +0 -64
- package/src/evals/dataset.ts +0 -67
- package/src/evals/evaluator.ts +0 -81
- package/src/hitl/bridge.ts +0 -160
- package/src/middleware/commandSanitizer.ts +0 -60
- package/src/middleware/loopDetection.ts +0 -63
- package/src/middleware/permission.ts +0 -72
- package/src/middleware/pipeline.ts +0 -75
- package/src/middleware/preCompletion.ts +0 -94
- package/src/middleware/types.ts +0 -45
- package/src/sandbox/bootstrap.ts +0 -121
- package/src/sandbox/manager.ts +0 -239
- package/src/sandbox/sync.ts +0 -157
- package/src/skills/loader.ts +0 -143
- package/src/skills/tools.ts +0 -99
- package/src/skills/types.ts +0 -13
- package/src/test_cache.ts +0 -72
- package/src/tools/askUser.ts +0 -47
- package/src/tools/browser.ts +0 -137
- package/src/tools/index.d.ts.map +0 -1
- package/src/tools/index.ts +0 -237
- package/src/tools/registry.ts +0 -198
- package/src/tools/router.ts +0 -78
- package/src/tools/security.ts +0 -220
- package/src/tools/spawnAgent.ts +0 -158
- package/src/tools/webSearch.ts +0 -142
- package/src/tracing/analyzer.ts +0 -265
- package/src/tracing/langsmith.ts +0 -63
- package/src/tracing/sessionTracer.ts +0 -202
- package/src/tracing/types.ts +0 -49
- package/src/types/valyu.d.ts +0 -37
- package/src/ui/App.tsx +0 -404
- package/src/ui/components/HITLPrompt.tsx +0 -119
- package/src/ui/components/Header.tsx +0 -51
- package/src/ui/components/MessageBubble.tsx +0 -46
- package/src/ui/components/StatusBar.tsx +0 -138
- package/src/ui/components/StreamingText.tsx +0 -48
- package/src/ui/components/ToolCallPanel.tsx +0 -80
- package/tests/commands/commands.test.ts +0 -356
- package/tests/core/compactor.test.ts +0 -217
- package/tests/core/retryAndErrors.test.ts +0 -164
- package/tests/core/sessionResumer.test.ts +0 -95
- package/tests/core/sessionStore.test.ts +0 -84
- package/tests/core/stability.test.ts +0 -165
- package/tests/core/subAgent.test.ts +0 -238
- package/tests/hitl/hitlBridge.test.ts +0 -115
- package/tsconfig.json +0 -16
- package/vitest.config.ts +0 -10
- package/vitest.out +0 -48
- /package/dist/__tests__/{m55.test.d.ts → installHostDeps.test.d.ts} +0 -0
- /package/dist/__tests__/{middleware.test.d.ts → whitelistedBackend.test.d.ts} +0 -0
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { SandboxManager } from "../sandbox/manager.js";
|
|
3
|
-
|
|
4
|
-
// Mock the e2b SDK since we don't want real sandbox creation in tests
|
|
5
|
-
vi.mock("e2b", () => {
|
|
6
|
-
const mockSandbox = {
|
|
7
|
-
sandboxId: "test-sandbox-123",
|
|
8
|
-
commands: {
|
|
9
|
-
run: vi.fn().mockResolvedValue({
|
|
10
|
-
stdout: "mock output",
|
|
11
|
-
stderr: "",
|
|
12
|
-
exitCode: 0,
|
|
13
|
-
}),
|
|
14
|
-
},
|
|
15
|
-
files: {
|
|
16
|
-
write: vi.fn().mockResolvedValue(undefined),
|
|
17
|
-
read: vi.fn().mockResolvedValue("file content"),
|
|
18
|
-
list: vi.fn().mockResolvedValue([]),
|
|
19
|
-
},
|
|
20
|
-
kill: vi.fn().mockResolvedValue(undefined),
|
|
21
|
-
isRunning: vi.fn().mockResolvedValue(true),
|
|
22
|
-
setTimeout: vi.fn().mockResolvedValue(undefined),
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
Sandbox: {
|
|
27
|
-
create: vi.fn().mockResolvedValue(mockSandbox),
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe("SandboxManager", () => {
|
|
33
|
-
let manager: SandboxManager;
|
|
34
|
-
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
vi.clearAllMocks();
|
|
37
|
-
manager = new SandboxManager({ apiKey: "test-e2b-key" });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
afterEach(async () => {
|
|
41
|
-
// Ensure sandbox is cleaned up after each test
|
|
42
|
-
try {
|
|
43
|
-
await manager.destroy();
|
|
44
|
-
} catch {
|
|
45
|
-
// Already destroyed or never created
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// ─── Test #15: SandboxManager.create() initializes a sandbox ───
|
|
50
|
-
|
|
51
|
-
it("creates a sandbox and returns the sandbox ID", async () => {
|
|
52
|
-
const sandboxId = await manager.create();
|
|
53
|
-
|
|
54
|
-
expect(sandboxId).toBe("test-sandbox-123");
|
|
55
|
-
expect(manager.isActive()).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ─── Test #16: SandboxManager.destroy() cleans up the sandbox ───
|
|
59
|
-
|
|
60
|
-
it("destroys the sandbox and marks it as inactive", async () => {
|
|
61
|
-
await manager.create();
|
|
62
|
-
expect(manager.isActive()).toBe(true);
|
|
63
|
-
|
|
64
|
-
await manager.destroy();
|
|
65
|
-
expect(manager.isActive()).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// ─── Test #17: SandboxManager.exec() runs a command in the sandbox ───
|
|
69
|
-
|
|
70
|
-
it("executes a command in the sandbox and returns output", async () => {
|
|
71
|
-
await manager.create();
|
|
72
|
-
|
|
73
|
-
const result = await manager.exec("echo hello");
|
|
74
|
-
|
|
75
|
-
expect(result.stdout).toBe("mock output");
|
|
76
|
-
expect(result.exitCode).toBe(0);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// ─── Test #18: SandboxManager.exec() throws if sandbox not active ───
|
|
80
|
-
|
|
81
|
-
it("throws an error if exec is called before create", async () => {
|
|
82
|
-
await expect(manager.exec("echo hello")).rejects.toThrow(
|
|
83
|
-
/sandbox is not active/i
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// ─── Test #19: SandboxManager.uploadFile() writes a file to the sandbox ───
|
|
88
|
-
|
|
89
|
-
it("uploads a file to the sandbox filesystem", async () => {
|
|
90
|
-
await manager.create();
|
|
91
|
-
|
|
92
|
-
await manager.uploadFile("/workspace/src/foo.ts", "const x = 1;");
|
|
93
|
-
|
|
94
|
-
// Verify the E2B files.write was called
|
|
95
|
-
const { Sandbox } = await import("e2b");
|
|
96
|
-
const mockSandbox = await Sandbox.create();
|
|
97
|
-
expect(mockSandbox.files.write).toHaveBeenCalledWith(
|
|
98
|
-
"/workspace/src/foo.ts",
|
|
99
|
-
"const x = 1;"
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
SecurityScanTool,
|
|
4
|
-
DepScanTool,
|
|
5
|
-
bindSecuritySandbox,
|
|
6
|
-
} from "../tools/security.js";
|
|
7
|
-
import { SandboxManager } from "../sandbox/manager.js";
|
|
8
|
-
import { LazyInstaller } from "../sandbox/bootstrap.js";
|
|
9
|
-
|
|
10
|
-
// Helpers
|
|
11
|
-
const createMockSandbox = (active = true) => ({
|
|
12
|
-
exec: vi.fn(),
|
|
13
|
-
isActive: vi.fn().mockReturnValue(active),
|
|
14
|
-
create: vi.fn(),
|
|
15
|
-
destroy: vi.fn(),
|
|
16
|
-
uploadFile: vi.fn(),
|
|
17
|
-
getSandbox: vi.fn(),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe("SecurityScanTool", () => {
|
|
21
|
-
let mockSandbox: ReturnType<typeof createMockSandbox>;
|
|
22
|
-
let installer: LazyInstaller;
|
|
23
|
-
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
vi.clearAllMocks();
|
|
26
|
-
mockSandbox = createMockSandbox();
|
|
27
|
-
// Use custom template mode so ensureGeminiCli is instant
|
|
28
|
-
installer = new LazyInstaller(true);
|
|
29
|
-
bindSecuritySandbox(
|
|
30
|
-
mockSandbox as unknown as SandboxManager,
|
|
31
|
-
installer
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// ─── Test #39: Runs security:analyze and returns report ───
|
|
36
|
-
|
|
37
|
-
it("runs gemini security:analyze and returns the report", async () => {
|
|
38
|
-
mockSandbox.exec.mockResolvedValueOnce({
|
|
39
|
-
exitCode: 0,
|
|
40
|
-
stdout: "## Security Report\n\nNo critical vulnerabilities found.",
|
|
41
|
-
stderr: "",
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const result = await SecurityScanTool.execute({ target: "changes" });
|
|
45
|
-
|
|
46
|
-
expect(result.content).toContain("Security Report");
|
|
47
|
-
expect(mockSandbox.exec).toHaveBeenCalledWith(
|
|
48
|
-
expect.stringContaining("security:analyze")
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// ─── Test #40: Returns error for file scan without path ───
|
|
53
|
-
|
|
54
|
-
it("returns error when target is 'file' but no path provided", async () => {
|
|
55
|
-
const result = await SecurityScanTool.execute({ target: "file" });
|
|
56
|
-
|
|
57
|
-
expect(result.content).toMatch(/path.*required/i);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// ─── Test #41: Handles failed scans gracefully ───
|
|
61
|
-
|
|
62
|
-
it("returns failure info when scan exits with non-zero code", async () => {
|
|
63
|
-
mockSandbox.exec.mockResolvedValueOnce({
|
|
64
|
-
exitCode: 1,
|
|
65
|
-
stdout: "",
|
|
66
|
-
stderr: "Some error occurred",
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const result = await SecurityScanTool.execute({ target: "changes" });
|
|
70
|
-
|
|
71
|
-
expect(result.content).toContain("failed");
|
|
72
|
-
expect(result.content).toContain("Some error occurred");
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("DepScanTool", () => {
|
|
77
|
-
let mockSandbox: ReturnType<typeof createMockSandbox>;
|
|
78
|
-
let installer: LazyInstaller;
|
|
79
|
-
|
|
80
|
-
beforeEach(() => {
|
|
81
|
-
vi.clearAllMocks();
|
|
82
|
-
mockSandbox = createMockSandbox();
|
|
83
|
-
installer = new LazyInstaller(true); // pre-baked template
|
|
84
|
-
bindSecuritySandbox(
|
|
85
|
-
mockSandbox as unknown as SandboxManager,
|
|
86
|
-
installer
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// ─── Test #42: OSV-Scanner returns vulnerability report ───
|
|
91
|
-
|
|
92
|
-
it("runs osv-scanner and returns the report", async () => {
|
|
93
|
-
mockSandbox.exec.mockResolvedValueOnce({
|
|
94
|
-
exitCode: 0,
|
|
95
|
-
stdout: "Found 2 vulnerabilities:\n- CVE-2024-1234\n- CVE-2024-5678",
|
|
96
|
-
stderr: "",
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const result = await DepScanTool.execute({ format: "summary" });
|
|
100
|
-
|
|
101
|
-
expect(result.content).toContain("CVE-2024-1234");
|
|
102
|
-
expect(result.content).toContain("CVE-2024-5678");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// ─── Test #43: Falls back to npm audit when OSV-Scanner fails ───
|
|
106
|
-
|
|
107
|
-
it("falls back to npm audit if osv-scanner returns empty output", async () => {
|
|
108
|
-
// OSV-Scanner: empty output
|
|
109
|
-
mockSandbox.exec
|
|
110
|
-
.mockResolvedValueOnce({ exitCode: 1, stdout: "", stderr: "error" })
|
|
111
|
-
// npm audit fallback
|
|
112
|
-
.mockResolvedValueOnce({
|
|
113
|
-
exitCode: 0,
|
|
114
|
-
stdout: "found 0 vulnerabilities",
|
|
115
|
-
stderr: "",
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const result = await DepScanTool.execute({ format: "summary" });
|
|
119
|
-
|
|
120
|
-
expect(result.content).toContain("0 vulnerabilities");
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { AIMessageChunk } from "@langchain/core/messages";
|
|
3
|
-
import { ExecutionHarness } from "../core/agentLoop.js";
|
|
4
|
-
import { ContextState } from "../core/promptBuilder.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Creates a mock LLM that yields predefined chunks when .stream() is called.
|
|
8
|
-
* This avoids real API calls while testing streaming behavior.
|
|
9
|
-
*/
|
|
10
|
-
function createMockStreamingLlm(chunks: AIMessageChunk[]) {
|
|
11
|
-
return {
|
|
12
|
-
invoke: vi.fn(),
|
|
13
|
-
stream: vi.fn().mockResolvedValue({
|
|
14
|
-
async *[Symbol.asyncIterator]() {
|
|
15
|
-
for (const chunk of chunks) {
|
|
16
|
-
yield chunk;
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
}),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("ExecutionHarness Streaming", () => {
|
|
24
|
-
const baseState: ContextState = {
|
|
25
|
-
globalSystemInstructions: "You are a helpful assistant.",
|
|
26
|
-
projectMemory: "",
|
|
27
|
-
sessionContext: "",
|
|
28
|
-
conversationHistory: [],
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// ─── RED Test #8: streamStep emits text chunks to a callback ───
|
|
32
|
-
|
|
33
|
-
it("emits text content chunks to an onToken callback", async () => {
|
|
34
|
-
const chunks = [
|
|
35
|
-
new AIMessageChunk({ content: "Hello" }),
|
|
36
|
-
new AIMessageChunk({ content: " world" }),
|
|
37
|
-
new AIMessageChunk({ content: "!" }),
|
|
38
|
-
];
|
|
39
|
-
const mockLlm = createMockStreamingLlm(chunks);
|
|
40
|
-
const harness = new ExecutionHarness(mockLlm as any);
|
|
41
|
-
|
|
42
|
-
const receivedTokens: string[] = [];
|
|
43
|
-
const result = await harness.streamStep(baseState, {
|
|
44
|
-
onToken: (token: string) => receivedTokens.push(token),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Callback should have received each text chunk
|
|
48
|
-
expect(receivedTokens).toEqual(["Hello", " world", "!"]);
|
|
49
|
-
|
|
50
|
-
// The returned message should contain the full concatenated content
|
|
51
|
-
expect(result.content).toBe("Hello world!");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// ─── RED Test #9: streamStep buffers tool calls and returns complete AIMessage ───
|
|
55
|
-
|
|
56
|
-
it("buffers tool call chunks and returns a complete AIMessage with tool_calls", async () => {
|
|
57
|
-
const chunks = [
|
|
58
|
-
new AIMessageChunk({
|
|
59
|
-
content: "",
|
|
60
|
-
tool_call_chunks: [
|
|
61
|
-
{ name: "read_file", args: '{"path": "', index: 0, id: "tc_1", type: "tool_call_chunk" },
|
|
62
|
-
],
|
|
63
|
-
}),
|
|
64
|
-
new AIMessageChunk({
|
|
65
|
-
content: "",
|
|
66
|
-
tool_call_chunks: [
|
|
67
|
-
{ name: undefined, args: 'src/index.ts"}', index: 0, id: undefined, type: "tool_call_chunk" },
|
|
68
|
-
],
|
|
69
|
-
}),
|
|
70
|
-
];
|
|
71
|
-
const mockLlm = createMockStreamingLlm(chunks);
|
|
72
|
-
const harness = new ExecutionHarness(mockLlm as any);
|
|
73
|
-
|
|
74
|
-
const result = await harness.streamStep(baseState, {});
|
|
75
|
-
|
|
76
|
-
// The result should have tool_calls populated
|
|
77
|
-
expect(result.tool_calls).toBeDefined();
|
|
78
|
-
expect(result.tool_calls!.length).toBe(1);
|
|
79
|
-
expect(result.tool_calls![0].name).toBe("read_file");
|
|
80
|
-
expect(result.tool_calls![0].args).toEqual({ path: "src/index.ts" });
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { ToolRouter, ToolTarget } from "../tools/router.js";
|
|
3
|
-
|
|
4
|
-
describe("Tool Router", () => {
|
|
5
|
-
let router: ToolRouter;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
router = new ToolRouter();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
// ─── Test #20: Routes write_file to host ───
|
|
12
|
-
|
|
13
|
-
it("routes write_file to the host", () => {
|
|
14
|
-
expect(router.getTarget("write_file")).toBe(ToolTarget.HOST);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// ─── Test #21: Routes read_file to host ───
|
|
18
|
-
|
|
19
|
-
it("routes read_file to the host", () => {
|
|
20
|
-
expect(router.getTarget("read_file")).toBe(ToolTarget.HOST);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// ─── Test #22: Routes bash to sandbox ───
|
|
24
|
-
|
|
25
|
-
it("routes bash to the sandbox", () => {
|
|
26
|
-
expect(router.getTarget("bash")).toBe(ToolTarget.SANDBOX);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// ─── Test #23: Routes run_tests to sandbox ───
|
|
30
|
-
|
|
31
|
-
it("routes run_tests to the sandbox", () => {
|
|
32
|
-
expect(router.getTarget("run_tests")).toBe(ToolTarget.SANDBOX);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// ─── Test #24: Routes install_deps to sandbox ───
|
|
36
|
-
|
|
37
|
-
it("routes install_deps to the sandbox", () => {
|
|
38
|
-
expect(router.getTarget("install_deps")).toBe(ToolTarget.SANDBOX);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// ─── Test #25: Routes search_tools to host ───
|
|
42
|
-
|
|
43
|
-
it("routes search_tools to the host", () => {
|
|
44
|
-
expect(router.getTarget("search_tools")).toBe(ToolTarget.HOST);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// ─── Test #26: Unknown tools default to sandbox (safe) ───
|
|
48
|
-
|
|
49
|
-
it("defaults unknown tools to sandbox for safety", () => {
|
|
50
|
-
expect(router.getTarget("unknown_tool")).toBe(ToolTarget.SANDBOX);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import * as os from "node:os";
|
|
5
|
-
import { ReadFileTool, WriteFileTool } from "../tools/index.js";
|
|
6
|
-
|
|
7
|
-
describe("ReadFileTool", () => {
|
|
8
|
-
let tmpDir: string;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
tmpDir = fs.mkdtempSync(path.join(process.cwd(), ".joone-tools-test-"));
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// ─── Test #27: Reads a normal file ───
|
|
19
|
-
|
|
20
|
-
it("reads a small file and returns its content", async () => {
|
|
21
|
-
const filePath = path.join(tmpDir, "hello.txt");
|
|
22
|
-
fs.writeFileSync(filePath, "Hello, world!", "utf-8");
|
|
23
|
-
|
|
24
|
-
const result = await ReadFileTool.execute({ path: filePath });
|
|
25
|
-
|
|
26
|
-
expect(result.content).toBe("Hello, world!");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// ─── Test #28: Returns error for non-existent file ───
|
|
30
|
-
|
|
31
|
-
it("returns an error message for a non-existent file", async () => {
|
|
32
|
-
const result = await ReadFileTool.execute({
|
|
33
|
-
path: path.join(tmpDir, "nope.txt"),
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
expect(result.content).toMatch(/not found/i);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// ─── Test #29: File size guardrail rejects files over 512KB ───
|
|
40
|
-
|
|
41
|
-
it("rejects files larger than 512KB with a descriptive error", async () => {
|
|
42
|
-
const filePath = path.join(tmpDir, "big.txt");
|
|
43
|
-
// Create a 600KB file
|
|
44
|
-
const bigContent = "x".repeat(600 * 1024);
|
|
45
|
-
fs.writeFileSync(filePath, bigContent, "utf-8");
|
|
46
|
-
|
|
47
|
-
const result = await ReadFileTool.execute({ path: filePath });
|
|
48
|
-
|
|
49
|
-
expect(result.content).toMatch(/too large/i);
|
|
50
|
-
expect(result.content).toMatch(/512/);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// ─── Test #30: Line range slicing works ───
|
|
54
|
-
|
|
55
|
-
it("returns only the requested line range", async () => {
|
|
56
|
-
const filePath = path.join(tmpDir, "lines.txt");
|
|
57
|
-
const lines = Array.from({ length: 20 }, (_, i) => `Line ${i + 1}`);
|
|
58
|
-
fs.writeFileSync(filePath, lines.join("\n"), "utf-8");
|
|
59
|
-
|
|
60
|
-
const result = await ReadFileTool.execute({
|
|
61
|
-
path: filePath,
|
|
62
|
-
startLine: 5,
|
|
63
|
-
endLine: 7,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
expect(result.content).toContain("5: Line 5");
|
|
67
|
-
expect(result.content).toContain("6: Line 6");
|
|
68
|
-
expect(result.content).toContain("7: Line 7");
|
|
69
|
-
expect(result.content).not.toContain("4: Line 4");
|
|
70
|
-
expect(result.content).not.toContain("8: Line 8");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// ─── Test #31: Line count guardrail truncates long files ───
|
|
74
|
-
|
|
75
|
-
it("truncates files with more than 2000 lines", async () => {
|
|
76
|
-
const filePath = path.join(tmpDir, "long.txt");
|
|
77
|
-
// Create a file with 2500 short lines (under 512KB)
|
|
78
|
-
const lines = Array.from({ length: 2500 }, (_, i) => `L${i + 1}`);
|
|
79
|
-
fs.writeFileSync(filePath, lines.join("\n"), "utf-8");
|
|
80
|
-
|
|
81
|
-
const result = await ReadFileTool.execute({ path: filePath });
|
|
82
|
-
|
|
83
|
-
expect(result.content).toMatch(/truncated at 2000 lines/i);
|
|
84
|
-
expect(result.content).toContain("1: L1");
|
|
85
|
-
expect(result.content).toContain("2000: L2000");
|
|
86
|
-
expect(result.content).not.toContain("2001: L2001");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ─── Test #X: Security Guardrail Blocks Outside Files ───
|
|
90
|
-
|
|
91
|
-
it("blocks reading files outside the project workspace", async () => {
|
|
92
|
-
// Create a file in the OS tmp directory (guaranteed outside project workspace)
|
|
93
|
-
const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), "joone-outside-"));
|
|
94
|
-
const filePath = path.join(outsideDir, "secret.txt");
|
|
95
|
-
fs.writeFileSync(filePath, "secret token", "utf-8");
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const result = await ReadFileTool.execute({ path: filePath });
|
|
99
|
-
expect(result.isError).toBe(true);
|
|
100
|
-
expect(result.content).toMatch(/Security Error: Access Denied/i);
|
|
101
|
-
expect(result.content).toMatch(/outside the current project workspace/i);
|
|
102
|
-
} finally {
|
|
103
|
-
fs.rmSync(outsideDir, { recursive: true, force: true });
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe("WriteFileTool", () => {
|
|
109
|
-
let tmpDir: string;
|
|
110
|
-
|
|
111
|
-
beforeEach(() => {
|
|
112
|
-
tmpDir = fs.mkdtempSync(path.join(process.cwd(), ".joone-write-test-"));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
afterEach(() => {
|
|
116
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// ─── Test #32: Writes a file to disk ───
|
|
120
|
-
|
|
121
|
-
it("writes content to a file and confirms", async () => {
|
|
122
|
-
const filePath = path.join(tmpDir, "output.ts");
|
|
123
|
-
|
|
124
|
-
const result = await WriteFileTool.execute({
|
|
125
|
-
path: filePath,
|
|
126
|
-
content: "const x = 42;",
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
expect(result.content).toMatch(/file written/i);
|
|
130
|
-
expect(fs.readFileSync(filePath, "utf-8")).toBe("const x = 42;");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// ─── Test #33: Creates parent directories if needed ───
|
|
134
|
-
|
|
135
|
-
it("creates parent directories if they do not exist", async () => {
|
|
136
|
-
const filePath = path.join(tmpDir, "nested", "deep", "file.ts");
|
|
137
|
-
|
|
138
|
-
const result = await WriteFileTool.execute({
|
|
139
|
-
path: filePath,
|
|
140
|
-
content: "export {}",
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
expect(result.content).toMatch(/file written/i);
|
|
144
|
-
expect(fs.existsSync(filePath)).toBe(true);
|
|
145
|
-
});
|
|
146
|
-
});
|