assistme 0.8.6 → 0.8.8

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.
@@ -2878,6 +2878,8 @@ var SkillManager = class {
2878
2878
  userId = null;
2879
2879
  /** Tracks the in-flight loadFromDb() promise so callers can await it. */
2880
2880
  loadPromise = null;
2881
+ /** True once loadFromDb() completes without error (even with 0 skills). */
2882
+ loaded = false;
2881
2883
  /** Bounded cache for findRelevant() — keyed by normalized prompt, invalidated on skill changes. */
2882
2884
  relevanceCache = new LRUCache(MAX_RELEVANCE_CACHE_ENTRIES);
2883
2885
  /** Bounded, TTL-aware cache for marketplace discover results. */
@@ -2896,11 +2898,31 @@ var SkillManager = class {
2896
2898
  }
2897
2899
  }
2898
2900
  /**
2899
- * Wait for any in-flight loadFromDb() to finish.
2900
- * Safe to call multiple times resolves immediately if no load is pending.
2901
+ * Guarantee that loadFromDb() has completed at least once.
2902
+ * Awaits any in-flight load; retries once if the initial load failed.
2903
+ * Safe and cheap to call repeatedly — no-ops after the first success.
2901
2904
  */
2902
2905
  async ensureLoaded() {
2903
2906
  if (this.loadPromise) await this.loadPromise;
2907
+ if (!this.loaded && this.userId) {
2908
+ await this.loadFromDb();
2909
+ }
2910
+ }
2911
+ /**
2912
+ * Get a skill by name, with a DB search fallback.
2913
+ * Handles: race conditions, failed initial loads, and skills added externally
2914
+ * after the initial load (e.g. created via UI while agent is running).
2915
+ */
2916
+ async getWithDbFallback(name) {
2917
+ await this.ensureLoaded();
2918
+ const skill = this.skills.get(name);
2919
+ if (skill) return skill;
2920
+ const dbResults = await searchSkillsInDb(name, 5);
2921
+ const match = dbResults?.find((r) => r.name === name);
2922
+ if (!match) return void 0;
2923
+ this.loaded = false;
2924
+ await this.loadFromDb();
2925
+ return this.skills.get(name);
2904
2926
  }
