wave-agent-sdk 0.18.4 → 0.18.5
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/builtin/skills/settings/ENV.md +1 -1
- package/builtin/skills/settings/MEMORY.md +76 -0
- package/builtin/skills/settings/SKILL.md +4 -4
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +22 -15
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +17 -10
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +5 -4
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/gitUtils.js +6 -6
- package/dist/utils/openaiClient.d.ts.map +1 -1
- package/dist/utils/openaiClient.js +5 -25
- package/dist/utils/path.d.ts +7 -0
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +9 -0
- package/dist/utils/worktreeUtils.d.ts.map +1 -1
- package/dist/utils/worktreeUtils.js +19 -13
- package/package.json +1 -1
- package/src/managers/mcpManager.ts +26 -15
- package/src/tools/bashTool.ts +18 -9
- package/src/tools/editTool.ts +5 -7
- package/src/utils/constants.ts +1 -1
- package/src/utils/gitUtils.ts +6 -6
- package/src/utils/openaiClient.ts +5 -24
- package/src/utils/path.ts +10 -0
- package/src/utils/worktreeUtils.ts +46 -28
- package/builtin/skills/settings/MEMORY_RULES.md +0 -60
|
@@ -28,7 +28,7 @@ Wave uses several environment variables to control its core functionality.
|
|
|
28
28
|
| `WAVE_MODEL` | The primary AI model to use for the agent. | `gemini-3-flash` |
|
|
29
29
|
| `WAVE_FAST_MODEL` | The fast AI model to use for quick tasks. | `gemini-2.5-flash` |
|
|
30
30
|
| `WAVE_MAX_INPUT_TOKENS` | Maximum number of input tokens allowed. | `200000` |
|
|
31
|
-
| `WAVE_MAX_OUTPUT_TOKENS` | Maximum number of output tokens allowed. | `
|
|
31
|
+
| `WAVE_MAX_OUTPUT_TOKENS` | Maximum number of output tokens allowed. | `32000` |
|
|
32
32
|
| `WAVE_DISABLE_AUTO_MEMORY` | Set to `1` or `true` to disable the auto-memory feature. | `false` |
|
|
33
33
|
| `WAVE_AUTO_MEMORY_FREQUENCY` | Auto memory update frequency. `1` = every turn, `2` = every 2 turns, etc. | `1` |
|
|
34
34
|
| `WAVE_TASK_LIST_ID` | Explicitly set the task list ID for the session. | (Session ID) |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Wave Memory
|
|
2
|
+
|
|
3
|
+
Wave provides multiple memory layers to give the agent context-specific instructions and knowledge. Memory files are standard Markdown files loaded automatically at startup.
|
|
4
|
+
|
|
5
|
+
## Memory Layers
|
|
6
|
+
|
|
7
|
+
Wave looks for memory in the following locations, loaded in order of increasing priority:
|
|
8
|
+
|
|
9
|
+
1. **User Memory**: `~/.wave/AGENTS.md` — Global instructions across all projects
|
|
10
|
+
2. **Project Memory**: `AGENTS.md` in the project root — Project-specific instructions
|
|
11
|
+
3. **Memory Rules**: `.wave/rules/*.md` and `~/.wave/rules/*.md` — Modular, path-scoped rules (see below)
|
|
12
|
+
|
|
13
|
+
> `CLAUDE.md` is also supported as a fallback for both user and project memory, for compatibility with existing repositories.
|
|
14
|
+
|
|
15
|
+
## User Memory
|
|
16
|
+
|
|
17
|
+
Stored at `~/.wave/AGENTS.md`. Use it for cross-project preferences and global instructions, e.g. "always write tests for new features", "prefer functional style".
|
|
18
|
+
|
|
19
|
+
## Project Memory
|
|
20
|
+
|
|
21
|
+
### Project Memory File
|
|
22
|
+
|
|
23
|
+
The `AGENTS.md` file in the project root is the primary project-level memory. It is checked into the repository and shared with all contributors. Use it for:
|
|
24
|
+
|
|
25
|
+
- Build and test commands (e.g. "use pnpm not npm")
|
|
26
|
+
- Project structure and architecture conventions
|
|
27
|
+
- Coding standards and patterns
|
|
28
|
+
|
|
29
|
+
### Memory Rules
|
|
30
|
+
|
|
31
|
+
Memory rules are modular Markdown files that provide path-scoped instructions. They are discovered in:
|
|
32
|
+
|
|
33
|
+
- **Project scope**: `.wave/rules/*.md` (checked into the repo)
|
|
34
|
+
- **User scope**: `~/.wave/rules/*.md` (personal, not shared)
|
|
35
|
+
|
|
36
|
+
Each file can optionally include YAML frontmatter to scope rules to specific file paths:
|
|
37
|
+
|
|
38
|
+
```markdown
|
|
39
|
+
---
|
|
40
|
+
paths:
|
|
41
|
+
- "src/api/**/*.ts"
|
|
42
|
+
- "src/services/**/*.ts"
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
# API and Service Guidelines
|
|
46
|
+
|
|
47
|
+
- Always use `async/await` for asynchronous operations.
|
|
48
|
+
- Use `Zod` for input validation.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### YAML Frontmatter Fields
|
|
52
|
+
|
|
53
|
+
- `paths`: (Optional) A list of glob patterns. The rules in this file will only be active when the agent is working with files that match these patterns. If omitted, the rules are always active.
|
|
54
|
+
- `priority`: (Optional) A number controlling rule precedence. Higher priority rules override lower ones on conflict.
|
|
55
|
+
|
|
56
|
+
#### How Memory Rules are Loaded
|
|
57
|
+
|
|
58
|
+
- Wave recursively discovers all `.md` files in `.wave/rules/` and `~/.wave/rules/`.
|
|
59
|
+
- **Path-specific activation**: If a rule has a `paths` field, it is only included in the agent's context when a file being read or modified matches the glob patterns.
|
|
60
|
+
- **Unconditional rules**: Rules without a `paths` field are always active.
|
|
61
|
+
- **Priority**: Project-level rules take priority over user-level rules if there is a conflict.
|
|
62
|
+
|
|
63
|
+
#### Best Practices
|
|
64
|
+
|
|
65
|
+
- **Keep rules focused**: Create separate files for different topics (e.g. `testing.md`, `ui-components.md`).
|
|
66
|
+
- **Leverage path scoping**: Use the `paths` field to keep the agent's context window clean and relevant.
|
|
67
|
+
- **Share with your team**: Commit `.wave/rules/` to your git repository.
|
|
68
|
+
|
|
69
|
+
## Auto-Memory
|
|
70
|
+
|
|
71
|
+
In addition to manual memory files, Wave has an **auto-memory** feature that automatically extracts and remembers important information across sessions. This is stored in `~/.wave/projects/<project-id>/memory/MEMORY.md`.
|
|
72
|
+
|
|
73
|
+
You can control auto-memory in `settings.json`:
|
|
74
|
+
|
|
75
|
+
- `autoMemoryEnabled`: Enable or disable auto-memory (default: `true`).
|
|
76
|
+
- `autoMemoryFrequency`: Frequency of auto-memory extraction turns (default: `1`).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: settings
|
|
3
|
-
description: Manage Wave settings and get guidance on settings.json, hooks, environment variables, permissions, MCP servers, memory
|
|
3
|
+
description: Manage Wave settings and get guidance on settings.json, hooks, environment variables, permissions, MCP servers, memory, skills, subagents, plugins, and plugin marketplaces. Use this when the user wants to view, update, or learn how to configure Wave.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Wave Settings Skill
|
|
@@ -79,9 +79,9 @@ For detailed model configuration, see [MODELS.md](${WAVE_SKILL_DIR}/MODELS.md).
|
|
|
79
79
|
Connect to external servers to provide additional tools and context.
|
|
80
80
|
For detailed MCP configuration, see [MCP.md](${WAVE_SKILL_DIR}/MCP.md).
|
|
81
81
|
|
|
82
|
-
### 6. Memory
|
|
83
|
-
Provide context-specific instructions and
|
|
84
|
-
For detailed memory
|
|
82
|
+
### 6. Memory
|
|
83
|
+
Provide context-specific instructions and knowledge to the agent through user memory, project memory, memory rules, and auto-memory.
|
|
84
|
+
For detailed memory configuration, see [MEMORY.md](${WAVE_SKILL_DIR}/MEMORY.md).
|
|
85
85
|
|
|
86
86
|
### 7. Skills
|
|
87
87
|
Extend Wave's functionality by creating custom skills.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpManager.d.ts","sourceRoot":"","sources":["../../src/managers/mcpManager.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EACV,MAAM,EACN,eAAe,EACf,SAAS,EACT,OAAO,EACP,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAQ3B,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CAC3D;AAID,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC9C;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUnD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAsC7D;AAED,qBAAa,UAAU;IAanB,OAAO,CAAC,SAAS;IAZnB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,UAAU,CAA8C;IAEhE,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,iBAAiB,CAAkC;gBAGjD,SAAS,EAAE,SAAS,EAC5B,OAAO,GAAE,iBAAsB;IAMjC;;OAEG;IACG,UAAU,CACd,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,OAAe,GAC3B,OAAO,CAAC,IAAI,CAAC;IAgDV,kBAAkB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAO/C,UAAU,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAgEvC,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAWrD,SAAS,IAAI,SAAS,GAAG,IAAI;IAI7B,aAAa,IAAI,eAAe,EAAE;IAIlC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAIpD,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI;IASzE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO;IAuDzD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAgB7B,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"mcpManager.d.ts","sourceRoot":"","sources":["../../src/managers/mcpManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -363,28 +363,35 @@ export class McpManager {
|
|
|
363
363
|
cwd: this.workdir, // Use the agent's workdir as the process working directory
|
|
364
364
|
stderr: "pipe", // Pipe stderr to capture it
|
|
365
365
|
});
|
|
366
|
-
//
|
|
366
|
+
// Buffer stderr silently (MCP servers use stderr for all logging;
|
|
367
|
+
// stdout is reserved for JSON-RPC). Only dump it on connection
|
|
368
|
+
// success (once) or failure — not line-by-line in real time.
|
|
369
|
+
// Aligns with Claude Code's approach to avoid noise from [INFO],
|
|
370
|
+
// [DEBUG], Warning: lines that are not actual errors.
|
|
367
371
|
const stderr = transport.stderr;
|
|
372
|
+
let stderrOutput = "";
|
|
368
373
|
if (stderr) {
|
|
369
|
-
let buffer = "";
|
|
370
374
|
stderr.on("data", (chunk) => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
buffer = lines.pop() || "";
|
|
374
|
-
for (const line of lines) {
|
|
375
|
-
if (line.trim()) {
|
|
376
|
-
logger?.error(`[MCP Server ${name}] ${line}`);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
stderr.on("end", () => {
|
|
381
|
-
if (buffer.trim()) {
|
|
382
|
-
logger?.error(`[MCP Server ${name}] ${buffer}`);
|
|
375
|
+
if (stderrOutput.length < 64 * 1024 * 1024) {
|
|
376
|
+
stderrOutput += chunk.toString();
|
|
383
377
|
}
|
|
384
378
|
});
|
|
385
379
|
}
|
|
386
380
|
client = createClient();
|
|
387
|
-
|
|
381
|
+
try {
|
|
382
|
+
await client.connect(transport);
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
if (stderrOutput.trim()) {
|
|
386
|
+
logger?.error(`[MCP Server ${name}] Server stderr: ${stderrOutput.trim()}`);
|
|
387
|
+
}
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
// Dump accumulated stderr once after successful connection
|
|
391
|
+
if (stderrOutput.trim()) {
|
|
392
|
+
logger?.debug(`[MCP Server ${name}] Server stderr: ${stderrOutput.trim()}`);
|
|
393
|
+
stderrOutput = "";
|
|
394
|
+
}
|
|
388
395
|
const toolsResponse = await client.listTools();
|
|
389
396
|
tools =
|
|
390
397
|
toolsResponse.tools?.map((tool) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bashTool.d.ts","sourceRoot":"","sources":["../../src/tools/bashTool.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bashTool.d.ts","sourceRoot":"","sources":["../../src/tools/bashTool.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAuBtE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,UA2iBtB,CAAC"}
|
package/dist/tools/bashTool.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from "path";
|
|
|
4
4
|
import * as os from "os";
|
|
5
5
|
import { logger } from "../utils/globalLogger.js";
|
|
6
6
|
import { resolveShellPath } from "../utils/shellResolver.js";
|
|
7
|
+
import { toPosixPath } from "../utils/path.js";
|
|
7
8
|
import { stripAnsiColors } from "../utils/stringUtils.js";
|
|
8
9
|
import { processToolResult } from "../utils/toolResultStorage.js";
|
|
9
10
|
import { BASH_MAX_OUTPUT_CHARS } from "../constants/toolLimits.js";
|
|
@@ -211,7 +212,8 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
211
212
|
return new Promise((resolve) => {
|
|
212
213
|
// Create a temporary file to store the CWD
|
|
213
214
|
const tempCwdFile = path.join(os.tmpdir(), `wave_cwd_${Date.now()}_${Math.random().toString(36).substring(2, 11)}.tmp`);
|
|
214
|
-
const
|
|
215
|
+
const tempCwdFileForBash = toPosixPath(tempCwdFile);
|
|
216
|
+
const wrappedCommand = `${command} && pwd -P >| ${tempCwdFileForBash}`;
|
|
215
217
|
const child = spawn(wrappedCommand, {
|
|
216
218
|
shell: shellPath || true,
|
|
217
219
|
stdio: "pipe",
|
|
@@ -226,6 +228,17 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
226
228
|
let isAborted = false;
|
|
227
229
|
let isBackgrounded = false;
|
|
228
230
|
let isFinished = false;
|
|
231
|
+
// Best-effort cleanup of the temp CWD file — used by abort/error/exit paths
|
|
232
|
+
const cleanupTempFile = () => {
|
|
233
|
+
try {
|
|
234
|
+
if (fs.existsSync(tempCwdFile)) {
|
|
235
|
+
fs.unlinkSync(tempCwdFile);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// ignore — best-effort cleanup
|
|
240
|
+
}
|
|
241
|
+
};
|
|
229
242
|
const updateRealtimeResults = () => {
|
|
230
243
|
if (isAborted || isBackgrounded || isFinished)
|
|
231
244
|
return;
|
|
@@ -352,6 +365,7 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
352
365
|
}
|
|
353
366
|
}
|
|
354
367
|
}
|
|
368
|
+
cleanupTempFile();
|
|
355
369
|
const processedOutput = processToolResult(outputBuffer + (errorBuffer ? "\n" + errorBuffer : ""), BASH_MAX_OUTPUT_CHARS, "bash");
|
|
356
370
|
resolve({
|
|
357
371
|
success: false,
|
|
@@ -410,15 +424,7 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
410
424
|
newCwd = undefined;
|
|
411
425
|
}
|
|
412
426
|
finally {
|
|
413
|
-
|
|
414
|
-
try {
|
|
415
|
-
if (fs.existsSync(tempCwdFile)) {
|
|
416
|
-
fs.unlinkSync(tempCwdFile);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
catch (fileError) {
|
|
420
|
-
logger.error("Failed to clean up temp CWD file:", fileError);
|
|
421
|
-
}
|
|
427
|
+
cleanupTempFile();
|
|
422
428
|
}
|
|
423
429
|
// If CWD changed, call the onCwdChange callback and add notification
|
|
424
430
|
let cwdMessage;
|
|
@@ -462,6 +468,7 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
462
468
|
if (timeoutHandle) {
|
|
463
469
|
clearTimeout(timeoutHandle);
|
|
464
470
|
}
|
|
471
|
+
cleanupTempFile();
|
|
465
472
|
resolve({
|
|
466
473
|
success: false,
|
|
467
474
|
content: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editTool.d.ts","sourceRoot":"","sources":["../../src/tools/editTool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAgBtE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"editTool.d.ts","sourceRoot":"","sources":["../../src/tools/editTool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAgBtE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,UAgRtB,CAAC"}
|
package/dist/tools/editTool.js
CHANGED
|
@@ -95,9 +95,11 @@ Usage:
|
|
|
95
95
|
}
|
|
96
96
|
// Trigger conditional rule loading for this file
|
|
97
97
|
context.messageManager?.triggerFileRead(filePath);
|
|
98
|
-
// Enforce read-before-edit: the file must have been read first
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
// Enforce read-before-edit: the file must have been read or written first.
|
|
99
|
+
// readFileState is populated by Read, Write, and Edit tools — single source
|
|
100
|
+
// of truth, aligned with Claude Code's readFileState approach.
|
|
101
|
+
const resolvedPath = resolvePath(filePath, context.workdir);
|
|
102
|
+
if (!context.readFileState?.has(resolvedPath)) {
|
|
101
103
|
return {
|
|
102
104
|
success: false,
|
|
103
105
|
content: "",
|
|
@@ -105,7 +107,6 @@ Usage:
|
|
|
105
107
|
};
|
|
106
108
|
}
|
|
107
109
|
try {
|
|
108
|
-
const resolvedPath = resolvePath(filePath, context.workdir);
|
|
109
110
|
// Read file content
|
|
110
111
|
let originalContent;
|
|
111
112
|
try {
|
|
@@ -22,5 +22,5 @@ export declare const USER_MEMORY_FILE: string;
|
|
|
22
22
|
* AI related constants
|
|
23
23
|
*/
|
|
24
24
|
export declare const DEFAULT_WAVE_MAX_INPUT_TOKENS = 200000;
|
|
25
|
-
export declare const DEFAULT_WAVE_MAX_OUTPUT_TOKENS =
|
|
25
|
+
export declare const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 32000;
|
|
26
26
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/utils/constants.js
CHANGED
|
@@ -24,4 +24,4 @@ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "AGENTS.md");
|
|
|
24
24
|
* AI related constants
|
|
25
25
|
*/
|
|
26
26
|
export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 200000; // Default token limit
|
|
27
|
-
export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS =
|
|
27
|
+
export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 32000; // Default output token limit (aligned with Claude Code)
|
package/dist/utils/gitUtils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import * as fsSync from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
4
|
/**
|
|
5
5
|
* Check if a directory is a git repository
|
|
6
6
|
* @param dirPath Directory path
|
|
@@ -30,7 +30,7 @@ export function isGitRepository(dirPath) {
|
|
|
30
30
|
*/
|
|
31
31
|
export function getGitRepoRoot(cwd) {
|
|
32
32
|
try {
|
|
33
|
-
return
|
|
33
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
34
34
|
cwd,
|
|
35
35
|
encoding: "utf8",
|
|
36
36
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -47,7 +47,7 @@ export function getGitRepoRoot(cwd) {
|
|
|
47
47
|
*/
|
|
48
48
|
export function getGitCommonDir(cwd) {
|
|
49
49
|
try {
|
|
50
|
-
const commonDir =
|
|
50
|
+
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
51
51
|
cwd,
|
|
52
52
|
encoding: "utf8",
|
|
53
53
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -65,7 +65,7 @@ export function getGitCommonDir(cwd) {
|
|
|
65
65
|
*/
|
|
66
66
|
export function getGitMainRepoRoot(cwd) {
|
|
67
67
|
try {
|
|
68
|
-
const output =
|
|
68
|
+
const output = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
69
69
|
cwd,
|
|
70
70
|
encoding: "utf8",
|
|
71
71
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -223,7 +223,7 @@ export function getDefaultRemoteBranch(cwd) {
|
|
|
223
223
|
*/
|
|
224
224
|
export function hasUncommittedChanges(cwd) {
|
|
225
225
|
try {
|
|
226
|
-
const status =
|
|
226
|
+
const status = execFileSync("git", ["status", "--porcelain"], {
|
|
227
227
|
cwd,
|
|
228
228
|
encoding: "utf8",
|
|
229
229
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -243,7 +243,7 @@ export function hasUncommittedChanges(cwd) {
|
|
|
243
243
|
export function hasNewCommits(cwd, baseBranch) {
|
|
244
244
|
try {
|
|
245
245
|
const range = baseBranch ? `${baseBranch}..HEAD` : "@{u}..HEAD";
|
|
246
|
-
const log =
|
|
246
|
+
const log = execFileSync("git", ["log", range, "--oneline"], {
|
|
247
247
|
cwd,
|
|
248
248
|
encoding: "utf8",
|
|
249
249
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openaiClient.d.ts","sourceRoot":"","sources":["../../src/utils/openaiClient.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sCAAsC,EACtC,mCAAmC,EACnC,mBAAmB,EACnB,cAAc,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAyBnD,KAAK,YAAY,GACb,sCAAsC,GACtC,mCAAmC,CAAC;AAExC,UAAU,WAAW,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,UAAU,UAAU,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC;IACxC,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CACzC;AAED,qBAAa,YAAY;IACX,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEzC,IAAI,IAAI;;qBAGO,CAAC,SAAS,YAAY,UACrB,CAAC,YACC;gBAAE,MAAM,CAAC,EAAE,WAAW,CAAA;aAAE,KACjC,UAAU,CACX,CAAC,SAAS,mCAAmC,GACzC,aAAa,CAAC,mBAAmB,CAAC,GAClC,cAAc,CACnB;;MA2BN;YAEa,OAAO;
|
|
1
|
+
{"version":3,"file":"openaiClient.d.ts","sourceRoot":"","sources":["../../src/utils/openaiClient.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sCAAsC,EACtC,mCAAmC,EACnC,mBAAmB,EACnB,cAAc,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAyBnD,KAAK,YAAY,GACb,sCAAsC,GACtC,mCAAmC,CAAC;AAExC,UAAU,WAAW,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,UAAU,UAAU,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC;IACxC,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CACzC;AAED,qBAAa,YAAY;IACX,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEzC,IAAI,IAAI;;qBAGO,CAAC,SAAS,YAAY,UACrB,CAAC,YACC;gBAAE,MAAM,CAAC,EAAE,WAAW,CAAA;aAAE,KACjC,UAAU,CACX,CAAC,SAAS,mCAAmC,GACzC,aAAa,CAAC,mBAAmB,CAAC,GAClC,cAAc,CACnB;;MA2BN;YAEa,OAAO;YAuHN,oBAAoB;CAyCpC"}
|
|
@@ -91,36 +91,16 @@ export class OpenAIClient {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
let
|
|
94
|
+
let responseText = "";
|
|
95
95
|
try {
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
errorBody = JSON.parse(text);
|
|
99
|
-
}
|
|
100
|
-
catch (e) {
|
|
101
|
-
logger.error("Failed to parse error response body as JSON", {
|
|
102
|
-
error: e,
|
|
103
|
-
text,
|
|
104
|
-
});
|
|
105
|
-
errorBody = text;
|
|
106
|
-
}
|
|
96
|
+
responseText = await response.text();
|
|
107
97
|
}
|
|
108
98
|
catch (e) {
|
|
109
99
|
logger.error("Failed to read error response text", { error: e });
|
|
110
|
-
errorBody = {};
|
|
111
100
|
}
|
|
112
|
-
const error = new Error(
|
|
113
|
-
errorBody !== null &&
|
|
114
|
-
"error" in errorBody &&
|
|
115
|
-
typeof errorBody.error === "object" &&
|
|
116
|
-
errorBody.error !== null &&
|
|
117
|
-
"message" in errorBody.error
|
|
118
|
-
? String(errorBody.error.message)
|
|
119
|
-
: typeof errorBody === "string"
|
|
120
|
-
? errorBody
|
|
121
|
-
: response.statusText);
|
|
101
|
+
const error = new Error(`HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}: ${responseText}`);
|
|
122
102
|
error.status = response.status;
|
|
123
|
-
error.body =
|
|
103
|
+
error.body = responseText;
|
|
124
104
|
const retryableStatus = response.status === 429 ||
|
|
125
105
|
(response.status >= 500 && response.status !== 501);
|
|
126
106
|
if (retryableStatus && attempt < MAX_RETRIES) {
|
|
@@ -136,7 +116,7 @@ export class OpenAIClient {
|
|
|
136
116
|
logger.error("OpenAI API Error:", {
|
|
137
117
|
status: response.status,
|
|
138
118
|
statusText: response.statusText,
|
|
139
|
-
|
|
119
|
+
body: responseText,
|
|
140
120
|
});
|
|
141
121
|
throw error;
|
|
142
122
|
}
|
package/dist/utils/path.d.ts
CHANGED
|
@@ -12,4 +12,11 @@ export declare function resolvePath(filePath: string, workdir: string): string;
|
|
|
12
12
|
* @returns Path for display (relative or absolute)
|
|
13
13
|
*/
|
|
14
14
|
export declare function getDisplayPath(filePath: string, workdir: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Convert backslashes to forward slashes for shell compatibility on Windows.
|
|
17
|
+
* On Windows, os.tmpdir() returns paths with backslashes (e.g., C:\Users\foo\Temp),
|
|
18
|
+
* which are treated as escape characters when interpolated into shell command strings.
|
|
19
|
+
* Returns the path as-is on non-Windows platforms.
|
|
20
|
+
*/
|
|
21
|
+
export declare function toPosixPath(p: string): string;
|
|
15
22
|
//# sourceMappingURL=path.d.ts.map
|
package/dist/utils/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAarE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAwBxE"}
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAarE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAwBxE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C"}
|
package/dist/utils/path.js
CHANGED
|
@@ -46,3 +46,12 @@ export function getDisplayPath(filePath, workdir) {
|
|
|
46
46
|
}
|
|
47
47
|
return filePath;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Convert backslashes to forward slashes for shell compatibility on Windows.
|
|
51
|
+
* On Windows, os.tmpdir() returns paths with backslashes (e.g., C:\Users\foo\Temp),
|
|
52
|
+
* which are treated as escape characters when interpolated into shell command strings.
|
|
53
|
+
* Returns the path as-is on non-Windows platforms.
|
|
54
|
+
*/
|
|
55
|
+
export function toPosixPath(p) {
|
|
56
|
+
return process.platform === "win32" ? p.replace(/\\/g, "/") : p;
|
|
57
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktreeUtils.d.ts","sourceRoot":"","sources":["../../src/utils/worktreeUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBvD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CA6B7C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"worktreeUtils.d.ts","sourceRoot":"","sources":["../../src/utils/worktreeUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBvD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CA6B7C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CA6JtE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAmEvD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACrC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsClD"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Git worktree creation and removal utilities for the SDK.
|
|
3
3
|
* Used by EnterWorktree and ExitWorktree tools.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import { getGitMainRepoRoot, getDefaultRemoteBranch } from "./gitUtils.js";
|
|
@@ -61,7 +61,7 @@ export function generateWorktreeName() {
|
|
|
61
61
|
* Get the current HEAD commit SHA.
|
|
62
62
|
*/
|
|
63
63
|
export function getHeadCommit(cwd) {
|
|
64
|
-
return
|
|
64
|
+
return execFileSync("git", ["-C", cwd, "rev-parse", "HEAD"], {
|
|
65
65
|
encoding: "utf8",
|
|
66
66
|
stdio: ["ignore", "pipe", "ignore"],
|
|
67
67
|
}).trim();
|
|
@@ -97,7 +97,7 @@ export function createWorktree(name, cwd) {
|
|
|
97
97
|
}
|
|
98
98
|
try {
|
|
99
99
|
// Create worktree and branch
|
|
100
|
-
|
|
100
|
+
execFileSync("git", ["worktree", "add", "-b", branchName, worktreePath, baseBranch], {
|
|
101
101
|
cwd: repoRoot,
|
|
102
102
|
stdio: ["ignore", "pipe", "pipe"],
|
|
103
103
|
env: {
|
|
@@ -120,7 +120,7 @@ export function createWorktree(name, cwd) {
|
|
|
120
120
|
if (stderr.includes("already exists")) {
|
|
121
121
|
// Branch exists but worktree doesn't — attach to existing branch
|
|
122
122
|
try {
|
|
123
|
-
|
|
123
|
+
execFileSync("git", ["worktree", "add", worktreePath, branchName], {
|
|
124
124
|
cwd: repoRoot,
|
|
125
125
|
stdio: ["ignore", "pipe", "pipe"],
|
|
126
126
|
env: {
|
|
@@ -147,7 +147,7 @@ export function createWorktree(name, cwd) {
|
|
|
147
147
|
// Base branch not fetched yet — try fetching then retrying
|
|
148
148
|
const branchNameOnly = baseBranch.split("/").pop();
|
|
149
149
|
try {
|
|
150
|
-
|
|
150
|
+
execFileSync("git", ["fetch", "origin", branchNameOnly], {
|
|
151
151
|
cwd: repoRoot,
|
|
152
152
|
stdio: ["ignore", "pipe", "pipe"],
|
|
153
153
|
env: {
|
|
@@ -156,7 +156,7 @@ export function createWorktree(name, cwd) {
|
|
|
156
156
|
GIT_ASKPASS: "",
|
|
157
157
|
},
|
|
158
158
|
});
|
|
159
|
-
|
|
159
|
+
execFileSync("git", ["worktree", "add", "-b", branchName, worktreePath, baseBranch], {
|
|
160
160
|
cwd: repoRoot,
|
|
161
161
|
stdio: ["ignore", "pipe", "pipe"],
|
|
162
162
|
env: {
|
|
@@ -177,7 +177,7 @@ export function createWorktree(name, cwd) {
|
|
|
177
177
|
catch {
|
|
178
178
|
// Fetch or retry failed — fall back to HEAD
|
|
179
179
|
try {
|
|
180
|
-
|
|
180
|
+
execFileSync("git", ["worktree", "add", "-b", branchName, worktreePath, "HEAD"], {
|
|
181
181
|
cwd: repoRoot,
|
|
182
182
|
stdio: ["ignore", "pipe", "pipe"],
|
|
183
183
|
env: {
|
|
@@ -212,7 +212,7 @@ export function removeWorktree(info) {
|
|
|
212
212
|
// Get current branch in worktree before removing
|
|
213
213
|
let currentBranch;
|
|
214
214
|
try {
|
|
215
|
-
currentBranch =
|
|
215
|
+
currentBranch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
216
216
|
cwd: info.path,
|
|
217
217
|
encoding: "utf8",
|
|
218
218
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -222,13 +222,13 @@ export function removeWorktree(info) {
|
|
|
222
222
|
// Ignore errors
|
|
223
223
|
}
|
|
224
224
|
// Remove worktree
|
|
225
|
-
|
|
225
|
+
execFileSync("git", ["worktree", "remove", "--force", info.path], {
|
|
226
226
|
cwd: repoRoot,
|
|
227
227
|
stdio: ["ignore", "pipe", "pipe"],
|
|
228
228
|
});
|
|
229
229
|
// Delete worktree branch
|
|
230
230
|
try {
|
|
231
|
-
|
|
231
|
+
execFileSync("git", ["branch", "-D", info.branch], {
|
|
232
232
|
cwd: repoRoot,
|
|
233
233
|
stdio: ["ignore", "pipe", "pipe"],
|
|
234
234
|
});
|
|
@@ -246,7 +246,7 @@ export function removeWorktree(info) {
|
|
|
246
246
|
currentBranch !== "main" &&
|
|
247
247
|
currentBranch !== "master") {
|
|
248
248
|
try {
|
|
249
|
-
|
|
249
|
+
execFileSync("git", ["branch", "-D", currentBranch], {
|
|
250
250
|
cwd: repoRoot,
|
|
251
251
|
stdio: ["ignore", "pipe", "pipe"],
|
|
252
252
|
});
|
|
@@ -271,7 +271,7 @@ export function removeWorktree(info) {
|
|
|
271
271
|
*/
|
|
272
272
|
export function countWorktreeChanges(worktreePath, originalHeadCommit) {
|
|
273
273
|
try {
|
|
274
|
-
const statusOutput =
|
|
274
|
+
const statusOutput = execFileSync("git", ["-C", worktreePath, "status", "--porcelain"], {
|
|
275
275
|
encoding: "utf8",
|
|
276
276
|
stdio: ["ignore", "pipe", "ignore"],
|
|
277
277
|
});
|
|
@@ -281,7 +281,13 @@ export function countWorktreeChanges(worktreePath, originalHeadCommit) {
|
|
|
281
281
|
if (!originalHeadCommit) {
|
|
282
282
|
return null;
|
|
283
283
|
}
|
|
284
|
-
const revListOutput =
|
|
284
|
+
const revListOutput = execFileSync("git", [
|
|
285
|
+
"-C",
|
|
286
|
+
worktreePath,
|
|
287
|
+
"rev-list",
|
|
288
|
+
"--count",
|
|
289
|
+
`${originalHeadCommit}..HEAD`,
|
|
290
|
+
], {
|
|
285
291
|
encoding: "utf8",
|
|
286
292
|
stdio: ["ignore", "pipe", "ignore"],
|
|
287
293
|
});
|
package/package.json
CHANGED
|
@@ -484,29 +484,40 @@ export class McpManager {
|
|
|
484
484
|
stderr: "pipe", // Pipe stderr to capture it
|
|
485
485
|
});
|
|
486
486
|
|
|
487
|
-
//
|
|
487
|
+
// Buffer stderr silently (MCP servers use stderr for all logging;
|
|
488
|
+
// stdout is reserved for JSON-RPC). Only dump it on connection
|
|
489
|
+
// success (once) or failure — not line-by-line in real time.
|
|
490
|
+
// Aligns with Claude Code's approach to avoid noise from [INFO],
|
|
491
|
+
// [DEBUG], Warning: lines that are not actual errors.
|
|
488
492
|
const stderr = (transport as StdioClientTransport).stderr;
|
|
493
|
+
let stderrOutput = "";
|
|
489
494
|
if (stderr) {
|
|
490
|
-
let buffer = "";
|
|
491
495
|
stderr.on("data", (chunk: Buffer) => {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
buffer = lines.pop() || "";
|
|
495
|
-
for (const line of lines) {
|
|
496
|
-
if (line.trim()) {
|
|
497
|
-
logger?.error(`[MCP Server ${name}] ${line}`);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
stderr.on("end", () => {
|
|
502
|
-
if (buffer.trim()) {
|
|
503
|
-
logger?.error(`[MCP Server ${name}] ${buffer}`);
|
|
496
|
+
if (stderrOutput.length < 64 * 1024 * 1024) {
|
|
497
|
+
stderrOutput += chunk.toString();
|
|
504
498
|
}
|
|
505
499
|
});
|
|
506
500
|
}
|
|
507
501
|
|
|
508
502
|
client = createClient();
|
|
509
|
-
|
|
503
|
+
try {
|
|
504
|
+
await client.connect(transport);
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (stderrOutput.trim()) {
|
|
507
|
+
logger?.error(
|
|
508
|
+
`[MCP Server ${name}] Server stderr: ${stderrOutput.trim()}`,
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Dump accumulated stderr once after successful connection
|
|
515
|
+
if (stderrOutput.trim()) {
|
|
516
|
+
logger?.debug(
|
|
517
|
+
`[MCP Server ${name}] Server stderr: ${stderrOutput.trim()}`,
|
|
518
|
+
);
|
|
519
|
+
stderrOutput = "";
|
|
520
|
+
}
|
|
510
521
|
|
|
511
522
|
const toolsResponse = await client.listTools();
|
|
512
523
|
tools =
|
package/src/tools/bashTool.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from "path";
|
|
|
4
4
|
import * as os from "os";
|
|
5
5
|
import { logger } from "../utils/globalLogger.js";
|
|
6
6
|
import { resolveShellPath } from "../utils/shellResolver.js";
|
|
7
|
+
import { toPosixPath } from "../utils/path.js";
|
|
7
8
|
import { stripAnsiColors } from "../utils/stringUtils.js";
|
|
8
9
|
import { processToolResult } from "../utils/toolResultStorage.js";
|
|
9
10
|
import { BASH_MAX_OUTPUT_CHARS } from "../constants/toolLimits.js";
|
|
@@ -246,7 +247,8 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
246
247
|
os.tmpdir(),
|
|
247
248
|
`wave_cwd_${Date.now()}_${Math.random().toString(36).substring(2, 11)}.tmp`,
|
|
248
249
|
);
|
|
249
|
-
const
|
|
250
|
+
const tempCwdFileForBash = toPosixPath(tempCwdFile);
|
|
251
|
+
const wrappedCommand = `${command} && pwd -P >| ${tempCwdFileForBash}`;
|
|
250
252
|
|
|
251
253
|
const child: ChildProcess = spawn(wrappedCommand, {
|
|
252
254
|
shell: shellPath || true,
|
|
@@ -264,6 +266,17 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
264
266
|
let isBackgrounded = false;
|
|
265
267
|
let isFinished = false;
|
|
266
268
|
|
|
269
|
+
// Best-effort cleanup of the temp CWD file — used by abort/error/exit paths
|
|
270
|
+
const cleanupTempFile = () => {
|
|
271
|
+
try {
|
|
272
|
+
if (fs.existsSync(tempCwdFile)) {
|
|
273
|
+
fs.unlinkSync(tempCwdFile);
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
// ignore — best-effort cleanup
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
267
280
|
const updateRealtimeResults = () => {
|
|
268
281
|
if (isAborted || isBackgrounded || isFinished) return;
|
|
269
282
|
|
|
@@ -420,6 +433,8 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
420
433
|
}
|
|
421
434
|
}
|
|
422
435
|
|
|
436
|
+
cleanupTempFile();
|
|
437
|
+
|
|
423
438
|
const processedOutput = processToolResult(
|
|
424
439
|
outputBuffer + (errorBuffer ? "\n" + errorBuffer : ""),
|
|
425
440
|
BASH_MAX_OUTPUT_CHARS,
|
|
@@ -491,14 +506,7 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
491
506
|
);
|
|
492
507
|
newCwd = undefined;
|
|
493
508
|
} finally {
|
|
494
|
-
|
|
495
|
-
try {
|
|
496
|
-
if (fs.existsSync(tempCwdFile)) {
|
|
497
|
-
fs.unlinkSync(tempCwdFile);
|
|
498
|
-
}
|
|
499
|
-
} catch (fileError) {
|
|
500
|
-
logger.error("Failed to clean up temp CWD file:", fileError);
|
|
501
|
-
}
|
|
509
|
+
cleanupTempFile();
|
|
502
510
|
}
|
|
503
511
|
|
|
504
512
|
// If CWD changed, call the onCwdChange callback and add notification
|
|
@@ -560,6 +568,7 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
560
568
|
if (timeoutHandle) {
|
|
561
569
|
clearTimeout(timeoutHandle);
|
|
562
570
|
}
|
|
571
|
+
cleanupTempFile();
|
|
563
572
|
resolve({
|
|
564
573
|
success: false,
|
|
565
574
|
content: "",
|
package/src/tools/editTool.ts
CHANGED
|
@@ -112,11 +112,11 @@ Usage:
|
|
|
112
112
|
// Trigger conditional rule loading for this file
|
|
113
113
|
context.messageManager?.triggerFileRead(filePath);
|
|
114
114
|
|
|
115
|
-
// Enforce read-before-edit: the file must have been read first
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
) {
|
|
115
|
+
// Enforce read-before-edit: the file must have been read or written first.
|
|
116
|
+
// readFileState is populated by Read, Write, and Edit tools — single source
|
|
117
|
+
// of truth, aligned with Claude Code's readFileState approach.
|
|
118
|
+
const resolvedPath = resolvePath(filePath, context.workdir);
|
|
119
|
+
if (!context.readFileState?.has(resolvedPath)) {
|
|
120
120
|
return {
|
|
121
121
|
success: false,
|
|
122
122
|
content: "",
|
|
@@ -125,8 +125,6 @@ Usage:
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
try {
|
|
128
|
-
const resolvedPath = resolvePath(filePath, context.workdir);
|
|
129
|
-
|
|
130
128
|
// Read file content
|
|
131
129
|
let originalContent: string;
|
|
132
130
|
try {
|
package/src/utils/constants.ts
CHANGED
|
@@ -30,4 +30,4 @@ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "AGENTS.md");
|
|
|
30
30
|
* AI related constants
|
|
31
31
|
*/
|
|
32
32
|
export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 200000; // Default token limit
|
|
33
|
-
export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS =
|
|
33
|
+
export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 32000; // Default output token limit (aligned with Claude Code)
|
package/src/utils/gitUtils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import * as fsSync from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Check if a directory is a git repository
|
|
@@ -31,7 +31,7 @@ export function isGitRepository(dirPath: string): string {
|
|
|
31
31
|
*/
|
|
32
32
|
export function getGitRepoRoot(cwd: string): string {
|
|
33
33
|
try {
|
|
34
|
-
return
|
|
34
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
35
35
|
cwd,
|
|
36
36
|
encoding: "utf8",
|
|
37
37
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -48,7 +48,7 @@ export function getGitRepoRoot(cwd: string): string {
|
|
|
48
48
|
*/
|
|
49
49
|
export function getGitCommonDir(cwd: string): string {
|
|
50
50
|
try {
|
|
51
|
-
const commonDir =
|
|
51
|
+
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
52
52
|
cwd,
|
|
53
53
|
encoding: "utf8",
|
|
54
54
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -66,7 +66,7 @@ export function getGitCommonDir(cwd: string): string {
|
|
|
66
66
|
*/
|
|
67
67
|
export function getGitMainRepoRoot(cwd: string): string {
|
|
68
68
|
try {
|
|
69
|
-
const output =
|
|
69
|
+
const output = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
70
70
|
cwd,
|
|
71
71
|
encoding: "utf8",
|
|
72
72
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -229,7 +229,7 @@ export function getDefaultRemoteBranch(cwd: string): string {
|
|
|
229
229
|
*/
|
|
230
230
|
export function hasUncommittedChanges(cwd: string): boolean {
|
|
231
231
|
try {
|
|
232
|
-
const status =
|
|
232
|
+
const status = execFileSync("git", ["status", "--porcelain"], {
|
|
233
233
|
cwd,
|
|
234
234
|
encoding: "utf8",
|
|
235
235
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -249,7 +249,7 @@ export function hasUncommittedChanges(cwd: string): boolean {
|
|
|
249
249
|
export function hasNewCommits(cwd: string, baseBranch?: string): boolean {
|
|
250
250
|
try {
|
|
251
251
|
const range = baseBranch ? `${baseBranch}..HEAD` : "@{u}..HEAD";
|
|
252
|
-
const log =
|
|
252
|
+
const log = execFileSync("git", ["log", range, "--oneline"], {
|
|
253
253
|
cwd,
|
|
254
254
|
encoding: "utf8",
|
|
255
255
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -166,37 +166,18 @@ export class OpenAIClient {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
let
|
|
169
|
+
let responseText = "";
|
|
170
170
|
try {
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
errorBody = JSON.parse(text);
|
|
174
|
-
} catch (e) {
|
|
175
|
-
logger.error("Failed to parse error response body as JSON", {
|
|
176
|
-
error: e,
|
|
177
|
-
text,
|
|
178
|
-
});
|
|
179
|
-
errorBody = text;
|
|
180
|
-
}
|
|
171
|
+
responseText = await response.text();
|
|
181
172
|
} catch (e) {
|
|
182
173
|
logger.error("Failed to read error response text", { error: e });
|
|
183
|
-
errorBody = {};
|
|
184
174
|
}
|
|
185
175
|
|
|
186
176
|
const error = new Error(
|
|
187
|
-
|
|
188
|
-
errorBody !== null &&
|
|
189
|
-
"error" in errorBody &&
|
|
190
|
-
typeof (errorBody as { error: unknown }).error === "object" &&
|
|
191
|
-
(errorBody as { error: object }).error !== null &&
|
|
192
|
-
"message" in (errorBody as { error: { message: unknown } }).error
|
|
193
|
-
? String((errorBody as { error: { message: string } }).error.message)
|
|
194
|
-
: typeof errorBody === "string"
|
|
195
|
-
? errorBody
|
|
196
|
-
: response.statusText,
|
|
177
|
+
`HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}: ${responseText}`,
|
|
197
178
|
) as Error & { status?: number; body?: unknown };
|
|
198
179
|
error.status = response.status;
|
|
199
|
-
error.body =
|
|
180
|
+
error.body = responseText;
|
|
200
181
|
|
|
201
182
|
const retryableStatus =
|
|
202
183
|
response.status === 429 ||
|
|
@@ -215,7 +196,7 @@ export class OpenAIClient {
|
|
|
215
196
|
logger.error("OpenAI API Error:", {
|
|
216
197
|
status: response.status,
|
|
217
198
|
statusText: response.statusText,
|
|
218
|
-
|
|
199
|
+
body: responseText,
|
|
219
200
|
});
|
|
220
201
|
throw error;
|
|
221
202
|
}
|
package/src/utils/path.ts
CHANGED
|
@@ -54,3 +54,13 @@ export function getDisplayPath(filePath: string, workdir: string): string {
|
|
|
54
54
|
}
|
|
55
55
|
return filePath;
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Convert backslashes to forward slashes for shell compatibility on Windows.
|
|
60
|
+
* On Windows, os.tmpdir() returns paths with backslashes (e.g., C:\Users\foo\Temp),
|
|
61
|
+
* which are treated as escape characters when interpolated into shell command strings.
|
|
62
|
+
* Returns the path as-is on non-Windows platforms.
|
|
63
|
+
*/
|
|
64
|
+
export function toPosixPath(p: string): string {
|
|
65
|
+
return process.platform === "win32" ? p.replace(/\\/g, "/") : p;
|
|
66
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Used by EnterWorktree and ExitWorktree tools.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import { getGitMainRepoRoot, getDefaultRemoteBranch } from "./gitUtils.js";
|
|
@@ -81,7 +81,7 @@ export function generateWorktreeName(): string {
|
|
|
81
81
|
* Get the current HEAD commit SHA.
|
|
82
82
|
*/
|
|
83
83
|
export function getHeadCommit(cwd: string): string {
|
|
84
|
-
return
|
|
84
|
+
return execFileSync("git", ["-C", cwd, "rev-parse", "HEAD"], {
|
|
85
85
|
encoding: "utf8",
|
|
86
86
|
stdio: ["ignore", "pipe", "ignore"],
|
|
87
87
|
}).trim();
|
|
@@ -125,8 +125,9 @@ export function createWorktree(name: string, cwd: string): WorktreeInfo {
|
|
|
125
125
|
|
|
126
126
|
try {
|
|
127
127
|
// Create worktree and branch
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
execFileSync(
|
|
129
|
+
"git",
|
|
130
|
+
["worktree", "add", "-b", branchName, worktreePath, baseBranch],
|
|
130
131
|
{
|
|
131
132
|
cwd: repoRoot,
|
|
132
133
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -151,7 +152,7 @@ export function createWorktree(name: string, cwd: string): WorktreeInfo {
|
|
|
151
152
|
if (stderr.includes("already exists")) {
|
|
152
153
|
// Branch exists but worktree doesn't — attach to existing branch
|
|
153
154
|
try {
|
|
154
|
-
|
|
155
|
+
execFileSync("git", ["worktree", "add", worktreePath, branchName], {
|
|
155
156
|
cwd: repoRoot,
|
|
156
157
|
stdio: ["ignore", "pipe", "pipe"],
|
|
157
158
|
env: {
|
|
@@ -181,7 +182,7 @@ export function createWorktree(name: string, cwd: string): WorktreeInfo {
|
|
|
181
182
|
// Base branch not fetched yet — try fetching then retrying
|
|
182
183
|
const branchNameOnly = baseBranch.split("/").pop()!;
|
|
183
184
|
try {
|
|
184
|
-
|
|
185
|
+
execFileSync("git", ["fetch", "origin", branchNameOnly], {
|
|
185
186
|
cwd: repoRoot,
|
|
186
187
|
stdio: ["ignore", "pipe", "pipe"],
|
|
187
188
|
env: {
|
|
@@ -190,8 +191,9 @@ export function createWorktree(name: string, cwd: string): WorktreeInfo {
|
|
|
190
191
|
GIT_ASKPASS: "",
|
|
191
192
|
},
|
|
192
193
|
});
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
execFileSync(
|
|
195
|
+
"git",
|
|
196
|
+
["worktree", "add", "-b", branchName, worktreePath, baseBranch],
|
|
195
197
|
{
|
|
196
198
|
cwd: repoRoot,
|
|
197
199
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -213,15 +215,19 @@ export function createWorktree(name: string, cwd: string): WorktreeInfo {
|
|
|
213
215
|
} catch {
|
|
214
216
|
// Fetch or retry failed — fall back to HEAD
|
|
215
217
|
try {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
218
|
+
execFileSync(
|
|
219
|
+
"git",
|
|
220
|
+
["worktree", "add", "-b", branchName, worktreePath, "HEAD"],
|
|
221
|
+
{
|
|
222
|
+
cwd: repoRoot,
|
|
223
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
224
|
+
env: {
|
|
225
|
+
...process.env,
|
|
226
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
227
|
+
GIT_ASKPASS: "",
|
|
228
|
+
},
|
|
223
229
|
},
|
|
224
|
-
|
|
230
|
+
);
|
|
225
231
|
return {
|
|
226
232
|
name,
|
|
227
233
|
path: worktreePath,
|
|
@@ -253,24 +259,28 @@ export function removeWorktree(info: WorktreeInfo): void {
|
|
|
253
259
|
// Get current branch in worktree before removing
|
|
254
260
|
let currentBranch: string | undefined;
|
|
255
261
|
try {
|
|
256
|
-
currentBranch =
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
262
|
+
currentBranch = execFileSync(
|
|
263
|
+
"git",
|
|
264
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
265
|
+
{
|
|
266
|
+
cwd: info.path,
|
|
267
|
+
encoding: "utf8",
|
|
268
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
269
|
+
},
|
|
270
|
+
).trim();
|
|
261
271
|
} catch {
|
|
262
272
|
// Ignore errors
|
|
263
273
|
}
|
|
264
274
|
|
|
265
275
|
// Remove worktree
|
|
266
|
-
|
|
276
|
+
execFileSync("git", ["worktree", "remove", "--force", info.path], {
|
|
267
277
|
cwd: repoRoot,
|
|
268
278
|
stdio: ["ignore", "pipe", "pipe"],
|
|
269
279
|
});
|
|
270
280
|
|
|
271
281
|
// Delete worktree branch
|
|
272
282
|
try {
|
|
273
|
-
|
|
283
|
+
execFileSync("git", ["branch", "-D", info.branch], {
|
|
274
284
|
cwd: repoRoot,
|
|
275
285
|
stdio: ["ignore", "pipe", "pipe"],
|
|
276
286
|
});
|
|
@@ -293,7 +303,7 @@ export function removeWorktree(info: WorktreeInfo): void {
|
|
|
293
303
|
currentBranch !== "master"
|
|
294
304
|
) {
|
|
295
305
|
try {
|
|
296
|
-
|
|
306
|
+
execFileSync("git", ["branch", "-D", currentBranch], {
|
|
297
307
|
cwd: repoRoot,
|
|
298
308
|
stdio: ["ignore", "pipe", "pipe"],
|
|
299
309
|
});
|
|
@@ -320,8 +330,9 @@ export function countWorktreeChanges(
|
|
|
320
330
|
originalHeadCommit: string | undefined,
|
|
321
331
|
): { changedFiles: number; commits: number } | null {
|
|
322
332
|
try {
|
|
323
|
-
const statusOutput =
|
|
324
|
-
|
|
333
|
+
const statusOutput = execFileSync(
|
|
334
|
+
"git",
|
|
335
|
+
["-C", worktreePath, "status", "--porcelain"],
|
|
325
336
|
{
|
|
326
337
|
encoding: "utf8",
|
|
327
338
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -335,8 +346,15 @@ export function countWorktreeChanges(
|
|
|
335
346
|
return null;
|
|
336
347
|
}
|
|
337
348
|
|
|
338
|
-
const revListOutput =
|
|
339
|
-
|
|
349
|
+
const revListOutput = execFileSync(
|
|
350
|
+
"git",
|
|
351
|
+
[
|
|
352
|
+
"-C",
|
|
353
|
+
worktreePath,
|
|
354
|
+
"rev-list",
|
|
355
|
+
"--count",
|
|
356
|
+
`${originalHeadCommit}..HEAD`,
|
|
357
|
+
],
|
|
340
358
|
{
|
|
341
359
|
encoding: "utf8",
|
|
342
360
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Wave Memory Rules Configuration
|
|
2
|
-
|
|
3
|
-
Memory rules allow you to provide context-specific instructions and guidelines to the agent. This document explains how to create and manage memory rules in Wave.
|
|
4
|
-
|
|
5
|
-
## What are Memory Rules?
|
|
6
|
-
|
|
7
|
-
Memory rules are Markdown files that contain instructions for the agent. They are used to:
|
|
8
|
-
- Enforce coding styles and conventions.
|
|
9
|
-
- Provide project-specific context (e.g., "always use pnpm").
|
|
10
|
-
- Define architectural patterns and best practices.
|
|
11
|
-
- Share common knowledge across the team.
|
|
12
|
-
|
|
13
|
-
## Creating Memory Rules
|
|
14
|
-
|
|
15
|
-
Wave looks for memory rules in the following locations:
|
|
16
|
-
|
|
17
|
-
1. **User Scope**: `~/.wave/rules/*.md` (Global memory rules)
|
|
18
|
-
2. **Project Scope**: `.wave/rules/*.md` (Project-specific memory rules)
|
|
19
|
-
3. **Project Root**: `AGENTS.md` (Legacy project-level memory rules)
|
|
20
|
-
|
|
21
|
-
### File Structure
|
|
22
|
-
|
|
23
|
-
A memory rule file is a standard Markdown file. It can optionally include YAML frontmatter to scope the rules to specific file paths.
|
|
24
|
-
|
|
25
|
-
```markdown
|
|
26
|
-
---
|
|
27
|
-
paths:
|
|
28
|
-
- "src/api/**/*.ts"
|
|
29
|
-
- "src/services/**/*.ts"
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
# API and Service Guidelines
|
|
33
|
-
|
|
34
|
-
- Always use `async/await` for asynchronous operations.
|
|
35
|
-
- Use `Zod` for input validation.
|
|
36
|
-
- Follow the repository pattern for data access.
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### YAML Frontmatter Fields
|
|
40
|
-
|
|
41
|
-
- `paths`: (Optional) A list of glob patterns. The rules in this file will only be active when the agent is working with files that match these patterns. If omitted, the rules are always active.
|
|
42
|
-
|
|
43
|
-
## How Memory Rules are Loaded
|
|
44
|
-
|
|
45
|
-
Wave automatically discovers and loads all `.md` files in the `.wave/rules/` directory and its immediate subdirectories.
|
|
46
|
-
|
|
47
|
-
- **Path-Specific Activation**: If a memory rule has a `paths` field, it is only included in the agent's context if *any* file currently being read or modified matches the glob patterns.
|
|
48
|
-
- **Union of Rules**: If multiple files are in context, Wave activates the union of all matching memory rules.
|
|
49
|
-
- **Priority**: Project-level memory rules take priority over user-level memory rules if there is a conflict.
|
|
50
|
-
|
|
51
|
-
## Best Practices
|
|
52
|
-
|
|
53
|
-
- **Keep rules focused**: Create separate files for different topics (e.g., `testing.md`, `ui-components.md`).
|
|
54
|
-
- **Use clear instructions**: Write rules in a way that is easy for the agent to understand and follow.
|
|
55
|
-
- **Leverage path scoping**: Use the `paths` field to keep the agent's context window clean and relevant.
|
|
56
|
-
- **Share rules with your team**: Commit `.wave/rules/` to your git repository to ensure everyone on the team has the same context.
|
|
57
|
-
|
|
58
|
-
## Auto-Memory
|
|
59
|
-
|
|
60
|
-
In addition to manual memory rules, Wave also has an **auto-memory** feature that automatically remembers important information across sessions. This is stored in `~/.wave/projects/<project-id>/memory/MEMORY.md`. You can disable this feature in `settings.json` by setting `"autoMemoryEnabled": false`.
|