wave-agent-sdk 1.0.10 → 1.1.1

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.
Files changed (48) hide show
  1. package/dist/agent.d.ts +16 -7
  2. package/dist/agent.js +44 -34
  3. package/dist/builtin/skills/settings.js +48 -8
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +2 -0
  6. package/dist/managers/aiManager.d.ts +23 -1
  7. package/dist/managers/aiManager.js +80 -30
  8. package/dist/managers/backgroundTaskManager.js +12 -0
  9. package/dist/managers/pluginScopeManager.js +13 -5
  10. package/dist/managers/subagentManager.d.ts +1 -0
  11. package/dist/managers/subagentManager.js +5 -1
  12. package/dist/services/GitService.js +6 -6
  13. package/dist/services/aiService.js +6 -2
  14. package/dist/services/configurationService.d.ts +4 -4
  15. package/dist/services/configurationService.js +14 -7
  16. package/dist/services/hook.js +41 -7
  17. package/dist/services/initializationService.js +0 -21
  18. package/dist/services/jsonlHandler.d.ts +37 -2
  19. package/dist/services/jsonlHandler.js +55 -6
  20. package/dist/services/session.d.ts +35 -4
  21. package/dist/services/session.js +233 -36
  22. package/dist/services/worktreeHooks.d.ts +45 -0
  23. package/dist/services/worktreeHooks.js +133 -0
  24. package/dist/tools/agentTool.js +36 -1
  25. package/dist/tools/bashTool.js +129 -58
  26. package/dist/tools/enterWorktreeTool.d.ts +1 -1
  27. package/dist/tools/enterWorktreeTool.js +44 -31
  28. package/dist/tools/exitWorktreeTool.js +21 -26
  29. package/dist/types/agent.d.ts +5 -0
  30. package/dist/types/config.d.ts +2 -1
  31. package/dist/types/hooks.d.ts +3 -2
  32. package/dist/types/skills.d.ts +0 -1
  33. package/dist/types/skills.js +0 -1
  34. package/dist/utils/asyncWorkRegistry.d.ts +32 -0
  35. package/dist/utils/asyncWorkRegistry.js +81 -0
  36. package/dist/utils/containerSetup.js +5 -0
  37. package/dist/utils/markdownParser.js +23 -5
  38. package/dist/utils/path.d.ts +9 -0
  39. package/dist/utils/path.js +32 -0
  40. package/dist/utils/skillParser.js +3 -6
  41. package/dist/utils/tokenCalculation.d.ts +19 -1
  42. package/dist/utils/tokenCalculation.js +64 -0
  43. package/dist/utils/windowsPaths.d.ts +28 -0
  44. package/dist/utils/windowsPaths.js +47 -0
  45. package/dist/utils/worktreeSession.d.ts +6 -0
  46. package/dist/utils/worktreeUtils.d.ts +7 -0
  47. package/dist/utils/worktreeUtils.js +88 -40
  48. package/package.json +5 -3
@@ -5,6 +5,7 @@ import { join } from "path";
5
5
  import { tmpdir } from "os";
6
6
  import { SKILL_BASH_MAX_OUTPUT_CHARS, PREVIEW_SIZE_BYTES, } from "../constants/toolLimits.js";
7
7
  import { logger } from "./globalLogger.js";
8
+ import { resolveShellPath } from "./shellResolver.js";
8
9
  const execAsync = promisify(exec);
9
10
  /**
10
11
  * Parse YAML frontmatter from markdown content
@@ -198,12 +199,29 @@ export function replaceBashCommandsWithOutput(content, results) {
198
199
  */
