openfox 2.0.65 → 2.0.67
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-72DFHK5K.js} +16 -13
- package/dist/{chunk-Z6H7SFQR.js → chunk-2GRUQN2B.js} +435 -170
- package/dist/{chunk-LBGHZLLH.js → chunk-4QBDV6LX.js} +17 -14
- package/dist/{chunk-RYIREJAW.js → chunk-5EO6SEGV.js} +2 -2
- package/dist/{chunk-N4K3PNOS.js → chunk-B6JUWVTK.js} +95 -29
- 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-4UYKN6JU.js → chunk-MQHMT5WF.js} +3 -3
- package/dist/{chunk-VWPY2KWH.js → chunk-PYRH4G2J.js} +9 -35
- package/dist/{chunk-BGTEIA2S.js → chunk-TFCHWMOJ.js} +23 -30
- package/dist/{chunk-AIJE3RYH.js → chunk-TFDK6VSG.js} +33 -7
- package/dist/{chunk-X5YCWKNS.js → chunk-TJO4QFDQ.js} +59 -86
- package/dist/{chunk-QJ5FH5DP.js → chunk-TMHWQYWE.js} +4 -4
- package/dist/chunk-VLV3XWZS.js +191 -0
- package/dist/chunk-WGMJCFCE.js +14 -0
- package/dist/chunk-X5DOUA3Z.js +66 -0
- package/dist/{chunk-PXJ4NOP4.js → chunk-XUPKS5FL.js} +6 -4
- package/dist/{chunk-UEIQKG5P.js → chunk-YU6DZAVA.js} +303 -360
- package/dist/{chunk-OESYKZCM.js → chunk-ZLON3JTI.js} +3 -3
- 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-74CZVUWI.js} +9 -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-OVEFBJ26.js} +4 -4
- package/dist/{manager-344C5JLZ.js → manager-JXYRGR6B.js} +6 -2
- package/dist/{orchestrator-IYJRQQKB.js → orchestrator-FEMPJOZD.js} +15 -14
- package/dist/package.json +2 -1
- package/dist/platform-AB3GIEGO.js +21 -0
- package/dist/{processor-LUXKVIED.js → processor-QB3NMRMO.js} +16 -13
- 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-32FAA4FX.js} +20 -17
- package/dist/server/index.d.ts +26 -8
- package/dist/server/index.js +19 -16
- package/dist/server-OYE7GERR.js +37 -0
- package/dist/{settings-JA4XUR3W.js → settings-6ZPWOBWL.js} +3 -3
- package/dist/shared/index.d.ts +3 -3
- package/dist/{tools-ZZM36DAZ.js → tools-INWP6FGV.js} +14 -11
- 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-BELeAs12.css} +1 -1
- package/dist/web/assets/index-C3n65CNu.js +303 -0
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/dist/worktree-6U45P63P.js +30 -0
- package/package.json +2 -1
- package/dist/server-PXXGMFBT.js +0 -34
- package/dist/web/assets/index-Bsi_le80.js +0 -303
|
@@ -10,19 +10,24 @@ import {
|
|
|
10
10
|
createToolCallEvent,
|
|
11
11
|
createToolResultEvent,
|
|
12
12
|
streamLLMPure
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-TJO4QFDQ.js";
|
|
14
14
|
import {
|
|
15
15
|
startInspectProxy
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-4QBDV6LX.js";
|
|
17
17
|
import {
|
|
18
|
+
getProject
|
|
19
|
+
} from "./chunk-KXVFHMSM.js";
|
|
20
|
+
import {
|
|
21
|
+
checkAborted,
|
|
18
22
|
createProcess,
|
|
19
|
-
getPlatformShell,
|
|
20
23
|
getProcessLogs,
|
|
21
24
|
getProcessStatus,
|
|
22
25
|
getSessionProcesses,
|
|
26
|
+
spawnShell,
|
|
27
|
+
spawnShellProcess,
|
|
23
28
|
startProcessCommand,
|
|
24
29
|
stopProcess
|
|
25
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-TFCHWMOJ.js";
|
|
26
31
|
import {
|
|
27
32
|
getMaxPerSession,
|
|
28
33
|
getSessionProcessCount,
|
|
@@ -31,12 +36,15 @@ import {
|
|
|
31
36
|
import {
|
|
32
37
|
createMcpTools
|
|
33
38
|
} from "./chunk-NWO6GRYE.js";
|
|
39
|
+
import {
|
|
40
|
+
listBranches
|
|
41
|
+
} from "./chunk-VLV3XWZS.js";
|
|
34
42
|
import {
|
|
35
43
|
getCurrentContextWindowId,
|
|
36
44
|
getCurrentWindowMessageOptions,
|
|
37
45
|
getEventStore,
|
|
38
46
|
getRuntimeConfig
|
|
39
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-TFDK6VSG.js";
|
|
40
48
|
import {
|
|
41
49
|
buildContextMessagesFromEventHistory,
|
|
42
50
|
foldContextState,
|
|
@@ -46,7 +54,7 @@ import {
|
|
|
46
54
|
handleToolResult,
|
|
47
55
|
reorderToolMessages,
|
|
48
56
|
stripOrphanedToolCalls
|
|
49
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-C3VLGVSQ.js";
|
|
50
58
|
import {
|
|
51
59
|
createChatDoneMessage,
|
|
52
60
|
createChatMessageMessage,
|
|
@@ -59,14 +67,11 @@ import {
|
|
|
59
67
|
deleteSetting,
|
|
60
68
|
getSetting,
|
|
61
69
|
setSetting
|
|
62
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-XUPKS5FL.js";
|
|
63
71
|
import {
|
|
64
72
|
AskUserInterrupt,
|
|
65
73
|
askUserTool
|
|
66
74
|
} from "./chunk-EU3WWTFH.js";
|
|
67
|
-
import {
|
|
68
|
-
getProject
|
|
69
|
-
} from "./chunk-YQ3SOPBI.js";
|
|
70
75
|
import {
|
|
71
76
|
modelSupportsVision
|
|
72
77
|
} from "./chunk-V4IE7HJY.js";
|
|
@@ -225,6 +230,10 @@ async function isPathWithinSandbox(path, workdir, sessionId) {
|
|
|
225
230
|
function looksLikeRegex(str) {
|
|
226
231
|
return /[*?+[\]\\]/.test(str);
|
|
227
232
|
}
|
|
233
|
+
var isWindows = () => process.platform === "win32";
|
|
234
|
+
function isWindowsAbsolutePath(str) {
|
|
235
|
+
return /^[A-Za-z]:[\\/]/.test(str);
|
|
236
|
+
}
|
|
228
237
|
function isPlaceholderToken(str) {
|
|
229
238
|
return str.includes("__URL__") || str.includes("__FILEURL__") || str.includes("__SED__") || str.includes("__COMMIT_MSG__");
|
|
230
239
|
}
|
|
@@ -269,7 +278,12 @@ function extractAbsolutePathsFromCommand(command) {
|
|
|
269
278
|
if (content.startsWith("/") && looksLikeRegex(content)) {
|
|
270
279
|
continue;
|
|
271
280
|
}
|
|
272
|
-
if (content
|
|
281
|
+
if (isWindowsAbsolutePath(content)) {
|
|
282
|
+
const resolved = normalize(content);
|
|
283
|
+
if (!isSafePath(resolved)) {
|
|
284
|
+
paths.push(resolved);
|
|
285
|
+
}
|
|
286
|
+
} else if (!isWindows() && content.startsWith("/")) {
|
|
273
287
|
const resolved = normalize(content);
|
|
274
288
|
if (!isSafePath(resolved)) {
|
|
275
289
|
paths.push(resolved);
|
|
@@ -283,15 +297,27 @@ function extractAbsolutePathsFromCommand(command) {
|
|
|
283
297
|
}
|
|
284
298
|
}
|
|
285
299
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
300
|
+
if (isWindows()) {
|
|
301
|
+
const winAbsolutePattern = /(?:^|[\s=(])([A-Za-z]:[\\/][^\s"'|&;<>`()]+)/g;
|
|
302
|
+
while ((match = winAbsolutePattern.exec(sanitized)) !== null) {
|
|
303
|
+
const candidate = match[1];
|
|
304
|
+
if (isPlaceholderToken(candidate)) continue;
|
|
305
|
+
const resolved = normalize(candidate);
|
|
306
|
+
if (!isSafePath(resolved)) {
|
|
307
|
+
paths.push(resolved);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
const absolutePattern = /(?:^|[\s=(])(\/[^\s"'|&;<>`()]+)/g;
|
|
312
|
+
while ((match = absolutePattern.exec(sanitized)) !== null) {
|
|
313
|
+
const candidate = match[1];
|
|
314
|
+
if (isPlaceholderToken(candidate)) continue;
|
|
315
|
+
if (looksLikeRegex(candidate)) continue;
|
|
316
|
+
const resolved = normalize(candidate);
|
|
317
|
+
if (resolved === "/" || resolved === "") continue;
|
|
318
|
+
if (!isSafePath(resolved)) {
|
|
319
|
+
paths.push(resolved);
|
|
320
|
+
}
|
|
295
321
|
}
|
|
296
322
|
}
|
|
297
323
|
return [...new Set(paths)];
|
|
@@ -1448,25 +1474,10 @@ Make sure whitespace and indentation match exactly.`
|
|
|
1448
1474
|
);
|
|
1449
1475
|
|
|
1450
1476
|
// src/server/tools/shell.ts
|
|
1451
|
-
import { spawn
|
|
1477
|
+
import { spawn } from "child_process";
|
|
1452
1478
|
import { resolve as resolve4, isAbsolute as isAbsolute2 } from "path";
|
|
1453
1479
|
import { access } from "fs/promises";
|
|
1454
|
-
|
|
1455
|
-
// src/server/utils/shell.ts
|
|
1456
|
-
import { spawn } from "child_process";
|
|
1457
|
-
function checkAborted(signal) {
|
|
1458
|
-
return !!signal?.aborted;
|
|
1459
|
-
}
|
|
1460
|
-
function spawnShellProcess(command, cwd, _signal, detached = false) {
|
|
1461
|
-
const shell = getPlatformShell();
|
|
1462
|
-
return spawn(shell.command, [...shell.args, command], {
|
|
1463
|
-
cwd,
|
|
1464
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
1465
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1466
|
-
windowsHide: true,
|
|
1467
|
-
...detached ? { detached: true } : {}
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1480
|
+
import stripAnsi from "strip-ansi";
|
|
1470
1481
|
|
|
1471
1482
|
// src/server/tools/shell-tail.ts
|
|
1472
1483
|
var TAIL_END_RE = /^(.*?)\s*\|\s*tail\s+(?:-n\s+)?-?(\d+)\s*$/;
|
|
@@ -1491,7 +1502,7 @@ async function checkRtkAvailability() {
|
|
|
1491
1502
|
} catch {
|
|
1492
1503
|
try {
|
|
1493
1504
|
const out = await new Promise((resolve6, reject) => {
|
|
1494
|
-
const proc =
|
|
1505
|
+
const proc = spawn("rtk", ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
1495
1506
|
let output = "";
|
|
1496
1507
|
proc.stdout?.on("data", (d) => {
|
|
1497
1508
|
output += d.toString();
|
|
@@ -1587,13 +1598,14 @@ ${result.stderr}`;
|
|
|
1587
1598
|
const tailed = lines.slice(-tailInfo.tailLines);
|
|
1588
1599
|
output = tailed.join("\n");
|
|
1589
1600
|
}
|
|
1601
|
+
const visible = stripAnsi(output);
|
|
1590
1602
|
let truncated = false;
|
|
1591
|
-
if (
|
|
1603
|
+
if (visible.length > OUTPUT_LIMITS.run_command.maxBytes) {
|
|
1592
1604
|
output = output.slice(0, OUTPUT_LIMITS.run_command.maxBytes);
|
|
1593
1605
|
output += "\n\n[Output truncated due to size limit]";
|
|
1594
1606
|
truncated = true;
|
|
1595
1607
|
}
|
|
1596
|
-
const linesCount =
|
|
1608
|
+
const linesCount = visible.split("\n").length;
|
|
1597
1609
|
if (linesCount > OUTPUT_LIMITS.run_command.maxLines) {
|
|
1598
1610
|
const limitedLines = output.split("\n").slice(0, OUTPUT_LIMITS.run_command.maxLines);
|
|
1599
1611
|
output = limitedLines.join("\n");
|
|
@@ -1611,7 +1623,7 @@ async function tryRtkRewrite(command) {
|
|
|
1611
1623
|
if (!await checkRtkAvailability()) return command;
|
|
1612
1624
|
try {
|
|
1613
1625
|
const result = await new Promise((resolve6, reject) => {
|
|
1614
|
-
const proc =
|
|
1626
|
+
const proc = spawn("rtk", ["rewrite", command], {
|
|
1615
1627
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1616
1628
|
timeout: 2e3
|
|
1617
1629
|
});
|
|
@@ -2554,7 +2566,7 @@ function createToolProgressHandler(append, messageId, callId, _sessionId) {
|
|
|
2554
2566
|
}
|
|
2555
2567
|
|
|
2556
2568
|
// src/server/chat/execute-tools.ts
|
|
2557
|
-
import
|
|
2569
|
+
import stripAnsi2 from "strip-ansi";
|
|
2558
2570
|
var INTERRUPTED_ERROR = "Tool execution was interrupted by user";
|
|
2559
2571
|
function createInterruptedResult(startTime) {
|
|
2560
2572
|
return {
|
|
@@ -2618,19 +2630,24 @@ async function executeTools(assistantMsgId, toolCalls, ctx, append) {
|
|
|
2618
2630
|
};
|
|
2619
2631
|
}
|
|
2620
2632
|
if (toolCall.parseError) {
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2633
|
+
if (toolCall.name === "step_done") {
|
|
2634
|
+
const { parseError: _pe, rawArguments: _ra, ...rest } = toolCall;
|
|
2635
|
+
toolCall = { ...rest, arguments: {} };
|
|
2636
|
+
} else {
|
|
2637
|
+
const toolResult2 = {
|
|
2638
|
+
success: false,
|
|
2639
|
+
error: `Failed to parse tool call arguments: ${toolCall.parseError}. Please ensure your JSON function call arguments are valid.`,
|
|
2640
|
+
durationMs: 0,
|
|
2641
|
+
truncated: false
|
|
2642
|
+
};
|
|
2643
|
+
append(createToolResultEvent(assistantMsgId, toolCall.id, toolResult2));
|
|
2644
|
+
return {
|
|
2645
|
+
toolCall,
|
|
2646
|
+
toolResult: toolResult2,
|
|
2647
|
+
content: `Error: ${toolResult2.error}`,
|
|
2648
|
+
index
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2634
2651
|
}
|
|
2635
2652
|
const onProgress = ctx.onMessage ? createToolProgressHandler(append, assistantMsgId, toolCall.id, ctx.sessionId) : void 0;
|
|
2636
2653
|
const toolContext = {
|
|
@@ -2666,7 +2683,7 @@ async function executeTools(assistantMsgId, toolCalls, ctx, append) {
|
|
|
2666
2683
|
if (toolCall.name === "step_done" && toolResult.success) {
|
|
2667
2684
|
stepDoneCalled = true;
|
|
2668
2685
|
}
|
|
2669
|
-
const content =
|
|
2686
|
+
const content = stripAnsi2(
|
|
2670
2687
|
toolResult.success ? toolResult.output ?? "Success" : toolResult.output ? `${toolResult.output}
|
|
2671
2688
|
|
|
2672
2689
|
Error: ${toolResult.error}` : `Error: ${toolResult.error}`
|
|
@@ -2937,7 +2954,7 @@ ${CONTINUE_PROMPT}` : CONTINUE_PROMPT;
|
|
|
2937
2954
|
sessionManager.setCurrentContextSize(sessionId, result.usage.promptTokens);
|
|
2938
2955
|
if (!compacting) {
|
|
2939
2956
|
const contextState2 = sessionManager.getContextState(sessionId);
|
|
2940
|
-
const { shouldCompact, appendCompactionPrompt } = await import("./compactor-
|
|
2957
|
+
const { shouldCompact, appendCompactionPrompt } = await import("./compactor-74CZVUWI.js");
|
|
2941
2958
|
if (shouldCompact(contextState2.currentTokens, contextState2.maxTokens, runtimeConfig.context.compactionThreshold)) {
|
|
2942
2959
|
appendCompactionPrompt(sessionId, append);
|
|
2943
2960
|
compacting = true;
|
|
@@ -3018,7 +3035,7 @@ ${COMPACTION_PROMPT}`,
|
|
|
3018
3035
|
toolRegistry,
|
|
3019
3036
|
sessionManager,
|
|
3020
3037
|
sessionId,
|
|
3021
|
-
workdir: session.workdir,
|
|
3038
|
+
workdir: session.worktree ?? session.workdir,
|
|
3022
3039
|
turnMetrics,
|
|
3023
3040
|
signal,
|
|
3024
3041
|
onMessage,
|
|
@@ -3727,13 +3744,14 @@ async function executeSubAgent(options) {
|
|
|
3727
3744
|
})
|
|
3728
3745
|
);
|
|
3729
3746
|
}
|
|
3730
|
-
const
|
|
3747
|
+
const effectiveWorkdir = session.worktree ?? session.workdir;
|
|
3748
|
+
const { content: instructionContent } = await getAllInstructions(effectiveWorkdir, session.projectId);
|
|
3731
3749
|
const config = getRuntimeConfig();
|
|
3732
3750
|
const configDir = getGlobalConfigDir(config.mode ?? "production");
|
|
3733
3751
|
const skills = await getEnabledSkillMetadata(configDir, config.workdir);
|
|
3734
3752
|
const hasRunCommand = agentDef.metadata.allowedTools?.includes("run_command") ?? false;
|
|
3735
|
-
const gitignoreSection = hasRunCommand ? await loadGitIgnoreRules(
|
|
3736
|
-
const systemPrompt = buildBasePrompt(
|
|
3753
|
+
const gitignoreSection = hasRunCommand ? await loadGitIgnoreRules(effectiveWorkdir) : "";
|
|
3754
|
+
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
3755
|
const subAgentScope = { type: "subagent", sessionId, subAgentId, subAgentType };
|
|
3738
3756
|
const loopResult = await runTopLevelAgentLoop(
|
|
3739
3757
|
{
|
|
@@ -3841,7 +3859,7 @@ var callSubAgentTool = {
|
|
|
3841
3859
|
};
|
|
3842
3860
|
}
|
|
3843
3861
|
try {
|
|
3844
|
-
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-
|
|
3862
|
+
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-INWP6FGV.js");
|
|
3845
3863
|
const toolRegistry = getToolRegistryForAgent2(agentDef);
|
|
3846
3864
|
const turnMetrics = new TurnMetrics();
|
|
3847
3865
|
const result = await executeSubAgent({
|
|
@@ -4125,14 +4143,17 @@ var webFetchTool = createTool(
|
|
|
4125
4143
|
);
|
|
4126
4144
|
|
|
4127
4145
|
// src/server/dev-server/manager.ts
|
|
4128
|
-
import
|
|
4146
|
+
import "child_process";
|
|
4129
4147
|
import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
|
|
4130
4148
|
import { resolve as resolve5, join as join8 } from "path";
|
|
4149
|
+
import net from "net";
|
|
4131
4150
|
var MAX_LOG_LINES = 2e3;
|
|
4132
4151
|
var MAX_LOG_BYTES = 1e5;
|
|
4152
|
+
var DEFAULT_PROBE_PORT = 10469;
|
|
4153
|
+
var MAX_PORT_SCAN = 200;
|
|
4154
|
+
var PROBE_TIMEOUT_MS = 300;
|
|
4133
4155
|
var getDevServerConfigPath = (workdir) => {
|
|
4134
|
-
|
|
4135
|
-
return join8(resolve5(workdir), ".openfox", `${mode === "development" ? "dev-dev" : "dev"}.json`);
|
|
4156
|
+
return join8(resolve5(workdir), ".openfox", "dev.json");
|
|
4136
4157
|
};
|
|
4137
4158
|
var ERROR_PATTERNS = [
|
|
4138
4159
|
/EADDRINUSE/,
|
|
@@ -4150,6 +4171,9 @@ function createInstance() {
|
|
|
4150
4171
|
process: null,
|
|
4151
4172
|
state: "off",
|
|
4152
4173
|
config: null,
|
|
4174
|
+
resolvedUrl: null,
|
|
4175
|
+
resolvedCommand: null,
|
|
4176
|
+
assignedPort: null,
|
|
4153
4177
|
logs: [],
|
|
4154
4178
|
totalLogBytes: 0,
|
|
4155
4179
|
errorMessage: void 0,
|
|
@@ -4158,6 +4182,17 @@ function createInstance() {
|
|
|
4158
4182
|
proxyCleanup: null
|
|
4159
4183
|
};
|
|
4160
4184
|
}
|
|
4185
|
+
function parsePortFromUrl(url) {
|
|
4186
|
+
const substituted = url.replace(/\$\{PORT\}/g, String(DEFAULT_PROBE_PORT));
|
|
4187
|
+
try {
|
|
4188
|
+
const parsed = new URL(substituted);
|
|
4189
|
+
const port = parsed.port ? parseInt(parsed.port, 10) : DEFAULT_PROBE_PORT;
|
|
4190
|
+
return { hostname: parsed.hostname, port: isNaN(port) ? DEFAULT_PROBE_PORT : port };
|
|
4191
|
+
} catch {
|
|
4192
|
+
logger.warn("Failed to parse dev server URL, falling back to defaults", { url });
|
|
4193
|
+
return { hostname: "127.0.0.1", port: DEFAULT_PROBE_PORT };
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4161
4196
|
var DevServerManager = class {
|
|
4162
4197
|
instances = /* @__PURE__ */ new Map();
|
|
4163
4198
|
outputListeners = /* @__PURE__ */ new Set();
|
|
@@ -4190,21 +4225,78 @@ var DevServerManager = class {
|
|
|
4190
4225
|
listener(resolved, state, errorMessage);
|
|
4191
4226
|
}
|
|
4192
4227
|
}
|
|
4228
|
+
/** Probe whether a TCP port is in use */
|
|
4229
|
+
async probePort(host, port) {
|
|
4230
|
+
return new Promise((resolvePromise) => {
|
|
4231
|
+
const socket = new net.Socket();
|
|
4232
|
+
socket.setTimeout(PROBE_TIMEOUT_MS);
|
|
4233
|
+
socket.on("connect", () => {
|
|
4234
|
+
socket.destroy();
|
|
4235
|
+
resolvePromise(true);
|
|
4236
|
+
});
|
|
4237
|
+
socket.on("timeout", () => {
|
|
4238
|
+
socket.destroy();
|
|
4239
|
+
resolvePromise(false);
|
|
4240
|
+
});
|
|
4241
|
+
socket.on("error", () => {
|
|
4242
|
+
socket.destroy();
|
|
4243
|
+
resolvePromise(false);
|
|
4244
|
+
});
|
|
4245
|
+
socket.connect(port, host);
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Find a free port starting from preferred, scanning upward.
|
|
4250
|
+
* Throws if no free port found within range.
|
|
4251
|
+
*/
|
|
4252
|
+
async findFreePort(host, preferred) {
|
|
4253
|
+
const inUse = await this.probePort(host, preferred);
|
|
4254
|
+
if (!inUse) return preferred;
|
|
4255
|
+
const maxPort = Math.min(preferred + MAX_PORT_SCAN, 65535);
|
|
4256
|
+
for (let port = preferred + 1; port <= maxPort; port++) {
|
|
4257
|
+
const taken = await this.probePort(host, port);
|
|
4258
|
+
if (!taken) return port;
|
|
4259
|
+
}
|
|
4260
|
+
throw new Error(
|
|
4261
|
+
`No free port found in range ${preferred}-${maxPort} on ${host}. Close some applications or configure a different port in .openfox/dev.json`
|
|
4262
|
+
);
|
|
4263
|
+
}
|
|
4264
|
+
/** Substitute ${PORT} placeholders with actual port number */
|
|
4265
|
+
substitutePort(template, port) {
|
|
4266
|
+
return template.replace(/\$\{PORT\}/g, String(port));
|
|
4267
|
+
}
|
|
4268
|
+
/**
|
|
4269
|
+
* Load dev server config from workdir.
|
|
4270
|
+
* If not found and the path looks like a git worktree (contains /worktrees/),
|
|
4271
|
+
* falls back to the parent project root.
|
|
4272
|
+
*/
|
|
4193
4273
|
async loadConfig(workdir) {
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4274
|
+
const tryLoad = async (dir) => {
|
|
4275
|
+
try {
|
|
4276
|
+
const configPath = getDevServerConfigPath(dir);
|
|
4277
|
+
const raw = await readFile9(configPath, "utf-8");
|
|
4278
|
+
const parsed = JSON.parse(raw);
|
|
4279
|
+
if (!parsed.command || !parsed.url) return null;
|
|
4280
|
+
return {
|
|
4281
|
+
command: parsed.command,
|
|
4282
|
+
url: parsed.url,
|
|
4283
|
+
hotReload: parsed.hotReload ?? false,
|
|
4284
|
+
disableInspect: parsed.disableInspect ?? false
|
|
4285
|
+
};
|
|
4286
|
+
} catch {
|
|
4287
|
+
return null;
|
|
4288
|
+
}
|
|
4289
|
+
};
|
|
4290
|
+
const config = await tryLoad(workdir);
|
|
4291
|
+
if (config) return config;
|
|
4292
|
+
const wtIdx = workdir.indexOf("/worktrees/");
|
|
4293
|
+
if (wtIdx !== -1) {
|
|
4294
|
+
const projectRoot = workdir.slice(0, wtIdx);
|
|
4295
|
+
if (projectRoot !== workdir) {
|
|
4296
|
+
return tryLoad(projectRoot);
|
|
4297
|
+
}
|
|
4207
4298
|
}
|
|
4299
|
+
return null;
|
|
4208
4300
|
}
|
|
4209
4301
|
async saveConfig(workdir, config) {
|
|
4210
4302
|
const resolved = this.resolveWorkdir(workdir);
|
|
@@ -4222,29 +4314,32 @@ var DevServerManager = class {
|
|
|
4222
4314
|
if (!config) {
|
|
4223
4315
|
return this.getStatus(workdir);
|
|
4224
4316
|
}
|
|
4317
|
+
const { hostname, port: configuredPort } = parsePortFromUrl(config.url);
|
|
4318
|
+
const assignedPort = await this.findFreePort(hostname, configuredPort);
|
|
4319
|
+
instance.assignedPort = assignedPort;
|
|
4320
|
+
const resolvedCommand = this.substitutePort(config.command, assignedPort);
|
|
4321
|
+
const resolvedUrl = this.substitutePort(config.url, assignedPort);
|
|
4322
|
+
instance.resolvedCommand = resolvedCommand;
|
|
4323
|
+
instance.resolvedUrl = resolvedUrl;
|
|
4225
4324
|
instance.config = config;
|
|
4226
4325
|
instance.logs = [];
|
|
4227
4326
|
instance.totalLogBytes = 0;
|
|
4228
4327
|
instance.errorMessage = void 0;
|
|
4229
4328
|
instance.exited = false;
|
|
4230
|
-
if (!config.disableInspect &&
|
|
4329
|
+
if (!config.disableInspect && resolvedUrl && this._sessionManager) {
|
|
4231
4330
|
try {
|
|
4232
|
-
const { port, cleanup } = startInspectProxy(
|
|
4331
|
+
const { port, cleanup } = await startInspectProxy(resolvedUrl, this._sessionManager, assignedPort, workdir);
|
|
4233
4332
|
instance.inspectProxyPort = port;
|
|
4234
4333
|
instance.proxyCleanup = cleanup;
|
|
4235
|
-
logger.debug("Inspect proxy started", { workdir, port, target:
|
|
4334
|
+
logger.debug("Inspect proxy started", { workdir, port, target: resolvedUrl });
|
|
4236
4335
|
} catch (err) {
|
|
4237
4336
|
logger.warn("Failed to start inspect proxy", { workdir, error: err });
|
|
4238
4337
|
}
|
|
4239
4338
|
}
|
|
4240
4339
|
const resolved = this.resolveWorkdir(workdir);
|
|
4241
|
-
const
|
|
4242
|
-
const proc = spawn3(shell.command, [...shell.args, config.command], {
|
|
4340
|
+
const proc = spawnShell(resolvedCommand, {
|
|
4243
4341
|
cwd: resolved,
|
|
4244
|
-
|
|
4245
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
4246
|
-
detached: true,
|
|
4247
|
-
windowsHide: true
|
|
4342
|
+
detached: true
|
|
4248
4343
|
});
|
|
4249
4344
|
instance.process = proc;
|
|
4250
4345
|
const appendLog = (stream, content) => {
|
|
@@ -4297,7 +4392,7 @@ ${recentLogs}`.trim();
|
|
|
4297
4392
|
instance.state = "running";
|
|
4298
4393
|
instance.errorMessage = void 0;
|
|
4299
4394
|
this.emitStateChange(workdir, "running", void 0);
|
|
4300
|
-
logger.info("Dev server started", { workdir, command:
|
|
4395
|
+
logger.info("Dev server started", { workdir, command: resolvedCommand, port: assignedPort });
|
|
4301
4396
|
return this.getStatus(workdir);
|
|
4302
4397
|
}
|
|
4303
4398
|
async stop(workdir) {
|
|
@@ -4326,7 +4421,7 @@ ${recentLogs}`.trim();
|
|
|
4326
4421
|
const instance = this.getInstance(workdir);
|
|
4327
4422
|
return {
|
|
4328
4423
|
state: instance.state,
|
|
4329
|
-
url: instance.config?.url ?? null,
|
|
4424
|
+
url: instance.resolvedUrl ?? instance.config?.url ?? null,
|
|
4330
4425
|
hotReload: instance.config?.hotReload ?? false,
|
|
4331
4426
|
config: instance.config,
|
|
4332
4427
|
errorMessage: instance.errorMessage,
|
|
@@ -4372,7 +4467,7 @@ var devServerTool = createTool(
|
|
|
4372
4467
|
type: "function",
|
|
4373
4468
|
function: {
|
|
4374
4469
|
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
|
|
4470
|
+
description: "Control the project dev server. Start, stop, restart, check status, or fetch logs with optional pagination. Each workdir (project root or git worktree) gets its own independent dev server instance with auto-assigned ports. The dev server is configured via .openfox/dev.json \u2014 searched in the current workdir first, falling back to the project root. 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
4471
|
parameters: {
|
|
4377
4472
|
type: "object",
|
|
4378
4473
|
properties: {
|
|
@@ -4440,7 +4535,7 @@ var devServerTool = createTool(
|
|
|
4440
4535
|
}
|
|
4441
4536
|
if (!status.config) {
|
|
4442
4537
|
return helpers.error(
|
|
4443
|
-
'No .openfox/dev.json config found in the project root
|
|
4538
|
+
'No .openfox/dev.json config found. Create one in the project root:\n\n{\n "command": "npm run dev",\n "url": "http://localhost:3000",\n "hotReload": true,\n "disableInspect": false\n}\n\n(Worktrees inherit the project root config automatically.)'
|
|
4444
4539
|
);
|
|
4445
4540
|
}
|
|
4446
4541
|
return helpers.success(
|
|
@@ -4763,7 +4858,7 @@ var mcpConfigTool = createTool(
|
|
|
4763
4858
|
mcpNotifyChanged?.(sessionId);
|
|
4764
4859
|
}
|
|
4765
4860
|
async function rebuildTools() {
|
|
4766
|
-
const { setMcpTools: setMcpTools2 } = await import("./tools-
|
|
4861
|
+
const { setMcpTools: setMcpTools2 } = await import("./tools-INWP6FGV.js");
|
|
4767
4862
|
const mcpTools = createMcpTools(mcpManagerForTools);
|
|
4768
4863
|
setMcpTools2(mcpTools);
|
|
4769
4864
|
}
|
|
@@ -4861,270 +4956,6 @@ var mcpConfigTool = createTool(
|
|
|
4861
4956
|
}
|
|
4862
4957
|
);
|
|
4863
4958
|
|
|
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
4959
|
// src/server/tools/web-search.ts
|
|
5129
4960
|
var DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
5130
4961
|
var DEFAULT_MAX_RESULTS = 5;
|
|
@@ -5260,6 +5091,120 @@ var webSearchTool = createTool(
|
|
|
5260
5091
|
}
|
|
5261
5092
|
);
|
|
5262
5093
|
|
|
5094
|
+
// src/server/tools/worktree.ts
|
|
5095
|
+
var worktreeTool = createTool(
|
|
5096
|
+
"worktree",
|
|
5097
|
+
{
|
|
5098
|
+
type: "function",
|
|
5099
|
+
function: {
|
|
5100
|
+
name: "worktree",
|
|
5101
|
+
description: 'Manage git worktrees for the current session. Use this to work on isolated branches without affecting the main working tree.\n\nAsset strategy for .gitignored files (e.g. node_modules) is configured via .openfox/worktree.json in the project root with fields: ignoredAssets ("symlink"|"copy"|"skip", default "skip"), overrides (optional, per-path exceptions). The server reads this file automatically on create. You can manage it via read_file/write_file, or the user can configure it in Project Settings \u2192 Worktree Asset Strategy.\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)',
|
|
5102
|
+
parameters: {
|
|
5103
|
+
type: "object",
|
|
5104
|
+
properties: {
|
|
5105
|
+
action: {
|
|
5106
|
+
type: "string",
|
|
5107
|
+
enum: ["list_branches", "create", "attach", "close", "status"],
|
|
5108
|
+
description: "The action to perform"
|
|
5109
|
+
},
|
|
5110
|
+
name: {
|
|
5111
|
+
type: "string",
|
|
5112
|
+
description: "Branch name for creating a new worktree (required for action=create)"
|
|
5113
|
+
},
|
|
5114
|
+
path: {
|
|
5115
|
+
type: "string",
|
|
5116
|
+
description: "Path to an existing worktree directory (required for action=attach)"
|
|
5117
|
+
}
|
|
5118
|
+
},
|
|
5119
|
+
required: ["action"]
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
},
|
|
5123
|
+
async (args, context, helpers) => {
|
|
5124
|
+
const { sessionId, sessionManager } = context;
|
|
5125
|
+
const validActions = ["list_branches", "create", "attach", "close", "status"];
|
|
5126
|
+
if (!validActions.includes(args.action)) {
|
|
5127
|
+
return helpers.error(`Invalid action: ${args.action}. Must be one of: ${validActions.join(", ")}`);
|
|
5128
|
+
}
|
|
5129
|
+
switch (args.action) {
|
|
5130
|
+
case "list_branches": {
|
|
5131
|
+
const session = sessionManager.getSession(sessionId);
|
|
5132
|
+
if (!session) return helpers.error("Session not found");
|
|
5133
|
+
const project = sessionManager.getProject(session.projectId);
|
|
5134
|
+
if (!project) return helpers.error("Project not found");
|
|
5135
|
+
const branches = await listBranches(project.workdir);
|
|
5136
|
+
return helpers.success(JSON.stringify({ branches }, null, 2));
|
|
5137
|
+
}
|
|
5138
|
+
case "create": {
|
|
5139
|
+
if (!args.name || typeof args.name !== "string") {
|
|
5140
|
+
return helpers.error('Parameter "name" is required for action=create (the branch name)');
|
|
5141
|
+
}
|
|
5142
|
+
const updated = await sessionManager.createSessionWorktree(sessionId, args.name);
|
|
5143
|
+
return helpers.success(
|
|
5144
|
+
JSON.stringify(
|
|
5145
|
+
{
|
|
5146
|
+
worktree: updated.worktree,
|
|
5147
|
+
branch: args.name,
|
|
5148
|
+
message: `Worktree created and session attached to branch "${args.name}"`
|
|
5149
|
+
},
|
|
5150
|
+
null,
|
|
5151
|
+
2
|
|
5152
|
+
)
|
|
5153
|
+
);
|
|
5154
|
+
}
|
|
5155
|
+
case "attach": {
|
|
5156
|
+
if (!args.path || typeof args.path !== "string") {
|
|
5157
|
+
return helpers.error('Parameter "path" is required for action=attach (path to existing worktree)');
|
|
5158
|
+
}
|
|
5159
|
+
const updated = await sessionManager.attachSessionWorktree(sessionId, args.path);
|
|
5160
|
+
return helpers.success(
|
|
5161
|
+
JSON.stringify(
|
|
5162
|
+
{
|
|
5163
|
+
worktree: updated.worktree,
|
|
5164
|
+
message: `Session attached to existing worktree at ${args.path}`
|
|
5165
|
+
},
|
|
5166
|
+
null,
|
|
5167
|
+
2
|
|
5168
|
+
)
|
|
5169
|
+
);
|
|
5170
|
+
}
|
|
5171
|
+
case "close": {
|
|
5172
|
+
const updated = await sessionManager.closeSessionWorktree(sessionId);
|
|
5173
|
+
return helpers.success(
|
|
5174
|
+
JSON.stringify(
|
|
5175
|
+
{
|
|
5176
|
+
worktree: updated.worktree,
|
|
5177
|
+
workdir: updated.workdir,
|
|
5178
|
+
message: "Worktree closed. Session returned to project root."
|
|
5179
|
+
},
|
|
5180
|
+
null,
|
|
5181
|
+
2
|
|
5182
|
+
)
|
|
5183
|
+
);
|
|
5184
|
+
}
|
|
5185
|
+
case "status": {
|
|
5186
|
+
const session = sessionManager.getSession(sessionId);
|
|
5187
|
+
if (!session) return helpers.error("Session not found");
|
|
5188
|
+
const active = !!session.worktree;
|
|
5189
|
+
return helpers.success(
|
|
5190
|
+
JSON.stringify(
|
|
5191
|
+
{
|
|
5192
|
+
active,
|
|
5193
|
+
worktree: session.worktree ?? null,
|
|
5194
|
+
workdir: session.workdir,
|
|
5195
|
+
...active ? { branch: session.worktree?.split("/worktrees/")[1] ?? session.worktree } : {}
|
|
5196
|
+
},
|
|
5197
|
+
null,
|
|
5198
|
+
2
|
|
5199
|
+
)
|
|
5200
|
+
);
|
|
5201
|
+
}
|
|
5202
|
+
default:
|
|
5203
|
+
return helpers.error(`Unknown action: ${args.action}`);
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
);
|
|
5207
|
+
|
|
5263
5208
|
// src/server/tools/index.ts
|
|
5264
5209
|
var _builtInTools;
|
|
5265
5210
|
function getBuiltInTools() {
|
|
@@ -5280,7 +5225,7 @@ function getBuiltInTools() {
|
|
|
5280
5225
|
stepDoneTool,
|
|
5281
5226
|
backgroundProcessTool,
|
|
5282
5227
|
mcpConfigTool,
|
|
5283
|
-
|
|
5228
|
+
worktreeTool
|
|
5284
5229
|
];
|
|
5285
5230
|
}
|
|
5286
5231
|
return _builtInTools;
|
|
@@ -5516,8 +5461,6 @@ export {
|
|
|
5516
5461
|
PathAccessDeniedError,
|
|
5517
5462
|
providePathConfirmation,
|
|
5518
5463
|
cancelPathConfirmationsForSession,
|
|
5519
|
-
checkAborted,
|
|
5520
|
-
spawnShellProcess,
|
|
5521
5464
|
pathExists,
|
|
5522
5465
|
getDefaultIds,
|
|
5523
5466
|
loadItemsFromDir,
|
|
@@ -5578,4 +5521,4 @@ export {
|
|
|
5578
5521
|
getToolRegistryForAgent,
|
|
5579
5522
|
createToolRegistry
|
|
5580
5523
|
};
|
|
5581
|
-
//# sourceMappingURL=chunk-
|
|
5524
|
+
//# sourceMappingURL=chunk-YU6DZAVA.js.map
|