lastlight 0.16.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 (385) 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 -142
  103. package/dist/connectors/slack/connector.js +0 -494
  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 -19
  109. package/dist/connectors/slack/mrkdwn.js +0 -240
  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 -940
  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/blocks.d.ts +0 -23
  193. package/dist/notify/blocks.js +0 -118
  194. package/dist/notify/blocks.js.map +0 -1
  195. package/dist/notify/index.d.ts +0 -17
  196. package/dist/notify/index.js +0 -7
  197. package/dist/notify/index.js.map +0 -1
  198. package/dist/notify/model.d.ts +0 -57
  199. package/dist/notify/model.js +0 -89
  200. package/dist/notify/model.js.map +0 -1
  201. package/dist/notify/notifier.d.ts +0 -18
  202. package/dist/notify/notifier.js +0 -100
  203. package/dist/notify/notifier.js.map +0 -1
  204. package/dist/notify/render.d.ts +0 -21
  205. package/dist/notify/render.js +0 -59
  206. package/dist/notify/render.js.map +0 -1
  207. package/dist/notify/transports/github.d.ts +0 -26
  208. package/dist/notify/transports/github.js +0 -27
  209. package/dist/notify/transports/github.js.map +0 -1
  210. package/dist/notify/transports/slack.d.ts +0 -27
  211. package/dist/notify/transports/slack.js +0 -37
  212. package/dist/notify/transports/slack.js.map +0 -1
  213. package/dist/notify/types.d.ts +0 -119
  214. package/dist/notify/types.js +0 -16
  215. package/dist/notify/types.js.map +0 -1
  216. package/dist/providers.d.ts +0 -125
  217. package/dist/providers.js +0 -279
  218. package/dist/providers.js.map +0 -1
  219. package/dist/sandbox/docker.d.ts +0 -180
  220. package/dist/sandbox/docker.js +0 -517
  221. package/dist/sandbox/docker.js.map +0 -1
  222. package/dist/sandbox/egress-allowlist.d.ts +0 -74
  223. package/dist/sandbox/egress-allowlist.js +0 -164
  224. package/dist/sandbox/egress-allowlist.js.map +0 -1
  225. package/dist/sandbox/egress-firewall-config.d.ts +0 -127
  226. package/dist/sandbox/egress-firewall-config.js +0 -434
  227. package/dist/sandbox/egress-firewall-config.js.map +0 -1
  228. package/dist/sandbox/images.d.ts +0 -23
  229. package/dist/sandbox/images.js +0 -55
  230. package/dist/sandbox/images.js.map +0 -1
  231. package/dist/sandbox/index.d.ts +0 -112
  232. package/dist/sandbox/index.js +0 -332
  233. package/dist/sandbox/index.js.map +0 -1
  234. package/dist/sandbox/sandbox.d.ts +0 -214
  235. package/dist/sandbox/sandbox.js +0 -453
  236. package/dist/sandbox/sandbox.js.map +0 -1
  237. package/dist/sandbox/smol.d.ts +0 -162
  238. package/dist/sandbox/smol.js +0 -485
  239. package/dist/sandbox/smol.js.map +0 -1
  240. package/dist/session-log.d.ts +0 -63
  241. package/dist/session-log.js +0 -290
  242. package/dist/session-log.js.map +0 -1
  243. package/dist/state/approval-store.d.ts +0 -88
  244. package/dist/state/approval-store.js +0 -149
  245. package/dist/state/approval-store.js.map +0 -1
  246. package/dist/state/build-assets.d.ts +0 -50
  247. package/dist/state/build-assets.js +0 -154
  248. package/dist/state/build-assets.js.map +0 -1
  249. package/dist/state/db.d.ts +0 -81
  250. package/dist/state/db.js +0 -155
  251. package/dist/state/db.js.map +0 -1
  252. package/dist/state/execution-store.d.ts +0 -257
  253. package/dist/state/execution-store.js +0 -527
  254. package/dist/state/execution-store.js.map +0 -1
  255. package/dist/state/migrate.d.ts +0 -15
  256. package/dist/state/migrate.js +0 -175
  257. package/dist/state/migrate.js.map +0 -1
  258. package/dist/state/workflow-run-store.d.ts +0 -216
  259. package/dist/state/workflow-run-store.js +0 -403
  260. package/dist/state/workflow-run-store.js.map +0 -1
  261. package/dist/telemetry/index.d.ts +0 -38
  262. package/dist/telemetry/index.js +0 -253
  263. package/dist/telemetry/index.js.map +0 -1
  264. package/dist/telemetry/pi-events.d.ts +0 -12
  265. package/dist/telemetry/pi-events.js +0 -134
  266. package/dist/telemetry/pi-events.js.map +0 -1
  267. package/dist/workflows/dag.d.ts +0 -37
  268. package/dist/workflows/dag.js +0 -118
  269. package/dist/workflows/dag.js.map +0 -1
  270. package/dist/workflows/loader.d.ts +0 -53
  271. package/dist/workflows/loader.js +0 -421
  272. package/dist/workflows/loader.js.map +0 -1
  273. package/dist/workflows/loop-eval.d.ts +0 -28
  274. package/dist/workflows/loop-eval.js +0 -74
  275. package/dist/workflows/loop-eval.js.map +0 -1
  276. package/dist/workflows/phase-executor.d.ts +0 -222
  277. package/dist/workflows/phase-executor.js +0 -1153
  278. package/dist/workflows/phase-executor.js.map +0 -1
  279. package/dist/workflows/phase-ref.d.ts +0 -50
  280. package/dist/workflows/phase-ref.js +0 -91
  281. package/dist/workflows/phase-ref.js.map +0 -1
  282. package/dist/workflows/resume.d.ts +0 -58
  283. package/dist/workflows/resume.js +0 -313
  284. package/dist/workflows/resume.js.map +0 -1
  285. package/dist/workflows/runner.d.ts +0 -63
  286. package/dist/workflows/runner.js +0 -381
  287. package/dist/workflows/runner.js.map +0 -1
  288. package/dist/workflows/schema.d.ts +0 -344
  289. package/dist/workflows/schema.js +0 -460
  290. package/dist/workflows/schema.js.map +0 -1
  291. package/dist/workflows/simple.d.ts +0 -90
  292. package/dist/workflows/simple.js +0 -393
  293. package/dist/workflows/simple.js.map +0 -1
  294. package/dist/workflows/target-policy.d.ts +0 -74
  295. package/dist/workflows/target-policy.js +0 -80
  296. package/dist/workflows/target-policy.js.map +0 -1
  297. package/dist/workflows/templates.d.ts +0 -88
  298. package/dist/workflows/templates.js +0 -127
  299. package/dist/workflows/templates.js.map +0 -1
  300. package/dist/workflows/triggers.d.ts +0 -21
  301. package/dist/workflows/triggers.js +0 -45
  302. package/dist/workflows/triggers.js.map +0 -1
  303. package/dist/workflows/verdict.d.ts +0 -19
  304. package/dist/workflows/verdict.js +0 -30
  305. package/dist/workflows/verdict.js.map +0 -1
  306. package/dist/worktree/manager.d.ts +0 -43
  307. package/dist/worktree/manager.js +0 -127
  308. package/dist/worktree/manager.js.map +0 -1
  309. package/docker-compose.yml +0 -274
  310. package/sandbox.Dockerfile +0 -68
  311. package/skills/README.md +0 -42
  312. package/skills/browser-qa/SKILL.md +0 -208
  313. package/skills/browser-qa/scripts/agent-browser.mjs +0 -531
  314. package/skills/building/SKILL.md +0 -95
  315. package/skills/chat/SKILL.md +0 -36
  316. package/skills/code-review/SKILL.md +0 -82
  317. package/skills/debug-production/SKILL.md +0 -108
  318. package/skills/demo/SKILL.md +0 -228
  319. package/skills/demo/scripts/compose-demo.sh +0 -243
  320. package/skills/issue-answer/SKILL.md +0 -91
  321. package/skills/issue-comment/SKILL.md +0 -49
  322. package/skills/issue-triage/SKILL.md +0 -149
  323. package/skills/issue-triage/references/AGENT-BRIEF.md +0 -65
  324. package/skills/pr-comment/SKILL.md +0 -64
  325. package/skills/pr-review/SKILL.md +0 -148
  326. package/skills/pr-review/references/findings-schema.md +0 -89
  327. package/skills/qa-test/SKILL.md +0 -97
  328. package/skills/repo-health/SKILL.md +0 -67
  329. package/skills/security-feedback/SKILL.md +0 -173
  330. package/skills/security-feedback/references/templates.md +0 -77
  331. package/skills/security-review/SKILL.md +0 -215
  332. package/skills/security-review/references/issue-format.md +0 -302
  333. package/skills/verify/SKILL.md +0 -118
  334. package/workflows/answer.yaml +0 -44
  335. package/workflows/build.yaml +0 -129
  336. package/workflows/cron-health.yaml +0 -6
  337. package/workflows/cron-review.yaml +0 -8
  338. package/workflows/cron-security.yaml +0 -6
  339. package/workflows/cron-triage.yaml +0 -8
  340. package/workflows/demo.yaml +0 -62
  341. package/workflows/examples/bash-smoke.yaml +0 -41
  342. package/workflows/examples/parallel-review.yaml +0 -36
  343. package/workflows/examples/tdd-loop.yaml +0 -17
  344. package/workflows/explore.yaml +0 -80
  345. package/workflows/issue-comment.yaml +0 -15
  346. package/workflows/issue-triage.yaml +0 -22
  347. package/workflows/pr-comment.yaml +0 -16
  348. package/workflows/pr-fix.yaml +0 -17
  349. package/workflows/pr-review.yaml +0 -50
  350. package/workflows/prompts/answer.md +0 -68
  351. package/workflows/prompts/architect.md +0 -44
  352. package/workflows/prompts/classifier.md +0 -60
  353. package/workflows/prompts/classify-adds-info.md +0 -17
  354. package/workflows/prompts/demo.md +0 -125
  355. package/workflows/prompts/executor.md +0 -42
  356. package/workflows/prompts/explore-ask.md +0 -99
  357. package/workflows/prompts/explore-publish.md +0 -31
  358. package/workflows/prompts/explore-read.md +0 -120
  359. package/workflows/prompts/explore-synthesize.md +0 -88
  360. package/workflows/prompts/fix.md +0 -18
  361. package/workflows/prompts/guardrails.md +0 -96
  362. package/workflows/prompts/pr-fix.md +0 -26
  363. package/workflows/prompts/pr.md +0 -39
  364. package/workflows/prompts/qa-browser.md +0 -95
  365. package/workflows/prompts/qa-synth.md +0 -44
  366. package/workflows/prompts/qa-test.md +0 -60
  367. package/workflows/prompts/re-reviewer.md +0 -33
  368. package/workflows/prompts/reviewer.md +0 -60
  369. package/workflows/prompts/verify-browser.md +0 -93
  370. package/workflows/prompts/verify-synth.md +0 -44
  371. package/workflows/prompts/verify.md +0 -63
  372. package/workflows/qa-test.yaml +0 -94
  373. package/workflows/repo-health.yaml +0 -15
  374. package/workflows/security-feedback.yaml +0 -25
  375. package/workflows/security-review.yaml +0 -37
  376. package/workflows/verify.yaml +0 -93
  377. /package/dist/{cli/cli-config.d.ts → cli-config.d.ts} +0 -0
  378. /package/dist/{cli/cli-config.js → cli-config.js} +0 -0
  379. /package/dist/{cli/cli-format.d.ts → cli-format.d.ts} +0 -0
  380. /package/dist/{cli/cli-format.js → cli-format.js} +0 -0
  381. /package/dist/{cli/cli-timeline.d.ts → cli-timeline.d.ts} +0 -0
  382. /package/dist/{cli/cli-timeline.js → cli-timeline.js} +0 -0
  383. /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
  384. /package/dist/{cli/oauth-cli.d.ts → oauth-cli.d.ts} +0 -0
  385. /package/dist/{cli/setup.d.ts → setup.d.ts} +0 -0
