openfox 2.0.76 → 2.0.78

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 (76) hide show
  1. package/dist/{chat-handler-3BZ4PDL2.js → chat-handler-5ZHPYU24.js} +21 -20
  2. package/dist/{chunk-MF3UW36X.js → chunk-37WF2Z3T.js} +84 -17
  3. package/dist/{chunk-OBQPXUXI.js → chunk-5CQRB7AD.js} +2 -2
  4. package/dist/chunk-64TKWWNF.js +7 -0
  5. package/dist/{chunk-LXFLRM7M.js → chunk-6AE3JPDJ.js} +2 -2
  6. package/dist/chunk-AANVMTYU.js +327 -0
  7. package/dist/{chunk-YMFCFMMB.js → chunk-AFJLY43Z.js} +8 -3
  8. package/dist/{chunk-OCVRIPQP.js → chunk-CJDYSUU5.js} +3 -3
  9. package/dist/{chunk-OMWVB2XC.js → chunk-DI4SIPFT.js} +46 -27
  10. package/dist/{chunk-7772ENZN.js → chunk-EHVDAVYG.js} +4 -3
  11. package/dist/{chunk-DH3SSBZN.js → chunk-EYIWUONA.js} +2 -2
  12. package/dist/{chunk-NGJ5XJ7D.js → chunk-M7EFXRDS.js} +57 -290
  13. package/dist/{chunk-4QHDQP2N.js → chunk-MTUYDBSL.js} +2 -2
  14. package/dist/{chunk-HHUIXHHX.js → chunk-NUR2B4RU.js} +7 -7
  15. package/dist/{chunk-46RORSKU.js → chunk-NZIYELSQ.js} +9 -7
  16. package/dist/{chunk-7QXRQ5ZL.js → chunk-RGUUZD5B.js} +5 -1
  17. package/dist/{chunk-EZK7CGJ7.js → chunk-RMXPVQP4.js} +315 -45
  18. package/dist/{chunk-D6CI2B7B.js → chunk-SGBXBP3S.js} +253 -110
  19. package/dist/{chunk-O3NUNU4H.js → chunk-SJTG56CH.js} +5 -4
  20. package/dist/{chunk-DJASQ2ZB.js → chunk-TSVFRWSG.js} +24 -5
  21. package/dist/{chunk-XTK6E2QR.js → chunk-UIU7OGHP.js} +26 -10
  22. package/dist/{chunk-J7VCI5EX.js → chunk-UMX7MMNW.js} +652 -248
  23. package/dist/{chunk-HLHGNHMV.js → chunk-VGWJFXPR.js} +23 -11
  24. package/dist/{chunk-KVRSAC4Z.js → chunk-VPFUXGMM.js} +6 -1
  25. package/dist/{chunk-W4ZEG3LX.js → chunk-WL2WEJAK.js} +2 -2
  26. package/dist/{chunk-HAQV6AJP.js → chunk-WUSWNLTV.js} +3 -3
  27. package/dist/{chunk-5GOOSZX7.js → chunk-X6BXZFO7.js} +1 -1
  28. package/dist/{chunk-WGAAQASA.js → chunk-YFVSSMA3.js} +3 -2
  29. package/dist/cli/dev.js +1 -1
  30. package/dist/cli/index.js +1 -1
  31. package/dist/{client-TDPQVBQ7.js → client-V5DZRO2D.js} +3 -3
  32. package/dist/{client-pure-NMLDKRDN.js → client-pure-5YE5WFJP.js} +2 -2
  33. package/dist/compactor-G7B3V24F.js +24 -0
  34. package/dist/{config-EUMVEFHU.js → config-MD74ETY5.js} +2 -2
  35. package/dist/{events-FLWRLM6V.js → events-KNOZX7YG.js} +7 -4
  36. package/dist/{folding-ONH3D4OK.js → folding-OPP2BVUB.js} +2 -2
  37. package/dist/{inspect-proxy-JHZV34QR.js → inspect-proxy-DRYIIH4O.js} +4 -4
  38. package/dist/{manager-OPMCSYN2.js → manager-ATWZUL3O.js} +5 -5
  39. package/dist/{orchestrator-5IMCWX5P.js → orchestrator-L6LX5BV7.js} +20 -19
  40. package/dist/package.json +6 -1
  41. package/dist/{path-security-ZROWNL22.js → path-security-HUXQHEQ5.js} +10 -7
  42. package/dist/{platform-QGXO3CNR.js → platform-VBNNMTR5.js} +4 -4
  43. package/dist/{processor-3R4GS6VB.js → processor-UQX5KL56.js} +21 -20
  44. package/dist/{project-creator-C22J43BC.js → project-creator-NNUITNUI.js} +3 -3
  45. package/dist/{projects-WSVINK2C.js → projects-ZGJGICGT.js} +3 -3
  46. package/dist/{protocol-Sr6Jr4ZS.d.ts → protocol-BXxJmsOW.d.ts} +2 -1
  47. package/dist/{protocol-EWEVW65B.js → protocol-Q7X4NEME.js} +3 -3
  48. package/dist/provider/index.d.ts +4 -4
  49. package/dist/{provider-G2YC2IC4.js → provider-PI3RDDE4.js} +6 -6
  50. package/dist/{provider-manager-SQIELYLK.js → provider-manager-PD3BXUHY.js} +4 -4
  51. package/dist/{serve-MXKMKZYY.js → serve-QWC3U5MB.js} +26 -25
  52. package/dist/server/index.d.ts +27 -4
  53. package/dist/server/index.js +25 -24
  54. package/dist/server-3XUD2NB7.js +40 -0
  55. package/dist/sessions-SFG42M4W.js +42 -0
  56. package/dist/{settings-7XDMSPIC.js → settings-6M55N4Q2.js} +5 -3
  57. package/dist/shared/index.d.ts +3 -3
  58. package/dist/shared/index.js +1 -1
  59. package/dist/{tools-JRCUWPJO.js → tools-HGKUD327.js} +26 -23
  60. package/dist/{types-C4cG9CtC.d.ts → types-C308SU4B.d.ts} +3 -0
  61. package/dist/{types-DSZhFA8O.d.ts → types-o07LJcLI.d.ts} +1 -1
  62. package/dist/{update-OXDQ75M7.js → update-NWZALB5M.js} +2 -2
  63. package/dist/{update-server-4MTM7M5P.js → update-server-V6GJ43WH.js} +2 -2
  64. package/dist/web/assets/index-7_xKqeJc.js +305 -0
  65. package/dist/web/assets/index-DRkTjPo3.css +32 -0
  66. package/dist/web/index.html +4 -4
  67. package/dist/web/manifest.webmanifest +1 -1
  68. package/dist/web/registerSW.js +1 -1
  69. package/dist/web/sw.js +1 -1
  70. package/dist/{workspace-6QTTUMMW.js → workspace-J74ZMSOJ.js} +14 -2
  71. package/package.json +6 -1
  72. package/dist/chunk-KQEVAO2Q.js +0 -7
  73. package/dist/compactor-BB3YYVRP.js +0 -23
  74. package/dist/server-SPYFMFQE.js +0 -39
  75. package/dist/web/assets/index-BNVBxKnB.css +0 -32
  76. package/dist/web/assets/index-ghDkIdlf.js +0 -305
