@edspencer/paddock 0.0.1 → 0.57.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 (296) hide show
  1. package/README.md +69 -0
  2. package/install-notice.mjs +29 -0
  3. package/package.json +58 -5
  4. package/packages/server/dist/adoptable.d.ts +127 -0
  5. package/packages/server/dist/adoptable.js +315 -0
  6. package/packages/server/dist/app.d.ts +67 -0
  7. package/packages/server/dist/app.js +325 -0
  8. package/packages/server/dist/archive.d.ts +42 -0
  9. package/packages/server/dist/archive.js +125 -0
  10. package/packages/server/dist/attachments-config.d.ts +114 -0
  11. package/packages/server/dist/attachments-config.js +165 -0
  12. package/packages/server/dist/attachments-hint.d.ts +53 -0
  13. package/packages/server/dist/attachments-hint.js +140 -0
  14. package/packages/server/dist/attachments.d.ts +33 -0
  15. package/packages/server/dist/attachments.js +117 -0
  16. package/packages/server/dist/auth.d.ts +84 -0
  17. package/packages/server/dist/auth.js +247 -0
  18. package/packages/server/dist/background.d.ts +36 -0
  19. package/packages/server/dist/background.js +137 -0
  20. package/packages/server/dist/bind-safety.d.ts +56 -0
  21. package/packages/server/dist/bind-safety.js +51 -0
  22. package/packages/server/dist/brand.d.ts +44 -0
  23. package/packages/server/dist/brand.js +88 -0
  24. package/packages/server/dist/chat-dto.d.ts +210 -0
  25. package/packages/server/dist/chat-dto.js +308 -0
  26. package/packages/server/dist/cli/import-chats.d.ts +21 -0
  27. package/packages/server/dist/cli/import-chats.js +197 -0
  28. package/packages/server/dist/cli/paddock.d.ts +2 -0
  29. package/packages/server/dist/cli/paddock.js +247 -0
  30. package/packages/server/dist/concurrency.d.ts +57 -0
  31. package/packages/server/dist/concurrency.js +71 -0
  32. package/packages/server/dist/config.d.ts +538 -0
  33. package/packages/server/dist/config.js +625 -0
  34. package/packages/server/dist/curation-config.d.ts +55 -0
  35. package/packages/server/dist/curation-config.js +64 -0
  36. package/packages/server/dist/environment-prompt.d.ts +52 -0
  37. package/packages/server/dist/environment-prompt.js +61 -0
  38. package/packages/server/dist/event-bus.d.ts +87 -0
  39. package/packages/server/dist/event-bus.js +48 -0
  40. package/packages/server/dist/git.d.ts +128 -0
  41. package/packages/server/dist/git.js +446 -0
  42. package/packages/server/dist/github-auth.d.ts +44 -0
  43. package/packages/server/dist/github-auth.js +169 -0
  44. package/packages/server/dist/herdctl-agent-config.d.ts +98 -0
  45. package/packages/server/dist/herdctl-agent-config.js +331 -0
  46. package/packages/server/dist/herdctl-agent-names.d.ts +172 -0
  47. package/packages/server/dist/herdctl-agent-names.js +228 -0
  48. package/packages/server/dist/herdctl-jobs-index.d.ts +65 -0
  49. package/packages/server/dist/herdctl-jobs-index.js +225 -0
  50. package/packages/server/dist/herdctl-jobs.d.ts +98 -0
  51. package/packages/server/dist/herdctl-jobs.js +225 -0
  52. package/packages/server/dist/herdctl.d.ts +623 -0
  53. package/packages/server/dist/herdctl.js +1279 -0
  54. package/packages/server/dist/hook-config.d.ts +217 -0
  55. package/packages/server/dist/hook-config.js +224 -0
  56. package/packages/server/dist/http-bytes.d.ts +24 -0
  57. package/packages/server/dist/http-bytes.js +52 -0
  58. package/packages/server/dist/index.d.ts +1 -0
  59. package/packages/server/dist/index.js +14 -0
  60. package/packages/server/dist/instance-config.d.ts +143 -0
  61. package/packages/server/dist/instance-config.js +385 -0
  62. package/packages/server/dist/localcommand.d.ts +23 -0
  63. package/packages/server/dist/localcommand.js +140 -0
  64. package/packages/server/dist/management-auth.d.ts +99 -0
  65. package/packages/server/dist/management-auth.js +165 -0
  66. package/packages/server/dist/management-config.d.ts +164 -0
  67. package/packages/server/dist/management-config.js +286 -0
  68. package/packages/server/dist/management-mcp-server.d.ts +20 -0
  69. package/packages/server/dist/management-mcp-server.js +95 -0
  70. package/packages/server/dist/management-metadata.d.ts +67 -0
  71. package/packages/server/dist/management-metadata.js +56 -0
  72. package/packages/server/dist/management-ops.d.ts +110 -0
  73. package/packages/server/dist/management-ops.js +449 -0
  74. package/packages/server/dist/management-policy.d.ts +172 -0
  75. package/packages/server/dist/management-policy.js +259 -0
  76. package/packages/server/dist/message-provenance.d.ts +112 -0
  77. package/packages/server/dist/message-provenance.js +260 -0
  78. package/packages/server/dist/models.d.ts +164 -0
  79. package/packages/server/dist/models.js +208 -0
  80. package/packages/server/dist/openapi.d.ts +66 -0
  81. package/packages/server/dist/openapi.js +183 -0
  82. package/packages/server/dist/parent-detach.d.ts +30 -0
  83. package/packages/server/dist/parent-detach.js +109 -0
  84. package/packages/server/dist/preload.d.ts +43 -0
  85. package/packages/server/dist/preload.js +55 -0
  86. package/packages/server/dist/project-files.d.ts +76 -0
  87. package/packages/server/dist/project-files.js +191 -0
  88. package/packages/server/dist/project-mime.d.ts +35 -0
  89. package/packages/server/dist/project-mime.js +55 -0
  90. package/packages/server/dist/project-paths.d.ts +88 -0
  91. package/packages/server/dist/project-paths.js +126 -0
  92. package/packages/server/dist/project-types.d.ts +316 -0
  93. package/packages/server/dist/project-types.js +48 -0
  94. package/packages/server/dist/projects.d.ts +202 -0
  95. package/packages/server/dist/projects.js +894 -0
  96. package/packages/server/dist/queued-message.d.ts +35 -0
  97. package/packages/server/dist/queued-message.js +116 -0
  98. package/packages/server/dist/read-state.d.ts +46 -0
  99. package/packages/server/dist/read-state.js +139 -0
  100. package/packages/server/dist/recovery-config.d.ts +111 -0
  101. package/packages/server/dist/recovery-config.js +105 -0
  102. package/packages/server/dist/recovery.d.ts +262 -0
  103. package/packages/server/dist/recovery.js +490 -0
  104. package/packages/server/dist/resume-drain.d.ts +122 -0
  105. package/packages/server/dist/resume-drain.js +158 -0
  106. package/packages/server/dist/route-context.d.ts +168 -0
  107. package/packages/server/dist/route-context.js +39 -0
  108. package/packages/server/dist/route-errors.d.ts +8 -0
  109. package/packages/server/dist/route-errors.js +13 -0
  110. package/packages/server/dist/routes/chats.d.ts +12 -0
  111. package/packages/server/dist/routes/chats.js +1406 -0
  112. package/packages/server/dist/routes/git.d.ts +13 -0
  113. package/packages/server/dist/routes/git.js +236 -0
  114. package/packages/server/dist/routes/mcp.d.ts +83 -0
  115. package/packages/server/dist/routes/mcp.js +250 -0
  116. package/packages/server/dist/routes/meta.d.ts +15 -0
  117. package/packages/server/dist/routes/meta.js +447 -0
  118. package/packages/server/dist/routes/projects.d.ts +25 -0
  119. package/packages/server/dist/routes/projects.js +736 -0
  120. package/packages/server/dist/routes/triggers.d.ts +21 -0
  121. package/packages/server/dist/routes/triggers.js +324 -0
  122. package/packages/server/dist/routes/workspace-mount.d.ts +48 -0
  123. package/packages/server/dist/routes/workspace-mount.js +32 -0
  124. package/packages/server/dist/routes.d.ts +26 -0
  125. package/packages/server/dist/routes.js +29 -0
  126. package/packages/server/dist/run-provenance.d.ts +109 -0
  127. package/packages/server/dist/run-provenance.js +206 -0
  128. package/packages/server/dist/runs.d.ts +92 -0
  129. package/packages/server/dist/runs.js +72 -0
  130. package/packages/server/dist/schedule-config.d.ts +68 -0
  131. package/packages/server/dist/schedule-config.js +75 -0
  132. package/packages/server/dist/schedule-session.d.ts +34 -0
  133. package/packages/server/dist/schedule-session.js +120 -0
  134. package/packages/server/dist/self-mcp-descriptions.d.ts +20 -0
  135. package/packages/server/dist/self-mcp-descriptions.js +145 -0
  136. package/packages/server/dist/self-mcp-read.d.ts +15 -0
  137. package/packages/server/dist/self-mcp-read.js +97 -0
  138. package/packages/server/dist/self-mcp-triggers.d.ts +30 -0
  139. package/packages/server/dist/self-mcp-triggers.js +144 -0
  140. package/packages/server/dist/self-mcp-types.d.ts +248 -0
  141. package/packages/server/dist/self-mcp-types.js +10 -0
  142. package/packages/server/dist/self-mcp-util.d.ts +73 -0
  143. package/packages/server/dist/self-mcp-util.js +157 -0
  144. package/packages/server/dist/self-mcp-write.d.ts +35 -0
  145. package/packages/server/dist/self-mcp-write.js +215 -0
  146. package/packages/server/dist/self-mcp.d.ts +95 -0
  147. package/packages/server/dist/self-mcp.js +448 -0
  148. package/packages/server/dist/send-file-mcp.d.ts +46 -0
  149. package/packages/server/dist/send-file-mcp.js +281 -0
  150. package/packages/server/dist/session-hub.d.ts +186 -0
  151. package/packages/server/dist/session-hub.js +291 -0
  152. package/packages/server/dist/spawn-capability.d.ts +99 -0
  153. package/packages/server/dist/spawn-capability.js +106 -0
  154. package/packages/server/dist/star.d.ts +26 -0
  155. package/packages/server/dist/star.js +94 -0
  156. package/packages/server/dist/start.d.ts +1 -0
  157. package/packages/server/dist/start.js +21 -0
  158. package/packages/server/dist/subagents.d.ts +337 -0
  159. package/packages/server/dist/subagents.js +626 -0
  160. package/packages/server/dist/sweep.d.ts +122 -0
  161. package/packages/server/dist/sweep.js +696 -0
  162. package/packages/server/dist/tooldetails.d.ts +33 -0
  163. package/packages/server/dist/tooldetails.js +466 -0
  164. package/packages/server/dist/transcribe.d.ts +69 -0
  165. package/packages/server/dist/transcribe.js +119 -0
  166. package/packages/server/dist/transcripts.d.ts +42 -0
  167. package/packages/server/dist/transcripts.js +164 -0
  168. package/packages/server/dist/trigger-config.d.ts +278 -0
  169. package/packages/server/dist/trigger-config.js +417 -0
  170. package/packages/server/dist/trigger-runtime.d.ts +94 -0
  171. package/packages/server/dist/trigger-runtime.js +82 -0
  172. package/packages/server/dist/trigger-session.d.ts +34 -0
  173. package/packages/server/dist/trigger-session.js +122 -0
  174. package/packages/server/dist/triggers.d.ts +83 -0
  175. package/packages/server/dist/triggers.js +124 -0
  176. package/packages/server/dist/trusted-proxy.d.ts +57 -0
  177. package/packages/server/dist/trusted-proxy.js +157 -0
  178. package/packages/server/dist/turn-notice.d.ts +154 -0
  179. package/packages/server/dist/turn-notice.js +271 -0
  180. package/packages/server/dist/unread.d.ts +36 -0
  181. package/packages/server/dist/unread.js +123 -0
  182. package/packages/server/dist/usage.d.ts +66 -0
  183. package/packages/server/dist/usage.js +243 -0
  184. package/packages/server/dist/wake-injection.d.ts +156 -0
  185. package/packages/server/dist/wake-injection.js +136 -0
  186. package/packages/server/dist/whisper-local.d.ts +9 -0
  187. package/packages/server/dist/whisper-local.js +122 -0
  188. package/packages/server/dist/ws-context.d.ts +158 -0
  189. package/packages/server/dist/ws-context.js +1 -0
  190. package/packages/server/dist/ws-protocol.d.ts +423 -0
  191. package/packages/server/dist/ws-protocol.js +251 -0
  192. package/packages/server/dist/ws-self-mcp.d.ts +52 -0
  193. package/packages/server/dist/ws-self-mcp.js +28 -0
  194. package/packages/server/dist/ws-triggers.d.ts +29 -0
  195. package/packages/server/dist/ws-triggers.js +261 -0
  196. package/packages/server/dist/ws-turn.d.ts +86 -0
  197. package/packages/server/dist/ws-turn.js +751 -0
  198. package/packages/server/dist/ws.d.ts +62 -0
  199. package/packages/server/dist/ws.js +1024 -0
  200. package/packages/server/package.json +6 -0
  201. package/packages/web/dist/assets/InstanceConfigPage-Bypd_quL.js +2 -0
  202. package/packages/web/dist/assets/MarkdownRenderer-Co8w9iuX.js +30 -0
  203. package/packages/web/dist/assets/ProjectRedirect-DXrA24Cy.js +2 -0
  204. package/packages/web/dist/assets/ProjectView-B9opP7Sv.js +13 -0
  205. package/packages/web/dist/assets/ProjectsGrid-D2cwb2jY.js +2 -0
  206. package/packages/web/dist/assets/arc-B--FIRB8.js +2 -0
  207. package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-B6dRWZZ6.js +37 -0
  208. package/packages/web/dist/assets/bash-I8pq0VWm.js +2 -0
  209. package/packages/web/dist/assets/blockDiagram-GPEHLZMM-DLxFAvvf.js +133 -0
  210. package/packages/web/dist/assets/c-D2F1sQUL.js +2 -0
  211. package/packages/web/dist/assets/c4Diagram-AAUBKEIU-CNee5ks4.js +11 -0
  212. package/packages/web/dist/assets/channel-Bw0A52CB.js +2 -0
  213. package/packages/web/dist/assets/chunk-2J33WTMH-DObw03Xl.js +2 -0
  214. package/packages/web/dist/assets/chunk-4BX2VUAB-CH2SR6nN.js +2 -0
  215. package/packages/web/dist/assets/chunk-55IACEB6-WlVpegT3.js +2 -0
  216. package/packages/web/dist/assets/chunk-727SXJPM-g8GT9Rgd.js +207 -0
  217. package/packages/web/dist/assets/chunk-AQP2D5EJ-DbN4PsDF.js +232 -0
  218. package/packages/web/dist/assets/chunk-FMBD7UC4-DSN3BjFG.js +16 -0
  219. package/packages/web/dist/assets/chunk-ND2GUHAM-D9R4le0z.js +2 -0
  220. package/packages/web/dist/assets/chunk-QZHKN3VN-D4YgRaKH.js +2 -0
  221. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-itf_kt-q.js +2 -0
  222. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-itf_kt-q.js +2 -0
  223. package/packages/web/dist/assets/core-CysJ2gLj.js +4 -0
  224. package/packages/web/dist/assets/cose-bilkent-S5V4N54A-D1PB0KNu.js +2 -0
  225. package/packages/web/dist/assets/cpp-Bm3ii6kY.js +2 -0
  226. package/packages/web/dist/assets/csharp-BNpuYZMG.js +2 -0
  227. package/packages/web/dist/assets/css-DazXZka4.js +2 -0
  228. package/packages/web/dist/assets/cytoscape.esm-CUqq0XTU.js +332 -0
  229. package/packages/web/dist/assets/dagre-BM42HDAG-C6Uuq-dq.js +5 -0
  230. package/packages/web/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
  231. package/packages/web/dist/assets/diagram-2AECGRRQ-C9JvQ425.js +44 -0
  232. package/packages/web/dist/assets/diagram-5GNKFQAL-Cu6pdh2d.js +11 -0
  233. package/packages/web/dist/assets/diagram-KO2AKTUF-PmXcUQhY.js +4 -0
  234. package/packages/web/dist/assets/diagram-LMA3HP47-9ww6_9Iq.js +25 -0
  235. package/packages/web/dist/assets/diagram-OG6HWLK6-DWQVjv_n.js +25 -0
  236. package/packages/web/dist/assets/erDiagram-TEJ5UH35-6F7xTuff.js +86 -0
  237. package/packages/web/dist/assets/flowDiagram-I6XJVG4X-SCiGu7m6.js +163 -0
  238. package/packages/web/dist/assets/format-yf7Vf9kR.js +2 -0
  239. package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BNpPwxkL.js +293 -0
  240. package/packages/web/dist/assets/gitGraphDiagram-PVQCEYII-C0gyAJjW.js +107 -0
  241. package/packages/web/dist/assets/go-BPx_y7j8.js +2 -0
  242. package/packages/web/dist/assets/graph-BlwPajkw.js +2 -0
  243. package/packages/web/dist/assets/index-B-YuzkF6.js +71 -0
  244. package/packages/web/dist/assets/index-CvGiQddO.css +1 -0
  245. package/packages/web/dist/assets/infoDiagram-5YYISTIA-CJ2CFPfy.js +3 -0
  246. package/packages/web/dist/assets/ini-B3BVgEsG.js +2 -0
  247. package/packages/web/dist/assets/init-Gi6I4Gst.js +2 -0
  248. package/packages/web/dist/assets/ishikawaDiagram-YF4QCWOH-Cs0D32Ry.js +71 -0
  249. package/packages/web/dist/assets/java-B5VKqtH2.js +2 -0
  250. package/packages/web/dist/assets/javascript-BKRaQes9.js +2 -0
  251. package/packages/web/dist/assets/journeyDiagram-JHISSGLW-Bc9fuI2i.js +140 -0
  252. package/packages/web/dist/assets/json-DIYVocXf.js +2 -0
  253. package/packages/web/dist/assets/kanban-definition-UN3LZRKU-D_tC7QfC.js +90 -0
  254. package/packages/web/dist/assets/katex-C5jXJg4s.js +258 -0
  255. package/packages/web/dist/assets/kotlin-Cw-eF_bm.js +3 -0
  256. package/packages/web/dist/assets/lastTab-CU1zsF0j.js +2 -0
  257. package/packages/web/dist/assets/layout-Boy8vhSP.js +2 -0
  258. package/packages/web/dist/assets/linear-BzGHkF9K.js +2 -0
  259. package/packages/web/dist/assets/mermaid.core-DlbsYzE-.js +310 -0
  260. package/packages/web/dist/assets/mindmap-definition-RKZ34NQL-Bxeh-ToD.js +97 -0
  261. package/packages/web/dist/assets/ordinal-Cboi1Yqb.js +2 -0
  262. package/packages/web/dist/assets/php-CRdd4dfS.js +3 -0
  263. package/packages/web/dist/assets/pieDiagram-4H26LBE5-CDmilo8S.js +31 -0
  264. package/packages/web/dist/assets/python-kSlZHhJb.js +2 -0
  265. package/packages/web/dist/assets/quadrantDiagram-W4KKPZXB-D14-JyE9.js +8 -0
  266. package/packages/web/dist/assets/queued-LamEU0GH.js +2 -0
  267. package/packages/web/dist/assets/requirementDiagram-4Y6WPE33-CJ1PUGu-.js +85 -0
  268. package/packages/web/dist/assets/ruby-7T4ktxyv.js +2 -0
  269. package/packages/web/dist/assets/rust-HC3MBUhY.js +2 -0
  270. package/packages/web/dist/assets/sankeyDiagram-5OEKKPKP-BkUK73W0.js +41 -0
  271. package/packages/web/dist/assets/scss-DZLA7YXz.js +2 -0
  272. package/packages/web/dist/assets/sequenceDiagram-3UESZ5HK-sbcvsuLp.js +163 -0
  273. package/packages/web/dist/assets/sql-BC88IN-V.js +2 -0
  274. package/packages/web/dist/assets/stateDiagram-AJRCARHV-DV-7BdHK.js +2 -0
  275. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BlkGdXKW.js +2 -0
  276. package/packages/web/dist/assets/swift-Dn8kKO_1.js +2 -0
  277. package/packages/web/dist/assets/timeline-definition-PNZ67QCA-C5su21G_.js +121 -0
  278. package/packages/web/dist/assets/typescript-C2FFdlUC.js +2 -0
  279. package/packages/web/dist/assets/vennDiagram-CIIHVFJN-JNkIJAaQ.js +35 -0
  280. package/packages/web/dist/assets/wardley-L42UT6IY-qcz6t2vR.js +174 -0
  281. package/packages/web/dist/assets/wardleyDiagram-YWT4CUSO-CMrOIZ-c.js +79 -0
  282. package/packages/web/dist/assets/xml-BXBhIUeX.js +2 -0
  283. package/packages/web/dist/assets/xychartDiagram-2RQKCTM6-C9q6Oy9u.js +8 -0
  284. package/packages/web/dist/assets/yaml-drsDnIKT.js +2 -0
  285. package/packages/web/dist/favicon.ico +0 -0
  286. package/packages/web/dist/fonts/inter-latin.woff2 +0 -0
  287. package/packages/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
  288. package/packages/web/dist/icons/apple-touch-icon.png +0 -0
  289. package/packages/web/dist/icons/favicon-16.png +0 -0
  290. package/packages/web/dist/icons/favicon-32.png +0 -0
  291. package/packages/web/dist/icons/icon-192.png +0 -0
  292. package/packages/web/dist/icons/icon-512.png +0 -0
  293. package/packages/web/dist/icons/maskable-512.png +0 -0
  294. package/packages/web/dist/index.html +56 -0
  295. package/packages/web/dist/manifest.webmanifest +32 -0
  296. package/packages/web/dist/sw.js +139 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * herdctl-jobs — the on-disk `job-*.yaml` reads (run history + the unread badge)
