@withbuddi/buddi 0.1.0-pre.37 → 0.1.0-pre.39

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 (815) hide show
  1. package/extension/background.js +764 -32
  2. package/extension/manifest.json +2 -2
  3. package/node_modules/@buddi/cli/dist/.tsbuildinfo +1 -1
  4. package/node_modules/@buddi/cli/dist/access-cmd.d.ts +10 -0
  5. package/node_modules/@buddi/cli/dist/access-cmd.d.ts.map +1 -0
  6. package/node_modules/@buddi/cli/dist/access-cmd.js +180 -0
  7. package/node_modules/@buddi/cli/dist/access-cmd.js.map +1 -0
  8. package/node_modules/@buddi/cli/dist/args.d.ts +10 -0
  9. package/node_modules/@buddi/cli/dist/args.d.ts.map +1 -1
  10. package/node_modules/@buddi/cli/dist/args.js +34 -1
  11. package/node_modules/@buddi/cli/dist/args.js.map +1 -1
  12. package/node_modules/@buddi/cli/dist/commands.d.ts.map +1 -1
  13. package/node_modules/@buddi/cli/dist/commands.js +39 -2
  14. package/node_modules/@buddi/cli/dist/commands.js.map +1 -1
  15. package/node_modules/@buddi/cli/dist/doctor-probes.d.ts.map +1 -1
  16. package/node_modules/@buddi/cli/dist/doctor-probes.js +54 -29
  17. package/node_modules/@buddi/cli/dist/doctor-probes.js.map +1 -1
  18. package/node_modules/@buddi/cli/dist/doctor.d.ts +36 -12
  19. package/node_modules/@buddi/cli/dist/doctor.d.ts.map +1 -1
  20. package/node_modules/@buddi/cli/dist/doctor.js +32 -9
  21. package/node_modules/@buddi/cli/dist/doctor.js.map +1 -1
  22. package/node_modules/@buddi/cli/dist/main.d.ts +2 -0
  23. package/node_modules/@buddi/cli/dist/main.d.ts.map +1 -1
  24. package/node_modules/@buddi/cli/dist/main.js +20 -2
  25. package/node_modules/@buddi/cli/dist/main.js.map +1 -1
  26. package/node_modules/@buddi/core/dist/.tsbuildinfo +1 -1
  27. package/node_modules/@buddi/core/dist/agents/catalog.d.ts +2 -0
  28. package/node_modules/@buddi/core/dist/agents/catalog.d.ts.map +1 -1
  29. package/node_modules/@buddi/core/dist/agents/catalog.js +1 -0
  30. package/node_modules/@buddi/core/dist/agents/catalog.js.map +1 -1
  31. package/node_modules/@buddi/core/dist/agents/edit.d.ts +2 -0
  32. package/node_modules/@buddi/core/dist/agents/edit.d.ts.map +1 -1
  33. package/node_modules/@buddi/core/dist/agents/edit.js +3 -1
  34. package/node_modules/@buddi/core/dist/agents/edit.js.map +1 -1
  35. package/node_modules/@buddi/core/dist/agents/frontmatter.d.ts +11 -0
  36. package/node_modules/@buddi/core/dist/agents/frontmatter.d.ts.map +1 -1
  37. package/node_modules/@buddi/core/dist/agents/frontmatter.js +7 -0
  38. package/node_modules/@buddi/core/dist/agents/frontmatter.js.map +1 -1
  39. package/node_modules/@buddi/core/dist/index.d.ts +1 -0
  40. package/node_modules/@buddi/core/dist/index.d.ts.map +1 -1
  41. package/node_modules/@buddi/core/dist/index.js +1 -0
  42. package/node_modules/@buddi/core/dist/index.js.map +1 -1
  43. package/node_modules/@buddi/core/dist/plugin/index.d.ts +3 -0
  44. package/node_modules/@buddi/core/dist/plugin/index.d.ts.map +1 -1
  45. package/node_modules/@buddi/core/dist/plugin/index.js +3 -0
  46. package/node_modules/@buddi/core/dist/plugin/index.js.map +1 -1
  47. package/node_modules/@buddi/core/dist/plugin/version.d.ts +1 -1
  48. package/node_modules/@buddi/core/dist/plugin/version.js +1 -1
  49. package/node_modules/@buddi/core/dist/questions/store.d.ts +12 -0
  50. package/node_modules/@buddi/core/dist/questions/store.d.ts.map +1 -1
  51. package/node_modules/@buddi/core/dist/questions/store.js +11 -1
  52. package/node_modules/@buddi/core/dist/questions/store.js.map +1 -1
  53. package/node_modules/@buddi/core/dist/questions/types.d.ts +8 -0
  54. package/node_modules/@buddi/core/dist/questions/types.d.ts.map +1 -1
  55. package/node_modules/@buddi/core/dist/questions/types.js +15 -0
  56. package/node_modules/@buddi/core/dist/questions/types.js.map +1 -1
  57. package/node_modules/@buddi/core/dist/queue/jobs.d.ts +8 -0
  58. package/node_modules/@buddi/core/dist/queue/jobs.d.ts.map +1 -1
  59. package/node_modules/@buddi/core/dist/queue/jobs.js +9 -3
  60. package/node_modules/@buddi/core/dist/queue/jobs.js.map +1 -1
  61. package/node_modules/@buddi/core/dist/queue/worker.d.ts +2 -0
  62. package/node_modules/@buddi/core/dist/queue/worker.d.ts.map +1 -1
  63. package/node_modules/@buddi/core/dist/queue/worker.js +2 -1
  64. package/node_modules/@buddi/core/dist/queue/worker.js.map +1 -1
  65. package/node_modules/@buddi/core/dist/registry.d.ts +7 -1
  66. package/node_modules/@buddi/core/dist/registry.d.ts.map +1 -1
  67. package/node_modules/@buddi/core/dist/registry.js +73 -11
  68. package/node_modules/@buddi/core/dist/registry.js.map +1 -1
  69. package/node_modules/@buddi/core/dist/routes.d.ts +153 -0
  70. package/node_modules/@buddi/core/dist/routes.d.ts.map +1 -0
  71. package/node_modules/@buddi/core/dist/routes.js +47 -0
  72. package/node_modules/@buddi/core/dist/routes.js.map +1 -0
  73. package/node_modules/@buddi/core/dist/scheduler/missions.d.ts +5 -3
  74. package/node_modules/@buddi/core/dist/scheduler/missions.d.ts.map +1 -1
  75. package/node_modules/@buddi/core/dist/scheduler/missions.js +9 -7
  76. package/node_modules/@buddi/core/dist/scheduler/missions.js.map +1 -1
  77. package/node_modules/@buddi/core/dist/scheduler/types.d.ts +7 -0
  78. package/node_modules/@buddi/core/dist/scheduler/types.d.ts.map +1 -1
  79. package/node_modules/@buddi/core/dist/scheduler/types.js +1 -0
  80. package/node_modules/@buddi/core/dist/scheduler/types.js.map +1 -1
  81. package/node_modules/@buddi/core/dist/tools.d.ts +59 -2
  82. package/node_modules/@buddi/core/dist/tools.d.ts.map +1 -1
  83. package/node_modules/@buddi/core/dist/tools.js.map +1 -1
  84. package/node_modules/@buddi/core/migrations/075_access_providers.sql +43 -0
  85. package/node_modules/@buddi/core/migrations/076_mission_browser.sql +7 -0
  86. package/node_modules/@buddi/gateway/dist/.tsbuildinfo +1 -1
  87. package/node_modules/@buddi/gateway/dist/agents/canvas.d.ts +12 -12
  88. package/node_modules/@buddi/gateway/dist/agents/catalogue-package.d.ts +38 -20
  89. package/node_modules/@buddi/gateway/dist/agents/catalogue-package.d.ts.map +1 -1
  90. package/node_modules/@buddi/gateway/dist/agents/catalogue-package.js +6 -0
  91. package/node_modules/@buddi/gateway/dist/agents/catalogue-package.js.map +1 -1
  92. package/node_modules/@buddi/gateway/dist/agents/delegation.d.ts +10 -0
  93. package/node_modules/@buddi/gateway/dist/agents/delegation.d.ts.map +1 -1
  94. package/node_modules/@buddi/gateway/dist/agents/delegation.js +1 -0
  95. package/node_modules/@buddi/gateway/dist/agents/delegation.js.map +1 -1
  96. package/node_modules/@buddi/gateway/dist/agents/platform-catalogue.d.ts +7 -1
  97. package/node_modules/@buddi/gateway/dist/agents/platform-catalogue.d.ts.map +1 -1
  98. package/node_modules/@buddi/gateway/dist/agents/platform-catalogue.js +15 -6
  99. package/node_modules/@buddi/gateway/dist/agents/platform-catalogue.js.map +1 -1
  100. package/node_modules/@buddi/gateway/dist/bootstrap.d.ts.map +1 -1
  101. package/node_modules/@buddi/gateway/dist/bootstrap.js +8 -0
  102. package/node_modules/@buddi/gateway/dist/bootstrap.js.map +1 -1
  103. package/node_modules/@buddi/gateway/dist/browser-host.d.ts +30 -1
  104. package/node_modules/@buddi/gateway/dist/browser-host.d.ts.map +1 -1
  105. package/node_modules/@buddi/gateway/dist/browser-host.js +56 -1
  106. package/node_modules/@buddi/gateway/dist/browser-host.js.map +1 -1
  107. package/node_modules/@buddi/gateway/dist/chat/session.d.ts.map +1 -1
  108. package/node_modules/@buddi/gateway/dist/chat/session.js +9 -5
  109. package/node_modules/@buddi/gateway/dist/chat/session.js.map +1 -1
  110. package/node_modules/@buddi/gateway/dist/index.d.ts +2 -1
  111. package/node_modules/@buddi/gateway/dist/index.d.ts.map +1 -1
  112. package/node_modules/@buddi/gateway/dist/index.js +2 -1
  113. package/node_modules/@buddi/gateway/dist/index.js.map +1 -1
  114. package/node_modules/@buddi/gateway/dist/missions/ask-cap.d.ts +26 -0
  115. package/node_modules/@buddi/gateway/dist/missions/ask-cap.d.ts.map +1 -0
  116. package/node_modules/@buddi/gateway/dist/missions/ask-cap.js +83 -0
  117. package/node_modules/@buddi/gateway/dist/missions/ask-cap.js.map +1 -0
  118. package/node_modules/@buddi/gateway/dist/missions/execute.d.ts +45 -1
  119. package/node_modules/@buddi/gateway/dist/missions/execute.d.ts.map +1 -1
  120. package/node_modules/@buddi/gateway/dist/missions/execute.js +156 -10
  121. package/node_modules/@buddi/gateway/dist/missions/execute.js.map +1 -1
  122. package/node_modules/@buddi/gateway/dist/missions/parked.d.ts +139 -0
  123. package/node_modules/@buddi/gateway/dist/missions/parked.d.ts.map +1 -0
  124. package/node_modules/@buddi/gateway/dist/missions/parked.js +255 -0
  125. package/node_modules/@buddi/gateway/dist/missions/parked.js.map +1 -0
  126. package/node_modules/@buddi/gateway/dist/serve.d.ts +7 -0
  127. package/node_modules/@buddi/gateway/dist/serve.d.ts.map +1 -1
  128. package/node_modules/@buddi/gateway/dist/serve.js +49 -9
  129. package/node_modules/@buddi/gateway/dist/serve.js.map +1 -1
  130. package/node_modules/@buddi/gateway/dist/surfaces/browser-cards.d.ts +18 -0
  131. package/node_modules/@buddi/gateway/dist/surfaces/browser-cards.d.ts.map +1 -0
  132. package/node_modules/@buddi/gateway/dist/surfaces/browser-cards.js +42 -0
  133. package/node_modules/@buddi/gateway/dist/surfaces/browser-cards.js.map +1 -0
  134. package/node_modules/@buddi/gateway/dist/surfaces/browser-continuation.js +1 -1
  135. package/node_modules/@buddi/gateway/dist/surfaces/browser-continuation.js.map +1 -1
  136. package/node_modules/@buddi/gateway/dist/surfaces/owner-request.d.ts +6 -0
  137. package/node_modules/@buddi/gateway/dist/surfaces/owner-request.d.ts.map +1 -1
  138. package/node_modules/@buddi/gateway/dist/surfaces/owner-request.js +7 -1
  139. package/node_modules/@buddi/gateway/dist/surfaces/owner-request.js.map +1 -1
  140. package/node_modules/@buddi/gateway/dist/system-context.d.ts +7 -0
  141. package/node_modules/@buddi/gateway/dist/system-context.d.ts.map +1 -1
  142. package/node_modules/@buddi/gateway/dist/system-context.js +14 -7
  143. package/node_modules/@buddi/gateway/dist/system-context.js.map +1 -1
  144. package/node_modules/@buddi/gateway/dist/telegram/browser-view.d.ts +17 -17
  145. package/node_modules/@buddi/gateway/dist/telegram/browser-view.d.ts.map +1 -1
  146. package/node_modules/@buddi/gateway/dist/telegram/browser-view.js +33 -37
  147. package/node_modules/@buddi/gateway/dist/telegram/browser-view.js.map +1 -1
  148. package/node_modules/@buddi/gateway/dist/telegram/channel.d.ts.map +1 -1
  149. package/node_modules/@buddi/gateway/dist/telegram/channel.js +17 -1
  150. package/node_modules/@buddi/gateway/dist/telegram/channel.js.map +1 -1
  151. package/node_modules/@buddi/gateway/dist/telegram/main.d.ts.map +1 -1
  152. package/node_modules/@buddi/gateway/dist/telegram/main.js +11 -3
  153. package/node_modules/@buddi/gateway/dist/telegram/main.js.map +1 -1
  154. package/node_modules/@buddi/gateway/dist/telegram/surface.d.ts.map +1 -1
  155. package/node_modules/@buddi/gateway/dist/telegram/surface.js +10 -0
  156. package/node_modules/@buddi/gateway/dist/telegram/surface.js.map +1 -1
  157. package/node_modules/@buddi/gateway/dist/tips/facts.d.ts +8 -0
  158. package/node_modules/@buddi/gateway/dist/tips/facts.d.ts.map +1 -1
  159. package/node_modules/@buddi/gateway/dist/tips/facts.js +8 -2
  160. package/node_modules/@buddi/gateway/dist/tips/facts.js.map +1 -1
  161. package/node_modules/@buddi/gateway/dist/tips/rules.d.ts +2 -0
  162. package/node_modules/@buddi/gateway/dist/tips/rules.d.ts.map +1 -1
  163. package/node_modules/@buddi/gateway/dist/tips/rules.js +15 -0
  164. package/node_modules/@buddi/gateway/dist/tips/rules.js.map +1 -1
  165. package/node_modules/@buddi/gateway/dist/web/access/arrival.d.ts +24 -0
  166. package/node_modules/@buddi/gateway/dist/web/access/arrival.d.ts.map +1 -0
  167. package/node_modules/@buddi/gateway/dist/web/access/arrival.js +18 -0
  168. package/node_modules/@buddi/gateway/dist/web/access/arrival.js.map +1 -0
  169. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-api.d.ts +124 -0
  170. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-api.d.ts.map +1 -0
  171. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-api.js +206 -0
  172. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-api.js.map +1 -0
  173. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-setup.d.ts +163 -0
  174. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-setup.d.ts.map +1 -0
  175. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-setup.js +438 -0
  176. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-setup.js.map +1 -0
  177. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-token.d.ts +31 -0
  178. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-token.d.ts.map +1 -0
  179. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-token.js +74 -0
  180. package/node_modules/@buddi/gateway/dist/web/access/cloudflare-token.js.map +1 -0
  181. package/node_modules/@buddi/gateway/dist/web/access/cloudflare.d.ts +145 -0
  182. package/node_modules/@buddi/gateway/dist/web/access/cloudflare.d.ts.map +1 -0
  183. package/node_modules/@buddi/gateway/dist/web/access/cloudflare.js +474 -0
  184. package/node_modules/@buddi/gateway/dist/web/access/cloudflare.js.map +1 -0
  185. package/node_modules/@buddi/gateway/dist/web/access/index.d.ts +9 -0
  186. package/node_modules/@buddi/gateway/dist/web/access/index.d.ts.map +1 -0
  187. package/node_modules/@buddi/gateway/dist/web/access/index.js +9 -0
  188. package/node_modules/@buddi/gateway/dist/web/access/index.js.map +1 -0
  189. package/node_modules/@buddi/gateway/dist/web/access/ingress.d.ts +30 -0
  190. package/node_modules/@buddi/gateway/dist/web/access/ingress.d.ts.map +1 -0
  191. package/node_modules/@buddi/gateway/dist/web/access/ingress.js +76 -0
  192. package/node_modules/@buddi/gateway/dist/web/access/ingress.js.map +1 -0
  193. package/node_modules/@buddi/gateway/dist/web/access/provider.d.ts +173 -0
  194. package/node_modules/@buddi/gateway/dist/web/access/provider.d.ts.map +1 -0
  195. package/node_modules/@buddi/gateway/dist/web/access/provider.js +11 -0
  196. package/node_modules/@buddi/gateway/dist/web/access/provider.js.map +1 -0
  197. package/node_modules/@buddi/gateway/dist/web/access/registry.d.ts +33 -0
  198. package/node_modules/@buddi/gateway/dist/web/access/registry.d.ts.map +1 -0
  199. package/node_modules/@buddi/gateway/dist/web/access/registry.js +32 -0
  200. package/node_modules/@buddi/gateway/dist/web/access/registry.js.map +1 -0
  201. package/node_modules/@buddi/gateway/dist/web/{tailscale.d.ts → access/tailscale.d.ts} +27 -0
  202. package/node_modules/@buddi/gateway/dist/web/access/tailscale.d.ts.map +1 -0
  203. package/node_modules/@buddi/gateway/dist/web/{tailscale.js → access/tailscale.js} +118 -3
  204. package/node_modules/@buddi/gateway/dist/web/access/tailscale.js.map +1 -0
  205. package/node_modules/@buddi/gateway/dist/web/agents.d.ts +2 -0
  206. package/node_modules/@buddi/gateway/dist/web/agents.d.ts.map +1 -1
  207. package/node_modules/@buddi/gateway/dist/web/agents.js +19 -1
  208. package/node_modules/@buddi/gateway/dist/web/agents.js.map +1 -1
  209. package/node_modules/@buddi/gateway/dist/web/api-routes.d.ts +4 -3
  210. package/node_modules/@buddi/gateway/dist/web/api-routes.d.ts.map +1 -1
  211. package/node_modules/@buddi/gateway/dist/web/api-routes.js +106 -19
  212. package/node_modules/@buddi/gateway/dist/web/api-routes.js.map +1 -1
  213. package/node_modules/@buddi/gateway/dist/web/attention.d.ts.map +1 -1
  214. package/node_modules/@buddi/gateway/dist/web/attention.js +11 -3
  215. package/node_modules/@buddi/gateway/dist/web/attention.js.map +1 -1
  216. package/node_modules/@buddi/gateway/dist/web/chat.d.ts +23 -0
  217. package/node_modules/@buddi/gateway/dist/web/chat.d.ts.map +1 -1
  218. package/node_modules/@buddi/gateway/dist/web/chat.js +62 -2
  219. package/node_modules/@buddi/gateway/dist/web/chat.js.map +1 -1
  220. package/node_modules/@buddi/gateway/dist/web/client-key.d.ts +3 -0
  221. package/node_modules/@buddi/gateway/dist/web/client-key.d.ts.map +1 -1
  222. package/node_modules/@buddi/gateway/dist/web/client-key.js +8 -1
  223. package/node_modules/@buddi/gateway/dist/web/client-key.js.map +1 -1
  224. package/node_modules/@buddi/gateway/dist/web/extension.d.ts +21 -1
  225. package/node_modules/@buddi/gateway/dist/web/extension.d.ts.map +1 -1
  226. package/node_modules/@buddi/gateway/dist/web/extension.js +183 -3
  227. package/node_modules/@buddi/gateway/dist/web/extension.js.map +1 -1
  228. package/node_modules/@buddi/gateway/dist/web/http.d.ts +7 -0
  229. package/node_modules/@buddi/gateway/dist/web/http.d.ts.map +1 -1
  230. package/node_modules/@buddi/gateway/dist/web/http.js +15 -1
  231. package/node_modules/@buddi/gateway/dist/web/http.js.map +1 -1
  232. package/node_modules/@buddi/gateway/dist/web/index.d.ts +1 -1
  233. package/node_modules/@buddi/gateway/dist/web/index.d.ts.map +1 -1
  234. package/node_modules/@buddi/gateway/dist/web/index.js +1 -1
  235. package/node_modules/@buddi/gateway/dist/web/index.js.map +1 -1
  236. package/node_modules/@buddi/gateway/dist/web/onboarding.js +1 -1
  237. package/node_modules/@buddi/gateway/dist/web/onboarding.js.map +1 -1
  238. package/node_modules/@buddi/gateway/dist/web/remote-hand.d.ts.map +1 -1
  239. package/node_modules/@buddi/gateway/dist/web/remote-hand.js +45 -2
  240. package/node_modules/@buddi/gateway/dist/web/remote-hand.js.map +1 -1
  241. package/node_modules/@buddi/gateway/dist/web/secrets.d.ts +26 -1
  242. package/node_modules/@buddi/gateway/dist/web/secrets.d.ts.map +1 -1
  243. package/node_modules/@buddi/gateway/dist/web/secrets.js +66 -3
  244. package/node_modules/@buddi/gateway/dist/web/secrets.js.map +1 -1
  245. package/node_modules/@buddi/gateway/dist/web/server.d.ts +38 -2
  246. package/node_modules/@buddi/gateway/dist/web/server.d.ts.map +1 -1
  247. package/node_modules/@buddi/gateway/dist/web/server.js +634 -147
  248. package/node_modules/@buddi/gateway/dist/web/server.js.map +1 -1
  249. package/node_modules/@buddi/gateway/dist/web/sessions.d.ts +24 -14
  250. package/node_modules/@buddi/gateway/dist/web/sessions.d.ts.map +1 -1
  251. package/node_modules/@buddi/gateway/dist/web/sessions.js +42 -14
  252. package/node_modules/@buddi/gateway/dist/web/sessions.js.map +1 -1
  253. package/node_modules/@buddi/gateway/dist/web/signed-out.d.ts +10 -0
  254. package/node_modules/@buddi/gateway/dist/web/signed-out.d.ts.map +1 -1
  255. package/node_modules/@buddi/gateway/dist/web/signed-out.js +47 -0
  256. package/node_modules/@buddi/gateway/dist/web/signed-out.js.map +1 -1
  257. package/node_modules/@buddi/gateway/dist/web/version.d.ts +2 -0
  258. package/node_modules/@buddi/gateway/dist/web/version.d.ts.map +1 -1
  259. package/node_modules/@buddi/gateway/dist/web/version.js +5 -1
  260. package/node_modules/@buddi/gateway/dist/web/version.js.map +1 -1
  261. package/node_modules/@buddi/install/dist/.tsbuildinfo +1 -1
  262. package/node_modules/@buddi/install/dist/app-layout.d.ts +127 -0
  263. package/node_modules/@buddi/install/dist/app-layout.d.ts.map +1 -0
  264. package/node_modules/@buddi/install/dist/app-layout.js +323 -0
  265. package/node_modules/@buddi/install/dist/app-layout.js.map +1 -0
  266. package/node_modules/@buddi/install/dist/index.d.ts +1 -0
  267. package/node_modules/@buddi/install/dist/index.d.ts.map +1 -1
  268. package/node_modules/@buddi/install/dist/index.js +1 -0
  269. package/node_modules/@buddi/install/dist/index.js.map +1 -1
  270. package/node_modules/@buddi/install/dist/launcher.d.ts.map +1 -1
  271. package/node_modules/@buddi/install/dist/launcher.js +18 -4
  272. package/node_modules/@buddi/install/dist/launcher.js.map +1 -1
  273. package/node_modules/@buddi/install/dist/provenance.d.ts +34 -0
  274. package/node_modules/@buddi/install/dist/provenance.d.ts.map +1 -0
  275. package/node_modules/@buddi/install/dist/provenance.js +200 -0
  276. package/node_modules/@buddi/install/dist/provenance.js.map +1 -0
  277. package/node_modules/@buddi/install/dist/supervisor.d.ts.map +1 -1
  278. package/node_modules/@buddi/install/dist/supervisor.js +9 -4
  279. package/node_modules/@buddi/install/dist/supervisor.js.map +1 -1
  280. package/node_modules/@buddi/install/dist/upgrade.d.ts +77 -3
  281. package/node_modules/@buddi/install/dist/upgrade.d.ts.map +1 -1
  282. package/node_modules/@buddi/install/dist/upgrade.js +325 -43
  283. package/node_modules/@buddi/install/dist/upgrade.js.map +1 -1
  284. package/node_modules/@buddi/runtime/dist/.tsbuildinfo +1 -1
  285. package/node_modules/@buddi/runtime/dist/delegate.d.ts +8 -0
  286. package/node_modules/@buddi/runtime/dist/delegate.d.ts.map +1 -1
  287. package/node_modules/@buddi/runtime/dist/delegate.js +19 -1
  288. package/node_modules/@buddi/runtime/dist/delegate.js.map +1 -1
  289. package/node_modules/@buddi/runtime/dist/loop.d.ts +2 -0
  290. package/node_modules/@buddi/runtime/dist/loop.d.ts.map +1 -1
  291. package/node_modules/@buddi/runtime/dist/loop.js +18 -4
  292. package/node_modules/@buddi/runtime/dist/loop.js.map +1 -1
  293. package/node_modules/@buddi/tool-artifacts/dist/.tsbuildinfo +1 -1
  294. package/node_modules/@buddi/tool-browser/dist/.tsbuildinfo +1 -1
  295. package/node_modules/@buddi/tool-browser/dist/availability.d.ts +2 -2
  296. package/node_modules/@buddi/tool-browser/dist/availability.d.ts.map +1 -1
  297. package/node_modules/@buddi/tool-browser/dist/availability.js +2 -2
  298. package/node_modules/@buddi/tool-browser/dist/availability.js.map +1 -1
  299. package/node_modules/@buddi/tool-browser/dist/controller.d.ts +82 -58
  300. package/node_modules/@buddi/tool-browser/dist/controller.d.ts.map +1 -1
  301. package/node_modules/@buddi/tool-browser/dist/controller.js +823 -217
  302. package/node_modules/@buddi/tool-browser/dist/controller.js.map +1 -1
  303. package/node_modules/@buddi/tool-browser/dist/driver.d.ts +3 -1
  304. package/node_modules/@buddi/tool-browser/dist/driver.d.ts.map +1 -1
  305. package/node_modules/@buddi/tool-browser/dist/driver.js +82 -1
  306. package/node_modules/@buddi/tool-browser/dist/driver.js.map +1 -1
  307. package/node_modules/@buddi/tool-browser/dist/extension.d.ts +70 -5
  308. package/node_modules/@buddi/tool-browser/dist/extension.d.ts.map +1 -1
  309. package/node_modules/@buddi/tool-browser/dist/extension.js +94 -9
  310. package/node_modules/@buddi/tool-browser/dist/extension.js.map +1 -1
  311. package/node_modules/@buddi/tool-browser/dist/index.d.ts +17 -7
  312. package/node_modules/@buddi/tool-browser/dist/index.d.ts.map +1 -1
  313. package/node_modules/@buddi/tool-browser/dist/index.js +35 -15
  314. package/node_modules/@buddi/tool-browser/dist/index.js.map +1 -1
  315. package/node_modules/@buddi/tool-browser/dist/logins.d.ts +150 -0
  316. package/node_modules/@buddi/tool-browser/dist/logins.d.ts.map +1 -0
  317. package/node_modules/@buddi/tool-browser/dist/logins.js +476 -0
  318. package/node_modules/@buddi/tool-browser/dist/logins.js.map +1 -0
  319. package/node_modules/@buddi/tool-browser/dist/manager.d.ts +39 -21
  320. package/node_modules/@buddi/tool-browser/dist/manager.d.ts.map +1 -1
  321. package/node_modules/@buddi/tool-browser/dist/manager.js +229 -136
  322. package/node_modules/@buddi/tool-browser/dist/manager.js.map +1 -1
  323. package/node_modules/@buddi/tool-browser/dist/routes.d.ts +151 -0
  324. package/node_modules/@buddi/tool-browser/dist/routes.d.ts.map +1 -0
  325. package/node_modules/@buddi/tool-browser/dist/routes.js +331 -0
  326. package/node_modules/@buddi/tool-browser/dist/routes.js.map +1 -0
  327. package/node_modules/@buddi/tool-browser/dist/secrets.d.ts +3 -1
  328. package/node_modules/@buddi/tool-browser/dist/secrets.d.ts.map +1 -1
  329. package/node_modules/@buddi/tool-browser/dist/secrets.js +10 -6
  330. package/node_modules/@buddi/tool-browser/dist/secrets.js.map +1 -1
  331. package/node_modules/@buddi/tool-browser/dist/service.d.ts +182 -115
  332. package/node_modules/@buddi/tool-browser/dist/service.d.ts.map +1 -1
  333. package/node_modules/@buddi/tool-browser/dist/service.js +491 -388
  334. package/node_modules/@buddi/tool-browser/dist/service.js.map +1 -1
  335. package/node_modules/@buddi/tool-browser/dist/settings.d.ts +88 -0
  336. package/node_modules/@buddi/tool-browser/dist/settings.d.ts.map +1 -0
  337. package/node_modules/@buddi/tool-browser/dist/settings.js +86 -0
  338. package/node_modules/@buddi/tool-browser/dist/settings.js.map +1 -0
  339. package/node_modules/@buddi/tool-browser/dist/telemetry.d.ts +203 -0
  340. package/node_modules/@buddi/tool-browser/dist/telemetry.d.ts.map +1 -0
  341. package/node_modules/@buddi/tool-browser/dist/telemetry.js +142 -0
  342. package/node_modules/@buddi/tool-browser/dist/telemetry.js.map +1 -0
  343. package/node_modules/@buddi/tool-browser/dist/types.d.ts +102 -3
  344. package/node_modules/@buddi/tool-browser/dist/types.d.ts.map +1 -1
  345. package/node_modules/@buddi/tool-browser/dist/types.js +27 -9
  346. package/node_modules/@buddi/tool-browser/dist/types.js.map +1 -1
  347. package/node_modules/@buddi/tool-email/dist/.tsbuildinfo +1 -1
  348. package/node_modules/@buddi/tool-host/dist/.tsbuildinfo +1 -1
  349. package/node_modules/@buddi/tool-mcp/dist/.tsbuildinfo +1 -1
  350. package/node_modules/@buddi/tool-memory/dist/.tsbuildinfo +1 -1
  351. package/node_modules/@buddi/tool-web/dist/.tsbuildinfo +1 -1
  352. package/node_modules/hono/dist/cjs/hono-base.js +2 -0
  353. package/node_modules/hono/dist/cjs/middleware/language/language.js +1 -1
  354. package/node_modules/hono/dist/cjs/middleware/mount/index.js +71 -0
  355. package/node_modules/hono/dist/cjs/request.js +4 -4
  356. package/node_modules/hono/dist/hono-base.js +2 -0
  357. package/node_modules/hono/dist/middleware/mount/index.js +70 -0
  358. package/node_modules/hono/dist/request.js +4 -4
  359. package/node_modules/hono/dist/types/hono-base.d.ts +2 -0
  360. package/node_modules/hono/dist/types/jsx/base.d.ts +1 -1
  361. package/node_modules/hono/dist/types/middleware/mount/index.d.ts +46 -0
  362. package/node_modules/hono/dist/types/request.d.ts +4 -4
  363. package/node_modules/hono/package.json +9 -1
  364. package/node_modules/imapflow/CHANGELOG.md +15 -0
  365. package/node_modules/imapflow/dist/cjs/package-info.d.ts +1 -1
  366. package/node_modules/imapflow/dist/cjs/package-info.js +1 -1
  367. package/node_modules/imapflow/dist/cjs/search-compiler.js +21 -31
  368. package/node_modules/imapflow/dist/esm/package-info.d.ts +1 -1
  369. package/node_modules/imapflow/dist/esm/package-info.js +1 -1
  370. package/node_modules/imapflow/dist/esm/search-compiler.js +21 -31
  371. package/node_modules/imapflow/package.json +2 -2
  372. package/node_modules/pino/CONTRIBUTING.md +1 -5
  373. package/node_modules/pino/LICENSE +1 -1
  374. package/node_modules/pino/README.md +1 -1
  375. package/node_modules/pino/SECURITY.md +2 -18
  376. package/node_modules/pino/browser.js +8 -68
  377. package/node_modules/pino/docs/api.md +4 -75
  378. package/node_modules/pino/docs/asynchronous.md +3 -18
  379. package/node_modules/pino/docs/browser.md +0 -63
  380. package/node_modules/pino/docs/child-loggers.md +1 -22
  381. package/node_modules/pino/docs/ecosystem.md +0 -1
  382. package/node_modules/pino/docs/help.md +3 -11
  383. package/node_modules/pino/docs/lts.md +0 -29
  384. package/node_modules/pino/docs/pretty.md +0 -3
  385. package/node_modules/pino/docs/redaction.md +8 -3
  386. package/node_modules/pino/docs/transports.md +0 -71
  387. package/node_modules/pino/docs/web.md +14 -11
  388. package/node_modules/pino/eslint.config.js +7 -15
  389. package/node_modules/pino/lib/caller.js +0 -10
  390. package/node_modules/pino/lib/meta.js +1 -1
  391. package/node_modules/pino/lib/multistream.js +7 -15
  392. package/node_modules/pino/lib/redaction.js +3 -8
  393. package/node_modules/pino/lib/symbols.js +1 -3
  394. package/node_modules/pino/lib/tools.js +2 -2
  395. package/node_modules/pino/lib/transport.js +1 -14
  396. package/node_modules/pino/lib/worker.js +1 -4
  397. package/node_modules/pino/package.json +22 -14
  398. package/node_modules/pino/pino.d.ts +736 -709
  399. package/node_modules/pino/pino.js +2 -27
  400. package/node_modules/pino/test/basic.test.js +0 -66
  401. package/node_modules/pino/test/escaping.test.js +0 -17
  402. package/node_modules/pino/test/fixtures/ts/transpile.cjs +3 -3
  403. package/node_modules/pino/test/helper.js +2 -6
  404. package/node_modules/pino/test/multistream.test.js +0 -64
  405. package/node_modules/pino/test/redact.test.js +0 -30
  406. package/node_modules/pino/test/transport/core.test.js +0 -85
  407. package/node_modules/pino/test/transport/sync-false.test.js +0 -12
  408. package/node_modules/pino/test/transport/uses-pino-config.test.js +4 -4
  409. package/node_modules/pino/test/types/pino-import.test-d.cts +30 -0
  410. package/node_modules/pino/test/types/pino-multistream.test-d.ts +28 -0
  411. package/node_modules/pino/test/types/pino-top-export.test-d.ts +34 -0
  412. package/node_modules/pino/test/types/{pino-transport.tst.ts → pino-transport.test-d.ts} +70 -85
  413. package/node_modules/pino/test/types/pino-type-only.test-d.ts +75 -0
  414. package/node_modules/pino/test/types/{pino.tst.ts → pino.test-d.ts} +137 -135
  415. package/node_modules/pino/test/types/pino.ts +7 -14
  416. package/node_modules/pino/tsconfig.json +6 -6
  417. package/node_modules/real-require/README.md +6 -6
  418. package/node_modules/real-require/package.json +21 -10
  419. package/node_modules/real-require/src/index.js +2 -2
  420. package/node_modules/thread-stream/node_modules/real-require/LICENSE.md +21 -0
  421. package/node_modules/thread-stream/node_modules/real-require/README.md +51 -0
  422. package/node_modules/thread-stream/node_modules/real-require/package.json +38 -0
  423. package/node_modules/thread-stream/node_modules/real-require/src/index.js +14 -0
  424. package/package.json +2 -2
  425. package/packages/cli/dist/.tsbuildinfo +1 -1
  426. package/packages/cli/dist/access-cmd.d.ts +10 -0
  427. package/packages/cli/dist/access-cmd.d.ts.map +1 -0
  428. package/packages/cli/dist/access-cmd.js +180 -0
  429. package/packages/cli/dist/access-cmd.js.map +1 -0
  430. package/packages/cli/dist/args.d.ts +10 -0
  431. package/packages/cli/dist/args.d.ts.map +1 -1
  432. package/packages/cli/dist/args.js +34 -1
  433. package/packages/cli/dist/args.js.map +1 -1
  434. package/packages/cli/dist/commands.d.ts.map +1 -1
  435. package/packages/cli/dist/commands.js +39 -2
  436. package/packages/cli/dist/commands.js.map +1 -1
  437. package/packages/cli/dist/doctor-probes.d.ts.map +1 -1
  438. package/packages/cli/dist/doctor-probes.js +54 -29
  439. package/packages/cli/dist/doctor-probes.js.map +1 -1
  440. package/packages/cli/dist/doctor.d.ts +36 -12
  441. package/packages/cli/dist/doctor.d.ts.map +1 -1
  442. package/packages/cli/dist/doctor.js +32 -9
  443. package/packages/cli/dist/doctor.js.map +1 -1
  444. package/packages/cli/dist/main.d.ts +2 -0
  445. package/packages/cli/dist/main.d.ts.map +1 -1
  446. package/packages/cli/dist/main.js +20 -2
  447. package/packages/cli/dist/main.js.map +1 -1
  448. package/packages/core/dist/.tsbuildinfo +1 -1
  449. package/packages/core/dist/agents/catalog.d.ts +2 -0
  450. package/packages/core/dist/agents/catalog.d.ts.map +1 -1
  451. package/packages/core/dist/agents/catalog.js +1 -0
  452. package/packages/core/dist/agents/catalog.js.map +1 -1
  453. package/packages/core/dist/agents/edit.d.ts +2 -0
  454. package/packages/core/dist/agents/edit.d.ts.map +1 -1
  455. package/packages/core/dist/agents/edit.js +3 -1
  456. package/packages/core/dist/agents/edit.js.map +1 -1
  457. package/packages/core/dist/agents/frontmatter.d.ts +11 -0
  458. package/packages/core/dist/agents/frontmatter.d.ts.map +1 -1
  459. package/packages/core/dist/agents/frontmatter.js +7 -0
  460. package/packages/core/dist/agents/frontmatter.js.map +1 -1
  461. package/packages/core/dist/index.d.ts +1 -0
  462. package/packages/core/dist/index.d.ts.map +1 -1
  463. package/packages/core/dist/index.js +1 -0
  464. package/packages/core/dist/index.js.map +1 -1
  465. package/packages/core/dist/plugin/index.d.ts +3 -0
  466. package/packages/core/dist/plugin/index.d.ts.map +1 -1
  467. package/packages/core/dist/plugin/index.js +3 -0
  468. package/packages/core/dist/plugin/index.js.map +1 -1
  469. package/packages/core/dist/plugin/version.d.ts +1 -1
  470. package/packages/core/dist/plugin/version.js +1 -1
  471. package/packages/core/dist/questions/store.d.ts +12 -0
  472. package/packages/core/dist/questions/store.d.ts.map +1 -1
  473. package/packages/core/dist/questions/store.js +11 -1
  474. package/packages/core/dist/questions/store.js.map +1 -1
  475. package/packages/core/dist/questions/types.d.ts +8 -0
  476. package/packages/core/dist/questions/types.d.ts.map +1 -1
  477. package/packages/core/dist/questions/types.js +15 -0
  478. package/packages/core/dist/questions/types.js.map +1 -1
  479. package/packages/core/dist/queue/jobs.d.ts +8 -0
  480. package/packages/core/dist/queue/jobs.d.ts.map +1 -1
  481. package/packages/core/dist/queue/jobs.js +9 -3
  482. package/packages/core/dist/queue/jobs.js.map +1 -1
  483. package/packages/core/dist/queue/worker.d.ts +2 -0
  484. package/packages/core/dist/queue/worker.d.ts.map +1 -1
  485. package/packages/core/dist/queue/worker.js +2 -1
  486. package/packages/core/dist/queue/worker.js.map +1 -1
  487. package/packages/core/dist/registry.d.ts +7 -1
  488. package/packages/core/dist/registry.d.ts.map +1 -1
  489. package/packages/core/dist/registry.js +73 -11
  490. package/packages/core/dist/registry.js.map +1 -1
  491. package/packages/core/dist/routes.d.ts +153 -0
  492. package/packages/core/dist/routes.d.ts.map +1 -0
  493. package/packages/core/dist/routes.js +47 -0
  494. package/packages/core/dist/routes.js.map +1 -0
  495. package/packages/core/dist/scheduler/missions.d.ts +5 -3
  496. package/packages/core/dist/scheduler/missions.d.ts.map +1 -1
  497. package/packages/core/dist/scheduler/missions.js +9 -7
  498. package/packages/core/dist/scheduler/missions.js.map +1 -1
  499. package/packages/core/dist/scheduler/types.d.ts +7 -0
  500. package/packages/core/dist/scheduler/types.d.ts.map +1 -1
  501. package/packages/core/dist/scheduler/types.js +1 -0
  502. package/packages/core/dist/scheduler/types.js.map +1 -1
  503. package/packages/core/dist/tools.d.ts +59 -2
  504. package/packages/core/dist/tools.d.ts.map +1 -1
  505. package/packages/core/dist/tools.js.map +1 -1
  506. package/packages/core/migrations/075_access_providers.sql +43 -0
  507. package/packages/core/migrations/076_mission_browser.sql +7 -0
  508. package/packages/gateway/dist/.tsbuildinfo +1 -1
  509. package/packages/gateway/dist/agents/canvas.d.ts +12 -12
  510. package/packages/gateway/dist/agents/catalogue-package.d.ts +38 -20
  511. package/packages/gateway/dist/agents/catalogue-package.d.ts.map +1 -1
  512. package/packages/gateway/dist/agents/catalogue-package.js +6 -0
  513. package/packages/gateway/dist/agents/catalogue-package.js.map +1 -1
  514. package/packages/gateway/dist/agents/delegation.d.ts +10 -0
  515. package/packages/gateway/dist/agents/delegation.d.ts.map +1 -1
  516. package/packages/gateway/dist/agents/delegation.js +1 -0
  517. package/packages/gateway/dist/agents/delegation.js.map +1 -1
  518. package/packages/gateway/dist/agents/platform-catalogue.d.ts +7 -1
  519. package/packages/gateway/dist/agents/platform-catalogue.d.ts.map +1 -1
  520. package/packages/gateway/dist/agents/platform-catalogue.js +15 -6
  521. package/packages/gateway/dist/agents/platform-catalogue.js.map +1 -1
  522. package/packages/gateway/dist/bootstrap.d.ts.map +1 -1
  523. package/packages/gateway/dist/bootstrap.js +8 -0
  524. package/packages/gateway/dist/bootstrap.js.map +1 -1
  525. package/packages/gateway/dist/browser-host.d.ts +30 -1
  526. package/packages/gateway/dist/browser-host.d.ts.map +1 -1
  527. package/packages/gateway/dist/browser-host.js +56 -1
  528. package/packages/gateway/dist/browser-host.js.map +1 -1
  529. package/packages/gateway/dist/chat/session.d.ts.map +1 -1
  530. package/packages/gateway/dist/chat/session.js +9 -5
  531. package/packages/gateway/dist/chat/session.js.map +1 -1
  532. package/packages/gateway/dist/index.d.ts +2 -1
  533. package/packages/gateway/dist/index.d.ts.map +1 -1
  534. package/packages/gateway/dist/index.js +2 -1
  535. package/packages/gateway/dist/index.js.map +1 -1
  536. package/packages/gateway/dist/missions/ask-cap.d.ts +26 -0
  537. package/packages/gateway/dist/missions/ask-cap.d.ts.map +1 -0
  538. package/packages/gateway/dist/missions/ask-cap.js +83 -0
  539. package/packages/gateway/dist/missions/ask-cap.js.map +1 -0
  540. package/packages/gateway/dist/missions/execute.d.ts +45 -1
  541. package/packages/gateway/dist/missions/execute.d.ts.map +1 -1
  542. package/packages/gateway/dist/missions/execute.js +156 -10
  543. package/packages/gateway/dist/missions/execute.js.map +1 -1
  544. package/packages/gateway/dist/missions/parked.d.ts +139 -0
  545. package/packages/gateway/dist/missions/parked.d.ts.map +1 -0
  546. package/packages/gateway/dist/missions/parked.js +255 -0
  547. package/packages/gateway/dist/missions/parked.js.map +1 -0
  548. package/packages/gateway/dist/serve.d.ts +7 -0
  549. package/packages/gateway/dist/serve.d.ts.map +1 -1
  550. package/packages/gateway/dist/serve.js +49 -9
  551. package/packages/gateway/dist/serve.js.map +1 -1
  552. package/packages/gateway/dist/surfaces/browser-cards.d.ts +18 -0
  553. package/packages/gateway/dist/surfaces/browser-cards.d.ts.map +1 -0
  554. package/packages/gateway/dist/surfaces/browser-cards.js +42 -0
  555. package/packages/gateway/dist/surfaces/browser-cards.js.map +1 -0
  556. package/packages/gateway/dist/surfaces/browser-continuation.js +1 -1
  557. package/packages/gateway/dist/surfaces/browser-continuation.js.map +1 -1
  558. package/packages/gateway/dist/surfaces/owner-request.d.ts +6 -0
  559. package/packages/gateway/dist/surfaces/owner-request.d.ts.map +1 -1
  560. package/packages/gateway/dist/surfaces/owner-request.js +7 -1
  561. package/packages/gateway/dist/surfaces/owner-request.js.map +1 -1
  562. package/packages/gateway/dist/system-context.d.ts +7 -0
  563. package/packages/gateway/dist/system-context.d.ts.map +1 -1
  564. package/packages/gateway/dist/system-context.js +14 -7
  565. package/packages/gateway/dist/system-context.js.map +1 -1
  566. package/packages/gateway/dist/telegram/browser-view.d.ts +17 -17
  567. package/packages/gateway/dist/telegram/browser-view.d.ts.map +1 -1
  568. package/packages/gateway/dist/telegram/browser-view.js +33 -37
  569. package/packages/gateway/dist/telegram/browser-view.js.map +1 -1
  570. package/packages/gateway/dist/telegram/channel.d.ts.map +1 -1
  571. package/packages/gateway/dist/telegram/channel.js +17 -1
  572. package/packages/gateway/dist/telegram/channel.js.map +1 -1
  573. package/packages/gateway/dist/telegram/main.d.ts.map +1 -1
  574. package/packages/gateway/dist/telegram/main.js +11 -3
  575. package/packages/gateway/dist/telegram/main.js.map +1 -1
  576. package/packages/gateway/dist/telegram/surface.d.ts.map +1 -1
  577. package/packages/gateway/dist/telegram/surface.js +10 -0
  578. package/packages/gateway/dist/telegram/surface.js.map +1 -1
  579. package/packages/gateway/dist/tips/facts.d.ts +8 -0
  580. package/packages/gateway/dist/tips/facts.d.ts.map +1 -1
  581. package/packages/gateway/dist/tips/facts.js +8 -2
  582. package/packages/gateway/dist/tips/facts.js.map +1 -1
  583. package/packages/gateway/dist/tips/rules.d.ts +2 -0
  584. package/packages/gateway/dist/tips/rules.d.ts.map +1 -1
  585. package/packages/gateway/dist/tips/rules.js +15 -0
  586. package/packages/gateway/dist/tips/rules.js.map +1 -1
  587. package/packages/gateway/dist/web/access/arrival.d.ts +24 -0
  588. package/packages/gateway/dist/web/access/arrival.d.ts.map +1 -0
  589. package/packages/gateway/dist/web/access/arrival.js +18 -0
  590. package/packages/gateway/dist/web/access/arrival.js.map +1 -0
  591. package/packages/gateway/dist/web/access/cloudflare-api.d.ts +124 -0
  592. package/packages/gateway/dist/web/access/cloudflare-api.d.ts.map +1 -0
  593. package/packages/gateway/dist/web/access/cloudflare-api.js +206 -0
  594. package/packages/gateway/dist/web/access/cloudflare-api.js.map +1 -0
  595. package/packages/gateway/dist/web/access/cloudflare-setup.d.ts +163 -0
  596. package/packages/gateway/dist/web/access/cloudflare-setup.d.ts.map +1 -0
  597. package/packages/gateway/dist/web/access/cloudflare-setup.js +438 -0
  598. package/packages/gateway/dist/web/access/cloudflare-setup.js.map +1 -0
  599. package/packages/gateway/dist/web/access/cloudflare-token.d.ts +31 -0
  600. package/packages/gateway/dist/web/access/cloudflare-token.d.ts.map +1 -0
  601. package/packages/gateway/dist/web/access/cloudflare-token.js +74 -0
  602. package/packages/gateway/dist/web/access/cloudflare-token.js.map +1 -0
  603. package/packages/gateway/dist/web/access/cloudflare.d.ts +145 -0
  604. package/packages/gateway/dist/web/access/cloudflare.d.ts.map +1 -0
  605. package/packages/gateway/dist/web/access/cloudflare.js +474 -0
  606. package/packages/gateway/dist/web/access/cloudflare.js.map +1 -0
  607. package/packages/gateway/dist/web/access/index.d.ts +9 -0
  608. package/packages/gateway/dist/web/access/index.d.ts.map +1 -0
  609. package/packages/gateway/dist/web/access/index.js +9 -0
  610. package/packages/gateway/dist/web/access/index.js.map +1 -0
  611. package/packages/gateway/dist/web/access/ingress.d.ts +30 -0
  612. package/packages/gateway/dist/web/access/ingress.d.ts.map +1 -0
  613. package/packages/gateway/dist/web/access/ingress.js +76 -0
  614. package/packages/gateway/dist/web/access/ingress.js.map +1 -0
  615. package/packages/gateway/dist/web/access/provider.d.ts +173 -0
  616. package/packages/gateway/dist/web/access/provider.d.ts.map +1 -0
  617. package/packages/gateway/dist/web/access/provider.js +11 -0
  618. package/packages/gateway/dist/web/access/provider.js.map +1 -0
  619. package/packages/gateway/dist/web/access/registry.d.ts +33 -0
  620. package/packages/gateway/dist/web/access/registry.d.ts.map +1 -0
  621. package/packages/gateway/dist/web/access/registry.js +32 -0
  622. package/packages/gateway/dist/web/access/registry.js.map +1 -0
  623. package/packages/gateway/dist/web/{tailscale.d.ts → access/tailscale.d.ts} +27 -0
  624. package/packages/gateway/dist/web/access/tailscale.d.ts.map +1 -0
  625. package/packages/gateway/dist/web/{tailscale.js → access/tailscale.js} +118 -3
  626. package/packages/gateway/dist/web/access/tailscale.js.map +1 -0
  627. package/packages/gateway/dist/web/agents.d.ts +2 -0
  628. package/packages/gateway/dist/web/agents.d.ts.map +1 -1
  629. package/packages/gateway/dist/web/agents.js +19 -1
  630. package/packages/gateway/dist/web/agents.js.map +1 -1
  631. package/packages/gateway/dist/web/api-routes.d.ts +4 -3
  632. package/packages/gateway/dist/web/api-routes.d.ts.map +1 -1
  633. package/packages/gateway/dist/web/api-routes.js +106 -19
  634. package/packages/gateway/dist/web/api-routes.js.map +1 -1
  635. package/packages/gateway/dist/web/attention.d.ts.map +1 -1
  636. package/packages/gateway/dist/web/attention.js +11 -3
  637. package/packages/gateway/dist/web/attention.js.map +1 -1
  638. package/packages/gateway/dist/web/chat.d.ts +23 -0
  639. package/packages/gateway/dist/web/chat.d.ts.map +1 -1
  640. package/packages/gateway/dist/web/chat.js +62 -2
  641. package/packages/gateway/dist/web/chat.js.map +1 -1
  642. package/packages/gateway/dist/web/client-key.d.ts +3 -0
  643. package/packages/gateway/dist/web/client-key.d.ts.map +1 -1
  644. package/packages/gateway/dist/web/client-key.js +8 -1
  645. package/packages/gateway/dist/web/client-key.js.map +1 -1
  646. package/packages/gateway/dist/web/extension.d.ts +21 -1
  647. package/packages/gateway/dist/web/extension.d.ts.map +1 -1
  648. package/packages/gateway/dist/web/extension.js +183 -3
  649. package/packages/gateway/dist/web/extension.js.map +1 -1
  650. package/packages/gateway/dist/web/http.d.ts +7 -0
  651. package/packages/gateway/dist/web/http.d.ts.map +1 -1
  652. package/packages/gateway/dist/web/http.js +15 -1
  653. package/packages/gateway/dist/web/http.js.map +1 -1
  654. package/packages/gateway/dist/web/index.d.ts +1 -1
  655. package/packages/gateway/dist/web/index.d.ts.map +1 -1
  656. package/packages/gateway/dist/web/index.js +1 -1
  657. package/packages/gateway/dist/web/index.js.map +1 -1
  658. package/packages/gateway/dist/web/onboarding.js +1 -1
  659. package/packages/gateway/dist/web/onboarding.js.map +1 -1
  660. package/packages/gateway/dist/web/remote-hand.d.ts.map +1 -1
  661. package/packages/gateway/dist/web/remote-hand.js +45 -2
  662. package/packages/gateway/dist/web/remote-hand.js.map +1 -1
  663. package/packages/gateway/dist/web/secrets.d.ts +26 -1
  664. package/packages/gateway/dist/web/secrets.d.ts.map +1 -1
  665. package/packages/gateway/dist/web/secrets.js +66 -3
  666. package/packages/gateway/dist/web/secrets.js.map +1 -1
  667. package/packages/gateway/dist/web/server.d.ts +38 -2
  668. package/packages/gateway/dist/web/server.d.ts.map +1 -1
  669. package/packages/gateway/dist/web/server.js +634 -147
  670. package/packages/gateway/dist/web/server.js.map +1 -1
  671. package/packages/gateway/dist/web/sessions.d.ts +24 -14
  672. package/packages/gateway/dist/web/sessions.d.ts.map +1 -1
  673. package/packages/gateway/dist/web/sessions.js +42 -14
  674. package/packages/gateway/dist/web/sessions.js.map +1 -1
  675. package/packages/gateway/dist/web/signed-out.d.ts +10 -0
  676. package/packages/gateway/dist/web/signed-out.d.ts.map +1 -1
  677. package/packages/gateway/dist/web/signed-out.js +47 -0
  678. package/packages/gateway/dist/web/signed-out.js.map +1 -1
  679. package/packages/gateway/dist/web/version.d.ts +2 -0
  680. package/packages/gateway/dist/web/version.d.ts.map +1 -1
  681. package/packages/gateway/dist/web/version.js +5 -1
  682. package/packages/gateway/dist/web/version.js.map +1 -1
  683. package/packages/install/dist/.tsbuildinfo +1 -1
  684. package/packages/install/dist/app-layout.d.ts +127 -0
  685. package/packages/install/dist/app-layout.d.ts.map +1 -0
  686. package/packages/install/dist/app-layout.js +323 -0
  687. package/packages/install/dist/app-layout.js.map +1 -0
  688. package/packages/install/dist/index.d.ts +1 -0
  689. package/packages/install/dist/index.d.ts.map +1 -1
  690. package/packages/install/dist/index.js +1 -0
  691. package/packages/install/dist/index.js.map +1 -1
  692. package/packages/install/dist/launcher.d.ts.map +1 -1
  693. package/packages/install/dist/launcher.js +18 -4
  694. package/packages/install/dist/launcher.js.map +1 -1
  695. package/packages/install/dist/provenance.d.ts +34 -0
  696. package/packages/install/dist/provenance.d.ts.map +1 -0
  697. package/packages/install/dist/provenance.js +200 -0
  698. package/packages/install/dist/provenance.js.map +1 -0
  699. package/packages/install/dist/supervisor.d.ts.map +1 -1
  700. package/packages/install/dist/supervisor.js +9 -4
  701. package/packages/install/dist/supervisor.js.map +1 -1
  702. package/packages/install/dist/upgrade.d.ts +77 -3
  703. package/packages/install/dist/upgrade.d.ts.map +1 -1
  704. package/packages/install/dist/upgrade.js +325 -43
  705. package/packages/install/dist/upgrade.js.map +1 -1
  706. package/packages/runtime/dist/.tsbuildinfo +1 -1
  707. package/packages/runtime/dist/delegate.d.ts +8 -0
  708. package/packages/runtime/dist/delegate.d.ts.map +1 -1
  709. package/packages/runtime/dist/delegate.js +19 -1
  710. package/packages/runtime/dist/delegate.js.map +1 -1
  711. package/packages/runtime/dist/loop.d.ts +2 -0
  712. package/packages/runtime/dist/loop.d.ts.map +1 -1
  713. package/packages/runtime/dist/loop.js +18 -4
  714. package/packages/runtime/dist/loop.js.map +1 -1
  715. package/packages/tools/artifacts/dist/.tsbuildinfo +1 -1
  716. package/packages/tools/browser/dist/.tsbuildinfo +1 -1
  717. package/packages/tools/browser/dist/availability.d.ts +2 -2
  718. package/packages/tools/browser/dist/availability.d.ts.map +1 -1
  719. package/packages/tools/browser/dist/availability.js +2 -2
  720. package/packages/tools/browser/dist/availability.js.map +1 -1
  721. package/packages/tools/browser/dist/controller.d.ts +82 -58
  722. package/packages/tools/browser/dist/controller.d.ts.map +1 -1
  723. package/packages/tools/browser/dist/controller.js +823 -217
  724. package/packages/tools/browser/dist/controller.js.map +1 -1
  725. package/packages/tools/browser/dist/driver.d.ts +3 -1
  726. package/packages/tools/browser/dist/driver.d.ts.map +1 -1
  727. package/packages/tools/browser/dist/driver.js +82 -1
  728. package/packages/tools/browser/dist/driver.js.map +1 -1
  729. package/packages/tools/browser/dist/extension.d.ts +70 -5
  730. package/packages/tools/browser/dist/extension.d.ts.map +1 -1
  731. package/packages/tools/browser/dist/extension.js +94 -9
  732. package/packages/tools/browser/dist/extension.js.map +1 -1
  733. package/packages/tools/browser/dist/index.d.ts +17 -7
  734. package/packages/tools/browser/dist/index.d.ts.map +1 -1
  735. package/packages/tools/browser/dist/index.js +35 -15
  736. package/packages/tools/browser/dist/index.js.map +1 -1
  737. package/packages/tools/browser/dist/logins.d.ts +150 -0
  738. package/packages/tools/browser/dist/logins.d.ts.map +1 -0
  739. package/packages/tools/browser/dist/logins.js +476 -0
  740. package/packages/tools/browser/dist/logins.js.map +1 -0
  741. package/packages/tools/browser/dist/manager.d.ts +39 -21
  742. package/packages/tools/browser/dist/manager.d.ts.map +1 -1
  743. package/packages/tools/browser/dist/manager.js +229 -136
  744. package/packages/tools/browser/dist/manager.js.map +1 -1
  745. package/packages/tools/browser/dist/routes.d.ts +151 -0
  746. package/packages/tools/browser/dist/routes.d.ts.map +1 -0
  747. package/packages/tools/browser/dist/routes.js +331 -0
  748. package/packages/tools/browser/dist/routes.js.map +1 -0
  749. package/packages/tools/browser/dist/secrets.d.ts +3 -1
  750. package/packages/tools/browser/dist/secrets.d.ts.map +1 -1
  751. package/packages/tools/browser/dist/secrets.js +10 -6
  752. package/packages/tools/browser/dist/secrets.js.map +1 -1
  753. package/packages/tools/browser/dist/service.d.ts +182 -115
  754. package/packages/tools/browser/dist/service.d.ts.map +1 -1
  755. package/packages/tools/browser/dist/service.js +491 -388
  756. package/packages/tools/browser/dist/service.js.map +1 -1
  757. package/packages/tools/browser/dist/settings.d.ts +88 -0
  758. package/packages/tools/browser/dist/settings.d.ts.map +1 -0
  759. package/packages/tools/browser/dist/settings.js +86 -0
  760. package/packages/tools/browser/dist/settings.js.map +1 -0
  761. package/packages/tools/browser/dist/telemetry.d.ts +203 -0
  762. package/packages/tools/browser/dist/telemetry.d.ts.map +1 -0
  763. package/packages/tools/browser/dist/telemetry.js +142 -0
  764. package/packages/tools/browser/dist/telemetry.js.map +1 -0
  765. package/packages/tools/browser/dist/types.d.ts +102 -3
  766. package/packages/tools/browser/dist/types.d.ts.map +1 -1
  767. package/packages/tools/browser/dist/types.js +27 -9
  768. package/packages/tools/browser/dist/types.js.map +1 -1
  769. package/packages/tools/email/dist/.tsbuildinfo +1 -1
  770. package/packages/tools/host/dist/.tsbuildinfo +1 -1
  771. package/packages/tools/mcp/dist/.tsbuildinfo +1 -1
  772. package/packages/tools/memory/dist/.tsbuildinfo +1 -1
  773. package/packages/tools/web/dist/.tsbuildinfo +1 -1
  774. package/packages/web/dist/assets/index-DYfeUW-0.css +1 -0
  775. package/packages/web/dist/assets/index-vpc904_b.js +146 -0
  776. package/packages/web/dist/assets/{lottie_light-CKJ88Yuk.js → lottie_light-BFAMYgLA.js} +1 -1
  777. package/packages/web/dist/build.json +1 -1
  778. package/packages/web/dist/index.html +2 -2
  779. package/packages/web/dist/precache.json +4 -4
  780. package/node_modules/@buddi/gateway/dist/web/apps.d.ts +0 -18
  781. package/node_modules/@buddi/gateway/dist/web/apps.d.ts.map +0 -1
  782. package/node_modules/@buddi/gateway/dist/web/apps.js +0 -84
  783. package/node_modules/@buddi/gateway/dist/web/apps.js.map +0 -1
  784. package/node_modules/@buddi/gateway/dist/web/tailscale.d.ts.map +0 -1
  785. package/node_modules/@buddi/gateway/dist/web/tailscale.js.map +0 -1
  786. package/node_modules/@buddi/tool-browser/dist/computer.d.ts +0 -153
  787. package/node_modules/@buddi/tool-browser/dist/computer.d.ts.map +0 -1
  788. package/node_modules/@buddi/tool-browser/dist/computer.js +0 -392
  789. package/node_modules/@buddi/tool-browser/dist/computer.js.map +0 -1
  790. package/node_modules/@buddi/tool-browser/dist/native/buddi-computer +0 -0
  791. package/node_modules/pino/test/browser-bindings.test.js +0 -114
  792. package/node_modules/pino/test/browser-redaction.test.js +0 -264
  793. package/node_modules/pino/test/browser-set-bindings.test.js +0 -99
  794. package/node_modules/pino/test/caller.test.js +0 -20
  795. package/node_modules/pino/test/fixtures/caller-loop.js +0 -6
  796. package/node_modules/pino/test/smoke.js +0 -59
  797. package/node_modules/pino/test/types/pino-import.tst.cts +0 -30
  798. package/node_modules/pino/test/types/pino-import.tst.js +0 -73
  799. package/node_modules/pino/test/types/pino-multistream.tst.ts +0 -49
  800. package/node_modules/pino/test/types/pino-top-export.tst.ts +0 -32
  801. package/node_modules/pino/test/types/pino-type-only.tst.ts +0 -77
  802. package/node_modules/pino/test/types/tsconfig.json +0 -10
  803. package/packages/gateway/dist/web/apps.d.ts +0 -18
  804. package/packages/gateway/dist/web/apps.d.ts.map +0 -1
  805. package/packages/gateway/dist/web/apps.js +0 -84
  806. package/packages/gateway/dist/web/apps.js.map +0 -1
  807. package/packages/gateway/dist/web/tailscale.d.ts.map +0 -1
  808. package/packages/gateway/dist/web/tailscale.js.map +0 -1
  809. package/packages/tools/browser/dist/computer.d.ts +0 -153
  810. package/packages/tools/browser/dist/computer.d.ts.map +0 -1
  811. package/packages/tools/browser/dist/computer.js +0 -392
  812. package/packages/tools/browser/dist/computer.js.map +0 -1
  813. package/packages/tools/browser/dist/native/buddi-computer +0 -0
  814. package/packages/web/dist/assets/index-CN1AXMI1.css +0 -1
  815. package/packages/web/dist/assets/index-CQRE9LJX.js +0 -145
