@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/README.zh-TW.md +7 -1
- package/dist/agent-cli-instructions.md +9 -2
- package/dist/agent-cli.js +63 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +4 -1
- package/dist/agent-file-read.js +189 -16
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.d.ts +11 -0
- package/dist/agent-whats-new.js +29 -5
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +45 -13
- 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/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +18 -8
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +8 -0
- package/dist/backend/codex-metadata.js +74 -17
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.js +15 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +2 -0
- package/dist/backend/credential-profile.js +2 -0
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/types.d.ts +6 -0
- package/dist/backend/types.js +15 -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 +23 -3
- package/dist/backend-command.js +25 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/bug-report-cli.d.ts +30 -0
- package/dist/bug-report-cli.js +138 -0
- package/dist/bug-report-cli.js.map +1 -0
- package/dist/bug-report.d.ts +47 -0
- package/dist/bug-report.js +136 -0
- package/dist/bug-report.js.map +1 -0
- 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-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +55 -3
- package/dist/cache-service.js +152 -16
- 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 +9 -0
- package/dist/channel/adapters/discord.js +49 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +22 -11
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +6 -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 +224 -111
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +6 -0
- package/dist/config-validator.js +129 -3
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +13 -1
- package/dist/config.js +14 -5
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-notice.d.ts +7 -0
- package/dist/cross-instance-notice.js +10 -0
- package/dist/cross-instance-notice.js.map +1 -1
- package/dist/daemon.d.ts +24 -1
- package/dist/daemon.js +151 -19
- 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 +17 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/diagnostic-redaction.d.ts +10 -0
- package/dist/diagnostic-redaction.js +167 -0
- package/dist/diagnostic-redaction.js.map +1 -0
- package/dist/fleet-context.d.ts +10 -0
- package/dist/fleet-manager.d.ts +505 -15
- package/dist/fleet-manager.js +1870 -204
- 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/bug-report/SKILL.md +31 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +17 -6
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
- 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/scheduling/SKILL.md +4 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +190 -47
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +545 -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-config-impact.js +7 -1
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -1
- package/dist/instance-lifecycle.js +79 -18
- 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 +173 -12
- package/dist/locale.js.map +1 -1
- package/dist/log-steps.d.ts +1 -0
- package/dist/log-steps.js +15 -0
- package/dist/log-steps.js.map +1 -1
- package/dist/login-controller.d.ts +45 -0
- package/dist/login-controller.js +101 -5
- package/dist/login-controller.js.map +1 -1
- package/dist/ls-attach-resolve.d.ts +96 -0
- package/dist/ls-attach-resolve.js +180 -0
- package/dist/ls-attach-resolve.js.map +1 -0
- package/dist/ls-rows.d.ts +12 -0
- package/dist/ls-rows.js +82 -3
- package/dist/ls-rows.js.map +1 -1
- 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.d.ts +10 -0
- package/dist/needs-you.js +2 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +11 -0
- package/dist/outbound-handlers.js +139 -11
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +200 -4
- package/dist/outbound-schemas.js +124 -11
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +17 -4
- 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 +19 -1
- package/dist/quickstart-api.js +94 -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/restart-progress.d.ts +80 -1
- package/dist/restart-progress.js +306 -21
- 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/db.d.ts +40 -13
- package/dist/scheduler/db.js +123 -89
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/task-board-text.d.ts +26 -0
- package/dist/scheduler/task-board-text.js +135 -0
- package/dist/scheduler/task-board-text.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +650 -0
- package/dist/scheduler/task-board.js +1938 -0
- package/dist/scheduler/task-board.js.map +1 -0
- package/dist/scheduler/types.d.ts +6 -1
- package/dist/search/search-docs.d.ts +58 -0
- package/dist/search/search-docs.js +63 -0
- package/dist/search/search-docs.js.map +1 -0
- package/dist/search/search-index.d.ts +141 -0
- package/dist/search/search-index.js +440 -0
- package/dist/search/search-index.js.map +1 -0
- package/dist/search/search-store.d.ts +122 -0
- package/dist/search/search-store.js +236 -0
- package/dist/search/search-store.js.map +1 -0
- package/dist/search/search-worker.d.ts +1 -0
- package/dist/search/search-worker.js +163 -0
- package/dist/search/search-worker.js.map +1 -0
- package/dist/service-installer.js +12 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +23 -0
- package/dist/settings-api.js +164 -3
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +104 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +3 -1
- package/dist/settings-confirmation.js +2 -0
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +34 -3
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +11 -0
- package/dist/settings-http-confirmation.js +10 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +23 -7
- package/dist/setup-form.js.map +1 -1
- package/dist/spawn-gate.d.ts +1 -0
- package/dist/spawn-gate.js +13 -3
- package/dist/spawn-gate.js.map +1 -1
- package/dist/task-board-tool.d.ts +25 -0
- package/dist/task-board-tool.js +154 -0
- package/dist/task-board-tool.js.map +1 -0
- package/dist/task-board-waker.d.ts +44 -0
- package/dist/task-board-waker.js +128 -0
- package/dist/task-board-waker.js.map +1 -0
- package/dist/tmux-exec-helper.d.ts +94 -0
- package/dist/tmux-exec-helper.js +407 -0
- package/dist/tmux-exec-helper.js.map +1 -0
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +42 -11
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +0 -7
- package/dist/tool-permissions.js +44 -5
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +17 -1
- package/dist/topic-commands.js +45 -19
- package/dist/topic-commands.js.map +1 -1
- 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 +63 -1
- package/dist/ui/app-effects.js +90 -32
- package/dist/ui/backend-signin.js +297 -0
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +8 -8
- package/dist/ui/chat-effects.js +110 -15
- package/dist/ui/chat-msg-actions.js +176 -0
- package/dist/ui/chat-render.js +21 -3
- package/dist/ui/chat-store.js +64 -12
- package/dist/ui/chat-strings.js +85 -8
- package/dist/ui/chat-thread.js +204 -21
- package/dist/ui/effect-trial-controller.js +13 -8
- package/dist/ui/effect-trial-panel.js +19 -16
- package/dist/ui/effects-waker.js +70 -0
- package/dist/ui/fleet-canvas-layout.js +117 -0
- package/dist/ui/fleet-canvas.js +555 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-route-scene.js +207 -0
- package/dist/ui/fleet-route-worker.js +21 -0
- package/dist/ui/fleet-route.js +88 -0
- package/dist/ui/fleet-task-card.js +436 -0
- package/dist/ui/fleet-task-common.js +448 -0
- package/dist/ui/fleet-tasks.js +535 -0
- package/dist/ui/fleet-traffic.js +359 -0
- package/dist/ui/fleet-usage.js +435 -0
- package/dist/ui/panel-chat.js +264 -66
- package/dist/ui/panel-details.js +2 -1
- package/dist/ui/panel-fleet.js +65 -83
- package/dist/ui/panel-needs.js +4 -2
- package/dist/ui/panel-settings.js +56 -11
- package/dist/ui/preview.js +3 -2
- package/dist/ui/search-dialog.js +349 -0
- package/dist/ui/settings-appearance.js +44 -19
- package/dist/ui/settings-apply.js +3 -3
- package/dist/ui/settings-catalog.js +92 -0
- package/dist/ui/settings-confirm.js +7 -2
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +4 -3
- 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 +23 -9
- package/dist/ui/settings-owner-effects.js +15 -13
- package/dist/ui/settings-strings.js +122 -42
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/agend-auth.js +1 -1
- package/dist/ui/shared/app-i18n.js +11 -2
- package/dist/ui/shared/app-prefs.js +17 -4
- package/dist/ui/shared/app-route.js +19 -1
- package/dist/ui/shared/app-session.js +2 -0
- package/dist/ui/shared/app-shell.js +174 -30
- package/dist/ui/shared/app-store.js +26 -1
- package/dist/ui/shared/app-stream.js +5 -1
- package/dist/ui/shared/app-themes.js +140 -7
- package/dist/ui/shared/app.css +705 -20
- package/dist/ui/shared/app.js +26 -15
- package/dist/ui/shared/card-strings.js +8 -0
- package/dist/ui/shared/effect-app-runtime.js +68 -19
- package/dist/ui/shared/effect-chat-runtime.js +16 -3
- package/dist/ui/shared/effect-frame.js +26 -7
- package/dist/ui/shared/effect-geometry.js +24 -9
- package/dist/ui/shared/effect-owner-source.js +8 -1
- package/dist/ui/shared/effect-policy.js +21 -7
- package/dist/ui/shared/effect-profiler.js +31 -9
- package/dist/ui/shared/effect-registry.js +97 -50
- package/dist/ui/shared/effect-stock-snapshot.js +9 -0
- package/dist/ui/shared/effect-stock-trust.js +62 -10
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-source.js +1 -1
- package/dist/ui/shared/effect-trial-stage.js +54 -31
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/identity-card.js +100 -11
- package/dist/ui/shared/identity-color.js +14 -10
- package/dist/ui/shared/instance-nav.js +179 -23
- package/dist/ui/shared/owner-css.js +30 -1
- package/dist/ui/shared/panel-view.js +24 -4
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/ui-menu.js +20 -7
- package/dist/ui/shared/ui-toast.js +16 -3
- package/dist/ui/shared/usage-dialog.js +34 -3
- package/dist/ui/shared/view-strings.js +72 -0
- package/dist/ui/shared/web-authorize.js +198 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/ui/web-terminal/terminal.html +1 -1
- package/dist/update-launch.d.ts +9 -0
- package/dist/update-launch.js +38 -12
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/format-rich.js +6 -3
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +2 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/i18n.d.ts +13 -1
- package/dist/usage/i18n.js +29 -0
- package/dist/usage/i18n.js.map +1 -1
- package/dist/usage/providers.d.ts +81 -2
- package/dist/usage/providers.js +240 -38
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.d.ts +5 -1
- package/dist/usage/usage-api.js +184 -58
- package/dist/usage/usage-api.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/user-systemd.d.ts +24 -0
- package/dist/user-systemd.js +75 -0
- package/dist/user-systemd.js.map +1 -0
- package/dist/view-api.d.ts +39 -0
- package/dist/view-api.js +69 -6
- 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 +98 -14
- package/dist/web-api.js +1027 -83
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-policy.d.ts +4 -6
- package/dist/web-appearance-policy.js +5 -12
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +3 -0
- package/dist/web-appearance-tools.js +22 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +27 -2
- package/dist/web-appearance-view.js +129 -14
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +17 -3
- package/dist/web-appearance.js +85 -8
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +2 -2
- package/dist/web-asset-parts.js +3 -3
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +3 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.d.ts +130 -0
- package/dist/web-backend-login.js +225 -0
- package/dist/web-backend-login.js.map +1 -0
- package/dist/web-card.d.ts +14 -0
- package/dist/web-card.js +40 -6
- package/dist/web-card.js.map +1 -1
- 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 +83 -0
- package/dist/web-chat-history.js +137 -5
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +6 -1
- package/dist/web-commands.js +1 -1
- package/dist/web-commands.js.map +1 -1
- 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.js +46 -1
- package/dist/web-effect-bundle.js.map +1 -1
- 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-profiles.d.ts +4 -4
- package/dist/web-effect-service.d.ts +11 -4
- package/dist/web-effect-service.js +25 -4
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.d.ts +1 -0
- package/dist/web-effect-tools.js +4 -4
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.d.ts +2 -2
- package/dist/web-effect-trials.js +3 -3
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.js +1 -1
- package/dist/web-effect-upload.js.map +1 -1
- package/dist/web-effect-worker.d.ts +1 -1
- package/dist/web-effect-worker.js +2 -2
- package/dist/web-effect-worker.js.map +1 -1
- package/dist/web-effect.d.ts +1 -1
- package/dist/web-guest-access.d.ts +126 -0
- package/dist/web-guest-access.js +238 -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-host-guard.d.ts +11 -0
- package/dist/web-host-guard.js +11 -0
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-identity-color.d.ts +1 -1
- package/dist/web-identity-color.js +2 -2
- package/dist/web-identity-color.js.map +1 -1
- package/dist/web-identity-palette.d.ts +1 -1
- package/dist/web-identity-palette.js +2 -2
- package/dist/web-identity-palette.js.map +1 -1
- package/dist/web-image-meta.d.ts +13 -0
- package/dist/web-image-meta.js +62 -0
- package/dist/web-image-meta.js.map +1 -1
- package/dist/web-libavoid.d.ts +14 -0
- package/dist/web-libavoid.js +46 -0
- package/dist/web-libavoid.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 +1 -1
- package/dist/web-mod-agent.js +7 -5
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +9 -1
- package/dist/web-mod-bundle.js +62 -3
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-export.d.ts +9 -0
- package/dist/web-mod-export.js +21 -0
- package/dist/web-mod-export.js.map +1 -1
- package/dist/web-mod-store.d.ts +7 -1
- package/dist/web-mod-store.js +19 -6
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +44 -1
- package/dist/web-owner-css-store.js +70 -9
- package/dist/web-owner-css-store.js.map +1 -1
- package/dist/web-prefs.d.ts +14 -0
- package/dist/web-prefs.js +37 -4
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -2
- package/dist/web-preview.js +4 -2
- 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 +49 -2
- package/dist/web-settings-patch.js +145 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +94 -0
- package/dist/web-settings-tools.js +301 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- 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 +39 -0
- package/dist/web-theme-store.js +122 -11
- package/dist/web-theme-store.js.map +1 -1
- package/dist/web-theme.d.ts +31 -0
- package/dist/web-theme.js +99 -2
- package/dist/web-theme.js.map +1 -1
- 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/windows-shortcuts.d.ts +78 -0
- package/dist/windows-shortcuts.js +203 -0
- package/dist/windows-shortcuts.js.map +1 -0
- package/dist/workflow-templates/default.md +4 -1
- package/dist/yaml-shared-nodes.d.ts +25 -0
- package/dist/yaml-shared-nodes.js +185 -0
- package/dist/yaml-shared-nodes.js.map +1 -0
- package/launcher/agend-install.cjs +553 -0
- package/launcher/install-admission.cjs +4 -39
- package/launcher/postinstall.cjs +14 -12
- package/package.json +4 -2
- package/scripts/preinstall-guard.cjs +2 -5
- package/templates/windows-shortcuts.ps1 +181 -0
- package/launcher/old-updater-note.cjs +0 -48
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "./app-html.js";
|
|
5
5
|
import { t } from "./app-i18n.js";
|
|
6
6
|
import { useLease } from "./app-ctx.js";
|
|
7
|
+
import { guestPage } from "./app-store.js";
|
|
7
8
|
import { readStream } from "./read-stream.js";
|
|
8
9
|
import { Dialog } from "./ui-dialog.js";
|
|
9
10
|
import { ErrorState, Skeleton } from "./ui-states.js";
|
|
@@ -38,6 +39,24 @@ function resetText(iso) {
|
|
|
38
39
|
if (ms <= 0) return tn("usage.reset.soon");
|
|
39
40
|
return usageText("", { key: "usage.reset.in", args: [usageDuration(ms)] });
|
|
40
41
|
}
|
|
42
|
+
/** "14:05" today, "10/19 14:05" another day, in this browser's time: when last known numbers were read, a window reset. */
|
|
43
|
+
export function usageClock(iso, now = Date.now()) {
|
|
44
|
+
const at = new Date(iso || "");
|
|
45
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
46
|
+
const hm = `${String(at.getHours()).padStart(2, "0")}:${String(at.getMinutes()).padStart(2, "0")}`;
|
|
47
|
+
return new Date(now).toDateString() === at.toDateString() ? hm : `${at.getMonth() + 1}/${at.getDate()} ${hm}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A row showing its last known numbers (the live read failed; #2236 follow-up): "已過期 · 快取於 14:05" and, for an expiry
|
|
51
|
+
* or another failure, what to do on a second line (its full text is the mark's tooltip too). A row from an older
|
|
52
|
+
* server (no `cachedAt`) says its hint, as before.
|
|
53
|
+
*/
|
|
54
|
+
export function staleNote(p) {
|
|
55
|
+
if (!p.stale) return null;
|
|
56
|
+
if (!p.cachedAt || !p.staleCause) return { mark: usageText(p.hint || "", p.hintI18n), detail: "" };
|
|
57
|
+
const detail = p.staleCause !== "transient" && (p.hint || p.hintI18n) ? usageText(p.hint || "", p.hintI18n) : "";
|
|
58
|
+
return { mark: usageText("", { key: `usage.stale.${p.staleCause}`, args: [usageClock(p.cachedAt)] }), detail };
|
|
59
|
+
}
|
|
41
60
|
function expiryText(iso) {
|
|
42
61
|
if (!iso) return "";
|
|
43
62
|
const at = new Date(iso), ms = at.getTime() - Date.now();
|
|
@@ -47,6 +66,8 @@ function expiryText(iso) {
|
|
|
47
66
|
export function UsageMetric({ m }) {
|
|
48
67
|
const bar = useRef(null);
|
|
49
68
|
const label = usageText(m.label, m.labelI18n);
|
|
69
|
+
// Its window has reset since these numbers were read: no number and no bar — an old one is never shown as current.
|
|
70
|
+
if (m.resetPassed) return html`<div class="u-metric u-reset-since"><div class="u-row"><span class="u-label">${label}</span><span class="u-val">${usageText("", { key: "usage.metric.reset_since", args: [usageClock(m.resetsAt)] })}</span></div></div>`;
|
|
50
71
|
const note = m.note ? usageText(m.note, m.noteI18n) : "";
|
|
51
72
|
const pct = m.type === "percent" ? Math.min(100, Math.max(0, m.used ?? 0)) : 0;
|
|
52
73
|
useLayoutEffect(() => { if (bar.current) bar.current.style.width = `${pct}%`; }, [pct]); // CSSOM (#1300)
|
|
@@ -69,10 +90,13 @@ export function UsageMetric({ m }) {
|
|
|
69
90
|
/**
|
|
70
91
|
* #2014: a row's title. A credential profile's row (`profile`; its API `name` is "Claude (<profile>)") shows the
|
|
71
92
|
* provider name on the title line beside the plan badge, and the profile on its own full-width muted line under it;
|
|
72
|
-
* the full name is the tooltip, and the dialog shows the profile unclipped.
|
|
93
|
+
* the full name is the tooltip, and the dialog shows the profile unclipped. The default login's row beside profiles
|
|
94
|
+
* (`defaultLogin`; "Claude (default)", #2236) does the same with "default" in the page's language, never a clipped
|
|
95
|
+
* "Claude (d…". Any other row: its name.
|
|
73
96
|
*/
|
|
74
97
|
export function providerTitle(p) {
|
|
75
98
|
const name = String((p && p.name) || ""), profile = p && typeof p.profile === "string" && p.profile ? p.profile : "";
|
|
99
|
+
if (!profile && p && p.defaultLogin === true && name.endsWith(" (default)")) return { base: name.slice(0, -" (default)".length), profile: tn("usage.default_login") };
|
|
76
100
|
const suffix = ` (${profile})`;
|
|
77
101
|
return profile && name.endsWith(suffix) ? { base: name.slice(0, -suffix.length), profile } : { base: name, profile };
|
|
78
102
|
}
|
|
@@ -144,14 +168,14 @@ export function UsageBody({ owner }) {
|
|
|
144
168
|
else {
|
|
145
169
|
const providers = ordered(data.providers);
|
|
146
170
|
content = html`${!providers.length ? html`<p class="note">${tn("usage.empty_active")}</p>` : null}
|
|
147
|
-
${providers.map((p, i) => html`<section key=${key(p)} class
|
|
171
|
+
${providers.map((p, i) => html`<section key=${key(p)} class=${p.stale ? "u-provider is-stale" : "u-provider"}>
|
|
148
172
|
<div class="u-head"><strong title=${p.name}>${providerTitle(p).base}</strong>${p.plan ? html`<span class="pill u-plan" title=${p.plan}><span aria-hidden="true">${planBadge(p.plan)}</span><span class="sr-only">${p.plan}</span></span>` : null}
|
|
149
173
|
<span class="u-order">
|
|
150
174
|
<button type="button" class="icon-btn" disabled=${i === 0} aria-label=${tn("usageMoveUp")} title=${tn("usageMoveUp")} onClick=${() => move(key(p), -1, data.providers)}><${Icon} name="up" size=${14} /></button>
|
|
151
175
|
<button type="button" class="icon-btn" disabled=${i === providers.length - 1} aria-label=${tn("usageMoveDown")} title=${tn("usageMoveDown")} onClick=${() => move(key(p), 1, data.providers)}><${Icon} name="down" size=${14} /></button>
|
|
152
176
|
</span></div>
|
|
153
177
|
${providerTitle(p).profile ? html`<div class="u-profile" title=${p.name}>${providerTitle(p).profile}</div>` : null}
|
|
154
|
-
${p.stale ? html`<p class="note"
|
|
178
|
+
${p.stale ? html`<p class="note u-stale" title=${staleNote(p).detail || null}>${staleNote(p).mark}</p>${staleNote(p).detail ? html`<p class="note u-stale-detail">${staleNote(p).detail}</p>` : null}` : null}
|
|
155
179
|
${p.status === "error" && !p.transient ? html`<p class="u-err">${usageText(p.error || tn("usage.error_fallback"), p.errorI18n)}</p>`
|
|
156
180
|
: p.status === "error" && p.transient ? html`<p class="note">${usageText(p.error || tn("usage.error_fallback"), p.errorI18n)}</p>`
|
|
157
181
|
: p.status === "no-credentials" ? html`<p class="note">${tn("usage.not_logged_in")}</p>${p.hint ? html`<p class="note">${usageText(p.hint, p.hintI18n)}</p>` : null}`
|
|
@@ -159,11 +183,18 @@ export function UsageBody({ owner }) {
|
|
|
159
183
|
${!p.stale && p.hint ? html`<p class="note">${usageText(p.hint, p.hintI18n)}</p>` : !(p.metrics || []).length ? html`<p class="note">${tn("usage.no_data")}</p>` : null}`}
|
|
160
184
|
</section>`)}
|
|
161
185
|
<div class="u-foot"><span class="note" title=${new Date(data.fetchedAt).toLocaleString()}>${updatedAgo(data.fetchedAt)}${data.refreshing ? html` · <span class="u-refreshing">${tn("usageRefreshing")}</span>` : null}</span>
|
|
186
|
+
${byAgent() ? html`<a class="btn btn-ghost btn-sm u-by-agent" href="/ui/fleet/usage" title=${tn("usageByAgentTitle")}>${tn("usageByAgent")}</a>` : null}
|
|
162
187
|
<button type="button" class="btn btn-sm" onClick=${() => load(true)}>${tn("usageRefresh")}</button></div>`;
|
|
163
188
|
}
|
|
164
189
|
return content;
|
|
165
190
|
}
|
|
166
191
|
|
|
192
|
+
/**
|
|
193
|
+
* #2122: the plans above are per login; each agent's own tokens and API-equivalent are Fleet → Usage — linked from the
|
|
194
|
+
* app (not View's anonymous reader, not a guest, who has no Fleet).
|
|
195
|
+
*/
|
|
196
|
+
const byAgent = () => !guestPage() && typeof location !== "undefined" && location.pathname.startsWith("/ui");
|
|
197
|
+
|
|
167
198
|
/** `owner`: the navigation that opened it — its reads and the minute's timer live and end with it. */
|
|
168
199
|
export function UsageDialog({ onClose, owner = "" }) {
|
|
169
200
|
return html`<${Dialog} title=${tn("usage.title")} onClose=${onClose} wide=${true}><${UsageBody} owner=${`usage-dialog:${owner}`} /></${Dialog}>`;
|
|
@@ -40,6 +40,8 @@ register("view", {
|
|
|
40
40
|
"usageMinutes": "{n} min",
|
|
41
41
|
"usageHours": "{n} h",
|
|
42
42
|
"usageRefresh": "Refresh",
|
|
43
|
+
"usageByAgent": "By agent →",
|
|
44
|
+
"usageByAgentTitle": "Each agent's tokens and API-equivalent (Fleet → Usage)",
|
|
43
45
|
"usageHigh": "high",
|
|
44
46
|
"usageNear": "near limit",
|
|
45
47
|
"usageMoveUp": "Move up",
|
|
@@ -51,6 +53,16 @@ register("view", {
|
|
|
51
53
|
"tooltipEffort": "Effort: {value}",
|
|
52
54
|
"tooltipCost": "Cost: {value}",
|
|
53
55
|
"tooltipUnavailable": "Unavailable",
|
|
56
|
+
"srcConfigured": " (configured)",
|
|
57
|
+
"srcFleetDefault": " (fleet default)",
|
|
58
|
+
"modelDefault": "default",
|
|
59
|
+
"modelDefaultWhy": "default ({reason})",
|
|
60
|
+
"modelCliDefault": "{model} (default)",
|
|
61
|
+
"modelWhyStart": "detected when it starts",
|
|
62
|
+
"modelWhyCli": "this CLI does not report a default",
|
|
63
|
+
"groupOther": "Other",
|
|
64
|
+
"groupTeam": "Team",
|
|
65
|
+
"groupTag": "Tag",
|
|
54
66
|
"statusRunning": "Running",
|
|
55
67
|
"statusPaused": "Paused",
|
|
56
68
|
"statusStopped": "Stopped",
|
|
@@ -63,6 +75,7 @@ register("view", {
|
|
|
63
75
|
"usage.title_plain": "AI subscription usage",
|
|
64
76
|
"usage.empty_active": "No active backends with usage tracking",
|
|
65
77
|
"usage.not_logged_in": "not logged in",
|
|
78
|
+
"usage.default_login": "default",
|
|
66
79
|
"usage.error_fallback": "error",
|
|
67
80
|
"usage.no_data": "no data",
|
|
68
81
|
"usage.reset.in": "resets in {0}",
|
|
@@ -125,6 +138,10 @@ register("view", {
|
|
|
125
138
|
"usage.error.rate_limited": "Rate limited by {0} — try again later.",
|
|
126
139
|
"usage.error.rate_limited_transient": "{0}: can't fetch usage right now (rate limited). This does not affect your agents.",
|
|
127
140
|
"usage.stale_rate_limited": "data from {0} min ago — can't refresh right now",
|
|
141
|
+
"usage.stale.expired": "expired · cached at {0}",
|
|
142
|
+
"usage.stale.transient": "not updated · cached at {0}",
|
|
143
|
+
"usage.stale.error": "couldn't read · cached at {0}",
|
|
144
|
+
"usage.metric.reset_since": "reset since {0}",
|
|
128
145
|
"usage.error.http": "Usage request failed (HTTP {0}).",
|
|
129
146
|
"usage.error.billing_http": "Billing request failed (HTTP {0}).",
|
|
130
147
|
"usage.error.invalid_response": "Invalid response from usage endpoint.",
|
|
@@ -162,6 +179,9 @@ register("view", {
|
|
|
162
179
|
"cardBio": "Bio (up to 280 characters, 3 line breaks)",
|
|
163
180
|
"cardPortrait": "Portrait",
|
|
164
181
|
"cardBanner": "Banner",
|
|
182
|
+
"cardBannerFocus": "Banner focus",
|
|
183
|
+
"cardBannerFocusNote": "What stays in view when the banner is cropped: the dashed area shows on every screen and card size; the yellow one is a phone's medium card.",
|
|
184
|
+
"cardFocusFollow": "Follow the agent's",
|
|
165
185
|
"cardRemove": "Remove yours",
|
|
166
186
|
"cardKeep": "Keep yours",
|
|
167
187
|
"clearCard": "Clear card",
|
|
@@ -198,6 +218,22 @@ register("view", {
|
|
|
198
218
|
"favDown": "Move {name} down",
|
|
199
219
|
"favFull": "Favorites are full ({max})",
|
|
200
220
|
"favFiltered": "Clear the filter to reorder favorites",
|
|
221
|
+
"favSorted": "Favorites are sorted by last reply — set them back to your order to move them",
|
|
222
|
+
"replyJustNow": "just now",
|
|
223
|
+
"replyMinutes": "{n} min ago",
|
|
224
|
+
"replyHours": "{n} h ago",
|
|
225
|
+
"replyYesterday": "yesterday",
|
|
226
|
+
"replyDays": "{n} d ago",
|
|
227
|
+
"replyTitle": "Last replied: {time}",
|
|
228
|
+
"sortTitle": "Sort the list",
|
|
229
|
+
"sortCustom": "Custom",
|
|
230
|
+
"sortCustomHint": "Your own order (drag rows); replies never move them",
|
|
231
|
+
"sortRecent": "Recent replies",
|
|
232
|
+
"sortRecentHint": "No groups: the last reply first",
|
|
233
|
+
"sortFavs": "Favorites, in Recent replies",
|
|
234
|
+
"sortFavsFixed": "Pinned on top, in your order",
|
|
235
|
+
"sortFavsRecent": "Sorted by last reply too",
|
|
236
|
+
"recentOthers": "Recent replies first",
|
|
201
237
|
"pinnedCurrent": "Current",
|
|
202
238
|
"pinnedLabel": "The instance you have open"
|
|
203
239
|
},
|
|
@@ -237,6 +273,8 @@ register("view", {
|
|
|
237
273
|
"usageMinutes": "{n} 分鐘",
|
|
238
274
|
"usageHours": "{n} 小時",
|
|
239
275
|
"usageRefresh": "重新整理",
|
|
276
|
+
"usageByAgent": "依 agent →",
|
|
277
|
+
"usageByAgentTitle": "每個 agent 的 token 與 API 等值(Fleet → 用量)",
|
|
240
278
|
"usageHigh": "偏高",
|
|
241
279
|
"usageNear": "接近上限",
|
|
242
280
|
"usageMoveUp": "上移",
|
|
@@ -248,6 +286,16 @@ register("view", {
|
|
|
248
286
|
"tooltipEffort": "推理強度:{value}",
|
|
249
287
|
"tooltipCost": "費用:{value}",
|
|
250
288
|
"tooltipUnavailable": "無資料",
|
|
289
|
+
"srcConfigured": "(設定值)",
|
|
290
|
+
"srcFleetDefault": "(fleet 預設)",
|
|
291
|
+
"modelDefault": "預設",
|
|
292
|
+
"modelDefaultWhy": "預設({reason})",
|
|
293
|
+
"modelCliDefault": "{model}(預設)",
|
|
294
|
+
"modelWhyStart": "啟動時偵測",
|
|
295
|
+
"modelWhyCli": "這個 CLI 不提供預設值",
|
|
296
|
+
"groupOther": "其他",
|
|
297
|
+
"groupTeam": "團隊",
|
|
298
|
+
"groupTag": "標籤",
|
|
251
299
|
"statusRunning": "運行中",
|
|
252
300
|
"statusPaused": "已暫停",
|
|
253
301
|
"statusStopped": "已停止",
|
|
@@ -260,6 +308,7 @@ register("view", {
|
|
|
260
308
|
"usage.title_plain": "AI 訂閱用量",
|
|
261
309
|
"usage.empty_active": "沒有使用中且支援用量追蹤的 Backend",
|
|
262
310
|
"usage.not_logged_in": "未登入",
|
|
311
|
+
"usage.default_login": "預設",
|
|
263
312
|
"usage.error_fallback": "錯誤",
|
|
264
313
|
"usage.no_data": "無資料",
|
|
265
314
|
"usage.reset.in": "{0} 後重置",
|
|
@@ -322,6 +371,10 @@ register("view", {
|
|
|
322
371
|
"usage.error.rate_limited": "{0} 暫時限制請求速率,請稍後再試。",
|
|
323
372
|
"usage.error.rate_limited_transient": "暫時查不到 {0} 用量({0} 限制查詢頻率),不影響你的 agent。",
|
|
324
373
|
"usage.stale_rate_limited": "{0} 分鐘前的資料,暫時無法更新",
|
|
374
|
+
"usage.stale.expired": "已過期 · 快取於 {0}",
|
|
375
|
+
"usage.stale.transient": "暫時無法更新 · 快取於 {0}",
|
|
376
|
+
"usage.stale.error": "讀取失敗 · 快取於 {0}",
|
|
377
|
+
"usage.metric.reset_since": "已於 {0} 重置",
|
|
325
378
|
"usage.error.http": "用量請求失敗(HTTP {0})。",
|
|
326
379
|
"usage.error.billing_http": "計費請求失敗(HTTP {0})。",
|
|
327
380
|
"usage.error.invalid_response": "用量端點回傳了無效資料。",
|
|
@@ -359,6 +412,9 @@ register("view", {
|
|
|
359
412
|
"cardBio": "簡介(最多 280 個字、3 次換行)",
|
|
360
413
|
"cardPortrait": "肖像",
|
|
361
414
|
"cardBanner": "橫幅",
|
|
415
|
+
"cardBannerFocus": "橫幅焦點",
|
|
416
|
+
"cardBannerFocusNote": "橫幅被裁切時保留在畫面中的部分:虛線框在所有螢幕與卡片大小都看得到;黃色框是手機上中卡片的範圍。",
|
|
417
|
+
"cardFocusFollow": "跟隨 agent 的設定",
|
|
362
418
|
"cardRemove": "移除你的",
|
|
363
419
|
"cardKeep": "保留你的",
|
|
364
420
|
"clearCard": "清除卡片",
|
|
@@ -395,6 +451,22 @@ register("view", {
|
|
|
395
451
|
"favDown": "把 {name} 下移",
|
|
396
452
|
"favFull": "最愛已滿({max} 個)",
|
|
397
453
|
"favFiltered": "清除篩選後才能調整最愛順序",
|
|
454
|
+
"favSorted": "最愛目前依最近回覆排序;改回「照你的順序」才能移動",
|
|
455
|
+
"replyJustNow": "剛剛",
|
|
456
|
+
"replyMinutes": "{n} 分鐘前",
|
|
457
|
+
"replyHours": "{n} 小時前",
|
|
458
|
+
"replyYesterday": "昨天",
|
|
459
|
+
"replyDays": "{n} 天前",
|
|
460
|
+
"replyTitle": "最後回覆:{time}",
|
|
461
|
+
"sortTitle": "排序方式",
|
|
462
|
+
"sortCustom": "自訂",
|
|
463
|
+
"sortCustomHint": "你拖曳排好的順序,有回覆也不會跳動",
|
|
464
|
+
"sortRecent": "最近回覆",
|
|
465
|
+
"sortRecentHint": "不分組,最後回覆的在上面",
|
|
466
|
+
"sortFavs": "最愛(最近回覆模式時)",
|
|
467
|
+
"sortFavsFixed": "固定在最上面,照你的順序",
|
|
468
|
+
"sortFavsRecent": "也依最近回覆排序",
|
|
469
|
+
"recentOthers": "最近回覆在上",
|
|
398
470
|
"pinnedCurrent": "目前",
|
|
399
471
|
"pinnedLabel": "你目前開著的 instance"
|
|
400
472
|
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// #1939 S2b (design §0.1): "Authorize this browser". The app shows this browser's state — verified (until when), a
|
|
2
|
+
// Confirm card waiting in the account's Discord or Telegram DM (with its countdown), or why it can't be authorized here —
|
|
3
|
+
// and asks for the card. The card itself is the gate: nothing here can authorize anything.
|
|
4
|
+
import { html, useEffect, useRef, useState } from "./app-html.js";
|
|
5
|
+
import { register, t } from "./app-i18n.js";
|
|
6
|
+
import { guestPage } from "./app-store.js";
|
|
7
|
+
|
|
8
|
+
register("webauth", {
|
|
9
|
+
en: {
|
|
10
|
+
verified: "Verified · speaks as you until {0}",
|
|
11
|
+
notVerified: "Not verified",
|
|
12
|
+
authorize: "Authorize this browser",
|
|
13
|
+
waiting: "Waiting for your Confirm in Discord or Telegram ({0})",
|
|
14
|
+
sent: "Sent — confirm it in your DM within 5 minutes.",
|
|
15
|
+
publicLink: "Not on the temporary public link: open the dashboard directly to authorize.",
|
|
16
|
+
reason_disabled: "Web authority is turned off.",
|
|
17
|
+
reason_loopback_only: "Only this computer can authorize, and this browser is not on it.",
|
|
18
|
+
reason_no_account: "This browser was not signed in as an account (agend web): sign in with /web to authorize.",
|
|
19
|
+
reason_not_admin: "Your account is not a fleet admin here.",
|
|
20
|
+
reason_not_admin_session: "Guests can't authorize.",
|
|
21
|
+
reason_token_rotated: "The dashboard's token changed: sign in again.",
|
|
22
|
+
reason_public_link: "Not on the temporary public link.",
|
|
23
|
+
reason_not_session: "Sign in to authorize.",
|
|
24
|
+
reason_other: "This browser can't be authorized here.",
|
|
25
|
+
refusal_pending: "A request is already waiting in your DM.",
|
|
26
|
+
refusal_rate_limited: "Three requests this hour — try again in {0} min.",
|
|
27
|
+
refusal_dm_unavailable: "Couldn't send you a DM. Open your DMs with the bot, then try again.",
|
|
28
|
+
refusal_other: "That didn't work: {0}",
|
|
29
|
+
settings: "Settings",
|
|
30
|
+
verifiedShort: "Verified",
|
|
31
|
+
authorizeShort: "Authorize",
|
|
32
|
+
authorizeWhy: "Not verified: authorize this browser to speak as you",
|
|
33
|
+
waitingShort: "Waiting for your Confirm ({0})",
|
|
34
|
+
},
|
|
35
|
+
"zh-TW": {
|
|
36
|
+
verified: "已驗證 · 以你的身分發言到 {0}",
|
|
37
|
+
notVerified: "未驗證",
|
|
38
|
+
authorize: "授權這個瀏覽器",
|
|
39
|
+
waiting: "等你在 Discord 或 Telegram 按下確認({0})",
|
|
40
|
+
sent: "已送出——請在 5 分鐘內到私訊確認。",
|
|
41
|
+
publicLink: "臨時公開連結上無法授權:請直接開啟儀表板再授權。",
|
|
42
|
+
reason_disabled: "網頁授權已關閉。",
|
|
43
|
+
reason_loopback_only: "只有這台電腦可以授權,而這個瀏覽器不在這台電腦上。",
|
|
44
|
+
reason_no_account: "這個瀏覽器不是用帳號登入的(agend web):請用 /web 登入後再授權。",
|
|
45
|
+
reason_not_admin: "你的帳號在這裡不是 fleet 管理者。",
|
|
46
|
+
reason_not_admin_session: "訪客無法授權。",
|
|
47
|
+
reason_token_rotated: "儀表板的 token 已變更:請重新登入。",
|
|
48
|
+
reason_public_link: "臨時公開連結上無法授權。",
|
|
49
|
+
reason_not_session: "登入後才能授權。",
|
|
50
|
+
reason_other: "這個瀏覽器在這裡無法授權。",
|
|
51
|
+
refusal_pending: "已經有一個請求在你的私訊中等待。",
|
|
52
|
+
refusal_rate_limited: "這一小時已請求三次——請 {0} 分鐘後再試。",
|
|
53
|
+
refusal_dm_unavailable: "無法私訊給你。請開放與 bot 的私訊後再試一次。",
|
|
54
|
+
refusal_other: "沒有成功:{0}",
|
|
55
|
+
settings: "設定",
|
|
56
|
+
verifiedShort: "已驗證",
|
|
57
|
+
authorizeShort: "授權",
|
|
58
|
+
authorizeWhy: "未驗證:授權這個瀏覽器以你的身分發言",
|
|
59
|
+
waitingShort: "等待確認({0})",
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const tn = (k, ...v) => t(`webauth.${k}`, ...v);
|
|
63
|
+
const REASONS = ["disabled", "loopback_only", "no_account", "not_admin", "not_admin_session", "token_rotated", "public_link", "not_session"];
|
|
64
|
+
// Until when: the time, with the day when it isn't today (an authorization lasts up to 48 h).
|
|
65
|
+
const clock = (ms) => {
|
|
66
|
+
const d = new Date(ms), today = new Date().toDateString() === d.toDateString();
|
|
67
|
+
return today ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
|
68
|
+
: d.toLocaleString([], { weekday: "short", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
|
|
69
|
+
};
|
|
70
|
+
const mmss = (ms) => { const s = Math.max(0, Math.ceil(ms / 1000)); return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`; };
|
|
71
|
+
const publicLink = () => globalThis.document?.body?.dataset?.publicLink === "1";
|
|
72
|
+
|
|
73
|
+
// One read per page load, shared by every line that shows it (the composer's, the session menu's): coming back to a chat
|
|
74
|
+
// reads nothing (#1374). It is read again only after an ask, and while a card is pending.
|
|
75
|
+
// An answer of any kind counts as this page's read (one that isn't a state hides the line); only a network failure lets a
|
|
76
|
+
// later mount try again. Signed out (401: the session ended, e.g. Not me), the line goes.
|
|
77
|
+
let shared = null, answered = false, inflight = null, signedOut = false;
|
|
78
|
+
async function readStatus() {
|
|
79
|
+
inflight ??= (async () => {
|
|
80
|
+
try {
|
|
81
|
+
const r = await fetch("/ui/authorization", { credentials: "same-origin" });
|
|
82
|
+
answered = true;
|
|
83
|
+
if (r.status === 401) { signedOut = true; shared = null; }
|
|
84
|
+
const body = r.ok ? await r.json().catch(() => null) : null;
|
|
85
|
+
if (body && typeof body.state === "string") shared = body;
|
|
86
|
+
} catch { /* the line stays as it was; a later mount may try again */ }
|
|
87
|
+
finally { inflight = null; }
|
|
88
|
+
return shared;
|
|
89
|
+
})();
|
|
90
|
+
return inflight;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** This browser's authorization, kept fresh while a card is pending (every 3 s, until it is decided or expires). */
|
|
94
|
+
export function useWebAuthorization() {
|
|
95
|
+
const [st, setSt] = useState(shared), [notice, setNotice] = useState(null);
|
|
96
|
+
const [deadline, setDeadline] = useState(0), [now, setNow] = useState(() => Date.now());
|
|
97
|
+
const alive = useRef(true);
|
|
98
|
+
const load = async () => {
|
|
99
|
+
if (publicLink() || guestPage()) return;
|
|
100
|
+
const body = await readStatus();
|
|
101
|
+
if (!alive.current) return;
|
|
102
|
+
if (signedOut) { setSt(null); setNotice(null); return; }
|
|
103
|
+
if (!body) return;
|
|
104
|
+
setSt(body);
|
|
105
|
+
if (body.state !== "pending") setNotice((n) => (n === tn("sent") ? null : n)); // "Sent —" was about the card
|
|
106
|
+
if (body.state === "pending" && typeof body.expiresInMs === "number") setDeadline(Date.now() + body.expiresInMs);
|
|
107
|
+
};
|
|
108
|
+
useEffect(() => { alive.current = true; if (!answered) load(); return () => { alive.current = false; }; }, []);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!st || st.state !== "pending") return undefined;
|
|
111
|
+
const tick = setInterval(() => setNow(Date.now()), 1000), poll = setInterval(load, 3000);
|
|
112
|
+
return () => { clearInterval(tick); clearInterval(poll); };
|
|
113
|
+
}, [st && st.state]);
|
|
114
|
+
// "Verified · until X" is read again at X (a page left open must not keep saying verified after it ends).
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!st || st.state !== "verified" || typeof st.until !== "number") return undefined;
|
|
117
|
+
const at = setTimeout(() => { resetWebAuthorization(); load(); }, Math.max(0, Math.min(st.until - Date.now() + 1000, 2 ** 31 - 1)));
|
|
118
|
+
return () => clearTimeout(at);
|
|
119
|
+
}, [st && st.state, st && st.until]);
|
|
120
|
+
const ask = async () => {
|
|
121
|
+
setNotice(null);
|
|
122
|
+
let r = null, body = null;
|
|
123
|
+
try { r = await fetch("/ui/authorization", { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: "{}" }); body = await r.json(); } catch { body = null; }
|
|
124
|
+
if (r && r.ok && body && body.ok) { setNotice(tn("sent")); setSt(shared = { state: "pending", expiresInMs: body.expiresInMs }); setDeadline(Date.now() + body.expiresInMs); return; }
|
|
125
|
+
const refusal = body && body.refusal;
|
|
126
|
+
if (refusal === "rate_limited") setNotice(tn("refusal_rate_limited", Math.max(1, Math.ceil((body.retryAfterMs || 0) / 60_000))));
|
|
127
|
+
else if (refusal === "pending" || refusal === "dm_unavailable") setNotice(tn(`refusal_${refusal}`));
|
|
128
|
+
else if (REASONS.includes(refusal)) setNotice(tn(`reason_${refusal}`));
|
|
129
|
+
else setNotice(tn("refusal_other", String(refusal || (r && r.status) || "?")));
|
|
130
|
+
load();
|
|
131
|
+
};
|
|
132
|
+
return { st, notice, ask, left: deadline - now };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Forget the page's one read, so the next line that mounts reads again: Settings → Web calls it when it shows (a revoke
|
|
137
|
+
* or a changed switch there changes what the line should say). Tests use it too: each case is its own page load.
|
|
138
|
+
*/
|
|
139
|
+
export function resetWebAuthorization() { shared = null; answered = false; inflight = null; signedOut = false; }
|
|
140
|
+
|
|
141
|
+
/** The line under the composer: verified, waiting, or "Not verified" with the button (or why not, with the fix). */
|
|
142
|
+
export function AuthorizeChip() {
|
|
143
|
+
const a = useWebAuthorization();
|
|
144
|
+
if (guestPage()) return null;
|
|
145
|
+
if (publicLink()) return html`<p class="webauth-chip note" data-state="public">${tn("publicLink")}</p>`;
|
|
146
|
+
if (!a.st) return null;
|
|
147
|
+
return html`<div class="webauth-chip" data-state=${a.st.state}>${body(a)}${a.notice ? html` <span class="note" role="status">${a.notice}</span>` : null}</div>`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The composer's row (#2215, the user's request, 2026-10-19): the same states as AuthorizeChip, each kept to a few words so
|
|
152
|
+
* they share one row with the key hint. What a state would have said at length is in its tooltip and its accessible
|
|
153
|
+
* name: when "Verified" ends, why it can't be authorized, the countdown's ask. A tap on "✓ Verified" shows when it
|
|
154
|
+
* ends (touch has no hover) in a bubble above the row, until the next tap, a tap elsewhere or Escape. What pressing
|
|
155
|
+
* Authorize answered (sent, refused) is said in the row itself, in the hint's place (#2216 review: a bubble left over the
|
|
156
|
+
* composer covered Attach and what was being typed, with nothing to close it).
|
|
157
|
+
*/
|
|
158
|
+
export function ComposerAuth() {
|
|
159
|
+
const a = useWebAuthorization();
|
|
160
|
+
const [open, setOpen] = useState(false);
|
|
161
|
+
const tip = useRef("wa-tip-" + Math.random().toString(36).slice(2, 8));
|
|
162
|
+
useEffect(() => { setOpen(false); }, [a.st && a.st.state]); // a new state closes the bubble
|
|
163
|
+
if (guestPage()) return null;
|
|
164
|
+
if (publicLink()) return html`<span class="webauth-row note" data-state="public" title=${tn("publicLink")}>${tn("publicLink")}</span>`;
|
|
165
|
+
if (!a.st) return null;
|
|
166
|
+
const st = a.st, id = tip.current;
|
|
167
|
+
const said = a.notice ? html`<span class="webauth-notice" role="status">${a.notice}</span>` : null;
|
|
168
|
+
if (st.state === "verified") {
|
|
169
|
+
const until = tn("verified", clock(st.until));
|
|
170
|
+
return html`<span class="webauth-row" data-state="verified">
|
|
171
|
+
<button type="button" class="webauth-ok" title=${until} aria-label=${`✓ ${until}`} aria-expanded=${open ? "true" : "false"}
|
|
172
|
+
aria-controls=${id} onClick=${() => setOpen((v) => !v)} onBlur=${() => setOpen(false)}
|
|
173
|
+
onKeyDown=${(e) => { if (e.key === "Escape" && open) { e.stopPropagation(); setOpen(false); } }}>✓ ${tn("verifiedShort")}</button><span
|
|
174
|
+
id=${id} class="webauth-tip" role="tooltip" hidden=${!open}>${until}</span>${said}</span>`;
|
|
175
|
+
}
|
|
176
|
+
if (st.state === "pending") {
|
|
177
|
+
const asks = tn("waiting", mmss(a.left));
|
|
178
|
+
return html`<span class="webauth-row" data-state="pending"><span class="note" title=${a.notice ? `${asks} — ${a.notice}` : asks}>${tn("waitingShort", mmss(a.left))}</span>${said}</span>`;
|
|
179
|
+
}
|
|
180
|
+
if (st.reason === "not_authorized" || st.reason === "authorization_expired") {
|
|
181
|
+
return html`<span class="webauth-row" data-state="none"><span class="note" aria-hidden="true">${tn("notVerified")} ·</span><button type="button" class="webauth-ask"
|
|
182
|
+
title=${tn("authorizeWhy")} aria-label=${tn("authorizeWhy")} onClick=${a.ask}>${tn("authorizeShort")}</button>${said}</span>`;
|
|
183
|
+
}
|
|
184
|
+
const why = REASONS.includes(st.reason) ? tn(`reason_${st.reason}`) : tn("reason_other");
|
|
185
|
+
return html`<span class="webauth-row" data-state="none"><span class="note" title=${why}>${tn("notVerified")}<span class="sr-only">: ${why}</span></span>${st.reason === "disabled" || st.reason === "loopback_only"
|
|
186
|
+
? html` · <a href="/settings/web" title=${why}>${tn("settings")}</a>` : null}</span>`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function body(a) {
|
|
190
|
+
const st = a.st;
|
|
191
|
+
if (st.state === "verified") return html`<span class="webauth-ok">✓ ${tn("verified", clock(st.until))}</span>`;
|
|
192
|
+
if (st.state === "pending") return html`<span class="note">${tn("waiting", mmss(a.left))}</span>`;
|
|
193
|
+
if (st.reason === "not_authorized" || st.reason === "authorization_expired")
|
|
194
|
+
return html`<span class="note">${tn("notVerified")} ·</span> <button type="button" class="btn btn-sm" onClick=${a.ask}>${tn("authorize")}</button>`;
|
|
195
|
+
const why = REASONS.includes(st.reason) ? tn(`reason_${st.reason}`) : tn("reason_other");
|
|
196
|
+
// The fix, where there is one the person can take here: the switch lives in Settings → Web.
|
|
197
|
+
return html`<span class="note">${tn("notVerified")}: ${why}</span>${st.reason === "disabled" || st.reason === "loopback_only" ? html` <a href="/settings/web">${tn("settings")}</a>` : null}`;
|
|
198
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
html, body { margin: 0; height: 100%; background: #111; color: #ddd; font: 14px system-ui, sans-serif; }
|
|
1
|
+
html, body { margin: 0; height: 100%; background: #111; color: #ddd; font: 14px system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji; }
|
|
2
2
|
body { display: flex; flex-direction: column; }
|
|
3
3
|
#bar { display: flex; align-items: center; gap: 12px; padding: 6px 10px; background: #1c1c1c; border-bottom: 1px solid #333; }
|
|
4
4
|
#bar #title { font-weight: 600; }
|
|
@@ -6,18 +6,18 @@ body { display: flex; flex-direction: column; }
|
|
|
6
6
|
#bar button { background: #402; color: #fbb; border: 1px solid #733; border-radius: 4px; padding: 3px 10px; cursor: pointer; }
|
|
7
7
|
#gate { max-width: 560px; margin: 40px auto; padding: 0 16px; line-height: 1.5; }
|
|
8
8
|
#gate form { display: flex; gap: 8px; margin-top: 12px; }
|
|
9
|
-
#gate input { flex: 1; font: 16px ui-monospace, monospace; letter-spacing: 1px; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
|
|
9
|
+
#gate input { flex: 1; font: 16px ui-monospace, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji; letter-spacing: 1px; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
|
|
10
10
|
#gate button { padding: 8px 16px; background: #246; color: #fff; border: 0; border-radius: 4px; cursor: pointer; }
|
|
11
11
|
#gate-msg { color: #f88; min-height: 1.5em; }
|
|
12
12
|
#term { flex: 1; min-height: 0; padding: 4px; overflow: hidden; } /* never drawn over the rows below it (#1242) */
|
|
13
13
|
#code-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 10px; background: #1c1c1c; border-top: 1px solid #333; }
|
|
14
14
|
#code-row[hidden] { display: none; }
|
|
15
|
-
#code-row input { flex: 1; min-width: 12em; font: 16px ui-monospace, monospace; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
|
|
15
|
+
#code-row input { flex: 1; min-width: 12em; font: 16px ui-monospace, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
|
|
16
16
|
#code-row button { padding: 8px 16px; background: #246; color: #fff; border: 0; border-radius: 4px; cursor: pointer; }
|
|
17
17
|
#code-msg { color: #9ad; min-height: 1.2em; }
|
|
18
18
|
#code-hint { flex-basis: 100%; margin: 0; color: #aaa; font-size: 13px; }
|
|
19
19
|
#keys { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px; background: #1c1c1c; border-top: 1px solid #333; }
|
|
20
|
-
#keys button { background: #222; color: #ddd; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; font: 14px ui-monospace, monospace; cursor: pointer; }
|
|
20
|
+
#keys button { background: #222; color: #ddd; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; font: 14px ui-monospace, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji; cursor: pointer; }
|
|
21
21
|
#done { padding: 10px; background: #1c1c1c; border-top: 1px solid #333; }
|
|
22
22
|
#done.ok { color: #8d8; } #done.bad { color: #f88; }
|
|
23
23
|
@media (min-width: 900px) { #keys { display: none; } }
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
</header>
|
|
18
18
|
<section id="gate">
|
|
19
19
|
<p>This terminal is connected to <strong>one command only</strong> and closes when it exits or when the time limit lapses. There is no shell behind it.</p>
|
|
20
|
-
<p>Paste the <strong>access token</strong> you
|
|
20
|
+
<p>Paste the <strong>access token</strong> you were given: privately in chat, or in the dashboard's sign-in dialog. It is separate from this link; the link alone grants nothing.</p>
|
|
21
21
|
<form id="gate-form" autocomplete="off">
|
|
22
22
|
<input id="token" type="text" inputmode="text" autocapitalize="characters" spellcheck="false" placeholder="access token" maxlength="40" required>
|
|
23
23
|
<button type="submit">Open</button>
|
package/dist/update-launch.d.ts
CHANGED
|
@@ -43,11 +43,20 @@ export declare function openUpdateLog(logPath: string, header: string): {
|
|
|
43
43
|
} | null;
|
|
44
44
|
export interface UpdateLaunchDeps {
|
|
45
45
|
platform: string;
|
|
46
|
+
/** This process's uid: a system manager's scope is root's to ask for (#2274). */
|
|
47
|
+
uid?(): number;
|
|
46
48
|
cgroup(): Promise<string>;
|
|
47
49
|
version(): Promise<string | null>;
|
|
48
50
|
nonce(): string;
|
|
49
51
|
}
|
|
50
52
|
export declare const defaultUpdateLaunchDeps: UpdateLaunchDeps;
|
|
53
|
+
/**
|
|
54
|
+
* Which systemd manager runs the service the fleet is in: "user" (a unit below user@UID.service — com.agend.fleet),
|
|
55
|
+
* "system" (a unit of the system manager — the legacy /etc/systemd/system/agend.service, #2274), false (no service: a
|
|
56
|
+
* detached fleet), null (unknown: unreadable, or rows that disagree). Unknown is not proof that a detached child can
|
|
57
|
+
* escape a service's KillMode.
|
|
58
|
+
*/
|
|
59
|
+
export declare function serviceManagerOf(text: string): "user" | "system" | false | null;
|
|
51
60
|
/** Unknown is not proof that a detached child can escape a service's KillMode. */
|
|
52
61
|
export declare function inServiceCgroup(text: string): boolean | null;
|
|
53
62
|
export type UpdateLaunch = {
|
package/dist/update-launch.js
CHANGED
|
@@ -69,14 +69,16 @@ function updateTarget(output) {
|
|
|
69
69
|
*/
|
|
70
70
|
export function updateFixHint(output, opts = {}) {
|
|
71
71
|
const target = updateTarget(output);
|
|
72
|
+
// #2264: one repair for an install left half-done — the installer, which puts back or removes what npm left aside.
|
|
73
|
+
const installer = `\`curl -fsSL https://songsid.github.io/AgEnD/install.sh | ${target === "<version>" ? "" : `AGEND_VERSION=${target} `}bash\``;
|
|
72
74
|
if (opts.agendGone)
|
|
73
|
-
return
|
|
75
|
+
return installer;
|
|
74
76
|
if (/^npm (?:ERR!|error) code ENOTEMPTY\b/im.test(output)) {
|
|
75
77
|
const named = /^npm (?:ERR!|error) dest (.+)$/im.exec(output)?.[1] ?? /ENOTEMPTY[^\n]*rename '[^'\n]+' -> '([^'\n]+)'/.exec(output)?.[1];
|
|
76
78
|
const ours = named && isAbsolute(named) && !/['"\s\\`$]/.test(named)
|
|
77
79
|
&& basename(dirname(named)) === "@songsid" && /^\.agend[A-Za-z0-9._-]*$/.test(basename(named));
|
|
78
80
|
if (ours)
|
|
79
|
-
return t("update.failure.remove_staging",
|
|
81
|
+
return t("update.failure.remove_staging", installer);
|
|
80
82
|
return null;
|
|
81
83
|
}
|
|
82
84
|
if (/systemd does not know /.test(output))
|
|
@@ -144,6 +146,7 @@ function readRunTail(logPath, offset) {
|
|
|
144
146
|
}
|
|
145
147
|
export const defaultUpdateLaunchDeps = {
|
|
146
148
|
platform: process.platform,
|
|
149
|
+
uid: () => (typeof process.getuid === "function" ? process.getuid() : -1),
|
|
147
150
|
cgroup: async () => {
|
|
148
151
|
const started = performance.now();
|
|
149
152
|
const result = await readFile("/proc/self/cgroup", { encoding: "utf8", signal: AbortSignal.timeout(2000) });
|
|
@@ -165,20 +168,38 @@ export const defaultUpdateLaunchDeps = {
|
|
|
165
168
|
},
|
|
166
169
|
nonce: () => randomBytes(16).toString("hex"),
|
|
167
170
|
};
|
|
168
|
-
/**
|
|
169
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Which systemd manager runs the service the fleet is in: "user" (a unit below user@UID.service — com.agend.fleet),
|
|
173
|
+
* "system" (a unit of the system manager — the legacy /etc/systemd/system/agend.service, #2274), false (no service: a
|
|
174
|
+
* detached fleet), null (unknown: unreadable, or rows that disagree). Unknown is not proof that a detached child can
|
|
175
|
+
* escape a service's KillMode.
|
|
176
|
+
*/
|
|
177
|
+
export function serviceManagerOf(text) {
|
|
170
178
|
if (!text || text.length > 64 * 1024 || text.includes("\0"))
|
|
171
179
|
return null;
|
|
172
180
|
const rows = text.trimEnd().split("\n");
|
|
173
|
-
|
|
181
|
+
const managers = new Set();
|
|
174
182
|
for (const row of rows) {
|
|
175
183
|
const parsed = /^(\d+):([^:]*):(\/[^\r\n]*)$/.exec(row);
|
|
176
184
|
if (!parsed || parsed[3].split("/").some(part => part === "." || part === ".."))
|
|
177
185
|
return null;
|
|
178
|
-
// user@UID.service is the user manager itself, not a fleet stop boundary.
|
|
179
|
-
|
|
186
|
+
// user@UID.service is the user manager itself, not a fleet stop boundary: the units below it are the user's.
|
|
187
|
+
let underUserManager = false;
|
|
188
|
+
for (const part of parsed[3].split("/")) {
|
|
189
|
+
if (/^user@\d+\.service$/.test(part))
|
|
190
|
+
underUserManager = true;
|
|
191
|
+
else if (part.endsWith(".service"))
|
|
192
|
+
managers.add(underUserManager ? "user" : "system");
|
|
193
|
+
}
|
|
180
194
|
}
|
|
181
|
-
|
|
195
|
+
if (managers.size > 1)
|
|
196
|
+
return null;
|
|
197
|
+
return managers.size ? [...managers][0] : false;
|
|
198
|
+
}
|
|
199
|
+
/** Unknown is not proof that a detached child can escape a service's KillMode. */
|
|
200
|
+
export function inServiceCgroup(text) {
|
|
201
|
+
const manager = serviceManagerOf(text);
|
|
202
|
+
return manager === null ? null : manager !== false;
|
|
182
203
|
}
|
|
183
204
|
/** Same UID, no privileged or remote manager, no credentials in argv. No unsafe detached fallback. */
|
|
184
205
|
export async function resolveUpdateLaunch(agend, deps = defaultUpdateLaunchDeps) {
|
|
@@ -192,11 +213,16 @@ export async function resolveUpdateLaunch(agend, deps = defaultUpdateLaunchDeps)
|
|
|
192
213
|
catch {
|
|
193
214
|
return { ok: false, reason: "cannot read the fleet's service cgroup" };
|
|
194
215
|
}
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
216
|
+
const manager = serviceManagerOf(before);
|
|
217
|
+
if (manager === null)
|
|
197
218
|
return { ok: false, reason: "cannot identify the fleet's service cgroup" };
|
|
198
|
-
if (!
|
|
219
|
+
if (!manager)
|
|
199
220
|
return { ok: true, ...plain };
|
|
221
|
+
// #2274: a fleet under the system manager (the legacy agend.service, root) has no user bus; its updater's scope is the
|
|
222
|
+
// system manager's — which only root may ask for.
|
|
223
|
+
if (manager === "system" && (deps.uid?.() ?? -1) !== 0) {
|
|
224
|
+
return { ok: false, reason: "the fleet runs as a system service, and only root can give its updater a scope of its own" };
|
|
225
|
+
}
|
|
200
226
|
let version;
|
|
201
227
|
try {
|
|
202
228
|
version = await deps.version();
|
|
@@ -222,7 +248,7 @@ export async function resolveUpdateLaunch(agend, deps = defaultUpdateLaunchDeps)
|
|
|
222
248
|
}
|
|
223
249
|
const scope = `agend-updater-${nonce}.scope`;
|
|
224
250
|
return { ok: true, command: "systemd-run", scope, args: [
|
|
225
|
-
"--user", "--scope", "--quiet", "--collect", "--no-ask-password", `--unit=${scope}`,
|
|
251
|
+
...(manager === "user" ? ["--user"] : []), "--scope", "--quiet", "--collect", "--no-ask-password", `--unit=${scope}`,
|
|
226
252
|
...expansion, "--", plain.command, ...plain.args,
|
|
227
253
|
] };
|
|
228
254
|
}
|