@songsid/agend 2.3.0-beta.22 → 2.3.0-beta.24

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 (298) hide show
  1. package/README.md +2 -2
  2. package/README.zh-TW.md +2 -2
  3. package/dist/agent-cli.js +4 -2
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.d.ts +1 -0
  6. package/dist/agent-endpoint.js +4 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-whats-new.d.ts +5 -0
  9. package/dist/agent-whats-new.js +16 -1
  10. package/dist/agent-whats-new.js.map +1 -1
  11. package/dist/auth-api.d.ts +7 -0
  12. package/dist/auth-api.js +54 -21
  13. package/dist/auth-api.js.map +1 -1
  14. package/dist/backend/codex-package.d.ts +30 -0
  15. package/dist/backend/codex-package.js +133 -0
  16. package/dist/backend/codex-package.js.map +1 -0
  17. package/dist/backend/codex.d.ts +2 -0
  18. package/dist/backend/codex.js +9 -2
  19. package/dist/backend/codex.js.map +1 -1
  20. package/dist/backend/grok.js +3 -1
  21. package/dist/backend/grok.js.map +1 -1
  22. package/dist/backend/kiro.d.ts +9 -1
  23. package/dist/backend/kiro.js +102 -1
  24. package/dist/backend/kiro.js.map +1 -1
  25. package/dist/backend/opencode-permission.js +1 -1
  26. package/dist/backend/opencode-permission.js.map +1 -1
  27. package/dist/backend/opencode.js +2 -1
  28. package/dist/backend/opencode.js.map +1 -1
  29. package/dist/binary-probe.js +1 -1
  30. package/dist/bug-report-cli.js +11 -10
  31. package/dist/bug-report-cli.js.map +1 -1
  32. package/dist/bug-report.js +11 -10
  33. package/dist/bug-report.js.map +1 -1
  34. package/dist/bundled-skills.d.ts +7 -3
  35. package/dist/bundled-skills.js +16 -7
  36. package/dist/bundled-skills.js.map +1 -1
  37. package/dist/channel/adapters/discord.js +3 -3
  38. package/dist/channel/adapters/discord.js.map +1 -1
  39. package/dist/channel/mcp-server.js +12 -4
  40. package/dist/channel/mcp-server.js.map +1 -1
  41. package/dist/channel/mcp-tools.d.ts +3 -1
  42. package/dist/channel/mcp-tools.js +36 -0
  43. package/dist/channel/mcp-tools.js.map +1 -1
  44. package/dist/child-process.d.ts +21 -0
  45. package/dist/child-process.js +54 -0
  46. package/dist/child-process.js.map +1 -0
  47. package/dist/cli.js +188 -204
  48. package/dist/cli.js.map +1 -1
  49. package/dist/command-table.d.ts +1 -1
  50. package/dist/command-table.js +1 -1
  51. package/dist/command-table.js.map +1 -1
  52. package/dist/config-validator.js +20 -1
  53. package/dist/config-validator.js.map +1 -1
  54. package/dist/config.js +3 -1
  55. package/dist/config.js.map +1 -1
  56. package/dist/daemon.d.ts +8 -0
  57. package/dist/daemon.js +31 -8
  58. package/dist/daemon.js.map +1 -1
  59. package/dist/darwin-memory.js +1 -1
  60. package/dist/doctor.d.ts +5 -0
  61. package/dist/doctor.js +42 -0
  62. package/dist/doctor.js.map +1 -1
  63. package/dist/export-import.js +4 -3
  64. package/dist/export-import.js.map +1 -1
  65. package/dist/fleet-level-config.d.ts +1 -1
  66. package/dist/fleet-level-config.js +2 -0
  67. package/dist/fleet-level-config.js.map +1 -1
  68. package/dist/fleet-manager.d.ts +45 -0
  69. package/dist/fleet-manager.js +324 -36
  70. package/dist/fleet-manager.js.map +1 -1
  71. package/dist/full-restart.d.ts +5 -5
  72. package/dist/full-restart.js +5 -5
  73. package/dist/full-restart.js.map +1 -1
  74. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +48 -10
  75. package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
  76. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +7 -8
  77. package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
  78. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
  79. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
  80. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +17 -4
  81. package/dist/general-knowledge/steering/core-rules.md +1 -1
  82. package/dist/instance-config-impact.js +10 -3
  83. package/dist/instance-config-impact.js.map +1 -1
  84. package/dist/instance-lifecycle.js +4 -4
  85. package/dist/locale.js +296 -4
  86. package/dist/locale.js.map +1 -1
  87. package/dist/login-flows.js +1 -1
  88. package/dist/login-shell.js +3 -1
  89. package/dist/login-shell.js.map +1 -1
  90. package/dist/ls-rows.js +1 -1
  91. package/dist/needs-you-hub.d.ts +98 -1
  92. package/dist/needs-you-hub.js +313 -44
  93. package/dist/needs-you-hub.js.map +1 -1
  94. package/dist/needs-you-live.d.ts +5 -2
  95. package/dist/needs-you-live.js +6 -3
  96. package/dist/needs-you-live.js.map +1 -1
  97. package/dist/needs-you.d.ts +58 -3
  98. package/dist/needs-you.js +36 -3
  99. package/dist/needs-you.js.map +1 -1
  100. package/dist/outbound-schemas.d.ts +3 -3
  101. package/dist/public-link-progress.d.ts +12 -1
  102. package/dist/public-link-progress.js +5 -10
  103. package/dist/public-link-progress.js.map +1 -1
  104. package/dist/public-tunnel-config.d.ts +5 -0
  105. package/dist/public-tunnel-config.js +45 -1
  106. package/dist/public-tunnel-config.js.map +1 -1
  107. package/dist/public-web-link.js +6 -5
  108. package/dist/public-web-link.js.map +1 -1
  109. package/dist/quickstart.js +15 -9
  110. package/dist/quickstart.js.map +1 -1
  111. package/dist/resource-report.js +1 -1
  112. package/dist/scheduler/task-board.d.ts +41 -1
  113. package/dist/scheduler/task-board.js +152 -5
  114. package/dist/scheduler/task-board.js.map +1 -1
  115. package/dist/sd-notify.js +2 -1
  116. package/dist/sd-notify.js.map +1 -1
  117. package/dist/service-installer.d.ts +69 -0
  118. package/dist/service-installer.js +182 -0
  119. package/dist/service-installer.js.map +1 -1
  120. package/dist/service-owner.d.ts +44 -0
  121. package/dist/service-owner.js +48 -0
  122. package/dist/service-owner.js.map +1 -0
  123. package/dist/service-plan.d.ts +8 -0
  124. package/dist/service-plan.js +13 -0
  125. package/dist/service-plan.js.map +1 -1
  126. package/dist/settings-api.d.ts +4 -0
  127. package/dist/settings-api.js +21 -3
  128. package/dist/settings-api.js.map +1 -1
  129. package/dist/settings-change.js +88 -4
  130. package/dist/settings-change.js.map +1 -1
  131. package/dist/settings-confirmation.d.ts +2 -0
  132. package/dist/settings-confirmation.js +14 -8
  133. package/dist/settings-confirmation.js.map +1 -1
  134. package/dist/settings-control.js +2 -1
  135. package/dist/settings-control.js.map +1 -1
  136. package/dist/settings-effect.js +2 -0
  137. package/dist/settings-effect.js.map +1 -1
  138. package/dist/settings-http-confirmation.d.ts +2 -0
  139. package/dist/settings-http-confirmation.js +3 -2
  140. package/dist/settings-http-confirmation.js.map +1 -1
  141. package/dist/settings-request-capability.d.ts +4 -1
  142. package/dist/settings-request-capability.js +11 -3
  143. package/dist/settings-request-capability.js.map +1 -1
  144. package/dist/setup-auth.d.ts +1 -0
  145. package/dist/setup-auth.js +2 -1
  146. package/dist/setup-auth.js.map +1 -1
  147. package/dist/setup-host.d.ts +1 -0
  148. package/dist/setup-host.js +7 -3
  149. package/dist/setup-host.js.map +1 -1
  150. package/dist/setup-wizard.js +1 -1
  151. package/dist/setup-wizard.js.map +1 -1
  152. package/dist/task-board-summary.d.ts +101 -0
  153. package/dist/task-board-summary.js +177 -0
  154. package/dist/task-board-summary.js.map +1 -0
  155. package/dist/task-board-waker.d.ts +8 -1
  156. package/dist/task-board-waker.js +15 -6
  157. package/dist/task-board-waker.js.map +1 -1
  158. package/dist/tmux-control.js +1 -1
  159. package/dist/tmux-control.js.map +1 -1
  160. package/dist/tmux-exec-helper.d.ts +13 -3
  161. package/dist/tmux-exec-helper.js +34 -29
  162. package/dist/tmux-exec-helper.js.map +1 -1
  163. package/dist/tmux-manager.js +1 -1
  164. package/dist/tmux-read.js +1 -1
  165. package/dist/tmux-read.js.map +1 -1
  166. package/dist/topic-commands.d.ts +3 -0
  167. package/dist/topic-commands.js +29 -18
  168. package/dist/topic-commands.js.map +1 -1
  169. package/dist/transcript-monitor.js +5 -0
  170. package/dist/transcript-monitor.js.map +1 -1
  171. package/dist/transcript-sources.d.ts +2 -0
  172. package/dist/transcript-sources.js +4 -0
  173. package/dist/transcript-sources.js.map +1 -1
  174. package/dist/tunnel/cloudflared-install.js +3 -1
  175. package/dist/tunnel/cloudflared-install.js.map +1 -1
  176. package/dist/tunnel/cloudflared.d.ts +1 -1
  177. package/dist/tunnel/cloudflared.js +51 -10
  178. package/dist/tunnel/cloudflared.js.map +1 -1
  179. package/dist/tunnel/named-diagnostics.d.ts +13 -0
  180. package/dist/tunnel/named-diagnostics.js +52 -0
  181. package/dist/tunnel/named-diagnostics.js.map +1 -0
  182. package/dist/tunnel/types.d.ts +2 -1
  183. package/dist/tunnel/types.js.map +1 -1
  184. package/dist/types.d.ts +9 -0
  185. package/dist/ui/app-effects.js +10 -4
  186. package/dist/ui/chat-strings.js +3 -0
  187. package/dist/ui/chat-thread.js +14 -7
  188. package/dist/ui/effect-trial-offer.js +2 -31
  189. package/dist/ui/effects-waker.js +2 -70
  190. package/dist/ui/fleet-task-card.js +5 -5
  191. package/dist/ui/fleet-task-common.js +43 -9
  192. package/dist/ui/fleet-task-work.js +107 -0
  193. package/dist/ui/fleet-tasks.js +46 -12
  194. package/dist/ui/image-lightbox.js +39 -1
  195. package/dist/ui/image-zoom.js +242 -0
  196. package/dist/ui/panel-chat.js +24 -13
  197. package/dist/ui/panel-details.js +2 -0
  198. package/dist/ui/panel-fleet.js +6 -2
  199. package/dist/ui/panel-needs.js +21 -6
  200. package/dist/ui/panel-settings.js +35 -13
  201. package/dist/ui/preview-panel.js +2 -1
  202. package/dist/ui/search-dialog.js +2 -1
  203. package/dist/ui/settings-appearance.js +44 -10
  204. package/dist/ui/settings-dialogs.js +33 -17
  205. package/dist/ui/settings-discord.js +6 -2
  206. package/dist/ui/settings-guests.js +2 -1
  207. package/dist/ui/settings-model.js +2 -11
  208. package/dist/ui/settings-mods.js +8 -3
  209. package/dist/ui/settings-strings.js +32 -0
  210. package/dist/ui/settings-token.js +3 -1
  211. package/dist/ui/settings-web-authority.js +6 -3
  212. package/dist/ui/settings-wizard.js +13 -7
  213. package/dist/ui/shared/app-i18n.js +2 -2
  214. package/dist/ui/shared/app-needs.js +2 -2
  215. package/dist/ui/shared/app-store.js +2 -2
  216. package/dist/ui/shared/app.css +93 -32
  217. package/dist/ui/shared/app.js +5 -1
  218. package/dist/ui/shared/effect-app-runtime.js +7 -5
  219. package/dist/ui/shared/effect-chat-runtime.js +2 -7
  220. package/dist/ui/shared/effect-cursor.js +3 -2
  221. package/dist/ui/shared/effect-display-metadata.js +39 -0
  222. package/dist/ui/shared/effect-registry.js +4 -2
  223. package/dist/ui/shared/effect-runtime-loader.js +3 -1
  224. package/dist/ui/shared/effect-stock-trust.js +5 -5
  225. package/dist/ui/shared/feature-flags.js +127 -0
  226. package/dist/ui/shared/identity-color.js +6 -3
  227. package/dist/ui/shared/instance-nav.js +3 -2
  228. package/dist/ui/shared/panel-view.js +14 -29
  229. package/dist/ui/shared/text-undo.js +210 -0
  230. package/dist/ui/shared/ui-dialog.js +4 -3
  231. package/dist/ui/shared/ui-icons.js +2 -0
  232. package/dist/ui/shared/usage-dialog.js +29 -8
  233. package/dist/ui/shared/view-strings.js +2 -4
  234. package/dist/ui/web-terminal/terminal.html +10 -10
  235. package/dist/ui/web-terminal/terminal.js +58 -12
  236. package/dist/update-dispatch.js +1 -1
  237. package/dist/update-launch.d.ts +1 -1
  238. package/dist/update-launch.js +1 -2
  239. package/dist/update-launch.js.map +1 -1
  240. package/dist/update-preflight.js +1 -1
  241. package/dist/usage/providers.js +1 -1
  242. package/dist/usage/providers.js.map +1 -1
  243. package/dist/view-api.js +1 -1
  244. package/dist/web-api.d.ts +6 -0
  245. package/dist/web-api.js +163 -21
  246. package/dist/web-api.js.map +1 -1
  247. package/dist/web-app-icon-worker.js +2 -1
  248. package/dist/web-app-icon-worker.js.map +1 -1
  249. package/dist/web-app-icon.d.ts +10 -8
  250. package/dist/web-app-icon.js +14 -13
  251. package/dist/web-app-icon.js.map +1 -1
  252. package/dist/web-appearance-tools.d.ts +1 -0
  253. package/dist/web-appearance-tools.js +7 -3
  254. package/dist/web-appearance-tools.js.map +1 -1
  255. package/dist/web-appearance.d.ts +4 -10
  256. package/dist/web-appearance.js +4 -3
  257. package/dist/web-appearance.js.map +1 -1
  258. package/dist/web-auth.d.ts +8 -4
  259. package/dist/web-auth.js +28 -17
  260. package/dist/web-auth.js.map +1 -1
  261. package/dist/web-chat-history.d.ts +14 -0
  262. package/dist/web-chat-history.js +26 -1
  263. package/dist/web-chat-history.js.map +1 -1
  264. package/dist/web-cookie-names.d.ts +5 -0
  265. package/dist/web-cookie-names.js +12 -0
  266. package/dist/web-cookie-names.js.map +1 -0
  267. package/dist/web-experimental-http.d.ts +2 -0
  268. package/dist/web-experimental-http.js +12 -0
  269. package/dist/web-experimental-http.js.map +1 -0
  270. package/dist/web-experimental.d.ts +11 -0
  271. package/dist/web-experimental.js +49 -0
  272. package/dist/web-experimental.js.map +1 -0
  273. package/dist/web-guest-access.d.ts +1 -0
  274. package/dist/web-guest-access.js +4 -2
  275. package/dist/web-guest-access.js.map +1 -1
  276. package/dist/web-mod-store.d.ts +2 -0
  277. package/dist/web-mod-store.js +10 -0
  278. package/dist/web-mod-store.js.map +1 -1
  279. package/dist/web-png.d.ts +4 -0
  280. package/dist/web-png.js +10 -2
  281. package/dist/web-png.js.map +1 -1
  282. package/dist/web-preview.d.ts +2 -0
  283. package/dist/web-preview.js +2 -0
  284. package/dist/web-preview.js.map +1 -1
  285. package/dist/web-returning-device.d.ts +4 -2
  286. package/dist/web-returning-device.js +12 -5
  287. package/dist/web-returning-device.js.map +1 -1
  288. package/dist/web-settings-patch.d.ts +2 -1
  289. package/dist/web-settings-patch.js +5 -1
  290. package/dist/web-settings-patch.js.map +1 -1
  291. package/dist/web-settings-tools.d.ts +1 -0
  292. package/dist/web-settings-tools.js +14 -10
  293. package/dist/web-settings-tools.js.map +1 -1
  294. package/dist/web-terminal.js +1 -1
  295. package/dist/web-theme-store.d.ts +2 -2
  296. package/dist/web-theme-store.js +6 -3
  297. package/dist/web-theme-store.js.map +1 -1
  298. package/package.json +11 -11
