@songsid/agend 2.3.3-beta.4 → 2.3.3-beta.6

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 (250) hide show
  1. package/dist/agent-cli.js +13 -112
  2. package/dist/agent-cli.js.map +1 -1
  3. package/dist/agent-endpoint.d.ts +0 -1
  4. package/dist/agent-endpoint.js +1 -9
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-whats-new.d.ts +3 -6
  7. package/dist/agent-whats-new.js +7 -18
  8. package/dist/agent-whats-new.js.map +1 -1
  9. package/dist/auth-api.d.ts +0 -7
  10. package/dist/auth-api.js +1 -15
  11. package/dist/auth-api.js.map +1 -1
  12. package/dist/backend/claude-code.js +4 -2
  13. package/dist/backend/claude-code.js.map +1 -1
  14. package/dist/backend/codex.d.ts +1 -0
  15. package/dist/backend/codex.js +106 -15
  16. package/dist/backend/codex.js.map +1 -1
  17. package/dist/backend/grok.js +57 -11
  18. package/dist/backend/grok.js.map +1 -1
  19. package/dist/backend/types.d.ts +35 -0
  20. package/dist/backend/types.js.map +1 -1
  21. package/dist/bundled-skills.d.ts +4 -6
  22. package/dist/bundled-skills.js +10 -14
  23. package/dist/bundled-skills.js.map +1 -1
  24. package/dist/channel/mcp-server.js +5 -5
  25. package/dist/channel/mcp-server.js.map +1 -1
  26. package/dist/channel/mcp-tools.d.ts +1 -3
  27. package/dist/channel/mcp-tools.js +4 -58
  28. package/dist/channel/mcp-tools.js.map +1 -1
  29. package/dist/cli.js +3 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/config-validator.js +6 -3
  32. package/dist/config-validator.js.map +1 -1
  33. package/dist/daemon.d.ts +17 -3
  34. package/dist/daemon.js +76 -107
  35. package/dist/daemon.js.map +1 -1
  36. package/dist/dashboard-live.d.ts +1 -1
  37. package/dist/dashboard-live.js +3 -3
  38. package/dist/dashboard-live.js.map +1 -1
  39. package/dist/dashboard-permissions.d.ts +1 -1
  40. package/dist/dashboard-permissions.js +1 -3
  41. package/dist/dashboard-permissions.js.map +1 -1
  42. package/dist/fleet-manager.d.ts +31 -44
  43. package/dist/fleet-manager.js +94 -227
  44. package/dist/fleet-manager.js.map +1 -1
  45. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
  46. package/dist/general-knowledge/skills/session-management/SKILL.md +6 -0
  47. package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
  48. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +26 -63
  49. package/dist/instance-lifecycle.d.ts +6 -0
  50. package/dist/instance-lifecycle.js +20 -4
  51. package/dist/instance-lifecycle.js.map +1 -1
  52. package/dist/locale.js +14 -4
  53. package/dist/locale.js.map +1 -1
  54. package/dist/outbound-handlers.js +19 -7
  55. package/dist/outbound-handlers.js.map +1 -1
  56. package/dist/outbound-schemas.d.ts +0 -62
  57. package/dist/outbound-schemas.js +3 -42
  58. package/dist/outbound-schemas.js.map +1 -1
  59. package/dist/public-web-gateway.js +0 -9
  60. package/dist/public-web-gateway.js.map +1 -1
  61. package/dist/public-web-link.d.ts +2 -0
  62. package/dist/public-web-link.js +5 -0
  63. package/dist/public-web-link.js.map +1 -1
  64. package/dist/search/search-docs.d.ts +1 -1
  65. package/dist/search/search-docs.js +2 -2
  66. package/dist/search/search-docs.js.map +1 -1
  67. package/dist/settings-api.d.ts +6 -0
  68. package/dist/settings-api.js +14 -2
  69. package/dist/settings-api.js.map +1 -1
  70. package/dist/settings-change.js +3 -1
  71. package/dist/settings-change.js.map +1 -1
  72. package/dist/settings-confirmation.d.ts +17 -3
  73. package/dist/settings-confirmation.js +8 -5
  74. package/dist/settings-confirmation.js.map +1 -1
  75. package/dist/settings-http-confirmation.js +31 -1
  76. package/dist/settings-http-confirmation.js.map +1 -1
  77. package/dist/tool-permissions.js +3 -10
  78. package/dist/tool-permissions.js.map +1 -1
  79. package/dist/topic-commands.d.ts +27 -7
  80. package/dist/topic-commands.js +94 -36
  81. package/dist/topic-commands.js.map +1 -1
  82. package/dist/types.d.ts +2 -1
  83. package/dist/ui/chat-ambient.js +4 -4
  84. package/dist/ui/chat-commands.js +3 -2
  85. package/dist/ui/chat-store.js +4 -1
  86. package/dist/ui/chat-strings.js +12 -46
  87. package/dist/ui/chat-thread.js +51 -59
  88. package/dist/ui/fleet-office-skin.js +37 -14
  89. package/dist/ui/fleet-office.js +31 -7
  90. package/dist/ui/html-attachment.js +22 -8
  91. package/dist/ui/panel-chat.js +21 -50
  92. package/dist/ui/preview-panel.js +12 -21
  93. package/dist/ui/preview.js +10 -3
  94. package/dist/ui/search-dialog.js +30 -14
  95. package/dist/ui/settings-appearance.js +1 -3
  96. package/dist/ui/settings-confirm.js +18 -0
  97. package/dist/ui/settings-guests.js +13 -4
  98. package/dist/ui/settings-learn.js +1 -1
  99. package/dist/ui/settings-mods.js +5 -12
  100. package/dist/ui/settings-registry.js +0 -1
  101. package/dist/ui/settings-strings.js +6 -6
  102. package/dist/ui/shared/app-i18n.js +13 -2
  103. package/dist/ui/shared/app-prefs.js +17 -17
  104. package/dist/ui/shared/app-shell.js +47 -9
  105. package/dist/ui/shared/app-store.js +6 -2
  106. package/dist/ui/shared/app.css +19 -36
  107. package/dist/ui/shared/app.js +5 -4
  108. package/dist/ui/shared/instance-nav.js +9 -9
  109. package/dist/ui/shared/instance-switch.js +8 -2
  110. package/dist/ui/shared/panel-view.js +22 -8
  111. package/dist/ui/shared/view-strings.js +6 -0
  112. package/dist/usage/usage-api.d.ts +3 -0
  113. package/dist/usage/usage-api.js +5 -1
  114. package/dist/usage/usage-api.js.map +1 -1
  115. package/dist/view-api.d.ts +2 -0
  116. package/dist/view-api.js +64 -0
  117. package/dist/view-api.js.map +1 -1
  118. package/dist/web-api.d.ts +9 -17
  119. package/dist/web-api.js +255 -371
  120. package/dist/web-api.js.map +1 -1
  121. package/dist/web-appearance-tools.d.ts +0 -2
  122. package/dist/web-appearance-tools.js +2 -5
  123. package/dist/web-appearance-tools.js.map +1 -1
  124. package/dist/web-appearance-view.d.ts +0 -6
  125. package/dist/web-appearance-view.js +0 -1
  126. package/dist/web-appearance-view.js.map +1 -1
  127. package/dist/web-appearance.d.ts +14 -7
  128. package/dist/web-appearance.js +5 -6
  129. package/dist/web-appearance.js.map +1 -1
  130. package/dist/web-asset-parts.d.ts +1 -17
  131. package/dist/web-asset-parts.js +1 -15
  132. package/dist/web-asset-parts.js.map +1 -1
  133. package/dist/web-auth.js +1 -7
  134. package/dist/web-auth.js.map +1 -1
  135. package/dist/web-chat-history.d.ts +3 -7
  136. package/dist/web-chat-history.js +10 -17
  137. package/dist/web-chat-history.js.map +1 -1
  138. package/dist/web-commands.d.ts +9 -1
  139. package/dist/web-commands.js +13 -0
  140. package/dist/web-commands.js.map +1 -1
  141. package/dist/web-experimental.d.ts +2 -11
  142. package/dist/web-experimental.js +8 -36
  143. package/dist/web-experimental.js.map +1 -1
  144. package/dist/web-guest-access.d.ts +40 -2
  145. package/dist/web-guest-access.js +50 -14
  146. package/dist/web-guest-access.js.map +1 -1
  147. package/dist/web-mod-bundle.d.ts +2 -45
  148. package/dist/web-mod-bundle.js +15 -63
  149. package/dist/web-mod-bundle.js.map +1 -1
  150. package/dist/web-mod-store.d.ts +3 -48
  151. package/dist/web-mod-store.js +30 -97
  152. package/dist/web-mod-store.js.map +1 -1
  153. package/dist/web-prefs.d.ts +13 -0
  154. package/dist/web-prefs.js +38 -0
  155. package/dist/web-prefs.js.map +1 -1
  156. package/dist/web-preview.d.ts +6 -13
  157. package/dist/web-preview.js +12 -23
  158. package/dist/web-preview.js.map +1 -1
  159. package/dist/web-session.d.ts +11 -3
  160. package/dist/web-session.js +22 -11
  161. package/dist/web-session.js.map +1 -1
  162. package/dist/web-settings-patch.d.ts +1 -0
  163. package/dist/web-settings-patch.js +6 -3
  164. package/dist/web-settings-patch.js.map +1 -1
  165. package/dist/web-settings-tools.d.ts +0 -13
  166. package/dist/web-settings-tools.js +12 -84
  167. package/dist/web-settings-tools.js.map +1 -1
  168. package/package.json +1 -1
  169. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
  170. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -672
  171. package/dist/ui/app-effects.js +0 -251
  172. package/dist/ui/chat-effect-posters.js +0 -42
  173. package/dist/ui/chat-effects.js +0 -167
  174. package/dist/ui/effect-trial-controller.js +0 -124
  175. package/dist/ui/effect-trial-offer.js +0 -2
  176. package/dist/ui/effect-trial-panel.js +0 -40
  177. package/dist/ui/effects-waker.js +0 -2
  178. package/dist/ui/settings-effects.js +0 -202
  179. package/dist/ui/settings-owner-effects.js +0 -82
  180. package/dist/ui/shared/effect-app-runtime.js +0 -299
  181. package/dist/ui/shared/effect-baseline.js +0 -64
  182. package/dist/ui/shared/effect-chat-runtime.js +0 -162
  183. package/dist/ui/shared/effect-cursor.js +0 -126
  184. package/dist/ui/shared/effect-display-metadata.js +0 -39
  185. package/dist/ui/shared/effect-frame.js +0 -146
  186. package/dist/ui/shared/effect-geometry.js +0 -137
  187. package/dist/ui/shared/effect-host-style.js +0 -10
  188. package/dist/ui/shared/effect-owner-regions.js +0 -74
  189. package/dist/ui/shared/effect-owner-source.js +0 -122
  190. package/dist/ui/shared/effect-policy.js +0 -151
  191. package/dist/ui/shared/effect-poster-reader.js +0 -98
  192. package/dist/ui/shared/effect-posters.js +0 -117
  193. package/dist/ui/shared/effect-profiler.js +0 -184
  194. package/dist/ui/shared/effect-profiles.js +0 -65
  195. package/dist/ui/shared/effect-registry.js +0 -545
  196. package/dist/ui/shared/effect-runtime-loader.js +0 -236
  197. package/dist/ui/shared/effect-safety.js +0 -153
  198. package/dist/ui/shared/effect-stock-snapshot.js +0 -43
  199. package/dist/ui/shared/effect-stock-trust.js +0 -217
  200. package/dist/ui/shared/effect-trial-shadow.js +0 -62
  201. package/dist/ui/shared/effect-trial-source.js +0 -103
  202. package/dist/ui/shared/effect-trial-stage.js +0 -170
  203. package/dist/ui/shared/effect-trial.css +0 -40
  204. package/dist/ui/shared/feature-flags.js +0 -127
  205. package/dist/ui/shared/owner-effect-prefs.js +0 -117
  206. package/dist/web-effect-bootstrap.d.ts +0 -12
  207. package/dist/web-effect-bootstrap.js +0 -49
  208. package/dist/web-effect-bootstrap.js.map +0 -1
  209. package/dist/web-effect-bundle.d.ts +0 -28
  210. package/dist/web-effect-bundle.js +0 -171
  211. package/dist/web-effect-bundle.js.map +0 -1
  212. package/dist/web-effect-canonical.d.ts +0 -8
  213. package/dist/web-effect-canonical.js +0 -27
  214. package/dist/web-effect-canonical.js.map +0 -1
  215. package/dist/web-effect-library.d.ts +0 -114
  216. package/dist/web-effect-library.js +0 -530
  217. package/dist/web-effect-library.js.map +0 -1
  218. package/dist/web-effect-playback.d.ts +0 -30
  219. package/dist/web-effect-playback.js +0 -71
  220. package/dist/web-effect-playback.js.map +0 -1
  221. package/dist/web-effect-poster.d.ts +0 -8
  222. package/dist/web-effect-poster.js +0 -52
  223. package/dist/web-effect-poster.js.map +0 -1
  224. package/dist/web-effect-profiles.d.ts +0 -118
  225. package/dist/web-effect-profiles.js +0 -181
  226. package/dist/web-effect-profiles.js.map +0 -1
  227. package/dist/web-effect-service.d.ts +0 -93
  228. package/dist/web-effect-service.js +0 -335
  229. package/dist/web-effect-service.js.map +0 -1
  230. package/dist/web-effect-tools.d.ts +0 -48
  231. package/dist/web-effect-tools.js +0 -129
  232. package/dist/web-effect-tools.js.map +0 -1
  233. package/dist/web-effect-trials.d.ts +0 -86
  234. package/dist/web-effect-trials.js +0 -204
  235. package/dist/web-effect-trials.js.map +0 -1
  236. package/dist/web-effect-upload.d.ts +0 -27
  237. package/dist/web-effect-upload.js +0 -185
  238. package/dist/web-effect-upload.js.map +0 -1
  239. package/dist/web-effect-worker.d.ts +0 -8
  240. package/dist/web-effect-worker.js +0 -170
  241. package/dist/web-effect-worker.js.map +0 -1
  242. package/dist/web-effect.d.ts +0 -103
  243. package/dist/web-effect.js +0 -153
  244. package/dist/web-effect.js.map +0 -1
  245. package/dist/web-experimental-http.d.ts +0 -2
  246. package/dist/web-experimental-http.js +0 -12
  247. package/dist/web-experimental-http.js.map +0 -1
  248. package/dist/web-mod-agent.d.ts +0 -50
  249. package/dist/web-mod-agent.js +0 -207
  250. package/dist/web-mod-agent.js.map +0 -1
