@edspencer/paddock 0.0.1 → 0.56.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 (294) 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 +512 -0
  33. package/packages/server/dist/config.js +593 -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/event-bus.d.ts +87 -0
  37. package/packages/server/dist/event-bus.js +48 -0
  38. package/packages/server/dist/git.d.ts +128 -0
  39. package/packages/server/dist/git.js +446 -0
  40. package/packages/server/dist/github-auth.d.ts +44 -0
  41. package/packages/server/dist/github-auth.js +169 -0
  42. package/packages/server/dist/herdctl-agent-config.d.ts +98 -0
  43. package/packages/server/dist/herdctl-agent-config.js +331 -0
  44. package/packages/server/dist/herdctl-agent-names.d.ts +172 -0
  45. package/packages/server/dist/herdctl-agent-names.js +228 -0
  46. package/packages/server/dist/herdctl-jobs-index.d.ts +65 -0
  47. package/packages/server/dist/herdctl-jobs-index.js +225 -0
  48. package/packages/server/dist/herdctl-jobs.d.ts +98 -0
  49. package/packages/server/dist/herdctl-jobs.js +225 -0
  50. package/packages/server/dist/herdctl.d.ts +572 -0
  51. package/packages/server/dist/herdctl.js +1218 -0
  52. package/packages/server/dist/hook-config.d.ts +217 -0
  53. package/packages/server/dist/hook-config.js +224 -0
  54. package/packages/server/dist/http-bytes.d.ts +24 -0
  55. package/packages/server/dist/http-bytes.js +52 -0
  56. package/packages/server/dist/index.d.ts +1 -0
  57. package/packages/server/dist/index.js +14 -0
  58. package/packages/server/dist/instance-config.d.ts +135 -0
  59. package/packages/server/dist/instance-config.js +345 -0
  60. package/packages/server/dist/localcommand.d.ts +23 -0
  61. package/packages/server/dist/localcommand.js +140 -0
  62. package/packages/server/dist/management-auth.d.ts +99 -0
  63. package/packages/server/dist/management-auth.js +165 -0
  64. package/packages/server/dist/management-config.d.ts +164 -0
  65. package/packages/server/dist/management-config.js +286 -0
  66. package/packages/server/dist/management-mcp-server.d.ts +20 -0
  67. package/packages/server/dist/management-mcp-server.js +95 -0
  68. package/packages/server/dist/management-metadata.d.ts +67 -0
  69. package/packages/server/dist/management-metadata.js +56 -0
  70. package/packages/server/dist/management-ops.d.ts +110 -0
  71. package/packages/server/dist/management-ops.js +449 -0
  72. package/packages/server/dist/management-policy.d.ts +172 -0
  73. package/packages/server/dist/management-policy.js +259 -0
  74. package/packages/server/dist/message-provenance.d.ts +112 -0
  75. package/packages/server/dist/message-provenance.js +260 -0
  76. package/packages/server/dist/models.d.ts +164 -0
  77. package/packages/server/dist/models.js +208 -0
  78. package/packages/server/dist/openapi.d.ts +66 -0
  79. package/packages/server/dist/openapi.js +183 -0
  80. package/packages/server/dist/parent-detach.d.ts +30 -0
  81. package/packages/server/dist/parent-detach.js +109 -0
  82. package/packages/server/dist/preload.d.ts +43 -0
  83. package/packages/server/dist/preload.js +55 -0
  84. package/packages/server/dist/project-files.d.ts +76 -0
  85. package/packages/server/dist/project-files.js +191 -0
  86. package/packages/server/dist/project-mime.d.ts +35 -0
  87. package/packages/server/dist/project-mime.js +55 -0
  88. package/packages/server/dist/project-paths.d.ts +88 -0
  89. package/packages/server/dist/project-paths.js +126 -0
  90. package/packages/server/dist/project-types.d.ts +316 -0
  91. package/packages/server/dist/project-types.js +48 -0
  92. package/packages/server/dist/projects.d.ts +202 -0
  93. package/packages/server/dist/projects.js +894 -0
  94. package/packages/server/dist/queued-message.d.ts +35 -0
  95. package/packages/server/dist/queued-message.js +116 -0
  96. package/packages/server/dist/read-state.d.ts +46 -0
  97. package/packages/server/dist/read-state.js +139 -0
  98. package/packages/server/dist/recovery-config.d.ts +111 -0
  99. package/packages/server/dist/recovery-config.js +105 -0
  100. package/packages/server/dist/recovery.d.ts +262 -0
  101. package/packages/server/dist/recovery.js +490 -0
  102. package/packages/server/dist/resume-drain.d.ts +122 -0
  103. package/packages/server/dist/resume-drain.js +158 -0
  104. package/packages/server/dist/route-context.d.ts +168 -0
  105. package/packages/server/dist/route-context.js +39 -0
  106. package/packages/server/dist/route-errors.d.ts +8 -0
  107. package/packages/server/dist/route-errors.js +13 -0
  108. package/packages/server/dist/routes/chats.d.ts +12 -0
  109. package/packages/server/dist/routes/chats.js +1406 -0
  110. package/packages/server/dist/routes/git.d.ts +13 -0
  111. package/packages/server/dist/routes/git.js +236 -0
  112. package/packages/server/dist/routes/mcp.d.ts +83 -0
  113. package/packages/server/dist/routes/mcp.js +250 -0
  114. package/packages/server/dist/routes/meta.d.ts +15 -0
  115. package/packages/server/dist/routes/meta.js +447 -0
  116. package/packages/server/dist/routes/projects.d.ts +25 -0
  117. package/packages/server/dist/routes/projects.js +736 -0
  118. package/packages/server/dist/routes/triggers.d.ts +21 -0
  119. package/packages/server/dist/routes/triggers.js +324 -0
  120. package/packages/server/dist/routes/workspace-mount.d.ts +48 -0
  121. package/packages/server/dist/routes/workspace-mount.js +32 -0
  122. package/packages/server/dist/routes.d.ts +26 -0
  123. package/packages/server/dist/routes.js +29 -0
  124. package/packages/server/dist/run-provenance.d.ts +109 -0
  125. package/packages/server/dist/run-provenance.js +206 -0
  126. package/packages/server/dist/runs.d.ts +92 -0
  127. package/packages/server/dist/runs.js +72 -0
  128. package/packages/server/dist/schedule-config.d.ts +68 -0
  129. package/packages/server/dist/schedule-config.js +75 -0
  130. package/packages/server/dist/schedule-session.d.ts +34 -0
  131. package/packages/server/dist/schedule-session.js +120 -0
  132. package/packages/server/dist/self-mcp-descriptions.d.ts +20 -0
  133. package/packages/server/dist/self-mcp-descriptions.js +145 -0
  134. package/packages/server/dist/self-mcp-read.d.ts +15 -0
  135. package/packages/server/dist/self-mcp-read.js +97 -0
  136. package/packages/server/dist/self-mcp-triggers.d.ts +30 -0
  137. package/packages/server/dist/self-mcp-triggers.js +144 -0
  138. package/packages/server/dist/self-mcp-types.d.ts +248 -0
  139. package/packages/server/dist/self-mcp-types.js +10 -0
  140. package/packages/server/dist/self-mcp-util.d.ts +73 -0
  141. package/packages/server/dist/self-mcp-util.js +157 -0
  142. package/packages/server/dist/self-mcp-write.d.ts +35 -0
  143. package/packages/server/dist/self-mcp-write.js +215 -0
  144. package/packages/server/dist/self-mcp.d.ts +95 -0
  145. package/packages/server/dist/self-mcp.js +448 -0
  146. package/packages/server/dist/send-file-mcp.d.ts +46 -0
  147. package/packages/server/dist/send-file-mcp.js +281 -0
  148. package/packages/server/dist/session-hub.d.ts +186 -0
  149. package/packages/server/dist/session-hub.js +291 -0
  150. package/packages/server/dist/spawn-capability.d.ts +99 -0
  151. package/packages/server/dist/spawn-capability.js +106 -0
  152. package/packages/server/dist/star.d.ts +26 -0
  153. package/packages/server/dist/star.js +94 -0
  154. package/packages/server/dist/start.d.ts +1 -0
  155. package/packages/server/dist/start.js +21 -0
  156. package/packages/server/dist/subagents.d.ts +337 -0
  157. package/packages/server/dist/subagents.js +626 -0
  158. package/packages/server/dist/sweep.d.ts +122 -0
  159. package/packages/server/dist/sweep.js +696 -0
  160. package/packages/server/dist/tooldetails.d.ts +33 -0
  161. package/packages/server/dist/tooldetails.js +466 -0
  162. package/packages/server/dist/transcribe.d.ts +69 -0
  163. package/packages/server/dist/transcribe.js +119 -0
  164. package/packages/server/dist/transcripts.d.ts +42 -0
  165. package/packages/server/dist/transcripts.js +164 -0
  166. package/packages/server/dist/trigger-config.d.ts +278 -0
  167. package/packages/server/dist/trigger-config.js +417 -0
  168. package/packages/server/dist/trigger-runtime.d.ts +94 -0
  169. package/packages/server/dist/trigger-runtime.js +82 -0
  170. package/packages/server/dist/trigger-session.d.ts +34 -0
  171. package/packages/server/dist/trigger-session.js +122 -0
  172. package/packages/server/dist/triggers.d.ts +83 -0
  173. package/packages/server/dist/triggers.js +124 -0
  174. package/packages/server/dist/trusted-proxy.d.ts +57 -0
  175. package/packages/server/dist/trusted-proxy.js +157 -0
  176. package/packages/server/dist/turn-notice.d.ts +154 -0
  177. package/packages/server/dist/turn-notice.js +271 -0
  178. package/packages/server/dist/unread.d.ts +36 -0
  179. package/packages/server/dist/unread.js +123 -0
  180. package/packages/server/dist/usage.d.ts +66 -0
  181. package/packages/server/dist/usage.js +243 -0
  182. package/packages/server/dist/wake-injection.d.ts +156 -0
  183. package/packages/server/dist/wake-injection.js +136 -0
  184. package/packages/server/dist/whisper-local.d.ts +9 -0
  185. package/packages/server/dist/whisper-local.js +122 -0
  186. package/packages/server/dist/ws-context.d.ts +158 -0
  187. package/packages/server/dist/ws-context.js +1 -0
  188. package/packages/server/dist/ws-protocol.d.ts +423 -0
  189. package/packages/server/dist/ws-protocol.js +251 -0
  190. package/packages/server/dist/ws-self-mcp.d.ts +52 -0
  191. package/packages/server/dist/ws-self-mcp.js +28 -0
  192. package/packages/server/dist/ws-triggers.d.ts +29 -0
  193. package/packages/server/dist/ws-triggers.js +261 -0
  194. package/packages/server/dist/ws-turn.d.ts +86 -0
  195. package/packages/server/dist/ws-turn.js +751 -0
  196. package/packages/server/dist/ws.d.ts +62 -0
  197. package/packages/server/dist/ws.js +1024 -0
  198. package/packages/server/package.json +6 -0
  199. package/packages/web/dist/assets/InstanceConfigPage-4cQGTj9Y.js +2 -0
  200. package/packages/web/dist/assets/MarkdownRenderer-CJZFLj_8.js +30 -0
  201. package/packages/web/dist/assets/ProjectRedirect-BHwVbbqB.js +2 -0
  202. package/packages/web/dist/assets/ProjectView-BOvuAITg.js +13 -0
  203. package/packages/web/dist/assets/ProjectsGrid-BCojZOGY.js +2 -0
  204. package/packages/web/dist/assets/arc-BVyxvnkl.js +2 -0
  205. package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-Cd9Puqg1.js +37 -0
  206. package/packages/web/dist/assets/bash-I8pq0VWm.js +2 -0
  207. package/packages/web/dist/assets/blockDiagram-GPEHLZMM-B_AQt_Rx.js +133 -0
  208. package/packages/web/dist/assets/c-D2F1sQUL.js +2 -0
  209. package/packages/web/dist/assets/c4Diagram-AAUBKEIU-7e0TQtXB.js +11 -0
  210. package/packages/web/dist/assets/channel-C_1aISNn.js +2 -0
  211. package/packages/web/dist/assets/chunk-2J33WTMH-Dz4WZmUZ.js +2 -0
  212. package/packages/web/dist/assets/chunk-4BX2VUAB-DVWYldo6.js +2 -0
  213. package/packages/web/dist/assets/chunk-55IACEB6-BIMrizQd.js +2 -0
  214. package/packages/web/dist/assets/chunk-727SXJPM-CL2q9fF1.js +207 -0
  215. package/packages/web/dist/assets/chunk-AQP2D5EJ-BSq3HlQ9.js +232 -0
  216. package/packages/web/dist/assets/chunk-FMBD7UC4-Bgt64pLE.js +16 -0
  217. package/packages/web/dist/assets/chunk-ND2GUHAM-C8Pr2A38.js +2 -0
  218. package/packages/web/dist/assets/chunk-QZHKN3VN-BAOqh-MN.js +2 -0
  219. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-B8JE7cHJ.js +2 -0
  220. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-B8JE7cHJ.js +2 -0
  221. package/packages/web/dist/assets/core-lJeGACQg.js +4 -0
  222. package/packages/web/dist/assets/cose-bilkent-S5V4N54A-BF4tameA.js +2 -0
  223. package/packages/web/dist/assets/cpp-Bm3ii6kY.js +2 -0
  224. package/packages/web/dist/assets/csharp-BNpuYZMG.js +2 -0
  225. package/packages/web/dist/assets/css-DazXZka4.js +2 -0
  226. package/packages/web/dist/assets/cytoscape.esm-CUqq0XTU.js +332 -0
  227. package/packages/web/dist/assets/dagre-BM42HDAG-DdsLM6ql.js +5 -0
  228. package/packages/web/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
  229. package/packages/web/dist/assets/diagram-2AECGRRQ-BQ78S_RM.js +44 -0
  230. package/packages/web/dist/assets/diagram-5GNKFQAL-CAKE6nHE.js +11 -0
  231. package/packages/web/dist/assets/diagram-KO2AKTUF-fEvHRXgr.js +4 -0
  232. package/packages/web/dist/assets/diagram-LMA3HP47-CtWsgeSm.js +25 -0
  233. package/packages/web/dist/assets/diagram-OG6HWLK6-Buog8k7_.js +25 -0
  234. package/packages/web/dist/assets/erDiagram-TEJ5UH35-CWvVVPZS.js +86 -0
  235. package/packages/web/dist/assets/flowDiagram-I6XJVG4X-DsCGXtp4.js +163 -0
  236. package/packages/web/dist/assets/format-DamJQ9LO.js +2 -0
  237. package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-CsdwIvvC.js +293 -0
  238. package/packages/web/dist/assets/gitGraphDiagram-PVQCEYII-hZvrX1sD.js +107 -0
  239. package/packages/web/dist/assets/go-BPx_y7j8.js +2 -0
  240. package/packages/web/dist/assets/graph-BlwPajkw.js +2 -0
  241. package/packages/web/dist/assets/index-CGbZdrm8.js +71 -0
  242. package/packages/web/dist/assets/index-DJDQ210-.css +1 -0
  243. package/packages/web/dist/assets/infoDiagram-5YYISTIA-nE_Vtz_l.js +3 -0
  244. package/packages/web/dist/assets/ini-B3BVgEsG.js +2 -0
  245. package/packages/web/dist/assets/init-Gi6I4Gst.js +2 -0
  246. package/packages/web/dist/assets/ishikawaDiagram-YF4QCWOH-2Nlqs0oH.js +71 -0
  247. package/packages/web/dist/assets/java-B5VKqtH2.js +2 -0
  248. package/packages/web/dist/assets/javascript-BKRaQes9.js +2 -0
  249. package/packages/web/dist/assets/journeyDiagram-JHISSGLW-mjU1fMc4.js +140 -0
  250. package/packages/web/dist/assets/json-DIYVocXf.js +2 -0
  251. package/packages/web/dist/assets/kanban-definition-UN3LZRKU-C_9xNaa3.js +90 -0
  252. package/packages/web/dist/assets/katex-C5jXJg4s.js +258 -0
  253. package/packages/web/dist/assets/kotlin-Cw-eF_bm.js +3 -0
  254. package/packages/web/dist/assets/lastTab-CU1zsF0j.js +2 -0
  255. package/packages/web/dist/assets/layout-Boy8vhSP.js +2 -0
  256. package/packages/web/dist/assets/linear-BxKd91ho.js +2 -0
  257. package/packages/web/dist/assets/mermaid.core-C-CuhD8I.js +310 -0
  258. package/packages/web/dist/assets/mindmap-definition-RKZ34NQL-Bs6yfcZv.js +97 -0
  259. package/packages/web/dist/assets/ordinal-Cboi1Yqb.js +2 -0
  260. package/packages/web/dist/assets/php-CRdd4dfS.js +3 -0
  261. package/packages/web/dist/assets/pieDiagram-4H26LBE5-DNdqX7O5.js +31 -0
  262. package/packages/web/dist/assets/python-kSlZHhJb.js +2 -0
  263. package/packages/web/dist/assets/quadrantDiagram-W4KKPZXB-ZMJl9OdZ.js +8 -0
  264. package/packages/web/dist/assets/queued-LamEU0GH.js +2 -0
  265. package/packages/web/dist/assets/requirementDiagram-4Y6WPE33-RmxMdm2q.js +85 -0
  266. package/packages/web/dist/assets/ruby-7T4ktxyv.js +2 -0
  267. package/packages/web/dist/assets/rust-HC3MBUhY.js +2 -0
  268. package/packages/web/dist/assets/sankeyDiagram-5OEKKPKP-OAAhWcfK.js +41 -0
  269. package/packages/web/dist/assets/scss-DZLA7YXz.js +2 -0
  270. package/packages/web/dist/assets/sequenceDiagram-3UESZ5HK-BWF7rCyN.js +163 -0
  271. package/packages/web/dist/assets/sql-BC88IN-V.js +2 -0
  272. package/packages/web/dist/assets/stateDiagram-AJRCARHV-M3b_zqqo.js +2 -0
  273. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CI7saFDa.js +2 -0
  274. package/packages/web/dist/assets/swift-Dn8kKO_1.js +2 -0
  275. package/packages/web/dist/assets/timeline-definition-PNZ67QCA-BpOCh63N.js +121 -0
  276. package/packages/web/dist/assets/typescript-C2FFdlUC.js +2 -0
  277. package/packages/web/dist/assets/vennDiagram-CIIHVFJN-D_iPH45Q.js +35 -0
  278. package/packages/web/dist/assets/wardley-L42UT6IY-DVD9I2Om.js +174 -0
  279. package/packages/web/dist/assets/wardleyDiagram-YWT4CUSO-Bn1IKwjn.js +79 -0
  280. package/packages/web/dist/assets/xml-BXBhIUeX.js +2 -0
  281. package/packages/web/dist/assets/xychartDiagram-2RQKCTM6-DwtI0bkR.js +8 -0
  282. package/packages/web/dist/assets/yaml-drsDnIKT.js +2 -0
  283. package/packages/web/dist/favicon.ico +0 -0
  284. package/packages/web/dist/fonts/inter-latin.woff2 +0 -0
  285. package/packages/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
  286. package/packages/web/dist/icons/apple-touch-icon.png +0 -0
  287. package/packages/web/dist/icons/favicon-16.png +0 -0
  288. package/packages/web/dist/icons/favicon-32.png +0 -0
  289. package/packages/web/dist/icons/icon-192.png +0 -0
  290. package/packages/web/dist/icons/icon-512.png +0 -0
  291. package/packages/web/dist/icons/maskable-512.png +0 -0
  292. package/packages/web/dist/index.html +56 -0
  293. package/packages/web/dist/manifest.webmanifest +32 -0
  294. package/packages/web/dist/sw.js +139 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Models module — the single source of truth for the Claude model ids paddock