package/dist/web-api.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { canvasWorkerEnabled, CANVAS_WORKER_EXPERIMENTAL_MESSAGE } from "./web-experimental.js";
2
+ import { isCanvasWorkerHttpPath } from "./web-experimental-http.js";
1
3
  import { webHumanOrigin } from "./human-delivery-receipts.js";
2
4
  import { classifyWebPeer, confirmAfterBody, noAuthority } from "./authority/web-authority.js";
3
5
  import { guestChatRefusal, guestCommandText, guestDeliveries, guestFrameData, guestHistory, guestMessages, guestPollBody, guestPrefs, guestRequestOf, guestSeesMessage, guestStatus } from "./web-guest-access.js";
@@ -38,7 +40,7 @@ import { readStatuslineModel, resolveInstanceContext } from "./topic-commands.js
38
40
  import { z } from "zod";
39
41
  import { evaluateWebRequest, isPassiveWebRead, isSecureRequest, isWebRequestAuthorized, WEB_TOKEN_INVALID_MESSAGE } from "./web-auth.js";
40
42
  import { dashboardOriginFor } from "./web-preview.js";
41
- import { newWebMessageId, parseLastEventId, webChatReplyRef, webChatReplyText } from "./web-chat-history.js";
43
+ import { WEB_CHAT_CARDS_OVERFLOW, newWebMessageId, parseLastEventId, webChatReplyRef, webChatReplyText } from "./web-chat-history.js";
42
44
  import { attachmentDelivery, createPartFile, displayName, fileTooLargeText, INLINE_MIME, isFileId, MEDIA_RANGE_MAX, publicAttachment, UPLOAD_LIMITS, UPLOAD_MAX_MS, UPLOAD_STALL_MS, UploadSniffer, uploadLimits, wellFormed } from "./web-upload.js";
