@songsid/agend 2.3.0-beta.8 → 2.3.0
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 +16 -8
- package/README.zh-TW.md +16 -8
- package/dist/adapter-world.js.map +1 -1
- package/dist/agend-home.d.ts +5 -0
- package/dist/agend-home.js +7 -0
- package/dist/agend-home.js.map +1 -0
- package/dist/agent-cli-instructions.md +9 -2
- package/dist/agent-cli.js +44 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +9 -0
- package/dist/agent-endpoint.js +23 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.js +1 -1
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.d.ts +16 -0
- package/dist/agent-whats-new.js +54 -11
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/apply-job.js.map +1 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +12 -0
- package/dist/auth-api.js +77 -22
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +4 -0
- package/dist/authority/authority-state.js +17 -8
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/authority/web-authority.d.ts +16 -8
- package/dist/authority/web-authority.js +19 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.d.ts +2 -2
- package/dist/backend/antigravity.js +2 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js +13 -4
- 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-package.d.ts +30 -0
- package/dist/backend/codex-package.js +133 -0
- package/dist/backend/codex-package.js.map +1 -0
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +25 -5
- 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/effort-metadata.js.map +1 -1
- package/dist/backend/grok.js +58 -3
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.js.map +1 -1
- package/dist/backend/kiro-kas-store.js.map +1 -1
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro-v3-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +9 -1
- package/dist/backend/kiro.js +102 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/mock.js.map +1 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/opencode-permission.js +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js +2 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/session-signals.js.map +1 -1
- package/dist/backend-availability.js.map +1 -1
- package/dist/backend-command.d.ts +2 -0
- package/dist/backend-command.js +3 -1
- package/dist/backend-command.js.map +1 -1
- package/dist/backend-outage.js.map +1 -1
- package/dist/binary-probe.js +1 -1
- package/dist/binary-probe.js.map +1 -1
- package/dist/bug-report-cli.d.ts +30 -0
- package/dist/bug-report-cli.js +139 -0
- package/dist/bug-report-cli.js.map +1 -0
- package/dist/bug-report.d.ts +47 -0
- package/dist/bug-report.js +137 -0
- package/dist/bug-report.js.map +1 -0
- package/dist/bundled-skills.d.ts +18 -0
- package/dist/bundled-skills.js +38 -0
- package/dist/bundled-skills.js.map +1 -0
- package/dist/cache-service.js +1 -1
- 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 +4 -0
- package/dist/channel/adapters/discord.js +57 -12
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +6 -0
- package/dist/channel/adapters/telegram.js +54 -14
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-server.js +21 -4
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +3 -1
- package/dist/channel/mcp-tools.js +43 -6
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.js.map +1 -1
- package/dist/channel/sticker-download.js.map +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/child-process.d.ts +21 -0
- package/dist/child-process.js +54 -0
- package/dist/child-process.js.map +1 -0
- package/dist/classic-channel-manager.js +3 -2
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli-entry.js.map +1 -1
- package/dist/cli-flags.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +443 -311
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.js.map +1 -1
- package/dist/completion-install.js.map +1 -1
- package/dist/completion.js.map +1 -1
- package/dist/config-validator.d.ts +6 -0
- package/dist/config-validator.js +81 -8
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +15 -3
- package/dist/config.js +41 -10
- package/dist/config.js.map +1 -1
- package/dist/connection-secrets.js.map +1 -1
- package/dist/context-guardian.js.map +1 -1
- package/dist/coredump-filter.js.map +1 -1
- package/dist/cpu-profile.js.map +1 -1
- package/dist/cross-instance-envelope.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 +39 -1
- package/dist/daemon.js +234 -15
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.js.map +1 -1
- package/dist/darwin-memory.js +1 -1
- package/dist/darwin-memory.js.map +1 -1
- package/dist/dashboard-live.d.ts +8 -0
- package/dist/dashboard-live.js +35 -0
- package/dist/dashboard-live.js.map +1 -0
- package/dist/dashboard-permissions.d.ts +69 -0
- package/dist/dashboard-permissions.js +138 -0
- package/dist/dashboard-permissions.js.map +1 -0
- 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-consumed-watch.js.map +1 -1
- package/dist/delivery-outbox.js +5 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/delivery-reconciliation.test.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/doctor.d.ts +8 -0
- package/dist/doctor.js +52 -0
- package/dist/doctor.js.map +1 -1
- package/dist/e2e-isolation.js.map +1 -1
- package/dist/event-log.js.map +1 -1
- package/dist/export-import.js +7 -5
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-label.js.map +1 -1
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-lock.js +3 -2
- package/dist/fleet-lock.js.map +1 -1
- package/dist/fleet-manager.d.ts +258 -15
- package/dist/fleet-manager.js +1605 -304
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +18 -0
- package/dist/fleet-process-handlers.js +70 -0
- package/dist/fleet-process-handlers.js.map +1 -0
- package/dist/fleet-startup.d.ts +14 -0
- package/dist/fleet-startup.js +30 -0
- package/dist/fleet-startup.js.map +1 -0
- package/dist/fleet-traffic.d.ts +36 -0
- package/dist/fleet-traffic.js +100 -0
- package/dist/fleet-traffic.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +0 -20
- package/dist/forged-envelope-scan.js +3 -2
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/full-restart.d.ts +5 -5
- package/dist/full-restart.js +5 -5
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/bug-report/SKILL.md +31 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +52 -11
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +5 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +3 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +8 -9
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- 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.canvas-worker-off.md +321 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +28 -4
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +553 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +170 -35
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/general-knowledge/steering/core-rules.md +1 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/idle-gate-log.d.ts +19 -0
- package/dist/idle-gate-log.js +15 -0
- package/dist/idle-gate-log.js.map +1 -0
- package/dist/instance-config-impact.js +25 -4
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -1
- package/dist/instance-lifecycle.js +86 -25
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +12 -6
- package/dist/instructions.js +19 -10
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.js.map +1 -1
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-log-gate.js.map +1 -1
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/locale.js +428 -20
- package/dist/locale.js.map +1 -1
- package/dist/log-service.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/logger.js +3 -2
- package/dist/logger.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/login-flows.js +1 -1
- package/dist/login-manager.js.map +1 -1
- package/dist/login-shell.js +3 -1
- package/dist/login-shell.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 +83 -4
- package/dist/ls-rows.js.map +1 -1
- package/dist/memory-pressure.js.map +1 -1
- package/dist/muse-usage-relay.js.map +1 -1
- package/dist/needs-you-hub.d.ts +98 -1
- package/dist/needs-you-hub.js +313 -44
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +5 -2
- package/dist/needs-you-live.js +6 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +68 -3
- package/dist/needs-you.js +38 -4
- package/dist/needs-you.js.map +1 -1
- package/dist/network-family.js.map +1 -1
- package/dist/operator-control-paths.js +3 -4
- package/dist/operator-control-paths.js.map +1 -1
- package/dist/outbound-handlers.d.ts +3 -0
- package/dist/outbound-handlers.js +76 -19
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +112 -64
- package/dist/outbound-schemas.js +39 -12
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/outbox-open-error.js.map +1 -1
- package/dist/pane-context-cache.js.map +1 -1
- package/dist/paths.js.map +1 -1
- package/dist/private-dir.js.map +1 -1
- package/dist/probe-worker-pool.js.map +1 -1
- package/dist/profile-control.js.map +1 -1
- package/dist/prompt-file-ref.js.map +1 -1
- package/dist/provider-probe.js.map +1 -1
- package/dist/provider-secret-registry.d.ts +5 -3
- package/dist/provider-secret-registry.js +8 -2
- package/dist/provider-secret-registry.js.map +1 -1
- package/dist/public-link-auto-open.d.ts +32 -0
- package/dist/public-link-auto-open.js +86 -0
- package/dist/public-link-auto-open.js.map +1 -0
- package/dist/public-link-owner.d.ts +10 -0
- package/dist/public-link-owner.js +36 -0
- package/dist/public-link-owner.js.map +1 -0
- package/dist/public-link-progress.d.ts +12 -1
- package/dist/public-link-progress.js +5 -10
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +11 -0
- package/dist/public-tunnel-config.js +68 -0
- package/dist/public-tunnel-config.js.map +1 -0
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +19 -6
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +48 -3
- package/dist/public-web-link.js +177 -51
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +9 -0
- package/dist/quickstart-api.js +82 -0
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +5 -0
- package/dist/quickstart.js +40 -25
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-dedup.js.map +1 -1
- package/dist/resource-report.js +1 -1
- package/dist/resource-report.js.map +1 -1
- package/dist/restart-guard.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.js.map +1 -1
- package/dist/runtime-cpu-profile.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/scheduler.js.map +1 -1
- package/dist/scheduler/task-board-text.d.ts +26 -0
- package/dist/scheduler/task-board-text.js +140 -0
- package/dist/scheduler/task-board-text.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +730 -0
- package/dist/scheduler/task-board.js +2179 -0
- package/dist/scheduler/task-board.js.map +1 -0
- package/dist/scheduler/types.d.ts +6 -1
- package/dist/sd-notify.js +2 -1
- package/dist/sd-notify.js.map +1 -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/secret-file.js.map +1 -1
- package/dist/secret-store.d.ts +2 -0
- package/dist/secret-store.js +21 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/service-installer.d.ts +75 -0
- package/dist/service-installer.js +206 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +44 -0
- package/dist/service-owner.js +48 -0
- package/dist/service-owner.js.map +1 -0
- package/dist/service-plan.d.ts +39 -2
- package/dist/service-plan.js +188 -6
- package/dist/service-plan.js.map +1 -1
- package/dist/session-oom.d.ts +21 -0
- package/dist/session-oom.js +139 -0
- package/dist/session-oom.js.map +1 -0
- package/dist/settings-api.d.ts +13 -0
- package/dist/settings-api.js +73 -12
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.js.map +1 -1
- package/dist/settings-change.d.ts +5 -0
- package/dist/settings-change.js +167 -6
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +9 -1
- package/dist/settings-confirmation.js +18 -8
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-control.js +2 -1
- package/dist/settings-control.js.map +1 -1
- package/dist/settings-effect.js +29 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +13 -0
- package/dist/settings-http-confirmation.js +15 -3
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-instance-patch.js +13 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/settings-request-capability.d.ts +4 -1
- package/dist/settings-request-capability.js +11 -3
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/settings-transaction.js.map +1 -1
- package/dist/setup-auth.d.ts +1 -0
- package/dist/setup-auth.js +2 -1
- package/dist/setup-auth.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/setup-host.d.ts +6 -1
- package/dist/setup-host.js +15 -4
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +6 -3
- package/dist/setup-wizard.js.map +1 -1
- package/dist/spawn-gate.d.ts +5 -0
- package/dist/spawn-gate.js +28 -8
- package/dist/spawn-gate.js.map +1 -1
- package/dist/start-attempt.d.ts +1 -0
- package/dist/start-attempt.js +18 -0
- package/dist/start-attempt.js.map +1 -0
- package/dist/start-failure-notice.d.ts +4 -0
- package/dist/start-failure-notice.js +20 -0
- package/dist/start-failure-notice.js.map +1 -0
- package/dist/start-failure.d.ts +147 -0
- package/dist/start-failure.js +281 -0
- package/dist/start-failure.js.map +1 -0
- package/dist/startup-memory-deadline.d.ts +23 -0
- package/dist/startup-memory-deadline.js +55 -0
- package/dist/startup-memory-deadline.js.map +1 -0
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/storm-window.js.map +1 -1
- package/dist/systemd-peer-bus.js.map +1 -1
- package/dist/target-queue-worker.js.map +1 -1
- package/dist/task-board-summary.d.ts +101 -0
- package/dist/task-board-summary.js +177 -0
- package/dist/task-board-summary.js.map +1 -0
- package/dist/task-board-tool.d.ts +25 -0
- package/dist/task-board-tool.js +155 -0
- package/dist/task-board-tool.js.map +1 -0
- package/dist/task-board-waker.d.ts +51 -0
- package/dist/task-board-waker.js +141 -0
- package/dist/task-board-waker.js.map +1 -0
- package/dist/tips.js.map +1 -1
- package/dist/tmux-control.js +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +104 -0
- package/dist/tmux-exec-helper.js +412 -0
- package/dist/tmux-exec-helper.js.map +1 -0
- package/dist/tmux-manager.d.ts +1 -0
- package/dist/tmux-manager.js +29 -13
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.js +2 -2
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.js +9 -42
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-archiver.js.map +1 -1
- package/dist/topic-commands.d.ts +13 -0
- package/dist/topic-commands.js +56 -33
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.js.map +1 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.js +5 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +2 -0
- package/dist/transcript-sources.js +4 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js +3 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -16
- package/dist/tunnel/cloudflared.js +100 -13
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/lease.js +3 -2
- package/dist/tunnel/lease.js.map +1 -1
- package/dist/tunnel/manager.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +13 -0
- package/dist/tunnel/named-diagnostics.js +52 -0
- package/dist/tunnel/named-diagnostics.js.map +1 -0
- package/dist/tunnel/public-fetch.js.map +1 -1
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.d.ts +4 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +55 -2
- package/dist/ui/app-effects.js +27 -5
- package/dist/ui/app.html +2 -0
- package/dist/ui/backend-signin.js +297 -0
- package/dist/ui/chat-effects.js +15 -7
- package/dist/ui/chat-msg-actions.js +176 -0
- package/dist/ui/chat-render.js +67 -20
- package/dist/ui/chat-store.js +218 -48
- package/dist/ui/chat-strings.js +42 -10
- package/dist/ui/chat-thread.js +249 -23
- package/dist/ui/effect-trial-controller.js +13 -8
- package/dist/ui/effect-trial-offer.js +2 -31
- package/dist/ui/effects-waker.js +2 -0
- package/dist/ui/fleet-canvas-layout.js +13 -4
- package/dist/ui/fleet-canvas.js +120 -43
- package/dist/ui/fleet-office-editor.js +149 -0
- package/dist/ui/fleet-office-model.js +358 -0
- package/dist/ui/fleet-office-pack.js +199 -0
- package/dist/ui/fleet-office-skin.js +474 -0
- package/dist/ui/fleet-office-store.js +49 -0
- package/dist/ui/fleet-office-zones.js +177 -0
- package/dist/ui/fleet-office.js +434 -0
- package/dist/ui/fleet-org.js +21 -8
- package/dist/ui/fleet-route-scene.js +272 -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 +482 -0
- package/dist/ui/fleet-task-work.js +107 -0
- package/dist/ui/fleet-tasks.js +569 -0
- package/dist/ui/fleet-traffic.js +227 -45
- package/dist/ui/fleet-usage.js +36 -2
- package/dist/ui/guest-refused.html +41 -0
- package/dist/ui/icons/icon-192.png +0 -0
- package/dist/ui/icons/icon-512.png +0 -0
- package/dist/ui/image-lightbox.js +45 -1
- package/dist/ui/image-zoom.js +242 -0
- package/dist/ui/panel-chat.js +282 -89
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +32 -71
- package/dist/ui/panel-needs.js +23 -6
- package/dist/ui/panel-settings.js +158 -32
- package/dist/ui/preview-panel.js +2 -1
- package/dist/ui/search-dialog.js +350 -0
- package/dist/ui/settings-appearance.js +170 -10
- package/dist/ui/settings-apply.js +3 -3
- package/dist/ui/settings-confirm.js +7 -2
- package/dist/ui/settings-dialogs.js +46 -21
- package/dist/ui/settings-discord.js +6 -2
- package/dist/ui/settings-guests.js +2 -1
- package/dist/ui/settings-learn.js +1 -0
- package/dist/ui/settings-model.js +51 -18
- package/dist/ui/settings-mods.js +8 -3
- package/dist/ui/settings-owner-effects.js +6 -4
- package/dist/ui/settings-strings.js +180 -6
- package/dist/ui/settings-token.js +3 -1
- package/dist/ui/settings-web-authority.js +13 -5
- package/dist/ui/settings-wizard.js +13 -7
- package/dist/ui/shared/agend-auth.js +3 -1
- package/dist/ui/shared/app-i18n.js +12 -5
- package/dist/ui/shared/app-needs.js +2 -2
- package/dist/ui/shared/app-prefs.js +15 -0
- package/dist/ui/shared/app-route.js +18 -0
- package/dist/ui/shared/app-shell.js +172 -28
- package/dist/ui/shared/app-store.js +3 -2
- package/dist/ui/shared/app-stream.js +7 -2
- package/dist/ui/shared/app-themes.js +116 -1
- package/dist/ui/shared/app.css +681 -54
- package/dist/ui/shared/app.js +9 -1
- package/dist/ui/shared/card-strings.js +4 -0
- package/dist/ui/shared/effect-app-runtime.js +7 -5
- package/dist/ui/shared/effect-chat-runtime.js +2 -7
- package/dist/ui/shared/effect-cursor.js +3 -2
- package/dist/ui/shared/effect-display-metadata.js +39 -0
- package/dist/ui/shared/effect-owner-source.js +8 -1
- package/dist/ui/shared/effect-policy.js +2 -2
- package/dist/ui/shared/effect-registry.js +30 -11
- package/dist/ui/shared/effect-runtime-loader.js +3 -1
- package/dist/ui/shared/effect-stock-snapshot.js +9 -0
- package/dist/ui/shared/effect-stock-trust.js +40 -9
- package/dist/ui/shared/effect-trial-source.js +1 -1
- package/dist/ui/shared/effect-trial-stage.js +27 -21
- package/dist/ui/shared/effect-trial.css +1 -1
- package/dist/ui/shared/feature-flags.js +127 -0
- package/dist/ui/shared/guest-refused.js +35 -0
- package/dist/ui/shared/header-tools.js +2 -2
- package/dist/ui/shared/identity-card.js +26 -8
- package/dist/ui/shared/identity-color.js +17 -10
- package/dist/ui/shared/instance-nav.js +183 -28
- package/dist/ui/shared/owner-css.js +30 -1
- package/dist/ui/shared/panel-view.js +14 -29
- package/dist/ui/shared/text-undo.js +210 -0
- package/dist/ui/shared/tokens.css +13 -3
- package/dist/ui/shared/ui-dialog.js +5 -4
- package/dist/ui/shared/ui-icons.js +4 -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 +206 -16
- package/dist/ui/shared/view-strings.js +68 -4
- package/dist/ui/shared/web-authorize.js +50 -1
- package/dist/ui/signin.html +2 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/ui/web-terminal/terminal.html +10 -10
- package/dist/ui/web-terminal/terminal.js +58 -12
- package/dist/update-check.js.map +1 -1
- package/dist/update-dispatch.js +1 -1
- package/dist/update-dispatch.js.map +1 -1
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +10 -1
- package/dist/update-launch.js +39 -14
- package/dist/update-launch.js.map +1 -1
- package/dist/update-marker.js.map +1 -1
- package/dist/update-preflight.js +1 -1
- package/dist/update-preflight.js.map +1 -1
- package/dist/update-progress.d.ts +3 -1
- package/dist/update-progress.js +3 -1
- package/dist/update-progress.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 +89 -2
- package/dist/usage/providers.js +247 -41
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/usage/usage-api.d.ts +5 -1
- package/dist/usage/usage-api.js +185 -58
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.js +1 -1
- package/dist/usage-rollup.js.map +1 -1
- 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 +7 -0
- package/dist/view-api.js +16 -4
- package/dist/view-api.js.map +1 -1
- package/dist/wake-coordinator.js.map +1 -1
- package/dist/web-ambient.js +9 -2
- package/dist/web-ambient.js.map +1 -1
- package/dist/web-api.d.ts +58 -10
- package/dist/web-api.js +1104 -71
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon-worker.d.ts +1 -0
- package/dist/web-app-icon-worker.js +16 -0
- package/dist/web-app-icon-worker.js.map +1 -0
- package/dist/web-app-icon.d.ts +109 -0
- package/dist/web-app-icon.js +257 -0
- package/dist/web-app-icon.js.map +1 -0
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +1 -0
- package/dist/web-appearance-tools.js +7 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +4 -10
- package/dist/web-appearance.js +7 -4
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-auth.d.ts +8 -4
- package/dist/web-auth.js +33 -21
- 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.js.map +1 -1
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +118 -8
- package/dist/web-chat-history.js +220 -25
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +5 -1
- package/dist/web-commands.js +1 -1
- package/dist/web-commands.js.map +1 -1
- package/dist/web-cookie-names.d.ts +5 -0
- package/dist/web-cookie-names.js +12 -0
- package/dist/web-cookie-names.js.map +1 -0
- package/dist/web-effect-bundle.d.ts +2 -2
- package/dist/web-effect-library.js.map +1 -1
- package/dist/web-effect-playback.js.map +1 -1
- package/dist/web-effect-profiles.d.ts +16 -16
- package/dist/web-effect-profiles.js +3 -2
- package/dist/web-effect-profiles.js.map +1 -1
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.js.map +1 -1
- package/dist/web-effect.d.ts +6 -6
- package/dist/web-effect.js.map +1 -1
- package/dist/web-experimental-http.d.ts +2 -0
- package/dist/web-experimental-http.js +12 -0
- package/dist/web-experimental-http.js.map +1 -0
- package/dist/web-experimental.d.ts +11 -0
- package/dist/web-experimental.js +49 -0
- package/dist/web-experimental.js.map +1 -0
- package/dist/web-guest-access.d.ts +4 -2
- package/dist/web-guest-access.js +22 -10
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-guest-rooms.js.map +1 -1
- 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-icons.d.ts +33 -1
- package/dist/web-icons.js +83 -11
- package/dist/web-icons.js.map +1 -1
- package/dist/web-identity-color.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-images.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.js.map +1 -1
- package/dist/web-mentions.js.map +1 -1
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -0
- package/dist/web-mod-store.js +10 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-office-store.d.ts +104 -0
- package/dist/web-office-store.js +236 -0
- package/dist/web-office-store.js.map +1 -0
- 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-owner-css.js.map +1 -1
- package/dist/web-png.d.ts +33 -0
- package/dist/web-png.js +329 -0
- package/dist/web-png.js.map +1 -0
- package/dist/web-prefs.d.ts +6 -0
- package/dist/web-prefs.js +29 -3
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +2 -0
- package/dist/web-preview.js +2 -0
- package/dist/web-preview.js.map +1 -1
- package/dist/web-profile.js.map +1 -1
- package/dist/web-reactions.d.ts +35 -0
- package/dist/web-reactions.js +47 -0
- package/dist/web-reactions.js.map +1 -0
- package/dist/web-request-context.d.ts +5 -0
- package/dist/web-request-context.js.map +1 -1
- package/dist/web-returning-device.d.ts +4 -2
- package/dist/web-returning-device.js +13 -6
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-schedules.js.map +1 -1
- package/dist/web-send-dedupe.d.ts +36 -0
- package/dist/web-send-dedupe.js +69 -0
- package/dist/web-send-dedupe.js.map +1 -0
- package/dist/web-session-authorization.js.map +1 -1
- package/dist/web-session.js +1 -1
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +11 -5
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +6 -0
- package/dist/web-settings-tools.js +52 -17
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal-http.js +15 -2
- package/dist/web-terminal-http.js.map +1 -1
- package/dist/web-terminal.js +1 -1
- package/dist/web-terminal.js.map +1 -1
- package/dist/web-theme-store.d.ts +2 -2
- package/dist/web-theme-store.js +6 -3
- package/dist/web-theme-store.js.map +1 -1
- package/dist/web-theme.d.ts +29 -0
- package/dist/web-theme.js +89 -1
- package/dist/web-theme.js.map +1 -1
- package/dist/web-upload.d.ts +18 -1
- package/dist/web-upload.js +45 -7
- package/dist/web-upload.js.map +1 -1
- package/dist/web-widget.js.map +1 -1
- package/dist/webhook-emitter.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/ws-server.js.map +1 -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 +828 -0
- package/launcher/install-admission.cjs +4 -39
- package/launcher/postinstall.cjs +14 -12
- package/launcher/private-home.cjs +43 -0
- package/package.json +16 -13
- 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,828 @@
|
|
|
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
|
+
var ensurePrivateHome = require("./private-home.cjs").ensurePrivateHome;
|
|
28
|
+
|
|
29
|
+
function defaultDeps() {
|
|
30
|
+
return {
|
|
31
|
+
run: function (cmd, args, opts) {
|
|
32
|
+
var o = opts || {};
|
|
33
|
+
var r = childProcess.spawnSync(cmd, args, { encoding: "utf8", stdio: o.inherit ? "inherit" : ["ignore", "pipe", "pipe"], env: o.env || process.env, timeout: o.timeoutMs });
|
|
34
|
+
return { status: r.status, signal: r.signal, stdout: r.stdout || "", stderr: r.stderr || "", error: r.error ? String(r.error.code || r.error.message) : null };
|
|
35
|
+
},
|
|
36
|
+
log: function (line) { process.stdout.write(line + "\n"); },
|
|
37
|
+
pid: process.pid,
|
|
38
|
+
alive: function (pid) { try { process.kill(pid, 0); return true; } catch (e) { return e.code === "EPERM"; } },
|
|
39
|
+
uid: typeof process.getuid === "function" ? process.getuid() : -1,
|
|
40
|
+
env: process.env,
|
|
41
|
+
now: function () { return Date.now(); },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readJson(file) { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch (e) { return null; } }
|
|
46
|
+
function last(text) { var lines = String(text || "").trim().split("\n"); return (lines[lines.length - 1] || "").trim(); }
|
|
47
|
+
/** A whole AgEnD package in `dir` — its package.json and every bin it names — gives its version; a partial one, null. */
|
|
48
|
+
function isAgendPackage(dir) {
|
|
49
|
+
var p = readJson(path.join(dir, "package.json"));
|
|
50
|
+
if (!p || p.name !== "@songsid/agend" || typeof p.version !== "string" || !p.bin || typeof p.bin !== "object") return null;
|
|
51
|
+
var bins = Object.keys(p.bin).map(function (k) { return p.bin[k]; });
|
|
52
|
+
return bins.length && bins.every(function (b) { return typeof b === "string" && fs.existsSync(path.join(dir, b)); }) ? p.version : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The update marker the fleet edits its chat message from (src/update-marker.ts) — written only while this installer
|
|
57
|
+
* owns it (#2264 review). It is claimed once this installer holds the prefix: when the `agend update` that started it
|
|
58
|
+
* wrote it (AGEND_UPDATE_OWNER_PID), or when its writer is gone (an update that died: the user's stuck "downloading").
|
|
59
|
+
* A marker another live process writes — a running update's, a new fleet's — is never changed. Every write re-reads it
|
|
60
|
+
* and writes only while this installer is still its writer (a new update or a new fleet that took it over keeps it).
|
|
61
|
+
*/
|
|
62
|
+
function claimMarker(home, deps, opts) {
|
|
63
|
+
var marker = readJson(path.join(home, MARKER));
|
|
64
|
+
if (!marker || !marker.progress || typeof marker.pid !== "number") return null;
|
|
65
|
+
var owner = Number(deps.env.AGEND_UPDATE_OWNER_PID) || 0;
|
|
66
|
+
var ours = marker.pid === deps.pid || (owner > 0 && marker.pid === owner);
|
|
67
|
+
if (!ours && (opts.ownOnly || deps.alive(marker.pid))) return null;
|
|
68
|
+
return writeMarker(home, marker, deps);
|
|
69
|
+
}
|
|
70
|
+
function writeMarker(home, marker, deps) {
|
|
71
|
+
var file = path.join(home, MARKER);
|
|
72
|
+
marker.pid = deps.pid;
|
|
73
|
+
var tmp = file + "." + deps.pid + ".tmp";
|
|
74
|
+
try { fs.writeFileSync(tmp, JSON.stringify(marker), { mode: 384 }); fs.renameSync(tmp, file); return true; } catch (e) { return false; }
|
|
75
|
+
}
|
|
76
|
+
function setStage(home, claimed, stage, details, deps) {
|
|
77
|
+
if (!claimed) return false;
|
|
78
|
+
var marker = readJson(path.join(home, MARKER));
|
|
79
|
+
if (!marker || !marker.progress || marker.pid !== deps.pid) return false;
|
|
80
|
+
var previous = marker.progress.stage;
|
|
81
|
+
marker.progress.stage = stage;
|
|
82
|
+
if (details.version) marker.progress.version = details.version;
|
|
83
|
+
if (details.error) marker.progress.error = String(details.error).replace(/\s+/g, " ").trim().slice(0, 300);
|
|
84
|
+
if (stage === "failed") marker.progress.failedStage = previous;
|
|
85
|
+
return writeMarker(home, marker, deps);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Is npm provably idle on this host — is no npm process changing installed packages? Only an npm that installs,
|
|
90
|
+
* uninstalls, updates, links or rebuilds can be reifying this prefix. `npm exec` (this installer's own ancestor; AgEnD
|
|
91
|
+
* fleets run their MCP servers that way, for hours), `npm view`, `npm run` cannot, so they do not count. npm rewrites its
|
|
92
|
+
* process title to `npm <command> <args>` without its flags, but argv as started shows them, and a flag is read the way
|
|
93
|
+
* npm reads it (Prism r2): one that takes a value consumes the next word whatever it is (`npm --prefix run install` is an
|
|
94
|
+
* install); one that takes none consumes nothing; an unknown one may be either, and both readings are kept. The npm
|
|
95
|
+
* counts as idle only if every reading's command is one that cannot reify; an unknown word as the command is busy. A
|
|
96
|
+
* process list that cannot be read (ps failed, a row that does not parse, this process missing from it) is not idle.
|
|
97
|
+
*/
|
|
98
|
+
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)$/;
|
|
99
|
+
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)$/;
|
|
100
|
+
// npm's flags that take a value (npm 10 config definitions) and the common ones that take none.
|
|
101
|
+
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)$/;
|
|
102
|
+
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))$/;
|
|
103
|
+
function isNpmCommand(word) { return NPM_CHANGES.test(word) || NPM_READS.test(word); }
|
|
104
|
+
function npmCommands(args) {
|
|
105
|
+
var words = args.split(/\s+/).filter(function (w) { return w !== ""; });
|
|
106
|
+
var at = -1;
|
|
107
|
+
for (var k = 0; k < words.length && k < 3; k++) {
|
|
108
|
+
if (/(?:^|\/)(?:npm|npx)$/.test(words[k]) || /(?:^|\/)(?:npm|npx)-cli\.js$/.test(words[k])) { at = k; break; }
|
|
109
|
+
}
|
|
110
|
+
if (at < 0) return null;
|
|
111
|
+
if (/npx(?:-cli\.js)?$/.test(words[at])) return ["exec"];
|
|
112
|
+
// Every command this argv can mean: `pending` = the next word may be an unknown flag's value.
|
|
113
|
+
var readings = function (from, pending) {
|
|
114
|
+
for (var w = from; w < words.length; w++) {
|
|
115
|
+
var word = words[w];
|
|
116
|
+
if (word.charAt(0) === "-") {
|
|
117
|
+
if (word.indexOf("=") >= 0 || NPM_BOOLEAN_FLAGS.test(word)) { pending = false; continue; }
|
|
118
|
+
if (NPM_VALUE_FLAGS.test(word)) { w++; pending = false; continue; } // its value, whatever it says
|
|
119
|
+
pending = true; // unknown: a value may follow, or not
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
var asCommand = isNpmCommand(word) ? [word] : ["?"];
|
|
123
|
+
return pending ? asCommand.concat(readings(w + 1, false)) : asCommand;
|
|
124
|
+
}
|
|
125
|
+
return pending ? [] : ["?"];
|
|
126
|
+
};
|
|
127
|
+
var all = readings(at + 1, false);
|
|
128
|
+
return all.length ? all : ["?"];
|
|
129
|
+
}
|
|
130
|
+
function npmIdleFromProcessList(output, ownPid) {
|
|
131
|
+
var rows = String(output).trim().split("\n").map(function (line) {
|
|
132
|
+
var m = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line);
|
|
133
|
+
return m ? { pid: Number(m[1]), parent: Number(m[2]), args: m[3] } : null;
|
|
134
|
+
});
|
|
135
|
+
if (!rows.length || rows.some(function (row) { return !row; })) return false;
|
|
136
|
+
if (!rows.some(function (row) { return row.pid === ownPid; })) return false;
|
|
137
|
+
return !rows.some(function (row) {
|
|
138
|
+
var commands = npmCommands(row.args);
|
|
139
|
+
return commands !== null && !commands.every(function (c) { return NPM_READS.test(c); });
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function npmIdle(deps) {
|
|
143
|
+
var r = deps.run("ps", ["-A", "-o", "pid=,ppid=,args="], { timeoutMs: 5000 });
|
|
144
|
+
return r.status === 0 && npmIdleFromProcessList(r.stdout, deps.pid);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Move a leftover into `<prefix>/.agend-quarantine/<time>-<pid>/` — kept as evidence, never deleted here. */
|
|
148
|
+
function quarantine(prefix, source, deps, name) {
|
|
149
|
+
var dir = path.join(prefix, ".agend-quarantine", new Date(deps.now()).toISOString().replace(/[:.]/g, "-") + "-" + deps.pid);
|
|
150
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
151
|
+
var to = path.join(dir, name || path.basename(source));
|
|
152
|
+
fs.renameSync(source, to);
|
|
153
|
+
return to;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readText(file) { try { return fs.readFileSync(file, "utf8"); } catch (e) { return null; } }
|
|
157
|
+
/** Create `file` holding `text` only if it does not exist: written aside, then linked into place (whole or not at all). */
|
|
158
|
+
function createWhole(file, text, deps) {
|
|
159
|
+
var tmp = file + "." + deps.pid + ".tmp";
|
|
160
|
+
fs.writeFileSync(tmp, text, { mode: 384 });
|
|
161
|
+
try { fs.linkSync(tmp, file); return true; } catch (e) { return false; } finally { try { fs.unlinkSync(tmp); } catch (e) { /* gone */ } }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The lock of updaters from before #2264 (src/install-lock.ts acquireInstallLock), which they alone understand — so it is
|
|
166
|
+
* kept by their own rules (Prism r3): a record in their format (pid + start time, read by install-admission.cjs, so a
|
|
167
|
+
* running old updater sees this installer as a live holder and waits); a holder judged as they judge it (live, stale or
|
|
168
|
+
* unknown — unknown blocks); and a stale lock moved only by the holder of `<lock>.reclaim` (claimed whole), under which
|
|
169
|
+
* the lock can change only by its own holder — proven gone. Nothing is ever moved aside to be judged afterwards. One
|
|
170
|
+
* thing more than they do: a lock that cannot be read (created, never filled: the user's, cut short) — their
|
|
171
|
+
* 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
|
|
172
|
+
* open(wx)). So it counts as stale only when it is proven that no process has it open (openedBy); its age is no evidence
|
|
173
|
+
* (Prism r4: an old writer paused between open and write for a minute is still admitted when it resumes).
|
|
174
|
+
*/
|
|
175
|
+
var lockReaders = require("./install-admission.cjs");
|
|
176
|
+
/**
|
|
177
|
+
* Does any process have `file` open? true / false, or null when that cannot be told — which blocks. Only processes of the
|
|
178
|
+
* file's owner can have created it and hold it open to write (its creator is its owner). Linux: every such process's
|
|
179
|
+
* /proc/<pid>/fd, matched by device and inode. A process of the same user whose descriptors cannot be read is one that
|
|
180
|
+
* made itself non-dumpable (sd-pam, ssh-agent, gpg-agent); Node never does, so it is an old updater only if its command
|
|
181
|
+
* line names node or agend — then, and when even that cannot be read, null. macOS: lsof (exit 1 with no output = none).
|
|
182
|
+
* Elsewhere: null.
|
|
183
|
+
*/
|
|
184
|
+
/** What a descriptor's link names when it is no file on disk: it cannot be the lock. */
|
|
185
|
+
var NOT_A_FILE = /^(?:(?:pipe|socket):\[\d+\]|anon_inode:.*|\/dev\/(?:null|zero|u?random|full|tty\w*|console|ptmx|pts\/\d+))$/;
|
|
186
|
+
/** Why the last openedBy answered as it did (a refusal names it, so a stuck host says exactly what could not be read). */
|
|
187
|
+
var openedWhy = "";
|
|
188
|
+
function openedBy(file, deps) {
|
|
189
|
+
openedWhy = "";
|
|
190
|
+
var unknown = function (why) { openedWhy = why; return null; };
|
|
191
|
+
var because = function (e, what) { return (e && e.code ? e.code : String(e)) + " on " + what; };
|
|
192
|
+
if (deps.openedBy) return deps.openedBy(file);
|
|
193
|
+
// Only a definite "it is gone" (ENOENT) may be skipped; a permission, I/O or any other error proves nothing (Prism r5:
|
|
194
|
+
// /proc/<pid>/fd is behind ptrace checks) → unknown, which blocks.
|
|
195
|
+
var stat = deps.procStat || fs.statSync;
|
|
196
|
+
var readdir = deps.procReaddir || fs.readdirSync;
|
|
197
|
+
var gone = function (e) { return e && e.code === "ENOENT"; };
|
|
198
|
+
var st;
|
|
199
|
+
try { st = stat(file); } catch (e) { return gone(e) ? false : unknown(because(e, file)); }
|
|
200
|
+
if (process.platform === "linux") {
|
|
201
|
+
var pids;
|
|
202
|
+
try { pids = fs.readdirSync("/proc").filter(function (n) { return /^\d+$/.test(n); }); } catch (e) { return unknown(because(e, "/proc")); }
|
|
203
|
+
for (var k = 0; k < pids.length; k++) {
|
|
204
|
+
var owner;
|
|
205
|
+
try { owner = stat("/proc/" + pids[k]).uid; } catch (e) { if (gone(e)) continue; return unknown(because(e, "/proc/" + pids[k])); } // gone meanwhile, or unknown
|
|
206
|
+
if (owner !== st.uid) continue;
|
|
207
|
+
var fds;
|
|
208
|
+
// A process this one may not inspect (EACCES: non-dumpable, or other credentials — the kernel's ptrace check on
|
|
209
|
+
// its descriptors) is not an old updater, a plain node process of this user, unless its command line says so.
|
|
210
|
+
var notInspectable = function (pid, what) {
|
|
211
|
+
var cmdline = null;
|
|
212
|
+
try { cmdline = fs.readFileSync("/proc/" + pid + "/cmdline", "utf8"); } catch (e2) { if (gone(e2)) return "gone"; }
|
|
213
|
+
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))) + ")";
|
|
214
|
+
return "skip";
|
|
215
|
+
};
|
|
216
|
+
try { fds = readdir("/proc/" + pids[k] + "/fd"); } catch (e) {
|
|
217
|
+
if (gone(e)) continue;
|
|
218
|
+
if (e.code !== "EACCES") return unknown(because(e, "/proc/" + pids[k] + "/fd"));
|
|
219
|
+
var verdict = notInspectable(pids[k], "/proc/" + pids[k] + "/fd");
|
|
220
|
+
if (verdict === "gone" || verdict === "skip") continue;
|
|
221
|
+
return unknown(verdict);
|
|
222
|
+
}
|
|
223
|
+
for (var f = 0; f < fds.length; f++) {
|
|
224
|
+
var seen;
|
|
225
|
+
var fdPath = "/proc/" + pids[k] + "/fd/" + fds[f];
|
|
226
|
+
try { seen = stat(fdPath); } catch (e) {
|
|
227
|
+
if (gone(e)) continue; // closed meanwhile
|
|
228
|
+
// Its target cannot be stat'ed (a security module, as on CI runners): the link itself still names it. A pipe,
|
|
229
|
+
// a socket, an anonymous inode or a terminal/null device is not the lock (a regular file in the prefix).
|
|
230
|
+
var target = null, linkError = null;
|
|
231
|
+
try { target = (deps.procReadlink || fs.readlinkSync)(fdPath); } catch (e2) { if (gone(e2)) continue; linkError = e2; }
|
|
232
|
+
if (target !== null && NOT_A_FILE.test(target)) continue;
|
|
233
|
+
if (target === null && e.code === "EACCES" && linkError && linkError.code === "EACCES") {
|
|
234
|
+
// Neither its target nor its link may be read: the process is not inspectable at all (CI runners have one).
|
|
235
|
+
var seenAs = notInspectable(pids[k], fdPath);
|
|
236
|
+
if (seenAs === "gone" || seenAs === "skip") break;
|
|
237
|
+
return unknown(seenAs);
|
|
238
|
+
}
|
|
239
|
+
return unknown(because(e, fdPath) + (target !== null ? " → " + target : ""));
|
|
240
|
+
}
|
|
241
|
+
if (seen.dev === st.dev && seen.ino === st.ino) { openedWhy = "pid " + pids[k] + " has it open"; return true; }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
if (process.platform === "darwin") {
|
|
247
|
+
var r = deps.run(fs.existsSync("/usr/sbin/lsof") ? "/usr/sbin/lsof" : "lsof", ["-nP", "-t", "--", file], { timeoutMs: 15000 });
|
|
248
|
+
if (r.status === 0 && String(r.stdout).trim()) { openedWhy = "pid " + String(r.stdout).trim().split("\n").join(", ") + " has it open"; return true; }
|
|
249
|
+
if (r.status === 1 && !String(r.stdout).trim() && !String(r.stderr).trim()) return false;
|
|
250
|
+
return unknown("lsof exited " + (r.status === null ? r.error || r.signal : r.status) + (String(r.stderr).trim() ? ": " + String(r.stderr).trim().slice(0, 120) : ""));
|
|
251
|
+
}
|
|
252
|
+
return unknown("no way to see open files on " + process.platform);
|
|
253
|
+
}
|
|
254
|
+
/** An unreadable lock that may still be being written: refused, saying what was seen. */
|
|
255
|
+
function beingWritten(lock, why) {
|
|
256
|
+
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";
|
|
257
|
+
}
|
|
258
|
+
function legacyRecord(prefix, deps, spec, home) {
|
|
259
|
+
var r = deps.lockReaders || lockReaders;
|
|
260
|
+
var ticks = r.startTicks(deps.pid), boot = r.bootId();
|
|
261
|
+
var record = { pid: deps.pid, processStart: r.processStart(deps.pid) || "" };
|
|
262
|
+
if (ticks !== null && boot !== null) { record.startTicks = ticks; record.bootId = boot; }
|
|
263
|
+
record.prefix = prefix; record.targetSpec = spec; record.agendHome = home;
|
|
264
|
+
record.token = require("crypto").randomBytes(16).toString("hex");
|
|
265
|
+
record.createdAt = new Date(deps.now()).toISOString();
|
|
266
|
+
return JSON.stringify(record) + "\n";
|
|
267
|
+
}
|
|
268
|
+
function legacyState(text, file, deps) {
|
|
269
|
+
var r = deps.lockReaders || lockReaders;
|
|
270
|
+
var holder = r.parse(text);
|
|
271
|
+
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 };
|
|
272
|
+
if (!fs.existsSync(file)) return { state: "gone" };
|
|
273
|
+
var open = openedBy(file, deps);
|
|
274
|
+
return { state: open === false ? "stale" : "unknown", unreadable: true, why: openedWhy };
|
|
275
|
+
}
|
|
276
|
+
/** Move a stale legacy lock away by their rule: only under `<lock>.reclaim`, and only the bytes judged. */
|
|
277
|
+
function reclaimLegacy(prefix, lock, judged, judgedState, record, deps) {
|
|
278
|
+
var marker = lock + ".reclaim";
|
|
279
|
+
if (!createWhole(marker, record, deps)) {
|
|
280
|
+
var other = (deps.lockReaders || lockReaders).parse(readText(marker) || "");
|
|
281
|
+
var otherState = other ? legacyState(readText(marker) || "", marker, deps).state : "unknown";
|
|
282
|
+
return otherState === "live" ? { refuse: "another install is replacing the stale lock on " + prefix + "; run this again in a moment" }
|
|
283
|
+
: { refuse: "a reclaim marker " + marker + " was left behind (pid " + (other ? other.pid : "unknown") + "); if no `agend update` runs, remove it and run this again" };
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
if (deps.onStep) deps.onStep("legacy-reclaimed");
|
|
287
|
+
// Under the marker the lock changes only by its own (gone) holder: still the one judged, else nothing is moved.
|
|
288
|
+
if (readText(lock) !== judged) return { changed: true };
|
|
289
|
+
// An unreadable one: still not open anywhere (no writer can open it again: theirs is open(wx) on a new file).
|
|
290
|
+
if (judgedState.unreadable && openedBy(lock, deps) !== false) return { refuse: beingWritten(lock, openedWhy) };
|
|
291
|
+
quarantine(prefix, lock, deps);
|
|
292
|
+
return { moved: true };
|
|
293
|
+
} finally {
|
|
294
|
+
if (readText(marker) === record) { try { fs.unlinkSync(marker); } catch (e) { /* gone */ } }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
var ENTRIES = ".agend-install.d";
|
|
299
|
+
function holderOf(text) {
|
|
300
|
+
var holder = null;
|
|
301
|
+
try { holder = JSON.parse(text); } catch (e) { holder = null; }
|
|
302
|
+
return holder && typeof holder.pid === "number" ? holder : null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Admission (#2264 review): this installer owns the prefix before anything in it is touched, and keeps it through the
|
|
307
|
+
* repair, the install, its retry and the rollback.
|
|
308
|
+
*
|
|
309
|
+
* Between these installers it is announce-then-check — nothing of another installer is ever moved to decide it (Prism
|
|
310
|
+
* r2: a takeover of a shared lock path can always move a newer owner's lock by a stale reader). Each installer writes an
|
|
311
|
+
* entry of its own, `<prefix>/.agend-install.d/<pid>-<random>` (whole, written aside and linked), and only then lists the
|
|
312
|
+
* others: one whose writer runs → it withdraws its own entry and refuses (75). Of two that race, at least the later one
|
|
313
|
+
* sees the earlier's entry, so two are never admitted (both may refuse: run again). An entry whose writer is gone has a
|
|
314
|
+
* name no one else will ever write, so it is moved to quarantine without a race.
|
|
315
|
+
*
|
|
316
|
+
* The admitted installer then also holds `<prefix>/.agend-install.lock`, the lock updaters from before #2264 take, by
|
|
317
|
+
* their rules (above): one of theirs that runs, or whose holder cannot be told → refuse; a stale one is moved to
|
|
318
|
+
* quarantine under their `.reclaim` marker. Its token is this installer's: every npm install it runs presents it
|
|
319
|
+
* (AGEND_INSTALL_TOKEN), because a release from before #2264 — a target installed by --spec, the previous version put
|
|
320
|
+
* back — admits its own postinstall only for the token of the live lock (#2286 review).
|
|
321
|
+
* Returns { release, notes, token } or { refuse }.
|
|
322
|
+
*/
|
|
323
|
+
function admit(prefix, deps, opts) {
|
|
324
|
+
opts = opts || {};
|
|
325
|
+
// The prefix as old updaters name it (realpath): one lock however the prefix was reached.
|
|
326
|
+
try { prefix = fs.realpathSync(prefix); } catch (e) { /* the writes below say */ }
|
|
327
|
+
var dir = path.join(prefix, ENTRIES);
|
|
328
|
+
var name = deps.pid + "-" + (deps.nonce ? deps.nonce() : require("crypto").randomBytes(8).toString("hex"));
|
|
329
|
+
var mine = path.join(dir, name);
|
|
330
|
+
var record = JSON.stringify({ pid: deps.pid, by: "agend-install", createdAt: new Date(deps.now()).toISOString() }) + "\n";
|
|
331
|
+
var notes = [];
|
|
332
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch (e) { /* the create below says */ }
|
|
333
|
+
if (!createWhole(mine, record, deps)) return { refuse: "cannot write an install entry in " + dir };
|
|
334
|
+
var withdraw = function () { if (readText(mine) === record) { try { fs.unlinkSync(mine); } catch (e) { /* gone */ } } };
|
|
335
|
+
if (deps.onStep) deps.onStep("announced");
|
|
336
|
+
var others = [];
|
|
337
|
+
try { others = fs.readdirSync(dir).filter(function (n) { return n !== name && !/\.tmp$/.test(n); }); } catch (e) { withdraw(); return { refuse: "cannot list " + dir }; }
|
|
338
|
+
for (var k = 0; k < others.length; k++) {
|
|
339
|
+
var holder = holderOf(readText(path.join(dir, others[k])));
|
|
340
|
+
if (holder && holder.pid !== deps.pid && deps.alive(holder.pid)) {
|
|
341
|
+
withdraw();
|
|
342
|
+
return { refuse: "another AgEnD install (pid " + holder.pid + ") is running in " + prefix + "; let it finish, then run this again" };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
others.forEach(function (n) {
|
|
346
|
+
// Gone (or written by no installer): its name is its writer's alone.
|
|
347
|
+
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 */ }
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
var lock = path.join(prefix, LOCK);
|
|
351
|
+
var legacy = legacyRecord(prefix, deps, opts.spec || "", opts.home || "");
|
|
352
|
+
for (var attempt = 0; attempt < 3; attempt++) {
|
|
353
|
+
if (createWhole(lock, legacy, deps)) {
|
|
354
|
+
return { notes: notes, token: JSON.parse(legacy).token, release: function () {
|
|
355
|
+
if (readText(lock) === legacy) { try { fs.unlinkSync(lock); } catch (e) { /* gone */ } }
|
|
356
|
+
withdraw();
|
|
357
|
+
} };
|
|
358
|
+
}
|
|
359
|
+
var text = readText(lock);
|
|
360
|
+
if (text === null) continue;
|
|
361
|
+
var judged = legacyState(text, lock, deps);
|
|
362
|
+
if (judged.state === "gone") continue;
|
|
363
|
+
if (judged.state !== "stale") {
|
|
364
|
+
withdraw();
|
|
365
|
+
return { refuse: judged.unreadable
|
|
366
|
+
? beingWritten(lock, judged.why)
|
|
367
|
+
: "another AgEnD install (pid " + judged.pid + ") is running in " + prefix + "; let it finish, then run this again" };
|
|
368
|
+
}
|
|
369
|
+
if (deps.onStep) deps.onStep("legacy-judged");
|
|
370
|
+
var taken = reclaimLegacy(prefix, lock, text, judged, legacy, deps);
|
|
371
|
+
if (taken.refuse) { withdraw(); return { refuse: taken.refuse }; }
|
|
372
|
+
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)");
|
|
373
|
+
}
|
|
374
|
+
withdraw();
|
|
375
|
+
return { refuse: "another AgEnD install keeps claiming " + lock + "; let it finish, then run this again" };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* What an interrupted install leaves, put right — only by the installer that owns the prefix (admit), and only while
|
|
380
|
+
* npm is provably idle (above); otherwise npm's leftovers are left alone. npm's retire sibling `@songsid/.agend-<hash>`
|
|
381
|
+
* (npm moves the installed package there before it writes the new one; killed in between, every later `npm install -g`
|
|
382
|
+
* fails ENOTEMPTY on it): the only whole copy of AgEnD is put back (a partial new package in its place goes to
|
|
383
|
+
* quarantine); any other sibling, and npm's retired bin links `bin/.agend-<hash>`, go to quarantine. The lock is not
|
|
384
|
+
* this function's: it never reads or moves one.
|
|
385
|
+
*/
|
|
386
|
+
function repair(root, prefix, deps) {
|
|
387
|
+
var notes = [];
|
|
388
|
+
var scope = path.join(root, "@songsid");
|
|
389
|
+
var current = path.join(scope, "agend");
|
|
390
|
+
var siblings = [];
|
|
391
|
+
try { siblings = fs.readdirSync(scope).filter(function (n) { return /^\.agend-/.test(n); }); } catch (e) { siblings = []; }
|
|
392
|
+
var binDir = path.join(prefix, "bin");
|
|
393
|
+
var binSiblings = [];
|
|
394
|
+
try { binSiblings = fs.readdirSync(binDir).filter(function (n) { return /^\.agend(-agent|-install)?-[A-Za-z0-9]{8}$/.test(n); }); } catch (e) { binSiblings = []; }
|
|
395
|
+
if (!siblings.length && !binSiblings.length) return { notes: notes, changed: false };
|
|
396
|
+
if (!(deps.npmIdle || npmIdle)(deps)) {
|
|
397
|
+
notes.push("another npm is running on this host, so what an earlier install left in " + scope + " was not touched");
|
|
398
|
+
return { notes: notes, changed: false };
|
|
399
|
+
}
|
|
400
|
+
siblings.forEach(function (name) {
|
|
401
|
+
var dir = path.join(scope, name);
|
|
402
|
+
var version = isAgendPackage(dir);
|
|
403
|
+
if (version && !isAgendPackage(current)) {
|
|
404
|
+
if (fs.existsSync(current)) quarantine(prefix, current, deps);
|
|
405
|
+
fs.renameSync(dir, current);
|
|
406
|
+
notes.push("put back AgEnD v" + version + " that an interrupted install had moved aside (" + name + ")");
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
notes.push("moved " + name + ", left by an interrupted install, to " + quarantine(prefix, dir, deps));
|
|
410
|
+
});
|
|
411
|
+
binSiblings.forEach(function (name) {
|
|
412
|
+
try { quarantine(prefix, path.join(binDir, name), deps); notes.push("moved bin/" + name + ", left by an interrupted install, to quarantine"); } catch (e) { /* gone */ }
|
|
413
|
+
});
|
|
414
|
+
return { notes: notes, changed: true };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* The service AgEnD installed, as `agend restart` picks its authority: the legacy system unit first, then the user unit
|
|
419
|
+
* or the launchd job. null: a hand-started fleet, or none.
|
|
420
|
+
*/
|
|
421
|
+
function serviceKind(deps) {
|
|
422
|
+
var home = deps.env.HOME || os.homedir();
|
|
423
|
+
if (fs.existsSync(deps.systemUnit || "/etc/systemd/system/agend.service")) return "system";
|
|
424
|
+
if (fs.existsSync(path.join(home, ".config", "systemd", "user", "com.agend.fleet.service"))) return "user";
|
|
425
|
+
if (fs.existsSync(path.join(home, "Library", "LaunchAgents", "com.agend.fleet.plist"))) return "launchd";
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
/** The fleet that runs now: the live process fleet.pid names, else 0. */
|
|
429
|
+
function fleetPid(home, deps) {
|
|
430
|
+
var pid = Number(String(readText(path.join(home, "fleet.pid")) || "").trim());
|
|
431
|
+
return pid > 0 && deps.alive(pid) ? pid : 0;
|
|
432
|
+
}
|
|
433
|
+
function fleetRunning(home, deps) { return fleetPid(home, deps) > 0; }
|
|
434
|
+
|
|
435
|
+
function versionOf(agendBin, deps) {
|
|
436
|
+
var r = deps.run(agendBin, ["--version"], { timeoutMs: 60000 });
|
|
437
|
+
return r.status === 0 ? last(r.stdout).replace(/^v/i, "") : null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* #2420: what an update whose new service does not come up goes back to — the 2.2 updater's C6 rollback
|
|
442
|
+
* (src/package-preimage.ts, src/service-activation.ts), here for every update this installer runs. Before npm, the
|
|
443
|
+
* installed package (with its node_modules: its bundled Node, the runtime receipt and key) is copied to
|
|
444
|
+
* `<prefix>/.agend-rollback/<version>-<time>/agend` — the same filesystem, so putting it back is a rename — with where
|
|
445
|
+
* each of its bin links points; and the service definition's bytes are kept. Only on a host with a service: a
|
|
446
|
+
* hand-started fleet's restart is not rolled back (2.2 neither: its ownership cannot be proven).
|
|
447
|
+
*/
|
|
448
|
+
var ROLLBACK_DIR = ".agend-rollback";
|
|
449
|
+
/** A copy a rollback could not use is kept on purpose (its answer names it): this file in it says why. */
|
|
450
|
+
var KEPT = "kept.txt";
|
|
451
|
+
/**
|
|
452
|
+
* Copies left by an installer that died before its own clean-up (SIGKILL, a killed cgroup): each is a whole package
|
|
453
|
+
* with its Node (~235 MB) and nothing else ever removes it. Only this installer's names (`<version>-<time>-<pid>`), only
|
|
454
|
+
* when that pid no longer runs, and never one a rollback kept on purpose; called while this installer owns the prefix,
|
|
455
|
+
* so no other installer is writing one. 2.2's updater's copies (`<version>-<time>`, which it prunes itself) are left.
|
|
456
|
+
*/
|
|
457
|
+
function pruneStale(prefix, deps) {
|
|
458
|
+
var base = path.join(prefix, ROLLBACK_DIR);
|
|
459
|
+
var names = [];
|
|
460
|
+
try { names = fs.readdirSync(base); } catch (e) { return []; }
|
|
461
|
+
var notes = [];
|
|
462
|
+
names.forEach(function (name) {
|
|
463
|
+
var m = /-\d{3}Z-(\d+)$/.exec(name);
|
|
464
|
+
if (!m || Number(m[1]) === deps.pid || deps.alive(Number(m[1]))) return;
|
|
465
|
+
var dir = path.join(base, name);
|
|
466
|
+
if (fs.existsSync(path.join(dir, KEPT))) return;
|
|
467
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); notes.push("removed a rollback copy left by an installer that is no longer running (" + name + ")"); } catch (e) { /* tried again next time */ }
|
|
468
|
+
});
|
|
469
|
+
return notes;
|
|
470
|
+
}
|
|
471
|
+
function binNames(pkgDir) {
|
|
472
|
+
var p = readJson(path.join(pkgDir, "package.json"));
|
|
473
|
+
return p && p.bin && typeof p.bin === "object" ? Object.keys(p.bin) : [];
|
|
474
|
+
}
|
|
475
|
+
function takePreimage(root, prefix, deps) {
|
|
476
|
+
var pkg = path.join(root, "@songsid", "agend");
|
|
477
|
+
var version = isAgendPackage(pkg);
|
|
478
|
+
if (!version) return { preimage: null };
|
|
479
|
+
var dir = path.join(prefix, ROLLBACK_DIR, version + "-" + new Date(deps.now()).toISOString().replace(/[:.]/g, "-") + "-" + deps.pid);
|
|
480
|
+
try {
|
|
481
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
482
|
+
fs.cpSync(pkg, path.join(dir, "agend"), { recursive: true, verbatimSymlinks: true, preserveTimestamps: true });
|
|
483
|
+
} catch (e) {
|
|
484
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (e2) { /* nothing to keep */ }
|
|
485
|
+
return { refuse: "the installed AgEnD could not be copied aside for a rollback (" + (e && e.message ? e.message : String(e)) + ")" };
|
|
486
|
+
}
|
|
487
|
+
var links = {};
|
|
488
|
+
binNames(pkg).forEach(function (name) {
|
|
489
|
+
var link = path.join(prefix, "bin", name);
|
|
490
|
+
try { links[name] = fs.lstatSync(link).isSymbolicLink() ? fs.readlinkSync(link) : null; } catch (e) { links[name] = null; }
|
|
491
|
+
});
|
|
492
|
+
return { preimage: { dir: dir, version: version, links: links } };
|
|
493
|
+
}
|
|
494
|
+
/** The preimage back in place of what npm installed, and its bin links (a bin only the new release has goes). */
|
|
495
|
+
function restorePreimage(root, prefix, p) {
|
|
496
|
+
var pkg = path.join(root, "@songsid", "agend");
|
|
497
|
+
var aside = path.join(p.dir, "replaced");
|
|
498
|
+
var names = binNames(pkg);
|
|
499
|
+
try {
|
|
500
|
+
if (fs.existsSync(pkg)) fs.renameSync(pkg, aside);
|
|
501
|
+
fs.renameSync(path.join(p.dir, "agend"), pkg);
|
|
502
|
+
} catch (e) {
|
|
503
|
+
return "the previous package could not be put back from " + p.dir + " (" + (e && e.message ? e.message : String(e)) + ")";
|
|
504
|
+
}
|
|
505
|
+
Object.keys(p.links).forEach(function (n) { if (names.indexOf(n) < 0) names.push(n); });
|
|
506
|
+
for (var k = 0; k < names.length; k++) {
|
|
507
|
+
var link = path.join(prefix, "bin", names[k]);
|
|
508
|
+
var isLink = false;
|
|
509
|
+
try { isLink = fs.lstatSync(link).isSymbolicLink(); } catch (e) { isLink = false; }
|
|
510
|
+
if (isLink) { try { fs.unlinkSync(link); } catch (e) { /* gone */ } }
|
|
511
|
+
if (p.links[names[k]]) {
|
|
512
|
+
try { fs.symlinkSync(p.links[names[k]], link); } catch (e) { return link + " could not be restored (" + (e && e.message ? e.message : String(e)) + ")"; }
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
try { fs.rmSync(p.dir, { recursive: true, force: true }); } catch (e) { /* the copy is spent either way */ }
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** The service as its manager names it: the system unit `agend`, the user unit `com.agend.fleet`, the launchd job. */
|
|
520
|
+
function serviceOf(kind, deps) {
|
|
521
|
+
var home = deps.env.HOME || os.homedir();
|
|
522
|
+
if (kind === "system") return { kind: kind, unit: "agend", scope: [], file: deps.systemUnit || "/etc/systemd/system/agend.service" };
|
|
523
|
+
if (kind === "user") return { kind: kind, unit: "com.agend.fleet", scope: ["--user"], file: path.join(home, ".config", "systemd", "user", "com.agend.fleet.service") };
|
|
524
|
+
if (kind === "launchd") return { kind: kind, label: "com.agend.fleet", domain: "gui/" + deps.uid, file: path.join(home, "Library", "LaunchAgents", "com.agend.fleet.plist") };
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
var MANAGER_MS = 15 * 60 * 1000; // a stop waits out the fleet's own stop grace; a start, its READY
|
|
528
|
+
function manage(svc, args, deps, timeoutMs) {
|
|
529
|
+
if (svc.kind === "launchd") return deps.run("launchctl", args, { timeoutMs: timeoutMs || 15000 });
|
|
530
|
+
var env = deps.env;
|
|
531
|
+
// `systemctl --user` reaches the user's manager through XDG_RUNTIME_DIR (its private socket: no D-Bus needed).
|
|
532
|
+
if (svc.kind === "user" && !env.XDG_RUNTIME_DIR && deps.uid >= 0) env = Object.assign({}, env, { XDG_RUNTIME_DIR: "/run/user/" + deps.uid });
|
|
533
|
+
return deps.run("systemctl", svc.scope.concat(args), { timeoutMs: timeoutMs || 15000, env: env });
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* What the manager says of the service: { known, running, stopped, pid }. systemd: active with a main pid is running;
|
|
537
|
+
* inactive or failed is stopped (anything else — activating, an auto-restart, deactivating — is neither). launchd: a
|
|
538
|
+
* loaded job in state running with a pid is running; launchctl print's "not loaded" (113, or 125: no such domain) is
|
|
539
|
+
* stopped. A query that does not complete is not known.
|
|
540
|
+
*/
|
|
541
|
+
function serviceState(svc, deps) {
|
|
542
|
+
if (svc.kind === "launchd") {
|
|
543
|
+
var p = manage(svc, ["print", svc.domain + "/" + svc.label], deps);
|
|
544
|
+
if (p.signal || p.status === null) return { known: false };
|
|
545
|
+
if (p.status === 113 || p.status === 125) return { known: true, running: false, stopped: true, pid: 0 };
|
|
546
|
+
if (p.status !== 0) return { known: false };
|
|
547
|
+
var pid = /^\s*pid = (\d+)\s*$/m.exec(p.stdout);
|
|
548
|
+
var state = /^\s*state = (.+?)\s*$/m.exec(p.stdout);
|
|
549
|
+
var runs = /^\s*runs = (\d+)\s*$/m.exec(p.stdout);
|
|
550
|
+
var running = !!pid && !!state && state[1] === "running";
|
|
551
|
+
return { known: true, running: running, stopped: false, pid: running ? Number(pid[1]) : 0, state: state ? state[1] : "", runs: runs ? Number(runs[1]) : -1 };
|
|
552
|
+
}
|
|
553
|
+
var r = manage(svc, ["show", "-p", "ActiveState", "-p", "SubState", "-p", "MainPID", "-p", "NeedDaemonReload", svc.unit], deps);
|
|
554
|
+
if (r.status !== 0 || r.signal) return { known: false };
|
|
555
|
+
var props = {};
|
|
556
|
+
String(r.stdout).split("\n").forEach(function (line) { var at = line.indexOf("="); if (at > 0) props[line.slice(0, at)] = line.slice(at + 1).trim(); });
|
|
557
|
+
if (!props.ActiveState) return { known: false };
|
|
558
|
+
var main = Number(props.MainPID) || 0;
|
|
559
|
+
return { known: true, running: props.ActiveState === "active" && main > 0, stopped: props.ActiveState === "inactive" || props.ActiveState === "failed",
|
|
560
|
+
pid: main, state: props.ActiveState + "/" + (props.SubState || ""), needReload: props.NeedDaemonReload === "yes" };
|
|
561
|
+
}
|
|
562
|
+
function clockOf(deps) { return deps.monotonicNow || function () { return Number(process.hrtime.bigint() / BigInt(1000000)); }; }
|
|
563
|
+
function sleepOf(deps) { return deps.sleep || function (ms) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }; }
|
|
564
|
+
/** Poll launchd (bounded, monotonic) until `done(state)` — a bootout returns before the job is gone (macOS 15). */
|
|
565
|
+
function awaitLaunchd(svc, deps, done) {
|
|
566
|
+
var clock = clockOf(deps), sleep = sleepOf(deps);
|
|
567
|
+
var deadline = clock() + 10000;
|
|
568
|
+
for (;;) {
|
|
569
|
+
var s = serviceState(svc, deps);
|
|
570
|
+
if (done(s) || clock() >= deadline) return s;
|
|
571
|
+
sleep(100);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/** The fleet's web/health port: fleet.yaml's top-level `health_port:`, else the fleet's default. */
|
|
575
|
+
function healthPort(home) {
|
|
576
|
+
var m = /^health_port:[ \t]*(\d+)[ \t]*(?:#.*)?$/m.exec(readText(path.join(home, "fleet.yaml")) || "");
|
|
577
|
+
return m ? Number(m[1]) : 19280;
|
|
578
|
+
}
|
|
579
|
+
/** Does a fleet that finished starting answer GET /health there (200, or 503 with its problems listed)? */
|
|
580
|
+
function healthAnswers(port, deps) {
|
|
581
|
+
if (deps.health) return deps.health(port);
|
|
582
|
+
var script = "require('http').get({host:'127.0.0.1',port:" + Number(port) + ",path:'/health',timeout:3000},function(r){var b='';r.on('data',function(d){b+=d});" +
|
|
583
|
+
"r.on('end',function(){try{process.exit(JSON.parse(b).startupComplete===true?0:3)}catch(e){process.exit(4)}})}).on('error',function(){process.exit(5)}).on('timeout',function(){process.exit(6)})";
|
|
584
|
+
return deps.run(process.execPath, ["-e", script], { timeoutMs: 10000 }).status === 0;
|
|
585
|
+
}
|
|
586
|
+
var LAUNCHD_READY_MS = 5 * 60 * 1000;
|
|
587
|
+
var LAUNCHD_STEADY_MS = 60 * 1000;
|
|
588
|
+
/**
|
|
589
|
+
* #2420: launchd starts a job without waiting for it (nothing like systemd's READY): `agend restart` / `agend start`
|
|
590
|
+
* return before the fleet runs, and a fleet that exits at start is started again by KeepAlive, forever. So under launchd
|
|
591
|
+
* the fleet proves itself: the job runs the pid fleet.pid names (the fleet writes it as it starts), and /health answers
|
|
592
|
+
* with its startup complete — or, with no answer there (its port taken: the fleet then runs without it), that same pid
|
|
593
|
+
* has run for a minute. The job started again (its runs count grew) is a fleet that exited. null: up; else why not.
|
|
594
|
+
*/
|
|
595
|
+
function launchdReady(svc, home, deps) {
|
|
596
|
+
var clock = clockOf(deps), sleep = sleepOf(deps);
|
|
597
|
+
var deadline = clock() + LAUNCHD_READY_MS, port = healthPort(home), firstRuns = null, steadyPid = 0, steadySince = 0;
|
|
598
|
+
for (;;) {
|
|
599
|
+
var s = serviceState(svc, deps);
|
|
600
|
+
if (s.known && s.stopped) return "the job is not loaded";
|
|
601
|
+
if (s.known) {
|
|
602
|
+
if (firstRuns === null) firstRuns = s.runs;
|
|
603
|
+
else if (s.runs > firstRuns) return "the fleet exited and launchd started it again (runs " + firstRuns + " → " + s.runs + ")";
|
|
604
|
+
if (s.running && fleetPid(home, deps) === s.pid) {
|
|
605
|
+
if (healthAnswers(port, deps)) return null;
|
|
606
|
+
if (steadyPid !== s.pid) { steadyPid = s.pid; steadySince = clock(); }
|
|
607
|
+
else if (clock() - steadySince >= LAUNCHD_STEADY_MS) return null;
|
|
608
|
+
} else steadyPid = 0;
|
|
609
|
+
}
|
|
610
|
+
if (clock() >= deadline) return "the fleet did not come up within " + LAUNCHD_READY_MS / 1000 + "s (launchd: " + (s.known ? s.state || "no state" : "state unknown") + ")";
|
|
611
|
+
sleep(1000);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function stopService(svc, deps) {
|
|
615
|
+
if (svc.kind === "launchd") {
|
|
616
|
+
manage(svc, ["bootout", svc.domain + "/" + svc.label], deps);
|
|
617
|
+
return awaitLaunchd(svc, deps, function (s) { return s.known && s.stopped; });
|
|
618
|
+
}
|
|
619
|
+
// A stop cancels a queued restart job and ends an auto-restart: what systemd then reports is settled.
|
|
620
|
+
manage(svc, ["stop", svc.unit], deps, MANAGER_MS);
|
|
621
|
+
return serviceState(svc, deps);
|
|
622
|
+
}
|
|
623
|
+
function startService(svc, home, deps) {
|
|
624
|
+
if (svc.kind === "launchd") {
|
|
625
|
+
var boot = manage(svc, ["bootstrap", svc.domain, svc.file], deps);
|
|
626
|
+
if (boot.status !== 0) return { known: true, running: false, stopped: true, pid: 0, why: "launchctl bootstrap exited " + (boot.status === null ? boot.signal : boot.status) };
|
|
627
|
+
var notUp = launchdReady(svc, home, deps);
|
|
628
|
+
if (notUp) return { known: true, running: false, pid: 0, why: notUp };
|
|
629
|
+
return serviceState(svc, deps);
|
|
630
|
+
}
|
|
631
|
+
// The failed start may have used up the unit's start limit: cleared, as `agend restart` does.
|
|
632
|
+
manage(svc, ["reset-failed", svc.unit], deps);
|
|
633
|
+
var started = manage(svc, ["start", svc.unit], deps, MANAGER_MS);
|
|
634
|
+
var s = serviceState(svc, deps);
|
|
635
|
+
if (started.status !== 0 && !s.running) s.why = "systemctl start exited " + (started.status === null ? started.signal : started.status);
|
|
636
|
+
return s;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function main(argv, injected) {
|
|
640
|
+
var deps = injected || defaultDeps();
|
|
641
|
+
var args = argv || [];
|
|
642
|
+
var upgrade = args.indexOf("--upgrade") >= 0;
|
|
643
|
+
var specAt = args.indexOf("--spec");
|
|
644
|
+
var targetAt = args.indexOf("--target");
|
|
645
|
+
// Where the previous version is reinstalled from when the new one does not prove itself (tests and mirrors).
|
|
646
|
+
var previousAt = args.indexOf("--previous-spec");
|
|
647
|
+
var own = readJson(path.join(__dirname, "..", "package.json")) || {};
|
|
648
|
+
var version = own.version;
|
|
649
|
+
var log = deps.log;
|
|
650
|
+
// `agend update` names the release it chose: this must be that release's own installer, never another one npm found
|
|
651
|
+
// on PATH (a target without agend-install makes `npm exec` run whatever agend-install PATH has; #2264 review).
|
|
652
|
+
if (targetAt >= 0 && args[targetAt + 1] !== version) {
|
|
653
|
+
log(" ✗ this is the installer of AgEnD v" + version + ", not of v" + args[targetAt + 1] + " — nothing was changed");
|
|
654
|
+
return 1;
|
|
655
|
+
}
|
|
656
|
+
// The package this installer ships in, unless a test or a mirror names another source of the same version.
|
|
657
|
+
var spec = specAt >= 0 ? args[specAt + 1] : "@songsid/agend@" + version;
|
|
658
|
+
// An exact `@songsid/agend@<version>` spec names the version to prove; anything else (a tarball, a tag) is this one.
|
|
659
|
+
var exact = /^@songsid\/agend@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(spec);
|
|
660
|
+
if (exact) version = exact[1];
|
|
661
|
+
var home = deps.env.AGEND_HOME || path.join(os.homedir(), ".agend");
|
|
662
|
+
var claimed = false;
|
|
663
|
+
var fail = function (stage, message) {
|
|
664
|
+
log(" ✗ " + message);
|
|
665
|
+
setStage(home, claimed, "failed", { error: message }, deps);
|
|
666
|
+
return stage === "lock" ? 75 : 1;
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
try { ensurePrivateHome(home, { log: log }); }
|
|
670
|
+
catch (error) { return fail("install", error.message); }
|
|
671
|
+
|
|
672
|
+
log(" AgEnD installer v" + version + (upgrade ? " (upgrade)" : ""));
|
|
673
|
+
var root = last(deps.run("npm", ["root", "-g"], { timeoutMs: 30000 }).stdout);
|
|
674
|
+
var prefix = last(deps.run("npm", ["prefix", "-g"], { timeoutMs: 30000 }).stdout);
|
|
675
|
+
if (!root || !prefix) {
|
|
676
|
+
claimed = claimMarker(home, deps, { ownOnly: true });
|
|
677
|
+
return fail("install", "npm did not say where it installs global packages (npm root -g / npm prefix -g)");
|
|
678
|
+
}
|
|
679
|
+
var admitted = admit(prefix, deps, { spec: spec, home: home });
|
|
680
|
+
admitted.notes && admitted.notes.forEach(function (n) { log(" ↺ " + n); });
|
|
681
|
+
if (admitted.refuse) {
|
|
682
|
+
// Refused: another installer owns the prefix. Only the marker of the `agend update` that started THIS installer
|
|
683
|
+
// says so; anyone else's (the running update's) is left exactly as it is.
|
|
684
|
+
claimed = claimMarker(home, deps, { ownOnly: true });
|
|
685
|
+
return fail("lock", admitted.refuse);
|
|
686
|
+
}
|
|
687
|
+
var preimage = null, definition = null, keepPreimage = false;
|
|
688
|
+
try {
|
|
689
|
+
claimed = claimMarker(home, deps, {});
|
|
690
|
+
setStage(home, claimed, "downloading", { version: version }, deps);
|
|
691
|
+
var fixed = repair(root, prefix, deps);
|
|
692
|
+
fixed.notes.forEach(function (n) { log(" ↺ " + n); });
|
|
693
|
+
pruneStale(prefix, deps).forEach(function (n) { log(" ↺ " + n); });
|
|
694
|
+
var previous = isAgendPackage(path.join(root, "@songsid", "agend"));
|
|
695
|
+
var bin = path.join(prefix, "bin", "agend");
|
|
696
|
+
// #2420: on a host with a service, what an update whose new service does not come up goes back to.
|
|
697
|
+
var svc = serviceOf(serviceKind(deps), deps);
|
|
698
|
+
if (svc && previous) {
|
|
699
|
+
var taken = takePreimage(root, prefix, deps);
|
|
700
|
+
if (taken.refuse) return fail("install", "Not updating: " + taken.refuse + ". Nothing was changed");
|
|
701
|
+
preimage = taken.preimage;
|
|
702
|
+
definition = readText(svc.file);
|
|
703
|
+
}
|
|
704
|
+
// #2286 review: the lock's token, for a release from before #2264 (its postinstall refuses any other install).
|
|
705
|
+
var npmEnv = Object.assign({}, deps.env);
|
|
706
|
+
npmEnv[lockReaders.TOKEN_ENV] = admitted.token;
|
|
707
|
+
var install = function () { return deps.run("npm", ["install", "-g", spec], { inherit: true, env: npmEnv }); };
|
|
708
|
+
var done = install();
|
|
709
|
+
if (done.status !== 0) {
|
|
710
|
+
// npm itself can leave its sibling behind (a failure inside its own move): once more, only if repairing moved one.
|
|
711
|
+
var again = repair(root, prefix, deps);
|
|
712
|
+
again.notes.forEach(function (n) { log(" ↺ " + n); });
|
|
713
|
+
if (again.changed) done = install();
|
|
714
|
+
}
|
|
715
|
+
var got = done.status === 0 ? versionOf(bin, deps) : null;
|
|
716
|
+
if (got !== version) {
|
|
717
|
+
var why = done.status !== 0 ? "npm install -g " + spec + " failed (exit " + (done.status === null ? done.signal : done.status) + ")"
|
|
718
|
+
: "after npm install, " + bin + " reports " + (got || "nothing") + ", not v" + version;
|
|
719
|
+
// What actually runs now. npm rolls a failed install back itself: when the previous version still runs, nothing
|
|
720
|
+
// else is done and that is what is said. Only an `agend` that does not run is reinstalled.
|
|
721
|
+
var running = versionOf(bin, deps);
|
|
722
|
+
if (running && done.status !== 0) return fail("install", why + "; v" + running + " is still installed and runs — nothing was changed");
|
|
723
|
+
if (previous && previous !== version) {
|
|
724
|
+
repair(root, prefix, deps);
|
|
725
|
+
var back = deps.run("npm", ["install", "-g", previousAt >= 0 ? args[previousAt + 1] : "@songsid/agend@" + previous], { inherit: true, env: npmEnv });
|
|
726
|
+
if (back.status === 0 && versionOf(bin, deps) === previous) return fail("install", why + "; v" + previous + " was reinstalled and runs");
|
|
727
|
+
}
|
|
728
|
+
return fail("install", why + (previous ? "; v" + previous + " could not be put back — run the installer again" : ""));
|
|
729
|
+
}
|
|
730
|
+
log(" ✓ AgEnD v" + version + " is installed (" + bin + ")");
|
|
731
|
+
// Root under nvm: the service, cron and non-login shells do not source nvm — give them the same agend and node.
|
|
732
|
+
if (deps.uid === 0 && prefix.indexOf("/.nvm/") >= 0) {
|
|
733
|
+
[["agend", bin], ["node", path.join(prefix, "bin", "node")]].forEach(function (pair) {
|
|
734
|
+
deps.run("ln", ["-sf", pair[1], "/usr/local/bin/" + pair[0]], {});
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
setStage(home, claimed, "installed", { version: version }, deps);
|
|
738
|
+
|
|
739
|
+
// A fresh install (no fleet set up, not an upgrade): the next step is the setup wizard.
|
|
740
|
+
var kind = serviceKind(deps);
|
|
741
|
+
var configured = fs.existsSync(path.join(home, "fleet.yaml")) || fs.existsSync(path.join(home, "fleet.pid"));
|
|
742
|
+
if (!configured && !upgrade && !kind) { log(" Next: run `agend quickstart` to set up AgEnD."); return 0; }
|
|
743
|
+
// The service stays under the manager it has (`agend restart` picks the same one: system unit, else user unit or
|
|
744
|
+
// launchd job): the new release's `agend install --no-activate` refreshes that definition without starting anything
|
|
745
|
+
// (a legacy system unit is refreshed in place — never a second, user unit), then the fleet moves onto the new release:
|
|
746
|
+
// restarted when it runs, started when its service is installed but stopped (the user's: the session that ran it
|
|
747
|
+
// ended). A hand-started fleet gets no unit (#1631) and is restarted only by an upgrade.
|
|
748
|
+
// #2420: the fleet the service ran before this update (its fleet.pid, alive): a failure that never stopped it
|
|
749
|
+
// keeps it, one that did brings the previous service back.
|
|
750
|
+
var fleetBefore = fleetPid(home, deps);
|
|
751
|
+
/**
|
|
752
|
+
* #2420: the new release's service did not come up (its refresh failed, or its restart or start did). With a
|
|
753
|
+
* preimage, the previous package and service definition go back and the previous service runs again — the 2.2
|
|
754
|
+
* updater's C6 rollback. What the manager is still running or starting is stopped first and must be proven
|
|
755
|
+
* stopped, or nothing is rolled back; a fleet the failure never stopped (the same pid still runs) is left running.
|
|
756
|
+
* Without a preimage (no service before this update, no previous AgEnD), it says what to run, as before.
|
|
757
|
+
*/
|
|
758
|
+
var rollBack = function (stage, why, hint) {
|
|
759
|
+
if (!preimage || !svc) return fail(stage, why + "; " + hint);
|
|
760
|
+
var name = svc.kind === "launchd" ? "the launchd job " + svc.label : "the " + (svc.kind === "user" ? "user" : "system") + " unit " + svc.unit;
|
|
761
|
+
var kept = "; the previous package is kept in " + preimage.dir;
|
|
762
|
+
log(" ↩ " + why + " — rolling back to v" + preimage.version);
|
|
763
|
+
var untouched = fleetBefore > 0 && fleetPid(home, deps) === fleetBefore;
|
|
764
|
+
if (!untouched) {
|
|
765
|
+
var now = serviceState(svc, deps);
|
|
766
|
+
if (!now.known) { keepPreimage = true; return fail(stage, why + ". What runs " + name + " now cannot be told, so nothing was rolled back" + kept + " — check it, then run `agend restart`"); }
|
|
767
|
+
var stopped = stopService(svc, deps);
|
|
768
|
+
if (!stopped.known || !stopped.stopped) { keepPreimage = true; return fail(stage, why + ". " + name + " could not be stopped (" + (stopped.known ? stopped.state : "its state cannot be told") + "), so nothing was rolled back" + kept); }
|
|
769
|
+
}
|
|
770
|
+
var notBack = restorePreimage(root, prefix, preimage);
|
|
771
|
+
if (notBack) { keepPreimage = true; return fail(stage, why + ". " + notBack + " — reinstall it: npm install -g @songsid/agend@" + preimage.version); }
|
|
772
|
+
var restored = [];
|
|
773
|
+
if (definition !== null && readText(svc.file) !== definition) {
|
|
774
|
+
try { fs.writeFileSync(svc.file, definition); } catch (e) { return fail(stage, why + ". v" + preimage.version + " is back, but " + svc.file + " could not be restored (" + (e && e.code ? e.code : String(e)) + ") — run `agend install --no-activate`, then `agend start`"); }
|
|
775
|
+
}
|
|
776
|
+
if (svc.kind === "launchd") {
|
|
777
|
+
// The refresh's activation plan names the new plist: never left for a later `agend restart` to act on.
|
|
778
|
+
try { fs.unlinkSync(path.join(home, "service-plan.json")); } catch (e) { /* none */ }
|
|
779
|
+
} else {
|
|
780
|
+
var reload = manage(svc, ["daemon-reload"], deps);
|
|
781
|
+
var loaded = reload.status === 0 ? serviceState(svc, deps) : { known: false };
|
|
782
|
+
if (!loaded.known || loaded.needReload) restored.push("run `systemctl" + (svc.kind === "user" ? " --user" : "") + " daemon-reload`: systemd may still have the new definition loaded");
|
|
783
|
+
}
|
|
784
|
+
var back = versionOf(bin, deps);
|
|
785
|
+
var what = "the previous package (v" + preimage.version + ") and " + name + " were restored";
|
|
786
|
+
if (back !== preimage.version) restored.push(bin + " reports " + (back || "nothing") + ", not v" + preimage.version);
|
|
787
|
+
var outcome;
|
|
788
|
+
if (untouched) outcome = what + "; the previous service was never stopped and still runs (pid " + fleetBefore + ")";
|
|
789
|
+
else if (fleetBefore > 0) {
|
|
790
|
+
var started = startService(svc, home, deps);
|
|
791
|
+
outcome = started.running ? what + "; the previous service is running (pid " + started.pid + ")"
|
|
792
|
+
: what + ", but the previous service did not start (" + (started.why || (started.known ? started.state : "its state cannot be told")) + ") — run `agend start`";
|
|
793
|
+
} else outcome = what + "; the service was stopped before this update and is left stopped — `agend start` starts it";
|
|
794
|
+
return fail(stage, why + ". Rolled back: " + outcome + (restored.length ? "; " + restored.join("; ") : "") + ". The update did not succeed");
|
|
795
|
+
};
|
|
796
|
+
if (kind) {
|
|
797
|
+
var service = deps.run(bin, ["install", "--no-activate"], { inherit: true });
|
|
798
|
+
if (service.status !== 0) return rollBack("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`");
|
|
799
|
+
}
|
|
800
|
+
var env = Object.assign({}, deps.env, { AGEND_RESTART_ORIGIN: upgrade ? "agend-update" : "agend-install" });
|
|
801
|
+
if (fleetRunning(home, deps) && (kind || upgrade)) {
|
|
802
|
+
var restarted = deps.run(bin, ["restart", "--yes"], { inherit: true, env: env });
|
|
803
|
+
// 75: the restart job is still running (systemd goes on with it); the new fleet settles the marker.
|
|
804
|
+
if (restarted.status !== 0 && restarted.status !== 75) return rollBack("stopping", "the fleet did not restart onto v" + version + " (exit " + restarted.status + ")", "run `agend restart`");
|
|
805
|
+
} else if (kind) {
|
|
806
|
+
var started = deps.run(bin, ["start"], { inherit: true, env: env });
|
|
807
|
+
if (started.status !== 0) return rollBack("starting", "the " + kind + " service did not start (exit " + started.status + ")", "AgEnD v" + version + " is installed — run `agend start`");
|
|
808
|
+
}
|
|
809
|
+
// Under launchd an exit 0 is not the fleet running (above): it must come up on the new release first.
|
|
810
|
+
var activated = restarted ? restarted.status === 0 : !!started;
|
|
811
|
+
if (svc && svc.kind === "launchd" && activated) {
|
|
812
|
+
var notUp = launchdReady(svc, home, deps);
|
|
813
|
+
if (notUp) return rollBack(restarted ? "stopping" : "starting", "the launchd job did not come up on v" + version + ": " + notUp, "run `agend restart`");
|
|
814
|
+
}
|
|
815
|
+
return 0;
|
|
816
|
+
} finally {
|
|
817
|
+
// Spent (put back), or not needed: only a rollback that could not finish keeps its copy, says where, and marks it
|
|
818
|
+
// so that no later installer prunes it.
|
|
819
|
+
if (preimage && !keepPreimage) { try { fs.rmSync(preimage.dir, { recursive: true, force: true }); } catch (e) { /* best effort */ } }
|
|
820
|
+
if (preimage && keepPreimage && fs.existsSync(preimage.dir)) {
|
|
821
|
+
try { fs.writeFileSync(path.join(preimage.dir, KEPT), "AgEnD v" + preimage.version + ", kept by the installer of v" + version + " (pid " + deps.pid + ") after a rollback it could not finish. Remove this directory once you no longer need it.\n"); } catch (e) { /* the answer already named it */ }
|
|
822
|
+
}
|
|
823
|
+
admitted.release();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
module.exports = { main: main, repair: repair, admit: admit, openedBy: openedBy, setStage: setStage, claimMarker: claimMarker, npmIdleFromProcessList: npmIdleFromProcessList, npmCommands: npmCommands, LOCK: LOCK };
|
|
828
|
+
if (require.main === module) process.exit(main(process.argv.slice(2)));
|