@@ -1,13 +1,31 @@
1
+ import {
2
+ checkAborted,
3
+ createProcess,
4
+ getProcessLogs,
5
+ getProcessStatus,
6
+ getSessionProcesses,
7
+ spawnShell,
8
+ spawnShellProcess,
9
+ startProcessCommand,
10
+ stopProcess
11
+ } from "./chunk-5CQRB7AD.js";
1
12
  import {
2
13
  createMcpTools
3
14
  } from "./chunk-NWO6GRYE.js";
4
15
  import {
5
16
  applyMcpServerUpdate
6
- } from "./chunk-KVRSAC4Z.js";
17
+ } from "./chunk-VPFUXGMM.js";
7
18
  import {
8
19
  getGitBranch,
9
20
  listWorkspaces
10
- } from "./chunk-MF3UW36X.js";
21
+ } from "./chunk-37WF2Z3T.js";
22
+ import {
23
+ PathAccessDeniedError,
24
+ extractAbsolutePathsFromCommand,
25
+ extractSensitivePathsFromCommand,
26
+ registerPathConfirmation,
27
+ requestPathAccess
28
+ } from "./chunk-NZIYELSQ.js";
11
29
  import {
12
30
  AskUserInterrupt,
13
31
  askUserTool
@@ -24,47 +42,30 @@ import {
24
42
  createToolCallEvent,
25
43
  createToolResultEvent,
26
44
  streamLLMPure
27
- } from "./chunk-7772ENZN.js";
28
- import {
29
- startInspectProxy
30
- } from "./chunk-DH3SSBZN.js";
31
- import {
32
- getProject
33
- } from "./chunk-LXFLRM7M.js";
34
- import {
35
- checkAborted,
36
- createProcess,
37
- getProcessLogs,
38
- getProcessStatus,
39
- getSessionProcesses,
40
- spawnShell,
41
- spawnShellProcess,
42
- startProcessCommand,
43
- stopProcess
44
- } from "./chunk-OBQPXUXI.js";
45
+ } from "./chunk-EHVDAVYG.js";
45
46
  import {
46
47
  SETTINGS_KEYS,
47
48
  deleteSetting,
48
49
  getSetting,
49
50
  setSetting
50
- } from "./chunk-YMFCFMMB.js";
51
+ } from "./chunk-AFJLY43Z.js";
52
+ import {
53
+ startInspectProxy
54
+ } from "./chunk-EYIWUONA.js";
55
+ import {
56
+ getProject
57
+ } from "./chunk-6AE3JPDJ.js";
51
58
  import {
52
59
  getMaxPerSession,
53
60
  getSessionProcessCount,
54
61
  terminateProcessTree
55
62
  } from "./chunk-I72ACQ6R.js";
56
- import {
57
- PathAccessDeniedError,
58
- extractAbsolutePathsFromCommand,
59
- extractSensitivePathsFromCommand,
60
- requestPathAccess
61
- } from "./chunk-46RORSKU.js";
62
63
  import {
63
64
  getCurrentContextWindowId,
64
65
  getCurrentWindowMessageOptions,
65
66
  getEventStore,
66
67
  getRuntimeConfig
67
- } from "./chunk-NGJ5XJ7D.js";
68
+ } from "./chunk-M7EFXRDS.js";
68
69
  import {
69
70
  buildContextMessagesFromEventHistory,
70
71
  foldContextState,
@@ -74,30 +75,35 @@ import {
74
75
  handleToolResult,
75
76
  reorderToolMessages,
76
77
  stripOrphanedToolCalls
77
- } from "./chunk-DJASQ2ZB.js";
78
+ } from "./chunk-TSVFRWSG.js";
78
79
  import {
79
80
  createChatDoneMessage,
80
81
  createChatMessageMessage,
81
82
  createChatMessageUpdatedMessage,
83
+ createChatPathConfirmationMessage,
82
84
  createQueueStateMessage
83
- } from "./chunk-O3NUNU4H.js";
85
+ } from "./chunk-SJTG56CH.js";
84
86
  import {
85
87
  modelSupportsVision
86
88
  } from "./chunk-V4IE7HJY.js";
