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.
Files changed (88) hide show
  1. package/builtin/skills/settings/HOOKS.md +69 -0
  2. package/builtin/skills/settings/PLUGINS.md +171 -0
  3. package/builtin/skills/settings/SKILL.md +8 -3
  4. package/dist/agent.d.ts +2 -2
  5. package/dist/agent.d.ts.map +1 -1
  6. package/dist/agent.js +12 -3
  7. package/dist/core/plugin.d.ts +2 -2
  8. package/dist/core/plugin.d.ts.map +1 -1
  9. package/dist/core/plugin.js +7 -7
  10. package/dist/managers/aiManager.d.ts +6 -6
  11. package/dist/managers/aiManager.d.ts.map +1 -1
  12. package/dist/managers/aiManager.js +122 -59
  13. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  14. package/dist/managers/backgroundTaskManager.js +28 -30
  15. package/dist/managers/hookManager.d.ts +16 -1
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +97 -8
  18. package/dist/managers/messageManager.d.ts +19 -4
  19. package/dist/managers/messageManager.d.ts.map +1 -1
  20. package/dist/managers/messageManager.js +63 -18
  21. package/dist/managers/pluginManager.d.ts.map +1 -1
  22. package/dist/managers/pluginManager.js +1 -1
  23. package/dist/prompts/index.d.ts +1 -1
  24. package/dist/prompts/index.d.ts.map +1 -1
  25. package/dist/prompts/index.js +1 -1
  26. package/dist/services/MarketplaceService.d.ts +42 -12
  27. package/dist/services/MarketplaceService.d.ts.map +1 -1
  28. package/dist/services/MarketplaceService.js +225 -105
  29. package/dist/services/aiService.d.ts +3 -3
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +7 -7
  32. package/dist/services/configurationService.d.ts +17 -1
  33. package/dist/services/configurationService.d.ts.map +1 -1
  34. package/dist/services/configurationService.js +104 -0
  35. package/dist/services/hook.d.ts.map +1 -1
  36. package/dist/services/hook.js +15 -0
  37. package/dist/services/initializationService.d.ts.map +1 -1
  38. package/dist/services/initializationService.js +24 -1
  39. package/dist/services/interactionService.js +1 -1
  40. package/dist/services/pluginLoader.d.ts.map +1 -1
  41. package/dist/services/pluginLoader.js +7 -1
  42. package/dist/services/session.d.ts +1 -1
  43. package/dist/services/session.js +7 -7
  44. package/dist/services/taskManager.d.ts +1 -1
  45. package/dist/services/taskManager.js +1 -1
  46. package/dist/types/configuration.d.ts +7 -0
  47. package/dist/types/configuration.d.ts.map +1 -1
  48. package/dist/types/core.d.ts +1 -1
  49. package/dist/types/core.d.ts.map +1 -1
  50. package/dist/types/hooks.d.ts +9 -1
  51. package/dist/types/hooks.d.ts.map +1 -1
  52. package/dist/types/hooks.js +2 -0
  53. package/dist/types/marketplace.d.ts +2 -0
  54. package/dist/types/marketplace.d.ts.map +1 -1
  55. package/dist/types/messaging.d.ts +3 -3
  56. package/dist/types/messaging.d.ts.map +1 -1
  57. package/dist/utils/convertMessagesForAPI.d.ts +1 -1
  58. package/dist/utils/convertMessagesForAPI.js +7 -7
  59. package/dist/utils/groupMessagesByApiRound.d.ts +1 -1
  60. package/dist/utils/groupMessagesByApiRound.js +6 -6
  61. package/dist/utils/messageOperations.d.ts.map +1 -1
  62. package/dist/utils/messageOperations.js +3 -3
  63. package/package.json +1 -1
  64. package/src/agent.ts +16 -3
  65. package/src/core/plugin.ts +13 -7
  66. package/src/managers/aiManager.ts +142 -63
  67. package/src/managers/backgroundTaskManager.ts +33 -42
  68. package/src/managers/hookManager.ts +125 -10
  69. package/src/managers/messageManager.ts +76 -22
  70. package/src/managers/pluginManager.ts +4 -1
  71. package/src/prompts/index.ts +1 -1
  72. package/src/services/MarketplaceService.ts +301 -111
  73. package/src/services/aiService.ts +11 -11
  74. package/src/services/configurationService.ts +131 -0
  75. package/src/services/hook.ts +17 -0
  76. package/src/services/initializationService.ts +33 -1
  77. package/src/services/interactionService.ts +1 -1
  78. package/src/services/pluginLoader.ts +7 -1
  79. package/src/services/session.ts +7 -7
  80. package/src/services/taskManager.ts +1 -1
  81. package/src/types/configuration.ts +8 -0
  82. package/src/types/core.ts +1 -1
  83. package/src/types/hooks.ts +16 -2
  84. package/src/types/marketplace.ts +2 -0
  85. package/src/types/messaging.ts +3 -3
  86. package/src/utils/convertMessagesForAPI.ts +8 -8
  87. package/src/utils/groupMessagesByApiRound.ts +6 -6
  88. package/src/utils/messageOperations.ts +3 -5
