@songsid/agend 2.3.1 → 2.3.2-beta.10

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 (338) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/dist/agent-cli.js +21 -2
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +5 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/antigravity-usage-scan.d.ts +58 -0
  7. package/dist/antigravity-usage-scan.js +257 -0
  8. package/dist/antigravity-usage-scan.js.map +1 -0
  9. package/dist/antigravity-usage-worker.d.ts +1 -0
  10. package/dist/antigravity-usage-worker.js +10 -0
  11. package/dist/antigravity-usage-worker.js.map +1 -0
  12. package/dist/auth-api.js +4 -0
  13. package/dist/auth-api.js.map +1 -1
  14. package/dist/authority/authority-state.d.ts +4 -0
  15. package/dist/authority/authority-state.js +16 -10
  16. package/dist/authority/authority-state.js.map +1 -1
  17. package/dist/authority/session-authorizations.d.ts +7 -1
  18. package/dist/authority/session-authorizations.js +6 -4
  19. package/dist/authority/session-authorizations.js.map +1 -1
  20. package/dist/authority/web-authority.d.ts +19 -6
  21. package/dist/authority/web-authority.js +22 -8
  22. package/dist/authority/web-authority.js.map +1 -1
  23. package/dist/backend/kiro-identity.d.ts +64 -0
  24. package/dist/backend/kiro-identity.js +101 -0
  25. package/dist/backend/kiro-identity.js.map +1 -1
  26. package/dist/backend/kiro-retired-model.d.ts +62 -0
  27. package/dist/backend/kiro-retired-model.js +160 -0
  28. package/dist/backend/kiro-retired-model.js.map +1 -0
  29. package/dist/backend/kiro.d.ts +71 -2
  30. package/dist/backend/kiro.js +352 -11
  31. package/dist/backend/kiro.js.map +1 -1
  32. package/dist/backend/types.d.ts +59 -0
  33. package/dist/backend/types.js.map +1 -1
  34. package/dist/backend-signin-guide.d.ts +1 -0
  35. package/dist/backend-signin-guide.js +9 -0
  36. package/dist/backend-signin-guide.js.map +1 -0
  37. package/dist/backend-signin-pending.d.ts +10 -0
  38. package/dist/backend-signin-pending.js +44 -0
  39. package/dist/backend-signin-pending.js.map +1 -0
  40. package/dist/browser-backend-signins.d.ts +56 -0
  41. package/dist/browser-backend-signins.js +189 -0
  42. package/dist/browser-backend-signins.js.map +1 -0
  43. package/dist/cache-ledger.d.ts +38 -2
  44. package/dist/cache-ledger.js +133 -8
  45. package/dist/cache-ledger.js.map +1 -1
  46. package/dist/cache-prices.d.ts +2 -1
  47. package/dist/cache-prices.js +5 -2
  48. package/dist/cache-prices.js.map +1 -1
  49. package/dist/cache-service.d.ts +68 -0
  50. package/dist/cache-service.js +355 -13
  51. package/dist/cache-service.js.map +1 -1
  52. package/dist/channel/adapters/discord.d.ts +13 -0
  53. package/dist/channel/adapters/discord.js +103 -16
  54. package/dist/channel/adapters/discord.js.map +1 -1
  55. package/dist/channel/mcp-tools.js +34 -7
  56. package/dist/channel/mcp-tools.js.map +1 -1
  57. package/dist/channel/tool-router.js +2 -1
  58. package/dist/channel/tool-router.js.map +1 -1
  59. package/dist/channel/types.d.ts +11 -0
  60. package/dist/classic-channel-manager.d.ts +11 -0
  61. package/dist/classic-channel-manager.js +39 -1
  62. package/dist/classic-channel-manager.js.map +1 -1
  63. package/dist/cli.js +21 -2
  64. package/dist/cli.js.map +1 -1
  65. package/dist/config-validator.d.ts +7 -1
  66. package/dist/config-validator.js +92 -12
  67. package/dist/config-validator.js.map +1 -1
  68. package/dist/config.d.ts +19 -0
  69. package/dist/config.js +31 -1
  70. package/dist/config.js.map +1 -1
  71. package/dist/cpu-quota.d.ts +23 -0
  72. package/dist/cpu-quota.js +86 -0
  73. package/dist/cpu-quota.js.map +1 -0
  74. package/dist/daemon.d.ts +57 -3
  75. package/dist/daemon.js +330 -50
  76. package/dist/daemon.js.map +1 -1
  77. package/dist/dashboard-permissions.d.ts +20 -6
  78. package/dist/dashboard-permissions.js +58 -9
  79. package/dist/dashboard-permissions.js.map +1 -1
  80. package/dist/delivery-outbox.d.ts +12 -1
  81. package/dist/delivery-outbox.js +67 -15
  82. package/dist/delivery-outbox.js.map +1 -1
  83. package/dist/doctor.d.ts +3 -0
  84. package/dist/doctor.js +11 -1
  85. package/dist/doctor.js.map +1 -1
  86. package/dist/event-log.d.ts +5 -0
  87. package/dist/event-log.js +13 -0
  88. package/dist/event-log.js.map +1 -1
  89. package/dist/fleet-manager.d.ts +212 -0
  90. package/dist/fleet-manager.js +914 -111
  91. package/dist/fleet-manager.js.map +1 -1
  92. package/dist/fleet-process-handlers.d.ts +1 -0
  93. package/dist/fleet-process-handlers.js +9 -1
  94. package/dist/fleet-process-handlers.js.map +1 -1
  95. package/dist/forged-envelope-scan.d.ts +9 -0
  96. package/dist/forged-envelope-scan.js +30 -5
  97. package/dist/forged-envelope-scan.js.map +1 -1
  98. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
  99. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +14 -1
  100. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +28 -5
  101. package/dist/general-knowledge/skills/web-appearance/SKILL.md +31 -8
  102. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +138 -59
  103. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +145 -61
  104. package/dist/guest-seen-people.d.ts +38 -0
  105. package/dist/guest-seen-people.js +78 -0
  106. package/dist/guest-seen-people.js.map +1 -0
  107. package/dist/instance-config-impact.js +14 -6
  108. package/dist/instance-config-impact.js.map +1 -1
  109. package/dist/instance-lifecycle.d.ts +20 -0
  110. package/dist/instance-lifecycle.js +71 -13
  111. package/dist/instance-lifecycle.js.map +1 -1
  112. package/dist/kiro-db-reader.d.ts +5 -0
  113. package/dist/kiro-db-reader.js +18 -0
  114. package/dist/kiro-db-reader.js.map +1 -1
  115. package/dist/kiro-engine-status.d.ts +2 -0
  116. package/dist/kiro-engine-status.js +9 -0
  117. package/dist/kiro-engine-status.js.map +1 -1
  118. package/dist/kiro-migrate.d.ts +49 -0
  119. package/dist/kiro-migrate.js +105 -0
  120. package/dist/kiro-migrate.js.map +1 -0
  121. package/dist/kiro-transcript-lane.d.ts +2 -0
  122. package/dist/kiro-transcript-lane.js.map +1 -1
  123. package/dist/kiro-transcript-worker.js +1 -0
  124. package/dist/kiro-transcript-worker.js.map +1 -1
  125. package/dist/kiro-tui-forensic.d.ts +7 -0
  126. package/dist/kiro-tui-forensic.js +110 -0
  127. package/dist/kiro-tui-forensic.js.map +1 -0
  128. package/dist/kiro-usage-run.d.ts +7 -0
  129. package/dist/kiro-usage-run.js +39 -0
  130. package/dist/kiro-usage-run.js.map +1 -0
  131. package/dist/kiro-usage-scan.d.ts +49 -0
  132. package/dist/kiro-usage-scan.js +223 -0
  133. package/dist/kiro-usage-scan.js.map +1 -0
  134. package/dist/kiro-usage-worker.d.ts +1 -0
  135. package/dist/kiro-usage-worker.js +10 -0
  136. package/dist/kiro-usage-worker.js.map +1 -0
  137. package/dist/kiro-usage.d.ts +58 -0
  138. package/dist/kiro-usage.js +310 -0
  139. package/dist/kiro-usage.js.map +1 -0
  140. package/dist/locale.js +120 -12
  141. package/dist/locale.js.map +1 -1
  142. package/dist/log-service.js +1 -1
  143. package/dist/log-service.js.map +1 -1
  144. package/dist/login-url.d.ts +4 -0
  145. package/dist/login-url.js +22 -0
  146. package/dist/login-url.js.map +1 -0
  147. package/dist/mac-auto-restore.d.ts +117 -0
  148. package/dist/mac-auto-restore.js +294 -0
  149. package/dist/mac-auto-restore.js.map +1 -0
  150. package/dist/needs-you-hub.d.ts +2 -1
  151. package/dist/needs-you-hub.js +1 -1
  152. package/dist/needs-you-hub.js.map +1 -1
  153. package/dist/needs-you.d.ts +8 -2
  154. package/dist/needs-you.js +6 -0
  155. package/dist/needs-you.js.map +1 -1
  156. package/dist/notice-delivery.d.ts +16 -0
  157. package/dist/notice-delivery.js +35 -0
  158. package/dist/notice-delivery.js.map +1 -0
  159. package/dist/office-share-page.d.ts +16 -0
  160. package/dist/office-share-page.js +85 -0
  161. package/dist/office-share-page.js.map +1 -0
  162. package/dist/office-share-service.d.ts +91 -0
  163. package/dist/office-share-service.js +176 -0
  164. package/dist/office-share-service.js.map +1 -0
  165. package/dist/office-share.d.ts +258 -0
  166. package/dist/office-share.js +549 -0
  167. package/dist/office-share.js.map +1 -0
  168. package/dist/opencode-usage-scan.d.ts +28 -0
  169. package/dist/opencode-usage-scan.js +92 -0
  170. package/dist/opencode-usage-scan.js.map +1 -0
  171. package/dist/opencode-usage-worker.d.ts +1 -0
  172. package/dist/opencode-usage-worker.js +10 -0
  173. package/dist/opencode-usage-worker.js.map +1 -0
  174. package/dist/outbound-handlers.js +22 -2
  175. package/dist/outbound-handlers.js.map +1 -1
  176. package/dist/outbound-schemas.d.ts +28 -2
  177. package/dist/outbound-schemas.js +29 -2
  178. package/dist/outbound-schemas.js.map +1 -1
  179. package/dist/public-web-gateway.js +16 -2
  180. package/dist/public-web-gateway.js.map +1 -1
  181. package/dist/quickstart-api.js +13 -2
  182. package/dist/quickstart-api.js.map +1 -1
  183. package/dist/quickstart.js +2 -1
  184. package/dist/quickstart.js.map +1 -1
  185. package/dist/remote-card-answer.d.ts +83 -0
  186. package/dist/remote-card-answer.js +208 -0
  187. package/dist/remote-card-answer.js.map +1 -0
  188. package/dist/reply-triggers.d.ts +2 -0
  189. package/dist/reply-triggers.js +3 -0
  190. package/dist/reply-triggers.js.map +1 -0
  191. package/dist/settings-api.d.ts +28 -0
  192. package/dist/settings-api.js +148 -17
  193. package/dist/settings-api.js.map +1 -1
  194. package/dist/settings-change.js +54 -17
  195. package/dist/settings-change.js.map +1 -1
  196. package/dist/settings-effect.js +12 -3
  197. package/dist/settings-effect.js.map +1 -1
  198. package/dist/settings-instance-patch.js +4 -1
  199. package/dist/settings-instance-patch.js.map +1 -1
  200. package/dist/setup-form.d.ts +1 -1
  201. package/dist/setup-form.js +55 -6
  202. package/dist/setup-form.js.map +1 -1
  203. package/dist/setup-host.d.ts +9 -0
  204. package/dist/setup-host.js +127 -6
  205. package/dist/setup-host.js.map +1 -1
  206. package/dist/setup-signin-assets.d.ts +2 -0
  207. package/dist/setup-signin-assets.js +6 -0
  208. package/dist/setup-signin-assets.js.map +1 -0
  209. package/dist/setup-wizard.js +2 -0
  210. package/dist/setup-wizard.js.map +1 -1
  211. package/dist/start-attempt.d.ts +9 -5
  212. package/dist/start-attempt.js +27 -8
  213. package/dist/start-attempt.js.map +1 -1
  214. package/dist/start-failure-chat.d.ts +4 -0
  215. package/dist/start-failure-chat.js +23 -6
  216. package/dist/start-failure-chat.js.map +1 -1
  217. package/dist/start-failure-notice.d.ts +4 -0
  218. package/dist/start-failure-notice.js +8 -0
  219. package/dist/start-failure-notice.js.map +1 -1
  220. package/dist/start-failure.d.ts +25 -0
  221. package/dist/start-failure.js +75 -2
  222. package/dist/start-failure.js.map +1 -1
  223. package/dist/structured-signin-transport.d.ts +8 -0
  224. package/dist/structured-signin-transport.js +79 -0
  225. package/dist/structured-signin-transport.js.map +1 -0
  226. package/dist/structured-signin.d.ts +131 -0
  227. package/dist/structured-signin.js +388 -0
  228. package/dist/structured-signin.js.map +1 -0
  229. package/dist/tool-permissions.js +12 -0
  230. package/dist/tool-permissions.js.map +1 -1
  231. package/dist/topic-commands.js +6 -2
  232. package/dist/topic-commands.js.map +1 -1
  233. package/dist/transcript-sources.d.ts +26 -2
  234. package/dist/transcript-sources.js +103 -40
  235. package/dist/transcript-sources.js.map +1 -1
  236. package/dist/turn-reply-guard.d.ts +25 -0
  237. package/dist/turn-reply-guard.js +111 -12
  238. package/dist/turn-reply-guard.js.map +1 -1
  239. package/dist/types.d.ts +46 -9
  240. package/dist/ui/backend-signin.js +68 -12
  241. package/dist/ui/chat-ambient.js +1 -1
  242. package/dist/ui/chat-effects.js +4 -4
  243. package/dist/ui/chat-render.js +5 -2
  244. package/dist/ui/chat-strings.js +4 -4
  245. package/dist/ui/chat-thread.js +2 -2
  246. package/dist/ui/first-run.js +2 -2
  247. package/dist/ui/fleet-canvas-view.js +114 -0
  248. package/dist/ui/fleet-canvas.js +15 -77
  249. package/dist/ui/fleet-office-editor.js +94 -14
  250. package/dist/ui/fleet-office-layout.js +235 -0
  251. package/dist/ui/fleet-office-model.js +12 -6
  252. package/dist/ui/fleet-office-overview.js +167 -0
  253. package/dist/ui/fleet-office-pack.d.ts +44 -0
  254. package/dist/ui/fleet-office-pack.js +352 -37
  255. package/dist/ui/fleet-office-skin.js +220 -36
  256. package/dist/ui/fleet-office-store.js +112 -7
  257. package/dist/ui/fleet-office-view.js +155 -0
  258. package/dist/ui/fleet-office-zones.js +264 -42
  259. package/dist/ui/fleet-office.js +957 -160
  260. package/dist/ui/fleet-usage.js +26 -21
  261. package/dist/ui/office-share-dialog.js +145 -0
  262. package/dist/ui/office-share-page.js +164 -0
  263. package/dist/ui/panel-chat.js +9 -2
  264. package/dist/ui/panel-details.js +2 -2
  265. package/dist/ui/panel-fleet.js +11 -77
  266. package/dist/ui/panel-log.js +4 -4
  267. package/dist/ui/panel-needs.js +4 -4
  268. package/dist/ui/panel-settings.js +608 -167
  269. package/dist/ui/search-dialog.js +26 -7
  270. package/dist/ui/settings-appearance.js +82 -29
  271. package/dist/ui/settings-apply.js +42 -7
  272. package/dist/ui/settings-dialogs.js +76 -23
  273. package/dist/ui/settings-draft.js +51 -0
  274. package/dist/ui/settings-guests.js +255 -79
  275. package/dist/ui/settings-learn.js +2 -2
  276. package/dist/ui/settings-model.js +16 -6
  277. package/dist/ui/settings-registry.js +146 -0
  278. package/dist/ui/settings-strings.js +116 -44
  279. package/dist/ui/settings-web-authority.js +34 -23
  280. package/dist/ui/shared/app-i18n.js +18 -2
  281. package/dist/ui/shared/app-nav.js +10 -5
  282. package/dist/ui/shared/app-prefs.js +80 -2
  283. package/dist/ui/shared/app-route.js +39 -5
  284. package/dist/ui/shared/app-shell.js +73 -10
  285. package/dist/ui/shared/app-stream.js +3 -1
  286. package/dist/ui/shared/app.css +307 -17
  287. package/dist/ui/shared/app.js +3 -0
  288. package/dist/ui/shared/effect-stock-trust.js +1 -1
  289. package/dist/ui/shared/identity-color.js +4 -4
  290. package/dist/ui/shared/office/cozy.png +0 -0
  291. package/dist/ui/shared/side-panel.js +95 -0
  292. package/dist/ui/shared/signin.js +2 -2
  293. package/dist/ui/shared/ui-dialog.js +6 -1
  294. package/dist/ui/shared/ui-icons.js +2 -0
  295. package/dist/ui/shared/web-authorize.js +13 -12
  296. package/dist/ui/structured-signin.js +115 -0
  297. package/dist/usage-report.d.ts +20 -0
  298. package/dist/usage-report.js +54 -17
  299. package/dist/usage-report.js.map +1 -1
  300. package/dist/usage-rollup.d.ts +9 -1
  301. package/dist/usage-rollup.js +20 -4
  302. package/dist/usage-rollup.js.map +1 -1
  303. package/dist/web-api.d.ts +26 -2
  304. package/dist/web-api.js +361 -5
  305. package/dist/web-api.js.map +1 -1
  306. package/dist/web-auth.js +8 -2
  307. package/dist/web-auth.js.map +1 -1
  308. package/dist/web-backend-login.d.ts +1 -7
  309. package/dist/web-backend-login.js +2 -24
  310. package/dist/web-backend-login.js.map +1 -1
  311. package/dist/web-experimental.d.ts +1 -1
  312. package/dist/web-experimental.js +1 -1
  313. package/dist/web-experimental.js.map +1 -1
  314. package/dist/web-mod-agent.js +2 -2
  315. package/dist/web-mod-agent.js.map +1 -1
  316. package/dist/web-office-acts.d.ts +173 -0
  317. package/dist/web-office-acts.js +285 -0
  318. package/dist/web-office-acts.js.map +1 -0
  319. package/dist/web-office-skins.d.ts +109 -0
  320. package/dist/web-office-skins.js +299 -0
  321. package/dist/web-office-skins.js.map +1 -0
  322. package/dist/web-office-store.d.ts +8 -2
  323. package/dist/web-office-store.js +77 -14
  324. package/dist/web-office-store.js.map +1 -1
  325. package/dist/web-settings-patch.d.ts +8 -1
  326. package/dist/web-settings-patch.js +65 -5
  327. package/dist/web-settings-patch.js.map +1 -1
  328. package/dist/web-shell-routes.d.ts +4 -2
  329. package/dist/web-shell-routes.js +17 -7
  330. package/dist/web-shell-routes.js.map +1 -1
  331. package/dist/working-recovery.d.ts +104 -0
  332. package/dist/working-recovery.js +298 -0
  333. package/dist/working-recovery.js.map +1 -0
  334. package/dist/working-state-journal.d.ts +5 -1
  335. package/dist/working-state-journal.js +31 -3
  336. package/dist/working-state-journal.js.map +1 -1
  337. package/package.json +3 -2
  338. package/templates/windows-shortcuts.ps1 +155 -140
