openfox 2.0.64 → 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-LI4R4Z2E.js → chat-handler-ZRDNSLOG.js} +14 -12
- package/dist/{chunk-WD75RSGR.js → chunk-3ACDQKK6.js} +358 -340
- 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-JFQSQO2N.js → chunk-OE5B44P6.js} +278 -68
- package/dist/{chunk-SVSKQYFH.js → chunk-PVRSNXD4.js} +6 -4
- package/dist/{chunk-X5YCWKNS.js → chunk-R4UTFE63.js} +53 -83
- package/dist/{chunk-O3G6X5CT.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-73N5CJH3.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-HZNY77HZ.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-PFN7C5JI.js → orchestrator-LZQTDOWM.js} +13 -11
- package/dist/package.json +1 -1
- package/dist/{processor-AYYXP5NS.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-54KALE6R.js → serve-HGHFN3D2.js} +18 -16
- package/dist/server/index.d.ts +26 -8
- package/dist/server/index.js +17 -15
- package/dist/{server-Z2A5YZM6.js → server-W42ENYFX.js} +15 -13
- package/dist/{settings-ASLTSUE2.js → settings-ZZ6AKFPD.js} +3 -3
- package/dist/shared/index.d.ts +3 -3
- package/dist/{tools-R6LPK6OS.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-DvPmgi8F.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-CkE65FP4.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,
|
|
@@ -54,19 +57,19 @@ import {
|
|
|
54
57
|
createChatPathConfirmationMessage,
|
|
55
58
|
createQueueStateMessage
|
|
56
59
|
} from "./chunk-F4PMNP7S.js";
|
|
57
|
-
import {
|
|
58
|
-
AskUserInterrupt,
|
|
59
|
-
askUserTool
|
|
60
|
-
} from "./chunk-EU3WWTFH.js";
|
|
61
60
|
import {
|
|
62
61
|
SETTINGS_KEYS,
|
|
63
62
|
deleteSetting,
|
|
64
63
|
getSetting,
|
|
65
64
|
setSetting
|
|
66
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-PVRSNXD4.js";
|
|
66
|
+
import {
|
|
67
|
+
AskUserInterrupt,
|
|
68
|
+
askUserTool
|
|
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)];
|
|
@@ -1448,7 +1472,10 @@ Make sure whitespace and indentation match exactly.`
|
|
|
1448
1472
|
);
|
|
1449
1473
|
|
|
1450
1474
|
// src/server/tools/shell.ts
|
|
1475
|
+
import { spawn as spawn2 } from "child_process";
|
|
1451
1476
|
import { resolve as resolve4, isAbsolute as isAbsolute2 } from "path";
|
|
1477
|
+
import { access } from "fs/promises";
|
|
1478
|
+
import stripAnsi from "strip-ansi";
|
|
1452
1479
|
|
|
1453
1480
|
// src/server/utils/shell.ts
|
|
1454
1481
|
import { spawn } from "child_process";
|
|
@@ -1480,6 +1507,33 @@ function stripTailPipe(command) {
|
|
|
1480
1507
|
}
|
|
1481
1508
|
|
|
1482
1509
|
// src/server/tools/shell.ts
|
|
1510
|
+
var rtkAvailable;
|
|
1511
|
+
async function checkRtkAvailability() {
|
|
1512
|
+
if (rtkAvailable !== void 0) return rtkAvailable;
|
|
1513
|
+
try {
|
|
1514
|
+
await access("/usr/local/bin/rtk");
|
|
1515
|
+
rtkAvailable = true;
|
|
1516
|
+
} catch {
|
|
1517
|
+
try {
|
|
1518
|
+
const out = await new Promise((resolve6, reject) => {
|
|
1519
|
+
const proc = spawn2("rtk", ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
1520
|
+
let output = "";
|
|
1521
|
+
proc.stdout?.on("data", (d) => {
|
|
1522
|
+
output += d.toString();
|
|
1523
|
+
});
|
|
1524
|
+
proc.on("error", reject);
|
|
1525
|
+
proc.on("close", (code) => {
|
|
1526
|
+
if (code === 0) resolve6(output.trim());
|
|
1527
|
+
else reject(new Error(`exit ${code}`));
|
|
1528
|
+
});
|
|
1529
|
+
});
|
|
1530
|
+
rtkAvailable = out.startsWith("rtk ");
|
|
1531
|
+
} catch {
|
|
1532
|
+
rtkAvailable = false;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
return rtkAvailable;
|
|
1536
|
+
}
|
|
1483
1537
|
function hasBackgroundAmpersand(command) {
|
|
1484
1538
|
const trimmed = command.replace(/[;\s]+$/, "");
|
|
1485
1539
|
if (trimmed.endsWith("&")) {
|
|
@@ -1538,7 +1592,9 @@ var runCommandTool = createTool(
|
|
|
1538
1592
|
await helpers.checkPathAccess(pathsToCheck, args.command);
|
|
1539
1593
|
const tailInfo = stripTailPipe(args.command);
|
|
1540
1594
|
const execCommand = tailInfo ? tailInfo.command : args.command;
|
|
1541
|
-
const
|
|
1595
|
+
const useRtk = getSetting(SETTINGS_KEYS.TOOLS_USE_RTK) === "true";
|
|
1596
|
+
const finalCommand = useRtk ? await tryRtkRewrite(execCommand) : execCommand;
|
|
1597
|
+
const result = await executeCommand(finalCommand, workingDir, timeout, context.signal, context.onProgress);
|
|
1542
1598
|
let output = "";
|
|
1543
1599
|
if (result.stdout) {
|
|
1544
1600
|
output += result.stdout;
|
|
@@ -1556,13 +1612,14 @@ ${result.stderr}`;
|
|
|
1556
1612
|
const tailed = lines.slice(-tailInfo.tailLines);
|
|
1557
1613
|
output = tailed.join("\n");
|
|
1558
1614
|
}
|
|
1615
|
+
const visible = stripAnsi(output);
|
|
1559
1616
|
let truncated = false;
|
|
1560
|
-
if (
|
|
1617
|
+
if (visible.length > OUTPUT_LIMITS.run_command.maxBytes) {
|
|
1561
1618
|
output = output.slice(0, OUTPUT_LIMITS.run_command.maxBytes);
|
|
1562
1619
|
output += "\n\n[Output truncated due to size limit]";
|
|
1563
1620
|
truncated = true;
|
|
1564
1621
|
}
|
|
1565
|
-
const linesCount =
|
|
1622
|
+
const linesCount = visible.split("\n").length;
|
|
1566
1623
|
if (linesCount > OUTPUT_LIMITS.run_command.maxLines) {
|
|
1567
1624
|
const limitedLines = output.split("\n").slice(0, OUTPUT_LIMITS.run_command.maxLines);
|
|
1568
1625
|
output = limitedLines.join("\n");
|
|
@@ -1576,6 +1633,29 @@ ${result.stderr}`;
|
|
|
1576
1633
|
});
|
|
1577
1634
|
}
|
|
1578
1635
|
);
|
|
1636
|
+
async function tryRtkRewrite(command) {
|
|
1637
|
+
if (!await checkRtkAvailability()) return command;
|
|
1638
|
+
try {
|
|
1639
|
+
const result = await new Promise((resolve6, reject) => {
|
|
1640
|
+
const proc = spawn2("rtk", ["rewrite", command], {
|
|
1641
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1642
|
+
timeout: 2e3
|
|
1643
|
+
});
|
|
1644
|
+
let stdout = "";
|
|
1645
|
+
proc.stdout?.on("data", (data) => {
|
|
1646
|
+
stdout += data.toString();
|
|
1647
|
+
});
|
|
1648
|
+
proc.on("error", reject);
|
|
1649
|
+
proc.on("close", (code) => {
|
|
1650
|
+
if (code === 0 || code === 3) resolve6(stdout.trim());
|
|
1651
|
+
else reject(new Error(`exit ${code}`));
|
|
1652
|
+
});
|
|
1653
|
+
});
|
|
1654
|
+
if (result && result !== command) return result;
|
|
1655
|
+
} catch {
|
|
1656
|
+
}
|
|
1657
|
+
return command;
|
|
1658
|
+
}
|
|
1579
1659
|
function executeCommand(command, cwd, timeout, signal, onProgress) {
|
|
1580
1660
|
return new Promise((resolve6, reject) => {
|
|
1581
1661
|
if (checkAborted(signal)) {
|
|
@@ -1800,24 +1880,24 @@ ${Object.entries(schema.fields).map(([f, t]) => ` ${f}: ${t}`).join("\n")}`
|
|
|
1800
1880
|
);
|
|
1801
1881
|
|
|
1802
1882
|
// src/server/sub-agents/manager.ts
|
|
1803
|
-
import { readFile as readFile8, access as
|
|
1883
|
+
import { readFile as readFile8, access as access5 } from "fs/promises";
|
|
1804
1884
|
import { join as join7, dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
|
|
1805
1885
|
|
|
1806
1886
|
// src/server/agents/registry.ts
|
|
1807
|
-
import { readdir as readdir3, readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3, access as
|
|
1887
|
+
import { readdir as readdir3, readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3, access as access3, unlink as unlink2 } from "fs/promises";
|
|
1808
1888
|
import { join as join4, dirname as dirname2 } from "path";
|
|
1809
1889
|
import { constants as constants2 } from "fs";
|
|
1810
1890
|
import { fileURLToPath } from "url";
|
|
1811
1891
|
import matter2 from "gray-matter";
|
|
1812
1892
|
|
|
1813
1893
|
// src/server/shared/item-loader.ts
|
|
1814
|
-
import { readdir as readdir2, readFile as readFile4, access, writeFile as writeFile3, mkdir as mkdir2, unlink } from "fs/promises";
|
|
1894
|
+
import { readdir as readdir2, readFile as readFile4, access as access2, writeFile as writeFile3, mkdir as mkdir2, unlink } from "fs/promises";
|
|
1815
1895
|
import { join as join3 } from "path";
|
|
1816
1896
|
import { constants } from "fs";
|
|
1817
1897
|
import matter from "gray-matter";
|
|
1818
1898
|
async function pathExists(path) {
|
|
1819
1899
|
try {
|
|
1820
|
-
await
|
|
1900
|
+
await access2(path, constants.R_OK);
|
|
1821
1901
|
return true;
|
|
1822
1902
|
} catch {
|
|
1823
1903
|
return false;
|
|
@@ -1895,7 +1975,7 @@ function getProjectAgentsDir(projectDir) {
|
|
|
1895
1975
|
}
|
|
1896
1976
|
async function pathExists2(path) {
|
|
1897
1977
|
try {
|
|
1898
|
-
await
|
|
1978
|
+
await access3(path, constants2.R_OK);
|
|
1899
1979
|
return true;
|
|
1900
1980
|
} catch {
|
|
1901
1981
|
return false;
|
|
@@ -2074,7 +2154,7 @@ async function deleteAgent(configDir, agentId) {
|
|
|
2074
2154
|
}
|
|
2075
2155
|
|
|
2076
2156
|
// src/server/context/instructions.ts
|
|
2077
|
-
import { readFile as readFile6, access as
|
|
2157
|
+
import { readFile as readFile6, access as access4 } from "fs/promises";
|
|
2078
2158
|
import { join as join5, dirname as dirname3 } from "path";
|
|
2079
2159
|
import { constants as constants3 } from "fs";
|
|
2080
2160
|
var INSTRUCTION_FILENAMES = ["AGENTS.md", "CLAUDE.md"];
|
|
@@ -2157,7 +2237,7 @@ ${agentContent}`);
|
|
|
2157
2237
|
}
|
|
2158
2238
|
async function fileExists(path) {
|
|
2159
2239
|
try {
|
|
2160
|
-
await
|
|
2240
|
+
await access4(path, constants3.R_OK);
|
|
2161
2241
|
return true;
|
|
2162
2242
|
} catch {
|
|
2163
2243
|
return false;
|
|
@@ -2500,7 +2580,7 @@ function createToolProgressHandler(append, messageId, callId, _sessionId) {
|
|
|
2500
2580
|
}
|
|
2501
2581
|
|
|
2502
2582
|
// src/server/chat/execute-tools.ts
|
|
2503
|
-
import
|
|
2583
|
+
import stripAnsi2 from "strip-ansi";
|
|
2504
2584
|
var INTERRUPTED_ERROR = "Tool execution was interrupted by user";
|
|
2505
2585
|
function createInterruptedResult(startTime) {
|
|
2506
2586
|
return {
|
|
@@ -2564,19 +2644,24 @@ async function executeTools(assistantMsgId, toolCalls, ctx, append) {
|
|
|
2564
2644
|
};
|
|
2565
2645
|
}
|
|
2566
2646
|
if (toolCall.parseError) {
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
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
|
+
}
|
|
2580
2665
|
}
|
|
2581
2666
|
const onProgress = ctx.onMessage ? createToolProgressHandler(append, assistantMsgId, toolCall.id, ctx.sessionId) : void 0;
|
|
2582
2667
|
const toolContext = {
|
|
@@ -2612,7 +2697,7 @@ async function executeTools(assistantMsgId, toolCalls, ctx, append) {
|
|
|
2612
2697
|
if (toolCall.name === "step_done" && toolResult.success) {
|
|
2613
2698
|
stepDoneCalled = true;
|
|
2614
2699
|
}
|
|
2615
|
-
const content =
|
|
2700
|
+
const content = stripAnsi2(
|
|
2616
2701
|
toolResult.success ? toolResult.output ?? "Success" : toolResult.output ? `${toolResult.output}
|
|
2617
2702
|
|
|
2618
2703
|
Error: ${toolResult.error}` : `Error: ${toolResult.error}`
|
|
@@ -2883,7 +2968,7 @@ ${CONTINUE_PROMPT}` : CONTINUE_PROMPT;
|
|
|
2883
2968
|
sessionManager.setCurrentContextSize(sessionId, result.usage.promptTokens);
|
|
2884
2969
|
if (!compacting) {
|
|
2885
2970
|
const contextState2 = sessionManager.getContextState(sessionId);
|
|
2886
|
-
const { shouldCompact, appendCompactionPrompt } = await import("./compactor-
|
|
2971
|
+
const { shouldCompact, appendCompactionPrompt } = await import("./compactor-V43WJ3WX.js");
|
|
2887
2972
|
if (shouldCompact(contextState2.currentTokens, contextState2.maxTokens, runtimeConfig.context.compactionThreshold)) {
|
|
2888
2973
|
appendCompactionPrompt(sessionId, append);
|
|
2889
2974
|
compacting = true;
|
|
@@ -2964,7 +3049,7 @@ ${COMPACTION_PROMPT}`,
|
|
|
2964
3049
|
toolRegistry,
|
|
2965
3050
|
sessionManager,
|
|
2966
3051
|
sessionId,
|
|
2967
|
-
workdir: session.workdir,
|
|
3052
|
+
workdir: session.worktree ?? session.workdir,
|
|
2968
3053
|
turnMetrics,
|
|
2969
3054
|
signal,
|
|
2970
3055
|
onMessage,
|
|
@@ -3585,7 +3670,7 @@ async function loadGitIgnoreRules(workdir) {
|
|
|
3585
3670
|
while (true) {
|
|
3586
3671
|
const gitignorePath = join7(currentDir, ".gitignore");
|
|
3587
3672
|
try {
|
|
3588
|
-
await
|
|
3673
|
+
await access5(gitignorePath);
|
|
3589
3674
|
const content = await readFile8(gitignorePath, "utf-8");
|
|
3590
3675
|
const trimmed = content.trim();
|
|
3591
3676
|
if (!trimmed) return "";
|
|
@@ -3673,13 +3758,14 @@ async function executeSubAgent(options) {
|
|
|
3673
3758
|
})
|
|
3674
3759
|
);
|
|
3675
3760
|
}
|
|
3676
|
-
const
|
|
3761
|
+
const effectiveWorkdir = session.worktree ?? session.workdir;
|
|
3762
|
+
const { content: instructionContent } = await getAllInstructions(effectiveWorkdir, session.projectId);
|
|
3677
3763
|
const config = getRuntimeConfig();
|
|
3678
3764
|
const configDir = getGlobalConfigDir(config.mode ?? "production");
|
|
3679
3765
|
const skills = await getEnabledSkillMetadata(configDir, config.workdir);
|
|
3680
3766
|
const hasRunCommand = agentDef.metadata.allowedTools?.includes("run_command") ?? false;
|
|
3681
|
-
const gitignoreSection = hasRunCommand ? await loadGitIgnoreRules(
|
|
3682
|
-
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;
|
|
3683
3769
|
const subAgentScope = { type: "subagent", sessionId, subAgentId, subAgentType };
|
|
3684
3770
|
const loopResult = await runTopLevelAgentLoop(
|
|
3685
3771
|
{
|
|
@@ -3787,7 +3873,7 @@ var callSubAgentTool = {
|
|
|
3787
3873
|
};
|
|
3788
3874
|
}
|
|
3789
3875
|
try {
|
|
3790
|
-
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-
|
|
3876
|
+
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-2EB7C6H2.js");
|
|
3791
3877
|
const toolRegistry = getToolRegistryForAgent2(agentDef);
|
|
3792
3878
|
const turnMetrics = new TurnMetrics();
|
|
3793
3879
|
const result = await executeSubAgent({
|
|
@@ -4071,11 +4157,15 @@ var webFetchTool = createTool(
|
|
|
4071
4157
|
);
|
|
4072
4158
|
|
|
4073
4159
|
// src/server/dev-server/manager.ts
|
|
4074
|
-
import { spawn as
|
|
4160
|
+
import { spawn as spawn3 } from "child_process";
|
|
4075
4161
|
import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
|
|
4076
4162
|
import { resolve as resolve5, join as join8 } from "path";
|
|
4163
|
+
import net from "net";
|
|
4077
4164
|
var MAX_LOG_LINES = 2e3;
|
|
4078
4165
|
var MAX_LOG_BYTES = 1e5;
|
|
4166
|
+
var DEFAULT_PROBE_PORT = 3e3;
|
|
4167
|
+
var MAX_PORT_SCAN = 200;
|
|
4168
|
+
var PROBE_TIMEOUT_MS = 300;
|
|
4079
4169
|
var getDevServerConfigPath = (workdir) => {
|
|
4080
4170
|
const mode = getRuntimeConfig().mode ?? "production";
|
|
4081
4171
|
return join8(resolve5(workdir), ".openfox", `${mode === "development" ? "dev-dev" : "dev"}.json`);
|
|
@@ -4096,6 +4186,9 @@ function createInstance() {
|
|
|
4096
4186
|
process: null,
|
|
4097
4187
|
state: "off",
|
|
4098
4188
|
config: null,
|
|
4189
|
+
resolvedUrl: null,
|
|
4190
|
+
resolvedCommand: null,
|
|
4191
|
+
assignedPort: null,
|
|
4099
4192
|
logs: [],
|
|
4100
4193
|
totalLogBytes: 0,
|
|
4101
4194
|
errorMessage: void 0,
|
|
@@ -4104,6 +4197,17 @@ function createInstance() {
|
|
|
4104
4197
|
proxyCleanup: null
|
|
4105
4198
|
};
|
|
4106
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
|
+
}
|
|
4107
4211
|
var DevServerManager = class {
|
|
4108
4212
|
instances = /* @__PURE__ */ new Map();
|
|
4109
4213
|
outputListeners = /* @__PURE__ */ new Set();
|
|
@@ -4136,21 +4240,78 @@ var DevServerManager = class {
|
|
|
4136
4240
|
listener(resolved, state, errorMessage);
|
|
4137
4241
|
}
|
|
4138
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
|
+
*/
|
|
4139
4288
|
async loadConfig(workdir) {
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
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
|
+
}
|
|
4153
4313
|
}
|
|
4314
|
+
return null;
|
|
4154
4315
|
}
|
|
4155
4316
|
async saveConfig(workdir, config) {
|
|
4156
4317
|
const resolved = this.resolveWorkdir(workdir);
|
|
@@ -4168,24 +4329,31 @@ var DevServerManager = class {
|
|
|
4168
4329
|
if (!config) {
|
|
4169
4330
|
return this.getStatus(workdir);
|
|
4170
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;
|
|
4171
4339
|
instance.config = config;
|
|
4172
4340
|
instance.logs = [];
|
|
4173
4341
|
instance.totalLogBytes = 0;
|
|
4174
4342
|
instance.errorMessage = void 0;
|
|
4175
4343
|
instance.exited = false;
|
|
4176
|
-
if (!config.disableInspect &&
|
|
4344
|
+
if (!config.disableInspect && resolvedUrl && this._sessionManager) {
|
|
4177
4345
|
try {
|
|
4178
|
-
const { port, cleanup } = startInspectProxy(
|
|
4346
|
+
const { port, cleanup } = startInspectProxy(resolvedUrl, this._sessionManager, workdir);
|
|
4179
4347
|
instance.inspectProxyPort = port;
|
|
4180
4348
|
instance.proxyCleanup = cleanup;
|
|
4181
|
-
logger.debug("Inspect proxy started", { workdir, port, target:
|
|
4349
|
+
logger.debug("Inspect proxy started", { workdir, port, target: resolvedUrl });
|
|
4182
4350
|
} catch (err) {
|
|
4183
4351
|
logger.warn("Failed to start inspect proxy", { workdir, error: err });
|
|
4184
4352
|
}
|
|
4185
4353
|
}
|
|
4186
4354
|
const resolved = this.resolveWorkdir(workdir);
|
|
4187
4355
|
const shell = getPlatformShell();
|
|
4188
|
-
const proc =
|
|
4356
|
+
const proc = spawn3(shell.command, [...shell.args, resolvedCommand], {
|
|
4189
4357
|
cwd: resolved,
|
|
4190
4358
|
env: { ...process.env, FORCE_COLOR: "1" },
|
|
4191
4359
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -4243,7 +4411,7 @@ ${recentLogs}`.trim();
|
|
|
4243
4411
|
instance.state = "running";
|
|
4244
4412
|
instance.errorMessage = void 0;
|
|
4245
4413
|
this.emitStateChange(workdir, "running", void 0);
|
|
4246
|
-
logger.info("Dev server started", { workdir, command:
|
|
4414
|
+
logger.info("Dev server started", { workdir, command: resolvedCommand, port: assignedPort });
|
|
4247
4415
|
return this.getStatus(workdir);
|
|
4248
4416
|
}
|
|
4249
4417
|
async stop(workdir) {
|
|
@@ -4272,7 +4440,7 @@ ${recentLogs}`.trim();
|
|
|
4272
4440
|
const instance = this.getInstance(workdir);
|
|
4273
4441
|
return {
|
|
4274
4442
|
state: instance.state,
|
|
4275
|
-
url: instance.config?.url ?? null,
|
|
4443
|
+
url: instance.resolvedUrl ?? instance.config?.url ?? null,
|
|
4276
4444
|
hotReload: instance.config?.hotReload ?? false,
|
|
4277
4445
|
config: instance.config,
|
|
4278
4446
|
errorMessage: instance.errorMessage,
|
|
@@ -4318,7 +4486,7 @@ var devServerTool = createTool(
|
|
|
4318
4486
|
type: "function",
|
|
4319
4487
|
function: {
|
|
4320
4488
|
name: "dev_server",
|
|
4321
|
-
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).",
|
|
4322
4490
|
parameters: {
|
|
4323
4491
|
type: "object",
|
|
4324
4492
|
properties: {
|
|
@@ -4709,7 +4877,7 @@ var mcpConfigTool = createTool(
|
|
|
4709
4877
|
mcpNotifyChanged?.(sessionId);
|
|
4710
4878
|
}
|
|
4711
4879
|
async function rebuildTools() {
|
|
4712
|
-
const { setMcpTools: setMcpTools2 } = await import("./tools-
|
|
4880
|
+
const { setMcpTools: setMcpTools2 } = await import("./tools-2EB7C6H2.js");
|
|
4713
4881
|
const mcpTools = createMcpTools(mcpManagerForTools);
|
|
4714
4882
|
setMcpTools2(mcpTools);
|
|
4715
4883
|
}
|
|
@@ -4807,270 +4975,6 @@ var mcpConfigTool = createTool(
|
|
|
4807
4975
|
}
|
|
4808
4976
|
);
|
|
4809
4977
|
|
|
4810
|
-
// src/server/tools/trace-code.ts
|
|
4811
|
-
import { stat as stat2, readFile as readFile10 } from "fs/promises";
|
|
4812
|
-
function graphNode(location, depth, relation, symbolName, symbolKind) {
|
|
4813
|
-
const node = { location, depth, relation, symbolName };
|
|
4814
|
-
if (symbolKind) node.symbolKind = symbolKind;
|
|
4815
|
-
return node;
|
|
4816
|
-
}
|
|
4817
|
-
var MAX_DEPTH = 5;
|
|
4818
|
-
var VALID_DIRECTIONS = ["up", "down", "both"];
|
|
4819
|
-
var SNIPPET_RADIUS = 2;
|
|
4820
|
-
var CACHE_MAX_SIZE = 50;
|
|
4821
|
-
function locationKey(loc) {
|
|
4822
|
-
return `${loc.path}:${loc.line}:${loc.character}`;
|
|
4823
|
-
}
|
|
4824
|
-
function formatLocation(loc, workdir) {
|
|
4825
|
-
const relPath = loc.path.startsWith(workdir) ? loc.path.slice(workdir.length + 1) : loc.path;
|
|
4826
|
-
return `${relPath}:${loc.line + 1}:${loc.character}`;
|
|
4827
|
-
}
|
|
4828
|
-
var fileCache = /* @__PURE__ */ new Map();
|
|
4829
|
-
async function getCachedLines(path, approvedPaths) {
|
|
4830
|
-
if (!approvedPaths.has(path)) return [];
|
|
4831
|
-
const cached = fileCache.get(path);
|
|
4832
|
-
if (cached) {
|
|
4833
|
-
try {
|
|
4834
|
-
const stats = await stat2(path);
|
|
4835
|
-
if (stats.mtimeMs === cached.mtimeMs) {
|
|
4836
|
-
return cached.lines;
|
|
4837
|
-
}
|
|
4838
|
-
} catch {
|
|
4839
|
-
}
|
|
4840
|
-
}
|
|
4841
|
-
try {
|
|
4842
|
-
const stats = await stat2(path);
|
|
4843
|
-
const content = await readFile10(path, "utf-8");
|
|
4844
|
-
const lines = content.split("\n");
|
|
4845
|
-
fileCache.set(path, { mtimeMs: stats.mtimeMs, lines });
|
|
4846
|
-
if (fileCache.size > CACHE_MAX_SIZE) {
|
|
4847
|
-
const oldest = fileCache.keys().next();
|
|
4848
|
-
if (!oldest.done && oldest.value !== void 0) {
|
|
4849
|
-
fileCache.delete(oldest.value);
|
|
4850
|
-
}
|
|
4851
|
-
}
|
|
4852
|
-
return lines;
|
|
4853
|
-
} catch {
|
|
4854
|
-
return [];
|
|
4855
|
-
}
|
|
4856
|
-
}
|
|
4857
|
-
async function getSnippet(path, line, approvedPaths, radius = SNIPPET_RADIUS) {
|
|
4858
|
-
const lines = await getCachedLines(path, approvedPaths);
|
|
4859
|
-
if (lines.length === 0) return "";
|
|
4860
|
-
const start = Math.max(0, line - radius);
|
|
4861
|
-
const end = Math.min(lines.length - 1, line + radius);
|
|
4862
|
-
const lineNumWidth = String(end + 1).length;
|
|
4863
|
-
const result = [];
|
|
4864
|
-
for (let i = start; i <= end; i++) {
|
|
4865
|
-
const gutter = i === line ? ">" : " ";
|
|
4866
|
-
const lineNum = String(i + 1).padStart(lineNumWidth);
|
|
4867
|
-
result.push(`${gutter} ${lineNum}\u2502 ${lines[i] ?? ""}`);
|
|
4868
|
-
}
|
|
4869
|
-
return result.join("\n");
|
|
4870
|
-
}
|
|
4871
|
-
function formatEdgeLabel(relation) {
|
|
4872
|
-
switch (relation) {
|
|
4873
|
-
case "definition":
|
|
4874
|
-
return "definition";
|
|
4875
|
-
case "references":
|
|
4876
|
-
return "reference";
|
|
4877
|
-
case "type-definition":
|
|
4878
|
-
return "type definition";
|
|
4879
|
-
default:
|
|
4880
|
-
return relation;
|
|
4881
|
-
}
|
|
4882
|
-
}
|
|
4883
|
-
async function collectNodes(lsp, startLocations, symbolName, symbolKind, maxDepth, direction) {
|
|
4884
|
-
const visited = /* @__PURE__ */ new Set();
|
|
4885
|
-
const nodes = [];
|
|
4886
|
-
const edges = [];
|
|
4887
|
-
const queue = startLocations.map((loc) => ({ location: loc, depth: 0, relation: "match" }));
|
|
4888
|
-
let idx = 0;
|
|
4889
|
-
while (idx < queue.length) {
|
|
4890
|
-
const item = queue[idx];
|
|
4891
|
-
idx++;
|
|
4892
|
-
const key = locationKey(item.location);
|
|
4893
|
-
if (visited.has(key)) continue;
|
|
4894
|
-
visited.add(key);
|
|
4895
|
-
nodes.push(graphNode(item.location, item.depth, item.relation, symbolName, symbolKind));
|
|
4896
|
-
if (item.parentKey) {
|
|
4897
|
-
const relation = item.relation === "definition" ? "definition" : item.relation === "type-definition" ? "type-definition" : "references";
|
|
4898
|
-
edges.push({ from: item.parentKey, to: key, relation });
|
|
4899
|
-
}
|
|
4900
|
-
if (item.depth >= maxDepth) continue;
|
|
4901
|
-
const { path, line, character } = item.location;
|
|
4902
|
-
if (direction === "down" || direction === "both") {
|
|
4903
|
-
const defs = await lsp.getDefinition(path, line, character);
|
|
4904
|
-
for (const def of defs) {
|
|
4905
|
-
const defKey = locationKey(def);
|
|
4906
|
-
if (!visited.has(defKey)) {
|
|
4907
|
-
queue.push({ location: def, depth: item.depth + 1, relation: "definition", parentKey: key });
|
|
4908
|
-
}
|
|
4909
|
-
}
|
|
4910
|
-
const typeDefs = await lsp.getTypeDefinition(path, line, character);
|
|
4911
|
-
for (const td of typeDefs) {
|
|
4912
|
-
const tdKey = locationKey(td);
|
|
4913
|
-
if (!visited.has(tdKey)) {
|
|
4914
|
-
queue.push({ location: td, depth: item.depth + 1, relation: "type-definition", parentKey: key });
|
|
4915
|
-
}
|
|
4916
|
-
}
|
|
4917
|
-
}
|
|
4918
|
-
if (direction === "up" || direction === "both") {
|
|
4919
|
-
const refs = await lsp.getReferences(path, line, character);
|
|
4920
|
-
for (const ref of refs) {
|
|
4921
|
-
const refKey = locationKey(ref);
|
|
4922
|
-
if (!visited.has(refKey)) {
|
|
4923
|
-
queue.push({ location: ref, depth: item.depth + 1, relation: "reference", parentKey: key });
|
|
4924
|
-
}
|
|
4925
|
-
}
|
|
4926
|
-
}
|
|
4927
|
-
}
|
|
4928
|
-
return { nodes, edges };
|
|
4929
|
-
}
|
|
4930
|
-
async function formatOutput(symbolName, direction, depth, nodes, edges, workdir, approvedPaths) {
|
|
4931
|
-
const lines = [];
|
|
4932
|
-
lines.push(`Symbol: ${symbolName}`);
|
|
4933
|
-
lines.push(`Direction: ${direction} | Depth: ${depth}`);
|
|
4934
|
-
lines.push(`Nodes: ${nodes.length} | Edges: ${edges.length}`);
|
|
4935
|
-
lines.push("");
|
|
4936
|
-
if (nodes.length === 0) {
|
|
4937
|
-
lines.push("No results found.");
|
|
4938
|
-
return lines.join("\n");
|
|
4939
|
-
}
|
|
4940
|
-
const byDepth = /* @__PURE__ */ new Map();
|
|
4941
|
-
for (const node of nodes) {
|
|
4942
|
-
const group = byDepth.get(node.depth) ?? [];
|
|
4943
|
-
group.push(node);
|
|
4944
|
-
byDepth.set(node.depth, group);
|
|
4945
|
-
}
|
|
4946
|
-
for (const [depth2, group] of [...byDepth.entries()].sort(([a], [b]) => a - b)) {
|
|
4947
|
-
lines.push(`\u2500\u2500 Depth ${depth2} \u2500\u2500`);
|
|
4948
|
-
const fileGroups = /* @__PURE__ */ new Map();
|
|
4949
|
-
for (const node of group) {
|
|
4950
|
-
const fileKey = node.location.path;
|
|
4951
|
-
const fileGroup = fileGroups.get(fileKey) ?? [];
|
|
4952
|
-
fileGroup.push(node);
|
|
4953
|
-
fileGroups.set(fileKey, fileGroup);
|
|
4954
|
-
}
|
|
4955
|
-
for (const [filePath, fileNodes] of fileGroups) {
|
|
4956
|
-
const relPath = filePath.startsWith(workdir) ? filePath.slice(workdir.length + 1) : filePath;
|
|
4957
|
-
if (fileNodes.length > 5) {
|
|
4958
|
-
const kinds = [...new Set(fileNodes.map((n) => formatEdgeLabel(n.relation)))];
|
|
4959
|
-
lines.push(` ${kinds.join(", ")} \xD7 ${fileNodes.length} in ${relPath}`);
|
|
4960
|
-
} else {
|
|
4961
|
-
for (const node of fileNodes) {
|
|
4962
|
-
const label = formatEdgeLabel(node.relation);
|
|
4963
|
-
const kind = node.symbolKind ? ` (${node.symbolKind})` : "";
|
|
4964
|
-
const loc = formatLocation(node.location, workdir);
|
|
4965
|
-
lines.push(` ${label}: ${loc}${kind}`);
|
|
4966
|
-
const snippet = await getSnippet(node.location.path, node.location.line, approvedPaths);
|
|
4967
|
-
if (snippet) {
|
|
4968
|
-
for (const snipLine of snippet.split("\n")) {
|
|
4969
|
-
lines.push(` ${snipLine}`);
|
|
4970
|
-
}
|
|
4971
|
-
}
|
|
4972
|
-
}
|
|
4973
|
-
}
|
|
4974
|
-
}
|
|
4975
|
-
lines.push("");
|
|
4976
|
-
}
|
|
4977
|
-
if (edges.length > 0 && edges.length <= 20) {
|
|
4978
|
-
lines.push("Edges:");
|
|
4979
|
-
for (const edge of edges) {
|
|
4980
|
-
lines.push(` ${edge.from} \u2500\u2500${edge.relation}\u2500\u2500\u25B6 ${edge.to}`);
|
|
4981
|
-
}
|
|
4982
|
-
lines.push("");
|
|
4983
|
-
} else if (edges.length > 20) {
|
|
4984
|
-
lines.push(`Edges: ${edges.length} total (suppressed, too many to display individually)`);
|
|
4985
|
-
lines.push("");
|
|
4986
|
-
}
|
|
4987
|
-
return lines.join("\n");
|
|
4988
|
-
}
|
|
4989
|
-
var traceCodeTool = createTool(
|
|
4990
|
-
"trace_code",
|
|
4991
|
-
{
|
|
4992
|
-
type: "function",
|
|
4993
|
-
function: {
|
|
4994
|
-
name: "trace_code",
|
|
4995
|
-
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.',
|
|
4996
|
-
parameters: {
|
|
4997
|
-
type: "object",
|
|
4998
|
-
properties: {
|
|
4999
|
-
symbol: {
|
|
5000
|
-
type: "string",
|
|
5001
|
-
description: 'Symbol name to trace (e.g., "rebuildTools", "handleSubmit", "UserProfile")'
|
|
5002
|
-
},
|
|
5003
|
-
file: {
|
|
5004
|
-
type: "string",
|
|
5005
|
-
description: "Seed file for LSP (any file referencing the symbol). Determines language. Not required to be the definition file. Relative to workdir."
|
|
5006
|
-
},
|
|
5007
|
-
depth: {
|
|
5008
|
-
type: "number",
|
|
5009
|
-
description: "Graph traversal depth (default: 1, max: 5). How many hops to follow. Start with 1 for immediate defs+refs.",
|
|
5010
|
-
default: 1
|
|
5011
|
-
},
|
|
5012
|
-
direction: {
|
|
5013
|
-
type: "string",
|
|
5014
|
-
enum: ["up", "down", "both"],
|
|
5015
|
-
description: '"down" follows definitions, "up" finds references, "both" does both (default).',
|
|
5016
|
-
default: "both"
|
|
5017
|
-
}
|
|
5018
|
-
},
|
|
5019
|
-
required: ["symbol", "file"]
|
|
5020
|
-
}
|
|
5021
|
-
}
|
|
5022
|
-
},
|
|
5023
|
-
async (args, context, helpers) => {
|
|
5024
|
-
const symbol = args.symbol?.trim();
|
|
5025
|
-
if (!symbol) {
|
|
5026
|
-
return helpers.error("symbol is required");
|
|
5027
|
-
}
|
|
5028
|
-
const file = args.file?.trim();
|
|
5029
|
-
if (!file) {
|
|
5030
|
-
return helpers.error("file is required");
|
|
5031
|
-
}
|
|
5032
|
-
const depth = args.depth ?? 1;
|
|
5033
|
-
if (depth > MAX_DEPTH) {
|
|
5034
|
-
return helpers.error(`depth cannot exceed ${MAX_DEPTH}`);
|
|
5035
|
-
}
|
|
5036
|
-
const direction = args.direction ?? "both";
|
|
5037
|
-
if (!VALID_DIRECTIONS.includes(direction)) {
|
|
5038
|
-
return helpers.error(`direction must be one of: ${VALID_DIRECTIONS.join(", ")}`);
|
|
5039
|
-
}
|
|
5040
|
-
const lsp = context.lspManager;
|
|
5041
|
-
if (!lsp) {
|
|
5042
|
-
return helpers.error("LSP is not available. The trace_code tool requires a running LSP server.");
|
|
5043
|
-
}
|
|
5044
|
-
const fullPath = helpers.resolvePath(file);
|
|
5045
|
-
try {
|
|
5046
|
-
await stat2(fullPath);
|
|
5047
|
-
} catch {
|
|
5048
|
-
return helpers.error(`File not found: "${file}". Check that the path exists.`);
|
|
5049
|
-
}
|
|
5050
|
-
let symbols;
|
|
5051
|
-
try {
|
|
5052
|
-
symbols = await lsp.seedAndFindWorkspaceSymbol(symbol, fullPath);
|
|
5053
|
-
} catch {
|
|
5054
|
-
return helpers.error(`Failed to search for symbol "${symbol}". LSP may not be responding.`);
|
|
5055
|
-
}
|
|
5056
|
-
if (symbols.length === 0) {
|
|
5057
|
-
const installHint = lsp.getInstallHint(fullPath);
|
|
5058
|
-
const hint = installHint ? ` ${installHint}` : "";
|
|
5059
|
-
return helpers.error(
|
|
5060
|
-
`Symbol "${symbol}" not found in "${file}". Check that the symbol name is correct and that the LSP server has indexed the project.${hint}`
|
|
5061
|
-
);
|
|
5062
|
-
}
|
|
5063
|
-
const startLocations = symbols.map((s) => s.location);
|
|
5064
|
-
const symbolKind = symbols[0].kind;
|
|
5065
|
-
const { nodes, edges } = await collectNodes(lsp, startLocations, symbol, symbolKind, depth, direction);
|
|
5066
|
-
const allPaths = [...new Set(nodes.map((n) => n.location.path))];
|
|
5067
|
-
await helpers.checkPathAccess(allPaths);
|
|
5068
|
-
const approvedPaths = new Set(allPaths);
|
|
5069
|
-
const output = await formatOutput(symbol, direction, depth, nodes, edges, context.workdir, approvedPaths);
|
|
5070
|
-
return helpers.success(output);
|
|
5071
|
-
}
|
|
5072
|
-
);
|
|
5073
|
-
|
|
5074
4978
|
// src/server/tools/web-search.ts
|
|
5075
4979
|
var DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
5076
4980
|
var DEFAULT_MAX_RESULTS = 5;
|
|
@@ -5206,6 +5110,120 @@ var webSearchTool = createTool(
|
|
|
5206
5110
|
}
|
|
5207
5111
|
);
|
|
5208
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
|
+
|
|
5209
5227
|
// src/server/tools/index.ts
|
|
5210
5228
|
var _builtInTools;
|
|
5211
5229
|
function getBuiltInTools() {
|
|
@@ -5226,7 +5244,7 @@ function getBuiltInTools() {
|
|
|
5226
5244
|
stepDoneTool,
|
|
5227
5245
|
backgroundProcessTool,
|
|
5228
5246
|
mcpConfigTool,
|
|
5229
|
-
|
|
5247
|
+
worktreeTool
|
|
5230
5248
|
];
|
|
5231
5249
|
}
|
|
5232
5250
|
return _builtInTools;
|
|
@@ -5524,4 +5542,4 @@ export {
|
|
|
5524
5542
|
getToolRegistryForAgent,
|
|
5525
5543
|
createToolRegistry
|
|
5526
5544
|
};
|
|
5527
|
-
//# sourceMappingURL=chunk-
|
|
5545
|
+
//# sourceMappingURL=chunk-3ACDQKK6.js.map
|