@songsid/agend 2.1.12 → 2.2.0-alpha.1

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 (358) hide show
  1. package/README.md +2 -2
  2. package/README.zh-TW.md +2 -2
  3. package/dist/auth/one-time-code.d.ts +19 -0
  4. package/dist/auth/one-time-code.js +49 -0
  5. package/dist/auth/one-time-code.js.map +1 -0
  6. package/dist/auth-api.d.ts +52 -0
  7. package/dist/auth-api.js +346 -0
  8. package/dist/auth-api.js.map +1 -0
  9. package/dist/backend/claude-code.d.ts +4 -0
  10. package/dist/backend/claude-code.js +97 -0
  11. package/dist/backend/claude-code.js.map +1 -1
  12. package/dist/backend/codex.js +59 -30
  13. package/dist/backend/codex.js.map +1 -1
  14. package/dist/backend/kiro-agent.d.ts +66 -0
  15. package/dist/backend/kiro-agent.js +302 -0
  16. package/dist/backend/kiro-agent.js.map +1 -0
  17. package/dist/backend/kiro-identity.d.ts +80 -0
  18. package/dist/backend/kiro-identity.js +537 -0
  19. package/dist/backend/kiro-identity.js.map +1 -0
  20. package/dist/backend/kiro-v2-store-worker.d.ts +1 -0
  21. package/dist/backend/kiro-v2-store-worker.js +87 -0
  22. package/dist/backend/kiro-v2-store-worker.js.map +1 -0
  23. package/dist/backend/kiro-v2-store.d.ts +54 -0
  24. package/dist/backend/kiro-v2-store.js +131 -0
  25. package/dist/backend/kiro-v2-store.js.map +1 -0
  26. package/dist/backend/kiro-v3-identity.d.ts +5 -0
  27. package/dist/backend/kiro-v3-identity.js +22 -0
  28. package/dist/backend/kiro-v3-identity.js.map +1 -1
  29. package/dist/backend/kiro.d.ts +83 -7
  30. package/dist/backend/kiro.js +418 -91
  31. package/dist/backend/kiro.js.map +1 -1
  32. package/dist/backend/types.d.ts +45 -3
  33. package/dist/backend/types.js.map +1 -1
  34. package/dist/channel/adapters/discord.d.ts +1 -0
  35. package/dist/channel/adapters/discord.js +29 -10
  36. package/dist/channel/adapters/discord.js.map +1 -1
  37. package/dist/channel/adapters/telegram.d.ts +1 -0
  38. package/dist/channel/adapters/telegram.js +13 -1
  39. package/dist/channel/adapters/telegram.js.map +1 -1
  40. package/dist/channel/types.d.ts +9 -1
  41. package/dist/classic-channel-manager.d.ts +18 -10
  42. package/dist/classic-channel-manager.js +45 -18
  43. package/dist/classic-channel-manager.js.map +1 -1
  44. package/dist/cli.js +225 -127
  45. package/dist/cli.js.map +1 -1
  46. package/dist/command-table.d.ts +5 -3
  47. package/dist/command-table.js +29 -27
  48. package/dist/command-table.js.map +1 -1
  49. package/dist/config-validator.js +46 -0
  50. package/dist/config-validator.js.map +1 -1
  51. package/dist/config.js +12 -0
  52. package/dist/config.js.map +1 -1
  53. package/dist/connection-secrets.d.ts +6 -0
  54. package/dist/connection-secrets.js +4 -0
  55. package/dist/connection-secrets.js.map +1 -1
  56. package/dist/daemon.d.ts +89 -4
  57. package/dist/daemon.js +661 -77
  58. package/dist/daemon.js.map +1 -1
  59. package/dist/darwin-memory.d.ts +6 -1
  60. package/dist/darwin-memory.js +17 -6
  61. package/dist/darwin-memory.js.map +1 -1
  62. package/dist/delivery-consumed-watch.d.ts +85 -0
  63. package/dist/delivery-consumed-watch.js +244 -0
  64. package/dist/delivery-consumed-watch.js.map +1 -0
  65. package/dist/delivery-outbox.d.ts +61 -0
  66. package/dist/delivery-outbox.js +120 -2
  67. package/dist/delivery-outbox.js.map +1 -1
  68. package/dist/delivery-reconciliation.d.ts +14 -5
  69. package/dist/delivery-reconciliation.js +27 -4
  70. package/dist/delivery-reconciliation.js.map +1 -1
  71. package/dist/delivery-reconciliation.test.js +35 -1
  72. package/dist/delivery-reconciliation.test.js.map +1 -1
  73. package/dist/event-loop-watch.d.ts +28 -0
  74. package/dist/event-loop-watch.js +75 -3
  75. package/dist/event-loop-watch.js.map +1 -1
  76. package/dist/fleet-context.d.ts +22 -2
  77. package/dist/fleet-level-config.d.ts +2 -2
  78. package/dist/fleet-level-config.js +6 -1
  79. package/dist/fleet-level-config.js.map +1 -1
  80. package/dist/fleet-manager.d.ts +293 -12
  81. package/dist/fleet-manager.js +2364 -449
  82. package/dist/fleet-manager.js.map +1 -1
  83. package/dist/host-memory.d.ts +4 -0
  84. package/dist/host-memory.js.map +1 -1
  85. package/dist/instance-config-impact.js +5 -0
  86. package/dist/instance-config-impact.js.map +1 -1
  87. package/dist/instance-lifecycle.d.ts +10 -5
  88. package/dist/instance-lifecycle.js +162 -53
  89. package/dist/instance-lifecycle.js.map +1 -1
  90. package/dist/locale.js +150 -16
  91. package/dist/locale.js.map +1 -1
  92. package/dist/login-controller.d.ts +2 -0
  93. package/dist/login-controller.js +17 -5
  94. package/dist/login-controller.js.map +1 -1
  95. package/dist/memory-pressure.d.ts +7 -8
  96. package/dist/memory-pressure.js +76 -15
  97. package/dist/memory-pressure.js.map +1 -1
  98. package/dist/monotonic-budget.d.ts +2 -0
  99. package/dist/monotonic-budget.js +28 -0
  100. package/dist/monotonic-budget.js.map +1 -0
  101. package/dist/needs-you-hub.d.ts +114 -0
  102. package/dist/needs-you-hub.js +370 -0
  103. package/dist/needs-you-hub.js.map +1 -0
  104. package/dist/needs-you-live.d.ts +116 -0
  105. package/dist/needs-you-live.js +228 -0
  106. package/dist/needs-you-live.js.map +1 -0
  107. package/dist/needs-you.d.ts +132 -0
  108. package/dist/needs-you.js +164 -0
  109. package/dist/needs-you.js.map +1 -0
  110. package/dist/node-version-guard.d.ts +30 -0
  111. package/dist/node-version-guard.js +50 -0
  112. package/dist/node-version-guard.js.map +1 -0
  113. package/dist/operator-control-paths.d.ts +4 -0
  114. package/dist/operator-control-paths.js +24 -0
  115. package/dist/operator-control-paths.js.map +1 -0
  116. package/dist/outbound-handlers.d.ts +2 -0
  117. package/dist/outbound-handlers.js +9 -2
  118. package/dist/outbound-handlers.js.map +1 -1
  119. package/dist/outbound-schemas.d.ts +5 -5
  120. package/dist/profile-control.js +4 -20
  121. package/dist/profile-control.js.map +1 -1
  122. package/dist/public-web-gateway.d.ts +18 -0
  123. package/dist/public-web-gateway.js +141 -0
  124. package/dist/public-web-gateway.js.map +1 -0
  125. package/dist/public-web-link.d.ts +53 -0
  126. package/dist/public-web-link.js +175 -0
  127. package/dist/public-web-link.js.map +1 -0
  128. package/dist/quickstart-api.d.ts +8 -1
  129. package/dist/quickstart-api.js +86 -48
  130. package/dist/quickstart-api.js.map +1 -1
  131. package/dist/quickstart.d.ts +1 -0
  132. package/dist/quickstart.js +6 -2
  133. package/dist/quickstart.js.map +1 -1
  134. package/dist/scheduler/db.d.ts +7 -1
  135. package/dist/scheduler/db.js +38 -0
  136. package/dist/scheduler/db.js.map +1 -1
  137. package/dist/scheduler/index.d.ts +1 -1
  138. package/dist/scheduler/retry-label.d.ts +11 -0
  139. package/dist/scheduler/retry-label.js +24 -0
  140. package/dist/scheduler/retry-label.js.map +1 -0
  141. package/dist/scheduler/scheduler.d.ts +55 -2
  142. package/dist/scheduler/scheduler.js +220 -8
  143. package/dist/scheduler/scheduler.js.map +1 -1
  144. package/dist/scheduler/types.d.ts +22 -0
  145. package/dist/scheduler/types.js.map +1 -1
  146. package/dist/secret-store.d.ts +5 -0
  147. package/dist/secret-store.js +20 -0
  148. package/dist/secret-store.js.map +1 -1
  149. package/dist/service-activation.d.ts +110 -0
  150. package/dist/service-activation.js +302 -0
  151. package/dist/service-activation.js.map +1 -0
  152. package/dist/service-installer.d.ts +7 -0
  153. package/dist/service-installer.js +7 -4
  154. package/dist/service-installer.js.map +1 -1
  155. package/dist/service-restart-selection.d.ts +6 -5
  156. package/dist/service-restart-selection.js +6 -5
  157. package/dist/service-restart-selection.js.map +1 -1
  158. package/dist/settings-api.d.ts +10 -2
  159. package/dist/settings-api.js +175 -72
  160. package/dist/settings-api.js.map +1 -1
  161. package/dist/settings-baseline.d.ts +20 -0
  162. package/dist/settings-baseline.js +104 -0
  163. package/dist/settings-baseline.js.map +1 -0
  164. package/dist/settings-change.d.ts +21 -0
  165. package/dist/settings-change.js +133 -0
  166. package/dist/settings-change.js.map +1 -0
  167. package/dist/settings-confirmation.d.ts +105 -0
  168. package/dist/settings-confirmation.js +232 -0
  169. package/dist/settings-confirmation.js.map +1 -0
  170. package/dist/settings-control.d.ts +41 -0
  171. package/dist/settings-control.js +294 -0
  172. package/dist/settings-control.js.map +1 -0
  173. package/dist/settings-effect.d.ts +24 -0
  174. package/dist/settings-effect.js +169 -0
  175. package/dist/settings-effect.js.map +1 -0
  176. package/dist/settings-http-confirmation.d.ts +29 -0
  177. package/dist/settings-http-confirmation.js +123 -0
  178. package/dist/settings-http-confirmation.js.map +1 -0
  179. package/dist/settings-instance-patch.d.ts +4 -0
  180. package/dist/settings-instance-patch.js +26 -0
  181. package/dist/settings-instance-patch.js.map +1 -0
  182. package/dist/settings-request-capability.d.ts +16 -0
  183. package/dist/settings-request-capability.js +32 -0
  184. package/dist/settings-request-capability.js.map +1 -0
  185. package/dist/settings-transaction.d.ts +65 -0
  186. package/dist/settings-transaction.js +223 -0
  187. package/dist/settings-transaction.js.map +1 -0
  188. package/dist/setup-auth.d.ts +10 -18
  189. package/dist/setup-auth.js +14 -38
  190. package/dist/setup-auth.js.map +1 -1
  191. package/dist/setup-form.d.ts +1 -1
  192. package/dist/setup-form.js +75 -18
  193. package/dist/setup-form.js.map +1 -1
  194. package/dist/setup-guide.d.ts +13 -0
  195. package/dist/setup-guide.js +25 -0
  196. package/dist/setup-guide.js.map +1 -0
  197. package/dist/setup-host.d.ts +16 -0
  198. package/dist/setup-host.js +197 -32
  199. package/dist/setup-host.js.map +1 -1
  200. package/dist/setup-wizard.js +3 -0
  201. package/dist/setup-wizard.js.map +1 -1
  202. package/dist/slash-authz.d.ts +17 -3
  203. package/dist/slash-authz.js +5 -0
  204. package/dist/slash-authz.js.map +1 -1
  205. package/dist/spawn-gate.js +3 -3
  206. package/dist/spawn-gate.js.map +1 -1
  207. package/dist/statusline-watcher.d.ts +5 -0
  208. package/dist/statusline-watcher.js +2 -0
  209. package/dist/statusline-watcher.js.map +1 -1
  210. package/dist/steer-capability.d.ts +6 -0
  211. package/dist/steer-capability.js +8 -0
  212. package/dist/steer-capability.js.map +1 -1
  213. package/dist/sync-work-attribution.d.ts +14 -0
  214. package/dist/sync-work-attribution.js +43 -8
  215. package/dist/sync-work-attribution.js.map +1 -1
  216. package/dist/tips.js +10 -5
  217. package/dist/tips.js.map +1 -1
  218. package/dist/tmux-control.d.ts +35 -5
  219. package/dist/tmux-control.js +291 -57
  220. package/dist/tmux-control.js.map +1 -1
  221. package/dist/tmux-manager.d.ts +20 -6
  222. package/dist/tmux-manager.js +90 -47
  223. package/dist/tmux-manager.js.map +1 -1
  224. package/dist/tmux-read.d.ts +59 -0
  225. package/dist/tmux-read.js +213 -0
  226. package/dist/tmux-read.js.map +1 -0
  227. package/dist/topic-commands.d.ts +21 -10
  228. package/dist/topic-commands.js +191 -58
  229. package/dist/topic-commands.js.map +1 -1
  230. package/dist/transcript-delta-reader.js +2 -1
  231. package/dist/transcript-delta-reader.js.map +1 -1
  232. package/dist/tunnel/cloudflared-install.d.ts +2 -0
  233. package/dist/tunnel/cloudflared-install.js +1 -1
  234. package/dist/tunnel/cloudflared-install.js.map +1 -1
  235. package/dist/tunnel/purpose-lane.d.ts +14 -0
  236. package/dist/tunnel/purpose-lane.js +43 -0
  237. package/dist/tunnel/purpose-lane.js.map +1 -0
  238. package/dist/turn-reply-guard.d.ts +2 -0
  239. package/dist/turn-reply-guard.js +10 -1
  240. package/dist/turn-reply-guard.js.map +1 -1
  241. package/dist/types.d.ts +33 -0
  242. package/dist/ui/app.html +18 -0
  243. package/dist/ui/chat-render.js +478 -0
  244. package/dist/ui/chat-store.js +289 -0
  245. package/dist/ui/chat-strings.js +69 -0
  246. package/dist/ui/chat-thread.js +209 -0
  247. package/dist/ui/chat-tour.js +116 -0
  248. package/dist/ui/fleet-org.js +142 -0
  249. package/dist/ui/panel-chat.js +418 -0
  250. package/dist/ui/panel-fleet.js +413 -0
  251. package/dist/ui/panel-needs.js +202 -0
  252. package/dist/ui/panel-settings.js +750 -0
  253. package/dist/ui/preview.js +266 -0
  254. package/dist/ui/settings-apply.js +239 -0
  255. package/dist/ui/settings-confirm.js +186 -0
  256. package/dist/ui/settings-dialogs.js +546 -0
  257. package/dist/ui/settings-model.js +277 -0
  258. package/dist/ui/settings-strings.js +652 -0
  259. package/dist/ui/settings-wizard.js +140 -0
  260. package/dist/ui/shared/agend-auth.js +93 -0
  261. package/dist/ui/shared/app-ctx.js +80 -0
  262. package/dist/ui/shared/app-html.js +9 -0
  263. package/dist/ui/shared/app-i18n.js +95 -0
  264. package/dist/ui/shared/app-nav.js +73 -0
  265. package/dist/ui/shared/app-needs.js +114 -0
  266. package/dist/ui/shared/app-route.js +73 -0
  267. package/dist/ui/shared/app-session.js +66 -0
  268. package/dist/ui/shared/app-shell.js +344 -0
  269. package/dist/ui/shared/app-store.js +51 -0
  270. package/dist/ui/shared/app-stream.js +201 -0
  271. package/dist/ui/shared/app.css +676 -0
  272. package/dist/ui/shared/app.js +88 -0
  273. package/dist/ui/shared/fonts/OFL.txt +92 -0
  274. package/dist/ui/shared/fonts/README.md +21 -0
  275. package/dist/ui/shared/fonts/inter.woff2 +0 -0
  276. package/dist/ui/shared/htm.module.js +1 -0
  277. package/dist/ui/shared/panel-view.js +655 -0
  278. package/dist/ui/shared/preact-hooks.module.js +2 -0
  279. package/dist/ui/shared/preact.module.js +2 -0
  280. package/dist/ui/shared/signin.js +130 -0
  281. package/dist/ui/shared/theme.js +29 -0
  282. package/dist/ui/shared/tokens.css +153 -0
  283. package/dist/ui/shared/ui-confirm.js +43 -0
  284. package/dist/ui/shared/ui-dialog.js +45 -0
  285. package/dist/ui/shared/ui-icons.js +61 -0
  286. package/dist/ui/shared/ui-menu.js +39 -0
  287. package/dist/ui/shared/ui-states.js +20 -0
  288. package/dist/ui/shared/ui-toast.js +18 -0
  289. package/dist/ui/shared/vendor/htm.LICENSE +202 -0
  290. package/dist/ui/shared/vendor/preact.LICENSE +21 -0
  291. package/dist/ui/shared/vendor/vendor.json +39 -0
  292. package/dist/ui/shared/view-strings.js +309 -0
  293. package/dist/ui/signin.html +51 -0
  294. package/dist/update-check.d.ts +6 -2
  295. package/dist/update-check.js +12 -3
  296. package/dist/update-check.js.map +1 -1
  297. package/dist/update-install.d.ts +71 -0
  298. package/dist/update-install.js +162 -0
  299. package/dist/update-install.js.map +1 -0
  300. package/dist/upgrade-notices.d.ts +17 -0
  301. package/dist/upgrade-notices.js +93 -0
  302. package/dist/upgrade-notices.js.map +1 -0
  303. package/dist/usage/statusline-usage.d.ts +2 -0
  304. package/dist/usage/statusline-usage.js +14 -10
  305. package/dist/usage/statusline-usage.js.map +1 -1
  306. package/dist/view-api.d.ts +6 -1
  307. package/dist/view-api.js +62 -51
  308. package/dist/view-api.js.map +1 -1
  309. package/dist/web-api.d.ts +71 -14
  310. package/dist/web-api.js +492 -77
  311. package/dist/web-api.js.map +1 -1
  312. package/dist/web-auth.d.ts +71 -27
  313. package/dist/web-auth.js +169 -69
  314. package/dist/web-auth.js.map +1 -1
  315. package/dist/web-body.d.ts +3 -0
  316. package/dist/web-body.js +55 -0
  317. package/dist/web-body.js.map +1 -0
  318. package/dist/web-channel-echo.d.ts +9 -0
  319. package/dist/web-channel-echo.js +55 -0
  320. package/dist/web-channel-echo.js.map +1 -0
  321. package/dist/web-chat-history.d.ts +125 -0
  322. package/dist/web-chat-history.js +184 -0
  323. package/dist/web-chat-history.js.map +1 -0
  324. package/dist/web-continuation.d.ts +7 -0
  325. package/dist/web-continuation.js +22 -0
  326. package/dist/web-continuation.js.map +1 -0
  327. package/dist/web-host-guard.d.ts +34 -0
  328. package/dist/web-host-guard.js +57 -1
  329. package/dist/web-host-guard.js.map +1 -1
  330. package/dist/web-login.d.ts +72 -0
  331. package/dist/web-login.js +132 -0
  332. package/dist/web-login.js.map +1 -0
  333. package/dist/web-org.d.ts +55 -0
  334. package/dist/web-org.js +67 -0
  335. package/dist/web-org.js.map +1 -0
  336. package/dist/web-preview.d.ts +77 -0
  337. package/dist/web-preview.js +240 -0
  338. package/dist/web-preview.js.map +1 -0
  339. package/dist/web-request-context.d.ts +10 -0
  340. package/dist/web-request-context.js +13 -0
  341. package/dist/web-request-context.js.map +1 -0
  342. package/dist/web-session.d.ts +167 -0
  343. package/dist/web-session.js +389 -0
  344. package/dist/web-session.js.map +1 -0
  345. package/dist/web-shell-routes.d.ts +59 -0
  346. package/dist/web-shell-routes.js +96 -0
  347. package/dist/web-shell-routes.js.map +1 -0
  348. package/dist/web-upload.d.ts +183 -0
  349. package/dist/web-upload.js +468 -0
  350. package/dist/web-upload.js.map +1 -0
  351. package/dist/website-tips-renderer.d.ts +2 -2
  352. package/dist/website-tips-renderer.js +3 -3
  353. package/dist/website-tips-renderer.js.map +1 -1
  354. package/package.json +8 -5
  355. package/scripts/preinstall-guard.cjs +36 -0
  356. package/dist/ui/dashboard.html +0 -774
  357. package/dist/ui/settings.html +0 -2165
  358. package/dist/ui/view.html +0 -1139
