@phnx-labs/agents-cli 1.22.119 → 1.22.121
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/CHANGELOG.md +236 -0
- package/README.md +19 -2
- package/dist/bootstrap.d.ts +0 -15
- package/dist/bootstrap.js +10 -409
- package/dist/cli/command-registry.d.ts +0 -58
- package/dist/cli/command-registry.js +2 -74
- package/dist/commands/accounts.d.ts +0 -17
- package/dist/commands/accounts.js +1 -52
- package/dist/commands/alias.d.ts +0 -12
- package/dist/commands/alias.js +0 -6
- package/dist/commands/apply.d.ts +0 -15
- package/dist/commands/apply.js +0 -63
- package/dist/commands/audit.d.ts +0 -12
- package/dist/commands/audit.js +0 -13
- package/dist/commands/auth.js +4 -12
- package/dist/commands/beta.d.ts +0 -6
- package/dist/commands/beta.js +2 -6
- package/dist/commands/browser-sessions-picker.d.ts +0 -8
- package/dist/commands/browser-sessions-picker.js +0 -26
- package/dist/commands/browser.d.ts +0 -57
- package/dist/commands/browser.js +0 -80
- package/dist/commands/budget.d.ts +0 -13
- package/dist/commands/budget.js +0 -2
- package/dist/commands/cli.d.ts +0 -12
- package/dist/commands/cli.js +0 -4
- package/dist/commands/cloud.d.ts +0 -9
- package/dist/commands/cloud.js +0 -43
- package/dist/commands/commands.d.ts +0 -9
- package/dist/commands/commands.js +0 -18
- package/dist/commands/computer-sessions-picker.d.ts +0 -11
- package/dist/commands/computer-sessions-picker.js +0 -29
- package/dist/commands/computer.d.ts +0 -62
- package/dist/commands/computer.js +3 -114
- package/dist/commands/config.d.ts +0 -14
- package/dist/commands/config.js +21 -60
- package/dist/commands/cost.d.ts +0 -11
- package/dist/commands/cost.js +1 -17
- package/dist/commands/daemon-test-harness.d.ts +0 -32
- package/dist/commands/daemon-test-harness.js +1 -36
- package/dist/commands/daemon.d.ts +0 -46
- package/dist/commands/daemon.js +8 -255
- package/dist/commands/detach-core.d.ts +0 -28
- package/dist/commands/detach-core.js +0 -30
- package/dist/commands/detach.d.ts +1 -12
- package/dist/commands/detach.js +16 -52
- package/dist/commands/device-config-test-harness.js +0 -12
- package/dist/commands/doctor.d.ts +0 -103
- package/dist/commands/doctor.js +3 -279
- package/dist/commands/events.d.ts +0 -29
- package/dist/commands/events.js +1 -55
- package/dist/commands/exec.d.ts +0 -163
- package/dist/commands/exec.js +15 -964
- package/dist/commands/factory.d.ts +0 -6
- package/dist/commands/factory.js +0 -2
- package/dist/commands/feed-watch.js +0 -20
- package/dist/commands/feed.d.ts +0 -38
- package/dist/commands/feed.js +1 -151
- package/dist/commands/feedback.d.ts +0 -5
- package/dist/commands/feedback.js +0 -5
- package/dist/commands/fleet-capture.d.ts +0 -12
- package/dist/commands/fleet-capture.js +1 -39
- package/dist/commands/focus.d.ts +6 -94
- package/dist/commands/focus.js +13 -183
- package/dist/commands/fork.d.ts +0 -21
- package/dist/commands/fork.js +0 -45
- package/dist/commands/funnel.d.ts +0 -3
- package/dist/commands/go.d.ts +2 -71
- package/dist/commands/go.js +5 -99
- package/dist/commands/harness-hooks.d.ts +0 -41
- package/dist/commands/harness-hooks.js +0 -48
- package/dist/commands/harness-wizard.d.ts +0 -137
- package/dist/commands/harness-wizard.js +0 -109
- package/dist/commands/harness.d.ts +0 -63
- package/dist/commands/harness.js +0 -103
- package/dist/commands/hooks.d.ts +0 -9
- package/dist/commands/hooks.js +0 -26
- package/dist/commands/humans.d.ts +0 -8
- package/dist/commands/humans.js +0 -8
- package/dist/commands/import.d.ts +0 -22
- package/dist/commands/import.js +0 -79
- package/dist/commands/insights.d.ts +0 -37
- package/dist/commands/insights.js +3 -115
- package/dist/commands/inspect.d.ts +0 -117
- package/dist/commands/inspect.js +2 -448
- package/dist/commands/lease.d.ts +0 -28
- package/dist/commands/lease.js +0 -43
- package/dist/commands/logs.d.ts +0 -23
- package/dist/commands/logs.js +0 -30
- package/dist/commands/mailboxes.d.ts +0 -18
- package/dist/commands/mailboxes.js +0 -29
- package/dist/commands/mcp.d.ts +0 -9
- package/dist/commands/mcp.js +0 -53
- package/dist/commands/memory.d.ts +0 -7
- package/dist/commands/memory.js +0 -7
- package/dist/commands/menubar.d.ts +0 -8
- package/dist/commands/menubar.js +0 -24
- package/dist/commands/message.d.ts +0 -23
- package/dist/commands/message.js +0 -50
- package/dist/commands/mine.d.ts +0 -22
- package/dist/commands/mine.js +0 -21
- package/dist/commands/models.d.ts +0 -8
- package/dist/commands/models.js +0 -33
- package/dist/commands/modes.d.ts +0 -10
- package/dist/commands/modes.js +0 -13
- package/dist/commands/monitors.d.ts +0 -9
- package/dist/commands/monitors.js +3 -160
- package/dist/commands/open.d.ts +0 -26
- package/dist/commands/open.js +0 -10
- package/dist/commands/output.d.ts +0 -18
- package/dist/commands/output.js +1 -34
- package/dist/commands/packages-materialize.d.ts +0 -11
- package/dist/commands/packages-materialize.js +0 -3
- package/dist/commands/packages.d.ts +0 -10
- package/dist/commands/packages.js +0 -50
- package/dist/commands/perf.d.ts +0 -41
- package/dist/commands/perf.js +0 -55
- package/dist/commands/permissions.d.ts +0 -9
- package/dist/commands/permissions.js +0 -43
- package/dist/commands/plugins.d.ts +0 -14
- package/dist/commands/plugins.js +2 -55
- package/dist/commands/profiles.d.ts +0 -25
- package/dist/commands/profiles.js +0 -27
- package/dist/commands/projects-todo.d.ts +2 -0
- package/dist/commands/projects-todo.js +132 -0
- package/dist/commands/projects.d.ts +0 -85
- package/dist/commands/projects.js +344 -291
- package/dist/commands/prune.js +0 -30
- package/dist/commands/ps-roster.d.ts +92 -0
- package/dist/commands/ps-roster.js +744 -0
- package/dist/commands/ps.d.ts +6 -0
- package/dist/commands/ps.js +98 -0
- package/dist/commands/refresh-rules.d.ts +0 -13
- package/dist/commands/refresh-rules.js +0 -14
- package/dist/commands/reminders.d.ts +0 -7
- package/dist/commands/repo.d.ts +0 -89
- package/dist/commands/repo.js +7 -205
- package/dist/commands/resource-view.d.ts +0 -33
- package/dist/commands/resource-view.js +0 -49
- package/dist/commands/resume.d.ts +1 -19
- package/dist/commands/resume.js +0 -50
- package/dist/commands/route.d.ts +0 -10
- package/dist/commands/route.js +0 -19
- package/dist/commands/routines.d.ts +0 -42
- package/dist/commands/routines.js +5 -290
- package/dist/commands/routines.test-fixture.d.ts +0 -34
- package/dist/commands/routines.test-fixture.js +4 -100
- package/dist/commands/rules.d.ts +0 -9
- package/dist/commands/rules.js +1 -6
- package/dist/commands/run-account-picker.d.ts +0 -59
- package/dist/commands/run-account-picker.js +0 -71
- package/dist/commands/run-broadcast.d.ts +0 -9
- package/dist/commands/run-broadcast.js +0 -7
- package/dist/commands/run-cloud.d.ts +0 -15
- package/dist/commands/run-cloud.js +0 -37
- package/dist/commands/run-device-picker.d.ts +0 -25
- package/dist/commands/run-device-picker.js +1 -53
- package/dist/commands/secrets-passthrough.d.ts +0 -31
- package/dist/commands/secrets-passthrough.js +0 -10
- package/dist/commands/send.d.ts +0 -20
- package/dist/commands/send.js +14 -18
- package/dist/commands/sessions-backfill.d.ts +0 -7
- package/dist/commands/sessions-backfill.js +1 -16
- package/dist/commands/sessions-backup-setup.js +0 -11
- package/dist/commands/sessions-bookmark.d.ts +0 -8
- package/dist/commands/sessions-bookmark.js +0 -28
- package/dist/commands/sessions-browser.d.ts +2 -117
- package/dist/commands/sessions-browser.js +5 -243
- package/dist/commands/sessions-export.d.ts +1 -26
- package/dist/commands/sessions-export.js +3 -81
- package/dist/commands/sessions-import.d.ts +0 -14
- package/dist/commands/sessions-import.js +0 -59
- package/dist/commands/sessions-inject.d.ts +0 -60
- package/dist/commands/sessions-inject.js +3 -96
- package/dist/commands/sessions-migrate.d.ts +2 -37
- package/dist/commands/sessions-migrate.js +7 -159
- package/dist/commands/sessions-optimize.d.ts +0 -12
- package/dist/commands/sessions-picker-factory.d.ts +0 -7
- package/dist/commands/sessions-picker-factory.js +0 -8
- package/dist/commands/sessions-picker.d.ts +4 -103
- package/dist/commands/sessions-picker.js +13 -352
- package/dist/commands/sessions-render.d.ts +1 -2
- package/dist/commands/sessions-render.js +3 -7
- package/dist/commands/sessions-resume.d.ts +1 -24
- package/dist/commands/sessions-resume.js +2 -65
- package/dist/commands/sessions-share.d.ts +1 -9
- package/dist/commands/sessions-share.js +1 -45
- package/dist/commands/sessions-stats.d.ts +0 -24
- package/dist/commands/sessions-stats.js +0 -49
- package/dist/commands/sessions-stop.d.ts +1 -20
- package/dist/commands/sessions-stop.js +4 -9
- package/dist/commands/sessions-tail.d.ts +1 -19
- package/dist/commands/sessions-tail.js +3 -36
- package/dist/commands/sessions-trace.d.ts +5 -36
- package/dist/commands/sessions-trace.js +7 -82
- package/dist/commands/sessions.d.ts +1 -666
- package/dist/commands/sessions.js +30 -2279
- package/dist/commands/sessions.test-fixture.d.ts +0 -9
- package/dist/commands/sessions.test-fixture.js +8 -23
- package/dist/commands/setup-accounts.d.ts +0 -6
- package/dist/commands/setup-browser.d.ts +0 -23
- package/dist/commands/setup-browser.js +0 -28
- package/dist/commands/setup-computer.d.ts +0 -16
- package/dist/commands/setup-computer.js +0 -28
- package/dist/commands/setup-fleet.d.ts +0 -8
- package/dist/commands/setup-fleet.js +0 -11
- package/dist/commands/setup-mine.d.ts +0 -14
- package/dist/commands/setup-mine.js +0 -25
- package/dist/commands/setup-preferences.d.ts +0 -47
- package/dist/commands/setup-preferences.js +0 -53
- package/dist/commands/setup-secrets.d.ts +2 -24
- package/dist/commands/setup-secrets.js +0 -24
- package/dist/commands/setup-term.d.ts +0 -21
- package/dist/commands/setup-term.js +0 -21
- package/dist/commands/setup-terminal.d.ts +0 -1
- package/dist/commands/setup-terminal.js +0 -1
- package/dist/commands/setup-tools.d.ts +2 -0
- package/dist/commands/setup-tools.js +73 -0
- package/dist/commands/setup.d.ts +0 -20
- package/dist/commands/setup.js +4 -57
- package/dist/commands/skills.d.ts +0 -9
- package/dist/commands/skills.js +1 -22
- package/dist/commands/snapshot.d.ts +0 -9
- package/dist/commands/snapshot.js +0 -10
- package/dist/commands/ssh.d.ts +0 -64
- package/dist/commands/ssh.js +9 -420
- package/dist/commands/status.d.ts +0 -12
- package/dist/commands/status.js +0 -26
- package/dist/commands/subagents.d.ts +0 -8
- package/dist/commands/subagents.js +0 -31
- package/dist/commands/sync.d.ts +0 -37
- package/dist/commands/sync.js +0 -256
- package/dist/commands/teams-picker.d.ts +0 -3
- package/dist/commands/teams-picker.js +0 -34
- package/dist/commands/teams.d.ts +1 -76
- package/dist/commands/teams.js +9 -477
- package/dist/commands/tmux.d.ts +0 -25
- package/dist/commands/tmux.js +0 -46
- package/dist/commands/traces.js +0 -19
- package/dist/commands/trash.d.ts +0 -17
- package/dist/commands/trash.js +1 -11
- package/dist/commands/uninstall.d.ts +0 -9
- package/dist/commands/uninstall.js +0 -6
- package/dist/commands/update.d.ts +0 -22
- package/dist/commands/update.js +0 -44
- package/dist/commands/upgrade.d.ts +0 -1
- package/dist/commands/upgrade.js +0 -1
- package/dist/commands/utils.d.ts +0 -85
- package/dist/commands/utils.js +1 -95
- package/dist/commands/versions.d.ts +0 -16
- package/dist/commands/versions.js +0 -134
- package/dist/commands/view.d.ts +0 -90
- package/dist/commands/view.js +4 -343
- package/dist/commands/watchdog.d.ts +0 -19
- package/dist/commands/watchdog.js +3 -43
- package/dist/commands/webhook.d.ts +0 -8
- package/dist/commands/webhook.js +0 -14
- package/dist/commands/workflows.d.ts +0 -8
- package/dist/commands/workflows.js +0 -6
- package/dist/index.d.ts +0 -25
- package/dist/index.js +5 -119
- package/dist/lib/account-capabilities.d.ts +0 -34
- package/dist/lib/account-capabilities.js +0 -32
- package/dist/lib/account-catalog.d.ts +0 -128
- package/dist/lib/account-catalog.js +8 -154
- package/dist/lib/account-provider-registry.d.ts +0 -14
- package/dist/lib/account-provider-registry.js +0 -8
- package/dist/lib/account-registry.d.ts +0 -74
- package/dist/lib/account-registry.js +14 -189
- package/dist/lib/account-schema.d.ts +0 -19
- package/dist/lib/account-schema.js +0 -43
- package/dist/lib/accounting/account-launch.d.ts +0 -15
- package/dist/lib/accounting/account-launch.js +1 -3
- package/dist/lib/accounting/account-pool-collect.d.ts +0 -26
- package/dist/lib/accounting/account-pool-collect.js +0 -42
- package/dist/lib/accounting/account-pool.d.ts +0 -41
- package/dist/lib/accounting/account-pool.js +0 -17
- package/dist/lib/accounting/capacity.d.ts +0 -32
- package/dist/lib/accounting/capacity.js +0 -32
- package/dist/lib/accounting/rotate.d.ts +0 -518
- package/dist/lib/accounting/rotate.js +4 -598
- package/dist/lib/accounting/usage-ingest.d.ts +0 -1
- package/dist/lib/accounting/usage-ingest.js +0 -44
- package/dist/lib/accounting/usage-sync.d.ts +0 -60
- package/dist/lib/accounting/usage-sync.js +4 -51
- package/dist/lib/accounting/usage.d.ts +0 -632
- package/dist/lib/accounting/usage.js +14 -1055
- package/dist/lib/accounts/add.d.ts +0 -60
- package/dist/lib/accounts/add.js +0 -127
- package/dist/lib/accounts/auth-operation-lock.d.ts +0 -1
- package/dist/lib/accounts/auth-operation-lock.js +0 -4
- package/dist/lib/accounts/migrate.d.ts +0 -9
- package/dist/lib/accounts/migrate.js +3 -42
- package/dist/lib/accounts/slots.d.ts +0 -25
- package/dist/lib/accounts/slots.js +1 -49
- package/dist/lib/acp/client.d.ts +0 -16
- package/dist/lib/acp/client.js +0 -29
- package/dist/lib/acp/harnesses.d.ts +0 -15
- package/dist/lib/acp/harnesses.js +0 -16
- package/dist/lib/acp/run.d.ts +0 -8
- package/dist/lib/acp/run.js +0 -8
- package/dist/lib/actor.d.ts +1 -71
- package/dist/lib/actor.js +1 -99
- package/dist/lib/add-dir.d.ts +0 -46
- package/dist/lib/add-dir.js +1 -70
- package/dist/lib/address.d.ts +0 -17
- package/dist/lib/address.js +0 -15
- package/dist/lib/agent-cli-commands.d.ts +0 -10
- package/dist/lib/agent-cli-commands.js +0 -10
- package/dist/lib/agent-modes.d.ts +0 -29
- package/dist/lib/agent-modes.js +0 -31
- package/dist/lib/agent-spec/agents.d.ts +0 -426
- package/dist/lib/agent-spec/agents.js +37 -924
- package/dist/lib/agent-spec/index.d.ts +0 -5
- package/dist/lib/agent-spec/index.js +0 -14
- package/dist/lib/agent-spec/materialize.d.ts +0 -10
- package/dist/lib/agent-spec/materialize.js +2 -139
- package/dist/lib/agent-spec/package-resolve.d.ts +0 -8
- package/dist/lib/agent-spec/package-resolve.js +1 -44
- package/dist/lib/agent-spec/package-schema.d.ts +0 -2
- package/dist/lib/agent-spec/package-schema.js +0 -11
- package/dist/lib/agent-spec/package-types.d.ts +0 -27
- package/dist/lib/agent-spec/package-types.js +0 -1
- package/dist/lib/agent-spec/primitives.d.ts +0 -18
- package/dist/lib/agent-spec/primitives.js +0 -27
- package/dist/lib/agent-spec/provider.js +0 -2
- package/dist/lib/agent-spec/resolve.d.ts +0 -23
- package/dist/lib/agent-spec/resolve.js +0 -41
- package/dist/lib/agent-spec/types.d.ts +0 -34
- package/dist/lib/agent-spec/types.js +0 -6
- package/dist/lib/agents.d.ts +0 -1
- package/dist/lib/agents.js +0 -1
- package/dist/lib/analytics/mix-commands.d.ts +0 -23
- package/dist/lib/analytics/mix-commands.js +0 -26
- package/dist/lib/analytics/recipes.d.ts +0 -1
- package/dist/lib/analytics/recipes.js +5 -10
- package/dist/lib/analytics/usage-db.js +4 -6
- package/dist/lib/answer-router.d.ts +0 -61
- package/dist/lib/answer-router.js +0 -57
- package/dist/lib/app-bundle-install.d.ts +0 -13
- package/dist/lib/app-bundle-install.js +0 -50
- package/dist/lib/artifact-actions.d.ts +0 -15
- package/dist/lib/artifact-actions.js +0 -18
- package/dist/lib/artifacts-client.d.ts +0 -10
- package/dist/lib/artifacts-client.js +0 -17
- package/dist/lib/ask-classifier.d.ts +0 -38
- package/dist/lib/ask-classifier.js +0 -27
- package/dist/lib/audit/log.d.ts +0 -47
- package/dist/lib/audit/log.js +1 -59
- package/dist/lib/auth-health.d.ts +0 -213
- package/dist/lib/auth-health.js +16 -266
- package/dist/lib/auth-mint.d.ts +0 -83
- package/dist/lib/auth-mint.js +8 -85
- package/dist/lib/auto-pull-worker.d.ts +0 -10
- package/dist/lib/auto-pull-worker.js +3 -48
- package/dist/lib/auto-pull.d.ts +0 -64
- package/dist/lib/auto-pull.js +0 -79
- package/dist/lib/beta.d.ts +0 -9
- package/dist/lib/beta.js +0 -9
- package/dist/lib/binary-shadow.d.ts +0 -22
- package/dist/lib/binary-shadow.js +4 -34
- package/dist/lib/boot-profile.d.ts +0 -10
- package/dist/lib/boot-profile.js +1 -31
- package/dist/lib/brand.d.ts +0 -20
- package/dist/lib/brand.js +0 -50
- package/dist/lib/browser/context.d.ts +0 -54
- package/dist/lib/browser/context.js +0 -49
- package/dist/lib/browser/paths.d.ts +0 -21
- package/dist/lib/browser/paths.js +0 -26
- package/dist/lib/browser/record.d.ts +0 -34
- package/dist/lib/browser/record.js +0 -38
- package/dist/lib/browser/sessions-list.d.ts +1 -79
- package/dist/lib/browser/sessions-list.js +2 -98
- package/dist/lib/browser-client.d.ts +0 -112
- package/dist/lib/browser-client.js +1 -90
- package/dist/lib/budget/config.d.ts +0 -6
- package/dist/lib/budget/config.js +0 -29
- package/dist/lib/budget/enforce.d.ts +0 -50
- package/dist/lib/budget/enforce.js +0 -27
- package/dist/lib/budget/ledger.d.ts +0 -23
- package/dist/lib/budget/ledger.js +0 -28
- package/dist/lib/budget/live-cloud.d.ts +0 -26
- package/dist/lib/budget/live-cloud.js +0 -12
- package/dist/lib/budget/live-team.d.ts +0 -17
- package/dist/lib/budget/live-team.js +1 -35
- package/dist/lib/budget/preflight.d.ts +0 -51
- package/dist/lib/budget/preflight.js +0 -36
- package/dist/lib/byok-usage.d.ts +0 -20
- package/dist/lib/byok-usage.js +1 -24
- package/dist/lib/capabilities.d.ts +0 -24
- package/dist/lib/capabilities.js +0 -29
- package/dist/lib/catchup.d.ts +0 -87
- package/dist/lib/catchup.js +1 -81
- package/dist/lib/channels/owner-forward.d.ts +0 -70
- package/dist/lib/channels/owner-forward.js +1 -43
- package/dist/lib/channels/owner-sink.js +0 -21
- package/dist/lib/channels/providers/desktop.d.ts +0 -37
- package/dist/lib/channels/providers/desktop.js +0 -72
- package/dist/lib/channels/providers/index.d.ts +0 -1
- package/dist/lib/channels/providers/index.js +2 -6
- package/dist/lib/channels/providers/mailbox.js +0 -7
- package/dist/lib/channels/providers/openclaw-telegram.js +0 -8
- package/dist/lib/channels/providers/rush.d.ts +0 -2
- package/dist/lib/channels/providers/rush.js +0 -26
- package/dist/lib/channels/providers/session.d.ts +2 -0
- package/dist/lib/channels/providers/session.js +26 -0
- package/dist/lib/channels/registry.d.ts +0 -27
- package/dist/lib/channels/registry.js +0 -10
- package/dist/lib/channels/resolve.d.ts +0 -19
- package/dist/lib/channels/resolve.js +0 -2
- package/dist/lib/channels/send.d.ts +0 -50
- package/dist/lib/channels/send.js +0 -25
- package/dist/lib/checkpoint.d.ts +0 -36
- package/dist/lib/checkpoint.js +0 -28
- package/dist/lib/claude-account-token.d.ts +0 -98
- package/dist/lib/claude-account-token.js +7 -169
- package/dist/lib/claude-statusline.d.ts +0 -41
- package/dist/lib/claude-statusline.js +0 -68
- package/dist/lib/cli-entry.d.ts +0 -26
- package/dist/lib/cli-entry.js +1 -61
- package/dist/lib/cli-resources.d.ts +31 -89
- package/dist/lib/cli-resources.js +116 -152
- package/dist/lib/cloud/antigravity.d.ts +0 -29
- package/dist/lib/cloud/antigravity.js +0 -36
- package/dist/lib/cloud/codex.d.ts +0 -8
- package/dist/lib/cloud/codex.js +0 -41
- package/dist/lib/cloud/cursor.d.ts +0 -5
- package/dist/lib/cloud/cursor.js +2 -7
- package/dist/lib/cloud/dispatch.d.ts +0 -13
- package/dist/lib/cloud/dispatch.js +1 -48
- package/dist/lib/cloud/factory.d.ts +0 -52
- package/dist/lib/cloud/factory.js +3 -67
- package/dist/lib/cloud/host.d.ts +0 -39
- package/dist/lib/cloud/host.js +5 -48
- package/dist/lib/cloud/registry.d.ts +0 -25
- package/dist/lib/cloud/registry.js +0 -29
- package/dist/lib/cloud/rush.d.ts +0 -47
- package/dist/lib/cloud/rush.js +1 -82
- package/dist/lib/cloud/session-index.d.ts +0 -27
- package/dist/lib/cloud/session-index.js +1 -32
- package/dist/lib/cloud/store.d.ts +0 -18
- package/dist/lib/cloud/store.js +0 -28
- package/dist/lib/cloud/stream.d.ts +0 -14
- package/dist/lib/cloud/stream.js +0 -25
- package/dist/lib/cloud/types.d.ts +0 -168
- package/dist/lib/cloud/types.js +0 -46
- package/dist/lib/cloudflare/creds.d.ts +0 -2
- package/dist/lib/cloudflare/creds.js +0 -14
- package/dist/lib/cloudflare/provision.d.ts +0 -8
- package/dist/lib/cloudflare/provision.js +1 -19
- package/dist/lib/codex-home.d.ts +0 -60
- package/dist/lib/codex-home.js +2 -72
- package/dist/lib/codex-policy.d.ts +0 -10
- package/dist/lib/codex-policy.js +0 -13
- package/dist/lib/command-skills.d.ts +0 -10
- package/dist/lib/command-skills.js +0 -10
- package/dist/lib/commands.d.ts +0 -74
- package/dist/lib/commands.js +0 -112
- package/dist/lib/comms-render.d.ts +0 -5
- package/dist/lib/comms-render.js +0 -5
- package/dist/lib/computer/context.d.ts +0 -23
- package/dist/lib/computer/context.js +0 -14
- package/dist/lib/computer/policy.d.ts +0 -20
- package/dist/lib/computer/policy.js +0 -53
- package/dist/lib/computer/record.d.ts +0 -33
- package/dist/lib/computer/record.js +1 -48
- package/dist/lib/computer/sessions-list.d.ts +1 -128
- package/dist/lib/computer/sessions-list.js +6 -165
- package/dist/lib/computer-client.d.ts +0 -106
- package/dist/lib/computer-client.js +5 -86
- package/dist/lib/concurrency.d.ts +0 -14
- package/dist/lib/concurrency.js +0 -3
- package/dist/lib/config-drift.d.ts +0 -31
- package/dist/lib/config-drift.js +0 -32
- package/dist/lib/config-keys.d.ts +1 -65
- package/dist/lib/config-keys.js +6 -52
- package/dist/lib/config-transfer.d.ts +0 -14
- package/dist/lib/config-transfer.js +0 -26
- package/dist/lib/convert.d.ts +0 -17
- package/dist/lib/convert.js +0 -18
- package/dist/lib/crabbox/cli.d.ts +0 -156
- package/dist/lib/crabbox/cli.js +1 -188
- package/dist/lib/crabbox/config.d.ts +0 -18
- package/dist/lib/crabbox/config.js +1 -19
- package/dist/lib/crabbox/lease.d.ts +0 -93
- package/dist/lib/crabbox/lease.js +4 -148
- package/dist/lib/crabbox/progress.d.ts +0 -56
- package/dist/lib/crabbox/progress.js +2 -49
- package/dist/lib/crabbox/runtimes.d.ts +0 -94
- package/dist/lib/crabbox/runtimes.js +1 -111
- package/dist/lib/crabbox/setup-copy.d.ts +0 -64
- package/dist/lib/crabbox/setup-copy.js +1 -53
- package/dist/lib/daemon/account-state-daemon-service.d.ts +0 -48
- package/dist/lib/daemon/account-state-daemon-service.js +2 -63
- package/dist/lib/daemon/attention-notify-service.d.ts +0 -12
- package/dist/lib/daemon/attention-notify-service.js +1 -24
- package/dist/lib/daemon/auth-sync-service.d.ts +0 -28
- package/dist/lib/daemon/auth-sync-service.js +0 -56
- package/dist/lib/daemon/catchup-service.d.ts +0 -34
- package/dist/lib/daemon/catchup-service.js +0 -30
- package/dist/lib/daemon/daemon.d.ts +0 -374
- package/dist/lib/daemon/daemon.js +44 -1049
- package/dist/lib/daemon/daemon.test-fixture.d.ts +0 -19
- package/dist/lib/daemon/daemon.test-fixture.js +1 -24
- package/dist/lib/daemon/device-probe-service.d.ts +0 -16
- package/dist/lib/daemon/device-probe-service.js +0 -20
- package/dist/lib/daemon/feed-stream-service.d.ts +0 -15
- package/dist/lib/daemon/feed-stream-service.js +0 -20
- package/dist/lib/daemon/harness-update-service.d.ts +2 -61
- package/dist/lib/daemon/harness-update-service.js +6 -86
- package/dist/lib/daemon/heartbeat-service.d.ts +0 -1
- package/dist/lib/daemon/heartbeat-service.js +0 -1
- package/dist/lib/daemon/leaked-daemons.d.ts +0 -46
- package/dist/lib/daemon/leaked-daemons.js +2 -78
- package/dist/lib/daemon/monitor-engine-service.d.ts +0 -13
- package/dist/lib/daemon/monitor-engine-service.js +3 -15
- package/dist/lib/daemon/runner.d.ts +0 -194
- package/dist/lib/daemon/runner.js +39 -760
- package/dist/lib/daemon/self-heal-service.d.ts +18 -17
- package/dist/lib/daemon/self-heal-service.js +83 -37
- package/dist/lib/daemon/self-update-service.d.ts +1 -131
- package/dist/lib/daemon/self-update-service.js +26 -189
- package/dist/lib/daemon/service.d.ts +0 -73
- package/dist/lib/daemon/service.js +0 -34
- package/dist/lib/daemon/session-index-service.d.ts +0 -12
- package/dist/lib/daemon/session-index-service.js +0 -23
- package/dist/lib/daemon/session-state-service.d.ts +0 -8
- package/dist/lib/daemon/session-state-service.js +0 -8
- package/dist/lib/daemon/session-summarizer-service.d.ts +0 -14
- package/dist/lib/daemon/session-summarizer-service.js +1 -19
- package/dist/lib/daemon/session-title-service.d.ts +0 -25
- package/dist/lib/daemon/session-title-service.js +0 -32
- package/dist/lib/daemon/state-dir-check-service.d.ts +0 -24
- package/dist/lib/daemon/state-dir-check-service.js +0 -29
- package/dist/lib/daemon/supervisor.d.ts +0 -112
- package/dist/lib/daemon/supervisor.js +4 -147
- package/dist/lib/daemon/testdata/process-view-daemon.js +0 -2
- package/dist/lib/daemon/testdata/process-view-start.js +0 -5
- package/dist/lib/daemon/tmux-reap-service.d.ts +0 -1
- package/dist/lib/daemon/tmux-reap-service.js +0 -1
- package/dist/lib/daemon/usage-sync-service.d.ts +0 -23
- package/dist/lib/daemon/usage-sync-service.js +0 -26
- package/dist/lib/daemon/watchdog-service.d.ts +0 -9
- package/dist/lib/daemon/watchdog-service.js +0 -13
- package/dist/lib/daemon/webhook-receiver-service.d.ts +0 -1
- package/dist/lib/daemon/webhook-receiver-service.js +0 -1
- package/dist/lib/daemon-health.d.ts +0 -55
- package/dist/lib/daemon-health.js +4 -77
- package/dist/lib/daemon-services.d.ts +0 -36
- package/dist/lib/daemon-services.js +0 -45
- package/dist/lib/daemon-ticks.d.ts +0 -126
- package/dist/lib/daemon-ticks.js +1 -156
- package/dist/lib/daemon-webhooks.d.ts +0 -57
- package/dist/lib/daemon-webhooks.js +2 -54
- package/dist/lib/deeplink/register.d.ts +0 -15
- package/dist/lib/deeplink/register.js +0 -60
- package/dist/lib/deeplink/url.d.ts +0 -24
- package/dist/lib/deeplink/url.js +1 -33
- package/dist/lib/device-config.d.ts +1 -192
- package/dist/lib/device-config.js +86 -240
- package/dist/lib/devices/config-migration.d.ts +0 -43
- package/dist/lib/devices/config-migration.js +7 -127
- package/dist/lib/devices/connect.d.ts +0 -168
- package/dist/lib/devices/connect.js +6 -254
- package/dist/lib/devices/device-docs.d.ts +0 -24
- package/dist/lib/devices/device-docs.js +0 -46
- package/dist/lib/devices/discovery-policy.d.ts +0 -30
- package/dist/lib/devices/discovery-policy.js +3 -34
- package/dist/lib/devices/doctor-findings.d.ts +0 -109
- package/dist/lib/devices/doctor-findings.js +30 -326
- package/dist/lib/devices/doctor-overview-cache.d.ts +0 -33
- package/dist/lib/devices/doctor-overview-cache.js +0 -81
- package/dist/lib/devices/fleet-divergence.d.ts +0 -89
- package/dist/lib/devices/fleet-divergence.js +1 -60
- package/dist/lib/devices/fleet-inventory.d.ts +0 -27
- package/dist/lib/devices/fleet-inventory.js +0 -38
- package/dist/lib/devices/fleet.d.ts +0 -95
- package/dist/lib/devices/fleet.js +0 -79
- package/dist/lib/devices/harness-inventory.d.ts +0 -75
- package/dist/lib/devices/harness-inventory.js +0 -101
- package/dist/lib/devices/health-report.d.ts +0 -37
- package/dist/lib/devices/health-report.js +0 -73
- package/dist/lib/devices/health.d.ts +0 -71
- package/dist/lib/devices/health.js +2 -82
- package/dist/lib/devices/interactive-host.d.ts +0 -10
- package/dist/lib/devices/interactive-host.js +0 -34
- package/dist/lib/devices/known-hosts.d.ts +0 -45
- package/dist/lib/devices/known-hosts.js +0 -77
- package/dist/lib/devices/pending.d.ts +0 -31
- package/dist/lib/devices/pending.js +7 -67
- package/dist/lib/devices/pool.d.ts +0 -36
- package/dist/lib/devices/pool.js +0 -30
- package/dist/lib/devices/reachability.d.ts +0 -25
- package/dist/lib/devices/reachability.js +0 -14
- package/dist/lib/devices/registry.d.ts +0 -141
- package/dist/lib/devices/registry.js +4 -151
- package/dist/lib/devices/resolve-profile.d.ts +0 -18
- package/dist/lib/devices/resolve-profile.js +0 -18
- package/dist/lib/devices/resolve-target.d.ts +0 -20
- package/dist/lib/devices/resolve-target.js +1 -51
- package/dist/lib/devices/rollout-verify.d.ts +1 -81
- package/dist/lib/devices/rollout-verify.js +0 -77
- package/dist/lib/devices/self-host.d.ts +0 -7
- package/dist/lib/devices/self-host.js +1 -29
- package/dist/lib/devices/ssh-config.d.ts +0 -18
- package/dist/lib/devices/ssh-config.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +0 -48
- package/dist/lib/devices/stats-cache.js +0 -80
- package/dist/lib/devices/sync.d.ts +0 -97
- package/dist/lib/devices/sync.js +0 -88
- package/dist/lib/devices/tailscale.d.ts +0 -18
- package/dist/lib/devices/tailscale.js +1 -38
- package/dist/lib/devices/terminfo.d.ts +0 -27
- package/dist/lib/devices/terminfo.js +2 -64
- package/dist/lib/devices/worker-pick.d.ts +0 -15
- package/dist/lib/devices/worker-pick.js +0 -48
- package/dist/lib/diff-text.d.ts +0 -18
- package/dist/lib/diff-text.js +0 -15
- package/dist/lib/doctor-diff.d.ts +0 -79
- package/dist/lib/doctor-diff.js +2 -181
- package/dist/lib/drift-sync.d.ts +0 -29
- package/dist/lib/drift-sync.js +0 -40
- package/dist/lib/drift.d.ts +0 -43
- package/dist/lib/drift.js +0 -31
- package/dist/lib/event-families.d.ts +0 -17
- package/dist/lib/event-families.js +0 -33
- package/dist/lib/event-provenance.d.ts +0 -4
- package/dist/lib/event-provenance.js +0 -5
- package/dist/lib/event-stream.d.ts +0 -29
- package/dist/lib/event-stream.js +0 -36
- package/dist/lib/events-ingest.d.ts +0 -27
- package/dist/lib/events-ingest.js +0 -54
- package/dist/lib/exec-account-home.d.ts +0 -24
- package/dist/lib/exec-account-home.js +0 -41
- package/dist/lib/exec-bounded.d.ts +1 -37
- package/dist/lib/exec-bounded.js +10 -46
- package/dist/lib/exec.d.ts +3 -583
- package/dist/lib/exec.js +18 -1025
- package/dist/lib/factory/snapshot.js +1 -15
- package/dist/lib/feed/activity-stream.d.ts +0 -27
- package/dist/lib/feed/activity-stream.js +4 -80
- package/dist/lib/feed/activity.d.ts +1 -243
- package/dist/lib/feed/activity.js +8 -273
- package/dist/lib/feed/answer.d.ts +0 -103
- package/dist/lib/feed/answer.js +0 -267
- package/dist/lib/feed/attention.d.ts +0 -56
- package/dist/lib/feed/attention.js +0 -161
- package/dist/lib/feed/envelope.d.ts +2 -17
- package/dist/lib/feed/envelope.js +0 -14
- package/dist/lib/feed/events.d.ts +0 -118
- package/dist/lib/feed/events.js +13 -213
- package/dist/lib/feed/feed.d.ts +1 -250
- package/dist/lib/feed/feed.js +24 -334
- package/dist/lib/feed/hub-server.d.ts +0 -100
- package/dist/lib/feed/hub-server.js +1 -191
- package/dist/lib/feed/hub.d.ts +0 -53
- package/dist/lib/feed/hub.js +0 -89
- package/dist/lib/feed/pr-status.d.ts +1 -19
- package/dist/lib/feed/pr-status.js +0 -18
- package/dist/lib/feed/tool-activity.d.ts +0 -59
- package/dist/lib/feed/tool-activity.js +1 -119
- package/dist/lib/feed/tools.d.ts +0 -100
- package/dist/lib/feed/tools.js +0 -89
- package/dist/lib/feed/watch.d.ts +0 -33
- package/dist/lib/feed/watch.js +2 -94
- package/dist/lib/feed-broadcast.d.ts +0 -148
- package/dist/lib/feed-broadcast.js +1 -283
- package/dist/lib/feed-outcome.d.ts +0 -50
- package/dist/lib/feed-outcome.js +1 -67
- package/dist/lib/feed-policy.d.ts +0 -6
- package/dist/lib/feed-policy.js +1 -27
- package/dist/lib/feed-post.d.ts +0 -61
- package/dist/lib/feed-post.js +4 -61
- package/dist/lib/feed-ranking.d.ts +1 -1
- package/dist/lib/fleet/apply.d.ts +0 -64
- package/dist/lib/fleet/apply.js +0 -125
- package/dist/lib/fleet/auth-sync.d.ts +0 -48
- package/dist/lib/fleet/auth-sync.js +4 -49
- package/dist/lib/fleet/capture.d.ts +0 -26
- package/dist/lib/fleet/capture.js +1 -36
- package/dist/lib/fleet/manifest.d.ts +0 -38
- package/dist/lib/fleet/manifest.js +0 -40
- package/dist/lib/fleet/types.d.ts +1 -138
- package/dist/lib/fleet/types.js +0 -12
- package/dist/lib/fleet-cache.d.ts +0 -12
- package/dist/lib/fleet-cache.js +0 -24
- package/dist/lib/fleet-shared-state.d.ts +6 -59
- package/dist/lib/fleet-shared-state.js +5 -51
- package/dist/lib/fleet-status.d.ts +0 -27
- package/dist/lib/fleet-status.js +0 -48
- package/dist/lib/format.d.ts +0 -74
- package/dist/lib/format.js +3 -77
- package/dist/lib/friction-heuristics.d.ts +0 -20
- package/dist/lib/friction-heuristics.js +0 -8
- package/dist/lib/fs-atomic.d.ts +0 -51
- package/dist/lib/fs-atomic.js +8 -66
- package/dist/lib/fs-walk.d.ts +0 -13
- package/dist/lib/fs-walk.js +0 -22
- package/dist/lib/fuzzy.d.ts +0 -29
- package/dist/lib/fuzzy.js +0 -31
- package/dist/lib/gemini-settings.js +0 -12
- package/dist/lib/git.d.ts +0 -366
- package/dist/lib/git.js +7 -574
- package/dist/lib/github/ci-failure.d.ts +30 -0
- package/dist/lib/github/ci-failure.js +95 -0
- package/dist/lib/github/gh-overload.d.ts +0 -38
- package/dist/lib/github/gh-overload.js +3 -55
- package/dist/lib/github/pr-mergeable.d.ts +3 -34
- package/dist/lib/github/pr-mergeable.js +2 -42
- package/dist/lib/github/pr-verdict.d.ts +0 -44
- package/dist/lib/github/pr-verdict.js +0 -36
- package/dist/lib/github/project-prs.d.ts +135 -105
- package/dist/lib/github/project-prs.js +530 -170
- package/dist/lib/github/release-drift.d.ts +45 -0
- package/dist/lib/github/release-drift.js +91 -0
- package/dist/lib/github/rest.d.ts +0 -50
- package/dist/lib/github/rest.js +0 -49
- package/dist/lib/github/viewer.d.ts +27 -0
- package/dist/lib/github/viewer.js +101 -0
- package/dist/lib/goose-commands.d.ts +0 -29
- package/dist/lib/goose-commands.js +3 -48
- package/dist/lib/harness/adapter.d.ts +0 -151
- package/dist/lib/harness/adapter.js +0 -41
- package/dist/lib/harness/adapters/claude.d.ts +0 -8
- package/dist/lib/harness/adapters/claude.js +0 -89
- package/dist/lib/harness/adapters/codex.js +0 -18
- package/dist/lib/harness/adapters/copilot.js +0 -3
- package/dist/lib/harness/adapters/cursor.js +0 -16
- package/dist/lib/harness/adapters/droid.js +0 -3
- package/dist/lib/harness/adapters/kimi.js +0 -19
- package/dist/lib/harness/adapters/muse.js +0 -9
- package/dist/lib/harness/adapters/opencode.js +0 -2
- package/dist/lib/harness/exec-config-version.d.ts +0 -2
- package/dist/lib/harness/exec-config-version.js +0 -13
- package/dist/lib/harness/index.js +0 -10
- package/dist/lib/harness-auth-capabilities.d.ts +0 -26
- package/dist/lib/harness-auth-capabilities.js +0 -18
- package/dist/lib/harness-connection-test.d.ts +0 -27
- package/dist/lib/harness-connection-test.js +2 -20
- package/dist/lib/heal.d.ts +0 -45
- package/dist/lib/heal.js +2 -82
- package/dist/lib/help.d.ts +0 -32
- package/dist/lib/help.js +0 -41
- package/dist/lib/helper-download.d.ts +0 -85
- package/dist/lib/helper-download.js +0 -95
- package/dist/lib/helper-versions.d.ts +0 -51
- package/dist/lib/helper-versions.js +1 -45
- package/dist/lib/hooks/cache.d.ts +0 -40
- package/dist/lib/hooks/cache.js +8 -122
- package/dist/lib/hooks/install.d.ts +0 -253
- package/dist/lib/hooks/install.js +49 -822
- package/dist/lib/hooks/match.d.ts +0 -24
- package/dist/lib/hooks/match.js +0 -31
- package/dist/lib/hooks/profile.d.ts +0 -21
- package/dist/lib/hooks/profile.js +0 -28
- package/dist/lib/hosts/credential-transport.d.ts +0 -6
- package/dist/lib/hosts/credential-transport.js +0 -17
- package/dist/lib/hosts/credentials.d.ts +0 -35
- package/dist/lib/hosts/credentials.js +0 -38
- package/dist/lib/hosts/dispatch.d.ts +0 -140
- package/dist/lib/hosts/dispatch.js +0 -176
- package/dist/lib/hosts/logs.d.ts +0 -25
- package/dist/lib/hosts/logs.js +1 -51
- package/dist/lib/hosts/option.d.ts +0 -12
- package/dist/lib/hosts/option.js +0 -12
- package/dist/lib/hosts/passthrough.d.ts +0 -129
- package/dist/lib/hosts/passthrough.js +12 -249
- package/dist/lib/hosts/progress.d.ts +0 -90
- package/dist/lib/hosts/progress.js +5 -115
- package/dist/lib/hosts/providers/devices.d.ts +0 -19
- package/dist/lib/hosts/providers/devices.js +1 -34
- package/dist/lib/hosts/providers/local.d.ts +0 -14
- package/dist/lib/hosts/providers/local.js +0 -22
- package/dist/lib/hosts/ready.d.ts +0 -99
- package/dist/lib/hosts/ready.js +0 -101
- package/dist/lib/hosts/reconcile.d.ts +0 -38
- package/dist/lib/hosts/reconcile.js +1 -50
- package/dist/lib/hosts/reconnect.d.ts +0 -75
- package/dist/lib/hosts/reconnect.js +0 -98
- package/dist/lib/hosts/registry.d.ts +0 -74
- package/dist/lib/hosts/registry.js +0 -110
- package/dist/lib/hosts/remote-cmd.d.ts +1 -228
- package/dist/lib/hosts/remote-cmd.js +19 -294
- package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +0 -1
- package/dist/lib/hosts/remote-cmd.test-fixture.js +0 -12
- package/dist/lib/hosts/remote-os.d.ts +0 -19
- package/dist/lib/hosts/remote-os.js +0 -22
- package/dist/lib/hosts/remote-session-id.d.ts +0 -22
- package/dist/lib/hosts/remote-session-id.js +0 -24
- package/dist/lib/hosts/routing-flag.d.ts +0 -21
- package/dist/lib/hosts/routing-flag.js +0 -21
- package/dist/lib/hosts/run-target.d.ts +0 -53
- package/dist/lib/hosts/run-target.js +0 -56
- package/dist/lib/hosts/session-index.d.ts +1 -53
- package/dist/lib/hosts/session-index.js +2 -59
- package/dist/lib/hosts/session-marker.d.ts +0 -31
- package/dist/lib/hosts/session-marker.js +0 -32
- package/dist/lib/hosts/ssh-config.d.ts +0 -25
- package/dist/lib/hosts/ssh-config.js +1 -32
- package/dist/lib/hosts/tasks.d.ts +0 -54
- package/dist/lib/hosts/tasks.js +0 -35
- package/dist/lib/hosts/types.d.ts +0 -39
- package/dist/lib/hosts/types.js +0 -22
- package/dist/lib/humans.d.ts +0 -21
- package/dist/lib/humans.js +0 -30
- package/dist/lib/identity/client.d.ts +1 -35
- package/dist/lib/identity/client.js +3 -29
- package/dist/lib/identity/index.d.ts +3 -27
- package/dist/lib/identity/index.js +8 -24
- package/dist/lib/import.d.ts +0 -104
- package/dist/lib/import.js +1 -137
- package/dist/lib/installations/active-check.d.ts +0 -53
- package/dist/lib/installations/active-check.js +0 -49
- package/dist/lib/installations/index.d.ts +0 -9
- package/dist/lib/installations/index.js +0 -9
- package/dist/lib/installations/installation-lock.js +0 -8
- package/dist/lib/installations/launch-gate.d.ts +0 -56
- package/dist/lib/installations/launch-gate.js +0 -83
- package/dist/lib/installations/migrate.d.ts +0 -100
- package/dist/lib/installations/migrate.js +105 -572
- package/dist/lib/installations/native-command.d.ts +0 -1
- package/dist/lib/installations/native-command.js +0 -6
- package/dist/lib/installations/resolve.d.ts +0 -24
- package/dist/lib/installations/resolve.js +0 -24
- package/dist/lib/installations/shims.d.ts +0 -395
- package/dist/lib/installations/shims.js +30 -957
- package/dist/lib/installations/store.d.ts +0 -199
- package/dist/lib/installations/store.js +1 -297
- package/dist/lib/installations/strategies.d.ts +0 -44
- package/dist/lib/installations/strategies.js +0 -87
- package/dist/lib/installations/types.d.ts +1 -67
- package/dist/lib/installations/types.js +0 -6
- package/dist/lib/installations/update-cancellation.d.ts +0 -74
- package/dist/lib/installations/update-cancellation.js +0 -74
- package/dist/lib/installations/update-policy.d.ts +0 -57
- package/dist/lib/installations/update-policy.js +0 -62
- package/dist/lib/installations/update-runtime.d.ts +0 -88
- package/dist/lib/installations/update-runtime.js +1 -127
- package/dist/lib/installations/update.d.ts +0 -53
- package/dist/lib/installations/update.js +0 -95
- package/dist/lib/installations/versions.d.ts +0 -310
- package/dist/lib/installations/versions.js +14 -837
- package/dist/lib/isolation-boundary-report.d.ts +0 -7
- package/dist/lib/isolation-boundary-report.js +0 -7
- package/dist/lib/launch-identity.d.ts +0 -8
- package/dist/lib/launch-identity.js +0 -8
- package/dist/lib/linear-autoclose.d.ts +0 -25
- package/dist/lib/linear-autoclose.js +0 -19
- package/dist/lib/linear-cache.d.ts +0 -57
- package/dist/lib/linear-cache.js +1 -81
- package/dist/lib/linear-project-counts.d.ts +0 -83
- package/dist/lib/linear-project-counts.js +2 -64
- package/dist/lib/linear-projects.d.ts +0 -62
- package/dist/lib/linear-projects.js +0 -85
- package/dist/lib/linear-rate-limit.d.ts +0 -21
- package/dist/lib/linear-rate-limit.js +1 -93
- package/dist/lib/linux-userns.d.ts +1 -42
- package/dist/lib/linux-userns.js +0 -47
- package/dist/lib/lock-compromise.d.ts +0 -7
- package/dist/lib/lock-compromise.js +0 -7
- package/dist/lib/log-follow.d.ts +0 -2
- package/dist/lib/log-follow.js +5 -15
- package/dist/lib/loop.d.ts +0 -106
- package/dist/lib/loop.js +0 -135
- package/dist/lib/machine-id.d.ts +0 -19
- package/dist/lib/machine-id.js +0 -19
- package/dist/lib/mailbox-gc.d.ts +0 -7
- package/dist/lib/mailbox-gc.js +0 -25
- package/dist/lib/mailbox-target.d.ts +0 -31
- package/dist/lib/mailbox-target.js +0 -27
- package/dist/lib/mailbox.d.ts +0 -77
- package/dist/lib/mailbox.js +5 -105
- package/dist/lib/manifest.d.ts +0 -4
- package/dist/lib/manifest.js +0 -36
- package/dist/lib/markdown.d.ts +0 -3
- package/dist/lib/markdown.js +0 -9
- package/dist/lib/mcp-registry.d.ts +1 -50
- package/dist/lib/mcp-registry.js +0 -64
- package/dist/lib/mcp.d.ts +0 -115
- package/dist/lib/mcp.js +3 -199
- package/dist/lib/memory-cache.d.ts +0 -11
- package/dist/lib/memory-cache.js +0 -9
- package/dist/lib/memory.d.ts +0 -52
- package/dist/lib/memory.js +12 -74
- package/dist/lib/menubar/download-menubar.d.ts +0 -35
- package/dist/lib/menubar/download-menubar.js +0 -38
- package/dist/lib/menubar/install-menubar.d.ts +0 -405
- package/dist/lib/menubar/install-menubar.js +25 -667
- package/dist/lib/menubar/migrate-prefs.d.ts +0 -47
- package/dist/lib/menubar/migrate-prefs.js +1 -70
- package/dist/lib/menubar/notify-desktop.d.ts +0 -82
- package/dist/lib/menubar/notify-desktop.js +0 -68
- package/dist/lib/menubar/resolve-version.d.ts +0 -41
- package/dist/lib/menubar/resolve-version.js +2 -44
- package/dist/lib/menubar/snapshot.d.ts +13 -66
- package/dist/lib/menubar/snapshot.js +45 -39
- package/dist/lib/merged-resources.d.ts +0 -10
- package/dist/lib/merged-resources.js +0 -10
- package/dist/lib/migrate-fold.d.ts +0 -31
- package/dist/lib/migrate-fold.js +6 -38
- package/dist/lib/model-tier-overrides.d.ts +0 -30
- package/dist/lib/model-tier-overrides.js +0 -14
- package/dist/lib/model-tiers.d.ts +0 -43
- package/dist/lib/model-tiers.js +5 -78
- package/dist/lib/models.d.ts +0 -145
- package/dist/lib/models.js +3 -333
- package/dist/lib/monitors/config.d.ts +0 -187
- package/dist/lib/monitors/config.js +4 -101
- package/dist/lib/monitors/dispatch.d.ts +0 -22
- package/dist/lib/monitors/dispatch.js +0 -35
- package/dist/lib/monitors/engine.d.ts +1 -53
- package/dist/lib/monitors/engine.js +17 -106
- package/dist/lib/monitors/fingerprint.d.ts +0 -43
- package/dist/lib/monitors/fingerprint.js +0 -57
- package/dist/lib/monitors/pid-watch.d.ts +0 -31
- package/dist/lib/monitors/pid-watch.js +0 -31
- package/dist/lib/monitors/remote.d.ts +0 -52
- package/dist/lib/monitors/remote.js +0 -42
- package/dist/lib/monitors/sources/command.d.ts +2 -9
- package/dist/lib/monitors/sources/command.js +13 -34
- package/dist/lib/monitors/sources/device.d.ts +0 -10
- package/dist/lib/monitors/sources/device.js +0 -14
- package/dist/lib/monitors/sources/failure.d.ts +0 -27
- package/dist/lib/monitors/sources/failure.js +0 -27
- package/dist/lib/monitors/sources/file.d.ts +0 -10
- package/dist/lib/monitors/sources/file.js +0 -11
- package/dist/lib/monitors/sources/http.d.ts +0 -7
- package/dist/lib/monitors/sources/http.js +0 -9
- package/dist/lib/monitors/sources/index.d.ts +0 -9
- package/dist/lib/monitors/sources/index.js +0 -9
- package/dist/lib/monitors/sources/poll.d.ts +0 -8
- package/dist/lib/monitors/sources/poll.js +0 -8
- package/dist/lib/monitors/sources/types.d.ts +0 -18
- package/dist/lib/monitors/sources/types.js +0 -8
- package/dist/lib/monitors/sources/webhook.d.ts +0 -7
- package/dist/lib/monitors/sources/webhook.js +0 -7
- package/dist/lib/monitors/sources/ws.d.ts +0 -10
- package/dist/lib/monitors/sources/ws.js +3 -13
- package/dist/lib/monitors/state.d.ts +0 -132
- package/dist/lib/monitors/state.js +2 -86
- package/dist/lib/native-accounts.d.ts +0 -32
- package/dist/lib/native-accounts.js +0 -17
- package/dist/lib/net-close.d.ts +0 -9
- package/dist/lib/net-close.js +0 -5
- package/dist/lib/notify.d.ts +0 -47
- package/dist/lib/notify.js +0 -30
- package/dist/lib/onepassword.d.ts +0 -14
- package/dist/lib/onepassword.js +0 -12
- package/dist/lib/open-url.d.ts +0 -38
- package/dist/lib/open-url.js +1 -69
- package/dist/lib/openclaw-keychain.js +2 -7
- package/dist/lib/operator.d.ts +0 -9
- package/dist/lib/operator.js +0 -34
- package/dist/lib/origin-machine.d.ts +0 -16
- package/dist/lib/origin-machine.js +0 -16
- package/dist/lib/output/git-output.d.ts +0 -31
- package/dist/lib/output/git-output.js +4 -43
- package/dist/lib/overdue.d.ts +0 -35
- package/dist/lib/overdue.js +0 -60
- package/dist/lib/owner-message.d.ts +0 -20
- package/dist/lib/owner-message.js +1 -40
- package/dist/lib/packages/output-home.d.ts +0 -28
- package/dist/lib/packages/output-home.js +0 -114
- package/dist/lib/paths.d.ts +0 -26
- package/dist/lib/paths.js +1 -27
- package/dist/lib/percentile.d.ts +0 -11
- package/dist/lib/percentile.js +0 -11
- package/dist/lib/perf/db.d.ts +0 -16
- package/dist/lib/perf/db.js +3 -32
- package/dist/lib/perf/spool.d.ts +0 -12
- package/dist/lib/perf/spool.js +0 -13
- package/dist/lib/perf/types.d.ts +0 -26
- package/dist/lib/perf/types.js +0 -1
- package/dist/lib/permissions-registry.d.ts +0 -48
- package/dist/lib/permissions-registry.js +0 -108
- package/dist/lib/permissions.d.ts +0 -215
- package/dist/lib/permissions.js +4 -407
- package/dist/lib/picker.d.ts +0 -124
- package/dist/lib/picker.js +0 -140
- package/dist/lib/placement.d.ts +0 -57
- package/dist/lib/placement.js +0 -47
- package/dist/lib/platform/exec.d.ts +0 -73
- package/dist/lib/platform/exec.js +0 -80
- package/dist/lib/platform/index.d.ts +0 -14
- package/dist/lib/platform/index.js +0 -14
- package/dist/lib/platform/ipc.d.ts +0 -10
- package/dist/lib/platform/ipc.js +0 -13
- package/dist/lib/platform/links.d.ts +0 -14
- package/dist/lib/platform/links.js +0 -25
- package/dist/lib/platform/paths.d.ts +0 -48
- package/dist/lib/platform/paths.js +0 -52
- package/dist/lib/platform/posixpath.d.ts +0 -18
- package/dist/lib/platform/posixpath.js +0 -62
- package/dist/lib/platform/process.d.ts +0 -67
- package/dist/lib/platform/process.js +5 -99
- package/dist/lib/platform/winpath.d.ts +0 -50
- package/dist/lib/platform/winpath.js +0 -81
- package/dist/lib/plugins/plugin-marketplace.d.ts +0 -165
- package/dist/lib/plugins/plugin-marketplace.js +10 -238
- package/dist/lib/plugins/plugins.d.ts +0 -238
- package/dist/lib/plugins/plugins.js +20 -454
- package/dist/lib/plugins/skills.d.ts +0 -86
- package/dist/lib/plugins/skills.js +1 -139
- package/dist/lib/pr-land-detach.d.ts +0 -17
- package/dist/lib/pr-land-detach.js +0 -30
- package/dist/lib/pricing/cost.d.ts +0 -29
- package/dist/lib/pricing/cost.js +0 -38
- package/dist/lib/pricing/index.d.ts +0 -6
- package/dist/lib/pricing/index.js +0 -6
- package/dist/lib/pricing/table.d.ts +0 -8
- package/dist/lib/pricing/table.js +3 -56
- package/dist/lib/probe.d.ts +0 -5
- package/dist/lib/probe.js +0 -40
- package/dist/lib/profiles-presets.d.ts +0 -20
- package/dist/lib/profiles-presets.js +1 -36
- package/dist/lib/profiles.d.ts +0 -185
- package/dist/lib/profiles.js +1 -158
- package/dist/lib/project-doctor.d.ts +0 -29
- package/dist/lib/project-doctor.js +0 -26
- package/dist/lib/project-focus.d.ts +0 -41
- package/dist/lib/project-focus.js +0 -49
- package/dist/lib/project-import.d.ts +0 -41
- package/dist/lib/project-import.js +0 -36
- package/dist/lib/project-key.d.ts +0 -65
- package/dist/lib/project-key.js +0 -70
- package/dist/lib/project-launch.d.ts +0 -49
- package/dist/lib/project-launch.js +2 -91
- package/dist/lib/project-probe.d.ts +0 -86
- package/dist/lib/project-probe.js +1 -88
- package/dist/lib/project-pull.d.ts +0 -96
- package/dist/lib/project-pull.js +0 -122
- package/dist/lib/project-resources.d.ts +0 -44
- package/dist/lib/project-resources.js +7 -171
- package/dist/lib/project-root.d.ts +0 -95
- package/dist/lib/project-root.js +0 -101
- package/dist/lib/project-schedule.d.ts +0 -43
- package/dist/lib/project-schedule.js +0 -52
- package/dist/lib/project-status.d.ts +0 -108
- package/dist/lib/project-status.js +0 -117
- package/dist/lib/projects.d.ts +0 -197
- package/dist/lib/projects.js +1 -213
- package/dist/lib/pwsh.d.ts +0 -41
- package/dist/lib/pwsh.js +0 -51
- package/dist/lib/quick-todo.d.ts +84 -0
- package/dist/lib/quick-todo.js +243 -0
- package/dist/lib/reconcile-and-repair.d.ts +0 -78
- package/dist/lib/reconcile-and-repair.js +3 -66
- package/dist/lib/redact.d.ts +0 -29
- package/dist/lib/redact.js +0 -54
- package/dist/lib/refresh-coordinator.d.ts +0 -15
- package/dist/lib/refresh-coordinator.js +0 -10
- package/dist/lib/refresh.d.ts +0 -39
- package/dist/lib/refresh.js +0 -45
- package/dist/lib/registry.d.ts +0 -65
- package/dist/lib/registry.js +0 -127
- package/dist/lib/reminders.d.ts +0 -21
- package/dist/lib/reminders.js +0 -30
- package/dist/lib/remote-agents-json.d.ts +0 -44
- package/dist/lib/remote-agents-json.js +1 -44
- package/dist/lib/reserved-stores.d.ts +0 -28
- package/dist/lib/reserved-stores.js +2 -28
- package/dist/lib/resource-aliases.d.ts +0 -12
- package/dist/lib/resource-aliases.js +0 -12
- package/dist/lib/resource-content-diff.d.ts +0 -28
- package/dist/lib/resource-content-diff.js +7 -34
- package/dist/lib/resource-inventory.d.ts +0 -51
- package/dist/lib/resource-inventory.js +0 -45
- package/dist/lib/resource-patterns.d.ts +0 -32
- package/dist/lib/resource-patterns.js +0 -34
- package/dist/lib/resource-profiles.d.ts +0 -7
- package/dist/lib/resource-profiles.js +0 -11
- package/dist/lib/resources/types.d.ts +0 -50
- package/dist/lib/resources/types.js +0 -7
- package/dist/lib/resources/workflows.d.ts +0 -7
- package/dist/lib/resources/workflows.js +0 -13
- package/dist/lib/resources.d.ts +0 -65
- package/dist/lib/resources.js +0 -78
- package/dist/lib/routers.d.ts +0 -67
- package/dist/lib/routers.js +0 -60
- package/dist/lib/routine-activation.d.ts +0 -23
- package/dist/lib/routine-activation.js +0 -24
- package/dist/lib/routine-context.d.ts +1 -91
- package/dist/lib/routine-context.js +0 -107
- package/dist/lib/routine-notify-owner.d.ts +0 -79
- package/dist/lib/routine-notify-owner.js +1 -103
- package/dist/lib/routine-notify.d.ts +0 -70
- package/dist/lib/routine-notify.js +1 -75
- package/dist/lib/routine-process-cleanup.d.ts +0 -1
- package/dist/lib/routine-process-cleanup.js +1 -4
- package/dist/lib/routine-readiness.d.ts +0 -63
- package/dist/lib/routine-readiness.js +4 -86
- package/dist/lib/routines-format.d.ts +0 -40
- package/dist/lib/routines-format.js +0 -67
- package/dist/lib/routines-placement.d.ts +0 -33
- package/dist/lib/routines-placement.js +0 -40
- package/dist/lib/routines-project.d.ts +0 -57
- package/dist/lib/routines-project.js +6 -80
- package/dist/lib/rules/compile.d.ts +0 -66
- package/dist/lib/rules/compile.js +6 -118
- package/dist/lib/rules/compose.d.ts +0 -56
- package/dist/lib/rules/compose.js +2 -83
- package/dist/lib/rules/rules.d.ts +0 -23
- package/dist/lib/rules/rules.js +0 -35
- package/dist/lib/rules/run-sync.d.ts +0 -16
- package/dist/lib/rules/run-sync.js +1 -44
- package/dist/lib/run-config.d.ts +0 -7
- package/dist/lib/run-config.js +0 -8
- package/dist/lib/run-defaults.d.ts +0 -14
- package/dist/lib/run-defaults.js +0 -14
- package/dist/lib/run-notify.d.ts +0 -18
- package/dist/lib/run-notify.js +0 -36
- package/dist/lib/run-trace-sync.d.ts +0 -21
- package/dist/lib/run-trace-sync.js +2 -66
- package/dist/lib/rush-session.d.ts +0 -26
- package/dist/lib/rush-session.js +0 -28
- package/dist/lib/sandbox.d.ts +0 -45
- package/dist/lib/sandbox.js +5 -93
- package/dist/lib/scheduler.d.ts +0 -28
- package/dist/lib/scheduler.js +0 -45
- package/dist/lib/scheduling/routines.d.ts +0 -652
- package/dist/lib/scheduling/routines.js +8 -475
- package/dist/lib/secrets-cli.d.ts +3 -8
- package/dist/lib/secrets-cli.js +1 -19
- package/dist/lib/secrets-client.d.ts +0 -117
- package/dist/lib/secrets-client.js +4 -275
- package/dist/lib/secrets-policy.d.ts +0 -142
- package/dist/lib/secrets-policy.js +4 -237
- package/dist/lib/secrets-types.d.ts +0 -87
- package/dist/lib/secrets-types.js +0 -13
- package/dist/lib/self-heal/checks/hook-manifest.js +0 -25
- package/dist/lib/self-heal/checks/hook-runtime.js +0 -5
- package/dist/lib/self-heal/checks/install-staging.d.ts +0 -1
- package/dist/lib/self-heal/checks/install-staging.js +1 -28
- package/dist/lib/self-heal/checks/menubar-helper.js +0 -5
- package/dist/lib/self-heal/checks/path.js +0 -9
- package/dist/lib/self-heal/checks/resources.js +0 -5
- package/dist/lib/self-heal/checks/shadowing.js +2 -16
- package/dist/lib/self-heal/checks/shims.js +0 -14
- package/dist/lib/self-heal/child.d.ts +12 -0
- package/dist/lib/self-heal/child.js +23 -0
- package/dist/lib/self-heal/registry.d.ts +0 -9
- package/dist/lib/self-heal/registry.js +0 -16
- package/dist/lib/self-heal/types.d.ts +0 -12
- package/dist/lib/self-heal/types.js +0 -15
- package/dist/lib/self-update.d.ts +0 -312
- package/dist/lib/self-update.js +9 -360
- package/dist/lib/service-manifest.d.ts +0 -39
- package/dist/lib/service-manifest.js +0 -72
- package/dist/lib/session/active.d.ts +13 -869
- package/dist/lib/session/active.js +20 -976
- package/dist/lib/session/actor-sidecar.d.ts +1 -32
- package/dist/lib/session/actor-sidecar.js +1 -41
- package/dist/lib/session/bookmarks.d.ts +0 -33
- package/dist/lib/session/bookmarks.js +0 -37
- package/dist/lib/session/bundle.d.ts +0 -86
- package/dist/lib/session/bundle.js +0 -54
- package/dist/lib/session/claude-accounts.d.ts +0 -40
- package/dist/lib/session/claude-accounts.js +0 -49
- package/dist/lib/session/cloud.d.ts +1 -23
- package/dist/lib/session/cloud.js +0 -26
- package/dist/lib/session/db.d.ts +5 -658
- package/dist/lib/session/db.js +22 -1443
- package/dist/lib/session/detached.d.ts +0 -21
- package/dist/lib/session/detached.js +0 -29
- package/dist/lib/session/discover.d.ts +3 -420
- package/dist/lib/session/discover.js +30 -1123
- package/dist/lib/session/fork.d.ts +1 -41
- package/dist/lib/session/fork.js +0 -19
- package/dist/lib/session/ghostty-tabs.d.ts +0 -24
- package/dist/lib/session/ghostty-tabs.js +0 -37
- package/dist/lib/session/glance-files.d.ts +19 -0
- package/dist/lib/session/glance-files.js +258 -0
- package/dist/lib/session/hook-sessions.d.ts +0 -33
- package/dist/lib/session/hook-sessions.js +4 -99
- package/dist/lib/session/host-link.d.ts +1 -94
- package/dist/lib/session/host-link.js +1 -81
- package/dist/lib/session/inject-target.d.ts +8 -0
- package/dist/lib/session/inject-target.js +32 -0
- package/dist/lib/session/inject.d.ts +0 -15
- package/dist/lib/session/inject.js +0 -15
- package/dist/lib/session/live-metadata.d.ts +1 -34
- package/dist/lib/session/live-metadata.js +1 -73
- package/dist/lib/session/local-tmux-attach.d.ts +0 -37
- package/dist/lib/session/local-tmux-attach.js +1 -76
- package/dist/lib/session/migrate-targets.d.ts +0 -46
- package/dist/lib/session/migrate-targets.js +0 -24
- package/dist/lib/session/migrations.d.ts +0 -12
- package/dist/lib/session/migrations.js +1 -20
- package/dist/lib/session/mirror.d.ts +0 -32
- package/dist/lib/session/mirror.js +0 -77
- package/dist/lib/session/origin-machine.js +0 -3
- package/dist/lib/session/pid-registry.d.ts +0 -80
- package/dist/lib/session/pid-registry.js +2 -81
- package/dist/lib/session/presence.d.ts +0 -39
- package/dist/lib/session/presence.js +2 -52
- package/dist/lib/session/process-view.d.ts +0 -11
- package/dist/lib/session/process-view.js +0 -25
- package/dist/lib/session/projection.d.ts +0 -3
- package/dist/lib/session/projection.js +0 -10
- package/dist/lib/session/provenance.d.ts +1 -37
- package/dist/lib/session/provenance.js +0 -69
- package/dist/lib/session/recovery.d.ts +1 -40
- package/dist/lib/session/recovery.js +2 -80
- package/dist/lib/session/relative-time.js +0 -3
- package/dist/lib/session/remote/peer-stream.d.ts +0 -42
- package/dist/lib/session/remote/peer-stream.js +0 -64
- package/dist/lib/session/remote/remote-bundle.d.ts +0 -6
- package/dist/lib/session/remote/remote-bundle.js +0 -21
- package/dist/lib/session/remote/remote-list.d.ts +1 -110
- package/dist/lib/session/remote/remote-list.js +5 -139
- package/dist/lib/session/remote/remote.d.ts +0 -98
- package/dist/lib/session/remote/remote.js +3 -119
- package/dist/lib/session/remote/watch.d.ts +1 -34
- package/dist/lib/session/remote/watch.js +12 -72
- package/dist/lib/session/remote-active.d.ts +0 -35
- package/dist/lib/session/remote-active.js +0 -48
- package/dist/lib/session/remote-bundle.js +0 -2
- package/dist/lib/session/remote-list.js +0 -2
- package/dist/lib/session/remote-preview-cache.d.ts +0 -7
- package/dist/lib/session/remote-preview-cache.js +1 -76
- package/dist/lib/session/remote.js +0 -2
- package/dist/lib/session/resume-command.d.ts +0 -6
- package/dist/lib/session/resume-command.js +0 -6
- package/dist/lib/session/resume-owner.d.ts +1 -52
- package/dist/lib/session/resume-owner.js +0 -56
- package/dist/lib/session/run-names.d.ts +0 -24
- package/dist/lib/session/run-names.js +0 -26
- package/dist/lib/session/session-cache.d.ts +2 -146
- package/dist/lib/session/session-cache.js +26 -203
- package/dist/lib/session/short-id.js +0 -3
- package/dist/lib/session/sync/agents.d.ts +0 -68
- package/dist/lib/session/sync/agents.js +0 -0
- package/dist/lib/session/sync/backend.d.ts +0 -39
- package/dist/lib/session/sync/backend.js +0 -45
- package/dist/lib/session/sync/config.d.ts +0 -27
- package/dist/lib/session/sync/config.js +0 -56
- package/dist/lib/session/sync/managed-config.d.ts +0 -17
- package/dist/lib/session/sync/managed-config.js +0 -17
- package/dist/lib/session/sync/managed-key.d.ts +0 -38
- package/dist/lib/session/sync/managed-key.js +1 -42
- package/dist/lib/session/sync/net-client.d.ts +0 -36
- package/dist/lib/session/sync/net-client.js +0 -30
- package/dist/lib/session/sync/provision.d.ts +0 -2
- package/dist/lib/session/sync/provision.js +0 -13
- package/dist/lib/session/sync/r2.d.ts +0 -20
- package/dist/lib/session/sync/r2.js +0 -20
- package/dist/lib/session/sync/transcript-crypto.d.ts +0 -37
- package/dist/lib/session/sync/transcript-crypto.js +5 -37
- package/dist/lib/session/sync/worker-template.d.ts +0 -5
- package/dist/lib/session/sync/worker-template.js +0 -40
- package/dist/lib/session/testdata/pid-registry-namespace.js +0 -2
- package/dist/lib/session/testdata/process-view-private.js +0 -1
- package/dist/lib/session/throughput.d.ts +0 -24
- package/dist/lib/session/throughput.js +1 -25
- package/dist/lib/session/timeline-pass.d.ts +1 -63
- package/dist/lib/session/timeline-pass.js +161 -148
- package/dist/lib/session/title.d.ts +0 -182
- package/dist/lib/session/title.js +0 -139
- package/dist/lib/session/tool-index.d.ts +2 -21
- package/dist/lib/session/tool-index.js +3 -75
- package/dist/lib/session/tool-store.d.ts +2 -48
- package/dist/lib/session/tool-store.js +1 -33
- package/dist/lib/session/viewing-in.d.ts +0 -67
- package/dist/lib/session/viewing-in.js +1 -71
- package/dist/lib/session/watch.js +0 -2
- package/dist/lib/session/width.js +0 -3
- package/dist/lib/sessions-client.d.ts +0 -46
- package/dist/lib/sessions-client.js +36 -113
- package/dist/lib/settings-manifest.d.ts +1 -17
- package/dist/lib/settings-manifest.js +4 -57
- package/dist/lib/setup-tool-install.d.ts +0 -1
- package/dist/lib/setup-tool-install.js +2 -7
- package/dist/lib/setup-tool-status.d.ts +0 -3
- package/dist/lib/setup-tool-status.js +4 -14
- package/dist/lib/sha256-asset.d.ts +0 -20
- package/dist/lib/sha256-asset.js +0 -20
- package/dist/lib/share-runtime.d.ts +0 -10
- package/dist/lib/share-runtime.js +1 -27
- package/dist/lib/shim-heal.d.ts +0 -15
- package/dist/lib/shim-heal.js +3 -29
- package/dist/lib/signin-badge.d.ts +0 -72
- package/dist/lib/signin-badge.js +0 -86
- package/dist/lib/sink-format.d.ts +0 -32
- package/dist/lib/sink-format.js +0 -14
- package/dist/lib/smart-launch.d.ts +0 -75
- package/dist/lib/smart-launch.js +1 -83
- package/dist/lib/snapshot.d.ts +0 -46
- package/dist/lib/snapshot.js +5 -31
- package/dist/lib/spinner.d.ts +0 -37
- package/dist/lib/spinner.js +0 -37
- package/dist/lib/sqlite.d.ts +0 -13
- package/dist/lib/sqlite.js +3 -71
- package/dist/lib/ssh-exec.d.ts +0 -209
- package/dist/lib/ssh-exec.js +11 -248
- package/dist/lib/ssh-tunnel.d.ts +0 -43
- package/dist/lib/ssh-tunnel.js +2 -48
- package/dist/lib/staleness/checkers/commands.d.ts +0 -4
- package/dist/lib/staleness/checkers/commands.js +0 -4
- package/dist/lib/staleness/checkers/hooks.d.ts +0 -10
- package/dist/lib/staleness/checkers/hooks.js +0 -15
- package/dist/lib/staleness/checkers/mcp.d.ts +0 -9
- package/dist/lib/staleness/checkers/mcp.js +0 -9
- package/dist/lib/staleness/checkers/permissions.d.ts +0 -12
- package/dist/lib/staleness/checkers/permissions.js +0 -13
- package/dist/lib/staleness/checkers/plugins.d.ts +0 -8
- package/dist/lib/staleness/checkers/plugins.js +0 -8
- package/dist/lib/staleness/checkers/rules.d.ts +0 -15
- package/dist/lib/staleness/checkers/rules.js +0 -26
- package/dist/lib/staleness/checkers/skills.d.ts +0 -4
- package/dist/lib/staleness/checkers/skills.js +0 -4
- package/dist/lib/staleness/checkers/subagents.d.ts +0 -9
- package/dist/lib/staleness/checkers/subagents.js +0 -9
- package/dist/lib/staleness/checkers/types.d.ts +0 -34
- package/dist/lib/staleness/checkers/types.js +0 -19
- package/dist/lib/staleness/checkers/workflows.d.ts +0 -7
- package/dist/lib/staleness/checkers/workflows.js +0 -7
- package/dist/lib/staleness/detectors/commands.js +0 -17
- package/dist/lib/staleness/detectors/hooks.js +1 -6
- package/dist/lib/staleness/detectors/mcp.js +0 -4
- package/dist/lib/staleness/detectors/permissions.js +10 -23
- package/dist/lib/staleness/detectors/rules.js +0 -6
- package/dist/lib/staleness/detectors/skills.d.ts +0 -1
- package/dist/lib/staleness/detectors/skills.js +1 -12
- package/dist/lib/staleness/detectors/types.d.ts +0 -10
- package/dist/lib/staleness/fingerprint.d.ts +0 -19
- package/dist/lib/staleness/fingerprint.js +0 -35
- package/dist/lib/staleness/index.d.ts +0 -28
- package/dist/lib/staleness/index.js +1 -45
- package/dist/lib/staleness/layers.d.ts +0 -23
- package/dist/lib/staleness/layers.js +0 -34
- package/dist/lib/staleness/prune.d.ts +0 -71
- package/dist/lib/staleness/prune.js +2 -27
- package/dist/lib/staleness/registry.d.ts +0 -10
- package/dist/lib/staleness/registry.js +3 -33
- package/dist/lib/staleness/types.d.ts +0 -36
- package/dist/lib/staleness/types.js +0 -5
- package/dist/lib/staleness/writers/commands.js +1 -48
- package/dist/lib/staleness/writers/hooks.js +1 -18
- package/dist/lib/staleness/writers/kinds.d.ts +0 -12
- package/dist/lib/staleness/writers/kinds.js +0 -1
- package/dist/lib/staleness/writers/lazy-map.d.ts +0 -11
- package/dist/lib/staleness/writers/mcp.js +0 -13
- package/dist/lib/staleness/writers/plugins.js +0 -4
- package/dist/lib/staleness/writers/rules.d.ts +0 -1
- package/dist/lib/staleness/writers/rules.js +1 -19
- package/dist/lib/staleness/writers/skills.js +3 -14
- package/dist/lib/staleness/writers/sources.d.ts +0 -11
- package/dist/lib/staleness/writers/sources.js +0 -32
- package/dist/lib/staleness/writers/subagents.js +0 -16
- package/dist/lib/staleness/writers/types.d.ts +0 -58
- package/dist/lib/staleness/writers/workflows.js +1 -1
- package/dist/lib/standalone-tools.d.ts +24 -0
- package/dist/lib/standalone-tools.js +58 -0
- package/dist/lib/star-nudge.d.ts +0 -32
- package/dist/lib/star-nudge.js +2 -36
- package/dist/lib/startup/command-registry.d.ts +0 -45
- package/dist/lib/startup/command-registry.js +1 -46
- package/dist/lib/startup/dev-build.d.ts +0 -9
- package/dist/lib/startup/dev-build.js +1 -42
- package/dist/lib/startup/root-command.d.ts +0 -19
- package/dist/lib/startup/root-command.js +0 -19
- package/dist/lib/startup/spellcheck.d.ts +0 -13
- package/dist/lib/startup/spellcheck.js +0 -13
- package/dist/lib/state.d.ts +0 -376
- package/dist/lib/state.js +19 -737
- package/dist/lib/storage/selection.d.ts +0 -40
- package/dist/lib/storage/selection.js +0 -29
- package/dist/lib/subagents-registry.d.ts +0 -56
- package/dist/lib/subagents-registry.js +0 -99
- package/dist/lib/subagents.d.ts +0 -128
- package/dist/lib/subagents.js +1 -158
- package/dist/lib/summarizer/config.d.ts +0 -37
- package/dist/lib/summarizer/config.js +0 -37
- package/dist/lib/summarizer/pass.d.ts +0 -20
- package/dist/lib/summarizer/pass.js +1 -34
- package/dist/lib/summarizer/summarize.d.ts +1 -49
- package/dist/lib/summarizer/summarize.js +0 -35
- package/dist/lib/sync-passphrase.d.ts +0 -16
- package/dist/lib/sync-passphrase.js +1 -18
- package/dist/lib/sync-status.d.ts +0 -72
- package/dist/lib/sync-status.js +0 -55
- package/dist/lib/sync-umbrella.d.ts +0 -54
- package/dist/lib/sync-umbrella.js +0 -48
- package/dist/lib/system-run-sync.d.ts +0 -5
- package/dist/lib/system-run-sync.js +1 -31
- package/dist/lib/teams/agents.d.ts +1 -444
- package/dist/lib/teams/agents.js +11 -871
- package/dist/lib/teams/api.d.ts +2 -52
- package/dist/lib/teams/api.js +4 -63
- package/dist/lib/teams/debug.d.ts +0 -6
- package/dist/lib/teams/debug.js +0 -6
- package/dist/lib/teams/delivery.d.ts +0 -41
- package/dist/lib/teams/delivery.js +0 -32
- package/dist/lib/teams/file_ops.d.ts +0 -11
- package/dist/lib/teams/file_ops.js +0 -11
- package/dist/lib/teams/forEach.d.ts +0 -76
- package/dist/lib/teams/forEach.js +1 -77
- package/dist/lib/teams/index.d.ts +1 -7
- package/dist/lib/teams/index.js +1 -7
- package/dist/lib/teams/parsers.d.ts +0 -4
- package/dist/lib/teams/parsers.js +0 -81
- package/dist/lib/teams/persistence.d.ts +0 -2
- package/dist/lib/teams/persistence.js +0 -12
- package/dist/lib/teams/placement-probe.d.ts +0 -12
- package/dist/lib/teams/placement-probe.js +1 -51
- package/dist/lib/teams/pr-watch.d.ts +0 -107
- package/dist/lib/teams/pr-watch.js +1 -107
- package/dist/lib/teams/registry.d.ts +0 -36
- package/dist/lib/teams/registry.js +0 -47
- package/dist/lib/teams/remoteWorktree.d.ts +0 -50
- package/dist/lib/teams/remoteWorktree.js +1 -96
- package/dist/lib/teams/scheduler.d.ts +0 -111
- package/dist/lib/teams/scheduler.js +2 -125
- package/dist/lib/teams/summarizer.d.ts +0 -22
- package/dist/lib/teams/summarizer.js +6 -50
- package/dist/lib/teams/supervisor.d.ts +0 -38
- package/dist/lib/teams/supervisor.js +0 -21
- package/dist/lib/teams/worktree.d.ts +0 -95
- package/dist/lib/teams/worktree.js +3 -110
- package/dist/lib/template.d.ts +0 -21
- package/dist/lib/template.js +0 -23
- package/dist/lib/term-client.d.ts +0 -26
- package/dist/lib/term-client.js +0 -48
- package/dist/lib/term-driver.d.ts +0 -5
- package/dist/lib/term-driver.js +0 -8
- package/dist/lib/terminal/backends/ghostty.d.ts +0 -2
- package/dist/lib/terminal/backends/ghostty.js +0 -12
- package/dist/lib/terminal/backends/index.d.ts +0 -17
- package/dist/lib/terminal/backends/index.js +0 -15
- package/dist/lib/terminal/backends/iterm.d.ts +0 -2
- package/dist/lib/terminal/backends/iterm.js +0 -10
- package/dist/lib/terminal/backends/terminal-app.d.ts +0 -10
- package/dist/lib/terminal/backends/terminal-app.js +0 -30
- package/dist/lib/terminal/backends/tmux.d.ts +0 -10
- package/dist/lib/terminal/backends/tmux.js +0 -2
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +0 -25
- package/dist/lib/terminal/backends/vscodium-agent.js +0 -36
- package/dist/lib/terminal/engine.d.ts +0 -17
- package/dist/lib/terminal/engine.js +0 -10
- package/dist/lib/terminal/index.d.ts +0 -8
- package/dist/lib/terminal/inject.d.ts +1 -173
- package/dist/lib/terminal/inject.js +0 -146
- package/dist/lib/terminal/policy.d.ts +0 -8
- package/dist/lib/terminal/policy.js +0 -1
- package/dist/lib/terminal/preferred.d.ts +0 -66
- package/dist/lib/terminal/preferred.js +0 -30
- package/dist/lib/terminal/quote.d.ts +0 -8
- package/dist/lib/terminal/quote.js +0 -8
- package/dist/lib/terminal/resolve.d.ts +0 -46
- package/dist/lib/terminal/resolve.js +0 -30
- package/dist/lib/terminal/run-surface.d.ts +0 -56
- package/dist/lib/terminal/run-surface.js +0 -46
- package/dist/lib/terminal/shell.d.ts +0 -3
- package/dist/lib/terminal/shell.js +0 -13
- package/dist/lib/terminal/transport.d.ts +0 -14
- package/dist/lib/terminal/transport.js +1 -30
- package/dist/lib/terminal/types.d.ts +0 -40
- package/dist/lib/terminal/types.js +0 -10
- package/dist/lib/text/relative-time.d.ts +0 -36
- package/dist/lib/text/relative-time.js +0 -43
- package/dist/lib/text/short-id.d.ts +0 -16
- package/dist/lib/text/short-id.js +0 -16
- package/dist/lib/text/width.d.ts +0 -24
- package/dist/lib/text/width.js +10 -52
- package/dist/lib/tmux/binary.d.ts +0 -36
- package/dist/lib/tmux/binary.js +0 -28
- package/dist/lib/tmux/index.d.ts +0 -5
- package/dist/lib/tmux/index.js +0 -5
- package/dist/lib/tmux/orphan-reap.d.ts +0 -253
- package/dist/lib/tmux/orphan-reap.js +3 -299
- package/dist/lib/tmux/paths.d.ts +0 -14
- package/dist/lib/tmux/paths.js +0 -14
- package/dist/lib/tmux/session.d.ts +0 -263
- package/dist/lib/tmux/session.js +6 -344
- package/dist/lib/traces/backend.d.ts +0 -20
- package/dist/lib/traces/backend.js +0 -23
- package/dist/lib/traces/classify.d.ts +0 -20
- package/dist/lib/traces/classify.js +0 -7
- package/dist/lib/traces/insights.d.ts +1 -84
- package/dist/lib/traces/insights.js +0 -126
- package/dist/lib/traces/phenotype.d.ts +0 -69
- package/dist/lib/traces/phenotype.js +0 -150
- package/dist/lib/traces/provision.d.ts +0 -1
- package/dist/lib/traces/provision.js +0 -5
- package/dist/lib/traces/schema2-build.d.ts +3 -60
- package/dist/lib/traces/schema2-build.js +8 -149
- package/dist/lib/traces/schema2-danger.d.ts +0 -30
- package/dist/lib/traces/schema2-danger.js +1 -63
- package/dist/lib/traces/schema2.d.ts +0 -11
- package/dist/lib/traces/segments.d.ts +0 -44
- package/dist/lib/traces/segments.js +0 -65
- package/dist/lib/traces/sync.d.ts +2 -239
- package/dist/lib/traces/sync.js +15 -237
- package/dist/lib/traces/worker-template.d.ts +0 -3
- package/dist/lib/traces/worker-template.js +0 -29
- package/dist/lib/triggers/handlers.d.ts +0 -82
- package/dist/lib/triggers/handlers.js +0 -75
- package/dist/lib/triggers/webhook.d.ts +0 -158
- package/dist/lib/triggers/webhook.js +0 -143
- package/dist/lib/types.d.ts +0 -736
- package/dist/lib/types.js +0 -50
- package/dist/lib/uninstall.d.ts +0 -17
- package/dist/lib/uninstall.js +0 -75
- package/dist/lib/usage-backoff.d.ts +0 -20
- package/dist/lib/usage-backoff.js +3 -60
- package/dist/lib/usage-refresh.d.ts +0 -154
- package/dist/lib/usage-refresh.js +8 -212
- package/dist/lib/verbs.d.ts +0 -12
- package/dist/lib/verbs.js +0 -12
- package/dist/lib/version-duplicates.d.ts +0 -6
- package/dist/lib/version-duplicates.js +0 -6
- package/dist/lib/version.d.ts +0 -55
- package/dist/lib/version.js +1 -58
- package/dist/lib/view-types.d.ts +0 -48
- package/dist/lib/warn-unpushed.d.ts +0 -27
- package/dist/lib/warn-unpushed.js +1 -45
- package/dist/lib/watchdog/history.d.ts +0 -1
- package/dist/lib/watchdog/history.js +0 -3
- package/dist/lib/watchdog/index.js +0 -2
- package/dist/lib/watchdog/log.d.ts +0 -19
- package/dist/lib/watchdog/log.js +0 -34
- package/dist/lib/watchdog/read.d.ts +0 -27
- package/dist/lib/watchdog/read.js +3 -58
- package/dist/lib/watchdog/rotate.d.ts +0 -139
- package/dist/lib/watchdog/rotate.js +2 -151
- package/dist/lib/watchdog/runner.d.ts +0 -125
- package/dist/lib/watchdog/runner.js +3 -223
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -2
- package/dist/lib/watchdog/watchdog-agent.d.ts +0 -31
- package/dist/lib/watchdog/watchdog-agent.js +0 -27
- package/dist/lib/watchdog/watchdog.d.ts +0 -7
- package/dist/lib/watchdog/watchdog.js +0 -10
- package/dist/lib/watchdog/watchdogTail.js +0 -7
- package/dist/lib/whats-new.d.ts +0 -10
- package/dist/lib/whats-new.js +0 -21
- package/dist/lib/workflows-registry.d.ts +0 -45
- package/dist/lib/workflows-registry.js +0 -60
- package/dist/lib/workflows.d.ts +0 -282
- package/dist/lib/workflows.js +3 -213
- package/dist/lib/worktree/held.d.ts +0 -89
- package/dist/lib/worktree/held.js +1 -103
- package/dist/lib/wrap.d.ts +0 -32
- package/dist/lib/wrap.js +0 -32
- package/dist/lib/yaml-io.d.ts +0 -45
- package/dist/lib/yaml-io.js +0 -47
- package/dist/session-tracker/dist/adapters/claude.js +0 -2
- package/dist/session-tracker/dist/hook.sh +4 -37
- package/dist/session-tracker/dist/index.d.ts +0 -7
- package/dist/session-tracker/dist/index.js +0 -5
- package/dist/session-tracker/dist/install-hook.d.ts +0 -5
- package/dist/session-tracker/dist/install-hook.js +1 -15
- package/dist/session-tracker/dist/prune-state.js +0 -4
- package/dist/session-tracker/dist/reader.js +0 -4
- package/dist/session-tracker/dist/state-file.d.ts +0 -3
- package/dist/session-tracker/dist/state-file.js +3 -8
- package/package.json +3 -4
- package/scripts/postinstall.js +8 -116
- package/dist/lib/session/artifacts.d.ts +0 -14
- package/dist/lib/session/artifacts.js +0 -101
- package/dist/lib/session/bash-command.d.ts +0 -63
- package/dist/lib/session/bash-command.js +0 -490
- package/dist/lib/session/digest.d.ts +0 -66
- package/dist/lib/session/digest.js +0 -328
- package/dist/lib/session/highlights.d.ts +0 -151
- package/dist/lib/session/highlights.js +0 -391
- package/dist/lib/session/insights.d.ts +0 -165
- package/dist/lib/session/insights.js +0 -563
- package/dist/lib/session/linear.d.ts +0 -19
- package/dist/lib/session/linear.js +0 -99
- package/dist/lib/session/parse.d.ts +0 -224
- package/dist/lib/session/parse.js +0 -2408
- package/dist/lib/session/prompt.d.ts +0 -213
- package/dist/lib/session/prompt.js +0 -601
- package/dist/lib/session/render.d.ts +0 -133
- package/dist/lib/session/render.js +0 -1047
- package/dist/lib/session/share-html.d.ts +0 -35
- package/dist/lib/session/share-html.js +0 -319
- package/dist/lib/session/shell-programs.d.ts +0 -33
- package/dist/lib/session/shell-programs.js +0 -380
- package/dist/lib/session/state.d.ts +0 -269
- package/dist/lib/session/state.js +0 -777
- package/dist/lib/session/stream-render.d.ts +0 -4
- package/dist/lib/session/stream-render.js +0 -131
- package/dist/lib/session/tail.d.ts +0 -88
- package/dist/lib/session/tail.js +0 -294
- package/dist/lib/session/team-filter.d.ts +0 -129
- package/dist/lib/session/team-filter.js +0 -269
- package/dist/lib/session/timeline.d.ts +0 -172
- package/dist/lib/session/timeline.js +0 -636
- package/dist/lib/session/tool-calls.d.ts +0 -132
- package/dist/lib/session/tool-calls.js +0 -674
- package/dist/lib/session/trajectory-compare.d.ts +0 -48
- package/dist/lib/session/trajectory-compare.js +0 -116
- package/dist/lib/session/trajectory-html.d.ts +0 -19
- package/dist/lib/session/trajectory-html.js +0 -771
- package/dist/lib/session/trajectory-lineage.d.ts +0 -93
- package/dist/lib/session/trajectory-lineage.js +0 -195
- package/dist/lib/session/trajectory-text.d.ts +0 -38
- package/dist/lib/session/trajectory-text.js +0 -284
- package/dist/lib/session/trajectory.d.ts +0 -145
- package/dist/lib/session/trajectory.js +0 -394
- package/dist/lib/session/types.d.ts +0 -627
- package/dist/lib/session/types.js +0 -43
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { daemonProcessViewAllowed, recordDaemonProcessView } from '../session/process-view.js';
|
|
2
|
-
/**
|
|
3
|
-
* Daemon lifecycle management for the routines scheduler.
|
|
4
|
-
*
|
|
5
|
-
* The daemon is a long-running process that holds a JobScheduler and
|
|
6
|
-
* triggers jobs on their cron schedules. It can be managed via launchd
|
|
7
|
-
* (macOS), systemd (Linux), or as a plain detached process. PID tracking,
|
|
8
|
-
* log output, reload (SIGHUP), and graceful shutdown are handled here.
|
|
9
|
-
*/
|
|
10
2
|
import { spawn, execFileSync } from 'child_process';
|
|
11
3
|
import * as fs from 'fs';
|
|
12
4
|
import * as path from 'path';
|
|
@@ -17,7 +9,6 @@ import { isAlive, killTree, backgroundSpawnOptions, waitForExit } from '../platf
|
|
|
17
9
|
import { listJobs as listAllJobs } from '../scheduling/routines.js';
|
|
18
10
|
import { syncAllProjectRoutines } from '../routines-project.js';
|
|
19
11
|
import { JobScheduler } from '../scheduler.js';
|
|
20
|
-
// MonitorEngine is now owned by MonitorEngineService (daemon/monitor-engine-service.ts).
|
|
21
12
|
import { executeJobDetached, listLiveRoutineChildren } from './runner.js';
|
|
22
13
|
import { detectOverdueJobs, notifyOverdue } from '../overdue.js';
|
|
23
14
|
import { runCatchup } from '../catchup.js';
|
|
@@ -32,18 +23,7 @@ import { ServiceSupervisor } from './supervisor.js';
|
|
|
32
23
|
import { emit, emitAsync, emitRoutineEnd } from '../feed/events.js';
|
|
33
24
|
import { readDaemonServicesConfig, drainDaemonServiceRestartQueue } from '../daemon-services.js';
|
|
34
25
|
import { sleepSync } from '../fs-atomic.js';
|
|
35
|
-
/**
|
|
36
|
-
* The live `ServiceSupervisor` for the current `runDaemon()` invocation, or
|
|
37
|
-
* `null` before boot / after shutdown. In-process only — a separate `agents
|
|
38
|
-
* daemon status` process cannot see this; per-service health that must
|
|
39
|
-
* survive across processes goes through `daemon-health.ts` instead (which
|
|
40
|
-
* `ServiceSupervisor` already writes on every tick). This getter exists so a
|
|
41
|
-
* FUTURE same-process consumer (e.g. a `daemon services` live-status IPC
|
|
42
|
-
* handler) can read the supervisor's live state (`running`/`stopped`, plus the
|
|
43
|
-
* failure streak) without needing its own reference to `runDaemon()`'s locals.
|
|
44
|
-
*/
|
|
45
26
|
let activeServiceSupervisor = null;
|
|
46
|
-
/** Health for every service currently registered on the live supervisor, or `null` if the daemon isn't running in this process. */
|
|
47
27
|
export function getServiceSupervisorHealth() {
|
|
48
28
|
return activeServiceSupervisor?.health() ?? null;
|
|
49
29
|
}
|
|
@@ -52,133 +32,32 @@ const LIFETIME_FILE = 'daemon.lifetime';
|
|
|
52
32
|
const LOCK_FILE = 'daemon.lock';
|
|
53
33
|
const LOG_FILE = 'logs.jsonl';
|
|
54
34
|
const HEARTBEAT_FILE = 'heartbeat.json';
|
|
55
|
-
const LOG_MAX_SIZE = 5 * 1024 * 1024;
|
|
35
|
+
const LOG_MAX_SIZE = 5 * 1024 * 1024;
|
|
56
36
|
const LOG_ROTATE_COUNT = 3;
|
|
57
37
|
const PLIST_NAME = 'com.phnx-labs.agents-daemon';
|
|
58
38
|
const SYSTEMD_UNIT = 'agents-daemon.service';
|
|
59
|
-
|
|
60
|
-
* The service-manager identifiers of the REAL install's daemon — never
|
|
61
|
-
* namespaced. A caller under a redirected HOME (a test/e2e harness) uses these
|
|
62
|
-
* to recognize the box's production daemon as owned, where
|
|
63
|
-
* `daemonSystemdUnitName()`/`daemonServiceLabel()` would name only its own
|
|
64
|
-
* sandbox job (W4, PHNX-3736).
|
|
65
|
-
*/
|
|
39
|
+
// Redirected HOME instances namespace manager ids because launchd/systemd key by label/unit, not manifest path; tests must never unload production.
|
|
66
40
|
export function productionDaemonServiceNames() {
|
|
67
41
|
return { systemdUnit: SYSTEMD_UNIT, launchdLabel: PLIST_NAME };
|
|
68
42
|
}
|
|
69
|
-
/**
|
|
70
|
-
* RUSH-2639 (residual): launchd/systemd route `unload`/`load`/`list` by the
|
|
71
|
-
* service identifier ALONE, never by the plist/unit file's path. Baking the
|
|
72
|
-
* caller's HOME into the plist content (the earlier RUSH-2639 fix, above)
|
|
73
|
-
* keeps the STARTED daemon inside its sandbox, but every hermetic-test
|
|
74
|
-
* instance and every real interactive install still share the one literal
|
|
75
|
-
* `PLIST_NAME`/`SYSTEMD_UNIT` string. `startDaemonLocked`'s own `unload`
|
|
76
|
-
* before `load` is written to be a no-op ("not loaded, expected") for a
|
|
77
|
-
* plist that has never been loaded — but confirmed on darwin: when a
|
|
78
|
-
* DIFFERENT plist is already loaded under that same label, `unload
|
|
79
|
-
* <this-instance's-own-never-loaded-path>` still tears down the OTHER job
|
|
80
|
-
* (verified directly against real launchctl with two throwaway plists
|
|
81
|
-
* sharing one label — the second job's own `unload` silently kills the
|
|
82
|
-
* first, still alive under a different path). On a machine running several
|
|
83
|
-
* hermetic test forks at once (CI) — or a developer's own suite next to
|
|
84
|
-
* their real always-on daemon — that "other job" is a live daemon with
|
|
85
|
-
* DIFFERENT baked-in state.
|
|
86
|
-
*
|
|
87
|
-
* Namespace the identifier itself whenever HOME has been redirected away
|
|
88
|
-
* from the account's real home. `os.userInfo().homedir` reads the OS/passwd
|
|
89
|
-
* record directly and ignores `$HOME` (unlike `os.homedir()`, which honors
|
|
90
|
-
* it), so comparing the two detects exactly this redirection — true for
|
|
91
|
-
* every hermetic test process, false for every real interactive/production
|
|
92
|
-
* invocation, so a real user's daemon keeps registering under the unchanged
|
|
93
|
-
* production identifier.
|
|
94
|
-
*
|
|
95
|
-
* The rule itself now lives in `service-manifest.ts` — the daemon was the first
|
|
96
|
-
* manifest to need it, not the only one — and is re-exported here because it is
|
|
97
|
-
* part of this module's published surface.
|
|
98
|
-
*/
|
|
99
43
|
export { isolatedHomeSuffix };
|
|
100
|
-
/** launchd Label for this process's daemon — namespaced under a redirected HOME. */
|
|
101
44
|
export function daemonServiceLabel() {
|
|
102
45
|
return namespacedServiceLabel(PLIST_NAME);
|
|
103
46
|
}
|
|
104
|
-
/** systemd --user unit name for this process's daemon — namespaced under a redirected HOME. */
|
|
105
47
|
export function daemonSystemdUnitName() {
|
|
106
48
|
const suffix = isolatedHomeSuffix();
|
|
107
49
|
return suffix ? `agents-daemon-sandbox-${suffix}.service` : SYSTEMD_UNIT;
|
|
108
50
|
}
|
|
109
|
-
// Catch-up cadence + the supervised CatchupService live in catchup-service.ts
|
|
110
|
-
// (PHNX-3608): the pass now runs under the ServiceSupervisor with a deadline +
|
|
111
|
-
// AbortSignal + circuit breaker instead of a bare setInterval.
|
|
112
|
-
/**
|
|
113
|
-
* Cadences for the in-process background ticks, named here beside the other
|
|
114
|
-
* tick constants rather than left as inline literals at their `setInterval`
|
|
115
|
-
* (RUSH-2423). Self-heal, state-dir-check, watchdog, and device-probe
|
|
116
|
-
* cadences moved to their own `*-service.ts` files (RUSH-3193 P3, alongside
|
|
117
|
-
* session-index/account-state/etc.) — see those files for the per-service
|
|
118
|
-
* trade-offs (self-heal's 6h/cheap-to-be-late repair cadence, state-dir-check's
|
|
119
|
-
* env override for tests, watchdog/device-probe's shared 3min in-process
|
|
120
|
-
* housekeeping cadence, NOT a routine — RUSH-2495). The secrets broker and its
|
|
121
|
-
* self-heal/reap ticks moved out of this daemon entirely with the standalone
|
|
122
|
-
* `secrets` engine (PHNX-3989 OWN-1) — this daemon no longer hosts that broker.
|
|
123
|
-
*/
|
|
124
|
-
// Session-index warm interval/deadline live in session-index-service.ts now
|
|
125
|
-
// (RUSH-3193 — migrated onto ServiceSupervisor).
|
|
126
51
|
const WEDGE_THRESHOLD_TICKS = 3;
|
|
127
52
|
const DAEMON_HEARTBEAT_TICK_MS = 60_000;
|
|
128
|
-
/**
|
|
129
|
-
* Crash-loop pacing and auto-start bounding (RUSH-2418, PHNX-4116). Two layers,
|
|
130
|
-
* because a daemon that dies during startup must be paced but must NEVER be
|
|
131
|
-
* abandoned:
|
|
132
|
-
*
|
|
133
|
-
* 1. **The OS supervisor paces AND always retries the respawn.** launchd's
|
|
134
|
-
* `KeepAlive` with `ThrottleInterval=30` (and systemd's `Restart=always`
|
|
135
|
-
* `RestartSec=30`) relaunch a dead daemon every ~30s. Crucially systemd's
|
|
136
|
-
* `StartLimitIntervalSec=0` REMOVES the burst cap (PHNX-4116): a repeatedly
|
|
137
|
-
* deadline-breaching daemon is restarted every ~30s indefinitely rather than
|
|
138
|
-
* parked in `failed` and left dark until a human runs `systemctl restart`.
|
|
139
|
-
* The owner requirement is that a wedged daemon always recovers on its own —
|
|
140
|
-
* "just work like systemd/launchd". A supervised deadline breach is precisely
|
|
141
|
-
* what forces the exit (see `ServiceSupervisor.exitForRestart`), so the exit
|
|
142
|
-
* is the recovery, not a failure to give up on.
|
|
143
|
-
* 2. **The application-level circuit breaker** below still stops *auto*-starts
|
|
144
|
-
* from re-entering the loop from the other direction — a foreground command
|
|
145
|
-
* that calls `ensureDaemonStarted()` on every invocation. This bounds the
|
|
146
|
-
* IMPLICIT starts a busy operator would otherwise trigger; the OS-level
|
|
147
|
-
* unit-restart above is deliberately unbounded.
|
|
148
|
-
*/
|
|
149
53
|
const DAEMON_THROTTLE_SECONDS = 30;
|
|
150
|
-
/**
|
|
151
|
-
* How many consecutive failed daemon starts disable the *implicit* auto-start
|
|
152
|
-
* (`ensureDaemonStarted`). This bounds foreground-command auto-starts only; the
|
|
153
|
-
* OS-level unit restart is deliberately unbounded (`StartLimitIntervalSec=0`) so
|
|
154
|
-
* a wedged daemon always recovers (PHNX-4116). `agents daemon start` is the
|
|
155
|
-
* deliberate override and is never gated by this.
|
|
156
|
-
*/
|
|
157
54
|
export const DAEMON_AUTOSTART_FAILURE_LIMIT = 5;
|
|
158
55
|
export function schedulerGateTransition(running, enabled) {
|
|
159
56
|
if (running)
|
|
160
57
|
return enabled ? 'reload' : 'stop';
|
|
161
58
|
return enabled ? 'boot' : 'none';
|
|
162
59
|
}
|
|
163
|
-
|
|
164
|
-
* Wrap an async routine so it runs AT MOST ONCE, however many callers fire it.
|
|
165
|
-
*
|
|
166
|
-
* Extracted rather than left as a `let shuttingDown = false` inside runDaemon so
|
|
167
|
-
* the property can actually be tested (RUSH-2423). The daemon's shutdown is
|
|
168
|
-
* reachable from SIGTERM, SIGINT, and the state-dir self-check, but a real
|
|
169
|
-
* shutdown completes in ~26ms, so the re-entrant window is not reachable from
|
|
170
|
-
* outside the process — an end-to-end "send three signals" test passes with the
|
|
171
|
-
* guard removed and proves nothing. The mechanism is what is testable, so the
|
|
172
|
-
* mechanism is what is separated out.
|
|
173
|
-
*
|
|
174
|
-
* The flag is set synchronously before the first `await`, which is what makes
|
|
175
|
-
* this safe: two callers in the same tick cannot both get past it.
|
|
176
|
-
*
|
|
177
|
-
* A rejected `fn` leaves the guard SET — one attempt is all there is, and the
|
|
178
|
-
* rejection propagates to the caller that made it. That is right for shutdown
|
|
179
|
-
* (a failed shutdown must not be silently retried by the next signal) but is
|
|
180
|
-
* the thing to re-examine before giving this a second consumer.
|
|
181
|
-
*/
|
|
60
|
+
// Latch before the first await and never retry after rejection: daemon shutdown is single-shot.
|
|
182
61
|
export function singleShot(fn) {
|
|
183
62
|
let ran = false;
|
|
184
63
|
return async () => {
|
|
@@ -199,11 +78,7 @@ function getPidPath() {
|
|
|
199
78
|
function getLockPath() {
|
|
200
79
|
return path.join(ensureDaemonDir(), LOCK_FILE);
|
|
201
80
|
}
|
|
202
|
-
|
|
203
|
-
* Acquire an exclusive start lock. Returns a release function on success,
|
|
204
|
-
* or null if another process already holds the lock. Uses O_EXCL to
|
|
205
|
-
* atomically create the file — no TOCTOU window.
|
|
206
|
-
*/
|
|
81
|
+
// Start, claim, and stop share this O_EXCL lifecycle lock; unreadable live identity is unverified, never stale.
|
|
207
82
|
function acquireStartLock() {
|
|
208
83
|
const lockPath = getLockPath();
|
|
209
84
|
try {
|
|
@@ -214,38 +89,30 @@ function acquireStartLock() {
|
|
|
214
89
|
try {
|
|
215
90
|
fs.unlinkSync(lockPath);
|
|
216
91
|
}
|
|
217
|
-
catch {
|
|
92
|
+
catch { }
|
|
218
93
|
};
|
|
219
94
|
}
|
|
220
95
|
catch (err) {
|
|
221
96
|
if (err.code === 'EEXIST') {
|
|
222
|
-
// Lock file exists — check if the holder is still alive (stale lock recovery)
|
|
223
97
|
try {
|
|
224
98
|
const holderPid = parseInt(fs.readFileSync(lockPath, 'utf-8').trim(), 10);
|
|
225
99
|
if (!isNaN(holderPid)) {
|
|
226
100
|
try {
|
|
227
101
|
process.kill(holderPid, 0);
|
|
228
|
-
return null;
|
|
102
|
+
return null;
|
|
229
103
|
}
|
|
230
104
|
catch {
|
|
231
|
-
// holder is dead, remove stale lock and retry once
|
|
232
105
|
fs.unlinkSync(lockPath);
|
|
233
106
|
return acquireStartLock();
|
|
234
107
|
}
|
|
235
108
|
}
|
|
236
109
|
}
|
|
237
|
-
catch {
|
|
110
|
+
catch { }
|
|
238
111
|
return null;
|
|
239
112
|
}
|
|
240
113
|
throw err;
|
|
241
114
|
}
|
|
242
115
|
}
|
|
243
|
-
/**
|
|
244
|
-
* Stop is a lifecycle mutation just like start/claim, so it must cross the same
|
|
245
|
-
* lock. A claim can legitimately hold the lock through the incumbent's 5s
|
|
246
|
-
* graceful window plus the 2s hard-kill backstop; wait beyond that complete
|
|
247
|
-
* takeover window before failing loud instead of running teardown unlocked.
|
|
248
|
-
*/
|
|
249
116
|
const STOP_LOCK_WAIT_MS = 10_000;
|
|
250
117
|
const STOP_LOCK_POLL_MS = 50;
|
|
251
118
|
function acquireLifecycleLock() {
|
|
@@ -259,14 +126,6 @@ function acquireLifecycleLock() {
|
|
|
259
126
|
sleepSync(Math.min(STOP_LOCK_POLL_MS, deadline - Date.now()));
|
|
260
127
|
}
|
|
261
128
|
}
|
|
262
|
-
/**
|
|
263
|
-
* Absolute path to the daemon's structured log.
|
|
264
|
-
*
|
|
265
|
-
* Exported because two commands rebuilt the same path from a hardcoded
|
|
266
|
-
* `'logs.jsonl'` literal (`commands/daemon.ts`, `commands/routines.ts`), so
|
|
267
|
-
* renaming the file would have silently pointed them at nothing (RUSH-2423).
|
|
268
|
-
* One definition, three callers.
|
|
269
|
-
*/
|
|
270
129
|
export function getDaemonLogPath() {
|
|
271
130
|
return path.join(ensureDaemonDir(), LOG_FILE);
|
|
272
131
|
}
|
|
@@ -276,7 +135,6 @@ function getLaunchdPlistPath() {
|
|
|
276
135
|
function getSystemdUnitPath() {
|
|
277
136
|
return path.join(os.homedir(), '.config', 'systemd', 'user', daemonSystemdUnitName());
|
|
278
137
|
}
|
|
279
|
-
/** Read the stored daemon PID from disk. Returns null if not present or invalid. */
|
|
280
138
|
export function readDaemonPid(daemonDir) {
|
|
281
139
|
const pidPath = daemonDir ? path.join(daemonDir, PID_FILE) : getPidPath();
|
|
282
140
|
if (!fs.existsSync(pidPath))
|
|
@@ -289,25 +147,23 @@ export function readDaemonPid(daemonDir) {
|
|
|
289
147
|
return null;
|
|
290
148
|
}
|
|
291
149
|
}
|
|
292
|
-
/** Write the daemon PID to the pid file. */
|
|
293
150
|
export function writeDaemonPid(pid) {
|
|
294
151
|
fs.writeFileSync(getPidPath(), String(pid), 'utf-8');
|
|
295
152
|
}
|
|
296
|
-
/** Remove the daemon PID file. */
|
|
297
153
|
export function removeDaemonPid() {
|
|
298
154
|
const pidPath = getPidPath();
|
|
299
155
|
if (fs.existsSync(pidPath)) {
|
|
300
156
|
fs.unlinkSync(pidPath);
|
|
301
157
|
}
|
|
302
158
|
}
|
|
303
|
-
|
|
159
|
+
// Remove registration only while it still names the owner observed by this cleanup.
|
|
304
160
|
function removeDaemonPidIfOwned(pid) {
|
|
305
161
|
if (readDaemonPid() !== pid)
|
|
306
162
|
return false;
|
|
307
163
|
try {
|
|
308
164
|
fs.unlinkSync(getPidPath());
|
|
309
165
|
}
|
|
310
|
-
catch {
|
|
166
|
+
catch { }
|
|
311
167
|
return readDaemonPid() !== pid;
|
|
312
168
|
}
|
|
313
169
|
function getHeartbeatPath() {
|
|
@@ -318,7 +174,7 @@ export function writeHeartbeat(pid = process.pid) {
|
|
|
318
174
|
try {
|
|
319
175
|
fs.writeFileSync(getHeartbeatPath(), JSON.stringify(hb), 'utf-8');
|
|
320
176
|
}
|
|
321
|
-
catch {
|
|
177
|
+
catch { }
|
|
322
178
|
}
|
|
323
179
|
export function readHeartbeat() {
|
|
324
180
|
try {
|
|
@@ -336,43 +192,15 @@ export function removeHeartbeat() {
|
|
|
336
192
|
try {
|
|
337
193
|
fs.unlinkSync(getHeartbeatPath());
|
|
338
194
|
}
|
|
339
|
-
catch {
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* A heartbeat is "fresh" when its last tick falls inside the freshness window. A
|
|
343
|
-
* fresh heartbeat whose pid is alive is proof of a live, ticking daemon even
|
|
344
|
-
* when the pid file has been lost, which is why `resolveLiveDaemonPid` trusts it.
|
|
345
|
-
*
|
|
346
|
-
* There is no separate "wedged" verdict any more (PHNX-4116): a daemon whose
|
|
347
|
-
* event loop stalls no longer sits `wedged` waiting for a human — a supervised
|
|
348
|
-
* service that breaches its deadline exits the process, and systemd/launchd
|
|
349
|
-
* restart it. So the daemon is simply `running` or `stopped`.
|
|
350
|
-
*/
|
|
195
|
+
catch { }
|
|
196
|
+
}
|
|
351
197
|
function isHeartbeatFresh(hb) {
|
|
352
198
|
const elapsed = Date.now() - Date.parse(hb.lastTick);
|
|
353
199
|
return elapsed <= WEDGE_THRESHOLD_TICKS * DAEMON_HEARTBEAT_TICK_MS;
|
|
354
200
|
}
|
|
355
|
-
/** How long stopDaemon waits for a SIGTERMed daemon to exit before escalating. */
|
|
356
201
|
const STOP_GRACE_MS = 5000;
|
|
357
|
-
/** How long it waits after the hard tree-kill before giving up. */
|
|
358
202
|
const STOP_KILL_GRACE_MS = 2000;
|
|
359
|
-
|
|
360
|
-
* Resolve the PID of the live daemon, tolerant of a pid-file/heartbeat desync.
|
|
361
|
-
*
|
|
362
|
-
* The daemon writes the pid file once (on claim/start) but rewrites the
|
|
363
|
-
* heartbeat every tick. If the pid file is lost while the daemon keeps ticking
|
|
364
|
-
* — e.g. an earlier isDaemonRunning() found a stale/reused/dead pid and cleared
|
|
365
|
-
* the file, or it was removed out from under a live daemon — the pid file reads
|
|
366
|
-
* empty even though a daemon is genuinely alive and firing jobs. Reading only
|
|
367
|
-
* the pid file then reports "stopped" for a running scheduler, and (worse) lets
|
|
368
|
-
* claimDaemonInstance() start a SECOND daemon that double-fires every routine.
|
|
369
|
-
*
|
|
370
|
-
* So: trust the pid file only when its pid is a live `__daemon-run`; otherwise
|
|
371
|
-
* trust a FRESH heartbeat whose pid passes the same identity check. Callers that
|
|
372
|
-
* already own daemon.lock may request repair, re-adopting the heartbeat pid or
|
|
373
|
-
* removing the exact stale pid they observed. Read-only liveness probes never
|
|
374
|
-
* mutate shared state outside that lock.
|
|
375
|
-
*/
|
|
203
|
+
// A fresh heartbeat may repair a lost pid record; unknown identity is preserved so inspection failure cannot create a second daemon.
|
|
376
204
|
function resolveLiveDaemonPid(repair = false) {
|
|
377
205
|
const pid = readDaemonPid();
|
|
378
206
|
const pidIdentity = pid !== null ? daemonProcessIdentity(pid) : 'dead';
|
|
@@ -381,17 +209,13 @@ function resolveLiveDaemonPid(repair = false) {
|
|
|
381
209
|
const hb = readHeartbeat();
|
|
382
210
|
if (hb && isHeartbeatFresh(hb) && daemonProcessIdentity(hb.pid) === 'daemon') {
|
|
383
211
|
if (repair && pid !== hb.pid)
|
|
384
|
-
writeDaemonPid(hb.pid);
|
|
212
|
+
writeDaemonPid(hb.pid);
|
|
385
213
|
return hb.pid;
|
|
386
214
|
}
|
|
387
|
-
// A failed command-line inspection is not proof the pid is stale. Leave the
|
|
388
|
-
// registration intact so a sandbox/permission failure cannot erase the only
|
|
389
|
-
// owner record and trigger a duplicate daemon.
|
|
390
215
|
if (repair && pid !== null && pidIdentity !== 'unknown')
|
|
391
216
|
removeDaemonPidIfOwned(pid);
|
|
392
217
|
return null;
|
|
393
218
|
}
|
|
394
|
-
/** A live recorded pid whose command identity could not be inspected. */
|
|
395
219
|
function unverifiedLiveDaemonPid() {
|
|
396
220
|
const pid = readDaemonPid();
|
|
397
221
|
if (pid !== null && daemonProcessIdentity(pid) === 'unknown')
|
|
@@ -401,17 +225,7 @@ function unverifiedLiveDaemonPid() {
|
|
|
401
225
|
return hb.pid;
|
|
402
226
|
return null;
|
|
403
227
|
}
|
|
404
|
-
/**
|
|
405
|
-
* Check whether a daemon is alive — via the pid file, or a fresh heartbeat when
|
|
406
|
-
* the pid file has been lost (see resolveLiveDaemonPid). The observation itself
|
|
407
|
-
* is read-only; when it finds desync it opportunistically acquires daemon.lock
|
|
408
|
-
* and repeats the observation there before repairing. A contended probe still
|
|
409
|
-
* returns the observed liveness without mutating another lifecycle operation's
|
|
410
|
-
* state.
|
|
411
|
-
*/
|
|
412
228
|
export function isDaemonRunning() {
|
|
413
|
-
// Fail safe for reporting/start suppression: inability to inspect a live pid
|
|
414
|
-
// is never permission to declare it dead and launch a duplicate.
|
|
415
229
|
if (unverifiedLiveDaemonPid() !== null)
|
|
416
230
|
return true;
|
|
417
231
|
const recordedPid = readDaemonPid();
|
|
@@ -428,63 +242,22 @@ export function isDaemonRunning() {
|
|
|
428
242
|
release();
|
|
429
243
|
}
|
|
430
244
|
}
|
|
431
|
-
/**
|
|
432
|
-
* Single-instance claim for the daemon foreground entrypoint.
|
|
433
|
-
*
|
|
434
|
-
* `agents __daemon-run` is reachable directly — a manual invocation, or a
|
|
435
|
-
* service-manager restart that races a still-alive predecessor — bypassing the
|
|
436
|
-
* start lock in startDaemon(). Without this guard runDaemon() would call
|
|
437
|
-
* writeDaemonPid() unconditionally, clobber a live daemon's recorded PID, and
|
|
438
|
-
* run a second JobScheduler concurrently, so every cron routine fires twice.
|
|
439
|
-
*
|
|
440
|
-
* LAST-WINS takeover (SING-11, RUSH-2352): when a live daemon already owns the
|
|
441
|
-
* pid file, this does NOT defer to it — it evicts the incumbent and becomes the
|
|
442
|
-
* survivor, so a second install can never leave two daemons running. Returns true
|
|
443
|
-
* and records our PID once the incumbent is provably dead (its resources
|
|
444
|
-
* released). Returns false when another `__daemon-run` currently holds the
|
|
445
|
-
* O_EXCL start lock, or when the incumbent cannot be safely identified/evicted,
|
|
446
|
-
* in which case the caller must exit without touching further state.
|
|
447
|
-
* The read-evict-write is serialized behind the same start lock startDaemon()
|
|
448
|
-
* uses, so two `_run` processes can't both claim in the window between the
|
|
449
|
-
* liveness check and the write.
|
|
450
|
-
*/
|
|
451
245
|
export function claimDaemonInstance() {
|
|
452
|
-
// A nested caller cannot interpret legacy numeric lock/PID files. Authenticate
|
|
453
|
-
// before acquiring the lock (whose stale-PID cleanup itself mutates state).
|
|
454
246
|
if (!daemonProcessViewAllowed()) {
|
|
455
247
|
console.error('Daemon startup requires the owning process namespace. Automatic reuse of a private-container HOME across namespaces is unsupported; run in its owning namespace or use a fresh HOME.');
|
|
456
248
|
return false;
|
|
457
249
|
}
|
|
458
|
-
// A stop owns this same lock through teardown. Waiting here is load-bearing:
|
|
459
|
-
// returning false while stopDaemon() holds it lets this replacement exit 0,
|
|
460
|
-
// then the stop completes with no singleton left alive. The bounded lifecycle
|
|
461
|
-
// acquisition also preserves concurrent-start serialization: after the first
|
|
462
|
-
// claimer publishes its pid, the waiter takes the lock and performs the normal
|
|
463
|
-
// last-wins takeover rather than ever running the read-evict-write unlocked.
|
|
464
250
|
const release = acquireLifecycleLock();
|
|
465
251
|
if (!release)
|
|
466
252
|
return false;
|
|
467
253
|
try {
|
|
468
|
-
// Recheck under lifecycle serialization; invocation is not provenance.
|
|
469
254
|
if (!daemonProcessViewAllowed())
|
|
470
255
|
return false;
|
|
471
256
|
recordDaemonProcessView();
|
|
472
|
-
// Do not overwrite a live-but-uninspectable owner. This is the non-
|
|
473
|
-
// destructive side of the same fail-closed rule stopDaemon applies.
|
|
474
257
|
if (unverifiedLiveDaemonPid() !== null)
|
|
475
258
|
return false;
|
|
476
|
-
// resolveLiveDaemonPid() also consults a fresh heartbeat, so a live daemon
|
|
477
|
-
// whose pid file was lost is still found and evicted — otherwise a missing
|
|
478
|
-
// pid file would let both this instance AND the orphaned incumbent run a
|
|
479
|
-
// JobScheduler at once and double-fire every routine.
|
|
480
259
|
const existing = resolveLiveDaemonPid(true);
|
|
481
260
|
if (existing !== null && existing !== process.pid) {
|
|
482
|
-
// Evict, and WAIT for the incumbent to be provably dead — its graceful
|
|
483
|
-
// handleShutdown releasing its socket bindings (feed-stream hub, monitor)
|
|
484
|
-
// — before we write our pid and (later, in runDaemon) bind our own.
|
|
485
|
-
// Binding before the release recreates the two-owners-on-one-socket orphan
|
|
486
|
-
// documented at stopDaemon below, so the pid file is not written until the
|
|
487
|
-
// prior owner is gone.
|
|
488
261
|
if (!evictIncumbentDaemon(existing))
|
|
489
262
|
return false;
|
|
490
263
|
}
|
|
@@ -495,21 +268,6 @@ export function claimDaemonInstance() {
|
|
|
495
268
|
release();
|
|
496
269
|
}
|
|
497
270
|
}
|
|
498
|
-
/**
|
|
499
|
-
* SIGTERM a live incumbent daemon and block until it is provably dead, so its
|
|
500
|
-
* graceful handleShutdown has released its socket bindings (the feed-stream hub,
|
|
501
|
-
* the monitor socket) BEFORE the newcomer binds anything of its own (SING-11) —
|
|
502
|
-
* the daemon no longer hosts a secrets broker socket (the standalone `secrets`
|
|
503
|
-
* CLI owns it, PHNX-3989 OWN-1) nor a browser IPC socket (the standalone
|
|
504
|
-
* `browser` CLI owns it, PHNX-4101). Escalates
|
|
505
|
-
* to killTree after the grace window. Passes the POSITIVE pid so the kill reaches
|
|
506
|
-
* only the incumbent daemon — never its detached routine children, which run in
|
|
507
|
-
* their own process groups and must survive takeover (SING-11a); the new daemon
|
|
508
|
-
* re-adopts them via monitorRunningJobs. Synchronous to match claimDaemonInstance's
|
|
509
|
-
* read-evict-write, which runs under the O_EXCL start lock; mirrors stopDaemon's
|
|
510
|
-
* grace-then-escalate shape and constants exactly, because the same
|
|
511
|
-
* proof-of-release requirement applies.
|
|
512
|
-
*/
|
|
513
271
|
function evictIncumbentDaemon(pid) {
|
|
514
272
|
const beforeSignal = daemonProcessIdentity(pid);
|
|
515
273
|
if (beforeSignal === 'dead' || beforeSignal === 'other')
|
|
@@ -517,9 +275,6 @@ function evictIncumbentDaemon(pid) {
|
|
|
517
275
|
if (beforeSignal === 'unknown')
|
|
518
276
|
return false;
|
|
519
277
|
if (process.platform === 'win32') {
|
|
520
|
-
// No graceful termination signal on Windows — take the incumbent down and
|
|
521
|
-
// still wait for the kill to land before the caller binds anything (mirrors
|
|
522
|
-
// stopDaemon's win32 branch).
|
|
523
278
|
killTree(pid);
|
|
524
279
|
if (waitForExit(pid, STOP_KILL_GRACE_MS))
|
|
525
280
|
return true;
|
|
@@ -534,67 +289,39 @@ function evictIncumbentDaemon(pid) {
|
|
|
534
289
|
return afterSignal === 'dead' || afterSignal === 'other';
|
|
535
290
|
}
|
|
536
291
|
if (waitForExit(pid, STOP_GRACE_MS))
|
|
537
|
-
return true;
|
|
292
|
+
return true;
|
|
538
293
|
const beforeKill = daemonProcessIdentity(pid);
|
|
539
294
|
if (beforeKill === 'dead' || beforeKill === 'other')
|
|
540
295
|
return true;
|
|
541
296
|
if (beforeKill === 'unknown')
|
|
542
297
|
return false;
|
|
543
|
-
killTree(pid);
|
|
298
|
+
killTree(pid);
|
|
544
299
|
if (waitForExit(pid, STOP_KILL_GRACE_MS))
|
|
545
300
|
return true;
|
|
546
301
|
const afterKill = daemonProcessIdentity(pid);
|
|
547
302
|
return afterKill === 'dead' || afterKill === 'other';
|
|
548
303
|
}
|
|
549
|
-
/** Directory that registers every live daemon of THIS device (one per state dir). */
|
|
550
304
|
function getDaemonInstancesDir() {
|
|
551
305
|
return path.join(getDaemonDir(), 'instances');
|
|
552
306
|
}
|
|
553
|
-
/**
|
|
554
|
-
* Record this daemon in the device's instance registry — a marker file named by
|
|
555
|
-
* pid under `<daemonDir>/instances/`. The registry, not a process scan, is how
|
|
556
|
-
* the reaper enumerates the device singleton: because the dir lives INSIDE the
|
|
557
|
-
* state dir (`AGENTS_DAEMON_DIR` ?? `<HOME>/.agents/.cache/helpers/daemon`), every
|
|
558
|
-
* daemon of one device — however it was launched — registers in the same place,
|
|
559
|
-
* while a genuinely separate install/home or a test fixture registers under its
|
|
560
|
-
* own state dir and is invisible here. This is what fixes the two-entry pile-up:
|
|
561
|
-
* the compiled `dist/bin/agents` binary and the `node <shim>` JS entry have
|
|
562
|
-
* different `process.argv[1]`, so the old launch-entry-scoped `ps` match never
|
|
563
|
-
* reaped across them and duplicates accumulated (78 observed on one box), every
|
|
564
|
-
* routine double-firing. Best-effort — the reaper self-heals a missing/stale
|
|
565
|
-
* marker, and reading another process's ENV to key on the state dir directly is
|
|
566
|
-
* not portable (hardened macOS hides it from `ps`), so identity rides the shared
|
|
567
|
-
* on-disk registry instead. No-op on Windows (POSIX-only reaper).
|
|
568
|
-
*/
|
|
569
307
|
export function registerDaemonInstance(pid = process.pid) {
|
|
570
308
|
if (process.platform === 'win32')
|
|
571
309
|
return;
|
|
572
310
|
try {
|
|
573
311
|
const dir = getDaemonInstancesDir();
|
|
574
312
|
fs.mkdirSync(dir, { recursive: true });
|
|
575
|
-
// The command line is stored for diagnostics; the filename (pid) is identity.
|
|
576
313
|
fs.writeFileSync(path.join(dir, String(pid)), process.argv.slice(1).join(' '), 'utf-8');
|
|
577
314
|
}
|
|
578
|
-
catch {
|
|
315
|
+
catch { }
|
|
579
316
|
}
|
|
580
|
-
/** Remove this daemon's registry marker on graceful shutdown. */
|
|
581
317
|
export function unregisterDaemonInstance(pid = process.pid) {
|
|
582
318
|
if (process.platform === 'win32')
|
|
583
319
|
return;
|
|
584
320
|
try {
|
|
585
321
|
fs.rmSync(path.join(getDaemonInstancesDir(), String(pid)), { force: true });
|
|
586
322
|
}
|
|
587
|
-
catch {
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Reap stray duplicate daemons of THIS device — every registrant in the instance
|
|
591
|
-
* registry that is a live `agents __daemon-run` and is neither this process nor
|
|
592
|
-
* the current pid-file owner. A predecessor SIGKILLed/OOM-ed without cleanup, or a
|
|
593
|
-
* duplicate that lost the pid-file write race, would otherwise keep a second
|
|
594
|
-
* scheduler alive and double-fire jobs even after claimDaemonInstance() hands the
|
|
595
|
-
* pid file to the survivor. Also garbage-collects markers whose pid is dead or was
|
|
596
|
-
* reused by an unrelated process. No-op on Windows (POSIX-only).
|
|
597
|
-
*/
|
|
323
|
+
catch { }
|
|
324
|
+
}
|
|
598
325
|
export function reapStrayDaemons(keepPid = process.pid) {
|
|
599
326
|
const details = [];
|
|
600
327
|
let reaped = 0;
|
|
@@ -606,28 +333,25 @@ export function reapStrayDaemons(keepPid = process.pid) {
|
|
|
606
333
|
entries = fs.readdirSync(dir);
|
|
607
334
|
}
|
|
608
335
|
catch {
|
|
609
|
-
return { reaped, details };
|
|
336
|
+
return { reaped, details };
|
|
610
337
|
}
|
|
611
338
|
const ownerPid = resolveLiveDaemonPid();
|
|
612
339
|
const dropMarker = (name) => {
|
|
613
340
|
try {
|
|
614
341
|
fs.rmSync(path.join(dir, name), { force: true });
|
|
615
342
|
}
|
|
616
|
-
catch {
|
|
343
|
+
catch { }
|
|
617
344
|
};
|
|
618
345
|
for (const name of entries) {
|
|
619
346
|
const pid = parseInt(name, 10);
|
|
620
347
|
if (isNaN(pid) || String(pid) !== name)
|
|
621
|
-
continue;
|
|
348
|
+
continue;
|
|
622
349
|
if (pid === keepPid || pid === process.pid || pid === ownerPid)
|
|
623
350
|
continue;
|
|
624
|
-
// Dead registrant → stale marker.
|
|
625
351
|
if (!isAlive(pid)) {
|
|
626
352
|
dropMarker(name);
|
|
627
353
|
continue;
|
|
628
354
|
}
|
|
629
|
-
// Live pid, but guard against pid reuse: only a real `__daemon-run` is ours.
|
|
630
|
-
// Process ARGS are readable cross-platform (unlike ENV on hardened macOS).
|
|
631
355
|
const identity = daemonProcessIdentity(pid);
|
|
632
356
|
if (identity === 'unknown') {
|
|
633
357
|
details.push(`could not verify stray daemon pid ${pid}; marker retained`);
|
|
@@ -640,16 +364,13 @@ export function reapStrayDaemons(keepPid = process.pid) {
|
|
|
640
364
|
try {
|
|
641
365
|
process.kill(pid, 'SIGTERM');
|
|
642
366
|
}
|
|
643
|
-
catch {
|
|
367
|
+
catch { }
|
|
644
368
|
if (waitForExit(pid, STOP_GRACE_MS)) {
|
|
645
369
|
reaped++;
|
|
646
370
|
details.push(`reaped stray daemon pid ${pid}`);
|
|
647
371
|
dropMarker(name);
|
|
648
372
|
continue;
|
|
649
373
|
}
|
|
650
|
-
// The pid may have been recycled during the grace window. Never hard-kill
|
|
651
|
-
// it unless it still identifies as a daemon; a stale registry marker is all
|
|
652
|
-
// we own when the command identity changed.
|
|
653
374
|
const beforeKill = daemonProcessIdentity(pid);
|
|
654
375
|
if (beforeKill === 'unknown') {
|
|
655
376
|
details.push(`could not reverify stray daemon pid ${pid}; marker retained`);
|
|
@@ -666,8 +387,6 @@ export function reapStrayDaemons(keepPid = process.pid) {
|
|
|
666
387
|
dropMarker(name);
|
|
667
388
|
}
|
|
668
389
|
else {
|
|
669
|
-
// Keep the only marker for a process that survived both signals so the
|
|
670
|
-
// next reaper/doctor can still see it.
|
|
671
390
|
details.push(`stray daemon pid ${pid} survived SIGKILL`);
|
|
672
391
|
}
|
|
673
392
|
}
|
|
@@ -683,9 +402,6 @@ function daemonProcessIdentity(pid) {
|
|
|
683
402
|
encoding: 'utf-8',
|
|
684
403
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
685
404
|
});
|
|
686
|
-
// getDaemonLaunch always places __daemon-run last. Matching it anywhere in
|
|
687
|
-
// the command line mistakes an agent prompt or shell script that merely
|
|
688
|
-
// mentions the token for the shared daemon.
|
|
689
405
|
return /(?:^|\s)["']?__daemon-run["']?\s*$/.test(out.trim()) ? 'daemon' : 'other';
|
|
690
406
|
}
|
|
691
407
|
catch {
|
|
@@ -711,7 +427,6 @@ function rotateLogsIfNeeded(logPath) {
|
|
|
711
427
|
}
|
|
712
428
|
catch { }
|
|
713
429
|
}
|
|
714
|
-
/** Append a JSONL log entry to the daemon log file (owner-only permissions). */
|
|
715
430
|
export function log(level, message) {
|
|
716
431
|
const logPath = getDaemonLogPath();
|
|
717
432
|
rotateLogsIfNeeded(logPath);
|
|
@@ -720,30 +435,21 @@ export function log(level, message) {
|
|
|
720
435
|
try {
|
|
721
436
|
fs.chmodSync(logPath, 0o600);
|
|
722
437
|
}
|
|
723
|
-
catch {
|
|
724
|
-
// Mirror into the unified event stream so `agents events --module daemon` sees
|
|
725
|
-
// always-on process lifecycle the same way secrets/browser/computer do.
|
|
726
|
-
// Fail soft — the daemon log file remains the primary sink for `daemon logs`.
|
|
438
|
+
catch { }
|
|
727
439
|
try {
|
|
728
440
|
const lvl = level.toUpperCase();
|
|
729
441
|
const event = lvl === 'ERROR' || lvl === 'FATAL' ? 'daemon.error'
|
|
730
442
|
: lvl === 'START' || /starting|started/i.test(message) ? 'daemon.start'
|
|
731
443
|
: lvl === 'STOP' || /stopping|stopped|shutting down/i.test(message) ? 'daemon.stop'
|
|
732
444
|
: 'daemon.info';
|
|
733
|
-
// Fire-and-forget the event mirror: `log()` is a synchronous primitive on
|
|
734
|
-
// every daemon tick's `ctx.log`, and the mirror's event-log lock would
|
|
735
|
-
// otherwise block the shared event loop for up to 30s under contention
|
|
736
|
-
// (PHNX-3695). The daemon-log append above is the primary, synchronous sink;
|
|
737
|
-
// the mirror is best-effort, so a fire-and-forget async write is correct.
|
|
738
445
|
void emitAsync(event, {
|
|
739
446
|
module: 'daemon',
|
|
740
447
|
detail: redactSecrets(message).slice(0, 500),
|
|
741
448
|
status: lvl,
|
|
742
449
|
}).catch(() => { });
|
|
743
450
|
}
|
|
744
|
-
catch {
|
|
451
|
+
catch { }
|
|
745
452
|
}
|
|
746
|
-
/** Keep synchronous signal-handler failures inside the daemon's crash barrier. */
|
|
747
453
|
export function guardSignalHandler(handler, onError) {
|
|
748
454
|
return () => {
|
|
749
455
|
try {
|
|
@@ -754,24 +460,6 @@ export function guardSignalHandler(handler, onError) {
|
|
|
754
460
|
}
|
|
755
461
|
};
|
|
756
462
|
}
|
|
757
|
-
/** Main daemon loop: load jobs, schedule crons, monitor runs, and handle signals. */
|
|
758
|
-
/**
|
|
759
|
-
* Anchor the daemon's working directory to a stable, always-present path.
|
|
760
|
-
*
|
|
761
|
-
* The daemon is long-lived and inherits whatever cwd it was launched from — often
|
|
762
|
-
* a git worktree (e.g. a `.agents/worktrees/<slug>/` a session happened to be in).
|
|
763
|
-
* When that directory is later removed (`git worktree remove`, `rm -rf`), the
|
|
764
|
-
* daemon keeps the deleted inode as its cwd — a process cannot chdir out of a
|
|
765
|
-
* deleted directory on its own — and every job it spawns inherits the dead cwd
|
|
766
|
-
* (`spawnJobAttempt` and command runs pass no explicit `cwd`, so the child uses
|
|
767
|
-
* the parent's). Bun then fails `getcwd()` during startup and every routine crashes
|
|
768
|
-
* at 0 seconds with `ENOENT: Bun could not find a file` before the agent even runs.
|
|
769
|
-
*
|
|
770
|
-
* Re-anchoring to the home directory once, at daemon startup, makes the daemon
|
|
771
|
-
* immune regardless of how it was launched (systemd unit, launchd, or a manual
|
|
772
|
-
* `agents __daemon-run` from any directory). Returns the resolved cwd, or null if
|
|
773
|
-
* anchoring failed (logged, non-fatal).
|
|
774
|
-
*/
|
|
775
463
|
export function anchorDaemonCwd() {
|
|
776
464
|
const home = os.homedir();
|
|
777
465
|
try {
|
|
@@ -783,22 +471,6 @@ export function anchorDaemonCwd() {
|
|
|
783
471
|
return null;
|
|
784
472
|
}
|
|
785
473
|
}
|
|
786
|
-
/**
|
|
787
|
-
* Surface, at the daemon's OWN startup, that it was launched from an ephemeral
|
|
788
|
-
* root that will wedge it if the directory is removed. This is the runtime
|
|
789
|
-
* companion to the launch-time check in validateDaemonBinary (which only runs
|
|
790
|
-
* when the daemon is *spawned* via getDaemonLaunch): a direct
|
|
791
|
-
* `agents __daemon-run` from a temp or worktree build — e.g. a review/verify
|
|
792
|
-
* checkout under /tmp — never passes through that path, so without this the
|
|
793
|
-
* wedge risk stays invisible until jobs start ENOENT-ing on their dynamic
|
|
794
|
-
* imports. Best-effort and non-fatal; the cwd is already handled by
|
|
795
|
-
* anchorDaemonCwd, but a deleted module root can only be flagged, not repaired.
|
|
796
|
-
*
|
|
797
|
-
* `resolveBin` is injectable (defaults to getAgentsBinPath) so the wiring — the
|
|
798
|
-
* predicate call, the WARN, and the non-fatal guard around a throwing resolver —
|
|
799
|
-
* is testable. Returns the warning message it logged, or null when the launch
|
|
800
|
-
* root is stable (or could not be resolved).
|
|
801
|
-
*/
|
|
802
474
|
export function warnEphemeralDaemonRoot(resolveBin = getAgentsBinPath) {
|
|
803
475
|
try {
|
|
804
476
|
const bin = resolveBin();
|
|
@@ -816,27 +488,6 @@ export function warnEphemeralDaemonRoot(resolveBin = getAgentsBinPath) {
|
|
|
816
488
|
return null;
|
|
817
489
|
}
|
|
818
490
|
}
|
|
819
|
-
/**
|
|
820
|
-
* Test-home tripwire (PHNX-2545). The routines/daemon test suite spawns real
|
|
821
|
-
* `agents __daemon-run` processes against an isolated /tmp HOME. If that HOME
|
|
822
|
-
* override fails to reach the child — an `env: {...process.env}` spawn that
|
|
823
|
-
* forgot to set it, a login shell that reset HOME — the daemon resolves its
|
|
824
|
-
* state dir under the operator's REAL home and its scheduler/watchdog then tick
|
|
825
|
-
* against shared production state. That is the exact leak the ticket reports:
|
|
826
|
-
* real test daemons found alive on a fleet box, each a second live scheduler
|
|
827
|
-
* racing the legitimate one, in violation of the execution-singularity spec.
|
|
828
|
-
*
|
|
829
|
-
* A test that spawns a daemon sets AGENTS_DAEMON_TEST_HOME to the isolated home
|
|
830
|
-
* it provisioned. When that marker is present, this daemon's resolved state dir
|
|
831
|
-
* MUST sit under it; otherwise the daemon refuses to boot — failing loud before
|
|
832
|
-
* it claims an instance, writes a pid, or fires a single tick (the throw is
|
|
833
|
-
* caught in index.ts's `__daemon-run` handler, logged, and exits non-zero) —
|
|
834
|
-
* rather than running in the wrong directory against the real host. In
|
|
835
|
-
* production the marker is never set, so this is a no-op there.
|
|
836
|
-
*
|
|
837
|
-
* `daemonDir`/`testHome` are injectable so the pure guard is unit-testable
|
|
838
|
-
* without spawning a process; the defaults read the live daemon dir and env.
|
|
839
|
-
*/
|
|
840
491
|
export function assertTestDaemonHome(daemonDir = getDaemonDir(), testHome = process.env.AGENTS_DAEMON_TEST_HOME) {
|
|
841
492
|
if (!testHome)
|
|
842
493
|
return;
|
|
@@ -850,28 +501,8 @@ export function assertTestDaemonHome(daemonDir = getDaemonDir(), testHome = proc
|
|
|
850
501
|
throw new Error(msg);
|
|
851
502
|
}
|
|
852
503
|
}
|
|
853
|
-
// ---------------------------------------------------------------------------
|
|
854
|
-
// Module-level periodic maintenance helpers (RUSH-2422)
|
|
855
|
-
//
|
|
856
|
-
// These were inline closures inside runDaemon(). Moved here so they are
|
|
857
|
-
// named in stack traces, readable without scrolling through runDaemon's
|
|
858
|
-
// 500-line body, and not recreated on every function invocation.
|
|
859
|
-
//
|
|
860
|
-
// self-heal moved to SelfHealService on the ServiceSupervisor (RUSH-3193 P3)
|
|
861
|
-
// — the supervisor's own per-tick deadline + inFlight guard replaces its local
|
|
862
|
-
// `healing` flag. state-dir-check moved to StateDirCheckService (RUSH-3193
|
|
863
|
-
// P3), registered after `handleShutdown` is declared — see its registration
|
|
864
|
-
// site below. The secrets broker (self-heal, reap, and hosting) moved out of
|
|
865
|
-
// this daemon entirely with the standalone `secrets` engine (PHNX-3989 OWN-1).
|
|
866
|
-
// ---------------------------------------------------------------------------
|
|
867
504
|
export async function runDaemon() {
|
|
868
|
-
// PHNX-2545 test-home tripwire — FIRST, before this daemon claims an instance,
|
|
869
|
-
// writes a pid, or fires any tick. A test-spawned daemon that lost its isolated
|
|
870
|
-
// HOME override must refuse to run against the operator's real state rather than
|
|
871
|
-
// schedule against the real host. No-op in production (the marker is never set).
|
|
872
505
|
assertTestDaemonHome();
|
|
873
|
-
// Lifecycle readers and launchers do not run services. Load their code only
|
|
874
|
-
// in the daemon process, before it claims or publishes lifecycle state.
|
|
875
506
|
const [{ SessionIndexService }, { SessionSummarizerService }, { SessionTitleService }, { MonitorEngineService }, { AccountUsageService, AccountAuthService }, { CatchupService }, { WatchdogService }, { DeviceProbeService }, { SelfHealService }, { SelfUpdateService }, { HarnessUpdateService }, { AuthSyncService }, { UsageSyncService }, { StateDirCheckService }, { SessionStateService }, { FeedStreamService }, { AttentionNotifyService }, { WebhookReceiverService }, { HeartbeatService }, { TmuxReapService },] = await Promise.all([
|
|
876
507
|
import('./session-index-service.js'),
|
|
877
508
|
import('./session-summarizer-service.js'),
|
|
@@ -894,13 +525,6 @@ export async function runDaemon() {
|
|
|
894
525
|
import('./heartbeat-service.js'),
|
|
895
526
|
import('./tmux-reap-service.js'),
|
|
896
527
|
]);
|
|
897
|
-
// Install the shared-daemon reload signal boundary BEFORE publishing our PID
|
|
898
|
-
// in claimDaemonInstance(). Browser/routines clients use that PID to decide a
|
|
899
|
-
// daemon exists and may request a service reload immediately. POSIX otherwise
|
|
900
|
-
// applies its default SIGHUP action during the rest of startup and terminates
|
|
901
|
-
// the whole process — exactly the client-caused eviction PHNX-3605 forbids.
|
|
902
|
-
// Requests received before services are ready coalesce into one reload and are
|
|
903
|
-
// applied through the normal guarded handler once startup completes.
|
|
904
528
|
let reloadRequestedDuringStartup = false;
|
|
905
529
|
let liveReloadHandler = null;
|
|
906
530
|
const dispatchReloadSignal = () => {
|
|
@@ -911,22 +535,12 @@ export async function runDaemon() {
|
|
|
911
535
|
};
|
|
912
536
|
if (process.platform !== 'win32')
|
|
913
537
|
process.on('SIGHUP', dispatchReloadSignal);
|
|
914
|
-
// Single-instance guard (last-wins, SING-11): a direct `agents __daemon-run`
|
|
915
|
-
// (manual, or a service-manager restart racing a live predecessor) EVICTS the
|
|
916
|
-
// incumbent and becomes the survivor. claimDaemonInstance returns false only
|
|
917
|
-
// when a concurrent `__daemon-run` currently holds the start lock — that peer
|
|
918
|
-
// is mid-takeover and will be the singleton, so this instance stands down.
|
|
919
538
|
if (!claimDaemonInstance()) {
|
|
920
539
|
if (process.platform !== 'win32')
|
|
921
540
|
process.removeListener('SIGHUP', dispatchReloadSignal);
|
|
922
541
|
log('WARN', `Another daemon owns lifecycle state or is mid-takeover; this instance (PID ${process.pid}) is exiting`);
|
|
923
|
-
// Exit cleanly (0) so a service manager treats it as an orderly no-op
|
|
924
|
-
// rather than a failure to restart-flap on.
|
|
925
542
|
process.exit(0);
|
|
926
543
|
}
|
|
927
|
-
// Deterministic integration-test seam for the PID-published/startup-complete
|
|
928
|
-
// signal window above. It is honored only inside an explicitly isolated test
|
|
929
|
-
// HOME; production daemons never pause here.
|
|
930
544
|
const startupDelayRaw = process.env.AGENTS_DAEMON_TEST_STARTUP_DELAY_MS;
|
|
931
545
|
if (process.env.AGENTS_DAEMON_TEST_HOME && startupDelayRaw) {
|
|
932
546
|
const startupDelayMs = Number(startupDelayRaw);
|
|
@@ -935,21 +549,12 @@ export async function runDaemon() {
|
|
|
935
549
|
}
|
|
936
550
|
await new Promise((resolve) => setTimeout(resolve, startupDelayMs));
|
|
937
551
|
}
|
|
938
|
-
// Unlike the pid and heartbeat files, this marker is written exactly once
|
|
939
|
-
// for this daemon lifetime. Status probes deliberately repair those other
|
|
940
|
-
// files, so they cannot prove that the original state dir still exists.
|
|
941
552
|
const lifetimePath = path.join(getDaemonDir(), LIFETIME_FILE);
|
|
942
553
|
const lifetimeToken = `${process.pid}:${Date.now()}`;
|
|
943
554
|
fs.writeFileSync(lifetimePath, lifetimeToken, 'utf-8');
|
|
944
555
|
log('INFO', `Daemon started (PID: ${process.pid})`);
|
|
945
556
|
anchorDaemonCwd();
|
|
946
557
|
warnEphemeralDaemonRoot();
|
|
947
|
-
// Converge the device-config/pins stores (legacy central block /
|
|
948
|
-
// auto-launch.json / tracked-doc pins → per-device docs + pins file).
|
|
949
|
-
// Idempotent, cheap no-op once folded. The daemon boots via
|
|
950
|
-
// `agents __daemon-run`, which bypasses bootstrap's migration sentinel — so
|
|
951
|
-
// the daemon runs this itself so its scheduler/watchdog gates read the
|
|
952
|
-
// converged store.
|
|
953
558
|
try {
|
|
954
559
|
const { migrateDeviceConfigStores } = await import('../devices/config-migration.js');
|
|
955
560
|
migrateDeviceConfigStores();
|
|
@@ -957,12 +562,6 @@ export async function runDaemon() {
|
|
|
957
562
|
catch (err) {
|
|
958
563
|
log('WARN', `device config migration failed: ${err.message}`);
|
|
959
564
|
}
|
|
960
|
-
// Version-skew one-shot (RUSH-2435): retrofit the current pane-died hook onto
|
|
961
|
-
// any managed tmux session a pre-fix binary left with a stale one. The 5-min
|
|
962
|
-
// `tmux-reconcile` routine that used to run this on a poll was deleted
|
|
963
|
-
// (RUSH-2495) — startup + `ensureSessionHookRepaired` at attach time
|
|
964
|
-
// (tmux/session.ts) now cover what that poll used to. Idempotent and
|
|
965
|
-
// non-destructive: a session already at the current schema is a no-op.
|
|
966
565
|
try {
|
|
967
566
|
const { reconcileSessionHooks } = await import('../tmux/session.js');
|
|
968
567
|
const { isTmuxInstalled } = await import('../tmux/binary.js');
|
|
@@ -975,21 +574,8 @@ export async function runDaemon() {
|
|
|
975
574
|
catch (err) {
|
|
976
575
|
log('WARN', `tmux hook reconcile failed: ${err.message}`);
|
|
977
576
|
}
|
|
978
|
-
// Per-service toggles live outside device-config so they can be checked by
|
|
979
|
-
// service clients (e.g. secrets) without loading the whole config stack.
|
|
980
577
|
let servicesConfig = readDaemonServicesConfig();
|
|
981
578
|
const isEnabled = (id) => servicesConfig.services[id] !== false;
|
|
982
|
-
// The daemon holds NO Claude credential of its own. Routine runs authenticate
|
|
983
|
-
// exactly like an interactive `agents run`: through the per-account
|
|
984
|
-
// CLAUDE_CONFIG_DIR login on this device (its own auto-refreshing
|
|
985
|
-
// .credentials.json). Claude Code's interactive access token is short-lived but
|
|
986
|
-
// refreshes itself per-device; a routine whose account login has gone dead is
|
|
987
|
-
// skipped up front by the auth-health preflight (runner.ts) with a re-login
|
|
988
|
-
// hint, rather than papered over by an injected fallback token.
|
|
989
|
-
// Register this daemon in the device instance registry, then reap any stray
|
|
990
|
-
// duplicate that slipped past the start lock or was orphaned by a hard-crash —
|
|
991
|
-
// before it can double-fire jobs. Registration comes first so a racing peer's
|
|
992
|
-
// reaper can see this pid, and so this reaper never mistakes itself for a stray.
|
|
993
579
|
registerDaemonInstance();
|
|
994
580
|
try {
|
|
995
581
|
const strays = reapStrayDaemons();
|
|
@@ -1002,19 +588,12 @@ export async function runDaemon() {
|
|
|
1002
588
|
catch (err) {
|
|
1003
589
|
log('ERROR', `Stray daemon reaper failed: ${err.message}`);
|
|
1004
590
|
}
|
|
1005
|
-
// Socket services: monitor engine, account-state, and the feed-stream hub are
|
|
1006
|
-
// all managed by the ServiceSupervisor (RUSH-3193 P2). The secrets broker moved
|
|
1007
|
-
// with the standalone `secrets` engine (PHNX-3989 OWN-1) and the browser IPC
|
|
1008
|
-
// service with the standalone `browser` CLI (PHNX-4101) — this daemon no longer
|
|
1009
|
-
// hosts either; each standalone owns its own lifecycle exclusively.
|
|
1010
591
|
const supervisor = new ServiceSupervisor();
|
|
1011
592
|
if (isEnabled('session-state')) {
|
|
1012
593
|
supervisor.register(new SessionStateService(() => supervisor.runNow('session-state')));
|
|
1013
594
|
}
|
|
1014
595
|
else
|
|
1015
596
|
log('INFO', 'Live session-state service disabled');
|
|
1016
|
-
// The shared feed fan-out. Registered even when disabled at boot so a later
|
|
1017
|
-
// `agents daemon services enable feed-stream` brings it up over SIGHUP.
|
|
1018
597
|
supervisor.register(new FeedStreamService(), { enabled: isEnabled('feed-stream') });
|
|
1019
598
|
if (!isEnabled('feed-stream'))
|
|
1020
599
|
log('INFO', 'Shared feed stream service disabled');
|
|
@@ -1023,9 +602,6 @@ export async function runDaemon() {
|
|
|
1023
602
|
supervisor.register(monitorEngineSvc);
|
|
1024
603
|
else
|
|
1025
604
|
log('INFO', 'Monitor engine disabled');
|
|
1026
|
-
// Usage and auth refresh are two INDEPENDENT supervised services (PHNX-3608)
|
|
1027
|
-
// so a run of usage-refresh throws is recorded against usage alone and never
|
|
1028
|
-
// starves the slower auth refresh — each keeps ticking on its own interval.
|
|
1029
605
|
if (isEnabled('account-state'))
|
|
1030
606
|
supervisor.register(new AccountUsageService());
|
|
1031
607
|
else
|
|
@@ -1034,19 +610,7 @@ export async function runDaemon() {
|
|
|
1034
610
|
supervisor.register(new AccountAuthService());
|
|
1035
611
|
else
|
|
1036
612
|
log('INFO', 'Account-auth service disabled');
|
|
1037
|
-
// The routine scheduler handle. Declared HERE — before the CatchupService
|
|
1038
|
-
// registration — because `supervisor.startAll()` below fires each service's
|
|
1039
|
-
// first tick synchronously, so the `catchup` tick reads `scheduler` during
|
|
1040
|
-
// startAll, BEFORE this `let` would initialise if it lived at its old textual
|
|
1041
|
-
// position further down. That was a real TDZ `ReferenceError` on every boot
|
|
1042
|
-
// ("Cannot access 'scheduler' before initialization"), not a race (PHNX-3608).
|
|
1043
|
-
// `bootScheduler`/`stopScheduler` (hoisted below) assign this same binding.
|
|
1044
613
|
let scheduler = null;
|
|
1045
|
-
// Catch-up recovery under the supervisor (PHNX-3608). The closures reference
|
|
1046
|
-
// `scheduler` (declared just above) and `catchupPass` (a hoisted function
|
|
1047
|
-
// declaration). The tick self-gates on the scheduler being booted, so it is a
|
|
1048
|
-
// cheap no-op — including on its immediate first tick during startAll, when
|
|
1049
|
-
// `scheduler` is still null — on a device whose scheduler.enabled gate is off.
|
|
1050
614
|
if (isEnabled('catchup')) {
|
|
1051
615
|
supervisor.register(new CatchupService({
|
|
1052
616
|
isSchedulerBooted: () => scheduler !== null,
|
|
@@ -1056,40 +620,22 @@ export async function runDaemon() {
|
|
|
1056
620
|
else {
|
|
1057
621
|
log('INFO', 'Catch-up recovery service disabled');
|
|
1058
622
|
}
|
|
1059
|
-
// The browser IPC service and its task reaper are the standalone `browser`
|
|
1060
|
-
// CLI's now (@phnx-labs/browser-cli, PHNX-4101): it hosts its own IPC service
|
|
1061
|
-
// and runs its own `prune` reaper. agents-cli no longer constructs a
|
|
1062
|
-
// BrowserService, binds a browser socket, or reaps browser tasks — see
|
|
1063
|
-
// `commands/browser.ts` and `docs/browser.md`.
|
|
1064
623
|
if (isEnabled('session-index'))
|
|
1065
624
|
supervisor.register(new SessionIndexService());
|
|
1066
625
|
else
|
|
1067
626
|
log('INFO', 'Session-index warm service disabled');
|
|
1068
|
-
// Session summarizer (PHNX-3939) — registered when the service toggle is on,
|
|
1069
|
-
// but each tick is a no-op unless the operator also set summarizer.enabled and
|
|
1070
|
-
// a model endpoint, so registering it costs nothing while unconfigured.
|
|
1071
627
|
if (isEnabled('session-summarizer'))
|
|
1072
628
|
supervisor.register(new SessionSummarizerService());
|
|
1073
629
|
else
|
|
1074
630
|
log('INFO', 'Session summarizer service disabled');
|
|
1075
|
-
// Attention desktop banners (PHNX-4004) — posts one actionable native banner
|
|
1076
|
-
// per new attention key. Reader-independent: it fires whether or not a
|
|
1077
|
-
// `sessions watch` reader is present, and the notified-ledger is its
|
|
1078
|
-
// idempotency truth across daemon restarts.
|
|
1079
631
|
if (isEnabled('attention-notify'))
|
|
1080
632
|
supervisor.register(new AttentionNotifyService());
|
|
1081
633
|
else
|
|
1082
634
|
log('INFO', 'Attention-notify service disabled');
|
|
1083
|
-
// Session titles (PHNX-3797) — generates each session row's headline once,
|
|
1084
|
-
// with a cheap model, off the request path.
|
|
1085
635
|
if (isEnabled('session-title'))
|
|
1086
636
|
supervisor.register(new SessionTitleService());
|
|
1087
637
|
else
|
|
1088
638
|
log('INFO', 'Session-title service disabled');
|
|
1089
|
-
// Watchdog, device-probe, and self-heal are all periodic services managed
|
|
1090
|
-
// by the ServiceSupervisor (RUSH-3193 P3). Each is gated the same way as
|
|
1091
|
-
// the socket services above; state-dir-check is registered separately,
|
|
1092
|
-
// later, after `handleShutdown` exists (see below).
|
|
1093
639
|
if (isEnabled('watchdog'))
|
|
1094
640
|
supervisor.register(new WatchdogService());
|
|
1095
641
|
else
|
|
@@ -1132,15 +678,6 @@ export async function runDaemon() {
|
|
|
1132
678
|
log('INFO', 'Tmux reap service disabled');
|
|
1133
679
|
await supervisor.startAll({ log });
|
|
1134
680
|
activeServiceSupervisor = supervisor;
|
|
1135
|
-
// scheduler.enabled=false in this machine's device doc means NO routines fire
|
|
1136
|
-
// here — the scheduler and its catchup recovery simply never start, while the
|
|
1137
|
-
// daemon keeps its other duties (feed stream, session sync).
|
|
1138
|
-
// The refusal message is the same one the start surfaces
|
|
1139
|
-
// (`routines add` auto-start, manual `routines start`) raise. The gate is
|
|
1140
|
-
// re-evaluated on every SIGHUP reload (handleReload below) via
|
|
1141
|
-
// schedulerGateTransition, so flipping the key never needs a daemon restart.
|
|
1142
|
-
// Also honour the daemon-services toggle so `agents daemon services disable scheduler`
|
|
1143
|
-
// has a single, obvious effect.
|
|
1144
681
|
const schedulerEnabledAtBoot = isSchedulerEnabled() && isEnabled('scheduler');
|
|
1145
682
|
if (!schedulerEnabledAtBoot) {
|
|
1146
683
|
try {
|
|
@@ -1166,15 +703,10 @@ export async function runDaemon() {
|
|
|
1166
703
|
...(config.agent ? { agent: config.agent } : {}),
|
|
1167
704
|
...(config.workflow ? { workflow: config.workflow } : {}),
|
|
1168
705
|
});
|
|
1169
|
-
// RUSH-2030: branded desktop notification on start (agent/workflow routines;
|
|
1170
|
-
// suppressed for command housekeeping). Finish/output is fired from the
|
|
1171
|
-
// onFinish hook below — executeJobDetached finalizes the run in-process, so
|
|
1172
|
-
// the monitor tick never sees the live transition. Never let a notification
|
|
1173
|
-
// failure break the trigger.
|
|
1174
706
|
try {
|
|
1175
707
|
notifyRoutineStart(config);
|
|
1176
708
|
}
|
|
1177
|
-
catch {
|
|
709
|
+
catch { }
|
|
1178
710
|
try {
|
|
1179
711
|
const meta = await executeJobDetached(config, {
|
|
1180
712
|
onFinish: (final) => {
|
|
@@ -1182,11 +714,7 @@ export async function runDaemon() {
|
|
|
1182
714
|
try {
|
|
1183
715
|
notifyRoutineFinish(final);
|
|
1184
716
|
}
|
|
1185
|
-
catch {
|
|
1186
|
-
// RUSH-2288: a failed/timed-out routine also reaches the OWNER's phone
|
|
1187
|
-
// (in-process owner channel stack), not just the local desktop. Green
|
|
1188
|
-
// runs are silent — the builder returns early. Async + swallowed so a
|
|
1189
|
-
// delivery hiccup never blocks the finish path.
|
|
717
|
+
catch { }
|
|
1190
718
|
void notifyOwnerRoutineFinish(final)
|
|
1191
719
|
.then((r) => {
|
|
1192
720
|
if (r.attempts.length && !r.delivered)
|
|
@@ -1205,17 +733,10 @@ export async function runDaemon() {
|
|
|
1205
733
|
status: 'failed',
|
|
1206
734
|
detail: redactSecrets(message).slice(0, 500),
|
|
1207
735
|
});
|
|
1208
|
-
// RUSH-2030: the START ping already fired unconditionally above. A pre-spawn
|
|
1209
|
-
// failure produces no run record and thus no onFinish, so send a synthetic
|
|
1210
|
-
// "failed to start" finish here — otherwise the user is left with an orphaned
|
|
1211
|
-
// "Routine started" and never told it failed.
|
|
1212
736
|
try {
|
|
1213
737
|
notifyRoutineStartFailed(config, message);
|
|
1214
738
|
}
|
|
1215
|
-
catch {
|
|
1216
|
-
// RUSH-2288: the pre-spawn failure (e.g. auth_failed) is exactly the one the
|
|
1217
|
-
// per-routine `agents send --to owner` prompt can never send — its agent never ran —
|
|
1218
|
-
// so the daemon reaches the owner directly.
|
|
739
|
+
catch { }
|
|
1219
740
|
void notifyOwnerRoutineStartFailed(config, message)
|
|
1220
741
|
.then((r) => {
|
|
1221
742
|
if (r.attempts.length && !r.delivered)
|
|
@@ -1224,13 +745,6 @@ export async function runDaemon() {
|
|
|
1224
745
|
.catch(() => { });
|
|
1225
746
|
}
|
|
1226
747
|
};
|
|
1227
|
-
// `scheduler` is declared earlier (before the CatchupService registration) to
|
|
1228
|
-
// avoid a TDZ read during supervisor.startAll — see the comment there.
|
|
1229
|
-
// Boot the scheduler. Called at daemon start when the gate allows, and again
|
|
1230
|
-
// from handleReload when the gate flips on. Catch-up recovery is a separate
|
|
1231
|
-
// supervised service (CatchupService, registered above) that self-gates on
|
|
1232
|
-
// `scheduler !== null`; here we just kick an immediate supervised pass so a
|
|
1233
|
-
// fresh boot catches up missed fires without waiting a full CATCHUP_TICK_MS.
|
|
1234
748
|
function bootScheduler() {
|
|
1235
749
|
scheduler = new JobScheduler(triggerJob);
|
|
1236
750
|
scheduler.loadAll();
|
|
@@ -1242,14 +756,10 @@ export async function runDaemon() {
|
|
|
1242
756
|
if (supervisor.isRegistered('catchup'))
|
|
1243
757
|
supervisor.runNow('catchup');
|
|
1244
758
|
}
|
|
1245
|
-
// Stop the scheduler (gate flipped off on reload). The CatchupService keeps its
|
|
1246
|
-
// supervised timer but its tick no-ops once `scheduler` is null.
|
|
1247
759
|
function stopScheduler() {
|
|
1248
760
|
scheduler?.stopAll();
|
|
1249
761
|
scheduler = null;
|
|
1250
762
|
}
|
|
1251
|
-
// Materialise opted-in project routines into the user layer on every start
|
|
1252
|
-
// so a fresh daemon picks up project YAML without a separate sync step.
|
|
1253
763
|
try {
|
|
1254
764
|
const result = syncAllProjectRoutines();
|
|
1255
765
|
const n = result.projects.reduce((acc, p) => acc + p.synced.length, 0);
|
|
@@ -1261,36 +771,6 @@ export async function runDaemon() {
|
|
|
1261
771
|
}
|
|
1262
772
|
if (schedulerEnabledAtBoot)
|
|
1263
773
|
bootScheduler();
|
|
1264
|
-
// Live-session metadata publishing is owned by SessionStateService. Its
|
|
1265
|
-
// presence watcher requests an immediate supervised tick when a reader
|
|
1266
|
-
// connects; callers consume the journal instead of duplicating the gather.
|
|
1267
|
-
// Session-index warm (RUSH-2682) is registered on the supervisor above
|
|
1268
|
-
// (RUSH-3193 P2) alongside the socket services.
|
|
1269
|
-
// Watchdog and device-probe are now managed by WatchdogService /
|
|
1270
|
-
// DeviceProbeService on the supervisor (RUSH-3193 P3), registered above
|
|
1271
|
-
// alongside the socket services. The supervisor fires an immediate first
|
|
1272
|
-
// tick on start, which replaces device-probe's old `void
|
|
1273
|
-
// runDeviceProbeTick()` kick-off (the 3-minute lag that used to leave the
|
|
1274
|
-
// menubar showing 20 phantom NEW DEVICES after a hermetic leak).
|
|
1275
|
-
// Monitor engine is now managed by MonitorEngineService on the supervisor
|
|
1276
|
-
// (RUSH-3193 P2). Access it via monitorEngineSvc.getEngine() in handleReload.
|
|
1277
|
-
// Backlog recovery: any enabled recurring job whose most-recent expected fire
|
|
1278
|
-
// is older than its most-recent recorded run was missed — the laptop slept,
|
|
1279
|
-
// the machine was off, or the daemon crashed through the fire. croner only
|
|
1280
|
-
// schedules forward from "now", so nothing replays it on its own.
|
|
1281
|
-
//
|
|
1282
|
-
// Every miss is RECORDED as a `missed` run and, unless the routine sets
|
|
1283
|
-
// `catchup: false`, RUN late. Runs on a timer as well as at startup: a startup
|
|
1284
|
-
// pass alone misses a fire lost while the daemon stayed up but its event loop
|
|
1285
|
-
// was wedged, or one lost across an OS suspend that the process survived.
|
|
1286
|
-
// A pass awaits executeJobDetached per job and an off-box (host/cloud)
|
|
1287
|
-
// dispatch can block for a while. Overlap is now guarded by the supervisor's
|
|
1288
|
-
// per-service inFlight guard (CatchupService) — a slow pass never overlaps the
|
|
1289
|
-
// next supervised tick — rather than a local `catchingUp` flag; and the
|
|
1290
|
-
// idempotency of the `missed` record still guards across passes and daemon
|
|
1291
|
-
// restarts. `signal` aborts at the CatchupService deadline, so a wedged pass is
|
|
1292
|
-
// abandoned + restarted instead of latching (PHNX-3608). Function declaration
|
|
1293
|
-
// (hoisted) so the CatchupService registration above can reference it.
|
|
1294
774
|
async function catchupPass(signal) {
|
|
1295
775
|
try {
|
|
1296
776
|
const overdue = detectOverdueJobs();
|
|
@@ -1306,9 +786,6 @@ export async function runDaemon() {
|
|
|
1306
786
|
notifyOverdue(overdue);
|
|
1307
787
|
const outcomes = await runCatchup({ overdue });
|
|
1308
788
|
for (const o of outcomes) {
|
|
1309
|
-
// Every variant handled explicitly: a catch-all else would log the
|
|
1310
|
-
// benign 'claimed-elsewhere' (another process legitimately won the
|
|
1311
|
-
// claim) as an ERROR with an undefined reason.
|
|
1312
789
|
switch (o.result) {
|
|
1313
790
|
case 'ran':
|
|
1314
791
|
log('INFO', `Caught up '${o.name}' (run: ${o.runId})`);
|
|
@@ -1323,9 +800,6 @@ export async function runDaemon() {
|
|
|
1323
800
|
log('ERROR', `Catchup for '${o.name}' failed: ${o.error}`);
|
|
1324
801
|
break;
|
|
1325
802
|
default: {
|
|
1326
|
-
// Compile-time exhaustiveness: a new CatchupOutcome variant fails
|
|
1327
|
-
// typecheck here rather than silently landing in the wrong log level,
|
|
1328
|
-
// which is exactly how 'claimed-elsewhere' was first missed.
|
|
1329
803
|
const unhandled = o.result;
|
|
1330
804
|
log('ERROR', `Catchup for '${o.name}' returned an unhandled result: ${String(unhandled)}`);
|
|
1331
805
|
}
|
|
@@ -1333,76 +807,23 @@ export async function runDaemon() {
|
|
|
1333
807
|
}
|
|
1334
808
|
}
|
|
1335
809
|
catch (err) {
|
|
1336
|
-
// Ordinary pass errors are logged, not re-thrown: a transient catchup
|
|
1337
|
-
// failure should not be counted as a service failure. A HANG is still
|
|
1338
|
-
// caught — the CatchupService deadline aborts the tick and the supervisor
|
|
1339
|
-
// exits the daemon for an OS restart regardless of this swallow (PHNX-4116).
|
|
1340
810
|
log('ERROR', `Catchup pass failed: ${err.message}`);
|
|
1341
811
|
}
|
|
1342
812
|
}
|
|
1343
|
-
//
|
|
1344
|
-
// standalone `browser` CLI (PHNX-4101) — it hosts its own IPC service now.
|
|
1345
|
-
// Webhook receivers: signed webhook receiver(s) + their funnel (RUSH-2548).
|
|
1346
|
-
// Resolves each receiver's signing secret headlessly through the standalone
|
|
1347
|
-
// `secrets` CLI (an agentOnly secrets-client read) — no AGENTS_SECRETS_PASSPHRASE,
|
|
1348
|
-
// no nohup. Binds nothing unless daemon/webhooks.yaml declares a receiver, so an
|
|
1349
|
-
// unconfigured box no-ops.
|
|
1350
|
-
// Signed webhook ingress is owned by WebhookReceiverService, including
|
|
1351
|
-
// per-service failure isolation, measured health, and shutdown cleanup.
|
|
1352
|
-
// Resource self-heal is now managed by SelfHealService on the supervisor
|
|
1353
|
-
// (RUSH-3193 P3), registered above alongside the socket services. The
|
|
1354
|
-
// supervisor's immediate first tick on start replaces the old
|
|
1355
|
-
// SELF_HEAL_KICKOFF_MS (30s) delayed kickoff timer — see self-heal-service.ts.
|
|
1356
|
-
// The secrets broker's self-heal and keychain-reap ticks (formerly
|
|
1357
|
-
// SecretsBrokerService / KeychainReapService, RUSH-1817 / RUSH-2232) moved
|
|
1358
|
-
// out of this daemon entirely with the standalone `secrets` engine
|
|
1359
|
-
// (PHNX-3989 OWN-1) — the standalone owns its own broker lifecycle.
|
|
1360
|
-
// RUSH-2501: reap tmux sessions whose panes are all dead. Daemon-only
|
|
1361
|
-
// (single executor). Dead managed panes and their orphan helpers are
|
|
1362
|
-
// reaped by TmuxReapService.
|
|
1363
|
-
// Abandoned browser-task reaping (RUSH-2622) left this daemon with the
|
|
1364
|
-
// standalone `browser` CLI (PHNX-4101) — its own `prune` reaper owns it now.
|
|
1365
|
-
// RUSH-2367 / RUSH-3193 P3: state-dir-check (self-terminate guard) is
|
|
1366
|
-
// registered on the supervisor further below, once `handleShutdown` exists
|
|
1367
|
-
// — see the registration site after its declaration for why.
|
|
1368
|
-
// RUSH-2418: startup is over — the scheduler, feed stream, monitor engine and
|
|
1369
|
-
// every background tick are up. Only NOW does this daemon
|
|
1370
|
-
// clear the auto-start failure streak `ensureDaemonStarted` reads. Clearing it
|
|
1371
|
-
// at claim time instead would reset the breaker for a process that dies while
|
|
1372
|
-
// initializing a subsystem, which is exactly the crash loop it exists to stop.
|
|
813
|
+
// Clear the start-failure streak only after every startup subsystem is live.
|
|
1373
814
|
recordSubsystemOk(SUBSYSTEM_DAEMON_START);
|
|
1374
815
|
const handleReload = () => {
|
|
1375
816
|
log('INFO', 'Reloading jobs (SIGHUP)');
|
|
1376
|
-
// Re-read per-service toggles so `agents daemon services enable|disable`
|
|
1377
|
-
// followed by `agents daemon reload` is truthful. Most services require a
|
|
1378
|
-
// restart to start/stop safely; log those instead of pretending they applied.
|
|
1379
817
|
const reloadedConfig = readDaemonServicesConfig();
|
|
1380
818
|
const reloadedEnabled = (id) => reloadedConfig.services[id] !== false;
|
|
1381
819
|
for (const id of Object.keys(servicesConfig.services)) {
|
|
1382
820
|
const was = servicesConfig.services[id] !== false;
|
|
1383
821
|
const now = reloadedEnabled(id);
|
|
1384
822
|
if (was !== now) {
|
|
1385
|
-
// The scheduler is not a supervised service — its gate is re-evaluated
|
|
1386
|
-
// live later in this handler, so don't tell the user to restart for it.
|
|
1387
823
|
if (id === 'scheduler') {
|
|
1388
824
|
continue;
|
|
1389
825
|
}
|
|
1390
|
-
// RUSH-3193 P4: a service the supervisor already owns takes the toggle
|
|
1391
|
-
// live via supervisor.start/stop — no restart needed. monitors is a
|
|
1392
|
-
// supervised service (PHNX-3608): its enable/disable takes effect through
|
|
1393
|
-
// that generic supervisor.start/stop path, so a disabled monitors service
|
|
1394
|
-
// actually stops dispatching (its supervised tick is torn down) instead of
|
|
1395
|
-
// being ticked with the last-loaded set. browser-ipc is deliberately
|
|
1396
|
-
// registered in a stopped state too (PHNX-3605), so a later browser client
|
|
1397
|
-
// can enable that service without restarting the shared daemon. Most other
|
|
1398
|
-
// services are only registered when enabled at boot; one disabled at boot
|
|
1399
|
-
// was never registered, so it falls through to the same "restart to apply"
|
|
1400
|
-
// advice as before.
|
|
1401
826
|
if (supervisor.isRegistered(id)) {
|
|
1402
|
-
// A periodic service may be inside a real tick when SIGHUP arrives.
|
|
1403
|
-
// Queue the desired transition behind that exact promise: deadlines
|
|
1404
|
-
// detect a wedge but cannot cancel arbitrary work, and polling here
|
|
1405
|
-
// would create another lifecycle timer outside the supervisor.
|
|
1406
827
|
const action = supervisor.awaitIdle(id).then(() => now ? supervisor.start(id) : supervisor.stop(id));
|
|
1407
828
|
void action
|
|
1408
829
|
.then(() => log('INFO', `Service '${id}' ${now ? 'started' : 'stopped'} live (SIGHUP reload)`))
|
|
@@ -1413,9 +834,7 @@ export async function runDaemon() {
|
|
|
1413
834
|
}
|
|
1414
835
|
}
|
|
1415
836
|
}
|
|
1416
|
-
// Remember the reloaded state so subsequent reloads log transitions truthfully.
|
|
1417
837
|
servicesConfig = reloadedConfig;
|
|
1418
|
-
// Drain queued `agents daemon services restart <id>` requests (RUSH-3193 P4).
|
|
1419
838
|
for (const id of drainDaemonServiceRestartQueue()) {
|
|
1420
839
|
if (supervisor.isRegistered(id)) {
|
|
1421
840
|
void supervisor.awaitIdle(id).then(() => supervisor.restartOne(id))
|
|
@@ -1426,9 +845,6 @@ export async function runDaemon() {
|
|
|
1426
845
|
log('WARN', `Restart requested for '${id}' but it is not supervisor-managed on this daemon — restart the daemon instead`);
|
|
1427
846
|
}
|
|
1428
847
|
}
|
|
1429
|
-
// Refresh user-layer copies of opted-in project routines BEFORE the
|
|
1430
|
-
// scheduler reloads, so YAML edits under `<project>/.agents/routines/`
|
|
1431
|
-
// take effect on the next fire without a manual `routines sync`.
|
|
1432
848
|
try {
|
|
1433
849
|
const result = syncAllProjectRoutines();
|
|
1434
850
|
const n = result.projects.reduce((acc, p) => acc + p.synced.length, 0);
|
|
@@ -1439,11 +855,6 @@ export async function runDaemon() {
|
|
|
1439
855
|
catch (err) {
|
|
1440
856
|
log('WARN', `Project routines sync failed: ${err.message}`);
|
|
1441
857
|
}
|
|
1442
|
-
// Re-evaluate the scheduler.enabled gate: flipping the key takes effect on
|
|
1443
|
-
// this reload, no daemon restart needed. A `routines add` on a re-enabled
|
|
1444
|
-
// box signals exactly this reload, which boots the scheduler — the
|
|
1445
|
-
// "Scheduler reloaded" it prints is then truthful, not a dead-end.
|
|
1446
|
-
// Also honour the daemon-services toggle.
|
|
1447
858
|
const schedulerEnabledNow = isSchedulerEnabled() && reloadedEnabled('scheduler');
|
|
1448
859
|
const transition = schedulerGateTransition(scheduler !== null, schedulerEnabledNow);
|
|
1449
860
|
if (transition === 'boot') {
|
|
@@ -1459,12 +870,6 @@ export async function runDaemon() {
|
|
|
1459
870
|
const reloaded = scheduler.listScheduled();
|
|
1460
871
|
log('INFO', `Reloaded ${reloaded.length} jobs`);
|
|
1461
872
|
}
|
|
1462
|
-
// Refresh monitor CONFIGS when the engine is live and monitors stays enabled
|
|
1463
|
-
// (the common `monitors add/edit` + SIGHUP case). The enable/disable
|
|
1464
|
-
// TRANSITION itself is handled by the generic supervisor.start/stop loop
|
|
1465
|
-
// above (PHNX-3608) — a disabled monitors service is supervisor.stop()'d
|
|
1466
|
-
// there, which tears down its supervised tick so nothing dispatches; an
|
|
1467
|
-
// off-transition leaves getEngine() null, so this reload is correctly skipped.
|
|
1468
873
|
const liveMonitorEngine = monitorEngineSvc.getEngine();
|
|
1469
874
|
if (liveMonitorEngine && reloadedEnabled('monitors')) {
|
|
1470
875
|
try {
|
|
@@ -1475,18 +880,9 @@ export async function runDaemon() {
|
|
|
1475
880
|
}
|
|
1476
881
|
}
|
|
1477
882
|
};
|
|
1478
|
-
//
|
|
1479
|
-
// SIGINT, and StateDirCheckService's independent `onMissing` callback (which
|
|
1480
|
-
// calls this same handler), and two of those can arrive together — a
|
|
1481
|
-
// service manager that SIGTERMs a daemon whose state dir was just removed.
|
|
1482
|
-
// It was only INCIDENTALLY safe before (every step inside happens to be
|
|
1483
|
-
// idempotent); the guard makes single-shot a property of the function
|
|
1484
|
-
// rather than one that every step added later has to re-earn.
|
|
883
|
+
// Cleanup removes lifetime, pid, heartbeat, and registry state only when each still belongs to this instance.
|
|
1485
884
|
const handleShutdown = singleShot(async () => {
|
|
1486
885
|
log('INFO', 'Daemon shutting down');
|
|
1487
|
-
// supervisor.stopAll() stops every registered service, including socket
|
|
1488
|
-
// hosts, session publishers/indexers, monitor/account work, and every
|
|
1489
|
-
// maintenance timer; state-dir-check joins the registry just below.
|
|
1490
886
|
await supervisor.stopAll();
|
|
1491
887
|
activeServiceSupervisor = null;
|
|
1492
888
|
stopScheduler();
|
|
@@ -1495,7 +891,6 @@ export async function runDaemon() {
|
|
|
1495
891
|
fs.unlinkSync(lifetimePath);
|
|
1496
892
|
}
|
|
1497
893
|
catch {
|
|
1498
|
-
// Already removed with the state dir, or replaced by a newer owner.
|
|
1499
894
|
}
|
|
1500
895
|
removeDaemonPidIfOwned(process.pid);
|
|
1501
896
|
if (readHeartbeat()?.pid === process.pid)
|
|
@@ -1503,14 +898,6 @@ export async function runDaemon() {
|
|
|
1503
898
|
unregisterDaemonInstance();
|
|
1504
899
|
process.exit(0);
|
|
1505
900
|
});
|
|
1506
|
-
// State-dir self-check (RUSH-2367 self-terminate guard) is registered on
|
|
1507
|
-
// the supervisor here — AFTER `handleShutdown` above — rather than
|
|
1508
|
-
// alongside watchdog/device-probe/self-heal earlier. The
|
|
1509
|
-
// supervisor fires an immediate first tick on `register()`+`start()`; doing
|
|
1510
|
-
// that before `handleShutdown` exists would reference the const in its
|
|
1511
|
-
// temporal dead zone the moment a mismatch is ever detected. Registering it
|
|
1512
|
-
// here, once `handleShutdown` is a real function, removes that risk
|
|
1513
|
-
// entirely rather than relying on the marker always matching on tick one.
|
|
1514
901
|
if (isEnabled('state-dir-check')) {
|
|
1515
902
|
supervisor.register(new StateDirCheckService({
|
|
1516
903
|
lifetimePath,
|
|
@@ -1523,13 +910,10 @@ export async function runDaemon() {
|
|
|
1523
910
|
log('INFO', 'State-dir self-check disabled');
|
|
1524
911
|
}
|
|
1525
912
|
liveReloadHandler = guardSignalHandler(handleReload, (err) => {
|
|
1526
|
-
// Signal callbacks sit outside the supervisor's service barriers. A
|
|
1527
|
-
// reload failure must be observable without reaching the process-wide
|
|
1528
|
-
// uncaughtException handler and taking down every daemon service.
|
|
1529
913
|
try {
|
|
1530
914
|
log('ERROR', `SIGHUP reload failed: ${err.message}`);
|
|
1531
915
|
}
|
|
1532
|
-
catch {
|
|
916
|
+
catch { }
|
|
1533
917
|
});
|
|
1534
918
|
if (reloadRequestedDuringStartup) {
|
|
1535
919
|
reloadRequestedDuringStartup = false;
|
|
@@ -1540,22 +924,13 @@ export async function runDaemon() {
|
|
|
1540
924
|
process.on('SIGINT', () => handleShutdown());
|
|
1541
925
|
await new Promise(() => { });
|
|
1542
926
|
}
|
|
1543
|
-
/** Escape a string for safe inclusion in an XML <string> node. */
|
|
1544
927
|
function xmlEscape(s) {
|
|
1545
928
|
return s
|
|
1546
929
|
.replace(/&/g, '&')
|
|
1547
930
|
.replace(/</g, '<')
|
|
1548
931
|
.replace(/>/g, '>');
|
|
1549
932
|
}
|
|
1550
|
-
|
|
1551
|
-
* Write a launchd plist or systemd unit with owner-only permissions atomically.
|
|
1552
|
-
*
|
|
1553
|
-
* `writeFileSync`'s `mode` is honored only when the file is *created*, so we
|
|
1554
|
-
* unlink any pre-existing manifest first. That guarantees every write is a
|
|
1555
|
-
* fresh 0600 create — closing the TOCTOU window on new files AND re-locking a
|
|
1556
|
-
* stale world-readable manifest left by an older install — since these files
|
|
1557
|
-
* embed long-lived credentials.
|
|
1558
|
-
*/
|
|
933
|
+
// Manifests are owner-only and carry only the canonical HOME/AGENTS_REAL_HOME/PATH surface.
|
|
1559
934
|
export function writeOwnerOnlyServiceManifest(filePath, content) {
|
|
1560
935
|
const dir = path.dirname(filePath);
|
|
1561
936
|
if (!fs.existsSync(dir)) {
|
|
@@ -1564,29 +939,6 @@ export function writeOwnerOnlyServiceManifest(filePath, content) {
|
|
|
1564
939
|
fs.rmSync(filePath, { force: true });
|
|
1565
940
|
fs.writeFileSync(filePath, content, { encoding: 'utf-8', mode: 0o600 });
|
|
1566
941
|
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Generate a macOS launchd plist for auto-starting the daemon.
|
|
1569
|
-
*
|
|
1570
|
-
* The plist never embeds a Claude OAuth token: the daemon holds no Claude
|
|
1571
|
-
* credential at all. Routine runs authenticate through the per-account
|
|
1572
|
-
* CLAUDE_CONFIG_DIR login on this device, exactly like an interactive
|
|
1573
|
-
* `agents run`, so no credential ever touches the service manifest.
|
|
1574
|
-
*
|
|
1575
|
-
* RUSH-2639: launchd does NOT inherit `launchctl load`'s caller's process
|
|
1576
|
-
* environment — a spawned daemon only ever sees the login session's default
|
|
1577
|
-
* env plus whatever this dict adds/overrides. Before this fix the dict carried
|
|
1578
|
-
* only PATH, so HOME resolved to the launchd session's own value regardless of
|
|
1579
|
-
* what HOME the process that generated (and loaded) the plist was running
|
|
1580
|
-
* under. In production that's a no-op (the login session's HOME already is the
|
|
1581
|
-
* real HOME), but under a hermetic test harness that redirects HOME to a
|
|
1582
|
-
* fork-private sandbox, a launchd-started daemon silently escaped the sandbox
|
|
1583
|
-
* and bootstrapped `~/.agents` (.cache/.history/.system/routines) in the
|
|
1584
|
-
* developer's/runner's REAL home. Baking HOME (and the AGENTS_REAL_HOME seam
|
|
1585
|
-
* every version-home consumer honors, see tests/setup.ts) into the plist at
|
|
1586
|
-
* generation time makes the launchd child inherit the SAME home the caller
|
|
1587
|
-
* resolved, exactly like the plain detached-spawn path already does via
|
|
1588
|
-
* `env: {...process.env}`.
|
|
1589
|
-
*/
|
|
1590
942
|
export function generateLaunchdPlist(agentsBin = getAgentsBinPath()) {
|
|
1591
943
|
const launch = getDaemonLaunch(agentsBin);
|
|
1592
944
|
const logPath = getDaemonLogPath();
|
|
@@ -1623,25 +975,9 @@ ${[launch.command, ...launch.args].map((arg) => ` <string>${xmlEscape(arg)}</
|
|
|
1623
975
|
</dict>
|
|
1624
976
|
</plist>`;
|
|
1625
977
|
}
|
|
1626
|
-
/** Quote one systemd ExecStart argument without delegating parsing to a shell. */
|
|
1627
978
|
function systemdExecArg(value) {
|
|
1628
979
|
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
1629
980
|
}
|
|
1630
|
-
/**
|
|
1631
|
-
* Generate a Linux systemd user unit for auto-starting the daemon.
|
|
1632
|
-
*
|
|
1633
|
-
* The unit never embeds a Claude OAuth token: the daemon holds no Claude
|
|
1634
|
-
* credential at all. Routine runs authenticate through the per-account
|
|
1635
|
-
* CLAUDE_CONFIG_DIR login on this device, exactly like an interactive
|
|
1636
|
-
* `agents run`, so no credential ever touches the unit file.
|
|
1637
|
-
*
|
|
1638
|
-
* RUSH-2639: same seam as `generateLaunchdPlist` — a systemd --user unit is
|
|
1639
|
-
* started by the user's systemd instance, not the process that generated the
|
|
1640
|
-
* unit, so HOME is whatever that session provides unless this file pins it.
|
|
1641
|
-
* Baking HOME (and AGENTS_REAL_HOME) in at generation time keeps a
|
|
1642
|
-
* hermetic-test-started unit inside its sandbox instead of resolving against
|
|
1643
|
-
* the real account home.
|
|
1644
|
-
*/
|
|
1645
981
|
export function generateSystemdUnit(agentsBin = getAgentsBinPath()) {
|
|
1646
982
|
const launch = getDaemonLaunch(agentsBin);
|
|
1647
983
|
const execStart = [launch.command, ...launch.args].map(systemdExecArg).join(' ');
|
|
@@ -1664,26 +1000,9 @@ Environment=AGENTS_REAL_HOME=${realHome}
|
|
|
1664
1000
|
[Install]
|
|
1665
1001
|
WantedBy=default.target`;
|
|
1666
1002
|
}
|
|
1667
|
-
// Binary-resolution helpers (getAgentsBinPath / isNodeScriptEntry / getCliLaunch)
|
|
1668
|
-
// live in ./cli-entry.js — a leaf module. Re-exported so existing
|
|
1669
|
-
// `from './daemon.js'` importers of getAgentsBinPath keep resolving.
|
|
1670
1003
|
export { getAgentsBinPath };
|
|
1671
|
-
/**
|
|
1672
|
-
* Ask the service manager for the daemon's live PID. Used as a fallback when
|
|
1673
|
-
* the daemon hasn't yet written its pid file but launchd/systemd already report
|
|
1674
|
-
* it running — so a start never has to surface a null PID for a daemon that is
|
|
1675
|
-
* in fact up. Returns null when the service isn't running or the query fails.
|
|
1676
|
-
*
|
|
1677
|
-
* `names` defaults to THIS process's (possibly sandbox-namespaced) job; a
|
|
1678
|
-
* caller under a redirected HOME passes `productionDaemonServiceNames()` to
|
|
1679
|
-
* ask after the real install's unit instead. Read-only either way.
|
|
1680
|
-
*/
|
|
1681
1004
|
export function readServiceManagerPid(platform = os.platform(), names) {
|
|
1682
1005
|
const resolved = names ?? { systemdUnit: daemonSystemdUnitName(), launchdLabel: daemonServiceLabel() };
|
|
1683
|
-
// The registration gate exists because a sandboxed process must not REGISTER
|
|
1684
|
-
// or tear down jobs in the real per-user service manager. Asking after an
|
|
1685
|
-
// explicitly named job is read-only — in particular the production unit from
|
|
1686
|
-
// a redirected-HOME caller (W4) — and mutates nothing, so it is let through.
|
|
1687
1006
|
if (names === undefined && !serviceManagerRegistrationAllowed().allowed)
|
|
1688
1007
|
return null;
|
|
1689
1008
|
try {
|
|
@@ -1701,32 +1020,13 @@ export function readServiceManagerPid(platform = os.platform(), names) {
|
|
|
1701
1020
|
}
|
|
1702
1021
|
}
|
|
1703
1022
|
}
|
|
1704
|
-
catch {
|
|
1023
|
+
catch { }
|
|
1705
1024
|
return null;
|
|
1706
1025
|
}
|
|
1707
|
-
/**
|
|
1708
|
-
* Thrown when a daemon LAUNCH is attempted under a redirected HOME without the
|
|
1709
|
-
* explicit test opt-in (W4, PHNX-3736). `bootstrap.ts` prints the message
|
|
1710
|
-
* without a stack — this is user-actionable, not an engineering bug.
|
|
1711
|
-
*/
|
|
1712
1026
|
export class RedirectedHomeDaemonError extends Error {
|
|
1713
1027
|
name = 'RedirectedHomeDaemonError';
|
|
1714
1028
|
}
|
|
1715
|
-
|
|
1716
|
-
* W4 (PHNX-3736): never LAUNCH a daemon under a redirected (sandbox/test) HOME
|
|
1717
|
-
* without an explicit opt-in. A daemon started there keeps its own pid file
|
|
1718
|
-
* under the temp home, so the real install's pid-file takeover can never see
|
|
1719
|
-
* it — the leaked `HOME=/tmp/pin-e2e-<pid>` daemon that ran 4+ days on
|
|
1720
|
-
* yosemite-s1 was launched exactly this way by a headless e2e session that
|
|
1721
|
-
* never cleaned up. RUSH-3021 closed this for `ensureDaemonStarted`'s
|
|
1722
|
-
* AUTO-start path but left the explicit `startDaemon()` open, which is the
|
|
1723
|
-
* path the e2e harness took.
|
|
1724
|
-
*
|
|
1725
|
-
* Placed after the `already-running` early-return: reporting a live daemon (so
|
|
1726
|
-
* `daemon stop` can still kill a leaked one) must stay possible under any
|
|
1727
|
-
* HOME. `AGENTS_ALLOW_TEST_DAEMON=1` is the deliberate test/e2e seam — a
|
|
1728
|
-
* harness that sets it owns stopping what it starts.
|
|
1729
|
-
*/
|
|
1029
|
+
// A redirected HOME daemon is invisible to production takeover and can outlive its sandbox, so explicit test opt-in is required.
|
|
1730
1030
|
function assertDaemonLaunchHomeAllowed() {
|
|
1731
1031
|
const suffix = isolatedHomeSuffix();
|
|
1732
1032
|
if (!suffix)
|
|
@@ -1738,10 +1038,7 @@ function assertDaemonLaunchHomeAllowed() {
|
|
|
1738
1038
|
`install's pid-file takeover, and outlives whatever launched it (PHNX-3736). ` +
|
|
1739
1039
|
`For a deliberate test/e2e launch set AGENTS_ALLOW_TEST_DAEMON=1 — and stop the daemon when done.`);
|
|
1740
1040
|
}
|
|
1741
|
-
/** Start the daemon via launchd, systemd, or as a detached process. */
|
|
1742
1041
|
export function startDaemon(agentsBin) {
|
|
1743
|
-
// The public launcher must obey the same namespace boundary as its child:
|
|
1744
|
-
// even probing/repairing legacy PID state or pruning a lock can mutate it.
|
|
1745
1042
|
if (!daemonProcessViewAllowed())
|
|
1746
1043
|
throw new Error('Daemon startup requires the owning process namespace. Automatic reuse of a private-container HOME across namespaces is unsupported; run in its owning namespace or use a fresh HOME.');
|
|
1747
1044
|
if (isDaemonRunning()) {
|
|
@@ -1751,14 +1048,9 @@ export function startDaemon(agentsBin) {
|
|
|
1751
1048
|
assertDaemonLaunchHomeAllowed();
|
|
1752
1049
|
const releaseLock = acquireStartLock();
|
|
1753
1050
|
if (!releaseLock) {
|
|
1754
|
-
// Another process is already starting the daemon
|
|
1755
1051
|
const pid = waitForPid(3000);
|
|
1756
1052
|
return { pid, method: 'already-starting' };
|
|
1757
1053
|
}
|
|
1758
|
-
// Released by startDaemonLocked the moment the launch has been ISSUED, and
|
|
1759
|
-
// again here as the backstop for every path that returned before reaching
|
|
1760
|
-
// that point (a throw, or the platform default branch). Idempotent so the
|
|
1761
|
-
// double call is a no-op rather than unlinking a lock a later claimer owns.
|
|
1762
1054
|
let released = false;
|
|
1763
1055
|
const releaseOnce = () => {
|
|
1764
1056
|
if (released)
|
|
@@ -1766,26 +1058,11 @@ export function startDaemon(agentsBin) {
|
|
|
1766
1058
|
released = true;
|
|
1767
1059
|
releaseLock();
|
|
1768
1060
|
};
|
|
1769
|
-
// RUSH-2418: count starts PESSIMISTICALLY, and let a daemon that reaches
|
|
1770
|
-
// steady state clear the streak itself (`recordSubsystemOk` at the end of
|
|
1771
|
-
// runDaemon's startup). Recording a failure only on an observable error would
|
|
1772
|
-
// miss the crash loop entirely: a daemon that spawns and then dies returns a
|
|
1773
|
-
// perfectly real `child.pid`, so the launcher has no error to see. Every path
|
|
1774
|
-
// out of startDaemonLocked is either pid-truthy or a throw, so an
|
|
1775
|
-
// outcome-shaped check here can only ever catch an unspawnable binary — not
|
|
1776
|
-
// the failure this breaker exists for. Marking the attempt up front and
|
|
1777
|
-
// clearing on proven health inverts that: the streak grows exactly when
|
|
1778
|
-
// starts stop producing a daemon that lives.
|
|
1779
|
-
//
|
|
1780
|
-
// The no-launch returns above (`already-running`, `already-starting`) return
|
|
1781
|
-
// before this point on purpose — they attempted nothing, so they count nothing.
|
|
1782
1061
|
recordSubsystemError(SUBSYSTEM_DAEMON_START, 'start issued; no daemon has reported healthy since');
|
|
1783
1062
|
try {
|
|
1784
1063
|
return startDaemonLocked(agentsBin ?? getAgentsBinPath(), releaseOnce);
|
|
1785
1064
|
}
|
|
1786
1065
|
catch (err) {
|
|
1787
|
-
// Replace the provisional reason with the real one — the streak is already
|
|
1788
|
-
// counted, this just makes `agents daemon doctor` name the actual cause.
|
|
1789
1066
|
recordSubsystemErrorReason(SUBSYSTEM_DAEMON_START, `start failed: ${err?.message ?? String(err)}`);
|
|
1790
1067
|
throw err;
|
|
1791
1068
|
}
|
|
@@ -1793,60 +1070,17 @@ export function startDaemon(agentsBin) {
|
|
|
1793
1070
|
releaseOnce();
|
|
1794
1071
|
}
|
|
1795
1072
|
}
|
|
1796
|
-
/**
|
|
1797
|
-
* Is the auto-start circuit breaker open (RUSH-2418)? True once
|
|
1798
|
-
* {@link DAEMON_AUTOSTART_FAILURE_LIMIT} consecutive starts have failed to
|
|
1799
|
-
* produce a daemon that reported healthy. Pure read of the persisted health
|
|
1800
|
-
* record, and `agents daemon doctor` reports the same record — the message the
|
|
1801
|
-
* breaker prints has to lead somewhere that can explain it.
|
|
1802
|
-
*/
|
|
1803
1073
|
export function isDaemonAutostartCircuitOpen() {
|
|
1804
1074
|
const health = readSubsystemHealth(SUBSYSTEM_DAEMON_START);
|
|
1805
1075
|
return (health?.consecutiveFailures ?? 0) >= DAEMON_AUTOSTART_FAILURE_LIMIT;
|
|
1806
1076
|
}
|
|
1807
|
-
/**
|
|
1808
|
-
* Bring the always-on daemon up as a side effect of a background-adjacent
|
|
1809
|
-
* command (secrets unlock, browser start, ...), not only from `routines add`.
|
|
1810
|
-
*
|
|
1811
|
-
* Delegates to the single `startDaemon` entrypoint, so it honors the
|
|
1812
|
-
* single-instance start lock and is a no-op when a daemon is already running
|
|
1813
|
-
* (returns `already-running`). Best-effort: any failure is swallowed and null
|
|
1814
|
-
* returned, so ensuring the daemon can never break the foreground command that
|
|
1815
|
-
* happened to bring it up. See issue #415.
|
|
1816
|
-
*/
|
|
1817
1077
|
export function ensureDaemonStarted() {
|
|
1818
|
-
// RUSH-2354: honor daemon.enabled — a background-adjacent caller (secrets
|
|
1819
|
-
// unlock, browser start, ...) must not resurrect a daemon the owner
|
|
1820
|
-
// explicitly turned off. `agents daemon start` is the deliberate override
|
|
1821
|
-
// and calls startDaemon() directly instead of going through this helper.
|
|
1822
1078
|
if (!isDaemonEnabled())
|
|
1823
1079
|
return null;
|
|
1824
|
-
// A live daemon is the answer whatever the failure history says — the breaker
|
|
1825
|
-
// gates LAUNCHING one, never reporting one that is already up. Checked first
|
|
1826
|
-
// so a stale failure streak can't make a healthy daemon read as absent to
|
|
1827
|
-
// callers that branch on this return (e.g. secrets/agent.ts).
|
|
1828
1080
|
if (isDaemonRunning())
|
|
1829
1081
|
return startDaemon();
|
|
1830
|
-
// RUSH-3021: never LAUNCH a daemon from a redirected (sandbox/test) HOME.
|
|
1831
|
-
// #2860 gated service-manager registration on this signal but left the
|
|
1832
|
-
// detached spawn itself ungated, so a test-spawned CLI could fork a daemon
|
|
1833
|
-
// into the test's temp HOME; the child outlives the test and races its
|
|
1834
|
-
// recursive teardown rm (ENOTEMPTY). Placed after the already-running branch
|
|
1835
|
-
// — reporting a live daemon stays allowed, same as the circuit breaker.
|
|
1836
|
-
// AGENTS_SERVICE_MANAGER_ALLOW_REDIRECTED_HOME=1 is the test seam for suites
|
|
1837
|
-
// that exercise daemon startup deliberately. The explicit `agents daemon
|
|
1838
|
-
// start` path is gated separately by startDaemon's own redirected-HOME
|
|
1839
|
-
// refusal (AGENTS_ALLOW_TEST_DAEMON=1, W4/PHNX-3736).
|
|
1840
1082
|
if (!serviceManagerRegistrationAllowed().allowed)
|
|
1841
1083
|
return null;
|
|
1842
|
-
// RUSH-2418: the auto-start circuit breaker. A daemon that dies during
|
|
1843
|
-
// startup would otherwise be relaunched by EVERY foreground command that
|
|
1844
|
-
// wants one (secrets unlock, browser start, watchdog, ...) — an
|
|
1845
|
-
// application-level crash loop the OS supervisor's throttle cannot see,
|
|
1846
|
-
// because each attempt is a fresh service start rather than a respawn. After
|
|
1847
|
-
// DAEMON_AUTOSTART_FAILURE_LIMIT consecutive failures, refuse and say why.
|
|
1848
|
-
// Deliberately NOT applied in startDaemon(): `agents daemon start` is the
|
|
1849
|
-
// operator's override and must always be able to retry.
|
|
1850
1084
|
if (isDaemonAutostartCircuitOpen()) {
|
|
1851
1085
|
process.stderr.write(`[agents] daemon auto-start disabled after ${DAEMON_AUTOSTART_FAILURE_LIMIT} consecutive failed starts. ` +
|
|
1852
1086
|
`Run 'agents daemon doctor' to diagnose, or 'agents daemon start' to retry anyway.\n`);
|
|
@@ -1859,29 +1093,9 @@ export function ensureDaemonStarted() {
|
|
|
1859
1093
|
return null;
|
|
1860
1094
|
}
|
|
1861
1095
|
}
|
|
1862
|
-
|
|
1863
|
-
* Issue the launch, then wait for the child to record its pid.
|
|
1864
|
-
*
|
|
1865
|
-
* RUSH-2417: the wait phase MUST NOT hold the start lock. `acquireStartLock`
|
|
1866
|
-
* and `claimDaemonInstance` resolve the same `<daemonDir>/daemon.lock`, so a
|
|
1867
|
-
* parent that busy-waits on `waitForPid` while still holding it deterministically
|
|
1868
|
-
* defeats the child it just launched: the child's `claimDaemonInstance` hits
|
|
1869
|
-
* EEXIST, reads a holder pid that IS alive (this process), and exits with the
|
|
1870
|
-
* false "another daemon is mid-takeover" warning — every launchd/systemd start
|
|
1871
|
-
* on a fresh install. The lock's job is to keep two concurrent `startDaemon()`
|
|
1872
|
-
* calls from both launching, and that is done once `launchctl load` /
|
|
1873
|
-
* `systemctl start` / the detached spawn has been issued, so `releaseLock()` is
|
|
1874
|
-
* called there rather than in the caller's `finally`.
|
|
1875
|
-
*
|
|
1876
|
-
* Releasing early cannot produce two daemons: launchd (one plist label) and
|
|
1877
|
-
* systemd (one unit) are singletons that no-op a second start, and the detached
|
|
1878
|
-
* path is covered by `claimDaemonInstance`'s last-wins takeover (SING-11) —
|
|
1879
|
-
* a second claimer evicts the incumbent rather than running beside it.
|
|
1880
|
-
*/
|
|
1096
|
+
// Release the start lock before the child claims it; manager success is not health until pid/heartbeat proof appears.
|
|
1881
1097
|
function startDaemonLocked(agentsBin, releaseLock) {
|
|
1882
1098
|
const platform = os.platform();
|
|
1883
|
-
// Same contract on the fallback path: the spawn IS the launch, so the lock is
|
|
1884
|
-
// dropped before the child exists rather than in a `finally` the child races.
|
|
1885
1099
|
const detachedFallback = () => {
|
|
1886
1100
|
releaseLock();
|
|
1887
1101
|
return startDetached({ agentsBin });
|
|
@@ -1895,28 +1109,18 @@ function startDaemonLocked(agentsBin, releaseLock) {
|
|
|
1895
1109
|
if (!fs.existsSync(plistDir)) {
|
|
1896
1110
|
fs.mkdirSync(plistDir, { recursive: true });
|
|
1897
1111
|
}
|
|
1898
|
-
// The plist carries no credential (RUSH-1759 — the daemon reads the OAuth
|
|
1899
|
-
// token itself at startup); still create owner-only atomically to match the
|
|
1900
|
-
// detached path and keep the log/PATH surface owner-private.
|
|
1901
1112
|
writeOwnerOnlyServiceManifest(plistPath, generateLaunchdPlist(agentsBin));
|
|
1902
1113
|
try {
|
|
1903
1114
|
execFileSync('launchctl', ['unload', plistPath], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
1904
1115
|
}
|
|
1905
|
-
catch {
|
|
1906
|
-
// launchctl prints `Load failed:` and exits 0 when the label is in a
|
|
1907
|
-
// stuck state from a prior session — so a zero exit code isn't proof
|
|
1908
|
-
// of success. If no pid materializes within the window, give up on
|
|
1909
|
-
// launchd and fall through to a plain detached spawn.
|
|
1116
|
+
catch { }
|
|
1910
1117
|
execFileSync('launchctl', ['load', plistPath], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1911
|
-
// Launch issued — the child needs this lock to claim (RUSH-2417).
|
|
1912
1118
|
releaseLock();
|
|
1913
1119
|
const pid = waitForPid(3000) ?? readServiceManagerPid();
|
|
1914
1120
|
if (pid)
|
|
1915
1121
|
return { pid, method: 'launchd' };
|
|
1916
|
-
// launchctl claimed success but nothing ran. Fall through.
|
|
1917
1122
|
}
|
|
1918
1123
|
catch {
|
|
1919
|
-
// load threw — fall through to detached spawn
|
|
1920
1124
|
}
|
|
1921
1125
|
}
|
|
1922
1126
|
else {
|
|
@@ -1932,22 +1136,16 @@ function startDaemonLocked(agentsBin, releaseLock) {
|
|
|
1932
1136
|
if (!fs.existsSync(unitDir)) {
|
|
1933
1137
|
fs.mkdirSync(unitDir, { recursive: true });
|
|
1934
1138
|
}
|
|
1935
|
-
// Carries no credential (RUSH-1759 — the daemon reads the OAuth token
|
|
1936
|
-
// itself at startup); owner-only to keep the PATH/log surface private.
|
|
1937
1139
|
writeOwnerOnlyServiceManifest(unitPath, generateSystemdUnit(agentsBin));
|
|
1938
1140
|
execFileSync('systemctl', ['--user', 'daemon-reload'], { encoding: 'utf-8' });
|
|
1939
1141
|
execFileSync('systemctl', ['--user', 'enable', daemonSystemdUnitName()], { encoding: 'utf-8' });
|
|
1940
1142
|
execFileSync('systemctl', ['--user', 'start', daemonSystemdUnitName()], { encoding: 'utf-8' });
|
|
1941
|
-
// Launch issued — the child needs this lock to claim (RUSH-2417).
|
|
1942
1143
|
releaseLock();
|
|
1943
1144
|
const pid = waitForPid(3000) ?? readServiceManagerPid();
|
|
1944
1145
|
if (pid)
|
|
1945
1146
|
return { pid, method: 'systemd' };
|
|
1946
|
-
// systemctl returned success but no PID surfaced — fall through to a
|
|
1947
|
-
// plain detached spawn rather than reporting a null PID.
|
|
1948
1147
|
}
|
|
1949
1148
|
catch {
|
|
1950
|
-
// start threw — fall through to detached spawn
|
|
1951
1149
|
}
|
|
1952
1150
|
}
|
|
1953
1151
|
else {
|
|
@@ -1957,74 +1155,19 @@ function startDaemonLocked(agentsBin, releaseLock) {
|
|
|
1957
1155
|
}
|
|
1958
1156
|
return startDetached({ agentsBin });
|
|
1959
1157
|
}
|
|
1960
|
-
/**
|
|
1961
|
-
* Resolve how to launch the daemon: `node <entry> __daemon-run`, matching the
|
|
1962
|
-
* exact form that works under a direct `__daemon-run`.
|
|
1963
|
-
*
|
|
1964
|
-
* We spawn the Node runtime (`process.execPath`) with the CLI entry as an
|
|
1965
|
-
* argument rather than executing the entry path directly. Executing the `.js`
|
|
1966
|
-
* path relies on its shebang on POSIX, and on Windows CreateProcess can't run a
|
|
1967
|
-
* `.js`/shim directly at all — it gets launched through a transient
|
|
1968
|
-
* console-owning wrapper (cmd.exe / the npm shim). When that wrapper exits it
|
|
1969
|
-
* closes its console, and the detached daemon sharing that console receives a
|
|
1970
|
-
* console-close event that trips its shutdown handler — the daemon comes up,
|
|
1971
|
-
* binds its sockets, then tears itself down ~36ms later (#556).
|
|
1972
|
-
* Going through `process.execPath` means a real PE/binary is spawned with
|
|
1973
|
-
* `detached: true` and no console, so nothing signals the daemon after launch.
|
|
1974
|
-
*
|
|
1975
|
-
* When the entry isn't a Node script (e.g. a native compiled launcher), run it
|
|
1976
|
-
* directly — it owns its own runtime resolution.
|
|
1977
|
-
*/
|
|
1978
1158
|
export function getDaemonLaunch(agentsBin = getAgentsBinPath()) {
|
|
1979
1159
|
const { warnings } = validateDaemonBinary(agentsBin);
|
|
1980
1160
|
for (const w of warnings)
|
|
1981
1161
|
process.stderr.write(`[agents] ${w}\n`);
|
|
1982
1162
|
return getCliLaunch(['__daemon-run'], agentsBin);
|
|
1983
1163
|
}
|
|
1984
|
-
/**
|
|
1985
|
-
* The directory of the Node runtime that generated this service manifest, kept
|
|
1986
|
-
* first on the daemon's PATH. Both the shim's shebang and any child routine
|
|
1987
|
-
* process then resolve the exact Node that installed the service — never an
|
|
1988
|
-
* ancient system node or a pruned nvm version. Replaces the old hardcoded
|
|
1989
|
-
* `~/.nvm/versions/node/v24.0.0/bin`, which went stale the moment that patch
|
|
1990
|
-
* release was upgraded away and bricked the daemon fleet-wide.
|
|
1991
|
-
*/
|
|
1992
1164
|
function daemonNodeBinDir() {
|
|
1993
1165
|
return path.dirname(process.execPath);
|
|
1994
1166
|
}
|
|
1995
|
-
/**
|
|
1996
|
-
* Login-shell user-bin dirs a service-manager-started daemon would otherwise
|
|
1997
|
-
* miss. systemd/launchd pin PATH and never source `~/.profile`, so they never
|
|
1998
|
-
* see `~/.rush/bin` (where `rush` lands) or `~/.local/bin` (XDG user-bin).
|
|
1999
|
-
* Monitor `notify` and dispatched `agents run` children inherit this PATH;
|
|
2000
|
-
* without these dirs the rush-backed owner channel fails with
|
|
2001
|
-
* `rush CLI not found on PATH` (PHNX-3075) while an interactive shell on the
|
|
2002
|
-
* same box succeeds. Uses the same HOME the manifest bakes (RUSH-2639).
|
|
2003
|
-
*/
|
|
2004
1167
|
function daemonUserBinDirs() {
|
|
2005
1168
|
const home = serviceManifestHomeEnv().HOME;
|
|
2006
1169
|
return [path.join(home, '.rush', 'bin'), localBinDir(home)];
|
|
2007
1170
|
}
|
|
2008
|
-
/**
|
|
2009
|
-
* The full PATH value the daemon service manifest pins, in order: the directory
|
|
2010
|
-
* of the `agents` shim itself FIRST, then the Node runtime dir, then login-shell
|
|
2011
|
-
* user-bin dirs (`~/.rush/bin`, `~/.local/bin`), then the platform's system dirs.
|
|
2012
|
-
*
|
|
2013
|
-
* The shim's own dir must lead so a scheduled `command` routine that shells out
|
|
2014
|
-
* to the bare name `agents` (`/bin/sh -c 'agents repo pull system'`) resolves the
|
|
2015
|
-
* SAME binary the daemon is running. When the Node runtime dir came first, a
|
|
2016
|
-
* stale `agents` install inside that dir (common with nvm or an npm global in the
|
|
2017
|
-
* same Node prefix) shadowed the current binary and routines failed with an
|
|
2018
|
-
* `unknown command` error against the wrong build.
|
|
2019
|
-
*
|
|
2020
|
-
* The Node runtime dir stays second so the shim's shebang (`#!/usr/bin/env node`)
|
|
2021
|
-
* still resolves the exact Node that installed the service — never an ancient
|
|
2022
|
-
* system node or a pruned nvm version. User-bin dirs sit after that pair so a
|
|
2023
|
-
* `~/.local/bin/agents` cannot shadow the daemon binary, but `rush` (and other
|
|
2024
|
-
* login-shell CLIs) still resolve. Deduped across the whole list, so a
|
|
2025
|
-
* Node/shim dir that already appears among the system dirs (e.g. a
|
|
2026
|
-
* `/usr/local/bin` install) never doubles.
|
|
2027
|
-
*/
|
|
2028
1171
|
function daemonPathValue(agentsBin, systemDirs) {
|
|
2029
1172
|
return [...new Set([
|
|
2030
1173
|
path.dirname(agentsBin),
|
|
@@ -2033,34 +1176,9 @@ function daemonPathValue(agentsBin, systemDirs) {
|
|
|
2033
1176
|
...systemDirs,
|
|
2034
1177
|
])].join(':');
|
|
2035
1178
|
}
|
|
2036
|
-
/**
|
|
2037
|
-
* Build the argv to relaunch the `agents` CLI with the given subcommand args.
|
|
2038
|
-
*
|
|
2039
|
-
* Resolves the real on-disk binary via getAgentsBinPath(), then dispatches: a
|
|
2040
|
-
* `.js` entry runs under node (`node <entry> …`), a native/compiled binary runs
|
|
2041
|
-
* directly (`<bin> …`).
|
|
2042
|
-
*
|
|
2043
|
-
* Callers MUST route self-spawns through this rather than hand-rolling
|
|
2044
|
-
* `[process.execPath, process.argv[1], …]`: under the compiled standalone binary
|
|
2045
|
-
* (#315) `process.argv[1]` is the bun virtual entry `/$bunfs/root/agents`, so the
|
|
2046
|
-
* hand-rolled form becomes `agents /$bunfs/root/agents …` → the CLI receives the
|
|
2047
|
-
* bunfs path as a subcommand and dies with "unknown command '/$bunfs/root/agents'".
|
|
2048
|
-
* getAgentsBinPath() resolves that virtual entry to the physical process.execPath.
|
|
2049
|
-
*/
|
|
2050
1179
|
export function getAgentsInvocation(subArgs, agentsBin = getAgentsBinPath()) {
|
|
2051
1180
|
return getCliLaunch(subArgs, agentsBin);
|
|
2052
1181
|
}
|
|
2053
|
-
/**
|
|
2054
|
-
* A daemon binary living under an ephemeral path — a git worktree, or a temp
|
|
2055
|
-
* directory (`/tmp`, `/var/folders`, `/dev/shm`) — is a latent wedge. The daemon
|
|
2056
|
-
* is long-lived but resolves its own job modules by dynamic `import()` rooted at
|
|
2057
|
-
* this entry (getAgentsBinPath → process.argv[1]). If that directory is later
|
|
2058
|
-
* removed (`git worktree remove`, a `/tmp` cleanup, a review/verify checkout
|
|
2059
|
-
* teardown) the running daemon keeps ENOENT-ing on every job it loads —
|
|
2060
|
-
* `anchorDaemonCwd` rescues the cwd, but nothing can re-root a deleted module
|
|
2061
|
-
* tree. Returns a human phrase naming the ephemeral kind, or null for a stable
|
|
2062
|
-
* install path (version home, a global npm prefix, a normal source checkout).
|
|
2063
|
-
*/
|
|
2064
1182
|
export function describeEphemeralDaemonRoot(binPath) {
|
|
2065
1183
|
if (/[/\\]\.agents[/\\]worktrees[/\\]/.test(binPath))
|
|
2066
1184
|
return 'a git worktree';
|
|
@@ -2090,25 +1208,14 @@ export function startDetached(opts = {}) {
|
|
|
2090
1208
|
const logPath = opts.logPath ?? getDaemonLogPath();
|
|
2091
1209
|
const logFd = fs.openSync(logPath, 'a');
|
|
2092
1210
|
const { command, args } = getDaemonLaunch(agentsBin);
|
|
2093
|
-
// fdStdio: the log-file fds make windowsHide inert (libuv skips
|
|
2094
|
-
// CREATE_NO_WINDOW when a stdio fd is inherited), so on Windows the daemon
|
|
2095
|
-
// must DETACH to own no console — otherwise it shares the launcher's console
|
|
2096
|
-
// and a console-close event tears it down when the launcher exits (#556).
|
|
2097
1211
|
const child = spawn(command, args, {
|
|
2098
1212
|
stdio: ['ignore', logFd, logFd],
|
|
2099
1213
|
...backgroundSpawnOptions({ cwd: os.homedir(), fdStdio: true }),
|
|
2100
1214
|
env: opts.env ?? process.env,
|
|
2101
1215
|
});
|
|
2102
|
-
// A failed spawn (ENOENT/EACCES) emits 'error' asynchronously; without a
|
|
2103
|
-
// listener that would crash the parent as an unhandled EventEmitter error.
|
|
2104
|
-
// The synchronous `!child.pid` guard below is what reports the failure loudly.
|
|
2105
1216
|
child.on('error', () => { });
|
|
2106
1217
|
child.unref();
|
|
2107
1218
|
fs.closeSync(logFd);
|
|
2108
|
-
// `spawn` leaves `pid` undefined only when the process could not be created.
|
|
2109
|
-
// Returning null here (the old `child.pid || null`) let callers report
|
|
2110
|
-
// "PID: null" as if the daemon had started — a start with no PID is a failed
|
|
2111
|
-
// start, so fail loudly instead of manufacturing a phantom success.
|
|
2112
1219
|
if (!child.pid) {
|
|
2113
1220
|
throw new Error(`Failed to start daemon: spawning '${command}' produced no PID (binary missing or not executable?)`);
|
|
2114
1221
|
}
|
|
@@ -2125,11 +1232,6 @@ function waitForPid(timeoutMs) {
|
|
|
2125
1232
|
}
|
|
2126
1233
|
return readDaemonPid();
|
|
2127
1234
|
}
|
|
2128
|
-
/**
|
|
2129
|
-
* Read the pid a state file claims, or null when it is absent/unreadable/not
|
|
2130
|
-
* pid-shaped. The lifetime marker stores `<pid>:<epochMs>`; the heartbeat
|
|
2131
|
-
* stores JSON with a `pid`.
|
|
2132
|
-
*/
|
|
2133
1235
|
function claimedPid(read) {
|
|
2134
1236
|
try {
|
|
2135
1237
|
return read();
|
|
@@ -2138,17 +1240,6 @@ function claimedPid(read) {
|
|
|
2138
1240
|
return null;
|
|
2139
1241
|
}
|
|
2140
1242
|
}
|
|
2141
|
-
/**
|
|
2142
|
-
* The lifetime marker, heartbeat, and instance-registry entry, described so
|
|
2143
|
-
* {@link stopDaemon} can assert them the same way it asserts the two sockets.
|
|
2144
|
-
*
|
|
2145
|
-
* Ownership, not mere presence, decides: a file naming a pid that is alive and
|
|
2146
|
-
* is not the daemon we just stopped belongs to a DIFFERENT daemon (a successor
|
|
2147
|
-
* that started during the stop, or a peer serving this state dir), and deleting
|
|
2148
|
-
* it would break that live daemon — the same reasoning the broker-socket branch
|
|
2149
|
-
* above uses for a standalone owner. Everything else is residue from a provably
|
|
2150
|
-
* dead owner and is reclaimed.
|
|
2151
|
-
*/
|
|
2152
1243
|
export function stopResidueArtifacts(stoppedPid, survivors = []) {
|
|
2153
1244
|
const artifacts = [];
|
|
2154
1245
|
const lifetimePath = path.join(getDaemonDir(), LIFETIME_FILE);
|
|
@@ -2165,7 +1256,7 @@ export function stopResidueArtifacts(stoppedPid, survivors = []) {
|
|
|
2165
1256
|
reclaim: () => { try {
|
|
2166
1257
|
fs.unlinkSync(lifetimePath);
|
|
2167
1258
|
}
|
|
2168
|
-
catch {
|
|
1259
|
+
catch { } },
|
|
2169
1260
|
stillPresent: () => fs.existsSync(lifetimePath),
|
|
2170
1261
|
});
|
|
2171
1262
|
const heartbeatPath = getHeartbeatPath();
|
|
@@ -2173,33 +1264,16 @@ export function stopResidueArtifacts(stoppedPid, survivors = []) {
|
|
|
2173
1264
|
artifacts.push({
|
|
2174
1265
|
label: 'daemon heartbeat',
|
|
2175
1266
|
present: fs.existsSync(heartbeatPath),
|
|
2176
|
-
// A stale heartbeat is not cosmetic: resolveLiveDaemonPid() trusts a FRESH
|
|
2177
|
-
// one to re-adopt a daemon whose pid file was lost, so leaving one behind
|
|
2178
|
-
// can make a dead daemon read as running.
|
|
2179
1267
|
ownedByLiveOther: hb !== null && (survivors.includes(hb.pid)
|
|
2180
1268
|
|| (hb.pid !== stoppedPid && isAlive(hb.pid))),
|
|
2181
1269
|
reclaim: () => removeHeartbeat(),
|
|
2182
1270
|
stillPresent: () => fs.existsSync(heartbeatPath),
|
|
2183
1271
|
});
|
|
2184
|
-
// POSIX-only, matching registerDaemonInstance/unregisterDaemonInstance.
|
|
2185
1272
|
if (process.platform !== 'win32' && stoppedPid !== null) {
|
|
2186
1273
|
const markerPath = path.join(getDaemonInstancesDir(), String(stoppedPid));
|
|
2187
1274
|
artifacts.push({
|
|
2188
1275
|
label: 'daemon instance registry entry',
|
|
2189
1276
|
present: fs.existsSync(markerPath),
|
|
2190
|
-
// The marker is named by pid, so it is unambiguously this daemon's — but
|
|
2191
|
-
// "this daemon" is only residue once it is actually DEAD. If the kill did
|
|
2192
|
-
// not land, deleting the marker erases the very record
|
|
2193
|
-
// `findSurvivingStateDirDaemons` enumerates, so the next `agents daemon
|
|
2194
|
-
// stop` would find an empty registry and a cleared pid file and report
|
|
2195
|
-
// `ok: true` with the daemon still running.
|
|
2196
|
-
//
|
|
2197
|
-
// "Dead" is decided by the caller's OWN survivor scan, not by `isAlive`:
|
|
2198
|
-
// a SIGKILLed child is a zombie until its parent reaps it, and `kill(pid,
|
|
2199
|
-
// 0)` succeeds on a zombie. Keyed off `isAlive` this kept the entry of a
|
|
2200
|
-
// daemon that was already gone, so the stop stopped being able to report
|
|
2201
|
-
// its own state truthfully. The survivor scan matches a live
|
|
2202
|
-
// `__daemon-run`, which a zombie is not.
|
|
2203
1277
|
ownedByLiveOther: survivors.includes(stoppedPid),
|
|
2204
1278
|
reclaim: () => unregisterDaemonInstance(stoppedPid),
|
|
2205
1279
|
stillPresent: () => fs.existsSync(markerPath),
|
|
@@ -2207,22 +1281,6 @@ export function stopResidueArtifacts(stoppedPid, survivors = []) {
|
|
|
2207
1281
|
}
|
|
2208
1282
|
return artifacts;
|
|
2209
1283
|
}
|
|
2210
|
-
/**
|
|
2211
|
-
* Live `__daemon-run` processes still registered in THIS state dir's instance
|
|
2212
|
-
* registry, excluding `exclude`. State-dir-scoped by construction: the registry
|
|
2213
|
-
* lives inside this state dir, so a daemon serving a DIFFERENT state dir (a test
|
|
2214
|
-
* fixture with its own HOME, a separate install/home) registers elsewhere and is
|
|
2215
|
-
* invisible here — it is never a stop/takeover target. POSIX-only (the registry
|
|
2216
|
-
* and its `ps` liveness probe are); `[]` on Windows.
|
|
2217
|
-
*
|
|
2218
|
-
* Exported for `agents daemon status`/`doctor`/`services` (RUSH-2368): those
|
|
2219
|
-
* commands previously flagged every `__daemon-run` on the box (a raw `ps` scan)
|
|
2220
|
-
* as a "duplicate" of this daemon, which misreported test fixtures under their
|
|
2221
|
-
* own HOME — and therefore their own state dir and registry — as strays to
|
|
2222
|
-
* kill. This registry read is the same scope the reaper (`reapStrayDaemons`)
|
|
2223
|
-
* and the stop postcondition (`stopDaemon`) already use, so the display and the
|
|
2224
|
-
* reaper agree on what a duplicate is.
|
|
2225
|
-
*/
|
|
2226
1284
|
function findStateDirDaemonProcesses(exclude) {
|
|
2227
1285
|
const live = [];
|
|
2228
1286
|
const unverified = [];
|
|
@@ -2239,7 +1297,7 @@ function findStateDirDaemonProcesses(exclude) {
|
|
|
2239
1297
|
for (const name of entries) {
|
|
2240
1298
|
const pid = parseInt(name, 10);
|
|
2241
1299
|
if (isNaN(pid) || String(pid) !== name)
|
|
2242
|
-
continue;
|
|
1300
|
+
continue;
|
|
2243
1301
|
if (exclude.has(pid))
|
|
2244
1302
|
continue;
|
|
2245
1303
|
const identity = daemonProcessIdentity(pid);
|
|
@@ -2253,17 +1311,7 @@ function findStateDirDaemonProcesses(exclude) {
|
|
|
2253
1311
|
export function findSurvivingStateDirDaemons(exclude) {
|
|
2254
1312
|
return findStateDirDaemonProcesses(exclude).live;
|
|
2255
1313
|
}
|
|
2256
|
-
|
|
2257
|
-
* Stop the daemon and ASSERT its postcondition (SING-12, RUSH-2355), unloading it
|
|
2258
|
-
* from launchd/systemd if applicable.
|
|
2259
|
-
*
|
|
2260
|
-
* The SIGTERM → grace → killTree sequence is unchanged; what it adds is
|
|
2261
|
-
* verification: after the daemon is gone it checks that no `__daemon-run` for
|
|
2262
|
-
* THIS state dir survives and reclaims the state files a killTree escalation
|
|
2263
|
-
* (which skips the graceful handleShutdown) can leave stale. The browser IPC
|
|
2264
|
-
* socket is no longer among them — it left with the standalone `browser` CLI
|
|
2265
|
-
* (PHNX-4101). It never reports success on an unverified stop.
|
|
2266
|
-
*/
|
|
1314
|
+
// Stop crosses the same lifecycle lock as start and claim, preventing teardown from racing takeover.
|
|
2267
1315
|
export function stopDaemon() {
|
|
2268
1316
|
const releaseLock = acquireLifecycleLock();
|
|
2269
1317
|
if (!releaseLock) {
|
|
@@ -2296,7 +1344,6 @@ function pathIdentityMatches(filePath, expected) {
|
|
|
2296
1344
|
const current = readPathIdentity(filePath);
|
|
2297
1345
|
return current !== null && current.dev === expected.dev && current.ino === expected.ino;
|
|
2298
1346
|
}
|
|
2299
|
-
/** daemon.lock is held for this entire read-signal-verify-cleanup transaction. */
|
|
2300
1347
|
function stopDaemonLocked() {
|
|
2301
1348
|
const platform = os.platform();
|
|
2302
1349
|
const released = [];
|
|
@@ -2314,9 +1361,6 @@ function stopDaemonLocked() {
|
|
|
2314
1361
|
detachedChildren: listLiveRoutineChildren(),
|
|
2315
1362
|
};
|
|
2316
1363
|
}
|
|
2317
|
-
// Capture the target and its path-bound resources while lifecycle writers are
|
|
2318
|
-
// excluded. resolveLiveDaemonPid(true) rejects a reused/non-daemon pid before
|
|
2319
|
-
// any service-manager teardown or direct signal and repairs only under lock.
|
|
2320
1364
|
const pid = resolveLiveDaemonPid(true);
|
|
2321
1365
|
if (platform === 'darwin') {
|
|
2322
1366
|
const plistPath = getLaunchdPlistPath();
|
|
@@ -2337,7 +1381,7 @@ function stopDaemonLocked() {
|
|
|
2337
1381
|
try {
|
|
2338
1382
|
fs.unlinkSync(plistPath);
|
|
2339
1383
|
}
|
|
2340
|
-
catch {
|
|
1384
|
+
catch { }
|
|
2341
1385
|
}
|
|
2342
1386
|
}
|
|
2343
1387
|
if (platform === 'linux') {
|
|
@@ -2360,14 +1404,11 @@ function stopDaemonLocked() {
|
|
|
2360
1404
|
try {
|
|
2361
1405
|
fs.unlinkSync(unitPath);
|
|
2362
1406
|
}
|
|
2363
|
-
catch {
|
|
1407
|
+
catch { }
|
|
2364
1408
|
}
|
|
2365
1409
|
}
|
|
2366
1410
|
if (pid) {
|
|
2367
1411
|
if (process.platform === 'win32') {
|
|
2368
|
-
// Windows has no graceful termination signal — terminate the daemon and
|
|
2369
|
-
// its job/browser child tree in one shot (taskkill /T), so stop doesn't
|
|
2370
|
-
// report success while children keep running.
|
|
2371
1412
|
if (isLiveDaemon(pid)) {
|
|
2372
1413
|
killTree(pid);
|
|
2373
1414
|
escalated = true;
|
|
@@ -2375,22 +1416,11 @@ function stopDaemonLocked() {
|
|
|
2375
1416
|
}
|
|
2376
1417
|
}
|
|
2377
1418
|
else {
|
|
2378
|
-
// Revalidate immediately before the signal: the pid may have exited and
|
|
2379
|
-
// been reused since the ownership snapshot above.
|
|
2380
1419
|
if (isLiveDaemon(pid)) {
|
|
2381
1420
|
try {
|
|
2382
1421
|
process.kill(pid, 'SIGTERM');
|
|
2383
1422
|
}
|
|
2384
|
-
catch {
|
|
2385
|
-
// Wait for it to actually go. This used to be a setTimeout escalation plus
|
|
2386
|
-
// an immediate removeDaemonPid(), which had two failure modes: in a
|
|
2387
|
-
// short-lived process (the npm postinstall) the timer never fired at all,
|
|
2388
|
-
// and clearing the pid file while the old daemon still ran made
|
|
2389
|
-
// isDaemonRunning() report false, so startDaemon() launched a SECOND
|
|
2390
|
-
// daemon. Its hosted broker then unlinked the live socket and rebound,
|
|
2391
|
-
// orphaning the first broker with every unlocked bundle still in its RAM
|
|
2392
|
-
// and unreachable — two brokers on one socket path, seen on a real machine
|
|
2393
|
-
// after an install into a second prefix.
|
|
1423
|
+
catch { }
|
|
2394
1424
|
if (!waitForExit(pid, STOP_GRACE_MS) && isLiveDaemon(pid)) {
|
|
2395
1425
|
killTree(pid);
|
|
2396
1426
|
escalated = true;
|
|
@@ -2399,13 +1429,9 @@ function stopDaemonLocked() {
|
|
|
2399
1429
|
}
|
|
2400
1430
|
}
|
|
2401
1431
|
}
|
|
2402
|
-
// ── Assert the postcondition (SING-12) ────────────────────────────────────
|
|
2403
|
-
// No `__daemon-run` for this state dir may survive the stop.
|
|
2404
1432
|
const stateDirProcesses = findStateDirDaemonProcesses(new Set([process.pid]));
|
|
2405
1433
|
const survivors = stateDirProcesses.live;
|
|
2406
1434
|
const unverifiedSurvivors = stateDirProcesses.unverified;
|
|
2407
|
-
// The registry is best-effort and may be missing, so always re-check the
|
|
2408
|
-
// direct target as well. This is the only check available on Windows.
|
|
2409
1435
|
if (pid && !survivors.includes(pid) && !unverifiedSurvivors.includes(pid)) {
|
|
2410
1436
|
const identity = daemonProcessIdentity(pid);
|
|
2411
1437
|
if (identity === 'daemon')
|
|
@@ -2425,9 +1451,6 @@ function stopDaemonLocked() {
|
|
|
2425
1451
|
if (pid && !targetStillOwnsResources) {
|
|
2426
1452
|
released.push('daemon process');
|
|
2427
1453
|
}
|
|
2428
|
-
// Delete the registration only after the target is provably gone, and only
|
|
2429
|
-
// if the file still names that target. A replacement value belongs to a
|
|
2430
|
-
// successor (or another writer) and is both preserved and reported.
|
|
2431
1454
|
const currentPid = readDaemonPid();
|
|
2432
1455
|
if (pid !== null && !targetStillOwnsResources) {
|
|
2433
1456
|
if (removeDaemonPidIfOwned(pid))
|
|
@@ -2443,19 +1466,6 @@ function stopDaemonLocked() {
|
|
|
2443
1466
|
else if (pid === null && currentPid !== null) {
|
|
2444
1467
|
surviving.push(`daemon pid registration changed to ${currentPid} during stop`);
|
|
2445
1468
|
}
|
|
2446
|
-
// The browser IPC socket is the standalone `browser` CLI's now (PHNX-4101):
|
|
2447
|
-
// the daemon no longer binds it, so there is nothing to reclaim here. browser-cli
|
|
2448
|
-
// owns its own socket lifecycle under `~/.agents/.cache/helpers/browser/`.
|
|
2449
|
-
// ── The state files a killed daemon cannot clean up itself (RUSH-2421) ─────
|
|
2450
|
-
// handleShutdown removes the lifetime marker, the heartbeat and this pid's
|
|
2451
|
-
// instance-registry entry — but it only runs on the GRACEFUL path. Every
|
|
2452
|
-
// escalation above (killTree, and the whole win32 branch) skips it, so those
|
|
2453
|
-
// three outlive the daemon and the stop reported `ok: true` while its state
|
|
2454
|
-
// dir still described a daemon that no longer exists. Each is stale metadata
|
|
2455
|
-
// with real consequences: a leftover heartbeat is what `resolveLiveDaemonPid`
|
|
2456
|
-
// consults to re-adopt a "live" daemon, and a leftover registry entry is what
|
|
2457
|
-
// `reapStrayDaemons` enumerates. Same shape as the sockets above — reclaim
|
|
2458
|
-
// what a provably dead owner left, never touch what a live one owns.
|
|
2459
1469
|
for (const artifact of stopResidueArtifacts(pid, [...survivors, ...unverifiedSurvivors])) {
|
|
2460
1470
|
if (!artifact.present) {
|
|
2461
1471
|
released.push(artifact.label);
|
|
@@ -2471,8 +1481,6 @@ function stopDaemonLocked() {
|
|
|
2471
1481
|
else
|
|
2472
1482
|
released.push(`${artifact.label} (reclaimed)`);
|
|
2473
1483
|
}
|
|
2474
|
-
// In-flight detached routine children survive on purpose (SING-11a) — report,
|
|
2475
|
-
// never kill: severing a live agent mid-run is worse than a daemon restart.
|
|
2476
1484
|
const detachedChildren = listLiveRoutineChildren();
|
|
2477
1485
|
return {
|
|
2478
1486
|
ok: surviving.length === 0,
|
|
@@ -2483,14 +1491,6 @@ function stopDaemonLocked() {
|
|
|
2483
1491
|
detachedChildren,
|
|
2484
1492
|
};
|
|
2485
1493
|
}
|
|
2486
|
-
/**
|
|
2487
|
-
* Get current daemon status including running state, PID, enabled job count, and
|
|
2488
|
-
* the supervised-restart history `agents daemon status` renders (PHNX-4116).
|
|
2489
|
-
*
|
|
2490
|
-
* There is no `wedged` state: a stalled daemon exits for a supervised
|
|
2491
|
-
* systemd/launchd restart rather than sitting unresponsive, so the daemon is
|
|
2492
|
-
* simply `running` or `stopped`, and its recent restarts are reported instead.
|
|
2493
|
-
*/
|
|
2494
1494
|
export function getDaemonStatus() {
|
|
2495
1495
|
const running = isDaemonRunning();
|
|
2496
1496
|
const pid = readDaemonPid();
|
|
@@ -2498,12 +1498,12 @@ export function getDaemonStatus() {
|
|
|
2498
1498
|
try {
|
|
2499
1499
|
jobCount = listAllJobs().filter((j) => j.enabled).length;
|
|
2500
1500
|
}
|
|
2501
|
-
catch {
|
|
1501
|
+
catch { }
|
|
2502
1502
|
let binaryPath = null;
|
|
2503
1503
|
try {
|
|
2504
1504
|
binaryPath = getAgentsBinPath();
|
|
2505
1505
|
}
|
|
2506
|
-
catch {
|
|
1506
|
+
catch { }
|
|
2507
1507
|
const recentRestarts = readRecentDaemonRestarts(Date.now() - 24 * 60 * 60 * 1000);
|
|
2508
1508
|
const lastRestart = recentRestarts.length > 0 ? recentRestarts[recentRestarts.length - 1] : null;
|
|
2509
1509
|
return {
|
|
@@ -2519,7 +1519,6 @@ export function getDaemonStatus() {
|
|
|
2519
1519
|
lastRestartAt: lastRestart ? lastRestart.at : null,
|
|
2520
1520
|
};
|
|
2521
1521
|
}
|
|
2522
|
-
/** Read the daemon log, optionally limited to the last N lines. */
|
|
2523
1522
|
export function readDaemonLog(lines) {
|
|
2524
1523
|
const logPath = getDaemonLogPath();
|
|
2525
1524
|
if (!fs.existsSync(logPath))
|
|
@@ -2530,15 +1529,11 @@ export function readDaemonLog(lines) {
|
|
|
2530
1529
|
const allLines = content.split('\n');
|
|
2531
1530
|
return allLines.slice(-lines).join('\n');
|
|
2532
1531
|
}
|
|
2533
|
-
/** Send SIGHUP to the daemon to trigger a job reload. */
|
|
2534
1532
|
export function signalDaemonReload() {
|
|
2535
1533
|
const pid = readDaemonPid();
|
|
2536
1534
|
if (!pid)
|
|
2537
1535
|
return false;
|
|
2538
1536
|
if (process.platform === 'win32') {
|
|
2539
|
-
// Windows has no SIGHUP, so signal-based live reload isn't available. Sending
|
|
2540
|
-
// it would throw; instead report "not reloaded" so callers tell the user to
|
|
2541
|
-
// restart the daemon to pick up job changes.
|
|
2542
1537
|
return false;
|
|
2543
1538
|
}
|
|
2544
1539
|
try {
|