@phnx-labs/agents-cli 1.22.14 → 1.22.15
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 +13 -0
- package/README.md +4 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/cloud.js +4 -6
- package/dist/commands/packages.js +90 -22
- package/dist/commands/projects.d.ts +2 -5
- package/dist/commands/projects.js +92 -65
- package/dist/commands/routines.js +53 -71
- package/dist/commands/setup-watchdog.d.ts +3 -0
- package/dist/commands/setup-watchdog.js +54 -0
- package/dist/commands/setup.js +8 -1
- package/dist/commands/ssh.d.ts +11 -0
- package/dist/commands/ssh.js +18 -4
- package/dist/commands/watchdog.d.ts +3 -4
- package/dist/commands/watchdog.js +40 -28
- package/dist/lib/auto-dispatch.d.ts +8 -10
- package/dist/lib/auto-dispatch.js +20 -42
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +4 -3
- package/dist/lib/project-import.d.ts +7 -46
- package/dist/lib/project-import.js +9 -108
- package/dist/lib/projects.d.ts +17 -0
- package/dist/lib/projects.js +17 -1
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +11 -0
- package/dist/lib/routine-activation.d.ts +14 -0
- package/dist/lib/routine-activation.js +78 -0
- package/dist/lib/routines-project.d.ts +1 -1
- package/dist/lib/routines-project.js +3 -9
- package/dist/lib/routines.d.ts +3 -1
- package/dist/lib/routines.js +32 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/types.d.ts +14 -1
- package/dist/lib/watchdog/rotate.d.ts +1 -1
- package/dist/lib/watchdog/rotate.js +1 -1
- package/package.json +1 -1
- package/dist/lib/watchdog/routine.d.ts +0 -44
- package/dist/lib/watchdog/routine.js +0 -69
package/dist/lib/routines.js
CHANGED
|
@@ -17,6 +17,7 @@ import { ALL_AGENT_IDS } from './agents.js';
|
|
|
17
17
|
import { machineId, normalizeHost } from './machine-id.js';
|
|
18
18
|
import { resolveActor } from './actor.js';
|
|
19
19
|
import { percentile } from './percentile.js';
|
|
20
|
+
import { enabledRoutineNames, replaceEnabledRoutines, routineEnabledOnThisDevice, setRoutineEnabledOnThisDevice, } from './routine-activation.js';
|
|
20
21
|
export const HOST_STRATEGIES = ['local', 'host', 'fleet', 'cloud'];
|
|
21
22
|
/** Canonical set of accepted GitHub trigger events — single source for validation. */
|
|
22
23
|
export const GITHUB_TRIGGER_EVENTS = [
|
|
@@ -82,6 +83,9 @@ export function finalizeRunMeta(meta, status, exitCode, opts) {
|
|
|
82
83
|
* catchup/overdue, manual run) gates on this.
|
|
83
84
|
*/
|
|
84
85
|
export function jobRunsOnThisDevice(config) {
|
|
86
|
+
const activated = routineEnabledOnThisDevice(config.name);
|
|
87
|
+
if (activated !== null)
|
|
88
|
+
return activated;
|
|
85
89
|
const owner = routineOwnerDevice(config);
|
|
86
90
|
// Unrestricted: no pin means fleet-wide by design (`watchdog`, `check-updates`).
|
|
87
91
|
if (owner === null)
|
|
@@ -260,7 +264,7 @@ export function listJobs(cwd) {
|
|
|
260
264
|
jobs.push(job);
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
|
-
return jobs;
|
|
267
|
+
return jobs.map(applyDeviceActivation);
|
|
264
268
|
}
|
|
265
269
|
/**
|
|
266
270
|
* Read a single job config by name, checking project > user > system.
|
|
@@ -286,12 +290,16 @@ export function readJob(name, cwd) {
|
|
|
286
290
|
const job = readJobFromDir(dir, name);
|
|
287
291
|
if (job) {
|
|
288
292
|
if (scope === 'project')
|
|
289
|
-
return overlayUserRoutineDevices(job, readJobFromDir(userDir, name));
|
|
290
|
-
return job;
|
|
293
|
+
return applyDeviceActivation(overlayUserRoutineDevices(job, readJobFromDir(userDir, name)));
|
|
294
|
+
return applyDeviceActivation(job);
|
|
291
295
|
}
|
|
292
296
|
}
|
|
293
297
|
return null;
|
|
294
298
|
}
|
|
299
|
+
function applyDeviceActivation(job) {
|
|
300
|
+
const activated = routineEnabledOnThisDevice(job.name);
|
|
301
|
+
return activated === null ? job : { ...job, enabled: activated };
|
|
302
|
+
}
|
|
295
303
|
function readJobFromDir(dir, name) {
|
|
296
304
|
for (const ext of ['.yml', '.yaml']) {
|
|
297
305
|
const filePath = safeJoin(dir, name + ext);
|
|
@@ -327,6 +335,10 @@ function readJobFile(filePath) {
|
|
|
327
335
|
...JOB_DEFAULTS,
|
|
328
336
|
...parsed,
|
|
329
337
|
name: parsed.name || path.basename(filePath).replace(/\.ya?ml$/, ''),
|
|
338
|
+
// Before a device manifest exists, preserve only explicit legacy state.
|
|
339
|
+
// A new built-in definition with no `enabled:` field stays opt-in until
|
|
340
|
+
// setup materializes this host's routines list.
|
|
341
|
+
enabled: Object.prototype.hasOwnProperty.call(parsed, 'enabled') ? parsed.enabled !== false : false,
|
|
330
342
|
};
|
|
331
343
|
}
|
|
332
344
|
catch {
|
|
@@ -368,15 +380,12 @@ export function writeJob(config) {
|
|
|
368
380
|
delete output.effort;
|
|
369
381
|
if (output.timeout === '10m')
|
|
370
382
|
delete output.timeout;
|
|
371
|
-
|
|
372
|
-
delete output.enabled;
|
|
383
|
+
delete output.enabled;
|
|
373
384
|
if (output.runOnce === false || output.runOnce === undefined)
|
|
374
385
|
delete output.runOnce;
|
|
375
386
|
if (output.catchup === true || output.catchup === undefined)
|
|
376
387
|
delete output.catchup;
|
|
377
|
-
|
|
378
|
-
if (!devArr || devArr.length === 0)
|
|
379
|
-
delete output.devices;
|
|
388
|
+
delete output.devices;
|
|
380
389
|
let existingText = null;
|
|
381
390
|
if (ymlExists || yamlExists) {
|
|
382
391
|
try {
|
|
@@ -441,8 +450,20 @@ export function setJobEnabled(name, enabled) {
|
|
|
441
450
|
const job = readJob(name);
|
|
442
451
|
if (!job)
|
|
443
452
|
throw new Error(`Job '${name}' not found`);
|
|
444
|
-
|
|
445
|
-
|
|
453
|
+
const legacyEnabled = enabledRoutineNames() === null
|
|
454
|
+
? listJobs().filter((candidate) => candidate.enabled && jobRunsOnThisDevice(candidate)).map((candidate) => candidate.name)
|
|
455
|
+
: [];
|
|
456
|
+
setRoutineEnabledOnThisDevice(name, enabled, legacyEnabled);
|
|
457
|
+
}
|
|
458
|
+
/** Materialize legacy definition state into this device's activation manifest. */
|
|
459
|
+
export function migrateLegacyRoutineActivation() {
|
|
460
|
+
if (enabledRoutineNames() !== null)
|
|
461
|
+
return false;
|
|
462
|
+
const jobs = listJobs();
|
|
463
|
+
if (!jobs.some((job) => job.enabled || Array.isArray(job.devices)))
|
|
464
|
+
return false;
|
|
465
|
+
replaceEnabledRoutines(jobs.filter((job) => job.enabled && jobRunsOnThisDevice(job)).map((job) => job.name));
|
|
466
|
+
return true;
|
|
446
467
|
}
|
|
447
468
|
/** Validate a partial job config, returning a list of human-readable errors. */
|
|
448
469
|
export function validateJob(config) {
|
|
@@ -606,15 +627,6 @@ export function validateJob(config) {
|
|
|
606
627
|
if (config.catchup !== undefined && typeof config.catchup !== 'boolean') {
|
|
607
628
|
errors.push('catchup must be a boolean (false to skip running a missed fire late)');
|
|
608
629
|
}
|
|
609
|
-
// Off-box placement without a devices pin fires on every fleet daemon and
|
|
610
|
-
// each dispatches once (RUSH-1980). Enforce the pin at validation so hand
|
|
611
|
-
// edits and devices --clear cannot re-open the hole.
|
|
612
|
-
if (placementRequiresFiringPin(strategy)) {
|
|
613
|
-
if (!config.devices || config.devices.length === 0) {
|
|
614
|
-
errors.push(`hostStrategy: ${strategy} requires devices: [<name>] to pin which daemon may fire ` +
|
|
615
|
-
'(otherwise every fleet daemon dispatches once). Pin to one machine, e.g. devices: [this-host]');
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
630
|
return errors;
|
|
619
631
|
}
|
|
620
632
|
/** Validate a job trigger block, returning a list of human-readable errors. */
|
|
@@ -1155,6 +1167,7 @@ export function installJobFromSource(sourcePath, name) {
|
|
|
1155
1167
|
return { success: false, error: errors.join(', ') };
|
|
1156
1168
|
}
|
|
1157
1169
|
writeJob(config);
|
|
1170
|
+
setJobEnabled(config.name, config.enabled);
|
|
1158
1171
|
return { success: true };
|
|
1159
1172
|
}
|
|
1160
1173
|
catch (err) {
|
|
Binary file
|
|
Binary file
|
package/dist/lib/state.js
CHANGED
|
@@ -812,6 +812,7 @@ const META_KEY_SCOPE = {
|
|
|
812
812
|
versions: 'device',
|
|
813
813
|
defaultBrowserProfile: 'device',
|
|
814
814
|
deviceConfig: 'device',
|
|
815
|
+
deviceRoutines: 'device',
|
|
815
816
|
// Central — synced via agents.yaml.
|
|
816
817
|
run: 'central',
|
|
817
818
|
model: 'central',
|
|
@@ -909,7 +910,7 @@ function serializeCentral(central) {
|
|
|
909
910
|
return isEmpty ? META_HEADER : doc.toString({ collectionStyle: 'block' });
|
|
910
911
|
}
|
|
911
912
|
function writeMetaUnlocked(meta) {
|
|
912
|
-
const { agents, isolatedAgents, versions, defaultBrowserProfile, deviceConfig, ...central } = meta;
|
|
913
|
+
const { agents, isolatedAgents, versions, defaultBrowserProfile, deviceConfig, deviceRoutines, ...central } = meta;
|
|
913
914
|
// Write the machine-local files FIRST, then strip central — so a crash mid-write
|
|
914
915
|
// never removes pins/versions from central before they're persisted elsewhere.
|
|
915
916
|
const devicePath = getDeviceMetaPath();
|
|
@@ -923,7 +924,8 @@ function writeMetaUnlocked(meta) {
|
|
|
923
924
|
// Device-scope config (`maxAgents`, `schedulerEnabled`, …) is per-machine, so it
|
|
924
925
|
// rides the device doc under `config:` — never the central doc that syncs.
|
|
925
926
|
const hasDeviceConfig = !!deviceConfig && Object.keys(deviceConfig).length > 0;
|
|
926
|
-
|
|
927
|
+
const hasDeviceRoutines = Array.isArray(deviceRoutines);
|
|
928
|
+
if (hasAgents || hasIsolatedAgents || hasDefaultBrowser || hasDeviceConfig || hasDeviceRoutines) {
|
|
927
929
|
// Device-local doc carries `agents:` pins, `defaultBrowserProfile:`, and the
|
|
928
930
|
// device-scope `config:` — all per-machine and must never land in central
|
|
929
931
|
// agents.yaml (which syncs).
|
|
@@ -936,6 +938,8 @@ function writeMetaUnlocked(meta) {
|
|
|
936
938
|
deviceDoc.defaultBrowserProfile = defaultBrowserProfile;
|
|
937
939
|
if (hasDeviceConfig)
|
|
938
940
|
deviceDoc.config = deviceConfig;
|
|
941
|
+
if (hasDeviceRoutines)
|
|
942
|
+
deviceDoc.routines = deviceRoutines;
|
|
939
943
|
fs.mkdirSync(path.dirname(devicePath), { recursive: true });
|
|
940
944
|
writeIfChanged(devicePath, META_HEADER + yaml.stringify(deviceDoc));
|
|
941
945
|
}
|
|
@@ -971,8 +975,12 @@ function writeMetaUnlocked(meta) {
|
|
|
971
975
|
function overlayMachineLocal(meta) {
|
|
972
976
|
const devicePath = getDeviceMetaPath();
|
|
973
977
|
if (fs.existsSync(devicePath)) {
|
|
978
|
+
let dm = null;
|
|
974
979
|
try {
|
|
975
|
-
|
|
980
|
+
dm = yaml.parse(fs.readFileSync(devicePath, 'utf-8'));
|
|
981
|
+
}
|
|
982
|
+
catch { /* preserve the existing tolerance for malformed legacy device YAML */ }
|
|
983
|
+
if (dm) {
|
|
976
984
|
if (dm?.agents)
|
|
977
985
|
meta.agents = { ...meta.agents, ...dm.agents };
|
|
978
986
|
if (dm?.isolatedAgents)
|
|
@@ -981,8 +989,13 @@ function overlayMachineLocal(meta) {
|
|
|
981
989
|
meta.defaultBrowserProfile = dm.defaultBrowserProfile;
|
|
982
990
|
if (dm?.config)
|
|
983
991
|
meta.deviceConfig = dm.config;
|
|
992
|
+
if (dm && Object.prototype.hasOwnProperty.call(dm, 'routines')) {
|
|
993
|
+
if (!Array.isArray(dm.routines) || dm.routines.some((name) => typeof name !== 'string')) {
|
|
994
|
+
throw new Error(`Device config corrupted at ${devicePath}: routines must be a string list.`);
|
|
995
|
+
}
|
|
996
|
+
meta.deviceRoutines = dm.routines;
|
|
997
|
+
}
|
|
984
998
|
}
|
|
985
|
-
catch { /* ignore malformed device file */ }
|
|
986
999
|
}
|
|
987
1000
|
const vrPath = getVersionResourcesPath();
|
|
988
1001
|
if (fs.existsSync(vrPath)) {
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -511,10 +511,16 @@ export interface RegistrySearchResult {
|
|
|
511
511
|
}
|
|
512
512
|
/** A package that has been resolved from a registry and is ready to install. */
|
|
513
513
|
export interface ResolvedPackage {
|
|
514
|
-
|
|
514
|
+
/** `plugin` is Phase 5 packaging: `agents install plugin:<spec>` → plugins install. */
|
|
515
|
+
type: 'mcp' | 'skill' | 'git' | 'plugin';
|
|
515
516
|
source: string;
|
|
516
517
|
mcpEntry?: McpServerEntry;
|
|
517
518
|
skillEntry?: SkillEntry;
|
|
519
|
+
/**
|
|
520
|
+
* Plugin install spec (`name@url`, path, or bare source) when `type === 'plugin'`.
|
|
521
|
+
* Same grammar as `agents plugins install <spec>`.
|
|
522
|
+
*/
|
|
523
|
+
pluginSpec?: string;
|
|
518
524
|
}
|
|
519
525
|
/** Categories of resources that can be synced into an agent version home. */
|
|
520
526
|
export type ResourceType = 'commands' | 'skills' | 'hooks' | 'memory' | 'mcp' | 'permissions' | 'subagents' | 'plugins' | 'workflows';
|
|
@@ -937,6 +943,13 @@ export interface Meta {
|
|
|
937
943
|
* device-config registry (`maxAgents`, `schedulerEnabled`, `notes`). Per-machine by design — unset = today's behavior.
|
|
938
944
|
*/
|
|
939
945
|
deviceConfig?: Record<string, unknown>;
|
|
946
|
+
/**
|
|
947
|
+
* Routine names enabled on this machine. In memory this stays distinct from
|
|
948
|
+
* portable user config; state.ts writes it as top-level `routines:` in
|
|
949
|
+
* `~/.agents/devices/<machine>/agents.yaml`. Presence in the list is the whole
|
|
950
|
+
* activation state: absent means disabled on this device.
|
|
951
|
+
*/
|
|
952
|
+
deviceRoutines?: string[];
|
|
940
953
|
/**
|
|
941
954
|
* Agent-host registry keyed by host name (`agents hosts`). Portable user
|
|
942
955
|
* config synced with `agents repo push/pull`. For `ssh-config` hosts this is
|
|
@@ -166,7 +166,7 @@ export declare function isWatchdogRotateEnabled(): boolean;
|
|
|
166
166
|
* Persist `watchdog.rotate: on|off`. Called by the `agents watchdog rotate
|
|
167
167
|
* on|off` subcommand (commands/watchdog.ts) — the rotate-only switch the
|
|
168
168
|
* Factory migration uses so a user who opted out of autoRotate keeps nudging
|
|
169
|
-
* (rather than `agents watchdog
|
|
169
|
+
* (rather than `agents watchdog off`, which disables the whole watchdog here).
|
|
170
170
|
*/
|
|
171
171
|
export declare function setWatchdogRotateEnabled(on: boolean): void;
|
|
172
172
|
export interface RotateGateResult {
|
|
@@ -291,7 +291,7 @@ export function isWatchdogRotateEnabled() {
|
|
|
291
291
|
* Persist `watchdog.rotate: on|off`. Called by the `agents watchdog rotate
|
|
292
292
|
* on|off` subcommand (commands/watchdog.ts) — the rotate-only switch the
|
|
293
293
|
* Factory migration uses so a user who opted out of autoRotate keeps nudging
|
|
294
|
-
* (rather than `agents watchdog
|
|
294
|
+
* (rather than `agents watchdog off`, which disables the whole watchdog here).
|
|
295
295
|
*/
|
|
296
296
|
export function setWatchdogRotateEnabled(on) {
|
|
297
297
|
const meta = readMeta();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.15",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The watchdog, expressed as a routine.
|
|
3
|
-
*
|
|
4
|
-
* The always-on watchdog is not a bespoke subsystem — it is a plain `command`
|
|
5
|
-
* routine the daemon's existing scheduler fires: `agents watchdog --nudge`, once
|
|
6
|
-
* every couple of minutes. Enabling / disabling the watchdog is enabling /
|
|
7
|
-
* disabling that routine, so there is ONE durable, inspectable concept
|
|
8
|
-
* (`agents routines list` shows it; the scheduler catches it up if the daemon was
|
|
9
|
-
* down) instead of a private sentinel file and a hand-rolled `--watch` loop.
|
|
10
|
-
*
|
|
11
|
-
* A `command` routine — not `agent`/`workflow` — because the tick is deterministic
|
|
12
|
-
* housekeeping: no LLM, no auth, no sandbox. Each fire runs ONE bounded tick (the
|
|
13
|
-
* cron is the loop), so the daemon's per-fire model fits without a long-lived
|
|
14
|
-
* process.
|
|
15
|
-
*/
|
|
16
|
-
import { type JobConfig } from '../routines.js';
|
|
17
|
-
/** The routine name. `agents routines list` / `readJob` key on this. */
|
|
18
|
-
export declare const WATCHDOG_ROUTINE_NAME = "watchdog";
|
|
19
|
-
/**
|
|
20
|
-
* Fire every 2 minutes. Mirrors the extension's 120s tick, and is well inside the
|
|
21
|
-
* 5m stall threshold and 20m nudge cooldown, so a stall is noticed promptly
|
|
22
|
-
* without the daemon spawning a tick more often than the cooldown can matter.
|
|
23
|
-
*/
|
|
24
|
-
export declare const WATCHDOG_ROUTINE_SCHEDULE = "*/2 * * * *";
|
|
25
|
-
/** The plain shell each fire runs: one bounded, deterministic nudge tick. */
|
|
26
|
-
export declare const WATCHDOG_ROUTINE_COMMAND = "agents watchdog --nudge";
|
|
27
|
-
/**
|
|
28
|
-
* Build the watchdog routine config. Pure — touches no disk. `mode`/`effort`/
|
|
29
|
-
* `timeout` are required by JobConfig but ignored for `command` routines;
|
|
30
|
-
* writeJob() drops the default ('auto'/'10m') values so the on-disk file stays
|
|
31
|
-
* to the point (name + schedule + command + enabled).
|
|
32
|
-
*/
|
|
33
|
-
export declare function buildWatchdogRoutine(enabled: boolean): JobConfig;
|
|
34
|
-
/** Is the watchdog routine present on disk (user or system layer)? */
|
|
35
|
-
export declare function watchdogRoutineExists(): boolean;
|
|
36
|
-
/** Is the watchdog routine present AND enabled? */
|
|
37
|
-
export declare function isWatchdogRoutineEnabled(): boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Ensure the watchdog routine exists with the requested enabled state, preserving
|
|
40
|
-
* any user edits. Absent → create it; present → only flip `enabled` (never clobber
|
|
41
|
-
* a schedule or command the user tuned). Idempotent: calling it twice with the
|
|
42
|
-
* same state is a no-op after the first.
|
|
43
|
-
*/
|
|
44
|
-
export declare function ensureWatchdogRoutine(enabled: boolean): void;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The watchdog, expressed as a routine.
|
|
3
|
-
*
|
|
4
|
-
* The always-on watchdog is not a bespoke subsystem — it is a plain `command`
|
|
5
|
-
* routine the daemon's existing scheduler fires: `agents watchdog --nudge`, once
|
|
6
|
-
* every couple of minutes. Enabling / disabling the watchdog is enabling /
|
|
7
|
-
* disabling that routine, so there is ONE durable, inspectable concept
|
|
8
|
-
* (`agents routines list` shows it; the scheduler catches it up if the daemon was
|
|
9
|
-
* down) instead of a private sentinel file and a hand-rolled `--watch` loop.
|
|
10
|
-
*
|
|
11
|
-
* A `command` routine — not `agent`/`workflow` — because the tick is deterministic
|
|
12
|
-
* housekeeping: no LLM, no auth, no sandbox. Each fire runs ONE bounded tick (the
|
|
13
|
-
* cron is the loop), so the daemon's per-fire model fits without a long-lived
|
|
14
|
-
* process.
|
|
15
|
-
*/
|
|
16
|
-
import { readJob, writeJob, setJobEnabled } from '../routines.js';
|
|
17
|
-
/** The routine name. `agents routines list` / `readJob` key on this. */
|
|
18
|
-
export const WATCHDOG_ROUTINE_NAME = 'watchdog';
|
|
19
|
-
/**
|
|
20
|
-
* Fire every 2 minutes. Mirrors the extension's 120s tick, and is well inside the
|
|
21
|
-
* 5m stall threshold and 20m nudge cooldown, so a stall is noticed promptly
|
|
22
|
-
* without the daemon spawning a tick more often than the cooldown can matter.
|
|
23
|
-
*/
|
|
24
|
-
export const WATCHDOG_ROUTINE_SCHEDULE = '*/2 * * * *';
|
|
25
|
-
/** The plain shell each fire runs: one bounded, deterministic nudge tick. */
|
|
26
|
-
export const WATCHDOG_ROUTINE_COMMAND = 'agents watchdog --nudge';
|
|
27
|
-
/**
|
|
28
|
-
* Build the watchdog routine config. Pure — touches no disk. `mode`/`effort`/
|
|
29
|
-
* `timeout` are required by JobConfig but ignored for `command` routines;
|
|
30
|
-
* writeJob() drops the default ('auto'/'10m') values so the on-disk file stays
|
|
31
|
-
* to the point (name + schedule + command + enabled).
|
|
32
|
-
*/
|
|
33
|
-
export function buildWatchdogRoutine(enabled) {
|
|
34
|
-
return {
|
|
35
|
-
name: WATCHDOG_ROUTINE_NAME,
|
|
36
|
-
schedule: WATCHDOG_ROUTINE_SCHEDULE,
|
|
37
|
-
command: WATCHDOG_ROUTINE_COMMAND,
|
|
38
|
-
enabled,
|
|
39
|
-
mode: 'auto',
|
|
40
|
-
effort: 'auto',
|
|
41
|
-
timeout: '10m',
|
|
42
|
-
prompt: '',
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/** Is the watchdog routine present on disk (user or system layer)? */
|
|
46
|
-
export function watchdogRoutineExists() {
|
|
47
|
-
return readJob(WATCHDOG_ROUTINE_NAME) !== null;
|
|
48
|
-
}
|
|
49
|
-
/** Is the watchdog routine present AND enabled? */
|
|
50
|
-
export function isWatchdogRoutineEnabled() {
|
|
51
|
-
const job = readJob(WATCHDOG_ROUTINE_NAME);
|
|
52
|
-
return job !== null && job.enabled === true;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Ensure the watchdog routine exists with the requested enabled state, preserving
|
|
56
|
-
* any user edits. Absent → create it; present → only flip `enabled` (never clobber
|
|
57
|
-
* a schedule or command the user tuned). Idempotent: calling it twice with the
|
|
58
|
-
* same state is a no-op after the first.
|
|
59
|
-
*/
|
|
60
|
-
export function ensureWatchdogRoutine(enabled) {
|
|
61
|
-
const existing = readJob(WATCHDOG_ROUTINE_NAME);
|
|
62
|
-
if (existing === null) {
|
|
63
|
-
writeJob(buildWatchdogRoutine(enabled));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (existing.enabled !== enabled) {
|
|
67
|
-
setJobEnabled(WATCHDOG_ROUTINE_NAME, enabled);
|
|
68
|
-
}
|
|
69
|
-
}
|