openfox 2.0.61 → 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.
@@ -5,8 +5,8 @@ import {
5
5
  } from "./chunk-NVPVDDQZ.js";
6
6
  import {
7
7
  runChatTurn
8
- } from "./chunk-ZHSCXD75.js";
9
- import "./chunk-KX4C5Q5J.js";
8
+ } from "./chunk-L4WACPUX.js";
9
+ import "./chunk-WEB4WBUF.js";
10
10
  import "./chunk-LBGHZLLH.js";
11
11
  import "./chunk-BGTEIA2S.js";
12
12
  import "./chunk-PK2RMVMB.js";
@@ -154,4 +154,4 @@ export {
154
154
  startChatSession,
155
155
  stopSessionExecution
156
156
  };
157
- //# sourceMappingURL=chat-handler-6DRGMDXZ.js.map
157
+ //# sourceMappingURL=chat-handler-TRAITVLO.js.map
@@ -208,7 +208,7 @@ async function runCli(options) {
208
208
  if (!configExists) {
209
209
  await runNetworkSetup(mode);
210
210
  }
211
- const { runServe } = await import("./serve-V44O2QE6.js");
211
+ const { runServe } = await import("./serve-CNA5NKOR.js");
212
212
  const serveOptions = { mode };
213
213
  if (values.port) serveOptions.port = parseInt(values.port);
214
214
  if (values["no-browser"] === true) serveOptions.openBrowser = false;
@@ -220,4 +220,4 @@ async function runCli(options) {
220
220
  export {
221
221
  runCli
222
222
  };
223
- //# sourceMappingURL=chunk-S5DWUGKZ.js.map
223
+ //# sourceMappingURL=chunk-3WL5YCP6.js.map
@@ -1,12 +1,12 @@
1
1
  import {
2
+ applyDynamicContext,
3
+ computeSessionHash,
2
4
  injectWorkflowKickoffIfNeeded,
3
5
  runAgentTurn,
4
6
  runChatTurn
5
- } from "./chunk-ZHSCXD75.js";
7
+ } from "./chunk-L4WACPUX.js";
6
8
  import {
7
- applyDynamicContext,
8
9
  checkAborted,
9
- computeSessionHash,
10
10
  deleteItemFromDir,
11
11
  devServerManager,
12
12
  executeSubAgent,
@@ -16,7 +16,7 @@ import {
16
16
  loadAllAgentsDefault,
17
17
  saveItemToDir,
18
18
  spawnShellProcess
19
- } from "./chunk-KX4C5Q5J.js";
19
+ } from "./chunk-WEB4WBUF.js";
20
20
  import {
21
21
  getPlatformShell,
22
22
  onProcessEvent
@@ -2102,4 +2102,4 @@ export {
2102
2102
  signalMcpReady,
2103
2103
  createWebSocketServer
2104
2104
  };
2105
- //# sourceMappingURL=chunk-AA4G4DGK.js.map
2105
+ //# sourceMappingURL=chunk-BKTBHEPY.js.map
@@ -1,23 +1,22 @@
1
1
  import {
2
2
  PathAccessDeniedError,
3
- buildCachedPrompt,
4
- computeDynamicContextHash,
5
3
  createAssemblyResult,
6
4
  findAgentById,
7
5
  getAllInstructions,
8
6
  getConversationMessages,
9
7
  getEnabledSkillMetadata,
10
- getToolFingerprint,
8
+ getSubAgents,
11
9
  getToolRegistryForAgent,
12
10
  loadAllAgentsDefault,
13
11
  processEventsForConversation,
14
12
  runTopLevelAgentLoop
15
- } from "./chunk-KX4C5Q5J.js";
13
+ } from "./chunk-WEB4WBUF.js";
16
14
  import {
17
15
  TurnMetrics,
18
16
  WORKFLOW_KICKOFF_PROMPT,
19
17
  buildAgentReminder,
20
18
  buildAgentSmallReminder,
19
+ buildTopLevelSystemPrompt,
21
20
  createChatDoneEvent,
22
21
  createMessageStartEvent
23
22
  } from "./chunk-6VDCH5ML.js";
@@ -37,6 +36,64 @@ import {
37
36
  getGlobalConfigDir
38
37
  } from "./chunk-CQGTEGKL.js";
39
38
 
39
+ // src/server/chat/dynamic-context.ts
40
+ import { createHash } from "crypto";
41
+ function computeDynamicContextHash(instructionContent, skills, toolFingerprint) {
42
+ const dynamicInputs = JSON.stringify({
43
+ instructions: instructionContent,
44
+ skills: skills.map((s) => s.id).sort(),
45
+ ...toolFingerprint ? { tools: toolFingerprint } : {}
46
+ });
47
+ return createHash("sha256").update(dynamicInputs).digest("hex");
48
+ }
49
+ function getToolFingerprint(tools) {
50
+ return tools.map((t) => `${t.function.name}:${JSON.stringify(t.function.parameters)}`).sort().join("|");
51
+ }
52
+ async function loadSessionContext(sessionManager, sessionId) {
53
+ const session = sessionManager.requireSession(sessionId);
54
+ const { content: instructionContent } = await getAllInstructions(session.workdir, session.projectId);
55
+ const runtimeConfig = getRuntimeConfig();
56
+ const configDir = getGlobalConfigDir(runtimeConfig.mode ?? "production");
57
+ const skills = await getEnabledSkillMetadata(configDir, runtimeConfig.workdir);
58
+ return { instructionContent: instructionContent ?? "", skills };
59
+ }
60
+ function resolveAgentDef(sessionManager, sessionId) {
61
+ return loadAllAgentsDefault().then((allAgents) => {
62
+ const session = sessionManager.requireSession(sessionId);
63
+ return findAgentById(session.mode, allAgents) ?? findAgentById("planner", allAgents);
64
+ });
65
+ }
66
+ async function buildCachedPrompt(sessionManager, sessionId, agentDef) {
67
+ const { instructionContent, skills } = await loadSessionContext(sessionManager, sessionId);
68
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-33FQNFKC.js");
69
+ const tools = getToolRegistryForAgent2(agentDef).definitions;
70
+ const toolFingerprint = getToolFingerprint(tools);
71
+ const allAgents = await loadAllAgentsDefault();
72
+ const subAgentDefs = getSubAgents(allAgents);
73
+ const session = sessionManager.requireSession(sessionId);
74
+ const systemPrompt = buildTopLevelSystemPrompt(session.workdir, instructionContent || void 0, skills, subAgentDefs);
75
+ const hash = computeDynamicContextHash(instructionContent, skills, toolFingerprint);
76
+ return { systemPrompt, tools, hash };
77
+ }
78
+ async function computeSessionHash(sessionManager, sessionId) {
79
+ const { instructionContent, skills } = await loadSessionContext(sessionManager, sessionId);
80
+ const agentDef = await resolveAgentDef(sessionManager, sessionId);
81
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-33FQNFKC.js");
82
+ const tools = getToolRegistryForAgent2(agentDef).definitions;
83
+ const toolFingerprint = getToolFingerprint(tools);
84
+ return computeDynamicContextHash(instructionContent, skills, toolFingerprint);
85
+ }
86
+ async function applyDynamicContext(sessionManager, sessionId) {
87
+ const session = sessionManager.requireSession(sessionId);
88
+ const allAgents = await loadAllAgentsDefault();
89
+ const agentDef = findAgentById(session.mode, allAgents) ?? findAgentById("planner", allAgents);
90
+ const { systemPrompt, tools, hash } = await buildCachedPrompt(sessionManager, sessionId, agentDef);
91
+ sessionManager.setCachedPrompt(sessionId, systemPrompt, tools, hash);
92
+ sessionManager.setDynamicContextChanged(sessionId, false);
93
+ sessionManager.clearDebugDump(sessionId);
94
+ logger.debug("applyDynamicContext done", { sessionId, hash, toolCount: tools.length });
95
+ }
96
+
40
97
  // src/server/chat/orchestrator.ts
41
98
  async function buildRetryPatterns() {
42
99
  const { getSetting, SETTINGS_KEYS } = await import("./settings-ASLTSUE2.js");
@@ -320,8 +377,10 @@ function buildSnapshot(sessionManager, sessionId, _lastStats) {
320
377
  }
321
378
 
322
379
  export {
380
+ computeSessionHash,
381
+ applyDynamicContext,
323
382
  runChatTurn,
324
383
  runAgentTurn,
325
384
  injectWorkflowKickoffIfNeeded
326
385
  };
327
- //# sourceMappingURL=chunk-ZHSCXD75.js.map
386
+ //# sourceMappingURL=chunk-L4WACPUX.js.map
@@ -21,7 +21,7 @@ import {
21
21
  tokenFromPassword,
22
22
  verifyPassword,
23
23
  workflowExists
24
- } from "./chunk-AA4G4DGK.js";
24
+ } from "./chunk-BKTBHEPY.js";
25
25
  import {
26
26
  agentExists,
27
27
  createToolRegistry,
@@ -66,7 +66,7 @@ import {
66
66
  setSkillEnabled,
67
67
  skillExists,
68
68
  updateOwnedSkill
69
- } from "./chunk-KX4C5Q5J.js";
69
+ } from "./chunk-WEB4WBUF.js";
70
70
  import {
71
71
  getPathSeparator,
72
72
  isAbsolutePath
@@ -4078,7 +4078,7 @@ import { Router as Router8 } from "express";
4078
4078
  import { spawn as spawn2 } from "child_process";
4079
4079
 
4080
4080
  // src/constants.ts
4081
- var VERSION = "2.0.61";
4081
+ var VERSION = "2.0.62";
4082
4082
 
4083
4083
  // src/server/routes/auto-update.ts
4084
4084
  var updateInProgress = false;
@@ -4283,7 +4283,7 @@ async function createServerHandle(config3) {
4283
4283
  setMcpTools(mcpTools);
4284
4284
  logger.info("MCP tools registered", { count: mcpTools.length });
4285
4285
  }
4286
- const { signalMcpReady } = await import("./server-JDB2QVKP.js");
4286
+ const { signalMcpReady } = await import("./server-CI5IEBOC.js");
4287
4287
  signalMcpReady();
4288
4288
  });
