openfox 2.0.65 → 2.0.66
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/README.md +1 -0
- package/dist/agent-defaults/builder.agent.md +1 -1
- package/dist/agent-defaults/code-reviewer.agent.md +0 -1
- package/dist/agent-defaults/explorer.agent.md +0 -1
- package/dist/agent-defaults/planner.agent.md +1 -1
- package/dist/{branch.api-LAN42BA5.js → branch.api-3YJSWOZG.js} +5 -1
- package/dist/{chat-handler-MC3XZ3OV.js → chat-handler-ZRDNSLOG.js} +14 -12
- package/dist/{chunk-UEIQKG5P.js → chunk-3ACDQKK6.js} +290 -326
- package/dist/{chunk-RYIREJAW.js → chunk-5EO6SEGV.js} +2 -2
- package/dist/{chunk-OESYKZCM.js → chunk-5V5DM5E4.js} +3 -3
- package/dist/{chunk-QBYQ7KDX.js → chunk-B7WGOK7G.js} +16 -15
- package/dist/{chunk-Z6W4YHJB.js → chunk-BL5LOLVA.js} +17 -1
- package/dist/{chunk-TMUBLQFC.js → chunk-C3VLGVSQ.js} +3 -3
- package/dist/{chunk-ZPIZLTWU.js → chunk-GMXKJGJ2.js} +5 -1
- package/dist/{chunk-YQ3SOPBI.js → chunk-KXVFHMSM.js} +2 -2
- package/dist/chunk-OBK3I4KD.js +137 -0
- package/dist/{chunk-Z6H7SFQR.js → chunk-OE5B44P6.js} +258 -65
- package/dist/{chunk-PXJ4NOP4.js → chunk-PVRSNXD4.js} +2 -2
- package/dist/{chunk-X5YCWKNS.js → chunk-R4UTFE63.js} +53 -83
- package/dist/{chunk-4UYKN6JU.js → chunk-R7EED6ZU.js} +3 -3
- package/dist/{chunk-AIJE3RYH.js → chunk-TFDK6VSG.js} +33 -7
- package/dist/{chunk-QJ5FH5DP.js → chunk-TMHWQYWE.js} +4 -4
- package/dist/{chunk-VWPY2KWH.js → chunk-UBIHP5Z7.js} +8 -8
- package/dist/chunk-WGMJCFCE.js +14 -0
- package/dist/{chunk-LBGHZLLH.js → chunk-WNA5VPZG.js} +2 -2
- package/dist/{chunk-N4K3PNOS.js → chunk-ZUM2ZJOX.js} +46 -18
- package/dist/cli/dev.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/{client-LAKW7TA5.js → client-BUX62J2K.js} +3 -3
- package/dist/{client-pure-DO3S675T.js → client-pure-U6ZYUPJQ.js} +6 -4
- package/dist/{compactor-I3N56PSJ.js → compactor-V43WJ3WX.js} +7 -7
- package/dist/{events-52XADT6Y.js → events-CDQ4HASP.js} +4 -4
- package/dist/{folding-2BGFLV5J.js → folding-V2K34EGN.js} +2 -2
- package/dist/{inspect-proxy-BV25JERQ.js → inspect-proxy-PKMDR47R.js} +4 -4
- package/dist/{orchestrator-IYJRQQKB.js → orchestrator-LZQTDOWM.js} +13 -11
- package/dist/package.json +1 -1
- package/dist/{processor-LUXKVIED.js → processor-7KAXESY5.js} +14 -12
- package/dist/{project-creator-IDW7RACH.js → project-creator-EMGRSPYS.js} +18 -5
- package/dist/{projects-BY227WT2.js → projects-7HSBZY2D.js} +3 -3
- package/dist/{protocol-Cq66hdDX.d.ts → protocol-42f_Png6.d.ts} +1 -1
- package/dist/provider/index.d.ts +4 -4
- package/dist/{provider-GYOW4OWQ.js → provider-BDH6JGZB.js} +4 -4
- package/dist/{provider-manager-HRHNF73A.js → provider-manager-D3KIOD5E.js} +4 -4
- package/dist/{serve-ZRTWVOL2.js → serve-HGHFN3D2.js} +18 -16
- package/dist/server/index.d.ts +26 -8
- package/dist/server/index.js +17 -15
- package/dist/{server-PXXGMFBT.js → server-W42ENYFX.js} +15 -13
- package/dist/{settings-JA4XUR3W.js → settings-ZZ6AKFPD.js} +3 -3
- package/dist/shared/index.d.ts +3 -3
- package/dist/{tools-ZZM36DAZ.js → tools-2EB7C6H2.js} +12 -10
- package/dist/{types-CDZakn7f.d.ts → types-apiMd7Df.d.ts} +1 -1
- package/dist/{types-akHmgvBk.d.ts → types-vezAEt_y.d.ts} +3 -1
- package/dist/web/assets/{index-Dc05znz4.css → index-B0Q5uK2h.css} +1 -1
- package/dist/web/assets/index-BhQN93bI.js +303 -0
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/dist/worktree-EIF75WGF.js +26 -0
- package/package.json +1 -1
- package/dist/web/assets/index-Bsi_le80.js +0 -303
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
createToolCallEvent,
|
|
11
11
|
createToolResultEvent,
|
|
12
12
|
streamLLMPure
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-R4UTFE63.js";
|
|
14
14
|
import {
|
|
15
15
|
startInspectProxy
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-WNA5VPZG.js";
|
|
17
17
|
import {
|
|
18
18
|
createProcess,
|
|
19
19
|
getPlatformShell,
|
|
@@ -31,12 +31,15 @@ import {
|
|
|
31
31
|
import {
|
|
32
32
|
createMcpTools
|
|
33
33
|
} from "./chunk-NWO6GRYE.js";
|
|
34
|
+
import {
|
|
35
|
+
listBranches
|
|
36
|
+
} from "./chunk-OBK3I4KD.js";
|
|
34
37
|
import {
|
|
35
38
|
getCurrentContextWindowId,
|
|
36
39
|
getCurrentWindowMessageOptions,
|
|
37
40
|
getEventStore,
|
|
38
41
|
getRuntimeConfig
|
|
39
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-TFDK6VSG.js";
|
|
40
43
|
import {
|
|
41
44
|
buildContextMessagesFromEventHistory,
|
|
42
45
|
foldContextState,
|
|
@@ -46,7 +49,7 @@ import {
|
|
|
46
49
|
handleToolResult,
|
|
47
50
|
reorderToolMessages,
|
|
48
51
|
stripOrphanedToolCalls
|
|
49
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-C3VLGVSQ.js";
|
|
50
53
|
import {
|
|
51
54
|
createChatDoneMessage,
|
|
52
55
|
createChatMessageMessage,
|
|
@@ -59,14 +62,14 @@ import {
|
|
|
59
62
|
deleteSetting,
|
|
60
63
|
getSetting,
|
|
61
64
|
setSetting
|
|
62
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-PVRSNXD4.js";
|
|
63
66
|
import {
|
|
64
67
|
AskUserInterrupt,
|
|
65
68
|
askUserTool
|
|
66
69
|
} from "./chunk-EU3WWTFH.js";
|
|
67
70
|
import {
|
|
68
71
|
getProject
|
|
69
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-KXVFHMSM.js";
|
|
70
73
|
import {
|
|
71
74
|
modelSupportsVision
|
|
72
75
|
} from "./chunk-V4IE7HJY.js";
|
|
@@ -225,6 +228,10 @@ async function isPathWithinSandbox(path, workdir, sessionId) {
|
|
|
225
228
|
function looksLikeRegex(str) {
|
|
226
229
|
return /[*?+[\]\\]/.test(str);
|
|
227
230
|
}
|
|
231
|
+
var isWindows = () => process.platform === "win32";
|
|
232
|
+
function isWindowsAbsolutePath(str) {
|
|
233
|
+
return /^[A-Za-z]:[\\/]/.test(str);
|
|
234
|
+
}
|
|
228
235
|
function isPlaceholderToken(str) {
|
|
229
236
|
return str.includes("__URL__") || str.includes("__FILEURL__") || str.includes("__SED__") || str.includes("__COMMIT_MSG__");
|
|
230
237
|
}
|
|
@@ -269,7 +276,12 @@ function extractAbsolutePathsFromCommand(command) {
|
|
|
269
276
|
if (content.startsWith("/") && looksLikeRegex(content)) {
|
|
270
277
|
continue;
|
|
271
278
|
}
|
|
272
|
-
if (content
|
|
279
|
+
if (isWindowsAbsolutePath(content)) {
|
|
280
|
+
const resolved = normalize(content);
|
|
281
|
+
if (!isSafePath(resolved)) {
|
|
282
|
+
paths.push(resolved);
|
|
283
|
+
}
|
|
284
|
+
} else if (!isWindows() && content.startsWith("/")) {
|
|
273
285
|
const resolved = normalize(content);
|
|
274
286
|
if (!isSafePath(resolved)) {
|
|
275
287
|
paths.push(resolved);
|
|
@@ -283,15 +295,27 @@ function extractAbsolutePathsFromCommand(command) {
|
|
|
283
295
|
}
|
|
284
296
|
}
|
|
285
297
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
298
|
+
if (isWindows()) {
|
|
299
|
+
const winAbsolutePattern = /(?:^|[\s=(])([A-Za-z]:[\\/][^\s"'|&;<>`()]+)/g;
|
|
300
|
+
while ((match = winAbsolutePattern.exec(sanitized)) !== null) {
|
|
301
|
+
const candidate = match[1];
|
|
302
|
+
if (isPlaceholderToken(candidate)) continue;
|
|
303
|
+
const resolved = normalize(candidate);
|
|
304
|
+
if (!isSafePath(resolved)) {
|
|
305
|
+
paths.push(resolved);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
const absolutePattern = /(?:^|[\s=(])(\/[^\s"'|&;<>`()]+)/g;
|
|
310
|
+
while ((match = absolutePattern.exec(sanitized)) !== null) {
|
|
311
|
+
const candidate = match[1];
|
|
312
|
+
if (isPlaceholderToken(candidate)) continue;
|
|
313
|
+
if (looksLikeRegex(candidate)) continue;
|
|
314
|
+
const resolved = normalize(candidate);
|
|
315
|
+
if (resolved === "/" || resolved === "") continue;
|
|
316
|
+
if (!isSafePath(resolved)) {
|
|
317
|
+
paths.push(resolved);
|
|
318
|
+
}
|
|
295
319
|
}
|
|
296
320
|
}
|
|
297
321
|
return [...new Set(paths)];
|
|
@@ -1451,6 +1475,7 @@ Make sure whitespace and indentation match exactly.`
|
|
|
1451
1475
|
import { spawn as spawn2 } from "child_process";
|
|
1452
1476
|
import { resolve as resolve4, isAbsolute as isAbsolute2 } from "path";
|
|
1453
1477
|
import { access } from "fs/promises";
|
|
1478
|
+
import stripAnsi from "strip-ansi";
|
|
1454
1479
|
|
|
1455
1480
|
// src/server/utils/shell.ts
|
|
1456
1481
|
import { spawn } from "child_process";
|
|
@@ -1587,13 +1612,14 @@ ${result.stderr}`;
|
|
|
1587
1612
|
const tailed = lines.slice(-tailInfo.tailLines);
|
|
1588
1613
|
output = tailed.join("\n");
|
|
1589
1614
|
}
|
|
1615
|
+
const visible = stripAnsi(output);
|
|
1590
1616
|
let truncated = false;
|
|
1591
|
-
if (
|
|
1617
|
+
if (visible.length > OUTPUT_LIMITS.run_command.maxBytes) {
|
|
1592
1618
|
output = output.slice(0, OUTPUT_LIMITS.run_command.maxBytes);
|
|
1593
1619
|
output += "\n\n[Output truncated due to size limit]";
|
|
1594
1620
|
truncated = true;
|
|
1595
1621
|
}
|
|
1596
|
-
const linesCount =
|
|
1622
|
+
const linesCount = visible.split("\n").length;
|
|
1597
1623
|
if (linesCount > OUTPUT_LIMITS.run_command.maxLines) {
|
|
1598
1624
|
const limitedLines = output.split("\n").slice(0, OUTPUT_LIMITS.run_command.maxLines);
|
|
1599
1625
|
output = limitedLines.join("\n");
|
|
@@ -2554,7 +2580,7 @@ function createToolProgressHandler(append, messageId, callId, _sessionId) {
|
|
|
2554
2580
|
}
|
|
2555
2581
|
|
|
2556
2582
|
// src/server/chat/execute-tools.ts
|
|
2557
|
-
import
|
|
2583
|
+
import stripAnsi2 from "strip-ansi";
|
|
2558
2584
|
var INTERRUPTED_ERROR = "Tool execution was interrupted by user";
|
|
2559
2585
|
function createInterruptedResult(startTime) {
|
|
2560
2586
|
return {
|
|
@@ -2618,19 +2644,24 @@ async function executeTools(assistantMsgId, toolCalls, ctx, append) {
|
|
|
2618
2644
|
};
|
|
2619
2645
|
}
|
|
2620
2646
|
if (toolCall.parseError) {
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2647
|
+
if (toolCall.name === "step_done") {
|
|
2648
|
+
const { parseError: _pe, rawArguments: _ra, ...rest } = toolCall;
|
|
2649
|
+
toolCall = { ...rest, arguments: {} };
|
|
2650
|
+
} else {
|
|
2651
|
+
const toolResult2 = {
|
|
2652
|
+
success: false,
|
|
2653
|
+
error: `Failed to parse tool call arguments: ${toolCall.parseError}. Please ensure your JSON function call arguments are valid.`,
|
|
2654
|
+
durationMs: 0,
|
|
2655
|
+
truncated: false
|
|
2656
|
+
};
|
|
2657
|
+
append(createToolResultEvent(assistantMsgId, toolCall.id, toolResult2));
|
|
2658
|
+
return {
|
|
2659
|
+
toolCall,
|
|
2660
|
+
toolResult: toolResult2,
|
|
2661
|
+
content: `Error: ${toolResult2.error}`,
|
|
2662
|
+
index
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2634
2665
|
}
|
|
2635
2666
|
const onProgress = ctx.onMessage ? createToolProgressHandler(append, assistantMsgId, toolCall.id, ctx.sessionId) : void 0;
|
|
2636
2667
|
const toolContext = {
|
|
@@ -2666,7 +2697,7 @@ async function executeTools(assistantMsgId, toolCalls, ctx, append) {
|
|
|
2666
2697
|
if (toolCall.name === "step_done" && toolResult.success) {
|
|
2667
2698
|
stepDoneCalled = true;
|
|
2668
2699
|
}
|
|
2669
|
-
const content =
|
|
2700
|
+
const content = stripAnsi2(
|
|
2670
2701
|
toolResult.success ? toolResult.output ?? "Success" : toolResult.output ? `${toolResult.output}
|
|
2671
2702
|
|
|
2672
2703
|
Error: ${toolResult.error}` : `Error: ${toolResult.error}`
|
|
@@ -2937,7 +2968,7 @@ ${CONTINUE_PROMPT}` : CONTINUE_PROMPT;
|
|
|
2937
2968
|
sessionManager.setCurrentContextSize(sessionId, result.usage.promptTokens);
|
|
2938
2969
|
if (!compacting) {
|
|
2939
2970
|
const contextState2 = sessionManager.getContextState(sessionId);
|
|
2940
|
-
const { shouldCompact, appendCompactionPrompt } = await import("./compactor-
|
|
2971
|
+
const { shouldCompact, appendCompactionPrompt } = await import("./compactor-V43WJ3WX.js");
|
|
2941
2972
|
if (shouldCompact(contextState2.currentTokens, contextState2.maxTokens, runtimeConfig.context.compactionThreshold)) {
|
|
2942
2973
|
appendCompactionPrompt(sessionId, append);
|
|
2943
2974
|
compacting = true;
|
|
@@ -3018,7 +3049,7 @@ ${COMPACTION_PROMPT}`,
|
|
|
3018
3049
|
toolRegistry,
|
|
3019
3050
|
sessionManager,
|
|
3020
3051
|
sessionId,
|
|
3021
|
-
workdir: session.workdir,
|
|
3052
|
+
workdir: session.worktree ?? session.workdir,
|
|
3022
3053
|
turnMetrics,
|
|
3023
3054
|
signal,
|
|
3024
3055
|
onMessage,
|
|
@@ -3727,13 +3758,14 @@ async function executeSubAgent(options) {
|
|
|
3727
3758
|
})
|
|
3728
3759
|
);
|
|
3729
3760
|
}
|
|
3730
|
-
const
|
|
3761
|
+
const effectiveWorkdir = session.worktree ?? session.workdir;
|
|
3762
|
+
const { content: instructionContent } = await getAllInstructions(effectiveWorkdir, session.projectId);
|
|
3731
3763
|
const config = getRuntimeConfig();
|
|
3732
3764
|
const configDir = getGlobalConfigDir(config.mode ?? "production");
|
|
3733
3765
|
const skills = await getEnabledSkillMetadata(configDir, config.workdir);
|
|
3734
3766
|
const hasRunCommand = agentDef.metadata.allowedTools?.includes("run_command") ?? false;
|
|
3735
|
-
const gitignoreSection = hasRunCommand ? await loadGitIgnoreRules(
|
|
3736
|
-
const systemPrompt = buildBasePrompt(
|
|
3767
|
+
const gitignoreSection = hasRunCommand ? await loadGitIgnoreRules(effectiveWorkdir) : "";
|
|
3768
|
+
const systemPrompt = buildBasePrompt(effectiveWorkdir, void 0, skills.length > 0 ? skills : void 0, llmClient.getModel()) + "\n\n" + agentDef.prompt + (gitignoreSection ? "\n\n" + gitignoreSection : "") + RETURN_VALUE_INSTRUCTION;
|
|
3737
3769
|
const subAgentScope = { type: "subagent", sessionId, subAgentId, subAgentType };
|
|
3738
3770
|
const loopResult = await runTopLevelAgentLoop(
|
|
3739
3771
|
{
|
|
@@ -3841,7 +3873,7 @@ var callSubAgentTool = {
|
|
|
3841
3873
|
};
|
|
3842
3874
|
}
|
|
3843
3875
|
try {
|
|
3844
|
-
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-
|
|
3876
|
+
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-2EB7C6H2.js");
|
|
3845
3877
|
const toolRegistry = getToolRegistryForAgent2(agentDef);
|
|
3846
3878
|
const turnMetrics = new TurnMetrics();
|
|
3847
3879
|
const result = await executeSubAgent({
|
|
@@ -4128,8 +4160,12 @@ var webFetchTool = createTool(
|
|
|
4128
4160
|
import { spawn as spawn3 } from "child_process";
|
|
4129
4161
|
import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
|
|
4130
4162
|
import { resolve as resolve5, join as join8 } from "path";
|
|
4163
|
+
import net from "net";
|
|
4131
4164
|
var MAX_LOG_LINES = 2e3;
|
|
4132
4165
|
var MAX_LOG_BYTES = 1e5;
|
|
4166
|
+
var DEFAULT_PROBE_PORT = 3e3;
|
|
4167
|
+
var MAX_PORT_SCAN = 200;
|
|
4168
|
+
var PROBE_TIMEOUT_MS = 300;
|
|
4133
4169
|
var getDevServerConfigPath = (workdir) => {
|
|
4134
4170
|
const mode = getRuntimeConfig().mode ?? "production";
|
|
4135
4171
|
return join8(resolve5(workdir), ".openfox", `${mode === "development" ? "dev-dev" : "dev"}.json`);
|
|
@@ -4150,6 +4186,9 @@ function createInstance() {
|
|
|
4150
4186
|
process: null,
|
|
4151
4187
|
state: "off",
|
|
4152
4188
|
config: null,
|
|
4189
|
+
resolvedUrl: null,
|
|
4190
|
+
resolvedCommand: null,
|
|
4191
|
+
assignedPort: null,
|
|
4153
4192
|
logs: [],
|
|
4154
4193
|
totalLogBytes: 0,
|
|
4155
4194
|
errorMessage: void 0,
|
|
@@ -4158,6 +4197,17 @@ function createInstance() {
|
|
|
4158
4197
|
proxyCleanup: null
|
|
4159
4198
|
};
|
|
4160
4199
|
}
|
|
4200
|
+
function parsePortFromUrl(url) {
|
|
4201
|
+
const substituted = url.replace(/\$\{PORT\}/g, String(DEFAULT_PROBE_PORT));
|
|
4202
|
+
try {
|
|
4203
|
+
const parsed = new URL(substituted);
|
|
4204
|
+
const port = parsed.port ? parseInt(parsed.port, 10) : DEFAULT_PROBE_PORT;
|
|
4205
|
+
return { hostname: parsed.hostname, port: isNaN(port) ? DEFAULT_PROBE_PORT : port };
|
|
4206
|
+
} catch {
|
|
4207
|
+
logger.warn("Failed to parse dev server URL, falling back to defaults", { url });
|
|
4208
|
+
return { hostname: "127.0.0.1", port: DEFAULT_PROBE_PORT };
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4161
4211
|
var DevServerManager = class {
|
|
4162
4212
|
instances = /* @__PURE__ */ new Map();
|
|
4163
4213
|
outputListeners = /* @__PURE__ */ new Set();
|
|
@@ -4190,21 +4240,78 @@ var DevServerManager = class {
|
|
|
4190
4240
|
listener(resolved, state, errorMessage);
|
|
4191
4241
|
}
|
|
4192
4242
|
}
|
|
4243
|
+
/** Probe whether a TCP port is in use */
|
|
4244
|
+
async probePort(host, port) {
|
|
4245
|
+
return new Promise((resolvePromise) => {
|
|
4246
|
+
const socket = new net.Socket();
|
|
4247
|
+
socket.setTimeout(PROBE_TIMEOUT_MS);
|
|
4248
|
+
socket.on("connect", () => {
|
|
4249
|
+
socket.destroy();
|
|
4250
|
+
resolvePromise(true);
|
|
4251
|
+
});
|
|
4252
|
+
socket.on("timeout", () => {
|
|
4253
|
+
socket.destroy();
|
|
4254
|
+
resolvePromise(false);
|
|
4255
|
+
});
|
|
4256
|
+
socket.on("error", () => {
|
|
4257
|
+
socket.destroy();
|
|
4258
|
+
resolvePromise(false);
|
|
4259
|
+
});
|
|
4260
|
+
socket.connect(port, host);
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4263
|
+
/**
|
|
4264
|
+
* Find a free port starting from preferred, scanning upward.
|
|
4265
|
+
* Throws if no free port found within range.
|
|
4266
|
+
*/
|
|
4267
|
+
async findFreePort(host, preferred) {
|
|
4268
|
+
const inUse = await this.probePort(host, preferred);
|
|
4269
|
+
if (!inUse) return preferred;
|
|
4270
|
+
const maxPort = Math.min(preferred + MAX_PORT_SCAN, 65535);
|
|
4271
|
+
for (let port = preferred + 1; port <= maxPort; port++) {
|
|
4272
|
+
const taken = await this.probePort(host, port);
|
|
4273
|
+
if (!taken) return port;
|
|
4274
|
+
}
|
|
4275
|
+
throw new Error(
|
|
4276
|
+
`No free port found in range ${preferred}-${maxPort} on ${host}. Close some applications or configure a different port in .openfox/dev.json`
|
|
4277
|
+
);
|
|
4278
|
+
}
|
|
4279
|
+
/** Substitute ${PORT} placeholders with actual port number */
|
|
4280
|
+
substitutePort(template, port) {
|
|
4281
|
+
return template.replace(/\$\{PORT\}/g, String(port));
|
|
4282
|
+
}
|
|
4283
|
+
/**
|
|
4284
|
+
* Load dev server config from workdir.
|
|
4285
|
+
* If not found and the path looks like a git worktree (contains /worktrees/),
|
|
4286
|
+
* falls back to the parent project root.
|
|
4287
|
+
*/
|
|
4193
4288
|
async loadConfig(workdir) {
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4289
|
+
const tryLoad = async (dir) => {
|
|
4290
|
+
try {
|
|
4291
|
+
const configPath = getDevServerConfigPath(dir);
|
|
4292
|
+
const raw = await readFile9(configPath, "utf-8");
|
|
4293
|
+
const parsed = JSON.parse(raw);
|
|
4294
|
+
if (!parsed.command || !parsed.url) return null;
|
|
4295
|
+
return {
|
|
4296
|
+
command: parsed.command,
|
|
4297
|
+
url: parsed.url,
|
|
4298
|
+
hotReload: parsed.hotReload ?? false,
|
|
4299
|
+
disableInspect: parsed.disableInspect ?? false
|
|
4300
|
+
};
|
|
4301
|
+
} catch {
|
|
4302
|
+
return null;
|
|
4303
|
+
}
|
|
4304
|
+
};
|
|
4305
|
+
const config = await tryLoad(workdir);
|
|
4306
|
+
if (config) return config;
|
|
4307
|
+
const wtIdx = workdir.indexOf("/worktrees/");
|
|
4308
|
+
if (wtIdx !== -1) {
|
|
4309
|
+
const projectRoot = workdir.slice(0, wtIdx);
|
|
4310
|
+
if (projectRoot !== workdir) {
|
|
4311
|
+
return tryLoad(projectRoot);
|
|
4312
|
+
}
|
|
4207
4313
|
}
|
|
4314
|
+
return null;
|
|
4208
4315
|
}
|
|
4209
4316
|
async saveConfig(workdir, config) {
|
|
4210
4317
|
const resolved = this.resolveWorkdir(workdir);
|
|
@@ -4222,24 +4329,31 @@ var DevServerManager = class {
|
|
|
4222
4329
|
if (!config) {
|
|
4223
4330
|
return this.getStatus(workdir);
|
|
4224
4331
|
}
|
|
4332
|
+
const { hostname, port: configuredPort } = parsePortFromUrl(config.url);
|
|
4333
|
+
const assignedPort = await this.findFreePort(hostname, configuredPort);
|
|
4334
|
+
instance.assignedPort = assignedPort;
|
|
4335
|
+
const resolvedCommand = this.substitutePort(config.command, assignedPort);
|
|
4336
|
+
const resolvedUrl = this.substitutePort(config.url, assignedPort);
|
|
4337
|
+
instance.resolvedCommand = resolvedCommand;
|
|
4338
|
+
instance.resolvedUrl = resolvedUrl;
|
|
4225
4339
|
instance.config = config;
|
|
4226
4340
|
instance.logs = [];
|
|
4227
4341
|
instance.totalLogBytes = 0;
|
|
4228
4342
|
instance.errorMessage = void 0;
|
|
4229
4343
|
instance.exited = false;
|
|
4230
|
-
if (!config.disableInspect &&
|
|
4344
|
+
if (!config.disableInspect && resolvedUrl && this._sessionManager) {
|
|
4231
4345
|
try {
|
|
4232
|
-
const { port, cleanup } = startInspectProxy(
|
|
4346
|
+
const { port, cleanup } = startInspectProxy(resolvedUrl, this._sessionManager, workdir);
|
|
4233
4347
|
instance.inspectProxyPort = port;
|
|
4234
4348
|
instance.proxyCleanup = cleanup;
|
|
4235
|
-
logger.debug("Inspect proxy started", { workdir, port, target:
|
|
4349
|
+
logger.debug("Inspect proxy started", { workdir, port, target: resolvedUrl });
|
|
4236
4350
|
} catch (err) {
|
|
4237
4351
|
logger.warn("Failed to start inspect proxy", { workdir, error: err });
|
|
4238
4352
|
}
|
|
4239
4353
|
}
|
|
4240
4354
|
const resolved = this.resolveWorkdir(workdir);
|
|
4241
4355
|
const shell = getPlatformShell();
|
|
4242
|
-
const proc = spawn3(shell.command, [...shell.args,
|
|
4356
|
+
const proc = spawn3(shell.command, [...shell.args, resolvedCommand], {
|
|
4243
4357
|
cwd: resolved,
|
|
4244
4358
|
env: { ...process.env, FORCE_COLOR: "1" },
|
|
4245
4359
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -4297,7 +4411,7 @@ ${recentLogs}`.trim();
|
|
|
4297
4411
|
instance.state = "running";
|
|
4298
4412
|
instance.errorMessage = void 0;
|
|
4299
4413
|
this.emitStateChange(workdir, "running", void 0);
|
|
4300
|
-
logger.info("Dev server started", { workdir, command:
|
|
4414
|
+
logger.info("Dev server started", { workdir, command: resolvedCommand, port: assignedPort });
|
|
4301
4415
|
return this.getStatus(workdir);
|
|
4302
4416
|
}
|
|
4303
4417
|
async stop(workdir) {
|
|
@@ -4326,7 +4440,7 @@ ${recentLogs}`.trim();
|
|
|
4326
4440
|
const instance = this.getInstance(workdir);
|
|
4327
4441
|
return {
|
|
4328
4442
|
state: instance.state,
|
|
4329
|
-
url: instance.config?.url ?? null,
|
|
4443
|
+
url: instance.resolvedUrl ?? instance.config?.url ?? null,
|
|
4330
4444
|
hotReload: instance.config?.hotReload ?? false,
|
|
4331
4445
|
config: instance.config,
|
|
4332
4446
|
errorMessage: instance.errorMessage,
|
|
@@ -4372,7 +4486,7 @@ var devServerTool = createTool(
|
|
|
4372
4486
|
type: "function",
|
|
4373
4487
|
function: {
|
|
4374
4488
|
name: "dev_server",
|
|
4375
|
-
description: "Control the project dev server. Start, stop, restart, check status, or fetch logs with optional pagination. The dev server is configured via .openfox/dev.json in the project root with fields: command (string, required), url (string, required), hotReload (boolean, optional, default false), disableInspect (boolean, optional, default false).",
|
|
4489
|
+
description: "Control the project dev server. Start, stop, restart, check status, or fetch logs with optional pagination. The dev server is configured via .openfox/dev.json in the project root with fields: command (string, required), url (string, required), hotReload (boolean, optional, default false), disableInspect (boolean, optional, default false). You can use ${PORT} in command and url \u2014 it will be replaced with an available port at runtime (auto-assigned if the configured port is taken).",
|
|
4376
4490
|
parameters: {
|
|
4377
4491
|
type: "object",
|
|
4378
4492
|
properties: {
|
|
@@ -4763,7 +4877,7 @@ var mcpConfigTool = createTool(
|
|
|
4763
4877
|
mcpNotifyChanged?.(sessionId);
|
|
4764
4878
|
}
|
|
4765
4879
|
async function rebuildTools() {
|
|
4766
|
-
const { setMcpTools: setMcpTools2 } = await import("./tools-
|
|
4880
|
+
const { setMcpTools: setMcpTools2 } = await import("./tools-2EB7C6H2.js");
|
|
4767
4881
|
const mcpTools = createMcpTools(mcpManagerForTools);
|
|
4768
4882
|
setMcpTools2(mcpTools);
|
|
4769
4883
|
}
|
|
@@ -4861,270 +4975,6 @@ var mcpConfigTool = createTool(
|
|
|
4861
4975
|
}
|
|
4862
4976
|
);
|
|
4863
4977
|
|
|
4864
|
-
// src/server/tools/trace-code.ts
|
|
4865
|
-
import { stat as stat2, readFile as readFile10 } from "fs/promises";
|
|
4866
|
-
function graphNode(location, depth, relation, symbolName, symbolKind) {
|
|
4867
|
-
const node = { location, depth, relation, symbolName };
|
|
4868
|
-
if (symbolKind) node.symbolKind = symbolKind;
|
|
4869
|
-
return node;
|
|
4870
|
-
}
|
|
4871
|
-
var MAX_DEPTH = 5;
|
|
4872
|
-
var VALID_DIRECTIONS = ["up", "down", "both"];
|
|
4873
|
-
var SNIPPET_RADIUS = 2;
|
|
4874
|
-
var CACHE_MAX_SIZE = 50;
|
|
4875
|
-
function locationKey(loc) {
|
|
4876
|
-
return `${loc.path}:${loc.line}:${loc.character}`;
|
|
4877
|
-
}
|
|
4878
|
-
function formatLocation(loc, workdir) {
|
|
4879
|
-
const relPath = loc.path.startsWith(workdir) ? loc.path.slice(workdir.length + 1) : loc.path;
|
|
4880
|
-
return `${relPath}:${loc.line + 1}:${loc.character}`;
|
|
4881
|
-
}
|
|
4882
|
-
var fileCache = /* @__PURE__ */ new Map();
|
|
4883
|
-
async function getCachedLines(path, approvedPaths) {
|
|
4884
|
-
if (!approvedPaths.has(path)) return [];
|
|
4885
|
-
const cached = fileCache.get(path);
|
|
4886
|
-
if (cached) {
|
|
4887
|
-
try {
|
|
4888
|
-
const stats = await stat2(path);
|
|
4889
|
-
if (stats.mtimeMs === cached.mtimeMs) {
|
|
4890
|
-
return cached.lines;
|
|
4891
|
-
}
|
|
4892
|
-
} catch {
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
try {
|
|
4896
|
-
const stats = await stat2(path);
|
|
4897
|
-
const content = await readFile10(path, "utf-8");
|
|
4898
|
-
const lines = content.split("\n");
|
|
4899
|
-
fileCache.set(path, { mtimeMs: stats.mtimeMs, lines });
|
|
4900
|
-
if (fileCache.size > CACHE_MAX_SIZE) {
|
|
4901
|
-
const oldest = fileCache.keys().next();
|
|
4902
|
-
if (!oldest.done && oldest.value !== void 0) {
|
|
4903
|
-
fileCache.delete(oldest.value);
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4906
|
-
return lines;
|
|
4907
|
-
} catch {
|
|
4908
|
-
return [];
|
|
4909
|
-
}
|
|
4910
|
-
}
|
|
4911
|
-
async function getSnippet(path, line, approvedPaths, radius = SNIPPET_RADIUS) {
|
|
4912
|
-
const lines = await getCachedLines(path, approvedPaths);
|
|
4913
|
-
if (lines.length === 0) return "";
|
|
4914
|
-
const start = Math.max(0, line - radius);
|
|
4915
|
-
const end = Math.min(lines.length - 1, line + radius);
|
|
4916
|
-
const lineNumWidth = String(end + 1).length;
|
|
4917
|
-
const result = [];
|
|
4918
|
-
for (let i = start; i <= end; i++) {
|
|
4919
|
-
const gutter = i === line ? ">" : " ";
|
|
4920
|
-
const lineNum = String(i + 1).padStart(lineNumWidth);
|
|
4921
|
-
result.push(`${gutter} ${lineNum}\u2502 ${lines[i] ?? ""}`);
|
|
4922
|
-
}
|
|
4923
|
-
return result.join("\n");
|
|
4924
|
-
}
|
|
4925
|
-
function formatEdgeLabel(relation) {
|
|
4926
|
-
switch (relation) {
|
|
4927
|
-
case "definition":
|
|
4928
|
-
return "definition";
|
|
4929
|
-
case "references":
|
|
4930
|
-
return "reference";
|
|
4931
|
-
case "type-definition":
|
|
4932
|
-
return "type definition";
|
|
4933
|
-
default:
|
|
4934
|
-
return relation;
|
|
4935
|
-
}
|
|
4936
|
-
}
|
|
4937
|
-
async function collectNodes(lsp, startLocations, symbolName, symbolKind, maxDepth, direction) {
|
|
4938
|
-
const visited = /* @__PURE__ */ new Set();
|
|
4939
|
-
const nodes = [];
|
|
4940
|
-
const edges = [];
|
|
4941
|
-
const queue = startLocations.map((loc) => ({ location: loc, depth: 0, relation: "match" }));
|
|
4942
|
-
let idx = 0;
|
|
4943
|
-
while (idx < queue.length) {
|
|
4944
|
-
const item = queue[idx];
|
|
4945
|
-
idx++;
|
|
4946
|
-
const key = locationKey(item.location);
|
|
4947
|
-
if (visited.has(key)) continue;
|
|
4948
|
-
visited.add(key);
|
|
4949
|
-
nodes.push(graphNode(item.location, item.depth, item.relation, symbolName, symbolKind));
|
|
4950
|
-
if (item.parentKey) {
|
|
4951
|
-
const relation = item.relation === "definition" ? "definition" : item.relation === "type-definition" ? "type-definition" : "references";
|
|
4952
|
-
edges.push({ from: item.parentKey, to: key, relation });
|
|
4953
|
-
}
|
|
4954
|
-
if (item.depth >= maxDepth) continue;
|
|
4955
|
-
const { path, line, character } = item.location;
|
|
4956
|
-
if (direction === "down" || direction === "both") {
|
|
4957
|
-
const defs = await lsp.getDefinition(path, line, character);
|
|
4958
|
-
for (const def of defs) {
|
|
4959
|
-
const defKey = locationKey(def);
|
|
4960
|
-
if (!visited.has(defKey)) {
|
|
4961
|
-
queue.push({ location: def, depth: item.depth + 1, relation: "definition", parentKey: key });
|
|
4962
|
-
}
|
|
4963
|
-
}
|
|
4964
|
-
const typeDefs = await lsp.getTypeDefinition(path, line, character);
|
|
4965
|
-
for (const td of typeDefs) {
|
|
4966
|
-
const tdKey = locationKey(td);
|
|
4967
|
-
if (!visited.has(tdKey)) {
|
|
4968
|
-
queue.push({ location: td, depth: item.depth + 1, relation: "type-definition", parentKey: key });
|
|
4969
|
-
}
|
|
4970
|
-
}
|
|
4971
|
-
}
|
|
4972
|
-
if (direction === "up" || direction === "both") {
|
|
4973
|
-
const refs = await lsp.getReferences(path, line, character);
|
|
4974
|
-
for (const ref of refs) {
|
|
4975
|
-
const refKey = locationKey(ref);
|
|
4976
|
-
if (!visited.has(refKey)) {
|
|
4977
|
-
queue.push({ location: ref, depth: item.depth + 1, relation: "reference", parentKey: key });
|
|
4978
|
-
}
|
|
4979
|
-
}
|
|
4980
|
-
}
|
|
4981
|
-
}
|
|
4982
|
-
return { nodes, edges };
|
|
4983
|
-
}
|
|
4984
|
-
async function formatOutput(symbolName, direction, depth, nodes, edges, workdir, approvedPaths) {
|
|
4985
|
-
const lines = [];
|
|
4986
|
-
lines.push(`Symbol: ${symbolName}`);
|
|
4987
|
-
lines.push(`Direction: ${direction} | Depth: ${depth}`);
|
|
4988
|
-
lines.push(`Nodes: ${nodes.length} | Edges: ${edges.length}`);
|
|
4989
|
-
lines.push("");
|
|
4990
|
-
if (nodes.length === 0) {
|
|
4991
|
-
lines.push("No results found.");
|
|
4992
|
-
return lines.join("\n");
|
|
4993
|
-
}
|
|
4994
|
-
const byDepth = /* @__PURE__ */ new Map();
|
|
4995
|
-
for (const node of nodes) {
|
|
4996
|
-
const group = byDepth.get(node.depth) ?? [];
|
|
4997
|
-
group.push(node);
|
|
4998
|
-
byDepth.set(node.depth, group);
|
|
4999
|
-
}
|
|
5000
|
-
for (const [depth2, group] of [...byDepth.entries()].sort(([a], [b]) => a - b)) {
|
|
5001
|
-
lines.push(`\u2500\u2500 Depth ${depth2} \u2500\u2500`);
|
|
5002
|
-
const fileGroups = /* @__PURE__ */ new Map();
|
|
5003
|
-
for (const node of group) {
|
|
5004
|
-
const fileKey = node.location.path;
|
|
5005
|
-
const fileGroup = fileGroups.get(fileKey) ?? [];
|
|
5006
|
-
fileGroup.push(node);
|
|
5007
|
-
fileGroups.set(fileKey, fileGroup);
|
|
5008
|
-
}
|
|
5009
|
-
for (const [filePath, fileNodes] of fileGroups) {
|
|
5010
|
-
const relPath = filePath.startsWith(workdir) ? filePath.slice(workdir.length + 1) : filePath;
|
|
5011
|
-
if (fileNodes.length > 5) {
|
|
5012
|
-
const kinds = [...new Set(fileNodes.map((n) => formatEdgeLabel(n.relation)))];
|
|
5013
|
-
lines.push(` ${kinds.join(", ")} \xD7 ${fileNodes.length} in ${relPath}`);
|
|
5014
|
-
} else {
|
|
5015
|
-
for (const node of fileNodes) {
|
|
5016
|
-
const label = formatEdgeLabel(node.relation);
|
|
5017
|
-
const kind = node.symbolKind ? ` (${node.symbolKind})` : "";
|
|
5018
|
-
const loc = formatLocation(node.location, workdir);
|
|
5019
|
-
lines.push(` ${label}: ${loc}${kind}`);
|
|
5020
|
-
const snippet = await getSnippet(node.location.path, node.location.line, approvedPaths);
|
|
5021
|
-
if (snippet) {
|
|
5022
|
-
for (const snipLine of snippet.split("\n")) {
|
|
5023
|
-
lines.push(` ${snipLine}`);
|
|
5024
|
-
}
|
|
5025
|
-
}
|
|
5026
|
-
}
|
|
5027
|
-
}
|
|
5028
|
-
}
|
|
5029
|
-
lines.push("");
|
|
5030
|
-
}
|
|
5031
|
-
if (edges.length > 0 && edges.length <= 20) {
|
|
5032
|
-
lines.push("Edges:");
|
|
5033
|
-
for (const edge of edges) {
|
|
5034
|
-
lines.push(` ${edge.from} \u2500\u2500${edge.relation}\u2500\u2500\u25B6 ${edge.to}`);
|
|
5035
|
-
}
|
|
5036
|
-
lines.push("");
|
|
5037
|
-
} else if (edges.length > 20) {
|
|
5038
|
-
lines.push(`Edges: ${edges.length} total (suppressed, too many to display individually)`);
|
|
5039
|
-
lines.push("");
|
|
5040
|
-
}
|
|
5041
|
-
return lines.join("\n");
|
|
5042
|
-
}
|
|
5043
|
-
var traceCodeTool = createTool(
|
|
5044
|
-
"trace_code",
|
|
5045
|
-
{
|
|
5046
|
-
type: "function",
|
|
5047
|
-
function: {
|
|
5048
|
-
name: "trace_code",
|
|
5049
|
-
description: 'Trace a symbol through the codebase using LSP. Finds definitions, references, and type definitions up to a configurable depth. The "file" param is just a seed for LSP \u2014 use any file where you saw the symbol, it searches the whole project.',
|
|
5050
|
-
parameters: {
|
|
5051
|
-
type: "object",
|
|
5052
|
-
properties: {
|
|
5053
|
-
symbol: {
|
|
5054
|
-
type: "string",
|
|
5055
|
-
description: 'Symbol name to trace (e.g., "rebuildTools", "handleSubmit", "UserProfile")'
|
|
5056
|
-
},
|
|
5057
|
-
file: {
|
|
5058
|
-
type: "string",
|
|
5059
|
-
description: "Seed file for LSP (any file referencing the symbol). Determines language. Not required to be the definition file. Relative to workdir."
|
|
5060
|
-
},
|
|
5061
|
-
depth: {
|
|
5062
|
-
type: "number",
|
|
5063
|
-
description: "Graph traversal depth (default: 1, max: 5). How many hops to follow. Start with 1 for immediate defs+refs.",
|
|
5064
|
-
default: 1
|
|
5065
|
-
},
|
|
5066
|
-
direction: {
|
|
5067
|
-
type: "string",
|
|
5068
|
-
enum: ["up", "down", "both"],
|
|
5069
|
-
description: '"down" follows definitions, "up" finds references, "both" does both (default).',
|
|
5070
|
-
default: "both"
|
|
5071
|
-
}
|
|
5072
|
-
},
|
|
5073
|
-
required: ["symbol", "file"]
|
|
5074
|
-
}
|
|
5075
|
-
}
|
|
5076
|
-
},
|
|
5077
|
-
async (args, context, helpers) => {
|
|
5078
|
-
const symbol = args.symbol?.trim();
|
|
5079
|
-
if (!symbol) {
|
|
5080
|
-
return helpers.error("symbol is required");
|
|
5081
|
-
}
|
|
5082
|
-
const file = args.file?.trim();
|
|
5083
|
-
if (!file) {
|
|
5084
|
-
return helpers.error("file is required");
|
|
5085
|
-
}
|
|
5086
|
-
const depth = args.depth ?? 1;
|
|
5087
|
-
if (depth > MAX_DEPTH) {
|
|
5088
|
-
return helpers.error(`depth cannot exceed ${MAX_DEPTH}`);
|
|
5089
|
-
}
|
|
5090
|
-
const direction = args.direction ?? "both";
|
|
5091
|
-
if (!VALID_DIRECTIONS.includes(direction)) {
|
|
5092
|
-
return helpers.error(`direction must be one of: ${VALID_DIRECTIONS.join(", ")}`);
|
|
5093
|
-
}
|
|
5094
|
-
const lsp = context.lspManager;
|
|
5095
|
-
if (!lsp) {
|
|
5096
|
-
return helpers.error("LSP is not available. The trace_code tool requires a running LSP server.");
|
|
5097
|
-
}
|
|
5098
|
-
const fullPath = helpers.resolvePath(file);
|
|
5099
|
-
try {
|
|
5100
|
-
await stat2(fullPath);
|
|
5101
|
-
} catch {
|
|
5102
|
-
return helpers.error(`File not found: "${file}". Check that the path exists.`);
|
|
5103
|
-
}
|
|
5104
|
-
let symbols;
|
|
5105
|
-
try {
|
|
5106
|
-
symbols = await lsp.seedAndFindWorkspaceSymbol(symbol, fullPath);
|
|
5107
|
-
} catch {
|
|
5108
|
-
return helpers.error(`Failed to search for symbol "${symbol}". LSP may not be responding.`);
|
|
5109
|
-
}
|
|
5110
|
-
if (symbols.length === 0) {
|
|
5111
|
-
const installHint = lsp.getInstallHint(fullPath);
|
|
5112
|
-
const hint = installHint ? ` ${installHint}` : "";
|
|
5113
|
-
return helpers.error(
|
|
5114
|
-
`Symbol "${symbol}" not found in "${file}". Check that the symbol name is correct and that the LSP server has indexed the project.${hint}`
|
|
5115
|
-
);
|
|
5116
|
-
}
|
|
5117
|
-
const startLocations = symbols.map((s) => s.location);
|
|
5118
|
-
const symbolKind = symbols[0].kind;
|
|
5119
|
-
const { nodes, edges } = await collectNodes(lsp, startLocations, symbol, symbolKind, depth, direction);
|
|
5120
|
-
const allPaths = [...new Set(nodes.map((n) => n.location.path))];
|
|
5121
|
-
await helpers.checkPathAccess(allPaths);
|
|
5122
|
-
const approvedPaths = new Set(allPaths);
|
|
5123
|
-
const output = await formatOutput(symbol, direction, depth, nodes, edges, context.workdir, approvedPaths);
|
|
5124
|
-
return helpers.success(output);
|
|
5125
|
-
}
|
|
5126
|
-
);
|
|
5127
|
-
|
|
5128
4978
|
// src/server/tools/web-search.ts
|
|
5129
4979
|
var DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
5130
4980
|
var DEFAULT_MAX_RESULTS = 5;
|
|
@@ -5260,6 +5110,120 @@ var webSearchTool = createTool(
|
|
|
5260
5110
|
}
|
|
5261
5111
|
);
|
|
5262
5112
|
|
|
5113
|
+
// src/server/tools/worktree.ts
|
|
5114
|
+
var worktreeTool = createTool(
|
|
5115
|
+
"worktree",
|
|
5116
|
+
{
|
|
5117
|
+
type: "function",
|
|
5118
|
+
function: {
|
|
5119
|
+
name: "worktree",
|
|
5120
|
+
description: "Manage git worktrees for the current session. Use this to work on isolated branches without affecting the main working tree.\n\nActions:\n- list_branches: List local git branches for the current project\n- create: Create a new branch + git worktree and attach this session to it (provide: name)\n- attach: Attach this session to an existing git worktree by path (provide: path)\n- close: Close the current worktree and return the session to the project root\n- status: Show current worktree state (active branch, worktree path)",
|
|
5121
|
+
parameters: {
|
|
5122
|
+
type: "object",
|
|
5123
|
+
properties: {
|
|
5124
|
+
action: {
|
|
5125
|
+
type: "string",
|
|
5126
|
+
enum: ["list_branches", "create", "attach", "close", "status"],
|
|
5127
|
+
description: "The action to perform"
|
|
5128
|
+
},
|
|
5129
|
+
name: {
|
|
5130
|
+
type: "string",
|
|
5131
|
+
description: "Branch name for creating a new worktree (required for action=create)"
|
|
5132
|
+
},
|
|
5133
|
+
path: {
|
|
5134
|
+
type: "string",
|
|
5135
|
+
description: "Path to an existing worktree directory (required for action=attach)"
|
|
5136
|
+
}
|
|
5137
|
+
},
|
|
5138
|
+
required: ["action"]
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
},
|
|
5142
|
+
async (args, context, helpers) => {
|
|
5143
|
+
const { sessionId, sessionManager } = context;
|
|
5144
|
+
const validActions = ["list_branches", "create", "attach", "close", "status"];
|
|
5145
|
+
if (!validActions.includes(args.action)) {
|
|
5146
|
+
return helpers.error(`Invalid action: ${args.action}. Must be one of: ${validActions.join(", ")}`);
|
|
5147
|
+
}
|
|
5148
|
+
switch (args.action) {
|
|
5149
|
+
case "list_branches": {
|
|
5150
|
+
const session = sessionManager.getSession(sessionId);
|
|
5151
|
+
if (!session) return helpers.error("Session not found");
|
|
5152
|
+
const project = sessionManager.getProject(session.projectId);
|
|
5153
|
+
if (!project) return helpers.error("Project not found");
|
|
5154
|
+
const branches = await listBranches(project.workdir);
|
|
5155
|
+
return helpers.success(JSON.stringify({ branches }, null, 2));
|
|
5156
|
+
}
|
|
5157
|
+
case "create": {
|
|
5158
|
+
if (!args.name || typeof args.name !== "string") {
|
|
5159
|
+
return helpers.error('Parameter "name" is required for action=create (the branch name)');
|
|
5160
|
+
}
|
|
5161
|
+
const updated = await sessionManager.createSessionWorktree(sessionId, args.name);
|
|
5162
|
+
return helpers.success(
|
|
5163
|
+
JSON.stringify(
|
|
5164
|
+
{
|
|
5165
|
+
worktree: updated.worktree,
|
|
5166
|
+
branch: args.name,
|
|
5167
|
+
message: `Worktree created and session attached to branch "${args.name}"`
|
|
5168
|
+
},
|
|
5169
|
+
null,
|
|
5170
|
+
2
|
|
5171
|
+
)
|
|
5172
|
+
);
|
|
5173
|
+
}
|
|
5174
|
+
case "attach": {
|
|
5175
|
+
if (!args.path || typeof args.path !== "string") {
|
|
5176
|
+
return helpers.error('Parameter "path" is required for action=attach (path to existing worktree)');
|
|
5177
|
+
}
|
|
5178
|
+
const updated = await sessionManager.attachSessionWorktree(sessionId, args.path);
|
|
5179
|
+
return helpers.success(
|
|
5180
|
+
JSON.stringify(
|
|
5181
|
+
{
|
|
5182
|
+
worktree: updated.worktree,
|
|
5183
|
+
message: `Session attached to existing worktree at ${args.path}`
|
|
5184
|
+
},
|
|
5185
|
+
null,
|
|
5186
|
+
2
|
|
5187
|
+
)
|
|
5188
|
+
);
|
|
5189
|
+
}
|
|
5190
|
+
case "close": {
|
|
5191
|
+
const updated = await sessionManager.closeSessionWorktree(sessionId);
|
|
5192
|
+
return helpers.success(
|
|
5193
|
+
JSON.stringify(
|
|
5194
|
+
{
|
|
5195
|
+
worktree: updated.worktree,
|
|
5196
|
+
workdir: updated.workdir,
|
|
5197
|
+
message: "Worktree closed. Session returned to project root."
|
|
5198
|
+
},
|
|
5199
|
+
null,
|
|
5200
|
+
2
|
|
5201
|
+
)
|
|
5202
|
+
);
|
|
5203
|
+
}
|
|
5204
|
+
case "status": {
|
|
5205
|
+
const session = sessionManager.getSession(sessionId);
|
|
5206
|
+
if (!session) return helpers.error("Session not found");
|
|
5207
|
+
const active = !!session.worktree;
|
|
5208
|
+
return helpers.success(
|
|
5209
|
+
JSON.stringify(
|
|
5210
|
+
{
|
|
5211
|
+
active,
|
|
5212
|
+
worktree: session.worktree ?? null,
|
|
5213
|
+
workdir: session.workdir,
|
|
5214
|
+
...active ? { branch: session.worktree?.split("/worktrees/")[1] ?? session.worktree } : {}
|
|
5215
|
+
},
|
|
5216
|
+
null,
|
|
5217
|
+
2
|
|
5218
|
+
)
|
|
5219
|
+
);
|
|
5220
|
+
}
|
|
5221
|
+
default:
|
|
5222
|
+
return helpers.error(`Unknown action: ${args.action}`);
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
);
|
|
5226
|
+
|
|
5263
5227
|
// src/server/tools/index.ts
|
|
5264
5228
|
var _builtInTools;
|
|
5265
5229
|
function getBuiltInTools() {
|
|
@@ -5280,7 +5244,7 @@ function getBuiltInTools() {
|
|
|
5280
5244
|
stepDoneTool,
|
|
5281
5245
|
backgroundProcessTool,
|
|
5282
5246
|
mcpConfigTool,
|
|
5283
|
-
|
|
5247
|
+
worktreeTool
|
|
5284
5248
|
];
|
|
5285
5249
|
}
|
|
5286
5250
|
return _builtInTools;
|
|
@@ -5578,4 +5542,4 @@ export {
|
|
|
5578
5542
|
getToolRegistryForAgent,
|
|
5579
5543
|
createToolRegistry
|
|
5580
5544
|
};
|
|
5581
|
-
//# sourceMappingURL=chunk-
|
|
5545
|
+
//# sourceMappingURL=chunk-3ACDQKK6.js.map
|