@phnx-labs/agents-cli 1.22.116 → 1.22.118
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 +247 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/browser.d.ts +52 -70
- package/dist/commands/browser.js +192 -3562
- package/dist/commands/daemon.d.ts +39 -2
- package/dist/commands/daemon.js +72 -73
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.d.ts +18 -0
- package/dist/commands/exec.js +104 -12
- package/dist/commands/repo.js +13 -49
- package/dist/commands/route.js +5 -5
- package/dist/commands/routines.js +39 -12
- package/dist/commands/sessions-render.d.ts +1 -1
- package/dist/commands/sessions-render.js +0 -1
- package/dist/commands/sessions.test-fixture.d.ts +0 -1
- package/dist/commands/sessions.test-fixture.js +0 -37
- package/dist/commands/setup-browser.d.ts +17 -12
- package/dist/commands/setup-browser.js +52 -87
- package/dist/commands/setup-preferences.d.ts +0 -17
- package/dist/commands/setup-preferences.js +18 -49
- package/dist/commands/setup-term.d.ts +4 -3
- package/dist/commands/setup-term.js +5 -4
- package/dist/commands/setup.js +11 -11
- package/dist/commands/ssh.js +10 -1
- package/dist/commands/sync.js +6 -44
- package/dist/commands/teams.js +0 -1
- package/dist/commands/update.d.ts +19 -0
- package/dist/commands/update.js +34 -6
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +47 -2
- package/dist/index.js +107 -26
- package/dist/lib/account-capabilities.js +0 -2
- package/dist/lib/account-catalog.d.ts +38 -0
- package/dist/lib/account-catalog.js +110 -26
- package/dist/lib/account-provider-registry.js +1 -1
- package/dist/lib/account-registry.d.ts +1 -1
- package/dist/lib/account-registry.js +36 -2
- package/dist/lib/accounting/rotate.js +22 -8
- package/dist/lib/accounting/usage-ingest.d.ts +6 -1
- package/dist/lib/accounting/usage-ingest.js +115 -45
- package/dist/lib/accounting/usage-sync.d.ts +134 -31
- package/dist/lib/accounting/usage-sync.js +227 -66
- package/dist/lib/accounting/usage.d.ts +16 -3
- package/dist/lib/accounting/usage.js +26 -8
- package/dist/lib/accounts/slots.d.ts +8 -0
- package/dist/lib/accounts/slots.js +17 -0
- package/dist/lib/add-dir.js +0 -1
- package/dist/lib/agent-cli-commands.js +0 -1
- package/dist/lib/agent-spec/agents.d.ts +6 -7
- package/dist/lib/agent-spec/agents.js +6 -60
- package/dist/lib/auth-health.d.ts +69 -1
- package/dist/lib/auth-health.js +133 -4
- package/dist/lib/browser/context.d.ts +82 -0
- package/dist/lib/browser/context.js +88 -0
- package/dist/lib/browser/paths.d.ts +25 -0
- package/dist/lib/browser/paths.js +51 -0
- package/dist/lib/browser/record.d.ts +38 -0
- package/dist/lib/browser/record.js +57 -0
- package/dist/lib/browser/sessions-list.js +1 -2
- package/dist/lib/browser-client.d.ts +158 -0
- package/dist/lib/browser-client.js +234 -0
- package/dist/lib/channels/owner-sink.d.ts +1 -18
- package/dist/lib/channels/owner-sink.js +29 -82
- package/dist/lib/channels/providers/rush.d.ts +7 -7
- package/dist/lib/channels/providers/rush.js +127 -57
- package/dist/lib/claude-statusline.d.ts +1 -1
- package/dist/lib/claude-statusline.js +15 -3
- package/dist/lib/commands.js +1 -1
- package/dist/lib/crabbox/lease.js +1 -1
- package/dist/lib/crabbox/runtimes.js +1 -3
- package/dist/lib/daemon/account-state-daemon-service.js +1 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
- package/dist/lib/daemon/auth-sync-service.js +37 -43
- package/dist/lib/daemon/daemon.d.ts +25 -14
- package/dist/lib/daemon/daemon.js +92 -154
- package/dist/lib/daemon/runner.d.ts +2 -2
- package/dist/lib/daemon/runner.js +32 -9
- package/dist/lib/daemon/self-update-service.d.ts +35 -34
- package/dist/lib/daemon/self-update-service.js +40 -36
- package/dist/lib/daemon/service.d.ts +12 -8
- package/dist/lib/daemon/supervisor.d.ts +65 -54
- package/dist/lib/daemon/supervisor.js +136 -171
- package/dist/lib/daemon/testdata/process-view-start.js +5 -2
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
- package/dist/lib/daemon/usage-sync-service.js +46 -78
- package/dist/lib/daemon-health.d.ts +22 -5
- package/dist/lib/daemon-health.js +47 -3
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +0 -10
- package/dist/lib/daemon-ticks.js +4 -2
- package/dist/lib/devices/doctor-findings.js +8 -11
- package/dist/lib/devices/harness-inventory.js +14 -5
- package/dist/lib/exec-account-home.js +5 -0
- package/dist/lib/exec.js +0 -11
- package/dist/lib/feed/events.js +0 -1
- package/dist/lib/feed/tool-activity.js +5 -2
- package/dist/lib/fleet-shared-state.d.ts +45 -0
- package/dist/lib/fleet-shared-state.js +78 -9
- package/dist/lib/fs-atomic.js +25 -0
- package/dist/lib/harness-auth-capabilities.js +0 -1
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hooks/cache.d.ts +7 -0
- package/dist/lib/hooks/cache.js +27 -2
- package/dist/lib/hooks/install.js +7 -1
- package/dist/lib/hosts/ready.d.ts +38 -8
- package/dist/lib/hosts/ready.js +62 -14
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/installations/active-check.d.ts +27 -0
- package/dist/lib/installations/active-check.js +61 -0
- package/dist/lib/installations/migrate.js +6 -3
- package/dist/lib/installations/shims.js +9 -7
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +12 -1
- package/dist/lib/manifest.js +1 -1
- package/dist/lib/models.js +0 -2
- package/dist/lib/open-url.js +28 -54
- package/dist/lib/placement.d.ts +2 -0
- package/dist/lib/placement.js +5 -0
- package/dist/lib/profiles.js +0 -2
- package/dist/lib/refresh.js +1 -1
- package/dist/lib/routine-readiness.d.ts +35 -0
- package/dist/lib/routine-readiness.js +74 -16
- package/dist/lib/rules/compile.js +6 -0
- package/dist/lib/sandbox.js +0 -2
- package/dist/lib/secrets-policy.d.ts +89 -11
- package/dist/lib/secrets-policy.js +175 -28
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +65 -18
- package/dist/lib/session/discover.js +1 -283
- package/dist/lib/session/mirror.d.ts +4 -3
- package/dist/lib/session/mirror.js +4 -3
- package/dist/lib/session/parse.d.ts +0 -2
- package/dist/lib/session/parse.js +1 -138
- package/dist/lib/session/throughput.d.ts +1 -3
- package/dist/lib/session/throughput.js +0 -20
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +2 -2
- package/dist/lib/sessions-client.d.ts +60 -1
- package/dist/lib/sessions-client.js +227 -4
- package/dist/lib/setup-tool-status.js +8 -12
- package/dist/lib/signin-badge.d.ts +3 -2
- package/dist/lib/signin-badge.js +6 -3
- package/dist/lib/smart-launch.js +4 -1
- package/dist/lib/staleness/detectors/commands.d.ts +1 -1
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
- package/dist/lib/staleness/detectors/rules.d.ts +1 -1
- package/dist/lib/staleness/detectors/skills.d.ts +1 -1
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.d.ts +1 -1
- package/dist/lib/staleness/writers/hooks.d.ts +1 -1
- package/dist/lib/staleness/writers/mcp.d.ts +1 -1
- package/dist/lib/staleness/writers/permissions.d.ts +1 -1
- package/dist/lib/staleness/writers/plugins.d.ts +1 -1
- package/dist/lib/staleness/writers/rules.d.ts +1 -1
- package/dist/lib/staleness/writers/skills.d.ts +1 -1
- package/dist/lib/staleness/writers/sources.js +5 -0
- package/dist/lib/staleness/writers/subagents.d.ts +1 -1
- package/dist/lib/staleness/writers/workflows.d.ts +1 -1
- package/dist/lib/state.d.ts +6 -4
- package/dist/lib/state.js +7 -17
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +1 -136
- package/dist/lib/teams/placement-probe.js +50 -15
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/types.d.ts +10 -1
- package/dist/lib/types.js +1 -1
- package/dist/lib/view-types.d.ts +32 -0
- package/dist/lib/watchdog/read.js +0 -1
- package/dist/lib/watchdog/rotate.js +1 -2
- package/dist/lib/watchdog/runner.js +2 -2
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdogTail.js +0 -31
- package/package.json +1 -1
- package/scripts/postinstall.js +8 -8
- package/dist/browser.d.ts +0 -2
- package/dist/browser.js +0 -17
- package/dist/commands/browser-picker.d.ts +0 -18
- package/dist/commands/browser-picker.js +0 -97
- package/dist/lib/browser/arc-discovery.d.ts +0 -52
- package/dist/lib/browser/arc-discovery.js +0 -197
- package/dist/lib/browser/arc-dom.d.ts +0 -14
- package/dist/lib/browser/arc-dom.js +0 -121
- package/dist/lib/browser/caller-identity.d.ts +0 -40
- package/dist/lib/browser/caller-identity.js +0 -175
- package/dist/lib/browser/cdp.d.ts +0 -51
- package/dist/lib/browser/cdp.js +0 -254
- package/dist/lib/browser/chrome.d.ts +0 -141
- package/dist/lib/browser/chrome.js +0 -704
- package/dist/lib/browser/chromium-discovery.d.ts +0 -28
- package/dist/lib/browser/chromium-discovery.js +0 -96
- package/dist/lib/browser/devices.d.ts +0 -34
- package/dist/lib/browser/devices.js +0 -61
- package/dist/lib/browser/domain-skills.d.ts +0 -71
- package/dist/lib/browser/domain-skills.js +0 -195
- package/dist/lib/browser/drivers/arc.d.ts +0 -40
- package/dist/lib/browser/drivers/arc.js +0 -276
- package/dist/lib/browser/drivers/firefox.d.ts +0 -99
- package/dist/lib/browser/drivers/firefox.js +0 -377
- package/dist/lib/browser/drivers/local.d.ts +0 -55
- package/dist/lib/browser/drivers/local.js +0 -266
- package/dist/lib/browser/drivers/ssh.d.ts +0 -120
- package/dist/lib/browser/drivers/ssh.js +0 -467
- package/dist/lib/browser/editor.d.ts +0 -3
- package/dist/lib/browser/editor.js +0 -50
- package/dist/lib/browser/ffmpeg.d.ts +0 -12
- package/dist/lib/browser/ffmpeg.js +0 -184
- package/dist/lib/browser/firefox-discovery.d.ts +0 -69
- package/dist/lib/browser/firefox-discovery.js +0 -162
- package/dist/lib/browser/har.d.ts +0 -85
- package/dist/lib/browser/har.js +0 -77
- package/dist/lib/browser/hygiene.d.ts +0 -97
- package/dist/lib/browser/hygiene.js +0 -153
- package/dist/lib/browser/index.d.ts +0 -5
- package/dist/lib/browser/index.js +0 -5
- package/dist/lib/browser/input.d.ts +0 -7
- package/dist/lib/browser/input.js +0 -92
- package/dist/lib/browser/ipc.d.ts +0 -204
- package/dist/lib/browser/ipc.js +0 -1453
- package/dist/lib/browser/login-detection.d.ts +0 -87
- package/dist/lib/browser/login-detection.js +0 -274
- package/dist/lib/browser/profiles.d.ts +0 -422
- package/dist/lib/browser/profiles.js +0 -1157
- package/dist/lib/browser/refs.d.ts +0 -89
- package/dist/lib/browser/refs.js +0 -191
- package/dist/lib/browser/registry.d.ts +0 -70
- package/dist/lib/browser/registry.js +0 -252
- package/dist/lib/browser/remote-control.d.ts +0 -59
- package/dist/lib/browser/remote-control.js +0 -85
- package/dist/lib/browser/resolve-target.d.ts +0 -66
- package/dist/lib/browser/resolve-target.js +0 -264
- package/dist/lib/browser/runtime-state.d.ts +0 -291
- package/dist/lib/browser/runtime-state.js +0 -584
- package/dist/lib/browser/secret-ref.d.ts +0 -10
- package/dist/lib/browser/secret-ref.js +0 -14
- package/dist/lib/browser/service.d.ts +0 -812
- package/dist/lib/browser/service.js +0 -4693
- package/dist/lib/browser/stream.d.ts +0 -17
- package/dist/lib/browser/stream.js +0 -72
- package/dist/lib/browser/task-index.d.ts +0 -78
- package/dist/lib/browser/task-index.js +0 -199
- package/dist/lib/browser/types.d.ts +0 -647
- package/dist/lib/browser/types.js +0 -61
- package/dist/lib/browser/upload.d.ts +0 -29
- package/dist/lib/browser/upload.js +0 -298
- package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
- package/dist/lib/daemon/browser-ipc-service.js +0 -46
- package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
- package/dist/lib/daemon/browser-task-reap-service.js +0 -26
- package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
- package/dist/lib/fleet-shared-repo-sync.js +0 -490
|
@@ -51,9 +51,10 @@ export declare function daemonServiceLabel(): string;
|
|
|
51
51
|
export declare function daemonSystemdUnitName(): string;
|
|
52
52
|
/**
|
|
53
53
|
* How many consecutive failed daemon starts disable the *implicit* auto-start
|
|
54
|
-
* (`ensureDaemonStarted`).
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* (`ensureDaemonStarted`). This bounds foreground-command auto-starts only; the
|
|
55
|
+
* OS-level unit restart is deliberately unbounded (`StartLimitIntervalSec=0`) so
|
|
56
|
+
* a wedged daemon always recovers (PHNX-4116). `agents daemon start` is the
|
|
57
|
+
* deliberate override and is never gated by this.
|
|
57
58
|
*/
|
|
58
59
|
export declare const DAEMON_AUTOSTART_FAILURE_LIMIT = 5;
|
|
59
60
|
/**
|
|
@@ -111,7 +112,6 @@ interface DaemonHeartbeat {
|
|
|
111
112
|
export declare function writeHeartbeat(pid?: number): void;
|
|
112
113
|
export declare function readHeartbeat(): DaemonHeartbeat | null;
|
|
113
114
|
export declare function removeHeartbeat(): void;
|
|
114
|
-
export declare function isDaemonWedged(): boolean;
|
|
115
115
|
/**
|
|
116
116
|
* Check whether a daemon is alive — via the pid file, or a fresh heartbeat when
|
|
117
117
|
* the pid file has been lost (see resolveLiveDaemonPid). The observation itself
|
|
@@ -349,7 +349,7 @@ export declare function ensureDaemonStarted(): {
|
|
|
349
349
|
* console-owning wrapper (cmd.exe / the npm shim). When that wrapper exits it
|
|
350
350
|
* closes its console, and the detached daemon sharing that console receives a
|
|
351
351
|
* console-close event that trips its shutdown handler — the daemon comes up,
|
|
352
|
-
* binds
|
|
352
|
+
* binds its sockets, then tears itself down ~36ms later (#556).
|
|
353
353
|
* Going through `process.execPath` means a real PE/binary is spawned with
|
|
354
354
|
* `detached: true` and no console, so nothing signals the daemon after launch.
|
|
355
355
|
*
|
|
@@ -452,24 +452,35 @@ export declare function findSurvivingStateDirDaemons(exclude: Set<number>): numb
|
|
|
452
452
|
* from launchd/systemd if applicable.
|
|
453
453
|
*
|
|
454
454
|
* The SIGTERM → grace → killTree sequence is unchanged; what it adds is
|
|
455
|
-
* verification
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* left stale; this reclaims it (the owner is provably dead) and reports it. It
|
|
461
|
-
* never reports success on an unverified stop.
|
|
455
|
+
* verification: after the daemon is gone it checks that no `__daemon-run` for
|
|
456
|
+
* THIS state dir survives and reclaims the state files a killTree escalation
|
|
457
|
+
* (which skips the graceful handleShutdown) can leave stale. The browser IPC
|
|
458
|
+
* socket is no longer among them — it left with the standalone `browser` CLI
|
|
459
|
+
* (PHNX-4101). It never reports success on an unverified stop.
|
|
462
460
|
*/
|
|
463
461
|
export declare function stopDaemon(): DaemonStopResult;
|
|
464
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* Get current daemon status including running state, PID, enabled job count, and
|
|
464
|
+
* the supervised-restart history `agents daemon status` renders (PHNX-4116).
|
|
465
|
+
*
|
|
466
|
+
* There is no `wedged` state: a stalled daemon exits for a supervised
|
|
467
|
+
* systemd/launchd restart rather than sitting unresponsive, so the daemon is
|
|
468
|
+
* simply `running` or `stopped`, and its recent restarts are reported instead.
|
|
469
|
+
*/
|
|
465
470
|
export declare function getDaemonStatus(): {
|
|
466
|
-
state: 'running' | '
|
|
471
|
+
state: 'running' | 'stopped';
|
|
467
472
|
running: boolean;
|
|
468
473
|
pid: number | null;
|
|
469
474
|
jobCount: number;
|
|
470
475
|
logPath: string;
|
|
471
476
|
binaryPath: string | null;
|
|
472
477
|
heartbeat: DaemonHeartbeat | null;
|
|
478
|
+
/** Supervised restarts in the last 24h (a service breaching its deadline exits the daemon for an OS restart). */
|
|
479
|
+
restarts24h: number;
|
|
480
|
+
/** The most recent supervised-restart cause, or null if none in the last 24h. */
|
|
481
|
+
lastRestartCause: string | null;
|
|
482
|
+
/** ISO timestamp of the most recent supervised restart in the last 24h, or null. */
|
|
483
|
+
lastRestartAt: string | null;
|
|
473
484
|
};
|
|
474
485
|
/** Read the daemon log, optionally limited to the last N lines. */
|
|
475
486
|
export declare function readDaemonLog(lines?: number): string;
|
|
@@ -23,12 +23,11 @@ import { detectOverdueJobs, notifyOverdue } from '../overdue.js';
|
|
|
23
23
|
import { runCatchup } from '../catchup.js';
|
|
24
24
|
import { notifyRoutineStart, notifyRoutineFinish, notifyRoutineStartFailed } from '../routine-notify.js';
|
|
25
25
|
import { notifyOwnerRoutineFinish, notifyOwnerRoutineStartFailed } from '../routine-notify-owner.js';
|
|
26
|
-
import { getSocketPath as getBrowserIpcSocketPath } from '../browser/ipc.js';
|
|
27
26
|
import { redactSecrets } from '../redact.js';
|
|
28
27
|
import { getAgentsBinPath, getCliLaunch, BUN_VIRTUAL_ROOT } from '../cli-entry.js';
|
|
29
28
|
import { localBinDir } from '../platform/posixpath.js';
|
|
30
29
|
import { isSchedulerEnabled, assertSchedulerEnabled, isDaemonEnabled } from '../device-config.js';
|
|
31
|
-
import { recordSubsystemOk, recordSubsystemError, recordSubsystemErrorReason, readSubsystemHealth, SUBSYSTEM_DAEMON_START } from '../daemon-health.js';
|
|
30
|
+
import { recordSubsystemOk, recordSubsystemError, recordSubsystemErrorReason, readSubsystemHealth, readRecentDaemonRestarts, SUBSYSTEM_DAEMON_START } from '../daemon-health.js';
|
|
32
31
|
import { ServiceSupervisor } from './supervisor.js';
|
|
33
32
|
import { emit, emitAsync, emitRoutineEnd } from '../feed/events.js';
|
|
34
33
|
import { readDaemonServicesConfig, drainDaemonServiceRestartQueue } from '../daemon-services.js';
|
|
@@ -40,8 +39,8 @@ import { sleepSync } from '../fs-atomic.js';
|
|
|
40
39
|
* survive across processes goes through `daemon-health.ts` instead (which
|
|
41
40
|
* `ServiceSupervisor` already writes on every tick). This getter exists so a
|
|
42
41
|
* FUTURE same-process consumer (e.g. a `daemon services` live-status IPC
|
|
43
|
-
* handler) can read the supervisor's
|
|
44
|
-
*
|
|
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.
|
|
45
44
|
*/
|
|
46
45
|
let activeServiceSupervisor = null;
|
|
47
46
|
/** Health for every service currently registered on the live supervisor, or `null` if the daemon isn't running in this process. */
|
|
@@ -127,31 +126,33 @@ export function daemonSystemdUnitName() {
|
|
|
127
126
|
const WEDGE_THRESHOLD_TICKS = 3;
|
|
128
127
|
const DAEMON_HEARTBEAT_TICK_MS = 60_000;
|
|
129
128
|
/**
|
|
130
|
-
* Crash-loop
|
|
131
|
-
*
|
|
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
132
|
*
|
|
133
|
-
* 1. **The OS supervisor paces the respawn.**
|
|
134
|
-
* `
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* re-entering the loop from the other direction — a foreground command
|
|
145
|
-
* calls `ensureDaemonStarted()` on every invocation.
|
|
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.
|
|
146
148
|
*/
|
|
147
149
|
const DAEMON_THROTTLE_SECONDS = 30;
|
|
148
|
-
const DAEMON_START_LIMIT_INTERVAL_SECONDS = 300;
|
|
149
|
-
const DAEMON_START_LIMIT_BURST = 5;
|
|
150
150
|
/**
|
|
151
151
|
* How many consecutive failed daemon starts disable the *implicit* auto-start
|
|
152
|
-
* (`ensureDaemonStarted`).
|
|
153
|
-
*
|
|
154
|
-
*
|
|
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.
|
|
155
156
|
*/
|
|
156
157
|
export const DAEMON_AUTOSTART_FAILURE_LIMIT = 5;
|
|
157
158
|
export function schedulerGateTransition(running, enabled) {
|
|
@@ -338,28 +339,19 @@ export function removeHeartbeat() {
|
|
|
338
339
|
catch { /* already removed */ }
|
|
339
340
|
}
|
|
340
341
|
/**
|
|
341
|
-
* A heartbeat is "fresh" when its last tick falls inside the
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
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`.
|
|
345
350
|
*/
|
|
346
351
|
function isHeartbeatFresh(hb) {
|
|
347
352
|
const elapsed = Date.now() - Date.parse(hb.lastTick);
|
|
348
353
|
return elapsed <= WEDGE_THRESHOLD_TICKS * DAEMON_HEARTBEAT_TICK_MS;
|
|
349
354
|
}
|
|
350
|
-
export function isDaemonWedged() {
|
|
351
|
-
const pid = readDaemonPid();
|
|
352
|
-
if (!pid)
|
|
353
|
-
return false;
|
|
354
|
-
if (!isLiveDaemon(pid))
|
|
355
|
-
return false;
|
|
356
|
-
const hb = readHeartbeat();
|
|
357
|
-
if (!hb)
|
|
358
|
-
return false;
|
|
359
|
-
if (hb.pid !== pid)
|
|
360
|
-
return false;
|
|
361
|
-
return !isHeartbeatFresh(hb);
|
|
362
|
-
}
|
|
363
355
|
/** How long stopDaemon waits for a SIGTERMed daemon to exit before escalating. */
|
|
364
356
|
const STOP_GRACE_MS = 5000;
|
|
365
357
|
/** How long it waits after the hard tree-kill before giving up. */
|
|
@@ -488,10 +480,11 @@ export function claimDaemonInstance() {
|
|
|
488
480
|
const existing = resolveLiveDaemonPid(true);
|
|
489
481
|
if (existing !== null && existing !== process.pid) {
|
|
490
482
|
// Evict, and WAIT for the incumbent to be provably dead — its graceful
|
|
491
|
-
// handleShutdown releasing
|
|
492
|
-
// pid and (later, in runDaemon) bind our own.
|
|
493
|
-
// recreates the two-owners-on-one-socket orphan
|
|
494
|
-
// below, so the pid file is not written until the
|
|
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.
|
|
495
488
|
if (!evictIncumbentDaemon(existing))
|
|
496
489
|
return false;
|
|
497
490
|
}
|
|
@@ -504,10 +497,11 @@ export function claimDaemonInstance() {
|
|
|
504
497
|
}
|
|
505
498
|
/**
|
|
506
499
|
* SIGTERM a live incumbent daemon and block until it is provably dead, so its
|
|
507
|
-
* graceful handleShutdown has released
|
|
508
|
-
* newcomer binds anything of its own (SING-11) —
|
|
509
|
-
* secrets broker socket
|
|
510
|
-
* PHNX-3989 OWN-1)
|
|
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
|
|
511
505
|
* to killTree after the grace window. Passes the POSITIVE pid so the kill reaches
|
|
512
506
|
* only the incumbent daemon — never its detached routine children, which run in
|
|
513
507
|
* their own process groups and must survive takeover (SING-11a); the new daemon
|
|
@@ -878,15 +872,13 @@ export async function runDaemon() {
|
|
|
878
872
|
assertTestDaemonHome();
|
|
879
873
|
// Lifecycle readers and launchers do not run services. Load their code only
|
|
880
874
|
// in the daemon process, before it claims or publishes lifecycle state.
|
|
881
|
-
const [{
|
|
882
|
-
import('../browser/service.js'),
|
|
875
|
+
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([
|
|
883
876
|
import('./session-index-service.js'),
|
|
884
877
|
import('./session-summarizer-service.js'),
|
|
885
878
|
import('./session-title-service.js'),
|
|
886
879
|
import('./monitor-engine-service.js'),
|
|
887
880
|
import('./account-state-daemon-service.js'),
|
|
888
881
|
import('./catchup-service.js'),
|
|
889
|
-
import('./browser-ipc-service.js'),
|
|
890
882
|
import('./watchdog-service.js'),
|
|
891
883
|
import('./device-probe-service.js'),
|
|
892
884
|
import('./self-heal-service.js'),
|
|
@@ -901,7 +893,6 @@ export async function runDaemon() {
|
|
|
901
893
|
import('./webhook-receiver-service.js'),
|
|
902
894
|
import('./heartbeat-service.js'),
|
|
903
895
|
import('./tmux-reap-service.js'),
|
|
904
|
-
import('./browser-task-reap-service.js'),
|
|
905
896
|
]);
|
|
906
897
|
// Install the shared-daemon reload signal boundary BEFORE publishing our PID
|
|
907
898
|
// in claimDaemonInstance(). Browser/routines clients use that PID to decide a
|
|
@@ -1011,11 +1002,11 @@ export async function runDaemon() {
|
|
|
1011
1002
|
catch (err) {
|
|
1012
1003
|
log('ERROR', `Stray daemon reaper failed: ${err.message}`);
|
|
1013
1004
|
}
|
|
1014
|
-
// Socket services: monitor engine, account-state, and
|
|
1015
|
-
// managed by the ServiceSupervisor (RUSH-3193 P2). The secrets broker moved
|
|
1016
|
-
// with the standalone `secrets` engine (PHNX-3989 OWN-1)
|
|
1017
|
-
//
|
|
1018
|
-
// lifecycle exclusively.
|
|
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.
|
|
1019
1010
|
const supervisor = new ServiceSupervisor();
|
|
1020
1011
|
if (isEnabled('session-state')) {
|
|
1021
1012
|
supervisor.register(new SessionStateService(() => supervisor.runNow('session-state')));
|
|
@@ -1023,8 +1014,7 @@ export async function runDaemon() {
|
|
|
1023
1014
|
else
|
|
1024
1015
|
log('INFO', 'Live session-state service disabled');
|
|
1025
1016
|
// The shared feed fan-out. Registered even when disabled at boot so a later
|
|
1026
|
-
// `agents daemon services enable feed-stream` brings it up over SIGHUP
|
|
1027
|
-
// same live-transition shape browser IPC uses.
|
|
1017
|
+
// `agents daemon services enable feed-stream` brings it up over SIGHUP.
|
|
1028
1018
|
supervisor.register(new FeedStreamService(), { enabled: isEnabled('feed-stream') });
|
|
1029
1019
|
if (!isEnabled('feed-stream'))
|
|
1030
1020
|
log('INFO', 'Shared feed stream service disabled');
|
|
@@ -1034,8 +1024,8 @@ export async function runDaemon() {
|
|
|
1034
1024
|
else
|
|
1035
1025
|
log('INFO', 'Monitor engine disabled');
|
|
1036
1026
|
// Usage and auth refresh are two INDEPENDENT supervised services (PHNX-3608)
|
|
1037
|
-
// so a run of usage-refresh
|
|
1038
|
-
// slower auth refresh — each
|
|
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.
|
|
1039
1029
|
if (isEnabled('account-state'))
|
|
1040
1030
|
supervisor.register(new AccountUsageService());
|
|
1041
1031
|
else
|
|
@@ -1066,14 +1056,11 @@ export async function runDaemon() {
|
|
|
1066
1056
|
else {
|
|
1067
1057
|
log('INFO', 'Catch-up recovery service disabled');
|
|
1068
1058
|
}
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
//
|
|
1073
|
-
|
|
1074
|
-
supervisor.register(new BrowserIPCService(browserService), { enabled: isEnabled('browser-ipc') });
|
|
1075
|
-
if (!isEnabled('browser-ipc'))
|
|
1076
|
-
log('INFO', 'Browser IPC service disabled');
|
|
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`.
|
|
1077
1064
|
if (isEnabled('session-index'))
|
|
1078
1065
|
supervisor.register(new SessionIndexService());
|
|
1079
1066
|
else
|
|
@@ -1143,17 +1130,11 @@ export async function runDaemon() {
|
|
|
1143
1130
|
supervisor.register(new TmuxReapService());
|
|
1144
1131
|
else
|
|
1145
1132
|
log('INFO', 'Tmux reap service disabled');
|
|
1146
|
-
if (isEnabled('browser-task-reap')) {
|
|
1147
|
-
supervisor.register(new BrowserTaskReapService(browserService));
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
log('INFO', 'Browser-task reap service disabled');
|
|
1151
|
-
}
|
|
1152
1133
|
await supervisor.startAll({ log });
|
|
1153
1134
|
activeServiceSupervisor = supervisor;
|
|
1154
1135
|
// scheduler.enabled=false in this machine's device doc means NO routines fire
|
|
1155
1136
|
// here — the scheduler and its catchup recovery simply never start, while the
|
|
1156
|
-
// daemon keeps its other duties (
|
|
1137
|
+
// daemon keeps its other duties (feed stream, session sync).
|
|
1157
1138
|
// The refusal message is the same one the start surfaces
|
|
1158
1139
|
// (`routines add` auto-start, manual `routines start`) raise. The gate is
|
|
1159
1140
|
// re-evaluated on every SIGHUP reload (handleReload below) via
|
|
@@ -1353,14 +1334,14 @@ export async function runDaemon() {
|
|
|
1353
1334
|
}
|
|
1354
1335
|
catch (err) {
|
|
1355
1336
|
// Ordinary pass errors are logged, not re-thrown: a transient catchup
|
|
1356
|
-
// failure should not
|
|
1357
|
-
// CatchupService deadline aborts the tick and the supervisor
|
|
1358
|
-
//
|
|
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).
|
|
1359
1340
|
log('ERROR', `Catchup pass failed: ${err.message}`);
|
|
1360
1341
|
}
|
|
1361
1342
|
}
|
|
1362
|
-
//
|
|
1363
|
-
//
|
|
1343
|
+
// The browser IPC server and its orphan reap left this daemon with the
|
|
1344
|
+
// standalone `browser` CLI (PHNX-4101) — it hosts its own IPC service now.
|
|
1364
1345
|
// Webhook receivers: signed webhook receiver(s) + their funnel (RUSH-2548).
|
|
1365
1346
|
// Resolves each receiver's signing secret headlessly through the standalone
|
|
1366
1347
|
// `secrets` CLI (an agentOnly secrets-client read) — no AGENTS_SECRETS_PASSPHRASE,
|
|
@@ -1379,15 +1360,13 @@ export async function runDaemon() {
|
|
|
1379
1360
|
// RUSH-2501: reap tmux sessions whose panes are all dead. Daemon-only
|
|
1380
1361
|
// (single executor). Dead managed panes and their orphan helpers are
|
|
1381
1362
|
// reaped by TmuxReapService.
|
|
1382
|
-
//
|
|
1383
|
-
//
|
|
1384
|
-
// Abandoned browser tasks are reaped by BrowserTaskReapService, sharing the
|
|
1385
|
-
// same BrowserService instance as BrowserIPCService.
|
|
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.
|
|
1386
1365
|
// RUSH-2367 / RUSH-3193 P3: state-dir-check (self-terminate guard) is
|
|
1387
1366
|
// registered on the supervisor further below, once `handleShutdown` exists
|
|
1388
1367
|
// — see the registration site after its declaration for why.
|
|
1389
|
-
// RUSH-2418: startup is over — the scheduler,
|
|
1390
|
-
//
|
|
1368
|
+
// RUSH-2418: startup is over — the scheduler, feed stream, monitor engine and
|
|
1369
|
+
// every background tick are up. Only NOW does this daemon
|
|
1391
1370
|
// clear the auto-start failure streak `ensureDaemonStarted` reads. Clearing it
|
|
1392
1371
|
// at claim time instead would reset the breaker for a process that dies while
|
|
1393
1372
|
// initializing a subsystem, which is exactly the crash loop it exists to stop.
|
|
@@ -1670,14 +1649,14 @@ export function generateSystemdUnit(agentsBin = getAgentsBinPath()) {
|
|
|
1670
1649
|
return `[Unit]
|
|
1671
1650
|
Description=Agents Daemon - Scheduled Job Runner
|
|
1672
1651
|
After=network.target
|
|
1673
|
-
StartLimitIntervalSec
|
|
1674
|
-
StartLimitBurst=${DAEMON_START_LIMIT_BURST}
|
|
1652
|
+
StartLimitIntervalSec=0
|
|
1675
1653
|
|
|
1676
1654
|
[Service]
|
|
1677
1655
|
Type=simple
|
|
1678
1656
|
ExecStart=${execStart}
|
|
1679
1657
|
Restart=always
|
|
1680
1658
|
RestartSec=${DAEMON_THROTTLE_SECONDS}
|
|
1659
|
+
KillMode=process
|
|
1681
1660
|
Environment=PATH=${daemonPathValue(agentsBin, ['/usr/local/bin', '/usr/bin', '/bin'])}
|
|
1682
1661
|
Environment=HOME=${home}
|
|
1683
1662
|
Environment=AGENTS_REAL_HOME=${realHome}
|
|
@@ -1989,7 +1968,7 @@ function startDaemonLocked(agentsBin, releaseLock) {
|
|
|
1989
1968
|
* console-owning wrapper (cmd.exe / the npm shim). When that wrapper exits it
|
|
1990
1969
|
* closes its console, and the detached daemon sharing that console receives a
|
|
1991
1970
|
* console-close event that trips its shutdown handler — the daemon comes up,
|
|
1992
|
-
* binds
|
|
1971
|
+
* binds its sockets, then tears itself down ~36ms later (#556).
|
|
1993
1972
|
* Going through `process.execPath` means a real PE/binary is spawned with
|
|
1994
1973
|
* `detached: true` and no console, so nothing signals the daemon after launch.
|
|
1995
1974
|
*
|
|
@@ -2279,13 +2258,11 @@ export function findSurvivingStateDirDaemons(exclude) {
|
|
|
2279
2258
|
* from launchd/systemd if applicable.
|
|
2280
2259
|
*
|
|
2281
2260
|
* The SIGTERM → grace → killTree sequence is unchanged; what it adds is
|
|
2282
|
-
* verification
|
|
2283
|
-
*
|
|
2284
|
-
*
|
|
2285
|
-
*
|
|
2286
|
-
*
|
|
2287
|
-
* left stale; this reclaims it (the owner is provably dead) and reports it. It
|
|
2288
|
-
* never reports success on an unverified stop.
|
|
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.
|
|
2289
2266
|
*/
|
|
2290
2267
|
export function stopDaemon() {
|
|
2291
2268
|
const releaseLock = acquireLifecycleLock();
|
|
@@ -2341,16 +2318,6 @@ function stopDaemonLocked() {
|
|
|
2341
2318
|
// excluded. resolveLiveDaemonPid(true) rejects a reused/non-daemon pid before
|
|
2342
2319
|
// any service-manager teardown or direct signal and repairs only under lock.
|
|
2343
2320
|
const pid = resolveLiveDaemonPid(true);
|
|
2344
|
-
const browserSock = process.platform === 'win32' ? null : getBrowserIpcSocketPath();
|
|
2345
|
-
// Capture the socket's inode INDEPENDENTLY of whether a live pid resolved
|
|
2346
|
-
// (PHNX-3618). A daemon that died between the CLI liveness precheck and this
|
|
2347
|
-
// locked read leaves resolveLiveDaemonPid() returning null while its ungraceful
|
|
2348
|
-
// exit left the binding on disk — capturing only when `pid !== null` meant that
|
|
2349
|
-
// genuinely stale socket could never be reclaimed and was reported "ownership
|
|
2350
|
-
// could not be verified". The inode is the successor guard: a fresh daemon that
|
|
2351
|
-
// rebinds during the stop gets a new inode, so a later identity match still
|
|
2352
|
-
// proves this exact binding is the one we captured, never a successor's.
|
|
2353
|
-
const browserSockOwner = browserSock ? readPathIdentity(browserSock) : null;
|
|
2354
2321
|
if (platform === 'darwin') {
|
|
2355
2322
|
const plistPath = getLaunchdPlistPath();
|
|
2356
2323
|
if (fs.existsSync(plistPath)) {
|
|
@@ -2476,49 +2443,9 @@ function stopDaemonLocked() {
|
|
|
2476
2443
|
else if (pid === null && currentPid !== null) {
|
|
2477
2444
|
surviving.push(`daemon pid registration changed to ${currentPid} during stop`);
|
|
2478
2445
|
}
|
|
2479
|
-
//
|
|
2480
|
-
//
|
|
2481
|
-
//
|
|
2482
|
-
// dead, so reclaim it and report.
|
|
2483
|
-
if (process.platform !== 'win32') {
|
|
2484
|
-
// A binding is provably stale — and reclaimable — when the inode on disk is
|
|
2485
|
-
// still the exact one captured under this lock AND no daemon (the signalled
|
|
2486
|
-
// target OR any successor for this state dir) survives to own it. Keying the
|
|
2487
|
-
// "a daemon still owns it" test on the whole survivor set rather than only the
|
|
2488
|
-
// captured target is what lets a daemon that died BEFORE this stop (pid ===
|
|
2489
|
-
// null, PHNX-3618) still have its stale socket reclaimed, while a live
|
|
2490
|
-
// successor — which either rebound the inode or shows up as a survivor — is
|
|
2491
|
-
// never touched (PHNX-3607's never-delete-a-successor invariant).
|
|
2492
|
-
const aDaemonSurvives = survivors.length > 0 || unverifiedSurvivors.length > 0;
|
|
2493
|
-
if (browserSock && fs.existsSync(browserSock)) {
|
|
2494
|
-
const ownsCapturedBinding = browserSockOwner !== null
|
|
2495
|
-
&& pathIdentityMatches(browserSock, browserSockOwner);
|
|
2496
|
-
if (ownsCapturedBinding && !aDaemonSurvives) {
|
|
2497
|
-
try {
|
|
2498
|
-
fs.unlinkSync(browserSock);
|
|
2499
|
-
}
|
|
2500
|
-
catch { /* failed to reclaim the captured binding */ }
|
|
2501
|
-
if (pathIdentityMatches(browserSock, browserSockOwner))
|
|
2502
|
-
surviving.push('browser IPC socket not released');
|
|
2503
|
-
else if (fs.existsSync(browserSock))
|
|
2504
|
-
surviving.push('browser IPC socket ownership changed during stop');
|
|
2505
|
-
else
|
|
2506
|
-
released.push('browser IPC socket (reclaimed)');
|
|
2507
|
-
}
|
|
2508
|
-
else if (aDaemonSurvives) {
|
|
2509
|
-
surviving.push('browser IPC socket still owned by a surviving daemon');
|
|
2510
|
-
}
|
|
2511
|
-
else {
|
|
2512
|
-
// The path was absent when this transaction captured the target (a
|
|
2513
|
-
// successor bound it afterward) or a successor replaced the inode. Either
|
|
2514
|
-
// way this invocation does not own the current binding and must leave it.
|
|
2515
|
-
surviving.push('browser IPC socket ownership could not be verified');
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
else {
|
|
2519
|
-
released.push('browser IPC socket');
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
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/`.
|
|
2522
2449
|
// ── The state files a killed daemon cannot clean up itself (RUSH-2421) ─────
|
|
2523
2450
|
// handleShutdown removes the lifetime marker, the heartbeat and this pid's
|
|
2524
2451
|
// instance-registry entry — but it only runs on the GRACEFUL path. Every
|
|
@@ -2556,10 +2483,16 @@ function stopDaemonLocked() {
|
|
|
2556
2483
|
detachedChildren,
|
|
2557
2484
|
};
|
|
2558
2485
|
}
|
|
2559
|
-
/**
|
|
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
|
+
*/
|
|
2560
2494
|
export function getDaemonStatus() {
|
|
2561
2495
|
const running = isDaemonRunning();
|
|
2562
|
-
const wedged = running && isDaemonWedged();
|
|
2563
2496
|
const pid = readDaemonPid();
|
|
2564
2497
|
let jobCount = 0;
|
|
2565
2498
|
try {
|
|
@@ -2571,14 +2504,19 @@ export function getDaemonStatus() {
|
|
|
2571
2504
|
binaryPath = getAgentsBinPath();
|
|
2572
2505
|
}
|
|
2573
2506
|
catch { /* resolution failed */ }
|
|
2507
|
+
const recentRestarts = readRecentDaemonRestarts(Date.now() - 24 * 60 * 60 * 1000);
|
|
2508
|
+
const lastRestart = recentRestarts.length > 0 ? recentRestarts[recentRestarts.length - 1] : null;
|
|
2574
2509
|
return {
|
|
2575
|
-
state:
|
|
2510
|
+
state: running ? 'running' : 'stopped',
|
|
2576
2511
|
running,
|
|
2577
2512
|
pid,
|
|
2578
2513
|
jobCount,
|
|
2579
2514
|
logPath: getDaemonLogPath(),
|
|
2580
2515
|
binaryPath,
|
|
2581
2516
|
heartbeat: readHeartbeat(),
|
|
2517
|
+
restarts24h: recentRestarts.length,
|
|
2518
|
+
lastRestartCause: lastRestart ? `${lastRestart.subsystem}: ${lastRestart.cause}` : null,
|
|
2519
|
+
lastRestartAt: lastRestart ? lastRestart.at : null,
|
|
2582
2520
|
};
|
|
2583
2521
|
}
|
|
2584
2522
|
/** Read the daemon log, optionally limited to the last N lines. */
|
|
@@ -87,8 +87,8 @@ export declare function routineSpawnCwd(config: Pick<JobConfig, 'name' | 'projec
|
|
|
87
87
|
* modeFlags.plan so claudeAdapter.routineModeArgs can splice
|
|
88
88
|
* plan → acceptEdits / auto / skip.
|
|
89
89
|
*
|
|
90
|
-
* Returns undefined when `agent` is outside ROUTINE_AGENT_IDS (
|
|
91
|
-
*
|
|
90
|
+
* Returns undefined when `agent` is outside ROUTINE_AGENT_IDS (grok and every
|
|
91
|
+
* other harness the daemon does not fire locally).
|
|
92
92
|
*/
|
|
93
93
|
export declare function bakeRoutineArgv(agent: string): string[] | undefined;
|
|
94
94
|
/** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
|