assistme 0.8.5 → 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.
@@ -2876,6 +2876,10 @@ var SkillManager = class {
2876
2876
  skills = /* @__PURE__ */ new Map();
2877
2877
  idfCache = /* @__PURE__ */ new Map();
2878
2878
  userId = null;
2879
+ /** Tracks the in-flight loadFromDb() promise so callers can await it. */
2880
+ loadPromise = null;
2881
+ /** True once loadFromDb() completes without error (even with 0 skills). */
2882
+ loaded = false;
2879
2883
  /** Bounded cache for findRelevant() — keyed by normalized prompt, invalidated on skill changes. */
2880
2884
  relevanceCache = new LRUCache(MAX_RELEVANCE_CACHE_ENTRIES);
2881
2885
  /** Bounded, TTL-aware cache for marketplace discover results. */
@@ -2885,6 +2889,42 @@ var SkillManager = class {
2885
2889
  }
2886
2890
  async loadFromDb() {
2887
2891
  if (!this.userId) return;
2892
+ if (this.loadPromise) return this.loadPromise;
2893
+ this.loadPromise = this._doLoadFromDb();
2894
+ try {
2895
+ await this.loadPromise;
2896
+ } finally {
2897
+ this.loadPromise = null;
2898
+ }
2899
+ }
2900
+ /**
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.
2904
+ */
2905
+ async ensureLoaded() {
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);
2926
+ }
2927
+ async _doLoadFromDb() {
2888
2928
  try {
2889
2929
  const data = await callMcpHandler("skill.load");
2890
2930
  this.skills.clear();
@@ -2894,6 +2934,7 @@ var SkillManager = class {
2894
2934
  this.skills.set(row.name, this.rowToSkill(row));
2895
2935
  }
2896
2936
  this.rebuildIdfCache();
2937
+ this.loaded = true;
2897
2938
  if (this.skills.size > 0) {
2898
2939
  log.info(`Loaded ${this.skills.size} skill(s) from DB`);
2899
2940
  }
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setSessionBusy,
28
28
  toggleScheduledTask,
29
29
  updateHeartbeat
30
- } from "./chunk-4E5QAYTQ.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-4E5QAYTQ.js";
22
+ } from "../chunk-QBXD76HA.js";
23
23
  import {
24
24
  EDSGER_PRODUCT_SLUG,
25
25
  JobRunner,
@@ -1778,7 +1778,7 @@ function createAgentToolsServer(deps) {
1778
1778
  description: z2.string().optional().describe("Updated description (optional)")
1779
1779
  },
1780
1780
  async (args) => {
1781
- const existing = skillManager.get(args.name);
1781
+ const existing = await skillManager.getWithDbFallback(args.name);
1782
1782
  if (!existing) {
1783
1783
  const available = skillManager.getAll().map((s) => s.name).join(", ");
1784
1784
  return {
@@ -1824,7 +1824,7 @@ function createAgentToolsServer(deps) {
1824
1824
  arguments: z2.string().optional().describe("Arguments to pass to the skill (replaces $ARGUMENTS placeholders)")
1825
1825
  },
1826
1826
  async (args) => {
1827
- let skill = skillManager.get(args.name);
1827
+ let skill = await skillManager.getWithDbFallback(args.name);
1828
1828
  if (!skill) {
1829
1829
  const confirmResult = await confirmMarketplaceSkill(
1830
1830
  skillManager,
@@ -2138,7 +2138,7 @@ Use \`ask_user\` to request these from the user, or create them yourself (e.g. r
2138
2138
  author_name: z2.string().optional().describe("Your display name as the author")
2139
2139
  },
2140
2140
  async (args) => {
2141
- const skill = skillManager.get(args.name);
2141
+ const skill = await skillManager.getWithDbFallback(args.name);
2142
2142
  if (!skill) {
2143
2143
  return {
2144
2144
  content: [
@@ -3321,6 +3321,7 @@ var TaskProcessor = class {
3321
3321
  let tokenUsage;
3322
3322
  try {
3323
3323
  await emitEvent(task.id, "status_change", { status: "running" });
3324
+ await this.skillManager.ensureLoaded();
3324
3325
  const systemPrompt = await buildSystemPrompt(task, {
3325
3326
  memoryManager: this.memoryManager,
3326
3327
  skillManager: this.skillManager,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistme",
3
- "version": "0.8.5",
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,
@@ -63,6 +63,11 @@ export class SkillManager {
63
63
  private idfCache: Map<string, number> = new Map();
64
64
  private userId: string | null = null;
65
65
 
66
+ /** Tracks the in-flight loadFromDb() promise so callers can await it. */
67
+ private loadPromise: Promise<void> | null = null;
68
+ /** True once loadFromDb() completes without error (even with 0 skills). */
69
+ private loaded = false;
70
+
66
71
  /** Bounded cache for findRelevant() — keyed by normalized prompt, invalidated on skill changes. */
67
72
  private relevanceCache = new LRUCache<
68
73
  string,
@@ -79,6 +84,52 @@ export class SkillManager {
79
84
  async loadFromDb(): Promise<void> {
80
85
  if (!this.userId) return;
81
86
 
87
+ // Deduplicate concurrent loads: reuse existing in-flight promise
88
+ if (this.loadPromise) return this.loadPromise;
89
+
90
+ this.loadPromise = this._doLoadFromDb();
91
+ try {
92
+ await this.loadPromise;
93
+ } finally {
94
+ this.loadPromise = null;
95
+ }
96
+ }
97
+
98
+ /**
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.
102
+ */
103
+ async ensureLoaded(): Promise<void> {
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);
130
+ }
131
+
132
+ private async _doLoadFromDb(): Promise<void> {
82
133
  try {
83
134
  const data = await callMcpHandler<unknown[]>("skill.load");
84
135
 
@@ -90,6 +141,7 @@ export class SkillManager {
90
141
  }
91
142
 
92
143
  this.rebuildIdfCache();
144
+ this.loaded = true;
93
145
 
94
146
  if (this.skills.size > 0) {
95
147
  log.info(`Loaded ${this.skills.size} skill(s) from DB`);
@@ -173,7 +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
- 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);
177
178
  if (!existing) {
178
179
  const available = skillManager
179
180
  .getAll()
@@ -228,7 +229,8 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
228
229
  .describe("Arguments to pass to the skill (replaces $ARGUMENTS placeholders)"),
229
230
  },
230
231
  async (args) => {
231
- 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);
232
234
 
233
235
  // If not in user's collection, try marketplace — requires user confirmation
234
236
  if (!skill) {
@@ -541,7 +543,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
541
543
  author_name: z.string().optional().describe("Your display name as the author"),
542
544
  },
543
545
  async (args) => {
544
- const skill = skillManager.get(args.name);
546
+ const skill = await skillManager.getWithDbFallback(args.name);
545
547
  if (!skill) {
546
548
  return {
547
549
  content: [