openfox 2.0.13 → 2.0.15

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.
Files changed (36) hide show
  1. package/dist/{chat-handler-6NUNVXIU.js → chat-handler-RYOG3WJP.js} +9 -7
  2. package/dist/{chunk-VFRWE5X4.js → chunk-2GX5EIIU.js} +481 -2104
  3. package/dist/{chunk-FQIT7VIA.js → chunk-3IKRNKPI.js} +6 -6
  4. package/dist/{chunk-A52FXWJX.js → chunk-3SGGOBCL.js} +10 -387
  5. package/dist/chunk-EKTGRBDC.js +2072 -0
  6. package/dist/{chunk-3QB2RMX2.js → chunk-F4PMNP7S.js} +2 -2
  7. package/dist/{chunk-TIKQWNYK.js → chunk-J7KOV4ST.js} +2 -2
  8. package/dist/{chunk-2VDLXCLO.js → chunk-KPKSN362.js} +288 -26
  9. package/dist/chunk-NWO6GRYE.js +39 -0
  10. package/dist/chunk-PQ56PX7L.js +397 -0
  11. package/dist/{chunk-HPCGVAS4.js → chunk-SNQT7LNU.js} +18 -1
  12. package/dist/{chunk-5BDVM6YI.js → chunk-YD6NDTKF.js} +1 -1
  13. package/dist/{chunk-EZJUU54W.js → chunk-Z64KW2HD.js} +23 -17
  14. package/dist/cli/dev.js +1 -1
  15. package/dist/cli/index.js +1 -1
  16. package/dist/{config-BU66P4KX.js → config-Z66BQTNX.js} +2 -2
  17. package/dist/{orchestrator-WXEZHUVQ.js → orchestrator-KRCVDTZ6.js} +8 -6
  18. package/dist/package.json +3 -3
  19. package/dist/{processor-GA5NG3T7.js → processor-JHR3Z42W.js} +9 -7
  20. package/dist/{protocol-CaLuIetw.d.ts → protocol-BC1QSQ-Y.d.ts} +16 -1
  21. package/dist/{protocol-YYWMFR35.js → protocol-BKNLAEPJ.js} +3 -3
  22. package/dist/{provider-QPECLUZ7.js → provider-YBTRC77Y.js} +7 -5
  23. package/dist/{provider-manager-YA2WALTF.js → provider-manager-DETXLDKW.js} +3 -2
  24. package/dist/{serve-TJHQ326L.js → serve-AV74ODQ6.js} +13 -9
  25. package/dist/server/index.d.ts +21 -2
  26. package/dist/server/index.js +11 -7
  27. package/dist/server-P7E2IPVL.js +31 -0
  28. package/dist/shared/index.d.ts +2 -2
  29. package/dist/shared/index.js +1 -1
  30. package/dist/tool-adapter-B7QP6NLA.js +7 -0
  31. package/dist/{tools-THXBQJ7A.js → tools-WEE4XDTL.js} +9 -5
  32. package/dist/web/assets/{index-CQw-9GC9.js → index-BMz5-yAf.js} +76 -75
  33. package/dist/web/assets/{index-Db3yUKBI.css → index-Ho_tVUbw.css} +1 -1
  34. package/dist/web/index.html +2 -2
  35. package/dist/web/sw.js +1 -1
  36. package/package.json +3 -3
