assistme 0.8.6 → 0.8.7

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
  }
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-QBXD76HA.js";
31
31
  import {
32
32
  HEARTBEAT_INTERVAL_MS,
33
33
  HEARTBEAT_LOG_MAX_ENTRIES,
@@ -19,7 +19,7 @@ import {
19
19
  pollActionResponse,
20
20
  resetEventSequence,
21
21
  setActionRequest
22
- } from "../chunk-ZBZWNZVA.js";
22
+ } from "../chunk-QBXD76HA.js";
23
23
  import {
24
24
  EDSGER_PRODUCT_SLUG,
25
25
  JobRunner,
@@ -1778,8 +1778,7 @@ function createAgentToolsServer(deps) {
1778
1778
  description: z2.string().optional().describe("Updated description (optional)")
1779
1779
  },
1780
1780
  async (args) => {
1781
- await skillManager.ensureLoaded();
1782
- const existing = skillManager.get(args.name);
1781
+ const existing = await skillManager.getWithDbFallback(args.name);
1783
1782
  if (!existing) {
1784
1783
  const available = skillManager.getAll().map((s) => s.name).join(", ");
1785
1784
  return {
@@ -1825,8 +1824,7 @@ function createAgentToolsServer(deps) {
1825
1824
  arguments: z2.string().optional().describe("Arguments to pass to the skill (replaces $ARGUMENTS placeholders)")
1826
1825
  },
1827
1826
  async (args) => {
1828
- await skillManager.ensureLoaded();
1829
- let skill = skillManager.get(args.name);
1827
+ let skill = await skillManager.getWithDbFallback(args.name);
1830
1828
  if (!skill) {
1831
1829
  const confirmResult = await confirmMarketplaceSkill(
1832
1830
  skillManager,
@@ -2140,7 +2138,7 @@ Use \`ask_user\` to request these from the user, or create them yourself (e.g. r
2140
2138
  author_name: z2.string().optional().describe("Your display name as the author")
2141
2139
  },
2142
2140
  async (args) => {
2143
- const skill = skillManager.get(args.name);
2141
+ const skill = await skillManager.getWithDbFallback(args.name);
2144
2142
  if (!skill) {
2145
2143
  return {
2146
2144
  content: [
@@ -3323,6 +3321,7 @@ var TaskProcessor = class {
3323
3321
  let tokenUsage;
3324
3322
  try {
3325
3323
  await emitEvent(task.id, "status_change", { status: "running" });
3324
+ await this.skillManager.ensureLoaded();
3326
3325
  const systemPrompt = await buildSystemPrompt(task, {
3327
3326
  memoryManager: this.memoryManager,
3328
3327
  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.7",
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`);
@@ -173,10 +173,8 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
173
173
  description: z.string().optional().describe("Updated description (optional)"),
174
174
  },
175
175
  async (args) => {
176
- // Ensure DB skills are loaded (avoids race with async loadFromDb)
177
- await skillManager.ensureLoaded();
178
-
179
- const existing = skillManager.get(args.name);
176
+ // Use DB fallback to handle race condition with async loadFromDb
177
+ const existing = await skillManager.getWithDbFallback(args.name);
180
178
  if (!existing) {
181
179
  const available = skillManager
182
180
  .getAll()
@@ -231,10 +229,8 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
231
229
  .describe("Arguments to pass to the skill (replaces $ARGUMENTS placeholders)"),
232
230
  },
233
231
  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);
232
+ // Use DB fallback to handle race condition with async loadFromDb
233
+ let skill = await skillManager.getWithDbFallback(args.name);
238
234
 
239
235
  // If not in user's collection, try marketplace — requires user confirmation
240
236
  if (!skill) {
@@ -547,7 +543,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
547
543
  author_name: z.string().optional().describe("Your display name as the author"),
548
544
  },
549
545
  async (args) => {
550
- const skill = skillManager.get(args.name);
546
+ const skill = await skillManager.getWithDbFallback(args.name);
551
547
  if (!skill) {
552
548
  return {
553
549
  content: [