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.
Files changed (235) hide show
  1. package/dist/__tests__/config.test.js +1 -0
  2. package/dist/__tests__/config.test.js.map +1 -1
  3. package/dist/__tests__/installHostDeps.test.js +45 -0
  4. package/dist/__tests__/installHostDeps.test.js.map +1 -0
  5. package/dist/__tests__/whitelistedBackend.test.js +18 -0
  6. package/dist/__tests__/whitelistedBackend.test.js.map +1 -0
  7. package/dist/cli/config.d.ts +2 -0
  8. package/dist/cli/config.js +1 -0
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/index.js +84 -97
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/commands/builtinCommands.js +6 -6
  13. package/dist/commands/builtinCommands.js.map +1 -1
  14. package/dist/commands/commandRegistry.d.ts +3 -1
  15. package/dist/commands/commandRegistry.js.map +1 -1
  16. package/dist/core/agentLoop.d.ts +11 -28
  17. package/dist/core/agentLoop.js +68 -229
  18. package/dist/core/agentLoop.js.map +1 -1
  19. package/dist/core/compactor.js +2 -2
  20. package/dist/core/compactor.js.map +1 -1
  21. package/dist/core/contextGuard.d.ts +5 -0
  22. package/dist/core/contextGuard.js +30 -3
  23. package/dist/core/contextGuard.js.map +1 -1
  24. package/dist/core/events.d.ts +45 -0
  25. package/dist/core/events.js +8 -0
  26. package/dist/core/events.js.map +1 -0
  27. package/dist/core/promptBuilder.js.map +1 -1
  28. package/dist/core/sessionStore.js +3 -2
  29. package/dist/core/sessionStore.js.map +1 -1
  30. package/dist/core/tokenCounter.d.ts +8 -1
  31. package/dist/core/tokenCounter.js +28 -0
  32. package/dist/core/tokenCounter.js.map +1 -1
  33. package/dist/hitl/bridge.js +1 -27
  34. package/dist/hitl/bridge.js.map +1 -1
  35. package/dist/middleware/loopDetection.d.ts +7 -23
  36. package/dist/middleware/loopDetection.js +38 -42
  37. package/dist/middleware/loopDetection.js.map +1 -1
  38. package/dist/sandbox/whitelistedBackend.d.ts +5 -0
  39. package/dist/sandbox/whitelistedBackend.js +27 -0
  40. package/dist/sandbox/whitelistedBackend.js.map +1 -0
  41. package/dist/tools/askUser.d.ts +12 -3
  42. package/dist/tools/askUser.js +16 -28
  43. package/dist/tools/askUser.js.map +1 -1
  44. package/dist/tools/bashTool.d.ts +11 -0
  45. package/dist/tools/bashTool.js +51 -0
  46. package/dist/tools/bashTool.js.map +1 -0
  47. package/dist/tools/index.d.ts +15 -27
  48. package/dist/tools/index.js +9 -181
  49. package/dist/tools/index.js.map +1 -1
  50. package/dist/tools/installHostDeps.d.ts +8 -0
  51. package/dist/tools/installHostDeps.js +44 -0
  52. package/dist/tools/installHostDeps.js.map +1 -0
  53. package/dist/tracing/sessionTracer.d.ts +1 -0
  54. package/dist/tracing/sessionTracer.js +4 -1
  55. package/dist/tracing/sessionTracer.js.map +1 -1
  56. package/dist/ui/App.js +116 -55
  57. package/dist/ui/App.js.map +1 -1
  58. package/dist/ui/components/ActionLog.d.ts +7 -0
  59. package/dist/ui/components/ActionLog.js +63 -0
  60. package/dist/ui/components/ActionLog.js.map +1 -0
  61. package/dist/ui/components/FileBrowser.d.ts +2 -0
  62. package/dist/ui/components/FileBrowser.js +41 -0
  63. package/dist/ui/components/FileBrowser.js.map +1 -0
  64. package/dist/ui/components/MessageBubble.js +1 -1
  65. package/dist/ui/components/MessageBubble.js.map +1 -1
  66. package/package.json +8 -5
  67. package/AGENTS.md +0 -56
  68. package/Handover.md +0 -115
  69. package/PROGRESS.md +0 -160
  70. package/dist/__tests__/m55.test.js +0 -160
  71. package/dist/__tests__/m55.test.js.map +0 -1
  72. package/dist/__tests__/middleware.test.js +0 -169
  73. package/dist/__tests__/middleware.test.js.map +0 -1
  74. package/dist/__tests__/optimizations.test.d.ts +0 -1
  75. package/dist/__tests__/optimizations.test.js +0 -136
  76. package/dist/__tests__/optimizations.test.js.map +0 -1
  77. package/dist/__tests__/security.test.d.ts +0 -1
  78. package/dist/__tests__/security.test.js +0 -86
  79. package/dist/__tests__/security.test.js.map +0 -1
  80. package/dist/__tests__/streaming.test.d.ts +0 -1
  81. package/dist/__tests__/streaming.test.js +0 -71
  82. package/dist/__tests__/streaming.test.js.map +0 -1
  83. package/dist/__tests__/toolRouter.test.d.ts +0 -1
  84. package/dist/__tests__/toolRouter.test.js +0 -37
  85. package/dist/__tests__/toolRouter.test.js.map +0 -1
  86. package/dist/__tests__/tools.test.d.ts +0 -1
  87. package/dist/__tests__/tools.test.js +0 -112
  88. package/dist/__tests__/tools.test.js.map +0 -1
  89. package/dist/core/subAgent.d.ts +0 -56
  90. package/dist/core/subAgent.js +0 -240
  91. package/dist/core/subAgent.js.map +0 -1
  92. package/dist/debug_google.d.ts +0 -1
  93. package/dist/debug_google.js +0 -23
  94. package/dist/debug_google.js.map +0 -1
  95. package/dist/middleware/commandSanitizer.d.ts +0 -18
  96. package/dist/middleware/commandSanitizer.js +0 -50
  97. package/dist/middleware/commandSanitizer.js.map +0 -1
  98. package/dist/middleware/permission.d.ts +0 -17
  99. package/dist/middleware/permission.js +0 -59
  100. package/dist/middleware/permission.js.map +0 -1
  101. package/dist/middleware/pipeline.d.ts +0 -31
  102. package/dist/middleware/pipeline.js +0 -62
  103. package/dist/middleware/pipeline.js.map +0 -1
  104. package/dist/middleware/preCompletion.d.ts +0 -29
  105. package/dist/middleware/preCompletion.js +0 -82
  106. package/dist/middleware/preCompletion.js.map +0 -1
  107. package/dist/middleware/types.d.ts +0 -40
  108. package/dist/middleware/types.js +0 -8
  109. package/dist/middleware/types.js.map +0 -1
  110. package/dist/skills/loader.d.ts +0 -55
  111. package/dist/skills/loader.js +0 -132
  112. package/dist/skills/loader.js.map +0 -1
  113. package/dist/skills/tools.d.ts +0 -5
  114. package/dist/skills/tools.js +0 -78
  115. package/dist/skills/tools.js.map +0 -1
  116. package/dist/test_cache.d.ts +0 -1
  117. package/dist/test_cache.js +0 -55
  118. package/dist/test_cache.js.map +0 -1
  119. package/dist/test_google.d.ts +0 -1
  120. package/dist/test_google.js +0 -36
  121. package/dist/test_google.js.map +0 -1
  122. package/dist/tools/browser.d.ts +0 -19
  123. package/dist/tools/browser.js +0 -111
  124. package/dist/tools/browser.js.map +0 -1
  125. package/dist/tools/registry.d.ts +0 -31
  126. package/dist/tools/registry.js +0 -168
  127. package/dist/tools/registry.js.map +0 -1
  128. package/dist/tools/router.d.ts +0 -34
  129. package/dist/tools/router.js +0 -75
  130. package/dist/tools/router.js.map +0 -1
  131. package/dist/tools/security.d.ts +0 -28
  132. package/dist/tools/security.js +0 -183
  133. package/dist/tools/security.js.map +0 -1
  134. package/dist/tools/spawnAgent.d.ts +0 -19
  135. package/dist/tools/spawnAgent.js +0 -130
  136. package/dist/tools/spawnAgent.js.map +0 -1
  137. package/dist/tools/webSearch.d.ts +0 -6
  138. package/dist/tools/webSearch.js +0 -120
  139. package/dist/tools/webSearch.js.map +0 -1
  140. package/docs/01_insights_and_patterns.md +0 -27
  141. package/docs/02_edge_cases_and_mitigations.md +0 -143
  142. package/docs/03_initial_implementation_plan.md +0 -66
  143. package/docs/04_tech_stack_proposal.md +0 -20
  144. package/docs/05_prd.md +0 -87
  145. package/docs/06_user_stories.md +0 -72
  146. package/docs/07_system_architecture.md +0 -138
  147. package/docs/08_roadmap.md +0 -200
  148. package/e2b/Dockerfile +0 -26
  149. package/src/__tests__/bootstrap.test.ts +0 -111
  150. package/src/__tests__/config.test.ts +0 -97
  151. package/src/__tests__/m55.test.ts +0 -238
  152. package/src/__tests__/middleware.test.ts +0 -219
  153. package/src/__tests__/modelFactory.test.ts +0 -63
  154. package/src/__tests__/optimizations.test.ts +0 -201
  155. package/src/__tests__/promptBuilder.test.ts +0 -141
  156. package/src/__tests__/sandbox.test.ts +0 -102
  157. package/src/__tests__/security.test.ts +0 -122
  158. package/src/__tests__/streaming.test.ts +0 -82
  159. package/src/__tests__/toolRouter.test.ts +0 -52
  160. package/src/__tests__/tools.test.ts +0 -146
  161. package/src/__tests__/tracing.test.ts +0 -196
  162. package/src/agents/agentRegistry.ts +0 -69
  163. package/src/agents/agentSpec.ts +0 -67
  164. package/src/agents/builtinAgents.ts +0 -142
  165. package/src/cli/config.ts +0 -124
  166. package/src/cli/index.ts +0 -742
  167. package/src/cli/modelFactory.ts +0 -174
  168. package/src/cli/postinstall.ts +0 -28
  169. package/src/cli/providers.ts +0 -107
  170. package/src/commands/builtinCommands.ts +0 -293
  171. package/src/commands/commandRegistry.ts +0 -194
  172. package/src/core/agentLoop.d.ts.map +0 -1
  173. package/src/core/agentLoop.ts +0 -312
  174. package/src/core/autoSave.ts +0 -95
  175. package/src/core/compactor.ts +0 -252
  176. package/src/core/contextGuard.ts +0 -129
  177. package/src/core/errors.ts +0 -202
  178. package/src/core/promptBuilder.d.ts.map +0 -1
  179. package/src/core/promptBuilder.ts +0 -139
  180. package/src/core/reasoningRouter.ts +0 -121
  181. package/src/core/retry.ts +0 -75
  182. package/src/core/sessionResumer.ts +0 -90
  183. package/src/core/sessionStore.ts +0 -216
  184. package/src/core/subAgent.ts +0 -339
  185. package/src/core/tokenCounter.ts +0 -64
  186. package/src/evals/dataset.ts +0 -67
  187. package/src/evals/evaluator.ts +0 -81
  188. package/src/hitl/bridge.ts +0 -160
  189. package/src/middleware/commandSanitizer.ts +0 -60
  190. package/src/middleware/loopDetection.ts +0 -63
  191. package/src/middleware/permission.ts +0 -72
  192. package/src/middleware/pipeline.ts +0 -75
  193. package/src/middleware/preCompletion.ts +0 -94
  194. package/src/middleware/types.ts +0 -45
  195. package/src/sandbox/bootstrap.ts +0 -121
  196. package/src/sandbox/manager.ts +0 -239
  197. package/src/sandbox/sync.ts +0 -157
  198. package/src/skills/loader.ts +0 -143
  199. package/src/skills/tools.ts +0 -99
  200. package/src/skills/types.ts +0 -13
  201. package/src/test_cache.ts +0 -72
  202. package/src/tools/askUser.ts +0 -47
  203. package/src/tools/browser.ts +0 -137
  204. package/src/tools/index.d.ts.map +0 -1
  205. package/src/tools/index.ts +0 -237
  206. package/src/tools/registry.ts +0 -198
  207. package/src/tools/router.ts +0 -78
  208. package/src/tools/security.ts +0 -220
  209. package/src/tools/spawnAgent.ts +0 -158
  210. package/src/tools/webSearch.ts +0 -142
  211. package/src/tracing/analyzer.ts +0 -265
  212. package/src/tracing/langsmith.ts +0 -63
  213. package/src/tracing/sessionTracer.ts +0 -202
  214. package/src/tracing/types.ts +0 -49
  215. package/src/types/valyu.d.ts +0 -37
  216. package/src/ui/App.tsx +0 -404
  217. package/src/ui/components/HITLPrompt.tsx +0 -119
  218. package/src/ui/components/Header.tsx +0 -51
  219. package/src/ui/components/MessageBubble.tsx +0 -46
  220. package/src/ui/components/StatusBar.tsx +0 -138
  221. package/src/ui/components/StreamingText.tsx +0 -48
  222. package/src/ui/components/ToolCallPanel.tsx +0 -80
  223. package/tests/commands/commands.test.ts +0 -356
  224. package/tests/core/compactor.test.ts +0 -217
  225. package/tests/core/retryAndErrors.test.ts +0 -164
  226. package/tests/core/sessionResumer.test.ts +0 -95
  227. package/tests/core/sessionStore.test.ts +0 -84
  228. package/tests/core/stability.test.ts +0 -165
  229. package/tests/core/subAgent.test.ts +0 -238
  230. package/tests/hitl/hitlBridge.test.ts +0 -115
  231. package/tsconfig.json +0 -16
  232. package/vitest.config.ts +0 -10
  233. package/vitest.out +0 -48
  234. /package/dist/__tests__/{m55.test.d.ts → installHostDeps.test.d.ts} +0 -0
  235. /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
- });