openfox 2.0.129 → 2.0.131

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 (113) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -0
  3. package/dist/CHANGELOG.md +44 -0
  4. package/dist/auth-D3WLIBTG.js +19 -0
  5. package/dist/{auto-config-454YE6KC.js → auto-config-5VSNLVNI.js} +6 -5
  6. package/dist/{backend-4YPEC3J2.js → backend-NDBY3DAQ.js} +4 -2
  7. package/dist/{chat-handler-TH6JBO6G.js → chat-handler-RAIMQU7F.js} +35 -35
  8. package/dist/{chunk-ISE55FOR.js → chunk-2AI7VYPN.js} +11 -1
  9. package/dist/{chunk-HO7A7TMF.js → chunk-55DGZIZ6.js} +15 -5
  10. package/dist/{chunk-L24A6GZ7.js → chunk-5AI3LHRQ.js} +85 -57
  11. package/dist/{chunk-ZFNBATE3.js → chunk-6EZDW4JS.js} +3 -2
  12. package/dist/{chunk-ZXWJ6JTL.js → chunk-6MUSEPV7.js} +1197 -225
  13. package/dist/{chunk-OHTBWA7L.js → chunk-6SUBBT2M.js} +13 -13
  14. package/dist/{chunk-6LX43YRA.js → chunk-7ET46SNE.js} +4 -4
  15. package/dist/{chunk-GIJUWPRQ.js → chunk-7JVBDBXE.js} +2 -2
  16. package/dist/{chunk-MYNNMV75.js → chunk-7LGMQ5KS.js} +54 -36
  17. package/dist/{chunk-ZIL6VKLU.js → chunk-BKMWZTNO.js} +3 -3
  18. package/dist/chunk-BMBOSVRM.js +7 -0
  19. package/dist/{auth-GTGIGX5X.js → chunk-CQGJ5RWB.js} +4 -4
  20. package/dist/{chunk-3B232XG4.js → chunk-DPFET73G.js} +2 -2
  21. package/dist/chunk-DQTN4B33.js +235 -0
  22. package/dist/{chunk-KBESUA3W.js → chunk-ELYHHEYL.js} +3 -3
  23. package/dist/{chunk-DABWCAXS.js → chunk-EUOWCVU6.js} +19 -6
  24. package/dist/{chunk-YJD5A375.js → chunk-FGNJMKDV.js} +5 -5
  25. package/dist/{chunk-ATWOXI52.js → chunk-HCYSGLY2.js} +1 -1
  26. package/dist/{chunk-OYBUJIC3.js → chunk-JU757HVI.js} +5 -1
  27. package/dist/{chunk-AQBRI7TM.js → chunk-KWAHD2JA.js} +13 -8
  28. package/dist/{chunk-N2Q3KYWM.js → chunk-LQKXJPKP.js} +8 -3
  29. package/dist/{chunk-EHFIUXWZ.js → chunk-LW6MMSTD.js} +2 -2
  30. package/dist/{chunk-ZIYETXOW.js → chunk-M5T4RDPJ.js} +4 -4
  31. package/dist/{chunk-66MG44TF.js → chunk-MGEYAYGU.js} +2 -2
  32. package/dist/{chunk-RV5G7QQU.js → chunk-MQMPIXON.js} +3 -3
  33. package/dist/{chunk-K2QKX4ZJ.js → chunk-OA2FURC3.js} +8 -2
  34. package/dist/{chunk-567L5OJ6.js → chunk-P57IBMK4.js} +2 -2
  35. package/dist/{chunk-P6GTPKE5.js → chunk-QJG5K3X2.js} +278 -16
  36. package/dist/{chunk-2COZT3YI.js → chunk-RQPHGBWI.js} +24 -24
  37. package/dist/chunk-SVALN5KE.js +394 -0
  38. package/dist/{session-status-6Q5B5PEN.js → chunk-T3S5MTMD.js} +2 -3
  39. package/dist/{chunk-7YP4O4XE.js → chunk-TACWRDDC.js} +3 -3
  40. package/dist/{chunk-7ZMOP5LS.js → chunk-TS5WKTFB.js} +18 -1
  41. package/dist/{chunk-PBFGJFGC.js → chunk-UV6IH6HK.js} +32 -10
  42. package/dist/{chunk-7RIEI2TE.js → chunk-WCM5ZW5N.js} +52 -17
  43. package/dist/{chunk-Y3BSBKHY.js → chunk-XE6UC5Z3.js} +2 -2
  44. package/dist/{chunk-26WS7AH6.js → chunk-Y6ZGHOKM.js} +39 -7
  45. package/dist/chunk-YNMP647F.js +24 -0
  46. package/dist/{chunk-H6YIVK67.js → chunk-ZCFQOC3C.js} +26 -6
  47. package/dist/{chunk-IJFKJ5D6.js → chunk-ZL4IAFF3.js} +4 -4
  48. package/dist/{chunk-JQMQ4DGC.js → chunk-ZVHUISW7.js} +76 -32
  49. package/dist/cli/dev.js +1 -1
  50. package/dist/cli/index.js +1 -1
  51. package/dist/client-E6QLHPHD.js +16 -0
  52. package/dist/{client-pure-GU2SV3DD.js → client-pure-FLKP6PTI.js} +2 -2
  53. package/dist/compactor-OEJAA2YP.js +32 -0
  54. package/dist/dynamic-context-Y555VDLA.js +52 -0
  55. package/dist/{events-GMHRX4LF.js → events-ZJPRGVL2.js} +9 -9
  56. package/dist/{folding-7K2GRPF4.js → folding-QRZOHY2I.js} +7 -7
  57. package/dist/http-client-P4XTKAZA.js +11 -0
  58. package/dist/{inspect-proxy-UKPU3Z64.js → inspect-proxy-A3OROWPG.js} +4 -4
  59. package/dist/launch-Y4H7FDU2.js +53 -0
  60. package/dist/{manager-NOKJQTTF.js → manager-TMBFBCQL.js} +5 -5
  61. package/dist/mcp-YU7AM3UV.js +120 -0
  62. package/dist/{model-catalog-ZLOWTQA7.js → model-catalog-2PCKTYGW.js} +2 -2
  63. package/dist/{oauth-provider-DGBC2YM6.js → oauth-provider-VHUS2KEG.js} +8 -2
  64. package/dist/orchestrator-SQN2SJ2A.js +64 -0
  65. package/dist/package.json +1 -1
  66. package/dist/{path-security-UDZ7UVWK.js → path-security-CBACXAMS.js} +13 -13
  67. package/dist/{platform-EMKWQTZX.js → platform-5LQXOZO3.js} +4 -4
  68. package/dist/plugins-registry.json +12 -0
  69. package/dist/{processor-7RDR675X.js → processor-MEHODYNE.js} +34 -34
  70. package/dist/{profiles-HXGJIPF2.js → profiles-EBCVFYG7.js} +2 -2
  71. package/dist/{project-creator-JF3YVQAH.js → project-creator-6AGQTNXB.js} +3 -3
  72. package/dist/{projects-TPSLCJP2.js → projects-5U37RUTL.js} +3 -3
  73. package/dist/{protocol-DMneA8mx.d.ts → protocol-BMUoqwYH.d.ts} +2 -1
  74. package/dist/{protocol-TS3GVTT3.js → protocol-Y7MRKRUO.js} +3 -3
  75. package/dist/provider/index.d.ts +4 -4
  76. package/dist/{provider-DXEICEIE.js → provider-6YRII4IV.js} +11 -11
  77. package/dist/{provider-manager-PU7RS2TK.js → provider-manager-URNJBK5Q.js} +11 -11
  78. package/dist/{registry-Z4GVYFTV.js → registry-CMSSZHDC.js} +5 -5
  79. package/dist/{serve-Z5ITDPCQ.js → serve-NPADSJG7.js} +45 -43
  80. package/dist/server/index.d.ts +28 -4
  81. package/dist/server/index.js +42 -40
  82. package/dist/server-5TY22HDN.js +56 -0
  83. package/dist/service-VPXGREXF.js +27 -0
  84. package/dist/{session-overrides-4WAD3X5Q.js → session-overrides-OECQYRDK.js} +7 -7
  85. package/dist/session-status-UFO5IFI7.js +10 -0
  86. package/dist/{sessions-D7VRCHQY.js → sessions-NMPX5ICI.js} +6 -6
  87. package/dist/{settings-VOWWO4VG.js → settings-MDCRNQBY.js} +3 -3
  88. package/dist/shared/index.d.ts +3 -3
  89. package/dist/shared/index.js +1 -1
  90. package/dist/skill-defaults/browser/SKILL.md +20 -1
  91. package/dist/{tasks-SE3DOPVP.js → tasks-MNPXK2S5.js} +14 -14
  92. package/dist/{tools-6XUSIJN2.js → tools-6BDT5YMT.js} +37 -37
  93. package/dist/{types-PAKlAzp9.d.ts → types-BIkpxjaj.d.ts} +11 -2
  94. package/dist/{types-BWuVnaBw.d.ts → types-CoY8Ka1C.d.ts} +1 -1
  95. package/dist/{update-EJUI6PSV.js → update-XKCOBE47.js} +2 -2
  96. package/dist/web/assets/{index-CGKd7WLQ.js → index-Bx5r3aJx.js} +88 -88
  97. package/dist/web/assets/{index-CbX8veVW.css → index-Y300llHY.css} +1 -1
  98. package/dist/web/index.html +2 -2
  99. package/dist/web/sw.js +1 -1
  100. package/dist/{workspace-6CUE26YA.js → workspace-LM646OHC.js} +4 -4
  101. package/package.json +1 -1
  102. package/plugins-registry.json +12 -0
  103. package/dist/chunk-HIRDDCPD.js +0 -7
  104. package/dist/chunk-PHP7PLFX.js +0 -151
  105. package/dist/chunk-RSB4AR2A.js +0 -108
  106. package/dist/client-RZGGDGUA.js +0 -16
  107. package/dist/compactor-WKFNTUXL.js +0 -32
  108. package/dist/dynamic-context-ZVE733UI.js +0 -28
  109. package/dist/http-client-ROVNSHF6.js +0 -11
  110. package/dist/launch-5ARSU7EM.js +0 -53
  111. package/dist/orchestrator-E6GIZGEN.js +0 -64
  112. package/dist/server-MUJ7EHEI.js +0 -56
  113. package/dist/service-INQWTYY2.js +0 -27
@@ -1,13 +1,21 @@
1
+ import {
2
+ projectSessionStatus
3
+ } from "./chunk-T3S5MTMD.js";
4
+ import {
5
+ openFolder
6
+ } from "./chunk-WLJJRX22.js";
1
7
  import {
2
8
  McpOAuthProvider,
3
9
  setMcpOAuthServerPort
4
- } from "./chunk-RSB4AR2A.js";
10
+ } from "./chunk-DQTN4B33.js";
5
11
  import {
12
+ readMcpOAuthEntry,
6
13
  setMcpOAuthStoreMode,
7
14
  setMcpOAuthStorePath
8
15
  } from "./chunk-IJ53Y77N.js";
9
16
  import {
10
17
  createWebSocketServer,
18
+ currentSessionToken,
11
19
  getAuthConfig,
12
20
  hasPassword,
13
21
  isValidToken,
@@ -16,10 +24,10 @@ import {
16
24
  terminalManager,
17
25
  tokenFromPassword,
18
26
  verifyPassword
19
- } from "./chunk-MYNNMV75.js";
27
+ } from "./chunk-7LGMQ5KS.js";
20
28
  import {
21
29
  TEMPLATE_VARIABLES
22
- } from "./chunk-2COZT3YI.js";
30
+ } from "./chunk-RQPHGBWI.js";
23
31
  import {
24
32
  createToolRegistry,
25
33
  devServerManager,
@@ -28,42 +36,13 @@ import {
28
36
  getBuiltInToolNames,
29
37
  resolveLLMClientForAgent,
30
38
  setAgentModelOverride,
39
+ setMcpBootstrapForTools,
31
40
  setMcpConfigMode,
32
41
  setMcpConfigPath,
33
42
  setMcpManagerForTools,
34
43
  setMcpTools,
35
44
  setNotifyMcpServersChanged
36
- } from "./chunk-L24A6GZ7.js";
37
- import {
38
- deleteOwnedSkill,
39
- deleteProjectSkill,
40
- deleteSkill,
41
- findSkillById,
42
- getDefaultSkillIds,
43
- isDefaultSkill,
44
- isSkillEnabled,
45
- loadAllSkills,
46
- loadAllSkillsWithDiagnostics,
47
- loadDefaultSkills,
48
- loadProjectSkills,
49
- loadUserSkills,
50
- saveSkill,
51
- saveSkillToProject,
52
- setSkillEnabled,
53
- skillExists,
54
- updateOwnedSkill
55
- } from "./chunk-ZIL6VKLU.js";
56
- import {
57
- openFolder
58
- } from "./chunk-WLJJRX22.js";
59
- import {
60
- ALWAYS_ALLOWED,
61
- ALWAYS_ALLOWED_FOR_SUBAGENTS,
62
- TOP_LEVEL_ONLY_TOOLS
63
- } from "./chunk-GIJUWPRQ.js";
64
- import {
65
- createMcpTools
66
- } from "./chunk-JE7LW7Y6.js";
45
+ } from "./chunk-5AI3LHRQ.js";
67
46
  import {
68
47
  checkoutBranchFromSharedSource,
69
48
  deleteWorkspace,
@@ -80,7 +59,7 @@ import {
80
59
  validateRef,
81
60
  validateWorkspaceName,
82
61
  workspaceExists
83
- } from "./chunk-Y3BSBKHY.js";
62
+ } from "./chunk-XE6UC5Z3.js";
84
63
  import {
85
64
  commandExists,
86
65
  deleteCommand,
@@ -93,6 +72,7 @@ import {
93
72
  getDefaultWorkflowIds,
94
73
  isDefaultCommand,
95
74
  isDefaultWorkflow,
75
+ listAvailableWorkflows,
96
76
  loadAllCommands,
97
77
  loadAllWorkflows,
98
78
  loadDefaultCommands,
@@ -107,12 +87,49 @@ import {
107
87
  saveWorkflow,
108
88
  saveWorkflowToProject,
109
89
  workflowExists
110
- } from "./chunk-H6YIVK67.js";
90
+ } from "./chunk-ZCFQOC3C.js";
111
91
  import {
112
92
  getSessionDisabledServers,
113
93
  setSessionDisabledServers
114
- } from "./chunk-7YP4O4XE.js";
94
+ } from "./chunk-TACWRDDC.js";
95
+ import {
96
+ createMcpTools
97
+ } from "./chunk-JE7LW7Y6.js";
98
+ import {
99
+ deleteOwnedSkill,
100
+ deleteProjectSkill,
101
+ deleteSkill,
102
+ findSkillById,
103
+ getDefaultSkillIds,
104
+ isDefaultSkill,
105
+ isSkillEnabled,
106
+ loadAllSkills,
107
+ loadAllSkillsWithDiagnostics,
108
+ loadDefaultSkills,
109
+ loadProjectSkills,
110
+ loadUserSkills,
111
+ saveSkill,
112
+ saveSkillToProject,
113
+ setSkillEnabled,
114
+ skillExists,
115
+ updateOwnedSkill
116
+ } from "./chunk-BKMWZTNO.js";
117
+ import {
118
+ ALWAYS_ALLOWED,
119
+ ALWAYS_ALLOWED_FOR_SUBAGENTS,
120
+ TOP_LEVEL_ONLY_TOOLS
121
+ } from "./chunk-7JVBDBXE.js";
122
+ import {
123
+ EventEmitter,
124
+ getPendingQuestionsForSession,
125
+ provideAnswer
126
+ } from "./chunk-JURM3RPZ.js";
127
+ import {
128
+ getPendingConfirmationsBySession,
129
+ providePathConfirmation
130
+ } from "./chunk-Y6ZGHOKM.js";
115
131
  import {
132
+ combineEventsWithSnapshot,
116
133
  emitAssistantMessageStart,
117
134
  emitContextState,
118
135
  emitCriteriaSet,
@@ -128,7 +145,10 @@ import {
128
145
  getEventStore,
129
146
  getSessionState,
130
147
  initEventStore
131
- } from "./chunk-YJD5A375.js";
148
+ } from "./chunk-FGNJMKDV.js";
149
+ import {
150
+ buildMessagesFromStoredEvents
151
+ } from "./chunk-M5T4RDPJ.js";
132
152
  import {
133
153
  clearWorkflowExecution,
134
154
  createSession,
@@ -154,27 +174,7 @@ import {
154
174
  updateSessionRunning,
155
175
  updateSessionWorkdir,
156
176
  updateWorkflowExecutionStatus
157
- } from "./chunk-AQBRI7TM.js";
158
- import {
159
- createContextStateMessage,
160
- toClientSession
161
- } from "./chunk-567L5OJ6.js";
162
- import {
163
- getPathSeparator,
164
- isAbsolutePath
165
- } from "./chunk-66MG44TF.js";
166
- import {
167
- EventEmitter
168
- } from "./chunk-JURM3RPZ.js";
169
- import {
170
- createProviderManager,
171
- detectModel,
172
- getLlmStatus,
173
- parseDefaultModelSelection
174
- } from "./chunk-JQMQ4DGC.js";
175
- import {
176
- VERSION
177
- } from "./chunk-HIRDDCPD.js";
177
+ } from "./chunk-KWAHD2JA.js";
178
178
  import {
179
179
  agentExists,
180
180
  deleteAgent,
@@ -191,47 +191,69 @@ import {
191
191
  saveAgent,
192
192
  saveAgentToProject,
193
193
  setRuntimeConfig
194
- } from "./chunk-KBESUA3W.js";
194
+ } from "./chunk-ELYHHEYL.js";
195
195
  import {
196
196
  findFileByInternalId,
197
197
  pathExists
198
198
  } from "./chunk-OSXPQSLW.js";