@@ -56,7 +56,6 @@ import { carryOverDeps } from '../surfaces/carry-over.js';
56
56
  import { deleteCarryOver } from '../surfaces/browser-handoff.js';
57
57
  import { ProviderSettingsError } from '../providers.js';
58
58
  import { ProviderAccountError } from '../provider-accounts.js';
59
- import { listBrowserProfiles, listInstalledApps } from './apps.js';
60
59
  import { agentSearchPath, EXAMPLES_AGENTS_DIR } from '../agents/catalog.js';
61
60
  import { setDelegatesFromWeb } from './write.js';
62
61
  import { mlxhBaseUrl, probeMlxh } from '../mlxh.js';
@@ -65,7 +64,7 @@ import { OnboardingRefusal, WEB_ONBOARDING_STEPS, WEB_ONBOARDING_SURFACE, claimO
65
64
  import { TakeOnRefusal, readTakeOn, startTakeOn } from './take-on.js';
66
65
  import { TelegramWebError, saveTelegramToken, telegramBot, telegramDevices, telegramPairing, telegramStatus, unpairTelegramDevice, } from './telegram.js';
67
66
  import { PAGE_ROUTE, sendPageFile, actOnPage, actRateLimited, listPageDescriptors, runPageQuery, } from './pages.js';
68
- import { listSecrets, secretUses, secretsAct } from './secrets.js';
67
+ import { listSecrets, ownerLoginKey, ownerLoginNames, ownerLoginStore, secretUses, secretsAct } from './secrets.js';
69
68
  import { sayRoute, transcribeRoute } from './speech.js';
70
69
  import { createServer } from 'node:http';
71
70
  import { asksEachTime, getAction, idleRolloverMs, inRecovery, listMissions, listPendingActions, isJobState, parseAgentFile, setSentinelEnabled, snoozeFinding, snoozeFindings, unmuteFindings } from '@buddi/core';
@@ -77,6 +76,7 @@ import { hostService } from '@buddi/tool-host';
77
76
  import { listToolPermissions, revokeToolPermission, getArtifact, readArtifactBytes, artifactBytesExist, discardUnreferencedUpload, listLibrary, getLibraryEntry, decodeCursor, filterKey, textPreviewable, readArtifactPrefix, FILE_FAMILIES, LIBRARY_PAGE_MAX, getOwnerProfile, saveOwnerProfile, isKnownTimezone, listGroups, getGroup, createGroup, updateGroup, archiveGroup, deleteGroup, restoreGroup, clearGroupHistory, groupHistorySize, GROUP_UNDO_MS, GroupRefusal, createGroupConversation, listGroupConversations, latestGroupConversation, openGroupRequest, conversationGroup } from '@buddi/core';
78
77
  import { EXPORT_MIME, MAX_EXPORT_SOURCE_BYTES, exportFormats, exportName } from '../export/document.js';
79
78
  import { ExportRefused, runExport } from '../export/convert.js';
79
+ import { createVault } from '@buddi/core';
80
80
  import { beginOnboarding, completeOnboarding, markStepDone, setOnboardingDetails, skipOnboarding, readWebSetting, writeWebSetting, isAssetKey, readPluginAsset } from '@buddi/core';
81
81
  import { listMemory, setPreference, forgetPreference, updateNote, forgetNote } from '@buddi/tool-memory';
82
82
  import { purgeGroups, stopGroupWork } from './group-lifecycle.js';
@@ -94,7 +94,14 @@ import { ATTACHMENTS_UNAVAILABLE, CHAT_CONVERSATIONS_LIMIT, CHAT_UNAVAILABLE, WE
94
94
  import { readAgentAttention, streamAttention } from './attention.js';
95
95
  import { PreviewApp, PreviewTickets, parsePreviewApiPath } from './preview.js';
96
96
  import { allowedOrigins, isLoopback, webAssetsDir, webUrl } from './config.js';
97
- import { TAILSCALE_SETTING_KEY, daemonWhois, isLoginRefusal, isUnansweredRefusal, plausibleLogin, proxiedThroughTailscale, sameLogin, tailscaleIdentity, tailscaleRefusalReason, tailscaleSelf, toTailscaleSetting, } from './tailscale.js';
97
+ import { TAILSCALE_SETTING_KEY, daemonWhois, plausibleLogin, proxiedThroughTailscale, tailscaleProvider, tailscaleSelf, tailscaleServeCommand, } from './access/tailscale.js';
98
+ import { CLOUDFLARE_SETTING_KEY, cloudflareProvider, createJwks, normalizeTeamDomain, plausibleTeamDomain, validateCloudflareInput, } from './access/cloudflare.js';
99
+ import { arrivalOf } from './access/arrival.js';
100
+ import { CloudflareApiError, createCloudflareApi, CLOUDFLARE_PERMISSION_LINES, CLOUDFLARE_TOKEN_URL } from './access/cloudflare-api.js';
101
+ import { CLOUDFLARE_SETUP_KEY, checkSetupInput, claimSetupOperation, freshProgress, setupBusySentence, setupOperation, removeCloudflareSetup, runCloudflareSetup, } from './access/cloudflare-setup.js';
102
+ import { ownerSecretTokenStore } from './access/cloudflare-token.js';
103
+ import { createAccessRegistry } from './access/registry.js';
104
+ import { createIngress } from './access/ingress.js';
98
105
  import { clientKey } from './client-key.js';
99
106
  import { retryHref, sendSignedOut, wantsSignedOutPage } from './signed-out.js';
100
107
  import { extensionEndpoint } from './extension.js';
@@ -116,6 +123,7 @@ import { createRequirements } from '../plugins/requires.js';
116
123
  import { placesList, placesRoute } from './places.js';
117
124
  import { peopleRoute } from './people.js';
118
125
  import { syncDateMissions } from '../missions/dates.js';
126
+ import { resumeParkedForPage } from '../missions/parked.js';
119
127
  import { OWNER_DATE_FORMATS, OWNER_TIME_FORMATS, validDayMonth } from '@buddi/core';
120
128
  import { readFacts, webSettingsStore } from '../tips/facts.js';
121
129
  import { dismissAgentOffer, isPendingAccept, raiseAgentOffers, readAgentOffers } from './agent-offers.js';
@@ -160,6 +168,21 @@ const PREVIEW_APPS = new WeakMap();
160
168
  * reloads once the next process answers `/_buddi/ready` with a new `boot`.
161
169
  */
162
170
  const CLOSING_SAYS = new WeakMap();
171
+ /** Each server's ingress listener (web/access/ingress.ts). */
172
+ const INGRESS_OF = new WeakMap();
173
+ /** Each server's request handler, for a listener without a socket of its own (the relay, provider 3). */
174
+ const DISPATCH_OF = new WeakMap();
175
+ /** The ingress listener belonging to a server built by `createWebApp`. */
176
+ export function ingressOf(server) {
177
+ return INGRESS_OF.get(server);
178
+ }
179
+ /**
180
+ * The request handler of a server built by `createWebApp`: what an in-process
181
+ * listener hands a request to after tagging its arrival (`markRequestArrival`).
182
+ */
183
+ export function dispatchOf(server) {
184
+ return DISPATCH_OF.get(server);
185
+ }
163
186
  /** How long a closing frame may take to reach the pages before the sockets go. */
164
187
  const CLOSING_FLUSH_MS = 250;
165
188
  /** The chat surface this server is running, if any. */
@@ -270,13 +293,13 @@ export function createWebApp(deps) {
270
293
  // A locked dashboard drives nothing (docs/dashboard.md, "Lock screen").
271
294
  if (await lock.locked(session))
272
295
  return null;
273
- if (session.via === 'tailscale') {
274
- let refusal;
275
- const confirmed = await identityOf(req, now, (reason) => { refusal = reason; });
276
- if (!confirmed || !sameLogin(confirmed.login, session.tailscaleLogin)) {
277
- // "Could not ask" is not an answer: no socket now, but the session stays.
278
- if (!confirmed && isUnansweredRefusal(refusal))
279
- return null;
296
+ if (session.via === 'provider') {
297
+ const confirmed = await access.confirm(session, req, now);
298
+ // "Could not ask", or a stray request without the provider's proof:
299
+ // no socket now, but the session stays.
300
+ if (confirmed.answer === 'unanswered' || confirmed.answer === 'refuse')
301
+ return null;
302
+ if (confirmed.answer === 'end') {
280
303
  await sessions.destroy(session.id).catch(() => { });
281
304
  return null;
282
305
  }
@@ -427,6 +450,7 @@ export function createWebApp(deps) {
427
450
  now: deps.now,
428
451
  get timezone() { return deps.timezone; },
429
452
  log,
453
+ browser: deps.browser ?? browserHost(deps.env ?? process.env),
430
454
  ...deps.chat,
431
455
  })
432
456
  : undefined;
@@ -438,6 +462,27 @@ export function createWebApp(deps) {
438
462
  * Failures are the recovery's own business and land in the log.
439
463
  */
440
464
  void chat?.recoverPendingInput().catch((err) => log(`web chat: recovering queued input failed: ${err instanceof Error ? err.message : String(err)}`));
465
+ /*
466
+ * Give it back: the run that waited on the page carries on. A mission parked
467
+ * on it wakes (missions/parked.ts); otherwise the conversation the take-over
468
+ * held gets its turn. Either way with no message from the owner.
469
+ */
470
+ const offGiveBack = (deps.browser ?? browserHost(deps.env ?? process.env)).onGiveBack?.((info) => {
471
+ void (async () => {
472
+ if (await resumeParkedForPage(deps.pool, info.conversationId, deps.now()))
473
+ return;
474
+ await chat?.continueAfterGiveBack({ conversationId: info.conversationId, agentId: info.agentId });
475
+ })().catch((err) => log(`browser: carrying on after the page was given back failed: ${err instanceof Error ? err.message : String(err)}`));
476
+ });
477
+ /*
478
+ * A sign-in the owner made on a page they held, kept when they say Save
479
+ * (docs/browser.md, "Saving a sign-in"): the browser host's keeper hands the
480
+ * pair to core's owner-secret store, as the owner, and to nothing else.
481
+ */
482
+ {
483
+ const loginDeps = { pool: deps.pool, registry: deps.registry, ctx: deps.ctx, now: deps.now };
484
+ (deps.browser ?? browserHost(deps.env ?? process.env)).logins?.useStore(ownerLoginStore(loginDeps), ownerLoginNames(loginDeps), ownerLoginKey(deps.env ?? process.env));
485
+ }
441
486
  const streams = new StreamBudget();
442
487
  // A bundle script's run asks every time: its card offers no standing permission (`asksEachTime`).
443
488
  const permissionScopes = (tool, envelope) => deps.registry.lookup(tool)?.reusableApproval && !asksEachTime(envelope)
@@ -483,55 +528,204 @@ export function createWebApp(deps) {
483
528
  // ticket-and-session gate. The override is a test seam, nothing more.
484
529
  const openAccess = deps.openAccess ?? (deps.env?.BUDDI_WEB_REQUIRE_AUTH !== '1' && isLoopback(deps.config.host));
485
530
  /*
486
- * Signing in through Tailscale: the daemon this process asks.
531
+ * Trusted access providers (web/access/): something in front of buddi
532
+ * proves who is knocking, buddi checks the proof itself, and its own
533
+ * `remote` session rules apply. Tailscale (provider 1) asks the local
534
+ * daemon; Cloudflare Access (provider 2) verifies the JWT Access signs.
487
535
  *
488
536
  * Which sessions were established that way is a field on the session itself
489
- * (`via`), not a map beside it — provenance that can drift out of step with
490
- * the session it describes is provenance that eventually lies. It is what
491
- * keeps a tailnet session from widening its own access (`PUT /api/tailscale`
537
+ * (`via: 'provider'` and `provider`), not a map beside it. It is what keeps
538
+ * a provider session from widening its own access (changing a provider
492
539
  * wants a session from this machine) and what lets `/api/session` say how
493
540
  * the browser got in.
494
541
  */
495
542
  const whois = deps.tailscale?.whois ?? daemonWhois();
496
543
  const tailscaleSelfOf = deps.tailscale?.self ?? (() => tailscaleSelf());
544
+ const jwks = deps.cloudflare?.jwks ?? createJwks({ log });
545
+ const boundPort = () => server.address()?.port ?? deps.config.port;
546
+ const accessCtx = {
547
+ dashboardPort: boundPort,
548
+ ingressPort: () => ingress.port(),
549
+ publicOrigin: () => deps.config.publicOrigin,
550
+ };
551
+ const tailscale = tailscaleProvider({ whois, self: tailscaleSelfOf, log });
552
+ const cloudflare = cloudflareProvider({ jwks, log, ingressProblem: () => ingress.problem() });
553
+ const access = createAccessRegistry({
554
+ providers: [tailscale, cloudflare],
555
+ readSetting: (key) => readWebSetting(deps.pool, key),
556
+ });
557
+ /** The command that publishes this dashboard on the tailnet, with this installation's own ports. */
558
+ const serveCommand = () => tailscaleServeCommand(boundPort(), deps.config.publicOrigin);
559
+ const readTailscaleSetting = async () => access.settingOf(tailscale);
560
+ /*
561
+ * The Cloudflare setting as last read, so the synchronous Origin check can
562
+ * honour the public address the panel stored. Refreshed on every read.
563
+ */
564
+ let cloudflareSeen = null;
565
+ const readCloudflareSetting = async () => {
566
+ cloudflareSeen = await access.settingOf(cloudflare);
567
+ return cloudflareSeen;
568
+ };
569
+ /** Did this request come through a provider, or does it hold a session one minted? Then the block is read-only. */
570
+ const throughProvider = (req, session) => session.via === 'provider' || arrivalOf(req) !== 'main' || proxiedThroughTailscale(req);
571
+ /** The Tailscale panel's whole payload (unchanged by the move behind the provider interface). */
572
+ const tailscaleView = async (req, session) => {
573
+ const stored = await readTailscaleSetting();
574
+ const daemon = await tailscaleSelfOf();
575
+ return {
576
+ enabled: stored.enabled,
577
+ login: stored.login,
578
+ available: daemon.available,
579
+ self: daemon.self,
580
+ proxied: throughProvider(req, session),
581
+ // What the owner has to run on this machine, with this installation's own two ports in it.
582
+ serveCommand: serveCommand(),
583
+ };
584
+ };
585
+ /** The Cloudflare panel's payload: the stored fields, the status, the setup copy with the real ingress port. */
586
+ const cloudflareView = async (req, session) => {
587
+ const stored = await readCloudflareSetting();
588
+ const status = await cloudflare.status(stored, accessCtx);
589
+ const visit = cloudflare.lastVisit();
590
+ return {
591
+ enabled: stored.enabled,
592
+ teamDomain: stored.teamDomain,
593
+ aud: stored.aud,
594
+ email: stored.email,
595
+ publicOrigin: stored.publicOrigin,
596
+ status,
597
+ ingressPort: ingress.port() ?? accessCtx.dashboardPort() + 2,
598
+ listening: ingress.port() !== null,
599
+ lastVisit: visit ? { at: visit.at.toISOString(), email: visit.email } : null,
600
+ setup: cloudflare.setup(stored, accessCtx),
601
+ proxied: throughProvider(req, session),
602
+ };
603
+ };
497
604
  /**
498
- * The command that publishes this dashboard on the tailnet, with this
499
- * installation's own numbers in it: the HTTPS port of the configured public
500
- * origin, and the port the gateway is actually listening on.
605
+ * Store a Cloudflare setting and do what a Save does: end the sessions it
606
+ * admitted unless the same person, team and application stay on, then bind
607
+ * or close the ingress listener. The panel's Save and "Set it up for me"
608
+ * both come through here.
501
609
  */
502
- const serveCommand = () => {
503
- const bound = server.address()?.port ?? deps.config.port;
504
- let https = 443;
505
- if (deps.config.publicOrigin) {
506
- const port = new URL(deps.config.publicOrigin).port;
507
- if (port !== '')
508
- https = Number(port);
610
+ const applyCloudflareSetting = async (value) => {
611
+ const before = await readCloudflareSetting();
612
+ await writeWebSetting(deps.pool, CLOUDFLARE_SETTING_KEY, value);
613
+ await readCloudflareSetting();
614
+ const same = before.enabled && value.enabled && before.email.toLowerCase() === value.email.toLowerCase()
615
+ && before.teamDomain === value.teamDomain && before.aud === value.aud;
616
+ if (!same) {
617
+ const forgotten = new Set();
618
+ await sessions.forget((s) => { const drop = s.via === 'provider' && s.provider === 'cloudflare-access'; if (drop && s.id)
619
+ forgotten.add(s.id); return drop; });
620
+ hand.revoke((lease) => forgotten.has(lease), 'Cloudflare access changed. Sign in again.');
509
621
  }
510
- return `tailscale serve --bg --https=${https} http://127.0.0.1:${bound}`;
622
+ await ingress.sync();
511
623
  };
512
- const readTailscaleSetting = async () => toTailscaleSetting(await readWebSetting(deps.pool, TAILSCALE_SETTING_KEY).catch(() => null));
513
- /**
514
- * Who the daemon says this request is, with the sign-in budget in front of
515
- * the daemon. Used to mint a session and, on every later request, to confirm
516
- * the one the browser is holding.
624
+ /*
625
+ * "Set it up for me" (access/cloudflare-setup.ts): one run at a time, in
626
+ * the background — the health wait lasts as long as the owner takes to run
627
+ * one command — and its progress kept here for the panel to poll. The token
628
+ * is an owner secret, read once per run.
629
+ */
630
+ let cfTokens = deps.cloudflare?.tokens ?? null;
631
+ const tokenStore = () => (cfTokens ??= ownerSecretTokenStore(deps.pool, createVault({ env: deps.env ?? process.env })));
632
+ let setupRun = null;
633
+ const readSetupRecord = async () => {
634
+ const row = await readWebSetting(deps.pool, CLOUDFLARE_SETUP_KEY);
635
+ return row && typeof row === 'object' && typeof row.host === 'string' ? row : null;
636
+ };
637
+ const setupDepsFor = (token, lease, signal) => ({
638
+ api: createCloudflareApi({ token, transport: deps.cloudflare?.api?.transport, baseUrl: deps.cloudflare?.api?.baseUrl }),
639
+ ingressPort: ingress.port() ?? (askedIngressPort() || null) ?? accessCtx.dashboardPort() + 2,
640
+ platform: deps.cloudflare?.platform ?? process.platform,
641
+ readSetting: readCloudflareSetting,
642
+ saveSetting: applyCloudflareSetting,
643
+ readRecord: readSetupRecord,
644
+ saveRecord: (record) => writeWebSetting(deps.pool, CLOUDFLARE_SETUP_KEY, record),
645
+ test: (team) => jwks.refresh(team),
646
+ ...(deps.cloudflare?.setup ?? {}),
647
+ signal,
648
+ lease,
649
+ });
650
+ const setupView = async () => {
651
+ const record = await readSetupRecord();
652
+ return {
653
+ progress: setupRun?.progress ?? freshProgress(record?.host ?? '', record?.email ?? ''),
654
+ tokenStored: await tokenStore().has().catch(() => false),
655
+ record: record ? { host: record.host, email: record.email, zone: record.zone.name, teamDomain: record.teamDomain } : null,
656
+ permissions: CLOUDFLARE_PERMISSION_LINES,
657
+ tokenUrl: CLOUDFLARE_TOKEN_URL,
658
+ ingressPort: ingress.port() ?? (askedIngressPort() || null) ?? accessCtx.dashboardPort() + 2,
659
+ };
660
+ };
661
+ /** Run in the background; the lease (claimed by the caller) goes when the run ends. */
662
+ const startSetup = (lease, job, first) => {
663
+ const abort = new AbortController();
664
+ const run = { progress: first, abort, done: Promise.resolve() };
665
+ setupRun = run;
666
+ run.done = job((p) => { run.progress = p; }, abort.signal)
667
+ .then((p) => { run.progress = p; })
668
+ .catch(() => { run.progress = { ...run.progress, state: 'failed', error: 'The setup stopped unexpectedly. Try again.' }; })
669
+ .finally(() => lease.release());
670
+ };
671
+ /*
672
+ * A setting written elsewhere (`buddi access cloudflare setup` writes the
673
+ * database directly) reaches the ingress listener here: bound or closed
674
+ * within a quarter of a minute.
517
675
  */
518
- const identityOf = (req, now, onRefusal) => tailscaleIdentity(req, {
519
- setting: readTailscaleSetting,
520
- whois,
676
+ const ingressResync = setInterval(() => {
677
+ void readCloudflareSetting().then((setting) => {
678
+ if (setting.enabled !== (ingress.port() !== null))
679
+ return ingress.sync();
680
+ return undefined;
681
+ }).catch(() => undefined);
682
+ }, 15_000);
683
+ ingressResync.unref?.();
684
+ /** Every provider's row, for "Sign in from elsewhere". */
685
+ const accessView = async (req, session) => ({
686
+ proxied: throughProvider(req, session),
687
+ providers: await Promise.all(access.providers.map(async (p) => {
688
+ const setting = await access.settingOf(p);
689
+ return { id: p.id, title: p.title, identity: p.identity, proxy: p.proxy, enabled: p.enabled(setting), status: await p.status(setting, accessCtx) };
690
+ })),
691
+ });
692
+ /*
693
+ * The ingress listener (specs/trusted-access.md §3.3): a second loopback
694
+ * listener, the dashboard's port + 2 (or `BUDDI_INGRESS_PORT`), bound only
695
+ * while a this-machine provider other than Tailscale is on. Every request
696
+ * on it is remote, whatever its headers or Host say, and it never passes a
697
+ * loopback-only check. cloudflared points here, never at the dashboard's
698
+ * own port, so a tunnel arrival can never be mistaken for this machine.
699
+ */
700
+ /** `BUDDI_INGRESS_PORT`, when it names a port. */
701
+ function askedIngressPort() {
702
+ const raw = (deps.env ?? process.env).BUDDI_INGRESS_PORT?.trim();
703
+ const asked = raw ? Number(raw) : NaN;
704
+ return Number.isInteger(asked) && asked >= 0 && asked <= 65535 ? asked : null;
705
+ }
706
+ const ingress = createIngress({
707
+ onRequest: (req, res) => onRequest(req, res),
708
+ onUpgrade: (req, socket, head) => { server.emit('upgrade', req, socket, head); },
709
+ port: () => askedIngressPort() ?? (deps.config.port === 0 ? 0 : boundPort() + 2),
710
+ wanted: async () => (await readCloudflareSetting()).enabled,
521
711
  log,
522
- now: () => now,
523
- onRefusal,
524
712
  });
525
713
  /**
526
714
  * The pair a browser holds: the HttpOnly session and the readable CSRF value
527
715
  * the page has to echo back in a header. Both carry the same `Max-Age`, which
528
716
  * is the lifetime this session's scope earned it.
529
717
  */
718
+ /*
719
+ * Secure cookies for a remote session behind an HTTPS proxy: the configured
720
+ * public origin, or anything on the ingress listener (Cloudflare's edge
721
+ * always speaks HTTPS to the browser).
722
+ */
723
+ const secureCookies = (req, scope) => scope === 'remote' && (!!deps.config.publicOrigin || arrivalOf(req) === 'ingress');
530
724
  const sessionCookies = (req, session) => {
531
725
  const maxAgeSeconds = SessionStore.maxAgeSeconds(session);
532
726
  return [
533
- cookieHeader(sessionCookieName(cookiePort(req)), session.id, { httpOnly: true, maxAgeSeconds, secure: session.scope === 'remote' && !!deps.config.publicOrigin }),
534
- cookieHeader(csrfCookieName(cookiePort(req)), session.csrf, { httpOnly: false, maxAgeSeconds, secure: session.scope === 'remote' && !!deps.config.publicOrigin }),
727
+ cookieHeader(sessionCookieName(cookiePort(req)), session.id, { httpOnly: true, maxAgeSeconds, secure: secureCookies(req, session.scope) }),
728
+ cookieHeader(csrfCookieName(cookiePort(req)), session.csrf, { httpOnly: false, maxAgeSeconds, secure: secureCookies(req, session.scope) }),
535
729
  ];
536
730
  };
537
731
  /**
@@ -540,7 +734,7 @@ export function createWebApp(deps) {
540
734
  * polling page then sends no credential at all, which counts as nothing.
541
735
  */
542
736
  const expiredCookies = (req) => {
543
- const secure = requestScope(req) === 'remote' && !!deps.config.publicOrigin;
737
+ const secure = secureCookies(req, requestScope(req));
544
738
  return [
545
739
  cookieHeader(sessionCookieName(cookiePort(req)), '', { httpOnly: true, maxAgeSeconds: 0, secure }),
546
740
  cookieHeader(csrfCookieName(cookiePort(req)), '', { httpOnly: false, maxAgeSeconds: 0, secure }),
@@ -548,9 +742,23 @@ export function createWebApp(deps) {
548
742
  };
549
743
  /**
550
744
  * What the signed-out page should offer this request: how it arrived,
551
- * whether Tailscale can sign it in, and whether a lockout is running.
745
+ * which provider can sign it in, why it could not, and whether a lockout is
746
+ * running.
552
747
  */
553
- const signedOutFor = async (req, pathname, search, now, refusal) => {
748
+ const signedOutFor = async (req, pathname, search, now, refused, key) => {
749
+ const lockedForMs = limiter.retryAfterMs(key, now);
750
+ const locked = lockedForMs > 0 ? { lockedForMs } : {};
751
+ if (arrivalOf(req) === 'ingress') {
752
+ return {
753
+ retry: retryHref(pathname, search),
754
+ arrived: 'remote',
755
+ provider: {
756
+ title: cloudflare.title,
757
+ ...(refused?.provider === 'cloudflare-access' ? { refusal: refused.refusal.refusal, kind: refused.refusal.kind } : { refusal: 'no-assertion', kind: 'other' }),
758
+ },
759
+ ...locked,
760
+ };
761
+ }
554
762
  const proxied = proxiedThroughTailscale(req);
555
763
  let publicHost;
556
764
  try {
@@ -569,14 +777,14 @@ export function createWebApp(deps) {
569
777
  const scope = requestScope(req);
570
778
  const arrived = proxied || (publicHost !== undefined && host === publicHost) ? 'tailnet' : scope === 'local' ? 'local' : 'remote';
571
779
  const setting = arrived === 'tailnet' ? await readTailscaleSetting() : null;
572
- const lockedForMs = limiter.retryAfterMs(clientKey(req), now);
780
+ const tailscaleRefusal = refused?.provider === 'tailscale' ? refused.refusal : undefined;
573
781
  return {
574
782
  retry: retryHref(pathname, search),
575
783
  arrived,
576
- tailscaleSignIn: !!setting?.enabled && !isLoginRefusal(refusal),
577
- ...(isLoginRefusal(refusal) ? { tailscaleRefusal: tailscaleRefusalReason(refusal) } : {}),
578
- ...(isUnansweredRefusal(refusal) ? { tailscaleUnanswered: true } : {}),
579
- ...(lockedForMs > 0 ? { lockedForMs } : {}),
784
+ tailscaleSignIn: !!setting?.enabled && tailscaleRefusal?.kind !== 'login',
785
+ ...(tailscaleRefusal?.kind === 'login' ? { tailscaleRefusal: tailscaleRefusal.sentence } : {}),
786
+ ...(tailscaleRefusal?.kind === 'unanswered' ? { tailscaleUnanswered: true } : {}),
787
+ ...locked,
580
788
  };
581
789
  };
582
790
  /**
@@ -585,7 +793,16 @@ export function createWebApp(deps) {
585
793
  * loopback page and the tailnet page each keep their own pair. Set, read and
586
794
  * checked through this one function.
587
795
  */
588
- const cookiePort = (req) => requestPort(req, server.address()?.port ?? deps.config.port, deps.config.publicOrigin);
796
+ const cookiePort = (req) => {
797
+ // Cloudflare's edge serves the page on HTTPS's own port, whatever
798
+ // cloudflared says on the way in; the page names its cookie by it.
799
+ if (arrivalOf(req) === 'ingress') {
800
+ const host = req.headers.host ?? '';
801
+ if (!/:\d+$/.test(host.replace(/^\[[^\]]*\]/, '')))
802
+ return 443;
803
+ }
804
+ return requestPort(req, server.address()?.port ?? deps.config.port, deps.config.publicOrigin);
805
+ };
589
806
  /**
590
807
  * The origins a write may claim, resolved against the port actually bound.
591
808
  *
@@ -596,8 +813,13 @@ export function createWebApp(deps) {
596
813
  let originCache;
597
814
  const allowed = () => {
598
815
  const bound = server.address()?.port ?? deps.config.port;
599
- if (originCache?.port !== bound) {
600
- originCache = { port: bound, set: new Set(allowedOrigins({ ...deps.config, port: bound })) };
816
+ // The public address the Cloudflare panel stored, while that provider is on.
817
+ const extra = cloudflareSeen?.enabled ? cloudflareSeen.publicOrigin : '';
818
+ if (originCache?.port !== bound || originCache.extra !== extra) {
819
+ const set = new Set(allowedOrigins({ ...deps.config, port: bound }));
820
+ if (extra)
821
+ set.add(extra);
822
+ originCache = { port: bound, extra, set };
601
823
  }
602
824
  return originCache.set;
603
825
  };
@@ -605,7 +827,7 @@ export function createWebApp(deps) {
605
827
  // caller that needs to drain it on shutdown (or in a test) can, without
606
828
  // `createWebApp` growing a second return value every existing caller would
607
829
  // have to unpack.
608
- const server = createServer((req, res) => {
830
+ function onRequest(req, res) {
609
831
  handle(req, res).catch((err) => {
610
832
  // The lock could not say whether this request may pass: refused, never let in (web/lock.ts).
611
833
  if (err instanceof LockUnavailable) {
@@ -622,9 +844,14 @@ export function createWebApp(deps) {
622
844
  else
623
845
  res.end();
624
846
  });
625
- });
847
+ }
848
+ const server = createServer(onRequest);
626
849
  if (chat)
627
850
  WEB_CHATS.set(server, chat);
851
+ INGRESS_OF.set(server, ingress);
852
+ // The seam the relay (provider 3) hands its in-process requests to,
853
+ // tagged `markRequestArrival(req, 'relay')` first. Never replayed as HTTP to 127.0.0.1.
854
+ DISPATCH_OF.set(server, onRequest);
628
855
  PREVIEW_APPS.set(server, previews);
629
856
  CLOSING_SAYS.set(server, async () => {
630
857
  const said = frame('closing', goingAway === undefined ? {} : { for: goingAway });
@@ -655,13 +882,25 @@ export function createWebApp(deps) {
655
882
  // rather than in `startWebServer` so every caller, tests included, has it.
656
883
  extension.attach(server);
657
884
  extension.attachPath(REMOTE_HAND_SOCKET_PATH, (req, socket, head) => hand.upgrade(req, socket, head));
658
- server.once('close', () => { extension.shutdown(); hand.shutdown(); clearInterval(idleSweep); });
885
+ server.once('close', () => { extension.shutdown(); hand.shutdown(); clearInterval(idleSweep); clearInterval(ingressResync); setupRun?.abort.abort(); void ingress.close(); });
886
+ server.once('close', () => offGiveBack?.());
659
887
  return server;
660
888
  async function handle(req, res) {
661
889
  const now = deps.now();
662
890
  const method = (req.method ?? 'GET').toUpperCase();
663
891
  const url = parseUrl(req);
664
- const key = clientKey(req);
892
+ /*
893
+ * The bucket failed sign-ins count against (client-key.ts, §7.5): by the
894
+ * arrival path, so a stranger behind a tunnel can never lock out this
895
+ * machine or the tailnet. A request on the ingress listener is asked of
896
+ * its provider at once — a JWT check against cached keys — because its
897
+ * bucket depends on it: `cf:unverified` until it verifies, then the
898
+ * client's own address.
899
+ */
900
+ let key = access.bucketOf(req) ?? clientKey(req);
901
+ const early = arrivalOf(req) === 'main' ? null : await access.identify(req, now);
902
+ if (early?.result.ok && early.result.identity.bucket)
903
+ key = early.result.identity.bucket;
665
904
  // No CORS, and therefore no preflight.
666
905
  if (method === 'OPTIONS')
667
906
  return sendEmpty(res, 405);
@@ -763,12 +1002,12 @@ export function createWebApp(deps) {
763
1002
  const presentedSession = cookies[sessionCookieName(cookiePort(req))];
764
1003
  let session = await sessions.resolve(presentedSession, scope, now);
765
1004
  /*
766
- * Why Tailscale gave this request no identity, when it was asked. Only
767
- * "this login is not the one allowed" changes what the signed-out page
768
- * says; every other reason is the ordinary "you're signed out".
1005
+ * Why a provider gave this request no identity, when it was asked. Only
1006
+ * "this person is not the one allowed" and "could not ask" change what
1007
+ * the signed-out page says; every other reason is the ordinary "you're
1008
+ * signed out".
769
1009
  */
770
- let refusal;
771
- const noteRefusal = (reason) => { refusal = reason; };
1010
+ let refused = early && !early.result.ok ? { provider: early.provider.id, refusal: early.result } : undefined;
772
1011
  /*
773
1012
  * Set when the browser presented a session that is gone: the refusal then
774
1013
  * expires its cookies, so it stops presenting them (and stops counting).
@@ -777,7 +1016,7 @@ export function createWebApp(deps) {
777
1016
  /*
778
1017
  * The answer to a refused request: the signed-out page for a person
779
1018
  * opening a page, the empty status for everything else (docs/web.md).
780
- * 401 is "signed out", 429 "too many tries from here", 503 "Tailscale
1019
+ * 401 is "signed out", 429 "too many tries from here", 503 "the provider
781
1020
  * could not be asked; the session is still good, try again".
782
1021
  */
783
1022
  const refuse = async (status = 401) => {
@@ -788,33 +1027,40 @@ export function createWebApp(deps) {
788
1027
  headers['Retry-After'] = '5';
789
1028
  if (!wantsSignedOutPage(req, method, url.pathname))
790
1029
  return sendEmpty(res, status, headers);
791
- return sendSignedOut(res, await signedOutFor(req, url.pathname, url.search, now, refusal), headers, status);
1030
+ return sendSignedOut(res, await signedOutFor(req, url.pathname, url.search, now, refused, key), headers, status);
792
1031
  };
793
- /* A Tailscale session this request ended: not a guess, so never a failed sign-in. */
1032
+ /* A provider session this request ended: not a guess, so never a failed sign-in. */
794
1033
  let revoked = false;
795
1034
  /*
796
- * A Tailscale session is re-confirmed on every single request.
1035
+ * A provider session is re-confirmed on every single request.
797
1036
  *
798
- * A cookie on its own would be a bearer token the tailnet identity is only
799
- * loosely related to: it would outlive the setting being turned off, the
800
- * allowed login being changed, and the device being handed to somebody
801
- * else. So the daemon is asked again — cheap, because a whois answer is
802
- * reused for a minute — and the login it names now must be the login this
803
- * session was minted for. Anything else and the session is gone, not
804
- * merely ignored: the browser is 401 and has to sign in again, which it
805
- * can only do if it is still the person the owner allowed.
1037
+ * A cookie on its own would be a bearer token the provider's identity is
1038
+ * only loosely related to: it would outlive the setting being turned off,
1039
+ * the allowed login being changed, and the device being handed to somebody
1040
+ * else. So the provider is asked again — Tailscale's daemon (a whois
1041
+ * answer is reused for a minute), Cloudflare's JWT on this very request —
1042
+ * and the person it names now must be the one this session was minted
1043
+ * for. Anything else and the session is gone, not merely ignored.
806
1044
  */
807
- if (session?.via === 'tailscale') {
808
- const confirmed = await identityOf(req, now, noteRefusal);
809
- if (!confirmed || !sameLogin(confirmed.login, session.tailscaleLogin)) {
810
- /*
811
- * The daemon could not be asked (busy, starting, timed out). That is
812
- * not an answer about anyone, so the session is not ended for it —
813
- * a restart's first burst of requests used to end every tailnet
814
- * session this way — and nothing is counted. This request waits.
815
- */
816
- if (!confirmed && isUnansweredRefusal(refusal))
817
- return refuse(503);
1045
+ if (session?.via === 'provider') {
1046
+ const confirmed = await access.confirm(session, req, now);
1047
+ if (confirmed.refusal)
1048
+ refused = { provider: session.provider, refusal: confirmed.refusal };
1049
+ /*
1050
+ * The provider could not be asked (a daemon busy or starting, keys that
1051
+ * could not be fetched). That is not an answer about anyone, so the
1052
+ * session is not ended for it and nothing is counted. This request waits.
1053
+ */
1054
+ if (confirmed.answer === 'unanswered')
1055
+ return refuse(503);
1056
+ /*
1057
+ * This request lacks the provider's proof (Cloudflare's header), but
1058
+ * the session is not ended for it: a stray request without the header
1059
+ * must not sign the owner out. Nothing is counted either.
1060
+ */
1061
+ if (confirmed.answer === 'refuse')
1062
+ return refuse(401);
1063
+ if (confirmed.answer === 'end') {
818
1064
  const ended = session.id;
819
1065
  // A hand this session was holding does not outlive the session.
820
1066
  hand.revoke((lease) => lease === ended);
@@ -823,7 +1069,7 @@ export function createWebApp(deps) {
823
1069
  revoked = true;
824
1070
  forgetCookies = true;
825
1071
  // The identity on this very request may still earn a new session below.
826
- refusal = undefined;
1072
+ refused = undefined;
827
1073
  }
828
1074
  }
829
1075
  /*
@@ -848,26 +1094,32 @@ export function createWebApp(deps) {
848
1094
  res.setHeader('Set-Cookie', sessionCookies(req, session));
849
1095
  }
850
1096
  /*
851
- * Signed in through Tailscale.
1097
+ * Signed in through a trusted access provider.
852
1098
  *
853
- * The proxy runs on this machine and the daemon on this machine confirms
854
- * who is behind the forwarded address; when that agrees with the login the
855
- * owner allowed, this is the owner's own tailnet identity arriving and the
856
- * request gets a `remote` session exactly as the ticket exchange would
857
- * have minted one — same cookies, same 12 hours, same CSRF on every write.
858
- * Anything less returns null and the request falls through to the 401
859
- * below, which is what it would have got before this existed.
1099
+ * Tailscale: the proxy runs on this machine and the daemon confirms who
1100
+ * is behind the forwarded address. Cloudflare Access: the JWT Access
1101
+ * signed verifies against the team's keys, for this application, naming
1102
+ * the allowed email, and it arrived on the ingress listener. When that
1103
+ * agrees with the person the owner allowed, the request gets a `remote`
1104
+ * session exactly as the ticket exchange would have minted one — same
1105
+ * cookies, same 12 hours, same CSRF on every write. Anything less falls
1106
+ * through to the 401 below. A provider identity signs in during a lockout.
860
1107
  */
861
1108
  if (!session) {
862
- const identity = await identityOf(req, now, noteRefusal);
863
- if (identity) {
1109
+ const asked = early ?? await access.identify(req, now);
1110
+ if (asked && !asked.result.ok)
1111
+ refused = { provider: asked.provider.id, refusal: asked.result };
1112
+ if (asked?.result.ok) {
1113
+ const identity = asked.result.identity;
864
1114
  limiter.reset(key);
865
1115
  const locked = await lock.startLocked('browser');
1116
+ const capMs = Math.min(asked.provider.absoluteCapMs, identity.expiresAt ? Math.max(0, identity.expiresAt.getTime() - now.getTime()) : asked.provider.absoluteCapMs);
866
1117
  session = sessions.create('remote', now, {
867
- via: 'tailscale',
868
- tailscaleLogin: identity.login,
869
- tailscaleAddress: identity.address,
870
- tailscaleName: identity.name,
1118
+ via: 'provider',
1119
+ provider: identity.provider,
1120
+ providerSubject: identity.subject,
1121
+ ...(identity.detail ? { providerDetail: identity.detail } : {}),
1122
+ absoluteCapMs: capMs,
871
1123
  }, locked ? { locked } : {});
872
1124
  res.setHeader('Set-Cookie', sessionCookies(req, session));
873
1125
  }
@@ -882,14 +1134,26 @@ export function createWebApp(deps) {
882
1134
  // the same stale cookie counts once however often it is sent: all tailnet
883
1135
  // and tunnel traffic shares 127.0.0.1, so a forgotten tab must not be
884
1136
  // able to lock the owner out of every way in.
885
- if (!presentedSession)
1137
+ //
1138
+ // A provider's credential that failed (a Cloudflare assertion that does
1139
+ // not verify) is an attempt too, cookie or not, in the bucket the
1140
+ // provider names (per Cf-Connecting-Ip). No assertion at all, or keys
1141
+ // that could not be fetched, carry no attempt.
1142
+ const tried = refused?.refusal.attempt;
1143
+ if (!presentedSession && !tried)
886
1144
  return refuse();
887
- forgetCookies = true;
1145
+ if (tried)
1146
+ key = tried.bucket;
1147
+ if (presentedSession)
1148
+ forgetCookies = true;
888
1149
  if (revoked)
889
1150
  return refuse();
890
1151
  if (limiter.blocked(key, now))
891
1152
  return refuse(429);
892
- limiter.failCredential(key, presentedSession, now);
1153
+ if (tried)
1154
+ limiter.failCredential(key, tried.credential, now);
1155
+ if (presentedSession)
1156
+ limiter.failCredential(key, presentedSession, now);
893
1157
  return refuse();
894
1158
  }
895
1159
  /*
@@ -964,7 +1228,7 @@ export function createWebApp(deps) {
964
1228
  agentIds: () => deps.catalog.list().map((a) => a.id),
965
1229
  });
966
1230
  /** The Keys and secrets page (docs/owner-secrets.md §6): core's own queries and ownerOnly tools. */
967
- const secretsDeps = () => ({ pool: deps.pool, registry: deps.registry, ctx: deps.ctx, now: deps.now });
1231
+ const secretsDeps = () => ({ pool: deps.pool, registry: deps.registry, ctx: deps.ctx, now: deps.now, ...(browser.logins ? { logins: browser.logins } : {}) });
968
1232
  /** Talking to buddi on the dashboard: the speech plugin's two tools, as the owner. */
969
1233
  const speechDeps = () => ({ pool: deps.pool, registry: deps.registry, ctx: deps.ctx, now: deps.now, agents: () => deps.catalog.list() });
970
1234
  /** The same, for the version and upgrade routes. */
@@ -1088,6 +1352,10 @@ export function createWebApp(deps) {
1088
1352
  now: deps.now,
1089
1353
  agents: () => deps.catalog.list(),
1090
1354
  plugins: () => deps.registry.manifests().map((m) => m.name),
1355
+ appsWithoutPlugin: () => {
1356
+ const apps = browser.status().routes?.find((route) => route.kind === 'apps');
1357
+ return apps !== undefined && apps.installed === false && apps.mode !== undefined && apps.mode !== 'off';
1358
+ },
1091
1359
  needsSetup: async () => {
1092
1360
  const out = [];
1093
1361
  for (const manifest of deps.registry.manifests()) {
@@ -1387,15 +1655,16 @@ export function createWebApp(deps) {
1387
1655
  const permissions = (await listToolPermissions(deps.pool, deps.ctx.ownerId)).filter(p => p.tool === 'host.exec' && (!agentId || p.agentId === agentId) && (!conversationId || !p.conversationId || p.conversationId === conversationId));
1388
1656
  return sendJson(res, 200, { permissions, runs: host.runs(deps.ctx.ownerId, agentId, conversationId) });
1389
1657
  }
1390
- case '/api/host/apps':
1391
- return sendJson(res, 200, { apps: await listInstalledApps() });
1392
- case '/api/host/browser-profiles':
1393
- return sendJson(res, 200, { profiles: await listBrowserProfiles(q.get('app') ?? '') });
1394
1658
  case '/api/extension':
1395
1659
  return sendJson(res, 200, await extension.view());
1396
1660
  case '/api/browser':
1397
1661
  return sendJson(res, 200, q.has('conversationId') && q.has('agentId')
1398
1662
  ? browser.status({ agentId: q.get('agentId'), conversationId: q.get('conversationId') }) : browser.status());
1663
+ case '/api/browser/telemetry': {
1664
+ // Stops by cause, cards and routes over the last week: how flakiness is seen to fall.
1665
+ const summary = browser.telemetrySummary?.(Number(q.get('days') ?? '7') || 7);
1666
+ return sendJson(res, 200, summary ?? { days: 7, tasks: 0, stops: 0, cards: 0, byCause: [], routes: {}, stopsPerTask: 0 });
1667
+ }
1399
1668
  case '/api/browser/screenshot': {
1400
1669
  const expectedSession = url.searchParams.get('sessionId');
1401
1670
  const current = browser.status(expectedSession !== null ? { sessionId: expectedSession } : undefined);
@@ -1434,10 +1703,14 @@ export function createWebApp(deps) {
1434
1703
  // from the page rather than implied by a number in a source file.
1435
1704
  scope: session.scope,
1436
1705
  // How this browser got in. `local` is the open loopback mint,
1437
- // `ticket` the five-minute ticket exchange, `tailscale` an identity the
1438
- // local daemon confirmed — and then the name to greet.
1439
- signedInThrough: session.via,
1440
- ...(session.via === 'tailscale' ? { tailscaleName: session.tailscaleName, tailscaleLogin: session.tailscaleLogin } : {}),
1706
+ // `ticket` the five-minute ticket exchange, or the provider that
1707
+ // verified it (`tailscale`, `cloudflare-access`) — and then the
1708
+ // name to greet.
1709
+ signedInThrough: session.via === 'provider' ? session.provider : session.via,
1710
+ ...(session.via === 'provider' ? { provider: session.provider, providerSubject: session.providerSubject } : {}),
1711
+ ...(session.via === 'provider' && session.provider === 'tailscale'
1712
+ ? { tailscaleName: session.providerDetail?.name ?? session.providerSubject, tailscaleLogin: session.providerSubject }
1713
+ : {}),
1441
1714
  expiresAt: session.expiresAt.toISOString(),
1442
1715
  // What this gateway is running. The page keeps it across an
1443
1716
  // upgrade so that "it came back" can be told from "it is still
@@ -1452,20 +1725,24 @@ export function createWebApp(deps) {
1452
1725
  * memory), and whether this very request came through the proxy —
1453
1726
  * which is what disables the switch on a tailnet browser.
1454
1727
  */
1455
- case '/api/tailscale': {
1456
- const stored = await readTailscaleSetting();
1457
- const daemon = await tailscaleSelfOf();
1458
- return sendJson(res, 200, {
1459
- enabled: stored.enabled,
1460
- login: stored.login,
1461
- available: daemon.available,
1462
- self: daemon.self,
1463
- proxied: session.via === 'tailscale' || proxiedThroughTailscale(req),
1464
- // What the owner has to run on this machine, with this
1465
- // installation's own two ports in it.
1466
- serveCommand: serveCommand(),
1467
- });
1468
- }
1728
+ // `/api/tailscale` stays one release as an alias (docs/api.md).
1729
+ case '/api/tailscale':
1730
+ case '/api/access/tailscale':
1731
+ return sendJson(res, 200, await tailscaleView(req, session));
1732
+ /*
1733
+ * "Sign in from elsewhere": every provider's row — its status in one
1734
+ * line — and whether this very request came through one, which makes
1735
+ * the whole block read-only.
1736
+ */
1737
+ case '/api/access':
1738
+ return sendJson(res, 200, await accessView(req, session));
1739
+ case '/api/access/cloudflare-access':
1740
+ return sendJson(res, 200, await cloudflareView(req, session));
1741
+ case '/api/access/cloudflare-access/setup':
1742
+ // The install line holds the tunnel's connector token: this machine only.
1743
+ if (session.via !== 'local')
1744
+ return sendJson(res, 403, { error: 'Change this from the computer buddi runs on.' });
1745
+ return sendJson(res, 200, await setupView());
1469
1746
  case '/api/overview':
1470
1747
  return sendJson(res, 200, {
1471
1748
  ...(await readOverview({
@@ -2225,7 +2502,7 @@ export function createWebApp(deps) {
2225
2502
  * everything else.
2226
2503
  */
2227
2504
  if (method === 'PUT') {
2228
- const puttable = ['/api/backups/schedule', '/api/backups/passphrase', '/api/version/check', '/api/tailscale', '/api/notifications/settings', '/api/notifications/focus'];
2505
+ const puttable = ['/api/backups/schedule', '/api/backups/passphrase', '/api/version/check', '/api/tailscale', '/api/access/tailscale', '/api/access/cloudflare-access', '/api/notifications/settings', '/api/notifications/focus'];
2229
2506
  if (!puttable.includes(path))
2230
2507
  return sendEmpty(res, 405);
2231
2508
  let put;
@@ -2235,7 +2512,20 @@ export function createWebApp(deps) {
2235
2512
  catch {
2236
2513
  return sendJson(res, 400, { error: 'request body must be JSON' });
2237
2514
  }
2238
- if (path === '/api/tailscale') {
2515
+ if (path === '/api/access/cloudflare-access') {
2516
+ // Who may widen access: only a browser on this machine, as for Tailscale below.
2517
+ if (session.via !== 'local') {
2518
+ return sendJson(res, 403, { error: 'Change this from the computer buddi runs on.' });
2519
+ }
2520
+ const checked = validateCloudflareInput(put);
2521
+ if (!checked.ok)
2522
+ return sendJson(res, 400, { error: checked.error });
2523
+ await applyCloudflareSetting(checked.value);
2524
+ // On Save buddi fetches the team's keys once and says whether it worked.
2525
+ const test = checked.value.enabled ? await jwks.refresh(checked.value.teamDomain) : null;
2526
+ return sendJson(res, 200, { ...(await cloudflareView(req, session)), ...(test ? { test } : {}) });
2527
+ }
2528
+ if (path === '/api/tailscale' || path === '/api/access/tailscale') {
2239
2529
  /*
2240
2530
  * Who may widen access: only a browser that is already on this
2241
2531
  * machine.
@@ -2272,20 +2562,11 @@ export function createWebApp(deps) {
2272
2562
  * in a browser on the far side of the tailnet.
2273
2563
  */
2274
2564
  const forgotten = new Set();
2275
- await sessions.forget((s) => { const drop = s.via === 'tailscale'; if (drop && s.id)
2565
+ await sessions.forget((s) => { const drop = s.via === 'provider' && s.provider === 'tailscale'; if (drop && s.id)
2276
2566
  forgotten.add(s.id); return drop; });
2277
2567
  // Including whichever of them had a hand on the owner's browser.
2278
2568
  hand.revoke((lease) => forgotten.has(lease), 'Tailscale access changed. Sign in again.');
2279
- const daemon = await tailscaleSelfOf();
2280
- const stored = toTailscaleSetting({ enabled, login });
2281
- return sendJson(res, 200, {
2282
- enabled: stored.enabled,
2283
- login: stored.login,
2284
- available: daemon.available,
2285
- self: daemon.self,
2286
- proxied: false,
2287
- serveCommand: serveCommand(),
2288
- });
2569
+ return sendJson(res, 200, await tailscaleView(req, session));
2289
2570
  }
2290
2571
  if (path === '/api/version/check')
2291
2572
  return reply(res, await versionCheckRoute(versionDeps(), 'PUT', put));
@@ -2299,6 +2580,155 @@ export function createWebApp(deps) {
2299
2580
  }
2300
2581
  if (method !== 'POST')
2301
2582
  return sendEmpty(res, 405);
2583
+ /*
2584
+ * "Test my setup": fetch the team's signing keys for the domain in the
2585
+ * form (saved or not) and say what came back. Nothing is stored. From
2586
+ * this machine only, like the setting itself: it makes this computer
2587
+ * fetch a URL the caller names part of.
2588
+ */
2589
+ /*
2590
+ * "Set it up for me": start a run (202, then poll GET …/setup), stop the
2591
+ * health wait, remove what buddi made, or forget the kept API token. From
2592
+ * this machine only, like the setting it fills in.
2593
+ */
2594
+ if (path === '/api/access/cloudflare-access/setup' || path === '/api/access/cloudflare-access/setup/stop' || path === '/api/access/cloudflare-access/setup/remove' || path === '/api/access/cloudflare-access/setup/forget-token') {
2595
+ if (session.via !== 'local')
2596
+ return sendJson(res, 403, { error: 'Change this from the computer buddi runs on.' });
2597
+ /*
2598
+ * Forget the token: drop the owner secret. Not while a run holds it.
2599
+ * Cloudflare still honours it until the owner revokes it there.
2600
+ */
2601
+ if (path.endsWith('/forget-token')) {
2602
+ const lease = claimSetupOperation('setup');
2603
+ if (!lease)
2604
+ return sendJson(res, 409, { error: setupBusySentence(setupOperation()) });
2605
+ try {
2606
+ try {
2607
+ await tokenStore().remove();
2608
+ }
2609
+ catch {
2610
+ return sendJson(res, 409, { error: 'The token could not be forgotten. Check that the vault is unlocked, then try again.' });
2611
+ }
2612
+ return sendJson(res, 200, await setupView());
2613
+ }
2614
+ finally {
2615
+ lease.release();
2616
+ }
2617
+ }
2618
+ /*
2619
+ * One operation at a time (cloudflare-setup's lock, claimed before the
2620
+ * first await and held to the end): a second setup or a removal while
2621
+ * one runs is a 409, and so is Stop while a removal runs.
2622
+ */
2623
+ if (path.endsWith('/stop')) {
2624
+ const going = setupOperation();
2625
+ if (going === 'remove')
2626
+ return sendJson(res, 409, { error: setupBusySentence(going) });
2627
+ const lease = going === null ? claimSetupOperation('setup') : null;
2628
+ try {
2629
+ setupRun?.abort.abort();
2630
+ await setupRun?.done;
2631
+ return sendJson(res, 200, await setupView());
2632
+ }
2633
+ finally {
2634
+ lease?.release();
2635
+ }
2636
+ }
2637
+ const lease = claimSetupOperation(path.endsWith('/remove') ? 'remove' : 'setup');
2638
+ if (!lease)
2639
+ return sendJson(res, 409, { error: setupBusySentence(setupOperation()) });
2640
+ let handedOff = false;
2641
+ try {
2642
+ const body = await readJsonBody(req).catch(() => ({}));
2643
+ const str = (v) => (typeof v === 'string' ? v.trim() : '');
2644
+ const tokens = tokenStore();
2645
+ const pasted = str(body.token);
2646
+ if (path.endsWith('/remove')) {
2647
+ const token = pasted || await tokens.use().catch(() => null);
2648
+ if (!token)
2649
+ return sendJson(res, 400, { error: 'Paste the Cloudflare API token again: buddi needs it to remove what it made.' });
2650
+ const run = {
2651
+ progress: { ...freshProgress(), state: 'removing', steps: [] }, abort: new AbortController(), done: Promise.resolve(),
2652
+ };
2653
+ setupRun = run;
2654
+ const progress = await removeCloudflareSetup({ ...setupDepsFor(token, lease), host: str(body.host) || undefined }, (p) => { run.progress = p; });
2655
+ run.progress = progress;
2656
+ // The token stays kept: the panel offers to forget it (…/setup/forget-token), and Set it up again reuses it.
2657
+ return sendJson(res, 200, await setupView());
2658
+ }
2659
+ const input = { host: str(body.host), email: str(body.email), zone: str(body.zone) || undefined, adopt: body.adopt === true };
2660
+ const invalid = checkSetupInput(input);
2661
+ if (invalid)
2662
+ return sendJson(res, 400, { error: invalid });
2663
+ if (pasted) {
2664
+ if (/\s/.test(pasted) || pasted.length < 20 || pasted.length > 400)
2665
+ return sendJson(res, 400, { error: 'That doesn’t look like a Cloudflare API token.' });
2666
+ try {
2667
+ await tokens.put(pasted);
2668
+ }
2669
+ catch {
2670
+ return sendJson(res, 409, { error: 'The token could not be kept in the vault. Check that the vault is unlocked, then try again.' });
2671
+ }
2672
+ }
2673
+ const token = pasted || await tokens.use().catch(() => null);
2674
+ if (!token)
2675
+ return sendJson(res, 400, { error: 'Paste a Cloudflare API token.' });
2676
+ const first = { ...freshProgress(input.host, input.email), state: 'running' };
2677
+ startSetup(lease, (onProgress, signal) => runCloudflareSetup(input, setupDepsFor(token, lease, signal), onProgress), first);
2678
+ handedOff = true;
2679
+ return sendJson(res, 202, await setupView());
2680
+ }
2681
+ finally {
2682
+ if (!handedOff)
2683
+ lease.release();
2684
+ }
2685
+ }
2686
+ /*
2687
+ * The setup form's domain choice: check the token and list the zones it
2688
+ * can see. The pasted token is only used for these two calls — keeping it
2689
+ * stays with setup — and never goes back out, not even in an error.
2690
+ */
2691
+ if (path === '/api/access/cloudflare-access/zones') {
2692
+ if (session.via !== 'local')
2693
+ return sendJson(res, 403, { error: 'Change this from the computer buddi runs on.' });
2694
+ const body = await readJsonBody(req).catch(() => ({}));
2695
+ const pasted = typeof body?.token === 'string' ? body.token.trim() : '';
2696
+ if (pasted && (/\s/.test(pasted) || pasted.length < 20 || pasted.length > 400))
2697
+ return sendJson(res, 400, { error: 'That doesn’t look like a Cloudflare API token.' });
2698
+ const token = pasted || await tokenStore().use().catch(() => null);
2699
+ if (!token)
2700
+ return sendJson(res, 400, { error: 'Paste a Cloudflare API token.' });
2701
+ const api = createCloudflareApi({ token, transport: deps.cloudflare?.api?.transport, baseUrl: deps.cloudflare?.api?.baseUrl });
2702
+ try {
2703
+ if (await api.verifyToken() === 'inactive')
2704
+ return sendJson(res, 400, { error: 'Cloudflare says this API token is not active. Check that it has not expired or been turned off.' });
2705
+ const zones = await api.zones();
2706
+ return sendJson(res, 200, { zones: zones.map((z) => ({ id: z.id, name: z.name })) });
2707
+ }
2708
+ catch (error) {
2709
+ if (!(error instanceof CloudflareApiError))
2710
+ throw error;
2711
+ const refused = error.status === 401 || error.permission !== undefined || (error.status >= 400 && error.status < 500 && error.status !== 429);
2712
+ return sendJson(res, refused ? 400 : 502, { error: error.message });
2713
+ }
2714
+ }
2715
+ if (path === '/api/access/cloudflare-access/test') {
2716
+ if (session.via !== 'local')
2717
+ return sendJson(res, 403, { error: 'Change this from the computer buddi runs on.' });
2718
+ const body = await readJsonBody(req).catch(() => ({}));
2719
+ const team = normalizeTeamDomain(typeof body.teamDomain === 'string' ? body.teamDomain : (await readCloudflareSetting()).teamDomain);
2720
+ if (!plausibleTeamDomain(team)) {
2721
+ return sendJson(res, 400, { error: 'That is not a Cloudflare team domain. It looks like yourteam.cloudflareaccess.com, under Zero Trust → Settings.' });
2722
+ }
2723
+ const result = await jwks.refresh(team);
2724
+ return sendJson(res, 200, {
2725
+ ...result,
2726
+ teamDomain: team,
2727
+ sentence: result.ok ? `${team} answered with ${result.keys} signing key${result.keys === 1 ? '' : 's'}.` : result.error,
2728
+ listening: ingress.port() !== null,
2729
+ ingressPort: ingress.port(),
2730
+ });
2731
+ }
2302
2732
  if (path === '/api/extension/pair') {
2303
2733
  // A six-digit code is worth guessing at scale, so a wrong one costs a
2304
2734
  // budget — this session's pairing budget, not the shared sign-in one.
@@ -2345,24 +2775,71 @@ export function createWebApp(deps) {
2345
2775
  return sendJson(res, 200, { ok: false, message: error instanceof Error ? error.message : String(error) });
2346
2776
  }
2347
2777
  }
2348
- if (path === '/api/browser/settings' || path === '/api/browser/permissions') {
2778
+ if (path === '/api/browser/settings') {
2349
2779
  const body = await readJsonBody(req);
2350
2780
  try {
2351
- if (path.endsWith('/settings')) {
2352
- if (!browser.configure)
2353
- return sendJson(res, 409, { error: 'This host does not support changing control modes.' });
2354
- return sendJson(res, 200, await browser.configure(body));
2355
- }
2356
- if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).some((key) => key !== 'prompt') || ('prompt' in body && typeof body.prompt !== 'boolean'))
2357
- return sendJson(res, 400, { error: 'Expected {prompt: boolean}' });
2358
- if (!browser.checkPermissions)
2359
- return sendJson(res, 409, { error: 'This host does not support native permission checks.' });
2360
- return sendJson(res, 200, await browser.checkPermissions(body.prompt === true));
2781
+ if (!browser.configure)
2782
+ return sendJson(res, 409, { error: 'This host does not support changing control modes.' });
2783
+ return sendJson(res, 200, await browser.configure(body));
2361
2784
  }
2362
2785
  catch (error) {
2363
2786
  return sendJson(res, 409, { error: error instanceof Error ? error.message : String(error) });
2364
2787
  }
2365
2788
  }
2789
+ /*
2790
+ * Pin one conversation to a route (the chip under the composer,
2791
+ * `/use browser:chrome`), or clear it with `auto`. A pin narrows; it never
2792
+ * allows a route the owner's switches forbid.
2793
+ */
2794
+ if (path === '/api/browser/pin') {
2795
+ const body = await readJsonBody(req);
2796
+ if (typeof body?.conversationId !== 'string' || typeof body.route !== 'string')
2797
+ return sendJson(res, 400, { error: 'Expected {conversationId: string, route: auto|own|chrome|apps}' });
2798
+ if (!browser.pin)
2799
+ return sendJson(res, 409, { error: 'This host cannot pin a route.' });
2800
+ try {
2801
+ return sendJson(res, 200, await browser.pin(body.conversationId, body.route));
2802
+ }
2803
+ catch (error) {
2804
+ return sendJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
2805
+ }
2806
+ }
2807
+ /*
2808
+ * A tap on a browser card (Look, Keep going, Take over, Use my Chrome,
2809
+ * Resume) from a page that draws its own buttons: the same as the owner
2810
+ * sending the label in the chat, without a message.
2811
+ */
2812
+ if (path === '/api/browser/card') {
2813
+ const body = await readJsonBody(req);
2814
+ if (typeof body?.conversationId !== 'string' || typeof body.answer !== 'string' || body.answer.length > 80)
2815
+ return sendJson(res, 400, { error: 'Expected {conversationId: string, answer: string}' });
2816
+ const answered = await browser.touch?.({ conversationId: body.conversationId, text: body.answer });
2817
+ return sendJson(res, 200, { ...(answered && 'answered' in answered ? answered : {}), status: browser.status({ conversationId: body.conversationId }) });
2818
+ }
2819
+ /*
2820
+ * "Save this login for amazon.com?" answered in the Page tab: Save, Not
2821
+ * now or Never for this site. The owner's session and CSRF, like every
2822
+ * write here; the body names the question, never a password — that is in
2823
+ * the browser host's keeper, which hands it to the owner-secret store on
2824
+ * Save and drops it otherwise.
2825
+ */
2826
+ if (path === '/api/browser/login') {
2827
+ const body = await readJsonBody(req);
2828
+ const decision = body?.decision;
2829
+ if (typeof body?.id !== 'string' || body.id.length > 80 || (decision !== 'save' && decision !== 'later' && decision !== 'never')) {
2830
+ return sendJson(res, 400, { error: 'Expected {id: string, decision: save|later|never}' });
2831
+ }
2832
+ if (!browser.logins)
2833
+ return sendJson(res, 409, { error: 'This host keeps no sign-ins.' });
2834
+ try {
2835
+ const answer = await browser.logins.decide(body.id, decision);
2836
+ return sendJson(res, 200, { outcome: answer.outcome, ...(answer.saved ? { saved: { name: answer.saved.name, site: answer.saved.site, username: answer.saved.username, savedAt: answer.saved.savedAt } } : {}) });
2837
+ }
2838
+ catch {
2839
+ // Never the error's own words: they are the store's, and the store had the password.
2840
+ return sendJson(res, 409, { error: 'buddi could not keep that login. Add it in Settings → Keys and secrets.' });
2841
+ }
2842
+ }
2366
2843
  const control = /^\/api\/browser\/(stop|takeover|resume|release)$/.exec(path);
2367
2844
  if (control) {
2368
2845
  const body = await readJsonBody(req);
@@ -2377,10 +2854,14 @@ export function createWebApp(deps) {
2377
2854
  // frozen picture.
2378
2855
  if (action !== 'takeover')
2379
2856
  await hand.close(action === 'stop' ? undefined : sessionId, action === 'resume' ? 'You gave the screen back.' : 'That session was released.');
2380
- const status = await browser.control(action, sessionId);
2857
+ // Stop agents' browsing expires (an hour by default); `forever` is "until I say".
2858
+ const status = await browser.control(action, sessionId, action === 'stop' && body?.forever === true ? { forever: true } : undefined);
2381
2859
  // Whether this screen can be driven from here at all. The dashboard
2382
2860
  // shows the mode's own sentence when it cannot.
2383
2861
  const taken = sessionId ?? status.session?.id;
2862
+ // A page in the owner's Chrome is taken over where it is: its tab came to the front there, so no frame will come.
2863
+ if (action === 'takeover' && status.held)
2864
+ return sendJson(res, 200, { ...status, hand: false });
2384
2865
  const offer = action === 'takeover' && taken ? browser.hand?.({ sessionId: taken }) : undefined;
2385
2866
  /*
2386
2867
  * "Your browser" with Chrome closed: the take-over itself succeeds (the
@@ -2388,7 +2869,7 @@ export function createWebApp(deps) {
2388
2869
  * as that, with a reason the dashboard offers its ways out on, rather
2389
2870
  * than the mode's "use this conversation's host tab".
2390
2871
  */
2391
- if (offer && offer.supported && !offer.hand && status.mode === 'extension' && !extension.connected()) {
2872
+ if (offer && offer.supported && !offer.hand && (status.route === 'chrome' || status.mode === 'extension') && !extension.connected()) {
2392
2873
  return sendJson(res, 200, { ...status, hand: false, handReason: 'browser-offline', handMessage: 'Your browser isn’t connected.' });
2393
2874
  }
2394
2875
  return sendJson(res, 200, offer
@@ -3724,6 +4205,10 @@ export async function startWebServer(deps) {
3724
4205
  */
3725
4206
  if (previewPort !== null)
3726
4207
  publishPreviewPort(deps.env ?? process.env, previewPort);
4208
+ // The ingress listener, after the previews have taken their port: bound
4209
+ // now when Cloudflare Access is on, and from then on as the setting changes.
4210
+ const ingress = ingressOf(server);
4211
+ await ingress.sync();
3727
4212
  /*
3728
4213
  * An approval nobody decides expires, and a delegation waiting on one is
3729
4214
  * handed the expiry as its failure: the approval's own lifetime is the
@@ -3741,6 +4226,7 @@ export async function startWebServer(deps) {
3741
4226
  server,
3742
4227
  port,
3743
4228
  previewPort,
4229
+ ingress,
3744
4230
  url: webUrl({ host: deps.config.host, port }),
3745
4231
  chat: webChatOf(server),
3746
4232
  close: async () => {
@@ -3752,6 +4238,7 @@ export async function startWebServer(deps) {
3752
4238
  // still held, and the next thing to want it — the next test, the
3753
4239
  // gateway coming back up — finds it taken.
3754
4240
  await Promise.all([
4241
+ ingress.close(),
3755
4242
  new Promise((resolve) => {
3756
4243
  if (!previews || previewPort === null)
3757
4244
  return resolve();