wave-agent-sdk 0.11.6 → 0.11.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.
Files changed (105) hide show
  1. package/builtin/skills/init/SKILL.md +2 -0
  2. package/builtin/skills/settings/SKILLS.md +3 -2
  3. package/builtin/skills/settings/SUBAGENTS.md +1 -3
  4. package/dist/agent.d.ts +6 -0
  5. package/dist/agent.d.ts.map +1 -1
  6. package/dist/agent.js +18 -1
  7. package/dist/constants/tools.d.ts +1 -1
  8. package/dist/constants/tools.d.ts.map +1 -1
  9. package/dist/constants/tools.js +1 -1
  10. package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
  11. package/dist/managers/MemoryRuleManager.js +1 -9
  12. package/dist/managers/aiManager.d.ts.map +1 -1
  13. package/dist/managers/aiManager.js +22 -3
  14. package/dist/managers/messageManager.d.ts +13 -5
  15. package/dist/managers/messageManager.d.ts.map +1 -1
  16. package/dist/managers/messageManager.js +62 -34
  17. package/dist/managers/pluginManager.d.ts.map +1 -1
  18. package/dist/managers/pluginManager.js +4 -2
  19. package/dist/managers/slashCommandManager.d.ts +2 -0
  20. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  21. package/dist/managers/slashCommandManager.js +98 -4
  22. package/dist/managers/toolManager.d.ts.map +1 -1
  23. package/dist/managers/toolManager.js +8 -2
  24. package/dist/prompts/index.d.ts +2 -0
  25. package/dist/prompts/index.d.ts.map +1 -1
  26. package/dist/prompts/index.js +5 -0
  27. package/dist/services/GitService.d.ts +1 -0
  28. package/dist/services/GitService.d.ts.map +1 -1
  29. package/dist/services/GitService.js +16 -0
  30. package/dist/services/MarketplaceService.d.ts +7 -0
  31. package/dist/services/MarketplaceService.d.ts.map +1 -1
  32. package/dist/services/MarketplaceService.js +321 -252
  33. package/dist/services/aiService.d.ts +34 -0
  34. package/dist/services/aiService.d.ts.map +1 -1
  35. package/dist/services/aiService.js +124 -1
  36. package/dist/services/initializationService.d.ts.map +1 -1
  37. package/dist/services/initializationService.js +18 -0
  38. package/dist/tools/agentTool.js +3 -3
  39. package/dist/tools/bashTool.d.ts.map +1 -1
  40. package/dist/tools/bashTool.js +4 -4
  41. package/dist/tools/editTool.d.ts.map +1 -1
  42. package/dist/tools/editTool.js +2 -0
  43. package/dist/tools/globTool.d.ts.map +1 -1
  44. package/dist/tools/globTool.js +15 -3
  45. package/dist/tools/grepTool.d.ts.map +1 -1
  46. package/dist/tools/grepTool.js +38 -12
  47. package/dist/tools/readTool.d.ts.map +1 -1
  48. package/dist/tools/readTool.js +61 -0
  49. package/dist/tools/skillTool.js +2 -2
  50. package/dist/tools/types.d.ts +16 -0
  51. package/dist/tools/types.d.ts.map +1 -1
  52. package/dist/tools/webFetchTool.d.ts +3 -0
  53. package/dist/tools/webFetchTool.d.ts.map +1 -0
  54. package/dist/tools/webFetchTool.js +171 -0
  55. package/dist/tools/writeTool.d.ts.map +1 -1
  56. package/dist/tools/writeTool.js +2 -0
  57. package/dist/types/commands.d.ts +1 -1
  58. package/dist/types/commands.d.ts.map +1 -1
  59. package/dist/types/messaging.d.ts +1 -0
  60. package/dist/types/messaging.d.ts.map +1 -1
  61. package/dist/utils/bashParser.d.ts +14 -0
  62. package/dist/utils/bashParser.d.ts.map +1 -1
  63. package/dist/utils/bashParser.js +243 -142
  64. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  65. package/dist/utils/convertMessagesForAPI.js +7 -0
  66. package/dist/utils/fileUtils.d.ts +8 -0
  67. package/dist/utils/fileUtils.d.ts.map +1 -1
  68. package/dist/utils/fileUtils.js +52 -0
  69. package/dist/utils/messageOperations.d.ts +12 -3
  70. package/dist/utils/messageOperations.d.ts.map +1 -1
  71. package/dist/utils/messageOperations.js +77 -9
  72. package/package.json +4 -2
  73. package/src/agent.ts +19 -1
  74. package/src/constants/tools.ts +1 -1
  75. package/src/managers/MemoryRuleManager.ts +1 -10
  76. package/src/managers/aiManager.ts +23 -3
  77. package/src/managers/messageManager.ts +76 -38
  78. package/src/managers/pluginManager.ts +4 -2
  79. package/src/managers/slashCommandManager.ts +130 -4
  80. package/src/managers/toolManager.ts +11 -2
  81. package/src/prompts/index.ts +6 -0
  82. package/src/services/GitService.ts +20 -0
  83. package/src/services/MarketplaceService.ts +397 -324
  84. package/src/services/aiService.ts +197 -1
  85. package/src/services/initializationService.ts +38 -0
  86. package/src/tools/agentTool.ts +3 -3
  87. package/src/tools/bashTool.ts +3 -4
  88. package/src/tools/editTool.ts +3 -0
  89. package/src/tools/globTool.ts +16 -3
  90. package/src/tools/grepTool.ts +41 -13
  91. package/src/tools/readTool.ts +69 -0
  92. package/src/tools/skillTool.ts +2 -2
  93. package/src/tools/types.ts +13 -0
  94. package/src/tools/webFetchTool.ts +194 -0
  95. package/src/tools/writeTool.ts +3 -0
  96. package/src/types/commands.ts +1 -1
  97. package/src/types/messaging.ts +1 -0
  98. package/src/utils/bashParser.ts +268 -157
  99. package/src/utils/convertMessagesForAPI.ts +8 -0
  100. package/src/utils/fileUtils.ts +69 -0
  101. package/src/utils/messageOperations.ts +84 -9
  102. package/dist/tools/taskOutputTool.d.ts +0 -3
  103. package/dist/tools/taskOutputTool.d.ts.map +0 -1
  104. package/dist/tools/taskOutputTool.js +0 -198
  105. package/src/tools/taskOutputTool.ts +0 -222
