@phnx-labs/agents-cli 1.20.78 → 1.20.82
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 +487 -0
- package/README.md +25 -25
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +9 -0
- package/dist/commands/activity.js +153 -24
- package/dist/commands/apply.js +11 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/commands.d.ts +1 -1
- package/dist/commands/commands.js +7 -6
- package/dist/commands/defaults.js +3 -11
- package/dist/commands/doctor.d.ts +65 -0
- package/dist/commands/doctor.js +575 -31
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +24 -5
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +87 -11
- package/dist/commands/harness.js +2 -2
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +8 -4
- package/dist/commands/logs.js +6 -0
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -6
- package/dist/commands/permissions.js +1 -1
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +4 -4
- package/dist/commands/repo.d.ts +78 -0
- package/dist/commands/repo.js +187 -11
- package/dist/commands/routines.js +12 -2
- package/dist/commands/rules.js +13 -12
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +100 -32
- package/dist/commands/sessions-inject.d.ts +7 -4
- package/dist/commands/sessions-inject.js +18 -18
- package/dist/commands/sessions-picker.js +32 -3
- package/dist/commands/sessions-resume.d.ts +16 -0
- package/dist/commands/sessions-resume.js +14 -13
- package/dist/commands/sessions.d.ts +31 -0
- package/dist/commands/sessions.js +141 -16
- package/dist/commands/set.d.ts +15 -0
- package/dist/commands/set.js +79 -0
- package/dist/commands/setup-fleet.d.ts +21 -0
- package/dist/commands/setup-fleet.js +201 -0
- package/dist/commands/setup-secrets.d.ts +20 -0
- package/dist/commands/setup-secrets.js +223 -0
- package/dist/commands/setup.js +17 -3
- package/dist/commands/skills.js +1 -1
- package/dist/commands/sync.js +7 -2
- package/dist/commands/teams-picker.js +0 -1
- package/dist/commands/teams.d.ts +33 -1
- package/dist/commands/teams.js +208 -44
- package/dist/commands/versions.js +10 -5
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +11 -0
- package/dist/index.js +5 -8
- package/dist/lib/acp/harnesses.js +0 -7
- package/dist/lib/activity.d.ts +156 -0
- package/dist/lib/activity.js +282 -0
- package/dist/lib/agents.d.ts +9 -2
- package/dist/lib/agents.js +72 -63
- package/dist/lib/browser/service.js +3 -0
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/capabilities.js +6 -2
- package/dist/lib/crabbox/lease.js +2 -2
- package/dist/lib/crabbox/setup-copy.d.ts +4 -4
- package/dist/lib/crabbox/setup-copy.js +4 -4
- package/dist/lib/daemon.d.ts +18 -0
- package/dist/lib/daemon.js +47 -6
- package/dist/lib/devices/fleet.d.ts +1 -1
- package/dist/lib/devices/fleet.js +1 -1
- package/dist/lib/doctor-diff.d.ts +20 -0
- package/dist/lib/doctor-diff.js +6 -1
- package/dist/lib/drift.d.ts +31 -11
- package/dist/lib/drift.js +58 -7
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +10 -1
- package/dist/lib/exec.js +35 -8
- package/dist/lib/feed-post.d.ts +28 -1
- package/dist/lib/feed-post.js +110 -2
- package/dist/lib/fleet/apply.js +8 -1
- package/dist/lib/fleet/auth-sync.d.ts +18 -1
- package/dist/lib/fleet/auth-sync.js +25 -11
- package/dist/lib/fleet/remote-login.js +5 -0
- package/dist/lib/git.d.ts +14 -14
- package/dist/lib/git.js +41 -44
- package/dist/lib/hooks.d.ts +42 -0
- package/dist/lib/hooks.js +137 -93
- package/dist/lib/hosts/dispatch.d.ts +28 -1
- package/dist/lib/hosts/dispatch.js +33 -5
- package/dist/lib/hosts/option.js +2 -2
- package/dist/lib/hosts/passthrough.d.ts +22 -1
- package/dist/lib/hosts/passthrough.js +238 -13
- package/dist/lib/hosts/run-target.d.ts +2 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/hq/floor.d.ts +1 -1
- package/dist/lib/hq/floor.js +6 -1
- package/dist/lib/mcp.js +3 -71
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/notify-desktop.d.ts +15 -0
- package/dist/lib/menubar/notify-desktop.js +41 -5
- package/dist/lib/merged-resources.d.ts +11 -0
- package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
- package/dist/lib/permissions.d.ts +0 -32
- package/dist/lib/permissions.js +4 -157
- package/dist/lib/picker.js +1 -1
- package/dist/lib/platform/process.d.ts +16 -5
- package/dist/lib/platform/process.js +54 -0
- package/dist/lib/plugins.d.ts +0 -8
- package/dist/lib/plugins.js +4 -110
- package/dist/lib/project-resources.js +4 -1
- package/dist/lib/refresh.d.ts +5 -3
- package/dist/lib/refresh.js +7 -5
- package/dist/lib/resources/commands.js +4 -1
- package/dist/lib/resources/mcp.js +0 -4
- package/dist/lib/resources/permissions.d.ts +1 -1
- package/dist/lib/resources/permissions.js +1 -5
- package/dist/lib/resources/rules.js +4 -1
- package/dist/lib/resources/skills.js +4 -1
- package/dist/lib/resources/subagents.js +4 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/routines.d.ts +20 -0
- package/dist/lib/routines.js +6 -0
- package/dist/lib/run-defaults.d.ts +1 -0
- package/dist/lib/run-defaults.js +9 -0
- package/dist/lib/runner.d.ts +0 -12
- package/dist/lib/runner.js +49 -10
- 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 +16 -0
- package/dist/lib/secrets/agent.js +105 -3
- package/dist/lib/secrets/audit.d.ts +46 -0
- package/dist/lib/secrets/audit.js +56 -0
- package/dist/lib/secrets/bundles.d.ts +11 -8
- package/dist/lib/secrets/bundles.js +31 -23
- package/dist/lib/secrets/index.d.ts +2 -1
- package/dist/lib/secrets/index.js +8 -2
- package/dist/lib/secrets/remote.d.ts +75 -2
- package/dist/lib/secrets/remote.js +139 -5
- package/dist/lib/secrets/sync.js +3 -3
- package/dist/lib/session/active.d.ts +110 -26
- package/dist/lib/session/active.js +161 -50
- package/dist/lib/session/actor-sidecar.d.ts +23 -0
- package/dist/lib/session/actor-sidecar.js +101 -0
- package/dist/lib/session/db.d.ts +4 -1
- package/dist/lib/session/db.js +65 -8
- package/dist/lib/session/discover.d.ts +4 -0
- package/dist/lib/session/discover.js +13 -0
- package/dist/lib/session/hook-sessions.d.ts +9 -0
- package/dist/lib/session/hook-sessions.js +57 -8
- package/dist/lib/session/pid-registry.d.ts +13 -0
- package/dist/lib/session/render.d.ts +2 -0
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/types.d.ts +16 -0
- package/dist/lib/staleness/detectors/commands.js +2 -2
- package/dist/lib/staleness/detectors/permissions.js +1 -45
- package/dist/lib/staleness/writers/commands.js +4 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -2
- package/dist/lib/startup/command-registry.js +2 -4
- package/dist/lib/subagents-registry.js +5 -12
- package/dist/lib/subagents.d.ts +0 -10
- package/dist/lib/subagents.js +0 -12
- package/dist/lib/teams/agents.d.ts +0 -2
- package/dist/lib/teams/agents.js +10 -27
- package/dist/lib/teams/index.d.ts +1 -1
- package/dist/lib/teams/index.js +1 -1
- package/dist/lib/types.d.ts +17 -9
- package/dist/lib/usage.d.ts +28 -5
- package/dist/lib/usage.js +271 -8
- package/dist/lib/versions.js +7 -1
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +25 -13
- package/package.json +1 -2
- package/dist/commands/check.d.ts +0 -15
- package/dist/commands/check.js +0 -180
- package/dist/commands/resources.d.ts +0 -5
package/dist/lib/agents.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core agent configuration and detection module.
|
|
3
3
|
*
|
|
4
|
-
* Defines the canonical registry of
|
|
5
|
-
* Gemini, Cursor, OpenCode, OpenClaw, Copilot, Amp, Kiro, Goose, Grok)
|
|
6
|
-
* CLI commands, config paths, capability flags, and MCP integration
|
|
4
|
+
* Defines the canonical registry of current and legacy AI coding agents (Claude,
|
|
5
|
+
* Codex, Gemini, Cursor, OpenCode, OpenClaw, Copilot, Amp, Kiro, Goose, Grok)
|
|
6
|
+
* with their CLI commands, config paths, capability flags, and MCP integration
|
|
7
|
+
* points.
|
|
7
8
|
*
|
|
8
9
|
* Provides functions for detecting installed CLIs, resolving version-managed binaries,
|
|
9
10
|
* reading account/auth info, and managing MCP server registrations across agents.
|
|
@@ -256,13 +257,15 @@ export const AGENTS = {
|
|
|
256
257
|
nativeAgentsSkillsDir: true,
|
|
257
258
|
// Google retired the Gemini CLI (announced at Google I/O 2026, May 19); the `gemini`
|
|
258
259
|
// command stopped serving free/Pro/Ultra requests on June 18, 2026. Antigravity CLI
|
|
259
|
-
// (`agy`) is the official successor.
|
|
260
|
+
// (`agy`) is the official successor. Hard deprecation keeps legacy Gemini
|
|
261
|
+
// sessions/config parseable while blocking install/import/sync.
|
|
260
262
|
deprecated: {
|
|
261
263
|
by: 'Google',
|
|
262
264
|
date: 'June 18, 2026',
|
|
263
265
|
reason: 'The Gemini CLI was retired for free, Pro, and Ultra tiers and no longer serves requests (announced at Google I/O 2026 on May 19).',
|
|
264
266
|
replacement: 'antigravity',
|
|
265
267
|
url: 'https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/',
|
|
268
|
+
hard: true,
|
|
266
269
|
},
|
|
267
270
|
// gemini hooks: shipped in v0.26.0 (Jan 2026); older binaries silently ignore the `hooks` key.
|
|
268
271
|
// extensions: gemini-extension.json bundles shipped in v0.8.0; custom subagents in v0.36.0.
|
|
@@ -697,58 +700,18 @@ export const AGENTS = {
|
|
|
697
700
|
rulesImports: false,
|
|
698
701
|
},
|
|
699
702
|
},
|
|
700
|
-
// ForgeCode (`forge`) from Tailcall. It reads AGENTS.md project rules,
|
|
701
|
-
// SKILL.md directories, and MCP servers from `.mcp.json` files.
|
|
702
|
-
forge: {
|
|
703
|
-
id: 'forge',
|
|
704
|
-
name: 'ForgeCode',
|
|
705
|
-
color: 'greenBright',
|
|
706
|
-
cliCommand: 'forge',
|
|
707
|
-
npmPackage: '',
|
|
708
|
-
installScript: 'curl -fsSL https://forgecode.dev/cli | sh',
|
|
709
|
-
configDir: path.join(HOME, '.forge'),
|
|
710
|
-
commandsDir: path.join(HOME, '.forge', 'commands'),
|
|
711
|
-
commandsSubdir: 'commands',
|
|
712
|
-
skillsDir: path.join(HOME, '.forge', 'skills'),
|
|
713
|
-
hooksDir: 'hooks',
|
|
714
|
-
instructionsFile: 'AGENTS.md',
|
|
715
|
-
format: 'markdown',
|
|
716
|
-
variableSyntax: '$ARGUMENTS',
|
|
717
|
-
supportsHooks: false,
|
|
718
|
-
// Commands: ForgeCode reads Markdown slash commands from `~/.forge/commands/<name>.md`
|
|
719
|
-
// (also the shared `~/.agents/commands/`); the filename is the command name.
|
|
720
|
-
// Subagents: named `.md` agent definitions with YAML frontmatter under
|
|
721
|
-
// `~/.forge/agents/<name>.md` — same Markdown+frontmatter shape as Droid/Copilot
|
|
722
|
-
// (no `color` field), so transformSubagentForForge aliases transformSubagentForDroid.
|
|
723
|
-
// See https://forgecode.dev/docs/commands/ and /docs/agent-definition-guide/.
|
|
724
|
-
capabilities: {
|
|
725
|
-
hooks: false,
|
|
726
|
-
mcp: true,
|
|
727
|
-
mcpHttp: true,
|
|
728
|
-
mcpHeaders: false,
|
|
729
|
-
// Permissions: ~/.forge/permissions.yaml (or $FORGE_CONFIG/permissions.yaml)
|
|
730
|
-
// carries ordered operation-family glob policies. It is active only when
|
|
731
|
-
// `.forge.toml` sets `restricted = true`; MCP tools bypass this file.
|
|
732
|
-
allowlist: true,
|
|
733
|
-
skills: true,
|
|
734
|
-
commands: true,
|
|
735
|
-
plugins: false,
|
|
736
|
-
subagents: true,
|
|
737
|
-
rules: { file: 'AGENTS.md' },
|
|
738
|
-
workflows: false,
|
|
739
|
-
memory: false,
|
|
740
|
-
modes: ['edit'],
|
|
741
|
-
rulesImports: false,
|
|
742
|
-
},
|
|
743
|
-
},
|
|
744
703
|
};
|
|
745
|
-
/** All
|
|
704
|
+
/** All current and legacy agent IDs derived from the AGENTS registry. */
|
|
746
705
|
export const ALL_AGENT_IDS = Object.keys(AGENTS);
|
|
706
|
+
/** Agents retained only for legacy reads, not install/import/sync targets. */
|
|
707
|
+
export const HARD_DEPRECATED_AGENT_IDS = ALL_AGENT_IDS.filter((id) => AGENTS[id].deprecated?.hard);
|
|
708
|
+
/** Agents that can receive managed installs, imports, and resource sync writes. */
|
|
709
|
+
export const MANAGED_AGENT_IDS = ALL_AGENT_IDS.filter((id) => !AGENTS[id].deprecated?.hard);
|
|
747
710
|
/**
|
|
748
711
|
* A self-updating agent is a single global binary installed by an official
|
|
749
712
|
* `curl … | sh` / `brew install` script that carries NO version token — the
|
|
750
713
|
* installer can only ever fetch the *current* release, and the binary then keeps
|
|
751
|
-
* itself up to date in place (droid, grok, antigravity, cursor, hermes,
|
|
714
|
+
* itself up to date in place (droid, grok, antigravity, cursor, hermes,
|
|
752
715
|
* kiro, goose). There is no semver to pin, so agents-cli must not model these as
|
|
753
716
|
* having multiple installable version-homes the way it does for npm-packaged
|
|
754
717
|
* agents (claude, codex, kimi, …).
|
|
@@ -768,6 +731,9 @@ export function isSelfUpdatingAgent(agent) {
|
|
|
768
731
|
const cfg = AGENTS[agent];
|
|
769
732
|
return !cfg.npmPackage && !!cfg.installScript && !cfg.installScript.includes('VERSION');
|
|
770
733
|
}
|
|
734
|
+
export function isAgentHardDeprecated(agent) {
|
|
735
|
+
return AGENTS[agent].deprecated?.hard === true;
|
|
736
|
+
}
|
|
771
737
|
// Capability-filtered agent lists used to live here as `*_CAPABLE_AGENTS`
|
|
772
738
|
// constants. They were a frequent source of silent-skip bugs (e.g. grok
|
|
773
739
|
// rules sync gated on `COMMANDS_CAPABLE_AGENTS`). Use `capableAgents(cap)`
|
|
@@ -1552,6 +1518,38 @@ export async function getAccountInfo(agentId, home) {
|
|
|
1552
1518
|
const email = data.active || null;
|
|
1553
1519
|
return { ...empty, email, signedIn: !!email, lastActive };
|
|
1554
1520
|
}
|
|
1521
|
+
case 'cursor': {
|
|
1522
|
+
// Cursor CLI keeps account metadata in ~/.cursor/cli-config.json
|
|
1523
|
+
// (authInfo: { email, userId, authId }) and its OAuth tokens SEPARATELY
|
|
1524
|
+
// in ~/.config/cursor/auth.json ({ accessToken, refreshToken }). Presence
|
|
1525
|
+
// of an access token is the signed-in signal; email/ids come from
|
|
1526
|
+
// cli-config. authId is the OAuth subject (e.g. "google-oauth2|<n>") — the
|
|
1527
|
+
// same value the usage endpoint keys on (see getCursorUsageInfo).
|
|
1528
|
+
const cfgPath = resolveAccountCredentialPath(base, '.cursor', 'cli-config.json');
|
|
1529
|
+
if (!cfgPath)
|
|
1530
|
+
return { ...empty, lastActive };
|
|
1531
|
+
try {
|
|
1532
|
+
const cfg = JSON.parse(await fs.promises.readFile(cfgPath, 'utf-8'));
|
|
1533
|
+
const authInfo = cfg?.authInfo;
|
|
1534
|
+
const email = typeof authInfo?.email === 'string' ? authInfo.email : null;
|
|
1535
|
+
const accountId = normalizeIdentityPart(authInfo?.authId ?? authInfo?.userId);
|
|
1536
|
+
if (!email && !accountId)
|
|
1537
|
+
return { ...empty, lastActive };
|
|
1538
|
+
const authPath = resolveAccountCredentialPath(base, '.config', 'cursor', 'auth.json');
|
|
1539
|
+
let hasToken = false;
|
|
1540
|
+
if (authPath) {
|
|
1541
|
+
try {
|
|
1542
|
+
const tok = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
|
|
1543
|
+
hasToken = typeof tok?.accessToken === 'string' && tok.accessToken.length > 0;
|
|
1544
|
+
}
|
|
1545
|
+
catch { /* unreadable token file */ }
|
|
1546
|
+
}
|
|
1547
|
+
const accountKey = buildIdentityKey(agentId, [['user', accountId]]);
|
|
1548
|
+
return { ...empty, email, accountId, accountKey, signedIn: hasToken || !!email, lastActive };
|
|
1549
|
+
}
|
|
1550
|
+
catch { }
|
|
1551
|
+
return { ...empty, lastActive };
|
|
1552
|
+
}
|
|
1555
1553
|
case 'grok': {
|
|
1556
1554
|
// Grok stores auth in ~/.grok/auth.json as a map keyed by
|
|
1557
1555
|
// "<oidc_issuer>::<client_id>" -> { email, user_id, refresh_token,
|
|
@@ -1867,7 +1865,7 @@ function buildIdentityKey(agentId, parts) {
|
|
|
1867
1865
|
/** Register an MCP server with an agent's CLI via `mcp add`. */
|
|
1868
1866
|
export async function registerMcp(agentId, name, command, scope = 'user', transport = 'stdio', options) {
|
|
1869
1867
|
const agent = AGENTS[agentId];
|
|
1870
|
-
if (!
|
|
1868
|
+
if (!supports(agentId, 'mcp').ok) {
|
|
1871
1869
|
return { success: false, error: 'Agent does not support MCP' };
|
|
1872
1870
|
}
|
|
1873
1871
|
if (transport === 'http' && !supports(agentId, 'mcpHttp').ok) {
|
|
@@ -1876,7 +1874,7 @@ export async function registerMcp(agentId, name, command, scope = 'user', transp
|
|
|
1876
1874
|
if (transport === 'http' && options?.headers && Object.keys(options.headers).length > 0 && !supports(agentId, 'mcpHeaders').ok) {
|
|
1877
1875
|
return { success: false, error: 'skipped: HTTP MCP headers are only supported for Claude registration' };
|
|
1878
1876
|
}
|
|
1879
|
-
if (agentId === 'hermes'
|
|
1877
|
+
if (agentId === 'hermes') {
|
|
1880
1878
|
try {
|
|
1881
1879
|
writeMcpToConfig(agentId, name, command, scope, transport, options?.home);
|
|
1882
1880
|
return { success: true };
|
|
@@ -1927,10 +1925,10 @@ export async function registerMcp(agentId, name, command, scope = 'user', transp
|
|
|
1927
1925
|
/** Unregister (remove) a named MCP server from an agent's CLI config. */
|
|
1928
1926
|
export async function unregisterMcp(agentId, name, options) {
|
|
1929
1927
|
const agent = AGENTS[agentId];
|
|
1930
|
-
if (!
|
|
1928
|
+
if (!supports(agentId, 'mcp').ok) {
|
|
1931
1929
|
return { success: false, error: 'Agent does not support MCP' };
|
|
1932
1930
|
}
|
|
1933
|
-
if (agentId === 'hermes'
|
|
1931
|
+
if (agentId === 'hermes') {
|
|
1934
1932
|
try {
|
|
1935
1933
|
removeMcpFromConfig(agentId, name, options?.home);
|
|
1936
1934
|
return { success: true };
|
|
@@ -2282,8 +2280,6 @@ export function getUserMcpConfigPath(agentId) {
|
|
|
2282
2280
|
return path.join(agent.configDir, 'mcp.json');
|
|
2283
2281
|
case 'hermes':
|
|
2284
2282
|
return path.join(agent.configDir, 'config.yaml');
|
|
2285
|
-
case 'forge':
|
|
2286
|
-
return path.join(agent.configDir, '.mcp.json');
|
|
2287
2283
|
default:
|
|
2288
2284
|
// Gemini and others use settings.json
|
|
2289
2285
|
return path.join(agent.configDir, 'settings.json');
|
|
@@ -2320,8 +2316,6 @@ export function getMcpConfigPathForHome(agentId, home) {
|
|
|
2320
2316
|
return path.join(home, '.factory', 'mcp.json');
|
|
2321
2317
|
case 'hermes':
|
|
2322
2318
|
return path.join(home, '.hermes', 'config.yaml');
|
|
2323
|
-
case 'forge':
|
|
2324
|
-
return path.join(home, '.forge', '.mcp.json');
|
|
2325
2319
|
default:
|
|
2326
2320
|
return path.join(home, agentConfigDirName(agentId), 'settings.json');
|
|
2327
2321
|
}
|
|
@@ -2361,8 +2355,6 @@ export function getProjectMcpConfigPath(agentId, cwd = process.cwd()) {
|
|
|
2361
2355
|
return path.join(cwd, '.factory', 'mcp.json');
|
|
2362
2356
|
case 'hermes':
|
|
2363
2357
|
return path.join(cwd, '.hermes', 'config.yaml');
|
|
2364
|
-
case 'forge':
|
|
2365
|
-
return path.join(cwd, '.mcp.json');
|
|
2366
2358
|
default:
|
|
2367
2359
|
return path.join(cwd, `.${agentId}`, 'settings.json');
|
|
2368
2360
|
}
|
|
@@ -2506,9 +2498,6 @@ export const AGENT_NAME_ALIASES = {
|
|
|
2506
2498
|
droid: 'droid',
|
|
2507
2499
|
hermes: 'hermes',
|
|
2508
2500
|
'hermes-agent': 'hermes',
|
|
2509
|
-
forge: 'forge',
|
|
2510
|
-
forgecode: 'forge',
|
|
2511
|
-
'forge-code': 'forge',
|
|
2512
2501
|
};
|
|
2513
2502
|
/**
|
|
2514
2503
|
* Resolve a user-provided agent name (alias, shorthand, or canonical) to its AgentId.
|
|
@@ -2559,6 +2548,26 @@ export function deprecationNotice(agent) {
|
|
|
2559
2548
|
lines.push(` ${dep.url}`);
|
|
2560
2549
|
return lines;
|
|
2561
2550
|
}
|
|
2551
|
+
export function hardDeprecationNotice(agent) {
|
|
2552
|
+
const dep = AGENTS[agent].deprecated;
|
|
2553
|
+
if (!dep?.hard)
|
|
2554
|
+
return null;
|
|
2555
|
+
const name = AGENTS[agent].name;
|
|
2556
|
+
const lines = [
|
|
2557
|
+
`${name} is no longer supported by agents-cli because ${dep.by} retired it (${dep.date}).`,
|
|
2558
|
+
` ${dep.reason}`,
|
|
2559
|
+
];
|
|
2560
|
+
if (dep.replacement) {
|
|
2561
|
+
const rep = AGENTS[dep.replacement];
|
|
2562
|
+
lines.push(` Use ${rep.name} instead: agents add ${rep.id}`);
|
|
2563
|
+
}
|
|
2564
|
+
if (dep.url)
|
|
2565
|
+
lines.push(` ${dep.url}`);
|
|
2566
|
+
return lines;
|
|
2567
|
+
}
|
|
2568
|
+
export function hardDeprecationError(agent) {
|
|
2569
|
+
return hardDeprecationNotice(agent)?.join('\n') ?? `${AGENTS[agent].name} is no longer supported.`;
|
|
2570
|
+
}
|
|
2562
2571
|
/**
|
|
2563
2572
|
* Print a deprecation warning (yellow) if the agent's registry entry carries a
|
|
2564
2573
|
* `deprecated` marker; no-op otherwise. Call from any user entry point that
|
|
@@ -14,6 +14,7 @@ import { clickAtCoords, hoverAtCoords, scrollAtCoords, typeText, pressKey, focus
|
|
|
14
14
|
import { typeEditorText } from './editor.js';
|
|
15
15
|
import { detectUploadPattern, stageUploadFile, uploadToDropTarget, uploadToFileInput, uploadViaFileChooser, } from './upload.js';
|
|
16
16
|
import { emit } from '../events.js';
|
|
17
|
+
import { resolveActor } from '../actor.js';
|
|
17
18
|
import { sshExecAsync } from '../ssh-exec.js';
|
|
18
19
|
function isPathInside(candidate, dir) {
|
|
19
20
|
const rel = path.relative(dir, candidate);
|
|
@@ -331,6 +332,7 @@ export class BrowserService {
|
|
|
331
332
|
currentTabId: undefined,
|
|
332
333
|
createdAt: Date.now(),
|
|
333
334
|
pid: conn.pid,
|
|
335
|
+
owner: resolveActor().id, // who launched this browser task (RUSH-2020)
|
|
334
336
|
};
|
|
335
337
|
// For Electron, get the existing window as the tab
|
|
336
338
|
if (conn.electron) {
|
|
@@ -2012,6 +2014,7 @@ export class BrowserService {
|
|
|
2012
2014
|
endedAt: Date.now(),
|
|
2013
2015
|
domains,
|
|
2014
2016
|
tabCount: Object.keys(task.tabs).length,
|
|
2017
|
+
...(task.owner ? { owner: task.owner } : {}), // carry who launched it (RUSH-2020)
|
|
2015
2018
|
});
|
|
2016
2019
|
// Keep only last 50 entries
|
|
2017
2020
|
history = history.slice(0, 50);
|
|
@@ -78,6 +78,11 @@ export interface Task {
|
|
|
78
78
|
currentTabId?: string;
|
|
79
79
|
createdAt: number;
|
|
80
80
|
pid: number;
|
|
81
|
+
/**
|
|
82
|
+
* Resolved actor id (`resolveActor().id`) stamped at task start — who launched
|
|
83
|
+
* this browser task. Optional: tasks persisted before RUSH-2020 carry none.
|
|
84
|
+
*/
|
|
85
|
+
owner?: string;
|
|
81
86
|
/**
|
|
82
87
|
* Per-tab snapshot of the last ref listing captured for that tab
|
|
83
88
|
* (shortId -> {descriptors, opts}). Persisted to tasks.json so a later
|
|
@@ -126,6 +131,8 @@ export interface HistoricalTask {
|
|
|
126
131
|
endedAt: number;
|
|
127
132
|
domains: string[];
|
|
128
133
|
tabCount: number;
|
|
134
|
+
/** Actor id who launched the task (RUSH-2020); absent for pre-RUSH-2020 history. */
|
|
135
|
+
owner?: string;
|
|
129
136
|
}
|
|
130
137
|
export type IPCAction = 'start' | 'record-start' | 'record-stop' | 'done' | 'stop' | 'status' | 'history' | 'navigate' | 'tab-add' | 'tab-focus' | 'tab-close' | 'tab-list' | 'evaluate' | 'screenshot' | 'pdf' | 'refs' | 'click' | 'type' | 'press' | 'hover' | 'scroll' | 'set-viewport' | 'set-device' | 'console' | 'errors' | 'requests' | 'response-body' | 'wait' | 'set-download-path' | 'wait-download' | 'upload' | 'getAppLogs' | 'version';
|
|
131
138
|
export interface IPCRequest {
|
package/dist/lib/capabilities.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* is below `since`, the install path skips the write and surfaces a clear
|
|
8
8
|
* reason instead of silently corrupting an older binary's settings file.
|
|
9
9
|
*/
|
|
10
|
-
import { AGENTS } from './agents.js';
|
|
10
|
+
import { AGENTS, MANAGED_AGENT_IDS } from './agents.js';
|
|
11
11
|
/**
|
|
12
12
|
* Compare semver-like versions ("0.116.0" vs "0.115.9"). Local copy to avoid
|
|
13
13
|
* importing versions.ts (which imports agents.ts, which imports this file).
|
|
@@ -37,6 +37,8 @@ function getCapability(agent, cap) {
|
|
|
37
37
|
* Useful for filtering UI lists; does not check installed version.
|
|
38
38
|
*/
|
|
39
39
|
export function isCapable(agent, cap) {
|
|
40
|
+
if (AGENTS[agent]?.deprecated?.hard)
|
|
41
|
+
return false;
|
|
40
42
|
const c = getCapability(agent, cap);
|
|
41
43
|
return c !== false;
|
|
42
44
|
}
|
|
@@ -47,6 +49,8 @@ export function isCapable(agent, cap) {
|
|
|
47
49
|
* capable" filters but NOT for install-time gating.
|
|
48
50
|
*/
|
|
49
51
|
export function supports(agent, cap, version) {
|
|
52
|
+
if (AGENTS[agent]?.deprecated?.hard)
|
|
53
|
+
return { ok: false, reason: 'unsupported' };
|
|
50
54
|
const c = getCapability(agent, cap);
|
|
51
55
|
if (c === false)
|
|
52
56
|
return { ok: false, reason: 'unsupported' };
|
|
@@ -78,5 +82,5 @@ export function explainSkip(agent, cap, result, version) {
|
|
|
78
82
|
}
|
|
79
83
|
/** All agents whose `capabilities[cap]` is anything other than `false`. */
|
|
80
84
|
export function capableAgents(cap) {
|
|
81
|
-
return
|
|
85
|
+
return MANAGED_AGENT_IDS.filter((id) => isCapable(id, cap));
|
|
82
86
|
}
|
|
@@ -137,7 +137,7 @@ export function buildBootstrapScript(opts) {
|
|
|
137
137
|
// (leaseAndRun, before this script). Here — after ENSURE_AGENTS_CLI installed
|
|
138
138
|
// the CLI — we materialize that config into the runtime home. Gated by
|
|
139
139
|
// copySetup (cleared for --bare). Best-effort; a refresh failure never aborts.
|
|
140
|
-
copySetup ? [step('copy-setup'), 'agents
|
|
140
|
+
copySetup ? [step('copy-setup'), 'agents sync --local -y >/dev/null 2>&1 || true'].join('\n') : '',
|
|
141
141
|
// Marker on its own line: the command layer shows everything before this as
|
|
142
142
|
// setup progress and everything after (the agent's output) verbatim.
|
|
143
143
|
`echo ${q(LEASE_AGENT_MARKER)}`,
|
|
@@ -176,7 +176,7 @@ export async function leaseAndRun(opts) {
|
|
|
176
176
|
// Setup-copy (F1, RUSH-1920): push the git-tracked ~/.agents config onto the
|
|
177
177
|
// box from the host, over crabbox's own per-lease ssh (a raw ssh fails
|
|
178
178
|
// publickey). rsync only here — the box has no agents-cli yet, so the matching
|
|
179
|
-
// `agents
|
|
179
|
+
// `agents sync --local` runs inside the bootstrap script, after the install
|
|
180
180
|
// step. Best-effort: a copy failure never aborts the run (the agent just runs
|
|
181
181
|
// without the config).
|
|
182
182
|
if (opts.copySetup !== false) {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 2. `rsync` that exact file set to `~/.agents` on the box over crabbox's OWN
|
|
13
13
|
* ssh invocation (`crabboxSshArgv`) — crabbox provisions a per-lease identity
|
|
14
14
|
* key, so a raw `ssh crabbox@ip` fails publickey; only crabbox's key works.
|
|
15
|
-
* 3. `agents
|
|
15
|
+
* 3. `agents sync --local` on the box so the copied config takes effect.
|
|
16
16
|
*
|
|
17
17
|
* NEVER copies `~/.claude` / `~/.claude.json` — they live in `$HOME`, not
|
|
18
18
|
* `~/.agents`, and are rebuilt on the box by `agents add` + refresh; an explicit
|
|
@@ -34,7 +34,7 @@ export interface CopySetupOptions {
|
|
|
34
34
|
/** Receives combined stdout/stderr of the rsync + refresh, if set. */
|
|
35
35
|
onData?: (chunk: string) => void;
|
|
36
36
|
/**
|
|
37
|
-
* Run `agents
|
|
37
|
+
* Run `agents sync --local` on the box after the push (default `true`). Set
|
|
38
38
|
* `false` when the caller runs the refresh itself in the box bootstrap — the
|
|
39
39
|
* lease path does this so the refresh runs AFTER the box installs agents-cli
|
|
40
40
|
* (this host-side push happens before the box boots agents-cli, so a host-side
|
|
@@ -47,7 +47,7 @@ export interface CopySetupResult {
|
|
|
47
47
|
files: string[];
|
|
48
48
|
/** rsync exit code, or null when the process failed to spawn. */
|
|
49
49
|
pushExitCode: number | null;
|
|
50
|
-
/** `agents
|
|
50
|
+
/** `agents sync --local` exit code, or null when it was skipped/failed to spawn. */
|
|
51
51
|
refreshExitCode: number | null;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
@@ -80,7 +80,7 @@ export declare function buildSetupRsyncArgs(opts: {
|
|
|
80
80
|
/**
|
|
81
81
|
* Replicate the git-tracked subset of the local `~/.agents` onto the crabbox box
|
|
82
82
|
* and refresh it. Enumerates tracked files, rsyncs them over ssh, then runs
|
|
83
|
-
* `agents
|
|
83
|
+
* `agents sync --local` on the box. Refresh is skipped when the push fails or the
|
|
84
84
|
* file set is empty (nothing to refresh). Never throws — surfaces failure through
|
|
85
85
|
* the returned exit codes.
|
|
86
86
|
*/
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 2. `rsync` that exact file set to `~/.agents` on the box over crabbox's OWN
|
|
13
13
|
* ssh invocation (`crabboxSshArgv`) — crabbox provisions a per-lease identity
|
|
14
14
|
* key, so a raw `ssh crabbox@ip` fails publickey; only crabbox's key works.
|
|
15
|
-
* 3. `agents
|
|
15
|
+
* 3. `agents sync --local` on the box so the copied config takes effect.
|
|
16
16
|
*
|
|
17
17
|
* NEVER copies `~/.claude` / `~/.claude.json` — they live in `$HOME`, not
|
|
18
18
|
* `~/.agents`, and are rebuilt on the box by `agents add` + refresh; an explicit
|
|
@@ -88,7 +88,7 @@ function runStreaming(cmd, args, env, onData) {
|
|
|
88
88
|
/**
|
|
89
89
|
* Replicate the git-tracked subset of the local `~/.agents` onto the crabbox box
|
|
90
90
|
* and refresh it. Enumerates tracked files, rsyncs them over ssh, then runs
|
|
91
|
-
* `agents
|
|
91
|
+
* `agents sync --local` on the box. Refresh is skipped when the push fails or the
|
|
92
92
|
* file set is empty (nothing to refresh). Never throws — surfaces failure through
|
|
93
93
|
* the returned exit codes.
|
|
94
94
|
*/
|
|
@@ -115,8 +115,8 @@ export async function copySetupToBox(opts) {
|
|
|
115
115
|
const pushExitCode = await runStreaming('rsync', rsyncArgs, env, opts.onData);
|
|
116
116
|
let refreshExitCode = null;
|
|
117
117
|
if (pushExitCode === 0 && opts.refresh !== false) {
|
|
118
|
-
// ssh <opts> crabbox@host bash -lc 'agents
|
|
119
|
-
const refreshArgs = [...sshArgv.slice(1), 'bash', '-lc', 'agents
|
|
118
|
+
// ssh <opts> crabbox@host bash -lc 'agents sync --local -y'
|
|
119
|
+
const refreshArgs = [...sshArgv.slice(1), 'bash', '-lc', 'agents sync --local -y'];
|
|
120
120
|
refreshExitCode = await runStreaming('ssh', refreshArgs, env, opts.onData);
|
|
121
121
|
}
|
|
122
122
|
return { files, pushExitCode, refreshExitCode };
|
package/dist/lib/daemon.d.ts
CHANGED
|
@@ -68,6 +68,24 @@ export declare function reapStrayDaemons(keepPid?: number): {
|
|
|
68
68
|
/** Append a JSONL log entry to the daemon log file (owner-only permissions). */
|
|
69
69
|
export declare function log(level: string, message: string): void;
|
|
70
70
|
/** Main daemon loop: load jobs, schedule crons, monitor runs, and handle signals. */
|
|
71
|
+
/**
|
|
72
|
+
* Anchor the daemon's working directory to a stable, always-present path.
|
|
73
|
+
*
|
|
74
|
+
* The daemon is long-lived and inherits whatever cwd it was launched from — often
|
|
75
|
+
* a git worktree (e.g. a `.agents/worktrees/<slug>/` a session happened to be in).
|
|
76
|
+
* When that directory is later removed (`git worktree remove`, `rm -rf`), the
|
|
77
|
+
* daemon keeps the deleted inode as its cwd — a process cannot chdir out of a
|
|
78
|
+
* deleted directory on its own — and every job it spawns inherits the dead cwd
|
|
79
|
+
* (`spawnJobAttempt` and command runs pass no explicit `cwd`, so the child uses
|
|
80
|
+
* the parent's). Bun then fails `getcwd()` during startup and every routine crashes
|
|
81
|
+
* at 0 seconds with `ENOENT: Bun could not find a file` before the agent even runs.
|
|
82
|
+
*
|
|
83
|
+
* Re-anchoring to the home directory once, at daemon startup, makes the daemon
|
|
84
|
+
* immune regardless of how it was launched (systemd unit, launchd, or a manual
|
|
85
|
+
* `agents __daemon-run` from any directory). Returns the resolved cwd, or null if
|
|
86
|
+
* anchoring failed (logged, non-fatal).
|
|
87
|
+
*/
|
|
88
|
+
export declare function anchorDaemonCwd(): string | null;
|
|
71
89
|
export declare function runDaemon(): Promise<void>;
|
|
72
90
|
/**
|
|
73
91
|
* Write a launchd plist or systemd unit with owner-only permissions atomically.
|
package/dist/lib/daemon.js
CHANGED
|
@@ -11,7 +11,7 @@ import * as fs from 'fs';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import * as os from 'os';
|
|
13
13
|
import { getDaemonDir as getDaemonDirRoot } from './state.js';
|
|
14
|
-
import { isAlive, killTree, backgroundSpawnOptions } from './platform/index.js';
|
|
14
|
+
import { isAlive, killTree, backgroundSpawnOptions, waitForExit } from './platform/index.js';
|
|
15
15
|
import { listJobs as listAllJobs } from './routines.js';
|
|
16
16
|
import { syncAllProjectRoutines } from './routines-project.js';
|
|
17
17
|
import { JobScheduler } from './scheduler.js';
|
|
@@ -171,6 +171,10 @@ export function isDaemonWedged() {
|
|
|
171
171
|
const elapsed = Date.now() - Date.parse(hb.lastTick);
|
|
172
172
|
return elapsed > WEDGE_THRESHOLD_TICKS * MONITOR_TICK_MS;
|
|
173
173
|
}
|
|
174
|
+
/** How long stopDaemon waits for a SIGTERMed daemon to exit before escalating. */
|
|
175
|
+
const STOP_GRACE_MS = 5000;
|
|
176
|
+
/** How long it waits after the hard tree-kill before giving up. */
|
|
177
|
+
const STOP_KILL_GRACE_MS = 2000;
|
|
174
178
|
/** Check if the daemon process is alive by sending signal 0 to the stored PID. */
|
|
175
179
|
export function isDaemonRunning() {
|
|
176
180
|
const pid = readDaemonPid();
|
|
@@ -289,6 +293,34 @@ export function log(level, message) {
|
|
|
289
293
|
catch { /* best effort */ }
|
|
290
294
|
}
|
|
291
295
|
/** Main daemon loop: load jobs, schedule crons, monitor runs, and handle signals. */
|
|
296
|
+
/**
|
|
297
|
+
* Anchor the daemon's working directory to a stable, always-present path.
|
|
298
|
+
*
|
|
299
|
+
* The daemon is long-lived and inherits whatever cwd it was launched from — often
|
|
300
|
+
* a git worktree (e.g. a `.agents/worktrees/<slug>/` a session happened to be in).
|
|
301
|
+
* When that directory is later removed (`git worktree remove`, `rm -rf`), the
|
|
302
|
+
* daemon keeps the deleted inode as its cwd — a process cannot chdir out of a
|
|
303
|
+
* deleted directory on its own — and every job it spawns inherits the dead cwd
|
|
304
|
+
* (`spawnJobAttempt` and command runs pass no explicit `cwd`, so the child uses
|
|
305
|
+
* the parent's). Bun then fails `getcwd()` during startup and every routine crashes
|
|
306
|
+
* at 0 seconds with `ENOENT: Bun could not find a file` before the agent even runs.
|
|
307
|
+
*
|
|
308
|
+
* Re-anchoring to the home directory once, at daemon startup, makes the daemon
|
|
309
|
+
* immune regardless of how it was launched (systemd unit, launchd, or a manual
|
|
310
|
+
* `agents __daemon-run` from any directory). Returns the resolved cwd, or null if
|
|
311
|
+
* anchoring failed (logged, non-fatal).
|
|
312
|
+
*/
|
|
313
|
+
export function anchorDaemonCwd() {
|
|
314
|
+
const home = os.homedir();
|
|
315
|
+
try {
|
|
316
|
+
process.chdir(home);
|
|
317
|
+
return home;
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
log('WARN', `Could not anchor daemon cwd to ${home}: ${err.message}`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
292
324
|
export async function runDaemon() {
|
|
293
325
|
// Single-instance guard: a direct `agents __daemon-run` (manual, or a
|
|
294
326
|
// service-manager restart racing a live predecessor) must not clobber a
|
|
@@ -301,6 +333,7 @@ export async function runDaemon() {
|
|
|
301
333
|
process.exit(0);
|
|
302
334
|
}
|
|
303
335
|
log('INFO', `Daemon started (PID: ${process.pid})`);
|
|
336
|
+
anchorDaemonCwd();
|
|
304
337
|
// The daemon holds NO Claude credential of its own. Routine runs authenticate
|
|
305
338
|
// exactly like an interactive `agents run`: through the per-account
|
|
306
339
|
// CLAUDE_CONFIG_DIR login on this device (its own auto-refreshing
|
|
@@ -1161,11 +1194,19 @@ export function stopDaemon() {
|
|
|
1161
1194
|
process.kill(pid, 'SIGTERM');
|
|
1162
1195
|
}
|
|
1163
1196
|
catch { /* process already exited */ }
|
|
1164
|
-
//
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1197
|
+
// Wait for it to actually go. This used to be a setTimeout escalation plus
|
|
1198
|
+
// an immediate removeDaemonPid(), which had two failure modes: in a
|
|
1199
|
+
// short-lived process (the npm postinstall) the timer never fired at all,
|
|
1200
|
+
// and clearing the pid file while the old daemon still ran made
|
|
1201
|
+
// isDaemonRunning() report false, so startDaemon() launched a SECOND
|
|
1202
|
+
// daemon. Its hosted broker then unlinked the live socket and rebound,
|
|
1203
|
+
// orphaning the first broker with every unlocked bundle still in its RAM
|
|
1204
|
+
// and unreachable — two brokers on one socket path, seen on a real machine
|
|
1205
|
+
// after an install into a second prefix.
|
|
1206
|
+
if (!waitForExit(pid, STOP_GRACE_MS)) {
|
|
1207
|
+
killTree(pid);
|
|
1208
|
+
waitForExit(pid, STOP_KILL_GRACE_MS);
|
|
1209
|
+
}
|
|
1169
1210
|
}
|
|
1170
1211
|
}
|
|
1171
1212
|
removeDaemonPid();
|
|
@@ -48,7 +48,7 @@ export interface FanOutDeviceResult<T> {
|
|
|
48
48
|
export declare function planFleetTargets(reg: DeviceRegistry): FleetTarget[];
|
|
49
49
|
/**
|
|
50
50
|
* Remote fan-out targets for the fleet health/drift gates (`fleet status`,
|
|
51
|
-
* `check --devices`): every planned device except this machine and control-only
|
|
51
|
+
* `doctor --check --devices`): every planned device except this machine and control-only
|
|
52
52
|
* cockpits. A control device never runs agents (mirrors doctor's fan-out, which
|
|
53
53
|
* drops it via `isControlDevice`), so counting it as unreachable/drift would make
|
|
54
54
|
* the CI gate fail on every run for a fleet that merely has a cockpit registered.
|
|
@@ -44,7 +44,7 @@ export function planFleetTargets(reg) {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Remote fan-out targets for the fleet health/drift gates (`fleet status`,
|
|
47
|
-
* `check --devices`): every planned device except this machine and control-only
|
|
47
|
+
* `doctor --check --devices`): every planned device except this machine and control-only
|
|
48
48
|
* cockpits. A control device never runs agents (mirrors doctor's fan-out, which
|
|
49
49
|
* drops it via `isControlDevice`), so counting it as unreachable/drift would make
|
|
50
50
|
* the CI gate fail on every run for a fleet that merely has a cockpit registered.
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { AgentId } from './types.js';
|
|
23
23
|
import type { DiscoveredPlugin } from './types.js';
|
|
24
|
+
import { type HookWiringReport } from './hooks.js';
|
|
24
25
|
export type DoctorKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'permissions' | 'subagents' | 'plugins' | 'promptcuts';
|
|
25
26
|
export type DiffStatus = 'ok' | 'diff' | 'missing' | 'extra';
|
|
26
27
|
export type SourceLayer = 'project' | 'user' | 'system' | 'extra';
|
|
@@ -37,6 +38,19 @@ export interface ResourceDiff {
|
|
|
37
38
|
* "0.6.1→0.7.0, missing skills: ship, learn". Currently set for plugins. */
|
|
38
39
|
detail?: string;
|
|
39
40
|
}
|
|
41
|
+
/** A source layer (user / system / an extra repo) that is behind its upstream —
|
|
42
|
+
* reconciled against stale truth. Attached to the report by the doctor command
|
|
43
|
+
* (needs a git probe, kept out of the pure per-version diff). */
|
|
44
|
+
export interface SourceLayerBehind {
|
|
45
|
+
layer: 'user' | 'system' | 'extra';
|
|
46
|
+
/** Human label for the layer's on-disk root (e.g. `~/.agents`). */
|
|
47
|
+
label: string;
|
|
48
|
+
/** `agents repo pull` alias for the remediation hint. */
|
|
49
|
+
alias: string;
|
|
50
|
+
behind: number;
|
|
51
|
+
/** Upstream ref the layer trails (e.g. `origin/main`). */
|
|
52
|
+
branch: string;
|
|
53
|
+
}
|
|
40
54
|
export interface VersionResourceReport {
|
|
41
55
|
agent: AgentId;
|
|
42
56
|
version: string;
|
|
@@ -58,6 +72,12 @@ export interface VersionResourceReport {
|
|
|
58
72
|
missing: number;
|
|
59
73
|
extra: number;
|
|
60
74
|
};
|
|
75
|
+
/** Wiring inspection for the version's native settings.json (claude/droid).
|
|
76
|
+
* Populated only when the `hooks` kind is in scope. */
|
|
77
|
+
hookWiring?: HookWiringReport;
|
|
78
|
+
/** Source layers behind their upstream. Filled by the doctor command (a git
|
|
79
|
+
* probe), not the pure diff — undefined when uncomputed. */
|
|
80
|
+
sourceBehind?: SourceLayerBehind[];
|
|
61
81
|
}
|
|
62
82
|
/**
|
|
63
83
|
* Describe how a version's marketplace MIRROR of a plugin diverges from its
|
package/dist/lib/doctor-diff.js
CHANGED
|
@@ -33,7 +33,7 @@ import { shouldInstallCommandAsSkill, commandSkillMatches, commandSkillName } fr
|
|
|
33
33
|
import { gooseCommandMatches, gooseCommandsDir } from './goose-commands.js';
|
|
34
34
|
import { supports } from './capabilities.js';
|
|
35
35
|
import { listSkillsInVersionHome, getVersionSkillsDir } from './skills.js';
|
|
36
|
-
import { listHooksInVersionHome, listHookEntriesFromDir } from './hooks.js';
|
|
36
|
+
import { listHooksInVersionHome, listHookEntriesFromDir, checkVersionHookWiring } from './hooks.js';
|
|
37
37
|
const RULES_DOC_FILENAME = 'README.md';
|
|
38
38
|
const ALL_KINDS = [
|
|
39
39
|
'commands',
|
|
@@ -611,6 +611,10 @@ export function diffVersionResources(agent, version, options = {}) {
|
|
|
611
611
|
empty.skills = diffSkills(agent, version, cwd, excludeProject);
|
|
612
612
|
if (requested.has('hooks'))
|
|
613
613
|
empty.hooks = diffHooks(agent, version, cwd);
|
|
614
|
+
// Wiring check: a hook FILE can be present and byte-identical to source (ok
|
|
615
|
+
// above) yet never referenced in settings.json, so it never fires. Only
|
|
616
|
+
// meaningful when hooks are in scope.
|
|
617
|
+
const hookWiring = requested.has('hooks') ? checkVersionHookWiring(agent, version) : undefined;
|
|
614
618
|
if (requested.has('rules'))
|
|
615
619
|
empty.rules = diffRules(agent, version, cwd, excludeProject);
|
|
616
620
|
if (requested.has('mcp'))
|
|
@@ -649,6 +653,7 @@ export function diffVersionResources(agent, version, options = {}) {
|
|
|
649
653
|
},
|
|
650
654
|
kinds: empty,
|
|
651
655
|
summary: { ok, diff, missing, extra },
|
|
656
|
+
...(hookWiring ? { hookWiring } : {}),
|
|
652
657
|
};
|
|
653
658
|
}
|
|
654
659
|
export const DOCTOR_ALL_KINDS = ALL_KINDS;
|