199
199
  import {
200
+ createContextStateMessage,
201
+ toClientSession
202
+ } from "./chunk-P57IBMK4.js";
203
+ import {
204
+ getPathSeparator,
205
+ isAbsolutePath
206
+ } from "./chunk-MGEYAYGU.js";
207
+ import {
208
+ createProviderManager,
209
+ detectModel,
210
+ getLlmStatus,
211
+ parseDefaultModelSelection
212
+ } from "./chunk-ZVHUISW7.js";
213
+ import {
214
+ buildOpenFoxMcpBootstrap
215
+ } from "./chunk-YNMP647F.js";
216
+ import {
217
+ VERSION
218
+ } from "./chunk-BMBOSVRM.js";
219
+ import {
220
+ deleteProject,
200
221
  getProject,
201
222
  getProjectByWorkdir,
202
223
  updateProject
203
- } from "./chunk-3B232XG4.js";
224
+ } from "./chunk-DPFET73G.js";
204
225
  import {
205
226
  getModelProfile
206
- } from "./chunk-7ZMOP5LS.js";
227
+ } from "./chunk-TS5WKTFB.js";
207
228
  import {
229
+ detectBackendFromUrl,
208
230
  getBackendDisplayName
209
- } from "./chunk-PBFGJFGC.js";
231
+ } from "./chunk-UV6IH6HK.js";
210
232
  import {
211
233
  buildModelsUrl
212
234
  } from "./chunk-HNCM3D7Y.js";
213
235
  import {
214
236
  SessionNotFoundError,
215
237
  WorkspaceInUseError
216
- } from "./chunk-ZFNBATE3.js";
238
+ } from "./chunk-6EZDW4JS.js";
217
239
  import {
218
240
  deleteSetting,
219
241
  getSetting,
220
242
  setSetting
221
- } from "./chunk-N2Q3KYWM.js";
243
+ } from "./chunk-LQKXJPKP.js";
222
244
  import {
223
245
  initDatabase
224
- } from "./chunk-OYBUJIC3.js";
246
+ } from "./chunk-JU757HVI.js";
225
247
  import {
226
248
  isReasoningEffortValue
227
- } from "./chunk-ISE55FOR.js";
249
+ } from "./chunk-2AI7VYPN.js";
228
250
  import {
229
251
  logger,
230
252
  setLogLevel
231
253
  } from "./chunk-K44MW7JJ.js";
232
254
  import {
233
255
  createServerMessage
234
- } from "./chunk-ATWOXI52.js";
256
+ } from "./chunk-HCYSGLY2.js";
235
257
  import {
236
258
  getGlobalConfigDir
237
259
  } from "./chunk-CQGTEGKL.js";
@@ -776,6 +798,30 @@ var RULES = [
776
798
  tools: [],
777
799
  response: "TypeScript is a strongly typed programming language that builds on JavaScript."
778
800
  },
801
+ // Default-workflow completion: the mock must be able to drive the built-in
802
+ // workflow to $done (finalize/summarize agent steps + sub-agent return_value).
803
+ {
804
+ match: /You must call return_value with a summary of your findings/i,
805
+ tools: [
806
+ { name: "return_value", arguments: { content: "No issues found in the reviewed changes.", result: "success" } }
807
+ ],
808
+ response: "Returning findings summary."
809
+ },
810
+ {
811
+ match: /## Code Review Findings/i,
812
+ tools: [{ name: "step_done", arguments: {} }],
813
+ response: "All review findings resolved."
814
+ },
815
+ {
816
+ match: /The step cannot complete because not all review findings/i,
817
+ tools: [{ name: "step_done", arguments: {} }],
818
+ response: "Review findings handled."
819
+ },
820
+ {
821
+ match: /Produce a concise summary of what was accomplished/i,
822
+ tools: [{ name: "step_done", arguments: {} }],
823
+ response: "Summary produced."
824
+ },
779
825
  {
780
826
  match: /.*/,
781
827
  tools: [],
@@ -1561,7 +1607,8 @@ var McpManager = class {
1561
1607
  httpOpts.requestInit = { headers };
1562
1608
  }
1563
1609
  if (entry.config.oauth) {
1564
- httpOpts.authProvider = new McpOAuthProvider(name, entry.config.url);
1610
+ const stored = await readMcpOAuthEntry(name, entry.config.url);
1611
+ httpOpts.authProvider = stored?.tokens ? new McpOAuthProvider(name, entry.config.url) : McpOAuthProvider.forBackgroundProbe(name, entry.config.url);
1565
1612
  }
1566
1613
  transport = new StreamableHTTPClientTransport(new URL(entry.config.url), httpOpts);
1567
1614
  } else {
@@ -1715,7 +1762,11 @@ var McpManager = class {
1715
1762
  }
1716
1763
  const content = result.content;
1717
1764
  const textParts = content.filter((c) => c.type === "text").map((c) => c.text);
1718
- return { success: !result.isError, output: textParts.join("\n") };
1765
+ const text = textParts.join("\n");
1766
+ if (result.isError) {
1767
+ return { success: false, error: text || "MCP tool call failed" };
1768
+ }
1769
+ return { success: true, output: text };
1719
1770
  } catch (error) {
1720
1771
  return { success: false, error: error instanceof Error ? error.message : String(error) };
1721
1772
  }
