assistme 0.2.1 → 0.2.3
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 +86 -355
- package/package.json +1 -1
- package/src/agent/mcp-servers.ts +62 -122
- package/src/agent/processor.ts +6 -30
- package/src/agent/skill-extractor.ts +28 -8
- package/src/agent/skills.ts +18 -20
package/dist/index.js
CHANGED
|
@@ -1965,27 +1965,28 @@ _(${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
|
}
|
|
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
|
+
}
|
|
1987
1988
|
log.info(`Skill "${name}" created in skills table (pending approval)`);
|
|
1988
|
-
return
|
|
1989
|
+
return row;
|
|
1989
1990
|
} catch (err) {
|
|
1990
1991
|
log.debug(`Skill create error: ${err}`);
|
|
1991
1992
|
return null;
|
|
@@ -2327,91 +2328,6 @@ function preprocessDynamicContext(content, cwd) {
|
|
|
2327
2328
|
});
|
|
2328
2329
|
}
|
|
2329
2330
|
|
|
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
2331
|
// src/utils/retry.ts
|
|
2416
2332
|
async function withRetry(fn, opts = {}) {
|
|
2417
2333
|
const {
|
|
@@ -2883,138 +2799,6 @@ function getLimiterForTool(toolName) {
|
|
|
2883
2799
|
return null;
|
|
2884
2800
|
}
|
|
2885
2801
|
|
|
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
2802
|
// src/agent/mcp-servers.ts
|
|
3019
2803
|
async function callTool(name, input) {
|
|
3020
2804
|
const limiter = getLimiterForTool(name);
|
|
@@ -3356,129 +3140,93 @@ ${content}`;
|
|
|
3356
3140
|
),
|
|
3357
3141
|
tool(
|
|
3358
3142
|
"skill_generate",
|
|
3359
|
-
"
|
|
3143
|
+
"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
3144
|
{
|
|
3361
3145
|
job_name: z.string().describe(
|
|
3362
3146
|
"Short name for this job/role. Example: '\u7535\u5546\u8FD0\u8425', 'Frontend Dev', 'Data Analyst'"
|
|
3363
3147
|
),
|
|
3364
3148
|
job_description: z.string().describe(
|
|
3365
3149
|
"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.")
|
|
3150
|
+
)
|
|
3368
3151
|
},
|
|
3369
3152
|
async (args) => {
|
|
3370
3153
|
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}
|
|
3154
|
+
let response = `## Job: ${args.job_name}
|
|
3155
|
+
`;
|
|
3156
|
+
response += `**Description:** ${args.job_description}
|
|
3382
3157
|
|
|
3383
|
-
|
|
3158
|
+
`;
|
|
3159
|
+
if (existingNames.length > 0) {
|
|
3160
|
+
response += `**Existing skills (do NOT duplicate):** ${existingNames.join(", ")}
|
|
3384
3161
|
|
|
3385
3162
|
`;
|
|
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}
|
|
3163
|
+
}
|
|
3164
|
+
response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills. `;
|
|
3165
|
+
response += `For each skill, call \`skill_create\` with:
|
|
3394
3166
|
`;
|
|
3395
|
-
|
|
3396
|
-
const auto = s.automatable ? "" : " (needs human assistance)";
|
|
3397
|
-
response2 += `- **${s.name}**: ${s.description}${auto}
|
|
3167
|
+
response += `- name: kebab-case name (e.g. "slack-message-check")
|
|
3398
3168
|
`;
|
|
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(() => {
|
|
3457
|
-
});
|
|
3458
|
-
}
|
|
3459
|
-
let response = `## Skills Generated for "${args.job_name}"
|
|
3169
|
+
response += `- description: one-line description
|
|
3170
|
+
`;
|
|
3171
|
+
response += `- instructions: detailed step-by-step markdown instructions the agent can follow
|
|
3172
|
+
`;
|
|
3173
|
+
response += `- emoji: a single emoji representing the skill
|
|
3460
3174
|
|
|
3461
3175
|
`;
|
|
3462
|
-
response += `
|
|
3176
|
+
response += `After creating each skill, call \`skill_add\` with the returned skill ID to add it to the user's collection.
|
|
3463
3177
|
|
|
3464
3178
|
`;
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
const emoji = skill?.metadata.emoji || "";
|
|
3468
|
-
response += `- ${emoji} **${name}**: ${skill?.description || ""}
|
|
3179
|
+
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.
|
|
3180
|
+
|
|
3469
3181
|
`;
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3182
|
+
response += `**Guidelines for skill instructions:**
|
|
3183
|
+
`;
|
|
3184
|
+
response += `- Write clear, actionable markdown steps
|
|
3185
|
+
`;
|
|
3186
|
+
response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks
|
|
3187
|
+
`;
|
|
3188
|
+
response += `- Include error handling steps
|
|
3189
|
+
`;
|
|
3190
|
+
response += `- Use placeholders like {query}, {date} for variable inputs
|
|
3191
|
+
`;
|
|
3192
|
+
response += `- Each skill should be a single, well-defined workflow (10-25 steps)
|
|
3474
3193
|
`;
|
|
3475
|
-
}
|
|
3476
|
-
response += "\nThese skills are now available. When you give me a task that matches, I'll automatically use the right skill.";
|
|
3477
|
-
response += "\nUse `skill_invoke` to test any skill, or `skill_improve` to refine them after use.";
|
|
3478
|
-
log.success(`Job "${args.job_name}": created ${created.length} skills`);
|
|
3479
3194
|
return { content: [{ type: "text", text: response }] };
|
|
3480
3195
|
}
|
|
3481
3196
|
),
|
|
3197
|
+
tool(
|
|
3198
|
+
"skill_link_job",
|
|
3199
|
+
"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.",
|
|
3200
|
+
{
|
|
3201
|
+
job_name: z.string().describe("Name of the job to link skills to"),
|
|
3202
|
+
job_description: z.string().describe("Job description (used if job doesn't exist yet)"),
|
|
3203
|
+
skill_names: z.array(z.string()).describe("Names of skills to link to this job")
|
|
3204
|
+
},
|
|
3205
|
+
async (args) => {
|
|
3206
|
+
if (!userId) {
|
|
3207
|
+
return {
|
|
3208
|
+
content: [{ type: "text", text: "Not authenticated. Cannot link job." }]
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
try {
|
|
3212
|
+
await saveJobToDb(userId, args.job_name, args.job_description, args.skill_names);
|
|
3213
|
+
log.success(`Job "${args.job_name}": linked ${args.skill_names.length} skills and marked as analyzed`);
|
|
3214
|
+
return {
|
|
3215
|
+
content: [{
|
|
3216
|
+
type: "text",
|
|
3217
|
+
text: `Job "${args.job_name}" linked with ${args.skill_names.length} skills and marked as analyzed.`
|
|
3218
|
+
}]
|
|
3219
|
+
};
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
return {
|
|
3222
|
+
content: [{
|
|
3223
|
+
type: "text",
|
|
3224
|
+
text: `Failed to link job: ${err instanceof Error ? err.message : err}`
|
|
3225
|
+
}]
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
),
|
|
3482
3230
|
tool(
|
|
3483
3231
|
"skill_browse",
|
|
3484
3232
|
"Browse the skill marketplace to discover skills published by the community. Search by keyword, filter by category, and sort by popularity or rating.",
|
|
@@ -3793,7 +3541,8 @@ async function saveJobToDb(userId, jobName, jobDescription, createdSkillNames) {
|
|
|
3793
3541
|
}
|
|
3794
3542
|
}
|
|
3795
3543
|
}
|
|
3796
|
-
|
|
3544
|
+
await sb.from("agent_jobs").update({ skills_analyzed: true }).eq("id", job.id);
|
|
3545
|
+
log.debug(`Job "${jobName}" saved to DB with ${createdSkillNames.length} skill link(s), marked as analyzed`);
|
|
3797
3546
|
} catch (err) {
|
|
3798
3547
|
log.debug(`saveJobToDb error: ${err}`);
|
|
3799
3548
|
}
|
|
@@ -3875,6 +3624,8 @@ Available capabilities:
|
|
|
3875
3624
|
- You can remember things about the user using memory_store
|
|
3876
3625
|
- Use this when you learn preferences, important facts, or standing instructions
|
|
3877
3626
|
- Your stored memories persist across conversations
|
|
3627
|
+
- PROACTIVELY use memory_store during tasks when you discover user preferences, habits, or important context
|
|
3628
|
+
- Before completing a task, consider if anything learned should be remembered for future conversations
|
|
3878
3629
|
|
|
3879
3630
|
4. SKILL PLANNING (pre-task):
|
|
3880
3631
|
- Before executing a complex task, analyze if it matches an existing skill (use skill_invoke)
|
|
@@ -4017,6 +3768,7 @@ var TaskProcessor = class {
|
|
|
4017
3768
|
"mcp__assistme-agent__skill_invoke",
|
|
4018
3769
|
"mcp__assistme-agent__skill_search",
|
|
4019
3770
|
"mcp__assistme-agent__skill_generate",
|
|
3771
|
+
"mcp__assistme-agent__skill_link_job",
|
|
4020
3772
|
"mcp__assistme-agent__skill_browse",
|
|
4021
3773
|
"mcp__assistme-agent__skill_add",
|
|
4022
3774
|
"mcp__assistme-agent__skill_publish",
|
|
@@ -4130,27 +3882,6 @@ var TaskProcessor = class {
|
|
|
4130
3882
|
convHistory.splice(0, convHistory.length - MAX_HISTORY_ENTRIES * 2);
|
|
4131
3883
|
}
|
|
4132
3884
|
this.historyCache.set(task.conversation_id, convHistory);
|
|
4133
|
-
if (this.memoryManager && finalResponse) {
|
|
4134
|
-
const mm = this.memoryManager;
|
|
4135
|
-
const taskIdRef = task.id;
|
|
4136
|
-
extractMemoriesWithLLM(task.prompt, finalResponse).then(async (memories) => {
|
|
4137
|
-
for (const mem of memories) {
|
|
4138
|
-
try {
|
|
4139
|
-
await mm.remember(mem.content, mem.category, {
|
|
4140
|
-
importance: mem.importance,
|
|
4141
|
-
tags: mem.tags,
|
|
4142
|
-
sourceMessageId: taskIdRef
|
|
4143
|
-
});
|
|
4144
|
-
log.info(`Memory extracted: [${mem.category}] ${mem.content.slice(0, 60)}...`);
|
|
4145
|
-
} catch {
|
|
4146
|
-
}
|
|
4147
|
-
}
|
|
4148
|
-
if (memories.length > 0) {
|
|
4149
|
-
log.success(`${memories.length} memory(s) auto-extracted`);
|
|
4150
|
-
}
|
|
4151
|
-
}).catch(() => {
|
|
4152
|
-
});
|
|
4153
|
-
}
|
|
4154
3885
|
} catch (err) {
|
|
4155
3886
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
4156
3887
|
log.error(`Task failed: ${errorMsg}`);
|
package/package.json
CHANGED
package/src/agent/mcp-servers.ts
CHANGED
|
@@ -10,7 +10,8 @@ import { log } from "../utils/logger.js";
|
|
|
10
10
|
import type { MemoryManager, MemoryCategory } from "./memory.js";
|
|
11
11
|
import type { SkillManager } from "./skills.js";
|
|
12
12
|
import { substituteArguments, preprocessDynamicContext } from "./skills.js";
|
|
13
|
-
|
|
13
|
+
// decomposeJob and generateSkillFromDescription removed — the agent SDK handles
|
|
14
|
+
// job analysis and skill generation directly (no separate LLM API calls needed)
|
|
14
15
|
import { getSupabase } from "../db/supabase.js";
|
|
15
16
|
import { JobRunner } from "./job-runner.js";
|
|
16
17
|
import {
|
|
@@ -405,8 +406,9 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
405
406
|
),
|
|
406
407
|
tool(
|
|
407
408
|
"skill_generate",
|
|
408
|
-
"
|
|
409
|
-
"
|
|
409
|
+
"Prepare context for generating skills from a job description. Returns existing skills and job info " +
|
|
410
|
+
"so you can analyze the job and create skills using skill_create + skill_add. " +
|
|
411
|
+
"After creating all skills, call skill_link_job to link them to the job and mark it as analyzed.",
|
|
410
412
|
{
|
|
411
413
|
job_name: z.string().describe(
|
|
412
414
|
"Short name for this job/role. Example: '电商运营', 'Frontend Dev', 'Data Analyst'"
|
|
@@ -415,135 +417,67 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
415
417
|
"Description of the user's job, role, and daily tasks. Can be in any language. " +
|
|
416
418
|
"Example: '我是电商运营,每天要看竞品价格、写商品文案、回复客户评论'"
|
|
417
419
|
),
|
|
418
|
-
auto_create: z
|
|
419
|
-
.boolean()
|
|
420
|
-
.optional()
|
|
421
|
-
.describe("If true, automatically create all generated skills. If false (default), return the plan for review first."),
|
|
422
420
|
},
|
|
423
421
|
async (args) => {
|
|
424
|
-
// Step 1: Decompose job into skill specs
|
|
425
422
|
const existingNames = skillManager.getAll().map((s) => s.name);
|
|
426
|
-
const specs = await decomposeJob(args.job_description, existingNames);
|
|
427
|
-
|
|
428
|
-
if (specs.length === 0) {
|
|
429
|
-
return {
|
|
430
|
-
content: [{
|
|
431
|
-
type: "text",
|
|
432
|
-
text: "Could not decompose the job description into automatable skills. Try providing more detail about your daily tasks.",
|
|
433
|
-
}],
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// If not auto-creating, return the decomposition plan
|
|
438
|
-
if (!args.auto_create) {
|
|
439
|
-
let response = `## Job Analysis: ${args.job_name}\n\nI identified **${specs.length} skills** from your job description:\n\n`;
|
|
440
|
-
|
|
441
|
-
const byPriority = { high: [] as typeof specs, medium: [] as typeof specs, low: [] as typeof specs };
|
|
442
|
-
for (const s of specs) {
|
|
443
|
-
(byPriority[s.priority] || byPriority.medium).push(s);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
for (const [priority, items] of Object.entries(byPriority)) {
|
|
447
|
-
if (items.length === 0) continue;
|
|
448
|
-
const label = priority === "high" ? "High Priority" : priority === "medium" ? "Medium Priority" : "Low Priority";
|
|
449
|
-
response += `### ${label}\n`;
|
|
450
|
-
for (const s of items) {
|
|
451
|
-
const auto = s.automatable ? "" : " (needs human assistance)";
|
|
452
|
-
response += `- **${s.name}**: ${s.description}${auto}\n`;
|
|
453
|
-
}
|
|
454
|
-
response += "\n";
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
response += `Call \`skill_generate\` again with \`job_name: "${args.job_name}"\` and \`auto_create: true\` to create all these skills, `;
|
|
458
|
-
response += "or use `skill_create` to create individual skills manually.";
|
|
459
|
-
|
|
460
|
-
return { content: [{ type: "text", text: response }] };
|
|
461
|
-
}
|
|
462
423
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const failed: string[] = [];
|
|
466
|
-
|
|
467
|
-
// Generate skills in parallel (batch of 3 to avoid rate limits)
|
|
468
|
-
for (let i = 0; i < specs.length; i += 3) {
|
|
469
|
-
const batch = specs.slice(i, i + 3);
|
|
470
|
-
const results = await Promise.allSettled(
|
|
471
|
-
batch.map((spec) =>
|
|
472
|
-
generateSkillFromDescription(spec.name, spec.description, args.job_description)
|
|
473
|
-
)
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
for (let j = 0; j < results.length; j++) {
|
|
477
|
-
const spec = batch[j];
|
|
478
|
-
const result = results[j];
|
|
479
|
-
|
|
480
|
-
if (result.status === "fulfilled" && result.value) {
|
|
481
|
-
const generated = result.value;
|
|
482
|
-
// Check for duplicates
|
|
483
|
-
const existing = skillManager.findSimilar(generated.name);
|
|
484
|
-
if (existing) {
|
|
485
|
-
failed.push(`${spec.name} (duplicate of "${existing.name}")`);
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Create in skills table (draft)
|
|
490
|
-
const createResult = await skillManager.create(
|
|
491
|
-
generated.name,
|
|
492
|
-
generated.description || spec.description,
|
|
493
|
-
generated.steps,
|
|
494
|
-
{
|
|
495
|
-
source: "job_generated",
|
|
496
|
-
emoji: generated.emoji,
|
|
497
|
-
keywords: generated.keywords,
|
|
498
|
-
}
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
if (createResult) {
|
|
502
|
-
// Add to user's collection (approval)
|
|
503
|
-
const added = await skillManager.addSkill(createResult.id);
|
|
504
|
-
if (added) {
|
|
505
|
-
created.push(generated.name);
|
|
506
|
-
} else {
|
|
507
|
-
failed.push(`${spec.name} (add failed)`);
|
|
508
|
-
}
|
|
509
|
-
} else {
|
|
510
|
-
failed.push(spec.name);
|
|
511
|
-
}
|
|
512
|
-
} else {
|
|
513
|
-
failed.push(spec.name);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
424
|
+
let response = `## Job: ${args.job_name}\n`;
|
|
425
|
+
response += `**Description:** ${args.job_description}\n\n`;
|
|
517
426
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
saveJobToDb(
|
|
521
|
-
userId,
|
|
522
|
-
args.job_name,
|
|
523
|
-
args.job_description,
|
|
524
|
-
created
|
|
525
|
-
).catch(() => {});
|
|
427
|
+
if (existingNames.length > 0) {
|
|
428
|
+
response += `**Existing skills (do NOT duplicate):** ${existingNames.join(", ")}\n\n`;
|
|
526
429
|
}
|
|
527
430
|
|
|
528
|
-
|
|
529
|
-
response += `
|
|
431
|
+
response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills. `;
|
|
432
|
+
response += `For each skill, call \`skill_create\` with:\n`;
|
|
433
|
+
response += `- name: kebab-case name (e.g. "slack-message-check")\n`;
|
|
434
|
+
response += `- description: one-line description\n`;
|
|
435
|
+
response += `- instructions: detailed step-by-step markdown instructions the agent can follow\n`;
|
|
436
|
+
response += `- emoji: a single emoji representing the skill\n\n`;
|
|
437
|
+
response += `After creating each skill, call \`skill_add\` with the returned skill ID to add it to the user's collection.\n\n`;
|
|
438
|
+
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.\n\n`;
|
|
439
|
+
response += `**Guidelines for skill instructions:**\n`;
|
|
440
|
+
response += `- Write clear, actionable markdown steps\n`;
|
|
441
|
+
response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks\n`;
|
|
442
|
+
response += `- Include error handling steps\n`;
|
|
443
|
+
response += `- Use placeholders like {query}, {date} for variable inputs\n`;
|
|
444
|
+
response += `- Each skill should be a single, well-defined workflow (10-25 steps)\n`;
|
|
530
445
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
446
|
+
return { content: [{ type: "text", text: response }] };
|
|
447
|
+
}
|
|
448
|
+
),
|
|
449
|
+
tool(
|
|
450
|
+
"skill_link_job",
|
|
451
|
+
"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.",
|
|
452
|
+
{
|
|
453
|
+
job_name: z.string().describe("Name of the job to link skills to"),
|
|
454
|
+
job_description: z.string().describe("Job description (used if job doesn't exist yet)"),
|
|
455
|
+
skill_names: z.array(z.string()).describe("Names of skills to link to this job"),
|
|
456
|
+
},
|
|
457
|
+
async (args) => {
|
|
458
|
+
if (!userId) {
|
|
459
|
+
return {
|
|
460
|
+
content: [{ type: "text", text: "Not authenticated. Cannot link job." }],
|
|
461
|
+
};
|
|
535
462
|
}
|
|
536
463
|
|
|
537
|
-
|
|
538
|
-
|
|
464
|
+
try {
|
|
465
|
+
await saveJobToDb(userId, args.job_name, args.job_description, args.skill_names);
|
|
466
|
+
log.success(`Job "${args.job_name}": linked ${args.skill_names.length} skills and marked as analyzed`);
|
|
467
|
+
return {
|
|
468
|
+
content: [{
|
|
469
|
+
type: "text",
|
|
470
|
+
text: `Job "${args.job_name}" linked with ${args.skill_names.length} skills and marked as analyzed.`,
|
|
471
|
+
}],
|
|
472
|
+
};
|
|
473
|
+
} catch (err) {
|
|
474
|
+
return {
|
|
475
|
+
content: [{
|
|
476
|
+
type: "text",
|
|
477
|
+
text: `Failed to link job: ${err instanceof Error ? err.message : err}`,
|
|
478
|
+
}],
|
|
479
|
+
};
|
|
539
480
|
}
|
|
540
|
-
|
|
541
|
-
response += "\nThese skills are now available. When you give me a task that matches, I'll automatically use the right skill.";
|
|
542
|
-
response += "\nUse `skill_invoke` to test any skill, or `skill_improve` to refine them after use.";
|
|
543
|
-
|
|
544
|
-
log.success(`Job "${args.job_name}": created ${created.length} skills`);
|
|
545
|
-
|
|
546
|
-
return { content: [{ type: "text", text: response }] };
|
|
547
481
|
}
|
|
548
482
|
),
|
|
549
483
|
tool(
|
|
@@ -933,7 +867,13 @@ async function saveJobToDb(
|
|
|
933
867
|
}
|
|
934
868
|
}
|
|
935
869
|
|
|
936
|
-
|
|
870
|
+
// Mark job as analyzed
|
|
871
|
+
await sb
|
|
872
|
+
.from("agent_jobs")
|
|
873
|
+
.update({ skills_analyzed: true })
|
|
874
|
+
.eq("id", job.id);
|
|
875
|
+
|
|
876
|
+
log.debug(`Job "${jobName}" saved to DB with ${createdSkillNames.length} skill link(s), marked as analyzed`);
|
|
937
877
|
} catch (err) {
|
|
938
878
|
log.debug(`saveJobToDb error: ${err}`);
|
|
939
879
|
}
|
package/src/agent/processor.ts
CHANGED
|
@@ -20,7 +20,6 @@ import { log, newCorrelationId, setCorrelationId } from "../utils/logger.js";
|
|
|
20
20
|
import { getBrowser } from "../tools/browser.js";
|
|
21
21
|
import { MemoryManager } from "./memory.js";
|
|
22
22
|
import { SkillManager } from "./skills.js";
|
|
23
|
-
import { extractMemoriesWithLLM } from "./memory-extractor.js";
|
|
24
23
|
import { type ToolCallRecord } from "./skill-extractor.js";
|
|
25
24
|
import { withRetry } from "../utils/retry.js";
|
|
26
25
|
import {
|
|
@@ -56,6 +55,8 @@ Available capabilities:
|
|
|
56
55
|
- You can remember things about the user using memory_store
|
|
57
56
|
- Use this when you learn preferences, important facts, or standing instructions
|
|
58
57
|
- Your stored memories persist across conversations
|
|
58
|
+
- PROACTIVELY use memory_store during tasks when you discover user preferences, habits, or important context
|
|
59
|
+
- Before completing a task, consider if anything learned should be remembered for future conversations
|
|
59
60
|
|
|
60
61
|
4. SKILL PLANNING (pre-task):
|
|
61
62
|
- Before executing a complex task, analyze if it matches an existing skill (use skill_invoke)
|
|
@@ -232,6 +233,7 @@ export class TaskProcessor {
|
|
|
232
233
|
"mcp__assistme-agent__skill_invoke",
|
|
233
234
|
"mcp__assistme-agent__skill_search",
|
|
234
235
|
"mcp__assistme-agent__skill_generate",
|
|
236
|
+
"mcp__assistme-agent__skill_link_job",
|
|
235
237
|
"mcp__assistme-agent__skill_browse",
|
|
236
238
|
"mcp__assistme-agent__skill_add",
|
|
237
239
|
"mcp__assistme-agent__skill_publish",
|
|
@@ -367,35 +369,9 @@ export class TaskProcessor {
|
|
|
367
369
|
}
|
|
368
370
|
this.historyCache.set(task.conversation_id, convHistory);
|
|
369
371
|
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
//
|
|
373
|
-
if (this.memoryManager && finalResponse) {
|
|
374
|
-
const mm = this.memoryManager;
|
|
375
|
-
const taskIdRef = task.id;
|
|
376
|
-
extractMemoriesWithLLM(task.prompt, finalResponse)
|
|
377
|
-
.then(async (memories) => {
|
|
378
|
-
for (const mem of memories) {
|
|
379
|
-
try {
|
|
380
|
-
await mm.remember(mem.content, mem.category, {
|
|
381
|
-
importance: mem.importance,
|
|
382
|
-
tags: mem.tags,
|
|
383
|
-
sourceMessageId: taskIdRef,
|
|
384
|
-
});
|
|
385
|
-
log.info(`Memory extracted: [${mem.category}] ${mem.content.slice(0, 60)}...`);
|
|
386
|
-
} catch {
|
|
387
|
-
// Non-critical — skip individual memory failures
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if (memories.length > 0) {
|
|
391
|
-
log.success(`${memories.length} memory(s) auto-extracted`);
|
|
392
|
-
}
|
|
393
|
-
})
|
|
394
|
-
.catch(() => {});
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Note: Skill creation/improvement is now handled pre-task via the
|
|
398
|
-
// skill_create → user approval → skill_add flow, not post-task extraction.
|
|
372
|
+
// Note: Memory extraction and skill creation are handled by the agent itself
|
|
373
|
+
// during task execution via memory_store and skill_create tools.
|
|
374
|
+
// No separate LLM API calls needed — the agent SDK handles everything.
|
|
399
375
|
} catch (err) {
|
|
400
376
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
401
377
|
log.error(`Task failed: ${errorMsg}`);
|
|
@@ -228,12 +228,19 @@ export async function decomposeJob(
|
|
|
228
228
|
}),
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
-
if (!response.ok)
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
const errText = await response.text().catch(() => "");
|
|
233
|
+
log.debug(`decomposeJob API error: ${response.status} ${errText.slice(0, 300)}`);
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
232
236
|
|
|
233
237
|
const data = await response.json();
|
|
234
238
|
const text =
|
|
235
239
|
data.content?.[0]?.type === "text" ? data.content[0].text : "";
|
|
236
|
-
if (!text)
|
|
240
|
+
if (!text) {
|
|
241
|
+
log.debug(`decomposeJob: empty response from API`);
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
237
244
|
|
|
238
245
|
const jsonStr = text
|
|
239
246
|
.replace(/```json\n?/g, "")
|
|
@@ -241,7 +248,10 @@ export async function decomposeJob(
|
|
|
241
248
|
.trim();
|
|
242
249
|
const specs: SkillSpec[] = JSON.parse(jsonStr);
|
|
243
250
|
|
|
244
|
-
if (!Array.isArray(specs))
|
|
251
|
+
if (!Array.isArray(specs)) {
|
|
252
|
+
log.debug(`decomposeJob: response is not an array: ${jsonStr.slice(0, 200)}`);
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
245
255
|
|
|
246
256
|
// Sanitize names
|
|
247
257
|
return specs
|
|
@@ -255,7 +265,7 @@ export async function decomposeJob(
|
|
|
255
265
|
.replace(/^-|-$/g, ""),
|
|
256
266
|
}));
|
|
257
267
|
} catch (err) {
|
|
258
|
-
log.debug(`Job decomposition failed: ${err}`);
|
|
268
|
+
log.debug(`Job decomposition failed: ${err instanceof Error ? err.message : err}`);
|
|
259
269
|
return [];
|
|
260
270
|
}
|
|
261
271
|
}
|
|
@@ -321,12 +331,19 @@ export async function generateSkillFromDescription(
|
|
|
321
331
|
}),
|
|
322
332
|
});
|
|
323
333
|
|
|
324
|
-
if (!response.ok)
|
|
334
|
+
if (!response.ok) {
|
|
335
|
+
const errText = await response.text().catch(() => "");
|
|
336
|
+
log.debug(`generateSkill API error: ${response.status} ${errText.slice(0, 300)}`);
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
325
339
|
|
|
326
340
|
const data = await response.json();
|
|
327
341
|
const text =
|
|
328
342
|
data.content?.[0]?.type === "text" ? data.content[0].text : "";
|
|
329
|
-
if (!text)
|
|
343
|
+
if (!text) {
|
|
344
|
+
log.debug(`generateSkill: empty response from API`);
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
330
347
|
|
|
331
348
|
const jsonStr = text
|
|
332
349
|
.replace(/```json\n?/g, "")
|
|
@@ -334,7 +351,10 @@ export async function generateSkillFromDescription(
|
|
|
334
351
|
.trim();
|
|
335
352
|
const skill = JSON.parse(jsonStr);
|
|
336
353
|
|
|
337
|
-
if (!skill || !skill.name || !skill.steps)
|
|
354
|
+
if (!skill || !skill.name || !skill.steps) {
|
|
355
|
+
log.debug(`generateSkill: invalid response structure: ${jsonStr.slice(0, 200)}`);
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
338
358
|
|
|
339
359
|
skill.name = skill.name
|
|
340
360
|
.toLowerCase()
|
|
@@ -344,7 +364,7 @@ export async function generateSkillFromDescription(
|
|
|
344
364
|
|
|
345
365
|
return skill;
|
|
346
366
|
} catch (err) {
|
|
347
|
-
log.debug(`Skill generation failed: ${err}`);
|
|
367
|
+
log.debug(`Skill generation failed: ${err instanceof Error ? err.message : err}`);
|
|
348
368
|
return null;
|
|
349
369
|
}
|
|
350
370
|
}
|
package/src/agent/skills.ts
CHANGED
|
@@ -371,33 +371,31 @@ export class SkillManager {
|
|
|
371
371
|
? { openclaw: { emoji: options.emoji } }
|
|
372
372
|
: {};
|
|
373
373
|
|
|
374
|
-
const { data, error } = await sb
|
|
375
|
-
.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
metadata,
|
|
386
|
-
source,
|
|
387
|
-
is_public: false,
|
|
388
|
-
},
|
|
389
|
-
{ onConflict: "author_id,name" }
|
|
390
|
-
)
|
|
391
|
-
.select("id, name")
|
|
392
|
-
.single();
|
|
374
|
+
const { data, error } = await sb.rpc("create_skill", {
|
|
375
|
+
p_user_id: this.userId,
|
|
376
|
+
p_name: name,
|
|
377
|
+
p_description: description,
|
|
378
|
+
p_content: content,
|
|
379
|
+
p_version: "1.0.0",
|
|
380
|
+
p_source: source,
|
|
381
|
+
p_emoji: options?.emoji || null,
|
|
382
|
+
p_keywords: options?.keywords || [],
|
|
383
|
+
p_metadata: metadata,
|
|
384
|
+
});
|
|
393
385
|
|
|
394
386
|
if (error) {
|
|
395
387
|
log.debug(`Skill create failed for "${name}": ${error.message}`);
|
|
396
388
|
return null;
|
|
397
389
|
}
|
|
398
390
|
|
|
391
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
392
|
+
if (!row) {
|
|
393
|
+
log.debug(`Skill create returned no data for "${name}"`);
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
|
|
399
397
|
log.info(`Skill "${name}" created in skills table (pending approval)`);
|
|
400
|
-
return
|
|
398
|
+
return row as { id: string; name: string };
|
|
401
399
|
} catch (err) {
|
|
402
400
|
log.debug(`Skill create error: ${err}`);
|
|
403
401
|
return null;
|