2905
2927
  async _doLoadFromDb() {
2906
2928
  try {
@@ -2912,6 +2934,7 @@ var SkillManager = class {
2912
2934
  this.skills.set(row.name, this.rowToSkill(row));
2913
2935
  }
2914
2936
  this.rebuildIdfCache();
2937
+ this.loaded = true;
2915
2938
  if (this.skills.size > 0) {
2916
2939
  log.info(`Loaded ${this.skills.size} skill(s) from DB`);
2917
2940
  }
@@ -3711,6 +3734,7 @@ export {
3711
3734
  executeShell,
3712
3735
  MemoryManager,
3713
3736
  LRUCache,
3737
+ upsertAgentSkill,
3714
3738
  SkillManager,
3715
3739
  getLocalStore,
3716
3740
  getCredentialStore
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setSessionBusy,
28
28
  toggleScheduledTask,
29
29
  updateHeartbeat
30
- } from "./chunk-ZBZWNZVA.js";
30
+ } from "./chunk-T3DBLWUW.js";
31
31
  import {
32
32
  HEARTBEAT_INTERVAL_MS,
33
33
  HEARTBEAT_LOG_MAX_ENTRIES,
@@ -18,8 +18,9 @@ import {
18
18
  getNextRunTime,
19
19
  pollActionResponse,
20
20
  resetEventSequence,
21
- setActionRequest
22
- } from "../chunk-ZBZWNZVA.js";
21
+ setActionRequest,
22
+ upsertAgentSkill
23
+ } from "../chunk-T3DBLWUW.js";
23
24
  import {
24
25
  EDSGER_PRODUCT_SLUG,
25
26
  JobRunner,
@@ -1779,25 +1780,26 @@ function createAgentToolsServer(deps) {
1779
1780
  },
1780
1781
  async (args) => {
1781
1782
  await skillManager.ensureLoaded();
1782
- const existing = skillManager.get(args.name);
1783
- if (!existing) {
1784
- const available = skillManager.getAll().map((s) => s.name).join(", ");
1783
+ if (skillManager.update(args.name, args.improved_instructions, args.description)) {
1784
+ log.success(`Self-improvement: improved skill "${args.name}"`);
1785
1785
  return {
1786
1786
  content: [
1787
1787
  {
1788
1788
  type: "text",
1789
- text: `Skill "${args.name}" not found. Available skills: ${available}`
1789
+ text: `Skill "${args.name}" improved and version bumped.`
1790
1790
  }
1791
1791
  ]
1792
1792
  };
1793
1793
  }
1794
- const updated = skillManager.update(
1795
- args.name,
1796
- args.improved_instructions,
1797
- args.description
1798
- );
1799
- if (updated) {
1800
- log.success(`Self-improvement: improved skill "${args.name}"`);
1794
+ const result = await upsertAgentSkill({
1795
+ name: args.name,
1796
+ description: args.description || "",
1797
+ content: args.improved_instructions,
1798
+ version: "1.0.1",
1799
+ source: "auto_improved"
1800
+ });
1801
+ if (result) {
1802
+ log.success(`Self-improvement: improved skill "${args.name}" (direct DB)`);
1801
1803
  return {
1802
1804
  content: [
1803
1805
  {
@@ -1825,8 +1827,7 @@ function createAgentToolsServer(deps) {
1825
1827
  arguments: z2.string().optional().describe("Arguments to pass to the skill (replaces $ARGUMENTS placeholders)")
1826
1828
  },
1827
1829
  async (args) => {
1828
- await skillManager.ensureLoaded();
1829
- let skill = skillManager.get(args.name);
1830
+ let skill = await skillManager.getWithDbFallback(args.name);
1830
1831
  if (!skill) {
1831
1832
  const confirmResult = await confirmMarketplaceSkill(
1832
1833
  skillManager,
@@ -2140,7 +2141,7 @@ Use \`ask_user\` to request these from the user, or create them yourself (e.g. r
2140
2141
  author_name: z2.string().optional().describe("Your display name as the author")
2141
2142
  },
2142
2143
  async (args) => {
2143
- const skill = skillManager.get(args.name);
2144
+ const skill = await skillManager.getWithDbFallback(args.name);
2144
2145
  if (!skill) {
2145
2146
  return {
2146
2147
  content: [
@@ -3323,6 +3324,7 @@ var TaskProcessor = class {
3323
3324
  let tokenUsage;
3324
3325
  try {
3325
3326
  await emitEvent(task.id, "status_change", { status: "running" });
3327
+ await this.skillManager.ensureLoaded();
3326
3328
  const systemPrompt = await buildSystemPrompt(task, {
3327
3329
  memoryManager: this.memoryManager,
3328
3330
  skillManager: this.skillManager,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistme",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "AssistMe CLI Agent - AI-powered agentic assistant for code, browser, and automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -181,6 +181,9 @@ export class TaskProcessor {
181
181
  try {
182
182
  await emitEvent(task.id, "status_change", { status: "running" });
183
183
 
184
+ // Guarantee skills are loaded before building prompt or running tools
185
+ await this.skillManager.ensureLoaded();
186
+
184
187
  // Build system prompt with memories + skills + history
185
188
  const systemPrompt = await buildSystemPrompt(task, {
186
189
  memoryManager: this.memoryManager,
@@ -65,6 +65,8 @@ export class SkillManager {
65
65
 
66
66
  /** Tracks the in-flight loadFromDb() promise so callers can await it. */
67
67
  private loadPromise: Promise<void> | null = null;
68
+ /** True once loadFromDb() completes without error (even with 0 skills). */
69
+ private loaded = false;
68
70
 
69
71
  /** Bounded cache for findRelevant() — keyed by normalized prompt, invalidated on skill changes. */
70
72
  private relevanceCache = new LRUCache<
@@ -94,11 +96,37 @@ export class SkillManager {
94
96
  }
95
97
 
96
98
  /**
97
- * Wait for any in-flight loadFromDb() to finish.
98
- * Safe to call multiple times resolves immediately if no load is pending.
99
+ * Guarantee that loadFromDb() has completed at least once.
100
+ * Awaits any in-flight load; retries once if the initial load failed.
101
+ * Safe and cheap to call repeatedly — no-ops after the first success.
99
102
  */
100
103
  async ensureLoaded(): Promise<void> {
101
104
  if (this.loadPromise) await this.loadPromise;
105
+ if (!this.loaded && this.userId) {
106
+ await this.loadFromDb();
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Get a skill by name, with a DB search fallback.
112
+ * Handles: race conditions, failed initial loads, and skills added externally
113
+ * after the initial load (e.g. created via UI while agent is running).
114
+ */
115
+ async getWithDbFallback(name: string): Promise<Skill | undefined> {
116
+ await this.ensureLoaded();
117
+
118
+ const skill = this.skills.get(name);
119
+ if (skill) return skill;
120
+
121
+ // In-memory miss: check if the skill exists in DB via search
122
+ const dbResults = await searchSkillsInDb(name, 5);
123
+ const match = dbResults?.find((r) => r.name === name);
124
+ if (!match) return undefined;
125
+
126
+ // Found in DB — reload all skills into memory and return
127
+ this.loaded = false; // force reload
128
+ await this.loadFromDb();
129
+ return this.skills.get(name);
102
130
  }
103
131
 
104
132
  private async _doLoadFromDb(): Promise<void> {
@@ -113,6 +141,7 @@ export class SkillManager {
113
141
  }
114
142
 
115
143
  this.rebuildIdfCache();
144
+ this.loaded = true;
116
145
 
117
146
  if (this.skills.size > 0) {
118
147
  log.info(`Loaded ${this.skills.size} skill(s) from DB`);
@@ -10,6 +10,7 @@ import type { SkillManager } from "../agent/skills.js";
10
10
  import { substituteArguments, preprocessDynamicContext } from "../agent/skill-utils.js";
11
11
  import { validateSkillName } from "../agent/skill-utils.js";
12
12
  import { callMcpHandler } from "../db/api-client.js";
13
+ import { upsertAgentSkill } from "../agent/skill-db.js";
13
14
  import { JobRunner } from "../agent/job-runner.js";
14
15
  import { createScheduledTask, getNextRunTime } from "../agent/scheduler.js";
15
16
  import { getCredentialStore, type CredentialType } from "../credentials/index.js";
@@ -173,32 +174,32 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
173
174
  description: z.string().optional().describe("Updated description (optional)"),
174
175
  },
175
176
  async (args) => {
176
- // Ensure DB skills are loaded (avoids race with async loadFromDb)
177
+ // Fast path: skill is in memory update in-memory + DB
177
178
  await skillManager.ensureLoaded();
178
-
179
- const existing = skillManager.get(args.name);
180
- if (!existing) {
181
- const available = skillManager
182
- .getAll()
183
- .map((s) => s.name)
184
- .join(", ");
179
+ if (skillManager.update(args.name, args.improved_instructions, args.description)) {
180
+ log.success(`Self-improvement: improved skill "${args.name}"`);
185
181
  return {
186
182
  content: [
187
183
  {
188
184
  type: "text",
189
- text: `Skill "${args.name}" not found. Available skills: ${available}`,
185
+ text: `Skill "${args.name}" improved and version bumped.`,
190
186
  },
191
187
  ],
192
188
  };
193
189
  }
194
190
 
195
- const updated = skillManager.update(
196
- args.name,
197
- args.improved_instructions,
198
- args.description
199
- );
200
- if (updated) {
201
- log.success(`Self-improvement: improved skill "${args.name}"`);
191
+ // Fallback: skill not in memory (e.g. loadFromDb failed, race condition,
192
+ // or invoked from detail page where prompt already contains full content).
193
+ // Write directly to agent_skills — the tool params already have everything needed.
194
+ const result = await upsertAgentSkill({
195
+ name: args.name,
196
+ description: args.description || "",
197
+ content: args.improved_instructions,
198
+ version: "1.0.1",
199
+ source: "auto_improved",
200
+ });
201
+ if (result) {
202
+ log.success(`Self-improvement: improved skill "${args.name}" (direct DB)`);
202
203
  return {
203
204
  content: [
204
205
  {
@@ -231,10 +232,8 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
231
232
  .describe("Arguments to pass to the skill (replaces $ARGUMENTS placeholders)"),
232
233
  },
233
234
  async (args) => {
234
- // Ensure DB skills are loaded (avoids race with async loadFromDb)
235
- await skillManager.ensureLoaded();
236
-
237
- let skill = skillManager.get(args.name);
235
+ // Use DB fallback to handle race condition with async loadFromDb
236
+ let skill = await skillManager.getWithDbFallback(args.name);
238
237
 
239
238
  // If not in user's collection, try marketplace — requires user confirmation
240
239
  if (!skill) {
@@ -547,7 +546,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
547
546
  author_name: z.string().optional().describe("Your display name as the author"),
548
547
  },
549
548
  async (args) => {
550
- const skill = skillManager.get(args.name);
549
+ const skill = await skillManager.getWithDbFallback(args.name);
551
550
  if (!skill) {
552
551
  return {
553
552
  content: [