@@ -176,13 +176,6 @@ import { OfficeSkinStore } from "./web-office-skins.js";
176
176
  import { OfficeActs } from "./web-office-acts.js";
177
177
  import { OFFICE_PACK_TOOLS, OfficePackTools } from "./web-office-pack-tools.js";
178
178
  import { WebAppearance } from "./web-appearance.js";
179
- import { WebEffectLibrary } from "./web-effect-library.js";
180
- import { WebEffectTrials } from "./web-effect-trials.js";
181
- import { WebEffectService } from "./web-effect-service.js";
182
- import { WebEffectProfiles } from "./web-effect-profiles.js";
183
- import { WebEffectTools, WEB_EFFECT_TOOLS } from "./web-effect-tools.js";
184
- import { resolveEffectParams } from "./web-effect.js";
185
- import { cw1EffectChecker } from "./web-mod-bundle.js";
186
179
  import { effectiveAppearanceLimits } from "./web-appearance-limits.js";
187
180
  import { WEB_NAME_FALLBACK, safeWebName, validateWebName, webNameInUse, webPersonKey } from "./web-profile.js";
188
181
  import { ReplyButtonStore, parseReplyButtons, replyButtonClickText, replyButtonsFallbackText, REPLY_BUTTON_PREFIX } from "./reply-buttons.js";
