@dbx-tools/appkit-mastra 0.9.74 → 0.9.79
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 +53 -42
- package/index.ts +8 -3
- package/lib/index.d.ts +8 -3
- package/lib/index.js +6 -3
- package/lib/src/agents.js +38 -5
- package/lib/src/config.d.ts +7 -7
- package/lib/src/config.js +4 -4
- package/lib/src/genie.js +9 -15
- package/lib/src/mlflow.js +5 -5
- package/lib/src/monty-native.d.ts +40 -0
- package/lib/src/monty-native.js +100 -0
- package/lib/src/monty-sandbox.js +7 -3
- package/lib/src/remote-skills.js +26 -2
- package/lib/src/skill-cache.d.ts +80 -56
- package/lib/src/skill-cache.js +215 -158
- package/lib/src/skill-search.d.ts +30 -0
- package/lib/src/skill-search.js +171 -0
- package/lib/src/workspaces.d.ts +10 -5
- package/lib/src/workspaces.js +132 -117
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +34 -15
- package/src/agents.ts +39 -6
- package/src/config.ts +10 -10
- package/src/genie.ts +8 -13
- package/src/mlflow.ts +4 -4
- package/src/monty-native.ts +149 -0
- package/src/monty-sandbox.ts +7 -2
- package/src/remote-skills.ts +32 -1
- package/src/skill-cache.ts +278 -243
- package/src/skill-search.ts +203 -0
- package/src/workspaces.ts +177 -139
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand skill discovery backed by a materialized workspace catalogue.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { errorUtils } from "@dbx-tools/shared-core";
|
|
8
|
+
import type { ProcessInputStepArgs, Processor } from "@mastra/core/processors";
|
|
9
|
+
import { MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
|
|
10
|
+
import { createTool } from "@mastra/core/tools";
|
|
11
|
+
import Fuse from "fuse.js";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import type { SkillCatalogueResolver } from "./skill-cache.ts";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_STATE_TTL_MS = 60 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
interface ThreadState {
|
|
18
|
+
lastAccessed: number;
|
|
19
|
+
skills: Map<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Configuration for {@link CatalogueSkillSearchProcessor}. */
|
|
23
|
+
export interface CatalogueSkillSearchProcessorOptions {
|
|
24
|
+
minScore?: number;
|
|
25
|
+
resolve: SkillCatalogueResolver;
|
|
26
|
+
topK?: number;
|
|
27
|
+
ttlMs?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Mastra-compatible search, load, and lazy-read tools over one cached catalogue. */
|
|
31
|
+
export class CatalogueSkillSearchProcessor implements Processor<"skill-catalogue-search"> {
|
|
32
|
+
readonly id = "skill-catalogue-search";
|
|
33
|
+
readonly name = "Skill Catalogue Search";
|
|
34
|
+
readonly providesSkillDiscovery = "on-demand" as const;
|
|
35
|
+
|
|
36
|
+
private readonly minScore: number;
|
|
37
|
+
private readonly resolve: SkillCatalogueResolver;
|
|
38
|
+
private readonly states = new Map<string, ThreadState>();
|
|
39
|
+
private readonly topK: number;
|
|
40
|
+
private readonly ttlMs: number;
|
|
41
|
+
|
|
42
|
+
constructor(options: CatalogueSkillSearchProcessorOptions) {
|
|
43
|
+
this.resolve = options.resolve;
|
|
44
|
+
this.topK = options.topK ?? 5;
|
|
45
|
+
this.minScore = options.minScore ?? 0.1;
|
|
46
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_STATE_TTL_MS;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async processInputStep(args: ProcessInputStepArgs): Promise<{ tools: Record<string, unknown> }> {
|
|
50
|
+
const owner = await this.resolve(args);
|
|
51
|
+
const catalogue = await owner.get(args.abortSignal);
|
|
52
|
+
const state = this.threadState(args);
|
|
53
|
+
args.messageList.addSystem(
|
|
54
|
+
"To discover available skills, call search_skills with a keyword query. " +
|
|
55
|
+
"To load a skill's instructions, call load_skill with the exact skill name. " +
|
|
56
|
+
"Use skill_read only after loading a skill when its instructions require a reference, script, or asset.",
|
|
57
|
+
);
|
|
58
|
+
for (const [skillName, instructions] of state.skills) {
|
|
59
|
+
args.messageList.addSystem(`[Skill: ${skillName}]\n\n${instructions}`);
|
|
60
|
+
}
|
|
61
|
+
const search = new Fuse(catalogue.skills, {
|
|
62
|
+
includeScore: true,
|
|
63
|
+
ignoreLocation: true,
|
|
64
|
+
threshold: 1,
|
|
65
|
+
keys: [
|
|
66
|
+
{ name: "name", weight: 0.5 },
|
|
67
|
+
{ name: "description", weight: 0.35 },
|
|
68
|
+
{ name: "instructions", weight: 0.15 },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
tools: {
|
|
73
|
+
...(args.tools ?? {}),
|
|
74
|
+
search_skills: createTool({
|
|
75
|
+
id: "search_skills",
|
|
76
|
+
description:
|
|
77
|
+
"Search for available skills by keyword. Load a useful result with load_skill.",
|
|
78
|
+
inputSchema: z.object({
|
|
79
|
+
query: z.string().trim().min(1, "Query is required").describe("Search keywords."),
|
|
80
|
+
}),
|
|
81
|
+
outputSchema: z.object({
|
|
82
|
+
results: z.array(
|
|
83
|
+
z.object({
|
|
84
|
+
name: z.string(),
|
|
85
|
+
description: z.string(),
|
|
86
|
+
score: z.number(),
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
message: z.string(),
|
|
90
|
+
}),
|
|
91
|
+
execute: async ({ query }) => {
|
|
92
|
+
const results = search
|
|
93
|
+
.search(query)
|
|
94
|
+
.map((result) => ({
|
|
95
|
+
name: result.item.name,
|
|
96
|
+
description:
|
|
97
|
+
result.item.description.length > 150
|
|
98
|
+
? `${result.item.description.slice(0, 147)}...`
|
|
99
|
+
: result.item.description,
|
|
100
|
+
score: Math.round((1 - (result.score ?? 1)) * 100) / 100,
|
|
101
|
+
}))
|
|
102
|
+
.filter(({ score }) => score >= this.minScore)
|
|
103
|
+
.slice(0, this.topK);
|
|
104
|
+
return {
|
|
105
|
+
results,
|
|
106
|
+
message:
|
|
107
|
+
results.length === 0
|
|
108
|
+
? `No skills found matching "${query}". Try different keywords.`
|
|
109
|
+
: `Found ${results.length} skill(s). Use load_skill with the exact skill name to load its instructions.`,
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
load_skill: createTool({
|
|
114
|
+
id: "load_skill",
|
|
115
|
+
description: "Load a skill's full instructions into the conversation.",
|
|
116
|
+
inputSchema: z.object({
|
|
117
|
+
skillName: z.string().describe("Exact skill name returned by search_skills."),
|
|
118
|
+
}),
|
|
119
|
+
outputSchema: z.object({
|
|
120
|
+
success: z.boolean(),
|
|
121
|
+
message: z.string(),
|
|
122
|
+
skillName: z.string().optional(),
|
|
123
|
+
}),
|
|
124
|
+
execute: async ({ skillName }) => {
|
|
125
|
+
if (state.skills.has(skillName)) {
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
message: `Skill "${skillName}" is already loaded.`,
|
|
129
|
+
skillName,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const skill = catalogue.skills.find(({ name }) => name === skillName);
|
|
133
|
+
if (!skill) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
message: `Skill "${skillName}" not found. Use search_skills to find available skills.`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
state.skills.set(skillName, skill.instructions);
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
message: `Skill "${skillName}" loaded. Its instructions are now available as context.`,
|
|
143
|
+
skillName,
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
skill_read: createTool({
|
|
148
|
+
id: "skill_read",
|
|
149
|
+
description:
|
|
150
|
+
"Read a reference, script, template, or asset from a loaded skill. The path is relative to the skill root.",
|
|
151
|
+
inputSchema: z.object({
|
|
152
|
+
skillName: z.string().describe("Exact loaded skill name."),
|
|
153
|
+
path: z.string().describe("File path relative to the skill root."),
|
|
154
|
+
startLine: z.number().int().positive().optional(),
|
|
155
|
+
endLine: z.number().int().positive().optional(),
|
|
156
|
+
}),
|
|
157
|
+
execute: async ({ skillName, path, startLine, endLine }) => {
|
|
158
|
+
if (!state.skills.has(skillName)) {
|
|
159
|
+
return `Skill "${skillName}" is not loaded. Call load_skill first.`;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const source = await owner.readSkillFile(skillName, path, args.abortSignal);
|
|
163
|
+
return lineRange(source, path, startLine, endLine);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return `Could not read "${path}" from skill "${skillName}": ${errorUtils.errorMessage(error)}`;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private threadState(args: ProcessInputStepArgs): ThreadState {
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
for (const [key, state] of this.states) {
|
|
176
|
+
if (now - state.lastAccessed > this.ttlMs) this.states.delete(key);
|
|
177
|
+
}
|
|
178
|
+
const key = args.requestContext?.get(MASTRA_THREAD_ID_KEY) ?? "default";
|
|
179
|
+
const current = this.states.get(String(key)) ?? {
|
|
180
|
+
lastAccessed: now,
|
|
181
|
+
skills: new Map<string, string>(),
|
|
182
|
+
};
|
|
183
|
+
current.lastAccessed = now;
|
|
184
|
+
this.states.set(String(key), current);
|
|
185
|
+
return current;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function lineRange(
|
|
190
|
+
source: string,
|
|
191
|
+
path: string,
|
|
192
|
+
startLine: number | undefined,
|
|
193
|
+
endLine: number | undefined,
|
|
194
|
+
): string {
|
|
195
|
+
if (startLine === undefined && endLine === undefined) return source;
|
|
196
|
+
const lines = source.split(/\r?\n/);
|
|
197
|
+
const start = Math.max(1, startLine ?? 1);
|
|
198
|
+
const end = Math.min(lines.length, endLine ?? lines.length);
|
|
199
|
+
if (start > end || start > lines.length) {
|
|
200
|
+
return `File "${path}" has ${lines.length} lines; requested range ${start}-${end} is empty.`;
|
|
201
|
+
}
|
|
202
|
+
return `${path} (lines ${start}-${end} of ${lines.length})\n${lines.slice(start - 1, end).join("\n")}`;
|
|
203
|
+
}
|