openfox 2.0.60 → 2.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-defaults/builder.agent.md +1 -0
- package/dist/agent-defaults/planner.agent.md +1 -0
- package/dist/{chat-handler-MBR6ZQTX.js → chat-handler-TRAITVLO.js} +4 -4
- package/dist/{chunk-MTBQQ5DN.js → chunk-3WL5YCP6.js} +2 -2
- package/dist/{chunk-DOJKA4XO.js → chunk-BKTBHEPY.js} +5 -5
- package/dist/{chunk-NY6IGGKZ.js → chunk-L4WACPUX.js} +65 -6
- package/dist/{chunk-DEWNWF2P.js → chunk-QZRS2RF5.js} +72 -22
- package/dist/{chunk-WNSAJCWT.js → chunk-SVSKQYFH.js} +6 -2
- package/dist/{chunk-3JRYK5UG.js → chunk-WEB4WBUF.js} +306 -88
- package/dist/cli/dev.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/{orchestrator-PIZJ42J2.js → orchestrator-TQ23BAFQ.js} +4 -4
- package/dist/package.json +2 -1
- package/dist/{processor-PKKTNKWY.js → processor-3ZOFGZP5.js} +4 -4
- package/dist/{serve-GKS55Y2C.js → serve-CNA5NKOR.js} +6 -6
- package/dist/server/index.js +5 -5
- package/dist/{server-LMH7ZH5X.js → server-CI5IEBOC.js} +5 -5
- package/dist/{settings-2TQA7VTP.js → settings-ASLTSUE2.js} +2 -2
- package/dist/{tools-WGAZKL56.js → tools-33FQNFKC.js} +3 -3
- package/dist/web/assets/{index-D4bVx84a.css → index-BGkyTfuQ.css} +1 -1
- package/dist/web/assets/index-CETZPQXr.js +302 -0
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +2 -1
- package/dist/web/assets/index-C0YKw2cD.js +0 -302
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
deleteSetting,
|
|
35
35
|
getSetting,
|
|
36
36
|
setSetting
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-SVSKQYFH.js";
|
|
38
38
|
import {
|
|
39
39
|
getProject
|
|
40
40
|
} from "./chunk-YQ3SOPBI.js";
|
|
@@ -42,7 +42,6 @@ import {
|
|
|
42
42
|
COMPACTION_PROMPT,
|
|
43
43
|
TurnMetrics,
|
|
44
44
|
buildBasePrompt,
|
|
45
|
-
buildTopLevelSystemPrompt,
|
|
46
45
|
computeEffectiveTools,
|
|
47
46
|
consumeStreamGenerator,
|
|
48
47
|
createChatDoneEvent,
|
|
@@ -91,8 +90,11 @@ var OUTPUT_LIMITS = {
|
|
|
91
90
|
read_file: {
|
|
92
91
|
maxLines: 2e3,
|
|
93
92
|
maxBytes: 1e5,
|
|
94
|
-
maxImageBytes: 2097152
|
|
93
|
+
maxImageBytes: 2097152,
|
|
95
94
|
// 2MB for images
|
|
95
|
+
maxPdfPages: 50,
|
|
96
|
+
maxFileBytes: 20971520
|
|
97
|
+
// 20MB general file safety limit
|
|
96
98
|
},
|
|
97
99
|
run_command: {
|
|
98
100
|
maxLines: 2e3,
|
|
@@ -224,7 +226,7 @@ function looksLikeRegex(str) {
|
|
|
224
226
|
return /[*?+[\]\\]/.test(str);
|
|
225
227
|
}
|
|
226
228
|
function isPlaceholderToken(str) {
|
|
227
|
-
return str.includes("__URL__") || str.includes("__FILEURL__") || str.includes("__SED__");
|
|
229
|
+
return str.includes("__URL__") || str.includes("__FILEURL__") || str.includes("__SED__") || str.includes("__COMMIT_MSG__");
|
|
228
230
|
}
|
|
229
231
|
function extractAbsolutePathsFromCommand(command) {
|
|
230
232
|
if (!command.trim()) {
|
|
@@ -236,6 +238,10 @@ function extractAbsolutePathsFromCommand(command) {
|
|
|
236
238
|
sanitized = sanitized.replace(/s\/[^/]*\/[^/]*\/[gip]*/g, " __SED__ ");
|
|
237
239
|
sanitized = sanitized.replace(/s\|[^|]*\|[^|]*\|[gip]*/g, " __SED__ ");
|
|
238
240
|
sanitized = sanitized.replace(/s:[^:]*:[^:]*:[gip]*/g, " __SED__ ");
|
|
241
|
+
sanitized = sanitized.replace(
|
|
242
|
+
/git\s+commit\s+(?:-[^-]\s*|--message\s+)(["'])(?:\\?.)*?\1/gi,
|
|
243
|
+
(match2) => match2.replace(/\/[^\s"'|&;<>`()]+/g, " __COMMIT_MSG__ ")
|
|
244
|
+
);
|
|
239
245
|
const fileUrlMatches = command.matchAll(/file:\/\/([^\s'"]+)/g);
|
|
240
246
|
for (const match2 of fileUrlMatches) {
|
|
241
247
|
const filePath = match2[1];
|
|
@@ -489,6 +495,13 @@ function cancelPathConfirmationsForSession(sessionId, reason) {
|
|
|
489
495
|
}
|
|
490
496
|
|
|
491
497
|
// src/server/tools/tool-helpers.ts
|
|
498
|
+
function buildSignal(timeoutMs, contextSignal) {
|
|
499
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
500
|
+
if (contextSignal) {
|
|
501
|
+
return AbortSignal.any([timeoutSignal, contextSignal]);
|
|
502
|
+
}
|
|
503
|
+
return timeoutSignal;
|
|
504
|
+
}
|
|
492
505
|
function validateAction(action, allowed, startTime) {
|
|
493
506
|
if (!action || !allowed.includes(action)) {
|
|
494
507
|
return {
|
|
@@ -809,6 +822,67 @@ function stringToUtf32(str, littleEndian) {
|
|
|
809
822
|
|
|
810
823
|
// src/server/tools/read.ts
|
|
811
824
|
import { fileTypeFromBuffer } from "file-type";
|
|
825
|
+
|
|
826
|
+
// src/server/tools/pdf-utils.ts
|
|
827
|
+
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
|
|
828
|
+
var PDF_HEADER = Buffer.from("%PDF");
|
|
829
|
+
function isPdfBuffer(buffer) {
|
|
830
|
+
return buffer.length > 4 && buffer.subarray(0, 4).equals(PDF_HEADER);
|
|
831
|
+
}
|
|
832
|
+
function isPasswordError(err) {
|
|
833
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
834
|
+
return message.toLowerCase().includes("password") || message.toLowerCase().includes("encrypt");
|
|
835
|
+
}
|
|
836
|
+
function formatPdfErrorMessage(err) {
|
|
837
|
+
if (isPasswordError(err)) {
|
|
838
|
+
return "This PDF is password-protected. Unlock it with an external tool first.";
|
|
839
|
+
}
|
|
840
|
+
return `Failed to read PDF: ${err instanceof Error ? err.message : String(err)}`;
|
|
841
|
+
}
|
|
842
|
+
function processPdfContent(text, maxBytes) {
|
|
843
|
+
let output = text;
|
|
844
|
+
let truncated = false;
|
|
845
|
+
if (output.length > maxBytes) {
|
|
846
|
+
output = output.slice(0, maxBytes) + "\n\n[Output truncated due to size limit]";
|
|
847
|
+
truncated = true;
|
|
848
|
+
}
|
|
849
|
+
const isScanned = output.replace(/\[Page \d+\/\d+\]\n/g, "").trim().length === 0;
|
|
850
|
+
return { output, truncated, isScanned };
|
|
851
|
+
}
|
|
852
|
+
async function extractPdfText(buffer) {
|
|
853
|
+
const doc = await getDocument({ data: Uint8Array.from(buffer) }).promise;
|
|
854
|
+
const pageCount = doc.numPages;
|
|
855
|
+
const rawMeta = await doc.getMetadata();
|
|
856
|
+
const info = rawMeta.info;
|
|
857
|
+
const title = info?.["Title"] || null;
|
|
858
|
+
const author = info?.["Author"] || null;
|
|
859
|
+
const pages = [];
|
|
860
|
+
const limitedPageCount = Math.min(pageCount, OUTPUT_LIMITS.read_file.maxPdfPages);
|
|
861
|
+
try {
|
|
862
|
+
for (let i = 1; i <= limitedPageCount; i++) {
|
|
863
|
+
const page = await doc.getPage(i);
|
|
864
|
+
try {
|
|
865
|
+
const content = await page.getTextContent();
|
|
866
|
+
const pageText = content.items.map((item) => "str" in item ? item.str : "").join(" ");
|
|
867
|
+
pages.push(`[Page ${i}/${pageCount}]
|
|
868
|
+
${pageText}`);
|
|
869
|
+
} finally {
|
|
870
|
+
page.cleanup();
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
} finally {
|
|
874
|
+
doc.cleanup();
|
|
875
|
+
}
|
|
876
|
+
let text = pages.join("\n\n");
|
|
877
|
+
if (pageCount > limitedPageCount) {
|
|
878
|
+
text += `
|
|
879
|
+
|
|
880
|
+
[PDF has ${pageCount} pages, showing first ${limitedPageCount}. Use a shell command to process more.]`;
|
|
881
|
+
}
|
|
882
|
+
return { text, pageCount, title, author };
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/server/tools/read.ts
|
|
812
886
|
async function detectImageType(buffer, filePath) {
|
|
813
887
|
const fileType = await fileTypeFromBuffer(buffer);
|
|
814
888
|
const imageMimeTypes = ["image/png", "image/jpeg", "image/gif", "image/webp", "image/bmp", "image/svg+xml"];
|
|
@@ -862,7 +936,7 @@ var readFileTool = createTool(
|
|
|
862
936
|
type: "function",
|
|
863
937
|
function: {
|
|
864
938
|
name: "read_file",
|
|
865
|
-
description: "Read the contents of a file or list directory contents. For text files: returns file content. For images (PNG, JPEG, GIF, WebP, BMP, SVG): returns base64-encoded data with MIME type metadata. For directories: returns a tree-formatted listing of entries with file sizes.",
|
|
939
|
+
description: "Read the contents of a file or list directory contents. For text files: returns file content. For images (PNG, JPEG, GIF, WebP, BMP, SVG): returns base64-encoded data with MIME type metadata. For PDF files: extracts text content page by page. For directories: returns a tree-formatted listing of entries with file sizes.",
|
|
866
940
|
parameters: {
|
|
867
941
|
type: "object",
|
|
868
942
|
properties: {
|
|
@@ -894,6 +968,11 @@ var readFileTool = createTool(
|
|
|
894
968
|
const listing = await listDirectory(fullPath, args.path);
|
|
895
969
|
return helpers.success(listing);
|
|
896
970
|
}
|
|
971
|
+
if (stats.size > OUTPUT_LIMITS.read_file.maxFileBytes) {
|
|
972
|
+
return helpers.error(
|
|
973
|
+
`File size (${stats.size} bytes) exceeds maximum file size (20MB). Use shell command to process large files.`
|
|
974
|
+
);
|
|
975
|
+
}
|
|
897
976
|
if (stats.size > OUTPUT_LIMITS.read_file.maxImageBytes) {
|
|
898
977
|
return helpers.error(
|
|
899
978
|
`File size (${stats.size} bytes) exceeds image size limit (2MB). Use shell command to process large files.`
|
|
@@ -921,6 +1000,42 @@ var readFileTool = createTool(
|
|
|
921
1000
|
}
|
|
922
1001
|
});
|
|
923
1002
|
}
|
|
1003
|
+
if (isPdfBuffer(rawBuffer)) {
|
|
1004
|
+
try {
|
|
1005
|
+
const { text, pageCount, title, author } = await extractPdfText(rawBuffer);
|
|
1006
|
+
const { output: output2, truncated: truncated2, isScanned } = processPdfContent(text, OUTPUT_LIMITS.read_file.maxBytes);
|
|
1007
|
+
const contentHash3 = await computeFileHash(fullPath);
|
|
1008
|
+
if (contentHash3) {
|
|
1009
|
+
context.sessionManager.recordFileRead(context.sessionId, fullPath, contentHash3);
|
|
1010
|
+
}
|
|
1011
|
+
if (isScanned) {
|
|
1012
|
+
return helpers.success(
|
|
1013
|
+
`[PDF: ${args.path} \u2014 This PDF has no text layer (scanned or image-only). Use a shell command with OCR to extract text.]`,
|
|
1014
|
+
false,
|
|
1015
|
+
{
|
|
1016
|
+
metadata: {
|
|
1017
|
+
format: "pdf",
|
|
1018
|
+
pageCount,
|
|
1019
|
+
title,
|
|
1020
|
+
author,
|
|
1021
|
+
path: fullPath
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
return helpers.success(output2, truncated2, {
|
|
1027
|
+
metadata: {
|
|
1028
|
+
format: "pdf",
|
|
1029
|
+
pageCount,
|
|
1030
|
+
title,
|
|
1031
|
+
author,
|
|
1032
|
+
path: fullPath
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
return helpers.error(formatPdfErrorMessage(err));
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
924
1039
|
const { encoding, confidence } = detectEncoding(rawBuffer);
|
|
925
1040
|
const content = decodeContent(rawBuffer, encoding);
|
|
926
1041
|
const lines = content.split("\n");
|
|
@@ -3651,7 +3766,7 @@ var callSubAgentTool = {
|
|
|
3651
3766
|
};
|
|
3652
3767
|
}
|
|
3653
3768
|
try {
|
|
3654
|
-
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-
|
|
3769
|
+
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-33FQNFKC.js");
|
|
3655
3770
|
const toolRegistry = getToolRegistryForAgent2(agentDef);
|
|
3656
3771
|
const turnMetrics = new TurnMetrics();
|
|
3657
3772
|
const result = await executeSubAgent({
|
|
@@ -3790,13 +3905,6 @@ function buildAcceptHeader(format) {
|
|
|
3790
3905
|
return "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
|
|
3791
3906
|
}
|
|
3792
3907
|
}
|
|
3793
|
-
function buildSignal(timeoutMs, contextSignal) {
|
|
3794
|
-
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
3795
|
-
if (contextSignal) {
|
|
3796
|
-
return AbortSignal.any([timeoutSignal, contextSignal]);
|
|
3797
|
-
}
|
|
3798
|
-
return timeoutSignal;
|
|
3799
|
-
}
|
|
3800
3908
|
function convertHTMLToMarkdown(html) {
|
|
3801
3909
|
const turndown = new TurndownService({
|
|
3802
3910
|
headingStyle: "atx",
|
|
@@ -3818,7 +3926,7 @@ var webFetchTool = createTool(
|
|
|
3818
3926
|
type: "function",
|
|
3819
3927
|
function: {
|
|
3820
3928
|
name: "web_fetch",
|
|
3821
|
-
description: "Fetch content from a URL and return it as text, markdown, or HTML. Use this to retrieve and analyze web content such as documentation, API references, or web pages.",
|
|
3929
|
+
description: "Fetch content from a URL and return it as text, markdown, or HTML. For PDF URLs, extracts text content page by page. Use this to retrieve and analyze web content such as documentation, API references, or web pages.",
|
|
3822
3930
|
parameters: {
|
|
3823
3931
|
type: "object",
|
|
3824
3932
|
properties: {
|
|
@@ -3880,6 +3988,40 @@ var webFetchTool = createTool(
|
|
|
3880
3988
|
}
|
|
3881
3989
|
});
|
|
3882
3990
|
}
|
|
3991
|
+
const rawBuffer = Buffer.from(arrayBuffer);
|
|
3992
|
+
const isPdfResponse = mime === "application/pdf" || isPdfBuffer(rawBuffer);
|
|
3993
|
+
if (isPdfResponse) {
|
|
3994
|
+
try {
|
|
3995
|
+
const { text, pageCount, title, author } = await extractPdfText(rawBuffer);
|
|
3996
|
+
const { output: output2, truncated: truncated2, isScanned } = processPdfContent(text, OUTPUT_LIMITS.web_fetch.maxBytes);
|
|
3997
|
+
if (isScanned) {
|
|
3998
|
+
return helpers.success(
|
|
3999
|
+
"[PDF: This PDF has no text layer (scanned or image-only). Use an external tool with OCR to extract text.]",
|
|
4000
|
+
false,
|
|
4001
|
+
{
|
|
4002
|
+
metadata: {
|
|
4003
|
+
format: "pdf",
|
|
4004
|
+
pageCount,
|
|
4005
|
+
title,
|
|
4006
|
+
author,
|
|
4007
|
+
url
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
);
|
|
4011
|
+
}
|
|
4012
|
+
return helpers.success(output2, truncated2, {
|
|
4013
|
+
metadata: {
|
|
4014
|
+
format: "pdf",
|
|
4015
|
+
pageCount,
|
|
4016
|
+
title,
|
|
4017
|
+
author,
|
|
4018
|
+
url
|
|
4019
|
+
}
|
|
4020
|
+
});
|
|
4021
|
+
} catch (err) {
|
|
4022
|
+
return helpers.error(formatPdfErrorMessage(err));
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
3883
4025
|
const rawContent = new TextDecoder().decode(arrayBuffer);
|
|
3884
4026
|
let output;
|
|
3885
4027
|
switch (format) {
|
|
@@ -4446,64 +4588,6 @@ These processes run independently of agent turns and persist across session comp
|
|
|
4446
4588
|
}
|
|
4447
4589
|
);
|
|
4448
4590
|
|
|
4449
|
-
// src/server/chat/dynamic-context.ts
|
|
4450
|
-
import { createHash as createHash3 } from "crypto";
|
|
4451
|
-
function computeDynamicContextHash(instructionContent, skills, toolFingerprint) {
|
|
4452
|
-
const dynamicInputs = JSON.stringify({
|
|
4453
|
-
instructions: instructionContent,
|
|
4454
|
-
skills: skills.map((s) => s.id).sort(),
|
|
4455
|
-
...toolFingerprint ? { tools: toolFingerprint } : {}
|
|
4456
|
-
});
|
|
4457
|
-
return createHash3("sha256").update(dynamicInputs).digest("hex");
|
|
4458
|
-
}
|
|
4459
|
-
function getToolFingerprint(tools) {
|
|
4460
|
-
return tools.map((t) => `${t.function.name}:${JSON.stringify(t.function.parameters)}`).sort().join("|");
|
|
4461
|
-
}
|
|
4462
|
-
async function loadSessionContext(sessionManager, sessionId) {
|
|
4463
|
-
const session = sessionManager.requireSession(sessionId);
|
|
4464
|
-
const { content: instructionContent } = await getAllInstructions(session.workdir, session.projectId);
|
|
4465
|
-
const runtimeConfig = getRuntimeConfig();
|
|
4466
|
-
const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
|
|
4467
|
-
const skills = await getEnabledSkillMetadata(configDir, runtimeConfig.workdir);
|
|
4468
|
-
return { instructionContent: instructionContent ?? "", skills };
|
|
4469
|
-
}
|
|
4470
|
-
function resolveAgentDef2(sessionManager, sessionId) {
|
|
4471
|
-
return loadAllAgentsDefault().then((allAgents) => {
|
|
4472
|
-
const session = sessionManager.requireSession(sessionId);
|
|
4473
|
-
return findAgentById(session.mode, allAgents) ?? findAgentById("planner", allAgents);
|
|
4474
|
-
});
|
|
4475
|
-
}
|
|
4476
|
-
async function buildCachedPrompt(sessionManager, sessionId, agentDef) {
|
|
4477
|
-
const { instructionContent, skills } = await loadSessionContext(sessionManager, sessionId);
|
|
4478
|
-
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-WGAZKL56.js");
|
|
4479
|
-
const tools = getToolRegistryForAgent2(agentDef).definitions;
|
|
4480
|
-
const toolFingerprint = getToolFingerprint(tools);
|
|
4481
|
-
const allAgents = await loadAllAgentsDefault();
|
|
4482
|
-
const subAgentDefs = getSubAgents(allAgents);
|
|
4483
|
-
const session = sessionManager.requireSession(sessionId);
|
|
4484
|
-
const systemPrompt = buildTopLevelSystemPrompt(session.workdir, instructionContent || void 0, skills, subAgentDefs);
|
|
4485
|
-
const hash = computeDynamicContextHash(instructionContent, skills, toolFingerprint);
|
|
4486
|
-
return { systemPrompt, tools, hash };
|
|
4487
|
-
}
|
|
4488
|
-
async function computeSessionHash(sessionManager, sessionId) {
|
|
4489
|
-
const { instructionContent, skills } = await loadSessionContext(sessionManager, sessionId);
|
|
4490
|
-
const agentDef = await resolveAgentDef2(sessionManager, sessionId);
|
|
4491
|
-
const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-WGAZKL56.js");
|
|
4492
|
-
const tools = getToolRegistryForAgent2(agentDef).definitions;
|
|
4493
|
-
const toolFingerprint = getToolFingerprint(tools);
|
|
4494
|
-
return computeDynamicContextHash(instructionContent, skills, toolFingerprint);
|
|
4495
|
-
}
|
|
4496
|
-
async function applyDynamicContext(sessionManager, sessionId) {
|
|
4497
|
-
const session = sessionManager.requireSession(sessionId);
|
|
4498
|
-
const allAgents = await loadAllAgentsDefault();
|
|
4499
|
-
const agentDef = findAgentById(session.mode, allAgents) ?? findAgentById("planner", allAgents);
|
|
4500
|
-
const { systemPrompt, tools, hash } = await buildCachedPrompt(sessionManager, sessionId, agentDef);
|
|
4501
|
-
sessionManager.setCachedPrompt(sessionId, systemPrompt, tools, hash);
|
|
4502
|
-
sessionManager.setDynamicContextChanged(sessionId, false);
|
|
4503
|
-
sessionManager.clearDebugDump(sessionId);
|
|
4504
|
-
logger.debug("applyDynamicContext done", { sessionId, hash, toolCount: tools.length });
|
|
4505
|
-
}
|
|
4506
|
-
|
|
4507
4591
|
// src/server/tools/mcp-config.ts
|
|
4508
4592
|
var mcpManagerForTools = null;
|
|
4509
4593
|
var mcpConfigMode = "production";
|
|
@@ -4598,8 +4682,13 @@ var mcpConfigTool = createTool(
|
|
|
4598
4682
|
const updated = updater(mcpServers);
|
|
4599
4683
|
await saveGlobalConfig(mcpConfigMode, { ...globalConfig, mcpServers: updated }, mcpConfigPath);
|
|
4600
4684
|
}
|
|
4685
|
+
const APPLY_PROMPT_MESSAGE = 'The user must click "Update system prompt" to apply changes.';
|
|
4686
|
+
function notifyContextChanged(sessionId) {
|
|
4687
|
+
context.sessionManager.setDynamicContextChanged(sessionId, true);
|
|
4688
|
+
mcpNotifyChanged?.(sessionId);
|
|
4689
|
+
}
|
|
4601
4690
|
async function rebuildTools() {
|
|
4602
|
-
const { setMcpTools: setMcpTools2 } = await import("./tools-
|
|
4691
|
+
const { setMcpTools: setMcpTools2 } = await import("./tools-33FQNFKC.js");
|
|
4603
4692
|
const mcpTools = createMcpTools(mcpManagerForTools);
|
|
4604
4693
|
setMcpTools2(mcpTools);
|
|
4605
4694
|
}
|
|
@@ -4650,11 +4739,10 @@ var mcpConfigTool = createTool(
|
|
|
4650
4739
|
});
|
|
4651
4740
|
await mcpManagerForTools.addServer(args.name, serverCfg);
|
|
4652
4741
|
await rebuildTools();
|
|
4653
|
-
|
|
4654
|
-
mcpNotifyChanged?.(context.sessionId);
|
|
4742
|
+
notifyContextChanged(context.sessionId);
|
|
4655
4743
|
const server = mcpManagerForTools.getServer(args.name);
|
|
4656
4744
|
const toolCount = server?.tools.length ?? 0;
|
|
4657
|
-
return helpers.success(`Added MCP server "${args.name}" (${toolCount} tools discovered)
|
|
4745
|
+
return helpers.success(`Added MCP server "${args.name}" (${toolCount} tools discovered). ${APPLY_PROMPT_MESSAGE}`);
|
|
4658
4746
|
}
|
|
4659
4747
|
if (args.action === "remove") {
|
|
4660
4748
|
if (!args.name) return helpers.error("Missing required field: name");
|
|
@@ -4664,9 +4752,8 @@ var mcpConfigTool = createTool(
|
|
|
4664
4752
|
});
|
|
4665
4753
|
mcpManagerForTools.removeServer(args.name);
|
|
4666
4754
|
await rebuildTools();
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
return helpers.success(`Removed MCP server "${args.name}".`);
|
|
4755
|
+
notifyContextChanged(context.sessionId);
|
|
4756
|
+
return helpers.success(`Removed MCP server "${args.name}". ${APPLY_PROMPT_MESSAGE}`);
|
|
4670
4757
|
}
|
|
4671
4758
|
if (args.action === "toggle-tool") {
|
|
4672
4759
|
if (!args.name) return helpers.error("Missing required field: name");
|
|
@@ -4690,10 +4777,9 @@ var mcpConfigTool = createTool(
|
|
|
4690
4777
|
});
|
|
4691
4778
|
await mcpManagerForTools.setToolEnabled(args.name, args.toolName, args.enabled);
|
|
4692
4779
|
await rebuildTools();
|
|
4693
|
-
|
|
4694
|
-
mcpNotifyChanged?.(context.sessionId);
|
|
4780
|
+
notifyContextChanged(context.sessionId);
|
|
4695
4781
|
return helpers.success(
|
|
4696
|
-
`Tool "${args.toolName}" ${args.enabled ? "enabled" : "disabled"} on server "${args.name}"
|
|
4782
|
+
`Tool "${args.toolName}" ${args.enabled ? "enabled" : "disabled"} on server "${args.name}". ${APPLY_PROMPT_MESSAGE}`
|
|
4697
4783
|
);
|
|
4698
4784
|
}
|
|
4699
4785
|
return helpers.error("Unexpected error");
|
|
@@ -4964,6 +5050,141 @@ var traceCodeTool = createTool(
|
|
|
4964
5050
|
}
|
|
4965
5051
|
);
|
|
4966
5052
|
|
|
5053
|
+
// src/server/tools/web-search.ts
|
|
5054
|
+
var DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
5055
|
+
var DEFAULT_MAX_RESULTS = 5;
|
|
5056
|
+
function getTavilyApiKey() {
|
|
5057
|
+
return process.env["TAVILY_API_KEY"] ?? getSetting(SETTINGS_KEYS.SEARCH_TAVILY_API_KEY);
|
|
5058
|
+
}
|
|
5059
|
+
function getSearxngUrl() {
|
|
5060
|
+
return process.env["SEARXNG_URL"] ?? getSetting(SETTINGS_KEYS.SEARCH_SEARXNG_URL);
|
|
5061
|
+
}
|
|
5062
|
+
function getSearxngApiKey() {
|
|
5063
|
+
return process.env["SEARXNG_API_KEY"] ?? getSetting(SETTINGS_KEYS.SEARCH_SEARXNG_API_KEY);
|
|
5064
|
+
}
|
|
5065
|
+
function getActiveEngine() {
|
|
5066
|
+
const preferredEngine = getSetting(SETTINGS_KEYS.SEARCH_ENGINE);
|
|
5067
|
+
if (preferredEngine === "") {
|
|
5068
|
+
return null;
|
|
5069
|
+
}
|
|
5070
|
+
if (preferredEngine === "tavily") {
|
|
5071
|
+
const apiKey = getTavilyApiKey();
|
|
5072
|
+
if (!apiKey) return null;
|
|
5073
|
+
return { engine: "tavily", apiKey };
|
|
5074
|
+
}
|
|
5075
|
+
if (preferredEngine === "searxng") {
|
|
5076
|
+
const url = getSearxngUrl();
|
|
5077
|
+
if (!url) return null;
|
|
5078
|
+
const searxngApiKey = getSearxngApiKey();
|
|
5079
|
+
return searxngApiKey ? { engine: "searxng", url, apiKey: searxngApiKey } : { engine: "searxng", url };
|
|
5080
|
+
}
|
|
5081
|
+
const tavilyKey = getTavilyApiKey();
|
|
5082
|
+
if (tavilyKey) return { engine: "tavily", apiKey: tavilyKey };
|
|
5083
|
+
const searxngUrl = getSearxngUrl();
|
|
5084
|
+
if (searxngUrl) {
|
|
5085
|
+
const searxngApiKey = getSearxngApiKey();
|
|
5086
|
+
return searxngApiKey ? { engine: "searxng", url: searxngUrl, apiKey: searxngApiKey } : { engine: "searxng", url: searxngUrl };
|
|
5087
|
+
}
|
|
5088
|
+
return null;
|
|
5089
|
+
}
|
|
5090
|
+
async function searchTavily(query, maxResults, apiKey, signal) {
|
|
5091
|
+
const response = await fetch("https://api.tavily.com/search", {
|
|
5092
|
+
method: "POST",
|
|
5093
|
+
headers: { "Content-Type": "application/json" },
|
|
5094
|
+
body: JSON.stringify({ api_key: apiKey, query, max_results: maxResults }),
|
|
5095
|
+
signal
|
|
5096
|
+
});
|
|
5097
|
+
if (!response.ok) {
|
|
5098
|
+
const body = await response.text().catch(() => "");
|
|
5099
|
+
throw new Error(`Tavily search failed (${response.status}): ${body}`);
|
|
5100
|
+
}
|
|
5101
|
+
const data = await response.json();
|
|
5102
|
+
return (data.results ?? []).map((r) => ({ title: r.title, url: r.url, content: r.content }));
|
|
5103
|
+
}
|
|
5104
|
+
async function searchSearxng(query, maxResults, config, signal) {
|
|
5105
|
+
const baseUrl = config.url.replace(/\/+$/, "");
|
|
5106
|
+
const url = new URL(`${baseUrl}/search`);
|
|
5107
|
+
url.searchParams.set("format", "json");
|
|
5108
|
+
url.searchParams.set("q", query);
|
|
5109
|
+
const headers = {};
|
|
5110
|
+
if (config.apiKey) {
|
|
5111
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
5112
|
+
}
|
|
5113
|
+
const response = await fetch(url.toString(), { headers, signal });
|
|
5114
|
+
if (!response.ok) {
|
|
5115
|
+
const body = await response.text().catch(() => "");
|
|
5116
|
+
throw new Error(`SearXNG search failed (${response.status}): ${body}`);
|
|
5117
|
+
}
|
|
5118
|
+
const data = await response.json();
|
|
5119
|
+
return (data.results ?? []).slice(0, maxResults).map((r) => ({
|
|
5120
|
+
title: r.title,
|
|
5121
|
+
url: r.url,
|
|
5122
|
+
content: r.content
|
|
5123
|
+
}));
|
|
5124
|
+
}
|
|
5125
|
+
function formatResults(results) {
|
|
5126
|
+
return results.map((r, i) => `[${i + 1}] ${r.title}
|
|
5127
|
+
URL: ${r.url}
|
|
5128
|
+
${r.content}`).join("\n\n");
|
|
5129
|
+
}
|
|
5130
|
+
function clampMaxResults(maxResults) {
|
|
5131
|
+
return maxResults > 0 ? maxResults : DEFAULT_MAX_RESULTS;
|
|
5132
|
+
}
|
|
5133
|
+
var webSearchTool = createTool(
|
|
5134
|
+
"web_search",
|
|
5135
|
+
{
|
|
5136
|
+
type: "function",
|
|
5137
|
+
function: {
|
|
5138
|
+
name: "web_search",
|
|
5139
|
+
description: "Search the web using a configured search engine (Tavily or SearXNG). Returns a list of results with title, URL, and content snippet for each. Use this to find relevant web pages, then use web_fetch to retrieve full content.",
|
|
5140
|
+
parameters: {
|
|
5141
|
+
type: "object",
|
|
5142
|
+
properties: {
|
|
5143
|
+
query: {
|
|
5144
|
+
type: "string",
|
|
5145
|
+
description: "The search query"
|
|
5146
|
+
},
|
|
5147
|
+
max_results: {
|
|
5148
|
+
type: "number",
|
|
5149
|
+
description: "Maximum number of search results to return (default: 5)"
|
|
5150
|
+
}
|
|
5151
|
+
},
|
|
5152
|
+
required: ["query"]
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
},
|
|
5156
|
+
async (args, context, helpers) => {
|
|
5157
|
+
const query = args.query;
|
|
5158
|
+
const maxResults = clampMaxResults(args.max_results ?? DEFAULT_MAX_RESULTS);
|
|
5159
|
+
const active = getActiveEngine();
|
|
5160
|
+
if (!active) {
|
|
5161
|
+
return helpers.error(
|
|
5162
|
+
"web_search requires at least one search engine configured. Set the TAVILY_API_KEY environment variable, or configure SearXNG via SEARXNG_URL (and optionally SEARXNG_API_KEY). You can also configure these in Settings > Advanced > Search Engine."
|
|
5163
|
+
);
|
|
5164
|
+
}
|
|
5165
|
+
const signal = buildSignal(DEFAULT_TIMEOUT_MS2, context.signal);
|
|
5166
|
+
try {
|
|
5167
|
+
let results;
|
|
5168
|
+
if (active.engine === "tavily") {
|
|
5169
|
+
results = await searchTavily(query, maxResults, active.apiKey, signal);
|
|
5170
|
+
} else {
|
|
5171
|
+
const searxngConfig = { url: active.url };
|
|
5172
|
+
if (active.apiKey) searxngConfig.apiKey = active.apiKey;
|
|
5173
|
+
results = await searchSearxng(query, maxResults, searxngConfig, signal);
|
|
5174
|
+
}
|
|
5175
|
+
if (results.length === 0) {
|
|
5176
|
+
return helpers.success("No search results found.", false);
|
|
5177
|
+
}
|
|
5178
|
+
return helpers.success(formatResults(results), false);
|
|
5179
|
+
} catch (error) {
|
|
5180
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
5181
|
+
return helpers.error("Search request timed out (15s). Try a more specific query.");
|
|
5182
|
+
}
|
|
5183
|
+
return helpers.error(error instanceof Error ? error.message : "Search request failed");
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
);
|
|
5187
|
+
|
|
4967
5188
|
// src/server/tools/index.ts
|
|
4968
5189
|
var _builtInTools;
|
|
4969
5190
|
function getBuiltInTools() {
|
|
@@ -4979,6 +5200,7 @@ function getBuiltInTools() {
|
|
|
4979
5200
|
loadSkillTool,
|
|
4980
5201
|
returnValueTool,
|
|
4981
5202
|
webFetchTool,
|
|
5203
|
+
webSearchTool,
|
|
4982
5204
|
devServerTool,
|
|
4983
5205
|
stepDoneTool,
|
|
4984
5206
|
backgroundProcessTool,
|
|
@@ -5226,6 +5448,7 @@ export {
|
|
|
5226
5448
|
getDefaultAgentIds,
|
|
5227
5449
|
isDefaultAgent,
|
|
5228
5450
|
findAgentById,
|
|
5451
|
+
getSubAgents,
|
|
5229
5452
|
getTopLevelAgents,
|
|
5230
5453
|
agentExists,
|
|
5231
5454
|
saveAgent,
|
|
@@ -5256,11 +5479,6 @@ export {
|
|
|
5256
5479
|
executeSubAgent,
|
|
5257
5480
|
devServerManager,
|
|
5258
5481
|
stepDoneTool,
|
|
5259
|
-
computeDynamicContextHash,
|
|
5260
|
-
getToolFingerprint,
|
|
5261
|
-
buildCachedPrompt,
|
|
5262
|
-
computeSessionHash,
|
|
5263
|
-
applyDynamicContext,
|
|
5264
5482
|
setMcpManagerForTools,
|
|
5265
5483
|
setMcpConfigMode,
|
|
5266
5484
|
setMcpConfigPath,
|
|
@@ -5274,4 +5492,4 @@ export {
|
|
|
5274
5492
|
getToolRegistryForAgent,
|
|
5275
5493
|
createToolRegistry
|
|
5276
5494
|
};
|
|
5277
|
-
//# sourceMappingURL=chunk-
|
|
5495
|
+
//# sourceMappingURL=chunk-WEB4WBUF.js.map
|
package/dist/cli/dev.js
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -2,15 +2,15 @@ import {
|
|
|
2
2
|
injectWorkflowKickoffIfNeeded,
|
|
3
3
|
runAgentTurn,
|
|
4
4
|
runChatTurn
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-L4WACPUX.js";
|
|
6
|
+
import "./chunk-WEB4WBUF.js";
|
|
7
7
|
import "./chunk-LBGHZLLH.js";
|
|
8
8
|
import "./chunk-BGTEIA2S.js";
|
|
9
9
|
import "./chunk-PK2RMVMB.js";
|
|
10
10
|
import "./chunk-NWO6GRYE.js";
|
|
11
11
|
import "./chunk-F4PMNP7S.js";
|
|
12
12
|
import "./chunk-EU3WWTFH.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-SVSKQYFH.js";
|
|
14
14
|
import "./chunk-YQ3SOPBI.js";
|
|
15
15
|
import {
|
|
16
16
|
TurnMetrics,
|
|
@@ -43,4 +43,4 @@ export {
|
|
|
43
43
|
runAgentTurn,
|
|
44
44
|
runChatTurn
|
|
45
45
|
};
|
|
46
|
-
//# sourceMappingURL=orchestrator-
|
|
46
|
+
//# sourceMappingURL=orchestrator-TQ23BAFQ.js.map
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openfox",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.62",
|
|
4
4
|
"description": "Local-LLM-first agentic coding assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"multer": "^2.2.0",
|
|
80
80
|
"node-pty": "^1.2.0-beta.12",
|
|
81
81
|
"open": "^11.0.0",
|
|
82
|
+
"pdfjs-dist": "^6.1.200",
|
|
82
83
|
"pyright": "^1.1.408",
|
|
83
84
|
"react-markdown": "^10.1.0",
|
|
84
85
|
"react-syntax-highlighter": "^16.1.1",
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
finalizeTurnCompletion,
|
|
4
4
|
generateSessionNameForSession
|
|
5
5
|
} from "./chunk-NVPVDDQZ.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-WEB4WBUF.js";
|
|
7
7
|
import "./chunk-LBGHZLLH.js";
|
|
8
8
|
import "./chunk-BGTEIA2S.js";
|
|
9
9
|
import "./chunk-PK2RMVMB.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
createSessionRunningMessage
|
|
14
14
|
} from "./chunk-F4PMNP7S.js";
|
|
15
15
|
import "./chunk-EU3WWTFH.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-SVSKQYFH.js";
|
|
17
17
|
import "./chunk-YQ3SOPBI.js";
|
|
18
18
|
import "./chunk-6VDCH5ML.js";
|
|
19
19
|
import {
|
|
@@ -174,7 +174,7 @@ var QueueProcessor = class {
|
|
|
174
174
|
backend: provider?.backend ?? llmClient.getBackend(),
|
|
175
175
|
model: llmClient.getModel()
|
|
176
176
|
};
|
|
177
|
-
const { runChatTurn } = await import("./orchestrator-
|
|
177
|
+
const { runChatTurn } = await import("./orchestrator-TQ23BAFQ.js");
|
|
178
178
|
const runChatTurnParams = buildRunChatTurnParams({
|
|
179
179
|
sessionManager,
|
|
180
180
|
sessionId,
|
|
@@ -222,4 +222,4 @@ var QueueProcessor = class {
|
|
|
222
222
|
export {
|
|
223
223
|
QueueProcessor
|
|
224
224
|
};
|
|
225
|
-
//# sourceMappingURL=processor-
|
|
225
|
+
//# sourceMappingURL=processor-3ZOFGZP5.js.map
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION,
|
|
3
3
|
createServer
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-QZRS2RF5.js";
|
|
5
|
+
import "./chunk-BKTBHEPY.js";
|
|
6
|
+
import "./chunk-L4WACPUX.js";
|
|
7
|
+
import "./chunk-WEB4WBUF.js";
|
|
8
8
|
import "./chunk-LBGHZLLH.js";
|
|
9
9
|
import "./chunk-BGTEIA2S.js";
|
|
10
10
|
import "./chunk-PK2RMVMB.js";
|
|
11
11
|
import "./chunk-NWO6GRYE.js";
|
|
12
12
|
import "./chunk-F4PMNP7S.js";
|
|
13
13
|
import "./chunk-EU3WWTFH.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-SVSKQYFH.js";
|
|
15
15
|
import "./chunk-YQ3SOPBI.js";
|
|
16
16
|
import "./chunk-GJU35MDV.js";
|
|
17
17
|
import "./chunk-6VDCH5ML.js";
|
|
@@ -205,4 +205,4 @@ async function runServe(options) {
|
|
|
205
205
|
export {
|
|
206
206
|
runServe
|
|
207
207
|
};
|
|
208
|
-
//# sourceMappingURL=serve-
|
|
208
|
+
//# sourceMappingURL=serve-CNA5NKOR.js.map
|