@@ -14,6 +14,7 @@ export class MarketplaceService {
14
14
  this.pluginsDir = getPluginsDir();
15
15
  this.knownMarketplacesPath = path.join(this.pluginsDir, "known_marketplaces.json");
16
16
  this.installedPluginsPath = path.join(this.pluginsDir, "installed_plugins.json");
17
+ this.lockPath = path.join(this.pluginsDir, ".lock");
17
18
  this.tmpDir = path.join(this.pluginsDir, "tmp");
18
19
  this.cacheDir = path.join(this.pluginsDir, "cache");
19
20
  this.marketplacesDir = path.join(this.pluginsDir, "marketplaces");
@@ -30,6 +31,46 @@ export class MarketplaceService {
30
31
  }
31
32
  });
32
33
  }
34
+ /**
35
+ * Acquires a file-based lock and executes the provided function.
36
+ * Supports re-entrancy within the same process.
37
+ */
38
+ async withLock(fn) {
39
+ if (MarketplaceService.isLockedInProcess) {
40
+ return await fn();
41
+ }
42
+ let lockFd;
43
+ const maxRetries = 600; // 60 seconds total
44
+ const retryDelay = 100;
45
+ for (let i = 0; i < maxRetries; i++) {
46
+ try {
47
+ lockFd = await fs.open(this.lockPath, "wx");
48
+ break;
49
+ }
50
+ catch (error) {
51
+ if (error &&
52
+ typeof error === "object" &&
53
+ "code" in error &&
54
+ error.code === "EEXIST") {
55
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
56
+ continue;
57
+ }
58
+ throw error;
59
+ }
60
+ }
61
+ if (!lockFd) {
62
+ throw new Error(`Failed to acquire marketplace lock after ${maxRetries} retries. If no other wave-agent process is running, please delete ${this.lockPath}`);
63
+ }
64
+ MarketplaceService.isLockedInProcess = true;
65
+ try {
66
+ return await fn();
67
+ }
68
+ finally {
69
+ MarketplaceService.isLockedInProcess = false;
70
+ await lockFd.close();
71
+ await fs.unlink(this.lockPath).catch(() => { });
72
+ }
73
+ }
33
74
  /**
34
75
  * Loads the known marketplaces registry
35
76
  */
