@phnx-labs/agents-cli 1.20.71 → 1.20.73
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 +325 -1
- 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/commands.js +2 -0
- package/dist/commands/doctor.js +8 -4
- 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/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/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 +39 -10
- package/dist/commands/mcp.js +2 -0
- 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/resource-view.d.ts +2 -0
- package/dist/commands/resource-view.js +8 -0
- 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.d.ts +6 -0
- package/dist/commands/sessions.js +26 -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 +3 -1
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/skills.js +2 -0
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/status.js +8 -2
- package/dist/commands/subagents.js +3 -1
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/uninstall.d.ts +11 -0
- package/dist/commands/uninstall.js +158 -0
- package/dist/commands/utils.d.ts +29 -0
- package/dist/commands/utils.js +24 -0
- package/dist/commands/versions.js +120 -11
- package/dist/index.js +83 -12
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.js +16 -5
- 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/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 +14 -5
- package/dist/lib/daemon.js +52 -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/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/logs.d.ts +12 -0
- package/dist/lib/hosts/logs.js +18 -0
- 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/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 +291 -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 +36 -0
- package/dist/lib/shims.js +120 -16
- 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 +3 -0
- 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 +73 -0
- package/dist/lib/uninstall.d.ts +84 -0
- package/dist/lib/uninstall.js +347 -0
- package/dist/lib/usage.d.ts +13 -1
- package/dist/lib/usage.js +32 -14
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +27 -0
- package/dist/lib/versions.js +248 -79
- 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.
|
|
@@ -34,34 +35,44 @@ import { parseMcpServerConfig, isProjectMcpTrusted } from './mcp.js';
|
|
|
34
35
|
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } 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());
|
|
@@ -1148,6 +1199,37 @@ export function setGlobalDefault(agent, version) {
|
|
|
1148
1199
|
}
|
|
1149
1200
|
writeMeta(meta);
|
|
1150
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Path to the sentinel file that marks a version as an isolated install.
|
|
1204
|
+
*
|
|
1205
|
+
* It lives at the version-dir root (a sibling of `home/`), so it is carried
|
|
1206
|
+
* along when `softDeleteVersionDir` moves the whole directory to trash and is
|
|
1207
|
+
* restored intact by `agents trash restore`. Its mere presence is the marker;
|
|
1208
|
+
* the contents are an informational timestamp only.
|
|
1209
|
+
*/
|
|
1210
|
+
function getIsolatedMarkerPath(agent, version) {
|
|
1211
|
+
return path.join(getVersionDir(agent, version), '.isolated');
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Mark an installed version as an isolated install (`agents add --isolated`).
|
|
1215
|
+
*
|
|
1216
|
+
* Isolated versions are fully self-contained: they never become the global
|
|
1217
|
+
* default and never own the user's real `~/.<agent>` config directory. This
|
|
1218
|
+
* flag is what keeps every "adopting" code path away from them.
|
|
1219
|
+
*/
|
|
1220
|
+
export function markVersionIsolated(agent, version) {
|
|
1221
|
+
fs.writeFileSync(getIsolatedMarkerPath(agent, version), `${new Date().toISOString()}\n`, { mode: 0o600 });
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Whether a version was installed as an isolated install (`agents add --isolated`).
|
|
1225
|
+
*
|
|
1226
|
+
* Used to exclude such versions from global-default promotion and from any
|
|
1227
|
+
* flow that would touch the user's real `~/.<agent>` directory, and to gate the
|
|
1228
|
+
* `--isolated` safety check on `agents remove`.
|
|
1229
|
+
*/
|
|
1230
|
+
export function isVersionIsolated(agent, version) {
|
|
1231
|
+
return fs.existsSync(getIsolatedMarkerPath(agent, version));
|
|
1232
|
+
}
|
|
1151
1233
|
/**
|
|
1152
1234
|
* Grok's official installer writes into ~/.grok/downloads, which (because we
|
|
1153
1235
|
* symlink ~/.grok to the active version home) resolves to the PREVIOUS default
|
|
@@ -1459,16 +1541,23 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1459
1541
|
return { success: false, installedVersion: version, error: err.message };
|
|
1460
1542
|
}
|
|
1461
1543
|
}
|
|
1544
|
+
// Version-dir entries that are STATE, not install output, and so must survive a
|
|
1545
|
+
// clean reinstall: `home/`, and the `.isolated` marker that is the single source
|
|
1546
|
+
// of truth for "this copy is walled off". Losing the marker would silently demote
|
|
1547
|
+
// an isolated copy to a normal one on its first repair — after which shim
|
|
1548
|
+
// self-heal would hand it a bare `<agent>` shim and a PATH entry.
|
|
1549
|
+
const PRESERVED_ON_CLEAN_REINSTALL = new Set(['home', '.isolated']);
|
|
1462
1550
|
/**
|
|
1463
1551
|
* Remove install artifacts from a version directory, preserving `home/` which
|
|
1464
1552
|
* contains the user's conversation history, sessions, history.jsonl, tasks,
|
|
1465
|
-
* todos, file-history, etc.
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1553
|
+
* todos, file-history, etc. (and the `.isolated` marker — see
|
|
1554
|
+
* PRESERVED_ON_CLEAN_REINSTALL). Used by the install pipeline (NOT by
|
|
1555
|
+
* removeVersion) to clean up staging artifacts when a fresh install collides
|
|
1556
|
+
* with an existing dir. removeVersion uses soft-delete instead.
|
|
1468
1557
|
*/
|
|
1469
1558
|
function removeInstallArtifacts(versionDir) {
|
|
1470
1559
|
for (const entry of fs.readdirSync(versionDir)) {
|
|
1471
|
-
if (entry
|
|
1560
|
+
if (PRESERVED_ON_CLEAN_REINSTALL.has(entry))
|
|
1472
1561
|
continue;
|
|
1473
1562
|
fs.rmSync(path.join(versionDir, entry), { recursive: true, force: true });
|
|
1474
1563
|
}
|
|
@@ -1580,9 +1669,12 @@ async function reconcileGlobalBinaryVersions(agent) {
|
|
|
1580
1669
|
}
|
|
1581
1670
|
if (foldedAny) {
|
|
1582
1671
|
invalidateInstalledVersionsCache(agent);
|
|
1583
|
-
// Keep the recorded default pointing at a dir that still exists on disk
|
|
1672
|
+
// Keep the recorded default pointing at a dir that still exists on disk —
|
|
1673
|
+
// but never promote an isolated copy into the default slot (same rule as the
|
|
1674
|
+
// post-removal promotion filter and `agents use`). Leaving it unset is right:
|
|
1675
|
+
// an isolated-only agent has no default by design.
|
|
1584
1676
|
const def = getGlobalDefault(agent);
|
|
1585
|
-
if (!def || !fs.existsSync(getVersionDir(agent, def))) {
|
|
1677
|
+
if ((!def || !fs.existsSync(getVersionDir(agent, def))) && !isVersionIsolated(agent, survivor)) {
|
|
1586
1678
|
setGlobalDefault(agent, survivor);
|
|
1587
1679
|
}
|
|
1588
1680
|
}
|
|
@@ -1676,14 +1768,20 @@ export function removeVersion(agent, version) {
|
|
|
1676
1768
|
// broke every launcher after removing the pinned default.
|
|
1677
1769
|
if (getGlobalDefault(agent) === version) {
|
|
1678
1770
|
const remaining = listInstalledVersions(agent);
|
|
1679
|
-
|
|
1680
|
-
|
|
1771
|
+
// Never auto-promote an isolated install to the global default: it was
|
|
1772
|
+
// installed to stay separate from the user's setup, and promoting it would
|
|
1773
|
+
// silently make `<agent>` resolve to it (and let a later `use` adopt its
|
|
1774
|
+
// home into `~/.<agent>`). Prefer the newest NON-isolated survivor; if every
|
|
1775
|
+
// survivor is isolated, clear the default rather than adopt one.
|
|
1776
|
+
const promotable = remaining.filter((v) => !isVersionIsolated(agent, v));
|
|
1777
|
+
if (promotable.length > 0) {
|
|
1778
|
+
const newestRemaining = promotable[promotable.length - 1];
|
|
1681
1779
|
setGlobalDefault(agent, newestRemaining);
|
|
1682
1780
|
console.log(chalk.yellow(`Default ${agent} was ${version} (removed); reassigned to ${newestRemaining}. Change it with: agents use ${agent}@<version>`));
|
|
1683
1781
|
}
|
|
1684
1782
|
else {
|
|
1685
1783
|
setGlobalDefault(agent, undefined);
|
|
1686
|
-
console.log(chalk.yellow(`Removed the last
|
|
1784
|
+
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>`));
|
|
1687
1785
|
}
|
|
1688
1786
|
}
|
|
1689
1787
|
// Clean up dangling config symlink if it pointed to the removed version
|
|
@@ -1977,6 +2075,17 @@ export async function verifyInstalledBinaryLaunches(agent, version) {
|
|
|
1977
2075
|
* 4. Nothing runnable installed → install `latest`, pin it, return it.
|
|
1978
2076
|
* 5. Give up → return null (caller surfaces a clear error).
|
|
1979
2077
|
*
|
|
2078
|
+
* Isolation boundary (both directions — steps 3/4 are the only mutating ones):
|
|
2079
|
+
* - An ISOLATED target gets step 2 and nothing else. Falling back would let
|
|
2080
|
+
* `agents run <agent>@<isolated>` silently repoint the user's NORMAL default,
|
|
2081
|
+
* and installing `latest` would materialize a version they never asked for.
|
|
2082
|
+
* A failed repair returns null so the caller says so plainly.
|
|
2083
|
+
* - An isolated version is never a fallback CANDIDATE either, whatever the
|
|
2084
|
+
* target is: promoting one to the global default is exactly what `agents use`
|
|
2085
|
+
* refuses and what removeVersion's promotion filter excludes, so it must not
|
|
2086
|
+
* happen here by the back door (the daemon's launch-health pass calls this
|
|
2087
|
+
* unattended every ~6h).
|
|
2088
|
+
*
|
|
1980
2089
|
* Gated to npm-package agents: their native binary ships as an optional per-arch
|
|
1981
2090
|
* dependency whose tarball can extract partially (interrupted/raced install) —
|
|
1982
2091
|
* the exact failure this repairs. Agents with a global/native binary (grok,
|
|
@@ -1988,14 +2097,28 @@ export async function ensureAgentRunnable(agent, version, log) {
|
|
|
1988
2097
|
return version;
|
|
1989
2098
|
if ((await verifyInstalledBinaryLaunches(agent, version)).ok)
|
|
1990
2099
|
return version;
|
|
2100
|
+
// Read the marker BEFORE the repair: the reinstall rewrites the version dir,
|
|
2101
|
+
// so isolation must be decided from the pre-repair state, not re-read after.
|
|
2102
|
+
const targetIsolated = isVersionIsolated(agent, version);
|
|
1991
2103
|
log?.(`${cfg.name}@${version} is broken (platform binary missing) — repairing…`);
|
|
1992
2104
|
const repair = await installVersion(agent, version, undefined, { clean: true });
|
|
1993
2105
|
if (repair.success && (await verifyInstalledBinaryLaunches(agent, version)).ok) {
|
|
1994
2106
|
log?.(`repaired ${cfg.name}@${version}.`);
|
|
1995
2107
|
return version;
|
|
1996
2108
|
}
|
|
2109
|
+
// An isolated copy is walled off from the rest of the setup: repairing it in
|
|
2110
|
+
// place is the ONLY thing we may do. No fallback, no install, no default
|
|
2111
|
+
// switch — surface the failure and let the caller tell the user.
|
|
2112
|
+
if (targetIsolated) {
|
|
2113
|
+
log?.(`${cfg.name}@${version} is an isolated install and could not be repaired — leaving your default ${cfg.name} untouched.`);
|
|
2114
|
+
return null;
|
|
2115
|
+
}
|
|
1997
2116
|
// In-place repair failed → adopt another installed version that launches.
|
|
1998
|
-
|
|
2117
|
+
// Isolated copies are excluded: they are deliberately not promotable.
|
|
2118
|
+
const others = listInstalledVersions(agent)
|
|
2119
|
+
.filter(v => v !== version && !isVersionIsolated(agent, v))
|
|
2120
|
+
.sort(compareVersions)
|
|
2121
|
+
.reverse();
|
|
1999
2122
|
for (const cand of others) {
|
|
2000
2123
|
if ((await verifyInstalledBinaryLaunches(agent, cand)).ok) {
|
|
2001
2124
|
setGlobalDefault(agent, cand);
|
|
@@ -2004,9 +2127,28 @@ export async function ensureAgentRunnable(agent, version, log) {
|
|
|
2004
2127
|
}
|
|
2005
2128
|
}
|
|
2006
2129
|
// Nothing runnable installed → last resort: install latest and pin it.
|
|
2130
|
+
//
|
|
2131
|
+
// …unless `latest` is a version the user already holds as an ISOLATED copy. A
|
|
2132
|
+
// normal and an isolated install of the same version share one on-disk dir
|
|
2133
|
+
// (see the coexistence refusal in commands/versions.ts), so installing would
|
|
2134
|
+
// commandeer that copy and pinning it would hand an isolated install the
|
|
2135
|
+
// global default — the same breach the candidate filter above prevents.
|
|
2136
|
+
// Resolved BEFORE installing so the isolated copy is not even rebuilt.
|
|
2137
|
+
const latestVersion = await getLatestNpmVersion(agent);
|
|
2138
|
+
if (latestVersion && isVersionIsolated(agent, latestVersion)) {
|
|
2139
|
+
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.`);
|
|
2140
|
+
log?.(`install one explicitly: agents add ${agent}@latest`);
|
|
2141
|
+
return null;
|
|
2142
|
+
}
|
|
2007
2143
|
log?.(`no runnable ${cfg.name} version installed — installing ${cfg.name}@latest…`);
|
|
2008
2144
|
const latest = await installVersion(agent, 'latest', undefined, { clean: true });
|
|
2009
2145
|
if (latest.success) {
|
|
2146
|
+
// Belt-and-braces: `latest` could have moved between the probe above and the
|
|
2147
|
+
// install. Never pin an isolated version, whatever the resolution said.
|
|
2148
|
+
if (isVersionIsolated(agent, latest.installedVersion)) {
|
|
2149
|
+
log?.(`${cfg.name}@${latest.installedVersion} is an isolated copy and can't be set as your default.`);
|
|
2150
|
+
return null;
|
|
2151
|
+
}
|
|
2010
2152
|
setGlobalDefault(agent, latest.installedVersion);
|
|
2011
2153
|
log?.(`installed ${cfg.name}@${latest.installedVersion} and set it as the default.`);
|
|
2012
2154
|
return latest.installedVersion;
|
|
@@ -2082,9 +2224,10 @@ export function listRepoNames() {
|
|
|
2082
2224
|
* `expandPatterns` matches `source:*` patterns against. This is the single
|
|
2083
2225
|
* source of truth for how each kind attributes its source layer:
|
|
2084
2226
|
* - commands/skills/hooks/subagents → real layer from `listResources`
|
|
2085
|
-
* - permissions →
|
|
2227
|
+
* - permissions → real layer from permissions/groups
|
|
2086
2228
|
* - mcp → project vs user scope preserved
|
|
2087
|
-
* - plugins
|
|
2229
|
+
* - plugins → user repo
|
|
2230
|
+
* - workflows → real layer from workflow directories
|
|
2088
2231
|
* Both the persisted-pattern sync path and `buildRepoScopedSelection` use it
|
|
2089
2232
|
* so the attribution can't drift between the two.
|
|
2090
2233
|
*/
|
|
@@ -2095,14 +2238,24 @@ function resourceSourceMap(kind, cwd, available) {
|
|
|
2095
2238
|
case 'hooks':
|
|
2096
2239
|
case 'subagents':
|
|
2097
2240
|
return new Map(listResources(kind, cwd).map(r => [r.name, r.source]));
|
|
2098
|
-
case 'permissions':
|
|
2099
|
-
|
|
2241
|
+
case 'permissions': {
|
|
2242
|
+
const sources = sourceMapFromPermissionGroups(cwd);
|
|
2243
|
+
return new Map(available.permissions.flatMap((n) => {
|
|
2244
|
+
const source = sources.get(n);
|
|
2245
|
+
return source ? [[n, source]] : [];
|
|
2246
|
+
}));
|
|
2247
|
+
}
|
|
2100
2248
|
case 'mcp':
|
|
2101
2249
|
return new Map(getScopedMcpResources(cwd).map(r => [r.name, r.scope]));
|
|
2102
2250
|
case 'plugins':
|
|
2103
2251
|
return new Map(available.plugins.map(n => [n, 'user']));
|
|
2104
|
-
case 'workflows':
|
|
2105
|
-
|
|
2252
|
+
case 'workflows': {
|
|
2253
|
+
const sources = sourceMapFromWorkflows(cwd);
|
|
2254
|
+
return new Map(available.workflows.flatMap((n) => {
|
|
2255
|
+
const source = sources.get(n);
|
|
2256
|
+
return source ? [[n, source]] : [];
|
|
2257
|
+
}));
|
|
2258
|
+
}
|
|
2106
2259
|
}
|
|
2107
2260
|
}
|
|
2108
2261
|
/**
|
|
@@ -2202,11 +2355,10 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2202
2355
|
// only sets fields that aren't already present, preserving user edits).
|
|
2203
2356
|
{
|
|
2204
2357
|
const extraAliases = extraRepos.map(e => e.alias);
|
|
2205
|
-
const allLayers = defaultPatterns(extraAliases);
|
|
2206
2358
|
const noProject = defaultPatterns(extraAliases, false);
|
|
2207
2359
|
ensureVersionResourcePatterns(agent, version, {
|
|
2208
|
-
commands:
|
|
2209
|
-
skills:
|
|
2360
|
+
commands: noProject,
|
|
2361
|
+
skills: noProject,
|
|
2210
2362
|
hooks: noProject, // hooks: no project layer (security)
|
|
2211
2363
|
subagents: noProject,
|
|
2212
2364
|
plugins: noProject,
|
|
@@ -2258,6 +2410,9 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2258
2410
|
}
|
|
2259
2411
|
}
|
|
2260
2412
|
}
|
|
2413
|
+
if (projectAgentsDir) {
|
|
2414
|
+
syncProjectResourcesToAgent(agent, version, projectAgentsDir);
|
|
2415
|
+
}
|
|
2261
2416
|
// Fast guard: skip the entire sync when the caller requested a full sync and
|
|
2262
2417
|
// nothing has changed since the last full sync. Pattern-derived selections
|
|
2263
2418
|
// still count as full syncs because they are the persisted intended scope,
|
|
@@ -2310,6 +2465,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2310
2465
|
}
|
|
2311
2466
|
return [];
|
|
2312
2467
|
};
|
|
2468
|
+
const trustedCommandNames = (names) => names.filter((name) => resolveCommandSource(name) !== null);
|
|
2313
2469
|
// Sync commands — dispatch through WRITERS.commands. The writer dispatches
|
|
2314
2470
|
// between native (file copy / TOML conversion) and commands-as-skills
|
|
2315
2471
|
// (grok, Codex >= 0.117.0) based on `shouldInstallCommandAsSkill`. The
|
|
@@ -2317,8 +2473,8 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2317
2473
|
// takes the commands-as-skills path — silently dropping every command.
|
|
2318
2474
|
const commandsWriter = getWriter('commands', agent);
|
|
2319
2475
|
const commandsToSync = selection
|
|
2320
|
-
? resolveSelection(selection.commands, available.commands)
|
|
2321
|
-
: available.commands; // No selection = sync all
|
|
2476
|
+
? trustedCommandNames(resolveSelection(selection.commands, available.commands))
|
|
2477
|
+
: trustedCommandNames(available.commands); // No selection = sync all trusted commands, excluding project-only commands
|
|
2322
2478
|
const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
|
|
2323
2479
|
if (commandsToSync.length > 0 && commandsWriter) {
|
|
2324
2480
|
const commandsTarget = path.join(agentDir, agentConfig.commandsSubdir);
|
|
@@ -2356,9 +2512,18 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2356
2512
|
// ~/.agents/skills/ (Gemini) are not registered; we clear the version-home
|
|
2357
2513
|
// skills dir for them so a stale per-version copy never shadows central.
|
|
2358
2514
|
const skillsWriter = getWriter('skills', agent);
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2515
|
+
const pluginsWriter = getWriter('plugins', agent);
|
|
2516
|
+
const pluginsToSync = selection
|
|
2517
|
+
? resolveSelection(selection.plugins, available.plugins)
|
|
2518
|
+
: (pluginsWriter ? available.plugins : []);
|
|
2519
|
+
const pluginSkillsToSync = listPluginSkillNames({ agent, plugins: new Set(pluginsToSync) });
|
|
2520
|
+
const trustedSkillNames = (names) => names.filter((name) => resolveSkillSource(name, { agent, plugins: new Set(pluginsToSync) }) !== null);
|
|
2521
|
+
const selectedSkillsToSync = selection
|
|
2522
|
+
? trustedSkillNames(resolveSelection(selection.skills, available.skills))
|
|
2523
|
+
: trustedSkillNames(available.skills);
|
|
2524
|
+
let skillsToSync = userPassedSelection
|
|
2525
|
+
? selectedSkillsToSync
|
|
2526
|
+
: Array.from(new Set([...selectedSkillsToSync, ...pluginSkillsToSync]));
|
|
2362
2527
|
if (commandsAsSkills && commandsToSync.length > 0 && skillsToSync.length > 0) {
|
|
2363
2528
|
const commandNames = new Set(commandsToSync);
|
|
2364
2529
|
const skillRoots = [
|
|
@@ -2451,7 +2616,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2451
2616
|
const overridePreset = Array.isArray(selection?.memory) && selection.memory.length === 1 && selection.memory[0] !== 'AGENTS'
|
|
2452
2617
|
? selection.memory[0]
|
|
2453
2618
|
: null;
|
|
2454
|
-
const preset = overridePreset || getActiveRulesPreset(agent, version);
|
|
2619
|
+
const preset = overridePreset || activeRulesPreset() || getActiveRulesPreset(agent, version);
|
|
2455
2620
|
const r = rulesWriter.write({ version, versionHome, selection: { preset }, cwd });
|
|
2456
2621
|
result.memory.push(...r.synced);
|
|
2457
2622
|
// rulesPreset is tracked separately via setActiveRulesPreset.
|
|
@@ -2538,9 +2703,11 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2538
2703
|
// as commands/skills/hooks).
|
|
2539
2704
|
const subagentsWriter = getWriter('subagents', agent);
|
|
2540
2705
|
const subagentsGate = supports(agent, 'subagents', version);
|
|
2706
|
+
const installedSubagentNames = new Set(listInstalledSubagents().map((subagent) => subagent.name));
|
|
2707
|
+
const trustedSubagentNames = (names) => names.filter((name) => installedSubagentNames.has(name));
|
|
2541
2708
|
const subagentsRequested = selection
|
|
2542
|
-
? resolveSelection(selection.subagents, available.subagents)
|
|
2543
|
-
: (subagentsWriter ? available.subagents : []);
|
|
2709
|
+
? trustedSubagentNames(resolveSelection(selection.subagents, available.subagents))
|
|
2710
|
+
: (subagentsWriter ? trustedSubagentNames(available.subagents) : []);
|
|
2544
2711
|
const subagentsToSync = subagentsGate.ok ? subagentsRequested : [];
|
|
2545
2712
|
if (subagentsRequested.length > 0 && !subagentsGate.ok) {
|
|
2546
2713
|
console.warn(explainSkip(agent, 'subagents', subagentsGate, version) + ' -- skipped');
|
|
@@ -2569,10 +2736,6 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2569
2736
|
}
|
|
2570
2737
|
}
|
|
2571
2738
|
// Sync plugins — dispatch through WRITERS.plugins.
|
|
2572
|
-
const pluginsWriter = getWriter('plugins', agent);
|
|
2573
|
-
const pluginsToSync = selection
|
|
2574
|
-
? resolveSelection(selection.plugins, available.plugins)
|
|
2575
|
-
: (pluginsWriter ? available.plugins : []);
|
|
2576
2739
|
if (pluginsToSync.length > 0 && pluginsWriter) {
|
|
2577
2740
|
const r = pluginsWriter.write({ version, versionHome, selection: pluginsToSync, cwd });
|
|
2578
2741
|
result.plugins.push(...r.synced);
|
|
@@ -2580,9 +2743,15 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2580
2743
|
// Sync workflows — dispatch through WRITERS.workflows.
|
|
2581
2744
|
const workflowsWriter = getWriter('workflows', agent);
|
|
2582
2745
|
const workflowsGate = supports(agent, 'workflows', version);
|
|
2746
|
+
const trustedWorkflowNames = (names) => {
|
|
2747
|
+
if (names.length === 0)
|
|
2748
|
+
return [];
|
|
2749
|
+
const installedWorkflowNames = new Set(listInstalledWorkflows().keys());
|
|
2750
|
+
return names.filter((name) => installedWorkflowNames.has(name));
|
|
2751
|
+
};
|
|
2583
2752
|
const workflowsRequested = selection
|
|
2584
|
-
? resolveSelection(selection.workflows, available.workflows)
|
|
2585
|
-
: (workflowsWriter ? available.workflows : []);
|
|
2753
|
+
? trustedWorkflowNames(resolveSelection(selection.workflows, available.workflows))
|
|
2754
|
+
: (workflowsWriter ? trustedWorkflowNames(available.workflows) : []);
|
|
2586
2755
|
const workflowsToSync = workflowsGate.ok ? workflowsRequested : [];
|
|
2587
2756
|
if (workflowsRequested.length > 0 && !workflowsGate.ok) {
|
|
2588
2757
|
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
|
*
|