@@ -195,7 +188,6 @@ import { autoIdentityColor } from "./web-identity-color.js";
195
188
  import { AppearanceTools } from "./web-appearance-tools.js";
196
189
  import { cardVersion, resolveCard } from "./web-card.js";
197
190
  import { ModStore } from "./web-mod-store.js";
198
- import { AgentMods } from "./web-mod-agent.js";
199
191
  import { downloadStickerImage } from "./channel/sticker-download.js";
200
192
  import { WebImageCache, imageMimeForFile, WEB_IMAGE_MAX_AGE_MS, WEB_IMAGE_MAX_BYTES, WEB_IMAGE_RETRY_MS } from "./web-images.js";
201
193
  import { accentColor, avatarFor, ownBotConnections } from "./instance-avatar.js";
@@ -214,7 +206,7 @@ import { crossInstanceVisibility, runVisibilityCommand, xmsgVisibility } from ".
214
206
  import { SEARCH_RETENTION_DAYS, SearchIndex } from "./search/search-index.js";
215
207
  import { chatDoc, taskDoc, xmsgDoc } from "./search/search-docs.js";
216
208
  import { WebSettingsTools } from "./web-settings-tools.js";
217
- import { bindGuestRequest, guestRouteDecision } from "./web-guest-access.js";
209
+ import { GUEST_COMMAND_RUNS, GUEST_PREF_WRITES, GUEST_SEARCHES, bindGuestRequest, guestRouteDecision } from "./web-guest-access.js";
218
210
  import { GUEST_ROOM_TICK_MS, GuestRoomMembership, guestGroupRooms } from "./web-guest-rooms.js";
219
211
  import { Delegations, TrafficLog, trafficItem } from "./fleet-traffic.js";
220
212
  import { installedChannel, updateNoticeKey } from "./update-check.js";
@@ -259,14 +251,14 @@ import { buildOrgChart } from "./web-org.js";
259
251
  import { topMemoryHogs } from "./memory-hogs.js";
260
252
  import { GENERAL_POINTING_REFUSALS, SNOWFLAKE, hasPlaceRefs, placeRef, webRoute, webTextLinks, placeSuffix, renderPlaceRefs, stripPlaceRefs, topicUrl, worldPlaceOf } from "./topic-link.js";
261
253
  import { CacheService, cacheWindow } from "./cache-service.js";
262
- import { runWebCommand } from "./web-commands.js";
254
+ import { guestWebCommands, runWebCommand } from "./web-commands.js";
263
255
  import { CompactWatch, launchIdentity } from "./compact-watch.js";
264
256
  import { agyLegacyAgentsFile, danglingSymlink, migrateAgyAgentsFile } from "./backend/antigravity.js";
265
257
  import { claudeConfigDirFor, claudeProjectKey } from "./backend/claude-code.js";
266
258
  import { sharedRolloutIndex } from "./rollout-index.js";
267
259
  import { TASK_BOARD_ON_NOTICE, dashboardRefusalIn, taskBoardSwitchedOn } from "./dashboard-permissions.js";
268
260
  import { mayUseTool, resolveToolSet, scheduleOpRefusal, toolForIpcType, toolRefusedMessage, } from "./tool-permissions.js";
269
- import { authorizeSession, decideWebGate, loadOrCreateWebToken, readWebToken, sessionCookieUpgrade } from "./web-auth.js";
261
+ import { authorizeSession, decideWebGate, loadOrCreateWebToken, readSessionCookies, readWebToken, sessionCookieUpgrade } from "./web-auth.js";
270
262
  import { bypassesWebGate, handleAuthRequest, serveGuestRefusedPage, serveSigninPage } from "./auth-api.js";
271
263
  import { isWebPageNavigation } from "./web-shell-routes.js";
272
264
  import { guestSessionPolicy, localSessionPolicy, tokenEpoch, WebSessionStore } from "./web-session.js";
@@ -279,10 +271,8 @@ import { decisionListAnswer } from "./decision-list.js";
279
271
  import { CodeRequestLimiter, ReturningDevices } from "./web-returning-device.js";
280
272
  import { allowedHostNames, applyWebSecurityHeaders, hostnameOf, isHostAllowed, LOOPBACK_HOST_NAMES, WEB_HOST_REJECTED_MESSAGE } from "./web-host-guard.js";
281
273
  import { createPreviewListener, normalizeOrigin, previewAvailability, previewSettings } from "./web-preview.js";
282
- import { canvasWorkerEnabled, isCanvasWorkerTool, CANVAS_WORKER_EXPERIMENTAL_MESSAGE } from "./web-experimental.js";
283
- import { isCanvasWorkerHttpPath } from "./web-experimental-http.js";
274
+ import { isCanvasWorkerTool, CANVAS_WORKER_EXPERIMENTAL_MESSAGE } from "./web-experimental.js";
284
275
  import { bundledSkillContents } from "./bundled-skills.js";
285
- import { createEffectBootstrap } from "./web-effect-bootstrap.js";
286
276
  import { fleetLevelDifferences, fleetLevelSignature, appendedConnections } from "./fleet-level-config.js";
287
277
  import { checkSelfRestartAllowance, recordSelfRestartAttempt } from "./self-restart-limit.js";
288
278
  import { SecretStore } from "./secret-store.js";
@@ -988,11 +978,6 @@ export class FleetManager {
988
978
  collabInstances = new Set();
989
979
  // Health endpoint
990
980
  healthServer = null;
991
- /** #2118: independent of HTML preview availability/port; fixed bootstrap only, no effect resources. */
992
- effectBootstrap = createEffectBootstrap();
993
- /** Fixed trusted document coherence, independent of the HTML preview listener; never a resource grant. */
994
- get effectBoot() { return this.effectBootstrap.boot; }
995
- canvasWorkerEnabled() { return canvasWorkerEnabled(this.fleetConfig?.web); }
996
981
  healthServerDrain = null;
997
982
  /** #1306: the preview listener (health_port + 1 by default), and whether it is listening. */
998
983
  previewListener = null;
@@ -1171,7 +1156,7 @@ export class FleetManager {
1171
1156
  /** #1629: every instance's theme, placements, assets and lock (web-themes.json). */
1172
1157
  get themeStore() {
1173
1158
  // r4.1 Q3 (b): the card's limits are read from fleet.yaml at each upload (a change applies without a restart).
1174
- return (this.themeStoreInstance ??= new ThemeStore(this.dataDir, { limits: () => effectiveAppearanceLimits(this.fleetConfig?.web?.appearance).limits, effectsDropped: ids => this.effectLibrary.collectUnreferenced(ids) }));
1159
+ return (this.themeStoreInstance ??= new ThemeStore(this.dataDir, { limits: () => effectiveAppearanceLimits(this.fleetConfig?.web?.appearance).limits }));
1175
1160
  }
1176
1161
  officeStoreInstance;
1177
1162
  /** Fleet → Org chart → Office: the offices people made (web-offices.json, beside web-themes.json). */
@@ -1183,6 +1168,12 @@ export class FleetManager {
1183
1168
  officeSkinWrites = new OfficeWriteLimiter(undefined, { perWindow: 10, windowMs: 60_000 });
1184
1169
  /** How often a person may save the offices (PUT /ui/offices); memory only, as the appearance limiter. */
1185
1170
  officeWrites = new OfficeWriteLimiter();
1171
+ /** #2855: a guest's preference saves, per account. */
1172
+ guestPrefWrites = new OfficeWriteLimiter(undefined, GUEST_PREF_WRITES);
1173
+ /** #2855: a guest's commands, per account. */
1174
+ guestCommandRuns = new OfficeWriteLimiter(undefined, GUEST_COMMAND_RUNS);
1175
+ /** #2855: a trusted guest's searches, per account. */
1176
+ guestSearches = new OfficeWriteLimiter(undefined, { perWindow: GUEST_SEARCHES.perWindow, windowMs: GUEST_SEARCHES.windowMs });
1186
1177
  officeSharesInstance;
1187
1178
  /**
1188
1179
  * #2636: Office share links — the live Office view for people outside the fleet, read-only, on a quick tunnel of
@@ -1378,96 +1369,16 @@ export class FleetManager {
1378
1369
  }
1379
1370
  catch { /* the next frame carries the rev */ } }));