@@ -2761,6 +2812,8 @@ var SessionManager = class {
2761
2812
  providerManager;
2762
2813
  dynamicContextChangedStore = /* @__PURE__ */ new Map();
2763
2814
  debugDumpStore = /* @__PURE__ */ new Map();
2815
+ announcedPromptHashStore = /* @__PURE__ */ new Map();
2816
+ announcedToolFingerprintStore = /* @__PURE__ */ new Map();
2764
2817
  warmedUpSessions = /* @__PURE__ */ new Set();
2765
2818
  switchLocks = /* @__PURE__ */ new Map();
2766
2819
  workspaceCreationLocks = /* @__PURE__ */ new Map();
@@ -3027,7 +3080,7 @@ var SessionManager = class {
3027
3080
  updateSessionMessageCount(newSession.id, messages.length);
3028
3081
  const cached2 = getSessionCachedPrompt(originalSessionId);
3029
3082
  if (cached2) {
3030
- updateSessionCachedPrompt(newSession.id, cached2.systemPrompt, cached2.tools, cached2.hash);
3083
+ updateSessionCachedPrompt(newSession.id, cached2.systemPrompt, cached2.tools, cached2.hash, cached2.promptHash);
3031
3084
  this.markWarmedUp(newSession.id);
3032
3085
  }
3033
3086
  this.emit({ type: "session_updated", session: this.requireSession(newSession.id) });
@@ -3097,6 +3150,8 @@ var SessionManager = class {
3097
3150
  eventStore.deleteSession(id);
3098
3151
  this.messageQueues.delete(id);
3099
3152
  this.warmedUpSessions.delete(id);
3153
+ this.announcedPromptHashStore.delete(id);
3154
+ this.announcedToolFingerprintStore.delete(id);
3100
3155
  deleteSession(id);
3101
3156
  if (this.activeSessionId === id) {
3102
3157
  this.activeSessionId = null;
@@ -3580,7 +3635,7 @@ var SessionManager = class {
3580
3635
  const state = getSessionState(sessionId, this.getCurrentModelContext(sessionId));
3581
3636
  const maxTokens = this.getCurrentModelContext(sessionId);
3582
3637
  const currentTokens = promptTokens + completionTokens;
3583
- const compactionCount = state?.contextState.compactionCount ?? 0;
3638
+ const compactionCount = subAgentId ? 0 : state?.contextState.compactionCount ?? 0;
3584
3639
  const dynamicContextChanged = this.getDynamicContextChanged(sessionId);
3585
3640
  emitContextState(
3586
3641
  sessionId,
@@ -3819,8 +3874,8 @@ var SessionManager = class {
3819
3874
  resetWarmup(sessionId) {
3820
3875
  this.warmedUpSessions.delete(sessionId);
3821
3876
  }
3822
- setCachedPrompt(sessionId, systemPrompt, tools, hash) {
3823
- updateSessionCachedPrompt(sessionId, systemPrompt, tools, hash);
3877
+ setCachedPrompt(sessionId, systemPrompt, tools, hash, promptHash) {
3878
+ updateSessionCachedPrompt(sessionId, systemPrompt, tools, hash, promptHash);
3824
3879
  this.resetWarmup(sessionId);
3825
3880
  }
3826
3881
  getCachedPrompt(sessionId) {
@@ -3839,6 +3894,27 @@ var SessionManager = class {
3839
3894
  getDynamicContextChanged(sessionId) {
3840
3895
  return this.dynamicContextChangedStore.get(sessionId) ?? false;
3841
3896
  }
3897
+ /**
3898
+ * The instructions/skills/model hash last announced to the model via a
3899
+ * system-prompt-change reminder. Drives exactly-once prompt-drift reminders.
3900
+ */
3901
+ getAnnouncedPromptHash(sessionId) {
3902
+ return this.announcedPromptHashStore.get(sessionId);
3903
+ }
3904
+ setAnnouncedPromptHash(sessionId, hash) {
3905
+ this.announcedPromptHashStore.set(sessionId, hash);
3906
+ }
3907
+ /**
3908
+ * The tool fingerprint last announced to the model via a tool-change
3909
+ * reminder. Drives exactly-once tool-drift reminders WITHOUT mutating the
3910
+ * cached prefix (the cached tools stay frozen until a manual rebase).
3911
+ */
3912
+ getAnnouncedToolFingerprint(sessionId) {
3913
+ return this.announcedToolFingerprintStore.get(sessionId);
3914
+ }
3915
+ setAnnouncedToolFingerprint(sessionId, fingerprint) {
3916
+ this.announcedToolFingerprintStore.set(sessionId, fingerprint);
3917
+ }
3842
3918
  /**
3843
3919
  * @deprecated Use addTokensUsed instead
3844
3920
  */
@@ -4748,10 +4824,766 @@ function createWorkflowRoutes(configDir, config3, projectDir) {
4748
4824
  return createCrudRoutes(crudConfig, configDir, projectDir);
4749
4825
  }
4750
4826
 
4751
- // src/server/routes/dev-server.ts
4827
+ // src/server/mcp/server/endpoint.ts
4752
4828
  import { Router as Router4 } from "express";
4753
- function createDevServerRoutes() {
4829
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4830
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4831
+
4832
+ // src/server/mcp/server/tools.ts
4833
+ import { z } from "zod";
4834
+ var CONTINUE_PROMPT = "Continue working on the acceptance criteria.";
4835
+ var MAX_DETAIL_CONTENT_LENGTH_DEFAULT = 400;
4836
+ var MIN_DETAIL_CONTENT_LENGTH = 50;
4837
+ var MAX_DETAIL_CONTENT_LENGTH_HARD_CAP = 1e4;
4838
+ var WAIT_TIMEOUT_SECONDS_DEFAULT = 60;
4839
+ var WAIT_TIMEOUT_SECONDS_MIN = 5;
4840
+ var WAIT_TIMEOUT_SECONDS_MAX = 600;
4841
+ var WAIT_POLL_INTERVAL_MS = 500;
4842
+ var WAIT_IDLE_GRACE_MS = 2e3;
4843
+ var METADATA_SCHEMAS = {
4844
+ criteria: {
4845
+ description: "Acceptance criteria that drive workflow transitions",
4846
+ fields: {
4847
+ id: "string (auto-generated)",
4848
+ description: "string \u2014 what needs to be done and how to verify it",
4849
+ status: "pending | completed | passed | failed"
4850
+ },
4851
+ defaultStatus: "pending"
4852
+ },
4853
+ todos: {
4854
+ description: "Task tracking items for the builder",
4855
+ fields: {
4856
+ id: "string (auto-generated)",
4857
+ description: "string \u2014 task description",
4858
+ status: "pending | in_progress | completed"
4859
+ },
4860
+ defaultStatus: "pending"
4861
+ },
4862
+ review_findings: {
4863
+ description: "Code review findings from the code_reviewer sub-agent",
4864
+ fields: {
4865
+ id: "string (auto-generated)",
4866
+ description: "string \u2014 finding description",
4867
+ status: "open | resolved | dismissed",
4868
+ severity: "minor | major | critical (optional)"
4869
+ },
4870
+ defaultStatus: "open"
4871
+ }
4872
+ };
4873
+ function computeSettlement(session, pendingQuestionsCount, pendingConfirmationsCount, activeExecutionStatus, completedEvidence = false) {
4874
+ if (session.phase === "done") return { settled: true, outcome: "completed" };
4875
+ if (session.phase === "blocked") return { settled: true, outcome: "blocked" };
4876
+ const waitingForUser = pendingQuestionsCount > 0 || pendingConfirmationsCount > 0 || activeExecutionStatus === "waiting";
4877
+ if (waitingForUser) return { settled: true, outcome: "waiting" };
4878
+ if (completedEvidence && !session.isRunning) return { settled: true, outcome: "completed" };
4879
+ return { settled: false, outcome: null };
4880
+ }
4881
+ function workflowPayload(execution) {
4882
+ if (!execution) return null;
4883
+ const pausedAtUserStep = execution.status === "waiting" && (execution.pendingChoices?.length ?? 0) > 0;
4884
+ return {
4885
+ id: execution.id,
4886
+ name: execution.workflowName,
4887
+ status: execution.status,
4888
+ ...execution.currentStepId ? { currentStepId: execution.currentStepId } : {},
4889
+ ...execution.currentStepName ? { currentStepName: execution.currentStepName } : {},
4890
+ stepOutput: execution.stepOutput,
4891
+ ...execution.pendingChoices ? { pendingChoices: execution.pendingChoices } : {},
4892
+ ...execution.subGroup ? { subGroup: execution.subGroup } : {},
4893
+ ...pausedAtUserStep ? { resumeHint: "Call openfox_resume_workflow with choice set to one of the pendingChoices ids above." } : {}
4894
+ };
4895
+ }
4896
+ function normalizeMetadataId(id) {
4897
+ if (typeof id !== "string" && typeof id !== "number") return void 0;
4898
+ if (id === "") return void 0;
4899
+ return String(id);
4900
+ }
4901
+ function resolveWaitTimeout(value) {
4902
+ if (typeof value !== "number" || !Number.isFinite(value)) return WAIT_TIMEOUT_SECONDS_DEFAULT;
4903
+ return Math.max(WAIT_TIMEOUT_SECONDS_MIN, Math.min(WAIT_TIMEOUT_SECONDS_MAX, Math.floor(value)));
4904
+ }
4905
+ var sleep2 = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
4906
+ function ok(payload) {
4907
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
4908
+ }
4909
+ function fail(message) {
4910
+ return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
4911
+ }
4912
+ function truncate(content, maxLength) {
4913
+ return content.length > maxLength ? `${content.slice(0, maxLength)}...` : content;
4914
+ }
4915
+ function resolveMaxContentLength(value) {
4916
+ if (typeof value !== "number" || !Number.isFinite(value)) return MAX_DETAIL_CONTENT_LENGTH_DEFAULT;
4917
+ return Math.max(MIN_DETAIL_CONTENT_LENGTH, Math.min(MAX_DETAIL_CONTENT_LENGTH_HARD_CAP, Math.floor(value)));
4918
+ }
4919
+ function buildSessionStatusPayload(sessionId, deps) {
4920
+ const session = deps.sessionManager.getSession(sessionId);
4921
+ if (!session) return null;
4922
+ const pendingQuestions = deps.pendingQuestions(sessionId);
4923
+ const pendingConfirmations = deps.pendingConfirmations(sessionId);
4924
+ const activeExecution = deps.sessionManager.getActiveWorkflowExecution(sessionId);
4925
+ const status = projectSessionStatus({
4926
+ session,
4927
+ pendingQuestionsCount: pendingQuestions.length,
4928
+ pendingConfirmationsCount: pendingConfirmations.length,
4929
+ activeWorkflowStepName: activeExecution?.currentStepName ?? null
4930
+ });
4931
+ return {
4932
+ ...status,
4933
+ isRunning: session.isRunning,
4934
+ pending: { questions: pendingQuestions, confirmations: pendingConfirmations },
4935
+ workflow: workflowPayload(activeExecution)
4936
+ };
4937
+ }
4938
+ function createOpenFoxMcpTools(deps) {
4939
+ const { sessionManager } = deps;
4940
+ const requireSession = (sessionId) => {
4941
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
4942
+ return { ok: false, error: "sessionId is required" };
4943
+ }
4944
+ const session = sessionManager.getSession(sessionId);
4945
+ if (!session) {
4946
+ return { ok: false, error: `Session not found: ${sessionId}` };
4947
+ }
4948
+ return { ok: true, session };
4949
+ };
4950
+ const requireSessionAndId = (args) => {
4951
+ const check = requireSession(args["sessionId"]);
4952
+ if (!check.ok) return check;
4953
+ return { ok: true, session: check.session, sessionId: check.session.id };
4954
+ };
4955
+ const resolvePendingCallId = (sessionId, callId, pending, noun) => {
4956
+ if (typeof callId !== "string" || callId.length === 0) {
4957
+ return { kind: "list" };
4958
+ }
4959
+ if (!pending.some((item) => item.callId === callId)) {
4960
+ return { kind: "error", error: `No pending ${noun} with callId "${callId}" for session ${sessionId}` };
4961
+ }
4962
+ return { kind: "found", callId };
4963
+ };
4964
+ const tools = [
4965
+ {
4966
+ name: "openfox_projects",
4967
+ description: "List all OpenFox projects.",
4968
+ inputSchema: {},
4969
+ handler: async () => {
4970
+ const projects = deps.listProjects();
4971
+ return ok(projects);
4972
+ }
4973
+ },
4974
+ {
4975
+ name: "openfox_create_project",
4976
+ description: "Create a new OpenFox project at the given directory. Creates the directory and initializes git if missing, then registers the project.",
4977
+ inputSchema: {
4978
+ name: z.string().describe("Project name (letters, numbers, hyphens, underscores, dots, spaces)"),
4979
+ workdir: z.string().describe("Absolute path of the project directory")
4980
+ },
4981
+ handler: async (args) => {
4982
+ const name = args["name"];
4983
+ const workdir = args["workdir"];
4984
+ if (typeof name !== "string" || name.trim().length === 0) {
4985
+ return fail("name is required");
4986
+ }
4987
+ if (typeof workdir !== "string" || workdir.trim().length === 0) {
4988
+ return fail("workdir is required");
4989
+ }
4990
+ try {
4991
+ const project = await deps.createProject(name.trim(), workdir.trim());
4992
+ return ok({ project });
4993
+ } catch (error) {
4994
+ return fail(error instanceof Error ? error.message : String(error));
4995
+ }
4996
+ }
4997
+ },
4998
+ {
4999
+ name: "openfox_delete_project",
5000
+ description: "Delete an OpenFox project and all its sessions by id.",
5001
+ inputSchema: {
5002
+ projectId: z.string().describe("Project id to delete")
5003
+ },
5004
+ handler: async (args) => {
5005
+ const projectId = args["projectId"];
5006
+ if (typeof projectId !== "string" || projectId.length === 0) {
5007
+ return fail("projectId is required");
5008
+ }
5009
+ const deleted = deps.deleteProject(projectId);
5010
+ return ok({ deleted });
5011
+ }
5012
+ },
5013
+ {
5014
+ name: "openfox_create_session",
5015
+ description: "Create a new OpenFox session in a project, optionally selecting an agent (e.g. planner or builder) as the session mode.",
5016
+ inputSchema: {
5017
+ projectId: z.string().describe("Project to create the session in"),
5018
+ title: z.string().optional().describe("Optional session title"),
5019
+ agentId: z.string().optional().describe("Optional agent to use as the session mode (validated against available top-level agents)")
5020
+ },
5021
+ handler: async (args) => {
5022
+ const projectId = args["projectId"];
5023
+ if (typeof projectId !== "string" || projectId.length === 0) {
5024
+ return fail("projectId is required");
5025
+ }
5026
+ const title = typeof args["title"] === "string" ? args["title"] : void 0;
5027
+ const rawAgentId = args["agentId"];
5028
+ const agentId = typeof rawAgentId === "string" && rawAgentId.trim().length > 0 ? rawAgentId : void 0;
5029
+ try {
5030
+ const session = sessionManager.createSession(projectId, title, null, null);
5031
+ if (agentId) {
5032
+ const project = sessionManager.getProject(projectId);
5033
+ const topLevelIds = project ? await deps.topLevelAgentIds(project.workdir) : [];
5034
+ if (!topLevelIds.includes(agentId)) {
5035
+ return fail(
5036
+ `Invalid agentId "${agentId}". Must be one of: ${topLevelIds.join(", ") || "(none available)"}`
5037
+ );
5038
+ }
5039
+ sessionManager.setMode(session.id, agentId);
5040
+ }
5041
+ return ok({
5042
+ sessionId: session.id,
5043
+ projectId,
5044
+ title: session.metadata?.title ?? title ?? null,
5045
+ mode: agentId ?? session.mode
5046
+ });
5047
+ } catch (error) {
5048
+ return fail(error instanceof Error ? error.message : String(error));
5049
+ }
5050
+ }
5051
+ },
5052
+ {
5053
+ name: "openfox_set_mode",
5054
+ description: "Switch an existing session to a different mode (agent, e.g. planner or builder) at any time. The mode is validated against the session project\u2019s top-level agents.",
5055
+ inputSchema: {
5056
+ sessionId: z.string().describe("Session to switch"),
5057
+ mode: z.string().describe("Target mode: one of the session project\u2019s top-level agents")
5058
+ },
5059
+ handler: async (args) => {
5060
+ const check = requireSession(args["sessionId"]);
5061
+ if (!check.ok) return fail(check.error);
5062
+ const session = check.session;
5063
+ const mode = args["mode"];
5064
+ if (typeof mode !== "string" || mode.trim().length === 0) {
5065
+ return fail("mode is required");
5066
+ }
5067
+ const project = sessionManager.getProject(session.projectId);
5068
+ const topLevelIds = project ? await deps.topLevelAgentIds(project.workdir) : [];
5069
+ if (!topLevelIds.includes(mode)) {
5070
+ return fail(`Invalid mode "${mode}". Must be one of: ${topLevelIds.join(", ") || "(none available)"}`);
5071
+ }
5072
+ const updated = sessionManager.setMode(session.id, mode);
5073
+ return ok({ sessionId: session.id, mode: updated?.mode ?? mode });
5074
+ }
5075
+ },
5076
+ {
5077
+ name: "openfox_sessions",
5078
+ description: "List sessions, optionally scoped to a project, with running state and pending interaction counts.",
5079
+ inputSchema: {
5080
+ projectId: z.string().optional().describe("Scope the listing to a project"),
5081
+ limit: z.number().int().positive().optional().describe("Maximum sessions to return (default 20)"),
5082
+ offset: z.number().int().nonnegative().optional().describe("Number of sessions to skip (default 0)")
5083
+ },
5084
+ handler: async (args) => {
5085
+ const limit = typeof args["limit"] === "number" ? args["limit"] : 20;
5086
+ const offset = typeof args["offset"] === "number" ? args["offset"] : 0;
5087
+ const projectId = args["projectId"];
5088
+ const result = typeof projectId === "string" && projectId.length > 0 ? sessionManager.listSessionsByProject(projectId, limit, offset) : sessionManager.listSessionsLimited(limit, offset);
5089
+ const sessions = result.sessions.map((s) => ({
5090
+ id: s.id,
5091
+ projectId: s.projectId,
5092
+ title: s.title ?? null,
5093
+ isRunning: s.isRunning,
5094
+ pendingQuestionCount: deps.pendingQuestions(s.id).length,
5095
+ pendingConfirmationCount: deps.pendingConfirmations(s.id).length,
5096
+ pausedWorkflowStep: sessionManager.getActiveWorkflowExecution(s.id)?.status === "waiting",
5097
+ updatedAt: s.updatedAt
5098
+ }));
5099
+ return ok({ sessions, hasMore: result.hasMore });
5100
+ }
5101
+ },
5102
+ {
5103
+ name: "openfox_session_status",
5104
+ description: "Read a session state snapshot: projected status, running flag, active workflow execution (including a paused user step with its choices), pending questions and confirmations, last activity, and a UI link.",
5105
+ inputSchema: {
5106
+ sessionId: z.string().describe("Session to inspect")
5107
+ },
5108
+ handler: async (args) => {
5109
+ const check = requireSession(args["sessionId"]);
5110
+ if (!check.ok) return fail(check.error);
5111
+ const payload = buildSessionStatusPayload(check.session.id, deps);
5112
+ if (!payload) return fail(`Session not found: ${check.session.id}`);
5113
+ return ok(payload);
5114
+ }
5115
+ },
5116
+ {
5117
+ name: "openfox_session_detail",
5118
+ description: "Read the most recent messages of a session (content truncated) so progress can be followed without a live WebSocket.",
5119
+ inputSchema: {
5120
+ sessionId: z.string().describe("Session to read"),
5121
+ limit: z.number().int().positive().optional().describe("Maximum messages to return (default 20, capped at 50)"),
5122
+ maxContentLength: z.number().int().positive().optional().describe("Maximum content length per message (default 400, clamped to 50\u201310000)")
5123
+ },
5124
+ handler: async (args) => {
5125
+ const check = requireSession(args["sessionId"]);
5126
+ if (!check.ok) return fail(check.error);
5127
+ const limit = typeof args["limit"] === "number" ? args["limit"] : 20;
5128
+ const maxContentLength = resolveMaxContentLength(args["maxContentLength"]);
5129
+ const { messages, hiddenCount } = deps.recentMessages(check.session.id, limit);
5130
+ return ok({
5131
+ messages: messages.map((message) => ({
5132
+ id: message.id,
5133
+ role: message.role,
5134
+ content: truncate(message.content ?? "", maxContentLength),
5135
+ ...message.toolCalls && message.toolCalls.length > 0 ? { toolCalls: message.toolCalls.map((toolCall) => toolCall.name) } : {}
5136
+ })),
5137
+ hiddenCount
5138
+ });
5139
+ }
5140
+ },
5141
+ {
5142
+ name: "openfox_session_metadata",
5143
+ description: "Manage structured session data by key (criteria, todos, review_findings, ...). Actions: get (list entries for a key), list (keys), add, update, remove, schema. Writes land in the same store as the agent\u2019s session_metadata tool, so they drive workflow transitions.",
5144
+ inputSchema: {
5145
+ sessionId: z.string().describe("Session to manage"),
5146
+ action: z.enum(["get", "list", "add", "update", "remove", "schema"]).describe("Operation to perform"),
5147
+ key: z.string().optional().describe("Metadata key (required for get/add/update/remove/schema)"),
5148
+ id: z.string().optional().describe("Item id (required for update and remove)"),
5149
+ description: z.string().optional().describe("Item description (required for add)"),
5150
+ status: z.string().optional().describe("Item status (for add/update)")
5151
+ },
5152
+ handler: async (args) => {
5153
+ const check = requireSession(args["sessionId"]);
5154
+ if (!check.ok) return fail(check.error);
5155
+ const session = check.session;
5156
+ const action = args["action"];
5157
+ const key = typeof args["key"] === "string" && args["key"].length > 0 ? args["key"] : void 0;
5158
+ const entries = session.metadataEntries ?? {};
5159
+ if (action === "list") {
5160
+ return ok({ keys: Object.entries(entries).map(([k, v]) => ({ key: k, count: v.length })) });
5161
+ }
5162
+ if (action === "schema") {
5163
+ if (!key) return fail("key is required for schema");
5164
+ const schema = METADATA_SCHEMAS[key];
5165
+ if (!schema) return ok({ key, generic: true });
5166
+ return ok({ key, description: schema.description, fields: schema.fields });
5167
+ }
5168
+ if (action === "get") {
5169
+ if (!key) return fail("key is required for get");
5170
+ return ok({ key, entries: entries[key] ?? [] });
5171
+ }
5172
+ if (action === "add") {
5173
+ if (!key) return fail("key is required for add");
5174
+ const description = args["description"];
5175
+ if (typeof description !== "string" || description.trim().length === 0) {
5176
+ return fail("description is required for add");
5177
+ }
5178
+ const current = entries[key] ?? [];
5179
+ const newEntry = {
5180
+ id: normalizeMetadataId(args["id"]) ?? (current.length + 1).toString(),
5181
+ description,
5182
+ status: typeof args["status"] === "string" ? args["status"] : METADATA_SCHEMAS[key]?.defaultStatus ?? "pending"
5183
+ };
5184
+ const updated = [...current, newEntry];
5185
+ deps.setMetadataEntries(session.id, key, updated);
5186
+ return ok({ key, added: newEntry, entries: updated });
5187
+ }
5188
+ if (action === "update") {
5189
+ if (!key) return fail("key is required for update");
5190
+ const id = normalizeMetadataId(args["id"]);
5191
+ if (!id) return fail("id is required for update");
5192
+ const current = entries[key];
5193
+ if (!current) return fail(`Key "${key}" not found`);
5194
+ const idx = current.findIndex((e) => e.id === id);
5195
+ if (idx === -1) return fail(`Item "${id}" not found in "${key}"`);
5196
+ const description = args["description"];
5197
+ const status = args["status"];
5198
+ if (typeof description !== "string" && typeof status !== "string") {
5199
+ return fail("provide at least one of description or status for update");
5200
+ }
5201
+ const updated = current.map(
5202
+ (e, i) => i === idx ? {
5203
+ ...e,
5204
+ ...typeof description === "string" ? { description } : {},
5205
+ ...typeof status === "string" ? { status } : {}
5206
+ } : e
5207
+ );
5208
+ deps.setMetadataEntries(session.id, key, updated);
5209
+ return ok({ key, updated: id, entries: updated });
5210
+ }
5211
+ if (action === "remove") {
5212
+ if (!key) return fail("key is required for remove");
5213
+ const id = normalizeMetadataId(args["id"]);
5214
+ if (!id) return fail("id is required for remove");
5215
+ const current = entries[key];
5216
+ if (!current) return fail(`Key "${key}" not found`);
5217
+ const updated = current.filter((e) => e.id !== id);
5218
+ if (updated.length === current.length) return fail(`Item "${id}" not found in "${key}"`);
5219
+ deps.setMetadataEntries(session.id, key, updated);
5220
+ return ok({ key, removed: id, entries: updated });
5221
+ }
5222
+ return fail(`Unknown action "${String(action)}". Must be one of: get, list, add, update, remove, schema`);
5223
+ }
5224
+ },
5225
+ {
5226
+ name: "openfox_send_message",
5227
+ description: "Queue a message for a session. Safe on running sessions (queued, processed at the next turn boundary) and starts a new turn on idle sessions. Returns the queue state.",
5228
+ inputSchema: {
5229
+ sessionId: z.string().describe("Session to message"),
5230
+ content: z.string().optional().describe("Message content"),
5231
+ attachments: z.array(z.record(z.string(), z.unknown())).optional().describe("Optional attachments"),
5232
+ messageKind: z.string().optional().describe("Optional message kind tag")
5233
+ },
5234
+ handler: async (args) => {
5235
+ const check = requireSession(args["sessionId"]);
5236
+ if (!check.ok) return fail(check.error);
5237
+ const content = typeof args["content"] === "string" ? args["content"] : void 0;
5238
+ const rawAttachments = args["attachments"];
5239
+ const attachments = Array.isArray(rawAttachments) ? rawAttachments : void 0;
5240
+ const messageKind = typeof args["messageKind"] === "string" ? args["messageKind"] : void 0;
5241
+ if (!content?.trim() && !(attachments && attachments.length > 0)) {
5242
+ return fail("content or attachments is required");
5243
+ }
5244
+ sessionManager.queueMessage(check.session.id, "asap", content, attachments, messageKind);
5245
+ return ok({ queued: true, queueState: sessionManager.getQueueState(check.session.id) });
5246
+ }
5247
+ },
5248
+ {
5249
+ name: "openfox_continue",
5250
+ description: "Continue an idle or blocked session: a blocked session is reset to the build phase and an auto-prompt is queued; an idle session is acknowledged (use openfox_send_message to drive it with content).",
5251
+ inputSchema: {
5252
+ sessionId: z.string().describe("Session to continue")
5253
+ },
5254
+ handler: async (args) => {
5255
+ const check = requireSessionAndId(args);
5256
+ if (!check.ok) return fail(check.error);
5257
+ const { session, sessionId } = check;
5258
+ if (session.isRunning) {
5259
+ return fail(`Session ${sessionId} is already running`);
5260
+ }
5261
+ if (session.phase === "blocked") {
5262
+ sessionManager.setPhase(sessionId, "build");
5263
+ sessionManager.queueMessage(sessionId, "asap", CONTINUE_PROMPT, void 0, "auto-prompt");
5264
+ return ok({ accepted: true });
5265
+ }
5266
+ return ok({
5267
+ accepted: true,
5268
+ note: `Session ${sessionId} is idle \u2014 use openfox_send_message to give it work.`
5269
+ });
5270
+ }
5271
+ },
5272
+ {
5273
+ name: "openfox_wait",
5274
+ description: "Block until a session settles: it finishes (completed), blocks (blocked), or pauses for the user (waiting \u2014 a pending question, path confirmation, or a paused workflow user step). Returns the outcome and a full status snapshot, or times out.",
5275
+ inputSchema: {
5276
+ sessionId: z.string().describe("Session to wait on"),
5277
+ timeout: z.number().int().positive().optional().describe("Maximum wait in seconds (default 60, clamped 5\u2013600)")
5278
+ },
5279
+ handler: async (args) => {
5280
+ const check = requireSession(args["sessionId"]);
5281
+ if (!check.ok) return fail(check.error);
5282
+ const sessionId = check.session.id;
5283
+ const timeoutSeconds = resolveWaitTimeout(args["timeout"]);
5284
+ const startedAt = Date.now();
5285
+ const deadline = startedAt + timeoutSeconds * 1e3;
5286
+ const graceUntil = startedAt + WAIT_IDLE_GRACE_MS;
5287
+ let sawRunning = false;
5288
+ while (true) {
5289
+ const session = sessionManager.getSession(sessionId);
5290
+ if (!session) return fail(`Session not found: ${sessionId}`);
5291
+ if (session.isRunning) sawRunning = true;
5292
+ const pendingQuestions = deps.pendingQuestions(sessionId);
5293
+ const pendingConfirmations = deps.pendingConfirmations(sessionId);
5294
+ const activeExecution = sessionManager.getActiveWorkflowExecution(sessionId);
5295
+ const now = Date.now();
5296
+ const hasWork = (session.messages?.length ?? 0) > 0;
5297
+ const completedEvidence = sawRunning || hasWork && now >= graceUntil;
5298
+ const settlement = computeSettlement(
5299
+ session,
5300
+ pendingQuestions.length,
5301
+ pendingConfirmations.length,
5302
+ activeExecution?.status ?? null,
5303
+ completedEvidence
5304
+ );
5305
+ if (settlement.settled) {
5306
+ return ok({
5307
+ settled: true,
5308
+ outcome: settlement.outcome,
5309
+ waitedMs: now - startedAt,
5310
+ status: buildSessionStatusPayload(sessionId, deps)
5311
+ });
5312
+ }
5313
+ if (now >= deadline) {
5314
+ return ok({
5315
+ settled: false,
5316
+ timedOut: true,
5317
+ waitedMs: now - startedAt,
5318
+ status: buildSessionStatusPayload(sessionId, deps)
5319
+ });
5320
+ }
5321
+ await sleep2(WAIT_POLL_INTERVAL_MS);
5322
+ }
5323
+ }
5324
+ },
5325
+ {
5326
+ name: "openfox_stop",
5327
+ description: "Stop a session completely: drains the queue, aborts the active execution, and cancels pending interactions.",
5328
+ inputSchema: {
5329
+ sessionId: z.string().describe("Session to stop")
5330
+ },
5331
+ handler: async (args) => {
5332
+ const check = requireSession(args["sessionId"]);
5333
+ if (!check.ok) return fail(check.error);
5334
+ deps.stopSession(check.session.id);
5335
+ return ok({ stopped: true });
5336
+ }
5337
+ },
5338
+ {
5339
+ name: "openfox_workflows",
5340
+ description: "List the workflows available to a project (builtin, user, and project scope) with their parameters.",
5341
+ inputSchema: {
5342
+ projectDir: z.string().optional().describe("Project directory for project-scoped workflows")
5343
+ },
5344
+ handler: async (args) => {
5345
+ const projectDir = typeof args["projectDir"] === "string" ? args["projectDir"] : void 0;
5346
+ return ok(await deps.listWorkflows(projectDir));
5347
+ }
5348
+ },
5349
+ {
5350
+ name: "openfox_launch_workflow",
5351
+ description: "Launch (or resume) a workflow run in a session. On idle sessions the run starts immediately; on running sessions it is queued as a workflow-launch marker. Supports resuming paused user steps via resumeFrom/userChoice.",
5352
+ inputSchema: {
5353
+ sessionId: z.string().describe("Session to run the workflow in"),
5354
+ workflowId: z.string().optional().describe("Workflow to run (default: the active workflow)"),
5355
+ params: z.record(z.string(), z.string()).optional().describe("Workflow parameters"),
5356
+ subGroup: z.string().optional().describe("Run only a sub-group slice of the workflow"),
5357
+ scope: z.enum(["builtin", "user", "project"]).optional().describe("Workflow scope to resolve from"),
5358
+ resumeFrom: z.string().optional().describe("Step id to resume a paused workflow from"),
5359
+ stepOutput: z.record(z.string(), z.string()).optional().describe("Step output supplied when resuming"),
5360
+ userChoice: z.string().optional().describe("Branch chosen by the user at a paused user step"),
5361
+ content: z.string().optional().describe("Optional extra instruction for the run")
5362
+ },
5363
+ handler: async (args) => {
5364
+ const check = requireSessionAndId(args);
5365
+ if (!check.ok) return fail(check.error);
5366
+ const { session, sessionId } = check;
5367
+ const payload = {
5368
+ ...typeof args["workflowId"] === "string" ? { workflowId: args["workflowId"] } : {},
5369
+ ...args["params"] && typeof args["params"] === "object" ? { params: args["params"] } : {},
5370
+ ...typeof args["subGroup"] === "string" ? { subGroup: args["subGroup"] } : {},
5371
+ ...typeof args["scope"] === "string" ? { scope: args["scope"] } : {},
5372
+ ...typeof args["resumeFrom"] === "string" ? { resumeFrom: args["resumeFrom"] } : {},
5373
+ ...args["stepOutput"] && typeof args["stepOutput"] === "object" ? { stepOutput: args["stepOutput"] } : {},
5374
+ ...typeof args["userChoice"] === "string" ? { userChoice: args["userChoice"] } : {},
5375
+ ...typeof args["content"] === "string" ? { content: args["content"] } : {}
5376
+ };
5377
+ if (payload.workflowId && !payload.resumeFrom) {
5378
+ const workflows = await deps.listWorkflows(session.workdir);
5379
+ const known = workflows.some((w) => w.id === payload.workflowId);
5380
+ if (!known) {
5381
+ return fail(`Workflow "${payload.workflowId}" not found for project ${session.workdir}`);
5382
+ }
5383
+ }
5384
+ if (session.isRunning) {
5385
+ let fullContent = payload.content ?? "";
5386
+ if (payload.workflowId) {
5387
+ const workflowInfo = `// Workflow: ${payload.workflowId}`;
5388
+ fullContent = fullContent ? `${workflowInfo}
5389
+
5390
+ ${fullContent}` : workflowInfo;
5391
+ }
5392
+ if (!fullContent) {
5393
+ return fail("Session is running \u2014 give a workflowId or content to queue for the next turn boundary.");
5394
+ }
5395
+ sessionManager.queueMessage(sessionId, "asap", fullContent, void 0, "workflow-launch");
5396
+ return ok({ queued: true, queueState: sessionManager.getQueueState(sessionId) });
5397
+ }
5398
+ if (session.phase === "blocked") {
5399
+ sessionManager.setPhase(sessionId, "build");
5400
+ }
5401
+ deps.launchWorkflow(sessionId, payload);
5402
+ return ok({ launched: true });
5403
+ }
5404
+ },
5405
+ {
5406
+ name: "openfox_resume_workflow",
5407
+ description: 'Resolve a paused workflow user step by picking one of its choices (e.g. "Work in current workspace"). The session must be paused at a user step \u2014 see openfox_session_status \u2192 workflow.pendingChoices (and the resumeHint there). Accepts the choice id or its label.',
5408
+ inputSchema: {
5409
+ sessionId: z.string().describe("Session paused at a workflow user step"),
5410
+ choice: z.string().describe("One of the pendingChoices surfaced by openfox_session_status (id or label)")
5411
+ },
5412
+ handler: async (args) => {
5413
+ const check = requireSession(args["sessionId"]);
5414
+ if (!check.ok) return fail(check.error);
5415
+ const sessionId = check.session.id;
5416
+ const choice = args["choice"];
5417
+ if (typeof choice !== "string" || choice.trim().length === 0) {
5418
+ return fail("choice is required \u2014 pick one of the pendingChoices surfaced by openfox_session_status");
5419
+ }
5420
+ const execution = sessionManager.getActiveWorkflowExecution(sessionId);
5421
+ if (!execution || execution.status !== "waiting" || !execution.currentStepId) {
5422
+ return fail(`Session ${sessionId} is not paused at a workflow user step`);
5423
+ }
5424
+ const pendingChoices = execution.pendingChoices ?? [];
5425
+ if (pendingChoices.length === 0) {
5426
+ return fail(`No choices available for paused step "${execution.currentStepName ?? execution.currentStepId}"`);
5427
+ }
5428
+ const match = pendingChoices.find((c) => c.id === choice || c.label === choice);
5429
+ if (!match) {
5430
+ return fail(`Invalid choice "${choice}". Available: ${pendingChoices.map((c) => c.id).join(", ")}`);
5431
+ }
5432
+ deps.launchWorkflow(sessionId, {
5433
+ workflowId: execution.workflowId,
5434
+ resumeFrom: execution.currentStepId,
5435
+ userChoice: match.id,
5436
+ stepOutput: execution.stepOutput
5437
+ });
5438
+ return ok({
5439
+ resumed: true,
5440
+ choice: match.id,
5441
+ step: execution.currentStepName ?? execution.currentStepId
5442
+ });
5443
+ }
5444
+ },
5445
+ {
5446
+ name: "openfox_stop_workflow",
5447
+ description: "Abort the active workflow run of a session (task/MCP-launched runs): aborts a live run or cancels a paused user-step execution, and errors when nothing is active. Does not stop plain chat turns.",
5448
+ inputSchema: {
5449
+ sessionId: z.string().describe("Session whose workflow run should be aborted")
5450
+ },
5451
+ handler: async (args) => {
5452
+ const check = requireSession(args["sessionId"]);
5453
+ if (!check.ok) return fail(check.error);
5454
+ const stopped = deps.stopWorkflow(check.session.id);
5455
+ if (!stopped) return fail("No active workflow run to stop");
5456
+ return ok({ stopped: true, aborted: stopped.aborted });
5457
+ }
5458
+ },
5459
+ {
5460
+ name: "openfox_answer",
5461
+ description: "Answer or skip a pending ask_user question of a session by callId. Without a callId, lists the session pending questions.",
5462
+ inputSchema: {
5463
+ sessionId: z.string().describe("Session the question belongs to"),
5464
+ callId: z.string().optional().describe("Pending question to answer (omit to list)"),
5465
+ answer: z.string().optional().describe("The answer to provide"),
5466
+ skip: z.boolean().optional().describe("Skip the question instead of answering it")
5467
+ },
5468
+ handler: async (args) => {
5469
+ const check = requireSession(args["sessionId"]);
5470
+ if (!check.ok) return fail(check.error);
5471
+ const session = check.session;
5472
+ const pending = deps.pendingQuestions(session.id);
5473
+ const resolved = resolvePendingCallId(session.id, args["callId"], pending, "question");
5474
+ if (resolved.kind === "list") return ok({ questions: pending });
5475
+ if (resolved.kind === "error") return fail(resolved.error);
5476
+ const callId = resolved.callId;
5477
+ const skip = args["skip"] === true;
5478
+ const answer = typeof args["answer"] === "string" ? args["answer"] : "";
5479
+ if (!skip && answer.length === 0) {
5480
+ return fail("answer is required unless skip is true");
5481
+ }
5482
+ const found = deps.answerQuestion(callId, skip ? "" : answer, skip || void 0);
5483
+ if (!found) {
5484
+ return fail(`Question ${callId} is no longer pending`);
5485
+ }
5486
+ return ok({ answered: true, callId, skipped: skip || void 0 });
5487
+ }
5488
+ },
5489
+ {
5490
+ name: "openfox_confirm",
5491
+ description: "Resolve a pending path confirmation of a session (approve/deny, optionally always-allow the paths). Without a callId, lists the session pending confirmations.",
5492
+ inputSchema: {
5493
+ sessionId: z.string().describe("Session the confirmation belongs to"),
5494
+ callId: z.string().optional().describe("Pending confirmation to resolve (omit to list)"),
5495
+ approved: z.boolean().optional().describe("Approve or deny the requested access"),
5496
+ alwaysAllow: z.boolean().optional().describe("Permanently allow the paths for this session")
5497
+ },
5498
+ handler: async (args) => {
5499
+ const check = requireSession(args["sessionId"]);
5500
+ if (!check.ok) return fail(check.error);
5501
+ const session = check.session;
5502
+ const pending = deps.pendingConfirmations(session.id);
5503
+ const resolved = resolvePendingCallId(session.id, args["callId"], pending, "confirmation");
5504
+ if (resolved.kind === "list") return ok({ confirmations: pending });
5505
+ if (resolved.kind === "error") return fail(resolved.error);
5506
+ const callId = resolved.callId;
5507
+ const approved = args["approved"];
5508
+ if (typeof approved !== "boolean") {
5509
+ return fail("approved (boolean) is required");
5510
+ }
5511
+ const alwaysAllow = args["alwaysAllow"];
5512
+ const found = deps.confirmPath(callId, approved, alwaysAllow);
5513
+ if (!found) {
5514
+ return fail(`Confirmation ${callId} is no longer pending`);
5515
+ }
5516
+ return ok({ confirmed: true, callId, approved });
5517
+ }
5518
+ }
5519
+ ];
5520
+ return tools;
5521
+ }
5522
+
5523
+ // src/server/mcp/server/endpoint.ts
5524
+ function buildOpenFoxMcpServer(deps) {
5525
+ const server = new McpServer({
5526
+ name: "openfox",
5527
+ version: "1.0.0"
5528
+ });
5529
+ for (const tool of createOpenFoxMcpTools(deps)) {
5530
+ const config3 = { description: tool.description };
5531
+ if (Object.keys(tool.inputSchema).length > 0) {
5532
+ config3["inputSchema"] = tool.inputSchema;
5533
+ }
5534
+ server.registerTool(
5535
+ tool.name,
5536
+ config3,
5537
+ async (args) => tool.handler(args ?? {})
5538
+ );
5539
+ }
5540
+ return server;
5541
+ }
5542
+ function createOpenFoxMcpRouter(options) {
4754
5543
  const router = Router4();
5544
+ router.all("/", async (req, res) => {
5545
+ try {
5546
+ if (options.isAuthRequired() && !await options.isAuthorized(req)) {
5547
+ res.status(401).json({ error: "Unauthorized" });
5548
+ return;
5549
+ }
5550
+ const deps = options.resolveDeps();
5551
+ if (!deps) {
5552
+ res.status(503).json({ error: "MCP server is not ready" });
5553
+ return;
5554
+ }
5555
+ const server = buildOpenFoxMcpServer(deps);
5556
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
5557
+ await server.connect(transport);
5558
+ await transport.handleRequest(req, res, req.body);
5559
+ } catch (error) {
5560
+ logger.error("MCP endpoint request failed", {
5561
+ error: error instanceof Error ? error.message : String(error)
5562
+ });
5563
+ if (!res.headersSent) {
5564
+ res.status(500).json({ error: "MCP request failed" });
5565
+ }
5566
+ }
5567
+ });
5568
+ return router;
5569
+ }
5570
+ function extractSessionToken(req) {
5571
+ const header = req.headers["x-session-token"];
5572
+ if (typeof header === "string" && header.length > 0) {
5573
+ return header;
5574
+ }
5575
+ const authorization = req.headers["authorization"];
5576
+ if (typeof authorization === "string" && authorization.startsWith("Bearer ")) {
5577
+ const token = authorization.slice("Bearer ".length).trim();
5578
+ return token.length > 0 ? token : null;
5579
+ }
5580
+ return null;
5581
+ }
5582
+
5583
+ // src/server/routes/dev-server.ts
5584
+ import { Router as Router5 } from "express";
5585
+ function createDevServerRoutes() {
5586
+ const router = Router5();
4755
5587
  router.get("/", async (req, res) => {
4756
5588
  const workdir = req.query["workdir"];
4757
5589
  if (!workdir) return res.status(400).json({ error: "workdir required" });
@@ -4849,7 +5681,7 @@ function createDevServerRoutes() {
4849
5681
  }
4850
5682
 
4851
5683
  // src/server/routes/workspace-config.ts
4852
- import { Router as Router5 } from "express";
5684
+ import { Router as Router6 } from "express";
4853
5685
  import { stat as stat2, mkdir as mkdir3, readdir, access as access3 } from "fs/promises";
4854
5686
  import { constants as constants3 } from "fs";
4855
5687
  import { resolve as resolve4, isAbsolute as isAbsolute3, join as join5, win32 } from "path";
@@ -4946,7 +5778,7 @@ async function findOrphanedWorkspaces(dir) {
4946
5778
  return results;
4947
5779
  }
4948
5780
  function createWorkspaceConfigRoutes(sessionManager) {
4949
- const router = Router5();
5781
+ const router = Router6();
4950
5782
  router.get("/config", async (req, res) => {
4951
5783
  const workdir = req.query["workdir"];
4952
5784
  if (!workdir) return res.status(400).json({ error: "workdir required" });
@@ -5080,9 +5912,9 @@ function createWorkspaceConfigRoutes(sessionManager) {
5080
5912
  }
5081
5913
 
5082
5914
  // src/server/routes/terminals.ts
5083
- import { Router as Router6 } from "express";
5915
+ import { Router as Router7 } from "express";
5084
5916
  function createTerminalRoutes() {
5085
- const router = Router6();
5917
+ const router = Router7();
5086
5918
  router.get("/", (req, res) => {
5087
5919
  const projectId = req.query["projectId"];
5088
5920
  const sessions = projectId ? terminalManager.getByProject(projectId) : [];
@@ -5121,11 +5953,11 @@ function createTerminalRoutes() {
5121
5953
  }
5122
5954
 
5123
5955
  // src/server/routes/directories.ts
5124
- import { Router as Router7 } from "express";
5956
+ import { Router as Router8 } from "express";
5125
5957
  import { readdir as readdir2 } from "fs/promises";
5126
5958
  import { resolve as resolve5, join as join6, dirname as dirname4, basename as basename2 } from "path";
5127
5959
  function createDirectoryRoutes() {
5128
- const router = Router7();
5960
+ const router = Router8();
5129
5961
  const DEFAULT_BASE_PATH = process.cwd();
5130
5962
  router.get("/", async (req, res) => {
5131
5963
  const path = req.query["path"] || DEFAULT_BASE_PATH;
@@ -5160,7 +5992,7 @@ function createDirectoryRoutes() {
5160
5992
  }
5161
5993
 
5162
5994
  // src/server/routes/file-search.ts
5163
- import { Router as Router8 } from "express";
5995
+ import { Router as Router9 } from "express";
5164
5996
  import fg from "fast-glob";
5165
5997
  var IGNORED_PATTERNS = [
5166
5998
  "**/node_modules/**",
@@ -5173,7 +6005,7 @@ var IGNORED_PATTERNS = [
5173
6005
  "**/.openfox/**"
5174
6006
  ];
5175
6007
  function createFileSearchRoutes() {
5176
- const router = Router8();
6008
+ const router = Router9();
5177
6009
  router.get("/", async (req, res) => {
5178
6010
  const query = req.query["q"] || "";
5179
6011
  const workdir = req.query["workdir"] || process.cwd();
@@ -5265,7 +6097,7 @@ function fuzzyScore(filePath, query) {
5265
6097
  }
5266
6098
 
5267
6099
  // src/server/routes/auto-update.ts
5268
- import { Router as Router9 } from "express";
6100
+ import { Router as Router10 } from "express";
5269
6101
  import { spawn as spawn2 } from "child_process";
5270
6102
  var updateInProgress = false;
5271
6103
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -5362,7 +6194,7 @@ async function getLatestDevelopVersion() {
5362
6194
  });
5363
6195
  }
5364
6196
  function createAutoUpdateRoutes(options = {}) {
5365
- const router = Router9();
6197
+ const router = Router10();
5366
6198
  router.get("/check", async (req, res) => {
5367
6199
  const isService = isRunningAsService();
5368
6200
  const isDev = isDevMode();
@@ -5479,9 +6311,9 @@ function createAutoUpdateRoutes(options = {}) {
5479
6311
  }
5480
6312
 
5481
6313
  // src/server/routes/provider-auth.ts
5482
- import { Router as Router10 } from "express";
6314
+ import { Router as Router11 } from "express";
5483
6315
  function createProviderAuthRoutes(config3, providerManager, registry) {
5484
- const router = Router10();
6316
+ const router = Router11();
5485
6317
  function resolveAuthContext(providerId, res) {
5486
6318
  const provider = providerManager.getProviders().find((item) => item.id === providerId);
5487
6319
  if (!provider) {
@@ -5710,7 +6542,7 @@ async function loadProviderPlugins(options) {
5710
6542
  }
5711
6543
 
5712
6544
  // src/server/routes/plugins.ts
5713
- import { Router as Router11 } from "express";
6545
+ import { Router as Router12 } from "express";
5714
6546
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL2 } from "url";
5715
6547
  import { dirname as dirname6, resolve as resolve7, join as join8 } from "path";
5716
6548
  import { existsSync } from "fs";
@@ -5727,7 +6559,7 @@ async function openFolderRoute(dir, res) {
5727
6559
  }
5728
6560
  }
5729
6561
  function createPluginRoutes(options) {
5730
- const router = Router11();
6562
+ const router = Router12();
5731
6563
  const { config: config3, providerAdapters, pluginDiagnostics, logger: logger2 } = options;
5732
6564
  let registryCache = null;
5733
6565
  router.get("/registry", async (_req, res) => {
@@ -5968,6 +6800,10 @@ function detectWsl() {
5968
6800
 
5969
6801
  // src/server/index.ts
5970
6802
  var __dirname3 = dirname7(fileURLToPath6(import.meta.url));
6803
+ async function announceContextDrift(sessionManager, sessionIds) {
6804
+ const { injectContextDriftRemindersForSessions } = await import("./dynamic-context-Y555VDLA.js");
6805
+ await injectContextDriftRemindersForSessions(sessionManager, sessionIds);
6806
+ }
5971
6807
  async function createServerHandle(config3) {
5972
6808
  setRuntimeConfig(config3);
5973
6809
  setLogLevel(config3.logging?.level ?? void 0, config3.mode);
@@ -6072,7 +6908,7 @@ async function createServerHandle(config3) {
6072
6908
  toolRegistry = createToolRegistry();
6073
6909
  logger.info("MCP tools registered", { count: mcpTools.length });
6074
6910
  }
6075
- const { signalMcpReady } = await import("./server-MUJ7EHEI.js");
6911
+ const { signalMcpReady } = await import("./server-5TY22HDN.js");
6076
6912
  signalMcpReady();
6077
6913
  });
6078
6914
  const app = express();
@@ -6095,6 +6931,25 @@ async function createServerHandle(config3) {
6095
6931
  };
6096
6932
  app.use("/api", authMiddleware);
6097
6933
  app.use(express.json({ limit: "75mb" }));
6934
+ let mcpToolDeps = null;
6935
+ const mcpAuth = {
6936
+ isAuthRequired: () => {
6937
+ const cfg = getAuthConfig();
6938
+ return cfg?.strategy === "network" && cfg.encryptedPassword != null;
6939
+ },
6940
+ isAuthorized: async (req) => {
6941
+ const token = extractSessionToken(req);
6942
+ return token ? isValidToken(token) : false;
6943
+ }
6944
+ };
6945
+ app.use(
6946
+ "/mcp",
6947
+ createOpenFoxMcpRouter({
6948
+ resolveDeps: () => mcpToolDeps,
6949
+ isAuthRequired: mcpAuth.isAuthRequired,
6950
+ isAuthorized: mcpAuth.isAuthorized
6951
+ })
6952
+ );
6098
6953
  app.get("/api/health", (_req, res) => {
6099
6954
  res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
6100
6955
  });
@@ -6155,7 +7010,7 @@ async function createServerHandle(config3) {
6155
7010
  res.json({ tools });
6156
7011
  });
6157
7012
  app.get("/api/projects", async (_req, res) => {
6158
- const { listProjects: listProjects2 } = await import("./projects-TPSLCJP2.js");
7013
+ const { listProjects: listProjects2 } = await import("./projects-5U37RUTL.js");
6159
7014
  const projects = listProjects2();
6160
7015
  res.json({ projects });
6161
7016
  });
@@ -6164,7 +7019,7 @@ async function createServerHandle(config3) {
6164
7019
  if (!name || !workdir) {
6165
7020
  return res.status(400).json({ error: "name and workdir are required" });
6166
7021
  }
6167
- const { createDirectoryWithGit } = await import("./project-creator-JF3YVQAH.js");
7022
+ const { createDirectoryWithGit } = await import("./project-creator-6AGQTNXB.js");
6168
7023
  try {
6169
7024
  const project = await createDirectoryWithGit(name, workdir);
6170
7025
  res.status(201).json({ project });
@@ -6209,7 +7064,7 @@ async function createServerHandle(config3) {
6209
7064
  }
6210
7065
  });
6211
7066
  app.get("/api/projects/:id", async (req, res) => {
6212
- const { getProject: getProject2 } = await import("./projects-TPSLCJP2.js");
7067
+ const { getProject: getProject2 } = await import("./projects-5U37RUTL.js");
6213
7068
  const project = getProject2(req.params.id);
6214
7069
  if (!project) {
6215
7070
  return res.status(404).json({ error: "Project not found" });
@@ -6217,7 +7072,7 @@ async function createServerHandle(config3) {
6217
7072
  res.json({ project });
6218
7073
  });
6219
7074
  app.get("/api/projects/:id/open-folder", async (req, res) => {
6220
- const { getProject: getProject2 } = await import("./projects-TPSLCJP2.js");
7075
+ const { getProject: getProject2 } = await import("./projects-5U37RUTL.js");
6221
7076
  const project = getProject2(req.params.id);
6222
7077
  if (!project) {
6223
7078
  return res.status(404).json({ error: "Project not found" });
@@ -6231,7 +7086,7 @@ async function createServerHandle(config3) {
6231
7086
  }
6232
7087
  });
6233
7088
  app.put("/api/projects/:id", async (req, res) => {
6234
- const { updateProject: updateProject2 } = await import("./projects-TPSLCJP2.js");
7089
+ const { updateProject: updateProject2 } = await import("./projects-5U37RUTL.js");
6235
7090
  const { name, customInstructions, dangerLevel, defaultAgent } = req.body;
6236
7091
  const updates = {};
6237
7092
  if (name !== void 0) updates.name = name;
@@ -6245,16 +7100,16 @@ async function createServerHandle(config3) {
6245
7100
  res.json({ project: updated });
6246
7101
  });
6247
7102
  app.delete("/api/projects/:id", async (req, res) => {
6248
- const { getProject: getProject2, deleteProject } = await import("./projects-TPSLCJP2.js");
7103
+ const { getProject: getProject2, deleteProject: deleteProject2 } = await import("./projects-5U37RUTL.js");
6249
7104
  const project = getProject2(req.params.id);
6250
7105
  if (!project) {
6251
7106
  return res.status(404).json({ error: "Project not found" });
6252
7107
  }
6253
- deleteProject(req.params.id);
7108
+ deleteProject2(req.params.id);
6254
7109
  res.json({ success: true });
6255
7110
  });
6256
7111
  app.put("/api/projects/:id/star", async (req, res) => {
6257
- const { toggleStar } = await import("./projects-TPSLCJP2.js");
7112
+ const { toggleStar } = await import("./projects-5U37RUTL.js");
6258
7113
  const { isStarred } = req.body;
6259
7114
  if (typeof isStarred !== "boolean") {
6260
7115
  return res.status(400).json({ error: "isStarred is required and must be a boolean" });
@@ -6268,9 +7123,9 @@ async function createServerHandle(config3) {
6268
7123
  const sessionFavoriteRouter = express.Router();
6269
7124
  registerSessionFavoriteRoute(sessionFavoriteRouter, sessionManager);
6270
7125
  app.use("/api", sessionFavoriteRouter);
6271
- const { createTasksService } = await import("./service-INQWTYY2.js");
6272
- const { registerTaskRoutes } = await import("./tasks-SE3DOPVP.js");
6273
- const { setTasksService } = await import("./tools-6XUSIJN2.js");
7126
+ const { createTasksService } = await import("./service-VPXGREXF.js");
7127
+ const { registerTaskRoutes } = await import("./tasks-MNPXK2S5.js");
7128
+ const { setTasksService } = await import("./tools-6BDT5YMT.js");
6274
7129
  const tasksService = createTasksService({
6275
7130
  sessionManager,
6276
7131
  config: config3,
@@ -6283,26 +7138,26 @@ async function createServerHandle(config3) {
6283
7138
  registerTaskRoutes(tasksRouter, tasksService);
6284
7139
  app.use("/api", tasksRouter);
6285
7140
  app.get("/api/projects/:id/branches", async (req, res) => {
6286
- const { getProject: getProject2 } = await import("./projects-TPSLCJP2.js");
7141
+ const { getProject: getProject2 } = await import("./projects-5U37RUTL.js");
6287
7142
  const project = getProject2(req.params.id);
6288
7143
  if (!project) return res.status(404).json({ error: "Project not found" });
6289
- const { listBranches } = await import("./workspace-6CUE26YA.js");
7144
+ const { listBranches } = await import("./workspace-LM646OHC.js");
6290
7145
  const branches = await listBranches(project.workdir);
6291
7146
  res.json({ branches });
6292
7147
  });
6293
7148
  app.post("/api/projects/:id/checkout", async (req, res) => {
6294
- const { getProject: getProject2 } = await import("./projects-TPSLCJP2.js");
7149
+ const { getProject: getProject2 } = await import("./projects-5U37RUTL.js");
6295
7150
  const project = getProject2(req.params.id);
6296
7151
  if (!project) return res.status(404).json({ error: "Project not found" });
6297
7152
  const { branch } = req.body;
6298
7153
  if (!branch || typeof branch !== "string") return res.status(400).json({ error: "branch is required" });
6299
- const { checkoutBranch, isGitRepository } = await import("./workspace-6CUE26YA.js");
7154
+ const { checkoutBranch, isGitRepository } = await import("./workspace-LM646OHC.js");
6300
7155
  if (!await isGitRepository(project.workdir)) {
6301
7156
  return res.status(400).json({ error: "Project is not a git repository" });
6302
7157
  }
6303
7158
  try {
6304
7159
  await checkoutBranch(project.workdir, branch);
6305
- const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-D7VRCHQY.js");
7160
+ const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-NMPX5ICI.js");
6306
7161
  const allSessions = sessionManager.listSessions();
6307
7162
  for (const s of allSessions) {
6308
7163
  if (sessionManager.getEffectiveWorkdir(s.id) === project.workdir) {
@@ -6316,12 +7171,12 @@ async function createServerHandle(config3) {
6316
7171
  }
6317
7172
  });
6318
7173
  app.post("/api/projects/:id/checkout-new", async (req, res) => {
6319
- const { getProject: getProject2 } = await import("./projects-TPSLCJP2.js");
7174
+ const { getProject: getProject2 } = await import("./projects-5U37RUTL.js");
6320
7175
  const project = getProject2(req.params.id);
6321
7176
  if (!project) return res.status(404).json({ error: "Project not found" });
6322
7177
  const { name, sourceBranch } = req.body;
6323
7178
  if (!name || typeof name !== "string") return res.status(400).json({ error: "name is required" });
6324
- const { createBranch, resolveAndValidateSourceBranch: resolveAndValidateSourceBranch2, validateRef: validateRef2, getDefaultBranch, isGitRepository } = await import("./workspace-6CUE26YA.js");
7179
+ const { createBranch, resolveAndValidateSourceBranch: resolveAndValidateSourceBranch2, validateRef: validateRef2, getDefaultBranch, isGitRepository } = await import("./workspace-LM646OHC.js");
6325
7180
  if (!await isGitRepository(project.workdir)) {
6326
7181
  return res.status(400).json({ error: "Project is not a git repository" });
6327
7182
  }
@@ -6338,7 +7193,7 @@ async function createServerHandle(config3) {
6338
7193
  );
6339
7194
  }
6340
7195
  await createBranch(project.workdir, name, sb);
6341
- const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-D7VRCHQY.js");
7196
+ const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-NMPX5ICI.js");
6342
7197
  const allSessions = sessionManager.listSessions();
6343
7198
  for (const s of allSessions) {
6344
7199
  if (sessionManager.getEffectiveWorkdir(s.id) === project.workdir) {
@@ -6355,7 +7210,7 @@ async function createServerHandle(config3) {
6355
7210
  const session = sessionManager.getSession(req.params.id);
6356
7211
  if (!session) return res.status(404).json({ error: "Session not found" });
6357
7212
  const effectiveWorkdir = sessionManager.getEffectiveWorkdir(req.params.id);
6358
- const { listBranches, getDefaultBranch } = await import("./workspace-6CUE26YA.js");
7213
+ const { listBranches, getDefaultBranch } = await import("./workspace-LM646OHC.js");
6359
7214
  const [branches, defaultBranch] = await Promise.all([
6360
7215
  listBranches(effectiveWorkdir),
6361
7216
  getDefaultBranch(session.workdir)
@@ -6369,8 +7224,8 @@ async function createServerHandle(config3) {
6369
7224
  const { branch } = req.body;
6370
7225
  if (!branch || typeof branch !== "string") return res.status(400).json({ error: "branch is required" });
6371
7226
  const effectiveWorkdir = sessionManager.getEffectiveWorkdir(req.params.id);
6372
- const { checkoutBranch, validateRef: validateRef2 } = await import("./workspace-6CUE26YA.js");
6373
- const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-D7VRCHQY.js");
7227
+ const { checkoutBranch, validateRef: validateRef2 } = await import("./workspace-LM646OHC.js");
7228
+ const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-NMPX5ICI.js");
6374
7229
  try {
6375
7230
  await validateRef2(effectiveWorkdir, branch);
6376
7231
  await checkoutBranch(effectiveWorkdir, branch);
@@ -6412,16 +7267,16 @@ All file and git operations should use this branch.
6412
7267
  const { name, sourceBranch } = req.body;
6413
7268
  if (!name || typeof name !== "string") return res.status(400).json({ error: "name is required" });
6414
7269
  const effectiveWorkdir = sessionManager.getEffectiveWorkdir(req.params.id);
6415
- const { validateRef: validateRef2 } = await import("./workspace-6CUE26YA.js");
6416
- const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-D7VRCHQY.js");
7270
+ const { validateRef: validateRef2 } = await import("./workspace-LM646OHC.js");
7271
+ const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-NMPX5ICI.js");
6417
7272
  try {
6418
7273
  await validateRef2(effectiveWorkdir, name);
6419
7274
  if (sourceBranch) {
6420
- const { resolveAndValidateSourceBranch: resolveAndValidateSourceBranch2, createBranch } = await import("./workspace-6CUE26YA.js");
7275
+ const { resolveAndValidateSourceBranch: resolveAndValidateSourceBranch2, createBranch } = await import("./workspace-LM646OHC.js");
6421
7276
  const sb = await resolveAndValidateSourceBranch2(effectiveWorkdir, sourceBranch, session.workdir);
6422
7277
  await createBranch(effectiveWorkdir, name, sb);
6423
7278
  } else {
6424
- const { createBranch, getDefaultBranch, resolveAndValidateSourceBranch: resolveAndValidateSourceBranch2 } = await import("./workspace-6CUE26YA.js");
7279
+ const { createBranch, getDefaultBranch, resolveAndValidateSourceBranch: resolveAndValidateSourceBranch2 } = await import("./workspace-LM646OHC.js");
6425
7280
  const defaultBranch = await getDefaultBranch(session.workdir);
6426
7281
  const sb = await resolveAndValidateSourceBranch2(effectiveWorkdir, defaultBranch, session.workdir);
6427
7282
  await createBranch(effectiveWorkdir, name, sb);
@@ -6459,17 +7314,17 @@ All file and git operations should use this branch.
6459
7314
  }
6460
7315
  });
6461
7316
  app.get("/api/projects/:id/workspaces", async (req, res) => {
6462
- const { getProject: getProject2 } = await import("./projects-TPSLCJP2.js");
7317
+ const { getProject: getProject2 } = await import("./projects-5U37RUTL.js");
6463
7318
  const project = getProject2(req.params.id);
6464
7319
  if (!project) return res.status(404).json({ error: "Project not found" });
6465
- const { listWorkspaces } = await import("./workspace-6CUE26YA.js");
7320
+ const { listWorkspaces } = await import("./workspace-LM646OHC.js");
6466
7321
  const all = await listWorkspaces(project.name, project.workdir);
6467
7322
  const workspacesList = all.filter((ws) => ws.path !== project.workdir);
6468
7323
  res.json({ workspaces: workspacesList });
6469
7324
  });
6470
7325
  app.get("/api/sessions", async (req, res) => {
6471
- const { getRecentUserPromptsForSession } = await import("./events-GMHRX4LF.js");
6472
- const { getPendingConfirmationsBySession } = await import("./path-security-UDZ7UVWK.js");
7326
+ const { getRecentUserPromptsForSession } = await import("./events-ZJPRGVL2.js");
7327
+ const { getPendingConfirmationsBySession: getPendingConfirmationsBySession2 } = await import("./path-security-CBACXAMS.js");
6473
7328
  const projectId = req.query["projectId"];
6474
7329
  const rawLimit = req.query["limit"];
6475
7330
  const offset = parseInt(req.query["offset"]) || 0;
@@ -6492,7 +7347,7 @@ All file and git operations should use this branch.
6492
7347
  ...session,
6493
7348
  recentUserPrompts: getRecentUserPromptsForSession(session.id, 10)
6494
7349
  }));
6495
- const allPending = getPendingConfirmationsBySession();
7350
+ const allPending = getPendingConfirmationsBySession2();
6496
7351
  const pendingConfirmationsBySession = {};
6497
7352
  for (const s of sessions) {
6498
7353
  const confs = allPending[s.id];
@@ -6524,7 +7379,7 @@ All file and git operations should use this branch.
6524
7379
  disabledServers = mcpManager.getAllServers().filter((s) => s.config.disabled).map((s) => s.name);
6525
7380
  }
6526
7381
  if (disabledServers.length > 0) {
6527
- const { setSessionDisabledServers: setSessionDisabledServers2 } = await import("./session-overrides-4WAD3X5Q.js");
7382
+ const { setSessionDisabledServers: setSessionDisabledServers2 } = await import("./session-overrides-OECQYRDK.js");
6528
7383
  setSessionDisabledServers2(session.id, disabledServers);
6529
7384
  }
6530
7385
  } catch {
@@ -6561,7 +7416,7 @@ All file and git operations should use this branch.
6561
7416
  if (!target || typeof target !== "string") return res.status(400).json({ error: "target is required" });
6562
7417
  const project = sessionManager.getProject(session.projectId);
6563
7418
  if (!project) return res.status(404).json({ error: "Project not found" });
6564
- const { isGitRepository } = await import("./workspace-6CUE26YA.js");
7419
+ const { isGitRepository } = await import("./workspace-LM646OHC.js");
6565
7420
  if (!await isGitRepository(project.workdir)) {
6566
7421
  return res.status(400).json({ error: "Project is not a git repository" });
6567
7422
  }
@@ -6579,7 +7434,7 @@ All file and git operations should use this branch.
6579
7434
  if (!target || typeof target !== "string") return res.status(400).json({ error: "target is required" });
6580
7435
  const project = sessionManager.getProject(session.projectId);
6581
7436
  if (!project) return res.status(404).json({ error: "Project not found" });
6582
- const { isGitRepository } = await import("./workspace-6CUE26YA.js");
7437
+ const { isGitRepository } = await import("./workspace-LM646OHC.js");
6583
7438
  if (!await isGitRepository(project.workdir)) {
6584
7439
  return res.status(400).json({ error: "Project is not a git repository" });
6585
7440
  }
@@ -6597,22 +7452,22 @@ All file and git operations should use this branch.
6597
7452
  }
6598
7453
  });
6599
7454
  app.get("/api/sessions/:id", async (req, res) => {
6600
- const { getEventStore: getEventStore2, combineEventsWithSnapshot } = await import("./events-GMHRX4LF.js");
6601
- const { buildMessagesFromStoredEvents, foldPendingConfirmations } = await import("./folding-7K2GRPF4.js");
6602
- const { getPendingQuestionsForSession } = await import("./tools-6XUSIJN2.js");
6603
- const { getMaxVisibleItems } = await import("./settings-VOWWO4VG.js");
7455
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEventsWithSnapshot2 } = await import("./events-ZJPRGVL2.js");
7456
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2, foldPendingConfirmations } = await import("./folding-QRZOHY2I.js");
7457
+ const { getPendingQuestionsForSession: getPendingQuestionsForSession2 } = await import("./tools-6BDT5YMT.js");
7458
+ const { getMaxVisibleItems } = await import("./settings-MDCRNQBY.js");
6604
7459
  const session = sessionManager.getSession(req.params.id);
6605
7460
  if (!session) {
6606
7461
  return res.status(404).json({ error: "Session not found" });
6607
7462
  }
6608
7463
  const eventStore = getEventStore2();
6609
7464
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(req.params.id);
6610
- const events = combineEventsWithSnapshot(req.params.id, snapshot, eventsSinceSnapshot);
7465
+ const events = combineEventsWithSnapshot2(req.params.id, snapshot, eventsSinceSnapshot);
6611
7466
  const maxVisibleItems = req.query["full"] === "true" ? void 0 : getMaxVisibleItems() || void 0;
6612
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems);
7467
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems);
6613
7468
  const contextState = sessionManager.getContextState(req.params.id);
6614
7469
  const queueState = sessionManager.getQueueState(req.params.id);
6615
- const pendingQuestions = getPendingQuestionsForSession(req.params.id);
7470
+ const pendingQuestions = getPendingQuestionsForSession2(req.params.id);
6616
7471
  const pendingConfirmations = foldPendingConfirmations(events);
6617
7472
  const activeWorkflowExecution = sessionManager.getDisplayWorkflowExecution(req.params.id);
6618
7473
  res.json({
@@ -6627,10 +7482,10 @@ All file and git operations should use this branch.
6627
7482
  });
6628
7483
  });
6629
7484
  app.get("/api/sessions/:id/status", async (req, res) => {
6630
- const { projectSessionStatus } = await import("./session-status-6Q5B5PEN.js");
6631
- const { getPendingQuestionsForSession } = await import("./tools-6XUSIJN2.js");
6632
- const { getEventStore: getEventStore2, combineEventsWithSnapshot } = await import("./events-GMHRX4LF.js");
6633
- const { foldPendingConfirmations } = await import("./folding-7K2GRPF4.js");
7485
+ const { projectSessionStatus: projectSessionStatus2 } = await import("./session-status-UFO5IFI7.js");
7486
+ const { getPendingQuestionsForSession: getPendingQuestionsForSession2 } = await import("./tools-6BDT5YMT.js");
7487
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEventsWithSnapshot2 } = await import("./events-ZJPRGVL2.js");
7488
+ const { foldPendingConfirmations } = await import("./folding-QRZOHY2I.js");
6634
7489
  const sessionId = req.params["id"];
6635
7490
  if (!sessionId) {
6636
7491
  return res.status(400).json({ error: "Session id is required" });
@@ -6641,12 +7496,12 @@ All file and git operations should use this branch.
6641
7496
  }
6642
7497
  const activeWorkflowExecution = sessionManager.getActiveWorkflowExecution(sessionId);
6643
7498
  const activeWorkflowStepName = activeWorkflowExecution?.currentStepName ?? null;
6644
- const pendingQuestions = getPendingQuestionsForSession(sessionId);
7499
+ const pendingQuestions = getPendingQuestionsForSession2(sessionId);
6645
7500
  const eventStore = getEventStore2();
6646
7501
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6647
- const events = combineEventsWithSnapshot(sessionId, snapshot, eventsSinceSnapshot);
7502
+ const events = combineEventsWithSnapshot2(sessionId, snapshot, eventsSinceSnapshot);
6648
7503
  const pendingConfirmations = foldPendingConfirmations(events);
6649
- const status = projectSessionStatus({
7504
+ const status = projectSessionStatus2({
6650
7505
  session,
6651
7506
  pendingQuestionsCount: pendingQuestions.length,
6652
7507
  pendingConfirmationsCount: pendingConfirmations.length,
@@ -6660,8 +7515,8 @@ All file and git operations should use this branch.
6660
7515
  if (!session) {
6661
7516
  return res.status(404).json({ error: "Session not found" });
6662
7517
  }
6663
- const { stopSessionExecution } = await import("./chat-handler-TH6JBO6G.js");
6664
- const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-6XUSIJN2.js");
7518
+ const { stopSessionExecution } = await import("./chat-handler-RAIMQU7F.js");
7519
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-6BDT5YMT.js");
6665
7520
  sessionManager.clearMessageQueue(sessionId);
6666
7521
  stopSessionExecution(sessionId, sessionManager);
6667
7522
  abortSession(sessionId);
@@ -6690,9 +7545,9 @@ All file and git operations should use this branch.
6690
7545
  res.json({ success: true });
6691
7546
  });
6692
7547
  app.post("/api/sessions/:id/provider", async (req, res) => {
6693
- const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-GMHRX4LF.js");
6694
- const { buildMessagesFromStoredEvents } = await import("./folding-7K2GRPF4.js");
6695
- const { getMaxVisibleItems } = await import("./settings-VOWWO4VG.js");
7548
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-ZJPRGVL2.js");
7549
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-QRZOHY2I.js");
7550
+ const { getMaxVisibleItems } = await import("./settings-MDCRNQBY.js");
6696
7551
  const sessionId = req.params.id;
6697
7552
  const session = sessionManager.getSession(sessionId);
6698
7553
  if (!session) {
@@ -6709,19 +7564,20 @@ All file and git operations should use this branch.
6709
7564
  const resolvedModel = model ?? provider?.models?.[0]?.id ?? "auto";
6710
7565
  sessionManager.setSessionProvider(sessionId, providerId, resolvedModel, true, reasoningEffort);
6711
7566
  sessionManager.setSessionProviderActive(sessionId, true);
7567
+ await announceContextDrift(sessionManager, [sessionId]);
6712
7568
  const contextState = sessionManager.getContextState(sessionId);
6713
7569
  const eventStore = getEventStore2();
6714
7570
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6715
7571
  const events = combineEv(sessionId, snapshot, eventsSinceSnapshot);
6716
7572
  const maxVisibleItems = getMaxVisibleItems();
6717
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
7573
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems || void 0);
6718
7574
  const updatedSession = sessionManager.getSession(sessionId);
6719
7575
  res.json({ session: toClientSession(updatedSession), messages, hiddenCount, contextState });
6720
7576
  });
6721
7577
  app.delete("/api/sessions/:id/provider", async (req, res) => {
6722
- const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-GMHRX4LF.js");
6723
- const { buildMessagesFromStoredEvents } = await import("./folding-7K2GRPF4.js");
6724
- const { getMaxVisibleItems } = await import("./settings-VOWWO4VG.js");
7578
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-ZJPRGVL2.js");
7579
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-QRZOHY2I.js");
7580
+ const { getMaxVisibleItems } = await import("./settings-MDCRNQBY.js");
6725
7581
  const sessionId = req.params.id;
6726
7582
  const session = sessionManager.getSession(sessionId);
6727
7583
  if (!session) {
@@ -6729,11 +7585,12 @@ All file and git operations should use this branch.
6729
7585
  }
6730
7586
  sessionManager.setSessionProvider(sessionId, null, null, false, null);
6731
7587
  sessionManager.setSessionProviderActive(sessionId, true);
7588
+ await announceContextDrift(sessionManager, [sessionId]);
6732
7589
  const eventStore = getEventStore2();
6733
7590
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6734
7591
  const events = combineEv(sessionId, snapshot, eventsSinceSnapshot);
6735
7592
  const maxVisibleItems = getMaxVisibleItems();
6736
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
7593
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems || void 0);
6737
7594
  const updatedSession = sessionManager.getSession(sessionId);
6738
7595
  res.json({ session: toClientSession(updatedSession), messages, hiddenCount });
6739
7596
  });
@@ -6850,9 +7707,9 @@ All file and git operations should use this branch.
6850
7707
  res.json({ success: true });
6851
7708
  });
6852
7709
  app.put("/api/sessions/:id/mode", async (req, res) => {
6853
- const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-GMHRX4LF.js");
6854
- const { buildMessagesFromStoredEvents } = await import("./folding-7K2GRPF4.js");
6855
- const { getMaxVisibleItems } = await import("./settings-VOWWO4VG.js");
7710
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-ZJPRGVL2.js");
7711
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-QRZOHY2I.js");
7712
+ const { getMaxVisibleItems } = await import("./settings-MDCRNQBY.js");
6856
7713
  const sessionId = req.params.id;
6857
7714
  const session = sessionManager.getSession(sessionId);
6858
7715
  if (!session) {
@@ -6872,7 +7729,7 @@ All file and git operations should use this branch.
6872
7729
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6873
7730
  const events = combineEv(sessionId, snapshot, eventsSinceSnapshot);
6874
7731
  const maxVisibleItems = getMaxVisibleItems();
6875
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
7732
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems || void 0);
6876
7733
  const updatedSession = sessionManager.getSession(sessionId);
6877
7734
  res.json({ session: toClientSession(updatedSession), messages, hiddenCount });
6878
7735
  });
@@ -6910,7 +7767,15 @@ All file and git operations should use this branch.
6910
7767
  return res.status(400).json({ error: "disabledServers must be an array of strings" });
6911
7768
  }
6912
7769
  setSessionDisabledServers(sessionId, disabledServers);
6913
- sessionManager.setDynamicContextChanged(sessionId, true);
7770
+ const messages = session.messages ?? [];
7771
+ if (messages.length === 0) {
7772
+ const { applyDynamicContext } = await import("./dynamic-context-Y555VDLA.js");
7773
+ const modelName = session.providerModel ?? providerManager.getCurrentModel();
7774
+ await applyDynamicContext(sessionManager, sessionId, modelName);
7775
+ } else {
7776
+ sessionManager.setDynamicContextChanged(sessionId, true);
7777
+ }
7778
+ await announceContextDrift(sessionManager, [sessionId]);
6914
7779
  const state = sessionManager.getContextState(sessionId);
6915
7780
  wssExports.broadcastForSession(sessionId, createContextStateMessage(state));
6916
7781
  res.json({ disabledServers: getSessionDisabledServers(sessionId) });
@@ -6938,7 +7803,7 @@ All file and git operations should use this branch.
6938
7803
  if (alwaysAllow !== void 0 && typeof alwaysAllow !== "boolean") {
6939
7804
  return res.status(400).json({ error: "alwaysAllow must be a boolean if provided" });
6940
7805
  }
6941
- const { providePathConfirmation, getConfirmationSessionId } = await import("./tools-6XUSIJN2.js");
7806
+ const { providePathConfirmation: providePathConfirmation2, getConfirmationSessionId } = await import("./tools-6BDT5YMT.js");
6942
7807
  const pendingSessionId = getConfirmationSessionId(callId);
6943
7808
  if (!pendingSessionId) {
6944
7809
  return res.status(404).json({ error: "No pending path confirmation with that ID" });
@@ -6946,22 +7811,22 @@ All file and git operations should use this branch.
6946
7811
  if (pendingSessionId !== sessionId) {
6947
7812
  return res.status(403).json({ error: "Confirmation does not belong to this session" });
6948
7813
  }
6949
- const result = providePathConfirmation(callId, approved, alwaysAllow);
7814
+ const result = providePathConfirmation2(callId, approved, alwaysAllow);
6950
7815
  if (!result.found) {
6951
7816
  return res.status(404).json({ error: "No pending path confirmation with that ID" });
6952
7817
  }
6953
- const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEvents } = await import("./events-GMHRX4LF.js");
6954
- const { buildMessagesFromStoredEvents, foldPendingConfirmations } = await import("./folding-7K2GRPF4.js");
6955
- const { createSessionStateMessage } = await import("./protocol-TS3GVTT3.js");
6956
- const { getPendingQuestionsForSession } = await import("./tools-6XUSIJN2.js");
6957
- const { getMaxVisibleItems } = await import("./settings-VOWWO4VG.js");
7818
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEvents } = await import("./events-ZJPRGVL2.js");
7819
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2, foldPendingConfirmations } = await import("./folding-QRZOHY2I.js");
7820
+ const { createSessionStateMessage } = await import("./protocol-Y7MRKRUO.js");
7821
+ const { getPendingQuestionsForSession: getPendingQuestionsForSession2 } = await import("./tools-6BDT5YMT.js");
7822
+ const { getMaxVisibleItems } = await import("./settings-MDCRNQBY.js");
6958
7823
  const eventStore = getEventStore2();
6959
7824
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6960
7825
  const events = combineEvents(sessionId, snapshot, eventsSinceSnapshot);
6961
7826
  const maxVisibleItems = getMaxVisibleItems();
6962
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
7827
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems || void 0);
6963
7828
  const pendingConfirmations = foldPendingConfirmations(events);
6964
- const pendingQuestions = getPendingQuestionsForSession(sessionId);
7829
+ const pendingQuestions = getPendingQuestionsForSession2(sessionId);
6965
7830
  const session = sessionManager.getSession(sessionId);
6966
7831
  if (session) {
6967
7832
  const stateMsg = createSessionStateMessage(
@@ -6991,8 +7856,8 @@ All file and git operations should use this branch.
6991
7856
  if (!skip && typeof answer !== "string") {
6992
7857
  return res.status(400).json({ error: "answer is required when not skipping" });
6993
7858
  }
6994
- const { provideAnswer } = await import("./tools-6XUSIJN2.js");
6995
- const found = provideAnswer(callId, answer ?? "", skip ?? false);
7859
+ const { provideAnswer: provideAnswer2 } = await import("./tools-6BDT5YMT.js");
7860
+ const found = provideAnswer2(callId, answer ?? "", skip ?? false);
6996
7861
  if (!found) {
6997
7862
  return res.status(404).json({ error: "No pending question with that ID" });
6998
7863
  }
@@ -7024,7 +7889,7 @@ All file and git operations should use this branch.
7024
7889
  if (!session) {
7025
7890
  return res.status(404).json({ error: "Session not found" });
7026
7891
  }
7027
- const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-VOWWO4VG.js");
7892
+ const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-MDCRNQBY.js");
7028
7893
  if (getSetting2(SETTINGS_KEYS.CACHE_WARMING) !== "true") {
7029
7894
  return res.json({ success: false, reason: "disabled" });
7030
7895
  }
@@ -7057,7 +7922,7 @@ All file and git operations should use this branch.
7057
7922
  backend: activeProvider?.backend ?? llmClient.getBackend(),
7058
7923
  model: llmClient.getModel()
7059
7924
  };
7060
- const { runAgentTurn, TurnMetrics } = await import("./orchestrator-E6GIZGEN.js");
7925
+ const { runAgentTurn, TurnMetrics } = await import("./orchestrator-SQN2SJ2A.js");
7061
7926
  runAgentTurn(
7062
7927
  {
7063
7928
  sessionManager,
@@ -7095,15 +7960,15 @@ All file and git operations should use this branch.
7095
7960
  if (!session) {
7096
7961
  return res.status(404).json({ error: "Session not found" });
7097
7962
  }
7098
- const { stopSessionExecution } = await import("./chat-handler-TH6JBO6G.js");
7099
- const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-6XUSIJN2.js");
7963
+ const { stopSessionExecution } = await import("./chat-handler-RAIMQU7F.js");
7964
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-6BDT5YMT.js");
7100
7965
  const queuedMessages = sessionManager.getQueueState(sessionId);
7101
7966
  sessionManager.clearMessageQueue(sessionId);
7102
7967
  stopSessionExecution(sessionId, sessionManager);
7103
7968
  abortSession(sessionId);
7104
7969
  cancelQuestionsForSession(sessionId, "Session stopped by user");
7105
7970
  cancelPathConfirmationsForSession(sessionId, "Session stopped by user");
7106
- const eventStore = (await import("./events-GMHRX4LF.js")).getEventStore();
7971
+ const eventStore = (await import("./events-ZJPRGVL2.js")).getEventStore();
7107
7972
  eventStore.append(sessionId, { type: "running.changed", data: { isRunning: false } });
7108
7973
  res.json({ success: true, queuedMessages });
7109
7974
  });
@@ -7117,7 +7982,7 @@ All file and git operations should use this branch.
7117
7982
  if (typeof messageIndex !== "number" || messageIndex < 0) {
7118
7983
  return res.status(400).json({ error: "messageIndex must be a non-negative number" });
7119
7984
  }
7120
- const { truncateSessionMessages } = await import("./events-GMHRX4LF.js");
7985
+ const { truncateSessionMessages } = await import("./events-ZJPRGVL2.js");
7121
7986
  truncateSessionMessages(sessionId, messageIndex);
7122
7987
  res.json({ success: true });
7123
7988
  });
@@ -7137,11 +8002,11 @@ All file and git operations should use this branch.
7137
8002
  if (attachments !== void 0 && !Array.isArray(attachments)) {
7138
8003
  return res.status(400).json({ error: "attachments must be an array if provided" });
7139
8004
  }
7140
- const { getEventStore: getEventStore2 } = await import("./events-GMHRX4LF.js");
7141
- const { buildMessagesFromStoredEvents } = await import("./folding-7K2GRPF4.js");
8005
+ const { getEventStore: getEventStore2 } = await import("./events-ZJPRGVL2.js");
8006
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-QRZOHY2I.js");
7142
8007
  const eventStore = getEventStore2();
7143
8008
  const events = eventStore.getEvents(sessionId);
7144
- const { messages } = buildMessagesFromStoredEvents(events);
8009
+ const { messages } = buildMessagesFromStoredEvents2(events);
7145
8010
  const msgIndex = messages.findIndex((m) => m.id === messageId);
7146
8011
  if (msgIndex === -1) {
7147
8012
  return res.status(400).json({ error: "Message not found" });
@@ -7150,7 +8015,7 @@ All file and git operations should use this branch.
7150
8015
  if (msg.role !== "user" || msg.isSystemGenerated) {
7151
8016
  return res.status(400).json({ error: "Can only replay user messages" });
7152
8017
  }
7153
- const { truncateSessionMessages } = await import("./events-GMHRX4LF.js");
8018
+ const { truncateSessionMessages } = await import("./events-ZJPRGVL2.js");
7154
8019
  truncateSessionMessages(sessionId, msgIndex - 1);
7155
8020
  sessionManager.queueMessage(
7156
8021
  sessionId,
@@ -7212,7 +8077,7 @@ All file and git operations should use this branch.
7212
8077
  res.json({ accepted: true });
7213
8078
  });
7214
8079
  app.get("/api/settings", async (req, res) => {
7215
- const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-VOWWO4VG.js");
8080
+ const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-MDCRNQBY.js");
7216
8081
  const keysParam = req.query["keys"];
7217
8082
  if (!keysParam) {
7218
8083
  return res.status(400).json({ error: "keys query parameter is required" });
@@ -7225,19 +8090,19 @@ All file and git operations should use this branch.
7225
8090
  res.json(result);
7226
8091
  });
7227
8092
  app.get("/api/settings/:key", async (req, res) => {
7228
- const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-VOWWO4VG.js");
8093
+ const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-MDCRNQBY.js");
7229
8094
  const key = req.params.key;
7230
8095
  const value = getSetting2(key) ?? SETTINGS_DEFAULTS[key] ?? null;
7231
8096
  res.json({ key, value });
7232
8097
  });
7233
8098
  app.get("/api/settings/:key", async (req, res) => {
7234
- const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-VOWWO4VG.js");
8099
+ const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-MDCRNQBY.js");
7235
8100
  const key = req.params.key;
7236
8101
  const value = getSetting2(key) ?? SETTINGS_DEFAULTS[key] ?? null;
7237
8102
  res.json({ key, value });
7238
8103
  });
7239
8104
  app.put("/api/settings/:key", async (req, res) => {
7240
- const { setSetting: setSetting2 } = await import("./settings-VOWWO4VG.js");
8105
+ const { setSetting: setSetting2 } = await import("./settings-MDCRNQBY.js");
7241
8106
  const key = req.params.key;
7242
8107
  const { value } = req.body;
7243
8108
  if (value === void 0) {
@@ -7264,7 +8129,7 @@ All file and git operations should use this branch.
7264
8129
  }
7265
8130
  });
7266
8131
  app.get("/api/tools/shells", async (_req, res) => {
7267
- const { listAvailableShells } = await import("./platform-EMKWQTZX.js");
8132
+ const { listAvailableShells } = await import("./platform-5LQXOZO3.js");
7268
8133
  res.json({ shells: listAvailableShells() });
7269
8134
  });
7270
8135
  app.get("/api/config", async (_req, res) => {
@@ -7338,6 +8203,7 @@ All file and git operations should use this branch.
7338
8203
  ...m.requestBody !== void 0 && { requestBody: m.requestBody },
7339
8204
  ...m.reasoningEfforts !== void 0 && { reasoningEfforts: m.reasoningEfforts },
7340
8205
  ...m.reasoningEffortOverride !== void 0 && { reasoningEffortOverride: m.reasoningEffortOverride },
8206
+ ...m.modes !== void 0 && { modes: m.modes },
7341
8207
  ...m.supportsVision !== void 0 && { supportsVision: m.supportsVision },
7342
8208
  ...m.thinkingEnabled !== void 0 && { thinkingEnabled: m.thinkingEnabled },
7343
8209
  ...m.thinkingLevel !== void 0 && { thinkingLevel: m.thinkingLevel },
@@ -7356,7 +8222,7 @@ All file and git operations should use this branch.
7356
8222
  }));
7357
8223
  }
7358
8224
  app.post("/api/proxy/test", async (_req, res) => {
7359
- const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-VOWWO4VG.js");
8225
+ const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-MDCRNQBY.js");
7360
8226
  const proxyUrl = getSetting2(SETTINGS_KEYS.PROXY_URL);
7361
8227
  if (!proxyUrl) {
7362
8228
  return res.status(400).json({ success: false, error: "No proxy URL configured" });
@@ -7387,7 +8253,7 @@ All file and git operations should use this branch.
7387
8253
  res.json({
7388
8254
  success: true,
7389
8255
  url,
7390
- backend: reqBackend || "unknown",
8256
+ backend: reqBackend !== "unknown" && reqBackend ? reqBackend : detectBackendFromUrl(url) ?? "unknown",
7391
8257
  model
7392
8258
  });
7393
8259
  } catch (error) {
@@ -7448,7 +8314,7 @@ All file and git operations should use this branch.
7448
8314
  }
7449
8315
  });
7450
8316
  app.post("/api/proxy/test", async (_req, res) => {
7451
- const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-VOWWO4VG.js");
8317
+ const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-MDCRNQBY.js");
7452
8318
  const proxyUrl = getSetting2(SETTINGS_KEYS.PROXY_URL);
7453
8319
  if (!proxyUrl) {
7454
8320
  return res.status(400).json({ success: false, error: "No proxy URL configured" });
@@ -7475,9 +8341,9 @@ All file and git operations should use this branch.
7475
8341
  const backend = req.query["backend"];
7476
8342
  if (!url) return res.status(400).json({ error: "url is required" });
7477
8343
  try {
7478
- const { fetchModelsWithContext } = await import("./provider-manager-PU7RS2TK.js");
7479
- const { getModelProfile: getModelProfile2 } = await import("./profiles-HXGJIPF2.js");
7480
- const { getCatalogEntry } = await import("./model-catalog-ZLOWTQA7.js");
8344
+ const { fetchModelsWithContext } = await import("./provider-manager-URNJBK5Q.js");
8345
+ const { getModelProfile: getModelProfile2 } = await import("./profiles-EBCVFYG7.js");
8346
+ const { getCatalogEntry } = await import("./model-catalog-2PCKTYGW.js");
7481
8347
  const models = await fetchModelsWithContext(
7482
8348
  url,
7483
8349
  apiKey,
@@ -7493,6 +8359,7 @@ All file and git operations should use this branch.
7493
8359
  return {
7494
8360
  id: m.id,
7495
8361
  contextWindow: m.contextWindow,
8362
+ supportsVision: m.supportsVision ?? profile.supportsVision,
7496
8363
  defaultTemperature: profile.temperature,
7497
8364
  defaultTopP: profile.topP,
7498
8365
  defaultTopK: profile.topK,
@@ -7514,7 +8381,7 @@ All file and git operations should use this branch.
7514
8381
  if (!url) return res.status(400).json({ error: "url is required" });
7515
8382
  if (!models?.length) return res.status(400).json({ error: "models is required" });
7516
8383
  try {
7517
- const { autoConfig } = await import("./auto-config-454YE6KC.js");
8384
+ const { autoConfig } = await import("./auto-config-5VSNLVNI.js");
7518
8385
  const result = await autoConfig({
7519
8386
  url,
7520
8387
  ...apiKey ? { apiKey } : {},
@@ -7546,10 +8413,10 @@ All file and git operations should use this branch.
7546
8413
  });
7547
8414
  return res.json({ success: true, message: { content: response2.content }, raw: response2 });
7548
8415
  }
7549
- const { getModelProfile: getModelProfile2 } = await import("./profiles-HXGJIPF2.js");
7550
- const { getBackendCapabilities } = await import("./backend-4YPEC3J2.js");
7551
- const { buildNonStreamingCreateParams } = await import("./client-pure-GU2SV3DD.js");
7552
- const { OpenAIHttpClient } = await import("./http-client-ROVNSHF6.js");
8416
+ const { getModelProfile: getModelProfile2 } = await import("./profiles-EBCVFYG7.js");
8417
+ const { getBackendCapabilities } = await import("./backend-NDBY3DAQ.js");
8418
+ const { buildNonStreamingCreateParams } = await import("./client-pure-FLKP6PTI.js");
8419
+ const { OpenAIHttpClient } = await import("./http-client-P4XTKAZA.js");
7553
8420
  const { ensureVersionPrefix } = await import("./url-utils-C5BM7NF7.js");
7554
8421
  const profile = getModelProfile2(model);
7555
8422
  const capabilities = getBackendCapabilities(backend || "unknown");
@@ -7565,7 +8432,7 @@ All file and git operations should use this branch.
7565
8432
  modelSettings["queryParams"] = JSON.parse(rawQP);
7566
8433
  } else {
7567
8434
  const modeEnabled = mode === "thinking" ? modelConfig?.thinkingEnabled : modelConfig?.nonThinkingEnabled;
7568
- if (modeEnabled) {
8435
+ if (modeEnabled && capabilities.supportsChatTemplateKwargs) {
7569
8436
  modelSettings["chatTemplateKwargs"] = mode === "thinking" ? { enable_thinking: true } : { enable_thinking: false };
7570
8437
  }
7571
8438
  }
@@ -7622,7 +8489,7 @@ All file and git operations should use this branch.
7622
8489
  try {
7623
8490
  const { loadGlobalConfig, saveGlobalConfig, addProvider, setDefaultModelSelection } = await import("./config-LOK3LXFH.js");
7624
8491
  const globalConfig = await loadGlobalConfig(config3.mode ?? "production", config3.globalConfigPath);
7625
- const providerBackend = backend;
8492
+ const providerBackend = backend === "unknown" ? detectBackendFromUrl(url) ?? "unknown" : backend;
7626
8493
  const providerModels = modelConfigs?.length ? buildModelConfigs(modelConfigs) : model ? [{ id: model, contextWindow: 2e5, source: "user" }] : [];
7627
8494
  const configWithProvider = addProvider(globalConfig, {
7628
8495
  name,
@@ -7679,15 +8546,15 @@ All file and git operations should use this branch.
7679
8546
  }
7680
8547
  });
7681
8548
  app.put("/api/config/vision-fallback", async (req, res) => {
7682
- const { z } = await import("zod");
7683
- const visionFallbackUpdateSchema = z.object({
7684
- enabled: z.boolean().optional(),
7685
- url: z.string().optional(),
7686
- model: z.string().optional(),
7687
- timeout: z.number().positive().optional(),
7688
- backend: z.enum(["ollama", "openai"]).optional(),
7689
- providerModelRef: z.string().optional(),
7690
- apiKey: z.string().optional()
8549
+ const { z: z2 } = await import("zod");
8550
+ const visionFallbackUpdateSchema = z2.object({
8551
+ enabled: z2.boolean().optional(),
8552
+ url: z2.string().optional(),
8553
+ model: z2.string().optional(),
8554
+ timeout: z2.number().positive().optional(),
8555
+ backend: z2.enum(["ollama", "openai"]).optional(),
8556
+ providerModelRef: z2.string().optional(),
8557
+ apiKey: z2.string().optional()
7691
8558
  });
7692
8559
  const parsed = visionFallbackUpdateSchema.safeParse(req.body);
7693
8560
  if (!parsed.success) {
@@ -7722,14 +8589,14 @@ All file and git operations should use this branch.
7722
8589
  }
7723
8590
  });
7724
8591
  app.post("/api/config/vision-fallback/test", async (req, res) => {
7725
- const { z } = await import("zod");
7726
- const testSchema = z.object({
7727
- url: z.string().optional(),
7728
- model: z.string().optional(),
7729
- backend: z.enum(["ollama", "openai"]).optional(),
7730
- providerModelRef: z.string().optional(),
7731
- timeout: z.number().positive().optional(),
7732
- apiKey: z.string().optional()
8592
+ const { z: z2 } = await import("zod");
8593
+ const testSchema = z2.object({
8594
+ url: z2.string().optional(),
8595
+ model: z2.string().optional(),
8596
+ backend: z2.enum(["ollama", "openai"]).optional(),
8597
+ providerModelRef: z2.string().optional(),
8598
+ timeout: z2.number().positive().optional(),
8599
+ apiKey: z2.string().optional()
7733
8600
  });
7734
8601
  const parsed = testSchema.safeParse(req.body);
7735
8602
  if (!parsed.success) {
@@ -7966,7 +8833,7 @@ All file and git operations should use this branch.
7966
8833
  });
7967
8834
  async function rebuildMcpTools() {
7968
8835
  const { createMcpTools: createMcpTools2 } = await import("./tool-adapter-FJAFLDZA.js");
7969
- const { setMcpTools: setMcpTools2 } = await import("./tools-6XUSIJN2.js");
8836
+ const { setMcpTools: setMcpTools2 } = await import("./tools-6BDT5YMT.js");
7970
8837
  const mcpTools = createMcpTools2(mcpManager);
7971
8838
  setMcpTools2(mcpTools);
7972
8839
  toolRegistry = createToolRegistry();
@@ -8058,6 +8925,10 @@ All file and git operations should use this branch.
8058
8925
  for (const s of sessions) {
8059
8926
  sessionManager.setDynamicContextChanged(s.id, true);
8060
8927
  }
8928
+ await announceContextDrift(
8929
+ sessionManager,
8930
+ sessions.map((s) => s.id)
8931
+ );
8061
8932
  }
8062
8933
  const allServers = mcpManager.getAllServers();
8063
8934
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: allServers }));
@@ -8136,6 +9007,10 @@ All file and git operations should use this branch.
8136
9007
  for (const s of sessions) {
8137
9008
  sessionManager.setDynamicContextChanged(s.id, true);
8138
9009
  }
9010
+ await announceContextDrift(
9011
+ sessionManager,
9012
+ sessions.map((s) => s.id)
9013
+ );
8139
9014
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: mcpManager.getAllServers() }));
8140
9015
  res.json({ server: mcpManager.getServer(name) });
8141
9016
  } catch (error) {
@@ -8168,11 +9043,15 @@ All file and git operations should use this branch.
8168
9043
  for (const s of sessions) {
8169
9044
  sessionManager.setDynamicContextChanged(s.id, true);
8170
9045
  }
9046
+ await announceContextDrift(
9047
+ sessionManager,
9048
+ sessions.map((s) => s.id)
9049
+ );
8171
9050
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: mcpManager.getAllServers() }));
8172
9051
  res.json({ success: true });
8173
9052
  });
8174
9053
  function oauthCallbackPage(message) {
8175
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>OpenFox</title></head><body style="font-family:system-ui;padding:2rem"><p>${message}</p></body></html>`;
9054
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>OpenFox</title></head><body style="font-family:system-ui;padding:2rem"><p>${message}</p><script>try{new BroadcastChannel('openfox-oauth').postMessage({type:'oauth-callback-complete'})}catch{}</script></body></html>`;
8176
9055
  }
8177
9056
  function parseOAuthResponse(value) {
8178
9057
  let params;
@@ -8189,14 +9068,19 @@ All file and git operations should use this branch.
8189
9068
  async function applyMcpOAuthResult(name) {
8190
9069
  await mcpManager.connectServer(name);
8191
9070
  await rebuildMcpTools();
8192
- for (const s of sessionManager.listSessions()) {
9071
+ const sessions = sessionManager.listSessions();
9072
+ for (const s of sessions) {
8193
9073
  sessionManager.setDynamicContextChanged(s.id, true);
8194
9074
  }
9075
+ await announceContextDrift(
9076
+ sessionManager,
9077
+ sessions.map((s) => s.id)
9078
+ );
8195
9079
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: mcpManager.getAllServers() }));
8196
9080
  }
8197
9081
  async function completeMcpOAuth(name, serverUrl, code) {
8198
9082
  const { auth } = await import("@modelcontextprotocol/sdk/client/auth.js");
8199
- const { McpOAuthProvider: McpOAuthProvider2 } = await import("./oauth-provider-DGBC2YM6.js");
9083
+ const { McpOAuthProvider: McpOAuthProvider2 } = await import("./oauth-provider-VHUS2KEG.js");
8200
9084
  const { clearMcpOAuthAuthorizationState } = await import("./oauth-store-KECHUT54.js");
8201
9085
  const provider = new McpOAuthProvider2(name, serverUrl);
8202
9086
  try {
@@ -8229,8 +9113,9 @@ All file and git operations should use this branch.
8229
9113
  }
8230
9114
  try {
8231
9115
  const { auth } = await import("@modelcontextprotocol/sdk/client/auth.js");
8232
- const { McpOAuthProvider: McpOAuthProvider2 } = await import("./oauth-provider-DGBC2YM6.js");
9116
+ const { McpOAuthProvider: McpOAuthProvider2, rejectStaleOAuthClient } = await import("./oauth-provider-VHUS2KEG.js");
8233
9117
  const provider = new McpOAuthProvider2(name, server.config.url);
9118
+ await rejectStaleOAuthClient(provider);
8234
9119
  const result = await auth(provider, { serverUrl: server.config.url });
8235
9120
  if (result === "AUTHORIZED") {
8236
9121
  await applyMcpOAuthResult(name);
@@ -8286,8 +9171,8 @@ All file and git operations should use this branch.
8286
9171
  if (!parsed.code || !parsed.state) {
8287
9172
  return res.status(400).json({ error: "Paste the whole callback URL, both code and state are needed" });
8288
9173
  }
8289
- const { readMcpOAuthEntry } = await import("./oauth-store-KECHUT54.js");
8290
- const entry = await readMcpOAuthEntry(name, serverUrl);
9174
+ const { readMcpOAuthEntry: readMcpOAuthEntry2 } = await import("./oauth-store-KECHUT54.js");
9175
+ const entry = await readMcpOAuthEntry2(name, serverUrl);
8291
9176
  if (!entry?.state || entry.state !== parsed.state) {
8292
9177
  return res.status(400).json({ error: "This callback does not match the pending authorization" });
8293
9178
  }
@@ -8341,13 +9226,17 @@ All file and git operations should use this branch.
8341
9226
  for (const s of sessions) {
8342
9227
  sessionManager.setDynamicContextChanged(s.id, true);
8343
9228
  }
9229
+ await announceContextDrift(
9230
+ sessionManager,
9231
+ sessions.map((s) => s.id)
9232
+ );
8344
9233
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: mcpManager.getAllServers() }));
8345
9234
  res.json({ success: true });
8346
9235
  } catch (error) {
8347
9236
  res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
8348
9237
  }
8349
9238
  });
8350
- const { listProjects } = await import("./projects-TPSLCJP2.js");
9239
+ const { listProjects } = await import("./projects-5U37RUTL.js");
8351
9240
  const allProjects = listProjects();
8352
9241
  const projectDir = allProjects.find(
8353
9242
  (p) => p.workdir === config3.workdir || config3.workdir && p.workdir.startsWith(config3.workdir + "/")
@@ -8374,13 +9263,13 @@ All file and git operations should use this branch.
8374
9263
  })
8375
9264
  );
8376
9265
  app.get("/api/sessions/:id/background-processes", async (req, res) => {
8377
- const { getSessionProcesses } = await import("./manager-NOKJQTTF.js");
9266
+ const { getSessionProcesses } = await import("./manager-TMBFBCQL.js");
8378
9267
  const sessionId = req.params.id;
8379
9268
  const processes = getSessionProcesses(sessionId);
8380
9269
  res.json({ processes });
8381
9270
  });
8382
9271
  app.post("/api/sessions/:id/background-process/:processId/stop", async (req, res) => {
8383
- const { stopProcess } = await import("./manager-NOKJQTTF.js");
9272
+ const { stopProcess } = await import("./manager-TMBFBCQL.js");
8384
9273
  const sessionId = req.params.id;
8385
9274
  const processId = req.params.processId;
8386
9275
  const session = sessionManager.getSession(sessionId);
@@ -8520,7 +9409,7 @@ All file and git operations should use this branch.
8520
9409
  );
8521
9410
  const wss = wssExports.wss;
8522
9411
  deferTasksBroadcast = (projectId, payload) => wssExports.broadcastForProject(projectId, "", { type: "tasks.update", payload });
8523
- const { launchWorkflowRun, abortRunnerRun } = await import("./launch-5ARSU7EM.js");
9412
+ const { launchWorkflowRun, abortRunnerRun } = await import("./launch-Y4H7FDU2.js");
8524
9413
  deferTasksLaunchWorkflow = (sessionId, launch) => {
8525
9414
  const effective = sessionManager.resolveEffectiveProviderModel(sessionId);
8526
9415
  let llmClient = getLLMClient();
@@ -8547,9 +9436,14 @@ All file and git operations should use this branch.
8547
9436
  broadcastForSession: wssExports.broadcastForSession
8548
9437
  },
8549
9438
  {
8550
- workflowId: launch.workflowId,
9439
+ ...launch.workflowId ? { workflowId: launch.workflowId } : {},
8551
9440
  ...launch.params && Object.keys(launch.params).length > 0 ? { params: launch.params } : {},
9441
+ ...launch.subGroup ? { subGroup: launch.subGroup } : {},
8552
9442
  ...launch.scope ? { scope: launch.scope } : {},
9443
+ ...launch.resumeFrom ? { resumeFrom: launch.resumeFrom } : {},
9444
+ ...launch.stepOutput ? { stepOutput: launch.stepOutput } : {},
9445
+ ...launch.userChoice ? { userChoice: launch.userChoice } : {},
9446
+ ...launch.content ? { content: launch.content } : {},
8553
9447
  ...launch.attachments && launch.attachments.length > 0 ? { attachments: launch.attachments } : {}
8554
9448
  }
8555
9449
  );
@@ -8560,7 +9454,7 @@ All file and git operations should use this branch.
8560
9454
  const state = sessionManager.getContextState(sessionId);
8561
9455
  wssExports.broadcastForSession(sessionId, createContextStateMessage(state));
8562
9456
  });
8563
- const { QueueProcessor } = await import("./processor-7RDR675X.js");
9457
+ const { QueueProcessor } = await import("./processor-MEHODYNE.js");
8564
9458
  const queueProcessor = new QueueProcessor({
8565
9459
  sessionManager,
8566
9460
  providerManager,
@@ -8581,6 +9475,80 @@ All file and git operations should use this branch.
8581
9475
  }
8582
9476
  return aborted;
8583
9477
  };
9478
+ mcpToolDeps = {
9479
+ sessionManager,
9480
+ listProjects: () => listProjects(),
9481
+ createProject: async (name, workdir) => {
9482
+ const { createDirectoryWithGit } = await import("./project-creator-6AGQTNXB.js");
9483
+ return createDirectoryWithGit(name, workdir);
9484
+ },
9485
+ deleteProject: (projectId) => {
9486
+ const project = getProject(projectId);
9487
+ if (!project) return false;
9488
+ deleteProject(projectId);
9489
+ return true;
9490
+ },
9491
+ listWorkflows: (projectDir2) => listAvailableWorkflows(getGlobalConfigDir(config3.mode ?? "production"), projectDir2),
9492
+ topLevelAgentIds: async (workdir) => {
9493
+ const agents = await loadAllAgentsDefault(workdir);
9494
+ return getTopLevelAgents(agents).map((a) => a.metadata.id);
9495
+ },
9496
+ launchWorkflow: (sessionId, launch) => deferTasksLaunchWorkflow(sessionId, launch),
9497
+ stopSession: (sessionId) => {
9498
+ void (async () => {
9499
+ const { stopSessionExecution } = await import("./chat-handler-RAIMQU7F.js");
9500
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-6BDT5YMT.js");
9501
+ sessionManager.clearMessageQueue(sessionId);
9502
+ stopSessionExecution(sessionId, sessionManager);
9503
+ abortSession(sessionId);
9504
+ cancelQuestionsForSession(sessionId, "Session stopped by user");
9505
+ cancelPathConfirmationsForSession(sessionId, "Session stopped by user");
9506
+ getEventStore().append(sessionId, { type: "running.changed", data: { isRunning: false } });
9507
+ })().catch((error) => {
9508
+ logger.error(`MCP stopSession failed for ${sessionId}`, {
9509
+ error: error instanceof Error ? error.message : String(error)
9510
+ });
9511
+ });
9512
+ },
9513
+ stopWorkflow: (sessionId) => {
9514
+ const runningAborted = abortRunnerRun(sessionId);
9515
+ if (runningAborted) return { aborted: "running" };
9516
+ const execution = sessionManager.getActiveWorkflowExecution(sessionId);
9517
+ if (execution && execution.status === "waiting") {
9518
+ sessionManager.cancelWorkflow(
9519
+ sessionId,
9520
+ execution.id,
9521
+ execution.workflowId,
9522
+ execution.workflowName,
9523
+ execution.workflowColor
9524
+ );
9525
+ return { aborted: "paused" };
9526
+ }
9527
+ return null;
9528
+ },
9529
+ answerQuestion: (callId, answer, skip) => provideAnswer(callId, answer, skip),
9530
+ pendingQuestions: (sessionId) => getPendingQuestionsForSession(sessionId),
9531
+ confirmPath: (callId, approved, alwaysAllow) => providePathConfirmation(callId, approved, alwaysAllow).found,
9532
+ pendingConfirmations: (sessionId) => getPendingConfirmationsBySession()[sessionId] ?? [],
9533
+ setMetadataEntries: (sessionId, key, entries) => sessionManager.setMetadataEntries(sessionId, key, entries),
9534
+ recentMessages: (sessionId, limit) => {
9535
+ const eventStore = getEventStore();
9536
+ const { snapshot, events } = eventStore.getEventsSinceSnapshot(sessionId);
9537
+ const combined = combineEventsWithSnapshot(sessionId, snapshot, events);
9538
+ return buildMessagesFromStoredEvents(combined, Math.max(1, Math.min(limit, 50)));
9539
+ }
9540
+ };
9541
+ let mcpActualPort = null;
9542
+ setMcpBootstrapForTools(async () => {
9543
+ const authRequired = mcpAuth.isAuthRequired();
9544
+ const sessionToken = authRequired ? await currentSessionToken() : null;
9545
+ return buildOpenFoxMcpBootstrap({
9546
+ host: config3.server.host ?? "127.0.0.1",
9547
+ port: mcpActualPort ?? config3.server.port,
9548
+ authRequired,
9549
+ sessionToken
9550
+ });
9551
+ });
8584
9552
  return {
8585
9553
  httpServer,
8586
9554
  ctx: { config: config3, sessionManager, llmClient: getLLMClient(), toolRegistry, providerManager },
@@ -8591,9 +9559,13 @@ All file and git operations should use this branch.
8591
9559
  const addr = httpServer.address();
8592
9560
  const actualPort = typeof addr === "object" && addr ? addr.port : listenPort;
8593
9561
  setMcpOAuthServerPort(actualPort);
9562
+ mcpActualPort = actualPort;
8594
9563
  const client = getLLMClient();
8595
9564
  logger.info(`OpenFox server running at http://${host}:${actualPort}`);
8596
9565
  logger.info(`WebSocket available at ws://${host}:${actualPort}/ws`);
9566
+ logger.info(
9567
+ `MCP endpoint available at http://${host}:${actualPort}/mcp \u2014 run 'openfox mcp' for a paste-ready client config`
9568
+ );
8597
9569
  logger.info(`LLM backend: ${client.getBackend()}, model: ${client.getModel()}, url: ${config3.llm.baseUrl}`);
8598
9570
  resolve9({ port: actualPort });
8599
9571
  });
@@ -8604,7 +9576,7 @@ All file and git operations should use this branch.
8604
9576
  void (async () => {
8605
9577
  await devServerManager.stopAll();
8606
9578
  await mcpManager.disconnectAll();
8607
- const { stopAllInspectProxies } = await import("./inspect-proxy-UKPU3Z64.js");
9579
+ const { stopAllInspectProxies } = await import("./inspect-proxy-A3OROWPG.js");
8608
9580
  stopAllInspectProxies();
8609
9581
  const { cleanupAllProcesses } = await import("./store-JYPZ2RXB.js");
8610
9582
  cleanupAllProcesses();
@@ -8646,4 +9618,4 @@ export {
8646
9618
  createServerHandle,
8647
9619
  createServer
8648
9620
  };
8649
- //# sourceMappingURL=chunk-ZXWJ6JTL.js.map
9621
+ //# sourceMappingURL=chunk-6MUSEPV7.js.map