@songsid/agend 2.3.0-beta.21 → 2.3.0-beta.23
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.
- package/README.md +2 -2
- package/README.zh-TW.md +2 -2
- package/dist/agent-cli.js +4 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +9 -0
- package/dist/agent-endpoint.js +15 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +5 -0
- package/dist/agent-whats-new.js +16 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +7 -0
- package/dist/auth-api.js +10 -3
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex-package.d.ts +30 -0
- package/dist/backend/codex-package.js +133 -0
- package/dist/backend/codex-package.js.map +1 -0
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +9 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +56 -3
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +9 -1
- package/dist/backend/kiro.js +102 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode-permission.js +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js +2 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/binary-probe.js +1 -1
- package/dist/bug-report-cli.js +1 -1
- package/dist/bundled-skills.d.ts +7 -3
- package/dist/bundled-skills.js +16 -7
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +3 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +12 -4
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +3 -1
- package/dist/channel/mcp-tools.js +38 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/child-process.d.ts +21 -0
- package/dist/child-process.js +54 -0
- package/dist/child-process.js.map +1 -0
- package/dist/cli.js +167 -182
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +28 -2
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +16 -2
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +14 -0
- package/dist/daemon.js +45 -8
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.js +1 -1
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -1
- package/dist/export-import.js +1 -1
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +73 -2
- package/dist/fleet-manager.js +400 -33
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.d.ts +5 -5
- package/dist/full-restart.js +5 -5
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +8 -9
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +19 -4
- package/dist/general-knowledge/steering/core-rules.md +1 -1
- package/dist/instance-config-impact.js +13 -3
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +6 -6
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +2 -1
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +137 -10
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.js +1 -1
- package/dist/login-shell.js +3 -1
- package/dist/login-shell.js.map +1 -1
- package/dist/ls-rows.js +1 -1
- package/dist/needs-you-hub.d.ts +98 -1
- package/dist/needs-you-hub.js +313 -44
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +5 -2
- package/dist/needs-you-live.js +6 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +58 -3
- package/dist/needs-you.js +36 -3
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.js +25 -10
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +3 -3
- package/dist/provider-secret-registry.d.ts +5 -3
- package/dist/provider-secret-registry.js +8 -2
- package/dist/provider-secret-registry.js.map +1 -1
- package/dist/public-link-progress.d.ts +12 -1
- package/dist/public-link-progress.js +5 -10
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +11 -0
- package/dist/public-tunnel-config.js +68 -0
- package/dist/public-tunnel-config.js.map +1 -0
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +3 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +13 -2
- package/dist/public-web-link.js +37 -10
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart.js +14 -8
- package/dist/quickstart.js.map +1 -1
- package/dist/resource-report.js +1 -1
- package/dist/scheduler/task-board.d.ts +41 -1
- package/dist/scheduler/task-board.js +150 -4
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.js +2 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/secret-store.d.ts +2 -0
- package/dist/secret-store.js +21 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/service-installer.d.ts +69 -0
- package/dist/service-installer.js +181 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +44 -0
- package/dist/service-owner.js +48 -0
- package/dist/service-owner.js.map +1 -0
- package/dist/settings-api.d.ts +5 -0
- package/dist/settings-api.js +32 -10
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +86 -3
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +6 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +2 -0
- package/dist/settings-http-confirmation.js +4 -3
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-request-capability.d.ts +4 -1
- package/dist/settings-request-capability.js +11 -3
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/setup-host.js +1 -1
- package/dist/setup-wizard.js +1 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-summary.d.ts +101 -0
- package/dist/task-board-summary.js +177 -0
- package/dist/task-board-summary.js.map +1 -0
- package/dist/task-board-waker.d.ts +8 -1
- package/dist/task-board-waker.js +15 -6
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.js +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +5 -1
- package/dist/tmux-exec-helper.js +13 -21
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.js +1 -1
- package/dist/tmux-read.js +1 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/topic-commands.d.ts +3 -0
- package/dist/topic-commands.js +29 -18
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +5 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +2 -0
- package/dist/transcript-sources.js +4 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js +3 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -16
- package/dist/tunnel/cloudflared.js +65 -13
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +13 -0
- package/dist/tunnel/named-diagnostics.js +52 -0
- package/dist/tunnel/named-diagnostics.js.map +1 -0
- package/dist/tunnel/types.d.ts +2 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/ui/app-effects.js +10 -4
- package/dist/ui/app.html +2 -0
- package/dist/ui/chat-render.js +39 -0
- package/dist/ui/chat-store.js +8 -0
- package/dist/ui/chat-strings.js +5 -2
- package/dist/ui/chat-thread.js +15 -8
- package/dist/ui/effect-trial-offer.js +2 -31
- package/dist/ui/effects-waker.js +2 -70
- package/dist/ui/fleet-canvas.js +14 -6
- package/dist/ui/fleet-route-scene.js +86 -21
- package/dist/ui/fleet-task-card.js +5 -5
- package/dist/ui/fleet-task-common.js +41 -7
- package/dist/ui/fleet-task-work.js +107 -0
- package/dist/ui/fleet-tasks.js +46 -12
- package/dist/ui/fleet-traffic.js +57 -10
- package/dist/ui/icons/icon-192.png +0 -0
- package/dist/ui/icons/icon-512.png +0 -0
- package/dist/ui/image-lightbox.js +39 -1
- package/dist/ui/image-zoom.js +242 -0
- package/dist/ui/panel-chat.js +24 -13
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +6 -2
- package/dist/ui/panel-needs.js +21 -6
- package/dist/ui/panel-settings.js +90 -15
- package/dist/ui/preview-panel.js +2 -1
- package/dist/ui/search-dialog.js +2 -1
- package/dist/ui/settings-appearance.js +159 -9
- package/dist/ui/settings-dialogs.js +33 -17
- package/dist/ui/settings-discord.js +6 -2
- package/dist/ui/settings-guests.js +2 -1
- package/dist/ui/settings-learn.js +1 -0
- package/dist/ui/settings-model.js +2 -11
- package/dist/ui/settings-mods.js +6 -3
- package/dist/ui/settings-strings.js +80 -0
- package/dist/ui/settings-token.js +3 -1
- package/dist/ui/settings-web-authority.js +6 -3
- package/dist/ui/settings-wizard.js +13 -7
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +2 -2
- package/dist/ui/shared/app-store.js +2 -2
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +125 -28
- package/dist/ui/shared/app.js +5 -1
- package/dist/ui/shared/effect-app-runtime.js +7 -5
- package/dist/ui/shared/effect-chat-runtime.js +2 -7
- package/dist/ui/shared/effect-cursor.js +3 -2
- package/dist/ui/shared/effect-display-metadata.js +39 -0
- package/dist/ui/shared/effect-registry.js +4 -2
- package/dist/ui/shared/effect-runtime-loader.js +3 -1
- package/dist/ui/shared/effect-stock-trust.js +1 -1
- package/dist/ui/shared/feature-flags.js +127 -0
- package/dist/ui/shared/identity-color.js +6 -3
- package/dist/ui/shared/instance-nav.js +3 -2
- package/dist/ui/shared/text-undo.js +210 -0
- package/dist/ui/shared/ui-dialog.js +4 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/signin.html +2 -0
- package/dist/update-dispatch.js +1 -1
- package/dist/update-launch.d.ts +1 -1
- package/dist/update-launch.js +1 -2
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/providers.js +1 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +1 -1
- package/dist/web-api.d.ts +12 -2
- package/dist/web-api.js +266 -18
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon-worker.d.ts +1 -0
- package/dist/web-app-icon-worker.js +15 -0
- package/dist/web-app-icon-worker.js.map +1 -0
- package/dist/web-app-icon.d.ts +107 -0
- package/dist/web-app-icon.js +256 -0
- package/dist/web-app-icon.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +1 -0
- package/dist/web-appearance-tools.js +7 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance.d.ts +4 -10
- package/dist/web-appearance.js +4 -3
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-chat-history.d.ts +37 -0
- package/dist/web-chat-history.js +57 -2
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-experimental-http.d.ts +2 -0
- package/dist/web-experimental-http.js +12 -0
- package/dist/web-experimental-http.js.map +1 -0
- package/dist/web-experimental.d.ts +11 -0
- package/dist/web-experimental.js +49 -0
- package/dist/web-experimental.js.map +1 -0
- package/dist/web-guest-access.d.ts +1 -0
- package/dist/web-guest-access.js +11 -3
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-icons.d.ts +33 -1
- package/dist/web-icons.js +83 -11
- package/dist/web-icons.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -0
- package/dist/web-mod-store.js +10 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-png.d.ts +29 -0
- package/dist/web-png.js +321 -0
- package/dist/web-png.js.map +1 -0
- package/dist/web-preview.d.ts +2 -0
- package/dist/web-preview.js +2 -0
- package/dist/web-preview.js.map +1 -1
- package/dist/web-reactions.d.ts +34 -0
- package/dist/web-reactions.js +47 -0
- package/dist/web-reactions.js.map +1 -0
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +5 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +1 -0
- package/dist/web-settings-tools.js +14 -10
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal.js +1 -1
- package/dist/web-theme-store.d.ts +2 -2
- package/dist/web-theme-store.js +6 -3
- package/dist/web-theme-store.js.map +1 -1
- package/package.json +1 -1
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";
|
|
@@ -5,6 +7,8 @@ import { withSpawnHoldReply } from "./spawn-hold-reply.js";
|
|
|
5
7
|
import { binaryProbe } from "./binary-probe.js";
|
|
6
8
|
import { settingsRequestExecution, settingsWrite, isSettingsReplay } from "./settings-request-capability.js";
|
|
7
9
|
import { readBoundedWebBody } from "./web-body.js";
|
|
10
|
+
import { APP_ICON_MAX_BYTES } from "./web-app-icon.js";
|
|
11
|
+
import { withAppIconLinks } from "./web-icons.js";
|
|
8
12
|
import { MOD_MAX_BYTES } from "./web-mod-bundle.js";
|
|
9
13
|
import { buildModBundle } from "./web-mod-export.js";
|
|
10
14
|
import { AGENT_WIDGET_SLOTS, checkWidgetData, checkWidgetSpec } from "./web-widget.js";
|
|
@@ -36,7 +40,7 @@ import { readStatuslineModel, resolveInstanceContext } from "./topic-commands.js
|
|
|
36
40
|
import { z } from "zod";
|
|
37
41
|
import { evaluateWebRequest, isPassiveWebRead, isSecureRequest, isWebRequestAuthorized, WEB_TOKEN_INVALID_MESSAGE } from "./web-auth.js";
|
|
38
42
|
import { dashboardOriginFor } from "./web-preview.js";
|
|
39
|
-
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";
|
|
40
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";
|
|
41
45
|
import { getAgendHome } from "./paths.js";
|
|
42
46
|
import { OPERATOR, isOwnerEffectKey } from "./web-prefs.js";
|
|
@@ -121,11 +125,20 @@ const CardMoveSchema = z.object({
|
|
|
121
125
|
version: z.number().int().optional(),
|
|
122
126
|
}).strict();
|
|
123
127
|
const CardReorderSchema = z.object({ after: CardRef.nullable().optional(), before: CardRef.nullable().optional() }).strict();
|
|
124
|
-
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
|
+
}
|
|
125
136
|
const ProjectPatchSchema = z.object({
|
|
126
137
|
name: z.string().max(256).optional(),
|
|
127
138
|
description: z.string().max(BOARD_TEXT).optional(),
|
|
128
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(),
|
|
129
142
|
}).strict();
|
|
130
143
|
const TeamCreateSchema = z.object({
|
|
131
144
|
name: z.string().min(1).max(128).regex(/^[A-Za-z0-9._-]+$/),
|
|
@@ -167,6 +180,68 @@ const safeInstanceSegment = (name) => /^[^\\/\x00]+$/.test(name) && name !== "."
|
|
|
167
180
|
*/
|
|
168
181
|
/** #1920 phase 1b: the open streams that are a guest's, and whose — each event is projected for them (guestFrameData). */
|
|
169
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
|
+
}
|
|
170
245
|
/**
|
|
171
246
|
* #1920 §5.2: one event for the guests' streams only, each as projected for it — a message that has just become what
|
|
172
247
|
* its channel saw (an admin's page already has it, unchanged).
|
|
@@ -252,6 +327,8 @@ function jsonText(res, status, body) {
|
|
|
252
327
|
* board (the newest revision replaces the previous), so it holds one board's JSON at most.
|
|
253
328
|
*/
|
|
254
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();
|
|
255
332
|
/**
|
|
256
333
|
* #1630 (#1635 review): an answer that waited on something (a list, a download) is sent only if the request is still
|
|
257
334
|
* allowed — the session not revoked meanwhile, a public link's exposure still current. Otherwise 401, and nothing.
|
|
@@ -264,6 +341,104 @@ function stillAuthorized(req, res, url, ctx) {
|
|
|
264
341
|
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
265
342
|
return false;
|
|
266
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* #2308: the app icon. GET: its state, for the settings card (a signed-in person's session). PUT (one PNG, at most
|
|
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
|
|
347
|
+
* session, a guest or the public link; checked again once the body has arrived.
|
|
348
|
+
*/
|
|
349
|
+
function handleAppIcon(req, res, url, ctx, method) {
|
|
350
|
+
const store = ctx.appIcon;
|
|
351
|
+
if (!store) {
|
|
352
|
+
json(res, 404, { error: "The app icon is not kept on this fleet" });
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
const reading = method === "GET" || method === "HEAD";
|
|
356
|
+
if (!reading && method !== "PUT" && method !== "DELETE") {
|
|
357
|
+
res.setHeader("Allow", "GET, PUT, DELETE");
|
|
358
|
+
json(res, 405, { error: "method not allowed" });
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
if (gatewayRequestContext(req)) {
|
|
362
|
+
json(res, reading ? 404 : 403, { error: "app_icon_not_over_public_link" });
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
const who = () => {
|
|
366
|
+
const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
367
|
+
if (verdict.kind !== "allow")
|
|
368
|
+
return { status: 401, error: WEB_TOKEN_INVALID_MESSAGE };
|
|
369
|
+
if (verdict.via !== "session" || !verdict.session || verdict.session.tier === "guest")
|
|
370
|
+
return { status: 403, error: "app_icon_needs_a_signed_in_person" };
|
|
371
|
+
if (!reading && verdict.session.tier !== "admin")
|
|
372
|
+
return { status: 403, error: "app_icon_admin_only" };
|
|
373
|
+
return { person: webPersonKey(verdict.session.owner) };
|
|
374
|
+
};
|
|
375
|
+
const first = who();
|
|
376
|
+
if ("status" in first) {
|
|
377
|
+
json(res, first.status, { error: first.error });
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
const state = () => { const s = store.current(); return s ? { custom: true, version: s.version, updatedAt: s.updatedAt } : { custom: false, version: "default", updatedAt: null }; };
|
|
381
|
+
if (reading) {
|
|
382
|
+
json(res, 200, state());
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
if (method === "DELETE") {
|
|
386
|
+
const reset = store.reset();
|
|
387
|
+
if (!reset.ok) {
|
|
388
|
+
json(res, reset.status, { error: reset.error, ...(reset.detail ? { detail: reset.detail } : {}) });
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
json(res, 200, state());
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
// Still allowed, still answering, the store still open: asked after each await (the body; the conversion).
|
|
395
|
+
const again = () => {
|
|
396
|
+
if (!permitWebContinuation(req, res, ctx))
|
|
397
|
+
return null;
|
|
398
|
+
if (res.destroyed)
|
|
399
|
+
return null;
|
|
400
|
+
const now = who();
|
|
401
|
+
if ("status" in now) {
|
|
402
|
+
json(res, now.status, { error: now.error });
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
return now.person;
|
|
406
|
+
};
|
|
407
|
+
(async () => {
|
|
408
|
+
let bytes;
|
|
409
|
+
try {
|
|
410
|
+
bytes = await readBoundedWebBody(req, APP_ICON_MAX_BYTES);
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
json(res, 413, { error: "app_icon_too_large", detail: "at most 5 MiB" });
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!again())
|
|
417
|
+
return;
|
|
418
|
+
// #2308 review: decoding and making the sizes is seconds of CPU at worst — a worker thread's, not the fleet loop's.
|
|
419
|
+
const prepared = await store.prepare(bytes);
|
|
420
|
+
if (!prepared.ok) {
|
|
421
|
+
json(res, prepared.status, { error: prepared.error, ...(prepared.detail ? { detail: prepared.detail } : {}) });
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const person = again();
|
|
425
|
+
if (!person)
|
|
426
|
+
return;
|
|
427
|
+
const saved = store.commit(prepared.prepared, person);
|
|
428
|
+
if (!saved.ok) {
|
|
429
|
+
json(res, saved.status, { error: saved.error, ...(saved.detail ? { detail: saved.detail } : {}) });
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
json(res, 200, state());
|
|
433
|
+
})().catch((err) => {
|
|
434
|
+
ctx.logger.error({ err: err.message }, "App icon request failed");
|
|
435
|
+
try {
|
|
436
|
+
json(res, 500, { error: "App icon request failed" });
|
|
437
|
+
}
|
|
438
|
+
catch { /* already answered */ }
|
|
439
|
+
});
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
267
442
|
/**
|
|
268
443
|
* #1629 r4 §3: the owner-CSS routes (see their call site). `who()`, asked first and again after any body: the person a
|
|
269
444
|
* signed-in human session is, or the status to answer.
|
|
@@ -561,6 +736,10 @@ function handleEffectTrials(req, res, url, ctx, path, method) {
|
|
|
561
736
|
json(res, now.status, { error: now.error });
|
|
562
737
|
return;
|
|
563
738
|
}
|
|
739
|
+
if (!canvasWorkerEnabled(ctx.fleetConfig?.web)) {
|
|
740
|
+
json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
564
743
|
if (now.record !== first.record || now.sessions !== first.sessions || now.actor.sessionHash !== first.actor.sessionHash
|
|
565
744
|
|| now.actor.person !== first.actor.person || now.actor.epoch !== first.actor.epoch) {
|
|
566
745
|
json(res, 409, { error: "trials_person_changed" });
|
|
@@ -796,6 +975,10 @@ function handleEffects(req, res, url, ctx, path, method) {
|
|
|
796
975
|
json(res, now.status, { error: now.error });
|
|
797
976
|
return;
|
|
798
977
|
}
|
|
978
|
+
if (!canvasWorkerEnabled(ctx.fleetConfig?.web)) {
|
|
979
|
+
json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
799
982
|
if (now.person !== first.person || now.epoch !== first.epoch) {
|
|
800
983
|
json(res, 409, { error: "effects_person_changed" });
|
|
801
984
|
return;
|
|
@@ -917,6 +1100,10 @@ function handleMods(req, res, url, ctx, path, method) {
|
|
|
917
1100
|
// So the export ACL judges what the file carries, not what was installed: it downloads only when this person may
|
|
918
1101
|
// read every effect in it (their own claim on that content, or a shared one). A file without effects is any admin's.
|
|
919
1102
|
const fx = mods.bundleEffects(fileMatch[1]);
|
|
1103
|
+
if (!canvasWorkerEnabled(ctx.fleetConfig?.web) && (fx === "unknown" || (fx?.length ?? 0) > 0)) {
|
|
1104
|
+
json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
920
1107
|
if (fx === "unknown" || (fx ?? []).some(id => !ctx.modPlacedEffect?.(first.person, id))) {
|
|
921
1108
|
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" });
|
|
922
1109
|
return true;
|
|
@@ -1047,7 +1234,7 @@ export function exportMod(ctx, person, body) {
|
|
|
1047
1234
|
// The effects the theme places travel with it, as the library keeps them (a person may export what is placed).
|
|
1048
1235
|
// Only effects this person may carry away travel (Sol's ACL); the rest are counted, never packaged.
|
|
1049
1236
|
// #2017: an older placement of an effect that doesn't play in chat stays out (a mod can't place it): counted.
|
|
1050
|
-
const effects = rec.placements.effects.flatMap(p => { const e = ctx.modPlacedEffect?.(person, p.id); if (!e || !filesPlayInChat(e.files)) {
|
|
1237
|
+
const effects = (canvasWorkerEnabled(ctx.fleetConfig?.web) ? rec.placements.effects : []).flatMap(p => { const e = ctx.modPlacedEffect?.(person, p.id); if (!e || !filesPlayInChat(e.files)) {
|
|
1051
1238
|
omitted++;
|
|
1052
1239
|
return [];
|
|
1053
1240
|
} return [{ name: e.name, files: e.files, params: p.params ?? {} }]; });
|
|
@@ -1298,6 +1485,10 @@ function statusTick(ctx, t) {
|
|
|
1298
1485
|
export function handleWebRequest(req, res, url, ctx) {
|
|
1299
1486
|
const path = url.pathname;
|
|
1300
1487
|
const method = req.method ?? "GET";
|
|
1488
|
+
if (!canvasWorkerEnabled(ctx.fleetConfig?.web) && isCanvasWorkerHttpPath(path)) {
|
|
1489
|
+
json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
|
|
1490
|
+
return true;
|
|
1491
|
+
}
|
|
1301
1492
|
// Auth check for all /ui routes. Defence in depth behind the fleet-manager
|
|
1302
1493
|
// gate: the same session cookie or header token, and an unset token closes
|
|
1303
1494
|
// the panel instead of comparing null against a missing credential.
|
|
@@ -1445,16 +1636,20 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1445
1636
|
}
|
|
1446
1637
|
json(res, 200, {
|
|
1447
1638
|
status: ctx.getUiStatus(),
|
|
1448
|
-
|
|
1639
|
+
// #2341 review: over the public link, without a summary line that names a card hidden there.
|
|
1640
|
+
messages: history ? visibleMessages(ctx, !!gatewayRequestContext(req), fresh ? history.after(0) : history.replayFor(parseLastEventId(after))) : [],
|
|
1449
1641
|
// The ticks too, so polling never has to re-read a chat's history — that read would count as the person's
|
|
1450
1642
|
// activity; this poll does not (isPassiveWebRead).
|
|
1451
1643
|
deliveries: history ? history.deliveries() : [],
|
|
1452
1644
|
// #1266: a reply's buttons that ended after the page saw the reply (the message itself is not sent again).
|
|
1453
1645
|
reply_buttons: history ? history.buttonStates() : [],
|
|
1646
|
+
// #2305: the instance's reactions, likewise (a reaction is no new message).
|
|
1647
|
+
reactions: history ? history.reactionStates() : [],
|
|
1454
1648
|
// And the fleet prompts open on the dashboard (C4): prompt events are stream-only too.
|
|
1455
1649
|
prompts: ctx.listWebPrompts?.() ?? [],
|
|
1456
|
-
// #1386: "Needs you" rides the passive channels only — no endpoint of its own to poll.
|
|
1457
|
-
|
|
1650
|
+
// #1386: "Needs you" rides the passive channels only — no endpoint of its own to poll. Over the public link,
|
|
1651
|
+
// without the task board's hidden cards (#2306 review).
|
|
1652
|
+
needs: visibleNeeds(ctx, !!gatewayRequestContext(req), ctx.needsYouItems?.() ?? []),
|
|
1458
1653
|
// #1577: the newest message this answer accounts for — after a restart, the last restored one until this process
|
|
1459
1654
|
// records its own (`<boot>-0` would be after(0): the whole restored history again on every poll).
|
|
1460
1655
|
cursor: history ? history.pollCursor() : null,
|
|
@@ -1570,7 +1765,8 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1570
1765
|
}
|
|
1571
1766
|
// #1920 §5.2: a guest gets what the channel showed of it, and its own messages (the gate checked the scope).
|
|
1572
1767
|
const guest = guestRequestOf(req);
|
|
1573
|
-
const messages = !ctx.webChatHistory ? [] : guest ? guestHistory(ctx.webChatHistory, instance, limit, guest, guest.scope())
|
|
1768
|
+
const messages = !ctx.webChatHistory ? [] : guest ? guestHistory(ctx.webChatHistory, instance, limit, guest, guest.scope())
|
|
1769
|
+
: visibleMessages(ctx, !!gatewayRequestContext(req), ctx.webChatHistory.list(instance, limit));
|
|
1574
1770
|
json(res, 200, { messages, boot: ctx.webChatHistory?.boot ?? null, lastId: ctx.webChatHistory?.lastId ?? 0 });
|
|
1575
1771
|
return true;
|
|
1576
1772
|
}
|
|
@@ -2064,6 +2260,10 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
2064
2260
|
return;
|
|
2065
2261
|
}
|
|
2066
2262
|
const personal = [...parsed.patch.keys()].some(isOwnerEffectKey);
|
|
2263
|
+
if (personal && !canvasWorkerEnabled(ctx.fleetConfig?.web)) {
|
|
2264
|
+
json(res, 403, { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE });
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2067
2267
|
if (personal && now.person === null) {
|
|
2068
2268
|
json(res, 403, { error: "prefs_need_a_signed_in_person" });
|
|
2069
2269
|
return;
|
|
@@ -2103,6 +2303,9 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
2103
2303
|
if (path === "/ui/owner-css.css" || path === "/ui/owner-css" || path.startsWith("/ui/owner-css/")) {
|
|
2104
2304
|
return handleOwnerCss(req, res, url, ctx, path, method);
|
|
2105
2305
|
}
|
|
2306
|
+
// #2308: the app icon (handleAppIcon: its state for a signed-in person; changing it, an admin's session only).
|
|
2307
|
+
if (path === "/ui/app-icon")
|
|
2308
|
+
return handleAppIcon(req, res, url, ctx, method);
|
|
2106
2309
|
// #1629 r4-5: mods — a person's import, uninstall and export (handleMods: sessions only, never the public link).
|
|
2107
2310
|
if (path === "/ui/mods" || path.startsWith("/ui/mods/"))
|
|
2108
2311
|
return handleMods(req, res, url, ctx, path, method);
|
|
@@ -2537,7 +2740,7 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
2537
2740
|
}
|
|
2538
2741
|
// ── Task board (scheduler/task-board.ts) ──────────────
|
|
2539
2742
|
// Reads are the session's; every write is the signed-in person's, through the same rules an instance's tools use.
|
|
2540
|
-
if (path === "/ui/tasks/board" || path === "/ui/tasks/search" || path.startsWith("/ui/tasks/cards") || path.startsWith("/ui/tasks/projects")) {
|
|
2743
|
+
if (path === "/ui/tasks/board" || path === "/ui/tasks/search" || path === "/ui/tasks/work" || path.startsWith("/ui/tasks/cards") || path.startsWith("/ui/tasks/projects")) {
|
|
2541
2744
|
const board = ctx.scheduler?.db.board;
|
|
2542
2745
|
if (!board) {
|
|
2543
2746
|
json(res, 503, { error: "The task board is not available" });
|
|
@@ -2551,9 +2754,7 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
2551
2754
|
return true;
|
|
2552
2755
|
}
|
|
2553
2756
|
// …nor shows the cards of instances kept out of sight (cross_instance_visibility: hidden).
|
|
2554
|
-
const
|
|
2555
|
-
const hiddenName = (n) => !!gateway && typeof n === "string" && !!n && !!ctx.fleetConfig?.instances?.[n] && crossInstanceVisibility(fleet, n) === "hidden";
|
|
2556
|
-
const hidden = (c) => [c.assignee, c.createdBy, c.dispatcher?.name].some(hiddenName);
|
|
2757
|
+
const { hiddenName, hidden } = boardHiding(ctx, !!gateway);
|
|
2557
2758
|
// #2303 review: what a visible card shows of the cards around it follows the same rule — its children, its main
|
|
2558
2759
|
// task (a search hit's too) — and so do the words and the history a hidden instance left on it.
|
|
2559
2760
|
// …and a hidden instance's words on a visible card are not counted, named or found there (#2304 review).
|
|
@@ -2590,12 +2791,53 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
2590
2791
|
return true;
|
|
2591
2792
|
}
|
|
2592
2793
|
const cards = board.boardCards(undefined, allDone).filter((c) => !hidden(c));
|
|
2593
|
-
|
|
2794
|
+
// #2352: over the public link a hidden lead is not named (the project reads as having none).
|
|
2795
|
+
const projects = board.listProjects(true).map((p) => (p.leadInstance && hiddenName(p.leadInstance) ? { ...p, leadInstance: null } : p));
|
|
2796
|
+
// #2355 review: the names a project's lead may be — the fleet's instances (a Classic-only room is not one); over the
|
|
2797
|
+
// public link, not a hidden one.
|
|
2798
|
+
const leads = Object.keys(ctx.fleetConfig?.instances ?? {}).filter((n) => !hiddenName(n)).sort();
|
|
2799
|
+
const body = JSON.stringify({ rev, cards: board.views(cards, scope), projects, leads, stats: board.projectStats() });
|
|
2594
2800
|
if (key)
|
|
2595
2801
|
boardBodies.set(board, { key, body });
|
|
2596
2802
|
jsonText(res, 200, body);
|
|
2597
2803
|
return true;
|
|
2598
2804
|
}
|
|
2805
|
+
if (method === "GET" && path === "/ui/tasks/work") {
|
|
2806
|
+
// #2246 step 3: each instance's work on the board — what its `task mine` reads (myWork) — for the board's
|
|
2807
|
+
// Instance list and an instance's Details page (`?instance=`): the fleet's instances and anyone else a live
|
|
2808
|
+
// card is assigned to. Over the public link, not those kept out of sight, nor a card that names one. Every
|
|
2809
|
+
// name's work is one workOf() read and the cards one views() read, whatever the number of instances (#2316
|
|
2810
|
+
// review); on the dashboard the answer is built once per board revision and the names asked for, and shared
|
|
2811
|
+
// by every open drawer (the public link's is filtered per request, as the board's).
|
|
2812
|
+
const only = url.searchParams.get("instance");
|
|
2813
|
+
const names = (only ? [only] : [...new Set([...Object.keys(ctx.fleetConfig?.instances ?? {}), ...board.liveAssignees()])]).filter((n) => !hiddenName(n));
|
|
2814
|
+
const key = gateway ? null : `${board.revision()}:${only === null ? "*" : `=${only}`}:${names.join("\u0000")}`;
|
|
2815
|
+
const kept = key ? workBodies.get(board) : undefined;
|
|
2816
|
+
if (kept && kept.key === key) {
|
|
2817
|
+
jsonText(res, 200, kept.body);
|
|
2818
|
+
return true;
|
|
2819
|
+
}
|
|
2820
|
+
const byName = board.workOf(names);
|
|
2821
|
+
const works = names.map((name) => ({ name, w: byName.get(name) }));
|
|
2822
|
+
const all = new Map();
|
|
2823
|
+
for (const { w } of works)
|
|
2824
|
+
for (const c of [...w.doing, ...w.blocked, ...w.review, ...w.ready, ...w.waiting.map((x) => x.card), ...w.mains.map((x) => x.card)])
|
|
2825
|
+
if (!hidden(c))
|
|
2826
|
+
all.set(c.uuid, c);
|
|
2827
|
+
const viewOf = new Map(board.views([...all.values()], scope).map((v) => [v.uuid, v]));
|
|
2828
|
+
const drawn = (cards) => cards.flatMap((c) => (viewOf.has(c.uuid) ? [viewOf.get(c.uuid)] : []));
|
|
2829
|
+
const codes = (cards) => cards.filter((x) => all.has(x.card.uuid)).map((x) => x.card.code);
|
|
2830
|
+
const body = JSON.stringify({ instances: works.map(({ name, w }) => ({
|
|
2831
|
+
name,
|
|
2832
|
+
doing: drawn(w.doing), blocked: drawn(w.blocked), review: drawn(w.review), ready: drawn(w.ready),
|
|
2833
|
+
waiting: drawn(w.waiting.map((x) => x.card)), mains: drawn(w.mains.map((x) => x.card)),
|
|
2834
|
+
sentBack: codes(w.sentBack), newComments: codes(w.newComments),
|
|
2835
|
+
})) });
|
|
2836
|
+
if (key)
|
|
2837
|
+
workBodies.set(board, { key, body });
|
|
2838
|
+
jsonText(res, 200, body);
|
|
2839
|
+
return true;
|
|
2840
|
+
}
|
|
2599
2841
|
if (method === "GET" && path === "/ui/tasks/search") {
|
|
2600
2842
|
const names = new Map(board.listProjects(true).map((p) => [p.id, p.name]));
|
|
2601
2843
|
const hits = board.search(url.searchParams.get("q") ?? "", Number(url.searchParams.get("limit")) || 20, scope).filter((h) => !hidden(h.card));
|
|
@@ -2644,11 +2886,11 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
2644
2886
|
return true;
|
|
2645
2887
|
}
|
|
2646
2888
|
if (method === "POST" && path === "/ui/tasks/projects") {
|
|
2647
|
-
boardWrite(req, res, url, ctx, board, ProjectCreateSchema, (input, person) => ({ project: board.createProject(person, input) })
|
|
2889
|
+
boardWrite(req, res, url, ctx, board, ProjectCreateSchema, (input, person) => { checkLeadIsInstance(ctx, input.leadInstance); return { project: board.createProject(person, input) }; });
|
|
2648
2890
|
return true;
|
|
2649
2891
|
}
|
|
2650
2892
|
if (method === "POST" && project) {
|
|
2651
|
-
boardWrite(req, res, url, ctx, board, ProjectPatchSchema, (patch, person) => ({ project: board.updateProject(person, Number(project[1]), patch) })
|
|
2893
|
+
boardWrite(req, res, url, ctx, board, ProjectPatchSchema, (patch, person) => { checkLeadIsInstance(ctx, patch.leadInstance); return { project: board.updateProject(person, Number(project[1]), patch) }; });
|
|
2652
2894
|
return true;
|
|
2653
2895
|
}
|
|
2654
2896
|
}
|
|
@@ -3296,9 +3538,10 @@ function handleSendMessage(req, res, ctx) {
|
|
|
3296
3538
|
// the fleet-wide switch is off for a room that is unset) resolved at send time inside sendClassicWebEcho.
|
|
3297
3539
|
const canClassicEcho = ctx.isClassicInstance?.(instance) === true && !!ctx.sendClassicWebEcho;
|
|
3298
3540
|
const settleClassicEcho = canClassicEcho ? ctx.reserveWebChannelEcho?.(instance, async () => {
|
|
3299
|
-
const
|
|
3541
|
+
const copies = [];
|
|
3542
|
+
const posted = await ctx.sendClassicWebEcho(instance, formatWebChannelEcho(person.name, preview, t("web.echo_full_text"), { guest: !!guest }), person.name, copies);
|
|
3300
3543
|
if (posted > 0)
|
|
3301
|
-
ctx.webMessageInChannel?.(instance, messageId); // #1920 §5.2: the room saw it
|
|
3544
|
+
ctx.webMessageInChannel?.(instance, messageId, copies); // #1920 §5.2: the room saw it (#2305: its copies)
|
|
3302
3545
|
}) : undefined;
|
|
3303
3546
|
const settleEcho = canEcho ? ctx.reserveWebChannelEcho?.(instance, async () => {
|
|
3304
3547
|
// A replacement adapter or edited binding is not the route we reserved.
|
|
@@ -3307,7 +3550,11 @@ function handleSendMessage(req, res, ctx) {
|
|
|
3307
3550
|
|| ctx.fleetConfig?.instances[instance]?.topic_id !== topicId)
|
|
3308
3551
|
return;
|
|
3309
3552
|
const sent = await syncAdapter.sendText(String(groupId), formatWebChannelEcho(person.name, preview, t("web.echo_full_text"), { guest: !!guest }), { threadId: String(topicId), format: "text", allowedMentions: { parse: [] } });
|
|
3310
|
-
|
|
3553
|
+
const copyId = sent && typeof sent === "object" && typeof sent.messageId === "string" ? sent.messageId : "";
|
|
3554
|
+
// #1920 §5.2: the topic saw it. #2305: and where it is, for a reaction to the web message.
|
|
3555
|
+
const reacting = syncAdapter;
|
|
3556
|
+
ctx.webMessageInChannel?.(instance, messageId, copyId && typeof reacting.react === "function"
|
|
3557
|
+
? [{ adapter: reacting, chatId: String(groupId), threadId: String(topicId), messageId: copyId }] : []);
|
|
3311
3558
|
return sent;
|
|
3312
3559
|
}) : undefined;
|
|
3313
3560
|
let delivered;
|
|
@@ -3655,6 +3902,7 @@ function shellBodyTag(req, ctx, mode, viewer) {
|
|
|
3655
3902
|
+ ` data-usage-panel="${ctx.fleetConfig?.web?.usage_panel === false || viewer?.guest ? "0" : "1"}"`
|
|
3656
3903
|
// #1218: whether the Log tab is on (web.log_tab; off by default): the tab says so and offers to turn it on.
|
|
3657
3904
|
+ ` data-log-tab="${ctx.fleetConfig?.web?.log_tab === true ? "1" : "0"}"`
|
|
3905
|
+
+ ` data-canvas-worker="${canvasWorkerEnabled(ctx.fleetConfig?.web) ? "1" : "0"}"`
|
|
3658
3906
|
// #1937: the upload limits in force for this page (the config's; the public link's own cap on it) — what the chat
|
|
3659
3907
|
// checks before sending a file, and the numbers its words say. The server checks again on every upload.
|
|
3660
3908
|
+ ` data-upload-limits="${attr(JSON.stringify(uploadLimits(ctx.fleetConfig?.web, { publicLink: gatewayRequestContext(req) !== undefined })))}"`
|
|
@@ -3686,7 +3934,7 @@ export function serveAppShell(req, res, ctx, mode) {
|
|
|
3686
3934
|
const effectOrigin = gatewayRequestContext(req)?.expectedOrigin ?? dashboardOriginFor(typeof req.headers.host === "string" ? req.headers.host : undefined, isSecureRequest(req));
|
|
3687
3935
|
const effectFrame = shellEffect(req, ctx, mode) && effectOrigin ? `${effectOrigin}/effect-bootstrap` : null;
|
|
3688
3936
|
const frameSrc = [previewFrames, effectFrame].filter(Boolean).join(" ");
|
|
3689
|
-
sendPanelHtml(res, html.replace('<html lang="en">', shellHtmlTag(ctx, mode, viewer)).replace("<body>", shellBodyTag(req, ctx, mode, viewer)), 200, {}, { ...(frameSrc ? { frameSrc } : {}), mediaSrc });
|
|
3937
|
+
sendPanelHtml(res, withAppIconLinks(html).replace('<html lang="en">', shellHtmlTag(ctx, mode, viewer)).replace("<body>", shellBodyTag(req, ctx, mode, viewer)), 200, {}, { ...(frameSrc ? { frameSrc } : {}), mediaSrc });
|
|
3690
3938
|
}
|
|
3691
3939
|
catch {
|
|
3692
3940
|
json(res, 500, { error: "app.html not found" });
|