@phnx-labs/agents-cli 1.20.73 → 1.20.76
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 +989 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +31 -1
- package/dist/commands/exec.js +31 -3
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +87 -9
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +15 -2
- package/dist/commands/secrets.js +44 -21
- package/dist/commands/sessions-browser.js +1 -1
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.js +16 -1
- package/dist/commands/sessions.d.ts +36 -13
- package/dist/commands/sessions.js +210 -40
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/setup.js +6 -1
- package/dist/commands/ssh.js +71 -35
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +51 -11
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +99 -31
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +14 -4
- package/dist/lib/activity.js +17 -5
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +56 -0
- package/dist/lib/agents.js +133 -7
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +60 -0
- package/dist/lib/daemon.js +18 -2
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +21 -0
- package/dist/lib/devices/fleet.js +26 -0
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +66 -0
- package/dist/lib/exec.js +195 -6
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +2 -0
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +13 -5
- package/dist/lib/hosts/run-target.js +26 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +37 -2
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +25 -0
- package/dist/lib/import.js +107 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.d.ts +20 -0
- package/dist/lib/menubar/install-menubar.js +45 -1
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +7 -2
- package/dist/lib/overdue.js +18 -11
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/project-resources.js +20 -5
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routines.d.ts +26 -0
- package/dist/lib/routines.js +19 -2
- package/dist/lib/runner.js +104 -7
- package/dist/lib/secrets/agent.d.ts +66 -1
- package/dist/lib/secrets/agent.js +154 -90
- package/dist/lib/secrets/bundles.d.ts +34 -0
- package/dist/lib/secrets/bundles.js +179 -19
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +21 -3
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +114 -7
- package/dist/lib/session/active.js +323 -52
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +48 -0
- package/dist/lib/session/db.js +238 -14
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +465 -2
- package/dist/lib/session/discover.js +1648 -468
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +130 -30
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +22 -10
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.js +6 -2
- package/dist/lib/shims.d.ts +27 -1
- package/dist/lib/shims.js +88 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +15 -36
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +48 -2
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +22 -2
- package/dist/lib/versions.js +98 -10
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
package/dist/commands/view.js
CHANGED
|
@@ -4,13 +4,13 @@ import { visibleWidth, termLink } from '../lib/format.js';
|
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
|
|
7
|
+
import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getUnmanagedCliState, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
|
|
8
8
|
import { loginHint } from '../lib/signin-badge.js';
|
|
9
9
|
import { machineId } from '../lib/machine-id.js';
|
|
10
10
|
import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth-health.js';
|
|
11
11
|
import { agentReportsUsage, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
12
12
|
import { readManifest } from '../lib/manifest.js';
|
|
13
|
-
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, } from '../lib/versions.js';
|
|
13
|
+
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/versions.js';
|
|
14
14
|
import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
|
|
15
15
|
import { getAgentResources } from '../lib/resources.js';
|
|
16
16
|
import { resolveVersionFilter, AgentSpecError } from '../lib/agent-spec/index.js';
|
|
@@ -23,6 +23,7 @@ import { getCentralRulesFileName } from '../lib/rules/rules.js';
|
|
|
23
23
|
import { composeRulesFromState } from '../lib/rules/compose.js';
|
|
24
24
|
import { getConfiguredRunStrategy } from '../lib/rotate.js';
|
|
25
25
|
import { resolveRunDefaults } from '../lib/run-defaults.js';
|
|
26
|
+
import { resolveConfiguredModel } from '../lib/models.js';
|
|
26
27
|
import { listProfiles, profileSummary } from '../lib/profiles.js';
|
|
27
28
|
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
28
29
|
import { confirm } from '@inquirer/prompts';
|
|
@@ -243,9 +244,27 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
243
244
|
? `Checking ${agentLabel(filterAgentId)} agents...`
|
|
244
245
|
: 'Checking installed agents...';
|
|
245
246
|
const spinner = ora({ text: spinnerText, isSilent: !process.stdout.isTTY }).start();
|
|
246
|
-
const cliStates = await getAllCliStates();
|
|
247
|
-
spinner.stop();
|
|
248
247
|
const agentsToShow = filterAgentId ? [filterAgentId] : ALL_AGENT_IDS;
|
|
248
|
+
// A globally-installed CLI is superseded only by a NORMAL managed version — that
|
|
249
|
+
// is when agents-cli owns the launcher and a "global" row would just be our own
|
|
250
|
+
// shim reported back. `--isolated` promises the opposite: no default, no bare
|
|
251
|
+
// shim, no adopted launcher, the user's own `~/.<agent>` untouched. So an
|
|
252
|
+
// isolated-only install must not make that still-live global CLI disappear from
|
|
253
|
+
// `agents view` — the two are genuinely separate installs and both get listed.
|
|
254
|
+
const hasNonIsolatedVersion = (agentId) => listInstalledVersions(agentId).some((v) => !isVersionIsolated(agentId, v));
|
|
255
|
+
// Every `cliStates` read in this function feeds the "Not Managed by Agents CLI"
|
|
256
|
+
// block, so resolve it the way that block means it: the user's own CLI on PATH.
|
|
257
|
+
// `getCliState` would answer with a version-dir install — including an isolated
|
|
258
|
+
// copy that is deliberately absent from PATH — and print it as "(global)".
|
|
259
|
+
//
|
|
260
|
+
// Resolved only for agents that can actually reach that block. `getCliState`
|
|
261
|
+
// deliberately avoids subprocesses for a version-managed agent, and PATH
|
|
262
|
+
// resolution costs a `<cli> --version` spawn on a cold cache — so probing an
|
|
263
|
+
// agent whose global row is suppressed anyway would be pure added latency.
|
|
264
|
+
const cliStates = Object.fromEntries(await Promise.all(agentsToShow
|
|
265
|
+
.filter((agentId) => !hasNonIsolatedVersion(agentId))
|
|
266
|
+
.map(async (agentId) => [agentId, await getUnmanagedCliState(agentId)])));
|
|
267
|
+
spinner.stop();
|
|
249
268
|
const showPaths = !!filterAgentId;
|
|
250
269
|
const profilesByAgent = getProfilesByAgent(filterAgentId);
|
|
251
270
|
const profileSummaries = [...profilesByAgent.values()].flat();
|
|
@@ -276,19 +295,18 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
276
295
|
const infoFetches = [];
|
|
277
296
|
const globalInfoFetches = [];
|
|
278
297
|
for (const agentId of agentsToShow) {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
info,
|
|
288
|
-
})));
|
|
289
|
-
}
|
|
298
|
+
for (const ver of listInstalledVersions(agentId)) {
|
|
299
|
+
const home = getVersionHomePath(agentId, ver);
|
|
300
|
+
infoFetches.push(getAccountInfo(agentId, home).then((info) => ({
|
|
301
|
+
agentId,
|
|
302
|
+
version: ver,
|
|
303
|
+
home,
|
|
304
|
+
info,
|
|
305
|
+
})));
|
|
290
306
|
}
|
|
291
|
-
|
|
307
|
+
// Mirrors the classification below: fetch the global account whenever the
|
|
308
|
+
// global install will still be rendered (no versions at all, or isolated-only).
|
|
309
|
+
if (!hasNonIsolatedVersion(agentId)) {
|
|
292
310
|
globalInfoFetches.push(getAccountInfo(agentId).then((info) => ({
|
|
293
311
|
agentId,
|
|
294
312
|
cliVersion: cliStates[agentId]?.version || null,
|
|
@@ -356,10 +374,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
356
374
|
if (versions.length > 0) {
|
|
357
375
|
versionManaged.push(agentId);
|
|
358
376
|
}
|
|
359
|
-
|
|
360
|
-
|
|
377
|
+
if (cliState?.installed) {
|
|
378
|
+
// Isolated-only installs sit alongside the global CLI rather than replacing it.
|
|
379
|
+
if (!hasNonIsolatedVersion(agentId))
|
|
380
|
+
globallyInstalled.push(agentId);
|
|
361
381
|
}
|
|
362
|
-
else if (hasProfiles) {
|
|
382
|
+
else if (versions.length === 0 && hasProfiles) {
|
|
363
383
|
profileOnly.push(agentId);
|
|
364
384
|
}
|
|
365
385
|
}
|
|
@@ -376,6 +396,22 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
376
396
|
liveVersionByAgent.set(agentId, live);
|
|
377
397
|
}));
|
|
378
398
|
const displayVersion = (agentId, dirVersion) => liveVersionByAgent.get(agentId) ?? dirVersion;
|
|
399
|
+
// Uncolored row label, shared by the width pass and the render so padding lines
|
|
400
|
+
// up. An isolated copy is never the global default (installing one deliberately
|
|
401
|
+
// records no default), so the two tags can't collide.
|
|
402
|
+
const versionRowLabel = (agentId, version, globalDefault) => {
|
|
403
|
+
const shown = displayVersion(agentId, version);
|
|
404
|
+
if (version === globalDefault)
|
|
405
|
+
return `${shown} (default)`;
|
|
406
|
+
if (isVersionIsolated(agentId, version)) {
|
|
407
|
+
// The isolated default is what a bare `agents run <agent>` reaches, so it is
|
|
408
|
+
// worth distinguishing from the other isolated copies sitting beside it.
|
|
409
|
+
return getIsolatedDefault(agentId) === version
|
|
410
|
+
? `${shown} (isolated default)`
|
|
411
|
+
: `${shown} (isolated)`;
|
|
412
|
+
}
|
|
413
|
+
return shown;
|
|
414
|
+
};
|
|
379
415
|
// Show version-managed agents
|
|
380
416
|
if (versionManaged.length > 0) {
|
|
381
417
|
// Calculate column widths across all agents for alignment
|
|
@@ -384,13 +420,15 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
384
420
|
let maxPlanWidth = 3;
|
|
385
421
|
let maxUsageWidth = 0;
|
|
386
422
|
let maxStatusWidth = 0;
|
|
423
|
+
// The configured model sits right after the version, at the same priority.
|
|
424
|
+
// Resolve once here (fs + catalog reads) and reuse in the render loop.
|
|
425
|
+
let maxModelWidth = 0;
|
|
426
|
+
const modelByKey = new Map();
|
|
387
427
|
for (const agentId of versionManaged) {
|
|
388
428
|
const versions = listInstalledVersions(agentId);
|
|
389
429
|
const globalDefault = getGlobalDefault(agentId);
|
|
390
430
|
for (const v of versions) {
|
|
391
|
-
|
|
392
|
-
const label = v === globalDefault ? `${shown} (default)` : shown;
|
|
393
|
-
maxVerLabel = Math.max(maxVerLabel, label.length);
|
|
431
|
+
maxVerLabel = Math.max(maxVerLabel, versionRowLabel(agentId, v, globalDefault).length);
|
|
394
432
|
const rawInfo = infoMap.get(`${agentId}:${v}`);
|
|
395
433
|
const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
396
434
|
const accountLabel = accountColumnLabel(info);
|
|
@@ -398,6 +436,11 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
398
436
|
maxEmail = Math.max(maxEmail, accountLabel.length);
|
|
399
437
|
if (info?.plan)
|
|
400
438
|
maxPlanWidth = Math.max(maxPlanWidth, info.plan.length);
|
|
439
|
+
const model = resolveConfiguredModel(agentId, v)?.model;
|
|
440
|
+
if (model) {
|
|
441
|
+
modelByKey.set(`${agentId}:${v}`, model);
|
|
442
|
+
maxModelWidth = Math.max(maxModelWidth, model.length);
|
|
443
|
+
}
|
|
401
444
|
}
|
|
402
445
|
// Profile rows share these columns with version rows so they line up.
|
|
403
446
|
for (const profile of profilesByAgent.get(agentId) ?? []) {
|
|
@@ -431,7 +474,14 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
431
474
|
const globalDefault = getGlobalDefault(agentId);
|
|
432
475
|
const runStrategy = getConfiguredRunStrategy(agentId);
|
|
433
476
|
const strategyLabel = chalk.gray(` (${runStrategy})`);
|
|
434
|
-
|
|
477
|
+
// `(no default)` is a nudge to go set one. It would read as a contradiction
|
|
478
|
+
// directly above a row tagged `(isolated default)`, and it would be bad
|
|
479
|
+
// advice besides: for an isolated-only agent the pointer below IS how a
|
|
480
|
+
// bare `agents run <agent>` resolves, and setting a global default is
|
|
481
|
+
// precisely what `--isolated` exists to avoid.
|
|
482
|
+
const noDefaultLabel = !globalDefault && !getIsolatedDefault(agentId)
|
|
483
|
+
? chalk.yellow(' (no default)')
|
|
484
|
+
: '';
|
|
435
485
|
console.log(` ${chalk.bold(agentLabel(agentId))}${strategyLabel}${noDefaultLabel}`);
|
|
436
486
|
// Sort versions with default first, then by semver descending
|
|
437
487
|
const sortedVersions = [...versions].sort((a, b) => {
|
|
@@ -443,16 +493,27 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
443
493
|
});
|
|
444
494
|
for (const version of sortedVersions) {
|
|
445
495
|
const isDefault = version === globalDefault;
|
|
496
|
+
const isolated = !isDefault && isVersionIsolated(agentId, version);
|
|
497
|
+
const isolatedTag = getIsolatedDefault(agentId) === version ? ' (isolated default)' : ' (isolated)';
|
|
446
498
|
const shown = displayVersion(agentId, version);
|
|
447
|
-
const base =
|
|
448
|
-
const
|
|
449
|
-
const label = isDefault
|
|
499
|
+
const base = versionRowLabel(agentId, version, globalDefault);
|
|
500
|
+
const tagPad = ' '.repeat(maxVerLabel - base.length);
|
|
501
|
+
const label = isDefault
|
|
502
|
+
? `${shown}${chalk.green(' (default)')}${tagPad}`
|
|
503
|
+
: isolated
|
|
504
|
+
? `${shown}${chalk.gray(isolatedTag)}${tagPad}`
|
|
505
|
+
: base.padEnd(maxVerLabel);
|
|
450
506
|
const rawInfo = infoMap.get(`${agentId}:${version}`);
|
|
451
507
|
const vInfo = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
452
508
|
const usageKey = getUsageLookupKey(vInfo);
|
|
453
509
|
const usageInfo = usageKey ? usageByKey.get(usageKey) : undefined;
|
|
454
510
|
// Build columns, trimming trailing whitespace when columns are empty
|
|
455
511
|
const parts = [` ${label}`];
|
|
512
|
+
// Configured model — same priority as the version, right beside it.
|
|
513
|
+
if (maxModelWidth > 0) {
|
|
514
|
+
const model = modelByKey.get(`${agentId}:${version}`) ?? '';
|
|
515
|
+
parts.push(chalk.yellow(model.padEnd(maxModelWidth)));
|
|
516
|
+
}
|
|
456
517
|
const hasEmail = !!vInfo?.email;
|
|
457
518
|
const signedIn = !!vInfo?.signedIn;
|
|
458
519
|
const usageUnavailable = agentReportsUsage(agentId) && signedIn && !usageInfo?.snapshot;
|
|
@@ -462,12 +523,11 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
462
523
|
// Otherwise it reflects install time (misleading "just now" for fresh installs).
|
|
463
524
|
const activeStr = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
|
|
464
525
|
const hasActive = activeStr.length > 0;
|
|
526
|
+
// The model now has its own column above; keep only the run mode here.
|
|
465
527
|
const runDefaults = resolveRunDefaults(agentId, version);
|
|
466
528
|
const runDefaultBits = [];
|
|
467
529
|
if (runDefaults.mode)
|
|
468
530
|
runDefaultBits.push(`mode:${runDefaults.mode}`);
|
|
469
|
-
if (runDefaults.model)
|
|
470
|
-
runDefaultBits.push(`model:${runDefaults.model}`);
|
|
471
531
|
if (!hasEmail && !hasUsage && !signedIn) {
|
|
472
532
|
// Installed but never signed in
|
|
473
533
|
parts.push(chalk.gray('(logged out — log in with: ' + loginHint(agentId) + ')'));
|
|
@@ -509,10 +569,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
509
569
|
// No status badge, no last-active — profiles don't accumulate usage state.
|
|
510
570
|
for (const profile of profilesByAgent.get(agentId) ?? []) {
|
|
511
571
|
const nameCol = chalk.cyan(profile.name.padEnd(maxVerLabel));
|
|
572
|
+
// Pad the model column so profile rows line up with version rows.
|
|
573
|
+
const modelPad = maxModelWidth > 0 ? `${' '.repeat(maxModelWidth)} ` : '';
|
|
512
574
|
const authCol = chalk.gray(profile.auth.padEnd(maxEmail));
|
|
513
575
|
const usageEquivalent = profileKindAndModel(profile.model, maxPlanWidth);
|
|
514
576
|
const usagePad = ' '.repeat(Math.max(0, maxUsageWidth - visibleWidth(usageEquivalent)));
|
|
515
|
-
console.log(` ${nameCol} ${authCol} ${chalk.gray(usageEquivalent + usagePad)}`);
|
|
577
|
+
console.log(` ${nameCol} ${modelPad}${authCol} ${chalk.gray(usageEquivalent + usagePad)}`);
|
|
516
578
|
if (showPaths) {
|
|
517
579
|
console.log(chalk.gray(` ${profile.path}`));
|
|
518
580
|
}
|
|
@@ -580,7 +642,9 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
580
642
|
// Profile rows under a globally-installed harness. Use a simpler
|
|
581
643
|
// alignment here since this section doesn't share column state with
|
|
582
644
|
// the version-managed block.
|
|
583
|
-
|
|
645
|
+
// An isolated-only agent now appears in BOTH blocks; its profiles already
|
|
646
|
+
// rendered under the version-managed one, so don't print them twice.
|
|
647
|
+
const profilesHere = versionManaged.includes(agentId) ? [] : (profilesByAgent.get(agentId) ?? []);
|
|
584
648
|
if (profilesHere.length > 0) {
|
|
585
649
|
const nameWidth = Math.max(globalMaxVerLabel, ...profilesHere.map((p) => p.name.length));
|
|
586
650
|
const authWidth = Math.max(...profilesHere.map((p) => p.auth.length));
|
|
@@ -833,9 +897,12 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
833
897
|
const accountLabel = accountColumnLabel(accountInfo);
|
|
834
898
|
const emailStr = accountLabel ? chalk.cyan(` ${accountLabel}`) : '';
|
|
835
899
|
const status = chalk.green(version);
|
|
900
|
+
// Configured model sits right beside the version, same priority (no label).
|
|
901
|
+
const configuredModel = resolveConfiguredModel(agentId, version);
|
|
902
|
+
const modelStr = configuredModel ? chalk.yellow(` ${configuredModel.model}`) : '';
|
|
836
903
|
const usageStr = formatUsageSummary(usageInfo.snapshot?.plan ?? accountInfo.plan, null);
|
|
837
904
|
const usagePart = usageStr ? ` ${usageStr}` : '';
|
|
838
|
-
console.log(` ${colorAgent(agentId)(AGENTS[agentId].name.padEnd(14))} ${status}${emailStr}${usagePart}`);
|
|
905
|
+
console.log(` ${colorAgent(agentId)(AGENTS[agentId].name.padEnd(14))} ${status}${modelStr}${emailStr}${usagePart}`);
|
|
839
906
|
const usageLines = formatUsageSection(usageInfo);
|
|
840
907
|
if (usageLines.length > 0) {
|
|
841
908
|
console.log();
|
|
@@ -1173,6 +1240,7 @@ async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
|
1173
1240
|
: [],
|
|
1174
1241
|
lastActive: info.lastActive ? info.lastActive.toISOString() : null,
|
|
1175
1242
|
path: getVersionDir(agentId, version),
|
|
1243
|
+
configuredModel: resolveConfiguredModel(agentId, version),
|
|
1176
1244
|
};
|
|
1177
1245
|
if (wantResources && resourceSync) {
|
|
1178
1246
|
entry.resources = collectVersionResources(agentId, home, process.cwd(), cliStates?.[agentId]?.installed ?? false, resourceSections, resourceSync);
|
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
*
|
|
9
9
|
* agents watchdog one tick, dry — prints what it WOULD nudge/skip and why
|
|
10
10
|
* agents watchdog --nudge one tick, actually injects (explicit opt-in)
|
|
11
|
-
* agents watchdog --watch
|
|
11
|
+
* agents watchdog --watch manual poll loop (dry unless --nudge)
|
|
12
12
|
* agents watchdog --json machine-readable tick output (for the menu-bar)
|
|
13
|
-
* agents watchdog enable|disable
|
|
13
|
+
* agents watchdog enable|disable turn the always-on watchdog routine on/off
|
|
14
14
|
* agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
|
|
15
|
+
*
|
|
16
|
+
* The always-on watchdog is a daemon-fired ROUTINE, not a private sentinel + a
|
|
17
|
+
* hand-rolled loop: `enable` creates/enables a `watchdog` command routine
|
|
18
|
+
* (`agents watchdog --nudge` every couple of minutes) and reloads the daemon;
|
|
19
|
+
* `disable` pauses it. See ../lib/watchdog/routine.ts.
|
|
15
20
|
*/
|
|
16
21
|
import type { Command } from 'commander';
|
|
17
22
|
/** Register the `agents watchdog` command tree. */
|
|
@@ -8,41 +8,41 @@
|
|
|
8
8
|
*
|
|
9
9
|
* agents watchdog one tick, dry — prints what it WOULD nudge/skip and why
|
|
10
10
|
* agents watchdog --nudge one tick, actually injects (explicit opt-in)
|
|
11
|
-
* agents watchdog --watch
|
|
11
|
+
* agents watchdog --watch manual poll loop (dry unless --nudge)
|
|
12
12
|
* agents watchdog --json machine-readable tick output (for the menu-bar)
|
|
13
|
-
* agents watchdog enable|disable
|
|
13
|
+
* agents watchdog enable|disable turn the always-on watchdog routine on/off
|
|
14
14
|
* agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
|
|
15
|
+
*
|
|
16
|
+
* The always-on watchdog is a daemon-fired ROUTINE, not a private sentinel + a
|
|
17
|
+
* hand-rolled loop: `enable` creates/enables a `watchdog` command routine
|
|
18
|
+
* (`agents watchdog --nudge` every couple of minutes) and reloads the daemon;
|
|
19
|
+
* `disable` pauses it. See ../lib/watchdog/routine.ts.
|
|
15
20
|
*/
|
|
16
21
|
import chalk from 'chalk';
|
|
17
|
-
import * as fs from 'fs';
|
|
18
22
|
import * as path from 'path';
|
|
19
23
|
import { setHelpSections } from '../lib/help.js';
|
|
20
24
|
import { parseDuration } from '../lib/hooks/cache.js';
|
|
21
25
|
import { getRuntimeStateDir } from '../lib/state.js';
|
|
26
|
+
import { setJobEnabled } from '../lib/routines.js';
|
|
27
|
+
import { ensureWatchdogRoutine, isWatchdogRoutineEnabled, watchdogRoutineExists, WATCHDOG_ROUTINE_NAME, WATCHDOG_ROUTINE_SCHEDULE, } from '../lib/watchdog/routine.js';
|
|
22
28
|
import { runWatchdogTick, writePolicySentinel, DEFAULT_THRESHOLDS, } from '../lib/watchdog/runner.js';
|
|
23
29
|
/** Default state dir the runner and these subcommands share. */
|
|
24
30
|
function stateDir() {
|
|
25
31
|
return path.join(getRuntimeStateDir(), 'watchdog');
|
|
26
32
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
38
|
-
fs.writeFileSync(p, 'enabled\n');
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
try {
|
|
42
|
-
fs.rmSync(p);
|
|
43
|
-
}
|
|
44
|
-
catch { /* already off */ }
|
|
33
|
+
/**
|
|
34
|
+
* (Re)load the daemon so a just-changed routine takes effect without a restart.
|
|
35
|
+
* Best-effort: enabling starts the daemon if it is not running, then SIGHUPs it.
|
|
36
|
+
* Dynamic import keeps daemon.ts's heavy deps off the watchdog command's load path.
|
|
37
|
+
*/
|
|
38
|
+
async function reloadDaemonForRoutine(startIfStopped) {
|
|
39
|
+
const { isDaemonRunning, ensureDaemonStarted, signalDaemonReload } = await import('../lib/daemon.js');
|
|
40
|
+
if (isDaemonRunning()) {
|
|
41
|
+
signalDaemonReload();
|
|
42
|
+
return;
|
|
45
43
|
}
|
|
44
|
+
if (startIfStopped)
|
|
45
|
+
ensureDaemonStarted();
|
|
46
46
|
}
|
|
47
47
|
/** Parse a duration flag ("60s", "5m", "1h") to ms, or fall back to `fallbackMs`. */
|
|
48
48
|
function durationMsOr(raw, fallbackMs) {
|
|
@@ -97,7 +97,7 @@ export function registerWatchdogCommand(program) {
|
|
|
97
97
|
.command('watchdog')
|
|
98
98
|
.description('Auto-nudge stalled agent terminals: detect stalls, resolve the exact split, inject "Continue." — no menu-bar needed.')
|
|
99
99
|
.option('--nudge', 'Actually inject (default is a dry run that only reports what it would do)')
|
|
100
|
-
.option('--watch', '
|
|
100
|
+
.option('--watch', 'Manual poll loop: run a tick every --interval (dry unless --nudge; the always-on path is `watchdog enable`)')
|
|
101
101
|
.option('--interval <dur>', 'Poll interval in --watch mode (e.g. 30s, 1m)', '30s')
|
|
102
102
|
.option('--stall <dur>', 'Idle time before a session counts as stalled', humanMs(DEFAULT_THRESHOLDS.stallMs))
|
|
103
103
|
.option('--cooldown <dur>', 'Minimum time between nudges to the same session', humanMs(DEFAULT_THRESHOLDS.cooldownMs))
|
|
@@ -113,12 +113,11 @@ export function registerWatchdogCommand(program) {
|
|
|
113
113
|
cooldownMs: durationMsOr(opts.cooldown, DEFAULT_THRESHOLDS.cooldownMs),
|
|
114
114
|
dormantMs: durationMsOr(opts.dormant, DEFAULT_THRESHOLDS.dormantMs),
|
|
115
115
|
};
|
|
116
|
-
// Injection gate: --nudge is the explicit
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
const computeWillInject = () => opts.nudge === true || (opts.watch === true && isGloballyEnabled());
|
|
116
|
+
// Injection gate: --nudge is the explicit opt-in to actually inject. Bare
|
|
117
|
+
// `agents watchdog` (and `--watch` without `--nudge`) is dry. The always-on
|
|
118
|
+
// path is the daemon routine, which runs `agents watchdog --nudge` — so the
|
|
119
|
+
// routine's enabled state IS the on/off switch, not a flag read here.
|
|
120
|
+
const computeWillInject = () => opts.nudge === true;
|
|
122
121
|
const tickOnce = async (willInject) => runWatchdogTick({
|
|
123
122
|
nudge: willInject,
|
|
124
123
|
nudgeText: opts.text,
|
|
@@ -137,11 +136,11 @@ export function registerWatchdogCommand(program) {
|
|
|
137
136
|
printTick(result, willInject);
|
|
138
137
|
return;
|
|
139
138
|
}
|
|
140
|
-
//
|
|
139
|
+
// Manual poll loop (for ad-hoc use; the always-on path is `enable`'s routine).
|
|
141
140
|
const intervalMs = durationMsOr(opts.interval, 30_000);
|
|
142
141
|
if (!computeWillInject() && !opts.json) {
|
|
143
|
-
console.log(chalk.yellow(`watchdog --watch is DETECT-ONLY
|
|
144
|
-
`
|
|
142
|
+
console.log(chalk.yellow(`watchdog --watch is DETECT-ONLY. Pass --nudge to inject, ` +
|
|
143
|
+
`or run 'agents watchdog enable' for the always-on daemon routine.`));
|
|
145
144
|
}
|
|
146
145
|
// eslint-disable-next-line no-constant-condition
|
|
147
146
|
while (true) {
|
|
@@ -163,50 +162,67 @@ export function registerWatchdogCommand(program) {
|
|
|
163
162
|
# One tick, actually inject "Continue." into stalled+addressable splits
|
|
164
163
|
agents watchdog --nudge
|
|
165
164
|
|
|
166
|
-
#
|
|
167
|
-
agents watchdog --watch --interval 30s --stall 60s --cooldown 5m
|
|
165
|
+
# Manual watch loop every 30s, tighter stall threshold (ad-hoc; dry unless --nudge)
|
|
166
|
+
agents watchdog --watch --nudge --interval 30s --stall 60s --cooldown 5m
|
|
168
167
|
|
|
169
168
|
# Machine-readable for the menu-bar
|
|
170
169
|
agents watchdog --json
|
|
171
170
|
|
|
172
|
-
# Turn on the
|
|
171
|
+
# Turn on the ALWAYS-ON watchdog (a daemon-fired routine)
|
|
173
172
|
agents watchdog enable
|
|
174
173
|
|
|
175
174
|
# Leave one session detected-but-untouched
|
|
176
175
|
agents watchdog policy <sessionId> handsoff
|
|
177
176
|
`,
|
|
178
177
|
notes: `
|
|
179
|
-
Decision path
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
Decision path: a cheap deterministic pre-filter resolves the obvious cases
|
|
179
|
+
(clearly complete -> skip; a clear promise-without-toolcall -> nudge) and
|
|
180
|
+
ESCALATES the judgment-heavy cases -- a session parked on a question, or an
|
|
181
|
+
ambiguous stall -- to a smart brain. The brain drives the agent to finish
|
|
182
|
+
end-to-end when it asked a needless / already-authorized question or paused
|
|
183
|
+
with work left, and leaves it for the human on genuine cases (credentials,
|
|
184
|
+
an irreversible or outward-facing action, a real ambiguous decision, or a
|
|
185
|
+
completed task). The brain is a customizable 'watchdog' workflow (drop a
|
|
186
|
+
WORKFLOW.md in project/user workflows/ to override the prompt + model);
|
|
187
|
+
absent one, the built-in prompt runs via 'agents run --mode plan'. Pass
|
|
188
|
+
--smart to force every stalled candidate through the brain.
|
|
183
189
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
Delivery (answer-router): a running agent is steered via its mailbox; a
|
|
191
|
+
parked-on-question agent is answered into its EXACT split -- tmux / iTerm /
|
|
192
|
+
an IDE integrated terminal (VS Codium / Cursor / VS Code) -- or re-entered
|
|
193
|
+
via resume when headless. A parked agent with no addressable rail (e.g.
|
|
194
|
+
Ghostty with no tmux) is flagged for the menu-bar and SKIPPED -- never a
|
|
195
|
+
guessed or frontmost target.
|
|
188
196
|
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
Always-on: 'agents watchdog enable' creates + enables a 'watchdog' command
|
|
198
|
+
routine ('${WATCHDOG_ROUTINE_SCHEDULE}' -> agents watchdog --nudge) and reloads the
|
|
199
|
+
daemon; 'disable' pauses it. Inspect it with 'agents routines list'. Defaults
|
|
200
|
+
OFF. Per-session policy: off (ignore), keep (default), handsoff (detect + flag).
|
|
191
201
|
|
|
192
202
|
State (tray-readable): ${path.join('~/.agents/.cache/state/watchdog', '{nudges,flags,last-tick}.json')}
|
|
193
203
|
`,
|
|
194
204
|
});
|
|
195
|
-
// ---
|
|
205
|
+
// --- always-on enable/disable/status (backed by the daemon routine) --------
|
|
196
206
|
cmd.command('enable')
|
|
197
|
-
.description('Turn ON
|
|
198
|
-
.action(() => {
|
|
199
|
-
|
|
200
|
-
|
|
207
|
+
.description('Turn ON the always-on watchdog: create/enable the `watchdog` routine and (re)load the daemon.')
|
|
208
|
+
.action(async () => {
|
|
209
|
+
ensureWatchdogRoutine(true);
|
|
210
|
+
await reloadDaemonForRoutine(true);
|
|
211
|
+
console.log(chalk.green(`watchdog: ENABLED — routine '${WATCHDOG_ROUTINE_NAME}' fires ${WATCHDOG_ROUTINE_SCHEDULE} (agents watchdog --nudge)`));
|
|
201
212
|
});
|
|
202
213
|
cmd.command('disable')
|
|
203
|
-
.description('Turn OFF
|
|
204
|
-
.action(() => {
|
|
205
|
-
|
|
206
|
-
|
|
214
|
+
.description('Turn OFF the always-on watchdog (pause the `watchdog` routine).')
|
|
215
|
+
.action(async () => {
|
|
216
|
+
if (!watchdogRoutineExists()) {
|
|
217
|
+
console.log(chalk.dim('watchdog: already off (no routine)'));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
setJobEnabled(WATCHDOG_ROUTINE_NAME, false);
|
|
221
|
+
await reloadDaemonForRoutine(false);
|
|
222
|
+
console.log(chalk.yellow('watchdog: DISABLED (routine paused)'));
|
|
207
223
|
});
|
|
208
224
|
cmd.command('status')
|
|
209
|
-
.description('Show whether
|
|
225
|
+
.description('Show whether the always-on watchdog routine is enabled and where state is written.')
|
|
210
226
|
.option('--json', 'Emit status as JSON (for the menu-bar / scripts)')
|
|
211
227
|
.action((_opts, command) => {
|
|
212
228
|
// The parent `watchdog` command also declares --json and greedily parses it
|
|
@@ -214,12 +230,12 @@ export function registerWatchdogCommand(program) {
|
|
|
214
230
|
// parent, not this subcommand. optsWithGlobals() merges both levels, so we
|
|
215
231
|
// read it correctly regardless of which command commander bound it to.
|
|
216
232
|
const json = command.optsWithGlobals().json === true;
|
|
217
|
-
const on =
|
|
233
|
+
const on = isWatchdogRoutineEnabled();
|
|
218
234
|
if (json) {
|
|
219
|
-
console.log(JSON.stringify({ enabled: on, stateDir: stateDir() }));
|
|
235
|
+
console.log(JSON.stringify({ enabled: on, routine: WATCHDOG_ROUTINE_NAME, stateDir: stateDir() }));
|
|
220
236
|
return;
|
|
221
237
|
}
|
|
222
|
-
console.log(`
|
|
238
|
+
console.log(`always-on watchdog: ${on ? chalk.green('ON') : chalk.dim('off')} (routine '${WATCHDOG_ROUTINE_NAME}')`);
|
|
223
239
|
console.log(`state dir: ${chalk.dim(stateDir())}`);
|
|
224
240
|
});
|
|
225
241
|
// --- per-session policy ----------------------------------------------------
|
package/dist/index.js
CHANGED
|
@@ -26,11 +26,49 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
26
26
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
27
27
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
28
28
|
const VERSION = packageJson.version;
|
|
29
|
+
import { SYNC_GET_CMD, SYNC_PING_CMD, SYNC_LOCK_CMD } from './lib/secrets/sync-commands.js';
|
|
29
30
|
if (process.argv[2] === '__vault-age-helper') {
|
|
30
31
|
const { runVaultAgeHelperCli } = await import('./lib/secrets/vault-age-helper.js');
|
|
31
32
|
await runVaultAgeHelperCli();
|
|
32
33
|
process.exit(process.exitCode ?? 0);
|
|
33
34
|
}
|
|
35
|
+
// Synchronous secrets-broker clients (src/lib/secrets/agent.ts). These are the
|
|
36
|
+
// hot read path: `readAndResolveBundleEnv` is synchronous all the way down, so
|
|
37
|
+
// it can't await a socket round-trip — it spawns one of these and reads the
|
|
38
|
+
// exit code (0 = hit/alive, 3 = miss/down).
|
|
39
|
+
//
|
|
40
|
+
// Intercepted HERE, before commander and before the startup STATEMENTS, for the
|
|
41
|
+
// same reason as __daemon-run and __vault-age-helper above. Everything below
|
|
42
|
+
// this point runs `checkForUpdates()` and `spawnDetachedSync()` on every
|
|
43
|
+
// non-help invocation — so registering these as ordinary hidden subcommands
|
|
44
|
+
// would fire an update check and fork a detached background sync on *every
|
|
45
|
+
// cache hit*, which is both a fork storm on the hot path and a source of stdout
|
|
46
|
+
// writes that could corrupt the JSON payload. Keep them above the line;
|
|
47
|
+
// agent.test.ts asserts this ordering so the block can't drift downward.
|
|
48
|
+
//
|
|
49
|
+
// The tokens are imported from the leaf module sync-commands.ts — the SAME
|
|
50
|
+
// bindings the clients spawn with, so this dispatch and those spawns cannot
|
|
51
|
+
// drift apart. It is a leaf precisely so binding them here is free: importing
|
|
52
|
+
// agent.js would pull the whole secrets graph into every invocation. Matching
|
|
53
|
+
// string literals were the earlier design; a text-scanning test could not
|
|
54
|
+
// reliably catch their divergence, and the divergence is silent — the CLI says
|
|
55
|
+
// "unknown command", the client reads that as "broker down", and every read
|
|
56
|
+
// falls back to a Touch ID prompt.
|
|
57
|
+
//
|
|
58
|
+
// "Before the startup statements", not "before all code": ESM hoists the
|
|
59
|
+
// imports below, so those module bodies evaluate first. They are side-effect
|
|
60
|
+
// free today (no top-level stdout write or spawn) — which is what makes this
|
|
61
|
+
// safe, and worth preserving.
|
|
62
|
+
if (process.argv[2] === SYNC_GET_CMD ||
|
|
63
|
+
process.argv[2] === SYNC_PING_CMD ||
|
|
64
|
+
process.argv[2] === SYNC_LOCK_CMD) {
|
|
65
|
+
const { runAgentGetSync, runAgentPingSync, runAgentLockSync } = await import('./lib/secrets/agent.js');
|
|
66
|
+
const name = process.argv[3] ?? '';
|
|
67
|
+
const code = process.argv[2] === SYNC_GET_CMD ? await runAgentGetSync(name)
|
|
68
|
+
: process.argv[2] === SYNC_PING_CMD ? await runAgentPingSync()
|
|
69
|
+
: await runAgentLockSync(name);
|
|
70
|
+
process.exit(code);
|
|
71
|
+
}
|
|
34
72
|
import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, installPackageIntoPrefix, installPackageWithBun, verifyInstalledVersion, refreshAliasShims, downloadVerifiedTarball, } from './lib/self-update.js';
|
|
35
73
|
// Detect dev/working-tree builds and default the noisy startup steps off.
|
|
36
74
|
// Three cases trip this:
|
|
@@ -55,9 +93,10 @@ if (IS_DEV_BUILD) {
|
|
|
55
93
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
56
94
|
// first byte of output), the registry maps a command name to a thunk that
|
|
57
95
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
58
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadResources, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadRoutines, loadMonitors, loadRun, loadFork, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadCheck, loadStatus, loadProfiles, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadHq, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
96
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadResources, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadRun, loadFork, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadCheck, loadStatus, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadHq, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
59
97
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
60
98
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
99
|
+
import { getCliLaunch } from './lib/cli-entry.js';
|
|
61
100
|
import { emit, redactArgs } from './lib/events.js';
|
|
62
101
|
// Transparent shim delegate: the generated Windows `.cmd` shims invoke
|
|
63
102
|
// `agents __shim <agent>[@version] <raw args>`. Intercept here, before commander
|
|
@@ -294,7 +333,7 @@ async function showWhatsNew(fromVersion, toVersion) {
|
|
|
294
333
|
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
295
334
|
import { getUpdateCheckPath, getMigratedSentinelPath, getUserAgentsDir, getRuntimeStateDir } from './lib/state.js';
|
|
296
335
|
import { resolveBrandName, disabledCommandsForActiveBrand } from './lib/brand.js';
|
|
297
|
-
import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, findAgentsCliInstalls, } from './lib/self-update.js';
|
|
336
|
+
import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, findAgentsCliInstalls, resolveRunningPackageRoot, } from './lib/self-update.js';
|
|
298
337
|
const UPDATE_CHECK_FILE = getUpdateCheckPath();
|
|
299
338
|
/**
|
|
300
339
|
* Warn once when PATH resolves `agents` to a different agents-cli install
|
|
@@ -306,7 +345,16 @@ const UPDATE_CHECK_FILE = getUpdateCheckPath();
|
|
|
306
345
|
*/
|
|
307
346
|
function maybeWarnMultiInstall() {
|
|
308
347
|
const sentinel = path.join(getRuntimeStateDir(), 'multi-install-warned');
|
|
309
|
-
|
|
348
|
+
let runningRoot;
|
|
349
|
+
try {
|
|
350
|
+
runningRoot = resolveRunningPackageRoot(__dirname);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// Without a real root for the running copy there is nothing to compare
|
|
354
|
+
// against, and a guess here is exactly what produced the phantom
|
|
355
|
+
// "/$bunfs" install. This warning is advisory — stay silent instead.
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
310
358
|
const byRoot = new Map();
|
|
311
359
|
byRoot.set(runningRoot, { version: VERSION, note: 'running' });
|
|
312
360
|
for (const install of findAgentsCliInstalls(process.env.PATH || '')) {
|
|
@@ -370,7 +418,7 @@ function printResolvedPackage(metadata) {
|
|
|
370
418
|
console.log(chalk.gray(`Integrity: ${metadata.integrity}`));
|
|
371
419
|
}
|
|
372
420
|
async function installResolvedPackage(metadata) {
|
|
373
|
-
const packageRoot =
|
|
421
|
+
const packageRoot = resolveRunningPackageRoot(__dirname);
|
|
374
422
|
// Download the published tarball and prove its bytes match the registry
|
|
375
423
|
// integrity BEFORE installing anything. A `name@version` spec would let the
|
|
376
424
|
// package manager fetch and install whatever the registry serves with no
|
|
@@ -466,9 +514,11 @@ async function promptUpgrade(latestVersion) {
|
|
|
466
514
|
console.log();
|
|
467
515
|
// Re-exec the verified install's entrypoint and exit. PATH lookup of
|
|
468
516
|
// `agents` could resolve a different copy (dev build, another prefix)
|
|
469
|
-
// than the one that was just upgraded.
|
|
470
|
-
|
|
471
|
-
|
|
517
|
+
// than the one that was just upgraded. getCliLaunch resolves the JS-vs-
|
|
518
|
+
// standalone shape — never hand-roll `[process.execPath, entrypoint]`,
|
|
519
|
+
// which hands the bun virtual entry to a compiled binary as a bogus arg.
|
|
520
|
+
const { command, args } = getCliLaunch(process.argv.slice(2));
|
|
521
|
+
const result = spawnSync(command, args, {
|
|
472
522
|
stdio: 'inherit',
|
|
473
523
|
shell: false,
|
|
474
524
|
});
|
|
@@ -730,6 +780,7 @@ async function registerAllEagerCommands() {
|
|
|
730
780
|
await reg(loadWorktree);
|
|
731
781
|
await reg(loadVersions);
|
|
732
782
|
await reg(loadImport);
|
|
783
|
+
await reg(loadExport);
|
|
733
784
|
await reg(loadPackages);
|
|
734
785
|
await reg(loadRoutines);
|
|
735
786
|
await reg(loadMonitors);
|
|
@@ -746,6 +797,7 @@ async function registerAllEagerCommands() {
|
|
|
746
797
|
await reg(loadStatus);
|
|
747
798
|
registerExecAliasCommand(program);
|
|
748
799
|
await reg(loadProfiles);
|
|
800
|
+
await reg(loadHarness);
|
|
749
801
|
await reg(loadSecrets);
|
|
750
802
|
await reg(loadLogin);
|
|
751
803
|
await reg(loadWallet);
|