43
45
  import { getAgendHome } from "./paths.js";
44
46
  import { OPERATOR, isOwnerEffectKey } from "./web-prefs.js";
@@ -123,11 +125,20 @@ const CardMoveSchema = z.object({
123
125
  version: z.number().int().optional(),
124
126
  }).strict();
125
127
  const CardReorderSchema = z.object({ after: CardRef.nullable().optional(), before: CardRef.nullable().optional() }).strict();
126
- const ProjectCreateSchema = z.object({ name: z.string().max(256), description: z.string().max(BOARD_TEXT).optional() }).strict();
128
+ const ProjectCreateSchema = z.object({ name: z.string().max(256), description: z.string().max(BOARD_TEXT).optional(), leadInstance: z.string().max(256).nullable().optional() }).strict();
129
+ /** #2352: a project's lead must be one of the fleet's instances now (the board keeps any name, as for an assignee). */
130
+ function checkLeadIsInstance(ctx, lead) {
131
+ const name = lead?.trim();
132
+ if (name && !(ctx.fleetConfig?.instances && Object.hasOwn(ctx.fleetConfig.instances, name))) {
133
+ throw new TaskBoardError("invalid", `"${name.slice(0, 64)}" is not one of the fleet's instances`, "lead_not_instance");
134
+ }
135
+ }
127
136
  const ProjectPatchSchema = z.object({
128
137
  name: z.string().max(256).optional(),
129
138
  description: z.string().max(BOARD_TEXT).optional(),
130
139
  archived: z.boolean().optional(),
140
+ /** #2352: the instance whose channel lists the project's waiting cards (null or "": General). */
141
+ leadInstance: z.string().max(256).nullable().optional(),
131
142
  }).strict();
