@phnx-labs/agents-cli 1.22.53 → 1.22.55
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 +208 -0
- package/README.md +59 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +219 -173
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +10 -8
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +2 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +7 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/routines.js +31 -2
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +64 -20
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +79 -3
- package/dist/commands/share.js +347 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +18 -2
- package/dist/commands/status.js +14 -0
- package/dist/commands/view.d.ts +11 -1
- package/dist/commands/view.js +35 -7
- package/dist/lib/account-registry.js +15 -3
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +68 -1
- package/dist/lib/accounting/usage.js +116 -10
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/cdp.d.ts +1 -1
- package/dist/lib/browser/cdp.js +1 -1
- package/dist/lib/browser/ffmpeg.d.ts +12 -0
- package/dist/lib/browser/ffmpeg.js +184 -0
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/browser/service.js +119 -25
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/browser-task-reap-service.d.ts +14 -0
- package/dist/lib/daemon/browser-task-reap-service.js +26 -0
- package/dist/lib/daemon/daemon.js +87 -176
- package/dist/lib/daemon/heartbeat-service.d.ts +13 -0
- package/dist/lib/daemon/heartbeat-service.js +26 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +9 -5
- package/dist/lib/daemon/monitor-engine-service.js +15 -7
- package/dist/lib/daemon/runner.d.ts +15 -0
- package/dist/lib/daemon/runner.js +23 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +5 -4
- package/dist/lib/daemon/secrets-broker-service.js +17 -32
- package/dist/lib/daemon/service.d.ts +2 -2
- package/dist/lib/daemon/service.js +1 -1
- package/dist/lib/daemon/session-state-service.d.ts +21 -0
- package/dist/lib/daemon/session-state-service.js +34 -0
- package/dist/lib/daemon/supervisor.d.ts +17 -7
- package/dist/lib/daemon/supervisor.js +87 -14
- package/dist/lib/daemon/tmux-reap-service.d.ts +11 -0
- package/dist/lib/daemon/tmux-reap-service.js +28 -0
- package/dist/lib/daemon/webhook-receiver-service.d.ts +9 -0
- package/dist/lib/daemon/webhook-receiver-service.js +17 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +25 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +5 -5
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/harness-inventory.js +5 -2
- package/dist/lib/exec.d.ts +28 -0
- package/dist/lib/exec.js +73 -7
- package/dist/lib/feed/feed.d.ts +1 -1
- package/dist/lib/feed/feed.js +23 -1
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.d.ts +5 -1
- package/dist/lib/monitors/engine.js +13 -4
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/scheduling/routines.d.ts +47 -0
- package/dist/lib/scheduling/routines.js +70 -1
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.js +1 -4
- package/dist/lib/session/db.d.ts +41 -5
- package/dist/lib/session/db.js +132 -30
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +119 -25
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +8 -0
- package/dist/lib/share/capture.js +11 -2
- package/dist/lib/share/publish.d.ts +56 -5
- package/dist/lib/share/publish.js +126 -18
- package/dist/lib/share/worker-template.d.ts +3 -12
- package/dist/lib/share/worker-template.js +860 -59
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +16 -0
- package/dist/lib/state.js +178 -46
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +7 -0
- package/package.json +9 -2
|
@@ -17,22 +17,19 @@ 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
|
-
import { executeJobDetached,
|
|
20
|
+
import { executeJobDetached, 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
26
|
import { getSocketPath as getBrowserIpcSocketPath } from '../browser/ipc.js';
|
|
27
|
-
import { startHostedWebhookReceivers } from '../daemon-webhooks.js';
|
|
28
27
|
import { secretsBrokerSocketPath, brokerPidAlive } from '../secrets/agent.js';
|
|
29
28
|
import { redactSecrets } from '../redact.js';
|
|
30
29
|
import { getAgentsBinPath, getCliLaunch, BUN_VIRTUAL_ROOT } from '../cli-entry.js';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
30
|
+
import { localBinDir } from '../platform/posixpath.js';
|
|
31
|
+
import { isSchedulerEnabled, assertSchedulerEnabled, isDaemonEnabled } from '../device-config.js';
|
|
33
32
|
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
33
|
import { ServiceSupervisor } from './supervisor.js';
|
|
37
34
|
import { SessionIndexService } from './session-index-service.js';
|
|
38
35
|
import { SecretsBrokerService } from './secrets-broker-service.js';
|
|
@@ -43,7 +40,13 @@ import { WatchdogService } from './watchdog-service.js';
|
|
|
43
40
|
import { DeviceProbeService } from './device-probe-service.js';
|
|
44
41
|
import { SelfHealService } from './self-heal-service.js';
|
|
45
42
|
import { KeychainReapService } from './keychain-reap-service.js';
|
|
43
|
+
import { AuthSyncService } from './auth-sync-service.js';
|
|
46
44
|
import { StateDirCheckService } from './state-dir-check-service.js';
|
|
45
|
+
import { SessionStateService } from './session-state-service.js';
|
|
46
|
+
import { WebhookReceiverService } from './webhook-receiver-service.js';
|
|
47
|
+
import { HeartbeatService } from './heartbeat-service.js';
|
|
48
|
+
import { TmuxReapService } from './tmux-reap-service.js';
|
|
49
|
+
import { BrowserTaskReapService } from './browser-task-reap-service.js';
|
|
47
50
|
import { emit, emitRoutineEnd } from '../feed/events.js';
|
|
48
51
|
import { readDaemonServicesConfig, drainDaemonServiceRestartQueue } from '../daemon-services.js';
|
|
49
52
|
/**
|
|
@@ -110,7 +113,6 @@ export function daemonSystemdUnitName() {
|
|
|
110
113
|
const suffix = isolatedHomeSuffix();
|
|
111
114
|
return suffix ? `agents-daemon-sandbox-${suffix}.service` : SYSTEMD_UNIT;
|
|
112
115
|
}
|
|
113
|
-
const MONITOR_TICK_MS = 60_000;
|
|
114
116
|
/**
|
|
115
117
|
* How often to re-scan for missed fires. Deliberately slower than the monitor
|
|
116
118
|
* tick: detection walks a week of cron occurrences per routine
|
|
@@ -134,17 +136,10 @@ const CATCHUP_TICK_MS = 5 * 60_000;
|
|
|
134
136
|
* from wedges every keychain-backed secret on the box, so it runs minutely.
|
|
135
137
|
*/
|
|
136
138
|
// BROKER_SELF_HEAL_TICK_MS moved to secrets-broker-service.ts (RUSH-3193 P2).
|
|
137
|
-
// RUSH-2501: reap tmux sessions whose panes are all dead every 5 minutes.
|
|
138
|
-
const DEAD_PANE_REAP_TICK_MS = 5 * 60_000;
|
|
139
|
-
// RUSH-2622: close abandoned browser-task tabs every 5 minutes.
|
|
140
|
-
const BROWSER_TASK_REAP_TICK_MS = 5 * 60_000;
|
|
141
|
-
// Keep the active-session cache/journal fresh for sessions watch + Factory.
|
|
142
|
-
// Matches DEFAULT_ACTIVE_CACHE_MAX_AGE_MS (15s) so long-lived watchers never
|
|
143
|
-
// outlive the publisher (RUSH-2484 — CLI-owned continuous publish).
|
|
144
|
-
const ACTIVE_SESSIONS_WARM_TICK_MS = 15_000;
|
|
145
139
|
// Session-index warm interval/deadline live in session-index-service.ts now
|
|
146
140
|
// (RUSH-3193 — migrated onto ServiceSupervisor).
|
|
147
141
|
const WEDGE_THRESHOLD_TICKS = 3;
|
|
142
|
+
const DAEMON_HEARTBEAT_TICK_MS = 60_000;
|
|
148
143
|
/**
|
|
149
144
|
* Crash-loop prevention (RUSH-2418). Three layers, because none of them alone
|
|
150
145
|
* bounds a daemon that dies during startup:
|
|
@@ -346,7 +341,7 @@ export function removeHeartbeat() {
|
|
|
346
341
|
*/
|
|
347
342
|
function isHeartbeatFresh(hb) {
|
|
348
343
|
const elapsed = Date.now() - Date.parse(hb.lastTick);
|
|
349
|
-
return elapsed <= WEDGE_THRESHOLD_TICKS *
|
|
344
|
+
return elapsed <= WEDGE_THRESHOLD_TICKS * DAEMON_HEARTBEAT_TICK_MS;
|
|
350
345
|
}
|
|
351
346
|
export function isDaemonWedged() {
|
|
352
347
|
const pid = readDaemonPid();
|
|
@@ -723,87 +718,6 @@ export function warnEphemeralDaemonRoot(resolveBin = getAgentsBinPath) {
|
|
|
723
718
|
// registered after `handleShutdown` is declared — see its registration site
|
|
724
719
|
// below. runBrokerSelfHeal was moved into SecretsBrokerService (RUSH-3193 P2).
|
|
725
720
|
// ---------------------------------------------------------------------------
|
|
726
|
-
// In-flight guards: each flag prevents a slow tick from being re-entered
|
|
727
|
-
// by the next timer fire before the previous one finishes.
|
|
728
|
-
let reapingDeadPanes = false;
|
|
729
|
-
let reapingBrowserTasks = false;
|
|
730
|
-
/**
|
|
731
|
-
* RUSH-2501: kill tmux sessions on the helper socket whose panes are ALL dead.
|
|
732
|
-
* RUSH-2521: and terminate the helper processes (MCP servers, harness background
|
|
733
|
-
* daemons) whose owning agent has exited — killing the pane only SIGHUPs its
|
|
734
|
-
* foreground process group, so anything that left that group outlives it.
|
|
735
|
-
*
|
|
736
|
-
* Runs every DEAD_PANE_REAP_TICK_MS (5 min). The daemon is the single executor
|
|
737
|
-
* so no UI surface can race it.
|
|
738
|
-
*/
|
|
739
|
-
async function runDeadPaneReap() {
|
|
740
|
-
if (reapingDeadPanes)
|
|
741
|
-
return;
|
|
742
|
-
reapingDeadPanes = true;
|
|
743
|
-
try {
|
|
744
|
-
const { reapDeadTmuxPanes } = await import('../tmux/session.js');
|
|
745
|
-
const { getDefaultSocketPath } = await import('../tmux/paths.js');
|
|
746
|
-
const result = await reapDeadTmuxPanes(getDefaultSocketPath());
|
|
747
|
-
for (const w of result.warnings)
|
|
748
|
-
log('WARN', `Dead-pane reaper: ${w}`);
|
|
749
|
-
if (result.processes > 0) {
|
|
750
|
-
log('INFO', `Dead-pane reaper: terminated ${result.processes} orphaned helper process(es)`);
|
|
751
|
-
for (const d of result.processDetails)
|
|
752
|
-
log('INFO', ` ${d}`);
|
|
753
|
-
}
|
|
754
|
-
if (result.reaped > 0) {
|
|
755
|
-
log('INFO', `Dead-pane reaper: reaped ${result.reaped} session(s)`);
|
|
756
|
-
for (const d of result.details)
|
|
757
|
-
log('INFO', ` killed ${d}`);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
catch (err) {
|
|
761
|
-
log('ERROR', `Dead-pane reaper failed: ${err.message}`);
|
|
762
|
-
}
|
|
763
|
-
finally {
|
|
764
|
-
reapingDeadPanes = false;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* RUSH-2622: close abandoned browser-task tabs — a task whose owning agent
|
|
769
|
-
* session has exited, or one idle past `browser.task-idle-minutes` (default 30,
|
|
770
|
-
* 0 disables idle reaping only). Same policy `agents browser prune` triggers on
|
|
771
|
-
* demand; this is the periodic side, and the daemon is the single executor so
|
|
772
|
-
* no UI surface can race it. `service` is the daemon's own long-lived
|
|
773
|
-
* BrowserService — the browser IPC server it started earlier in `runDaemon()`.
|
|
774
|
-
*
|
|
775
|
-
* Runs every BROWSER_TASK_REAP_TICK_MS (5 min).
|
|
776
|
-
*/
|
|
777
|
-
async function runBrowserTaskReap(service) {
|
|
778
|
-
if (reapingBrowserTasks)
|
|
779
|
-
return;
|
|
780
|
-
reapingBrowserTasks = true;
|
|
781
|
-
try {
|
|
782
|
-
const result = await service.reapAbandoned({ idleMs: resolveBrowserTaskIdleMs() });
|
|
783
|
-
if (result.closed.length > 0) {
|
|
784
|
-
log('INFO', `Browser-task reaper: closed ${result.closed.length} task(s)`);
|
|
785
|
-
for (const c of result.closed)
|
|
786
|
-
log('INFO', ` ${c.task} (${c.reason}, profile ${c.profile})`);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
catch (err) {
|
|
790
|
-
log('ERROR', `Browser-task reaper failed: ${err.message}`);
|
|
791
|
-
}
|
|
792
|
-
finally {
|
|
793
|
-
reapingBrowserTasks = false;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* One monitor tick: write heartbeat + drain the running-job monitor + reap
|
|
798
|
-
* stale terminal routine process groups. Runs on MONITOR_TICK_MS.
|
|
799
|
-
*/
|
|
800
|
-
function runMonitorTick() {
|
|
801
|
-
writeHeartbeat();
|
|
802
|
-
monitorRunningJobs();
|
|
803
|
-
const reaped = reapTerminalRoutineProcesses();
|
|
804
|
-
if (reaped.length > 0)
|
|
805
|
-
log('WARN', `Reaped ${reaped.length} terminal routine process group(s): ${reaped.join(', ')}`);
|
|
806
|
-
}
|
|
807
721
|
export async function runDaemon() {
|
|
808
722
|
// Single-instance guard (last-wins, SING-11): a direct `agents __daemon-run`
|
|
809
723
|
// (manual, or a service-manager restart racing a live predecessor) EVICTS the
|
|
@@ -889,6 +803,11 @@ export async function runDaemon() {
|
|
|
889
803
|
// broker starts socket-first (#416) and `agents secrets` resolves within
|
|
890
804
|
// ms of daemon start, exactly as before.
|
|
891
805
|
const supervisor = new ServiceSupervisor();
|
|
806
|
+
if (isEnabled('session-state')) {
|
|
807
|
+
supervisor.register(new SessionStateService(() => supervisor.runNow('session-state')));
|
|
808
|
+
}
|
|
809
|
+
else
|
|
810
|
+
log('INFO', 'Live session-state service disabled');
|
|
892
811
|
if (isEnabled('secrets-broker'))
|
|
893
812
|
supervisor.register(new SecretsBrokerService());
|
|
894
813
|
else
|
|
@@ -902,12 +821,13 @@ export async function runDaemon() {
|
|
|
902
821
|
supervisor.register(new AccountStateDaemonService());
|
|
903
822
|
else
|
|
904
823
|
log('INFO', 'Account-state service disabled');
|
|
905
|
-
// BrowserIPCService
|
|
906
|
-
//
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
824
|
+
// BrowserIPCService and BrowserTaskReapService share one long-lived
|
|
825
|
+
// BrowserService when both are enabled. Either service can run independently.
|
|
826
|
+
const browserService = isEnabled('browser-ipc') || isEnabled('browser-task-reap')
|
|
827
|
+
? new BrowserService()
|
|
828
|
+
: null;
|
|
829
|
+
if (isEnabled('browser-ipc') && browserService)
|
|
830
|
+
supervisor.register(new BrowserIPCService(browserService));
|
|
911
831
|
else
|
|
912
832
|
log('INFO', 'Browser IPC service disabled');
|
|
913
833
|
if (isEnabled('session-index'))
|
|
@@ -934,6 +854,28 @@ export async function runDaemon() {
|
|
|
934
854
|
supervisor.register(new KeychainReapService());
|
|
935
855
|
else
|
|
936
856
|
log('INFO', 'Keychain-reap service disabled');
|
|
857
|
+
if (isEnabled('auth-sync'))
|
|
858
|
+
supervisor.register(new AuthSyncService());
|
|
859
|
+
else
|
|
860
|
+
log('INFO', 'Auth-sync service disabled');
|
|
861
|
+
if (isEnabled('webhook-receiver'))
|
|
862
|
+
supervisor.register(new WebhookReceiverService());
|
|
863
|
+
else
|
|
864
|
+
log('INFO', 'Webhook receiver service disabled');
|
|
865
|
+
if (isEnabled('daemon-heartbeat'))
|
|
866
|
+
supervisor.register(new HeartbeatService(() => writeHeartbeat()));
|
|
867
|
+
else
|
|
868
|
+
log('INFO', 'Daemon heartbeat service disabled');
|
|
869
|
+
if (isEnabled('tmux-reap'))
|
|
870
|
+
supervisor.register(new TmuxReapService());
|
|
871
|
+
else
|
|
872
|
+
log('INFO', 'Tmux reap service disabled');
|
|
873
|
+
if (isEnabled('browser-task-reap') && browserService) {
|
|
874
|
+
supervisor.register(new BrowserTaskReapService(browserService));
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
log('INFO', 'Browser-task reap service disabled');
|
|
878
|
+
}
|
|
937
879
|
await supervisor.startAll({ log });
|
|
938
880
|
activeServiceSupervisor = supervisor;
|
|
939
881
|
// scheduler.enabled=false in this machine's device doc means NO routines fire
|
|
@@ -1070,36 +1012,9 @@ export async function runDaemon() {
|
|
|
1070
1012
|
}
|
|
1071
1013
|
if (schedulerEnabledAtBoot)
|
|
1072
1014
|
bootScheduler();
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
//
|
|
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.
|
|
1079
|
-
let activeSessionsWarmInFlight = false;
|
|
1080
|
-
const runActiveSessionsWarm = async () => {
|
|
1081
|
-
if (activeSessionsWarmInFlight)
|
|
1082
|
-
return;
|
|
1083
|
-
activeSessionsWarmInFlight = true;
|
|
1084
|
-
try {
|
|
1085
|
-
await runActiveSessionsWarmTick();
|
|
1086
|
-
}
|
|
1087
|
-
catch (err) {
|
|
1088
|
-
log('WARN', `active-sessions warm failed: ${err.message}`);
|
|
1089
|
-
}
|
|
1090
|
-
finally {
|
|
1091
|
-
activeSessionsWarmInFlight = false;
|
|
1092
|
-
}
|
|
1093
|
-
};
|
|
1094
|
-
void runActiveSessionsWarm();
|
|
1095
|
-
const activeSessionsWarmInterval = setInterval(() => { void runActiveSessionsWarm(); }, ACTIVE_SESSIONS_WARM_TICK_MS);
|
|
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(); });
|
|
1015
|
+
// Live-session metadata publishing is owned by SessionStateService. Its
|
|
1016
|
+
// presence watcher requests an immediate supervised tick when a reader
|
|
1017
|
+
// connects; callers consume the journal instead of duplicating the gather.
|
|
1103
1018
|
// Session-index warm (RUSH-2682) is registered on the supervisor above
|
|
1104
1019
|
// (RUSH-3193 P2) alongside the socket services.
|
|
1105
1020
|
// Watchdog and device-probe are now managed by WatchdogService /
|
|
@@ -1185,23 +1100,8 @@ export async function runDaemon() {
|
|
|
1185
1100
|
// Started after the broker (above) so it resolves each receiver's signing
|
|
1186
1101
|
// secret headlessly — no AGENTS_SECRETS_PASSPHRASE, no nohup. Binds nothing
|
|
1187
1102
|
// unless daemon/webhooks.yaml declares a receiver, so an unconfigured box no-ops.
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
try {
|
|
1191
|
-
webhookReceivers = await startHostedWebhookReceivers({ log });
|
|
1192
|
-
log('INFO', webhookReceivers.count > 0
|
|
1193
|
-
? `Webhook receiver hosting ${webhookReceivers.count} receiver(s)`
|
|
1194
|
-
: 'Webhook receiver service enabled; no receivers declared in daemon/webhooks.yaml');
|
|
1195
|
-
}
|
|
1196
|
-
catch (err) {
|
|
1197
|
-
log('WARN', `Webhook receiver host skipped: ${err.message}`);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
else {
|
|
1201
|
-
log('INFO', 'Webhook receiver service disabled');
|
|
1202
|
-
}
|
|
1203
|
-
runMonitorTick();
|
|
1204
|
-
const monitorInterval = setInterval(runMonitorTick, MONITOR_TICK_MS);
|
|
1103
|
+
// Signed webhook ingress is owned by WebhookReceiverService, including
|
|
1104
|
+
// per-service failure isolation, measured health, and shutdown cleanup.
|
|
1205
1105
|
// Resource self-heal is now managed by SelfHealService on the supervisor
|
|
1206
1106
|
// (RUSH-3193 P3), registered above alongside the socket services. The
|
|
1207
1107
|
// supervisor's immediate first tick on start replaces the old
|
|
@@ -1212,16 +1112,11 @@ export async function runDaemon() {
|
|
|
1212
1112
|
// supervisor (RUSH-3193 P3), registered above alongside the socket services.
|
|
1213
1113
|
// RUSH-2501: reap tmux sessions whose panes are all dead. Runs on the same
|
|
1214
1114
|
// 5-min cadence as the keychain reaper. Daemon-only (single executor).
|
|
1215
|
-
|
|
1216
|
-
const deadPaneReapInterval = setInterval(() => { void runDeadPaneReap(); }, DEAD_PANE_REAP_TICK_MS);
|
|
1115
|
+
// Dead managed panes and their orphan helpers are reaped by TmuxReapService.
|
|
1217
1116
|
// RUSH-2622: close abandoned browser-task tabs on the same 5-min cadence,
|
|
1218
|
-
// reusing the daemon's
|
|
1219
|
-
//
|
|
1220
|
-
|
|
1221
|
-
if (browserSvcForReap) {
|
|
1222
|
-
void runBrowserTaskReap(browserSvcForReap); // kick-off on startup
|
|
1223
|
-
browserTaskReapInterval = setInterval(() => { void runBrowserTaskReap(browserSvcForReap); }, BROWSER_TASK_REAP_TICK_MS);
|
|
1224
|
-
}
|
|
1117
|
+
// reusing the daemon's long-lived BrowserService when browser IPC is enabled.
|
|
1118
|
+
// Abandoned browser tasks are reaped by BrowserTaskReapService, sharing the
|
|
1119
|
+
// same BrowserService instance as BrowserIPCService.
|
|
1225
1120
|
// RUSH-2367 / RUSH-3193 P3: state-dir-check (self-terminate guard) is
|
|
1226
1121
|
// registered on the supervisor further below, once `handleShutdown` exists
|
|
1227
1122
|
// — see the registration site after its declaration for why.
|
|
@@ -1259,7 +1154,11 @@ export async function runDaemon() {
|
|
|
1259
1154
|
// never registered, so it falls through to the same "restart to
|
|
1260
1155
|
// apply" advice as before.
|
|
1261
1156
|
if (supervisor.isRegistered(id)) {
|
|
1262
|
-
|
|
1157
|
+
// A periodic service may be inside a real tick when SIGHUP arrives.
|
|
1158
|
+
// Queue the desired transition behind that exact promise: deadlines
|
|
1159
|
+
// detect a wedge but cannot cancel arbitrary work, and polling here
|
|
1160
|
+
// would create another lifecycle timer outside the supervisor.
|
|
1161
|
+
const action = supervisor.awaitIdle(id).then(() => now ? supervisor.start(id) : supervisor.stop(id));
|
|
1263
1162
|
void action
|
|
1264
1163
|
.then(() => log('INFO', `Service '${id}' ${now ? 'started' : 'stopped'} live (SIGHUP reload)`))
|
|
1265
1164
|
.catch((err) => log('WARN', `Service '${id}' live ${now ? 'start' : 'stop'} failed: ${err.message}`));
|
|
@@ -1274,7 +1173,7 @@ export async function runDaemon() {
|
|
|
1274
1173
|
// Drain queued `agents daemon services restart <id>` requests (RUSH-3193 P4).
|
|
1275
1174
|
for (const id of drainDaemonServiceRestartQueue()) {
|
|
1276
1175
|
if (supervisor.isRegistered(id)) {
|
|
1277
|
-
void supervisor.restartOne(id)
|
|
1176
|
+
void supervisor.awaitIdle(id).then(() => supervisor.restartOne(id))
|
|
1278
1177
|
.then(() => log('INFO', `Service '${id}' restarted live (SIGHUP reload)`))
|
|
1279
1178
|
.catch((err) => log('WARN', `Service '${id}' live restart failed: ${err.message}`));
|
|
1280
1179
|
}
|
|
@@ -1345,20 +1244,12 @@ export async function runDaemon() {
|
|
|
1345
1244
|
// rather than one that every step added later has to re-earn.
|
|
1346
1245
|
const handleShutdown = singleShot(async () => {
|
|
1347
1246
|
log('INFO', 'Daemon shutting down');
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
//
|
|
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.
|
|
1247
|
+
// supervisor.stopAll() stops every registered service, including socket
|
|
1248
|
+
// hosts, session publishers/indexers, monitor/account work, and every
|
|
1249
|
+
// maintenance timer; state-dir-check joins the registry just below.
|
|
1354
1250
|
await supervisor.stopAll();
|
|
1355
1251
|
activeServiceSupervisor = null;
|
|
1356
1252
|
stopScheduler();
|
|
1357
|
-
await webhookReceivers?.close();
|
|
1358
|
-
clearInterval(monitorInterval);
|
|
1359
|
-
clearInterval(deadPaneReapInterval);
|
|
1360
|
-
if (browserTaskReapInterval)
|
|
1361
|
-
clearInterval(browserTaskReapInterval);
|
|
1362
1253
|
try {
|
|
1363
1254
|
if (fs.readFileSync(lifetimePath, 'utf-8') === lifetimeToken)
|
|
1364
1255
|
fs.unlinkSync(lifetimePath);
|
|
@@ -1800,10 +1691,23 @@ export function getDaemonLaunch(agentsBin = getAgentsBinPath()) {
|
|
|
1800
1691
|
function daemonNodeBinDir() {
|
|
1801
1692
|
return path.dirname(process.execPath);
|
|
1802
1693
|
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Login-shell user-bin dirs a service-manager-started daemon would otherwise
|
|
1696
|
+
* miss. systemd/launchd pin PATH and never source `~/.profile`, so they never
|
|
1697
|
+
* see `~/.rush/bin` (where `rush` lands) or `~/.local/bin` (XDG user-bin).
|
|
1698
|
+
* Monitor `notify` and dispatched `agents run` children inherit this PATH;
|
|
1699
|
+
* without these dirs the rush-backed owner channel fails with
|
|
1700
|
+
* `rush CLI not found on PATH` (PHNX-3075) while an interactive shell on the
|
|
1701
|
+
* same box succeeds. Uses the same HOME the manifest bakes (RUSH-2639).
|
|
1702
|
+
*/
|
|
1703
|
+
function daemonUserBinDirs() {
|
|
1704
|
+
const home = serviceManifestHomeEnv().HOME;
|
|
1705
|
+
return [path.join(home, '.rush', 'bin'), localBinDir(home)];
|
|
1706
|
+
}
|
|
1803
1707
|
/**
|
|
1804
1708
|
* The full PATH value the daemon service manifest pins, in order: the directory
|
|
1805
|
-
* of the `agents` shim itself FIRST, then the Node runtime dir, then
|
|
1806
|
-
* platform's system dirs.
|
|
1709
|
+
* of the `agents` shim itself FIRST, then the Node runtime dir, then login-shell
|
|
1710
|
+
* user-bin dirs (`~/.rush/bin`, `~/.local/bin`), then the platform's system dirs.
|
|
1807
1711
|
*
|
|
1808
1712
|
* The shim's own dir must lead so a scheduled `command` routine that shells out
|
|
1809
1713
|
* to the bare name `agents` (`/bin/sh -c 'agents repo pull system'`) resolves the
|
|
@@ -1814,12 +1718,19 @@ function daemonNodeBinDir() {
|
|
|
1814
1718
|
*
|
|
1815
1719
|
* The Node runtime dir stays second so the shim's shebang (`#!/usr/bin/env node`)
|
|
1816
1720
|
* still resolves the exact Node that installed the service — never an ancient
|
|
1817
|
-
* system node or a pruned nvm version.
|
|
1721
|
+
* system node or a pruned nvm version. User-bin dirs sit after that pair so a
|
|
1722
|
+
* `~/.local/bin/agents` cannot shadow the daemon binary, but `rush` (and other
|
|
1723
|
+
* login-shell CLIs) still resolve. Deduped across the whole list, so a
|
|
1818
1724
|
* Node/shim dir that already appears among the system dirs (e.g. a
|
|
1819
1725
|
* `/usr/local/bin` install) never doubles.
|
|
1820
1726
|
*/
|
|
1821
1727
|
function daemonPathValue(agentsBin, systemDirs) {
|
|
1822
|
-
return [...new Set([
|
|
1728
|
+
return [...new Set([
|
|
1729
|
+
path.dirname(agentsBin),
|
|
1730
|
+
daemonNodeBinDir(),
|
|
1731
|
+
...daemonUserBinDirs(),
|
|
1732
|
+
...systemDirs,
|
|
1733
|
+
])].join(':');
|
|
1823
1734
|
}
|
|
1824
1735
|
/**
|
|
1825
1736
|
* Build the argv to relaunch the `agents` CLI with the given subcommand args.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Daemon heartbeat and routine-process reconciliation under supervision. */
|
|
2
|
+
import type { DaemonServiceId } from '../daemon-services.js';
|
|
3
|
+
import { BasePeriodicService, type DaemonContext } from './service.js';
|
|
4
|
+
export declare class HeartbeatService extends BasePeriodicService {
|
|
5
|
+
private readonly publishHeartbeat;
|
|
6
|
+
readonly id: DaemonServiceId;
|
|
7
|
+
readonly intervalMs = 60000;
|
|
8
|
+
readonly deadlineMs = 30000;
|
|
9
|
+
constructor(publishHeartbeat: () => void);
|
|
10
|
+
protected onStart(_ctx: DaemonContext): Promise<void>;
|
|
11
|
+
protected onStop(): Promise<void>;
|
|
12
|
+
protected onTick(ctx: DaemonContext): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Daemon heartbeat and routine-process reconciliation under supervision. */
|
|
2
|
+
import { monitorRunningJobs } from './runner.js';
|
|
3
|
+
import { reapTerminalRoutineProcesses } from '../routine-process-cleanup.js';
|
|
4
|
+
import { BasePeriodicService } from './service.js';
|
|
5
|
+
const HEARTBEAT_TICK_MS = 60_000;
|
|
6
|
+
const HEARTBEAT_DEADLINE_MS = 30_000;
|
|
7
|
+
export class HeartbeatService extends BasePeriodicService {
|
|
8
|
+
publishHeartbeat;
|
|
9
|
+
id = 'daemon-heartbeat';
|
|
10
|
+
intervalMs = HEARTBEAT_TICK_MS;
|
|
11
|
+
deadlineMs = HEARTBEAT_DEADLINE_MS;
|
|
12
|
+
constructor(publishHeartbeat) {
|
|
13
|
+
super();
|
|
14
|
+
this.publishHeartbeat = publishHeartbeat;
|
|
15
|
+
}
|
|
16
|
+
async onStart(_ctx) { }
|
|
17
|
+
async onStop() { }
|
|
18
|
+
async onTick(ctx) {
|
|
19
|
+
this.publishHeartbeat();
|
|
20
|
+
monitorRunningJobs();
|
|
21
|
+
const reaped = reapTerminalRoutineProcesses();
|
|
22
|
+
if (reaped.length > 0) {
|
|
23
|
+
ctx.log('WARN', `Reaped ${reaped.length} terminal routine process group(s): ${reaped.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -2,21 +2,25 @@
|
|
|
2
2
|
* MonitorEngine lifecycle as a `DaemonService` (RUSH-3193 P2).
|
|
3
3
|
*
|
|
4
4
|
* Wraps the `MonitorEngine` (event-triggered watchers) under the
|
|
5
|
-
* `ServiceSupervisor` contract.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* `ServiceSupervisor` contract. The engine can own a timer when embedded by a
|
|
6
|
+
* foreground caller, but the daemon starts it in external-scheduler mode so
|
|
7
|
+
* every evaluation cycle receives the supervisor's deadline, health, and
|
|
8
|
+
* circuit-breaker semantics.
|
|
8
9
|
*
|
|
9
10
|
* `getEngine()` exposes the underlying `MonitorEngine` so `daemon.ts`'s
|
|
10
11
|
* SIGHUP handler can call `engine.reload()` when needed.
|
|
11
12
|
*/
|
|
12
|
-
import {
|
|
13
|
+
import { BasePeriodicService, type DaemonContext } from './service.js';
|
|
13
14
|
import type { DaemonServiceId } from '../daemon-services.js';
|
|
14
15
|
import { MonitorEngine } from '../monitors/engine.js';
|
|
15
|
-
export declare class MonitorEngineService extends
|
|
16
|
+
export declare class MonitorEngineService extends BasePeriodicService {
|
|
16
17
|
readonly id: DaemonServiceId;
|
|
18
|
+
readonly intervalMs = 5000;
|
|
19
|
+
readonly deadlineMs: number;
|
|
17
20
|
private engine;
|
|
18
21
|
/** Returns the live engine after `start()`, or `null` before/after. */
|
|
19
22
|
getEngine(): MonitorEngine | null;
|
|
20
23
|
protected onStart(ctx: DaemonContext): Promise<void>;
|
|
21
24
|
protected onStop(): Promise<void>;
|
|
25
|
+
protected onTick(_ctx: DaemonContext): Promise<void>;
|
|
22
26
|
}
|
|
@@ -2,17 +2,20 @@
|
|
|
2
2
|
* MonitorEngine lifecycle as a `DaemonService` (RUSH-3193 P2).
|
|
3
3
|
*
|
|
4
4
|
* Wraps the `MonitorEngine` (event-triggered watchers) under the
|
|
5
|
-
* `ServiceSupervisor` contract.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* `ServiceSupervisor` contract. The engine can own a timer when embedded by a
|
|
6
|
+
* foreground caller, but the daemon starts it in external-scheduler mode so
|
|
7
|
+
* every evaluation cycle receives the supervisor's deadline, health, and
|
|
8
|
+
* circuit-breaker semantics.
|
|
8
9
|
*
|
|
9
10
|
* `getEngine()` exposes the underlying `MonitorEngine` so `daemon.ts`'s
|
|
10
11
|
* SIGHUP handler can call `engine.reload()` when needed.
|
|
11
12
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import { MonitorEngine } from '../monitors/engine.js';
|
|
14
|
-
export class MonitorEngineService extends
|
|
13
|
+
import { BasePeriodicService } from './service.js';
|
|
14
|
+
import { MONITOR_ENGINE_TICK_MS, MonitorEngine } from '../monitors/engine.js';
|
|
15
|
+
export class MonitorEngineService extends BasePeriodicService {
|
|
15
16
|
id = 'monitors';
|
|
17
|
+
intervalMs = MONITOR_ENGINE_TICK_MS;
|
|
18
|
+
deadlineMs = 2 * 60_000;
|
|
16
19
|
engine = null;
|
|
17
20
|
/** Returns the live engine after `start()`, or `null` before/after. */
|
|
18
21
|
getEngine() {
|
|
@@ -20,10 +23,15 @@ export class MonitorEngineService extends BaseDaemonService {
|
|
|
20
23
|
}
|
|
21
24
|
async onStart(ctx) {
|
|
22
25
|
this.engine = new MonitorEngine((level, message) => ctx.log(level, message));
|
|
23
|
-
this.engine.start();
|
|
26
|
+
this.engine.start({ externalScheduler: true });
|
|
24
27
|
}
|
|
25
28
|
async onStop() {
|
|
26
29
|
this.engine?.stop();
|
|
27
30
|
this.engine = null;
|
|
28
31
|
}
|
|
32
|
+
async onTick(_ctx) {
|
|
33
|
+
if (!this.engine)
|
|
34
|
+
throw new Error('monitor engine tick requested before start');
|
|
35
|
+
await this.engine.tick();
|
|
36
|
+
}
|
|
29
37
|
}
|
|
@@ -233,6 +233,21 @@ export declare function inferFinalStatusFromLog(stdoutPath: string, agent: Agent
|
|
|
233
233
|
* `host:`-placed runs have no local pid and are excluded.
|
|
234
234
|
*/
|
|
235
235
|
export declare function listLiveRoutineChildren(): number[];
|
|
236
|
+
/**
|
|
237
|
+
* Whether a run record marked `running` is genuinely in flight right now. A
|
|
238
|
+
* `running` status alone is not enough: `writeActiveClaim` stamps a provisional
|
|
239
|
+
* claim as `running` with `pid: null` BEFORE the child spawns, and
|
|
240
|
+
* `monitorRunningJobs()` does not reap a null-pid record (it has no process to
|
|
241
|
+
* probe, and ages it out only past the configured timeout — up to a week). So a
|
|
242
|
+
* daemon crash in that spawn window leaves a `running` record for a run that is
|
|
243
|
+
* not running (RUSH-2640). A caller answering "is one running now?" MUST gate on
|
|
244
|
+
* this, mirroring `listLiveRoutineChildren`'s local-liveness test:
|
|
245
|
+
* - a local child is in flight only while its pid is still ours (alive, same
|
|
246
|
+
* birth time — a dead-and-reused pid does not count);
|
|
247
|
+
* - a `host:`-placed run has no local pid by design and is in flight until its
|
|
248
|
+
* remote `.exit` is reconciled (`finalizeHostRun`).
|
|
249
|
+
*/
|
|
250
|
+
export declare function isRunGenuinelyInFlight(meta: RunMeta): boolean;
|
|
236
251
|
/** Scan all runs marked "running" and finalize any whose process has exited. */
|
|
237
252
|
export declare function monitorRunningJobs(): void;
|
|
238
253
|
export {};
|
|
@@ -2263,6 +2263,29 @@ export function listLiveRoutineChildren() {
|
|
|
2263
2263
|
}
|
|
2264
2264
|
return pids;
|
|
2265
2265
|
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Whether a run record marked `running` is genuinely in flight right now. A
|
|
2268
|
+
* `running` status alone is not enough: `writeActiveClaim` stamps a provisional
|
|
2269
|
+
* claim as `running` with `pid: null` BEFORE the child spawns, and
|
|
2270
|
+
* `monitorRunningJobs()` does not reap a null-pid record (it has no process to
|
|
2271
|
+
* probe, and ages it out only past the configured timeout — up to a week). So a
|
|
2272
|
+
* daemon crash in that spawn window leaves a `running` record for a run that is
|
|
2273
|
+
* not running (RUSH-2640). A caller answering "is one running now?" MUST gate on
|
|
2274
|
+
* this, mirroring `listLiveRoutineChildren`'s local-liveness test:
|
|
2275
|
+
* - a local child is in flight only while its pid is still ours (alive, same
|
|
2276
|
+
* birth time — a dead-and-reused pid does not count);
|
|
2277
|
+
* - a `host:`-placed run has no local pid by design and is in flight until its
|
|
2278
|
+
* remote `.exit` is reconciled (`finalizeHostRun`).
|
|
2279
|
+
*/
|
|
2280
|
+
export function isRunGenuinelyInFlight(meta) {
|
|
2281
|
+
if (meta.status !== 'running')
|
|
2282
|
+
return false;
|
|
2283
|
+
if (meta.hostTaskId)
|
|
2284
|
+
return true;
|
|
2285
|
+
if (!meta.pid)
|
|
2286
|
+
return false;
|
|
2287
|
+
return isPidOurs(meta.pid, meta.spawnedAt);
|
|
2288
|
+
}
|
|
2266
2289
|
/** Scan all runs marked "running" and finalize any whose process has exited. */
|
|
2267
2290
|
export function monitorRunningJobs() {
|
|
2268
2291
|
const runsDir = getRunsDir();
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
* over hosting whenever the daemon is NOT already hosting AND no healthy
|
|
9
9
|
* standalone broker answers a ping.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { BasePeriodicService, type DaemonContext } from './service.js';
|
|
12
12
|
import type { DaemonServiceId } from '../daemon-services.js';
|
|
13
|
-
export declare class SecretsBrokerService extends
|
|
13
|
+
export declare class SecretsBrokerService extends BasePeriodicService {
|
|
14
14
|
readonly id: DaemonServiceId;
|
|
15
|
+
readonly intervalMs = 60000;
|
|
16
|
+
readonly deadlineMs = 30000;
|
|
15
17
|
private hostedBroker;
|
|
16
|
-
private selfHealTimer;
|
|
17
|
-
private selfHealInFlight;
|
|
18
18
|
protected onStart(ctx: DaemonContext): Promise<void>;
|
|
19
19
|
protected onStop(): Promise<void>;
|
|
20
|
+
protected onTick(ctx: DaemonContext): Promise<void>;
|
|
20
21
|
}
|