@@ -1,3 +1,4 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  export const WEB_HOST_REJECTED_MESSAGE = "Host not allowed — if you reach AgEnD through a reverse proxy or another name, add it to web.allowed_hosts in fleet.yaml";
2
3
  /** Names a browser on this machine uses for a loopback listener. */
3
4
  export const LOOPBACK_HOST_NAMES = ["localhost", "127.0.0.1", "[::1]"];
@@ -30,6 +31,34 @@ export function hostnameOf(value) {
30
31
  // either direction.
31
32
  return match[1].replace(/\.$/, "") || null;
32
33
  }
34
+ /**
35
+ * What a page served here may load and where it may send anything.
36
+ *
37
+ * `script-src` is this origin only — no `'unsafe-inline'` (#1268): a panel's own inline
38
+ * script runs because the panel is served with a per-response nonce for it
39
+ * (sendPanelHtml), and no panel has an inline `on*=` handler. Injected markup can
40
+ * therefore not run script. `connect-src`, `img-src` and `form-action` are this
41
+ * origin, so nothing read can be posted elsewhere, and `base-uri`, `object-src` and
42
+ * `frame-ancestors` are closed. Styles are the same (#1300): `style-src 'self'`, and a
43
+ * panel's own `<style>` block carries the response's nonce. No panel has a `style="…"`
44
+ * attribute — what a script colours or sizes at run time goes through the style object
45
+ * (CSSOM), which the policy does not govern — so injected markup cannot add inline styles of its
46
+ * own (it can still carry the page's existing class names).
47
+ *
48
+ * Fonts, scripts and styles are all served from here; nothing loads from a CDN.
49
+ */
50
+ export const WEB_CONTENT_SECURITY_POLICY = [
51
+ "default-src 'self'",
52
+ "script-src 'self'",
53
+ "style-src 'self'",
54
+ "img-src 'self' data: blob:",
55
+ "font-src 'self' data:",
56
+ "connect-src 'self'",
57
+ "object-src 'none'",
58
+ "base-uri 'none'",
59
+ "form-action 'self'",
60
+ "frame-ancestors 'none'",
61
+ ].join("; ");
33
62
  /** Loopback names, the `hostname:` handed out in /dashboard links, and `web.allowed_hosts`. */
