@tyvm/knowhow 0.0.108 → 0.0.109-dev.2b94ba2

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 (236) hide show
  1. package/README.md +45 -0
  2. package/package.json +9 -4
  3. package/scripts/build-for-node.sh +10 -24
  4. package/scripts/publish.sh +86 -0
  5. package/src/agents/base/base.ts +10 -0
  6. package/src/agents/tools/execCommand.ts +49 -6
  7. package/src/agents/tools/index.ts +0 -1
  8. package/src/agents/tools/list.ts +2 -4
  9. package/src/chat/CliChatService.ts +11 -2
  10. package/src/chat/modules/AgentModule.ts +61 -31
  11. package/src/chat/modules/SessionsModule.ts +47 -3
  12. package/src/chat/modules/SystemModule.ts +2 -2
  13. package/src/chat/renderer/CompactRenderer.ts +20 -0
  14. package/src/chat/renderer/ConsoleRenderer.ts +19 -0
  15. package/src/chat/renderer/FancyRenderer.ts +19 -0
  16. package/src/chat/renderer/types.ts +11 -0
  17. package/src/cli.ts +91 -659
  18. package/src/clients/anthropic.ts +18 -17
  19. package/src/clients/index.ts +31 -11
  20. package/src/clients/openai.ts +8 -5
  21. package/src/clients/types.ts +48 -10
  22. package/src/clients/withRetry.ts +89 -0
  23. package/src/cloudWorker.ts +175 -113
  24. package/src/commands/agent.ts +246 -0
  25. package/src/commands/misc.ts +174 -0
  26. package/src/commands/modules.ts +552 -0
  27. package/src/commands/services.ts +77 -0
  28. package/src/commands/workers.ts +168 -0
  29. package/src/config.ts +38 -1
  30. package/src/fileSync.ts +70 -29
  31. package/src/hashes.ts +35 -13
  32. package/src/index.ts +18 -0
  33. package/src/logger.ts +197 -0
  34. package/src/plugins/embedding.ts +11 -6
  35. package/src/plugins/plugins.ts +0 -21
  36. package/src/plugins/vim.ts +5 -16
  37. package/src/processors/JsonCompressor.ts +6 -6
  38. package/src/services/EventService.ts +61 -1
  39. package/src/services/KnowhowClient.ts +34 -4
  40. package/src/services/MediaProcessorService.ts +79 -10
  41. package/src/services/modules/index.ts +102 -53
  42. package/src/services/modules/types.ts +6 -0
  43. package/src/tunnel.ts +216 -0
  44. package/src/types.ts +0 -1
  45. package/src/worker.ts +105 -312
  46. package/src/workers/auth/WsMiddleware.ts +99 -0
  47. package/src/workers/auth/authMiddleware.ts +104 -0
  48. package/src/workers/auth/types.ts +14 -2
  49. package/src/workers/tools/index.ts +2 -0
  50. package/src/workers/tools/reloadConfig.ts +84 -0
  51. package/tests/services/WorkerReloadConfig.test.ts +141 -0
  52. package/tests/unit/clients/AIClient.test.ts +446 -0
  53. package/tests/unit/clients/withRetry.test.ts +319 -0
  54. package/tests/unit/commands/github-credentials.test.ts +210 -0
  55. package/tests/unit/modules/moduleLoading.test.ts +39 -37
  56. package/tests/unit/plugins/pluginLoading.test.ts +0 -85
  57. package/ts_build/package.json +9 -4
  58. package/ts_build/src/agents/base/base.js +11 -0
  59. package/ts_build/src/agents/base/base.js.map +1 -1
  60. package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
  61. package/ts_build/src/agents/tools/execCommand.js +39 -5
  62. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  63. package/ts_build/src/agents/tools/index.d.ts +0 -1
  64. package/ts_build/src/agents/tools/index.js +0 -1
  65. package/ts_build/src/agents/tools/index.js.map +1 -1
  66. package/ts_build/src/agents/tools/list.js +2 -4
  67. package/ts_build/src/agents/tools/list.js.map +1 -1
  68. package/ts_build/src/chat/CliChatService.js +14 -2
  69. package/ts_build/src/chat/CliChatService.js.map +1 -1
  70. package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
  71. package/ts_build/src/chat/modules/AgentModule.js +43 -20
  72. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  73. package/ts_build/src/chat/modules/SessionsModule.js +37 -3
  74. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  75. package/ts_build/src/chat/modules/SystemModule.js +2 -2
  76. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  77. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
  78. package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
  79. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
  80. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
  81. package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
  82. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
  83. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
  84. package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
  85. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
  86. package/ts_build/src/chat/renderer/types.d.ts +2 -0
  87. package/ts_build/src/cli.js +47 -519
  88. package/ts_build/src/cli.js.map +1 -1
  89. package/ts_build/src/clients/anthropic.d.ts +5 -5
  90. package/ts_build/src/clients/anthropic.js +18 -17
  91. package/ts_build/src/clients/anthropic.js.map +1 -1
  92. package/ts_build/src/clients/index.js +9 -10
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/openai.js +4 -4
  95. package/ts_build/src/clients/openai.js.map +1 -1
  96. package/ts_build/src/clients/types.d.ts +15 -8
  97. package/ts_build/src/clients/withRetry.d.ts +2 -0
  98. package/ts_build/src/clients/withRetry.js +60 -0
  99. package/ts_build/src/clients/withRetry.js.map +1 -0
  100. package/ts_build/src/cloudWorker.d.ts +14 -0
  101. package/ts_build/src/cloudWorker.js +105 -66
  102. package/ts_build/src/cloudWorker.js.map +1 -1
  103. package/ts_build/src/commands/agent.d.ts +6 -0
  104. package/ts_build/src/commands/agent.js +229 -0
  105. package/ts_build/src/commands/agent.js.map +1 -0
  106. package/ts_build/src/commands/misc.d.ts +10 -0
  107. package/ts_build/src/commands/misc.js +197 -0
  108. package/ts_build/src/commands/misc.js.map +1 -0
  109. package/ts_build/src/commands/modules.d.ts +3 -0
  110. package/ts_build/src/commands/modules.js +487 -0
  111. package/ts_build/src/commands/modules.js.map +1 -0
  112. package/ts_build/src/commands/services.d.ts +5 -0
  113. package/ts_build/src/commands/services.js +87 -0
  114. package/ts_build/src/commands/services.js.map +1 -0
  115. package/ts_build/src/commands/workers.d.ts +6 -0
  116. package/ts_build/src/commands/workers.js +168 -0
  117. package/ts_build/src/commands/workers.js.map +1 -0
  118. package/ts_build/src/config.d.ts +1 -0
  119. package/ts_build/src/config.js +33 -1
  120. package/ts_build/src/config.js.map +1 -1
  121. package/ts_build/src/fileSync.d.ts +6 -0
  122. package/ts_build/src/fileSync.js +50 -23
  123. package/ts_build/src/fileSync.js.map +1 -1
  124. package/ts_build/src/hashes.d.ts +2 -2
  125. package/ts_build/src/hashes.js +35 -9
  126. package/ts_build/src/hashes.js.map +1 -1
  127. package/ts_build/src/index.d.ts +1 -0
  128. package/ts_build/src/index.js +17 -1
  129. package/ts_build/src/index.js.map +1 -1
  130. package/ts_build/src/logger.d.ts +21 -0
  131. package/ts_build/src/logger.js +106 -0
  132. package/ts_build/src/logger.js.map +1 -0
  133. package/ts_build/src/plugins/embedding.js +4 -3
  134. package/ts_build/src/plugins/embedding.js.map +1 -1
  135. package/ts_build/src/plugins/plugins.d.ts +0 -2
  136. package/ts_build/src/plugins/plugins.js +0 -11
  137. package/ts_build/src/plugins/plugins.js.map +1 -1
  138. package/ts_build/src/plugins/vim.js +3 -9
  139. package/ts_build/src/plugins/vim.js.map +1 -1
  140. package/ts_build/src/processors/JsonCompressor.js +4 -4
  141. package/ts_build/src/processors/JsonCompressor.js.map +1 -1
  142. package/ts_build/src/services/EventService.d.ts +6 -1
  143. package/ts_build/src/services/EventService.js +29 -0
  144. package/ts_build/src/services/EventService.js.map +1 -1
  145. package/ts_build/src/services/KnowhowClient.d.ts +13 -1
  146. package/ts_build/src/services/KnowhowClient.js +19 -2
  147. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  148. package/ts_build/src/services/MediaProcessorService.d.ts +5 -4
  149. package/ts_build/src/services/MediaProcessorService.js +53 -8
  150. package/ts_build/src/services/MediaProcessorService.js.map +1 -1
  151. package/ts_build/src/services/modules/index.d.ts +33 -0
  152. package/ts_build/src/services/modules/index.js +73 -49
  153. package/ts_build/src/services/modules/index.js.map +1 -1
  154. package/ts_build/src/services/modules/types.d.ts +6 -0
  155. package/ts_build/src/tunnel.d.ts +27 -0
  156. package/ts_build/src/tunnel.js +112 -0
  157. package/ts_build/src/tunnel.js.map +1 -0
  158. package/ts_build/src/types.d.ts +0 -1
  159. package/ts_build/src/types.js.map +1 -1
  160. package/ts_build/src/worker.d.ts +1 -4
  161. package/ts_build/src/worker.js +59 -227
  162. package/ts_build/src/worker.js.map +1 -1
  163. package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
  164. package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
  165. package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
  166. package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
  167. package/ts_build/src/workers/auth/authMiddleware.js +60 -0
  168. package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
  169. package/ts_build/src/workers/auth/types.d.ts +8 -1
  170. package/ts_build/src/workers/tools/index.d.ts +2 -0
  171. package/ts_build/src/workers/tools/index.js +4 -1
  172. package/ts_build/src/workers/tools/index.js.map +1 -1
  173. package/ts_build/src/workers/tools/reloadConfig.d.ts +14 -0
  174. package/ts_build/src/workers/tools/reloadConfig.js +48 -0
  175. package/ts_build/src/workers/tools/reloadConfig.js.map +1 -0
  176. package/ts_build/tests/services/WorkerReloadConfig.test.d.ts +1 -0
  177. package/ts_build/tests/services/WorkerReloadConfig.test.js +86 -0
  178. package/ts_build/tests/services/WorkerReloadConfig.test.js.map +1 -0
  179. package/ts_build/tests/unit/clients/AIClient.test.d.ts +1 -0
  180. package/ts_build/tests/unit/clients/AIClient.test.js +339 -0
  181. package/ts_build/tests/unit/clients/AIClient.test.js.map +1 -0
  182. package/ts_build/tests/unit/clients/withRetry.test.d.ts +1 -0
  183. package/ts_build/tests/unit/clients/withRetry.test.js +225 -0
  184. package/ts_build/tests/unit/clients/withRetry.test.js.map +1 -0
  185. package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
  186. package/ts_build/tests/unit/commands/github-credentials.test.js +145 -0
  187. package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
  188. package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
  189. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
  190. package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
  191. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  192. package/src/agents/tools/executeScript/README.md +0 -94
  193. package/src/agents/tools/executeScript/definition.ts +0 -79
  194. package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
  195. package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
  196. package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
  197. package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
  198. package/src/agents/tools/executeScript/index.ts +0 -98
  199. package/src/services/script-execution/SandboxContext.ts +0 -282
  200. package/src/services/script-execution/ScriptExecutor.ts +0 -441
  201. package/src/services/script-execution/ScriptPolicy.ts +0 -194
  202. package/src/services/script-execution/ScriptTracer.ts +0 -249
  203. package/src/services/script-execution/types.ts +0 -134
  204. package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
  205. package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
  206. package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
  207. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
  208. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
  209. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
  210. package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
  211. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
  212. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
  213. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
  214. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
  215. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
  216. package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
  217. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
  218. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
  219. package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
  220. package/ts_build/src/agents/tools/executeScript/index.js +0 -72
  221. package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
  222. package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
  223. package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
  224. package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
  225. package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
  226. package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
  227. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
  228. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
  229. package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
  230. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
  231. package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
  232. package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
  233. package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
  234. package/ts_build/src/services/script-execution/types.d.ts +0 -108
  235. package/ts_build/src/services/script-execution/types.js +0 -3
  236. package/ts_build/src/services/script-execution/types.js.map +0 -1
