@atolis-hq/wake 0.2.97 → 0.3.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 (442) hide show
  1. package/dist/src/activities/agent/agent-activity-definition.js +26 -0
  2. package/dist/src/activities/agent/agent-activity.js +171 -0
  3. package/dist/src/activities/agent/agent-result.js +149 -0
  4. package/dist/src/activities/contracts/activity.js +10 -0
  5. package/dist/src/activities/contracts/config.js +2 -0
  6. package/dist/src/activities/contracts/event-schema.js +302 -0
  7. package/dist/src/activities/contracts/events.js +42 -0
  8. package/dist/src/activities/contracts/identifiers.js +29 -0
  9. package/dist/src/activities/contracts/registry.js +71 -0
  10. package/dist/src/activities/contracts/streams.js +17 -0
  11. package/dist/src/activities/contracts/vocabulary.js +58 -0
  12. package/dist/src/activities/index.js +21 -0
  13. package/dist/src/activities/pr/activity-support.js +111 -0
  14. package/dist/src/activities/pr/application.js +158 -0
  15. package/dist/src/activities/pr/approve.js +85 -0
  16. package/dist/src/activities/pr/capability.js +11 -0
  17. package/dist/src/activities/pr/decision-claim.js +76 -0
  18. package/dist/src/activities/pr/event-drafts.js +66 -0
  19. package/dist/src/activities/pr/intent.js +37 -0
  20. package/dist/src/activities/pr/merge-authority-gate.js +14 -0
  21. package/dist/src/activities/pr/merge.js +110 -0
  22. package/dist/src/activities/pr/policy.js +135 -0
  23. package/dist/src/activities/pr/projection.js +130 -0
  24. package/dist/src/activities/pr/vocabulary.js +30 -0
  25. package/dist/src/activities/review/authorization.js +22 -0
  26. package/dist/src/activities/review/contracts.js +22 -0
  27. package/dist/src/activities/review/signals.js +3 -0
  28. package/dist/src/bootstrap/analytics-projection.js +26 -0
  29. package/dist/src/bootstrap/board-projection.js +337 -0
  30. package/dist/src/bootstrap/composition-root.js +382 -0
  31. package/dist/src/bootstrap/config/load-config.js +51 -0
  32. package/dist/src/bootstrap/config/root-schema.js +134 -0
  33. package/dist/src/bootstrap/external-ref.js +12 -0
  34. package/dist/src/bootstrap/fake-provider-files.js +44 -0
  35. package/dist/src/bootstrap/fake-scenarios.js +22 -0
  36. package/dist/src/bootstrap/index.js +48 -0
  37. package/dist/src/bootstrap/initialise.js +434 -0
  38. package/dist/src/bootstrap/paths.js +15 -0
  39. package/dist/src/bootstrap/projection-runtime.js +27 -0
  40. package/dist/src/bootstrap/runner-quota-reporter.js +21 -0
  41. package/dist/src/bootstrap/runner-registry.js +30 -0
  42. package/dist/src/bootstrap/schedule-checkpoint-store.js +49 -0
  43. package/dist/src/bootstrap/self-update-application.js +195 -0
  44. package/dist/src/bootstrap/self-update-failure-log.js +33 -0
  45. package/dist/src/bootstrap/source-update-port.js +47 -0
  46. package/dist/src/bootstrap/status-publish-activity.js +42 -0
  47. package/dist/src/bootstrap/surface-api-applications.js +325 -0
  48. package/dist/src/bootstrap/surface-api-execution-applications.js +95 -0
  49. package/dist/src/bootstrap/surface-api-metadata.js +12 -0
  50. package/dist/src/bootstrap/surface-api-projection-pages.js +22 -0
  51. package/dist/src/bootstrap/surface-api-run-context.js +5 -0
  52. package/dist/src/bootstrap/surface-api-transcripts.js +57 -0
  53. package/dist/src/bootstrap/surface-api-work-applications.js +199 -0
  54. package/dist/src/bootstrap/surface-applications.js +7 -0
  55. package/dist/src/bootstrap/surface-cli-applications.js +701 -0
  56. package/dist/src/bootstrap/update-ledger.js +61 -0
  57. package/dist/src/bootstrap/update-maintenance-lease.js +176 -0
  58. package/dist/src/{version.js → bootstrap/version.js} +35 -51
  59. package/dist/src/control-plane/application/advance-once.js +184 -0
  60. package/dist/src/control-plane/application/control-plane-projection.js +42 -0
  61. package/dist/src/control-plane/application/control-plane-service.js +42 -0
  62. package/dist/src/control-plane/application/intake-pipeline.js +29 -0
  63. package/dist/src/control-plane/application/runner-control-service.js +66 -0
  64. package/dist/src/control-plane/application/runner-pipeline.js +57 -0
  65. package/dist/src/control-plane/application/schedule-service.js +47 -0
  66. package/dist/src/control-plane/application/work-cancellation-policy.js +31 -0
  67. package/dist/src/control-plane/contracts/commands.js +10 -0
  68. package/dist/src/control-plane/contracts/config.js +22 -0
  69. package/dist/src/control-plane/contracts/events.js +81 -0
  70. package/dist/src/control-plane/contracts/streams.js +5 -0
  71. package/dist/src/control-plane/domain/dispatch-policy.js +15 -0
  72. package/dist/src/control-plane/domain/quota-policy.js +26 -0
  73. package/dist/src/control-plane/domain/runner-quota.js +35 -0
  74. package/dist/src/control-plane/domain/schedule-policy.js +46 -0
  75. package/dist/src/control-plane/index.js +20 -0
  76. package/dist/src/control-plane/infrastructure/intake-host.js +24 -0
  77. package/dist/src/control-plane/infrastructure/resident-host.js +45 -0
  78. package/dist/src/control-plane/infrastructure/tick-host.js +35 -0
  79. package/dist/src/execution/application/activation-claim.js +50 -0
  80. package/dist/src/execution/application/active-run-cancellation.js +14 -0
  81. package/dist/src/execution/application/execution-activity.js +97 -0
  82. package/dist/src/execution/application/execution-projection.js +17 -0
  83. package/dist/src/execution/application/execution-service.js +372 -0
  84. package/dist/src/execution/application/execution-validation.js +26 -0
  85. package/dist/src/execution/application/recovery-service.js +212 -0
  86. package/dist/src/execution/application/run-lifecycle.js +115 -0
  87. package/dist/src/execution/application/run-liveness-service.js +80 -0
  88. package/dist/src/execution/application/run-repository.js +27 -0
  89. package/dist/src/execution/contracts/commands.js +1 -0
  90. package/dist/src/execution/contracts/config.js +35 -0
  91. package/dist/src/execution/contracts/control-plane.js +1 -0
  92. package/dist/src/execution/contracts/event-factory.js +53 -0
  93. package/dist/src/execution/contracts/events.js +274 -0
  94. package/dist/src/execution/contracts/identifiers.js +5 -0
  95. package/dist/src/execution/contracts/liveness.js +1 -0
  96. package/dist/src/execution/contracts/results.js +1 -0
  97. package/dist/src/execution/contracts/runner.js +15 -0
  98. package/dist/src/execution/contracts/streams.js +10 -0
  99. package/dist/src/execution/contracts/views.js +1 -0
  100. package/dist/src/execution/contracts/vocabulary.js +43 -0
  101. package/dist/src/execution/contracts/workspace.js +1 -0
  102. package/dist/src/execution/domain/run-result.js +34 -0
  103. package/dist/src/execution/domain/run.js +142 -0
  104. package/dist/src/execution/index.js +31 -0
  105. package/dist/src/execution/infrastructure/agent-runner-adapter.js +114 -0
  106. package/dist/src/execution/infrastructure/process-execution.js +29 -0
  107. package/dist/src/execution/infrastructure/prompt-templates.js +42 -0
  108. package/dist/src/execution/infrastructure/runners/claude.js +120 -0
  109. package/dist/src/execution/infrastructure/runners/codex.js +143 -0
  110. package/dist/src/execution/infrastructure/runners/command.js +4 -0
  111. package/dist/src/execution/infrastructure/runners/cursor.js +69 -0
  112. package/dist/src/execution/infrastructure/runners/fake-scenarios.js +99 -0
  113. package/dist/src/execution/infrastructure/runners/fake.js +65 -0
  114. package/dist/src/execution/infrastructure/runners/registry.js +26 -0
  115. package/dist/src/execution/infrastructure/transcript-store.js +223 -0
  116. package/dist/src/execution/infrastructure/transcripts.js +11 -0
  117. package/dist/src/execution/infrastructure/workspace/fake-workspace.js +19 -0
  118. package/dist/src/execution/infrastructure/workspace/git-workspace.js +241 -0
  119. package/dist/src/integrations/application/agent-run-publication-reactor.js +111 -0
  120. package/dist/src/integrations/application/artifact-registration-reactor.js +159 -0
  121. package/dist/src/integrations/application/poll-service.js +25 -0
  122. package/dist/src/integrations/application/terminal-agent-run-report.js +40 -0
  123. package/dist/src/integrations/application/work-admission.js +31 -0
  124. package/dist/src/integrations/application/work-conclusion.js +19 -0
  125. package/dist/src/integrations/contracts/artifact-events.js +60 -0
  126. package/dist/src/integrations/contracts/artifact-vocabulary.js +9 -0
  127. package/dist/src/integrations/contracts/config.js +14 -0
  128. package/dist/src/integrations/contracts/identifiers.js +5 -0
  129. package/dist/src/integrations/contracts/intake-rules.js +14 -0
  130. package/dist/src/integrations/contracts/intake.js +1 -0
  131. package/dist/src/integrations/contracts/outcome-vocabulary.js +7 -0
  132. package/dist/src/integrations/contracts/provider.js +37 -0
  133. package/dist/src/integrations/contracts/streams.js +10 -0
  134. package/dist/src/integrations/delivery/application/delivery-outcome-reactor.js +75 -0
  135. package/dist/src/integrations/delivery/application/delivery-projector.js +165 -0
  136. package/dist/src/integrations/delivery/application/delivery-service.js +173 -0
  137. package/dist/src/integrations/delivery/contracts/commands.js +1 -0
  138. package/dist/src/integrations/delivery/contracts/config.js +1 -0
  139. package/dist/src/integrations/delivery/contracts/event-factory.js +18 -0
  140. package/dist/src/integrations/delivery/contracts/events.js +86 -0
  141. package/dist/src/integrations/delivery/contracts/intents.js +89 -0
  142. package/dist/src/integrations/delivery/contracts/views.js +1 -0
  143. package/dist/src/integrations/delivery/contracts/vocabulary.js +22 -0
  144. package/dist/src/integrations/fake/durable-delivery-provider.js +48 -0
  145. package/dist/src/integrations/fake/external-sink.js +9 -0
  146. package/dist/src/integrations/fake/external-source.js +44 -0
  147. package/dist/src/integrations/fake/inbound-translator.js +130 -0
  148. package/dist/src/integrations/fake/provider.js +95 -0
  149. package/dist/src/integrations/github/application/agent-context-reader.js +54 -0
  150. package/dist/src/integrations/github/application/agent-run-comment.js +94 -0
  151. package/dist/src/integrations/github/application/comment-history-reader.js +43 -0
  152. package/dist/src/integrations/github/application/inbound-context.js +10 -0
  153. package/dist/src/integrations/github/application/inbound-review-signals.js +159 -0
  154. package/dist/src/integrations/github/application/inbound-translator.js +231 -0
  155. package/dist/src/integrations/github/application/inbound-watch-gate-signals.js +84 -0
  156. package/dist/src/integrations/github/application/intake-policy.js +23 -0
  157. package/dist/src/integrations/github/application/outbound-translator.js +47 -0
  158. package/dist/src/integrations/github/application/poll-service.js +29 -0
  159. package/dist/src/integrations/github/application/pull-request-translation.js +10 -0
  160. package/dist/src/integrations/github/application/resource-links.js +8 -0
  161. package/dist/src/integrations/github/application/review-command-translator.js +16 -0
  162. package/dist/src/integrations/github/application/wake-labels.js +95 -0
  163. package/dist/src/integrations/github/contracts/config.js +47 -0
  164. package/dist/src/integrations/github/contracts/events.js +127 -0
  165. package/dist/src/integrations/github/contracts/external-key.js +19 -0
  166. package/dist/src/integrations/github/contracts/payloads.js +9 -0
  167. package/dist/src/integrations/github/contracts/vocabulary.js +55 -0
  168. package/dist/src/integrations/github/index.js +31 -0
  169. package/dist/src/integrations/github/infrastructure/client-reads.js +214 -0
  170. package/dist/src/integrations/github/infrastructure/client.js +132 -0
  171. package/dist/src/integrations/github/infrastructure/content-fingerprint.js +10 -0
  172. package/dist/src/integrations/github/infrastructure/delivery.js +31 -0
  173. package/dist/src/integrations/github/infrastructure/etag-cache.js +56 -0
  174. package/dist/src/integrations/github/infrastructure/gh-auth.js +19 -0
  175. package/dist/src/integrations/github/infrastructure/issue-source.js +109 -0
  176. package/dist/src/integrations/github/infrastructure/pr-source.js +186 -0
  177. package/dist/src/integrations/github/infrastructure/review-source.js +70 -0
  178. package/dist/src/integrations/github/infrastructure/source.js +108 -0
  179. package/dist/src/integrations/github/provider.js +96 -0
  180. package/dist/src/integrations/index.js +30 -0
  181. package/dist/src/kernel/contracts/checkpoint-store.js +1 -0
  182. package/dist/src/kernel/contracts/clock.js +1 -0
  183. package/dist/src/kernel/contracts/commands.js +1 -0
  184. package/dist/src/kernel/contracts/event-journal.js +2 -0
  185. package/dist/src/kernel/contracts/event-schema.js +54 -0
  186. package/dist/src/kernel/contracts/events.js +11 -0
  187. package/dist/src/kernel/contracts/id-generator.js +1 -0
  188. package/dist/src/kernel/contracts/identifiers.js +8 -0
  189. package/dist/src/kernel/contracts/projection-store.js +1 -0
  190. package/dist/src/kernel/contracts/relations.js +1 -0
  191. package/dist/src/kernel/contracts/schema.js +17 -0
  192. package/dist/src/kernel/contracts/vocabulary.js +6 -0
  193. package/dist/src/kernel/domain/event-envelope.js +29 -0
  194. package/dist/src/kernel/domain/match-mode.js +11 -0
  195. package/dist/src/kernel/domain/relation.js +10 -0
  196. package/dist/src/kernel/index.js +17 -0
  197. package/dist/src/{lib/clock.js → kernel/infrastructure/system-clock.js} +3 -3
  198. package/dist/src/kernel/infrastructure/ulid-id-generator.js +9 -0
  199. package/dist/src/main.js +137 -1030
  200. package/dist/src/orchestration/application/accept-activity-outcome.js +25 -0
  201. package/dist/src/orchestration/application/accept-signal.js +35 -0
  202. package/dist/src/orchestration/application/advance-workflow.js +160 -0
  203. package/dist/src/orchestration/application/coordination-claims.js +107 -0
  204. package/dist/src/orchestration/application/group-budget-recorder.js +43 -0
  205. package/dist/src/orchestration/application/orchestration-projection.js +20 -0
  206. package/dist/src/orchestration/application/orchestration-repository.js +36 -0
  207. package/dist/src/orchestration/application/orchestration-service.js +82 -0
  208. package/dist/src/orchestration/application/request-child.js +118 -0
  209. package/dist/src/orchestration/application/signal-reactor.js +17 -0
  210. package/dist/src/orchestration/application/start-workflow.js +51 -0
  211. package/dist/src/orchestration/application/watch-reactor.js +82 -0
  212. package/dist/src/orchestration/contracts/activity-outcome.js +18 -0
  213. package/dist/src/orchestration/contracts/commands.js +1 -0
  214. package/dist/src/orchestration/contracts/config.js +128 -0
  215. package/dist/src/orchestration/contracts/event-decoder.js +263 -0
  216. package/dist/src/orchestration/contracts/events.js +41 -0
  217. package/dist/src/orchestration/contracts/identifiers.js +35 -0
  218. package/dist/src/orchestration/contracts/streams.js +38 -0
  219. package/dist/src/orchestration/contracts/views.js +1 -0
  220. package/dist/src/orchestration/contracts/vocabulary.js +30 -0
  221. package/dist/src/orchestration/domain/activation-policy.js +55 -0
  222. package/dist/src/orchestration/domain/approval-defaults.js +17 -0
  223. package/dist/src/orchestration/domain/child-policy.js +63 -0
  224. package/dist/src/orchestration/domain/compiler.js +162 -0
  225. package/dist/src/orchestration/domain/coordination-events.js +25 -0
  226. package/dist/src/orchestration/domain/decision-events.js +47 -0
  227. package/dist/src/orchestration/domain/interpreter.js +49 -0
  228. package/dist/src/orchestration/domain/operator-retry-policy.js +34 -0
  229. package/dist/src/orchestration/domain/retry-policy.js +26 -0
  230. package/dist/src/orchestration/domain/signal-policy.js +120 -0
  231. package/dist/src/orchestration/domain/supplemental-policy.js +61 -0
  232. package/dist/src/orchestration/domain/transition.js +55 -0
  233. package/dist/src/orchestration/domain/workflow-graph.js +41 -0
  234. package/dist/src/orchestration/domain/workflow-instance-events.js +236 -0
  235. package/dist/src/orchestration/domain/workflow-instance.js +41 -0
  236. package/dist/src/orchestration/domain/workflow-selector.js +21 -0
  237. package/dist/src/orchestration/index.js +19 -0
  238. package/dist/src/persistence/application/projection-runner.js +79 -0
  239. package/dist/src/persistence/filesystem/file-checkpoint-store.js +35 -0
  240. package/dist/src/persistence/filesystem/file-event-journal.js +143 -0
  241. package/dist/src/persistence/filesystem/file-lock.js +91 -0
  242. package/dist/src/persistence/filesystem/file-projection-store.js +64 -0
  243. package/dist/src/persistence/index.js +8 -0
  244. package/dist/src/persistence/memory/in-memory-checkpoint-store.js +14 -0
  245. package/dist/src/persistence/memory/in-memory-event-journal.js +88 -0
  246. package/dist/src/persistence/memory/in-memory-projection-store.js +17 -0
  247. package/dist/src/resources/application/lookup-projections.js +48 -0
  248. package/dist/src/resources/application/resource-lookup.js +16 -0
  249. package/dist/src/resources/application/resource-projections.js +81 -0
  250. package/dist/src/resources/application/resource-repository.js +18 -0
  251. package/dist/src/resources/application/resource-service.js +83 -0
  252. package/dist/src/resources/contracts/commands.js +1 -0
  253. package/dist/src/resources/contracts/config.js +2 -0
  254. package/dist/src/resources/contracts/events.js +83 -0
  255. package/dist/src/resources/contracts/identifiers.js +15 -0
  256. package/dist/src/resources/contracts/streams.js +6 -0
  257. package/dist/src/resources/contracts/views.js +1 -0
  258. package/dist/src/resources/contracts/vocabulary.js +67 -0
  259. package/dist/src/resources/domain/correlation.js +8 -0
  260. package/dist/src/resources/domain/resource.js +58 -0
  261. package/dist/src/resources/index.js +14 -0
  262. package/dist/src/surfaces/api/contracts/activities.js +1 -0
  263. package/dist/src/surfaces/api/contracts/board.js +2 -0
  264. package/dist/src/surfaces/api/contracts/common.js +46 -0
  265. package/dist/src/surfaces/api/contracts/control-plane.js +5 -0
  266. package/dist/src/surfaces/api/contracts/events.js +1 -0
  267. package/dist/src/surfaces/api/contracts/execution.js +1 -0
  268. package/dist/src/surfaces/api/contracts/index.js +13 -0
  269. package/dist/src/surfaces/api/contracts/observability.js +1 -0
  270. package/dist/src/surfaces/api/contracts/orchestration.js +1 -0
  271. package/dist/src/surfaces/api/contracts/resources.js +1 -0
  272. package/dist/src/surfaces/api/contracts/status.js +1 -0
  273. package/dist/src/surfaces/api/contracts/system.js +1 -0
  274. package/dist/src/surfaces/api/contracts/transport-values.js +39 -0
  275. package/dist/src/surfaces/api/contracts/work.js +17 -0
  276. package/dist/src/surfaces/api/http-server.js +121 -0
  277. package/dist/src/surfaces/api/presenters/activities.js +1 -0
  278. package/dist/src/surfaces/api/presenters/board.js +3 -0
  279. package/dist/src/surfaces/api/presenters/control-plane.js +1 -0
  280. package/dist/src/surfaces/api/presenters/events.js +1 -0
  281. package/dist/src/surfaces/api/presenters/execution.js +59 -0
  282. package/dist/src/surfaces/api/presenters/observability.js +1 -0
  283. package/dist/src/surfaces/api/presenters/orchestration.js +19 -0
  284. package/dist/src/surfaces/api/presenters/resources.js +15 -0
  285. package/dist/src/surfaces/api/presenters/status.js +3 -0
  286. package/dist/src/surfaces/api/presenters/system.js +14 -0
  287. package/dist/src/surfaces/api/presenters/work.js +16 -0
  288. package/dist/src/surfaces/api/problem-details.js +9 -0
  289. package/dist/src/surfaces/api/router.js +13 -0
  290. package/dist/src/surfaces/api/routes/activities.js +1 -0
  291. package/dist/src/surfaces/api/routes/applications.js +1 -0
  292. package/dist/src/surfaces/api/routes/commands.js +82 -0
  293. package/dist/src/surfaces/api/routes/control-plane.js +6 -0
  294. package/dist/src/surfaces/api/routes/events.js +1 -0
  295. package/dist/src/surfaces/api/routes/execution.js +8 -0
  296. package/dist/src/surfaces/api/routes/index.js +18 -0
  297. package/dist/src/surfaces/api/routes/observability.js +1 -0
  298. package/dist/src/surfaces/api/routes/orchestration.js +1 -0
  299. package/dist/src/surfaces/api/routes/pagination.js +78 -0
  300. package/dist/src/surfaces/api/routes/read.js +135 -0
  301. package/dist/src/surfaces/api/routes/resources.js +1 -0
  302. package/dist/src/surfaces/api/routes/responses.js +67 -0
  303. package/dist/src/surfaces/api/routes/system.js +1 -0
  304. package/dist/src/surfaces/api/routes/work.js +9 -0
  305. package/dist/src/surfaces/cli/commands/api.js +1 -0
  306. package/dist/src/surfaces/cli/commands/audit.js +10 -0
  307. package/dist/src/surfaces/cli/commands/correlate.js +2 -0
  308. package/dist/src/surfaces/cli/commands/doctor.js +6 -0
  309. package/dist/src/surfaces/cli/commands/init.js +39 -0
  310. package/dist/src/surfaces/cli/commands/sandbox-entrypoint.js +33 -0
  311. package/dist/src/surfaces/cli/commands/sandbox-setup.js +29 -0
  312. package/dist/src/surfaces/cli/commands/sandbox.js +105 -0
  313. package/dist/src/surfaces/cli/commands/self-update.js +38 -0
  314. package/dist/src/surfaces/cli/commands/smoke.js +16 -0
  315. package/dist/src/surfaces/cli/commands/start.js +1 -0
  316. package/dist/src/surfaces/cli/commands/stop.js +13 -0
  317. package/dist/src/surfaces/cli/commands/tick.js +1 -0
  318. package/dist/src/surfaces/cli/commands/ui.js +2 -0
  319. package/dist/src/surfaces/cli/commands/validate-state.js +5 -0
  320. package/dist/src/surfaces/cli/infrastructure/docker-cli.js +221 -0
  321. package/dist/src/surfaces/cli/infrastructure/process-log.js +86 -0
  322. package/dist/src/surfaces/cli/main.js +140 -0
  323. package/dist/src/surfaces/cli/usage.js +33 -0
  324. package/dist/src/surfaces/contracts/config.js +34 -0
  325. package/dist/src/surfaces/index.js +47 -0
  326. package/dist/src/surfaces/web-assets/assets/index-Bfn4FwZG.css +1 -0
  327. package/dist/src/surfaces/web-assets/assets/index-ipB6JPGU.js +1090 -0
  328. package/dist/src/surfaces/web-assets/index.html +13 -0
  329. package/dist/src/surfaces/web-host/asset-source.js +1 -0
  330. package/dist/src/surfaces/web-host/packaged-assets.js +44 -0
  331. package/dist/src/work/application/work-projection.js +59 -0
  332. package/dist/src/work/application/work-repository.js +18 -0
  333. package/dist/src/work/application/work-service.js +117 -0
  334. package/dist/src/work/contracts/commands.js +1 -0
  335. package/dist/src/work/contracts/config.js +2 -0
  336. package/dist/src/work/contracts/events.js +103 -0
  337. package/dist/src/work/contracts/identifiers.js +5 -0
  338. package/dist/src/work/contracts/streams.js +6 -0
  339. package/dist/src/work/contracts/views.js +1 -0
  340. package/dist/src/work/contracts/vocabulary.js +6 -0
  341. package/dist/src/work/domain/work-item.js +89 -0
  342. package/dist/src/work/index.js +11 -0
  343. package/docker/Dockerfile +10 -6
  344. package/docker/Dockerfile.packaged +3 -3
  345. package/package.json +29 -4
  346. package/prompts/implement.md +2 -2
  347. package/templates/SETUP.md +7 -7
  348. package/dist/src/adapters/claude/claude-runner.js +0 -529
  349. package/dist/src/adapters/codex/codex-runner.js +0 -503
  350. package/dist/src/adapters/cursor/cursor-runner.js +0 -418
  351. package/dist/src/adapters/docker/docker-cli.js +0 -150
  352. package/dist/src/adapters/fake/fake-artifact-verifier.js +0 -14
  353. package/dist/src/adapters/fake/fake-github-pull-request-activity-source.js +0 -77
  354. package/dist/src/adapters/fake/fake-resource-index.js +0 -21
  355. package/dist/src/adapters/fake/fake-runner.js +0 -77
  356. package/dist/src/adapters/fake/fake-ticketing-system.js +0 -185
  357. package/dist/src/adapters/fake/fake-workspace-manager.js +0 -81
  358. package/dist/src/adapters/fs/resource-index.js +0 -126
  359. package/dist/src/adapters/fs/self-update-ledger.js +0 -17
  360. package/dist/src/adapters/fs/state-store.js +0 -775
  361. package/dist/src/adapters/git/git-workspace-manager.js +0 -289
  362. package/dist/src/adapters/github/github-artifact-verifier.js +0 -38
  363. package/dist/src/adapters/github/github-auth.js +0 -16
  364. package/dist/src/adapters/github/github-client.js +0 -249
  365. package/dist/src/adapters/github/github-etag-cache.js +0 -57
  366. package/dist/src/adapters/github/github-issues-work-source.js +0 -692
  367. package/dist/src/adapters/github/github-pull-request-activity-source.js +0 -584
  368. package/dist/src/adapters/github/github-pull-request-merge-actor.js +0 -45
  369. package/dist/src/adapters/http/ui-assets.js +0 -1188
  370. package/dist/src/adapters/http/ui-data.js +0 -1107
  371. package/dist/src/adapters/http/ui-server.js +0 -458
  372. package/dist/src/adapters/runner/cli-command.js +0 -73
  373. package/dist/src/adapters/runner/prompt-templates.js +0 -76
  374. package/dist/src/adapters/runner/runner-cli-adapter.js +0 -112
  375. package/dist/src/adapters/runner/runner-registry.js +0 -139
  376. package/dist/src/adapters/runner/runtime-events.js +0 -32
  377. package/dist/src/adapters/runner/stage-prompt.js +0 -268
  378. package/dist/src/adapters/runner/transcripts.js +0 -25
  379. package/dist/src/cli/audit-command.js +0 -36
  380. package/dist/src/cli/correlate-command.js +0 -62
  381. package/dist/src/cli/doctor-command.js +0 -48
  382. package/dist/src/cli/init-command.js +0 -18
  383. package/dist/src/cli/sandbox-command.js +0 -258
  384. package/dist/src/cli/sandbox-entrypoint-command.js +0 -88
  385. package/dist/src/cli/sandbox-exec-logging.js +0 -7
  386. package/dist/src/cli/sandbox-resume.js +0 -79
  387. package/dist/src/cli/sandbox-setup-command.js +0 -31
  388. package/dist/src/cli/scaffold-assets.js +0 -100
  389. package/dist/src/cli/self-update-command.js +0 -198
  390. package/dist/src/cli/startup-preflight.js +0 -134
  391. package/dist/src/cli/stop-command.js +0 -46
  392. package/dist/src/cli/ui-command.js +0 -27
  393. package/dist/src/config/defaults.js +0 -10
  394. package/dist/src/config/discover-config-files.js +0 -11
  395. package/dist/src/config/load-config.js +0 -69
  396. package/dist/src/config/split-config.js +0 -14
  397. package/dist/src/core/active-run-recovery.js +0 -44
  398. package/dist/src/core/approval-intents.js +0 -16
  399. package/dist/src/core/audit-events.js +0 -82
  400. package/dist/src/core/control-plane.js +0 -192
  401. package/dist/src/core/event-builders.js +0 -153
  402. package/dist/src/core/event-resolver.js +0 -249
  403. package/dist/src/core/lifecycle-service.js +0 -8
  404. package/dist/src/core/live-execution.js +0 -81
  405. package/dist/src/core/outbox.js +0 -198
  406. package/dist/src/core/policy-engine.js +0 -389
  407. package/dist/src/core/projection-updater.js +0 -693
  408. package/dist/src/core/quota-backoff.js +0 -69
  409. package/dist/src/core/run-lease.js +0 -38
  410. package/dist/src/core/scheduled-workflow-source.js +0 -199
  411. package/dist/src/core/sink-router.js +0 -107
  412. package/dist/src/core/stale-run-reconciler.js +0 -352
  413. package/dist/src/core/tick-runner.js +0 -2442
  414. package/dist/src/core/workspace-cleanup.js +0 -117
  415. package/dist/src/domain/branch-naming.js +0 -14
  416. package/dist/src/domain/custom-commands.js +0 -58
  417. package/dist/src/domain/event-types.js +0 -158
  418. package/dist/src/domain/manual-labels.js +0 -1
  419. package/dist/src/domain/resource-uri.js +0 -38
  420. package/dist/src/domain/runner-routing.js +0 -127
  421. package/dist/src/domain/runtime-events.js +0 -36
  422. package/dist/src/domain/schema.js +0 -1233
  423. package/dist/src/domain/sources.js +0 -16
  424. package/dist/src/domain/stages.js +0 -36
  425. package/dist/src/domain/work-item-labels.js +0 -48
  426. package/dist/src/domain/work-item-lifecycle.js +0 -10
  427. package/dist/src/domain/work-item-status.js +0 -33
  428. package/dist/src/domain/workflows.js +0 -159
  429. package/dist/src/lib/deep-merge.js +0 -14
  430. package/dist/src/lib/detached-process-logging.js +0 -46
  431. package/dist/src/lib/event-log.js +0 -21
  432. package/dist/src/lib/format.js +0 -41
  433. package/dist/src/lib/json-file.js +0 -23
  434. package/dist/src/lib/lock.js +0 -181
  435. package/dist/src/lib/log-rotation.js +0 -42
  436. package/dist/src/lib/paths.js +0 -52
  437. package/dist/src/lib/process-identity.js +0 -54
  438. package/dist/src/lib/state-health.js +0 -30
  439. package/dist/src/lib/work-id.js +0 -19
  440. package/dist/src/lib/yaml-file.js +0 -20
  441. /package/dist/src/{core → activities/pr}/contracts.js +0 -0
  442. /package/dist/src/{domain/types.js → control-plane/contracts/views.js} +0 -0
