network-ai 5.10.2 → 5.11.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 (272) hide show
  1. package/INTEGRATION_GUIDE.md +2 -2
  2. package/README.md +5 -3
  3. package/SKILL.md +3 -3
  4. package/dist/esm/adapters/a2a-adapter.js +235 -0
  5. package/dist/esm/adapters/a2a-adapter.js.map +1 -0
  6. package/dist/esm/adapters/adapter-registry.js +613 -0
  7. package/dist/esm/adapters/adapter-registry.js.map +1 -0
  8. package/dist/esm/adapters/agno-adapter.js +140 -0
  9. package/dist/esm/adapters/agno-adapter.js.map +1 -0
  10. package/dist/esm/adapters/anthropic-computer-use-adapter.js +180 -0
  11. package/dist/esm/adapters/anthropic-computer-use-adapter.js.map +1 -0
  12. package/dist/esm/adapters/aps-adapter.js +289 -0
  13. package/dist/esm/adapters/aps-adapter.js.map +1 -0
  14. package/dist/esm/adapters/autogen-adapter.js +141 -0
  15. package/dist/esm/adapters/autogen-adapter.js.map +1 -0
  16. package/dist/esm/adapters/base-adapter.js +104 -0
  17. package/dist/esm/adapters/base-adapter.js.map +1 -0
  18. package/dist/esm/adapters/browser-agent-adapter.js +219 -0
  19. package/dist/esm/adapters/browser-agent-adapter.js.map +1 -0
  20. package/dist/esm/adapters/codex-adapter.js +318 -0
  21. package/dist/esm/adapters/codex-adapter.js.map +1 -0
  22. package/dist/esm/adapters/copilot-adapter.js +132 -0
  23. package/dist/esm/adapters/copilot-adapter.js.map +1 -0
  24. package/dist/esm/adapters/crewai-adapter.js +148 -0
  25. package/dist/esm/adapters/crewai-adapter.js.map +1 -0
  26. package/dist/esm/adapters/custom-adapter.js +142 -0
  27. package/dist/esm/adapters/custom-adapter.js.map +1 -0
  28. package/dist/esm/adapters/custom-streaming-adapter.js +181 -0
  29. package/dist/esm/adapters/custom-streaming-adapter.js.map +1 -0
  30. package/dist/esm/adapters/dspy-adapter.js +127 -0
  31. package/dist/esm/adapters/dspy-adapter.js.map +1 -0
  32. package/dist/esm/adapters/haystack-adapter.js +149 -0
  33. package/dist/esm/adapters/haystack-adapter.js.map +1 -0
  34. package/dist/esm/adapters/hermes-adapter.js +217 -0
  35. package/dist/esm/adapters/hermes-adapter.js.map +1 -0
  36. package/dist/esm/adapters/index.js +109 -0
  37. package/dist/esm/adapters/index.js.map +1 -0
  38. package/dist/esm/adapters/langchain-adapter.js +134 -0
  39. package/dist/esm/adapters/langchain-adapter.js.map +1 -0
  40. package/dist/esm/adapters/langchain-streaming-adapter.js +161 -0
  41. package/dist/esm/adapters/langchain-streaming-adapter.js.map +1 -0
  42. package/dist/esm/adapters/langgraph-adapter.js +119 -0
  43. package/dist/esm/adapters/langgraph-adapter.js.map +1 -0
  44. package/dist/esm/adapters/llamaindex-adapter.js +135 -0
  45. package/dist/esm/adapters/llamaindex-adapter.js.map +1 -0
  46. package/dist/esm/adapters/mcp-adapter.js +200 -0
  47. package/dist/esm/adapters/mcp-adapter.js.map +1 -0
  48. package/dist/esm/adapters/minimax-adapter.js +233 -0
  49. package/dist/esm/adapters/minimax-adapter.js.map +1 -0
  50. package/dist/esm/adapters/nemoclaw-adapter.js +465 -0
  51. package/dist/esm/adapters/nemoclaw-adapter.js.map +1 -0
  52. package/dist/esm/adapters/openai-agents-adapter.js +118 -0
  53. package/dist/esm/adapters/openai-agents-adapter.js.map +1 -0
  54. package/dist/esm/adapters/openai-assistants-adapter.js +130 -0
  55. package/dist/esm/adapters/openai-assistants-adapter.js.map +1 -0
  56. package/dist/esm/adapters/openclaw-adapter.js +107 -0
  57. package/dist/esm/adapters/openclaw-adapter.js.map +1 -0
  58. package/dist/esm/adapters/orchestrator-adapter.js +218 -0
  59. package/dist/esm/adapters/orchestrator-adapter.js.map +1 -0
  60. package/dist/esm/adapters/pydantic-ai-adapter.js +163 -0
  61. package/dist/esm/adapters/pydantic-ai-adapter.js.map +1 -0
  62. package/dist/esm/adapters/rlm-adapter.js +167 -0
  63. package/dist/esm/adapters/rlm-adapter.js.map +1 -0
  64. package/dist/esm/adapters/semantic-kernel-adapter.js +123 -0
  65. package/dist/esm/adapters/semantic-kernel-adapter.js.map +1 -0
  66. package/dist/esm/adapters/streaming-base-adapter.js +74 -0
  67. package/dist/esm/adapters/streaming-base-adapter.js.map +1 -0
  68. package/dist/esm/adapters/vertex-ai-adapter.js +166 -0
  69. package/dist/esm/adapters/vertex-ai-adapter.js.map +1 -0
  70. package/dist/esm/demo-control-plane.js +147 -0
  71. package/dist/esm/demo-control-plane.js.map +1 -0
  72. package/dist/esm/demo-worktree-dashboard.js +131 -0
  73. package/dist/esm/demo-worktree-dashboard.js.map +1 -0
  74. package/dist/esm/examples/01-hello-swarm.js +165 -0
  75. package/dist/esm/examples/01-hello-swarm.js.map +1 -0
  76. package/dist/esm/examples/02-fsm-pipeline.js +189 -0
  77. package/dist/esm/examples/02-fsm-pipeline.js.map +1 -0
  78. package/dist/esm/examples/03-parallel-agents.js +192 -0
  79. package/dist/esm/examples/03-parallel-agents.js.map +1 -0
  80. package/dist/esm/examples/05-code-review-swarm.js +1177 -0
  81. package/dist/esm/examples/05-code-review-swarm.js.map +1 -0
  82. package/dist/esm/examples/06-ai-pipeline-demo.js +263 -0
  83. package/dist/esm/examples/06-ai-pipeline-demo.js.map +1 -0
  84. package/dist/esm/examples/07-full-showcase.js +946 -0
  85. package/dist/esm/examples/07-full-showcase.js.map +1 -0
  86. package/dist/esm/examples/08-control-plane-stress-demo.js +186 -0
  87. package/dist/esm/examples/08-control-plane-stress-demo.js.map +1 -0
  88. package/dist/esm/examples/09-real-langchain.js +231 -0
  89. package/dist/esm/examples/09-real-langchain.js.map +1 -0
  90. package/dist/esm/examples/10-nemoclaw-sandbox-swarm.js +270 -0
  91. package/dist/esm/examples/10-nemoclaw-sandbox-swarm.js.map +1 -0
  92. package/dist/esm/examples/demo-runner.js +119 -0
  93. package/dist/esm/examples/demo-runner.js.map +1 -0
  94. package/dist/esm/index.js +1352 -0
  95. package/dist/esm/index.js.map +1 -0
  96. package/dist/esm/lib/adapter-hooks.js +216 -0
  97. package/dist/esm/lib/adapter-hooks.js.map +1 -0
  98. package/dist/esm/lib/adapter-test-harness.js +118 -0
  99. package/dist/esm/lib/adapter-test-harness.js.map +1 -0
  100. package/dist/esm/lib/agent-conversation.js +155 -0
  101. package/dist/esm/lib/agent-conversation.js.map +1 -0
  102. package/dist/esm/lib/agent-debate.js +146 -0
  103. package/dist/esm/lib/agent-debate.js.map +1 -0
  104. package/dist/esm/lib/agent-memory.js +336 -0
  105. package/dist/esm/lib/agent-memory.js.map +1 -0
  106. package/dist/esm/lib/agent-runtime.js +818 -0
  107. package/dist/esm/lib/agent-runtime.js.map +1 -0
  108. package/dist/esm/lib/agent-vcr.js +218 -0
  109. package/dist/esm/lib/agent-vcr.js.map +1 -0
  110. package/dist/esm/lib/anomaly-detector.js +178 -0
  111. package/dist/esm/lib/anomaly-detector.js.map +1 -0
  112. package/dist/esm/lib/approval-inbox.js +385 -0
  113. package/dist/esm/lib/approval-inbox.js.map +1 -0
  114. package/dist/esm/lib/auth-guardian.js +692 -0
  115. package/dist/esm/lib/auth-guardian.js.map +1 -0
  116. package/dist/esm/lib/auth-validator.js +32 -0
  117. package/dist/esm/lib/auth-validator.js.map +1 -0
  118. package/dist/esm/lib/blackboard-backend-crdt.js +251 -0
  119. package/dist/esm/lib/blackboard-backend-crdt.js.map +1 -0
  120. package/dist/esm/lib/blackboard-backend-redis.js +244 -0
  121. package/dist/esm/lib/blackboard-backend-redis.js.map +1 -0
  122. package/dist/esm/lib/blackboard-backend.js +141 -0
  123. package/dist/esm/lib/blackboard-backend.js.map +1 -0
  124. package/dist/esm/lib/blackboard-validator.js +985 -0
  125. package/dist/esm/lib/blackboard-validator.js.map +1 -0
  126. package/dist/esm/lib/circuit-breaker.js +164 -0
  127. package/dist/esm/lib/circuit-breaker.js.map +1 -0
  128. package/dist/esm/lib/claim-verifier.js +173 -0
  129. package/dist/esm/lib/claim-verifier.js.map +1 -0
  130. package/dist/esm/lib/comparison-runner.js +138 -0
  131. package/dist/esm/lib/comparison-runner.js.map +1 -0
  132. package/dist/esm/lib/compliance-monitor.js +261 -0
  133. package/dist/esm/lib/compliance-monitor.js.map +1 -0
  134. package/dist/esm/lib/confidence-filter.js +210 -0
  135. package/dist/esm/lib/confidence-filter.js.map +1 -0
  136. package/dist/esm/lib/config-watcher.js +215 -0
  137. package/dist/esm/lib/config-watcher.js.map +1 -0
  138. package/dist/esm/lib/consistency.js +274 -0
  139. package/dist/esm/lib/consistency.js.map +1 -0
  140. package/dist/esm/lib/console-ui.js +276 -0
  141. package/dist/esm/lib/console-ui.js.map +1 -0
  142. package/dist/esm/lib/context-throttler.js +171 -0
  143. package/dist/esm/lib/context-throttler.js.map +1 -0
  144. package/dist/esm/lib/control-plane.js +527 -0
  145. package/dist/esm/lib/control-plane.js.map +1 -0
  146. package/dist/esm/lib/cost-governor.js +128 -0
  147. package/dist/esm/lib/cost-governor.js.map +1 -0
  148. package/dist/esm/lib/cost-heatmap.js +161 -0
  149. package/dist/esm/lib/cost-heatmap.js.map +1 -0
  150. package/dist/esm/lib/coverage-gate.js +213 -0
  151. package/dist/esm/lib/coverage-gate.js.map +1 -0
  152. package/dist/esm/lib/coverage-reporter.js +177 -0
  153. package/dist/esm/lib/coverage-reporter.js.map +1 -0
  154. package/dist/esm/lib/crdt.js +141 -0
  155. package/dist/esm/lib/crdt.js.map +1 -0
  156. package/dist/esm/lib/dashboard-server.js +403 -0
  157. package/dist/esm/lib/dashboard-server.js.map +1 -0
  158. package/dist/esm/lib/dry-run.js +130 -0
  159. package/dist/esm/lib/dry-run.js.map +1 -0
  160. package/dist/esm/lib/env-manager.js +518 -0
  161. package/dist/esm/lib/env-manager.js.map +1 -0
  162. package/dist/esm/lib/errors.js +201 -0
  163. package/dist/esm/lib/errors.js.map +1 -0
  164. package/dist/esm/lib/event-bus.js +229 -0
  165. package/dist/esm/lib/event-bus.js.map +1 -0
  166. package/dist/esm/lib/explainability.js +102 -0
  167. package/dist/esm/lib/explainability.js.map +1 -0
  168. package/dist/esm/lib/fan-out.js +237 -0
  169. package/dist/esm/lib/fan-out.js.map +1 -0
  170. package/dist/esm/lib/federated-budget.js +322 -0
  171. package/dist/esm/lib/federated-budget.js.map +1 -0
  172. package/dist/esm/lib/fsm-journey.js +478 -0
  173. package/dist/esm/lib/fsm-journey.js.map +1 -0
  174. package/dist/esm/lib/goal-decomposer.js +698 -0
  175. package/dist/esm/lib/goal-decomposer.js.map +1 -0
  176. package/dist/esm/lib/goal-dsl.js +391 -0
  177. package/dist/esm/lib/goal-dsl.js.map +1 -0
  178. package/dist/esm/lib/job-queue.js +310 -0
  179. package/dist/esm/lib/job-queue.js.map +1 -0
  180. package/dist/esm/lib/landscape-agent.js +134 -0
  181. package/dist/esm/lib/landscape-agent.js.map +1 -0
  182. package/dist/esm/lib/learning-loop.js +181 -0
  183. package/dist/esm/lib/learning-loop.js.map +1 -0
  184. package/dist/esm/lib/lifecycle-hooks.js +148 -0
  185. package/dist/esm/lib/lifecycle-hooks.js.map +1 -0
  186. package/dist/esm/lib/locked-blackboard.js +1295 -0
  187. package/dist/esm/lib/locked-blackboard.js.map +1 -0
  188. package/dist/esm/lib/logger.js +150 -0
  189. package/dist/esm/lib/logger.js.map +1 -0
  190. package/dist/esm/lib/mcp-blackboard-tools.js +298 -0
  191. package/dist/esm/lib/mcp-blackboard-tools.js.map +1 -0
  192. package/dist/esm/lib/mcp-bridge.js +357 -0
  193. package/dist/esm/lib/mcp-bridge.js.map +1 -0
  194. package/dist/esm/lib/mcp-tool-consumer.js +287 -0
  195. package/dist/esm/lib/mcp-tool-consumer.js.map +1 -0
  196. package/dist/esm/lib/mcp-tools-control.js +392 -0
  197. package/dist/esm/lib/mcp-tools-control.js.map +1 -0
  198. package/dist/esm/lib/mcp-tools-extended.js +371 -0
  199. package/dist/esm/lib/mcp-tools-extended.js.map +1 -0
  200. package/dist/esm/lib/mcp-transport-http.js +528 -0
  201. package/dist/esm/lib/mcp-transport-http.js.map +1 -0
  202. package/dist/esm/lib/mcp-transport-sse.js +503 -0
  203. package/dist/esm/lib/mcp-transport-sse.js.map +1 -0
  204. package/dist/esm/lib/metrics.js +284 -0
  205. package/dist/esm/lib/metrics.js.map +1 -0
  206. package/dist/esm/lib/orchestrator-types.js +66 -0
  207. package/dist/esm/lib/orchestrator-types.js.map +1 -0
  208. package/dist/esm/lib/otel-bridge.js +167 -0
  209. package/dist/esm/lib/otel-bridge.js.map +1 -0
  210. package/dist/esm/lib/partition-planner.js +246 -0
  211. package/dist/esm/lib/partition-planner.js.map +1 -0
  212. package/dist/esm/lib/phase-pipeline.js +367 -0
  213. package/dist/esm/lib/phase-pipeline.js.map +1 -0
  214. package/dist/esm/lib/playground.js +224 -0
  215. package/dist/esm/lib/playground.js.map +1 -0
  216. package/dist/esm/lib/qa-orchestrator.js +296 -0
  217. package/dist/esm/lib/qa-orchestrator.js.map +1 -0
  218. package/dist/esm/lib/quadtree.js +259 -0
  219. package/dist/esm/lib/quadtree.js.map +1 -0
  220. package/dist/esm/lib/route-classifier.js +217 -0
  221. package/dist/esm/lib/route-classifier.js.map +1 -0
  222. package/dist/esm/lib/semantic-search.js +235 -0
  223. package/dist/esm/lib/semantic-search.js.map +1 -0
  224. package/dist/esm/lib/shared-blackboard.js +249 -0
  225. package/dist/esm/lib/shared-blackboard.js.map +1 -0
  226. package/dist/esm/lib/skill-composer.js +190 -0
  227. package/dist/esm/lib/skill-composer.js.map +1 -0
  228. package/dist/esm/lib/speculative-executor.js +107 -0
  229. package/dist/esm/lib/speculative-executor.js.map +1 -0
  230. package/dist/esm/lib/strategy-agent.js +626 -0
  231. package/dist/esm/lib/strategy-agent.js.map +1 -0
  232. package/dist/esm/lib/swarm-transport.js +307 -0
  233. package/dist/esm/lib/swarm-transport.js.map +1 -0
  234. package/dist/esm/lib/swarm-utils.js +510 -0
  235. package/dist/esm/lib/swarm-utils.js.map +1 -0
  236. package/dist/esm/lib/task-decomposer.js +272 -0
  237. package/dist/esm/lib/task-decomposer.js.map +1 -0
  238. package/dist/esm/lib/telemetry-provider.js +207 -0
  239. package/dist/esm/lib/telemetry-provider.js.map +1 -0
  240. package/dist/esm/lib/timeline-scrubber.js +173 -0
  241. package/dist/esm/lib/timeline-scrubber.js.map +1 -0
  242. package/dist/esm/lib/topology.js +591 -0
  243. package/dist/esm/lib/topology.js.map +1 -0
  244. package/dist/esm/lib/transport-agent.js +366 -0
  245. package/dist/esm/lib/transport-agent.js.map +1 -0
  246. package/dist/esm/lib/work-tree-dashboard.js +583 -0
  247. package/dist/esm/lib/work-tree-dashboard.js.map +1 -0
  248. package/dist/esm/lib/work-tree-ui.js +333 -0
  249. package/dist/esm/lib/work-tree-ui.js.map +1 -0
  250. package/dist/esm/lib/work-tree.js +480 -0
  251. package/dist/esm/lib/work-tree.js.map +1 -0
  252. package/dist/esm/run.js +144 -0
  253. package/dist/esm/run.js.map +1 -0
  254. package/dist/esm/security.js +1122 -0
  255. package/dist/esm/security.js.map +1 -0
  256. package/dist/index.d.ts +2 -0
  257. package/dist/index.d.ts.map +1 -1
  258. package/dist/index.js +6 -1
  259. package/dist/index.js.map +1 -1
  260. package/dist/lib/mcp-transport-http.d.ts +203 -0
  261. package/dist/lib/mcp-transport-http.d.ts.map +1 -0
  262. package/dist/lib/mcp-transport-http.js +528 -0
  263. package/dist/lib/mcp-transport-http.js.map +1 -0
  264. package/dist/lib/phase-pipeline.d.ts +31 -0
  265. package/dist/lib/phase-pipeline.d.ts.map +1 -1
  266. package/dist/lib/phase-pipeline.js +93 -1
  267. package/dist/lib/phase-pipeline.js.map +1 -1
  268. package/dist/lib/semantic-search.d.ts +42 -6
  269. package/dist/lib/semantic-search.d.ts.map +1 -1
  270. package/dist/lib/semantic-search.js +87 -6
  271. package/dist/lib/semantic-search.js.map +1 -1
  272. package/package.json +24 -4
