@yuaone/core 0.1.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 (235) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +15 -0
  3. package/dist/__tests__/context-manager.test.d.ts +6 -0
  4. package/dist/__tests__/context-manager.test.d.ts.map +1 -0
  5. package/dist/__tests__/context-manager.test.js +220 -0
  6. package/dist/__tests__/context-manager.test.js.map +1 -0
  7. package/dist/__tests__/governor.test.d.ts +6 -0
  8. package/dist/__tests__/governor.test.d.ts.map +1 -0
  9. package/dist/__tests__/governor.test.js +210 -0
  10. package/dist/__tests__/governor.test.js.map +1 -0
  11. package/dist/__tests__/model-router.test.d.ts +6 -0
  12. package/dist/__tests__/model-router.test.d.ts.map +1 -0
  13. package/dist/__tests__/model-router.test.js +329 -0
  14. package/dist/__tests__/model-router.test.js.map +1 -0
  15. package/dist/agent-logger.d.ts +384 -0
  16. package/dist/agent-logger.d.ts.map +1 -0
  17. package/dist/agent-logger.js +820 -0
  18. package/dist/agent-logger.js.map +1 -0
  19. package/dist/agent-loop.d.ts +163 -0
  20. package/dist/agent-loop.d.ts.map +1 -0
  21. package/dist/agent-loop.js +609 -0
  22. package/dist/agent-loop.js.map +1 -0
  23. package/dist/agent-modes.d.ts +85 -0
  24. package/dist/agent-modes.d.ts.map +1 -0
  25. package/dist/agent-modes.js +418 -0
  26. package/dist/agent-modes.js.map +1 -0
  27. package/dist/approval.d.ts +137 -0
  28. package/dist/approval.d.ts.map +1 -0
  29. package/dist/approval.js +299 -0
  30. package/dist/approval.js.map +1 -0
  31. package/dist/async-completion-queue.d.ts +56 -0
  32. package/dist/async-completion-queue.d.ts.map +1 -0
  33. package/dist/async-completion-queue.js +77 -0
  34. package/dist/async-completion-queue.js.map +1 -0
  35. package/dist/auto-fix.d.ts +174 -0
  36. package/dist/auto-fix.d.ts.map +1 -0
  37. package/dist/auto-fix.js +319 -0
  38. package/dist/auto-fix.js.map +1 -0
  39. package/dist/codebase-context.d.ts +396 -0
  40. package/dist/codebase-context.d.ts.map +1 -0
  41. package/dist/codebase-context.js +1260 -0
  42. package/dist/codebase-context.js.map +1 -0
  43. package/dist/conflict-resolver.d.ts +191 -0
  44. package/dist/conflict-resolver.d.ts.map +1 -0
  45. package/dist/conflict-resolver.js +524 -0
  46. package/dist/conflict-resolver.js.map +1 -0
  47. package/dist/constants.d.ts +52 -0
  48. package/dist/constants.d.ts.map +1 -0
  49. package/dist/constants.js +141 -0
  50. package/dist/constants.js.map +1 -0
  51. package/dist/context-budget.d.ts +435 -0
  52. package/dist/context-budget.d.ts.map +1 -0
  53. package/dist/context-budget.js +903 -0
  54. package/dist/context-budget.js.map +1 -0
  55. package/dist/context-compressor.d.ts +143 -0
  56. package/dist/context-compressor.d.ts.map +1 -0
  57. package/dist/context-compressor.js +511 -0
  58. package/dist/context-compressor.js.map +1 -0
  59. package/dist/context-manager.d.ts +112 -0
  60. package/dist/context-manager.d.ts.map +1 -0
  61. package/dist/context-manager.js +247 -0
  62. package/dist/context-manager.js.map +1 -0
  63. package/dist/continuous-reflection.d.ts +267 -0
  64. package/dist/continuous-reflection.d.ts.map +1 -0
  65. package/dist/continuous-reflection.js +338 -0
  66. package/dist/continuous-reflection.js.map +1 -0
  67. package/dist/cross-file-refactor.d.ts +352 -0
  68. package/dist/cross-file-refactor.d.ts.map +1 -0
  69. package/dist/cross-file-refactor.js +1544 -0
  70. package/dist/cross-file-refactor.js.map +1 -0
  71. package/dist/dag-orchestrator.d.ts +138 -0
  72. package/dist/dag-orchestrator.d.ts.map +1 -0
  73. package/dist/dag-orchestrator.js +379 -0
  74. package/dist/dag-orchestrator.js.map +1 -0
  75. package/dist/debate-orchestrator.d.ts +301 -0
  76. package/dist/debate-orchestrator.d.ts.map +1 -0
  77. package/dist/debate-orchestrator.js +719 -0
  78. package/dist/debate-orchestrator.js.map +1 -0
  79. package/dist/dependency-analyzer.d.ts +113 -0
  80. package/dist/dependency-analyzer.d.ts.map +1 -0
  81. package/dist/dependency-analyzer.js +444 -0
  82. package/dist/dependency-analyzer.js.map +1 -0
  83. package/dist/design-loop.d.ts +59 -0
  84. package/dist/design-loop.d.ts.map +1 -0
  85. package/dist/design-loop.js +344 -0
  86. package/dist/design-loop.js.map +1 -0
  87. package/dist/doc-intelligence.d.ts +383 -0
  88. package/dist/doc-intelligence.d.ts.map +1 -0
  89. package/dist/doc-intelligence.js +1307 -0
  90. package/dist/doc-intelligence.js.map +1 -0
  91. package/dist/dynamic-role-generator.d.ts +76 -0
  92. package/dist/dynamic-role-generator.d.ts.map +1 -0
  93. package/dist/dynamic-role-generator.js +194 -0
  94. package/dist/dynamic-role-generator.js.map +1 -0
  95. package/dist/errors.d.ts +69 -0
  96. package/dist/errors.d.ts.map +1 -0
  97. package/dist/errors.js +102 -0
  98. package/dist/errors.js.map +1 -0
  99. package/dist/event-bus.d.ts +159 -0
  100. package/dist/event-bus.d.ts.map +1 -0
  101. package/dist/event-bus.js +305 -0
  102. package/dist/event-bus.js.map +1 -0
  103. package/dist/execution-engine.d.ts +425 -0
  104. package/dist/execution-engine.d.ts.map +1 -0
  105. package/dist/execution-engine.js +1555 -0
  106. package/dist/execution-engine.js.map +1 -0
  107. package/dist/git-intelligence.d.ts +306 -0
  108. package/dist/git-intelligence.d.ts.map +1 -0
  109. package/dist/git-intelligence.js +1099 -0
  110. package/dist/git-intelligence.js.map +1 -0
  111. package/dist/governor.d.ts +77 -0
  112. package/dist/governor.d.ts.map +1 -0
  113. package/dist/governor.js +161 -0
  114. package/dist/governor.js.map +1 -0
  115. package/dist/hierarchical-planner.d.ts +313 -0
  116. package/dist/hierarchical-planner.d.ts.map +1 -0
  117. package/dist/hierarchical-planner.js +981 -0
  118. package/dist/hierarchical-planner.js.map +1 -0
  119. package/dist/index.d.ts +121 -0
  120. package/dist/index.d.ts.map +1 -0
  121. package/dist/index.js +123 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/intent-inference.d.ts +103 -0
  124. package/dist/intent-inference.d.ts.map +1 -0
  125. package/dist/intent-inference.js +605 -0
  126. package/dist/intent-inference.js.map +1 -0
  127. package/dist/interrupt-manager.d.ts +143 -0
  128. package/dist/interrupt-manager.d.ts.map +1 -0
  129. package/dist/interrupt-manager.js +196 -0
  130. package/dist/interrupt-manager.js.map +1 -0
  131. package/dist/kernel.d.ts +564 -0
  132. package/dist/kernel.d.ts.map +1 -0
  133. package/dist/kernel.js +1419 -0
  134. package/dist/kernel.js.map +1 -0
  135. package/dist/language-support.d.ts +232 -0
  136. package/dist/language-support.d.ts.map +1 -0
  137. package/dist/language-support.js +1134 -0
  138. package/dist/language-support.js.map +1 -0
  139. package/dist/llm-client.d.ts +82 -0
  140. package/dist/llm-client.d.ts.map +1 -0
  141. package/dist/llm-client.js +475 -0
  142. package/dist/llm-client.js.map +1 -0
  143. package/dist/mcp-client.d.ts +232 -0
  144. package/dist/mcp-client.d.ts.map +1 -0
  145. package/dist/mcp-client.js +718 -0
  146. package/dist/mcp-client.js.map +1 -0
  147. package/dist/memory-manager.d.ts +200 -0
  148. package/dist/memory-manager.d.ts.map +1 -0
  149. package/dist/memory-manager.js +568 -0
  150. package/dist/memory-manager.js.map +1 -0
  151. package/dist/memory.d.ts +87 -0
  152. package/dist/memory.d.ts.map +1 -0
  153. package/dist/memory.js +341 -0
  154. package/dist/memory.js.map +1 -0
  155. package/dist/model-router.d.ts +245 -0
  156. package/dist/model-router.d.ts.map +1 -0
  157. package/dist/model-router.js +632 -0
  158. package/dist/model-router.js.map +1 -0
  159. package/dist/parallel-executor.d.ts +125 -0
  160. package/dist/parallel-executor.d.ts.map +1 -0
  161. package/dist/parallel-executor.js +201 -0
  162. package/dist/parallel-executor.js.map +1 -0
  163. package/dist/perf-optimizer.d.ts +212 -0
  164. package/dist/perf-optimizer.d.ts.map +1 -0
  165. package/dist/perf-optimizer.js +721 -0
  166. package/dist/perf-optimizer.js.map +1 -0
  167. package/dist/persona.d.ts +305 -0
  168. package/dist/persona.d.ts.map +1 -0
  169. package/dist/persona.js +887 -0
  170. package/dist/persona.js.map +1 -0
  171. package/dist/planner.d.ts +70 -0
  172. package/dist/planner.d.ts.map +1 -0
  173. package/dist/planner.js +264 -0
  174. package/dist/planner.js.map +1 -0
  175. package/dist/qa-pipeline.d.ts +365 -0
  176. package/dist/qa-pipeline.d.ts.map +1 -0
  177. package/dist/qa-pipeline.js +1352 -0
  178. package/dist/qa-pipeline.js.map +1 -0
  179. package/dist/reasoning-adapter.d.ts +116 -0
  180. package/dist/reasoning-adapter.d.ts.map +1 -0
  181. package/dist/reasoning-adapter.js +187 -0
  182. package/dist/reasoning-adapter.js.map +1 -0
  183. package/dist/role-registry.d.ts +55 -0
  184. package/dist/role-registry.d.ts.map +1 -0
  185. package/dist/role-registry.js +192 -0
  186. package/dist/role-registry.js.map +1 -0
  187. package/dist/sandbox-tiers.d.ts +327 -0
  188. package/dist/sandbox-tiers.d.ts.map +1 -0
  189. package/dist/sandbox-tiers.js +928 -0
  190. package/dist/sandbox-tiers.js.map +1 -0
  191. package/dist/security-scanner.d.ts +222 -0
  192. package/dist/security-scanner.d.ts.map +1 -0
  193. package/dist/security-scanner.js +1129 -0
  194. package/dist/security-scanner.js.map +1 -0
  195. package/dist/security.d.ts +93 -0
  196. package/dist/security.d.ts.map +1 -0
  197. package/dist/security.js +393 -0
  198. package/dist/security.js.map +1 -0
  199. package/dist/self-reflection.d.ts +397 -0
  200. package/dist/self-reflection.d.ts.map +1 -0
  201. package/dist/self-reflection.js +908 -0
  202. package/dist/self-reflection.js.map +1 -0
  203. package/dist/session-persistence.d.ts +191 -0
  204. package/dist/session-persistence.d.ts.map +1 -0
  205. package/dist/session-persistence.js +395 -0
  206. package/dist/session-persistence.js.map +1 -0
  207. package/dist/speculative-executor.d.ts +210 -0
  208. package/dist/speculative-executor.d.ts.map +1 -0
  209. package/dist/speculative-executor.js +618 -0
  210. package/dist/speculative-executor.js.map +1 -0
  211. package/dist/state-machine.d.ts +289 -0
  212. package/dist/state-machine.d.ts.map +1 -0
  213. package/dist/state-machine.js +695 -0
  214. package/dist/state-machine.js.map +1 -0
  215. package/dist/sub-agent.d.ts +177 -0
  216. package/dist/sub-agent.d.ts.map +1 -0
  217. package/dist/sub-agent.js +303 -0
  218. package/dist/sub-agent.js.map +1 -0
  219. package/dist/system-prompt.d.ts +26 -0
  220. package/dist/system-prompt.d.ts.map +1 -0
  221. package/dist/system-prompt.js +84 -0
  222. package/dist/system-prompt.js.map +1 -0
  223. package/dist/test-intelligence.d.ts +439 -0
  224. package/dist/test-intelligence.d.ts.map +1 -0
  225. package/dist/test-intelligence.js +1165 -0
  226. package/dist/test-intelligence.js.map +1 -0
  227. package/dist/types.d.ts +632 -0
  228. package/dist/types.d.ts.map +1 -0
  229. package/dist/types.js +6 -0
  230. package/dist/types.js.map +1 -0
  231. package/dist/vector-index.d.ts +314 -0
  232. package/dist/vector-index.d.ts.map +1 -0
  233. package/dist/vector-index.js +618 -0
  234. package/dist/vector-index.js.map +1 -0
  235. package/package.json +41 -0
