@phnx-labs/agents-cli 1.22.46 → 1.22.49
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 +663 -0
- package/LICENSE +102 -182
- package/README.md +96 -49
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +243 -25
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +711 -62
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +26 -24
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +107 -53
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +7 -6
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +4 -3
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +5 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +243 -18
- package/dist/commands/teams.js +34 -2
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +32 -62
- package/dist/commands/watchdog.js +9 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +84 -6
- package/dist/lib/accounting/usage.js +293 -75
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +25 -15
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +40 -9
- package/dist/lib/browser/profiles.d.ts +104 -65
- package/dist/lib/browser/profiles.js +277 -165
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +28 -2
- package/dist/lib/browser/remote-control.js +35 -3
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +44 -11
- package/dist/lib/browser/runtime-state.js +98 -15
- package/dist/lib/browser/service.d.ts +65 -9
- package/dist/lib/browser/service.js +196 -61
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +6 -11
- package/dist/lib/config-keys.js +18 -19
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +22 -20
- package/dist/lib/device-config.d.ts +25 -10
- package/dist/lib/device-config.js +90 -39
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +26 -0
- package/dist/lib/devices/stats-cache.js +59 -7
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +70 -14
- package/dist/lib/exec.js +110 -38
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +19 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +35 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +44 -7
- package/dist/lib/menubar/install-menubar.js +105 -23
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +86 -2
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +142 -8
- package/dist/lib/session/discover.d.ts +23 -1
- package/dist/lib/session/discover.js +44 -10
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +17 -3
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +93 -1
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +19 -0
- package/dist/lib/session/trajectory-html.js +770 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +284 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +384 -0
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +6 -3
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +2 -2
- package/dist/lib/state.js +11 -5
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +204 -27
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +34 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +18 -4
- package/dist/lib/usage-refresh.js +68 -19
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +5 -7
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
|
@@ -16,25 +16,51 @@ import { isAlive, killTree, backgroundSpawnOptions, waitForExit } from '../platf
|
|
|
16
16
|
import { listJobs as listAllJobs } from '../scheduling/routines.js';
|
|
17
17
|
import { syncAllProjectRoutines } from '../routines-project.js';
|
|
18
18
|
import { JobScheduler } from '../scheduler.js';
|
|
19
|
-
|
|
19
|
+
// MonitorEngine is now owned by MonitorEngineService (daemon/monitor-engine-service.ts).
|
|
20
20
|
import { executeJobDetached, monitorRunningJobs, listLiveRoutineChildren } from './runner.js';
|
|
21
21
|
import { detectOverdueJobs, notifyOverdue } from '../overdue.js';
|
|
22
22
|
import { runCatchup } from '../catchup.js';
|
|
23
23
|
import { notifyRoutineStart, notifyRoutineFinish, notifyRoutineStartFailed } from '../routine-notify.js';
|
|
24
24
|
import { notifyOwnerRoutineFinish, notifyOwnerRoutineStartFailed } from '../routine-notify-owner.js';
|
|
25
25
|
import { BrowserService } from '../browser/service.js';
|
|
26
|
-
import {
|
|
26
|
+
import { getSocketPath as getBrowserIpcSocketPath } from '../browser/ipc.js';
|
|
27
27
|
import { startHostedWebhookReceivers } from '../daemon-webhooks.js';
|
|
28
28
|
import { secretsBrokerSocketPath, brokerPidAlive } from '../secrets/agent.js';
|
|
29
29
|
import { redactSecrets } from '../redact.js';
|
|
30
30
|
import { getAgentsBinPath, getCliLaunch, BUN_VIRTUAL_ROOT } from '../cli-entry.js';
|
|
31
|
-
import { isSchedulerEnabled, assertSchedulerEnabled, isDaemonEnabled,
|
|
31
|
+
import { isSchedulerEnabled, assertSchedulerEnabled, isDaemonEnabled, resolveBrowserTaskIdleMs } from '../device-config.js';
|
|
32
32
|
import { reapTerminalRoutineProcesses } from '../routine-process-cleanup.js';
|
|
33
|
-
import { recordSubsystemOk, recordSubsystemError, recordSubsystemErrorReason, readSubsystemHealth,
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
33
|
+
import { recordSubsystemOk, recordSubsystemError, recordSubsystemErrorReason, readSubsystemHealth, SUBSYSTEM_DAEMON_START } from '../daemon-health.js';
|
|
34
|
+
import { runActiveSessionsWarmTick } from '../daemon-ticks.js';
|
|
35
|
+
import { watchActiveSessionsReaderPresence } from '../session/session-cache.js';
|
|
36
|
+
import { ServiceSupervisor } from './supervisor.js';
|
|
37
|
+
import { SessionIndexService } from './session-index-service.js';
|
|
38
|
+
import { SecretsBrokerService } from './secrets-broker-service.js';
|
|
39
|
+
import { MonitorEngineService } from './monitor-engine-service.js';
|
|
40
|
+
import { AccountStateDaemonService } from './account-state-daemon-service.js';
|
|
41
|
+
import { BrowserIPCService } from './browser-ipc-service.js';
|
|
42
|
+
import { WatchdogService } from './watchdog-service.js';
|
|
43
|
+
import { DeviceProbeService } from './device-probe-service.js';
|
|
44
|
+
import { SelfHealService } from './self-heal-service.js';
|
|
45
|
+
import { KeychainReapService } from './keychain-reap-service.js';
|
|
46
|
+
import { StateDirCheckService } from './state-dir-check-service.js';
|
|
36
47
|
import { emit, emitRoutineEnd } from '../feed/events.js';
|
|
37
|
-
import { readDaemonServicesConfig } from '../daemon-services.js';
|
|
48
|
+
import { readDaemonServicesConfig, drainDaemonServiceRestartQueue } from '../daemon-services.js';
|
|
49
|
+
/**
|
|
50
|
+
* The live `ServiceSupervisor` for the current `runDaemon()` invocation, or
|
|
51
|
+
* `null` before boot / after shutdown. In-process only — a separate `agents
|
|
52
|
+
* daemon status` process cannot see this; per-service health that must
|
|
53
|
+
* survive across processes goes through `daemon-health.ts` instead (which
|
|
54
|
+
* `ServiceSupervisor` already writes on every tick). This getter exists so a
|
|
55
|
+
* FUTURE same-process consumer (e.g. a `daemon services` live-status IPC
|
|
56
|
+
* handler) can read the supervisor's richer state (`parked`, not just
|
|
57
|
+
* ok/error) without needing its own reference to `runDaemon()`'s locals.
|
|
58
|
+
*/
|
|
59
|
+
let activeServiceSupervisor = null;
|
|
60
|
+
/** Health for every service currently registered on the live supervisor, or `null` if the daemon isn't running in this process. */
|
|
61
|
+
export function getServiceSupervisorHealth() {
|
|
62
|
+
return activeServiceSupervisor?.health() ?? null;
|
|
63
|
+
}
|
|
38
64
|
const PID_FILE = 'daemon.pid';
|
|
39
65
|
const LIFETIME_FILE = 'daemon.lifetime';
|
|
40
66
|
const LOCK_FILE = 'daemon.lock';
|
|
@@ -96,42 +122,28 @@ const CATCHUP_TICK_MS = 5 * 60_000;
|
|
|
96
122
|
/**
|
|
97
123
|
* Cadences for the in-process background ticks, named here beside the other
|
|
98
124
|
* tick constants rather than left as inline literals at their `setInterval`
|
|
99
|
-
* (RUSH-2423).
|
|
125
|
+
* (RUSH-2423). Self-heal, keychain-reap, state-dir-check, watchdog, and
|
|
126
|
+
* device-probe cadences moved to their own `*-service.ts` files (RUSH-3193
|
|
127
|
+
* P3, alongside session-index/account-state/etc.) — see those files for the
|
|
128
|
+
* per-service trade-offs (self-heal's 6h/cheap-to-be-late repair cadence,
|
|
129
|
+
* keychain-reap's 5min `ps`-shell cost bound, state-dir-check's env override
|
|
130
|
+
* for tests, watchdog/device-probe's shared 3min in-process housekeeping
|
|
131
|
+
* cadence, NOT a routine — RUSH-2495).
|
|
100
132
|
*
|
|
101
|
-
* - **Self-heal** repairs slow rot (a stale non-default version, an invalid
|
|
102
|
-
* plugin manifest), so it is cheap to be late and expensive to run often —
|
|
103
|
-
* hence 6h, plus one staggered kickoff shortly after start so shims and PATH
|
|
104
|
-
* settle without making launch itself busy.
|
|
105
133
|
* - **Broker self-heal** is a bare `agentPing`, and the failure it recovers
|
|
106
134
|
* from wedges every keychain-backed secret on the box, so it runs minutely.
|
|
107
|
-
* - **Keychain reap** shells `ps` once per pass; 5 min bounds that cost while
|
|
108
|
-
* still clearing orphans well inside a human's attention span.
|
|
109
|
-
* - **State-dir self-check** is two `fs` reads. Its env override exists for
|
|
110
|
-
* tests, which cannot wait a minute to observe the self-terminate guard.
|
|
111
135
|
*/
|
|
112
|
-
|
|
113
|
-
const SELF_HEAL_KICKOFF_MS = 30_000;
|
|
114
|
-
const BROKER_SELF_HEAL_TICK_MS = 60_000;
|
|
115
|
-
const KEYCHAIN_REAP_TICK_MS = 5 * 60_000;
|
|
136
|
+
// BROKER_SELF_HEAL_TICK_MS moved to secrets-broker-service.ts (RUSH-3193 P2).
|
|
116
137
|
// RUSH-2501: reap tmux sessions whose panes are all dead every 5 minutes.
|
|
117
138
|
const DEAD_PANE_REAP_TICK_MS = 5 * 60_000;
|
|
118
139
|
// RUSH-2622: close abandoned browser-task tabs every 5 minutes.
|
|
119
140
|
const BROWSER_TASK_REAP_TICK_MS = 5 * 60_000;
|
|
120
|
-
const STATE_DIR_CHECK_TICK_MS = 60_000;
|
|
121
|
-
// Watchdog nudges this host's own stalled sessions; device-probe refreshes
|
|
122
|
-
// registered devices' reachability and surfaces newly appeared tailnet nodes.
|
|
123
|
-
// Both are daemon-owned housekeeping timers, NOT routines (RUSH-2495) — plain
|
|
124
|
-
// in-process intervals the daemon holds directly, same cadence the old timers ran.
|
|
125
|
-
const WATCHDOG_TICK_MS = 3 * 60_000;
|
|
126
|
-
const DEVICE_PROBE_TICK_MS = 3 * 60_000;
|
|
127
141
|
// Keep the active-session cache/journal fresh for sessions watch + Factory.
|
|
128
142
|
// Matches DEFAULT_ACTIVE_CACHE_MAX_AGE_MS (15s) so long-lived watchers never
|
|
129
143
|
// outlive the publisher (RUSH-2484 — CLI-owned continuous publish).
|
|
130
144
|
const ACTIVE_SESSIONS_WARM_TICK_MS = 15_000;
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
// `agents sessions*` call (RUSH-2682 — indexing was lazy, nothing scheduled it).
|
|
134
|
-
const SESSION_INDEX_WARM_TICK_MS = 20_000;
|
|
145
|
+
// Session-index warm interval/deadline live in session-index-service.ts now
|
|
146
|
+
// (RUSH-3193 — migrated onto ServiceSupervisor).
|
|
135
147
|
const WEDGE_THRESHOLD_TICKS = 3;
|
|
136
148
|
/**
|
|
137
149
|
* Crash-loop prevention (RUSH-2418). Three layers, because none of them alone
|
|
@@ -704,75 +716,17 @@ export function warnEphemeralDaemonRoot(resolveBin = getAgentsBinPath) {
|
|
|
704
716
|
// named in stack traces, readable without scrolling through runDaemon's
|
|
705
717
|
// 500-line body, and not recreated on every function invocation.
|
|
706
718
|
//
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
//
|
|
719
|
+
// self-heal and keychain-reap moved to SelfHealService / KeychainReapService
|
|
720
|
+
// on the ServiceSupervisor (RUSH-3193 P3) — the supervisor's own per-tick
|
|
721
|
+
// deadline + inFlight guard replaces their local `healing`/`reapingKeychain`
|
|
722
|
+
// flags. state-dir-check moved to StateDirCheckService (RUSH-3193 P3),
|
|
723
|
+
// registered after `handleShutdown` is declared — see its registration site
|
|
724
|
+
// below. runBrokerSelfHeal was moved into SecretsBrokerService (RUSH-3193 P2).
|
|
713
725
|
// ---------------------------------------------------------------------------
|
|
714
726
|
// In-flight guards: each flag prevents a slow tick from being re-entered
|
|
715
727
|
// by the next timer fire before the previous one finishes.
|
|
716
|
-
let healing = false;
|
|
717
|
-
let reapingKeychain = false;
|
|
718
728
|
let reapingDeadPanes = false;
|
|
719
729
|
let reapingBrowserTasks = false;
|
|
720
|
-
/**
|
|
721
|
-
* Resource self-heal: fill missing resources, repair invalid manifests, and
|
|
722
|
-
* fast-forward pristine stale plugins. Conservative 'safe' mode: never
|
|
723
|
-
* overwrites hand-edited content. Runs ~every 6h plus once ~30s after startup.
|
|
724
|
-
*
|
|
725
|
-
* Does not run when the daemon's state directory no longer exists — that is the
|
|
726
|
-
* self-terminate guard's signal to shut down; background maintenance must not
|
|
727
|
-
* recreate the tree while it is mid-exit.
|
|
728
|
-
*/
|
|
729
|
-
async function runHealCheck() {
|
|
730
|
-
if (healing)
|
|
731
|
-
return;
|
|
732
|
-
if (!fs.existsSync(getDaemonDir()))
|
|
733
|
-
return;
|
|
734
|
-
healing = true;
|
|
735
|
-
try {
|
|
736
|
-
const { runSelfHeal, selfHealChangedAnything, selfHealNeedsAttention, summarizeSelfHeal } = await import('../self-heal/registry.js');
|
|
737
|
-
if (!fs.existsSync(getDaemonDir()))
|
|
738
|
-
return;
|
|
739
|
-
const report = await runSelfHeal({ mode: 'safe' });
|
|
740
|
-
if (selfHealChangedAnything(report) || selfHealNeedsAttention(report)) {
|
|
741
|
-
log('INFO', `self-heal: ${summarizeSelfHeal(report)}`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
catch (err) {
|
|
745
|
-
log('ERROR', `self-heal check failed: ${err.message}`);
|
|
746
|
-
}
|
|
747
|
-
finally {
|
|
748
|
-
healing = false;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Keychain orphan reaper: kill stuck keychain helper and `agents` processes
|
|
753
|
-
* whose keychain call never returned. Runs every 5 min. Single-executor: only
|
|
754
|
-
* the daemon runs this, so no cross-device race.
|
|
755
|
-
*/
|
|
756
|
-
async function runKeychainReap() {
|
|
757
|
-
if (reapingKeychain)
|
|
758
|
-
return;
|
|
759
|
-
reapingKeychain = true;
|
|
760
|
-
try {
|
|
761
|
-
const { reapOrphanedKeychainProcesses } = await import('../secrets/reaper.js');
|
|
762
|
-
const result = reapOrphanedKeychainProcesses();
|
|
763
|
-
if (result.reaped > 0) {
|
|
764
|
-
log('WARN', `Reaped ${result.reaped} keychain orphan/stuck process(es)`);
|
|
765
|
-
for (const d of result.details)
|
|
766
|
-
log('WARN', ` ${d}`);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
catch (err) {
|
|
770
|
-
log('ERROR', `Keychain reaper failed: ${err.message}`);
|
|
771
|
-
}
|
|
772
|
-
finally {
|
|
773
|
-
reapingKeychain = false;
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
730
|
/**
|
|
777
731
|
* RUSH-2501: kill tmux sessions on the helper socket whose panes are ALL dead.
|
|
778
732
|
* RUSH-2521: and terminate the helper processes (MCP servers, harness background
|
|
@@ -813,7 +767,7 @@ async function runDeadPaneReap() {
|
|
|
813
767
|
/**
|
|
814
768
|
* RUSH-2622: close abandoned browser-task tabs — a task whose owning agent
|
|
815
769
|
* session has exited, or one idle past `browser.task-idle-minutes` (default 30,
|
|
816
|
-
* 0 disables idle reaping only). Same policy `agents browser
|
|
770
|
+
* 0 disables idle reaping only). Same policy `agents browser prune` triggers on
|
|
817
771
|
* demand; this is the periodic side, and the daemon is the single executor so
|
|
818
772
|
* no UI surface can race it. `service` is the daemon's own long-lived
|
|
819
773
|
* BrowserService — the browser IPC server it started earlier in `runDaemon()`.
|
|
@@ -929,36 +883,59 @@ export async function runDaemon() {
|
|
|
929
883
|
catch (err) {
|
|
930
884
|
log('ERROR', `Stray daemon reaper failed: ${err.message}`);
|
|
931
885
|
}
|
|
932
|
-
//
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
//
|
|
936
|
-
//
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
try {
|
|
942
|
-
const { agentPing, startHostedBroker } = await import('../secrets/agent.js');
|
|
943
|
-
if ((await agentPing()).reachable) {
|
|
944
|
-
log('INFO', 'Secrets broker already running (standalone); daemon not hosting it');
|
|
945
|
-
}
|
|
946
|
-
else {
|
|
947
|
-
hostedBroker = await startHostedBroker();
|
|
948
|
-
if (hostedBroker)
|
|
949
|
-
log('INFO', 'Secrets broker hosted in daemon (socket-first)');
|
|
950
|
-
}
|
|
951
|
-
recordSubsystemOk(SUBSYSTEM_SECRETS_BROKER);
|
|
952
|
-
}
|
|
953
|
-
catch (err) {
|
|
954
|
-
const message = err.message;
|
|
955
|
-
log('WARN', `Secrets broker host skipped: ${message}`);
|
|
956
|
-
recordSubsystemError(SUBSYSTEM_SECRETS_BROKER, message);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
else {
|
|
886
|
+
// Socket services: secrets broker, monitor engine, account-state, and
|
|
887
|
+
// browser IPC are all managed by the ServiceSupervisor (RUSH-3193 P2).
|
|
888
|
+
// The supervisor is created here — before the scheduler — so the secrets
|
|
889
|
+
// broker starts socket-first (#416) and `agents secrets` resolves within
|
|
890
|
+
// ms of daemon start, exactly as before.
|
|
891
|
+
const supervisor = new ServiceSupervisor();
|
|
892
|
+
if (isEnabled('secrets-broker'))
|
|
893
|
+
supervisor.register(new SecretsBrokerService());
|
|
894
|
+
else
|
|
960
895
|
log('INFO', 'Secrets broker service disabled; daemon not hosting it');
|
|
961
|
-
|
|
896
|
+
const monitorEngineSvc = new MonitorEngineService();
|
|
897
|
+
if (isEnabled('monitors'))
|
|
898
|
+
supervisor.register(monitorEngineSvc);
|
|
899
|
+
else
|
|
900
|
+
log('INFO', 'Monitor engine disabled');
|
|
901
|
+
if (isEnabled('account-state'))
|
|
902
|
+
supervisor.register(new AccountStateDaemonService());
|
|
903
|
+
else
|
|
904
|
+
log('INFO', 'Account-state service disabled');
|
|
905
|
+
// BrowserIPCService owns the orphan reap (previously daemon.ts:1303-1318)
|
|
906
|
+
// and the BrowserIPCServer lifecycle. BrowserService is also constructed here
|
|
907
|
+
// so daemon.ts can access it for the browser-task-reap interval (task #3).
|
|
908
|
+
const browserSvcForReap = isEnabled('browser-ipc') ? new BrowserService() : null;
|
|
909
|
+
if (browserSvcForReap)
|
|
910
|
+
supervisor.register(new BrowserIPCService(browserSvcForReap));
|
|
911
|
+
else
|
|
912
|
+
log('INFO', 'Browser IPC service disabled');
|
|
913
|
+
if (isEnabled('session-index'))
|
|
914
|
+
supervisor.register(new SessionIndexService());
|
|
915
|
+
else
|
|
916
|
+
log('INFO', 'Session-index warm service disabled');
|
|
917
|
+
// Watchdog, device-probe, self-heal, and keychain-reap are all periodic
|
|
918
|
+
// services managed by the ServiceSupervisor (RUSH-3193 P3). Each is gated
|
|
919
|
+
// the same way as the socket services above; state-dir-check is registered
|
|
920
|
+
// separately, later, after `handleShutdown` exists (see below).
|
|
921
|
+
if (isEnabled('watchdog'))
|
|
922
|
+
supervisor.register(new WatchdogService());
|
|
923
|
+
else
|
|
924
|
+
log('INFO', 'Watchdog service disabled');
|
|
925
|
+
if (isEnabled('device-probe'))
|
|
926
|
+
supervisor.register(new DeviceProbeService());
|
|
927
|
+
else
|
|
928
|
+
log('INFO', 'Device-probe service disabled');
|
|
929
|
+
if (isEnabled('self-heal'))
|
|
930
|
+
supervisor.register(new SelfHealService());
|
|
931
|
+
else
|
|
932
|
+
log('INFO', 'Self-heal service disabled');
|
|
933
|
+
if (isEnabled('keychain-reap'))
|
|
934
|
+
supervisor.register(new KeychainReapService());
|
|
935
|
+
else
|
|
936
|
+
log('INFO', 'Keychain-reap service disabled');
|
|
937
|
+
await supervisor.startAll({ log });
|
|
938
|
+
activeServiceSupervisor = supervisor;
|
|
962
939
|
// scheduler.enabled=false in this machine's device doc means NO routines fire
|
|
963
940
|
// here — the scheduler and its catchup recovery simply never start, while the
|
|
964
941
|
// daemon keeps its other duties (secrets broker, browser IPC, session sync).
|
|
@@ -1093,21 +1070,12 @@ export async function runDaemon() {
|
|
|
1093
1070
|
}
|
|
1094
1071
|
if (schedulerEnabledAtBoot)
|
|
1095
1072
|
bootScheduler();
|
|
1096
|
-
// Usage and authentication are first-party device state, so the daemon owns
|
|
1097
|
-
// their timers directly rather than scheduling them as built-in routines.
|
|
1098
|
-
// Explicit CLI refreshes converge on the same cross-process refresh leases.
|
|
1099
|
-
const accountStateService = isEnabled('account-state')
|
|
1100
|
-
? startAccountStateService({
|
|
1101
|
-
refreshUsage: runUsageRefreshTick,
|
|
1102
|
-
refreshAuth: runFleetCacheWarmTick,
|
|
1103
|
-
onError: (area, error) => log('WARN', `${area} state refresh failed: ${error.message}`),
|
|
1104
|
-
})
|
|
1105
|
-
: null;
|
|
1106
|
-
if (!accountStateService)
|
|
1107
|
-
log('INFO', 'Account-state service disabled');
|
|
1108
1073
|
// Active-sessions publish-own: continuous journal writer for `sessions watch`.
|
|
1109
1074
|
// Fire once immediately so a cold daemon does not leave watchers on
|
|
1110
|
-
// "awaiting publisher" for a full tick (RUSH-2484).
|
|
1075
|
+
// "awaiting publisher" for a full tick (RUSH-2484). This boot-time fire alone
|
|
1076
|
+
// no-ops on a genuinely cold boot (no reader has connected yet) — the
|
|
1077
|
+
// presence watch below is what actually closes the gap for a watcher that
|
|
1078
|
+
// connects later, whether at cold boot or after the reader-idle window.
|
|
1111
1079
|
let activeSessionsWarmInFlight = false;
|
|
1112
1080
|
const runActiveSessionsWarm = async () => {
|
|
1113
1081
|
if (activeSessionsWarmInFlight)
|
|
@@ -1125,135 +1093,23 @@ export async function runDaemon() {
|
|
|
1125
1093
|
};
|
|
1126
1094
|
void runActiveSessionsWarm();
|
|
1127
1095
|
const activeSessionsWarmInterval = setInterval(() => { void runActiveSessionsWarm(); }, ACTIVE_SESSIONS_WARM_TICK_MS);
|
|
1128
|
-
//
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
else if (indexed > 0)
|
|
1146
|
-
log('INFO', `session-index warm: indexed ${indexed} transcript(s)`);
|
|
1147
|
-
}
|
|
1148
|
-
catch (err) {
|
|
1149
|
-
log('WARN', `session-index warm failed: ${err.message}`);
|
|
1150
|
-
}
|
|
1151
|
-
finally {
|
|
1152
|
-
sessionIndexWarmInFlight = false;
|
|
1153
|
-
}
|
|
1154
|
-
};
|
|
1155
|
-
void runSessionIndexWarm();
|
|
1156
|
-
const sessionIndexWarmInterval = setInterval(() => { void runSessionIndexWarm(); }, SESSION_INDEX_WARM_TICK_MS);
|
|
1157
|
-
// Watchdog: nudge this host's own stalled agent sessions. Gated on the
|
|
1158
|
-
// `watchdog.enabled` device-config flag (`agents watchdog enable`), so the
|
|
1159
|
-
// timer always fires but only does work when the user opted in. Overlap-safe
|
|
1160
|
-
// via the in-flight guard (a slow pass never overlaps the next tick).
|
|
1161
|
-
let watchdogInterval;
|
|
1162
|
-
if (isEnabled('watchdog')) {
|
|
1163
|
-
let watchdogInFlight = false;
|
|
1164
|
-
const runWatchdogTick = async () => {
|
|
1165
|
-
if (watchdogInFlight)
|
|
1166
|
-
return;
|
|
1167
|
-
watchdogInFlight = true;
|
|
1168
|
-
try {
|
|
1169
|
-
if (getConfigValue('watchdog.enabled').value !== true)
|
|
1170
|
-
return;
|
|
1171
|
-
const { runWatchdogPass } = await import('../watchdog/service.js');
|
|
1172
|
-
const result = await runWatchdogPass({ nudge: true });
|
|
1173
|
-
log('INFO', `watchdog: ${result.counts.total} live, ${result.counts.stalled} stalled, ${result.counts.nudged} nudged`);
|
|
1174
|
-
emit('watchdog.action', {
|
|
1175
|
-
module: 'watchdog',
|
|
1176
|
-
total: result.counts.total,
|
|
1177
|
-
stalled: result.counts.stalled,
|
|
1178
|
-
nudged: result.counts.nudged,
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
catch (err) {
|
|
1182
|
-
log('WARN', `watchdog tick failed: ${err.message}`);
|
|
1183
|
-
}
|
|
1184
|
-
finally {
|
|
1185
|
-
watchdogInFlight = false;
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
watchdogInterval = setInterval(() => { void runWatchdogTick(); }, WATCHDOG_TICK_MS);
|
|
1189
|
-
}
|
|
1190
|
-
else {
|
|
1191
|
-
log('INFO', 'Watchdog service disabled');
|
|
1192
|
-
}
|
|
1193
|
-
// Device probe: refresh registered devices' reachability and detect newly
|
|
1194
|
-
// appeared tailnet nodes, dropping a sentinel per pending device so the
|
|
1195
|
-
// menu-bar helper can surface "NEW DEVICES → Register / Ignore". Refresh mode
|
|
1196
|
-
// never auto-registers a newcomer; a machine without tailscale is a clean
|
|
1197
|
-
// no-op. `reconcilePendingSentinels` re-subtracts the ignore-list AND the
|
|
1198
|
-
// registered roster, so a dismissed or already-known device is never
|
|
1199
|
-
// re-surfaced (RUSH-2495 + registry-empty pollution). On soft-fail (no
|
|
1200
|
-
// tailscale) we still prune registered/ignored sentinels so a hermetic test
|
|
1201
|
-
// leak cannot leave fleet boxes in NEW DEVICES forever.
|
|
1202
|
-
let deviceProbeInterval;
|
|
1203
|
-
if (isEnabled('device-probe')) {
|
|
1204
|
-
let deviceProbeInFlight = false;
|
|
1205
|
-
const runDeviceProbeTick = async () => {
|
|
1206
|
-
if (deviceProbeInFlight)
|
|
1207
|
-
return;
|
|
1208
|
-
deviceProbeInFlight = true;
|
|
1209
|
-
try {
|
|
1210
|
-
const { runDeviceSync } = await import('../devices/sync.js');
|
|
1211
|
-
const { reconcilePendingSentinels, pruneDismissedPendingSentinels, } = await import('../devices/pending.js');
|
|
1212
|
-
const dev = await runDeviceSync({ soft: true, mode: 'refresh' });
|
|
1213
|
-
if (!dev.ok) {
|
|
1214
|
-
await pruneDismissedPendingSentinels();
|
|
1215
|
-
if (dev.reason)
|
|
1216
|
-
log('WARN', `device probe soft-fail: ${dev.reason}`);
|
|
1217
|
-
return;
|
|
1218
|
-
}
|
|
1219
|
-
await reconcilePendingSentinels(dev.pending);
|
|
1220
|
-
if (dev.pending.length) {
|
|
1221
|
-
log('INFO', `devices: ${dev.pending.length} new pending (${dev.pending.map((p) => p.name).join(', ')})`);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
catch (err) {
|
|
1225
|
-
log('WARN', `device probe tick failed: ${err.message}`);
|
|
1226
|
-
}
|
|
1227
|
-
finally {
|
|
1228
|
-
deviceProbeInFlight = false;
|
|
1229
|
-
}
|
|
1230
|
-
};
|
|
1231
|
-
// Fire once on start so a leftover pollution set is cleared without waiting
|
|
1232
|
-
// for the first interval tick (the 3-minute lag is how the menubar sat on
|
|
1233
|
-
// 20 phantom NEW DEVICES after a hermetic leak).
|
|
1234
|
-
void runDeviceProbeTick();
|
|
1235
|
-
deviceProbeInterval = setInterval(() => { void runDeviceProbeTick(); }, DEVICE_PROBE_TICK_MS);
|
|
1236
|
-
}
|
|
1237
|
-
else {
|
|
1238
|
-
log('INFO', 'Device-probe service disabled');
|
|
1239
|
-
}
|
|
1240
|
-
// Monitor engine: event-triggered watchers, beside the cron scheduler. Same
|
|
1241
|
-
// daemon, same dispatch seam — a monitor is a routine whose trigger is a
|
|
1242
|
-
// watched source instead of a clock. Reloads on SIGHUP alongside the scheduler.
|
|
1243
|
-
const monitorEngine = isEnabled('monitors')
|
|
1244
|
-
? new MonitorEngine((level, message) => log(level, message))
|
|
1245
|
-
: null;
|
|
1246
|
-
if (monitorEngine) {
|
|
1247
|
-
try {
|
|
1248
|
-
monitorEngine.start();
|
|
1249
|
-
}
|
|
1250
|
-
catch (err) {
|
|
1251
|
-
log('ERROR', `Monitor engine failed to start: ${err.message}`);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
else {
|
|
1255
|
-
log('INFO', 'Monitor engine disabled');
|
|
1256
|
-
}
|
|
1096
|
+
// Out-of-band trigger (RUSH-2484): a reader connecting only writes a presence
|
|
1097
|
+
// timestamp (noteActiveSessionsJournalReader in watch.ts) with no path back to
|
|
1098
|
+
// this timer, so on its own the interval above leaves a fresh connection
|
|
1099
|
+
// waiting up to ACTIVE_SESSIONS_WARM_TICK_MS for its first rows. This watches
|
|
1100
|
+
// for the idle->recent edge and gathers immediately instead of waiting for the
|
|
1101
|
+
// next scheduled tick — a genuinely idle box with no reader still never gathers.
|
|
1102
|
+
const stopActiveSessionsReaderWatch = watchActiveSessionsReaderPresence(() => { void runActiveSessionsWarm(); });
|
|
1103
|
+
// Session-index warm (RUSH-2682) is registered on the supervisor above
|
|
1104
|
+
// (RUSH-3193 P2) alongside the socket services.
|
|
1105
|
+
// Watchdog and device-probe are now managed by WatchdogService /
|
|
1106
|
+
// DeviceProbeService on the supervisor (RUSH-3193 P3), registered above
|
|
1107
|
+
// alongside the socket services. The supervisor fires an immediate first
|
|
1108
|
+
// tick on start, which replaces device-probe's old `void
|
|
1109
|
+
// runDeviceProbeTick()` kick-off (the 3-minute lag that used to leave the
|
|
1110
|
+
// menubar showing 20 phantom NEW DEVICES after a hermetic leak).
|
|
1111
|
+
// Monitor engine is now managed by MonitorEngineService on the supervisor
|
|
1112
|
+
// (RUSH-3193 P2). Access it via monitorEngineSvc.getEngine() in handleReload.
|
|
1257
1113
|
// Backlog recovery: any enabled recurring job whose most-recent expected fire
|
|
1258
1114
|
// is older than its most-recent recorded run was missed — the laptop slept,
|
|
1259
1115
|
// the machine was off, or the daemon crashed through the fire. croner only
|
|
@@ -1263,7 +1119,7 @@ export async function runDaemon() {
|
|
|
1263
1119
|
// `catchup: false`, RUN late. Runs on a timer as well as at startup: a startup
|
|
1264
1120
|
// pass alone misses a fire lost while the daemon stayed up but its event loop
|
|
1265
1121
|
// was wedged, or one lost across an OS suspend that the process survived.
|
|
1266
|
-
// Overlap guard, same shape as
|
|
1122
|
+
// Overlap guard, same shape as SelfHealService's supervisor-owned inFlight guard. A pass
|
|
1267
1123
|
// awaits executeJobDetached per job and an off-box (host/cloud) dispatch can
|
|
1268
1124
|
// block for a while, so a slow pass could still be working when the next tick
|
|
1269
1125
|
// fires. Both passes would then see a job the first has not yet reached as
|
|
@@ -1323,42 +1179,9 @@ export async function runDaemon() {
|
|
|
1323
1179
|
catchingUp = false;
|
|
1324
1180
|
}
|
|
1325
1181
|
}
|
|
1326
|
-
//
|
|
1327
|
-
//
|
|
1328
|
-
//
|
|
1329
|
-
// tunnel it had open — and the next session would either hijack those
|
|
1330
|
-
// (cdp:// profile silently driven via stale ssh tunnel) or fail to
|
|
1331
|
-
// bind because the ports are still claimed.
|
|
1332
|
-
try {
|
|
1333
|
-
const { reapOrphanedProcesses } = await import('../browser/runtime-state.js');
|
|
1334
|
-
const result = reapOrphanedProcesses();
|
|
1335
|
-
if (result.reaped > 0) {
|
|
1336
|
-
log('INFO', `Reaped ${result.reaped} orphan process(es) from prior daemon(s)`);
|
|
1337
|
-
for (const d of result.details)
|
|
1338
|
-
log('INFO', ` ${d}`);
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
catch (err) {
|
|
1342
|
-
log('ERROR', `Orphan reaper failed: ${err.message}`);
|
|
1343
|
-
}
|
|
1344
|
-
const browserService = isEnabled('browser-ipc') ? new BrowserService() : null;
|
|
1345
|
-
const browserIPC = browserService ? new BrowserIPCServer(browserService) : null;
|
|
1346
|
-
if (browserIPC) {
|
|
1347
|
-
try {
|
|
1348
|
-
await browserIPC.start();
|
|
1349
|
-
log('INFO', 'Browser IPC server started');
|
|
1350
|
-
recordSubsystemOk(SUBSYSTEM_BROWSER_IPC);
|
|
1351
|
-
}
|
|
1352
|
-
catch (err) {
|
|
1353
|
-
const message = err.message;
|
|
1354
|
-
log('ERROR', `Browser IPC failed to start: ${message}`);
|
|
1355
|
-
recordSubsystemError(SUBSYSTEM_BROWSER_IPC, message);
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
else {
|
|
1359
|
-
log('INFO', 'Browser IPC service disabled');
|
|
1360
|
-
}
|
|
1361
|
-
// 5th hosted service: signed webhook receiver(s) + their funnel (RUSH-2548).
|
|
1182
|
+
// Browser orphan reap and IPC server start are now managed by BrowserIPCService
|
|
1183
|
+
// on the supervisor (RUSH-3193 P2). The orphan reap runs inside onStart().
|
|
1184
|
+
// Webhook receivers: signed webhook receiver(s) + their funnel (RUSH-2548).
|
|
1362
1185
|
// Started after the broker (above) so it resolves each receiver's signing
|
|
1363
1186
|
// secret headlessly — no AGENTS_SECRETS_PASSPHRASE, no nohup. Binds nothing
|
|
1364
1187
|
// unless daemon/webhooks.yaml declares a receiver, so an unconfigured box no-ops.
|
|
@@ -1379,122 +1202,29 @@ export async function runDaemon() {
|
|
|
1379
1202
|
}
|
|
1380
1203
|
runMonitorTick();
|
|
1381
1204
|
const monitorInterval = setInterval(runMonitorTick, MONITOR_TICK_MS);
|
|
1382
|
-
// Resource
|
|
1383
|
-
//
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
else {
|
|
1391
|
-
log('INFO', 'Self-heal service disabled');
|
|
1392
|
-
}
|
|
1393
|
-
// RUSH-1817: the startup host decision above is one-shot. If a standalone
|
|
1394
|
-
// broker answered agentPing() at daemon start, the daemon declined to host —
|
|
1395
|
-
// but should that standalone later die or crash-loop, nothing takes over and
|
|
1396
|
-
// every `agents secrets unlock|export|start` fails until a manual restart
|
|
1397
|
-
// (this wedged all keychain-backed secrets on zion and blocked a release).
|
|
1398
|
-
// Re-probe on a cadence: whenever the daemon is NOT itself hosting AND no
|
|
1399
|
-
// healthy broker answers a ping, take over hosting. startHostedBroker binds
|
|
1400
|
-
// the socket only when it is free, so a take-over never races a live broker.
|
|
1401
|
-
// Inline (not module-level): closes over `hostedBroker`, a mutable runDaemon-
|
|
1402
|
-
// local var also closed over by handleShutdown. Lifting it would require
|
|
1403
|
-
// hostedBroker to be module-level state too — wider than this refactor's scope.
|
|
1404
|
-
let brokerSelfHealInterval;
|
|
1405
|
-
if (isEnabled('secrets-broker')) {
|
|
1406
|
-
let selfHealingBroker = false;
|
|
1407
|
-
const runBrokerSelfHeal = async () => {
|
|
1408
|
-
if (selfHealingBroker)
|
|
1409
|
-
return;
|
|
1410
|
-
selfHealingBroker = true;
|
|
1411
|
-
try {
|
|
1412
|
-
const { agentPing, startHostedBroker } = await import('../secrets/agent.js');
|
|
1413
|
-
const reachable = (await agentPing()).reachable;
|
|
1414
|
-
if (!shouldTakeOverBroker(hostedBroker != null, reachable))
|
|
1415
|
-
return;
|
|
1416
|
-
hostedBroker = await startHostedBroker();
|
|
1417
|
-
if (hostedBroker) {
|
|
1418
|
-
log('WARN', 'Secrets broker was unreachable; daemon took over hosting (self-heal)');
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
catch (err) {
|
|
1422
|
-
log('WARN', `Secrets broker self-heal skipped: ${err.message}`);
|
|
1423
|
-
}
|
|
1424
|
-
finally {
|
|
1425
|
-
selfHealingBroker = false;
|
|
1426
|
-
}
|
|
1427
|
-
};
|
|
1428
|
-
brokerSelfHealInterval = setInterval(() => { void runBrokerSelfHeal(); }, BROKER_SELF_HEAL_TICK_MS);
|
|
1429
|
-
}
|
|
1430
|
-
// RUSH-2232: reap orphaned keychain helpers and `agents` processes stuck on a
|
|
1431
|
-
// keychain call. Runs as a 5-min interval in the daemon (the single executor)
|
|
1432
|
-
// so no UI surface can race it. See runKeychainReap above.
|
|
1433
|
-
let keychainReapInterval;
|
|
1434
|
-
if (isEnabled('keychain-reap')) {
|
|
1435
|
-
keychainReapInterval = setInterval(() => { void runKeychainReap(); }, KEYCHAIN_REAP_TICK_MS);
|
|
1436
|
-
}
|
|
1437
|
-
else {
|
|
1438
|
-
log('INFO', 'Keychain-reap service disabled');
|
|
1439
|
-
}
|
|
1205
|
+
// Resource self-heal is now managed by SelfHealService on the supervisor
|
|
1206
|
+
// (RUSH-3193 P3), registered above alongside the socket services. The
|
|
1207
|
+
// supervisor's immediate first tick on start replaces the old
|
|
1208
|
+
// SELF_HEAL_KICKOFF_MS (30s) delayed kickoff timer — see self-heal-service.ts.
|
|
1209
|
+
// Broker self-heal (RUSH-1817) is now encapsulated in SecretsBrokerService
|
|
1210
|
+
// (RUSH-3193 P2). The interval fires inside onStart() and is stopped in onStop().
|
|
1211
|
+
// RUSH-2232: keychain-reap is now managed by KeychainReapService on the
|
|
1212
|
+
// supervisor (RUSH-3193 P3), registered above alongside the socket services.
|
|
1440
1213
|
// RUSH-2501: reap tmux sessions whose panes are all dead. Runs on the same
|
|
1441
1214
|
// 5-min cadence as the keychain reaper. Daemon-only (single executor).
|
|
1442
1215
|
void runDeadPaneReap(); // kick-off on startup so the backlog clears immediately
|
|
1443
1216
|
const deadPaneReapInterval = setInterval(() => { void runDeadPaneReap(); }, DEAD_PANE_REAP_TICK_MS);
|
|
1444
1217
|
// RUSH-2622: close abandoned browser-task tabs on the same 5-min cadence,
|
|
1445
|
-
// reusing the daemon's own long-lived BrowserService
|
|
1446
|
-
//
|
|
1447
|
-
// through otherwise.
|
|
1218
|
+
// reusing the daemon's own long-lived BrowserService. `browserSvcForReap` is
|
|
1219
|
+
// the BrowserService created for BrowserIPCService above; null when disabled.
|
|
1448
1220
|
let browserTaskReapInterval;
|
|
1449
|
-
if (
|
|
1450
|
-
void runBrowserTaskReap(
|
|
1451
|
-
browserTaskReapInterval = setInterval(() => { void runBrowserTaskReap(
|
|
1452
|
-
}
|
|
1453
|
-
// RUSH-2367: self-terminate if this daemon's own state dir has been removed
|
|
1454
|
-
// out from under it — the shape of a leaked test-fixture daemon whose /tmp
|
|
1455
|
-
// HOME was deleted by its test's own cleanup while the process itself
|
|
1456
|
-
// somehow survived (lost the SIGTERM/SIGKILL race, or outlived a killed
|
|
1457
|
-
// test runner before its `finally` ever ran). Nothing else can reach a
|
|
1458
|
-
// daemon in that state: no `agents daemon` command targets it, since a
|
|
1459
|
-
// different HOME resolves a different ensureDaemonDir() and therefore a
|
|
1460
|
-
// different instance registry — without this it runs forever. Reads
|
|
1461
|
-
// the lifetime marker directly, never the local ensureDaemonDir() wrapper,
|
|
1462
|
-
// which recreates the directory as a side effect and would defeat the check.
|
|
1463
|
-
// Heartbeat/status paths may recreate the directory and pid file after a
|
|
1464
|
-
// deletion; they never recreate this per-lifetime token.
|
|
1465
|
-
// Inline (not module-level): closes over `lifetimePath` and `lifetimeToken`,
|
|
1466
|
-
// per-boot constants computed once at runDaemon() start that cannot be
|
|
1467
|
-
// pre-computed at module load time.
|
|
1468
|
-
let stateDirCheckInterval;
|
|
1469
|
-
if (isEnabled('state-dir-check')) {
|
|
1470
|
-
let checkingStateDir = false;
|
|
1471
|
-
const runStateDirSelfCheck = () => {
|
|
1472
|
-
if (checkingStateDir)
|
|
1473
|
-
return;
|
|
1474
|
-
checkingStateDir = true;
|
|
1475
|
-
try {
|
|
1476
|
-
let markerMatches = false;
|
|
1477
|
-
try {
|
|
1478
|
-
markerMatches = fs.readFileSync(lifetimePath, 'utf-8') === lifetimeToken;
|
|
1479
|
-
}
|
|
1480
|
-
catch {
|
|
1481
|
-
// A missing state dir or marker is the condition this guard detects.
|
|
1482
|
-
}
|
|
1483
|
-
if (!markerMatches) {
|
|
1484
|
-
log('WARN', `Daemon state dir ${getDaemonDir()} no longer exists; exiting (self-terminate guard)`);
|
|
1485
|
-
void handleShutdown();
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
finally {
|
|
1489
|
-
checkingStateDir = false;
|
|
1490
|
-
}
|
|
1491
|
-
};
|
|
1492
|
-
const stateDirCheckMs = Number(process.env.AGENTS_DAEMON_STATE_DIR_CHECK_MS) || STATE_DIR_CHECK_TICK_MS;
|
|
1493
|
-
stateDirCheckInterval = setInterval(runStateDirSelfCheck, stateDirCheckMs);
|
|
1494
|
-
}
|
|
1495
|
-
else {
|
|
1496
|
-
log('INFO', 'State-dir self-check disabled');
|
|
1221
|
+
if (browserSvcForReap) {
|
|
1222
|
+
void runBrowserTaskReap(browserSvcForReap); // kick-off on startup
|
|
1223
|
+
browserTaskReapInterval = setInterval(() => { void runBrowserTaskReap(browserSvcForReap); }, BROWSER_TASK_REAP_TICK_MS);
|
|
1497
1224
|
}
|
|
1225
|
+
// RUSH-2367 / RUSH-3193 P3: state-dir-check (self-terminate guard) is
|
|
1226
|
+
// registered on the supervisor further below, once `handleShutdown` exists
|
|
1227
|
+
// — see the registration site after its declaration for why.
|
|
1498
1228
|
// RUSH-2418: startup is over — the scheduler, browser IPC, broker decision,
|
|
1499
1229
|
// monitor engine and every background tick are up. Only NOW does this daemon
|
|
1500
1230
|
// clear the auto-start failure streak `ensureDaemonStarted` reads. Clearing it
|
|
@@ -1512,16 +1242,46 @@ export async function runDaemon() {
|
|
|
1512
1242
|
const was = servicesConfig.services[id] !== false;
|
|
1513
1243
|
const now = reloadedEnabled(id);
|
|
1514
1244
|
if (was !== now) {
|
|
1515
|
-
// scheduler and monitors are re-evaluated live later in
|
|
1516
|
-
// so don't tell the user they need a restart for those.
|
|
1245
|
+
// scheduler and monitors' off-transition are re-evaluated live later in
|
|
1246
|
+
// this handler, so don't tell the user they need a restart for those.
|
|
1517
1247
|
if (id === 'scheduler' || (id === 'monitors' && !now)) {
|
|
1518
1248
|
continue;
|
|
1519
1249
|
}
|
|
1520
|
-
|
|
1250
|
+
if (id === 'monitors') {
|
|
1251
|
+
// monitors' on-transition still needs a restart — starting the
|
|
1252
|
+
// engine live is not wired up (see the liveMonitorEngine block below).
|
|
1253
|
+
log('INFO', `Service '${id}' toggled on — restart daemon to apply`);
|
|
1254
|
+
continue;
|
|
1255
|
+
}
|
|
1256
|
+
// RUSH-3193 P4: a service the supervisor already owns (it was enabled
|
|
1257
|
+
// at daemon boot, so it was registered) takes the toggle live via
|
|
1258
|
+
// supervisor.start/stop — no restart needed. One disabled at boot was
|
|
1259
|
+
// never registered, so it falls through to the same "restart to
|
|
1260
|
+
// apply" advice as before.
|
|
1261
|
+
if (supervisor.isRegistered(id)) {
|
|
1262
|
+
const action = now ? supervisor.start(id) : supervisor.stop(id);
|
|
1263
|
+
void action
|
|
1264
|
+
.then(() => log('INFO', `Service '${id}' ${now ? 'started' : 'stopped'} live (SIGHUP reload)`))
|
|
1265
|
+
.catch((err) => log('WARN', `Service '${id}' live ${now ? 'start' : 'stop'} failed: ${err.message}`));
|
|
1266
|
+
}
|
|
1267
|
+
else {
|
|
1268
|
+
log('INFO', `Service '${id}' toggled ${now ? 'on' : 'off'} — restart daemon to apply`);
|
|
1269
|
+
}
|
|
1521
1270
|
}
|
|
1522
1271
|
}
|
|
1523
1272
|
// Remember the reloaded state so subsequent reloads log transitions truthfully.
|
|
1524
1273
|
servicesConfig = reloadedConfig;
|
|
1274
|
+
// Drain queued `agents daemon services restart <id>` requests (RUSH-3193 P4).
|
|
1275
|
+
for (const id of drainDaemonServiceRestartQueue()) {
|
|
1276
|
+
if (supervisor.isRegistered(id)) {
|
|
1277
|
+
void supervisor.restartOne(id)
|
|
1278
|
+
.then(() => log('INFO', `Service '${id}' restarted live (SIGHUP reload)`))
|
|
1279
|
+
.catch((err) => log('WARN', `Service '${id}' live restart failed: ${err.message}`));
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
log('WARN', `Restart requested for '${id}' but it is not supervisor-managed on this daemon — restart the daemon instead`);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1525
1285
|
// Refresh user-layer copies of opted-in project routines BEFORE the
|
|
1526
1286
|
// scheduler reloads, so YAML edits under `<project>/.agents/routines/`
|
|
1527
1287
|
// take effect on the next fire without a manual `routines sync`.
|
|
@@ -1557,17 +1317,18 @@ export async function runDaemon() {
|
|
|
1557
1317
|
}
|
|
1558
1318
|
// Monitor engine can be stopped on reload if disabled; starting it requires
|
|
1559
1319
|
// a restart, so we only handle the off transition here.
|
|
1560
|
-
|
|
1320
|
+
const liveMonitorEngine = monitorEngineSvc.getEngine();
|
|
1321
|
+
if (liveMonitorEngine) {
|
|
1561
1322
|
if (!reloadedEnabled('monitors')) {
|
|
1562
1323
|
log('WARN', 'monitors service is now disabled — stopping monitor engine; restart daemon to re-enable');
|
|
1563
1324
|
try {
|
|
1564
|
-
|
|
1325
|
+
liveMonitorEngine.stop();
|
|
1565
1326
|
}
|
|
1566
1327
|
catch { /* best-effort */ }
|
|
1567
1328
|
}
|
|
1568
1329
|
else {
|
|
1569
1330
|
try {
|
|
1570
|
-
|
|
1331
|
+
liveMonitorEngine.reload();
|
|
1571
1332
|
}
|
|
1572
1333
|
catch (err) {
|
|
1573
1334
|
log('ERROR', `Monitor engine reload failed: ${err.message}`);
|
|
@@ -1576,39 +1337,28 @@ export async function runDaemon() {
|
|
|
1576
1337
|
}
|
|
1577
1338
|
};
|
|
1578
1339
|
// Structurally single-shot (RUSH-2423). Shutdown is reachable from SIGTERM,
|
|
1579
|
-
// SIGINT, and
|
|
1580
|
-
// and two of those can arrive together — a
|
|
1581
|
-
// daemon whose state dir was just removed.
|
|
1582
|
-
// before (every step inside happens to be
|
|
1583
|
-
// single-shot a property of the function
|
|
1584
|
-
// added later has to re-earn.
|
|
1340
|
+
// SIGINT, and StateDirCheckService's independent `onMissing` callback (which
|
|
1341
|
+
// calls this same handler), and two of those can arrive together — a
|
|
1342
|
+
// service manager that SIGTERMs a daemon whose state dir was just removed.
|
|
1343
|
+
// It was only INCIDENTALLY safe before (every step inside happens to be
|
|
1344
|
+
// idempotent); the guard makes single-shot a property of the function
|
|
1345
|
+
// rather than one that every step added later has to re-earn.
|
|
1585
1346
|
const handleShutdown = singleShot(async () => {
|
|
1586
1347
|
log('INFO', 'Daemon shutting down');
|
|
1587
|
-
accountStateService?.stop();
|
|
1588
1348
|
clearInterval(activeSessionsWarmInterval);
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1349
|
+
stopActiveSessionsReaderWatch();
|
|
1350
|
+
// supervisor.stopAll() stops every registered service: secrets-broker
|
|
1351
|
+
// (closes hostedBroker + self-heal timer), monitor engine, account-state,
|
|
1352
|
+
// browser IPC, session-index, watchdog, device-probe, self-heal,
|
|
1353
|
+
// keychain-reap, and (once registered just below) state-dir-check.
|
|
1354
|
+
await supervisor.stopAll();
|
|
1355
|
+
activeServiceSupervisor = null;
|
|
1594
1356
|
stopScheduler();
|
|
1595
|
-
monitorEngine?.stop();
|
|
1596
|
-
await browserIPC?.stop();
|
|
1597
1357
|
await webhookReceivers?.close();
|
|
1598
1358
|
clearInterval(monitorInterval);
|
|
1599
|
-
if (healInterval)
|
|
1600
|
-
clearInterval(healInterval);
|
|
1601
|
-
if (healKickoff)
|
|
1602
|
-
clearTimeout(healKickoff);
|
|
1603
|
-
if (brokerSelfHealInterval)
|
|
1604
|
-
clearInterval(brokerSelfHealInterval);
|
|
1605
|
-
if (keychainReapInterval)
|
|
1606
|
-
clearInterval(keychainReapInterval);
|
|
1607
1359
|
clearInterval(deadPaneReapInterval);
|
|
1608
1360
|
if (browserTaskReapInterval)
|
|
1609
1361
|
clearInterval(browserTaskReapInterval);
|
|
1610
|
-
if (stateDirCheckInterval)
|
|
1611
|
-
clearInterval(stateDirCheckInterval);
|
|
1612
1362
|
try {
|
|
1613
1363
|
if (fs.readFileSync(lifetimePath, 'utf-8') === lifetimeToken)
|
|
1614
1364
|
fs.unlinkSync(lifetimePath);
|
|
@@ -1616,12 +1366,30 @@ export async function runDaemon() {
|
|
|
1616
1366
|
catch {
|
|
1617
1367
|
// Already removed with the state dir, or replaced by a newer owner.
|
|
1618
1368
|
}
|
|
1619
|
-
hostedBroker?.close();
|
|
1620
1369
|
removeDaemonPid();
|
|
1621
1370
|
removeHeartbeat();
|
|
1622
1371
|
unregisterDaemonInstance();
|
|
1623
1372
|
process.exit(0);
|
|
1624
1373
|
});
|
|
1374
|
+
// State-dir self-check (RUSH-2367 self-terminate guard) is registered on
|
|
1375
|
+
// the supervisor here — AFTER `handleShutdown` above — rather than
|
|
1376
|
+
// alongside watchdog/device-probe/self-heal/keychain-reap earlier. The
|
|
1377
|
+
// supervisor fires an immediate first tick on `register()`+`start()`; doing
|
|
1378
|
+
// that before `handleShutdown` exists would reference the const in its
|
|
1379
|
+
// temporal dead zone the moment a mismatch is ever detected. Registering it
|
|
1380
|
+
// here, once `handleShutdown` is a real function, removes that risk
|
|
1381
|
+
// entirely rather than relying on the marker always matching on tick one.
|
|
1382
|
+
if (isEnabled('state-dir-check')) {
|
|
1383
|
+
supervisor.register(new StateDirCheckService({
|
|
1384
|
+
lifetimePath,
|
|
1385
|
+
lifetimeToken,
|
|
1386
|
+
onMissing: () => { void handleShutdown(); },
|
|
1387
|
+
}));
|
|
1388
|
+
await supervisor.start('state-dir-check');
|
|
1389
|
+
}
|
|
1390
|
+
else {
|
|
1391
|
+
log('INFO', 'State-dir self-check disabled');
|
|
1392
|
+
}
|
|
1625
1393
|
process.on('SIGHUP', handleReload);
|
|
1626
1394
|
process.on('SIGTERM', () => handleShutdown());
|
|
1627
1395
|
process.on('SIGINT', () => handleShutdown());
|