wave-agent-sdk 0.12.10 → 0.13.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/constants/tools.d.ts +1 -0
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +1 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +5 -3
- package/dist/managers/mcpManager.d.ts +9 -0
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +46 -1
- package/dist/managers/messageManager.d.ts +10 -0
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +39 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +15 -9
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +4 -2
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +9 -1
- package/dist/services/MarketplaceService.d.ts +6 -1
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +34 -1
- package/dist/services/configurationService.d.ts +1 -5
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +21 -21
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +1 -1
- package/dist/services/interactionService.d.ts.map +1 -1
- package/dist/services/interactionService.js +2 -2
- package/dist/tools/enterPlanMode.d.ts +6 -0
- package/dist/tools/enterPlanMode.d.ts.map +1 -0
- package/dist/tools/enterPlanMode.js +87 -0
- package/dist/types/messaging.d.ts +4 -1
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/permissions.d.ts +1 -1
- package/dist/types/permissions.d.ts.map +1 -1
- package/dist/types/permissions.js +2 -1
- package/dist/utils/containerSetup.js +2 -4
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +2 -1
- package/dist/utils/messageOperations.d.ts +2 -1
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +14 -6
- package/package.json +1 -1
- package/src/constants/tools.ts +1 -0
- package/src/managers/aiManager.ts +12 -3
- package/src/managers/mcpManager.ts +56 -1
- package/src/managers/messageManager.ts +43 -0
- package/src/managers/slashCommandManager.ts +17 -10
- package/src/managers/subagentManager.ts +4 -2
- package/src/managers/toolManager.ts +13 -1
- package/src/services/MarketplaceService.ts +33 -1
- package/src/services/configurationService.ts +21 -22
- package/src/services/initializationService.ts +3 -1
- package/src/services/interactionService.ts +3 -2
- package/src/tools/enterPlanMode.ts +107 -0
- package/src/types/messaging.ts +4 -1
- package/src/types/permissions.ts +2 -0
- package/src/utils/containerSetup.ts +4 -4
- package/src/utils/convertMessagesForAPI.ts +2 -1
- package/src/utils/messageOperations.ts +15 -5
|
@@ -430,6 +430,8 @@ export class MessageManager {
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
public updateToolBlock(params: AgentToolBlockUpdateParams): void {
|
|
433
|
+
// Finalize any streaming text/reasoning blocks before adding/updating a tool block
|
|
434
|
+
this.finalizeCurrentStreamingBlocks();
|
|
433
435
|
const newMessages = updateToolBlockInMessage({
|
|
434
436
|
messages: this.messages,
|
|
435
437
|
...params,
|
|
@@ -447,6 +449,8 @@ export class MessageManager {
|
|
|
447
449
|
messageId: string,
|
|
448
450
|
params: Omit<AgentToolBlockUpdateParams, "id">,
|
|
449
451
|
): string {
|
|
452
|
+
// Finalize any streaming text/reasoning blocks before adding a tool block
|
|
453
|
+
this.finalizeCurrentStreamingBlocks();
|
|
450
454
|
const { messages: newMessages, toolBlockId } =
|
|
451
455
|
addToolBlockToMessageInMessages(this.messages, messageId, params);
|
|
452
456
|
this.setMessages(newMessages);
|
|
@@ -647,12 +651,14 @@ export class MessageManager {
|
|
|
647
651
|
lastMessage.blocks[textBlockIndex] = {
|
|
648
652
|
type: "text",
|
|
649
653
|
content: newAccumulatedContent,
|
|
654
|
+
stage: "streaming",
|
|
650
655
|
};
|
|
651
656
|
} else {
|
|
652
657
|
// Add new text block if none exists
|
|
653
658
|
lastMessage.blocks.push({
|
|
654
659
|
type: "text",
|
|
655
660
|
content: newAccumulatedContent,
|
|
661
|
+
stage: "streaming",
|
|
656
662
|
});
|
|
657
663
|
}
|
|
658
664
|
|
|
@@ -696,12 +702,14 @@ export class MessageManager {
|
|
|
696
702
|
lastMessage.blocks[reasoningBlockIndex] = {
|
|
697
703
|
type: "reasoning",
|
|
698
704
|
content: newAccumulatedReasoning,
|
|
705
|
+
stage: "streaming",
|
|
699
706
|
};
|
|
700
707
|
} else {
|
|
701
708
|
// Add new reasoning block if none exists
|
|
702
709
|
lastMessage.blocks.push({
|
|
703
710
|
type: "reasoning",
|
|
704
711
|
content: newAccumulatedReasoning,
|
|
712
|
+
stage: "streaming",
|
|
705
713
|
});
|
|
706
714
|
}
|
|
707
715
|
|
|
@@ -714,6 +722,41 @@ export class MessageManager {
|
|
|
714
722
|
this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
|
|
715
723
|
}
|
|
716
724
|
|
|
725
|
+
/**
|
|
726
|
+
* Public wrapper for finalizeCurrentStreamingBlocks.
|
|
727
|
+
* Finalizes text/reasoning blocks after streaming completes (e.g. final response with no tools).
|
|
728
|
+
*/
|
|
729
|
+
public finalizeStreamingBlocks(): void {
|
|
730
|
+
this.finalizeCurrentStreamingBlocks();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Finalize streaming text/reasoning blocks by setting their stage to "end".
|
|
735
|
+
* Called when a new block (e.g. tool) is appended during streaming.
|
|
736
|
+
*/
|
|
737
|
+
private finalizeCurrentStreamingBlocks(): void {
|
|
738
|
+
if (this.messages.length === 0) return;
|
|
739
|
+
const lastMessage = this.messages[this.messages.length - 1];
|
|
740
|
+
if (lastMessage.role !== "assistant") return;
|
|
741
|
+
|
|
742
|
+
const newBlocks = lastMessage.blocks.map((block) => {
|
|
743
|
+
if (
|
|
744
|
+
(block.type === "text" || block.type === "reasoning") &&
|
|
745
|
+
block.stage === "streaming"
|
|
746
|
+
) {
|
|
747
|
+
return { ...block, stage: "end" as const };
|
|
748
|
+
}
|
|
749
|
+
return block;
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// Only update if something changed
|
|
753
|
+
const changed = newBlocks.some((b, i) => b !== lastMessage.blocks[i]);
|
|
754
|
+
if (changed) {
|
|
755
|
+
lastMessage.blocks = newBlocks;
|
|
756
|
+
this.callbacks.onMessagesChange?.([...this.messages]);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
717
760
|
/**
|
|
718
761
|
* Remove the last user message from the conversation
|
|
719
762
|
* Used for hook error handling when the user prompt needs to be erased
|
|
@@ -179,7 +179,8 @@ export class SlashCommandManager {
|
|
|
179
179
|
if (skill.context === "fork") {
|
|
180
180
|
// Forked skill execution: add user message with text + tool block
|
|
181
181
|
const messageId = this.messageManager.addUserMessage({
|
|
182
|
-
content: `/${skill.name} ${args
|
|
182
|
+
content: `/${skill.name}${args ? ` ${args}` : ""}`,
|
|
183
|
+
customCommandContent: prepared.content,
|
|
183
184
|
});
|
|
184
185
|
|
|
185
186
|
const toolBlockId = this.messageManager.addToolBlockToMessage(
|
|
@@ -285,7 +286,8 @@ export class SlashCommandManager {
|
|
|
285
286
|
|
|
286
287
|
// Add user message with the processed content
|
|
287
288
|
this.messageManager.addUserMessage({
|
|
288
|
-
content:
|
|
289
|
+
content: `/${skill.name}${args ? ` ${args}` : ""}`,
|
|
290
|
+
customCommandContent: result.content,
|
|
289
291
|
});
|
|
290
292
|
|
|
291
293
|
// Trigger AI response
|
|
@@ -489,20 +491,25 @@ export class SlashCommandManager {
|
|
|
489
491
|
// Parse bash commands from the content
|
|
490
492
|
const { commands, processedContent } = parseBashCommands(content);
|
|
491
493
|
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
+
// Add user message immediately so text block shows before bash execution
|
|
495
|
+
const messageId = this.messageManager.addUserMessage({
|
|
496
|
+
content: `/${commandName}`,
|
|
497
|
+
customCommandContent: processedContent,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// Execute bash commands and update the message if any exist
|
|
494
501
|
if (commands.length > 0) {
|
|
495
502
|
const bashResults = await executeBashCommands(commands, this.workdir);
|
|
496
|
-
finalContent = replaceBashCommandsWithOutput(
|
|
503
|
+
const finalContent = replaceBashCommandsWithOutput(
|
|
497
504
|
processedContent,
|
|
498
505
|
bashResults,
|
|
499
506
|
);
|
|
500
|
-
}
|
|
501
507
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
508
|
+
// Update the user message with the bash-processed content
|
|
509
|
+
this.messageManager.updateUserMessage(messageId, {
|
|
510
|
+
customCommandContent: finalContent,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
506
513
|
|
|
507
514
|
// Execute the AI conversation with custom configuration
|
|
508
515
|
await this.aiManager.sendAIMessage({
|
|
@@ -343,7 +343,8 @@ export class SubagentManager {
|
|
|
343
343
|
outputPath: logPath,
|
|
344
344
|
subagentId: instance.subagentId,
|
|
345
345
|
onStop: () => {
|
|
346
|
-
instance.logStream?.
|
|
346
|
+
instance.logStream?.destroy();
|
|
347
|
+
instance.logStream = undefined;
|
|
347
348
|
instance.aiManager.abortAIMessage();
|
|
348
349
|
this.cleanupInstance(instance.subagentId);
|
|
349
350
|
},
|
|
@@ -421,7 +422,8 @@ export class SubagentManager {
|
|
|
421
422
|
outputPath: logPath,
|
|
422
423
|
subagentId: instance.subagentId,
|
|
423
424
|
onStop: () => {
|
|
424
|
-
instance.logStream?.
|
|
425
|
+
instance.logStream?.destroy();
|
|
426
|
+
instance.logStream = undefined;
|
|
425
427
|
instance.aiManager.abortAIMessage();
|
|
426
428
|
this.cleanupInstance(instance.subagentId);
|
|
427
429
|
},
|
|
@@ -4,6 +4,7 @@ import { taskStopTool } from "../tools/taskStopTool.js";
|
|
|
4
4
|
import { editTool } from "../tools/editTool.js";
|
|
5
5
|
import { writeTool } from "../tools/writeTool.js";
|
|
6
6
|
import { exitPlanModeTool } from "../tools/exitPlanMode.js";
|
|
7
|
+
import { enterPlanModeTool } from "../tools/enterPlanMode.js";
|
|
7
8
|
import { askUserQuestionTool } from "../tools/askUserQuestion.js";
|
|
8
9
|
import { cronCreateTool } from "../tools/cronCreateTool.js";
|
|
9
10
|
import { cronDeleteTool } from "../tools/cronDeleteTool.js";
|
|
@@ -106,6 +107,7 @@ class ToolManager {
|
|
|
106
107
|
editTool,
|
|
107
108
|
writeTool,
|
|
108
109
|
exitPlanModeTool,
|
|
110
|
+
enterPlanModeTool,
|
|
109
111
|
askUserQuestionTool,
|
|
110
112
|
globTool,
|
|
111
113
|
grepTool,
|
|
@@ -296,13 +298,23 @@ class ToolManager {
|
|
|
296
298
|
return false;
|
|
297
299
|
}
|
|
298
300
|
if (effectivePermissionMode === "bypassPermissions") {
|
|
299
|
-
if (
|
|
301
|
+
if (
|
|
302
|
+
tool.name === "ExitPlanMode" ||
|
|
303
|
+
tool.name === "AskUserQuestion" ||
|
|
304
|
+
tool.name === "EnterPlanMode"
|
|
305
|
+
) {
|
|
300
306
|
return false;
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
if (tool.name === "ExitPlanMode") {
|
|
304
310
|
return effectivePermissionMode === "plan";
|
|
305
311
|
}
|
|
312
|
+
if (tool.name === "EnterPlanMode") {
|
|
313
|
+
return (
|
|
314
|
+
effectivePermissionMode !== "plan" &&
|
|
315
|
+
effectivePermissionMode !== "bypassPermissions"
|
|
316
|
+
);
|
|
317
|
+
}
|
|
306
318
|
return true;
|
|
307
319
|
})
|
|
308
320
|
.map((tool) => {
|
|
@@ -69,7 +69,28 @@ export class MarketplaceService {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* Check if a lock file is stale by reading its PID and checking if the process is alive.
|
|
73
|
+
* Returns true if the lock is stale and safe to remove.
|
|
74
|
+
*/
|
|
75
|
+
private async isStaleLock(): Promise<boolean> {
|
|
76
|
+
try {
|
|
77
|
+
const content = await fs.readFile(this.lockPath, "utf-8");
|
|
78
|
+
const pid = parseInt(content.trim(), 10);
|
|
79
|
+
if (isNaN(pid)) return true;
|
|
80
|
+
// Check if the process is still running
|
|
81
|
+
try {
|
|
82
|
+
process.kill(pid, 0);
|
|
83
|
+
return false; // Process exists, lock is valid
|
|
84
|
+
} catch {
|
|
85
|
+
return true; // Process doesn't exist, lock is stale
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
return true; // Can't read lock file, assume stale
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Acquires a file-based lock (with PID tracking for stale lock detection) and executes the provided function.
|
|
73
94
|
* Supports re-entrancy within the same process.
|
|
74
95
|
*/
|
|
75
96
|
private async withLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
@@ -92,6 +113,14 @@ export class MarketplaceService {
|
|
|
92
113
|
"code" in error &&
|
|
93
114
|
error.code === "EEXIST"
|
|
94
115
|
) {
|
|
116
|
+
// Check for stale lock every 60 retries (every ~6 seconds)
|
|
117
|
+
if (i > 0 && i % 60 === 0) {
|
|
118
|
+
const stale = await this.isStaleLock();
|
|
119
|
+
if (stale) {
|
|
120
|
+
await fs.unlink(this.lockPath).catch(() => {});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
95
124
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
96
125
|
continue;
|
|
97
126
|
}
|
|
@@ -105,6 +134,9 @@ export class MarketplaceService {
|
|
|
105
134
|
);
|
|
106
135
|
}
|
|
107
136
|
|
|
137
|
+
// Write PID into the lock file for stale lock detection
|
|
138
|
+
await fs.writeFile(this.lockPath, String(process.pid), "utf-8");
|
|
139
|
+
|
|
108
140
|
MarketplaceService.isLockedInProcess = true;
|
|
109
141
|
try {
|
|
110
142
|
return await fn();
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { readFileSync, existsSync, promises as fs } from "fs";
|
|
9
9
|
import * as path from "path";
|
|
10
10
|
import { isValidHookEvent } from "../types/hooks.js";
|
|
11
|
+
import { logger } from "../utils/globalLogger.js";
|
|
11
12
|
import type {
|
|
12
13
|
ConfigurationLoadResult,
|
|
13
14
|
ValidationResult,
|
|
@@ -50,8 +51,8 @@ import { parseCustomHeaders } from "../utils/stringUtils.js";
|
|
|
50
51
|
*/
|
|
51
52
|
export class ConfigurationService {
|
|
52
53
|
private currentConfiguration: WaveConfiguration | null = null;
|
|
53
|
-
private env: Record<string, string> = {};
|
|
54
54
|
private options: AgentOptions = {};
|
|
55
|
+
private _configuredEnvKeys = new Set<string>();
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
58
|
* Set agent options for configuration resolution
|
|
@@ -356,14 +357,13 @@ export class ConfigurationService {
|
|
|
356
357
|
* This replaces direct process.env modification
|
|
357
358
|
*/
|
|
358
359
|
setEnvironmentVars(env: Record<string, string>): void {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return { ...this.env };
|
|
360
|
+
for (const [key, value] of Object.entries(env)) {
|
|
361
|
+
if (process.env[key] !== undefined && !this._configuredEnvKeys.has(key)) {
|
|
362
|
+
logger.warn(`Overriding environment variable: ${key}`);
|
|
363
|
+
}
|
|
364
|
+
process.env[key] = value;
|
|
365
|
+
this._configuredEnvKeys.add(key);
|
|
366
|
+
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
// =============================================================================
|
|
@@ -396,7 +396,7 @@ export class ConfigurationService {
|
|
|
396
396
|
} else if (this.options.apiKey !== undefined) {
|
|
397
397
|
resolvedApiKey = this.options.apiKey;
|
|
398
398
|
} else {
|
|
399
|
-
resolvedApiKey =
|
|
399
|
+
resolvedApiKey = process.env.WAVE_API_KEY;
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
// Resolve base URL: override > options > env (settings.json) > process.env
|
|
@@ -407,7 +407,7 @@ export class ConfigurationService {
|
|
|
407
407
|
} else if (this.options.baseURL !== undefined) {
|
|
408
408
|
resolvedBaseURL = this.options.baseURL;
|
|
409
409
|
} else {
|
|
410
|
-
resolvedBaseURL =
|
|
410
|
+
resolvedBaseURL = process.env.WAVE_BASE_URL || "";
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
// Fallback to process.env if still not resolved (for dynamic updates in tests)
|
|
@@ -422,7 +422,7 @@ export class ConfigurationService {
|
|
|
422
422
|
throw new ConfigurationError(CONFIG_ERRORS.MISSING_BASE_URL, "baseURL", {
|
|
423
423
|
constructor: baseURL,
|
|
424
424
|
environment: process.env.WAVE_BASE_URL,
|
|
425
|
-
settings:
|
|
425
|
+
settings: process.env.WAVE_BASE_URL,
|
|
426
426
|
});
|
|
427
427
|
}
|
|
428
428
|
|
|
@@ -436,7 +436,7 @@ export class ConfigurationService {
|
|
|
436
436
|
|
|
437
437
|
// Resolve custom headers from environment: env (settings.json) > process.env
|
|
438
438
|
const envCustomHeaders =
|
|
439
|
-
|
|
439
|
+
process.env.WAVE_CUSTOM_HEADERS || process.env.WAVE_CUSTOM_HEADERS || "";
|
|
440
440
|
const parsedEnvHeaders = parseCustomHeaders(envCustomHeaders);
|
|
441
441
|
|
|
442
442
|
// Merge headers: env headers < options < override
|
|
@@ -475,14 +475,14 @@ export class ConfigurationService {
|
|
|
475
475
|
const resolvedAgentModel =
|
|
476
476
|
model ||
|
|
477
477
|
this.options.model ||
|
|
478
|
-
|
|
478
|
+
process.env.WAVE_MODEL ||
|
|
479
479
|
process.env.WAVE_MODEL;
|
|
480
480
|
|
|
481
481
|
// Resolve fast model: override > options > env (settings.json) > process.env
|
|
482
482
|
const resolvedFastModel =
|
|
483
483
|
fastModel ||
|
|
484
484
|
this.options.fastModel ||
|
|
485
|
-
|
|
485
|
+
process.env.WAVE_FAST_MODEL ||
|
|
486
486
|
process.env.WAVE_FAST_MODEL;
|
|
487
487
|
|
|
488
488
|
// Validate required fields
|
|
@@ -490,7 +490,6 @@ export class ConfigurationService {
|
|
|
490
490
|
throw new ConfigurationError(CONFIG_ERRORS.MISSING_MODEL, "model", {
|
|
491
491
|
constructor: model,
|
|
492
492
|
environment: process.env.WAVE_MODEL,
|
|
493
|
-
settings: this.env.WAVE_MODEL,
|
|
494
493
|
});
|
|
495
494
|
}
|
|
496
495
|
|
|
@@ -501,7 +500,6 @@ export class ConfigurationService {
|
|
|
501
500
|
{
|
|
502
501
|
constructor: fastModel,
|
|
503
502
|
environment: process.env.WAVE_FAST_MODEL,
|
|
504
|
-
settings: this.env.WAVE_FAST_MODEL,
|
|
505
503
|
},
|
|
506
504
|
);
|
|
507
505
|
}
|
|
@@ -549,7 +547,7 @@ export class ConfigurationService {
|
|
|
549
547
|
|
|
550
548
|
// Try env (settings.json) first, then process.env
|
|
551
549
|
const envMaxInputTokens =
|
|
552
|
-
|
|
550
|
+
process.env.WAVE_MAX_INPUT_TOKENS || process.env.WAVE_MAX_INPUT_TOKENS;
|
|
553
551
|
if (envMaxInputTokens) {
|
|
554
552
|
const parsed = parseInt(envMaxInputTokens, 10);
|
|
555
553
|
if (!isNaN(parsed)) {
|
|
@@ -599,7 +597,8 @@ export class ConfigurationService {
|
|
|
599
597
|
|
|
600
598
|
// 2. WAVE_DISABLE_AUTO_MEMORY environment variable
|
|
601
599
|
const disableAutoMemory =
|
|
602
|
-
|
|
600
|
+
process.env.WAVE_DISABLE_AUTO_MEMORY ||
|
|
601
|
+
process.env.WAVE_DISABLE_AUTO_MEMORY;
|
|
603
602
|
if (disableAutoMemory === "1" || disableAutoMemory === "true") {
|
|
604
603
|
return false;
|
|
605
604
|
}
|
|
@@ -621,7 +620,7 @@ export class ConfigurationService {
|
|
|
621
620
|
|
|
622
621
|
// 2. WAVE_AUTO_MEMORY_FREQUENCY environment variable
|
|
623
622
|
const envFrequency =
|
|
624
|
-
|
|
623
|
+
process.env.WAVE_AUTO_MEMORY_FREQUENCY ||
|
|
625
624
|
process.env.WAVE_AUTO_MEMORY_FREQUENCY;
|
|
626
625
|
if (envFrequency) {
|
|
627
626
|
const parsed = parseInt(envFrequency, 10);
|
|
@@ -653,7 +652,7 @@ export class ConfigurationService {
|
|
|
653
652
|
|
|
654
653
|
// Try env (settings.json) first, then process.env
|
|
655
654
|
const envMaxOutputTokens =
|
|
656
|
-
|
|
655
|
+
process.env.WAVE_MAX_OUTPUT_TOKENS || process.env.WAVE_MAX_OUTPUT_TOKENS;
|
|
657
656
|
if (envMaxOutputTokens) {
|
|
658
657
|
const parsed = parseInt(envMaxOutputTokens, 10);
|
|
659
658
|
if (!isNaN(parsed) && parsed > 0) {
|
|
@@ -680,7 +679,7 @@ export class ConfigurationService {
|
|
|
680
679
|
|
|
681
680
|
// Add current model from options or environment
|
|
682
681
|
const currentModel =
|
|
683
|
-
this.options.model ||
|
|
682
|
+
this.options.model || process.env.WAVE_MODEL || process.env.WAVE_MODEL;
|
|
684
683
|
if (currentModel) {
|
|
685
684
|
models.add(currentModel);
|
|
686
685
|
}
|
|
@@ -192,7 +192,9 @@ export class InitializationService {
|
|
|
192
192
|
transcriptPath: messageManager.getTranscriptPath(),
|
|
193
193
|
cwd: workdir,
|
|
194
194
|
worktreeName: agentOptions.worktreeName,
|
|
195
|
-
env:
|
|
195
|
+
env: Object.fromEntries(
|
|
196
|
+
Object.entries(process.env).filter((e) => e[1] !== undefined),
|
|
197
|
+
) as Record<string, string>,
|
|
196
198
|
});
|
|
197
199
|
|
|
198
200
|
// Process hook results
|
|
@@ -33,7 +33,6 @@ export class InteractionService {
|
|
|
33
33
|
slashCommandManager,
|
|
34
34
|
hookManager,
|
|
35
35
|
workdir,
|
|
36
|
-
configurationService,
|
|
37
36
|
logger,
|
|
38
37
|
aiManager,
|
|
39
38
|
} = context;
|
|
@@ -83,7 +82,9 @@ export class InteractionService {
|
|
|
83
82
|
transcriptPath: messageManager.getTranscriptPath(),
|
|
84
83
|
cwd: workdir,
|
|
85
84
|
userPrompt: content,
|
|
86
|
-
env:
|
|
85
|
+
env: Object.fromEntries(
|
|
86
|
+
Object.entries(process.env).filter((e) => e[1] !== undefined),
|
|
87
|
+
) as Record<string, string>, // Include environment variables
|
|
87
88
|
},
|
|
88
89
|
);
|
|
89
90
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { logger } from "../utils/globalLogger.js";
|
|
2
|
+
import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
|
|
3
|
+
import { ENTER_PLAN_MODE_TOOL_NAME } from "../constants/tools.js";
|
|
4
|
+
import { OPERATION_CANCELLED_BY_USER } from "../types/permissions.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enter Plan Mode Tool Plugin
|
|
8
|
+
*/
|
|
9
|
+
export const enterPlanModeTool: ToolPlugin = {
|
|
10
|
+
name: ENTER_PLAN_MODE_TOOL_NAME,
|
|
11
|
+
config: {
|
|
12
|
+
type: "function",
|
|
13
|
+
function: {
|
|
14
|
+
name: ENTER_PLAN_MODE_TOOL_NAME,
|
|
15
|
+
description:
|
|
16
|
+
"Request to enter plan mode for complex tasks requiring user approval before coding",
|
|
17
|
+
parameters: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {},
|
|
20
|
+
required: [],
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
prompt:
|
|
26
|
+
() => `Use this tool to proactively request entering plan mode when a task is non-trivial and benefits from planning before implementation.
|
|
27
|
+
|
|
28
|
+
## When to Use This Tool
|
|
29
|
+
- Multi-file changes or refactoring that requires architectural decisions
|
|
30
|
+
- New features with multiple implementation steps or design trade-offs
|
|
31
|
+
- Tasks where you want user approval on approach before writing code
|
|
32
|
+
- Complex bug fixes that span multiple components
|
|
33
|
+
|
|
34
|
+
## When NOT to Use This Tool
|
|
35
|
+
- Simple fixes (typos, small logic changes in a single file)
|
|
36
|
+
- Research tasks (searching files, reading code, gathering information)
|
|
37
|
+
- Tasks where the implementation approach is straightforward and unambiguous
|
|
38
|
+
- When already in plan mode
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
1. Task: "Refactor the authentication system to use OAuth2" - Use EnterPlanMode to propose an approach before implementation.
|
|
43
|
+
2. Task: "Add input validation to the login form" - Do NOT use EnterPlanMode, just implement it.
|
|
44
|
+
3. Task: "Migrate the database schema to support multi-tenancy" - Use EnterPlanMode to plan the migration steps.
|
|
45
|
+
4. Task: "Fix the off-by-one error in the pagination logic" - Do NOT use EnterPlanMode, just fix the bug.
|
|
46
|
+
`,
|
|
47
|
+
execute: async (
|
|
48
|
+
_args: Record<string, unknown>,
|
|
49
|
+
context: ToolContext,
|
|
50
|
+
): Promise<ToolResult> => {
|
|
51
|
+
try {
|
|
52
|
+
if (!context.permissionManager) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
content: "",
|
|
56
|
+
error: "Permission manager is not available",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const permissionContext = context.permissionManager.createContext(
|
|
61
|
+
ENTER_PLAN_MODE_TOOL_NAME,
|
|
62
|
+
context.permissionMode || "default",
|
|
63
|
+
context.canUseToolCallback,
|
|
64
|
+
{},
|
|
65
|
+
context.toolCallId,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// No "allow always" option for plan mode transitions (matching Claude Code behavior)
|
|
69
|
+
permissionContext.hidePersistentOption = true;
|
|
70
|
+
|
|
71
|
+
const permissionResult =
|
|
72
|
+
await context.permissionManager.checkPermission(permissionContext);
|
|
73
|
+
|
|
74
|
+
if (permissionResult.behavior === "deny") {
|
|
75
|
+
if (permissionResult.message === OPERATION_CANCELLED_BY_USER) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
content: OPERATION_CANCELLED_BY_USER,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
content: `User declined to enter plan mode. Proceed in current mode.`,
|
|
84
|
+
error: permissionResult.message
|
|
85
|
+
? undefined
|
|
86
|
+
: "Operation declined by user",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
content:
|
|
93
|
+
"Plan mode entered successfully. Please write your plan to the plan file and use ExitPlanMode when ready for approval.",
|
|
94
|
+
shortResult: "Plan mode entered",
|
|
95
|
+
};
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const errorMessage =
|
|
98
|
+
error instanceof Error ? error.message : String(error);
|
|
99
|
+
logger.error(`EnterPlanMode tool error: ${errorMessage}`);
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
content: "",
|
|
103
|
+
error: errorMessage,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
package/src/types/messaging.ts
CHANGED
|
@@ -32,7 +32,9 @@ export type MessageBlock =
|
|
|
32
32
|
export interface TextBlock {
|
|
33
33
|
type: "text";
|
|
34
34
|
content: string;
|
|
35
|
+
customCommandContent?: string;
|
|
35
36
|
source?: MessageSource;
|
|
37
|
+
stage?: "streaming" | "end";
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export interface ErrorBlock {
|
|
@@ -77,7 +79,7 @@ export interface BangBlock {
|
|
|
77
79
|
type: "bang";
|
|
78
80
|
command: string;
|
|
79
81
|
output: string;
|
|
80
|
-
|
|
82
|
+
stage: "running" | "end";
|
|
81
83
|
exitCode: number | null;
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -90,6 +92,7 @@ export interface CompressBlock {
|
|
|
90
92
|
export interface ReasoningBlock {
|
|
91
93
|
type: "reasoning";
|
|
92
94
|
content: string;
|
|
95
|
+
stage?: "streaming" | "end";
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
export interface FileHistoryBlock {
|
package/src/types/permissions.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
EDIT_TOOL_NAME,
|
|
13
13
|
BASH_TOOL_NAME,
|
|
14
14
|
WRITE_TOOL_NAME,
|
|
15
|
+
ENTER_PLAN_MODE_TOOL_NAME,
|
|
15
16
|
EXIT_PLAN_MODE_TOOL_NAME,
|
|
16
17
|
ASK_USER_QUESTION_TOOL_NAME,
|
|
17
18
|
} from "../constants/tools.js";
|
|
@@ -68,6 +69,7 @@ export const RESTRICTED_TOOLS = [
|
|
|
68
69
|
EDIT_TOOL_NAME,
|
|
69
70
|
BASH_TOOL_NAME,
|
|
70
71
|
WRITE_TOOL_NAME,
|
|
72
|
+
ENTER_PLAN_MODE_TOOL_NAME,
|
|
71
73
|
EXIT_PLAN_MODE_TOOL_NAME,
|
|
72
74
|
ASK_USER_QUESTION_TOOL_NAME,
|
|
73
75
|
] as const;
|
|
@@ -111,9 +111,7 @@ export function setupAgentContainer(
|
|
|
111
111
|
container.register("MessageManager", messageManager);
|
|
112
112
|
|
|
113
113
|
const resolvedTaskListId =
|
|
114
|
-
|
|
115
|
-
process.env.WAVE_TASK_LIST_ID ||
|
|
116
|
-
messageManager.getRootSessionId();
|
|
114
|
+
process.env.WAVE_TASK_LIST_ID || messageManager.getRootSessionId();
|
|
117
115
|
|
|
118
116
|
const taskManager = new TaskManager(container, resolvedTaskListId);
|
|
119
117
|
container.register("TaskManager", taskManager);
|
|
@@ -189,7 +187,9 @@ export function setupAgentContainer(
|
|
|
189
187
|
cwd: workdir,
|
|
190
188
|
toolName: context.toolName,
|
|
191
189
|
toolInput: context.toolInput,
|
|
192
|
-
env:
|
|
190
|
+
env: Object.fromEntries(
|
|
191
|
+
Object.entries(process.env).filter((e) => e[1] !== undefined),
|
|
192
|
+
) as Record<string, string>,
|
|
193
193
|
});
|
|
194
194
|
|
|
195
195
|
if (results.length > 0) {
|
|
@@ -191,9 +191,10 @@ export function convertMessagesForAPI(
|
|
|
191
191
|
block.content &&
|
|
192
192
|
block.content.trim().length > 0
|
|
193
193
|
) {
|
|
194
|
+
const textForApi = block.customCommandContent ?? block.content;
|
|
194
195
|
contentParts.push({
|
|
195
196
|
type: "text",
|
|
196
|
-
text:
|
|
197
|
+
text: textForApi,
|
|
197
198
|
});
|
|
198
199
|
}
|
|
199
200
|
|