package/dist/web-api.js CHANGED
@@ -15,6 +15,7 @@ import { AGENT_WIDGET_SLOTS, checkWidgetData, checkWidgetSpec } from "./web-widg
15
15
  import { checkOwnerCss, ownerCssAssetRefs } from "./web-owner-css.js";
16
16
  import { canonicalText } from "./web-theme.js";
17
17
  import { EFFECT_WIRE_LIMIT } from "./web-effect-upload.js";
18
+ import { SKIN_WIRE_BYTES } from "./web-office-skins.js";
18
19
  import { gatewayRequestContext } from "./web-request-context.js";
19
20
  import { crossInstanceVisibility } from "./cross-instance-notice.js";
20
21
  import { permitWebContinuation } from "./web-continuation.js";
@@ -447,6 +448,230 @@ function handleAppIcon(req, res, url, ctx, method) {
447
448
  });
448
449
  return true;
449
450
  }
451
+ /**
452
+ * #2636: Office share links — GET /ui/office-share (the open ones, and whether this fleet offers them), POST { office,
453
+ * minutes?, text? } (a new one; answers its URL once its tunnel is up, or why not), DELETE /ui/office-share/<id|all>.
454
+ * An admin session only — on the dashboard reached directly, or (the user, #2636 follow-up) a fleet admin signed in over
455
+ * the /web public link: the same check either way. Never the CLI token, a read-only session or a guest. The share's own
456
+ * tunnel and gateway stay apart from /web's. The person is asked again once the body is in.
457
+ */
458
+ function handleOfficeShare(req, res, url, ctx, path, method) {
459
+ const service = ctx.officeShares;
460
+ if (!service) {
461
+ json(res, 404, { error: "Office share links are not kept on this fleet" });
462
+ return true;
463
+ }
464
+ const one = /^\/ui\/office-share\/([0-9a-f]{8}|all)$/.exec(path);
465
+ const known = (method === "GET" && path === "/ui/office-share") || (method === "POST" && path === "/ui/office-share") || (method === "DELETE" && !!one);
466
+ if (!known) {
467
+ json(res, 404, { error: "not found" });
468
+ return true;
469
+ }
470
+ const who = () => {
471
+ const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
472
+ if (verdict.kind !== "allow")
473
+ return { status: 401 };
474
+ if (verdict.via !== "session" || verdict.session?.tier !== "admin")
475
+ return { status: 403 };
476
+ const owner = verdict.session.owner;
477
+ return { person: owner ? webPersonKey(owner) : "admin-session", name: owner ? ctx.webPerson?.(owner)?.name ?? owner.userId : "admin" };
478
+ };
479
+ const refuse = (status) => json(res, status, { error: status === 401 ? WEB_TOKEN_INVALID_MESSAGE : "office_share_needs_an_admin_session" });
480
+ const first = who();
481
+ if ("status" in first) {
482
+ refuse(first.status);
483
+ return true;
484
+ }
485
+ res.setHeader("Cache-Control", "no-store");
486
+ if (method === "GET") {
487
+ // Each with its office's name as it is now (Settings' card has no office list of its own).
488
+ const names = new Map((ctx.officeStore?.list().offices ?? []).map(o => [o.id, o.name]));
489
+ json(res, 200, { enabled: service.enabled(), shares: service.list().map(s => ({ ...s, ...(names.has(s.office) ? { office_name: names.get(s.office) } : {}) })) });
490
+ return true;
491
+ }
492
+ if (method === "DELETE") {
493
+ const id = one[1];
494
+ const before = id === "all" ? null : service.list().find(s => s.id === id) ?? null;
495
+ const n = service.revoke(id);
496
+ if (n)
497
+ ctx.officeShareNotice?.("revoked", first.name, before, n);
498
+ json(res, n ? 200 : 404, n ? { revoked: n } : { error: "not_found" });
499
+ return true;
500
+ }
501
+ (async () => {
502
+ let body;
503
+ try {
504
+ body = await parseBody(req);
505
+ }
506
+ catch {
507
+ json(res, 400, { error: "Invalid JSON" });
508
+ return;
509
+ }
510
+ if (!permitWebContinuation(req, res, ctx))
511
+ return;
512
+ const now = who();
513
+ if ("status" in now) {
514
+ refuse(now.status);
515
+ return;
516
+ }
517
+ if (!body || typeof body !== "object" || Array.isArray(body) || typeof body.office !== "string"
518
+ || (body.minutes !== undefined && typeof body.minutes !== "number") || (body.text !== undefined && typeof body.text !== "boolean")) {
519
+ json(res, 400, { error: "office (string), minutes? (number), text? (boolean)" });
520
+ return;
521
+ }
522
+ const made = await service.create({ office: body.office, owner: now.person, ...(typeof body.minutes === "number" ? { minutes: body.minutes } : {}),
523
+ ...(typeof body.text === "boolean" ? { text: body.text } : {}) });
524
+ if (!made.ok) {
525
+ const status = made.error === "disabled" ? 404 : made.error === "tunnel" ? 503 : made.error === "too_many" ? 409 : 400;
526
+ json(res, status, { error: made.error, ...(made.reason ? { reason: made.reason } : {}) });
527
+ return;
528
+ }
529
+ ctx.officeShareNotice?.("created", now.name, made.share, 1);
530
+ json(res, 200, { share: made.share, url: made.url });
531
+ })().catch(err => {
532
+ ctx.logger.error({ err: err.message }, "Office share failed");
533
+ try {
534
+ json(res, 500, { error: "Office share failed" });
535
+ }
536
+ catch { /* already answered */ }
537
+ });
538
+ return true;
539
+ }
540
+ /**
541
+ * #2513: the Office view's skin packs (web-office-skins.ts). The reads are the offices' (GET /ui/offices): whoever may
542
+ * open the org chart, the public link too (its manifest lists them; a guest's routes do not): the list, a pack's
543
+ * manifest (?v=<sha>: immutable, else no-store) and its pictures (by the sha of their bytes: immutable, image/png,
544
+ * nosniff, sandboxed). The writes are PUT /ui/offices's: an admin session only — never the CLI token, never the public
545
+ * link (absent from its manifest, and refused here too) — asked again once the body is in; rate limited per person.
546
+ * Every page hears `office_skins` on the stream and reads the list again.
547
+ */
548
+ function handleOfficeSkins(req, res, url, ctx, path, method) {
549
+ const store = ctx.officeSkins;
550
+ if (!store) {
551
+ json(res, 404, { error: "Skin packs are not kept on this fleet" });
552
+ return true;
553
+ }
554
+ const one = /^\/ui\/office-skins\/([A-Za-z0-9_-]{1,40})$/.exec(path);
555
+ const picture = /^\/ui\/office-skins\/([A-Za-z0-9_-]{1,40})\/img\/([0-9a-f]{32})\.png$/.exec(path);
556
+ if (method === "GET" || method === "HEAD") {
557
+ res.setHeader("X-Content-Type-Options", "nosniff");
558
+ if (path === "/ui/office-skins") {
559
+ res.setHeader("Cache-Control", "private, no-store");
560
+ json(res, 200, store.list());
561
+ return true;
562
+ }
563
+ if (one) {
564
+ const pack = store.get(one[1]);
565
+ if (!pack) {
566
+ res.setHeader("Cache-Control", "private, no-store");
567
+ json(res, 404, { error: "skin_unknown" });
568
+ return true;
569
+ }
570
+ const v = url.searchParams.get("v");
571
+ res.setHeader("Cache-Control", v === pack.sha ? "private, max-age=31536000, immutable" : "private, no-store");
572
+ json(res, 200, pack);
573
+ return true;
574
+ }
575
+ if (picture) {
576
+ const bytes = store.picture(picture[1], picture[2]);
577
+ if (!bytes) {
578
+ res.setHeader("Cache-Control", "private, no-store");
579
+ json(res, 404, { error: "No such image" });
580
+ return true;
581
+ }
582
+ res.setHeader("Content-Type", "image/png");
583
+ // Keyed by the sha of its bytes: a picture at this URL never changes.
584
+ res.setHeader("Cache-Control", "private, max-age=31536000, immutable");
585
+ res.setHeader("Content-Security-Policy", "default-src 'none'; sandbox");
586
+ res.writeHead(200);
587
+ res.end(method === "HEAD" ? undefined : bytes);
588
+ return true;
589
+ }
590
+ json(res, 404, { error: "not found" });
591
+ return true;
592
+ }
593
+ const upload = method === "POST" && path === "/ui/office-skins", removal = method === "DELETE" && !!one;
594
+ if (!upload && !removal) {
595
+ json(res, 404, { error: "not found" });
596
+ return true;
597
+ }
598
+ if (gatewayRequestContext(req)) {
599
+ json(res, 403, { error: "skins_not_over_public_link" });
600
+ return true;
601
+ }
602
+ const who = () => {
603
+ const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
604
+ if (verdict.kind !== "allow")
605
+ return { status: 401 };
606
+ return verdict.via === "session" && verdict.session?.tier === "admin" ? { person: verdict.session.owner ? webPersonKey(verdict.session.owner) : "admin-session" } : { status: 403 };
607
+ };
608
+ const refuse = (status) => json(res, status, { error: status === 401 ? WEB_TOKEN_INVALID_MESSAGE : "skins_need_an_admin_session" });
609
+ const first = who();
610
+ if ("status" in first) {
611
+ refuse(first.status);
612
+ return true;
613
+ }
614
+ const limited = (person) => {
615
+ const allowed = ctx.officeSkinWrites ? ctx.officeSkinWrites.check(person) : { ok: true };
616
+ if (allowed.ok)
617
+ return false;
618
+ res.setHeader("Retry-After", String(allowed.retry_after_s));
619
+ json(res, 429, { error: "skins_rate_limited", retry_after_s: allowed.retry_after_s });
620
+ return true;
621
+ };
622
+ const answer = (r, body, person) => {
623
+ if (!r.ok) {
624
+ json(res, r.status, { error: r.error, ...(r.detail ? { detail: r.detail } : {}) });
625
+ return;
626
+ }
627
+ ctx.officeSkinWrites?.record(person);
628
+ ctx.emitSseEvent("office_skins", { rev: r.rev });
629
+ json(res, 200, body());
630
+ };
631
+ if (removal) {
632
+ if (limited(first.person))
633
+ return true;
634
+ const r = store.remove(one[1]);
635
+ answer(r, () => ({ rev: r.ok ? r.rev : "" }), first.person);
636
+ return true;
637
+ }
638
+ (async () => {
639
+ let bytes;
640
+ try {
641
+ bytes = await readBoundedWebBody(req, SKIN_WIRE_BYTES);
642
+ }
643
+ catch {
644
+ json(res, 413, { error: "skin_too_large", detail: `at most ${Math.floor(SKIN_WIRE_BYTES / 1024 / 1024)} MiB a request` });
645
+ return;
646
+ }
647
+ if (!permitWebContinuation(req, res, ctx))
648
+ return;
649
+ const now = who();
650
+ if ("status" in now) {
651
+ refuse(now.status);
652
+ return;
653
+ }
654
+ let body;
655
+ try {
656
+ body = JSON.parse(bytes.toString("utf8"));
657
+ }
658
+ catch {
659
+ json(res, 400, { error: "Invalid JSON" });
660
+ return;
661
+ }
662
+ if (limited(now.person))
663
+ return;
664
+ const r = store.add(body, now.person);
665
+ answer(r, () => (r.ok ? { rev: r.rev, pack: r.pack } : null), now.person);
666
+ })().catch(err => {
667
+ ctx.logger.error({ err: err.message }, "Uploading a skin pack failed");
668
+ try {
669
+ json(res, 500, { error: "Uploading a skin pack failed" });
670
+ }
671
+ catch { /* already answered */ }
672
+ });
673
+ return true;
674
+ }
450
675
  /**
451
676
  * #1629 r4 §3: the owner-CSS routes (see their call site). `who()`, asked first and again after any body: the person a
452
677
  * signed-in human session is, or the status to answer.
@@ -782,6 +1007,103 @@ function handleEffectTrials(req, res, url, ctx, path, method) {
782
1007
  catch { /* response ended */ } });
