opencode-dynamic-skills 1.1.0 → 1.2.0
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/README.md +46 -18
- package/README.zh-CN.md +41 -18
- package/dist/api.d.ts +5 -4
- package/dist/commands/SlashCommand.d.ts +1 -0
- package/dist/config.d.ts +21 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +658 -499
- package/dist/lib/SkillFs.d.ts +1 -0
- package/dist/services/Notifier.d.ts +13 -0
- package/dist/services/Notifier.test.d.ts +1 -0
- package/dist/tools/SkillRecommender.d.ts +25 -8
- package/dist/types.d.ts +17 -19
- package/package.json +1 -1
- package/dist/lib/createPromptRenderer.d.ts +0 -52
- package/dist/lib/createPromptRenderer.test.d.ts +0 -9
- package/dist/lib/getModelFormat.d.ts +0 -35
- package/dist/lib/renderers/JsonPromptRenderer.d.ts +0 -8
- package/dist/lib/renderers/JsonPromptRenderer.test.d.ts +0 -10
- package/dist/lib/renderers/MdPromptRenderer.d.ts +0 -18
- package/dist/lib/renderers/MdPromptRenderer.test.d.ts +0 -11
- package/dist/services/MessageModelIdAccountant.d.ts +0 -22
package/dist/index.js
CHANGED
|
@@ -15889,7 +15889,7 @@ function toolName(skillPath) {
|
|
|
15889
15889
|
|
|
15890
15890
|
// src/lib/SkillFs.ts
|
|
15891
15891
|
import { join } from "path";
|
|
15892
|
-
import { existsSync } from "fs";
|
|
15892
|
+
import { existsSync, statSync } from "fs";
|
|
15893
15893
|
|
|
15894
15894
|
// node_modules/mime/dist/types/other.js
|
|
15895
15895
|
var types = {
|
|
@@ -17075,13 +17075,24 @@ var Mime_default = Mime;
|
|
|
17075
17075
|
var src_default = new Mime_default(standard_default, other_default)._freeze();
|
|
17076
17076
|
|
|
17077
17077
|
// src/lib/SkillFs.ts
|
|
17078
|
+
function isRegularFile(filePath) {
|
|
17079
|
+
try {
|
|
17080
|
+
return statSync(filePath).isFile();
|
|
17081
|
+
} catch {
|
|
17082
|
+
return false;
|
|
17083
|
+
}
|
|
17084
|
+
}
|
|
17078
17085
|
var readSkillFile = async (path) => {
|
|
17079
17086
|
const file2 = Bun.file(path);
|
|
17080
17087
|
return file2.text();
|
|
17081
17088
|
};
|
|
17082
17089
|
var listSkillFiles = (skillPath, subdirectory) => {
|
|
17083
17090
|
const glob = new Bun.Glob(join(subdirectory, "**", "*"));
|
|
17084
|
-
return Array.from(glob.scanSync({ cwd: skillPath, absolute: true }));
|
|
17091
|
+
return Array.from(glob.scanSync({ cwd: skillPath, absolute: true })).filter(isRegularFile);
|
|
17092
|
+
};
|
|
17093
|
+
var listAllSkillFiles = (skillPath) => {
|
|
17094
|
+
const glob = new Bun.Glob("**/*");
|
|
17095
|
+
return Array.from(glob.scanSync({ cwd: skillPath, absolute: true })).filter(isRegularFile).filter((filePath) => !filePath.endsWith("/SKILL.md") && !filePath.endsWith("\\SKILL.md"));
|
|
17085
17096
|
};
|
|
17086
17097
|
var findSkillPaths = async (basePath) => {
|
|
17087
17098
|
const glob = new Bun.Glob("**/SKILL.md");
|
|
@@ -17682,6 +17693,7 @@ async function parseSkill(args) {
|
|
|
17682
17693
|
const scriptPaths = listSkillFiles(skillFullPath, "scripts");
|
|
17683
17694
|
const referencePaths = listSkillFiles(skillFullPath, "references");
|
|
17684
17695
|
const assetPaths = listSkillFiles(skillFullPath, "assets");
|
|
17696
|
+
const indexedFilePaths = listAllSkillFiles(skillFullPath);
|
|
17685
17697
|
return {
|
|
17686
17698
|
allowedTools: frontmatter.data["allowed-tools"],
|
|
17687
17699
|
compatibility: frontmatter.data.compatibility,
|
|
@@ -17693,6 +17705,7 @@ async function parseSkill(args) {
|
|
|
17693
17705
|
metadata: frontmatter.data.metadata,
|
|
17694
17706
|
name: frontmatter.data.name,
|
|
17695
17707
|
path: args.skillPath,
|
|
17708
|
+
files: createSkillResourceMap(skillFullPath, indexedFilePaths),
|
|
17696
17709
|
scripts: createSkillResourceMap(skillFullPath, scriptPaths),
|
|
17697
17710
|
references: createSkillResourceMap(skillFullPath, referencePaths),
|
|
17698
17711
|
assets: createSkillResourceMap(skillFullPath, assetPaths)
|
|
@@ -17735,6 +17748,9 @@ function createSkillFinder(provider) {
|
|
|
17735
17748
|
// src/tools/SkillRecommender.ts
|
|
17736
17749
|
var DEFAULT_RECOMMENDATION_LIMIT = 5;
|
|
17737
17750
|
var MIN_TERM_LENGTH = 3;
|
|
17751
|
+
function normalizeSkillSelector(selector) {
|
|
17752
|
+
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
17753
|
+
}
|
|
17738
17754
|
function getRecommendationReason(nameMatches, descMatches) {
|
|
17739
17755
|
const reasons = [];
|
|
17740
17756
|
if (nameMatches > 0) {
|
|
@@ -17748,46 +17764,190 @@ function getRecommendationReason(nameMatches, descMatches) {
|
|
|
17748
17764
|
}
|
|
17749
17765
|
return reasons.join("; ");
|
|
17750
17766
|
}
|
|
17751
|
-
function
|
|
17767
|
+
function formatResults(provider, task, recommendations) {
|
|
17768
|
+
return {
|
|
17769
|
+
mode: "recommend",
|
|
17770
|
+
query: task,
|
|
17771
|
+
skills: recommendations.map(({ name, description }) => ({
|
|
17772
|
+
name,
|
|
17773
|
+
description
|
|
17774
|
+
})),
|
|
17775
|
+
recommendations,
|
|
17776
|
+
guidance: recommendations.length > 0 ? "Use skill_use to load one of the recommended skills if you want to apply it." : "No strong skill recommendation was found. Try skill_find with a narrower query.",
|
|
17777
|
+
summary: {
|
|
17778
|
+
total: provider.controller.skills.length,
|
|
17779
|
+
matches: recommendations.length,
|
|
17780
|
+
feedback: recommendations.length > 0 ? `Recommended ${recommendations.length} skill(s) for: **${task}**` : `No strong recommendations found for: **${task}**`
|
|
17781
|
+
},
|
|
17782
|
+
debug: provider.debug
|
|
17783
|
+
};
|
|
17784
|
+
}
|
|
17785
|
+
function getHeuristicRecommendations(provider, task, limit) {
|
|
17786
|
+
const parsedQuery = parseQuery(task);
|
|
17787
|
+
const rankingTerms = parsedQuery.include.filter((term) => term.length >= MIN_TERM_LENGTH);
|
|
17788
|
+
const rankedMatches = provider.controller.skills.map((skill) => rankSkill(skill, rankingTerms)).filter((ranking) => ranking.totalScore > 0).sort((left, right) => {
|
|
17789
|
+
if (right.totalScore !== left.totalScore) {
|
|
17790
|
+
return right.totalScore - left.totalScore;
|
|
17791
|
+
}
|
|
17792
|
+
if (right.nameMatches !== left.nameMatches) {
|
|
17793
|
+
return right.nameMatches - left.nameMatches;
|
|
17794
|
+
}
|
|
17795
|
+
return left.skill.name.localeCompare(right.skill.name);
|
|
17796
|
+
});
|
|
17797
|
+
return rankedMatches.slice(0, limit).map((skill) => ({
|
|
17798
|
+
name: skill.skill.toolName,
|
|
17799
|
+
description: skill.skill.description,
|
|
17800
|
+
score: skill.totalScore,
|
|
17801
|
+
reason: getRecommendationReason(skill.nameMatches, skill.descMatches)
|
|
17802
|
+
}));
|
|
17803
|
+
}
|
|
17804
|
+
function buildModelRecommendationPrompt(args) {
|
|
17805
|
+
const catalog = args.skills.map((skill) => `- ${skill.toolName}: ${skill.description} (directory=${skill.name}, aliases=/${skill.name})`).join(`
|
|
17806
|
+
`);
|
|
17807
|
+
return [
|
|
17808
|
+
`User request: ${args.task}`,
|
|
17809
|
+
`Maximum recommendations: ${args.limit}`,
|
|
17810
|
+
"Available skills:",
|
|
17811
|
+
catalog,
|
|
17812
|
+
"",
|
|
17813
|
+
"Return strict JSON only. Do not include markdown fences unless required by the model."
|
|
17814
|
+
].join(`
|
|
17815
|
+
`);
|
|
17816
|
+
}
|
|
17817
|
+
function extractJsonPayload(text) {
|
|
17818
|
+
const fencedMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
17819
|
+
if (fencedMatch?.[1]) {
|
|
17820
|
+
return fencedMatch[1].trim();
|
|
17821
|
+
}
|
|
17822
|
+
const firstBrace = text.indexOf("{");
|
|
17823
|
+
const lastBrace = text.lastIndexOf("}");
|
|
17824
|
+
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
17825
|
+
return text.slice(firstBrace, lastBrace + 1);
|
|
17826
|
+
}
|
|
17827
|
+
throw new Error("Model response did not contain a JSON object");
|
|
17828
|
+
}
|
|
17829
|
+
function parseModelRecommendations(text) {
|
|
17830
|
+
const payload = JSON.parse(extractJsonPayload(text));
|
|
17831
|
+
if (!Array.isArray(payload.recommendations)) {
|
|
17832
|
+
throw new Error("Model response is missing a recommendations array");
|
|
17833
|
+
}
|
|
17834
|
+
return payload.recommendations.filter((recommendation) => recommendation.name?.trim()).map((recommendation) => ({
|
|
17835
|
+
name: recommendation.name.trim(),
|
|
17836
|
+
reason: recommendation.reason?.trim()
|
|
17837
|
+
}));
|
|
17838
|
+
}
|
|
17839
|
+
function getAssistantText(messages) {
|
|
17840
|
+
const assistantMessage = [...messages].reverse().find((message) => message.info.role === "assistant");
|
|
17841
|
+
if (!assistantMessage) {
|
|
17842
|
+
throw new Error("Model session did not return an assistant message");
|
|
17843
|
+
}
|
|
17844
|
+
const text = assistantMessage.parts.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
|
|
17845
|
+
`).trim();
|
|
17846
|
+
if (!text) {
|
|
17847
|
+
throw new Error("Model session did not return assistant text");
|
|
17848
|
+
}
|
|
17849
|
+
return text;
|
|
17850
|
+
}
|
|
17851
|
+
function mapModelRecommendationsToSkills(skills, recommendations, limit) {
|
|
17852
|
+
const skillByName = new Map;
|
|
17853
|
+
for (const skill of skills) {
|
|
17854
|
+
skillByName.set(normalizeSkillSelector(skill.toolName), skill);
|
|
17855
|
+
skillByName.set(normalizeSkillSelector(skill.name), skill);
|
|
17856
|
+
}
|
|
17857
|
+
const results = [];
|
|
17858
|
+
const seen = new Set;
|
|
17859
|
+
for (const [index, recommendation] of recommendations.entries()) {
|
|
17860
|
+
const skill = skillByName.get(normalizeSkillSelector(recommendation.name));
|
|
17861
|
+
if (!skill || seen.has(skill.toolName)) {
|
|
17862
|
+
continue;
|
|
17863
|
+
}
|
|
17864
|
+
seen.add(skill.toolName);
|
|
17865
|
+
results.push({
|
|
17866
|
+
name: skill.toolName,
|
|
17867
|
+
description: skill.description,
|
|
17868
|
+
score: Math.max(limit - index, 1),
|
|
17869
|
+
reason: recommendation.reason || "Recommended by the configured skill_recommend model"
|
|
17870
|
+
});
|
|
17871
|
+
if (results.length >= limit) {
|
|
17872
|
+
break;
|
|
17873
|
+
}
|
|
17874
|
+
}
|
|
17875
|
+
return results;
|
|
17876
|
+
}
|
|
17877
|
+
function shouldUseModel(config2, client) {
|
|
17878
|
+
return config2.strategy === "model" && Boolean(client) && config2.model.includes("/");
|
|
17879
|
+
}
|
|
17880
|
+
function parseConfiguredModel(model) {
|
|
17881
|
+
const trimmedModel = model.trim();
|
|
17882
|
+
const separatorIndex = trimmedModel.indexOf("/");
|
|
17883
|
+
if (separatorIndex <= 0 || separatorIndex === trimmedModel.length - 1) {
|
|
17884
|
+
throw new Error(`Invalid skillRecommend.model: ${model}`);
|
|
17885
|
+
}
|
|
17886
|
+
return {
|
|
17887
|
+
providerID: trimmedModel.slice(0, separatorIndex).trim(),
|
|
17888
|
+
modelID: trimmedModel.slice(separatorIndex + 1).trim()
|
|
17889
|
+
};
|
|
17890
|
+
}
|
|
17891
|
+
function unwrapClientData(response, action) {
|
|
17892
|
+
if (response.error) {
|
|
17893
|
+
const message = response.error instanceof Error ? response.error.message : String(response.error);
|
|
17894
|
+
throw new Error(`${action} failed: ${message}`);
|
|
17895
|
+
}
|
|
17896
|
+
if (response.data === undefined) {
|
|
17897
|
+
throw new Error(`${action} failed: missing response data`);
|
|
17898
|
+
}
|
|
17899
|
+
return response.data;
|
|
17900
|
+
}
|
|
17901
|
+
function getModelSessionClient(client) {
|
|
17902
|
+
return client;
|
|
17903
|
+
}
|
|
17904
|
+
async function getModelRecommendations(provider, options2, task, limit) {
|
|
17905
|
+
if (!options2.client) {
|
|
17906
|
+
throw new Error("Model-based recommendation requires an OpenCode client");
|
|
17907
|
+
}
|
|
17908
|
+
const client = getModelSessionClient(options2.client);
|
|
17909
|
+
const selectedModel = parseConfiguredModel(options2.config.model);
|
|
17910
|
+
const session = unwrapClientData(await client.session.create(), "session.create");
|
|
17911
|
+
try {
|
|
17912
|
+
unwrapClientData(await client.session.chat(session.id, {
|
|
17913
|
+
providerID: selectedModel.providerID,
|
|
17914
|
+
modelID: selectedModel.modelID,
|
|
17915
|
+
parts: [
|
|
17916
|
+
{
|
|
17917
|
+
type: "text",
|
|
17918
|
+
text: buildModelRecommendationPrompt({
|
|
17919
|
+
task,
|
|
17920
|
+
limit,
|
|
17921
|
+
skills: provider.controller.skills
|
|
17922
|
+
})
|
|
17923
|
+
}
|
|
17924
|
+
],
|
|
17925
|
+
system: options2.config.systemPrompt,
|
|
17926
|
+
tools: {}
|
|
17927
|
+
}), "session.chat");
|
|
17928
|
+
const messages = unwrapClientData(await client.session.messages(session.id), "session.messages");
|
|
17929
|
+
const modelOutput = getAssistantText(messages);
|
|
17930
|
+
const parsedRecommendations = parseModelRecommendations(modelOutput);
|
|
17931
|
+
return mapModelRecommendationsToSkills(provider.controller.skills, parsedRecommendations, limit);
|
|
17932
|
+
} finally {
|
|
17933
|
+
await client.session.delete(session.id).catch(() => {
|
|
17934
|
+
return;
|
|
17935
|
+
});
|
|
17936
|
+
}
|
|
17937
|
+
}
|
|
17938
|
+
function createSkillRecommender(provider, options2) {
|
|
17752
17939
|
return async (args) => {
|
|
17753
17940
|
await provider.controller.ready.whenReady();
|
|
17754
17941
|
const limit = Math.max(1, Math.min(args.limit ?? DEFAULT_RECOMMENDATION_LIMIT, 10));
|
|
17755
|
-
|
|
17756
|
-
|
|
17757
|
-
|
|
17758
|
-
|
|
17759
|
-
|
|
17760
|
-
|
|
17761
|
-
if (right.nameMatches !== left.nameMatches) {
|
|
17762
|
-
return right.nameMatches - left.nameMatches;
|
|
17942
|
+
if (shouldUseModel(options2.config, options2.client)) {
|
|
17943
|
+
try {
|
|
17944
|
+
const modelRecommendations = await getModelRecommendations(provider, options2, args.task, limit);
|
|
17945
|
+
return formatResults(provider, args.task, modelRecommendations);
|
|
17946
|
+
} catch (error45) {
|
|
17947
|
+
provider.logger.warn("Model-based skill_recommend failed; falling back to heuristic ranking.", error45);
|
|
17763
17948
|
}
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
const topMatches = rankedMatches.slice(0, limit);
|
|
17767
|
-
const recommendations = topMatches.map((skill) => {
|
|
17768
|
-
return {
|
|
17769
|
-
name: skill.skill.toolName,
|
|
17770
|
-
description: skill.skill.description,
|
|
17771
|
-
score: skill.totalScore,
|
|
17772
|
-
reason: getRecommendationReason(skill.nameMatches, skill.descMatches)
|
|
17773
|
-
};
|
|
17774
|
-
});
|
|
17775
|
-
return {
|
|
17776
|
-
mode: "recommend",
|
|
17777
|
-
query: args.task,
|
|
17778
|
-
skills: recommendations.map(({ name, description }) => ({
|
|
17779
|
-
name,
|
|
17780
|
-
description
|
|
17781
|
-
})),
|
|
17782
|
-
recommendations,
|
|
17783
|
-
guidance: recommendations.length > 0 ? "Use skill_use to load one of the recommended skills if you want to apply it." : "No strong skill recommendation was found. Try skill_find with a narrower query.",
|
|
17784
|
-
summary: {
|
|
17785
|
-
total: provider.controller.skills.length,
|
|
17786
|
-
matches: recommendations.length,
|
|
17787
|
-
feedback: recommendations.length > 0 ? `Recommended ${recommendations.length} skill(s) for: **${args.task}**` : `No strong recommendations found for: **${args.task}**`
|
|
17788
|
-
},
|
|
17789
|
-
debug: provider.debug
|
|
17790
|
-
};
|
|
17949
|
+
}
|
|
17950
|
+
return formatResults(provider, args.task, getHeuristicRecommendations(provider, args.task, limit));
|
|
17791
17951
|
};
|
|
17792
17952
|
}
|
|
17793
17953
|
|
|
@@ -17809,7 +17969,7 @@ function normalizeSkillResourcePath(inputPath) {
|
|
|
17809
17969
|
}
|
|
17810
17970
|
function getSkillResources(skill) {
|
|
17811
17971
|
const resources = new Map;
|
|
17812
|
-
for (const resourceMap of [skill.references, skill.scripts, skill.assets]) {
|
|
17972
|
+
for (const resourceMap of [skill.files, skill.references, skill.scripts, skill.assets]) {
|
|
17813
17973
|
for (const [relativePath, resource] of resourceMap.entries()) {
|
|
17814
17974
|
resources.set(normalizeSkillResourcePath(relativePath), resource);
|
|
17815
17975
|
}
|
|
@@ -17968,6 +18128,12 @@ function formatLoadedSkill(args) {
|
|
|
17968
18128
|
}
|
|
17969
18129
|
output.push("", "Relative file references in this skill resolve from the skill root directory.");
|
|
17970
18130
|
output.push("Use skill_resource with the exact root-relative path when you need a linked file.");
|
|
18131
|
+
if (args.skill.files.size > 0) {
|
|
18132
|
+
output.push("", "### Available files", "");
|
|
18133
|
+
for (const relativePath of Array.from(args.skill.files.keys()).sort()) {
|
|
18134
|
+
output.push(`- ${relativePath}`);
|
|
18135
|
+
}
|
|
18136
|
+
}
|
|
17971
18137
|
if (linkedResources.length > 0) {
|
|
17972
18138
|
output.push("", "### Linked files", "");
|
|
17973
18139
|
for (const link of linkedResources) {
|
|
@@ -17980,7 +18146,7 @@ function formatLoadedSkill(args) {
|
|
|
17980
18146
|
}
|
|
17981
18147
|
|
|
17982
18148
|
// src/tools/Skill.ts
|
|
17983
|
-
function
|
|
18149
|
+
function normalizeSkillSelector2(selector) {
|
|
17984
18150
|
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
17985
18151
|
}
|
|
17986
18152
|
function findSkill(registry2, selector) {
|
|
@@ -17988,9 +18154,9 @@ function findSkill(registry2, selector) {
|
|
|
17988
18154
|
if (directMatch) {
|
|
17989
18155
|
return directMatch;
|
|
17990
18156
|
}
|
|
17991
|
-
const normalizedSelector =
|
|
18157
|
+
const normalizedSelector = normalizeSkillSelector2(selector);
|
|
17992
18158
|
for (const skill of registry2.controller.skills) {
|
|
17993
|
-
if (
|
|
18159
|
+
if (normalizeSkillSelector2(skill.name) === normalizedSelector || normalizeSkillSelector2(skill.toolName) === normalizedSelector) {
|
|
17994
18160
|
return skill;
|
|
17995
18161
|
}
|
|
17996
18162
|
}
|
|
@@ -18020,7 +18186,7 @@ function createSkillTool(registry2) {
|
|
|
18020
18186
|
}
|
|
18021
18187
|
|
|
18022
18188
|
// src/tools/SkillUser.ts
|
|
18023
|
-
function
|
|
18189
|
+
function normalizeSkillSelector3(selector) {
|
|
18024
18190
|
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
18025
18191
|
}
|
|
18026
18192
|
function createSkillLoader(provider) {
|
|
@@ -18030,8 +18196,8 @@ function createSkillLoader(provider) {
|
|
|
18030
18196
|
const loaded = [];
|
|
18031
18197
|
const notFound = [];
|
|
18032
18198
|
for (const name of skillNames) {
|
|
18033
|
-
const normalizedName =
|
|
18034
|
-
const skill = registry2.get(name) ?? registry2.skills.find((candidate) =>
|
|
18199
|
+
const normalizedName = normalizeSkillSelector3(name);
|
|
18200
|
+
const skill = registry2.get(name) ?? registry2.skills.find((candidate) => normalizeSkillSelector3(candidate.name) === normalizedName || normalizeSkillSelector3(candidate.toolName) === normalizedName);
|
|
18035
18201
|
if (skill) {
|
|
18036
18202
|
loaded.push(skill);
|
|
18037
18203
|
} else {
|
|
@@ -18047,7 +18213,7 @@ function createSkillLoader(provider) {
|
|
|
18047
18213
|
}
|
|
18048
18214
|
|
|
18049
18215
|
// src/api.ts
|
|
18050
|
-
var createApi = async (config2) => {
|
|
18216
|
+
var createApi = async (config2, client) => {
|
|
18051
18217
|
const logger = createLogger(config2);
|
|
18052
18218
|
const registry2 = await createSkillRegistry(config2, logger);
|
|
18053
18219
|
return {
|
|
@@ -18055,7 +18221,10 @@ var createApi = async (config2) => {
|
|
|
18055
18221
|
logger,
|
|
18056
18222
|
config: config2,
|
|
18057
18223
|
findSkills: createSkillFinder(registry2),
|
|
18058
|
-
recommendSkills: createSkillRecommender(registry2
|
|
18224
|
+
recommendSkills: createSkillRecommender(registry2, {
|
|
18225
|
+
client,
|
|
18226
|
+
config: config2.skillRecommend
|
|
18227
|
+
}),
|
|
18059
18228
|
readResource: createSkillResourceReader(registry2),
|
|
18060
18229
|
loadSkill: createSkillLoader(registry2),
|
|
18061
18230
|
skillTool: createSkillTool(registry2)
|
|
@@ -18063,7 +18232,7 @@ var createApi = async (config2) => {
|
|
|
18063
18232
|
};
|
|
18064
18233
|
|
|
18065
18234
|
// node_modules/bunfig/dist/index.js
|
|
18066
|
-
import { existsSync as existsSync2, statSync } from "fs";
|
|
18235
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
18067
18236
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
18068
18237
|
import { homedir as homedir2 } from "os";
|
|
18069
18238
|
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
@@ -18185,7 +18354,7 @@ class ConfigCache {
|
|
|
18185
18354
|
if (!existsSync2(configPath)) {
|
|
18186
18355
|
return true;
|
|
18187
18356
|
}
|
|
18188
|
-
const stats =
|
|
18357
|
+
const stats = statSync2(configPath);
|
|
18189
18358
|
return stats.mtime > cachedTimestamp;
|
|
18190
18359
|
} catch {
|
|
18191
18360
|
return true;
|
|
@@ -18204,7 +18373,7 @@ class ConfigCache {
|
|
|
18204
18373
|
}
|
|
18205
18374
|
setWithFileCheck(configName, value, configPath, customTtl) {
|
|
18206
18375
|
try {
|
|
18207
|
-
const stats = existsSync2(configPath) ?
|
|
18376
|
+
const stats = existsSync2(configPath) ? statSync2(configPath) : null;
|
|
18208
18377
|
const fileTimestamp = stats ? stats.mtime : new Date;
|
|
18209
18378
|
this.set(configName, { value, fileTimestamp }, configPath, customTtl);
|
|
18210
18379
|
} catch {
|
|
@@ -22307,8 +22476,8 @@ class ConfigFileLoader {
|
|
|
22307
22476
|
}
|
|
22308
22477
|
async getFileModificationTime(filePath) {
|
|
22309
22478
|
try {
|
|
22310
|
-
const { statSync:
|
|
22311
|
-
const stats =
|
|
22479
|
+
const { statSync: statSync22 } = await import("fs");
|
|
22480
|
+
const stats = statSync22(filePath);
|
|
22312
22481
|
return stats.mtime;
|
|
22313
22482
|
} catch {
|
|
22314
22483
|
return null;
|
|
@@ -23114,8 +23283,46 @@ var defaultConfigDir3 = resolve7(process12.cwd(), "config");
|
|
|
23114
23283
|
var defaultGeneratedDir3 = resolve7(process12.cwd(), "src/generated");
|
|
23115
23284
|
|
|
23116
23285
|
// src/config.ts
|
|
23286
|
+
import { access as access3, mkdir as mkdir3, readFile, writeFile as writeFile3 } from "fs/promises";
|
|
23117
23287
|
import { homedir as homedir3 } from "os";
|
|
23118
23288
|
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join4, normalize, resolve as resolve8 } from "path";
|
|
23289
|
+
var DEFAULT_RESERVED_SLASH_COMMANDS = [
|
|
23290
|
+
"agent",
|
|
23291
|
+
"agents",
|
|
23292
|
+
"compact",
|
|
23293
|
+
"connect",
|
|
23294
|
+
"details",
|
|
23295
|
+
"editor",
|
|
23296
|
+
"exit",
|
|
23297
|
+
"export",
|
|
23298
|
+
"fork",
|
|
23299
|
+
"help",
|
|
23300
|
+
"init",
|
|
23301
|
+
"mcp",
|
|
23302
|
+
"model",
|
|
23303
|
+
"models",
|
|
23304
|
+
"new",
|
|
23305
|
+
"open",
|
|
23306
|
+
"redo",
|
|
23307
|
+
"sessions",
|
|
23308
|
+
"share",
|
|
23309
|
+
"skills",
|
|
23310
|
+
"terminal",
|
|
23311
|
+
"themes",
|
|
23312
|
+
"thinking",
|
|
23313
|
+
"undo",
|
|
23314
|
+
"unshare"
|
|
23315
|
+
];
|
|
23316
|
+
var DEFAULT_SKILL_RECOMMEND_SYSTEM_PROMPT = [
|
|
23317
|
+
"You are selecting the most relevant dynamic skills for a user request.",
|
|
23318
|
+
"Return strict JSON only with this shape:",
|
|
23319
|
+
'{"recommendations":[{"name":"skill_tool_name","reason":"why it matches"}]}',
|
|
23320
|
+
"Only recommend skills from the provided catalog.",
|
|
23321
|
+
"Prefer the smallest set of high-confidence matches."
|
|
23322
|
+
].join(" ");
|
|
23323
|
+
var MANAGED_PLUGIN_CONFIG_DIRECTORY = join4(homedir3(), ".config", "opencode");
|
|
23324
|
+
var MANAGED_PLUGIN_JSONC_FILENAME = "opencode-dynamic-skills.config.jsonc";
|
|
23325
|
+
var MANAGED_PLUGIN_JSON_FILENAME = "opencode-dynamic-skills.config.json";
|
|
23119
23326
|
function getOpenCodeConfigPaths() {
|
|
23120
23327
|
const home = homedir3();
|
|
23121
23328
|
const paths = [];
|
|
@@ -23206,34 +23413,278 @@ var defaultSkillBasePaths = [
|
|
|
23206
23413
|
join4(homedir3(), ".claude", "skills"),
|
|
23207
23414
|
join4(homedir3(), ".agents", "skills")
|
|
23208
23415
|
];
|
|
23209
|
-
|
|
23210
|
-
|
|
23211
|
-
|
|
23212
|
-
|
|
23416
|
+
function createDefaultSkillRecommendConfig() {
|
|
23417
|
+
return {
|
|
23418
|
+
strategy: "heuristic",
|
|
23419
|
+
model: "",
|
|
23420
|
+
systemPrompt: DEFAULT_SKILL_RECOMMEND_SYSTEM_PROMPT
|
|
23421
|
+
};
|
|
23422
|
+
}
|
|
23423
|
+
function createDefaultNotificationConfig() {
|
|
23424
|
+
return {
|
|
23425
|
+
enabled: false,
|
|
23426
|
+
success: true,
|
|
23427
|
+
errors: true
|
|
23428
|
+
};
|
|
23429
|
+
}
|
|
23430
|
+
function createDefaultPluginConfig() {
|
|
23431
|
+
return {
|
|
23213
23432
|
debug: false,
|
|
23214
23433
|
basePaths: defaultSkillBasePaths,
|
|
23215
|
-
promptRenderer: "xml",
|
|
23216
|
-
modelRenderers: {},
|
|
23217
23434
|
slashCommandName: "skill",
|
|
23218
|
-
enableSkillAliases: true
|
|
23435
|
+
enableSkillAliases: true,
|
|
23436
|
+
reservedSlashCommands: [...DEFAULT_RESERVED_SLASH_COMMANDS],
|
|
23437
|
+
notifications: createDefaultNotificationConfig(),
|
|
23438
|
+
skillRecommend: createDefaultSkillRecommendConfig()
|
|
23439
|
+
};
|
|
23440
|
+
}
|
|
23441
|
+
function createConfigOptions(defaultConfig3) {
|
|
23442
|
+
return {
|
|
23443
|
+
name: "opencode-dynamic-skills",
|
|
23444
|
+
cwd: "./",
|
|
23445
|
+
defaultConfig: defaultConfig3
|
|
23446
|
+
};
|
|
23447
|
+
}
|
|
23448
|
+
function stripJsonComments(input) {
|
|
23449
|
+
let output = "";
|
|
23450
|
+
let inString = false;
|
|
23451
|
+
let escaped = false;
|
|
23452
|
+
let inLineComment = false;
|
|
23453
|
+
let inBlockComment = false;
|
|
23454
|
+
for (let index = 0;index < input.length; index += 1) {
|
|
23455
|
+
const current = input[index] ?? "";
|
|
23456
|
+
const next = input[index + 1] ?? "";
|
|
23457
|
+
if (inLineComment) {
|
|
23458
|
+
if (current === `
|
|
23459
|
+
` || current === "\r") {
|
|
23460
|
+
inLineComment = false;
|
|
23461
|
+
output += current;
|
|
23462
|
+
}
|
|
23463
|
+
continue;
|
|
23464
|
+
}
|
|
23465
|
+
if (inBlockComment) {
|
|
23466
|
+
if (current === "*" && next === "/") {
|
|
23467
|
+
inBlockComment = false;
|
|
23468
|
+
index += 1;
|
|
23469
|
+
continue;
|
|
23470
|
+
}
|
|
23471
|
+
if (current === `
|
|
23472
|
+
` || current === "\r") {
|
|
23473
|
+
output += current;
|
|
23474
|
+
}
|
|
23475
|
+
continue;
|
|
23476
|
+
}
|
|
23477
|
+
if (inString) {
|
|
23478
|
+
output += current;
|
|
23479
|
+
if (escaped) {
|
|
23480
|
+
escaped = false;
|
|
23481
|
+
continue;
|
|
23482
|
+
}
|
|
23483
|
+
if (current === "\\") {
|
|
23484
|
+
escaped = true;
|
|
23485
|
+
continue;
|
|
23486
|
+
}
|
|
23487
|
+
if (current === '"') {
|
|
23488
|
+
inString = false;
|
|
23489
|
+
}
|
|
23490
|
+
continue;
|
|
23491
|
+
}
|
|
23492
|
+
if (current === "/" && next === "/") {
|
|
23493
|
+
inLineComment = true;
|
|
23494
|
+
index += 1;
|
|
23495
|
+
continue;
|
|
23496
|
+
}
|
|
23497
|
+
if (current === "/" && next === "*") {
|
|
23498
|
+
inBlockComment = true;
|
|
23499
|
+
index += 1;
|
|
23500
|
+
continue;
|
|
23501
|
+
}
|
|
23502
|
+
if (current === '"') {
|
|
23503
|
+
inString = true;
|
|
23504
|
+
}
|
|
23505
|
+
output += current;
|
|
23219
23506
|
}
|
|
23220
|
-
|
|
23507
|
+
return output;
|
|
23508
|
+
}
|
|
23509
|
+
function removeTrailingJsonCommas(input) {
|
|
23510
|
+
let output = "";
|
|
23511
|
+
let inString = false;
|
|
23512
|
+
let escaped = false;
|
|
23513
|
+
for (let index = 0;index < input.length; index += 1) {
|
|
23514
|
+
const current = input[index] ?? "";
|
|
23515
|
+
if (inString) {
|
|
23516
|
+
output += current;
|
|
23517
|
+
if (escaped) {
|
|
23518
|
+
escaped = false;
|
|
23519
|
+
continue;
|
|
23520
|
+
}
|
|
23521
|
+
if (current === "\\") {
|
|
23522
|
+
escaped = true;
|
|
23523
|
+
continue;
|
|
23524
|
+
}
|
|
23525
|
+
if (current === '"') {
|
|
23526
|
+
inString = false;
|
|
23527
|
+
}
|
|
23528
|
+
continue;
|
|
23529
|
+
}
|
|
23530
|
+
if (current === '"') {
|
|
23531
|
+
inString = true;
|
|
23532
|
+
output += current;
|
|
23533
|
+
continue;
|
|
23534
|
+
}
|
|
23535
|
+
if (current === ",") {
|
|
23536
|
+
let lookahead = index + 1;
|
|
23537
|
+
while (lookahead < input.length && /\s/.test(input[lookahead] ?? "")) {
|
|
23538
|
+
lookahead += 1;
|
|
23539
|
+
}
|
|
23540
|
+
const nextToken = input[lookahead];
|
|
23541
|
+
if (nextToken === "}" || nextToken === "]") {
|
|
23542
|
+
continue;
|
|
23543
|
+
}
|
|
23544
|
+
}
|
|
23545
|
+
output += current;
|
|
23546
|
+
}
|
|
23547
|
+
return output;
|
|
23548
|
+
}
|
|
23549
|
+
function parseJsonc(input) {
|
|
23550
|
+
return JSON.parse(removeTrailingJsonCommas(stripJsonComments(input)));
|
|
23551
|
+
}
|
|
23552
|
+
function trimString(value) {
|
|
23553
|
+
return value?.trim() ?? "";
|
|
23554
|
+
}
|
|
23555
|
+
function mergePluginConfig(baseConfig, override) {
|
|
23556
|
+
const skillRecommend = {
|
|
23557
|
+
...baseConfig.skillRecommend,
|
|
23558
|
+
...override.skillRecommend
|
|
23559
|
+
};
|
|
23560
|
+
const notifications = {
|
|
23561
|
+
...baseConfig.notifications,
|
|
23562
|
+
...override.notifications
|
|
23563
|
+
};
|
|
23564
|
+
const overrideReservedSlashCommands = override.reservedSlashCommands?.map((command) => trimString(command)).filter(Boolean) ?? [];
|
|
23565
|
+
return {
|
|
23566
|
+
...baseConfig,
|
|
23567
|
+
...override,
|
|
23568
|
+
skillRecommend: {
|
|
23569
|
+
strategy: skillRecommend.strategy === "model" ? "model" : "heuristic",
|
|
23570
|
+
model: trimString(skillRecommend.model),
|
|
23571
|
+
systemPrompt: trimString(skillRecommend.systemPrompt) || DEFAULT_SKILL_RECOMMEND_SYSTEM_PROMPT
|
|
23572
|
+
},
|
|
23573
|
+
reservedSlashCommands: overrideReservedSlashCommands.length > 0 ? overrideReservedSlashCommands : baseConfig.reservedSlashCommands,
|
|
23574
|
+
notifications: {
|
|
23575
|
+
enabled: notifications.enabled === true,
|
|
23576
|
+
success: notifications.success !== false,
|
|
23577
|
+
errors: notifications.errors !== false
|
|
23578
|
+
}
|
|
23579
|
+
};
|
|
23580
|
+
}
|
|
23581
|
+
function getManagedPluginConfigPaths(configDirectory = MANAGED_PLUGIN_CONFIG_DIRECTORY) {
|
|
23582
|
+
return [
|
|
23583
|
+
join4(configDirectory, MANAGED_PLUGIN_JSONC_FILENAME),
|
|
23584
|
+
join4(configDirectory, MANAGED_PLUGIN_JSON_FILENAME)
|
|
23585
|
+
];
|
|
23586
|
+
}
|
|
23587
|
+
async function fileExists(filePath) {
|
|
23588
|
+
try {
|
|
23589
|
+
await access3(filePath);
|
|
23590
|
+
return true;
|
|
23591
|
+
} catch {
|
|
23592
|
+
return false;
|
|
23593
|
+
}
|
|
23594
|
+
}
|
|
23595
|
+
function renderManagedPluginConfigJsonc(config3 = createDefaultPluginConfig()) {
|
|
23596
|
+
const basePaths = config3.basePaths.map((basePath) => ` ${JSON.stringify(basePath)}`).join(`,
|
|
23597
|
+
`);
|
|
23598
|
+
return [
|
|
23599
|
+
"{",
|
|
23600
|
+
" // Enable verbose plugin logging output.",
|
|
23601
|
+
` "debug": ${JSON.stringify(config3.debug)},`,
|
|
23602
|
+
"",
|
|
23603
|
+
" // Global skill roots. Project-local .opencode/.claude/.agents paths are appended automatically.",
|
|
23604
|
+
' "basePaths": [',
|
|
23605
|
+
basePaths,
|
|
23606
|
+
" ],",
|
|
23607
|
+
"",
|
|
23608
|
+
" // Explicit slash entrypoint, for example: /skill git-release",
|
|
23609
|
+
` "slashCommandName": ${JSON.stringify(config3.slashCommandName)},`,
|
|
23610
|
+
"",
|
|
23611
|
+
" // When true, /<skill-name> alias invocations can be intercepted after submit.",
|
|
23612
|
+
` "enableSkillAliases": ${JSON.stringify(config3.enableSkillAliases)},`,
|
|
23613
|
+
"",
|
|
23614
|
+
" // Builtin or protected slash commands that should never be intercepted as /<skill-name> aliases.",
|
|
23615
|
+
' "reservedSlashCommands": [',
|
|
23616
|
+
config3.reservedSlashCommands.map((command) => ` ${JSON.stringify(command)}`).join(`,
|
|
23617
|
+
`),
|
|
23618
|
+
" ],",
|
|
23619
|
+
"",
|
|
23620
|
+
" // Best-effort OS notifications triggered by this plugin.",
|
|
23621
|
+
' "notifications": {',
|
|
23622
|
+
` "enabled": ${JSON.stringify(config3.notifications.enabled)},`,
|
|
23623
|
+
` "success": ${JSON.stringify(config3.notifications.success)},`,
|
|
23624
|
+
` "errors": ${JSON.stringify(config3.notifications.errors)}`,
|
|
23625
|
+
" },",
|
|
23626
|
+
"",
|
|
23627
|
+
' // skill_recommend strategy. Set strategy to "model" and fill model with provider/model to use an internal LLM call.',
|
|
23628
|
+
' "skillRecommend": {',
|
|
23629
|
+
` "strategy": ${JSON.stringify(config3.skillRecommend.strategy)},`,
|
|
23630
|
+
" // Model format: provider/model, for example openai/gpt-5.2",
|
|
23631
|
+
` "model": ${JSON.stringify(config3.skillRecommend.model)},`,
|
|
23632
|
+
' // The model must return strict JSON: {"recommendations":[{"name":"skill_tool_name","reason":"why it matches"}]}',
|
|
23633
|
+
` "systemPrompt": ${JSON.stringify(config3.skillRecommend.systemPrompt)}`,
|
|
23634
|
+
" }",
|
|
23635
|
+
"}",
|
|
23636
|
+
""
|
|
23637
|
+
].join(`
|
|
23638
|
+
`);
|
|
23639
|
+
}
|
|
23640
|
+
async function ensureManagedPluginConfigFile(configDirectory = MANAGED_PLUGIN_CONFIG_DIRECTORY) {
|
|
23641
|
+
for (const candidatePath of getManagedPluginConfigPaths(configDirectory)) {
|
|
23642
|
+
if (await fileExists(candidatePath)) {
|
|
23643
|
+
return candidatePath;
|
|
23644
|
+
}
|
|
23645
|
+
}
|
|
23646
|
+
await mkdir3(configDirectory, { recursive: true });
|
|
23647
|
+
const configPath = join4(configDirectory, MANAGED_PLUGIN_JSONC_FILENAME);
|
|
23648
|
+
await writeFile3(configPath, renderManagedPluginConfigJsonc(), "utf8");
|
|
23649
|
+
return configPath;
|
|
23650
|
+
}
|
|
23651
|
+
async function loadManagedPluginConfig(configDirectory = MANAGED_PLUGIN_CONFIG_DIRECTORY) {
|
|
23652
|
+
for (const candidatePath of getManagedPluginConfigPaths(configDirectory)) {
|
|
23653
|
+
if (!await fileExists(candidatePath)) {
|
|
23654
|
+
continue;
|
|
23655
|
+
}
|
|
23656
|
+
const content = await readFile(candidatePath, "utf8");
|
|
23657
|
+
if (candidatePath.endsWith(".jsonc")) {
|
|
23658
|
+
return parseJsonc(content);
|
|
23659
|
+
}
|
|
23660
|
+
return JSON.parse(content);
|
|
23661
|
+
}
|
|
23662
|
+
return {};
|
|
23663
|
+
}
|
|
23221
23664
|
async function getPluginConfig(ctx) {
|
|
23222
|
-
const
|
|
23665
|
+
const defaultConfig3 = createDefaultPluginConfig();
|
|
23666
|
+
await ensureManagedPluginConfigFile();
|
|
23667
|
+
const managedConfig = await loadManagedPluginConfig();
|
|
23668
|
+
const resolvedConfig = await loadConfig5(createConfigOptions(defaultConfig3));
|
|
23669
|
+
const mergedConfig = mergePluginConfig(mergePluginConfig(defaultConfig3, managedConfig), resolvedConfig);
|
|
23223
23670
|
const configuredBasePaths = [
|
|
23224
|
-
...
|
|
23671
|
+
...mergedConfig.basePaths,
|
|
23225
23672
|
...getProjectSkillBasePaths(ctx.directory, ctx.worktree)
|
|
23226
23673
|
];
|
|
23227
|
-
|
|
23228
|
-
return
|
|
23674
|
+
mergedConfig.basePaths = normalizeBasePaths(configuredBasePaths, ctx.directory);
|
|
23675
|
+
return mergedConfig;
|
|
23229
23676
|
}
|
|
23230
23677
|
|
|
23231
23678
|
// src/commands/SlashCommand.ts
|
|
23232
23679
|
var SLASH_COMMAND_SENTINEL = "<!-- opencode-dynamic-skills:slash-expanded -->";
|
|
23233
23680
|
var RECOMMEND_COMMAND_SENTINEL = "<!-- opencode-dynamic-skills:skill-recommend-expanded -->";
|
|
23234
|
-
function
|
|
23681
|
+
function normalizeSkillSelector4(selector) {
|
|
23235
23682
|
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
23236
23683
|
}
|
|
23684
|
+
function isReservedSlashCommand(invocationName, reservedSlashCommands) {
|
|
23685
|
+
const normalizedInvocationName = invocationName.trim().toLowerCase();
|
|
23686
|
+
return reservedSlashCommands.some((reservedCommand) => reservedCommand.trim().toLowerCase() === normalizedInvocationName);
|
|
23687
|
+
}
|
|
23237
23688
|
function parseSlashCommand(text, slashCommandName) {
|
|
23238
23689
|
const trimmedText = text.trim();
|
|
23239
23690
|
if (!trimmedText.startsWith("/")) {
|
|
@@ -23268,12 +23719,12 @@ function findSkillBySelector(registry2, selector) {
|
|
|
23268
23719
|
if (directMatch) {
|
|
23269
23720
|
return directMatch;
|
|
23270
23721
|
}
|
|
23271
|
-
const normalizedSelector =
|
|
23722
|
+
const normalizedSelector = normalizeSkillSelector4(selector);
|
|
23272
23723
|
for (const skill of registry2.controller.skills) {
|
|
23273
|
-
if (
|
|
23724
|
+
if (normalizeSkillSelector4(skill.toolName) === normalizedSelector) {
|
|
23274
23725
|
return skill;
|
|
23275
23726
|
}
|
|
23276
|
-
if (
|
|
23727
|
+
if (normalizeSkillSelector4(skill.name) === normalizedSelector) {
|
|
23277
23728
|
return skill;
|
|
23278
23729
|
}
|
|
23279
23730
|
}
|
|
@@ -23328,6 +23779,9 @@ async function rewriteSlashCommandText(args) {
|
|
|
23328
23779
|
if (isAliasInvocation && !args.enableSkillAliases) {
|
|
23329
23780
|
return null;
|
|
23330
23781
|
}
|
|
23782
|
+
if (isAliasInvocation && isReservedSlashCommand(parsedCommand.invocationName, args.reservedSlashCommands ?? [])) {
|
|
23783
|
+
return null;
|
|
23784
|
+
}
|
|
23331
23785
|
await args.registry.controller.ready.whenReady();
|
|
23332
23786
|
const skill = findSkillBySelector(args.registry, parsedCommand.skillSelector);
|
|
23333
23787
|
if (!skill) {
|
|
@@ -23340,26 +23794,6 @@ async function rewriteSlashCommandText(args) {
|
|
|
23340
23794
|
});
|
|
23341
23795
|
}
|
|
23342
23796
|
|
|
23343
|
-
// src/lib/renderers/JsonPromptRenderer.ts
|
|
23344
|
-
var createJsonPromptRenderer = () => {
|
|
23345
|
-
const renderer = {
|
|
23346
|
-
format: "json",
|
|
23347
|
-
render(args) {
|
|
23348
|
-
if (args.type === "Skill") {
|
|
23349
|
-
return JSON.stringify({
|
|
23350
|
-
Skill: {
|
|
23351
|
-
...args.data,
|
|
23352
|
-
linkedResources: extractSkillLinks(args.data.content),
|
|
23353
|
-
skillRootInstruction: "Resolve linked files relative to the skill root and use skill_resource with the exact root-relative path."
|
|
23354
|
-
}
|
|
23355
|
-
}, null, 2);
|
|
23356
|
-
}
|
|
23357
|
-
return JSON.stringify({ [args.type]: args.data }, null, 2);
|
|
23358
|
-
}
|
|
23359
|
-
};
|
|
23360
|
-
return renderer;
|
|
23361
|
-
};
|
|
23362
|
-
|
|
23363
23797
|
// src/lib/xml.ts
|
|
23364
23798
|
function escapeXml(str2) {
|
|
23365
23799
|
return String(str2).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -23413,6 +23847,7 @@ var createXmlPromptRenderer = () => {
|
|
|
23413
23847
|
...skill,
|
|
23414
23848
|
linkedResources: extractSkillLinks(skill.content),
|
|
23415
23849
|
skillRootInstruction: "Resolve linked files relative to the skill root and use skill_resource with the exact root-relative path.",
|
|
23850
|
+
files: resourceMapToArray(skill.files),
|
|
23416
23851
|
references: resourceMapToArray(skill.references),
|
|
23417
23852
|
scripts: resourceMapToArray(skill.scripts),
|
|
23418
23853
|
assets: resourceMapToArray(skill.assets)
|
|
@@ -23443,362 +23878,120 @@ var createXmlPromptRenderer = () => {
|
|
|
23443
23878
|
return renderer;
|
|
23444
23879
|
};
|
|
23445
23880
|
|
|
23446
|
-
//
|
|
23447
|
-
|
|
23448
|
-
|
|
23449
|
-
|
|
23450
|
-
|
|
23451
|
-
|
|
23452
|
-
|
|
23453
|
-
|
|
23454
|
-
}
|
|
23455
|
-
return keys;
|
|
23456
|
-
}
|
|
23457
|
-
function _objectSpread(target) {
|
|
23458
|
-
for (var i = 1;i < arguments.length; i++) {
|
|
23459
|
-
var source = arguments[i] != null ? arguments[i] : {};
|
|
23460
|
-
i % 2 ? ownKeys(Object(source), true).forEach(function(key) {
|
|
23461
|
-
_defineProperty(target, key, source[key]);
|
|
23462
|
-
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function(key) {
|
|
23463
|
-
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
23464
|
-
});
|
|
23465
|
-
}
|
|
23466
|
-
return target;
|
|
23881
|
+
// src/services/Notifier.ts
|
|
23882
|
+
import { execFile } from "child_process";
|
|
23883
|
+
import { promisify } from "util";
|
|
23884
|
+
var execFileAsync = promisify(execFile);
|
|
23885
|
+
var NOTIFICATION_TITLE = "OpenCode Dynamic Skills";
|
|
23886
|
+
function createDefaultRunner() {
|
|
23887
|
+
return async (command, args) => {
|
|
23888
|
+
await execFileAsync(command, args);
|
|
23889
|
+
};
|
|
23467
23890
|
}
|
|
23468
|
-
function
|
|
23469
|
-
|
|
23470
|
-
if (
|
|
23471
|
-
|
|
23472
|
-
} else {
|
|
23473
|
-
obj[key] = value;
|
|
23891
|
+
function truncateNotificationText(value, maxLength = 180) {
|
|
23892
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
23893
|
+
if (normalized.length <= maxLength) {
|
|
23894
|
+
return normalized;
|
|
23474
23895
|
}
|
|
23475
|
-
return
|
|
23896
|
+
return `${normalized.slice(0, maxLength - 1)}\u2026`;
|
|
23476
23897
|
}
|
|
23477
|
-
function
|
|
23478
|
-
|
|
23479
|
-
return typeof key === "symbol" ? key : String(key);
|
|
23898
|
+
function escapeAppleScript(value) {
|
|
23899
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
23480
23900
|
}
|
|
23481
|
-
function
|
|
23482
|
-
|
|
23483
|
-
return input;
|
|
23484
|
-
var prim = input[Symbol.toPrimitive];
|
|
23485
|
-
if (prim !== undefined) {
|
|
23486
|
-
var res = prim.call(input, hint || "default");
|
|
23487
|
-
if (typeof res !== "object")
|
|
23488
|
-
return res;
|
|
23489
|
-
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
23490
|
-
}
|
|
23491
|
-
return (hint === "string" ? String : Number)(input);
|
|
23492
|
-
}
|
|
23493
|
-
var dedent = createDedent({});
|
|
23494
|
-
var dedent_default = dedent;
|
|
23495
|
-
function createDedent(options3) {
|
|
23496
|
-
dedent2.withOptions = (newOptions) => createDedent(_objectSpread(_objectSpread({}, options3), newOptions));
|
|
23497
|
-
return dedent2;
|
|
23498
|
-
function dedent2(strings, ...values) {
|
|
23499
|
-
const raw = typeof strings === "string" ? [strings] : strings.raw;
|
|
23500
|
-
const {
|
|
23501
|
-
alignValues = false,
|
|
23502
|
-
escapeSpecialCharacters = Array.isArray(strings),
|
|
23503
|
-
trimWhitespace = true
|
|
23504
|
-
} = options3;
|
|
23505
|
-
let result = "";
|
|
23506
|
-
for (let i = 0;i < raw.length; i++) {
|
|
23507
|
-
let next = raw[i];
|
|
23508
|
-
if (escapeSpecialCharacters) {
|
|
23509
|
-
next = next.replace(/\\\n[ \t]*/g, "").replace(/\\`/g, "`").replace(/\\\$/g, "$").replace(/\\\{/g, "{");
|
|
23510
|
-
}
|
|
23511
|
-
result += next;
|
|
23512
|
-
if (i < values.length) {
|
|
23513
|
-
const value = alignValues ? alignValue(values[i], result) : values[i];
|
|
23514
|
-
result += value;
|
|
23515
|
-
}
|
|
23516
|
-
}
|
|
23517
|
-
const lines = result.split(`
|
|
23518
|
-
`);
|
|
23519
|
-
let mindent = null;
|
|
23520
|
-
for (const l of lines) {
|
|
23521
|
-
const m = l.match(/^(\s+)\S+/);
|
|
23522
|
-
if (m) {
|
|
23523
|
-
const indent = m[1].length;
|
|
23524
|
-
if (!mindent) {
|
|
23525
|
-
mindent = indent;
|
|
23526
|
-
} else {
|
|
23527
|
-
mindent = Math.min(mindent, indent);
|
|
23528
|
-
}
|
|
23529
|
-
}
|
|
23530
|
-
}
|
|
23531
|
-
if (mindent !== null) {
|
|
23532
|
-
const m = mindent;
|
|
23533
|
-
result = lines.map((l) => l[0] === " " || l[0] === "\t" ? l.slice(m) : l).join(`
|
|
23534
|
-
`);
|
|
23535
|
-
}
|
|
23536
|
-
if (trimWhitespace) {
|
|
23537
|
-
result = result.trim();
|
|
23538
|
-
}
|
|
23539
|
-
if (escapeSpecialCharacters) {
|
|
23540
|
-
result = result.replace(/\\n/g, `
|
|
23541
|
-
`).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\v/g, "\v").replace(/\\b/g, "\b").replace(/\\f/g, "\f").replace(/\\0/g, "\x00").replace(/\\x([\da-fA-F]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16))).replace(/\\u\{([\da-fA-F]{1,6})\}/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/\\u([\da-fA-F]{4})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
|
|
23542
|
-
}
|
|
23543
|
-
if (typeof Bun !== "undefined") {
|
|
23544
|
-
result = result.replace(/\\u(?:\{([\da-fA-F]{1,6})\}|([\da-fA-F]{4}))/g, (_, braced, unbraced) => {
|
|
23545
|
-
var _ref;
|
|
23546
|
-
const hex3 = (_ref = braced !== null && braced !== undefined ? braced : unbraced) !== null && _ref !== undefined ? _ref : "";
|
|
23547
|
-
return String.fromCodePoint(parseInt(hex3, 16));
|
|
23548
|
-
});
|
|
23549
|
-
}
|
|
23550
|
-
return result;
|
|
23551
|
-
}
|
|
23901
|
+
function escapeXml2(value) {
|
|
23902
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
23552
23903
|
}
|
|
23553
|
-
function
|
|
23554
|
-
|
|
23555
|
-
|
|
23556
|
-
|
|
23557
|
-
|
|
23558
|
-
|
|
23559
|
-
|
|
23560
|
-
|
|
23561
|
-
|
|
23562
|
-
|
|
23563
|
-
|
|
23564
|
-
|
|
23904
|
+
function buildWindowsToastScript(title, message) {
|
|
23905
|
+
const xml = `<toast><visual><binding template='ToastGeneric'><text>${escapeXml2(title)}</text><text>${escapeXml2(message)}</text></binding></visual></toast>`;
|
|
23906
|
+
return [
|
|
23907
|
+
"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null",
|
|
23908
|
+
"[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null",
|
|
23909
|
+
"$xml = New-Object Windows.Data.Xml.Dom.XmlDocument",
|
|
23910
|
+
`$xml.LoadXml(@'${xml}'@)`,
|
|
23911
|
+
"$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)",
|
|
23912
|
+
`$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('${NOTIFICATION_TITLE}')`,
|
|
23913
|
+
"$notifier.Show($toast)"
|
|
23914
|
+
].join("; ");
|
|
23915
|
+
}
|
|
23916
|
+
function getNotificationCommand(platform, title, message) {
|
|
23917
|
+
switch (platform) {
|
|
23918
|
+
case "darwin":
|
|
23919
|
+
return {
|
|
23920
|
+
command: "osascript",
|
|
23921
|
+
args: [
|
|
23922
|
+
"-e",
|
|
23923
|
+
`display notification "${escapeAppleScript(message)}" with title "${escapeAppleScript(title)}"`
|
|
23924
|
+
]
|
|
23925
|
+
};
|
|
23926
|
+
case "linux":
|
|
23927
|
+
return {
|
|
23928
|
+
command: "notify-send",
|
|
23929
|
+
args: [title, message]
|
|
23930
|
+
};
|
|
23931
|
+
case "win32":
|
|
23932
|
+
return {
|
|
23933
|
+
command: "powershell",
|
|
23934
|
+
args: ["-NoProfile", "-Command", buildWindowsToastScript(title, message)]
|
|
23935
|
+
};
|
|
23936
|
+
default:
|
|
23937
|
+
return null;
|
|
23565
23938
|
}
|
|
23566
|
-
return value;
|
|
23567
23939
|
}
|
|
23568
|
-
|
|
23569
|
-
|
|
23570
|
-
|
|
23571
|
-
|
|
23572
|
-
|
|
23573
|
-
|
|
23574
|
-
for (const [key, value] of entries) {
|
|
23575
|
-
if (value === null || value === undefined) {
|
|
23576
|
-
continue;
|
|
23577
|
-
}
|
|
23578
|
-
const heading = "#".repeat(headingLevel);
|
|
23579
|
-
output += `${heading} ${key}`;
|
|
23580
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
23581
|
-
output += renderObject(value, Math.min(headingLevel + 1, 6), indentLevel);
|
|
23582
|
-
} else if (Array.isArray(value)) {
|
|
23583
|
-
output += renderArray(value, indentLevel);
|
|
23584
|
-
} else {
|
|
23585
|
-
const indent = " ".repeat(indentLevel);
|
|
23586
|
-
const escapedValue = htmlEscape(String(value));
|
|
23587
|
-
output += `${indent}- **${key}**: *${escapedValue}*`;
|
|
23588
|
-
}
|
|
23589
|
-
output += `
|
|
23590
|
-
`;
|
|
23591
|
-
}
|
|
23592
|
-
return output;
|
|
23593
|
-
};
|
|
23594
|
-
const renderArray = (arr, indentLevel) => {
|
|
23595
|
-
const indent = " ".repeat(indentLevel);
|
|
23596
|
-
let output = "";
|
|
23597
|
-
for (const item of arr) {
|
|
23598
|
-
if (item === null || item === undefined) {
|
|
23599
|
-
continue;
|
|
23600
|
-
}
|
|
23601
|
-
if (typeof item === "object" && !Array.isArray(item)) {
|
|
23602
|
-
const nestedObj = item;
|
|
23603
|
-
for (const [key, value] of Object.entries(nestedObj)) {
|
|
23604
|
-
if (value === null || value === undefined) {
|
|
23605
|
-
continue;
|
|
23606
|
-
}
|
|
23607
|
-
if (typeof value === "object") {
|
|
23608
|
-
if (Array.isArray(value)) {
|
|
23609
|
-
output += `${indent}- **${key}**:
|
|
23610
|
-
`;
|
|
23611
|
-
output += renderArray(value, indentLevel + 1);
|
|
23612
|
-
} else {
|
|
23613
|
-
output += `${indent}- **${key}**
|
|
23614
|
-
`;
|
|
23615
|
-
output += renderObject(value, 4, indentLevel + 1);
|
|
23616
|
-
}
|
|
23617
|
-
} else {
|
|
23618
|
-
const escapedValue = htmlEscape(String(value));
|
|
23619
|
-
output += `${indent}- **${key}**: *${escapedValue}*
|
|
23620
|
-
`;
|
|
23621
|
-
}
|
|
23622
|
-
}
|
|
23623
|
-
} else if (Array.isArray(item)) {
|
|
23624
|
-
output += renderArray(item, indentLevel + 1);
|
|
23625
|
-
} else {
|
|
23626
|
-
const escapedValue = htmlEscape(String(item));
|
|
23627
|
-
output += `${indent}- *${escapedValue}*
|
|
23628
|
-
`;
|
|
23629
|
-
}
|
|
23630
|
-
}
|
|
23631
|
-
return output;
|
|
23632
|
-
};
|
|
23633
|
-
const htmlEscape = (value) => {
|
|
23634
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
23635
|
-
};
|
|
23636
|
-
const renderSkill = (skill) => {
|
|
23637
|
-
const linkedResources = extractSkillLinks(skill.content);
|
|
23638
|
-
return dedent_default`
|
|
23639
|
-
# ${skill.name}
|
|
23640
|
-
|
|
23641
|
-
> Skill root:
|
|
23642
|
-
> ${skill.fullPath}
|
|
23643
|
-
|
|
23644
|
-
Relative file references in this skill resolve from the skill root directory.
|
|
23645
|
-
Always use skill_resource with the exact root-relative path for linked files.
|
|
23646
|
-
|
|
23647
|
-
${linkedResources.length > 0 ? dedent_default`
|
|
23648
|
-
## Linked files detected in skill content
|
|
23649
|
-
|
|
23650
|
-
${linkedResources.map((link) => `- [${link.label}](${link.originalPath}) \u2192 use skill_resource with ${link.resourcePath}`).join(`
|
|
23651
|
-
`)}
|
|
23652
|
-
` : ""}
|
|
23653
|
-
|
|
23654
|
-
${skill.content}
|
|
23655
|
-
|
|
23656
|
-
## Metadata
|
|
23657
|
-
|
|
23658
|
-
${skill.metadata ? renderObject(skill.metadata, 3) : ""}
|
|
23659
|
-
|
|
23660
|
-
## References
|
|
23661
|
-
|
|
23662
|
-
${skill.references ? renderArray(resourceMapToArray(skill.references), 1) : ""}
|
|
23663
|
-
|
|
23664
|
-
## Scripts
|
|
23665
|
-
|
|
23666
|
-
${skill.scripts ? renderArray(resourceMapToArray(skill.scripts), 1) : ""}
|
|
23667
|
-
|
|
23668
|
-
## Assets
|
|
23669
|
-
|
|
23670
|
-
${skill.assets ? renderArray(resourceMapToArray(skill.assets), 1) : ""}
|
|
23671
|
-
`;
|
|
23672
|
-
};
|
|
23673
|
-
const renderResource = (resource) => {
|
|
23674
|
-
return renderObject(resource, 3);
|
|
23675
|
-
};
|
|
23676
|
-
const renderSearchResult = (result) => {
|
|
23677
|
-
return renderObject(result, 3);
|
|
23678
|
-
};
|
|
23679
|
-
const renderer = {
|
|
23680
|
-
format: "md",
|
|
23681
|
-
render(args) {
|
|
23682
|
-
if (args.type === "Skill") {
|
|
23683
|
-
return renderSkill(args.data);
|
|
23684
|
-
}
|
|
23685
|
-
if (args.type === "SkillResource") {
|
|
23686
|
-
return renderResource(args.data);
|
|
23687
|
-
}
|
|
23688
|
-
if (args.type === "SkillSearchResults") {
|
|
23689
|
-
return renderSearchResult(args.data);
|
|
23690
|
-
}
|
|
23691
|
-
return renderObject({}, 3);
|
|
23940
|
+
function createNotifier(args) {
|
|
23941
|
+
const runner = args.shell ?? createDefaultRunner();
|
|
23942
|
+
const platform = args.platform ?? process.platform;
|
|
23943
|
+
async function notify(title, message) {
|
|
23944
|
+
if (!args.config.enabled) {
|
|
23945
|
+
return;
|
|
23692
23946
|
}
|
|
23693
|
-
|
|
23694
|
-
|
|
23695
|
-
|
|
23696
|
-
|
|
23697
|
-
// src/lib/createPromptRenderer.ts
|
|
23698
|
-
function createPromptRenderer() {
|
|
23699
|
-
const renderers = {
|
|
23700
|
-
json: createJsonPromptRenderer(),
|
|
23701
|
-
xml: createXmlPromptRenderer(),
|
|
23702
|
-
md: createMdPromptRenderer()
|
|
23703
|
-
};
|
|
23704
|
-
const getFormatter = (format) => {
|
|
23705
|
-
switch (format) {
|
|
23706
|
-
case "json":
|
|
23707
|
-
return renderers.json.render;
|
|
23708
|
-
case "xml":
|
|
23709
|
-
return renderers.xml.render;
|
|
23710
|
-
case "md":
|
|
23711
|
-
return renderers.md.render;
|
|
23712
|
-
default:
|
|
23713
|
-
throw new Error(`Unsupported format: ${format}`);
|
|
23947
|
+
const notificationCommand = getNotificationCommand(platform, truncateNotificationText(title, 80), truncateNotificationText(message));
|
|
23948
|
+
if (!notificationCommand) {
|
|
23949
|
+
args.logger.debug("Notifications are not supported on this platform.", platform);
|
|
23950
|
+
return;
|
|
23714
23951
|
}
|
|
23715
|
-
|
|
23716
|
-
|
|
23717
|
-
|
|
23718
|
-
|
|
23719
|
-
}
|
|
23720
|
-
|
|
23721
|
-
// src/lib/getModelFormat.ts
|
|
23722
|
-
function getModelFormat(args) {
|
|
23723
|
-
const { modelId, providerId, config: config3 } = args;
|
|
23724
|
-
const modelRenderers = config3.modelRenderers ?? {};
|
|
23725
|
-
if (providerId && modelId) {
|
|
23726
|
-
const combinedKey = `${providerId}-${modelId}`;
|
|
23727
|
-
if (combinedKey in modelRenderers) {
|
|
23728
|
-
return modelRenderers[combinedKey];
|
|
23952
|
+
try {
|
|
23953
|
+
await runner(notificationCommand.command, notificationCommand.args);
|
|
23954
|
+
} catch (error45) {
|
|
23955
|
+
args.logger.warn("Failed to send notification.", error45);
|
|
23729
23956
|
}
|
|
23730
23957
|
}
|
|
23731
|
-
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
|
|
23735
|
-
}
|
|
23736
|
-
|
|
23737
|
-
// src/services/MessageModelIdAccountant.ts
|
|
23738
|
-
function createMessageModelIdAccountant() {
|
|
23739
|
-
const modelUsage = new Map;
|
|
23740
|
-
const track = (info) => {
|
|
23741
|
-
if (!modelUsage.has(info.sessionID)) {
|
|
23742
|
-
modelUsage.set(info.sessionID, {});
|
|
23743
|
-
}
|
|
23744
|
-
const sessionMap = modelUsage.get(info.sessionID);
|
|
23745
|
-
sessionMap[info.messageID] = {
|
|
23746
|
-
modelID: info.modelID,
|
|
23747
|
-
providerID: info.providerID
|
|
23748
|
-
};
|
|
23749
|
-
};
|
|
23750
|
-
const untrackMessage = (args) => {
|
|
23751
|
-
const sessionMap = modelUsage.get(args.sessionID);
|
|
23752
|
-
if (sessionMap && sessionMap[args.messageID]) {
|
|
23753
|
-
delete sessionMap[args.messageID];
|
|
23754
|
-
if (Object.keys(sessionMap).length === 0) {
|
|
23755
|
-
modelUsage.delete(args.sessionID);
|
|
23958
|
+
return {
|
|
23959
|
+
async skillLoaded(skillNames) {
|
|
23960
|
+
if (!args.config.success || skillNames.length === 0) {
|
|
23961
|
+
return;
|
|
23756
23962
|
}
|
|
23963
|
+
const count = skillNames.length;
|
|
23964
|
+
const message = count === 1 ? `Injected skill: ${skillNames[0]}` : `Injected ${count} skills: ${skillNames.join(", ")}`;
|
|
23965
|
+
await notify(NOTIFICATION_TITLE, message);
|
|
23966
|
+
},
|
|
23967
|
+
async resourceLoaded(skillName, relativePath) {
|
|
23968
|
+
if (!args.config.success) {
|
|
23969
|
+
return;
|
|
23970
|
+
}
|
|
23971
|
+
await notify(NOTIFICATION_TITLE, `Injected ${relativePath} from ${skillName}`);
|
|
23972
|
+
},
|
|
23973
|
+
async error(title, message) {
|
|
23974
|
+
if (!args.config.errors) {
|
|
23975
|
+
return;
|
|
23976
|
+
}
|
|
23977
|
+
await notify(title, message);
|
|
23757
23978
|
}
|
|
23758
23979
|
};
|
|
23759
|
-
const untrackSession = (sessionID) => {
|
|
23760
|
-
modelUsage.delete(sessionID);
|
|
23761
|
-
};
|
|
23762
|
-
const getModelInfo = (args) => {
|
|
23763
|
-
const sessionMap = modelUsage.get(args.sessionID);
|
|
23764
|
-
return sessionMap ? sessionMap[args.messageID] : undefined;
|
|
23765
|
-
};
|
|
23766
|
-
const reset3 = () => {
|
|
23767
|
-
modelUsage.clear();
|
|
23768
|
-
};
|
|
23769
|
-
return {
|
|
23770
|
-
reset: reset3,
|
|
23771
|
-
track,
|
|
23772
|
-
untrackMessage,
|
|
23773
|
-
untrackSession,
|
|
23774
|
-
getModelInfo
|
|
23775
|
-
};
|
|
23776
23980
|
}
|
|
23777
23981
|
|
|
23778
23982
|
// src/index.ts
|
|
23779
23983
|
var SkillsPlugin = async (ctx) => {
|
|
23780
23984
|
const config3 = await getPluginConfig(ctx);
|
|
23781
|
-
const api2 = await createApi(config3);
|
|
23985
|
+
const api2 = await createApi(config3, ctx.client);
|
|
23782
23986
|
const sendPrompt = createInstructionInjector(ctx);
|
|
23783
|
-
const
|
|
23784
|
-
const
|
|
23987
|
+
const renderer = createXmlPromptRenderer().render;
|
|
23988
|
+
const notifier = createNotifier({
|
|
23989
|
+
config: config3.notifications,
|
|
23990
|
+
logger: api2.logger
|
|
23991
|
+
});
|
|
23785
23992
|
api2.registry.initialise();
|
|
23786
23993
|
return {
|
|
23787
|
-
"chat.message": async (
|
|
23788
|
-
if (input.messageID && input.model?.providerID && input.model?.modelID) {
|
|
23789
|
-
modelIdAccountant.track({
|
|
23790
|
-
messageID: input.messageID,
|
|
23791
|
-
providerID: input.model.providerID,
|
|
23792
|
-
modelID: input.model.modelID,
|
|
23793
|
-
sessionID: input.sessionID
|
|
23794
|
-
});
|
|
23795
|
-
}
|
|
23796
|
-
const format = getModelFormat({
|
|
23797
|
-
modelId: input.model?.modelID,
|
|
23798
|
-
providerId: input.model?.providerID,
|
|
23799
|
-
config: config3
|
|
23800
|
-
});
|
|
23801
|
-
promptRenderer.getFormatter(format);
|
|
23994
|
+
"chat.message": async (_input, output) => {
|
|
23802
23995
|
for (const part of output.parts) {
|
|
23803
23996
|
if (part.type !== "text") {
|
|
23804
23997
|
continue;
|
|
@@ -23812,22 +24005,24 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23812
24005
|
text: part.text,
|
|
23813
24006
|
registry: api2.registry,
|
|
23814
24007
|
slashCommandName: config3.slashCommandName,
|
|
23815
|
-
enableSkillAliases: config3.enableSkillAliases
|
|
24008
|
+
enableSkillAliases: config3.enableSkillAliases,
|
|
24009
|
+
reservedSlashCommands: config3.reservedSlashCommands
|
|
23816
24010
|
});
|
|
23817
24011
|
if (!rewrittenText) {
|
|
23818
24012
|
continue;
|
|
23819
24013
|
}
|
|
23820
24014
|
part.text = rewrittenText;
|
|
24015
|
+
const parsedSkillName = part.text.match(/\*\*Skill identifier\*\*: ([^\n]+)/)?.[1];
|
|
24016
|
+
if (parsedSkillName) {
|
|
24017
|
+
await notifier.skillLoaded([parsedSkillName]);
|
|
24018
|
+
}
|
|
23821
24019
|
break;
|
|
23822
24020
|
}
|
|
23823
24021
|
},
|
|
23824
24022
|
async event(args) {
|
|
23825
24023
|
switch (args.event.type) {
|
|
23826
|
-
case "
|
|
23827
|
-
|
|
23828
|
-
break;
|
|
23829
|
-
case "session.deleted":
|
|
23830
|
-
modelIdAccountant.untrackSession(args.event.properties.info.id);
|
|
24024
|
+
case "session.error":
|
|
24025
|
+
await notifier.error("OpenCode session error", "A session error occurred in the current session.");
|
|
23831
24026
|
break;
|
|
23832
24027
|
}
|
|
23833
24028
|
},
|
|
@@ -23839,29 +24034,23 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23839
24034
|
skill_names: tool.schema.array(tool.schema.string()).describe("An array of skill names to load.")
|
|
23840
24035
|
},
|
|
23841
24036
|
execute: async (args, toolCtx) => {
|
|
23842
|
-
|
|
23843
|
-
|
|
23844
|
-
|
|
23845
|
-
|
|
23846
|
-
|
|
23847
|
-
|
|
23848
|
-
|
|
23849
|
-
|
|
23850
|
-
|
|
23851
|
-
|
|
23852
|
-
|
|
23853
|
-
|
|
23854
|
-
const results = await api2.loadSkill(args.skill_names);
|
|
23855
|
-
for await (const skill of results.loaded) {
|
|
23856
|
-
await sendPrompt(renderer({ data: skill, type: "Skill" }), {
|
|
23857
|
-
sessionId: toolCtx.sessionID,
|
|
23858
|
-
agent: toolCtx.agent
|
|
24037
|
+
try {
|
|
24038
|
+
const results = await api2.loadSkill(args.skill_names);
|
|
24039
|
+
for await (const skill of results.loaded) {
|
|
24040
|
+
await sendPrompt(renderer({ data: skill, type: "Skill" }), {
|
|
24041
|
+
sessionId: toolCtx.sessionID,
|
|
24042
|
+
agent: toolCtx.agent
|
|
24043
|
+
});
|
|
24044
|
+
}
|
|
24045
|
+
await notifier.skillLoaded(results.loaded.map((skill) => skill.toolName));
|
|
24046
|
+
return JSON.stringify({
|
|
24047
|
+
loaded: results.loaded.map((skill) => skill.toolName),
|
|
24048
|
+
not_found: results.notFound
|
|
23859
24049
|
});
|
|
24050
|
+
} catch (error45) {
|
|
24051
|
+
await notifier.error("skill_use failed", error45 instanceof Error ? error45.message : String(error45));
|
|
24052
|
+
throw error45;
|
|
23860
24053
|
}
|
|
23861
|
-
return JSON.stringify({
|
|
23862
|
-
loaded: results.loaded.map((skill) => skill.toolName),
|
|
23863
|
-
not_found: results.notFound
|
|
23864
|
-
});
|
|
23865
24054
|
}
|
|
23866
24055
|
}),
|
|
23867
24056
|
skill_find: tool({
|
|
@@ -23869,19 +24058,7 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23869
24058
|
args: {
|
|
23870
24059
|
query: tool.schema.union([tool.schema.string(), tool.schema.array(tool.schema.string())]).describe("The search query string or array of strings.")
|
|
23871
24060
|
},
|
|
23872
|
-
execute: async (args
|
|
23873
|
-
const messageID = toolCtx.messageID;
|
|
23874
|
-
const sessionID = toolCtx.sessionID;
|
|
23875
|
-
const modelInfo = modelIdAccountant.getModelInfo({
|
|
23876
|
-
messageID,
|
|
23877
|
-
sessionID
|
|
23878
|
-
});
|
|
23879
|
-
const format = getModelFormat({
|
|
23880
|
-
config: config3,
|
|
23881
|
-
modelId: modelInfo?.modelID,
|
|
23882
|
-
providerId: modelInfo?.providerID
|
|
23883
|
-
});
|
|
23884
|
-
const renderer = promptRenderer.getFormatter(format);
|
|
24061
|
+
execute: async (args) => {
|
|
23885
24062
|
const results = await api2.findSkills(args);
|
|
23886
24063
|
const output = renderer({
|
|
23887
24064
|
data: results,
|
|
@@ -23896,19 +24073,7 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23896
24073
|
task: tool.schema.string().describe("The task or request to recommend skills for."),
|
|
23897
24074
|
limit: tool.schema.number().int().min(1).max(10).optional().describe("Maximum number of recommendations to return.")
|
|
23898
24075
|
},
|
|
23899
|
-
execute: async (args
|
|
23900
|
-
const messageID = toolCtx.messageID;
|
|
23901
|
-
const sessionID = toolCtx.sessionID;
|
|
23902
|
-
const modelInfo = modelIdAccountant.getModelInfo({
|
|
23903
|
-
messageID,
|
|
23904
|
-
sessionID
|
|
23905
|
-
});
|
|
23906
|
-
const format = getModelFormat({
|
|
23907
|
-
config: config3,
|
|
23908
|
-
modelId: modelInfo?.modelID,
|
|
23909
|
-
providerId: modelInfo?.providerID
|
|
23910
|
-
});
|
|
23911
|
-
const renderer = promptRenderer.getFormatter(format);
|
|
24076
|
+
execute: async (args) => {
|
|
23912
24077
|
const results = await api2.recommendSkills(args);
|
|
23913
24078
|
return renderer({
|
|
23914
24079
|
data: results,
|
|
@@ -23923,31 +24088,25 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23923
24088
|
relative_path: tool.schema.string().describe("The relative path to the resource file within the skill directory.")
|
|
23924
24089
|
},
|
|
23925
24090
|
execute: async (args, toolCtx) => {
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
|
|
23929
|
-
|
|
23930
|
-
|
|
23931
|
-
|
|
23932
|
-
|
|
23933
|
-
|
|
23934
|
-
|
|
23935
|
-
|
|
23936
|
-
|
|
23937
|
-
|
|
23938
|
-
|
|
23939
|
-
|
|
23940
|
-
|
|
24091
|
+
try {
|
|
24092
|
+
const result = await api2.readResource(args);
|
|
24093
|
+
if (!result.injection) {
|
|
24094
|
+
throw new Error("Failed to read resource");
|
|
24095
|
+
}
|
|
24096
|
+
await sendPrompt(renderer({ data: result.injection, type: "SkillResource" }), {
|
|
24097
|
+
sessionId: toolCtx.sessionID,
|
|
24098
|
+
agent: toolCtx.agent
|
|
24099
|
+
});
|
|
24100
|
+
await notifier.resourceLoaded(args.skill_name, result.injection.resource_path);
|
|
24101
|
+
return JSON.stringify({
|
|
24102
|
+
result: "Resource injected successfully",
|
|
24103
|
+
resource_path: result.injection.resource_path,
|
|
24104
|
+
resource_mimetype: result.injection.resource_mimetype
|
|
24105
|
+
});
|
|
24106
|
+
} catch (error45) {
|
|
24107
|
+
await notifier.error("skill_resource failed", error45 instanceof Error ? error45.message : String(error45));
|
|
24108
|
+
throw error45;
|
|
23941
24109
|
}
|
|
23942
|
-
await sendPrompt(renderer({ data: result.injection, type: "SkillResource" }), {
|
|
23943
|
-
sessionId: toolCtx.sessionID,
|
|
23944
|
-
agent: toolCtx.agent
|
|
23945
|
-
});
|
|
23946
|
-
return JSON.stringify({
|
|
23947
|
-
result: "Resource injected successfully",
|
|
23948
|
-
resource_path: result.injection.resource_path,
|
|
23949
|
-
resource_mimetype: result.injection.resource_mimetype
|
|
23950
|
-
});
|
|
23951
24110
|
}
|
|
23952
24111
|
})
|
|
23953
24112
|
}
|