lynkr 9.9.1 → 9.10.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.
Files changed (67) hide show
  1. package/README.md +50 -9
  2. package/bin/cli.js +2 -0
  3. package/bin/lynkr-init.js +4 -12
  4. package/bin/lynkr-reset.js +71 -0
  5. package/config/model-tiers.json +199 -52
  6. package/package.json +2 -2
  7. package/scripts/validate-difficulty-classifier.js +27 -6
  8. package/src/api/providers-handler.js +0 -1
  9. package/src/api/router.js +275 -160
  10. package/src/clients/databricks.js +95 -6
  11. package/src/config/index.js +3 -43
  12. package/src/orchestrator/index.js +117 -1235
  13. package/src/orchestrator/passthrough-stream.js +382 -0
  14. package/src/orchestrator/sse-transformer.js +408 -0
  15. package/src/routing/affinity-store.js +17 -3
  16. package/src/routing/difficulty-classifier.js +52 -10
  17. package/src/routing/index.js +35 -3
  18. package/src/routing/intent-score.js +20 -2
  19. package/src/routing/session-affinity.js +8 -1
  20. package/src/routing/side-channel-detector.js +103 -0
  21. package/src/server.js +0 -46
  22. package/src/agents/context-manager.js +0 -236
  23. package/src/agents/decomposition/dispatcher.js +0 -185
  24. package/src/agents/decomposition/gate.js +0 -136
  25. package/src/agents/decomposition/index.js +0 -183
  26. package/src/agents/decomposition/model-call.js +0 -75
  27. package/src/agents/decomposition/planner.js +0 -223
  28. package/src/agents/decomposition/synthesizer.js +0 -89
  29. package/src/agents/decomposition/telemetry.js +0 -55
  30. package/src/agents/definitions/loader.js +0 -653
  31. package/src/agents/executor.js +0 -457
  32. package/src/agents/index.js +0 -165
  33. package/src/agents/parallel-coordinator.js +0 -68
  34. package/src/agents/reflector.js +0 -331
  35. package/src/agents/skillbook.js +0 -331
  36. package/src/agents/store.js +0 -259
  37. package/src/edits/index.js +0 -171
  38. package/src/indexer/babel-parser.js +0 -213
  39. package/src/indexer/index.js +0 -1629
  40. package/src/indexer/navigation/index.js +0 -32
  41. package/src/indexer/navigation/providers/treeSitter.js +0 -36
  42. package/src/indexer/parser.js +0 -443
  43. package/src/tasks/store.js +0 -349
  44. package/src/tests/coverage.js +0 -173
  45. package/src/tests/index.js +0 -171
  46. package/src/tests/store.js +0 -213
  47. package/src/tools/agent-task.js +0 -145
  48. package/src/tools/code-mode.js +0 -304
  49. package/src/tools/decompose.js +0 -91
  50. package/src/tools/edits.js +0 -94
  51. package/src/tools/execution.js +0 -171
  52. package/src/tools/git.js +0 -1346
  53. package/src/tools/index.js +0 -306
  54. package/src/tools/indexer.js +0 -360
  55. package/src/tools/lazy-loader.js +0 -366
  56. package/src/tools/mcp-remote.js +0 -88
  57. package/src/tools/mcp.js +0 -116
  58. package/src/tools/process.js +0 -167
  59. package/src/tools/smart-selection.js +0 -180
  60. package/src/tools/stubs.js +0 -55
  61. package/src/tools/tasks.js +0 -260
  62. package/src/tools/tests.js +0 -132
  63. package/src/tools/tinyfish.js +0 -358
  64. package/src/tools/truncate.js +0 -106
  65. package/src/tools/web-client.js +0 -71
  66. package/src/tools/web.js +0 -415
  67. package/src/tools/workspace.js +0 -204