@@ -1,663 +0,0 @@
1
- import { randomUUID } from "crypto";
2
- import { routeEvent } from "./router.js";
3
- import { runDashboardUrl } from "../notify/model.js";
4
- import { getRuntimeConfig } from "../config/config.js";
5
- /**
6
- * Turn an EventEnvelope into a workflow dispatch (or an in-process handler
7
- * run). Classifies via `route`, then acts on the decision. Extracted from the
8
- * former `registry.onEvent` closure so every event branch is testable.
9
- */
10
- export async function dispatch(envelope, deps) {
11
- const route = await (deps.route ?? routeEvent)(envelope, { db: deps.db });
12
- if (route.action === "ignore") {
13
- return { kind: "ignored", reason: route.reason };
14
- }
15
- if (route.action === "reply") {
16
- await envelope.reply(route.message);
17
- return { kind: "replied", message: route.message };
18
- }
19
- // route.action === "handler"
20
- const { handler, context } = route;
21
- const routeKey = typeof context._routeKey === "string" ? context._routeKey : undefined;
22
- // Instant ack: the moment we've classified a GitHub event as something we'll
23
- // act on, react 👀 on the triggering comment/issue so the user sees feedback
24
- // before any (slower) workflow output lands. Covers every github-based path —
25
- // issue/PR comments, review comments, opened issues/PRs — and every explore
26
- // socratic reply (each arrives here as its own comment.created → explore-reply).
27
- // Fire-and-forget; the build path additionally reacts 🚀 when it starts.
28
- ackGithubEvent(envelope, deps.github);
29
- // Chat messages: handle in-process (no sandbox, low latency).
30
- if (handler === "chat") {
31
- return handleChat(envelope, context, deps);
32
- }
33
- // Chat reset: deactivate the session and confirm.
34
- if (handler === "chat-reset") {
35
- const sessionId = context.sessionId;
36
- if (sessionId) {
37
- deps.sessionManager.deactivateSession(sessionId);
38
- }
39
- await envelope.reply("Session reset. Starting fresh.");
40
- return { kind: "handled", handler };
41
- }
42
- // Status report: list running executions.
43
- if (handler === "status-report") {
44
- const running = deps.db.executions.runningExecutions();
45
- if (running.length === 0) {
46
- await envelope.reply("No tasks currently running.");
47
- }
48
- else {
49
- const lines = running.map((r) => `• *${r.skill}*${r.repo ? ` on ${r.repo}` : ""}${r.issueNumber ? ` #${r.issueNumber}` : ""} (started ${r.startedAt})`);
50
- await envelope.reply(`Running tasks:\n${lines.join("\n")}`);
51
- }
52
- return { kind: "handled", handler };
53
- }
54
- // Guard against double-dispatching the same work. Everything past this
55
- // point is a workflow dispatch (or a resume of one), so a run already in
56
- // flight for this trigger is a no-op.
57
- const triggerId = String(envelope.issueNumber || envelope.id);
58
- if (deps.db.executions.isRunning(handler, triggerId)) {
59
- console.log(`[event] Skipping: ${handler} already running for ${triggerId}`);
60
- if (envelope.type === "message") {
61
- await envelope.reply(`That task is already running. Use /status to check progress.`);
62
- }
63
- return { kind: "skipped", reason: `${handler} already running for ${triggerId}` };
64
- }
65
- // PR fix: lightweight fix-and-push driven by CI failures / a comment.
66
- if ((routeKey === "github.pr_fix" || handler === "pr-fix") && context.prNumber && context.repo) {
67
- return handlePrFix(context, handler, deps);
68
- }
69
- if (handler === "explore-reply") {
70
- return handleExploreReply(envelope, context, deps);
71
- }
72
- if (handler === "approval-response") {
73
- return handleApprovalResponse(envelope, context, deps);
74
- }
75
- // Build requests → the programmatic orchestrator (the `build` workflow).
76
- if ((routeKey === "github.issue_build" || routeKey === "slack.build" || handler === "github-orchestrator") &&
77
- context.issueNumber &&
78
- context.repo) {
79
- return handleBuild(envelope, context, handler, deps);
80
- }
81
- // The workflow context is the route context minus the internal _routeKey.
82
- const { _routeKey: _ignored, ...workflowContext } = context;
83
- // Messaging-triggered workflows: ack on start, report on completion.
84
- if (envelope.type === "message") {
85
- return handleMessageDispatch(envelope, handler, workflowContext, deps);
86
- }
87
- // Webhook-triggered workflows (the remaining default path).
88
- return handleWebhookDispatch(envelope, handler, routeKey, workflowContext, deps);
89
- }
90
- /**
91
- * React 👀 on the GitHub subject that triggered this event, as an immediate
92
- * "I've seen it and I'm acting on it" ack. Picks the right reactions endpoint
93
- * by event type: issue/PR comments and explore replies → the comment; PR review
94
- * comments → the review comment; freshly opened/reopened issues & PRs → the
95
- * issue/PR itself. Non-GitHub events (Slack) and events with no clear subject
96
- * (e.g. pr.synchronize) are skipped. Fire-and-forget — a failed reaction never
97
- * blocks dispatch.
98
- */
99
- function ackGithubEvent(envelope, github) {
100
- if (!github || envelope.source !== "github" || !envelope.repo)
101
- return;
102
- const [owner, repo] = envelope.repo.split("/");
103
- if (!owner || !repo)
104
- return;
105
- const raw = envelope.raw;
106
- const commentId = raw?.comment?.id;
107
- const fail = (err) => {
108
- const msg = err instanceof Error ? err.message : String(err);
109
- console.warn(`[event] Could not react 👀 to ${envelope.type}: ${msg}`);
110
- };
111
- switch (envelope.type) {
112
- case "comment.created":
113
- if (commentId)
114
- github.reactToComment(owner, repo, commentId, "eyes").catch(fail);
115
- break;
116
- case "pr_review_comment.created":
117
- if (commentId)
118
- github.reactToReviewComment(owner, repo, commentId, "eyes").catch(fail);
119
- break;
120
- case "issue.opened":
121
- case "issue.reopened":
122
- case "pr.opened":
123
- case "pr.reopened":
124
- if (envelope.issueNumber)
125
- github.reactToIssue(owner, repo, envelope.issueNumber, "eyes").catch(fail);
126
- break;
127
- default:
128
- // pr.synchronize, pr.merged, pr_review.submitted, etc. — no single
129
- // user-authored subject to ack, so stay quiet.
130
- break;
131
- }
132
- }
133
- /**
134
- * Messaging-triggered workflow dispatch. Posts a "Starting *<handler>*" ack
135
- * once the run row exists (with a dashboard deep link when `publicUrl` is set),
136
- * then reports completion / failure back to the thread. Paused runs stay quiet
137
- * — the workflow already posted gate instructions.
138
- */
139
- async function handleMessageDispatch(envelope, handler, workflowContext, deps) {
140
- const onRunStart = async (runId) => {
141
- // The run detail lives on the `runs` tab (WorkflowList reads ?run=…&phase=…);
142
- // `workflows` is the definition browser and ignores ?run=, so the deep link
143
- // must target `runs`. Shared by every messaging-dispatched workflow
144
- // (build / explore / triage / answer / …).
145
- const link = runDashboardUrl(deps.publicUrl, runId, handler);
146
- const body = link
147
- ? `Starting *${handler}*... I'll report back when it's done.\n<${link}|Live progress>`
148
- : `Starting *${handler}*... I'll report back when it's done.`;
149
- try {
150
- await envelope.reply(body);
151
- }
152
- catch (err) {
153
- const m = err instanceof Error ? err.message : String(err);
154
- console.warn(`[event] failed to post run-start ack: ${m}`);
155
- }
156
- };
157
- deps.dispatchWorkflow(handler, { ...workflowContext, _triggerType: "chat" }, onRunStart)
158
- .then(async (result) => {
159
- if (result.paused) {
160
- // Paused at a gate — the workflow already posted instructions.
161
- }
162
- else if (result.success) {
163
- await envelope.reply(`*${handler}* completed.`);
164
- }
165
- else {
166
- await envelope.reply(`*${handler}* failed${result.error ? `: ${result.error}` : ""}.`);
167
- }
168
- })
169
- .catch(async (err) => {
170
- const msg = err instanceof Error ? err.message : String(err);
171
- console.error(`[event] workflow ${handler} threw: ${msg}`);
172
- await envelope.reply(`*${handler}* failed: ${msg}`);
173
- });
174
- return { kind: "dispatched", workflow: handler };
175
- }
176
- /**
177
- * Webhook-triggered workflow dispatch. When `reviewPostsCheck` is on and this
178
- * is a PR-attention event routed to pr-review, posts an in-progress
179
- * `last-light/review` Check Run on the PR head SHA so branch protection can
180
- * gate the merge, then completes it from the workflow's terminal result.
181
- */
182
- async function handleWebhookDispatch(envelope, handler, routeKey, workflowContext, deps) {
183
- const { github } = deps;
184
- const isPrReviewEvent = envelope.type === "pr.opened" ||
185
- envelope.type === "pr.synchronize" ||
186
- envelope.type === "pr.reopened";
187
- const wantReviewCheck = deps.reviewPostsCheck &&
188
- isPrReviewEvent &&
189
- (routeKey === "github.pr_opened" ||
190
- routeKey === "github.pr_synchronize" ||
191
- routeKey === "github.pr_reopened" ||
192
- handler === "pr-review") &&
193
- !!github &&
194
- !!envelope.repo &&
195
- typeof envelope.prNumber === "number";
196
- let prCheckRunId;
197
- let prOwner = "";
198
- let prRepoName = "";
199
- let prNumberForCheck = 0;
200
- if (wantReviewCheck && github) {
201
- [prOwner, prRepoName] = envelope.repo.split("/");
202
- prNumberForCheck = envelope.prNumber;
203
- try {
204
- const prHeadSha = await github.getPullRequestHeadSha(prOwner, prRepoName, prNumberForCheck);
205
- prCheckRunId = await github.createCheckRun(prOwner, prRepoName, prHeadSha, "last-light/review", {
206
- output: {
207
- title: "Review in progress",
208
- summary: "Last Light is reviewing this PR. The conclusion will land here when the review completes.",
209
- },
210
- });
211
- console.log(`[check] Posted in-progress check ${prCheckRunId} for ${prOwner}/${prRepoName}#${prNumberForCheck} on ${prHeadSha.slice(0, 7)}`);
212
- }
213
- catch (err) {
214
- const msg = err instanceof Error ? err.message : String(err);
215
- console.warn(`[check] failed to create in-progress check: ${msg}`);
216
- }
217
- }
218
- // As soon as the run row exists, point the in-progress check at its dashboard
219
- // deep link so a reviewer can click the check's "Details" straight through to
220
- // the live run. Best-effort + only when the check and a public URL both exist.
221
- const onRunStart = async (runId) => {
222
- if (prCheckRunId === undefined || !github)
223
- return;
224
- const detailsUrl = runDashboardUrl(deps.publicUrl, runId, handler);
225
- if (!detailsUrl)
226
- return;
227
- try {
228
- await github.updateCheckRun(prOwner, prRepoName, prCheckRunId, { detailsUrl });
229
- console.log(`[check] linked check ${prCheckRunId} → ${detailsUrl}`);
230
- }
231
- catch (err) {
232
- const msg = err instanceof Error ? err.message : String(err);
233
- console.warn(`[check] failed to set details_url on check ${prCheckRunId}: ${msg}`);
234
- }
235
- };
236
- const workflowPromise = deps.dispatchWorkflow(handler, { ...workflowContext, _triggerType: "webhook" }, onRunStart);
237
- if (prCheckRunId !== undefined && github) {
238
- const checkId = prCheckRunId;
239
- const owner = prOwner;
240
- const repo = prRepoName;
241
- const prNumber = prNumberForCheck;
242
- workflowPromise
243
- .then(async (result) => {
244
- try {
245
- // Re-fetch head SHA in case the PR was rebased mid-review.
246
- const headSha = await github.getPullRequestHeadSha(owner, repo, prNumber);
247
- const review = await github.getLatestBotReview(owner, repo, prNumber, headSha, getRuntimeConfig()?.botLogin);
248
- const conclusion = !result.success
249
- ? "neutral"
250
- : review?.state === "APPROVED"
251
- ? "success"
252
- : review?.state === "CHANGES_REQUESTED"
253
- ? "failure"
254
- : "neutral";
255
- await github.updateCheckRun(owner, repo, checkId, {
256
- status: "completed",
257
- conclusion,
258
- output: {
259
- title: `Review ${conclusion === "success" ? "approved" : conclusion === "failure" ? "requested changes" : "completed"}`,
260
- summary: review?.body?.slice(0, 65000) || "Review complete.",
261
- },
262
- });
263
- console.log(`[check] Completed check ${checkId} → ${conclusion}`);
264
- }
265
- catch (err) {
266
- const msg = err instanceof Error ? err.message : String(err);
267
- console.warn(`[check] failed to complete check ${checkId}: ${msg}`);
268
- }
269
- })
270
- .catch(async (err) => {
271
- const msg = err instanceof Error ? err.message : String(err);
272
- try {
273
- await github.updateCheckRun(owner, repo, checkId, {
274
- status: "completed",
275
- conclusion: "neutral",
276
- output: { title: "Review errored", summary: `Workflow threw: ${msg.slice(0, 1000)}` },
277
- });
278
- }
279
- catch {
280
- /* ignore — best effort */
281
- }
282
- });
283
- }
284
- workflowPromise.catch((err) => {
285
- const msg = err instanceof Error ? err.message : String(err);
286
- console.error(`[event] Unhandled error in workflow ${handler}: ${msg}`);
287
- });
288
- return { kind: "dispatched", workflow: handler };
289
- }
290
- /**
291
- * Kick off the full build cycle for an issue. Fills in missing issue details
292
- * from the API, records a `build-cycle` execution row, acks the requester
293
- * (Slack reply or a 🚀 reaction on the trigger comment), then dispatches the
294
- * `build` workflow asynchronously.
295
- */
296
- async function handleBuild(envelope, context, handler, deps) {
297
- const repoStr = context.repo;
298
- const [owner, repo] = repoStr.includes("/") ? repoStr.split("/") : ["", repoStr];
299
- const issueNumber = context.issueNumber;
300
- if (!owner || !repo) {
301
- console.error(`[event] Invalid repo format: ${repoStr}`);
302
- return { kind: "ignored", reason: `invalid repo format: ${repoStr}` };
303
- }
304
- // Fetch full issue details if we don't have them.
305
- let issueTitle = context.title || "";
306
- let issueBody = context.body || "";
307
- let issueLabels = context.labels || [];
308
- if (deps.github && (!issueTitle || !issueBody || issueLabels.length === 0)) {
309
- try {
310
- const issue = await deps.github.getIssue(owner, repo, issueNumber);
311
- issueTitle = issueTitle || issue.title;
312
- issueBody = issueBody || issue.body || "";
313
- if (issueLabels.length === 0) {
314
- issueLabels = (issue.labels || [])
315
- .map((l) => (typeof l === "string" ? l : l.name))
316
- .filter(Boolean);
317
- }
318
- }
319
- catch (err) {
320
- console.warn(`[event] Could not fetch issue: ${err.message}`);
321
- }
322
- }
323
- const executionId = randomUUID();
324
- deps.db.executions.recordStart({
325
- id: executionId,
326
- triggerType: envelope.type === "message" ? "chat" : "webhook",
327
- triggerId: String(issueNumber),
328
- skill: "build-cycle",
329
- repo: repoStr,
330
- issueNumber,
331
- startedAt: new Date().toISOString(),
332
- });
333
- if (envelope.type === "message") {
334
- await envelope.reply(`Starting build cycle for ${repoStr}#${issueNumber}...`);
335
- }
336
- else if (deps.github) {
337
- // GitHub-triggered builds: react with 🚀 on the triggering comment so the
338
- // user sees an instant ack. Non-fatal if it fails.
339
- const commentId = envelope.raw?.comment?.id;
340
- if (commentId) {
341
- deps.github.reactToComment(owner, repo, commentId, "rocket").catch((err) => {
342
- const msg = err instanceof Error ? err.message : String(err);
343
- console.warn(`[event] Could not react to trigger comment: ${msg}`);
344
- });
345
- }
346
- }
347
- const buildWorkflow = handler === "github-orchestrator" ? "build" : handler;
348
- deps.dispatchWorkflow(buildWorkflow, {
349
- repo: repoStr,
350
- issueNumber,
351
- title: issueTitle || `Issue #${issueNumber}`,
352
- body: issueBody,
353
- labels: issueLabels,
354
- commentBody: context.commentBody,
355
- sender: context.sender || "unknown",
356
- _triggerType: envelope.type === "message" ? "chat" : "webhook",
357
- }).then((result) => {
358
- deps.db.executions.recordFinish(executionId, {
359
- success: result.success,
360
- error: result.success ? undefined : "Build cycle failed",
361
- durationMs: 0,
362
- });
363
- if (envelope.type === "message") {
364
- envelope.reply(result.success ? `Build cycle complete.` : `Build cycle failed.`);
365
- }
366
- }).catch((err) => {
367
- console.error(`[event] Build cycle failed:`, err);
368
- deps.db.executions.recordFinish(executionId, { success: false, error: err.message, durationMs: 0 });
369
- });
370
- return { kind: "dispatched", workflow: buildWorkflow };
371
- }
372
- /**
373
- * Lightweight PR fix — fix-and-push, no full build cycle. Resolves the PR's
374
- * head branch and CI failures (needed by the architect/executor), then
375
- * dispatches the `pr-fix` workflow. Bails if the branch can't be determined.
376
- */
377
- async function handlePrFix(context, handler, deps) {
378
- const repoStr = context.repo;
379
- const [owner, repo] = repoStr.includes("/") ? repoStr.split("/") : ["", repoStr];
380
- const prNumber = context.prNumber;
381
- if (!owner || !repo) {
382
- console.error(`[event] Invalid repo format: ${repoStr}`);
383
- return { kind: "ignored", reason: `invalid repo format: ${repoStr}` };
384
- }
385
- let prTitle = context.title || "";
386
- let prBody = context.body || "";
387
- let branch = "";
388
- let failedChecks = "";
389
- let isForkPr = false;
390
- let headRepoFullName = null;
391
- if (deps.github) {
392
- try {
393
- const pr = await deps.github.getPullRequest(owner, repo, prNumber);
394
- prTitle = prTitle || pr.title;
395
- prBody = prBody || pr.body || "";
396
- branch = pr.head.ref;
397
- // Cross-repo (fork) PR detection. A fork PR's head branch lives on
398
- // another repo we have no write access to, and its head ref isn't on
399
- // this repo's origin — so there's nothing for pr-fix to clone or push
400
- // to. Bail here, before any sandbox is provisioned. `head.repo` is null
401
- // when the source fork was deleted; treat that as a fork too (the branch
402
- // is gone either way).
403
- headRepoFullName = pr.head.repo?.full_name ?? null;
404
- const baseRepoFullName = pr.base.repo?.full_name ?? `${owner}/${repo}`;
405
- isForkPr = headRepoFullName === null || headRepoFullName !== baseRepoFullName;
406
- failedChecks = await deps.github.getFailedChecks(owner, repo, pr.head.sha);
407
- }
408
- catch (err) {
409
- console.warn(`[event] Could not fetch PR: ${err.message}`);
410
- }
411
- }
412
- if (isForkPr) {
413
- console.log(`[event] pr-fix skipped: PR #${prNumber} is a fork PR ` +
414
- `(head ${headRepoFullName ?? "deleted fork"} ≠ base ${owner}/${repo})`);
415
- if (deps.github) {
416
- const source = headRepoFullName ? `from \`${headRepoFullName}\`` : "from a now-deleted fork";
417
- await deps.github
418
- .postComment(owner, repo, prNumber, `I can't apply fixes to this PR — it comes ${source}, and I have no write access to the ` +
419
- `source branch (nor is its head ref on \`${owner}/${repo}\`). Re-create the change on a ` +
420
- `branch in \`${owner}/${repo}\` and I'll fix it there.`)
421
- .catch((e) => console.warn(`[event] fork-PR notice comment failed: ${e instanceof Error ? e.message : String(e)}`));
422
- }
423
- return { kind: "ignored", reason: `pr-fix not supported for fork PR #${prNumber}` };
424
- }
425
- if (!branch) {
426
- console.error(`[event] Could not determine branch for PR #${prNumber}`);
427
- return { kind: "ignored", reason: `could not determine branch for PR #${prNumber}` };
428
- }
429
- console.log(`[event] PR fix for ${repoStr}#${prNumber} on branch ${branch}`);
430
- const ciSection = failedChecks && !failedChecks.includes("No failed checks")
431
- ? `CI FAILURES (from GitHub Actions — fix these first):\n${failedChecks}`
432
- : "";
433
- deps.dispatchWorkflow(handler, {
434
- repo: repoStr,
435
- prNumber,
436
- title: prTitle,
437
- body: prBody,
438
- commentBody: context.commentBody || "",
439
- sender: context.sender || "unknown",
440
- branch,
441
- failedChecks,
442
- ciSection,
443
- _triggerType: "webhook",
444
- }).catch((err) => {
445
- console.error(`[event] PR fix failed:`, err);
446
- });
447
- return { kind: "dispatched", workflow: handler };
448
- }
449
- /**
450
- * Free-form user reply on a paused socratic explore run. Resolve the reply
451
- * gate with the message body, merge the Q&A into `scratch.socratic.qa` so the
452
- * next iteration sees the answer, and re-dispatch the explore workflow to
453
- * continue the loop.
454
- */
455
- async function handleExploreReply(envelope, context, deps) {
456
- const handled = { kind: "handled", handler: "explore-reply" };
457
- const workflowRunId = context.workflowRunId;
458
- const replyText = context.reply || "";
459
- const sender = context.sender || "unknown";
460
- const run = deps.db.runs.getRun(workflowRunId);
461
- if (!run) {
462
- console.warn(`[event] explore-reply: run ${workflowRunId} not found`);
463
- return handled;
464
- }
465
- const pending = deps.db.approvals.getPendingForWorkflow(workflowRunId);
466
- if (!pending || pending.kind !== "reply") {
467
- console.warn(`[event] explore-reply: no pending reply gate on ${workflowRunId}`);
468
- return handled;
469
- }
470
- // Append the QA entry to scratch.socratic.qa. The runner reads this via
471
- // {{scratch.socratic.qa}} on the next iteration. The bot's last question
472
- // lives on the execution row that produced it — resolve
473
- // `lastOutputExecutionId` through the DB; legacy rows that inline
474
- // `lastOutput` work too.
475
- const prevScratch = (run.scratch || {});
476
- const prevSocratic = (prevScratch.socratic || {});
477
- const qaList = Array.isArray(prevSocratic.qa) ? [...prevSocratic.qa] : [];
478
- const lastQuestion = (prevSocratic.lastOutputExecutionId
479
- ? deps.db.executions.getExecutionOutput(prevSocratic.lastOutputExecutionId) ?? ""
480
- : prevSocratic.lastOutput ?? "");
481
- qaList.push({
482
- question: lastQuestion,
483
- answer: replyText,
484
- sender,
485
- at: new Date().toISOString(),
486
- });
487
- // One transaction: resolve the reply gate (recording the reply text), merge
488
- // the QA into scratch, and flip the run back to running. Resume is then
489
- // ledger-driven: the runner re-runs from the top, completed phases skip via
490
- // shouldRunPhase, and the generic-loop node picks up from `scratch.iteration`
491
- // (persisted when it paused). The atomic op's double-reply guard throws on a
492
- // racing second reply — bail without a duplicate dispatch.
493
- try {
494
- deps.db.runs.resolveReplyGateAndResume(workflowRunId, pending.id, replyText, sender, {
495
- socratic: { ...prevSocratic, qa: qaList },
496
- });
497
- }
498
- catch (err) {
499
- console.warn(`[event] explore-reply: reply gate ${pending.id} already resolved — skipping duplicate resume:`, err);
500
- return handled;
501
- }
502
- // Re-dispatch. Use channelId/threadId from the current event context (the
503
- // router captured them from the reply envelope), not stored workflow
504
- // context — they were never persisted there.
505
- const isSlack = run.triggerId.startsWith("slack:");
506
- const replyChannelId = context.channelId;
507
- const replyThreadId = context.threadId;
508
- // Reconstruct owner/repo from the stored workflow context.
509
- const storedCtx = (run.context || {});
510
- const storedOwner = storedCtx.owner;
511
- const resumeRepo = storedOwner && run.repo ? `${storedOwner}/${run.repo}` : run.repo || undefined;
512
- console.log(`[event] explore-reply: resuming ${workflowRunId} after reply from ${sender}`);
513
- deps.dispatchWorkflow("explore", {
514
- repo: resumeRepo || (isSlack ? undefined : run.triggerId.split("#")[0]),
515
- issueNumber: run.issueNumber,
516
- sender,
517
- _triggerType: envelope.type === "message" ? "chat" : "webhook",
518
- triggerId: isSlack ? run.triggerId : undefined,
519
- channelId: replyChannelId,
520
- threadId: replyThreadId,
521
- }).catch((err) => console.error(`[event] explore-reply resume failed:`, err));
522
- return handled;
523
- }
524
- /**
525
- * Resolve a pending approval gate. On approval, resume the paused workflow run
526
- * (re-dispatching from the last completed phase via the ledger); on rejection,
527
- * mark the run failed. Replies on every path so the requester gets feedback.
528
- */
529
- async function handleApprovalResponse(envelope, context, deps) {
530
- const handled = { kind: "handled", handler: "approval-response" };
531
- const decision = context.decision;
532
- const sender = context.sender || "unknown";
533
- const reason = context.reason;
534
- const triggerId = context.repo && context.issueNumber
535
- ? `${context.repo}#${context.issueNumber}`
536
- : undefined;
537
- const approval = context.workflowRunId
538
- ? deps.db.approvals.getPendingForWorkflow(context.workflowRunId)
539
- : triggerId
540
- ? deps.db.approvals.getPendingByTrigger(triggerId)
541
- : null;
542
- if (!approval) {
543
- await envelope.reply("No pending approval found.");
544
- return handled;
545
- }
546
- if (decision === "approved") {
547
- // Re-trigger the workflow — resume logic picks up from DB state.
548
- const workflowRun = deps.db.runs.getRun(approval.workflowRunId);
549
- if (workflowRun && !deps.github) {
550
- // Record the approval, but we can't resume without the GitHub App.
551
- deps.db.approvals.respond(approval.id, "approved", sender, reason);
552
- await envelope.reply("Approval recorded, but cannot resume: GitHub App is not configured. Configure GITHUB_APP_ID and related env vars to enable build resumption.");
553
- return handled;
554
- }
555
- if (workflowRun && deps.github) {
556
- await envelope.reply(`Approved by ${sender}. Resuming \`${workflowRun.workflowName}\`...`);
557
- const [owner, repo] = workflowRun.triggerId.includes("/")
558
- ? workflowRun.triggerId.replace(/#\d+$/, "").split("/")
559
- : ["", ""];
560
- const issueNumber = workflowRun.issueNumber;
561
- if (owner && repo && issueNumber) {
562
- // One transaction: respond 'approved' + flip the run to running. The
563
- // long-running re-dispatch happens after the commit.
564
- deps.db.runs.resolveGateAndResume(approval.id, sender);
565
- deps.dispatchWorkflow(workflowRun.workflowName, {
566
- repo: `${owner}/${repo}`,
567
- issueNumber,
568
- title: `Issue #${issueNumber}`,
569
- body: "",
570
- sender,
571
- _triggerType: "approval",
572
- }).catch((err) => console.error(`[approval] Resume failed:`, err));
573
- }
574
- else {
575
- // Can't reconstruct the dispatch target — record without resuming.
576
- deps.db.approvals.respond(approval.id, "approved", sender, reason);
577
- }
578
- }
579
- else {
580
- // No workflow run for this approval — just record the response.
581
- deps.db.approvals.respond(approval.id, "approved", sender, reason);
582
- }
583
- }
584
- else {
585
- // One transaction: respond 'rejected' + fail the run (a no-op if the run
586
- // is already gone).
587
- deps.db.runs.resolveGateAndFail(approval.id, sender, reason);
588
- await envelope.reply(`Rejected by ${sender}. Build cycle aborted.${reason ? ` Reason: ${reason}` : ""}`);
589
- }
590
- return handled;
591
- }
592
- /**
593
- * Conversational chat turn. Records an `executions` row so chat usage shows up
594
- * in dashboard stats, runs the in-process turn, persists the minted agent
595
- * session id for resume on the next turn, and replies. Failures still record a
596
- * finish row and apologize rather than going silent.
597
- *
598
- * Per-session batching of bursty messaging input happens *before* this point,
599
- * in the MessageBatcher at the connector→dispatch boundary (see
600
- * `src/engine/message-batcher.ts`) — so by the time a turn reaches here it is
601
- * already one combined, send-ordered message. This handler just runs it.
602
- */
603
- async function handleChat(envelope, context, deps) {
604
- const messagingSessionId = context.sessionId;
605
- const message = context.message;
606
- const sender = context.sender;
607
- // Re-assert the thinking indicator for this turn (a batched burst can drain
608
- // as several turns; the per-arrival indicator is cleared by the first reply).
609
- envelope.typing?.().catch(() => { });
610
- await runChatTurn(deps, { sessionId: messagingSessionId, message, sender, reply: envelope.reply });
611
- return { kind: "handled", handler: "chat" };
612
- }
613
- /**
614
- * Run one chat turn end-to-end: record the execution, run the model, persist a
615
- * freshly-minted agent session id for resume, and post the reply (apologizing
616
- * on failure). Shared by the inline `handleChat` path and the ChatCoordinator,
617
- * so batched turns get identical execution/telemetry recording. `message` may
618
- * be a single message or a newline-combined batch.
619
- */
620
- export async function runChatTurn(deps, args) {
621
- const { sessionId, message, sender, reply } = args;
622
- // First message has no agent session → fresh; later messages resume.
623
- const resumeAgentSessionId = deps.sessionManager.getSession(sessionId)?.agentSessionId ?? undefined;
624
- // triggerId is the messaging-session id, so a whole Slack thread groups
625
- // together with `GROUP BY trigger_id`.
626
- const executionId = randomUUID();
627
- deps.db.executions.recordStart({
628
- id: executionId,
629
- triggerType: "chat",
630
- triggerId: sessionId,
631
- skill: "chat",
632
- startedAt: new Date().toISOString(),
633
- });
634
- try {
635
- const result = await deps.runChat(message, sessionId, sender, resumeAgentSessionId);
636
- if (result.agentSessionId && result.agentSessionId !== resumeAgentSessionId) {
637
- deps.sessionManager.setAgentSessionId(sessionId, result.agentSessionId);
638
- }
639
- deps.db.executions.recordFinish(executionId, {
640
- success: result.success,
641
- error: result.error,
642
- turns: result.turns,
643
- durationMs: result.durationMs,
644
- // dashboardSessionId so error rows still link to a (stub) jsonl envelope.
645
- sessionId: result.dashboardSessionId ?? result.agentSessionId,
646
- costUsd: result.costUsd,
647
- inputTokens: result.inputTokens,
648
- cacheCreationInputTokens: result.cacheCreationInputTokens,
649
- cacheReadInputTokens: result.cacheReadInputTokens,
650
- outputTokens: result.outputTokens,
651
- apiDurationMs: result.apiDurationMs,
652
- stopReason: result.stopReason,
653
- });
654
- await reply(result.text);
655
- }
656
- catch (err) {
657
- const msg = err instanceof Error ? err.message : String(err);
658
- console.error(`[dispatch] Chat error:`, msg);
659
- deps.db.executions.recordFinish(executionId, { success: false, error: msg, durationMs: 0 });
660
- await reply("Sorry, I encountered an error. Please try again.");
661
- }
662
- }
663
- //# sourceMappingURL=dispatcher.js.map