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 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(query2, limit = 10) {
1683
+ async search(query3, limit = 10) {
1684
1684
  try {
1685
1685
  return await callMcpHandler("memory.search", {
1686
- query: query2,
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 lower = prompt.toLowerCase();
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
- return scored.sort((a, b) => b.score - a.score).slice(0, maxResults).map((s) => s.skill);
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
- buildSkillDescriptions() {
1997
- const skills = this.getAll().filter((s) => !s.disableModelInvocation).sort((a, b) => {
1998
- if (a.metadata.always && !b.metadata.always) return -1;
1999
- if (!a.metadata.always && b.metadata.always) return 1;
2000
- return a.description.length - b.description.length;
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
- if (skills.length === 0) return "";
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\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 skill_invoke to explore)_
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.buildIdfCache();
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(query2, limit = 10) {
2259
+ async searchDb(query3, limit = 10) {
2206
2260
  if (this.userId) {
2207
2261
  try {
2208
- const data = await callMcpHandler("skill.search", { query: query2, limit });
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(query2, limit);
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 Confirmation Tool ─────────────────────────────────
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 PLANNING (pre-task):
3691
- - Before executing a complex task, analyze if it matches an existing skill (use skill_invoke)
3692
- - If no matching skill exists, consider whether this task represents a reusable workflow
3693
- - To create a new skill: use skill_create to save a draft, then ASK the user if they want to add it
3694
- - If the user approves, use skill_add to add it to their collection, then proceed with the task
3695
- - If a skill's instructions could be improved based on your experience, use skill_improve
3696
- - Use skill_search to find relevant skills when the task doesn't obviously match the listed skills
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 confirmation
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: false,
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 query({
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 (query2) => {
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(query2);
4682
+ const results = await mm.search(query3);
4331
4683
  if (results.length === 0) {
4332
- console.log(chalk7.yellow(`No memories matching "${query2}"`));
4684
+ console.log(chalk7.yellow(`No memories matching "${query3}"`));
4333
4685
  return;
4334
4686
  }
4335
4687
  console.log(chalk7.bold(`
4336
- Search results for "${query2}":`));
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 (query2) => {
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(query2);
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 "${query2}"`));
4763
+ console.log(chalk8.yellow(`No skills found for "${query3}"`));
4412
4764
  return;
4413
4765
  }
4414
4766
  console.log(chalk8.bold(`
4415
- Skills matching "${query2}":`));
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}]`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistme",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "description": "AssistMe CLI Agent - AI-powered assistant that controls your real browser",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",