wave-agent-sdk 0.14.0 → 0.14.2
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.
- package/builtin/skills/settings/HOOKS.md +69 -0
- package/builtin/skills/settings/PLUGINS.md +171 -0
- package/builtin/skills/settings/SKILL.md +8 -3
- package/dist/agent.d.ts +2 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +12 -3
- package/dist/core/plugin.d.ts +2 -2
- package/dist/core/plugin.d.ts.map +1 -1
- package/dist/core/plugin.js +7 -7
- package/dist/managers/aiManager.d.ts +6 -6
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +122 -59
- package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
- package/dist/managers/backgroundTaskManager.js +28 -30
- package/dist/managers/hookManager.d.ts +16 -1
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +97 -8
- package/dist/managers/messageManager.d.ts +19 -4
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +63 -18
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +1 -1
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +1 -1
- package/dist/services/MarketplaceService.d.ts +42 -12
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +225 -105
- package/dist/services/aiService.d.ts +3 -3
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +7 -7
- package/dist/services/configurationService.d.ts +17 -1
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +104 -0
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +15 -0
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +24 -1
- package/dist/services/interactionService.js +1 -1
- package/dist/services/pluginLoader.d.ts.map +1 -1
- package/dist/services/pluginLoader.js +7 -1
- package/dist/services/session.d.ts +1 -1
- package/dist/services/session.js +7 -7
- package/dist/services/taskManager.d.ts +1 -1
- package/dist/services/taskManager.js +1 -1
- package/dist/types/configuration.d.ts +7 -0
- package/dist/types/configuration.d.ts.map +1 -1
- package/dist/types/core.d.ts +1 -1
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +9 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +2 -0
- package/dist/types/marketplace.d.ts +2 -0
- package/dist/types/marketplace.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +3 -3
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts +1 -1
- package/dist/utils/convertMessagesForAPI.js +7 -7
- package/dist/utils/groupMessagesByApiRound.d.ts +1 -1
- package/dist/utils/groupMessagesByApiRound.js +6 -6
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +3 -3
- package/package.json +1 -1
- package/src/agent.ts +16 -3
- package/src/core/plugin.ts +13 -7
- package/src/managers/aiManager.ts +142 -63
- package/src/managers/backgroundTaskManager.ts +33 -42
- package/src/managers/hookManager.ts +125 -10
- package/src/managers/messageManager.ts +76 -22
- package/src/managers/pluginManager.ts +4 -1
- package/src/prompts/index.ts +1 -1
- package/src/services/MarketplaceService.ts +301 -111
- package/src/services/aiService.ts +11 -11
- package/src/services/configurationService.ts +131 -0
- package/src/services/hook.ts +17 -0
- package/src/services/initializationService.ts +33 -1
- package/src/services/interactionService.ts +1 -1
- package/src/services/pluginLoader.ts +7 -1
- package/src/services/session.ts +7 -7
- package/src/services/taskManager.ts +1 -1
- package/src/types/configuration.ts +8 -0
- package/src/types/core.ts +1 -1
- package/src/types/hooks.ts +16 -2
- package/src/types/marketplace.ts +2 -0
- package/src/types/messaging.ts +3 -3
- package/src/utils/convertMessagesForAPI.ts +8 -8
- package/src/utils/groupMessagesByApiRound.ts +6 -6
- package/src/utils/messageOperations.ts +3 -5
|
@@ -8,14 +8,21 @@ import {
|
|
|
8
8
|
InstalledPlugin,
|
|
9
9
|
InstalledPluginsRegistry,
|
|
10
10
|
MarketplaceManifest,
|
|
11
|
+
MarketplaceSource,
|
|
11
12
|
} from "../types/marketplace.js";
|
|
12
13
|
import { GitService } from "./GitService.js";
|
|
14
|
+
import { ConfigurationService } from "./configurationService.js";
|
|
15
|
+
import type { MarketplaceConfig, Scope } from "../types/configuration.js";
|
|
16
|
+
import { logger } from "../utils/globalLogger.js";
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Marketplace Service
|
|
16
20
|
*
|
|
17
21
|
* Handles local plugin marketplace registration, plugin installation,
|
|
18
22
|
* and state management for installed plugins.
|
|
23
|
+
*
|
|
24
|
+
* Marketplace declarations are now scoped (user/project/local) via settings files.
|
|
25
|
+
* known_marketplaces.json is kept as a cache for installLocation/lastUpdated metadata.
|
|
19
26
|
*/
|
|
20
27
|
export class MarketplaceService {
|
|
21
28
|
private static isLockedInProcess = false;
|
|
@@ -27,6 +34,8 @@ export class MarketplaceService {
|
|
|
27
34
|
private cacheDir: string;
|
|
28
35
|
private marketplacesDir: string;
|
|
29
36
|
private gitService: GitService;
|
|
37
|
+
private configurationService: ConfigurationService;
|
|
38
|
+
private workdir: string;
|
|
30
39
|
private static readonly BUILTIN_MARKETPLACE: KnownMarketplace = {
|
|
31
40
|
name: "wave-plugins-official",
|
|
32
41
|
source: {
|
|
@@ -36,7 +45,12 @@ export class MarketplaceService {
|
|
|
36
45
|
autoUpdate: true,
|
|
37
46
|
};
|
|
38
47
|
|
|
39
|
-
constructor(
|
|
48
|
+
constructor(
|
|
49
|
+
workdir: string = process.cwd(),
|
|
50
|
+
configurationService: ConfigurationService = new ConfigurationService(),
|
|
51
|
+
) {
|
|
52
|
+
this.workdir = workdir;
|
|
53
|
+
this.configurationService = configurationService;
|
|
40
54
|
this.pluginsDir = getPluginsDir();
|
|
41
55
|
this.knownMarketplacesPath = path.join(
|
|
42
56
|
this.pluginsDir,
|
|
@@ -53,6 +67,7 @@ export class MarketplaceService {
|
|
|
53
67
|
this.gitService = new GitService();
|
|
54
68
|
|
|
55
69
|
this.ensureDirectoryStructure();
|
|
70
|
+
this.runMigration();
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
/**
|
|
@@ -68,6 +83,44 @@ export class MarketplaceService {
|
|
|
68
83
|
);
|
|
69
84
|
}
|
|
70
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Backwards compatibility migration: migrate entries from known_marketplaces.json
|
|
88
|
+
* to user-level settings if they aren't already declared there.
|
|
89
|
+
*/
|
|
90
|
+
private async runMigration(): Promise<void> {
|
|
91
|
+
try {
|
|
92
|
+
const cacheRegistry = await this.getCacheRegistry();
|
|
93
|
+
const scopedMarketplaces =
|
|
94
|
+
this.configurationService.getMergedMarketplaces(this.workdir);
|
|
95
|
+
const userMarketplaces = this.configurationService.getScopedMarketplaces(
|
|
96
|
+
this.workdir,
|
|
97
|
+
"user",
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const entriesToMigrate = (cacheRegistry?.marketplaces ?? []).filter(
|
|
101
|
+
(m) => !scopedMarketplaces[m.name] && !userMarketplaces[m.name],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (entriesToMigrate.length > 0) {
|
|
105
|
+
for (const m of entriesToMigrate) {
|
|
106
|
+
if (m.name === MarketplaceService.BUILTIN_MARKETPLACE.name) continue;
|
|
107
|
+
const config: MarketplaceConfig = {
|
|
108
|
+
source: m.source,
|
|
109
|
+
autoUpdate: m.autoUpdate,
|
|
110
|
+
};
|
|
111
|
+
await this.configurationService.addMarketplaceToScope(
|
|
112
|
+
this.workdir,
|
|
113
|
+
"user",
|
|
114
|
+
m.name,
|
|
115
|
+
config,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Migration failure should not block startup
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
71
124
|
/**
|
|
72
125
|
* Check if a lock file is stale by reading its PID and checking if the process is alive.
|
|
73
126
|
* Returns true if the lock is stale and safe to remove.
|
|
@@ -77,15 +130,14 @@ export class MarketplaceService {
|
|
|
77
130
|
const content = await fs.readFile(this.lockPath, "utf-8");
|
|
78
131
|
const pid = parseInt(content.trim(), 10);
|
|
79
132
|
if (isNaN(pid)) return true;
|
|
80
|
-
// Check if the process is still running
|
|
81
133
|
try {
|
|
82
134
|
process.kill(pid, 0);
|
|
83
|
-
return false;
|
|
135
|
+
return false;
|
|
84
136
|
} catch {
|
|
85
|
-
return true;
|
|
137
|
+
return true;
|
|
86
138
|
}
|
|
87
139
|
} catch {
|
|
88
|
-
return true;
|
|
140
|
+
return true;
|
|
89
141
|
}
|
|
90
142
|
}
|
|
91
143
|
|
|
@@ -99,7 +151,7 @@ export class MarketplaceService {
|
|
|
99
151
|
}
|
|
100
152
|
|
|
101
153
|
let lockFd: Awaited<ReturnType<typeof fs.open>> | undefined;
|
|
102
|
-
const maxRetries = 600;
|
|
154
|
+
const maxRetries = 600;
|
|
103
155
|
const retryDelay = 100;
|
|
104
156
|
|
|
105
157
|
for (let i = 0; i < maxRetries; i++) {
|
|
@@ -113,7 +165,6 @@ export class MarketplaceService {
|
|
|
113
165
|
"code" in error &&
|
|
114
166
|
error.code === "EEXIST"
|
|
115
167
|
) {
|
|
116
|
-
// Check for stale lock every 60 retries (every ~6 seconds)
|
|
117
168
|
if (i > 0 && i % 60 === 0) {
|
|
118
169
|
const stale = await this.isStaleLock();
|
|
119
170
|
if (stale) {
|
|
@@ -134,7 +185,6 @@ export class MarketplaceService {
|
|
|
134
185
|
);
|
|
135
186
|
}
|
|
136
187
|
|
|
137
|
-
// Write PID into the lock file for stale lock detection
|
|
138
188
|
await fs.writeFile(this.lockPath, String(process.pid), "utf-8");
|
|
139
189
|
|
|
140
190
|
MarketplaceService.isLockedInProcess = true;
|
|
@@ -148,36 +198,96 @@ export class MarketplaceService {
|
|
|
148
198
|
}
|
|
149
199
|
|
|
150
200
|
/**
|
|
151
|
-
* Loads the
|
|
201
|
+
* Loads the cache registry from known_marketplaces.json
|
|
202
|
+
* Returns null if the file doesn't exist or has no parseable content (for first-run detection).
|
|
152
203
|
*/
|
|
153
|
-
async
|
|
204
|
+
async getCacheRegistry(): Promise<KnownMarketplacesRegistry | null> {
|
|
154
205
|
if (!existsSync(this.knownMarketplacesPath)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const content = await fs.readFile(this.knownMarketplacesPath, "utf-8");
|
|
210
|
+
if (!content.trim()) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return JSON.parse(content);
|
|
214
|
+
} catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Legacy method: loads known marketplaces with builtin injection.
|
|
221
|
+
* @deprecated Use listMarketplaces() instead, which combines scoped settings.
|
|
222
|
+
*/
|
|
223
|
+
async getKnownMarketplaces(): Promise<KnownMarketplacesRegistry> {
|
|
224
|
+
const cache = await this.getCacheRegistry();
|
|
225
|
+
// If cache is null (file doesn't exist or has no parseable content), inject builtin
|
|
226
|
+
if (cache === null) {
|
|
155
227
|
return {
|
|
156
228
|
marketplaces: [
|
|
157
229
|
{
|
|
158
230
|
...MarketplaceService.BUILTIN_MARKETPLACE,
|
|
159
231
|
isBuiltin: true,
|
|
232
|
+
declaredScope: "builtin",
|
|
160
233
|
},
|
|
161
234
|
],
|
|
162
235
|
};
|
|
163
236
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
237
|
+
// File has valid JSON - respect user's explicit choice even if empty
|
|
238
|
+
const hasBuiltin = cache.marketplaces.some(
|
|
239
|
+
(m) => m.name === MarketplaceService.BUILTIN_MARKETPLACE.name,
|
|
240
|
+
);
|
|
241
|
+
return {
|
|
242
|
+
marketplaces: cache.marketplaces.map((m) => ({
|
|
243
|
+
...m,
|
|
244
|
+
isBuiltin:
|
|
245
|
+
m.isBuiltin || m.name === MarketplaceService.BUILTIN_MARKETPLACE.name,
|
|
246
|
+
declaredScope: m.declaredScope ?? (hasBuiltin ? "builtin" : "user"),
|
|
247
|
+
})),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Updates the cache registry with metadata for a marketplace
|
|
253
|
+
*/
|
|
254
|
+
private async updateCacheMarketplace(
|
|
255
|
+
name: string,
|
|
256
|
+
metadata: Partial<KnownMarketplace>,
|
|
257
|
+
): Promise<void> {
|
|
258
|
+
const registry = await this.getCacheRegistry();
|
|
259
|
+
const marketplaces = registry?.marketplaces ?? [];
|
|
260
|
+
const existingIndex = marketplaces.findIndex((m) => m.name === name);
|
|
261
|
+
if (existingIndex >= 0) {
|
|
262
|
+
marketplaces[existingIndex] = {
|
|
263
|
+
...marketplaces[existingIndex],
|
|
264
|
+
...metadata,
|
|
265
|
+
};
|
|
266
|
+
} else {
|
|
267
|
+
marketplaces.push({
|
|
268
|
+
name,
|
|
269
|
+
source: metadata.source || { source: "directory", path: "" },
|
|
270
|
+
...metadata,
|
|
271
|
+
});
|
|
180
272
|
}
|
|
273
|
+
const tmpPath = `${this.knownMarketplacesPath}.tmp`;
|
|
274
|
+
await fs.writeFile(tmpPath, JSON.stringify({ marketplaces }, null, 2));
|
|
275
|
+
await fs.rename(tmpPath, this.knownMarketplacesPath);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Removes a marketplace from the cache registry
|
|
280
|
+
*/
|
|
281
|
+
private async removeFromCache(name: string): Promise<void> {
|
|
282
|
+
const registry = await this.getCacheRegistry();
|
|
283
|
+
const marketplaces = registry?.marketplaces ?? [];
|
|
284
|
+
const filtered = marketplaces.filter((m) => m.name !== name);
|
|
285
|
+
const tmpPath = `${this.knownMarketplacesPath}.tmp`;
|
|
286
|
+
await fs.writeFile(
|
|
287
|
+
tmpPath,
|
|
288
|
+
JSON.stringify({ marketplaces: filtered }, null, 2),
|
|
289
|
+
);
|
|
290
|
+
await fs.rename(tmpPath, this.knownMarketplacesPath);
|
|
181
291
|
}
|
|
182
292
|
|
|
183
293
|
/**
|
|
@@ -199,17 +309,6 @@ export class MarketplaceService {
|
|
|
199
309
|
}
|
|
200
310
|
}
|
|
201
311
|
|
|
202
|
-
/**
|
|
203
|
-
* Saves the known marketplaces registry
|
|
204
|
-
*/
|
|
205
|
-
async saveKnownMarketplaces(
|
|
206
|
-
registry: KnownMarketplacesRegistry,
|
|
207
|
-
): Promise<void> {
|
|
208
|
-
const tmpPath = `${this.knownMarketplacesPath}.tmp`;
|
|
209
|
-
await fs.writeFile(tmpPath, JSON.stringify(registry, null, 2));
|
|
210
|
-
await fs.rename(tmpPath, this.knownMarketplacesPath);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
312
|
/**
|
|
214
313
|
* Saves the installed plugins registry
|
|
215
314
|
*/
|
|
@@ -238,7 +337,6 @@ export class MarketplaceService {
|
|
|
238
337
|
const content = await fs.readFile(manifestPath, "utf-8");
|
|
239
338
|
const manifest = JSON.parse(content);
|
|
240
339
|
|
|
241
|
-
// Basic validation
|
|
242
340
|
if (
|
|
243
341
|
!manifest.name ||
|
|
244
342
|
!manifest.plugins ||
|
|
@@ -253,25 +351,60 @@ export class MarketplaceService {
|
|
|
253
351
|
/**
|
|
254
352
|
* Resolves the local path for a marketplace
|
|
255
353
|
*/
|
|
256
|
-
public getMarketplacePath(
|
|
257
|
-
if (
|
|
258
|
-
return
|
|
259
|
-
} else if (
|
|
260
|
-
return path.join(this.marketplacesDir,
|
|
354
|
+
public getMarketplacePath(source: MarketplaceSource): string {
|
|
355
|
+
if (source.source === "directory") {
|
|
356
|
+
return source.path;
|
|
357
|
+
} else if (source.source === "github") {
|
|
358
|
+
return path.join(this.marketplacesDir, source.repo);
|
|
261
359
|
} else {
|
|
262
|
-
|
|
263
|
-
const hash = crypto
|
|
264
|
-
.createHash("md5")
|
|
265
|
-
.update(marketplace.source.url)
|
|
266
|
-
.digest("hex");
|
|
360
|
+
const hash = crypto.createHash("md5").update(source.url).digest("hex");
|
|
267
361
|
return path.join(this.marketplacesDir, hash);
|
|
268
362
|
}
|
|
269
363
|
}
|
|
270
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Builds a KnownMarketplace from a scoped config, enriched with cache metadata
|
|
367
|
+
*/
|
|
368
|
+
private async buildMarketplaceEntry(
|
|
369
|
+
name: string,
|
|
370
|
+
config: MarketplaceConfig,
|
|
371
|
+
cache: KnownMarketplace | undefined,
|
|
372
|
+
declaredScope: "user" | "project" | "local" | "builtin",
|
|
373
|
+
): Promise<KnownMarketplace> {
|
|
374
|
+
return {
|
|
375
|
+
name,
|
|
376
|
+
source: config.source,
|
|
377
|
+
autoUpdate: config.autoUpdate ?? cache?.autoUpdate,
|
|
378
|
+
lastUpdated: cache?.lastUpdated,
|
|
379
|
+
isBuiltin: name === MarketplaceService.BUILTIN_MARKETPLACE.name,
|
|
380
|
+
declaredScope,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Finds which scope declared a marketplace (user, project, local, or builtin)
|
|
386
|
+
*/
|
|
387
|
+
getMarketplaceDeclaringSource(name: string): Scope | "builtin" | null {
|
|
388
|
+
if (name === MarketplaceService.BUILTIN_MARKETPLACE.name) return "builtin";
|
|
389
|
+
|
|
390
|
+
const scopes: Scope[] = ["local", "project", "user"];
|
|
391
|
+
for (const scope of scopes) {
|
|
392
|
+
const scoped = this.configurationService.getScopedMarketplaces(
|
|
393
|
+
this.workdir,
|
|
394
|
+
scope,
|
|
395
|
+
);
|
|
396
|
+
if (scoped[name]) return scope;
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
|
|
271
401
|
/**
|
|
272
402
|
* Adds a new marketplace (local directory, GitHub repo, or Git URL)
|
|
273
403
|
*/
|
|
274
|
-
async addMarketplace(
|
|
404
|
+
async addMarketplace(
|
|
405
|
+
input: string,
|
|
406
|
+
scope: Scope = "user",
|
|
407
|
+
): Promise<KnownMarketplace> {
|
|
275
408
|
return this.withLock(async () => {
|
|
276
409
|
let marketplace: KnownMarketplace;
|
|
277
410
|
|
|
@@ -287,7 +420,6 @@ export class MarketplaceService {
|
|
|
287
420
|
!path.isAbsolute(input) &&
|
|
288
421
|
!input.startsWith("."))
|
|
289
422
|
) {
|
|
290
|
-
// Git or GitHub repo
|
|
291
423
|
let urlOrRepo = input;
|
|
292
424
|
let ref: string | undefined;
|
|
293
425
|
|
|
@@ -295,11 +427,11 @@ export class MarketplaceService {
|
|
|
295
427
|
[urlOrRepo, ref] = input.split("#");
|
|
296
428
|
}
|
|
297
429
|
|
|
298
|
-
const
|
|
299
|
-
? {
|
|
300
|
-
: {
|
|
430
|
+
const tempSource: MarketplaceSource = isFullUrl
|
|
431
|
+
? { source: "git", url: urlOrRepo, ref }
|
|
432
|
+
: { source: "github", repo: urlOrRepo, ref };
|
|
301
433
|
|
|
302
|
-
const targetPath = this.getMarketplacePath(
|
|
434
|
+
const targetPath = this.getMarketplacePath(tempSource);
|
|
303
435
|
|
|
304
436
|
if (!existsSync(targetPath)) {
|
|
305
437
|
try {
|
|
@@ -329,7 +461,6 @@ export class MarketplaceService {
|
|
|
329
461
|
lastUpdated: new Date().toISOString(),
|
|
330
462
|
};
|
|
331
463
|
} else {
|
|
332
|
-
// Local directory format
|
|
333
464
|
const absolutePath = path.resolve(input);
|
|
334
465
|
let manifest: MarketplaceManifest;
|
|
335
466
|
try {
|
|
@@ -348,54 +479,97 @@ export class MarketplaceService {
|
|
|
348
479
|
};
|
|
349
480
|
}
|
|
350
481
|
|
|
351
|
-
const
|
|
482
|
+
const config: MarketplaceConfig = {
|
|
483
|
+
source: marketplace.source,
|
|
484
|
+
autoUpdate: marketplace.autoUpdate,
|
|
485
|
+
};
|
|
352
486
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
487
|
+
await this.configurationService.addMarketplaceToScope(
|
|
488
|
+
this.workdir,
|
|
489
|
+
scope,
|
|
490
|
+
marketplace.name,
|
|
491
|
+
config,
|
|
356
492
|
);
|
|
357
|
-
if (existingIndex >= 0) {
|
|
358
|
-
registry.marketplaces[existingIndex] = marketplace;
|
|
359
|
-
} else {
|
|
360
|
-
registry.marketplaces.push(marketplace);
|
|
361
|
-
}
|
|
362
493
|
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
494
|
+
// Update cache with metadata
|
|
495
|
+
await this.updateCacheMarketplace(marketplace.name, {
|
|
496
|
+
source: marketplace.source,
|
|
497
|
+
autoUpdate: marketplace.autoUpdate,
|
|
498
|
+
lastUpdated: marketplace.lastUpdated,
|
|
499
|
+
});
|
|
366
500
|
|
|
367
|
-
await this.saveKnownMarketplaces(registry);
|
|
368
501
|
return marketplace;
|
|
369
502
|
});
|
|
370
503
|
}
|
|
371
504
|
|
|
372
505
|
/**
|
|
373
|
-
* Lists all registered marketplaces
|
|
506
|
+
* Lists all registered marketplaces by combining scoped settings + built-in
|
|
374
507
|
*/
|
|
375
508
|
async listMarketplaces(): Promise<KnownMarketplace[]> {
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
509
|
+
const scopedMarketplaces = this.configurationService.getMergedMarketplaces(
|
|
510
|
+
this.workdir,
|
|
511
|
+
);
|
|
512
|
+
const cacheRegistry = await this.getCacheRegistry();
|
|
513
|
+
const cacheMap = new Map(
|
|
514
|
+
(cacheRegistry?.marketplaces ?? []).map((m) => [m.name, m]),
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
const result: KnownMarketplace[] = [];
|
|
518
|
+
|
|
519
|
+
// Add built-in marketplace
|
|
520
|
+
result.push({
|
|
521
|
+
...MarketplaceService.BUILTIN_MARKETPLACE,
|
|
522
|
+
isBuiltin: true,
|
|
523
|
+
declaredScope: "builtin",
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Add all scoped marketplaces (local overrides project overrides user)
|
|
527
|
+
for (const [name, config] of Object.entries(scopedMarketplaces)) {
|
|
528
|
+
if (name === MarketplaceService.BUILTIN_MARKETPLACE.name) continue;
|
|
529
|
+
const cache = cacheMap.get(name);
|
|
530
|
+
const declaredScope = this.getMarketplaceDeclaringSource(name) as
|
|
531
|
+
| "user"
|
|
532
|
+
| "project"
|
|
533
|
+
| "local";
|
|
534
|
+
result.push(
|
|
535
|
+
await this.buildMarketplaceEntry(name, config, cache, declaredScope),
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Add cache entries not yet in scoped settings (backwards compatibility)
|
|
540
|
+
for (const [name, cache] of cacheMap.entries()) {
|
|
541
|
+
if (name === MarketplaceService.BUILTIN_MARKETPLACE.name) continue;
|
|
542
|
+
if (scopedMarketplaces[name]) continue;
|
|
543
|
+
result.push({
|
|
544
|
+
...cache,
|
|
545
|
+
isBuiltin: false,
|
|
546
|
+
declaredScope: cache.declaredScope ?? "user",
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return result;
|
|
381
551
|
}
|
|
382
552
|
|
|
383
553
|
/**
|
|
384
|
-
* Removes a marketplace by name
|
|
554
|
+
* Removes a marketplace by name from the specified scope
|
|
385
555
|
*/
|
|
386
|
-
async removeMarketplace(name: string): Promise<void> {
|
|
556
|
+
async removeMarketplace(name: string, scope?: Scope): Promise<void> {
|
|
387
557
|
return this.withLock(async () => {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
registry.marketplaces = registry.marketplaces.filter(
|
|
391
|
-
(m) => m.name !== name,
|
|
392
|
-
);
|
|
558
|
+
const targetScope =
|
|
559
|
+
scope || this.getMarketplaceDeclaringSource(name) || "user";
|
|
393
560
|
|
|
394
|
-
if (
|
|
395
|
-
throw new Error(
|
|
561
|
+
if (targetScope === "builtin") {
|
|
562
|
+
throw new Error("Cannot remove built-in marketplace");
|
|
396
563
|
}
|
|
397
564
|
|
|
398
|
-
await this.
|
|
565
|
+
await this.configurationService.removeMarketplaceFromScope(
|
|
566
|
+
this.workdir,
|
|
567
|
+
targetScope,
|
|
568
|
+
name,
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
// Also remove from cache
|
|
572
|
+
await this.removeFromCache(name);
|
|
399
573
|
});
|
|
400
574
|
}
|
|
401
575
|
|
|
@@ -407,10 +581,10 @@ export class MarketplaceService {
|
|
|
407
581
|
options?: { updatePlugins?: boolean },
|
|
408
582
|
): Promise<void> {
|
|
409
583
|
return this.withLock(async () => {
|
|
410
|
-
const
|
|
584
|
+
const marketplaces = await this.listMarketplaces();
|
|
411
585
|
const toUpdate = name
|
|
412
|
-
?
|
|
413
|
-
:
|
|
586
|
+
? marketplaces.filter((m) => m.name === name)
|
|
587
|
+
: marketplaces;
|
|
414
588
|
|
|
415
589
|
if (name && toUpdate.length === 0) {
|
|
416
590
|
throw new Error(`Marketplace ${name} not found`);
|
|
@@ -430,7 +604,7 @@ export class MarketplaceService {
|
|
|
430
604
|
);
|
|
431
605
|
continue;
|
|
432
606
|
}
|
|
433
|
-
const targetPath = this.getMarketplacePath(marketplace);
|
|
607
|
+
const targetPath = this.getMarketplacePath(marketplace.source);
|
|
434
608
|
if (existsSync(targetPath)) {
|
|
435
609
|
await this.gitService.pull(targetPath);
|
|
436
610
|
} else {
|
|
@@ -447,13 +621,17 @@ export class MarketplaceService {
|
|
|
447
621
|
);
|
|
448
622
|
}
|
|
449
623
|
}
|
|
450
|
-
// For directory source, we just re-validate the manifest
|
|
451
624
|
const manifest = await this.loadMarketplaceManifest(
|
|
452
|
-
this.getMarketplacePath(marketplace),
|
|
625
|
+
this.getMarketplacePath(marketplace.source),
|
|
453
626
|
);
|
|
454
627
|
|
|
455
628
|
marketplace.lastUpdated = new Date().toISOString();
|
|
456
629
|
|
|
630
|
+
// Update cache metadata
|
|
631
|
+
await this.updateCacheMarketplace(marketplace.name, {
|
|
632
|
+
lastUpdated: marketplace.lastUpdated,
|
|
633
|
+
});
|
|
634
|
+
|
|
457
635
|
if (options?.updatePlugins) {
|
|
458
636
|
const installedRegistry = await this.getInstalledPlugins();
|
|
459
637
|
const pluginsToUpdate = installedRegistry.plugins.filter(
|
|
@@ -464,7 +642,7 @@ export class MarketplaceService {
|
|
|
464
642
|
(p) => p.name === plugin.name,
|
|
465
643
|
);
|
|
466
644
|
if (!pluginEntry) {
|
|
467
|
-
|
|
645
|
+
logger.warn(
|
|
468
646
|
`Plugin "${plugin.name}" no longer found in marketplace "${marketplace.name}". Uninstalling...`,
|
|
469
647
|
);
|
|
470
648
|
try {
|
|
@@ -505,8 +683,6 @@ export class MarketplaceService {
|
|
|
505
683
|
`Some marketplaces failed to update:\n${errors.join("\n")}`,
|
|
506
684
|
);
|
|
507
685
|
}
|
|
508
|
-
|
|
509
|
-
await this.saveKnownMarketplaces(registry);
|
|
510
686
|
});
|
|
511
687
|
}
|
|
512
688
|
|
|
@@ -515,17 +691,20 @@ export class MarketplaceService {
|
|
|
515
691
|
*/
|
|
516
692
|
async autoUpdateAll(): Promise<void> {
|
|
517
693
|
return this.withLock(async () => {
|
|
518
|
-
const
|
|
519
|
-
|
|
694
|
+
const scopedMarketplaces =
|
|
695
|
+
this.configurationService.getMergedMarketplaces(this.workdir);
|
|
696
|
+
const toAutoUpdate = Object.entries(scopedMarketplaces)
|
|
697
|
+
.filter(([, config]) => config.autoUpdate)
|
|
698
|
+
.map(([name]) => name);
|
|
520
699
|
|
|
521
|
-
for (const
|
|
700
|
+
for (const marketplaceName of toAutoUpdate) {
|
|
522
701
|
try {
|
|
523
|
-
await this.updateMarketplace(
|
|
702
|
+
await this.updateMarketplace(marketplaceName, {
|
|
524
703
|
updatePlugins: true,
|
|
525
704
|
});
|
|
526
705
|
} catch (error) {
|
|
527
706
|
console.error(
|
|
528
|
-
`Auto-update failed for marketplace "${
|
|
707
|
+
`Auto-update failed for marketplace "${marketplaceName}":`,
|
|
529
708
|
error,
|
|
530
709
|
);
|
|
531
710
|
}
|
|
@@ -538,13 +717,30 @@ export class MarketplaceService {
|
|
|
538
717
|
*/
|
|
539
718
|
async toggleAutoUpdate(name: string, enabled: boolean): Promise<void> {
|
|
540
719
|
return this.withLock(async () => {
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
720
|
+
const declaringSource = this.getMarketplaceDeclaringSource(name);
|
|
721
|
+
if (!declaringSource || declaringSource === "builtin") {
|
|
722
|
+
throw new Error(`Marketplace ${name} not found`);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const scoped = this.configurationService.getScopedMarketplaces(
|
|
726
|
+
this.workdir,
|
|
727
|
+
declaringSource,
|
|
728
|
+
);
|
|
729
|
+
const config = scoped[name];
|
|
730
|
+
if (!config) {
|
|
544
731
|
throw new Error(`Marketplace ${name} not found`);
|
|
545
732
|
}
|
|
546
|
-
|
|
547
|
-
|
|
733
|
+
|
|
734
|
+
config.autoUpdate = enabled;
|
|
735
|
+
await this.configurationService.addMarketplaceToScope(
|
|
736
|
+
this.workdir,
|
|
737
|
+
declaringSource,
|
|
738
|
+
name,
|
|
739
|
+
config,
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
// Also update cache
|
|
743
|
+
await this.updateCacheMarketplace(name, { autoUpdate: enabled });
|
|
548
744
|
});
|
|
549
745
|
}
|
|
550
746
|
|
|
@@ -567,7 +763,7 @@ export class MarketplaceService {
|
|
|
567
763
|
throw new Error(`Marketplace ${marketplaceName} not found`);
|
|
568
764
|
}
|
|
569
765
|
|
|
570
|
-
const marketplacePath = this.getMarketplacePath(marketplace);
|
|
766
|
+
const marketplacePath = this.getMarketplacePath(marketplace.source);
|
|
571
767
|
const manifest = await this.loadMarketplaceManifest(marketplacePath);
|
|
572
768
|
const pluginEntry = manifest.plugins.find((p) => p.name === pluginName);
|
|
573
769
|
if (!pluginEntry) {
|
|
@@ -615,7 +811,6 @@ export class MarketplaceService {
|
|
|
615
811
|
const pluginManifest = JSON.parse(pluginManifestContent);
|
|
616
812
|
const version = pluginManifest.version || "1.0.0";
|
|
617
813
|
|
|
618
|
-
// Atomic installation
|
|
619
814
|
const tmpPluginDir = path.join(
|
|
620
815
|
this.tmpDir,
|
|
621
816
|
`${pluginName}-${Date.now()}`,
|
|
@@ -623,7 +818,7 @@ export class MarketplaceService {
|
|
|
623
818
|
try {
|
|
624
819
|
if (isGitSource) {
|
|
625
820
|
await fs.rename(pluginSrcPath, tmpPluginDir);
|
|
626
|
-
tempCloneDir = undefined;
|
|
821
|
+
tempCloneDir = undefined;
|
|
627
822
|
} else {
|
|
628
823
|
await fs.cp(pluginSrcPath, tmpPluginDir, { recursive: true });
|
|
629
824
|
}
|
|
@@ -665,14 +860,12 @@ export class MarketplaceService {
|
|
|
665
860
|
await this.saveInstalledPlugins(installedRegistry);
|
|
666
861
|
return installedPlugin;
|
|
667
862
|
} catch (error) {
|
|
668
|
-
// Cleanup tmp dir if it exists
|
|
669
863
|
if (existsSync(tmpPluginDir)) {
|
|
670
864
|
await fs.rm(tmpPluginDir, { recursive: true, force: true });
|
|
671
865
|
}
|
|
672
866
|
throw error;
|
|
673
867
|
}
|
|
674
868
|
} catch (error) {
|
|
675
|
-
// Cleanup temp clone dir if it exists
|
|
676
869
|
if (tempCloneDir && existsSync(tempCloneDir)) {
|
|
677
870
|
await fs.rm(tempCloneDir, { recursive: true, force: true });
|
|
678
871
|
}
|
|
@@ -712,16 +905,13 @@ export class MarketplaceService {
|
|
|
712
905
|
|
|
713
906
|
const pluginToRemove = installedRegistry.plugins[pluginIndex];
|
|
714
907
|
|
|
715
|
-
// Remove from registry first
|
|
716
908
|
installedRegistry.plugins.splice(pluginIndex, 1);
|
|
717
909
|
await this.saveInstalledPlugins(installedRegistry);
|
|
718
910
|
|
|
719
|
-
// Check if any other project is still using this same cache path
|
|
720
911
|
const isStillReferenced = installedRegistry.plugins.some(
|
|
721
912
|
(p) => p.cachePath === pluginToRemove.cachePath,
|
|
722
913
|
);
|
|
723
914
|
|
|
724
|
-
// Only remove cached files if no other references exist
|
|
725
915
|
if (!isStillReferenced && existsSync(pluginToRemove.cachePath)) {
|
|
726
916
|
await fs.rm(pluginToRemove.cachePath, { recursive: true, force: true });
|
|
727
917
|
}
|