lastlight 0.15.0 → 0.16.1

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 (382) hide show
  1. package/README.md +71 -692
  2. package/dist/cli-config.js.map +1 -0
  3. package/dist/cli-format.js.map +1 -0
  4. package/dist/{cli/cli-server.d.ts → cli-server.d.ts} +26 -1
  5. package/dist/{cli/cli-server.js → cli-server.js} +40 -5
  6. package/dist/cli-server.js.map +1 -0
  7. package/dist/cli-timeline.js.map +1 -0
  8. package/dist/{cli/cli.js → cli.js} +4 -3
  9. package/dist/cli.js.map +1 -0
  10. package/dist/{cli/fork-cli.d.ts → fork-cli.d.ts} +3 -3
  11. package/dist/{cli/fork-cli.js → fork-cli.js} +28 -27
  12. package/dist/fork-cli.js.map +1 -0
  13. package/dist/{cli/oauth-cli.js → oauth-cli.js} +1 -1
  14. package/dist/oauth-cli.js.map +1 -0
  15. package/dist/{cli/setup.js → setup.js} +10 -4
  16. package/dist/setup.js.map +1 -0
  17. package/dist/{cli/skills-install.d.ts → skills-install.d.ts} +6 -0
  18. package/dist/{cli/skills-install.js → skills-install.js} +6 -2
  19. package/dist/skills-install.js.map +1 -0
  20. package/package.json +20 -65
  21. package/plugins/lastlight/.claude-plugin/plugin.json +1 -1
  22. package/agent-context/rules.md +0 -80
  23. package/agent-context/security.md +0 -69
  24. package/agent-context/soul.md +0 -21
  25. package/config/default.yaml +0 -119
  26. package/deploy/.env.production.example +0 -100
  27. package/deploy/entrypoint.sh +0 -82
  28. package/deploy/native/README.md +0 -160
  29. package/deploy/native/install.sh +0 -110
  30. package/deploy/native/lastlight.env.example +0 -95
  31. package/deploy/native/lastlight.service +0 -50
  32. package/deploy/sandbox-entrypoint.sh +0 -93
  33. package/dist/admin/auth.d.ts +0 -51
  34. package/dist/admin/auth.js +0 -121
  35. package/dist/admin/auth.js.map +0 -1
  36. package/dist/admin/chat-session-reader.d.ts +0 -39
  37. package/dist/admin/chat-session-reader.js +0 -84
  38. package/dist/admin/chat-session-reader.js.map +0 -1
  39. package/dist/admin/docker.d.ts +0 -61
  40. package/dist/admin/docker.js +0 -187
  41. package/dist/admin/docker.js.map +0 -1
  42. package/dist/admin/index.d.ts +0 -9
  43. package/dist/admin/index.js +0 -59
  44. package/dist/admin/index.js.map +0 -1
  45. package/dist/admin/routes.d.ts +0 -81
  46. package/dist/admin/routes.js +0 -1381
  47. package/dist/admin/routes.js.map +0 -1
  48. package/dist/admin/sessions.d.ts +0 -89
  49. package/dist/admin/sessions.js +0 -204
  50. package/dist/admin/sessions.js.map +0 -1
  51. package/dist/admin/tail.d.ts +0 -12
  52. package/dist/admin/tail.js +0 -145
  53. package/dist/admin/tail.js.map +0 -1
  54. package/dist/admin/version.d.ts +0 -22
  55. package/dist/admin/version.js +0 -92
  56. package/dist/admin/version.js.map +0 -1
  57. package/dist/cli/cli-config.js.map +0 -1
  58. package/dist/cli/cli-format.js.map +0 -1
  59. package/dist/cli/cli-server.js.map +0 -1
  60. package/dist/cli/cli-timeline.js.map +0 -1
  61. package/dist/cli/cli.js.map +0 -1
  62. package/dist/cli/fork-cli.js.map +0 -1
  63. package/dist/cli/oauth-cli.js.map +0 -1
  64. package/dist/cli/setup.js.map +0 -1
  65. package/dist/cli/skills-install.js.map +0 -1
  66. package/dist/config/config-resolve.d.ts +0 -28
  67. package/dist/config/config-resolve.js +0 -47
  68. package/dist/config/config-resolve.js.map +0 -1
  69. package/dist/config/config.d.ts +0 -157
  70. package/dist/config/config.js +0 -581
  71. package/dist/config/config.js.map +0 -1
  72. package/dist/config/core-pin.d.ts +0 -16
  73. package/dist/config/core-pin.js +0 -85
  74. package/dist/config/core-pin.js.map +0 -1
  75. package/dist/config/overlay-assets.d.ts +0 -20
  76. package/dist/config/overlay-assets.js +0 -114
  77. package/dist/config/overlay-assets.js.map +0 -1
  78. package/dist/config/overlay-bootstrap.d.ts +0 -44
  79. package/dist/config/overlay-bootstrap.js +0 -304
  80. package/dist/config/overlay-bootstrap.js.map +0 -1
  81. package/dist/connectors/github-webhook.d.ts +0 -45
  82. package/dist/connectors/github-webhook.js +0 -321
  83. package/dist/connectors/github-webhook.js.map +0 -1
  84. package/dist/connectors/index.d.ts +0 -18
  85. package/dist/connectors/index.js +0 -41
  86. package/dist/connectors/index.js.map +0 -1
  87. package/dist/connectors/messaging/base.d.ts +0 -42
  88. package/dist/connectors/messaging/base.js +0 -139
  89. package/dist/connectors/messaging/base.js.map +0 -1
  90. package/dist/connectors/messaging/delivery.d.ts +0 -14
  91. package/dist/connectors/messaging/delivery.js +0 -38
  92. package/dist/connectors/messaging/delivery.js.map +0 -1
  93. package/dist/connectors/messaging/index.d.ts +0 -4
  94. package/dist/connectors/messaging/index.js +0 -4
  95. package/dist/connectors/messaging/index.js.map +0 -1
  96. package/dist/connectors/messaging/session-manager.d.ts +0 -53
  97. package/dist/connectors/messaging/session-manager.js +0 -255
  98. package/dist/connectors/messaging/session-manager.js.map +0 -1
  99. package/dist/connectors/messaging/types.d.ts +0 -58
  100. package/dist/connectors/messaging/types.js +0 -7
  101. package/dist/connectors/messaging/types.js.map +0 -1
  102. package/dist/connectors/slack/connector.d.ts +0 -100
  103. package/dist/connectors/slack/connector.js +0 -341
  104. package/dist/connectors/slack/connector.js.map +0 -1
  105. package/dist/connectors/slack/index.d.ts +0 -2
  106. package/dist/connectors/slack/index.js +0 -3
  107. package/dist/connectors/slack/index.js.map +0 -1
  108. package/dist/connectors/slack/mrkdwn.d.ts +0 -8
  109. package/dist/connectors/slack/mrkdwn.js +0 -123
  110. package/dist/connectors/slack/mrkdwn.js.map +0 -1
  111. package/dist/connectors/types.d.ts +0 -61
  112. package/dist/connectors/types.js +0 -2
  113. package/dist/connectors/types.js.map +0 -1
  114. package/dist/cron/fanout.d.ts +0 -33
  115. package/dist/cron/fanout.js +0 -55
  116. package/dist/cron/fanout.js.map +0 -1
  117. package/dist/cron/jobs.d.ts +0 -18
  118. package/dist/cron/jobs.js +0 -37
  119. package/dist/cron/jobs.js.map +0 -1
  120. package/dist/cron/scheduler.d.ts +0 -45
  121. package/dist/cron/scheduler.js +0 -105
  122. package/dist/cron/scheduler.js.map +0 -1
  123. package/dist/engine/agent-executor.d.ts +0 -34
  124. package/dist/engine/agent-executor.js +0 -246
  125. package/dist/engine/agent-executor.js.map +0 -1
  126. package/dist/engine/chat/chat-runner.d.ts +0 -132
  127. package/dist/engine/chat/chat-runner.js +0 -424
  128. package/dist/engine/chat/chat-runner.js.map +0 -1
  129. package/dist/engine/chat/chat-skills.d.ts +0 -45
  130. package/dist/engine/chat/chat-skills.js +0 -184
  131. package/dist/engine/chat/chat-skills.js.map +0 -1
  132. package/dist/engine/chat/chat.d.ts +0 -54
  133. package/dist/engine/chat/chat.js +0 -316
  134. package/dist/engine/chat/chat.js.map +0 -1
  135. package/dist/engine/chat/message-batcher.d.ts +0 -32
  136. package/dist/engine/chat/message-batcher.js +0 -109
  137. package/dist/engine/chat/message-batcher.js.map +0 -1
  138. package/dist/engine/dispatcher.d.ts +0 -73
  139. package/dist/engine/dispatcher.js +0 -663
  140. package/dist/engine/dispatcher.js.map +0 -1
  141. package/dist/engine/event-shim.d.ts +0 -117
  142. package/dist/engine/event-shim.js +0 -435
  143. package/dist/engine/event-shim.js.map +0 -1
  144. package/dist/engine/executors/orchestrator.d.ts +0 -81
  145. package/dist/engine/executors/orchestrator.js +0 -365
  146. package/dist/engine/executors/orchestrator.js.map +0 -1
  147. package/dist/engine/executors/shared.d.ts +0 -189
  148. package/dist/engine/executors/shared.js +0 -616
  149. package/dist/engine/executors/shared.js.map +0 -1
  150. package/dist/engine/github/git-auth.d.ts +0 -62
  151. package/dist/engine/github/git-auth.js +0 -161
  152. package/dist/engine/github/git-auth.js.map +0 -1
  153. package/dist/engine/github/github-app-client.d.ts +0 -23
  154. package/dist/engine/github/github-app-client.js +0 -33
  155. package/dist/engine/github/github-app-client.js.map +0 -1
  156. package/dist/engine/github/github-tools.d.ts +0 -18
  157. package/dist/engine/github/github-tools.js +0 -263
  158. package/dist/engine/github/github-tools.js.map +0 -1
  159. package/dist/engine/github/github.d.ts +0 -1193
  160. package/dist/engine/github/github.js +0 -357
  161. package/dist/engine/github/github.js.map +0 -1
  162. package/dist/engine/github/profiles.d.ts +0 -279
  163. package/dist/engine/github/profiles.js +0 -42
  164. package/dist/engine/github/profiles.js.map +0 -1
  165. package/dist/engine/github/review-poster.d.ts +0 -98
  166. package/dist/engine/github/review-poster.js +0 -177
  167. package/dist/engine/github/review-poster.js.map +0 -1
  168. package/dist/engine/llm.d.ts +0 -79
  169. package/dist/engine/llm.js +0 -269
  170. package/dist/engine/llm.js.map +0 -1
  171. package/dist/engine/oauth.d.ts +0 -43
  172. package/dist/engine/oauth.js +0 -103
  173. package/dist/engine/oauth.js.map +0 -1
  174. package/dist/engine/router.d.ts +0 -29
  175. package/dist/engine/router.js +0 -680
  176. package/dist/engine/router.js.map +0 -1
  177. package/dist/engine/screen/classifier.d.ts +0 -93
  178. package/dist/engine/screen/classifier.js +0 -215
  179. package/dist/engine/screen/classifier.js.map +0 -1
  180. package/dist/engine/screen/screen.d.ts +0 -41
  181. package/dist/engine/screen/screen.js +0 -93
  182. package/dist/engine/screen/screen.js.map +0 -1
  183. package/dist/evals-api.d.ts +0 -24
  184. package/dist/evals-api.js +0 -22
  185. package/dist/evals-api.js.map +0 -1
  186. package/dist/index.d.ts +0 -1
  187. package/dist/index.js +0 -923
  188. package/dist/index.js.map +0 -1
  189. package/dist/managed-repos.d.ts +0 -30
  190. package/dist/managed-repos.js +0 -72
  191. package/dist/managed-repos.js.map +0 -1
  192. package/dist/notify/index.d.ts +0 -15
  193. package/dist/notify/index.js +0 -6
  194. package/dist/notify/index.js.map +0 -1
  195. package/dist/notify/model.d.ts +0 -57
  196. package/dist/notify/model.js +0 -89
  197. package/dist/notify/model.js.map +0 -1
  198. package/dist/notify/notifier.d.ts +0 -17
  199. package/dist/notify/notifier.js +0 -87
  200. package/dist/notify/notifier.js.map +0 -1
  201. package/dist/notify/render.d.ts +0 -21
  202. package/dist/notify/render.js +0 -59
  203. package/dist/notify/render.js.map +0 -1
  204. package/dist/notify/transports/github.d.ts +0 -26
  205. package/dist/notify/transports/github.js +0 -26
  206. package/dist/notify/transports/github.js.map +0 -1
  207. package/dist/notify/transports/slack.d.ts +0 -26
  208. package/dist/notify/transports/slack.js +0 -28
  209. package/dist/notify/transports/slack.js.map +0 -1
  210. package/dist/notify/types.d.ts +0 -95
  211. package/dist/notify/types.js +0 -16
  212. package/dist/notify/types.js.map +0 -1
  213. package/dist/providers.d.ts +0 -125
  214. package/dist/providers.js +0 -279
  215. package/dist/providers.js.map +0 -1
  216. package/dist/sandbox/docker.d.ts +0 -180
  217. package/dist/sandbox/docker.js +0 -517
  218. package/dist/sandbox/docker.js.map +0 -1
  219. package/dist/sandbox/egress-allowlist.d.ts +0 -74
  220. package/dist/sandbox/egress-allowlist.js +0 -164
  221. package/dist/sandbox/egress-allowlist.js.map +0 -1
  222. package/dist/sandbox/egress-firewall-config.d.ts +0 -127
  223. package/dist/sandbox/egress-firewall-config.js +0 -434
  224. package/dist/sandbox/egress-firewall-config.js.map +0 -1
  225. package/dist/sandbox/images.d.ts +0 -23
  226. package/dist/sandbox/images.js +0 -55
  227. package/dist/sandbox/images.js.map +0 -1
  228. package/dist/sandbox/index.d.ts +0 -112
  229. package/dist/sandbox/index.js +0 -332
  230. package/dist/sandbox/index.js.map +0 -1
  231. package/dist/sandbox/sandbox.d.ts +0 -214
  232. package/dist/sandbox/sandbox.js +0 -453
  233. package/dist/sandbox/sandbox.js.map +0 -1
  234. package/dist/sandbox/smol.d.ts +0 -162
  235. package/dist/sandbox/smol.js +0 -485
  236. package/dist/sandbox/smol.js.map +0 -1
  237. package/dist/session-log.d.ts +0 -63
  238. package/dist/session-log.js +0 -290
  239. package/dist/session-log.js.map +0 -1
  240. package/dist/state/approval-store.d.ts +0 -88
  241. package/dist/state/approval-store.js +0 -149
  242. package/dist/state/approval-store.js.map +0 -1
  243. package/dist/state/build-assets.d.ts +0 -50
  244. package/dist/state/build-assets.js +0 -154
  245. package/dist/state/build-assets.js.map +0 -1
  246. package/dist/state/db.d.ts +0 -81
  247. package/dist/state/db.js +0 -155
  248. package/dist/state/db.js.map +0 -1
  249. package/dist/state/execution-store.d.ts +0 -257
  250. package/dist/state/execution-store.js +0 -527
  251. package/dist/state/execution-store.js.map +0 -1
  252. package/dist/state/migrate.d.ts +0 -15
  253. package/dist/state/migrate.js +0 -175
  254. package/dist/state/migrate.js.map +0 -1
  255. package/dist/state/workflow-run-store.d.ts +0 -216
  256. package/dist/state/workflow-run-store.js +0 -403
  257. package/dist/state/workflow-run-store.js.map +0 -1
  258. package/dist/telemetry/index.d.ts +0 -38
  259. package/dist/telemetry/index.js +0 -253
  260. package/dist/telemetry/index.js.map +0 -1
  261. package/dist/telemetry/pi-events.d.ts +0 -12
  262. package/dist/telemetry/pi-events.js +0 -134
  263. package/dist/telemetry/pi-events.js.map +0 -1
  264. package/dist/workflows/dag.d.ts +0 -37
  265. package/dist/workflows/dag.js +0 -118
  266. package/dist/workflows/dag.js.map +0 -1
  267. package/dist/workflows/loader.d.ts +0 -53
  268. package/dist/workflows/loader.js +0 -421
  269. package/dist/workflows/loader.js.map +0 -1
  270. package/dist/workflows/loop-eval.d.ts +0 -28
  271. package/dist/workflows/loop-eval.js +0 -74
  272. package/dist/workflows/loop-eval.js.map +0 -1
  273. package/dist/workflows/phase-executor.d.ts +0 -215
  274. package/dist/workflows/phase-executor.js +0 -1144
  275. package/dist/workflows/phase-executor.js.map +0 -1
  276. package/dist/workflows/phase-ref.d.ts +0 -50
  277. package/dist/workflows/phase-ref.js +0 -91
  278. package/dist/workflows/phase-ref.js.map +0 -1
  279. package/dist/workflows/resume.d.ts +0 -58
  280. package/dist/workflows/resume.js +0 -313
  281. package/dist/workflows/resume.js.map +0 -1
  282. package/dist/workflows/runner.d.ts +0 -63
  283. package/dist/workflows/runner.js +0 -364
  284. package/dist/workflows/runner.js.map +0 -1
  285. package/dist/workflows/schema.d.ts +0 -344
  286. package/dist/workflows/schema.js +0 -460
  287. package/dist/workflows/schema.js.map +0 -1
  288. package/dist/workflows/simple.d.ts +0 -90
  289. package/dist/workflows/simple.js +0 -393
  290. package/dist/workflows/simple.js.map +0 -1
  291. package/dist/workflows/target-policy.d.ts +0 -74
  292. package/dist/workflows/target-policy.js +0 -80
  293. package/dist/workflows/target-policy.js.map +0 -1
  294. package/dist/workflows/templates.d.ts +0 -88
  295. package/dist/workflows/templates.js +0 -127
  296. package/dist/workflows/templates.js.map +0 -1
  297. package/dist/workflows/triggers.d.ts +0 -21
  298. package/dist/workflows/triggers.js +0 -45
  299. package/dist/workflows/triggers.js.map +0 -1
  300. package/dist/workflows/verdict.d.ts +0 -19
  301. package/dist/workflows/verdict.js +0 -30
  302. package/dist/workflows/verdict.js.map +0 -1
  303. package/dist/worktree/manager.d.ts +0 -43
  304. package/dist/worktree/manager.js +0 -127
  305. package/dist/worktree/manager.js.map +0 -1
  306. package/docker-compose.yml +0 -274
  307. package/sandbox.Dockerfile +0 -68
  308. package/skills/README.md +0 -42
  309. package/skills/browser-qa/SKILL.md +0 -208
  310. package/skills/browser-qa/scripts/agent-browser.mjs +0 -531
  311. package/skills/building/SKILL.md +0 -95
  312. package/skills/chat/SKILL.md +0 -36
  313. package/skills/code-review/SKILL.md +0 -82
  314. package/skills/debug-production/SKILL.md +0 -108
  315. package/skills/demo/SKILL.md +0 -228
  316. package/skills/demo/scripts/compose-demo.sh +0 -243
  317. package/skills/issue-answer/SKILL.md +0 -91
  318. package/skills/issue-comment/SKILL.md +0 -49
  319. package/skills/issue-triage/SKILL.md +0 -149
  320. package/skills/issue-triage/references/AGENT-BRIEF.md +0 -65
  321. package/skills/pr-comment/SKILL.md +0 -64
  322. package/skills/pr-review/SKILL.md +0 -148
  323. package/skills/pr-review/references/findings-schema.md +0 -89
  324. package/skills/qa-test/SKILL.md +0 -97
  325. package/skills/repo-health/SKILL.md +0 -67
  326. package/skills/security-feedback/SKILL.md +0 -173
  327. package/skills/security-feedback/references/templates.md +0 -77
  328. package/skills/security-review/SKILL.md +0 -215
  329. package/skills/security-review/references/issue-format.md +0 -302
  330. package/skills/verify/SKILL.md +0 -118
  331. package/workflows/answer.yaml +0 -44
  332. package/workflows/build.yaml +0 -129
  333. package/workflows/cron-health.yaml +0 -6
  334. package/workflows/cron-review.yaml +0 -8
  335. package/workflows/cron-security.yaml +0 -6
  336. package/workflows/cron-triage.yaml +0 -8
  337. package/workflows/demo.yaml +0 -62
  338. package/workflows/examples/bash-smoke.yaml +0 -41
  339. package/workflows/examples/parallel-review.yaml +0 -36
  340. package/workflows/examples/tdd-loop.yaml +0 -17
  341. package/workflows/explore.yaml +0 -80
  342. package/workflows/issue-comment.yaml +0 -15
  343. package/workflows/issue-triage.yaml +0 -22
  344. package/workflows/pr-comment.yaml +0 -16
  345. package/workflows/pr-fix.yaml +0 -17
  346. package/workflows/pr-review.yaml +0 -50
  347. package/workflows/prompts/answer.md +0 -68
  348. package/workflows/prompts/architect.md +0 -44
  349. package/workflows/prompts/classifier.md +0 -60
  350. package/workflows/prompts/classify-adds-info.md +0 -17
  351. package/workflows/prompts/demo.md +0 -125
  352. package/workflows/prompts/executor.md +0 -42
  353. package/workflows/prompts/explore-ask.md +0 -99
  354. package/workflows/prompts/explore-publish.md +0 -31
  355. package/workflows/prompts/explore-read.md +0 -120
  356. package/workflows/prompts/explore-synthesize.md +0 -88
  357. package/workflows/prompts/fix.md +0 -18
  358. package/workflows/prompts/guardrails.md +0 -96
  359. package/workflows/prompts/pr-fix.md +0 -26
  360. package/workflows/prompts/pr.md +0 -39
  361. package/workflows/prompts/qa-browser.md +0 -95
  362. package/workflows/prompts/qa-synth.md +0 -44
  363. package/workflows/prompts/qa-test.md +0 -60
  364. package/workflows/prompts/re-reviewer.md +0 -33
  365. package/workflows/prompts/reviewer.md +0 -60
  366. package/workflows/prompts/verify-browser.md +0 -93
  367. package/workflows/prompts/verify-synth.md +0 -44
  368. package/workflows/prompts/verify.md +0 -63
  369. package/workflows/qa-test.yaml +0 -94
  370. package/workflows/repo-health.yaml +0 -15
  371. package/workflows/security-feedback.yaml +0 -25
  372. package/workflows/security-review.yaml +0 -37
  373. package/workflows/verify.yaml +0 -93
  374. /package/dist/{cli/cli-config.d.ts → cli-config.d.ts} +0 -0
  375. /package/dist/{cli/cli-config.js → cli-config.js} +0 -0
  376. /package/dist/{cli/cli-format.d.ts → cli-format.d.ts} +0 -0
  377. /package/dist/{cli/cli-format.js → cli-format.js} +0 -0
  378. /package/dist/{cli/cli-timeline.d.ts → cli-timeline.d.ts} +0 -0
  379. /package/dist/{cli/cli-timeline.js → cli-timeline.js} +0 -0
  380. /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
  381. /package/dist/{cli/oauth-cli.d.ts → oauth-cli.d.ts} +0 -0
  382. /package/dist/{cli/setup.d.ts → setup.d.ts} +0 -0
