@phnx-labs/agents-cli 1.20.72 → 1.20.74
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 +395 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +21 -5
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/import.js +51 -3
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +9 -2
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/versions.js +36 -5
- package/dist/commands/view.js +75 -27
- package/dist/index.js +80 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.d.ts +35 -0
- package/dist/lib/agents.js +79 -6
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +56 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/import.d.ts +23 -0
- package/dist/lib/import.js +74 -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.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +306 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +35 -0
- package/dist/lib/shims.js +159 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +7 -1
- package/dist/lib/startup/command-registry.js +19 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +269 -78
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
package/dist/lib/versions.js
CHANGED
|
@@ -24,6 +24,7 @@ import { checkbox, select } from '@inquirer/prompts';
|
|
|
24
24
|
import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getSkillsDir, getResolvedRulesDir, getUserRulesDir, getVersionResources, ensureVersionResourcePatterns, getProjectAgentsDir, getPromptcutsPath, getUserPromptcutsPath, getEnabledExtraRepos, getAgentsDir, getUserAgentsDir, getTrashVersionsDir, getActiveRulesPreset, getHomeDir } from './state.js';
|
|
25
25
|
import { defaultPatterns, expandPatterns } from './resource-patterns.js';
|
|
26
26
|
import { listResources } from './resources.js';
|
|
27
|
+
import { activeRulesPreset, filterNamesForActiveResourceProfile } from './resource-profiles.js';
|
|
27
28
|
// VERSION_RE + compareVersions are owned by the agent-spec engine primitives
|
|
28
29
|
// (single source of truth). Re-exported below so existing importers of
|
|
29
30
|
// `compareVersions` from './versions.js' keep working.
|
|
@@ -31,37 +32,47 @@ import { VERSION_RE, compareVersions } from './agent-spec/primitives.js';
|
|
|
31
32
|
import { AGENTS, agentConfigDirName, getAccountEmail, resolveAgentName, formatAgentError, findInPath, isSelfUpdatingAgent } from './agents.js';
|
|
32
33
|
import { discoverPermissionGroups, getActivePermissionPresetName, readPermissionPresetRecipe, PERMISSION_PRESET_ENV_VAR } from './permissions.js';
|
|
33
34
|
import { parseMcpServerConfig, isProjectMcpTrusted } from './mcp.js';
|
|
34
|
-
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
|
|
35
|
+
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink, assertIsolationBoundary, } from './shims.js';
|
|
35
36
|
import { importInstallScriptBinary } from './import.js';
|
|
36
37
|
import { IS_WINDOWS, composeWin32CommandLine } from './platform/index.js';
|
|
38
|
+
import { listInstalledSubagents } from './subagents.js';
|
|
39
|
+
import { listInstalledWorkflows } from './workflows.js';
|
|
37
40
|
import { pruneVersionHomeHookEntriesFromSettings } from './hooks.js';
|
|
38
41
|
import { supports, explainSkip } from './capabilities.js';
|
|
39
|
-
import { discoverPlugins } from './plugins.js';
|
|
42
|
+
import { discoverPlugins, marketplaceSpecForName } from './plugins.js';
|
|
40
43
|
import { loadManifest, saveManifest, buildManifest as buildSyncManifest, isStale } from './staleness/index.js';
|
|
41
44
|
import { emit } from './events.js';
|
|
42
45
|
import { safeJoin } from './paths.js';
|
|
43
46
|
import { readSkillSourceCommandMarker, shouldInstallCommandAsSkill } from './command-skills.js';
|
|
44
47
|
import { getWriter, getDetector } from './staleness/registry.js';
|
|
45
48
|
import { syncMemoryToVersionHome } from './memory.js';
|
|
49
|
+
import { listPluginSkillNames, resolveCommandSource, resolveSkillSource } from './staleness/writers/sources.js';
|
|
50
|
+
import { syncProjectResourcesToAgent } from './project-resources.js';
|
|
46
51
|
/** Promisified exec for running shell commands. */
|
|
47
52
|
const execAsync = promisify(exec);
|
|
48
53
|
const execFileAsync = promisify(execFile);
|
|
49
54
|
const RULES_DOC_FILENAME = 'README.md';
|
|
50
|
-
function
|
|
55
|
+
function getLayeredResourceBases(cwd) {
|
|
51
56
|
const projectAgentsDir = getProjectAgentsDir(cwd);
|
|
52
57
|
const userBase = getUserAgentsDir();
|
|
53
58
|
const systemBase = getAgentsDir();
|
|
54
59
|
const resourceBases = [];
|
|
55
60
|
if (projectAgentsDir) {
|
|
56
|
-
resourceBases.push({
|
|
61
|
+
resourceBases.push({ source: 'project', base: projectAgentsDir });
|
|
57
62
|
}
|
|
58
|
-
resourceBases.push({
|
|
59
|
-
resourceBases.push({
|
|
63
|
+
resourceBases.push({ source: 'user', base: userBase });
|
|
64
|
+
resourceBases.push({ source: 'system', base: systemBase });
|
|
60
65
|
for (const extra of getEnabledExtraRepos()) {
|
|
61
|
-
resourceBases.push({
|
|
66
|
+
resourceBases.push({ source: extra.alias, base: extra.dir });
|
|
62
67
|
}
|
|
63
68
|
return resourceBases;
|
|
64
69
|
}
|
|
70
|
+
function getResourceBases(cwd) {
|
|
71
|
+
return getLayeredResourceBases(cwd).map(({ base, source }) => ({
|
|
72
|
+
base,
|
|
73
|
+
scope: source === 'project' ? 'project' : 'user',
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
65
76
|
function getScopedMcpResources(cwd) {
|
|
66
77
|
const resources = new Map();
|
|
67
78
|
for (const { base, scope } of getResourceBases(cwd)) {
|
|
@@ -79,6 +90,55 @@ function getScopedMcpResources(cwd) {
|
|
|
79
90
|
}
|
|
80
91
|
return Array.from(resources.values());
|
|
81
92
|
}
|
|
93
|
+
function sourceMapFromResources(kind, cwd) {
|
|
94
|
+
return new Map(listResources(kind, cwd).map(r => [r.name, r.source]));
|
|
95
|
+
}
|
|
96
|
+
function sourceMapFromLayeredDirectory(cwd, relativePath, listNames) {
|
|
97
|
+
const resources = new Map();
|
|
98
|
+
for (const { base, source } of getLayeredResourceBases(cwd)) {
|
|
99
|
+
const dir = path.join(base, ...relativePath);
|
|
100
|
+
if (!fs.existsSync(dir))
|
|
101
|
+
continue;
|
|
102
|
+
for (const name of listNames(dir)) {
|
|
103
|
+
if (!resources.has(name))
|
|
104
|
+
resources.set(name, source);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return resources;
|
|
108
|
+
}
|
|
109
|
+
function sourceMapFromPermissionGroups(cwd) {
|
|
110
|
+
return sourceMapFromLayeredDirectory(cwd, ['permissions', 'groups'], (dir) => fs.readdirSync(dir)
|
|
111
|
+
.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))
|
|
112
|
+
.map(f => f.replace(/\.(yaml|yml)$/, '')));
|
|
113
|
+
}
|
|
114
|
+
function sourceMapFromWorkflows(cwd) {
|
|
115
|
+
return sourceMapFromLayeredDirectory(cwd, ['workflows'], (dir) => fs.readdirSync(dir, { withFileTypes: true })
|
|
116
|
+
.filter(d => d.isDirectory() && fs.existsSync(path.join(dir, d.name, 'WORKFLOW.md')))
|
|
117
|
+
.map(d => d.name));
|
|
118
|
+
}
|
|
119
|
+
function sourceMapFromPluginSkills(plugins, activePluginNames, cwd) {
|
|
120
|
+
const sourceRank = new Map([
|
|
121
|
+
['user', 0],
|
|
122
|
+
['system', 1],
|
|
123
|
+
['project', 3],
|
|
124
|
+
]);
|
|
125
|
+
const entries = plugins
|
|
126
|
+
.filter(plugin => activePluginNames.has(plugin.name))
|
|
127
|
+
.map(plugin => {
|
|
128
|
+
const spec = marketplaceSpecForName(plugin.marketplace, cwd);
|
|
129
|
+
const source = spec.kind === 'extra' ? spec.alias : spec.kind;
|
|
130
|
+
return { plugin, source, rank: sourceRank.get(source) ?? 2 };
|
|
131
|
+
})
|
|
132
|
+
.sort((a, b) => a.rank - b.rank);
|
|
133
|
+
const sources = new Map();
|
|
134
|
+
for (const { plugin, source } of entries) {
|
|
135
|
+
for (const skill of plugin.skills) {
|
|
136
|
+
if (!sources.has(skill))
|
|
137
|
+
sources.set(skill, source);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return sources;
|
|
141
|
+
}
|
|
82
142
|
/**
|
|
83
143
|
* Get all available resources from ~/.agents/.
|
|
84
144
|
*/
|
|
@@ -110,7 +170,7 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
110
170
|
commandNames.add(name);
|
|
111
171
|
}
|
|
112
172
|
}
|
|
113
|
-
result.commands = Array.from(commandNames);
|
|
173
|
+
result.commands = filterNamesForActiveResourceProfile('commands', Array.from(commandNames), sourceMapFromResources('commands', cwd));
|
|
114
174
|
// Skills (directories, excluding hidden)
|
|
115
175
|
const skillNames = new Set();
|
|
116
176
|
for (const { base } of resourceBases) {
|
|
@@ -124,13 +184,13 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
124
184
|
skillNames.add(name);
|
|
125
185
|
}
|
|
126
186
|
}
|
|
127
|
-
result.skills = Array.from(skillNames);
|
|
128
|
-
// Hooks
|
|
129
|
-
// executable bit on a file with a non-data extension.
|
|
130
|
-
// like `README.md` (docs) or `promptcuts.yaml` (data read
|
|
131
|
-
// expand-promptcuts script) lives in hooks/ but is not a
|
|
132
|
-
// runs chmod 0o755'd everything they copied, so an exec bit
|
|
133
|
-
// longer be trusted as the signal.
|
|
187
|
+
result.skills = filterNamesForActiveResourceProfile('skills', Array.from(skillNames), sourceMapFromResources('skills', cwd));
|
|
188
|
+
// Hooks. A hook is either a directory bundle or an actual script file: known
|
|
189
|
+
// script extension, OR executable bit on a file with a non-data extension.
|
|
190
|
+
// Auxiliary content like `README.md` (docs) or `promptcuts.yaml` (data read
|
|
191
|
+
// directly by the expand-promptcuts script) lives in hooks/ but is not a
|
|
192
|
+
// hook. Older sync runs chmod 0o755'd everything they copied, so an exec bit
|
|
193
|
+
// alone can no longer be trusted as the signal.
|
|
134
194
|
const NON_SCRIPT_EXTS = new Set(['.md', '.markdown', '.rst', '.txt', '.yaml', '.yml', '.json', '.toml', '.ini', '.conf']);
|
|
135
195
|
const SCRIPT_EXTS = new Set(['.sh', '.bash', '.zsh', '.py', '.js', '.ts', '.mjs', '.cjs', '.rb', '.pl', '.ps1']);
|
|
136
196
|
const hookNames = new Set();
|
|
@@ -142,7 +202,13 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
142
202
|
if (name.startsWith('.'))
|
|
143
203
|
continue;
|
|
144
204
|
try {
|
|
145
|
-
const stat = fs.
|
|
205
|
+
const stat = fs.lstatSync(path.join(hooksDir, name));
|
|
206
|
+
if (stat.isSymbolicLink())
|
|
207
|
+
continue;
|
|
208
|
+
if (stat.isDirectory()) {
|
|
209
|
+
hookNames.add(name);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
146
212
|
if (!stat.isFile())
|
|
147
213
|
continue;
|
|
148
214
|
const ext = path.extname(name).toLowerCase();
|
|
@@ -156,7 +222,7 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
156
222
|
catch { /* ignore unreadable */ }
|
|
157
223
|
}
|
|
158
224
|
}
|
|
159
|
-
result.hooks = Array.from(hookNames);
|
|
225
|
+
result.hooks = filterNamesForActiveResourceProfile('hooks', Array.from(hookNames), sourceMapFromResources('hooks', cwd));
|
|
160
226
|
// Rules — list available presets across layers (project > user > extras > system).
|
|
161
227
|
// The composer selects exactly one preset per sync; this list drives the
|
|
162
228
|
// resource-count display and `agents rules switch` picker. Routes through
|
|
@@ -184,22 +250,12 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
184
250
|
// malformed rules.yaml — skip silently; the composer will surface the error.
|
|
185
251
|
}
|
|
186
252
|
}
|
|
187
|
-
result.memory = Array.from(presetNames);
|
|
188
|
-
|
|
253
|
+
result.memory = filterNamesForActiveResourceProfile('memory', Array.from(presetNames));
|
|
254
|
+
const scopedMcp = getScopedMcpResources(cwd);
|
|
255
|
+
result.mcp = filterNamesForActiveResourceProfile('mcp', scopedMcp.map(resource => resource.name), new Map(scopedMcp.map(resource => [resource.name, resource.scope])));
|
|
189
256
|
// Permission groups (from permissions/groups/*.yaml)
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
const permsGroupsDir = path.join(base, 'permissions', 'groups');
|
|
193
|
-
if (!fs.existsSync(permsGroupsDir))
|
|
194
|
-
continue;
|
|
195
|
-
const names = fs.readdirSync(permsGroupsDir)
|
|
196
|
-
.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))
|
|
197
|
-
.map(f => f.replace(/\.(yaml|yml)$/, ''));
|
|
198
|
-
for (const name of names) {
|
|
199
|
-
permissionNames.add(name);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
result.permissions = Array.from(permissionNames);
|
|
257
|
+
const permissionSources = sourceMapFromPermissionGroups(cwd);
|
|
258
|
+
result.permissions = filterNamesForActiveResourceProfile('permissions', Array.from(permissionSources.keys()), permissionSources);
|
|
203
259
|
// Subagents (directories with AGENT.md)
|
|
204
260
|
const subagentNames = new Set();
|
|
205
261
|
for (const { base } of resourceBases) {
|
|
@@ -213,24 +269,19 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
213
269
|
subagentNames.add(name);
|
|
214
270
|
}
|
|
215
271
|
}
|
|
216
|
-
result.subagents = Array.from(subagentNames);
|
|
272
|
+
result.subagents = filterNamesForActiveResourceProfile('subagents', Array.from(subagentNames), sourceMapFromResources('subagents', cwd));
|
|
217
273
|
// Workflows (directories with WORKFLOW.md)
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
const workflowsDir = path.join(base, 'workflows');
|
|
221
|
-
if (!fs.existsSync(workflowsDir))
|
|
222
|
-
continue;
|
|
223
|
-
const names = fs.readdirSync(workflowsDir, { withFileTypes: true })
|
|
224
|
-
.filter(d => d.isDirectory() && fs.existsSync(path.join(workflowsDir, d.name, 'WORKFLOW.md')))
|
|
225
|
-
.map(d => d.name);
|
|
226
|
-
for (const name of names) {
|
|
227
|
-
workflowNames.add(name);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
result.workflows = Array.from(workflowNames);
|
|
274
|
+
const workflowSources = sourceMapFromWorkflows(cwd);
|
|
275
|
+
result.workflows = filterNamesForActiveResourceProfile('workflows', Array.from(workflowSources.keys()), workflowSources);
|
|
231
276
|
// Plugins (directories with .claude-plugin/plugin.json)
|
|
232
277
|
const allPlugins = discoverPlugins();
|
|
233
|
-
result.plugins = allPlugins.map(p => p.name);
|
|
278
|
+
result.plugins = filterNamesForActiveResourceProfile('plugins', allPlugins.map(p => p.name), new Map(allPlugins.map(p => [p.name, 'user'])));
|
|
279
|
+
const activePlugins = new Set(result.plugins);
|
|
280
|
+
const pluginSkillNames = filterNamesForActiveResourceProfile('skills', listPluginSkillNames({ plugins: activePlugins }), sourceMapFromPluginSkills(allPlugins, activePlugins, cwd));
|
|
281
|
+
for (const name of pluginSkillNames) {
|
|
282
|
+
if (!skillNames.has(name))
|
|
283
|
+
result.skills.push(name);
|
|
284
|
+
}
|
|
234
285
|
// Promptcuts — present if either layer exists. Reads merge user + system
|
|
235
286
|
// with user precedence (see readMergedPromptcuts); writes always go to user.
|
|
236
287
|
result.promptcuts = fs.existsSync(getUserPromptcutsPath()) || fs.existsSync(getPromptcutsPath());
|
|
@@ -1135,6 +1186,14 @@ export function getGlobalDefault(agent) {
|
|
|
1135
1186
|
* Set the global default version for an agent.
|
|
1136
1187
|
*/
|
|
1137
1188
|
export function setGlobalDefault(agent, version) {
|
|
1189
|
+
// A global default is what owns the launcher and arms the self-heal `shadowing`
|
|
1190
|
+
// check, so recording one for an isolated-only agent is the root of the original
|
|
1191
|
+
// breach. Clearing is always allowed — `removeVersion` legitimately clears a
|
|
1192
|
+
// default as the last non-isolated version goes away, which is the very moment
|
|
1193
|
+
// an agent BECOMES isolated-only.
|
|
1194
|
+
if (version !== undefined) {
|
|
1195
|
+
assertIsolationBoundary(agent, 'set a global default');
|
|
1196
|
+
}
|
|
1138
1197
|
const meta = readMeta();
|
|
1139
1198
|
if (!meta.agents) {
|
|
1140
1199
|
meta.agents = {};
|
|
@@ -1148,6 +1207,34 @@ export function setGlobalDefault(agent, version) {
|
|
|
1148
1207
|
}
|
|
1149
1208
|
writeMeta(meta);
|
|
1150
1209
|
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Get the preferred ISOLATED version for an agent — the copy a bare
|
|
1212
|
+
* `agents run <agent>` falls back to when there is no global default.
|
|
1213
|
+
*/
|
|
1214
|
+
export function getIsolatedDefault(agent) {
|
|
1215
|
+
const meta = readMeta();
|
|
1216
|
+
return meta.isolatedAgents?.[agent] || null;
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Set (or clear, with `undefined`) the preferred isolated version.
|
|
1220
|
+
*
|
|
1221
|
+
* Deliberately does NOT touch the launcher, the bare shim, the `~/.<agent>` config
|
|
1222
|
+
* symlink or the global default — the five things `setDefaultVersion` does. This is
|
|
1223
|
+
* a pointer inside the sandbox, so it stays inside the sandbox.
|
|
1224
|
+
*/
|
|
1225
|
+
export function setIsolatedDefault(agent, version) {
|
|
1226
|
+
const meta = readMeta();
|
|
1227
|
+
if (!meta.isolatedAgents) {
|
|
1228
|
+
meta.isolatedAgents = {};
|
|
1229
|
+
}
|
|
1230
|
+
if (version === undefined) {
|
|
1231
|
+
delete meta.isolatedAgents[agent];
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
meta.isolatedAgents[agent] = version;
|
|
1235
|
+
}
|
|
1236
|
+
writeMeta(meta);
|
|
1237
|
+
}
|
|
1151
1238
|
/**
|
|
1152
1239
|
* Path to the sentinel file that marks a version as an isolated install.
|
|
1153
1240
|
*
|
|
@@ -1490,16 +1577,23 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1490
1577
|
return { success: false, installedVersion: version, error: err.message };
|
|
1491
1578
|
}
|
|
1492
1579
|
}
|
|
1580
|
+
// Version-dir entries that are STATE, not install output, and so must survive a
|
|
1581
|
+
// clean reinstall: `home/`, and the `.isolated` marker that is the single source
|
|
1582
|
+
// of truth for "this copy is walled off". Losing the marker would silently demote
|
|
1583
|
+
// an isolated copy to a normal one on its first repair — after which shim
|
|
1584
|
+
// self-heal would hand it a bare `<agent>` shim and a PATH entry.
|
|
1585
|
+
const PRESERVED_ON_CLEAN_REINSTALL = new Set(['home', '.isolated']);
|
|
1493
1586
|
/**
|
|
1494
1587
|
* Remove install artifacts from a version directory, preserving `home/` which
|
|
1495
1588
|
* contains the user's conversation history, sessions, history.jsonl, tasks,
|
|
1496
|
-
* todos, file-history, etc.
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1589
|
+
* todos, file-history, etc. (and the `.isolated` marker — see
|
|
1590
|
+
* PRESERVED_ON_CLEAN_REINSTALL). Used by the install pipeline (NOT by
|
|
1591
|
+
* removeVersion) to clean up staging artifacts when a fresh install collides
|
|
1592
|
+
* with an existing dir. removeVersion uses soft-delete instead.
|
|
1499
1593
|
*/
|
|
1500
1594
|
function removeInstallArtifacts(versionDir) {
|
|
1501
1595
|
for (const entry of fs.readdirSync(versionDir)) {
|
|
1502
|
-
if (entry
|
|
1596
|
+
if (PRESERVED_ON_CLEAN_REINSTALL.has(entry))
|
|
1503
1597
|
continue;
|
|
1504
1598
|
fs.rmSync(path.join(versionDir, entry), { recursive: true, force: true });
|
|
1505
1599
|
}
|
|
@@ -1611,9 +1705,12 @@ async function reconcileGlobalBinaryVersions(agent) {
|
|
|
1611
1705
|
}
|
|
1612
1706
|
if (foldedAny) {
|
|
1613
1707
|
invalidateInstalledVersionsCache(agent);
|
|
1614
|
-
// Keep the recorded default pointing at a dir that still exists on disk
|
|
1708
|
+
// Keep the recorded default pointing at a dir that still exists on disk —
|
|
1709
|
+
// but never promote an isolated copy into the default slot (same rule as the
|
|
1710
|
+
// post-removal promotion filter and `agents use`). Leaving it unset is right:
|
|
1711
|
+
// an isolated-only agent has no default by design.
|
|
1615
1712
|
const def = getGlobalDefault(agent);
|
|
1616
|
-
if (!def || !fs.existsSync(getVersionDir(agent, def))) {
|
|
1713
|
+
if ((!def || !fs.existsSync(getVersionDir(agent, def))) && !isVersionIsolated(agent, survivor)) {
|
|
1617
1714
|
setGlobalDefault(agent, survivor);
|
|
1618
1715
|
}
|
|
1619
1716
|
}
|
|
@@ -1723,6 +1820,13 @@ export function removeVersion(agent, version) {
|
|
|
1723
1820
|
console.log(chalk.yellow(`Removed the last non-isolated ${agent} version and cleared its default. Reinstall with: agents add ${agent}, then set one with: agents use ${agent}@<version>`));
|
|
1724
1821
|
}
|
|
1725
1822
|
}
|
|
1823
|
+
// Same for the isolated pointer: a removed version must not stay the answer to a
|
|
1824
|
+
// bare `agents run <agent>`. Prefer the newest remaining isolated copy so removing
|
|
1825
|
+
// one of several does not silently drop the user back to their PATH binary.
|
|
1826
|
+
if (getIsolatedDefault(agent) === version) {
|
|
1827
|
+
const survivors = listInstalledVersions(agent).filter((v) => isVersionIsolated(agent, v));
|
|
1828
|
+
setIsolatedDefault(agent, survivors.length > 0 ? survivors[survivors.length - 1] : undefined);
|
|
1829
|
+
}
|
|
1726
1830
|
// Clean up dangling config symlink if it pointed to the removed version
|
|
1727
1831
|
const symlinkVersion = getConfigSymlinkVersion(agent);
|
|
1728
1832
|
if (symlinkVersion === version) {
|
|
@@ -1794,7 +1898,23 @@ export function resolveVersion(agent, projectPath) {
|
|
|
1794
1898
|
}
|
|
1795
1899
|
}
|
|
1796
1900
|
// Fall back to global default
|
|
1797
|
-
|
|
1901
|
+
const globalDefault = getGlobalDefault(agent);
|
|
1902
|
+
if (globalDefault)
|
|
1903
|
+
return globalDefault;
|
|
1904
|
+
// Last resort: the preferred isolated copy. Strictly a fallback — a global
|
|
1905
|
+
// default always wins, so nothing changes for anyone who has one. Without this,
|
|
1906
|
+
// an isolated-only user cannot reach their installs by bare name at all: the
|
|
1907
|
+
// resolution chain ended here, so `agents run codex` fell through to whatever
|
|
1908
|
+
// `codex` meant on PATH, and only `agents run codex@<version>` worked.
|
|
1909
|
+
//
|
|
1910
|
+
// The pointer is verified on read. It survives in agents.yaml across a trash +
|
|
1911
|
+
// restore cycle, but a version removed for good would otherwise leave a dangling
|
|
1912
|
+
// pin that resolves to a directory that is not there.
|
|
1913
|
+
const isolated = getIsolatedDefault(agent);
|
|
1914
|
+
if (isolated && isVersionInstalled(agent, isolated) && isVersionIsolated(agent, isolated)) {
|
|
1915
|
+
return isolated;
|
|
1916
|
+
}
|
|
1917
|
+
return null;
|
|
1798
1918
|
}
|
|
1799
1919
|
/**
|
|
1800
1920
|
* Normalize a user-supplied @version token across CLI subcommands.
|
|
@@ -2014,6 +2134,17 @@ export async function verifyInstalledBinaryLaunches(agent, version) {
|
|
|
2014
2134
|
* 4. Nothing runnable installed → install `latest`, pin it, return it.
|
|
2015
2135
|
* 5. Give up → return null (caller surfaces a clear error).
|
|
2016
2136
|
*
|
|
2137
|
+
* Isolation boundary (both directions — steps 3/4 are the only mutating ones):
|
|
2138
|
+
* - An ISOLATED target gets step 2 and nothing else. Falling back would let
|
|
2139
|
+
* `agents run <agent>@<isolated>` silently repoint the user's NORMAL default,
|
|
2140
|
+
* and installing `latest` would materialize a version they never asked for.
|
|
2141
|
+
* A failed repair returns null so the caller says so plainly.
|
|
2142
|
+
* - An isolated version is never a fallback CANDIDATE either, whatever the
|
|
2143
|
+
* target is: promoting one to the global default is exactly what `agents use`
|
|
2144
|
+
* refuses and what removeVersion's promotion filter excludes, so it must not
|
|
2145
|
+
* happen here by the back door (the daemon's launch-health pass calls this
|
|
2146
|
+
* unattended every ~6h).
|
|
2147
|
+
*
|
|
2017
2148
|
* Gated to npm-package agents: their native binary ships as an optional per-arch
|
|
2018
2149
|
* dependency whose tarball can extract partially (interrupted/raced install) —
|
|
2019
2150
|
* the exact failure this repairs. Agents with a global/native binary (grok,
|
|
@@ -2025,14 +2156,28 @@ export async function ensureAgentRunnable(agent, version, log) {
|
|
|
2025
2156
|
return version;
|
|
2026
2157
|
if ((await verifyInstalledBinaryLaunches(agent, version)).ok)
|
|
2027
2158
|
return version;
|
|
2159
|
+
// Read the marker BEFORE the repair: the reinstall rewrites the version dir,
|
|
2160
|
+
// so isolation must be decided from the pre-repair state, not re-read after.
|
|
2161
|
+
const targetIsolated = isVersionIsolated(agent, version);
|
|
2028
2162
|
log?.(`${cfg.name}@${version} is broken (platform binary missing) — repairing…`);
|
|
2029
2163
|
const repair = await installVersion(agent, version, undefined, { clean: true });
|
|
2030
2164
|
if (repair.success && (await verifyInstalledBinaryLaunches(agent, version)).ok) {
|
|
2031
2165
|
log?.(`repaired ${cfg.name}@${version}.`);
|
|
2032
2166
|
return version;
|
|
2033
2167
|
}
|
|
2168
|
+
// An isolated copy is walled off from the rest of the setup: repairing it in
|
|
2169
|
+
// place is the ONLY thing we may do. No fallback, no install, no default
|
|
2170
|
+
// switch — surface the failure and let the caller tell the user.
|
|
2171
|
+
if (targetIsolated) {
|
|
2172
|
+
log?.(`${cfg.name}@${version} is an isolated install and could not be repaired — leaving your default ${cfg.name} untouched.`);
|
|
2173
|
+
return null;
|
|
2174
|
+
}
|
|
2034
2175
|
// In-place repair failed → adopt another installed version that launches.
|
|
2035
|
-
|
|
2176
|
+
// Isolated copies are excluded: they are deliberately not promotable.
|
|
2177
|
+
const others = listInstalledVersions(agent)
|
|
2178
|
+
.filter(v => v !== version && !isVersionIsolated(agent, v))
|
|
2179
|
+
.sort(compareVersions)
|
|
2180
|
+
.reverse();
|
|
2036
2181
|
for (const cand of others) {
|
|
2037
2182
|
if ((await verifyInstalledBinaryLaunches(agent, cand)).ok) {
|
|
2038
2183
|
setGlobalDefault(agent, cand);
|
|
@@ -2041,9 +2186,28 @@ export async function ensureAgentRunnable(agent, version, log) {
|
|
|
2041
2186
|
}
|
|
2042
2187
|
}
|
|
2043
2188
|
// Nothing runnable installed → last resort: install latest and pin it.
|
|
2189
|
+
//
|
|
2190
|
+
// …unless `latest` is a version the user already holds as an ISOLATED copy. A
|
|
2191
|
+
// normal and an isolated install of the same version share one on-disk dir
|
|
2192
|
+
// (see the coexistence refusal in commands/versions.ts), so installing would
|
|
2193
|
+
// commandeer that copy and pinning it would hand an isolated install the
|
|
2194
|
+
// global default — the same breach the candidate filter above prevents.
|
|
2195
|
+
// Resolved BEFORE installing so the isolated copy is not even rebuilt.
|
|
2196
|
+
const latestVersion = await getLatestNpmVersion(agent);
|
|
2197
|
+
if (latestVersion && isVersionIsolated(agent, latestVersion)) {
|
|
2198
|
+
log?.(`no runnable ${cfg.name} version installed — ${cfg.name}@${latestVersion} is the latest, but you hold it as an isolated copy, so it can't become your default.`);
|
|
2199
|
+
log?.(`install one explicitly: agents add ${agent}@latest`);
|
|
2200
|
+
return null;
|
|
2201
|
+
}
|
|
2044
2202
|
log?.(`no runnable ${cfg.name} version installed — installing ${cfg.name}@latest…`);
|
|
2045
2203
|
const latest = await installVersion(agent, 'latest', undefined, { clean: true });
|
|
2046
2204
|
if (latest.success) {
|
|
2205
|
+
// Belt-and-braces: `latest` could have moved between the probe above and the
|
|
2206
|
+
// install. Never pin an isolated version, whatever the resolution said.
|
|
2207
|
+
if (isVersionIsolated(agent, latest.installedVersion)) {
|
|
2208
|
+
log?.(`${cfg.name}@${latest.installedVersion} is an isolated copy and can't be set as your default.`);
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2047
2211
|
setGlobalDefault(agent, latest.installedVersion);
|
|
2048
2212
|
log?.(`installed ${cfg.name}@${latest.installedVersion} and set it as the default.`);
|
|
2049
2213
|
return latest.installedVersion;
|
|
@@ -2119,9 +2283,10 @@ export function listRepoNames() {
|
|
|
2119
2283
|
* `expandPatterns` matches `source:*` patterns against. This is the single
|
|
2120
2284
|
* source of truth for how each kind attributes its source layer:
|
|
2121
2285
|
* - commands/skills/hooks/subagents → real layer from `listResources`
|
|
2122
|
-
* - permissions →
|
|
2286
|
+
* - permissions → real layer from permissions/groups
|
|
2123
2287
|
* - mcp → project vs user scope preserved
|
|
2124
|
-
* - plugins
|
|
2288
|
+
* - plugins → user repo
|
|
2289
|
+
* - workflows → real layer from workflow directories
|
|
2125
2290
|
* Both the persisted-pattern sync path and `buildRepoScopedSelection` use it
|
|
2126
2291
|
* so the attribution can't drift between the two.
|
|
2127
2292
|
*/
|
|
@@ -2132,14 +2297,24 @@ function resourceSourceMap(kind, cwd, available) {
|
|
|
2132
2297
|
case 'hooks':
|
|
2133
2298
|
case 'subagents':
|
|
2134
2299
|
return new Map(listResources(kind, cwd).map(r => [r.name, r.source]));
|
|
2135
|
-
case 'permissions':
|
|
2136
|
-
|
|
2300
|
+
case 'permissions': {
|
|
2301
|
+
const sources = sourceMapFromPermissionGroups(cwd);
|
|
2302
|
+
return new Map(available.permissions.flatMap((n) => {
|
|
2303
|
+
const source = sources.get(n);
|
|
2304
|
+
return source ? [[n, source]] : [];
|
|
2305
|
+
}));
|
|
2306
|
+
}
|
|
2137
2307
|
case 'mcp':
|
|
2138
2308
|
return new Map(getScopedMcpResources(cwd).map(r => [r.name, r.scope]));
|
|
2139
2309
|
case 'plugins':
|
|
2140
2310
|
return new Map(available.plugins.map(n => [n, 'user']));
|
|
2141
|
-
case 'workflows':
|
|
2142
|
-
|
|
2311
|
+
case 'workflows': {
|
|
2312
|
+
const sources = sourceMapFromWorkflows(cwd);
|
|
2313
|
+
return new Map(available.workflows.flatMap((n) => {
|
|
2314
|
+
const source = sources.get(n);
|
|
2315
|
+
return source ? [[n, source]] : [];
|
|
2316
|
+
}));
|
|
2317
|
+
}
|
|
2143
2318
|
}
|
|
2144
2319
|
}
|
|
2145
2320
|
/**
|
|
@@ -2239,11 +2414,10 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2239
2414
|
// only sets fields that aren't already present, preserving user edits).
|
|
2240
2415
|
{
|
|
2241
2416
|
const extraAliases = extraRepos.map(e => e.alias);
|
|
2242
|
-
const allLayers = defaultPatterns(extraAliases);
|
|
2243
2417
|
const noProject = defaultPatterns(extraAliases, false);
|
|
2244
2418
|
ensureVersionResourcePatterns(agent, version, {
|
|
2245
|
-
commands:
|
|
2246
|
-
skills:
|
|
2419
|
+
commands: noProject,
|
|
2420
|
+
skills: noProject,
|
|
2247
2421
|
hooks: noProject, // hooks: no project layer (security)
|
|
2248
2422
|
subagents: noProject,
|
|
2249
2423
|
plugins: noProject,
|
|
@@ -2295,6 +2469,9 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2295
2469
|
}
|
|
2296
2470
|
}
|
|
2297
2471
|
}
|
|
2472
|
+
if (projectAgentsDir) {
|
|
2473
|
+
syncProjectResourcesToAgent(agent, version, projectAgentsDir);
|
|
2474
|
+
}
|
|
2298
2475
|
// Fast guard: skip the entire sync when the caller requested a full sync and
|
|
2299
2476
|
// nothing has changed since the last full sync. Pattern-derived selections
|
|
2300
2477
|
// still count as full syncs because they are the persisted intended scope,
|
|
@@ -2347,6 +2524,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2347
2524
|
}
|
|
2348
2525
|
return [];
|
|
2349
2526
|
};
|
|
2527
|
+
const trustedCommandNames = (names) => names.filter((name) => resolveCommandSource(name) !== null);
|
|
2350
2528
|
// Sync commands — dispatch through WRITERS.commands. The writer dispatches
|
|
2351
2529
|
// between native (file copy / TOML conversion) and commands-as-skills
|
|
2352
2530
|
// (grok, Codex >= 0.117.0) based on `shouldInstallCommandAsSkill`. The
|
|
@@ -2354,8 +2532,8 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2354
2532
|
// takes the commands-as-skills path — silently dropping every command.
|
|
2355
2533
|
const commandsWriter = getWriter('commands', agent);
|
|
2356
2534
|
const commandsToSync = selection
|
|
2357
|
-
? resolveSelection(selection.commands, available.commands)
|
|
2358
|
-
: available.commands; // No selection = sync all
|
|
2535
|
+
? trustedCommandNames(resolveSelection(selection.commands, available.commands))
|
|
2536
|
+
: trustedCommandNames(available.commands); // No selection = sync all trusted commands, excluding project-only commands
|
|
2359
2537
|
const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
|
|
2360
2538
|
if (commandsToSync.length > 0 && commandsWriter) {
|
|
2361
2539
|
const commandsTarget = path.join(agentDir, agentConfig.commandsSubdir);
|
|
@@ -2393,9 +2571,18 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2393
2571
|
// ~/.agents/skills/ (Gemini) are not registered; we clear the version-home
|
|
2394
2572
|
// skills dir for them so a stale per-version copy never shadows central.
|
|
2395
2573
|
const skillsWriter = getWriter('skills', agent);
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2574
|
+
const pluginsWriter = getWriter('plugins', agent);
|
|
2575
|
+
const pluginsToSync = selection
|
|
2576
|
+
? resolveSelection(selection.plugins, available.plugins)
|
|
2577
|
+
: (pluginsWriter ? available.plugins : []);
|
|
2578
|
+
const pluginSkillsToSync = listPluginSkillNames({ agent, plugins: new Set(pluginsToSync) });
|
|
2579
|
+
const trustedSkillNames = (names) => names.filter((name) => resolveSkillSource(name, { agent, plugins: new Set(pluginsToSync) }) !== null);
|
|
2580
|
+
const selectedSkillsToSync = selection
|
|
2581
|
+
? trustedSkillNames(resolveSelection(selection.skills, available.skills))
|
|
2582
|
+
: trustedSkillNames(available.skills);
|
|
2583
|
+
let skillsToSync = userPassedSelection
|
|
2584
|
+
? selectedSkillsToSync
|
|
2585
|
+
: Array.from(new Set([...selectedSkillsToSync, ...pluginSkillsToSync]));
|
|
2399
2586
|
if (commandsAsSkills && commandsToSync.length > 0 && skillsToSync.length > 0) {
|
|
2400
2587
|
const commandNames = new Set(commandsToSync);
|
|
2401
2588
|
const skillRoots = [
|
|
@@ -2488,7 +2675,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2488
2675
|
const overridePreset = Array.isArray(selection?.memory) && selection.memory.length === 1 && selection.memory[0] !== 'AGENTS'
|
|
2489
2676
|
? selection.memory[0]
|
|
2490
2677
|
: null;
|
|
2491
|
-
const preset = overridePreset || getActiveRulesPreset(agent, version);
|
|
2678
|
+
const preset = overridePreset || activeRulesPreset() || getActiveRulesPreset(agent, version);
|
|
2492
2679
|
const r = rulesWriter.write({ version, versionHome, selection: { preset }, cwd });
|
|
2493
2680
|
result.memory.push(...r.synced);
|
|
2494
2681
|
// rulesPreset is tracked separately via setActiveRulesPreset.
|
|
@@ -2575,9 +2762,11 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2575
2762
|
// as commands/skills/hooks).
|
|
2576
2763
|
const subagentsWriter = getWriter('subagents', agent);
|
|
2577
2764
|
const subagentsGate = supports(agent, 'subagents', version);
|
|
2765
|
+
const installedSubagentNames = new Set(listInstalledSubagents().map((subagent) => subagent.name));
|
|
2766
|
+
const trustedSubagentNames = (names) => names.filter((name) => installedSubagentNames.has(name));
|
|
2578
2767
|
const subagentsRequested = selection
|
|
2579
|
-
? resolveSelection(selection.subagents, available.subagents)
|
|
2580
|
-
: (subagentsWriter ? available.subagents : []);
|
|
2768
|
+
? trustedSubagentNames(resolveSelection(selection.subagents, available.subagents))
|
|
2769
|
+
: (subagentsWriter ? trustedSubagentNames(available.subagents) : []);
|
|
2581
2770
|
const subagentsToSync = subagentsGate.ok ? subagentsRequested : [];
|
|
2582
2771
|
if (subagentsRequested.length > 0 && !subagentsGate.ok) {
|
|
2583
2772
|
console.warn(explainSkip(agent, 'subagents', subagentsGate, version) + ' -- skipped');
|
|
@@ -2606,10 +2795,6 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2606
2795
|
}
|
|
2607
2796
|
}
|
|
2608
2797
|
// Sync plugins — dispatch through WRITERS.plugins.
|
|
2609
|
-
const pluginsWriter = getWriter('plugins', agent);
|
|
2610
|
-
const pluginsToSync = selection
|
|
2611
|
-
? resolveSelection(selection.plugins, available.plugins)
|
|
2612
|
-
: (pluginsWriter ? available.plugins : []);
|
|
2613
2798
|
if (pluginsToSync.length > 0 && pluginsWriter) {
|
|
2614
2799
|
const r = pluginsWriter.write({ version, versionHome, selection: pluginsToSync, cwd });
|
|
2615
2800
|
result.plugins.push(...r.synced);
|
|
@@ -2617,9 +2802,15 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2617
2802
|
// Sync workflows — dispatch through WRITERS.workflows.
|
|
2618
2803
|
const workflowsWriter = getWriter('workflows', agent);
|
|
2619
2804
|
const workflowsGate = supports(agent, 'workflows', version);
|
|
2805
|
+
const trustedWorkflowNames = (names) => {
|
|
2806
|
+
if (names.length === 0)
|
|
2807
|
+
return [];
|
|
2808
|
+
const installedWorkflowNames = new Set(listInstalledWorkflows().keys());
|
|
2809
|
+
return names.filter((name) => installedWorkflowNames.has(name));
|
|
2810
|
+
};
|
|
2620
2811
|
const workflowsRequested = selection
|
|
2621
|
-
? resolveSelection(selection.workflows, available.workflows)
|
|
2622
|
-
: (workflowsWriter ? available.workflows : []);
|
|
2812
|
+
? trustedWorkflowNames(resolveSelection(selection.workflows, available.workflows))
|
|
2813
|
+
: (workflowsWriter ? trustedWorkflowNames(available.workflows) : []);
|
|
2623
2814
|
const workflowsToSync = workflowsGate.ok ? workflowsRequested : [];
|
|
2624
2815
|
if (workflowsRequested.length > 0 && !workflowsGate.ok) {
|
|
2625
2816
|
console.warn(explainSkip(agent, 'workflows', workflowsGate, version) + ' -- skipped');
|
package/dist/lib/workflows.d.ts
CHANGED
|
@@ -294,6 +294,8 @@ export declare function transformWorkflowForKimi(workflowPath: string, name: str
|
|
|
294
294
|
* user-authored workflow of the same name.
|
|
295
295
|
*/
|
|
296
296
|
export declare function transformWorkflowForAntigravity(workflowPath: string, name: string): string;
|
|
297
|
+
/** Convert a canonical agents-cli workflow bundle into an OpenClaw Lobster file. */
|
|
298
|
+
export declare function transformWorkflowForOpenClaw(workflowPath: string, name: string): string;
|
|
297
299
|
/**
|
|
298
300
|
* Resolve an `agents run <workflow>` reference.
|
|
299
301
|
*
|