assistme 0.2.6 → 0.2.8
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/dist/index.js +228 -16
- package/package.json +1 -1
- package/src/agent/mcp-servers.ts +129 -14
- package/src/agent/processor.ts +3 -0
- package/src/agent/skills.ts +136 -5
package/dist/index.js
CHANGED
|
@@ -1812,6 +1812,11 @@ var SkillManager = class {
|
|
|
1812
1812
|
* Convert a DB row to a Skill object.
|
|
1813
1813
|
*/
|
|
1814
1814
|
rowToSkill(row) {
|
|
1815
|
+
const meta = row.metadata || {};
|
|
1816
|
+
const rawVars = row.variables || meta.variables;
|
|
1817
|
+
const variables = Array.isArray(rawVars) ? rawVars : void 0;
|
|
1818
|
+
const rawConfig = row.config;
|
|
1819
|
+
const config = rawConfig && typeof rawConfig === "object" ? rawConfig : void 0;
|
|
1815
1820
|
return {
|
|
1816
1821
|
name: row.name,
|
|
1817
1822
|
description: row.description || "",
|
|
@@ -1827,7 +1832,9 @@ var SkillManager = class {
|
|
|
1827
1832
|
filePath: "",
|
|
1828
1833
|
source: row.source || "manual",
|
|
1829
1834
|
dbId: row.id,
|
|
1830
|
-
sourceSkillId: row.source_skill_id || void 0
|
|
1835
|
+
sourceSkillId: row.source_skill_id || void 0,
|
|
1836
|
+
variables,
|
|
1837
|
+
config
|
|
1831
1838
|
};
|
|
1832
1839
|
}
|
|
1833
1840
|
/**
|
|
@@ -1966,7 +1973,13 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
1966
1973
|
try {
|
|
1967
1974
|
const sb = getSupabase();
|
|
1968
1975
|
const source = options?.source || "manual";
|
|
1969
|
-
const metadata =
|
|
1976
|
+
const metadata = {};
|
|
1977
|
+
if (options?.emoji) {
|
|
1978
|
+
metadata.openclaw = { emoji: options.emoji };
|
|
1979
|
+
}
|
|
1980
|
+
if (options?.variables && options.variables.length > 0) {
|
|
1981
|
+
metadata.variables = options.variables;
|
|
1982
|
+
}
|
|
1970
1983
|
const { data, error } = await sb.rpc("create_skill", {
|
|
1971
1984
|
p_user_id: this.userId,
|
|
1972
1985
|
p_name: name,
|
|
@@ -2138,6 +2151,10 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2138
2151
|
if (!this.userId) return;
|
|
2139
2152
|
try {
|
|
2140
2153
|
const sb = getSupabase();
|
|
2154
|
+
const metadata = {};
|
|
2155
|
+
if (options?.variables && options.variables.length > 0) {
|
|
2156
|
+
metadata.variables = options.variables;
|
|
2157
|
+
}
|
|
2141
2158
|
const { data, error } = await sb.rpc("upsert_agent_skill", {
|
|
2142
2159
|
p_user_id: this.userId,
|
|
2143
2160
|
p_name: name,
|
|
@@ -2148,7 +2165,8 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2148
2165
|
p_emoji: options?.emoji || null,
|
|
2149
2166
|
p_keywords: options?.keywords || [],
|
|
2150
2167
|
p_change_summary: options?.changeSummary || null,
|
|
2151
|
-
p_source_skill_id: options?.sourceSkillId || null
|
|
2168
|
+
p_source_skill_id: options?.sourceSkillId || null,
|
|
2169
|
+
p_metadata: Object.keys(metadata).length > 0 ? metadata : null
|
|
2152
2170
|
});
|
|
2153
2171
|
if (error) {
|
|
2154
2172
|
log.debug(`DB skill sync failed for "${name}": ${error.message}`);
|
|
@@ -2158,6 +2176,9 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2158
2176
|
if (skill && data && typeof data === "object" && "id" in data) {
|
|
2159
2177
|
skill.dbId = data.id;
|
|
2160
2178
|
}
|
|
2179
|
+
if (skill && options?.variables) {
|
|
2180
|
+
skill.variables = options.variables;
|
|
2181
|
+
}
|
|
2161
2182
|
log.debug(`Skill "${name}" synced to agent_skills`);
|
|
2162
2183
|
} catch (err) {
|
|
2163
2184
|
log.debug(`DB skill sync error for "${name}": ${err}`);
|
|
@@ -2314,7 +2335,54 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2314
2335
|
return [];
|
|
2315
2336
|
}
|
|
2316
2337
|
}
|
|
2338
|
+
// ── Variable Configuration ──────────────────────────────────────────
|
|
2339
|
+
/**
|
|
2340
|
+
* Update user-specific variable values (config) for a skill.
|
|
2341
|
+
* Persists to agent_skills.config in DB and updates in-memory.
|
|
2342
|
+
*/
|
|
2343
|
+
async updateConfig(skillName, config) {
|
|
2344
|
+
const skill = this.skills.get(skillName);
|
|
2345
|
+
if (!skill) return false;
|
|
2346
|
+
const merged = { ...skill.config || {}, ...config };
|
|
2347
|
+
skill.config = merged;
|
|
2348
|
+
if (this.userId && skill.dbId) {
|
|
2349
|
+
try {
|
|
2350
|
+
const sb = getSupabase();
|
|
2351
|
+
await sb.from("agent_skills").update({ config: merged }).eq("id", skill.dbId).eq("user_id", this.userId);
|
|
2352
|
+
log.debug(`Config updated for skill "${skillName}"`);
|
|
2353
|
+
} catch (err) {
|
|
2354
|
+
log.debug(`Config update failed for "${skillName}": ${err}`);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
return true;
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Get the unresolved (unconfigured) variables for a skill.
|
|
2361
|
+
* Returns variables that are required but have no value in config.
|
|
2362
|
+
*/
|
|
2363
|
+
getUnconfiguredVariables(skillName) {
|
|
2364
|
+
const skill = this.skills.get(skillName);
|
|
2365
|
+
if (!skill?.variables) return [];
|
|
2366
|
+
return skill.variables.filter((v) => {
|
|
2367
|
+
if (!v.required) return false;
|
|
2368
|
+
const value = skill.config?.[v.name];
|
|
2369
|
+
return value === void 0 || value === null || value === "";
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2317
2372
|
};
|
|
2373
|
+
function substituteVariables(content, config, variables) {
|
|
2374
|
+
if (!content.includes("{{")) return content;
|
|
2375
|
+
return content.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
|
|
2376
|
+
const value = config?.[varName];
|
|
2377
|
+
if (value !== void 0 && value !== null) {
|
|
2378
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
2379
|
+
return String(value);
|
|
2380
|
+
}
|
|
2381
|
+
const varDef = variables?.find((v) => v.name === varName);
|
|
2382
|
+
if (varDef?.default !== void 0) return varDef.default;
|
|
2383
|
+
return `{{${varName}: [NOT CONFIGURED]}}`;
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2318
2386
|
function substituteArguments(content, args) {
|
|
2319
2387
|
const parts = args.split(/\s+/);
|
|
2320
2388
|
content = content.replace(/\$ARGUMENTS/g, args);
|
|
@@ -2976,12 +3044,24 @@ function createAgentToolsServer(deps) {
|
|
|
2976
3044
|
),
|
|
2977
3045
|
tool(
|
|
2978
3046
|
"skill_create",
|
|
2979
|
-
"Create a new skill and add it to the user's collection. Returns the skill ID on success.",
|
|
3047
|
+
"Create a new skill and add it to the user's collection. Returns the skill ID on success. Use {{variable_name}} syntax in instructions for user-specific data (e.g. {{github_repos}}, {{slack_channel}}).",
|
|
2980
3048
|
{
|
|
2981
3049
|
name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
|
|
2982
3050
|
description: z.string().describe("One-line description of what this skill does"),
|
|
2983
|
-
instructions: z.string().describe(
|
|
2984
|
-
|
|
3051
|
+
instructions: z.string().describe(
|
|
3052
|
+
"Markdown step-by-step instructions. Use {{variable_name}} placeholders for user-specific data (e.g. {{github_repos}}, {{trello_board}}, {{slack_channel}}). These will be resolved with user's config at invoke time."
|
|
3053
|
+
),
|
|
3054
|
+
emoji: z.string().optional().describe("Single emoji representing this skill"),
|
|
3055
|
+
variables: z.array(z.object({
|
|
3056
|
+
name: z.string().describe("Variable name matching {{name}} in instructions"),
|
|
3057
|
+
description: z.string().describe("Human-readable description of what this variable is for"),
|
|
3058
|
+
type: z.enum(["string", "string[]", "number", "boolean"]).describe("Data type"),
|
|
3059
|
+
required: z.boolean().describe("Whether the skill needs this to function"),
|
|
3060
|
+
default: z.string().optional().describe("Default value if user doesn't configure"),
|
|
3061
|
+
example: z.string().optional().describe("Example value to guide the user")
|
|
3062
|
+
})).optional().describe(
|
|
3063
|
+
"Variables that need user-specific configuration. Define one for each {{variable}} used in instructions."
|
|
3064
|
+
)
|
|
2985
3065
|
},
|
|
2986
3066
|
async (args) => {
|
|
2987
3067
|
const existing = skillManager.findSimilar(args.name);
|
|
@@ -2999,7 +3079,11 @@ function createAgentToolsServer(deps) {
|
|
|
2999
3079
|
args.name,
|
|
3000
3080
|
args.description,
|
|
3001
3081
|
args.instructions,
|
|
3002
|
-
{
|
|
3082
|
+
{
|
|
3083
|
+
source: "manual",
|
|
3084
|
+
emoji: args.emoji,
|
|
3085
|
+
variables: args.variables
|
|
3086
|
+
}
|
|
3003
3087
|
);
|
|
3004
3088
|
if (!result) {
|
|
3005
3089
|
return {
|
|
@@ -3014,17 +3098,30 @@ function createAgentToolsServer(deps) {
|
|
|
3014
3098
|
{
|
|
3015
3099
|
source: "manual",
|
|
3016
3100
|
emoji: args.emoji,
|
|
3017
|
-
sourceSkillId: result.id
|
|
3101
|
+
sourceSkillId: result.id,
|
|
3102
|
+
variables: args.variables
|
|
3018
3103
|
}
|
|
3019
3104
|
);
|
|
3105
|
+
let responseText = `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`;
|
|
3106
|
+
if (args.variables && args.variables.length > 0) {
|
|
3107
|
+
const requiredVars = args.variables.filter((v) => v.required);
|
|
3108
|
+
if (requiredVars.length > 0) {
|
|
3109
|
+
responseText += `
|
|
3110
|
+
|
|
3111
|
+
**Variables that need configuration:**
|
|
3112
|
+
`;
|
|
3113
|
+
for (const v of requiredVars) {
|
|
3114
|
+
const example = v.example ? ` (e.g. ${v.example})` : "";
|
|
3115
|
+
responseText += `- \`{{${v.name}}}\`: ${v.description}${example}
|
|
3116
|
+
`;
|
|
3117
|
+
}
|
|
3118
|
+
responseText += `
|
|
3119
|
+
Use \`skill_configure\` to set these values for the user, or ask the user to provide them.`;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3020
3122
|
log.success(`Skill "${args.name}" created and added to collection`);
|
|
3021
3123
|
return {
|
|
3022
|
-
content: [
|
|
3023
|
-
{
|
|
3024
|
-
type: "text",
|
|
3025
|
-
text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`
|
|
3026
|
-
}
|
|
3027
|
-
]
|
|
3124
|
+
content: [{ type: "text", text: responseText }]
|
|
3028
3125
|
};
|
|
3029
3126
|
}
|
|
3030
3127
|
),
|
|
@@ -3096,6 +3193,7 @@ function createAgentToolsServer(deps) {
|
|
|
3096
3193
|
};
|
|
3097
3194
|
}
|
|
3098
3195
|
let content = skill.content;
|
|
3196
|
+
content = substituteVariables(content, skill.config, skill.variables);
|
|
3099
3197
|
if (args.arguments) {
|
|
3100
3198
|
content = substituteArguments(content, args.arguments);
|
|
3101
3199
|
}
|
|
@@ -3114,6 +3212,18 @@ ${content}`;
|
|
|
3114
3212
|
**Allowed tools for this skill:** ${skill.allowedTools.join(", ")}
|
|
3115
3213
|
`;
|
|
3116
3214
|
}
|
|
3215
|
+
const unconfigured = skillManager.getUnconfiguredVariables(args.name);
|
|
3216
|
+
if (unconfigured.length > 0) {
|
|
3217
|
+
response += `
|
|
3218
|
+
|
|
3219
|
+
**\u26A0 Unconfigured variables \u2014 use \`skill_configure\` to set these:**
|
|
3220
|
+
`;
|
|
3221
|
+
for (const v of unconfigured) {
|
|
3222
|
+
const example = v.example ? ` (e.g. ${v.example})` : "";
|
|
3223
|
+
response += `- \`{{${v.name}}}\`: ${v.description}${example}
|
|
3224
|
+
`;
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3117
3227
|
log.info(`Skill invoked: "${args.name}"`);
|
|
3118
3228
|
skillManager.logInvocation(args.name, {
|
|
3119
3229
|
messageId: taskId,
|
|
@@ -3153,6 +3263,53 @@ ${content}`;
|
|
|
3153
3263
|
return { content: [{ type: "text", text: response }] };
|
|
3154
3264
|
}
|
|
3155
3265
|
),
|
|
3266
|
+
tool(
|
|
3267
|
+
"skill_configure",
|
|
3268
|
+
"Set user-specific variable values for a skill. Variables are defined in the skill template (e.g. {{github_repos}}) and need to be configured with the user's actual data before the skill can work properly.",
|
|
3269
|
+
{
|
|
3270
|
+
name: z.string().describe("Name of the skill to configure"),
|
|
3271
|
+
config: z.record(z.unknown()).describe(
|
|
3272
|
+
'Key-value pairs of variable values. Keys must match variable names defined in the skill. Example: {"github_repos": ["octocat/hello-world", "myorg/myrepo"], "github_username": "octocat"}'
|
|
3273
|
+
)
|
|
3274
|
+
},
|
|
3275
|
+
async (args) => {
|
|
3276
|
+
const skill = skillManager.get(args.name);
|
|
3277
|
+
if (!skill) {
|
|
3278
|
+
const available = skillManager.getAll().map((s) => s.name).join(", ");
|
|
3279
|
+
return {
|
|
3280
|
+
content: [{
|
|
3281
|
+
type: "text",
|
|
3282
|
+
text: `Skill "${args.name}" not found. Available: ${available}`
|
|
3283
|
+
}]
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
const updated = await skillManager.updateConfig(args.name, args.config);
|
|
3287
|
+
if (!updated) {
|
|
3288
|
+
return {
|
|
3289
|
+
content: [{ type: "text", text: `Failed to update config for "${args.name}".` }]
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
const unconfigured = skillManager.getUnconfiguredVariables(args.name);
|
|
3293
|
+
let responseText = `Configuration updated for skill "${args.name}".`;
|
|
3294
|
+
if (unconfigured.length > 0) {
|
|
3295
|
+
responseText += `
|
|
3296
|
+
|
|
3297
|
+
**Still needs configuration:**
|
|
3298
|
+
`;
|
|
3299
|
+
for (const v of unconfigured) {
|
|
3300
|
+
const example = v.example ? ` (e.g. ${v.example})` : "";
|
|
3301
|
+
responseText += `- \`{{${v.name}}}\`: ${v.description}${example}
|
|
3302
|
+
`;
|
|
3303
|
+
}
|
|
3304
|
+
} else {
|
|
3305
|
+
responseText += ` All required variables are configured.`;
|
|
3306
|
+
}
|
|
3307
|
+
log.info(`Config updated for skill "${args.name}": ${Object.keys(args.config).join(", ")}`);
|
|
3308
|
+
return {
|
|
3309
|
+
content: [{ type: "text", text: responseText }]
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
),
|
|
3156
3313
|
tool(
|
|
3157
3314
|
"skill_generate",
|
|
3158
3315
|
"Prepare context for generating skills from a job description. Returns existing skills and job info so you can analyze the job and create skills using skill_create (which auto-adds to user's collection). After creating all skills, call skill_link_job to link them to the job and mark it as analyzed.",
|
|
@@ -3201,6 +3358,8 @@ ${content}`;
|
|
|
3201
3358
|
response += `- instructions: detailed step-by-step markdown instructions the agent can follow
|
|
3202
3359
|
`;
|
|
3203
3360
|
response += `- emoji: a single emoji representing the skill
|
|
3361
|
+
`;
|
|
3362
|
+
response += `- variables: define user-specific data needed by the skill (see below)
|
|
3204
3363
|
|
|
3205
3364
|
`;
|
|
3206
3365
|
response += `skill_create automatically adds the skill to the user's collection \u2014 no need to call skill_add.
|
|
@@ -3216,10 +3375,60 @@ ${content}`;
|
|
|
3216
3375
|
response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks
|
|
3217
3376
|
`;
|
|
3218
3377
|
response += `- Include error handling steps
|
|
3219
|
-
`;
|
|
3220
|
-
response += `- Use placeholders like {query}, {date} for variable inputs
|
|
3221
3378
|
`;
|
|
3222
3379
|
response += `- Each skill should be a single, well-defined workflow (10-25 steps)
|
|
3380
|
+
|
|
3381
|
+
`;
|
|
3382
|
+
response += `**IMPORTANT \u2014 Use {{variables}} for user-specific data:**
|
|
3383
|
+
`;
|
|
3384
|
+
response += `Skills often need user-specific information (accounts, repos, boards, channels, etc.).
|
|
3385
|
+
`;
|
|
3386
|
+
response += `Instead of hardcoding or asking at runtime, use \`{{variable_name}}\` placeholders in instructions.
|
|
3387
|
+
|
|
3388
|
+
`;
|
|
3389
|
+
response += `Example: A GitHub PR review skill should use \`{{github_repos}}\` in its instructions:
|
|
3390
|
+
`;
|
|
3391
|
+
response += ` "Navigate to each repository in {{github_repos}} and check for open PRs..."
|
|
3392
|
+
|
|
3393
|
+
`;
|
|
3394
|
+
response += `For each \`{{variable}}\` used in instructions, add a matching entry in the \`variables\` array:
|
|
3395
|
+
`;
|
|
3396
|
+
response += `\`\`\`json
|
|
3397
|
+
`;
|
|
3398
|
+
response += `{
|
|
3399
|
+
`;
|
|
3400
|
+
response += ` "name": "github_repos",
|
|
3401
|
+
`;
|
|
3402
|
+
response += ` "description": "GitHub repositories to monitor (owner/repo format)",
|
|
3403
|
+
`;
|
|
3404
|
+
response += ` "type": "string[]",
|
|
3405
|
+
`;
|
|
3406
|
+
response += ` "required": true,
|
|
3407
|
+
`;
|
|
3408
|
+
response += ` "example": "octocat/hello-world, myorg/myrepo"
|
|
3409
|
+
`;
|
|
3410
|
+
response += `}
|
|
3411
|
+
`;
|
|
3412
|
+
response += `\`\`\`
|
|
3413
|
+
|
|
3414
|
+
`;
|
|
3415
|
+
response += `Common variables by platform:
|
|
3416
|
+
`;
|
|
3417
|
+
response += `- GitHub: github_repos, github_username, github_org
|
|
3418
|
+
`;
|
|
3419
|
+
response += `- Trello: trello_board_url, trello_list_names
|
|
3420
|
+
`;
|
|
3421
|
+
response += `- Slack: slack_channels, slack_workspace_url
|
|
3422
|
+
`;
|
|
3423
|
+
response += `- Email: email_labels, email_filters
|
|
3424
|
+
`;
|
|
3425
|
+
response += `- E-commerce: store_url, competitor_urls, product_categories
|
|
3426
|
+
`;
|
|
3427
|
+
response += `- General: timezone, language, notification_channel
|
|
3428
|
+
|
|
3429
|
+
`;
|
|
3430
|
+
response += `After creating skills, if any have required variables, call \`skill_configure\` to set initial values `;
|
|
3431
|
+
response += `(ask the user for the values first).
|
|
3223
3432
|
`;
|
|
3224
3433
|
return { content: [{ type: "text", text: response }] };
|
|
3225
3434
|
}
|
|
@@ -3721,6 +3930,8 @@ Available capabilities:
|
|
|
3721
3930
|
- If the user approves, use skill_add to add it to their collection, then proceed with the task
|
|
3722
3931
|
- If a skill's instructions could be improved based on your experience, use skill_improve
|
|
3723
3932
|
- Use skill_search to find relevant skills when the task doesn't obviously match the listed skills
|
|
3933
|
+
- Skills use {{variable_name}} placeholders for user-specific data (repos, channels, boards, etc.)
|
|
3934
|
+
- Use skill_configure to set variable values after creating skills or when the user provides their data
|
|
3724
3935
|
|
|
3725
3936
|
5. JOB AUTOMATION:
|
|
3726
3937
|
- When the user describes their job/role/daily work, use skill_generate to decompose it into automatable skills
|
|
@@ -3854,6 +4065,7 @@ var TaskProcessor = class {
|
|
|
3854
4065
|
"mcp__assistme-agent__skill_improve",
|
|
3855
4066
|
"mcp__assistme-agent__skill_invoke",
|
|
3856
4067
|
"mcp__assistme-agent__skill_search",
|
|
4068
|
+
"mcp__assistme-agent__skill_configure",
|
|
3857
4069
|
"mcp__assistme-agent__skill_generate",
|
|
3858
4070
|
"mcp__assistme-agent__skill_link_job",
|
|
3859
4071
|
"mcp__assistme-agent__skill_browse",
|
package/package.json
CHANGED
package/src/agent/mcp-servers.ts
CHANGED
|
@@ -8,8 +8,8 @@ import { executeTool } from "../tools/index.js";
|
|
|
8
8
|
import { getLimiterForTool } from "../utils/rate-limiter.js";
|
|
9
9
|
import { log } from "../utils/logger.js";
|
|
10
10
|
import type { MemoryManager, MemoryCategory } from "./memory.js";
|
|
11
|
-
import type { SkillManager } from "./skills.js";
|
|
12
|
-
import { substituteArguments, preprocessDynamicContext } from "./skills.js";
|
|
11
|
+
import type { SkillManager, SkillVariable } from "./skills.js";
|
|
12
|
+
import { substituteArguments, substituteVariables, preprocessDynamicContext } from "./skills.js";
|
|
13
13
|
import { getSupabase, emitEvent, setActionRequest, pollActionResponse } from "../db/supabase.js";
|
|
14
14
|
import { JobRunner } from "./job-runner.js";
|
|
15
15
|
import {
|
|
@@ -214,12 +214,26 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
214
214
|
),
|
|
215
215
|
tool(
|
|
216
216
|
"skill_create",
|
|
217
|
-
"Create a new skill and add it to the user's collection. Returns the skill ID on success."
|
|
217
|
+
"Create a new skill and add it to the user's collection. Returns the skill ID on success. " +
|
|
218
|
+
"Use {{variable_name}} syntax in instructions for user-specific data (e.g. {{github_repos}}, {{slack_channel}}).",
|
|
218
219
|
{
|
|
219
220
|
name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
|
|
220
221
|
description: z.string().describe("One-line description of what this skill does"),
|
|
221
|
-
instructions: z.string().describe(
|
|
222
|
+
instructions: z.string().describe(
|
|
223
|
+
"Markdown step-by-step instructions. Use {{variable_name}} placeholders for user-specific data " +
|
|
224
|
+
"(e.g. {{github_repos}}, {{trello_board}}, {{slack_channel}}). These will be resolved with user's config at invoke time."
|
|
225
|
+
),
|
|
222
226
|
emoji: z.string().optional().describe("Single emoji representing this skill"),
|
|
227
|
+
variables: z.array(z.object({
|
|
228
|
+
name: z.string().describe("Variable name matching {{name}} in instructions"),
|
|
229
|
+
description: z.string().describe("Human-readable description of what this variable is for"),
|
|
230
|
+
type: z.enum(["string", "string[]", "number", "boolean"]).describe("Data type"),
|
|
231
|
+
required: z.boolean().describe("Whether the skill needs this to function"),
|
|
232
|
+
default: z.string().optional().describe("Default value if user doesn't configure"),
|
|
233
|
+
example: z.string().optional().describe("Example value to guide the user"),
|
|
234
|
+
})).optional().describe(
|
|
235
|
+
"Variables that need user-specific configuration. Define one for each {{variable}} used in instructions."
|
|
236
|
+
),
|
|
223
237
|
},
|
|
224
238
|
async (args) => {
|
|
225
239
|
// Check for duplicates in user's collection
|
|
@@ -240,7 +254,11 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
240
254
|
args.name,
|
|
241
255
|
args.description,
|
|
242
256
|
args.instructions,
|
|
243
|
-
{
|
|
257
|
+
{
|
|
258
|
+
source: "manual",
|
|
259
|
+
emoji: args.emoji,
|
|
260
|
+
variables: args.variables as SkillVariable[] | undefined,
|
|
261
|
+
}
|
|
244
262
|
);
|
|
245
263
|
|
|
246
264
|
if (!result) {
|
|
@@ -259,17 +277,27 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
259
277
|
source: "manual",
|
|
260
278
|
emoji: args.emoji,
|
|
261
279
|
sourceSkillId: result.id,
|
|
280
|
+
variables: args.variables as SkillVariable[] | undefined,
|
|
262
281
|
}
|
|
263
282
|
);
|
|
264
283
|
|
|
284
|
+
// Build response with variable info
|
|
285
|
+
let responseText = `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`;
|
|
286
|
+
if (args.variables && args.variables.length > 0) {
|
|
287
|
+
const requiredVars = args.variables.filter((v) => v.required);
|
|
288
|
+
if (requiredVars.length > 0) {
|
|
289
|
+
responseText += `\n\n**Variables that need configuration:**\n`;
|
|
290
|
+
for (const v of requiredVars) {
|
|
291
|
+
const example = v.example ? ` (e.g. ${v.example})` : "";
|
|
292
|
+
responseText += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
|
|
293
|
+
}
|
|
294
|
+
responseText += `\nUse \`skill_configure\` to set these values for the user, or ask the user to provide them.`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
265
298
|
log.success(`Skill "${args.name}" created and added to collection`);
|
|
266
299
|
return {
|
|
267
|
-
content: [
|
|
268
|
-
{
|
|
269
|
-
type: "text",
|
|
270
|
-
text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`,
|
|
271
|
-
},
|
|
272
|
-
],
|
|
300
|
+
content: [{ type: "text", text: responseText }],
|
|
273
301
|
};
|
|
274
302
|
}
|
|
275
303
|
),
|
|
@@ -354,6 +382,9 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
354
382
|
|
|
355
383
|
let content = skill.content;
|
|
356
384
|
|
|
385
|
+
// Substitute {{variable}} placeholders with user config values
|
|
386
|
+
content = substituteVariables(content, skill.config, skill.variables);
|
|
387
|
+
|
|
357
388
|
// Substitute $ARGUMENTS placeholders
|
|
358
389
|
if (args.arguments) {
|
|
359
390
|
content = substituteArguments(content, args.arguments);
|
|
@@ -374,6 +405,16 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
374
405
|
response += `\n\n**Allowed tools for this skill:** ${skill.allowedTools.join(", ")}\n`;
|
|
375
406
|
}
|
|
376
407
|
|
|
408
|
+
// Warn about unconfigured required variables
|
|
409
|
+
const unconfigured = skillManager.getUnconfiguredVariables(args.name);
|
|
410
|
+
if (unconfigured.length > 0) {
|
|
411
|
+
response += `\n\n**⚠ Unconfigured variables — use \`skill_configure\` to set these:**\n`;
|
|
412
|
+
for (const v of unconfigured) {
|
|
413
|
+
const example = v.example ? ` (e.g. ${v.example})` : "";
|
|
414
|
+
response += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
377
418
|
log.info(`Skill invoked: "${args.name}"`);
|
|
378
419
|
|
|
379
420
|
// Log invocation to DB (fire-and-forget)
|
|
@@ -416,6 +457,56 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
416
457
|
return { content: [{ type: "text", text: response }] };
|
|
417
458
|
}
|
|
418
459
|
),
|
|
460
|
+
tool(
|
|
461
|
+
"skill_configure",
|
|
462
|
+
"Set user-specific variable values for a skill. Variables are defined in the skill template (e.g. {{github_repos}}) " +
|
|
463
|
+
"and need to be configured with the user's actual data before the skill can work properly.",
|
|
464
|
+
{
|
|
465
|
+
name: z.string().describe("Name of the skill to configure"),
|
|
466
|
+
config: z.record(z.unknown()).describe(
|
|
467
|
+
"Key-value pairs of variable values. Keys must match variable names defined in the skill. " +
|
|
468
|
+
"Example: {\"github_repos\": [\"octocat/hello-world\", \"myorg/myrepo\"], \"github_username\": \"octocat\"}"
|
|
469
|
+
),
|
|
470
|
+
},
|
|
471
|
+
async (args) => {
|
|
472
|
+
const skill = skillManager.get(args.name);
|
|
473
|
+
if (!skill) {
|
|
474
|
+
const available = skillManager.getAll().map((s) => s.name).join(", ");
|
|
475
|
+
return {
|
|
476
|
+
content: [{
|
|
477
|
+
type: "text",
|
|
478
|
+
text: `Skill "${args.name}" not found. Available: ${available}`,
|
|
479
|
+
}],
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const updated = await skillManager.updateConfig(args.name, args.config);
|
|
484
|
+
if (!updated) {
|
|
485
|
+
return {
|
|
486
|
+
content: [{ type: "text", text: `Failed to update config for "${args.name}".` }],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Check if there are still unconfigured required variables
|
|
491
|
+
const unconfigured = skillManager.getUnconfiguredVariables(args.name);
|
|
492
|
+
let responseText = `Configuration updated for skill "${args.name}".`;
|
|
493
|
+
|
|
494
|
+
if (unconfigured.length > 0) {
|
|
495
|
+
responseText += `\n\n**Still needs configuration:**\n`;
|
|
496
|
+
for (const v of unconfigured) {
|
|
497
|
+
const example = v.example ? ` (e.g. ${v.example})` : "";
|
|
498
|
+
responseText += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
responseText += ` All required variables are configured.`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
log.info(`Config updated for skill "${args.name}": ${Object.keys(args.config).join(", ")}`);
|
|
505
|
+
return {
|
|
506
|
+
content: [{ type: "text", text: responseText }],
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
),
|
|
419
510
|
tool(
|
|
420
511
|
"skill_generate",
|
|
421
512
|
"Prepare context for generating skills from a job description. Returns existing skills and job info " +
|
|
@@ -451,15 +542,39 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
451
542
|
response += `- name: kebab-case name (e.g. "slack-message-check")\n`;
|
|
452
543
|
response += `- description: one-line description\n`;
|
|
453
544
|
response += `- instructions: detailed step-by-step markdown instructions the agent can follow\n`;
|
|
454
|
-
response += `- emoji: a single emoji representing the skill\n
|
|
545
|
+
response += `- emoji: a single emoji representing the skill\n`;
|
|
546
|
+
response += `- variables: define user-specific data needed by the skill (see below)\n\n`;
|
|
455
547
|
response += `skill_create automatically adds the skill to the user's collection — no need to call skill_add.\n\n`;
|
|
456
548
|
response += `After ALL skills are created, call \`skill_link_job\` with job_name="${args.job_name}" and the list of created skill names to link them and mark the job as analyzed.\n\n`;
|
|
457
549
|
response += `**Guidelines for skill instructions:**\n`;
|
|
458
550
|
response += `- Write clear, actionable markdown steps\n`;
|
|
459
551
|
response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks\n`;
|
|
460
552
|
response += `- Include error handling steps\n`;
|
|
461
|
-
response += `-
|
|
462
|
-
response +=
|
|
553
|
+
response += `- Each skill should be a single, well-defined workflow (10-25 steps)\n\n`;
|
|
554
|
+
response += `**IMPORTANT — Use {{variables}} for user-specific data:**\n`;
|
|
555
|
+
response += `Skills often need user-specific information (accounts, repos, boards, channels, etc.).\n`;
|
|
556
|
+
response += `Instead of hardcoding or asking at runtime, use \`{{variable_name}}\` placeholders in instructions.\n\n`;
|
|
557
|
+
response += `Example: A GitHub PR review skill should use \`{{github_repos}}\` in its instructions:\n`;
|
|
558
|
+
response += ` "Navigate to each repository in {{github_repos}} and check for open PRs..."\n\n`;
|
|
559
|
+
response += `For each \`{{variable}}\` used in instructions, add a matching entry in the \`variables\` array:\n`;
|
|
560
|
+
response += `\`\`\`json\n`;
|
|
561
|
+
response += `{\n`;
|
|
562
|
+
response += ` "name": "github_repos",\n`;
|
|
563
|
+
response += ` "description": "GitHub repositories to monitor (owner/repo format)",\n`;
|
|
564
|
+
response += ` "type": "string[]",\n`;
|
|
565
|
+
response += ` "required": true,\n`;
|
|
566
|
+
response += ` "example": "octocat/hello-world, myorg/myrepo"\n`;
|
|
567
|
+
response += `}\n`;
|
|
568
|
+
response += `\`\`\`\n\n`;
|
|
569
|
+
response += `Common variables by platform:\n`;
|
|
570
|
+
response += `- GitHub: github_repos, github_username, github_org\n`;
|
|
571
|
+
response += `- Trello: trello_board_url, trello_list_names\n`;
|
|
572
|
+
response += `- Slack: slack_channels, slack_workspace_url\n`;
|
|
573
|
+
response += `- Email: email_labels, email_filters\n`;
|
|
574
|
+
response += `- E-commerce: store_url, competitor_urls, product_categories\n`;
|
|
575
|
+
response += `- General: timezone, language, notification_channel\n\n`;
|
|
576
|
+
response += `After creating skills, if any have required variables, call \`skill_configure\` to set initial values `;
|
|
577
|
+
response += `(ask the user for the values first).\n`;
|
|
463
578
|
|
|
464
579
|
return { content: [{ type: "text", text: response }] };
|
|
465
580
|
}
|
package/src/agent/processor.ts
CHANGED
|
@@ -65,6 +65,8 @@ Available capabilities:
|
|
|
65
65
|
- If the user approves, use skill_add to add it to their collection, then proceed with the task
|
|
66
66
|
- If a skill's instructions could be improved based on your experience, use skill_improve
|
|
67
67
|
- Use skill_search to find relevant skills when the task doesn't obviously match the listed skills
|
|
68
|
+
- Skills use {{variable_name}} placeholders for user-specific data (repos, channels, boards, etc.)
|
|
69
|
+
- Use skill_configure to set variable values after creating skills or when the user provides their data
|
|
68
70
|
|
|
69
71
|
5. JOB AUTOMATION:
|
|
70
72
|
- When the user describes their job/role/daily work, use skill_generate to decompose it into automatable skills
|
|
@@ -232,6 +234,7 @@ export class TaskProcessor {
|
|
|
232
234
|
"mcp__assistme-agent__skill_improve",
|
|
233
235
|
"mcp__assistme-agent__skill_invoke",
|
|
234
236
|
"mcp__assistme-agent__skill_search",
|
|
237
|
+
"mcp__assistme-agent__skill_configure",
|
|
235
238
|
"mcp__assistme-agent__skill_generate",
|
|
236
239
|
"mcp__assistme-agent__skill_link_job",
|
|
237
240
|
"mcp__assistme-agent__skill_browse",
|
package/src/agent/skills.ts
CHANGED
|
@@ -67,6 +67,26 @@ export interface SkillMetadata {
|
|
|
67
67
|
skillKey?: string;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* A variable that a skill requires from the user.
|
|
72
|
+
* Variables are defined in the skill template (e.g. `{{github_repos}}`)
|
|
73
|
+
* and resolved with user-specific values stored in agent_skills.config.
|
|
74
|
+
*/
|
|
75
|
+
export interface SkillVariable {
|
|
76
|
+
/** Variable name used in templates, e.g. "github_repos" */
|
|
77
|
+
name: string;
|
|
78
|
+
/** Human-readable description, e.g. "GitHub repositories to monitor" */
|
|
79
|
+
description: string;
|
|
80
|
+
/** Data type of the variable */
|
|
81
|
+
type: "string" | "string[]" | "number" | "boolean";
|
|
82
|
+
/** Whether the skill cannot function without this variable */
|
|
83
|
+
required: boolean;
|
|
84
|
+
/** Default value if not configured by user */
|
|
85
|
+
default?: string;
|
|
86
|
+
/** Example value to guide the user, e.g. "octocat/hello-world, myorg/myrepo" */
|
|
87
|
+
example?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
70
90
|
export interface Skill {
|
|
71
91
|
name: string;
|
|
72
92
|
description: string;
|
|
@@ -83,6 +103,10 @@ export interface Skill {
|
|
|
83
103
|
source: "bundled" | "manual" | "external" | "auto_extracted" | "auto_improved" | "job_generated";
|
|
84
104
|
dbId?: string; // UUID from agent_skills table
|
|
85
105
|
sourceSkillId?: string; // UUID from skills table (origin)
|
|
106
|
+
/** Variable definitions declared by the skill template */
|
|
107
|
+
variables?: SkillVariable[];
|
|
108
|
+
/** User-specific variable values (from agent_skills.config) */
|
|
109
|
+
config?: Record<string, unknown>;
|
|
86
110
|
}
|
|
87
111
|
|
|
88
112
|
/**
|
|
@@ -174,6 +198,15 @@ export class SkillManager {
|
|
|
174
198
|
* Convert a DB row to a Skill object.
|
|
175
199
|
*/
|
|
176
200
|
private rowToSkill(row: Record<string, unknown>): Skill {
|
|
201
|
+
// Parse variables from metadata.variables or top-level variables column
|
|
202
|
+
const meta = (row.metadata || {}) as Record<string, unknown>;
|
|
203
|
+
const rawVars = (row.variables || meta.variables) as SkillVariable[] | undefined;
|
|
204
|
+
const variables = Array.isArray(rawVars) ? rawVars : undefined;
|
|
205
|
+
|
|
206
|
+
// Parse user config (variable values) from config column
|
|
207
|
+
const rawConfig = row.config as Record<string, unknown> | undefined;
|
|
208
|
+
const config = rawConfig && typeof rawConfig === "object" ? rawConfig : undefined;
|
|
209
|
+
|
|
177
210
|
return {
|
|
178
211
|
name: row.name as string,
|
|
179
212
|
description: (row.description as string) || "",
|
|
@@ -190,6 +223,8 @@ export class SkillManager {
|
|
|
190
223
|
source: (row.source as Skill["source"]) || "manual",
|
|
191
224
|
dbId: row.id as string,
|
|
192
225
|
sourceSkillId: (row.source_skill_id as string) || undefined,
|
|
226
|
+
variables,
|
|
227
|
+
config,
|
|
193
228
|
};
|
|
194
229
|
}
|
|
195
230
|
|
|
@@ -360,16 +395,20 @@ export class SkillManager {
|
|
|
360
395
|
name: string,
|
|
361
396
|
description: string,
|
|
362
397
|
content: string,
|
|
363
|
-
options?: { source?: string; emoji?: string; keywords?: string[] }
|
|
398
|
+
options?: { source?: string; emoji?: string; keywords?: string[]; variables?: SkillVariable[] }
|
|
364
399
|
): Promise<{ id: string; name: string } | null> {
|
|
365
400
|
if (!this.userId) return null;
|
|
366
401
|
|
|
367
402
|
try {
|
|
368
403
|
const sb = getSupabase();
|
|
369
404
|
const source = options?.source || "manual";
|
|
370
|
-
const metadata =
|
|
371
|
-
|
|
372
|
-
:
|
|
405
|
+
const metadata: Record<string, unknown> = {};
|
|
406
|
+
if (options?.emoji) {
|
|
407
|
+
metadata.openclaw = { emoji: options.emoji };
|
|
408
|
+
}
|
|
409
|
+
if (options?.variables && options.variables.length > 0) {
|
|
410
|
+
metadata.variables = options.variables;
|
|
411
|
+
}
|
|
373
412
|
|
|
374
413
|
const { data, error } = await sb.rpc("create_skill", {
|
|
375
414
|
p_user_id: this.userId,
|
|
@@ -607,12 +646,20 @@ export class SkillManager {
|
|
|
607
646
|
keywords?: string[];
|
|
608
647
|
changeSummary?: string;
|
|
609
648
|
sourceSkillId?: string;
|
|
649
|
+
variables?: SkillVariable[];
|
|
610
650
|
}
|
|
611
651
|
): Promise<void> {
|
|
612
652
|
if (!this.userId) return;
|
|
613
653
|
|
|
614
654
|
try {
|
|
615
655
|
const sb = getSupabase();
|
|
656
|
+
|
|
657
|
+
// Store variables in metadata for agent_skills
|
|
658
|
+
const metadata: Record<string, unknown> = {};
|
|
659
|
+
if (options?.variables && options.variables.length > 0) {
|
|
660
|
+
metadata.variables = options.variables;
|
|
661
|
+
}
|
|
662
|
+
|
|
616
663
|
const { data, error } = await sb.rpc("upsert_agent_skill", {
|
|
617
664
|
p_user_id: this.userId,
|
|
618
665
|
p_name: name,
|
|
@@ -624,6 +671,7 @@ export class SkillManager {
|
|
|
624
671
|
p_keywords: options?.keywords || [],
|
|
625
672
|
p_change_summary: options?.changeSummary || null,
|
|
626
673
|
p_source_skill_id: options?.sourceSkillId || null,
|
|
674
|
+
p_metadata: Object.keys(metadata).length > 0 ? metadata : null,
|
|
627
675
|
});
|
|
628
676
|
|
|
629
677
|
if (error) {
|
|
@@ -631,11 +679,14 @@ export class SkillManager {
|
|
|
631
679
|
return;
|
|
632
680
|
}
|
|
633
681
|
|
|
634
|
-
// Update in-memory dbId
|
|
682
|
+
// Update in-memory skill with dbId and variables
|
|
635
683
|
const skill = this.skills.get(name);
|
|
636
684
|
if (skill && data && typeof data === "object" && "id" in (data as Record<string, unknown>)) {
|
|
637
685
|
skill.dbId = (data as Record<string, unknown>).id as string;
|
|
638
686
|
}
|
|
687
|
+
if (skill && options?.variables) {
|
|
688
|
+
skill.variables = options.variables;
|
|
689
|
+
}
|
|
639
690
|
|
|
640
691
|
log.debug(`Skill "${name}" synced to agent_skills`);
|
|
641
692
|
} catch (err) {
|
|
@@ -861,10 +912,90 @@ export class SkillManager {
|
|
|
861
912
|
return [];
|
|
862
913
|
}
|
|
863
914
|
}
|
|
915
|
+
|
|
916
|
+
// ── Variable Configuration ──────────────────────────────────────────
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Update user-specific variable values (config) for a skill.
|
|
920
|
+
* Persists to agent_skills.config in DB and updates in-memory.
|
|
921
|
+
*/
|
|
922
|
+
async updateConfig(
|
|
923
|
+
skillName: string,
|
|
924
|
+
config: Record<string, unknown>
|
|
925
|
+
): Promise<boolean> {
|
|
926
|
+
const skill = this.skills.get(skillName);
|
|
927
|
+
if (!skill) return false;
|
|
928
|
+
|
|
929
|
+
// Merge with existing config
|
|
930
|
+
const merged = { ...(skill.config || {}), ...config };
|
|
931
|
+
skill.config = merged;
|
|
932
|
+
|
|
933
|
+
// Persist to DB
|
|
934
|
+
if (this.userId && skill.dbId) {
|
|
935
|
+
try {
|
|
936
|
+
const sb = getSupabase();
|
|
937
|
+
await sb
|
|
938
|
+
.from("agent_skills")
|
|
939
|
+
.update({ config: merged })
|
|
940
|
+
.eq("id", skill.dbId)
|
|
941
|
+
.eq("user_id", this.userId);
|
|
942
|
+
log.debug(`Config updated for skill "${skillName}"`);
|
|
943
|
+
} catch (err) {
|
|
944
|
+
log.debug(`Config update failed for "${skillName}": ${err}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Get the unresolved (unconfigured) variables for a skill.
|
|
953
|
+
* Returns variables that are required but have no value in config.
|
|
954
|
+
*/
|
|
955
|
+
getUnconfiguredVariables(skillName: string): SkillVariable[] {
|
|
956
|
+
const skill = this.skills.get(skillName);
|
|
957
|
+
if (!skill?.variables) return [];
|
|
958
|
+
|
|
959
|
+
return skill.variables.filter((v) => {
|
|
960
|
+
if (!v.required) return false;
|
|
961
|
+
const value = skill.config?.[v.name];
|
|
962
|
+
return value === undefined || value === null || value === "";
|
|
963
|
+
});
|
|
964
|
+
}
|
|
864
965
|
}
|
|
865
966
|
|
|
866
967
|
// ── Exported Utility Functions ─────────────────────────────────────
|
|
867
968
|
|
|
969
|
+
/**
|
|
970
|
+
* Substitute {{variable_name}} placeholders in skill content with user config values.
|
|
971
|
+
* Also handles array values by joining with ", ".
|
|
972
|
+
*
|
|
973
|
+
* Unresolved variables are left as-is with a note: {{variable_name: [NOT CONFIGURED]}}
|
|
974
|
+
*/
|
|
975
|
+
export function substituteVariables(
|
|
976
|
+
content: string,
|
|
977
|
+
config: Record<string, unknown> | undefined,
|
|
978
|
+
variables?: SkillVariable[]
|
|
979
|
+
): string {
|
|
980
|
+
if (!content.includes("{{")) return content;
|
|
981
|
+
|
|
982
|
+
return content.replace(/\{\{(\w+)\}\}/g, (match, varName: string) => {
|
|
983
|
+
const value = config?.[varName];
|
|
984
|
+
|
|
985
|
+
if (value !== undefined && value !== null) {
|
|
986
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
987
|
+
return String(value);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Check for default value in variable definitions
|
|
991
|
+
const varDef = variables?.find((v) => v.name === varName);
|
|
992
|
+
if (varDef?.default !== undefined) return varDef.default;
|
|
993
|
+
|
|
994
|
+
// Leave as-is with warning for unresolved
|
|
995
|
+
return `{{${varName}: [NOT CONFIGURED]}}`;
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
868
999
|
/**
|
|
869
1000
|
* Substitute $ARGUMENTS, $ARGUMENTS[N], and $N placeholders in skill content.
|
|
870
1001
|
*/
|