1380
1371
  }
1381
- effectLibraryInstance;
1382
- effectTrialsInstance;
1383
- /** Lazy, single-owner storage. Register every host reference source before any operation can collect. */
1384
- get effectLibrary() {
1385
- if (!this.effectLibraryInstance) {
1386
- const library = new WebEffectLibrary(this.dataDir);
1387
- // Providers are lazy; publish only after every registration succeeds.
1388
- library.registerReferenceSource("themes", () => {
1389
- if (!this.themeStore.readable())
1390
- throw new Error("themes_unreadable");
1391
- return this.themeStore.placedEffects().flatMap(row => row.ids.map(id => ({ id, holder: `instance:${row.name}` })));
1392
- });
1393
- library.registerReferenceSource("prefs", () => this.webPrefs.ownerEffectReferences());
1394
- library.registerReferenceSource("mods", () => {
1395
- const mods = this.mods.list();
1396
- if (mods.unreadable)
1397
- throw new Error("mods_unreadable");
1398
- return mods.mods.flatMap(mod => mod.effects.map(effect => ({ id: effect.id, holder: `mod:${mod.id}` })));
1399
- });
1400
- this.effectTrialsInstance = new WebEffectTrials({ library, known: name => this.isKnownInstance(name) });
1401
- this.effectLibraryInstance = library;
1402
- }
1403
- return this.effectLibraryInstance;
1404
- }
1405
- /** Volatile Try offers and session-bound runs share this library. */
1406
- get effectTrials() { if (!this.effectTrialsInstance)
1407
- void this.effectLibrary; return this.effectTrialsInstance; }
1408
- effectProfilesInstance;
1409
- get effectProfiles() { return (this.effectProfilesInstance ??= new WebEffectProfiles(this.dataDir)); }
1410
- effectServiceInstance;
1411
- get effectService() {
1412
- return (this.effectServiceInstance ??= new WebEffectService({ library: this.effectLibrary, themes: this.themeStore, profiles: this.effectProfiles, prefs: this.webPrefs, trials: this.effectTrials,
1413
- known: name => this.isKnownInstance(name), changed: () => { try {
1414
- this.emitSseEvent("status", this.getUiStatus());
1415
- }
1416
- catch { /* next status carries revision */ } } }));
1417
- }
1418
- effectToolsService;
1419
- /** Caller identity is supplied only by the owned IPC connection / verified agent endpoint token. */
1420
- get effectTools() {
1421
- return (this.effectToolsService ??= new WebEffectTools({
1422
- library: this.effectLibrary, limiter: this.webAppearance.limiter,
1423
- authorize: (name, buckets) => !this.isKnownInstance(name) ? { error: "unknown_instance" } : this.webAppearance.authorizeEffect(name, buckets),
1424
- placed: name => this.themeStore.get(name).placements.effects,
1425
- place: (name, effects) => {
1426
- const before = this.themeStore.get(name).placements.effects;
1427
- const explicit = new Set(effects.filter(e => typeof e !== "string").map(e => e.id));
1428
- const w = this.themeStore.setPlacements(name, { effects }, "agent", what => {
1429
- // An unchanged human/private placement may be retained, never exposed or newly acquired by an agent.
1430
- const r = explicit.has(what.id) ? this.effectLibrary.read(what.id, { kind: "agent", id: name })
1431
- : this.effectLibrary.readReferenced(what.id, `instance:${name}`);
1432
- if (!r.ok)
1433
- return r.error;
1434
- const params = explicit.has(what.id) ? what.params : before.find(e => e.id === what.id)?.params;
1435
- return resolveEffectParams(r.entry.manifest, params ?? {}).ok ? null : "effect_params";
1436
- });
1437
- return w.ok ? { ok: true, rev: w.rev } : { ok: false, error: w.error };
1438
- },
1439
- changed: () => { try {
1440
- this.emitSseEvent("status", this.getUiStatus());
1441
- }
1442
- catch { /* later status carries placements */ } },
1443
- // A nonmutating own/shared offer is not an appearance write. Only an explicit non-public admin-session click may start it.
1444
- preview: async (instance, id) => {
1445
- const result = this.effectTrials.offer(instance, id);
1446
- if (!result.ok)
1447
- return { error: "not_found" };
1448
- const present = this.webChatHistory.list(instance).some(row => row.effectTrial?.offer === result.offer.offer);
1449
- if (!present && !this.publishEffectTrial(instance, result.offer))
1450
- return { error: "trial_unavailable" };
1451
- return { id, status: "offered", expiresAt: result.offer.expiresAt, message: "A trial card is in your web chat. A signed-in admin outside the public link can start it; nothing was applied." };
1452
- },
1453
- profiles: id => this.effectProfiles.read(id),
1454
- profileSummary: id => this.effectService.profileSummary(id),
1455
- profileSummaries: ids => this.effectService.profileSummaries(ids),
1456
- playbackSummary: (name, id) => this.effectService.playbackSummary(name, id),
1457
- }));
1458
- }
1459
1372
  appearanceToolsService;
1460
1373
  /** #1629 r4 §1: get_web_appearance / preview_web_appearance — the caller's own look, resolved as the page resolves it. */