87
89
  import {
88
90
  OUTPUT_LIMITS,
91
+ cacheSet,
92
+ contentHash,
93
+ decodeDataUrl,
94
+ extractPdfContent,
89
95
  extractPdfText,
90
96
  formatPdfErrorMessage,
91
97
  isPdfBuffer,
92
98
  processPdfContent
93
- } from "./chunk-EZK7CGJ7.js";
99
+ } from "./chunk-RMXPVQP4.js";
94
100
  import {
95
101
  logger
96
102
  } from "./chunk-K44MW7JJ.js";
97
103
  import {
98
104
  loadGlobalConfig,
99
105
  saveGlobalConfig
100
- } from "./chunk-WGAAQASA.js";
106
+ } from "./chunk-YFVSSMA3.js";
101
107
  import {
102
108
  getGlobalConfigDir
103
109
  } from "./chunk-CQGTEGKL.js";
@@ -107,7 +113,34 @@ import { readFile as readFile2, stat, readdir } from "fs/promises";
107
113
  import { extname, join } from "path";
108
114
 
109
115
  // src/server/tools/tool-helpers.ts
116
+ import { randomUUID } from "crypto";
110
117
  import { resolve, isAbsolute } from "path";
118
+ function requestUserConfirmation(context, toolLabel, desc) {
119
+ if (context.dangerLevel === "dangerous") return Promise.resolve(true);
120
+ if (context.isSubAgent) return Promise.resolve(false);
121
+ if (typeof context.onEvent !== "function") return Promise.resolve(false);
122
+ const callId = context.toolCallId ?? randomUUID();
123
+ try {
124
+ const eventStore = getEventStore();
125
+ eventStore.append(context.sessionId, {
126
+ type: "path.confirmation_pending",
127
+ data: { callId, tool: toolLabel, paths: [desc], workdir: context.workdir, reason: "dangerous_command" }
128
+ });
129
+ } catch {
130
+ }
131
+ const promise = registerPathConfirmation(
132
+ callId,
133
+ [desc],
134
+ context.sessionId,
135
+ toolLabel,
136
+ context.workdir,
137
+ "dangerous_command"
138
+ );
139
+ context.onEvent(
140
+ createChatPathConfirmationMessage(callId, toolLabel, [desc], context.workdir, "dangerous_command")
141
+ );
142
+ return promise;
143
+ }
111
144
  function buildSignal(timeoutMs, contextSignal) {
112
145
  const timeoutSignal = AbortSignal.timeout(timeoutMs);
113
146
  if (contextSignal) {
@@ -188,7 +221,7 @@ function createTool(name, definition, handler2) {
188
221
  try {
189
222
  return await handler2(args, context, helpers);
190
223
  } catch (error) {
191
- if (error instanceof PathAccessDeniedError) {
224
+ if (error instanceof PathAccessDeniedError || error instanceof AskUserInterrupt) {
192
225
  throw error;
193
226
  }
194
227
  return {
@@ -1005,6 +1038,47 @@ function stripTailPipe(command) {
1005
1038
  }
1006
1039
 
1007
1040
  // src/server/tools/shell.ts
1041
+ var ESCAPE_PATTERNS = [
1042
+ /\bcd\s+['"]?(?:\.\.|\/|~)/,
1043
+ /\bcd\s+\$/,
1044
+ // cd with variable expansion ($HOME, ${HOME})
1045
+ /\bcd\s+\$\(/,
1046
+ // cd with command substitution $(...)
1047
+ /\bcd\s+`/,
1048
+ // cd with backtick substitution
1049
+ /\bgit\s+-C\s+/,
1050
+ /GIT_DIR=/,
1051
+ /\bgit\s+--work-tree[\s=]/,
1052
+ /\bgit\s+--git-dir[\s=]/
1053
+ ];
1054
+ function detectEscapePattern(command) {
1055
+ const normalized = command.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
1056
+ for (const pattern of ESCAPE_PATTERNS) {
1057
+ const match = normalized.match(pattern);
1058
+ if (match) {
1059
+ return match[0].trim();
1060
+ }
1061
+ }
1062
+ const segments = command.split(/[;&|]|&&|\|\|/);
1063
+ for (const segment of segments) {
1064
+ const trimmed = segment.trim();
1065
+ if (/^cd\s+.+\.\./.test(trimmed) || /^cd\s+['"]?\//.test(trimmed) || /^cd\s+['"]?~/.test(trimmed) || /^cd\s+['"]?\.\./.test(trimmed) || /^cd\s+.*\$\(/.test(trimmed) || // cd with $(...) substitution
1066
+ /^cd\s+.*`/.test(trimmed) || // cd with backtick substitution
1067
+ /^cd\s+.*\$\{?[A-Za-z_]/.test(trimmed)) {
1068
+ return trimmed;
1069
+ }
1070
+ }
1071
+ return null;
1072
+ }
1073
+ function detectGitMutation(command) {
1074
+ const gitMatch = command.match(
1075
+ /\bgit\s+['"]?(?:checkout|switch|branch\s+(-[dDmcMC]|--delete|--move|--copy|--force|-f)|-b\s+\S+|merge|rebase|reset|cherry-pick|worktree|clone|pull|push|fetch|update-ref|symbolic-ref)/
1076
+ );
1077
+ if (gitMatch) {
1078
+ return gitMatch[0].trim();
1079
+ }
1080
+ return null;
1081
+ }
1008
1082
  var rtkAvailable;
1009
1083
  async function checkRtkAvailability() {
1010
1084
  if (rtkAvailable !== void 0) return rtkAvailable;
@@ -1048,7 +1122,7 @@ var runCommandTool = createTool(
1048
1122
  type: "function",
1049
1123
  function: {
1050
1124
  name: "run_command",
1051
- description: 'Execute a shell command. Returns stdout, stderr, and exit code. Does NOT support trailing "&" for backgrounding \u2014 use background_process tool instead.',
1125
+ description: 'Execute a shell command. Returns stdout, stderr, and exit code. Does NOT support trailing "&" for backgrounding \u2014 use background_process tool instead.\nCommands run from your working directory automatically \u2014 do NOT prepend "cd /path/" to change to a directory you are already in. Doing so triggers unnecessary security confirmations.',
1052
1126
  parameters: {
1053
1127
  type: "object",
1054
1128
  properties: {
@@ -1076,6 +1150,26 @@ var runCommandTool = createTool(
1076
1150
  `Use background_process tool (action: "start") for background/long-running commands instead of '&'. See the tool description for details.`
1077
1151
  );
1078
1152
  }
1153
+ const escapeMatch = detectEscapePattern(args.command);
1154
+ if (escapeMatch) {
1155
+ const desc = `Command "${args.command}" contains workspace escape pattern "${escapeMatch}". Allow this command?`;
1156
+ const approved = await requestUserConfirmation(context, "command", desc);
1157
+ if (!approved) {
1158
+ return helpers.error(
1159
+ `User denied: command "${escapeMatch}" would escape the workspace. Use the workspace tool to switch workspaces directly.`
1160
+ );
1161
+ }
1162
+ }
1163
+ const mutationMatch = detectGitMutation(args.command);
1164
+ if (mutationMatch) {
1165
+ const desc = `Command "${args.command}" modifies Git state (${mutationMatch}). Allow this Git operation?`;
1166
+ const approved = await requestUserConfirmation(context, "command", desc);
1167
+ if (!approved) {
1168
+ return helpers.error(
1169
+ `User denied: "${mutationMatch}" modifies Git state. Use the workspace tool to switch workspaces or branches.`
1170
+ );
1171
+ }
1172
+ }
1079
1173
  const workingDir = args.cwd ? helpers.resolvePath(args.cwd) : context.workdir;
1080
1174
  const pathsToCheck = [workingDir];
1081
1175
  const commandPaths = extractAbsolutePathsFromCommand(args.command);
@@ -2208,7 +2302,7 @@ async function executeTools(assistantMsgId, toolCalls, ctx, append) {
2208
2302
  const onProgress = ctx.onMessage ? createToolProgressHandler(append, assistantMsgId, toolCall.id, ctx.sessionId) : void 0;
2209
2303
  const toolContext = {
2210
2304
  sessionManager: ctx.sessionManager,
2211
- workdir: ctx.workdir,
2305
+ workdir: ctx.sessionManager.getEffectiveWorkdir(ctx.sessionId),
2212
2306
  sessionId: ctx.sessionId,
2213
2307
  signal: ctx.signal,
2214
2308
  llmClient: ctx.llmClient,
@@ -2513,7 +2607,7 @@ ${CONTINUE_PROMPT}` : CONTINUE_PROMPT;
2513
2607
  sessionManager.setCurrentContextSize(sessionId, result.usage.promptTokens, config.subAgentMetadata?.subAgentId);
2514
2608
  if (!compacting) {
2515
2609
  const contextState2 = sessionManager.getContextState(sessionId);
2516
- const { shouldCompact, appendCompactionPrompt } = await import("./compactor-BB3YYVRP.js");
2610
+ const { shouldCompact, appendCompactionPrompt } = await import("./compactor-G7B3V24F.js");
2517
2611
  if (shouldCompact(contextState2.currentTokens, contextState2.maxTokens, runtimeConfig.context.compactionThreshold)) {
2518
2612
  appendCompactionPrompt(sessionId, append);
2519
2613
  compacting = true;
@@ -2870,10 +2964,9 @@ async function describeImageFromDataUrl(dataUrl, visionModel, options) {
2870
2964
  }
2871
2965
 
2872
2966
  // src/server/context/image-processor.ts
2873
- import { createHash as createHash2 } from "crypto";
2874
2967
  async function loadVisionModelFromGlobalConfig() {
2875
2968
  try {
2876
- const { loadGlobalConfig: loadGlobalConfig2, getVisionFallback } = await import("./config-EUMVEFHU.js");
2969
+ const { loadGlobalConfig: loadGlobalConfig2, getVisionFallback } = await import("./config-MD74ETY5.js");
2877
2970
  const runtimeConfig = getRuntimeConfig();
2878
2971
  const mode = runtimeConfig.mode ?? "production";
2879
2972
  const globalConfig = await loadGlobalConfig2(mode);
@@ -2891,12 +2984,12 @@ async function loadVisionModelFromGlobalConfig() {
2891
2984
  return void 0;
2892
2985
  }
2893
2986
  var descriptionCache = /* @__PURE__ */ new Map();
2894
- function contentHash(data) {
2895
- return createHash2("sha256").update(data).digest("hex").slice(0, 16);
2896
- }
2897
2987
  function isImageAttachment(att) {
2898
2988
  return att.mimeType.startsWith("image/");
2899
2989
  }
2990
+ function isPdfAttachment(att) {
2991
+ return att.mimeType === "application/pdf";
2992
+ }
2900
2993
  function hasImageMetadata(result) {
2901
2994
  const meta = result.metadata;
2902
2995
  if (!meta) return false;
@@ -2904,46 +2997,17 @@ function hasImageMetadata(result) {
2904
2997
  const mimeType = meta["mimeType"];
2905
2998
  return typeof dataUrl === "string" && typeof mimeType === "string" && mimeType.startsWith("image/");
2906
2999
  }
2907
- async function describeAttachment(att, messageId, options, descriptions) {
2908
- const cacheKey = contentHash(att.data);
2909
- if (descriptionCache.has(cacheKey)) {
2910
- const cached = descriptionCache.get(cacheKey);
2911
- descriptions.set(att.id, cached);
2912
- return cached;
2913
- }
2914
- if (options.visionModel) {
2915
- const startData = {
2916
- messageId,
2917
- attachmentId: att.id
2918
- };
2919
- if (att.filename !== void 0) {
2920
- startData.filename = att.filename;
2921
- }
2922
- options.onEvent?.({ type: "vision_fallback.start", data: startData });
2923
- const description = await describeImageFromDataUrl(att.data, options.visionModel, {
2924
- context: att.filename ? `File: ${att.filename}` : void 0,
2925
- signal: options.signal
2926
- });
2927
- descriptionCache.set(cacheKey, description);
2928
- descriptions.set(att.id, description);
2929
- options.onEvent?.({ type: "vision_fallback.done", data: { messageId, attachmentId: att.id, description } });
2930
- return description;
2931
- }
2932
- const placeholder = `[Image: ${att.filename || "image"}]`;
2933
- descriptions.set(att.id, placeholder);
2934
- return placeholder;
2935
- }
2936
- async function describeToolResultImage(dataUrl, filename, toolCallId, messageId, options, descriptions) {
3000
+ async function describeImageDataUrl(dataUrl, attachmentId, messageId, options, descriptions, filename) {
2937
3001
  const cacheKey = contentHash(dataUrl);
2938
3002
  if (descriptionCache.has(cacheKey)) {
2939
3003
  const cached = descriptionCache.get(cacheKey);
2940
- descriptions.set(toolCallId, cached);
3004
+ descriptions.set(attachmentId, cached);
2941
3005
  return cached;
2942
3006
  }
2943
3007
  if (options.visionModel) {
2944
3008
  const startData = {
2945
3009
  messageId,
2946
- attachmentId: toolCallId
3010
+ attachmentId
2947
3011
  };
2948
3012
  if (filename !== void 0) {
2949
3013
  startData.filename = filename;
@@ -2953,18 +3017,61 @@ async function describeToolResultImage(dataUrl, filename, toolCallId, messageId,
2953
3017
  context: filename ? `File: ${filename}` : void 0,
2954
3018
  signal: options.signal
2955
3019
  });
2956
- descriptionCache.set(cacheKey, description);
2957
- descriptions.set(toolCallId, description);
2958
- options.onEvent?.({
2959
- type: "vision_fallback.done",
2960
- data: { messageId, attachmentId: toolCallId, description }
2961
- });
3020
+ cacheSet(descriptionCache, cacheKey, description);
3021
+ descriptions.set(attachmentId, description);
3022
+ options.onEvent?.({ type: "vision_fallback.done", data: { messageId, attachmentId, description } });
2962
3023
  return description;
2963
3024
  }
2964
- const placeholder = `[Image: ${filename || "image"}]`;
2965
- descriptions.set(toolCallId, placeholder);
3025
+ const placeholder = filename ? `[Image: ${filename}]` : "[Image]";
3026
+ descriptions.set(attachmentId, placeholder);
2966
3027
  return placeholder;
2967
3028
  }
3029
+ async function describeAttachment(att, messageId, options, descriptions) {
3030
+ return describeImageDataUrl(att.data, att.id, messageId, options, descriptions, att.filename);
3031
+ }
3032
+ async function describePdfAttachment(att, messageId, options, descriptions) {
3033
+ const hasVision = !!options.visionModel;
3034
+ const cacheKey = `pdf:${hasVision}:${contentHash(att.data)}`;
3035
+ const filenameLabel = att.filename || "document.pdf";
3036
+ if (descriptionCache.has(cacheKey)) {
3037
+ const cached = descriptionCache.get(cacheKey);
3038
+ const output2 = `[PDF: ${filenameLabel}]${cached}`;
3039
+ descriptions.set(att.id, output2);
3040
+ return output2;
3041
+ }
3042
+ const buffer = decodeDataUrl(att.data);
3043
+ if (!buffer) {
3044
+ const fallback = `[PDF: ${filenameLabel}] (could not decode)`;
3045
+ cacheSet(descriptionCache, cacheKey, fallback);
3046
+ descriptions.set(att.id, fallback);
3047
+ return fallback;
3048
+ }
3049
+ let blocks;
3050
+ try {
3051
+ const result = await extractPdfContent(buffer);
3052
+ blocks = result.blocks;
3053
+ } catch {
3054
+ const errorText = `[PDF: ${filenameLabel}] (could not extract content)`;
3055
+ descriptions.set(att.id, errorText);
3056
+ return errorText;
3057
+ }
3058
+ let body = "";
3059
+ let imageIndex = 0;
3060
+ for (const block of blocks) {
3061
+ if (block.type === "text" && block.content) {
3062
+ body += "\n\n" + block.content;
3063
+ } else if (block.type === "image" && block.dataUrl) {
3064
+ const imgAttachmentId = `${att.id}/image-${imageIndex}`;
3065
+ const description = await describeImageDataUrl(block.dataUrl, imgAttachmentId, messageId, options, descriptions);
3066
+ body += "\n\n" + description;
3067
+ imageIndex++;
3068
+ }
3069
+ }
3070
+ const output = `[PDF: ${filenameLabel}]${body}`;
3071
+ cacheSet(descriptionCache, cacheKey, body);
3072
+ descriptions.set(att.id, output);
3073
+ return output;
3074
+ }
2968
3075
  async function processContextImages(events, options) {
2969
3076
  if (options.modelSupportsVision) {
2970
3077
  return { events, descriptions: /* @__PURE__ */ new Map() };
@@ -2975,9 +3082,8 @@ async function processContextImages(events, options) {
2975
3082
  if (event.type === "message.start") {
2976
3083
  const data = event.data;
2977
3084
  if (!data.attachments || data.attachments.length === 0) continue;
2978
- const imageAtts = data.attachments.filter(isImageAttachment);
2979
- if (imageAtts.length === 0) continue;
2980
3085
  let enriched = false;
3086
+ const imageAtts = data.attachments.filter(isImageAttachment);
2981
3087
  for (const att of imageAtts) {
2982
3088
  if (att.description) {
2983
3089
  descriptions.set(att.id, att.description);
@@ -2987,6 +3093,16 @@ async function processContextImages(events, options) {
2987
3093
  att.description = description;
2988
3094
  enriched = true;
2989
3095
  }
3096
+ const pdfAtts = data.attachments.filter(isPdfAttachment);
3097
+ for (const att of pdfAtts) {
3098
+ if (att.pdfContent) {
3099
+ descriptions.set(att.id, att.pdfContent);
3100
+ continue;
3101
+ }
3102
+ const pdfContent = await describePdfAttachment(att, data.messageId, options, descriptions);
3103
+ att.pdfContent = pdfContent;
3104
+ enriched = true;
3105
+ }
2990
3106
  if (enriched && options.persistEvent) {
2991
3107
  options.persistEvent(event.sessionId, event.seq, data);
2992
3108
  }
@@ -3001,13 +3117,13 @@ async function processContextImages(events, options) {
3001
3117
  }
3002
3118
  const dataUrl = meta["dataUrl"];
3003
3119
  const path = meta["path"];
3004
- const description = await describeToolResultImage(
3120
+ const description = await describeImageDataUrl(
3005
3121
  dataUrl,
3006
- path,
3007
3122
  data.toolCallId,
3008
3123
  data.messageId,
3009
3124
  options,
3010
- descriptions
3125
+ descriptions,
3126
+ path
3011
3127
  );
3012
3128
  meta["description"] = description;
3013
3129
  if (options.persistEvent) {
@@ -3019,16 +3135,24 @@ async function processContextImages(events, options) {
3019
3135
  let enriched = false;
3020
3136
  for (const message of snapshot.messages) {
3021
3137
  if (message.role === "user" && message.attachments && message.attachments.length > 0) {
3022
- const imageAtts = message.attachments.filter(isImageAttachment);
3023
- if (imageAtts.length === 0) continue;
3024
- for (const att of imageAtts) {
3025
- if (att.description) {
3026
- descriptions.set(att.id, att.description);
3027
- continue;
3138
+ for (const att of message.attachments) {
3139
+ if (isImageAttachment(att)) {
3140
+ if (att.description) {
3141
+ descriptions.set(att.id, att.description);
3142
+ continue;
3143
+ }
3144
+ const description = await describeAttachment(att, message.id, options, descriptions);
3145
+ att.description = description;
3146
+ enriched = true;
3147
+ } else if (isPdfAttachment(att)) {
3148
+ if (att.pdfContent) {
3149
+ descriptions.set(att.id, att.pdfContent);
3150
+ continue;
3151
+ }
3152
+ const pdfContent = await describePdfAttachment(att, message.id, options, descriptions);
3153
+ att.pdfContent = pdfContent;
3154
+ enriched = true;
3028
3155
  }
3029
- const description = await describeAttachment(att, message.id, options, descriptions);
3030
- att.description = description;
3031
- enriched = true;
3032
3156
  }
3033
3157
  }
3034
3158
  if (message.role === "assistant" && message.toolCalls) {
@@ -3041,13 +3165,13 @@ async function processContextImages(events, options) {
3041
3165
  }
3042
3166
  const dataUrl = meta["dataUrl"];
3043
3167
  const path = meta["path"];
3044
- const description = await describeToolResultImage(
3168
+ const description = await describeImageDataUrl(
3045
3169
  dataUrl,
3046
- path,
3047
3170
  toolCall.id,
3048
3171
  message.id,
3049
3172
  options,
3050
- descriptions
3173
+ descriptions,
3174
+ path
3051
3175
  );
3052
3176
  meta["description"] = description;
3053
3177
  enriched = true;
@@ -3418,7 +3542,7 @@ var callSubAgentTool = {
3418
3542
  };
3419
3543
  }
3420
3544
  try {
3421
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-JRCUWPJO.js");
3545
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-HGKUD327.js");
3422
3546
  const toolRegistry = getToolRegistryForAgent2(agentDef);
3423
3547
  const turnMetrics = new TurnMetrics();
3424
3548
  const result = await executeSubAgent({
@@ -4388,6 +4512,10 @@ var mcpConfigTool = createTool(
4388
4512
  enabled: {
4389
4513
  type: "boolean",
4390
4514
  description: "Whether the tool should be enabled (required for: toggle-tool)"
4515
+ },
4516
+ timeout: {
4517
+ type: "number",
4518
+ description: "Timeout in seconds for MCP tool calls (optional)"
4391
4519
  }
4392
4520
  },
4393
4521
  required: ["action"]
@@ -4417,7 +4545,7 @@ var mcpConfigTool = createTool(
4417
4545
  mcpNotifyChanged?.(sessionId);
4418
4546
  }
4419
4547
  async function rebuildTools() {
4420
- const { setMcpTools: setMcpTools2 } = await import("./tools-JRCUWPJO.js");
4548
+ const { setMcpTools: setMcpTools2 } = await import("./tools-HGKUD327.js");
4421
4549
  const mcpTools = createMcpTools(mcpManagerForTools);
4422
4550
  setMcpTools2(mcpTools);
4423
4551
  }
@@ -4454,13 +4582,17 @@ var mcpConfigTool = createTool(
4454
4582
  } else if (!args.command) {
4455
4583
  return helpers.error("command is required for stdio transport");
4456
4584
  }
4585
+ if (args.timeout !== void 0 && (typeof args.timeout !== "number" || args.timeout <= 0)) {
4586
+ return helpers.error("timeout must be a positive number");
4587
+ }
4457
4588
  const serverCfg = {
4458
4589
  transport: args.transport ?? "stdio",
4459
4590
  ...args.command ? { command: args.command } : {},
4460
4591
  ...args.args && args.args.length > 0 ? { args: args.args } : {},
4461
4592
  ...args.env && Object.keys(args.env).length > 0 ? { env: args.env } : {},
4462
4593
  ...args.url ? { url: args.url } : {},
4463
- ...args.headers && Object.keys(args.headers).length > 0 ? { headers: args.headers } : {}
4594
+ ...args.headers && Object.keys(args.headers).length > 0 ? { headers: args.headers } : {},
4595
+ ...args.timeout !== void 0 ? { timeout: args.timeout } : {}
4464
4596
  };
4465
4597
  await persistAndRebuild((mcpServers) => {
4466
4598
  mcpServers[args.name] = serverCfg;
@@ -4485,7 +4617,8 @@ var mcpConfigTool = createTool(
4485
4617
  ...args.args !== void 0 ? { args: args.args } : {},
4486
4618
  ...args.env !== void 0 ? { env: args.env } : {},
4487
4619
  ...args.url !== void 0 ? { url: args.url } : {},
4488
- ...args.headers !== void 0 ? { headers: args.headers } : {}
4620
+ ...args.headers !== void 0 ? { headers: args.headers } : {},
4621
+ ...args.timeout !== void 0 ? { timeout: args.timeout } : {}
4489
4622
  };
4490
4623
  const { error: updateError } = await applyMcpServerUpdate({
4491
4624
  name: args.name,
@@ -4691,7 +4824,7 @@ var workspaceTool = createTool(
4691
4824
  type: "function",
4692
4825
  function: {
4693
4826
  name: "workspace",
4694
- description: 'Manage workspaces for the current session. A workspace is a cloned copy of the project.\n\nUse "switch" to move between workspaces. Target "original" for the project root, or a workspace name.\nIf the workspace does not exist yet, it is created automatically.\n\nSetup commands (e.g. npm install) are configured via .openfox/workspace.json.\n\nActions:\n- switch: Switch to a workspace (target: "original" or a name, optional branch for new workspaces)\n- list: List all workspaces with their current branch and active status\n- delete: Delete a workspace by name (cannot delete "original")',
4827
+ description: 'Manage workspaces for the current session. A workspace is a cloned copy of the project.\n\nUse "switch" to move between workspaces. Target "original" for the project root, or a workspace name.\nIf the workspace does not exist yet, it is created automatically.\n\nYou can also change the branch of the current workspace by calling switch with the current\nworkspace name and a branch parameter. The branch will be checked out without recreating\nthe workspace and without losing uncommitted changes.\n\nIf the requested branch does not exist, it will be created. By default it is based on the\ndefault branch of the project (origin/HEAD). You can specify sourceBranch to base the new\nbranch on a different existing branch.\n\nActions that change the workspace (switch, delete) require explicit user approval via a confirmation dialog.\n\nSetup commands (e.g. npm install) are configured via .openfox/workspace.json.\n\nActions:\n- switch: Switch to a workspace (target: "original" or a name, optional branch for new workspaces)\n- list: List all workspaces with their current branch and active status\n- delete: Delete a workspace by name (cannot delete "original")',
4695
4828
  parameters: {
4696
4829
  type: "object",
4697
4830
  properties: {
@@ -4706,7 +4839,11 @@ var workspaceTool = createTool(
4706
4839
  },
4707
4840
  branch: {
4708
4841
  type: "string",
4709
- description: "Optional git branch to check out when creating a new workspace"
4842
+ description: "Optional git branch to check out when creating or switching to a workspace"
4843
+ },
4844
+ sourceBranch: {
4845
+ type: "string",
4846
+ description: "If branch does not exist, base the new branch on this existing branch. Default: origin/HEAD."
4710
4847
  }
4711
4848
  },
4712
4849
  required: ["action"]
@@ -4724,7 +4861,13 @@ var workspaceTool = createTool(
4724
4861
  if (!args.target || typeof args.target !== "string") {
4725
4862
  return helpers.error('Parameter "target" is required for action=switch ("original" or a workspace name)');
4726
4863
  }
4727
- const updated = await sessionManager.switchWorkspace(sessionId, args.target, args.branch);
4864
+ const currentSession = sessionManager.getSession(sessionId);
4865
+ const isBranchChange = args.branch !== void 0 && currentSession != null && args.target !== "original" && currentSession.workspace?.split("/").pop() === args.target;
4866
+ const label = args.target === "original" ? "original project" : `workspace "${args.target}"`;
4867
+ const desc = isBranchChange ? `Change branch to "${args.branch}" on ${label}` : `Switch to ${label}${args.branch ? ` on branch "${args.branch}"` : ""}`;
4868
+ const approved = await requestUserConfirmation(context, "workspace", desc);
4869
+ if (!approved) return helpers.error(`User denied: ${isBranchChange ? "branch change" : `switch to ${label}`}`);
4870
+ const updated = await sessionManager.switchWorkspace(sessionId, args.target, args.branch, args.sourceBranch);
4728
4871
  const wsName = args.target === "original" ? "original" : updated.workspace?.split("/").pop() ?? args.target;
4729
4872
  const branch = await getGitBranch(updated.workspace ?? updated.workdir);
4730
4873
  return helpers.success(
@@ -4746,7 +4889,7 @@ var workspaceTool = createTool(
4746
4889
  const project = sessionManager.getProject(session.projectId);
4747
4890
  if (!project) return helpers.error("Project not found");
4748
4891
  const currentBranch = await getGitBranch(session.workspace ?? session.workdir);
4749
- const named = await listWorkspaces(project.name);
4892
+ const named = await listWorkspaces(project.name, project.workdir);
4750
4893
  const activeWsName = session.workspace?.split("/").pop() ?? null;
4751
4894
  const workspaces = [
4752
4895
  { name: "original", branch: currentBranch, active: !session.workspace },
@@ -4762,9 +4905,9 @@ var workspaceTool = createTool(
4762
4905
  if (!args.target || typeof args.target !== "string") {
4763
4906
  return helpers.error('Parameter "target" is required for action=delete (the workspace name)');
4764
4907
  }
4765
- if (args.target === "original") {
4766
- return helpers.error("Cannot delete the original workspace");
4767
- }
4908
+ if (args.target === "original") return helpers.error("Cannot delete the original workspace");
4909
+ const approved = await requestUserConfirmation(context, "workspace", `Delete workspace "${args.target}"`);
4910
+ if (!approved) return helpers.error(`User denied: delete workspace "${args.target}"`);
4768
4911
  await sessionManager.deleteWorkspace(sessionId, args.target);
4769
4912
  return helpers.success(
4770
4913
  JSON.stringify(
@@ -5065,4 +5208,4 @@ export {
5065
5208
  getToolRegistryForAgent,
5066
5209
  createToolRegistry
5067
5210
  };
5068
- //# sourceMappingURL=chunk-D6CI2B7B.js.map
5211
+ //# sourceMappingURL=chunk-SGBXBP3S.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServerMessage,
3
3
  isClientMessage
4
- } from "./chunk-5GOOSZX7.js";
4
+ } from "./chunk-X6BXZFO7.js";
5
5
 
6
6
  // src/server/ws/protocol.ts
7
7
  function enrichMessagesWithToolResults(messages) {
@@ -43,7 +43,7 @@ function serializeServerMessage(message) {
43
43
  function createErrorMessage(code, message, correlationId) {
44
44
  return createServerMessage("error", { code, message }, correlationId);
45
45
  }
46
- function createSessionStateMessage(session, messages, pendingConfirmations = [], pendingQuestions, gitStatus, correlationId) {
46
+ function createSessionStateMessage(session, messages, pendingConfirmations = [], pendingQuestions, gitStatus, correlationId, hiddenCount) {
47
47
  const enrichedMessages = enrichMessagesWithToolResults(messages);
48
48
  return createServerMessage(
49
49
  "session.state",
@@ -52,7 +52,8 @@ function createSessionStateMessage(session, messages, pendingConfirmations = [],
52
52
  messages: enrichedMessages,
53
53
  pendingConfirmations,
54
54
  ...pendingQuestions ? { pendingQuestions } : {},
55
- ...gitStatus ? { gitStatus } : {}
55
+ ...gitStatus ? { gitStatus } : {},
56
+ ...hiddenCount !== void 0 ? { hiddenCount } : {}
56
57
  },
57
58
  correlationId
58
59
  );
@@ -343,4 +344,4 @@ export {
343
344
  createQueueStateMessage,
344
345
  storedEventToServerMessage
345
346
  };
346
- //# sourceMappingURL=chunk-O3NUNU4H.js.map
347
+ //# sourceMappingURL=chunk-SJTG56CH.js.map