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.
- package/builtin/skills/loop/SKILL.md +29 -3
- package/dist/agent.d.ts +7 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +34 -11
- package/dist/constants/tools.d.ts +3 -0
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts +13 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +69 -17
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +9 -0
- package/dist/managers/mcpManager.d.ts +4 -1
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +25 -5
- package/dist/managers/permissionManager.d.ts +0 -2
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +0 -30
- package/dist/managers/slashCommandManager.d.ts +1 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +4 -0
- package/dist/managers/toolManager.d.ts +6 -0
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +41 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +14 -4
- package/dist/services/initializationService.d.ts +0 -2
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +3 -35
- package/dist/services/memory.d.ts +6 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +27 -14
- package/dist/tools/cronCreateTool.d.ts.map +1 -1
- package/dist/tools/cronCreateTool.js +71 -6
- package/dist/tools/cronDeleteTool.d.ts.map +1 -1
- package/dist/tools/cronDeleteTool.js +5 -1
- package/dist/tools/cronListTool.d.ts.map +1 -1
- package/dist/tools/cronListTool.js +5 -1
- package/dist/tools/enterWorktreeTool.d.ts +8 -0
- package/dist/tools/enterWorktreeTool.d.ts.map +1 -0
- package/dist/tools/enterWorktreeTool.js +144 -0
- package/dist/tools/exitWorktreeTool.d.ts +8 -0
- package/dist/tools/exitWorktreeTool.d.ts.map +1 -0
- package/dist/tools/exitWorktreeTool.js +184 -0
- package/dist/tools/taskManagementTools.d.ts.map +1 -1
- package/dist/tools/taskManagementTools.js +4 -0
- package/dist/tools/toolSearchTool.d.ts +15 -0
- package/dist/tools/toolSearchTool.d.ts.map +1 -0
- package/dist/tools/toolSearchTool.js +185 -0
- package/dist/tools/types.d.ts +19 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts.map +1 -1
- package/dist/tools/webFetchTool.js +1 -0
- package/dist/types/agent.d.ts +6 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +3 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +1 -0
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +4 -6
- package/dist/utils/cronToHuman.d.ts +6 -0
- package/dist/utils/cronToHuman.d.ts.map +1 -0
- package/dist/utils/cronToHuman.js +79 -0
- package/dist/utils/isDeferredTool.d.ts +19 -0
- package/dist/utils/isDeferredTool.d.ts.map +1 -0
- package/dist/utils/isDeferredTool.js +31 -0
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +1 -0
- package/dist/utils/parseCronExpression.d.ts +6 -0
- package/dist/utils/parseCronExpression.d.ts.map +1 -0
- package/dist/utils/parseCronExpression.js +74 -0
- package/dist/utils/worktreeSession.d.ts +26 -0
- package/dist/utils/worktreeSession.d.ts.map +1 -0
- package/dist/utils/worktreeSession.js +14 -0
- package/dist/utils/worktreeUtils.d.ts +42 -0
- package/dist/utils/worktreeUtils.d.ts.map +1 -0
- package/dist/utils/worktreeUtils.js +236 -0
- package/package.json +1 -1
- package/src/agent.ts +49 -12
- package/src/constants/tools.ts +3 -0
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +73 -18
- package/src/managers/hookManager.ts +10 -0
- package/src/managers/mcpManager.ts +32 -6
- package/src/managers/permissionManager.ts +0 -42
- package/src/managers/slashCommandManager.ts +6 -0
- package/src/managers/toolManager.ts +47 -1
- package/src/prompts/index.ts +17 -3
- package/src/services/initializationService.ts +2 -41
- package/src/services/memory.ts +30 -17
- package/src/tools/cronCreateTool.ts +81 -8
- package/src/tools/cronDeleteTool.ts +7 -2
- package/src/tools/cronListTool.ts +7 -2
- package/src/tools/enterWorktreeTool.ts +183 -0
- package/src/tools/exitWorktreeTool.ts +242 -0
- package/src/tools/taskManagementTools.ts +4 -0
- package/src/tools/toolSearchTool.ts +228 -0
- package/src/tools/types.ts +19 -0
- package/src/tools/webFetchTool.ts +1 -0
- package/src/types/agent.ts +6 -0
- package/src/types/hooks.ts +4 -0
- package/src/utils/containerSetup.ts +7 -8
- package/src/utils/cronToHuman.ts +99 -0
- package/src/utils/isDeferredTool.ts +36 -0
- package/src/utils/mcpUtils.ts +1 -0
- package/src/utils/parseCronExpression.ts +78 -0
- package/src/utils/worktreeSession.ts +36 -0
- 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
|
+
}
|