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