@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
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// #2264: AgEnD's one installer. Install, upgrade and repair are this file, always run from the TARGET release itself:
|
|
4
|
+
//
|
|
5
|
+
// npm exec --yes --package @songsid/agend@<version> -- agend-install [--upgrade]
|
|
6
|
+
//
|
|
7
|
+
// install.sh (and so the Windows installer) and `agend update` both end here, so the newest install logic runs on every
|
|
8
|
+
// install — never an old updater that happens to be on the host. It installs exactly the version it ships in, and:
|
|
9
|
+
// 1. owns the npm prefix first (admit(): a live installer's lock → stop, nothing changed) and keeps it to the end;
|
|
10
|
+
// 2. repairs what an interrupted install left in the prefix (repair(): only while no npm changes packages; the only
|
|
11
|
+
// copy of AgEnD put back, anything else moved to quarantine, never deleted);
|
|
12
|
+
// 3. `npm install -g @songsid/agend@<this version>` (once more only if repairing moved something npm itself left);
|
|
13
|
+
// 4. proves `<prefix>/bin/agend --version` is this version; otherwise says what runs now — the previous version that
|
|
14
|
+
// npm kept is left as it is, and only an `agend` that does not run is reinstalled;
|
|
15
|
+
// 5. on a host with a service: `agend install --no-activate` under the manager it has, then the fleet is restarted
|
|
16
|
+
// (or started, when its service is stopped); a hand-started fleet is restarted by an upgrade only;
|
|
17
|
+
// 6. records each step in the update marker the fleet edits its chat message from — only a marker it owns.
|
|
18
|
+
// Nothing here refuses a manual `npm install -g`: the lock only keeps two of THESE installers from running at once.
|
|
19
|
+
// Old syntax only (var, no ?. ??): this runs on whatever Node runs npm.
|
|
20
|
+
var fs = require("fs");
|
|
21
|
+
var path = require("path");
|
|
22
|
+
var os = require("os");
|
|
23
|
+
var childProcess = require("child_process");
|
|
24
|
+
|
|
25
|
+
var LOCK = ".agend-install.lock";
|
|
26
|
+
var MARKER = "update-in-progress.json";
|
|
27
|
+
|
|
28
|
+
function defaultDeps() {
|
|
29
|
+
return {
|
|
30
|
+
run: function (cmd, args, opts) {
|
|
31
|
+
var o = opts || {};
|
|
32
|
+
var r = childProcess.spawnSync(cmd, args, { encoding: "utf8", stdio: o.inherit ? "inherit" : ["ignore", "pipe", "pipe"], env: o.env || process.env, timeout: o.timeoutMs });
|
|
33
|
+
return { status: r.status, signal: r.signal, stdout: r.stdout || "", stderr: r.stderr || "", error: r.error ? String(r.error.code || r.error.message) : null };
|
|
34
|
+
},
|
|
35
|
+
log: function (line) { process.stdout.write(line + "\n"); },
|
|
36
|
+
pid: process.pid,
|
|
37
|
+
alive: function (pid) { try { process.kill(pid, 0); return true; } catch (e) { return e.code === "EPERM"; } },
|
|
38
|
+
uid: typeof process.getuid === "function" ? process.getuid() : -1,
|
|
39
|
+
env: process.env,
|
|
40
|
+
now: function () { return Date.now(); },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readJson(file) { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch (e) { return null; } }
|
|
45
|
+
function last(text) { var lines = String(text || "").trim().split("\n"); return (lines[lines.length - 1] || "").trim(); }
|
|
46
|
+
/** A whole AgEnD package in `dir` — its package.json and every bin it names — gives its version; a partial one, null. */
|
|
47
|
+
function isAgendPackage(dir) {
|
|
48
|
+
var p = readJson(path.join(dir, "package.json"));
|
|
49
|
+
if (!p || p.name !== "@songsid/agend" || typeof p.version !== "string" || !p.bin || typeof p.bin !== "object") return null;
|
|
50
|
+
var bins = Object.keys(p.bin).map(function (k) { return p.bin[k]; });
|
|
51
|
+
return bins.length && bins.every(function (b) { return typeof b === "string" && fs.existsSync(path.join(dir, b)); }) ? p.version : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The update marker the fleet edits its chat message from (src/update-marker.ts) — written only while this installer
|
|
56
|
+
* owns it (#2264 review). It is claimed once this installer holds the prefix: when the `agend update` that started it
|
|
57
|
+
* wrote it (AGEND_UPDATE_OWNER_PID), or when its writer is gone (an update that died: the user's stuck "downloading").
|
|
58
|
+
* A marker another live process writes — a running update's, a new fleet's — is never changed. Every write re-reads it
|
|
59
|
+
* and writes only while this installer is still its writer (a new update or a new fleet that took it over keeps it).
|
|
60
|
+
*/
|
|
61
|
+
function claimMarker(home, deps, opts) {
|
|
62
|
+
var marker = readJson(path.join(home, MARKER));
|
|
63
|
+
if (!marker || !marker.progress || typeof marker.pid !== "number") return null;
|
|
64
|
+
var owner = Number(deps.env.AGEND_UPDATE_OWNER_PID) || 0;
|
|
65
|
+
var ours = marker.pid === deps.pid || (owner > 0 && marker.pid === owner);
|
|
66
|
+
if (!ours && (opts.ownOnly || deps.alive(marker.pid))) return null;
|
|
67
|
+
return writeMarker(home, marker, deps);
|
|
68
|
+
}
|
|
69
|
+
function writeMarker(home, marker, deps) {
|
|
70
|
+
var file = path.join(home, MARKER);
|
|
71
|
+
marker.pid = deps.pid;
|
|
72
|
+
var tmp = file + "." + deps.pid + ".tmp";
|
|
73
|
+
try { fs.writeFileSync(tmp, JSON.stringify(marker), { mode: 384 }); fs.renameSync(tmp, file); return true; } catch (e) { return false; }
|
|
74
|
+
}
|
|
75
|
+
function setStage(home, claimed, stage, details, deps) {
|
|
76
|
+
if (!claimed) return false;
|
|
77
|
+
var marker = readJson(path.join(home, MARKER));
|
|
78
|
+
if (!marker || !marker.progress || marker.pid !== deps.pid) return false;
|
|
79
|
+
var previous = marker.progress.stage;
|
|
80
|
+
marker.progress.stage = stage;
|
|
81
|
+
if (details.version) marker.progress.version = details.version;
|
|
82
|
+
if (details.error) marker.progress.error = String(details.error).replace(/\s+/g, " ").trim().slice(0, 300);
|
|
83
|
+
if (stage === "failed") marker.progress.failedStage = previous;
|
|
84
|
+
return writeMarker(home, marker, deps);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Is npm provably idle on this host — is no npm process changing installed packages? Only an npm that installs,
|
|
89
|
+
* uninstalls, updates, links or rebuilds can be reifying this prefix. `npm exec` (this installer's own ancestor; AgEnD
|
|
90
|
+
* fleets run their MCP servers that way, for hours), `npm view`, `npm run` cannot, so they do not count. npm rewrites its
|
|
91
|
+
* process title to `npm <command> <args>` without its flags, but argv as started shows them, and a flag is read the way
|
|
92
|
+
* npm reads it (Prism r2): one that takes a value consumes the next word whatever it is (`npm --prefix run install` is an
|
|
93
|
+
* install); one that takes none consumes nothing; an unknown one may be either, and both readings are kept. The npm
|
|
94
|
+
* counts as idle only if every reading's command is one that cannot reify; an unknown word as the command is busy. A
|
|
95
|
+
* process list that cannot be read (ps failed, a row that does not parse, this process missing from it) is not idle.
|
|
96
|
+
*/
|
|
97
|
+
var NPM_CHANGES = /^(?:install|i|in|ins|inst|insta|instal|isnt|isnta|isntal|isntall|add|uninstall|un|unlink|remove|rm|r|update|up|upgrade|udpate|link|ln|ci|clean-install|install-clean|ic|isntall-clean|install-ci-test|cit|install-test|it|dedupe|ddp|find-dupes|prune|rebuild|rb)$/;
|
|
98
|
+
var NPM_READS = /^(?:exec|x|access|adduser|add-user|login|logout|audit|bugs|issues|cache|completion|config|c|get|set|deprecate|diff|dist-tag|dist-tags|docs|home|doctor|edit|explain|why|explore|fund|help|help-search|hook|init|create|innit|ls|list|la|ll|org|ogr|outdated|owner|author|pack|ping|pkg|prefix|profile|publish|query|repo|restart|root|run|run-script|rum|urn|sbom|search|find|s|se|shrinkwrap|star|stars|start|stop|team|test|tst|t|token|unpublish|unstar|version|verison|view|v|info|show|whoami)$/;
|
|
99
|
+
// npm's flags that take a value (npm 10 config definitions) and the common ones that take none.
|
|
100
|
+
var NPM_VALUE_FLAGS = /^(?:--(?:prefix|userconfig|globalconfig|cache|registry|loglevel|workspace|tag|otp|package|call|omit|include|before|access|scope|script-shell|shell|node-options|location|install-strategy|depth|auth-type|cafile|cert|key|proxy|https-proxy|noproxy|local-address|fetch-retries|fetch-timeout|maxsockets|user-agent|editor|browser|viewer|init-module|tag-version-prefix|preid|message|logs-dir|logs-max|replace-registry-host|cidr|expect-results|expect-result-count|sbom-format|sbom-type|lockfile-version|os|cpu|libc)|-w|-C|-c|-m)$/;
|
|
101
|
+
var NPM_BOOLEAN_FLAGS = /^(?:-g|-y|-f|-s|-q|-d|-dd|-ddd|-S|-D|-O|-E|-B|-P|-l|-a|-h|-H|-v|--(?:no-[a-z0-9-]+|global|yes|force|silent|quiet|dry-run|json|offline|prefer-offline|prefer-online|ignore-scripts|foreground-scripts|legacy-peer-deps|strict-peer-deps|package-lock-only|save|save-dev|save-optional|save-exact|save-prod|save-peer|save-bundle|global-style|install-links|link|verbose|long|parseable|audit|fund|workspaces|include-workspace-root|if-present|production|usage|help|version|all|unicode|progress|update-notifier|bin-links|rebuild-bundle|engine-strict|prefer-dedupe|read-only|provenance|timing|umask))$/;
|
|
102
|
+
function isNpmCommand(word) { return NPM_CHANGES.test(word) || NPM_READS.test(word); }
|
|
103
|
+
function npmCommands(args) {
|
|
104
|
+
var words = args.split(/\s+/).filter(function (w) { return w !== ""; });
|
|
105
|
+
var at = -1;
|
|
106
|
+
for (var k = 0; k < words.length && k < 3; k++) {
|
|
107
|
+
if (/(?:^|\/)(?:npm|npx)$/.test(words[k]) || /(?:^|\/)(?:npm|npx)-cli\.js$/.test(words[k])) { at = k; break; }
|
|
108
|
+
}
|
|
109
|
+
if (at < 0) return null;
|
|
110
|
+
if (/npx(?:-cli\.js)?$/.test(words[at])) return ["exec"];
|
|
111
|
+
// Every command this argv can mean: `pending` = the next word may be an unknown flag's value.
|
|
112
|
+
var readings = function (from, pending) {
|
|
113
|
+
for (var w = from; w < words.length; w++) {
|
|
114
|
+
var word = words[w];
|
|
115
|
+
if (word.charAt(0) === "-") {
|
|
116
|
+
if (word.indexOf("=") >= 0 || NPM_BOOLEAN_FLAGS.test(word)) { pending = false; continue; }
|
|
117
|
+
if (NPM_VALUE_FLAGS.test(word)) { w++; pending = false; continue; } // its value, whatever it says
|
|
118
|
+
pending = true; // unknown: a value may follow, or not
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
var asCommand = isNpmCommand(word) ? [word] : ["?"];
|
|
122
|
+
return pending ? asCommand.concat(readings(w + 1, false)) : asCommand;
|
|
123
|
+
}
|
|
124
|
+
return pending ? [] : ["?"];
|
|
125
|
+
};
|
|
126
|
+
var all = readings(at + 1, false);
|
|
127
|
+
return all.length ? all : ["?"];
|
|
128
|
+
}
|
|
129
|
+
function npmIdleFromProcessList(output, ownPid) {
|
|
130
|
+
var rows = String(output).trim().split("\n").map(function (line) {
|
|
131
|
+
var m = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line);
|
|
132
|
+
return m ? { pid: Number(m[1]), parent: Number(m[2]), args: m[3] } : null;
|
|
133
|
+
});
|
|
134
|
+
if (!rows.length || rows.some(function (row) { return !row; })) return false;
|
|
135
|
+
if (!rows.some(function (row) { return row.pid === ownPid; })) return false;
|
|
136
|
+
return !rows.some(function (row) {
|
|
137
|
+
var commands = npmCommands(row.args);
|
|
138
|
+
return commands !== null && !commands.every(function (c) { return NPM_READS.test(c); });
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function npmIdle(deps) {
|
|
142
|
+
var r = deps.run("ps", ["-A", "-o", "pid=,ppid=,args="], { timeoutMs: 5000 });
|
|
143
|
+
return r.status === 0 && npmIdleFromProcessList(r.stdout, deps.pid);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Move a leftover into `<prefix>/.agend-quarantine/<time>-<pid>/` — kept as evidence, never deleted here. */
|
|
147
|
+
function quarantine(prefix, source, deps, name) {
|
|
148
|
+
var dir = path.join(prefix, ".agend-quarantine", new Date(deps.now()).toISOString().replace(/[:.]/g, "-") + "-" + deps.pid);
|
|
149
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
150
|
+
var to = path.join(dir, name || path.basename(source));
|
|
151
|
+
fs.renameSync(source, to);
|
|
152
|
+
return to;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function readText(file) { try { return fs.readFileSync(file, "utf8"); } catch (e) { return null; } }
|
|
156
|
+
/** Create `file` holding `text` only if it does not exist: written aside, then linked into place (whole or not at all). */
|
|
157
|
+
function createWhole(file, text, deps) {
|
|
158
|
+
var tmp = file + "." + deps.pid + ".tmp";
|
|
159
|
+
fs.writeFileSync(tmp, text, { mode: 384 });
|
|
160
|
+
try { fs.linkSync(tmp, file); return true; } catch (e) { return false; } finally { try { fs.unlinkSync(tmp); } catch (e) { /* gone */ } }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The lock of updaters from before #2264 (src/install-lock.ts acquireInstallLock), which they alone understand — so it is
|
|
165
|
+
* kept by their own rules (Prism r3): a record in their format (pid + start time, read by install-admission.cjs, so a
|
|
166
|
+
* running old updater sees this installer as a live holder and waits); a holder judged as they judge it (live, stale or
|
|
167
|
+
* unknown — unknown blocks); and a stale lock moved only by the holder of `<lock>.reclaim` (claimed whole), under which
|
|
168
|
+
* the lock can change only by its own holder — proven gone. Nothing is ever moved aside to be judged afterwards. One
|
|
169
|
+
* thing more than they do: a lock that cannot be read (created, never filled: the user's, cut short) — their
|
|
170
|
+
* create-then-write keeps the file open from the create to the write, and no one else can open it to write it (theirs is
|
|
171
|
+
* open(wx)). So it counts as stale only when it is proven that no process has it open (openedBy); its age is no evidence
|
|
172
|
+
* (Prism r4: an old writer paused between open and write for a minute is still admitted when it resumes).
|
|
173
|
+
*/
|
|
174
|
+
var lockReaders = require("./install-admission.cjs");
|
|
175
|
+
/**
|
|
176
|
+
* Does any process have `file` open? true / false, or null when that cannot be told — which blocks. Only processes of the
|
|
177
|
+
* file's owner can have created it and hold it open to write (its creator is its owner). Linux: every such process's
|
|
178
|
+
* /proc/<pid>/fd, matched by device and inode. A process of the same user whose descriptors cannot be read is one that
|
|
179
|
+
* made itself non-dumpable (sd-pam, ssh-agent, gpg-agent); Node never does, so it is an old updater only if its command
|
|
180
|
+
* line names node or agend — then, and when even that cannot be read, null. macOS: lsof (exit 1 with no output = none).
|
|
181
|
+
* Elsewhere: null.
|
|
182
|
+
*/
|
|
183
|
+
/** What a descriptor's link names when it is no file on disk: it cannot be the lock. */
|
|
184
|
+
var NOT_A_FILE = /^(?:(?:pipe|socket):\[\d+\]|anon_inode:.*|\/dev\/(?:null|zero|u?random|full|tty\w*|console|ptmx|pts\/\d+))$/;
|
|
185
|
+
/** Why the last openedBy answered as it did (a refusal names it, so a stuck host says exactly what could not be read). */
|
|
186
|
+
var openedWhy = "";
|
|
187
|
+
function openedBy(file, deps) {
|
|
188
|
+
openedWhy = "";
|
|
189
|
+
var unknown = function (why) { openedWhy = why; return null; };
|
|
190
|
+
var because = function (e, what) { return (e && e.code ? e.code : String(e)) + " on " + what; };
|
|
191
|
+
if (deps.openedBy) return deps.openedBy(file);
|
|
192
|
+
// Only a definite "it is gone" (ENOENT) may be skipped; a permission, I/O or any other error proves nothing (Prism r5:
|
|
193
|
+
// /proc/<pid>/fd is behind ptrace checks) → unknown, which blocks.
|
|
194
|
+
var stat = deps.procStat || fs.statSync;
|
|
195
|
+
var readdir = deps.procReaddir || fs.readdirSync;
|
|
196
|
+
var gone = function (e) { return e && e.code === "ENOENT"; };
|
|
197
|
+
var st;
|
|
198
|
+
try { st = stat(file); } catch (e) { return gone(e) ? false : unknown(because(e, file)); }
|
|
199
|
+
if (process.platform === "linux") {
|
|
200
|
+
var pids;
|
|
201
|
+
try { pids = fs.readdirSync("/proc").filter(function (n) { return /^\d+$/.test(n); }); } catch (e) { return unknown(because(e, "/proc")); }
|
|
202
|
+
for (var k = 0; k < pids.length; k++) {
|
|
203
|
+
var owner;
|
|
204
|
+
try { owner = stat("/proc/" + pids[k]).uid; } catch (e) { if (gone(e)) continue; return unknown(because(e, "/proc/" + pids[k])); } // gone meanwhile, or unknown
|
|
205
|
+
if (owner !== st.uid) continue;
|
|
206
|
+
var fds;
|
|
207
|
+
// A process this one may not inspect (EACCES: non-dumpable, or other credentials — the kernel's ptrace check on
|
|
208
|
+
// its descriptors) is not an old updater, a plain node process of this user, unless its command line says so.
|
|
209
|
+
var notInspectable = function (pid, what) {
|
|
210
|
+
var cmdline = null;
|
|
211
|
+
try { cmdline = fs.readFileSync("/proc/" + pid + "/cmdline", "utf8"); } catch (e2) { if (gone(e2)) return "gone"; }
|
|
212
|
+
if (cmdline === null || /node|agend/i.test(cmdline)) return "EACCES on " + what + " (" + (cmdline === null ? "its command line unreadable" : JSON.stringify(cmdline.replace(/\0/g, " ").trim().slice(0, 80))) + ")";
|
|
213
|
+
return "skip";
|
|
214
|
+
};
|
|
215
|
+
try { fds = readdir("/proc/" + pids[k] + "/fd"); } catch (e) {
|
|
216
|
+
if (gone(e)) continue;
|
|
217
|
+
if (e.code !== "EACCES") return unknown(because(e, "/proc/" + pids[k] + "/fd"));
|
|
218
|
+
var verdict = notInspectable(pids[k], "/proc/" + pids[k] + "/fd");
|
|
219
|
+
if (verdict === "gone" || verdict === "skip") continue;
|
|
220
|
+
return unknown(verdict);
|
|
221
|
+
}
|
|
222
|
+
for (var f = 0; f < fds.length; f++) {
|
|
223
|
+
var seen;
|
|
224
|
+
var fdPath = "/proc/" + pids[k] + "/fd/" + fds[f];
|
|
225
|
+
try { seen = stat(fdPath); } catch (e) {
|
|
226
|
+
if (gone(e)) continue; // closed meanwhile
|
|
227
|
+
// Its target cannot be stat'ed (a security module, as on CI runners): the link itself still names it. A pipe,
|
|
228
|
+
// a socket, an anonymous inode or a terminal/null device is not the lock (a regular file in the prefix).
|
|
229
|
+
var target = null, linkError = null;
|
|
230
|
+
try { target = (deps.procReadlink || fs.readlinkSync)(fdPath); } catch (e2) { if (gone(e2)) continue; linkError = e2; }
|
|
231
|
+
if (target !== null && NOT_A_FILE.test(target)) continue;
|
|
232
|
+
if (target === null && e.code === "EACCES" && linkError && linkError.code === "EACCES") {
|
|
233
|
+
// Neither its target nor its link may be read: the process is not inspectable at all (CI runners have one).
|
|
234
|
+
var seenAs = notInspectable(pids[k], fdPath);
|
|
235
|
+
if (seenAs === "gone" || seenAs === "skip") break;
|
|
236
|
+
return unknown(seenAs);
|
|
237
|
+
}
|
|
238
|
+
return unknown(because(e, fdPath) + (target !== null ? " → " + target : ""));
|
|
239
|
+
}
|
|
240
|
+
if (seen.dev === st.dev && seen.ino === st.ino) { openedWhy = "pid " + pids[k] + " has it open"; return true; }
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
if (process.platform === "darwin") {
|
|
246
|
+
var r = deps.run(fs.existsSync("/usr/sbin/lsof") ? "/usr/sbin/lsof" : "lsof", ["-nP", "-t", "--", file], { timeoutMs: 15000 });
|
|
247
|
+
if (r.status === 0 && String(r.stdout).trim()) { openedWhy = "pid " + String(r.stdout).trim().split("\n").join(", ") + " has it open"; return true; }
|
|
248
|
+
if (r.status === 1 && !String(r.stdout).trim() && !String(r.stderr).trim()) return false;
|
|
249
|
+
return unknown("lsof exited " + (r.status === null ? r.error || r.signal : r.status) + (String(r.stderr).trim() ? ": " + String(r.stderr).trim().slice(0, 120) : ""));
|
|
250
|
+
}
|
|
251
|
+
return unknown("no way to see open files on " + process.platform);
|
|
252
|
+
}
|
|
253
|
+
/** An unreadable lock that may still be being written: refused, saying what was seen. */
|
|
254
|
+
function beingWritten(lock, why) {
|
|
255
|
+
return "the install lock " + lock + " is being written by another AgEnD install, or whether it still is cannot be told" + (why ? " (" + why + ")" : "") + "; run this again in a moment";
|
|
256
|
+
}
|
|
257
|
+
function legacyRecord(prefix, deps, spec, home) {
|
|
258
|
+
var r = deps.lockReaders || lockReaders;
|
|
259
|
+
var ticks = r.startTicks(deps.pid), boot = r.bootId();
|
|
260
|
+
var record = { pid: deps.pid, processStart: r.processStart(deps.pid) || "" };
|
|
261
|
+
if (ticks !== null && boot !== null) { record.startTicks = ticks; record.bootId = boot; }
|
|
262
|
+
record.prefix = prefix; record.targetSpec = spec; record.agendHome = home;
|
|
263
|
+
record.token = require("crypto").randomBytes(16).toString("hex");
|
|
264
|
+
record.createdAt = new Date(deps.now()).toISOString();
|
|
265
|
+
return JSON.stringify(record) + "\n";
|
|
266
|
+
}
|
|
267
|
+
function legacyState(text, file, deps) {
|
|
268
|
+
var r = deps.lockReaders || lockReaders;
|
|
269
|
+
var holder = r.parse(text);
|
|
270
|
+
if (holder) return { state: holder.pid === deps.pid ? "stale" : r.holderState(holder, { exists: deps.alive, processStart: r.processStart, startTicks: r.startTicks, bootId: r.bootId }), pid: holder.pid };
|
|
271
|
+
if (!fs.existsSync(file)) return { state: "gone" };
|
|
272
|
+
var open = openedBy(file, deps);
|
|
273
|
+
return { state: open === false ? "stale" : "unknown", unreadable: true, why: openedWhy };
|
|
274
|
+
}
|
|
275
|
+
/** Move a stale legacy lock away by their rule: only under `<lock>.reclaim`, and only the bytes judged. */
|
|
276
|
+
function reclaimLegacy(prefix, lock, judged, judgedState, record, deps) {
|
|
277
|
+
var marker = lock + ".reclaim";
|
|
278
|
+
if (!createWhole(marker, record, deps)) {
|
|
279
|
+
var other = (deps.lockReaders || lockReaders).parse(readText(marker) || "");
|
|
280
|
+
var otherState = other ? legacyState(readText(marker) || "", marker, deps).state : "unknown";
|
|
281
|
+
return otherState === "live" ? { refuse: "another install is replacing the stale lock on " + prefix + "; run this again in a moment" }
|
|
282
|
+
: { refuse: "a reclaim marker " + marker + " was left behind (pid " + (other ? other.pid : "unknown") + "); if no `agend update` runs, remove it and run this again" };
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
if (deps.onStep) deps.onStep("legacy-reclaimed");
|
|
286
|
+
// Under the marker the lock changes only by its own (gone) holder: still the one judged, else nothing is moved.
|
|
287
|
+
if (readText(lock) !== judged) return { changed: true };
|
|
288
|
+
// An unreadable one: still not open anywhere (no writer can open it again: theirs is open(wx) on a new file).
|
|
289
|
+
if (judgedState.unreadable && openedBy(lock, deps) !== false) return { refuse: beingWritten(lock, openedWhy) };
|
|
290
|
+
quarantine(prefix, lock, deps);
|
|
291
|
+
return { moved: true };
|
|
292
|
+
} finally {
|
|
293
|
+
if (readText(marker) === record) { try { fs.unlinkSync(marker); } catch (e) { /* gone */ } }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
var ENTRIES = ".agend-install.d";
|
|
298
|
+
function holderOf(text) {
|
|
299
|
+
var holder = null;
|
|
300
|
+
try { holder = JSON.parse(text); } catch (e) { holder = null; }
|
|
301
|
+
return holder && typeof holder.pid === "number" ? holder : null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Admission (#2264 review): this installer owns the prefix before anything in it is touched, and keeps it through the
|
|
306
|
+
* repair, the install, its retry and the rollback.
|
|
307
|
+
*
|
|
308
|
+
* Between these installers it is announce-then-check — nothing of another installer is ever moved to decide it (Prism
|
|
309
|
+
* r2: a takeover of a shared lock path can always move a newer owner's lock by a stale reader). Each installer writes an
|
|
310
|
+
* entry of its own, `<prefix>/.agend-install.d/<pid>-<random>` (whole, written aside and linked), and only then lists the
|
|
311
|
+
* others: one whose writer runs → it withdraws its own entry and refuses (75). Of two that race, at least the later one
|
|
312
|
+
* sees the earlier's entry, so two are never admitted (both may refuse: run again). An entry whose writer is gone has a
|
|
313
|
+
* name no one else will ever write, so it is moved to quarantine without a race.
|
|
314
|
+
*
|
|
315
|
+
* The admitted installer then also holds `<prefix>/.agend-install.lock`, the lock updaters from before #2264 take, by
|
|
316
|
+
* their rules (above): one of theirs that runs, or whose holder cannot be told → refuse; a stale one is moved to
|
|
317
|
+
* quarantine under their `.reclaim` marker. Its token is this installer's: every npm install it runs presents it
|
|
318
|
+
* (AGEND_INSTALL_TOKEN), because a release from before #2264 — a target installed by --spec, the previous version put
|
|
319
|
+
* back — admits its own postinstall only for the token of the live lock (#2286 review).
|
|
320
|
+
* Returns { release, notes, token } or { refuse }.
|
|
321
|
+
*/
|
|
322
|
+
function admit(prefix, deps, opts) {
|
|
323
|
+
opts = opts || {};
|
|
324
|
+
// The prefix as old updaters name it (realpath): one lock however the prefix was reached.
|
|
325
|
+
try { prefix = fs.realpathSync(prefix); } catch (e) { /* the writes below say */ }
|
|
326
|
+
var dir = path.join(prefix, ENTRIES);
|
|
327
|
+
var name = deps.pid + "-" + (deps.nonce ? deps.nonce() : require("crypto").randomBytes(8).toString("hex"));
|
|
328
|
+
var mine = path.join(dir, name);
|
|
329
|
+
var record = JSON.stringify({ pid: deps.pid, by: "agend-install", createdAt: new Date(deps.now()).toISOString() }) + "\n";
|
|
330
|
+
var notes = [];
|
|
331
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch (e) { /* the create below says */ }
|
|
332
|
+
if (!createWhole(mine, record, deps)) return { refuse: "cannot write an install entry in " + dir };
|
|
333
|
+
var withdraw = function () { if (readText(mine) === record) { try { fs.unlinkSync(mine); } catch (e) { /* gone */ } } };
|
|
334
|
+
if (deps.onStep) deps.onStep("announced");
|
|
335
|
+
var others = [];
|
|
336
|
+
try { others = fs.readdirSync(dir).filter(function (n) { return n !== name && !/\.tmp$/.test(n); }); } catch (e) { withdraw(); return { refuse: "cannot list " + dir }; }
|
|
337
|
+
for (var k = 0; k < others.length; k++) {
|
|
338
|
+
var holder = holderOf(readText(path.join(dir, others[k])));
|
|
339
|
+
if (holder && holder.pid !== deps.pid && deps.alive(holder.pid)) {
|
|
340
|
+
withdraw();
|
|
341
|
+
return { refuse: "another AgEnD install (pid " + holder.pid + ") is running in " + prefix + "; let it finish, then run this again" };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
others.forEach(function (n) {
|
|
345
|
+
// Gone (or written by no installer): its name is its writer's alone.
|
|
346
|
+
try { quarantine(prefix, path.join(dir, n), deps); notes.push("moved aside an install entry left by an installer that is no longer running"); } catch (e) { /* gone */ }
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
var lock = path.join(prefix, LOCK);
|
|
350
|
+
var legacy = legacyRecord(prefix, deps, opts.spec || "", opts.home || "");
|
|
351
|
+
for (var attempt = 0; attempt < 3; attempt++) {
|
|
352
|
+
if (createWhole(lock, legacy, deps)) {
|
|
353
|
+
return { notes: notes, token: JSON.parse(legacy).token, release: function () {
|
|
354
|
+
if (readText(lock) === legacy) { try { fs.unlinkSync(lock); } catch (e) { /* gone */ } }
|
|
355
|
+
withdraw();
|
|
356
|
+
} };
|
|
357
|
+
}
|
|
358
|
+
var text = readText(lock);
|
|
359
|
+
if (text === null) continue;
|
|
360
|
+
var judged = legacyState(text, lock, deps);
|
|
361
|
+
if (judged.state === "gone") continue;
|
|
362
|
+
if (judged.state !== "stale") {
|
|
363
|
+
withdraw();
|
|
364
|
+
return { refuse: judged.unreadable
|
|
365
|
+
? beingWritten(lock, judged.why)
|
|
366
|
+
: "another AgEnD install (pid " + judged.pid + ") is running in " + prefix + "; let it finish, then run this again" };
|
|
367
|
+
}
|
|
368
|
+
if (deps.onStep) deps.onStep("legacy-judged");
|
|
369
|
+
var taken = reclaimLegacy(prefix, lock, text, judged, legacy, deps);
|
|
370
|
+
if (taken.refuse) { withdraw(); return { refuse: taken.refuse }; }
|
|
371
|
+
if (taken.moved) notes.push(judged.unreadable ? "moved aside an install lock that could not be read" : "moved aside an install lock left by pid " + judged.pid + " (no longer running)");
|
|
372
|
+
}
|
|
373
|
+
withdraw();
|
|
374
|
+
return { refuse: "another AgEnD install keeps claiming " + lock + "; let it finish, then run this again" };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* What an interrupted install leaves, put right — only by the installer that owns the prefix (admit), and only while
|
|
379
|
+
* npm is provably idle (above); otherwise npm's leftovers are left alone. npm's retire sibling `@songsid/.agend-<hash>`
|
|
380
|
+
* (npm moves the installed package there before it writes the new one; killed in between, every later `npm install -g`
|
|
381
|
+
* fails ENOTEMPTY on it): the only whole copy of AgEnD is put back (a partial new package in its place goes to
|
|
382
|
+
* quarantine); any other sibling, and npm's retired bin links `bin/.agend-<hash>`, go to quarantine. The lock is not
|
|
383
|
+
* this function's: it never reads or moves one.
|
|
384
|
+
*/
|
|
385
|
+
function repair(root, prefix, deps) {
|
|
386
|
+
var notes = [];
|
|
387
|
+
var scope = path.join(root, "@songsid");
|
|
388
|
+
var current = path.join(scope, "agend");
|
|
389
|
+
var siblings = [];
|
|
390
|
+
try { siblings = fs.readdirSync(scope).filter(function (n) { return /^\.agend-/.test(n); }); } catch (e) { siblings = []; }
|
|
391
|
+
var binDir = path.join(prefix, "bin");
|
|
392
|
+
var binSiblings = [];
|
|
393
|
+
try { binSiblings = fs.readdirSync(binDir).filter(function (n) { return /^\.agend(-agent|-install)?-[A-Za-z0-9]{8}$/.test(n); }); } catch (e) { binSiblings = []; }
|
|
394
|
+
if (!siblings.length && !binSiblings.length) return { notes: notes, changed: false };
|
|
395
|
+
if (!(deps.npmIdle || npmIdle)(deps)) {
|
|
396
|
+
notes.push("another npm is running on this host, so what an earlier install left in " + scope + " was not touched");
|
|
397
|
+
return { notes: notes, changed: false };
|
|
398
|
+
}
|
|
399
|
+
siblings.forEach(function (name) {
|
|
400
|
+
var dir = path.join(scope, name);
|
|
401
|
+
var version = isAgendPackage(dir);
|
|
402
|
+
if (version && !isAgendPackage(current)) {
|
|
403
|
+
if (fs.existsSync(current)) quarantine(prefix, current, deps);
|
|
404
|
+
fs.renameSync(dir, current);
|
|
405
|
+
notes.push("put back AgEnD v" + version + " that an interrupted install had moved aside (" + name + ")");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
notes.push("moved " + name + ", left by an interrupted install, to " + quarantine(prefix, dir, deps));
|
|
409
|
+
});
|
|
410
|
+
binSiblings.forEach(function (name) {
|
|
411
|
+
try { quarantine(prefix, path.join(binDir, name), deps); notes.push("moved bin/" + name + ", left by an interrupted install, to quarantine"); } catch (e) { /* gone */ }
|
|
412
|
+
});
|
|
413
|
+
return { notes: notes, changed: true };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The service AgEnD installed, as `agend restart` picks its authority: the legacy system unit first, then the user unit
|
|
418
|
+
* or the launchd job. null: a hand-started fleet, or none.
|
|
419
|
+
*/
|
|
420
|
+
function serviceKind(deps) {
|
|
421
|
+
var home = deps.env.HOME || os.homedir();
|
|
422
|
+
if (fs.existsSync(deps.systemUnit || "/etc/systemd/system/agend.service")) return "system";
|
|
423
|
+
if (fs.existsSync(path.join(home, ".config", "systemd", "user", "com.agend.fleet.service"))) return "user";
|
|
424
|
+
if (fs.existsSync(path.join(home, "Library", "LaunchAgents", "com.agend.fleet.plist"))) return "launchd";
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
/** A fleet runs now: fleet.pid names a live process. */
|
|
428
|
+
function fleetRunning(home, deps) {
|
|
429
|
+
var pid = Number(String(readText(path.join(home, "fleet.pid")) || "").trim());
|
|
430
|
+
return pid > 0 && deps.alive(pid);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function versionOf(agendBin, deps) {
|
|
434
|
+
var r = deps.run(agendBin, ["--version"], { timeoutMs: 60000 });
|
|
435
|
+
return r.status === 0 ? last(r.stdout).replace(/^v/i, "") : null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function main(argv, injected) {
|
|
439
|
+
var deps = injected || defaultDeps();
|
|
440
|
+
var args = argv || [];
|
|
441
|
+
var upgrade = args.indexOf("--upgrade") >= 0;
|
|
442
|
+
var specAt = args.indexOf("--spec");
|
|
443
|
+
var targetAt = args.indexOf("--target");
|
|
444
|
+
// Where the previous version is reinstalled from when the new one does not prove itself (tests and mirrors).
|
|
445
|
+
var previousAt = args.indexOf("--previous-spec");
|
|
446
|
+
var own = readJson(path.join(__dirname, "..", "package.json")) || {};
|
|
447
|
+
var version = own.version;
|
|
448
|
+
var log = deps.log;
|
|
449
|
+
// `agend update` names the release it chose: this must be that release's own installer, never another one npm found
|
|
450
|
+
// on PATH (a target without agend-install makes `npm exec` run whatever agend-install PATH has; #2264 review).
|
|
451
|
+
if (targetAt >= 0 && args[targetAt + 1] !== version) {
|
|
452
|
+
log(" ✗ this is the installer of AgEnD v" + version + ", not of v" + args[targetAt + 1] + " — nothing was changed");
|
|
453
|
+
return 1;
|
|
454
|
+
}
|
|
455
|
+
// The package this installer ships in, unless a test or a mirror names another source of the same version.
|
|
456
|
+
var spec = specAt >= 0 ? args[specAt + 1] : "@songsid/agend@" + version;
|
|
457
|
+
// An exact `@songsid/agend@<version>` spec names the version to prove; anything else (a tarball, a tag) is this one.
|
|
458
|
+
var exact = /^@songsid\/agend@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(spec);
|
|
459
|
+
if (exact) version = exact[1];
|
|
460
|
+
var home = deps.env.AGEND_HOME || path.join(os.homedir(), ".agend");
|
|
461
|
+
var claimed = false;
|
|
462
|
+
var fail = function (stage, message) {
|
|
463
|
+
log(" ✗ " + message);
|
|
464
|
+
setStage(home, claimed, "failed", { error: message }, deps);
|
|
465
|
+
return stage === "lock" ? 75 : 1;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
log(" AgEnD installer v" + version + (upgrade ? " (upgrade)" : ""));
|
|
469
|
+
var root = last(deps.run("npm", ["root", "-g"], { timeoutMs: 30000 }).stdout);
|
|
470
|
+
var prefix = last(deps.run("npm", ["prefix", "-g"], { timeoutMs: 30000 }).stdout);
|
|
471
|
+
if (!root || !prefix) {
|
|
472
|
+
claimed = claimMarker(home, deps, { ownOnly: true });
|
|
473
|
+
return fail("install", "npm did not say where it installs global packages (npm root -g / npm prefix -g)");
|
|
474
|
+
}
|
|
475
|
+
var admitted = admit(prefix, deps, { spec: spec, home: home });
|
|
476
|
+
admitted.notes && admitted.notes.forEach(function (n) { log(" ↺ " + n); });
|
|
477
|
+
if (admitted.refuse) {
|
|
478
|
+
// Refused: another installer owns the prefix. Only the marker of the `agend update` that started THIS installer
|
|
479
|
+
// says so; anyone else's (the running update's) is left exactly as it is.
|
|
480
|
+
claimed = claimMarker(home, deps, { ownOnly: true });
|
|
481
|
+
return fail("lock", admitted.refuse);
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
claimed = claimMarker(home, deps, {});
|
|
485
|
+
setStage(home, claimed, "downloading", { version: version }, deps);
|
|
486
|
+
var fixed = repair(root, prefix, deps);
|
|
487
|
+
fixed.notes.forEach(function (n) { log(" ↺ " + n); });
|
|
488
|
+
var previous = isAgendPackage(path.join(root, "@songsid", "agend"));
|
|
489
|
+
var bin = path.join(prefix, "bin", "agend");
|
|
490
|
+
// #2286 review: the lock's token, for a release from before #2264 (its postinstall refuses any other install).
|
|
491
|
+
var npmEnv = Object.assign({}, deps.env);
|
|
492
|
+
npmEnv[lockReaders.TOKEN_ENV] = admitted.token;
|
|
493
|
+
var install = function () { return deps.run("npm", ["install", "-g", spec], { inherit: true, env: npmEnv }); };
|
|
494
|
+
var done = install();
|
|
495
|
+
if (done.status !== 0) {
|
|
496
|
+
// npm itself can leave its sibling behind (a failure inside its own move): once more, only if repairing moved one.
|
|
497
|
+
var again = repair(root, prefix, deps);
|
|
498
|
+
again.notes.forEach(function (n) { log(" ↺ " + n); });
|
|
499
|
+
if (again.changed) done = install();
|
|
500
|
+
}
|
|
501
|
+
var got = done.status === 0 ? versionOf(bin, deps) : null;
|
|
502
|
+
if (got !== version) {
|
|
503
|
+
var why = done.status !== 0 ? "npm install -g " + spec + " failed (exit " + (done.status === null ? done.signal : done.status) + ")"
|
|
504
|
+
: "after npm install, " + bin + " reports " + (got || "nothing") + ", not v" + version;
|
|
505
|
+
// What actually runs now. npm rolls a failed install back itself: when the previous version still runs, nothing
|
|
506
|
+
// else is done and that is what is said. Only an `agend` that does not run is reinstalled.
|
|
507
|
+
var running = versionOf(bin, deps);
|
|
508
|
+
if (running && done.status !== 0) return fail("install", why + "; v" + running + " is still installed and runs — nothing was changed");
|
|
509
|
+
if (previous && previous !== version) {
|
|
510
|
+
repair(root, prefix, deps);
|
|
511
|
+
var back = deps.run("npm", ["install", "-g", previousAt >= 0 ? args[previousAt + 1] : "@songsid/agend@" + previous], { inherit: true, env: npmEnv });
|
|
512
|
+
if (back.status === 0 && versionOf(bin, deps) === previous) return fail("install", why + "; v" + previous + " was reinstalled and runs");
|
|
513
|
+
}
|
|
514
|
+
return fail("install", why + (previous ? "; v" + previous + " could not be put back — run the installer again" : ""));
|
|
515
|
+
}
|
|
516
|
+
log(" ✓ AgEnD v" + version + " is installed (" + bin + ")");
|
|
517
|
+
// Root under nvm: the service, cron and non-login shells do not source nvm — give them the same agend and node.
|
|
518
|
+
if (deps.uid === 0 && prefix.indexOf("/.nvm/") >= 0) {
|
|
519
|
+
[["agend", bin], ["node", path.join(prefix, "bin", "node")]].forEach(function (pair) {
|
|
520
|
+
deps.run("ln", ["-sf", pair[1], "/usr/local/bin/" + pair[0]], {});
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
setStage(home, claimed, "installed", { version: version }, deps);
|
|
524
|
+
|
|
525
|
+
// A fresh install (no fleet set up, not an upgrade): the next step is the setup wizard.
|
|
526
|
+
var kind = serviceKind(deps);
|
|
527
|
+
var configured = fs.existsSync(path.join(home, "fleet.yaml")) || fs.existsSync(path.join(home, "fleet.pid"));
|
|
528
|
+
if (!configured && !upgrade && !kind) { log(" Next: run `agend quickstart` to set up AgEnD."); return 0; }
|
|
529
|
+
// The service stays under the manager it has (`agend restart` picks the same one: system unit, else user unit or
|
|
530
|
+
// launchd job): the new release's `agend install --no-activate` refreshes that definition without starting anything
|
|
531
|
+
// (a legacy system unit is refreshed in place — never a second, user unit), then the fleet moves onto the new release:
|
|
532
|
+
// restarted when it runs, started when its service is installed but stopped (the user's: the session that ran it
|
|
533
|
+
// ended). A hand-started fleet gets no unit (#1631) and is restarted only by an upgrade.
|
|
534
|
+
if (kind) {
|
|
535
|
+
var service = deps.run(bin, ["install", "--no-activate"], { inherit: true });
|
|
536
|
+
if (service.status !== 0) return fail("starting", "`agend install --no-activate` could not refresh the " + kind + " service (exit " + service.status + "); AgEnD v" + version + " is installed — run `agend install --no-activate`, then `agend restart`");
|
|
537
|
+
}
|
|
538
|
+
var env = Object.assign({}, deps.env, { AGEND_RESTART_ORIGIN: upgrade ? "agend-update" : "agend-install" });
|
|
539
|
+
if (fleetRunning(home, deps) && (kind || upgrade)) {
|
|
540
|
+
var restarted = deps.run(bin, ["restart", "--yes"], { inherit: true, env: env });
|
|
541
|
+
if (restarted.status !== 0 && restarted.status !== 75) return fail("stopping", "the fleet did not restart onto v" + version + " (exit " + restarted.status + "); run `agend restart`");
|
|
542
|
+
} else if (kind) {
|
|
543
|
+
var started = deps.run(bin, ["start"], { inherit: true, env: env });
|
|
544
|
+
if (started.status !== 0) return fail("starting", "the " + kind + " service did not start (exit " + started.status + "); AgEnD v" + version + " is installed — run `agend start`");
|
|
545
|
+
}
|
|
546
|
+
return 0;
|
|
547
|
+
} finally {
|
|
548
|
+
admitted.release();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
module.exports = { main: main, repair: repair, admit: admit, openedBy: openedBy, setStage: setStage, claimMarker: claimMarker, npmIdleFromProcessList: npmIdleFromProcessList, npmCommands: npmCommands, LOCK: LOCK };
|
|
553
|
+
if (require.main === module) process.exit(main(process.argv.slice(2)));
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// #1450 C1:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// - no lock, no token → a plain external install: proceed (the documented restriction covers the rest);
|
|
6
|
-
// - a live lock, its own token → the updater's own npm child: proceed;
|
|
7
|
-
// - a live lock, other/no token → a foreign install while AgEnD owns the prefix: refuse;
|
|
8
|
-
// - a token, but no live lock → late or stale (it cannot authorise a successor): refuse;
|
|
9
|
-
// - a lock that cannot be read → refuse, naming the file (unknown ownership blocks).
|
|
2
|
+
// #1450 C1, reduced by #2264: the install lock's process readers (start time, start ticks, boot id), shared by the
|
|
3
|
+
// updater's lock (src/install-lock.ts). Nothing here decides whether an install may run any more — a manual
|
|
4
|
+
// `npm install -g` is never refused because of a lock (#2264: one that could not be read blocked every install).
|
|
10
5
|
// Same syntax constraints as runtime-platform.cjs: this may run on an old Node.
|
|
11
6
|
var fs = require("fs");
|
|
12
7
|
var path = require("path");
|
|
@@ -124,37 +119,7 @@ function parse(text) {
|
|
|
124
119
|
|| (typeof r.startTicks === "string" && /^\d+$/.test(r.startTicks) && typeof r.bootId === "string" && /^[0-9a-f-]{36}$/.test(r.bootId))) ? r : null;
|
|
125
120
|
}
|
|
126
121
|
|
|
127
|
-
/** { ok: true, why } or { ok: false, reason }. deps (tests): env, processStart. */
|
|
128
|
-
function admit(pkgDir, deps) {
|
|
129
|
-
var d = deps || {};
|
|
130
|
-
var env = d.env || process.env;
|
|
131
|
-
var token = env[TOKEN_ENV] || "";
|
|
132
|
-
var real;
|
|
133
|
-
try { real = fs.realpathSync(pkgDir); } catch (e) { real = pkgDir; }
|
|
134
|
-
var prefix = globalPrefix(real);
|
|
135
|
-
if (!prefix) return token ? { ok: false, reason: "an AgEnD install token was presented, but this is not a global install it could belong to" } : { ok: true, why: "not a global install" };
|
|
136
|
-
try { prefix = fs.realpathSync(prefix); } catch (e) { /* as derived */ }
|
|
137
|
-
var lockPath = path.join(prefix, LOCK);
|
|
138
|
-
var text = null;
|
|
139
|
-
try { text = fs.readFileSync(lockPath, "utf8"); } catch (e) {
|
|
140
|
-
if (e.code !== "ENOENT") return { ok: false, reason: "the install lock " + lockPath + " cannot be read (" + e.code + ")" };
|
|
141
|
-
}
|
|
142
|
-
if (text === null) {
|
|
143
|
-
return token ? { ok: false, reason: "an AgEnD install token was presented, but no update holds " + lockPath + " (a late or stale token)" } : { ok: true, why: "no lock: an external install" };
|
|
144
|
-
}
|
|
145
|
-
var lock = parse(text);
|
|
146
|
-
if (!lock) return { ok: false, reason: "the install lock " + lockPath + " cannot be read as a lock; if no `agend update` is running, remove it and retry" };
|
|
147
|
-
var state = holderState(lock, d);
|
|
148
|
-
if (state === "unknown") return { ok: false, reason: "whether the AgEnD update that holds " + lockPath + " (pid " + lock.pid + ") is still running cannot be told; retry" };
|
|
149
|
-
if (state === "stale") {
|
|
150
|
-
return token ? { ok: false, reason: "the AgEnD update that held " + lockPath + " is gone, so its token cannot authorise this install" } : { ok: true, why: "a stale lock and no token: an external install" };
|
|
151
|
-
}
|
|
152
|
-
if (lock.prefix !== prefix) return { ok: false, reason: "the install lock " + lockPath + " names another prefix (" + lock.prefix + ")" };
|
|
153
|
-
if (token && token === lock.token) return { ok: true, why: "the updater's own install" };
|
|
154
|
-
return { ok: false, reason: "an `agend update` (pid " + lock.pid + ") is installing into " + prefix + " right now; wait for it, then retry" };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
122
|
module.exports = {
|
|
158
|
-
LOCK: LOCK, TOKEN_ENV: TOKEN_ENV,
|
|
123
|
+
LOCK: LOCK, TOKEN_ENV: TOKEN_ENV, globalPrefix: globalPrefix, processStart: processStart, startTicks: startTicks, bootId: bootId,
|
|
159
124
|
sameStartText: sameStartText, START_TOLERANCE_MS: START_TOLERANCE_MS, exists: exists, holderState: holderState, parse: parse,
|
|
160
125
|
};
|