assistme 0.2.2 → 0.2.4
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 +87 -356
- package/package.json +1 -1
- package/src/agent/mcp-servers.ts +53 -123
- package/src/agent/processor.test.ts +1 -4
- package/src/agent/processor.ts +6 -30
- package/src/agent/skill-extractor.ts +0 -439
- package/src/agent/skills.ts +23 -21
- package/src/agent/memory-extractor.ts +0 -128
- package/src/utils/validation.test.ts +0 -153
- package/src/utils/validation.ts +0 -101
package/dist/index.js
CHANGED
|
@@ -1965,27 +1965,30 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
1965
1965
|
const sb = getSupabase();
|
|
1966
1966
|
const source = options?.source || "manual";
|
|
1967
1967
|
const metadata = options?.emoji ? { openclaw: { emoji: options.emoji } } : {};
|
|
1968
|
-
const { data, error } = await sb.
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
is_public: false
|
|
1980
|
-
},
|
|
1981
|
-
{ onConflict: "author_id,name" }
|
|
1982
|
-
).select("id, name").single();
|
|
1968
|
+
const { data, error } = await sb.rpc("create_skill", {
|
|
1969
|
+
p_user_id: this.userId,
|
|
1970
|
+
p_name: name,
|
|
1971
|
+
p_description: description,
|
|
1972
|
+
p_content: content,
|
|
1973
|
+
p_version: "1.0.0",
|
|
1974
|
+
p_source: source,
|
|
1975
|
+
p_emoji: options?.emoji || null,
|
|
1976
|
+
p_keywords: options?.keywords || [],
|
|
1977
|
+
p_metadata: metadata
|
|
1978
|
+
});
|
|
1983
1979
|
if (error) {
|
|
1984
1980
|
log.debug(`Skill create failed for "${name}": ${error.message}`);
|
|
1985
1981
|
return null;
|
|
1986
1982
|
}
|
|
1987
|
-
|
|
1988
|
-
|
|
1983
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
1984
|
+
if (!row) {
|
|
1985
|
+
log.debug(`Skill create returned no data for "${name}"`);
|
|
1986
|
+
return null;
|
|
1987
|
+
}
|
|
1988
|
+
const id = row.out_id || row.id;
|
|
1989
|
+
const skillName = row.out_name || row.name;
|
|
1990
|
+
log.info(`Skill "${skillName}" created in skills table (pending approval)`);
|
|
1991
|
+
return { id, name: skillName };
|
|
1989
1992
|
} catch (err) {
|
|
1990
1993
|
log.debug(`Skill create error: ${err}`);
|
|
1991
1994
|
return null;
|
|
@@ -2327,91 +2330,6 @@ function preprocessDynamicContext(content, cwd) {
|
|
|
2327
2330
|
});
|
|
2328
2331
|
}
|
|
2329
2332
|
|
|
2330
|
-
// src/agent/memory-extractor.ts
|
|
2331
|
-
var EXTRACTION_PROMPT = `You are a memory extraction system. Analyze the following conversation between a user and an AI assistant. Extract any facts worth remembering about the user for future conversations.
|
|
2332
|
-
|
|
2333
|
-
Rules:
|
|
2334
|
-
- Only extract FACTUAL information about the user, not about the task itself
|
|
2335
|
-
- Be concise: each memory should be a single clear statement
|
|
2336
|
-
- Don't extract trivial/obvious things (e.g. "user asked a question")
|
|
2337
|
-
- Categories: "preference" (likes/dislikes/habits), "instruction" (standing orders like "always do X"), "context" (work/life info), "fact" (specific facts), "general" (other)
|
|
2338
|
-
- Importance: 3-4 for minor preferences, 5-6 for useful context, 7-8 for strong preferences/instructions, 9-10 for critical standing instructions
|
|
2339
|
-
- If nothing worth remembering, return an empty array
|
|
2340
|
-
- Max 3 memories per conversation
|
|
2341
|
-
- Support both English and Chinese content
|
|
2342
|
-
|
|
2343
|
-
Respond with ONLY valid JSON \u2014 no markdown, no explanation:
|
|
2344
|
-
[{"content": "...", "category": "...", "importance": N, "tags": ["..."]}]
|
|
2345
|
-
|
|
2346
|
-
If nothing to remember: []`;
|
|
2347
|
-
async function extractMemoriesWithLLM(taskPrompt, taskResult) {
|
|
2348
|
-
const config = getConfig();
|
|
2349
|
-
const apiKey = config.anthropicApiKey;
|
|
2350
|
-
if (!apiKey) {
|
|
2351
|
-
log.debug("No API key, skipping LLM memory extraction");
|
|
2352
|
-
return [];
|
|
2353
|
-
}
|
|
2354
|
-
const truncatedPrompt = taskPrompt.slice(0, 2e3);
|
|
2355
|
-
const truncatedResult = taskResult.slice(0, 3e3);
|
|
2356
|
-
const userMessage = `<user_request>
|
|
2357
|
-
${truncatedPrompt}
|
|
2358
|
-
</user_request>
|
|
2359
|
-
|
|
2360
|
-
<assistant_response>
|
|
2361
|
-
${truncatedResult}
|
|
2362
|
-
</assistant_response>`;
|
|
2363
|
-
try {
|
|
2364
|
-
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
2365
|
-
method: "POST",
|
|
2366
|
-
headers: {
|
|
2367
|
-
"Content-Type": "application/json",
|
|
2368
|
-
"x-api-key": apiKey,
|
|
2369
|
-
"anthropic-version": "2023-06-01"
|
|
2370
|
-
},
|
|
2371
|
-
body: JSON.stringify({
|
|
2372
|
-
model: "claude-haiku-4-5-20251001",
|
|
2373
|
-
max_tokens: 512,
|
|
2374
|
-
system: EXTRACTION_PROMPT,
|
|
2375
|
-
messages: [{ role: "user", content: userMessage }]
|
|
2376
|
-
})
|
|
2377
|
-
});
|
|
2378
|
-
if (!response.ok) {
|
|
2379
|
-
const errText = await response.text();
|
|
2380
|
-
log.debug(`Memory extraction API error: ${response.status} ${errText.slice(0, 200)}`);
|
|
2381
|
-
return [];
|
|
2382
|
-
}
|
|
2383
|
-
const data = await response.json();
|
|
2384
|
-
const text = data.content?.[0]?.type === "text" ? data.content[0].text : "";
|
|
2385
|
-
if (!text || text.trim() === "[]") {
|
|
2386
|
-
log.debug("LLM memory extraction: nothing to remember");
|
|
2387
|
-
return [];
|
|
2388
|
-
}
|
|
2389
|
-
const jsonStr = text.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
|
|
2390
|
-
const memories = JSON.parse(jsonStr);
|
|
2391
|
-
const validCategories = [
|
|
2392
|
-
"general",
|
|
2393
|
-
"preference",
|
|
2394
|
-
"instruction",
|
|
2395
|
-
"context",
|
|
2396
|
-
"skill_learned",
|
|
2397
|
-
"fact"
|
|
2398
|
-
];
|
|
2399
|
-
return memories.filter(
|
|
2400
|
-
(m) => m.content && m.content.length > 5 && validCategories.includes(m.category)
|
|
2401
|
-
).map((m) => ({
|
|
2402
|
-
content: m.content.slice(0, 500),
|
|
2403
|
-
category: m.category,
|
|
2404
|
-
importance: Math.max(1, Math.min(10, m.importance || 5)),
|
|
2405
|
-
tags: Array.isArray(m.tags) ? m.tags.slice(0, 5) : []
|
|
2406
|
-
})).slice(0, 3);
|
|
2407
|
-
} catch (err) {
|
|
2408
|
-
log.debug(
|
|
2409
|
-
`LLM memory extraction failed: ${err instanceof Error ? err.message : err}`
|
|
2410
|
-
);
|
|
2411
|
-
return [];
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
2333
|
// src/utils/retry.ts
|
|
2416
2334
|
async function withRetry(fn, opts = {}) {
|
|
2417
2335
|
const {
|
|
@@ -2883,138 +2801,6 @@ function getLimiterForTool(toolName) {
|
|
|
2883
2801
|
return null;
|
|
2884
2802
|
}
|
|
2885
2803
|
|
|
2886
|
-
// src/agent/skill-extractor.ts
|
|
2887
|
-
var DECOMPOSE_JOB_PROMPT = `You are a job analysis system. Given a person's job description, decompose their work into individual AUTOMATABLE SKILLS that an AI agent could learn and execute.
|
|
2888
|
-
|
|
2889
|
-
The AI agent has these capabilities:
|
|
2890
|
-
- Control a real Chrome browser (navigate, click, type, read pages, take screenshots)
|
|
2891
|
-
- Read/write files on the local machine
|
|
2892
|
-
- Execute shell commands
|
|
2893
|
-
- Store memories and learn from interactions
|
|
2894
|
-
|
|
2895
|
-
Rules:
|
|
2896
|
-
- Focus on RECURRING, REPETITIVE tasks that benefit from automation
|
|
2897
|
-
- Each skill should be a single, well-defined workflow
|
|
2898
|
-
- Prioritize tasks the person does frequently
|
|
2899
|
-
- Mark tasks as automatable:true only if the agent can handle them end-to-end
|
|
2900
|
-
- Name skills in kebab-case
|
|
2901
|
-
- Be specific \u2014 "check-email" is too vague, "summarize-unread-gmail" is better
|
|
2902
|
-
- Generate 5-15 skills depending on job complexity
|
|
2903
|
-
- Consider both web-based and file-based workflows
|
|
2904
|
-
|
|
2905
|
-
Respond with ONLY valid JSON array \u2014 no markdown wrapping:
|
|
2906
|
-
[{"name": "skill-name", "description": "What this skill does", "category": "category", "priority": "high|medium|low", "automatable": true|false}]`;
|
|
2907
|
-
async function decomposeJob(jobDescription, existingSkillNames = []) {
|
|
2908
|
-
const config = getConfig();
|
|
2909
|
-
const apiKey = config.anthropicApiKey;
|
|
2910
|
-
if (!apiKey) return [];
|
|
2911
|
-
const existingNote = existingSkillNames.length > 0 ? `
|
|
2912
|
-
|
|
2913
|
-
The agent already has these skills (do NOT duplicate): ${existingSkillNames.join(", ")}` : "";
|
|
2914
|
-
try {
|
|
2915
|
-
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
2916
|
-
method: "POST",
|
|
2917
|
-
headers: {
|
|
2918
|
-
"Content-Type": "application/json",
|
|
2919
|
-
"x-api-key": apiKey,
|
|
2920
|
-
"anthropic-version": "2023-06-01"
|
|
2921
|
-
},
|
|
2922
|
-
body: JSON.stringify({
|
|
2923
|
-
model: "claude-haiku-4-5-20251001",
|
|
2924
|
-
max_tokens: 2048,
|
|
2925
|
-
system: DECOMPOSE_JOB_PROMPT,
|
|
2926
|
-
messages: [
|
|
2927
|
-
{
|
|
2928
|
-
role: "user",
|
|
2929
|
-
content: `<job_description>
|
|
2930
|
-
${jobDescription.slice(0, 3e3)}
|
|
2931
|
-
</job_description>${existingNote}`
|
|
2932
|
-
}
|
|
2933
|
-
]
|
|
2934
|
-
})
|
|
2935
|
-
});
|
|
2936
|
-
if (!response.ok) return [];
|
|
2937
|
-
const data = await response.json();
|
|
2938
|
-
const text = data.content?.[0]?.type === "text" ? data.content[0].text : "";
|
|
2939
|
-
if (!text) return [];
|
|
2940
|
-
const jsonStr = text.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
|
|
2941
|
-
const specs = JSON.parse(jsonStr);
|
|
2942
|
-
if (!Array.isArray(specs)) return [];
|
|
2943
|
-
return specs.filter((s) => s.name && s.description).map((s) => ({
|
|
2944
|
-
...s,
|
|
2945
|
-
name: s.name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")
|
|
2946
|
-
}));
|
|
2947
|
-
} catch (err) {
|
|
2948
|
-
log.debug(`Job decomposition failed: ${err}`);
|
|
2949
|
-
return [];
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
var GENERATE_PROMPT = `You are a skill authoring system for an AI agent. Given a skill name, description, and context about the user's work, generate detailed step-by-step instructions that the AI agent can follow to execute this skill.
|
|
2953
|
-
|
|
2954
|
-
The AI agent has these capabilities:
|
|
2955
|
-
- Control a real Chrome browser (navigate, click, type, read pages, take screenshots)
|
|
2956
|
-
- Read/write files on the local machine
|
|
2957
|
-
- Execute shell commands (Bash)
|
|
2958
|
-
- Store memories about the user
|
|
2959
|
-
|
|
2960
|
-
Rules:
|
|
2961
|
-
- Write clear, actionable markdown instructions
|
|
2962
|
-
- Use numbered steps with bold action names
|
|
2963
|
-
- Include error handling (what to do if a page doesn't load, element not found, etc.)
|
|
2964
|
-
- Use placeholders like {query}, {date}, {recipient} for variable inputs
|
|
2965
|
-
- Include a $ARGUMENTS line at the top if the skill accepts parameters
|
|
2966
|
-
- Be specific about which browser tools to use (browser_navigate, browser_click, etc.)
|
|
2967
|
-
- Include validation steps (verify the action worked)
|
|
2968
|
-
- Keep instructions thorough but concise (10-25 steps)
|
|
2969
|
-
|
|
2970
|
-
Respond with ONLY valid JSON \u2014 no markdown wrapping:
|
|
2971
|
-
{"name": "skill-name", "description": "One-line description", "steps": "## Workflow\\n\\n$ARGUMENTS: describe expected arguments\\n\\n1. **Step one**\\n...", "emoji": "\u{1F527}", "keywords": ["keyword1", "keyword2"]}`;
|
|
2972
|
-
async function generateSkillFromDescription(name, description, jobContext) {
|
|
2973
|
-
const config = getConfig();
|
|
2974
|
-
const apiKey = config.anthropicApiKey;
|
|
2975
|
-
if (!apiKey) return null;
|
|
2976
|
-
const contextBlock = jobContext ? `
|
|
2977
|
-
|
|
2978
|
-
<user_job_context>
|
|
2979
|
-
${jobContext.slice(0, 1e3)}
|
|
2980
|
-
</user_job_context>` : "";
|
|
2981
|
-
try {
|
|
2982
|
-
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
2983
|
-
method: "POST",
|
|
2984
|
-
headers: {
|
|
2985
|
-
"Content-Type": "application/json",
|
|
2986
|
-
"x-api-key": apiKey,
|
|
2987
|
-
"anthropic-version": "2023-06-01"
|
|
2988
|
-
},
|
|
2989
|
-
body: JSON.stringify({
|
|
2990
|
-
model: "claude-haiku-4-5-20251001",
|
|
2991
|
-
max_tokens: 2048,
|
|
2992
|
-
system: GENERATE_PROMPT,
|
|
2993
|
-
messages: [
|
|
2994
|
-
{
|
|
2995
|
-
role: "user",
|
|
2996
|
-
content: `Generate a skill for:
|
|
2997
|
-
Name: ${name}
|
|
2998
|
-
Description: ${description}${contextBlock}`
|
|
2999
|
-
}
|
|
3000
|
-
]
|
|
3001
|
-
})
|
|
3002
|
-
});
|
|
3003
|
-
if (!response.ok) return null;
|
|
3004
|
-
const data = await response.json();
|
|
3005
|
-
const text = data.content?.[0]?.type === "text" ? data.content[0].text : "";
|
|
3006
|
-
if (!text) return null;
|
|
3007
|
-
const jsonStr = text.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
|
|
3008
|
-
const skill = JSON.parse(jsonStr);
|
|
3009
|
-
if (!skill || !skill.name || !skill.steps) return null;
|
|
3010
|
-
skill.name = skill.name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3011
|
-
return skill;
|
|
3012
|
-
} catch (err) {
|
|
3013
|
-
log.debug(`Skill generation failed: ${err}`);
|
|
3014
|
-
return null;
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
|
|
3018
2804
|
// src/agent/mcp-servers.ts
|
|
3019
2805
|
async function callTool(name, input) {
|
|
3020
2806
|
const limiter = getLimiterForTool(name);
|
|
@@ -3356,130 +3142,93 @@ ${content}`;
|
|
|
3356
3142
|
),
|
|
3357
3143
|
tool(
|
|
3358
3144
|
"skill_generate",
|
|
3359
|
-
"
|
|
3145
|
+
"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 + skill_add. After creating all skills, call skill_link_job to link them to the job and mark it as analyzed.",
|
|
3360
3146
|
{
|
|
3361
3147
|
job_name: z.string().describe(
|
|
3362
3148
|
"Short name for this job/role. Example: '\u7535\u5546\u8FD0\u8425', 'Frontend Dev', 'Data Analyst'"
|
|
3363
3149
|
),
|
|
3364
3150
|
job_description: z.string().describe(
|
|
3365
3151
|
"Description of the user's job, role, and daily tasks. Can be in any language. Example: '\u6211\u662F\u7535\u5546\u8FD0\u8425\uFF0C\u6BCF\u5929\u8981\u770B\u7ADE\u54C1\u4EF7\u683C\u3001\u5199\u5546\u54C1\u6587\u6848\u3001\u56DE\u590D\u5BA2\u6237\u8BC4\u8BBA'"
|
|
3366
|
-
)
|
|
3367
|
-
auto_create: z.boolean().optional().describe("If true, automatically create all generated skills. If false (default), return the plan for review first.")
|
|
3152
|
+
)
|
|
3368
3153
|
},
|
|
3369
3154
|
async (args) => {
|
|
3370
3155
|
const existingNames = skillManager.getAll().map((s) => s.name);
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
content: [{
|
|
3375
|
-
type: "text",
|
|
3376
|
-
text: "Could not decompose the job description into automatable skills. Try providing more detail about your daily tasks."
|
|
3377
|
-
}]
|
|
3378
|
-
};
|
|
3379
|
-
}
|
|
3380
|
-
if (!args.auto_create) {
|
|
3381
|
-
let response2 = `## Job Analysis: ${args.job_name}
|
|
3156
|
+
let response = `## Job: ${args.job_name}
|
|
3157
|
+
`;
|
|
3158
|
+
response += `**Description:** ${args.job_description}
|
|
3382
3159
|
|
|
3383
|
-
|
|
3160
|
+
`;
|
|
3161
|
+
if (existingNames.length > 0) {
|
|
3162
|
+
response += `**Existing skills (do NOT duplicate):** ${existingNames.join(", ")}
|
|
3384
3163
|
|
|
3385
3164
|
`;
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
}
|
|
3390
|
-
for (const [priority, items] of Object.entries(byPriority)) {
|
|
3391
|
-
if (items.length === 0) continue;
|
|
3392
|
-
const label = priority === "high" ? "High Priority" : priority === "medium" ? "Medium Priority" : "Low Priority";
|
|
3393
|
-
response2 += `### ${label}
|
|
3165
|
+
}
|
|
3166
|
+
response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills. `;
|
|
3167
|
+
response += `For each skill, call \`skill_create\` with:
|
|
3394
3168
|
`;
|
|
3395
|
-
|
|
3396
|
-
const auto = s.automatable ? "" : " (needs human assistance)";
|
|
3397
|
-
response2 += `- **${s.name}**: ${s.description}${auto}
|
|
3169
|
+
response += `- name: kebab-case name (e.g. "slack-message-check")
|
|
3398
3170
|
`;
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
return { content: [{ type: "text", text: response2 }] };
|
|
3405
|
-
}
|
|
3406
|
-
const created = [];
|
|
3407
|
-
const failed = [];
|
|
3408
|
-
for (let i = 0; i < specs.length; i += 3) {
|
|
3409
|
-
const batch = specs.slice(i, i + 3);
|
|
3410
|
-
const results = await Promise.allSettled(
|
|
3411
|
-
batch.map(
|
|
3412
|
-
(spec) => generateSkillFromDescription(spec.name, spec.description, args.job_description)
|
|
3413
|
-
)
|
|
3414
|
-
);
|
|
3415
|
-
for (let j = 0; j < results.length; j++) {
|
|
3416
|
-
const spec = batch[j];
|
|
3417
|
-
const result = results[j];
|
|
3418
|
-
if (result.status === "fulfilled" && result.value) {
|
|
3419
|
-
const generated = result.value;
|
|
3420
|
-
const existing = skillManager.findSimilar(generated.name);
|
|
3421
|
-
if (existing) {
|
|
3422
|
-
failed.push(`${spec.name} (duplicate of "${existing.name}")`);
|
|
3423
|
-
continue;
|
|
3424
|
-
}
|
|
3425
|
-
const createResult = await skillManager.create(
|
|
3426
|
-
generated.name,
|
|
3427
|
-
generated.description || spec.description,
|
|
3428
|
-
generated.steps,
|
|
3429
|
-
{
|
|
3430
|
-
source: "job_generated",
|
|
3431
|
-
emoji: generated.emoji,
|
|
3432
|
-
keywords: generated.keywords
|
|
3433
|
-
}
|
|
3434
|
-
);
|
|
3435
|
-
if (createResult) {
|
|
3436
|
-
const added = await skillManager.addSkill(createResult.id);
|
|
3437
|
-
if (added) {
|
|
3438
|
-
created.push(generated.name);
|
|
3439
|
-
} else {
|
|
3440
|
-
failed.push(`${spec.name} (add failed)`);
|
|
3441
|
-
}
|
|
3442
|
-
} else {
|
|
3443
|
-
failed.push(spec.name);
|
|
3444
|
-
}
|
|
3445
|
-
} else {
|
|
3446
|
-
failed.push(spec.name);
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
}
|
|
3450
|
-
if (userId) {
|
|
3451
|
-
saveJobToDb(
|
|
3452
|
-
userId,
|
|
3453
|
-
args.job_name,
|
|
3454
|
-
args.job_description,
|
|
3455
|
-
created
|
|
3456
|
-
).catch((err) => {
|
|
3457
|
-
log.debug(`saveJobToDb error: ${err}`);
|
|
3458
|
-
});
|
|
3459
|
-
}
|
|
3460
|
-
let response = `## Skills Generated for "${args.job_name}"
|
|
3171
|
+
response += `- description: one-line description
|
|
3172
|
+
`;
|
|
3173
|
+
response += `- instructions: detailed step-by-step markdown instructions the agent can follow
|
|
3174
|
+
`;
|
|
3175
|
+
response += `- emoji: a single emoji representing the skill
|
|
3461
3176
|
|
|
3462
3177
|
`;
|
|
3463
|
-
response += `
|
|
3178
|
+
response += `After creating each skill, call \`skill_add\` with the returned skill ID to add it to the user's collection.
|
|
3464
3179
|
|
|
3465
3180
|
`;
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
const emoji = skill?.metadata.emoji || "";
|
|
3469
|
-
response += `- ${emoji} **${name}**: ${skill?.description || ""}
|
|
3181
|
+
response += `After ALL skills are created and added, 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.
|
|
3182
|
+
|
|
3470
3183
|
`;
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3184
|
+
response += `**Guidelines for skill instructions:**
|
|
3185
|
+
`;
|
|
3186
|
+
response += `- Write clear, actionable markdown steps
|
|
3187
|
+
`;
|
|
3188
|
+
response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks
|
|
3189
|
+
`;
|
|
3190
|
+
response += `- Include error handling steps
|
|
3191
|
+
`;
|
|
3192
|
+
response += `- Use placeholders like {query}, {date} for variable inputs
|
|
3193
|
+
`;
|
|
3194
|
+
response += `- Each skill should be a single, well-defined workflow (10-25 steps)
|
|
3475
3195
|
`;
|
|
3476
|
-
}
|
|
3477
|
-
response += "\nThese skills are now available. When you give me a task that matches, I'll automatically use the right skill.";
|
|
3478
|
-
response += "\nUse `skill_invoke` to test any skill, or `skill_improve` to refine them after use.";
|
|
3479
|
-
log.success(`Job "${args.job_name}": created ${created.length} skills`);
|
|
3480
3196
|
return { content: [{ type: "text", text: response }] };
|
|
3481
3197
|
}
|
|
3482
3198
|
),
|
|
3199
|
+
tool(
|
|
3200
|
+
"skill_link_job",
|
|
3201
|
+
"Link created skills to a job and mark it as analyzed. Call this after creating all skills for a job via skill_create + skill_add.",
|
|
3202
|
+
{
|
|
3203
|
+
job_name: z.string().describe("Name of the job to link skills to"),
|
|
3204
|
+
job_description: z.string().describe("Job description (used if job doesn't exist yet)"),
|
|
3205
|
+
skill_names: z.array(z.string()).describe("Names of skills to link to this job")
|
|
3206
|
+
},
|
|
3207
|
+
async (args) => {
|
|
3208
|
+
if (!userId) {
|
|
3209
|
+
return {
|
|
3210
|
+
content: [{ type: "text", text: "Not authenticated. Cannot link job." }]
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
try {
|
|
3214
|
+
await saveJobToDb(userId, args.job_name, args.job_description, args.skill_names);
|
|
3215
|
+
log.success(`Job "${args.job_name}": linked ${args.skill_names.length} skills and marked as analyzed`);
|
|
3216
|
+
return {
|
|
3217
|
+
content: [{
|
|
3218
|
+
type: "text",
|
|
3219
|
+
text: `Job "${args.job_name}" linked with ${args.skill_names.length} skills and marked as analyzed.`
|
|
3220
|
+
}]
|
|
3221
|
+
};
|
|
3222
|
+
} catch (err) {
|
|
3223
|
+
return {
|
|
3224
|
+
content: [{
|
|
3225
|
+
type: "text",
|
|
3226
|
+
text: `Failed to link job: ${err instanceof Error ? err.message : err}`
|
|
3227
|
+
}]
|
|
3228
|
+
};
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
),
|
|
3483
3232
|
tool(
|
|
3484
3233
|
"skill_browse",
|
|
3485
3234
|
"Browse the skill marketplace to discover skills published by the community. Search by keyword, filter by category, and sort by popularity or rating.",
|
|
@@ -3877,6 +3626,8 @@ Available capabilities:
|
|
|
3877
3626
|
- You can remember things about the user using memory_store
|
|
3878
3627
|
- Use this when you learn preferences, important facts, or standing instructions
|
|
3879
3628
|
- Your stored memories persist across conversations
|
|
3629
|
+
- PROACTIVELY use memory_store during tasks when you discover user preferences, habits, or important context
|
|
3630
|
+
- Before completing a task, consider if anything learned should be remembered for future conversations
|
|
3880
3631
|
|
|
3881
3632
|
4. SKILL PLANNING (pre-task):
|
|
3882
3633
|
- Before executing a complex task, analyze if it matches an existing skill (use skill_invoke)
|
|
@@ -4019,6 +3770,7 @@ var TaskProcessor = class {
|
|
|
4019
3770
|
"mcp__assistme-agent__skill_invoke",
|
|
4020
3771
|
"mcp__assistme-agent__skill_search",
|
|
4021
3772
|
"mcp__assistme-agent__skill_generate",
|
|
3773
|
+
"mcp__assistme-agent__skill_link_job",
|
|
4022
3774
|
"mcp__assistme-agent__skill_browse",
|
|
4023
3775
|
"mcp__assistme-agent__skill_add",
|
|
4024
3776
|
"mcp__assistme-agent__skill_publish",
|
|
@@ -4132,27 +3884,6 @@ var TaskProcessor = class {
|
|
|
4132
3884
|
convHistory.splice(0, convHistory.length - MAX_HISTORY_ENTRIES * 2);
|
|
4133
3885
|
}
|
|
4134
3886
|
this.historyCache.set(task.conversation_id, convHistory);
|
|
4135
|
-
if (this.memoryManager && finalResponse) {
|
|
4136
|
-
const mm = this.memoryManager;
|
|
4137
|
-
const taskIdRef = task.id;
|
|
4138
|
-
extractMemoriesWithLLM(task.prompt, finalResponse).then(async (memories) => {
|
|
4139
|
-
for (const mem of memories) {
|
|
4140
|
-
try {
|
|
4141
|
-
await mm.remember(mem.content, mem.category, {
|
|
4142
|
-
importance: mem.importance,
|
|
4143
|
-
tags: mem.tags,
|
|
4144
|
-
sourceMessageId: taskIdRef
|
|
4145
|
-
});
|
|
4146
|
-
log.info(`Memory extracted: [${mem.category}] ${mem.content.slice(0, 60)}...`);
|
|
4147
|
-
} catch {
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
|
-
if (memories.length > 0) {
|
|
4151
|
-
log.success(`${memories.length} memory(s) auto-extracted`);
|
|
4152
|
-
}
|
|
4153
|
-
}).catch(() => {
|
|
4154
|
-
});
|
|
4155
|
-
}
|
|
4156
3887
|
} catch (err) {
|
|
4157
3888
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
4158
3889
|
log.error(`Task failed: ${errorMsg}`);
|