132
143
  const TeamCreateSchema = z.object({
133
144
  name: z.string().min(1).max(128).regex(/^[A-Za-z0-9._-]+$/),
@@ -169,6 +180,68 @@ const safeInstanceSegment = (name) => /^[^\\/\x00]+$/.test(name) && name !== "."
169
180
  */
170
181
  /** #1920 phase 1b: the open streams that are a guest's, and whose — each event is projected for them (guestFrameData). */
171
182
  const streamGuests = new WeakMap();
183
+ /**
184
+ * The task board's rule over the public link: an instance kept out of sight (cross_instance_visibility: hidden) is not
185
+ * shown, nor a card that names one as its assignee, creator or dispatcher. Off the link nothing is hidden. Read when
186
+ * asked: the fleet's config as it is now.
187
+ */
188
+ function boardHiding(ctx, gateway) {
189
+ const fleet = ctx.fleetConfig;
190
+ const hiddenName = (n) => gateway && typeof n === "string" && !!n && !!ctx.fleetConfig?.instances?.[n] && crossInstanceVisibility(fleet, n) === "hidden";
191
+ const hidden = (c) => [c.assignee, c.createdBy, c.dispatcher?.name].some(hiddenName);
192
+ return { hiddenName, hidden };
193
+ }
194
+ /**
195
+ * Chat history as the public link may see it (#2341 review): a status line that names board cards (the daily summary)
196
+ * is withheld while one of them is hidden there now — or no longer on the board — read when asked, so a card hidden
197
+ * after the line was written is withheld too. Off the link, as is. The cards are read in one query per answer.
198
+ */
199
+ function visibleMessages(ctx, gateway, messages) {
200
+ if (!gateway)
201
+ return messages;
202
+ // #2341 review r2: a line that named too many cards to keep them all is withheld whole.
203
+ messages = messages.filter((m) => !(m.cards ?? []).includes(WEB_CHAT_CARDS_OVERFLOW));
204
+ const named = [...new Set(messages.flatMap((m) => m.cards ?? []))];
205
+ if (!named.length)
206
+ return messages;
207
+ const board = ctx.scheduler?.db.board;
208
+ const { hidden } = boardHiding(ctx, true);
209
+ let found;
210
+ try {
211
+ found = new Map((board ? board.cardsByCode(named) : []).map((c) => [c.code, c]));
212
+ }
213
+ catch {
214
+ found = new Map();
215
+ }
216
+ const withheld = new Set(named.filter((code) => { const c = found.get(code); return !c || hidden(c); }));
217
+ return messages.filter((m) => !(m.cards ?? []).some((code) => withheld.has(code)));
218
+ }
219
+ /**
220
+ * Needs you as the public link may see it (#2306 review): a task item whose card the board hides there — or a card
221
+ * the board no longer has — is left out, with its title and question; everything else as listed. Off the link, as is.
222
+ * The link reads Needs you by /ui/poll only: its page has no stream (/ui/events answers it 404).
223
+ */
224
+ function visibleNeeds(ctx, gateway, items) {
225
+ if (!gateway)
226
+ return items;
227
+ const board = ctx.scheduler?.db.board;
228
+ const { hidden } = boardHiding(ctx, true);
229
+ return items.filter((i) => {
230
+ const item = i;
231
+ if (item.type !== "task")
232
+ return true;
233
+ const code = item.card?.code;
234
+ if (!board || typeof code !== "string")
235
+ return false;
236
+ try {
237
+ const card = board.findCard(code);
238
+ return !!card && !hidden(card);
239
+ }
240
+ catch {
241
+ return false;
242
+ }
243
+ });
244
+ }
172
245
  /**
173
246
  * #1920 §5.2: one event for the guests' streams only, each as projected for it — a message that has just become what
174
247
  * its channel saw (an admin's page already has it, unchanged).
@@ -254,6 +327,8 @@ function jsonText(res, status, body) {
254
327
  * board (the newest revision replaces the previous), so it holds one board's JSON at most.
255
328
  */
256
329
  const boardBodies = new WeakMap();
330
+ /** #2316 review: the same for who works on what (GET /ui/tasks/work), per (revision, names asked for). */
331
+ const workBodies = new WeakMap();
257
332
  /**
258
333
  * #1630 (#1635 review): an answer that waited on something (a list, a download) is sent only if the request is still
259
334
  * allowed — the session not revoked meanwhile, a public link's exposure still current. Otherwise 401, and nothing.
@@ -271,10 +346,14 @@ function stillAuthorized(req, res, url, ctx) {
271
346
  * 5 MiB) and DELETE (back to the bundled icon): a signed-in admin's session only — never the CLI's token, a read-only
272
347
  * session, a guest or the public link; checked again once the body has arrived.
273
348
  */
349
+ /** An app icon refusal as the page reads it: its code, the English detail and, when named, the detail's key (#2390). */
350
+ function appIconRefusal(r) {
351
+ return { error: r.error, ...(r.detail ? { detail: r.detail } : {}), ...(r.detail_key ? { detail_key: r.detail_key } : {}), ...(r.detail_args ? { detail_args: r.detail_args } : {}) };
352
+ }
274
353
  function handleAppIcon(req, res, url, ctx, method) {
275
354
  const store = ctx.appIcon;
276
355
  if (!store) {
277
- json(res, 404, { error: "The app icon is not kept on this fleet" });
356
+ json(res, 404, { error: "app_icon_not_kept", detail: "The app icon is not kept on this fleet" });
278
357
  return true;
279
358
  }
280
359
  const reading = method === "GET" || method === "HEAD";
@@ -310,7 +389,7 @@ function handleAppIcon(req, res, url, ctx, method) {
310
389
  if (method === "DELETE") {
311
390
  const reset = store.reset();
312
391
  if (!reset.ok) {
313
- json(res, reset.status, { error: reset.error, ...(reset.detail ? { detail: reset.detail } : {}) });
392
+ json(res, reset.status, appIconRefusal(reset));
314
393
  return true;
315
394
  }
316
395
  json(res, 200, state());
@@ -335,7 +414,7 @@ function handleAppIcon(req, res, url, ctx, method) {
335
414
  bytes = await readBoundedWebBody(req, APP_ICON_MAX_BYTES);
336
415
  }
337
416
  catch {
338
- json(res, 413, { error: "app_icon_too_large", detail: "at most 5 MiB" });
417
+ json(res, 413, { error: "app_icon_too_large", detail: "at most 5 MiB", detail_key: "max_size", detail_args: [5] });
339
418
  return;
340
419
  }
341
420
  if (!again())
@@ -343,7 +422,7 @@ function handleAppIcon(req, res, url, ctx, method) {
343
422
  // #2308 review: decoding and making the sizes is seconds of CPU at worst — a worker thread's, not the fleet loop's.
344
423
  const prepared = await store.prepare(bytes);
345
424
  if (!prepared.ok) {
346
- json(res, prepared.status, { error: prepared.error, ...(prepared.detail ? { detail: prepared.detail } : {}) });
425
+ json(res, prepared.status, appIconRefusal(prepared));
347
426
  return;
348
427
  }
349
428
  const person = again();
@@ -351,14 +430,14 @@ function handleAppIcon(req, res, url, ctx, method) {
351
430
  return;
352
431
  const saved = store.commit(prepared.prepared, person);
353
432
  if (!saved.ok) {
354
- json(res, saved.status, { error: saved.error, ...(saved.detail ? { detail: saved.detail } : {}) });
433
+ json(res, saved.status, appIconRefusal(saved));
355
434
  return;
356
435
  }
357
436
  json(res, 200, state());
358
437
  })().catch((err) => {
359
438
  ctx.logger.error({ err: err.message }, "App icon request failed");
360
439
  try {
361
- json(res, 500, { error: "App icon request failed" });
440
+ json(res, 500, { error: "app_icon_failed", detail: "App icon request failed" });
362
441
  }
363
442
  catch { /* already answered */ }
364
443
  });
@@ -661,6 +740,10 @@ function handleEffectTrials(req, res, url, ctx, path, method) {
661
740
  json(res, now.status, { error: now.error });
662
741
  return;
663
742
  }
743
+ if (!canvasWorkerEnabled(ctx.fleetConfig?.web)) {
744
+ json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
745
+ return;
746
+ }
664
747
  if (now.record !== first.record || now.sessions !== first.sessions || now.actor.sessionHash !== first.actor.sessionHash
665
748
  || now.actor.person !== first.actor.person || now.actor.epoch !== first.actor.epoch) {
666
749
  json(res, 409, { error: "trials_person_changed" });
@@ -896,6 +979,10 @@ function handleEffects(req, res, url, ctx, path, method) {
896
979
  json(res, now.status, { error: now.error });
897
980
  return;
898
981
  }
982
+ if (!canvasWorkerEnabled(ctx.fleetConfig?.web)) {
983
+ json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
984
+ return;
985
+ }
899
986
  if (now.person !== first.person || now.epoch !== first.epoch) {
900
987
  json(res, 409, { error: "effects_person_changed" });
901
988
  return;
@@ -1017,6 +1104,10 @@ function handleMods(req, res, url, ctx, path, method) {
1017
1104
  // So the export ACL judges what the file carries, not what was installed: it downloads only when this person may
1018
1105
  // read every effect in it (their own claim on that content, or a shared one). A file without effects is any admin's.
1019
1106
  const fx = mods.bundleEffects(fileMatch[1]);
1107
+ if (!canvasWorkerEnabled(ctx.fleetConfig?.web) && (fx === "unknown" || (fx?.length ?? 0) > 0)) {
1108
+ json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
1109
+ return true;
1110
+ }
1020
1111
  if (fx === "unknown" || (fx ?? []).some(id => !ctx.modPlacedEffect?.(first.person, id))) {
1021
1112
  json(res, 403, { error: "mod_effects_not_yours", detail: "this mod's file carries effects that are not yours to share: export your own look instead" });
1022
1113
  return true;
@@ -1147,7 +1238,7 @@ export function exportMod(ctx, person, body) {
1147
1238
  // The effects the theme places travel with it, as the library keeps them (a person may export what is placed).
1148
1239
  // Only effects this person may carry away travel (Sol's ACL); the rest are counted, never packaged.
1149
1240
  // #2017: an older placement of an effect that doesn't play in chat stays out (a mod can't place it): counted.
1150
- const effects = rec.placements.effects.flatMap(p => { const e = ctx.modPlacedEffect?.(person, p.id); if (!e || !filesPlayInChat(e.files)) {
1241
+ const effects = (canvasWorkerEnabled(ctx.fleetConfig?.web) ? rec.placements.effects : []).flatMap(p => { const e = ctx.modPlacedEffect?.(person, p.id); if (!e || !filesPlayInChat(e.files)) {
1151
1242
  omitted++;
1152
1243
  return [];
1153
1244
  } return [{ name: e.name, files: e.files, params: p.params ?? {} }]; });
@@ -1398,6 +1489,10 @@ function statusTick(ctx, t) {
1398
1489
  export function handleWebRequest(req, res, url, ctx) {
1399
1490
  const path = url.pathname;
1400
1491
  const method = req.method ?? "GET";
1492
+ if (!canvasWorkerEnabled(ctx.fleetConfig?.web) && isCanvasWorkerHttpPath(path)) {
1493
+ json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
1494
+ return true;
1495
+ }
1401
1496
  // Auth check for all /ui routes. Defence in depth behind the fleet-manager
1402
1497
  // gate: the same session cookie or header token, and an unset token closes
1403
1498
  // the panel instead of comparing null against a missing credential.
@@ -1545,7 +1640,8 @@ export function handleWebRequest(req, res, url, ctx) {
1545
1640
  }
1546
1641
  json(res, 200, {
1547
1642
  status: ctx.getUiStatus(),
1548
- messages: history ? (fresh ? history.after(0) : history.replayFor(parseLastEventId(after))) : [],
1643
+ // #2341 review: over the public link, without a summary line that names a card hidden there.
1644
+ messages: history ? visibleMessages(ctx, !!gatewayRequestContext(req), fresh ? history.after(0) : history.replayFor(parseLastEventId(after))) : [],
1549
1645
  // The ticks too, so polling never has to re-read a chat's history — that read would count as the person's
1550
1646
  // activity; this poll does not (isPassiveWebRead).
1551
1647
  deliveries: history ? history.deliveries() : [],
@@ -1555,8 +1651,9 @@ export function handleWebRequest(req, res, url, ctx) {
1555
1651
  reactions: history ? history.reactionStates() : [],
1556
1652
  // And the fleet prompts open on the dashboard (C4): prompt events are stream-only too.
1557
1653
  prompts: ctx.listWebPrompts?.() ?? [],
1558
- // #1386: "Needs you" rides the passive channels only — no endpoint of its own to poll.
1559
- needs: ctx.needsYouItems?.() ?? [],
1654
+ // #1386: "Needs you" rides the passive channels only — no endpoint of its own to poll. Over the public link,
1655
+ // without the task board's hidden cards (#2306 review).
1656
+ needs: visibleNeeds(ctx, !!gatewayRequestContext(req), ctx.needsYouItems?.() ?? []),
1560
1657
  // #1577: the newest message this answer accounts for — after a restart, the last restored one until this process
1561
1658
  // records its own (`<boot>-0` would be after(0): the whole restored history again on every poll).
1562
1659
  cursor: history ? history.pollCursor() : null,
@@ -1672,7 +1769,8 @@ export function handleWebRequest(req, res, url, ctx) {
1672
1769
  }
1673
1770
  // #1920 §5.2: a guest gets what the channel showed of it, and its own messages (the gate checked the scope).
1674
1771
  const guest = guestRequestOf(req);
1675
- const messages = !ctx.webChatHistory ? [] : guest ? guestHistory(ctx.webChatHistory, instance, limit, guest, guest.scope()) : ctx.webChatHistory.list(instance, limit);
1772
+ const messages = !ctx.webChatHistory ? [] : guest ? guestHistory(ctx.webChatHistory, instance, limit, guest, guest.scope())
1773
+ : visibleMessages(ctx, !!gatewayRequestContext(req), ctx.webChatHistory.list(instance, limit));
1676
1774
  json(res, 200, { messages, boot: ctx.webChatHistory?.boot ?? null, lastId: ctx.webChatHistory?.lastId ?? 0 });
1677
1775
  return true;
1678
1776
  }
@@ -2166,6 +2264,10 @@ export function handleWebRequest(req, res, url, ctx) {
2166
2264
  return;
2167
2265
  }
2168
2266
  const personal = [...parsed.patch.keys()].some(isOwnerEffectKey);
2267
+ if (personal && !canvasWorkerEnabled(ctx.fleetConfig?.web)) {
2268
+ json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
2269
+ return;
2270
+ }
2169
2271
  if (personal && now.person === null) {
2170
2272
  json(res, 403, { error: "prefs_need_a_signed_in_person" });
2171
2273
  return;
@@ -2642,10 +2744,10 @@ export function handleWebRequest(req, res, url, ctx) {
2642
2744
  }
2643
2745
  // ── Task board (scheduler/task-board.ts) ──────────────
2644
2746
  // Reads are the session's; every write is the signed-in person's, through the same rules an instance's tools use.
2645
- if (path === "/ui/tasks/board" || path === "/ui/tasks/search" || path.startsWith("/ui/tasks/cards") || path.startsWith("/ui/tasks/projects")) {
2747
+ if (path === "/ui/tasks/board" || path === "/ui/tasks/search" || path === "/ui/tasks/work" || path.startsWith("/ui/tasks/cards") || path.startsWith("/ui/tasks/projects")) {
2646
2748
  const board = ctx.scheduler?.db.board;
2647
2749
  if (!board) {
2648
- json(res, 503, { error: "The task board is not available" });
2750
+ json(res, 503, { error: "The task board is not available", code: "unavailable", key: "unavailable" });
2649
2751
  return true;
2650
2752
  }
2651
2753
  // The temporary public link reads the board and changes nothing on it: confirming, sending back, cancelling and
@@ -2656,9 +2758,7 @@ export function handleWebRequest(req, res, url, ctx) {
2656
2758
  return true;
2657
2759
  }
2658
2760
  // …nor shows the cards of instances kept out of sight (cross_instance_visibility: hidden).
2659
- const fleet = ctx.fleetConfig;
2660
- const hiddenName = (n) => !!gateway && typeof n === "string" && !!n && !!ctx.fleetConfig?.instances?.[n] && crossInstanceVisibility(fleet, n) === "hidden";
2661
- const hidden = (c) => [c.assignee, c.createdBy, c.dispatcher?.name].some(hiddenName);
2761
+ const { hiddenName, hidden } = boardHiding(ctx, !!gateway);
2662
2762
  // #2303 review: what a visible card shows of the cards around it follows the same rule — its children, its main
2663
2763
  // task (a search hit's too) — and so do the words and the history a hidden instance left on it.
2664
2764
  // …and a hidden instance's words on a visible card are not counted, named or found there (#2304 review).
@@ -2695,12 +2795,53 @@ export function handleWebRequest(req, res, url, ctx) {
2695
2795
  return true;
2696
2796
  }
2697
2797
  const cards = board.boardCards(undefined, allDone).filter((c) => !hidden(c));
2698
- const body = JSON.stringify({ rev, cards: board.views(cards, scope), projects: board.listProjects(true), stats: board.projectStats() });
2798
+ // #2352: over the public link a hidden lead is not named (the project reads as having none).
2799
+ const projects = board.listProjects(true).map((p) => (p.leadInstance && hiddenName(p.leadInstance) ? { ...p, leadInstance: null } : p));
2800
+ // #2355 review: the names a project's lead may be — the fleet's instances (a Classic-only room is not one); over the
2801
+ // public link, not a hidden one.
2802
+ const leads = Object.keys(ctx.fleetConfig?.instances ?? {}).filter((n) => !hiddenName(n)).sort();
2803
+ const body = JSON.stringify({ rev, cards: board.views(cards, scope), projects, leads, stats: board.projectStats() });
2699
2804
  if (key)
2700
2805
  boardBodies.set(board, { key, body });
2701
2806
  jsonText(res, 200, body);
2702
2807
  return true;
2703
2808
  }
2809
+ if (method === "GET" && path === "/ui/tasks/work") {
2810
+ // #2246 step 3: each instance's work on the board — what its `task mine` reads (myWork) — for the board's
2811
+ // Instance list and an instance's Details page (`?instance=`): the fleet's instances and anyone else a live
2812
+ // card is assigned to. Over the public link, not those kept out of sight, nor a card that names one. Every
2813
+ // name's work is one workOf() read and the cards one views() read, whatever the number of instances (#2316
2814
+ // review); on the dashboard the answer is built once per board revision and the names asked for, and shared
2815
+ // by every open drawer (the public link's is filtered per request, as the board's).
2816
+ const only = url.searchParams.get("instance");
2817
+ const names = (only ? [only] : [...new Set([...Object.keys(ctx.fleetConfig?.instances ?? {}), ...board.liveAssignees()])]).filter((n) => !hiddenName(n));
2818
+ const key = gateway ? null : `${board.revision()}:${only === null ? "*" : `=${only}`}:${names.join("\u0000")}`;
2819
+ const kept = key ? workBodies.get(board) : undefined;
2820
+ if (kept && kept.key === key) {
2821
+ jsonText(res, 200, kept.body);
2822
+ return true;
2823
+ }
2824
+ const byName = board.workOf(names);
2825
+ const works = names.map((name) => ({ name, w: byName.get(name) }));
2826
+ const all = new Map();
2827
+ for (const { w } of works)
2828
+ for (const c of [...w.doing, ...w.blocked, ...w.review, ...w.ready, ...w.waiting.map((x) => x.card), ...w.mains.map((x) => x.card)])
2829
+ if (!hidden(c))
2830
+ all.set(c.uuid, c);
2831
+ const viewOf = new Map(board.views([...all.values()], scope).map((v) => [v.uuid, v]));
2832
+ const drawn = (cards) => cards.flatMap((c) => (viewOf.has(c.uuid) ? [viewOf.get(c.uuid)] : []));
2833
+ const codes = (cards) => cards.filter((x) => all.has(x.card.uuid)).map((x) => x.card.code);
2834
+ const body = JSON.stringify({ instances: works.map(({ name, w }) => ({
2835
+ name,
2836
+ doing: drawn(w.doing), blocked: drawn(w.blocked), review: drawn(w.review), ready: drawn(w.ready),
2837
+ waiting: drawn(w.waiting.map((x) => x.card)), mains: drawn(w.mains.map((x) => x.card)),
2838
+ sentBack: codes(w.sentBack), newComments: codes(w.newComments),
2839
+ })) });
2840
+ if (key)
2841
+ workBodies.set(board, { key, body });
2842
+ jsonText(res, 200, body);
2843
+ return true;
2844
+ }
2704
2845
  if (method === "GET" && path === "/ui/tasks/search") {
2705
2846
  const names = new Map(board.listProjects(true).map((p) => [p.id, p.name]));
2706
2847
  const hits = board.search(url.searchParams.get("q") ?? "", Number(url.searchParams.get("limit")) || 20, scope).filter((h) => !hidden(h.card));
@@ -2749,11 +2890,11 @@ export function handleWebRequest(req, res, url, ctx) {
2749
2890
  return true;
2750
2891
  }
2751
2892
  if (method === "POST" && path === "/ui/tasks/projects") {
2752
- boardWrite(req, res, url, ctx, board, ProjectCreateSchema, (input, person) => ({ project: board.createProject(person, input) }));
2893
+ boardWrite(req, res, url, ctx, board, ProjectCreateSchema, (input, person) => { checkLeadIsInstance(ctx, input.leadInstance); return { project: board.createProject(person, input) }; });
2753
2894
  return true;
2754
2895
  }
2755
2896
  if (method === "POST" && project) {
2756
- boardWrite(req, res, url, ctx, board, ProjectPatchSchema, (patch, person) => ({ project: board.updateProject(person, Number(project[1]), patch) }));
2897
+ boardWrite(req, res, url, ctx, board, ProjectPatchSchema, (patch, person) => { checkLeadIsInstance(ctx, patch.leadInstance); return { project: board.updateProject(person, Number(project[1]), patch) }; });
2757
2898
  return true;
2758
2899
  }
2759
2900
  }
@@ -3765,6 +3906,7 @@ function shellBodyTag(req, ctx, mode, viewer) {
3765
3906
  + ` data-usage-panel="${ctx.fleetConfig?.web?.usage_panel === false || viewer?.guest ? "0" : "1"}"`
3766
3907
  // #1218: whether the Log tab is on (web.log_tab; off by default): the tab says so and offers to turn it on.
3767
3908
  + ` data-log-tab="${ctx.fleetConfig?.web?.log_tab === true ? "1" : "0"}"`
3909
+ + ` data-canvas-worker="${canvasWorkerEnabled(ctx.fleetConfig?.web) ? "1" : "0"}"`
3768
3910
  // #1937: the upload limits in force for this page (the config's; the public link's own cap on it) — what the chat
3769
3911
  // checks before sending a file, and the numbers its words say. The server checks again on every upload.
3770
3912
  + ` data-upload-limits="${attr(JSON.stringify(uploadLimits(ctx.fleetConfig?.web, { publicLink: gatewayRequestContext(req) !== undefined })))}"`