opencode-plugin-preload-skills 1.2.0 → 1.4.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 +273 -139
- package/dist/index.cjs +306 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +308 -58
- package/dist/index.js.map +1 -1
- package/package.json +12 -4
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,47 @@ var path = require('path');
|
|
|
7
7
|
var os = require('os');
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
|
+
function estimateTokens(text) {
|
|
11
|
+
return Math.ceil(text.length / 4);
|
|
12
|
+
}
|
|
13
|
+
function matchGlobPattern(filePath, pattern) {
|
|
14
|
+
let regexPattern = pattern.replace(/\*\*\//g, "\0DOUBLESTARSLASH\0").replace(/\*\*/g, "\0DOUBLESTAR\0").replace(/\*/g, "\0SINGLESTAR\0").replace(/\?/g, "\0QUESTION\0");
|
|
15
|
+
regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
16
|
+
regexPattern = regexPattern.replace(/\x00DOUBLESTARSLASH\x00/g, "(?:[^/]+/)*").replace(/\x00DOUBLESTAR\x00/g, ".*").replace(/\x00SINGLESTAR\x00/g, "[^/]*").replace(/\x00QUESTION\x00/g, "[^/]");
|
|
17
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
18
|
+
return regex.test(filePath);
|
|
19
|
+
}
|
|
20
|
+
function checkCondition(condition, projectDir) {
|
|
21
|
+
if (condition.fileExists) {
|
|
22
|
+
const fullPath = path.join(projectDir, condition.fileExists);
|
|
23
|
+
if (!fs.existsSync(fullPath)) return false;
|
|
24
|
+
}
|
|
25
|
+
if (condition.packageHasDependency) {
|
|
26
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
27
|
+
if (!fs.existsSync(packageJsonPath)) return false;
|
|
28
|
+
try {
|
|
29
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
30
|
+
const deps = {
|
|
31
|
+
...packageJson.dependencies,
|
|
32
|
+
...packageJson.devDependencies,
|
|
33
|
+
...packageJson.peerDependencies
|
|
34
|
+
};
|
|
35
|
+
if (!deps[condition.packageHasDependency]) return false;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (condition.envVar) {
|
|
41
|
+
if (!process.env[condition.envVar]) return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function textContainsKeyword(text, keywords) {
|
|
46
|
+
const lowerText = text.toLowerCase();
|
|
47
|
+
return keywords.some((kw) => lowerText.includes(kw.toLowerCase()));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/skill-loader.ts
|
|
10
51
|
var SKILL_FILENAME = "SKILL.md";
|
|
11
52
|
var SKILL_SEARCH_PATHS = [
|
|
12
53
|
(dir) => path.join(dir, ".opencode", "skills"),
|
|
@@ -39,8 +80,19 @@ function parseFrontmatter(content) {
|
|
|
39
80
|
if (descMatch?.[1]) {
|
|
40
81
|
result.description = descMatch[1].trim();
|
|
41
82
|
}
|
|
83
|
+
const summaryMatch = frontmatter.match(/^summary:\s*(.+)$/m);
|
|
84
|
+
if (summaryMatch?.[1]) {
|
|
85
|
+
result.summary = summaryMatch[1].trim();
|
|
86
|
+
}
|
|
42
87
|
return result;
|
|
43
88
|
}
|
|
89
|
+
function extractAutoSummary(content, maxLength = 500) {
|
|
90
|
+
const withoutFrontmatter = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "");
|
|
91
|
+
const firstSection = withoutFrontmatter.split(/\n##\s/)[0] ?? "";
|
|
92
|
+
const cleaned = firstSection.replace(/^#\s+.+\n?/, "").replace(/\n+/g, " ").trim();
|
|
93
|
+
if (cleaned.length <= maxLength) return cleaned;
|
|
94
|
+
return cleaned.slice(0, maxLength).replace(/\s+\S*$/, "") + "...";
|
|
95
|
+
}
|
|
44
96
|
function loadSkill(skillName, projectDir) {
|
|
45
97
|
const filePath = findSkillFile(skillName, projectDir);
|
|
46
98
|
if (!filePath) {
|
|
@@ -48,12 +100,14 @@ function loadSkill(skillName, projectDir) {
|
|
|
48
100
|
}
|
|
49
101
|
try {
|
|
50
102
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
51
|
-
const { name, description } = parseFrontmatter(content);
|
|
103
|
+
const { name, description, summary } = parseFrontmatter(content);
|
|
52
104
|
return {
|
|
53
105
|
name: name ?? skillName,
|
|
54
106
|
description: description ?? "",
|
|
107
|
+
summary: summary ?? extractAutoSummary(content),
|
|
55
108
|
content,
|
|
56
|
-
filePath
|
|
109
|
+
filePath,
|
|
110
|
+
tokenCount: estimateTokens(content)
|
|
57
111
|
};
|
|
58
112
|
} catch {
|
|
59
113
|
return null;
|
|
@@ -72,28 +126,58 @@ function loadSkills(skillNames, projectDir) {
|
|
|
72
126
|
}
|
|
73
127
|
return skills;
|
|
74
128
|
}
|
|
75
|
-
function formatSkillsForInjection(skills) {
|
|
129
|
+
function formatSkillsForInjection(skills, options = false) {
|
|
76
130
|
if (!Array.isArray(skills) || skills.length === 0) {
|
|
77
131
|
return "";
|
|
78
132
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
133
|
+
const opts = typeof options === "boolean" ? { useSummaries: options } : options;
|
|
134
|
+
const globalUseSummaries = opts.useSummaries ?? false;
|
|
135
|
+
const skillSettings = opts.skillSettings ?? {};
|
|
136
|
+
const parts = skills.map((skill) => {
|
|
137
|
+
const perSkillSetting = skillSettings[skill.name]?.useSummary;
|
|
138
|
+
const shouldUseSummary = perSkillSetting ?? globalUseSummaries;
|
|
139
|
+
const content = shouldUseSummary && skill.summary ? skill.summary : skill.content;
|
|
140
|
+
return `<preloaded-skill name="${skill.name}">
|
|
141
|
+
${content}
|
|
142
|
+
</preloaded-skill>`;
|
|
143
|
+
});
|
|
84
144
|
return `<preloaded-skills>
|
|
85
145
|
The following skills have been automatically loaded for this session:
|
|
86
146
|
|
|
87
147
|
${parts.join("\n\n")}
|
|
88
148
|
</preloaded-skills>`;
|
|
89
149
|
}
|
|
150
|
+
function calculateTotalTokens(skills) {
|
|
151
|
+
return skills.reduce((sum, skill) => sum + skill.tokenCount, 0);
|
|
152
|
+
}
|
|
153
|
+
function filterSkillsByTokenBudget(skills, maxTokens) {
|
|
154
|
+
const result = [];
|
|
155
|
+
let totalTokens = 0;
|
|
156
|
+
for (const skill of skills) {
|
|
157
|
+
if (totalTokens + skill.tokenCount <= maxTokens) {
|
|
158
|
+
result.push(skill);
|
|
159
|
+
totalTokens += skill.tokenCount;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
90
164
|
|
|
91
165
|
// src/index.ts
|
|
92
166
|
var CONFIG_FILENAME = "preload-skills.json";
|
|
167
|
+
var ANALYTICS_FILENAME = "preload-skills-analytics.json";
|
|
93
168
|
var FILE_TOOLS = ["read", "edit", "write", "glob", "grep"];
|
|
94
169
|
var DEFAULT_CONFIG = {
|
|
95
170
|
skills: [],
|
|
96
171
|
fileTypeSkills: {},
|
|
172
|
+
agentSkills: {},
|
|
173
|
+
pathPatterns: {},
|
|
174
|
+
contentTriggers: {},
|
|
175
|
+
groups: {},
|
|
176
|
+
conditionalSkills: [],
|
|
177
|
+
skillSettings: {},
|
|
178
|
+
maxTokens: void 0,
|
|
179
|
+
useSummaries: false,
|
|
180
|
+
analytics: false,
|
|
97
181
|
persistAfterCompaction: true,
|
|
98
182
|
debug: false
|
|
99
183
|
};
|
|
@@ -110,7 +194,7 @@ function findConfigFile(projectDir) {
|
|
|
110
194
|
}
|
|
111
195
|
return null;
|
|
112
196
|
}
|
|
113
|
-
function
|
|
197
|
+
function parseStringArrayRecord(raw) {
|
|
114
198
|
if (!raw || typeof raw !== "object") return {};
|
|
115
199
|
const result = {};
|
|
116
200
|
for (const [key, value] of Object.entries(raw)) {
|
|
@@ -120,6 +204,28 @@ function parseFileTypeSkills(raw) {
|
|
|
120
204
|
}
|
|
121
205
|
return result;
|
|
122
206
|
}
|
|
207
|
+
function parseConditionalSkills(raw) {
|
|
208
|
+
if (!Array.isArray(raw)) return [];
|
|
209
|
+
return raw.filter(
|
|
210
|
+
(item) => typeof item === "object" && item !== null && typeof item.skill === "string" && typeof item.if === "object"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
function parseSkillSettings(raw) {
|
|
214
|
+
if (!raw || typeof raw !== "object") return {};
|
|
215
|
+
const result = {};
|
|
216
|
+
for (const [skillName, settings] of Object.entries(raw)) {
|
|
217
|
+
if (typeof settings === "object" && settings !== null) {
|
|
218
|
+
const parsed = {};
|
|
219
|
+
if ("useSummary" in settings && typeof settings.useSummary === "boolean") {
|
|
220
|
+
parsed.useSummary = settings.useSummary;
|
|
221
|
+
}
|
|
222
|
+
if (Object.keys(parsed).length > 0) {
|
|
223
|
+
result[skillName] = parsed;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
123
229
|
function loadConfigFile(projectDir) {
|
|
124
230
|
const configPath = findConfigFile(projectDir);
|
|
125
231
|
if (!configPath) {
|
|
@@ -130,7 +236,16 @@ function loadConfigFile(projectDir) {
|
|
|
130
236
|
const parsed = JSON.parse(content);
|
|
131
237
|
return {
|
|
132
238
|
skills: Array.isArray(parsed.skills) ? parsed.skills : [],
|
|
133
|
-
fileTypeSkills:
|
|
239
|
+
fileTypeSkills: parseStringArrayRecord(parsed.fileTypeSkills),
|
|
240
|
+
agentSkills: parseStringArrayRecord(parsed.agentSkills),
|
|
241
|
+
pathPatterns: parseStringArrayRecord(parsed.pathPatterns),
|
|
242
|
+
contentTriggers: parseStringArrayRecord(parsed.contentTriggers),
|
|
243
|
+
groups: parseStringArrayRecord(parsed.groups),
|
|
244
|
+
conditionalSkills: parseConditionalSkills(parsed.conditionalSkills),
|
|
245
|
+
skillSettings: parseSkillSettings(parsed.skillSettings),
|
|
246
|
+
maxTokens: typeof parsed.maxTokens === "number" ? parsed.maxTokens : void 0,
|
|
247
|
+
useSummaries: typeof parsed.useSummaries === "boolean" ? parsed.useSummaries : void 0,
|
|
248
|
+
analytics: typeof parsed.analytics === "boolean" ? parsed.analytics : void 0,
|
|
134
249
|
persistAfterCompaction: typeof parsed.persistAfterCompaction === "boolean" ? parsed.persistAfterCompaction : void 0,
|
|
135
250
|
debug: typeof parsed.debug === "boolean" ? parsed.debug : void 0
|
|
136
251
|
};
|
|
@@ -154,8 +269,29 @@ function getSkillsForExtension(ext, fileTypeSkills) {
|
|
|
154
269
|
}
|
|
155
270
|
return [...new Set(skills)];
|
|
156
271
|
}
|
|
272
|
+
function getSkillsForPath(filePath, pathPatterns) {
|
|
273
|
+
const skills = [];
|
|
274
|
+
for (const [pattern, skillNames] of Object.entries(pathPatterns)) {
|
|
275
|
+
if (matchGlobPattern(filePath, pattern)) {
|
|
276
|
+
skills.push(...skillNames);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return [...new Set(skills)];
|
|
280
|
+
}
|
|
281
|
+
function resolveSkillGroups(skillNames, groups) {
|
|
282
|
+
const resolved = [];
|
|
283
|
+
for (const name of skillNames) {
|
|
284
|
+
if (name.startsWith("@") && groups[name.slice(1)]) {
|
|
285
|
+
resolved.push(...groups[name.slice(1)]);
|
|
286
|
+
} else {
|
|
287
|
+
resolved.push(name);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return [...new Set(resolved)];
|
|
291
|
+
}
|
|
157
292
|
var PreloadSkillsPlugin = async (ctx) => {
|
|
158
293
|
const sessionStates = /* @__PURE__ */ new Map();
|
|
294
|
+
const analyticsData = /* @__PURE__ */ new Map();
|
|
159
295
|
const fileConfig = loadConfigFile(ctx.directory);
|
|
160
296
|
const config = {
|
|
161
297
|
...DEFAULT_CONFIG,
|
|
@@ -172,57 +308,176 @@ var PreloadSkillsPlugin = async (ctx) => {
|
|
|
172
308
|
}
|
|
173
309
|
});
|
|
174
310
|
};
|
|
311
|
+
const trackSkillUsage = (sessionID, skillName, triggerType) => {
|
|
312
|
+
if (!config.analytics) return;
|
|
313
|
+
if (!analyticsData.has(sessionID)) {
|
|
314
|
+
analyticsData.set(sessionID, {
|
|
315
|
+
sessionId: sessionID,
|
|
316
|
+
skillUsage: /* @__PURE__ */ new Map()
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
const data = analyticsData.get(sessionID);
|
|
320
|
+
const now = Date.now();
|
|
321
|
+
if (data.skillUsage.has(skillName)) {
|
|
322
|
+
const stats = data.skillUsage.get(skillName);
|
|
323
|
+
stats.loadCount++;
|
|
324
|
+
stats.lastLoaded = now;
|
|
325
|
+
} else {
|
|
326
|
+
data.skillUsage.set(skillName, {
|
|
327
|
+
skillName,
|
|
328
|
+
loadCount: 1,
|
|
329
|
+
triggerType,
|
|
330
|
+
firstLoaded: now,
|
|
331
|
+
lastLoaded: now
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
const saveAnalytics = () => {
|
|
336
|
+
if (!config.analytics) return;
|
|
337
|
+
try {
|
|
338
|
+
const analyticsPath = path.join(ctx.directory, ".opencode", ANALYTICS_FILENAME);
|
|
339
|
+
const dir = path.dirname(analyticsPath);
|
|
340
|
+
if (!fs.existsSync(dir)) {
|
|
341
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
342
|
+
}
|
|
343
|
+
const serializable = {};
|
|
344
|
+
for (const [sessionId, data] of analyticsData) {
|
|
345
|
+
serializable[sessionId] = {
|
|
346
|
+
sessionId: data.sessionId,
|
|
347
|
+
skillUsage: Object.fromEntries(data.skillUsage)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
fs.writeFileSync(analyticsPath, JSON.stringify(serializable, null, 2));
|
|
351
|
+
} catch {
|
|
352
|
+
log("warn", "Failed to save analytics");
|
|
353
|
+
}
|
|
354
|
+
};
|
|
175
355
|
const skillCache = /* @__PURE__ */ new Map();
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
356
|
+
const loadSkillsWithBudget = (skillNames, currentTokens, triggerType, sessionID) => {
|
|
357
|
+
const resolved = resolveSkillGroups(skillNames, config.groups ?? {});
|
|
358
|
+
let skills = loadSkills(resolved, ctx.directory);
|
|
359
|
+
for (const skill of skills) {
|
|
360
|
+
skillCache.set(skill.name, skill);
|
|
179
361
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
362
|
+
if (config.maxTokens) {
|
|
363
|
+
const remainingBudget = config.maxTokens - currentTokens;
|
|
364
|
+
skills = filterSkillsByTokenBudget(skills, remainingBudget);
|
|
365
|
+
}
|
|
366
|
+
for (const skill of skills) {
|
|
367
|
+
trackSkillUsage(sessionID, skill.name, triggerType);
|
|
183
368
|
}
|
|
184
|
-
return
|
|
369
|
+
return {
|
|
370
|
+
skills,
|
|
371
|
+
tokensUsed: calculateTotalTokens(skills)
|
|
372
|
+
};
|
|
373
|
+
};
|
|
374
|
+
const resolveConditionalSkills = () => {
|
|
375
|
+
if (!config.conditionalSkills?.length) return [];
|
|
376
|
+
const resolved = [];
|
|
377
|
+
for (const { skill, if: condition } of config.conditionalSkills) {
|
|
378
|
+
if (checkCondition(condition, ctx.directory)) {
|
|
379
|
+
resolved.push(skill);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return resolved;
|
|
185
383
|
};
|
|
186
384
|
let initialSkills = [];
|
|
187
385
|
let initialFormattedContent = "";
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
386
|
+
let initialTokensUsed = 0;
|
|
387
|
+
const allInitialSkillNames = [
|
|
388
|
+
...config.skills,
|
|
389
|
+
...resolveConditionalSkills()
|
|
390
|
+
];
|
|
391
|
+
if (allInitialSkillNames.length > 0) {
|
|
392
|
+
const result = loadSkillsWithBudget(
|
|
393
|
+
allInitialSkillNames,
|
|
394
|
+
0,
|
|
395
|
+
"initial",
|
|
396
|
+
"__init__"
|
|
397
|
+
);
|
|
398
|
+
initialSkills = result.skills;
|
|
399
|
+
initialTokensUsed = result.tokensUsed;
|
|
400
|
+
initialFormattedContent = formatSkillsForInjection(
|
|
401
|
+
initialSkills,
|
|
402
|
+
{ useSummaries: config.useSummaries, skillSettings: config.skillSettings }
|
|
403
|
+
);
|
|
194
404
|
const loadedNames = initialSkills.map((s) => s.name);
|
|
195
|
-
const missingNames =
|
|
196
|
-
|
|
405
|
+
const missingNames = allInitialSkillNames.filter(
|
|
406
|
+
(s) => !loadedNames.includes(s) && !s.startsWith("@")
|
|
407
|
+
);
|
|
408
|
+
log("info", `Loaded ${initialSkills.length} initial skills`, {
|
|
197
409
|
loaded: loadedNames,
|
|
410
|
+
tokens: initialTokensUsed,
|
|
198
411
|
missing: missingNames.length > 0 ? missingNames : void 0
|
|
199
412
|
});
|
|
200
|
-
if (missingNames.length > 0) {
|
|
201
|
-
log("warn", "Some configured skills were not found", {
|
|
202
|
-
missing: missingNames
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
413
|
}
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
414
|
+
const hasTriggeredSkills = Object.keys(config.fileTypeSkills ?? {}).length > 0 || Object.keys(config.agentSkills ?? {}).length > 0 || Object.keys(config.pathPatterns ?? {}).length > 0 || Object.keys(config.contentTriggers ?? {}).length > 0;
|
|
415
|
+
if (allInitialSkillNames.length === 0 && !hasTriggeredSkills) {
|
|
208
416
|
log("warn", "No skills configured. Create .opencode/preload-skills.json");
|
|
209
417
|
}
|
|
210
418
|
const getSessionState = (sessionID) => {
|
|
211
419
|
if (!sessionStates.has(sessionID)) {
|
|
212
420
|
sessionStates.set(sessionID, {
|
|
213
421
|
initialSkillsInjected: false,
|
|
214
|
-
loadedSkills: new Set(initialSkills.map((s) => s.name))
|
|
422
|
+
loadedSkills: new Set(initialSkills.map((s) => s.name)),
|
|
423
|
+
totalTokensUsed: initialTokensUsed
|
|
215
424
|
});
|
|
216
425
|
}
|
|
217
426
|
return sessionStates.get(sessionID);
|
|
218
427
|
};
|
|
219
428
|
const pendingSkillInjections = /* @__PURE__ */ new Map();
|
|
429
|
+
const queueSkillsForInjection = (sessionID, skillNames, triggerType, state) => {
|
|
430
|
+
const newSkillNames = skillNames.filter((name) => !state.loadedSkills.has(name));
|
|
431
|
+
if (newSkillNames.length === 0) return;
|
|
432
|
+
const result = loadSkillsWithBudget(
|
|
433
|
+
newSkillNames,
|
|
434
|
+
state.totalTokensUsed,
|
|
435
|
+
triggerType,
|
|
436
|
+
sessionID
|
|
437
|
+
);
|
|
438
|
+
if (result.skills.length > 0) {
|
|
439
|
+
for (const skill of result.skills) {
|
|
440
|
+
state.loadedSkills.add(skill.name);
|
|
441
|
+
}
|
|
442
|
+
state.totalTokensUsed += result.tokensUsed;
|
|
443
|
+
const existing = pendingSkillInjections.get(sessionID) ?? [];
|
|
444
|
+
pendingSkillInjections.set(sessionID, [...existing, ...result.skills]);
|
|
445
|
+
log("debug", `Queued ${triggerType} skills for injection`, {
|
|
446
|
+
sessionID,
|
|
447
|
+
skills: result.skills.map((s) => s.name),
|
|
448
|
+
tokens: result.tokensUsed
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
};
|
|
220
452
|
return {
|
|
221
453
|
"chat.message": async (input, output) => {
|
|
222
454
|
if (!input.sessionID) return;
|
|
223
455
|
const state = getSessionState(input.sessionID);
|
|
224
456
|
const firstTextPart = output.parts.find((p) => p.type === "text");
|
|
225
457
|
if (!firstTextPart || !("text" in firstTextPart)) return;
|
|
458
|
+
const messageText = firstTextPart.text;
|
|
459
|
+
if (input.agent && config.agentSkills?.[input.agent]) {
|
|
460
|
+
queueSkillsForInjection(
|
|
461
|
+
input.sessionID,
|
|
462
|
+
config.agentSkills[input.agent],
|
|
463
|
+
"agent",
|
|
464
|
+
state
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
if (config.contentTriggers) {
|
|
468
|
+
for (const [keyword, skillNames] of Object.entries(
|
|
469
|
+
config.contentTriggers
|
|
470
|
+
)) {
|
|
471
|
+
if (textContainsKeyword(messageText, [keyword])) {
|
|
472
|
+
queueSkillsForInjection(
|
|
473
|
+
input.sessionID,
|
|
474
|
+
skillNames,
|
|
475
|
+
"content",
|
|
476
|
+
state
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
226
481
|
const contentToInject = [];
|
|
227
482
|
if (!state.initialSkillsInjected && initialFormattedContent) {
|
|
228
483
|
contentToInject.push(initialFormattedContent);
|
|
@@ -234,10 +489,10 @@ var PreloadSkillsPlugin = async (ctx) => {
|
|
|
234
489
|
}
|
|
235
490
|
const pending = pendingSkillInjections.get(input.sessionID);
|
|
236
491
|
if (pending && pending.length > 0) {
|
|
237
|
-
const formatted = formatSkillsForInjection(pending);
|
|
492
|
+
const formatted = formatSkillsForInjection(pending, { useSummaries: config.useSummaries, skillSettings: config.skillSettings });
|
|
238
493
|
if (formatted) {
|
|
239
494
|
contentToInject.push(formatted);
|
|
240
|
-
log("info", "Injected
|
|
495
|
+
log("info", "Injected triggered skills", {
|
|
241
496
|
sessionID: input.sessionID,
|
|
242
497
|
skills: pending.map((s) => s.name)
|
|
243
498
|
});
|
|
@@ -253,7 +508,6 @@ ${firstTextPart.text}`;
|
|
|
253
508
|
}
|
|
254
509
|
},
|
|
255
510
|
"tool.execute.after": async (input, _output) => {
|
|
256
|
-
if (!hasFileTypeSkills) return;
|
|
257
511
|
if (!FILE_TOOLS.includes(input.tool)) return;
|
|
258
512
|
if (!input.sessionID) return;
|
|
259
513
|
const state = getSessionState(input.sessionID);
|
|
@@ -262,27 +516,17 @@ ${firstTextPart.text}`;
|
|
|
262
516
|
const filePath = getFilePathFromArgs(toolArgs);
|
|
263
517
|
if (!filePath) return;
|
|
264
518
|
const ext = path.extname(filePath);
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
for (const name of skillNames) {
|
|
270
|
-
if (state.loadedSkills.has(name)) continue;
|
|
271
|
-
const skill = getOrLoadSkill(name);
|
|
272
|
-
if (skill) {
|
|
273
|
-
newSkills.push(skill);
|
|
274
|
-
state.loadedSkills.add(name);
|
|
519
|
+
if (ext && config.fileTypeSkills) {
|
|
520
|
+
const extSkills = getSkillsForExtension(ext, config.fileTypeSkills);
|
|
521
|
+
if (extSkills.length > 0) {
|
|
522
|
+
queueSkillsForInjection(input.sessionID, extSkills, "fileType", state);
|
|
275
523
|
}
|
|
276
524
|
}
|
|
277
|
-
if (
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
filePath,
|
|
283
|
-
extension: ext,
|
|
284
|
-
skills: newSkills.map((s) => s.name)
|
|
285
|
-
});
|
|
525
|
+
if (config.pathPatterns) {
|
|
526
|
+
const pathSkills = getSkillsForPath(filePath, config.pathPatterns);
|
|
527
|
+
if (pathSkills.length > 0) {
|
|
528
|
+
queueSkillsForInjection(input.sessionID, pathSkills, "path", state);
|
|
529
|
+
}
|
|
286
530
|
}
|
|
287
531
|
},
|
|
288
532
|
"experimental.session.compacting": async (input, output) => {
|
|
@@ -295,7 +539,10 @@ ${firstTextPart.text}`;
|
|
|
295
539
|
if (skill) allLoadedSkills.push(skill);
|
|
296
540
|
}
|
|
297
541
|
if (allLoadedSkills.length === 0) return;
|
|
298
|
-
const formatted = formatSkillsForInjection(
|
|
542
|
+
const formatted = formatSkillsForInjection(
|
|
543
|
+
allLoadedSkills,
|
|
544
|
+
{ useSummaries: config.useSummaries, skillSettings: config.skillSettings }
|
|
545
|
+
);
|
|
299
546
|
output.context.push(
|
|
300
547
|
`## Preloaded Skills
|
|
301
548
|
|
|
@@ -308,13 +555,16 @@ ${formatted}`
|
|
|
308
555
|
sessionID: input.sessionID,
|
|
309
556
|
skillCount: allLoadedSkills.length
|
|
310
557
|
});
|
|
558
|
+
saveAnalytics();
|
|
311
559
|
},
|
|
312
560
|
event: async ({ event }) => {
|
|
313
561
|
if (event.type === "session.deleted" && "sessionID" in event.properties) {
|
|
314
562
|
const sessionID = event.properties.sessionID;
|
|
315
563
|
sessionStates.delete(sessionID);
|
|
316
564
|
pendingSkillInjections.delete(sessionID);
|
|
565
|
+
analyticsData.delete(sessionID);
|
|
317
566
|
log("debug", "Cleaned up session state", { sessionID });
|
|
567
|
+
saveAnalytics();
|
|
318
568
|
}
|
|
319
569
|
}
|
|
320
570
|
};
|