3
+ * and the adoption/attribution writes, split out of {@link HerdctlService}
4
+ * (issue #403).
5
+ *
6
+ * Every function here depends only on the shared jobs directory (`<stateDir>/jobs`)
7
+ * plus fs/YAML and the pure agent-name helpers — NO `fleet`/live-session state —
8
+ * so the cluster is isolated + testable on its own. {@link HerdctlService} keeps
9
+ * thin public wrappers (`lastTurnCompletedAt`, `listProjectRuns`, …) that thread
10
+ * `this.cfg.stateDir`, and its internal fork/promote/attribute methods call the
11
+ * write helpers here directly.
12
+ *
13
+ * The two unread-badge reads take a {@link JobsDirIndex} instead of a `stateDir`
14
+ * (#529): they run on nearly every request, and re-parsing every record each time
15
+ * was 61% of all busy server CPU. The index owns the directory and keeps the parse
16
+ * incremental; these functions are pure folds over it. The write helpers below
17
+ * still take `stateDir` — they are rare, and the index self-heals from their
18
+ * mtime changes (`HerdctlService` also invalidates it explicitly at each site).
19
+ */
20
+ import { promises as fs } from "node:fs";
21
+ import path from "node:path";
22
+ import YAML from "yaml";
23
+ import { listJobs } from "@herdctl/core";
24
+ import { keeperAgentName, keeperSlugFromAgent } from "./herdctl-agent-names.js";
25
+ import { jobsDirOf } from "./herdctl-jobs-index.js";
26
+ /**
27
+ * Map each chat session id to the ISO timestamp of its most recent COMPLETED
28
+ * turn, read cheaply from herdctl's job-metadata records (NOT by parsing
29
+ * transcripts). In `batch` drive mode every keeper turn runs via `trigger()`,
30
+ * which writes a `job-*.yaml` whose `finished_at` is stamped when the turn
31
+ * finishes and whose `session_id` is filled in on completion — so the latest
32
+ * `finished_at` across a session's records is exactly "the agent last finished a
33
+ * turn." NOTE the default drive mode is `session`, whose turns write NO job
34
+ * record (see the caveat on {@link listProjectRuns}); on that path this map is
35
+ * fed only by paddock's synthetic adoption records. This is the server signal
36
+ * for the unread affordance (#160, reused per-project by #161): unlike the
37
+ * transcript mtime (`DiscoveredSession.mtime`) it does NOT tick on the user's
38
+ * own sends.
39
+ *
40
+ * Records still running (no `finished_at`) or not yet session-resolved (no
41
+ * `session_id`) are skipped. The synthetic adoption records paddock writes
42
+ * carry an earlier, mid-turn `finished_at`, so the max naturally prefers the
43
+ * real completion. Session-mode turns (`openChatSession`) write no job record,
44
+ * so their chats have no server timestamp and rely on the client live event.
45
+ *
46
+ * Reads through {@link JobsDirIndex}, which keeps the parse incremental
47
+ * (mtime+size keyed, completed records only) — see there for why that is safe
48
+ * and what it is worth. This function itself is a pure fold over the index.
49
+ */
50
+ export async function lastTurnCompletedAt(index) {
51
+ const out = new Map();
52
+ for (const { sessionId, finishedAt } of await index.read()) {
53
+ // ISO-8601 UTC strings sort lexicographically in chronological order.
54
+ const prev = out.get(sessionId);
55
+ if (!prev || finishedAt > prev)
56
+ out.set(sessionId, finishedAt);
57
+ }
58
+ return out;
59
+ }
60
+ /**
61
+ * Per-project variant of {@link lastTurnCompletedAt} for the sidebar unread
62
+ * badge (#161): group the same cheap job-record scan by the KEEPER agent that
63
+ * owns each session, so the projects-list payload can carry a compact
64
+ * `{ sessionId, lastTurnCompletedAt }` list per project WITHOUT the N+1
65
+ * `listSessions` fan-out or any transcript parse. Returns `slug -> (sessionId
66
+ * -> latest finished_at)`.
67
+ *
68
+ * Only keeper-attributed records (`agent: keeper-<slug>`) are kept — sweeper,
69
+ * trigger and hook records carry their own session ids that are not chats, so
70
+ * `keeperSlugFromAgent` returning `null` naturally filters them out. A promoted
71
+ * chat is grouped under its keeper slug (its keeper record).
72
+ */
73
+ export async function lastTurnCompletedAtByProject(index) {
74
+ const out = new Map();
75
+ for (const { sessionId, finishedAt, agent } of await index.read()) {
76
+ if (agent === null)
77
+ continue;
78
+ const slug = keeperSlugFromAgent(agent);
79
+ // only keeper (workspace) chats — skip sweeper/hook/trigger agents.
80
+ // `""` is the ROOT workspace's key, so compare against null explicitly: a
81
+ // falsy check would drop every root chat from the unread badge.
82
+ if (slug === null)
83
+ continue;
84
+ let bySession = out.get(slug);
85
+ if (!bySession) {
86
+ bySession = new Map();
87
+ out.set(slug, bySession);
88
+ }
89
+ // ISO-8601 UTC strings sort lexicographically in chronological order.
90
+ const prev = bySession.get(sessionId);
91
+ if (!prev || finishedAt > prev)
92
+ bySession.set(sessionId, finishedAt);
93
+ }
94
+ return out;
95
+ }
96
+ /**
97
+ * The raw herdctl job records for one project's keeper agent, most-recent
98
+ * first — the data source for the "while you were away" run-history view (E3 /
99
+ * #268 / DD-6). Each `trigger()` (batch drive mode) turn writes one
100
+ * `job-*.yaml` carrying `trigger_type`, `status`, `started_at`/`finished_at`,
101
+ * `duration_seconds`, `session_id`, `schedule` and `forked_from`; this reads
102
+ * them via core's `listJobs` (importable from `@herdctl/core`, sorted by
103
+ * `started_at` descending) filtered to `keeper-<slug>`, so sweeper/trigger
104
+ * records are excluded.
105
+ *
106
+ * The true human/scheduled/spawned provenance is carried by Paddock's
107
+ * {@link RunProvenanceStore} (origin/depth keyed by `session_id`), NOT by
108
+ * `trigger_type` — paddock-initiated turns still write `trigger_type:"manual"`
109
+ * (see ws.ts). The caller joins the two.
110
+ *
111
+ * Caveat (documented at {@link lastTurnCompletedAt}): session-mode turns
112
+ * (`openChatSession`) write NO job record, so runs driven that way don't
113
+ * appear here — only batch `trigger()` turns and paddock's synthetic adoption
114
+ * records do. Cost columns (DD-4) are P3 and not yet on the record.
115
+ */
116
+ export async function listProjectRuns(stateDir, project, limit = 100) {
117
+ const jobsDir = jobsDirOf(stateDir);
118
+ const agent = keeperAgentName(project.slug);
119
+ const { jobs } = await listJobs(jobsDir, { agent }).catch(() => ({ jobs: [], errors: 0 }));
120
+ return limit > 0 ? jobs.slice(0, limit) : jobs;
121
+ }
122
+ /**
123
+ * Job records for a SET of agents, most-recent first (Epic T follow-up / #327) —
124
+ * the data source for the Triggers tab's per-trigger last-run column. Used to pull
125
+ * one project's keeper AND every scoped `trigger-<slug>-<name>` agent in a single
126
+ * pass so {@link import("./trigger-runtime.js").buildTriggerRuntime} can attribute a
127
+ * scoped trigger's newest run by agent name. `listJobs` has no multi-agent filter,
128
+ * so this scans the jobs dir once (unfiltered) and keeps only the requested agents;
129
+ * order (started_at descending) is preserved. Errors swallow to `[]` so the runtime
130
+ * view degrades to config-only rather than failing to render.
131
+ */
132
+ export async function listRunsForAgents(stateDir, agents, limit = 200) {
133
+ if (agents.length === 0)
134
+ return [];
135
+ const jobsDir = jobsDirOf(stateDir);
136
+ const wanted = new Set(agents);
137
+ const { jobs } = await listJobs(jobsDir).catch(() => ({ jobs: [], errors: 0 }));
138
+ const filtered = jobs.filter((j) => wanted.has(j.agent));
139
+ return limit > 0 ? filtered.slice(0, limit) : filtered;
140
+ }
141
+ /**
142
+ * Point every herdctl job record for `sessionId` at the project's keeper so
143
+ * the core attribution index (last-write-wins per session) lists the session
144
+ * under the project. A session can already have MANY job records (one per
145
+ * turn) attributed elsewhere; simply adding a keeper record alongside them is
146
+ * not enough — whichever record the index visits last wins. So we rewrite the
147
+ * `agent` field of all existing records for the session. When none exist
148
+ * (e.g. a transcript migrated from outside paddock), we synthesize one.
149
+ */
150
+ export async function reattributeSession(stateDir, sessionId, project, when) {
151
+ const jobsDir = jobsDirOf(stateDir);
152
+ await fs.mkdir(jobsDir, { recursive: true });
153
+ const keeper = keeperAgentName(project.slug);
154
+ let entries = [];
155
+ try {
156
+ entries = await fs.readdir(jobsDir);
157
+ }
158
+ catch {
159
+ entries = [];
160
+ }
161
+ let matched = 0;
162
+ for (const name of entries) {
163
+ if (!name.endsWith(".yaml"))
164
+ continue;
165
+ const file = path.join(jobsDir, name);
166
+ let parsed = null;
167
+ try {
168
+ parsed = YAML.parse(await fs.readFile(file, "utf8"));
169
+ }
170
+ catch {
171
+ continue;
172
+ }
173
+ if (!parsed || parsed.session_id !== sessionId)
174
+ continue;
175
+ matched++;
176
+ if (parsed.agent === keeper)
177
+ continue;
178
+ parsed.agent = keeper;
179
+ await fs.writeFile(file, YAML.stringify(parsed), "utf8");
180
+ }
181
+ // No existing job records for the session — synthesize one (migration path).
182
+ if (matched === 0)
183
+ await writeAdoptionJob(stateDir, sessionId, project, when);
184
+ }
185
+ /**
186
+ * Write a herdctl job-metadata YAML mapping `sessionId -> keeper agent` so the
187
+ * core attribution index lists the session under the project. Mirrors the shape
188
+ * of a real job record (and the JobMetadataSchema: the id must match
189
+ * `job-YYYY-MM-DD-[a-z0-9]{6}`).
190
+ */
191
+ export async function writeAdoptionJob(stateDir, sessionId, project, when) {
192
+ await writeAgentAdoptionJob(stateDir, sessionId, keeperAgentName(project.slug), when);
193
+ }
194
+ /**
195
+ * Underlying adoption-record writer, parametrized by the target agent name so
196
+ * every caller (fork/promote/adopt — see {@link attributeRunningSession}) shares
197
+ * one implementation. Writes a `<jobId>.yaml` mapping the
198
+ * session id to `agentName` plus a matching empty `.jsonl` output file.
199
+ */
200
+ export async function writeAgentAdoptionJob(stateDir, sessionId, agentName, when) {
201
+ const jobsDir = jobsDirOf(stateDir);
202
+ await fs.mkdir(jobsDir, { recursive: true });
203
+ const iso = (Number.isNaN(when.getTime()) ? new Date() : when).toISOString();
204
+ const date = iso.slice(0, 10);
205
+ const jobId = `job-${date}-${sessionId.slice(0, 6).toLowerCase()}`;
206
+ const outputFile = path.join(jobsDir, `${jobId}.jsonl`);
207
+ const record = {
208
+ id: jobId,
209
+ agent: agentName,
210
+ schedule: null,
211
+ trigger_type: "web",
212
+ status: "completed",
213
+ exit_reason: "success",
214
+ session_id: sessionId,
215
+ forked_from: null,
216
+ started_at: iso,
217
+ finished_at: iso,
218
+ duration_seconds: 0,
219
+ output_file: outputFile,
220
+ };
221
+ await fs.writeFile(path.join(jobsDir, `${jobId}.yaml`), YAML.stringify(record), "utf8");
222
+ // herdctl's listJobs tolerates a missing output file, but keep parity with
223
+ // a real job record by touching an empty one.
224
+ await fs.writeFile(outputFile, "", "utf8").catch(() => undefined);
225
+ }