@@ -0,0 +1,397 @@
1
+ import {
2
+ buildNonStreamingCreateParams,
3
+ buildStreamingCreateParams,
4
+ getBackendCapabilities,
5
+ getModelProfile,
6
+ getThinking,
7
+ mapFinishReason
8
+ } from "./chunk-Z4SWOUWC.js";
9
+ import {
10
+ logger
11
+ } from "./chunk-K44MW7JJ.js";
12
+
13
+ // src/server/llm/url-utils.ts
14
+ var VERSION_PREFIX_REGEX = /\/v\d+(\/|$)/;
15
+ function hasVersionPrefix(url) {
16
+ return VERSION_PREFIX_REGEX.test(url);
17
+ }
18
+ function ensureVersionPrefix(url, defaultVersion = "/v1") {
19
+ if (hasVersionPrefix(url)) return url;
20
+ return `${url.replace(/\/+$/, "")}${defaultVersion}`;
21
+ }
22
+ function stripVersionPrefix(url) {
23
+ return url.replace(/\/v\d+\/?$/, "");
24
+ }
25
+ function buildModelsUrl(baseUrl) {
26
+ return `${ensureVersionPrefix(baseUrl)}/models`;
27
+ }
28
+
29
+ // src/server/llm/client.ts
30
+ import OpenAI from "openai";
31
+
32
+ // src/server/utils/errors.ts
33
+ var OpenFoxError = class extends Error {
34
+ constructor(message, code, details) {
35
+ super(message);
36
+ this.code = code;
37
+ this.details = details;
38
+ this.name = "OpenFoxError";
39
+ }
40
+ };
41
+ var SessionNotFoundError = class extends OpenFoxError {
42
+ constructor(sessionId) {
43
+ super(`Session not found: ${sessionId}`, "SESSION_NOT_FOUND", { sessionId });
44
+ this.name = "SessionNotFoundError";
45
+ }
46
+ };
47
+ var LLMError = class extends OpenFoxError {
48
+ constructor(message, details) {
49
+ super(message, "LLM_ERROR", details);
50
+ this.name = "LLMError";
51
+ }
52
+ };
53
+
54
+ // src/server/llm/client.ts
55
+ import { Agent, setGlobalDispatcher } from "undici";
56
+ var agent = new Agent({ allowH2: true });
57
+ setGlobalDispatcher(agent);
58
+ function createLLMClient(config, initialBackend = "unknown") {
59
+ const baseURL = ensureVersionPrefix(config.llm.baseUrl);
60
+ const openai = new OpenAI({
61
+ baseURL,
62
+ apiKey: config.llm.apiKey ?? "not-needed"
63
+ });
64
+ let model = config.llm.model;
65
+ let profile = getModelProfile(model);
66
+ let backend = initialBackend;
67
+ let capabilities = getBackendCapabilities(backend);
68
+ const reasoningEffort = config.llm.reasoningEffort;
69
+ const thinkingField = config.llm.thinkingField;
70
+ const idleTimeout = config.llm.idleTimeout ?? 3e4;
71
+ return {
72
+ getModel() {
73
+ return model;
74
+ },
75
+ getProfile() {
76
+ return profile;
77
+ },
78
+ getBackend() {
79
+ return backend;
80
+ },
81
+ setBackend(newBackend) {
82
+ logger.debug("Setting LLM backend", { from: backend, to: newBackend });
83
+ backend = newBackend;
84
+ capabilities = getBackendCapabilities(newBackend);
85
+ },
86
+ setModel(newModel) {
87
+ const newProfile = getModelProfile(newModel);
88
+ logger.debug("Switching model", {
89
+ from: model,
90
+ to: newModel,
91
+ profile: newProfile.name,
92
+ temperature: newProfile.temperature
93
+ });
94
+ model = newModel;
95
+ profile = newProfile;
96
+ },
97
+ async complete(request) {
98
+ logger.debug("LLM complete request", {
99
+ messageCount: request.messages.length,
100
+ hasTools: !!request.tools?.length,
101
+ profile: profile.name,
102
+ reasoningEffort: request.reasoningEffort ?? reasoningEffort
103
+ });
104
+ try {
105
+ const resolvedEffort = request.reasoningEffort ?? reasoningEffort;
106
+ const { params: createParams } = await buildNonStreamingCreateParams({
107
+ model,
108
+ request,
109
+ profile,
110
+ capabilities,
111
+ ...resolvedEffort ? { reasoningEffort: resolvedEffort } : {},
112
+ ...thinkingField ? { thinkingField } : {}
113
+ });
114
+ const response = await openai.chat.completions.create(createParams, {
115
+ signal: request.signal
116
+ });
117
+ const choice = response.choices[0];
118
+ if (!choice) {
119
+ throw new LLMError("No completion choice returned");
120
+ }
121
+ const message = choice.message;
122
+ const content = message.content ?? "";
123
+ const thinkingContent = getThinking(message, thinkingField) ?? "";
124
+ const toolCalls = message.tool_calls?.map((tc) => ({
125
+ id: tc.id,
126
+ name: tc.function.name,
127
+ arguments: JSON.parse(tc.function.arguments)
128
+ }));
129
+ return {
130
+ id: response.id,
131
+ content,
132
+ ...thinkingContent ? { thinkingContent } : {},
133
+ ...toolCalls && toolCalls.length > 0 ? { toolCalls } : {},
134
+ finishReason: mapFinishReason(choice.finish_reason),
135
+ usage: {
136
+ promptTokens: response.usage?.prompt_tokens ?? 0,
137
+ completionTokens: response.usage?.completion_tokens ?? 0,
138
+ totalTokens: response.usage?.total_tokens ?? 0
139
+ }
140
+ };
141
+ } catch (error) {
142
+ logger.error("LLM complete error", { error: String(error) });
143
+ throw new LLMError(error instanceof Error ? error.message : "Unknown LLM error", {
144
+ originalError: error instanceof Error ? error : void 0
145
+ });
146
+ }
147
+ },
148
+ async *stream(request) {
149
+ const resolvedEffort = request.reasoningEffort ?? reasoningEffort;
150
+ logger.debug("LLM stream request", {
151
+ messageCount: request.messages.length,
152
+ hasTools: !!request.tools?.length,
153
+ profile: profile.name,
154
+ reasoningEffort: resolvedEffort,
155
+ idleTimeout
156
+ });
157
+ try {
158
+ const createParams = await buildStreamingCreateParams({
159
+ model,
160
+ request,
161
+ profile,
162
+ capabilities,
163
+ ...resolvedEffort ? { reasoningEffort: resolvedEffort } : {},
164
+ ...thinkingField ? { thinkingField } : {}
165
+ });
166
+ const { params: streamingParams } = createParams;
167
+ const stream = await openai.chat.completions.create(streamingParams, {
168
+ signal: request.signal
169
+ });
170
+ let fullContent = "";
171
+ let fullThinking = "";
172
+ const toolCalls = /* @__PURE__ */ new Map();
173
+ let finishReason = "stop";
174
+ let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
175
+ let responseId = "";
176
+ let lastChunkTime = Date.now();
177
+ const idleTimeoutController = new AbortController();
178
+ const idleTimer = setInterval(() => {
179
+ const idleDuration = Date.now() - lastChunkTime;
180
+ if (idleDuration > idleTimeout) {
181
+ logger.warn("LLM stream idle timeout triggered", { idleDuration, idleTimeout });
182
+ idleTimeoutController.abort();
183
+ }
184
+ }, 100);
185
+ const onAbort = () => clearInterval(idleTimer);
186
+ request.signal?.addEventListener("abort", onAbort, { once: true });
187
+ try {
188
+ for await (const chunk of stream) {
189
+ if (idleTimeoutController.signal.aborted) {
190
+ throw new Error(`LLM stream idle timeout: no chunks received for ${idleTimeout}ms`);
191
+ }
192
+ lastChunkTime = Date.now();
193
+ responseId = chunk.id;
194
+ if (chunk.usage) {
195
+ usage = {
196
+ promptTokens: chunk.usage.prompt_tokens,
197
+ completionTokens: chunk.usage.completion_tokens,
198
+ totalTokens: chunk.usage.total_tokens
199
+ };
200
+ }
201
+ const choice = chunk.choices[0];
202
+ if (!choice) continue;
203
+ if (choice.finish_reason) {
204
+ finishReason = mapFinishReason(choice.finish_reason);
205
+ }
206
+ const delta = choice.delta;
207
+ const reasoning = getThinking(delta, thinkingField);
208
+ if (reasoning) {
209
+ fullThinking += reasoning;
210
+ yield { type: "thinking_delta", content: reasoning };
211
+ }
212
+ if (delta.content) {
213
+ fullContent += delta.content;
214
+ yield { type: "text_delta", content: delta.content };
215
+ }
216
+ if (delta.tool_calls) {
217
+ for (const tc of delta.tool_calls) {
218
+ const existing = toolCalls.get(tc.index);
219
+ if (!existing) {
220
+ toolCalls.set(tc.index, {
221
+ id: tc.id ?? "",
222
+ name: tc.function?.name ?? "",
223
+ arguments: tc.function?.arguments ?? ""
224
+ });
225
+ } else {
226
+ if (tc.id) existing.id = tc.id;
227
+ if (tc.function?.name) existing.name += tc.function.name;
228
+ if (tc.function?.arguments) existing.arguments += tc.function.arguments;
229
+ }
230
+ yield {
231
+ type: "tool_call_delta",
232
+ index: tc.index,
233
+ ...tc.id ? { id: tc.id } : {},
234
+ ...tc.function?.name ? { name: tc.function.name } : {},
235
+ ...tc.function?.arguments ? { arguments: tc.function.arguments } : {}
236
+ };
237
+ }
238
+ }
239
+ }
240
+ } finally {
241
+ clearInterval(idleTimer);
242
+ request.signal?.removeEventListener("abort", onAbort);
243
+ }
244
+ const finalContent = fullContent.trim();
245
+ const finalThinking = fullThinking.trim();
246
+ const parsedToolCalls = [];
247
+ for (const [, tc] of toolCalls) {
248
+ try {
249
+ parsedToolCalls.push({
250
+ id: tc.id,
251
+ name: tc.name,
252
+ arguments: JSON.parse(tc.arguments)
253
+ });
254
+ } catch (error) {
255
+ logger.warn("Failed to parse tool call arguments", { name: tc.name, arguments: tc.arguments });
256
+ parsedToolCalls.push({
257
+ id: tc.id,
258
+ name: tc.name,
259
+ arguments: {},
260
+ parseError: error instanceof Error ? error.message : "Unknown JSON parse error",
261
+ rawArguments: tc.arguments
262
+ });
263
+ }
264
+ }
265
+ yield {
266
+ type: "done",
267
+ response: {
268
+ id: responseId,
269
+ content: finalContent,
270
+ ...finalThinking ? { thinkingContent: finalThinking } : {},
271
+ ...parsedToolCalls.length > 0 ? { toolCalls: parsedToolCalls } : {},
272
+ finishReason,
273
+ usage
274
+ }
275
+ };
276
+ } catch (error) {
277
+ logger.error("LLM stream error", { error });
278
+ yield {
279
+ type: "error",
280
+ error: error instanceof Error ? error.message : "Unknown LLM error"
281
+ };
282
+ }
283
+ }
284
+ };
285
+ }
286
+
287
+ // src/server/llm/models.ts
288
+ var modelCache = /* @__PURE__ */ new Map();
289
+ var llmStatus = "unknown";
290
+ var lastActiveUrl = null;
291
+ var CACHE_TTL_MS = 3e4;
292
+ function getCacheKey(url) {
293
+ return stripVersionPrefix(url);
294
+ }
295
+ async function detectModel(llmBaseUrl, retries = 3, silent = false) {
296
+ const cacheKey = getCacheKey(llmBaseUrl);
297
+ const now = Date.now();
298
+ const cached = modelCache.get(cacheKey);
299
+ if (cached && now - cached.timestamp < CACHE_TTL_MS) {
300
+ lastActiveUrl = cacheKey;
301
+ llmStatus = "connected";
302
+ return cached.model;
303
+ }
304
+ const url = buildModelsUrl(llmBaseUrl);
305
+ for (let attempt = 1; attempt <= retries; attempt++) {
306
+ try {
307
+ if (silent) {
308
+ logger.debug("Fetching models from LLM server", { url, attempt });
309
+ }
310
+ const response = await fetch(url, {
311
+ signal: AbortSignal.timeout(1e4)
312
+ });
313
+ if (!response.ok) {
314
+ if (silent) {
315
+ logger.debug("Failed to fetch models from LLM server", { status: response.status, attempt });
316
+ } else {
317
+ logger.warn("Failed to fetch models from LLM server", { status: response.status, attempt });
318
+ }
319
+ if (attempt < retries) {
320
+ await new Promise((r) => setTimeout(r, 1e3 * attempt));
321
+ continue;
322
+ }
323
+ llmStatus = "disconnected";
324
+ return cached?.model ?? null;
325
+ }
326
+ const data = await response.json();
327
+ if (data.data && data.data.length > 0) {
328
+ const modelData = data.data[0];
329
+ const modelId = modelData.id;
330
+ modelCache.set(cacheKey, {
331
+ model: modelId,
332
+ modelInfo: modelData,
333
+ timestamp: now
334
+ });
335
+ lastActiveUrl = cacheKey;
336
+ llmStatus = "connected";
337
+ if (silent) {
338
+ logger.debug("Detected LLM model", {
339
+ model: modelId,
340
+ maxLen: modelData.max_model_len,
341
+ root: modelData.root
342
+ });
343
+ } else {
344
+ logger.info("Detected LLM model", {
345
+ model: modelId,
346
+ maxLen: modelData.max_model_len,
347
+ root: modelData.root
348
+ });
349
+ }
350
+ return modelId;
351
+ }
352
+ if (silent) {
353
+ logger.debug("LLM server returned empty models list");
354
+ } else {
355
+ logger.warn("LLM server returned empty models list");
356
+ }
357
+ llmStatus = "disconnected";
358
+ return null;
359
+ } catch (error) {
360
+ const errMsg = error instanceof Error ? error.message : String(error);
361
+ if (silent) {
362
+ logger.debug("Could not detect model from LLM server", { error: errMsg, attempt });
363
+ } else {
364
+ logger.warn("Could not detect model from LLM server", { error: errMsg, attempt });
365
+ }
366
+ if (attempt < retries) {
367
+ await new Promise((r) => setTimeout(r, 1e3 * attempt));
368
+ continue;
369
+ }
370
+ }
371
+ }
372
+ llmStatus = "disconnected";
373
+ return cached?.model ?? null;
374
+ }
375
+ function getLlmStatus() {
376
+ return llmStatus;
377
+ }
378
+ function clearModelCache(url) {
379
+ if (url) {
380
+ modelCache.delete(getCacheKey(url));
381
+ } else {
382
+ modelCache.clear();
383
+ }
384
+ llmStatus = "unknown";
385
+ }
386
+
387
+ export {
388
+ SessionNotFoundError,
389
+ ensureVersionPrefix,
390
+ stripVersionPrefix,
391
+ buildModelsUrl,
392
+ createLLMClient,
393
+ detectModel,
394
+ getLlmStatus,
395
+ clearModelCache
396
+ };
397
+ //# sourceMappingURL=chunk-PQ56PX7L.js.map
@@ -61,9 +61,19 @@ var visionFallbackSchema = z.object({
61
61
  model: z.string().default("qwen3.5:0.8b"),
62
62
  timeout: z.number().default(120)
63
63
  });
