assistme 0.2.9 → 0.3.1
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 +357 -61
- package/package.json +1 -1
- package/src/agent/mcp-servers.ts +42 -27
- package/src/agent/processor.ts +61 -16
- package/src/agent/skill-evaluator.ts +258 -0
- package/src/agent/skills.ts +110 -14
package/dist/index.js
CHANGED
|
@@ -1651,7 +1651,7 @@ var SessionManager = class {
|
|
|
1651
1651
|
|
|
1652
1652
|
// src/agent/processor.ts
|
|
1653
1653
|
import {
|
|
1654
|
-
query
|
|
1654
|
+
query as query2
|
|
1655
1655
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
1656
1656
|
|
|
1657
1657
|
// src/agent/memory.ts
|
|
@@ -1680,10 +1680,10 @@ var MemoryManager = class {
|
|
|
1680
1680
|
/**
|
|
1681
1681
|
* Search memories by query text. Uses ILIKE + tag containment.
|
|
1682
1682
|
*/
|
|
1683
|
-
async search(
|
|
1683
|
+
async search(query3, limit = 10) {
|
|
1684
1684
|
try {
|
|
1685
1685
|
return await callMcpHandler("memory.search", {
|
|
1686
|
-
query:
|
|
1686
|
+
query: query3,
|
|
1687
1687
|
limit
|
|
1688
1688
|
});
|
|
1689
1689
|
} catch (err) {
|
|
@@ -1902,6 +1902,8 @@ var SkillManager = class {
|
|
|
1902
1902
|
skills = /* @__PURE__ */ new Map();
|
|
1903
1903
|
idfCache = /* @__PURE__ */ new Map();
|
|
1904
1904
|
userId = null;
|
|
1905
|
+
/** Cache for findRelevant() — keyed by prompt, invalidated on skill changes */
|
|
1906
|
+
relevanceCache = /* @__PURE__ */ new Map();
|
|
1905
1907
|
DESCRIPTION_BUDGET_CHARS = 16e3;
|
|
1906
1908
|
setUserId(userId) {
|
|
1907
1909
|
this.userId = userId;
|
|
@@ -1939,9 +1941,15 @@ var SkillManager = class {
|
|
|
1939
1941
|
filePath: "",
|
|
1940
1942
|
source: row.source || "manual",
|
|
1941
1943
|
dbId: row.id,
|
|
1942
|
-
sourceSkillId: row.source_skill_id || void 0
|
|
1944
|
+
sourceSkillId: row.source_skill_id || void 0,
|
|
1945
|
+
invocationCount: row.invocation_count || 0
|
|
1943
1946
|
};
|
|
1944
1947
|
}
|
|
1948
|
+
/** Invalidate caches when skills change (create, add, update, remove). */
|
|
1949
|
+
invalidateCaches() {
|
|
1950
|
+
this.relevanceCache.clear();
|
|
1951
|
+
this.buildIdfCache();
|
|
1952
|
+
}
|
|
1945
1953
|
buildIdfCache() {
|
|
1946
1954
|
this.idfCache.clear();
|
|
1947
1955
|
const docFreq = /* @__PURE__ */ new Map();
|
|
@@ -1964,7 +1972,12 @@ var SkillManager = class {
|
|
|
1964
1972
|
return this.skills.get(name);
|
|
1965
1973
|
}
|
|
1966
1974
|
findRelevant(prompt, maxResults = 3) {
|
|
1967
|
-
const
|
|
1975
|
+
const cacheKey = prompt.toLowerCase();
|
|
1976
|
+
const cached = this.relevanceCache.get(cacheKey);
|
|
1977
|
+
if (cached && cached.maxResults >= maxResults) {
|
|
1978
|
+
return cached.results.slice(0, maxResults);
|
|
1979
|
+
}
|
|
1980
|
+
const lower = cacheKey;
|
|
1968
1981
|
const promptTokens = tokenize(lower);
|
|
1969
1982
|
const promptTokenSet = new Set(promptTokens);
|
|
1970
1983
|
const idf = (word) => this.idfCache.get(word) || 1;
|
|
@@ -1991,18 +2004,39 @@ var SkillManager = class {
|
|
|
1991
2004
|
}
|
|
1992
2005
|
if (score > 0) scored.push({ skill, score });
|
|
1993
2006
|
}
|
|
1994
|
-
|
|
2007
|
+
const results = scored.sort((a, b) => b.score - a.score).slice(0, maxResults).map((s) => s.skill);
|
|
2008
|
+
this.relevanceCache.set(cacheKey, { results, maxResults });
|
|
2009
|
+
return results;
|
|
1995
2010
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2011
|
+
/**
|
|
2012
|
+
* Build lightweight skill descriptions for the system prompt.
|
|
2013
|
+
* When a taskPrompt is provided, relevant skills are prioritized to the top;
|
|
2014
|
+
* remaining skills are sorted by usage frequency (invocationCount).
|
|
2015
|
+
*/
|
|
2016
|
+
buildSkillDescriptions(taskPrompt) {
|
|
2017
|
+
const all = this.getAll().filter((s) => !s.disableModelInvocation);
|
|
2018
|
+
if (all.length === 0) return "";
|
|
2019
|
+
const alwaysSkills = all.filter((s) => s.metadata.always);
|
|
2020
|
+
const rest = all.filter((s) => !s.metadata.always);
|
|
2021
|
+
let relevantNames = null;
|
|
2022
|
+
if (taskPrompt) {
|
|
2023
|
+
const relevant = this.findRelevant(taskPrompt, 10);
|
|
2024
|
+
relevantNames = new Set(relevant.map((s) => s.name));
|
|
2025
|
+
}
|
|
2026
|
+
const sorted = rest.sort((a, b) => {
|
|
2027
|
+
if (relevantNames) {
|
|
2028
|
+
const aRelevant = relevantNames.has(a.name);
|
|
2029
|
+
const bRelevant = relevantNames.has(b.name);
|
|
2030
|
+
if (aRelevant && !bRelevant) return -1;
|
|
2031
|
+
if (!aRelevant && bRelevant) return 1;
|
|
2032
|
+
}
|
|
2033
|
+
return (b.invocationCount || 0) - (a.invocationCount || 0);
|
|
2001
2034
|
});
|
|
2002
|
-
|
|
2035
|
+
const skills = [...alwaysSkills, ...sorted];
|
|
2003
2036
|
let budget = this.DESCRIPTION_BUDGET_CHARS;
|
|
2004
2037
|
let prompt = "\n\n## Your Skills\n";
|
|
2005
|
-
prompt += "These are your approved skills. Use skill_invoke to load full instructions when a task matches.\n
|
|
2038
|
+
prompt += "These are your approved skills. Use skill_invoke to load full instructions when a task matches.\n";
|
|
2039
|
+
prompt += "If no skill matches but the task is a reusable pattern, consider creating one with skill_create.\n\n";
|
|
2006
2040
|
let included = 0;
|
|
2007
2041
|
for (const skill of skills) {
|
|
2008
2042
|
const emoji = skill.metadata.emoji || "";
|
|
@@ -2016,7 +2050,7 @@ var SkillManager = class {
|
|
|
2016
2050
|
}
|
|
2017
2051
|
if (included < skills.length) {
|
|
2018
2052
|
prompt += `
|
|
2019
|
-
_(${skills.length - included} additional skills available \u2014 use
|
|
2053
|
+
_(${skills.length - included} additional skills available \u2014 use skill_search to find more)_
|
|
2020
2054
|
`;
|
|
2021
2055
|
}
|
|
2022
2056
|
return prompt;
|
|
@@ -2062,6 +2096,24 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2062
2096
|
}
|
|
2063
2097
|
const id = row.out_id || row.id;
|
|
2064
2098
|
const skillName = row.out_name || row.name;
|
|
2099
|
+
this.skills.set(skillName, {
|
|
2100
|
+
name: skillName,
|
|
2101
|
+
description,
|
|
2102
|
+
version: "1.0.0",
|
|
2103
|
+
userInvocable: true,
|
|
2104
|
+
disableModelInvocation: false,
|
|
2105
|
+
keywords: options?.keywords || [],
|
|
2106
|
+
allowedTools: [],
|
|
2107
|
+
argumentHint: "",
|
|
2108
|
+
metadata: options?.emoji ? { emoji: options.emoji } : {},
|
|
2109
|
+
homepage: "",
|
|
2110
|
+
content,
|
|
2111
|
+
filePath: "",
|
|
2112
|
+
source: options?.source || "manual",
|
|
2113
|
+
dbId: id,
|
|
2114
|
+
invocationCount: 0
|
|
2115
|
+
});
|
|
2116
|
+
this.invalidateCaches();
|
|
2065
2117
|
log.info(`Skill "${skillName}" created in skills table (pending approval)`);
|
|
2066
2118
|
return { id, name: skillName };
|
|
2067
2119
|
} catch (err) {
|
|
@@ -2086,7 +2138,7 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2086
2138
|
source_skill_id: skillId
|
|
2087
2139
|
});
|
|
2088
2140
|
this.skills.set(skill.name, skill);
|
|
2089
|
-
this.
|
|
2141
|
+
this.invalidateCaches();
|
|
2090
2142
|
log.info(`Skill "${row.name}" added to user's collection`);
|
|
2091
2143
|
return skill;
|
|
2092
2144
|
} catch (err) {
|
|
@@ -2098,6 +2150,7 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2098
2150
|
const skill = this.skills.get(name);
|
|
2099
2151
|
if (!skill) return false;
|
|
2100
2152
|
this.skills.delete(name);
|
|
2153
|
+
this.invalidateCaches();
|
|
2101
2154
|
this.removeFromDb(name).catch(() => {
|
|
2102
2155
|
});
|
|
2103
2156
|
return true;
|
|
@@ -2141,6 +2194,7 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2141
2194
|
skill.content = newContent;
|
|
2142
2195
|
skill.description = newDescription;
|
|
2143
2196
|
skill.version = newVersion;
|
|
2197
|
+
this.invalidateCaches();
|
|
2144
2198
|
this.syncToAgentSkills(name, newDescription, newContent, newVersion, {
|
|
2145
2199
|
source: "auto_improved"
|
|
2146
2200
|
}).catch(() => {
|
|
@@ -2202,10 +2256,10 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2202
2256
|
log.debug(`Invocation log error: ${err}`);
|
|
2203
2257
|
}
|
|
2204
2258
|
}
|
|
2205
|
-
async searchDb(
|
|
2259
|
+
async searchDb(query3, limit = 10) {
|
|
2206
2260
|
if (this.userId) {
|
|
2207
2261
|
try {
|
|
2208
|
-
const data = await callMcpHandler("skill.search", { query:
|
|
2262
|
+
const data = await callMcpHandler("skill.search", { query: query3, limit });
|
|
2209
2263
|
if (data) {
|
|
2210
2264
|
return data.map((row) => ({
|
|
2211
2265
|
name: row.name,
|
|
@@ -2218,7 +2272,7 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2218
2272
|
} catch {
|
|
2219
2273
|
}
|
|
2220
2274
|
}
|
|
2221
|
-
const results = this.findRelevant(
|
|
2275
|
+
const results = this.findRelevant(query3, limit);
|
|
2222
2276
|
return results.map((s) => ({
|
|
2223
2277
|
name: s.name,
|
|
2224
2278
|
description: s.description,
|
|
@@ -2292,6 +2346,17 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
2292
2346
|
}
|
|
2293
2347
|
}
|
|
2294
2348
|
};
|
|
2349
|
+
function validateSkillName(name) {
|
|
2350
|
+
if (!name || name.length === 0) return "name is empty";
|
|
2351
|
+
if (name.length > 64) return `name too long (${name.length}/64 chars)`;
|
|
2352
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
2353
|
+
return `name must be lowercase kebab-case (a-z, 0-9, hyphens), no leading/trailing/consecutive hyphens. Got: "${name}"`;
|
|
2354
|
+
}
|
|
2355
|
+
return null;
|
|
2356
|
+
}
|
|
2357
|
+
function normalizeSkillName(name) {
|
|
2358
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, 64);
|
|
2359
|
+
}
|
|
2295
2360
|
function substituteArguments(content, args) {
|
|
2296
2361
|
const parts = args.split(/\s+/);
|
|
2297
2362
|
content = content.replace(/\$ARGUMENTS/g, args);
|
|
@@ -2309,6 +2374,184 @@ function preprocessDynamicContext(content, cwd) {
|
|
|
2309
2374
|
});
|
|
2310
2375
|
}
|
|
2311
2376
|
|
|
2377
|
+
// src/agent/skill-evaluator.ts
|
|
2378
|
+
import {
|
|
2379
|
+
query
|
|
2380
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
2381
|
+
var SKILL_EVALUATION_PROMPT = `You just completed a task. Now evaluate whether it should be saved as a reusable Agent Skill.
|
|
2382
|
+
|
|
2383
|
+
## Agent Skills Format (agentskills.io)
|
|
2384
|
+
|
|
2385
|
+
A skill follows the SKILL.md format:
|
|
2386
|
+
- name: 1-64 chars, lowercase kebab-case (a-z, 0-9, hyphens), no leading/trailing/consecutive hyphens
|
|
2387
|
+
- description: 1-1024 chars, describe WHAT it does AND WHEN to use it, include searchable keywords
|
|
2388
|
+
- body: markdown step-by-step instructions, examples, edge cases. Keep under 500 lines, <5000 tokens.
|
|
2389
|
+
- Use generic placeholders (e.g. {url}, {query}, {product_name}) instead of specific values
|
|
2390
|
+
- Instructions should be a REUSABLE workflow, not a transcript of what just happened
|
|
2391
|
+
- Include error handling steps and tool references (browser_navigate, browser_read_page, Bash, Read, etc.)
|
|
2392
|
+
|
|
2393
|
+
## Your Decision
|
|
2394
|
+
|
|
2395
|
+
Respond with ONLY a JSON object (no markdown, no explanation outside the JSON). Choose one action:
|
|
2396
|
+
|
|
2397
|
+
1. **"create"** \u2014 The task is a reusable workflow worth saving.
|
|
2398
|
+
Include: name, description, instructions (full SKILL.md body), emoji, keywords (3-5, include Chinese if task was in Chinese)
|
|
2399
|
+
|
|
2400
|
+
2. **"update"** \u2014 An existing skill should be improved based on what you just learned.
|
|
2401
|
+
Include: existing_skill_name, improved_instructions (full updated body), improved_description (if changed)
|
|
2402
|
+
|
|
2403
|
+
3. **"skip"** \u2014 Not worth capturing (simple Q&A, one-off, too vague, already fully covered by existing skill).
|
|
2404
|
+
|
|
2405
|
+
Always include "reason" explaining your decision.
|
|
2406
|
+
|
|
2407
|
+
Use your judgment \u2014 no rigid rules. Consider: Is this repeatable? Can it be generalized? Would it save time next time?`;
|
|
2408
|
+
async function evaluateAndMaybeCreateSkill(opts) {
|
|
2409
|
+
const { sessionId, skillManager, model } = opts;
|
|
2410
|
+
if (!sessionId) {
|
|
2411
|
+
log.debug("Skill evaluation skipped: no session ID to resume");
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
const existingSkills = skillManager.getAll();
|
|
2415
|
+
const existingList = existingSkills.length > 0 ? existingSkills.map((s) => `- ${s.name}: ${s.description}`).join("\n") : "(no existing skills)";
|
|
2416
|
+
const prompt = `${SKILL_EVALUATION_PROMPT}
|
|
2417
|
+
|
|
2418
|
+
## Existing Skills (do NOT duplicate these)
|
|
2419
|
+
${existingList}
|
|
2420
|
+
|
|
2421
|
+
Respond with a JSON object now.`;
|
|
2422
|
+
try {
|
|
2423
|
+
let responseText = "";
|
|
2424
|
+
for await (const message of query({
|
|
2425
|
+
prompt,
|
|
2426
|
+
options: {
|
|
2427
|
+
resume: sessionId,
|
|
2428
|
+
model,
|
|
2429
|
+
maxTurns: 1,
|
|
2430
|
+
allowedTools: []
|
|
2431
|
+
}
|
|
2432
|
+
})) {
|
|
2433
|
+
if (message.type === "assistant") {
|
|
2434
|
+
const assistantMsg = message;
|
|
2435
|
+
for (const block of assistantMsg.message.content) {
|
|
2436
|
+
if (block.type === "text") {
|
|
2437
|
+
responseText += block.text;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
} else if (message.type === "result") {
|
|
2441
|
+
const resultMsg = message;
|
|
2442
|
+
if (resultMsg.subtype === "success" && "total_cost_usd" in resultMsg) {
|
|
2443
|
+
log.debug(`Skill evaluation cost: $${resultMsg.total_cost_usd.toFixed(4)}`);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
const decision = parseJsonResponse(responseText);
|
|
2448
|
+
if (!decision) {
|
|
2449
|
+
log.debug("Skill evaluation: no valid JSON in response");
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (!["create", "update", "skip"].includes(decision.action)) {
|
|
2453
|
+
log.debug("Skill evaluation: invalid action");
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
await executeSkillDecision(decision, skillManager);
|
|
2457
|
+
} catch (err) {
|
|
2458
|
+
log.debug(`Skill evaluation error: ${err}`);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
async function executeSkillDecision(decision, skillManager) {
|
|
2462
|
+
switch (decision.action) {
|
|
2463
|
+
case "create": {
|
|
2464
|
+
if (!decision.name || !decision.instructions) {
|
|
2465
|
+
log.debug("Skill create skipped: missing name or instructions");
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
let skillName = decision.name;
|
|
2469
|
+
if (validateSkillName(skillName)) {
|
|
2470
|
+
skillName = normalizeSkillName(skillName);
|
|
2471
|
+
if (!skillName || validateSkillName(skillName)) {
|
|
2472
|
+
log.debug(`Skill create skipped: name "${decision.name}" cannot be normalized`);
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
log.debug(`Normalized skill name: "${decision.name}" \u2192 "${skillName}"`);
|
|
2476
|
+
}
|
|
2477
|
+
const existing = skillManager.findSimilar(skillName);
|
|
2478
|
+
if (existing) {
|
|
2479
|
+
log.debug(`Skill create skipped: similar skill "${existing.name}" exists`);
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
const result = await skillManager.create(
|
|
2483
|
+
skillName,
|
|
2484
|
+
decision.description || "",
|
|
2485
|
+
decision.instructions,
|
|
2486
|
+
{
|
|
2487
|
+
source: "auto_extracted",
|
|
2488
|
+
emoji: decision.emoji,
|
|
2489
|
+
keywords: decision.keywords
|
|
2490
|
+
}
|
|
2491
|
+
);
|
|
2492
|
+
if (result) {
|
|
2493
|
+
await skillManager.syncToAgentSkills(
|
|
2494
|
+
skillName,
|
|
2495
|
+
decision.description || "",
|
|
2496
|
+
decision.instructions,
|
|
2497
|
+
"1.0.0",
|
|
2498
|
+
{
|
|
2499
|
+
source: "auto_extracted",
|
|
2500
|
+
emoji: decision.emoji,
|
|
2501
|
+
keywords: decision.keywords,
|
|
2502
|
+
sourceSkillId: result.id
|
|
2503
|
+
}
|
|
2504
|
+
);
|
|
2505
|
+
log.info(`Auto-created skill "${skillName}": ${decision.reason}`);
|
|
2506
|
+
}
|
|
2507
|
+
break;
|
|
2508
|
+
}
|
|
2509
|
+
case "update": {
|
|
2510
|
+
if (!decision.existing_skill_name || !decision.improved_instructions) {
|
|
2511
|
+
log.debug("Skill update skipped: missing skill name or instructions");
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
const updated = skillManager.update(
|
|
2515
|
+
decision.existing_skill_name,
|
|
2516
|
+
decision.improved_instructions,
|
|
2517
|
+
decision.improved_description
|
|
2518
|
+
);
|
|
2519
|
+
if (updated) {
|
|
2520
|
+
log.info(`Auto-improved skill "${decision.existing_skill_name}": ${decision.reason}`);
|
|
2521
|
+
} else {
|
|
2522
|
+
log.debug(`Skill update failed: "${decision.existing_skill_name}" not found`);
|
|
2523
|
+
}
|
|
2524
|
+
break;
|
|
2525
|
+
}
|
|
2526
|
+
case "skip":
|
|
2527
|
+
log.debug(`Skill evaluation: skip \u2014 ${decision.reason}`);
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
function parseJsonResponse(text) {
|
|
2532
|
+
const trimmed = text.trim();
|
|
2533
|
+
try {
|
|
2534
|
+
const parsed = JSON.parse(trimmed);
|
|
2535
|
+
if (parsed.action) return parsed;
|
|
2536
|
+
} catch {
|
|
2537
|
+
}
|
|
2538
|
+
const start = trimmed.indexOf("{");
|
|
2539
|
+
if (start === -1) return null;
|
|
2540
|
+
let depth = 0;
|
|
2541
|
+
for (let i = start; i < trimmed.length; i++) {
|
|
2542
|
+
if (trimmed[i] === "{") depth++;
|
|
2543
|
+
else if (trimmed[i] === "}") depth--;
|
|
2544
|
+
if (depth === 0) {
|
|
2545
|
+
try {
|
|
2546
|
+
return JSON.parse(trimmed.slice(start, i + 1));
|
|
2547
|
+
} catch {
|
|
2548
|
+
return null;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
return null;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2312
2555
|
// src/utils/retry.ts
|
|
2313
2556
|
async function withRetry(fn, opts = {}) {
|
|
2314
2557
|
const {
|
|
@@ -2961,6 +3204,15 @@ function createAgentToolsServer(deps) {
|
|
|
2961
3204
|
emoji: z.string().optional().describe("Single emoji representing this skill")
|
|
2962
3205
|
},
|
|
2963
3206
|
async (args) => {
|
|
3207
|
+
const nameError = validateSkillName(args.name);
|
|
3208
|
+
if (nameError) {
|
|
3209
|
+
return {
|
|
3210
|
+
content: [{
|
|
3211
|
+
type: "text",
|
|
3212
|
+
text: `Invalid skill name: ${nameError}. Use lowercase kebab-case like "flight-booking".`
|
|
3213
|
+
}]
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
2964
3216
|
const existing = skillManager.findSimilar(args.name);
|
|
2965
3217
|
if (existing) {
|
|
2966
3218
|
return {
|
|
@@ -3156,17 +3408,17 @@ ${content}`;
|
|
|
3156
3408
|
response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills.
|
|
3157
3409
|
|
|
3158
3410
|
`;
|
|
3159
|
-
response += `**IMPORTANT \u2014 You MUST use
|
|
3411
|
+
response += `**IMPORTANT \u2014 You MUST use ask_user before creating skills:**
|
|
3160
3412
|
`;
|
|
3161
3413
|
response += `1. Analyze the job and draft a list of proposed skills (name, emoji, one-line description for each).
|
|
3162
3414
|
`;
|
|
3163
|
-
response += `2. Call \`
|
|
3415
|
+
response += `2. Call \`ask_user\` with the formatted skill list as "question" and these options:
|
|
3164
3416
|
`;
|
|
3165
3417
|
response += ` - options: [{label: "Approve All", action_key: "approve_all", description: "Create all proposed skills"}, {label: "Cancel", action_key: "cancel", description: "Do not create any skills"}]
|
|
3166
3418
|
`;
|
|
3167
3419
|
response += `3. WAIT for the response. If action_key is "approve_all", create all skills using \`skill_create\`. If "cancel", stop.
|
|
3168
3420
|
`;
|
|
3169
|
-
response += `4. Do NOT ask for confirmation in text. Do NOT create skills without calling
|
|
3421
|
+
response += `4. Do NOT ask for confirmation in text. Do NOT create skills without calling ask_user first.
|
|
3170
3422
|
|
|
3171
3423
|
`;
|
|
3172
3424
|
response += `For each skill, call \`skill_create\` with:
|
|
@@ -3333,56 +3585,60 @@ ${content}`;
|
|
|
3333
3585
|
};
|
|
3334
3586
|
}
|
|
3335
3587
|
),
|
|
3336
|
-
// ── User
|
|
3588
|
+
// ── User Interaction Tool ───────────────────────────────────
|
|
3337
3589
|
tool(
|
|
3338
|
-
"
|
|
3339
|
-
"
|
|
3590
|
+
"ask_user",
|
|
3591
|
+
"Ask the user a question via the web UI and wait for their response. Shows a message with optional predefined option buttons PLUS a free-text input field \u2014 the user can either click a suggested option or type a custom answer. ALWAYS provide options when you can suggest likely answers. Do NOT use this for information you can discover yourself (git remote, file contents, etc.).",
|
|
3340
3592
|
{
|
|
3341
|
-
|
|
3593
|
+
question: z.string().describe("The question to ask (supports markdown). Be specific about what you need and why."),
|
|
3342
3594
|
options: z.array(z.object({
|
|
3343
3595
|
label: z.string().describe("Button label shown to user"),
|
|
3344
3596
|
action_key: z.string().describe("Machine-readable key returned when selected"),
|
|
3345
3597
|
description: z.string().optional().describe("Tooltip/description for this option")
|
|
3346
|
-
})).describe("
|
|
3598
|
+
})).optional().describe("Suggested options shown as buttons. The user can always type a custom answer instead."),
|
|
3599
|
+
placeholder: z.string().optional().describe("Placeholder text for the free-text input field"),
|
|
3347
3600
|
timeout_seconds: z.number().optional().describe("How long to wait for response (default: 300)")
|
|
3348
3601
|
},
|
|
3349
3602
|
async (args) => {
|
|
3350
|
-
const actionId = `
|
|
3603
|
+
const actionId = `ask_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
3351
3604
|
const timeout = (args.timeout_seconds || 300) * 1e3;
|
|
3352
3605
|
const actionData = {
|
|
3353
3606
|
id: actionId,
|
|
3354
|
-
type: "
|
|
3355
|
-
message: args.
|
|
3356
|
-
options: args.options,
|
|
3607
|
+
type: "ask_user",
|
|
3608
|
+
message: args.question,
|
|
3609
|
+
options: args.options || [],
|
|
3610
|
+
placeholder: args.placeholder || "",
|
|
3357
3611
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3358
3612
|
};
|
|
3359
3613
|
try {
|
|
3360
3614
|
await setActionRequest(taskId, actionData);
|
|
3361
|
-
log.info(`
|
|
3615
|
+
log.info(`Ask user ${actionId}: "${args.question.slice(0, 80)}..."`);
|
|
3362
3616
|
emitEvent(taskId, "user_action_request", actionData).catch(() => {
|
|
3363
3617
|
});
|
|
3364
3618
|
const startTime = Date.now();
|
|
3365
3619
|
const pollInterval = 2e3;
|
|
3366
3620
|
while (Date.now() - startTime < timeout) {
|
|
3367
3621
|
const response = await pollActionResponse(taskId);
|
|
3368
|
-
if (response) {
|
|
3369
|
-
const actionKey = response.action_key ||
|
|
3370
|
-
const
|
|
3371
|
-
|
|
3622
|
+
if (response && (!response.action_id || response.action_id === actionId)) {
|
|
3623
|
+
const actionKey = response.action_key || "";
|
|
3624
|
+
const text = response.text || "";
|
|
3625
|
+
const label = response.label || actionKey || text;
|
|
3626
|
+
log.info(`User responded: "${label}"`);
|
|
3372
3627
|
return {
|
|
3373
3628
|
content: [{
|
|
3374
3629
|
type: "text",
|
|
3375
3630
|
text: JSON.stringify({
|
|
3376
3631
|
status: "responded",
|
|
3377
|
-
action_key: actionKey,
|
|
3378
|
-
label
|
|
3632
|
+
action_key: actionKey || "custom_input",
|
|
3633
|
+
label,
|
|
3634
|
+
text: text || label
|
|
3379
3635
|
})
|
|
3380
3636
|
}]
|
|
3381
3637
|
};
|
|
3382
3638
|
}
|
|
3383
3639
|
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
3384
3640
|
}
|
|
3385
|
-
log.warn(`
|
|
3641
|
+
log.warn(`Ask user ${actionId} timed out after ${args.timeout_seconds || 300}s`);
|
|
3386
3642
|
return {
|
|
3387
3643
|
content: [{
|
|
3388
3644
|
type: "text",
|
|
@@ -3393,11 +3649,11 @@ ${content}`;
|
|
|
3393
3649
|
}]
|
|
3394
3650
|
};
|
|
3395
3651
|
} catch (err) {
|
|
3396
|
-
log.error(`
|
|
3652
|
+
log.error(`ask_user failed: ${err}`);
|
|
3397
3653
|
return {
|
|
3398
3654
|
content: [{
|
|
3399
3655
|
type: "text",
|
|
3400
|
-
text: `Failed to
|
|
3656
|
+
text: `Failed to ask user: ${err instanceof Error ? err.message : err}`
|
|
3401
3657
|
}]
|
|
3402
3658
|
};
|
|
3403
3659
|
}
|
|
@@ -3687,17 +3943,29 @@ Available capabilities:
|
|
|
3687
3943
|
- PROACTIVELY use memory_store during tasks when you discover user preferences, habits, or important context
|
|
3688
3944
|
- Before completing a task, consider if anything learned should be remembered for future conversations
|
|
3689
3945
|
|
|
3690
|
-
4. SKILL
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3946
|
+
4. SKILL-AWARE EXECUTION (CRITICAL \u2014 follow this for EVERY task):
|
|
3947
|
+
Step A \u2014 Search: Before executing ANY task, check if an existing skill matches (use skill_invoke or skill_search).
|
|
3948
|
+
Step B \u2014 If skill found: load it with skill_invoke and follow its instructions precisely. If the instructions are incomplete or wrong, adapt and improve as you go \u2014 note what changed.
|
|
3949
|
+
Step C \u2014 If NO skill found: BEFORE executing, draft a skill plan following the Agent Skills format:
|
|
3950
|
+
Skill Draft: [kebab-case-name]
|
|
3951
|
+
Description: [what this skill does and when to use it]
|
|
3952
|
+
Steps:
|
|
3953
|
+
1. [first step]
|
|
3954
|
+
2. [second step]
|
|
3955
|
+
...
|
|
3956
|
+
The draft should be a reusable workflow, not specific to this one request. Use generic placeholders where the user provided specific values.
|
|
3957
|
+
Step D \u2014 Execute: Follow the skill draft (or loaded skill) step by step. Refine the draft as you discover better approaches, edge cases, or missing steps.
|
|
3958
|
+
Step E \u2014 After execution: The system will automatically evaluate whether to save the skill. You do NOT need to call skill_create manually.
|
|
3959
|
+
|
|
3960
|
+
Agent Skills format reference (agentskills.io):
|
|
3961
|
+
- name: 1-64 chars, lowercase kebab-case (a-z, 0-9, hyphens), no leading/trailing/consecutive hyphens
|
|
3962
|
+
- description: 1-1024 chars, describe what the skill does AND when to use it, include keywords for discoverability
|
|
3963
|
+
- body: markdown step-by-step instructions, examples, edge cases. Keep under 500 lines.
|
|
3964
|
+
- Progressive disclosure: metadata (~100 tokens) \u2192 instructions (<5000 tokens) \u2192 references (on demand)
|
|
3697
3965
|
|
|
3698
3966
|
5. JOB AUTOMATION:
|
|
3699
3967
|
- When the user describes their job/role/daily work, use skill_generate to decompose it into automatable skills
|
|
3700
|
-
- ALWAYS use
|
|
3968
|
+
- ALWAYS use ask_user to get user approval before creating skills \u2014 never create skills without approval
|
|
3701
3969
|
- Use job_run to start a job \u2014 it gives you the job's goal and available skills as capabilities
|
|
3702
3970
|
- When running a job, be AGENTIC: decide dynamically what to do based on what you discover
|
|
3703
3971
|
- Do NOT follow a fixed sequence \u2014 if checking Slack reveals a task that needs GitHub, go do GitHub immediately
|
|
@@ -3730,6 +3998,15 @@ Guidelines:
|
|
|
3730
3998
|
- Summarize results clearly at the end
|
|
3731
3999
|
- When you learn something about the user (preferences, habits), use memory_store to remember it
|
|
3732
4000
|
|
|
4001
|
+
CRITICAL \u2014 Ask before you guess:
|
|
4002
|
+
- Before executing a task, verify you have all required information. If anything is ambiguous or missing, use ask_user to ask.
|
|
4003
|
+
- First try to resolve unknowns yourself: check memories, read workspace files (e.g. git remote, config files), or infer from conversation history.
|
|
4004
|
+
- If you still lack a critical piece of information after self-resolution, ASK the user via ask_user. Do NOT guess, assume defaults, or proceed with incomplete information.
|
|
4005
|
+
- When asking, provide suggested options as buttons whenever possible \u2014 the user can always type a custom answer instead.
|
|
4006
|
+
- Examples of when to ask: which account/repo/project to target, what format the user wants, which of multiple options to choose, credentials or URLs that cannot be inferred.
|
|
4007
|
+
- Keep questions specific and actionable. Explain what you already know and what exactly you need.
|
|
4008
|
+
- After receiving the answer, store it with memory_store if it is likely to be useful in future conversations.
|
|
4009
|
+
|
|
3733
4010
|
Workspace path: {workspace_path}`;
|
|
3734
4011
|
var MAX_HISTORY_ENTRIES = 10;
|
|
3735
4012
|
var MAX_RESPONSE_LENGTH = 1500;
|
|
@@ -3754,6 +4031,18 @@ var TaskProcessor = class {
|
|
|
3754
4031
|
setSessionId(sessionId) {
|
|
3755
4032
|
this.sessionId = sessionId;
|
|
3756
4033
|
}
|
|
4034
|
+
/**
|
|
4035
|
+
* Post-task: resume the same Agent SDK session to evaluate whether
|
|
4036
|
+
* to create/update a skill. The agent already has full context from
|
|
4037
|
+
* the task it just completed — no need to re-describe anything.
|
|
4038
|
+
*/
|
|
4039
|
+
async evaluateSkillPostTask(agentSessionId, model) {
|
|
4040
|
+
await evaluateAndMaybeCreateSkill({
|
|
4041
|
+
sessionId: agentSessionId,
|
|
4042
|
+
skillManager: this.skillManager,
|
|
4043
|
+
model
|
|
4044
|
+
});
|
|
4045
|
+
}
|
|
3757
4046
|
async processTask(task) {
|
|
3758
4047
|
const config = getConfig();
|
|
3759
4048
|
resetEventSequence();
|
|
@@ -3763,6 +4052,7 @@ var TaskProcessor = class {
|
|
|
3763
4052
|
let finalResponse = "";
|
|
3764
4053
|
const toolCallRecords = [];
|
|
3765
4054
|
let tokenUsage;
|
|
4055
|
+
let agentSessionId;
|
|
3766
4056
|
try {
|
|
3767
4057
|
await emitEvent(task.id, "status_change", { status: "running" });
|
|
3768
4058
|
let systemPrompt = BASE_SYSTEM_PROMPT.replace("{workspace_path}", config.workspacePath);
|
|
@@ -3776,7 +4066,7 @@ var TaskProcessor = class {
|
|
|
3776
4066
|
log.debug(`Memory load failed: ${err}`);
|
|
3777
4067
|
}
|
|
3778
4068
|
}
|
|
3779
|
-
const skillPrompt = this.skillManager.buildSkillDescriptions();
|
|
4069
|
+
const skillPrompt = this.skillManager.buildSkillDescriptions(task.prompt);
|
|
3780
4070
|
if (skillPrompt) {
|
|
3781
4071
|
systemPrompt += skillPrompt;
|
|
3782
4072
|
}
|
|
@@ -3832,8 +4122,8 @@ var TaskProcessor = class {
|
|
|
3832
4122
|
"mcp__assistme-agent__skill_browse",
|
|
3833
4123
|
"mcp__assistme-agent__skill_add",
|
|
3834
4124
|
"mcp__assistme-agent__skill_publish",
|
|
3835
|
-
// User
|
|
3836
|
-
"mcp__assistme-
|
|
4125
|
+
// User interaction
|
|
4126
|
+
"mcp__assistme-agent__ask_user",
|
|
3837
4127
|
// Job automation tools
|
|
3838
4128
|
"mcp__assistme-agent__job_run",
|
|
3839
4129
|
"mcp__assistme-agent__job_schedule",
|
|
@@ -3864,7 +4154,7 @@ var TaskProcessor = class {
|
|
|
3864
4154
|
"assistme-agent": agentToolsServer
|
|
3865
4155
|
},
|
|
3866
4156
|
hooks: eventHooks,
|
|
3867
|
-
persistSession:
|
|
4157
|
+
persistSession: true,
|
|
3868
4158
|
abortController
|
|
3869
4159
|
};
|
|
3870
4160
|
const taskStartTime = Date.now();
|
|
@@ -3872,7 +4162,7 @@ var TaskProcessor = class {
|
|
|
3872
4162
|
abortController.abort();
|
|
3873
4163
|
}, taskTimeoutMs);
|
|
3874
4164
|
try {
|
|
3875
|
-
for await (const message of
|
|
4165
|
+
for await (const message of query2({
|
|
3876
4166
|
prompt: promptMessages(),
|
|
3877
4167
|
options
|
|
3878
4168
|
})) {
|
|
@@ -3924,6 +4214,9 @@ var TaskProcessor = class {
|
|
|
3924
4214
|
break;
|
|
3925
4215
|
}
|
|
3926
4216
|
default:
|
|
4217
|
+
if (message.type === "system" && "subtype" in message && message.subtype === "init") {
|
|
4218
|
+
agentSessionId = message.session_id;
|
|
4219
|
+
}
|
|
3927
4220
|
log.debug(`SDK message type: ${message.type}`);
|
|
3928
4221
|
break;
|
|
3929
4222
|
}
|
|
@@ -3944,6 +4237,9 @@ var TaskProcessor = class {
|
|
|
3944
4237
|
convHistory.splice(0, convHistory.length - MAX_HISTORY_ENTRIES * 2);
|
|
3945
4238
|
}
|
|
3946
4239
|
this.historyCache.set(task.conversation_id, convHistory);
|
|
4240
|
+
if (agentSessionId) {
|
|
4241
|
+
this.evaluateSkillPostTask(agentSessionId, config.model).catch((err) => log.debug(`Post-task skill evaluation skipped: ${err}`));
|
|
4242
|
+
}
|
|
3947
4243
|
} catch (err) {
|
|
3948
4244
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3949
4245
|
log.error(`Task failed: ${errorMsg}`);
|
|
@@ -4323,17 +4619,17 @@ Memories (${memories.length}):`));
|
|
|
4323
4619
|
process.exit(1);
|
|
4324
4620
|
}
|
|
4325
4621
|
});
|
|
4326
|
-
memoryCmd.command("search <query>").description("Search memories").action(async (
|
|
4622
|
+
memoryCmd.command("search <query>").description("Search memories").action(async (query3) => {
|
|
4327
4623
|
try {
|
|
4328
4624
|
const userId = await getCurrentUserId();
|
|
4329
4625
|
const mm = new MemoryManager(userId);
|
|
4330
|
-
const results = await mm.search(
|
|
4626
|
+
const results = await mm.search(query3);
|
|
4331
4627
|
if (results.length === 0) {
|
|
4332
|
-
console.log(chalk7.yellow(`No memories matching "${
|
|
4628
|
+
console.log(chalk7.yellow(`No memories matching "${query3}"`));
|
|
4333
4629
|
return;
|
|
4334
4630
|
}
|
|
4335
4631
|
console.log(chalk7.bold(`
|
|
4336
|
-
Search results for "${
|
|
4632
|
+
Search results for "${query3}":`));
|
|
4337
4633
|
for (const m of results) {
|
|
4338
4634
|
console.log(` [${m.category}] ${m.content}`);
|
|
4339
4635
|
}
|
|
@@ -4401,18 +4697,18 @@ function registerSkillCommands(program2) {
|
|
|
4401
4697
|
);
|
|
4402
4698
|
console.log();
|
|
4403
4699
|
});
|
|
4404
|
-
skillCmd.command("search <query>").description("Search skills in your collection and marketplace").action(async (
|
|
4700
|
+
skillCmd.command("search <query>").description("Search skills in your collection and marketplace").action(async (query3) => {
|
|
4405
4701
|
const spinner = ora4("Searching skills...").start();
|
|
4406
4702
|
const sm = await getAuthenticatedSkillManager();
|
|
4407
4703
|
try {
|
|
4408
|
-
const results = await sm.searchDb(
|
|
4704
|
+
const results = await sm.searchDb(query3);
|
|
4409
4705
|
spinner.stop();
|
|
4410
4706
|
if (results.length === 0) {
|
|
4411
|
-
console.log(chalk8.yellow(`No skills found for "${
|
|
4707
|
+
console.log(chalk8.yellow(`No skills found for "${query3}"`));
|
|
4412
4708
|
return;
|
|
4413
4709
|
}
|
|
4414
4710
|
console.log(chalk8.bold(`
|
|
4415
|
-
Skills matching "${
|
|
4711
|
+
Skills matching "${query3}":`));
|
|
4416
4712
|
for (const r of results) {
|
|
4417
4713
|
const emoji = r.emoji ? `${r.emoji} ` : "";
|
|
4418
4714
|
console.log(` ${emoji}${chalk8.cyan(r.name)} [${r.source}]`);
|