wave-agent-sdk 1.0.3 → 1.0.4
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 +19 -0
- package/dist/agent.js +29 -0
- package/dist/managers/aiManager.js +1 -0
- package/dist/managers/permissionManager.d.ts +16 -0
- package/dist/managers/permissionManager.js +50 -3
- package/dist/managers/subagentManager.js +1 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +14 -2
- package/dist/services/configurationService.d.ts +5 -0
- package/dist/services/configurationService.js +33 -0
- package/dist/types/agent.d.ts +6 -0
- package/dist/utils/containerSetup.js +1 -0
- package/package.json +1 -1
- package/src/agent.ts +38 -0
- package/src/managers/aiManager.ts +2 -0
- package/src/managers/permissionManager.ts +58 -4
- package/src/managers/subagentManager.ts +2 -0
- package/src/prompts/index.ts +21 -4
- package/src/services/configurationService.ts +40 -0
- package/src/types/agent.ts +6 -0
- package/src/utils/containerSetup.ts +1 -0
package/dist/agent.d.ts
CHANGED
|
@@ -299,6 +299,25 @@ export declare class Agent {
|
|
|
299
299
|
path: string;
|
|
300
300
|
mimeType: string;
|
|
301
301
|
}>): Promise<void>;
|
|
302
|
+
/**
|
|
303
|
+
* Add an additional directory to the Safe Zone for this session.
|
|
304
|
+
*
|
|
305
|
+
* The directory immediately becomes part of the Safe Zone (permission
|
|
306
|
+
* checks and the system prompt), and with `remember: true` it is also
|
|
307
|
+
* persisted to `.wave/settings.local.json` `permissions.additionalDirectories`
|
|
308
|
+
* so future sessions pick it up automatically.
|
|
309
|
+
*
|
|
310
|
+
* @param directory Path of the directory to add (relative paths resolve against the workdir).
|
|
311
|
+
* @param options.remember Whether to persist the directory across sessions.
|
|
312
|
+
*/
|
|
313
|
+
addAdditionalDirectory(directory: string, options?: {
|
|
314
|
+
remember?: boolean;
|
|
315
|
+
}): Promise<void>;
|
|
316
|
+
/**
|
|
317
|
+
* Get the effective additional directories for this session (config +
|
|
318
|
+
* session-level union), matching what is listed in the system prompt.
|
|
319
|
+
*/
|
|
320
|
+
getAdditionalDirectories(): string[];
|
|
302
321
|
/** Get all MCP server status */
|
|
303
322
|
getMcpServers(): McpServerStatus[];
|
|
304
323
|
/** Connect MCP server */
|
package/dist/agent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LspManager } from "./managers/lspManager.js";
|
|
2
2
|
import { configValidator } from "./utils/configValidator.js";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import { parseTaskNotificationXml } from "./utils/notificationXml.js";
|
|
4
5
|
import { InitializationService } from "./services/initializationService.js";
|
|
5
6
|
import { InteractionService } from "./services/interactionService.js";
|
|
@@ -790,6 +791,34 @@ export class Agent {
|
|
|
790
791
|
abortMessage: () => this.abortMessage(),
|
|
791
792
|
}, content, images);
|
|
792
793
|
}
|
|
794
|
+
// ========== Additional Directories ==========
|
|
795
|
+
/**
|
|
796
|
+
* Add an additional directory to the Safe Zone for this session.
|
|
797
|
+
*
|
|
798
|
+
* The directory immediately becomes part of the Safe Zone (permission
|
|
799
|
+
* checks and the system prompt), and with `remember: true` it is also
|
|
800
|
+
* persisted to `.wave/settings.local.json` `permissions.additionalDirectories`
|
|
801
|
+
* so future sessions pick it up automatically.
|
|
802
|
+
*
|
|
803
|
+
* @param directory Path of the directory to add (relative paths resolve against the workdir).
|
|
804
|
+
* @param options.remember Whether to persist the directory across sessions.
|
|
805
|
+
*/
|
|
806
|
+
async addAdditionalDirectory(directory, options) {
|
|
807
|
+
const resolvedPath = path.isAbsolute(directory)
|
|
808
|
+
? directory
|
|
809
|
+
: path.resolve(this.workdir, directory);
|
|
810
|
+
this.permissionManager.addInstanceAdditionalDirectory(resolvedPath);
|
|
811
|
+
if (options?.remember) {
|
|
812
|
+
await this.configurationService.addAdditionalDirectory(this.workdir, resolvedPath);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Get the effective additional directories for this session (config +
|
|
817
|
+
* session-level union), matching what is listed in the system prompt.
|
|
818
|
+
*/
|
|
819
|
+
getAdditionalDirectories() {
|
|
820
|
+
return this.permissionManager.getEffectiveAdditionalDirectories();
|
|
821
|
+
}
|
|
793
822
|
// ========== MCP Management Methods ==========
|
|
794
823
|
/** Get all MCP server status */
|
|
795
824
|
getMcpServers() {
|
|
@@ -482,6 +482,7 @@ export class AIManager {
|
|
|
482
482
|
language: this.getLanguage(),
|
|
483
483
|
isSubagent: !!this.subagentType,
|
|
484
484
|
worktreeSession: this.getWorktreeSession(),
|
|
485
|
+
additionalWorkingDirectories: this.permissionManager?.getEffectiveAdditionalDirectories?.() ?? [],
|
|
485
486
|
autoMemory: autoMemoryOptions,
|
|
486
487
|
});
|
|
487
488
|
}
|
|
@@ -21,6 +21,8 @@ export interface PermissionManagerOptions {
|
|
|
21
21
|
instanceDeniedRules?: string[];
|
|
22
22
|
/** Additional directories considered part of the Safe Zone */
|
|
23
23
|
additionalDirectories?: string[];
|
|
24
|
+
/** Instance-specific additional directories (from AgentOptions, session-level) */
|
|
25
|
+
instanceAdditionalDirectories?: string[];
|
|
24
26
|
/** System additional directories (persistent across reloads) */
|
|
25
27
|
systemAdditionalDirectories?: string[];
|
|
26
28
|
/** Path to the current plan file */
|
|
@@ -37,6 +39,7 @@ export declare class PermissionManager {
|
|
|
37
39
|
private instanceDeniedRules;
|
|
38
40
|
private temporaryRules;
|
|
39
41
|
private additionalDirectories;
|
|
42
|
+
private instanceAdditionalDirectories;
|
|
40
43
|
private systemAdditionalDirectories;
|
|
41
44
|
private planFilePath?;
|
|
42
45
|
private hasExitedPlanMode;
|
|
@@ -83,6 +86,15 @@ export declare class PermissionManager {
|
|
|
83
86
|
* Get all additional directories
|
|
84
87
|
*/
|
|
85
88
|
getAdditionalDirectories(): string[];
|
|
89
|
+
/**
|
|
90
|
+
* Get all instance-specific additional directories (session-level, from AgentOptions or /add-dir)
|
|
91
|
+
*/
|
|
92
|
+
getInstanceAdditionalDirectories(): string[];
|
|
93
|
+
/**
|
|
94
|
+
* Get the effective union of additional directories (config + instance, deduplicated).
|
|
95
|
+
* This is the data source for system prompt injection and Safe Zone checks.
|
|
96
|
+
*/
|
|
97
|
+
getEffectiveAdditionalDirectories(): string[];
|
|
86
98
|
/**
|
|
87
99
|
* Get all system additional directories
|
|
88
100
|
*/
|
|
@@ -111,6 +123,10 @@ export declare class PermissionManager {
|
|
|
111
123
|
* Update the additional directories (e.g., when configuration reloads)
|
|
112
124
|
*/
|
|
113
125
|
updateAdditionalDirectories(directories: string[]): void;
|
|
126
|
+
/**
|
|
127
|
+
* Add an instance-level additional directory (session-level, e.g. /add-dir command)
|
|
128
|
+
*/
|
|
129
|
+
addInstanceAdditionalDirectory(directory: string): void;
|
|
114
130
|
/**
|
|
115
131
|
* Add a system-level additional directory that is persistent across configuration reloads
|
|
116
132
|
*/
|
|
@@ -63,6 +63,7 @@ export class PermissionManager {
|
|
|
63
63
|
this.instanceDeniedRules = [];
|
|
64
64
|
this.temporaryRules = [];
|
|
65
65
|
this.additionalDirectories = [];
|
|
66
|
+
this.instanceAdditionalDirectories = [];
|
|
66
67
|
this.systemAdditionalDirectories = [];
|
|
67
68
|
this.hasExitedPlanMode = false;
|
|
68
69
|
this.needsPlanModeExitAttachment = false;
|
|
@@ -73,13 +74,17 @@ export class PermissionManager {
|
|
|
73
74
|
this.instanceDeniedRules = options.instanceDeniedRules || [];
|
|
74
75
|
this.planFilePath = options.planFilePath;
|
|
75
76
|
this._logger = options.logger;
|
|
77
|
+
// Read workdir first so relative additional directories resolve against it.
|
|
78
|
+
this.workdir = this.container.get("Workdir");
|
|
79
|
+
this.worktreeName = this.container.get("WorktreeName");
|
|
80
|
+
this.mainRepoRoot = this.container.get("MainRepoRoot");
|
|
76
81
|
this.updateAdditionalDirectories(options.additionalDirectories || []);
|
|
82
|
+
for (const dir of options.instanceAdditionalDirectories || []) {
|
|
83
|
+
this.addInstanceAdditionalDirectory(dir);
|
|
84
|
+
}
|
|
77
85
|
for (const dir of options.systemAdditionalDirectories || []) {
|
|
78
86
|
this.addSystemAdditionalDirectory(dir);
|
|
79
87
|
}
|
|
80
|
-
this.workdir = this.container.get("Workdir");
|
|
81
|
-
this.worktreeName = this.container.get("WorktreeName");
|
|
82
|
-
this.mainRepoRoot = this.container.get("MainRepoRoot");
|
|
83
88
|
}
|
|
84
89
|
/**
|
|
85
90
|
* Resolve the working directory from the DI container
|
|
@@ -141,6 +146,30 @@ export class PermissionManager {
|
|
|
141
146
|
getAdditionalDirectories() {
|
|
142
147
|
return [...this.additionalDirectories];
|
|
143
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Get all instance-specific additional directories (session-level, from AgentOptions or /add-dir)
|
|
151
|
+
*/
|
|
152
|
+
getInstanceAdditionalDirectories() {
|
|
153
|
+
return [...this.instanceAdditionalDirectories];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get the effective union of additional directories (config + instance, deduplicated).
|
|
157
|
+
* This is the data source for system prompt injection and Safe Zone checks.
|
|
158
|
+
*/
|
|
159
|
+
getEffectiveAdditionalDirectories() {
|
|
160
|
+
const seen = new Set();
|
|
161
|
+
const result = [];
|
|
162
|
+
for (const dir of [
|
|
163
|
+
...this.additionalDirectories,
|
|
164
|
+
...this.instanceAdditionalDirectories,
|
|
165
|
+
]) {
|
|
166
|
+
if (!seen.has(dir)) {
|
|
167
|
+
seen.add(dir);
|
|
168
|
+
result.push(dir);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
144
173
|
/**
|
|
145
174
|
* Get all system additional directories
|
|
146
175
|
*/
|
|
@@ -189,6 +218,18 @@ export class PermissionManager {
|
|
|
189
218
|
return path.resolve(dir);
|
|
190
219
|
});
|
|
191
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Add an instance-level additional directory (session-level, e.g. /add-dir command)
|
|
223
|
+
*/
|
|
224
|
+
addInstanceAdditionalDirectory(directory) {
|
|
225
|
+
const workdir = this.workdir;
|
|
226
|
+
const resolvedPath = workdir && !path.isAbsolute(directory)
|
|
227
|
+
? path.resolve(workdir, directory)
|
|
228
|
+
: path.resolve(directory);
|
|
229
|
+
if (!this.instanceAdditionalDirectories.includes(resolvedPath)) {
|
|
230
|
+
this.instanceAdditionalDirectories.push(resolvedPath);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
192
233
|
/**
|
|
193
234
|
* Add a system-level additional directory that is persistent across configuration reloads
|
|
194
235
|
*/
|
|
@@ -250,6 +291,12 @@ export class PermissionManager {
|
|
|
250
291
|
return { isInside: true, resolvedPath: absolutePath };
|
|
251
292
|
}
|
|
252
293
|
}
|
|
294
|
+
// Check instance additional directories
|
|
295
|
+
for (const dir of this.instanceAdditionalDirectories) {
|
|
296
|
+
if (isPathInside(absolutePath, dir)) {
|
|
297
|
+
return { isInside: true, resolvedPath: absolutePath };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
253
300
|
// Check system additional directories
|
|
254
301
|
for (const dir of this.systemAdditionalDirectories) {
|
|
255
302
|
if (isPathInside(absolutePath, dir)) {
|
|
@@ -178,6 +178,7 @@ export class SubagentManager {
|
|
|
178
178
|
TASK_LIST_TOOL_NAME,
|
|
179
179
|
],
|
|
180
180
|
additionalDirectories: parentPermissionManager?.getAdditionalDirectories(),
|
|
181
|
+
instanceAdditionalDirectories: parentPermissionManager?.getInstanceAdditionalDirectories?.(),
|
|
181
182
|
systemAdditionalDirectories: parentPermissionManager?.getSystemAdditionalDirectories(),
|
|
182
183
|
planFilePath: parentPermissionManager?.getPlanFilePath(),
|
|
183
184
|
});
|
package/dist/prompts/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export declare function buildSystemPrompt(basePrompt: string | undefined, tools:
|
|
|
40
40
|
language?: string;
|
|
41
41
|
isSubagent?: boolean;
|
|
42
42
|
worktreeSession?: WorktreeSession | null;
|
|
43
|
+
additionalWorkingDirectories?: string[];
|
|
43
44
|
autoMemory?: {
|
|
44
45
|
directory: string;
|
|
45
46
|
content: string;
|
package/dist/prompts/index.js
CHANGED
|
@@ -346,10 +346,14 @@ export function buildSystemPrompt(basePrompt, tools, options = {}) {
|
|
|
346
346
|
const osVersion = getUnameSR();
|
|
347
347
|
const primaryWorkdir = options.originalWorkdir ?? options.workdir;
|
|
348
348
|
const worktreeSession = options.worktreeSession;
|
|
349
|
+
const additionalWorkingDirectories = options.additionalWorkingDirectories || [];
|
|
349
350
|
if (options.isSubagent) {
|
|
350
351
|
// Subagent env section, aligned with Claude Code's computeEnvInfo() +
|
|
351
352
|
// enhanceSystemPromptWithEnvDetails() (without the model description and
|
|
352
353
|
// knowledge cutoff lines, which Wave does not use).
|
|
354
|
+
const additionalDirsInfo = additionalWorkingDirectories.length > 0
|
|
355
|
+
? `Additional working directories: ${additionalWorkingDirectories.join(", ")}\n`
|
|
356
|
+
: "";
|
|
353
357
|
dynamicText += `
|
|
354
358
|
|
|
355
359
|
${SUBAGENT_ENV_NOTES}
|
|
@@ -358,7 +362,7 @@ Here is useful information about the environment you are running in:
|
|
|
358
362
|
<env>
|
|
359
363
|
Working directory: ${primaryWorkdir}
|
|
360
364
|
Is directory a git repo: ${isGitRepo}
|
|
361
|
-
Platform: ${platform}
|
|
365
|
+
${additionalDirsInfo}Platform: ${platform}
|
|
362
366
|
${shellInfo}
|
|
363
367
|
OS Version: ${osVersion}
|
|
364
368
|
</env>
|
|
@@ -374,6 +378,12 @@ OS Version: ${osVersion}
|
|
|
374
378
|
? `This is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root.`
|
|
375
379
|
: null,
|
|
376
380
|
`Is a git repository: ${isGitRepo}`,
|
|
381
|
+
additionalWorkingDirectories.length > 0
|
|
382
|
+
? `Additional working directories:`
|
|
383
|
+
: null,
|
|
384
|
+
additionalWorkingDirectories.length > 0
|
|
385
|
+
? additionalWorkingDirectories
|
|
386
|
+
: null,
|
|
377
387
|
`Platform: ${platform}`,
|
|
378
388
|
shellInfo,
|
|
379
389
|
`OS Version: ${osVersion}`,
|
|
@@ -381,7 +391,9 @@ OS Version: ${osVersion}
|
|
|
381
391
|
const envBlock = [
|
|
382
392
|
`# Environment`,
|
|
383
393
|
`You have been invoked in the following environment: `,
|
|
384
|
-
...envItems.
|
|
394
|
+
...envItems.flatMap((item) => Array.isArray(item)
|
|
395
|
+
? item.map((subItem) => ` - ${subItem}`)
|
|
396
|
+
: [` - ${item}`]),
|
|
385
397
|
].join("\n");
|
|
386
398
|
dynamicText += `\n\n${envBlock}`;
|
|
387
399
|
}
|
|
@@ -144,6 +144,11 @@ export declare class ConfigurationService {
|
|
|
144
144
|
* Add a permission rule to the local settings.local.json
|
|
145
145
|
*/
|
|
146
146
|
addAllowedRule(workdir: string, rule: string): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Add an additional directory to the local settings.local.json
|
|
149
|
+
* `permissions.additionalDirectories`, so it persists across sessions.
|
|
150
|
+
*/
|
|
151
|
+
addAdditionalDirectory(workdir: string, directory: string): Promise<void>;
|
|
147
152
|
/**
|
|
148
153
|
* Update the enabled state of a plugin in the specified scope
|
|
149
154
|
*/
|
|
@@ -741,6 +741,39 @@ export class ConfigurationService {
|
|
|
741
741
|
await atomicWriteFile(localConfigPath, JSON.stringify(config, null, 2));
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* Add an additional directory to the local settings.local.json
|
|
746
|
+
* `permissions.additionalDirectories`, so it persists across sessions.
|
|
747
|
+
*/
|
|
748
|
+
async addAdditionalDirectory(workdir, directory) {
|
|
749
|
+
const localConfigPath = path.join(workdir, ".wave", "settings.local.json");
|
|
750
|
+
// Ensure .wave directory exists
|
|
751
|
+
const waveDir = path.join(workdir, ".wave");
|
|
752
|
+
ensureWaveRuntimeFilesExcluded(workdir);
|
|
753
|
+
if (!existsSync(waveDir)) {
|
|
754
|
+
await fs.mkdir(waveDir, { recursive: true });
|
|
755
|
+
}
|
|
756
|
+
let config = {};
|
|
757
|
+
if (existsSync(localConfigPath)) {
|
|
758
|
+
try {
|
|
759
|
+
const content = await fs.readFile(localConfigPath, "utf-8");
|
|
760
|
+
config = JSON.parse(content);
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
// If file is corrupted, start with empty config
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (!config.permissions) {
|
|
767
|
+
config.permissions = {};
|
|
768
|
+
}
|
|
769
|
+
if (!config.permissions.additionalDirectories) {
|
|
770
|
+
config.permissions.additionalDirectories = [];
|
|
771
|
+
}
|
|
772
|
+
if (!config.permissions.additionalDirectories.includes(directory)) {
|
|
773
|
+
config.permissions.additionalDirectories.push(directory);
|
|
774
|
+
await atomicWriteFile(localConfigPath, JSON.stringify(config, null, 2));
|
|
775
|
+
}
|
|
776
|
+
}
|
|
744
777
|
/**
|
|
745
778
|
* Update the enabled state of a plugin in the specified scope
|
|
746
779
|
*/
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -72,6 +72,12 @@ export interface AgentOptions {
|
|
|
72
72
|
* These rules follow the standard permission rule syntax: `ToolName` or `ToolName(pattern)`.
|
|
73
73
|
*/
|
|
74
74
|
disallowedTools?: string[];
|
|
75
|
+
/**
|
|
76
|
+
* Optional list of additional directories to include in the Safe Zone
|
|
77
|
+
* for this agent session. These directories are listed in the system
|
|
78
|
+
* prompt and treated as safe for file operations (e.g. in acceptEdits mode).
|
|
79
|
+
*/
|
|
80
|
+
additionalDirectories?: string[];
|
|
75
81
|
/**
|
|
76
82
|
* Optional MCP server configs to connect at startup.
|
|
77
83
|
* Overrides .mcp.json for specified server names.
|
|
@@ -115,6 +115,7 @@ export function setupAgentContainer(setupOptions) {
|
|
|
115
115
|
const permissionManager = new PermissionManager(container, {
|
|
116
116
|
instanceAllowedRules: options.allowedTools,
|
|
117
117
|
instanceDeniedRules: options.disallowedTools,
|
|
118
|
+
instanceAdditionalDirectories: options.additionalDirectories,
|
|
118
119
|
});
|
|
119
120
|
if (configurationService.resolveAutoMemoryEnabled()) {
|
|
120
121
|
const autoMemoryDir = memoryService.getAutoMemoryDirectory(workdir);
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { configValidator } from "./utils/configValidator.js";
|
|
|
38
38
|
import { SkillManager } from "./managers/skillManager.js";
|
|
39
39
|
import { TaskManager } from "./services/taskManager.js";
|
|
40
40
|
import type { WorktreeSession } from "./utils/worktreeSession.js";
|
|
41
|
+
import path from "node:path";
|
|
41
42
|
import { parseTaskNotificationXml } from "./utils/notificationXml.js";
|
|
42
43
|
import { InitializationService } from "./services/initializationService.js";
|
|
43
44
|
import { InteractionService } from "./services/interactionService.js";
|
|
@@ -1045,6 +1046,43 @@ export class Agent {
|
|
|
1045
1046
|
);
|
|
1046
1047
|
}
|
|
1047
1048
|
|
|
1049
|
+
// ========== Additional Directories ==========
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Add an additional directory to the Safe Zone for this session.
|
|
1053
|
+
*
|
|
1054
|
+
* The directory immediately becomes part of the Safe Zone (permission
|
|
1055
|
+
* checks and the system prompt), and with `remember: true` it is also
|
|
1056
|
+
* persisted to `.wave/settings.local.json` `permissions.additionalDirectories`
|
|
1057
|
+
* so future sessions pick it up automatically.
|
|
1058
|
+
*
|
|
1059
|
+
* @param directory Path of the directory to add (relative paths resolve against the workdir).
|
|
1060
|
+
* @param options.remember Whether to persist the directory across sessions.
|
|
1061
|
+
*/
|
|
1062
|
+
public async addAdditionalDirectory(
|
|
1063
|
+
directory: string,
|
|
1064
|
+
options?: { remember?: boolean },
|
|
1065
|
+
): Promise<void> {
|
|
1066
|
+
const resolvedPath = path.isAbsolute(directory)
|
|
1067
|
+
? directory
|
|
1068
|
+
: path.resolve(this.workdir, directory);
|
|
1069
|
+
this.permissionManager.addInstanceAdditionalDirectory(resolvedPath);
|
|
1070
|
+
if (options?.remember) {
|
|
1071
|
+
await this.configurationService.addAdditionalDirectory(
|
|
1072
|
+
this.workdir,
|
|
1073
|
+
resolvedPath,
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Get the effective additional directories for this session (config +
|
|
1080
|
+
* session-level union), matching what is listed in the system prompt.
|
|
1081
|
+
*/
|
|
1082
|
+
public getAdditionalDirectories(): string[] {
|
|
1083
|
+
return this.permissionManager.getEffectiveAdditionalDirectories();
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1048
1086
|
// ========== MCP Management Methods ==========
|
|
1049
1087
|
|
|
1050
1088
|
/** Get all MCP server status */
|
|
@@ -746,6 +746,8 @@ export class AIManager {
|
|
|
746
746
|
language: this.getLanguage(),
|
|
747
747
|
isSubagent: !!this.subagentType,
|
|
748
748
|
worktreeSession: this.getWorktreeSession(),
|
|
749
|
+
additionalWorkingDirectories:
|
|
750
|
+
this.permissionManager?.getEffectiveAdditionalDirectories?.() ?? [],
|
|
749
751
|
autoMemory: autoMemoryOptions,
|
|
750
752
|
});
|
|
751
753
|
}
|
|
@@ -99,6 +99,8 @@ export interface PermissionManagerOptions {
|
|
|
99
99
|
instanceDeniedRules?: string[];
|
|
100
100
|
/** Additional directories considered part of the Safe Zone */
|
|
101
101
|
additionalDirectories?: string[];
|
|
102
|
+
/** Instance-specific additional directories (from AgentOptions, session-level) */
|
|
103
|
+
instanceAdditionalDirectories?: string[];
|
|
102
104
|
/** System additional directories (persistent across reloads) */
|
|
103
105
|
systemAdditionalDirectories?: string[];
|
|
104
106
|
/** Path to the current plan file */
|
|
@@ -115,6 +117,7 @@ export class PermissionManager {
|
|
|
115
117
|
private instanceDeniedRules: string[] = [];
|
|
116
118
|
private temporaryRules: string[] = [];
|
|
117
119
|
private additionalDirectories: string[] = [];
|
|
120
|
+
private instanceAdditionalDirectories: string[] = [];
|
|
118
121
|
private systemAdditionalDirectories: string[] = [];
|
|
119
122
|
private planFilePath?: string;
|
|
120
123
|
private hasExitedPlanMode: boolean = false;
|
|
@@ -136,14 +139,17 @@ export class PermissionManager {
|
|
|
136
139
|
this.instanceDeniedRules = options.instanceDeniedRules || [];
|
|
137
140
|
this.planFilePath = options.planFilePath;
|
|
138
141
|
this._logger = options.logger;
|
|
142
|
+
// Read workdir first so relative additional directories resolve against it.
|
|
143
|
+
this.workdir = this.container.get<string>("Workdir");
|
|
144
|
+
this.worktreeName = this.container.get<string | undefined>("WorktreeName");
|
|
145
|
+
this.mainRepoRoot = this.container.get<string | undefined>("MainRepoRoot");
|
|
139
146
|
this.updateAdditionalDirectories(options.additionalDirectories || []);
|
|
147
|
+
for (const dir of options.instanceAdditionalDirectories || []) {
|
|
148
|
+
this.addInstanceAdditionalDirectory(dir);
|
|
149
|
+
}
|
|
140
150
|
for (const dir of options.systemAdditionalDirectories || []) {
|
|
141
151
|
this.addSystemAdditionalDirectory(dir);
|
|
142
152
|
}
|
|
143
|
-
|
|
144
|
-
this.workdir = this.container.get<string>("Workdir");
|
|
145
|
-
this.worktreeName = this.container.get<string | undefined>("WorktreeName");
|
|
146
|
-
this.mainRepoRoot = this.container.get<string | undefined>("MainRepoRoot");
|
|
147
153
|
}
|
|
148
154
|
|
|
149
155
|
/**
|
|
@@ -221,6 +227,32 @@ export class PermissionManager {
|
|
|
221
227
|
return [...this.additionalDirectories];
|
|
222
228
|
}
|
|
223
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Get all instance-specific additional directories (session-level, from AgentOptions or /add-dir)
|
|
232
|
+
*/
|
|
233
|
+
public getInstanceAdditionalDirectories(): string[] {
|
|
234
|
+
return [...this.instanceAdditionalDirectories];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get the effective union of additional directories (config + instance, deduplicated).
|
|
239
|
+
* This is the data source for system prompt injection and Safe Zone checks.
|
|
240
|
+
*/
|
|
241
|
+
public getEffectiveAdditionalDirectories(): string[] {
|
|
242
|
+
const seen = new Set<string>();
|
|
243
|
+
const result: string[] = [];
|
|
244
|
+
for (const dir of [
|
|
245
|
+
...this.additionalDirectories,
|
|
246
|
+
...this.instanceAdditionalDirectories,
|
|
247
|
+
]) {
|
|
248
|
+
if (!seen.has(dir)) {
|
|
249
|
+
seen.add(dir);
|
|
250
|
+
result.push(dir);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
|
|
224
256
|
/**
|
|
225
257
|
* Get all system additional directories
|
|
226
258
|
*/
|
|
@@ -276,6 +308,21 @@ export class PermissionManager {
|
|
|
276
308
|
});
|
|
277
309
|
}
|
|
278
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Add an instance-level additional directory (session-level, e.g. /add-dir command)
|
|
313
|
+
*/
|
|
314
|
+
public addInstanceAdditionalDirectory(directory: string): void {
|
|
315
|
+
const workdir = this.workdir;
|
|
316
|
+
const resolvedPath =
|
|
317
|
+
workdir && !path.isAbsolute(directory)
|
|
318
|
+
? path.resolve(workdir, directory)
|
|
319
|
+
: path.resolve(directory);
|
|
320
|
+
|
|
321
|
+
if (!this.instanceAdditionalDirectories.includes(resolvedPath)) {
|
|
322
|
+
this.instanceAdditionalDirectories.push(resolvedPath);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
279
326
|
/**
|
|
280
327
|
* Add a system-level additional directory that is persistent across configuration reloads
|
|
281
328
|
*/
|
|
@@ -355,6 +402,13 @@ export class PermissionManager {
|
|
|
355
402
|
}
|
|
356
403
|
}
|
|
357
404
|
|
|
405
|
+
// Check instance additional directories
|
|
406
|
+
for (const dir of this.instanceAdditionalDirectories) {
|
|
407
|
+
if (isPathInside(absolutePath, dir)) {
|
|
408
|
+
return { isInside: true, resolvedPath: absolutePath };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
358
412
|
// Check system additional directories
|
|
359
413
|
for (const dir of this.systemAdditionalDirectories) {
|
|
360
414
|
if (isPathInside(absolutePath, dir)) {
|
|
@@ -341,6 +341,8 @@ export class SubagentManager {
|
|
|
341
341
|
],
|
|
342
342
|
additionalDirectories:
|
|
343
343
|
parentPermissionManager?.getAdditionalDirectories(),
|
|
344
|
+
instanceAdditionalDirectories:
|
|
345
|
+
parentPermissionManager?.getInstanceAdditionalDirectories?.(),
|
|
344
346
|
systemAdditionalDirectories:
|
|
345
347
|
parentPermissionManager?.getSystemAdditionalDirectories(),
|
|
346
348
|
planFilePath: parentPermissionManager?.getPlanFilePath(),
|
package/src/prompts/index.ts
CHANGED
|
@@ -396,6 +396,7 @@ export function buildSystemPrompt(
|
|
|
396
396
|
language?: string;
|
|
397
397
|
isSubagent?: boolean;
|
|
398
398
|
worktreeSession?: WorktreeSession | null;
|
|
399
|
+
additionalWorkingDirectories?: string[];
|
|
399
400
|
autoMemory?: {
|
|
400
401
|
directory: string;
|
|
401
402
|
content: string;
|
|
@@ -431,11 +432,17 @@ export function buildSystemPrompt(
|
|
|
431
432
|
const osVersion = getUnameSR();
|
|
432
433
|
const primaryWorkdir = options.originalWorkdir ?? options.workdir;
|
|
433
434
|
const worktreeSession = options.worktreeSession;
|
|
435
|
+
const additionalWorkingDirectories =
|
|
436
|
+
options.additionalWorkingDirectories || [];
|
|
434
437
|
|
|
435
438
|
if (options.isSubagent) {
|
|
436
439
|
// Subagent env section, aligned with Claude Code's computeEnvInfo() +
|
|
437
440
|
// enhanceSystemPromptWithEnvDetails() (without the model description and
|
|
438
441
|
// knowledge cutoff lines, which Wave does not use).
|
|
442
|
+
const additionalDirsInfo =
|
|
443
|
+
additionalWorkingDirectories.length > 0
|
|
444
|
+
? `Additional working directories: ${additionalWorkingDirectories.join(", ")}\n`
|
|
445
|
+
: "";
|
|
439
446
|
dynamicText += `
|
|
440
447
|
|
|
441
448
|
${SUBAGENT_ENV_NOTES}
|
|
@@ -444,7 +451,7 @@ Here is useful information about the environment you are running in:
|
|
|
444
451
|
<env>
|
|
445
452
|
Working directory: ${primaryWorkdir}
|
|
446
453
|
Is directory a git repo: ${isGitRepo}
|
|
447
|
-
Platform: ${platform}
|
|
454
|
+
${additionalDirsInfo}Platform: ${platform}
|
|
448
455
|
${shellInfo}
|
|
449
456
|
OS Version: ${osVersion}
|
|
450
457
|
</env>
|
|
@@ -453,21 +460,31 @@ OS Version: ${osVersion}
|
|
|
453
460
|
// Main agent env section, aligned with Claude Code's
|
|
454
461
|
// computeSimpleEnvInfo() (without the model description, knowledge
|
|
455
462
|
// cutoff, and marketing lines, which Wave does not use).
|
|
456
|
-
const envItems = [
|
|
463
|
+
const envItems: Array<string | string[]> = [
|
|
457
464
|
`Primary working directory: ${primaryWorkdir}`,
|
|
458
465
|
worktreeSession
|
|
459
466
|
? `This is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root.`
|
|
460
467
|
: null,
|
|
461
468
|
`Is a git repository: ${isGitRepo}`,
|
|
469
|
+
additionalWorkingDirectories.length > 0
|
|
470
|
+
? `Additional working directories:`
|
|
471
|
+
: null,
|
|
472
|
+
additionalWorkingDirectories.length > 0
|
|
473
|
+
? additionalWorkingDirectories
|
|
474
|
+
: null,
|
|
462
475
|
`Platform: ${platform}`,
|
|
463
476
|
shellInfo,
|
|
464
477
|
`OS Version: ${osVersion}`,
|
|
465
|
-
].filter((item): item is string => item !== null);
|
|
478
|
+
].filter((item): item is string | string[] => item !== null);
|
|
466
479
|
|
|
467
480
|
const envBlock = [
|
|
468
481
|
`# Environment`,
|
|
469
482
|
`You have been invoked in the following environment: `,
|
|
470
|
-
...envItems.
|
|
483
|
+
...envItems.flatMap((item) =>
|
|
484
|
+
Array.isArray(item)
|
|
485
|
+
? item.map((subItem) => ` - ${subItem}`)
|
|
486
|
+
: [` - ${item}`],
|
|
487
|
+
),
|
|
471
488
|
].join("\n");
|
|
472
489
|
|
|
473
490
|
dynamicText += `\n\n${envBlock}`;
|
|
@@ -919,6 +919,46 @@ export class ConfigurationService {
|
|
|
919
919
|
}
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
+
/**
|
|
923
|
+
* Add an additional directory to the local settings.local.json
|
|
924
|
+
* `permissions.additionalDirectories`, so it persists across sessions.
|
|
925
|
+
*/
|
|
926
|
+
async addAdditionalDirectory(
|
|
927
|
+
workdir: string,
|
|
928
|
+
directory: string,
|
|
929
|
+
): Promise<void> {
|
|
930
|
+
const localConfigPath = path.join(workdir, ".wave", "settings.local.json");
|
|
931
|
+
|
|
932
|
+
// Ensure .wave directory exists
|
|
933
|
+
const waveDir = path.join(workdir, ".wave");
|
|
934
|
+
ensureWaveRuntimeFilesExcluded(workdir);
|
|
935
|
+
if (!existsSync(waveDir)) {
|
|
936
|
+
await fs.mkdir(waveDir, { recursive: true });
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
let config: WaveConfiguration = {};
|
|
940
|
+
if (existsSync(localConfigPath)) {
|
|
941
|
+
try {
|
|
942
|
+
const content = await fs.readFile(localConfigPath, "utf-8");
|
|
943
|
+
config = JSON.parse(content);
|
|
944
|
+
} catch {
|
|
945
|
+
// If file is corrupted, start with empty config
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (!config.permissions) {
|
|
950
|
+
config.permissions = {};
|
|
951
|
+
}
|
|
952
|
+
if (!config.permissions.additionalDirectories) {
|
|
953
|
+
config.permissions.additionalDirectories = [];
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (!config.permissions.additionalDirectories.includes(directory)) {
|
|
957
|
+
config.permissions.additionalDirectories.push(directory);
|
|
958
|
+
await atomicWriteFile(localConfigPath, JSON.stringify(config, null, 2));
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
922
962
|
/**
|
|
923
963
|
* Update the enabled state of a plugin in the specified scope
|
|
924
964
|
*/
|
package/src/types/agent.ts
CHANGED
|
@@ -85,6 +85,12 @@ export interface AgentOptions {
|
|
|
85
85
|
* These rules follow the standard permission rule syntax: `ToolName` or `ToolName(pattern)`.
|
|
86
86
|
*/
|
|
87
87
|
disallowedTools?: string[];
|
|
88
|
+
/**
|
|
89
|
+
* Optional list of additional directories to include in the Safe Zone
|
|
90
|
+
* for this agent session. These directories are listed in the system
|
|
91
|
+
* prompt and treated as safe for file operations (e.g. in acceptEdits mode).
|
|
92
|
+
*/
|
|
93
|
+
additionalDirectories?: string[];
|
|
88
94
|
/**
|
|
89
95
|
* Optional MCP server configs to connect at startup.
|
|
90
96
|
* Overrides .mcp.json for specified server names.
|
|
@@ -176,6 +176,7 @@ export function setupAgentContainer(
|
|
|
176
176
|
const permissionManager = new PermissionManager(container, {
|
|
177
177
|
instanceAllowedRules: options.allowedTools,
|
|
178
178
|
instanceDeniedRules: options.disallowedTools,
|
|
179
|
+
instanceAdditionalDirectories: options.additionalDirectories,
|
|
179
180
|
});
|
|
180
181
|
if (configurationService.resolveAutoMemoryEnabled()) {
|
|
181
182
|
const autoMemoryDir = memoryService.getAutoMemoryDirectory(workdir);
|