@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,21 @@
1
+ /**
2
+ * Trigger routes (Epic T "Unify Triggers" / T3) — the per-project UNIFIED trigger
3
+ * management surface the Triggers tab drives, the sole successor that collapses the
4
+ * retired hooks + schedules REST/verbs onto ONE `TriggerService` over
5
+ * `project.yaml`'s single `triggers` block. A trigger is WHEN (`trigger`, a
6
+ * discriminated union `schedule|event|webhook`) + WHAT (`run`, the shared agent-run
7
+ * definition) + `enabled`. Each route delegates to TriggerService, which persists to
8
+ * project.yaml FIRST (the source of truth, re-armed on restart) THEN arms it,
9
+ * warning-but-not-failing if the runtime arm hiccups.
10
+ *
11
+ * Verb collapse (GG-3): enable/disable is NOT a separate route — it's `set` (PUT)
12
+ * with the `enabled` field flipped; new triggers default disabled.
13
+ */
14
+ import type { FastifyInstance } from "fastify";
15
+ import type { RouteCtx } from "../route-context.js";
16
+ /**
17
+ * The workspace-scoped trigger surface: registered once per workspace mount
18
+ * (root and per project) — see `workspace-mount.ts`. Paths are relative to the
19
+ * mount.
20
+ */
21
+ export declare function registerTriggerWorkspaceRoutes(app: FastifyInstance, ctx: RouteCtx): void;
@@ -0,0 +1,324 @@
1
+ import { keeperAgentName } from "../herdctl.js";
2
+ import { GRANTABLE_TOOLS } from "../hook-config.js";
3
+ import { buildTriggerRuntime } from "../trigger-runtime.js";
4
+ import { TRIGGER_EVENTS, TRIGGER_TYPES, isValidTriggerName, isCuratorTrigger, sanitizeTrigger, } from "../trigger-config.js";
5
+ import { sendProjectError } from "../route-errors.js";
6
+ /**
7
+ * The workspace-scoped trigger surface: registered once per workspace mount
8
+ * (root and per project) — see `workspace-mount.ts`. Paths are relative to the
9
+ * mount.
10
+ */
11
+ export function registerTriggerWorkspaceRoutes(app, ctx) {
12
+ const { projects, herdctl, triggers, fireTrigger } = ctx;
13
+ const triggersGuard = (reply) => {
14
+ if (!triggers) {
15
+ reply.code(503).send({ error: "Trigger management is unavailable", code: "unavailable" });
16
+ return false;
17
+ }
18
+ return true;
19
+ };
20
+ // List a project's triggers (DTOs) + the picker catalog: the grantable tools, the
21
+ // events an event-trigger can fire on, and the trigger types — so the Triggers tab
22
+ // renders a precise capability + type picker without hard-coding them client-side
23
+ // (folds in the G4 `GRANTABLE_TOOLS` list).
24
+ app.get("/triggers", {
25
+ schema: {
26
+ tags: ["Triggers"],
27
+ summary: "List a project's triggers plus the picker catalog",
28
+ description: "Returns the project's trigger DTOs together with the Triggers-tab picker catalog. " +
29
+ "Success (200) is an object with `triggers` (array of trigger DTOs, each combining the " +
30
+ "`trigger` when-clause, the `run` definition, `enabled`, and derived fields like `agentName`), " +
31
+ "`grantableTools` (the tools an event/webhook trigger may be granted), `events` (the lifecycle " +
32
+ "events an event trigger can fire on), and `triggerTypes` (the supported trigger kinds).",
33
+ params: {
34
+ type: "object",
35
+ properties: {
36
+ slug: { type: "string", description: "Project slug." },
37
+ },
38
+ required: ["slug"],
39
+ },
40
+ response: {
41
+ 200: {
42
+ description: "The trigger DTO list plus picker catalog (`triggers`, `grantableTools`, `events`, `triggerTypes`).",
43
+ type: "object",
44
+ additionalProperties: true,
45
+ },
46
+ },
47
+ },
48
+ }, async (req, reply) => {
49
+ if (!triggersGuard(reply))
50
+ return reply;
51
+ try {
52
+ const list = await triggers.list(req.params.slug); // throws not_found
53
+ return {
54
+ triggers: list,
55
+ grantableTools: GRANTABLE_TOOLS,
56
+ events: [...TRIGGER_EVENTS],
57
+ triggerTypes: [...TRIGGER_TYPES],
58
+ };
59
+ }
60
+ catch (err) {
61
+ return sendProjectError(reply, err);
62
+ }
63
+ });
64
+ // Get one trigger by name (404 when the project declares no such trigger).
65
+ app.get("/triggers/:name", {
66
+ schema: {
67
+ tags: ["Triggers"],
68
+ summary: "Get one trigger by name",
69
+ description: "Fetches a single trigger's config by name. Success (200) is an object with a `trigger` field " +
70
+ "holding the trigger DTO (`trigger` when-clause + `run` + `enabled` + derived fields). Returns 404 " +
71
+ "when the project declares no trigger with that name.",
72
+ params: {
73
+ type: "object",
74
+ properties: {
75
+ slug: { type: "string", description: "Project slug." },
76
+ name: { type: "string", description: "Trigger name." },
77
+ },
78
+ required: ["slug", "name"],
79
+ },
80
+ response: {
81
+ 200: {
82
+ description: "An object with a `trigger` field holding the trigger DTO.",
83
+ type: "object",
84
+ additionalProperties: true,
85
+ },
86
+ },
87
+ },
88
+ }, async (req, reply) => {
89
+ if (!triggersGuard(reply))
90
+ return reply;
91
+ const { slug, name } = req.params;
92
+ try {
93
+ const trigger = await triggers.get(slug, name); // throws not_found (project)
94
+ if (!trigger) {
95
+ return reply.code(404).send({ error: `No such trigger: ${name}`, code: "not_found" });
96
+ }
97
+ return { trigger };
98
+ }
99
+ catch (err) {
100
+ return sendProjectError(reply, err);
101
+ }
102
+ });
103
+ // Create or replace one trigger (keyed by name). Persists to project.yaml, then
104
+ // arms it so it's immediately fireable. Enabling/disabling is this same route with
105
+ // `enabled` flipped (GG-3). The body is the FULL record `{ trigger, run, enabled }`
106
+ // — a full replace (unlike the self-MCP set_trigger, which patches partial edits).
107
+ app.put("/triggers/:name", {
108
+ schema: {
109
+ tags: ["Triggers"],
110
+ summary: "Create or replace one trigger",
111
+ description: "Creates or fully replaces a trigger keyed by name, persisting it to project.yaml then arming it. " +
112
+ "Enabling/disabling is this same route with `enabled` flipped. The body is the FULL trigger record: " +
113
+ "`trigger` is the WHEN clause — a discriminated union on `trigger.type` (`schedule` with `cron` xor " +
114
+ "`interval`; `event` with an `on` value such as `onArchive`/`afterTurn`; `webhook` with a `path`, " +
115
+ "shape-reserved) — `run` is the shared agent-run definition (prompt/promptFile, model, tools, etc.), " +
116
+ "and `enabled` is the on/off flag. Success (200) is an object with a `trigger` field holding the " +
117
+ "persisted trigger DTO. Returns 400 for an invalid name or malformed trigger definition.",
118
+ params: {
119
+ type: "object",
120
+ properties: {
121
+ slug: { type: "string", description: "Project slug." },
122
+ name: { type: "string", description: "Trigger name." },
123
+ },
124
+ required: ["slug", "name"],
125
+ },
126
+ body: {
127
+ // Documentation-only (no validation/coercion): accept any/empty body.
128
+ type: ["object", "null"],
129
+ additionalProperties: true,
130
+ properties: {
131
+ trigger: {
132
+ description: "The WHEN clause: a discriminated union on `trigger.type` (`schedule`/`event`/`webhook`) with type-specific fields.",
133
+ },
134
+ run: {
135
+ description: "The WHAT clause: the shared agent-run definition (prompt/promptFile, model, tools, session mode, etc.).",
136
+ },
137
+ enabled: { description: "Whether the trigger is armed; new triggers default disabled." },
138
+ },
139
+ },
140
+ response: {
141
+ 200: {
142
+ description: "An object with a `trigger` field holding the persisted trigger DTO.",
143
+ type: "object",
144
+ additionalProperties: true,
145
+ },
146
+ },
147
+ },
148
+ }, async (req, reply) => {
149
+ if (!triggersGuard(reply))
150
+ return reply;
151
+ const { slug, name } = req.params;
152
+ if (!isValidTriggerName(name)) {
153
+ return reply.code(400).send({ error: `Invalid trigger name: ${name}`, code: "invalid" });
154
+ }
155
+ // Reject a malformed record early (bad discriminant, both/neither cron+interval,
156
+ // unknown event, both/neither prompt+promptFile) so the client gets a 400
157
+ // instead of the store's generic error; TriggerService.set re-validates.
158
+ if (!sanitizeTrigger(req.body)) {
159
+ return reply.code(400).send({ error: "Invalid trigger definition", code: "invalid" });
160
+ }
161
+ try {
162
+ const trigger = await triggers.set(slug, name, req.body); // throws not_found/invalid
163
+ return { trigger };
164
+ }
165
+ catch (err) {
166
+ return sendProjectError(reply, err);
167
+ }
168
+ });
169
+ // Delete one trigger. Removes it from project.yaml AND disarms it (an event
170
+ // trigger's agent is torn down; a schedule trigger's forwarded entry is dropped).
171
+ app.delete("/triggers/:name", {
172
+ schema: {
173
+ tags: ["Triggers"],
174
+ summary: "Delete one trigger",
175
+ description: "Removes a trigger from project.yaml AND disarms it (an event trigger's agent is torn down; a " +
176
+ "schedule trigger's forwarded entry is dropped). Success (200) is an object with `ok: true`, the " +
177
+ "`name`, and `removed` describing what was removed.",
178
+ params: {
179
+ type: "object",
180
+ properties: {
181
+ slug: { type: "string", description: "Project slug." },
182
+ name: { type: "string", description: "Trigger name." },
183
+ },
184
+ required: ["slug", "name"],
185
+ },
186
+ response: {
187
+ 200: {
188
+ description: "An object with `ok`, `name`, and `removed`.",
189
+ type: "object",
190
+ additionalProperties: true,
191
+ },
192
+ },
193
+ },
194
+ }, async (req, reply) => {
195
+ if (!triggersGuard(reply))
196
+ return reply;
197
+ const { slug, name } = req.params;
198
+ try {
199
+ const removed = await triggers.remove(slug, name); // throws not_found (project)
200
+ return reply.code(200).send({ ok: true, name, removed });
201
+ }
202
+ catch (err) {
203
+ return sendProjectError(reply, err);
204
+ }
205
+ });
206
+ // Per-trigger RUNTIME state (Epic T follow-up / #327) — the live "last-run / next-run
207
+ // / status" the Triggers tab renders alongside each trigger's config. `TriggerDto`
208
+ // carries config only, so this JOINS it with herdctl runtime state the tab lost when
209
+ // the Schedules section folded in: the cron scheduler's `ScheduleInfo` (next-fire,
210
+ // status — schedule triggers) + job records (last run, per the E3/#268 pattern) for
211
+ // each trigger's own scoped agent. Served as its OWN endpoint (not folded into the
212
+ // config list) so the tab can POLL it cheaply without re-fetching the config + picker
213
+ // catalog. A static path segment — matched before `/:name` — so no trigger shadows it.
214
+ app.get("/triggers/runtime", {
215
+ schema: {
216
+ tags: ["Triggers"],
217
+ summary: "Live runtime state for a project's triggers",
218
+ description: "Per-trigger live last-run / next-run / status, joining each trigger's config with herdctl runtime " +
219
+ "state (the cron scheduler's next-fire + status for schedule triggers, plus job records for last run). " +
220
+ "This is a static path segment, matched BEFORE `/:name`, so no trigger name shadows it. It is served " +
221
+ "as its own pollable endpoint (separate from the config list). Success (200) is an object with a " +
222
+ "`runtime` field describing each trigger's live state.",
223
+ params: {
224
+ type: "object",
225
+ properties: {
226
+ slug: { type: "string", description: "Project slug." },
227
+ },
228
+ required: ["slug"],
229
+ },
230
+ response: {
231
+ 200: {
232
+ description: "An object with a `runtime` field describing each trigger's live last-run/next-run/status.",
233
+ type: "object",
234
+ additionalProperties: true,
235
+ },
236
+ },
237
+ },
238
+ }, async (req, reply) => {
239
+ if (!triggersGuard(reply))
240
+ return reply;
241
+ try {
242
+ const project = await projects.get(req.params.slug); // throws not_found
243
+ const dtos = await triggers.list(project.slug);
244
+ // The agents a trigger's runs land under: the keeper (unscoped schedule
245
+ // triggers) + every trigger's own scoped `trigger-<slug>-<name>` agent.
246
+ const agents = [keeperAgentName(project.slug), ...dtos.map((d) => d.agentName)];
247
+ const [runs, schedules] = await Promise.all([
248
+ herdctl.listRunsForAgents(agents).catch(() => []),
249
+ herdctl.listAgentSchedules(project).catch(() => []),
250
+ ]);
251
+ return { runtime: buildTriggerRuntime(dtos, runs, schedules, project.slug) };
252
+ }
253
+ catch (err) {
254
+ return sendProjectError(reply, err);
255
+ }
256
+ });
257
+ // Fire a trigger NOW (Epic T follow-up / #327) — "Run now". Runs it through the same
258
+ // hub path a cron / event fire uses, so the resulting chat is a first-class, badged
259
+ // run (indistinguishable from an automatic fire). Fires ANY trigger type regardless
260
+ // of its `enabled` flag — a manual run is deliberate (mirrors the schedule DD-1 rule).
261
+ // 503 when the trigger fire entrypoint isn't wired (tests may omit it); 404 for an
262
+ // unknown trigger; 502 if the fire started no chat. Responds 202 with the session id.
263
+ app.post("/triggers/:name/run", {
264
+ schema: {
265
+ tags: ["Triggers"],
266
+ summary: "Run a trigger now",
267
+ description: "Fires a trigger immediately (\"Run now\") through the same hub path a cron/event fire uses, producing " +
268
+ "a first-class badged run. Fires any trigger type regardless of its `enabled` flag. Success is 202 with " +
269
+ "an object containing `ok: true`, the `name`, and the started `sessionId`. Returns 503 when firing is " +
270
+ "unavailable, 404 for an unknown trigger, 409 for the post-turn curator trigger (not runnable on demand), " +
271
+ "and 502 if the fire started no chat.",
272
+ params: {
273
+ type: "object",
274
+ properties: {
275
+ slug: { type: "string", description: "Project slug." },
276
+ name: { type: "string", description: "Trigger name." },
277
+ },
278
+ required: ["slug", "name"],
279
+ },
280
+ response: {
281
+ 202: {
282
+ description: "An object with `ok`, `name`, and the started `sessionId`.",
283
+ type: "object",
284
+ additionalProperties: true,
285
+ },
286
+ },
287
+ },
288
+ }, async (req, reply) => {
289
+ if (!triggersGuard(reply))
290
+ return reply;
291
+ const { slug, name } = req.params;
292
+ if (!fireTrigger) {
293
+ return reply.code(503).send({ error: "Trigger firing is unavailable", code: "unavailable" });
294
+ }
295
+ try {
296
+ const project = await projects.get(slug); // throws not_found
297
+ const rec = project.triggers?.[name];
298
+ if (!rec) {
299
+ return reply.code(404).send({ error: `No such trigger: ${name}`, code: "not_found" });
300
+ }
301
+ // The post-turn CURATOR (the folded-in sweeper — any `event`/`afterTurn` trigger,
302
+ // T5) is NOT a generic fireable trigger: it has no scoped `trigger-<slug>-<name>`
303
+ // agent (it runs via SweepService on the `afterTurn` event, needing a just-
304
+ // completed turn's context), so the generic fire path can't run it. Reject it with
305
+ // a clear 409 rather than letting the fire fail opaquely as a 502.
306
+ if (isCuratorTrigger(rec)) {
307
+ return reply.code(409).send({
308
+ error: "The post-turn curator trigger runs automatically after each turn and can't be run on demand.",
309
+ code: "not_runnable",
310
+ });
311
+ }
312
+ const sessionId = await fireTrigger(slug, name);
313
+ if (!sessionId) {
314
+ return reply
315
+ .code(502)
316
+ .send({ error: "Trigger fire did not start a chat", code: "trigger_failed" });
317
+ }
318
+ return reply.code(202).send({ ok: true, name, sessionId });
319
+ }
320
+ catch (err) {
321
+ return sendProjectError(reply, err);
322
+ }
323
+ });
324
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * workspace-mount — the seam that makes the root workspace and a project
3
+ * workspace run literally the same route handlers.
4
+ *
5
+ * A workspace is identified by its path relative to `projectsRoot`, and the root
6
+ * workspace's key is the empty string (see `project-paths.ts`). An empty string
7
+ * cannot ride in a URL path segment — `/api/projects//chats` does not match — so
8
+ * rather than smuggle a sentinel like `__root` into the wire, the
9
+ * workspace-scoped routes are a Fastify plugin **registered twice**:
10
+ *
11
+ * | mount | workspace key |
12
+ * |-------------------------|------------------|
13
+ * | `/api/root` | `""` |
14
+ * | `/api/projects/:slug` | `params.slug` |
15
+ *
16
+ * **This is the architectural guarantee, not a convenience.** "The root behaves
17
+ * exactly like a project" is true by construction — same handlers, same schemas,
18
+ * same error paths — instead of being a property someone has to remember to
19
+ * preserve. The previous design made the root a project with a reserved slug and
20
+ * then had to keep two resolution branches in step; one of them was missed and
21
+ * every root file route 404'd.
22
+ *
23
+ * Handlers are untouched by the split: they still read `req.params.slug`. On the
24
+ * root mount there is no `:slug` segment to match, so {@link rootParamsHook}
25
+ * injects `slug: ""` in `onRequest` — which runs BEFORE schema validation, so
26
+ * the existing `required: ["slug"]` params schemas keep validating unchanged.
27
+ * (Verified against Fastify directly rather than assumed.)
28
+ */
29
+ import type { FastifyInstance } from "fastify";
30
+ /** URL prefix for the root workspace's scoped routes. */
31
+ export declare const ROOT_MOUNT_PREFIX = "/api/root";
32
+ /** URL prefix (parametric) for a project workspace's scoped routes. */
33
+ export declare const PROJECT_MOUNT_PREFIX = "/api/projects/:slug";
34
+ /**
35
+ * Give the root mount the `slug` param its handlers and schemas expect.
36
+ *
37
+ * `onRequest` is deliberate: it is the earliest lifecycle hook, and in
38
+ * particular it runs before params validation, so a schema declaring
39
+ * `required: ["slug"]` still passes on a path that has no `:slug` segment.
40
+ */
41
+ export declare function rootParamsHook(app: FastifyInstance): void;
42
+ /**
43
+ * Register one workspace-scoped route group at both mounts.
44
+ *
45
+ * `group` is a plain Fastify plugin that declares its paths RELATIVE to the
46
+ * workspace (e.g. `/chats/:sessionId`, not `/api/projects/:slug/chats/:sessionId`).
47
+ */
48
+ export declare function mountWorkspaceRoutes(app: FastifyInstance, group: (app: FastifyInstance) => void): Promise<void>;
@@ -0,0 +1,32 @@
1
+ /** URL prefix for the root workspace's scoped routes. */
2
+ export const ROOT_MOUNT_PREFIX = "/api/root";
3
+ /** URL prefix (parametric) for a project workspace's scoped routes. */
4
+ export const PROJECT_MOUNT_PREFIX = "/api/projects/:slug";
5
+ /**
6
+ * Give the root mount the `slug` param its handlers and schemas expect.
7
+ *
8
+ * `onRequest` is deliberate: it is the earliest lifecycle hook, and in
9
+ * particular it runs before params validation, so a schema declaring
10
+ * `required: ["slug"]` still passes on a path that has no `:slug` segment.
11
+ */
12
+ export function rootParamsHook(app) {
13
+ app.addHook("onRequest", (req, _reply, done) => {
14
+ req.params = { ...(req.params ?? {}), slug: "" };
15
+ done();
16
+ });
17
+ }
18
+ /**
19
+ * Register one workspace-scoped route group at both mounts.
20
+ *
21
+ * `group` is a plain Fastify plugin that declares its paths RELATIVE to the
22
+ * workspace (e.g. `/chats/:sessionId`, not `/api/projects/:slug/chats/:sessionId`).
23
+ */
24
+ export async function mountWorkspaceRoutes(app, group) {
25
+ await app.register(async (scoped) => {
26
+ rootParamsHook(scoped);
27
+ group(scoped);
28
+ }, { prefix: ROOT_MOUNT_PREFIX });
29
+ await app.register(async (scoped) => {
30
+ group(scoped);
31
+ }, { prefix: PROJECT_MOUNT_PREFIX });
32
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * REST routes — composition root.
3
+ *
4
+ * The Fastify REST surface is one `registerRoutes(app, deps)` that builds the
5
+ * shared route context once (see {@link buildRouteContext}) and then hangs each
6
+ * per-group cluster off the app via a `registerXRoutes(app, ctx)` call. The
7
+ * handlers themselves live in the sibling group modules; the pure DTO/HTTP
8
+ * helpers live in `chat-dto.ts` / `http-bytes.ts` / `route-errors.ts` (issue
9
+ * #403 — break up oversized files). Each group registers directly off `app`
10
+ * (no Fastify plugins), preserving the original wiring shape.
11
+ *
12
+ * The route map, by group:
13
+ * meta — /api/transcription, /api/transcribe, /api/me, /api/health,
14
+ * /api/instance-config, /api/models, /api/fleet,
15
+ * /api/chat-files/:id, project chat upload
16
+ * git — /api/git[/push|/github/*], project git status/diff/commit
17
+ * projects — projects CRUD + promote, pins, files/changelog/overview/commands
18
+ * triggers — project triggers CRUD + runtime + run-now
19
+ * chats — workspace chat lifecycle (list/runs/usage/create/messages/
20
+ * subagents/context/delete/rename/fork/archive/star/seen/promote)
21
+ */
22
+ import type { FastifyInstance } from "fastify";
23
+ import { type RouteDeps } from "./route-context.js";
24
+ export type { RouteDeps } from "./route-context.js";
25
+ export { parseRangeHeader } from "./http-bytes.js";
26
+ export declare function registerRoutes(app: FastifyInstance, deps: RouteDeps): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import { buildRouteContext } from "./route-context.js";
2
+ import { registerMetaRoutes, registerMetaWorkspaceRoutes } from "./routes/meta.js";
3
+ import { registerGitRoutes, registerGitWorkspaceRoutes } from "./routes/git.js";
4
+ import { registerProjectRoutes, registerProjectWorkspaceRoutes } from "./routes/projects.js";
5
+ import { registerTriggerWorkspaceRoutes } from "./routes/triggers.js";
6
+ import { registerChatWorkspaceRoutes } from "./routes/chats.js";
7
+ import { registerMcpRoutes } from "./routes/mcp.js";
8
+ import { mountWorkspaceRoutes } from "./routes/workspace-mount.js";
9
+ export { parseRangeHeader } from "./http-bytes.js";
10
+ export async function registerRoutes(app, deps) {
11
+ const ctx = buildRouteContext(deps);
12
+ registerMetaRoutes(app, ctx);
13
+ registerGitRoutes(app, ctx);
14
+ registerProjectRoutes(app, ctx);
15
+ // The workspace-scoped half of every group, mounted twice: `/api/root` (the
16
+ // root workspace, key `""`) and `/api/projects/:slug` (a project). Same
17
+ // handlers both times — that identity is the point, not an optimisation.
18
+ await mountWorkspaceRoutes(app, (scoped) => {
19
+ registerProjectWorkspaceRoutes(scoped, ctx);
20
+ registerChatWorkspaceRoutes(scoped, ctx);
21
+ registerGitWorkspaceRoutes(scoped, ctx);
22
+ registerTriggerWorkspaceRoutes(scoped, ctx);
23
+ registerMetaWorkspaceRoutes(scoped, ctx);
24
+ });
25
+ // #312 M1: the external Management API gate. Registered unconditionally (it
26
+ // answers 404 when unconfigured) so an unconfigured `/mcp` can never fall
27
+ // through to the SPA not-found handler and be served the app shell.
28
+ registerMcpRoutes(app, ctx);
29
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Every origin, as VALUES — the single source of truth for both the type and the
3
+ * runtime allowlist {@link coerce} validates against.
4
+ *
5
+ * Declared this way round on purpose (#588). It used to be a hand-written union
6
+ * with a separate `readonly TurnOrigin[]` list, and `readonly TurnOrigin[]` only
7
+ * asserts that each element IS an origin — never that every origin is an
8
+ * element. So when the union grew, the allowlist silently lagged: `coerce()`
9
+ * returned null for the new member, and every chat carrying it lost BOTH its
10
+ * badge and its parent edge on read, with no error anywhere. Deriving the type
11
+ * from the list makes that impossible rather than merely documented.
12
+ */
13
+ export declare const ORIGINS: readonly ["human", "scheduled", "spawned", "hook", "adopted"];
14
+ /**
15
+ * How a turn/chat came to exist — the provenance dimension #267 badges on.
16
+ *
17
+ * `adopted` (#588) is the odd one out: the chat was not created by Paddock at
18
+ * all. It is a terminal `claude` session the user ran themselves and later
19
+ * imported, so it has no creating turn, no parent, and nothing ever "ran" it
20
+ * here. It is therefore a ROOT and it is ATTENDED — see `ORIGIN_IS_ROOT` in
21
+ * ./chat-dto.ts and `ORIGIN_IS_UNATTENDED` in ./runs.ts, both of which are
22
+ * `Record<TurnOrigin, boolean>` so that adding a member here forces a
23
+ * DECISION at each of them instead of a silent fallthrough.
24
+ */
25
+ export type TurnOrigin = (typeof ORIGINS)[number];
26
+ /** A chat's provenance marker: how it was created + how deep in a spawn tree. */
27
+ export interface RunProvenance {
28
+ origin: TurnOrigin;
29
+ /** Spawn hops from the human/scheduled root (0 = root). #262 gates on this. */
30
+ depth: number;
31
+ /**
32
+ * The chat that CREATED this one — the parent edge the nested chat list draws.
33
+ * `depth` alone says a chat was spawned and how deep, but never BY WHOM, so the
34
+ * list could badge a child yet not file it under its parent.
35
+ *
36
+ * Absent for roots, for chats created before this field existed, and for the
37
+ * external `/mcp` transport (no calling chat). Historical chats are backfilled
38
+ * at read time from MessageProvenanceStore — see `parentChat()` there.
39
+ */
40
+ parentSessionId?: string;
41
+ /** The parent's project slug — a parent can live in ANOTHER project. */
42
+ parentProject?: string;
43
+ }
44
+ /** A reference to the chat that created another one. */
45
+ export interface ParentChatRef {
46
+ project: string;
47
+ sessionId: string;
48
+ }
49
+ /** The root of any chat tree a human starts: origin `human`, depth 0. */
50
+ export declare const HUMAN_ROOT: RunProvenance;
51
+ /**
52
+ * A schedule-fired chat: origin `scheduled`, depth 0. A cron is a root trigger,
53
+ * exactly like a human — there is no scheduler wired yet (that's Epic D), but the
54
+ * marker path accepts it now.
55
+ */
56
+ export declare const SCHEDULED_ROOT: RunProvenance;
57
+ /**
58
+ * An IMPORTED terminal chat (#588): origin `adopted`, depth 0.
59
+ *
60
+ * Depth 0 because the session was never spawned by anything in this instance —
61
+ * the user ran it themselves in a terminal and Paddock only adopted the
62
+ * transcript. Stamped with `stampIfAbsent`, never `stamp`: if a chat somehow
63
+ * already carries a marker, whatever created it here knows more about its
64
+ * origin than the import does.
65
+ */
66
+ export declare const ADOPTED_ROOT: RunProvenance;
67
+ /**
68
+ * The provenance a child inherits when `parent` spawns it via a self-MCP write
69
+ * tool: origin becomes `spawned` and depth is one deeper. So a fan-out tree's
70
+ * depth is always the number of spawn hops from its human/scheduled root, which
71
+ * is exactly what #262 bounds with `maxSpawnDepth`.
72
+ */
73
+ export declare function childOf(parent: RunProvenance, parentRef?: ParentChatRef | null): RunProvenance;
74
+ export declare class RunProvenanceStore {
75
+ private readonly stateFile;
76
+ /** In-memory map of sessionId -> provenance (loaded once, written through). */
77
+ private state;
78
+ /**
79
+ * The in-flight load, cached so concurrent first-callers share ONE read.
80
+ * Caching only the resolved `state` would let several stamps that begin before
81
+ * the first `fs.readFile` resolves each build their own map and race on the
82
+ * assignment — a lost update (last writer's map wins, dropping the others'
83
+ * keys). Reachable when a keeper spawns several children right after startup.
84
+ */
85
+ private loadPromise;
86
+ /** Serialises concurrent writes so the file never interleaves. */
87
+ private writing;
88
+ constructor(dataDir: string);
89
+ /** Load the persisted map (lazily, deduped; tolerant of a missing/corrupt file). */
90
+ private ensureLoaded;
91
+ /** This chat's recorded provenance, or undefined if none. Non-throwing. */
92
+ get(sessionId: string): Promise<RunProvenance | undefined>;
93
+ /**
94
+ * Record a chat's provenance. Idempotent (a repeat of the same marker is a
95
+ * no-op), and a blank/unsafe session id or malformed marker is ignored. Callers
96
+ * stamp a chat when they CREATE it, so this is a fresh key in practice; it does
97
+ * overwrite if called again (unlike {@link stampIfAbsent}).
98
+ */
99
+ stamp(sessionId: string, provenance: RunProvenance): Promise<void>;
100
+ /**
101
+ * Record provenance ONLY if this chat has none yet — never clobbers an existing
102
+ * marker. Used on resume/wake paths (a scheduler wake resumes a chat that may
103
+ * already have been created by a human or a spawn), so its creation provenance
104
+ * survives.
105
+ */
106
+ stampIfAbsent(sessionId: string, provenance: RunProvenance): Promise<void>;
107
+ /** Write-through, serialised so overlapping stamps can't corrupt the file. */
108
+ private persist;
109
+ }