783
1008
  return true;
784
1009
  }
1010
+ /** #2599: the same closed browser controls as setup, for a current local human admin. */
1011
+ function handleBrowserBackendSignIns(req, res, url, ctx) {
1012
+ res.setHeader("Cache-Control", "private, no-store");
1013
+ res.setHeader("X-Content-Type-Options", "nosniff");
1014
+ const reading = req.method === "GET";
1015
+ if (gatewayRequestContext(req)) {
1016
+ json(res, reading ? 404 : 403, { error: "sign_in_not_over_public_link" });
1017
+ return true;
1018
+ }
1019
+ const who = () => {
1020
+ const v = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
1021
+ return v.kind === "allow" && v.via === "session" && v.session.tier === "admin" && v.session.surface === "local" ? v.session : null;
1022
+ };
1023
+ const owner = who();
1024
+ if (!owner) {
1025
+ json(res, 403, { error: "sign_in_needs_a_local_admin" });
1026
+ return true;
1027
+ }
1028
+ if (!["GET", "POST"].includes(req.method ?? "") || [...url.searchParams.keys()].length) {
1029
+ json(res, 404, { error: "not_found" });
1030
+ return true;
1031
+ }
1032
+ const service = ctx.browserBackendSignIns;
1033
+ if (!service) {
1034
+ json(res, 404, { error: "sign_in_unavailable" });
1035
+ return true;
1036
+ }
1037
+ void (async () => {
1038
+ let result;
1039
+ if (reading)
1040
+ result = await service.refresh(owner);
1041
+ else {
1042
+ let bytes;
1043
+ try {
1044
+ bytes = await readBoundedWebBody(req, 8192);
1045
+ }
1046
+ catch {
1047
+ json(res, 413, { error: "sign_in_body_bound" });
1048
+ return;
1049
+ }
1050
+ if (!permitWebContinuation(req, res, ctx))
1051
+ return;
1052
+ if (who() !== owner) {
1053
+ json(res, 409, { error: "signin_session_expired" });
1054
+ return;
1055
+ }
1056
+ let body;
1057
+ try {
1058
+ body = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1059
+ }
1060
+ catch {
1061
+ json(res, 400, { error: "sign_in_json" });
1062
+ return;
1063
+ }
1064
+ const parsed = z.discriminatedUnion("operation", [
1065
+ z.strictObject({ operation: z.literal("select"), backends: z.array(z.enum(["kiro-cli", "claude-code", "codex", "antigravity"])).max(4) }),
1066
+ z.strictObject({ operation: z.literal("start"), backend: z.enum(["kiro-cli", "claude-code", "codex"]), relogin: z.boolean().optional() }),
1067
+ z.strictObject({ operation: z.literal("input"), id: z.string().regex(/^[0-9a-f]{32}$/), revision: z.number().int().nonnegative(), action: z.discriminatedUnion("kind", [
1068
+ z.strictObject({ kind: z.literal("method"), choice: z.string().max(32) }),
1069
+ z.strictObject({ kind: z.enum(["code", "start_url", "region"]), value: z.string().max(4096) }),
1070
+ ]) }),
1071
+ z.strictObject({ operation: z.literal("skip"), backend: z.enum(["kiro-cli", "claude-code", "codex", "antigravity"]) }),
1072
+ z.strictObject({ operation: z.literal("cancel") }), z.strictObject({ operation: z.literal("refresh") }),
1073
+ ]).safeParse(body);
1074
+ if (!parsed.success) {
1075
+ json(res, 400, { error: "invalid_signin_action" });
1076
+ return;
1077
+ }
1078
+ const action = parsed.data;
1079
+ if (action.operation === "select")
1080
+ result = await service.select(owner, action.backends);
1081
+ else if (action.operation === "start")
1082
+ result = await service.begin(owner, action.backend, action.relogin === true);
1083
+ else if (action.operation === "input")
1084
+ result = await service.input(owner, action.id, action.revision, action.action);
1085
+ else if (action.operation === "skip")
1086
+ result = await service.skip(owner, action.backend);
1087
+ else if (action.operation === "cancel")
1088
+ result = await service.cancel(owner);
1089
+ else
1090
+ result = await service.refresh(owner, true);
1091
+ }
1092
+ if (!permitWebContinuation(req, res, ctx))
1093
+ return;
1094
+ if (who() !== owner) {
1095
+ json(res, 409, { error: "signin_session_expired" });
1096
+ return;
1097
+ }
1098
+ json(res, 200, result);
1099
+ })().catch(error => {
1100
+ const code = error instanceof Error ? error.message : "";
1101
+ const safe = /^(?:signin_(?:session_expired|console_only|cleanup_failed|action_(?:stale|invalid|ended|busy))|invalid_signin_(?:action|checklist))$/.test(code) ? code : "signin_failed";
1102
+ if (!res.destroyed && !res.writableEnded)
1103
+ json(res, safe === "signin_cleanup_failed" || safe === "signin_failed" ? 503 : 409, { error: safe });
1104
+ });
1105
+ return true;
1106
+ }
785
1107
  /**
786
1108
  * #2233: signing a backend in from the dashboard (web-backend-login.ts). A signed-in admin session on the local surface
787
1109
  * only — never the CLI's header token (an agent's), a guest, a read session or the public link (refused before the body
@@ -1311,11 +1633,13 @@ function webAuthorityOf(req, url, ctx, instance) {
1311
1633
  peer: webPeerOf(req), ...(instance !== undefined ? { instance } : {}) });
1312
1634
  }
1313
1635
  /**
1314
- * #1939 §3.4 / #2453 §0.2: where this request came from — the admin's named link by the gateway's own provenance (its
1315
- * exact hostname), else what the TCP peer and Host say (a quick tunnel's request is `lan`, and refused as public_link).
1636
+ * #1939 §3.4 / #2453 §0.2 / §0.3: where this request came from — over the public gateway, the link itself by the
1637
+ * gateway's own provenance (never a header): the admin's named tunnel on its exact hostname, else the temporary
1638
+ * quick-tunnel link. Off the gateway, what the TCP peer and Host say.
1316
1639
  */
