@phnx-labs/agents-cli 1.22.29 → 1.22.31
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 +88 -0
- package/README.md +44 -5
- package/dist/bin/agents +0 -0
- package/dist/commands/accounts.d.ts +13 -0
- package/dist/commands/accounts.js +32 -0
- package/dist/commands/daemon.d.ts +18 -0
- package/dist/commands/daemon.js +581 -0
- package/dist/commands/exec.js +66 -20
- package/dist/commands/focus.d.ts +4 -1
- package/dist/commands/focus.js +19 -4
- package/dist/commands/routines.js +29 -11
- package/dist/commands/secrets.d.ts +37 -0
- package/dist/commands/secrets.js +86 -105
- package/dist/commands/sessions-bookmark.d.ts +20 -0
- package/dist/commands/{sessions-favorite.js → sessions-bookmark.js} +42 -42
- package/dist/commands/sessions-browser.d.ts +10 -8
- package/dist/commands/sessions-browser.js +61 -32
- package/dist/commands/sessions-picker.d.ts +33 -1
- package/dist/commands/sessions-picker.js +102 -27
- package/dist/commands/sessions-stats.js +1 -1
- package/dist/commands/sessions.d.ts +21 -8
- package/dist/commands/sessions.js +328 -74
- package/dist/commands/view.d.ts +11 -0
- package/dist/commands/view.js +56 -29
- package/dist/index.js +37 -2
- package/dist/lib/account-labels.d.ts +24 -0
- package/dist/lib/account-labels.js +72 -0
- package/dist/lib/agents.d.ts +32 -1
- package/dist/lib/agents.js +96 -31
- package/dist/lib/daemon-health.d.ts +24 -0
- package/dist/lib/daemon-health.js +84 -0
- package/dist/lib/daemon-ticks.d.ts +81 -0
- package/dist/lib/daemon-ticks.js +190 -0
- package/dist/lib/daemon.d.ts +68 -18
- package/dist/lib/daemon.js +303 -338
- package/dist/lib/device-config.d.ts +10 -0
- package/dist/lib/device-config.js +27 -0
- package/dist/lib/exec.d.ts +27 -0
- package/dist/lib/exec.js +49 -2
- package/dist/lib/hosts/dispatch.d.ts +4 -0
- package/dist/lib/hosts/dispatch.js +4 -0
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +1 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/import.js +7 -6
- package/dist/lib/memory-cache.d.ts +19 -0
- package/dist/lib/memory-cache.js +31 -0
- 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 +13 -2
- package/dist/lib/picker.d.ts +6 -3
- package/dist/lib/picker.js +7 -2
- package/dist/lib/routine-activation.d.ts +2 -0
- package/dist/lib/routine-activation.js +16 -0
- package/dist/lib/runner.d.ts +18 -0
- package/dist/lib/runner.js +52 -0
- 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/secrets/agent.d.ts +19 -1
- package/dist/lib/secrets/agent.js +32 -6
- package/dist/lib/secrets/scope.d.ts +3 -3
- package/dist/lib/secrets/scope.js +3 -3
- package/dist/lib/secrets/session-store.d.ts +0 -4
- package/dist/lib/secrets/session-store.js +0 -5
- package/dist/lib/session/{favorites.d.ts → bookmarks.d.ts} +15 -15
- package/dist/lib/session/{favorites.js → bookmarks.js} +23 -23
- package/dist/lib/session/db.d.ts +15 -0
- package/dist/lib/session/db.js +90 -15
- package/dist/lib/session/discover.js +91 -39
- package/dist/lib/session/parse.d.ts +63 -0
- package/dist/lib/session/parse.js +165 -20
- package/dist/lib/session/session-cache.d.ts +9 -6
- package/dist/lib/session/session-cache.js +23 -6
- package/dist/lib/shims.js +12 -0
- package/dist/lib/startup/command-registry.d.ts +15 -1
- package/dist/lib/startup/command-registry.js +49 -0
- package/dist/lib/usage-refresh.js +3 -2
- package/dist/lib/usage.d.ts +12 -10
- package/dist/lib/usage.js +63 -144
- package/package.json +4 -1
- package/dist/commands/sessions-favorite.d.ts +0 -20
package/dist/commands/exec.js
CHANGED
|
@@ -445,17 +445,18 @@ async function handleTerminalHandoff(agentSpec, options, prompt) {
|
|
|
445
445
|
// the whole Kimi/DeepSeek/Qwen/GLM path — for `--terminal` runs only.
|
|
446
446
|
const rawTarget = parseRunAccountPickerRequest(agentSpec).normalizedAgentSpec.split('@')[0];
|
|
447
447
|
const knownAgent = resolveAgentName(rawTarget);
|
|
448
|
-
|
|
448
|
+
const [{ profileExists }, { resolveWorkflowRef }] = await Promise.all([
|
|
449
|
+
import('../lib/profiles.js'),
|
|
450
|
+
import('../lib/workflows.js'),
|
|
451
|
+
]);
|
|
452
|
+
const hasProfile = profileExists(rawTarget);
|
|
453
|
+
if (knownAgent && !hasProfile && isAgentHardDeprecated(knownAgent)) {
|
|
449
454
|
console.error(chalk.red(hardDeprecationError(knownAgent)));
|
|
450
455
|
process.exit(1);
|
|
451
456
|
}
|
|
452
457
|
if (!knownAgent) {
|
|
453
|
-
const [{ profileExists }, { resolveWorkflowRef }] = await Promise.all([
|
|
454
|
-
import('../lib/profiles.js'),
|
|
455
|
-
import('../lib/workflows.js'),
|
|
456
|
-
]);
|
|
457
458
|
const probeCwd = options.cwd ?? process.cwd();
|
|
458
|
-
if (!
|
|
459
|
+
if (!hasProfile && !resolveWorkflowRef(rawTarget, probeCwd)) {
|
|
459
460
|
console.error(chalk.red(`Unknown agent, profile, or workflow: ${rawTarget}. See \`agents list\` for the installed harnesses.`));
|
|
460
461
|
process.exit(1);
|
|
461
462
|
}
|
|
@@ -545,6 +546,7 @@ export function registerRunCommand(program) {
|
|
|
545
546
|
.option('--fallback <agents>', 'Comma-separated agents to try on rate-limit failure. Each entry accepts an optional @version pin (e.g., codex@0.116.0,antigravity). The primary runs first; if it exits with a rate-limit error, the next agent picks up via /continue handoff.')
|
|
546
547
|
.option('-b, --balanced', 'Shortcut for --strategy balanced. Ignored when @version is pinned.')
|
|
547
548
|
.option('--strategy <strategy>', 'Version/account selection strategy: pinned | available | balanced. Defaults to run.<agent>.strategy, then balanced (spreads load across healthy accounts and skips any that are rate-limited). (Legacy `rotate` accepted as alias for `balanced`.)')
|
|
549
|
+
.option('--account <label>', 'Run a healthy installed version currently signed into this named account (never falls back to another identity)')
|
|
548
550
|
.option('--acp', 'Route through the Agent Client Protocol instead of direct exec. Supported for claude via @zed-industries/claude-code-acp adapter. Unified event stream; emits ndjson when --json.')
|
|
549
551
|
.option('-y, --yes', 'Skip the interactive budget-confirm prompt (require_confirm_over). Never skips a hard budget block.', false)
|
|
550
552
|
.option('--loop', 'Re-inject the prompt/entrypoint each iteration until a stop condition (issue #332). Guards (--max-iterations, --budget, --until) are enforced outside the agent. Writes a checkpoint after every iteration for --resume-checkpoint.')
|
|
@@ -812,8 +814,10 @@ export function registerRunCommand(program) {
|
|
|
812
814
|
process.exit(1);
|
|
813
815
|
}
|
|
814
816
|
// Hard-deprecated harnesses cannot be run — point the user at the successor.
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
+
const runBaseAgentName = normalizedAgentSpec.split('@')[0];
|
|
818
|
+
const runBaseAgentId = resolveAgentName(runBaseAgentName);
|
|
819
|
+
const { profileExists: runProfileExists } = await import('../lib/profiles.js');
|
|
820
|
+
if (runBaseAgentId && !runProfileExists(runBaseAgentName) && isAgentHardDeprecated(runBaseAgentId)) {
|
|
817
821
|
console.error(chalk.red(hardDeprecationError(runBaseAgentId)));
|
|
818
822
|
process.exit(1);
|
|
819
823
|
}
|
|
@@ -1567,6 +1571,7 @@ export function registerRunCommand(program) {
|
|
|
1567
1571
|
agent: runAgent,
|
|
1568
1572
|
version: resumeId ? undefined : runVersion,
|
|
1569
1573
|
strategy: resumeId ? undefined : runStrategy,
|
|
1574
|
+
account: resumeId ? undefined : options.account,
|
|
1570
1575
|
fallback: options.fallback,
|
|
1571
1576
|
prompt,
|
|
1572
1577
|
mode: forwardedMode,
|
|
@@ -1654,6 +1659,7 @@ export function registerRunCommand(program) {
|
|
|
1654
1659
|
agent: runAgent,
|
|
1655
1660
|
version: resumeId ? undefined : runVersion,
|
|
1656
1661
|
strategy: resumeId ? undefined : runStrategy,
|
|
1662
|
+
account: resumeId ? undefined : options.account,
|
|
1657
1663
|
fallback: options.fallback,
|
|
1658
1664
|
prompt,
|
|
1659
1665
|
mode: forwardedMode,
|
|
@@ -1840,11 +1846,11 @@ export function registerRunCommand(program) {
|
|
|
1840
1846
|
// degenerates to a no-op ("I'll wait for the completion notification").
|
|
1841
1847
|
let workflowHasSubagents = false;
|
|
1842
1848
|
const cwd = options.cwd ?? process.cwd();
|
|
1849
|
+
if (accountPickerRequested && profileExists(rawAgent)) {
|
|
1850
|
+
console.error(chalk.red(`Account selection is not available for custom harness '${rawAgent}'. Run its concrete host agent with @ instead.`));
|
|
1851
|
+
process.exit(1);
|
|
1852
|
+
}
|
|
1843
1853
|
if (accountPickerRequested && !isValidAgent(rawAgent)) {
|
|
1844
|
-
if (profileExists(rawAgent)) {
|
|
1845
|
-
console.error(chalk.red(`Account selection is not available for custom harness '${rawAgent}'. Run its concrete host agent with @ instead.`));
|
|
1846
|
-
process.exit(1);
|
|
1847
|
-
}
|
|
1848
1854
|
if (resolveWorkflowRef(rawAgent, cwd)) {
|
|
1849
1855
|
console.error(chalk.red(`Account selection is not available for workflow '${rawAgent}'. Run a concrete agent with @ instead.`));
|
|
1850
1856
|
process.exit(1);
|
|
@@ -1886,14 +1892,11 @@ export function registerRunCommand(program) {
|
|
|
1886
1892
|
process.stderr.write(chalk.yellow(`[agents] --session-id ignored: auto picked ${agent} (only claude accepts a forced session id)\n`));
|
|
1887
1893
|
}
|
|
1888
1894
|
}
|
|
1889
|
-
else if (isValidAgent(rawAgent)) {
|
|
1890
|
-
agent = rawAgent;
|
|
1891
|
-
}
|
|
1892
1895
|
else if (profileExists(rawAgent)) {
|
|
1893
|
-
//
|
|
1894
|
-
//
|
|
1895
|
-
//
|
|
1896
|
-
//
|
|
1896
|
+
// A profile by this exact name exists. Profiles bind (host agent,
|
|
1897
|
+
// version, env overrides, keychain-backed auth) so Chinese models
|
|
1898
|
+
// (Kimi, DeepSeek, Qwen, GLM) can run inside Claude Code without a
|
|
1899
|
+
// local proxy, including when the profile name matches a native id.
|
|
1897
1900
|
try {
|
|
1898
1901
|
const resolved = resolveProfileForRun(rawAgent, options.model);
|
|
1899
1902
|
agent = resolved.agent;
|
|
@@ -1925,6 +1928,9 @@ export function registerRunCommand(program) {
|
|
|
1925
1928
|
process.exit(1);
|
|
1926
1929
|
}
|
|
1927
1930
|
}
|
|
1931
|
+
else if (isValidAgent(rawAgent)) {
|
|
1932
|
+
agent = rawAgent;
|
|
1933
|
+
}
|
|
1928
1934
|
else if (resolveWorkflowRef(rawAgent, cwd)) {
|
|
1929
1935
|
// Workflow: explicit directory, project .agents/workflows/<name>, user, system, or extra repo.
|
|
1930
1936
|
// Resolution follows resource precedence: direct path, then project > user > system > extras.
|
|
@@ -2148,6 +2154,23 @@ export function registerRunCommand(program) {
|
|
|
2148
2154
|
}
|
|
2149
2155
|
}
|
|
2150
2156
|
version = resolveVersionAlias(agent, version);
|
|
2157
|
+
if (options.account) {
|
|
2158
|
+
if (options.cloud || options.provider || options.lease) {
|
|
2159
|
+
console.error(chalk.red('--account selects a local installed identity and cannot be combined with cloud or lease placement.'));
|
|
2160
|
+
process.exit(1);
|
|
2161
|
+
}
|
|
2162
|
+
const { resolveAccountLabel } = await import('../lib/account-labels.js');
|
|
2163
|
+
try {
|
|
2164
|
+
const selected = await resolveAccountLabel(agent, options.account);
|
|
2165
|
+
if (version && version !== selected)
|
|
2166
|
+
throw new Error(`${agent}@${version} is not a healthy match for account '${options.account}'.`);
|
|
2167
|
+
version ??= selected;
|
|
2168
|
+
}
|
|
2169
|
+
catch (err) {
|
|
2170
|
+
console.error(chalk.red(err.message));
|
|
2171
|
+
process.exit(1);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2151
2174
|
// --resume: resolve a prior conversation and rewrite the run target to
|
|
2152
2175
|
// continue it. `version` here is already the alias-resolved candidate-version
|
|
2153
2176
|
// FILTER (undefined for default/any, concrete for @latest/@oldest/@x.y.z);
|
|
@@ -2320,7 +2343,7 @@ export function registerRunCommand(program) {
|
|
|
2320
2343
|
// the bare primary still resolves through the strategy — otherwise every
|
|
2321
2344
|
// `agents run claude --fallback codex` run lands on the pinned default
|
|
2322
2345
|
// account and account rotation silently stops (the gh-monitor heal bug).
|
|
2323
|
-
if (!accountPickerRequested && (strategy !== 'pinned' || options.balanced || explicitStrategy)) {
|
|
2346
|
+
if (!accountPickerRequested && !options.account && (strategy !== 'pinned' || options.balanced || explicitStrategy)) {
|
|
2324
2347
|
if (version) {
|
|
2325
2348
|
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: version ${version} is pinned\n`));
|
|
2326
2349
|
}
|
|
@@ -2434,6 +2457,29 @@ export function registerRunCommand(program) {
|
|
|
2434
2457
|
version = healed;
|
|
2435
2458
|
}
|
|
2436
2459
|
}
|
|
2460
|
+
// The harness may simply not be on this machine. The self-heal above only
|
|
2461
|
+
// runs when a managed version resolved, so with nothing installed we used
|
|
2462
|
+
// to fall through and spawn the bare `cliCommand`, which dies as
|
|
2463
|
+
// `exec: cursor-agent: not found` (exit 127) after a misleading
|
|
2464
|
+
// "looks logged out" banner (RUSH-2339). Probe the executable
|
|
2465
|
+
// buildExecCommand will actually spawn and fail loud instead.
|
|
2466
|
+
//
|
|
2467
|
+
// This is an EXISTENCE probe, not "does agents-cli manage a version". A
|
|
2468
|
+
// harness the user installed themselves (Homebrew, a vendor `curl | sh`, a
|
|
2469
|
+
// distro package) has no version home and MUST still launch — the PATH
|
|
2470
|
+
// branch of resolveLaunchBinary is what keeps that working.
|
|
2471
|
+
{
|
|
2472
|
+
const { resolveLaunchBinary } = await import('../lib/exec.js');
|
|
2473
|
+
// `version` already carries the self-heal's resolution above (it assigns
|
|
2474
|
+
// `version = healed` whenever a version resolved at all), so re-deriving
|
|
2475
|
+
// it with resolveVersion here would be dead.
|
|
2476
|
+
if (!resolveLaunchBinary(agent, version)) {
|
|
2477
|
+
const target = version ? `${agent}@${version}` : agent;
|
|
2478
|
+
console.error(chalk.red(`agents: ${target} is not installed on this machine.`));
|
|
2479
|
+
console.error(chalk.yellow(`Install it with: agents add ${target}`));
|
|
2480
|
+
process.exit(1);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2437
2483
|
const defaultVersion = version ?? resolveVersion(agent, cwd);
|
|
2438
2484
|
// Re-apply the active rules preset before every launch (issue: preset
|
|
2439
2485
|
// changes via `setActiveRulesPreset` only took effect after an explicit
|
package/dist/commands/focus.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface FocusOptions {
|
|
|
42
42
|
since?: string;
|
|
43
43
|
until?: string;
|
|
44
44
|
limit?: string;
|
|
45
|
-
|
|
45
|
+
bookmarks?: boolean;
|
|
46
46
|
unmanaged?: boolean;
|
|
47
47
|
sort?: string;
|
|
48
48
|
working?: boolean;
|
|
@@ -74,6 +74,9 @@ export declare function selectFallback(attachOnly: boolean | undefined): Unreach
|
|
|
74
74
|
export declare function focusAction(id: string | undefined, opts: FocusOptions): Promise<void>;
|
|
75
75
|
/** A retained pane is not attachable merely because tmux can still display it. */
|
|
76
76
|
export declare function isAttachableLiveSession(session: ActiveSession): boolean;
|
|
77
|
+
/** Focus one row selected by the shared session browser through the same
|
|
78
|
+
* attach/recover decision as `agents sessions focus <id>`. */
|
|
79
|
+
export declare function focusSelectedSession(meta: SessionMeta, active: ActiveSession | undefined, self: string): Promise<void>;
|
|
77
80
|
/**
|
|
78
81
|
* How a single selected live session opens in its new tab.
|
|
79
82
|
* - `attach` — a live tmux pane joined in the tab (a second client, no fork),
|
package/dist/commands/focus.js
CHANGED
|
@@ -32,7 +32,7 @@ const INHERITED_FOCUS_OPTIONS = [
|
|
|
32
32
|
'local', 'host', 'device', 'active', 'agent',
|
|
33
33
|
'claude', 'codex', 'kimi', 'antigravity', 'grok', 'opencode',
|
|
34
34
|
'all', 'teams', 'inTeam', 'routine', 'project', 'skill', 'plugin',
|
|
35
|
-
'since', 'until', 'limit', '
|
|
35
|
+
'since', 'until', 'limit', 'bookmarks', 'unmanaged', 'sort',
|
|
36
36
|
'working', 'idle', 'waiting', 'orphan', 'orphaned', 'crashed',
|
|
37
37
|
'closed', 'abandoned', 'queued', 'unknown',
|
|
38
38
|
];
|
|
@@ -93,7 +93,7 @@ export function registerFocusCommand(program) {
|
|
|
93
93
|
.option('--since <time>', 'Only sessions newer than this (for example 7d)')
|
|
94
94
|
.option('--until <time>', 'Only sessions older than this timestamp')
|
|
95
95
|
.option('-n, --limit <n>', 'Maximum candidates to load', '500')
|
|
96
|
-
.option('--
|
|
96
|
+
.option('--bookmarks', 'Only bookmarked sessions')
|
|
97
97
|
.option('--unmanaged', 'Also include native-home sessions outside managed versions')
|
|
98
98
|
.option('--sort <field>', 'Order candidates by recent, cost, or duration', 'recent')
|
|
99
99
|
.option('--working', 'Only live sessions currently doing work')
|
|
@@ -184,7 +184,7 @@ export async function focusAction(id, opts) {
|
|
|
184
184
|
agent: agentSelector ?? focusOptionAgent(opts),
|
|
185
185
|
teams: opts.teams === true,
|
|
186
186
|
team: opts.inTeam,
|
|
187
|
-
|
|
187
|
+
bookmarks: opts.bookmarks === true,
|
|
188
188
|
projectScope: opts.all || hosts.length > 0 || !!opts.project || idLookup ? 'all' : 'repo',
|
|
189
189
|
project: opts.project,
|
|
190
190
|
window: opts.since ?? (opts.all || idLookup ? undefined : '30d'),
|
|
@@ -304,7 +304,7 @@ function looksLikeIdentitySelector(selector) {
|
|
|
304
304
|
function hasFocusFilters(opts, statuses) {
|
|
305
305
|
return statuses.length > 0 || !!(opts.active || opts.local || opts.host?.length || opts.device?.length || focusOptionAgent(opts) ||
|
|
306
306
|
opts.all || opts.teams || opts.inTeam || opts.routine || opts.project || opts.skill || opts.plugin ||
|
|
307
|
-
opts.since || opts.until || opts.
|
|
307
|
+
opts.since || opts.until || opts.bookmarks || opts.unmanaged || (opts.sort && opts.sort !== 'recent'));
|
|
308
308
|
}
|
|
309
309
|
function focusSort(value) {
|
|
310
310
|
if (!value || value === 'recent')
|
|
@@ -370,6 +370,21 @@ async function focusResolvedSession(meta, liveById, self, fallback, attachOnly)
|
|
|
370
370
|
}
|
|
371
371
|
await resumeSessionInPlace(meta);
|
|
372
372
|
}
|
|
373
|
+
/** Focus one row selected by the shared session browser through the same
|
|
374
|
+
* attach/recover decision as `agents sessions focus <id>`. */
|
|
375
|
+
export async function focusSelectedSession(meta, active, self) {
|
|
376
|
+
if (active && !active.sessionId) {
|
|
377
|
+
if (isAttachableLiveSession(active)) {
|
|
378
|
+
await jumpTo(active, self, resumeInNewTab);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
console.log(chalk.yellow('This live session has no session id or living attach rail to focus.'));
|
|
382
|
+
process.exitCode = 1;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const liveById = active ? new Map([[meta.id, active]]) : new Map();
|
|
386
|
+
await focusResolvedSession(meta, liveById, self, resumeInNewTab, false);
|
|
387
|
+
}
|
|
373
388
|
function activeFromMeta(meta) {
|
|
374
389
|
return {
|
|
375
390
|
context: 'headless',
|
|
@@ -11,7 +11,7 @@ import * as fs from 'fs';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import * as yaml from 'yaml';
|
|
13
13
|
import { isDaemonRunning, signalDaemonReload, startDaemon, stopDaemon, readDaemonLog, getDaemonStatus, } from '../lib/daemon.js';
|
|
14
|
-
import { assertSchedulerEnabled } from '../lib/device-config.js';
|
|
14
|
+
import { assertSchedulerEnabled, assertDaemonEnabled, isDaemonEnabled } from '../lib/device-config.js';
|
|
15
15
|
import { resolveAgentName, isAgentHardDeprecated, hardDeprecationError, ROUTINE_AGENT_IDS } from '../lib/agents.js';
|
|
16
16
|
import { humanizeCron, humanizeNextRun, formatRepoLink, REPO_DISPLAY_MAX } from '../lib/routines-format.js';
|
|
17
17
|
import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, routineStats, getLatestRun, getRunDir, getJobPath, parseAtTime, hasCompletedOneShotRun, isOneShotLikeSchedule, isOneShotRoutine, isPastOneShotRoutine, jobRunsOnThisDevice, checkJobDeviceEligibility, normalizeTriggerEvent, parseHostStrategy, resolveHostStrategy, HOST_STRATEGIES, computeProjectGroup, computeProjectGroupKind, projectGroupKey, projectGroupTitle, projectGroupOrder, normalizeProjects, } from '../lib/routines.js';
|
|
@@ -344,6 +344,7 @@ function parseRoutineTrigger(options) {
|
|
|
344
344
|
function ensureSchedulerRunning(opts = {}) {
|
|
345
345
|
const log = opts.stderr ? console.error : console.log;
|
|
346
346
|
try {
|
|
347
|
+
assertDaemonEnabled();
|
|
347
348
|
assertSchedulerEnabled();
|
|
348
349
|
}
|
|
349
350
|
catch (err) {
|
|
@@ -1282,11 +1283,18 @@ export function registerRoutinesCommands(program) {
|
|
|
1282
1283
|
console.log(` ${chalk.cyan(job.name)} — missed ${chalk.gray(job.expectedAt.toLocaleString())}, last ran ${chalk.gray(last)}`);
|
|
1283
1284
|
}
|
|
1284
1285
|
// Need the daemon alive so spawned jobs are monitored and meta.json is
|
|
1285
|
-
// finalized. Start it if it isn't already running.
|
|
1286
|
+
// finalized. Start it if it isn't already running — unless daemon.enabled
|
|
1287
|
+
// is off, in which case this auto-start is skipped with a stated reason.
|
|
1286
1288
|
if (!options.dryRun && !isDaemonRunning()) {
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1289
|
+
try {
|
|
1290
|
+
assertDaemonEnabled();
|
|
1291
|
+
const started = startDaemon();
|
|
1292
|
+
if (started.pid) {
|
|
1293
|
+
console.log(chalk.gray(`\nStarted scheduler (PID: ${started.pid}) so catchup runs are monitored.`));
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
catch (err) {
|
|
1297
|
+
console.log(chalk.yellow(`\n${err.message}`));
|
|
1290
1298
|
}
|
|
1291
1299
|
}
|
|
1292
1300
|
console.log(chalk.bold(options.dryRun ? '\nRecording misses...' : '\nTriggering catchup runs...'));
|
|
@@ -1372,11 +1380,18 @@ export function registerRoutinesCommands(program) {
|
|
|
1372
1380
|
return;
|
|
1373
1381
|
}
|
|
1374
1382
|
// Fired jobs run detached via executeJobDetached (the same path cron
|
|
1375
|
-
// uses). Keep the daemon alive so each run's meta.json is finalized
|
|
1383
|
+
// uses). Keep the daemon alive so each run's meta.json is finalized —
|
|
1384
|
+
// unless daemon.enabled is off, in which case this auto-start is
|
|
1385
|
+
// skipped with a stated reason.
|
|
1376
1386
|
if (!isDaemonRunning()) {
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1387
|
+
if (!isDaemonEnabled()) {
|
|
1388
|
+
console.log(chalk.yellow(`Daemon is disabled (daemon.enabled=false) — run(s) below fire but are not monitored. Re-enable with: agents daemon enable`));
|
|
1389
|
+
}
|
|
1390
|
+
else {
|
|
1391
|
+
const started = startDaemon();
|
|
1392
|
+
if (started.pid) {
|
|
1393
|
+
console.log(chalk.gray(`Started scheduler (PID: ${started.pid}) so webhook runs are monitored.`));
|
|
1394
|
+
}
|
|
1380
1395
|
}
|
|
1381
1396
|
}
|
|
1382
1397
|
const fired = await fireWebhookJobs(webhook);
|
|
@@ -1649,8 +1664,11 @@ export function registerRoutinesCommands(program) {
|
|
|
1649
1664
|
.description('Start the background scheduler. Usually unnecessary — it auto-starts when you add your first routine.')
|
|
1650
1665
|
.action(() => {
|
|
1651
1666
|
try {
|
|
1652
|
-
// A manual start on a
|
|
1653
|
-
//
|
|
1667
|
+
// A manual start on a disabled device refuses with the same message the
|
|
1668
|
+
// auto-start surfaces give — `agents routines start` is a convenience
|
|
1669
|
+
// wrapper around the daemon, not the deliberate override. Use
|
|
1670
|
+
// `agents daemon start` to bypass daemon.enabled explicitly.
|
|
1671
|
+
assertDaemonEnabled();
|
|
1654
1672
|
assertSchedulerEnabled();
|
|
1655
1673
|
}
|
|
1656
1674
|
catch (err) {
|
|
@@ -12,6 +12,7 @@ import { bundleEnvToDotenv } from '../lib/secrets/push.js';
|
|
|
12
12
|
export { bundleEnvToDotenv };
|
|
13
13
|
import { type SecretsBackend, type SecretsBundle, type SecretsPolicy } from '../lib/secrets/bundles.js';
|
|
14
14
|
import { type SecretsListFilterOpts } from '../lib/secrets/list-filter.js';
|
|
15
|
+
import { type SecretLease } from '../lib/secrets/lease.js';
|
|
15
16
|
import { type BundleUsageSummary } from '../lib/secrets/usage-db.js';
|
|
16
17
|
/** Read all available data from stdin synchronously, trimmed. */
|
|
17
18
|
/**
|
|
@@ -51,6 +52,25 @@ export declare function parseImportSource(opts: {
|
|
|
51
52
|
* without a live SSH session.
|
|
52
53
|
*/
|
|
53
54
|
export declare function resolveUnlockTtlMs(ttl: string | undefined, until: string | undefined, now?: number): number;
|
|
55
|
+
/**
|
|
56
|
+
* Decide what an `unlock` holds: the whole bundle env, or — with --keys — only
|
|
57
|
+
* the resolved subset behind a lease that scopes the broker entry (agent.ts
|
|
58
|
+
* re-selects on load) and its own expiry. Fails closed: createSecretLease throws
|
|
59
|
+
* on an unknown or empty key subset. The unlock action's single scoping seam,
|
|
60
|
+
* exported so the whole-bundle vs scoped-subset decision is unit-testable without
|
|
61
|
+
* a live broker.
|
|
62
|
+
*/
|
|
63
|
+
export declare function scopeHeldEnv(input: {
|
|
64
|
+
bundle: string;
|
|
65
|
+
env: Record<string, string>;
|
|
66
|
+
keys: string | null;
|
|
67
|
+
ttlMs: number;
|
|
68
|
+
harness: string;
|
|
69
|
+
sleepPersist: boolean;
|
|
70
|
+
}): {
|
|
71
|
+
heldEnv: Record<string, string>;
|
|
72
|
+
lease?: SecretLease;
|
|
73
|
+
};
|
|
54
74
|
export declare function buildRemoteUnlockArgs(names: string[], opts: {
|
|
55
75
|
all?: boolean;
|
|
56
76
|
ttl?: string;
|
|
@@ -149,6 +169,23 @@ export declare function renderViewStatusLine(b: SecretsBundle, heldExpiresAt: nu
|
|
|
149
169
|
* and a hard abort at inject time, i.e. after it had already broken something.
|
|
150
170
|
*/
|
|
151
171
|
export declare function renderExpiringCol(b: SecretsBundle, now?: number): string;
|
|
172
|
+
/**
|
|
173
|
+
* Resolve an existing import target bundle (inheriting its backend) or create a
|
|
174
|
+
* new one with the requested backend. Refuses to silently downgrade a
|
|
175
|
+
* keychain-backed bundle to `file` — shared by every `import` source so the
|
|
176
|
+
* guard can't drift between them.
|
|
177
|
+
*
|
|
178
|
+
* `force` additionally recreates a bundle whose METADATA RECORD is present but
|
|
179
|
+
* undecryptable — a file store whose key was lost or rotated out from under it.
|
|
180
|
+
* That is precisely the state provisioning exists to repair (import is how a box
|
|
181
|
+
* gets its bundles back), and without this the import dies on `readBundle` and
|
|
182
|
+
* the only route left is deleting the record by hand on an already-degraded
|
|
183
|
+
* store. It is gated on `--force` on purpose: recreating unconditionally would
|
|
184
|
+
* destroy a perfectly healthy bundle for someone who merely forgot to set
|
|
185
|
+
* `AGENTS_SECRETS_PASSPHRASE`, which is the hazard `readBundleIfDecryptable`
|
|
186
|
+
* exists to describe. `--force` already means "overwrite what is there".
|
|
187
|
+
*/
|
|
188
|
+
export declare function resolveImportBundle(name: string, backendOpt: string | undefined, synced?: boolean, force?: boolean): SecretsBundle;
|
|
152
189
|
/** Register the `agents secrets` command tree. */
|
|
153
190
|
export declare function registerSecretsCommands(program: Command): void;
|
|
154
191
|
/** Validate a prompt-policy value, throwing a clear message on a bad one (the
|