wave-agent-sdk 0.15.1 → 0.15.2

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 (111) hide show
  1. package/builtin/skills/loop/SKILL.md +29 -3
  2. package/dist/agent.d.ts +7 -2
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +34 -11
  5. package/dist/constants/tools.d.ts +3 -0
  6. package/dist/constants/tools.d.ts.map +1 -1
  7. package/dist/constants/tools.js +3 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/managers/aiManager.d.ts +13 -1
  12. package/dist/managers/aiManager.d.ts.map +1 -1
  13. package/dist/managers/aiManager.js +69 -17
  14. package/dist/managers/hookManager.d.ts.map +1 -1
  15. package/dist/managers/hookManager.js +9 -0
  16. package/dist/managers/mcpManager.d.ts +4 -1
  17. package/dist/managers/mcpManager.d.ts.map +1 -1
  18. package/dist/managers/mcpManager.js +25 -5
  19. package/dist/managers/permissionManager.d.ts +0 -2
  20. package/dist/managers/permissionManager.d.ts.map +1 -1
  21. package/dist/managers/permissionManager.js +0 -30
  22. package/dist/managers/slashCommandManager.d.ts +1 -0
  23. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  24. package/dist/managers/slashCommandManager.js +4 -0
  25. package/dist/managers/toolManager.d.ts +6 -0
  26. package/dist/managers/toolManager.d.ts.map +1 -1
  27. package/dist/managers/toolManager.js +41 -1
  28. package/dist/prompts/index.d.ts.map +1 -1
  29. package/dist/prompts/index.js +14 -4
  30. package/dist/services/initializationService.d.ts +0 -2
  31. package/dist/services/initializationService.d.ts.map +1 -1
  32. package/dist/services/initializationService.js +3 -35
  33. package/dist/services/memory.d.ts +6 -0
  34. package/dist/services/memory.d.ts.map +1 -1
  35. package/dist/services/memory.js +27 -14
  36. package/dist/tools/cronCreateTool.d.ts.map +1 -1
  37. package/dist/tools/cronCreateTool.js +71 -6
  38. package/dist/tools/cronDeleteTool.d.ts.map +1 -1
  39. package/dist/tools/cronDeleteTool.js +5 -1
  40. package/dist/tools/cronListTool.d.ts.map +1 -1
  41. package/dist/tools/cronListTool.js +5 -1
  42. package/dist/tools/enterWorktreeTool.d.ts +8 -0
  43. package/dist/tools/enterWorktreeTool.d.ts.map +1 -0
  44. package/dist/tools/enterWorktreeTool.js +144 -0
  45. package/dist/tools/exitWorktreeTool.d.ts +8 -0
  46. package/dist/tools/exitWorktreeTool.d.ts.map +1 -0
  47. package/dist/tools/exitWorktreeTool.js +184 -0
  48. package/dist/tools/taskManagementTools.d.ts.map +1 -1
  49. package/dist/tools/taskManagementTools.js +4 -0
  50. package/dist/tools/toolSearchTool.d.ts +15 -0
  51. package/dist/tools/toolSearchTool.d.ts.map +1 -0
  52. package/dist/tools/toolSearchTool.js +185 -0
  53. package/dist/tools/types.d.ts +19 -0
  54. package/dist/tools/types.d.ts.map +1 -1
  55. package/dist/tools/webFetchTool.d.ts.map +1 -1
  56. package/dist/tools/webFetchTool.js +1 -0
  57. package/dist/types/agent.d.ts +6 -1
  58. package/dist/types/agent.d.ts.map +1 -1
  59. package/dist/types/hooks.d.ts +3 -1
  60. package/dist/types/hooks.d.ts.map +1 -1
  61. package/dist/types/hooks.js +1 -0
  62. package/dist/utils/containerSetup.d.ts.map +1 -1
  63. package/dist/utils/containerSetup.js +4 -6
  64. package/dist/utils/cronToHuman.d.ts +6 -0
  65. package/dist/utils/cronToHuman.d.ts.map +1 -0
  66. package/dist/utils/cronToHuman.js +79 -0
  67. package/dist/utils/isDeferredTool.d.ts +19 -0
  68. package/dist/utils/isDeferredTool.d.ts.map +1 -0
  69. package/dist/utils/isDeferredTool.js +31 -0
  70. package/dist/utils/mcpUtils.d.ts.map +1 -1
  71. package/dist/utils/mcpUtils.js +1 -0
  72. package/dist/utils/parseCronExpression.d.ts +6 -0
  73. package/dist/utils/parseCronExpression.d.ts.map +1 -0
  74. package/dist/utils/parseCronExpression.js +74 -0
  75. package/dist/utils/worktreeSession.d.ts +26 -0
  76. package/dist/utils/worktreeSession.d.ts.map +1 -0
  77. package/dist/utils/worktreeSession.js +14 -0
  78. package/dist/utils/worktreeUtils.d.ts +42 -0
  79. package/dist/utils/worktreeUtils.d.ts.map +1 -0
  80. package/dist/utils/worktreeUtils.js +236 -0
  81. package/package.json +1 -1
  82. package/src/agent.ts +49 -12
  83. package/src/constants/tools.ts +3 -0
  84. package/src/index.ts +1 -0
  85. package/src/managers/aiManager.ts +73 -18
  86. package/src/managers/hookManager.ts +10 -0
  87. package/src/managers/mcpManager.ts +32 -6
  88. package/src/managers/permissionManager.ts +0 -42
  89. package/src/managers/slashCommandManager.ts +6 -0
  90. package/src/managers/toolManager.ts +47 -1
  91. package/src/prompts/index.ts +17 -3
  92. package/src/services/initializationService.ts +2 -41
  93. package/src/services/memory.ts +30 -17
  94. package/src/tools/cronCreateTool.ts +81 -8
  95. package/src/tools/cronDeleteTool.ts +7 -2
  96. package/src/tools/cronListTool.ts +7 -2
  97. package/src/tools/enterWorktreeTool.ts +183 -0
  98. package/src/tools/exitWorktreeTool.ts +242 -0
  99. package/src/tools/taskManagementTools.ts +4 -0
  100. package/src/tools/toolSearchTool.ts +228 -0
  101. package/src/tools/types.ts +19 -0
  102. package/src/tools/webFetchTool.ts +1 -0
  103. package/src/types/agent.ts +6 -0
  104. package/src/types/hooks.ts +4 -0
  105. package/src/utils/containerSetup.ts +7 -8
  106. package/src/utils/cronToHuman.ts +99 -0
  107. package/src/utils/isDeferredTool.ts +36 -0
  108. package/src/utils/mcpUtils.ts +1 -0
  109. package/src/utils/parseCronExpression.ts +78 -0
  110. package/src/utils/worktreeSession.ts +36 -0
  111. package/src/utils/worktreeUtils.ts +288 -0
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Git worktree creation and removal utilities for the SDK.
3
+ * Used by EnterWorktree and ExitWorktree tools.
4
+ */
5
+
6
+ import { execSync } from "node:child_process";
7
+ import * as path from "node:path";
8
+ import * as fs from "node:fs";
9
+ import { getGitMainRepoRoot, getDefaultRemoteBranch } from "./gitUtils.js";
10
+ import { logger } from "./globalLogger.js";
11
+
12
+ export interface WorktreeInfo {
13
+ name: string;
14
+ path: string;
15
+ branch: string;
16
+ repoRoot: string;
17
+ isNew: boolean;
18
+ /** HEAD commit of the original branch at creation time, for dirty-check on exit */
19
+ originalHeadCommit?: string;
20
+ }
21
+
22
+ /**
23
+ * Validate a worktree name to prevent path traversal and invalid characters.
24
+ */
25
+ export function validateWorktreeName(name: string): void {
26
+ const MAX_LENGTH = 64;
27
+ if (name.length > MAX_LENGTH) {
28
+ throw new Error(
29
+ `Invalid worktree name: must be ${MAX_LENGTH} characters or fewer (got ${name.length})`,
30
+ );
31
+ }
32
+ for (const segment of name.split("/")) {
33
+ if (segment === "." || segment === "..") {
34
+ throw new Error(
35
+ `Invalid worktree name "${name}": must not contain "." or ".." path segments`,
36
+ );
37
+ }
38
+ if (!/^[a-zA-Z0-9._-]+$/.test(segment)) {
39
+ throw new Error(
40
+ `Invalid worktree name "${name}": each "/"-separated segment must be non-empty and contain only letters, digits, dots, underscores, and dashes`,
41
+ );
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Generate a random worktree name.
48
+ */
49
+ export function generateWorktreeName(): string {
50
+ const adjectives = [
51
+ "swift",
52
+ "calm",
53
+ "bold",
54
+ "keen",
55
+ "bright",
56
+ "cool",
57
+ "deep",
58
+ "fair",
59
+ "gentle",
60
+ "grand",
61
+ ];
62
+ const nouns = [
63
+ "fox",
64
+ "owl",
65
+ "hawk",
66
+ "wolf",
67
+ "bear",
68
+ "lynx",
69
+ "pike",
70
+ "kite",
71
+ "dove",
72
+ "stag",
73
+ ];
74
+ const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
75
+ const noun = nouns[Math.floor(Math.random() * nouns.length)];
76
+ const num = Math.floor(Math.random() * 900) + 100;
77
+ return `${adj}-${noun}-${num}`;
78
+ }
79
+
80
+ /**
81
+ * Get the current HEAD commit SHA.
82
+ */
83
+ export function getHeadCommit(cwd: string): string {
84
+ return execSync(`git -C "${cwd}" rev-parse HEAD`, {
85
+ encoding: "utf8",
86
+ stdio: ["ignore", "pipe", "ignore"],
87
+ }).trim();
88
+ }
89
+
90
+ /**
91
+ * Create a git worktree for use during a session.
92
+ */
93
+ export function createWorktree(name: string, cwd: string): WorktreeInfo {
94
+ const repoRoot = getGitMainRepoRoot(cwd);
95
+ if (!repoRoot) {
96
+ throw new Error(
97
+ "Cannot create a worktree: not in a git repository. Configure WorktreeCreate and WorktreeRemove hooks in settings.json to use worktree isolation with other VCS systems.",
98
+ );
99
+ }
100
+
101
+ // Capture HEAD commit before creating worktree (for dirty-check on exit)
102
+ const originalHeadCommit = getHeadCommit(cwd);
103
+
104
+ const worktreePath = path.join(repoRoot, ".wave", "worktrees", name);
105
+ const branchName = `worktree-${name}`;
106
+ const baseBranch = getDefaultRemoteBranch(cwd);
107
+
108
+ // Ensure parent directory exists
109
+ const parentDir = path.dirname(worktreePath);
110
+ if (!fs.existsSync(parentDir)) {
111
+ fs.mkdirSync(parentDir, { recursive: true });
112
+ }
113
+
114
+ // Check if worktree already exists
115
+ if (fs.existsSync(worktreePath)) {
116
+ return {
117
+ name,
118
+ path: worktreePath,
119
+ branch: branchName,
120
+ repoRoot,
121
+ isNew: false,
122
+ originalHeadCommit,
123
+ };
124
+ }
125
+
126
+ try {
127
+ // Create worktree and branch
128
+ execSync(
129
+ `git worktree add -b ${branchName} "${worktreePath}" ${baseBranch}`,
130
+ {
131
+ cwd: repoRoot,
132
+ stdio: ["ignore", "pipe", "pipe"],
133
+ env: {
134
+ ...process.env,
135
+ GIT_TERMINAL_PROMPT: "0",
136
+ GIT_ASKPASS: "",
137
+ },
138
+ },
139
+ );
140
+
141
+ return {
142
+ name,
143
+ path: worktreePath,
144
+ branch: branchName,
145
+ repoRoot,
146
+ isNew: true,
147
+ originalHeadCommit,
148
+ };
149
+ } catch (error: unknown) {
150
+ const stderr = (error as { stderr?: Buffer }).stderr?.toString() || "";
151
+ if (stderr.includes("already exists")) {
152
+ // Branch exists but worktree doesn't — attach to existing branch
153
+ try {
154
+ execSync(`git worktree add "${worktreePath}" ${branchName}`, {
155
+ cwd: repoRoot,
156
+ stdio: ["ignore", "pipe", "pipe"],
157
+ env: {
158
+ ...process.env,
159
+ GIT_TERMINAL_PROMPT: "0",
160
+ GIT_ASKPASS: "",
161
+ },
162
+ });
163
+ return {
164
+ name,
165
+ path: worktreePath,
166
+ branch: branchName,
167
+ repoRoot,
168
+ isNew: true,
169
+ originalHeadCommit,
170
+ };
171
+ } catch (innerError: unknown) {
172
+ throw new Error(
173
+ `Failed to add worktree: ${(innerError as Error).message}`,
174
+ );
175
+ }
176
+ }
177
+ throw new Error(
178
+ `Failed to create worktree: ${(error as Error).message}\n${stderr}`,
179
+ );
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Remove a git worktree and its branch.
185
+ */
186
+ export function removeWorktree(info: WorktreeInfo): void {
187
+ const repoRoot = info.repoRoot;
188
+
189
+ try {
190
+ // Get current branch in worktree before removing
191
+ let currentBranch: string | undefined;
192
+ try {
193
+ currentBranch = execSync(`git rev-parse --abbrev-ref HEAD`, {
194
+ cwd: info.path,
195
+ encoding: "utf8",
196
+ stdio: ["ignore", "pipe", "ignore"],
197
+ }).trim();
198
+ } catch {
199
+ // Ignore errors
200
+ }
201
+
202
+ // Remove worktree
203
+ execSync(`git worktree remove --force "${info.path}"`, {
204
+ cwd: repoRoot,
205
+ stdio: ["ignore", "pipe", "pipe"],
206
+ });
207
+
208
+ // Delete worktree branch
209
+ try {
210
+ execSync(`git branch -D ${info.branch}`, {
211
+ cwd: repoRoot,
212
+ stdio: ["ignore", "pipe", "pipe"],
213
+ });
214
+ } catch {
215
+ // Ignore errors
216
+ }
217
+
218
+ // Delete current branch if different and not protected
219
+ if (
220
+ currentBranch &&
221
+ currentBranch !== info.branch &&
222
+ currentBranch !== "HEAD"
223
+ ) {
224
+ const defaultRemoteBranch = getDefaultRemoteBranch(repoRoot);
225
+ const defaultBranchName = defaultRemoteBranch.split("/").pop();
226
+
227
+ if (
228
+ currentBranch !== defaultBranchName &&
229
+ currentBranch !== "main" &&
230
+ currentBranch !== "master"
231
+ ) {
232
+ try {
233
+ execSync(`git branch -D ${currentBranch}`, {
234
+ cwd: repoRoot,
235
+ stdio: ["ignore", "pipe", "pipe"],
236
+ });
237
+ } catch {
238
+ // Ignore errors
239
+ }
240
+ }
241
+ }
242
+ } catch (error: unknown) {
243
+ logger.error("Failed to remove worktree or branch:", {
244
+ error: error instanceof Error ? error.message : String(error),
245
+ worktreePath: info.path,
246
+ });
247
+ throw error;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Count uncommitted files and new commits in a worktree.
253
+ * Returns null if git commands fail (fail-closed).
254
+ */
255
+ export function countWorktreeChanges(
256
+ worktreePath: string,
257
+ originalHeadCommit: string | undefined,
258
+ ): { changedFiles: number; commits: number } | null {
259
+ try {
260
+ const statusOutput = execSync(
261
+ `git -C "${worktreePath}" status --porcelain`,
262
+ {
263
+ encoding: "utf8",
264
+ stdio: ["ignore", "pipe", "ignore"],
265
+ },
266
+ );
267
+ const changedFiles = statusOutput
268
+ .split("\n")
269
+ .filter((l) => l.trim() !== "").length;
270
+
271
+ if (!originalHeadCommit) {
272
+ return null;
273
+ }
274
+
275
+ const revListOutput = execSync(
276
+ `git -C "${worktreePath}" rev-list --count ${originalHeadCommit}..HEAD`,
277
+ {
278
+ encoding: "utf8",
279
+ stdio: ["ignore", "pipe", "ignore"],
280
+ },
281
+ );
282
+ const commits = parseInt(revListOutput.trim(), 10) || 0;
283
+
284
+ return { changedFiles, commits };
285
+ } catch {
286
+ return null;
287
+ }
288
+ }