assistme 0.2.9 → 0.3.0
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 +391 -39
- package/package.json +1 -1
- package/src/agent/mcp-servers.ts +86 -3
- package/src/agent/processor.ts +59 -14
- 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 {
|
|
@@ -3333,7 +3585,67 @@ ${content}`;
|
|
|
3333
3585
|
};
|
|
3334
3586
|
}
|
|
3335
3587
|
),
|
|
3336
|
-
// ── User
|
|
3588
|
+
// ── User Interaction Tools ──────────────────────────────────
|
|
3589
|
+
tool(
|
|
3590
|
+
"request_user_input",
|
|
3591
|
+
"Ask the user a clarifying question and wait for their free-text response. Use this when you need information that cannot be inferred from context, memory, or the workspace \u2014 e.g. which account to use, specific preferences, ambiguous instructions, or missing parameters for a skill. Do NOT use this for information you can discover yourself (git remote, file contents, etc.).",
|
|
3592
|
+
{
|
|
3593
|
+
question: z.string().describe("The question to ask the user (supports markdown). Be specific about what you need and why."),
|
|
3594
|
+
placeholder: z.string().optional().describe("Placeholder text for the input field (e.g. 'https://github.com/owner/repo')"),
|
|
3595
|
+
timeout_seconds: z.number().optional().describe("How long to wait for response (default: 300)")
|
|
3596
|
+
},
|
|
3597
|
+
async (args) => {
|
|
3598
|
+
const actionId = `input_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
3599
|
+
const timeout = (args.timeout_seconds || 300) * 1e3;
|
|
3600
|
+
const actionData = {
|
|
3601
|
+
id: actionId,
|
|
3602
|
+
type: "input",
|
|
3603
|
+
message: args.question,
|
|
3604
|
+
placeholder: args.placeholder || "",
|
|
3605
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3606
|
+
};
|
|
3607
|
+
try {
|
|
3608
|
+
await setActionRequest(taskId, actionData);
|
|
3609
|
+
log.info(`Input request ${actionId}: "${args.question.slice(0, 80)}..."`);
|
|
3610
|
+
emitEvent(taskId, "user_action_request", actionData).catch(() => {
|
|
3611
|
+
});
|
|
3612
|
+
const startTime = Date.now();
|
|
3613
|
+
const pollInterval = 2e3;
|
|
3614
|
+
while (Date.now() - startTime < timeout) {
|
|
3615
|
+
const response = await pollActionResponse(taskId);
|
|
3616
|
+
if (response && (!response.action_id || response.action_id === actionId)) {
|
|
3617
|
+
const text = response.text || response.value || "";
|
|
3618
|
+
log.info(`User input received: "${text.slice(0, 80)}"`);
|
|
3619
|
+
return {
|
|
3620
|
+
content: [{
|
|
3621
|
+
type: "text",
|
|
3622
|
+
text: JSON.stringify({ status: "responded", text })
|
|
3623
|
+
}]
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3626
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
3627
|
+
}
|
|
3628
|
+
log.warn(`Input request ${actionId} timed out`);
|
|
3629
|
+
return {
|
|
3630
|
+
content: [{
|
|
3631
|
+
type: "text",
|
|
3632
|
+
text: JSON.stringify({
|
|
3633
|
+
status: "timeout",
|
|
3634
|
+
message: "User did not respond within the timeout period."
|
|
3635
|
+
})
|
|
3636
|
+
}]
|
|
3637
|
+
};
|
|
3638
|
+
} catch (err) {
|
|
3639
|
+
log.error(`request_user_input failed: ${err}`);
|
|
3640
|
+
return {
|
|
3641
|
+
content: [{
|
|
3642
|
+
type: "text",
|
|
3643
|
+
text: `Failed to request user input: ${err instanceof Error ? err.message : err}`
|
|
3644
|
+
}]
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
),
|
|
3337
3649
|
tool(
|
|
3338
3650
|
"request_user_confirmation",
|
|
3339
3651
|
"Pause and ask the user for approval or input via the web UI. Returns the user's chosen action_key. Use this BEFORE creating skills, making irreversible changes, etc. The agent will block until the user responds or the timeout expires.",
|
|
@@ -3365,7 +3677,7 @@ ${content}`;
|
|
|
3365
3677
|
const pollInterval = 2e3;
|
|
3366
3678
|
while (Date.now() - startTime < timeout) {
|
|
3367
3679
|
const response = await pollActionResponse(taskId);
|
|
3368
|
-
if (response) {
|
|
3680
|
+
if (response && (!response.action_id || response.action_id === actionId)) {
|
|
3369
3681
|
const actionKey = response.action_key || response.action || "";
|
|
3370
3682
|
const label = response.label || actionKey;
|
|
3371
3683
|
log.info(`User responded: ${label} (${actionKey})`);
|
|
@@ -3687,13 +3999,25 @@ Available capabilities:
|
|
|
3687
3999
|
- PROACTIVELY use memory_store during tasks when you discover user preferences, habits, or important context
|
|
3688
4000
|
- Before completing a task, consider if anything learned should be remembered for future conversations
|
|
3689
4001
|
|
|
3690
|
-
4. SKILL
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
4002
|
+
4. SKILL-AWARE EXECUTION (CRITICAL \u2014 follow this for EVERY task):
|
|
4003
|
+
Step A \u2014 Search: Before executing ANY task, check if an existing skill matches (use skill_invoke or skill_search).
|
|
4004
|
+
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.
|
|
4005
|
+
Step C \u2014 If NO skill found: BEFORE executing, draft a skill plan following the Agent Skills format:
|
|
4006
|
+
Skill Draft: [kebab-case-name]
|
|
4007
|
+
Description: [what this skill does and when to use it]
|
|
4008
|
+
Steps:
|
|
4009
|
+
1. [first step]
|
|
4010
|
+
2. [second step]
|
|
4011
|
+
...
|
|
4012
|
+
The draft should be a reusable workflow, not specific to this one request. Use generic placeholders where the user provided specific values.
|
|
4013
|
+
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.
|
|
4014
|
+
Step E \u2014 After execution: The system will automatically evaluate whether to save the skill. You do NOT need to call skill_create manually.
|
|
4015
|
+
|
|
4016
|
+
Agent Skills format reference (agentskills.io):
|
|
4017
|
+
- name: 1-64 chars, lowercase kebab-case (a-z, 0-9, hyphens), no leading/trailing/consecutive hyphens
|
|
4018
|
+
- description: 1-1024 chars, describe what the skill does AND when to use it, include keywords for discoverability
|
|
4019
|
+
- body: markdown step-by-step instructions, examples, edge cases. Keep under 500 lines.
|
|
4020
|
+
- Progressive disclosure: metadata (~100 tokens) \u2192 instructions (<5000 tokens) \u2192 references (on demand)
|
|
3697
4021
|
|
|
3698
4022
|
5. JOB AUTOMATION:
|
|
3699
4023
|
- When the user describes their job/role/daily work, use skill_generate to decompose it into automatable skills
|
|
@@ -3730,6 +4054,14 @@ Guidelines:
|
|
|
3730
4054
|
- Summarize results clearly at the end
|
|
3731
4055
|
- When you learn something about the user (preferences, habits), use memory_store to remember it
|
|
3732
4056
|
|
|
4057
|
+
CRITICAL \u2014 Ask before you guess:
|
|
4058
|
+
- Before executing a task, verify you have all required information. If anything is ambiguous or missing, use request_user_input to ask.
|
|
4059
|
+
- First try to resolve unknowns yourself: check memories, read workspace files (e.g. git remote, config files), or infer from conversation history.
|
|
4060
|
+
- If you still lack a critical piece of information after self-resolution, ASK the user via request_user_input. Do NOT guess, assume defaults, or proceed with incomplete information.
|
|
4061
|
+
- 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.
|
|
4062
|
+
- Keep questions specific and actionable. Explain what you already know and what exactly you need.
|
|
4063
|
+
- After receiving the answer, store it with memory_store if it is likely to be useful in future conversations.
|
|
4064
|
+
|
|
3733
4065
|
Workspace path: {workspace_path}`;
|
|
3734
4066
|
var MAX_HISTORY_ENTRIES = 10;
|
|
3735
4067
|
var MAX_RESPONSE_LENGTH = 1500;
|
|
@@ -3754,6 +4086,18 @@ var TaskProcessor = class {
|
|
|
3754
4086
|
setSessionId(sessionId) {
|
|
3755
4087
|
this.sessionId = sessionId;
|
|
3756
4088
|
}
|
|
4089
|
+
/**
|
|
4090
|
+
* Post-task: resume the same Agent SDK session to evaluate whether
|
|
4091
|
+
* to create/update a skill. The agent already has full context from
|
|
4092
|
+
* the task it just completed — no need to re-describe anything.
|
|
4093
|
+
*/
|
|
4094
|
+
async evaluateSkillPostTask(agentSessionId, model) {
|
|
4095
|
+
await evaluateAndMaybeCreateSkill({
|
|
4096
|
+
sessionId: agentSessionId,
|
|
4097
|
+
skillManager: this.skillManager,
|
|
4098
|
+
model
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
3757
4101
|
async processTask(task) {
|
|
3758
4102
|
const config = getConfig();
|
|
3759
4103
|
resetEventSequence();
|
|
@@ -3763,6 +4107,7 @@ var TaskProcessor = class {
|
|
|
3763
4107
|
let finalResponse = "";
|
|
3764
4108
|
const toolCallRecords = [];
|
|
3765
4109
|
let tokenUsage;
|
|
4110
|
+
let agentSessionId;
|
|
3766
4111
|
try {
|
|
3767
4112
|
await emitEvent(task.id, "status_change", { status: "running" });
|
|
3768
4113
|
let systemPrompt = BASE_SYSTEM_PROMPT.replace("{workspace_path}", config.workspacePath);
|
|
@@ -3776,7 +4121,7 @@ var TaskProcessor = class {
|
|
|
3776
4121
|
log.debug(`Memory load failed: ${err}`);
|
|
3777
4122
|
}
|
|
3778
4123
|
}
|
|
3779
|
-
const skillPrompt = this.skillManager.buildSkillDescriptions();
|
|
4124
|
+
const skillPrompt = this.skillManager.buildSkillDescriptions(task.prompt);
|
|
3780
4125
|
if (skillPrompt) {
|
|
3781
4126
|
systemPrompt += skillPrompt;
|
|
3782
4127
|
}
|
|
@@ -3832,7 +4177,8 @@ var TaskProcessor = class {
|
|
|
3832
4177
|
"mcp__assistme-agent__skill_browse",
|
|
3833
4178
|
"mcp__assistme-agent__skill_add",
|
|
3834
4179
|
"mcp__assistme-agent__skill_publish",
|
|
3835
|
-
// User
|
|
4180
|
+
// User interaction
|
|
4181
|
+
"mcp__assistme-agent__request_user_input",
|
|
3836
4182
|
"mcp__assistme-agent__request_user_confirmation",
|
|
3837
4183
|
// Job automation tools
|
|
3838
4184
|
"mcp__assistme-agent__job_run",
|
|
@@ -3864,7 +4210,7 @@ var TaskProcessor = class {
|
|
|
3864
4210
|
"assistme-agent": agentToolsServer
|
|
3865
4211
|
},
|
|
3866
4212
|
hooks: eventHooks,
|
|
3867
|
-
persistSession:
|
|
4213
|
+
persistSession: true,
|
|
3868
4214
|
abortController
|
|
3869
4215
|
};
|
|
3870
4216
|
const taskStartTime = Date.now();
|
|
@@ -3872,7 +4218,7 @@ var TaskProcessor = class {
|
|
|
3872
4218
|
abortController.abort();
|
|
3873
4219
|
}, taskTimeoutMs);
|
|
3874
4220
|
try {
|
|
3875
|
-
for await (const message of
|
|
4221
|
+
for await (const message of query2({
|
|
3876
4222
|
prompt: promptMessages(),
|
|
3877
4223
|
options
|
|
3878
4224
|
})) {
|
|
@@ -3924,6 +4270,9 @@ var TaskProcessor = class {
|
|
|
3924
4270
|
break;
|
|
3925
4271
|
}
|
|
3926
4272
|
default:
|
|
4273
|
+
if (message.type === "system" && "subtype" in message && message.subtype === "init") {
|
|
4274
|
+
agentSessionId = message.session_id;
|
|
4275
|
+
}
|
|
3927
4276
|
log.debug(`SDK message type: ${message.type}`);
|
|
3928
4277
|
break;
|
|
3929
4278
|
}
|
|
@@ -3944,6 +4293,9 @@ var TaskProcessor = class {
|
|
|
3944
4293
|
convHistory.splice(0, convHistory.length - MAX_HISTORY_ENTRIES * 2);
|
|
3945
4294
|
}
|
|
3946
4295
|
this.historyCache.set(task.conversation_id, convHistory);
|
|
4296
|
+
if (agentSessionId) {
|
|
4297
|
+
this.evaluateSkillPostTask(agentSessionId, config.model).catch((err) => log.debug(`Post-task skill evaluation skipped: ${err}`));
|
|
4298
|
+
}
|
|
3947
4299
|
} catch (err) {
|
|
3948
4300
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3949
4301
|
log.error(`Task failed: ${errorMsg}`);
|
|
@@ -4323,17 +4675,17 @@ Memories (${memories.length}):`));
|
|
|
4323
4675
|
process.exit(1);
|
|
4324
4676
|
}
|
|
4325
4677
|
});
|
|
4326
|
-
memoryCmd.command("search <query>").description("Search memories").action(async (
|
|
4678
|
+
memoryCmd.command("search <query>").description("Search memories").action(async (query3) => {
|
|
4327
4679
|
try {
|
|
4328
4680
|
const userId = await getCurrentUserId();
|
|
4329
4681
|
const mm = new MemoryManager(userId);
|
|
4330
|
-
const results = await mm.search(
|
|
4682
|
+
const results = await mm.search(query3);
|
|
4331
4683
|
if (results.length === 0) {
|
|
4332
|
-
console.log(chalk7.yellow(`No memories matching "${
|
|
4684
|
+
console.log(chalk7.yellow(`No memories matching "${query3}"`));
|
|
4333
4685
|
return;
|
|
4334
4686
|
}
|
|
4335
4687
|
console.log(chalk7.bold(`
|
|
4336
|
-
Search results for "${
|
|
4688
|
+
Search results for "${query3}":`));
|
|
4337
4689
|
for (const m of results) {
|
|
4338
4690
|
console.log(` [${m.category}] ${m.content}`);
|
|
4339
4691
|
}
|
|
@@ -4401,18 +4753,18 @@ function registerSkillCommands(program2) {
|
|
|
4401
4753
|
);
|
|
4402
4754
|
console.log();
|
|
4403
4755
|
});
|
|
4404
|
-
skillCmd.command("search <query>").description("Search skills in your collection and marketplace").action(async (
|
|
4756
|
+
skillCmd.command("search <query>").description("Search skills in your collection and marketplace").action(async (query3) => {
|
|
4405
4757
|
const spinner = ora4("Searching skills...").start();
|
|
4406
4758
|
const sm = await getAuthenticatedSkillManager();
|
|
4407
4759
|
try {
|
|
4408
|
-
const results = await sm.searchDb(
|
|
4760
|
+
const results = await sm.searchDb(query3);
|
|
4409
4761
|
spinner.stop();
|
|
4410
4762
|
if (results.length === 0) {
|
|
4411
|
-
console.log(chalk8.yellow(`No skills found for "${
|
|
4763
|
+
console.log(chalk8.yellow(`No skills found for "${query3}"`));
|
|
4412
4764
|
return;
|
|
4413
4765
|
}
|
|
4414
4766
|
console.log(chalk8.bold(`
|
|
4415
|
-
Skills matching "${
|
|
4767
|
+
Skills matching "${query3}":`));
|
|
4416
4768
|
for (const r of results) {
|
|
4417
4769
|
const emoji = r.emoji ? `${r.emoji} ` : "";
|
|
4418
4770
|
console.log(` ${emoji}${chalk8.cyan(r.name)} [${r.source}]`);
|