@@ -3,14 +3,21 @@ import * as path from "path";
3
3
  import * as crypto from "crypto";
4
4
  import { getPluginsDir } from "../utils/configPaths.js";
5
5
  import { GitService } from "./GitService.js";
6
+ import { ConfigurationService } from "./configurationService.js";
7
+ import { logger } from "../utils/globalLogger.js";
6
8
  /**
7
9
  * Marketplace Service
8
10
  *
9
11
  * Handles local plugin marketplace registration, plugin installation,
10
12
  * and state management for installed plugins.
13
+ *
14
+ * Marketplace declarations are now scoped (user/project/local) via settings files.
15
+ * known_marketplaces.json is kept as a cache for installLocation/lastUpdated metadata.
11
16
  */
12
17
  export class MarketplaceService {
13
- constructor() {
18
+ constructor(workdir = process.cwd(), configurationService = new ConfigurationService()) {
19
+ this.workdir = workdir;
20
+ this.configurationService = configurationService;
14
21
  this.pluginsDir = getPluginsDir();
15
22
  this.knownMarketplacesPath = path.join(this.pluginsDir, "known_marketplaces.json");
16
23
  this.installedPluginsPath = path.join(this.pluginsDir, "installed_plugins.json");
@@ -20,6 +27,7 @@ export class MarketplaceService {
20
27
  this.marketplacesDir = path.join(this.pluginsDir, "marketplaces");
21
28
  this.gitService = new GitService();
22
29
  this.ensureDirectoryStructure();
30
+ this.runMigration();
23
31
  }
24
32
  /**
25
33
  * Ensures the required directory structure exists in ~/.wave/plugins
@@ -31,6 +39,32 @@ export class MarketplaceService {
31
39
  }
32
40
  });
33
41
  }
42
+ /**
43
+ * Backwards compatibility migration: migrate entries from known_marketplaces.json
44
+ * to user-level settings if they aren't already declared there.
45
+ */
46
+ async runMigration() {
47
+ try {
48
+ const cacheRegistry = await this.getCacheRegistry();
49
+ const scopedMarketplaces = this.configurationService.getMergedMarketplaces(this.workdir);
50
+ const userMarketplaces = this.configurationService.getScopedMarketplaces(this.workdir, "user");
51
+ const entriesToMigrate = (cacheRegistry?.marketplaces ?? []).filter((m) => !scopedMarketplaces[m.name] && !userMarketplaces[m.name]);
52
+ if (entriesToMigrate.length > 0) {
53
+ for (const m of entriesToMigrate) {
54
+ if (m.name === MarketplaceService.BUILTIN_MARKETPLACE.name)
55
+ continue;
56
+ const config = {
57
+ source: m.source,
58
+ autoUpdate: m.autoUpdate,
59
+ };
60
+ await this.configurationService.addMarketplaceToScope(this.workdir, "user", m.name, config);
61
+ }
62
+ }
63
+ }
64
+ catch {
65
+ // Migration failure should not block startup
66
+ }
67
+ }
34
68
  /**
35
69
  * Check if a lock file is stale by reading its PID and checking if the process is alive.
36
70
  * Returns true if the lock is stale and safe to remove.
@@ -41,17 +75,16 @@ export class MarketplaceService {
41
75
  const pid = parseInt(content.trim(), 10);
42
76
  if (isNaN(pid))
43
77
  return true;
44
- // Check if the process is still running
45
78
  try {
46
79
  process.kill(pid, 0);
47
- return false; // Process exists, lock is valid
80
+ return false;
48
81
  }
49
82
  catch {
50
- return true; // Process doesn't exist, lock is stale
83
+ return true;
51
84
  }
52
85
  }
53
86
  catch {
54
- return true; // Can't read lock file, assume stale
87
+ return true;
55
88
  }
56
89
  }
57
90
  /**
@@ -63,7 +96,7 @@ export class MarketplaceService {
63
96
  return await fn();
64
97
  }
65
98
  let lockFd;
66
- const maxRetries = 600; // 60 seconds total
99
+ const maxRetries = 600;
67
100
  const retryDelay = 100;
68
101
  for (let i = 0; i < maxRetries; i++) {
69
102
  try {
@@ -75,7 +108,6 @@ export class MarketplaceService {
75
108
  typeof error === "object" &&
76
109
  "code" in error &&
77
110
  error.code === "EEXIST") {
78
- // Check for stale lock every 60 retries (every ~6 seconds)
79
111
  if (i > 0 && i % 60 === 0) {
80
112
  const stale = await this.isStaleLock();
81
113
  if (stale) {
@@ -92,7 +124,6 @@ export class MarketplaceService {
92
124
  if (!lockFd) {
93
125
  throw new Error(`Failed to acquire marketplace lock after ${maxRetries} retries. If no other wave-agent process is running, please delete ${this.lockPath}`);
94
126
  }
95
- // Write PID into the lock file for stale lock detection
96
127
  await fs.writeFile(this.lockPath, String(process.pid), "utf-8");
97
128
  MarketplaceService.isLockedInProcess = true;
98
129
  try {
@@ -105,37 +136,86 @@ export class MarketplaceService {
105
136
  }
106
137
  }
107
138
  /**
108
- * Loads the known marketplaces registry
139
+ * Loads the cache registry from known_marketplaces.json
140
+ * Returns null if the file doesn't exist or has no parseable content (for first-run detection).
109
141
  */
110
- async getKnownMarketplaces() {
142
+ async getCacheRegistry() {
111
143
  if (!existsSync(this.knownMarketplacesPath)) {
144
+ return null;
145
+ }
146
+ try {
147
+ const content = await fs.readFile(this.knownMarketplacesPath, "utf-8");
148
+ if (!content.trim()) {
149
+ return null;
150
+ }
151
+ return JSON.parse(content);
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * Legacy method: loads known marketplaces with builtin injection.
159
+ * @deprecated Use listMarketplaces() instead, which combines scoped settings.
160
+ */
161
+ async getKnownMarketplaces() {
162
+ const cache = await this.getCacheRegistry();
163
+ // If cache is null (file doesn't exist or has no parseable content), inject builtin
164
+ if (cache === null) {
112
165
  return {
113
166
  marketplaces: [
114
167
  {
115
168
  ...MarketplaceService.BUILTIN_MARKETPLACE,
116
169
  isBuiltin: true,
170
+ declaredScope: "builtin",
117
171
  },
118
172
  ],
119
173
  };
120
174
  }
121
- try {
122
- const content = await fs.readFile(this.knownMarketplacesPath, "utf-8");
123
- if (!content.trim()) {
124
- return {
125
- marketplaces: [
126
- {
127
- ...MarketplaceService.BUILTIN_MARKETPLACE,
128
- isBuiltin: true,
129
- },
130
- ],
131
- };
132
- }
133
- return JSON.parse(content);
175
+ // File has valid JSON - respect user's explicit choice even if empty
176
+ const hasBuiltin = cache.marketplaces.some((m) => m.name === MarketplaceService.BUILTIN_MARKETPLACE.name);
177
+ return {
178
+ marketplaces: cache.marketplaces.map((m) => ({
179
+ ...m,
180
+ isBuiltin: m.isBuiltin || m.name === MarketplaceService.BUILTIN_MARKETPLACE.name,
181
+ declaredScope: m.declaredScope ?? (hasBuiltin ? "builtin" : "user"),
182
+ })),
183
+ };
184
+ }
185
+ /**
186
+ * Updates the cache registry with metadata for a marketplace
187
+ */
188
+ async updateCacheMarketplace(name, metadata) {
189
+ const registry = await this.getCacheRegistry();
190
+ const marketplaces = registry?.marketplaces ?? [];
191
+ const existingIndex = marketplaces.findIndex((m) => m.name === name);
192
+ if (existingIndex >= 0) {
193
+ marketplaces[existingIndex] = {
194
+ ...marketplaces[existingIndex],
195
+ ...metadata,
196
+ };
134
197
  }
135
- catch (error) {
136
- console.error("Failed to load known marketplaces:", error);
137
- return { marketplaces: [] };
198
+ else {
199
+ marketplaces.push({
200
+ name,
201
+ source: metadata.source || { source: "directory", path: "" },
202
+ ...metadata,
203
+ });
138
204
  }
205
+ const tmpPath = `${this.knownMarketplacesPath}.tmp`;
206
+ await fs.writeFile(tmpPath, JSON.stringify({ marketplaces }, null, 2));
207
+ await fs.rename(tmpPath, this.knownMarketplacesPath);
208
+ }
209
+ /**
210
+ * Removes a marketplace from the cache registry
211
+ */
212
+ async removeFromCache(name) {
213
+ const registry = await this.getCacheRegistry();
214
+ const marketplaces = registry?.marketplaces ?? [];
215
+ const filtered = marketplaces.filter((m) => m.name !== name);
216
+ const tmpPath = `${this.knownMarketplacesPath}.tmp`;
217
+ await fs.writeFile(tmpPath, JSON.stringify({ marketplaces: filtered }, null, 2));
218
+ await fs.rename(tmpPath, this.knownMarketplacesPath);
139
219
  }
140
220
  /**
141
221
  * Loads the installed plugins registry
@@ -156,14 +236,6 @@ export class MarketplaceService {
156
236
  return { plugins: [] };
157
237
  }
158
238
  }
159
- /**
160
- * Saves the known marketplaces registry
161
- */
162
- async saveKnownMarketplaces(registry) {
163
- const tmpPath = `${this.knownMarketplacesPath}.tmp`;
164
- await fs.writeFile(tmpPath, JSON.stringify(registry, null, 2));
165
- await fs.rename(tmpPath, this.knownMarketplacesPath);
166
- }
167
239
  /**
168
240
  * Saves the installed plugins registry
169
241
  */
@@ -182,7 +254,6 @@ export class MarketplaceService {
182
254
  }
183
255
  const content = await fs.readFile(manifestPath, "utf-8");
184
256
  const manifest = JSON.parse(content);
185
- // Basic validation
186
257
  if (!manifest.name ||
187
258
  !manifest.plugins ||
188
259
  !Array.isArray(manifest.plugins)) {
@@ -193,26 +264,49 @@ export class MarketplaceService {
193
264
  /**
194
265
  * Resolves the local path for a marketplace
195
266
  */
196
- getMarketplacePath(marketplace) {
197
- if (marketplace.source.source === "directory") {
198
- return marketplace.source.path;
267
+ getMarketplacePath(source) {
268
+ if (source.source === "directory") {
269
+ return source.path;
199
270
  }
200
- else if (marketplace.source.source === "github") {
201
- return path.join(this.marketplacesDir, marketplace.source.repo);
271
+ else if (source.source === "github") {
272
+ return path.join(this.marketplacesDir, source.repo);
202
273
  }
203
274
  else {
204
- // For general git, use a hash of the URL to avoid path issues
205
- const hash = crypto
206
- .createHash("md5")
207
- .update(marketplace.source.url)
208
- .digest("hex");
275
+ const hash = crypto.createHash("md5").update(source.url).digest("hex");
209
276
  return path.join(this.marketplacesDir, hash);
210
277
  }
211
278
  }
279
+ /**
280
+ * Builds a KnownMarketplace from a scoped config, enriched with cache metadata
281
+ */
282
+ async buildMarketplaceEntry(name, config, cache, declaredScope) {
283
+ return {
284
+ name,
285
+ source: config.source,
286
+ autoUpdate: config.autoUpdate ?? cache?.autoUpdate,
287
+ lastUpdated: cache?.lastUpdated,
288
+ isBuiltin: name === MarketplaceService.BUILTIN_MARKETPLACE.name,
289
+ declaredScope,
290
+ };
291
+ }
292
+ /**
293
+ * Finds which scope declared a marketplace (user, project, local, or builtin)
294
+ */
295
+ getMarketplaceDeclaringSource(name) {
296
+ if (name === MarketplaceService.BUILTIN_MARKETPLACE.name)
297
+ return "builtin";
298
+ const scopes = ["local", "project", "user"];
299
+ for (const scope of scopes) {
300
+ const scoped = this.configurationService.getScopedMarketplaces(this.workdir, scope);
301
+ if (scoped[name])
302
+ return scope;
303
+ }
304
+ return null;
305
+ }
212
306
  /**
213
307
  * Adds a new marketplace (local directory, GitHub repo, or Git URL)
214
308
  */
215
- async addMarketplace(input) {
309
+ async addMarketplace(input, scope = "user") {
216
310
  return this.withLock(async () => {
217
311
  let marketplace;
218
312
  const isFullUrl = input.startsWith("http://") ||
@@ -223,16 +317,15 @@ export class MarketplaceService {
223
317
  (input.includes("/") &&
224
318
  !path.isAbsolute(input) &&
225
319
  !input.startsWith("."))) {
226
- // Git or GitHub repo
227
320
  let urlOrRepo = input;
228
321
  let ref;
229
322
  if (input.includes("#")) {
230
323
  [urlOrRepo, ref] = input.split("#");
231
324
  }
232
- const tempMarketplace = isFullUrl
233
- ? { name: "", source: { source: "git", url: urlOrRepo, ref } }
234
- : { name: "", source: { source: "github", repo: urlOrRepo, ref } };
235
- const targetPath = this.getMarketplacePath(tempMarketplace);
325
+ const tempSource = isFullUrl
326
+ ? { source: "git", url: urlOrRepo, ref }
327
+ : { source: "github", repo: urlOrRepo, ref };
328
+ const targetPath = this.getMarketplacePath(tempSource);
236
329
  if (!existsSync(targetPath)) {
237
330
  try {
238
331
  await this.gitService.clone(urlOrRepo, targetPath, ref);
@@ -258,7 +351,6 @@ export class MarketplaceService {
258
351
  };
259
352
  }
260
353
  else {
261
- // Local directory format
262
354
  const absolutePath = path.resolve(input);
263
355
  let manifest;
264
356
  try {
@@ -274,44 +366,68 @@ export class MarketplaceService {
274
366
  lastUpdated: new Date().toISOString(),
275
367
  };
276
368
  }
277
- const registry = await this.getKnownMarketplaces();
278
- // Check if already exists
279
- const existingIndex = registry.marketplaces.findIndex((m) => m.name === marketplace.name);
280
- if (existingIndex >= 0) {
281
- registry.marketplaces[existingIndex] = marketplace;
282
- }
283
- else {
284
- registry.marketplaces.push(marketplace);
285
- }
286
- // Ensure builtin is included if we are creating the file for the first time
287
- // and it hasn't been explicitly removed yet.
288
- // (getKnownMarketplaces already handles the default injection)
289
- await this.saveKnownMarketplaces(registry);
369
+ const config = {
370
+ source: marketplace.source,
371
+ autoUpdate: marketplace.autoUpdate,
372
+ };
373
+ await this.configurationService.addMarketplaceToScope(this.workdir, scope, marketplace.name, config);
374
+ // Update cache with metadata
375
+ await this.updateCacheMarketplace(marketplace.name, {
376
+ source: marketplace.source,
377
+ autoUpdate: marketplace.autoUpdate,
378
+ lastUpdated: marketplace.lastUpdated,
379
+ });
290
380
  return marketplace;
291
381
  });
292
382
  }
293
383
  /**
294
- * Lists all registered marketplaces
384
+ * Lists all registered marketplaces by combining scoped settings + built-in
295
385
  */
296
386
  async listMarketplaces() {
297
- const registry = await this.getKnownMarketplaces();
298
- return registry.marketplaces.map((m) => ({
299
- ...m,
300
- isBuiltin: m.name === MarketplaceService.BUILTIN_MARKETPLACE.name,
301
- }));
387
+ const scopedMarketplaces = this.configurationService.getMergedMarketplaces(this.workdir);
388
+ const cacheRegistry = await this.getCacheRegistry();
389
+ const cacheMap = new Map((cacheRegistry?.marketplaces ?? []).map((m) => [m.name, m]));
390
+ const result = [];
391
+ // Add built-in marketplace
392
+ result.push({
393
+ ...MarketplaceService.BUILTIN_MARKETPLACE,
394
+ isBuiltin: true,
395
+ declaredScope: "builtin",
396
+ });
397
+ // Add all scoped marketplaces (local overrides project overrides user)
398
+ for (const [name, config] of Object.entries(scopedMarketplaces)) {
399
+ if (name === MarketplaceService.BUILTIN_MARKETPLACE.name)
400
+ continue;
401
+ const cache = cacheMap.get(name);
402
+ const declaredScope = this.getMarketplaceDeclaringSource(name);
403
+ result.push(await this.buildMarketplaceEntry(name, config, cache, declaredScope));
404
+ }
405
+ // Add cache entries not yet in scoped settings (backwards compatibility)
406
+ for (const [name, cache] of cacheMap.entries()) {
407
+ if (name === MarketplaceService.BUILTIN_MARKETPLACE.name)
408
+ continue;
409
+ if (scopedMarketplaces[name])
410
+ continue;
411
+ result.push({
412
+ ...cache,
413
+ isBuiltin: false,
414
+ declaredScope: cache.declaredScope ?? "user",
415
+ });
416
+ }
417
+ return result;
302
418
  }
303
419
  /**
304
- * Removes a marketplace by name
420
+ * Removes a marketplace by name from the specified scope
305
421
  */
306
- async removeMarketplace(name) {
422
+ async removeMarketplace(name, scope) {
307
423
  return this.withLock(async () => {
308
- const registry = await this.getKnownMarketplaces();
309
- const initialCount = registry.marketplaces.length;
310
- registry.marketplaces = registry.marketplaces.filter((m) => m.name !== name);
311
- if (registry.marketplaces.length === initialCount) {
312
- throw new Error(`Marketplace ${name} not found`);
424
+ const targetScope = scope || this.getMarketplaceDeclaringSource(name) || "user";
425
+ if (targetScope === "builtin") {
426
+ throw new Error("Cannot remove built-in marketplace");
313
427
  }
314
- await this.saveKnownMarketplaces(registry);
428
+ await this.configurationService.removeMarketplaceFromScope(this.workdir, targetScope, name);
429
+ // Also remove from cache
430
+ await this.removeFromCache(name);
315
431
  });
316
432
  }
317
433
  /**
@@ -319,10 +435,10 @@ export class MarketplaceService {
319
435
  */
320
436
  async updateMarketplace(name, options) {
321
437
  return this.withLock(async () => {
322
- const registry = await this.getKnownMarketplaces();
438
+ const marketplaces = await this.listMarketplaces();
323
439
  const toUpdate = name
324
- ? registry.marketplaces.filter((m) => m.name === name)
325
- : registry.marketplaces;
440
+ ? marketplaces.filter((m) => m.name === name)
441
+ : marketplaces;
326
442
  if (name && toUpdate.length === 0) {
327
443
  throw new Error(`Marketplace ${name} not found`);
328
444
  }
@@ -336,7 +452,7 @@ export class MarketplaceService {
336
452
  console.warn(`Skipping update for Git/GitHub marketplace "${marketplace.name}" because Git is not installed.`);
337
453
  continue;
338
454
  }
339
- const targetPath = this.getMarketplacePath(marketplace);
455
+ const targetPath = this.getMarketplacePath(marketplace.source);
340
456
  if (existsSync(targetPath)) {
341
457
  await this.gitService.pull(targetPath);
342
458
  }
@@ -351,16 +467,19 @@ export class MarketplaceService {
351
467
  await this.gitService.clone(url, targetPath, marketplace.source.ref);
352
468
  }
353
469
  }
354
- // For directory source, we just re-validate the manifest
355
- const manifest = await this.loadMarketplaceManifest(this.getMarketplacePath(marketplace));
470
+ const manifest = await this.loadMarketplaceManifest(this.getMarketplacePath(marketplace.source));
356
471
  marketplace.lastUpdated = new Date().toISOString();
472
+ // Update cache metadata
473
+ await this.updateCacheMarketplace(marketplace.name, {
474
+ lastUpdated: marketplace.lastUpdated,
475
+ });
357
476
  if (options?.updatePlugins) {
358
477
  const installedRegistry = await this.getInstalledPlugins();
359
478
  const pluginsToUpdate = installedRegistry.plugins.filter((p) => p.marketplace === marketplace.name);
360
479
  for (const plugin of pluginsToUpdate) {
361
480
  const pluginEntry = manifest.plugins.find((p) => p.name === plugin.name);
362
481
  if (!pluginEntry) {
363
- console.warn(`Plugin "${plugin.name}" no longer found in marketplace "${marketplace.name}". Uninstalling...`);
482
+ logger.warn(`Plugin "${plugin.name}" no longer found in marketplace "${marketplace.name}". Uninstalling...`);
364
483
  try {
365
484
  await this.uninstallPlugin(`${plugin.name}@${plugin.marketplace}`, plugin.projectPath);
366
485
  }
@@ -387,7 +506,6 @@ export class MarketplaceService {
387
506
  if (errors.length > 0) {
388
507
  throw new Error(`Some marketplaces failed to update:\n${errors.join("\n")}`);
389
508
  }
390
- await this.saveKnownMarketplaces(registry);
391
509
  });
392
510
  }
393
511
  /**
@@ -395,16 +513,18 @@ export class MarketplaceService {
395
513
  */
396
514
  async autoUpdateAll() {
397
515
  return this.withLock(async () => {
398
- const registry = await this.getKnownMarketplaces();
399
- const toAutoUpdate = registry.marketplaces.filter((m) => m.autoUpdate);
400
- for (const marketplace of toAutoUpdate) {
516
+ const scopedMarketplaces = this.configurationService.getMergedMarketplaces(this.workdir);
517
+ const toAutoUpdate = Object.entries(scopedMarketplaces)
518
+ .filter(([, config]) => config.autoUpdate)
519
+ .map(([name]) => name);
520
+ for (const marketplaceName of toAutoUpdate) {
401
521
  try {
402
- await this.updateMarketplace(marketplace.name, {
522
+ await this.updateMarketplace(marketplaceName, {
403
523
  updatePlugins: true,
404
524
  });
405
525
  }
406
526
  catch (error) {
407
- console.error(`Auto-update failed for marketplace "${marketplace.name}":`, error);
527
+ console.error(`Auto-update failed for marketplace "${marketplaceName}":`, error);
408
528
  }
409
529
  }
410
530
  });
@@ -414,13 +534,19 @@ export class MarketplaceService {
414
534
  */
415
535
  async toggleAutoUpdate(name, enabled) {
416
536
  return this.withLock(async () => {
417
- const registry = await this.getKnownMarketplaces();
418
- const marketplace = registry.marketplaces.find((m) => m.name === name);
419
- if (!marketplace) {
537
+ const declaringSource = this.getMarketplaceDeclaringSource(name);
538
+ if (!declaringSource || declaringSource === "builtin") {
539
+ throw new Error(`Marketplace ${name} not found`);
540
+ }
541
+ const scoped = this.configurationService.getScopedMarketplaces(this.workdir, declaringSource);
542
+ const config = scoped[name];
543
+ if (!config) {
420
544
  throw new Error(`Marketplace ${name} not found`);
421
545
  }
422
- marketplace.autoUpdate = enabled;
423
- await this.saveKnownMarketplaces(registry);
546
+ config.autoUpdate = enabled;
547
+ await this.configurationService.addMarketplaceToScope(this.workdir, declaringSource, name, config);
548
+ // Also update cache
549
+ await this.updateCacheMarketplace(name, { autoUpdate: enabled });
424
550
  });
425
551
  }
426
552
  /**
@@ -437,7 +563,7 @@ export class MarketplaceService {
437
563
  if (!marketplace) {
438
564
  throw new Error(`Marketplace ${marketplaceName} not found`);
439
565
  }
440
- const marketplacePath = this.getMarketplacePath(marketplace);
566
+ const marketplacePath = this.getMarketplacePath(marketplace.source);
441
567
  const manifest = await this.loadMarketplaceManifest(marketplacePath);
442
568
  const pluginEntry = manifest.plugins.find((p) => p.name === pluginName);
443
569
  if (!pluginEntry) {
@@ -470,12 +596,11 @@ export class MarketplaceService {
470
596
  const pluginManifestContent = await fs.readFile(pluginManifestPath, "utf-8");
471
597
  const pluginManifest = JSON.parse(pluginManifestContent);
472
598
  const version = pluginManifest.version || "1.0.0";
473
- // Atomic installation
474
599
  const tmpPluginDir = path.join(this.tmpDir, `${pluginName}-${Date.now()}`);
475
600
  try {
476
601
  if (isGitSource) {
477
602
  await fs.rename(pluginSrcPath, tmpPluginDir);
478
- tempCloneDir = undefined; // Already moved
603
+ tempCloneDir = undefined;
479
604
  }
480
605
  else {
481
606
  await fs.cp(pluginSrcPath, tmpPluginDir, { recursive: true });
@@ -507,7 +632,6 @@ export class MarketplaceService {
507
632
  return installedPlugin;
508
633
  }
509
634
  catch (error) {
510
- // Cleanup tmp dir if it exists
511
635
  if (existsSync(tmpPluginDir)) {
512
636
  await fs.rm(tmpPluginDir, { recursive: true, force: true });
513
637
  }
@@ -515,7 +639,6 @@ export class MarketplaceService {
515
639
  }
516
640
  }
517
641
  catch (error) {
518
- // Cleanup temp clone dir if it exists
519
642
  if (tempCloneDir && existsSync(tempCloneDir)) {
520
643
  await fs.rm(tempCloneDir, { recursive: true, force: true });
521
644
  }
@@ -540,12 +663,9 @@ export class MarketplaceService {
540
663
  throw new Error(`Plugin ${pluginName}@${marketplaceName} is not installed${projectPath ? ` for project ${projectPath}` : ""}`);
541
664
  }
542
665
  const pluginToRemove = installedRegistry.plugins[pluginIndex];
543
- // Remove from registry first
544
666
  installedRegistry.plugins.splice(pluginIndex, 1);
545
667
  await this.saveInstalledPlugins(installedRegistry);
546
- // Check if any other project is still using this same cache path
547
668
  const isStillReferenced = installedRegistry.plugins.some((p) => p.cachePath === pluginToRemove.cachePath);
548
- // Only remove cached files if no other references exist
549
669
  if (!isStillReferenced && existsSync(pluginToRemove.cachePath)) {
550
670
  await fs.rm(pluginToRemove.cachePath, { recursive: true, force: true });
551
671
  }
@@ -37,14 +37,14 @@ export interface CallAgentResult {
37
37
  additionalFields?: Record<string, unknown>;
38
38
  }
39
39
  export declare function callAgent(options: CallAgentOptions): Promise<CallAgentResult>;
40
- export interface CompressMessagesOptions {
40
+ export interface CompactMessagesOptions {
41
41
  gatewayConfig: GatewayConfig;
42
42
  modelConfig: ModelConfig;
43
43
  messages: ChatCompletionMessageParam[];
44
44
  abortSignal?: AbortSignal;
45
45
  model?: string;
46
46
  }
47
- export interface CompressMessagesResult {
47
+ export interface CompactMessagesResult {
48
48
  content: string;
49
49
  usage?: {
50
50
  prompt_tokens: number;
@@ -52,7 +52,7 @@ export interface CompressMessagesResult {
52
52
  total_tokens: number;
53
53
  };
54
54
  }
55
- export declare function compressMessages(options: CompressMessagesOptions): Promise<CompressMessagesResult>;
55
+ export declare function compactMessages(options: CompactMessagesOptions): Promise<CompactMessagesResult>;
56
56
  export interface ProcessWebContentOptions {
57
57
  gatewayConfig: GatewayConfig;
58
58
  modelConfig: ModelConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"aiService.d.ts","sourceRoot":"","sources":["../../src/services/aiService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aiService.d.ts","sourceRoot":"","sources":["../../src/services/aiService.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,7 @@ import { transformMessagesForClaudeCache, addCacheControlToLastTool, supportsPro
5
5
  import * as os from "os";
6
6
  import * as fs from "fs";
7
7
  import * as path from "path";
8
- import { COMPRESS_MESSAGES_SYSTEM_PROMPT, WEB_CONTENT_SYSTEM_PROMPT, BTW_SYSTEM_PROMPT, } from "../prompts/index.js";
8
+ import { COMPACT_MESSAGES_SYSTEM_PROMPT, WEB_CONTENT_SYSTEM_PROMPT, BTW_SYSTEM_PROMPT, } from "../prompts/index.js";
9
9
  // Global rate limiter state for 1 QPS
10
10
  let nextAllowedTime = 0;
11
11
  const MIN_INTERVAL = 1000; // 1 second for 1 QPS
@@ -450,7 +450,7 @@ async function processStreamingResponse(stream, onContentUpdate, onToolUpdate, o
450
450
  }
451
451
  return result;
452
452
  }
453
- export async function compressMessages(options) {
453
+ export async function compactMessages(options) {
454
454
  const { gatewayConfig, modelConfig, messages, abortSignal } = options;
455
455
  // Apply global 1 QPS rate limit
456
456
  if (process.env.NODE_ENV !== "test" ||
@@ -492,7 +492,7 @@ export async function compressMessages(options) {
492
492
  messages: [
493
493
  {
494
494
  role: "system",
495
- content: COMPRESS_MESSAGES_SYSTEM_PROMPT,
495
+ content: COMPACT_MESSAGES_SYSTEM_PROMPT,
496
496
  },
497
497
  ...cleanedMessages,
498
498
  {
@@ -505,7 +505,7 @@ export async function compressMessages(options) {
505
505
  });
506
506
  const content = response.choices[0]?.message?.content?.trim();
507
507
  if (!content) {
508
- throw new Error("Failed to compress conversation history: Empty response from AI");
508
+ throw new Error("Failed to compact conversation history: Empty response from AI");
509
509
  }
510
510
  const usage = response.usage
511
511
  ? {
@@ -521,10 +521,10 @@ export async function compressMessages(options) {
521
521
  }
522
522
  catch (error) {
523
523
  if (error.name === "AbortError") {
524
- logger.info("Compression request was aborted");
525
- throw new Error("Compression request was aborted");
524
+ logger.info("Compaction request was aborted");
525
+ throw new Error("Compaction request was aborted");
526
526
  }
527
- logger.error("Failed to compress messages:", error);
527
+ logger.error("Failed to compact messages:", error);
528
528
  throw error;
529
529
  }
530
530
  }