wave-agent-sdk 1.2.0 → 1.3.0
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/dist/agent.d.ts +58 -4
- package/dist/agent.js +91 -19
- package/dist/builtin/index.js +2 -0
- package/dist/builtin/skills/settings.js +1 -12
- package/dist/builtin/skills/wave-daemon.d.ts +1 -0
- package/dist/builtin/skills/wave-daemon.js +194 -0
- package/dist/constants/images.d.ts +26 -0
- package/dist/constants/images.js +26 -0
- package/dist/constants/index.d.ts +16 -0
- package/dist/constants/index.js +16 -0
- package/dist/constants/memory.d.ts +26 -0
- package/dist/constants/memory.js +34 -0
- package/dist/constants/messages.d.ts +11 -0
- package/dist/constants/messages.js +11 -0
- package/dist/constants/plugins.d.ts +8 -0
- package/dist/constants/plugins.js +8 -0
- package/dist/constants/tools.d.ts +1 -0
- package/dist/constants/tools.js +1 -0
- package/dist/core/plugin.d.ts +53 -13
- package/dist/core/plugin.js +134 -26
- package/dist/core/session.d.ts +1 -1
- package/dist/core/session.js +1 -1
- package/dist/exec/catalog.d.ts +140 -0
- package/dist/exec/catalog.js +470 -0
- package/dist/exec/catalogAnnouncement.d.ts +89 -0
- package/dist/exec/catalogAnnouncement.js +293 -0
- package/dist/exec/constants.d.ts +51 -0
- package/dist/exec/constants.js +51 -0
- package/dist/exec/execRuntime.d.ts +55 -0
- package/dist/exec/execRuntime.js +217 -0
- package/dist/exec/workerSource.d.ts +28 -0
- package/dist/exec/workerSource.js +299 -0
- package/dist/host/index.d.ts +23 -0
- package/dist/host/index.js +23 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/managers/MemoryRuleManager.d.ts +6 -0
- package/dist/managers/MemoryRuleManager.js +12 -0
- package/dist/managers/aiManager.d.ts +35 -1
- package/dist/managers/aiManager.js +190 -21
- package/dist/managers/backgroundTaskManager.js +14 -0
- package/dist/managers/hookManager.d.ts +13 -0
- package/dist/managers/hookManager.js +31 -4
- package/dist/managers/liveConfigManager.d.ts +33 -0
- package/dist/managers/liveConfigManager.js +103 -8
- package/dist/managers/lspManager.d.ts +9 -0
- package/dist/managers/lspManager.js +47 -18
- package/dist/managers/mcpManager.d.ts +45 -10
- package/dist/managers/mcpManager.js +103 -1
- package/dist/managers/messageManager.d.ts +48 -5
- package/dist/managers/messageManager.js +107 -21
- package/dist/managers/permissionManager.d.ts +40 -0
- package/dist/managers/permissionManager.js +63 -8
- package/dist/managers/pluginManager.d.ts +46 -2
- package/dist/managers/pluginManager.js +117 -11
- package/dist/managers/pluginScopeManager.d.ts +15 -2
- package/dist/managers/pluginScopeManager.js +20 -1
- package/dist/managers/skillManager.d.ts +19 -0
- package/dist/managers/skillManager.js +44 -0
- package/dist/managers/slashCommandManager.d.ts +10 -0
- package/dist/managers/slashCommandManager.js +35 -3
- package/dist/managers/subagentManager.d.ts +8 -0
- package/dist/managers/subagentManager.js +20 -0
- package/dist/managers/toolManager.d.ts +29 -3
- package/dist/managers/toolManager.js +87 -13
- package/dist/prompts/autoMemory.d.ts +9 -0
- package/dist/prompts/autoMemory.js +30 -31
- package/dist/prompts/autoMemoryExtraction.d.ts +4 -0
- package/dist/prompts/autoMemoryExtraction.js +8 -111
- package/dist/prompts/memoryTypes.d.ts +63 -0
- package/dist/prompts/memoryTypes.js +191 -0
- package/dist/services/GitService.d.ts +7 -0
- package/dist/services/GitService.js +23 -0
- package/dist/services/MarketplaceService.d.ts +101 -17
- package/dist/services/MarketplaceService.js +318 -119
- package/dist/services/artifactContent.d.ts +84 -0
- package/dist/services/artifactContent.js +204 -0
- package/dist/services/artifactSession.d.ts +6 -0
- package/dist/services/artifactSession.js +17 -0
- package/dist/services/autoMemoryService.js +5 -13
- package/dist/services/configurationService.d.ts +60 -9
- package/dist/services/configurationService.js +129 -54
- package/dist/services/contentSummarizer.d.ts +15 -0
- package/dist/services/contentSummarizer.js +45 -0
- package/dist/services/execAvailability.d.ts +9 -0
- package/dist/services/execAvailability.js +32 -0
- package/dist/services/fileWatcher.js +61 -6
- package/dist/services/initializationService.js +19 -15
- package/dist/services/interactionService.d.ts +9 -1
- package/dist/services/interactionService.js +28 -8
- package/dist/services/jsonlHandler.d.ts +84 -0
- package/dist/services/jsonlHandler.js +209 -14
- package/dist/services/memory.d.ts +3 -1
- package/dist/services/memory.js +13 -9
- package/dist/services/officialMarketplaceMirror.js +3 -2
- package/dist/services/pluginLoader.d.ts +12 -4
- package/dist/services/pluginLoader.js +38 -7
- package/dist/services/remoteSettingsService.js +16 -2
- package/dist/services/session.d.ts +74 -0
- package/dist/services/session.js +144 -3
- package/dist/services/sessionEntries.d.ts +2 -0
- package/dist/services/sessionEntries.js +20 -0
- package/dist/stdio/index.d.ts +3 -1
- package/dist/stdio/index.js +3 -1
- package/dist/stdio/notificationRouter.js +1 -0
- package/dist/stdio/stdioAgent.d.ts +14 -7
- package/dist/stdio/stdioAgent.js +19 -0
- package/dist/tools/artifactTool.js +406 -273
- package/dist/tools/bashTool.js +8 -6
- package/dist/tools/editTool.js +6 -3
- package/dist/tools/execTool.d.ts +2 -0
- package/dist/tools/execTool.js +165 -0
- package/dist/tools/grepTool.js +7 -1
- package/dist/tools/readTool.js +30 -2
- package/dist/tools/types.d.ts +34 -8
- package/dist/tools/webFetchTool.js +15 -166
- package/dist/tools/workflowTool.js +40 -8
- package/dist/tools/writeTool.js +6 -3
- package/dist/types/agent.d.ts +20 -5
- package/dist/types/configuration.d.ts +39 -1
- package/dist/types/marketplace.d.ts +40 -2
- package/dist/types/mcp.d.ts +39 -0
- package/dist/types/permissions.d.ts +22 -0
- package/dist/types/permissions.js +17 -0
- package/dist/types/plugins.d.ts +26 -2
- package/dist/types/skills.d.ts +15 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/containerSetup.js +43 -0
- package/dist/utils/convertMessagesForAPI.d.ts +7 -1
- package/dist/utils/convertMessagesForAPI.js +64 -14
- package/dist/utils/fileChangeReminder.d.ts +20 -0
- package/dist/utils/fileChangeReminder.js +153 -0
- package/dist/utils/fileSearch.js +4 -3
- package/dist/utils/fileUtils.d.ts +33 -0
- package/dist/utils/fileUtils.js +81 -0
- package/dist/utils/frontmatterYaml.d.ts +33 -0
- package/dist/utils/frontmatterYaml.js +192 -0
- package/dist/utils/imageBudget.d.ts +85 -0
- package/dist/utils/imageBudget.js +109 -0
- package/dist/utils/imageDimensions.d.ts +83 -0
- package/dist/utils/imageDimensions.js +232 -0
- package/dist/utils/imageProcessor.d.ts +66 -0
- package/dist/utils/imageProcessor.js +84 -0
- package/dist/utils/imageRewrite.d.ts +29 -0
- package/dist/utils/imageRewrite.js +251 -0
- package/dist/utils/markdownParser.d.ts +5 -1
- package/dist/utils/markdownParser.js +9 -51
- package/dist/utils/mcpInstructions.d.ts +61 -0
- package/dist/utils/mcpInstructions.js +126 -0
- package/dist/utils/mcpUtils.d.ts +7 -0
- package/dist/utils/mcpUtils.js +11 -2
- package/dist/utils/memoryAge.d.ts +32 -0
- package/dist/utils/memoryAge.js +47 -0
- package/dist/utils/memoryEntrypoint.d.ts +20 -0
- package/dist/utils/memoryEntrypoint.js +49 -0
- package/dist/utils/memoryIndex.d.ts +30 -0
- package/dist/utils/memoryIndex.js +76 -0
- package/dist/utils/messageOperations.d.ts +6 -2
- package/dist/utils/messageOperations.js +40 -29
- package/dist/utils/nestedMemory.d.ts +22 -0
- package/dist/utils/nestedMemory.js +61 -0
- package/dist/utils/npmTarball.d.ts +19 -0
- package/dist/utils/npmTarball.js +92 -0
- package/dist/utils/pluginSource.d.ts +37 -0
- package/dist/utils/pluginSource.js +73 -0
- package/dist/utils/ripgrep.d.ts +18 -4
- package/dist/utils/ripgrep.js +56 -4
- package/dist/utils/runtimeDeps.d.ts +35 -0
- package/dist/utils/runtimeDeps.js +426 -0
- package/dist/utils/skillParser.js +22 -52
- package/dist/utils/subagentParser.js +39 -43
- package/dist/utils/userSettings.d.ts +90 -0
- package/dist/utils/userSettings.js +291 -0
- package/package.json +10 -7
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { promises as fs, existsSync, mkdirSync } from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
4
5
|
import { getPluginsDir } from "../utils/configPaths.js";
|
|
5
6
|
import { GitService } from "./GitService.js";
|
|
7
|
+
import { parsePluginSource, describePluginSource, } from "../utils/pluginSource.js";
|
|
8
|
+
import { DEFAULT_PLUGIN_VERSION } from "../constants/plugins.js";
|
|
6
9
|
import { ALLOW_OFFICIAL_MARKET_GIT_FALLBACK, fetchOfficialMarketplaceFromMirror, } from "./officialMarketplaceMirror.js";
|
|
7
10
|
import { ConfigurationService } from "./configurationService.js";
|
|
8
11
|
import { logger, logError, logWarn } from "../utils/globalLogger.js";
|
|
@@ -17,6 +20,8 @@ import { logger, logError, logWarn } from "../utils/globalLogger.js";
|
|
|
17
20
|
*/
|
|
18
21
|
export class MarketplaceService {
|
|
19
22
|
constructor(workdir = process.cwd(), configurationService = new ConfigurationService()) {
|
|
23
|
+
/** In-flight [refreshMarketplaces] call (single-flight, spec 插件市场 场景 13). */
|
|
24
|
+
this.refreshInFlight = null;
|
|
20
25
|
this.workdir = workdir;
|
|
21
26
|
this.configurationService = configurationService;
|
|
22
27
|
this.pluginsDir = getPluginsDir();
|
|
@@ -59,7 +64,6 @@ export class MarketplaceService {
|
|
|
59
64
|
continue;
|
|
60
65
|
const config = {
|
|
61
66
|
source: m.source,
|
|
62
|
-
autoUpdate: m.autoUpdate,
|
|
63
67
|
};
|
|
64
68
|
await this.configurationService.addMarketplaceToScope(this.workdir, "user", m.name, config);
|
|
65
69
|
}
|
|
@@ -82,12 +86,11 @@ export class MarketplaceService {
|
|
|
82
86
|
// Add to user-scope settings if not already in any scope
|
|
83
87
|
const scoped = this.configurationService.getMergedMarketplaces(this.workdir);
|
|
84
88
|
if (!scoped[builtin.name]) {
|
|
85
|
-
await this.configurationService.addMarketplaceToScope(this.workdir, "user", builtin.name, { source: builtin.source
|
|
89
|
+
await this.configurationService.addMarketplaceToScope(this.workdir, "user", builtin.name, { source: builtin.source });
|
|
86
90
|
}
|
|
87
91
|
// Update cache: add entry + set builtinSeeded flag
|
|
88
92
|
await this.updateCacheMarketplace(builtin.name, {
|
|
89
93
|
source: builtin.source,
|
|
90
|
-
autoUpdate: builtin.autoUpdate,
|
|
91
94
|
isBuiltin: true,
|
|
92
95
|
declaredScope: "builtin",
|
|
93
96
|
});
|
|
@@ -133,10 +136,10 @@ export class MarketplaceService {
|
|
|
133
136
|
}
|
|
134
137
|
/**
|
|
135
138
|
* Acquires a file-based lock (with PID tracking for stale lock detection) and executes the provided function.
|
|
136
|
-
* Supports re-entrancy within the same
|
|
139
|
+
* Supports re-entrancy within the same call chain (see [lockChain]).
|
|
137
140
|
*/
|
|
138
141
|
async withLock(fn) {
|
|
139
|
-
if (MarketplaceService.
|
|
142
|
+
if (MarketplaceService.lockChain.getStore()) {
|
|
140
143
|
return await fn();
|
|
141
144
|
}
|
|
142
145
|
let lockFd;
|
|
@@ -169,12 +172,10 @@ export class MarketplaceService {
|
|
|
169
172
|
throw new Error(`Failed to acquire marketplace lock after ${maxRetries} retries. If no other wave-agent process is running, please delete ${this.lockPath}`);
|
|
170
173
|
}
|
|
171
174
|
await fs.writeFile(this.lockPath, String(process.pid), "utf-8");
|
|
172
|
-
MarketplaceService.isLockedInProcess = true;
|
|
173
175
|
try {
|
|
174
|
-
return await fn
|
|
176
|
+
return await MarketplaceService.lockChain.run(true, fn);
|
|
175
177
|
}
|
|
176
178
|
finally {
|
|
177
|
-
MarketplaceService.isLockedInProcess = false;
|
|
178
179
|
await lockFd.close();
|
|
179
180
|
await fs.unlink(this.lockPath).catch(() => { });
|
|
180
181
|
}
|
|
@@ -283,6 +284,36 @@ export class MarketplaceService {
|
|
|
283
284
|
await fs.writeFile(tmpPath, JSON.stringify(registry, null, 2));
|
|
284
285
|
await fs.rename(tmpPath, this.installedPluginsPath);
|
|
285
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Whether an install record belongs to the given install location.
|
|
289
|
+
*
|
|
290
|
+
* Records written before scopes were tracked carry no `scope`; they stand for
|
|
291
|
+
* "installed once on this machine" and match any scope, so uninstalling them
|
|
292
|
+
* keeps working (they are that plugin's only record). They still match by
|
|
293
|
+
* project path, so records of two different projects stay distinguishable.
|
|
294
|
+
*/
|
|
295
|
+
matchesInstallLocation(entry, location) {
|
|
296
|
+
if (!entry.scope) {
|
|
297
|
+
return (entry.projectPath === undefined ||
|
|
298
|
+
entry.projectPath === location.projectPath);
|
|
299
|
+
}
|
|
300
|
+
return (entry.scope === location.scope &&
|
|
301
|
+
entry.projectPath === location.projectPath);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The install location a record is keyed by. Scope-less legacy records are
|
|
305
|
+
* treated as user-scope when they have to be re-created (e.g. by an update).
|
|
306
|
+
*/
|
|
307
|
+
locationOf(entry) {
|
|
308
|
+
return { scope: entry.scope ?? "user", projectPath: entry.projectPath };
|
|
309
|
+
}
|
|
310
|
+
describeInstallLocations(entries) {
|
|
311
|
+
return entries
|
|
312
|
+
.map((entry) => entry.scope
|
|
313
|
+
? `${entry.scope}${entry.projectPath ? ` (${entry.projectPath})` : ""}`
|
|
314
|
+
: "unscoped")
|
|
315
|
+
.join(", ");
|
|
316
|
+
}
|
|
286
317
|
/**
|
|
287
318
|
* Loads a marketplace manifest from a local path
|
|
288
319
|
*/
|
|
@@ -326,7 +357,6 @@ export class MarketplaceService {
|
|
|
326
357
|
return {
|
|
327
358
|
name,
|
|
328
359
|
source: config.source,
|
|
329
|
-
autoUpdate: config.autoUpdate ?? cache?.autoUpdate,
|
|
330
360
|
lastUpdated: cache?.lastUpdated,
|
|
331
361
|
isBuiltin: name === MarketplaceService.BUILTIN_MARKETPLACE.name,
|
|
332
362
|
declaredScope,
|
|
@@ -346,6 +376,53 @@ export class MarketplaceService {
|
|
|
346
376
|
return "builtin";
|
|
347
377
|
return null;
|
|
348
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* 已注册的市场(作用域配置 + 缓存注册表合并去重),用于添加前的判重
|
|
381
|
+
*/
|
|
382
|
+
async listRegisteredMarketplaces() {
|
|
383
|
+
const scoped = this.configurationService.getMergedMarketplaces(this.workdir);
|
|
384
|
+
const registered = Object.entries(scoped).map(([name, config]) => ({
|
|
385
|
+
name,
|
|
386
|
+
source: config.source,
|
|
387
|
+
}));
|
|
388
|
+
const cache = await this.getCacheRegistry();
|
|
389
|
+
for (const entry of cache?.marketplaces ?? []) {
|
|
390
|
+
if (!registered.some((m) => m.name === entry.name)) {
|
|
391
|
+
registered.push({ name: entry.name, source: entry.source });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return registered;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* 添加市场前判重(spec ecosystem/plugin「设置页插件市场」场景 16):来源重复或
|
|
398
|
+
* 清单声明的名称与已有市场同名时不允许添加。只传 source 时仅判来源(用于克隆
|
|
399
|
+
* 之前,避免为一个已存在的来源白克隆一次)。
|
|
400
|
+
*/
|
|
401
|
+
async assertMarketplaceAddable(source, name) {
|
|
402
|
+
const registered = await this.listRegisteredMarketplaces();
|
|
403
|
+
const sameSource = registered.find((m) => MarketplaceService.isSameMarketplaceSource(m.source, source));
|
|
404
|
+
if (sameSource) {
|
|
405
|
+
throw new Error(`该市场来源已添加:${sameSource.name}`);
|
|
406
|
+
}
|
|
407
|
+
if (name && registered.some((m) => m.name === name)) {
|
|
408
|
+
throw new Error(`同名市场已存在:${name}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/** 两个市场来源是否指向同一处(同类型比 url/repo/path,忽略 ref) */
|
|
412
|
+
static isSameMarketplaceSource(a, b) {
|
|
413
|
+
if (a.source !== b.source)
|
|
414
|
+
return false;
|
|
415
|
+
if (a.source === "directory" && b.source === "directory") {
|
|
416
|
+
return a.path === b.path;
|
|
417
|
+
}
|
|
418
|
+
if (a.source === "github" && b.source === "github") {
|
|
419
|
+
return a.repo === b.repo;
|
|
420
|
+
}
|
|
421
|
+
if (a.source === "git" && b.source === "git") {
|
|
422
|
+
return a.url === b.url;
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
349
426
|
/**
|
|
350
427
|
* Adds a new marketplace (local directory, GitHub repo, or Git URL)
|
|
351
428
|
*/
|
|
@@ -368,6 +445,7 @@ export class MarketplaceService {
|
|
|
368
445
|
const tempSource = isFullUrl
|
|
369
446
|
? { source: "git", url: urlOrRepo, ref }
|
|
370
447
|
: { source: "github", repo: urlOrRepo, ref };
|
|
448
|
+
await this.assertMarketplaceAddable(tempSource);
|
|
371
449
|
const targetPath = this.getMarketplacePath(tempSource);
|
|
372
450
|
if (!existsSync(targetPath)) {
|
|
373
451
|
try {
|
|
@@ -389,12 +467,15 @@ export class MarketplaceService {
|
|
|
389
467
|
source: isFullUrl
|
|
390
468
|
? { source: "git", url: urlOrRepo, ref }
|
|
391
469
|
: { source: "github", repo: urlOrRepo, ref },
|
|
392
|
-
autoUpdate: false,
|
|
393
470
|
lastUpdated: new Date().toISOString(),
|
|
394
471
|
};
|
|
395
472
|
}
|
|
396
473
|
else {
|
|
397
474
|
const absolutePath = path.resolve(input);
|
|
475
|
+
await this.assertMarketplaceAddable({
|
|
476
|
+
source: "directory",
|
|
477
|
+
path: absolutePath,
|
|
478
|
+
});
|
|
398
479
|
let manifest;
|
|
399
480
|
try {
|
|
400
481
|
manifest = await this.loadMarketplaceManifest(absolutePath);
|
|
@@ -405,19 +486,18 @@ export class MarketplaceService {
|
|
|
405
486
|
marketplace = {
|
|
406
487
|
name: manifest.name,
|
|
407
488
|
source: { source: "directory", path: absolutePath },
|
|
408
|
-
autoUpdate: false,
|
|
409
489
|
lastUpdated: new Date().toISOString(),
|
|
410
490
|
};
|
|
411
491
|
}
|
|
492
|
+
// 名称来自市场自身清单,加载后才知道 → 同名判重放在这里(来源判重已在分支内)
|
|
493
|
+
await this.assertMarketplaceAddable(marketplace.source, marketplace.name);
|
|
412
494
|
const config = {
|
|
413
495
|
source: marketplace.source,
|
|
414
|
-
autoUpdate: marketplace.autoUpdate,
|
|
415
496
|
};
|
|
416
497
|
await this.configurationService.addMarketplaceToScope(this.workdir, scope, marketplace.name, config);
|
|
417
498
|
// Update cache with metadata
|
|
418
499
|
await this.updateCacheMarketplace(marketplace.name, {
|
|
419
500
|
source: marketplace.source,
|
|
420
|
-
autoUpdate: marketplace.autoUpdate,
|
|
421
501
|
lastUpdated: marketplace.lastUpdated,
|
|
422
502
|
});
|
|
423
503
|
return marketplace;
|
|
@@ -497,8 +577,18 @@ export class MarketplaceService {
|
|
|
497
577
|
}
|
|
498
578
|
/**
|
|
499
579
|
* Updates a specific marketplace or all marketplaces
|
|
580
|
+
*
|
|
581
|
+
* `refreshCheckout` (default true) pulls the checkout from its upstream; pass
|
|
582
|
+
* false to upgrade plugins against the current checkout only — the interactive
|
|
583
|
+
* "批量更新插件" path does that, because opening the plugin-marketplace surface
|
|
584
|
+
* already refreshed the checkout (spec 插件市场 A-013).
|
|
585
|
+
*
|
|
586
|
+
* Returns the number of installed plugins whose version actually changed
|
|
587
|
+
* during this run (0 = everything was already up to date) — the GUI hosts
|
|
588
|
+
* use it to tell "已更新 N 个插件" apart from "已是最新" (spec 插件市场 场景 13).
|
|
500
589
|
*/
|
|
501
590
|
async updateMarketplace(name, options) {
|
|
591
|
+
const refreshCheckout = options?.refreshCheckout ?? true;
|
|
502
592
|
return this.withLock(async () => {
|
|
503
593
|
const marketplaces = await this.listMarketplaces();
|
|
504
594
|
const toUpdate = name
|
|
@@ -509,65 +599,76 @@ export class MarketplaceService {
|
|
|
509
599
|
}
|
|
510
600
|
const isGitAvailable = await this.gitService.isGitAvailable();
|
|
511
601
|
const errors = [];
|
|
602
|
+
let upgradedPlugins = 0;
|
|
512
603
|
for (const marketplace of toUpdate) {
|
|
513
604
|
try {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
if (marketplace.source.source === "github" ||
|
|
534
|
-
marketplace.source.source === "git") {
|
|
535
|
-
if (!mirrorUpdated) {
|
|
536
|
-
if (!isGitAvailable) {
|
|
537
|
-
logWarn(`Skipping update for Git/GitHub marketplace "${marketplace.name}" because Git is not installed.`);
|
|
538
|
-
continue;
|
|
605
|
+
const targetPath = this.getMarketplacePath(marketplace.source);
|
|
606
|
+
if (refreshCheckout) {
|
|
607
|
+
// Builtin official marketplace: prefer the zip-snapshot mirror
|
|
608
|
+
// (content-addressed zip over a plain HTTP base, no git/GitHub
|
|
609
|
+
// needed). Only the builtin is special-cased by name — its
|
|
610
|
+
// `source` stays "github" in settings/cache, so there is zero
|
|
611
|
+
// data migration. Both envs' mirror channels are live and carry
|
|
612
|
+
// content (verified 2026-09-23); on mirror failure
|
|
613
|
+
// (network/timeout/404/bad content) fall back to the git path
|
|
614
|
+
// unless the kill switch forbids it.
|
|
615
|
+
const isOfficialBuiltin = marketplace.name === MarketplaceService.BUILTIN_MARKETPLACE.name;
|
|
616
|
+
let mirrorUpdated = false;
|
|
617
|
+
if (isOfficialBuiltin) {
|
|
618
|
+
const mirrorSha = await fetchOfficialMarketplaceFromMirror(targetPath, this.marketplacesDir);
|
|
619
|
+
if (mirrorSha !== null) {
|
|
620
|
+
mirrorUpdated = true;
|
|
539
621
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
622
|
+
else if (!ALLOW_OFFICIAL_MARKET_GIT_FALLBACK) {
|
|
623
|
+
logWarn(`Skipping update for official marketplace "${marketplace.name}": mirror fetch failed and git fallback is disabled.`);
|
|
624
|
+
continue;
|
|
543
625
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
626
|
+
}
|
|
627
|
+
if (marketplace.source.source === "github" ||
|
|
628
|
+
marketplace.source.source === "git") {
|
|
629
|
+
if (!mirrorUpdated) {
|
|
630
|
+
if (!isGitAvailable) {
|
|
631
|
+
logWarn(`Skipping update for Git/GitHub marketplace "${marketplace.name}" because Git is not installed.`);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (existsSync(targetPath)) {
|
|
635
|
+
await this.gitService.pull(targetPath);
|
|
548
636
|
}
|
|
549
637
|
else {
|
|
550
|
-
|
|
638
|
+
let url;
|
|
639
|
+
if (marketplace.source.source === "github") {
|
|
640
|
+
url = marketplace.source.repo;
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
url = marketplace.source.url;
|
|
644
|
+
}
|
|
645
|
+
await this.gitService.clone(url, targetPath, marketplace.source.ref);
|
|
551
646
|
}
|
|
552
|
-
await this.gitService.clone(url, targetPath, marketplace.source.ref);
|
|
553
647
|
}
|
|
554
648
|
}
|
|
555
649
|
}
|
|
556
|
-
const manifest = await this.loadMarketplaceManifest(
|
|
557
|
-
|
|
558
|
-
//
|
|
559
|
-
|
|
560
|
-
lastUpdated
|
|
561
|
-
|
|
650
|
+
const manifest = await this.loadMarketplaceManifest(targetPath);
|
|
651
|
+
// 只在真的刷了检出时记账:跳过刷新时把 lastUpdated 顶到当前时间会
|
|
652
|
+
// 让 CLI 的市场列表谎报「上次更新」时间。
|
|
653
|
+
if (refreshCheckout) {
|
|
654
|
+
marketplace.lastUpdated = new Date().toISOString();
|
|
655
|
+
// Update cache metadata
|
|
656
|
+
await this.updateCacheMarketplace(marketplace.name, {
|
|
657
|
+
lastUpdated: marketplace.lastUpdated,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
562
660
|
if (options?.updatePlugins) {
|
|
563
661
|
const installedRegistry = await this.getInstalledPlugins();
|
|
564
662
|
const pluginsToUpdate = installedRegistry.plugins.filter((p) => p.marketplace === marketplace.name);
|
|
663
|
+
// 升级前版本快照:安装完成后比对,只有版本真的变了的才算「已更新」
|
|
664
|
+
// (重新安装同版本 = 无更新,宿主据此提示「已是最新」)。
|
|
665
|
+
const versionBefore = new Map(pluginsToUpdate.map((p) => [p.name, p.version]));
|
|
565
666
|
for (const plugin of pluginsToUpdate) {
|
|
566
667
|
const pluginEntry = manifest.plugins.find((p) => p.name === plugin.name);
|
|
567
668
|
if (!pluginEntry) {
|
|
568
669
|
logger.warn(`Plugin "${plugin.name}" no longer found in marketplace "${marketplace.name}". Uninstalling...`);
|
|
569
670
|
try {
|
|
570
|
-
await this.uninstallPlugin(`${plugin.name}@${plugin.marketplace}`, plugin
|
|
671
|
+
await this.uninstallPlugin(`${plugin.name}@${plugin.marketplace}`, this.locationOf(plugin));
|
|
571
672
|
}
|
|
572
673
|
catch (error) {
|
|
573
674
|
logError(`Failed to uninstall orphaned plugin "${plugin.name}" from marketplace "${marketplace.name}":`, error);
|
|
@@ -575,12 +676,21 @@ export class MarketplaceService {
|
|
|
575
676
|
continue;
|
|
576
677
|
}
|
|
577
678
|
try {
|
|
578
|
-
await this.installPlugin(`${plugin.name}@${plugin.marketplace}`, plugin
|
|
679
|
+
await this.installPlugin(`${plugin.name}@${plugin.marketplace}`, this.locationOf(plugin));
|
|
579
680
|
}
|
|
580
681
|
catch (error) {
|
|
581
682
|
logError(`Failed to update plugin "${plugin.name}" from marketplace "${marketplace.name}":`, error);
|
|
582
683
|
}
|
|
583
684
|
}
|
|
685
|
+
const afterRegistry = await this.getInstalledPlugins();
|
|
686
|
+
for (const plugin of afterRegistry.plugins) {
|
|
687
|
+
if (plugin.marketplace !== marketplace.name)
|
|
688
|
+
continue;
|
|
689
|
+
const before = versionBefore.get(plugin.name);
|
|
690
|
+
if (before !== undefined && before !== plugin.version) {
|
|
691
|
+
upgradedPlugins++;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
584
694
|
}
|
|
585
695
|
}
|
|
586
696
|
catch (error) {
|
|
@@ -592,53 +702,63 @@ export class MarketplaceService {
|
|
|
592
702
|
if (errors.length > 0) {
|
|
593
703
|
throw new Error(`Some marketplaces failed to update:\n${errors.join("\n")}`);
|
|
594
704
|
}
|
|
705
|
+
return upgradedPlugins;
|
|
595
706
|
});
|
|
596
707
|
}
|
|
597
708
|
/**
|
|
598
|
-
*
|
|
709
|
+
* 交互界面的「批量更新插件」:按当前清单把该市场内所有已安装且有新版本的插件
|
|
710
|
+
* 升级到最新,**不拉取检出**——清单的新鲜度由打开插件市场界面时的那次刷新保证
|
|
711
|
+
* (spec 插件市场 A-013/场景 14)。检出尚未据此刷新的场景(无界面可依赖的非交互
|
|
712
|
+
* 命令)走 [updateMarketplace] 的默认行为。
|
|
599
713
|
*/
|
|
600
|
-
async
|
|
601
|
-
return this.
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
.filter(([, config]) => config.autoUpdate)
|
|
605
|
-
.map(([name]) => name);
|
|
606
|
-
for (const marketplaceName of toAutoUpdate) {
|
|
607
|
-
try {
|
|
608
|
-
await this.updateMarketplace(marketplaceName, {
|
|
609
|
-
updatePlugins: true,
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
catch (error) {
|
|
613
|
-
logError(`Auto-update failed for marketplace "${marketplaceName}":`, error);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
714
|
+
async updateMarketplacePlugins(name) {
|
|
715
|
+
return await this.updateMarketplace(name, {
|
|
716
|
+
updatePlugins: true,
|
|
717
|
+
refreshCheckout: false,
|
|
616
718
|
});
|
|
617
719
|
}
|
|
618
720
|
/**
|
|
619
|
-
*
|
|
721
|
+
* Refreshes the local checkout of every registered marketplace.
|
|
722
|
+
*
|
|
723
|
+
* Callers are the plugin-marketplace surfaces (GUI settings view / CLI plugin
|
|
724
|
+
* manager), refreshed when the user opens them — the agent-startup path no
|
|
725
|
+
* longer refreshes anything (spec 插件市场 A-013 场景 5).
|
|
726
|
+
*
|
|
727
|
+
* This is a fixed behavior with no opt-in/opt-out: `autoUpdate` no longer
|
|
728
|
+
* exists, so all marketplaces are refreshed alike (A-014). Refreshing only
|
|
729
|
+
* updates the checkout — installed plugins are never reinstalled here, so a
|
|
730
|
+
* newer plugin version surfaces as an explicit "更新" (per plugin) /
|
|
731
|
+
* "批量更新插件" (batch) action instead of being applied behind the user's
|
|
732
|
+
* back.
|
|
733
|
+
*
|
|
734
|
+
* Single-flight: a call landing while a refresh is running reuses it instead
|
|
735
|
+
* of pulling every checkout twice (spec 场景 13).
|
|
620
736
|
*/
|
|
621
|
-
async
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
737
|
+
async refreshMarketplaces() {
|
|
738
|
+
if (this.refreshInFlight)
|
|
739
|
+
return this.refreshInFlight;
|
|
740
|
+
this.refreshInFlight = this.withLock(async () => {
|
|
741
|
+
const marketplaces = this.configurationService.getMergedMarketplaces(this.workdir);
|
|
742
|
+
for (const marketplaceName of Object.keys(marketplaces)) {
|
|
743
|
+
try {
|
|
744
|
+
await this.updateMarketplace(marketplaceName);
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
logError(`Marketplace refresh failed for "${marketplaceName}":`, error);
|
|
748
|
+
}
|
|
631
749
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
// Also update cache
|
|
635
|
-
await this.updateCacheMarketplace(name, { autoUpdate: enabled });
|
|
750
|
+
}).finally(() => {
|
|
751
|
+
this.refreshInFlight = null;
|
|
636
752
|
});
|
|
753
|
+
return this.refreshInFlight;
|
|
637
754
|
}
|
|
638
755
|
/**
|
|
639
756
|
* Installs a plugin from a marketplace
|
|
757
|
+
*
|
|
758
|
+
* @param location 该次安装记账的位置(spec plugin A-015)。缺省只在内部调用
|
|
759
|
+
* (宿主加载时补装)出现,此时写入一条不带作用域的历史形态记录。
|
|
640
760
|
*/
|
|
641
|
-
async installPlugin(pluginAtMarketplace,
|
|
761
|
+
async installPlugin(pluginAtMarketplace, location) {
|
|
642
762
|
return this.withLock(async () => {
|
|
643
763
|
const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
|
|
644
764
|
if (!pluginName || !marketplaceName) {
|
|
@@ -655,25 +775,32 @@ export class MarketplaceService {
|
|
|
655
775
|
if (!pluginEntry) {
|
|
656
776
|
throw new Error(`Plugin ${pluginName} not found in marketplace ${marketplaceName}`);
|
|
657
777
|
}
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
pluginEntry.source
|
|
661
|
-
|
|
778
|
+
const parsedSource = parsePluginSource(pluginEntry.source);
|
|
779
|
+
if (!parsedSource) {
|
|
780
|
+
throw new Error(`Plugin ${pluginName} in marketplace ${marketplaceName} declares an unsupported source: ${describePluginSource(pluginEntry.source)}`);
|
|
781
|
+
}
|
|
782
|
+
const isGitSource = parsedSource.kind === "git";
|
|
662
783
|
let pluginSrcPath;
|
|
663
784
|
let tempCloneDir;
|
|
664
785
|
try {
|
|
665
|
-
if (
|
|
786
|
+
if (parsedSource.kind === "git") {
|
|
666
787
|
tempCloneDir = path.join(this.tmpDir, `clone-${Date.now()}`);
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
788
|
+
await this.gitService.clone(parsedSource.url, tempCloneDir, parsedSource.ref);
|
|
789
|
+
if (parsedSource.sha) {
|
|
790
|
+
// Without this a `ref` would drift with its branch; a sha that cannot
|
|
791
|
+
// be checked out fails loudly instead of silently using the branch tip
|
|
792
|
+
// (spec plugin A-021 场景 3).
|
|
793
|
+
await this.gitService.checkout(tempCloneDir, parsedSource.sha);
|
|
794
|
+
}
|
|
795
|
+
pluginSrcPath = parsedSource.subdir
|
|
796
|
+
? path.join(tempCloneDir, parsedSource.subdir)
|
|
797
|
+
: tempCloneDir;
|
|
798
|
+
if (parsedSource.subdir && !existsSync(pluginSrcPath)) {
|
|
799
|
+
throw new Error(`Subdirectory '${parsedSource.subdir}' not found in repository ${parsedSource.url}`);
|
|
671
800
|
}
|
|
672
|
-
await this.gitService.clone(url, tempCloneDir, ref);
|
|
673
|
-
pluginSrcPath = tempCloneDir;
|
|
674
801
|
}
|
|
675
802
|
else {
|
|
676
|
-
pluginSrcPath = path.resolve(marketplacePath,
|
|
803
|
+
pluginSrcPath = path.resolve(marketplacePath, parsedSource.path);
|
|
677
804
|
}
|
|
678
805
|
let pluginManifestPath = path.join(pluginSrcPath, ".wave-plugin", "plugin.json");
|
|
679
806
|
if (!existsSync(pluginManifestPath)) {
|
|
@@ -685,7 +812,7 @@ export class MarketplaceService {
|
|
|
685
812
|
}
|
|
686
813
|
const pluginManifestContent = await fs.readFile(pluginManifestPath, "utf-8");
|
|
687
814
|
const pluginManifest = JSON.parse(pluginManifestContent);
|
|
688
|
-
const version = pluginManifest.version ||
|
|
815
|
+
const version = pluginManifest.version || DEFAULT_PLUGIN_VERSION;
|
|
689
816
|
const tmpPluginDir = path.join(this.tmpDir, `${pluginName}-${Date.now()}`);
|
|
690
817
|
try {
|
|
691
818
|
if (isGitSource) {
|
|
@@ -704,13 +831,14 @@ export class MarketplaceService {
|
|
|
704
831
|
const installedRegistry = await this.getInstalledPlugins();
|
|
705
832
|
const existingIndex = installedRegistry.plugins.findIndex((p) => p.name === pluginName &&
|
|
706
833
|
p.marketplace === marketplaceName &&
|
|
707
|
-
p
|
|
834
|
+
(location ? this.matchesInstallLocation(p, location) : !p.scope));
|
|
708
835
|
const installedPlugin = {
|
|
709
836
|
name: pluginName,
|
|
710
837
|
marketplace: marketplaceName,
|
|
711
838
|
version,
|
|
712
839
|
cachePath,
|
|
713
|
-
|
|
840
|
+
scope: location?.scope,
|
|
841
|
+
projectPath: location?.projectPath,
|
|
714
842
|
};
|
|
715
843
|
if (existingIndex >= 0) {
|
|
716
844
|
installedRegistry.plugins[existingIndex] = installedPlugin;
|
|
@@ -738,45 +866,116 @@ export class MarketplaceService {
|
|
|
738
866
|
}
|
|
739
867
|
/**
|
|
740
868
|
* Uninstalls a plugin
|
|
869
|
+
*
|
|
870
|
+
* With a [location], only that scope's install record is removed — other
|
|
871
|
+
* scopes (including other projects' project/local records) keep theirs, and
|
|
872
|
+
* the shared cache directory is deleted only once no record references it
|
|
873
|
+
* (spec plugin A-015). Without one, every record of the plugin is removed
|
|
874
|
+
* (used by [updatePlugin] and the marketplace-level cleanup).
|
|
741
875
|
*/
|
|
742
|
-
async uninstallPlugin(pluginAtMarketplace,
|
|
876
|
+
async uninstallPlugin(pluginAtMarketplace, location) {
|
|
743
877
|
return this.withLock(async () => {
|
|
744
878
|
const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
|
|
745
879
|
if (!pluginName || !marketplaceName) {
|
|
746
880
|
throw new Error("Invalid plugin format. Use name@marketplace");
|
|
747
881
|
}
|
|
748
882
|
const installedRegistry = await this.getInstalledPlugins();
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
throw new Error(
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
883
|
+
const allRecords = installedRegistry.plugins.filter((p) => p.name === pluginName && p.marketplace === marketplaceName);
|
|
884
|
+
const pluginToRemove = allRecords.filter((p) => !location || this.matchesInstallLocation(p, location));
|
|
885
|
+
if (pluginToRemove.length === 0) {
|
|
886
|
+
const installedAt = this.describeInstallLocations(allRecords);
|
|
887
|
+
throw new Error(location
|
|
888
|
+
? `Plugin ${pluginName}@${marketplaceName} is not installed in ${location.scope} scope${location.projectPath ? ` for project ${location.projectPath}` : ""}${installedAt ? `. Installed at: ${installedAt}` : ""}`
|
|
889
|
+
: `Plugin ${pluginName}@${marketplaceName} is not installed`);
|
|
890
|
+
}
|
|
891
|
+
const removed = new Set(pluginToRemove);
|
|
892
|
+
installedRegistry.plugins = installedRegistry.plugins.filter((p) => !removed.has(p));
|
|
757
893
|
await this.saveInstalledPlugins(installedRegistry);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
894
|
+
for (const plugin of pluginToRemove) {
|
|
895
|
+
const isStillReferenced = installedRegistry.plugins.some((p) => p.cachePath === plugin.cachePath);
|
|
896
|
+
if (!isStillReferenced && existsSync(plugin.cachePath)) {
|
|
897
|
+
await fs.rm(plugin.cachePath, { recursive: true, force: true });
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Moves a plugin's install record to another location(「更换安装作用域」):
|
|
904
|
+
* the record currently at [from] (or the plugin's only record when [from] is
|
|
905
|
+
* omitted or unknown) is rewritten to [to], so that uninstalling the plugin
|
|
906
|
+
* at its new scope still finds it (spec plugin A-015). A duplicate record
|
|
907
|
+
* already at [to] wins and the moved one is dropped.
|
|
908
|
+
*/
|
|
909
|
+
async relocatePlugin(pluginAtMarketplace, from, to) {
|
|
910
|
+
return this.withLock(async () => {
|
|
911
|
+
const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
|
|
912
|
+
if (!pluginName || !marketplaceName) {
|
|
913
|
+
throw new Error("Invalid plugin format. Use name@marketplace");
|
|
914
|
+
}
|
|
915
|
+
const installedRegistry = await this.getInstalledPlugins();
|
|
916
|
+
const records = installedRegistry.plugins.filter((p) => p.name === pluginName && p.marketplace === marketplaceName);
|
|
917
|
+
const source = from
|
|
918
|
+
? records.find((p) => this.matchesInstallLocation(p, from))
|
|
919
|
+
: records[0];
|
|
920
|
+
if (!source)
|
|
921
|
+
return;
|
|
922
|
+
const duplicate = records.find((p) => p !== source && this.matchesInstallLocation(p, to));
|
|
923
|
+
if (duplicate) {
|
|
924
|
+
installedRegistry.plugins = installedRegistry.plugins.filter((p) => p !== source);
|
|
761
925
|
}
|
|
926
|
+
else {
|
|
927
|
+
source.scope = to.scope;
|
|
928
|
+
source.projectPath = to.projectPath;
|
|
929
|
+
}
|
|
930
|
+
await this.saveInstalledPlugins(installedRegistry);
|
|
762
931
|
});
|
|
763
932
|
}
|
|
764
933
|
/**
|
|
765
|
-
* Updates a plugin (uninstall followed by install)
|
|
934
|
+
* Updates a plugin (uninstall followed by install). Every install record of
|
|
935
|
+
* the plugin is re-created at its original location, so the install scopes
|
|
936
|
+
* stay unchanged (spec plugin「设置页插件市场」场景 12 / A-015).
|
|
766
937
|
*/
|
|
767
938
|
async updatePlugin(pluginAtMarketplace) {
|
|
768
939
|
return this.withLock(async () => {
|
|
940
|
+
const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
|
|
941
|
+
const before = await this.getInstalledPlugins();
|
|
942
|
+
const locations = before.plugins
|
|
943
|
+
.filter((p) => p.name === pluginName && p.marketplace === marketplaceName)
|
|
944
|
+
.map((p) => this.locationOf(p));
|
|
769
945
|
await this.uninstallPlugin(pluginAtMarketplace);
|
|
770
|
-
|
|
946
|
+
const installed = await this.installPlugin(pluginAtMarketplace, locations[0] ?? { scope: "user" });
|
|
947
|
+
if (locations.length > 1) {
|
|
948
|
+
const restored = await this.getInstalledPlugins();
|
|
949
|
+
for (const location of locations.slice(1)) {
|
|
950
|
+
restored.plugins.push({
|
|
951
|
+
name: installed.name,
|
|
952
|
+
marketplace: installed.marketplace,
|
|
953
|
+
version: installed.version,
|
|
954
|
+
cachePath: installed.cachePath,
|
|
955
|
+
scope: location.scope,
|
|
956
|
+
projectPath: location.projectPath,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
await this.saveInstalledPlugins(restored);
|
|
960
|
+
}
|
|
961
|
+
return installed;
|
|
771
962
|
});
|
|
772
963
|
}
|
|
773
964
|
}
|
|
774
|
-
|
|
965
|
+
/**
|
|
966
|
+
* 已持有市场操作锁的调用链标记(重入判据)。
|
|
967
|
+
*
|
|
968
|
+
* 用 AsyncLocalStorage 而不是进程内布尔值:锁内发起的嵌套调用(如
|
|
969
|
+
* refreshMarketplaces → updateMarketplace → installPlugin)必须跳过再次加锁,
|
|
970
|
+
* 否则会自锁;但**另一条并发调用链**必须老老实实排队。布尔值无法区分这两者
|
|
971
|
+
* ——第二个并发调用会以为「已在锁内」而跳过锁直接执行,于是两个操作并行改同一
|
|
972
|
+
* 个市场检出(并发两次 `git pull` 同一检出必然失败,spec 插件市场 场景 14)。
|
|
973
|
+
*/
|
|
974
|
+
MarketplaceService.lockChain = new AsyncLocalStorage();
|
|
775
975
|
MarketplaceService.BUILTIN_MARKETPLACE = {
|
|
776
976
|
name: "wave-plugins-official",
|
|
777
977
|
source: {
|
|
778
978
|
source: "github",
|
|
779
979
|
repo: "netease-lcap/wave-plugins-official",
|
|
780
980
|
},
|
|
781
|
-
autoUpdate: true,
|
|
782
981
|
};
|