@@ -0,0 +1,818 @@
1
+ "use strict";
2
+ /**
3
+ * Agent Runtime — Sandboxed execution environment for AI agents
4
+ *
5
+ * Provides controlled access to shell commands, file system, and system
6
+ * resources with policy enforcement, approval gates, and audit logging.
7
+ *
8
+ * Components:
9
+ * - SandboxPolicy — defines what agents are allowed to do
10
+ * - ShellExecutor — spawns child processes within policy constraints
11
+ * - FileAccessor — scoped file read/write with traversal protection
12
+ * - ApprovalGate — human-in-the-loop approval for sensitive operations
13
+ * - AgentRuntime — unified facade combining all components
14
+ *
15
+ * @module AgentRuntime
16
+ * @version 1.0.0
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.RuntimeExecutionError = exports.RuntimeApprovalError = exports.RuntimePolicyError = exports.AgentRuntime = exports.ApprovalGate = exports.FileAccessor = exports.ShellExecutor = exports.SandboxPolicy = exports.SourceProtectionError = void 0;
20
+ const child_process_1 = require("child_process");
21
+ const crypto_1 = require("crypto");
22
+ const promises_1 = require("fs/promises");
23
+ const path_1 = require("path");
24
+ const events_1 = require("events");
25
+ const security_1 = require("../security");
26
+ // ============================================================================
27
+ // ERRORS
28
+ // ============================================================================
29
+ /**
30
+ * Thrown when an agent attempts to access source code files or directories
31
+ * while `sourceProtection` is enabled on the sandbox policy.
32
+ */
33
+ class SourceProtectionError extends Error {
34
+ /** The path that was blocked */
35
+ blockedPath;
36
+ /** The agent that attempted access */
37
+ agentId;
38
+ constructor(blockedPath, agentId) {
39
+ super(`Source protection: access to '${blockedPath}' is blocked for agent '${agentId}'`);
40
+ this.name = 'SourceProtectionError';
41
+ this.blockedPath = blockedPath;
42
+ this.agentId = agentId;
43
+ }
44
+ }
45
+ exports.SourceProtectionError = SourceProtectionError;
46
+ // ============================================================================
47
+ // COMMAND PARSING
48
+ // ============================================================================
49
+ /**
50
+ * Shell metacharacters that enable command chaining, substitution, or
51
+ * redirection. Any of these appearing OUTSIDE of quotes turns a scoped
52
+ * allowlist entry (e.g. `git *`) into arbitrary execution and must be
53
+ * rejected (GHSA-qw6v-5fcf-5666).
54
+ */
55
+ const SHELL_METACHARACTERS = new Set([
56
+ ';', '&', '|', '$', '`', '(', ')', '<', '>', '{', '}', '\n', '\r',
57
+ ]);
58
+ /**
59
+ * Tokenize a command string into an argv array, honoring single and double
60
+ * quotes, and report whether any shell metacharacter appears OUTSIDE of quotes.
61
+ *
62
+ * Commands are executed with `shell: false`, so no shell ever interprets the
63
+ * result — quoted metacharacters are therefore safe, literal data. Unquoted
64
+ * metacharacters indicate an attempt to chain, substitute, or redirect commands
65
+ * and cause the command to be rejected before it can run.
66
+ *
67
+ * @param command - Raw command string to parse.
68
+ * @returns Parsed argv plus flags for unquoted metacharacters and malformed quoting.
69
+ */
70
+ function parseCommandLine(command) {
71
+ const argv = [];
72
+ let current = '';
73
+ let tokenOpen = false;
74
+ let quote = null;
75
+ let hasUnquotedMetacharacter = false;
76
+ for (let i = 0; i < command.length; i++) {
77
+ const ch = command[i];
78
+ if (quote) {
79
+ if (ch === quote) {
80
+ quote = null;
81
+ }
82
+ else {
83
+ current += ch;
84
+ }
85
+ tokenOpen = true;
86
+ continue;
87
+ }
88
+ if (ch === '"' || ch === "'") {
89
+ quote = ch;
90
+ tokenOpen = true;
91
+ continue;
92
+ }
93
+ if (ch === ' ' || ch === '\t') {
94
+ if (tokenOpen) {
95
+ argv.push(current);
96
+ current = '';
97
+ tokenOpen = false;
98
+ }
99
+ continue;
100
+ }
101
+ if (SHELL_METACHARACTERS.has(ch)) {
102
+ // Unquoted metacharacter — reject the whole command. Do not include it
103
+ // in argv so the result can never be silently executed.
104
+ hasUnquotedMetacharacter = true;
105
+ continue;
106
+ }
107
+ current += ch;
108
+ tokenOpen = true;
109
+ }
110
+ if (tokenOpen)
111
+ argv.push(current);
112
+ return { argv, hasUnquotedMetacharacter, unterminatedQuote: quote !== null };
113
+ }
114
+ // ============================================================================
115
+ // SANDBOX POLICY
116
+ // ============================================================================
117
+ const DEFAULT_POLICY = {
118
+ allowedCommands: [],
119
+ blockedCommands: [
120
+ 'rm -rf /',
121
+ 'rm -rf /*',
122
+ 'rmdir /s /q C:\\',
123
+ 'format *',
124
+ 'mkfs*',
125
+ 'dd if=*',
126
+ ':(){:|:&};:', // fork bomb
127
+ 'shutdown*',
128
+ 'reboot*',
129
+ 'halt*',
130
+ 'init 0',
131
+ 'init 6',
132
+ 'del /f /s /q C:\\*',
133
+ 'reg delete*',
134
+ 'net user*',
135
+ 'net localgroup*',
136
+ ],
137
+ allowedPaths: ['.'],
138
+ blockedPaths: [],
139
+ maxConcurrentProcesses: 5,
140
+ defaultTimeoutMs: 30_000,
141
+ defaultMaxOutputBytes: 1_048_576,
142
+ approvalRequired: [
143
+ 'rm *',
144
+ 'del *',
145
+ 'rmdir *',
146
+ 'git push*',
147
+ 'git reset --hard*',
148
+ 'npm publish*',
149
+ 'docker *',
150
+ 'kubectl *',
151
+ ],
152
+ autoApproveReads: true,
153
+ };
154
+ /**
155
+ * Sandbox policy engine — determines what agents are allowed to do.
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * const policy = new SandboxPolicy({
160
+ * basePath: '/project',
161
+ * allowedCommands: ['npm *', 'node *', 'git status'],
162
+ * });
163
+ * policy.isCommandAllowed('npm test'); // true
164
+ * policy.isCommandAllowed('rm -rf /'); // false
165
+ * ```
166
+ */
167
+ class SandboxPolicy {
168
+ config;
169
+ constructor(config) {
170
+ this.config = { ...DEFAULT_POLICY, ...config };
171
+ this.config.basePath = (0, path_1.resolve)(this.config.basePath);
172
+ }
173
+ /** Check if a command matches the policy's allowed list and isn't blocked */
174
+ isCommandAllowed(command) {
175
+ const trimmed = command.trim();
176
+ if (!trimmed)
177
+ return false;
178
+ // Reject shell metacharacters and malformed quoting BEFORE pattern matching.
179
+ // Commands run with `shell: false`, but a scoped allowlist entry such as
180
+ // `git *` must never be escapable into arbitrary execution via `;`, `|`,
181
+ // `&&`, `$(...)`, backticks, redirection, or newlines (GHSA-qw6v-5fcf-5666).
182
+ const parsed = parseCommandLine(trimmed);
183
+ if (parsed.hasUnquotedMetacharacter || parsed.unterminatedQuote || parsed.argv.length === 0) {
184
+ return false;
185
+ }
186
+ // Check blocked first (always wins)
187
+ if (this.matchesAny(trimmed, this.config.blockedCommands))
188
+ return false;
189
+ // If no allowedCommands, deny all
190
+ if (this.config.allowedCommands.length === 0)
191
+ return false;
192
+ // Check allowed
193
+ return this.matchesAny(trimmed, this.config.allowedCommands);
194
+ }
195
+ /**
196
+ * Tokenize a command into an argv array suitable for `shell: false`
197
+ * execution. Returns `null` for any command that contains unquoted shell
198
+ * metacharacters or malformed quoting — i.e. exactly the inputs that
199
+ * {@link isCommandAllowed} rejects. Quoted metacharacters are preserved as
200
+ * literal data within their token.
201
+ *
202
+ * @param command - Raw command string to tokenize.
203
+ * @returns The argv array, or `null` if the command is unsafe to execute.
204
+ */
205
+ tokenizeCommand(command) {
206
+ const parsed = parseCommandLine(command.trim());
207
+ if (parsed.hasUnquotedMetacharacter || parsed.unterminatedQuote || parsed.argv.length === 0) {
208
+ return null;
209
+ }
210
+ return parsed.argv;
211
+ }
212
+ /** Check if a command requires human approval */
213
+ requiresApproval(command) {
214
+ return this.matchesAny(command.trim(), this.config.approvalRequired);
215
+ }
216
+ /** Assess risk level of a command */
217
+ assessRisk(command) {
218
+ const trimmed = command.trim().toLowerCase();
219
+ const highRisk = ['rm ', 'del ', 'format', 'drop ', 'delete ', 'truncate ', 'git push', 'git reset', 'docker', 'kubectl'];
220
+ const medRisk = ['git ', 'npm ', 'pip ', 'mv ', 'move ', 'cp ', 'copy ', 'chmod ', 'chown '];
221
+ if (highRisk.some(p => trimmed.startsWith(p) || trimmed.includes(' ' + p)))
222
+ return 'high';
223
+ if (medRisk.some(p => trimmed.startsWith(p) || trimmed.includes(' ' + p)))
224
+ return 'medium';
225
+ return 'low';
226
+ }
227
+ /** Validate that a file path is within allowed scope */
228
+ isPathAllowed(filePath) {
229
+ const normalized = this.resolvePath(filePath);
230
+ if (!normalized)
231
+ return false;
232
+ // Check blocked paths
233
+ for (const blocked of this.config.blockedPaths) {
234
+ const blockedAbs = (0, path_1.resolve)(this.config.basePath, blocked);
235
+ if (normalized.startsWith(blockedAbs))
236
+ return false;
237
+ }
238
+ // Check allowed paths
239
+ for (const allowed of this.config.allowedPaths) {
240
+ const allowedAbs = (0, path_1.resolve)(this.config.basePath, allowed);
241
+ if (normalized.startsWith(allowedAbs))
242
+ return true;
243
+ }
244
+ return false;
245
+ }
246
+ /** Resolve and validate a path against basePath (returns null if traversal detected) */
247
+ resolvePath(filePath) {
248
+ const normalized = (0, path_1.normalize)(filePath);
249
+ const absolute = (0, path_1.isAbsolute)(normalized)
250
+ ? normalized
251
+ : (0, path_1.join)(this.config.basePath, normalized);
252
+ const resolved = (0, path_1.resolve)(absolute);
253
+ // Traversal check
254
+ if (!resolved.startsWith(this.config.basePath))
255
+ return null;
256
+ return resolved;
257
+ }
258
+ /** Get a copy of the current policy config */
259
+ getConfig() {
260
+ return { ...this.config };
261
+ }
262
+ /** Update allowed commands */
263
+ allowCommand(pattern) {
264
+ if (!this.config.allowedCommands.includes(pattern)) {
265
+ this.config.allowedCommands.push(pattern);
266
+ }
267
+ }
268
+ /** Remove an allowed command pattern */
269
+ disallowCommand(pattern) {
270
+ this.config.allowedCommands = this.config.allowedCommands.filter(c => c !== pattern);
271
+ }
272
+ /** Update allowed paths */
273
+ allowPath(path) {
274
+ if (!this.config.allowedPaths.includes(path)) {
275
+ this.config.allowedPaths.push(path);
276
+ }
277
+ }
278
+ /** Block a path */
279
+ blockPath(path) {
280
+ if (!this.config.blockedPaths.includes(path)) {
281
+ this.config.blockedPaths.push(path);
282
+ }
283
+ }
284
+ get basePath() { return this.config.basePath; }
285
+ get maxConcurrentProcesses() { return this.config.maxConcurrentProcesses; }
286
+ get defaultTimeoutMs() { return this.config.defaultTimeoutMs; }
287
+ get defaultMaxOutputBytes() { return this.config.defaultMaxOutputBytes; }
288
+ get autoApproveReads() { return this.config.autoApproveReads; }
289
+ /** Simple glob matching: supports * as wildcard */
290
+ matchesAny(value, patterns) {
291
+ return patterns.some(pattern => this.globMatch(pattern, value));
292
+ }
293
+ /** Match a simple glob pattern against a string */
294
+ globMatch(pattern, value) {
295
+ // Escape regex special chars except *
296
+ const regexStr = pattern
297
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
298
+ .replace(/\*/g, '.*');
299
+ return new RegExp(`^${regexStr}$`, 'i').test(value);
300
+ }
301
+ }
302
+ exports.SandboxPolicy = SandboxPolicy;
303
+ // ============================================================================
304
+ // SHELL EXECUTOR
305
+ // ============================================================================
306
+ /**
307
+ * Sandboxed shell command executor with timeout, output limits, and
308
+ * concurrent process tracking.
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * const executor = new ShellExecutor(policy);
313
+ * const result = await executor.execute('npm test', { agentId: 'tester' });
314
+ * console.log(result.exitCode, result.stdout);
315
+ * ```
316
+ */
317
+ class ShellExecutor {
318
+ policy;
319
+ activeProcesses = 0;
320
+ constructor(policy) {
321
+ this.policy = policy;
322
+ }
323
+ /** Execute a shell command within policy constraints */
324
+ async execute(command, opts = {}) {
325
+ // Policy check
326
+ if (!this.policy.isCommandAllowed(command)) {
327
+ throw new RuntimePolicyError(`Command blocked by policy: ${command}`);
328
+ }
329
+ // Concurrency check
330
+ if (this.activeProcesses >= this.policy.maxConcurrentProcesses) {
331
+ throw new RuntimePolicyError(`Concurrency limit reached (${this.policy.maxConcurrentProcesses} max)`);
332
+ }
333
+ const cwd = opts.cwd
334
+ ? (this.policy.resolvePath(opts.cwd) ?? this.policy.basePath)
335
+ : this.policy.basePath;
336
+ const timeoutMs = opts.timeoutMs ?? this.policy.defaultTimeoutMs;
337
+ const maxBytes = opts.maxOutputBytes ?? this.policy.defaultMaxOutputBytes;
338
+ this.activeProcesses++;
339
+ try {
340
+ return await this.spawnCommand(command, cwd, timeoutMs, maxBytes, opts.env);
341
+ }
342
+ finally {
343
+ this.activeProcesses--;
344
+ }
345
+ }
346
+ /** Get the number of currently running processes */
347
+ get running() { return this.activeProcesses; }
348
+ spawnCommand(command, cwd, timeoutMs, maxBytes, env) {
349
+ return new Promise((resolvePromise, reject) => {
350
+ // Execute with `shell: false` using a parsed argv. The command was already
351
+ // validated by `isCommandAllowed`, but we re-tokenize here as the single
352
+ // source of truth for what actually runs — no string is ever handed to a
353
+ // shell, so metacharacters cannot be interpreted (GHSA-qw6v-5fcf-5666).
354
+ const argv = this.policy.tokenizeCommand(command);
355
+ if (!argv || argv.length === 0) {
356
+ reject(new RuntimePolicyError(`Command rejected — shell metacharacters are not permitted: ${command}`));
357
+ return;
358
+ }
359
+ const file = argv[0];
360
+ const args = argv.slice(1);
361
+ const child = (0, child_process_1.spawn)(file, args, {
362
+ cwd,
363
+ env: env ? { ...process.env, ...env } : process.env,
364
+ stdio: ['ignore', 'pipe', 'pipe'],
365
+ windowsHide: true,
366
+ shell: false,
367
+ });
368
+ let stdout = '';
369
+ let stderr = '';
370
+ let totalBytes = 0;
371
+ let timedOut = false;
372
+ let truncated = false;
373
+ const timer = setTimeout(() => {
374
+ timedOut = true;
375
+ child.kill('SIGTERM');
376
+ setTimeout(() => { if (!child.killed)
377
+ child.kill('SIGKILL'); }, 2000);
378
+ }, timeoutMs);
379
+ child.stdout?.on('data', (chunk) => {
380
+ totalBytes += chunk.length;
381
+ if (totalBytes <= maxBytes) {
382
+ stdout += chunk.toString();
383
+ }
384
+ else if (!truncated) {
385
+ truncated = true;
386
+ stdout += '\n[OUTPUT TRUNCATED — exceeded limit]';
387
+ child.kill('SIGTERM');
388
+ }
389
+ });
390
+ child.stderr?.on('data', (chunk) => {
391
+ totalBytes += chunk.length;
392
+ if (totalBytes <= maxBytes) {
393
+ stderr += chunk.toString();
394
+ }
395
+ });
396
+ child.on('close', (code) => {
397
+ clearTimeout(timer);
398
+ resolvePromise({
399
+ exitCode: code ?? (timedOut ? 124 : 1),
400
+ stdout,
401
+ stderr,
402
+ durationMs: Date.now() - startTime,
403
+ timedOut,
404
+ truncated,
405
+ });
406
+ });
407
+ child.on('error', (err) => {
408
+ clearTimeout(timer);
409
+ reject(new RuntimeExecutionError(`Failed to spawn: ${err.message}`));
410
+ });
411
+ const startTime = Date.now();
412
+ });
413
+ }
414
+ }
415
+ exports.ShellExecutor = ShellExecutor;
416
+ // ============================================================================
417
+ // FILE ACCESSOR
418
+ // ============================================================================
419
+ /**
420
+ * Policy-scoped file system accessor. All paths are validated against
421
+ * the sandbox policy before any I/O.
422
+ *
423
+ * **Error contract:** All public methods (`read`, `write`, `list`) return a
424
+ * `{success: boolean, ...}` result object — they never throw. All access-denied
425
+ * paths (path traversal, out-of-scope under `sourceProtection`, policy-blocked
426
+ * reads/writes, and `SourceProtectionError`) are caught at the method boundary
427
+ * and converted to `{success: false, error: <message>}`. The `error` field
428
+ * contains a short description without leaking internal path details.
429
+ *
430
+ * @example
431
+ * ```typescript
432
+ * const files = new FileAccessor(policy);
433
+ * const result = await files.read('src/index.ts', 'reader-agent');
434
+ * ```
435
+ */
436
+ class FileAccessor {
437
+ policy;
438
+ constructor(policy) {
439
+ this.policy = policy;
440
+ }
441
+ /**
442
+ * Checks whether the resolved absolute path is blocked by source protection.
443
+ * Throws SourceProtectionError if access is denied.
444
+ */
445
+ checkSourceProtection(resolvedPath, agentId) {
446
+ const cfg = this.policy.getConfig();
447
+ if (!cfg.sourceProtection)
448
+ return;
449
+ const base = cfg.basePath;
450
+ const env = cfg.env ?? '';
451
+ // The only permitted subtree under source protection is data/<env>/ (when env
452
+ // is set) or data/ (legacy). All source code paths are blocked.
453
+ const dataEnvDir = env
454
+ ? (0, path_1.resolve)(base, 'data', env)
455
+ : (0, path_1.resolve)(base, 'data');
456
+ if (resolvedPath.startsWith(dataEnvDir + require('path').sep) || resolvedPath === dataEnvDir) {
457
+ return; // allowed
458
+ }
459
+ throw new SourceProtectionError(resolvedPath, agentId);
460
+ }
461
+ /** Read a file within the sandbox scope */
462
+ async read(filePath, agentId) {
463
+ const start = Date.now();
464
+ const resolved = this.policy.resolvePath(filePath);
465
+ if (!resolved) {
466
+ return { success: false, path: filePath, error: 'Path traversal blocked', durationMs: Date.now() - start };
467
+ }
468
+ if (!this.policy.isPathAllowed(filePath)) {
469
+ return { success: false, path: filePath, error: 'Path not in allowed scope', durationMs: Date.now() - start };
470
+ }
471
+ try {
472
+ this.checkSourceProtection(resolved, agentId);
473
+ }
474
+ catch (err) {
475
+ if (err instanceof SourceProtectionError) {
476
+ return { success: false, path: resolved, error: err.message, durationMs: Date.now() - start };
477
+ }
478
+ throw err;
479
+ }
480
+ try {
481
+ const content = await (0, promises_1.readFile)(resolved, 'utf-8');
482
+ return { success: true, path: resolved, content, durationMs: Date.now() - start };
483
+ }
484
+ catch (err) {
485
+ const msg = err instanceof Error ? err.message : String(err);
486
+ return { success: false, path: resolved, error: msg, durationMs: Date.now() - start };
487
+ }
488
+ }
489
+ /** Write a file within the sandbox scope */
490
+ async write(filePath, content, agentId) {
491
+ const start = Date.now();
492
+ const resolved = this.policy.resolvePath(filePath);
493
+ if (!resolved) {
494
+ return { success: false, path: filePath, error: 'Path traversal blocked', durationMs: Date.now() - start };
495
+ }
496
+ if (!this.policy.isPathAllowed(filePath)) {
497
+ return { success: false, path: filePath, error: 'Path not in allowed scope', durationMs: Date.now() - start };
498
+ }
499
+ try {
500
+ this.checkSourceProtection(resolved, agentId);
501
+ }
502
+ catch (err) {
503
+ if (err instanceof SourceProtectionError) {
504
+ return { success: false, path: resolved, error: err.message, durationMs: Date.now() - start };
505
+ }
506
+ throw err;
507
+ }
508
+ try {
509
+ await (0, promises_1.mkdir)((0, path_1.dirname)(resolved), { recursive: true });
510
+ await (0, promises_1.writeFile)(resolved, content, 'utf-8');
511
+ return { success: true, path: resolved, durationMs: Date.now() - start };
512
+ }
513
+ catch (err) {
514
+ const msg = err instanceof Error ? err.message : String(err);
515
+ return { success: false, path: resolved, error: msg, durationMs: Date.now() - start };
516
+ }
517
+ }
518
+ /** List directory contents within the sandbox scope */
519
+ async list(dirPath, agentId) {
520
+ const start = Date.now();
521
+ const resolved = this.policy.resolvePath(dirPath);
522
+ if (!resolved) {
523
+ return { success: false, path: dirPath, error: 'Path traversal blocked', durationMs: Date.now() - start };
524
+ }
525
+ if (!this.policy.isPathAllowed(dirPath)) {
526
+ return { success: false, path: dirPath, error: 'Path not in allowed scope', durationMs: Date.now() - start };
527
+ }
528
+ try {
529
+ this.checkSourceProtection(resolved, agentId);
530
+ }
531
+ catch (err) {
532
+ if (err instanceof SourceProtectionError) {
533
+ return { success: false, path: resolved, error: err.message, durationMs: Date.now() - start };
534
+ }
535
+ throw err;
536
+ }
537
+ try {
538
+ const items = await (0, promises_1.readdir)(resolved);
539
+ const entries = [];
540
+ for (const item of items) {
541
+ const itemPath = (0, path_1.join)(resolved, item);
542
+ const info = await (0, promises_1.stat)(itemPath);
543
+ entries.push(info.isDirectory() ? `${item}/` : item);
544
+ }
545
+ return { success: true, path: resolved, entries, durationMs: Date.now() - start };
546
+ }
547
+ catch (err) {
548
+ const msg = err instanceof Error ? err.message : String(err);
549
+ return { success: false, path: resolved, error: msg, durationMs: Date.now() - start };
550
+ }
551
+ }
552
+ }
553
+ exports.FileAccessor = FileAccessor;
554
+ // ============================================================================
555
+ // APPROVAL GATE
556
+ // ============================================================================
557
+ /**
558
+ * Human-in-the-loop approval gate. Queues requests and waits for
559
+ * a decision from the configured callback.
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * const gate = new ApprovalGate(async (req) => {
564
+ * return { approved: true, approvedBy: 'operator' };
565
+ * });
566
+ * const decision = await gate.request({ type: 'shell', target: 'npm publish', ... });
567
+ * ```
568
+ */
569
+ class ApprovalGate extends events_1.EventEmitter {
570
+ callback;
571
+ autoApproveAll;
572
+ history = [];
573
+ constructor(callback, autoApproveAll = false) {
574
+ super();
575
+ this.callback = callback ?? null;
576
+ this.autoApproveAll = autoApproveAll;
577
+ }
578
+ /** Request approval for an operation */
579
+ async request(req) {
580
+ this.emit('requested', req);
581
+ if (this.autoApproveAll) {
582
+ const decision = { approved: true, approvedBy: 'auto-approve-all' };
583
+ this.history.push({ request: req, decision });
584
+ this.emit('decided', req, decision);
585
+ return decision;
586
+ }
587
+ if (!this.callback) {
588
+ const decision = { approved: false, reason: 'No approval callback configured' };
589
+ this.history.push({ request: req, decision });
590
+ this.emit('decided', req, decision);
591
+ return decision;
592
+ }
593
+ const decision = await this.callback(req);
594
+ this.history.push({ request: req, decision });
595
+ this.emit('decided', req, decision);
596
+ return decision;
597
+ }
598
+ /** Get approval history */
599
+ getHistory() {
600
+ return this.history;
601
+ }
602
+ /** Get count of approvals/denials */
603
+ getStats() {
604
+ const approved = this.history.filter(h => h.decision.approved).length;
605
+ return { total: this.history.length, approved, denied: this.history.length - approved };
606
+ }
607
+ }
608
+ exports.ApprovalGate = ApprovalGate;
609
+ // ============================================================================
610
+ // AGENT RUNTIME (Facade)
611
+ // ============================================================================
612
+ /**
613
+ * Unified agent execution runtime. Combines policy, shell, file access,
614
+ * and approval into a single interface for agent consumption.
615
+ *
616
+ * @example
617
+ * ```typescript
618
+ * const runtime = new AgentRuntime({
619
+ * policy: {
620
+ * basePath: '/project',
621
+ * allowedCommands: ['npm *', 'node *', 'git status', 'git diff'],
622
+ * },
623
+ * onApproval: async (req) => {
624
+ * console.log(`Approve? ${req.type}: ${req.target}`);
625
+ * return { approved: true, approvedBy: 'operator' };
626
+ * },
627
+ * });
628
+ *
629
+ * const result = await runtime.exec('npm test', 'tester-agent');
630
+ * ```
631
+ */
632
+ class AgentRuntime extends events_1.EventEmitter {
633
+ policy;
634
+ shell;
635
+ files;
636
+ gate;
637
+ auditLog = [];
638
+ receiptManager = new security_1.SecureTokenManager();
639
+ constructor(opts) {
640
+ super();
641
+ this.policy = new SandboxPolicy(opts.policy);
642
+ this.shell = new ShellExecutor(this.policy);
643
+ this.files = new FileAccessor(this.policy);
644
+ this.gate = new ApprovalGate(opts.onApproval, opts.autoApproveAll);
645
+ // Wire approval events
646
+ this.gate.on('requested', (req) => {
647
+ this.emit('approval:requested', req);
648
+ this.audit({ action: 'approval_requested', agentId: req.agentId, target: req.target, result: 'success' });
649
+ });
650
+ this.gate.on('decided', (req, dec) => {
651
+ this.emit('approval:decided', req, dec);
652
+ this.audit({
653
+ action: dec.approved ? 'approval_granted' : 'approval_denied',
654
+ agentId: req.agentId,
655
+ target: req.target,
656
+ result: dec.approved ? 'success' : 'denied',
657
+ details: { approvedBy: dec.approvedBy, reason: dec.reason },
658
+ });
659
+ });
660
+ }
661
+ /**
662
+ * Execute a shell command with policy + approval checks.
663
+ * Returns ShellResult on success, throws on policy violation.
664
+ */
665
+ async exec(command, agentId, opts = {}) {
666
+ // Policy check
667
+ if (!this.policy.isCommandAllowed(command)) {
668
+ this.emit('policy:violation', agentId, command, 'Command not allowed by policy');
669
+ this.audit({ action: 'shell_execute', agentId, target: command, result: 'blocked' });
670
+ throw new RuntimePolicyError(`Command blocked by policy: ${command}`);
671
+ }
672
+ // Approval check
673
+ const needsApproval = opts.requiresApproval ?? this.policy.requiresApproval(command);
674
+ if (needsApproval) {
675
+ const decision = await this.gate.request({
676
+ type: 'shell',
677
+ target: command,
678
+ agentId,
679
+ risk: this.policy.assessRisk(command),
680
+ timestamp: Date.now(),
681
+ });
682
+ if (!decision.approved) {
683
+ this.audit({ action: 'shell_execute', agentId, target: command, result: 'denied', details: { reason: decision.reason } });
684
+ throw new RuntimeApprovalError(`Command denied: ${decision.reason ?? 'no reason given'}`);
685
+ }
686
+ }
687
+ // Execute
688
+ this.emit('command:start', agentId, command);
689
+ const result = await this.shell.execute(command, { ...opts, agentId });
690
+ this.emit('command:complete', agentId, command, result);
691
+ // Issue outcome-bound receipt — runtime authority, not agent's word
692
+ const outputHash = (0, crypto_1.createHash)('sha256')
693
+ .update(result.stdout + result.stderr + String(result.exitCode))
694
+ .digest('hex');
695
+ result.receipt = this.receiptManager.generateReceipt(agentId, 'shell_execute', command, result.exitCode, outputHash);
696
+ this.audit({
697
+ action: 'shell_execute',
698
+ agentId,
699
+ target: command,
700
+ result: result.exitCode === 0 ? 'success' : (result.timedOut ? 'timeout' : 'error'),
701
+ durationMs: result.durationMs,
702
+ details: { exitCode: result.exitCode, timedOut: result.timedOut },
703
+ });
704
+ return result;
705
+ }
706
+ /** Read a file with policy + optional approval */
707
+ async readFile(filePath, agentId) {
708
+ if (!this.policy.isPathAllowed(filePath)) {
709
+ this.emit('policy:violation', agentId, filePath, 'Path not allowed');
710
+ this.audit({ action: 'file_read', agentId, target: filePath, result: 'blocked' });
711
+ return { success: false, path: filePath, error: 'Path not allowed by policy', durationMs: 0 };
712
+ }
713
+ // Reads can auto-approve
714
+ if (!this.policy.autoApproveReads) {
715
+ const decision = await this.gate.request({
716
+ type: 'file_read', target: filePath, agentId, risk: 'low', timestamp: Date.now(),
717
+ });
718
+ if (!decision.approved) {
719
+ this.audit({ action: 'file_read', agentId, target: filePath, result: 'denied' });
720
+ return { success: false, path: filePath, error: 'Read denied', durationMs: 0 };
721
+ }
722
+ }
723
+ this.emit('file:access', agentId, filePath, 'read');
724
+ const result = await this.files.read(filePath, agentId);
725
+ this.audit({
726
+ action: 'file_read', agentId, target: filePath,
727
+ result: result.success ? 'success' : 'error', durationMs: result.durationMs,
728
+ });
729
+ return result;
730
+ }
731
+ /** Write a file with policy + approval */
732
+ async writeFile(filePath, content, agentId) {
733
+ if (!this.policy.isPathAllowed(filePath)) {
734
+ this.emit('policy:violation', agentId, filePath, 'Path not allowed');
735
+ this.audit({ action: 'file_write', agentId, target: filePath, result: 'blocked' });
736
+ return { success: false, path: filePath, error: 'Path not allowed by policy', durationMs: 0 };
737
+ }
738
+ const decision = await this.gate.request({
739
+ type: 'file_write', target: filePath, agentId, risk: 'medium', timestamp: Date.now(),
740
+ });
741
+ if (!decision.approved) {
742
+ this.audit({ action: 'file_write', agentId, target: filePath, result: 'denied' });
743
+ return { success: false, path: filePath, error: `Write denied: ${decision.reason ?? 'no reason'}`, durationMs: 0 };
744
+ }
745
+ this.emit('file:access', agentId, filePath, 'write');
746
+ const result = await this.files.write(filePath, content, agentId);
747
+ // Issue outcome-bound receipt on successful write
748
+ if (result.success) {
749
+ const contentHash = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
750
+ result.receipt = this.receiptManager.generateReceipt(agentId, 'file_write', result.path, 0, contentHash);
751
+ }
752
+ this.audit({
753
+ action: 'file_write', agentId, target: filePath,
754
+ result: result.success ? 'success' : 'error', durationMs: result.durationMs,
755
+ });
756
+ return result;
757
+ }
758
+ /** List a directory with policy check */
759
+ async listDir(dirPath, agentId) {
760
+ if (!this.policy.isPathAllowed(dirPath)) {
761
+ this.emit('policy:violation', agentId, dirPath, 'Path not allowed');
762
+ this.audit({ action: 'file_list', agentId, target: dirPath, result: 'blocked' });
763
+ return { success: false, path: dirPath, error: 'Path not allowed by policy', durationMs: 0 };
764
+ }
765
+ this.emit('file:access', agentId, dirPath, 'list');
766
+ const result = await this.files.list(dirPath, agentId);
767
+ this.audit({
768
+ action: 'file_list', agentId, target: dirPath,
769
+ result: result.success ? 'success' : 'error', durationMs: result.durationMs,
770
+ });
771
+ return result;
772
+ }
773
+ /** Get the internal audit log */
774
+ getAuditLog() {
775
+ return this.auditLog;
776
+ }
777
+ /** Clear the internal audit log */
778
+ clearAuditLog() {
779
+ this.auditLog = [];
780
+ }
781
+ audit(entry) {
782
+ const full = { ...entry, timestamp: new Date().toISOString() };
783
+ this.auditLog.push(full);
784
+ this.emit('audit', full);
785
+ }
786
+ }
787
+ exports.AgentRuntime = AgentRuntime;
788
+ // ============================================================================
789
+ // ERROR TYPES
790
+ // ============================================================================
791
+ /** Thrown when an operation violates the sandbox policy */
792
+ class RuntimePolicyError extends Error {
793
+ code = 'POLICY_VIOLATION';
794
+ constructor(message) {
795
+ super(message);
796
+ this.name = 'RuntimePolicyError';
797
+ }
798
+ }
799
+ exports.RuntimePolicyError = RuntimePolicyError;
800
+ /** Thrown when an operation is denied by the approval gate */
801
+ class RuntimeApprovalError extends Error {
802
+ code = 'APPROVAL_DENIED';
803
+ constructor(message) {
804
+ super(message);
805
+ this.name = 'RuntimeApprovalError';
806
+ }
807
+ }
808
+ exports.RuntimeApprovalError = RuntimeApprovalError;
809
+ /** Thrown when a command fails to spawn */
810
+ class RuntimeExecutionError extends Error {
811
+ code = 'EXECUTION_ERROR';
812
+ constructor(message) {
813
+ super(message);
814
+ this.name = 'RuntimeExecutionError';
815
+ }
816
+ }
817
+ exports.RuntimeExecutionError = RuntimeExecutionError;
818
+ //# sourceMappingURL=agent-runtime.js.map