1461
1374
  get appearanceTools() {
1462
1375
  return (this.appearanceToolsService ??= new AppearanceTools({
1463
- canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
1464
1376
  frame: (name) => (this.getUiStatus().instances.find(i => i.name === name) ?? { name }),
1465
1377
  theme: (name) => {
1466
1378
  const r = this.themeStore.get(name);
1467
1379
  return { theme: r.theme, locked: r.locked, widgets: r.placements.widgets, ambient: r.placements.ambient,
1468
1380
  glyphAsset: (id) => this.themeStore.asset(name, id) };
1469
1381
  },
1470
- effects: name => this.themeStore.readable() ? this.themeStore.get(name).placements.effects.map(p => ({ id: p.id, by: p.by, ...(p.params ? { params: p.params } : {}), playback: this.effectService.playbackSummary(name, p.id) })) : [],
1471
1382
  card: (name) => resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card),
1472
1383
  // #1958: the host's identity-colour settings, as the page reads them (an unreadable prefs file: the defaults).
1473
1384
  colorPrefs: () => { try {
@@ -1496,49 +1407,17 @@ export class FleetManager {
1496
1407
  return Object.hasOwn(this.fleetConfig?.instances ?? {}, name) || (this.classicChannels?.getAll() ?? []).some((ch) => ch.instanceName === name);
1497
1408
  }
1498
1409
  modStore;
1499
- /**
1500
- * #1629 r4-5 / B2: installed mods use the same validated library and scoped holder claims as direct uploads.
1501
- */
1410
+ /** #1629 r4-5: installed mods (#2799: a bundle that carries Canvas Worker effects is refused). */
1502
1411
  get mods() {
1503
1412
  return (this.modStore ??= new ModStore(this.dataDir, {
1504
1413
  themes: this.themeStore,
1505
1414
  prefs: this.webPrefs,
1506
1415
  ownerCss: this.ownerCss,
1507
- effects: this.effectLibrary,
1508
- canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
1509
- context: () => ({ agendVersion: agendVersion(), limits: this.themeStore.limits(), effects: cw1EffectChecker() }),
1416
+ context: () => ({ agendVersion: agendVersion(), limits: this.themeStore.limits() }),
1510
1417
  isKnownInstance: (name) => this.isKnownInstance(name),
1511
1418
  prefsIdentity: OPERATOR,
1512
1419
  }));
1513
1420
  }
1514
- agentModsService;
1515
- /** #1629 r4-5b: export_web_mod / propose_web_mod — the caller's own look as a file, and a proposal for a person. */
1516
- get agentMods() {
1517
- return (this.agentModsService ??= new AgentMods({
1518
- themes: this.themeStore, mods: this.mods, limiter: this.webAppearance.limiter,
1519
- webConfig: () => this.fleetConfig?.web ?? null,
1520
- // The mod store's own: a proposal is checked exactly as the person's import of it will be.
1521
- context: () => this.mods.checkContext(),
1522
- readOwnEffect: (instance, id) => { const r = this.effectLibrary.read(id, { kind: "agent", id: instance }); return r.ok ? { name: r.entry.manifest.name, files: r.entry.files } : null; },
1523
- notice: (instance, text) => { try {
1524
- this.emitSseEvent("message", { instance, sender: instance, role: "status", text, ts: new Date().toISOString() });
1525
- }
1526
- catch { /* the page reads it from Settings too */ } },
1527
- changed: () => { try {
1528
- this.emitSseEvent("status", this.getUiStatus());
1529
- }
1530
- catch { /* the next frame */ } },
1531
- }));
1532
- }
1533
- /**
1534
- * #1629 r4-5c: an effect as this person may carry it away in an export — their own claim, or one a person shared —
1535
- * read with their principal (Sol's ACL). Being able to view or run a placed effect never grants packaging someone
1536
- * else's private claim: the library's reference read is runtime access, not an export path. Null: not theirs.
1537
- */
1538
- modPlacedEffect(person, id) {
1539
- const r = this.effectLibrary.read(id, { kind: "human", id: person });
1540
- return r.ok ? { name: r.entry.manifest.name, files: r.entry.files } : null;
1541
- }
1542
1421
  /** #1629 r4-5: an instance's card text as every viewer sees it (a person's fields over the agent's). */
1543
1422
  modCardText(name) {
1544
1423
  const card = resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card);
@@ -1561,8 +1440,7 @@ export class FleetManager {
1561
1440
  */
1562
1441
  get webSettings() {
1563
1442
  return (this.webSettingsToolsService ??= new WebSettingsTools({
1564
- canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
1565
- prefs: this.webPrefs, ownerCss: this.ownerCss, mods: this.mods, effects: this.effectService,
1443
+ prefs: this.webPrefs, ownerCss: this.ownerCss,
1566
1444
  instances: () => [...Object.keys(this.fleetConfig?.instances ?? {}),
1567
1445
  ...(this.classicChannels?.getAll() ?? []).map((ch) => ch.instanceName).filter((n) => !Object.hasOwn(this.fleetConfig?.instances ?? {}, n))],
1568
1446
  colour: (name) => personIdentityColour(this.dataDir, name),
@@ -1571,7 +1449,6 @@ export class FleetManager {
1571
1449
  bannerFocus: (name) => personBannerFocus(this.dataDir, name),
1572
1450
  setBannerFocus: (name, focus) => { const w = setPersonBannerFocus(this.dataDir, name, focus); if (w.ok)
1573
1451
  this.identityChanged(); return w; },
1574
- placements: (name) => this.themeStore.get(name).placements.effects.map((e) => (typeof e === "string" ? { id: e } : e)),
1575
1452
  ambient: (name) => Object.fromEntries(Object.entries(this.themeStore.get(name).placements.ambient).flatMap(([k, a]) => a ? [[k, { spec: a.spec, by: a.by }]] : [])),
1576
1453
  setAmbient: (name, spec, which) => this.webAppearance.humanSetAmbient(name, spec, which),
1577
1454
  announce: () => { try {
@@ -1885,7 +1762,7 @@ export class FleetManager {
1885
1762
  retire();
1886
1763
  continue;
1887
1764
  }
1888
- if (store.attachPrompt(view.id, retire))
1765
+ if (store.attachPrompt(view.id, retire, { platform: adapter.type, connection: adapterId }))
1889
1766
  return;
1890
1767
  }
1891
1768
  catch {
@@ -6652,10 +6529,12 @@ export class FleetManager {
6652
6529
  dataDir: this.dataDir,
6653
6530
  onWarn: message => this.logger.warn(message),
6654
6531
  // #1620: fleet.yaml's web.session.local, read at every check — a Settings save or a reload applies at once
6655
- // (shortening ends the sessions it now outlives). The public link's policy stays fixed.
6532
+ // (shortening ends the sessions it now outlives). The public link's own cap stays fixed.
6656
6533
  localPolicy: () => localSessionPolicy(this.fleetConfig?.web?.session?.local),
6657
6534
  // #1920 §5.4: a guest's, from web.guests.session (never longer than the surface's own).
6658
6535
  guestPolicy: () => guestSessionPolicy(this.fleetConfig?.web?.guests?.session),
6536
+ // A session over the temporary link ends with the link (its TTL, at most 8 h on a quick tunnel).
6537
+ exposureEnd: id => this.publicWebLink?.endOf(id) ?? null,
6659
6538
  });
6660
6539
  this.enforceGuestSwitch(); // #1920: guests off while the fleet was down
6661
6540
  }
@@ -7299,7 +7178,7 @@ export class FleetManager {
7299
7178
  return { ...a, name: badge.name, platform: badge.platform, scope: [...this.guestScope(owner)].sort(), reach: [...this.guestReach(owner)].sort(),
7300
7179
  devices: devices.get(k) ?? 0, narrow: own(cfg?.narrow, `${a.adapterId}:${a.userId}`) ?? null,
7301
7180
  // #2348: the grant as fleet.yaml has it (null: none), and the last code an admin sent from Settings.
7302
- grant: grant ? { ...(grant.name ? { name: grant.name } : {}), instances: [...grant.instances] } : null,
7181
+ grant: grant ? { ...(grant.name ? { name: grant.name } : {}), instances: [...grant.instances], ...(grant.level === "trusted" ? { level: "trusted" } : {}) } : null,
7303
7182
  ...(sent ? { code: { at: sent.at, outcome: sent.outcome.kind, ...(sent.outcome.kind === "sent" ? { expiresAt: sent.at + sent.outcome.expiresInMs } : {}),
7304
7183
  ...(sent.outcome.kind === "dm_failed" ? { reason: sent.outcome.reason, platform: sent.outcome.platform, ...(sent.outcome.startUrl ? { startUrl: sent.outcome.startUrl } : {}) } : {}) } } : {}) };
7305
7184
  }).sort((x, y) => (y.lastSeen ?? -1) - (x.lastSeen ?? -1));
@@ -7347,7 +7226,7 @@ export class FleetManager {
7347
7226
  const when = Date.parse(r.created_at.includes("T") ? r.created_at : `${r.created_at.replace(" ", "T")}Z`);
7348
7227
  return r.event_type === "guest_grant_changed"
7349
7228
  ? [{ kind: "grant", at: when, account, name, via: p.via === "confirmed" ? "confirmed" : "direct", added: names(p.added), removed: names(p.removed),
7350
- endedSessions: typeof p.endedSessions === "number" ? p.endedSessions : 0 }]
7229
+ endedSessions: typeof p.endedSessions === "number" ? p.endedSessions : 0, ...(p.level === "chat" || p.level === "trusted" ? { level: p.level } : {}) }]
7351
7230
  : [{ kind: "code", at: when, account, name, outcome: typeof p.outcome === "string" ? p.outcome : "unknown" }];
7352
7231
  });
7353
7232
  }
@@ -7386,7 +7265,7 @@ export class FleetManager {
7386
7265
  * confirmed in General, the agents added and taken away, and the sessions a removal ended. Never message content.
7387
7266
  */
7388
7267
  noteGuestGrantChange(change) {
7389
- this.logger.info({ account: change.account, via: change.via, added: change.added.length, removed: change.removed.length }, "Guest access changed in Settings");
7268
+ this.logger.info({ account: change.account, via: change.via, added: change.added.length, removed: change.removed.length, ...(change.level ? { level: change.level } : {}) }, "Guest access changed in Settings");
7390
7269
  try {
7391
7270
  this.eventLog?.insert("fleet", "guest_grant_changed", { ...change });
7392
7271
  }
@@ -7471,6 +7350,8 @@ export class FleetManager {
7471
7350
  * #1920 §5.4: a guest's own `/web revoke` — its sessions and its outstanding code; nobody else's (an admin's revokes
7472
7351
  * everyone's, as before).
7473
7352
  */
7353
+ /** #2855: the admin removed a guest — its own preferences (theme, language, favourites, sort, filter) go too. */
7354
+ forgetGuestPrefs(owner) { this.webPrefs.forgetGuest(webPersonKey(owner)); }
7474
7355
  revokeGuestWebSessions(owner) {
7475
7356
  this.initializeWebSessions();
7476
7357
  const key = `${owner.adapterId}\u0000${owner.userId}`;
@@ -10364,6 +10245,11 @@ export class FleetManager {
10364
10245
  }
10365
10246
  return;
10366
10247
  }
10248
+ // #2870 (the user's choice): in a private chat, a command from someone ClassicBot does not serve here — not an
10249
+ // allowed user, not a ClassicBot admin, not a fleet admin, no room in this chat — gets no answer at all. /start
10250
+ // above still asks for access; a guest's /web is handled before it.
10251
+ if (isPrivateChat && text.startsWith("/") && this.isClassicPrivateStranger(msg.userId, chatId, msg.adapterId))
10252
+ return;
10367
10253
  // Handle /stop command
10368
10254
  if (text === "/stop" || text.startsWith("/stop ")) {
10369
10255
  if (!this.classicChannels.isAdmin(msg.userId)) {
@@ -10838,8 +10724,8 @@ export class FleetManager {
10838
10724
  }
10839
10725
  }
10840
10726
  checkToolPermission(sink, instanceName, tool) {
10841
- if (isCanvasWorkerTool(tool) && !this.canvasWorkerEnabled())
10842
- return { allowed: false, message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE };
10727
+ if (isCanvasWorkerTool(tool))
10728
+ return { allowed: false, message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }; // #2799: removed
10843
10729
  const profile = resolveToolSet(this.fleetConfig?.instances[instanceName], instanceName);
10844
10730
  const allowed = mayUseTool(profile, tool);
10845
10731
  if (!allowed) {
@@ -11572,19 +11458,6 @@ export class FleetManager {
11572
11458
  }
11573
11459
  dispatchTypedIpc(name, msg) {
11574
11460
  const type = String(msg.type);
11575
- if (type.startsWith("fleet_") && WEB_EFFECT_TOOLS.has(type.slice(6))) {
11576
- const ipc = this.instanceIpcClients.get(name), fleetRequestId = msg.fleetRequestId;
11577
- if (!ipc || typeof fleetRequestId !== "string")
11578
- return;
11579
- void this.handleWebThemeHttp(name, type.slice(6), (msg.payload ?? {})).then(result => {
11580
- if (this.instanceIpcClients.get(name) === ipc)
11581
- ipc.send({ type: "fleet_web_theme_response", fleetRequestId, result });
11582
- }, err => {
11583
- if (this.instanceIpcClients.get(name) === ipc)
11584
- ipc.send({ type: "fleet_web_theme_response", fleetRequestId, error: err instanceof Error ? err.message : "Effect operation failed" });
11585
- }).catch(err => this.logger.debug({ err }, "Effect tool response could not be written"));
11586
- return;
11587
- }
11588
11461
  if (type.startsWith("fleet_schedule_")) {
11589
11462
  this.handleScheduleCrud(name, msg);
11590
11463
  return;
@@ -11638,10 +11511,9 @@ export class FleetManager {
11638
11511
  const args = (msg.payload ?? {});
11639
11512
  const result = tool === "get_web_appearance" || tool === "preview_web_appearance"
11640
11513
  ? this.appearanceTools.handle(instanceName, tool, args)
11641
- : tool === "export_web_mod" || tool === "propose_web_mod" ? this.agentMods.handle(instanceName, tool, args)
11642
- : tool === "office_view" || tool === "office_set" ? this.officeActs.handle(instanceName, tool, args)
11643
- : OFFICE_PACK_IPC.has(`fleet_${tool}`) ? this.officePackTools.handle(instanceName, tool, args)
11644
- : this.webAppearance.handleTool(instanceName, tool, args);
11514
+ : tool === "office_view" || tool === "office_set" ? this.officeActs.handle(instanceName, tool, args)
11515
+ : OFFICE_PACK_IPC.has(`fleet_${tool}`) ? this.officePackTools.handle(instanceName, tool, args)
11516
+ : this.webAppearance.handleTool(instanceName, tool, args);
11645
11517
  ipc.send({ type: "fleet_web_theme_response", fleetRequestId, result });
11646
11518
  }
11647
11519
  catch (err) {
@@ -12066,17 +11938,10 @@ export class FleetManager {
12066
11938
  }
12067
11939
  /** #1629: `agend-agent theme …` — the same service as the tools; `instance` is the token's. */
12068
11940
  async handleWebThemeHttp(instance, tool, args) {
12069
- if (isCanvasWorkerTool(tool) && !this.canvasWorkerEnabled())
12070
- return { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE };
12071
- if (WEB_EFFECT_TOOLS.has(tool)) {
12072
- if (!this.isKnownInstance(instance))
12073
- return { error: "unknown_instance" };
12074
- return this.effectTools.handle(instance, tool, args);
12075
- }
11941
+ if (isCanvasWorkerTool(tool))
11942
+ return { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }; // #2799: removed
12076
11943
  if (tool === "get_web_appearance" || tool === "preview_web_appearance")
12077
11944
  return this.appearanceTools.handle(instance, tool, args);
12078
- if (tool === "export_web_mod" || tool === "propose_web_mod")
12079
- return this.agentMods.handle(instance, tool, args);
12080
11945
  if (tool === "office_view" || tool === "office_set")
12081
11946
  return this.officeActs.handle(instance, tool, args); // #2578
12082
11947
  if (OFFICE_PACK_IPC.has(`fleet_${tool}`))
@@ -12960,9 +12825,7 @@ export class FleetManager {
12960
12825
  this.themeStore.forget(name);
12961
12826
  this.webAppearance.forget(name);
12962
12827
  this.appearanceTools.forget(name);
12963
- this.effectToolsService?.forget(name);
12964
12828
  this.mods.forgetInstance(name);
12965
- this.agentMods.forget(name);
12966
12829
  this.officeActsInstance?.forget(name);
12967
12830
  this.officePackToolsInstance?.forget(name);
12968
12831
  }
@@ -15855,6 +15718,16 @@ export class FleetManager {
15855
15718
  return result;
15856
15719
  }
15857
15720
  /** #1269: an instance's chat command from the web chat (web-commands.ts); `publicLink`: a gateway request. */
15721
+ /** #2855: the table scope of an instance the web chat may command (a fleet instance or a ClassicBot room), else null. */
15722
+ webCommandScope(instance) {
15723
+ // Own entries only: a name like "__proto__" or "constructor" must not be found on the prototype (#1476 review).
15724
+ const instances = this.fleetConfig?.instances;
15725
+ if (instances && Object.prototype.hasOwnProperty.call(instances, instance))
15726
+ return isGeneralInstance(this.fleetConfig, instance) ? "general" : "fleet";
15727
+ return this.classicChannels?.getChannelIdByInstance(instance) !== undefined ? "classic" : null;
15728
+ }
15729
+ /** #2855: the platform an instance's channel is on, for the command table's cell (a web-only fleet: Discord's). */
15730
+ commandPlatform(instance) { return this.getAdapterForInstance(instance)?.type === "telegram" ? "telegram" : "discord"; }
15858
15731
  webCommand(input, opts) {
15859
15732
  const webMeta = (instance) => {
15860
15733
  const topicId = this.fleetConfig?.instances[instance]?.topic_id;
@@ -15863,6 +15736,7 @@ export class FleetManager {
15863
15736
  chatId: this.getGroupIdForInstance(instance) || "", messageId: newWebMessageId(),
15864
15737
  username: opts.person?.name ?? WEB_NAME_FALLBACK, userId: opts.person?.userId ?? WEB_NAME_FALLBACK,
15865
15738
  threadId: topicId != null ? String(topicId) : undefined, adapterId: this.getAdapterForInstance(instance)?.id ?? "", source: "web", humanOrigin: webHumanOrigin(true, opts.authority),
15739
+ ...(opts.guest ? { webGuest: true } : {}), // #2855: a guest's /steer or /btw reaches the agent as a guest's
15866
15740
  };
15867
15741
  };
15868
15742
  const choicesFromCache = (instance) => {
@@ -15874,13 +15748,8 @@ export class FleetManager {
15874
15748
  return { current, options: this.withCurrentModel(cached.models, current).map(o => ({ id: o.id, label: this.modelChoiceLabel(o, current) })) };
15875
15749
  };
15876
15750
  return runWebCommand({
15877
- // Own entries only: a name like "__proto__" or "constructor" must not be found on the prototype (#1476 review).
15878
- scope: (instance) => {
15879
- const instances = this.fleetConfig?.instances;
15880
- if (instances && Object.prototype.hasOwnProperty.call(instances, instance))
15881
- return isGeneralInstance(this.fleetConfig, instance) ? "general" : "fleet";
15882
- return this.classicChannels?.getChannelIdByInstance(instance) !== undefined ? "classic" : null;
15883
- },
15751
+ scope: (instance) => this.webCommandScope(instance),
15752
+ platform: (instance) => this.commandPlatform(instance),
15884
15753
  ctx: (instance) => this.topicCommands.getCtxText(instance),
15885
15754
  // Board 003648ac: a watch for the paste, armed when the daemon says it was written (raw_paste_written); one
15886
15755
  // for a command that was not handed over is dropped.
@@ -17557,6 +17426,13 @@ export class FleetManager {
17557
17426
  lines.push(t(admin === "added" ? "classic.approve_done_admin" : "classic.approve_done_admin_already", adminUserId ?? ""));
17558
17427
  }
17559
17428
  this.notifyInstanceTopic(entry.instanceName, lines.join("\n"));
17429
+ // #2870: the chat just allowed — and the new ClassicBot admin's own chat — get the ClassicBot menu now, not at the
17430
+ // bot's next start (Telegram only; the menu is per chat there).
17431
+ const reply = entry.classicReplyTo;
17432
+ if (reply && reply.adapter.type === "telegram" && scope !== "guild") {
17433
+ for (const chat of new Set([groupId, ...(adminUserId ? [adminUserId] : [])]))
17434
+ void this.topicCommands.addClassicMenuChat(reply.adapterId, chat);
17435
+ }
17560
17436
  await this.notifyClassicRequester(entry, "classic.request_allowed");
17561
17437
  }
17562
17438
  async notifyClassicRequester(entry, key) {
@@ -18156,14 +18032,6 @@ export class FleetManager {
18156
18032
  const room = this.classicChannels?.getAll().find(c => c.instanceName === name);
18157
18033
  return room ? `room:${room.channelId}:${room.adapterId ?? ""}` : null;
18158
18034
  }
18159
- /** The only Try-card publisher: no arbitrary SSE sender/role can manufacture this metadata. */
18160
- publishEffectTrial(instance, offer) {
18161
- const row = this.webChatHistory.recordEffectTrial(instance, offer, new Date().toISOString());
18162
- if (!row)
18163
- return false;
18164
- broadcastSseEvent(this.sseClients, "message", row, err => this.logger.debug({ err }, "SSE client write failed; evicting"), this.webChatHistory.cursorOf(row));
18165
- return true;
18166
- }
18167
18035
  /** Push an SSE event to all connected Web UI clients. */
18168
18036
  emitSseEvent(event, data) {
18169
18037
  // #1386: a prompt opened or closed, or an instance's state moved — "Needs you" may have changed.
@@ -18471,27 +18339,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
18471
18339
  this.syncRoleSkills(workDir, backend, "general", knowledgeDir);
18472
18340
  this.logger.debug({ knowledgeDir, workDir, backend }, "Synced general knowledge files");
18473
18341
  }
18474
- /** Update only AgEnD-managed skill copies; no launch or restart is needed for an experimental switch. */
18475
- canvasWorkerSettingChanged() {
18476
- this.refreshExperimentalSkills();
18477
- this.emitSseEvent("status", this.getUiStatus());
18478
- }
18479
- refreshExperimentalSkills() {
18480
- for (const [name, daemon] of this.daemons) {
18481
- try {
18482
- const cfg = daemon.getConfigSnapshot();
18483
- const backend = cfg.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
18484
- const workDir = this.resolveKnowledgeWorkDir(cfg.working_directory, backend, name);
18485
- const classic = this.classicChannels?.getAll().some(ch => ch.instanceName === name) === true;
18486
- if (classic && !insideManagedWorkspaces(workDir))
18487
- continue;
18488
- this.syncRoleSkills(workDir, backend, classic ? "classic" : cfg.general_topic ? "general" : "worker");
18489
- }
18490
- catch (err) {
18491
- this.logger.warn({ name, err }, "Could not refresh experimental skill visibility");
18492
- }
18493
- }
18494
- }
18495
18342
  /** Publish only the bundled skills eligible for an instance role. */
18496
18343
  syncRoleSkills(workDir, backend, role, knowledgeDir) {
18497
18344
  const skillSegments = FleetManager.SKILLS_DIR_SEGMENTS[backend];
@@ -18696,7 +18543,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
18696
18543
  continue;
18697
18544
  }
18698
18545
  managed.push(name);
18699
- const newContent = bundledSkillContents(join(srcSkills, name), this.canvasWorkerEnabled());
18546
+ const newContent = bundledSkillContents(join(srcSkills, name));
18700
18547
  if (refuse(dest))
18701
18548
  continue;
18702
18549
  try {
@@ -20854,6 +20701,17 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
20854
20701
  return (adapterId ? generals.find(([name, cfg]) => (cfg.channel_id ?? this.getInstanceAdapterId(name)) === adapterId) : undefined)?.[0]
20855
20702
  ?? generals[0]?.[0];
20856
20703
  }
20704
+ /**
20705
+ * #2870: someone ClassicBot does not serve in this private chat: not on allowed_users, not a ClassicBot admin, not a
20706
+ * fleet admin of this bot, and no ClassicBot room here. Their commands other than /start get no answer.
20707
+ */
20708
+ isClassicPrivateStranger(userId, chatId, adapterId) {
20709
+ const classic = this.classicChannels;
20710
+ if (!classic)
20711
+ return false;
20712
+ return !classic.isAdmin(userId) && !classic.isUserAllowed(userId) && !this.isFleetAdmin(userId, adapterId ?? "")
20713
+ && !classic.isClassicChannel(chatId, adapterId);
20714
+ }
20857
20715
  /** Return a user-facing blocker without mutating ClassicBot state. */
20858
20716
  validateClassicStart(channelId, userId, guildId, adapterId) {
20859
20717
  const classic = this.classicChannels;
@@ -21136,7 +20994,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21136
20994
  try {
21137
20995
  this.themeStore.forget(ch.instanceName);
21138
20996
  this.webAppearance.limiter.forget(ch.instanceName);
21139
- this.effectToolsService?.forget(ch.instanceName);
21140
20997
  }
21141
20998
  catch (err) {
21142
20999
  this.logger.warn({ err: err.message, instanceName: ch.instanceName }, "Dropping a removed room's web theme failed");
@@ -21643,9 +21500,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21643
21500
  this.getConfigHistory().accepted("fleet.yaml", this.configPath, this.loadedFleetSource);
21644
21501
  this.migrateOfficeStorage();
21645
21502
  this.migrateNoticeDeliveryStorage();
21646
- if (canvasWorkerEnabled(oldConfig?.web) !== this.canvasWorkerEnabled()) {
21647
- this.canvasWorkerSettingChanged();
21648
- }
21649
21503
  // Classic behavior settings share the fleet defaults but are not entries
21650
21504
  // in fleet.yaml. Snapshot the old effective chain before reloading the
21651
21505
  // Classic file so SIGHUP can hot-apply either source without waiting for
@@ -23512,6 +23366,21 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
23512
23366
  }
23513
23367
  return scope.size ? scope : NO_GUEST_SCOPE;
23514
23368
  }
23369
+ /** #2855: whether a request carries a guest's live session (its cookie; checked without counting as activity). */
23370
+ presentsGuestSession(req) {
23371
+ if (!this.webToken || !this.webSessions)
23372
+ return false;
23373
+ const epoch = tokenEpoch(this.webToken);
23374
+ return readSessionCookies(req, this.webToken).some(id => this.webSessions.authenticate(id, epoch, { touch: false })?.tier === "guest");
23375
+ }
23376
+ /** #2855: whether the admin made this guest Trusted — only an explicit grant can (web.guests.grants[account].level). */
23377
+ guestTrusted(owner) {
23378
+ const guests = this.fleetConfig?.web?.guests;
23379
+ if (guests?.enabled !== true || !guests.grants)
23380
+ return false;
23381
+ const key = `${owner.adapterId}:${owner.userId}`;
23382
+ return Object.hasOwn(guests.grants, key) && guests.grants[key]?.level === "trusted";
23383
+ }
23515
23384
  /**
23516
23385
  * #1920 (#2104 review): what an account reaches before an admin's narrowing — its connection's topics (an open one's
23517
23386
  * named agents), its own Telegram private room, its verified group rooms. Narrowing can only take from this set, so it
@@ -23690,13 +23559,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
23690
23559
  return;
23691
23560
  }
23692
23561
  const requestPath = new URL(req.url ?? "/", `http://localhost:${port}`).pathname;
23693
- if (!this.canvasWorkerEnabled() && isCanvasWorkerHttpPath(requestPath)) {
23694
- res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
23695
- res.end(JSON.stringify({ error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }));
23696
- return;
23697
- }
23698
- if (this.effectBootstrap.handle(req, res))
23699
- return;
23700
23562
  // Branding is public, after Host admission: automatic favicon requests
23701
23563
  // must not become noisy web-token 401s in the browser console.
23702
23564
  // #2308: the icon routes and the pages' icon links follow the app icon set in Settings.
@@ -23704,8 +23566,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
23704
23566
  if (handleWebIconRequest(req, res, requestPath))
23705
23567
  return;
23706
23568
  // Public: the health probe, /agent (instance-token auth of its own), the
23707
- // sign-in surface, and /view's reads unless web.view_access says otherwise.
23708
- if (bypassesWebGate(req, requestPath, this.fleetConfig, p => !gatewayRequestContext(req) && (isViewPath(p) || isUsagePath(p)))) {
23569
+ // sign-in surface, and /view's reads unless web.view_access says otherwise (#2855: never a guest's, which go
23570
+ // through the guest gate and its limits on either listener).
23571
+ if (bypassesWebGate(req, requestPath, this.fleetConfig, p => !gatewayRequestContext(req) && (isViewPath(p) || isUsagePath(p)) && !this.presentsGuestSession(req))) {
23709
23572
  // fall through to the handlers below
23710
23573
  }
23711
23574
  else {
@@ -23771,7 +23634,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
23771
23634
  streamOpened: () => { void this.refreshGuestRooms(owner, { mode: "force" }); },
23772
23635
  history: () => this.webChatHistory,
23773
23636
  // #1920 §5.1: guests chat unless the host made them read-only (web.guests.chat: false).
23774
- chat: () => this.fleetConfig?.web?.guests?.chat !== false });
23637
+ chat: () => this.fleetConfig?.web?.guests?.chat !== false,
23638
+ // #2855: the admin's level for this guest, read at each request (a change applies at once).
23639
+ trusted: () => this.guestTrusted(owner) });
23775
23640
  }
23776
23641
  if (decision.via === "session" && this.webToken) {
23777
23642
  const upgrade = sessionCookieUpgrade(req, this.webToken, decision.session);
@@ -24177,7 +24042,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24177
24042
  return;
24178
24043
  }
24179
24044
  const listener = createPreviewListener({ settings, healthPort: boundPort, config: this.fleetConfig,
24180
- canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
24181
24045
  // #1723: with a web.preview_origin, the open public link's dashboard frames this listener's preview too.
24182
24046
  extraOrigins: () => { const o = settings.origin ? this.publicWebLink?.openDashboardOrigin() : null; return o ? [o] : []; } });
24183
24047
  this.previewListener = listener;
@@ -24354,8 +24218,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24354
24218
  catch {
24355
24219
  return {};
24356
24220
  } })(),
24357
- // #2118: only this Chat's placement content; guest projection drops global revisions.
24358
- effect_chat_v: this.canvasWorkerEnabled() ? createHash("sha256").update(JSON.stringify(this.themeStore.get(name).placements.effects)).digest("hex").slice(0, 32) : null,
24359
24221
  // #1958: the identity colour's tiers (the page resolves them: the agent's only while it shows agents' themes).
24360
24222
  color: { ...(colors.get(name)?.color ? { person: colors.get(name).color } : {}), ...(image.agentColor ? { agent: image.agentColor } : {}),
24361
24223
  ...(image.roleColor ? { role: image.roleColor } : {}), auto: autoIdentityColor(name) },
@@ -24366,6 +24228,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24366
24228
  tags: identity.tags,
24367
24229
  teams: instanceTeams(this.fleetConfig?.teams, name), // #2172: the sidebar groups a team's members under it
24368
24230
  last_reply: this.webChatHistory.lastAgentReply(name) ?? undefined, // #2179: its last reply to a person (ISO)
24231
+ // #2855: its last reply that reached the channel — the only reply time a guest's sidebar gets (guestStatus).
24232
+ last_reply_channel: this.webChatHistory.lastAgentReply(name, { channel: true }) ?? undefined,
24233
+ // #2855: the web commands a guest may run here — the table's "anyone" cells for this channel (guestWebCommands);
24234
+ // only while guests are on (no guest reads it otherwise).
24235
+ ...(this.fleetConfig?.web?.guests?.enabled === true
24236
+ ? { guest_commands: (() => { const scope = this.webCommandScope(name); return scope ? guestWebCommands(scope, this.commandPlatform(name)) : []; })() } : {}),
24369
24237
  status: this.getInstanceStatus(name),
24370
24238
  // `state` (presentation: may be awaiting_input) and `execution_state` (working / idle / stuck, or null —
24371
24239
  // what the dashboard's activity events carry) come from instancePresentation (#1212).
@@ -24382,7 +24250,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24382
24250
  };
24383
24251
  });
24384
24252
  return {
24385
- canvas_worker: this.canvasWorkerEnabled(),
24386
24253
  instances,
24387
24254
  startup: this.startupDashboard?.snapshot() ?? null,
24388
24255
  publicLink: this.getPublicWebStatus(),