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
|
@@ -11,7 +11,7 @@ import { isValidHookEvent } from "../types/hooks.js";
|
|
|
11
11
|
import { logger } from "../utils/globalLogger.js";
|
|
12
12
|
import { getAllConfigPaths, getExistingConfigPaths, getUserConfigPaths, getProjectConfigPaths, } from "../utils/configPaths.js";
|
|
13
13
|
import { isValidEnvironmentVars, } from "../types/environment.js";
|
|
14
|
-
import { DEFAULT_WAVE_MAX_INPUT_TOKENS, DEFAULT_WAVE_MAX_OUTPUT_TOKENS, DEFAULT_SERVER_URL, } from "../utils/constants.js";
|
|
14
|
+
import { DEFAULT_WAVE_MAX_INPUT_TOKENS, DEFAULT_WAVE_MAX_OUTPUT_TOKENS, DEFAULT_SERVER_URL, DEFAULT_LANGUAGE, } from "../utils/constants.js";
|
|
15
15
|
import { parseCustomHeaders } from "../utils/stringUtils.js";
|
|
16
16
|
import { getRemoteSettingsSync, mergeRemoteSettings, } from "./remoteSettingsService.js";
|
|
17
17
|
import { createAuthAwareFetch } from "./authService.js";
|
|
@@ -199,22 +199,38 @@ export class ConfigurationService {
|
|
|
199
199
|
this._configuredEnvKeys = new Set();
|
|
200
200
|
// Per-session environment snapshot: settings.json `env` is stored here (NOT
|
|
201
201
|
// written to process.env) so multiple sessions in one `wave --stdio` process
|
|
202
|
-
// don't cross-pollute. Resolve methods read `this.
|
|
202
|
+
// don't cross-pollute. Resolve methods read `this.liveEnvSnapshot ?? process.env`.
|
|
203
203
|
this.envSnapshot = {};
|
|
204
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Register the turn-snapshot source (see {@link TurnConfigurationSnapshot}).
|
|
207
|
+
*/
|
|
208
|
+
setTurnSnapshotSource(source) {
|
|
209
|
+
this.turnSnapshotSource = source;
|
|
210
|
+
}
|
|
205
211
|
/**
|
|
206
212
|
* Set agent options for configuration resolution
|
|
207
213
|
*/
|
|
208
214
|
setOptions(options) {
|
|
209
215
|
this.options = options;
|
|
210
216
|
}
|
|
217
|
+
/** Configuration the current turn reads from (live configuration outside a turn). */
|
|
218
|
+
get liveConfiguration() {
|
|
219
|
+
const snapshot = this.turnSnapshotSource?.();
|
|
220
|
+
return snapshot ? snapshot.configuration : this.currentConfiguration;
|
|
221
|
+
}
|
|
222
|
+
/** Environment snapshot the current turn reads from. */
|
|
223
|
+
get liveEnvSnapshot() {
|
|
224
|
+
const snapshot = this.turnSnapshotSource?.();
|
|
225
|
+
return snapshot ? snapshot.env : this.envSnapshot;
|
|
226
|
+
}
|
|
211
227
|
/**
|
|
212
228
|
* Returns a copy of the per-session environment snapshot (settings.json `env`).
|
|
213
229
|
* Priority over OS env; does NOT include OS env. For subprocess spawning use
|
|
214
230
|
* {@link getMergedEnv} instead.
|
|
215
231
|
*/
|
|
216
232
|
getEnvSnapshot() {
|
|
217
|
-
return { ...this.
|
|
233
|
+
return { ...this.liveEnvSnapshot };
|
|
218
234
|
}
|
|
219
235
|
/**
|
|
220
236
|
* Returns OS env merged with the session snapshot (snapshot wins). Use this
|
|
@@ -222,7 +238,7 @@ export class ConfigurationService {
|
|
|
222
238
|
* so they inherit both OS env and the session's settings env.
|
|
223
239
|
*/
|
|
224
240
|
getMergedEnv() {
|
|
225
|
-
return Object.fromEntries(Object.entries({ ...process.env, ...this.
|
|
241
|
+
return Object.fromEntries(Object.entries({ ...process.env, ...this.liveEnvSnapshot }).filter(([, v]) => v !== undefined));
|
|
226
242
|
}
|
|
227
243
|
// Core loading operations
|
|
228
244
|
/**
|
|
@@ -420,7 +436,7 @@ export class ConfigurationService {
|
|
|
420
436
|
}
|
|
421
437
|
else {
|
|
422
438
|
resolvedApiKey =
|
|
423
|
-
this.
|
|
439
|
+
this.liveEnvSnapshot.WAVE_API_KEY ?? process.env.WAVE_API_KEY;
|
|
424
440
|
}
|
|
425
441
|
// Resolve base URL: override > options > env (settings.json) > process.env
|
|
426
442
|
// Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
|
|
@@ -433,23 +449,23 @@ export class ConfigurationService {
|
|
|
433
449
|
}
|
|
434
450
|
else {
|
|
435
451
|
resolvedBaseURL =
|
|
436
|
-
this.
|
|
452
|
+
this.liveEnvSnapshot.WAVE_BASE_URL ?? process.env.WAVE_BASE_URL;
|
|
437
453
|
}
|
|
438
454
|
// Fallback to process.env if still not resolved (for dynamic updates in tests)
|
|
439
455
|
if (resolvedApiKey === undefined) {
|
|
440
456
|
resolvedApiKey =
|
|
441
|
-
this.
|
|
457
|
+
this.liveEnvSnapshot.WAVE_API_KEY ?? process.env.WAVE_API_KEY;
|
|
442
458
|
}
|
|
443
459
|
if (!resolvedBaseURL) {
|
|
444
460
|
resolvedBaseURL =
|
|
445
|
-
this.
|
|
461
|
+
this.liveEnvSnapshot.WAVE_BASE_URL ?? process.env.WAVE_BASE_URL;
|
|
446
462
|
}
|
|
447
463
|
// Treat empty string as not provided
|
|
448
464
|
if (resolvedBaseURL?.trim() === "") {
|
|
449
465
|
resolvedBaseURL = undefined;
|
|
450
466
|
}
|
|
451
467
|
// Resolve custom headers from environment: env (settings.json) > process.env
|
|
452
|
-
const envCustomHeaders = this.
|
|
468
|
+
const envCustomHeaders = this.liveEnvSnapshot.WAVE_CUSTOM_HEADERS ??
|
|
453
469
|
process.env.WAVE_CUSTOM_HEADERS ??
|
|
454
470
|
"";
|
|
455
471
|
const parsedEnvHeaders = parseCustomHeaders(envCustomHeaders);
|
|
@@ -482,16 +498,16 @@ export class ConfigurationService {
|
|
|
482
498
|
// If admin wants hard enforcement, they set the `model` scalar field (overwrites local in mergeRemoteSettings).
|
|
483
499
|
const resolvedAgentModel = model ||
|
|
484
500
|
this.options.model ||
|
|
485
|
-
this.
|
|
486
|
-
(this.
|
|
501
|
+
this.liveConfiguration?.model ||
|
|
502
|
+
(this.liveEnvSnapshot.WAVE_MODEL ?? process.env.WAVE_MODEL);
|
|
487
503
|
// Resolve fast model: override > options > process.env (includes settings.json env)
|
|
488
504
|
const resolvedFastModel = fastModel ||
|
|
489
505
|
this.options.fastModel ||
|
|
490
|
-
(this.
|
|
506
|
+
(this.liveEnvSnapshot.WAVE_FAST_MODEL ?? process.env.WAVE_FAST_MODEL);
|
|
491
507
|
// Resolve vision model: override > options > process.env (includes settings.json env)
|
|
492
508
|
const resolvedVisionModel = visionModel ||
|
|
493
509
|
this.options.visionModel ||
|
|
494
|
-
(this.
|
|
510
|
+
(this.liveEnvSnapshot.WAVE_VISION_MODEL ?? process.env.WAVE_VISION_MODEL);
|
|
495
511
|
const baseConfig = {
|
|
496
512
|
model: resolvedAgentModel,
|
|
497
513
|
fastModel: resolvedFastModel,
|
|
@@ -500,8 +516,7 @@ export class ConfigurationService {
|
|
|
500
516
|
};
|
|
501
517
|
// Resolve fast model generation params from models[fastModel].options.
|
|
502
518
|
// Set on baseConfig before the modelSpecificConfig spread so it isn't overwritten.
|
|
503
|
-
const fastModelSource = resolvedFastModel &&
|
|
504
|
-
this.currentConfiguration?.models?.[resolvedFastModel];
|
|
519
|
+
const fastModelSource = resolvedFastModel && this.liveConfiguration?.models?.[resolvedFastModel];
|
|
505
520
|
if (fastModelSource && fastModelSource.options) {
|
|
506
521
|
baseConfig.fastModelOptions = fastModelSource.options;
|
|
507
522
|
}
|
|
@@ -514,7 +529,7 @@ export class ConfigurationService {
|
|
|
514
529
|
}
|
|
515
530
|
// Merge model-specific settings from configuration
|
|
516
531
|
const modelSpecificConfig = resolvedAgentModel &&
|
|
517
|
-
this.
|
|
532
|
+
this.liveConfiguration?.models?.[resolvedAgentModel];
|
|
518
533
|
if (modelSpecificConfig) {
|
|
519
534
|
const resolved = {
|
|
520
535
|
...baseConfig,
|
|
@@ -549,14 +564,14 @@ export class ConfigurationService {
|
|
|
549
564
|
// same chain as resolveModelConfig: caller > options > currentConfiguration > env.
|
|
550
565
|
const resolvedModel = model ||
|
|
551
566
|
this.options.model ||
|
|
552
|
-
this.
|
|
553
|
-
(this.
|
|
554
|
-
const modelConfig = resolvedModel && this.
|
|
567
|
+
this.liveConfiguration?.model ||
|
|
568
|
+
(this.liveEnvSnapshot.WAVE_MODEL ?? process.env.WAVE_MODEL);
|
|
569
|
+
const modelConfig = resolvedModel && this.liveConfiguration?.models?.[resolvedModel];
|
|
555
570
|
if (modelConfig && modelConfig.maxInputTokens !== undefined) {
|
|
556
571
|
return modelConfig.maxInputTokens;
|
|
557
572
|
}
|
|
558
573
|
// Try env (settings.json snapshot) first, then process.env
|
|
559
|
-
const envMaxInputTokens = this.
|
|
574
|
+
const envMaxInputTokens = this.liveEnvSnapshot.WAVE_MAX_INPUT_TOKENS ??
|
|
560
575
|
process.env.WAVE_MAX_INPUT_TOKENS;
|
|
561
576
|
if (envMaxInputTokens) {
|
|
562
577
|
const parsed = parseInt(envMaxInputTokens, 10);
|
|
@@ -569,9 +584,14 @@ export class ConfigurationService {
|
|
|
569
584
|
}
|
|
570
585
|
/**
|
|
571
586
|
* Resolves preferred language with fallbacks
|
|
572
|
-
* Resolution priority: override > options > settings.json >
|
|
587
|
+
* Resolution priority: override > options > settings.json > default (`zh-CN`)
|
|
588
|
+
*
|
|
589
|
+
* 末尾的默认值保证「未设置」时也有明确生效值,且与设置页下拉的默认项一致
|
|
590
|
+
* (spec agent-config 边界说明「语言默认值」)。调用方不得再假定
|
|
591
|
+
* 「undefined = 不注入语言指令」。
|
|
592
|
+
*
|
|
573
593
|
* @param constructorLanguage - Language override (optional)
|
|
574
|
-
* @returns Resolved language
|
|
594
|
+
* @returns Resolved language (never empty)
|
|
575
595
|
*/
|
|
576
596
|
resolveLanguage(constructorLanguage) {
|
|
577
597
|
// 1. Override (highest priority)
|
|
@@ -582,30 +602,42 @@ export class ConfigurationService {
|
|
|
582
602
|
if (this.options.language !== undefined) {
|
|
583
603
|
return this.options.language;
|
|
584
604
|
}
|
|
585
|
-
//
|
|
586
|
-
if (this.
|
|
587
|
-
return this.
|
|
605
|
+
// 3. settings.json (merged)
|
|
606
|
+
if (this.liveConfiguration?.language) {
|
|
607
|
+
return this.liveConfiguration.language;
|
|
588
608
|
}
|
|
589
|
-
|
|
609
|
+
// 4. Default
|
|
610
|
+
return DEFAULT_LANGUAGE;
|
|
590
611
|
}
|
|
591
612
|
/**
|
|
592
613
|
* Resolves auto-memory enabled state with fallbacks
|
|
593
|
-
* Resolution priority: session options (
|
|
614
|
+
* Resolution priority: session options (session-level override) > settings.json > WAVE_DISABLE_AUTO_MEMORY > default (true)
|
|
594
615
|
* @returns Resolved auto-memory enabled state
|
|
595
616
|
*/
|
|
596
617
|
resolveAutoMemoryEnabled() {
|
|
597
|
-
|
|
598
|
-
|
|
618
|
+
return this.resolveAutoMemoryEnabledFrom(this.liveConfiguration, this.liveEnvSnapshot);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Same resolution chain but read from the live merged configuration, ignoring
|
|
622
|
+
* the turn snapshot. The permission safe zone is a live mechanism (like
|
|
623
|
+
* permission rules, applied by LiveConfigManager on reload), so its sync must
|
|
624
|
+
* not be pinned to the turn-start value.
|
|
625
|
+
*/
|
|
626
|
+
resolveAutoMemoryEnabledNow() {
|
|
627
|
+
return this.resolveAutoMemoryEnabledFrom(this.currentConfiguration, this.envSnapshot);
|
|
628
|
+
}
|
|
629
|
+
resolveAutoMemoryEnabledFrom(config, env) {
|
|
630
|
+
// 1. Per-session options override (session-level override semantics; user
|
|
631
|
+
// preferences are no longer passed this way — see core/agent-config.md)
|
|
599
632
|
if (this.options.autoMemoryEnabled !== undefined) {
|
|
600
633
|
return this.options.autoMemoryEnabled;
|
|
601
634
|
}
|
|
602
635
|
// 2. settings.json (merged)
|
|
603
|
-
if (
|
|
604
|
-
return
|
|
636
|
+
if (config?.autoMemoryEnabled !== undefined) {
|
|
637
|
+
return config.autoMemoryEnabled;
|
|
605
638
|
}
|
|
606
639
|
// 3. WAVE_DISABLE_AUTO_MEMORY environment variable (settings snapshot > OS env)
|
|
607
|
-
const disableAutoMemory =
|
|
608
|
-
process.env.WAVE_DISABLE_AUTO_MEMORY;
|
|
640
|
+
const disableAutoMemory = env.WAVE_DISABLE_AUTO_MEMORY ?? process.env.WAVE_DISABLE_AUTO_MEMORY;
|
|
609
641
|
if (disableAutoMemory === "1" || disableAutoMemory === "true") {
|
|
610
642
|
return false;
|
|
611
643
|
}
|
|
@@ -618,7 +650,7 @@ export class ConfigurationService {
|
|
|
618
650
|
* @returns Resolved worktree base ref
|
|
619
651
|
*/
|
|
620
652
|
resolveWorktreeBaseRef() {
|
|
621
|
-
const baseRef = this.
|
|
653
|
+
const baseRef = this.liveConfiguration?.worktree?.baseRef;
|
|
622
654
|
if (baseRef === "head") {
|
|
623
655
|
return "head";
|
|
624
656
|
}
|
|
@@ -626,12 +658,13 @@ export class ConfigurationService {
|
|
|
626
658
|
}
|
|
627
659
|
/**
|
|
628
660
|
* Resolves auto-memory extraction frequency with fallbacks
|
|
629
|
-
* Resolution priority: session options (
|
|
661
|
+
* Resolution priority: session options (程序化会话级覆盖) > settings.json > WAVE_AUTO_MEMORY_FREQUENCY > default (1)
|
|
630
662
|
* @returns Resolved auto-memory extraction frequency (turns)
|
|
631
663
|
*/
|
|
632
664
|
resolveAutoMemoryFrequency() {
|
|
633
|
-
// 1. Per-session options override (
|
|
634
|
-
//
|
|
665
|
+
// 1. Per-session options override (Agent.create → setOptions 的程序化会话级
|
|
666
|
+
// 覆盖;三端设置页的值不走这里——它们落用户级 settings.json,由实时重载
|
|
667
|
+
// 在下一轮生效,见 spec core/agent-config.md「设置实时重载」)。
|
|
635
668
|
// Only positive values are honored so a malformed 0/negative never
|
|
636
669
|
// degenerates into per-turn extraction.
|
|
637
670
|
if (this.options.autoMemoryFrequency !== undefined &&
|
|
@@ -639,11 +672,11 @@ export class ConfigurationService {
|
|
|
639
672
|
return this.options.autoMemoryFrequency;
|
|
640
673
|
}
|
|
641
674
|
// 2. settings.json (merged)
|
|
642
|
-
if (this.
|
|
643
|
-
return this.
|
|
675
|
+
if (this.liveConfiguration?.autoMemoryFrequency !== undefined) {
|
|
676
|
+
return this.liveConfiguration.autoMemoryFrequency;
|
|
644
677
|
}
|
|
645
678
|
// 3. WAVE_AUTO_MEMORY_FREQUENCY environment variable (settings snapshot > OS env)
|
|
646
|
-
const envFrequency = this.
|
|
679
|
+
const envFrequency = this.liveEnvSnapshot.WAVE_AUTO_MEMORY_FREQUENCY ??
|
|
647
680
|
process.env.WAVE_AUTO_MEMORY_FREQUENCY;
|
|
648
681
|
if (envFrequency) {
|
|
649
682
|
const parsed = parseInt(envFrequency, 10);
|
|
@@ -670,7 +703,7 @@ export class ConfigurationService {
|
|
|
670
703
|
return this.options.maxTokens;
|
|
671
704
|
}
|
|
672
705
|
// Try env (settings.json snapshot) first, then process.env
|
|
673
|
-
const envMaxOutputTokens = this.
|
|
706
|
+
const envMaxOutputTokens = this.liveEnvSnapshot.WAVE_MAX_OUTPUT_TOKENS ??
|
|
674
707
|
process.env.WAVE_MAX_OUTPUT_TOKENS;
|
|
675
708
|
if (envMaxOutputTokens) {
|
|
676
709
|
const parsed = parseInt(envMaxOutputTokens, 10);
|
|
@@ -682,11 +715,20 @@ export class ConfigurationService {
|
|
|
682
715
|
return DEFAULT_WAVE_MAX_OUTPUT_TOKENS;
|
|
683
716
|
}
|
|
684
717
|
/**
|
|
685
|
-
* Set the active model in the session
|
|
718
|
+
* Set the active model in the session.
|
|
719
|
+
*
|
|
720
|
+
* Persisting to `~/.wave/settings.json` is best-effort: the in-memory model
|
|
721
|
+
* applies immediately, and a failed write (e.g. a locked file / rename EPERM
|
|
722
|
+
* on Windows) is logged instead of escaping as an unhandled rejection.
|
|
686
723
|
*/
|
|
687
|
-
setModel(model) {
|
|
724
|
+
async setModel(model) {
|
|
688
725
|
this.options.model = model;
|
|
689
|
-
|
|
726
|
+
try {
|
|
727
|
+
await this.persistModelToSettings(model);
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
logger.warn(`Failed to persist model to settings: ${error}`);
|
|
731
|
+
}
|
|
690
732
|
}
|
|
691
733
|
async persistModelToSettings(model) {
|
|
692
734
|
const configPath = getUserConfigPaths()[0]; // ~/.wave/settings.json
|
|
@@ -714,17 +756,17 @@ export class ConfigurationService {
|
|
|
714
756
|
const models = new Set();
|
|
715
757
|
// Add current model from options or environment (settings snapshot > OS env)
|
|
716
758
|
const currentModel = this.options.model ||
|
|
717
|
-
(this.
|
|
759
|
+
(this.liveEnvSnapshot.WAVE_MODEL ?? process.env.WAVE_MODEL);
|
|
718
760
|
if (currentModel) {
|
|
719
761
|
models.add(currentModel);
|
|
720
762
|
}
|
|
721
763
|
// Persisted model from settings (includes remote-merged)
|
|
722
|
-
if (this.
|
|
723
|
-
models.add(this.
|
|
764
|
+
if (this.liveConfiguration?.model) {
|
|
765
|
+
models.add(this.liveConfiguration.model);
|
|
724
766
|
}
|
|
725
767
|
// Add models from merged configuration
|
|
726
|
-
if (this.
|
|
727
|
-
Object.keys(this.
|
|
768
|
+
if (this.liveConfiguration?.models) {
|
|
769
|
+
Object.keys(this.liveConfiguration.models).forEach((model) => {
|
|
728
770
|
models.add(model);
|
|
729
771
|
});
|
|
730
772
|
}
|
|
@@ -734,7 +776,7 @@ export class ConfigurationService {
|
|
|
734
776
|
* Get the telemetry config from the loaded settings.
|
|
735
777
|
*/
|
|
736
778
|
resolveTelemetryConfig() {
|
|
737
|
-
return this.
|
|
779
|
+
return this.liveConfiguration?.monitoring?.telemetry;
|
|
738
780
|
}
|
|
739
781
|
/**
|
|
740
782
|
* Resolve all configuration file paths
|
|
@@ -934,11 +976,23 @@ export class ConfigurationService {
|
|
|
934
976
|
}
|
|
935
977
|
}
|
|
936
978
|
/**
|
|
937
|
-
* Get merged marketplaces from all scopes
|
|
979
|
+
* Get merged marketplaces from all scopes, with admin-managed (remote) entries
|
|
980
|
+
* taking precedence per key (spec enterprise server-managed-config「托管配置
|
|
981
|
+
* 下发插件市场与启用列表」场景 5).
|
|
938
982
|
*/
|
|
939
983
|
getMergedMarketplaces(workdir) {
|
|
940
984
|
const mergedConfig = loadMergedWaveConfig(workdir);
|
|
941
|
-
return
|
|
985
|
+
return {
|
|
986
|
+
...(mergedConfig?.marketplaces || {}),
|
|
987
|
+
...(this.getManagedMarketplaces() || {}),
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Marketplaces declared by the remote (managed) settings. `null` means the
|
|
992
|
+
* managed layer declares none, leaving the local configuration chain in charge.
|
|
993
|
+
*/
|
|
994
|
+
getManagedMarketplaces() {
|
|
995
|
+
return getRemoteSettingsSync()?.marketplaces ?? null;
|
|
942
996
|
}
|
|
943
997
|
/**
|
|
944
998
|
* Get marketplaces at a specific scope
|
|
@@ -1059,11 +1113,25 @@ export class ConfigurationService {
|
|
|
1059
1113
|
}
|
|
1060
1114
|
}
|
|
1061
1115
|
/**
|
|
1062
|
-
* Get merged enabled plugins from all scopes
|
|
1116
|
+
* Get merged enabled plugins from all scopes, with admin-managed (remote)
|
|
1117
|
+
* entries taking precedence per key (spec enterprise server-managed-config
|
|
1118
|
+
* 「托管配置下发插件市场与启用列表」场景 5). A managed `false` therefore
|
|
1119
|
+
* force-disables a plugin no matter what the local files say.
|
|
1063
1120
|
*/
|
|
1064
1121
|
getMergedEnabledPlugins(workdir) {
|
|
1065
1122
|
const mergedConfig = loadMergedWaveConfig(workdir);
|
|
1066
|
-
return
|
|
1123
|
+
return {
|
|
1124
|
+
...(mergedConfig?.enabledPlugins || {}),
|
|
1125
|
+
...(this.getManagedEnabledPlugins() || {}),
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Enabled-plugin entries declared by the remote (managed) settings. `null`
|
|
1130
|
+
* means the managed layer declares none. Doubles as the authoritative
|
|
1131
|
+
* "is this plugin managed by the organization" lookup (spec plugin A-024).
|
|
1132
|
+
*/
|
|
1133
|
+
getManagedEnabledPlugins() {
|
|
1134
|
+
return getRemoteSettingsSync()?.enabledPlugins ?? null;
|
|
1067
1135
|
}
|
|
1068
1136
|
/**
|
|
1069
1137
|
* Load Wave configuration from a JSON file
|
|
@@ -1193,6 +1261,7 @@ export function loadWaveConfigFromFile(filePath) {
|
|
|
1193
1261
|
marketplaces: config.marketplaces || undefined,
|
|
1194
1262
|
worktree: config.worktree || undefined,
|
|
1195
1263
|
enableArtifact: config.enableArtifact !== undefined ? config.enableArtifact : undefined,
|
|
1264
|
+
enableExec: config.enableExec !== undefined ? config.enableExec : undefined,
|
|
1196
1265
|
};
|
|
1197
1266
|
}
|
|
1198
1267
|
catch (error) {
|
|
@@ -1344,6 +1413,10 @@ export function loadMergedWaveConfig(workdir) {
|
|
|
1344
1413
|
if (config.enableArtifact !== undefined) {
|
|
1345
1414
|
mergedConfig.enableArtifact = config.enableArtifact;
|
|
1346
1415
|
}
|
|
1416
|
+
// Merge enableExec (last one wins)
|
|
1417
|
+
if (config.enableExec !== undefined) {
|
|
1418
|
+
mergedConfig.enableExec = config.enableExec;
|
|
1419
|
+
}
|
|
1347
1420
|
// Merge models
|
|
1348
1421
|
if (config.models) {
|
|
1349
1422
|
if (!mergedConfig.models)
|
|
@@ -1374,6 +1447,7 @@ export function loadMergedWaveConfig(workdir) {
|
|
|
1374
1447
|
language: mergedConfig.language,
|
|
1375
1448
|
model: mergedConfig.model,
|
|
1376
1449
|
autoMemoryEnabled: mergedConfig.autoMemoryEnabled,
|
|
1450
|
+
autoMemoryFrequency: mergedConfig.autoMemoryFrequency,
|
|
1377
1451
|
cleanupPeriodDays: mergedConfig.cleanupPeriodDays,
|
|
1378
1452
|
marketplaces: mergedConfig.marketplaces &&
|
|
1379
1453
|
Object.keys(mergedConfig.marketplaces).length > 0
|
|
@@ -1384,6 +1458,7 @@ export function loadMergedWaveConfig(workdir) {
|
|
|
1384
1458
|
: undefined,
|
|
1385
1459
|
worktree: mergedConfig.worktree,
|
|
1386
1460
|
enableArtifact: mergedConfig.enableArtifact,
|
|
1461
|
+
enableExec: mergedConfig.enableExec,
|
|
1387
1462
|
};
|
|
1388
1463
|
}
|
|
1389
1464
|
/**
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared fast-model content processing.
|
|
3
|
+
*
|
|
4
|
+
* WebFetch runs every fetched page through a small, fast model, and the
|
|
5
|
+
* reader-view artifact read summarizes someone else's page the same way.
|
|
6
|
+
* Both go through this single implementation (prompt → answer).
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolContext, ToolResult } from "../tools/types.js";
|
|
9
|
+
/** Human-readable byte size (e.g. "512B", "2.0KB", "1.5MB"). */
|
|
10
|
+
export declare function formatSize(bytes: number): string;
|
|
11
|
+
/**
|
|
12
|
+
* Run the fast model over `content` with `prompt` and return its answer as the
|
|
13
|
+
* tool result. The main model only ever sees this answer.
|
|
14
|
+
*/
|
|
15
|
+
export declare function processContentWithAI(url: string, prompt: string, content: string, statusCode: number, statusText: string, context: ToolContext, contentSize?: number): Promise<ToolResult>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared fast-model content processing.
|
|
3
|
+
*
|
|
4
|
+
* WebFetch runs every fetched page through a small, fast model, and the
|
|
5
|
+
* reader-view artifact read summarizes someone else's page the same way.
|
|
6
|
+
* Both go through this single implementation (prompt → answer).
|
|
7
|
+
*/
|
|
8
|
+
/** Human-readable byte size (e.g. "512B", "2.0KB", "1.5MB"). */
|
|
9
|
+
export function formatSize(bytes) {
|
|
10
|
+
if (bytes < 1024)
|
|
11
|
+
return `${bytes}B`;
|
|
12
|
+
if (bytes < 1024 * 1024)
|
|
13
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
14
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run the fast model over `content` with `prompt` and return its answer as the
|
|
18
|
+
* tool result. The main model only ever sees this answer.
|
|
19
|
+
*/
|
|
20
|
+
export async function processContentWithAI(url, prompt, content, statusCode, statusText, context, contentSize) {
|
|
21
|
+
if (!context.aiManager || !context.aiService) {
|
|
22
|
+
return {
|
|
23
|
+
success: false,
|
|
24
|
+
content: content,
|
|
25
|
+
error: "AI Manager or AI Service not available for processing content",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const modelConfig = context.aiManager.getModelConfig();
|
|
29
|
+
const fastModel = modelConfig.fastModel;
|
|
30
|
+
const aiResponse = await context.aiService.processWebContent({
|
|
31
|
+
gatewayConfig: context.aiManager.getGatewayConfig(),
|
|
32
|
+
modelConfig: modelConfig,
|
|
33
|
+
content: content,
|
|
34
|
+
prompt: prompt,
|
|
35
|
+
model: fastModel,
|
|
36
|
+
abortSignal: context.abortSignal,
|
|
37
|
+
});
|
|
38
|
+
const sizeStr = contentSize !== undefined ? formatSize(contentSize) : "unknown size";
|
|
39
|
+
const statusStr = `${statusCode} ${statusText}`.trim();
|
|
40
|
+
return {
|
|
41
|
+
success: true,
|
|
42
|
+
content: aiResponse.content || "",
|
|
43
|
+
shortResult: `Received ${sizeStr} (${statusStr}) from ${url}`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Code default for Exec availability. */
|
|
2
|
+
export declare const EXEC_DEFAULT_ENABLED = true;
|
|
3
|
+
/**
|
|
4
|
+
* Whether the Exec tool may be registered for the given workdir.
|
|
5
|
+
* Resolution order: remote managed settings (`enableExec` from
|
|
6
|
+
* `GET /api/wave/settings`, admin override) → explicit `enableExec` in local
|
|
7
|
+
* merged settings → code default.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isExecEnabled(workdir?: string): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exec feature availability.
|
|
3
|
+
*
|
|
4
|
+
* Exec collapses the MCP tool pool into a single scriptable tool, which changes
|
|
5
|
+
* what the model sees in `tools[]`. It is on by default (the collapse applies
|
|
6
|
+
* whenever the pool is non-empty — there is no minimum tool count);
|
|
7
|
+
* `enableExec: false` is the escape hatch for a session that wants the flat MCP
|
|
8
|
+
* declarations back without downgrading.
|
|
9
|
+
*/
|
|
10
|
+
import { loadMergedWaveConfig } from "./configurationService.js";
|
|
11
|
+
import { getRemoteSettingsSync } from "./remoteSettingsService.js";
|
|
12
|
+
/** Code default for Exec availability. */
|
|
13
|
+
export const EXEC_DEFAULT_ENABLED = true;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the Exec tool may be registered for the given workdir.
|
|
16
|
+
* Resolution order: remote managed settings (`enableExec` from
|
|
17
|
+
* `GET /api/wave/settings`, admin override) → explicit `enableExec` in local
|
|
18
|
+
* merged settings → code default.
|
|
19
|
+
*/
|
|
20
|
+
export function isExecEnabled(workdir) {
|
|
21
|
+
const remote = getRemoteSettingsSync();
|
|
22
|
+
if (remote?.enableExec !== undefined) {
|
|
23
|
+
return remote.enableExec;
|
|
24
|
+
}
|
|
25
|
+
if (workdir) {
|
|
26
|
+
const config = loadMergedWaveConfig(workdir);
|
|
27
|
+
if (config?.enableExec !== undefined) {
|
|
28
|
+
return config.enableExec;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return EXEC_DEFAULT_ENABLED;
|
|
32
|
+
}
|
|
@@ -5,7 +5,59 @@
|
|
|
5
5
|
* Handles file watching with debouncing, error recovery, and graceful fallbacks.
|
|
6
6
|
*/
|
|
7
7
|
import * as chokidar from "chokidar";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
8
10
|
import { EventEmitter } from "events";
|
|
11
|
+
/**
|
|
12
|
+
* Expand a watch path to its canonical long form (Windows only).
|
|
13
|
+
*
|
|
14
|
+
* libuv's fs-event backend resolves each event path with `GetLongPathNameW()`
|
|
15
|
+
* and then asserts the result is prefixed by the watched directory string
|
|
16
|
+
* (`uv__relative_path()`, src\win\fs-event.c). libuv <= 1.51 expanded the
|
|
17
|
+
* watched directory itself; 1.52 dropped that step (it ships with Node 24.16+,
|
|
18
|
+
* 26.x, and Electron 43 — which bundles Node 24.18), so `handle->dirw`
|
|
19
|
+
* keeps whatever the caller passed, and watching an 8.3 short path
|
|
20
|
+
* (`C:\Users\LIUYIQ~1\...` — what %TEMP% yields when it is configured with a
|
|
21
|
+
* short name) aborts the whole process on the first event. The abort is not a
|
|
22
|
+
* catchable error, so the root must be canonicalized before it reaches
|
|
23
|
+
* chokidar. `fs.realpathSync()` alone is not enough: only the `.native()`
|
|
24
|
+
* variant expands short names.
|
|
25
|
+
*
|
|
26
|
+
* Paths that don't exist yet (`~/.wave/settings.json` on a fresh install) are
|
|
27
|
+
* handled by expanding the nearest existing ancestor and appending the
|
|
28
|
+
* remaining segments back; if nothing can be resolved the input is returned
|
|
29
|
+
* unchanged.
|
|
30
|
+
*/
|
|
31
|
+
function toLongFormPath(target) {
|
|
32
|
+
if (process.platform !== "win32")
|
|
33
|
+
return target;
|
|
34
|
+
// Only drive-qualified (`C:\...`) and UNC (`\\server\share`) paths are real
|
|
35
|
+
// Windows paths. POSIX-style input (what tests and other platforms use) is
|
|
36
|
+
// passed through untouched rather than re-rooted onto the current drive.
|
|
37
|
+
if (!/^[a-zA-Z]:[\\/]/.test(target) && !target.startsWith("\\\\")) {
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
let candidate = target;
|
|
41
|
+
const missing = [];
|
|
42
|
+
for (;;) {
|
|
43
|
+
try {
|
|
44
|
+
const resolved = fs.realpathSync.native(candidate);
|
|
45
|
+
return missing.length > 0 ? path.join(resolved, ...missing) : resolved;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
const parent = path.dirname(candidate);
|
|
49
|
+
if (parent === candidate)
|
|
50
|
+
return target;
|
|
51
|
+
missing.unshift(path.basename(candidate));
|
|
52
|
+
candidate = parent;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Whether `filePath` (slash-normalized) is the watched root or inside it. */
|
|
57
|
+
function isWithin(filePath, root) {
|
|
58
|
+
const normalizedRoot = root.replace(/\\/g, "/");
|
|
59
|
+
return (filePath === normalizedRoot || filePath.startsWith(normalizedRoot + "/"));
|
|
60
|
+
}
|
|
9
61
|
export class FileWatcherService extends EventEmitter {
|
|
10
62
|
constructor(logger, config) {
|
|
11
63
|
super();
|
|
@@ -38,6 +90,7 @@ export class FileWatcherService extends EventEmitter {
|
|
|
38
90
|
// Create new watcher entry
|
|
39
91
|
const entry = {
|
|
40
92
|
path,
|
|
93
|
+
watchPath: toLongFormPath(path),
|
|
41
94
|
watcher: null,
|
|
42
95
|
isActive: false,
|
|
43
96
|
lastEvent: Date.now(),
|
|
@@ -64,7 +117,7 @@ export class FileWatcherService extends EventEmitter {
|
|
|
64
117
|
return;
|
|
65
118
|
try {
|
|
66
119
|
if (entry.watcher) {
|
|
67
|
-
entry.watcher.unwatch(
|
|
120
|
+
entry.watcher.unwatch(entry.watchPath);
|
|
68
121
|
}
|
|
69
122
|
this.watchers.delete(path);
|
|
70
123
|
}
|
|
@@ -148,7 +201,7 @@ export class FileWatcherService extends EventEmitter {
|
|
|
148
201
|
this.setupGlobalWatcherEvents();
|
|
149
202
|
}
|
|
150
203
|
// Add path to global watcher
|
|
151
|
-
this.globalWatcher.add(entry.
|
|
204
|
+
this.globalWatcher.add(entry.watchPath);
|
|
152
205
|
entry.watcher = this.globalWatcher;
|
|
153
206
|
entry.isActive = true;
|
|
154
207
|
entry.errorCount = 0;
|
|
@@ -207,12 +260,14 @@ export class FileWatcherService extends EventEmitter {
|
|
|
207
260
|
timestamp: Date.now(),
|
|
208
261
|
size: stats?.size,
|
|
209
262
|
};
|
|
210
|
-
// Notify all watchers that match the path or are parents of the path
|
|
263
|
+
// Notify all watchers that match the path or are parents of the path.
|
|
264
|
+
// Events arrive under whichever form chokidar was given (the long form on
|
|
265
|
+
// Windows), while entries are keyed by the caller's original path, so both
|
|
266
|
+
// have to be considered.
|
|
211
267
|
for (const [watchedPath, entry] of this.watchers.entries()) {
|
|
212
268
|
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
normalizedFilePath.startsWith(normalizedWatchedPath + "/")) {
|
|
269
|
+
if (isWithin(normalizedFilePath, watchedPath) ||
|
|
270
|
+
isWithin(normalizedFilePath, entry.watchPath)) {
|
|
216
271
|
entry.lastEvent = event.timestamp;
|
|
217
272
|
// Notify all callbacks for this watcher
|
|
218
273
|
for (const callback of entry.callbacks) {
|