@oscharko-dev/keiko 0.1.0-beta.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 (450) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +7 -0
  3. package/README.md +621 -0
  4. package/TRADEMARKS.md +41 -0
  5. package/dist/audit/aggregate.d.ts +5 -0
  6. package/dist/audit/aggregate.js +25 -0
  7. package/dist/audit/build.d.ts +2 -0
  8. package/dist/audit/build.js +224 -0
  9. package/dist/audit/errors.d.ts +25 -0
  10. package/dist/audit/errors.js +39 -0
  11. package/dist/audit/index-api.d.ts +14 -0
  12. package/dist/audit/index-api.js +131 -0
  13. package/dist/audit/index.d.ts +12 -0
  14. package/dist/audit/index.js +17 -0
  15. package/dist/audit/persist.d.ts +8 -0
  16. package/dist/audit/persist.js +40 -0
  17. package/dist/audit/redaction.d.ts +3 -0
  18. package/dist/audit/redaction.js +61 -0
  19. package/dist/audit/report.d.ts +18 -0
  20. package/dist/audit/report.js +50 -0
  21. package/dist/audit/retention.d.ts +3 -0
  22. package/dist/audit/retention.js +95 -0
  23. package/dist/audit/runid.d.ts +1 -0
  24. package/dist/audit/runid.js +29 -0
  25. package/dist/audit/side-file.d.ts +12 -0
  26. package/dist/audit/side-file.js +82 -0
  27. package/dist/audit/store.d.ts +12 -0
  28. package/dist/audit/store.js +198 -0
  29. package/dist/audit/types.d.ts +188 -0
  30. package/dist/audit/types.js +8 -0
  31. package/dist/audit/workflow-evidence.d.ts +27 -0
  32. package/dist/audit/workflow-evidence.js +145 -0
  33. package/dist/cli/context.d.ts +2 -0
  34. package/dist/cli/context.js +102 -0
  35. package/dist/cli/evaluate.d.ts +7 -0
  36. package/dist/cli/evaluate.js +207 -0
  37. package/dist/cli/evidence.d.ts +8 -0
  38. package/dist/cli/evidence.js +88 -0
  39. package/dist/cli/gateway-config.d.ts +10 -0
  40. package/dist/cli/gateway-config.js +12 -0
  41. package/dist/cli/gen-tests.d.ts +7 -0
  42. package/dist/cli/gen-tests.js +208 -0
  43. package/dist/cli/index.d.ts +2 -0
  44. package/dist/cli/index.js +14 -0
  45. package/dist/cli/investigate.d.ts +8 -0
  46. package/dist/cli/investigate.js +242 -0
  47. package/dist/cli/models.d.ts +3 -0
  48. package/dist/cli/models.js +64 -0
  49. package/dist/cli/run.d.ts +7 -0
  50. package/dist/cli/run.js +187 -0
  51. package/dist/cli/runner.d.ts +6 -0
  52. package/dist/cli/runner.js +83 -0
  53. package/dist/cli/ui.d.ts +31 -0
  54. package/dist/cli/ui.js +240 -0
  55. package/dist/cli/verify.d.ts +2 -0
  56. package/dist/cli/verify.js +103 -0
  57. package/dist/evaluations/fixtures/bug-investigation/happy-path.d.ts +2 -0
  58. package/dist/evaluations/fixtures/bug-investigation/happy-path.js +66 -0
  59. package/dist/evaluations/fixtures/bug-investigation/investigation-only.d.ts +2 -0
  60. package/dist/evaluations/fixtures/bug-investigation/investigation-only.js +39 -0
  61. package/dist/evaluations/fixtures/bug-investigation/unsafe-action.d.ts +2 -0
  62. package/dist/evaluations/fixtures/bug-investigation/unsafe-action.js +37 -0
  63. package/dist/evaluations/fixtures/index.d.ts +7 -0
  64. package/dist/evaluations/fixtures/index.js +35 -0
  65. package/dist/evaluations/fixtures/support.d.ts +5 -0
  66. package/dist/evaluations/fixtures/support.js +42 -0
  67. package/dist/evaluations/fixtures/unit-tests/happy-path.d.ts +2 -0
  68. package/dist/evaluations/fixtures/unit-tests/happy-path.js +40 -0
  69. package/dist/evaluations/fixtures/unit-tests/retry-then-accept.d.ts +2 -0
  70. package/dist/evaluations/fixtures/unit-tests/retry-then-accept.js +39 -0
  71. package/dist/evaluations/fixtures/unit-tests/unsafe-action.d.ts +2 -0
  72. package/dist/evaluations/fixtures/unit-tests/unsafe-action.js +32 -0
  73. package/dist/evaluations/index.d.ts +12 -0
  74. package/dist/evaluations/index.js +12 -0
  75. package/dist/evaluations/manifest-check.d.ts +1 -0
  76. package/dist/evaluations/manifest-check.js +48 -0
  77. package/dist/evaluations/model-provider.d.ts +12 -0
  78. package/dist/evaluations/model-provider.js +26 -0
  79. package/dist/evaluations/render.d.ts +2 -0
  80. package/dist/evaluations/render.js +59 -0
  81. package/dist/evaluations/runner-support.d.ts +27 -0
  82. package/dist/evaluations/runner-support.js +163 -0
  83. package/dist/evaluations/runner.d.ts +20 -0
  84. package/dist/evaluations/runner.js +174 -0
  85. package/dist/evaluations/scorer.d.ts +14 -0
  86. package/dist/evaluations/scorer.js +131 -0
  87. package/dist/evaluations/scripted-model.d.ts +6 -0
  88. package/dist/evaluations/scripted-model.js +26 -0
  89. package/dist/evaluations/surface-parity.d.ts +2 -0
  90. package/dist/evaluations/surface-parity.js +184 -0
  91. package/dist/evaluations/types.d.ts +74 -0
  92. package/dist/evaluations/types.js +16 -0
  93. package/dist/gateway/capabilities.d.ts +11 -0
  94. package/dist/gateway/capabilities.data.d.ts +2 -0
  95. package/dist/gateway/capabilities.data.js +203 -0
  96. package/dist/gateway/capabilities.js +41 -0
  97. package/dist/gateway/config.d.ts +15 -0
  98. package/dist/gateway/config.js +154 -0
  99. package/dist/gateway/errors.d.ts +72 -0
  100. package/dist/gateway/errors.js +82 -0
  101. package/dist/gateway/gateway.d.ts +19 -0
  102. package/dist/gateway/gateway.js +94 -0
  103. package/dist/gateway/index.d.ts +10 -0
  104. package/dist/gateway/index.js +11 -0
  105. package/dist/gateway/model-selection.d.ts +9 -0
  106. package/dist/gateway/model-selection.js +36 -0
  107. package/dist/gateway/normalize.d.ts +7 -0
  108. package/dist/gateway/normalize.js +93 -0
  109. package/dist/gateway/openai-adapter.d.ts +20 -0
  110. package/dist/gateway/openai-adapter.js +263 -0
  111. package/dist/gateway/redaction.d.ts +1 -0
  112. package/dist/gateway/redaction.js +51 -0
  113. package/dist/gateway/resilience.d.ts +24 -0
  114. package/dist/gateway/resilience.js +166 -0
  115. package/dist/gateway/types.d.ts +108 -0
  116. package/dist/gateway/types.js +2 -0
  117. package/dist/harness/adapters.d.ts +23 -0
  118. package/dist/harness/adapters.js +38 -0
  119. package/dist/harness/context.d.ts +33 -0
  120. package/dist/harness/context.js +21 -0
  121. package/dist/harness/emitter.d.ts +15 -0
  122. package/dist/harness/emitter.js +72 -0
  123. package/dist/harness/errors.d.ts +21 -0
  124. package/dist/harness/errors.js +39 -0
  125. package/dist/harness/executor.d.ts +3 -0
  126. package/dist/harness/executor.js +211 -0
  127. package/dist/harness/fingerprint.d.ts +6 -0
  128. package/dist/harness/fingerprint.js +43 -0
  129. package/dist/harness/index.d.ts +9 -0
  130. package/dist/harness/index.js +13 -0
  131. package/dist/harness/loop.d.ts +3 -0
  132. package/dist/harness/loop.js +159 -0
  133. package/dist/harness/patcher.d.ts +4 -0
  134. package/dist/harness/patcher.js +49 -0
  135. package/dist/harness/planner.d.ts +3 -0
  136. package/dist/harness/planner.js +21 -0
  137. package/dist/harness/ports.d.ts +61 -0
  138. package/dist/harness/ports.js +4 -0
  139. package/dist/harness/session.d.ts +25 -0
  140. package/dist/harness/session.js +116 -0
  141. package/dist/harness/sinks.d.ts +30 -0
  142. package/dist/harness/sinks.js +72 -0
  143. package/dist/harness/tasks/explain-plan.d.ts +3 -0
  144. package/dist/harness/tasks/explain-plan.js +29 -0
  145. package/dist/harness/tasks/generate-unit-tests.d.ts +3 -0
  146. package/dist/harness/tasks/generate-unit-tests.js +28 -0
  147. package/dist/harness/tasks/investigate-bug.d.ts +3 -0
  148. package/dist/harness/tasks/investigate-bug.js +31 -0
  149. package/dist/harness/tasks/policy.d.ts +11 -0
  150. package/dist/harness/tasks/policy.js +22 -0
  151. package/dist/harness/tasks/verify.d.ts +3 -0
  152. package/dist/harness/tasks/verify.js +16 -0
  153. package/dist/harness/types.d.ts +270 -0
  154. package/dist/harness/types.js +33 -0
  155. package/dist/index.d.ts +11 -0
  156. package/dist/index.js +36 -0
  157. package/dist/sdk/index.d.ts +9 -0
  158. package/dist/sdk/index.js +37 -0
  159. package/dist/sdk/run-agent.d.ts +16 -0
  160. package/dist/sdk/run-agent.js +56 -0
  161. package/dist/tools/browser/cdp-client.d.ts +35 -0
  162. package/dist/tools/browser/cdp-client.js +218 -0
  163. package/dist/tools/browser/errors.d.ts +25 -0
  164. package/dist/tools/browser/errors.js +55 -0
  165. package/dist/tools/browser/index.d.ts +5 -0
  166. package/dist/tools/browser/index.js +6 -0
  167. package/dist/tools/browser/session.d.ts +44 -0
  168. package/dist/tools/browser/session.js +748 -0
  169. package/dist/tools/browser/types.d.ts +48 -0
  170. package/dist/tools/browser/types.js +2 -0
  171. package/dist/tools/browser/validators.d.ts +5 -0
  172. package/dist/tools/browser/validators.js +97 -0
  173. package/dist/tools/errors.d.ts +59 -0
  174. package/dist/tools/errors.js +94 -0
  175. package/dist/tools/exec.d.ts +42 -0
  176. package/dist/tools/exec.js +327 -0
  177. package/dist/tools/index.d.ts +11 -0
  178. package/dist/tools/index.js +14 -0
  179. package/dist/tools/patch-content.d.ts +10 -0
  180. package/dist/tools/patch-content.js +126 -0
  181. package/dist/tools/patch-normalize.d.ts +1 -0
  182. package/dist/tools/patch-normalize.js +80 -0
  183. package/dist/tools/patch-parse.d.ts +8 -0
  184. package/dist/tools/patch-parse.js +201 -0
  185. package/dist/tools/patch.d.ts +18 -0
  186. package/dist/tools/patch.js +403 -0
  187. package/dist/tools/registry.d.ts +36 -0
  188. package/dist/tools/registry.js +231 -0
  189. package/dist/tools/sandbox.d.ts +8 -0
  190. package/dist/tools/sandbox.js +121 -0
  191. package/dist/tools/schemas.d.ts +2 -0
  192. package/dist/tools/schemas.js +51 -0
  193. package/dist/tools/terminal-policy.d.ts +9 -0
  194. package/dist/tools/terminal-policy.js +313 -0
  195. package/dist/tools/types.d.ts +99 -0
  196. package/dist/tools/types.js +103 -0
  197. package/dist/tools/writer.d.ts +7 -0
  198. package/dist/tools/writer.js +20 -0
  199. package/dist/ui/browser.d.ts +10 -0
  200. package/dist/ui/browser.js +231 -0
  201. package/dist/ui/chat-handlers.d.ts +4 -0
  202. package/dist/ui/chat-handlers.js +281 -0
  203. package/dist/ui/csp-hashes.json +17 -0
  204. package/dist/ui/csp.d.ts +2 -0
  205. package/dist/ui/csp.js +66 -0
  206. package/dist/ui/deps.d.ts +34 -0
  207. package/dist/ui/deps.js +137 -0
  208. package/dist/ui/evidence.d.ts +27 -0
  209. package/dist/ui/evidence.js +142 -0
  210. package/dist/ui/files-deny.d.ts +2 -0
  211. package/dist/ui/files-deny.js +12 -0
  212. package/dist/ui/files.d.ts +65 -0
  213. package/dist/ui/files.js +492 -0
  214. package/dist/ui/headers.d.ts +2 -0
  215. package/dist/ui/headers.js +21 -0
  216. package/dist/ui/host-check.d.ts +2 -0
  217. package/dist/ui/host-check.js +58 -0
  218. package/dist/ui/index.d.ts +20 -0
  219. package/dist/ui/index.js +23 -0
  220. package/dist/ui/load-csp.d.ts +1 -0
  221. package/dist/ui/load-csp.js +28 -0
  222. package/dist/ui/read-handlers.d.ts +8 -0
  223. package/dist/ui/read-handlers.js +247 -0
  224. package/dist/ui/routes.d.ts +36 -0
  225. package/dist/ui/routes.js +129 -0
  226. package/dist/ui/run-engine.d.ts +20 -0
  227. package/dist/ui/run-engine.js +345 -0
  228. package/dist/ui/run-handlers.d.ts +8 -0
  229. package/dist/ui/run-handlers.js +431 -0
  230. package/dist/ui/run-request.d.ts +13 -0
  231. package/dist/ui/run-request.js +219 -0
  232. package/dist/ui/runs.d.ts +43 -0
  233. package/dist/ui/runs.js +92 -0
  234. package/dist/ui/server.d.ts +11 -0
  235. package/dist/ui/server.js +143 -0
  236. package/dist/ui/sink.d.ts +27 -0
  237. package/dist/ui/sink.js +80 -0
  238. package/dist/ui/sse.d.ts +7 -0
  239. package/dist/ui/sse.js +27 -0
  240. package/dist/ui/static/404.html +1 -0
  241. package/dist/ui/static/_next/static/ca-A01hy9W98aRvMZKdAw/_buildManifest.js +1 -0
  242. package/dist/ui/static/_next/static/ca-A01hy9W98aRvMZKdAw/_ssgManifest.js +1 -0
  243. package/dist/ui/static/_next/static/chunks/255-d47fd57964443afe.js +1 -0
  244. package/dist/ui/static/_next/static/chunks/4-be1fef693af8e088.js +1 -0
  245. package/dist/ui/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  246. package/dist/ui/static/_next/static/chunks/app/_not-found/page-75825b09bcecad97.js +1 -0
  247. package/dist/ui/static/_next/static/chunks/app/launch/page-9c86a13c29884245.js +1 -0
  248. package/dist/ui/static/_next/static/chunks/app/layout-bdea63fe87947d50.js +1 -0
  249. package/dist/ui/static/_next/static/chunks/app/page-4168c12c68b7a853.js +1 -0
  250. package/dist/ui/static/_next/static/chunks/framework-a6e0b7e30f98059a.js +1 -0
  251. package/dist/ui/static/_next/static/chunks/main-778a50aebff02192.js +1 -0
  252. package/dist/ui/static/_next/static/chunks/main-app-30679af7240d63e9.js +1 -0
  253. package/dist/ui/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  254. package/dist/ui/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  255. package/dist/ui/static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  256. package/dist/ui/static/_next/static/chunks/webpack-4a462cecab786e93.js +1 -0
  257. package/dist/ui/static/_next/static/css/be7cb54d5c5673b6.css +1 -0
  258. package/dist/ui/static/assets/editors/goland.svg +35 -0
  259. package/dist/ui/static/assets/editors/intellij.svg +39 -0
  260. package/dist/ui/static/assets/editors/pycharm.svg +58 -0
  261. package/dist/ui/static/assets/editors/rustrover.svg +19 -0
  262. package/dist/ui/static/assets/editors/vscode.svg +1 -0
  263. package/dist/ui/static/assets/editors/webstorm.svg +21 -0
  264. package/dist/ui/static/assets/icons/anthropic.svg +1 -0
  265. package/dist/ui/static/assets/icons/brave.svg +1 -0
  266. package/dist/ui/static/assets/icons/css3.svg +1 -0
  267. package/dist/ui/static/assets/icons/docker.svg +1 -0
  268. package/dist/ui/static/assets/icons/git.svg +1 -0
  269. package/dist/ui/static/assets/icons/github.svg +1 -0
  270. package/dist/ui/static/assets/icons/go.svg +1 -0
  271. package/dist/ui/static/assets/icons/gradle.svg +1 -0
  272. package/dist/ui/static/assets/icons/grafana.svg +1 -0
  273. package/dist/ui/static/assets/icons/graphql.svg +1 -0
  274. package/dist/ui/static/assets/icons/html5.svg +1 -0
  275. package/dist/ui/static/assets/icons/image.svg +1 -0
  276. package/dist/ui/static/assets/icons/java.svg +1 -0
  277. package/dist/ui/static/assets/icons/javascript.svg +1 -0
  278. package/dist/ui/static/assets/icons/json.svg +1 -0
  279. package/dist/ui/static/assets/icons/kafka.svg +1 -0
  280. package/dist/ui/static/assets/icons/kubernetes.svg +1 -0
  281. package/dist/ui/static/assets/icons/linear.svg +1 -0
  282. package/dist/ui/static/assets/icons/markdown.svg +1 -0
  283. package/dist/ui/static/assets/icons/nginx.svg +1 -0
  284. package/dist/ui/static/assets/icons/nodejs.svg +1 -0
  285. package/dist/ui/static/assets/icons/notion.svg +1 -0
  286. package/dist/ui/static/assets/icons/openai.svg +1 -0
  287. package/dist/ui/static/assets/icons/playwright.svg +1 -0
  288. package/dist/ui/static/assets/icons/postgresql.svg +1 -0
  289. package/dist/ui/static/assets/icons/prometheus.svg +1 -0
  290. package/dist/ui/static/assets/icons/properties.svg +1 -0
  291. package/dist/ui/static/assets/icons/puppeteer.svg +1 -0
  292. package/dist/ui/static/assets/icons/python.svg +1 -0
  293. package/dist/ui/static/assets/icons/react.svg +1 -0
  294. package/dist/ui/static/assets/icons/redis.svg +1 -0
  295. package/dist/ui/static/assets/icons/rust.svg +1 -0
  296. package/dist/ui/static/assets/icons/sentry.svg +1 -0
  297. package/dist/ui/static/assets/icons/slack.svg +1 -0
  298. package/dist/ui/static/assets/icons/spring.svg +1 -0
  299. package/dist/ui/static/assets/icons/typescript.svg +1 -0
  300. package/dist/ui/static/assets/icons/upstash.svg +1 -0
  301. package/dist/ui/static/assets/icons/yaml.svg +1 -0
  302. package/dist/ui/static/assets/keiko-logo.svg +10 -0
  303. package/dist/ui/static/index.html +1 -0
  304. package/dist/ui/static/index.txt +19 -0
  305. package/dist/ui/static/keiko-logo.svg +10 -0
  306. package/dist/ui/static/launch.html +1 -0
  307. package/dist/ui/static/launch.txt +19 -0
  308. package/dist/ui/static.d.ts +3 -0
  309. package/dist/ui/static.js +72 -0
  310. package/dist/ui/store/chats.d.ts +14 -0
  311. package/dist/ui/store/chats.js +110 -0
  312. package/dist/ui/store/db.d.ts +6 -0
  313. package/dist/ui/store/db.js +182 -0
  314. package/dist/ui/store/errors.d.ts +12 -0
  315. package/dist/ui/store/errors.js +30 -0
  316. package/dist/ui/store/index.d.ts +6 -0
  317. package/dist/ui/store/index.js +6 -0
  318. package/dist/ui/store/messages.d.ts +5 -0
  319. package/dist/ui/store/messages.js +137 -0
  320. package/dist/ui/store/paths.d.ts +4 -0
  321. package/dist/ui/store/paths.js +69 -0
  322. package/dist/ui/store/projects.d.ts +7 -0
  323. package/dist/ui/store/projects.js +61 -0
  324. package/dist/ui/store/schema.d.ts +3 -0
  325. package/dist/ui/store/schema.js +77 -0
  326. package/dist/ui/store/types.d.ts +80 -0
  327. package/dist/ui/store/types.js +3 -0
  328. package/dist/ui/store/validation.d.ts +4 -0
  329. package/dist/ui/store/validation.js +72 -0
  330. package/dist/ui/store-handlers.d.ts +16 -0
  331. package/dist/ui/store-handlers.js +465 -0
  332. package/dist/ui/terminal-errors.d.ts +21 -0
  333. package/dist/ui/terminal-errors.js +45 -0
  334. package/dist/ui/terminal-evidence.d.ts +20 -0
  335. package/dist/ui/terminal-evidence.js +65 -0
  336. package/dist/ui/terminal-routes.d.ts +9 -0
  337. package/dist/ui/terminal-routes.js +219 -0
  338. package/dist/ui/terminal.d.ts +67 -0
  339. package/dist/ui/terminal.js +835 -0
  340. package/dist/verification/classify.d.ts +10 -0
  341. package/dist/verification/classify.js +53 -0
  342. package/dist/verification/detect.d.ts +4 -0
  343. package/dist/verification/detect.js +81 -0
  344. package/dist/verification/errors.d.ts +11 -0
  345. package/dist/verification/errors.js +21 -0
  346. package/dist/verification/index.d.ts +17 -0
  347. package/dist/verification/index.js +13 -0
  348. package/dist/verification/limits.d.ts +3 -0
  349. package/dist/verification/limits.js +40 -0
  350. package/dist/verification/monitor.d.ts +4 -0
  351. package/dist/verification/monitor.js +58 -0
  352. package/dist/verification/orchestrator.d.ts +16 -0
  353. package/dist/verification/orchestrator.js +363 -0
  354. package/dist/verification/plan.d.ts +9 -0
  355. package/dist/verification/plan.js +125 -0
  356. package/dist/verification/summary.d.ts +40 -0
  357. package/dist/verification/summary.js +67 -0
  358. package/dist/verification/types.d.ts +63 -0
  359. package/dist/verification/types.js +13 -0
  360. package/dist/workflows/bug-investigation/context.d.ts +7 -0
  361. package/dist/workflows/bug-investigation/context.js +119 -0
  362. package/dist/workflows/bug-investigation/descriptor.d.ts +3 -0
  363. package/dist/workflows/bug-investigation/descriptor.js +46 -0
  364. package/dist/workflows/bug-investigation/emit.d.ts +12 -0
  365. package/dist/workflows/bug-investigation/emit.js +35 -0
  366. package/dist/workflows/bug-investigation/events.d.ts +81 -0
  367. package/dist/workflows/bug-investigation/events.js +9 -0
  368. package/dist/workflows/bug-investigation/failure-parse.d.ts +3 -0
  369. package/dist/workflows/bug-investigation/failure-parse.js +154 -0
  370. package/dist/workflows/bug-investigation/guard.d.ts +2 -0
  371. package/dist/workflows/bug-investigation/guard.js +69 -0
  372. package/dist/workflows/bug-investigation/index.d.ts +7 -0
  373. package/dist/workflows/bug-investigation/index.js +13 -0
  374. package/dist/workflows/bug-investigation/internal.d.ts +37 -0
  375. package/dist/workflows/bug-investigation/internal.js +64 -0
  376. package/dist/workflows/bug-investigation/model-loop.d.ts +4 -0
  377. package/dist/workflows/bug-investigation/model-loop.js +223 -0
  378. package/dist/workflows/bug-investigation/parse.d.ts +3 -0
  379. package/dist/workflows/bug-investigation/parse.js +123 -0
  380. package/dist/workflows/bug-investigation/prompt.d.ts +4 -0
  381. package/dist/workflows/bug-investigation/prompt.js +107 -0
  382. package/dist/workflows/bug-investigation/report.d.ts +23 -0
  383. package/dist/workflows/bug-investigation/report.js +151 -0
  384. package/dist/workflows/bug-investigation/stages.d.ts +13 -0
  385. package/dist/workflows/bug-investigation/stages.js +242 -0
  386. package/dist/workflows/bug-investigation/types.d.ts +91 -0
  387. package/dist/workflows/bug-investigation/types.js +14 -0
  388. package/dist/workflows/bug-investigation/verify-stage.d.ts +10 -0
  389. package/dist/workflows/bug-investigation/verify-stage.js +91 -0
  390. package/dist/workflows/bug-investigation/workflow.d.ts +2 -0
  391. package/dist/workflows/bug-investigation/workflow.js +74 -0
  392. package/dist/workflows/descriptor.d.ts +20 -0
  393. package/dist/workflows/descriptor.js +8 -0
  394. package/dist/workflows/index.d.ts +3 -0
  395. package/dist/workflows/index.js +2 -0
  396. package/dist/workflows/unit-tests/context.d.ts +7 -0
  397. package/dist/workflows/unit-tests/context.js +129 -0
  398. package/dist/workflows/unit-tests/conventions.d.ts +4 -0
  399. package/dist/workflows/unit-tests/conventions.js +87 -0
  400. package/dist/workflows/unit-tests/descriptor.d.ts +4 -0
  401. package/dist/workflows/unit-tests/descriptor.js +43 -0
  402. package/dist/workflows/unit-tests/emit.d.ts +12 -0
  403. package/dist/workflows/unit-tests/emit.js +35 -0
  404. package/dist/workflows/unit-tests/events.d.ts +78 -0
  405. package/dist/workflows/unit-tests/events.js +7 -0
  406. package/dist/workflows/unit-tests/index.d.ts +6 -0
  407. package/dist/workflows/unit-tests/index.js +10 -0
  408. package/dist/workflows/unit-tests/internal.d.ts +35 -0
  409. package/dist/workflows/unit-tests/internal.js +43 -0
  410. package/dist/workflows/unit-tests/model-loop.d.ts +4 -0
  411. package/dist/workflows/unit-tests/model-loop.js +95 -0
  412. package/dist/workflows/unit-tests/parse.d.ts +6 -0
  413. package/dist/workflows/unit-tests/parse.js +68 -0
  414. package/dist/workflows/unit-tests/prompt.d.ts +4 -0
  415. package/dist/workflows/unit-tests/prompt.js +71 -0
  416. package/dist/workflows/unit-tests/report.d.ts +21 -0
  417. package/dist/workflows/unit-tests/report.js +90 -0
  418. package/dist/workflows/unit-tests/stages.d.ts +9 -0
  419. package/dist/workflows/unit-tests/stages.js +155 -0
  420. package/dist/workflows/unit-tests/types.d.ts +70 -0
  421. package/dist/workflows/unit-tests/types.js +11 -0
  422. package/dist/workflows/unit-tests/verify-stage.d.ts +9 -0
  423. package/dist/workflows/unit-tests/verify-stage.js +56 -0
  424. package/dist/workflows/unit-tests/workflow.d.ts +2 -0
  425. package/dist/workflows/unit-tests/workflow.js +58 -0
  426. package/dist/workspace/contextPack.d.ts +9 -0
  427. package/dist/workspace/contextPack.js +94 -0
  428. package/dist/workspace/detect.d.ts +3 -0
  429. package/dist/workspace/detect.js +135 -0
  430. package/dist/workspace/discovery.d.ts +9 -0
  431. package/dist/workspace/discovery.js +167 -0
  432. package/dist/workspace/errors.d.ts +39 -0
  433. package/dist/workspace/errors.js +66 -0
  434. package/dist/workspace/fs.d.ts +21 -0
  435. package/dist/workspace/fs.js +36 -0
  436. package/dist/workspace/ignore.d.ts +14 -0
  437. package/dist/workspace/ignore.js +176 -0
  438. package/dist/workspace/index.d.ts +11 -0
  439. package/dist/workspace/index.js +13 -0
  440. package/dist/workspace/paths.d.ts +2 -0
  441. package/dist/workspace/paths.js +38 -0
  442. package/dist/workspace/realpath.d.ts +7 -0
  443. package/dist/workspace/realpath.js +72 -0
  444. package/dist/workspace/retrieval.d.ts +9 -0
  445. package/dist/workspace/retrieval.js +74 -0
  446. package/dist/workspace/summary.d.ts +3 -0
  447. package/dist/workspace/summary.js +54 -0
  448. package/dist/workspace/types.d.ts +103 -0
  449. package/dist/workspace/types.js +27 -0
  450. package/package.json +58 -0