@@ -0,0 +1,928 @@
1
+ /**
2
+ * @module sandbox-tiers
3
+ * @description YUAN Agent Sandbox Execution Tiers (T0–T4).
4
+ *
5
+ * 5 levels of isolation based on task risk level:
6
+ * - T0: Read-Only — file read, grep, glob only
7
+ * - T1: Write-Restricted — T0 + specific file writes, no network
8
+ * - T2: Project-Scoped — full project read/write, limited shell
9
+ * - T3: Build-Enabled — T2 + npm/pnpm, localhost network
10
+ * - T4: Full-Network — T3 + external network (allowlist)
11
+ *
12
+ * The SandboxManager auto-selects a tier based on requested tools,
13
+ * target files, and shell commands, then validates every action
14
+ * against the tier's policy before allowing execution.
15
+ */
16
+ import { EventEmitter } from "node:events";
17
+ import path from "node:path";
18
+ // ══════════════════════════════════════════════════════════════════════
19
+ // Constants
20
+ // ══════════════════════════════════════════════════════════════════════
21
+ /** Maximum violation records kept in memory */
22
+ const MAX_VIOLATIONS = 500;
23
+ /** Maximum escalation history entries */
24
+ const MAX_ESCALATION_HISTORY = 100;
25
+ /** Tools that only read data */
26
+ const READ_ONLY_TOOLS = new Set([
27
+ "file_read",
28
+ "grep",
29
+ "glob",
30
+ "codebase_search",
31
+ "list_directory",
32
+ ]);
33
+ /** Tools that write files */
34
+ const WRITE_TOOLS = new Set(["file_write", "file_edit", "file_create"]);
35
+ /** Tools that delete files */
36
+ const DELETE_TOOLS = new Set(["file_delete"]);
37
+ /** Tools that execute shell commands */
38
+ const SHELL_TOOLS = new Set(["shell_exec", "shell_command", "bash"]);
39
+ /** Build-related command patterns */
40
+ const BUILD_COMMANDS = new Set([
41
+ "npm",
42
+ "pnpm",
43
+ "yarn",
44
+ "npx",
45
+ "tsc",
46
+ "make",
47
+ "cmake",
48
+ "cargo",
49
+ "go",
50
+ "gradle",
51
+ "mvn",
52
+ "pip",
53
+ "poetry",
54
+ ]);
55
+ /** Network-related command patterns */
56
+ const NETWORK_COMMANDS = new Set([
57
+ "curl",
58
+ "wget",
59
+ "fetch",
60
+ "http",
61
+ "ssh",
62
+ "scp",
63
+ "rsync",
64
+ "ftp",
65
+ ]);
66
+ /** Package install patterns (regex) */
67
+ const PACKAGE_INSTALL_PATTERNS = [
68
+ /^npm\s+install/,
69
+ /^npm\s+i\b/,
70
+ /^pnpm\s+add/,
71
+ /^pnpm\s+install/,
72
+ /^yarn\s+add/,
73
+ /^pip\s+install/,
74
+ /^cargo\s+install/,
75
+ /^go\s+get/,
76
+ ];
77
+ /** Default file size limit: 10 MB */
78
+ const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
79
+ // ══════════════════════════════════════════════════════════════════════
80
+ // SandboxManager
81
+ // ══════════════════════════════════════════════════════════════════════
82
+ /**
83
+ * SandboxManager — manages execution isolation tiers for the YUAN agent.
84
+ *
85
+ * Provides 5 tiers of isolation (T0–T4), auto-selects the appropriate tier
86
+ * based on requested tools and commands, and validates every action against
87
+ * the active tier's policy before allowing execution.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * const sandbox = new SandboxManager({ projectPath: "/my/project" });
92
+ *
93
+ * // Auto-select tier
94
+ * const decision = sandbox.selectTier(["file_read", "file_write"], ["src/app.ts"]);
95
+ * // => { tier: 1, reason: "file write required", ... }
96
+ *
97
+ * // Validate actions
98
+ * const { allowed } = sandbox.canWriteFile("src/app.ts");
99
+ * ```
100
+ */
101
+ export class SandboxManager extends EventEmitter {
102
+ config;
103
+ tiers;
104
+ state;
105
+ constructor(config) {
106
+ super();
107
+ this.config = {
108
+ projectPath: path.resolve(config.projectPath),
109
+ defaultTier: config.defaultTier ?? 2,
110
+ maxTier: config.maxTier ?? 3,
111
+ enableAutoEscalation: config.enableAutoEscalation ?? false,
112
+ auditLog: config.auditLog ?? true,
113
+ };
114
+ this.tiers = this.buildDefaultTiers();
115
+ this.state = {
116
+ currentTier: this.config.defaultTier,
117
+ sessionId: "",
118
+ writeCount: 0,
119
+ shellCount: 0,
120
+ violations: [],
121
+ escalationHistory: [],
122
+ };
123
+ }
124
+ // ──────────────────────────────────────────────────────────────────
125
+ // Tier Selection
126
+ // ──────────────────────────────────────────────────────────────────
127
+ /**
128
+ * Auto-select the appropriate sandbox tier based on requested tools,
129
+ * target files, and shell commands.
130
+ *
131
+ * @param tools - List of tool names that will be used
132
+ * @param targetFiles - List of file paths that may be modified
133
+ * @param shellCommands - Optional list of shell commands to execute
134
+ * @returns Decision with selected tier, reason, and influencing factors
135
+ */
136
+ selectTier(tools, targetFiles, shellCommands) {
137
+ let tier = 0;
138
+ const factors = [];
139
+ const toolSet = new Set(tools);
140
+ const commands = shellCommands ?? [];
141
+ // File write needed?
142
+ const hasWriteTools = tools.some((t) => WRITE_TOOLS.has(t));
143
+ if (hasWriteTools) {
144
+ tier = Math.max(tier, 1);
145
+ factors.push("file write required");
146
+ if (targetFiles.length > 5) {
147
+ tier = Math.max(tier, 2);
148
+ factors.push(`many files to modify (${targetFiles.length})`);
149
+ }
150
+ }
151
+ // File delete needed?
152
+ const hasDeleteTools = tools.some((t) => DELETE_TOOLS.has(t));
153
+ if (hasDeleteTools) {
154
+ tier = Math.max(tier, 2);
155
+ factors.push("file deletion required");
156
+ }
157
+ // Shell needed?
158
+ const hasShellTools = tools.some((t) => SHELL_TOOLS.has(t));
159
+ if (hasShellTools) {
160
+ tier = Math.max(tier, 2);
161
+ factors.push("shell execution required");
162
+ // Check for build commands
163
+ for (const cmd of commands) {
164
+ const executable = this.extractCommand(cmd);
165
+ if (this.isBuildCommand(executable)) {
166
+ tier = Math.max(tier, 3);
167
+ factors.push(`build command detected: ${executable}`);
168
+ break;
169
+ }
170
+ }
171
+ // Check for network commands
172
+ for (const cmd of commands) {
173
+ const executable = this.extractCommand(cmd);
174
+ if (this.isNetworkCommand(executable)) {
175
+ tier = Math.max(tier, 4);
176
+ factors.push(`network command detected: ${executable}`);
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ // Package install?
182
+ if (hasShellTools) {
183
+ for (const cmd of commands) {
184
+ if (PACKAGE_INSTALL_PATTERNS.some((p) => p.test(cmd.trim()))) {
185
+ tier = Math.max(tier, 4);
186
+ factors.push("package installation requires network");
187
+ break;
188
+ }
189
+ }
190
+ }
191
+ // Cap at maxTier
192
+ const uncapped = tier;
193
+ tier = Math.min(tier, this.config.maxTier);
194
+ if (tier < uncapped) {
195
+ factors.push(`capped from T${uncapped} to T${tier} by maxTier config`);
196
+ }
197
+ const policy = this.tiers.get(tier);
198
+ const reason = factors.length > 0
199
+ ? `T${tier} (${policy.name}): ${factors[0]}`
200
+ : `T${tier} (${policy.name}): read-only access sufficient`;
201
+ // Set the tier
202
+ this.setTier(tier, reason);
203
+ return {
204
+ tier,
205
+ reason,
206
+ factors,
207
+ overrideable: tier < 4,
208
+ };
209
+ }
210
+ /**
211
+ * Manually set the sandbox tier.
212
+ *
213
+ * @param tier - Target tier level
214
+ * @param reason - Reason for the tier change
215
+ * @throws If tier exceeds maxTier
216
+ */
217
+ setTier(tier, reason) {
218
+ if (tier > this.config.maxTier) {
219
+ this.recordViolation("setTier", `T${tier}`, `tier ${tier} exceeds maxTier ${this.config.maxTier}`, true);
220
+ return;
221
+ }
222
+ const prev = this.state.currentTier;
223
+ if (prev !== tier) {
224
+ this.state.currentTier = tier;
225
+ this.emit("tier:changed", prev, tier, reason);
226
+ }
227
+ }
228
+ /**
229
+ * Escalate to the next higher tier.
230
+ *
231
+ * @param reason - Why escalation is needed
232
+ * @returns true if escalation succeeded, false if already at maxTier
233
+ */
234
+ escalate(reason) {
235
+ if (!this.config.enableAutoEscalation) {
236
+ this.recordViolation("escalate", `T${this.state.currentTier}→T${(this.state.currentTier + 1)}`, "auto-escalation disabled", true);
237
+ return false;
238
+ }
239
+ const current = this.state.currentTier;
240
+ if (current >= this.config.maxTier) {
241
+ return false;
242
+ }
243
+ const next = (current + 1);
244
+ // Cap escalation history
245
+ if (this.state.escalationHistory.length >= MAX_ESCALATION_HISTORY) {
246
+ this.state.escalationHistory = this.state.escalationHistory.slice(-Math.floor(MAX_ESCALATION_HISTORY / 2));
247
+ }
248
+ this.state.escalationHistory.push({
249
+ from: current,
250
+ to: next,
251
+ reason,
252
+ timestamp: Date.now(),
253
+ });
254
+ this.state.currentTier = next;
255
+ this.emit("tier:changed", current, next, reason);
256
+ this.emit("escalation", current, next, reason);
257
+ return true;
258
+ }
259
+ /** Get the current active tier */
260
+ getCurrentTier() {
261
+ return this.state.currentTier;
262
+ }
263
+ /**
264
+ * Get the policy for a specific tier, or the current tier if omitted.
265
+ *
266
+ * @param tier - Tier to get policy for (defaults to current)
267
+ */
268
+ getTierPolicy(tier) {
269
+ const t = tier ?? this.state.currentTier;
270
+ const policy = this.tiers.get(t);
271
+ if (!policy) {
272
+ throw new Error(`Unknown sandbox tier: ${t}`);
273
+ }
274
+ return policy;
275
+ }
276
+ // ──────────────────────────────────────────────────────────────────
277
+ // Validation
278
+ // ──────────────────────────────────────────────────────────────────
279
+ /**
280
+ * Check if reading a file is allowed under the current tier.
281
+ *
282
+ * @param filePath - Absolute or relative file path
283
+ * @returns true if the read is allowed
284
+ */
285
+ canReadFile(filePath) {
286
+ const policy = this.getTierPolicy();
287
+ if (!policy.fileRead) {
288
+ this.recordViolation("file_read", filePath, "file reads not allowed at this tier", true);
289
+ return false;
290
+ }
291
+ // All tiers that allow reads can read any file in the project
292
+ const normalized = this.normalizePath(filePath);
293
+ if (!normalized.startsWith(this.config.projectPath)) {
294
+ this.recordViolation("file_read", filePath, "path is outside project directory", true);
295
+ return false;
296
+ }
297
+ return true;
298
+ }
299
+ /**
300
+ * Check if writing a file is allowed under the current tier.
301
+ *
302
+ * @param filePath - Absolute or relative file path
303
+ * @returns Object with allowed status and optional reason
304
+ */
305
+ canWriteFile(filePath) {
306
+ const policy = this.getTierPolicy();
307
+ if (!policy.fileWrite) {
308
+ const reason = "file writes not allowed at this tier";
309
+ this.recordViolation("file_write", filePath, reason, true);
310
+ return { allowed: false, reason };
311
+ }
312
+ // Check write count limit
313
+ if (this.state.writeCount >= policy.maxTotalWrites) {
314
+ const reason = `write limit reached (${policy.maxTotalWrites})`;
315
+ this.recordViolation("file_write", filePath, reason, true);
316
+ return { allowed: false, reason };
317
+ }
318
+ const normalized = this.normalizePath(filePath);
319
+ const relative = this.toRelative(normalized);
320
+ // Must be within project
321
+ if (!normalized.startsWith(this.config.projectPath)) {
322
+ const reason = "path is outside project directory";
323
+ this.recordViolation("file_write", filePath, reason, true);
324
+ return { allowed: false, reason };
325
+ }
326
+ // Check blocked paths
327
+ if (this.matchesPattern(relative, policy.blockedWritePaths)) {
328
+ const reason = `path matches blocked pattern`;
329
+ this.recordViolation("file_write", filePath, reason, true);
330
+ return { allowed: false, reason };
331
+ }
332
+ // Check allowed paths (if restricted)
333
+ if (policy.allowedWritePaths.length > 0 &&
334
+ !policy.allowedWritePaths.includes("**")) {
335
+ if (!this.matchesPattern(relative, policy.allowedWritePaths)) {
336
+ const reason = "path not in allowed write paths";
337
+ this.recordViolation("file_write", filePath, reason, true);
338
+ return { allowed: false, reason };
339
+ }
340
+ }
341
+ // Note: writeCount is incremented here. Callers should only call
342
+ // canWriteFile() when they intend to actually perform the write.
343
+ // For preview/validation, use validateToolCall() which does not
344
+ // increment counters directly.
345
+ this.state.writeCount++;
346
+ return { allowed: true };
347
+ }
348
+ /**
349
+ * Check if writing a file would be allowed WITHOUT incrementing counters.
350
+ * Use this for preview/validation — unlike canWriteFile, it has no side effects.
351
+ */
352
+ checkWriteFile(filePath) {
353
+ const policy = this.getTierPolicy();
354
+ if (!policy.fileWrite)
355
+ return { allowed: false, reason: "file writes not allowed at this tier" };
356
+ if (this.state.writeCount >= policy.maxTotalWrites)
357
+ return { allowed: false, reason: `write limit reached (${policy.maxTotalWrites})` };
358
+ const normalized = this.normalizePath(filePath);
359
+ if (!normalized.startsWith(this.config.projectPath))
360
+ return { allowed: false, reason: "path is outside project directory" };
361
+ const relative = this.toRelative(normalized);
362
+ if (this.matchesPattern(relative, policy.blockedWritePaths))
363
+ return { allowed: false, reason: "path matches blocked pattern" };
364
+ if (policy.allowedWritePaths.length > 0 && !policy.allowedWritePaths.includes("**")) {
365
+ if (!this.matchesPattern(relative, policy.allowedWritePaths))
366
+ return { allowed: false, reason: "path not in allowed write paths" };
367
+ }
368
+ return { allowed: true };
369
+ }
370
+ /**
371
+ * Check if deleting a file is allowed under the current tier.
372
+ *
373
+ * @param filePath - Absolute or relative file path
374
+ * @returns Object with allowed status and optional reason
375
+ */
376
+ canDeleteFile(filePath) {
377
+ const policy = this.getTierPolicy();
378
+ if (!policy.fileDelete) {
379
+ const reason = "file deletion not allowed at this tier";
380
+ this.recordViolation("file_delete", filePath, reason, true);
381
+ return { allowed: false, reason };
382
+ }
383
+ const normalized = this.normalizePath(filePath);
384
+ const relative = this.toRelative(normalized);
385
+ // Must be within project
386
+ if (!normalized.startsWith(this.config.projectPath)) {
387
+ const reason = "path is outside project directory";
388
+ this.recordViolation("file_delete", filePath, reason, true);
389
+ return { allowed: false, reason };
390
+ }
391
+ // Check blocked paths
392
+ if (this.matchesPattern(relative, policy.blockedWritePaths)) {
393
+ const reason = "path matches blocked pattern";
394
+ this.recordViolation("file_delete", filePath, reason, true);
395
+ return { allowed: false, reason };
396
+ }
397
+ return { allowed: true };
398
+ }
399
+ /**
400
+ * Check if a shell command is allowed under the current tier.
401
+ *
402
+ * @param command - The command string (e.g. "tsc --noEmit")
403
+ * @param args - Optional additional arguments
404
+ * @returns Object with allowed status and optional reason
405
+ */
406
+ canExecuteShell(command, args) {
407
+ const policy = this.getTierPolicy();
408
+ if (!policy.shellExec) {
409
+ const reason = "shell execution not allowed at this tier";
410
+ this.recordViolation("shell_exec", command, reason, true);
411
+ return { allowed: false, reason };
412
+ }
413
+ // Check shell count limit
414
+ if (this.state.shellCount >= policy.maxShellCalls) {
415
+ const reason = `shell call limit reached (${policy.maxShellCalls})`;
416
+ this.recordViolation("shell_exec", command, reason, true);
417
+ return { allowed: false, reason };
418
+ }
419
+ const fullCommand = args ? `${command} ${args.join(" ")}` : command;
420
+ const executable = this.extractCommand(fullCommand);
421
+ // Check blocked commands — defense-in-depth:
422
+ // 1. Exact executable match (after path.basename stripping)
423
+ // 2. Detect shell wrappers (bash -c, sh -c, eval, etc.)
424
+ // 3. Check if blocked command appears as executable in piped/chained commands
425
+ const SHELL_WRAPPERS = new Set(["bash", "sh", "zsh", "dash", "csh", "ksh", "env"]);
426
+ const isShellWrapped = SHELL_WRAPPERS.has(executable) &&
427
+ (fullCommand.includes(" -c ") || fullCommand.includes(" -c\"") || fullCommand.includes(" -c'"));
428
+ for (const blocked of policy.blockedCommands) {
429
+ // Exact executable match
430
+ if (executable === blocked) {
431
+ const reason = `command "${blocked}" is blocked at this tier`;
432
+ this.recordViolation("shell_exec", fullCommand, reason, true);
433
+ return { allowed: false, reason };
434
+ }
435
+ // Shell wrapper detection — block "bash -c 'rm -rf /'" etc.
436
+ if (isShellWrapped && fullCommand.includes(blocked)) {
437
+ const reason = `command "${blocked}" detected inside shell wrapper`;
438
+ this.recordViolation("shell_exec", fullCommand, reason, true);
439
+ return { allowed: false, reason };
440
+ }
441
+ // Check piped/chained commands (|, &&, ;, ||)
442
+ const segments = fullCommand.split(/\s*(?:\|{1,2}|&&|;)\s*/);
443
+ for (const segment of segments) {
444
+ const segCmd = segment.trim().split(/\s+/)[0];
445
+ if (segCmd && path.basename(segCmd) === blocked) {
446
+ const reason = `command "${blocked}" detected in chained command`;
447
+ this.recordViolation("shell_exec", fullCommand, reason, true);
448
+ return { allowed: false, reason };
449
+ }
450
+ }
451
+ }
452
+ // Check allowed commands (if restricted)
453
+ if (policy.allowedCommands.length > 0 &&
454
+ !policy.allowedCommands.includes("*")) {
455
+ if (!policy.allowedCommands.includes(executable)) {
456
+ const reason = `command "${executable}" not in allowed list`;
457
+ this.recordViolation("shell_exec", fullCommand, reason, true);
458
+ return { allowed: false, reason };
459
+ }
460
+ }
461
+ // Increment shell count
462
+ this.state.shellCount++;
463
+ return { allowed: true };
464
+ }
465
+ /**
466
+ * Check if a network request to a specific host is allowed.
467
+ *
468
+ * @param host - The hostname to check
469
+ * @returns Object with allowed status and optional reason
470
+ */
471
+ canAccessNetwork(host) {
472
+ const policy = this.getTierPolicy();
473
+ if (!policy.networkAccess) {
474
+ const reason = "network access not allowed at this tier";
475
+ this.recordViolation("network", host, reason, true);
476
+ return { allowed: false, reason };
477
+ }
478
+ // Strip port from host for comparison
479
+ const hostOnly = host.replace(/:\d+$/, "").toLowerCase();
480
+ // Check blocked hosts — includes subdomain matching
481
+ for (const blocked of policy.blockedHosts) {
482
+ const blockedLower = blocked.toLowerCase();
483
+ if (hostOnly === blockedLower ||
484
+ hostOnly.endsWith("." + blockedLower)) {
485
+ const reason = `host "${host}" is blocked (matches ${blocked})`;
486
+ this.recordViolation("network", host, reason, true);
487
+ return { allowed: false, reason };
488
+ }
489
+ }
490
+ // Block cloud metadata endpoints (AWS, GCP, Azure)
491
+ const METADATA_IPS = ["169.254.169.254", "metadata.google.internal", "100.100.100.200"];
492
+ if (METADATA_IPS.some((ip) => hostOnly === ip || hostOnly.endsWith("." + ip))) {
493
+ const reason = "cloud metadata endpoint blocked";
494
+ this.recordViolation("network", host, reason, true);
495
+ return { allowed: false, reason };
496
+ }
497
+ // Check allowed hosts (if restricted) — includes subdomain matching
498
+ if (policy.allowedHosts.length > 0 &&
499
+ !policy.allowedHosts.includes("*")) {
500
+ const isAllowed = policy.allowedHosts.some((allowed) => {
501
+ const allowedLower = allowed.toLowerCase();
502
+ return hostOnly === allowedLower || hostOnly.endsWith("." + allowedLower);
503
+ });
504
+ if (!isAllowed) {
505
+ const reason = `host "${host}" not in allowed list`;
506
+ this.recordViolation("network", host, reason, true);
507
+ return { allowed: false, reason };
508
+ }
509
+ }
510
+ return { allowed: true };
511
+ }
512
+ /**
513
+ * Validate a tool call against the current tier's policy.
514
+ *
515
+ * @param toolName - Name of the tool being called
516
+ * @param input - Tool input parameters
517
+ * @returns Object with allowed status and list of violations
518
+ */
519
+ validateToolCall(toolName, input) {
520
+ const violations = [];
521
+ // Read tools
522
+ if (READ_ONLY_TOOLS.has(toolName)) {
523
+ const filePath = (input.path ?? input.file_path ?? input.pattern);
524
+ if (filePath && !this.canReadFile(filePath)) {
525
+ violations.push(`file read not allowed: ${filePath}`);
526
+ }
527
+ }
528
+ // Write tools
529
+ if (WRITE_TOOLS.has(toolName)) {
530
+ const filePath = (input.path ?? input.file_path);
531
+ if (filePath) {
532
+ const result = this.canWriteFile(filePath);
533
+ if (!result.allowed) {
534
+ violations.push(`file write blocked: ${result.reason}`);
535
+ }
536
+ }
537
+ // Check file size
538
+ const content = input.content;
539
+ if (content) {
540
+ const policy = this.getTierPolicy();
541
+ const size = Buffer.byteLength(content, "utf-8");
542
+ if (size > policy.maxFileSize) {
543
+ violations.push(`file size ${size} exceeds limit ${policy.maxFileSize}`);
544
+ }
545
+ }
546
+ }
547
+ // Delete tools
548
+ if (DELETE_TOOLS.has(toolName)) {
549
+ const filePath = (input.path ?? input.file_path);
550
+ if (filePath) {
551
+ const result = this.canDeleteFile(filePath);
552
+ if (!result.allowed) {
553
+ violations.push(`file delete blocked: ${result.reason}`);
554
+ }
555
+ }
556
+ }
557
+ // Shell tools
558
+ if (SHELL_TOOLS.has(toolName)) {
559
+ const command = (input.command ?? input.cmd);
560
+ if (command) {
561
+ const result = this.canExecuteShell(command);
562
+ if (!result.allowed) {
563
+ violations.push(`shell exec blocked: ${result.reason}`);
564
+ }
565
+ }
566
+ }
567
+ return {
568
+ allowed: violations.length === 0,
569
+ violations,
570
+ };
571
+ }
572
+ // ──────────────────────────────────────────────────────────────────
573
+ // Monitoring
574
+ // ──────────────────────────────────────────────────────────────────
575
+ /** Get all recorded violations */
576
+ getViolations() {
577
+ return [...this.state.violations];
578
+ }
579
+ /** Get current sandbox state (readonly snapshot) */
580
+ getState() {
581
+ return { ...this.state, violations: [...this.state.violations] };
582
+ }
583
+ /**
584
+ * Reset counters for a new session.
585
+ *
586
+ * @param sessionId - New session identifier
587
+ */
588
+ reset(sessionId) {
589
+ this.state = {
590
+ currentTier: this.config.defaultTier,
591
+ sessionId,
592
+ writeCount: 0,
593
+ shellCount: 0,
594
+ violations: [],
595
+ escalationHistory: [],
596
+ };
597
+ }
598
+ // ──────────────────────────────────────────────────────────────────
599
+ // Private Helpers
600
+ // ──────────────────────────────────────────────────────────────────
601
+ /**
602
+ * Build the default tier policies (T0–T4).
603
+ *
604
+ * @returns Map of tier level to policy definition
605
+ */
606
+ buildDefaultTiers() {
607
+ const tiers = new Map();
608
+ // ── T0: Read-Only ──
609
+ tiers.set(0, {
610
+ tier: 0,
611
+ name: "Read-Only",
612
+ description: "File read, grep, glob only. No writes, no shell, no network.",
613
+ fileRead: true,
614
+ fileWrite: false,
615
+ fileDelete: false,
616
+ allowedWritePaths: [],
617
+ blockedWritePaths: [],
618
+ shellExec: false,
619
+ allowedCommands: [],
620
+ blockedCommands: [],
621
+ maxExecTime: 0,
622
+ networkAccess: false,
623
+ allowedHosts: [],
624
+ blockedHosts: [],
625
+ maxFileSize: DEFAULT_MAX_FILE_SIZE,
626
+ maxTotalWrites: 0,
627
+ maxShellCalls: 0,
628
+ });
629
+ // ── T1: Write-Restricted ──
630
+ tiers.set(1, {
631
+ tier: 1,
632
+ name: "Write-Restricted",
633
+ description: "Read + specific file writes (src/test). No shell, no network.",
634
+ fileRead: true,
635
+ fileWrite: true,
636
+ fileDelete: false,
637
+ allowedWritePaths: ["src/**", "test/**", "tests/**"],
638
+ blockedWritePaths: [
639
+ "**/node_modules/**",
640
+ "**/.env*",
641
+ "**/package-lock.json",
642
+ "**/pnpm-lock.yaml",
643
+ ],
644
+ shellExec: false,
645
+ allowedCommands: [],
646
+ blockedCommands: [],
647
+ maxExecTime: 0,
648
+ networkAccess: false,
649
+ allowedHosts: [],
650
+ blockedHosts: [],
651
+ maxFileSize: DEFAULT_MAX_FILE_SIZE,
652
+ maxTotalWrites: 10,
653
+ maxShellCalls: 0,
654
+ });
655
+ // ── T2: Project-Scoped ──
656
+ tiers.set(2, {
657
+ tier: 2,
658
+ name: "Project-Scoped",
659
+ description: "Full project read/write, limited shell (lint/format), no network.",
660
+ fileRead: true,
661
+ fileWrite: true,
662
+ fileDelete: true,
663
+ allowedWritePaths: ["**"],
664
+ blockedWritePaths: [
665
+ "**/node_modules/**",
666
+ "**/.env*",
667
+ "**/.git/**",
668
+ ],
669
+ shellExec: true,
670
+ allowedCommands: [
671
+ "tsc",
672
+ "eslint",
673
+ "prettier",
674
+ "cat",
675
+ "ls",
676
+ "wc",
677
+ "grep",
678
+ "find",
679
+ ],
680
+ blockedCommands: [
681
+ "rm -rf /",
682
+ "sudo",
683
+ "chmod",
684
+ "chown",
685
+ "kill",
686
+ "pkill",
687
+ "dd",
688
+ "mkfs",
689
+ ],
690
+ maxExecTime: 30_000,
691
+ networkAccess: false,
692
+ allowedHosts: [],
693
+ blockedHosts: [],
694
+ maxFileSize: DEFAULT_MAX_FILE_SIZE,
695
+ maxTotalWrites: 50,
696
+ maxShellCalls: 20,
697
+ });
698
+ // ── T3: Build-Enabled ──
699
+ tiers.set(3, {
700
+ tier: 3,
701
+ name: "Build-Enabled",
702
+ description: "Full project access, all shell (except blocked), localhost + registry network.",
703
+ fileRead: true,
704
+ fileWrite: true,
705
+ fileDelete: true,
706
+ allowedWritePaths: ["**"],
707
+ blockedWritePaths: [
708
+ "**/node_modules/**",
709
+ "**/.env*",
710
+ "**/.git/**",
711
+ ],
712
+ shellExec: true,
713
+ allowedCommands: ["*"],
714
+ blockedCommands: [
715
+ "sudo",
716
+ "chmod 777",
717
+ "rm -rf /",
718
+ "dd",
719
+ "mkfs",
720
+ "curl",
721
+ "wget",
722
+ ],
723
+ maxExecTime: 120_000,
724
+ networkAccess: true,
725
+ allowedHosts: [
726
+ "localhost",
727
+ "127.0.0.1",
728
+ "registry.npmjs.org",
729
+ "registry.yarnpkg.com",
730
+ ],
731
+ blockedHosts: [],
732
+ maxFileSize: DEFAULT_MAX_FILE_SIZE,
733
+ maxTotalWrites: 200,
734
+ maxShellCalls: 50,
735
+ });
736
+ // ── T4: Full-Network ──
737
+ tiers.set(4, {
738
+ tier: 4,
739
+ name: "Full-Network",
740
+ description: "Full access with external network. Cloud metadata endpoints blocked.",
741
+ fileRead: true,
742
+ fileWrite: true,
743
+ fileDelete: true,
744
+ allowedWritePaths: ["**"],
745
+ blockedWritePaths: [
746
+ "**/node_modules/**",
747
+ "**/.env*",
748
+ "**/.git/**",
749
+ ],
750
+ shellExec: true,
751
+ allowedCommands: ["*"],
752
+ blockedCommands: ["sudo", "rm -rf /", "dd", "mkfs"],
753
+ maxExecTime: 300_000,
754
+ networkAccess: true,
755
+ allowedHosts: ["*"],
756
+ blockedHosts: [
757
+ "169.254.169.254",
758
+ "metadata.google.internal",
759
+ ],
760
+ maxFileSize: DEFAULT_MAX_FILE_SIZE,
761
+ maxTotalWrites: 500,
762
+ maxShellCalls: 100,
763
+ });
764
+ return tiers;
765
+ }
766
+ /**
767
+ * Check if a relative path matches any of the given glob patterns.
768
+ * Uses a simplified glob matcher (supports `**`, `*`, and `?`).
769
+ *
770
+ * @param relativePath - Path relative to the project root
771
+ * @param patterns - Glob patterns to match against
772
+ * @returns true if the path matches any pattern
773
+ */
774
+ matchesPattern(relativePath, patterns) {
775
+ for (const pattern of patterns) {
776
+ if (this.globMatch(relativePath, pattern)) {
777
+ return true;
778
+ }
779
+ }
780
+ return false;
781
+ }
782
+ /**
783
+ * Simple glob matcher supporting `**` (any path segments), `*` (any chars
784
+ * within a segment), and `?` (single char).
785
+ *
786
+ * @param str - String to test
787
+ * @param pattern - Glob pattern
788
+ * @returns true if the string matches the pattern
789
+ */
790
+ globMatch(str, pattern) {
791
+ // Convert glob to regex
792
+ let regexStr = "^";
793
+ let i = 0;
794
+ while (i < pattern.length) {
795
+ const char = pattern[i];
796
+ if (char === "*" && pattern[i + 1] === "*") {
797
+ // `**` — match any path segments (including none)
798
+ regexStr += ".*";
799
+ i += 2;
800
+ // Skip trailing slash after **
801
+ if (pattern[i] === "/") {
802
+ i++;
803
+ }
804
+ }
805
+ else if (char === "*") {
806
+ // `*` — match any chars except `/`
807
+ regexStr += "[^/]*";
808
+ i++;
809
+ }
810
+ else if (char === "?") {
811
+ regexStr += "[^/]";
812
+ i++;
813
+ }
814
+ else if (".+()[]{}^$|\\".includes(char)) {
815
+ regexStr += "\\" + char;
816
+ i++;
817
+ }
818
+ else {
819
+ regexStr += char;
820
+ i++;
821
+ }
822
+ }
823
+ regexStr += "$";
824
+ try {
825
+ return new RegExp(regexStr).test(str);
826
+ }
827
+ catch {
828
+ return false;
829
+ }
830
+ }
831
+ /**
832
+ * Normalize and resolve a file path to an absolute path.
833
+ *
834
+ * @param filePath - The file path to normalize
835
+ * @returns Absolute resolved path
836
+ */
837
+ normalizePath(filePath) {
838
+ const resolved = path.isAbsolute(filePath)
839
+ ? path.resolve(filePath)
840
+ : path.resolve(this.config.projectPath, filePath);
841
+ // Defense against symlink traversal: use realpath-equivalent check.
842
+ // path.resolve normalizes ".." but cannot detect symlinks at this layer.
843
+ // The actual symlink resolution must happen at the filesystem layer (tools).
844
+ // Here we ensure the resolved path does not escape via ".." normalization.
845
+ const normalizedProject = path.resolve(this.config.projectPath);
846
+ if (!resolved.startsWith(normalizedProject + path.sep) && resolved !== normalizedProject) {
847
+ // Return a path that will definitely fail startsWith checks
848
+ return resolved;
849
+ }
850
+ return resolved;
851
+ }
852
+ /**
853
+ * Convert an absolute path to a project-relative path.
854
+ *
855
+ * @param absolutePath - Absolute file path
856
+ * @returns Path relative to the project root
857
+ */
858
+ toRelative(absolutePath) {
859
+ return path.relative(this.config.projectPath, absolutePath);
860
+ }
861
+ /**
862
+ * Record a sandbox violation and emit the appropriate event.
863
+ *
864
+ * @param action - What action was attempted
865
+ * @param resource - The resource involved
866
+ * @param rule - Which rule was violated
867
+ * @param blocked - Whether the action was blocked
868
+ */
869
+ recordViolation(action, resource, rule, blocked) {
870
+ const violation = {
871
+ tier: this.state.currentTier,
872
+ action,
873
+ resource,
874
+ rule,
875
+ timestamp: Date.now(),
876
+ blocked,
877
+ };
878
+ // Cap violations array to prevent unbounded memory growth
879
+ if (this.state.violations.length >= MAX_VIOLATIONS) {
880
+ // Keep the last half + new entry (preserve recent violations)
881
+ this.state.violations = this.state.violations.slice(-Math.floor(MAX_VIOLATIONS / 2));
882
+ }
883
+ this.state.violations.push(violation);
884
+ if (blocked) {
885
+ this.emit("violation:blocked", violation);
886
+ }
887
+ else {
888
+ this.emit("violation:warned", violation);
889
+ }
890
+ }
891
+ /**
892
+ * Extract the base command name from a full command string.
893
+ *
894
+ * @param command - Full command string (e.g. "pnpm install lodash")
895
+ * @returns The first token / executable name (e.g. "pnpm")
896
+ */
897
+ extractCommand(command) {
898
+ const trimmed = command.trim();
899
+ // Handle env vars prefix (e.g. "NODE_ENV=prod tsc")
900
+ const parts = trimmed.split(/\s+/);
901
+ for (const part of parts) {
902
+ if (!part.includes("=")) {
903
+ // Strip path prefix (e.g. "/usr/bin/node" → "node")
904
+ return path.basename(part);
905
+ }
906
+ }
907
+ return parts[0] ?? "";
908
+ }
909
+ /**
910
+ * Check if a command is a build-related command.
911
+ *
912
+ * @param command - The extracted command name
913
+ * @returns true if it's a build command
914
+ */
915
+ isBuildCommand(command) {
916
+ return BUILD_COMMANDS.has(command);
917
+ }
918
+ /**
919
+ * Check if a command is a network-related command.
920
+ *
921
+ * @param command - The extracted command name
922
+ * @returns true if it requires network access
923
+ */
924
+ isNetworkCommand(command) {
925
+ return NETWORK_COMMANDS.has(command);
926
+ }
927
+ }
928
+ //# sourceMappingURL=sandbox-tiers.js.map