@@ -1,441 +0,0 @@
1
- import ivm from "isolated-vm";
2
- import { services, ToolsService } from "../../services";
3
- import { AIClient, Clients } from "../../clients";
4
- import { SandboxContext } from "./SandboxContext";
5
- import { ScriptTracer } from "./ScriptTracer";
6
- import { ScriptPolicyEnforcer } from "./ScriptPolicy";
7
- import {
8
- ExecutionRequest,
9
- ExecutionResult,
10
- ResourceQuotas,
11
- SecurityPolicy,
12
- ExecutionTrace,
13
- } from "./types";
14
-
15
- /**
16
- * Executes TypeScript scripts in a secure sandbox environment
17
- */
18
- export class ScriptExecutor {
19
- private defaultQuotas: ResourceQuotas = {
20
- maxToolCalls: 50,
21
- maxTokens: 10000,
22
- maxExecutionTimeMs: 30000, // 30 seconds
23
- maxCostUsd: 1.0,
24
- maxMemoryMb: 100,
25
- };
26
-
27
- private defaultPolicy: SecurityPolicy = {
28
- allowlistedTools: [], // Empty means all tools allowed
29
- denylistedTools: [
30
- "executeScript", // Circular script execution
31
- "execCommand", // Dangerous system commands
32
- "writeFileChunk", // File system write access
33
- "patchFile", // File system modification
34
- ],
35
- maxScriptLength: 50000, // 50KB
36
- allowNetworkAccess: false,
37
- allowFileSystemAccess: false,
38
- };
39
-
40
- constructor(private toolsService: ToolsService, private clients: AIClient) {
41
- this.validateNodejsEnvironment();
42
- }
43
-
44
- /**
45
- * Validate that Node.js environment is properly configured for isolated-vm
46
- */
47
- private validateNodejsEnvironment(): void {
48
- // Get Node.js version
49
- const nodeVersion = process.version;
50
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10);
51
-
52
- // Check if Node.js 20+ and --no-node-snapshot flag is required
53
- if (majorVersion >= 20) {
54
- const hasNoNodeSnapshot = process.execArgv.includes('--no-node-snapshot');
55
-
56
- if (!hasNoNodeSnapshot) {
57
- const errorMessage = [
58
- `Node.js ${nodeVersion} detected. The executeScript tool requires the --no-node-snapshot flag for isolated-vm compatibility.`,
59
- '',
60
- 'This flag is automatically included when running knowhow commands via the CLI (e.g., `knowhow agent`, `knowhow chat`).',
61
- '',
62
- 'If you are programmatically using knowhow or running custom scripts:',
63
- '1. Start your application with: node --no-node-snapshot your-app.js',
64
- '2. Or update your package.json scripts to include the flag:',
65
- ' "scripts": {',
66
- ' "start": "node --no-node-snapshot dist/index.js"',
67
- ' }',
68
- '',
69
- 'Note: This flag is required for Node.js 20+ to ensure isolated-vm works correctly.'
70
- ].join('\n');
71
-
72
- throw new Error(errorMessage);
73
- }
74
- }
75
- }
76
-
77
- /**
78
- * Execute a TypeScript script in sandbox
79
- */
80
- async execute(request: ExecutionRequest): Promise<ExecutionResult> {
81
- const tracer = new ScriptTracer();
82
- const quotas = { ...this.defaultQuotas, ...request.quotas };
83
- const policy = { ...this.defaultPolicy, ...request.policy };
84
- const policyEnforcer = new ScriptPolicyEnforcer(quotas, policy);
85
-
86
- tracer.emitEvent("execution_start", {
87
- scriptLength: request.script.length,
88
- quotas,
89
- policy: {
90
- ...policy,
91
- // Don't log the full tool lists
92
- allowlistedTools: `${policy.allowlistedTools.length} tools`,
93
- denylistedTools: `${policy.denylistedTools.length} tools`,
94
- },
95
- });
96
-
97
- try {
98
- // Validate script
99
- const validation = policyEnforcer.validateScript(request.script, policy.allowNetworkAccess);
100
- if (!validation.valid) {
101
- tracer.emitEvent("script_validation_failed", {
102
- issues: validation.issues,
103
- });
104
-
105
- return {
106
- success: false,
107
- error: `Script validation failed: ${validation.issues.join(", ")}`,
108
- result: null,
109
- trace: tracer.getTrace(),
110
- artifacts: [],
111
- consoleOutput: [],
112
- };
113
- }
114
-
115
- tracer.emitEvent("script_validation_passed", {});
116
-
117
- // Create sandbox context
118
- const context = new SandboxContext(
119
- this.toolsService,
120
- this.clients,
121
- tracer,
122
- policyEnforcer
123
- );
124
-
125
- // Execute script with timeout
126
- const startTime = Date.now();
127
- const timeoutMs = quotas.maxExecutionTimeMs;
128
-
129
- const result = await this.executeWithTimeout(
130
- request.script,
131
- context,
132
- timeoutMs,
133
- tracer,
134
- policyEnforcer
135
- );
136
-
137
- const executionTime = Date.now() - startTime;
138
- tracer.emitEvent("execution_complete", {
139
- executionTimeMs: executionTime,
140
- finalUsage: policyEnforcer.getUsage(),
141
- });
142
-
143
- return {
144
- success: true,
145
- error: null,
146
- result,
147
- trace: tracer.getTrace(),
148
- artifacts: context.getArtifacts(),
149
- consoleOutput: context.getConsoleOutput(),
150
- };
151
- } catch (error) {
152
- const errorMessage =
153
- error instanceof Error ? error.message : String(error);
154
-
155
- tracer.emitEvent("execution_error", {
156
- error: errorMessage,
157
- finalUsage: policyEnforcer.getUsage(),
158
- });
159
-
160
- return {
161
- success: false,
162
- error: errorMessage,
163
- result: null,
164
- trace: tracer.getTrace(),
165
- artifacts: [],
166
- consoleOutput: [],
167
- };
168
- }
169
- }
170
-
171
- /**
172
- * Execute script with timeout protection
173
- */
174
- private async executeWithTimeout(
175
- script: string,
176
- context: SandboxContext,
177
- timeoutMs: number,
178
- tracer: ScriptTracer,
179
- policyEnforcer: ScriptPolicyEnforcer
180
- ): Promise<any> {
181
- return new Promise((resolve, reject) => {
182
- const timeoutId = setTimeout(() => {
183
- tracer.emitEvent("execution_timeout", { timeoutMs });
184
- reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
185
- }, timeoutMs);
186
-
187
- // Use isolated-vm for secure execution
188
- this.executeScriptSecure(script, context, tracer, policyEnforcer)
189
- .then((result) => {
190
- clearTimeout(timeoutId);
191
- resolve(result);
192
- })
193
- .catch((error) => {
194
- clearTimeout(timeoutId);
195
- reject(error);
196
- });
197
- });
198
- }
199
-
200
- /**
201
- * Secure script execution using isolated-vm
202
- */
203
- private async executeScriptSecure(
204
- script: string,
205
- context: SandboxContext,
206
- tracer: ScriptTracer,
207
- policyEnforcer: ScriptPolicyEnforcer
208
- ) {
209
- tracer.emitEvent("secure_execution_start", {
210
- note: "Using isolated-vm for secure execution",
211
- });
212
-
213
- // Create isolated VM instance with memory limit
214
- const isolate = new ivm.Isolate({
215
- memoryLimit: policyEnforcer.getQuotas().maxMemoryMb,
216
- });
217
-
218
- try {
219
- // Create new context within the isolate
220
- const vmContext = await isolate.createContext();
221
-
222
- tracer.emitEvent("vm_context_created", {});
223
-
224
- // Set up the global environment in the isolated context
225
- await this.setupIsolatedContext(vmContext, context, tracer);
226
-
227
- tracer.emitEvent("script_compilation_start", {});
228
-
229
- // Compile the script.
230
- // Many scripts follow the pattern: define functions, then call `main();` at the end.
231
- // The IIFE wrapper captures the *return value* of the function body, but `main();`
232
- // is an expression statement — it doesn't return anything from the IIFE.
233
- // We fix this by rewriting the last bare expression-statement into `return <expr>;`
234
- // so that the script's return value (e.g. the object from main()) is captured.
235
- const scriptWithReturn = this.injectReturnForLastExpression(script);
236
-
237
- const wrappedScript = `
238
- (async function() {
239
- "use strict";
240
- ${scriptWithReturn}
241
- })()
242
- `;
243
-
244
- const compiledScript = await isolate.compileScript(wrappedScript);
245
-
246
- tracer.emitEvent("script_compilation_complete", {});
247
- tracer.emitEvent("script_execution_start", {});
248
-
249
- // Execute the script and get the result
250
- // Note: do NOT set timeout here — it kills the isolate while awaiting host async promises.
251
- // The outer executeWithTimeout wrapper handles wall-clock timeout instead.
252
- const result = await compiledScript.run(vmContext, {
253
- promise: true,
254
- copy: true,
255
- });
256
-
257
- tracer.emitEvent("script_execution_complete", {
258
- resultType: typeof result,
259
- });
260
-
261
- return result;
262
- } finally {
263
- // Clean up the isolate
264
- isolate.dispose();
265
- tracer.emitEvent("vm_cleanup_complete", {});
266
- }
267
- }
268
-
269
- /**
270
- * Set up the isolated context with safe globals and sandbox functions
271
- */
272
- private async setupIsolatedContext(
273
- vmContext: ivm.Context,
274
- sandboxContext: SandboxContext,
275
- tracer: ScriptTracer
276
- ): Promise<void> {
277
- tracer.emitEvent("context_setup_start", {});
278
-
279
- const globalRef = vmContext.global;
280
- await globalRef.set("globalThis", globalRef.derefInto());
281
-
282
- // Helper function to expose async host functions
283
- const exposeAsync = async (
284
- name: string,
285
- fn: (...a: any[]) => Promise<any>
286
- ) => {
287
- await globalRef.set(
288
- `__host_${name}`,
289
- new ivm.Reference(async (...args: any[]) => {
290
- const result = await fn(...args);
291
- const safeResult = result !== undefined ? result : null;
292
- const plainResult =
293
- safeResult !== null && typeof safeResult === 'object'
294
- ? JSON.parse(JSON.stringify(safeResult))
295
- : safeResult;
296
- // copyInto() transfers the value into the isolate heap so it's directly usable
297
- return new ivm.ExternalCopy(plainResult).copyInto();
298
- })
299
- );
300
- // Use applySyncPromise so the script isolate suspends and yields the Node.js event loop
301
- // back to the host while waiting for async host operations (MCP stdio calls etc.).
302
- // Without this, the ivm isolate blocks the event loop and stdio-based MCP transports
303
- // can never deliver their responses → deadlock.
304
- await vmContext.eval(`
305
- globalThis.${name} = (...a) =>
306
- new Promise((resolve, reject) => {
307
- try {
308
- // applySyncPromise does not support result options — the Reference fn returns ExternalCopy
309
- const result = __host_${name}.applySyncPromise(undefined, a,
310
- { arguments: { copy: true } });
311
- resolve(result);
312
- } catch(e) { reject(e); }
313
- });
314
- `);
315
- };
316
-
317
- // Helper function to expose sync host functions
318
- const exposeSync = async (name: string, fn: (...a: any[]) => any) => {
319
- await globalRef.set(
320
- `__host_${name}`,
321
- new ivm.Reference((...args: any[]) => {
322
- const result = fn(...args);
323
- return new ivm.ExternalCopy(result).copyInto();
324
- })
325
- );
326
- await vmContext.eval(`
327
- globalThis.${name} = (...a) =>
328
- __host_${name}.apply(undefined, a,
329
- { arguments: { copy: true }, result: { copy: true } });
330
- `);
331
- };
332
-
333
- // Expose async sandbox functions
334
- await exposeAsync("callTool", async (tool, params) => {
335
- try {
336
- const result = await sandboxContext.callTool(tool as string, params);
337
- const { functionResp } = result;
338
- return functionResp !== undefined ? functionResp : null;
339
- } catch (err) {
340
- throw err;
341
- }
342
- });
343
- await exposeAsync("llm", (messages, options) =>
344
- sandboxContext.llm(messages, options || {})
345
- );
346
- await exposeAsync("sleep", (ms) => sandboxContext.sleep(ms));
347
-
348
- // Expose sync sandbox functions
349
- await exposeSync("createArtifact", (name, content, type) =>
350
- sandboxContext.createArtifact(name as string, content, type)
351
- );
352
- await exposeSync("getQuotaUsage", () => sandboxContext.getQuotaUsage());
353
-
354
- // Set up console bridging with individual function references
355
- for (const level of ["log", "info", "warn", "error"] as const) {
356
- await globalRef.set(
357
- `__console_${level}`,
358
- new ivm.Reference((...args: any[]) =>
359
- sandboxContext.console[level](...args)
360
- )
361
- );
362
- }
363
- await vmContext.eval(`
364
- globalThis.console = {};
365
- for (const lvl of ["log", "info", "warn", "error"]) {
366
- globalThis.console[lvl] = (...a) =>
367
- globalThis["__console_" + lvl].apply(undefined, a,
368
- { arguments: { copy: true } });
369
- }
370
- `);
371
-
372
- tracer.emitEvent("context_setup_complete", {});
373
- }
374
-
375
- /**
376
- * Legacy fallback execution method
377
- */
378
- private async executeScriptFallback(
379
- script: string,
380
- context: SandboxContext,
381
- tracer: ScriptTracer,
382
- policyEnforcer: ScriptPolicyEnforcer
383
- ): Promise<any> {
384
- // This is a fallback method that could use vm2 or other sandboxing
385
- throw new Error("Isolated-vm execution failed, no fallback available");
386
- }
387
-
388
- /**
389
- * Get default quotas
390
- */
391
- getDefaultQuotas(): ResourceQuotas {
392
- return { ...this.defaultQuotas };
393
- }
394
-
395
- /**
396
- * Get default policy
397
- */
398
- getDefaultPolicy(): SecurityPolicy {
399
- return { ...this.defaultPolicy };
400
- }
401
-
402
-
403
- /**
404
- * Rewrite the last bare expression-statement in a script to use `return` so
405
- * that the value propagates out of the IIFE wrapper.
406
- *
407
- * For example:
408
- * main(); → return main();
409
- * main() → return main()
410
- * someExpr; → return someExpr;
411
- *
412
- * We only transform the last non-empty, non-comment line that looks like a
413
- * plain expression statement (i.e. does NOT start with keywords that aren't
414
- * valid in expression position: `function`, `class`, `const`, `let`, `var`,
415
- * `if`, `for`, `while`, `do`, `switch`, `try`, `return`, `throw`, `break`,
416
- * `continue`, `import`, `export`, `{`).
417
- */
418
- private injectReturnForLastExpression(script: string): string {
419
- const lines = script.split('\n');
420
-
421
- // Walk backwards to find the last non-empty, non-comment line
422
- for (let i = lines.length - 1; i >= 0; i--) {
423
- const trimmed = lines[i].trim();
424
- if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
425
- continue;
426
- }
427
-
428
- // Skip lines that start with statement keywords — these can't be returned
429
- const statementKeywords = /^(function\s|class\s|const\s|let\s|var\s|if\s*[(]|for\s*[(]|while\s*[(]|do\s*[{]|switch\s*[(]|try\s*[{]|return\s|throw\s|break;|continue;|import\s|export\s|[{])/;
430
- if (statementKeywords.test(trimmed)) {
431
- break; // last meaningful line is a statement — don't touch it
432
- }
433
-
434
- // It looks like an expression statement — prepend `return`
435
- lines[i] = lines[i].replace(trimmed, `return ${trimmed}`);
436
- return lines.join('\n');
437
- }
438
-
439
- // No suitable last expression found — return script unchanged
440
- return script;
441
- }}
@@ -1,194 +0,0 @@
1
- import {
2
- ResourceQuotas,
3
- SecurityPolicy,
4
- QuotaUsage,
5
- PolicyViolation
6
- } from './types';
7
-
8
- /**
9
- * Enforces security policies and resource quotas for script execution
10
- */
11
- export class ScriptPolicyEnforcer {
12
- private usage: QuotaUsage;
13
- private violations: PolicyViolation[] = [];
14
- private complexityLimit: number = 150; // Arbitrary limit for script complexity
15
-
16
- constructor(
17
- private quotas: ResourceQuotas,
18
- private policy: SecurityPolicy,
19
- ) {
20
- this.usage = {
21
- toolCalls: 0,
22
- tokens: 0,
23
- executionTimeMs: 0,
24
- costUsd: 0
25
- };
26
- }
27
-
28
- /**
29
- * Check if a tool call is allowed
30
- */
31
- checkToolCall(toolName: string): boolean {
32
- // Check if tool is in denylist
33
- if (this.policy.denylistedTools && this.policy.denylistedTools.includes(toolName)) {
34
- this.recordViolation('tool_denied', `Tool '${toolName}' is in denylist`);
35
- return false;
36
- }
37
-
38
- // Check if tool is in allowlist (if allowlist is defined and not empty)
39
- if (this.policy.allowlistedTools && this.policy.allowlistedTools.length > 0 &&
40
- !this.policy.allowlistedTools.includes(toolName)) {
41
- this.recordViolation('tool_not_allowed', `Tool '${toolName}' is not in allowlist`);
42
- return false;
43
- }
44
-
45
- // Check quota
46
- if (this.usage.toolCalls >= this.quotas.maxToolCalls) {
47
- this.recordViolation('quota_exceeded', 'Maximum tool calls exceeded');
48
- return false;
49
- }
50
-
51
- return true;
52
- }
53
-
54
- /**
55
- * Record a tool call
56
- */
57
- recordToolCall(): void {
58
- this.usage.toolCalls++;
59
- }
60
-
61
- /**
62
- * Check if token usage is allowed
63
- */
64
- checkTokenUsage(tokens: number): boolean {
65
- if (this.usage.tokens + tokens > this.quotas.maxTokens) {
66
- this.recordViolation('quota_exceeded', 'Maximum tokens would be exceeded');
67
- return false;
68
- }
69
- return true;
70
- }
71
-
72
- /**
73
- * Record token usage
74
- */
75
- recordTokenUsage(tokens: number): void {
76
- this.usage.tokens += tokens;
77
- }
78
-
79
- /**
80
- * Check if execution time limit is exceeded
81
- */
82
- checkExecutionTime(currentTimeMs: number): boolean {
83
- if (currentTimeMs > this.quotas.maxExecutionTimeMs) {
84
- this.recordViolation('quota_exceeded', 'Maximum execution time exceeded');
85
- return false;
86
- }
87
- this.usage.executionTimeMs = currentTimeMs;
88
- return true;
89
- }
90
-
91
- /**
92
- * Check if cost limit is exceeded
93
- */
94
- checkCost(additionalCost: number): boolean {
95
- if (this.usage.costUsd + additionalCost > this.quotas.maxCostUsd) {
96
- this.recordViolation('quota_exceeded', 'Maximum cost would be exceeded');
97
- return false;
98
- }
99
- return true;
100
- }
101
-
102
- /**
103
- * Record cost usage
104
- */
105
- recordCost(cost: number): void {
106
- this.usage.costUsd += cost;
107
- }
108
-
109
- /**
110
- * Get current usage
111
- */
112
- getUsage(): QuotaUsage {
113
- return { ...this.usage };
114
- }
115
-
116
- /**
117
- * Get current quotas
118
- */
119
- getQuotas(): ResourceQuotas {
120
- return { ...this.quotas };
121
- }
122
-
123
- /**
124
- * Get all policy violations
125
- */
126
- getViolations(): PolicyViolation[] {
127
- return [...this.violations];
128
- }
129
-
130
- /**
131
- * Check if there are any violations
132
- */
133
- hasViolations(): boolean {
134
- return this.violations.length > 0;
135
- }
136
-
137
- /**
138
- * Get the most recent violation
139
- */
140
- getLastViolation(): PolicyViolation | undefined {
141
- return this.violations[this.violations.length - 1];
142
- }
143
-
144
- /**
145
- * Reset usage counters
146
- */
147
- resetUsage(): void {
148
- this.usage = {
149
- toolCalls: 0,
150
- tokens: 0,
151
- executionTimeMs: 0,
152
- costUsd: 0
153
- };
154
- }
155
-
156
- /**
157
- * Reset violations
158
- */
159
- resetViolations(): void {
160
- this.violations = [];
161
- }
162
-
163
- /**
164
- * Validate script content for security issues
165
- */
166
- validateScript(scriptContent: string, allowNetworkAccess?: boolean): { valid: boolean; issues: string[] } {
167
- const issues: string[] = [];
168
-
169
- // Check script length
170
- if (scriptContent.length > this.policy.maxScriptLength) {
171
- issues.push(`Script too long: ${scriptContent.length} > ${this.policy.maxScriptLength}`);
172
- }
173
-
174
- return {
175
- valid: issues.length === 0,
176
- issues
177
- };
178
- }
179
-
180
- /**
181
- * Record a policy violation
182
- */
183
- private recordViolation(type: 'quota_exceeded' | 'tool_denied' | 'tool_not_allowed' | 'script_validation', message: string): void {
184
- const violation: PolicyViolation = {
185
- id: `violation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
186
- type,
187
- message,
188
- timestamp: Date.now(),
189
- usage: { ...this.usage }
190
- };
191
-
192
- this.violations.push(violation);
193
- }
194
- }