@@ -1,434 +0,0 @@
1
- import { mkdirSync, writeFileSync } from "fs";
2
- import { join } from "path";
3
- import { DEFAULT_ALLOWLIST, mergeAllowlist } from "./egress-allowlist.js";
4
- /**
5
- * Egress firewall config generation for the docker sandbox backend.
6
- *
7
- * Architecture (replaces the earlier tinyproxy CONNECT-proxy):
8
- *
9
- * sandbox-egress (internal: true, subnet 172.30.0.0/24)
10
- * ├─ sandbox containers (docker run --dns <coredns-ip>)
11
- * ├─ coredns-strict 172.30.0.10 → returns nginx-strict IP for allowlist hosts, NXDOMAIN otherwise
12
- * ├─ coredns-open 172.30.0.11 → returns nginx-open IP for ANY hostname (except hard-deny SSRF magnets)
13
- * ├─ nginx-egress-strict 172.30.0.20 → ssl_preread, SNI must match allowlist; tunnel upstream via proxy-egress
14
- * └─ nginx-egress-open 172.30.0.21 → tunnel any SNI; client DNS already gated through coredns-open
15
- *
16
- * Why this instead of a forward proxy (tinyproxy/smokescreen): clients
17
- * inside the sandbox (especially the OpenAI/Anthropic SDKs that build
18
- * their own undici dispatchers) don't honour HTTP_PROXY / HTTPS_PROXY.
19
- * Forcing them to cooperate is a losing battle. By spoofing DNS and
20
- * peeking SNI at the network layer, the sandbox sees no proxy at all —
21
- * it dials `api.openai.com:443` directly. The firewall intercepts the
22
- * connection by virtue of being the only thing the spoofed DNS pointed
23
- * at.
24
- *
25
- * Inspired by Vercel Sandbox's egress firewall (SNI peeking, no env
26
- * vars). Their implementation also handles TLS termination for
27
- * credentials brokering and Postgres STARTTLS — we don't (yet).
28
- *
29
- * ## Wildcards
30
- *
31
- * Entries in egress-allowlist.ts with a leading dot (`".github.com"`)
32
- * match the apex plus every subdomain. nginx's `map` directive supports
33
- * this syntax natively. CoreDNS uses the `template` plugin with an
34
- * anchored regex to do the same.
35
- */
36
- /** Fixed IPs assigned in docker-compose.yml so `--dns` and nginx upstreams can reference them. */
37
- export const COREDNS_STRICT_IP = "172.30.0.10";
38
- export const COREDNS_OPEN_IP = "172.30.0.11";
39
- export const NGINX_STRICT_IP = "172.30.0.20";
40
- export const NGINX_OPEN_IP = "172.30.0.21";
41
- /**
42
- * Static IP of the in-network OTEL collector (docker backend). Sandboxes
43
- * dial it directly by IP — it lives on `sandbox-egress` alongside the
44
- * firewalls, so no DNS lookup (and therefore no coredns allowlist entry)
45
- * is involved. The collector is the ONLY OTLP endpoint a sandbox ever
46
- * sees: it terminates the sandbox's telemetry and re-exports to the real
47
- * backend over `proxy-egress` using credentials that stay host-side. This
48
- * is why the strict SNI firewall no longer needs to know about collector
49
- * hosts or non-443 ports — that hop happens on the trusted outbound leg,
50
- * not through `ssl_preread`.
51
- */
52
- export const OTEL_COLLECTOR_IP = "172.30.0.30";
53
- /** OTLP/HTTP + OTLP/gRPC receiver ports the in-network collector listens on. */
54
- export const OTEL_COLLECTOR_OTLP_HTTP_PORT = 4318;
55
- export const OTEL_COLLECTOR_OTLP_GRPC_PORT = 4317;
56
- /** Endpoint a docker sandbox is told to export OTLP to (HTTP/protobuf). */
57
- export const OTEL_COLLECTOR_SANDBOX_ENDPOINT = `http://${OTEL_COLLECTOR_IP}:${OTEL_COLLECTOR_OTLP_HTTP_PORT}`;
58
- /**
59
- * UID the `otel/opentelemetry-collector` image runs as (its Dockerfile
60
- * `USER`). The collector reads the OTLP config (which can hold backend auth
61
- * headers) as this non-root user. To let it read a mode-0600 file from the
62
- * shared volume WITHOUT running as root or making the secret world-readable,
63
- * the harness's own `lastlight` user is pinned to this same UID (see the
64
- * Dockerfile `useradd -u`), so the collector reads files it wrote as their
65
- * owner. The Dockerfile and the collector image tag in docker-compose.yml
66
- * must move together if a future image bumps the UID — enforced by a test in
67
- * docker-compose.test.ts.
68
- */
69
- export const OTEL_COLLECTOR_UID = 10001;
70
- /** Subnet for sandbox-egress. Compose declares this CIDR so the IPs above are valid. */
71
- export const SANDBOX_EGRESS_SUBNET = "172.30.0.0/24";
72
- /**
73
- * Hostnames the open coredns explicitly NXDOMAINs even in unrestricted
74
- * mode — known SSRF magnets. nginx's `ssl_preread` SNI inspection alone
75
- * can't catch hostnames that resolve to private IPs (the DNS server
76
- * we control gets the first say), so this is the right layer to enforce
77
- * "no cloud metadata, no internal services" even with `unrestricted_egress`.
78
- */
79
- const OPEN_MODE_HARD_DENY = [
80
- "metadata.google.internal",
81
- "169.254.169.254", // GCP / AWS / Azure metadata service IP literal
82
- ];
83
- /**
84
- * Convert one allowlist entry to its nginx `map` line.
85
- *
86
- * "openai.com" → '.openai.com $ssl_preread_server_name:443;'
87
- *
88
- * Every entry is treated as apex+subdomain (see egress-allowlist.ts).
89
- * The generated line uses nginx's `.foo.com` leading-dot syntax,
90
- * which is its built-in "match this domain and all subdomains" form.
91
- * Upstream is whatever SNI the client sent — we don't know which
92
- * subdomain they're targeting at config-gen time.
93
- */
94
- function nginxMapLine(entry) {
95
- return ` .${entry} $ssl_preread_server_name:443;`;
96
- }
97
- /**
98
- * Build one CoreDNS `match` regex per allowlist entry. All entries
99
- * share a SINGLE `template` block (see renderCorefileStrict) because
100
- * CoreDNS only honours one `template` per zone — multiple blocks
101
- * silently shadow each other.
102
- *
103
- * The `(^|\.)` prefix matches the apex (`openai.com.`) or any subdomain
104
- * (`api.openai.com.`) — same semantics as nginx's leading-dot form.
105
- */
106
- function corednsMatchLineForEntry(entry) {
107
- const escapedHost = entry.replaceAll(".", "\\.");
108
- return ` match (^|\\.)${escapedHost}\\.$`;
109
- }
110
- /** Build the strict nginx.conf — ssl_preread + map on SNI allowlist. */
111
- export function renderNginxStrictConf() {
112
- const mapLines = mergeAllowlist(DEFAULT_ALLOWLIST).map(nginxMapLine).join("\n");
113
- return `# Generated by lastlight at harness boot. DO NOT EDIT BY HAND —
114
- # source of truth is src/sandbox/egress-allowlist.ts.
115
-
116
- error_log /dev/stderr warn;
117
- pid /tmp/nginx.pid;
118
-
119
- events { worker_connections 1024; }
120
-
121
- # Streams (TCP/UDP). \`ssl_preread\` reads the TLS ClientHello without
122
- # terminating the session, exposing the SNI hostname as
123
- # \`$ssl_preread_server_name\`. We \`map\` that to an upstream — anything
124
- # unmatched is sunk to 127.0.0.1:1 (no listener, instant reset).
125
- stream {
126
- log_format basic '$remote_addr -> $ssl_preread_server_name -> $upstream_addr status=$status';
127
- access_log /dev/stdout basic;
128
-
129
- # Runtime resolver for upstream hostnames (docker's embedded DNS).
130
- resolver 127.0.0.11 valid=30s ipv6=off;
131
-
132
- # Allowlist. The \`hostnames\` directive enables nginx's wildcard
133
- # hostname match: a key like ".github.com" matches "github.com" AND
134
- # any subdomain. WITHOUT \`hostnames\`, map does exact string match
135
- # only and ".github.com" never matches anything real (we hit this
136
- # exact bug in prod — every allowlisted host fell through to the
137
- # black hole).
138
- map $ssl_preread_server_name $upstream_target {
139
- hostnames;
140
- ${mapLines}
141
- # Anything else → black hole. The connection resets immediately;
142
- # no data ever leaves the sandbox-egress network.
143
- default 127.0.0.1:1;
144
- }
145
-
146
- server {
147
- listen 443;
148
- ssl_preread on;
149
- proxy_pass $upstream_target;
150
- proxy_connect_timeout 5s;
151
- proxy_timeout 60s;
152
- }
153
- }
154
- `;
155
- }
156
- /** Build the open nginx.conf — no SNI allowlist, just tunnel whatever DNS pointed at. */
157
- export function renderNginxOpenConf() {
158
- return `# Generated by lastlight at harness boot. DO NOT EDIT BY HAND.
159
- # This nginx is used only by phases that declared \`unrestricted_egress: true\`.
160
- # No SNI allowlist — every host is tunnelled. The corresponding coredns-open
161
- # refuses to resolve the cloud-metadata hostnames so SSRF to them is blocked
162
- # even in this mode.
163
-
164
- error_log /dev/stderr warn;
165
- pid /tmp/nginx.pid;
166
-
167
- events { worker_connections 1024; }
168
-
169
- stream {
170
- log_format basic '$remote_addr -> $ssl_preread_server_name -> $upstream_addr status=$status';
171
- access_log /dev/stdout basic;
172
- resolver 127.0.0.11 valid=30s ipv6=off;
173
-
174
- server {
175
- listen 443;
176
- ssl_preread on;
177
- # $ssl_preread_server_name is whatever SNI the client sent. Tunnel
178
- # to that hostname on :443. No allowlist check.
179
- proxy_pass $ssl_preread_server_name:443;
180
- proxy_connect_timeout 5s;
181
- proxy_timeout 60s;
182
- }
183
- }
184
- `;
185
- }
186
- /** Build the Corefile that returns the strict nginx IP for allowlist hosts. */
187
- export function renderCorefileStrict() {
188
- // ALL allowlist entries share a SINGLE template block. CoreDNS only
189
- // honours one `template` per (class, type, zone), so multiple blocks
190
- // silently overwrite each other — we learned this in prod when only
191
- // the first host in the list resolved.
192
- const matchLines = mergeAllowlist(DEFAULT_ALLOWLIST).map(corednsMatchLineForEntry).join("\n");
193
- return `# Generated by lastlight from src/sandbox/egress-allowlist.ts.
194
- # All allowlist hosts live in a single \`template IN A\` block (multiple
195
- # template blocks per zone are NOT additive — they shadow each other).
196
- # Queries that don't match any \`match\` regex fall through to the
197
- # catch-all NXDOMAIN at the end.
198
- .:53 {
199
- template IN A {
200
- ${matchLines}
201
- answer "{{ .Name }} 60 IN A ${NGINX_STRICT_IP}"
202
- }
203
- # Catch-all: NXDOMAIN for everything else (including AAAA queries,
204
- # since nginx-egress only binds 443 on IPv4).
205
- template IN ANY {
206
- rcode NXDOMAIN
207
- }
208
- errors
209
- log
210
- }
211
- `;
212
- }
213
- /** Build the Corefile that returns the open nginx IP for ANY hostname (except hard-denies). */
214
- export function renderCorefileOpen() {
215
- // Hard-deny hosts each get their OWN zone block. CoreDNS routes queries
216
- // to the longest-suffix-matching zone, so `metadata.google.internal:53`
217
- // catches both the apex and every subdomain before the catch-all `.`
218
- // zone ever sees them. We do NOT use a multi-`template` layout inside
219
- // the catch-all zone because CoreDNS only honours one template per
220
- // (class, type) and prior templates with non-matching `match` clauses
221
- // silently drop the query — leaving sandboxes with no DNS answer at
222
- // all (which is exactly what failed in prod the first time explore
223
- // ran on the open path).
224
- const hardDenyZones = OPEN_MODE_HARD_DENY.map((host) => `# Hard-deny zone for ${host} (SSRF floor — NXDOMAIN even in unrestricted mode).
225
- ${host}:53 {
226
- template IN ANY {
227
- rcode NXDOMAIN
228
- }
229
- errors
230
- log
231
- }
232
- `).join("\n");
233
- return `# Generated by lastlight. Used by sandbox phases with unrestricted_egress: true.
234
- # Returns nginx-egress-open's IP for every hostname EXCEPT the SSRF hard-denies
235
- # (cloud metadata literals etc).
236
- .:53 {
237
- # Everything → nginx-egress-open IP. Hard-deny names are intercepted
238
- # by the per-host zones declared below (longest-suffix zone match).
239
- template IN A {
240
- answer "{{ .Name }} 60 IN A ${NGINX_OPEN_IP}"
241
- }
242
- # AAAA returns empty (we route only via IPv4).
243
- template IN AAAA {
244
- rcode NOERROR
245
- }
246
- errors
247
- log
248
- }
249
-
250
- ${hardDenyZones}`;
251
- }
252
- /**
253
- * Write the four config files under $STATE_DIR/proxy/. Idempotent.
254
- * Returns the directory path. Compose bind-mounts this dir into the
255
- * coredns + nginx containers read-only.
256
- */
257
- export function writeEgressFirewallConfigs(stateDir) {
258
- const dir = join(stateDir, "proxy");
259
- mkdirSync(dir, { recursive: true });
260
- writeFileSync(join(dir, "nginx-strict.conf"), renderNginxStrictConf());
261
- writeFileSync(join(dir, "nginx-open.conf"), renderNginxOpenConf());
262
- writeFileSync(join(dir, "Corefile.strict"), renderCorefileStrict());
263
- writeFileSync(join(dir, "Corefile.open"), renderCorefileOpen());
264
- return dir;
265
- }
266
- /**
267
- * Parse an OTLP headers string (`key1=val1,key2=val2`, the OTEL spec format
268
- * used by `OTEL_EXPORTER_OTLP_HEADERS`) into an ordered list of pairs.
269
- * Malformed segments (no `=`, empty key) are dropped. Values keep any inner
270
- * `=` so bearer tokens survive intact.
271
- */
272
- function parseOtlpHeaders(raw) {
273
- if (!raw)
274
- return [];
275
- const out = [];
276
- for (const segment of raw.split(",")) {
277
- const eq = segment.indexOf("=");
278
- if (eq <= 0)
279
- continue;
280
- const key = segment.slice(0, eq).trim();
281
- const value = segment.slice(eq + 1).trim();
282
- // Reject CR/LF in either part: a real newline in an HTTP header value is
283
- // header injection on the collector→backend leg, and would also break the
284
- // YAML scalar. A header carrying a newline is corruption/injection, not a
285
- // valid OTLP config — drop the pair rather than emit something dangerous.
286
- if (!key || /[\r\n]/.test(key) || /[\r\n]/.test(value))
287
- continue;
288
- out.push([key, value]);
289
- }
290
- return out;
291
- }
292
- /**
293
- * YAML double-quoted-scalar escape. Backslash and quote are the cases that
294
- * appear in tokens/URLs; CR/LF/TAB are escaped too as a hard guarantee that
295
- * we NEVER emit a syntactically invalid collector config regardless of input
296
- * (header values are already CR/LF-filtered in parseOtlpHeaders, but a stray
297
- * control char in any other field must not be able to break the file).
298
- */
299
- function yamlQuote(s) {
300
- return `"${s
301
- .replace(/\\/g, "\\\\")
302
- .replace(/"/g, '\\"')
303
- .replace(/\n/g, "\\n")
304
- .replace(/\r/g, "\\r")
305
- .replace(/\t/g, "\\t")}"`;
306
- }
307
- /** OTLP signals the collector pipelines cover, in stable output order. */
308
- const OTLP_SIGNALS = ["traces", "metrics", "logs"];
309
- /**
310
- * Resolve one signal's backend per the OTEL env precedence: a
311
- * signal-specific endpoint/headers var wins, otherwise the generic one.
312
- * Returns null when neither is set — that signal's pipeline then drops to
313
- * the `debug` exporter instead of being misrouted to another signal's URL.
314
- */
315
- function resolveSignalBackend(env, signal) {
316
- const SIG = signal.toUpperCase();
317
- const specificEndpoint = (env[`OTEL_EXPORTER_OTLP_${SIG}_ENDPOINT`] || "").trim();
318
- const genericEndpoint = (env.OTEL_EXPORTER_OTLP_ENDPOINT || "").trim();
319
- if (!specificEndpoint && !genericEndpoint)
320
- return null;
321
- const specificHeaders = (env[`OTEL_EXPORTER_OTLP_${SIG}_HEADERS`] || "").trim();
322
- const genericHeaders = env.OTEL_EXPORTER_OTLP_HEADERS;
323
- return specificEndpoint
324
- ? { endpointField: `${signal}_endpoint`, endpoint: specificEndpoint, headers: parseOtlpHeaders(specificHeaders || genericHeaders) }
325
- : { endpointField: "endpoint", endpoint: genericEndpoint, headers: parseOtlpHeaders(specificHeaders || genericHeaders) };
326
- }
327
- /**
328
- * Render the in-network OTEL collector config (docker backend).
329
- *
330
- * `opts.active` gates whether the collector forwards anywhere. It MUST track
331
- * the harness's effective `otel.enabled && otel.forwardToSandbox` — when
332
- * false, every pipeline is wired to a `debug` exporter (drops data) and NO
333
- * backend endpoint or credential is emitted, even if `OTEL_EXPORTER_OTLP_*`
334
- * env vars are present. This is the security floor: the collector listens on
335
- * a static IP that sandboxes can reach, so if forwarding weren't gated, a
336
- * sandbox could POST directly to it and exfil to the real backend regardless
337
- * of the documented `LASTLIGHT_OTEL_ENABLED` / `FORWARD_TO_SANDBOX` opt-outs.
338
- *
339
- * When active, the collector re-exports to the REAL backend — the standard
340
- * `OTEL_EXPORTER_OTLP_ENDPOINT` / `OTEL_EXPORTER_OTLP_HEADERS` plus their
341
- * per-signal `OTEL_EXPORTER_OTLP_<SIGNAL>_ENDPOINT` / `_HEADERS` overrides,
342
- * resolved with the spec's precedence. Each signal gets its own exporter so
343
- * a split traces/metrics/logs setup is routed correctly rather than all
344
- * collapsing onto whichever endpoint won a fallback chain. Those credentials
345
- * live here, in a file on the host mounted read-only into the collector, and
346
- * are NEVER forwarded into an untrusted sandbox. The sandbox can only
347
- * influence span *content* sent to the harness's own fixed backend — it
348
- * cannot redirect where the collector exports, so this adds no SSRF/exfil
349
- * surface.
350
- *
351
- * A signal with no configured endpoint (neither specific nor generic) is
352
- * likewise wired to `debug` so the collector still boots cleanly and accepts
353
- * connections — sandboxes simply never send what isn't routed.
354
- */
355
- export function renderOtelCollectorConfig(opts) {
356
- const env = opts.env ?? process.env;
357
- const exporterBlocks = [];
358
- const pipelineBlocks = [];
359
- let needsDebug = false;
360
- for (const signal of OTLP_SIGNALS) {
361
- // When inactive, resolve NO backend — every signal drops to debug and
362
- // not a single OTEL_* value reaches the generated file.
363
- const backend = opts.active ? resolveSignalBackend(env, signal) : null;
364
- let exporterName;
365
- if (!backend) {
366
- exporterName = "debug";
367
- needsDebug = true;
368
- }
369
- else {
370
- exporterName = `otlphttp/${signal}`;
371
- const headerLines = backend.headers.length
372
- ? `\n headers:\n${backend.headers.map(([k, v]) => ` ${yamlQuote(k)}: ${yamlQuote(v)}`).join("\n")}`
373
- : "";
374
- exporterBlocks.push(` ${exporterName}:\n ${backend.endpointField}: ${yamlQuote(backend.endpoint)}${headerLines}`);
375
- }
376
- pipelineBlocks.push(` ${signal}:\n receivers: [otlp]\n processors: [batch]\n exporters: [${exporterName}]`);
377
- }
378
- if (needsDebug)
379
- exporterBlocks.push(` debug:\n verbosity: normal`);
380
- const statusComment = opts.active
381
- ? `# Sandboxes export OTLP to this collector (reached by its internal IP on
382
- # sandbox-egress); the collector re-exports to the configured backend over
383
- # proxy-egress. Backend credentials below stay host-side — they are never
384
- # forwarded into a sandbox.`
385
- : `# Telemetry forwarding is DISABLED (LASTLIGHT_OTEL_ENABLED is off, or
386
- # LASTLIGHT_OTEL_FORWARD_TO_SANDBOX is false). The collector still listens so
387
- # sandboxes don't get connection errors, but every signal drops to a local
388
- # debug exporter — nothing is forwarded to any backend, and no OTEL_* endpoint
389
- # or credential is emitted here. This is the egress floor for the static
390
- # collector IP regardless of any OTEL_* env vars present.`;
391
- return `# Generated by lastlight at harness boot. DO NOT EDIT BY HAND —
392
- # source of truth is src/sandbox/egress-firewall-config.ts.
393
- #
394
- ${statusComment}
395
- receivers:
396
- otlp:
397
- protocols:
398
- http:
399
- endpoint: 0.0.0.0:${OTEL_COLLECTOR_OTLP_HTTP_PORT}
400
- grpc:
401
- endpoint: 0.0.0.0:${OTEL_COLLECTOR_OTLP_GRPC_PORT}
402
-
403
- processors:
404
- batch: {}
405
-
406
- exporters:
407
- ${exporterBlocks.join("\n")}
408
-
409
- service:
410
- pipelines:
411
- ${pipelineBlocks.join("\n")}
412
- `;
413
- }
414
- /**
415
- * Write the in-network collector config to $STATE_DIR/proxy/otel-collector.yaml.
416
- * Mode 0600 — it can contain backend auth headers. Called at harness boot
417
- * (and by the compose `egress-init` one-shot) so the file exists before the
418
- * `otel-collector` service reads it. `opts.active` gates forwarding — see
419
- * `renderOtelCollectorConfig`. The harness passes its parsed
420
- * `otel.enabled && otel.forwardToSandbox`; the env-only callers compute it
421
- * via `otelForwardActiveFromEnv`.
422
- */
423
- export function writeOtelCollectorConfig(stateDir, opts) {
424
- const dir = join(stateDir, "proxy");
425
- mkdirSync(dir, { recursive: true });
426
- const path = join(dir, "otel-collector.yaml");
427
- // Mode 0600 — it can carry backend auth headers. In the docker deployment
428
- // the writer (harness/egress-init, gosu'd to `lastlight`) and the collector
429
- // share UID 10001 (see OTEL_COLLECTOR_UID + the Dockerfile pin), so the
430
- // collector reads this file as its owner without it being world-readable.
431
- writeFileSync(path, renderOtelCollectorConfig(opts), { mode: 0o600 });
432
- return path;
433
- }
434
- //# sourceMappingURL=egress-firewall-config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"egress-firewall-config.js","sourceRoot":"","sources":["../../src/sandbox/egress-firewall-config.ts"],"names":[],"mappings":"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"}
@@ -1,23 +0,0 @@
1
- /**
2
- * Sandbox image names + the docker availability probes. Kept in this
3
- * dependency-light module (only `child_process`, no `./docker.js`) so callers
4
- * like the workflow runner can check image availability without importing the
5
- * full `DockerSandbox` machinery — `docker.ts` runs `promisify(execFile)` at
6
- * module load, which trips test suites that mock `child_process`.
7
- */
8
- /** The lean default sandbox image (`sandbox.Dockerfile`). */
9
- export declare const SANDBOX_IMAGE = "lastlight-sandbox:latest";
10
- /**
11
- * The enriched browser-QA image (Playwright + Chromium baked in). Built only
12
- * when QA is enabled (`sandbox-qa.Dockerfile`); a phase opts into it with
13
- * `sandbox_image: qa`. Fixed name, deliberately not overridable.
14
- */
15
- export declare const SANDBOX_IMAGE_QA = "lastlight-sandbox-qa:latest";
16
- /** Check if Docker sandbox mode is available (docker up + lean image built). */
17
- export declare function isSandboxAvailable(): boolean;
18
- /**
19
- * Whether the browser-QA sandbox image is present on this host. Used by the
20
- * runner to gracefully skip a `sandbox_image: qa` phase when the image hasn't
21
- * been built (the lean default deploy), rather than failing the run.
22
- */
23
- export declare function qaImageAvailable(): boolean;
@@ -1,55 +0,0 @@
1
- import { execFileSync } from "child_process";
2
- /**
3
- * Sandbox image names + the docker availability probes. Kept in this
4
- * dependency-light module (only `child_process`, no `./docker.js`) so callers
5
- * like the workflow runner can check image availability without importing the
6
- * full `DockerSandbox` machinery — `docker.ts` runs `promisify(execFile)` at
7
- * module load, which trips test suites that mock `child_process`.
8
- */
9
- /** The lean default sandbox image (`sandbox.Dockerfile`). */
10
- export const SANDBOX_IMAGE = "lastlight-sandbox:latest";
11
- /**
12
- * The enriched browser-QA image (Playwright + Chromium baked in). Built only
13
- * when QA is enabled (`sandbox-qa.Dockerfile`); a phase opts into it with
14
- * `sandbox_image: qa`. Fixed name, deliberately not overridable.
15
- */
16
- export const SANDBOX_IMAGE_QA = "lastlight-sandbox-qa:latest";
17
- /** Check if Docker sandbox mode is available (docker up + lean image built). */
18
- export function isSandboxAvailable() {
19
- return dockerAvailable() && sandboxImageExists(SANDBOX_IMAGE);
20
- }
21
- /** Cached check — only probe for the QA image once per process. */
22
- let _qaImageAvailable = null;
23
- /**
24
- * Whether the browser-QA sandbox image is present on this host. Used by the
25
- * runner to gracefully skip a `sandbox_image: qa` phase when the image hasn't
26
- * been built (the lean default deploy), rather than failing the run.
27
- */
28
- export function qaImageAvailable() {
29
- if (_qaImageAvailable === null) {
30
- _qaImageAvailable = dockerAvailable() && sandboxImageExists(SANDBOX_IMAGE_QA);
31
- }
32
- return _qaImageAvailable;
33
- }
34
- function dockerAvailable() {
35
- try {
36
- execFileSync("docker", ["info"], { stdio: "ignore", timeout: 5000 });
37
- return true;
38
- }
39
- catch {
40
- return false;
41
- }
42
- }
43
- function sandboxImageExists(imageName) {
44
- try {
45
- const out = execFileSync("docker", ["images", "-q", imageName], {
46
- encoding: "utf-8",
47
- stdio: ["pipe", "pipe", "pipe"],
48
- });
49
- return out.trim().length > 0;
50
- }
51
- catch {
52
- return false;
53
- }
54
- }
55
- //# sourceMappingURL=images.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"images.js","sourceRoot":"","sources":["../../src/sandbox/images.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;GAMG;AAEH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAExD;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,6BAA6B,CAAC;AAE9D,gFAAgF;AAChF,MAAM,UAAU,kBAAkB;IAChC,OAAO,eAAe,EAAE,IAAI,kBAAkB,CAAC,aAAa,CAAC,CAAC;AAChE,CAAC;AAED,mEAAmE;AACnE,IAAI,iBAAiB,GAAmB,IAAI,CAAC;AAE7C;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;QAC/B,iBAAiB,GAAG,eAAe,EAAE,IAAI,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,SAAS,eAAe;IACtB,IAAI,CAAC;QACH,YAAY,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,SAAiB;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE;YAC9D,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -1,112 +0,0 @@
1
- import { DockerSandbox } from "./docker.js";
2
- export { DockerSandbox } from "./docker.js";
3
- export { SANDBOX_IMAGE, SANDBOX_IMAGE_QA, isSandboxAvailable, qaImageAvailable, } from "./images.js";
4
- /**
5
- * Clean up orphaned sandbox containers from previous runs.
6
- * Called on startup to remove containers that survived a harness restart.
7
- */
8
- export declare function cleanupOrphanedSandboxes(): void;
9
- export declare function sandboxAvailable(): boolean;
10
- /**
11
- * Set up the per-task worktree directory (mkdir + optional pre-clone) without
12
- * touching Docker. Used by the non-docker sandbox modes (gondolin / none) where
13
- * agentic-pi runs in-process or in a VM rather than a container, but still
14
- * wants a per-task workspace cloned from the target branch.
15
- */
16
- export declare function setupTaskWorktree(opts: {
17
- taskId: string;
18
- stateDir: string;
19
- sandboxDir?: string;
20
- prePopulate?: {
21
- owner: string;
22
- repo: string;
23
- branch: string;
24
- token: string;
25
- /** Owning run id — stamped into a marker so a reused per-PR workspace
26
- * refreshes across runs but is preserved between phases of one run. */
27
- runId?: string;
28
- /** Clone shallowly (`--depth 1 --single-branch`) for read-only workflows. */
29
- shallow?: boolean;
30
- };
31
- }): string;
32
- /**
33
- * Create a sandbox for a task. Returns the sandbox and a cleanup function.
34
- * If Docker is not available, returns null (caller should fall back to direct execution).
35
- */
36
- export declare function createTaskSandbox(opts: {
37
- taskId: string;
38
- stateDir: string;
39
- sandboxDir?: string;
40
- env?: Record<string, string>;
41
- /**
42
- * When set, the harness clones the repo into workDir at the named branch
43
- * before starting the sandbox container. The agent then enters a
44
- * workspace that's already checked out, avoiding a redundant
45
- * `clone_repo` MCP call inside the session.
46
- *
47
- * Token must be alphanumeric (shape asserted upstream) and is embedded
48
- * into the clone URL just for this one-shot operation — git's own
49
- * credential.helper inside the sandbox covers subsequent push/pull.
50
- */
51
- prePopulate?: {
52
- owner: string;
53
- repo: string;
54
- branch: string;
55
- token: string;
56
- /** Owning run id — stamped into a marker so a reused per-PR workspace
57
- * refreshes across runs but is preserved between phases of one run. */
58
- runId?: string;
59
- /** Clone shallowly (`--depth 1 --single-branch`) for read-only workflows. */
60
- shallow?: boolean;
61
- };
62
- /**
63
- * IP of the coredns sidecar to use as the sandbox's DNS resolver.
64
- * Selects the egress policy: `172.30.0.10` (coredns-strict) for the
65
- * default allowlist, `172.30.0.11` (coredns-open) for phases that
66
- * declared `unrestricted_egress: true`. Passed to `docker run` as
67
- * `--dns <ip>`. See src/sandbox/egress-firewall-config.ts.
68
- */
69
- dnsIp?: string;
70
- /**
71
- * Override the container image. Defaults to the lean `SANDBOX_IMAGE`; a
72
- * browser-QA phase passes `SANDBOX_IMAGE_QA`. The caller is responsible for
73
- * ensuring the image exists (see `qaImageAvailable`).
74
- */
75
- imageName?: string;
76
- }): Promise<{
77
- sandbox: DockerSandbox;
78
- workDir: string;
79
- cleanup: () => Promise<void>;
80
- } | null>;
81
- type PrePopulate = {
82
- owner: string;
83
- repo: string;
84
- branch: string;
85
- token: string;
86
- runId?: string;
87
- shallow?: boolean;
88
- recreateFromBase?: boolean;
89
- };
90
- /**
91
- * Clone the repo into `<workDir>/<repo>` at the given branch.
92
- *
93
- * Cases:
94
- * - **Fresh dir** (no `.git`): clone. `--depth 1 --single-branch` for
95
- * read-only workflows (`pre.shallow`), `--depth 50` otherwise. For
96
- * `recreateFromBase` the branch is cut from the default branch directly
97
- * (never `clone --branch <feature>`).
98
- * - **Same run revisiting the workspace** (`.git` exists, run marker matches
99
- * `pre.runId`): preserve — this is a later phase of the same run reading
100
- * what an earlier phase wrote (architect's `plan.md`, the reviewer's
101
- * checkout). No git ops.
102
- * - **A fresh run reusing an old per-target workspace** (`.git` exists, marker
103
- * differs/absent):
104
- * - default (pr-review / pr-fix): fetch + hard-reset to the remote branch +
105
- * `git clean` that **keeps `node_modules`** so the next `npm install` is
106
- * incremental (the re-review fast path from issue #107).
107
- * - `recreateFromBase` (build): delete the stale checkout and re-clone from
108
- * the default branch — a re-triggered incomplete build starts again off
109
- * current `main` (issue #153).
110
- */
111
- export { prePopulateWorkspace as __prePopulateWorkspaceForTest };
112
- export declare function prePopulateWorkspace(workDir: string, pre: PrePopulate): void;