4289
4289
  const app = express();
@@ -4476,7 +4476,7 @@ async function createServerHandle(config3) {
4476
4476
  app.get("/api/sessions/:id", async (req, res) => {
4477
4477
  const { getEventStore: getEventStore2 } = await import("./events-52XADT6Y.js");
4478
4478
  const { buildMessagesFromStoredEvents } = await import("./folding-2BGFLV5J.js");
4479
- const { getPendingQuestionsForSession } = await import("./tools-7QUMO5DJ.js");
4479
+ const { getPendingQuestionsForSession } = await import("./tools-33FQNFKC.js");
4480
4480
  const session = sessionManager.getSession(req.params.id);
4481
4481
  if (!session) {
4482
4482
  return res.status(404).json({ error: "Session not found" });
@@ -4495,8 +4495,8 @@ async function createServerHandle(config3) {
4495
4495
  if (!session) {
4496
4496
  return res.status(404).json({ error: "Session not found" });
4497
4497
  }
4498
- const { stopSessionExecution } = await import("./chat-handler-6DRGMDXZ.js");
4499
- const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-7QUMO5DJ.js");
4498
+ const { stopSessionExecution } = await import("./chat-handler-TRAITVLO.js");
4499
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-33FQNFKC.js");
4500
4500
  sessionManager.clearMessageQueue(sessionId);
4501
4501
  stopSessionExecution(sessionId, sessionManager);
4502
4502
  abortSession(sessionId);
@@ -4650,7 +4650,7 @@ async function createServerHandle(config3) {
4650
4650
  if (!callId || approved === void 0) {
4651
4651
  return res.status(400).json({ error: "callId and approved are required" });
4652
4652
  }
4653
- const { providePathConfirmation } = await import("./tools-7QUMO5DJ.js");
4653
+ const { providePathConfirmation } = await import("./tools-33FQNFKC.js");
4654
4654
  const result = providePathConfirmation(callId, approved, alwaysAllow);
4655
4655
  if (!result.found) {
4656
4656
  return res.status(404).json({ error: "No pending path confirmation with that ID" });
@@ -4658,7 +4658,7 @@ async function createServerHandle(config3) {
4658
4658
  const { getEventStore: getEventStore2 } = await import("./events-52XADT6Y.js");
4659
4659
  const { buildMessagesFromStoredEvents, foldPendingConfirmations } = await import("./folding-2BGFLV5J.js");
4660
4660
  const { createSessionStateMessage } = await import("./protocol-CTNE3ANP.js");
4661
- const { getPendingQuestionsForSession } = await import("./tools-7QUMO5DJ.js");
4661
+ const { getPendingQuestionsForSession } = await import("./tools-33FQNFKC.js");
4662
4662
  const eventStore = getEventStore2();
4663
4663
  const events = eventStore.getEvents(sessionId);
4664
4664
  const messages = buildMessagesFromStoredEvents(events);
@@ -4679,7 +4679,7 @@ async function createServerHandle(config3) {
4679
4679
  if (!skip && typeof answer !== "string") {
4680
4680
  return res.status(400).json({ error: "answer is required when not skipping" });
4681
4681
  }
4682
- const { provideAnswer } = await import("./tools-7QUMO5DJ.js");
4682
+ const { provideAnswer } = await import("./tools-33FQNFKC.js");
4683
4683
  const found = provideAnswer(callId, answer ?? "", skip ?? false);
4684
4684
  if (!found) {
4685
4685
  return res.status(404).json({ error: "No pending question with that ID" });
@@ -4739,7 +4739,7 @@ async function createServerHandle(config3) {
4739
4739
  backend: activeProvider?.backend ?? llmClient.getBackend(),
4740
4740
  model: llmClient.getModel()
4741
4741
  };
4742
- const { runAgentTurn, TurnMetrics } = await import("./orchestrator-AHCSLBB3.js");
4742
+ const { runAgentTurn, TurnMetrics } = await import("./orchestrator-TQ23BAFQ.js");
4743
4743
  runAgentTurn(
4744
4744
  {
4745
4745
  sessionManager,
@@ -4777,8 +4777,8 @@ async function createServerHandle(config3) {
4777
4777
  if (!session) {
4778
4778
  return res.status(404).json({ error: "Session not found" });
4779
4779
  }
4780
- const { stopSessionExecution } = await import("./chat-handler-6DRGMDXZ.js");
4781
- const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-7QUMO5DJ.js");
4780
+ const { stopSessionExecution } = await import("./chat-handler-TRAITVLO.js");
4781
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-33FQNFKC.js");
4782
4782
  const queuedMessages = sessionManager.getQueueState(sessionId);
4783
4783
  sessionManager.clearMessageQueue(sessionId);
4784
4784
  stopSessionExecution(sessionId, sessionManager);
@@ -5343,7 +5343,7 @@ async function createServerHandle(config3) {
5343
5343
  });
5344
5344
  async function rebuildMcpTools() {
5345
5345
  const { createMcpTools: createMcpTools2 } = await import("./tool-adapter-MOGEI6TP.js");
5346
- const { setMcpTools: setMcpTools2 } = await import("./tools-7QUMO5DJ.js");
5346
+ const { setMcpTools: setMcpTools2 } = await import("./tools-33FQNFKC.js");
5347
5347
  const mcpTools = createMcpTools2(mcpManager);
5348
5348
  setMcpTools2(mcpTools);
5349
5349
  }
@@ -5663,7 +5663,7 @@ async function createServerHandle(config3) {
5663
5663
  const state = sessionManager.getContextState(sessionId);
5664
5664
  wssExports.broadcastForSession(sessionId, createContextStateMessage(state));
5665
5665
  });
5666
- const { QueueProcessor } = await import("./processor-45VWLZIV.js");
5666
+ const { QueueProcessor } = await import("./processor-3ZOFGZP5.js");
5667
5667
  const queueProcessor = new QueueProcessor({
5668
5668
  sessionManager,
5669
5669
  providerManager,
@@ -5743,4 +5743,4 @@ export {
5743
5743
  createServerHandle,
5744
5744
  createServer
5745
5745
  };
5746
- //# sourceMappingURL=chunk-LHTVTDQQ.js.map
5746
+ //# sourceMappingURL=chunk-QZRS2RF5.js.map
@@ -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];
@@ -816,6 +822,67 @@ function stringToUtf32(str, littleEndian) {
816
822
 
817
823
  // src/server/tools/read.ts
818
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
819
886
  async function detectImageType(buffer, filePath) {
820
887
  const fileType = await fileTypeFromBuffer(buffer);
821
888
  const imageMimeTypes = ["image/png", "image/jpeg", "image/gif", "image/webp", "image/bmp", "image/svg+xml"];
@@ -869,7 +936,7 @@ var readFileTool = createTool(
869
936
  type: "function",
870
937
  function: {
871
938
  name: "read_file",
872
- 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.",
873
940
  parameters: {
874
941
  type: "object",
875
942
  properties: {
@@ -901,6 +968,11 @@ var readFileTool = createTool(
901
968
  const listing = await listDirectory(fullPath, args.path);
902
969
  return helpers.success(listing);
903
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
+ }
904
976
  if (stats.size > OUTPUT_LIMITS.read_file.maxImageBytes) {
905
977
  return helpers.error(
906
978
  `File size (${stats.size} bytes) exceeds image size limit (2MB). Use shell command to process large files.`
@@ -928,6 +1000,42 @@ var readFileTool = createTool(
928
1000
  }
929
1001
  });
930
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
+ }
931
1039
  const { encoding, confidence } = detectEncoding(rawBuffer);
932
1040
  const content = decodeContent(rawBuffer, encoding);
933
1041
  const lines = content.split("\n");
@@ -3658,7 +3766,7 @@ var callSubAgentTool = {
3658
3766
  };
3659
3767
  }
3660
3768
  try {
3661
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-7QUMO5DJ.js");
3769
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-33FQNFKC.js");
3662
3770
  const toolRegistry = getToolRegistryForAgent2(agentDef);
3663
3771
  const turnMetrics = new TurnMetrics();
3664
3772
  const result = await executeSubAgent({
@@ -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-7QUMO5DJ.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-7QUMO5DJ.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-7QUMO5DJ.js");
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
- await applyDynamicContext(context.sessionManager, context.sessionId);
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
- await applyDynamicContext(context.sessionManager, context.sessionId);
4668
- mcpNotifyChanged?.(context.sessionId);
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
- await applyDynamicContext(context.sessionManager, context.sessionId);
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");
@@ -5362,6 +5448,7 @@ export {
5362
5448
  getDefaultAgentIds,
5363
5449
  isDefaultAgent,
5364
5450
  findAgentById,
5451
+ getSubAgents,
5365
5452
  getTopLevelAgents,
5366
5453
  agentExists,
5367
5454
  saveAgent,
@@ -5392,11 +5479,6 @@ export {
5392
5479
  executeSubAgent,
5393
5480
  devServerManager,
5394
5481
  stepDoneTool,
5395
- computeDynamicContextHash,
5396
- getToolFingerprint,
5397
- buildCachedPrompt,
5398
- computeSessionHash,
5399
- applyDynamicContext,
5400
5482
  setMcpManagerForTools,
5401
5483
  setMcpConfigMode,
5402
5484
  setMcpConfigPath,
@@ -5410,4 +5492,4 @@ export {
5410
5492
  getToolRegistryForAgent,
5411
5493
  createToolRegistry
5412
5494
  };
5413
- //# sourceMappingURL=chunk-KX4C5Q5J.js.map
5495
+ //# sourceMappingURL=chunk-WEB4WBUF.js.map
package/dist/cli/dev.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "../chunk-S5DWUGKZ.js";
4
+ } from "../chunk-3WL5YCP6.js";
5
5
  import "../chunk-XY3LESVZ.js";
6
6
  import {
7
7
  logger
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "../chunk-S5DWUGKZ.js";
4
+ } from "../chunk-3WL5YCP6.js";
5
5
  import "../chunk-XY3LESVZ.js";
6
6
  import {
7
7
  logger
@@ -2,8 +2,8 @@ import {
2
2
  injectWorkflowKickoffIfNeeded,
3
3
  runAgentTurn,
4
4
  runChatTurn
5
- } from "./chunk-ZHSCXD75.js";
6
- import "./chunk-KX4C5Q5J.js";
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";
@@ -43,4 +43,4 @@ export {
43
43
  runAgentTurn,
44
44
  runChatTurn
45
45
  };
46
- //# sourceMappingURL=orchestrator-AHCSLBB3.js.map
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.61",
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-KX4C5Q5J.js";
6
+ import "./chunk-WEB4WBUF.js";
7
7
  import "./chunk-LBGHZLLH.js";
8
8
  import "./chunk-BGTEIA2S.js";
9
9
  import "./chunk-PK2RMVMB.js";
@@ -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-AHCSLBB3.js");
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-45VWLZIV.js.map
225
+ //# sourceMappingURL=processor-3ZOFGZP5.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  VERSION,
3
3
  createServer
4
- } from "./chunk-LHTVTDQQ.js";
5
- import "./chunk-AA4G4DGK.js";
6
- import "./chunk-ZHSCXD75.js";
7
- import "./chunk-KX4C5Q5J.js";
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";
@@ -205,4 +205,4 @@ async function runServe(options) {
205
205
  export {
206
206
  runServe
207
207
  };
208
- //# sourceMappingURL=serve-V44O2QE6.js.map
208
+ //# sourceMappingURL=serve-CNA5NKOR.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createServer,
3
3
  createServerHandle
4
- } from "../chunk-LHTVTDQQ.js";
5
- import "../chunk-AA4G4DGK.js";
6
- import "../chunk-ZHSCXD75.js";
7
- import "../chunk-KX4C5Q5J.js";
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";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createWebSocketServer,
3
3
  signalMcpReady
4
- } from "./chunk-AA4G4DGK.js";
5
- import "./chunk-ZHSCXD75.js";
6
- import "./chunk-KX4C5Q5J.js";
4
+ } from "./chunk-BKTBHEPY.js";
5
+ import "./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";
@@ -35,4 +35,4 @@ export {
35
35
  createWebSocketServer,
36
36
  signalMcpReady
37
37
  };
38
- //# sourceMappingURL=server-JDB2QVKP.js.map
38
+ //# sourceMappingURL=server-CI5IEBOC.js.map
@@ -12,7 +12,7 @@ import {
12
12
  setMcpTools,
13
13
  stepDoneTool,
14
14
  validateToolAction
15
- } from "./chunk-KX4C5Q5J.js";
15
+ } from "./chunk-WEB4WBUF.js";
16
16
  import "./chunk-LBGHZLLH.js";
17
17
  import "./chunk-BGTEIA2S.js";
18
18
  import "./chunk-PK2RMVMB.js";
@@ -58,4 +58,4 @@ export {
58
58
  stepDoneTool,
59
59
  validateToolAction
60
60
  };
61
- //# sourceMappingURL=tools-7QUMO5DJ.js.map
61
+ //# sourceMappingURL=tools-33FQNFKC.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openfox",
3
- "version": "2.0.61",
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",