34
63
  export function allowedHostNames(config) {
35
64
  const names = new Set(LOOPBACK_HOST_NAMES);
@@ -67,9 +96,36 @@ export function applyWebSecurityHeaders(res) {
67
96
  // These pages have buttons that restart instances and change configuration;
68
97
  // a page that can be framed can be clicked through.
69
98
  res.setHeader("X-Frame-Options", "DENY");
70
- res.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
99
+ res.setHeader("Content-Security-Policy", WEB_CONTENT_SECURITY_POLICY);
71
100
  // Authorization now depends on a cookie, so a shared cache (a tunnel, a
72
101
  // corporate proxy) must not keep or replay any of it.
73
102
  res.setHeader("Cache-Control", "no-store");
74
103
  }
104
+ /**
105
+ * The panel policy for one response: WEB_CONTENT_SECURITY_POLICY with this response's nonce for scripts and styles,
106
+ * and — for /ui only, when a preview origin was chosen for this load (#1306) — `frame-src <preview origin>/frame`:
107
+ * path-scoped, so nothing else of that origin can be framed or navigated to. Without it, frames fall back to
108
+ * default-src 'self'.
109
+ */
110
+ export function panelContentSecurityPolicy(nonce, opts = {}) {
111
+ const policy = WEB_CONTENT_SECURITY_POLICY
112
+ .replace("script-src 'self'", `script-src 'self' 'nonce-${nonce}'`)
113
+ .replace("style-src 'self'", `style-src 'self' 'nonce-${nonce}'`);
114
+ return opts.frameSrc ? `${policy}; frame-src ${opts.frameSrc}` : policy;
115
+ }
116
+ /**
117
+ * Send a panel page (/ui, /view, /settings, the sign-in page). Its own inline `<script>` and `<style>` blocks get a
118
+ * fresh nonce, and the response's CSP names that nonce and nothing else inline (#1268, #1300): a script or a style
119
+ * that was not in the file as served — anything injected into the page — has no nonce and does not apply.
120
+ */
121
+ export function sendPanelHtml(res, html, status = 200, headers = {}, csp = {}) {
122
+ const nonce = randomBytes(18).toString("base64");
123
+ res.setHeader("Content-Security-Policy", panelContentSecurityPolicy(nonce, csp));
124
+ res.writeHead(status, { "Content-Type": "text/html; charset=utf-8", ...headers });
125
+ // Exactly the page's own bare `<script>` / `<style>` tags, as written in our files, get the nonce: a plain string
126
+ // match, not a tag filter. Any other spelling (`<SCRIPT>`, `<script src=…>`, `<script type=module>`) is left as is
127
+ // and so has no nonce: inline it is blocked, `src=` scripts load by 'self'. Widening the match would hand the
128
+ // nonce to more spellings; nothing here sanitises input.
129
+ res.end(html.split("<script>").join(`<script nonce="${nonce}">`).split("<style>").join(`<style nonce="${nonce}">`));
130
+ }
75
131
  //# sourceMappingURL=web-host-guard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"web-host-guard.js","sourceRoot":"","sources":["../src/web-host-guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"web-host-guard.js","sourceRoot":"","sources":["../src/web-host-guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import type { SessionTier } from "./web-session.js";
2
+ export declare const LOGIN_CODE_TTL_MS: number;
3
+ /** Same reasoning as setup: eight characters on a phone get mistyped. */
4
+ export declare const MAX_LOGIN_CODE_ATTEMPTS = 5;
5
+ export declare const LOGIN_BREAKER_WINDOW_MS: number;
6
+ export declare const LOGIN_BREAKER_THRESHOLD = 20;
7
+ export declare const LOGIN_BREAKER_PAUSE_MS: number;
8
+ export interface LoginCodeOwner {
9
+ /** Server-owned live adapter identity; never accepted from HTTP. */
10
+ readonly binding?: object;
11
+ readonly adapterId: string;
12
+ readonly userId: string;
13
+ readonly chatId: string;
14
+ readonly threadId?: string;
15
+ }
16
+ export interface IssuedLoginCode {
17
+ readonly issuanceId: string;
18
+ /** As typed: `ABCD-EFGH`. */
19
+ readonly display: string;
20
+ readonly expiresAt: number;
21
+ readonly tier: SessionTier;
22
+ }
23
+ export type LoginRedeemResult = {
24
+ readonly kind: "ok";
25
+ readonly tier: SessionTier;
26
+ readonly owner?: LoginCodeOwner;
27
+ }
28
+ /** Wrong, expired, spent, or none issued — deliberately indistinguishable. */
29
+ | {
30
+ readonly kind: "invalid";
31
+ }
32
+ /** The breaker is open. Even a correct code is refused until it closes. */
33
+ | {
34
+ readonly kind: "paused";
35
+ readonly retryAfterMs: number;
36
+ };
37
+ export interface WebLoginCodesOptions {
38
+ readonly now?: () => number;
39
+ /** For tests that need a known code. */
40
+ readonly generate?: () => string;
41
+ readonly onEvent?: (event: "issued" | "spent" | "burned" | "breaker-open") => void;
42
+ }
43
+ export declare class WebLoginCodes {
44
+ private outstanding;
45
+ private failures;
46
+ private pausedUntil;
47
+ private readonly now;
48
+ private readonly generate;
49
+ private readonly onEvent;
50
+ constructor(opts?: WebLoginCodesOptions);
51
+ /**
52
+ * Issue a code, replacing any outstanding one. Only ever called from a channel the operator controls.
53
+ * `epoch` is the current web token's (`tokenEpoch`): rotating the token — from this process or from
54
+ * `agend web-token rotate` in another — withdraws an unused code, because redemption re-checks it.
55
+ */
56
+ issue(opts: {
57
+ tier?: SessionTier;
58
+ epoch: string;
59
+ audience?: string;
60
+ owner?: LoginCodeOwner;
61
+ }): IssuedLoginCode;
62
+ /** Whether a redemption right now would be compared against anything. */
63
+ get hasOutstandingCode(): boolean;
64
+ /** Withdraw the outstanding code without spending it (`/dashboard revoke`). */
65
+ revoke(): void;
66
+ /** `currentEpoch` is the web token's epoch NOW: a code issued under another token is gone. */
67
+ revokeIfCurrent(issuanceId: string): void;
68
+ revokeAudience(audience: string): void;
69
+ redeem(input: string, currentEpoch: string, audience?: string): LoginRedeemResult;
70
+ private activeCode;
71
+ private recordFailure;
72
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The dashboard's login codes: who may redeem one, how often they may be wrong,
3
+ * and what happens when they are wrong too often.
4
+ *
5
+ * Deliberately not `SetupCredentials`. That class belongs to a ten-minute
6
+ * escape hatch whose answer to five wrong tries is to destroy itself; a fleet
7
+ * that runs for months must not be closable by whoever finds its address. So the
8
+ * rules here are different, and each is a property worth testing directly:
9
+ *
10
+ * - **No outstanding code, nothing to guess.** A redemption while no code has
11
+ * been issued gets the same refusal as a wrong code and counts against
12
+ * nothing. The door is only a door in the few minutes after the operator asked
13
+ * for a code on a channel only they can read.
14
+ * - **A code dies with its fifth wrong try — the code, not the fleet.** The
15
+ * cost to a stranger who guesses is that the operator asks for another.
16
+ * - **A breaker for the rest.** Enough wrong tries across codes pauses
17
+ * redemption for a while, correct codes included, so a patient guesser cannot
18
+ * turn five-per-code into an unbounded stream. Sessions already signed in are
19
+ * not affected: this object knows nothing about them.
20
+ * - **One code at a time, single use.** Issuing replaces the outstanding code;
21
+ * redeeming it spends it.
22
+ *
23
+ * The code is only an exchange key. What a successful redemption yields is a
24
+ * fresh 256-bit session id from `web-session.ts` that has no relation to it.
25
+ *
26
+ * Deliberately has no idea what an HTTP request is.
27
+ */
28
+ import { constantTimeMatches, formatOneTimeCode, generateOneTimeCode, normalizeOneTimeCode, } from "./auth/one-time-code.js";
29
+ import { randomBytes } from "node:crypto";
30
+ export const LOGIN_CODE_TTL_MS = 5 * 60 * 1000;
31
+ /** Same reasoning as setup: eight characters on a phone get mistyped. */
32
+ export const MAX_LOGIN_CODE_ATTEMPTS = 5;
33
+ export const LOGIN_BREAKER_WINDOW_MS = 15 * 60 * 1000;
34
+ export const LOGIN_BREAKER_THRESHOLD = 20;
35
+ export const LOGIN_BREAKER_PAUSE_MS = 5 * 60 * 1000;
36
+ /** Compared against when nothing is outstanding, so "no code" costs what "wrong code" costs. */
37
+ const NO_CODE_PLACEHOLDER = "AAAAAAAA";
38
+ export class WebLoginCodes {
39
+ outstanding = null;
40
+ failures = [];
41
+ pausedUntil = 0;
42
+ now;
43
+ generate;
44
+ onEvent;
45
+ constructor(opts = {}) {
46
+ this.now = opts.now ?? Date.now;
47
+ this.generate = opts.generate ?? generateOneTimeCode;
48
+ this.onEvent = opts.onEvent ?? (() => { });
49
+ }
50
+ /**
51
+ * Issue a code, replacing any outstanding one. Only ever called from a channel the operator controls.
52
+ * `epoch` is the current web token's (`tokenEpoch`): rotating the token — from this process or from
53
+ * `agend web-token rotate` in another — withdraws an unused code, because redemption re-checks it.
54
+ */
55
+ issue(opts) {
56
+ const code = this.generate();
57
+ const tier = opts.tier ?? "admin";
58
+ const expiresAt = this.now() + LOGIN_CODE_TTL_MS;
59
+ const issuanceId = randomBytes(16).toString("hex");
60
+ this.outstanding = { code, expiresAt, tier, epoch: opts.epoch, failures: 0, issuanceId, audience: opts.audience ?? "local", owner: opts.owner };
61
+ this.onEvent("issued");
62
+ return { display: formatOneTimeCode(code), expiresAt, tier, issuanceId };
63
+ }
64
+ /** Whether a redemption right now would be compared against anything. */
65
+ get hasOutstandingCode() {
66
+ return this.activeCode() !== null;
67
+ }
68
+ /** Withdraw the outstanding code without spending it (`/dashboard revoke`). */
69
+ revoke() {
70
+ this.outstanding = null;
71
+ }
72
+ /** `currentEpoch` is the web token's epoch NOW: a code issued under another token is gone. */
73
+ revokeIfCurrent(issuanceId) {
74
+ if (this.outstanding?.issuanceId === issuanceId)
75
+ this.outstanding = null;
76
+ }
77
+ revokeAudience(audience) {
78
+ if (this.outstanding?.audience === audience)
79
+ this.outstanding = null;
80
+ }
81
+ redeem(input, currentEpoch, audience = "local") {
82
+ const now = this.now();
83
+ if (now < this.pausedUntil)
84
+ return { kind: "paused", retryAfterMs: this.pausedUntil - now };
85
+ const provided = normalizeOneTimeCode(input);
86
+ // Nothing typed is not a guess; it costs nothing (same as the setup page).
87
+ if (!provided)
88
+ return { kind: "invalid" };
89
+ let active = this.activeCode();
90
+ if (active && active.epoch !== currentEpoch) {
91
+ // The token was rotated after this code was issued: the rotation withdrew it.
92
+ this.outstanding = null;
93
+ active = null;
94
+ }
95
+ if (!active) {
96
+ constantTimeMatches(provided, NO_CODE_PLACEHOLDER);
97
+ return { kind: "invalid" };
98
+ }
99
+ if (constantTimeMatches(provided, active.code) && active.audience === audience) {
100
+ this.outstanding = null;
101
+ this.onEvent("spent");
102
+ return { kind: "ok", tier: active.tier, ...(active.owner ? { owner: active.owner } : {}) };
103
+ }
104
+ active.failures += 1;
105
+ this.recordFailure(now);
106
+ if (active.failures >= MAX_LOGIN_CODE_ATTEMPTS) {
107
+ this.outstanding = null;
108
+ this.onEvent("burned");
109
+ }
110
+ return { kind: "invalid" };
111
+ }
112
+ activeCode() {
113
+ if (!this.outstanding)
114
+ return null;
115
+ if (this.now() >= this.outstanding.expiresAt) {
116
+ this.outstanding = null;
117
+ return null;
118
+ }
119
+ return this.outstanding;
120
+ }
121
+ recordFailure(now) {
122
+ this.failures.push(now);
123
+ const cutoff = now - LOGIN_BREAKER_WINDOW_MS;
124
+ this.failures = this.failures.filter(t => t > cutoff);
125
+ if (this.failures.length >= LOGIN_BREAKER_THRESHOLD) {
126
+ this.pausedUntil = now + LOGIN_BREAKER_PAUSE_MS;
127
+ this.failures = [];
128
+ this.onEvent("breaker-open");
129
+ }
130
+ }
131
+ }
132
+ //# sourceMappingURL=web-login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-login.js","sourceRoot":"","sources":["../src/web-login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * #1389: the fleet's org chart, as the web shows it (Fleet → Org chart). Presentation only (the user's Discord-first
3
+ * rule): nothing here is a new source of truth. The structure is read from what already exists — fleet.yaml's
4
+ * instances and teams, General (general_topic), each instance's world and thread — and the live state is not here at
5
+ * all: the page takes it from the stream it already follows (status, activity, `needs`).
6
+ */
7
+ import type { WorldPlace } from "./needs-you.js";
8
+ export interface OrgInstanceConfig {
9
+ description?: unknown;
10
+ tags?: unknown;
11
+ }
12
+ export interface OrgInput {
13
+ /** Every instance the dashboard lists, in its order (fleet.yaml's, then ClassicBot rooms). */
14
+ names: readonly string[];
15
+ instances: Readonly<Record<string, OrgInstanceConfig | undefined>>;
16
+ teams: Readonly<Record<string, {
17
+ members?: unknown;
18
+ description?: unknown;
19
+ } | undefined>> | undefined;
20
+ isGeneral(name: string): boolean;
21
+ isClassic(name: string): boolean;
22
+ /** The platform and group of the world the instance lives in, when it has one. */
23
+ place(name: string): WorldPlace | undefined;
24
+ /** The instance's own thread/topic (or ClassicBot channel) id. */
25
+ topic(name: string): string | undefined;
26
+ }
27
+ export interface OrgThread {
28
+ platform: "discord" | "telegram";
29
+ url: string;
30
+ }
31
+ export interface OrgInstance {
32
+ description?: string;
33
+ tags?: string[];
34
+ general?: true;
35
+ classic?: true;
36
+ thread?: OrgThread;
37
+ }
38
+ export interface OrgTeam {
39
+ name: string;
40
+ description?: string;
41
+ /** As configured, in order; a name the fleet no longer has is kept (the page shows it as missing). */
42
+ members: string[];
43
+ }
44
+ export interface OrgChart {
45
+ /** General instances (one per world at most, in practice): the top of the chart. */
46
+ general: string[];
47
+ teams: OrgTeam[];
48
+ instances: Record<string, OrgInstance>;
49
+ }
50
+ /**
51
+ * A browser link to an instance's thread: Discord's channel URL, Telegram's topic link. Undefined when the place or
52
+ * the id is not one a link can be built from (Telegram's General topic, 1, has no topic link).
53
+ */
54
+ export declare function threadUrl(place: WorldPlace | undefined, topicId: string | undefined): OrgThread | undefined;
55
+ export declare function buildOrgChart(input: OrgInput): OrgChart;
@@ -0,0 +1,67 @@
1
+ const SNOWFLAKE = /^\d{5,25}$/;
2
+ const MAX_DESCRIPTION = 500;
3
+ const MAX_TAGS = 12;
4
+ /**
5
+ * A browser link to an instance's thread: Discord's channel URL, Telegram's topic link. Undefined when the place or
6
+ * the id is not one a link can be built from (Telegram's General topic, 1, has no topic link).
7
+ */
8
+ export function threadUrl(place, topicId) {
9
+ if (!place || !topicId)
10
+ return undefined;
11
+ if (place.type === "discord") {
12
+ if (!place.groupId || !SNOWFLAKE.test(place.groupId) || !SNOWFLAKE.test(topicId))
13
+ return undefined;
14
+ return { platform: "discord", url: `https://discord.com/channels/${place.groupId}/${topicId}` };
15
+ }
16
+ if (place.type === "telegram") {
17
+ const m = /^-100(\d+)$/.exec(place.groupId ?? "");
18
+ if (!m || !/^\d+$/.test(topicId) || topicId === "1")
19
+ return undefined;
20
+ return { platform: "telegram", url: `https://t.me/c/${m[1]}/${topicId}` };
21
+ }
22
+ return undefined;
23
+ }
24
+ const text = (v) => {
25
+ if (typeof v !== "string")
26
+ return undefined;
27
+ const s = v.trim();
28
+ return s ? (s.length > MAX_DESCRIPTION ? `${s.slice(0, MAX_DESCRIPTION - 1)}…` : s) : undefined;
29
+ };
30
+ export function buildOrgChart(input) {
31
+ // Keyed by instance name, and any name the validators accept is a key — "__proto__" included (#1467 review): a
32
+ // prototype-less map makes every assignment an own property, so JSON carries it like any other name.
33
+ const instances = Object.create(null);
34
+ const general = [];
35
+ for (const name of input.names) {
36
+ const cfg = input.instances[name];
37
+ const entry = {};
38
+ const description = text(cfg?.description);
39
+ if (description)
40
+ entry.description = description;
41
+ if (Array.isArray(cfg?.tags)) {
42
+ const tags = cfg.tags.filter((t) => typeof t === "string" && t.trim() !== "").map(t => t.trim()).slice(0, MAX_TAGS);
43
+ if (tags.length)
44
+ entry.tags = tags;
45
+ }
46
+ if (input.isGeneral(name)) {
47
+ entry.general = true;
48
+ general.push(name);
49
+ }
50
+ if (input.isClassic(name))
51
+ entry.classic = true;
52
+ const thread = threadUrl(input.place(name), input.topic(name));
53
+ if (thread)
54
+ entry.thread = thread;
55
+ instances[name] = entry;
56
+ }
57
+ const teams = [];
58
+ for (const [name, team] of Object.entries(input.teams ?? {})) {
59
+ if (!team)
60
+ continue;
61
+ const members = Array.isArray(team.members) ? [...new Set(team.members.filter((m) => typeof m === "string" && m !== ""))] : [];
62
+ const description = text(team.description);
63
+ teams.push({ name, ...(description ? { description } : {}), members });
64
+ }
65
+ return { general, teams, instances };
66
+ }
67
+ //# sourceMappingURL=web-org.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-org.js","sourceRoot":"","sources":["../src/web-org.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import { type Server } from "node:http";
2
+ import { type HostGuardConfig } from "./web-host-guard.js";
3
+ /** The most HTML a preview takes (UTF-8 bytes): the parent refuses more before sending, the shim refuses it too. */
4
+ export declare const PREVIEW_HTML_MAX_BYTES: number;
5
+ export interface PreviewWebConfig {
6
+ preview?: unknown;
7
+ preview_port?: unknown;
8
+ preview_origin?: unknown;
9
+ }
10
+ export interface PreviewSettings {
11
+ /** `web.preview` (default true): false means no listener, and cards show Source / Download only. */
12
+ enabled: boolean;
13
+ /** The preview listener's port, on 127.0.0.1; null when there is no valid one (health_port 65535 and no preview_port). */
14
+ port: number | null;
15
+ /** `web.preview_origin`, normalised to scheme://host[:port], or null. */
16
+ origin: string | null;
17
+ }
18
+ /** The settings a fleet runs with: `web.preview`, `web.preview_port` (default health_port + 1), `web.preview_origin`. */
19
+ export declare function previewSettings(web: PreviewWebConfig | null | undefined, healthPort: number): PreviewSettings;
20
+ /** scheme://host[:port] for an http(s) URL with nothing else in it; null otherwise. A default port is dropped. */
21
+ export declare function normalizeOrigin(value: string): string | null;
22
+ /**
23
+ * Why a `web.preview_origin` cannot be used, or null when it can (design §3.1). It must be a bare http(s) origin
24
+ * (no path, query, credentials or wildcard), and its host must not be one the dashboard answers to — that would be
25
+ * a same-origin preview by another name.
26
+ */
27
+ export declare function previewOriginProblem(value: unknown, config: HostGuardConfig | null | undefined): string | null;
28
+ export interface PreviewAvailability {
29
+ /** The origin the server believes the browser is at (from Host and the TLS signal), or null. */
30
+ dashboardOrigin: string | null;
31
+ /** The preview origin for this load, or null: preview disabled for it. */
32
+ previewOrigin: string | null;
33
+ /** Why it is disabled, when it is. */
34
+ reason: string | null;
35
+ }
36
+ /** scheme://host[:port] from a Host header the host guard already accepted; a default port is dropped. */
37
+ export declare function dashboardOriginFor(hostHeader: string | undefined, secure: boolean): string | null;
38
+ /**
39
+ * For one /ui request: a loopback Host and no preview_origin → the same loopback name on the preview port;
40
+ * preview_origin set → that; anything else (a tunnel, a proxy, `hostname`, `allowed_hosts`) → disabled.
41
+ * Only Host is read; X-Forwarded-Host never is.
42
+ */
43
+ export declare function previewAvailability(settings: PreviewSettings | null, hostHeader: string | undefined, secure: boolean, accepted?: readonly string[]): PreviewAvailability;
44
+ /**
45
+ * The exact dashboard origins a preview may be framed by, and accept HTML from (design §5.1): the loopback names on
46
+ * the dashboard's port, plus — when a preview_origin is set — `https://<name>` for each name the dashboard answers to
47
+ * besides loopback (`hostname`, `web.allowed_hosts`). Never 'self', never *.
48
+ */
49
+ export declare function dashboardOrigins(settings: PreviewSettings, healthPort: number, config: HostGuardConfig | null | undefined): string[];
50
+ /** The /frame response's Content-Security-Policy (design §5.1): exact, no 'self', no *. */
51
+ export declare function previewFrameCsp(ancestors: readonly string[]): string;
52
+ /**
53
+ * The shim (design §4): posts `ready` with the listener's boot id; takes ONE `render` — from its parent window, from
54
+ * an exact listed dashboard origin (never "null"), with exactly {v, type, ch, html}, html ≤ 1 MiB — removes the WebRTC
55
+ * entry points from its realm (defence in depth only), and writes a heartbeat/height prologue and the HTML into its
56
+ * own document. Every later message is ignored.
57
+ */
58
+ export declare function buildShim(bootId: string, origins: readonly string[]): string;
59
+ export interface PreviewListener {
60
+ readonly server: Server;
61
+ readonly bootId: string;
62
+ readonly settings: PreviewSettings;
63
+ /** The dashboard origins the shim accepts HTML from and may be framed by. */
64
+ readonly origins: readonly string[];
65
+ close(): void;
66
+ }
67
+ /**
68
+ * The preview listener: GET /frame (no query) → the shim with the §5.1 headers; anything else → empty 404; a Host
69
+ * it does not answer to → empty 403. No X-Frame-Options (it must be framable by the dashboard), no cookie or
70
+ * header parsing beyond Host, nothing logged per request. Built once: the shim and its headers never change.
71
+ */
72
+ export declare function createPreviewListener(opts: {
73
+ settings: PreviewSettings;
74
+ healthPort: number;
75
+ config: HostGuardConfig | null | undefined;
76
+ bootId?: string;
77
+ }): PreviewListener;