lastlight 0.1.15 → 0.2.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 (478) hide show
  1. package/README.md +265 -118
  2. package/agent-context/rules.md +62 -9
  3. package/agent-context/security.md +69 -0
  4. package/config/default.yaml +89 -0
  5. package/deploy/.env.production.example +20 -2
  6. package/deploy/entrypoint.sh +16 -17
  7. package/deploy/native/README.md +160 -0
  8. package/deploy/native/install.sh +110 -0
  9. package/deploy/native/lastlight.env.example +95 -0
  10. package/deploy/native/lastlight.service +50 -0
  11. package/deploy/sandbox-entrypoint.sh +59 -42
  12. package/dist/admin/auth.d.ts +1 -1
  13. package/dist/admin/auth.js +3 -1
  14. package/dist/admin/auth.js.map +1 -1
  15. package/dist/admin/chat-session-reader.d.ts +11 -14
  16. package/dist/admin/chat-session-reader.js +27 -51
  17. package/dist/admin/chat-session-reader.js.map +1 -1
  18. package/dist/admin/docker.d.ts +37 -0
  19. package/dist/admin/docker.js +86 -1
  20. package/dist/admin/docker.js.map +1 -1
  21. package/dist/admin/index.js +38 -3
  22. package/dist/admin/index.js.map +1 -1
  23. package/dist/admin/log-search.test.d.ts +1 -0
  24. package/dist/admin/log-search.test.js +78 -0
  25. package/dist/admin/log-search.test.js.map +1 -0
  26. package/dist/admin/routes.d.ts +52 -3
  27. package/dist/admin/routes.js +780 -63
  28. package/dist/admin/routes.js.map +1 -1
  29. package/dist/admin/routes.test.js +680 -14
  30. package/dist/admin/routes.test.js.map +1 -1
  31. package/dist/admin/server-logs.test.d.ts +1 -0
  32. package/dist/admin/server-logs.test.js +58 -0
  33. package/dist/admin/server-logs.test.js.map +1 -0
  34. package/dist/admin/sessions.d.ts +26 -39
  35. package/dist/admin/sessions.js +86 -310
  36. package/dist/admin/sessions.js.map +1 -1
  37. package/dist/admin/sessions.test.d.ts +1 -0
  38. package/dist/admin/sessions.test.js +78 -0
  39. package/dist/admin/sessions.test.js.map +1 -0
  40. package/dist/admin/version.d.ts +15 -0
  41. package/dist/admin/version.js +73 -0
  42. package/dist/admin/version.js.map +1 -0
  43. package/dist/admin/version.test.d.ts +1 -0
  44. package/dist/admin/version.test.js +71 -0
  45. package/dist/admin/version.test.js.map +1 -0
  46. package/dist/cli-config.d.ts +55 -0
  47. package/dist/cli-config.js +119 -0
  48. package/dist/cli-config.js.map +1 -0
  49. package/dist/cli-config.test.d.ts +1 -0
  50. package/dist/cli-config.test.js +92 -0
  51. package/dist/cli-config.test.js.map +1 -0
  52. package/dist/cli-format.d.ts +19 -0
  53. package/dist/cli-format.js +107 -0
  54. package/dist/cli-format.js.map +1 -0
  55. package/dist/cli-server.d.ts +73 -0
  56. package/dist/cli-server.js +347 -0
  57. package/dist/cli-server.js.map +1 -0
  58. package/dist/cli-server.test.d.ts +1 -0
  59. package/dist/cli-server.test.js +38 -0
  60. package/dist/cli-server.test.js.map +1 -0
  61. package/dist/cli-timeline.d.ts +35 -0
  62. package/dist/cli-timeline.js +373 -0
  63. package/dist/cli-timeline.js.map +1 -0
  64. package/dist/cli-timeline.test.d.ts +1 -0
  65. package/dist/cli-timeline.test.js +104 -0
  66. package/dist/cli-timeline.test.js.map +1 -0
  67. package/dist/cli.d.ts +0 -19
  68. package/dist/cli.js +910 -194
  69. package/dist/cli.js.map +1 -1
  70. package/dist/config-overlay.test.d.ts +1 -0
  71. package/dist/config-overlay.test.js +112 -0
  72. package/dist/config-overlay.test.js.map +1 -0
  73. package/dist/config-resolve.d.ts +28 -0
  74. package/dist/config-resolve.js +47 -0
  75. package/dist/config-resolve.js.map +1 -0
  76. package/dist/config-resolve.test.d.ts +1 -0
  77. package/dist/config-resolve.test.js +68 -0
  78. package/dist/config-resolve.test.js.map +1 -0
  79. package/dist/config.d.ts +62 -49
  80. package/dist/config.js +452 -76
  81. package/dist/config.js.map +1 -1
  82. package/dist/config.test.js +201 -32
  83. package/dist/config.test.js.map +1 -1
  84. package/dist/connectors/github-webhook.js +83 -5
  85. package/dist/connectors/github-webhook.js.map +1 -1
  86. package/dist/connectors/github-webhook.test.d.ts +1 -0
  87. package/dist/connectors/github-webhook.test.js +156 -0
  88. package/dist/connectors/github-webhook.test.js.map +1 -0
  89. package/dist/connectors/messaging/session-manager.d.ts +18 -0
  90. package/dist/connectors/messaging/session-manager.js +83 -2
  91. package/dist/connectors/messaging/session-manager.js.map +1 -1
  92. package/dist/connectors/messaging/session-manager.test.d.ts +1 -0
  93. package/dist/connectors/messaging/session-manager.test.js +144 -0
  94. package/dist/connectors/messaging/session-manager.test.js.map +1 -0
  95. package/dist/connectors/slack/connector.d.ts +9 -0
  96. package/dist/connectors/slack/connector.js +15 -0
  97. package/dist/connectors/slack/connector.js.map +1 -1
  98. package/dist/connectors/slack/mrkdwn.js +81 -0
  99. package/dist/connectors/slack/mrkdwn.js.map +1 -1
  100. package/dist/connectors/slack/mrkdwn.test.js +49 -0
  101. package/dist/connectors/slack/mrkdwn.test.js.map +1 -1
  102. package/dist/connectors/types.d.ts +1 -1
  103. package/dist/cron/fanout.d.ts +33 -0
  104. package/dist/cron/fanout.js +55 -0
  105. package/dist/cron/fanout.js.map +1 -0
  106. package/dist/cron/fanout.test.d.ts +1 -0
  107. package/dist/cron/fanout.test.js +73 -0
  108. package/dist/cron/fanout.test.js.map +1 -0
  109. package/dist/cron/jobs.d.ts +5 -0
  110. package/dist/cron/jobs.js +10 -3
  111. package/dist/cron/jobs.js.map +1 -1
  112. package/dist/cron/scheduler.d.ts +12 -0
  113. package/dist/cron/scheduler.js +27 -1
  114. package/dist/cron/scheduler.js.map +1 -1
  115. package/dist/engine/agent-executor.d.ts +158 -0
  116. package/dist/engine/agent-executor.js +1140 -0
  117. package/dist/engine/agent-executor.js.map +1 -0
  118. package/dist/engine/agent-executor.test.d.ts +1 -0
  119. package/dist/engine/agent-executor.test.js +395 -0
  120. package/dist/engine/agent-executor.test.js.map +1 -0
  121. package/dist/engine/chat-runner.d.ts +123 -0
  122. package/dist/engine/chat-runner.js +379 -0
  123. package/dist/engine/chat-runner.js.map +1 -0
  124. package/dist/engine/chat-runner.test.d.ts +1 -0
  125. package/dist/engine/chat-runner.test.js +104 -0
  126. package/dist/engine/chat-runner.test.js.map +1 -0
  127. package/dist/engine/chat-skills.d.ts +45 -0
  128. package/dist/engine/chat-skills.js +184 -0
  129. package/dist/engine/chat-skills.js.map +1 -0
  130. package/dist/engine/chat-skills.test.d.ts +1 -0
  131. package/dist/engine/chat-skills.test.js +20 -0
  132. package/dist/engine/chat-skills.test.js.map +1 -0
  133. package/dist/engine/chat.d.ts +20 -23
  134. package/dist/engine/chat.js +251 -155
  135. package/dist/engine/chat.js.map +1 -1
  136. package/dist/engine/chat.test.d.ts +1 -0
  137. package/dist/engine/chat.test.js +42 -0
  138. package/dist/engine/chat.test.js.map +1 -0
  139. package/dist/engine/classifier.d.ts +35 -2
  140. package/dist/engine/classifier.js +170 -31
  141. package/dist/engine/classifier.js.map +1 -1
  142. package/dist/engine/classifier.test.d.ts +1 -0
  143. package/dist/engine/classifier.test.js +115 -0
  144. package/dist/engine/classifier.test.js.map +1 -0
  145. package/dist/engine/dispatcher.d.ts +60 -0
  146. package/dist/engine/dispatcher.js +601 -0
  147. package/dist/engine/dispatcher.js.map +1 -0
  148. package/dist/engine/dispatcher.test.d.ts +1 -0
  149. package/dist/engine/dispatcher.test.js +511 -0
  150. package/dist/engine/dispatcher.test.js.map +1 -0
  151. package/dist/engine/event-shim.d.ts +117 -0
  152. package/dist/engine/event-shim.js +435 -0
  153. package/dist/engine/event-shim.js.map +1 -0
  154. package/dist/engine/event-shim.test.d.ts +1 -0
  155. package/dist/engine/event-shim.test.js +194 -0
  156. package/dist/engine/event-shim.test.js.map +1 -0
  157. package/dist/engine/git-auth.d.ts +13 -17
  158. package/dist/engine/git-auth.js +99 -42
  159. package/dist/engine/git-auth.js.map +1 -1
  160. package/dist/engine/git-auth.test.d.ts +1 -0
  161. package/dist/engine/git-auth.test.js +117 -0
  162. package/dist/engine/git-auth.test.js.map +1 -0
  163. package/dist/engine/github-app-client.d.ts +7 -0
  164. package/dist/engine/github-app-client.js +16 -0
  165. package/dist/engine/github-app-client.js.map +1 -0
  166. package/dist/engine/github-app-client.test.d.ts +1 -0
  167. package/dist/engine/github-app-client.test.js +44 -0
  168. package/dist/engine/github-app-client.test.js.map +1 -0
  169. package/dist/engine/github-tools.d.ts +12 -0
  170. package/dist/engine/github-tools.js +261 -0
  171. package/dist/engine/github-tools.js.map +1 -0
  172. package/dist/engine/github.d.ts +1027 -43
  173. package/dist/engine/github.js +153 -14
  174. package/dist/engine/github.js.map +1 -1
  175. package/dist/engine/llm.d.ts +47 -0
  176. package/dist/engine/llm.js +221 -0
  177. package/dist/engine/llm.js.map +1 -0
  178. package/dist/engine/llm.test.d.ts +1 -0
  179. package/dist/engine/llm.test.js +189 -0
  180. package/dist/engine/llm.test.js.map +1 -0
  181. package/dist/engine/profiles.d.ts +249 -0
  182. package/dist/engine/profiles.js +42 -0
  183. package/dist/engine/profiles.js.map +1 -0
  184. package/dist/engine/router.d.ts +12 -6
  185. package/dist/engine/router.js +339 -81
  186. package/dist/engine/router.js.map +1 -1
  187. package/dist/engine/router.test.js +428 -78
  188. package/dist/engine/router.test.js.map +1 -1
  189. package/dist/engine/screen.d.ts +41 -0
  190. package/dist/engine/screen.js +93 -0
  191. package/dist/engine/screen.js.map +1 -0
  192. package/dist/engine/screen.test.d.ts +1 -0
  193. package/dist/engine/screen.test.js +82 -0
  194. package/dist/engine/screen.test.js.map +1 -0
  195. package/dist/index.js +377 -432
  196. package/dist/index.js.map +1 -1
  197. package/dist/managed-repos.d.ts +7 -9
  198. package/dist/managed-repos.js +12 -15
  199. package/dist/managed-repos.js.map +1 -1
  200. package/dist/managed-repos.test.js +24 -19
  201. package/dist/managed-repos.test.js.map +1 -1
  202. package/dist/notify/index.d.ts +15 -0
  203. package/dist/notify/index.js +6 -0
  204. package/dist/notify/index.js.map +1 -0
  205. package/dist/notify/model.d.ts +57 -0
  206. package/dist/notify/model.js +89 -0
  207. package/dist/notify/model.js.map +1 -0
  208. package/dist/notify/model.test.d.ts +1 -0
  209. package/dist/notify/model.test.js +109 -0
  210. package/dist/notify/model.test.js.map +1 -0
  211. package/dist/notify/notifier.d.ts +17 -0
  212. package/dist/notify/notifier.js +87 -0
  213. package/dist/notify/notifier.js.map +1 -0
  214. package/dist/notify/notifier.test.d.ts +1 -0
  215. package/dist/notify/notifier.test.js +100 -0
  216. package/dist/notify/notifier.test.js.map +1 -0
  217. package/dist/notify/render.d.ts +21 -0
  218. package/dist/notify/render.js +59 -0
  219. package/dist/notify/render.js.map +1 -0
  220. package/dist/notify/render.test.d.ts +1 -0
  221. package/dist/notify/render.test.js +65 -0
  222. package/dist/notify/render.test.js.map +1 -0
  223. package/dist/notify/transports/github.d.ts +26 -0
  224. package/dist/notify/transports/github.js +26 -0
  225. package/dist/notify/transports/github.js.map +1 -0
  226. package/dist/notify/transports/slack.d.ts +26 -0
  227. package/dist/notify/transports/slack.js +28 -0
  228. package/dist/notify/transports/slack.js.map +1 -0
  229. package/dist/notify/transports.test.d.ts +1 -0
  230. package/dist/notify/transports.test.js +71 -0
  231. package/dist/notify/transports.test.js.map +1 -0
  232. package/dist/notify/types.d.ts +95 -0
  233. package/dist/notify/types.js +16 -0
  234. package/dist/notify/types.js.map +1 -0
  235. package/dist/sandbox/docker-compose.test.d.ts +1 -0
  236. package/dist/sandbox/docker-compose.test.js +128 -0
  237. package/dist/sandbox/docker-compose.test.js.map +1 -0
  238. package/dist/sandbox/docker.d.ts +97 -9
  239. package/dist/sandbox/docker.js +192 -41
  240. package/dist/sandbox/docker.js.map +1 -1
  241. package/dist/sandbox/docker.test.d.ts +1 -0
  242. package/dist/sandbox/docker.test.js +137 -0
  243. package/dist/sandbox/docker.test.js.map +1 -0
  244. package/dist/sandbox/egress-allowlist.d.ts +69 -0
  245. package/dist/sandbox/egress-allowlist.js +164 -0
  246. package/dist/sandbox/egress-allowlist.js.map +1 -0
  247. package/dist/sandbox/egress-allowlist.test.d.ts +1 -0
  248. package/dist/sandbox/egress-allowlist.test.js +65 -0
  249. package/dist/sandbox/egress-allowlist.test.js.map +1 -0
  250. package/dist/sandbox/egress-firewall-config.d.ts +127 -0
  251. package/dist/sandbox/egress-firewall-config.js +434 -0
  252. package/dist/sandbox/egress-firewall-config.js.map +1 -0
  253. package/dist/sandbox/egress-firewall-config.test.d.ts +1 -0
  254. package/dist/sandbox/egress-firewall-config.test.js +244 -0
  255. package/dist/sandbox/egress-firewall-config.test.js.map +1 -0
  256. package/dist/sandbox/images.d.ts +23 -0
  257. package/dist/sandbox/images.js +55 -0
  258. package/dist/sandbox/images.js.map +1 -0
  259. package/dist/sandbox/index.d.ts +82 -3
  260. package/dist/sandbox/index.js +223 -93
  261. package/dist/sandbox/index.js.map +1 -1
  262. package/dist/sandbox/index.test.d.ts +1 -0
  263. package/dist/sandbox/index.test.js +157 -0
  264. package/dist/sandbox/index.test.js.map +1 -0
  265. package/dist/session-log.d.ts +63 -0
  266. package/dist/session-log.js +290 -0
  267. package/dist/session-log.js.map +1 -0
  268. package/dist/session-log.test.d.ts +1 -0
  269. package/dist/session-log.test.js +85 -0
  270. package/dist/session-log.test.js.map +1 -0
  271. package/dist/setup.d.ts +30 -1
  272. package/dist/setup.js +241 -25
  273. package/dist/setup.js.map +1 -1
  274. package/dist/setup.test.js +138 -6
  275. package/dist/setup.test.js.map +1 -1
  276. package/dist/state/approval-store.d.ts +86 -0
  277. package/dist/state/approval-store.js +140 -0
  278. package/dist/state/approval-store.js.map +1 -0
  279. package/dist/state/build-assets.d.ts +50 -0
  280. package/dist/state/build-assets.js +154 -0
  281. package/dist/state/build-assets.js.map +1 -0
  282. package/dist/state/build-assets.test.d.ts +1 -0
  283. package/dist/state/build-assets.test.js +106 -0
  284. package/dist/state/build-assets.test.js.map +1 -0
  285. package/dist/state/db.d.ts +65 -333
  286. package/dist/state/db.js +112 -835
  287. package/dist/state/db.js.map +1 -1
  288. package/dist/state/db.test.js +127 -91
  289. package/dist/state/db.test.js.map +1 -1
  290. package/dist/state/execution-store.d.ts +257 -0
  291. package/dist/state/execution-store.js +527 -0
  292. package/dist/state/execution-store.js.map +1 -0
  293. package/dist/state/migrate.d.ts +15 -0
  294. package/dist/state/migrate.js +175 -0
  295. package/dist/state/migrate.js.map +1 -0
  296. package/dist/state/workflow-run-store.d.ts +195 -0
  297. package/dist/state/workflow-run-store.js +363 -0
  298. package/dist/state/workflow-run-store.js.map +1 -0
  299. package/dist/state/workflow-run-store.test.d.ts +1 -0
  300. package/dist/state/workflow-run-store.test.js +264 -0
  301. package/dist/state/workflow-run-store.test.js.map +1 -0
  302. package/dist/telemetry/index.d.ts +38 -0
  303. package/dist/telemetry/index.js +253 -0
  304. package/dist/telemetry/index.js.map +1 -0
  305. package/dist/telemetry/index.test.d.ts +1 -0
  306. package/dist/telemetry/index.test.js +73 -0
  307. package/dist/telemetry/index.test.js.map +1 -0
  308. package/dist/telemetry/pi-events.d.ts +12 -0
  309. package/dist/telemetry/pi-events.js +134 -0
  310. package/dist/telemetry/pi-events.js.map +1 -0
  311. package/dist/telemetry/pi-events.test.d.ts +1 -0
  312. package/dist/telemetry/pi-events.test.js +69 -0
  313. package/dist/telemetry/pi-events.test.js.map +1 -0
  314. package/dist/workflows/dag.d.ts +13 -1
  315. package/dist/workflows/dag.js +7 -3
  316. package/dist/workflows/dag.js.map +1 -1
  317. package/dist/workflows/dag.test.js +22 -0
  318. package/dist/workflows/dag.test.js.map +1 -1
  319. package/dist/workflows/golden-build.test.d.ts +1 -0
  320. package/dist/workflows/golden-build.test.js +45 -0
  321. package/dist/workflows/golden-build.test.js.map +1 -0
  322. package/dist/workflows/loader-overlay.test.d.ts +1 -0
  323. package/dist/workflows/loader-overlay.test.js +101 -0
  324. package/dist/workflows/loader-overlay.test.js.map +1 -0
  325. package/dist/workflows/loader.d.ts +36 -11
  326. package/dist/workflows/loader.js +311 -87
  327. package/dist/workflows/loader.js.map +1 -1
  328. package/dist/workflows/loader.test.js +114 -10
  329. package/dist/workflows/loader.test.js.map +1 -1
  330. package/dist/workflows/loop-eval.js +2 -0
  331. package/dist/workflows/loop-eval.js.map +1 -1
  332. package/dist/workflows/loop-eval.test.js +14 -0
  333. package/dist/workflows/loop-eval.test.js.map +1 -1
  334. package/dist/workflows/phase-executor.d.ts +145 -0
  335. package/dist/workflows/phase-executor.js +691 -0
  336. package/dist/workflows/phase-executor.js.map +1 -0
  337. package/dist/workflows/phase-executor.test.d.ts +1 -0
  338. package/dist/workflows/phase-executor.test.js +434 -0
  339. package/dist/workflows/phase-executor.test.js.map +1 -0
  340. package/dist/workflows/phase-ref.d.ts +44 -0
  341. package/dist/workflows/phase-ref.js +78 -0
  342. package/dist/workflows/phase-ref.js.map +1 -0
  343. package/dist/workflows/phase-ref.test.d.ts +1 -0
  344. package/dist/workflows/phase-ref.test.js +24 -0
  345. package/dist/workflows/phase-ref.test.js.map +1 -0
  346. package/dist/workflows/resume.d.ts +5 -11
  347. package/dist/workflows/resume.js +88 -7
  348. package/dist/workflows/resume.js.map +1 -1
  349. package/dist/workflows/runner.d.ts +34 -27
  350. package/dist/workflows/runner.js +252 -930
  351. package/dist/workflows/runner.js.map +1 -1
  352. package/dist/workflows/runner.test.js +346 -35
  353. package/dist/workflows/runner.test.js.map +1 -1
  354. package/dist/workflows/schema.d.ts +52 -10
  355. package/dist/workflows/schema.js +147 -7
  356. package/dist/workflows/schema.js.map +1 -1
  357. package/dist/workflows/simple.d.ts +67 -3
  358. package/dist/workflows/simple.js +248 -87
  359. package/dist/workflows/simple.js.map +1 -1
  360. package/dist/workflows/simple.test.d.ts +1 -0
  361. package/dist/workflows/simple.test.js +107 -0
  362. package/dist/workflows/simple.test.js.map +1 -0
  363. package/dist/workflows/templates.d.ts +29 -0
  364. package/dist/workflows/templates.js +42 -4
  365. package/dist/workflows/templates.js.map +1 -1
  366. package/dist/workflows/templates.test.js +103 -0
  367. package/dist/workflows/templates.test.js.map +1 -1
  368. package/dist/workflows/triggers.d.ts +21 -0
  369. package/dist/workflows/triggers.js +44 -0
  370. package/dist/workflows/triggers.js.map +1 -0
  371. package/dist/workflows/verdict.d.ts +19 -0
  372. package/dist/workflows/verdict.js +30 -0
  373. package/dist/workflows/verdict.js.map +1 -0
  374. package/dist/workflows/verdict.test.d.ts +1 -0
  375. package/dist/workflows/verdict.test.js +44 -0
  376. package/dist/workflows/verdict.test.js.map +1 -0
  377. package/dist/worktree/manager.d.ts +1 -1
  378. package/dist/worktree/manager.js +14 -14
  379. package/dist/worktree/manager.js.map +1 -1
  380. package/dist/worktree/manager.test.d.ts +1 -0
  381. package/dist/worktree/manager.test.js +87 -0
  382. package/dist/worktree/manager.test.js.map +1 -0
  383. package/docker-compose.yml +206 -5
  384. package/package.json +17 -7
  385. package/sandbox.Dockerfile +97 -18
  386. package/skills/README.md +40 -0
  387. package/skills/browser-qa/SKILL.md +193 -0
  388. package/skills/browser-qa/scripts/agent-browser.mjs +380 -0
  389. package/skills/building/SKILL.md +95 -0
  390. package/skills/chat/SKILL.md +30 -11
  391. package/skills/code-review/SKILL.md +59 -0
  392. package/skills/debug-production/SKILL.md +108 -0
  393. package/skills/demo/SKILL.md +194 -0
  394. package/skills/demo/scripts/compose-demo.sh +205 -0
  395. package/skills/issue-answer/SKILL.md +90 -0
  396. package/skills/issue-comment/SKILL.md +38 -24
  397. package/skills/issue-triage/SKILL.md +103 -37
  398. package/skills/issue-triage/references/AGENT-BRIEF.md +61 -0
  399. package/skills/pr-comment/SKILL.md +64 -0
  400. package/skills/pr-review/SKILL.md +65 -49
  401. package/skills/qa-test/SKILL.md +97 -0
  402. package/skills/repo-health/SKILL.md +48 -45
  403. package/skills/security-feedback/SKILL.md +173 -0
  404. package/skills/security-feedback/references/templates.md +77 -0
  405. package/skills/security-review/SKILL.md +213 -0
  406. package/skills/security-review/references/issue-format.md +302 -0
  407. package/skills/verify/SKILL.md +118 -0
  408. package/workflows/answer.yaml +37 -0
  409. package/workflows/build.yaml +32 -22
  410. package/workflows/cron-security.yaml +6 -0
  411. package/workflows/demo.yaml +55 -0
  412. package/workflows/explore.yaml +9 -0
  413. package/workflows/pr-comment.yaml +16 -0
  414. package/workflows/pr-fix.yaml +3 -0
  415. package/workflows/pr-review.yaml +4 -1
  416. package/workflows/prompts/answer.md +67 -0
  417. package/workflows/prompts/architect.md +24 -7
  418. package/workflows/prompts/demo.md +100 -0
  419. package/workflows/prompts/executor.md +29 -20
  420. package/workflows/prompts/explore-ask.md +51 -22
  421. package/workflows/prompts/explore-publish.md +2 -2
  422. package/workflows/prompts/explore-read.md +24 -10
  423. package/workflows/prompts/explore-synthesize.md +15 -8
  424. package/workflows/prompts/fix.md +9 -10
  425. package/workflows/prompts/guardrails.md +26 -8
  426. package/workflows/prompts/pr-fix.md +6 -7
  427. package/workflows/prompts/pr.md +11 -10
  428. package/workflows/prompts/qa-browser.md +95 -0
  429. package/workflows/prompts/qa-synth.md +44 -0
  430. package/workflows/prompts/qa-test.md +60 -0
  431. package/workflows/prompts/re-reviewer.md +8 -9
  432. package/workflows/prompts/reviewer.md +10 -9
  433. package/workflows/prompts/verify-browser.md +93 -0
  434. package/workflows/prompts/verify-synth.md +44 -0
  435. package/workflows/prompts/verify.md +63 -0
  436. package/workflows/qa-test.yaml +87 -0
  437. package/workflows/security-feedback.yaml +25 -0
  438. package/workflows/security-review.yaml +29 -0
  439. package/workflows/verify.yaml +85 -0
  440. package/deploy/mcp-config.tmpl.json +0 -14
  441. package/dist/cron/rate-limits.d.ts +0 -26
  442. package/dist/cron/rate-limits.js +0 -193
  443. package/dist/cron/rate-limits.js.map +0 -1
  444. package/dist/engine/executor.d.ts +0 -75
  445. package/dist/engine/executor.js +0 -292
  446. package/dist/engine/executor.js.map +0 -1
  447. package/mcp-github-app/package.json +0 -18
  448. package/mcp-github-app/src/auth.js +0 -66
  449. package/mcp-github-app/src/github.js +0 -371
  450. package/mcp-github-app/src/index.js +0 -534
  451. package/skills/devops/webhook-subscriptions/SKILL.md +0 -180
  452. package/skills/github/DESCRIPTION.md +0 -3
  453. package/skills/github/codebase-inspection/SKILL.md +0 -115
  454. package/skills/github/github-auth/SKILL.md +0 -54
  455. package/skills/github/github-auth/scripts/gh-env.sh +0 -66
  456. package/skills/github/github-code-review/SKILL.md +0 -480
  457. package/skills/github/github-code-review/references/review-output-template.md +0 -74
  458. package/skills/github/github-issues/SKILL.md +0 -369
  459. package/skills/github/github-issues/templates/bug-report.md +0 -35
  460. package/skills/github/github-issues/templates/feature-request.md +0 -31
  461. package/skills/github/github-pr-workflow/SKILL.md +0 -395
  462. package/skills/github/github-pr-workflow/references/ci-troubleshooting.md +0 -183
  463. package/skills/github/github-pr-workflow/references/conventional-commits.md +0 -71
  464. package/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +0 -35
  465. package/skills/github/github-pr-workflow/templates/pr-body-feature.md +0 -33
  466. package/skills/github/github-repo-management/SKILL.md +0 -515
  467. package/skills/github/github-repo-management/references/github-api-cheatsheet.md +0 -161
  468. package/skills/github-orchestrator/SKILL.md +0 -103
  469. package/skills/mcp/DESCRIPTION.md +0 -3
  470. package/skills/mcp/mcporter/SKILL.md +0 -122
  471. package/skills/mcp/native-mcp/SKILL.md +0 -356
  472. package/skills/software-development/architect/SKILL.md +0 -154
  473. package/skills/software-development/assure-guardrails/SKILL.md +0 -239
  474. package/skills/software-development/plan/SKILL.md +0 -64
  475. package/skills/software-development/requesting-code-review/SKILL.md +0 -291
  476. package/skills/software-development/subagent-driven-development/SKILL.md +0 -433
  477. package/skills/software-development/systematic-debugging/SKILL.md +0 -366
  478. package/skills/software-development/test-driven-development/SKILL.md +0 -342