64
+ var mcpServerSchema = z.object({
65
+ transport: z.enum(["stdio", "http"]).default("stdio"),
66
+ command: z.string().optional(),
67
+ args: z.array(z.string()).optional(),
68
+ env: z.record(z.string(), z.string()).optional(),
69
+ url: z.string().optional(),
70
+ headers: z.record(z.string(), z.string()).optional(),
71
+ disabledTools: z.array(z.string()).optional()
72
+ });
64
73
  var defaultVisionFallback = { enabled: false, url: "http://localhost:11434", model: "qwen3.5:0.8b", timeout: 120 };
65
74
  var configSchema = z.object({
66
75
  providers: z.array(providerSchema).default([]),
76
+ mcpServers: z.record(z.string(), mcpServerSchema).optional(),
67
77
  defaultModelSelection: z.string().optional(),
68
78
  activeProviderId: z.string().optional(),
69
79
  activeWorkflowId: z.string().optional(),
@@ -74,6 +84,7 @@ var configSchema = z.object({
74
84
  visionFallback: visionFallbackSchema.optional()
75
85
  }).transform((data) => ({
76
86
  providers: data.providers ?? [],
87
+ mcpServers: data.mcpServers,
77
88
  defaultModelSelection: data.defaultModelSelection,
78
89
  activeProviderId: data.activeProviderId,
79
90
  activeWorkflowId: data.activeWorkflowId,
@@ -225,6 +236,7 @@ async function saveGlobalConfig(mode, config) {
225
236
  const configPath = getGlobalConfigPath(mode);
226
237
  const fullConfig = {
227
238
  providers: config.providers ?? [],
239
+ mcpServers: config.mcpServers,
228
240
  defaultModelSelection: config.defaultModelSelection,
229
241
  activeProviderId: config.activeProviderId,
230
242
  activeWorkflowId: config.activeWorkflowId,
@@ -260,6 +272,7 @@ function setDefaultModelSelection(config, providerId, model) {
260
272
  const defaultModelSelection = `${providerId}/${model}`;
261
273
  return {
262
274
  providers: config.providers?.map((p) => ({ ...p, isActive: p.id === providerId })) ?? [],
275
+ mcpServers: config.mcpServers,
263
276
  defaultModelSelection,
264
277
  activeProviderId: providerId,
265
278
  activeWorkflowId: config.activeWorkflowId,
@@ -287,6 +300,7 @@ function addProvider(config, provider) {
287
300
  ...(config.providers ?? []).map((p) => shouldActivate ? { ...p, isActive: false } : p),
288
301
  { ...newProvider, isActive: shouldActivate }
289
302
  ],
303
+ mcpServers: config.mcpServers,
290
304
  defaultModelSelection: shouldActivate ? `${newProvider.id}/auto` : config.defaultModelSelection,
291
305
  activeProviderId: shouldActivate ? newProvider.id : config.activeProviderId,
292
306
  activeWorkflowId: config.activeWorkflowId,
@@ -323,6 +337,7 @@ function removeProvider(config, providerId) {
323
337
  const newActiveId = wasActive && filtered.length > 0 ? filtered[0].id : wasActive ? void 0 : config.activeProviderId;
324
338
  return {
325
339
  providers: filtered.map((p) => ({ ...p, isActive: p.id === newActiveId })),
340
+ mcpServers: config.mcpServers,
326
341
  activeProviderId: newActiveId,
327
342
  defaultModelSelection: newDefaultModelSelection,
328
343
  activeWorkflowId: config.activeWorkflowId,
@@ -343,6 +358,7 @@ function activateProvider(config, providerId) {
343
358
  if (!provider) {
344
359
  return {
345
360
  providers: config.providers ?? [],
361
+ mcpServers: config.mcpServers,
346
362
  defaultModelSelection: config.defaultModelSelection,
347
363
  activeProviderId: config.activeProviderId,
348
364
  activeWorkflowId: config.activeWorkflowId,
@@ -360,6 +376,7 @@ function activateProvider(config, providerId) {
360
376
  }
361
377
  return {
362
378
  providers: (config.providers ?? []).map((p) => ({ ...p, isActive: p.id === providerId })),
379
+ mcpServers: config.mcpServers,
363
380
  defaultModelSelection: config.defaultModelSelection,
364
381
  activeProviderId: providerId,
365
382
  activeWorkflowId: config.activeWorkflowId,
@@ -419,4 +436,4 @@ export {
419
436
  activateProvider,
420
437
  mergeConfigs
421
438
  };
422
- //# sourceMappingURL=chunk-HPCGVAS4.js.map
439
+ //# sourceMappingURL=chunk-SNQT7LNU.js.map
@@ -26,4 +26,4 @@ export {
26
26
  isClientMessage,
27
27
  isServerMessage
28
28
  };
29
- //# sourceMappingURL=chunk-5BDVM6YI.js.map
29
+ //# sourceMappingURL=chunk-YD6NDTKF.js.map
@@ -1,17 +1,20 @@
1
1
  import {
2
2
  PathAccessDeniedError,
3
3
  assembleAgentRequest,
4
+ computeDynamicContextHash,
4
5
  createAssemblyResult,
6
+ createToolRegistry,
5
7
  findAgentById,
6
8
  getAllInstructions,
7
9
  getConversationMessages,
8
10
  getEnabledSkillMetadata,
9
11
  getSubAgents,
12
+ getToolFingerprint,
10
13
  getToolRegistryForAgent,
11
14
  loadAllAgentsDefault,
12
15
  processEventsForConversation,
13
16
  runTopLevelAgentLoop
14
- } from "./chunk-2VDLXCLO.js";
17
+ } from "./chunk-KPKSN362.js";
15
18
  import {
16
19
  TurnMetrics,
17
20
  WORKFLOW_KICKOFF_PROMPT,
@@ -36,16 +39,6 @@ import {
36
39
  logger
37
40
  } from "./chunk-K44MW7JJ.js";
38
41
 
39
- // src/server/chat/dynamic-context.ts
40
- import { createHash } from "crypto";
41
- function computeDynamicContextHash(instructionContent, skills) {
42
- const dynamicInputs = JSON.stringify({
43
- instructions: instructionContent,
44
- skills: skills.map((s) => s.id).sort()
45
- });
46
- return createHash("sha256").update(dynamicInputs).digest("hex");
47
- }
48
-
49
42
  // src/server/chat/orchestrator.ts
50
43
  async function buildRetryPatterns() {
51
44
  const { getSetting, SETTINGS_KEYS } = await import("./settings-6XX56F3F.js");
@@ -237,16 +230,30 @@ async function runAgentTurn(options, turnMetrics, agentId, append, callbacks) {
237
230
  onMessage: options.onMessage,
238
231
  assembleRequest: (input) => {
239
232
  const cached = options.sessionManager.getCachedPrompt(options.sessionId);
233
+ const liveTools = createToolRegistry().definitions;
234
+ const toolFingerprint = getToolFingerprint(liveTools);
240
235
  if (cached) {
241
- const currentHash = computeDynamicContextHash(instructionContent ?? "", skills);
236
+ const currentHash = computeDynamicContextHash(instructionContent ?? "", skills, toolFingerprint);
242
237
  if (cached.hash !== currentHash) {
238
+ logger.debug("assembleRequest: hash mismatch", {
239
+ sessionId: options.sessionId,
240
+ cachedHash: cached.hash,
241
+ currentHash,
242
+ cachedTools: cached.tools.map((t) => t.function.name),
243
+ liveTools: liveTools.map((t) => t.function.name)
244
+ });
243
245
  options.sessionManager.setDynamicContextChanged(options.sessionId, true);
246
+ options.sessionManager.setDebugDump(options.sessionId, {
247
+ cachedPrompt: cached.systemPrompt.slice(0, 5e3),
248
+ cachedTools: cached.tools.map((t) => t.function.name),
249
+ liveTools: liveTools.map((t) => t.function.name)
250
+ });
244
251
  }
245
252
  return createAssemblyResult({
246
253
  systemPrompt: cached.systemPrompt,
247
254
  messages: input.messages,
248
255
  injectedFiles: input.injectedFiles,
249
- requestTools: input.promptTools,
256
+ requestTools: cached.tools.length > 0 ? cached.tools : liveTools,
250
257
  toolChoice: input.toolChoice
251
258
  });
252
259
  }
@@ -256,8 +263,8 @@ async function runAgentTurn(options, turnMetrics, agentId, append, callbacks) {
256
263
  subAgentDefs,
257
264
  modelName: options.llmClient.getModel()
258
265
  });
259
- const hash = computeDynamicContextHash(instructionContent ?? "", skills);
260
- options.sessionManager.setCachedPrompt(options.sessionId, result.systemPrompt, hash);
266
+ const hash = computeDynamicContextHash(instructionContent ?? "", skills, toolFingerprint);
267
+ options.sessionManager.setCachedPrompt(options.sessionId, result.systemPrompt, result.tools, hash);
261
268
  return result;
262
269
  },
263
270
  getToolRegistry: () => getToolRegistryForAgent(agentDef),
@@ -309,9 +316,8 @@ function buildSnapshot(sessionManager, sessionId, _lastStats) {
309
316
  }
310
317
 
311
318
  export {
312
- computeDynamicContextHash,
313
319
  runChatTurn,
314
320
  runAgentTurn,
315
321
  injectWorkflowKickoffIfNeeded
316
322
  };
317
- //# sourceMappingURL=chunk-EZJUU54W.js.map
323
+ //# sourceMappingURL=chunk-Z64KW2HD.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-FQIT7VIA.js";
4
+ } from "../chunk-3IKRNKPI.js";
5
5
  import {
6
6
  logger
7
7
  } from "../chunk-K44MW7JJ.js";
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-FQIT7VIA.js";
4
+ } from "../chunk-3IKRNKPI.js";
5
5
  import {
6
6
  logger
7
7
  } from "../chunk-K44MW7JJ.js";
@@ -13,7 +13,7 @@ import {
13
13
  saveGlobalConfig,
14
14
  setDefaultModelSelection,
15
15
  updateProvider
16
- } from "./chunk-HPCGVAS4.js";
16
+ } from "./chunk-SNQT7LNU.js";
17
17
  import "./chunk-CQGTEGKL.js";
18
18
  export {
19
19
  activateProvider,
@@ -31,4 +31,4 @@ export {
31
31
  setDefaultModelSelection,
32
32
  updateProvider
33
33
  };
34
- //# sourceMappingURL=config-BU66P4KX.js.map
34
+ //# sourceMappingURL=config-Z66BQTNX.js.map
@@ -2,8 +2,9 @@ import {
2
2
  injectWorkflowKickoffIfNeeded,
3
3
  runAgentTurn,
4
4
  runChatTurn
5
- } from "./chunk-EZJUU54W.js";
6
- import "./chunk-2VDLXCLO.js";
5
+ } from "./chunk-Z64KW2HD.js";
6
+ import "./chunk-KPKSN362.js";
7
+ import "./chunk-XAMAYRDA.js";
7
8
  import {
8
9
  TurnMetrics,
9
10
  createChatDoneEvent,
@@ -15,14 +16,15 @@ import {
15
16
  import "./chunk-DL6ZILAF.js";
16
17
  import "./chunk-PBGOZMVY.js";
17
18
  import "./chunk-VRGRAQDG.js";
18
- import "./chunk-XAMAYRDA.js";
19
+ import "./chunk-NWO6GRYE.js";
19
20
  import "./chunk-SYG2ENUQ.js";
20
21
  import "./chunk-LX66KJPL.js";
21
- import "./chunk-3QB2RMX2.js";
22
+ import "./chunk-F4PMNP7S.js";
22
23
  import "./chunk-EU3WWTFH.js";
23
24
  import "./chunk-RFNEDBVO.js";
24
25
  import "./chunk-FBGWG4N6.js";
25
- import "./chunk-5BDVM6YI.js";
26
+ import "./chunk-YD6NDTKF.js";
27
+ import "./chunk-SNQT7LNU.js";
26
28
  import "./chunk-CQGTEGKL.js";
27
29
  import "./chunk-Z4SWOUWC.js";
28
30
  import "./chunk-K44MW7JJ.js";
@@ -37,4 +39,4 @@ export {
37
39
  runAgentTurn,
38
40
  runChatTurn
39
41
  };
40
- //# sourceMappingURL=orchestrator-WXEZHUVQ.js.map
42
+ //# sourceMappingURL=orchestrator-KRCVDTZ6.js.map
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openfox",
3
- "version": "2.0.13",
3
+ "version": "2.0.15",
4
4
  "description": "Local-LLM-first agentic coding assistant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,6 +60,7 @@
60
60
  "dependencies": {
61
61
  "@clack/prompts": "^1.1.0",
62
62
  "@fontsource/jetbrains-mono": "^5.2.8",
63
+ "@modelcontextprotocol/sdk": "^1.29.0",
63
64
  "@xterm/addon-fit": "^0.11.0",
64
65
  "@xterm/xterm": "^6.0.0",
65
66
  "bash-language-server": "^5.6.0",
@@ -79,12 +80,11 @@
79
80
  "react-syntax-highlighter": "^16.1.1",
80
81
  "remark-gfm": "^4.0.1",
81
82
  "shiki": "^4.2.0",
82
- "sql-language-server": "^1.7.1",
83
83
  "strip-ansi": "^7.2.0",
84
84
  "turndown": "^7.2.2",
85
- "undici": "^8.5.0",
86
85
  "typescript": "^5.8.2",
87
86
  "typescript-language-server": "^5.1.3",
87
+ "undici": "^8.5.0",
88
88
  "vite": "^6.1.0",
89
89
  "vscode-jsonrpc": "^8.2.1",
90
90
  "vscode-langservers-extracted": "^4.10.0",