@@ -0,0 +1,403 @@
1
+ // Patch workflow: validate (all checks, structured report), renderDryRun (preview, never
2
+ // writes), and applyPatch (fail-closed, atomic with multi-file rollback). Every target path is
3
+ // validated via resolveWithinWorkspace + isDenied; the validated absolute path is the ONLY value
4
+ // handed to the WorkspaceWriter, so a static analyser's path sanitizer sits on this boundary
5
+ // (ADR-0006 D4). node:fs is never imported here — reads go through WorkspaceFs, writes through
6
+ // WorkspaceWriter, both injected.
7
+ import { isDenied } from "../workspace/ignore.js";
8
+ import { resolveWithinWorkspace } from "../workspace/paths.js";
9
+ import { containedRealPathInfo } from "../workspace/realpath.js";
10
+ import { PathDeniedError, PathEscapeError } from "../workspace/errors.js";
11
+ import { nodeWorkspaceFs } from "../workspace/fs.js";
12
+ import { CommandCancelledError, PatchApplyDisabledError, PatchApplyError, PatchValidationError, } from "./errors.js";
13
+ import { computeFileContent } from "./patch-content.js";
14
+ import { normalizeUnifiedDiffHunks } from "./patch-normalize.js";
15
+ import { parseUnifiedDiff, PatchParseError } from "./patch-parse.js";
16
+ import { nodeWorkspaceWriter } from "./writer.js";
17
+ import { DEFAULT_PATCH_LIMITS, } from "./types.js";
18
+ function containsNul(value) {
19
+ return value.includes("\u0000");
20
+ }
21
+ function isBinaryDiff(diff) {
22
+ return (diff.includes("GIT binary patch") || /^Binary files .* differ$/m.test(diff) || containsNul(diff));
23
+ }
24
+ function hasEscapedDiffLineBreak(diff) {
25
+ return diff.includes("\\n+") || diff.includes("\\n-") || diff.includes("\\n ");
26
+ }
27
+ function enforcePath(workspace, fs, path) {
28
+ let resolved;
29
+ try {
30
+ resolved = resolveWithinWorkspace(workspace.root, path);
31
+ }
32
+ catch (error) {
33
+ if (error instanceof PathEscapeError) {
34
+ throw error;
35
+ }
36
+ throw error;
37
+ }
38
+ const rel = resolved.slice(workspace.root.length).replace(/^[/\\]/, "");
39
+ if (isDenied(rel === "" ? path : rel)) {
40
+ throw new PathDeniedError("path matches an always-on deny pattern", path);
41
+ }
42
+ const info = containedRealPathInfo(fs, workspace.root, resolved);
43
+ if (!realPathMatchesLexicalTarget(fs, resolved, rel, info.realRelative)) {
44
+ throw new PathDeniedError("path resolves through an in-workspace alias", path);
45
+ }
46
+ if (fs.exists(resolved) && (fs.stat(resolved).hardLinkCount ?? 1) > 1) {
47
+ throw new PathDeniedError("path resolves through a hard-linked alias", path);
48
+ }
49
+ if (isDenied(info.realRelative)) {
50
+ throw new PathDeniedError("path matches an always-on deny pattern", path);
51
+ }
52
+ return resolved;
53
+ }
54
+ function realPathMatchesLexicalTarget(fs, absolutePath, rel, realRelative) {
55
+ if (fs.exists(absolutePath)) {
56
+ return realRelative === rel;
57
+ }
58
+ return realRelative === "" || rel === realRelative || rel.startsWith(`${realRelative}/`);
59
+ }
60
+ function safePath(workspace, fs, path) {
61
+ try {
62
+ enforcePath(workspace, fs, path);
63
+ return undefined;
64
+ }
65
+ catch (error) {
66
+ if (error instanceof PathEscapeError) {
67
+ return { code: "path-unsafe", message: "path escapes the workspace", path };
68
+ }
69
+ if (error instanceof PathDeniedError) {
70
+ return { code: "path-denied", message: "path matches an always-on deny pattern", path };
71
+ }
72
+ throw error;
73
+ }
74
+ }
75
+ function collectPathReasons(workspace, fs, files) {
76
+ const reasons = [];
77
+ for (const file of files) {
78
+ const rejection = safePath(workspace, fs, file.path);
79
+ if (rejection !== undefined) {
80
+ reasons.push(rejection);
81
+ }
82
+ }
83
+ return reasons;
84
+ }
85
+ function readCurrent(workspace, fs, path) {
86
+ const absolute = resolveWithinWorkspace(workspace.root, path);
87
+ if (!fs.exists(absolute)) {
88
+ return undefined;
89
+ }
90
+ return fs.readFileUtf8(absolute);
91
+ }
92
+ function toLines(content) {
93
+ if (content.length === 0) {
94
+ return [];
95
+ }
96
+ const lines = content.split("\n");
97
+ if (lines.at(-1) === "") {
98
+ lines.pop();
99
+ }
100
+ return lines;
101
+ }
102
+ function hunkPreimageLines(file, hunkIndex) {
103
+ const hunk = file.hunks[hunkIndex];
104
+ if (hunk === undefined) {
105
+ return [];
106
+ }
107
+ return hunk.lines
108
+ .filter((line) => line.startsWith(" ") || line.startsWith("-"))
109
+ .map((line) => line.slice(1));
110
+ }
111
+ function startsWithSequence(lines, index, needle) {
112
+ return needle.every((line, offset) => lines[index + offset] === line);
113
+ }
114
+ function uniqueSequenceIndex(lines, needle) {
115
+ if (needle.length === 0 || needle.length > lines.length) {
116
+ return undefined;
117
+ }
118
+ let found;
119
+ for (let index = 0; index <= lines.length - needle.length; index += 1) {
120
+ if (!startsWithSequence(lines, index, needle)) {
121
+ continue;
122
+ }
123
+ if (found !== undefined) {
124
+ return undefined;
125
+ }
126
+ found = index;
127
+ }
128
+ return found;
129
+ }
130
+ function alignFileHunks(file, current) {
131
+ if (file.kind !== "modify") {
132
+ return file;
133
+ }
134
+ if (current === undefined) {
135
+ return isCreateOnlyModify(file) ? { ...file, kind: "create" } : file;
136
+ }
137
+ const currentLines = toLines(current);
138
+ const hunks = file.hunks.map((hunk, index) => {
139
+ const preimage = hunkPreimageLines(file, index);
140
+ if (hunk.oldStart > 0 && startsWithSequence(currentLines, hunk.oldStart - 1, preimage)) {
141
+ return hunk;
142
+ }
143
+ const anchor = uniqueSequenceIndex(currentLines, preimage);
144
+ if (anchor === undefined) {
145
+ return hunk;
146
+ }
147
+ const start = anchor + 1;
148
+ return { ...hunk, oldStart: start, newStart: start };
149
+ });
150
+ if (hunks.every((hunk, index) => hunk === file.hunks[index])) {
151
+ return file;
152
+ }
153
+ return { ...file, hunks };
154
+ }
155
+ function isCreateOnlyModify(file) {
156
+ return (file.hunks.length > 0 &&
157
+ file.hunks.every((hunk) => hunk.oldLines === 0 && hunk.lines.every((line) => line.startsWith("+"))));
158
+ }
159
+ function alignHunksToCurrentContent(workspace, fs, files) {
160
+ return files.map((file) => alignFileHunks(file, readCurrent(workspace, fs, file.path)));
161
+ }
162
+ function unanchoredModifyReasons(files) {
163
+ return files
164
+ .filter((file) => file.kind === "modify" && file.hunks.some((hunk) => hunk.oldStart <= 0))
165
+ .map((file) => ({
166
+ code: "malformed",
167
+ message: "modify hunk has no unique anchor",
168
+ path: file.path,
169
+ }));
170
+ }
171
+ function collectConflicts(workspace, fs, files) {
172
+ const conflicts = [];
173
+ for (const file of files) {
174
+ const current = readCurrent(workspace, fs, file.path);
175
+ const outcome = computeFileContent(file, current);
176
+ for (const conflict of outcome.conflicts) {
177
+ conflicts.push({ path: file.path, hunkIndex: conflict.hunkIndex, reason: conflict.reason });
178
+ }
179
+ }
180
+ return conflicts;
181
+ }
182
+ function sizeAndCountReasons(diff, files, totalChangedLines, limits, totalBytes) {
183
+ const reasons = [];
184
+ if (diff.trim().length > 0 && files.length === 0) {
185
+ reasons.push({ code: "malformed", message: "diff does not contain any file changes" });
186
+ }
187
+ if (totalBytes > limits.maxPatchBytes) {
188
+ reasons.push({
189
+ code: "size-limit",
190
+ message: `patch exceeds ${String(limits.maxPatchBytes)} bytes`,
191
+ });
192
+ }
193
+ if (isBinaryDiff(diff)) {
194
+ reasons.push({ code: "binary", message: "binary patches are not supported" });
195
+ }
196
+ if (hasEscapedDiffLineBreak(diff)) {
197
+ reasons.push({
198
+ code: "malformed",
199
+ message: "diff contains escaped newline markers; use real line breaks",
200
+ });
201
+ }
202
+ if (totalChangedLines > limits.maxChangedLines) {
203
+ reasons.push({
204
+ code: "line-limit",
205
+ message: `changed lines exceed ${String(limits.maxChangedLines)}`,
206
+ });
207
+ }
208
+ if (files.length > limits.maxFilesChanged) {
209
+ reasons.push({
210
+ code: "file-limit",
211
+ message: `files changed exceed ${String(limits.maxFilesChanged)}`,
212
+ });
213
+ }
214
+ return reasons;
215
+ }
216
+ function renderHeader(file) {
217
+ if (file.kind === "create") {
218
+ return ["--- /dev/null", `+++ b/${file.path}`];
219
+ }
220
+ if (file.kind === "delete") {
221
+ return [`--- a/${file.path}`, "+++ /dev/null"];
222
+ }
223
+ return [`--- a/${file.path}`, `+++ b/${file.path}`];
224
+ }
225
+ function renderParsedPatch(files) {
226
+ const lines = [];
227
+ for (const file of files) {
228
+ lines.push(...renderHeader(file));
229
+ for (const hunk of file.hunks) {
230
+ lines.push(`@@ -${String(hunk.oldStart)},${String(hunk.oldLines)} +${String(hunk.newStart)},${String(hunk.newLines)} @@`, ...hunk.lines);
231
+ }
232
+ }
233
+ return lines.join("\n");
234
+ }
235
+ function parseDiffForValidation(diff) {
236
+ try {
237
+ return { files: parseUnifiedDiff(diff).files, effectiveDiff: diff, normalized: false };
238
+ }
239
+ catch (error) {
240
+ if (!(error instanceof PatchParseError)) {
241
+ throw error;
242
+ }
243
+ const normalizedDiff = normalizeUnifiedDiffHunks(diff);
244
+ if (normalizedDiff === diff) {
245
+ throw error;
246
+ }
247
+ return {
248
+ files: parseUnifiedDiff(normalizedDiff).files,
249
+ effectiveDiff: normalizedDiff,
250
+ normalized: true,
251
+ };
252
+ }
253
+ }
254
+ function malformedValidation(diff, error) {
255
+ const message = error instanceof PatchParseError ? error.message : "unparseable diff";
256
+ return {
257
+ ok: false,
258
+ files: [],
259
+ totalChangedLines: 0,
260
+ totalBytes: Buffer.byteLength(diff, "utf8"),
261
+ reasons: [{ code: "malformed", message }],
262
+ conflicts: [],
263
+ };
264
+ }
265
+ function completeValidation(workspace, fs, limits, diff, parsed) {
266
+ const files = parsed.files;
267
+ const totalBytes = Buffer.byteLength(parsed.effectiveDiff, "utf8");
268
+ const totalChangedLines = files.reduce((sum, f) => sum + f.addedLines + f.removedLines, 0);
269
+ const pathAndSizeReasons = [
270
+ ...sizeAndCountReasons(parsed.effectiveDiff, files, totalChangedLines, limits, totalBytes),
271
+ ...collectPathReasons(workspace, fs, files),
272
+ ];
273
+ const alignedFiles = pathAndSizeReasons.length === 0 ? alignHunksToCurrentContent(workspace, fs, files) : files;
274
+ const aligned = alignedFiles.some((file, index) => file !== files[index]);
275
+ const effectiveDiff = parsed.normalized || aligned ? renderParsedPatch(alignedFiles) : diff;
276
+ const reasons = [...pathAndSizeReasons, ...unanchoredModifyReasons(alignedFiles)];
277
+ const conflicts = reasons.length === 0 ? collectConflicts(workspace, fs, alignedFiles) : [];
278
+ return {
279
+ ok: reasons.length === 0 && conflicts.length === 0,
280
+ files: alignedFiles,
281
+ totalChangedLines,
282
+ totalBytes: Buffer.byteLength(effectiveDiff, "utf8"),
283
+ ...(effectiveDiff === diff ? {} : { normalizedDiff: effectiveDiff }),
284
+ reasons,
285
+ conflicts,
286
+ };
287
+ }
288
+ export function validatePatch(workspace, diff, deps = {}) {
289
+ const fs = deps.fs ?? nodeWorkspaceFs;
290
+ const limits = deps.limits ?? DEFAULT_PATCH_LIMITS;
291
+ try {
292
+ return completeValidation(workspace, fs, limits, diff, parseDiffForValidation(diff));
293
+ }
294
+ catch (error) {
295
+ return malformedValidation(diff, error);
296
+ }
297
+ }
298
+ function renderFileLine(file) {
299
+ return `${file.kind} ${file.path} (+${String(file.addedLines)} -${String(file.removedLines)})`;
300
+ }
301
+ // Human-readable preview returned by propose_patch. NEVER writes. Lists per-file +/- counts,
302
+ // any rejection reasons, and any conflicts so the reviewer sees exactly what apply would do.
303
+ export function renderDryRun(validation) {
304
+ const header = validation.ok
305
+ ? `PATCH OK — ${String(validation.files.length)} file(s), ${String(validation.totalChangedLines)} changed line(s)`
306
+ : "PATCH REJECTED";
307
+ const fileLines = validation.files.map(renderFileLine);
308
+ const reasonLines = validation.reasons.map((r) => `reject[${r.code}]: ${r.message}${r.path === undefined ? "" : ` (${r.path})`}`);
309
+ const conflictLines = validation.conflicts.map((c) => `conflict: ${c.path} hunk#${String(c.hunkIndex)}: ${c.reason}`);
310
+ return [header, ...fileLines, ...reasonLines, ...conflictLines].join("\n");
311
+ }
312
+ function planWrites(workspace, fs, signal, files) {
313
+ const plans = [];
314
+ for (const file of files) {
315
+ if (signal.aborted) {
316
+ throw new CommandCancelledError("apply cancelled before write planning completed");
317
+ }
318
+ const absolute = enforcePath(workspace, fs, file.path);
319
+ const original = readCurrent(workspace, fs, file.path);
320
+ const outcome = computeFileContent(file, original);
321
+ plans.push({
322
+ path: file.path,
323
+ absolute,
324
+ kind: file.kind,
325
+ newContent: outcome.content,
326
+ original,
327
+ });
328
+ }
329
+ return plans;
330
+ }
331
+ function applyOne(writer, plan) {
332
+ if (plan.kind === "delete") {
333
+ writer.remove(plan.absolute);
334
+ return;
335
+ }
336
+ const dir = plan.absolute.replace(/[/\\][^/\\]*$/, "");
337
+ writer.mkdirp(dir);
338
+ writer.writeFileUtf8(plan.absolute, plan.newContent ?? "");
339
+ }
340
+ function rollback(writer, done) {
341
+ for (const plan of done) {
342
+ if (plan.original === undefined) {
343
+ writer.remove(plan.absolute);
344
+ }
345
+ else {
346
+ writer.writeFileUtf8(plan.absolute, plan.original);
347
+ }
348
+ }
349
+ }
350
+ function commit(writer, plans, signal) {
351
+ const done = [];
352
+ for (const plan of plans) {
353
+ if (isAbortRequested(signal)) {
354
+ rollback(writer, done);
355
+ throw new CommandCancelledError("apply cancelled during write phase");
356
+ }
357
+ try {
358
+ applyOne(writer, plan);
359
+ done.push(plan);
360
+ }
361
+ catch (error) {
362
+ rollback(writer, done);
363
+ const message = error instanceof Error ? error.message : "write failed";
364
+ throw new PatchApplyError(`apply failed, rolled back: ${message}`, plan.path);
365
+ }
366
+ if (isAbortRequested(signal)) {
367
+ rollback(writer, done);
368
+ throw new CommandCancelledError("apply cancelled during write phase");
369
+ }
370
+ }
371
+ }
372
+ function isAbortRequested(signal) {
373
+ return signal.aborted;
374
+ }
375
+ function summarize(plans) {
376
+ return {
377
+ changedFiles: plans.map((p) => p.path),
378
+ created: plans.filter((p) => p.kind === "create").map((p) => p.path),
379
+ deleted: plans.filter((p) => p.kind === "delete").map((p) => p.path),
380
+ };
381
+ }
382
+ // Applies a validated patch atomically. Fail-closed: no write unless applyEnabled === true.
383
+ // Order: gate → validate → abort check → plan (pure) → write with multi-file rollback.
384
+ export function applyPatch(workspace, diff, deps) {
385
+ if (!deps.applyEnabled) {
386
+ throw new PatchApplyDisabledError("apply is disabled (applyEnabled is false)");
387
+ }
388
+ const fs = deps.fs ?? nodeWorkspaceFs;
389
+ const writer = deps.writer ?? nodeWorkspaceWriter;
390
+ const validation = validatePatch(workspace, diff, {
391
+ fs,
392
+ ...(deps.limits ? { limits: deps.limits } : {}),
393
+ });
394
+ if (!validation.ok) {
395
+ throw new PatchValidationError("patch failed validation", validation.reasons, validation.conflicts);
396
+ }
397
+ if (deps.signal.aborted) {
398
+ throw new CommandCancelledError("apply cancelled before write phase");
399
+ }
400
+ const plans = planWrites(workspace, fs, deps.signal, validation.files);
401
+ commit(writer, plans, deps.signal);
402
+ return summarize(plans);
403
+ }
@@ -0,0 +1,36 @@
1
+ import type { ToolDefinition } from "../gateway/types.js";
2
+ import type { ToolCallRequest, ToolCallResult, ToolPort } from "../harness/ports.js";
3
+ import { type WorkspaceFs } from "../workspace/fs.js";
4
+ import type { WorkspaceInfo } from "../workspace/types.js";
5
+ import { type ExecutableResolver, type SpawnFn } from "./exec.js";
6
+ import { type WorkspaceWriter } from "./writer.js";
7
+ import { type ToolHostConfigInput } from "./types.js";
8
+ export declare class WorkspaceToolHost implements ToolPort {
9
+ private readonly workspace;
10
+ private readonly fs;
11
+ private readonly writer;
12
+ private readonly spawn;
13
+ private readonly resolveExecutable;
14
+ private readonly config;
15
+ private readonly processEnv;
16
+ private readonly now;
17
+ constructor(deps: {
18
+ readonly workspace: WorkspaceInfo;
19
+ readonly fs?: WorkspaceFs | undefined;
20
+ readonly writer?: WorkspaceWriter | undefined;
21
+ readonly spawn?: SpawnFn | undefined;
22
+ readonly resolveExecutable?: ExecutableResolver | undefined;
23
+ readonly config?: ToolHostConfigInput | undefined;
24
+ readonly processEnv?: NodeJS.ProcessEnv | undefined;
25
+ readonly now?: (() => number) | undefined;
26
+ });
27
+ listTools(): readonly ToolDefinition[];
28
+ execute(request: ToolCallRequest): Promise<ToolCallResult>;
29
+ private dispatch;
30
+ private readFile;
31
+ private listFiles;
32
+ private inspectScripts;
33
+ private runCommandTool;
34
+ private proposePatch;
35
+ private applyPatchTool;
36
+ }
@@ -0,0 +1,231 @@
1
+ // The tool host: WorkspaceToolHost implements the harness ToolPort. It narrows untrusted
2
+ // `Record<string,unknown>` arguments, dispatches the 6 tools through a handler map, and returns
3
+ // a ToolCallResult whose `output` is already redacted. run_command is the only tool that sets
4
+ // commandExecuted:true (the executor counts those against maxCommandExecutions). All filesystem
5
+ // reads go through the workspace layer; all writes through the WorkspaceWriter; all spawns through
6
+ // the injected SpawnFn — so a unit test needs no real secrets and no real processes.
7
+ import { discoverWithStats, readWorkspaceFile } from "../workspace/discovery.js";
8
+ import { nodeWorkspaceFs } from "../workspace/fs.js";
9
+ import { nodeSpawnFn, runCommand } from "./exec.js";
10
+ import { CommandCancelledError, ToolArgumentError, UnknownToolError } from "./errors.js";
11
+ import { applyPatch, renderDryRun, validatePatch } from "./patch.js";
12
+ import { TOOL_DEFINITIONS } from "./schemas.js";
13
+ import { nodeWorkspaceWriter } from "./writer.js";
14
+ import { resolveToolHostConfig, } from "./types.js";
15
+ function requireString(args, key, tool) {
16
+ const value = args[key];
17
+ if (typeof value !== "string" || value.length === 0) {
18
+ throw new ToolArgumentError(`argument '${key}' must be a non-empty string`, tool);
19
+ }
20
+ return value;
21
+ }
22
+ function optionalString(args, key, tool) {
23
+ const value = args[key];
24
+ if (value === undefined) {
25
+ return undefined;
26
+ }
27
+ if (typeof value !== "string") {
28
+ throw new ToolArgumentError(`argument '${key}' must be a string`, tool);
29
+ }
30
+ return value;
31
+ }
32
+ function optionalNumber(args, key, tool) {
33
+ const value = args[key];
34
+ if (value === undefined) {
35
+ return undefined;
36
+ }
37
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
38
+ throw new ToolArgumentError(`argument '${key}' must be a non-negative number`, tool);
39
+ }
40
+ return value;
41
+ }
42
+ function optionalBoolean(args, key, tool) {
43
+ const value = args[key];
44
+ if (value === undefined) {
45
+ return undefined;
46
+ }
47
+ if (typeof value !== "boolean") {
48
+ throw new ToolArgumentError(`argument '${key}' must be a boolean`, tool);
49
+ }
50
+ return value;
51
+ }
52
+ function optionalStringArray(args, key, tool) {
53
+ const value = args[key];
54
+ if (value === undefined) {
55
+ return undefined;
56
+ }
57
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
58
+ throw new ToolArgumentError(`argument '${key}' must be a string array`, tool);
59
+ }
60
+ return value;
61
+ }
62
+ function summarizeCommand(result) {
63
+ return JSON.stringify({
64
+ exitCode: result.exitCode,
65
+ signal: result.signal,
66
+ timedOut: result.timedOut,
67
+ truncated: result.truncated,
68
+ stdout: result.stdout,
69
+ stderr: result.stderr,
70
+ });
71
+ }
72
+ export class WorkspaceToolHost {
73
+ workspace;
74
+ fs;
75
+ writer;
76
+ spawn;
77
+ resolveExecutable;
78
+ config;
79
+ processEnv;
80
+ now;
81
+ constructor(deps) {
82
+ this.workspace = deps.workspace;
83
+ this.fs = deps.fs ?? nodeWorkspaceFs;
84
+ this.writer = deps.writer ?? nodeWorkspaceWriter;
85
+ this.spawn = deps.spawn ?? nodeSpawnFn;
86
+ this.resolveExecutable = deps.resolveExecutable;
87
+ this.config = resolveToolHostConfig(deps.config);
88
+ this.processEnv = deps.processEnv ?? process.env;
89
+ this.now = deps.now ?? Date.now;
90
+ }
91
+ listTools() {
92
+ return TOOL_DEFINITIONS;
93
+ }
94
+ async execute(request) {
95
+ if (request.signal.aborted) {
96
+ throw new CommandCancelledError("tool call cancelled before dispatch");
97
+ }
98
+ const startedAt = this.now();
99
+ const handled = await this.dispatch(request);
100
+ return {
101
+ toolCallId: request.toolCallId,
102
+ output: handled.output,
103
+ durationMs: this.now() - startedAt,
104
+ commandExecuted: handled.commandExecuted,
105
+ ...(handled.metadata === undefined ? {} : { metadata: handled.metadata }),
106
+ };
107
+ }
108
+ dispatch(request) {
109
+ const args = request.arguments;
110
+ switch (request.toolName) {
111
+ case "read_file":
112
+ return Promise.resolve(this.readFile(args));
113
+ case "list_files":
114
+ return Promise.resolve(this.listFiles(args));
115
+ case "inspect_package_scripts":
116
+ return Promise.resolve(this.inspectScripts(args));
117
+ case "run_command":
118
+ return this.runCommandTool(args, request.signal);
119
+ case "propose_patch":
120
+ return Promise.resolve(this.proposePatch(args));
121
+ case "apply_patch":
122
+ return Promise.resolve(this.applyPatchTool(args, request.signal));
123
+ default:
124
+ throw new UnknownToolError(`no such tool: ${request.toolName}`, request.toolName);
125
+ }
126
+ }
127
+ readFile(args) {
128
+ const path = requireString(args, "path", "read_file");
129
+ const maxBytes = optionalNumber(args, "maxBytes", "read_file") ?? this.config.maxReadBytes;
130
+ const content = readWorkspaceFile(this.workspace, path, { maxBytes }, this.fs);
131
+ return { output: JSON.stringify(content), commandExecuted: false };
132
+ }
133
+ listFiles(args) {
134
+ const maxDepth = optionalNumber(args, "maxDepth", "list_files");
135
+ const maxFiles = optionalNumber(args, "maxFiles", "list_files");
136
+ const applyGitignore = optionalBoolean(args, "applyGitignore", "list_files");
137
+ const result = discoverWithStats(this.workspace, {
138
+ maxDepth: maxDepth ?? 12,
139
+ maxFiles: maxFiles ?? 5_000,
140
+ applyGitignore: applyGitignore ?? true,
141
+ }, this.fs);
142
+ return { output: JSON.stringify(result), commandExecuted: false };
143
+ }
144
+ inspectScripts(args) {
145
+ const path = optionalString(args, "path", "inspect_package_scripts") ?? "package.json";
146
+ const content = readWorkspaceFile(this.workspace, path, { maxBytes: this.config.maxReadBytes }, this.fs);
147
+ const scripts = parseScripts(content.text, path);
148
+ return { output: JSON.stringify({ path, scripts }), commandExecuted: false };
149
+ }
150
+ async runCommandTool(args, signal) {
151
+ const command = requireString(args, "command", "run_command");
152
+ const cmdArgs = optionalStringArray(args, "args", "run_command") ?? [];
153
+ const cwd = optionalString(args, "cwd", "run_command");
154
+ const timeoutMs = optionalNumber(args, "timeoutMs", "run_command");
155
+ const result = await runCommand({ command, args: cmdArgs, cwd, timeoutMs, signal }, {
156
+ workspace: this.workspace,
157
+ policy: this.config.sandbox,
158
+ commandRules: this.config.commandRules,
159
+ spawn: this.spawn,
160
+ resolveExecutable: this.resolveExecutable,
161
+ processEnv: this.processEnv,
162
+ now: this.now,
163
+ });
164
+ return {
165
+ output: summarizeCommand(result),
166
+ commandExecuted: true,
167
+ metadata: {
168
+ kind: "command",
169
+ executable: command,
170
+ argCount: cmdArgs.length,
171
+ exitCode: result.exitCode,
172
+ timedOut: result.timedOut,
173
+ sandbox: {
174
+ envAllowlist: this.config.sandbox.envAllowlist,
175
+ network: this.config.sandbox.network,
176
+ maxOutputBytes: this.config.sandbox.maxOutputBytes,
177
+ timeoutMs: timeoutMs ?? this.config.sandbox.defaultTimeoutMs,
178
+ terminationGraceMs: this.config.sandbox.terminationGraceMs,
179
+ cwdRequested: cwd !== undefined,
180
+ },
181
+ },
182
+ };
183
+ }
184
+ proposePatch(args) {
185
+ const diff = requireString(args, "diff", "propose_patch");
186
+ const validation = validatePatch(this.workspace, diff, {
187
+ fs: this.fs,
188
+ limits: this.config.patchLimits,
189
+ });
190
+ return {
191
+ output: JSON.stringify({ validation, preview: renderDryRun(validation) }),
192
+ commandExecuted: false,
193
+ };
194
+ }
195
+ applyPatchTool(args, signal) {
196
+ const diff = requireString(args, "diff", "apply_patch");
197
+ const result = applyPatch(this.workspace, diff, {
198
+ applyEnabled: this.config.applyEnabled,
199
+ signal,
200
+ fs: this.fs,
201
+ writer: this.writer,
202
+ limits: this.config.patchLimits,
203
+ });
204
+ return {
205
+ output: JSON.stringify(result),
206
+ commandExecuted: false,
207
+ metadata: {
208
+ kind: "patch-apply",
209
+ changedFiles: result.changedFiles.length,
210
+ created: result.created.length,
211
+ deleted: result.deleted.length,
212
+ },
213
+ };
214
+ }
215
+ }
216
+ function parseScripts(text, path) {
217
+ let parsed;
218
+ try {
219
+ parsed = JSON.parse(text);
220
+ }
221
+ catch {
222
+ throw new ToolArgumentError(`${path} is not valid JSON`, "inspect_package_scripts");
223
+ }
224
+ if (typeof parsed !== "object" || parsed === null) {
225
+ return {};
226
+ }
227
+ const scripts = parsed.scripts;
228
+ return typeof scripts === "object" && scripts !== null
229
+ ? scripts
230
+ : {};
231
+ }
@@ -0,0 +1,8 @@
1
+ import type { CommandRule } from "./types.js";
2
+ export declare function buildSandboxEnv(processEnv: NodeJS.ProcessEnv, allowlist: readonly string[]): Record<string, string>;
3
+ export declare function collectSensitiveEnvValues(processEnv: NodeJS.ProcessEnv, allowlist: readonly string[]): readonly string[];
4
+ export interface CommandDecision {
5
+ readonly allowed: boolean;
6
+ readonly reason?: string | undefined;
7
+ }
8
+ export declare function isCommandAllowed(rules: readonly CommandRule[], executable: string, args: readonly string[]): CommandDecision;