joonecli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +12 -12
  2. package/dist/__tests__/optimizations.test.js.map +1 -1
  3. package/dist/__tests__/promptBuilder.test.js +14 -20
  4. package/dist/__tests__/promptBuilder.test.js.map +1 -1
  5. package/dist/agents/agentRegistry.d.ts +37 -0
  6. package/dist/agents/agentRegistry.js +58 -0
  7. package/dist/agents/agentRegistry.js.map +1 -0
  8. package/dist/agents/agentSpec.d.ts +54 -0
  9. package/dist/agents/agentSpec.js +9 -0
  10. package/dist/agents/agentSpec.js.map +1 -0
  11. package/dist/agents/builtinAgents.d.ts +20 -0
  12. package/{src/agents/builtinAgents.ts → dist/agents/builtinAgents.js} +84 -101
  13. package/dist/agents/builtinAgents.js.map +1 -0
  14. package/dist/cli/config.d.ts +4 -0
  15. package/dist/cli/config.js.map +1 -1
  16. package/dist/cli/index.js +29 -2
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/cli/postinstall.d.ts +2 -0
  19. package/dist/cli/postinstall.js +25 -0
  20. package/dist/cli/postinstall.js.map +1 -0
  21. package/dist/commands/builtinCommands.d.ts +21 -0
  22. package/dist/commands/builtinCommands.js +241 -0
  23. package/dist/commands/builtinCommands.js.map +1 -0
  24. package/dist/commands/commandRegistry.d.ts +92 -0
  25. package/dist/commands/commandRegistry.js +128 -0
  26. package/dist/commands/commandRegistry.js.map +1 -0
  27. package/dist/core/agentLoop.d.ts +7 -2
  28. package/dist/core/agentLoop.js +35 -13
  29. package/dist/core/agentLoop.js.map +1 -1
  30. package/dist/core/autoSave.d.ts +41 -0
  31. package/dist/core/autoSave.js +69 -0
  32. package/dist/core/autoSave.js.map +1 -0
  33. package/dist/core/compactor.d.ts +66 -0
  34. package/dist/core/compactor.js +170 -0
  35. package/dist/core/compactor.js.map +1 -0
  36. package/dist/core/contextGuard.d.ts +38 -0
  37. package/dist/core/contextGuard.js +122 -0
  38. package/dist/core/contextGuard.js.map +1 -0
  39. package/dist/core/events.d.ts +45 -0
  40. package/dist/core/events.js +8 -0
  41. package/dist/core/events.js.map +1 -0
  42. package/dist/core/promptBuilder.d.ts +16 -1
  43. package/dist/core/promptBuilder.js +27 -14
  44. package/dist/core/promptBuilder.js.map +1 -1
  45. package/dist/core/sessionResumer.js +3 -3
  46. package/dist/core/sessionResumer.js.map +1 -1
  47. package/dist/core/sessionStore.js +3 -2
  48. package/dist/core/sessionStore.js.map +1 -1
  49. package/dist/core/subAgent.d.ts +56 -0
  50. package/dist/core/subAgent.js +240 -0
  51. package/dist/core/subAgent.js.map +1 -0
  52. package/dist/core/tokenCounter.d.ts +8 -1
  53. package/dist/core/tokenCounter.js +28 -0
  54. package/dist/core/tokenCounter.js.map +1 -1
  55. package/dist/debug_google.d.ts +1 -0
  56. package/dist/debug_google.js +23 -0
  57. package/dist/debug_google.js.map +1 -0
  58. package/dist/middleware/permission.js +1 -0
  59. package/dist/middleware/permission.js.map +1 -1
  60. package/dist/test_google.d.ts +1 -0
  61. package/dist/test_google.js +32 -89
  62. package/dist/test_google.js.map +1 -0
  63. package/dist/tools/browser.js +4 -1
  64. package/dist/tools/browser.js.map +1 -1
  65. package/dist/tools/index.d.ts +2 -1
  66. package/dist/tools/index.js +11 -3
  67. package/dist/tools/index.js.map +1 -1
  68. package/dist/tools/installHostDeps.d.ts +2 -0
  69. package/dist/tools/installHostDeps.js +37 -0
  70. package/dist/tools/installHostDeps.js.map +1 -0
  71. package/dist/tools/router.js +3 -0
  72. package/dist/tools/router.js.map +1 -1
  73. package/dist/tools/spawnAgent.d.ts +19 -0
  74. package/dist/tools/spawnAgent.js +132 -0
  75. package/dist/tools/spawnAgent.js.map +1 -0
  76. package/dist/tracing/sessionTracer.d.ts +1 -0
  77. package/dist/tracing/sessionTracer.js +4 -1
  78. package/dist/tracing/sessionTracer.js.map +1 -1
  79. package/dist/ui/App.js +94 -6
  80. package/dist/ui/App.js.map +1 -1
  81. package/dist/ui/components/ActionLog.d.ts +7 -0
  82. package/dist/ui/components/ActionLog.js +63 -0
  83. package/dist/ui/components/ActionLog.js.map +1 -0
  84. package/dist/ui/components/FileBrowser.d.ts +2 -0
  85. package/dist/ui/components/FileBrowser.js +41 -0
  86. package/dist/ui/components/FileBrowser.js.map +1 -0
  87. package/package.json +5 -6
  88. package/AGENTS.md +0 -56
  89. package/Handover.md +0 -115
  90. package/PROGRESS.md +0 -160
  91. package/docs/01_insights_and_patterns.md +0 -27
  92. package/docs/02_edge_cases_and_mitigations.md +0 -143
  93. package/docs/03_initial_implementation_plan.md +0 -66
  94. package/docs/04_tech_stack_proposal.md +0 -20
  95. package/docs/05_prd.md +0 -87
  96. package/docs/06_user_stories.md +0 -72
  97. package/docs/07_system_architecture.md +0 -138
  98. package/docs/08_roadmap.md +0 -200
  99. package/e2b/Dockerfile +0 -26
  100. package/src/__tests__/bootstrap.test.ts +0 -111
  101. package/src/__tests__/config.test.ts +0 -97
  102. package/src/__tests__/m55.test.ts +0 -238
  103. package/src/__tests__/middleware.test.ts +0 -219
  104. package/src/__tests__/modelFactory.test.ts +0 -63
  105. package/src/__tests__/optimizations.test.ts +0 -201
  106. package/src/__tests__/promptBuilder.test.ts +0 -141
  107. package/src/__tests__/sandbox.test.ts +0 -102
  108. package/src/__tests__/security.test.ts +0 -122
  109. package/src/__tests__/streaming.test.ts +0 -82
  110. package/src/__tests__/toolRouter.test.ts +0 -52
  111. package/src/__tests__/tools.test.ts +0 -146
  112. package/src/__tests__/tracing.test.ts +0 -196
  113. package/src/agents/agentRegistry.ts +0 -69
  114. package/src/agents/agentSpec.ts +0 -67
  115. package/src/cli/config.ts +0 -124
  116. package/src/cli/index.ts +0 -730
  117. package/src/cli/modelFactory.ts +0 -174
  118. package/src/cli/providers.ts +0 -107
  119. package/src/commands/builtinCommands.ts +0 -293
  120. package/src/commands/commandRegistry.ts +0 -194
  121. package/src/core/agentLoop.d.ts.map +0 -1
  122. package/src/core/agentLoop.ts +0 -312
  123. package/src/core/autoSave.ts +0 -95
  124. package/src/core/compactor.ts +0 -252
  125. package/src/core/contextGuard.ts +0 -129
  126. package/src/core/errors.ts +0 -202
  127. package/src/core/promptBuilder.d.ts.map +0 -1
  128. package/src/core/promptBuilder.ts +0 -139
  129. package/src/core/reasoningRouter.ts +0 -121
  130. package/src/core/retry.ts +0 -75
  131. package/src/core/sessionResumer.ts +0 -90
  132. package/src/core/sessionStore.ts +0 -215
  133. package/src/core/subAgent.ts +0 -339
  134. package/src/core/tokenCounter.ts +0 -64
  135. package/src/evals/dataset.ts +0 -67
  136. package/src/evals/evaluator.ts +0 -81
  137. package/src/hitl/bridge.ts +0 -160
  138. package/src/middleware/commandSanitizer.ts +0 -60
  139. package/src/middleware/loopDetection.ts +0 -63
  140. package/src/middleware/permission.ts +0 -72
  141. package/src/middleware/pipeline.ts +0 -75
  142. package/src/middleware/preCompletion.ts +0 -94
  143. package/src/middleware/types.ts +0 -45
  144. package/src/sandbox/bootstrap.ts +0 -121
  145. package/src/sandbox/manager.ts +0 -239
  146. package/src/sandbox/sync.ts +0 -157
  147. package/src/skills/loader.ts +0 -143
  148. package/src/skills/tools.ts +0 -99
  149. package/src/skills/types.ts +0 -13
  150. package/src/test_cache.ts +0 -72
  151. package/src/test_google.js +0 -40
  152. package/src/test_google.ts +0 -40
  153. package/src/tools/askUser.ts +0 -47
  154. package/src/tools/browser.ts +0 -137
  155. package/src/tools/index.d.ts.map +0 -1
  156. package/src/tools/index.ts +0 -237
  157. package/src/tools/registry.ts +0 -198
  158. package/src/tools/router.ts +0 -78
  159. package/src/tools/security.ts +0 -220
  160. package/src/tools/spawnAgent.ts +0 -158
  161. package/src/tools/webSearch.ts +0 -142
  162. package/src/tracing/analyzer.ts +0 -265
  163. package/src/tracing/langsmith.ts +0 -63
  164. package/src/tracing/sessionTracer.ts +0 -202
  165. package/src/tracing/types.ts +0 -49
  166. package/src/types/valyu.d.ts +0 -37
  167. package/src/ui/App.tsx +0 -404
  168. package/src/ui/components/HITLPrompt.tsx +0 -119
  169. package/src/ui/components/Header.tsx +0 -51
  170. package/src/ui/components/MessageBubble.tsx +0 -46
  171. package/src/ui/components/StatusBar.tsx +0 -138
  172. package/src/ui/components/StreamingText.tsx +0 -48
  173. package/src/ui/components/ToolCallPanel.tsx +0 -80
  174. package/tests/commands/commands.test.ts +0 -356
  175. package/tests/core/compactor.test.ts +0 -217
  176. package/tests/core/retryAndErrors.test.ts +0 -164
  177. package/tests/core/sessionResumer.test.ts +0 -95
  178. package/tests/core/sessionStore.test.ts +0 -84
  179. package/tests/core/stability.test.ts +0 -165
  180. package/tests/core/subAgent.test.ts +0 -238
  181. package/tests/hitl/hitlBridge.test.ts +0 -115
  182. package/tsconfig.json +0 -16
  183. package/vitest.config.ts +0 -10
  184. package/vitest.out +0 -48
