wave-agent-sdk 0.16.9 → 0.16.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +5 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +18 -0
- package/dist/constants/toolLimits.d.ts +2 -0
- package/dist/constants/toolLimits.d.ts.map +1 -1
- package/dist/constants/toolLimits.js +2 -0
- package/dist/managers/aiManager.d.ts +5 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +21 -0
- package/dist/managers/hookManager.d.ts +6 -3
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +36 -13
- package/dist/managers/mcpManager.d.ts +4 -28
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +10 -127
- package/dist/services/authService.d.ts +33 -1
- package/dist/services/authService.d.ts.map +1 -1
- package/dist/services/authService.js +212 -11
- package/dist/services/configurationService.d.ts +1 -0
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +48 -6
- package/dist/services/hook.d.ts +4 -0
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +10 -0
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +11 -0
- package/dist/services/interactionService.d.ts.map +1 -1
- package/dist/services/interactionService.js +0 -12
- package/dist/services/remoteSettingsService.d.ts +21 -0
- package/dist/services/remoteSettingsService.d.ts.map +1 -0
- package/dist/services/remoteSettingsService.js +280 -0
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +58 -32
- package/dist/tools/types.d.ts +4 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/agent.d.ts +7 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/auth.d.ts +12 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/configuration.d.ts +20 -0
- package/dist/types/configuration.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +5 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +1 -0
- package/dist/types/mcp.d.ts +1 -1
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +9 -8
- package/dist/utils/gitUtils.d.ts +18 -1
- package/dist/utils/gitUtils.d.ts.map +1 -1
- package/dist/utils/gitUtils.js +120 -49
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +6 -1
- package/dist/utils/openaiClient.d.ts.map +1 -1
- package/dist/utils/openaiClient.js +4 -2
- package/dist/utils/toolResultStorage.d.ts +46 -0
- package/dist/utils/toolResultStorage.d.ts.map +1 -0
- package/dist/utils/toolResultStorage.js +90 -0
- package/dist/utils/worktreeUtils.d.ts.map +1 -1
- package/dist/utils/worktreeUtils.js +58 -0
- package/package.json +3 -3
- package/src/agent.ts +20 -0
- package/src/constants/toolLimits.ts +3 -0
- package/src/managers/aiManager.ts +37 -0
- package/src/managers/hookManager.ts +42 -17
- package/src/managers/mcpManager.ts +10 -178
- package/src/services/authService.ts +243 -16
- package/src/services/configurationService.ts +58 -6
- package/src/services/hook.ts +15 -0
- package/src/services/initializationService.ts +13 -0
- package/src/services/interactionService.ts +0 -18
- package/src/services/remoteSettingsService.ts +315 -0
- package/src/tools/bashTool.ts +70 -38
- package/src/tools/types.ts +4 -0
- package/src/types/agent.ts +7 -0
- package/src/types/auth.ts +10 -0
- package/src/types/configuration.ts +23 -0
- package/src/types/hooks.ts +7 -1
- package/src/types/mcp.ts +1 -1
- package/src/utils/containerSetup.ts +8 -8
- package/src/utils/gitUtils.ts +123 -48
- package/src/utils/mcpUtils.ts +12 -1
- package/src/utils/openaiClient.ts +5 -2
- package/src/utils/toolResultStorage.ts +117 -0
- package/src/utils/worktreeUtils.ts +63 -0
package/src/utils/gitUtils.ts
CHANGED
|
@@ -82,69 +82,144 @@ export function getGitMainRepoRoot(cwd: string): string {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
85
|
+
* Resolve the git directory for a repository by walking up from `cwd`.
|
|
86
|
+
* Handles both normal repos (.git is a directory) and worktrees
|
|
87
|
+
* (.git is a file pointing to the main repo's worktree git dir).
|
|
88
|
+
* For worktrees, reads the `commondir` file to find the common git dir.
|
|
89
|
+
* @param cwd Working directory to start searching from
|
|
90
|
+
* @returns Absolute path to the git directory, or null if not found
|
|
88
91
|
*/
|
|
89
|
-
export function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
export function resolveGitDir(cwd: string): string | null {
|
|
93
|
+
let currentPath = path.resolve(cwd);
|
|
94
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
95
|
+
const gitPath = path.join(currentPath, ".git");
|
|
96
|
+
if (fsSync.existsSync(gitPath)) {
|
|
97
|
+
const stat = fsSync.statSync(gitPath);
|
|
98
|
+
if (stat.isDirectory()) {
|
|
99
|
+
// Normal repo: .git is a directory
|
|
100
|
+
return gitPath;
|
|
101
|
+
}
|
|
102
|
+
// Worktree: .git is a file containing "gitdir: <path>"
|
|
103
|
+
try {
|
|
104
|
+
const content = fsSync.readFileSync(gitPath, "utf8").trim();
|
|
105
|
+
const prefix = "gitdir: ";
|
|
106
|
+
if (content.startsWith(prefix)) {
|
|
107
|
+
const worktreeGitDir = content.substring(prefix.length);
|
|
108
|
+
// Read commondir to find the common git dir
|
|
109
|
+
const commondirPath = path.join(worktreeGitDir, "commondir");
|
|
110
|
+
if (fsSync.existsSync(commondirPath)) {
|
|
111
|
+
const commondirRel = fsSync
|
|
112
|
+
.readFileSync(commondirPath, "utf8")
|
|
113
|
+
.trim();
|
|
114
|
+
return path.resolve(worktreeGitDir, commondirRel);
|
|
115
|
+
}
|
|
116
|
+
// Fallback: resolve ../.. from the worktree git dir
|
|
117
|
+
return path.resolve(worktreeGitDir, "..", "..");
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
currentPath = path.dirname(currentPath);
|
|
100
124
|
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
101
127
|
|
|
102
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Read a symbolic ref file in the git directory.
|
|
130
|
+
* If the file content starts with "ref: ", returns the target ref path.
|
|
131
|
+
* Symbolic refs are never packed in packed-refs, so only loose refs are checked.
|
|
132
|
+
* @param gitDir Absolute path to the git directory
|
|
133
|
+
* @param refPath Relative path to the ref file (e.g., "refs/remotes/origin/HEAD")
|
|
134
|
+
* @returns The symbolic ref target, or null if not a symbolic ref or on error
|
|
135
|
+
*/
|
|
136
|
+
function readSymref(gitDir: string, refPath: string): string | null {
|
|
103
137
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
const fullPath = path.join(gitDir, refPath);
|
|
139
|
+
const content = fsSync.readFileSync(fullPath, "utf8").trim();
|
|
140
|
+
if (content.startsWith("ref: ")) {
|
|
141
|
+
return content.substring("ref: ".length);
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
109
144
|
} catch {
|
|
110
|
-
|
|
145
|
+
return null;
|
|
111
146
|
}
|
|
147
|
+
}
|
|
112
148
|
|
|
113
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Check if a git ref exists, checking both loose refs and packed-refs.
|
|
151
|
+
* @param gitDir Absolute path to the git directory
|
|
152
|
+
* @param refPath Relative path to the ref (e.g., "refs/remotes/origin/main")
|
|
153
|
+
* @returns True if the ref exists, false otherwise
|
|
154
|
+
*/
|
|
155
|
+
function refExists(gitDir: string, refPath: string): boolean {
|
|
156
|
+
// 1. Check loose ref
|
|
157
|
+
const loosePath = path.join(gitDir, refPath);
|
|
158
|
+
if (fsSync.existsSync(loosePath)) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
// 2. Check packed-refs
|
|
114
162
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
163
|
+
const packedRefsPath = path.join(gitDir, "packed-refs");
|
|
164
|
+
const content = fsSync.readFileSync(packedRefsPath, "utf8");
|
|
165
|
+
for (const line of content.split("\n")) {
|
|
166
|
+
// Skip comments and peeled lines
|
|
167
|
+
if (line.startsWith("#") || line.startsWith("^")) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// Format: <sha> <refPath>
|
|
171
|
+
const parts = line.split(" ");
|
|
172
|
+
if (parts.length >= 2 && parts[1] === refPath) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
120
176
|
} catch {
|
|
121
|
-
//
|
|
177
|
+
// packed-refs doesn't exist or can't be read
|
|
122
178
|
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
123
181
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Get the default remote branch (e.g., origin/main) using filesystem reads.
|
|
184
|
+
* No subprocess calls — matches Claude Code's approach.
|
|
185
|
+
*
|
|
186
|
+
* Priority:
|
|
187
|
+
* 1. Read refs/remotes/origin/HEAD symref → extract branch name (verify it exists)
|
|
188
|
+
* 2. Check if refs/remotes/origin/main ref exists
|
|
189
|
+
* 3. Check if refs/remotes/origin/master ref exists
|
|
190
|
+
* 4. Hardcoded "main" fallback
|
|
191
|
+
*
|
|
192
|
+
* @param cwd Working directory
|
|
193
|
+
* @returns Default remote branch name
|
|
194
|
+
*/
|
|
195
|
+
export function getDefaultRemoteBranch(cwd: string): string {
|
|
196
|
+
const gitDir = resolveGitDir(cwd);
|
|
197
|
+
if (!gitDir) {
|
|
198
|
+
return "main";
|
|
133
199
|
}
|
|
134
200
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
201
|
+
// 1. Try reading origin/HEAD symref
|
|
202
|
+
const symref = readSymref(gitDir, "refs/remotes/origin/HEAD");
|
|
203
|
+
if (symref) {
|
|
204
|
+
const branch = symref.replace("refs/remotes/", "");
|
|
205
|
+
// Verify the resolved branch actually exists (origin/HEAD can be stale)
|
|
206
|
+
if (refExists(gitDir, symref)) {
|
|
207
|
+
return branch;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 2. Check if origin/main exists
|
|
212
|
+
if (refExists(gitDir, "refs/remotes/origin/main")) {
|
|
213
|
+
return "origin/main";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 3. Check if origin/master exists
|
|
217
|
+
if (refExists(gitDir, "refs/remotes/origin/master")) {
|
|
218
|
+
return "origin/master";
|
|
144
219
|
}
|
|
145
220
|
|
|
146
|
-
//
|
|
147
|
-
return "
|
|
221
|
+
// 4. Hardcoded fallback
|
|
222
|
+
return "main";
|
|
148
223
|
}
|
|
149
224
|
|
|
150
225
|
/**
|
package/src/utils/mcpUtils.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ChatCompletionFunctionTool } from "openai/resources.js";
|
|
2
2
|
import type { ToolPlugin, ToolResult, ToolContext } from "../tools/types.js";
|
|
3
3
|
import type { McpTool, McpServerStatus } from "../types/index.js";
|
|
4
|
+
import { processToolResult } from "./toolResultStorage.js";
|
|
5
|
+
import { DEFAULT_MAX_RESULT_SIZE_CHARS } from "../constants/toolLimits.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Recursively clean schema to remove unsupported fields
|
|
@@ -97,9 +99,18 @@ export function createMcpToolPlugin(
|
|
|
97
99
|
): Promise<ToolResult> {
|
|
98
100
|
try {
|
|
99
101
|
const result = await executeTool(prefixedName, args, context);
|
|
102
|
+
|
|
103
|
+
// Process content for size limits — only text content, not images
|
|
104
|
+
const processedContent = processToolResult(
|
|
105
|
+
result.content || `Executed ${mcpTool.name}`,
|
|
106
|
+
DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
107
|
+
`mcp_${serverName}_${mcpTool.name}`,
|
|
108
|
+
);
|
|
109
|
+
|
|
100
110
|
return {
|
|
101
111
|
success: true,
|
|
102
|
-
content:
|
|
112
|
+
content: processedContent,
|
|
113
|
+
images: result.images,
|
|
103
114
|
};
|
|
104
115
|
} catch (error) {
|
|
105
116
|
return {
|
|
@@ -176,8 +176,11 @@ export class OpenAIClient {
|
|
|
176
176
|
error.status = response.status;
|
|
177
177
|
error.body = errorBody;
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
const retryableStatus =
|
|
180
|
+
response.status === 429 ||
|
|
181
|
+
(response.status >= 500 && response.status !== 501);
|
|
182
|
+
if (retryableStatus && attempt < maxRetries) {
|
|
183
|
+
logger.warn("OpenAI API error, retrying...", {
|
|
181
184
|
attempt: attempt + 1,
|
|
182
185
|
status: response.status,
|
|
183
186
|
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tool result persistence and truncation logic.
|
|
3
|
+
*
|
|
4
|
+
* When a tool result exceeds a size threshold, the full content is saved to a
|
|
5
|
+
* file in /tmp/wave-tool-results/ and the model receives a <persisted-output>
|
|
6
|
+
* preview with the file path so it can use the Read tool to access the full output.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import * as os from "os";
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
14
|
+
PREVIEW_SIZE_BYTES,
|
|
15
|
+
} from "../constants/toolLimits.js";
|
|
16
|
+
import { logger } from "./globalLogger.js";
|
|
17
|
+
|
|
18
|
+
const TOOL_RESULTS_DIR = path.join(os.tmpdir(), "wave-tool-results");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get (and create if needed) the tool-results directory.
|
|
22
|
+
* Uses /tmp/wave-tool-results/ for simplicity and automatic OS cleanup.
|
|
23
|
+
*/
|
|
24
|
+
export function getToolResultsDir(): string {
|
|
25
|
+
fs.mkdirSync(TOOL_RESULTS_DIR, { recursive: true });
|
|
26
|
+
return TOOL_RESULTS_DIR;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Persist full tool output to a file in the tool-results directory.
|
|
31
|
+
* Returns the file path on success, or undefined on failure.
|
|
32
|
+
*/
|
|
33
|
+
export function persistToolResult(
|
|
34
|
+
content: string,
|
|
35
|
+
prefix: string = "tool",
|
|
36
|
+
): string | undefined {
|
|
37
|
+
try {
|
|
38
|
+
const dir = getToolResultsDir();
|
|
39
|
+
const id = `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
40
|
+
const filePath = path.join(dir, `${id}.txt`);
|
|
41
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
42
|
+
return filePath;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
logger?.error("Failed to persist tool result:", error);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate a preview from content: first `previewSize` characters with ellipsis.
|
|
51
|
+
*/
|
|
52
|
+
export function generatePreview(
|
|
53
|
+
content: string,
|
|
54
|
+
previewSize: number = PREVIEW_SIZE_BYTES,
|
|
55
|
+
): string {
|
|
56
|
+
if (content.length <= previewSize) return content;
|
|
57
|
+
return content.substring(0, previewSize) + "\n...";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build the <persisted-output> wrapper message that the model sees.
|
|
62
|
+
*
|
|
63
|
+
* Example output:
|
|
64
|
+
* <persisted-output>
|
|
65
|
+
* Output too large (150,000 characters). Full output saved to: /tmp/wave-tool-results/mcp_server_tool_12345.txt
|
|
66
|
+
* Preview (first 2,048 characters):
|
|
67
|
+
* {preview content}
|
|
68
|
+
* ...
|
|
69
|
+
* </persisted-output>
|
|
70
|
+
*/
|
|
71
|
+
export function buildPersistedOutputMessage(
|
|
72
|
+
totalChars: number,
|
|
73
|
+
filePath: string,
|
|
74
|
+
preview: string,
|
|
75
|
+
): string {
|
|
76
|
+
return [
|
|
77
|
+
"<persisted-output>",
|
|
78
|
+
`Output too large (${totalChars.toLocaleString()} characters). Full output saved to: ${filePath}`,
|
|
79
|
+
`Preview (first ${PREVIEW_SIZE_BYTES.toLocaleString()} characters):`,
|
|
80
|
+
preview,
|
|
81
|
+
"</persisted-output>",
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Process tool result: if content exceeds maxChars, persist to file and return
|
|
87
|
+
* truncated content with <persisted-output> wrapper. Otherwise return unchanged.
|
|
88
|
+
*
|
|
89
|
+
* This is the main entry point for both MCP and bash tools.
|
|
90
|
+
*
|
|
91
|
+
* @param content - The tool result content
|
|
92
|
+
* @param maxChars - Maximum characters before persistence kicks in (defaults to DEFAULT_MAX_RESULT_SIZE_CHARS)
|
|
93
|
+
* @param prefix - File name prefix for the persisted file (e.g. "bash", "mcp")
|
|
94
|
+
* @returns The content to send to the model (either original or persisted-output wrapper)
|
|
95
|
+
*/
|
|
96
|
+
export function processToolResult(
|
|
97
|
+
content: string,
|
|
98
|
+
maxChars: number = DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
99
|
+
prefix: string = "tool",
|
|
100
|
+
): string {
|
|
101
|
+
if (content.length <= maxChars) {
|
|
102
|
+
return content;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const filePath = persistToolResult(content, prefix);
|
|
106
|
+
|
|
107
|
+
if (filePath) {
|
|
108
|
+
const preview = generatePreview(content);
|
|
109
|
+
return buildPersistedOutputMessage(content.length, filePath, preview);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Fallback: truncation only (persistence failed)
|
|
113
|
+
return (
|
|
114
|
+
content.substring(0, maxChars) +
|
|
115
|
+
"\n\n... (output truncated, failed to persist full output)"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -174,6 +174,69 @@ export function createWorktree(name: string, cwd: string): WorktreeInfo {
|
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
+
if (
|
|
178
|
+
stderr.includes("not a valid object name") ||
|
|
179
|
+
stderr.includes("unknown revision")
|
|
180
|
+
) {
|
|
181
|
+
// Base branch not fetched yet — try fetching then retrying
|
|
182
|
+
const branchNameOnly = baseBranch.split("/").pop()!;
|
|
183
|
+
try {
|
|
184
|
+
execSync(`git fetch origin ${branchNameOnly}`, {
|
|
185
|
+
cwd: repoRoot,
|
|
186
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
187
|
+
env: {
|
|
188
|
+
...process.env,
|
|
189
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
190
|
+
GIT_ASKPASS: "",
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
execSync(
|
|
194
|
+
`git worktree add -b ${branchName} "${worktreePath}" ${baseBranch}`,
|
|
195
|
+
{
|
|
196
|
+
cwd: repoRoot,
|
|
197
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
198
|
+
env: {
|
|
199
|
+
...process.env,
|
|
200
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
201
|
+
GIT_ASKPASS: "",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
return {
|
|
206
|
+
name,
|
|
207
|
+
path: worktreePath,
|
|
208
|
+
branch: branchName,
|
|
209
|
+
repoRoot,
|
|
210
|
+
isNew: true,
|
|
211
|
+
originalHeadCommit,
|
|
212
|
+
};
|
|
213
|
+
} catch {
|
|
214
|
+
// Fetch or retry failed — fall back to HEAD
|
|
215
|
+
try {
|
|
216
|
+
execSync(`git worktree add -b ${branchName} "${worktreePath}" HEAD`, {
|
|
217
|
+
cwd: repoRoot,
|
|
218
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
219
|
+
env: {
|
|
220
|
+
...process.env,
|
|
221
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
222
|
+
GIT_ASKPASS: "",
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
name,
|
|
227
|
+
path: worktreePath,
|
|
228
|
+
branch: branchName,
|
|
229
|
+
repoRoot,
|
|
230
|
+
isNew: true,
|
|
231
|
+
originalHeadCommit,
|
|
232
|
+
};
|
|
233
|
+
} catch {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Failed to create worktree: ${(error as Error).message}\n${stderr}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
177
240
|
throw new Error(
|
|
178
241
|
`Failed to create worktree: ${(error as Error).message}\n${stderr}`,
|
|
179
242
|
);
|