1317
1640
  export function webPeerOf(req) {
1318
- return gatewayRequestContext(req)?.named === true ? "named_link" : classifyWebPeer(req);
1641
+ const gateway = gatewayRequestContext(req);
1642
+ return gateway ? (gateway.named === true ? "named_link" : "quick_link") : classifyWebPeer(req);
1319
1643
  }
1320
1644
  /**
1321
1645
  * #2122: what let this request in, asked now — its session (by handle, under the token it was signed in with) or the
@@ -2074,6 +2398,21 @@ export function handleWebRequest(req, res, url, ctx) {
2074
2398
  });
2075
2399
  return true;
2076
2400
  }
2401
+ // #2578: GET /ui/office-acts { acts }: the agents' live acts (office_set), read as the offices are (the public link
2402
+ // too, which has no stream); every change also comes as `office_acts` on the stream. Nothing here writes one.
2403
+ if (method === "GET" && path === "/ui/office-acts") {
2404
+ const acts = ctx.officeActs;
2405
+ if (!acts) {
2406
+ json(res, 404, { error: "Office acts are not kept on this fleet" });
2407
+ return true;
2408
+ }
2409
+ json(res, 200, { acts: acts.live(), now: Date.now() }); // now: the host's clock, for the page's offset
2410
+ return true;
2411
+ }
2412
+ if (path === "/ui/office-skins" || path.startsWith("/ui/office-skins/"))
2413
+ return handleOfficeSkins(req, res, url, ctx, path, method);
2414
+ if (path === "/ui/office-share" || path.startsWith("/ui/office-share/"))
2415
+ return handleOfficeShare(req, res, url, ctx, path, method);
2077
2416
  // ── #1630: avatars and Discord server emoji, same-origin (the page never loads a platform's CDN; img-src stays 'self') ──
2078
2417
  const imageMatch = path.match(/^\/ui\/img\/(bot|avatar|emoji)\/([^/]+)$/);
2079
2418
  if (method === "GET" && imageMatch) {
@@ -2422,6 +2761,8 @@ export function handleWebRequest(req, res, url, ctx) {
2422
2761
  if (path === "/ui/effect-trials" || path.startsWith("/ui/effect-trials/"))
2423
2762
  return handleEffectTrials(req, res, url, ctx, path, method);
2424
2763
  // #2233: backend sign-in — absent from the public manifest and the guest table, and refused here too.
2764
+ if (path === "/ui/browser-backend-signins")
2765
+ return handleBrowserBackendSignIns(req, res, url, ctx);
2425
2766
  if (path === "/ui/backend-login" || path.startsWith("/ui/backend-login/"))
2426
2767
  return handleBackendLogin(req, res, url, ctx, path, method);
2427
2768
  if (path === "/ui/effects" || path.startsWith("/ui/effects/"))
@@ -3268,10 +3609,10 @@ export function handleWebRequest(req, res, url, ctx) {
3268
3609
  return [403, "usage_report_not_over_public_link"];
3269
3610
  return null;
3270
3611
  };
3271
- // Over the public link, where to turn it on: Settings → Web on the dashboard reached directly (this computer's own
3612
+ // Over the public link, where to turn it on: Settings → Web dashboard on the dashboard reached directly (this computer's own
3272
3613
  // address — the public link's Settings only reads). Never the public origin's own Settings, which cannot change it.
3273
3614
  const answer = ([status, error]) => json(res, status, {
3274
- error, ...(status === 403 ? { settings: `http://localhost:${ctx.fleetConfig?.health_port ?? 19280}/settings/web` } : {})
3615
+ error, ...(status === 403 ? { settings: `http://localhost:${ctx.fleetConfig?.health_port ?? 19280}/settings/web-dashboard` } : {})
3275
3616
  });
3276
3617
  const first = refusal();
3277
3618
  if (first) {
@@ -3866,6 +4207,11 @@ function handleUpload(req, res, url, ctx) {
3866
4207
  // staging a file never wakes it. A stopped or deleted recipient must not regain an upload id.
3867
4208
  const deliverable = () => uploadRecipient(ctx, instance);
3868
4209
  const gone = () => drop(() => json(res, 404, { error: `Instance not found: ${instance}` }));
4210
+ // #2366: a guest's scope (and whether guests may chat) is asked again at each commit point — narrowed, revoked or made
4211
+ // read-only while the body was arriving, the upload is refused and its part file removed, as it would have been at
4212
+ // the start. Read now: the scope is never stored (guestRequestOf's scope() is the fleet's guestScope as it is now).
4213
+ const guestNow = () => guestChatRefusal(guestRequestOf(req), instance);
4214
+ const refuseGuest = (r) => drop(() => json(res, r.status, { error: r.error }));
3869
4215
  req.on("end", () => {
3870
4216
  if (over)
3871
4217
  return;
@@ -3880,6 +4226,11 @@ function handleUpload(req, res, url, ctx) {
3880
4226
  drop(() => json(res, 415, { error: "the file is empty" }));
3881
4227
  return;
3882
4228
  }
4229
+ const refusedAtEnd = guestNow();
4230
+ if (refusedAtEnd) {
4231
+ refuseGuest(refusedAtEnd);
4232
+ return;
4233
+ }
3883
4234
  if (!deliverable()) {
3884
4235
  gone();
3885
4236
  return;
@@ -3892,6 +4243,11 @@ function handleUpload(req, res, url, ctx) {
3892
4243
  drop();
3893
4244
  return;
3894
4245
  }
4246
+ const refusedAtFinish = guestNow(); // #2366: and the guest's scope may have changed in it
4247
+ if (refusedAtFinish) {
4248
+ refuseGuest(refusedAtFinish);
4249
+ return;
4250
+ }
3895
4251
  if (!deliverable()) {
3896
4252
  gone();
3897
4253
  return;