package/src/test_cache.ts DELETED
@@ -1,72 +0,0 @@
1
- import "dotenv/config";
2
- import { ChatAnthropic } from "@langchain/anthropic";
3
- import { ExecutionHarness } from "./core/agentLoop.js";
4
- import { ContextState } from "./core/promptBuilder.js";
5
- import { HumanMessage } from "@langchain/core/messages";
6
-
7
- async function runCacheTest() {
8
- console.log("=== Starting Prompt Caching Test ===\n");
9
-
10
- // We need Anthropic API Key for this to actually hit the network and measure cache
11
- if (!process.env.ANTHROPIC_API_KEY) {
12
- console.warn("WARNING: ANTHROPIC_API_KEY is not set in .env file. The LLM call will fail.");
13
- }
14
-
15
- const model = new ChatAnthropic({
16
- modelName: "claude-3-5-sonnet-20241022",
17
- temperature: 0,
18
- maxTokens: 4096,
19
- }); // No tools needed for this basic string test
20
-
21
- const harness = new ExecutionHarness(model, []);
22
-
23
- // 1. Create a massive static prefix (simulating a lot of project context)
24
- // We repeat a string many times to ensure we pass the 1024 token minimum for Anthropic caching.
25
- const massiveProjectMemory = Array(500).fill("Project Rule: Always write clean, modular TypeScript code with strict typings. ").join("\n");
26
-
27
- const state: ContextState = {
28
- globalSystemInstructions: "You are a helpful coding assistant. You remember rules carefully.",
29
- projectMemory: massiveProjectMemory,
30
- sessionContext: "User OS: Windows 11",
31
- conversationHistory: []
32
- };
33
-
34
- console.log("Turn 1: Initial Query (Should create cache)");
35
- state.conversationHistory.push(new HumanMessage("Hello! What is one of the project rules?"));
36
-
37
- const response1 = await harness.step(state);
38
- state.conversationHistory.push(response1);
39
-
40
- // In @langchain/anthropic, the actual usage stats (including cache hits/misses)
41
- // are stored in the response_metadata of the AIMessage.
42
- console.log(`Response 1: ${response1.content}`);
43
- console.log(`Token Usage 1: ${JSON.stringify(response1.response_metadata?.usage, null, 2)}\n`);
44
-
45
- // --- TURN 2 ---
46
- console.log("Turn 2: Follow-up Query (Should hit cache)");
47
- // We DO NOT change the globalSystemInstructions, projectMemory, or sessionContext.
48
- // We only append to the conversation history. This preserves the prefix!
49
- state.conversationHistory.push(new HumanMessage("Could you summarize the rule again briefly?"));
50
-
51
- const response2 = await harness.step(state);
52
- state.conversationHistory.push(response2);
53
-
54
- console.log(`Response 2: ${response2.content}`);
55
- console.log(`Token Usage 2: ${JSON.stringify(response2.response_metadata?.usage, null, 2)}\n`);
56
-
57
- // --- TURN 3: The System Reminder Pattern ---
58
- console.log("Turn 3: Using <system-reminder> to simulate environment change without breaking cache");
59
- // If a file changed, we DON'T update `state.projectMemory`. We inject a reminder.
60
- state.conversationHistory.push(new HumanMessage(
61
- "<system-reminder>\nThe file 'auth.ts' has just been deleted by the user.\n</system-reminder>\nWhat should we do if we need auth now?"
62
- ));
63
-
64
- const response3 = await harness.step(state);
65
- state.conversationHistory.push(response3);
66
-
67
- console.log(`Response 3: ${response3.content}`);
68
- console.log(`Token Usage 3: ${JSON.stringify(response3.response_metadata?.usage, null, 2)}\n`);}
69
-
70
- if (require.main === module) {
71
- runCacheTest().catch(console.error);
72
- }
@@ -1,40 +0,0 @@
1
- import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
2
- import { HumanMessage, SystemMessage } from "@langchain/core/messages";
3
- import * as dotenv from "dotenv";
4
-
5
- dotenv.config();
6
-
7
- async function main() {
8
- const model = new ChatGoogleGenerativeAI({
9
- model: "gemini-2.5-flash",
10
- });
11
-
12
- const messagesGrouped = [
13
- new SystemMessage("You are a helpful assistant."),
14
- new SystemMessage("Also, be concise."),
15
- new HumanMessage("Hello!"),
16
- ];
17
-
18
- try {
19
- await model.invoke(messagesGrouped);
20
- console.log("Success: Grouped SystemMessages at the start work perfectly.");
21
- } catch (e) {
22
- console.error("Grouped systems failed:", e.message);
23
- }
24
-
25
- const messagesMidstream = [
26
- new SystemMessage("You are a helpful assistant."),
27
- new HumanMessage("Hello!"),
28
- new SystemMessage("System recovery hint here."),
29
- new HumanMessage("What did I say?"),
30
- ];
31
-
32
- try {
33
- await model.invoke(messagesMidstream);
34
- console.log("Success: Mid-stream SystemMessages work.");
35
- } catch (e) {
36
- console.error("Midstream systems failed:", e.message);
37
- }
38
- }
39
-
40
- main();
@@ -1,40 +0,0 @@
1
- import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
2
- import { HumanMessage, SystemMessage } from "@langchain/core/messages";
3
- import * as dotenv from "dotenv";
4
-
5
- dotenv.config();
6
-
7
- async function main() {
8
- const model = new ChatGoogleGenerativeAI({
9
- model: "gemini-2.5-flash",
10
- });
11
-
12
- const messagesGrouped = [
13
- new SystemMessage("You are a helpful assistant."),
14
- new SystemMessage("Also, be concise."),
15
- new HumanMessage("Hello!"),
16
- ];
17
-
18
- try {
19
- await model.invoke(messagesGrouped);
20
- console.log("Success: Grouped SystemMessages at the start work perfectly.");
21
- } catch (e: any) {
22
- console.error("Grouped systems failed:", e.message);
23
- }
24
-
25
- const messagesMidstream = [
26
- new SystemMessage("You are a helpful assistant."),
27
- new HumanMessage("Hello!"),
28
- new SystemMessage("System recovery hint here."),
29
- new HumanMessage("What did I say?"),
30
- ];
31
-
32
- try {
33
- await model.invoke(messagesMidstream);
34
- console.log("Success: Mid-stream SystemMessages work.");
35
- } catch (e: any) {
36
- console.error("Midstream systems failed:", e.message);
37
- }
38
- }
39
-
40
- main();
@@ -1,47 +0,0 @@
1
- import { HITLBridge } from "../hitl/bridge.js";
2
- import { DynamicToolInterface, ToolResult } from "./index.js";
3
-
4
- /**
5
- * AskUserQuestionTool — allows the agent to ask the user a clarifying question mid-turn.
6
- *
7
- * Use cases:
8
- * - Resolving ambiguous requirements before coding.
9
- * - Getting user preferences (framework choice, styling, naming).
10
- * - Requesting approval of an implementation plan before proceeding.
11
- */
12
- export const AskUserQuestionTool: DynamicToolInterface = {
13
- name: "ask_user_question",
14
- description:
15
- "Ask the user a question and wait for their response. " +
16
- "Use this when you need clarification on the task, user preferences, " +
17
- "or approval before proceeding with a significant change. " +
18
- "You may optionally provide a list of answer choices.",
19
- schema: {
20
- type: "object" as const,
21
- properties: {
22
- question: {
23
- type: "string",
24
- description: "The question to ask the user.",
25
- },
26
- options: {
27
- type: "array",
28
- items: { type: "string" },
29
- description: "Optional list of predefined answer choices.",
30
- },
31
- },
32
- required: ["question"],
33
- },
34
- async execute(args: Record<string, unknown>): Promise<ToolResult> {
35
- const question = args.question as string;
36
- const options = args.options as string[] | undefined;
37
-
38
- if (!question || question.trim() === "") {
39
- return { content: "Error: You must provide a non-empty question.", isError: true };
40
- }
41
-
42
- const bridge = HITLBridge.getInstance();
43
- const answer = await bridge.askUser(question, options);
44
-
45
- return { content: answer };
46
- },
47
- };
@@ -1,137 +0,0 @@
1
- import { SandboxManager } from "../sandbox/manager.js";
2
- import { LazyInstaller } from "../sandbox/bootstrap.js";
3
- import { DynamicToolInterface, ToolResult } from "./index.js";
4
-
5
- // ─── Sandbox + Installer references ─────────────────────────────────────────
6
-
7
- let _sandboxManager: SandboxManager | null = null;
8
- let _installer: LazyInstaller | null = null;
9
-
10
- export function bindBrowserSandbox(
11
- sandbox: SandboxManager,
12
- installer: LazyInstaller
13
- ): void {
14
- _sandboxManager = sandbox;
15
- _installer = installer;
16
- }
17
-
18
- // ─── Helpers ────────────────────────────────────────────────────────────────────
19
-
20
- /**
21
- * Escapes a string so it can be safely used as an argument in a Bash shell command.
22
- * It wraps the string in single quotes and safely escapes internal single quotes.
23
- */
24
- function escapeBashArg(arg: string): string {
25
- return `'${arg.replace(/'/g, "'\\''")}'`;
26
- }
27
-
28
- // ─── BrowserTool ────────────────────────────────────────────────────────────────
29
-
30
- /**
31
- * Web Browser Tool — wraps Vercel Labs' `agent-browser` CLI.
32
- *
33
- * Provides compact accessibility-tree output optimized for LLMs
34
- * (low token usage vs raw HTML). Runs inside the E2B sandbox.
35
- *
36
- * Supported actions:
37
- * - navigate: Go to a URL
38
- * - snapshot: Get the accessibility tree (compact text representation)
39
- * - click: Click an element by ref
40
- * - type: Type text into a form field by ref
41
- * - screenshot: Capture a screenshot
42
- * - scroll: Scroll the page up or down
43
- */
44
- export const BrowserTool: DynamicToolInterface = {
45
- name: "browser",
46
- description:
47
- "Interact with web pages using a headless browser. Actions: navigate, snapshot, click, type, screenshot, scroll. " +
48
- "Returns compact accessibility-tree text output optimized for AI consumption.",
49
- schema: {
50
- type: "object",
51
- properties: {
52
- action: {
53
- type: "string",
54
- enum: ["navigate", "snapshot", "click", "type", "screenshot", "scroll"],
55
- description: "The browser action to perform",
56
- },
57
- url: {
58
- type: "string",
59
- description: "URL to navigate to (required for 'navigate')",
60
- },
61
- ref: {
62
- type: "string",
63
- description:
64
- "Element reference from the accessibility tree (required for 'click' and 'type')",
65
- },
66
- text: {
67
- type: "string",
68
- description: "Text to type (required for 'type')",
69
- },
70
- direction: {
71
- type: "string",
72
- enum: ["up", "down"],
73
- description: "Scroll direction for 'scroll' action (optional, defaults to 'down')",
74
- }, },
75
- required: ["action"],
76
- },
77
- execute: async (args: {
78
- action: string;
79
- url?: string;
80
- ref?: string;
81
- text?: string;
82
- direction?: string;
83
- }): Promise<ToolResult> => {
84
- if (!_sandboxManager || !_sandboxManager.isActive()) {
85
- return { content: "Sandbox is not active. Cannot use browser tool.", isError: true };
86
- }
87
-
88
- // Build the CLI command
89
- let command: string;
90
-
91
- switch (args.action) {
92
- case "navigate":
93
- if (!args.url) return { content: "Error: 'url' is required for navigate action.", isError: true };
94
- command = `agent-browser navigate ${escapeBashArg(args.url)} 2>&1`;
95
- break;
96
-
97
- case "snapshot":
98
- command = "agent-browser snapshot 2>&1";
99
- break;
100
-
101
- case "click":
102
- if (!args.ref) return { content: "Error: 'ref' is required for click action.", isError: true };
103
- command = `agent-browser click ${escapeBashArg(args.ref)} 2>&1`;
104
- break;
105
-
106
- case "type":
107
- if (!args.ref) return { content: "Error: 'ref' is required for type action.", isError: true };
108
- if (!args.text) return { content: "Error: 'text' is required for type action.", isError: true };
109
- command = `agent-browser type ${escapeBashArg(args.ref)} ${escapeBashArg(args.text)} 2>&1`;
110
- break;
111
-
112
- case "screenshot":
113
- command = "agent-browser screenshot 2>&1";
114
- break;
115
-
116
- case "scroll":
117
- const dir = args.direction || "down";
118
- command = `agent-browser scroll ${escapeBashArg(dir)} 2>&1`;
119
- break;
120
-
121
- default:
122
- return { content: `Error: Unknown action "${args.action}". Use: navigate, snapshot, click, type, screenshot, scroll.`, isError: true };
123
- }
124
-
125
- const result = await _sandboxManager.exec(command);
126
-
127
- if (result.exitCode !== 0) {
128
- return {
129
- content: `Browser action failed (exit code ${result.exitCode}):\n${result.stdout}\n${result.stderr}`,
130
- metadata: { exitCode: result.exitCode },
131
- isError: true
132
- };
133
- }
134
-
135
- return { content: result.stdout || "(no output)", metadata: { exitCode: result.exitCode }, isError: false };
136
- },
137
- };
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CACpD;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,oBActB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,oBAc1B,CAAC;AAEF,eAAO,MAAM,UAAU,wBAA2B,CAAC"}
@@ -1,237 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { SandboxManager } from "../sandbox/manager.js";
4
- import { FileSync } from "../sandbox/sync.js";
5
-
6
- export interface ToolResult {
7
- content: string;
8
- metadata?: Record<string, any>;
9
- isError?: boolean;
10
- }
11
-
12
- export interface DynamicToolInterface {
13
- name: string;
14
- description: string;
15
- schema: Record<string, any>;
16
- execute: (args: any) => Promise<ToolResult> | ToolResult;
17
- }
18
-
19
- // ─── Configuration ──────────────────────────────────────────────────────────────
20
-
21
- /** Maximum file size (in bytes) the agent is allowed to read into context. */
22
- const MAX_FILE_SIZE_BYTES = 512 * 1024; // 512 KB
23
-
24
- /** Maximum number of lines to return if file exceeds line limit. */
25
- const MAX_FILE_LINES = 2000;
26
-
27
- // ─── Sandbox reference (set at session start) ───────────────────────────────────
28
-
29
- let _sandboxManager: SandboxManager | null = null;
30
- let _fileSync: FileSync | null = null;
31
-
32
- /**
33
- * Binds the tools to a SandboxManager and FileSync instance.
34
- * Must be called at session start before any tool executions.
35
- */
36
- export function bindSandbox(sandbox: SandboxManager, fileSync: FileSync): void {
37
- _sandboxManager = sandbox;
38
- _fileSync = fileSync;
39
- }
40
-
41
- /**
42
- * Security: Validates that a resolved path is strictly inside the given workspace dir.
43
- * Prevents directory traversal attacks from accessing sensitive host files.
44
- */
45
- export function isPathInsideWorkspace(
46
- resolvedPath: string,
47
- workspaceDir = process.cwd()
48
- ): boolean {
49
- const normalizedWorkspace = path.resolve(workspaceDir);
50
- // path.relative returns a path relative to the first argument.
51
- // If it starts with '..' or is absolute, it means resolvedPath has escaped normalizedWorkspace.
52
- const relative = path.relative(normalizedWorkspace, resolvedPath);
53
- return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
54
- }
55
-
56
- // ─── BashTool ───────────────────────────────────────────────────────────────────
57
- // Executes shell commands inside the E2B sandbox.
58
- // The ToolRouter routes this to SANDBOX — the host machine is never exposed.
59
-
60
- export const BashTool: DynamicToolInterface = {
61
- name: "bash",
62
- description:
63
- "Runs a shell command inside an isolated sandbox. Use for tests, scripts, or installing dependencies. The host machine is never exposed.",
64
- schema: {
65
- type: "object",
66
- properties: {
67
- command: {
68
- type: "string",
69
- description: "The shell command to execute",
70
- },
71
- },
72
- required: ["command"],
73
- },
74
- execute: async (args: { command: string }): Promise<ToolResult> => {
75
- if (!_sandboxManager || !_sandboxManager.isActive()) {
76
- throw new Error(
77
- "Sandbox is not active. Cannot execute bash commands without an active sandbox session."
78
- );
79
- }
80
-
81
- // Sync any dirty files from host → sandbox before executing
82
- if (_fileSync && _fileSync.pendingCount() > 0) {
83
- await _fileSync.syncToSandbox(_sandboxManager);
84
- }
85
-
86
- const result = await _sandboxManager.exec(args.command);
87
-
88
- if (result.exitCode !== 0) {
89
- return {
90
- content: `Command failed (exit code ${result.exitCode}):\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`,
91
- metadata: { exitCode: result.exitCode },
92
- isError: true
93
- };
94
- }
95
-
96
- return {
97
- content: result.stdout || "(no output)",
98
- metadata: { exitCode: result.exitCode },
99
- isError: false
100
- };
101
- },
102
- };
103
-
104
- // ─── ReadFileTool ───────────────────────────────────────────────────────────────
105
- // Reads files from the HOST filesystem (so the user's real project is visible).
106
- // Includes a built-in file size guardrail to prevent sending huge files to the LLM.
107
-
108
- export const ReadFileTool: DynamicToolInterface = {
109
- name: "read_file",
110
- description:
111
- "Reads a file from the host filesystem. Includes a file size guardrail — files over 512KB are truncated to prevent context overflow.",
112
- schema: {
113
- type: "object",
114
- properties: {
115
- path: {
116
- type: "string",
117
- description: "Absolute or relative path to the file",
118
- },
119
- startLine: {
120
- type: "number",
121
- description: "Optional 1-indexed start line for partial reads",
122
- },
123
- endLine: {
124
- type: "number",
125
- description: "Optional 1-indexed end line for partial reads",
126
- },
127
- },
128
- required: ["path"],
129
- },
130
- execute: async (args: { path: string; startLine?: number; endLine?: number }): Promise<ToolResult> => {
131
- const filePath = path.resolve(args.path);
132
-
133
- // ── Security Guardrail ──
134
- if (!isPathInsideWorkspace(filePath)) {
135
- return {
136
- content: `Security Error: Access Denied. Cannot read files outside the current project workspace (${process.cwd()}).`,
137
- isError: true
138
- };
139
- }
140
-
141
- // ── Check existence ──
142
- if (!fs.existsSync(filePath)) {
143
- return {
144
- content: `Error: File not found — ${filePath}`,
145
- isError: true
146
- };
147
- }
148
-
149
- // ── File Size Guardrail ──
150
- const stat = fs.statSync(filePath);
151
- if (stat.size > MAX_FILE_SIZE_BYTES) {
152
- return {
153
- content: `Error: File too large (${Math.round(stat.size / 1024)}KB). Maximum size is 512KB. Use line ranges or grep_search.`,
154
- isError: true
155
- };
156
- }
157
-
158
- const fileContent = fs.readFileSync(filePath, "utf-8");
159
- const lines = fileContent.split("\n");
160
-
161
- if (args.startLine !== undefined || args.endLine !== undefined) {
162
- const start = Math.max(1, args.startLine ?? 1) - 1;
163
- const end = Math.min(lines.length, args.endLine ?? lines.length);
164
- const sliced = lines.slice(start, end);
165
- return { content: sliced.map((line, i) => `${start + i + 1}: ${line}`).join("\n") };
166
- }
167
-
168
- // ── Line count guardrail ──
169
- if (lines.length > MAX_FILE_LINES) {
170
- const truncated = lines.slice(0, MAX_FILE_LINES);
171
- return {
172
- content: truncated.map((line, i) => `${i + 1}: ${line}`).join("\n") +
173
- `\n\n--- Truncated at ${MAX_FILE_LINES} lines (total: ${lines.length}) ---`
174
- };
175
- }
176
-
177
- return { content: fileContent };
178
- },
179
- };
180
-
181
- // ─── WriteFileTool ──────────────────────────────────────────────────────────────
182
- // Writes files to the HOST filesystem (so the user sees changes in their IDE).
183
- // Marks written files as dirty so FileSync uploads them before sandbox execution.
184
-
185
- export const WriteFileTool: DynamicToolInterface = {
186
- name: "write_file",
187
- description:
188
- "Writes content to a file on the host filesystem. The user will see changes in their IDE immediately. The file is automatically synced to the sandbox before the next command execution.",
189
- schema: {
190
- type: "object",
191
- properties: {
192
- path: {
193
- type: "string",
194
- description: "Absolute or relative path to the file",
195
- },
196
- content: {
197
- type: "string",
198
- description: "The full file content to write",
199
- },
200
- },
201
- required: ["path", "content"],
202
- },
203
- execute: async (args: { path: string; content: string }): Promise<ToolResult> => {
204
- const filePath = path.resolve(args.path);
205
-
206
- // ── Security Guardrail ──
207
- if (!isPathInsideWorkspace(filePath)) {
208
- return {
209
- content: `Security Error: Access Denied. Cannot write files outside the current project workspace (${process.cwd()}).`,
210
- isError: true
211
- };
212
- }
213
-
214
- // Create parent directories if needed
215
- const dir = path.dirname(filePath);
216
- if (!fs.existsSync(dir)) {
217
- fs.mkdirSync(dir, { recursive: true });
218
- }
219
-
220
- fs.writeFileSync(filePath, args.content, "utf-8");
221
-
222
- // Mark file as dirty for next sandbox sync
223
- if (_fileSync) {
224
- _fileSync.markDirty(filePath);
225
- }
226
-
227
- return { content: `File written: ${filePath}` };
228
- },
229
- };
230
-
231
- // ─── Core Tool Set ──────────────────────────────────────────────────────────────
232
-
233
- export const CORE_TOOLS: DynamicToolInterface[] = [
234
- BashTool,
235
- ReadFileTool,
236
- WriteFileTool,
237
- ];