@@ -86,13 +127,17 @@ export class MarketplaceService {
86
127
  * Saves the known marketplaces registry
87
128
  */
88
129
  async saveKnownMarketplaces(registry) {
89
- await fs.writeFile(this.knownMarketplacesPath, JSON.stringify(registry, null, 2));
130
+ const tmpPath = `${this.knownMarketplacesPath}.tmp`;
131
+ await fs.writeFile(tmpPath, JSON.stringify(registry, null, 2));
132
+ await fs.rename(tmpPath, this.knownMarketplacesPath);
90
133
  }
91
134
  /**
92
135
  * Saves the installed plugins registry
93
136
  */
94
137
  async saveInstalledPlugins(registry) {
95
- await fs.writeFile(this.installedPluginsPath, JSON.stringify(registry, null, 2));
138
+ const tmpPath = `${this.installedPluginsPath}.tmp`;
139
+ await fs.writeFile(tmpPath, JSON.stringify(registry, null, 2));
140
+ await fs.rename(tmpPath, this.installedPluginsPath);
96
141
  }
97
142
  /**
98
143
  * Loads a marketplace manifest from a local path
@@ -135,78 +180,82 @@ export class MarketplaceService {
135
180
  * Adds a new marketplace (local directory, GitHub repo, or Git URL)
136
181
  */
137
182
  async addMarketplace(input) {
138
- let marketplace;
139
- const isFullUrl = input.startsWith("http://") ||
140
- input.startsWith("https://") ||
141
- input.startsWith("git@") ||
142
- input.startsWith("ssh://");
143
- if (isFullUrl ||
144
- (input.includes("/") && !path.isAbsolute(input) && !input.startsWith("."))) {
145
- // Git or GitHub repo
146
- let urlOrRepo = input;
147
- let ref;
148
- if (input.includes("#")) {
149
- [urlOrRepo, ref] = input.split("#");
150
- }
151
- const tempMarketplace = isFullUrl
152
- ? { name: "", source: { source: "git", url: urlOrRepo, ref } }
153
- : { name: "", source: { source: "github", repo: urlOrRepo, ref } };
154
- const targetPath = this.getMarketplacePath(tempMarketplace);
155
- if (!existsSync(targetPath)) {
183
+ return this.withLock(async () => {
184
+ let marketplace;
185
+ const isFullUrl = input.startsWith("http://") ||
186
+ input.startsWith("https://") ||
187
+ input.startsWith("git@") ||
188
+ input.startsWith("ssh://");
189
+ if (isFullUrl ||
190
+ (input.includes("/") &&
191
+ !path.isAbsolute(input) &&
192
+ !input.startsWith("."))) {
193
+ // Git or GitHub repo
194
+ let urlOrRepo = input;
195
+ let ref;
196
+ if (input.includes("#")) {
197
+ [urlOrRepo, ref] = input.split("#");
198
+ }
199
+ const tempMarketplace = isFullUrl
200
+ ? { name: "", source: { source: "git", url: urlOrRepo, ref } }
201
+ : { name: "", source: { source: "github", repo: urlOrRepo, ref } };
202
+ const targetPath = this.getMarketplacePath(tempMarketplace);
203
+ if (!existsSync(targetPath)) {
204
+ try {
205
+ await this.gitService.clone(urlOrRepo, targetPath, ref);
206
+ }
207
+ catch (error) {
208
+ throw new Error(`Failed to add marketplace from Git: ${error instanceof Error ? error.message : String(error)}`);
209
+ }
210
+ }
211
+ let manifest;
156
212
  try {
157
- await this.gitService.clone(urlOrRepo, targetPath, ref);
213
+ manifest = await this.loadMarketplaceManifest(targetPath);
158
214
  }
159
215
  catch (error) {
160
- throw new Error(`Failed to add marketplace from Git: ${error instanceof Error ? error.message : String(error)}`);
216
+ throw new Error(`Failed to load manifest from cloned repository: ${error instanceof Error ? error.message : String(error)}`);
161
217
  }
218
+ marketplace = {
219
+ name: manifest.name,
220
+ source: isFullUrl
221
+ ? { source: "git", url: urlOrRepo, ref }
222
+ : { source: "github", repo: urlOrRepo, ref },
223
+ autoUpdate: false,
224
+ lastUpdated: new Date().toISOString(),
225
+ };
162
226
  }
163
- let manifest;
164
- try {
165
- manifest = await this.loadMarketplaceManifest(targetPath);
166
- }
167
- catch (error) {
168
- throw new Error(`Failed to load manifest from cloned repository: ${error instanceof Error ? error.message : String(error)}`);
227
+ else {
228
+ // Local directory format
229
+ const absolutePath = path.resolve(input);
230
+ let manifest;
231
+ try {
232
+ manifest = await this.loadMarketplaceManifest(absolutePath);
233
+ }
234
+ catch (error) {
235
+ throw new Error(`Failed to load manifest from directory: ${error instanceof Error ? error.message : String(error)}`);
236
+ }
237
+ marketplace = {
238
+ name: manifest.name,
239
+ source: { source: "directory", path: absolutePath },
240
+ autoUpdate: false,
241
+ lastUpdated: new Date().toISOString(),
242
+ };
169
243
  }
170
- marketplace = {
171
- name: manifest.name,
172
- source: isFullUrl
173
- ? { source: "git", url: urlOrRepo, ref }
174
- : { source: "github", repo: urlOrRepo, ref },
175
- autoUpdate: false,
176
- lastUpdated: new Date().toISOString(),
177
- };
178
- }
179
- else {
180
- // Local directory format
181
- const absolutePath = path.resolve(input);
182
- let manifest;
183
- try {
184
- manifest = await this.loadMarketplaceManifest(absolutePath);
244
+ const registry = await this.getKnownMarketplaces();
245
+ // Check if already exists
246
+ const existingIndex = registry.marketplaces.findIndex((m) => m.name === marketplace.name);
247
+ if (existingIndex >= 0) {
248
+ registry.marketplaces[existingIndex] = marketplace;
185
249
  }
186
- catch (error) {
187
- throw new Error(`Failed to load manifest from directory: ${error instanceof Error ? error.message : String(error)}`);
250
+ else {
251
+ registry.marketplaces.push(marketplace);
188
252
  }
189
- marketplace = {
190
- name: manifest.name,
191
- source: { source: "directory", path: absolutePath },
192
- autoUpdate: false,
193
- lastUpdated: new Date().toISOString(),
194
- };
195
- }
196
- const registry = await this.getKnownMarketplaces();
197
- // Check if already exists
198
- const existingIndex = registry.marketplaces.findIndex((m) => m.name === marketplace.name);
199
- if (existingIndex >= 0) {
200
- registry.marketplaces[existingIndex] = marketplace;
201
- }
202
- else {
203
- registry.marketplaces.push(marketplace);
204
- }
205
- // Ensure builtin is included if we are creating the file for the first time
206
- // and it hasn't been explicitly removed yet.
207
- // (getKnownMarketplaces already handles the default injection)
208
- await this.saveKnownMarketplaces(registry);
209
- return marketplace;
253
+ // Ensure builtin is included if we are creating the file for the first time
254
+ // and it hasn't been explicitly removed yet.
255
+ // (getKnownMarketplaces already handles the default injection)
256
+ await this.saveKnownMarketplaces(registry);
257
+ return marketplace;
258
+ });
210
259
  }
211
260
  /**
212
261
  * Lists all registered marketplaces
@@ -222,244 +271,264 @@ export class MarketplaceService {
222
271
  * Removes a marketplace by name
223
272
  */
224
273
  async removeMarketplace(name) {
225
- const registry = await this.getKnownMarketplaces();
226
- const initialCount = registry.marketplaces.length;
227
- registry.marketplaces = registry.marketplaces.filter((m) => m.name !== name);
228
- if (registry.marketplaces.length === initialCount) {
229
- throw new Error(`Marketplace ${name} not found`);
230
- }
231
- await this.saveKnownMarketplaces(registry);
274
+ return this.withLock(async () => {
275
+ const registry = await this.getKnownMarketplaces();
276
+ const initialCount = registry.marketplaces.length;
277
+ registry.marketplaces = registry.marketplaces.filter((m) => m.name !== name);
278
+ if (registry.marketplaces.length === initialCount) {
279
+ throw new Error(`Marketplace ${name} not found`);
280
+ }
281
+ await this.saveKnownMarketplaces(registry);
282
+ });
232
283
  }
233
284
  /**
234
285
  * Updates a specific marketplace or all marketplaces
235
286
  */
236
287
  async updateMarketplace(name, options) {
237
- const registry = await this.getKnownMarketplaces();
238
- const toUpdate = name
239
- ? registry.marketplaces.filter((m) => m.name === name)
240
- : registry.marketplaces;
241
- if (name && toUpdate.length === 0) {
242
- throw new Error(`Marketplace ${name} not found`);
243
- }
244
- const isGitAvailable = await this.gitService.isGitAvailable();
245
- const errors = [];
246
- for (const marketplace of toUpdate) {
247
- try {
248
- if (marketplace.source.source === "github" ||
249
- marketplace.source.source === "git") {
250
- if (!isGitAvailable) {
251
- console.warn(`Skipping update for Git/GitHub marketplace "${marketplace.name}" because Git is not installed.`);
252
- continue;
253
- }
254
- const targetPath = this.getMarketplacePath(marketplace);
255
- if (existsSync(targetPath)) {
256
- await this.gitService.pull(targetPath);
257
- }
258
- else {
259
- let url;
260
- if (marketplace.source.source === "github") {
261
- url = marketplace.source.repo;
288
+ return this.withLock(async () => {
289
+ const registry = await this.getKnownMarketplaces();
290
+ const toUpdate = name
291
+ ? registry.marketplaces.filter((m) => m.name === name)
292
+ : registry.marketplaces;
293
+ if (name && toUpdate.length === 0) {
294
+ throw new Error(`Marketplace ${name} not found`);
295
+ }
296
+ const isGitAvailable = await this.gitService.isGitAvailable();
297
+ const errors = [];
298
+ for (const marketplace of toUpdate) {
299
+ try {
300
+ if (marketplace.source.source === "github" ||
301
+ marketplace.source.source === "git") {
302
+ if (!isGitAvailable) {
303
+ console.warn(`Skipping update for Git/GitHub marketplace "${marketplace.name}" because Git is not installed.`);
304
+ continue;
305
+ }
306
+ const targetPath = this.getMarketplacePath(marketplace);
307
+ if (existsSync(targetPath)) {
308
+ await this.gitService.pull(targetPath);
262
309
  }
263
310
  else {
264
- url = marketplace.source.url;
311
+ let url;
312
+ if (marketplace.source.source === "github") {
313
+ url = marketplace.source.repo;
314
+ }
315
+ else {
316
+ url = marketplace.source.url;
317
+ }
318
+ await this.gitService.clone(url, targetPath, marketplace.source.ref);
265
319
  }
266
- await this.gitService.clone(url, targetPath, marketplace.source.ref);
267
320
  }
268
- }
269
- // For directory source, we just re-validate the manifest
270
- const manifest = await this.loadMarketplaceManifest(this.getMarketplacePath(marketplace));
271
- marketplace.lastUpdated = new Date().toISOString();
272
- if (options?.updatePlugins) {
273
- const installedRegistry = await this.getInstalledPlugins();
274
- const pluginsToUpdate = installedRegistry.plugins.filter((p) => p.marketplace === marketplace.name);
275
- for (const plugin of pluginsToUpdate) {
276
- const pluginEntry = manifest.plugins.find((p) => p.name === plugin.name);
277
- if (!pluginEntry) {
278
- console.warn(`Plugin "${plugin.name}" no longer found in marketplace "${marketplace.name}". Uninstalling...`);
321
+ // For directory source, we just re-validate the manifest
322
+ const manifest = await this.loadMarketplaceManifest(this.getMarketplacePath(marketplace));
323
+ marketplace.lastUpdated = new Date().toISOString();
324
+ if (options?.updatePlugins) {
325
+ const installedRegistry = await this.getInstalledPlugins();
326
+ const pluginsToUpdate = installedRegistry.plugins.filter((p) => p.marketplace === marketplace.name);
327
+ for (const plugin of pluginsToUpdate) {
328
+ const pluginEntry = manifest.plugins.find((p) => p.name === plugin.name);
329
+ if (!pluginEntry) {
330
+ console.warn(`Plugin "${plugin.name}" no longer found in marketplace "${marketplace.name}". Uninstalling...`);
331
+ try {
332
+ await this.uninstallPlugin(`${plugin.name}@${plugin.marketplace}`, plugin.projectPath);
333
+ }
334
+ catch (error) {
335
+ console.error(`Failed to uninstall orphaned plugin "${plugin.name}" from marketplace "${marketplace.name}":`, error);
336
+ }
337
+ continue;
338
+ }
279
339
  try {
280
- await this.uninstallPlugin(`${plugin.name}@${plugin.marketplace}`, plugin.projectPath);
340
+ await this.installPlugin(`${plugin.name}@${plugin.marketplace}`, plugin.projectPath);
281
341
  }
282
342
  catch (error) {
283
- console.error(`Failed to uninstall orphaned plugin "${plugin.name}" from marketplace "${marketplace.name}":`, error);
343
+ console.error(`Failed to update plugin "${plugin.name}" from marketplace "${marketplace.name}":`, error);
284
344
  }
285
- continue;
286
- }
287
- try {
288
- await this.installPlugin(`${plugin.name}@${plugin.marketplace}`, plugin.projectPath);
289
- }
290
- catch (error) {
291
- console.error(`Failed to update plugin "${plugin.name}" from marketplace "${marketplace.name}":`, error);
292
345
  }
293
346
  }
294
347
  }
348
+ catch (error) {
349
+ const msg = `Failed to update marketplace "${marketplace.name}": ${error instanceof Error ? error.message : String(error)}`;
350
+ console.error(msg);
351
+ errors.push(msg);
352
+ }
295
353
  }
296
- catch (error) {
297
- const msg = `Failed to update marketplace "${marketplace.name}": ${error instanceof Error ? error.message : String(error)}`;
298
- console.error(msg);
299
- errors.push(msg);
354
+ if (errors.length > 0) {
355
+ throw new Error(`Some marketplaces failed to update:\n${errors.join("\n")}`);
300
356
  }
301
- }
302
- if (errors.length > 0) {
303
- throw new Error(`Some marketplaces failed to update:\n${errors.join("\n")}`);
304
- }
305
- await this.saveKnownMarketplaces(registry);
357
+ await this.saveKnownMarketplaces(registry);
358
+ });
306
359
  }
307
360
  /**
308
361
  * Automatically updates all marketplaces that have auto-update enabled
309
362
  */
310
363
  async autoUpdateAll() {
311
- const registry = await this.getKnownMarketplaces();
312
- const toAutoUpdate = registry.marketplaces.filter((m) => m.autoUpdate);
313
- for (const marketplace of toAutoUpdate) {
314
- try {
315
- await this.updateMarketplace(marketplace.name, { updatePlugins: true });
316
- }
317
- catch (error) {
318
- console.error(`Auto-update failed for marketplace "${marketplace.name}":`, error);
364
+ return this.withLock(async () => {
365
+ const registry = await this.getKnownMarketplaces();
366
+ const toAutoUpdate = registry.marketplaces.filter((m) => m.autoUpdate);
367
+ for (const marketplace of toAutoUpdate) {
368
+ try {
369
+ await this.updateMarketplace(marketplace.name, {
370
+ updatePlugins: true,
371
+ });
372
+ }
373
+ catch (error) {
374
+ console.error(`Auto-update failed for marketplace "${marketplace.name}":`, error);
375
+ }
319
376
  }
320
- }
377
+ });
321
378
  }
322
379
  /**
323
380
  * Toggles auto-update for a marketplace
324
381
  */
325
382
  async toggleAutoUpdate(name, enabled) {
326
- const registry = await this.getKnownMarketplaces();
327
- const marketplace = registry.marketplaces.find((m) => m.name === name);
328
- if (!marketplace) {
329
- throw new Error(`Marketplace ${name} not found`);
330
- }
331
- marketplace.autoUpdate = enabled;
332
- await this.saveKnownMarketplaces(registry);
383
+ return this.withLock(async () => {
384
+ const registry = await this.getKnownMarketplaces();
385
+ const marketplace = registry.marketplaces.find((m) => m.name === name);
386
+ if (!marketplace) {
387
+ throw new Error(`Marketplace ${name} not found`);
388
+ }
389
+ marketplace.autoUpdate = enabled;
390
+ await this.saveKnownMarketplaces(registry);
391
+ });
333
392
  }
334
393
  /**
335
394
  * Installs a plugin from a marketplace
336
395
  */
337
396
  async installPlugin(pluginAtMarketplace, projectPath) {
338
- const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
339
- if (!pluginName || !marketplaceName) {
340
- throw new Error("Invalid plugin format. Use name@marketplace");
341
- }
342
- const marketplaces = await this.listMarketplaces();
343
- const marketplace = marketplaces.find((m) => m.name === marketplaceName);
344
- if (!marketplace) {
345
- throw new Error(`Marketplace ${marketplaceName} not found`);
346
- }
347
- const marketplacePath = this.getMarketplacePath(marketplace);
348
- const manifest = await this.loadMarketplaceManifest(marketplacePath);
349
- const pluginEntry = manifest.plugins.find((p) => p.name === pluginName);
350
- if (!pluginEntry) {
351
- throw new Error(`Plugin ${pluginName} not found in marketplace ${marketplaceName}`);
352
- }
353
- const isGitSource = pluginEntry.source.startsWith("http://") ||
354
- pluginEntry.source.startsWith("https://") ||
355
- pluginEntry.source.startsWith("git@") ||
356
- pluginEntry.source.startsWith("ssh://");
357
- let pluginSrcPath;
358
- let tempCloneDir;
359
- if (isGitSource) {
360
- tempCloneDir = path.join(this.tmpDir, `clone-${Date.now()}`);
361
- let url = pluginEntry.source;
362
- let ref;
363
- if (url.includes("#")) {
364
- [url, ref] = url.split("#");
397
+ return this.withLock(async () => {
398
+ const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
399
+ if (!pluginName || !marketplaceName) {
400
+ throw new Error("Invalid plugin format. Use name@marketplace");
365
401
  }
366
- await this.gitService.clone(url, tempCloneDir, ref);
367
- pluginSrcPath = tempCloneDir;
368
- }
369
- else {
370
- pluginSrcPath = path.resolve(marketplacePath, pluginEntry.source);
371
- }
372
- const pluginManifestPath = path.join(pluginSrcPath, ".wave-plugin", "plugin.json");
373
- if (!existsSync(pluginManifestPath)) {
374
- if (tempCloneDir) {
375
- await fs.rm(tempCloneDir, { recursive: true, force: true });
402
+ const marketplaces = await this.listMarketplaces();
403
+ const marketplace = marketplaces.find((m) => m.name === marketplaceName);
404
+ if (!marketplace) {
405
+ throw new Error(`Marketplace ${marketplaceName} not found`);
376
406
  }
377
- throw new Error(`Plugin manifest not found at ${pluginManifestPath}`);
378
- }
379
- const pluginManifestContent = await fs.readFile(pluginManifestPath, "utf-8");
380
- const pluginManifest = JSON.parse(pluginManifestContent);
381
- const version = pluginManifest.version || "1.0.0";
382
- // Atomic installation
383
- const tmpPluginDir = path.join(this.tmpDir, `${pluginName}-${Date.now()}`);
384
- try {
385
- if (isGitSource) {
386
- await fs.rename(pluginSrcPath, tmpPluginDir);
387
- tempCloneDir = undefined; // Already moved
407
+ const marketplacePath = this.getMarketplacePath(marketplace);
408
+ const manifest = await this.loadMarketplaceManifest(marketplacePath);
409
+ const pluginEntry = manifest.plugins.find((p) => p.name === pluginName);
410
+ if (!pluginEntry) {
411
+ throw new Error(`Plugin ${pluginName} not found in marketplace ${marketplaceName}`);
388
412
  }
389
- else {
390
- await fs.cp(pluginSrcPath, tmpPluginDir, { recursive: true });
413
+ const isGitSource = pluginEntry.source.startsWith("http://") ||
414
+ pluginEntry.source.startsWith("https://") ||
415
+ pluginEntry.source.startsWith("git@") ||
416
+ pluginEntry.source.startsWith("ssh://");
417
+ let pluginSrcPath;
418
+ let tempCloneDir;
419
+ try {
420
+ if (isGitSource) {
421
+ tempCloneDir = path.join(this.tmpDir, `clone-${Date.now()}`);
422
+ let url = pluginEntry.source;
423
+ let ref;
424
+ if (url.includes("#")) {
425
+ [url, ref] = url.split("#");
426
+ }
427
+ await this.gitService.clone(url, tempCloneDir, ref);
428
+ pluginSrcPath = tempCloneDir;
429
+ }
430
+ else {
431
+ pluginSrcPath = path.resolve(marketplacePath, pluginEntry.source);
432
+ }
433
+ const pluginManifestPath = path.join(pluginSrcPath, ".wave-plugin", "plugin.json");
434
+ if (!existsSync(pluginManifestPath)) {
435
+ throw new Error(`Plugin manifest not found at ${pluginManifestPath}`);
436
+ }
437
+ const pluginManifestContent = await fs.readFile(pluginManifestPath, "utf-8");
438
+ const pluginManifest = JSON.parse(pluginManifestContent);
439
+ const version = pluginManifest.version || "1.0.0";
440
+ // Atomic installation
441
+ const tmpPluginDir = path.join(this.tmpDir, `${pluginName}-${Date.now()}`);
442
+ try {
443
+ if (isGitSource) {
444
+ await fs.rename(pluginSrcPath, tmpPluginDir);
445
+ tempCloneDir = undefined; // Already moved
446
+ }
447
+ else {
448
+ await fs.cp(pluginSrcPath, tmpPluginDir, { recursive: true });
449
+ }
450
+ const cachePath = path.join(this.cacheDir, marketplaceName, pluginName, version);
451
+ if (existsSync(cachePath)) {
452
+ await fs.rm(cachePath, { recursive: true, force: true });
453
+ }
454
+ await fs.mkdir(path.dirname(cachePath), { recursive: true });
455
+ await fs.rename(tmpPluginDir, cachePath);
456
+ const installedRegistry = await this.getInstalledPlugins();
457
+ const existingIndex = installedRegistry.plugins.findIndex((p) => p.name === pluginName &&
458
+ p.marketplace === marketplaceName &&
459
+ p.projectPath === projectPath);
460
+ const installedPlugin = {
461
+ name: pluginName,
462
+ marketplace: marketplaceName,
463
+ version,
464
+ cachePath,
465
+ projectPath,
466
+ };
467
+ if (existingIndex >= 0) {
468
+ installedRegistry.plugins[existingIndex] = installedPlugin;
469
+ }
470
+ else {
471
+ installedRegistry.plugins.push(installedPlugin);
472
+ }
473
+ await this.saveInstalledPlugins(installedRegistry);
474
+ return installedPlugin;
475
+ }
476
+ catch (error) {
477
+ // Cleanup tmp dir if it exists
478
+ if (existsSync(tmpPluginDir)) {
479
+ await fs.rm(tmpPluginDir, { recursive: true, force: true });
480
+ }
481
+ throw error;
482
+ }
391
483
  }
392
- const cachePath = path.join(this.cacheDir, marketplaceName, pluginName, version);
393
- if (existsSync(cachePath)) {
394
- await fs.rm(cachePath, { recursive: true, force: true });
484
+ catch (error) {
485
+ // Cleanup temp clone dir if it exists
486
+ if (tempCloneDir && existsSync(tempCloneDir)) {
487
+ await fs.rm(tempCloneDir, { recursive: true, force: true });
488
+ }
489
+ throw new Error(`Failed to install plugin ${pluginName}: ${error instanceof Error ? error.message : String(error)}`);
490
+ }
491
+ });
492
+ }
493
+ /**
494
+ * Uninstalls a plugin
495
+ */
496
+ async uninstallPlugin(pluginAtMarketplace, projectPath) {
497
+ return this.withLock(async () => {
498
+ const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
499
+ if (!pluginName || !marketplaceName) {
500
+ throw new Error("Invalid plugin format. Use name@marketplace");
395
501
  }
396
- await fs.mkdir(path.dirname(cachePath), { recursive: true });
397
- await fs.rename(tmpPluginDir, cachePath);
398
502
  const installedRegistry = await this.getInstalledPlugins();
399
- const existingIndex = installedRegistry.plugins.findIndex((p) => p.name === pluginName &&
503
+ const pluginIndex = installedRegistry.plugins.findIndex((p) => p.name === pluginName &&
400
504
  p.marketplace === marketplaceName &&
401
505
  p.projectPath === projectPath);
402
- const installedPlugin = {
403
- name: pluginName,
404
- marketplace: marketplaceName,
405
- version,
406
- cachePath,
407
- projectPath,
408
- };
409
- if (existingIndex >= 0) {
410
- installedRegistry.plugins[existingIndex] = installedPlugin;
411
- }
412
- else {
413
- installedRegistry.plugins.push(installedPlugin);
506
+ if (pluginIndex === -1) {
507
+ throw new Error(`Plugin ${pluginName}@${marketplaceName} is not installed${projectPath ? ` for project ${projectPath}` : ""}`);
414
508
  }
509
+ const pluginToRemove = installedRegistry.plugins[pluginIndex];
510
+ // Remove from registry first
511
+ installedRegistry.plugins.splice(pluginIndex, 1);
415
512
  await this.saveInstalledPlugins(installedRegistry);
416
- return installedPlugin;
417
- }
418
- catch (error) {
419
- // Cleanup tmp dirs if they exist
420
- if (existsSync(tmpPluginDir)) {
421
- await fs.rm(tmpPluginDir, { recursive: true, force: true });
513
+ // Check if any other project is still using this same cache path
514
+ const isStillReferenced = installedRegistry.plugins.some((p) => p.cachePath === pluginToRemove.cachePath);
515
+ // Only remove cached files if no other references exist
516
+ if (!isStillReferenced && existsSync(pluginToRemove.cachePath)) {
517
+ await fs.rm(pluginToRemove.cachePath, { recursive: true, force: true });
422
518
  }
423
- if (tempCloneDir && existsSync(tempCloneDir)) {
424
- await fs.rm(tempCloneDir, { recursive: true, force: true });
425
- }
426
- throw new Error(`Failed to install plugin ${pluginName}: ${error instanceof Error ? error.message : String(error)}`);
427
- }
428
- }
429
- /**
430
- * Uninstalls a plugin
431
- */
432
- async uninstallPlugin(pluginAtMarketplace, projectPath) {
433
- const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
434
- if (!pluginName || !marketplaceName) {
435
- throw new Error("Invalid plugin format. Use name@marketplace");
436
- }
437
- const installedRegistry = await this.getInstalledPlugins();
438
- const pluginIndex = installedRegistry.plugins.findIndex((p) => p.name === pluginName &&
439
- p.marketplace === marketplaceName &&
440
- p.projectPath === projectPath);
441
- if (pluginIndex === -1) {
442
- throw new Error(`Plugin ${pluginName}@${marketplaceName} is not installed${projectPath ? ` for project ${projectPath}` : ""}`);
443
- }
444
- const pluginToRemove = installedRegistry.plugins[pluginIndex];
445
- // Remove from registry first
446
- installedRegistry.plugins.splice(pluginIndex, 1);
447
- await this.saveInstalledPlugins(installedRegistry);
448
- // Check if any other project is still using this same cache path
449
- const isStillReferenced = installedRegistry.plugins.some((p) => p.cachePath === pluginToRemove.cachePath);
450
- // Only remove cached files if no other references exist
451
- if (!isStillReferenced && existsSync(pluginToRemove.cachePath)) {
452
- await fs.rm(pluginToRemove.cachePath, { recursive: true, force: true });
453
- }
519
+ });
454
520
  }
455
521
  /**
456
522
  * Updates a plugin (uninstall followed by install)
457
523
  */
458
524
  async updatePlugin(pluginAtMarketplace) {
459
- await this.uninstallPlugin(pluginAtMarketplace);
460
- return this.installPlugin(pluginAtMarketplace);
525
+ return this.withLock(async () => {
526
+ await this.uninstallPlugin(pluginAtMarketplace);
527
+ return this.installPlugin(pluginAtMarketplace);
528
+ });
461
529
  }
462
530
  }
531
+ MarketplaceService.isLockedInProcess = false;
463
532
  MarketplaceService.BUILTIN_MARKETPLACE = {
464
533
  name: "wave-plugins-official",
465
534
  source: {