@songsid/agend 2.2.1 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-cli-instructions.md +5 -2
- package/dist/agent-cli.js +284 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +29 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +38 -0
- package/dist/agent-file-read.js +317 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.d.ts +11 -0
- package/dist/agent-whats-new.js +34 -2
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/atomic-write.js +9 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +61 -12
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +38 -0
- package/dist/authority/authority-state.js +114 -0
- package/dist/authority/authority-state.js.map +1 -0
- package/dist/authority/session-authorizations.d.ts +43 -0
- package/dist/authority/session-authorizations.js +102 -0
- package/dist/authority/session-authorizations.js.map +1 -0
- package/dist/authority/web-authority.d.ts +120 -0
- package/dist/authority/web-authority.js +157 -0
- package/dist/authority/web-authority.js.map +1 -0
- package/dist/backend/antigravity.js +3 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +10 -5
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/launch-effort.js +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/muse.js +10 -2
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js +25 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-availability.d.ts +120 -0
- package/dist/backend-availability.js +396 -0
- package/dist/backend-availability.js.map +1 -0
- package/dist/backend-command.d.ts +21 -3
- package/dist/backend-command.js +23 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/bundled-skills.d.ts +14 -0
- package/dist/bundled-skills.js +29 -0
- package/dist/bundled-skills.js.map +1 -0
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +70 -3
- package/dist/cache-service.js +178 -17
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
- package/dist/channel/adapters/discord-client-retirement.js +57 -0
- package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +11 -0
- package/dist/channel/adapters/discord.js +66 -6
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +8 -1
- package/dist/channel/adapters/telegram.js +74 -4
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +48 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +21 -0
- package/dist/classic-channel-manager.d.ts +10 -5
- package/dist/classic-channel-manager.js +12 -9
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +142 -110
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +119 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.js +298 -22
- package/dist/daemon.js.map +1 -1
- package/dist/decision-list.d.ts +25 -0
- package/dist/decision-list.js +30 -0
- package/dist/decision-list.js.map +1 -0
- package/dist/delivery-outbox.d.ts +10 -0
- package/dist/delivery-outbox.js +12 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-context.d.ts +15 -0
- package/dist/fleet-manager.d.ts +514 -12
- package/dist/fleet-manager.js +1885 -163
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +18 -0
- package/dist/fleet-process-handlers.js +69 -0
- package/dist/fleet-process-handlers.js.map +1 -0
- package/dist/fleet-traffic.d.ts +74 -0
- package/dist/fleet-traffic.js +196 -0
- package/dist/fleet-traffic.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +492 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +482 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/human-delivery-receipts.d.ts +24 -1
- package/dist/human-delivery-receipts.js +64 -11
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/instance-avatar.js +3 -0
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +6 -1
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +2 -0
- package/dist/instance-lifecycle.js +20 -10
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-names.d.ts +4 -0
- package/dist/instance-names.js +12 -0
- package/dist/instance-names.js.map +1 -0
- package/dist/instructions.d.ts +12 -1
- package/dist/instructions.js +13 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +118 -10
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.js +35 -2
- package/dist/login-controller.js.map +1 -1
- package/dist/ls-attach-resolve.d.ts +95 -0
- package/dist/ls-attach-resolve.js +174 -0
- package/dist/ls-attach-resolve.js.map +1 -0
- package/dist/memory-hogs.d.ts +27 -0
- package/dist/memory-hogs.js +124 -0
- package/dist/memory-hogs.js.map +1 -0
- package/dist/needs-you-hub.d.ts +4 -0
- package/dist/needs-you-hub.js +31 -6
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +1 -13
- package/dist/needs-you.js +3 -37
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +15 -0
- package/dist/outbound-handlers.js +152 -11
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +228 -4
- package/dist/outbound-schemas.js +147 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +21 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +9 -0
- package/dist/public-web-link.js +8 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +10 -1
- package/dist/quickstart-api.js +12 -4
- package/dist/quickstart-api.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +4 -0
- package/dist/reply-buttons-controller.js +2 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/repo-worktree-guard.d.ts +36 -0
- package/dist/repo-worktree-guard.js +143 -0
- package/dist/repo-worktree-guard.js.map +1 -0
- package/dist/restart-progress.d.ts +65 -1
- package/dist/restart-progress.js +236 -10
- package/dist/restart-progress.js.map +1 -1
- package/dist/rollout-index.d.ts +5 -0
- package/dist/rollout-index.js +26 -5
- package/dist/rollout-index.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +70 -1
- package/dist/scheduler/scheduler.js +256 -17
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/service-installer.d.ts +2 -1
- package/dist/service-installer.js +18 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +23 -0
- package/dist/settings-api.js +233 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +112 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +32 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +5 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +16 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +4 -6
- package/dist/tool-permissions.js +100 -2
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +17 -7
- package/dist/topic-commands.js +39 -21
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.d.ts +128 -0
- package/dist/topic-link.js +269 -0
- package/dist/topic-link.js.map +1 -0
- package/dist/tunnel/cloudflared.js +37 -2
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/types.d.ts +3 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +61 -1
- package/dist/ui/app-effects.js +245 -0
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +42 -0
- package/dist/ui/chat-effects.js +160 -0
- package/dist/ui/chat-live-replies.js +58 -0
- package/dist/ui/chat-render.js +25 -7
- package/dist/ui/chat-store.js +41 -13
- package/dist/ui/chat-strings.js +86 -2
- package/dist/ui/chat-thread.js +204 -37
- package/dist/ui/chat-tour.js +1 -1
- package/dist/ui/effect-trial-controller.js +124 -0
- package/dist/ui/effect-trial-offer.js +31 -0
- package/dist/ui/effect-trial-panel.js +40 -0
- package/dist/ui/fleet-cache.js +23 -1
- package/dist/ui/fleet-canvas-layout.js +111 -0
- package/dist/ui/fleet-canvas.js +525 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-traffic.js +343 -0
- package/dist/ui/fleet-usage.js +401 -0
- package/dist/ui/image-lightbox.js +1 -1
- package/dist/ui/panel-chat.js +252 -58
- package/dist/ui/panel-details.js +20 -6
- package/dist/ui/panel-fleet.js +45 -19
- package/dist/ui/panel-log.js +6 -6
- package/dist/ui/panel-needs.js +3 -3
- package/dist/ui/panel-settings.js +58 -13
- package/dist/ui/preview-panel.js +25 -9
- package/dist/ui/preview.js +3 -2
- package/dist/ui/settings-appearance.js +349 -0
- package/dist/ui/settings-catalog.js +92 -0
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +202 -0
- package/dist/ui/settings-guests.js +117 -0
- package/dist/ui/settings-learn.js +25 -0
- package/dist/ui/settings-model.js +15 -6
- package/dist/ui/settings-mods.js +271 -0
- package/dist/ui/settings-owner-effects.js +80 -0
- package/dist/ui/settings-strings.js +335 -10
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/agend-auth.js +28 -4
- package/dist/ui/shared/agent-widget.js +248 -0
- package/dist/ui/shared/app-i18n.js +6 -2
- package/dist/ui/shared/app-prefs.js +34 -13
- package/dist/ui/shared/app-route.js +42 -2
- package/dist/ui/shared/app-session.js +3 -1
- package/dist/ui/shared/app-shell.js +119 -41
- package/dist/ui/shared/app-store.js +28 -1
- package/dist/ui/shared/app-stream.js +14 -7
- package/dist/ui/shared/app-themes.js +206 -0
- package/dist/ui/shared/app.css +566 -5
- package/dist/ui/shared/app.js +23 -10
- package/dist/ui/shared/card-strings.js +32 -0
- package/dist/ui/shared/effect-app-runtime.js +297 -0
- package/dist/ui/shared/effect-baseline.js +64 -0
- package/dist/ui/shared/effect-chat-runtime.js +167 -0
- package/dist/ui/shared/effect-cursor.js +125 -0
- package/dist/ui/shared/effect-frame.js +146 -0
- package/dist/ui/shared/effect-geometry.js +137 -0
- package/dist/ui/shared/effect-host-style.js +10 -0
- package/dist/ui/shared/effect-owner-regions.js +74 -0
- package/dist/ui/shared/effect-owner-source.js +122 -0
- package/dist/ui/shared/effect-policy.js +151 -0
- package/dist/ui/shared/effect-poster-reader.js +98 -0
- package/dist/ui/shared/effect-posters.js +117 -0
- package/dist/ui/shared/effect-profiler.js +184 -0
- package/dist/ui/shared/effect-profiles.js +65 -0
- package/dist/ui/shared/effect-registry.js +543 -0
- package/dist/ui/shared/effect-runtime-loader.js +234 -0
- package/dist/ui/shared/effect-safety.js +153 -0
- package/dist/ui/shared/effect-stock-snapshot.js +43 -0
- package/dist/ui/shared/effect-stock-trust.js +217 -0
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-source.js +103 -0
- package/dist/ui/shared/effect-trial-stage.js +170 -0
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/header-tools.js +1 -1
- package/dist/ui/shared/identity-card.js +273 -0
- package/dist/ui/shared/identity-color.js +261 -0
- package/dist/ui/shared/instance-nav.js +215 -41
- package/dist/ui/shared/owner-css.js +250 -0
- package/dist/ui/shared/owner-effect-prefs.js +117 -0
- package/dist/ui/shared/panel-view.js +103 -10
- package/dist/ui/shared/signin.js +2 -0
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/ui-dialog.js +1 -1
- package/dist/ui/shared/ui-icons.js +3 -0
- package/dist/ui/shared/ui-menu.js +1 -1
- package/dist/ui/shared/ui-toast.js +1 -1
- package/dist/ui/shared/usage-dialog.js +22 -2
- package/dist/ui/shared/view-strings.js +98 -2
- package/dist/ui/shared/web-authorize.js +151 -0
- package/dist/ui/signin.html +1 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/usage/providers.d.ts +28 -0
- package/dist/usage/providers.js +126 -36
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage-report.d.ts +158 -0
- package/dist/usage-report.js +226 -0
- package/dist/usage-report.js.map +1 -0
- package/dist/usage-rollup.d.ts +51 -0
- package/dist/usage-rollup.js +168 -0
- package/dist/usage-rollup.js.map +1 -0
- package/dist/view-api.d.ts +68 -0
- package/dist/view-api.js +249 -8
- package/dist/view-api.js.map +1 -1
- package/dist/web-ambient.d.ts +72 -0
- package/dist/web-ambient.js +161 -0
- package/dist/web-ambient.js.map +1 -0
- package/dist/web-api.d.ts +117 -7
- package/dist/web-api.js +1283 -70
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-limits.d.ts +82 -0
- package/dist/web-appearance-limits.js +83 -0
- package/dist/web-appearance-limits.js.map +1 -0
- package/dist/web-appearance-policy.d.ts +64 -0
- package/dist/web-appearance-policy.js +87 -0
- package/dist/web-appearance-policy.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +53 -0
- package/dist/web-appearance-tools.js +124 -0
- package/dist/web-appearance-tools.js.map +1 -0
- package/dist/web-appearance-view.d.ts +124 -0
- package/dist/web-appearance-view.js +278 -0
- package/dist/web-appearance-view.js.map +1 -0
- package/dist/web-appearance.d.ts +93 -0
- package/dist/web-appearance.js +373 -0
- package/dist/web-appearance.js.map +1 -0
- package/dist/web-asset-parts.d.ts +93 -0
- package/dist/web-asset-parts.js +133 -0
- package/dist/web-asset-parts.js.map +1 -0
- package/dist/web-auth.js +20 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +110 -0
- package/dist/web-card.js +266 -0
- package/dist/web-card.js.map +1 -0
- package/dist/web-channel-echo.d.ts +7 -1
- package/dist/web-channel-echo.js +6 -2
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +63 -0
- package/dist/web-chat-history.js +109 -6
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +1 -0
- package/dist/web-effect-bootstrap.d.ts +12 -0
- package/dist/web-effect-bootstrap.js +49 -0
- package/dist/web-effect-bootstrap.js.map +1 -0
- package/dist/web-effect-bundle.d.ts +28 -0
- package/dist/web-effect-bundle.js +171 -0
- package/dist/web-effect-bundle.js.map +1 -0
- package/dist/web-effect-canonical.d.ts +8 -0
- package/dist/web-effect-canonical.js +27 -0
- package/dist/web-effect-canonical.js.map +1 -0
- package/dist/web-effect-library.d.ts +114 -0
- package/dist/web-effect-library.js +530 -0
- package/dist/web-effect-library.js.map +1 -0
- package/dist/web-effect-playback.d.ts +30 -0
- package/dist/web-effect-playback.js +71 -0
- package/dist/web-effect-playback.js.map +1 -0
- package/dist/web-effect-poster.d.ts +8 -0
- package/dist/web-effect-poster.js +52 -0
- package/dist/web-effect-poster.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +118 -0
- package/dist/web-effect-profiles.js +180 -0
- package/dist/web-effect-profiles.js.map +1 -0
- package/dist/web-effect-service.d.ts +93 -0
- package/dist/web-effect-service.js +335 -0
- package/dist/web-effect-service.js.map +1 -0
- package/dist/web-effect-tools.d.ts +48 -0
- package/dist/web-effect-tools.js +129 -0
- package/dist/web-effect-tools.js.map +1 -0
- package/dist/web-effect-trials.d.ts +86 -0
- package/dist/web-effect-trials.js +204 -0
- package/dist/web-effect-trials.js.map +1 -0
- package/dist/web-effect-upload.d.ts +27 -0
- package/dist/web-effect-upload.js +185 -0
- package/dist/web-effect-upload.js.map +1 -0
- package/dist/web-effect-worker.d.ts +8 -0
- package/dist/web-effect-worker.js +170 -0
- package/dist/web-effect-worker.js.map +1 -0
- package/dist/web-effect.d.ts +103 -0
- package/dist/web-effect.js +153 -0
- package/dist/web-effect.js.map +1 -0
- package/dist/web-guest-access.d.ts +125 -0
- package/dist/web-guest-access.js +234 -0
- package/dist/web-guest-access.js.map +1 -0
- package/dist/web-guest-rooms.d.ts +82 -0
- package/dist/web-guest-rooms.js +135 -0
- package/dist/web-guest-rooms.js.map +1 -0
- package/dist/web-identity-color.d.ts +87 -0
- package/dist/web-identity-color.js +209 -0
- package/dist/web-identity-color.js.map +1 -0
- package/dist/web-identity-palette.d.ts +113 -0
- package/dist/web-identity-palette.js +84 -0
- package/dist/web-identity-palette.js.map +1 -0
- package/dist/web-image-meta.d.ts +38 -0
- package/dist/web-image-meta.js +262 -0
- package/dist/web-image-meta.js.map +1 -0
- package/dist/web-login.d.ts +47 -0
- package/dist/web-login.js +115 -0
- package/dist/web-login.js.map +1 -1
- package/dist/web-mod-agent.d.ts +50 -0
- package/dist/web-mod-agent.js +207 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +157 -0
- package/dist/web-mod-bundle.js +529 -0
- package/dist/web-mod-bundle.js.map +1 -0
- package/dist/web-mod-export.d.ts +68 -0
- package/dist/web-mod-export.js +100 -0
- package/dist/web-mod-export.js.map +1 -0
- package/dist/web-mod-store.d.ts +289 -0
- package/dist/web-mod-store.js +638 -0
- package/dist/web-mod-store.js.map +1 -0
- package/dist/web-org.d.ts +3 -3
- package/dist/web-org.js +11 -17
- package/dist/web-org.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +222 -0
- package/dist/web-owner-css-store.js +440 -0
- package/dist/web-owner-css-store.js.map +1 -0
- package/dist/web-owner-css.d.ts +58 -0
- package/dist/web-owner-css.js +552 -0
- package/dist/web-owner-css.js.map +1 -0
- package/dist/web-prefs.d.ts +62 -0
- package/dist/web-prefs.js +232 -36
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +15 -3
- package/dist/web-preview.js +28 -9
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +41 -0
- package/dist/web-returning-device.js +57 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-session-authorization.d.ts +132 -0
- package/dist/web-session-authorization.js +193 -0
- package/dist/web-session-authorization.js.map +1 -0
- package/dist/web-session.d.ts +70 -10
- package/dist/web-session.js +137 -22
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +67 -2
- package/dist/web-settings-patch.js +196 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +89 -0
- package/dist/web-settings-tools.js +273 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-shell-routes.d.ts +2 -2
- package/dist/web-shell-routes.js +2 -2
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-terminal-http.js +15 -2
- package/dist/web-terminal-http.js.map +1 -1
- package/dist/web-theme-store.d.ts +257 -0
- package/dist/web-theme-store.js +627 -0
- package/dist/web-theme-store.js.map +1 -0
- package/dist/web-theme.d.ts +123 -0
- package/dist/web-theme.js +416 -0
- package/dist/web-theme.js.map +1 -0
- package/dist/web-upload.d.ts +13 -0
- package/dist/web-upload.js +39 -3
- package/dist/web-upload.js.map +1 -1
- package/dist/web-widget.d.ts +72 -0
- package/dist/web-widget.js +245 -0
- package/dist/web-widget.js.map +1 -0
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/systemd.service.ejs +8 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path containment for checkout_repo / release_repo: an instance creates and removes git worktrees only inside its
|
|
3
|
+
* own `<instanceDir>/repos`, and removes only a worktree it can show is one. Nothing outside passes, whatever the
|
|
4
|
+
* path text says (`..`, a sibling with the same prefix, an absolute path, a symlink inside `repos/`).
|
|
5
|
+
*/
|
|
6
|
+
import { lstatSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
|
+
/** Where this instance's checkouts are recorded: path → the source repo's git common dir. */
|
|
9
|
+
export const CHECKOUTS_FILE = ".agend-checkouts.json";
|
|
10
|
+
/** realpath, or — for a path that does not exist (yet) — its normalized absolute form. */
|
|
11
|
+
function real(path) {
|
|
12
|
+
try {
|
|
13
|
+
return realpathSync(path);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return resolve(path);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** `candidate` strictly inside `root`: not root itself, not above it, not on another drive. Both must be real paths. */
|
|
20
|
+
function strictlyInside(root, candidate) {
|
|
21
|
+
const rel = relative(root, candidate);
|
|
22
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The `repos` directory of this instance, as a real directory. A `repos` that is a symlink (or not a directory) is
|
|
26
|
+
* refused: following it would put worktrees, and deletions, somewhere else.
|
|
27
|
+
*/
|
|
28
|
+
export function reposRoot(instanceDir) {
|
|
29
|
+
const dir = join(instanceDir, "repos");
|
|
30
|
+
try {
|
|
31
|
+
const st = lstatSync(dir);
|
|
32
|
+
if (st.isSymbolicLink() || !st.isDirectory())
|
|
33
|
+
return { ok: false, reason: "the instance repos directory is not a plain directory" };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { ok: false, reason: "the instance has no repos directory" };
|
|
37
|
+
}
|
|
38
|
+
return { ok: true, path: real(dir) };
|
|
39
|
+
}
|
|
40
|
+
/** Where checkout_repo may create a worktree: one fresh direct child of `repos`, never an existing path. */
|
|
41
|
+
export function checkoutTarget(instanceDir, source, branch) {
|
|
42
|
+
const root = reposRoot(instanceDir);
|
|
43
|
+
if (!root.ok)
|
|
44
|
+
return root;
|
|
45
|
+
const name = `${basename(source)}-${branch.replace(/\//g, "-")}`;
|
|
46
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\"))
|
|
47
|
+
return { ok: false, reason: "invalid checkout name" };
|
|
48
|
+
const target = join(root.path, name);
|
|
49
|
+
if (!strictlyInside(root.path, target) || relative(root.path, target) !== name)
|
|
50
|
+
return { ok: false, reason: "checkout path outside the instance repos directory" };
|
|
51
|
+
try {
|
|
52
|
+
lstatSync(target);
|
|
53
|
+
return { ok: false, reason: "a checkout already exists at that path" };
|
|
54
|
+
}
|
|
55
|
+
catch { /* free, as required */ }
|
|
56
|
+
return { ok: true, path: target };
|
|
57
|
+
}
|
|
58
|
+
/** The checkouts this instance recorded (path → git common dir). A missing or broken file is no records. */
|
|
59
|
+
export function readCheckouts(root) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = JSON.parse(readFileSync(join(root, CHECKOUTS_FILE), "utf8"));
|
|
62
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
63
|
+
return {};
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const [k, v] of Object.entries(raw))
|
|
66
|
+
if (typeof v === "string")
|
|
67
|
+
out[k] = v;
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function writeCheckouts(root, records) {
|
|
75
|
+
const file = join(root, CHECKOUTS_FILE), tmp = `${file}.${process.pid}.tmp`;
|
|
76
|
+
writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n", { mode: 0o600 });
|
|
77
|
+
renameSync(tmp, file);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The real path release_repo may remove, or why not. It must exist, be strictly inside this instance's `repos`
|
|
81
|
+
* (after resolving symlinks — a link inside `repos/` that points out resolves out, and is refused), and not be a
|
|
82
|
+
* symlink itself. Whether it is a worktree is checked separately (verifyWorktree), with git.
|
|
83
|
+
*/
|
|
84
|
+
export function releaseCandidate(instanceDir, rawPath) {
|
|
85
|
+
if (typeof rawPath !== "string" || rawPath.length === 0 || rawPath.includes("\0"))
|
|
86
|
+
return { ok: false, reason: "missing path" };
|
|
87
|
+
const root = reposRoot(instanceDir);
|
|
88
|
+
if (!root.ok)
|
|
89
|
+
return root;
|
|
90
|
+
const expanded = rawPath.replace(/^~(?=$|\/)/, process.env.HOME || "~");
|
|
91
|
+
const absolute = isAbsolute(expanded) ? expanded : join(root.path, expanded);
|
|
92
|
+
let st;
|
|
93
|
+
try {
|
|
94
|
+
st = lstatSync(absolute);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return { ok: false, reason: "no such checkout" };
|
|
98
|
+
}
|
|
99
|
+
if (st.isSymbolicLink() || !st.isDirectory())
|
|
100
|
+
return { ok: false, reason: "not a checkout directory" };
|
|
101
|
+
const candidate = real(absolute);
|
|
102
|
+
if (!strictlyInside(root.path, candidate))
|
|
103
|
+
return { ok: false, reason: "path outside the instance repos directory" };
|
|
104
|
+
return { ok: true, path: candidate };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A contained candidate is removable only as a linked git worktree whose top level is exactly that directory, and —
|
|
108
|
+
* when this instance recorded it — of the repository it was checked out from. `git` is injected for tests.
|
|
109
|
+
*/
|
|
110
|
+
export async function verifyWorktree(root, candidate, git) {
|
|
111
|
+
let top, gitDir, commonDir;
|
|
112
|
+
try {
|
|
113
|
+
top = real((await git(["rev-parse", "--show-toplevel"], candidate)).trim());
|
|
114
|
+
gitDir = real(resolve(candidate, (await git(["rev-parse", "--git-dir"], candidate)).trim()));
|
|
115
|
+
commonDir = real(resolve(candidate, (await git(["rev-parse", "--git-common-dir"], candidate)).trim()));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return { ok: false, reason: "not a git worktree" };
|
|
119
|
+
}
|
|
120
|
+
if (top !== candidate)
|
|
121
|
+
return { ok: false, reason: "not the top of a git worktree" };
|
|
122
|
+
// A linked worktree keeps its git dir under the common dir's worktrees/; the main checkout's git dir IS the common dir.
|
|
123
|
+
if (gitDir === commonDir || !strictlyInside(join(commonDir, "worktrees"), gitDir))
|
|
124
|
+
return { ok: false, reason: "not a linked git worktree" };
|
|
125
|
+
const recorded = readCheckouts(root)[candidate];
|
|
126
|
+
if (recorded !== undefined && real(recorded) !== commonDir)
|
|
127
|
+
return { ok: false, reason: "a different repository than the one checked out" };
|
|
128
|
+
if (recorded === undefined) {
|
|
129
|
+
// A checkout from before checkouts were recorded: its repository must list it as one of its worktrees.
|
|
130
|
+
let listed;
|
|
131
|
+
try {
|
|
132
|
+
listed = await git(["--git-dir", commonDir, "worktree", "list", "--porcelain"], commonDir);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return { ok: false, reason: "not a git worktree" };
|
|
136
|
+
}
|
|
137
|
+
const paths = listed.split("\n").filter(l => l.startsWith("worktree ")).map(l => real(l.slice("worktree ".length)));
|
|
138
|
+
if (!paths.includes(candidate))
|
|
139
|
+
return { ok: false, reason: "not a worktree of its repository" };
|
|
140
|
+
}
|
|
141
|
+
return { ok: true, commonDir };
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=repo-worktree-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-worktree-guard.js","sourceRoot":"","sources":["../src/repo-worktree-guard.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3F,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1E,6FAA6F;AAC7F,MAAM,CAAC,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAItD,0FAA0F;AAC1F,SAAS,IAAI,CAAC,IAAY;IACxB,IAAI,CAAC;QAAC,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,CAAC;AACpE,CAAC;AAED,wHAAwH;AACxH,SAAS,cAAc,CAAC,IAAY,EAAE,SAAiB;IACrD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACtC,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,WAAmB;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uDAAuD,EAAE,CAAC;IACtI,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qCAAqC,EAAE,CAAC;IAAC,CAAC;IAChF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,MAAc,EAAE,MAAc;IAChF,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,IAAI,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;IACjE,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAC/I,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oDAAoD,EAAE,CAAC;IACnK,IAAI,CAAC;QAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wCAAwC,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,uBAAuB,CAAC,CAAC;IACpI,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;QACrE,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChF,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAA+B;IAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,GAAG,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IAC5E,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7E,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAAmB,EAAE,OAAgB;IACpE,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAChI,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC7E,IAAI,EAAE,CAAC;IACP,IAAI,CAAC;QAAC,EAAE,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAAC,CAAC;IAC7F,IAAI,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;IACvG,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,2CAA2C,EAAE,CAAC;IACrH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,SAAiB,EACjB,GAAqD;IAErD,IAAI,GAAW,EAAE,MAAc,EAAE,SAAiB,CAAC;IACnD,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5E,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7F,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACzG,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAAC,CAAC;IAC/D,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,+BAA+B,EAAE,CAAC;IACrF,wHAAwH;IACxH,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IAC7I,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iDAAiD,EAAE,CAAC;IAC5I,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,uGAAuG;QACvG,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YAAC,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,SAAS,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;QAAC,CAAC;QACjK,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACpH,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;IACnG,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC"}
|
|
@@ -37,6 +37,19 @@ export type RestartProgressMode = "restart" | "update" | "reload";
|
|
|
37
37
|
export declare const RESTART_PROGRESS_TERMINAL_TIMEOUT_MS = 30000;
|
|
38
38
|
/** Minimum ms between non-terminal progress edits (throttle window). */
|
|
39
39
|
export declare const PROGRESS_THROTTLE_MS = 2500;
|
|
40
|
+
/**
|
|
41
|
+
* #2175: Telegram allows a bot about 20 messages a minute in one group, edits included, and every topic of a forum
|
|
42
|
+
* group shares it with the fleet's other startup messages. The 1 Hz update ticker drew 429s whose retry_after outlasted
|
|
43
|
+
* the terminal budget, leaving "Starting... 13/14" as the last word. Progress edits to Telegram are spaced this far
|
|
44
|
+
* apart (10 a minute); the newest frame is the one sent.
|
|
45
|
+
*/
|
|
46
|
+
export declare const TELEGRAM_PROGRESS_EDIT_SPACING_MS = 6000;
|
|
47
|
+
/**
|
|
48
|
+
* #2175: how long a terminal state that could not land within RESTART_PROGRESS_TERMINAL_TIMEOUT_MS (a 429 window
|
|
49
|
+
* longer than that, or the adapter not ready yet) keeps being retried in the background — after startup, never
|
|
50
|
+
* holding it.
|
|
51
|
+
*/
|
|
52
|
+
export declare const TERMINAL_BACKGROUND_WINDOW_MS: number;
|
|
40
53
|
/** #1716: the shared background-work line for the completion and every fallback path. */
|
|
41
54
|
export declare function formatBackgroundWorkEndedLine(names: readonly string[]): string;
|
|
42
55
|
/** One terminal text shared by the in-place edit and FleetManager fallback. */
|
|
@@ -88,6 +101,14 @@ export declare class RestartProgress {
|
|
|
88
101
|
private throttleTimer;
|
|
89
102
|
/** Resolves the active drainProgressEdits backoff sleep early when finish() is called. */
|
|
90
103
|
private wakeBackoff;
|
|
104
|
+
/** #2175: the end of the newest 429 window the provider named; no edit is sent before it. */
|
|
105
|
+
private rateLimitedUntil;
|
|
106
|
+
/** #2175: when the last progress edit was sent (Telegram pacing). */
|
|
107
|
+
private lastEditSentAt;
|
|
108
|
+
/** #2175: the background terminal retry, while one is owed. */
|
|
109
|
+
private deferredTerminal;
|
|
110
|
+
/** #2175: the last terminal text the provider acknowledged on the message. */
|
|
111
|
+
private landedText;
|
|
91
112
|
constructor(total: number, startedAt: number, logger: ProgressLogger, options?: RestartProgressOptions);
|
|
92
113
|
/** May be called before the channel adapter is ready; progress is retained. */
|
|
93
114
|
markReady(): void;
|
|
@@ -95,7 +116,10 @@ export declare class RestartProgress {
|
|
|
95
116
|
start(target: RestartProgressTarget | null): Promise<boolean>;
|
|
96
117
|
/** Adopt the pre-update message after the new fleet process reconnects. */
|
|
97
118
|
resume(target: RestartProgressTarget | null, messageId: string): Promise<boolean>;
|
|
98
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Deliver the terminal state. Returns true after an edit or fresh fallback succeeds, or when a passing obstacle (a
|
|
121
|
+
* 429 window, an adapter not ready yet) handed it to the background retry (#2175) — which owns it from then on.
|
|
122
|
+
*/
|
|
99
123
|
finish(summary?: RestartProgressSummary): Promise<boolean>;
|
|
100
124
|
get readyCount(): number;
|
|
101
125
|
/**
|
|
@@ -111,6 +135,8 @@ export declare class RestartProgress {
|
|
|
111
135
|
/** Tail-only coalescing for non-terminal progress. */
|
|
112
136
|
private scheduleProgressEdit;
|
|
113
137
|
private drainProgressEdits;
|
|
138
|
+
/** A sleep finish() can cut short (it then delivers the terminal state itself). */
|
|
139
|
+
private interruptibleSleep;
|
|
114
140
|
/**
|
|
115
141
|
* A terminal state must mean "delivered", not merely "had a message id".
|
|
116
142
|
* Wait through a bounded adapter-recovery window, re-resolving replacement
|
|
@@ -119,6 +145,44 @@ export declare class RestartProgress {
|
|
|
119
145
|
* 429 responses are retried with their retry-after delay up to the deadline.
|
|
120
146
|
*/
|
|
121
147
|
private deliverTerminal;
|
|
148
|
+
/**
|
|
149
|
+
* #2175: the terminal state could not land within the startup budget for a reason that passes — a 429 window, or
|
|
150
|
+
* the adapter not ready yet. Startup goes on; this retries after it (edit in place, else a new message) until
|
|
151
|
+
* TERMINAL_BACKGROUND_WINDOW_MS, each failure at warn and the give-up at error. Returns true: the completion is owed
|
|
152
|
+
* by this retry, so a caller must not post a second one into the same refusing chat.
|
|
153
|
+
*
|
|
154
|
+
* It is the message's only writer from here on: one provider call at a time; a newer text (replaceTerminalText)
|
|
155
|
+
* is written after the call in flight settles; and a call whose outcome is still unknown at its deadline is never
|
|
156
|
+
* followed by another write (a duplicate or an overwrite could follow) — it is logged loudly, and only its own
|
|
157
|
+
* late settlement may write the newer text it missed.
|
|
158
|
+
*/
|
|
159
|
+
private deferTerminal;
|
|
160
|
+
private scheduleDeferred;
|
|
161
|
+
/** One provider call for the deferred terminal; resolves "unknown" when its deadline passes unsettled. */
|
|
162
|
+
private deferredCall;
|
|
163
|
+
/** A call that settled after its deadline: the message holds `sent` if it landed; write a newer text it missed. */
|
|
164
|
+
private settleLate;
|
|
165
|
+
private attemptDeferred;
|
|
166
|
+
private deferredDelivered;
|
|
167
|
+
private loud;
|
|
168
|
+
/**
|
|
169
|
+
* #2175: no ready adapter by the deadline. One that is not ready (a gateway still connecting) passes — the
|
|
170
|
+
* background retry takes it; one that is ready means the budget went to a provider call that never settled, which
|
|
171
|
+
* stays a loud failure as before.
|
|
172
|
+
*/
|
|
173
|
+
private adapterNotReady;
|
|
174
|
+
/**
|
|
175
|
+
* #2175: hand a newer text for the same message (the completion with a cleanup warning) to the background retry
|
|
176
|
+
* while it owns the message, so the two never race on it. False when nothing is owed: the caller writes it itself.
|
|
177
|
+
*/
|
|
178
|
+
replaceTerminalText(text: string): boolean;
|
|
179
|
+
/**
|
|
180
|
+
* #2175: whether this exact text is known to have landed on the message (the provider acknowledged it). A text
|
|
181
|
+
* handed over with replaceTerminalText is not landed until then.
|
|
182
|
+
*/
|
|
183
|
+
terminalLanded(text: string): boolean;
|
|
184
|
+
/** #2175: whether the terminal state is still owed by the background retry. */
|
|
185
|
+
get terminalDeferred(): boolean;
|
|
122
186
|
private waitForReadyAdapter;
|
|
123
187
|
private beforeDeadline;
|
|
124
188
|
private terminalDeliveryFailed;
|
package/dist/restart-progress.js
CHANGED
|
@@ -6,6 +6,22 @@ const TERMINAL_DELIVERY_RETRY_MS = 1_000;
|
|
|
6
6
|
export const RESTART_PROGRESS_TERMINAL_TIMEOUT_MS = 30_000;
|
|
7
7
|
/** Minimum ms between non-terminal progress edits (throttle window). */
|
|
8
8
|
export const PROGRESS_THROTTLE_MS = 2_500;
|
|
9
|
+
/**
|
|
10
|
+
* #2175: Telegram allows a bot about 20 messages a minute in one group, edits included, and every topic of a forum
|
|
11
|
+
* group shares it with the fleet's other startup messages. The 1 Hz update ticker drew 429s whose retry_after outlasted
|
|
12
|
+
* the terminal budget, leaving "Starting... 13/14" as the last word. Progress edits to Telegram are spaced this far
|
|
13
|
+
* apart (10 a minute); the newest frame is the one sent.
|
|
14
|
+
*/
|
|
15
|
+
export const TELEGRAM_PROGRESS_EDIT_SPACING_MS = 6_000;
|
|
16
|
+
/**
|
|
17
|
+
* #2175: how long a terminal state that could not land within RESTART_PROGRESS_TERMINAL_TIMEOUT_MS (a 429 window
|
|
18
|
+
* longer than that, or the adapter not ready yet) keeps being retried in the background — after startup, never
|
|
19
|
+
* holding it.
|
|
20
|
+
*/
|
|
21
|
+
export const TERMINAL_BACKGROUND_WINDOW_MS = 10 * 60_000;
|
|
22
|
+
const TERMINAL_BACKGROUND_RETRY_MS = 15_000;
|
|
23
|
+
/** #2175: a 429 window must leave this much of the terminal budget for the attempt after it, else it is deferred. */
|
|
24
|
+
const TERMINAL_ATTEMPT_MARGIN_MS = 5_000;
|
|
9
25
|
function formatElapsed(ms) {
|
|
10
26
|
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
11
27
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -90,6 +106,14 @@ export class RestartProgress {
|
|
|
90
106
|
throttleTimer = null;
|
|
91
107
|
/** Resolves the active drainProgressEdits backoff sleep early when finish() is called. */
|
|
92
108
|
wakeBackoff = null;
|
|
109
|
+
/** #2175: the end of the newest 429 window the provider named; no edit is sent before it. */
|
|
110
|
+
rateLimitedUntil = 0;
|
|
111
|
+
/** #2175: when the last progress edit was sent (Telegram pacing). */
|
|
112
|
+
lastEditSentAt = Number.NEGATIVE_INFINITY;
|
|
113
|
+
/** #2175: the background terminal retry, while one is owed. */
|
|
114
|
+
deferredTerminal = null;
|
|
115
|
+
/** #2175: the last terminal text the provider acknowledged on the message. */
|
|
116
|
+
landedText = null;
|
|
93
117
|
constructor(total, startedAt, logger, options = {}) {
|
|
94
118
|
this.total = total;
|
|
95
119
|
this.startedAt = startedAt;
|
|
@@ -146,7 +170,10 @@ export class RestartProgress {
|
|
|
146
170
|
this.updateTimer.unref?.();
|
|
147
171
|
return true;
|
|
148
172
|
}
|
|
149
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* Deliver the terminal state. Returns true after an edit or fresh fallback succeeds, or when a passing obstacle (a
|
|
175
|
+
* 429 window, an adapter not ready yet) handed it to the background retry (#2175) — which owns it from then on.
|
|
176
|
+
*/
|
|
150
177
|
async finish(summary) {
|
|
151
178
|
if (!this.enabled)
|
|
152
179
|
return false;
|
|
@@ -252,22 +279,31 @@ export class RestartProgress {
|
|
|
252
279
|
const adapter = this.resolveAdapter(target);
|
|
253
280
|
if (!target || !messageId || !adapter || !this.adapterReady(adapter))
|
|
254
281
|
continue;
|
|
282
|
+
// #2175: Telegram's spacing (a 429 window is slept through below); the newest frame is sent when it opens.
|
|
283
|
+
const openAt = adapter.type === "telegram" ? this.lastEditSentAt + TELEGRAM_PROGRESS_EDIT_SPACING_MS : 0;
|
|
284
|
+
if (openAt > Date.now()) {
|
|
285
|
+
this.pendingProgressText ??= text;
|
|
286
|
+
await this.interruptibleSleep(openAt - Date.now());
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
255
289
|
try {
|
|
290
|
+
this.lastEditSentAt = Date.now();
|
|
256
291
|
await adapter.editMessage(target.chatId, messageId, text, target.threadId);
|
|
257
292
|
}
|
|
258
293
|
catch (err) {
|
|
259
294
|
const backoffMs = retryAfterMs(err);
|
|
260
295
|
if (backoffMs > 0) {
|
|
296
|
+
this.rateLimitedUntil = Math.max(this.rateLimitedUntil, Date.now() + backoffMs);
|
|
261
297
|
this.logger.warn({ retryAfterMs: backoffMs }, "Fleet restart progress edit throttled (429); backing off");
|
|
298
|
+
// A 429 answered after finish() began: the window is the terminal path's now (it waits it out or defers);
|
|
299
|
+
// sleeping here would only spend the terminal budget on this worker.
|
|
300
|
+
if (this.finished)
|
|
301
|
+
return;
|
|
262
302
|
// Re-queue the current frame (unless a newer one arrived) so the loop
|
|
263
303
|
// retries this edit after the backoff window.
|
|
264
304
|
if (this.pendingProgressText == null)
|
|
265
305
|
this.pendingProgressText = text;
|
|
266
|
-
await
|
|
267
|
-
const t = setTimeout(() => { this.wakeBackoff = null; resolve(); }, backoffMs);
|
|
268
|
-
t.unref?.();
|
|
269
|
-
this.wakeBackoff = () => { clearTimeout(t); this.wakeBackoff = null; resolve(); };
|
|
270
|
-
});
|
|
306
|
+
await this.interruptibleSleep(backoffMs);
|
|
271
307
|
}
|
|
272
308
|
else {
|
|
273
309
|
this.logger.warn({ err }, "Failed to edit fleet restart progress");
|
|
@@ -275,6 +311,14 @@ export class RestartProgress {
|
|
|
275
311
|
}
|
|
276
312
|
}
|
|
277
313
|
}
|
|
314
|
+
/** A sleep finish() can cut short (it then delivers the terminal state itself). */
|
|
315
|
+
interruptibleSleep(ms) {
|
|
316
|
+
return new Promise(resolve => {
|
|
317
|
+
const t = setTimeout(() => { this.wakeBackoff = null; resolve(); }, ms);
|
|
318
|
+
t.unref?.();
|
|
319
|
+
this.wakeBackoff = () => { clearTimeout(t); this.wakeBackoff = null; resolve(); };
|
|
320
|
+
});
|
|
321
|
+
}
|
|
278
322
|
/**
|
|
279
323
|
* A terminal state must mean "delivered", not merely "had a message id".
|
|
280
324
|
* Wait through a bounded adapter-recovery window, re-resolving replacement
|
|
@@ -299,16 +343,28 @@ export class RestartProgress {
|
|
|
299
343
|
}
|
|
300
344
|
let adapter = await this.waitForReadyAdapter(deadline);
|
|
301
345
|
if (!adapter)
|
|
302
|
-
return this.
|
|
346
|
+
return this.adapterNotReady(text);
|
|
303
347
|
// Retry terminal edit with 429-aware backoff until the deadline.
|
|
304
348
|
while (true) {
|
|
349
|
+
// #2175: a 429 window still open from the progress edits (finish() cut that sleep short) is waited out here;
|
|
350
|
+
// one that outlasts the budget goes to the background retry instead of an edit the provider would refuse.
|
|
351
|
+
if (this.rateLimitedUntil > Date.now()) {
|
|
352
|
+
if (this.rateLimitedUntil + TERMINAL_ATTEMPT_MARGIN_MS > deadline)
|
|
353
|
+
return this.deferTerminal(text, this.rateLimitedUntil, "rate limited (429)");
|
|
354
|
+
await new Promise(resolve => { const t = setTimeout(resolve, this.rateLimitedUntil - Date.now()); t.unref?.(); });
|
|
355
|
+
adapter = await this.waitForReadyAdapter(deadline);
|
|
356
|
+
if (!adapter)
|
|
357
|
+
return this.adapterNotReady(text);
|
|
358
|
+
}
|
|
305
359
|
const remaining = deadline - Date.now();
|
|
306
360
|
if (remaining <= 0)
|
|
307
361
|
break;
|
|
308
362
|
const editAdapter = adapter;
|
|
309
363
|
const edit = await this.beforeDeadline(() => editAdapter.editMessage(target.chatId, this.messageId, text, target.threadId), deadline);
|
|
310
|
-
if (edit.status === "fulfilled")
|
|
364
|
+
if (edit.status === "fulfilled") {
|
|
365
|
+
this.landedText = text;
|
|
311
366
|
return true;
|
|
367
|
+
}
|
|
312
368
|
if (edit.status === "timeout") {
|
|
313
369
|
this.logger.warn({}, "Timed out editing fleet restart progress terminal state");
|
|
314
370
|
break; // no point retrying after timeout
|
|
@@ -316,6 +372,9 @@ export class RestartProgress {
|
|
|
316
372
|
// rejected
|
|
317
373
|
const backoff = retryAfterMs(edit.reason);
|
|
318
374
|
if (backoff > 0) {
|
|
375
|
+
this.rateLimitedUntil = Math.max(this.rateLimitedUntil, Date.now() + backoff);
|
|
376
|
+
if (this.rateLimitedUntil + TERMINAL_ATTEMPT_MARGIN_MS > deadline)
|
|
377
|
+
return this.deferTerminal(text, this.rateLimitedUntil, "rate limited (429)");
|
|
319
378
|
const wait = Math.min(backoff, deadline - Date.now());
|
|
320
379
|
if (wait > 0) {
|
|
321
380
|
this.logger.warn({ retryAfterMs: backoff }, "Fleet restart terminal edit throttled (429); will retry");
|
|
@@ -326,7 +385,7 @@ export class RestartProgress {
|
|
|
326
385
|
// Re-resolve adapter after backoff (it may have rotated)
|
|
327
386
|
adapter = await this.waitForReadyAdapter(deadline);
|
|
328
387
|
if (!adapter)
|
|
329
|
-
return this.
|
|
388
|
+
return this.adapterNotReady(text);
|
|
330
389
|
continue;
|
|
331
390
|
}
|
|
332
391
|
}
|
|
@@ -338,14 +397,20 @@ export class RestartProgress {
|
|
|
338
397
|
// replacement generation before the one and only fresh-send attempt.
|
|
339
398
|
adapter = await this.waitForReadyAdapter(deadline);
|
|
340
399
|
if (!adapter)
|
|
341
|
-
return this.
|
|
400
|
+
return this.adapterNotReady(text);
|
|
342
401
|
const sendAdapter = adapter;
|
|
343
402
|
const sent = await this.beforeDeadline(() => sendAdapter.sendText(target.chatId, text, { threadId: target.threadId }), deadline);
|
|
344
403
|
if (sent.status === "fulfilled") {
|
|
404
|
+
this.landedText = text;
|
|
345
405
|
this.logger.warn({}, "Posted fresh fleet completion after terminal progress edit was unavailable");
|
|
346
406
|
return true;
|
|
347
407
|
}
|
|
348
408
|
if (sent.status === "rejected") {
|
|
409
|
+
const sendBackoff = retryAfterMs(sent.reason);
|
|
410
|
+
if (sendBackoff > 0) {
|
|
411
|
+
this.rateLimitedUntil = Math.max(this.rateLimitedUntil, Date.now() + sendBackoff);
|
|
412
|
+
return this.deferTerminal(text, this.rateLimitedUntil, "rate limited (429)");
|
|
413
|
+
}
|
|
349
414
|
this.logger.warn({ err: sent.reason }, "Failed to post fresh fleet completion after edit failure");
|
|
350
415
|
}
|
|
351
416
|
else {
|
|
@@ -353,6 +418,167 @@ export class RestartProgress {
|
|
|
353
418
|
}
|
|
354
419
|
return this.terminalDeliveryFailed();
|
|
355
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* #2175: the terminal state could not land within the startup budget for a reason that passes — a 429 window, or
|
|
423
|
+
* the adapter not ready yet. Startup goes on; this retries after it (edit in place, else a new message) until
|
|
424
|
+
* TERMINAL_BACKGROUND_WINDOW_MS, each failure at warn and the give-up at error. Returns true: the completion is owed
|
|
425
|
+
* by this retry, so a caller must not post a second one into the same refusing chat.
|
|
426
|
+
*
|
|
427
|
+
* It is the message's only writer from here on: one provider call at a time; a newer text (replaceTerminalText)
|
|
428
|
+
* is written after the call in flight settles; and a call whose outcome is still unknown at its deadline is never
|
|
429
|
+
* followed by another write (a duplicate or an overwrite could follow) — it is logged loudly, and only its own
|
|
430
|
+
* late settlement may write the newer text it missed.
|
|
431
|
+
*/
|
|
432
|
+
deferTerminal(text, at, reason) {
|
|
433
|
+
const state = {
|
|
434
|
+
text, timer: null, giveUpAt: Date.now() + TERMINAL_BACKGROUND_WINDOW_MS,
|
|
435
|
+
};
|
|
436
|
+
this.deferredTerminal = state;
|
|
437
|
+
this.logger.warn({ reason, retryInMs: Math.max(0, at - Date.now()) }, "Fleet completion could not land yet — retrying in the background");
|
|
438
|
+
this.scheduleDeferred(state, at);
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
scheduleDeferred(state, when) {
|
|
442
|
+
if (this.deferredTerminal !== state)
|
|
443
|
+
return;
|
|
444
|
+
if (when > state.giveUpAt) {
|
|
445
|
+
this.deferredTerminal = null;
|
|
446
|
+
this.loud({ window_ms: TERMINAL_BACKGROUND_WINDOW_MS }, "Fleet completion could not be delivered in the background window");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
state.timer = setTimeout(() => { state.timer = null; void this.attemptDeferred(state); }, Math.max(0, when - Date.now()));
|
|
450
|
+
state.timer.unref?.();
|
|
451
|
+
}
|
|
452
|
+
/** One provider call for the deferred terminal; resolves "unknown" when its deadline passes unsettled. */
|
|
453
|
+
async deferredCall(call, onLate) {
|
|
454
|
+
const op = Promise.resolve().then(call);
|
|
455
|
+
let late = false;
|
|
456
|
+
op.then(v => { if (late)
|
|
457
|
+
onLate(true, v); }, reason => { if (late)
|
|
458
|
+
onLate(false, undefined, reason); });
|
|
459
|
+
const result = await this.beforeDeadline(() => op, Date.now() + RESTART_PROGRESS_TERMINAL_TIMEOUT_MS);
|
|
460
|
+
if (result.status === "timeout") {
|
|
461
|
+
late = true;
|
|
462
|
+
return { status: "unknown" };
|
|
463
|
+
}
|
|
464
|
+
return result.status === "fulfilled" ? { status: "fulfilled", value: result.value } : { status: "rejected", reason: result.reason };
|
|
465
|
+
}
|
|
466
|
+
/** A call that settled after its deadline: the message holds `sent` if it landed; write a newer text it missed. */
|
|
467
|
+
settleLate(state, sent, ok, reason) {
|
|
468
|
+
if (this.deferredTerminal !== state)
|
|
469
|
+
return;
|
|
470
|
+
if (!ok) {
|
|
471
|
+
// Settled as refused: nothing landed, so another write is safe again — after the window a 429 names.
|
|
472
|
+
const backoff = retryAfterMs(reason);
|
|
473
|
+
if (backoff > 0)
|
|
474
|
+
this.rateLimitedUntil = Math.max(this.rateLimitedUntil, Date.now() + backoff);
|
|
475
|
+
this.logger.warn({ err: reason }, "A fleet completion call that had timed out was refused; retrying in the background");
|
|
476
|
+
this.scheduleDeferred(state, Math.max(Date.now() + TERMINAL_BACKGROUND_RETRY_MS, this.rateLimitedUntil));
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
this.landedText = sent;
|
|
480
|
+
if (state.text !== sent) {
|
|
481
|
+
this.logger.warn({}, "A late fleet completion call landed; writing the newer text it missed");
|
|
482
|
+
this.scheduleDeferred(state, Math.max(Date.now(), this.rateLimitedUntil));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
this.deferredTerminal = null;
|
|
486
|
+
this.logger.warn({}, "Fleet completion delivered late (a call that had timed out settled)");
|
|
487
|
+
}
|
|
488
|
+
async attemptDeferred(state) {
|
|
489
|
+
if (this.deferredTerminal !== state)
|
|
490
|
+
return;
|
|
491
|
+
const target = this.target;
|
|
492
|
+
if (!target || !this.messageId) {
|
|
493
|
+
this.deferredTerminal = null;
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const adapter = this.resolveAdapter();
|
|
497
|
+
if (!adapter || !this.adapterReady(adapter))
|
|
498
|
+
return this.scheduleDeferred(state, Date.now() + TERMINAL_BACKGROUND_RETRY_MS);
|
|
499
|
+
const sent = state.text;
|
|
500
|
+
const messageId = this.messageId;
|
|
501
|
+
const edit = await this.deferredCall(() => adapter.editMessage(target.chatId, messageId, sent, target.threadId), (ok, _value, reason) => this.settleLate(state, sent, ok, reason));
|
|
502
|
+
if (this.deferredTerminal !== state)
|
|
503
|
+
return;
|
|
504
|
+
if (edit.status === "unknown") {
|
|
505
|
+
return this.loud({ timeout_ms: RESTART_PROGRESS_TERMINAL_TIMEOUT_MS }, "Background fleet completion edit did not settle — not retried, its outcome is unknown");
|
|
506
|
+
}
|
|
507
|
+
if (edit.status === "fulfilled")
|
|
508
|
+
return this.deferredDelivered(state, sent, "Fleet completion delivered late (background retry)");
|
|
509
|
+
const editBackoff = retryAfterMs(edit.reason);
|
|
510
|
+
if (editBackoff > 0) {
|
|
511
|
+
this.rateLimitedUntil = Math.max(this.rateLimitedUntil, Date.now() + editBackoff);
|
|
512
|
+
this.logger.warn({ retryAfterMs: editBackoff }, "Fleet completion still throttled (429); retrying in the background");
|
|
513
|
+
return this.scheduleDeferred(state, this.rateLimitedUntil);
|
|
514
|
+
}
|
|
515
|
+
this.logger.warn({ err: edit.reason }, "Background fleet completion edit failed; posting it as a new message");
|
|
516
|
+
const fresh = state.text;
|
|
517
|
+
const posted = await this.deferredCall(() => adapter.sendText(target.chatId, fresh, { threadId: target.threadId }), (ok, value, reason) => {
|
|
518
|
+
if (ok && value)
|
|
519
|
+
this.messageId = value.messageId;
|
|
520
|
+
this.settleLate(state, fresh, ok, reason);
|
|
521
|
+
});
|
|
522
|
+
if (this.deferredTerminal !== state)
|
|
523
|
+
return;
|
|
524
|
+
if (posted.status === "unknown") {
|
|
525
|
+
return this.loud({ timeout_ms: RESTART_PROGRESS_TERMINAL_TIMEOUT_MS }, "Background fleet completion message did not settle — not retried, its outcome is unknown");
|
|
526
|
+
}
|
|
527
|
+
if (posted.status === "fulfilled") {
|
|
528
|
+
// Later writers (a newer text) edit the message that now carries the completion.
|
|
529
|
+
this.messageId = posted.value.messageId;
|
|
530
|
+
return this.deferredDelivered(state, fresh, "Fleet completion posted late as a new message (background retry)");
|
|
531
|
+
}
|
|
532
|
+
const sendBackoff = retryAfterMs(posted.reason);
|
|
533
|
+
if (sendBackoff > 0)
|
|
534
|
+
this.rateLimitedUntil = Math.max(this.rateLimitedUntil, Date.now() + sendBackoff);
|
|
535
|
+
this.logger.warn({ err: posted.reason }, "Background fleet completion message failed; retrying");
|
|
536
|
+
this.scheduleDeferred(state, Math.max(this.rateLimitedUntil, Date.now() + TERMINAL_BACKGROUND_RETRY_MS));
|
|
537
|
+
}
|
|
538
|
+
deferredDelivered(state, sent, message) {
|
|
539
|
+
this.logger.warn({}, message);
|
|
540
|
+
this.landedText = sent;
|
|
541
|
+
if (state.text !== sent)
|
|
542
|
+
return this.scheduleDeferred(state, Math.max(Date.now(), this.rateLimitedUntil));
|
|
543
|
+
if (state.timer)
|
|
544
|
+
clearTimeout(state.timer);
|
|
545
|
+
this.deferredTerminal = null;
|
|
546
|
+
}
|
|
547
|
+
loud(data, message) {
|
|
548
|
+
if (this.logger.error)
|
|
549
|
+
this.logger.error(data, message);
|
|
550
|
+
else
|
|
551
|
+
this.logger.warn(data, message);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* #2175: no ready adapter by the deadline. One that is not ready (a gateway still connecting) passes — the
|
|
555
|
+
* background retry takes it; one that is ready means the budget went to a provider call that never settled, which
|
|
556
|
+
* stays a loud failure as before.
|
|
557
|
+
*/
|
|
558
|
+
adapterNotReady(text) {
|
|
559
|
+
const adapter = this.resolveAdapter();
|
|
560
|
+
if (adapter && this.adapterReady(adapter))
|
|
561
|
+
return this.terminalDeliveryFailed();
|
|
562
|
+
return this.deferTerminal(text, Date.now() + TERMINAL_BACKGROUND_RETRY_MS, "adapter not ready");
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* #2175: hand a newer text for the same message (the completion with a cleanup warning) to the background retry
|
|
566
|
+
* while it owns the message, so the two never race on it. False when nothing is owed: the caller writes it itself.
|
|
567
|
+
*/
|
|
568
|
+
replaceTerminalText(text) {
|
|
569
|
+
const state = this.deferredTerminal;
|
|
570
|
+
if (!state)
|
|
571
|
+
return false;
|
|
572
|
+
state.text = text;
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* #2175: whether this exact text is known to have landed on the message (the provider acknowledged it). A text
|
|
577
|
+
* handed over with replaceTerminalText is not landed until then.
|
|
578
|
+
*/
|
|
579
|
+
terminalLanded(text) { return this.landedText === text; }
|
|
580
|
+
/** #2175: whether the terminal state is still owed by the background retry. */
|
|
581
|
+
get terminalDeferred() { return this.deferredTerminal !== null; }
|
|
356
582
|
async waitForReadyAdapter(deadline) {
|
|
357
583
|
while (true) {
|
|
358
584
|
const remaining = deadline - Date.now();
|