@@ -0,0 +1,40 @@
1
+ # Skills
2
+
3
+ Skill directories staged into the sandbox per workflow phase (and a subset into
4
+ chat). A phase declares `skill: <name>` / `skills: [<name>, …]`; the runner
5
+ stages the whole directory into `<agentCwd>/.agents/skills/<name>/` and
6
+ pi-coding-agent surfaces `name` + `description` in the system prompt. The agent
7
+ reads SKILL.md (and any `references/`) on demand via its `read` tool.
8
+
9
+ **Authoring guide:** [`docs/agents/writing-skills.md`](../docs/agents/writing-skills.md)
10
+ — the loading contract, the craft, and house conventions. Read it before adding
11
+ or editing a skill.
12
+
13
+ ## Skills
14
+
15
+ | Skill | Used by | Purpose |
16
+ |-------|---------|---------|
17
+ | [`chat`](chat/) | chat | Conversational assistant for Slack/Discord threads — answer repo/PR/issue questions, guide to natural-language triggers. |
18
+ | [`issue-triage`](issue-triage/) | `issue-triage.yaml`, chat | Move issues through the canonical triage state machine — categorise, label, dedupe, manage stale, write agent briefs. |
19
+ | [`issue-comment`](issue-comment/) | `issue-comment.yaml` | Handle non-build maintainer comments on issues/PRs — close, label, answer briefly, redirect build requests. |
20
+ | [`pr-review`](pr-review/) | `pr-review.yaml`, chat | Review a PR with structured, tiered feedback; build and test from the local checkout; post a formal review. |
21
+ | [`pr-comment`](pr-comment/) | `pr-comment.yaml` | Answer a maintainer's question about an open PR with concrete, code-cited evidence (not a full review). |
22
+ | [`repo-health`](repo-health/) | `repo-health.yaml`, chat | Generate a repository health report — open issues, PR backlog, CI status, action items. |
23
+ | [`security-review`](security-review/) | `security-review.yaml` | Diff-scoped SDLC security review; file one dated summary issue with a task-list of findings. |
24
+ | [`security-feedback`](security-feedback/) | `security-feedback.yaml` | Process a maintainer's comment on a security scan summary — break findings into issues or record suppressions. |
25
+ | [`building`](building/) | build (executor, reviewer), `pr-fix.yaml`, `pr-review.yaml` | Shared craft: install-first + package-manager detection, the test/lint/typecheck gate, and TDD discipline in the sandbox. |
26
+ | [`code-review`](code-review/) | build (reviewer), `pr-review.yaml` | Shared rubric: finding tiers (Critical/Important/Suggestions/Nits) and what-to-check. Referenced by both the branch-diff reviewer and the PR reviewer. |
27
+ | [`verify`](verify/) | `verify.yaml` | Test a behaviour claim as an investigator — CONFIRMED / REFUTED / INCONCLUSIVE with bash-captured evidence. |
28
+ | [`qa-test`](qa-test/) | `qa-test.yaml` | Run an automated QA flow against a CLI or locally-served app and report step-level pass/fail with evidence. |
29
+ | [`browser-qa`](browser-qa/) | `verify.yaml`, `qa-test.yaml`, `demo.yaml` | Drive a real headless browser against a locally-served web UI and capture screenshot (and, for `demo`, video) evidence. Gated to the docker QA image. |
30
+ | [`demo`](demo/) | `demo.yaml` | Record a short demo VIDEO of a PR/feature — drive the web UI, screen-record, and composite a titled, size-capped mp4 with ffmpeg. Gated to the docker QA image. |
31
+
32
+ `building` and `code-review` are **shared building blocks** — referenced by the
33
+ build-cycle prompts (`workflows/prompts/*.md`) and the PR skills so the install
34
+ gate and review rubric live in one place. They're staged alongside the phase's
35
+ primary skill/prompt and read on demand.
36
+
37
+ The live set is enforced at startup: `validateAssets()` (`src/workflows/loader.ts`)
38
+ resolves every workflow `skill:` reference and `CHAT_SKILL_NAMES`
39
+ (`src/engine/chat-skills.ts`). Adding a skill here does nothing until a phase or
40
+ the chat list references it.
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: browser-qa
3
+ description: Drive a real headless browser against a locally-served web UI and capture screenshot evidence, reporting step-level PASS/FAIL/BLOCKED with extracted DOM text and console errors. Use when a phase on the docker QA image must exercise a rendered UI and attach screenshots — it complements the text-evidence verify/qa-test skills.
4
+ version: 1.0.0
5
+ tags: [browser, qa, screenshots, evidence]
6
+ ---
7
+
8
+ # Browser QA
9
+
10
+ Drive a web UI with a real headless Chromium and report **step-level
11
+ pass/fail with evidence**: extracted DOM text, assertion results, console
12
+ errors, and screenshot files. The deliverable is a QA report — partial
13
+ coverage with documented failures is the expected output.
14
+
15
+ This skill is the **browser/screenshot path** that `verify` and `qa-test`
16
+ scope out to "a separate docker-gated capability". It only works on the
17
+ `lastlight-sandbox-qa:latest` docker image, which bakes in Playwright +
18
+ Chromium. You have bash + file tools but **no vision** — you reason over the
19
+ driver's JSON stdout (extracted text, assertion results, console errors).
20
+ The PNG screenshots are **human evidence only**; you never see them.
21
+
22
+ This skill uses the **building** skill for installing dependencies and
23
+ starting the repo's dev-server.
24
+
25
+ ## When this applies
26
+
27
+ A phase running on the docker QA image whose target is a **web app** the
28
+ repo serves on `localhost` and where rendered-UI behaviour or visual
29
+ evidence matters. For a CLI or a curl-able API, use `qa-test`/`verify`
30
+ instead — don't launch a browser you don't need.
31
+
32
+ ## FIRST — probe for a browser
33
+
34
+ The driver lives in this skill's bundle at
35
+ `<skill-dir>/scripts/agent-browser.mjs`. Resolve `<skill-dir>` from the
36
+ available-skills catalogue (it gives the absolute staged path of this
37
+ skill); do not assume a relative path from `$PWD` — the agent's cwd is the
38
+ checked-out repo, the bundle is a sibling.
39
+
40
+ Run the runtime probe before anything else:
41
+
42
+ ```bash
43
+ node <skill-dir>/scripts/agent-browser.mjs doctor
44
+ ```
45
+
46
+ - Exit 0 with `{"ok":true,"chromium":"..."}` → Chromium is available; proceed.
47
+ - Exit non-zero (e.g. `playwright not available …`, or no Chromium — the lean
48
+ default image, or a gondolin run) → **DO NOT attempt browser QA.** Fall back
49
+ to the text path (`verify`/`qa-test` style: curl the dev-server, capture
50
+ stdout) and **say in your report that the browser path was unavailable** and
51
+ you used the text path.
52
+
53
+ ## Start the app
54
+
55
+ Follow the **building** skill to install dependencies, then start the repo's
56
+ dev-server **in the background** and wait until it is listening on
57
+ `localhost:PORT` (poll with `curl` until it answers). Note the port — you
58
+ pass it as `--base-url`.
59
+
60
+ ## Author a flow
61
+
62
+ Write a `flow.json` describing the QA flow. Shape:
63
+
64
+ ```json
65
+ {
66
+ "baseUrl": "http://localhost:3000",
67
+ "viewport": { "width": 1280, "height": 800 },
68
+ "steps": [
69
+ { "name": "home", "goto": "/" },
70
+ { "click": "text=Login" },
71
+ { "fill": ["#email", "a@b.com"] },
72
+ { "type": ["#password", "hunter2"] },
73
+ { "press": "Enter" },
74
+ { "waitFor": "#dashboard" },
75
+ { "assertText": "Welcome" },
76
+ { "text": "h1" },
77
+ { "screenshot": "after-login" }
78
+ ]
79
+ }
80
+ ```
81
+
82
+ Step keys (a step may combine an action **plus** a trailing `screenshot`):
83
+
84
+ | Key | Value | Effect |
85
+ |--------------|----------------------|------------------------------------------------------------------|
86
+ | `goto` | path or URL | Navigate (resolved against `baseUrl`). **Failure is fatal.** |
87
+ | `click` | selector | Click the element. |
88
+ | `fill` | `[selector, value]` | Set an input's value. |
89
+ | `type` | `[selector, value]` | Type key-by-key into an input. |
90
+ | `press` | key (e.g. `Enter`) | Press a keyboard key. |
91
+ | `waitFor` | selector | Wait until the element is visible (~10s timeout). |
92
+ | `assertText` | string | Pass if the text is visible anywhere on the page; else step FAIL.|
93
+ | `text` | selector | Extract `textContent` into the step result (for you to read). |
94
+ | `screenshot` | basename | Write `<out-dir>/<basename>.png` (full page). |
95
+
96
+ The whole flow runs in **one Chromium session** — login state, cookies, and
97
+ navigation persist across steps.
98
+
99
+ ## Run the flow
100
+
101
+ ```bash
102
+ node <skill-dir>/scripts/agent-browser.mjs run flow.json \
103
+ --base-url http://localhost:PORT \
104
+ --out-dir <artifact-dir>
105
+ ```
106
+
107
+ The driver prints a single JSON report:
108
+
109
+ ```json
110
+ { "ok": false,
111
+ "baseUrl": "http://localhost:3000",
112
+ "steps": [
113
+ { "index": 0, "action": "goto", "ok": true, "ms": 412 },
114
+ { "index": 6, "action": "assertText", "ok": false, "ms": 10003,
115
+ "error": "assertText not found: \"Welcome\"" },
116
+ { "index": 7, "action": "text", "ok": true, "ms": 5, "text": "Dashboard" },
117
+ { "index": 8, "action": "screenshot", "ok": true, "ms": 88,
118
+ "screenshot": "/…/after-login.png" }
119
+ ],
120
+ "consoleErrors": ["Uncaught TypeError: …"],
121
+ "screenshots": ["/…/after-login.png"] }
122
+ ```
123
+
124
+ - A step error (selector not found, assertion miss, timeout) is recorded
125
+ `ok:false` and the run **continues** — best-effort coverage. The exception
126
+ is a `goto` that fails: that is fatal and the remaining steps are skipped.
127
+ - `ok` at the top level is true only when **every** step passed. The process
128
+ exits 0 even when steps failed — judge from the JSON, not the exit code.
129
+ A non-zero exit means a fatal harness error (bad flow file, launch failure).
130
+
131
+ ## Screenshots & artifacts
132
+
133
+ Pass `--out-dir <artifact-dir>` pointing at the run's artifact directory the
134
+ **prompt provides** (commonly the run's `.lastlight/<issueKey>/` dir,
135
+ referenced as the template variable the prompt passes in). Saving there lets
136
+ the harness **harvest** the PNGs into the dashboard Artifacts view. Reference
137
+ each screenshot path in your report.
138
+
139
+ Screenshot delivery requires **server-mode build-assets**. If the PNGs can't
140
+ be persisted (no artifact dir was provided, or build-assets is repo-mode),
141
+ still report the text/DOM observations and console errors, and say the
142
+ screenshots were **not retained**.
143
+
144
+ ## Evidence rules
145
+
146
+ Mirror `verify`/`qa-test` — you are an investigator, not an advocate:
147
+
148
+ - **Never fake a pass.** Don't hardcode expected text or edit the code under
149
+ test to force a green step.
150
+ - **Report real failures.** A step that genuinely fails is a finding — record
151
+ it `FAIL` with the assertion/selector and the driver's error, and don't bury
152
+ it.
153
+ - **Treat `consoleErrors` and page errors as findings**, even if the visible
154
+ flow "worked" — surface them in the report.
155
+ - If the browser is unavailable (probe failed), fall back to text and say so —
156
+ don't pretend a UI step ran.
157
+
158
+ ## Report
159
+
160
+ Produce your report as your **final message** — the workflow posts it for you
161
+ (don't `github_add_issue_comment` yourself; that would double-post).
162
+
163
+ **Be concise, and stay in the browser.** The deliverable is short: a one-line
164
+ environment summary, the results table, the screenshots, console errors, and a
165
+ one-line coverage note. Do **not** read or analyse the repo's source code (the
166
+ text pass already did) and do **not** narrate every step or dump extracted DOM
167
+ text — surface a `text`/assertion value only when it's the actual evidence for a
168
+ row (e.g. a FAIL).
169
+
170
+ ```
171
+ ## Browser QA: <target>
172
+
173
+ **Environment:** <branch / commit, package manager, how served, Chromium version>
174
+
175
+ ### Results
176
+ | Step | Status | Evidence |
177
+ |------|--------|----------|
178
+ | <step description> | PASS / FAIL / BLOCKED | <terse evidence + inline screenshot if any> |
179
+
180
+ ### Console errors
181
+ - <each consoleErrors / pageerror entry, or "none observed">
182
+
183
+ ### Coverage
184
+ <one or two lines: what was driven, and anything not tested and why>
185
+ ```
186
+
187
+ Reference screenshots **inline** as raw Markdown images when the prompt gives
188
+ you a public artifact base URL — write `![caption](<base>/<name>.png)` directly
189
+ in the report body (or table cell), **never** wrapped in backticks or a code
190
+ fence (inside a code span it renders as literal text, not an image). Otherwise
191
+ reference them by filename. Every step in the flow must
192
+ have a row and a result. Never report a flow as "passed" with steps you didn't
193
+ actually run — list them as untested instead.
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env node
2
+ // agent-browser.mjs — bundled headless-browser driver for Tier B browser QA.
3
+ //
4
+ // CONTRACT
5
+ // --------
6
+ // Runs inside the `lastlight-sandbox-qa:latest` docker image, which bakes in
7
+ // Playwright + Chromium at PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers and
8
+ // makes the global `playwright` package resolvable via NODE_PATH. The calling
9
+ // agent has bash + file tools but NO vision: it reasons over this CLI's JSON
10
+ // stdout (extracted text, assertion results, console errors). The PNG
11
+ // screenshots are human evidence only — the agent never "sees" them.
12
+ //
13
+ // Subcommands:
14
+ // doctor
15
+ // Runtime probe. Launches headless Chromium (--no-sandbox), opens
16
+ // about:blank, closes. On success prints {"ok":true,"chromium":"<version>"}
17
+ // and exits 0; on ANY failure prints {"ok":false,"error":"..."} and exits 1.
18
+ // The skill runs this first to decide browser-vs-text.
19
+ //
20
+ // run <flow.json> [--base-url URL] [--out-dir DIR] [--record-dir DIR]
21
+ // Executes a FLOW in ONE Chromium session (state preserved across steps) and
22
+ // prints a single JSON report.
23
+ //
24
+ // With --record-dir DIR (or `"record": true` in the flow), the whole session
25
+ // is screen-recorded via Playwright's native recordVideo and saved to
26
+ // <record-dir>/session.webm (default: the out-dir). The saved path is
27
+ // reported as `video` in the JSON. Used by the `/demo` workflow, which then
28
+ // composites the raw webm into a titled mp4 with compose-demo.sh.
29
+ //
30
+ // Flow shape:
31
+ // { "baseUrl": "http://localhost:3000",
32
+ // "viewport": {"width":1280,"height":800},
33
+ // "steps": [
34
+ // {"name":"home", "goto":"/"},
35
+ // {"click":"text=Login"},
36
+ // {"fill":["#email","a@b.com"]},
37
+ // {"type":["#search","hello"]},
38
+ // {"press":"Enter"},
39
+ // {"waitFor":"#dashboard"},
40
+ // {"assertText":"Welcome"},
41
+ // {"text":"h1"},
42
+ // {"screenshot":"after-login"}
43
+ // ] }
44
+ // A step may combine an action plus a trailing `screenshot`.
45
+ //
46
+ // Per-step semantics:
47
+ // - A step error (selector not found, assertion fail, timeout) is recorded
48
+ // {ok:false} and the run CONTINUES (best-effort QA) — EXCEPT a `goto`
49
+ // that throws is FATAL: remaining steps are marked skipped.
50
+ // - assertText passes if the text is visible anywhere on the page; on miss
51
+ // the step is {ok:false} but the run continues.
52
+ // - text extracts a selector's textContent into the step result.
53
+ // - screenshot writes <out-dir>/<basename>.png (full page).
54
+ //
55
+ // Final stdout: one JSON object
56
+ // { ok, baseUrl, steps:[{index,action,ok,ms,text?,screenshot?,error?}],
57
+ // consoleErrors:[...], screenshots:[paths] }
58
+ // Exit 0 even when steps failed (the agent reads the JSON to judge). Exit
59
+ // non-zero only on a FATAL harness error (bad flow file, launch failure).
60
+ //
61
+ // Dependency-free apart from playwright.
62
+
63
+ import { readFileSync, mkdirSync } from 'node:fs';
64
+ import { resolve, join } from 'node:path';
65
+ import { createRequire } from 'node:module';
66
+
67
+ // This file is ESM (.mjs); playwright is a CJS package. createRequire gives us
68
+ // a `require` that resolves an absolute package-dir path (see loadPlaywright).
69
+ const require = createRequire(import.meta.url);
70
+
71
+ const WAIT_TIMEOUT = 10_000; // sane default for waitFor / assertText probes
72
+
73
+ function emit(obj) {
74
+ process.stdout.write(JSON.stringify(obj) + '\n');
75
+ }
76
+
77
+ async function loadPlaywright() {
78
+ // Resolve playwright via CJS `require`: it handles an absolute package-dir
79
+ // path ($LASTLIGHT_PLAYWRIGHT, baked into the QA image) by reading the
80
+ // package.json `main` — which ESM `import()` of a directory does NOT do — and
81
+ // it honours NODE_PATH for the bare-specifier fallback. playwright ships a CJS
82
+ // entry, so `require` returns { chromium, … } directly.
83
+ const candidates = [process.env.LASTLIGHT_PLAYWRIGHT, 'playwright'].filter(Boolean);
84
+ for (const spec of candidates) {
85
+ try {
86
+ return require(spec);
87
+ } catch {
88
+ // try the next candidate
89
+ }
90
+ }
91
+ emit({
92
+ ok: false,
93
+ error:
94
+ 'playwright not available — browser QA needs the lastlight-sandbox-qa image',
95
+ });
96
+ process.exit(1);
97
+ }
98
+
99
+ async function doctor() {
100
+ const { chromium } = await loadPlaywright();
101
+ let browser;
102
+ try {
103
+ browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
104
+ const page = await browser.newPage();
105
+ await page.goto('about:blank');
106
+ const version = browser.version();
107
+ await page.close();
108
+ emit({ ok: true, chromium: version });
109
+ process.exit(0);
110
+ } catch (err) {
111
+ emit({ ok: false, error: String(err && err.message ? err.message : err) });
112
+ process.exit(1);
113
+ } finally {
114
+ if (browser) await browser.close().catch(() => {});
115
+ }
116
+ }
117
+
118
+ // Resolve a goto target against the base URL. Absolute URLs pass through.
119
+ function resolveUrl(target, baseUrl) {
120
+ if (/^https?:\/\//i.test(target)) return target;
121
+ if (!baseUrl) return target;
122
+ return baseUrl.replace(/\/+$/, '') + '/' + String(target).replace(/^\/+/, '');
123
+ }
124
+
125
+ // Returns the action name for a step (for reporting) — first recognized key.
126
+ function actionOf(step) {
127
+ for (const k of [
128
+ 'goto',
129
+ 'click',
130
+ 'fill',
131
+ 'type',
132
+ 'press',
133
+ 'waitFor',
134
+ 'assertText',
135
+ 'text',
136
+ ]) {
137
+ if (k in step) return k;
138
+ }
139
+ if ('screenshot' in step) return 'screenshot';
140
+ return 'noop';
141
+ }
142
+
143
+ async function execStep(page, step, baseUrl, outDir, screenshots) {
144
+ const result = {};
145
+ let fatal = false;
146
+
147
+ if ('goto' in step) {
148
+ // Navigation failure is FATAL.
149
+ try {
150
+ await page.goto(resolveUrl(step.goto, baseUrl), {
151
+ waitUntil: 'load',
152
+ timeout: WAIT_TIMEOUT * 3,
153
+ });
154
+ } catch (err) {
155
+ throw Object.assign(new Error(`goto failed: ${err.message}`), {
156
+ fatal: true,
157
+ });
158
+ }
159
+ } else if ('click' in step) {
160
+ await page.click(step.click, { timeout: WAIT_TIMEOUT });
161
+ } else if ('fill' in step) {
162
+ const [sel, val] = step.fill;
163
+ await page.fill(sel, val, { timeout: WAIT_TIMEOUT });
164
+ } else if ('type' in step) {
165
+ const [sel, val] = step.type;
166
+ await page.locator(sel).first().pressSequentially(val, {
167
+ timeout: WAIT_TIMEOUT,
168
+ });
169
+ } else if ('press' in step) {
170
+ await page.keyboard.press(step.press);
171
+ } else if ('waitFor' in step) {
172
+ await page.locator(step.waitFor).first().waitFor({
173
+ state: 'visible',
174
+ timeout: WAIT_TIMEOUT,
175
+ });
176
+ } else if ('assertText' in step) {
177
+ // Pass if visible anywhere on the page; miss => step fails, run continues.
178
+ try {
179
+ await page
180
+ .getByText(step.assertText, { exact: false })
181
+ .first()
182
+ .waitFor({ state: 'visible', timeout: WAIT_TIMEOUT });
183
+ } catch {
184
+ throw new Error(`assertText not found: ${JSON.stringify(step.assertText)}`);
185
+ }
186
+ } else if ('text' in step) {
187
+ const txt = await page
188
+ .locator(step.text)
189
+ .first()
190
+ .textContent({ timeout: WAIT_TIMEOUT });
191
+ result.text = txt == null ? '' : txt.trim();
192
+ }
193
+
194
+ // A trailing screenshot can ride along with any action (or stand alone).
195
+ if ('screenshot' in step) {
196
+ const base = String(step.screenshot).replace(/[^a-zA-Z0-9._-]/g, '_');
197
+ const path = join(outDir, `${base}.png`);
198
+ await page.screenshot({ path, fullPage: true });
199
+ result.screenshot = path;
200
+ screenshots.push(path);
201
+ }
202
+
203
+ return { result, fatal };
204
+ }
205
+
206
+ async function run(flowPath, baseUrlArg, outDirArg, recordDirArg) {
207
+ let flow;
208
+ try {
209
+ flow = JSON.parse(readFileSync(resolve(flowPath), 'utf8'));
210
+ } catch (err) {
211
+ emit({ ok: false, error: `cannot read flow file: ${err.message}` });
212
+ process.exit(1);
213
+ }
214
+ if (!flow || !Array.isArray(flow.steps)) {
215
+ emit({ ok: false, error: 'flow file must have a "steps" array' });
216
+ process.exit(1);
217
+ }
218
+
219
+ const baseUrl = baseUrlArg || flow.baseUrl || '';
220
+ const outDir = resolve(outDirArg || process.cwd());
221
+ // Record the session when --record-dir is passed OR the flow opts in with
222
+ // `"record": true`. The .webm lands in the record dir (default: out-dir).
223
+ const doRecord = !!recordDirArg || flow.record === true;
224
+ const videoDir = resolve(recordDirArg || outDir);
225
+ try {
226
+ mkdirSync(outDir, { recursive: true });
227
+ } catch {
228
+ /* best effort */
229
+ }
230
+ if (doRecord) {
231
+ try {
232
+ mkdirSync(videoDir, { recursive: true });
233
+ } catch {
234
+ /* best effort */
235
+ }
236
+ }
237
+
238
+ const viewport = flow.viewport || { width: 1280, height: 800 };
239
+ const { chromium } = await loadPlaywright();
240
+ let browser;
241
+ const steps = [];
242
+ const screenshots = [];
243
+ const consoleErrors = [];
244
+ let videoPath = null;
245
+
246
+ try {
247
+ browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
248
+ } catch (err) {
249
+ emit({
250
+ ok: false,
251
+ error: `browser launch failed: ${err && err.message ? err.message : err}`,
252
+ });
253
+ process.exit(1);
254
+ }
255
+
256
+ try {
257
+ const context = await browser.newContext({
258
+ viewport,
259
+ // Playwright records video per-context; the size matches the viewport so
260
+ // the clip isn't letterboxed. The .webm is flushed on context.close().
261
+ ...(doRecord ? { recordVideo: { dir: videoDir, size: viewport } } : {}),
262
+ });
263
+ const page = await context.newPage();
264
+
265
+ page.on('console', (msg) => {
266
+ if (msg.type() === 'error') consoleErrors.push(msg.text());
267
+ });
268
+ page.on('pageerror', (err) => {
269
+ consoleErrors.push(String(err && err.message ? err.message : err));
270
+ });
271
+
272
+ let fatalHit = false;
273
+ for (let i = 0; i < flow.steps.length; i++) {
274
+ const step = flow.steps[i];
275
+ const action = actionOf(step);
276
+
277
+ if (fatalHit) {
278
+ steps.push({ index: i, action, ok: false, ms: 0, error: 'skipped (prior fatal step)' });
279
+ continue;
280
+ }
281
+
282
+ const started = Date.now();
283
+ try {
284
+ const { result } = await execStep(page, step, baseUrl, outDir, screenshots);
285
+ steps.push({ index: i, action, ok: true, ms: Date.now() - started, ...result });
286
+ } catch (err) {
287
+ const entry = {
288
+ index: i,
289
+ action,
290
+ ok: false,
291
+ ms: Date.now() - started,
292
+ error: String(err && err.message ? err.message : err),
293
+ };
294
+ steps.push(entry);
295
+ if (err && err.fatal) fatalHit = true; // navigation failure: skip the rest
296
+ }
297
+ }
298
+
299
+ // Finalize a recording (if any) BEFORE emitting: saving the video requires
300
+ // the context to close, which flushes the .webm to disk. A save failure is a
301
+ // reported finding, not a fatal run error.
302
+ if (doRecord) {
303
+ const video = page.video();
304
+ await context.close();
305
+ if (video) {
306
+ const target = join(videoDir, 'session.webm');
307
+ try {
308
+ await video.saveAs(target);
309
+ videoPath = target;
310
+ await video.delete().catch(() => {});
311
+ } catch (err) {
312
+ consoleErrors.push(
313
+ `video save failed: ${err && err.message ? err.message : err}`,
314
+ );
315
+ }
316
+ }
317
+ }
318
+
319
+ emit({
320
+ ok: steps.every((s) => s.ok),
321
+ baseUrl,
322
+ steps,
323
+ consoleErrors,
324
+ screenshots,
325
+ ...(videoPath ? { video: videoPath } : {}),
326
+ });
327
+ process.exit(0);
328
+ } catch (err) {
329
+ // Unexpected harness error mid-run — still emit a JSON object.
330
+ emit({
331
+ ok: false,
332
+ error: String(err && err.message ? err.message : err),
333
+ baseUrl,
334
+ steps,
335
+ consoleErrors,
336
+ screenshots,
337
+ ...(videoPath ? { video: videoPath } : {}),
338
+ });
339
+ process.exit(1);
340
+ } finally {
341
+ if (browser) await browser.close().catch(() => {});
342
+ }
343
+ }
344
+
345
+ async function main() {
346
+ const [cmd, ...rest] = process.argv.slice(2);
347
+
348
+ if (cmd === 'doctor') {
349
+ await doctor();
350
+ return;
351
+ }
352
+
353
+ if (cmd === 'run') {
354
+ const positional = [];
355
+ let baseUrl;
356
+ let outDir;
357
+ let recordDir;
358
+ for (let i = 0; i < rest.length; i++) {
359
+ const a = rest[i];
360
+ if (a === '--base-url') baseUrl = rest[++i];
361
+ else if (a === '--out-dir') outDir = rest[++i];
362
+ else if (a === '--record-dir') recordDir = rest[++i];
363
+ else positional.push(a);
364
+ }
365
+ if (!positional[0]) {
366
+ emit({ ok: false, error: 'usage: agent-browser.mjs run <flow.json> [--base-url URL] [--out-dir DIR] [--record-dir DIR]' });
367
+ process.exit(1);
368
+ }
369
+ await run(positional[0], baseUrl, outDir, recordDir);
370
+ return;
371
+ }
372
+
373
+ emit({ ok: false, error: 'usage: agent-browser.mjs <doctor|run> ...' });
374
+ process.exit(1);
375
+ }
376
+
377
+ main().catch((err) => {
378
+ emit({ ok: false, error: String(err && err.message ? err.message : err) });
379
+ process.exit(1);
380
+ });