@ours.network/fleet 0.10.4 → 0.11.0
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/dist/application/fleet-query-service.d.ts +11 -0
- package/dist/application/fleet-query-service.js +9 -1
- package/dist/cli.js +200 -3
- package/dist/config.d.ts +2 -0
- package/dist/config.js +3 -1
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +22 -0
- package/dist/duration.d.ts +5 -0
- package/dist/duration.js +20 -0
- package/dist/ops.d.ts +16 -0
- package/dist/ops.js +112 -3
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +1 -0
- package/dist/resolved-plan.js +7 -0
- package/dist/watchdog/alerts.d.ts +34 -0
- package/dist/watchdog/alerts.js +78 -0
- package/dist/watchdog/briefing.d.ts +65 -0
- package/dist/watchdog/briefing.js +181 -0
- package/dist/watchdog/config.d.ts +49 -0
- package/dist/watchdog/config.js +114 -0
- package/dist/watchdog/query.d.ts +78 -0
- package/dist/watchdog/query.js +124 -0
- package/dist/watchdog/report.d.ts +53 -0
- package/dist/watchdog/report.js +126 -0
- package/dist/watchdog/run.d.ts +61 -0
- package/dist/watchdog/run.js +318 -0
- package/dist/watchdog/scheduler.d.ts +105 -0
- package/dist/watchdog/scheduler.js +244 -0
- package/dist/watchdog/service.d.ts +46 -0
- package/dist/watchdog/service.js +179 -0
- package/dist/watchdog/store.d.ts +85 -0
- package/dist/watchdog/store.js +226 -0
- package/dist/web/runtime.js +46 -2
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.js +18 -0
- package/dist/web-app/assets/{TerminalView-DcImdrI1.js → TerminalView-BvcIkuIF.js} +1 -1
- package/dist/web-app/assets/index-B-jtLAkp.css +1 -0
- package/dist/web-app/assets/index-CUN7ksTw.js +9 -0
- package/dist/web-app/index.html +2 -2
- package/package.json +1 -1
- package/dist/web-app/assets/index-BokQN1Ao.js +0 -9
- package/dist/web-app/assets/index-lAXzaOZM.css +0 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { FleetError } from '../application/errors.js';
|
|
4
|
+
import { ROLE_NAME_RE } from '../config.js';
|
|
5
|
+
import { watchdogsRoot } from '../paths.js';
|
|
6
|
+
import { WATCHDOG_STATUS_RANK } from './alerts.js';
|
|
7
|
+
import { readSchedulerState } from './scheduler.js';
|
|
8
|
+
import { listRuns, readReport } from './store.js';
|
|
9
|
+
/**
|
|
10
|
+
* Needs-attention integration (Task 19): worst current finding per role across
|
|
11
|
+
* every configured watchdog, for FleetQueryService.status() to fold into a
|
|
12
|
+
* role's problems. An `error`-status report carries no role evidence (the run
|
|
13
|
+
* itself failed) so it's skipped outright, matching alerts.ts's
|
|
14
|
+
* reconcileLedger rule. Healthy/idle findings (rank 0) never surface here —
|
|
15
|
+
* only actionable anomalies do. "Worst" is decided by WATCHDOG_STATUS_RANK;
|
|
16
|
+
* ties keep whichever watchdog was seen first. Takes `latestReport` as a
|
|
17
|
+
* parameter (rather than importing store.ts directly) so it stays a pure,
|
|
18
|
+
* disk-free function for unit testing; runtime.ts wires the real store.
|
|
19
|
+
*
|
|
20
|
+
* Skips disabled watchdogs (finding #5): a watchdog turned off in config
|
|
21
|
+
* still has its last stored report sitting on disk, and without this check
|
|
22
|
+
* that stale report's findings would pin roles in "Needs attention" forever
|
|
23
|
+
* — an operator disabling a noisy/broken watchdog has no way to make the
|
|
24
|
+
* findings it already produced go away.
|
|
25
|
+
*/
|
|
26
|
+
export function buildWatchdogFindings(cfg, latestReport) {
|
|
27
|
+
const findings = new Map();
|
|
28
|
+
for (const wd of cfg.watchdogs) {
|
|
29
|
+
if (!wd.enabled)
|
|
30
|
+
continue;
|
|
31
|
+
const report = latestReport(wd.name);
|
|
32
|
+
if (!report || report.status === 'error')
|
|
33
|
+
continue;
|
|
34
|
+
for (const finding of report.roles) {
|
|
35
|
+
const rank = WATCHDOG_STATUS_RANK[finding.status];
|
|
36
|
+
if (rank <= 0)
|
|
37
|
+
continue;
|
|
38
|
+
const existing = findings.get(finding.role);
|
|
39
|
+
if (existing && WATCHDOG_STATUS_RANK[existing.status] >= rank)
|
|
40
|
+
continue;
|
|
41
|
+
findings.set(finding.role, { watchdog: wd.name, status: finding.status, reason: finding.reason ?? '' });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return findings;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Memoizes a findings-map builder with a short TTL (mirrors runtime.ts's
|
|
48
|
+
* cachedConfigProvider pattern). status() calls its injected watchdogFindings
|
|
49
|
+
* provider once per role, so an unmemoized thunk turns a single list() sweep
|
|
50
|
+
* into O(roles x watchdogs) disk reads, repeated every 1-3s of console
|
|
51
|
+
* polling. `now` is injectable (defaults to Date.now) so the TTL boundary is
|
|
52
|
+
* unit-testable without real timers.
|
|
53
|
+
*/
|
|
54
|
+
export function cachedWatchdogFindingsProvider(build, ttlMs, now = Date.now) {
|
|
55
|
+
let cached;
|
|
56
|
+
return () => {
|
|
57
|
+
const at = now();
|
|
58
|
+
if (!cached || at - cached.at >= ttlMs)
|
|
59
|
+
cached = { at, value: build() };
|
|
60
|
+
return cached.value;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const DEFAULT_REPORTS_LIMIT = 50;
|
|
64
|
+
const MAX_REPORTS_LIMIT = 500;
|
|
65
|
+
/**
|
|
66
|
+
* Read-only view over watchdog config + on-disk scheduler state + stored
|
|
67
|
+
* reports, for the authenticated web console. Never mutates: `list()` reads
|
|
68
|
+
* config plus `readSchedulerState`/`listRuns` (both already tolerant of a
|
|
69
|
+
* corrupt/missing state.json or an empty/nonexistent reports dir, per
|
|
70
|
+
* store.ts and scheduler.ts), and `reports()`/`report()` reject unknown
|
|
71
|
+
* watchdog names before touching disk.
|
|
72
|
+
*/
|
|
73
|
+
export class WatchdogQueryService {
|
|
74
|
+
cfgProvider;
|
|
75
|
+
constructor(cfgProvider) {
|
|
76
|
+
this.cfgProvider = cfgProvider;
|
|
77
|
+
}
|
|
78
|
+
list() {
|
|
79
|
+
const cfg = this.cfgProvider();
|
|
80
|
+
const watchdogs = cfg.watchdogs.map((wd) => {
|
|
81
|
+
const state = readSchedulerState(wd.name);
|
|
82
|
+
const [latest = null] = listRuns(wd.name);
|
|
83
|
+
return {
|
|
84
|
+
name: wd.name, enabled: wd.enabled, heldDown: state.heldDown, heldSince: state.heldSince ?? null,
|
|
85
|
+
intervalMs: wd.intervalMs,
|
|
86
|
+
coordinator: wd.coordinator, watch: wd.watch,
|
|
87
|
+
lastRunAt: state.lastRunAt ?? null, nextRunAt: state.nextRunAt ?? null,
|
|
88
|
+
latest,
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
return { watchdogs };
|
|
92
|
+
}
|
|
93
|
+
reports(name, limit = DEFAULT_REPORTS_LIMIT) {
|
|
94
|
+
this.requireKnown(name);
|
|
95
|
+
const n = Number.isFinite(limit) ? Math.floor(limit) : DEFAULT_REPORTS_LIMIT;
|
|
96
|
+
const clamped = Math.min(Math.max(n, 1), MAX_REPORTS_LIMIT);
|
|
97
|
+
return { runs: listRuns(name).slice(0, clamped) };
|
|
98
|
+
}
|
|
99
|
+
report(name, runId) {
|
|
100
|
+
this.requireKnown(name);
|
|
101
|
+
// readReport parses the stored file with JSON.parse and returns the parsed object;
|
|
102
|
+
// fastify re-serializes it for the HTTP response, so what's actually verifiable (and
|
|
103
|
+
// what the tests assert) is deep equality with the stored JSON, not byte-for-byte
|
|
104
|
+
// identity of the response bytes against the file on disk.
|
|
105
|
+
const found = readReport(name, runId);
|
|
106
|
+
if (!found)
|
|
107
|
+
throw new FleetError('role_not_found', `no such report '${runId}' for watchdog '${name}'`);
|
|
108
|
+
return found;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Known means: configured today, OR a store directory already exists for a
|
|
112
|
+
* watchdog that used to be configured (its history should stay readable).
|
|
113
|
+
* The regex check runs before any filesystem lookup so a hostile `name`
|
|
114
|
+
* (path separators, '..', etc.) can never reach `join(watchdogsRoot(), name)`.
|
|
115
|
+
*/
|
|
116
|
+
requireKnown(name) {
|
|
117
|
+
const cfg = this.cfgProvider();
|
|
118
|
+
if (cfg.watchdogs.some(wd => wd.name === name))
|
|
119
|
+
return;
|
|
120
|
+
if (ROLE_NAME_RE.test(name) && existsSync(join(watchdogsRoot(), name)))
|
|
121
|
+
return;
|
|
122
|
+
throw new FleetError('role_not_found', `no such watchdog '${name}'`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export declare const WATCHDOG_ROLE_STATUSES: readonly ["healthy", "idle", "stale", "blocked", "off_briefing", "unreachable", "unknown"];
|
|
2
|
+
export type WatchdogRoleStatus = typeof WATCHDOG_ROLE_STATUSES[number];
|
|
3
|
+
export interface WatchdogEvidence {
|
|
4
|
+
source: string;
|
|
5
|
+
detail: string;
|
|
6
|
+
observed_at: string;
|
|
7
|
+
}
|
|
8
|
+
export interface WatchdogFinding {
|
|
9
|
+
role: string;
|
|
10
|
+
status: WatchdogRoleStatus;
|
|
11
|
+
reason?: string;
|
|
12
|
+
evidence?: WatchdogEvidence[];
|
|
13
|
+
alerted?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface WatchdogAlert {
|
|
16
|
+
role: string;
|
|
17
|
+
code: string;
|
|
18
|
+
coordinator: string;
|
|
19
|
+
sent_at: string;
|
|
20
|
+
}
|
|
21
|
+
export type WatchdogReportStatus = 'ok' | 'anomalies' | 'error';
|
|
22
|
+
export interface WatchdogReport {
|
|
23
|
+
schema_version: 1;
|
|
24
|
+
watchdog: string;
|
|
25
|
+
run_id: string;
|
|
26
|
+
started_at: string;
|
|
27
|
+
finished_at: string;
|
|
28
|
+
status: WatchdogReportStatus;
|
|
29
|
+
summary: {
|
|
30
|
+
checked: number;
|
|
31
|
+
healthy: number;
|
|
32
|
+
idle: number;
|
|
33
|
+
anomalies: number;
|
|
34
|
+
};
|
|
35
|
+
roles: WatchdogFinding[];
|
|
36
|
+
alerts: WatchdogAlert[];
|
|
37
|
+
error: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare function validateWatchdogReport(v: unknown): string[];
|
|
40
|
+
export declare function normalizeWatchdogReport(r: WatchdogReport, ctx: {
|
|
41
|
+
watchdog: string;
|
|
42
|
+
run_id: string;
|
|
43
|
+
}): WatchdogReport;
|
|
44
|
+
export declare function errorReport(ctx: {
|
|
45
|
+
watchdog: string;
|
|
46
|
+
run_id: string;
|
|
47
|
+
started_at: string;
|
|
48
|
+
finished_at: string;
|
|
49
|
+
error: string;
|
|
50
|
+
tail?: string;
|
|
51
|
+
}): WatchdogReport & {
|
|
52
|
+
tail?: string;
|
|
53
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export const WATCHDOG_ROLE_STATUSES = ['healthy', 'idle', 'stale', 'blocked', 'off_briefing', 'unreachable', 'unknown'];
|
|
2
|
+
const SUMMARY_KEYS = ['checked', 'healthy', 'idle', 'anomalies'];
|
|
3
|
+
function isPlainObject(v) {
|
|
4
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
5
|
+
}
|
|
6
|
+
export function validateWatchdogReport(v) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
if (!isPlainObject(v)) {
|
|
9
|
+
errors.push('report: expected an object');
|
|
10
|
+
return errors;
|
|
11
|
+
}
|
|
12
|
+
const r = v;
|
|
13
|
+
if (r.schema_version !== 1)
|
|
14
|
+
errors.push('schema_version: expected 1');
|
|
15
|
+
for (const key of ['watchdog', 'run_id', 'started_at', 'finished_at'])
|
|
16
|
+
if (typeof r[key] !== 'string')
|
|
17
|
+
errors.push(`${key}: expected a string`);
|
|
18
|
+
if (r.status !== 'ok' && r.status !== 'anomalies' && r.status !== 'error')
|
|
19
|
+
errors.push(`status: expected ok|anomalies|error, got '${String(r.status)}'`);
|
|
20
|
+
if (!isPlainObject(r.summary)) {
|
|
21
|
+
errors.push('summary: expected an object');
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
for (const key of SUMMARY_KEYS)
|
|
25
|
+
if (typeof r.summary[key] !== 'number')
|
|
26
|
+
errors.push(`summary.${key}: expected a number`);
|
|
27
|
+
}
|
|
28
|
+
if (!Array.isArray(r.roles)) {
|
|
29
|
+
errors.push('roles: expected an array');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
r.roles.forEach((role, i) => {
|
|
33
|
+
if (!isPlainObject(role)) {
|
|
34
|
+
errors.push(`roles[${i}]: expected an object`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (typeof role.role !== 'string')
|
|
38
|
+
errors.push(`roles[${i}].role: expected a string`);
|
|
39
|
+
if (!WATCHDOG_ROLE_STATUSES.includes(role.status))
|
|
40
|
+
errors.push(`roles[${i}].status: expected one of ${WATCHDOG_ROLE_STATUSES.join('|')}, got '${String(role.status)}'`);
|
|
41
|
+
if (role.status !== 'healthy' && role.status !== 'idle' && !role.reason)
|
|
42
|
+
errors.push(`roles[${i}]: non-healthy finding requires a reason`);
|
|
43
|
+
if (role.reason !== undefined && typeof role.reason !== 'string')
|
|
44
|
+
errors.push(`roles[${i}].reason: expected a string`);
|
|
45
|
+
if (role.evidence !== undefined) {
|
|
46
|
+
if (!Array.isArray(role.evidence)) {
|
|
47
|
+
errors.push(`roles[${i}].evidence: expected an array`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
role.evidence.forEach((ev, j) => {
|
|
51
|
+
if (!isPlainObject(ev)) {
|
|
52
|
+
errors.push(`roles[${i}].evidence[${j}]: expected an object`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const key of ['source', 'detail', 'observed_at'])
|
|
56
|
+
if (typeof ev[key] !== 'string')
|
|
57
|
+
errors.push(`roles[${i}].evidence[${j}].${key}: expected a string`);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(r.alerts)) {
|
|
64
|
+
errors.push('alerts: expected an array');
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
r.alerts.forEach((alert, i) => {
|
|
68
|
+
if (!isPlainObject(alert)) {
|
|
69
|
+
errors.push(`alerts[${i}]: expected an object`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const key of ['role', 'code', 'coordinator', 'sent_at'])
|
|
73
|
+
if (typeof alert[key] !== 'string')
|
|
74
|
+
errors.push(`alerts[${i}].${key}: expected a string`);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (r.error !== null && typeof r.error !== 'string')
|
|
78
|
+
errors.push('error: expected string or null');
|
|
79
|
+
return errors;
|
|
80
|
+
}
|
|
81
|
+
const cleanEvidence = (value, max = 280) => value.replace(/[\0-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '').trim().slice(0, max);
|
|
82
|
+
export function normalizeWatchdogReport(r, ctx) {
|
|
83
|
+
const copy = JSON.parse(JSON.stringify(r));
|
|
84
|
+
copy.watchdog = ctx.watchdog;
|
|
85
|
+
copy.run_id = ctx.run_id;
|
|
86
|
+
copy.roles = copy.roles.map(role => {
|
|
87
|
+
// Rebuilt from ONLY the known fields (final review #6a) — the old
|
|
88
|
+
// `{ ...role }` spread let any unknown per-finding key (e.g. an agent
|
|
89
|
+
// dumping a huge `pane_dump`) ride unbounded into the store. Report-level
|
|
90
|
+
// extras (tail/isolation) are untouched here and stay tolerated; this is
|
|
91
|
+
// a per-finding allowlist only.
|
|
92
|
+
const next = { role: role.role, status: role.status };
|
|
93
|
+
if (role.reason !== undefined)
|
|
94
|
+
next.reason = cleanEvidence(role.reason);
|
|
95
|
+
if (role.evidence !== undefined) {
|
|
96
|
+
next.evidence = role.evidence.slice(0, 3).map(ev => ({
|
|
97
|
+
source: cleanEvidence(ev.source),
|
|
98
|
+
detail: cleanEvidence(ev.detail),
|
|
99
|
+
observed_at: cleanEvidence(ev.observed_at),
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
if (role.alerted !== undefined)
|
|
103
|
+
next.alerted = Boolean(role.alerted);
|
|
104
|
+
return next;
|
|
105
|
+
});
|
|
106
|
+
return copy;
|
|
107
|
+
}
|
|
108
|
+
export function errorReport(ctx) {
|
|
109
|
+
return {
|
|
110
|
+
schema_version: 1,
|
|
111
|
+
watchdog: ctx.watchdog,
|
|
112
|
+
run_id: ctx.run_id,
|
|
113
|
+
started_at: ctx.started_at,
|
|
114
|
+
finished_at: ctx.finished_at,
|
|
115
|
+
status: 'error',
|
|
116
|
+
summary: { checked: 0, healthy: 0, idle: 0, anomalies: 0 },
|
|
117
|
+
roles: [],
|
|
118
|
+
alerts: [],
|
|
119
|
+
// Scheduler-built error strings can embed raw agent-controlled bytes
|
|
120
|
+
// (JSON.parse messages, validator interpolations of agent-supplied
|
|
121
|
+
// values) and are stored+printed as-is elsewhere — clean them the same
|
|
122
|
+
// way finding-level text is cleaned (final review #6b).
|
|
123
|
+
error: cleanEvidence(ctx.error),
|
|
124
|
+
...(ctx.tail !== undefined ? { tail: ctx.tail.slice(-4096) } : {}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ResolvedWatchdog } from './config.js';
|
|
2
|
+
import type { WatchdogReport } from './report.js';
|
|
3
|
+
import { type IdentityProvisioner } from '../creation.js';
|
|
4
|
+
import { type FleetConfig } from '../config.js';
|
|
5
|
+
/** A minimal handle over the launched child: kill it, or await its natural exit. */
|
|
6
|
+
export interface WatchdogChildHandle {
|
|
7
|
+
kill(): void;
|
|
8
|
+
exited: Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface WatchdogRunDeps {
|
|
11
|
+
binPath: string;
|
|
12
|
+
log(line: string): void;
|
|
13
|
+
now?(): Date;
|
|
14
|
+
sleep?(ms: number): Promise<void>;
|
|
15
|
+
identityProvisioner?: IdentityProvisioner;
|
|
16
|
+
/**
|
|
17
|
+
* Injectable child launcher for tests. Default: spawn
|
|
18
|
+
* `node <binPath> _run-watchdog <roleName>` detached:false, stdio to
|
|
19
|
+
* `<runDir>/run.log`. Returns kill() and an exited promise.
|
|
20
|
+
*/
|
|
21
|
+
launchChild?(binPath: string, roleName: string, runDir: string): WatchdogChildHandle;
|
|
22
|
+
/** Pre-loaded config (defaults inheritance). Falls back to `loadConfig()`. */
|
|
23
|
+
cfg?: FleetConfig;
|
|
24
|
+
}
|
|
25
|
+
export interface WatchdogRunOutcome {
|
|
26
|
+
report: WatchdogReport;
|
|
27
|
+
storedPath: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run one watchdog agent end-to-end in a clean-context temp state dir: provision
|
|
31
|
+
* identity, materialize the run's contract (briefing/manifest/role snapshot),
|
|
32
|
+
* launch the child, enforce a deadline against report.json as the completion
|
|
33
|
+
* sentinel, harvest whatever it wrote (or a synthetic error report if it
|
|
34
|
+
* didn't), store the result, and always clean up the temp dir.
|
|
35
|
+
*
|
|
36
|
+
* The scheduler's run-lock guarantees only one run per watchdog at a time;
|
|
37
|
+
* this function itself takes no lock (Task 8).
|
|
38
|
+
*/
|
|
39
|
+
export declare function executeWatchdogRun(wd: ResolvedWatchdog, deps: WatchdogRunDeps): Promise<WatchdogRunOutcome>;
|
|
40
|
+
/**
|
|
41
|
+
* Deliver one scheduler-level alert (e.g. held-down) by launching a minimal one-shot temp agent
|
|
42
|
+
* under the watchdog's own identity whose entire mission is: bind identity, send `text` to
|
|
43
|
+
* `wd.coordinator`, write `sent.json`, exit. The fleet process itself cannot send ours messages
|
|
44
|
+
* (owner-approved deviation 4), so this is how a scheduler tick that can't reach an operator any
|
|
45
|
+
* other way still gets a message out.
|
|
46
|
+
*
|
|
47
|
+
* Reuses the Task 7/8 run machinery (temp-dir prep, ensureIdentity, ResolvedRole shape, child
|
|
48
|
+
* launch, kill mechanics) but stores no report, touches no ledger findings, and writes no
|
|
49
|
+
* watch.json manifest — `sent.json` is this run's only completion sentinel. A fixed 2-minute
|
|
50
|
+
* deadline applies regardless of `wd.timeoutMs`.
|
|
51
|
+
*
|
|
52
|
+
* Unlike executeWatchdogRun (which relies on the scheduler's run lock), this function acquires
|
|
53
|
+
* and releases the run lock itself — a notifier run shares the same temp dir name
|
|
54
|
+
* (`agentDir(wd.identity, true)`) as a regular run, and nothing else serializes the two. Refusal
|
|
55
|
+
* to acquire is logged and treated as a no-op: the held-down state already means no regular runs
|
|
56
|
+
* are in flight, so a collision is rare. Any other failure (including a timeout) is logged as a
|
|
57
|
+
* warning and swallowed — a notifier failure must never throw into the scheduler loop.
|
|
58
|
+
*/
|
|
59
|
+
export declare function executeNotifierRun(wd: ResolvedWatchdog, text: string, deps: WatchdogRunDeps): Promise<void>;
|
|
60
|
+
/** What `_run-watchdog` calls: one supervised session, no cleanup — the parent harvests. */
|
|
61
|
+
export declare function runWatchdogAgent(name: string): Promise<void>;
|