199
200
  export async function executeBashCommands(commands, workdir, timeout = 30000) {
200
201
  const results = [];
201
- for (const command of commands) {
202
+ for (const originalCommand of commands) {
203
+ let command = originalCommand;
204
+ const execOptions = {
205
+ cwd: workdir,
206
+ timeout,
207
+ };
208
+ // Skill templates (e.g. !`gh pr view ... 2>/dev/null || echo '{}'`) use
209
+ // bash-only syntax. On Windows, child_process.exec defaults to cmd.exe,
210
+ // where `/dev/null` fails with a localized "path not found" error whose
211
+ // GBK bytes get mis-decoded as UTF-8 (mojibake). Run through Git Bash
212
+ // when available (same contract as the bash tool); otherwise force the
213
+ // UTF-8 codepage so cmd's own messages decode correctly.
214
+ if (process.platform === "win32") {
215
+ const bashPath = resolveShellPath();
216
+ if (bashPath) {
217
+ execOptions.shell = bashPath;
218
+ }
219
+ else {
220
+ command = `chcp 65001 >NUL && ${command}`;
221
+ }
222
+ }
202
223
  try {
203
- const { stdout, stderr } = await execAsync(command, {
204
- cwd: workdir,
205
- timeout,
206
- });
224
+ const { stdout, stderr } = await execAsync(command, execOptions);
207
225
  results.push({
208
226
  command,
209
227
  output: (stdout + (stderr || "")).trim(),
@@ -25,3 +25,12 @@ export declare function getDisplayPath(filePath: string, workdir: string): strin
25
25
  * Returns the path as-is on non-Windows platforms.
26
26
  */
27
27
  export declare function toPosixPath(p: string): string;
28
+ /**
29
+ * Convert a POSIX-style path to a native Windows path. Git Bash / MSYS shells
30
+ * print POSIX paths (e.g. `pwd -P` outputs `/c/Users/foo`), which Node.js on
31
+ * Windows would otherwise resolve against the current drive root (e.g. `/c/...`
32
+ * becomes `C:\c\...`) and fail with ENOENT. Handles `/c/...` (MSYS2/Git Bash),
33
+ * `/cygdrive/c/...` and `//server/share` (UNC) forms. Returns the path as-is on
34
+ * non-Windows platforms.
35
+ */
36
+ export declare function toWindowsPath(p: string): string;
@@ -61,3 +61,35 @@ export function getDisplayPath(filePath, workdir) {
61
61
  export function toPosixPath(p) {
62
62
  return process.platform === "win32" ? p.replace(/\\/g, "/") : p;
63
63
  }
64
+ /**
65
+ * Convert a POSIX-style path to a native Windows path. Git Bash / MSYS shells
66
+ * print POSIX paths (e.g. `pwd -P` outputs `/c/Users/foo`), which Node.js on
67
+ * Windows would otherwise resolve against the current drive root (e.g. `/c/...`
68
+ * becomes `C:\c\...`) and fail with ENOENT. Handles `/c/...` (MSYS2/Git Bash),
69
+ * `/cygdrive/c/...` and `//server/share` (UNC) forms. Returns the path as-is on
70
+ * non-Windows platforms.
71
+ */
72
+ export function toWindowsPath(p) {
73
+ if (process.platform !== "win32")
74
+ return p;
75
+ // UNC paths: //server/share -> \\server\share
76
+ if (p.startsWith("//")) {
77
+ return p.replace(/\//g, "\\");
78
+ }
79
+ // /cygdrive/c/... -> C:\...
80
+ const cygdriveMatch = p.match(/^\/cygdrive\/([A-Za-z])(\/|$)/);
81
+ if (cygdriveMatch) {
82
+ const driveLetter = cygdriveMatch[1].toUpperCase();
83
+ const rest = p.slice(("/cygdrive/" + cygdriveMatch[1]).length);
84
+ return driveLetter + ":" + (rest || "\\").replace(/\//g, "\\");
85
+ }
86
+ // /c/... (MSYS2/Git Bash) -> C:\...
87
+ const driveMatch = p.match(/^\/([A-Za-z])(\/|$)/);
88
+ if (driveMatch) {
89
+ const driveLetter = driveMatch[1].toUpperCase();
90
+ const rest = p.slice(2);
91
+ return driveLetter + ":" + (rest || "\\").replace(/\//g, "\\");
92
+ }
93
+ // Already Windows or relative — just flip slashes
94
+ return p.replace(/\//g, "\\");
95
+ }
@@ -147,7 +147,6 @@ export function validateSkillMetadata(metadata) {
147
147
  // Import SKILL_DEFAULTS dynamically to avoid circular imports
148
148
  const NAME_PATTERN = /^[a-z0-9-]+$/;
149
149
  const MAX_NAME_LENGTH = 64;
150
- const MAX_DESCRIPTION_LENGTH = 1024;
151
150
  const MIN_DESCRIPTION_LENGTH = 1;
152
151
  // Validate name
153
152
  if (!metadata.name) {
@@ -161,7 +160,8 @@ export function validateSkillMetadata(metadata) {
161
160
  errors.push("Skill name must contain only lowercase letters, numbers, and hyphens");
162
161
  }
163
162
  }
164
- // Validate description
163
+ // Validate description (no length limit, aligned with Claude Code which
164
+ // truncates long descriptions at render time instead of rejecting the skill)
165
165
  if (!metadata.description) {
166
166
  errors.push("Skill description is required");
167
167
  }
@@ -169,9 +169,6 @@ export function validateSkillMetadata(metadata) {
169
169
  if (metadata.description.length < MIN_DESCRIPTION_LENGTH) {
170
170
  errors.push(`Skill description must be at least ${MIN_DESCRIPTION_LENGTH} character`);
171
171
  }
172
- if (metadata.description.length > MAX_DESCRIPTION_LENGTH) {
173
- errors.push(`Skill description must be ${MAX_DESCRIPTION_LENGTH} characters or less`);
174
- }
175
172
  }
176
173
  return errors;
177
174
  }
@@ -194,7 +191,7 @@ export function formatSkillError(skillPath, errors) {
194
191
  " 1. Ensure SKILL.md has valid YAML frontmatter (---...---)",
195
192
  " 2. Include required fields: name and description",
196
193
  " 3. Use lowercase letters, numbers, and hyphens only for name",
197
- " 4. Keep name under 64 characters and description under 1024 characters",
194
+ " 4. Keep name under 64 characters",
198
195
  ].join("\n");
199
196
  return `${header}\n${errorList}\n\n${suggestions}`;
200
197
  }
@@ -1,4 +1,4 @@
1
- import type { Usage } from "../types/index.js";
1
+ import type { Message, Usage } from "../types/index.js";
2
2
  /**
3
3
  * Calculate comprehensive total tokens including cache-related tokens
4
4
  *
@@ -23,3 +23,21 @@ export declare function calculateComprehensiveTotalTokens(usage: Usage): number;
23
23
  export declare function extractLatestTotalTokens(messages: Array<{
24
24
  usage?: Usage;
25
25
  }>): number;
26
+ /**
27
+ * Estimate the full context size of a message list for pre-request
28
+ * auto-compaction checks (aligned with Claude Code's tokenCountWithEstimation).
29
+ *
30
+ * Anchors on the most recent message carrying real usage (the last API
31
+ * response): that response's total_tokens (OpenAI-compatible semantics —
32
+ * already includes cache hits) plus a rough character-based estimate for
33
+ * every message added after it. Falls back to a pure character estimate
34
+ * when no usage anchor exists yet (e.g. the first request of a session).
35
+ */
36
+ export declare function estimateContextTokens(messages: Message[]): number;
37
+ /**
38
+ * Rough per-message token estimate over all message blocks.
39
+ * Used to estimate the messages added since the last usage-bearing response.
40
+ * Image blocks are ignored (images are stripped before compaction) and file
41
+ * history snapshots live on disk, so their content is not in the context.
42
+ */
43
+ export declare function roughTokenCountForMessages(messages: Message[]): number;
@@ -1,3 +1,4 @@
1
+ import { estimateTokens } from "./tokenEstimate.js";
1
2
  /**
2
3
  * Calculate comprehensive total tokens including cache-related tokens
3
4
  *
@@ -34,3 +35,66 @@ export function extractLatestTotalTokens(messages) {
34
35
  }
35
36
  return 0; // No usage data found
36
37
  }
38
+ /**
39
+ * Estimate the full context size of a message list for pre-request
40
+ * auto-compaction checks (aligned with Claude Code's tokenCountWithEstimation).
41
+ *
42
+ * Anchors on the most recent message carrying real usage (the last API
43
+ * response): that response's total_tokens (OpenAI-compatible semantics —
44
+ * already includes cache hits) plus a rough character-based estimate for
45
+ * every message added after it. Falls back to a pure character estimate
46
+ * when no usage anchor exists yet (e.g. the first request of a session).
47
+ */
48
+ export function estimateContextTokens(messages) {
49
+ for (let i = messages.length - 1; i >= 0; i--) {
50
+ const usage = messages[i].usage;
51
+ if (usage) {
52
+ return (usage.total_tokens + roughTokenCountForMessages(messages.slice(i + 1)));
53
+ }
54
+ }
55
+ return roughTokenCountForMessages(messages);
56
+ }
57
+ /**
58
+ * Rough per-message token estimate over all message blocks.
59
+ * Used to estimate the messages added since the last usage-bearing response.
60
+ * Image blocks are ignored (images are stripped before compaction) and file
61
+ * history snapshots live on disk, so their content is not in the context.
62
+ */
63
+ export function roughTokenCountForMessages(messages) {
64
+ let total = 0;
65
+ for (const message of messages) {
66
+ for (const block of message.blocks) {
67
+ switch (block.type) {
68
+ case "text":
69
+ case "reasoning":
70
+ case "error":
71
+ case "compact":
72
+ total += estimateTokens(block.content);
73
+ break;
74
+ case "tool":
75
+ if (block.parameters) {
76
+ total += estimateTokens(block.parameters, "json");
77
+ }
78
+ if (block.result) {
79
+ total += estimateTokens(block.result);
80
+ }
81
+ break;
82
+ case "bang":
83
+ if (block.command) {
84
+ total += estimateTokens(block.command);
85
+ }
86
+ if (block.output) {
87
+ total += estimateTokens(block.output);
88
+ }
89
+ break;
90
+ case "task_notification":
91
+ total += estimateTokens(block.summary);
92
+ break;
93
+ case "image":
94
+ case "file_history":
95
+ break;
96
+ }
97
+ }
98
+ }
99
+ return total;
100
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Windows path conversion utilities for Git Bash (POSIX shell) execution.
3
+ *
4
+ * Hook commands on Windows are executed via Git Bash instead of cmd.exe
5
+ * (cmd.exe does not strip quotes from arguments after the first token, so
6
+ * `node "C:\path\script.js"` silently fails — see issue #1773). Git Bash
7
+ * cannot resolve Windows paths like `C:\Users\foo`: backslashes are treated
8
+ * as escape characters and the drive letter breaks POSIX semantics. These
9
+ * helpers convert Windows paths to the POSIX form Git Bash expects
10
+ * (`C:\Users\foo` → `/c/Users/foo`), matching Claude Code's
11
+ * `windowsPathToPosixPath`.
12
+ */
13
+ /**
14
+ * Convert a single Windows path to POSIX form for Git Bash:
15
+ * - `C:\Users\foo` → `/c/Users/foo`
16
+ * - `C:/Users/foo` → `/c/Users/foo`
17
+ * - `\\server\share` → `//server/share` (UNC preserved)
18
+ * - Already POSIX or relative paths are returned with slashes flipped.
19
+ */
20
+ export declare function windowsPathToPosixPath(p: string): string;
21
+ /**
22
+ * Convert every Windows absolute path inside a shell command string to POSIX
23
+ * form so Git Bash can parse it. Handles both quoted paths (which may contain
24
+ * spaces, e.g. `node "C:\Program Files\script.js"`) and bare paths
25
+ * (`cd C:\path\x`). Drive letters preceded by another alphanumeric character
26
+ * (e.g. URLs like `http://`) are left untouched.
27
+ */
28
+ export declare function toPosixCommand(command: string): string;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Windows path conversion utilities for Git Bash (POSIX shell) execution.
3
+ *
4
+ * Hook commands on Windows are executed via Git Bash instead of cmd.exe
5
+ * (cmd.exe does not strip quotes from arguments after the first token, so
6
+ * `node "C:\path\script.js"` silently fails — see issue #1773). Git Bash
7
+ * cannot resolve Windows paths like `C:\Users\foo`: backslashes are treated
8
+ * as escape characters and the drive letter breaks POSIX semantics. These
9
+ * helpers convert Windows paths to the POSIX form Git Bash expects
10
+ * (`C:\Users\foo` → `/c/Users/foo`), matching Claude Code's
11
+ * `windowsPathToPosixPath`.
12
+ */
13
+ /**
14
+ * Convert a single Windows path to POSIX form for Git Bash:
15
+ * - `C:\Users\foo` → `/c/Users/foo`
16
+ * - `C:/Users/foo` → `/c/Users/foo`
17
+ * - `\\server\share` → `//server/share` (UNC preserved)
18
+ * - Already POSIX or relative paths are returned with slashes flipped.
19
+ */
20
+ export function windowsPathToPosixPath(p) {
21
+ // UNC paths: \\server\share -> //server/share
22
+ if (p.startsWith("\\\\")) {
23
+ return p.replace(/\\/g, "/");
24
+ }
25
+ // Drive letter paths: C:\Users\foo -> /c/Users/foo
26
+ const match = p.match(/^([A-Za-z]):[\\/]/);
27
+ if (match) {
28
+ const driveLetter = match[1].toLowerCase();
29
+ return `/${driveLetter}${p.slice(2).replace(/\\/g, "/")}`;
30
+ }
31
+ // Already POSIX or relative — just flip slashes
32
+ return p.replace(/\\/g, "/");
33
+ }
34
+ /**
35
+ * Convert every Windows absolute path inside a shell command string to POSIX
36
+ * form so Git Bash can parse it. Handles both quoted paths (which may contain
37
+ * spaces, e.g. `node "C:\Program Files\script.js"`) and bare paths
38
+ * (`cd C:\path\x`). Drive letters preceded by another alphanumeric character
39
+ * (e.g. URLs like `http://`) are left untouched.
40
+ */
41
+ export function toPosixCommand(command) {
42
+ // Quoted paths first (may contain spaces) — keep the surrounding quotes.
43
+ let converted = command.replace(/"([A-Za-z]):[\\/][^"]*"/g, (quoted) => `"${windowsPathToPosixPath(quoted.slice(1, -1))}"`);
44
+ // Bare (unquoted) paths.
45
+ converted = converted.replace(/(?<![A-Za-z0-9])([A-Za-z]):[\\/][^\s"']*/g, (match) => windowsPathToPosixPath(match));
46
+ return converted;
47
+ }
@@ -22,4 +22,10 @@ export interface WorktreeSession {
22
22
  repoRoot: string;
23
23
  /** The HEAD commit of the original branch at worktree creation time */
24
24
  originalHeadCommit?: string;
25
+ /**
26
+ * True when this worktree was created by a WorktreeCreate hook (not by git).
27
+ * Deletion delegates to the WorktreeRemove hook; wave never runs
28
+ * `git worktree remove` for hook-based worktrees (aligned with Claude Code).
29
+ */
30
+ hookBased?: boolean;
25
31
  }
@@ -41,6 +41,13 @@ export declare function createWorktree(name: string, cwd: string, options?: {
41
41
  export declare function performPostCreationSetup(worktreePath: string, repoRoot: string): Promise<void>;
42
42
  /**
43
43
  * Remove a git worktree and its branch.
44
+ *
45
+ * Removal is best-effort: `git worktree remove --force` deletes the worktree
46
+ * metadata before the working directory, and on Windows its recursive deletion
47
+ * is MAX_PATH-limited — deep paths (e.g. node_modules) can fail with "Filename
48
+ * too long", leaving an orphan directory. When git fails we fall back to
49
+ * fs.rmSync with an extended-length path (bypasses MAX_PATH) and prune stale
50
+ * metadata. Failures are logged but never block branch deletion.
44
51
  */
45
52
  export declare function removeWorktree(info: WorktreeInfo): void;
46
53
  /**
@@ -498,32 +498,77 @@ export async function performPostCreationSetup(worktreePath, repoRoot) {
498
498
  await copyLocalSettingsToWorktree(repoRoot, worktreePath);
499
499
  await copyWorktreeIncludeFiles(repoRoot, worktreePath);
500
500
  }
501
+ /**
502
+ * On Windows, prefix an absolute path with the extended-length marker (`\\?\`)
503
+ * so recursive removal bypasses the 260-char MAX_PATH limit. POSIX paths are
504
+ * returned unchanged.
505
+ */
506
+ function toExtendedLengthPath(worktreePath) {
507
+ if (process.platform !== "win32") {
508
+ return worktreePath;
509
+ }
510
+ const absolute = path.win32.resolve(worktreePath);
511
+ if (absolute.startsWith("\\\\?\\")) {
512
+ return absolute;
513
+ }
514
+ if (absolute.startsWith("\\\\")) {
515
+ // UNC path: \\server\share -> \\?\UNC\server\share
516
+ return `\\\\?\\UNC\\${absolute.slice(2)}`;
517
+ }
518
+ return `\\\\?\\${absolute}`;
519
+ }
501
520
  /**
502
521
  * Remove a git worktree and its branch.
522
+ *
523
+ * Removal is best-effort: `git worktree remove --force` deletes the worktree
524
+ * metadata before the working directory, and on Windows its recursive deletion
525
+ * is MAX_PATH-limited — deep paths (e.g. node_modules) can fail with "Filename
526
+ * too long", leaving an orphan directory. When git fails we fall back to
527
+ * fs.rmSync with an extended-length path (bypasses MAX_PATH) and prune stale
528
+ * metadata. Failures are logged but never block branch deletion.
503
529
  */
504
530
  export function removeWorktree(info) {
505
531
  const repoRoot = info.repoRoot;
532
+ // Get current branch in worktree before removing
533
+ let currentBranch;
534
+ try {
535
+ currentBranch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
536
+ cwd: info.path,
537
+ encoding: "utf8",
538
+ stdio: ["ignore", "pipe", "ignore"],
539
+ }).trim();
540
+ }
541
+ catch {
542
+ // Ignore errors
543
+ }
544
+ // Remove worktree
506
545
  try {
507
- // Get current branch in worktree before removing
508
- let currentBranch;
509
- try {
510
- currentBranch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
511
- cwd: info.path,
512
- encoding: "utf8",
513
- stdio: ["ignore", "pipe", "ignore"],
514
- }).trim();
515
- }
516
- catch {
517
- // Ignore errors
518
- }
519
- // Remove worktree
520
546
  execFileSync("git", ["worktree", "remove", "--force", info.path], {
521
547
  cwd: repoRoot,
522
548
  stdio: ["ignore", "pipe", "pipe"],
523
549
  });
524
- // Delete worktree branch
550
+ }
551
+ catch (error) {
552
+ logger.warn("git worktree remove failed, falling back to fs.rmSync:", {
553
+ error: error instanceof Error ? error.message : String(error),
554
+ worktreePath: info.path,
555
+ });
556
+ try {
557
+ fs.rmSync(toExtendedLengthPath(info.path), {
558
+ recursive: true,
559
+ force: true,
560
+ });
561
+ }
562
+ catch (rmError) {
563
+ logger.error("Failed to remove worktree or branch:", {
564
+ error: rmError instanceof Error ? rmError.message : String(rmError),
565
+ worktreePath: info.path,
566
+ });
567
+ }
568
+ // git removes worktree metadata before the working directory; prune any
569
+ // leftovers in case git failed before deleting them.
525
570
  try {
526
- execFileSync("git", ["branch", "-D", info.branch], {
571
+ execFileSync("git", ["worktree", "prune"], {
527
572
  cwd: repoRoot,
528
573
  stdio: ["ignore", "pipe", "pipe"],
529
574
  });
@@ -531,33 +576,36 @@ export function removeWorktree(info) {
531
576
  catch {
532
577
  // Ignore errors
533
578
  }
534
- // Delete current branch if different and not protected
535
- if (currentBranch &&
536
- currentBranch !== info.branch &&
537
- currentBranch !== "HEAD") {
538
- const defaultRemoteBranch = getDefaultRemoteBranch(repoRoot);
539
- const defaultBranchName = defaultRemoteBranch.split("/").pop();
540
- if (currentBranch !== defaultBranchName &&
541
- currentBranch !== "main" &&
542
- currentBranch !== "master") {
543
- try {
544
- execFileSync("git", ["branch", "-D", currentBranch], {
545
- cwd: repoRoot,
546
- stdio: ["ignore", "pipe", "pipe"],
547
- });
548
- }
549
- catch {
550
- // Ignore errors
551
- }
552
- }
553
- }
554
579
  }
555
- catch (error) {
556
- logger.error("Failed to remove worktree or branch:", {
557
- error: error instanceof Error ? error.message : String(error),
558
- worktreePath: info.path,
580
+ // Delete worktree branch
581
+ try {
582
+ execFileSync("git", ["branch", "-D", info.branch], {
583
+ cwd: repoRoot,
584
+ stdio: ["ignore", "pipe", "pipe"],
559
585
  });
560
- throw error;
586
+ }
587
+ catch {
588
+ // Ignore errors
589
+ }
590
+ // Delete current branch if different and not protected
591
+ if (currentBranch &&
592
+ currentBranch !== info.branch &&
593
+ currentBranch !== "HEAD") {
594
+ const defaultRemoteBranch = getDefaultRemoteBranch(repoRoot);
595
+ const defaultBranchName = defaultRemoteBranch.split("/").pop();
596
+ if (currentBranch !== defaultBranchName &&
597
+ currentBranch !== "main" &&
598
+ currentBranch !== "master") {
599
+ try {
600
+ execFileSync("git", ["branch", "-D", currentBranch], {
601
+ cwd: repoRoot,
602
+ stdio: ["ignore", "pipe", "pipe"],
603
+ });
604
+ }
605
+ catch {
606
+ // Ignore errors
607
+ }
608
+ }
561
609
  }
562
610
  }
563
611
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "1.0.10",
3
+ "version": "1.1.1",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -63,11 +63,10 @@
63
63
  "@vitest/coverage-v8": "^4.1.7",
64
64
  "rimraf": "^6.1.2",
65
65
  "tsc-alias": "^1.8.16",
66
- "tsx": "^4.20.4",
67
66
  "vitest": "^4.1.7"
68
67
  },
69
68
  "engines": {
70
- "node": ">=20"
69
+ "node": ">=22"
71
70
  },
72
71
  "license": "MIT",
73
72
  "scripts": {
@@ -76,6 +75,9 @@
76
75
  "watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
77
76
  "test": "vitest run --reporter=dot",
78
77
  "test:coverage": "vitest run --coverage --reporter=dot",
78
+ "test:unit": "vitest run --reporter=dot --exclude 'tests/integration/**' --exclude '**/*.integration.test.ts'",
79
+ "test:unit:coverage": "vitest run --coverage --reporter=dot --exclude 'tests/integration/**' --exclude '**/*.integration.test.ts'",
80
+ "test:integration": "vitest run --reporter=dot tests/integration .integration.test",
79
81
  "lint": "eslint --cache",
80
82
  "format": "prettier --write ."
81
83
  }