3
+ * exposes (keeper + sweeper defaults, the picker list, and per-model context
4
+ * limits).
5
+ *
6
+ * The wire contract (CONTRACT-v3 §2/§3) and every consumer (the `/api/models`
7
+ * route, the project `model` resolution in projects.ts, the keeper/sweeper
8
+ * registration in herdctl.ts, and the context-meter math in ws.ts) read these
9
+ * constants — so changing the available models or a context limit is a one-file
10
+ * edit here.
11
+ *
12
+ * Context limits (verified against the Models API): Opus 5, Fable 5, Opus 4.8
13
+ * and Sonnet 5 all have a 1,000,000-token context window; Haiku 4.5 is 200,000.
14
+ * On the Max plan the keeper agents run Opus 5 at its full 1M window, so
15
+ * the context meter must use 1M for it — otherwise a long chat reads >100%.
16
+ */
17
+ /**
18
+ * Public first-party API list price per 1M tokens, used only to put a
19
+ * ballpark dollar figure on a chat's cumulative token consumption (issue #152).
20
+ * On the Max plan the keeper agents don't draw against a per-token
21
+ * quota, so this is an "at API rates" estimate for comparison, not real spend —
22
+ * the token counts are the honest metric. Cache-write / cache-read rates are the
23
+ * standard multiples of the input rate (5-minute ephemeral write = 1.25×, read =
24
+ * 0.1×), applied in {@link estimateCostUsd}.
25
+ */
26
+ export interface ModelPricing {
27
+ /** USD per 1M input tokens. */
28
+ inputPer1M: number;
29
+ /** USD per 1M output tokens. */
30
+ outputPer1M: number;
31
+ }
32
+ /** A single selectable model: its id, a human label, and its context window. */
33
+ export interface ModelInfo {
34
+ /** The Claude model id passed to herdctl / the SDK (e.g. "claude-opus-4-8"). */
35
+ id: string;
36
+ /** Human-friendly label for the picker (e.g. "Opus 4.8"). */
37
+ label: string;
38
+ /** Total context window in tokens (used for the context meter). */
39
+ contextLimit: number;
40
+ /** First-party list price per 1M tokens, for the cumulative-cost estimate. */
41
+ pricing?: ModelPricing;
42
+ }
43
+ /**
44
+ * The selectable models, in picker order. The first entry is the keeper default
45
+ * (Opus); the cheap Haiku is the sweeper default. Order here is the order the UI
46
+ * renders.
47
+ */
48
+ export declare const MODELS: ModelInfo[];
49
+ /** The model a project runs on unless the project overrides it. */
50
+ export declare const DEFAULT_MODEL = "claude-opus-5";
51
+ /**
52
+ * Resolve the models OFFERED (the picker/allow-list) from an optional list of
53
+ * allowed ids (issue #457 Step 2). {@link MODELS} stays the authoritative CATALOG
54
+ * — the source of every model's metadata (label/contextLimit/pricing) and the
55
+ * validation set for {@link isKnownModel}; this only narrows WHICH catalog models
56
+ * an instance/project offers.
57
+ *
58
+ * - `allowIds` undefined or empty ⇒ every catalog model is offered (today's
59
+ * behaviour, fully backward-compatible).
60
+ * - Otherwise the catalog is filtered to `allowIds`, PRESERVING catalog order and
61
+ * metadata (the returned entries are the catalog's own `ModelInfo` objects, so
62
+ * an operator can't set a wrong context limit — they only pick by id).
63
+ * - Unknown ids in `allowIds` are ignored (they aren't in the catalog).
64
+ *
65
+ * The result may be empty only when every id was unknown; callers that must never
66
+ * offer zero models (the config loader) collapse that back to the full catalog.
67
+ */
68
+ export declare function resolveModels(allowIds?: string[]): ModelInfo[];
69
+ /**
70
+ * The effective default model for a resolved model list: {@link DEFAULT_MODEL}
71
+ * when it's still offered, else the first offered model's id (the list is in
72
+ * picker order, so the first entry is the natural default). Falls back to
73
+ * {@link DEFAULT_MODEL} when the list is empty, so callers always get a
74
+ * concrete id.
75
+ */
76
+ export declare function resolveDefaultModel(models: ModelInfo[]): string;
77
+ /**
78
+ * Per-project keeper-agent settings surfaced in the project settings UI
79
+ * (issue #12). These mirror the fleet-wide `defaults` in herdctl.ts so a project
80
+ * that doesn't override a field inherits the same value it would have anyway —
81
+ * they're the single source of truth for both the defaults block and the DTO
82
+ * resolution.
83
+ */
84
+ /**
85
+ * The keeper `permission_mode` values offered in the settings UI. A curated
86
+ * subset of herdctl's PermissionMode enum (default / acceptEdits / plan /
87
+ * bypassPermissions) — the niche delegate/dontAsk modes are omitted.
88
+ */
89
+ export declare const PERMISSION_MODES: readonly ["default", "acceptEdits", "plan", "bypassPermissions"];
90
+ export type PermissionMode = (typeof PERMISSION_MODES)[number];
91
+ /** Defaults inherited when a project doesn't override the field. */
92
+ export declare const DEFAULT_PERMISSION_MODE: PermissionMode;
93
+ export declare const DEFAULT_MAX_TURNS = 200;
94
+ export declare const DEFAULT_DOCKER = false;
95
+ /** Upper bound on a project's `max_turns` (guards the UI + PATCH validation). */
96
+ export declare const MAX_TURNS_LIMIT = 1000;
97
+ /**
98
+ * How a keeper chat turn is driven (Paddock#111):
99
+ * - `batch` — one-shot `FleetManager.trigger()` per turn (the legacy path);
100
+ * the `claude` process exits at the turn boundary, so scheduled
101
+ * wakeups / background tasks silently die.
102
+ * - `session` — a persistent, herdctl-managed `openChatSession` per turn
103
+ * (`manageLifecycle: true`); the session is reaped when idle and
104
+ * its timer-class wakeups are re-triggered through the scheduler,
105
+ * so cross-turn autonomy (`ScheduleWakeup`, `/loop`) actually
106
+ * works. See herdctl#307 (reaper) and herdctl#303 (SDK bump).
107
+ */
108
+ export declare const DRIVE_MODES: readonly ["batch", "session"];
109
+ export type DriveMode = (typeof DRIVE_MODES)[number];
110
+ /**
111
+ * Default drive mode. `session` (#316): a fresh/un-configured instance
112
+ * gets cross-turn autonomy (`ScheduleWakeup`, `/loop`, reaper-backed background
113
+ * work) and the SDK streaming runtime by default, instead of only when an
114
+ * operator sets `PADDOCK_DRIVE_MODE=session`. The env var and per-project
115
+ * `driveMode` override still take precedence; this only moves the built-in
116
+ * default off the legacy one-shot `batch` path.
117
+ */
118
+ export declare const DEFAULT_DRIVE_MODE: DriveMode;
119
+ /** Whether `m` is one of the offered keeper permission modes. */
120
+ export declare function isKnownPermissionMode(m: string): m is PermissionMode;
121
+ /** Whether `m` is a known keeper drive mode. */
122
+ export declare function isKnownDriveMode(m: string): m is DriveMode;
123
+ /** Whether `n` is a valid keeper `max_turns` (positive integer within bounds). */
124
+ export declare function isValidMaxTurns(n: unknown): n is number;
125
+ /** The cheap model the post-turn sweeper (curator) always uses. */
126
+ export declare const SWEEPER_DEFAULT_MODEL = "claude-haiku-4-5-20251001";
127
+ /** Whether `id` is one of the known selectable models. */
128
+ export declare function isKnownModel(id: string): boolean;
129
+ /**
130
+ * The context limit (in tokens) for a model id. Falls back to 200000 for an
131
+ * unknown id so the context meter never divides by an undefined/zero limit.
132
+ */
133
+ export declare function getContextLimit(id: string): number;
134
+ /** The full ModelInfo for a model id, or undefined if it isn't a known model. */
135
+ export declare function getModelInfo(id: string): ModelInfo | undefined;
136
+ /** The four cumulative token totals a chat's dollar estimate is priced from. */
137
+ export interface TokenTotals {
138
+ inputTokens: number;
139
+ outputTokens: number;
140
+ cacheReadTokens: number;
141
+ cacheCreationTokens: number;
142
+ }
143
+ /**
144
+ * Ballpark USD cost of a chat's cumulative token consumption at first-party API
145
+ * list prices, or `null` for a model with no known pricing. Each token class is
146
+ * priced separately — input, output, cache-write (1.25× input) and cache-read
147
+ * (0.1× input) — because a single grand sum of "context tokens" would both
148
+ * double-count (each turn's input re-reflects the whole growing context, which
149
+ * is why {@link TokenTotals} must come from a per-turn cumulative sum) and
150
+ * misprice (output is 5× input, cache-read a tenth of it).
151
+ */
152
+ export declare function estimateCostUsd(modelId: string, t: TokenTotals): number | null;
153
+ /**
154
+ * Cost of a chat that may span several models, priced from each model's own
155
+ * rate. A chat's turns can run on different models (the composer lets you switch
156
+ * model per turn, and the project default may differ from what actually ran), so
157
+ * pricing the whole chat at one model — e.g. the project default — misprices by
158
+ * the ratio between them (a Haiku chat billed at Opus rates is 5× too high).
159
+ * Keys are the `message.model` recorded on each assistant turn; totals for a
160
+ * model with no known pricing are skipped. Returns the summed cost, or `null`
161
+ * only when *no* group could be priced (so an entirely-unknown-model chat hides
162
+ * its cost rather than showing $0.00).
163
+ */
164
+ export declare function estimateCostUsdByModel(byModel: Record<string, TokenTotals>): number | null;
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Models module — the single source of truth for the Claude model ids paddock
3
+ * exposes (keeper + sweeper defaults, the picker list, and per-model context
4
+ * limits).
5
+ *
6
+ * The wire contract (CONTRACT-v3 §2/§3) and every consumer (the `/api/models`
7
+ * route, the project `model` resolution in projects.ts, the keeper/sweeper
8
+ * registration in herdctl.ts, and the context-meter math in ws.ts) read these
9
+ * constants — so changing the available models or a context limit is a one-file
10
+ * edit here.
11
+ *
12
+ * Context limits (verified against the Models API): Opus 5, Fable 5, Opus 4.8
13
+ * and Sonnet 5 all have a 1,000,000-token context window; Haiku 4.5 is 200,000.
14
+ * On the Max plan the keeper agents run Opus 5 at its full 1M window, so
15
+ * the context meter must use 1M for it — otherwise a long chat reads >100%.
16
+ */
17
+ /**
18
+ * The selectable models, in picker order. The first entry is the keeper default
19
+ * (Opus); the cheap Haiku is the sweeper default. Order here is the order the UI
20
+ * renders.
21
+ */
22
+ export const MODELS = [
23
+ {
24
+ id: "claude-opus-5",
25
+ label: "Opus 5",
26
+ contextLimit: 1_000_000,
27
+ pricing: { inputPer1M: 5, outputPer1M: 25 },
28
+ },
29
+ {
30
+ id: "claude-opus-4-8",
31
+ label: "Opus 4.8",
32
+ contextLimit: 1_000_000,
33
+ pricing: { inputPer1M: 5, outputPer1M: 25 },
34
+ },
35
+ {
36
+ id: "claude-fable-5",
37
+ label: "Fable 5",
38
+ contextLimit: 1_000_000,
39
+ pricing: { inputPer1M: 10, outputPer1M: 50 },
40
+ },
41
+ {
42
+ id: "claude-sonnet-5",
43
+ label: "Sonnet 5",
44
+ contextLimit: 1_000_000,
45
+ pricing: { inputPer1M: 3, outputPer1M: 15 },
46
+ },
47
+ {
48
+ id: "claude-haiku-4-5-20251001",
49
+ label: "Haiku 4.5",
50
+ contextLimit: 200_000,
51
+ pricing: { inputPer1M: 1, outputPer1M: 5 },
52
+ },
53
+ ];
54
+ /** The model a project runs on unless the project overrides it. */
55
+ export const DEFAULT_MODEL = "claude-opus-5";
56
+ /**
57
+ * Resolve the models OFFERED (the picker/allow-list) from an optional list of
58
+ * allowed ids (issue #457 Step 2). {@link MODELS} stays the authoritative CATALOG
59
+ * — the source of every model's metadata (label/contextLimit/pricing) and the
60
+ * validation set for {@link isKnownModel}; this only narrows WHICH catalog models
61
+ * an instance/project offers.
62
+ *
63
+ * - `allowIds` undefined or empty ⇒ every catalog model is offered (today's
64
+ * behaviour, fully backward-compatible).
65
+ * - Otherwise the catalog is filtered to `allowIds`, PRESERVING catalog order and
66
+ * metadata (the returned entries are the catalog's own `ModelInfo` objects, so
67
+ * an operator can't set a wrong context limit — they only pick by id).
68
+ * - Unknown ids in `allowIds` are ignored (they aren't in the catalog).
69
+ *
70
+ * The result may be empty only when every id was unknown; callers that must never
71
+ * offer zero models (the config loader) collapse that back to the full catalog.
72
+ */
73
+ export function resolveModels(allowIds) {
74
+ if (!allowIds || allowIds.length === 0)
75
+ return MODELS;
76
+ const allow = new Set(allowIds);
77
+ return MODELS.filter((m) => allow.has(m.id));
78
+ }
79
+ /**
80
+ * The effective default model for a resolved model list: {@link DEFAULT_MODEL}
81
+ * when it's still offered, else the first offered model's id (the list is in
82
+ * picker order, so the first entry is the natural default). Falls back to
83
+ * {@link DEFAULT_MODEL} when the list is empty, so callers always get a
84
+ * concrete id.
85
+ */
86
+ export function resolveDefaultModel(models) {
87
+ if (models.length === 0)
88
+ return DEFAULT_MODEL;
89
+ return models.some((m) => m.id === DEFAULT_MODEL) ? DEFAULT_MODEL : models[0].id;
90
+ }
91
+ /**
92
+ * Per-project keeper-agent settings surfaced in the project settings UI
93
+ * (issue #12). These mirror the fleet-wide `defaults` in herdctl.ts so a project
94
+ * that doesn't override a field inherits the same value it would have anyway —
95
+ * they're the single source of truth for both the defaults block and the DTO
96
+ * resolution.
97
+ */
98
+ /**
99
+ * The keeper `permission_mode` values offered in the settings UI. A curated
100
+ * subset of herdctl's PermissionMode enum (default / acceptEdits / plan /
101
+ * bypassPermissions) — the niche delegate/dontAsk modes are omitted.
102
+ */
103
+ export const PERMISSION_MODES = ["default", "acceptEdits", "plan", "bypassPermissions"];
104
+ /** Defaults inherited when a project doesn't override the field. */
105
+ export const DEFAULT_PERMISSION_MODE = "acceptEdits";
106
+ export const DEFAULT_MAX_TURNS = 200;
107
+ export const DEFAULT_DOCKER = false;
108
+ /** Upper bound on a project's `max_turns` (guards the UI + PATCH validation). */
109
+ export const MAX_TURNS_LIMIT = 1000;
110
+ /**
111
+ * How a keeper chat turn is driven (Paddock#111):
112
+ * - `batch` — one-shot `FleetManager.trigger()` per turn (the legacy path);
113
+ * the `claude` process exits at the turn boundary, so scheduled
114
+ * wakeups / background tasks silently die.
115
+ * - `session` — a persistent, herdctl-managed `openChatSession` per turn
116
+ * (`manageLifecycle: true`); the session is reaped when idle and
117
+ * its timer-class wakeups are re-triggered through the scheduler,
118
+ * so cross-turn autonomy (`ScheduleWakeup`, `/loop`) actually
119
+ * works. See herdctl#307 (reaper) and herdctl#303 (SDK bump).
120
+ */
121
+ export const DRIVE_MODES = ["batch", "session"];
122
+ /**
123
+ * Default drive mode. `session` (#316): a fresh/un-configured instance
124
+ * gets cross-turn autonomy (`ScheduleWakeup`, `/loop`, reaper-backed background
125
+ * work) and the SDK streaming runtime by default, instead of only when an
126
+ * operator sets `PADDOCK_DRIVE_MODE=session`. The env var and per-project
127
+ * `driveMode` override still take precedence; this only moves the built-in
128
+ * default off the legacy one-shot `batch` path.
129
+ */
130
+ export const DEFAULT_DRIVE_MODE = "session";
131
+ /** Whether `m` is one of the offered keeper permission modes. */
132
+ export function isKnownPermissionMode(m) {
133
+ return PERMISSION_MODES.includes(m);
134
+ }
135
+ /** Whether `m` is a known keeper drive mode. */
136
+ export function isKnownDriveMode(m) {
137
+ return DRIVE_MODES.includes(m);
138
+ }
139
+ /** Whether `n` is a valid keeper `max_turns` (positive integer within bounds). */
140
+ export function isValidMaxTurns(n) {
141
+ return typeof n === "number" && Number.isInteger(n) && n >= 1 && n <= MAX_TURNS_LIMIT;
142
+ }
143
+ /** The cheap model the post-turn sweeper (curator) always uses. */
144
+ export const SWEEPER_DEFAULT_MODEL = "claude-haiku-4-5-20251001";
145
+ /** Default context limit when a model id is unknown (every current model is 200k). */
146
+ const DEFAULT_CONTEXT_LIMIT = 200000;
147
+ /** Whether `id` is one of the known selectable models. */
148
+ export function isKnownModel(id) {
149
+ return MODELS.some((m) => m.id === id);
150
+ }
151
+ /**
152
+ * The context limit (in tokens) for a model id. Falls back to 200000 for an
153
+ * unknown id so the context meter never divides by an undefined/zero limit.
154
+ */
155
+ export function getContextLimit(id) {
156
+ return MODELS.find((m) => m.id === id)?.contextLimit ?? DEFAULT_CONTEXT_LIMIT;
157
+ }
158
+ /** The full ModelInfo for a model id, or undefined if it isn't a known model. */
159
+ export function getModelInfo(id) {
160
+ return MODELS.find((m) => m.id === id);
161
+ }
162
+ /** Cache-write (5-minute ephemeral) is 1.25× the input rate; cache-read is 0.1×. */
163
+ const CACHE_WRITE_MULTIPLIER = 1.25;
164
+ const CACHE_READ_MULTIPLIER = 0.1;
165
+ /**
166
+ * Ballpark USD cost of a chat's cumulative token consumption at first-party API
167
+ * list prices, or `null` for a model with no known pricing. Each token class is
168
+ * priced separately — input, output, cache-write (1.25× input) and cache-read
169
+ * (0.1× input) — because a single grand sum of "context tokens" would both
170
+ * double-count (each turn's input re-reflects the whole growing context, which
171
+ * is why {@link TokenTotals} must come from a per-turn cumulative sum) and
172
+ * misprice (output is 5× input, cache-read a tenth of it).
173
+ */
174
+ export function estimateCostUsd(modelId, t) {
175
+ const pricing = getModelInfo(modelId)?.pricing;
176
+ if (!pricing)
177
+ return null;
178
+ const { inputPer1M, outputPer1M } = pricing;
179
+ const usd = (t.inputTokens * inputPer1M +
180
+ t.cacheCreationTokens * inputPer1M * CACHE_WRITE_MULTIPLIER +
181
+ t.cacheReadTokens * inputPer1M * CACHE_READ_MULTIPLIER +
182
+ t.outputTokens * outputPer1M) /
183
+ 1_000_000;
184
+ return usd;
185
+ }
186
+ /**
187
+ * Cost of a chat that may span several models, priced from each model's own
188
+ * rate. A chat's turns can run on different models (the composer lets you switch
189
+ * model per turn, and the project default may differ from what actually ran), so
190
+ * pricing the whole chat at one model — e.g. the project default — misprices by
191
+ * the ratio between them (a Haiku chat billed at Opus rates is 5× too high).
192
+ * Keys are the `message.model` recorded on each assistant turn; totals for a
193
+ * model with no known pricing are skipped. Returns the summed cost, or `null`
194
+ * only when *no* group could be priced (so an entirely-unknown-model chat hides
195
+ * its cost rather than showing $0.00).
196
+ */
197
+ export function estimateCostUsdByModel(byModel) {
198
+ let usd = 0;
199
+ let priced = false;
200
+ for (const [modelId, totals] of Object.entries(byModel)) {
201
+ const c = estimateCostUsd(modelId, totals);
202
+ if (c == null)
203
+ continue;
204
+ usd += c;
205
+ priced = true;
206
+ }
207
+ return priced ? usd : null;
208
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * OpenAPI document scaffolding for the Paddock HTTP API.
3
+ *
4
+ * The spec is *derived from the route schemas* — every REST route attaches a
5
+ * Fastify `schema` ({ tags, summary, description, params, querystring, body,
6
+ * response }) and `@fastify/swagger` (registered in app.ts before the routes)
7
+ * collects them into a live OpenAPI 3 document. When enabled
8
+ * (`PADDOCK_OPENAPI_ENABLED`) it is served at `/open-api` (Swagger UI) with the
9
+ * raw spec at `/open-api/json` plus the `/open-api.json` alias; the prefix is
10
+ * configurable via `PADDOCK_OPENAPI_PATH`. This module only provides the
11
+ * top-level document metadata (info, tags, security) that Fastify can't infer
12
+ * from routes, plus the Paddock-branded Swagger-UI mount options.
13
+ *
14
+ * IMPORTANT — the live conversational surface is NOT REST. Sending a message,
15
+ * streaming the reply, tool-call events, cancel, slash-commands and the live
16
+ * queue all flow over the WebSocket at `GET /ws` (see ws-protocol.ts). This spec
17
+ * documents the HTTP surface (reads + project/chat/trigger/git management +
18
+ * config + attachment upload/serve); the WS frame protocol is described in
19
+ * docs/API.md.
20
+ */
21
+ import type { SwaggerOptions } from "@fastify/swagger";
22
+ import type { FastifySwaggerUiOptions } from "@fastify/swagger-ui";
23
+ import type { AuthConfig } from "./config.js";
24
+ /** Tag groups — these MUST match the `tags` each route declares in its schema. */
25
+ export declare const OPENAPI_TAGS: readonly [{
26
+ readonly name: "System";
27
+ readonly description: "Health, identity, models, fleet, instance config, voice.";
28
+ }, {
29
+ readonly name: "Attachments";
30
+ readonly description: "Composer file uploads and serving agent-shared file bytes.";
31
+ }, {
32
+ readonly name: "Git";
33
+ readonly description: "Repo remote state, GitHub auth, per-project status/diff/commit.";
34
+ }, {
35
+ readonly name: "Projects";
36
+ readonly description: "Project CRUD, promote, files, changelog/overview, pins.";
37
+ }, {
38
+ readonly name: "Triggers";
39
+ readonly description: "Unified schedule/event/webhook triggers: CRUD, runtime status, run-now.";
40
+ }, {
41
+ readonly name: "Chats";
42
+ readonly description: "Chat (session) lifecycle: list, read messages, usage/context, fork, archive, star, rename, delete, run history, promote. NOTE: sending a message is WebSocket-only (GET /ws).";
43
+ }];
44
+ /**
45
+ * Build the @fastify/swagger options for a given app version + auth config. The
46
+ * security schemes and the Authentication section of the description reflect the
47
+ * running instance's mode (see {@link authDoc}).
48
+ */
49
+ export declare function buildSwaggerOptions(version: string, auth: AuthConfig): SwaggerOptions;
50
+ /** A logo/favicon image passed to Swagger UI ({ type, content } bytes). */
51
+ export interface SwaggerImage {
52
+ type: string;
53
+ content: Buffer;
54
+ }
55
+ export interface SwaggerUiBranding {
56
+ /** Route prefix the UI mounts under (e.g. `/open-api`). */
57
+ routePrefix: string;
58
+ /** Topbar logo image (the Paddock horse). */
59
+ logo?: SwaggerImage;
60
+ /** Browser-tab favicon image. */
61
+ favicon?: SwaggerImage;
62
+ /** Brand accent hex (e.g. #c2603c) used for the topbar trim. */
63
+ accent: string;
64
+ }
65
+ /** Swagger-UI mount options (served at `routePrefix`, default `/open-api`), Paddock-branded. */
66
+ export declare function buildSwaggerUiOptions(brand: SwaggerUiBranding): FastifySwaggerUiOptions;
@@ -0,0 +1,183 @@
1
+ /** Tag groups — these MUST match the `tags` each route declares in its schema. */
2
+ export const OPENAPI_TAGS = [
3
+ { name: "System", description: "Health, identity, models, fleet, instance config, voice." },
4
+ { name: "Attachments", description: "Composer file uploads and serving agent-shared file bytes." },
5
+ { name: "Git", description: "Repo remote state, GitHub auth, per-project status/diff/commit." },
6
+ { name: "Projects", description: "Project CRUD, promote, files, changelog/overview, pins." },
7
+ {
8
+ name: "Triggers",
9
+ description: "Unified schedule/event/webhook triggers: CRUD, runtime status, run-now.",
10
+ },
11
+ {
12
+ name: "Chats",
13
+ description: "Chat (session) lifecycle: list, read messages, usage/context, fork, archive, star, rename, delete, run history, promote. NOTE: sending a message is WebSocket-only (GET /ws).",
14
+ },
15
+ ];
16
+ const BASE_DESCRIPTION = [
17
+ "HTTP API for Paddock — the Claude Code workspace platform (persistent chats, projects, unified triggers).",
18
+ "",
19
+ "This document is **generated from the server's route schemas** via `@fastify/swagger`, so it",
20
+ "tracks the code: a new or changed route with a schema shows up here automatically.",
21
+ "",
22
+ "**Live chat is WebSocket, not REST.** Sending a message, streaming the response, tool-call",
23
+ "events, cancel, slash-commands and the live queue all flow over `GET /ws`. This spec covers the",
24
+ "HTTP surface: reads, project/chat/trigger/git management, instance config, and attachment",
25
+ "upload/serve. The two-step attachment flow is: `POST .../chats/:sessionId/upload` (HTTP, here)",
26
+ "then reference the returned file ids in the `chat:send` WebSocket frame.",
27
+ ].join("\n");
28
+ /**
29
+ * Resolve the OpenAPI security schemes + global requirement + a docs paragraph
30
+ * from the *running instance's* auth config. Paddock has no login of its own —
31
+ * auth is provider-agnostic (see AUTH.md), so the schemes reflect whichever mode
32
+ * this instance is configured for:
33
+ *
34
+ * - `none` → no scheme, no requirement (open; every request anonymous).
35
+ * - `trusted-header` → apiKey in the configured identity header (proxy-injected).
36
+ * - `jwt` → bearer (when the header is `Authorization`) or apiKey in
37
+ * the configured header, verified against the JWKS.
38
+ *
39
+ * The single most important operational note: when Swagger UI is served BY an
40
+ * authenticated Paddock instance on the SAME origin (e.g. behind Authentik /
41
+ * oauth2-proxy), a "Try it out" call is a same-origin XHR that already carries
42
+ * the browser's SSO session — the proxy authenticates it and injects the
43
+ * identity, so no token needs to be pasted into the Authorize dialog at all. The
44
+ * Authorize button matters for the token path: calling from a different origin,
45
+ * or a programmatic client, or testing jwt mode without the proxy in front.
46
+ */
47
+ function authDoc(auth) {
48
+ const header = "\n\n## Authentication\n\n";
49
+ const shared = "Paddock has no built-in login — authentication is provider-agnostic and handled at the edge " +
50
+ "(reverse proxy / IdP). **When these docs are served by the Paddock instance itself, behind your " +
51
+ "proxy, a `Try it out` request is a same-origin call that inherits your existing SSO session — it " +
52
+ "authenticates automatically, nothing to paste here.** Use the **Authorize** button only for the " +
53
+ "explicit-token path (a different origin, a programmatic client, or testing without the proxy).";
54
+ if (auth.mode === "trusted-header") {
55
+ return {
56
+ securitySchemes: {
57
+ proxyIdentity: {
58
+ type: "apiKey",
59
+ in: "header",
60
+ name: auth.userHeader,
61
+ description: `Trusted-header auth (\`PADDOCK_AUTH_MODE=trusted-header\`). The identity header your ` +
62
+ `reverse proxy injects after SSO — here \`${auth.userHeader}\` ` +
63
+ `(configurable via \`PADDOCK_AUTH_USER_HEADER\`). In production the proxy sets this and ` +
64
+ `Paddock trusts it, so the app MUST be reachable only through the proxy (a direct client ` +
65
+ `could otherwise forge the header).`,
66
+ },
67
+ },
68
+ security: [{ proxyIdentity: [] }],
69
+ paragraph: header +
70
+ shared +
71
+ `\n\nThis instance runs **trusted-header** mode: identity comes from the \`${auth.userHeader}\` header.`,
72
+ };
73
+ }
74
+ if (auth.mode === "jwt") {
75
+ const isBearer = auth.jwtHeader.toLowerCase() === "authorization";
76
+ const scheme = isBearer
77
+ ? {
78
+ bearerAuth: {
79
+ type: "http",
80
+ scheme: "bearer",
81
+ bearerFormat: "JWT",
82
+ description: "JWT auth (`PADDOCK_AUTH_MODE=jwt`). A signed token verified against the configured " +
83
+ "JWKS (`PADDOCK_AUTH_JWKS_URL`), sent as `Authorization: Bearer <token>`.",
84
+ },
85
+ }
86
+ : {
87
+ jwtHeader: {
88
+ type: "apiKey",
89
+ in: "header",
90
+ name: auth.jwtHeader,
91
+ description: `JWT auth (\`PADDOCK_AUTH_MODE=jwt\`). A signed token verified against the JWKS, sent ` +
92
+ `in the \`${auth.jwtHeader}\` header.`,
93
+ },
94
+ };
95
+ const schemeName = isBearer ? "bearerAuth" : "jwtHeader";
96
+ return {
97
+ securitySchemes: scheme,
98
+ security: [{ [schemeName]: [] }],
99
+ paragraph: header +
100
+ shared +
101
+ `\n\nThis instance runs **jwt** mode: a signed token in the \`${auth.jwtHeader}\` header, ` +
102
+ `verified against the JWKS.`,
103
+ };
104
+ }
105
+ // mode === "none"
106
+ return {
107
+ paragraph: header +
108
+ shared +
109
+ "\n\nThis instance runs **none** mode (open, unauthenticated) — every request is anonymous. " +
110
+ "A real deployment sits behind a proxy in `trusted-header` or `jwt` mode.",
111
+ };
112
+ }
113
+ /**
114
+ * Build the @fastify/swagger options for a given app version + auth config. The
115
+ * security schemes and the Authentication section of the description reflect the
116
+ * running instance's mode (see {@link authDoc}).
117
+ */
118
+ export function buildSwaggerOptions(version, auth) {
119
+ const a = authDoc(auth);
120
+ return {
121
+ openapi: {
122
+ openapi: "3.0.3",
123
+ info: {
124
+ title: "Paddock HTTP API",
125
+ description: BASE_DESCRIPTION + a.paragraph,
126
+ version,
127
+ },
128
+ tags: OPENAPI_TAGS.map((t) => ({ name: t.name, description: t.description })),
129
+ ...(a.securitySchemes ? { components: { securitySchemes: a.securitySchemes } } : {}),
130
+ ...(a.security ? { security: a.security } : {}),
131
+ },
132
+ // Keep untagged routes visible rather than hidden (the /ws upgrade is hidden
133
+ // separately via schema.hide).
134
+ hideUntagged: false,
135
+ };
136
+ }
137
+ /**
138
+ * Custom Swagger-UI CSS: swap in the Paddock look. Darkens the topbar, sizes the
139
+ * injected logo, tints the trim with the brand accent, and hides the spec-URL bar
140
+ * (which otherwise exposes the server's own URL in the header). Everything else
141
+ * is stock Swagger UI.
142
+ */
143
+ function brandCss(accent) {
144
+ return [
145
+ ".swagger-ui .topbar { background-color: #14181f; border-bottom: 3px solid " + accent + "; }",
146
+ ".swagger-ui .topbar .download-url-wrapper { display: none; }",
147
+ ".swagger-ui .topbar a { max-height: 40px; }",
148
+ ".swagger-ui .topbar a img { height: 36px; width: auto; content: normal; }",
149
+ ".swagger-ui .info .title small.version-stamp { background: " + accent + "; }",
150
+ ].join("\n");
151
+ }
152
+ /** Swagger-UI mount options (served at `routePrefix`, default `/open-api`), Paddock-branded. */
153
+ export function buildSwaggerUiOptions(brand) {
154
+ const opts = {
155
+ routePrefix: brand.routePrefix,
156
+ uiConfig: {
157
+ docExpansion: "list",
158
+ deepLinking: true,
159
+ displayRequestDuration: true,
160
+ persistAuthorization: true,
161
+ },
162
+ theme: {
163
+ title: "Paddock HTTP API",
164
+ css: [{ filename: "paddock.css", content: brandCss(brand.accent) }],
165
+ ...(brand.favicon
166
+ ? {
167
+ favicon: [
168
+ {
169
+ filename: "favicon.png",
170
+ rel: "icon",
171
+ type: brand.favicon.type,
172
+ sizes: "32x32",
173
+ content: brand.favicon.content,
174
+ },
175
+ ],
176
+ }
177
+ : {}),
178
+ },
179
+ };
180
+ if (brand.logo)
181
+ opts.logo = { type: brand.logo.type, content: brand.logo.content };
182
+ return opts;
183
+ }
@@ -0,0 +1,30 @@
1
+ export declare class ParentDetachStore {
2
+ private readonly stateFile;
3
+ /** In-memory set of detached keys (loaded once, written through on change). */
4
+ private detached;
5
+ /**
6
+ * The in-flight load, cached so concurrent first-callers share ONE read — the
7
+ * same lost-update guard {@link ArchiveStore} carries.
8
+ */
9
+ private loadPromise;
10
+ /** Serialises concurrent writes so the file never interleaves. */
11
+ private writing;
12
+ constructor(dataDir: string);
13
+ /** Load the persisted set (lazily, deduped; tolerant of a missing/corrupt file). */
14
+ private ensureLoaded;
15
+ /**
16
+ * Has this chat been detached from its parent? Non-throwing: any load error
17
+ * reads as false, so a corrupt sidecar degrades to "nesting as recorded"
18
+ * rather than flattening the whole list.
19
+ */
20
+ isDetached(agent: string, sessionId: string): Promise<boolean>;
21
+ /**
22
+ * Set (or clear) a chat's detached flag, persisting the change. Idempotent;
23
+ * returns whether the flag actually CHANGED. Clearing it re-exposes whatever
24
+ * the two resolver tiers say — which is what makes re-attach free: nothing was
25
+ * ever destroyed, only overridden.
26
+ */
27
+ setDetached(agent: string, sessionId: string, detached: boolean): Promise<boolean>;
28
+ /** Write-through, serialised so overlapping toggles can't corrupt the file. */
29
+ private persist;
30
+ }