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
package/dist/state/db.js CHANGED
@@ -1,870 +1,147 @@
1
1
  import Database from "better-sqlite3";
2
2
  import { resolve } from "path";
3
+ import { migrate } from "./migrate.js";
4
+ import { ExecutionStore } from "./execution-store.js";
5
+ import { ApprovalStore } from "./approval-store.js";
6
+ import { WorkflowRunStore } from "./workflow-run-store.js";
7
+ export { ExecutionStore } from "./execution-store.js";
8
+ export { ApprovalStore } from "./approval-store.js";
9
+ export { WorkflowRunStore } from "./workflow-run-store.js";
3
10
  const DEFAULT_DB_PATH = "lastlight.db";
4
11
  /**
5
12
  * Lightweight SQLite state for operational tracking.
6
13
  * NOT for conversation history — only execution logs and rate limits.
14
+ *
15
+ * Construction root for the three per-table stores (issue #97): it opens the
16
+ * single `Database` connection, runs {@link migrate}, and builds
17
+ * {@link ExecutionStore} / {@link ApprovalStore} / {@link WorkflowRunStore} on
18
+ * top of it (all sharing the one connection so better-sqlite3 transactions
19
+ * span every store). Callers reach the stores via `db.runs` / `db.approvals` /
20
+ * `db.executions`. StateDb itself retains only the cross-cutting cron-override
21
+ * and workflow kill-switch concerns.
7
22
  */
