@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
|
@@ -43,7 +43,25 @@ import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountCandidate, r
|
|
|
43
43
|
import { isHeadedDeviceRole, selfConfiguredDeviceRole } from '../device-config.js';
|
|
44
44
|
import { isSelfUpdatingAgent, ROUTINE_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../agents.js';
|
|
45
45
|
import { isCustomHarnessName, readProfile } from '../profiles.js';
|
|
46
|
-
import { findAccount, findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } from '../account-registry.js';
|
|
46
|
+
import { findAccount, findUnifiedAccount, listNativeAccounts, resolveAccountSelection, resolveCredentialAccount } from '../account-registry.js';
|
|
47
|
+
import { recordRunAuthOutcome } from '../auth-health.js';
|
|
48
|
+
/**
|
|
49
|
+
* Record a routine run's auth outcome as a per-account FACT (PHNX-4116). A worker
|
|
50
|
+
* runs entirely through routines and never probes, so an auth failure here — or a
|
|
51
|
+
* clean success that clears a stale failure — is the honest evidence `agents view`
|
|
52
|
+
* renders as `last used ok` / `last auth failure`. Best-effort: resolves the slot
|
|
53
|
+
* from the routine's account name so the fact lands on the right row, and never
|
|
54
|
+
* throws (a run it cannot attribute is simply not recorded).
|
|
55
|
+
*/
|
|
56
|
+
function recordRoutineAuthOutcome(agent, accountName, version, outcome) {
|
|
57
|
+
try {
|
|
58
|
+
const accountId = accountName
|
|
59
|
+
? listNativeAccounts(readMeta()).find((a) => a.agent === agent && a.name === accountName)?.id ?? null
|
|
60
|
+
: null;
|
|
61
|
+
recordRunAuthOutcome({ agent, accountId, version: version ?? null, account: accountName, outcome });
|
|
62
|
+
}
|
|
63
|
+
catch { /* best-effort */ }
|
|
64
|
+
}
|
|
47
65
|
export class RoutineAlreadyRunningError extends Error {
|
|
48
66
|
constructor(jobName, runId) {
|
|
49
67
|
super(`Routine '${jobName}' already has a running execution (${runId})`);
|
|
@@ -465,7 +483,6 @@ const ROUTINE_TRANSCRIPT_SPECS = {
|
|
|
465
483
|
claude: [{ root: ['.claude', 'projects'], ext: '.jsonl' }],
|
|
466
484
|
codex: [{ root: ['.codex', 'sessions'], ext: '.jsonl' }],
|
|
467
485
|
cursor: [{ root: ['.cursor', 'projects'], ext: '.jsonl' }],
|
|
468
|
-
gemini: [{ root: ['.gemini', 'tmp'], ext: '.json' }],
|
|
469
486
|
antigravity: [{ root: ['.gemini', 'antigravity-cli', 'conversations'], ext: '.db' }],
|
|
470
487
|
droid: [{ root: ['.factory', 'sessions'], ext: '.jsonl' }],
|
|
471
488
|
// Kimi splits a session across two files (session/discover.ts:4382-4384):
|
|
@@ -506,8 +523,8 @@ export function routineSpawnCwd(config) {
|
|
|
506
523
|
* modeFlags.plan so claudeAdapter.routineModeArgs can splice
|
|
507
524
|
* plan → acceptEdits / auto / skip.
|
|
508
525
|
*
|
|
509
|
-
* Returns undefined when `agent` is outside ROUTINE_AGENT_IDS (
|
|
510
|
-
*
|
|
526
|
+
* Returns undefined when `agent` is outside ROUTINE_AGENT_IDS (grok and every
|
|
527
|
+
* other harness the daemon does not fire locally).
|
|
511
528
|
*/
|
|
512
529
|
export function bakeRoutineArgv(agent) {
|
|
513
530
|
if (!ROUTINE_AGENT_IDS.includes(agent))
|
|
@@ -1577,6 +1594,7 @@ async function executeJobPlaced(config, deps, attempt) {
|
|
|
1577
1594
|
// success (processFailed:false).
|
|
1578
1595
|
if (isAuthFailureFromLog(attempt.logText, effectiveAgent, { processFailed: false })) {
|
|
1579
1596
|
const reason = authFailureReason(attempt.logText) ?? 'authentication_failed';
|
|
1597
|
+
recordRoutineAuthOutcome(attemptAgent, config.account, attemptVersion, { ok: false, verdict: 'revoked', detail: reason });
|
|
1580
1598
|
finalizeRunMeta(meta, 'failed', attempt.exitCode ?? 1, { errorMessage: `auth_failed: ${reason}` });
|
|
1581
1599
|
writeRunMeta(meta);
|
|
1582
1600
|
timer.end({ status: 'failed', exitCode: meta.exitCode ?? undefined, runId, error: `auth_failed: ${reason}` });
|
|
@@ -1584,6 +1602,9 @@ async function executeJobPlaced(config, deps, attempt) {
|
|
|
1584
1602
|
archiveRoutineTranscripts(meta, runDir, overlayHome);
|
|
1585
1603
|
return { meta, reportPath: null };
|
|
1586
1604
|
}
|
|
1605
|
+
// A clean routine completion is real auth evidence — record it so a worker's
|
|
1606
|
+
// token reads `last used ok` and a stale failure fact is cleared (PHNX-4116).
|
|
1607
|
+
recordRoutineAuthOutcome(attemptAgent, config.account, attemptVersion, { ok: true });
|
|
1587
1608
|
finalizeRunMeta(meta, 'completed', 0);
|
|
1588
1609
|
writeRunMeta(meta);
|
|
1589
1610
|
timer.end({ status: 'completed', exitCode: 0, runId });
|
|
@@ -1619,6 +1640,8 @@ async function executeJobPlaced(config, deps, attempt) {
|
|
|
1619
1640
|
const failureErrorMessage = authReason
|
|
1620
1641
|
? `auth_failed: ${authReason}`
|
|
1621
1642
|
: (attempt.error ?? undefined);
|
|
1643
|
+
if (authFailed)
|
|
1644
|
+
recordRoutineAuthOutcome(attemptAgent, config.account, attemptVersion, { ok: false, verdict: 'revoked', detail: authReason ?? undefined });
|
|
1622
1645
|
finalizeRunMeta(meta, 'failed', attempt.exitCode ?? 1, failureErrorMessage ? { errorMessage: failureErrorMessage } : undefined);
|
|
1623
1646
|
writeRunMeta(meta);
|
|
1624
1647
|
timer.end({
|
|
@@ -2044,10 +2067,15 @@ async function executeJobDetachedClaimed(config, attempt, hooks) {
|
|
|
2044
2067
|
// marker still catches an exit-0 auth failure on its own.
|
|
2045
2068
|
if (isAuthFailureFromLog(logText, effectiveAgent, { processFailed: (code ?? 1) !== 0 })) {
|
|
2046
2069
|
const reason = authFailureReason(logText) ?? 'authentication_failed';
|
|
2070
|
+
recordRoutineAuthOutcome(effectiveAgent, config.account, meta.version, { ok: false, verdict: 'revoked', detail: reason });
|
|
2047
2071
|
settle('failed', code ?? 1, `auth_failed: ${reason}`);
|
|
2048
2072
|
return;
|
|
2049
2073
|
}
|
|
2050
2074
|
const inferred = inferFinalStatusFromLog(stdoutPath, effectiveAgent);
|
|
2075
|
+
const finalStatus = inferred ? inferred.status : (code === 0 ? 'completed' : 'failed');
|
|
2076
|
+
// A clean completion clears any stale auth-failure fact for this account.
|
|
2077
|
+
if (finalStatus === 'completed')
|
|
2078
|
+
recordRoutineAuthOutcome(effectiveAgent, config.account, meta.version, { ok: true });
|
|
2051
2079
|
if (inferred) {
|
|
2052
2080
|
settle(inferred.status, inferred.exitCode);
|
|
2053
2081
|
}
|
|
@@ -2201,11 +2229,6 @@ export function extractReport(stdoutPath, agentType) {
|
|
|
2201
2229
|
: JSON.stringify(parsed.content);
|
|
2202
2230
|
}
|
|
2203
2231
|
}
|
|
2204
|
-
if (agentType === 'gemini') {
|
|
2205
|
-
if (parsed.type === 'text' && parsed.text) {
|
|
2206
|
-
lastMessage = parsed.text;
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
2232
|
}
|
|
2210
2233
|
catch { /* malformed JSONL line */ }
|
|
2211
2234
|
}
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
* package on disk, and only once that succeeds does it `process.exit(0)` —
|
|
20
20
|
* the OS supervisor (launchd `KeepAlive` / systemd `Restart=always`, see
|
|
21
21
|
* `daemon/AGENTS.md`'s crash-recovery model) relaunches the daemon, which
|
|
22
|
-
* then boots the new code. Clients do not need to be told anything:
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* then boots the new code. Clients do not need to be told anything: a socket
|
|
23
|
+
* client re-probes and reconnects when the daemon relaunches, and the
|
|
24
|
+
* scheduler's atomic `(routine, scheduledFor)` claim (see
|
|
25
|
+
* `docs/specifications.md` §Scheduling & execution singularity) means a routine
|
|
26
|
+
* mid-fire at the moment of exit is deduped safely across the restart rather
|
|
27
|
+
* than double-fired.
|
|
28
28
|
*
|
|
29
29
|
* Fail-closed is the whole point: every step below that can fail — the
|
|
30
30
|
* registry check, the install, the post-install verify — leaves the OLD
|
|
@@ -93,41 +93,42 @@ export interface SelfUpdateDeps {
|
|
|
93
93
|
export declare function installAndVerifyDefault(metadata: NpmLatestMetadata, packageRoot: string, signal: AbortSignal): Promise<void>;
|
|
94
94
|
/**
|
|
95
95
|
* Core self-update decision + action, shared by the periodic tick
|
|
96
|
-
* ({@link SelfUpdateService.onTick}) and the on-demand
|
|
97
|
-
* (
|
|
98
|
-
* version-skew
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
96
|
+
* ({@link SelfUpdateService.onTick}) and the on-demand trigger
|
|
97
|
+
* ({@link triggerSelfUpdateInBackground}) — one implementation, so a
|
|
98
|
+
* version-skew "update now" caller runs exactly the same fail-closed logic as the
|
|
99
|
+
* scheduled sweep. Returns rather than throws so callers decide their own exit
|
|
100
|
+
* timing (the periodic service exits immediately; an on-demand caller that must
|
|
101
|
+
* respond to a client BEFORE exiting delays via {@link scheduleSelfUpdateExit}).
|
|
102
|
+
* Concurrent callers share one in-flight attempt rather than racing separate
|
|
103
|
+
* installs.
|
|
104
104
|
*/
|
|
105
105
|
export declare function attemptSelfUpdateAndExit(ctx: DaemonContext, signal: AbortSignal, deps?: SelfUpdateDeps): Promise<SelfUpdateOutcome>;
|
|
106
106
|
/**
|
|
107
107
|
* Schedule the process exit for a verified self-update, exactly once, no
|
|
108
108
|
* matter how many callers observe `outcome.updated` on the shared
|
|
109
|
-
* `inFlightAttempt` promise. The periodic tick and an on-demand
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* protects EVERY caller's in-flight response, not just the IPC handler's own.
|
|
109
|
+
* `inFlightAttempt` promise. The periodic tick and an on-demand caller can both
|
|
110
|
+
* be awaiting that SAME promise — if the tick's continuation ran an immediate
|
|
111
|
+
* `process.exit(0)` while an on-demand caller's continuation had not yet flushed
|
|
112
|
+
* its response to a client, the tick's exit could win the race and the client
|
|
113
|
+
* would see a closed socket before any response (found in review, PHNX-3695).
|
|
114
|
+
* Routing every caller through this one guarded, always-delayed scheduling point
|
|
115
|
+
* means the delay protects EVERY caller's in-flight response.
|
|
117
116
|
*/
|
|
118
117
|
export declare function scheduleSelfUpdateExit(): void;
|
|
119
118
|
/**
|
|
120
|
-
* Fire
|
|
121
|
-
* promise WITHOUT the caller having to await it
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* browser
|
|
119
|
+
* Fire an on-demand self-update in the BACKGROUND and return its (bounded)
|
|
120
|
+
* promise WITHOUT the caller having to await it — the reusable primitive for a
|
|
121
|
+
* "update now" trigger that must respond to a client before the daemon exits.
|
|
122
|
+
* Awaiting the full check→download→install→verify inline would park the caller
|
|
123
|
+
* for tens of seconds (worst case ~15 min), the client-stall PHNX-3605 was
|
|
124
|
+
* written to prevent; instead a trigger responds "triggered" immediately and
|
|
125
|
+
* lets this run in the background — the daemon does install→verify→exit(0) on its
|
|
126
|
+
* own, the OS supervisor relaunches it, and the client reconnects.
|
|
127
|
+
*
|
|
128
|
+
* (Its one former production caller was the `request-self-update` verb on the
|
|
129
|
+
* daemon's browser IPC socket, removed with the standalone `browser` CLI in
|
|
130
|
+
* PHNX-4101. The primitive stays — tested and reusable — for a future on-demand
|
|
131
|
+
* trigger; the periodic tick does not use it, exiting inline instead.)
|
|
131
132
|
*
|
|
132
133
|
* The work still shares the module-level {@link attemptSelfUpdateAndExit}
|
|
133
134
|
* `inFlightAttempt` guard, so a concurrent trigger (or the periodic tick) can't
|
|
@@ -138,7 +139,7 @@ export declare function scheduleSelfUpdateExit(): void;
|
|
|
138
139
|
* `runSelfUpdateAttempt` already turns an install/verify failure into a
|
|
139
140
|
* not-updated outcome that leaves the running daemon untouched. The caller
|
|
140
141
|
* schedules the one decoupled {@link scheduleSelfUpdateExit} off the returned
|
|
141
|
-
* promise once `updated` is true, so the exit still fires after the
|
|
142
|
+
* promise once `updated` is true, so the exit still fires after the caller's
|
|
142
143
|
* response has flushed.
|
|
143
144
|
*/
|
|
144
145
|
export declare function triggerSelfUpdateInBackground(ctx: DaemonContext, deps?: SelfUpdateDeps): Promise<SelfUpdateOutcome>;
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
* package on disk, and only once that succeeds does it `process.exit(0)` —
|
|
20
20
|
* the OS supervisor (launchd `KeepAlive` / systemd `Restart=always`, see
|
|
21
21
|
* `daemon/AGENTS.md`'s crash-recovery model) relaunches the daemon, which
|
|
22
|
-
* then boots the new code. Clients do not need to be told anything:
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* then boots the new code. Clients do not need to be told anything: a socket
|
|
23
|
+
* client re-probes and reconnects when the daemon relaunches, and the
|
|
24
|
+
* scheduler's atomic `(routine, scheduledFor)` claim (see
|
|
25
|
+
* `docs/specifications.md` §Scheduling & execution singularity) means a routine
|
|
26
|
+
* mid-fire at the moment of exit is deduped safely across the restart rather
|
|
27
|
+
* than double-fired.
|
|
28
28
|
*
|
|
29
29
|
* Fail-closed is the whole point: every step below that can fail — the
|
|
30
30
|
* registry check, the install, the post-install verify — leaves the OLD
|
|
@@ -51,10 +51,13 @@ const SELF_UPDATE_TICK_MS = 75 * 60_000;
|
|
|
51
51
|
/**
|
|
52
52
|
* Hard cap per tick: a real download + npm/bun install + verify can
|
|
53
53
|
* legitimately take minutes on a slow link. 15 minutes matches the task's
|
|
54
|
-
* stated budget and is short relative to the ~75min cadence. Exported so
|
|
55
|
-
* on-demand
|
|
54
|
+
* stated budget and is short relative to the ~75min cadence. Exported so an
|
|
55
|
+
* on-demand self-update trigger ({@link triggerSelfUpdateInBackground}) can bound
|
|
56
56
|
* its own `AbortController` on the SAME budget the periodic tick runs under —
|
|
57
57
|
* one deadline, not two independently-tuned numbers that could drift apart.
|
|
58
|
+
* (Its former transport, the `request-self-update` verb on the daemon's browser
|
|
59
|
+
* IPC socket, left with the standalone `browser` CLI in PHNX-4101; the mechanism
|
|
60
|
+
* stays for a future on-demand caller.)
|
|
58
61
|
*/
|
|
59
62
|
const SELF_UPDATE_DEADLINE_MS = 15 * 60_000;
|
|
60
63
|
/**
|
|
@@ -193,14 +196,14 @@ function defaultSelfUpdateDeps() {
|
|
|
193
196
|
let inFlightAttempt = null;
|
|
194
197
|
/**
|
|
195
198
|
* Core self-update decision + action, shared by the periodic tick
|
|
196
|
-
* ({@link SelfUpdateService.onTick}) and the on-demand
|
|
197
|
-
* (
|
|
198
|
-
* version-skew
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
199
|
+
* ({@link SelfUpdateService.onTick}) and the on-demand trigger
|
|
200
|
+
* ({@link triggerSelfUpdateInBackground}) — one implementation, so a
|
|
201
|
+
* version-skew "update now" caller runs exactly the same fail-closed logic as the
|
|
202
|
+
* scheduled sweep. Returns rather than throws so callers decide their own exit
|
|
203
|
+
* timing (the periodic service exits immediately; an on-demand caller that must
|
|
204
|
+
* respond to a client BEFORE exiting delays via {@link scheduleSelfUpdateExit}).
|
|
205
|
+
* Concurrent callers share one in-flight attempt rather than racing separate
|
|
206
|
+
* installs.
|
|
204
207
|
*/
|
|
205
208
|
export async function attemptSelfUpdateAndExit(ctx, signal, deps = defaultSelfUpdateDeps()) {
|
|
206
209
|
if (inFlightAttempt)
|
|
@@ -292,14 +295,13 @@ let exitScheduled = false;
|
|
|
292
295
|
/**
|
|
293
296
|
* Schedule the process exit for a verified self-update, exactly once, no
|
|
294
297
|
* matter how many callers observe `outcome.updated` on the shared
|
|
295
|
-
* `inFlightAttempt` promise. The periodic tick and an on-demand
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* protects EVERY caller's in-flight response, not just the IPC handler's own.
|
|
298
|
+
* `inFlightAttempt` promise. The periodic tick and an on-demand caller can both
|
|
299
|
+
* be awaiting that SAME promise — if the tick's continuation ran an immediate
|
|
300
|
+
* `process.exit(0)` while an on-demand caller's continuation had not yet flushed
|
|
301
|
+
* its response to a client, the tick's exit could win the race and the client
|
|
302
|
+
* would see a closed socket before any response (found in review, PHNX-3695).
|
|
303
|
+
* Routing every caller through this one guarded, always-delayed scheduling point
|
|
304
|
+
* means the delay protects EVERY caller's in-flight response.
|
|
303
305
|
*/
|
|
304
306
|
export function scheduleSelfUpdateExit() {
|
|
305
307
|
if (exitScheduled)
|
|
@@ -308,17 +310,19 @@ export function scheduleSelfUpdateExit() {
|
|
|
308
310
|
setTimeout(() => process.exit(0), SELF_UPDATE_EXIT_DELAY_MS);
|
|
309
311
|
}
|
|
310
312
|
/**
|
|
311
|
-
* Fire
|
|
312
|
-
* promise WITHOUT the caller having to await it
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* browser
|
|
313
|
+
* Fire an on-demand self-update in the BACKGROUND and return its (bounded)
|
|
314
|
+
* promise WITHOUT the caller having to await it — the reusable primitive for a
|
|
315
|
+
* "update now" trigger that must respond to a client before the daemon exits.
|
|
316
|
+
* Awaiting the full check→download→install→verify inline would park the caller
|
|
317
|
+
* for tens of seconds (worst case ~15 min), the client-stall PHNX-3605 was
|
|
318
|
+
* written to prevent; instead a trigger responds "triggered" immediately and
|
|
319
|
+
* lets this run in the background — the daemon does install→verify→exit(0) on its
|
|
320
|
+
* own, the OS supervisor relaunches it, and the client reconnects.
|
|
321
|
+
*
|
|
322
|
+
* (Its one former production caller was the `request-self-update` verb on the
|
|
323
|
+
* daemon's browser IPC socket, removed with the standalone `browser` CLI in
|
|
324
|
+
* PHNX-4101. The primitive stays — tested and reusable — for a future on-demand
|
|
325
|
+
* trigger; the periodic tick does not use it, exiting inline instead.)
|
|
322
326
|
*
|
|
323
327
|
* The work still shares the module-level {@link attemptSelfUpdateAndExit}
|
|
324
328
|
* `inFlightAttempt` guard, so a concurrent trigger (or the periodic tick) can't
|
|
@@ -329,7 +333,7 @@ export function scheduleSelfUpdateExit() {
|
|
|
329
333
|
* `runSelfUpdateAttempt` already turns an install/verify failure into a
|
|
330
334
|
* not-updated outcome that leaves the running daemon untouched. The caller
|
|
331
335
|
* schedules the one decoupled {@link scheduleSelfUpdateExit} off the returned
|
|
332
|
-
* promise once `updated` is true, so the exit still fires after the
|
|
336
|
+
* promise once `updated` is true, so the exit still fires after the caller's
|
|
333
337
|
* response has flushed.
|
|
334
338
|
*/
|
|
335
339
|
export function triggerSelfUpdateInBackground(ctx, deps = defaultSelfUpdateDeps()) {
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
* know it is being supervised.
|
|
15
15
|
*/
|
|
16
16
|
import type { DaemonServiceId } from '../daemon-services.js';
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Lifecycle state a supervised service can be in. There is no `parked` state:
|
|
19
|
+
* a throw keeps the service `running` (it retries on the next tick) and a
|
|
20
|
+
* deadline breach exits the whole daemon for a supervised OS restart (PHNX-4116).
|
|
21
|
+
*/
|
|
22
|
+
export type ServiceState = 'idle' | 'running' | 'stopped';
|
|
19
23
|
/** A service's most recently observed health, as reported by the supervisor. */
|
|
20
24
|
export interface ServiceHealth {
|
|
21
25
|
state: ServiceState;
|
|
@@ -42,12 +46,12 @@ export interface DaemonService {
|
|
|
42
46
|
export interface PeriodicService extends DaemonService {
|
|
43
47
|
readonly intervalMs: number;
|
|
44
48
|
/**
|
|
45
|
-
* Hard cap per tick. An over-budget tick is
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* (PHNX-
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
* Hard cap per tick. An over-budget tick is a HANG that cannot be retried
|
|
50
|
+
* in-process (its promise may never settle), so the supervisor aborts the
|
|
51
|
+
* tick's {@link AbortSignal} and EXITS the daemon (code 70) for a supervised
|
|
52
|
+
* systemd/launchd restart (PHNX-4116) — there is no park or in-process backoff.
|
|
53
|
+
* A tick that awaits `signal` (or forwards it to its I/O) can observe the
|
|
54
|
+
* deadline and unwind cleanly before the process exits.
|
|
51
55
|
*/
|
|
52
56
|
readonly deadlineMs: number;
|
|
53
57
|
/**
|
|
@@ -1,52 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ServiceSupervisor (RUSH-3193 P1).
|
|
2
|
+
* ServiceSupervisor (RUSH-3193 P1, PHNX-4116).
|
|
3
3
|
*
|
|
4
4
|
* Owns the timer for every registered `PeriodicService`, replacing the bare
|
|
5
|
-
* `setInterval` closures in `runDaemon()`. Two failure modes motivated it,
|
|
6
|
-
*
|
|
5
|
+
* `setInterval` closures in `runDaemon()`. Two failure modes motivated it, both
|
|
6
|
+
* observed in production, and each is now handled without ever leaving a service
|
|
7
|
+
* silently dark:
|
|
7
8
|
*
|
|
8
9
|
* - A throw escaping a tick's local try/catch used to hit the process-wide
|
|
9
|
-
* `uncaughtException` handler and `process.exit(1)` the whole daemon,
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
* (
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* is drained separately in the background; `finishTick` is version-guarded so
|
|
24
|
-
* its late settlement can never disturb the fresh tick the restart started.
|
|
10
|
+
* `uncaughtException` handler and `process.exit(1)` the whole daemon, taking
|
|
11
|
+
* every OTHER service down with it. Here a thrown tick is caught per-service,
|
|
12
|
+
* recorded via `recordFailure`, and the service keeps ticking on its own
|
|
13
|
+
* interval — a throw is recoverable, so it is retried in place on the next
|
|
14
|
+
* tick and no sibling is disturbed.
|
|
15
|
+
* - A tick that HANGS on an unbounded await (SSH, keychain) used to latch its
|
|
16
|
+
* in-flight guard `true` forever, silently freezing that one service for the
|
|
17
|
+
* daemon's life (observed ~51h). A hang cannot be retried in-process — the
|
|
18
|
+
* promise may never settle — so when a tick (or a `start()`/`restart()`
|
|
19
|
+
* lifecycle call) BREACHES its deadline the supervisor records the cause,
|
|
20
|
+
* flushes it to disk, and EXITS the process (code 70). systemd
|
|
21
|
+
* (`Restart=always`, `RestartSec=30`, `StartLimitIntervalSec=0`) and launchd
|
|
22
|
+
* (`KeepAlive` + `ThrottleInterval=30`) restart the daemon within ~30s and the
|
|
23
|
+
* wedged service comes back healthy with it.
|
|
25
24
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
25
|
+
* This is the PHNX-4116 model: there is NO `parked` state and NO in-process
|
|
26
|
+
* backoff restart. A supervised service is `idle` before start, `running` while
|
|
27
|
+
* ticking, or `stopped` after a live disable / shutdown — a hang is not a fourth
|
|
28
|
+
* state to sit in, it is a reason to hand the daemon back to its OS supervisor,
|
|
29
|
+
* which is what actually "just works" like systemd/launchd. `start()`/`stop()`/
|
|
30
|
+
* `restart()` stay bounded by {@link ServiceSupervisorOptions.lifecycleDeadlineMs}
|
|
31
|
+
* so a wedged bind or close cannot stall daemon startup or shutdown; a start /
|
|
32
|
+
* restart lifecycle breach exits the same way a tick breach does.
|
|
32
33
|
*/
|
|
33
34
|
import type { DaemonServiceId } from '../daemon-services.js';
|
|
34
35
|
import type { DaemonContext, DaemonService, ServiceHealth } from './service.js';
|
|
35
36
|
interface ServiceSupervisorOptions {
|
|
36
|
-
/** Consecutive thrown tick failures before a service is parked. Deadline breaches park immediately. Default 3. */
|
|
37
|
-
parkAfterFailures?: number;
|
|
38
|
-
/** First restart backoff delay, doubled on each further failed restart attempt. Default 5s. */
|
|
39
|
-
backoffBaseMs?: number;
|
|
40
|
-
/** Backoff ceiling. Default 5 minutes. */
|
|
41
|
-
backoffMaxMs?: number;
|
|
42
37
|
/**
|
|
43
38
|
* Hard cap on a service's `start()`/`stop()`/`restart()` call (PHNX-3608). A
|
|
44
39
|
* wedged bind/close would otherwise stall `startAll()` (which awaits each
|
|
45
|
-
* `startOne` in turn) or `stopAll()` at shutdown. A start/restart that
|
|
46
|
-
*
|
|
47
|
-
* it is logged and the service is left marked stopped. Default 30s.
|
|
40
|
+
* `startOne` in turn) or `stopAll()` at shutdown. A start/restart that breaches
|
|
41
|
+
* it exits the process for a supervised restart (PHNX-4116); a stop that
|
|
42
|
+
* breaches it is logged and the service is left marked stopped. Default 30s.
|
|
48
43
|
*/
|
|
49
44
|
lifecycleDeadlineMs?: number;
|
|
45
|
+
/**
|
|
46
|
+
* How the supervisor ends the process on a deadline breach. Defaults to
|
|
47
|
+
* `process.exit`; injected in tests so a breach is observable without actually
|
|
48
|
+
* exiting the test runner (PHNX-4116).
|
|
49
|
+
*/
|
|
50
|
+
exit?: (code: number) => never;
|
|
50
51
|
}
|
|
51
52
|
interface RegisterServiceOptions {
|
|
52
53
|
/** Register the lifecycle owner but leave it stopped until a live enable. */
|
|
@@ -55,10 +56,8 @@ interface RegisterServiceOptions {
|
|
|
55
56
|
export declare class ServiceSupervisor {
|
|
56
57
|
private readonly registry;
|
|
57
58
|
private ctx;
|
|
58
|
-
private readonly parkAfterFailures;
|
|
59
|
-
private readonly backoffBaseMs;
|
|
60
|
-
private readonly backoffMaxMs;
|
|
61
59
|
private readonly lifecycleDeadlineMs;
|
|
60
|
+
private readonly exit;
|
|
62
61
|
constructor(opts?: ServiceSupervisorOptions);
|
|
63
62
|
/**
|
|
64
63
|
* Race `op` against a deadline. On breach the returned promise rejects with a
|
|
@@ -87,7 +86,11 @@ export declare class ServiceSupervisor {
|
|
|
87
86
|
startAll(ctx: DaemonContext): Promise<void>;
|
|
88
87
|
/** Stop every registered service and clear all timers. */
|
|
89
88
|
stopAll(): Promise<void>;
|
|
90
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Force one service to restart right now — drives `agents daemon services
|
|
91
|
+
* restart <id>` (RUSH-3193 P4). A plain stop + start, with no intermediate
|
|
92
|
+
* `parked` state (PHNX-4116).
|
|
93
|
+
*/
|
|
91
94
|
restartOne(id: DaemonServiceId): Promise<void>;
|
|
92
95
|
/** Whether `id` has a lifecycle owner on this supervisor, running or stopped. */
|
|
93
96
|
isRegistered(id: DaemonServiceId): boolean;
|
|
@@ -95,8 +98,7 @@ export declare class ServiceSupervisor {
|
|
|
95
98
|
registeredIds(): DaemonServiceId[];
|
|
96
99
|
/**
|
|
97
100
|
* Resolve when the service's current tick has really settled. Daemon control
|
|
98
|
-
* edges use this to queue a requested live transition without polling
|
|
99
|
-
* treating a deadline race as cancellation.
|
|
101
|
+
* edges use this to queue a requested live transition without polling.
|
|
100
102
|
*/
|
|
101
103
|
awaitIdle(id: DaemonServiceId): Promise<void>;
|
|
102
104
|
/**
|
|
@@ -117,22 +119,31 @@ export declare class ServiceSupervisor {
|
|
|
117
119
|
private stopOne;
|
|
118
120
|
private scheduleTimer;
|
|
119
121
|
/**
|
|
120
|
-
* Run one tick under a hard deadline (PHNX-3608). The tick receives
|
|
121
|
-
* `AbortSignal` that is aborted when the deadline elapses, so a cooperating
|
|
122
|
-
* tick can bound its own I/O and unwind. The deadline itself is
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
122
|
+
* Run one tick under a hard deadline (PHNX-3608, PHNX-4116). The tick receives
|
|
123
|
+
* an `AbortSignal` that is aborted when the deadline elapses, so a cooperating
|
|
124
|
+
* tick can bound its own I/O and unwind. The deadline itself is enforced with
|
|
125
|
+
* `Promise.race` — JS cannot forcibly cancel an arbitrary await.
|
|
126
|
+
*
|
|
127
|
+
* A tick that THROWS is recoverable: it is recorded and the interval keeps
|
|
128
|
+
* firing, so the service retries on its next tick. A tick that BREACHES its
|
|
129
|
+
* deadline is a hang that can never be retried in-process (the promise may
|
|
130
|
+
* never settle), so the supervisor exits the process (`exitForRestart`) and
|
|
131
|
+
* lets systemd/launchd restart the whole daemon.
|
|
130
132
|
*/
|
|
131
133
|
private runTick;
|
|
132
|
-
private park;
|
|
133
|
-
private scheduleRestart;
|
|
134
|
-
private attemptRestart;
|
|
135
134
|
private recordFailure;
|
|
136
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Record a deadline breach durably and exit the process so systemd/launchd
|
|
137
|
+
* restart the whole daemon (PHNX-4116). `recordSubsystemError` writes
|
|
138
|
+
* `health.json` synchronously (atomic write under a file lock), so the cause is
|
|
139
|
+
* on disk before we exit; `recordDaemonRestart` appends to the restart ledger
|
|
140
|
+
* `agents daemon status` reads for "restarts in the last 24h". Every timer is
|
|
141
|
+
* frozen first so no further tick fires between this decision and process death
|
|
142
|
+
* — and so an injected test `exit` (which does not actually exit) still observes
|
|
143
|
+
* exactly one call.
|
|
144
|
+
*/
|
|
145
|
+
private exitForRestart;
|
|
146
|
+
/** Clear every service's interval / startup timer — the process is exiting. */
|
|
147
|
+
private freezeTimers;
|
|
137
148
|
}
|
|
138
149
|
export {};
|