@duckmind/dm-windows-x64 0.60.6 → 0.60.9
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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +70 -123
- package/extensions/dm-9router-ext/src/index.js +410 -5
- package/extensions/dm-caveman/extensions/caveman.js +283 -12
- package/extensions/dm-cliproxy/index.js +182 -2
- package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
- package/extensions/dm-cliproxy/src/apply.js +228 -1
- package/extensions/dm-cliproxy/src/cache.js +42 -1
- package/extensions/dm-cliproxy/src/commands.js +50 -2
- package/extensions/dm-cliproxy/src/compat.js +81 -1
- package/extensions/dm-cliproxy/src/config.js +192 -2
- package/extensions/dm-cliproxy/src/conflicts.js +46 -1
- package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
- package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
- package/extensions/dm-cliproxy/src/log.js +23 -1
- package/extensions/dm-cliproxy/src/status-quota.js +77 -1
- package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
- package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
- package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
- package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
- package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
- package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
- package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
- package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
- package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
- package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
- package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
- package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
- package/extensions/dm-context/src/context.js +144 -1
- package/extensions/dm-context/src/index.js +339 -7
- package/extensions/dm-context/src/utils.js +9 -1
- package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
- package/extensions/dm-cua/index.js +75 -6
- package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
- package/extensions/dm-cua/src/browser-install.mjs +331 -2
- package/extensions/dm-fff/src/index.js +688 -12
- package/extensions/dm-fff/src/query.js +60 -1
- package/extensions/dm-goal/src/goal.js +894 -23
- package/extensions/dm-image2/index.js +103 -9
- package/extensions/dm-image2/src/image-lib.mjs +1275 -8
- package/extensions/dm-subagents/install.mjs +62 -8
- package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
- package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
- package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
- package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
- package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
- package/extensions/dm-subagents/src/agents/agents.js +1139 -11
- package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
- package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
- package/extensions/dm-subagents/src/agents/identity.js +29 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
- package/extensions/dm-subagents/src/agents/skills.js +614 -6
- package/extensions/dm-subagents/src/extension/config.js +35 -2
- package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
- package/extensions/dm-subagents/src/extension/doctor.js +172 -15
- package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
- package/extensions/dm-subagents/src/extension/index.js +521 -359
- package/extensions/dm-subagents/src/extension/rpc.js +266 -7
- package/extensions/dm-subagents/src/extension/schemas.js +275 -1
- package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
- package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
- package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
- package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
- package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
- package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
- package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
- package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
- package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
- package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
- package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
- package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
- package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
- package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
- package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
- package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
- package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
- package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
- package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
- package/extensions/dm-subagents/src/shared/formatters.js +98 -7
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
- package/extensions/dm-subagents/src/shared/model-info.js +62 -1
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
- package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
- package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
- package/extensions/dm-subagents/src/shared/settings.js +198 -11
- package/extensions/dm-subagents/src/shared/status-format.js +53 -1
- package/extensions/dm-subagents/src/shared/types.js +184 -6
- package/extensions/dm-subagents/src/shared/utils.js +462 -2
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
- package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
- package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
- package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
- package/extensions/dm-subagents/src/tui/render.js +1542 -4
- package/extensions/dm-usage/index.js +1294 -9
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
- package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
- package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
- package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
- package/extensions/greedysearch-dm/bin/search.mjs +620 -540
- package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
- package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
- package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
- package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
- package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
- package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
- package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
- package/extensions/greedysearch-dm/index.js +123 -23
- package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
- package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
- package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
- package/extensions/greedysearch-dm/src/github.mjs +222 -7
- package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
- package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
- package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
- package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
- package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
- package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
- package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
- package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
- package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
- package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
- package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
- package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
- package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
- package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
- package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
- package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
- package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
- package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
- package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
- package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
- package/package.json +1 -1
|
@@ -1,6 +1,614 @@
|
|
|
1
|
-
import{execSync
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { getAgentDir, getProjectConfigDir } from "../shared/utils.js";
|
|
6
|
+
const skillCache = new Map;
|
|
7
|
+
const MAX_CACHE_SIZE = 50;
|
|
8
|
+
let loadSkillsCache = null;
|
|
9
|
+
const LOAD_SKILLS_CACHE_TTL_MS = 5000;
|
|
10
|
+
const SUBAGENT_ORCHESTRATION_SKILL = "dm-subagents";
|
|
11
|
+
const SOURCE_PRIORITY = {
|
|
12
|
+
project: 700,
|
|
13
|
+
"project-settings": 650,
|
|
14
|
+
"project-package": 600,
|
|
15
|
+
user: 300,
|
|
16
|
+
"user-settings": 250,
|
|
17
|
+
"user-package": 200,
|
|
18
|
+
extension: 150,
|
|
19
|
+
builtin: 100,
|
|
20
|
+
unknown: 0
|
|
21
|
+
};
|
|
22
|
+
function stripSkillFrontmatter(content) {
|
|
23
|
+
const normalized = content.replace(/\r\n/g, `
|
|
24
|
+
`);
|
|
25
|
+
if (!normalized.startsWith("---"))
|
|
26
|
+
return normalized;
|
|
27
|
+
const endIndex = normalized.indexOf(`
|
|
28
|
+
---`, 3);
|
|
29
|
+
if (endIndex === -1)
|
|
30
|
+
return normalized;
|
|
31
|
+
return normalized.slice(endIndex + 4).trim();
|
|
32
|
+
}
|
|
33
|
+
function isWithinPath(filePath, dir) {
|
|
34
|
+
const relative = path.relative(dir, filePath);
|
|
35
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
36
|
+
}
|
|
37
|
+
function readOptionalJsonFile(filePath, label) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
|
|
42
|
+
if (code === "ENOENT")
|
|
43
|
+
return null;
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
throw new Error(`Failed to read ${label} '${filePath}': ${message}`, {
|
|
46
|
+
cause: error instanceof Error ? error : undefined
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function readJsonFileBestEffort(filePath) {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function extractSkillPathsFromPackageRoot(packageRoot, source, bestEffort = false) {
|
|
58
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
59
|
+
const pkg = bestEffort ? readJsonFileBestEffort(packageJsonPath) : readOptionalJsonFile(packageJsonPath, "package manifest");
|
|
60
|
+
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg))
|
|
61
|
+
return [];
|
|
62
|
+
const pi = pkg.pi;
|
|
63
|
+
if (!pi || typeof pi !== "object" || Array.isArray(pi))
|
|
64
|
+
return [];
|
|
65
|
+
const skills = pi.skills;
|
|
66
|
+
if (!Array.isArray(skills))
|
|
67
|
+
return [];
|
|
68
|
+
return skills.filter((entry) => typeof entry === "string").map((entry) => ({ path: path.resolve(packageRoot, entry), source }));
|
|
69
|
+
}
|
|
70
|
+
let cachedGlobalNpmRoot = null;
|
|
71
|
+
function getGlobalNpmRoot() {
|
|
72
|
+
if (cachedGlobalNpmRoot !== null)
|
|
73
|
+
return cachedGlobalNpmRoot;
|
|
74
|
+
try {
|
|
75
|
+
cachedGlobalNpmRoot = execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
76
|
+
return cachedGlobalNpmRoot;
|
|
77
|
+
} catch {
|
|
78
|
+
cachedGlobalNpmRoot = "";
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function collectInstalledPackageSkillPaths(cwd, agentDir) {
|
|
83
|
+
const projectConfigDir = getProjectConfigDir(cwd);
|
|
84
|
+
const dirs = [
|
|
85
|
+
{ path: path.join(projectConfigDir, "npm", "node_modules"), source: "project-package" },
|
|
86
|
+
{ path: path.join(agentDir, "npm", "node_modules"), source: "user-package" }
|
|
87
|
+
];
|
|
88
|
+
const globalRoot = getGlobalNpmRoot();
|
|
89
|
+
if (globalRoot) {
|
|
90
|
+
dirs.push({ path: globalRoot, source: "user-package" });
|
|
91
|
+
}
|
|
92
|
+
const results = [];
|
|
93
|
+
for (const dir of dirs) {
|
|
94
|
+
if (!fs.existsSync(dir.path))
|
|
95
|
+
continue;
|
|
96
|
+
let entries;
|
|
97
|
+
try {
|
|
98
|
+
entries = fs.readdirSync(dir.path, { withFileTypes: true });
|
|
99
|
+
} catch {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
if (entry.name.startsWith("."))
|
|
104
|
+
continue;
|
|
105
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
106
|
+
continue;
|
|
107
|
+
if (entry.name.startsWith("@")) {
|
|
108
|
+
const scopeDir = path.join(dir.path, entry.name);
|
|
109
|
+
let scopeEntries;
|
|
110
|
+
try {
|
|
111
|
+
scopeEntries = fs.readdirSync(scopeDir, { withFileTypes: true });
|
|
112
|
+
} catch {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const scopeEntry of scopeEntries) {
|
|
116
|
+
if (scopeEntry.name.startsWith("."))
|
|
117
|
+
continue;
|
|
118
|
+
if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink())
|
|
119
|
+
continue;
|
|
120
|
+
const pkgRoot = path.join(scopeDir, scopeEntry.name);
|
|
121
|
+
results.push(...extractSkillPathsFromPackageRoot(pkgRoot, dir.source, true));
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const pkgRoot = path.join(dir.path, entry.name);
|
|
126
|
+
results.push(...extractSkillPathsFromPackageRoot(pkgRoot, dir.source, true));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
130
|
+
}
|
|
131
|
+
function collectSettingsSkillPaths(cwd, agentDir) {
|
|
132
|
+
const results = [];
|
|
133
|
+
const projectConfigDir = getProjectConfigDir(cwd);
|
|
134
|
+
const settingsFiles = [
|
|
135
|
+
{ file: path.join(projectConfigDir, "settings.json"), base: projectConfigDir, source: "project-settings" },
|
|
136
|
+
{ file: path.join(agentDir, "settings.json"), base: agentDir, source: "user-settings" }
|
|
137
|
+
];
|
|
138
|
+
for (const { file, base, source } of settingsFiles) {
|
|
139
|
+
const settings = readOptionalJsonFile(file, "skills settings file");
|
|
140
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
141
|
+
continue;
|
|
142
|
+
const skills = settings.skills;
|
|
143
|
+
if (!Array.isArray(skills))
|
|
144
|
+
continue;
|
|
145
|
+
for (const entry of skills) {
|
|
146
|
+
if (typeof entry !== "string")
|
|
147
|
+
continue;
|
|
148
|
+
let resolved = entry;
|
|
149
|
+
if (resolved.startsWith("~/")) {
|
|
150
|
+
resolved = path.join(os.homedir(), resolved.slice(2));
|
|
151
|
+
} else if (!path.isAbsolute(resolved)) {
|
|
152
|
+
resolved = path.resolve(base, resolved);
|
|
153
|
+
}
|
|
154
|
+
results.push({ path: resolved, source });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
function isSafePackagePath(value) {
|
|
160
|
+
return value.length > 0 && !path.isAbsolute(value) && value.split(/[\\/]/).every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
161
|
+
}
|
|
162
|
+
function parseNpmPackageName(source) {
|
|
163
|
+
const spec = source.slice(4).trim();
|
|
164
|
+
if (!spec)
|
|
165
|
+
return;
|
|
166
|
+
const match = spec.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@(.+))?$/);
|
|
167
|
+
const packageName = match?.[1] ?? spec;
|
|
168
|
+
return isSafePackagePath(packageName) ? packageName : undefined;
|
|
169
|
+
}
|
|
170
|
+
function stripGitRef(repoPath) {
|
|
171
|
+
const atIndex = repoPath.indexOf("@");
|
|
172
|
+
const hashIndex = repoPath.indexOf("#");
|
|
173
|
+
const refIndex = [atIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
174
|
+
return refIndex === undefined ? repoPath : repoPath.slice(0, refIndex);
|
|
175
|
+
}
|
|
176
|
+
function parseGitPackagePath(source) {
|
|
177
|
+
const spec = source.slice(4).trim();
|
|
178
|
+
if (!spec)
|
|
179
|
+
return;
|
|
180
|
+
let host = "";
|
|
181
|
+
let repoPath = "";
|
|
182
|
+
const scpLike = spec.match(/^git@([^:]+):(.+)$/);
|
|
183
|
+
if (scpLike) {
|
|
184
|
+
host = scpLike[1] ?? "";
|
|
185
|
+
repoPath = scpLike[2] ?? "";
|
|
186
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(spec)) {
|
|
187
|
+
try {
|
|
188
|
+
const url = new URL(spec);
|
|
189
|
+
host = url.hostname;
|
|
190
|
+
repoPath = url.pathname.replace(/^\/+/, "");
|
|
191
|
+
} catch {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
const slashIndex = spec.indexOf("/");
|
|
196
|
+
if (slashIndex < 0)
|
|
197
|
+
return;
|
|
198
|
+
host = spec.slice(0, slashIndex);
|
|
199
|
+
repoPath = spec.slice(slashIndex + 1);
|
|
200
|
+
}
|
|
201
|
+
const normalizedPath = stripGitRef(repoPath).replace(/\.git$/, "").replace(/^\/+/, "");
|
|
202
|
+
if (!host || !isSafePackagePath(host) || !isSafePackagePath(normalizedPath) || normalizedPath.split(/[\\/]/).length < 2) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
return { host, repoPath: normalizedPath };
|
|
206
|
+
}
|
|
207
|
+
function resolveSettingsPackageRoot(source, baseDir) {
|
|
208
|
+
const trimmed = source.trim();
|
|
209
|
+
if (!trimmed)
|
|
210
|
+
return;
|
|
211
|
+
if (trimmed.startsWith("git:")) {
|
|
212
|
+
const parsed = parseGitPackagePath(trimmed);
|
|
213
|
+
return parsed ? path.join(baseDir, "git", parsed.host, parsed.repoPath) : undefined;
|
|
214
|
+
}
|
|
215
|
+
if (trimmed.startsWith("npm:")) {
|
|
216
|
+
const packageName = parseNpmPackageName(trimmed);
|
|
217
|
+
return packageName ? path.join(baseDir, "npm", "node_modules", packageName) : undefined;
|
|
218
|
+
}
|
|
219
|
+
const normalized = trimmed.startsWith("file:") ? trimmed.slice(5) : trimmed;
|
|
220
|
+
if (normalized === "~")
|
|
221
|
+
return os.homedir();
|
|
222
|
+
if (normalized.startsWith("~/"))
|
|
223
|
+
return path.join(os.homedir(), normalized.slice(2));
|
|
224
|
+
if (path.isAbsolute(normalized))
|
|
225
|
+
return normalized;
|
|
226
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith("./") || normalized.startsWith("../")) {
|
|
227
|
+
return path.resolve(baseDir, normalized);
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
function collectSettingsPackageSkillPaths(cwd, agentDir) {
|
|
232
|
+
const projectConfigDir = getProjectConfigDir(cwd);
|
|
233
|
+
const settingsFiles = [
|
|
234
|
+
{ file: path.join(projectConfigDir, "settings.json"), base: projectConfigDir, source: "project-package" },
|
|
235
|
+
{ file: path.join(agentDir, "settings.json"), base: agentDir, source: "user-package" }
|
|
236
|
+
];
|
|
237
|
+
const results = [];
|
|
238
|
+
for (const { file, base, source } of settingsFiles) {
|
|
239
|
+
const settings = readOptionalJsonFile(file, "skills settings file");
|
|
240
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
241
|
+
continue;
|
|
242
|
+
const packages = settings.packages;
|
|
243
|
+
if (!Array.isArray(packages))
|
|
244
|
+
continue;
|
|
245
|
+
for (const entry of packages) {
|
|
246
|
+
const packageSource = typeof entry === "string" ? entry : typeof entry === "object" && entry !== null && typeof entry.source === "string" ? entry.source : undefined;
|
|
247
|
+
if (!packageSource)
|
|
248
|
+
continue;
|
|
249
|
+
const packageRoot = resolveSettingsPackageRoot(packageSource, base);
|
|
250
|
+
if (!packageRoot)
|
|
251
|
+
continue;
|
|
252
|
+
results.push(...extractSkillPathsFromPackageRoot(packageRoot, source));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return results;
|
|
256
|
+
}
|
|
257
|
+
function buildSkillPaths(cwd, agentDir) {
|
|
258
|
+
const projectConfigDir = getProjectConfigDir(cwd);
|
|
259
|
+
const skillPaths = [
|
|
260
|
+
{ path: path.join(projectConfigDir, "skills"), source: "project" },
|
|
261
|
+
{ path: path.join(cwd, ".agents", "skills"), source: "project" },
|
|
262
|
+
{ path: path.join(agentDir, "skills"), source: "user" },
|
|
263
|
+
{ path: path.join(os.homedir(), ".agents", "skills"), source: "user" },
|
|
264
|
+
...collectInstalledPackageSkillPaths(cwd, agentDir),
|
|
265
|
+
...collectSettingsPackageSkillPaths(cwd, agentDir),
|
|
266
|
+
...extractSkillPathsFromPackageRoot(cwd, "project-package"),
|
|
267
|
+
...collectSettingsSkillPaths(cwd, agentDir)
|
|
268
|
+
];
|
|
269
|
+
const deduped = new Map;
|
|
270
|
+
for (const entry of skillPaths) {
|
|
271
|
+
const resolvedPath = path.resolve(entry.path);
|
|
272
|
+
const existing = deduped.get(resolvedPath);
|
|
273
|
+
if (!existing || (SOURCE_PRIORITY[entry.source] ?? 0) > (SOURCE_PRIORITY[existing.source] ?? 0)) {
|
|
274
|
+
deduped.set(resolvedPath, { path: resolvedPath, source: entry.source });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return [...deduped.values()];
|
|
278
|
+
}
|
|
279
|
+
function inferSkillSource(filePath, cwd, agentDir, sourceHint) {
|
|
280
|
+
if (sourceHint)
|
|
281
|
+
return sourceHint;
|
|
282
|
+
const projectConfigRoot = path.resolve(getProjectConfigDir(cwd));
|
|
283
|
+
const projectSkillsRoot = path.resolve(projectConfigRoot, "skills");
|
|
284
|
+
const projectPackagesRoot = path.resolve(projectConfigRoot, "npm", "node_modules");
|
|
285
|
+
const projectAgentsRoot = path.resolve(cwd, ".agents");
|
|
286
|
+
const userSkillsRoot = path.resolve(agentDir, "skills");
|
|
287
|
+
const userPackagesRoot = path.resolve(agentDir, "npm", "node_modules");
|
|
288
|
+
const userAgentRoot = path.resolve(agentDir);
|
|
289
|
+
const userAgentsRoot = path.resolve(os.homedir(), ".agents");
|
|
290
|
+
if (isWithinPath(filePath, projectPackagesRoot))
|
|
291
|
+
return "project-package";
|
|
292
|
+
if (isWithinPath(filePath, projectSkillsRoot) || isWithinPath(filePath, projectAgentsRoot))
|
|
293
|
+
return "project";
|
|
294
|
+
if (isWithinPath(filePath, projectConfigRoot))
|
|
295
|
+
return "project-settings";
|
|
296
|
+
if (isWithinPath(filePath, userPackagesRoot))
|
|
297
|
+
return "user-package";
|
|
298
|
+
if (isWithinPath(filePath, userSkillsRoot) || isWithinPath(filePath, userAgentsRoot))
|
|
299
|
+
return "user";
|
|
300
|
+
if (isWithinPath(filePath, userAgentRoot))
|
|
301
|
+
return "user-settings";
|
|
302
|
+
const globalRoot = getGlobalNpmRoot();
|
|
303
|
+
if (globalRoot && isWithinPath(filePath, globalRoot))
|
|
304
|
+
return "user-package";
|
|
305
|
+
return "unknown";
|
|
306
|
+
}
|
|
307
|
+
function chooseHigherPrioritySkill(existing, candidate) {
|
|
308
|
+
if (!existing)
|
|
309
|
+
return candidate;
|
|
310
|
+
const existingPriority = SOURCE_PRIORITY[existing.source] ?? 0;
|
|
311
|
+
const candidatePriority = SOURCE_PRIORITY[candidate.source] ?? 0;
|
|
312
|
+
if (candidatePriority > existingPriority)
|
|
313
|
+
return candidate;
|
|
314
|
+
if (candidatePriority < existingPriority)
|
|
315
|
+
return existing;
|
|
316
|
+
return candidate.order < existing.order ? candidate : existing;
|
|
317
|
+
}
|
|
318
|
+
function maybeReadSkillDescription(filePath) {
|
|
319
|
+
try {
|
|
320
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
321
|
+
const normalized = content.replace(/\r\n/g, `
|
|
322
|
+
`);
|
|
323
|
+
if (!normalized.startsWith("---"))
|
|
324
|
+
return;
|
|
325
|
+
const endIndex = normalized.indexOf(`
|
|
326
|
+
---`, 3);
|
|
327
|
+
if (endIndex === -1)
|
|
328
|
+
return;
|
|
329
|
+
const frontmatter = normalized.slice(3, endIndex).trim();
|
|
330
|
+
const match = frontmatter.match(/^description:\s*(.+)$/m);
|
|
331
|
+
if (!match)
|
|
332
|
+
return;
|
|
333
|
+
return match[1]?.trim().replace(/^['\"]|['\"]$/g, "");
|
|
334
|
+
} catch {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function collectFilesystemSkills(cwd, agentDir, skillPaths) {
|
|
339
|
+
const entries = [];
|
|
340
|
+
const seen = new Map;
|
|
341
|
+
const visitedDirectories = new Map;
|
|
342
|
+
let order = 0;
|
|
343
|
+
const pushEntry = (name, filePath, sourceHint) => {
|
|
344
|
+
const resolvedFile = path.resolve(filePath);
|
|
345
|
+
if (!fs.existsSync(resolvedFile))
|
|
346
|
+
return;
|
|
347
|
+
const source = inferSkillSource(resolvedFile, cwd, agentDir, sourceHint);
|
|
348
|
+
const existingIndex = seen.get(resolvedFile);
|
|
349
|
+
if (existingIndex !== undefined) {
|
|
350
|
+
const existing = entries[existingIndex];
|
|
351
|
+
if (existing && (SOURCE_PRIORITY[source] ?? 0) > (SOURCE_PRIORITY[existing.source] ?? 0)) {
|
|
352
|
+
entries[existingIndex] = {
|
|
353
|
+
...existing,
|
|
354
|
+
name,
|
|
355
|
+
source,
|
|
356
|
+
description: maybeReadSkillDescription(resolvedFile)
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
seen.set(resolvedFile, entries.length);
|
|
362
|
+
entries.push({
|
|
363
|
+
name,
|
|
364
|
+
filePath: resolvedFile,
|
|
365
|
+
source,
|
|
366
|
+
description: maybeReadSkillDescription(resolvedFile),
|
|
367
|
+
order: order++
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
const shouldSkipDirectory = (name) => name.startsWith(".") || name === "node_modules";
|
|
371
|
+
const markDirectoryVisited = (dirPath, sourceHint) => {
|
|
372
|
+
let resolvedDir;
|
|
373
|
+
try {
|
|
374
|
+
resolvedDir = fs.realpathSync(dirPath);
|
|
375
|
+
} catch {
|
|
376
|
+
resolvedDir = path.resolve(dirPath);
|
|
377
|
+
}
|
|
378
|
+
const priority = sourceHint ? SOURCE_PRIORITY[sourceHint] ?? 0 : SOURCE_PRIORITY.unknown;
|
|
379
|
+
const previousPriority = visitedDirectories.get(resolvedDir);
|
|
380
|
+
if (previousPriority !== undefined && previousPriority >= priority)
|
|
381
|
+
return false;
|
|
382
|
+
visitedDirectories.set(resolvedDir, priority);
|
|
383
|
+
return true;
|
|
384
|
+
};
|
|
385
|
+
const walkSkillDirectories = (dirPath, sourceHint) => {
|
|
386
|
+
if (!markDirectoryVisited(dirPath, sourceHint))
|
|
387
|
+
return;
|
|
388
|
+
const skillFile = path.join(dirPath, "SKILL.md");
|
|
389
|
+
if (fs.existsSync(skillFile)) {
|
|
390
|
+
pushEntry(path.basename(dirPath), skillFile, sourceHint);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
let entriesInDir;
|
|
394
|
+
try {
|
|
395
|
+
entriesInDir = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
396
|
+
} catch {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
for (const entry of entriesInDir) {
|
|
400
|
+
if (shouldSkipDirectory(entry.name))
|
|
401
|
+
continue;
|
|
402
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
403
|
+
continue;
|
|
404
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
405
|
+
let stat;
|
|
406
|
+
try {
|
|
407
|
+
stat = fs.statSync(entryPath);
|
|
408
|
+
} catch {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (stat.isDirectory()) {
|
|
412
|
+
walkSkillDirectories(entryPath, sourceHint);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
for (const skillPath of skillPaths) {
|
|
417
|
+
if (!fs.existsSync(skillPath.path))
|
|
418
|
+
continue;
|
|
419
|
+
let stat;
|
|
420
|
+
try {
|
|
421
|
+
stat = fs.statSync(skillPath.path);
|
|
422
|
+
} catch {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (stat.isFile()) {
|
|
426
|
+
const fileName = path.basename(skillPath.path);
|
|
427
|
+
if (!fileName.toLowerCase().endsWith(".md"))
|
|
428
|
+
continue;
|
|
429
|
+
const skillName = fileName.toLowerCase() === "skill.md" ? path.basename(path.dirname(skillPath.path)) : path.basename(fileName, path.extname(fileName));
|
|
430
|
+
pushEntry(skillName, skillPath.path, skillPath.source);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (!stat.isDirectory())
|
|
434
|
+
continue;
|
|
435
|
+
const rootSkillFile = path.join(skillPath.path, "SKILL.md");
|
|
436
|
+
if (fs.existsSync(rootSkillFile)) {
|
|
437
|
+
pushEntry(path.basename(skillPath.path), rootSkillFile, skillPath.source);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
markDirectoryVisited(skillPath.path, skillPath.source);
|
|
441
|
+
let childEntries;
|
|
442
|
+
try {
|
|
443
|
+
childEntries = fs.readdirSync(skillPath.path, { withFileTypes: true });
|
|
444
|
+
} catch {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
for (const child of childEntries) {
|
|
448
|
+
if (child.name.startsWith("."))
|
|
449
|
+
continue;
|
|
450
|
+
const childPath = path.join(skillPath.path, child.name);
|
|
451
|
+
if (child.isDirectory() || child.isSymbolicLink()) {
|
|
452
|
+
if (shouldSkipDirectory(child.name))
|
|
453
|
+
continue;
|
|
454
|
+
let childStat;
|
|
455
|
+
try {
|
|
456
|
+
childStat = fs.statSync(childPath);
|
|
457
|
+
} catch {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (childStat.isDirectory())
|
|
461
|
+
walkSkillDirectories(childPath, skillPath.source);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (child.isFile() && child.name.toLowerCase().endsWith(".md")) {
|
|
465
|
+
pushEntry(path.basename(child.name, path.extname(child.name)), childPath, skillPath.source);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return entries;
|
|
470
|
+
}
|
|
471
|
+
function getCachedSkills(cwd) {
|
|
472
|
+
const now = Date.now();
|
|
473
|
+
const agentDir = getAgentDir();
|
|
474
|
+
if (loadSkillsCache && loadSkillsCache.cwd === cwd && loadSkillsCache.agentDir === agentDir && now - loadSkillsCache.timestamp < LOAD_SKILLS_CACHE_TTL_MS) {
|
|
475
|
+
return loadSkillsCache.skills;
|
|
476
|
+
}
|
|
477
|
+
const skillPaths = buildSkillPaths(cwd, agentDir);
|
|
478
|
+
const loaded = collectFilesystemSkills(cwd, agentDir, skillPaths);
|
|
479
|
+
const dedupedByName = new Map;
|
|
480
|
+
for (const entry of loaded) {
|
|
481
|
+
const current = dedupedByName.get(entry.name);
|
|
482
|
+
dedupedByName.set(entry.name, chooseHigherPrioritySkill(current, entry));
|
|
483
|
+
}
|
|
484
|
+
const skills = [...dedupedByName.values()].sort((a, b) => a.order - b.order);
|
|
485
|
+
loadSkillsCache = { cwd, agentDir, skills, timestamp: now };
|
|
486
|
+
return skills;
|
|
487
|
+
}
|
|
488
|
+
export function resolveSkillPath(skillName, cwd) {
|
|
489
|
+
const skills = getCachedSkills(cwd);
|
|
490
|
+
const skill = skills.find((s) => s.name === skillName);
|
|
491
|
+
if (!skill)
|
|
492
|
+
return;
|
|
493
|
+
return { path: skill.filePath, source: skill.source };
|
|
494
|
+
}
|
|
495
|
+
function readSkill(skillName, skillPath, source) {
|
|
496
|
+
try {
|
|
497
|
+
const stat = fs.statSync(skillPath);
|
|
498
|
+
const cached = skillCache.get(skillPath);
|
|
499
|
+
if (cached && cached.mtime === stat.mtimeMs) {
|
|
500
|
+
return cached.skill;
|
|
501
|
+
}
|
|
502
|
+
const raw = fs.readFileSync(skillPath, "utf-8");
|
|
503
|
+
const content = stripSkillFrontmatter(raw);
|
|
504
|
+
const description = maybeReadSkillDescription(skillPath);
|
|
505
|
+
const skill = {
|
|
506
|
+
name: skillName,
|
|
507
|
+
path: skillPath,
|
|
508
|
+
content,
|
|
509
|
+
description,
|
|
510
|
+
source
|
|
511
|
+
};
|
|
512
|
+
skillCache.set(skillPath, { mtime: stat.mtimeMs, skill });
|
|
513
|
+
if (skillCache.size > MAX_CACHE_SIZE) {
|
|
514
|
+
const firstKey = skillCache.keys().next().value;
|
|
515
|
+
if (firstKey)
|
|
516
|
+
skillCache.delete(firstKey);
|
|
517
|
+
}
|
|
518
|
+
return skill;
|
|
519
|
+
} catch {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
export function resolveSkills(skillNames, cwd) {
|
|
524
|
+
const resolved = [];
|
|
525
|
+
const missing = [];
|
|
526
|
+
for (const name of skillNames) {
|
|
527
|
+
const trimmed = name.trim();
|
|
528
|
+
if (!trimmed)
|
|
529
|
+
continue;
|
|
530
|
+
if (trimmed === SUBAGENT_ORCHESTRATION_SKILL) {
|
|
531
|
+
missing.push(trimmed);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
const location = resolveSkillPath(trimmed, cwd);
|
|
535
|
+
if (!location) {
|
|
536
|
+
missing.push(trimmed);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const skill = readSkill(trimmed, location.path, location.source);
|
|
540
|
+
if (skill) {
|
|
541
|
+
resolved.push(skill);
|
|
542
|
+
} else {
|
|
543
|
+
missing.push(trimmed);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return { resolved, missing };
|
|
547
|
+
}
|
|
548
|
+
export function resolveSkillsWithFallback(skillNames, primaryCwd, fallbackCwd) {
|
|
549
|
+
const primary = resolveSkills(skillNames, primaryCwd);
|
|
550
|
+
if (!fallbackCwd || primary.missing.length === 0)
|
|
551
|
+
return primary;
|
|
552
|
+
if (path.resolve(primaryCwd) === path.resolve(fallbackCwd))
|
|
553
|
+
return primary;
|
|
554
|
+
const fallback = resolveSkills(primary.missing, fallbackCwd);
|
|
555
|
+
return {
|
|
556
|
+
resolved: [...primary.resolved, ...fallback.resolved],
|
|
557
|
+
missing: fallback.missing
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
export function buildSkillInjection(skills) {
|
|
561
|
+
if (skills.length === 0)
|
|
562
|
+
return "";
|
|
563
|
+
const lines = [
|
|
564
|
+
"The following configured skills are available to this subagent.",
|
|
565
|
+
"Use the read tool to load a skill's file when the task matches its description.",
|
|
566
|
+
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
|
|
567
|
+
"",
|
|
568
|
+
"<available_skills>"
|
|
569
|
+
];
|
|
570
|
+
for (const skill of skills) {
|
|
571
|
+
lines.push(" <skill>");
|
|
572
|
+
lines.push(` <name>${escapeXmlText(skill.name)}</name>`);
|
|
573
|
+
lines.push(` <description>${escapeXmlText(skill.description ?? "")}</description>`);
|
|
574
|
+
lines.push(` <location>${escapeXmlText(skill.path)}</location>`);
|
|
575
|
+
lines.push(" </skill>");
|
|
576
|
+
}
|
|
577
|
+
lines.push("</available_skills>");
|
|
578
|
+
return lines.join(`
|
|
579
|
+
`);
|
|
580
|
+
}
|
|
581
|
+
function escapeXmlText(value) {
|
|
582
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
583
|
+
}
|
|
584
|
+
export function normalizeSkillInput(input) {
|
|
585
|
+
if (input === false)
|
|
586
|
+
return false;
|
|
587
|
+
if (input === true || input === undefined)
|
|
588
|
+
return;
|
|
589
|
+
if (Array.isArray(input)) {
|
|
590
|
+
return [...new Set(input.map((s) => s.trim()).filter((s) => s.length > 0))];
|
|
591
|
+
}
|
|
592
|
+
const trimmed = input.trim();
|
|
593
|
+
if (trimmed.startsWith("[")) {
|
|
594
|
+
try {
|
|
595
|
+
const parsed = JSON.parse(trimmed);
|
|
596
|
+
if (Array.isArray(parsed)) {
|
|
597
|
+
return normalizeSkillInput(parsed);
|
|
598
|
+
}
|
|
599
|
+
} catch {}
|
|
600
|
+
}
|
|
601
|
+
return [...new Set(input.split(",").map((s) => s.trim()).filter((s) => s.length > 0))];
|
|
602
|
+
}
|
|
603
|
+
export function discoverAvailableSkills(cwd) {
|
|
604
|
+
const skills = getCachedSkills(cwd);
|
|
605
|
+
return skills.filter((s) => s.name !== SUBAGENT_ORCHESTRATION_SKILL).map((s) => ({
|
|
606
|
+
name: s.name,
|
|
607
|
+
source: s.source,
|
|
608
|
+
description: s.description
|
|
609
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
610
|
+
}
|
|
611
|
+
export function clearSkillCache() {
|
|
612
|
+
skillCache.clear();
|
|
613
|
+
loadSkillsCache = null;
|
|
614
|
+
}
|
|
@@ -1,2 +1,35 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getAgentDir } from "../shared/utils.js";
|
|
4
|
+
export function getConfigPath() {
|
|
5
|
+
return path.join(getAgentDir(), "extensions", "subagent", "config.json");
|
|
6
|
+
}
|
|
7
|
+
function readConfigForUpdate(configPath = getConfigPath()) {
|
|
8
|
+
if (!fs.existsSync(configPath))
|
|
9
|
+
return {};
|
|
10
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
11
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
12
|
+
throw new Error(`Subagent config at '${configPath}' must be a JSON object`);
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
export function saveConfig(config, configPath = getConfigPath()) {
|
|
17
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
18
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, "\t")}
|
|
19
|
+
`, "utf-8");
|
|
20
|
+
}
|
|
21
|
+
export function updateConfig(updater) {
|
|
22
|
+
const configPath = getConfigPath();
|
|
23
|
+
const next = updater(readConfigForUpdate(configPath));
|
|
24
|
+
saveConfig(next, configPath);
|
|
25
|
+
return next;
|
|
26
|
+
}
|
|
27
|
+
export function loadConfig() {
|
|
28
|
+
const configPath = getConfigPath();
|
|
29
|
+
try {
|
|
30
|
+
return readConfigForUpdate(configPath);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(`Failed to load subagent config from '${configPath}':`, error);
|
|
33
|
+
}
|
|
34
|
+
return {};
|
|
35
|
+
}
|