@@ -1,331 +0,0 @@
1
- const logger = require("../logger");
2
-
3
- /**
4
- * Reflector - Analyzes agent executions and extracts sophisticated patterns
5
- * Inspired by ACE framework's Reflector role
6
- */
7
- class Reflector {
8
- /**
9
- * Reflect on execution and generate skill recommendations
10
- * @param {Object} context - Execution context
11
- * @param {boolean} successful - Whether execution succeeded
12
- * @returns {Array} - Array of skill objects
13
- */
14
- static reflect(context, successful) {
15
- const skills = [];
16
-
17
- // Skip trivial executions
18
- if (context.steps < 2) {
19
- return skills;
20
- }
21
-
22
- // Analyze tool usage patterns
23
- const toolPatterns = this._analyzeToolUsage(context);
24
- skills.push(...toolPatterns);
25
-
26
- // Analyze execution efficiency
27
- const efficiencyPatterns = this._analyzeEfficiency(context, successful);
28
- skills.push(...efficiencyPatterns);
29
-
30
- // Analyze error handling
31
- const errorPatterns = this._analyzeErrors(context, successful);
32
- skills.push(...errorPatterns);
33
-
34
- // Analyze task-specific patterns
35
- const taskPatterns = this._analyzeTaskPatterns(context, successful);
36
- skills.push(...taskPatterns);
37
-
38
- logger.debug({
39
- agentType: context.agentName,
40
- skillsExtracted: skills.length,
41
- successful
42
- }, "Reflection complete");
43
-
44
- return skills;
45
- }
46
-
47
- /**
48
- * Analyze tool usage patterns
49
- */
50
- static _analyzeToolUsage(context) {
51
- const patterns = [];
52
- const transcript = context.transcript || [];
53
-
54
- // Extract tool sequence
55
- const toolSequence = transcript
56
- .filter(entry => entry.type === "tool_call" && !entry.error)
57
- .map(entry => entry.toolName);
58
-
59
- if (toolSequence.length === 0) {
60
- return patterns;
61
- }
62
-
63
- // Pattern 1: Common tool combinations
64
- if (toolSequence.length >= 2) {
65
- const uniqueTools = [...new Set(toolSequence)];
66
- const taskType = this._inferTaskType(context.taskPrompt);
67
-
68
- if (taskType) {
69
- patterns.push({
70
- pattern: `${taskType} requires ${uniqueTools.length} tools`,
71
- action: `Use: ${uniqueTools.join(" → ")}`,
72
- reasoning: `Observed successful sequence for ${taskType.toLowerCase()}`,
73
- tools: uniqueTools,
74
- confidence: 0.6
75
- });
76
- }
77
- }
78
-
79
- // Pattern 2: Tool repetition (indicates iteration/refinement)
80
- const toolCounts = {};
81
- toolSequence.forEach(tool => {
82
- toolCounts[tool] = (toolCounts[tool] || 0) + 1;
83
- });
84
-
85
- const repeatedTools = Object.entries(toolCounts)
86
- .filter(([_, count]) => count > 1)
87
- .map(([tool, _]) => tool);
88
-
89
- if (repeatedTools.length > 0) {
90
- patterns.push({
91
- pattern: "Iterative refinement needed",
92
- action: `Multiple calls to: ${repeatedTools.join(", ")}`,
93
- reasoning: "Task required iterative approach",
94
- tools: repeatedTools,
95
- confidence: 0.5
96
- });
97
- }
98
-
99
- // Pattern 3: First tool used (often indicates primary approach)
100
- if (toolSequence.length > 0) {
101
- const firstTool = toolSequence[0];
102
- const taskType = this._inferTaskType(context.taskPrompt);
103
-
104
- if (taskType) {
105
- patterns.push({
106
- pattern: `Start ${taskType} with exploration`,
107
- action: `Begin with ${firstTool}`,
108
- reasoning: `${firstTool} is effective starting point`,
109
- tools: [firstTool],
110
- confidence: 0.55
111
- });
112
- }
113
- }
114
-
115
- return patterns;
116
- }
117
-
118
- /**
119
- * Analyze execution efficiency
120
- */
121
- static _analyzeEfficiency(context, successful) {
122
- const patterns = [];
123
-
124
- if (!successful) {
125
- return patterns; // Only analyze successful executions for efficiency
126
- }
127
-
128
- const efficiency = context.steps / context.maxSteps;
129
-
130
- // Pattern 1: Highly efficient (< 50% of max steps)
131
- if (efficiency < 0.5) {
132
- const toolsUsed = this._extractUniqueTools(context);
133
-
134
- patterns.push({
135
- pattern: "Efficient execution pattern",
136
- action: `Quick resolution in ${context.steps} steps using ${toolsUsed.join(", ")}`,
137
- reasoning: `Completed efficiently (${Math.round(efficiency * 100)}% of max steps)`,
138
- tools: toolsUsed,
139
- confidence: 0.75
140
- });
141
- }
142
-
143
- // Pattern 2: Near max steps (> 80% of max)
144
- if (efficiency > 0.8) {
145
- patterns.push({
146
- pattern: "Complex task requiring many steps",
147
- action: "Consider breaking down or optimizing approach",
148
- reasoning: `Used ${context.steps}/${context.maxSteps} steps - may need optimization`,
149
- tools: this._extractUniqueTools(context),
150
- confidence: 0.4
151
- });
152
- }
153
-
154
- // Pattern 3: Token usage efficiency
155
- const tokensPerStep = (context.inputTokens + context.outputTokens) / context.steps;
156
- if (tokensPerStep < 1000) {
157
- patterns.push({
158
- pattern: "Token-efficient approach",
159
- action: "Concise tool usage and responses",
160
- reasoning: `Low token usage per step (~${Math.round(tokensPerStep)} tokens)`,
161
- tools: [],
162
- confidence: 0.6
163
- });
164
- }
165
-
166
- return patterns;
167
- }
168
-
169
- /**
170
- * Analyze error handling patterns
171
- */
172
- static _analyzeErrors(context, successful) {
173
- const patterns = [];
174
- const transcript = context.transcript || [];
175
-
176
- const errorEntries = transcript.filter(entry =>
177
- entry.type === "tool_call" && entry.error
178
- );
179
-
180
- if (errorEntries.length === 0) {
181
- return patterns;
182
- }
183
-
184
- // Pattern 1: Recovered from errors
185
- if (successful) {
186
- const failedTools = errorEntries.map(e => e.toolName);
187
- // Tools invoked *after* the last error entry are the recovery path.
188
- // Use the entry's position in the transcript, not its timestamp
189
- // (slicing by a ~1.7e12 timestamp always yielded []).
190
- const lastErrorEntry = errorEntries[errorEntries.length - 1];
191
- const lastErrorIndex = transcript.indexOf(lastErrorEntry);
192
- const recoveryTools = transcript
193
- .slice(lastErrorIndex + 1)
194
- .filter(e => e.type === "tool_call" && !e.error)
195
- .map(e => e.toolName);
196
-
197
- patterns.push({
198
- pattern: "Error recovery strategy",
199
- action: `After ${failedTools[0]} fails, try ${recoveryTools[0] || "alternative approach"}`,
200
- reasoning: `Successfully recovered from ${errorEntries.length} error(s)`,
201
- tools: [...new Set([...failedTools, ...recoveryTools])],
202
- confidence: 0.65
203
- });
204
- }
205
-
206
- // Pattern 2: Failed due to errors
207
- if (!successful) {
208
- const failedTools = [...new Set(errorEntries.map(e => e.toolName))];
209
-
210
- patterns.push({
211
- pattern: `Avoid ${failedTools.join(", ")} for this task type`,
212
- action: "Use alternative tools",
213
- reasoning: `These tools failed for ${this._inferTaskType(context.taskPrompt)}`,
214
- tools: failedTools,
215
- confidence: 0.3
216
- });
217
- }
218
-
219
- return patterns;
220
- }
221
-
222
- /**
223
- * Analyze task-specific patterns
224
- */
225
- static _analyzeTaskPatterns(context, successful) {
226
- const patterns = [];
227
-
228
- if (!successful) {
229
- return patterns;
230
- }
231
-
232
- const taskType = this._inferTaskType(context.taskPrompt);
233
- if (!taskType) {
234
- return patterns;
235
- }
236
-
237
- const tools = this._extractUniqueTools(context);
238
- const steps = context.steps;
239
-
240
- // Build comprehensive task-specific pattern
241
- patterns.push({
242
- pattern: `${taskType} methodology`,
243
- action: `Use ${tools.length} tools in sequence: ${tools.slice(0, 3).join(" → ")}${tools.length > 3 ? "..." : ""}`,
244
- reasoning: `Proven approach for ${taskType.toLowerCase()} (${steps} steps, ${successful ? "success" : "failed"})`,
245
- tools: tools,
246
- confidence: this._calculatePatternConfidence(context, successful)
247
- });
248
-
249
- return patterns;
250
- }
251
-
252
- /**
253
- * Infer task type from prompt
254
- */
255
- static _inferTaskType(prompt) {
256
- // Guard against a missing/non-string prompt so reflection never throws
257
- // (a throw here previously aborted all learning silently).
258
- if (typeof prompt !== "string" || prompt.length === 0) {
259
- return null;
260
- }
261
- const lower = prompt.toLowerCase();
262
-
263
- const taskTypes = [
264
- { keywords: ["find", "search", "locate", "where"], type: "Search task" },
265
- { keywords: ["list", "show", "display", "enumerate"], type: "Listing task" },
266
- { keywords: ["explain", "understand", "analyze", "examine"], type: "Analysis task" },
267
- { keywords: ["fix", "repair", "debug", "solve"], type: "Fix task" },
268
- { keywords: ["test", "verify", "check", "validate"], type: "Testing task" },
269
- { keywords: ["refactor", "improve", "clean", "optimize"], type: "Refactoring task" },
270
- { keywords: ["implement", "create", "add", "build"], type: "Implementation task" },
271
- { keywords: ["document", "write", "describe"], type: "Documentation task" }
272
- ];
273
-
274
- for (const { keywords, type } of taskTypes) {
275
- if (keywords.some(keyword => lower.includes(keyword))) {
276
- return type;
277
- }
278
- }
279
-
280
- return null;
281
- }
282
-
283
- /**
284
- * Extract unique tools from context
285
- */
286
- static _extractUniqueTools(context) {
287
- const transcript = context.transcript || [];
288
- const toolsUsed = transcript
289
- .filter(entry => entry.type === "tool_call" && !entry.error)
290
- .map(entry => entry.toolName);
291
-
292
- return [...new Set(toolsUsed)];
293
- }
294
-
295
- /**
296
- * Calculate pattern confidence based on execution quality
297
- */
298
- static _calculatePatternConfidence(context, successful) {
299
- if (!successful) {
300
- return 0.25;
301
- }
302
-
303
- let confidence = 0.5;
304
-
305
- // Boost for efficient execution
306
- const efficiency = context.steps / context.maxSteps;
307
- if (efficiency < 0.5) {
308
- confidence += 0.2;
309
- }
310
-
311
- // Boost for no errors
312
- const errorCount = (context.transcript || [])
313
- .filter(e => e.type === "tool_call" && e.error).length;
314
-
315
- if (errorCount === 0) {
316
- confidence += 0.15;
317
- } else {
318
- confidence -= (errorCount * 0.05);
319
- }
320
-
321
- // Boost for comprehensive tool usage (2-5 tools)
322
- const toolCount = this._extractUniqueTools(context).length;
323
- if (toolCount >= 2 && toolCount <= 5) {
324
- confidence += 0.1;
325
- }
326
-
327
- return Math.max(0.2, Math.min(0.9, confidence));
328
- }
329
- }
330
-
331
- module.exports = Reflector;
@@ -1,331 +0,0 @@
1
- const logger = require("../logger");
2
- const path = require("path");
3
- const fs = require("fs").promises;
4
-
5
- /**
6
- * Skillbook - Persistent knowledge store for agent learning
7
- * Each agent type has its own skillbook that evolves with experience
8
- */
9
- class Skillbook {
10
- constructor(agentType) {
11
- this.agentType = agentType;
12
- this.skills = new Map(); // pattern → skill object
13
- this.loaded = false;
14
- }
15
-
16
- /**
17
- * Add or update a skill
18
- * Uses incremental merging - doesn't replace existing knowledge
19
- */
20
- addSkill(skill) {
21
- if (!skill.pattern || !skill.action) {
22
- logger.warn({ skill }, "Invalid skill format, skipping");
23
- return false;
24
- }
25
-
26
- const key = this._normalizePattern(skill.pattern);
27
- const existing = this.skills.get(key);
28
-
29
- if (existing) {
30
- // Merge with existing skill
31
- existing.useCount++;
32
- existing.lastUsed = Date.now();
33
-
34
- // Update confidence (weighted average)
35
- const newConfidence = skill.confidence || 0.5;
36
- existing.confidence = (existing.confidence * 0.7) + (newConfidence * 0.3);
37
-
38
- // Update action if new one has higher confidence
39
- if (newConfidence > existing.confidence) {
40
- existing.action = skill.action;
41
- existing.reasoning = skill.reasoning || existing.reasoning;
42
- }
43
-
44
- logger.debug({
45
- agentType: this.agentType,
46
- pattern: skill.pattern,
47
- confidence: existing.confidence
48
- }, "Updated existing skill");
49
- } else {
50
- // Add new skill
51
- this.skills.set(key, {
52
- pattern: skill.pattern,
53
- action: skill.action,
54
- reasoning: skill.reasoning || "",
55
- tools: skill.tools || [],
56
- confidence: skill.confidence || 0.5,
57
- useCount: 1,
58
- createdAt: Date.now(),
59
- lastUsed: Date.now()
60
- });
61
-
62
- logger.info({
63
- agentType: this.agentType,
64
- pattern: skill.pattern,
65
- totalSkills: this.skills.size
66
- }, "Added new skill");
67
- }
68
-
69
- return true;
70
- }
71
-
72
- /**
73
- * Get top N skills for context injection
74
- * Sorted by: confidence * useCount * recency
75
- */
76
- getTopSkills(n = 5) {
77
- if (this.skills.size === 0) {
78
- return [];
79
- }
80
-
81
- const now = Date.now();
82
- const maxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
83
-
84
- return Array.from(this.skills.values())
85
- .map(skill => {
86
- // Recency factor (skills used recently are more valuable)
87
- const age = now - skill.lastUsed;
88
- const recencyFactor = Math.max(0.1, 1 - (age / maxAge));
89
-
90
- // Combined score
91
- const score = skill.confidence * skill.useCount * recencyFactor;
92
-
93
- return { ...skill, score };
94
- })
95
- .sort((a, b) => b.score - a.score)
96
- .slice(0, n)
97
- .map(skill => ({
98
- pattern: skill.pattern,
99
- action: skill.action,
100
- reasoning: skill.reasoning,
101
- confidence: skill.confidence
102
- }));
103
- }
104
-
105
- /**
106
- * Format skills for prompt injection
107
- */
108
- formatForPrompt(skills = null) {
109
- const topSkills = skills || this.getTopSkills(5);
110
-
111
- if (topSkills.length === 0) {
112
- return "";
113
- }
114
-
115
- const formatted = topSkills.map((skill, i) => {
116
- const confidenceBar = "█".repeat(Math.round(skill.confidence * 5));
117
- return `${i + 1}. ${skill.pattern}
118
- → ${skill.action}
119
- ${skill.reasoning ? ` Why: ${skill.reasoning}` : ''}
120
- Confidence: ${confidenceBar} ${Math.round(skill.confidence * 100)}%`;
121
- }).join("\n\n");
122
-
123
- return `
124
- ## Previously Learned Skills
125
-
126
- You've successfully used these patterns before. Consider applying them:
127
-
128
- ${formatted}
129
-
130
- Apply these learnings when relevant, but don't force them if the situation differs.
131
- `;
132
- }
133
-
134
- /**
135
- * Record when a skill is used
136
- */
137
- recordUsage(pattern, successful = true) {
138
- const key = this._normalizePattern(pattern);
139
- const skill = this.skills.get(key);
140
-
141
- if (skill) {
142
- skill.useCount++;
143
- skill.lastUsed = Date.now();
144
-
145
- // Adjust confidence based on success
146
- if (successful) {
147
- skill.confidence = Math.min(1.0, skill.confidence + 0.05);
148
- } else {
149
- skill.confidence = Math.max(0.1, skill.confidence - 0.1);
150
- }
151
-
152
- logger.debug({
153
- agentType: this.agentType,
154
- pattern,
155
- successful,
156
- newConfidence: skill.confidence
157
- }, "Recorded skill usage");
158
- }
159
- }
160
-
161
- /**
162
- * Prune low-quality skills
163
- */
164
- prune(minConfidence = 0.2, minUseCount = 3) {
165
- let pruned = 0;
166
-
167
- for (const [key, skill] of this.skills.entries()) {
168
- // Remove skills that have been tried multiple times but remain low confidence
169
- if (skill.useCount >= minUseCount && skill.confidence < minConfidence) {
170
- this.skills.delete(key);
171
- pruned++;
172
- }
173
- }
174
-
175
- if (pruned > 0) {
176
- logger.info({
177
- agentType: this.agentType,
178
- pruned,
179
- remaining: this.skills.size
180
- }, "Pruned low-confidence skills");
181
- }
182
-
183
- return pruned;
184
- }
185
-
186
- /**
187
- * Get statistics
188
- */
189
- getStats() {
190
- const skills = Array.from(this.skills.values());
191
-
192
- return {
193
- agentType: this.agentType,
194
- totalSkills: skills.length,
195
- averageConfidence: skills.reduce((sum, s) => sum + s.confidence, 0) / skills.length || 0,
196
- totalUses: skills.reduce((sum, s) => sum + s.useCount, 0),
197
- highConfidenceSkills: skills.filter(s => s.confidence >= 0.8).length
198
- };
199
- }
200
-
201
- /**
202
- * Save to disk (JSON format)
203
- */
204
- async save() {
205
- const filepath = this._getFilePath();
206
-
207
- try {
208
- // Ensure directory exists
209
- const dir = path.dirname(filepath);
210
- await fs.mkdir(dir, { recursive: true });
211
-
212
- const data = {
213
- agentType: this.agentType,
214
- skills: Array.from(this.skills.entries()),
215
- savedAt: Date.now(),
216
- version: "1.0"
217
- };
218
-
219
- await fs.writeFile(filepath, JSON.stringify(data, null, 2), 'utf8');
220
-
221
- logger.debug({
222
- agentType: this.agentType,
223
- filepath,
224
- skillCount: this.skills.size
225
- }, "Saved skillbook");
226
-
227
- return true;
228
- } catch (error) {
229
- logger.error({
230
- error: error.message,
231
- agentType: this.agentType,
232
- filepath
233
- }, "Failed to save skillbook");
234
- return false;
235
- }
236
- }
237
-
238
- /**
239
- * Load from disk
240
- */
241
- async load() {
242
- if (this.loaded) {
243
- return true; // Already loaded
244
- }
245
-
246
- const filepath = this._getFilePath();
247
-
248
- try {
249
- const content = await fs.readFile(filepath, 'utf8');
250
- const data = JSON.parse(content);
251
-
252
- if (data.agentType !== this.agentType) {
253
- throw new Error(`Agent type mismatch: expected ${this.agentType}, got ${data.agentType}`);
254
- }
255
-
256
- // Restore skills map
257
- this.skills = new Map(data.skills);
258
- this.loaded = true;
259
-
260
- logger.info({
261
- agentType: this.agentType,
262
- skillCount: this.skills.size,
263
- filepath
264
- }, "Loaded skillbook");
265
-
266
- return true;
267
- } catch (error) {
268
- if (error.code === 'ENOENT') {
269
- // File doesn't exist yet - this is fine for new agents
270
- logger.debug({
271
- agentType: this.agentType,
272
- filepath
273
- }, "No existing skillbook found, starting fresh");
274
- this.loaded = true;
275
- return true;
276
- }
277
-
278
- logger.error({
279
- error: error.message,
280
- agentType: this.agentType,
281
- filepath
282
- }, "Failed to load skillbook");
283
-
284
- this.loaded = true; // Mark as loaded to prevent retries
285
- return false;
286
- }
287
- }
288
-
289
- /**
290
- * Clear all skills (use with caution)
291
- */
292
- clear() {
293
- this.skills.clear();
294
- logger.warn({
295
- agentType: this.agentType
296
- }, "Cleared skillbook");
297
- }
298
-
299
- /**
300
- * Get file path for this agent's skillbook
301
- */
302
- _getFilePath() {
303
- const dataDir = path.join(process.cwd(), 'data', 'skillbooks');
304
- return path.join(dataDir, `${this.agentType.toLowerCase()}.json`);
305
- }
306
-
307
- /**
308
- * Normalize pattern for consistent matching
309
- */
310
- _normalizePattern(pattern) {
311
- return pattern.toLowerCase().trim().replace(/\s+/g, ' ');
312
- }
313
-
314
- /**
315
- * Static method: Load skillbook for agent type
316
- */
317
- static async load(agentType) {
318
- const skillbook = new Skillbook(agentType);
319
- await skillbook.load();
320
- return skillbook;
321
- }
322
-
323
- /**
324
- * Static method: Get or create skillbook
325
- */
326
- static async getOrCreate(agentType) {
327
- return Skillbook.load(agentType);
328
- }
329
- }
330
-
331
- module.exports = Skillbook;