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 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 {
@@ -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 request_user_confirmation before creating skills:**
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 \`request_user_confirmation\` with the formatted skill list as "message" and these options:
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 request_user_confirmation first.
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 Confirmation Tool ─────────────────────────────────
3588
+ // ── User Interaction Tool ───────────────────────────────────
3337
3589
  tool(
3338
- "request_user_confirmation",
3339
- "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.",
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
- message: z.string().describe("What to show the user (supports markdown)"),
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("Buttons/options to show the user"),
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 = `action_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
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: "confirmation",
3355
- message: args.message,
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(`Action request ${actionId} stored in metadata, waiting for user response...`);
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 || response.action || "";
3370
- const label = response.label || actionKey;
3371
- log.info(`User responded: ${label} (${actionKey})`);
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(`Action request ${actionId} timed out after ${args.timeout_seconds || 300}s`);
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(`request_user_confirmation failed: ${err}`);
3652
+ log.error(`ask_user failed: ${err}`);
3397
3653
  return {
3398
3654
  content: [{
3399
3655
  type: "text",
3400
- text: `Failed to request user confirmation: ${err instanceof Error ? err.message : err}`
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 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
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 request_user_confirmation to get user approval before creating skills \u2014 never create skills without approval
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 confirmation
3836
- "mcp__assistme-agent__request_user_confirmation",
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: false,
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 query({
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 (query2) => {
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(query2);
4626
+ const results = await mm.search(query3);
4331
4627
  if (results.length === 0) {
4332
- console.log(chalk7.yellow(`No memories matching "${query2}"`));
4628
+ console.log(chalk7.yellow(`No memories matching "${query3}"`));
4333
4629
  return;
4334
4630
  }
4335
4631
  console.log(chalk7.bold(`
4336
- Search results for "${query2}":`));
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 (query2) => {
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(query2);
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 "${query2}"`));
4707
+ console.log(chalk8.yellow(`No skills found for "${query3}"`));
4412
4708
  return;
4413
4709
  }
4414
4710
  console.log(chalk8.bold(`
4415
- Skills matching "${query2}":`));
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}]`);