@songsid/agend 2.3.2 → 2.3.3-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-cli-instructions.md +2 -2
- package/dist/agent-cli.js +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +23 -5
- package/dist/agent-whats-new.js +58 -40
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.js +1 -1
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/web-authority.d.ts +9 -4
- package/dist/authority/web-authority.js +10 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.js +6 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +20 -1
- package/dist/backend/claude-code.js +54 -29
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/claude-transcript-errors.d.ts +18 -0
- package/dist/backend/claude-transcript-errors.js +19 -0
- package/dist/backend/claude-transcript-errors.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +16 -3
- package/dist/backend/codex-session-lookup.js +45 -11
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +16 -0
- package/dist/backend/codex.js +184 -35
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +58 -12
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-reconcile-store.js +8 -1
- package/dist/backend/kiro-reconcile-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +0 -5
- package/dist/backend/kiro.js +2 -10
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +2 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +53 -4
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +60 -17
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +4 -1
- package/dist/classic-channel-manager.js +21 -7
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +23 -6
- package/dist/cli.js.map +1 -1
- package/dist/config-history.d.ts +50 -0
- package/dist/config-history.js +263 -0
- package/dist/config-history.js.map +1 -0
- package/dist/config-validator.js +10 -8
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +10 -5
- package/dist/cross-instance-envelope.js +20 -17
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +130 -64
- package/dist/daemon.js +603 -428
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.d.ts +8 -1
- package/dist/daily-summary.js +8 -5
- package/dist/daily-summary.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-outbox.d.ts +31 -22
- package/dist/delivery-outbox.js +119 -161
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js +10 -25
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +16 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-level-config.js +1 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +74 -66
- package/dist/fleet-manager.js +285 -414
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +5 -0
- package/dist/fleet-process-handlers.js +21 -0
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/fleet-service-owner.d.ts +11 -0
- package/dist/fleet-service-owner.js +3 -0
- package/dist/fleet-service-owner.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +7 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +30 -68
- package/dist/instance-lifecycle.d.ts +19 -0
- package/dist/instance-lifecycle.js +73 -15
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +6 -7
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-reconcile.js +3 -3
- package/dist/kiro-reconcile.js.map +1 -1
- package/dist/locale.js +96 -58
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/needs-you-hub.d.ts +1 -0
- package/dist/needs-you-hub.js +6 -3
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +3 -1
- package/dist/needs-you-live.js +24 -2
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +13 -2
- package/dist/needs-you.js +11 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/notify-socket.d.ts +10 -0
- package/dist/notify-socket.js +45 -0
- package/dist/notify-socket.js.map +1 -0
- package/dist/outbound-handlers.js +20 -8
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +4 -43
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/parked-notices.d.ts +87 -0
- package/dist/parked-notices.js +194 -0
- package/dist/parked-notices.js.map +1 -0
- package/dist/public-link-progress.d.ts +5 -2
- package/dist/public-link-progress.js +4 -3
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +12 -0
- package/dist/public-web-link.js +29 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quota-notice.d.ts +23 -0
- package/dist/quota-notice.js +22 -0
- package/dist/quota-notice.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +17 -25
- package/dist/scheduler/task-board.js +29 -39
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.d.ts +23 -15
- package/dist/sd-notify.js +88 -29
- package/dist/sd-notify.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +16 -1
- package/dist/settings-api.js +56 -16
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +17 -3
- package/dist/settings-confirmation.js +8 -5
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +0 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +31 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-wizard.d.ts +3 -0
- package/dist/setup-wizard.js +4 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-waker.d.ts +43 -1
- package/dist/task-board-waker.js +121 -9
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.d.ts +1 -1
- package/dist/tmux-control.js +3 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.js +18 -16
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +2 -0
- package/dist/tmux-read.js +7 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +29 -7
- package/dist/topic-commands.js +97 -37
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.d.ts +7 -0
- package/dist/transcript-monitor.js +48 -3
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +5 -0
- package/dist/tunnel/cloudflared.js +32 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +2 -1
- package/dist/tunnel/named-diagnostics.js +4 -1
- package/dist/tunnel/named-diagnostics.js.map +1 -1
- package/dist/tunnel/named-link-test.d.ts +90 -0
- package/dist/tunnel/named-link-test.js +270 -0
- package/dist/tunnel/named-link-test.js.map +1 -0
- package/dist/tunnel/purpose-lane.d.ts +3 -1
- package/dist/tunnel/purpose-lane.js +2 -0
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/types.d.ts +9 -11
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +12 -46
- package/dist/ui/chat-thread.js +51 -59
- package/dist/ui/fleet-office-skin.js +37 -14
- package/dist/ui/fleet-office.js +31 -7
- package/dist/ui/html-attachment.js +22 -8
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +11 -9
- package/dist/ui/panel-settings.js +5 -3
- package/dist/ui/preview-panel.js +12 -21
- package/dist/ui/preview.js +10 -3
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +3 -37
- package/dist/ui/settings-confirm.js +18 -0
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-named-test.js +144 -0
- package/dist/ui/settings-registry.js +0 -2
- package/dist/ui/settings-strings.js +16 -16
- package/dist/ui/settings-web-authority.js +4 -3
- package/dist/ui/shared/app-i18n.js +15 -4
- package/dist/ui/shared/app-needs.js +5 -1
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +47 -9
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +33 -39
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/ui/shared/web-authorize.js +35 -13
- package/dist/update-check.d.ts +13 -4
- package/dist/update-check.js +17 -4
- package/dist/update-check.js.map +1 -1
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +257 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +3 -8
- package/dist/web-experimental.js +9 -33
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -13
- package/dist/web-preview.js +12 -23
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -1
- package/dist/web-settings-patch.js +9 -7
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/workflow-templates/default.md +2 -2
- package/dist/working-recovery.d.ts +0 -2
- package/dist/working-recovery.js +2 -23
- package/dist/working-recovery.js.map +1 -1
- package/launcher/agend-install.cjs +7 -1
- package/launcher/fleet-service-owner.cjs +150 -0
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -673
- package/dist/task-board-summary.d.ts +0 -101
- package/dist/task-board-summary.js +0 -177
- package/dist/task-board-summary.js.map +0 -1
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/upgrade-notices.d.ts +0 -17
- package/dist/upgrade-notices.js +0 -93
- package/dist/upgrade-notices.js.map +0 -1
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -27,6 +27,7 @@ import { clearPublicLinkOwner, readPublicLinkOwner, writePublicLinkOwner } from
|
|
|
27
27
|
import { PUBLIC_PREVIEW_FRAME_SRC, PUBLIC_PREVIEW_TUNNEL_DIR, PublicPreview, publicPreviewWanted } from "./public-preview.js";
|
|
28
28
|
import { CloudflaredProvider } from "./tunnel/cloudflared.js";
|
|
29
29
|
import { ensureCloudflared } from "./tunnel/cloudflared-install.js";
|
|
30
|
+
import { namedTestLane, runNamedLinkTest } from "./tunnel/named-link-test.js";
|
|
30
31
|
import { OFFICE_SHARE_TUNNEL_DIR, OfficeShareService } from "./office-share-service.js";
|
|
31
32
|
import { ALL_OFFICES, WHOLE_FLEET } from "./office-share.js";
|
|
32
33
|
import { TunnelPurposeLane } from "./tunnel/purpose-lane.js";
|
|
@@ -52,7 +53,7 @@ import { applyKiroMigration, kiroMigrationPrompt, planKiroMigration } from "./ki
|
|
|
52
53
|
import { fleetLabel } from "./fleet-label.js";
|
|
53
54
|
import { beginFullRestartProgress as persistFullRestartProgress, beginUpdateProgress as persistUpdateProgress, clearUpdateMarker, isUpdateInProgress, readUpdateProgress, sameUpdateMarker, setUpdateProgressStage, updateMarkerIdentity, updateProgressOperation, updateReceiptOf, } from "./update-marker.js";
|
|
54
55
|
import { formatUpdateProgress } from "./update-progress.js";
|
|
55
|
-
import { sdNotify, sdNotifyBlocking, setSdNotifyWarn } from "./sd-notify.js";
|
|
56
|
+
import { sdNotify, sdNotifyBlocking, setSdNotifyWarn, startSdWatchdog } from "./sd-notify.js";
|
|
56
57
|
import { StartupMemoryDeadline, fleetStopConcurrency } from "./startup-memory-deadline.js";
|
|
57
58
|
import { readFleetMemory } from "./process-memory.js";
|
|
58
59
|
import { MemoryPressure } from "./memory-pressure.js";
|
|
@@ -67,7 +68,7 @@ const __dirname = dirname(__filename);
|
|
|
67
68
|
const DEFAULT_OPEN_ACCESS = { mode: "open", allowed_users: [], max_pending_codes: 0, code_expiry_minutes: 0 };
|
|
68
69
|
import { STATUS_EMOJI_CONFIG_KEYS, STATUS_EMOJI_KEYS, STATUS_EMOJI_SUGGESTIONS, TELEGRAM_REACTION_EMOJIS, builtinStatusEmojis, customEmojiValue, emojiImageUrl, normalizeEmoji, previewStatusEmojis, reactionForm, reactionMatchKey, resolveStatusEmojis, statusAvoidList, statusEmojiProblem, statusMatchKey, statusMatchKeys, textForm, } from "./status-emojis.js";
|
|
69
70
|
import { isProbeableRouteTarget } from "./fleet-context.js";
|
|
70
|
-
import { getEffectiveInstanceDefaults, loadFleetConfig, loadRawFleetConfig, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG, autoGeneralInstanceConfig } from "./config.js";
|
|
71
|
+
import { getEffectiveInstanceDefaults, loadFleetConfig, loadRawFleetConfig, loadFleetConfigSource, loadRawFleetConfigSource, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG, autoGeneralInstanceConfig } from "./config.js";
|
|
71
72
|
import { EventLog } from "./event-log.js";
|
|
72
73
|
import { binaryProbe } from "./binary-probe.js";
|
|
73
74
|
import { AVAILABILITY_STALE_MS, BackendAvailabilityStore, hostAvailabilityProbes } from "./backend-availability.js";
|
|
@@ -80,6 +81,7 @@ import { TmuxManager } from "./tmux-manager.js";
|
|
|
80
81
|
import { AccessManager } from "./channel/access-manager.js";
|
|
81
82
|
import { IpcClient } from "./channel/ipc-bridge.js";
|
|
82
83
|
import { usesChannelAddress } from "./channel/mcp-tools.js";
|
|
84
|
+
import { ParkedNotices, PARKED_NOTICE_LEDGER_FILE } from "./parked-notices.js";
|
|
83
85
|
import { formatFleetTime } from "./tz-utils.js";
|
|
84
86
|
import { createAdapter } from "./channel/factory.js";
|
|
85
87
|
import { isWebChannelEcho, WEB_ECHO_PREFIX } from "./web-channel-echo.js";
|
|
@@ -94,13 +96,12 @@ import { processAttachments } from "./channel/attachment-handler.js";
|
|
|
94
96
|
import { routeToolCall } from "./channel/tool-router.js";
|
|
95
97
|
import { Scheduler } from "./scheduler/index.js";
|
|
96
98
|
import { boardActivity, boardCardLimit, boardToolAction } from "./task-board-tool.js";
|
|
97
|
-
import { TaskBoardWaker } from "./task-board-waker.js";
|
|
99
|
+
import { TaskBoardWaker, namesCard } from "./task-board-waker.js";
|
|
98
100
|
import { TaskBoardError, waitsForPerson, wrapUpGap } from "./scheduler/task-board.js";
|
|
99
101
|
import { escapeTelegramHtml, scheduleClock, scheduleRetryLabel } from "./scheduler/retry-label.js";
|
|
100
102
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
101
103
|
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, parseCompactCommand, SAVE_FILENAME_RE, resolveInstanceContext, forgetInstanceContext, readStatuslineJson, statuslineModel } from "./topic-commands.js";
|
|
102
104
|
import { DailySummary } from "./daily-summary.js";
|
|
103
|
-
import { TaskBoardSummaryClock, buildSummary, isQuiet, renderSummary, summaryMarkFile, summaryTime } from "./task-board-summary.js";
|
|
104
105
|
import { WebhookEmitter } from "./webhook-emitter.js";
|
|
105
106
|
import { TmuxControlClient } from "./tmux-control.js";
|
|
106
107
|
import { safeHandler } from "./safe-async.js";
|
|
@@ -177,13 +178,6 @@ import { OfficeSkinStore } from "./web-office-skins.js";
|
|
|
177
178
|
import { OfficeActs } from "./web-office-acts.js";
|
|
178
179
|
import { OFFICE_PACK_TOOLS, OfficePackTools } from "./web-office-pack-tools.js";
|
|
179
180
|
import { WebAppearance } from "./web-appearance.js";
|
|
180
|
-
import { WebEffectLibrary } from "./web-effect-library.js";
|
|
181
|
-
import { WebEffectTrials } from "./web-effect-trials.js";
|
|
182
|
-
import { WebEffectService } from "./web-effect-service.js";
|
|
183
|
-
import { WebEffectProfiles } from "./web-effect-profiles.js";
|
|
184
|
-
import { WebEffectTools, WEB_EFFECT_TOOLS } from "./web-effect-tools.js";
|
|
185
|
-
import { resolveEffectParams } from "./web-effect.js";
|
|
186
|
-
import { cw1EffectChecker } from "./web-mod-bundle.js";
|
|
187
181
|
import { effectiveAppearanceLimits } from "./web-appearance-limits.js";
|
|
188
182
|
import { WEB_NAME_FALLBACK, safeWebName, validateWebName, webNameInUse, webPersonKey } from "./web-profile.js";
|
|
189
183
|
import { ReplyButtonStore, parseReplyButtons, replyButtonClickText, replyButtonsFallbackText, REPLY_BUTTON_PREFIX } from "./reply-buttons.js";
|
|
@@ -196,7 +190,6 @@ import { autoIdentityColor } from "./web-identity-color.js";
|
|
|
196
190
|
import { AppearanceTools } from "./web-appearance-tools.js";
|
|
197
191
|
import { cardVersion, resolveCard } from "./web-card.js";
|
|
198
192
|
import { ModStore } from "./web-mod-store.js";
|
|
199
|
-
import { AgentMods } from "./web-mod-agent.js";
|
|
200
193
|
import { downloadStickerImage } from "./channel/sticker-download.js";
|
|
201
194
|
import { WebImageCache, imageMimeForFile, WEB_IMAGE_MAX_AGE_MS, WEB_IMAGE_MAX_BYTES, WEB_IMAGE_RETRY_MS } from "./web-images.js";
|
|
202
195
|
import { accentColor, avatarFor, ownBotConnections } from "./instance-avatar.js";
|
|
@@ -215,7 +208,7 @@ import { crossInstanceVisibility, runVisibilityCommand, xmsgVisibility } from ".
|
|
|
215
208
|
import { SEARCH_RETENTION_DAYS, SearchIndex } from "./search/search-index.js";
|
|
216
209
|
import { chatDoc, taskDoc, xmsgDoc } from "./search/search-docs.js";
|
|
217
210
|
import { WebSettingsTools } from "./web-settings-tools.js";
|
|
218
|
-
import { bindGuestRequest, guestRouteDecision } from "./web-guest-access.js";
|
|
211
|
+
import { GUEST_COMMAND_RUNS, GUEST_PREF_WRITES, GUEST_SEARCHES, bindGuestRequest, guestRouteDecision } from "./web-guest-access.js";
|
|
219
212
|
import { GUEST_ROOM_TICK_MS, GuestRoomMembership, guestGroupRooms } from "./web-guest-rooms.js";
|
|
220
213
|
import { Delegations, TrafficLog, trafficItem } from "./fleet-traffic.js";
|
|
221
214
|
import { installedChannel, updateNoticeKey } from "./update-check.js";
|
|
@@ -235,6 +228,7 @@ import { remoteCardAction, remoteCardChoiceLabel, remoteCardOutcomeText, remoteC
|
|
|
235
228
|
import { recentChatContext } from "./log-tail.js";
|
|
236
229
|
import { describeSignalSource, recordInternalRequest, withOrigin } from "./fleet-control-audit.js";
|
|
237
230
|
import { handleAgentRequest, ToolNotPermittedError } from "./agent-endpoint.js";
|
|
231
|
+
import { ConfigHistory, ConfigSyntaxError, configIssueText } from "./config-history.js";
|
|
238
232
|
import { ClassicChannelManager, classicWebEchoOn, getClassicBackendChoices, inferClassicChannelType, isSelectableClassicBackend, readClassicLastActivityAt, readClassicRoomNames } from "./classic-channel-manager.js";
|
|
239
233
|
import { assertExplicitInstanceRemoval } from "./instance-removal.js";
|
|
240
234
|
import { KNOWN_BACKENDS, validateFleetConfig } from "./config-validator.js";
|
|
@@ -254,20 +248,19 @@ import { isSetupComplete, markSetupComplete } from "./setup-marker.js";
|
|
|
254
248
|
import { manualCleanupMessage, reapStaleTunnel } from "./tunnel/lease.js";
|
|
255
249
|
import { buildToolPermissionsNotice } from "./tool-permissions-notice.js";
|
|
256
250
|
import { NeedsYouHub } from "./needs-you-hub.js";
|
|
257
|
-
import { WEB_CHAT_NOTICE, claimNotice, hasWebChat, releaseNotice, upgradeNoticesPath } from "./upgrade-notices.js";
|
|
258
251
|
import { GENERAL_NAME, GENERAL_PAUSE_ERROR, adoptGeneralByName, freeGeneralName, isGeneralInstance } from "./general-instance.js";
|
|
259
252
|
import { buildOrgChart } from "./web-org.js";
|
|
260
253
|
import { topMemoryHogs } from "./memory-hogs.js";
|
|
261
254
|
import { GENERAL_POINTING_REFUSALS, SNOWFLAKE, hasPlaceRefs, placeRef, webRoute, webTextLinks, placeSuffix, renderPlaceRefs, stripPlaceRefs, topicUrl, worldPlaceOf } from "./topic-link.js";
|
|
262
255
|
import { CacheService, cacheWindow } from "./cache-service.js";
|
|
263
|
-
import { runWebCommand } from "./web-commands.js";
|
|
256
|
+
import { guestWebCommands, runWebCommand } from "./web-commands.js";
|
|
264
257
|
import { CompactWatch, launchIdentity } from "./compact-watch.js";
|
|
265
258
|
import { agyLegacyAgentsFile, danglingSymlink, migrateAgyAgentsFile } from "./backend/antigravity.js";
|
|
266
259
|
import { claudeConfigDirFor, claudeProjectKey } from "./backend/claude-code.js";
|
|
267
260
|
import { sharedRolloutIndex } from "./rollout-index.js";
|
|
268
261
|
import { TASK_BOARD_ON_NOTICE, dashboardRefusalIn, taskBoardSwitchedOn } from "./dashboard-permissions.js";
|
|
269
262
|
import { mayUseTool, resolveToolSet, scheduleOpRefusal, toolForIpcType, toolRefusedMessage, } from "./tool-permissions.js";
|
|
270
|
-
import { authorizeSession, decideWebGate, loadOrCreateWebToken, readWebToken, sessionCookieUpgrade } from "./web-auth.js";
|
|
263
|
+
import { authorizeSession, decideWebGate, loadOrCreateWebToken, readSessionCookies, readWebToken, sessionCookieUpgrade } from "./web-auth.js";
|
|
271
264
|
import { bypassesWebGate, handleAuthRequest, serveGuestRefusedPage, serveSigninPage } from "./auth-api.js";
|
|
272
265
|
import { isWebPageNavigation } from "./web-shell-routes.js";
|
|
273
266
|
import { guestSessionPolicy, localSessionPolicy, tokenEpoch, WebSessionStore } from "./web-session.js";
|
|
@@ -280,10 +273,8 @@ import { decisionListAnswer } from "./decision-list.js";
|
|
|
280
273
|
import { CodeRequestLimiter, ReturningDevices } from "./web-returning-device.js";
|
|
281
274
|
import { allowedHostNames, applyWebSecurityHeaders, hostnameOf, isHostAllowed, LOOPBACK_HOST_NAMES, WEB_HOST_REJECTED_MESSAGE } from "./web-host-guard.js";
|
|
282
275
|
import { createPreviewListener, normalizeOrigin, previewAvailability, previewSettings } from "./web-preview.js";
|
|
283
|
-
import {
|
|
284
|
-
import { isCanvasWorkerHttpPath } from "./web-experimental-http.js";
|
|
276
|
+
import { isCanvasWorkerTool, CANVAS_WORKER_EXPERIMENTAL_MESSAGE } from "./web-experimental.js";
|
|
285
277
|
import { bundledSkillContents } from "./bundled-skills.js";
|
|
286
|
-
import { createEffectBootstrap } from "./web-effect-bootstrap.js";
|
|
287
278
|
import { fleetLevelDifferences, fleetLevelSignature, appendedConnections } from "./fleet-level-config.js";
|
|
288
279
|
import { checkSelfRestartAllowance, recordSelfRestartAttempt } from "./self-restart-limit.js";
|
|
289
280
|
import { SecretStore } from "./secret-store.js";
|
|
@@ -612,7 +603,6 @@ export function deriveSpawnConcurrency(freeMemMB, cores) {
|
|
|
612
603
|
/** #926: how often reply obligations are checked, the owner-reminder grace, and the default overdue window. */
|
|
613
604
|
const REPLY_OBLIGATION_SWEEP_MS = 30_000;
|
|
614
605
|
const REPLY_REMINDER_GRACE_MS = 60_000;
|
|
615
|
-
const DEFAULT_REPLY_OVERDUE_MINUTES = 15;
|
|
616
606
|
/**
|
|
617
607
|
* The filter `list_emojis` and `list_stickers` take (#1226). Values may come from the agent CLI as strings, so a
|
|
618
608
|
* limit is read as a number and the flags accept "true". No limit means all of them — never zero.
|
|
@@ -676,32 +666,6 @@ export class FleetManager {
|
|
|
676
666
|
laneFree: name => !this.isInstanceRestarting(name) && !this.activeDurableTargets.has(name)
|
|
677
667
|
&& outbox.countForTarget(name, ["queued", "retry_wait", "delivering", "submission_started", "reconciliation_pending"]) === 0,
|
|
678
668
|
admit: (episode, tasks, advisory) => outbox.admitWorkingRecovery(episode, buildWorkingRecoveryPrompt(tasks, this.workingRecoveryCause, advisory), advisory),
|
|
679
|
-
summary: (group, rows) => {
|
|
680
|
-
const general = this.findGeneralInstance();
|
|
681
|
-
if (!general || !this.daemons.has(general))
|
|
682
|
-
return;
|
|
683
|
-
// Our own informational notice must not supersede General's recovery
|
|
684
|
-
// as unrelated pending work before it crosses the input boundary.
|
|
685
|
-
const generalEpisode = store.get(general);
|
|
686
|
-
if (generalEpisode?.outcome === "admitted" && generalEpisode.deliveryId) {
|
|
687
|
-
const state = outbox.get(generalEpisode.deliveryId)?.state;
|
|
688
|
-
if (!state || !["delivered", "uncertain", "failed", "cancelled"].includes(state))
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
const deferredAdvisories = rows.filter(row => row.outcome === "deferred"
|
|
692
|
-
&& (this.daemons.get(row.instance)?.getWorkingRecoveryState?.().background ?? null) === null)
|
|
693
|
-
.map(row => `${row.instance} (${row.reason})`);
|
|
694
|
-
const posted = store.summary(group, () => outbox.admitSystemNotice(general, `working-recovery-summary:${group}`, `[system:working-recovery-summary] Observed cause: unknown interruption${this.workingRecoveryCause === "unknown_interruption" ? "" : `; prior requested action: ${this.workingRecoveryCause}`}. ${rows.length} interrupted instances; continuation queued ${rows.filter(r => r.outcome === "admitted").length}, excluded ${rows.filter(r => r.outcome === "excluded").length}, still deferred ${rows.filter(r => r.outcome === "deferred").length}. ${rows.filter(r => r.reason === "advisory_background_unknown").length ? `Advisory, background unknown: ${rows.filter(r => r.reason === "advisory_background_unknown").map(r => r.instance).join(", ")}. ` : ""}${deferredAdvisories.length ? `Deferred advisory candidates (background unknown; all other gates still apply): ${deferredAdvisories.join(", ")}. ` : ""}Details are in the Log. Deferred work remains eligible; no action or confirmation is requested.`));
|
|
695
|
-
if (posted) {
|
|
696
|
-
const counts = { interrupted: rows.length, queued: rows.filter(row => row.outcome === "admitted").length,
|
|
697
|
-
excluded: rows.filter(row => row.outcome === "excluded").length, deferred: rows.filter(row => row.outcome === "deferred").length };
|
|
698
|
-
const requestedAction = this.workingRecoveryCause === "unknown_interruption" ? null : this.workingRecoveryCause;
|
|
699
|
-
this.logger.info({ group, ...counts, observedCause: "unknown_interruption", requestedAction }, "Interrupted work recovery summary queued for General");
|
|
700
|
-
this.eventLog?.insert(general, "working_state", { reason: "recovery_summary", group, ...counts,
|
|
701
|
-
observedCause: "unknown_interruption", requestedAction,
|
|
702
|
-
message: `Interrupted work summary queued for General: ${counts.interrupted} interrupted; ${counts.queued} queued, ${counts.excluded} excluded, ${counts.deferred} deferred.` });
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
669
|
log: (episode, outcome, reason) => {
|
|
706
670
|
const requestedAction = this.workingRecoveryCause === "unknown_interruption" ? null : this.workingRecoveryCause;
|
|
707
671
|
this.logger.info({ name: episode.instance, episode: episode.key, outcome, reason,
|
|
@@ -825,8 +789,6 @@ export class FleetManager {
|
|
|
825
789
|
costGuard = null;
|
|
826
790
|
statuslineWatcher;
|
|
827
791
|
dailySummary = null;
|
|
828
|
-
/** #2246 step 3: the task board's daily summary for the person (task-board-summary.ts). */
|
|
829
|
-
taskBoardSummary = null;
|
|
830
792
|
dailyTipScheduler = null;
|
|
831
793
|
/** The task board's wakes (step 2): an instance hears when someone else changes a card it owns or handed out. */
|
|
832
794
|
taskBoardWaker = null;
|
|
@@ -937,6 +899,12 @@ export class FleetManager {
|
|
|
937
899
|
kiroMigrationRuns = new Map();
|
|
938
900
|
/** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
|
|
939
901
|
needsYou = null;
|
|
902
|
+
configHistory = null;
|
|
903
|
+
configHistoryTimer = null;
|
|
904
|
+
loadedFleetSource = null;
|
|
905
|
+
getConfigHistory() {
|
|
906
|
+
return this.configHistory ??= new ConfigHistory(this.dataDir, () => this.needsYou?.poke(), message => this.logger.warn(message));
|
|
907
|
+
}
|
|
940
908
|
/** When the fleet first saw an instance crashed, for its "Needs you" age; cleared when it is not. */
|
|
941
909
|
needsCrashedAt = new Map();
|
|
942
910
|
/** Pause reason/time read from the marker, per paused instance; dropped on any attention change of it, and at most
|
|
@@ -1012,11 +980,6 @@ export class FleetManager {
|
|
|
1012
980
|
collabInstances = new Set();
|
|
1013
981
|
// Health endpoint
|
|
1014
982
|
healthServer = null;
|
|
1015
|
-
/** #2118: independent of HTML preview availability/port; fixed bootstrap only, no effect resources. */
|
|
1016
|
-
effectBootstrap = createEffectBootstrap();
|
|
1017
|
-
/** Fixed trusted document coherence, independent of the HTML preview listener; never a resource grant. */
|
|
1018
|
-
get effectBoot() { return this.effectBootstrap.boot; }
|
|
1019
|
-
canvasWorkerEnabled() { return canvasWorkerEnabled(this.fleetConfig?.web); }
|
|
1020
983
|
healthServerDrain = null;
|
|
1021
984
|
/** #1306: the preview listener (health_port + 1 by default), and whether it is listening. */
|
|
1022
985
|
previewListener = null;
|
|
@@ -1195,7 +1158,7 @@ export class FleetManager {
|
|
|
1195
1158
|
/** #1629: every instance's theme, placements, assets and lock (web-themes.json). */
|
|
1196
1159
|
get themeStore() {
|
|
1197
1160
|
// r4.1 Q3 (b): the card's limits are read from fleet.yaml at each upload (a change applies without a restart).
|
|
1198
|
-
return (this.themeStoreInstance ??= new ThemeStore(this.dataDir, { limits: () => effectiveAppearanceLimits(this.fleetConfig?.web?.appearance).limits
|
|
1161
|
+
return (this.themeStoreInstance ??= new ThemeStore(this.dataDir, { limits: () => effectiveAppearanceLimits(this.fleetConfig?.web?.appearance).limits }));
|
|
1199
1162
|
}
|
|
1200
1163
|
officeStoreInstance;
|
|
1201
1164
|
/** Fleet → Org chart → Office: the offices people made (web-offices.json, beside web-themes.json). */
|
|
@@ -1207,6 +1170,12 @@ export class FleetManager {
|
|
|
1207
1170
|
officeSkinWrites = new OfficeWriteLimiter(undefined, { perWindow: 10, windowMs: 60_000 });
|
|
1208
1171
|
/** How often a person may save the offices (PUT /ui/offices); memory only, as the appearance limiter. */
|
|
1209
1172
|
officeWrites = new OfficeWriteLimiter();
|
|
1173
|
+
/** #2855: a guest's preference saves, per account. */
|
|
1174
|
+
guestPrefWrites = new OfficeWriteLimiter(undefined, GUEST_PREF_WRITES);
|
|
1175
|
+
/** #2855: a guest's commands, per account. */
|
|
1176
|
+
guestCommandRuns = new OfficeWriteLimiter(undefined, GUEST_COMMAND_RUNS);
|
|
1177
|
+
/** #2855: a trusted guest's searches, per account. */
|
|
1178
|
+
guestSearches = new OfficeWriteLimiter(undefined, { perWindow: GUEST_SEARCHES.perWindow, windowMs: GUEST_SEARCHES.windowMs });
|
|
1210
1179
|
officeSharesInstance;
|
|
1211
1180
|
/**
|
|
1212
1181
|
* #2636: Office share links — the live Office view for people outside the fleet, read-only, on a quick tunnel of
|
|
@@ -1402,96 +1371,16 @@ export class FleetManager {
|
|
|
1402
1371
|
}
|
|
1403
1372
|
catch { /* the next frame carries the rev */ } }));
|
|
1404
1373
|
}
|
|
1405
|
-
effectLibraryInstance;
|
|
1406
|
-
effectTrialsInstance;
|
|
1407
|
-
/** Lazy, single-owner storage. Register every host reference source before any operation can collect. */
|
|
1408
|
-
get effectLibrary() {
|
|
1409
|
-
if (!this.effectLibraryInstance) {
|
|
1410
|
-
const library = new WebEffectLibrary(this.dataDir);
|
|
1411
|
-
// Providers are lazy; publish only after every registration succeeds.
|
|
1412
|
-
library.registerReferenceSource("themes", () => {
|
|
1413
|
-
if (!this.themeStore.readable())
|
|
1414
|
-
throw new Error("themes_unreadable");
|
|
1415
|
-
return this.themeStore.placedEffects().flatMap(row => row.ids.map(id => ({ id, holder: `instance:${row.name}` })));
|
|
1416
|
-
});
|
|
1417
|
-
library.registerReferenceSource("prefs", () => this.webPrefs.ownerEffectReferences());
|
|
1418
|
-
library.registerReferenceSource("mods", () => {
|
|
1419
|
-
const mods = this.mods.list();
|
|
1420
|
-
if (mods.unreadable)
|
|
1421
|
-
throw new Error("mods_unreadable");
|
|
1422
|
-
return mods.mods.flatMap(mod => mod.effects.map(effect => ({ id: effect.id, holder: `mod:${mod.id}` })));
|
|
1423
|
-
});
|
|
1424
|
-
this.effectTrialsInstance = new WebEffectTrials({ library, known: name => this.isKnownInstance(name) });
|
|
1425
|
-
this.effectLibraryInstance = library;
|
|
1426
|
-
}
|
|
1427
|
-
return this.effectLibraryInstance;
|
|
1428
|
-
}
|
|
1429
|
-
/** Volatile Try offers and session-bound runs share this library. */
|
|
1430
|
-
get effectTrials() { if (!this.effectTrialsInstance)
|
|
1431
|
-
void this.effectLibrary; return this.effectTrialsInstance; }
|
|
1432
|
-
effectProfilesInstance;
|
|
1433
|
-
get effectProfiles() { return (this.effectProfilesInstance ??= new WebEffectProfiles(this.dataDir)); }
|
|
1434
|
-
effectServiceInstance;
|
|
1435
|
-
get effectService() {
|
|
1436
|
-
return (this.effectServiceInstance ??= new WebEffectService({ library: this.effectLibrary, themes: this.themeStore, profiles: this.effectProfiles, prefs: this.webPrefs, trials: this.effectTrials,
|
|
1437
|
-
known: name => this.isKnownInstance(name), changed: () => { try {
|
|
1438
|
-
this.emitSseEvent("status", this.getUiStatus());
|
|
1439
|
-
}
|
|
1440
|
-
catch { /* next status carries revision */ } } }));
|
|
1441
|
-
}
|
|
1442
|
-
effectToolsService;
|
|
1443
|
-
/** Caller identity is supplied only by the owned IPC connection / verified agent endpoint token. */
|
|
1444
|
-
get effectTools() {
|
|
1445
|
-
return (this.effectToolsService ??= new WebEffectTools({
|
|
1446
|
-
library: this.effectLibrary, limiter: this.webAppearance.limiter,
|
|
1447
|
-
authorize: (name, buckets) => !this.isKnownInstance(name) ? { error: "unknown_instance" } : this.webAppearance.authorizeEffect(name, buckets),
|
|
1448
|
-
placed: name => this.themeStore.get(name).placements.effects,
|
|
1449
|
-
place: (name, effects) => {
|
|
1450
|
-
const before = this.themeStore.get(name).placements.effects;
|
|
1451
|
-
const explicit = new Set(effects.filter(e => typeof e !== "string").map(e => e.id));
|
|
1452
|
-
const w = this.themeStore.setPlacements(name, { effects }, "agent", what => {
|
|
1453
|
-
// An unchanged human/private placement may be retained, never exposed or newly acquired by an agent.
|
|
1454
|
-
const r = explicit.has(what.id) ? this.effectLibrary.read(what.id, { kind: "agent", id: name })
|
|
1455
|
-
: this.effectLibrary.readReferenced(what.id, `instance:${name}`);
|
|
1456
|
-
if (!r.ok)
|
|
1457
|
-
return r.error;
|
|
1458
|
-
const params = explicit.has(what.id) ? what.params : before.find(e => e.id === what.id)?.params;
|
|
1459
|
-
return resolveEffectParams(r.entry.manifest, params ?? {}).ok ? null : "effect_params";
|
|
1460
|
-
});
|
|
1461
|
-
return w.ok ? { ok: true, rev: w.rev } : { ok: false, error: w.error };
|
|
1462
|
-
},
|
|
1463
|
-
changed: () => { try {
|
|
1464
|
-
this.emitSseEvent("status", this.getUiStatus());
|
|
1465
|
-
}
|
|
1466
|
-
catch { /* later status carries placements */ } },
|
|
1467
|
-
// A nonmutating own/shared offer is not an appearance write. Only an explicit non-public admin-session click may start it.
|
|
1468
|
-
preview: async (instance, id) => {
|
|
1469
|
-
const result = this.effectTrials.offer(instance, id);
|
|
1470
|
-
if (!result.ok)
|
|
1471
|
-
return { error: "not_found" };
|
|
1472
|
-
const present = this.webChatHistory.list(instance).some(row => row.effectTrial?.offer === result.offer.offer);
|
|
1473
|
-
if (!present && !this.publishEffectTrial(instance, result.offer))
|
|
1474
|
-
return { error: "trial_unavailable" };
|
|
1475
|
-
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." };
|
|
1476
|
-
},
|
|
1477
|
-
profiles: id => this.effectProfiles.read(id),
|
|
1478
|
-
profileSummary: id => this.effectService.profileSummary(id),
|
|
1479
|
-
profileSummaries: ids => this.effectService.profileSummaries(ids),
|
|
1480
|
-
playbackSummary: (name, id) => this.effectService.playbackSummary(name, id),
|
|
1481
|
-
}));
|
|
1482
|
-
}
|
|
1483
1374
|
appearanceToolsService;
|
|
1484
1375
|
/** #1629 r4 §1: get_web_appearance / preview_web_appearance — the caller's own look, resolved as the page resolves it. */
|
|
1485
1376
|
get appearanceTools() {
|
|
1486
1377
|
return (this.appearanceToolsService ??= new AppearanceTools({
|
|
1487
|
-
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
1488
1378
|
frame: (name) => (this.getUiStatus().instances.find(i => i.name === name) ?? { name }),
|
|
1489
1379
|
theme: (name) => {
|
|
1490
1380
|
const r = this.themeStore.get(name);
|
|
1491
1381
|
return { theme: r.theme, locked: r.locked, widgets: r.placements.widgets, ambient: r.placements.ambient,
|
|
1492
1382
|
glyphAsset: (id) => this.themeStore.asset(name, id) };
|
|
1493
1383
|
},
|
|
1494
|
-
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) })) : [],
|
|
1495
1384
|
card: (name) => resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card),
|
|
1496
1385
|
// #1958: the host's identity-colour settings, as the page reads them (an unreadable prefs file: the defaults).
|
|
1497
1386
|
colorPrefs: () => { try {
|
|
@@ -1520,49 +1409,17 @@ export class FleetManager {
|
|
|
1520
1409
|
return Object.hasOwn(this.fleetConfig?.instances ?? {}, name) || (this.classicChannels?.getAll() ?? []).some((ch) => ch.instanceName === name);
|
|
1521
1410
|
}
|
|
1522
1411
|
modStore;
|
|
1523
|
-
/**
|
|
1524
|
-
* #1629 r4-5 / B2: installed mods use the same validated library and scoped holder claims as direct uploads.
|
|
1525
|
-
*/
|
|
1412
|
+
/** #1629 r4-5: installed mods (#2799: a bundle that carries Canvas Worker effects is refused). */
|
|
1526
1413
|
get mods() {
|
|
1527
1414
|
return (this.modStore ??= new ModStore(this.dataDir, {
|
|
1528
1415
|
themes: this.themeStore,
|
|
1529
1416
|
prefs: this.webPrefs,
|
|
1530
1417
|
ownerCss: this.ownerCss,
|
|
1531
|
-
|
|
1532
|
-
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
1533
|
-
context: () => ({ agendVersion: agendVersion(), limits: this.themeStore.limits(), effects: cw1EffectChecker() }),
|
|
1418
|
+
context: () => ({ agendVersion: agendVersion(), limits: this.themeStore.limits() }),
|
|
1534
1419
|
isKnownInstance: (name) => this.isKnownInstance(name),
|
|
1535
1420
|
prefsIdentity: OPERATOR,
|
|
1536
1421
|
}));
|
|
1537
1422
|
}
|
|
1538
|
-
agentModsService;
|
|
1539
|
-
/** #1629 r4-5b: export_web_mod / propose_web_mod — the caller's own look as a file, and a proposal for a person. */
|
|
1540
|
-
get agentMods() {
|
|
1541
|
-
return (this.agentModsService ??= new AgentMods({
|
|
1542
|
-
themes: this.themeStore, mods: this.mods, limiter: this.webAppearance.limiter,
|
|
1543
|
-
webConfig: () => this.fleetConfig?.web ?? null,
|
|
1544
|
-
// The mod store's own: a proposal is checked exactly as the person's import of it will be.
|
|
1545
|
-
context: () => this.mods.checkContext(),
|
|
1546
|
-
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; },
|
|
1547
|
-
notice: (instance, text) => { try {
|
|
1548
|
-
this.emitSseEvent("message", { instance, sender: instance, role: "status", text, ts: new Date().toISOString() });
|
|
1549
|
-
}
|
|
1550
|
-
catch { /* the page reads it from Settings too */ } },
|
|
1551
|
-
changed: () => { try {
|
|
1552
|
-
this.emitSseEvent("status", this.getUiStatus());
|
|
1553
|
-
}
|
|
1554
|
-
catch { /* the next frame */ } },
|
|
1555
|
-
}));
|
|
1556
|
-
}
|
|
1557
|
-
/**
|
|
1558
|
-
* #1629 r4-5c: an effect as this person may carry it away in an export — their own claim, or one a person shared —
|
|
1559
|
-
* read with their principal (Sol's ACL). Being able to view or run a placed effect never grants packaging someone
|
|
1560
|
-
* else's private claim: the library's reference read is runtime access, not an export path. Null: not theirs.
|
|
1561
|
-
*/
|
|
1562
|
-
modPlacedEffect(person, id) {
|
|
1563
|
-
const r = this.effectLibrary.read(id, { kind: "human", id: person });
|
|
1564
|
-
return r.ok ? { name: r.entry.manifest.name, files: r.entry.files } : null;
|
|
1565
|
-
}
|
|
1566
1423
|
/** #1629 r4-5: an instance's card text as every viewer sees it (a person's fields over the agent's). */
|
|
1567
1424
|
modCardText(name) {
|
|
1568
1425
|
const card = resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card);
|
|
@@ -1585,8 +1442,7 @@ export class FleetManager {
|
|
|
1585
1442
|
*/
|
|
1586
1443
|
get webSettings() {
|
|
1587
1444
|
return (this.webSettingsToolsService ??= new WebSettingsTools({
|
|
1588
|
-
|
|
1589
|
-
prefs: this.webPrefs, ownerCss: this.ownerCss, mods: this.mods, effects: this.effectService,
|
|
1445
|
+
prefs: this.webPrefs, ownerCss: this.ownerCss,
|
|
1590
1446
|
instances: () => [...Object.keys(this.fleetConfig?.instances ?? {}),
|
|
1591
1447
|
...(this.classicChannels?.getAll() ?? []).map((ch) => ch.instanceName).filter((n) => !Object.hasOwn(this.fleetConfig?.instances ?? {}, n))],
|
|
1592
1448
|
colour: (name) => personIdentityColour(this.dataDir, name),
|
|
@@ -1595,7 +1451,6 @@ export class FleetManager {
|
|
|
1595
1451
|
bannerFocus: (name) => personBannerFocus(this.dataDir, name),
|
|
1596
1452
|
setBannerFocus: (name, focus) => { const w = setPersonBannerFocus(this.dataDir, name, focus); if (w.ok)
|
|
1597
1453
|
this.identityChanged(); return w; },
|
|
1598
|
-
placements: (name) => this.themeStore.get(name).placements.effects.map((e) => (typeof e === "string" ? { id: e } : e)),
|
|
1599
1454
|
ambient: (name) => Object.fromEntries(Object.entries(this.themeStore.get(name).placements.ambient).flatMap(([k, a]) => a ? [[k, { spec: a.spec, by: a.by }]] : [])),
|
|
1600
1455
|
setAmbient: (name, spec, which) => this.webAppearance.humanSetAmbient(name, spec, which),
|
|
1601
1456
|
announce: () => { try {
|
|
@@ -1909,7 +1764,7 @@ export class FleetManager {
|
|
|
1909
1764
|
retire();
|
|
1910
1765
|
continue;
|
|
1911
1766
|
}
|
|
1912
|
-
if (store.attachPrompt(view.id, retire))
|
|
1767
|
+
if (store.attachPrompt(view.id, retire, { platform: adapter.type, connection: adapterId }))
|
|
1913
1768
|
return;
|
|
1914
1769
|
}
|
|
1915
1770
|
catch {
|
|
@@ -2436,12 +2291,10 @@ export class FleetManager {
|
|
|
2436
2291
|
const notice = silent
|
|
2437
2292
|
? t(`delivery.fleet_${row.state}_silent_schedule`, row.targetInstance, row.operationId, row.deliveryId, row.lastError ?? "unknown")
|
|
2438
2293
|
: t(`delivery.fleet_${row.state}`, row.targetInstance, row.operationId, row.deliveryId);
|
|
2294
|
+
// #2804: log-only. The person sees a failed or unconfirmed delivery as a Needs-you item (with Acknowledge),
|
|
2295
|
+
// not as a separate General post; the sending agent still gets its delivery-outcome notice.
|
|
2439
2296
|
this.logger.error({ deliveryId: row.deliveryId, operationId: row.operationId, target: row.targetInstance, state: row.state,
|
|
2440
2297
|
cause: row.lastError, ...(silent ? { silentSchedule: true } : {}) }, notice);
|
|
2441
|
-
// A self-scheduled silent heartbeat stays log-only, including the
|
|
2442
|
-
// operator alert; another source still receives the normal notice.
|
|
2443
|
-
if (!silent || row.sourceInstance !== row.targetInstance)
|
|
2444
|
-
this.notifyFleetError(notice);
|
|
2445
2298
|
}
|
|
2446
2299
|
}
|
|
2447
2300
|
});
|
|
@@ -2473,8 +2326,9 @@ export class FleetManager {
|
|
|
2473
2326
|
if (event.count)
|
|
2474
2327
|
this.needsYou?.poke(); // #1386: rows that expired failed — they wait on someone now
|
|
2475
2328
|
this.wakeCoordinator?.kick();
|
|
2329
|
+
// #2804: the expired rows are Needs-you items now; the count is logged, not posted to General.
|
|
2476
2330
|
if (event.count)
|
|
2477
|
-
this.
|
|
2331
|
+
this.logger.warn({ count: event.count, uncertain: event.uncertain ?? 0 }, t("delivery.expired", event.count, event.uncertain ?? 0));
|
|
2478
2332
|
});
|
|
2479
2333
|
this.scheduleDeliveryOutboxPump();
|
|
2480
2334
|
}
|
|
@@ -2604,27 +2458,8 @@ export class FleetManager {
|
|
|
2604
2458
|
const daemon = this.daemons.get(name);
|
|
2605
2459
|
if (daemon?.bootId === daemonBootId)
|
|
2606
2460
|
this.listenForWorkingState(name, daemon);
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
return;
|
|
2610
|
-
for (const prior of outbox.getUnansweredAccepted(name, daemonBootId)) {
|
|
2611
|
-
try {
|
|
2612
|
-
outbox.admitPostRestartOutcomeNotice(prior, daemonBootId);
|
|
2613
|
-
}
|
|
2614
|
-
catch (err) {
|
|
2615
|
-
this.logger.error({ err, source: name, operationId: prior.operationId }, "Could not persist post-restart delivery outcome notice");
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
// #926: a restart kills the turn that was working on a request, and a
|
|
2619
|
-
// resumed CLI does not pick it back up. Remind the owner once per restart.
|
|
2620
|
-
try {
|
|
2621
|
-
const now = new Date();
|
|
2622
|
-
this.expireReplyObligations(now);
|
|
2623
|
-
outbox.remindReplyObligations(name, { reason: "restart", graceMs: 0, now, since: now, restartId: daemonBootId });
|
|
2624
|
-
}
|
|
2625
|
-
catch (err) {
|
|
2626
|
-
this.logger.error({ err, owner: name }, "Could not persist the post-restart reply reminder");
|
|
2627
|
-
}
|
|
2461
|
+
// #2802: no post-restart notices. The outbox still delivers or fails a row accepted before the restart, and an open
|
|
2462
|
+
// request is reminded by the ordinary turn-ended nudge (sweepReplyObligations) once the owner is idle.
|
|
2628
2463
|
this.scheduleDeliveryOutboxPump();
|
|
2629
2464
|
}
|
|
2630
2465
|
workingStateListeners;
|
|
@@ -2708,9 +2543,8 @@ export class FleetManager {
|
|
|
2708
2543
|
}
|
|
2709
2544
|
}
|
|
2710
2545
|
/**
|
|
2711
|
-
* #926:
|
|
2712
|
-
*
|
|
2713
|
-
* notice when the owner is not working and nothing came back in time.
|
|
2546
|
+
* #926: idle owners with an unanswered request get one reminder (after the grace). #2802: the only reply notice —
|
|
2547
|
+
* requesters are no longer told about overdue or expired asks (they can check delivery_status / describe_instance).
|
|
2714
2548
|
*/
|
|
2715
2549
|
sweepReplyObligations(now = new Date()) {
|
|
2716
2550
|
const outbox = this.deliveryOutbox;
|
|
@@ -2721,10 +2555,8 @@ export class FleetManager {
|
|
|
2721
2555
|
this.expireReplyObligations(now);
|
|
2722
2556
|
for (const name of this.daemons.keys()) {
|
|
2723
2557
|
if (stateOf(name) === "idle")
|
|
2724
|
-
outbox.remindReplyObligations(name, { now, graceMs: REPLY_REMINDER_GRACE_MS
|
|
2558
|
+
outbox.remindReplyObligations(name, { now, graceMs: REPLY_REMINDER_GRACE_MS });
|
|
2725
2559
|
}
|
|
2726
|
-
const minutes = this.fleetConfig?.defaults?.reply_overdue_minutes ?? DEFAULT_REPLY_OVERDUE_MINUTES;
|
|
2727
|
-
outbox.notifyOverdueReplyObligations({ now, overdueMs: minutes * 60_000, ownerIdle: name => stateOf(name) !== "working" });
|
|
2728
2560
|
}
|
|
2729
2561
|
catch (err) {
|
|
2730
2562
|
this.logger.error({ err }, "Reply-obligation sweep failed");
|
|
@@ -2981,7 +2813,9 @@ export class FleetManager {
|
|
|
2981
2813
|
if ((current?.state === "delivering" || current?.state === "submission_started")
|
|
2982
2814
|
&& current.targetDaemonBootId === claimed.targetDaemonBootId
|
|
2983
2815
|
&& current.managerBootId === claimed.managerBootId) {
|
|
2984
|
-
|
|
2816
|
+
// #2804: logged, not posted. A delivery still in flight needs nobody yet; if it ends failed or unconfirmed it
|
|
2817
|
+
// becomes a Needs-you item.
|
|
2818
|
+
this.logger.warn({ deliveryId, target: claimed.targetInstance }, t("delivery.lane_waiting", claimed.targetInstance, Math.round(DURABLE_DELIVERY_LANE_ALERT_MS / 60_000), deliveryId));
|
|
2985
2819
|
}
|
|
2986
2820
|
}, DURABLE_DELIVERY_LANE_ALERT_MS);
|
|
2987
2821
|
alertTimer.unref?.();
|
|
@@ -3153,7 +2987,8 @@ export class FleetManager {
|
|
|
3153
2987
|
// fleet.yaml, sent SIGHUP, and got no reaction and no explanation.
|
|
3154
2988
|
this.logger.error({ err }, "SIGHUP config reload failed");
|
|
3155
2989
|
const message = err instanceof Error ? err.message : String(err);
|
|
3156
|
-
|
|
2990
|
+
if (!(err instanceof ConfigSyntaxError))
|
|
2991
|
+
this.notifyFleetError(t("fleet.reload_failed", message));
|
|
3157
2992
|
});
|
|
3158
2993
|
}
|
|
3159
2994
|
/**
|
|
@@ -3246,6 +3081,7 @@ export class FleetManager {
|
|
|
3246
3081
|
if (this.reloadPending)
|
|
3247
3082
|
this.scheduleReconcile();
|
|
3248
3083
|
void this.sweepOrphanedCancelButtons();
|
|
3084
|
+
void this.parkedNotices.sweep();
|
|
3249
3085
|
}
|
|
3250
3086
|
/**
|
|
3251
3087
|
* We are the post-update fleet: the update is over by definition. Clearing it here (rather than in the update
|
|
@@ -3389,19 +3225,19 @@ export class FleetManager {
|
|
|
3389
3225
|
};
|
|
3390
3226
|
}
|
|
3391
3227
|
/** Load fleet.yaml and build routing table */
|
|
3392
|
-
loadConfig(configPath) {
|
|
3228
|
+
loadConfig(configPath, options = {}) {
|
|
3393
3229
|
this.configPath = configPath;
|
|
3394
|
-
const
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
const
|
|
3400
|
-
const loaded = loadFleetConfig(configPath);
|
|
3230
|
+
const history = this.getConfigHistory();
|
|
3231
|
+
history.track("fleet.yaml", configPath);
|
|
3232
|
+
const selected = existsSync(configPath) ? history.read("fleet.yaml", configPath, options.startup) : { source: "{}\n", fallback: false };
|
|
3233
|
+
this.rawFleetDocument = parseDocument(selected.source, { keepSourceTokens: true });
|
|
3234
|
+
const raw = loadRawFleetConfigSource(selected.source);
|
|
3235
|
+
const loaded = loadFleetConfigSource(selected.source);
|
|
3401
3236
|
this.assertProviderSecretEnvKeys(loaded);
|
|
3402
3237
|
this.rawFleetConfig = raw;
|
|
3403
3238
|
this.settingsRuntimeRevision++;
|
|
3404
3239
|
this.fleetConfig = loaded;
|
|
3240
|
+
this.loadedFleetSource = selected;
|
|
3405
3241
|
this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
|
|
3406
3242
|
this.warnAboutRemovedLoginMode(loaded);
|
|
3407
3243
|
this.enforceGuestSwitch();
|
|
@@ -3410,6 +3246,8 @@ export class FleetManager {
|
|
|
3410
3246
|
// Scope the path to its owning data directory so another inherited home's override cannot enable CW.
|
|
3411
3247
|
process.env.AGEND_FLEET_DATA_DIR = resolvePath(this.dataDir);
|
|
3412
3248
|
process.env.AGEND_FLEET_CONFIG_PATH = resolvePath(configPath);
|
|
3249
|
+
if (options.remember !== false && existsSync(configPath))
|
|
3250
|
+
history.accepted("fleet.yaml", configPath, selected);
|
|
3413
3251
|
return this.fleetConfig;
|
|
3414
3252
|
}
|
|
3415
3253
|
relayLoginModeWarned = false;
|
|
@@ -6694,10 +6532,12 @@ export class FleetManager {
|
|
|
6694
6532
|
dataDir: this.dataDir,
|
|
6695
6533
|
onWarn: message => this.logger.warn(message),
|
|
6696
6534
|
// #1620: fleet.yaml's web.session.local, read at every check — a Settings save or a reload applies at once
|
|
6697
|
-
// (shortening ends the sessions it now outlives). The public link's
|
|
6535
|
+
// (shortening ends the sessions it now outlives). The public link's own cap stays fixed.
|
|
6698
6536
|
localPolicy: () => localSessionPolicy(this.fleetConfig?.web?.session?.local),
|
|
6699
6537
|
// #1920 §5.4: a guest's, from web.guests.session (never longer than the surface's own).
|
|
6700
6538
|
guestPolicy: () => guestSessionPolicy(this.fleetConfig?.web?.guests?.session),
|
|
6539
|
+
// A session over the temporary link ends with the link (its TTL, at most 8 h on a quick tunnel).
|
|
6540
|
+
exposureEnd: id => this.publicWebLink?.endOf(id) ?? null,
|
|
6701
6541
|
});
|
|
6702
6542
|
this.enforceGuestSwitch(); // #1920: guests off while the fleet was down
|
|
6703
6543
|
}
|
|
@@ -6746,6 +6586,32 @@ export class FleetManager {
|
|
|
6746
6586
|
return this.previewTunnel ??= new ManagedTunnel({ dataDir: join(this.dataDir, PUBLIC_PREVIEW_TUNNEL_DIR),
|
|
6747
6587
|
log: () => this.logger.warn("Public preview tunnel cleanup requires attention") });
|
|
6748
6588
|
}
|
|
6589
|
+
namedLinkTestRunning = false;
|
|
6590
|
+
/**
|
|
6591
|
+
* #2906 (the user, 2026-11-01): Settings → Access → Test connection, on the fixed-address values as filled in (the
|
|
6592
|
+
* typed connector token, else the saved one). One at a time; the result is closed step codes only — the token is
|
|
6593
|
+
* never logged or returned. Admin only, never over the public link (settings-api.ts).
|
|
6594
|
+
*/
|
|
6595
|
+
async testNamedLink(input) {
|
|
6596
|
+
if (this.namedLinkTestRunning)
|
|
6597
|
+
return { busy: true };
|
|
6598
|
+
this.namedLinkTestRunning = true;
|
|
6599
|
+
try {
|
|
6600
|
+
const typed = typeof input.token === "string" && input.token.trim() ? input.token : null;
|
|
6601
|
+
const result = await runNamedLinkTest({ hostname: input.hostname, port: input.port, protocol: input.protocol, token: typed ?? process.env[PUBLIC_TUNNEL_TOKEN_ENV] ?? null }, {
|
|
6602
|
+
healthPort: this.fleetConfig?.health_port ?? 19280,
|
|
6603
|
+
ensureBinary: async (signal) => (await ensureCloudflared({ dataDir: this.dataDir, pinnedOnly: true, signal })).path,
|
|
6604
|
+
liveMarker: values => this.publicWebLink?.liveNamedMarker(values) ?? null,
|
|
6605
|
+
reserve: namedTestLane(this.getTunnelLane()),
|
|
6606
|
+
current: input.current,
|
|
6607
|
+
});
|
|
6608
|
+
this.logger[result.cleanup ? "warn" : "info"]({ steps: result.steps.map(s => `${s.step}:${s.code}`), typedToken: !!typed, ...(result.cleanup ? { cleanup: result.cleanup } : {}) }, result.cleanup ? "Named link test: the test connector's stop could not be proven; the tunnel lane stays held" : "Named link test");
|
|
6609
|
+
return result;
|
|
6610
|
+
}
|
|
6611
|
+
finally {
|
|
6612
|
+
this.namedLinkTestRunning = false;
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6749
6615
|
getTunnelLane() {
|
|
6750
6616
|
return this.tunnelPurposeLane ??= new TunnelPurposeLane(new ManagedTunnel({ dataDir: this.dataDir,
|
|
6751
6617
|
log: () => this.logger.warn("Managed tunnel cleanup requires attention") }));
|
|
@@ -7121,7 +6987,8 @@ export class FleetManager {
|
|
|
7121
6987
|
: "\n" + t("dashboard.public_status", status.state, status.remainingSeconds ?? 0)
|
|
7122
6988
|
+ (status.expiresAt ? t("dashboard.public_until", formatFleetTime(status.expiresAt)) : ""));
|
|
7123
6989
|
const choices = [{ action: "local", label: t("dashboard.local_signin") },
|
|
7124
|
-
...(publicLinkSettings(this.fleetConfig?.web).allowed
|
|
6990
|
+
...(publicLinkSettings(this.fleetConfig?.web).allowed
|
|
6991
|
+
? [{ action: "public", label: t(publicLinkNamedHostname(this.fleetConfig?.web) ? "dashboard.public_open_named" : "dashboard.public_open") }] : []),
|
|
7125
6992
|
...(status.state !== "closed" ? [{ action: "close", label: t("dashboard.public_close") }] : [])];
|
|
7126
6993
|
const posted = await this.postNonceButtonPrompt({
|
|
7127
6994
|
prefix: "dashboard:", alertType: "login", instanceName: "dashboard", adapter, adapterId: owner.adapterId,
|
|
@@ -7341,7 +7208,7 @@ export class FleetManager {
|
|
|
7341
7208
|
return { ...a, name: badge.name, platform: badge.platform, scope: [...this.guestScope(owner)].sort(), reach: [...this.guestReach(owner)].sort(),
|
|
7342
7209
|
devices: devices.get(k) ?? 0, narrow: own(cfg?.narrow, `${a.adapterId}:${a.userId}`) ?? null,
|
|
7343
7210
|
// #2348: the grant as fleet.yaml has it (null: none), and the last code an admin sent from Settings.
|
|
7344
|
-
grant: grant ? { ...(grant.name ? { name: grant.name } : {}), instances: [...grant.instances] } : null,
|
|
7211
|
+
grant: grant ? { ...(grant.name ? { name: grant.name } : {}), instances: [...grant.instances], ...(grant.level === "trusted" ? { level: "trusted" } : {}) } : null,
|
|
7345
7212
|
...(sent ? { code: { at: sent.at, outcome: sent.outcome.kind, ...(sent.outcome.kind === "sent" ? { expiresAt: sent.at + sent.outcome.expiresInMs } : {}),
|
|
7346
7213
|
...(sent.outcome.kind === "dm_failed" ? { reason: sent.outcome.reason, platform: sent.outcome.platform, ...(sent.outcome.startUrl ? { startUrl: sent.outcome.startUrl } : {}) } : {}) } } : {}) };
|
|
7347
7214
|
}).sort((x, y) => (y.lastSeen ?? -1) - (x.lastSeen ?? -1));
|
|
@@ -7389,7 +7256,7 @@ export class FleetManager {
|
|
|
7389
7256
|
const when = Date.parse(r.created_at.includes("T") ? r.created_at : `${r.created_at.replace(" ", "T")}Z`);
|
|
7390
7257
|
return r.event_type === "guest_grant_changed"
|
|
7391
7258
|
? [{ kind: "grant", at: when, account, name, via: p.via === "confirmed" ? "confirmed" : "direct", added: names(p.added), removed: names(p.removed),
|
|
7392
|
-
endedSessions: typeof p.endedSessions === "number" ? p.endedSessions : 0 }]
|
|
7259
|
+
endedSessions: typeof p.endedSessions === "number" ? p.endedSessions : 0, ...(p.level === "chat" || p.level === "trusted" ? { level: p.level } : {}) }]
|
|
7393
7260
|
: [{ kind: "code", at: when, account, name, outcome: typeof p.outcome === "string" ? p.outcome : "unknown" }];
|
|
7394
7261
|
});
|
|
7395
7262
|
}
|
|
@@ -7428,7 +7295,7 @@ export class FleetManager {
|
|
|
7428
7295
|
* confirmed in General, the agents added and taken away, and the sessions a removal ended. Never message content.
|
|
7429
7296
|
*/
|
|
7430
7297
|
noteGuestGrantChange(change) {
|
|
7431
|
-
this.logger.info({ account: change.account, via: change.via, added: change.added.length, removed: change.removed.length }, "Guest access changed in Settings");
|
|
7298
|
+
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");
|
|
7432
7299
|
try {
|
|
7433
7300
|
this.eventLog?.insert("fleet", "guest_grant_changed", { ...change });
|
|
7434
7301
|
}
|
|
@@ -7513,6 +7380,8 @@ export class FleetManager {
|
|
|
7513
7380
|
* #1920 §5.4: a guest's own `/web revoke` — its sessions and its outstanding code; nobody else's (an admin's revokes
|
|
7514
7381
|
* everyone's, as before).
|
|
7515
7382
|
*/
|
|
7383
|
+
/** #2855: the admin removed a guest — its own preferences (theme, language, favourites, sort, filter) go too. */
|
|
7384
|
+
forgetGuestPrefs(owner) { this.webPrefs.forgetGuest(webPersonKey(owner)); }
|
|
7516
7385
|
revokeGuestWebSessions(owner) {
|
|
7517
7386
|
this.initializeWebSessions();
|
|
7518
7387
|
const key = `${owner.adapterId}\u0000${owner.userId}`;
|
|
@@ -8170,7 +8039,7 @@ export class FleetManager {
|
|
|
8170
8039
|
setSdNotifyWarn((fields, message) => this.logger.warn(fields, message));
|
|
8171
8040
|
if (this.watchdogTimer)
|
|
8172
8041
|
clearInterval(this.watchdogTimer);
|
|
8173
|
-
this.watchdogTimer =
|
|
8042
|
+
this.watchdogTimer = startSdWatchdog();
|
|
8174
8043
|
this.eventLoopWatch?.stop();
|
|
8175
8044
|
this.eventLoopWatch = startEventLoopWatch({ logger: this.logger });
|
|
8176
8045
|
try {
|
|
@@ -8219,7 +8088,7 @@ export class FleetManager {
|
|
|
8219
8088
|
this.tmuxExecHelperTimer ??= setInterval(() => tmuxExecHelper.maintain(), 5_000);
|
|
8220
8089
|
this.tmuxExecHelperTimer.unref?.();
|
|
8221
8090
|
this.rotateFleetLogs();
|
|
8222
|
-
const fleet = this.loadConfig(configPath);
|
|
8091
|
+
const fleet = this.loadConfig(configPath, { startup: true });
|
|
8223
8092
|
this.slimFleetConfigAtStartup();
|
|
8224
8093
|
this.logConfigWarningsAtStartup();
|
|
8225
8094
|
setLocale(detectLocale(fleet)); // user-facing text language (fleet.yaml defaults.locale / timezone)
|
|
@@ -8297,7 +8166,7 @@ export class FleetManager {
|
|
|
8297
8166
|
// migrates legacy single-bot entries and names without a suffix. Classic
|
|
8298
8167
|
// routing does NOT go through the routing engine (single-key, can't hold two
|
|
8299
8168
|
// bots in one channel) — it resolves per-bot via getInstanceByChannel.
|
|
8300
|
-
this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger);
|
|
8169
|
+
this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger, this.getConfigHistory());
|
|
8301
8170
|
const classicAdapters = fleet.channels?.length ? fleet.channels : (fleet.channel ? [fleet.channel] : []);
|
|
8302
8171
|
this.classicChannels.configureAdapters(classicAdapters);
|
|
8303
8172
|
// The unrecoverable-id report is deliberately NOT sent here: no adapter
|
|
@@ -8315,6 +8184,9 @@ export class FleetManager {
|
|
|
8315
8184
|
// rooms), before the dashboard serves anything; from now on every change is written back (off the loop).
|
|
8316
8185
|
this.restoreWebChat(fleet);
|
|
8317
8186
|
this.startSearchIndex(fleet);
|
|
8187
|
+
// Parse-only source monitoring; it does not apply hand-edited Fleet settings.
|
|
8188
|
+
this.configHistoryTimer = setInterval(() => this.configHistory?.check(), 1_000);
|
|
8189
|
+
this.configHistoryTimer.unref?.();
|
|
8318
8190
|
// Poll classicBot.yaml for external changes every 30s
|
|
8319
8191
|
this.classicReloadTimer = setInterval(() => {
|
|
8320
8192
|
// Attributes the synchronous part, up to the reload's first await (#1235).
|
|
@@ -8346,7 +8218,8 @@ export class FleetManager {
|
|
|
8346
8218
|
...fleet.defaults.daily_summary,
|
|
8347
8219
|
};
|
|
8348
8220
|
this.dailySummary = new DailySummary(summaryConfig, costGuardConfig.timezone, (text) => {
|
|
8349
|
-
|
|
8221
|
+
if (text !== null)
|
|
8222
|
+
this.postDailySummary(text);
|
|
8350
8223
|
// Rotate classic channel chat logs daily
|
|
8351
8224
|
this.classicChannels?.rotateLogs();
|
|
8352
8225
|
this.rotateInboxes();
|
|
@@ -8364,17 +8237,6 @@ export class FleetManager {
|
|
|
8364
8237
|
return DailySummary.generateText(this.eventLog, instances, costMap, this.costGuard?.getFleetTotalCents() ?? 0);
|
|
8365
8238
|
});
|
|
8366
8239
|
this.dailySummary.start();
|
|
8367
|
-
// #2246 step 3: the task board's daily summary — its own time (defaults.task_board.summary_at, read at every look),
|
|
8368
|
-
// in the fleet's zone; a summary missed while the fleet was down goes out when it is back.
|
|
8369
|
-
this.taskBoardSummary = new TaskBoardSummaryClock({
|
|
8370
|
-
at: () => summaryTime(this.fleetConfig?.defaults?.task_board?.summary_at) ?? null,
|
|
8371
|
-
zone: () => this.usageZone(),
|
|
8372
|
-
now: () => new Date(),
|
|
8373
|
-
mark: summaryMarkFile(join(this.dataDir, "task-board-summary.json")),
|
|
8374
|
-
send: (since, until) => this.postTaskBoardSummary(since, until),
|
|
8375
|
-
failed: (err) => this.logger.warn({ err }, "The task board's daily summary failed; the next minute tries again"),
|
|
8376
|
-
});
|
|
8377
|
-
this.taskBoardSummary.start();
|
|
8378
8240
|
// Rotate classic channel chat logs daily (piggyback on daily summary timer)
|
|
8379
8241
|
this.classicChannels?.rotateLogs();
|
|
8380
8242
|
this.rotateInboxes();
|
|
@@ -8463,7 +8325,7 @@ export class FleetManager {
|
|
|
8463
8325
|
fleet.instances[name] = autoGeneralInstanceConfig(fleet.defaults, generalDir, need.adapterId); // #2598: its kiro_ui is the fleet's
|
|
8464
8326
|
generalsCreated = true;
|
|
8465
8327
|
}
|
|
8466
|
-
if (generalsCreated)
|
|
8328
|
+
if (generalsCreated && !this.configHistory?.isFallback(this.configPath))
|
|
8467
8329
|
this.saveFleetConfig();
|
|
8468
8330
|
if (topicMode && (fleet.channel || fleet.channels?.length)) {
|
|
8469
8331
|
const schedulerConfig = {
|
|
@@ -8639,7 +8501,7 @@ export class FleetManager {
|
|
|
8639
8501
|
fixedGeneral = true;
|
|
8640
8502
|
}
|
|
8641
8503
|
}
|
|
8642
|
-
if (fixedGeneral)
|
|
8504
|
+
if (fixedGeneral && !this.configHistory?.isFallback(this.configPath))
|
|
8643
8505
|
this.saveFleetConfig();
|
|
8644
8506
|
// Auto-create topics AFTER adapter is ready (needs adapter.createTopic)
|
|
8645
8507
|
await this.topicCommands.autoCreateTopics();
|
|
@@ -8734,8 +8596,6 @@ export class FleetManager {
|
|
|
8734
8596
|
}
|
|
8735
8597
|
await this.sendFleetStartCompletionFallback(String(fleet.channel.group_id), text, generalThreadId != null ? String(generalThreadId) : undefined);
|
|
8736
8598
|
}
|
|
8737
|
-
// After "fleet ready", so the notice is not the first thing people see of a restart.
|
|
8738
|
-
void this.announceWebChatOnce(agendVersion);
|
|
8739
8599
|
// #named-link-ttl: a named link with auto_open comes back now — after the web listener and the chat connections.
|
|
8740
8600
|
this.startPublicLinkAutoOpen();
|
|
8741
8601
|
}
|
|
@@ -10415,6 +10275,11 @@ export class FleetManager {
|
|
|
10415
10275
|
}
|
|
10416
10276
|
return;
|
|
10417
10277
|
}
|
|
10278
|
+
// #2870 (the user's choice): in a private chat, a command from someone ClassicBot does not serve here — not an
|
|
10279
|
+
// allowed user, not a ClassicBot admin, not a fleet admin, no room in this chat — gets no answer at all. /start
|
|
10280
|
+
// above still asks for access; a guest's /web is handled before it.
|
|
10281
|
+
if (isPrivateChat && text.startsWith("/") && this.isClassicPrivateStranger(msg.userId, chatId, msg.adapterId))
|
|
10282
|
+
return;
|
|
10418
10283
|
// Handle /stop command
|
|
10419
10284
|
if (text === "/stop" || text.startsWith("/stop ")) {
|
|
10420
10285
|
if (!this.classicChannels.isAdmin(msg.userId)) {
|
|
@@ -10870,34 +10735,6 @@ export class FleetManager {
|
|
|
10870
10735
|
* their fleet is organised. Both stay theirs — this only makes sure they are
|
|
10871
10736
|
* not discovered by an agent failing at three in the morning.
|
|
10872
10737
|
*/
|
|
10873
|
-
/**
|
|
10874
|
-
* #1366: the first time a fleet runs 2.2 or later, tell each chat platform's General — once — that its agents can
|
|
10875
|
-
* now be talked to from a browser. Claimed in upgrade-notices.json before the send, released if the send fails.
|
|
10876
|
-
* Platforms with nowhere to post fleet notices (no group, or a Discord fleet with no General channel) are skipped
|
|
10877
|
-
* and not recorded, so they are told once they have one.
|
|
10878
|
-
*/
|
|
10879
|
-
async announceWebChatOnce(version) {
|
|
10880
|
-
if (!hasWebChat(version))
|
|
10881
|
-
return;
|
|
10882
|
-
const path = upgradeNoticesPath(this.dataDir);
|
|
10883
|
-
for (const [adapterId, world] of this.worlds) {
|
|
10884
|
-
if (this.shuttingDown)
|
|
10885
|
-
return;
|
|
10886
|
-
const target = this.fleetNoticeTarget(adapterId);
|
|
10887
|
-
if (!target)
|
|
10888
|
-
continue;
|
|
10889
|
-
if (!claimNotice(path, WEB_CHAT_NOTICE, adapterId))
|
|
10890
|
-
continue;
|
|
10891
|
-
try {
|
|
10892
|
-
await world.adapter.sendText(target.chatId, t("upgrade.web_chat"), target.opts);
|
|
10893
|
-
this.logger.info({ adapterId, notice: WEB_CHAT_NOTICE }, "Announced web chat in General");
|
|
10894
|
-
}
|
|
10895
|
-
catch (err) {
|
|
10896
|
-
const released = releaseNotice(path, WEB_CHAT_NOTICE, adapterId);
|
|
10897
|
-
this.logger.warn({ err, adapterId, released }, "Could not announce web chat in General — will try at the next start");
|
|
10898
|
-
}
|
|
10899
|
-
}
|
|
10900
|
-
}
|
|
10901
10738
|
announceToolPermissionsChange() {
|
|
10902
10739
|
try {
|
|
10903
10740
|
const thirtyDaysAgo = new Date(Date.now() - 30 * 864e5).toISOString().slice(0, 19).replace("T", " ");
|
|
@@ -10917,8 +10754,8 @@ export class FleetManager {
|
|
|
10917
10754
|
}
|
|
10918
10755
|
}
|
|
10919
10756
|
checkToolPermission(sink, instanceName, tool) {
|
|
10920
|
-
if (isCanvasWorkerTool(tool)
|
|
10921
|
-
return { allowed: false, message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE };
|
|
10757
|
+
if (isCanvasWorkerTool(tool))
|
|
10758
|
+
return { allowed: false, message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }; // #2799: removed
|
|
10922
10759
|
const profile = resolveToolSet(this.fleetConfig?.instances[instanceName], instanceName);
|
|
10923
10760
|
const allowed = mayUseTool(profile, tool);
|
|
10924
10761
|
if (!allowed) {
|
|
@@ -11651,19 +11488,6 @@ export class FleetManager {
|
|
|
11651
11488
|
}
|
|
11652
11489
|
dispatchTypedIpc(name, msg) {
|
|
11653
11490
|
const type = String(msg.type);
|
|
11654
|
-
if (type.startsWith("fleet_") && WEB_EFFECT_TOOLS.has(type.slice(6))) {
|
|
11655
|
-
const ipc = this.instanceIpcClients.get(name), fleetRequestId = msg.fleetRequestId;
|
|
11656
|
-
if (!ipc || typeof fleetRequestId !== "string")
|
|
11657
|
-
return;
|
|
11658
|
-
void this.handleWebThemeHttp(name, type.slice(6), (msg.payload ?? {})).then(result => {
|
|
11659
|
-
if (this.instanceIpcClients.get(name) === ipc)
|
|
11660
|
-
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, result });
|
|
11661
|
-
}, err => {
|
|
11662
|
-
if (this.instanceIpcClients.get(name) === ipc)
|
|
11663
|
-
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, error: err instanceof Error ? err.message : "Effect operation failed" });
|
|
11664
|
-
}).catch(err => this.logger.debug({ err }, "Effect tool response could not be written"));
|
|
11665
|
-
return;
|
|
11666
|
-
}
|
|
11667
11491
|
if (type.startsWith("fleet_schedule_")) {
|
|
11668
11492
|
this.handleScheduleCrud(name, msg);
|
|
11669
11493
|
return;
|
|
@@ -11717,10 +11541,9 @@ export class FleetManager {
|
|
|
11717
11541
|
const args = (msg.payload ?? {});
|
|
11718
11542
|
const result = tool === "get_web_appearance" || tool === "preview_web_appearance"
|
|
11719
11543
|
? this.appearanceTools.handle(instanceName, tool, args)
|
|
11720
|
-
: tool === "
|
|
11721
|
-
: tool
|
|
11722
|
-
:
|
|
11723
|
-
: this.webAppearance.handleTool(instanceName, tool, args);
|
|
11544
|
+
: tool === "office_view" || tool === "office_set" ? this.officeActs.handle(instanceName, tool, args)
|
|
11545
|
+
: OFFICE_PACK_IPC.has(`fleet_${tool}`) ? this.officePackTools.handle(instanceName, tool, args)
|
|
11546
|
+
: this.webAppearance.handleTool(instanceName, tool, args);
|
|
11724
11547
|
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, result });
|
|
11725
11548
|
}
|
|
11726
11549
|
catch (err) {
|
|
@@ -12145,17 +11968,10 @@ export class FleetManager {
|
|
|
12145
11968
|
}
|
|
12146
11969
|
/** #1629: `agend-agent theme …` — the same service as the tools; `instance` is the token's. */
|
|
12147
11970
|
async handleWebThemeHttp(instance, tool, args) {
|
|
12148
|
-
if (isCanvasWorkerTool(tool)
|
|
12149
|
-
return { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE };
|
|
12150
|
-
if (WEB_EFFECT_TOOLS.has(tool)) {
|
|
12151
|
-
if (!this.isKnownInstance(instance))
|
|
12152
|
-
return { error: "unknown_instance" };
|
|
12153
|
-
return this.effectTools.handle(instance, tool, args);
|
|
12154
|
-
}
|
|
11971
|
+
if (isCanvasWorkerTool(tool))
|
|
11972
|
+
return { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }; // #2799: removed
|
|
12155
11973
|
if (tool === "get_web_appearance" || tool === "preview_web_appearance")
|
|
12156
11974
|
return this.appearanceTools.handle(instance, tool, args);
|
|
12157
|
-
if (tool === "export_web_mod" || tool === "propose_web_mod")
|
|
12158
|
-
return this.agentMods.handle(instance, tool, args);
|
|
12159
11975
|
if (tool === "office_view" || tool === "office_set")
|
|
12160
11976
|
return this.officeActs.handle(instance, tool, args); // #2578
|
|
12161
11977
|
if (OFFICE_PACK_IPC.has(`fleet_${tool}`))
|
|
@@ -12696,6 +12512,7 @@ export class FleetManager {
|
|
|
12696
12512
|
},
|
|
12697
12513
|
});
|
|
12698
12514
|
this.rawFleetConfig = loadRawFleetConfig(this.configPath);
|
|
12515
|
+
this.configHistory?.accepted("fleet.yaml", this.configPath, { source: output, fallback: false });
|
|
12699
12516
|
// The instance map is defaults-expanded. A defaults edit changes that view even when it writes no instance
|
|
12700
12517
|
// leaves. Re-resolve from the committed YAML so inherited values cannot become stale overrides on the next save.
|
|
12701
12518
|
// Keep the FleetConfig object itself: Settings and other request contexts hold it.
|
|
@@ -12774,6 +12591,8 @@ export class FleetManager {
|
|
|
12774
12591
|
}
|
|
12775
12592
|
/** One-time upgrade migration; invalid YAML is never rewritten. */
|
|
12776
12593
|
migrateNoticeDeliveryStorage() {
|
|
12594
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12595
|
+
return;
|
|
12777
12596
|
if (this.fleetConfig && this.configPath && (this.fleetConfig.web?.notify_login === "dm" || this.fleetConfig.defaults.tips === "dm"
|
|
12778
12597
|
|| this.fleetConfig.web?.notice_delivery !== undefined)
|
|
12779
12598
|
&& validateFleetConfig(this.rawFleetConfig).valid) {
|
|
@@ -12788,6 +12607,8 @@ export class FleetManager {
|
|
|
12788
12607
|
}
|
|
12789
12608
|
}
|
|
12790
12609
|
migrateOfficeStorage() {
|
|
12610
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12611
|
+
return;
|
|
12791
12612
|
if (!this.fleetConfig || !this.configPath || !hasLegacyOffice(this.rawFleetConfig) || !validateFleetConfig(this.rawFleetConfig).valid)
|
|
12792
12613
|
return;
|
|
12793
12614
|
try {
|
|
@@ -12811,6 +12632,8 @@ export class FleetManager {
|
|
|
12811
12632
|
}
|
|
12812
12633
|
}
|
|
12813
12634
|
slimFleetConfigAtStartup() {
|
|
12635
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12636
|
+
return;
|
|
12814
12637
|
this.migrateOfficeStorage();
|
|
12815
12638
|
this.migrateNoticeDeliveryStorage();
|
|
12816
12639
|
const redundantPaths = collectRedundantInstanceDefaultPaths(this.rawFleetConfig);
|
|
@@ -13032,9 +12855,7 @@ export class FleetManager {
|
|
|
13032
12855
|
this.themeStore.forget(name);
|
|
13033
12856
|
this.webAppearance.forget(name);
|
|
13034
12857
|
this.appearanceTools.forget(name);
|
|
13035
|
-
this.effectToolsService?.forget(name);
|
|
13036
12858
|
this.mods.forgetInstance(name);
|
|
13037
|
-
this.agentMods.forget(name);
|
|
13038
12859
|
this.officeActsInstance?.forget(name);
|
|
13039
12860
|
this.officePackToolsInstance?.forget(name);
|
|
13040
12861
|
}
|
|
@@ -14392,51 +14213,6 @@ export class FleetManager {
|
|
|
14392
14213
|
}
|
|
14393
14214
|
return { chatId, opts: {} };
|
|
14394
14215
|
}
|
|
14395
|
-
/**
|
|
14396
|
-
* #2246 step 3: the task board's daily summary of [since, until) (task-board-summary.ts). Each chat world's General
|
|
14397
|
-
* gets its own instances' cards — a card that no world's instance holds (nobody's, a person's) goes to the primary
|
|
14398
|
-
* one — and each of that world's Generals shows the same text in the web chat. A world with nothing to say gets
|
|
14399
|
-
* nothing. A web-only fleet: everything, in the web chat.
|
|
14400
|
-
*/
|
|
14401
|
-
postTaskBoardSummary(since, until) {
|
|
14402
|
-
const board = this.scheduler?.db.board;
|
|
14403
|
-
if (!board)
|
|
14404
|
-
return;
|
|
14405
|
-
const read = board.summaryRead(since.toISOString(), until.toISOString(), this.dispatcherGone);
|
|
14406
|
-
const projects = board.listProjects(true);
|
|
14407
|
-
const stats = board.projectStats(this.dispatcherGone);
|
|
14408
|
-
const names = new Map(projects.map((p) => [p.id, p.name]));
|
|
14409
|
-
const configured = this.getPrimaryAdapterId();
|
|
14410
|
-
const primary = configured !== undefined && this.worlds.has(configured) ? configured : this.worlds.keys().next().value;
|
|
14411
|
-
const worldOf = (assignee) => {
|
|
14412
|
-
const owner = assignee ? this.getInstanceAdapterId(assignee) : undefined;
|
|
14413
|
-
return owner !== undefined && this.worlds.has(owner) ? owner : primary;
|
|
14414
|
-
};
|
|
14415
|
-
const zone = this.usageZone();
|
|
14416
|
-
for (const world of this.worlds.size ? [...this.worlds.keys()] : [undefined]) {
|
|
14417
|
-
const summary = buildSummary(read, since, until, { inChat: (assignee) => worldOf(assignee) === world, projects, stats });
|
|
14418
|
-
if (isQuiet(summary))
|
|
14419
|
-
continue;
|
|
14420
|
-
const text = renderSummary(summary, { zone, t: (key, ...args) => t(key, ...args), projectOf: (c) => (c.projectId === null ? null : names.get(c.projectId) ?? null) });
|
|
14421
|
-
const cards = [...new Set([...summary.review, ...summary.blocked, ...summary.done, ...summary.created].map((c) => c.code))];
|
|
14422
|
-
if (world !== undefined) {
|
|
14423
|
-
const adapter = this.worlds.get(world)?.adapter;
|
|
14424
|
-
const target = this.fleetNoticeTarget(world);
|
|
14425
|
-
if (adapter && target) {
|
|
14426
|
-
adapter.sendText(target.chatId, text, target.opts).catch(e => this.logger.warn({ err: e, world }, "Failed to send the task board's daily summary"));
|
|
14427
|
-
}
|
|
14428
|
-
else {
|
|
14429
|
-
this.logger.warn({ world }, "The task board's daily summary has no postable target — set a General topic or channel.options.general_channel_id");
|
|
14430
|
-
}
|
|
14431
|
-
}
|
|
14432
|
-
for (const [name, cfg] of Object.entries(this.fleetConfig?.instances ?? {})) {
|
|
14433
|
-
if (cfg.general_topic !== true || (world !== undefined && this.getInstanceAdapterId(name) !== world))
|
|
14434
|
-
continue;
|
|
14435
|
-
// #2341 review: with the cards it names, so the public link withholds it while one of them is hidden there.
|
|
14436
|
-
this.emitSseEvent("message", { instance: name, sender: name, role: "status", text, ts: new Date().toISOString(), cards });
|
|
14437
|
-
}
|
|
14438
|
-
}
|
|
14439
|
-
}
|
|
14440
14216
|
/**
|
|
14441
14217
|
* Post the daily summary where fleet-wide notices go.
|
|
14442
14218
|
*
|
|
@@ -14532,11 +14308,22 @@ export class FleetManager {
|
|
|
14532
14308
|
* would have to make this async and change every caller.
|
|
14533
14309
|
*/
|
|
14534
14310
|
notifyInstanceTopic(instanceName, text, extraOpts) {
|
|
14311
|
+
const send = this.sendToInstanceTopic(instanceName, text, extraOpts);
|
|
14312
|
+
if (!send)
|
|
14313
|
+
return false;
|
|
14314
|
+
send.sent.catch(e => this.logger.warn({ err: e, instanceName }, send.failure));
|
|
14315
|
+
return true;
|
|
14316
|
+
}
|
|
14317
|
+
/**
|
|
14318
|
+
* notifyInstanceTopic's routing, handing back the send itself — and which world sent it — for a caller that must
|
|
14319
|
+
* edit or delete the message later (#2897). Null when there is no adapter or no postable target (already logged).
|
|
14320
|
+
*/
|
|
14321
|
+
sendToInstanceTopic(instanceName, text, extraOpts) {
|
|
14535
14322
|
const adapter = this.getAdapterForInstance(instanceName) ?? this.adapter;
|
|
14536
14323
|
if (!adapter) {
|
|
14537
14324
|
// Early startup: adapters are created well after the fleet object exists.
|
|
14538
14325
|
this.logger.warn({ instanceName }, "No adapter yet — instance topic notification not sent");
|
|
14539
|
-
return
|
|
14326
|
+
return null;
|
|
14540
14327
|
}
|
|
14541
14328
|
const channelCfg = this.getChannelConfig(this.getInstanceAdapterId(instanceName));
|
|
14542
14329
|
const groupId = channelCfg?.group_id;
|
|
@@ -14545,17 +14332,13 @@ export class FleetManager {
|
|
|
14545
14332
|
const worldId = this.getInstanceAdapterId(instanceName);
|
|
14546
14333
|
if (threadId != null && groupId) {
|
|
14547
14334
|
const out = this.renderPlaces(worldId, text, { threadId: String(threadId), ...extraOpts });
|
|
14548
|
-
adapter.sendText(String(groupId), out.text, out.opts)
|
|
14549
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send instance topic notification"));
|
|
14550
|
-
return true;
|
|
14335
|
+
return { adapterId: worldId, sent: adapter.sendText(String(groupId), out.text, out.opts), failure: "Failed to send instance topic notification" };
|
|
14551
14336
|
}
|
|
14552
14337
|
// Classic instance: find its channelId from the classic manager
|
|
14553
14338
|
const classicChatId = this.classicChannels?.getChannelIdByInstance(instanceName);
|
|
14554
14339
|
if (classicChatId) {
|
|
14555
14340
|
// #1990: a ClassicBot room's readers need not be in the fleet's group, so its notices name places as plain text.
|
|
14556
|
-
adapter.sendText(classicChatId, stripPlaceRefs(text), extraOpts)
|
|
14557
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send classic notification"));
|
|
14558
|
-
return true;
|
|
14341
|
+
return { adapterId: worldId, sent: adapter.sendText(classicChatId, stripPlaceRefs(text), extraOpts), failure: "Failed to send classic notification" };
|
|
14559
14342
|
}
|
|
14560
14343
|
// Fallback: the instance has neither a topic nor a classic channel, so post
|
|
14561
14344
|
// where fleet-wide notices go. Not the bare group id: on Discord that is a
|
|
@@ -14563,12 +14346,67 @@ export class FleetManager {
|
|
|
14563
14346
|
const target = this.fleetNoticeTarget(worldId);
|
|
14564
14347
|
if (target) {
|
|
14565
14348
|
const out = this.renderPlaces(worldId, text, { ...target.opts, ...extraOpts });
|
|
14566
|
-
adapter.sendText(target.chatId, out.text, out.opts)
|
|
14567
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send notification (no topic)"));
|
|
14568
|
-
return true;
|
|
14349
|
+
return { adapterId: worldId, sent: adapter.sendText(target.chatId, out.text, out.opts), failure: "Failed to send notification (no topic)" };
|
|
14569
14350
|
}
|
|
14570
14351
|
this.logger.warn({ instanceName }, "No postable target — instance topic notification not sent");
|
|
14571
|
-
return
|
|
14352
|
+
return null;
|
|
14353
|
+
}
|
|
14354
|
+
// ── Parked-dialog notices (#2897) ──
|
|
14355
|
+
parkedNoticesStore = null;
|
|
14356
|
+
get parkedNotices() {
|
|
14357
|
+
this.parkedNoticesStore ??= new ParkedNotices({
|
|
14358
|
+
file: join(this.dataDir, PARKED_NOTICE_LEDGER_FILE),
|
|
14359
|
+
logger: this.logger,
|
|
14360
|
+
post: (instanceName, place, text) => this.postParkedNotice(instanceName, place, text),
|
|
14361
|
+
adapterFor: adapterId => this.worlds.get(adapterId)?.adapter
|
|
14362
|
+
?? (adapterId === this.getPrimaryAdapterId() ? this.adapter ?? undefined : undefined),
|
|
14363
|
+
resolvedText: () => t("fleet.dialog_parked_resolved"),
|
|
14364
|
+
// A ClassicBot room names places as plain text (#1990), as its post did.
|
|
14365
|
+
render: (row, text) => row.place === "topic" && this.classicChannels?.getChannelIdByInstance(row.instanceName) === row.chatId
|
|
14366
|
+
? stripPlaceRefs(text) : this.renderPlaces(row.adapterId, text).text,
|
|
14367
|
+
});
|
|
14368
|
+
return this.parkedNoticesStore;
|
|
14369
|
+
}
|
|
14370
|
+
/**
|
|
14371
|
+
* A held CLI dialog's notices: the instance-topic one and the General one, each edited in place while the
|
|
14372
|
+
* dialog stays (one live message per place, never a pile), and taken back by resolveParkedNotice.
|
|
14373
|
+
*/
|
|
14374
|
+
showParkedNotice(instanceName, topicText, generalText) {
|
|
14375
|
+
if (topicText !== null)
|
|
14376
|
+
void this.parkedNotices.show(instanceName, "topic", topicText);
|
|
14377
|
+
if (generalText !== null)
|
|
14378
|
+
void this.parkedNotices.show(instanceName, "general", generalText);
|
|
14379
|
+
}
|
|
14380
|
+
/** The held dialog is resolved, or the instance stopped or restarted: delete its notices (no-op when none). */
|
|
14381
|
+
resolveParkedNotice(instanceName) {
|
|
14382
|
+
void this.parkedNotices.resolve(instanceName);
|
|
14383
|
+
}
|
|
14384
|
+
async postParkedNotice(instanceName, place, text) {
|
|
14385
|
+
let send;
|
|
14386
|
+
if (place === "topic") {
|
|
14387
|
+
send = this.sendToInstanceTopic(instanceName, text);
|
|
14388
|
+
}
|
|
14389
|
+
else {
|
|
14390
|
+
// Where notifyFleetError posts, without its text throttle: the ledger is what keeps this to one message.
|
|
14391
|
+
const general = Object.entries(this.fleetConfig?.instances ?? {})
|
|
14392
|
+
.find(([, config]) => config.general_topic === true)?.[0];
|
|
14393
|
+
const target = general ? null : this.fleetNoticeTarget();
|
|
14394
|
+
if (general)
|
|
14395
|
+
send = this.sendToInstanceTopic(general, text);
|
|
14396
|
+
else if (this.adapter && target) {
|
|
14397
|
+
const out = this.renderPlaces(this.getPrimaryAdapterId(), text, target.opts);
|
|
14398
|
+
send = { adapterId: this.getPrimaryAdapterId(), sent: this.adapter.sendText(target.chatId, out.text, out.opts) };
|
|
14399
|
+
}
|
|
14400
|
+
else
|
|
14401
|
+
send = null;
|
|
14402
|
+
}
|
|
14403
|
+
if (!send)
|
|
14404
|
+
return null;
|
|
14405
|
+
const sent = await send.sent;
|
|
14406
|
+
const adapterId = send.adapterId ?? this.getPrimaryAdapterId();
|
|
14407
|
+
if (!adapterId)
|
|
14408
|
+
return null;
|
|
14409
|
+
return { adapterId, chatId: sent.chatId, ...(sent.threadId !== undefined ? { threadId: sent.threadId } : {}), messageId: sent.messageId };
|
|
14572
14410
|
}
|
|
14573
14411
|
// ── Nonce-armed button prompts (hang / assist / exit / clear) ──
|
|
14574
14412
|
//
|
|
@@ -15146,6 +14984,7 @@ export class FleetManager {
|
|
|
15146
14984
|
pauseReason, pausedAt,
|
|
15147
14985
|
crashedAt: crashed ? (this.needsCrashedAt.get(name) ?? null) : null,
|
|
15148
14986
|
signIn: pauseReason === "auth" ? this.signInFor(name) : null,
|
|
14987
|
+
usageHold: this.daemons.get(name)?.usageHold?.() ?? null,
|
|
15149
14988
|
});
|
|
15150
14989
|
}
|
|
15151
14990
|
return out;
|
|
@@ -15219,6 +15058,9 @@ export class FleetManager {
|
|
|
15219
15058
|
},
|
|
15220
15059
|
tasks: () => this.needsYouTaskItems(),
|
|
15221
15060
|
backendSignIns: () => this.needsYouBackendSignIns(),
|
|
15061
|
+
configIssues: () => (this.configHistory?.currentIssues() ?? []).map(issue => ({
|
|
15062
|
+
...issue, detail: configIssueText(issue, t), owner: this.worlds.keys().next().value,
|
|
15063
|
+
})),
|
|
15222
15064
|
taskAction: action => this.taskActionFromChat(action),
|
|
15223
15065
|
settings: () => ({
|
|
15224
15066
|
liveMessage: this.fleetConfig?.needs_you?.live_message !== false,
|
|
@@ -15968,6 +15810,16 @@ export class FleetManager {
|
|
|
15968
15810
|
return result;
|
|
15969
15811
|
}
|
|
15970
15812
|
/** #1269: an instance's chat command from the web chat (web-commands.ts); `publicLink`: a gateway request. */
|
|
15813
|
+
/** #2855: the table scope of an instance the web chat may command (a fleet instance or a ClassicBot room), else null. */
|
|
15814
|
+
webCommandScope(instance) {
|
|
15815
|
+
// Own entries only: a name like "__proto__" or "constructor" must not be found on the prototype (#1476 review).
|
|
15816
|
+
const instances = this.fleetConfig?.instances;
|
|
15817
|
+
if (instances && Object.prototype.hasOwnProperty.call(instances, instance))
|
|
15818
|
+
return isGeneralInstance(this.fleetConfig, instance) ? "general" : "fleet";
|
|
15819
|
+
return this.classicChannels?.getChannelIdByInstance(instance) !== undefined ? "classic" : null;
|
|
15820
|
+
}
|
|
15821
|
+
/** #2855: the platform an instance's channel is on, for the command table's cell (a web-only fleet: Discord's). */
|
|
15822
|
+
commandPlatform(instance) { return this.getAdapterForInstance(instance)?.type === "telegram" ? "telegram" : "discord"; }
|
|
15971
15823
|
webCommand(input, opts) {
|
|
15972
15824
|
const webMeta = (instance) => {
|
|
15973
15825
|
const topicId = this.fleetConfig?.instances[instance]?.topic_id;
|
|
@@ -15976,6 +15828,7 @@ export class FleetManager {
|
|
|
15976
15828
|
chatId: this.getGroupIdForInstance(instance) || "", messageId: newWebMessageId(),
|
|
15977
15829
|
username: opts.person?.name ?? WEB_NAME_FALLBACK, userId: opts.person?.userId ?? WEB_NAME_FALLBACK,
|
|
15978
15830
|
threadId: topicId != null ? String(topicId) : undefined, adapterId: this.getAdapterForInstance(instance)?.id ?? "", source: "web", humanOrigin: webHumanOrigin(true, opts.authority),
|
|
15831
|
+
...(opts.guest ? { webGuest: true } : {}), // #2855: a guest's /steer or /btw reaches the agent as a guest's
|
|
15979
15832
|
};
|
|
15980
15833
|
};
|
|
15981
15834
|
const choicesFromCache = (instance) => {
|
|
@@ -15987,13 +15840,8 @@ export class FleetManager {
|
|
|
15987
15840
|
return { current, options: this.withCurrentModel(cached.models, current).map(o => ({ id: o.id, label: this.modelChoiceLabel(o, current) })) };
|
|
15988
15841
|
};
|
|
15989
15842
|
return runWebCommand({
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
const instances = this.fleetConfig?.instances;
|
|
15993
|
-
if (instances && Object.prototype.hasOwnProperty.call(instances, instance))
|
|
15994
|
-
return isGeneralInstance(this.fleetConfig, instance) ? "general" : "fleet";
|
|
15995
|
-
return this.classicChannels?.getChannelIdByInstance(instance) !== undefined ? "classic" : null;
|
|
15996
|
-
},
|
|
15843
|
+
scope: (instance) => this.webCommandScope(instance),
|
|
15844
|
+
platform: (instance) => this.commandPlatform(instance),
|
|
15997
15845
|
ctx: (instance) => this.topicCommands.getCtxText(instance),
|
|
15998
15846
|
// Board 003648ac: a watch for the paste, armed when the daemon says it was written (raw_paste_written); one
|
|
15999
15847
|
// for a command that was not handed over is dropped.
|
|
@@ -17670,6 +17518,13 @@ export class FleetManager {
|
|
|
17670
17518
|
lines.push(t(admin === "added" ? "classic.approve_done_admin" : "classic.approve_done_admin_already", adminUserId ?? ""));
|
|
17671
17519
|
}
|
|
17672
17520
|
this.notifyInstanceTopic(entry.instanceName, lines.join("\n"));
|
|
17521
|
+
// #2870: the chat just allowed — and the new ClassicBot admin's own chat — get the ClassicBot menu now, not at the
|
|
17522
|
+
// bot's next start (Telegram only; the menu is per chat there).
|
|
17523
|
+
const reply = entry.classicReplyTo;
|
|
17524
|
+
if (reply && reply.adapter.type === "telegram" && scope !== "guild") {
|
|
17525
|
+
for (const chat of new Set([groupId, ...(adminUserId ? [adminUserId] : [])]))
|
|
17526
|
+
void this.topicCommands.addClassicMenuChat(reply.adapterId, chat);
|
|
17527
|
+
}
|
|
17673
17528
|
await this.notifyClassicRequester(entry, "classic.request_allowed");
|
|
17674
17529
|
}
|
|
17675
17530
|
async notifyClassicRequester(entry, key) {
|
|
@@ -18269,14 +18124,6 @@ export class FleetManager {
|
|
|
18269
18124
|
const room = this.classicChannels?.getAll().find(c => c.instanceName === name);
|
|
18270
18125
|
return room ? `room:${room.channelId}:${room.adapterId ?? ""}` : null;
|
|
18271
18126
|
}
|
|
18272
|
-
/** The only Try-card publisher: no arbitrary SSE sender/role can manufacture this metadata. */
|
|
18273
|
-
publishEffectTrial(instance, offer) {
|
|
18274
|
-
const row = this.webChatHistory.recordEffectTrial(instance, offer, new Date().toISOString());
|
|
18275
|
-
if (!row)
|
|
18276
|
-
return false;
|
|
18277
|
-
broadcastSseEvent(this.sseClients, "message", row, err => this.logger.debug({ err }, "SSE client write failed; evicting"), this.webChatHistory.cursorOf(row));
|
|
18278
|
-
return true;
|
|
18279
|
-
}
|
|
18280
18127
|
/** Push an SSE event to all connected Web UI clients. */
|
|
18281
18128
|
emitSseEvent(event, data) {
|
|
18282
18129
|
// #1386: a prompt opened or closed, or an instance's state moved — "Needs you" may have changed.
|
|
@@ -18321,8 +18168,8 @@ export class FleetManager {
|
|
|
18321
18168
|
* The task board's live side: every committed change goes to the open pages (never to a guest) — the board's new
|
|
18322
18169
|
* revision and the card numbers it touched; not who touched them (the page does not need it, and a public-link page
|
|
18323
18170
|
* must not learn a hidden instance's name from it). Its news goes to the instances whose cards someone else changed:
|
|
18324
|
-
* one "[Task board]" notice per instance, gathered for 10 s, held by the delivery outbox until it is taken
|
|
18325
|
-
* (task-board-waker.ts).
|
|
18171
|
+
* one "[Task board]" notice per instance, gathered for 10 s, held by the delivery outbox until it is taken, or
|
|
18172
|
+
* withdrawn once its cards changed again (#2801; task-board-waker.ts).
|
|
18326
18173
|
*/
|
|
18327
18174
|
attachTaskBoard(db) {
|
|
18328
18175
|
this.taskBoardWaker?.stop();
|
|
@@ -18331,6 +18178,19 @@ export class FleetManager {
|
|
|
18331
18178
|
const waker = this.taskBoardWaker = new TaskBoardWaker({
|
|
18332
18179
|
deliver: (instance, message, key) => this.admitTaskBoardNotice(instance, message, key),
|
|
18333
18180
|
isInstance: (name) => this.fleetConfig?.instances?.[name] != null,
|
|
18181
|
+
// #2785 (c), #2801: the maker's own message already told it; the board's line would repeat it.
|
|
18182
|
+
messagedAbout: (from, to, codes, sinceMs) => {
|
|
18183
|
+
const outbox = this.deliveryOutbox;
|
|
18184
|
+
if (!outbox?.isOpen)
|
|
18185
|
+
return false;
|
|
18186
|
+
return outbox.recentTextsFrom(to, from, new Date(sinceMs).toISOString()).some((text) => codes.some((code) => namesCard(text, code)));
|
|
18187
|
+
},
|
|
18188
|
+
// #2801: a notice still waiting whose cards have all changed again is not delivered.
|
|
18189
|
+
withdraw: (prefix, stale, reason) => {
|
|
18190
|
+
const outbox = this.deliveryOutbox;
|
|
18191
|
+
return outbox?.isOpen ? outbox.withdrawWaitingNotices(prefix, stale, reason).length : 0;
|
|
18192
|
+
},
|
|
18193
|
+
changedAt: (uuid) => db.board.lastChangeAt(uuid),
|
|
18334
18194
|
holds: (wake) => db.board.wakeHolds(wake),
|
|
18335
18195
|
card: (uuid) => db.board.findCard(uuid),
|
|
18336
18196
|
// #2352: the card's Needs you buttons are in this instance's own channel (it is the card's lane).
|
|
@@ -18347,12 +18207,13 @@ export class FleetManager {
|
|
|
18347
18207
|
},
|
|
18348
18208
|
log: (level, message, extra) => this.logger[level](extra ?? {}, message),
|
|
18349
18209
|
});
|
|
18350
|
-
db.board.onChange(({ rev, ids, projects, wakes }) => {
|
|
18210
|
+
db.board.onChange(({ rev, ids, projects, wakes, changed }) => {
|
|
18351
18211
|
// Refresh both old and new owners on reassignment; wakes are not an ownership index.
|
|
18352
18212
|
if (ids.length)
|
|
18353
18213
|
for (const name of this.daemons.keys())
|
|
18354
18214
|
this.observeWorkingState(name, "tasks");
|
|
18355
18215
|
this.emitSseEvent("tasks", { rev, ids, projects });
|
|
18216
|
+
waker.changed(changed);
|
|
18356
18217
|
waker.note(wakes);
|
|
18357
18218
|
// step 3: cards waiting for the person are Needs you items; #2355 review: a project's lead routes them too.
|
|
18358
18219
|
if (ids.length || projects)
|
|
@@ -18570,27 +18431,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
18570
18431
|
this.syncRoleSkills(workDir, backend, "general", knowledgeDir);
|
|
18571
18432
|
this.logger.debug({ knowledgeDir, workDir, backend }, "Synced general knowledge files");
|
|
18572
18433
|
}
|
|
18573
|
-
/** Update only AgEnD-managed skill copies; no launch or restart is needed for an experimental switch. */
|
|
18574
|
-
canvasWorkerSettingChanged() {
|
|
18575
|
-
this.refreshExperimentalSkills();
|
|
18576
|
-
this.emitSseEvent("status", this.getUiStatus());
|
|
18577
|
-
}
|
|
18578
|
-
refreshExperimentalSkills() {
|
|
18579
|
-
for (const [name, daemon] of this.daemons) {
|
|
18580
|
-
try {
|
|
18581
|
-
const cfg = daemon.getConfigSnapshot();
|
|
18582
|
-
const backend = cfg.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
18583
|
-
const workDir = this.resolveKnowledgeWorkDir(cfg.working_directory, backend, name);
|
|
18584
|
-
const classic = this.classicChannels?.getAll().some(ch => ch.instanceName === name) === true;
|
|
18585
|
-
if (classic && !insideManagedWorkspaces(workDir))
|
|
18586
|
-
continue;
|
|
18587
|
-
this.syncRoleSkills(workDir, backend, classic ? "classic" : cfg.general_topic ? "general" : "worker");
|
|
18588
|
-
}
|
|
18589
|
-
catch (err) {
|
|
18590
|
-
this.logger.warn({ name, err }, "Could not refresh experimental skill visibility");
|
|
18591
|
-
}
|
|
18592
|
-
}
|
|
18593
|
-
}
|
|
18594
18434
|
/** Publish only the bundled skills eligible for an instance role. */
|
|
18595
18435
|
syncRoleSkills(workDir, backend, role, knowledgeDir) {
|
|
18596
18436
|
const skillSegments = FleetManager.SKILLS_DIR_SEGMENTS[backend];
|
|
@@ -18795,7 +18635,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
18795
18635
|
continue;
|
|
18796
18636
|
}
|
|
18797
18637
|
managed.push(name);
|
|
18798
|
-
const newContent = bundledSkillContents(join(srcSkills, name)
|
|
18638
|
+
const newContent = bundledSkillContents(join(srcSkills, name));
|
|
18799
18639
|
if (refuse(dest))
|
|
18800
18640
|
continue;
|
|
18801
18641
|
try {
|
|
@@ -20953,6 +20793,17 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20953
20793
|
return (adapterId ? generals.find(([name, cfg]) => (cfg.channel_id ?? this.getInstanceAdapterId(name)) === adapterId) : undefined)?.[0]
|
|
20954
20794
|
?? generals[0]?.[0];
|
|
20955
20795
|
}
|
|
20796
|
+
/**
|
|
20797
|
+
* #2870: someone ClassicBot does not serve in this private chat: not on allowed_users, not a ClassicBot admin, not a
|
|
20798
|
+
* fleet admin of this bot, and no ClassicBot room here. Their commands other than /start get no answer.
|
|
20799
|
+
*/
|
|
20800
|
+
isClassicPrivateStranger(userId, chatId, adapterId) {
|
|
20801
|
+
const classic = this.classicChannels;
|
|
20802
|
+
if (!classic)
|
|
20803
|
+
return false;
|
|
20804
|
+
return !classic.isAdmin(userId) && !classic.isUserAllowed(userId) && !this.isFleetAdmin(userId, adapterId ?? "")
|
|
20805
|
+
&& !classic.isClassicChannel(chatId, adapterId);
|
|
20806
|
+
}
|
|
20956
20807
|
/** Return a user-facing blocker without mutating ClassicBot state. */
|
|
20957
20808
|
validateClassicStart(channelId, userId, guildId, adapterId) {
|
|
20958
20809
|
const classic = this.classicChannels;
|
|
@@ -21235,7 +21086,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21235
21086
|
try {
|
|
21236
21087
|
this.themeStore.forget(ch.instanceName);
|
|
21237
21088
|
this.webAppearance.limiter.forget(ch.instanceName);
|
|
21238
|
-
this.effectToolsService?.forget(ch.instanceName);
|
|
21239
21089
|
}
|
|
21240
21090
|
catch (err) {
|
|
21241
21091
|
this.logger.warn({ err: err.message, instanceName: ch.instanceName }, "Dropping a removed room's web theme failed");
|
|
@@ -21344,6 +21194,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21344
21194
|
const sharesStopped = this.officeSharesInstance?.close(); // #2636: every Office share ends, and its tunnel
|
|
21345
21195
|
// #1386: every live "Needs you" message says the fleet stopped (capabilities revoked first) — while the
|
|
21346
21196
|
// adapters can still edit. Bounded: a platform that does not answer must not hold the shutdown.
|
|
21197
|
+
if (this.configHistoryTimer)
|
|
21198
|
+
clearInterval(this.configHistoryTimer);
|
|
21199
|
+
this.configHistoryTimer = null;
|
|
21347
21200
|
const needsStopped = this.stopNeedsYou();
|
|
21348
21201
|
// #1490 P3: stop() now returns a Promise; await it so the final
|
|
21349
21202
|
// ledger flush completes before process.exit(0) in doStopAll's caller.
|
|
@@ -21399,7 +21252,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21399
21252
|
this.clearStatuslineWatchers();
|
|
21400
21253
|
this.costGuard?.stop();
|
|
21401
21254
|
this.dailySummary?.stop();
|
|
21402
|
-
this.taskBoardSummary?.stop();
|
|
21403
21255
|
this.dailyTipScheduler?.stop();
|
|
21404
21256
|
this.dailyTipScheduler = null;
|
|
21405
21257
|
if (this.updateCheckTimer) {
|
|
@@ -21455,6 +21307,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21455
21307
|
for (const timer of this.cancelButtonIdleRetireTimers.values())
|
|
21456
21308
|
clearTimeout(timer);
|
|
21457
21309
|
this.cancelButtonIdleRetireTimers.clear();
|
|
21310
|
+
// #2897: notices still being retired stay in the ledger; the next fleet's sweep finishes them.
|
|
21311
|
+
this.parkedNoticesStore?.dispose();
|
|
21458
21312
|
if (this.topicCleanupTimer) {
|
|
21459
21313
|
clearInterval(this.topicCleanupTimer);
|
|
21460
21314
|
this.topicCleanupTimer = null;
|
|
@@ -21690,14 +21544,16 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21690
21544
|
const previousRawConfig = this.rawFleetConfig;
|
|
21691
21545
|
const previousRawDocument = this.rawFleetDocument;
|
|
21692
21546
|
const previousSavedSnapshot = this.savedFleetConfigSnapshot;
|
|
21547
|
+
const previousSource = this.loadedFleetSource;
|
|
21693
21548
|
try {
|
|
21694
|
-
this.loadConfig(this.configPath);
|
|
21549
|
+
this.loadConfig(this.configPath, { remember: false });
|
|
21695
21550
|
}
|
|
21696
21551
|
catch (err) {
|
|
21697
21552
|
this.fleetConfig = oldConfig;
|
|
21698
21553
|
this.rawFleetConfig = previousRawConfig;
|
|
21699
21554
|
this.rawFleetDocument = previousRawDocument;
|
|
21700
21555
|
this.savedFleetConfigSnapshot = previousSavedSnapshot;
|
|
21556
|
+
this.loadedFleetSource = previousSource;
|
|
21701
21557
|
throw err;
|
|
21702
21558
|
}
|
|
21703
21559
|
const validation = validateFleetConfig(this.rawFleetConfig);
|
|
@@ -21713,6 +21569,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21713
21569
|
this.rawFleetConfig = previousRawConfig;
|
|
21714
21570
|
this.rawFleetDocument = previousRawDocument;
|
|
21715
21571
|
this.savedFleetConfigSnapshot = previousSavedSnapshot;
|
|
21572
|
+
this.loadedFleetSource = previousSource;
|
|
21716
21573
|
this.observeWebAuthority(); // #2453 review: the configuration back in force is the one seen
|
|
21717
21574
|
this.logger.error({
|
|
21718
21575
|
oldCount,
|
|
@@ -21733,11 +21590,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21733
21590
|
// mark every row done and tell the user "changes applied".
|
|
21734
21591
|
return { rejected: why };
|
|
21735
21592
|
}
|
|
21593
|
+
if (this.loadedFleetSource)
|
|
21594
|
+
this.getConfigHistory().accepted("fleet.yaml", this.configPath, this.loadedFleetSource);
|
|
21736
21595
|
this.migrateOfficeStorage();
|
|
21737
21596
|
this.migrateNoticeDeliveryStorage();
|
|
21738
|
-
if (canvasWorkerEnabled(oldConfig?.web) !== this.canvasWorkerEnabled()) {
|
|
21739
|
-
this.canvasWorkerSettingChanged();
|
|
21740
|
-
}
|
|
21741
21597
|
// Classic behavior settings share the fleet defaults but are not entries
|
|
21742
21598
|
// in fleet.yaml. Snapshot the old effective chain before reloading the
|
|
21743
21599
|
// Classic file so SIGHUP can hot-apply either source without waiting for
|
|
@@ -21947,7 +21803,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21947
21803
|
* the panel shows the mismatch instead of a button.
|
|
21948
21804
|
*/
|
|
21949
21805
|
checkStartupSignatureConsistency() {
|
|
21950
|
-
if (!this.configPath) {
|
|
21806
|
+
if (!this.configPath || this.configHistory?.isFallback(this.configPath)) {
|
|
21951
21807
|
this.fleetSignatureMismatch = null;
|
|
21952
21808
|
return;
|
|
21953
21809
|
}
|
|
@@ -23506,7 +23362,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23506
23362
|
// From the install's own channel tag, this is what /update installs — even a stable the release pointed the
|
|
23507
23363
|
// tag at (2.2.0 on @alpha / @beta): the plain /update text, never "/update stays on … use --stable" (#1928).
|
|
23508
23364
|
const key = fromChannel ? "update.available_current" : updateNoticeKey(currentVersion, target);
|
|
23509
|
-
|
|
23365
|
+
// #2775: null = a beta install and a stable target; beta users are never told to switch.
|
|
23366
|
+
if (key)
|
|
23367
|
+
this.notifyInstanceTopic(generalId, t(key, `v${target}`, `v${currentVersion}`));
|
|
23510
23368
|
}
|
|
23511
23369
|
}
|
|
23512
23370
|
}
|
|
@@ -23602,6 +23460,21 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23602
23460
|
}
|
|
23603
23461
|
return scope.size ? scope : NO_GUEST_SCOPE;
|
|
23604
23462
|
}
|
|
23463
|
+
/** #2855: whether a request carries a guest's live session (its cookie; checked without counting as activity). */
|
|
23464
|
+
presentsGuestSession(req) {
|
|
23465
|
+
if (!this.webToken || !this.webSessions)
|
|
23466
|
+
return false;
|
|
23467
|
+
const epoch = tokenEpoch(this.webToken);
|
|
23468
|
+
return readSessionCookies(req, this.webToken).some(id => this.webSessions.authenticate(id, epoch, { touch: false })?.tier === "guest");
|
|
23469
|
+
}
|
|
23470
|
+
/** #2855: whether the admin made this guest Trusted — only an explicit grant can (web.guests.grants[account].level). */
|
|
23471
|
+
guestTrusted(owner) {
|
|
23472
|
+
const guests = this.fleetConfig?.web?.guests;
|
|
23473
|
+
if (guests?.enabled !== true || !guests.grants)
|
|
23474
|
+
return false;
|
|
23475
|
+
const key = `${owner.adapterId}:${owner.userId}`;
|
|
23476
|
+
return Object.hasOwn(guests.grants, key) && guests.grants[key]?.level === "trusted";
|
|
23477
|
+
}
|
|
23605
23478
|
/**
|
|
23606
23479
|
* #1920 (#2104 review): what an account reaches before an admin's narrowing — its connection's topics (an open one's
|
|
23607
23480
|
* named agents), its own Telegram private room, its verified group rooms. Narrowing can only take from this set, so it
|
|
@@ -23780,13 +23653,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23780
23653
|
return;
|
|
23781
23654
|
}
|
|
23782
23655
|
const requestPath = new URL(req.url ?? "/", `http://localhost:${port}`).pathname;
|
|
23783
|
-
if (!this.canvasWorkerEnabled() && isCanvasWorkerHttpPath(requestPath)) {
|
|
23784
|
-
res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
23785
|
-
res.end(JSON.stringify({ error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }));
|
|
23786
|
-
return;
|
|
23787
|
-
}
|
|
23788
|
-
if (this.effectBootstrap.handle(req, res))
|
|
23789
|
-
return;
|
|
23790
23656
|
// Branding is public, after Host admission: automatic favicon requests
|
|
23791
23657
|
// must not become noisy web-token 401s in the browser console.
|
|
23792
23658
|
// #2308: the icon routes and the pages' icon links follow the app icon set in Settings.
|
|
@@ -23794,8 +23660,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23794
23660
|
if (handleWebIconRequest(req, res, requestPath))
|
|
23795
23661
|
return;
|
|
23796
23662
|
// Public: the health probe, /agent (instance-token auth of its own), the
|
|
23797
|
-
// sign-in surface, and /view's reads unless web.view_access says otherwise
|
|
23798
|
-
|
|
23663
|
+
// sign-in surface, and /view's reads unless web.view_access says otherwise (#2855: never a guest's, which go
|
|
23664
|
+
// through the guest gate and its limits on either listener).
|
|
23665
|
+
if (bypassesWebGate(req, requestPath, this.fleetConfig, p => !gatewayRequestContext(req) && (isViewPath(p) || isUsagePath(p)) && !this.presentsGuestSession(req))) {
|
|
23799
23666
|
// fall through to the handlers below
|
|
23800
23667
|
}
|
|
23801
23668
|
else {
|
|
@@ -23861,7 +23728,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23861
23728
|
streamOpened: () => { void this.refreshGuestRooms(owner, { mode: "force" }); },
|
|
23862
23729
|
history: () => this.webChatHistory,
|
|
23863
23730
|
// #1920 §5.1: guests chat unless the host made them read-only (web.guests.chat: false).
|
|
23864
|
-
chat: () => this.fleetConfig?.web?.guests?.chat !== false
|
|
23731
|
+
chat: () => this.fleetConfig?.web?.guests?.chat !== false,
|
|
23732
|
+
// #2855: the admin's level for this guest, read at each request (a change applies at once).
|
|
23733
|
+
trusted: () => this.guestTrusted(owner) });
|
|
23865
23734
|
}
|
|
23866
23735
|
if (decision.via === "session" && this.webToken) {
|
|
23867
23736
|
const upgrade = sessionCookieUpgrade(req, this.webToken, decision.session);
|
|
@@ -24267,7 +24136,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24267
24136
|
return;
|
|
24268
24137
|
}
|
|
24269
24138
|
const listener = createPreviewListener({ settings, healthPort: boundPort, config: this.fleetConfig,
|
|
24270
|
-
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
24271
24139
|
// #1723: with a web.preview_origin, the open public link's dashboard frames this listener's preview too.
|
|
24272
24140
|
extraOrigins: () => { const o = settings.origin ? this.publicWebLink?.openDashboardOrigin() : null; return o ? [o] : []; } });
|
|
24273
24141
|
this.previewListener = listener;
|
|
@@ -24444,8 +24312,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24444
24312
|
catch {
|
|
24445
24313
|
return {};
|
|
24446
24314
|
} })(),
|
|
24447
|
-
// #2118: only this Chat's placement content; guest projection drops global revisions.
|
|
24448
|
-
effect_chat_v: this.canvasWorkerEnabled() ? createHash("sha256").update(JSON.stringify(this.themeStore.get(name).placements.effects)).digest("hex").slice(0, 32) : null,
|
|
24449
24315
|
// #1958: the identity colour's tiers (the page resolves them: the agent's only while it shows agents' themes).
|
|
24450
24316
|
color: { ...(colors.get(name)?.color ? { person: colors.get(name).color } : {}), ...(image.agentColor ? { agent: image.agentColor } : {}),
|
|
24451
24317
|
...(image.roleColor ? { role: image.roleColor } : {}), auto: autoIdentityColor(name) },
|
|
@@ -24456,6 +24322,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24456
24322
|
tags: identity.tags,
|
|
24457
24323
|
teams: instanceTeams(this.fleetConfig?.teams, name), // #2172: the sidebar groups a team's members under it
|
|
24458
24324
|
last_reply: this.webChatHistory.lastAgentReply(name) ?? undefined, // #2179: its last reply to a person (ISO)
|
|
24325
|
+
// #2855: its last reply that reached the channel — the only reply time a guest's sidebar gets (guestStatus).
|
|
24326
|
+
last_reply_channel: this.webChatHistory.lastAgentReply(name, { channel: true }) ?? undefined,
|
|
24327
|
+
// #2855: the web commands a guest may run here — the table's "anyone" cells for this channel (guestWebCommands);
|
|
24328
|
+
// only while guests are on (no guest reads it otherwise).
|
|
24329
|
+
...(this.fleetConfig?.web?.guests?.enabled === true
|
|
24330
|
+
? { guest_commands: (() => { const scope = this.webCommandScope(name); return scope ? guestWebCommands(scope, this.commandPlatform(name)) : []; })() } : {}),
|
|
24459
24331
|
status: this.getInstanceStatus(name),
|
|
24460
24332
|
// `state` (presentation: may be awaiting_input) and `execution_state` (working / idle / stuck, or null —
|
|
24461
24333
|
// what the dashboard's activity events carry) come from instancePresentation (#1212).
|
|
@@ -24472,7 +24344,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24472
24344
|
};
|
|
24473
24345
|
});
|
|
24474
24346
|
return {
|
|
24475
|
-
canvas_worker: this.canvasWorkerEnabled(),
|
|
24476
24347
|
instances,
|
|
24477
24348
|
startup: this.startupDashboard?.snapshot() ?? null,
|
|
24478
24349
|
publicLink: this.getPublicWebStatus(),
|