8
23
  export class StateDb {
9
24
  db;
25
+ /** Owns the `executions` ledger. */
26
+ executions;
27
+ /** Owns the `workflow_approvals` table. */
28
+ approvals;
29
+ /** Aggregate root for `workflow_runs` + the atomic lifecycle operations. */
30
+ runs;
10
31
  constructor(dbPath) {
11
- this.db = new Database(resolve(dbPath || DEFAULT_DB_PATH));
32
+ // ":memory:" stays a real per-connection in-memory DB (used by tests for
33
+ // isolation) — only filesystem paths go through resolve(). Previously
34
+ // resolve(":memory:") produced a shared on-disk file named ":memory:",
35
+ // which let parallel test files contend on one DB and deadlock once the
36
+ // stores started holding write transactions.
37
+ const target = dbPath === ":memory:" ? ":memory:" : resolve(dbPath || DEFAULT_DB_PATH);
38
+ this.db = new Database(target);
12
39
  this.db.pragma("journal_mode = WAL");
13
- this.migrate();
14
- }
15
- migrate() {
16
- this.db.exec(`
17
- CREATE TABLE IF NOT EXISTS executions (
18
- id TEXT PRIMARY KEY,
19
- trigger_type TEXT NOT NULL,
20
- trigger_id TEXT NOT NULL,
21
- skill TEXT NOT NULL,
22
- repo TEXT,
23
- issue_number INTEGER,
24
- started_at TEXT NOT NULL,
25
- finished_at TEXT,
26
- success INTEGER,
27
- error TEXT,
28
- turns INTEGER,
29
- duration_ms INTEGER
30
- );
31
-
32
- CREATE TABLE IF NOT EXISTS rate_limits (
33
- resource TEXT PRIMARY KEY,
34
- remaining INTEGER,
35
- reset_at TEXT,
36
- updated_at TEXT
37
- );
38
-
39
- CREATE TABLE IF NOT EXISTS system_status (
40
- component TEXT PRIMARY KEY,
41
- state TEXT NOT NULL,
42
- reason TEXT,
43
- since TEXT NOT NULL,
44
- updated_at TEXT NOT NULL
45
- );
46
-
47
- CREATE INDEX IF NOT EXISTS idx_executions_trigger ON executions(trigger_type, trigger_id);
48
- CREATE INDEX IF NOT EXISTS idx_executions_skill ON executions(skill, started_at);
49
-
50
- CREATE TABLE IF NOT EXISTS workflow_runs (
51
- id TEXT PRIMARY KEY,
52
- workflow_name TEXT NOT NULL,
53
- trigger_id TEXT NOT NULL,
54
- repo TEXT,
55
- issue_number INTEGER,
56
- current_phase TEXT NOT NULL,
57
- phase_history TEXT NOT NULL DEFAULT '[]',
58
- status TEXT NOT NULL DEFAULT 'running',
59
- context TEXT,
60
- started_at TEXT NOT NULL,
61
- updated_at TEXT NOT NULL,
62
- finished_at TEXT
63
- );
64
- CREATE INDEX IF NOT EXISTS idx_workflow_runs_trigger ON workflow_runs(trigger_id, status);
65
- CREATE INDEX IF NOT EXISTS idx_workflow_runs_status ON workflow_runs(status);
66
-
67
- CREATE TABLE IF NOT EXISTS workflow_approvals (
68
- id TEXT PRIMARY KEY,
69
- workflow_run_id TEXT NOT NULL,
70
- gate TEXT NOT NULL,
71
- summary TEXT NOT NULL,
72
- status TEXT NOT NULL DEFAULT 'pending',
73
- requested_by TEXT,
74
- responded_by TEXT,
75
- response TEXT,
76
- responded_at TEXT,
77
- created_at TEXT NOT NULL
78
- );
79
- CREATE INDEX IF NOT EXISTS idx_approvals_workflow ON workflow_approvals(workflow_run_id);
80
- CREATE INDEX IF NOT EXISTS idx_approvals_status ON workflow_approvals(status);
81
- `);
82
- // Add node_statuses column for DAG parallelism (safe for existing DBs)
83
- try {
84
- this.db.exec(`ALTER TABLE workflow_runs ADD COLUMN node_statuses TEXT`);
85
- }
86
- catch {
87
- // Column already exists — ignore
88
- }
89
- // Mutable phase-to-phase state for features like the socratic explore
90
- // loop that accumulate data across reply-gate pauses.
91
- try {
92
- this.db.exec(`ALTER TABLE workflow_runs ADD COLUMN scratch TEXT`);
93
- }
94
- catch {
95
- // Column already exists — ignore
96
- }
97
- // Gate flavor: 'approve' (explicit approve/reject) vs 'reply' (resolves
98
- // on any free-form reply in the same thread — used by the socratic
99
- // explore loop).
100
- try {
101
- this.db.exec(`ALTER TABLE workflow_approvals ADD COLUMN kind TEXT NOT NULL DEFAULT 'approve'`);
102
- }
103
- catch {
104
- // Column already exists — ignore
105
- }
106
- // Add session_id column to executions so the dashboard can resolve a
107
- // phase click to its agent session log. Additive — safe on existing DBs.
108
- try {
109
- this.db.exec(`ALTER TABLE executions ADD COLUMN session_id TEXT`);
110
- }
111
- catch {
112
- // Column already exists — ignore
113
- }
114
- // Per-execution Claude usage metrics, captured from the stream-json
115
- // result message. Lets the dashboard show cost / tokens per phase and
116
- // lets us aggregate spend later. All additive, safe on existing DBs.
117
- for (const col of [
118
- "cost_usd REAL",
119
- "input_tokens INTEGER",
120
- "cache_creation_input_tokens INTEGER",
121
- "cache_read_input_tokens INTEGER",
122
- "output_tokens INTEGER",
123
- "api_duration_ms INTEGER",
124
- "stop_reason TEXT",
125
- // Links a phase execution to its owning workflow_run. Dedup and
126
- // "already running / done" checks scope to this column so a fresh
127
- // re-trigger of the same workflow on the same issue creates new
128
- // executions instead of reusing the old run's rows.
129
- "workflow_run_id TEXT",
130
- ]) {
131
- try {
132
- this.db.exec(`ALTER TABLE executions ADD COLUMN ${col}`);
133
- }
134
- catch {
135
- // Column already exists — ignore
136
- }
137
- }
138
- try {
139
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_executions_workflow_run ON executions(workflow_run_id, skill)`);
140
- }
141
- catch {
142
- // Index already exists — ignore
143
- }
144
- }
145
- recordStart(record) {
146
- this.db.prepare(`
147
- INSERT INTO executions (id, trigger_type, trigger_id, skill, repo, issue_number, started_at, workflow_run_id)
148
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
149
- `).run(record.id, record.triggerType, record.triggerId, record.skill, record.repo, record.issueNumber, record.startedAt, record.workflowRunId ?? null);
150
- }
151
- /**
152
- * Mid-run update: persist the agent session id as soon as the executor sees
153
- * the stream-json `system/init` line. Lets the dashboard surface the live
154
- * session log for an in-flight phase, not just for completed ones.
155
- */
156
- recordSessionId(id, sessionId) {
157
- this.db.prepare(`UPDATE executions SET session_id = ? WHERE id = ?`).run(sessionId, id);
158
- }
159
- recordFinish(id, result) {
160
- this.db.prepare(`
161
- UPDATE executions
162
- SET finished_at = ?,
163
- success = ?,
164
- error = ?,
165
- turns = ?,
166
- duration_ms = ?,
167
- session_id = COALESCE(?, session_id),
168
- cost_usd = COALESCE(?, cost_usd),
169
- input_tokens = COALESCE(?, input_tokens),
170
- cache_creation_input_tokens = COALESCE(?, cache_creation_input_tokens),
171
- cache_read_input_tokens = COALESCE(?, cache_read_input_tokens),
172
- output_tokens = COALESCE(?, output_tokens),
173
- api_duration_ms = COALESCE(?, api_duration_ms),
174
- stop_reason = COALESCE(?, stop_reason)
175
- WHERE id = ?
176
- `).run(new Date().toISOString(), result.success ? 1 : 0, result.error, result.turns, result.durationMs, result.sessionId ?? null, result.costUsd ?? null, result.inputTokens ?? null, result.cacheCreationInputTokens ?? null, result.cacheReadInputTokens ?? null, result.outputTokens ?? null, result.apiDurationMs ?? null, result.stopReason ?? null, id);
177
- }
178
- /**
179
- * List chat threads aggregated from the executions table.
180
- *
181
- * Each row collapses every execution sharing a `trigger_id` (the messaging
182
- * session id) into one summary so the dashboard's chat-sessions tab can
183
- * show one card per Slack thread, not one per turn. Joined with
184
- * `messaging_sessions` for the agent SDK session id (used to resolve to
185
- * the on-disk jsonl when streaming messages) and with `messaging_messages`
186
- * for the most recent assistant text (preview snippet).
187
- */
188
- listChatThreads(limit) {
189
- const rows = this.db.prepare(`
190
- SELECT
191
- e.trigger_id AS triggerId,
192
- ms.agent_session_id AS agentSessionId,
193
- MIN(e.started_at) AS firstStartedAt,
194
- MAX(COALESCE(e.finished_at, e.started_at)) AS lastActivityAt,
195
- COUNT(*) AS turnCount,
196
- SUM(COALESCE(e.cost_usd, 0)) AS totalCost,
197
- SUM(COALESCE(e.input_tokens, 0)) AS inputTokens,
198
- SUM(COALESCE(e.output_tokens, 0)) AS outputTokens,
199
- SUM(COALESCE(e.cache_read_input_tokens, 0)) AS cacheReadTokens,
200
- (
201
- SELECT mm.content
202
- FROM messaging_messages mm
203
- WHERE mm.session_id = e.trigger_id AND mm.role = 'assistant'
204
- ORDER BY mm.timestamp DESC
205
- LIMIT 1
206
- ) AS lastAssistantContent
207
- FROM executions e
208
- LEFT JOIN messaging_sessions ms ON ms.id = e.trigger_id
209
- WHERE e.skill = 'chat'
210
- GROUP BY e.trigger_id
211
- ORDER BY lastActivityAt DESC
212
- LIMIT ?
213
- `).all(limit);
214
- return rows;
215
- }
216
- /** Look up a single chat thread by its messaging session id (= trigger_id). */
217
- getChatThread(triggerId) {
218
- const row = this.db.prepare(`
219
- SELECT
220
- e.trigger_id AS triggerId,
221
- ms.agent_session_id AS agentSessionId,
222
- MIN(e.started_at) AS firstStartedAt,
223
- MAX(COALESCE(e.finished_at, e.started_at)) AS lastActivityAt,
224
- COUNT(*) AS turnCount,
225
- SUM(COALESCE(e.cost_usd, 0)) AS totalCost,
226
- SUM(COALESCE(e.input_tokens, 0)) AS inputTokens,
227
- SUM(COALESCE(e.output_tokens, 0)) AS outputTokens,
228
- SUM(COALESCE(e.cache_read_input_tokens, 0)) AS cacheReadTokens,
229
- (
230
- SELECT mm.content
231
- FROM messaging_messages mm
232
- WHERE mm.session_id = e.trigger_id AND mm.role = 'assistant'
233
- ORDER BY mm.timestamp DESC
234
- LIMIT 1
235
- ) AS lastAssistantContent
236
- FROM executions e
237
- LEFT JOIN messaging_sessions ms ON ms.id = e.trigger_id
238
- WHERE e.skill = 'chat' AND e.trigger_id = ?
239
- GROUP BY e.trigger_id
240
- `).get(triggerId);
241
- return row ?? null;
242
- }
243
- /** Check if a skill is currently running for a given trigger */
244
- isRunning(skill, triggerId) {
245
- const row = this.db.prepare(`
246
- SELECT 1 FROM executions
247
- WHERE skill = ? AND trigger_id = ? AND finished_at IS NULL
248
- LIMIT 1
249
- `).get(skill, triggerId);
250
- return !!row;
251
- }
252
- /** Check if a skill has already completed successfully for a given trigger */
253
- isCompleted(skill, triggerId) {
254
- const row = this.db.prepare(`
255
- SELECT 1 FROM executions
256
- WHERE skill = ? AND trigger_id = ? AND success = 1
257
- LIMIT 1
258
- `).get(skill, triggerId);
259
- return !!row;
260
- }
261
- /**
262
- * Check if a phase should run: not currently running AND not already
263
- * succeeded. When `workflowRunId` is provided, scopes the check to that
264
- * specific run — a fresh re-trigger (new workflow_run_id) will see no
265
- * matching rows and correctly return "run" even if a prior completed run
266
- * for the same trigger exists. Omit `workflowRunId` for legacy callers
267
- * (e.g. chat/status-report dedup) that want the old trigger-wide scope.
268
- */
269
- shouldRunPhase(skill, triggerId, workflowRunId) {
270
- const [scopeClause, scopeParam] = workflowRunId
271
- ? ["workflow_run_id = ?", workflowRunId]
272
- : ["trigger_id = ?", triggerId];
273
- const running = this.db.prepare(`
274
- SELECT 1 FROM executions
275
- WHERE skill = ? AND ${scopeClause} AND finished_at IS NULL
276
- LIMIT 1
277
- `).get(skill, scopeParam);
278
- if (running)
279
- return "running";
280
- const done = this.db.prepare(`
281
- SELECT 1 FROM executions
282
- WHERE skill = ? AND ${scopeClause} AND success = 1
283
- LIMIT 1
284
- `).get(skill, scopeParam);
285
- if (done)
286
- return "done";
287
- return "run";
288
- }
289
- /** Mark all stale "running" executions for a skill/trigger as failed.
290
- * Called when we detect no matching Docker container is alive. */
291
- markStaleAsFailed(skill, triggerId, workflowRunId) {
292
- const [scopeClause, scopeParam] = workflowRunId
293
- ? ["workflow_run_id = ?", workflowRunId]
294
- : ["trigger_id = ?", triggerId];
295
- const result = this.db.prepare(`
296
- UPDATE executions
297
- SET finished_at = ?, success = 0, error = 'stale: container no longer running'
298
- WHERE skill = ? AND ${scopeClause} AND finished_at IS NULL
299
- `).run(new Date().toISOString(), skill, scopeParam);
300
- return result.changes;
301
- }
302
- /**
303
- * Mark every unfinished execution row for a trigger as failed, regardless
304
- * of skill. Used by the boot-time recovery sweep to clear out phases that
305
- * were running when the harness crashed/restarted, so the runner's dedup
306
- * logic doesn't think they're still in flight.
307
- */
308
- markAllStaleForTrigger(triggerId, reason) {
309
- const result = this.db.prepare(`
310
- UPDATE executions
311
- SET finished_at = ?, success = 0, error = ?
312
- WHERE trigger_id = ? AND finished_at IS NULL
313
- `).run(new Date().toISOString(), reason, triggerId);
314
- return result.changes;
315
- }
316
- /**
317
- * Mark the most recent execution for a skill/trigger as failed.
318
- * Used when the agent SDK reported success but the orchestrator decided
319
- * the business outcome was a failure (e.g. guardrails BLOCKED, reviewer
320
- * REQUEST_CHANGES). Without this, runPhase would skip the phase on the
321
- * next run because the DB still says success=1.
322
- */
323
- markLatestAsFailed(skill, triggerId, reason, workflowRunId) {
324
- const [scopeClause, scopeParam] = workflowRunId
325
- ? ["workflow_run_id = ?", workflowRunId]
326
- : ["trigger_id = ?", triggerId];
327
- const result = this.db.prepare(`
328
- UPDATE executions
329
- SET success = 0, error = ?
330
- WHERE id = (
331
- SELECT id FROM executions
332
- WHERE skill = ? AND ${scopeClause}
333
- ORDER BY started_at DESC
334
- LIMIT 1
335
- )
336
- `).run(reason, skill, scopeParam);
337
- return result.changes;
338
- }
339
- /** Get recent executions for a skill */
340
- recentExecutions(skill, limit = 10) {
341
- return this.db.prepare(`
342
- SELECT * FROM executions
343
- WHERE skill = ?
344
- ORDER BY started_at DESC
345
- LIMIT ?
346
- `).all(skill, limit);
40
+ migrate(this.db);
41
+ this.executions = new ExecutionStore(this.db);
42
+ this.approvals = new ApprovalStore(this.db);
43
+ this.runs = new WorkflowRunStore(this.db, { approvals: this.approvals });
44
+ }
45
+ // ── Cron overrides ─────────────────────────────────────────────
46
+ /** Get the override row for a single cron, or null if none. */
47
+ getCronOverride(name) {
48
+ const row = this.db
49
+ .prepare(`SELECT name, enabled, schedule, updated_at, updated_by FROM cron_overrides WHERE name = ?`)
50
+ .get(name);
51
+ return row ? this.deserializeCronOverride(row) : null;
347
52
  }
348
- /** Count consecutive failures for a skill (for cron failure tracking) */
349
- consecutiveFailures(skill) {
350
- const rows = this.db.prepare(`
351
- SELECT success FROM executions
352
- WHERE skill = ? AND finished_at IS NOT NULL
353
- ORDER BY started_at DESC
354
- LIMIT 10
355
- `).all(skill);
356
- let count = 0;
53
+ /** All override rows keyed by cron name. */
54
+ getAllCronOverrides() {
55
+ const rows = this.db
56
+ .prepare(`SELECT name, enabled, schedule, updated_at, updated_by FROM cron_overrides`)
57
+ .all();
58
+ const map = new Map();
357
59
  for (const row of rows) {
358
- if (row.success === 0)
359
- count++;
360
- else
361
- break;
60
+ const o = this.deserializeCronOverride(row);
61
+ map.set(o.name, o);
362
62
  }
363
- return count;
364
- }
365
- /** Update rate limit state */
366
- updateRateLimit(resource, remaining, resetAt) {
367
- this.db.prepare(`
368
- INSERT INTO rate_limits (resource, remaining, reset_at, updated_at)
369
- VALUES (?, ?, ?, ?)
370
- ON CONFLICT(resource) DO UPDATE SET remaining = ?, reset_at = ?, updated_at = ?
371
- `).run(resource, remaining, resetAt, new Date().toISOString(), remaining, resetAt, new Date().toISOString());
372
- }
373
- /** Get all rate limit records */
374
- getRateLimits() {
375
- return this.db.prepare(`
376
- SELECT resource, remaining, reset_at, updated_at FROM rate_limits
377
- ORDER BY resource
378
- `).all();
379
- }
380
- // ── System status (component health tracking) ──
381
- /**
382
- * Get the current state of a system component.
383
- * Returns null if the component has never reported.
384
- */
385
- getSystemStatus(component) {
386
- const row = this.db.prepare(`
387
- SELECT state, reason, since, updated_at FROM system_status WHERE component = ?
388
- `).get(component);
389
- return row || null;
63
+ return map;
390
64
  }
391
65
  /**
392
- * Set the state of a system component. If the new state differs from the
393
- * current state, updates `since` to now (transition timestamp). Returns
394
- * true on transition (state changed), false on refresh (state unchanged).
66
+ * Upsert an override. Pass only the fields you want to change undefined
67
+ * fields preserve the existing value (or default to enabled=1, schedule=null
68
+ * on insert).
395
69
  */
396
- setSystemStatus(component, state, reason) {
397
- const now = new Date().toISOString();
398
- const existing = this.getSystemStatus(component);
399
- const transitioned = !existing || existing.state !== state;
400
- const since = transitioned ? now : existing.since;
401
- this.db.prepare(`
402
- INSERT INTO system_status (component, state, reason, since, updated_at)
403
- VALUES (?, ?, ?, ?, ?)
404
- ON CONFLICT(component) DO UPDATE SET
405
- state = excluded.state,
406
- reason = excluded.reason,
407
- since = excluded.since,
408
- updated_at = excluded.updated_at
409
- `).run(component, state, reason || null, since, now);
410
- return transitioned;
411
- }
412
- /** List all known component statuses. */
413
- listSystemStatus() {
414
- return this.db.prepare(`
415
- SELECT component, state, reason, since, updated_at FROM system_status
416
- ORDER BY component
417
- `).all();
418
- }
419
- /** Get all executions with pagination */
420
- allExecutions(limit = 100, offset = 0) {
421
- return this.db.prepare(`
422
- SELECT * FROM executions
423
- ORDER BY started_at DESC
424
- LIMIT ? OFFSET ?
425
- `).all(limit, offset);
426
- }
427
- /**
428
- * Get every execution recorded for a workflow run, with camelCase fields
429
- * ready for the dashboard. Ordered by start time ascending so phases
430
- * display in the order they ran. Scopes to a specific `workflow_run_id`
431
- * when provided (preferred — isolates fresh re-triggers from prior runs);
432
- * falls back to the legacy trigger-wide scope for rows written before the
433
- * workflow_run_id column existed.
434
- */
435
- getExecutionsForWorkflowRun(workflowRunId, triggerId, workflowName) {
436
- const skillPattern = workflowName ? `${workflowName}:%` : "%:%";
437
- const rows = this.db.prepare(`
438
- SELECT
439
- id,
440
- trigger_type AS triggerType,
441
- trigger_id AS triggerId,
442
- skill,
443
- repo,
444
- issue_number AS issueNumber,
445
- started_at AS startedAt,
446
- finished_at AS finishedAt,
447
- success,
448
- error,
449
- turns,
450
- duration_ms AS durationMs,
451
- session_id AS sessionId,
452
- cost_usd AS costUsd,
453
- input_tokens AS inputTokens,
454
- cache_creation_input_tokens AS cacheCreationInputTokens,
455
- cache_read_input_tokens AS cacheReadInputTokens,
456
- output_tokens AS outputTokens,
457
- api_duration_ms AS apiDurationMs,
458
- stop_reason AS stopReason,
459
- workflow_run_id AS workflowRunId
460
- FROM executions
461
- WHERE (workflow_run_id = ? OR (workflow_run_id IS NULL AND trigger_id = ?))
462
- AND skill LIKE ?
463
- ORDER BY started_at ASC
464
- `).all(workflowRunId, triggerId, skillPattern);
465
- return rows.map((r) => ({
466
- id: r.id,
467
- triggerType: r.triggerType,
468
- triggerId: r.triggerId,
469
- skill: r.skill,
470
- repo: r.repo ?? undefined,
471
- issueNumber: r.issueNumber ?? undefined,
472
- startedAt: r.startedAt,
473
- finishedAt: r.finishedAt ?? undefined,
474
- success: r.success === null || r.success === undefined ? undefined : Boolean(r.success),
475
- error: r.error ?? undefined,
476
- turns: r.turns ?? undefined,
477
- durationMs: r.durationMs ?? undefined,
478
- sessionId: r.sessionId ?? undefined,
479
- costUsd: r.costUsd ?? undefined,
480
- inputTokens: r.inputTokens ?? undefined,
481
- cacheCreationInputTokens: r.cacheCreationInputTokens ?? undefined,
482
- cacheReadInputTokens: r.cacheReadInputTokens ?? undefined,
483
- outputTokens: r.outputTokens ?? undefined,
484
- apiDurationMs: r.apiDurationMs ?? undefined,
485
- stopReason: r.stopReason ?? undefined,
486
- workflowRunId: r.workflowRunId ?? undefined,
487
- }));
488
- }
489
- /** Get currently running executions (no finished_at) */
490
- runningExecutions() {
491
- return this.db.prepare(`
492
- SELECT * FROM executions
493
- WHERE finished_at IS NULL
494
- ORDER BY started_at DESC
495
- `).all();
496
- }
497
- /** Aggregate execution stats */
498
- executionStats() {
499
- const today = new Date();
500
- today.setHours(0, 0, 0, 0);
501
- const todayIso = today.toISOString();
502
- const total = this.db.prepare(`SELECT COUNT(*) as c FROM executions`).get().c;
503
- const todayCount = this.db.prepare(`SELECT COUNT(*) as c FROM executions WHERE started_at >= ?`).get(todayIso).c;
504
- const running = this.db.prepare(`SELECT COUNT(*) as c FROM executions WHERE finished_at IS NULL`).get().c;
505
- const skillRows = this.db.prepare(`
506
- SELECT skill, COUNT(*) as count,
507
- SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success,
508
- SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as fail
509
- FROM executions GROUP BY skill
510
- `).all();
511
- const by_skill = {};
512
- for (const r of skillRows) {
513
- by_skill[r.skill] = { count: r.count, success: r.success, fail: r.fail };
514
- }
515
- const triggerRows = this.db.prepare(`
516
- SELECT trigger_type, COUNT(*) as count FROM executions GROUP BY trigger_type
517
- `).all();
518
- const by_trigger = {};
519
- for (const r of triggerRows) {
520
- by_trigger[r.trigger_type] = r.count;
521
- }
522
- return { total_executions: total, today_count: todayCount, by_skill, by_trigger, running };
523
- }
524
- /** Daily aggregated stats for the last N days */
525
- dailyStats(days) {
526
- // Build the inclusive UTC date window: [today - (days-1), today].
527
- // SQLite's date(started_at) returns a UTC YYYY-MM-DD string, so we
528
- // generate the same format here to align keys.
529
- const today = new Date();
530
- const startUtc = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - (days - 1)));
531
- const dateKeys = [];
532
- for (let i = 0; i < days; i++) {
533
- const d = new Date(startUtc);
534
- d.setUTCDate(startUtc.getUTCDate() + i);
535
- dateKeys.push(d.toISOString().slice(0, 10));
536
- }
537
- const rows = this.db.prepare(`
538
- SELECT
539
- date(started_at) AS date,
540
- COUNT(*) AS executions,
541
- SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS successes,
542
- SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) AS failures,
543
- COALESCE(SUM(input_tokens), 0) + COALESCE(SUM(output_tokens), 0) + COALESCE(SUM(cache_read_input_tokens), 0) AS totalTokens,
544
- COALESCE(SUM(input_tokens), 0) AS inputTokens,
545
- COALESCE(SUM(output_tokens), 0) AS outputTokens,
546
- COALESCE(SUM(cache_read_input_tokens), 0) AS cacheReadTokens,
547
- COALESCE(SUM(cost_usd), 0) AS costUsd
548
- FROM executions
549
- WHERE date(started_at) >= ?
550
- GROUP BY date(started_at)
551
- `).all(dateKeys[0]);
552
- const byDate = new Map(rows.map((r) => [r.date, r]));
553
- return dateKeys.map((date) => byDate.get(date) ?? {
554
- date,
555
- executions: 0,
556
- successes: 0,
557
- failures: 0,
558
- totalTokens: 0,
559
- inputTokens: 0,
560
- outputTokens: 0,
561
- cacheReadTokens: 0,
562
- costUsd: 0,
563
- });
564
- }
565
- /** Hourly aggregated stats for the last N hours (UTC). Bucket key is `YYYY-MM-DDTHH`. */
566
- hourlyStats(hours) {
567
- const now = new Date();
568
- const startUtc = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours() - (hours - 1)));
569
- const hourKeys = [];
570
- for (let i = 0; i < hours; i++) {
571
- const d = new Date(startUtc);
572
- d.setUTCHours(startUtc.getUTCHours() + i);
573
- // YYYY-MM-DDTHH (matches strftime('%Y-%m-%dT%H', …))
574
- hourKeys.push(d.toISOString().slice(0, 13));
575
- }
576
- const rows = this.db.prepare(`
577
- SELECT
578
- strftime('%Y-%m-%dT%H', started_at) AS date,
579
- COUNT(*) AS executions,
580
- SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS successes,
581
- SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) AS failures,
582
- COALESCE(SUM(input_tokens), 0) + COALESCE(SUM(output_tokens), 0) + COALESCE(SUM(cache_read_input_tokens), 0) AS totalTokens,
583
- COALESCE(SUM(input_tokens), 0) AS inputTokens,
584
- COALESCE(SUM(output_tokens), 0) AS outputTokens,
585
- COALESCE(SUM(cache_read_input_tokens), 0) AS cacheReadTokens,
586
- COALESCE(SUM(cost_usd), 0) AS costUsd
587
- FROM executions
588
- WHERE strftime('%Y-%m-%dT%H', started_at) >= ?
589
- GROUP BY strftime('%Y-%m-%dT%H', started_at)
590
- `).all(hourKeys[0]);
591
- const byHour = new Map(rows.map((r) => [r.date, r]));
592
- return hourKeys.map((date) => byHour.get(date) ?? {
593
- date,
594
- executions: 0,
595
- successes: 0,
596
- failures: 0,
597
- totalTokens: 0,
598
- inputTokens: 0,
599
- outputTokens: 0,
600
- cacheReadTokens: 0,
601
- costUsd: 0,
602
- });
603
- }
604
- // ── Workflow Runs ──────────────────────────────────────────────
605
- /** Create a new workflow run record */
606
- createWorkflowRun(run) {
70
+ setCronOverride(name, patch) {
607
71
  const now = new Date().toISOString();
608
- this.db.prepare(`
609
- INSERT INTO workflow_runs (id, workflow_name, trigger_id, repo, issue_number, current_phase, phase_history, status, context, scratch, started_at, updated_at)
610
- VALUES (?, ?, ?, ?, ?, ?, '[]', ?, ?, ?, ?, ?)
611
- `).run(run.id, run.workflowName, run.triggerId, run.repo ?? null, run.issueNumber ?? null, run.currentPhase, run.status, run.context ? JSON.stringify(run.context) : null, run.scratch ? JSON.stringify(run.scratch) : null, run.startedAt, now);
72
+ const existing = this.getCronOverride(name);
73
+ const enabled = patch.enabled ?? existing?.enabled ?? true;
74
+ const schedule = patch.schedule === undefined ? existing?.schedule ?? null : patch.schedule;
75
+ this.db
76
+ .prepare(`INSERT INTO cron_overrides (name, enabled, schedule, updated_at, updated_by)
77
+ VALUES (?, ?, ?, ?, ?)
78
+ ON CONFLICT(name) DO UPDATE SET
79
+ enabled = excluded.enabled,
80
+ schedule = excluded.schedule,
81
+ updated_at = excluded.updated_at,
82
+ updated_by = excluded.updated_by`)
83
+ .run(name, enabled ? 1 : 0, schedule, now, patch.updatedBy ?? null);
84
+ }
85
+ /** Remove the override entirely (revert to YAML defaults). */
86
+ clearCronOverride(name) {
87
+ this.db.prepare(`DELETE FROM cron_overrides WHERE name = ?`).run(name);
88
+ }
89
+ deserializeCronOverride(row) {
90
+ return {
91
+ name: row.name,
92
+ enabled: row.enabled === 1,
93
+ schedule: row.schedule ?? null,
94
+ updatedAt: row.updated_at,
95
+ updatedBy: row.updated_by ?? null,
96
+ };
612
97
  }
98
+ // ── Workflow overrides (kill switch) ───────────────────────────
613
99
  /**
614
- * Top-level merge of `patch` into the workflow run's scratch state.
615
- * Loop iterations can append to `scratch.socratic.qa` without clobbering
616
- * other keys.
100
+ * Cheap check used by every dispatch path. Returns true unless an explicit
101
+ * `workflow_overrides` row says otherwise.
617
102
  */
618
- updateWorkflowRunScratch(id, patch) {
619
- const now = new Date().toISOString();
103
+ isWorkflowEnabled(name) {
620
104
  const row = this.db
621
- .prepare(`SELECT scratch FROM workflow_runs WHERE id = ?`)
622
- .get(id);
105
+ .prepare(`SELECT enabled FROM workflow_overrides WHERE name = ?`)
106
+ .get(name);
623
107
  if (!row)
624
- return;
625
- const current = row.scratch ? JSON.parse(row.scratch) : {};
626
- const merged = { ...current, ...patch };
627
- this.db
628
- .prepare(`UPDATE workflow_runs SET scratch = ?, updated_at = ? WHERE id = ?`)
629
- .run(JSON.stringify(merged), now, id);
630
- }
631
- /** Update the current phase and append to phase history */
632
- updateWorkflowPhase(id, phase, entry) {
633
- const now = new Date().toISOString();
634
- const row = this.db.prepare(`SELECT phase_history FROM workflow_runs WHERE id = ?`).get(id);
635
- if (!row)
636
- return;
637
- const history = JSON.parse(row.phase_history);
638
- history.push(entry);
639
- this.db.prepare(`
640
- UPDATE workflow_runs SET current_phase = ?, phase_history = ?, updated_at = ? WHERE id = ?
641
- `).run(phase, JSON.stringify(history), now, id);
642
- }
643
- /** Mark a workflow run as finished */
644
- finishWorkflowRun(id, status, error) {
645
- const now = new Date().toISOString();
646
- this.db.prepare(`
647
- UPDATE workflow_runs SET status = ?, finished_at = ?, updated_at = ?, context = CASE
648
- WHEN ? IS NOT NULL THEN json_patch(COALESCE(context, '{}'), json_object('error', ?))
649
- ELSE context
650
- END WHERE id = ?
651
- `).run(status, now, now, error ?? null, error ?? null, id);
652
- }
653
- /** Get a single workflow run by ID */
654
- getWorkflowRun(id) {
655
- const row = this.db.prepare(`SELECT * FROM workflow_runs WHERE id = ?`).get(id);
656
- return row ? this.deserializeWorkflowRun(row) : null;
108
+ return true;
109
+ return row.enabled === 1;
657
110
  }
658
- /** Find the most recent active (running or paused) workflow run for a trigger */
659
- getWorkflowRunByTrigger(triggerId) {
660
- const row = this.db.prepare(`
661
- SELECT * FROM workflow_runs
662
- WHERE trigger_id = ? AND status IN ('running', 'paused')
663
- ORDER BY started_at DESC
664
- LIMIT 1
665
- `).get(triggerId);
666
- return row ? this.deserializeWorkflowRun(row) : null;
667
- }
668
- /** List all active (running or paused) workflow runs */
669
- activeWorkflowRuns() {
670
- const rows = this.db.prepare(`
671
- SELECT * FROM workflow_runs WHERE status IN ('running', 'paused') ORDER BY started_at DESC
672
- `).all();
673
- return rows.map((r) => this.deserializeWorkflowRun(r));
674
- }
675
- /** List recent workflow runs, ordered by start time descending */
676
- recentWorkflowRuns(limit = 20) {
677
- const rows = this.db.prepare(`
678
- SELECT * FROM workflow_runs ORDER BY started_at DESC LIMIT ?
679
- `).all(limit);
680
- return rows.map((r) => this.deserializeWorkflowRun(r));
681
- }
682
- /**
683
- * List workflow runs with pagination + filters. Returns the page slice
684
- * AND the post-filter total so the dashboard knows how many remain.
685
- *
686
- * - sinceIso filters `started_at >= sinceIso` (used for the header date range).
687
- * - workflowName filters by exact `workflow_name`.
688
- * - statuses filters to one of the workflow statuses (`running`, `paused`,
689
- * `succeeded`, `failed`, `cancelled`). Used for the dashboard's "live"
690
- * filter, which maps to ['running','paused'].
691
- */
692
- listWorkflowRuns(opts = {}) {
693
- const limit = opts.limit ?? 20;
694
- const offset = opts.offset ?? 0;
695
- const where = [];
696
- const params = [];
697
- if (opts.sinceIso) {
698
- where.push("started_at >= ?");
699
- params.push(opts.sinceIso);
700
- }
701
- if (opts.workflowName) {
702
- where.push("workflow_name = ?");
703
- params.push(opts.workflowName);
704
- }
705
- if (opts.statuses && opts.statuses.length > 0) {
706
- where.push(`status IN (${opts.statuses.map(() => "?").join(",")})`);
707
- params.push(...opts.statuses);
708
- }
709
- const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
710
- const total = this.db
711
- .prepare(`SELECT COUNT(*) as c FROM workflow_runs ${whereClause}`)
712
- .get(...params).c;
713
- const rows = this.db
714
- .prepare(`SELECT * FROM workflow_runs ${whereClause} ORDER BY started_at DESC LIMIT ? OFFSET ?`)
715
- .all(...params, limit, offset);
716
- return {
717
- runs: rows.map((r) => this.deserializeWorkflowRun(r)),
718
- total,
719
- };
111
+ getWorkflowOverride(name) {
112
+ const row = this.db
113
+ .prepare(`SELECT name, enabled, updated_at, updated_by FROM workflow_overrides WHERE name = ?`)
114
+ .get(name);
115
+ return row ? this.deserializeWorkflowOverride(row) : null;
720
116
  }
721
- /** Distinct workflow_name values, sorted alphabetically. */
722
- distinctWorkflowNames() {
117
+ getAllWorkflowOverrides() {
723
118
  const rows = this.db
724
- .prepare(`SELECT DISTINCT workflow_name FROM workflow_runs ORDER BY workflow_name ASC`)
119
+ .prepare(`SELECT name, enabled, updated_at, updated_by FROM workflow_overrides`)
725
120
  .all();
726
- return rows.map((r) => r.workflow_name);
727
- }
728
- /** Cancel a workflow run */
729
- cancelWorkflowRun(id) {
730
- const now = new Date().toISOString();
731
- this.db.prepare(`
732
- UPDATE workflow_runs SET status = 'cancelled', updated_at = ?, finished_at = ? WHERE id = ?
733
- `).run(now, now, id);
734
- }
735
- /** Pause a workflow run (waiting for approval) */
736
- pauseWorkflowRun(id) {
737
- const now = new Date().toISOString();
738
- this.db.prepare(`
739
- UPDATE workflow_runs SET status = 'paused', updated_at = ? WHERE id = ?
740
- `).run(now, id);
741
- }
742
- /** Resume a paused workflow run (set back to running) */
743
- resumeWorkflowRun(id) {
744
- const now = new Date().toISOString();
745
- this.db.prepare(`
746
- UPDATE workflow_runs SET status = 'running', updated_at = ? WHERE id = ?
747
- `).run(now, id);
748
- }
749
- // ── Workflow Approvals ─────────────────────────────────────────
750
- /** Create a new pending approval request */
751
- createApproval(approval) {
752
- this.db
753
- .prepare(`
754
- INSERT INTO workflow_approvals (id, workflow_run_id, gate, summary, status, kind, requested_by, created_at)
755
- VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)
756
- `)
757
- .run(approval.id, approval.workflowRunId, approval.gate, approval.summary, approval.kind ?? "approve", approval.requestedBy ?? null, approval.createdAt);
121
+ const map = new Map();
122
+ for (const row of rows) {
123
+ const o = this.deserializeWorkflowOverride(row);
124
+ map.set(o.name, o);
125
+ }
126
+ return map;
758
127
  }
759
- /**
760
- * Resolve a reply gate: marks the approval row as approved (reply gates
761
- * don't have approve/reject semantics — any reply is a "go") and stores
762
- * the reply text as the `response`. Used by the socratic explore loop.
763
- */
764
- resolveReplyGate(id, replyText, responder) {
128
+ setWorkflowEnabled(name, enabled, updatedBy) {
765
129
  const now = new Date().toISOString();
766
130
  this.db
767
- .prepare(`UPDATE workflow_approvals
768
- SET status = 'approved',
769
- responded_by = ?,
770
- response = ?,
771
- responded_at = ?
772
- WHERE id = ? AND kind = 'reply'`)
773
- .run(responder, replyText, now, id);
774
- }
775
- /**
776
- * Find the most recent pending reply gate for a given trigger id. Used by
777
- * the router to short-circuit free-form replies on a paused socratic
778
- * explore loop without re-running classifier logic.
779
- */
780
- getPendingReplyGateByTrigger(triggerId) {
781
- const row = this.db
782
- .prepare(`SELECT wa.* FROM workflow_approvals wa
783
- JOIN workflow_runs wr ON wa.workflow_run_id = wr.id
784
- WHERE wr.trigger_id = ? AND wa.status = 'pending' AND wa.kind = 'reply'
785
- ORDER BY wa.created_at DESC
786
- LIMIT 1`)
787
- .get(triggerId);
788
- return row ? this.deserializeApproval(row) : null;
789
- }
790
- /** Get a single approval by ID */
791
- getApproval(id) {
792
- const row = this.db.prepare(`SELECT * FROM workflow_approvals WHERE id = ?`).get(id);
793
- return row ? this.deserializeApproval(row) : null;
794
- }
795
- /** Find the pending approval for a workflow run */
796
- getPendingApprovalForWorkflow(workflowRunId) {
797
- const row = this.db.prepare(`
798
- SELECT * FROM workflow_approvals WHERE workflow_run_id = ? AND status = 'pending' LIMIT 1
799
- `).get(workflowRunId);
800
- return row ? this.deserializeApproval(row) : null;
801
- }
802
- /** Find the pending approval by trigger ID (join with workflow_runs) */
803
- getPendingApprovalByTrigger(triggerId) {
804
- const row = this.db.prepare(`
805
- SELECT wa.* FROM workflow_approvals wa
806
- JOIN workflow_runs wr ON wa.workflow_run_id = wr.id
807
- WHERE wr.trigger_id = ? AND wa.status = 'pending'
808
- ORDER BY wa.created_at DESC
809
- LIMIT 1
810
- `).get(triggerId);
811
- return row ? this.deserializeApproval(row) : null;
812
- }
813
- /** List all pending approvals */
814
- listPendingApprovals() {
815
- const rows = this.db.prepare(`
816
- SELECT * FROM workflow_approvals WHERE status = 'pending' ORDER BY created_at DESC
817
- `).all();
818
- return rows.map((r) => this.deserializeApproval(r));
819
- }
820
- /** Record the response to an approval */
821
- respondToApproval(id, status, respondedBy, response) {
822
- const now = new Date().toISOString();
823
- this.db.prepare(`
824
- UPDATE workflow_approvals SET status = ?, responded_by = ?, response = ?, responded_at = ? WHERE id = ?
825
- `).run(status, respondedBy, response ?? null, now, id);
826
- }
827
- /** Update a single node's status in the workflow run's nodeStatuses map. */
828
- updateNodeStatus(workflowId, nodeName, status) {
829
- const now = new Date().toISOString();
830
- const row = this.db.prepare(`SELECT node_statuses FROM workflow_runs WHERE id = ?`).get(workflowId);
831
- if (!row)
832
- return;
833
- const statuses = row.node_statuses ? JSON.parse(row.node_statuses) : {};
834
- statuses[nodeName] = status;
835
- this.db.prepare(`UPDATE workflow_runs SET node_statuses = ?, updated_at = ? WHERE id = ?`).run(JSON.stringify(statuses), now, workflowId);
836
- }
837
- deserializeApproval(row) {
838
- return {
839
- id: row.id,
840
- workflowRunId: row.workflow_run_id,
841
- gate: row.gate,
842
- summary: row.summary,
843
- status: row.status,
844
- kind: (row.kind || "approve"),
845
- requestedBy: row.requested_by || undefined,
846
- respondedBy: row.responded_by || undefined,
847
- response: row.response || undefined,
848
- respondedAt: row.responded_at || undefined,
849
- createdAt: row.created_at,
850
- };
851
- }
852
- deserializeWorkflowRun(row) {
131
+ .prepare(`INSERT INTO workflow_overrides (name, enabled, updated_at, updated_by)
132
+ VALUES (?, ?, ?, ?)
133
+ ON CONFLICT(name) DO UPDATE SET
134
+ enabled = excluded.enabled,
135
+ updated_at = excluded.updated_at,
136
+ updated_by = excluded.updated_by`)
137
+ .run(name, enabled ? 1 : 0, now, updatedBy ?? null);
138
+ }
139
+ deserializeWorkflowOverride(row) {
853
140
  return {
854
- id: row.id,
855
- workflowName: row.workflow_name,
856
- triggerId: row.trigger_id,
857
- repo: row.repo,
858
- issueNumber: row.issue_number,
859
- currentPhase: row.current_phase,
860
- phaseHistory: JSON.parse(row.phase_history),
861
- status: row.status,
862
- context: row.context ? JSON.parse(row.context) : undefined,
863
- scratch: row.scratch ? JSON.parse(row.scratch) : undefined,
864
- nodeStatuses: row.node_statuses ? JSON.parse(row.node_statuses) : undefined,
865
- startedAt: row.started_at,
141
+ name: row.name,
142
+ enabled: row.enabled === 1,
866
143
  updatedAt: row.updated_at,
867
- finishedAt: row.finished_at,
144
+ updatedBy: row.updated_by ?? null,
868
145
  };
869
146
  }
870
147
  /** Expose the underlying Database instance (for SessionManager, etc.) */