@@ -1,1107 +0,0 @@
1
- import { readFile, readdir, stat } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { buildResourceUri } from '../../domain/resource-uri.js';
4
- import { isTerminalStage } from '../../domain/stages.js';
5
- import { isWorkItemDeleted, isWorkItemFrozen } from '../../domain/work-item-lifecycle.js';
6
- import { universalQueueStage, workflowForProjection, workflowNameForProjection, } from '../../domain/workflows.js';
7
- import { readFileLockStatus } from '../../lib/lock.js';
8
- async function readLockInfo(lockFile, now, processInspector) {
9
- const status = await readFileLockStatus(lockFile, {
10
- now,
11
- ...(processInspector === undefined ? {} : { processInspector }),
12
- });
13
- return {
14
- present: status.present,
15
- ...(status.metadata === undefined ? {} : { pid: status.metadata.pid }),
16
- ...(status.metadata === undefined ? {} : { acquiredAt: status.metadata.acquiredAt }),
17
- ...(status.ageMs === undefined ? {} : { ageMs: status.ageMs }),
18
- pidAlive: status.active,
19
- ...(status.staleReason === undefined ? {} : { staleReason: status.staleReason }),
20
- };
21
- }
22
- /**
23
- * Reproduces the sentinel-driven part of policy eligibility for display purposes only.
24
- * It never decides anything the tick doesn't independently decide — this is a read model.
25
- */
26
- function deriveCondition(item, lastRun, config) {
27
- if (isWorkItemFrozen(item)) {
28
- return { condition: 'needs-human', reason: 'work item frozen' };
29
- }
30
- const stage = item.wake.stage;
31
- if (isTerminalStage(stage) || item.issue.state === 'closed') {
32
- return { condition: 'finished', reason: 'terminal stage' };
33
- }
34
- if (lastRun?.status === 'running') {
35
- return { condition: 'active', reason: 'run in flight' };
36
- }
37
- if (lastRun?.status === 'blocked' ||
38
- lastRun?.status === 'awaiting-approval' ||
39
- lastRun?.sentinel === 'BLOCKED' ||
40
- // A run record can never be written with this sentinel value again
41
- // (ADR 0002), but a pre-existing run record on disk still can be —
42
- // legacyTolerantRunnerSentinelSchema keeps it readable, so this branch
43
- // stays to classify it correctly rather than falling through to 'ready'.
44
- lastRun?.sentinel === 'AWAITING_APPROVAL') {
45
- return { condition: 'needs-human', reason: `sentinel ${lastRun?.sentinel ?? stage}` };
46
- }
47
- const workflow = workflowForProjection(item, config);
48
- const hasRoute = stage === universalQueueStage ? workflow !== null : workflow?.stages[stage] !== undefined;
49
- if (!hasRoute) {
50
- return { condition: 'error', reason: `no route configured for stage "${stage}"` };
51
- }
52
- if (lastRun?.sentinel === 'FAILED') {
53
- return { condition: 'error', reason: 'last run failed; awaiting operator/retry policy' };
54
- }
55
- if (item.issue.labels.includes('wake:scheduled-workflow')) {
56
- return { condition: 'scheduled', reason: 'scheduled workflow awaiting next run' };
57
- }
58
- return { condition: 'ready', reason: 'has a route and no blocking condition' };
59
- }
60
- function timeInStageMs(item, now) {
61
- const lastChange = item.wake.stageHistory.at(-1)?.changedAt ?? item.wake.syncedAt;
62
- return now.getTime() - Date.parse(lastChange);
63
- }
64
- function displayStatusForCard(item) {
65
- return item.context.status ?? 'queued';
66
- }
67
- function activeChildRunsForItem(item, runs, now) {
68
- return runs
69
- .filter((run) => run.workItemKey === item.workItemKey &&
70
- run.status === 'running' &&
71
- run.runId !== item.wake.lastRunId)
72
- .sort((left, right) => left.startedAt.localeCompare(right.startedAt))
73
- .map((run) => ({
74
- runId: run.runId,
75
- action: run.action,
76
- status: run.status,
77
- startedAt: run.startedAt,
78
- ageMs: now.getTime() - Date.parse(run.startedAt),
79
- isWatcher: run.metadata?.watcher === true,
80
- ...(run.routing?.runnerName === undefined ? {} : { runnerName: run.routing.runnerName }),
81
- ...(run.routing?.runnerKind === undefined ? {} : { runnerKind: run.routing.runnerKind }),
82
- ...(run.routing?.tier === undefined ? {} : { tier: run.routing.tier }),
83
- }));
84
- }
85
- export async function buildBoard(input) {
86
- const items = await input.stateStore.listIssueStates({
87
- archiveFreshnessDays: input.config.ui.archiveFreshnessDays,
88
- now: input.now,
89
- });
90
- // One bulk summarized scan shared by every item, instead of readRunRecord()
91
- // per item - deriveCondition only reads status/sentinel, so this doesn't
92
- // need full records, and doing a separate lookup (with its own fallback
93
- // scan when a flat run file is missing) per item made board loads scale
94
- // with items x run-history size instead of just run-history size.
95
- const runs = await input.stateStore.listRunRecordSummaries();
96
- const runsById = new Map(runs.map((run) => [run.runId, run]));
97
- const runTotalsByItem = new Map();
98
- for (const run of runs) {
99
- const existing = runTotalsByItem.get(run.workItemKey) ?? {
100
- totalRuns: 0,
101
- totalTokens: 0,
102
- totalCostUsd: 0,
103
- };
104
- existing.totalRuns += 1;
105
- const usage = run.tokenUsage;
106
- if (usage !== undefined) {
107
- existing.totalTokens +=
108
- (usage.inputTokens ?? 0) +
109
- (usage.outputTokens ?? 0) +
110
- (usage.cacheCreationInputTokens ?? 0) +
111
- (usage.cacheReadInputTokens ?? 0);
112
- existing.totalCostUsd += usage.costUsd ?? 0;
113
- }
114
- runTotalsByItem.set(run.workItemKey, existing);
115
- }
116
- return items
117
- .filter((item) => !isWorkItemDeleted(item))
118
- .map((item) => {
119
- const lastRun = item.wake.lastRunId === undefined ? null : (runsById.get(item.wake.lastRunId) ?? null);
120
- const { condition, reason } = deriveCondition(item, lastRun, input.config);
121
- const isFrozen = isWorkItemFrozen(item);
122
- const activeChildRuns = activeChildRunsForItem(item, runs, input.now);
123
- const activeMainRun = lastRun?.status === 'running'
124
- ? {
125
- runId: lastRun.runId,
126
- action: lastRun.action,
127
- startedAt: lastRun.startedAt,
128
- ageMs: input.now.getTime() - Date.parse(lastRun.startedAt),
129
- ...(lastRun.routing?.runnerName === undefined
130
- ? {}
131
- : { runnerName: lastRun.routing.runnerName }),
132
- ...(lastRun.routing?.tier === undefined ? {} : { tier: lastRun.routing.tier }),
133
- }
134
- : undefined;
135
- const runTotals = runTotalsByItem.get(item.workItemKey) ?? {
136
- totalRuns: 0,
137
- totalTokens: 0,
138
- totalCostUsd: 0,
139
- };
140
- return {
141
- workItemKey: item.workItemKey,
142
- repo: item.issue.repo,
143
- number: item.issue.number,
144
- title: item.issue.title,
145
- url: item.issue.url,
146
- stage: item.wake.stage,
147
- workflow: workflowNameForProjection(item, input.config),
148
- labels: item.issue.labels,
149
- isFrozen,
150
- condition,
151
- conditionReason: reason,
152
- timeInStageMs: timeInStageMs(item, input.now),
153
- totalRuns: runTotals.totalRuns,
154
- totalTokens: runTotals.totalTokens,
155
- totalCostUsd: runTotals.totalCostUsd,
156
- displayStatus: displayStatusForCard(item),
157
- lastRunAction: lastRun?.action,
158
- lastRunSentinel: lastRun?.sentinel,
159
- lastRunStatus: lastRun?.status,
160
- ...(activeMainRun === undefined ? {} : { activeMainRun }),
161
- ...(activeChildRuns.length === 0 ? {} : { activeChildRuns }),
162
- sessionId: item.wake.sessionId,
163
- workspacePath: item.wake.workspacePath,
164
- };
165
- });
166
- }
167
- export async function buildStatus(input) {
168
- const today = input.now.toISOString().slice(0, 10);
169
- const [ledger, paused, recentEvents, todaysRuns, recentRuns, board] = await Promise.all([
170
- input.stateStore.readLedger(),
171
- input.stateStore.isPaused(),
172
- input.stateStore.listRecentEventEnvelopes({ limit: 1 }),
173
- input.stateStore.listRunRecordSummariesForDate(today),
174
- input.stateStore.listRecentRunRecords(1),
175
- buildBoard(input),
176
- ]);
177
- const [tickLock, runnerLock] = await Promise.all([
178
- readLockInfo(input.stateStore.paths.tickLockFile, input.now, input.processInspector),
179
- readLockInfo(input.stateStore.paths.runnerLockFile, input.now, input.processInspector),
180
- ]);
181
- const tickLockLive = tickLock.present && tickLock.pidAlive === true;
182
- const runnerLockLive = runnerLock.present && runnerLock.pidAlive === true;
183
- // A quota-paused runner (#67) no longer stops the loop - routing falls
184
- // sideways to another candidate in the tier, so only the manual pause file
185
- // is a hard stop here. Per-runner health is surfaced separately below.
186
- //
187
- // Intake (tick.lock) and agent execution (runner.lock) are separate locks -
188
- // an in-flight agent run holds only runner.lock, so both must be checked or
189
- // the pill reads "idle" for the entire duration of a run.
190
- const loopState = paused
191
- ? 'paused'
192
- : runnerLockLive
193
- ? 'working'
194
- : tickLockLive
195
- ? 'polling'
196
- : 'idle';
197
- const runnerHealth = ledger?.runners ?? {};
198
- const lastEvent = recentEvents[0];
199
- const lastRun = recentRuns[0];
200
- const counters = {
201
- 'needs-human': 0,
202
- active: 0,
203
- scheduled: 0,
204
- ready: 0,
205
- error: 0,
206
- finished: 0,
207
- };
208
- for (const card of board) {
209
- counters[card.condition] += 1;
210
- }
211
- const intervalMs = input.config.scheduler.intervalMs;
212
- const sourceStates = await listSourceStates(input.stateStore.paths.sourceStateRoot);
213
- const worstAgeMs = sourceStates.length === 0
214
- ? undefined
215
- : Math.max(...sourceStates.map((source) => input.now.getTime() - Date.parse(source.lastSuccessfulPollAt)));
216
- const sourceFreshness = worstAgeMs === undefined
217
- ? { level: 'unknown' }
218
- : {
219
- ageMs: worstAgeMs,
220
- level: worstAgeMs > intervalMs * 10
221
- ? 'red'
222
- : worstAgeMs > intervalMs * 3
223
- ? 'amber'
224
- : 'ok',
225
- };
226
- return {
227
- loopState,
228
- paused,
229
- runnerHealth,
230
- lock: tickLock,
231
- runnerLock,
232
- lastEvent: lastEvent === undefined
233
- ? undefined
234
- : {
235
- at: lastEvent.ingestedAt,
236
- type: lastEvent.sourceEventType,
237
- workItemKey: lastEvent.workItemKey,
238
- },
239
- lastRun: lastRun === undefined
240
- ? undefined
241
- : {
242
- repo: lastRun.repo,
243
- issueNumber: lastRun.issueNumber,
244
- action: lastRun.action,
245
- sentinel: lastRun.sentinel,
246
- status: lastRun.status,
247
- },
248
- sourceFreshness,
249
- counters,
250
- runsToday: countToday(todaysRuns.map((run) => run.startedAt), input.now),
251
- failuresToday: countToday(todaysRuns.filter((run) => run.status === 'failed').map((run) => run.startedAt), input.now),
252
- costUsdToday: sumToday(todaysRuns.map((run) => ({ at: run.startedAt, value: run.tokenUsage?.costUsd ?? 0 })), input.now),
253
- };
254
- }
255
- function countToday(timestamps, now) {
256
- const today = now.toISOString().slice(0, 10);
257
- return timestamps.filter((ts) => ts.slice(0, 10) === today).length;
258
- }
259
- function sumToday(entries, now) {
260
- const today = now.toISOString().slice(0, 10);
261
- return entries
262
- .filter((entry) => entry.at.slice(0, 10) === today)
263
- .reduce((total, entry) => total + entry.value, 0);
264
- }
265
- async function listSourceStates(sourceStateRoot) {
266
- try {
267
- const sourceDirs = await readdir(sourceStateRoot);
268
- const results = [];
269
- for (const sourceDir of sourceDirs) {
270
- const files = await readdir(join(sourceStateRoot, sourceDir)).catch(() => []);
271
- for (const file of files) {
272
- if (!file.endsWith('.json')) {
273
- continue;
274
- }
275
- try {
276
- const raw = JSON.parse(await readFile(join(sourceStateRoot, sourceDir, file), 'utf8'));
277
- if (typeof raw.lastSuccessfulPollAt === 'string') {
278
- results.push({
279
- source: sourceDir,
280
- key: file.replace(/\.json$/, ''),
281
- lastSuccessfulPollAt: raw.lastSuccessfulPollAt,
282
- });
283
- }
284
- }
285
- catch {
286
- continue;
287
- }
288
- }
289
- }
290
- return results;
291
- }
292
- catch {
293
- return [];
294
- }
295
- }
296
- export async function buildItemDetail(input) {
297
- const workItemKey = input.workItemKey ??
298
- (input.resourceIndex === undefined ||
299
- input.provider === undefined ||
300
- input.repo === undefined ||
301
- input.issueNumber === undefined
302
- ? undefined
303
- : await input.resourceIndex.resolve(buildResourceUri(input.provider, 'issue', `${input.repo}#${input.issueNumber}`)));
304
- if (workItemKey === undefined)
305
- return null;
306
- const item = await input.stateStore.readIssueState(workItemKey);
307
- if (item === null) {
308
- return null;
309
- }
310
- const allRuns = await input.stateStore.listRunRecordSummaries();
311
- const runs = allRuns
312
- .filter((run) => run.workItemKey === item.workItemKey)
313
- .sort((left, right) => left.startedAt.localeCompare(right.startedAt));
314
- const events = await input.stateStore.listEventEnvelopesForWorkItem(item.workItemKey, 50);
315
- return { item, runs, events };
316
- }
317
- function parseTranscriptFileName(file) {
318
- if (!file.endsWith('.txt')) {
319
- return null;
320
- }
321
- const parts = file.slice(0, -'.txt'.length).split('.');
322
- if (parts.length < 4) {
323
- return null;
324
- }
325
- const kind = parts.at(-1);
326
- if (kind !== 'prompt' && kind !== 'response') {
327
- return null;
328
- }
329
- const action = parts.at(-2);
330
- const cli = parts.at(-3);
331
- const runId = parts.slice(0, -3).join('.');
332
- if (action === undefined || cli === undefined || runId.length === 0) {
333
- return null;
334
- }
335
- return {
336
- runId,
337
- cli,
338
- action,
339
- kind,
340
- role: kind === 'prompt' ? 'user' : 'agent',
341
- };
342
- }
343
- export async function buildItemTranscripts(input) {
344
- if (!input.config.transcripts.enabled) {
345
- return { enabled: false, sessions: [] };
346
- }
347
- const runs = (await input.stateStore.listRunRecordSummaries()).filter((run) => run.workItemKey === input.workItemKey);
348
- const runsById = new Map(runs.map((run) => [run.runId, run]));
349
- const workDir = input.stateStore.paths.transcriptWorkDir(input.workItemKey);
350
- const sessionDirs = (await readdir(workDir, { withFileTypes: true }).catch(() => []))
351
- .filter((entry) => entry.isDirectory())
352
- .map((entry) => entry.name);
353
- const sessionsByKey = new Map();
354
- for (const sessionKey of sessionDirs) {
355
- const sessionDir = join(workDir, sessionKey);
356
- const files = (await readdir(sessionDir, { withFileTypes: true }).catch(() => []))
357
- .filter((entry) => entry.isFile())
358
- .map((entry) => entry.name);
359
- for (const file of files) {
360
- const parsed = parseTranscriptFileName(file);
361
- if (parsed === null) {
362
- continue;
363
- }
364
- const run = runsById.get(parsed.runId);
365
- if (run === undefined) {
366
- continue;
367
- }
368
- const transcriptEntry = {
369
- runId: parsed.runId,
370
- action: parsed.action,
371
- cli: parsed.cli,
372
- role: parsed.role,
373
- startedAt: run.startedAt,
374
- text: await readFile(join(sessionDir, file), 'utf8'),
375
- };
376
- const canonicalSessionKey = run.sessionId ?? sessionKey;
377
- const existing = sessionsByKey.get(canonicalSessionKey);
378
- if (existing === undefined) {
379
- sessionsByKey.set(canonicalSessionKey, {
380
- sessionKey: canonicalSessionKey,
381
- ...(run.sessionId === undefined ? {} : { sessionId: run.sessionId }),
382
- entries: [transcriptEntry],
383
- });
384
- }
385
- else {
386
- existing.entries.push(transcriptEntry);
387
- }
388
- }
389
- }
390
- const sessions = [...sessionsByKey.values()];
391
- for (const session of sessions) {
392
- session.entries.sort((left, right) => {
393
- const timeOrder = left.startedAt.localeCompare(right.startedAt);
394
- if (timeOrder !== 0) {
395
- return timeOrder;
396
- }
397
- if (left.runId !== right.runId) {
398
- return left.runId.localeCompare(right.runId);
399
- }
400
- const roleRank = { user: 0, agent: 1 };
401
- return roleRank[left.role] - roleRank[right.role];
402
- });
403
- }
404
- sessions.sort((left, right) => {
405
- const leftStartedAt = left.entries[0]?.startedAt ?? '';
406
- const rightStartedAt = right.entries[0]?.startedAt ?? '';
407
- const timeOrder = leftStartedAt.localeCompare(rightStartedAt);
408
- return timeOrder === 0 ? left.sessionKey.localeCompare(right.sessionKey) : timeOrder;
409
- });
410
- return { enabled: true, sessions };
411
- }
412
- export async function buildEventsFeed(input) {
413
- const limit = input.limit ?? 200;
414
- return input.stateStore.listRecentEventEnvelopes({
415
- limit,
416
- ...(input.workItemKey === undefined ? {} : { workItemKey: input.workItemKey }),
417
- ...(input.direction === undefined ? {} : { direction: input.direction }),
418
- ...(input.type === undefined ? {} : { type: input.type }),
419
- });
420
- }
421
- export async function buildRuns(input) {
422
- const runs = await input.stateStore.listRunRecordSummaries();
423
- return runs
424
- .filter((run) => input.status === undefined || run.status === input.status)
425
- .filter((run) => input.action === undefined || run.action === input.action)
426
- .filter((run) => input.repo === undefined || run.repo === input.repo)
427
- .sort((left, right) => right.startedAt.localeCompare(left.startedAt));
428
- }
429
- const metricsWindows = new Set(['1d', '3d', '5d', '7d']);
430
- const metricsMetrics = new Set([
431
- 'runs-over-time',
432
- 'runs-by-status-over-time',
433
- 'runs-by-status',
434
- 'runs-by-action',
435
- 'runs-by-repo',
436
- 'runs-by-runner',
437
- 'runs-by-model',
438
- 'runs-by-tier',
439
- 'tokens-over-time',
440
- 'tokens-by-action',
441
- 'tokens-by-runner',
442
- 'tokens-by-model',
443
- 'duration-by-action',
444
- 'duration-over-time',
445
- 'work-items-over-time',
446
- 'work-item-durations',
447
- 'work-item-totals',
448
- ]);
449
- function parseMetricsWindow(value) {
450
- return metricsWindows.has(value) ? value : '1d';
451
- }
452
- function parseMetricsMetric(value) {
453
- return metricsMetrics.has(value) ? value : 'runs-over-time';
454
- }
455
- function utcDayStartMs(date) {
456
- return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
457
- }
458
- function monthDayLabel(date) {
459
- const months = [
460
- 'Jan',
461
- 'Feb',
462
- 'Mar',
463
- 'Apr',
464
- 'May',
465
- 'Jun',
466
- 'Jul',
467
- 'Aug',
468
- 'Sep',
469
- 'Oct',
470
- 'Nov',
471
- 'Dec',
472
- ];
473
- return `${months[date.getUTCMonth()]} ${date.getUTCDate()}`;
474
- }
475
- function pad2(value) {
476
- return String(value).padStart(2, '0');
477
- }
478
- function createTimeBuckets(window, now) {
479
- const dayMs = 24 * 60 * 60 * 1000;
480
- const hourMs = 60 * 60 * 1000;
481
- const todayStart = utcDayStartMs(now);
482
- if (window === '1d') {
483
- return Array.from({ length: 24 }, (_, hour) => {
484
- const startMs = todayStart + hour * hourMs;
485
- const day = new Date(startMs);
486
- return {
487
- bucket: `${day.toISOString().slice(0, 10)}T${pad2(hour)}`,
488
- label: `${pad2(hour)}:00`,
489
- startMs,
490
- endMs: startMs + hourMs,
491
- };
492
- });
493
- }
494
- if (window === '3d') {
495
- const start = todayStart - 2 * dayMs;
496
- return Array.from({ length: 12 }, (_, index) => {
497
- const startMs = start + index * 6 * hourMs;
498
- const day = new Date(startMs);
499
- const hour = day.getUTCHours();
500
- return {
501
- bucket: `${day.toISOString().slice(0, 10)}T${pad2(hour)}`,
502
- label: `${monthDayLabel(day)} ${pad2(hour)}:00`,
503
- startMs,
504
- endMs: startMs + 6 * hourMs,
505
- };
506
- });
507
- }
508
- const days = window === '5d' ? 5 : 7;
509
- const start = todayStart - (days - 1) * dayMs;
510
- return Array.from({ length: days }, (_, index) => {
511
- const startMs = start + index * dayMs;
512
- const day = new Date(startMs);
513
- return {
514
- bucket: day.toISOString().slice(0, 10),
515
- label: monthDayLabel(day),
516
- startMs,
517
- endMs: startMs + dayMs,
518
- };
519
- });
520
- }
521
- function inMetricsWindow(timestamp, buckets) {
522
- const at = Date.parse(timestamp);
523
- const first = buckets[0];
524
- const last = buckets.at(-1);
525
- return (Number.isFinite(at) &&
526
- first !== undefined &&
527
- last !== undefined &&
528
- at >= first.startMs &&
529
- at < last.endMs);
530
- }
531
- function findBucket(timestamp, buckets) {
532
- const at = Date.parse(timestamp);
533
- if (!Number.isFinite(at)) {
534
- return undefined;
535
- }
536
- return buckets.find((bucket) => at >= bucket.startMs && at < bucket.endMs);
537
- }
538
- async function listRunsForBuckets(stateStore, buckets) {
539
- const dates = [...new Set(buckets.map((bucket) => bucket.bucket.slice(0, 10)))];
540
- const runsById = new Map();
541
- const recordsByDate = await Promise.all(dates.map((date) => stateStore.listRunRecordSummariesForDate(date)));
542
- for (const records of recordsByDate) {
543
- for (const record of records) {
544
- runsById.set(record.runId, record);
545
- }
546
- }
547
- return [...runsById.values()].sort((left, right) => left.startedAt.localeCompare(right.startedAt));
548
- }
549
- function runTokenTotal(run) {
550
- const usage = run.tokenUsage;
551
- if (usage === undefined) {
552
- return 0;
553
- }
554
- return (usage.inputTokens +
555
- usage.outputTokens +
556
- (usage.cacheCreationInputTokens ?? 0) +
557
- (usage.cacheReadInputTokens ?? 0));
558
- }
559
- function median(values) {
560
- if (values.length === 0) {
561
- return undefined;
562
- }
563
- const sorted = values.slice().sort((left, right) => left - right);
564
- const middle = Math.floor(sorted.length / 2);
565
- if (sorted.length % 2 === 1) {
566
- return sorted[middle];
567
- }
568
- return ((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2;
569
- }
570
- function average(values) {
571
- return values.length === 0
572
- ? 0
573
- : values.reduce((total, value) => total + value, 0) / values.length;
574
- }
575
- function runDurationMs(run) {
576
- if (run.finishedAt === undefined) {
577
- return undefined;
578
- }
579
- const started = Date.parse(run.startedAt);
580
- const finished = Date.parse(run.finishedAt);
581
- if (!Number.isFinite(started) || !Number.isFinite(finished) || finished < started) {
582
- return undefined;
583
- }
584
- return finished - started;
585
- }
586
- function groupCount(entries, keyFor) {
587
- const counts = new Map();
588
- for (const entry of entries) {
589
- const key = keyFor(entry);
590
- counts.set(key, (counts.get(key) ?? 0) + 1);
591
- }
592
- return [...counts.entries()]
593
- .map(([key, count]) => ({ key, count }))
594
- .sort((left, right) => right.count - left.count || left.key.localeCompare(right.key));
595
- }
596
- function groupTokens(runs, keyFor) {
597
- const groups = new Map();
598
- for (const run of runs) {
599
- const key = keyFor(run);
600
- const existing = groups.get(key) ?? { count: 0, tokens: 0, costUsd: 0 };
601
- existing.count += 1;
602
- existing.tokens += runTokenTotal(run);
603
- existing.costUsd += run.tokenUsage?.costUsd ?? 0;
604
- groups.set(key, existing);
605
- }
606
- return [...groups.entries()]
607
- .map(([key, value]) => ({
608
- key,
609
- ...value,
610
- averageTokens: value.count === 0 ? 0 : value.tokens / value.count,
611
- averageCostUsd: value.count === 0 ? 0 : value.costUsd / value.count,
612
- }))
613
- .sort((left, right) => right.tokens - left.tokens || left.key.localeCompare(right.key));
614
- }
615
- function groupDurations(runs, keyFor) {
616
- const groups = new Map();
617
- for (const run of runs) {
618
- const duration = runDurationMs(run);
619
- if (duration === undefined) {
620
- continue;
621
- }
622
- const key = keyFor(run);
623
- groups.set(key, [...(groups.get(key) ?? []), duration]);
624
- }
625
- return [...groups.entries()]
626
- .map(([key, values]) => ({
627
- key,
628
- count: values.length,
629
- totalMs: values.reduce((total, value) => total + value, 0),
630
- averageMs: average(values),
631
- medianMs: median(values) ?? 0,
632
- }))
633
- .sort((left, right) => right.medianMs - left.medianMs || left.key.localeCompare(right.key));
634
- }
635
- function runnerKey(run) {
636
- return run.routing?.runnerName ?? 'unknown';
637
- }
638
- function tierKey(run) {
639
- return run.routing?.tier ?? 'unknown';
640
- }
641
- function modelKey(run, config) {
642
- const runnerName = run.routing?.runnerName;
643
- if (runnerName === undefined) {
644
- return 'unknown';
645
- }
646
- const runner = config.runners[runnerName];
647
- if (runner === undefined || runner.kind === 'fake') {
648
- return 'unknown';
649
- }
650
- return runner.model;
651
- }
652
- function completedAtForItem(item) {
653
- const doneEntry = item.wake.stageHistory.find((entry) => entry.stage === 'done');
654
- if (doneEntry !== undefined) {
655
- return doneEntry.changedAt;
656
- }
657
- if (item.issue.state === 'closed') {
658
- return item.issue.updatedAt;
659
- }
660
- if (isTerminalStage(item.wake.stage)) {
661
- return item.wake.stageHistory.at(-1)?.changedAt ?? item.issue.updatedAt;
662
- }
663
- return undefined;
664
- }
665
- function startedAtForItem(item) {
666
- return item.issue.createdAt ?? item.wake.stageHistory[0]?.changedAt;
667
- }
668
- function completedWorkItemDurations(items, buckets) {
669
- const rows = [];
670
- for (const item of items) {
671
- const completedAt = completedAtForItem(item);
672
- const startedAt = startedAtForItem(item);
673
- if (completedAt === undefined ||
674
- startedAt === undefined ||
675
- !inMetricsWindow(completedAt, buckets)) {
676
- continue;
677
- }
678
- const completedMs = Date.parse(completedAt);
679
- const startedMs = Date.parse(startedAt);
680
- if (!Number.isFinite(completedMs) || !Number.isFinite(startedMs) || completedMs < startedMs) {
681
- continue;
682
- }
683
- rows.push({
684
- key: item.workItemKey,
685
- repo: item.issue.repo,
686
- issueNumber: item.issue.number,
687
- durationMs: completedMs - startedMs,
688
- completedAt,
689
- });
690
- }
691
- return rows.sort((left, right) => right.completedAt.localeCompare(left.completedAt));
692
- }
693
- function buildMetricsSummary(runs, workItemDurations) {
694
- const runDurations = runs
695
- .map((run) => runDurationMs(run))
696
- .filter((duration) => duration !== undefined);
697
- return {
698
- totalRuns: runs.length,
699
- completedRuns: runs.filter((run) => run.status === 'completed').length,
700
- blockedRuns: runs.filter((run) => run.status === 'blocked').length,
701
- awaitingApprovalRuns: runs.filter((run) => run.status === 'awaiting-approval').length,
702
- failedRuns: runs.filter((run) => run.status === 'failed').length,
703
- totalTokens: runs.reduce((total, run) => total + runTokenTotal(run), 0),
704
- totalCostUsd: runs.reduce((total, run) => total + (run.tokenUsage?.costUsd ?? 0), 0),
705
- medianRunDurationMs: median(runDurations),
706
- completedWorkItems: workItemDurations.length,
707
- medianWorkItemDurationMs: median(workItemDurations.map((row) => row.durationMs)),
708
- };
709
- }
710
- export async function buildMetrics(input) {
711
- const window = parseMetricsWindow(input.window);
712
- const metric = parseMetricsMetric(input.metric);
713
- const buckets = createTimeBuckets(window, input.now);
714
- const [allRuns, items] = await Promise.all([
715
- listRunsForBuckets(input.stateStore, buckets),
716
- input.stateStore.listIssueStates(),
717
- ]);
718
- const runs = allRuns.filter((run) => inMetricsWindow(run.startedAt, buckets));
719
- const workItemDurations = completedWorkItemDurations(items, buckets);
720
- const summary = buildMetricsSummary(runs, workItemDurations);
721
- const runCountsForBucket = (bucket) => {
722
- const bucketRuns = runs.filter((run) => findBucket(run.startedAt, buckets)?.bucket === bucket.bucket);
723
- return {
724
- bucket: bucket.bucket,
725
- label: bucket.label,
726
- total: bucketRuns.length,
727
- completed: bucketRuns.filter((run) => run.status === 'completed').length,
728
- blocked: bucketRuns.filter((run) => run.status === 'blocked').length,
729
- awaitingApproval: bucketRuns.filter((run) => run.status === 'awaiting-approval').length,
730
- failed: bucketRuns.filter((run) => run.status === 'failed').length,
731
- other: bucketRuns.filter((run) => !['completed', 'blocked', 'awaiting-approval', 'failed'].includes(run.status)).length,
732
- };
733
- };
734
- const tokenCountsForBucket = (bucket) => {
735
- const bucketRuns = runs.filter((run) => findBucket(run.startedAt, buckets)?.bucket === bucket.bucket);
736
- return {
737
- bucket: bucket.bucket,
738
- label: bucket.label,
739
- tokens: bucketRuns.reduce((total, run) => total + runTokenTotal(run), 0),
740
- inputTokens: bucketRuns.reduce((total, run) => total + (run.tokenUsage?.inputTokens ?? 0), 0),
741
- outputTokens: bucketRuns.reduce((total, run) => total + (run.tokenUsage?.outputTokens ?? 0), 0),
742
- cacheCreationInputTokens: bucketRuns.reduce((total, run) => total + (run.tokenUsage?.cacheCreationInputTokens ?? 0), 0),
743
- cacheReadInputTokens: bucketRuns.reduce((total, run) => total + (run.tokenUsage?.cacheReadInputTokens ?? 0), 0),
744
- costUsd: bucketRuns.reduce((total, run) => total + (run.tokenUsage?.costUsd ?? 0), 0),
745
- };
746
- };
747
- switch (metric) {
748
- case 'runs-by-status':
749
- return {
750
- window,
751
- metric,
752
- generatedAt: input.now.toISOString(),
753
- summary,
754
- detail: {
755
- kind: 'run-counts',
756
- group: 'status',
757
- rows: groupCount(runs, (run) => run.status),
758
- },
759
- };
760
- case 'runs-by-status-over-time':
761
- return {
762
- window,
763
- metric,
764
- generatedAt: input.now.toISOString(),
765
- summary,
766
- detail: {
767
- kind: 'runs-by-status-over-time',
768
- rows: buckets.map(runCountsForBucket),
769
- },
770
- };
771
- case 'runs-by-action':
772
- return {
773
- window,
774
- metric,
775
- generatedAt: input.now.toISOString(),
776
- summary,
777
- detail: {
778
- kind: 'run-counts',
779
- group: 'action',
780
- rows: groupCount(runs, (run) => run.action),
781
- },
782
- };
783
- case 'runs-by-repo':
784
- return {
785
- window,
786
- metric,
787
- generatedAt: input.now.toISOString(),
788
- summary,
789
- detail: {
790
- kind: 'run-counts',
791
- group: 'repo',
792
- rows: groupCount(runs, (run) => run.repo),
793
- },
794
- };
795
- case 'runs-by-runner':
796
- return {
797
- window,
798
- metric,
799
- generatedAt: input.now.toISOString(),
800
- summary,
801
- detail: {
802
- kind: 'run-counts',
803
- group: 'runner',
804
- rows: groupCount(runs, runnerKey),
805
- },
806
- };
807
- case 'runs-by-model':
808
- return {
809
- window,
810
- metric,
811
- generatedAt: input.now.toISOString(),
812
- summary,
813
- detail: {
814
- kind: 'run-counts',
815
- group: 'model',
816
- rows: groupCount(runs, (run) => modelKey(run, input.config)),
817
- },
818
- };
819
- case 'runs-by-tier':
820
- return {
821
- window,
822
- metric,
823
- generatedAt: input.now.toISOString(),
824
- summary,
825
- detail: {
826
- kind: 'run-counts',
827
- group: 'tier',
828
- rows: groupCount(runs, tierKey),
829
- },
830
- };
831
- case 'tokens-over-time':
832
- return {
833
- window,
834
- metric,
835
- generatedAt: input.now.toISOString(),
836
- summary,
837
- detail: { kind: 'tokens-over-time', rows: buckets.map(tokenCountsForBucket) },
838
- };
839
- case 'tokens-by-action':
840
- return {
841
- window,
842
- metric,
843
- generatedAt: input.now.toISOString(),
844
- summary,
845
- detail: {
846
- kind: 'token-counts',
847
- group: 'action',
848
- rows: groupTokens(runs, (run) => run.action),
849
- },
850
- };
851
- case 'tokens-by-runner':
852
- return {
853
- window,
854
- metric,
855
- generatedAt: input.now.toISOString(),
856
- summary,
857
- detail: {
858
- kind: 'token-counts',
859
- group: 'runner',
860
- rows: groupTokens(runs, runnerKey),
861
- },
862
- };
863
- case 'tokens-by-model':
864
- return {
865
- window,
866
- metric,
867
- generatedAt: input.now.toISOString(),
868
- summary,
869
- detail: {
870
- kind: 'token-counts',
871
- group: 'model',
872
- rows: groupTokens(runs, (run) => modelKey(run, input.config)),
873
- },
874
- };
875
- case 'duration-by-action':
876
- return {
877
- window,
878
- metric,
879
- generatedAt: input.now.toISOString(),
880
- summary,
881
- detail: {
882
- kind: 'durations',
883
- group: 'action',
884
- rows: groupDurations(runs, (run) => run.action),
885
- },
886
- };
887
- case 'duration-over-time':
888
- return {
889
- window,
890
- metric,
891
- generatedAt: input.now.toISOString(),
892
- summary,
893
- detail: {
894
- kind: 'duration-over-time',
895
- rows: buckets.map((bucket) => {
896
- const values = runs
897
- .filter((run) => findBucket(run.startedAt, buckets)?.bucket === bucket.bucket)
898
- .map((run) => runDurationMs(run))
899
- .filter((duration) => duration !== undefined);
900
- return {
901
- bucket: bucket.bucket,
902
- label: bucket.label,
903
- count: values.length,
904
- totalMs: values.reduce((total, value) => total + value, 0),
905
- averageMs: average(values),
906
- medianMs: median(values) ?? 0,
907
- };
908
- }),
909
- },
910
- };
911
- case 'work-items-over-time':
912
- return {
913
- window,
914
- metric,
915
- generatedAt: input.now.toISOString(),
916
- summary,
917
- detail: {
918
- kind: 'work-items-over-time',
919
- rows: buckets.map((bucket) => ({
920
- bucket: bucket.bucket,
921
- label: bucket.label,
922
- completed: workItemDurations.filter((row) => findBucket(row.completedAt, buckets)?.bucket === bucket.bucket).length,
923
- })),
924
- },
925
- };
926
- case 'work-item-durations':
927
- return {
928
- window,
929
- metric,
930
- generatedAt: input.now.toISOString(),
931
- summary,
932
- detail: { kind: 'work-item-durations', rows: workItemDurations },
933
- };
934
- case 'work-item-totals': {
935
- const groups = new Map();
936
- for (const run of runs) {
937
- const key = run.workItemKey;
938
- const existing = groups.get(key) ?? { runCount: 0, totalTokens: 0, totalDurationMs: 0 };
939
- existing.runCount += 1;
940
- existing.totalTokens += runTokenTotal(run);
941
- existing.totalDurationMs += runDurationMs(run) ?? 0;
942
- groups.set(key, existing);
943
- }
944
- const rows = [...groups.entries()]
945
- .map(([key, value]) => ({ key, ...value }))
946
- .sort((left, right) => right.totalTokens - left.totalTokens || left.key.localeCompare(right.key));
947
- return {
948
- window,
949
- metric,
950
- generatedAt: input.now.toISOString(),
951
- summary,
952
- detail: { kind: 'work-item-totals', rows },
953
- };
954
- }
955
- case 'runs-over-time':
956
- return {
957
- window,
958
- metric,
959
- generatedAt: input.now.toISOString(),
960
- summary,
961
- detail: { kind: 'runs-over-time', rows: buckets.map(runCountsForBucket) },
962
- };
963
- }
964
- }
965
- const secretKeyPattern = /token|secret|key|password/i;
966
- function redact(value, keyHint = '') {
967
- if (secretKeyPattern.test(keyHint) && typeof value === 'string') {
968
- return '***redacted***';
969
- }
970
- if (Array.isArray(value)) {
971
- return value.map((entry) => redact(entry));
972
- }
973
- if (value !== null && typeof value === 'object') {
974
- const out = {};
975
- for (const [key, entry] of Object.entries(value)) {
976
- out[key] = redact(entry, key);
977
- }
978
- return out;
979
- }
980
- return value;
981
- }
982
- export async function buildConfigView(input) {
983
- const ledger = await input.stateStore.readLedger();
984
- const runnerHealth = ledger?.runners ?? {};
985
- const routingTable = Object.entries(input.config.workflows).flatMap(([workflow, definition]) => {
986
- return Object.entries(definition.stages).map(([stage, route]) => {
987
- const tier = route.tier ?? input.config.defaultTier;
988
- const candidates = input.config.tiers[tier] ?? [];
989
- const runnerName = route.runner ?? candidates[0];
990
- const runner = runnerName !== undefined ? input.config.runners[runnerName] : undefined;
991
- // Full fallback order for the tier (#67), each candidate's current pause
992
- // state so the UI can show not just who's active but who Wake would fall
993
- // sideways to next, and rotate back to once a pause expires.
994
- const candidateHealth = candidates.map((name) => {
995
- const health = runnerHealth[name];
996
- const pausedUntil = health?.pausedUntil;
997
- const paused = pausedUntil !== undefined && Date.parse(pausedUntil) > input.now.getTime();
998
- return { runnerName: name, paused, pausedUntil };
999
- });
1000
- return {
1001
- workflow,
1002
- stage,
1003
- action: route.action,
1004
- tier,
1005
- runnerName,
1006
- runnerKind: runner?.kind,
1007
- model: runner !== undefined && runner.kind !== 'fake' ? runner.model : undefined,
1008
- timeoutMs: runner !== undefined && runner.kind !== 'fake' ? runner.timeoutMs : undefined,
1009
- candidates: candidateHealth,
1010
- };
1011
- });
1012
- });
1013
- return { config: redact(input.config), routingTable };
1014
- }
1015
- async function dirSize(path) {
1016
- let files = 0;
1017
- let bytes = 0;
1018
- let entries;
1019
- try {
1020
- entries = await readdir(path);
1021
- }
1022
- catch {
1023
- return { files: 0, bytes: 0 };
1024
- }
1025
- for (const entry of entries) {
1026
- const entryPath = join(path, entry);
1027
- const info = await stat(entryPath).catch(() => null);
1028
- if (info === null) {
1029
- continue;
1030
- }
1031
- if (info.isDirectory()) {
1032
- const nested = await dirSize(entryPath);
1033
- files += nested.files;
1034
- bytes += nested.bytes;
1035
- }
1036
- else {
1037
- files += 1;
1038
- bytes += info.size;
1039
- }
1040
- }
1041
- return { files, bytes };
1042
- }
1043
- export async function buildHealth(input) {
1044
- const lock = await readLockInfo(input.stateStore.paths.tickLockFile, input.now);
1045
- const staleAfterMs = 15 * 60 * 1000;
1046
- const paused = await input.stateStore.isPaused();
1047
- const ledger = await input.stateStore.readLedger();
1048
- const sourceStates = await listSourceStates(input.stateStore.paths.sourceStateRoot);
1049
- const storageDirs = ['events', 'state', 'runs', 'workspaces'];
1050
- const storageSizes = await Promise.all(storageDirs.map((dir) => dirSize(join(input.stateStore.paths.wakeRoot, dir))));
1051
- const storage = Object.fromEntries(storageDirs.map((dir, i) => [dir, storageSizes[i]]));
1052
- const items = await input.stateStore.listIssueStates();
1053
- const integrityChecks = await Promise.all(items
1054
- .filter((item) => item.wake.workspacePath !== undefined)
1055
- .map(async (item) => {
1056
- const exists = await stat(item.wake.workspacePath)
1057
- .then(() => true)
1058
- .catch(() => false);
1059
- return exists
1060
- ? null
1061
- : {
1062
- path: item.wake.workspacePath,
1063
- problem: `workspacePath missing for ${item.workItemKey}`,
1064
- };
1065
- }));
1066
- const integrityIssues = integrityChecks.filter((issue) => issue !== null);
1067
- return {
1068
- lock: {
1069
- ...lock,
1070
- stale: lock.present && ((lock.ageMs ?? 0) >= staleAfterMs || lock.pidAlive === false),
1071
- staleAfterMs,
1072
- },
1073
- pause: { paused, runnerHealth: ledger?.runners ?? {} },
1074
- sources: sourceStates.map((source) => ({
1075
- ...source,
1076
- ageMs: input.now.getTime() - Date.parse(source.lastSuccessfulPollAt),
1077
- })),
1078
- storage,
1079
- integrityIssues,
1080
- };
1081
- }
1082
- export async function buildWorkspaces(input) {
1083
- const items = await input.stateStore.listIssueStates();
1084
- const byWorkspacePath = new Map(items
1085
- .filter((item) => item.wake.workspacePath !== undefined)
1086
- .map((item) => [item.wake.workspacePath, item]));
1087
- const workspaceRoot = input.stateStore.paths.workspaceRoot;
1088
- const workspaces = [];
1089
- const repoDirs = await readdir(workspaceRoot).catch(() => []);
1090
- for (const repoDir of repoDirs) {
1091
- const repoPath = join(workspaceRoot, repoDir);
1092
- const issueDirs = await readdir(repoPath).catch(() => []);
1093
- for (const issueDir of issueDirs) {
1094
- const fullPath = join(repoPath, issueDir);
1095
- const size = await dirSize(fullPath);
1096
- const matched = byWorkspacePath.get(fullPath);
1097
- workspaces.push({
1098
- path: fullPath,
1099
- repo: matched?.issue.repo,
1100
- issueNumber: matched?.issue.number,
1101
- size: size.bytes,
1102
- orphan: matched === undefined,
1103
- });
1104
- }
1105
- }
1106
- return workspaces;
1107
- }