assistme 0.2.2 → 0.2.4

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.
@@ -10,7 +10,6 @@ import { log } from "../utils/logger.js";
10
10
  import type { MemoryManager, MemoryCategory } from "./memory.js";
11
11
  import type { SkillManager } from "./skills.js";
12
12
  import { substituteArguments, preprocessDynamicContext } from "./skills.js";
13
- import { decomposeJob, generateSkillFromDescription } from "./skill-extractor.js";
14
13
  import { getSupabase } from "../db/supabase.js";
15
14
  import { JobRunner } from "./job-runner.js";
16
15
  import {
@@ -405,8 +404,9 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
405
404
  ),
406
405
  tool(
407
406
  "skill_generate",
408
- "Generate new skills from a job description. Analyzes the user's work, decomposes it into automatable tasks, " +
409
- "and creates skills for each one. Use this when the user describes their job/role and you want to set up skills to handle their work.",
407
+ "Prepare context for generating skills from a job description. Returns existing skills and job info " +
408
+ "so you can analyze the job and create skills using skill_create + skill_add. " +
409
+ "After creating all skills, call skill_link_job to link them to the job and mark it as analyzed.",
410
410
  {
411
411
  job_name: z.string().describe(
412
412
  "Short name for this job/role. Example: '电商运营', 'Frontend Dev', 'Data Analyst'"
@@ -415,137 +415,67 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
415
415
  "Description of the user's job, role, and daily tasks. Can be in any language. " +
416
416
  "Example: '我是电商运营,每天要看竞品价格、写商品文案、回复客户评论'"
417
417
  ),
418
- auto_create: z
419
- .boolean()
420
- .optional()
421
- .describe("If true, automatically create all generated skills. If false (default), return the plan for review first."),
422
418
  },
423
419
  async (args) => {
424
- // Step 1: Decompose job into skill specs
425
420
  const existingNames = skillManager.getAll().map((s) => s.name);
426
- const specs = await decomposeJob(args.job_description, existingNames);
427
-
428
- if (specs.length === 0) {
429
- return {
430
- content: [{
431
- type: "text",
432
- text: "Could not decompose the job description into automatable skills. Try providing more detail about your daily tasks.",
433
- }],
434
- };
435
- }
436
-
437
- // If not auto-creating, return the decomposition plan
438
- if (!args.auto_create) {
439
- let response = `## Job Analysis: ${args.job_name}\n\nI identified **${specs.length} skills** from your job description:\n\n`;
440
421
 
441
- const byPriority = { high: [] as typeof specs, medium: [] as typeof specs, low: [] as typeof specs };
442
- for (const s of specs) {
443
- (byPriority[s.priority] || byPriority.medium).push(s);
444
- }
445
-
446
- for (const [priority, items] of Object.entries(byPriority)) {
447
- if (items.length === 0) continue;
448
- const label = priority === "high" ? "High Priority" : priority === "medium" ? "Medium Priority" : "Low Priority";
449
- response += `### ${label}\n`;
450
- for (const s of items) {
451
- const auto = s.automatable ? "" : " (needs human assistance)";
452
- response += `- **${s.name}**: ${s.description}${auto}\n`;
453
- }
454
- response += "\n";
455
- }
422
+ let response = `## Job: ${args.job_name}\n`;
423
+ response += `**Description:** ${args.job_description}\n\n`;
456
424
 
457
- response += `Call \`skill_generate\` again with \`job_name: "${args.job_name}"\` and \`auto_create: true\` to create all these skills, `;
458
- response += "or use `skill_create` to create individual skills manually.";
459
-
460
- return { content: [{ type: "text", text: response }] };
461
- }
462
-
463
- // Step 2: Auto-create — generate full skills for each spec
464
- const created: string[] = [];
465
- const failed: string[] = [];
466
-
467
- // Generate skills in parallel (batch of 3 to avoid rate limits)
468
- for (let i = 0; i < specs.length; i += 3) {
469
- const batch = specs.slice(i, i + 3);
470
- const results = await Promise.allSettled(
471
- batch.map((spec) =>
472
- generateSkillFromDescription(spec.name, spec.description, args.job_description)
473
- )
474
- );
475
-
476
- for (let j = 0; j < results.length; j++) {
477
- const spec = batch[j];
478
- const result = results[j];
479
-
480
- if (result.status === "fulfilled" && result.value) {
481
- const generated = result.value;
482
- // Check for duplicates
483
- const existing = skillManager.findSimilar(generated.name);
484
- if (existing) {
485
- failed.push(`${spec.name} (duplicate of "${existing.name}")`);
486
- continue;
487
- }
488
-
489
- // Create in skills table (draft)
490
- const createResult = await skillManager.create(
491
- generated.name,
492
- generated.description || spec.description,
493
- generated.steps,
494
- {
495
- source: "job_generated",
496
- emoji: generated.emoji,
497
- keywords: generated.keywords,
498
- }
499
- );
500
-
501
- if (createResult) {
502
- // Add to user's collection (approval)
503
- const added = await skillManager.addSkill(createResult.id);
504
- if (added) {
505
- created.push(generated.name);
506
- } else {
507
- failed.push(`${spec.name} (add failed)`);
508
- }
509
- } else {
510
- failed.push(spec.name);
511
- }
512
- } else {
513
- failed.push(spec.name);
514
- }
515
- }
425
+ if (existingNames.length > 0) {
426
+ response += `**Existing skills (do NOT duplicate):** ${existingNames.join(", ")}\n\n`;
516
427
  }
517
428
 
518
- // Step 3: Save job to DB, link generated skills, and mark as analyzed
519
- if (userId) {
520
- saveJobToDb(
521
- userId,
522
- args.job_name,
523
- args.job_description,
524
- created
525
- ).catch((err) => {
526
- log.debug(`saveJobToDb error: ${err}`);
527
- });
528
- }
429
+ response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills. `;
430
+ response += `For each skill, call \`skill_create\` with:\n`;
431
+ response += `- name: kebab-case name (e.g. "slack-message-check")\n`;
432
+ response += `- description: one-line description\n`;
433
+ response += `- instructions: detailed step-by-step markdown instructions the agent can follow\n`;
434
+ response += `- emoji: a single emoji representing the skill\n\n`;
435
+ response += `After creating each skill, call \`skill_add\` with the returned skill ID to add it to the user's collection.\n\n`;
436
+ response += `After ALL skills are created and added, call \`skill_link_job\` with job_name="${args.job_name}" and the list of created skill names to link them and mark the job as analyzed.\n\n`;
437
+ response += `**Guidelines for skill instructions:**\n`;
438
+ response += `- Write clear, actionable markdown steps\n`;
439
+ response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks\n`;
440
+ response += `- Include error handling steps\n`;
441
+ response += `- Use placeholders like {query}, {date} for variable inputs\n`;
442
+ response += `- Each skill should be a single, well-defined workflow (10-25 steps)\n`;
529
443
 
530
- let response = `## Skills Generated for "${args.job_name}"\n\n`;
531
- response += `Created **${created.length}/${specs.length}** skills from your job description:\n\n`;
532
-
533
- for (const name of created) {
534
- const skill = skillManager.get(name);
535
- const emoji = skill?.metadata.emoji || "";
536
- response += `- ${emoji} **${name}**: ${skill?.description || ""}\n`;
444
+ return { content: [{ type: "text", text: response }] };
445
+ }
446
+ ),
447
+ tool(
448
+ "skill_link_job",
449
+ "Link created skills to a job and mark it as analyzed. Call this after creating all skills for a job via skill_create + skill_add.",
450
+ {
451
+ job_name: z.string().describe("Name of the job to link skills to"),
452
+ job_description: z.string().describe("Job description (used if job doesn't exist yet)"),
453
+ skill_names: z.array(z.string()).describe("Names of skills to link to this job"),
454
+ },
455
+ async (args) => {
456
+ if (!userId) {
457
+ return {
458
+ content: [{ type: "text", text: "Not authenticated. Cannot link job." }],
459
+ };
537
460
  }
538
461
 
539
- if (failed.length > 0) {
540
- response += `\nFailed to create: ${failed.join(", ")}\n`;
462
+ try {
463
+ await saveJobToDb(userId, args.job_name, args.job_description, args.skill_names);
464
+ log.success(`Job "${args.job_name}": linked ${args.skill_names.length} skills and marked as analyzed`);
465
+ return {
466
+ content: [{
467
+ type: "text",
468
+ text: `Job "${args.job_name}" linked with ${args.skill_names.length} skills and marked as analyzed.`,
469
+ }],
470
+ };
471
+ } catch (err) {
472
+ return {
473
+ content: [{
474
+ type: "text",
475
+ text: `Failed to link job: ${err instanceof Error ? err.message : err}`,
476
+ }],
477
+ };
541
478
  }
542
-
543
- response += "\nThese skills are now available. When you give me a task that matches, I'll automatically use the right skill.";
544
- response += "\nUse `skill_invoke` to test any skill, or `skill_improve` to refine them after use.";
545
-
546
- log.success(`Job "${args.job_name}": created ${created.length} skills`);
547
-
548
- return { content: [{ type: "text", text: response }] };
549
479
  }
550
480
  ),
551
481
  tool(
@@ -92,11 +92,8 @@ vi.mock("./skills.js", () => ({
92
92
  },
93
93
  }));
94
94
 
95
- vi.mock("./memory-extractor.js", () => ({
96
- extractMemoriesWithLLM: vi.fn().mockResolvedValue([]),
97
- }));
98
95
  vi.mock("./skill-extractor.js", () => ({
99
- // Only ToolCallRecord type is used — no functions imported
96
+ // Only ToolCallRecord type is used
100
97
  }));
101
98
 
102
99
  vi.mock("../utils/rate-limiter.js", () => ({
@@ -20,7 +20,6 @@ import { log, newCorrelationId, setCorrelationId } from "../utils/logger.js";
20
20
  import { getBrowser } from "../tools/browser.js";
21
21
  import { MemoryManager } from "./memory.js";
22
22
  import { SkillManager } from "./skills.js";
23
- import { extractMemoriesWithLLM } from "./memory-extractor.js";
24
23
  import { type ToolCallRecord } from "./skill-extractor.js";
25
24
  import { withRetry } from "../utils/retry.js";
26
25
  import {
@@ -56,6 +55,8 @@ Available capabilities:
56
55
  - You can remember things about the user using memory_store
57
56
  - Use this when you learn preferences, important facts, or standing instructions
58
57
  - Your stored memories persist across conversations
58
+ - PROACTIVELY use memory_store during tasks when you discover user preferences, habits, or important context
59
+ - Before completing a task, consider if anything learned should be remembered for future conversations
59
60
 
60
61
  4. SKILL PLANNING (pre-task):
61
62
  - Before executing a complex task, analyze if it matches an existing skill (use skill_invoke)
@@ -232,6 +233,7 @@ export class TaskProcessor {
232
233
  "mcp__assistme-agent__skill_invoke",
233
234
  "mcp__assistme-agent__skill_search",
234
235
  "mcp__assistme-agent__skill_generate",
236
+ "mcp__assistme-agent__skill_link_job",
235
237
  "mcp__assistme-agent__skill_browse",
236
238
  "mcp__assistme-agent__skill_add",
237
239
  "mcp__assistme-agent__skill_publish",
@@ -367,35 +369,9 @@ export class TaskProcessor {
367
369
  }
368
370
  this.historyCache.set(task.conversation_id, convHistory);
369
371
 
370
- // ── Post-task extraction (fire-and-forget, non-blocking) ──────
371
-
372
- // Auto-extract memories using LLM
373
- if (this.memoryManager && finalResponse) {
374
- const mm = this.memoryManager;
375
- const taskIdRef = task.id;
376
- extractMemoriesWithLLM(task.prompt, finalResponse)
377
- .then(async (memories) => {
378
- for (const mem of memories) {
379
- try {
380
- await mm.remember(mem.content, mem.category, {
381
- importance: mem.importance,
382
- tags: mem.tags,
383
- sourceMessageId: taskIdRef,
384
- });
385
- log.info(`Memory extracted: [${mem.category}] ${mem.content.slice(0, 60)}...`);
386
- } catch {
387
- // Non-critical — skip individual memory failures
388
- }
389
- }
390
- if (memories.length > 0) {
391
- log.success(`${memories.length} memory(s) auto-extracted`);
392
- }
393
- })
394
- .catch(() => {});
395
- }
396
-
397
- // Note: Skill creation/improvement is now handled pre-task via the
398
- // skill_create → user approval → skill_add flow, not post-task extraction.
372
+ // Note: Memory extraction and skill creation are handled by the agent itself
373
+ // during task execution via memory_store and skill_create tools.
374
+ // No separate LLM API calls needed — the agent SDK handles everything.
399
375
  } catch (err) {
400
376
  const errorMsg = err instanceof Error ? err.message : String(err);
401
377
  log.error(`Task failed: ${errorMsg}`);