openfox 2.0.128 → 2.0.130

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 (101) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +2 -0
  3. package/dist/CHANGELOG.md +43 -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-3O6AIOID.js → chat-handler-4WCKNCQV.js} +32 -32
  8. package/dist/chunk-25DAVVTP.js +395 -0
  9. package/dist/{chunk-ISE55FOR.js → chunk-2AI7VYPN.js} +11 -1
  10. package/dist/{chunk-VHMZBKWY.js → chunk-2OMXXCJJ.js} +24 -24
  11. package/dist/{chunk-JEMPUHAA.js → chunk-35GQEIH2.js} +13 -13
  12. package/dist/{chunk-N2Q3KYWM.js → chunk-3DZSHRXE.js} +7 -2
  13. package/dist/chunk-3OBZ55SX.js +7 -0
  14. package/dist/{chunk-ZIL6VKLU.js → chunk-64N4CZFT.js} +2 -2
  15. package/dist/{chunk-66MG44TF.js → chunk-6QZVAOSA.js} +2 -2
  16. package/dist/{chunk-VJRLIKLE.js → chunk-7ET46SNE.js} +4 -4
  17. package/dist/{chunk-YJD5A375.js → chunk-AYCB32NI.js} +5 -5
  18. package/dist/{chunk-GIJUWPRQ.js → chunk-CGCEAKG5.js} +2 -2
  19. package/dist/{auth-GTGIGX5X.js → chunk-CQGJ5RWB.js} +4 -4
  20. package/dist/{chunk-B2RPFZ3U.js → chunk-CYQUM42E.js} +76 -32
  21. package/dist/{chunk-D5QVV3LT.js → chunk-DHDIKVRT.js} +278 -16
  22. package/dist/{chunk-4MXY7ELN.js → chunk-DI5GTIAU.js} +1154 -196
  23. package/dist/chunk-DQTN4B33.js +235 -0
  24. package/dist/{chunk-RV5G7QQU.js → chunk-DQTXXHD2.js} +3 -3
  25. package/dist/{chunk-OWCJRSOU.js → chunk-DYIDDGMN.js} +54 -36
  26. package/dist/{chunk-KAZWU73N.js → chunk-EUOWCVU6.js} +20 -7
  27. package/dist/{chunk-CQW4QASE.js → chunk-EVVWDRM5.js} +45 -16
  28. package/dist/{chunk-KSDWQ6IZ.js → chunk-HCYSGLY2.js} +1 -1
  29. package/dist/{chunk-KBESUA3W.js → chunk-JHA2SPCU.js} +2 -2
  30. package/dist/{chunk-ZIYETXOW.js → chunk-JKRMMNUR.js} +4 -4
  31. package/dist/{chunk-FYXZRVOL.js → chunk-JSONBGSY.js} +24 -4
  32. package/dist/{chunk-LWDL7HWL.js → chunk-KHKRHGA6.js} +15 -5
  33. package/dist/{chunk-K2QKX4ZJ.js → chunk-OA2FURC3.js} +8 -2
  34. package/dist/{chunk-ZFNBATE3.js → chunk-OXMOKQPW.js} +3 -2
  35. package/dist/{chunk-XAAEP5XM.js → chunk-P57IBMK4.js} +6 -2
  36. package/dist/{session-status-6Q5B5PEN.js → chunk-T3S5MTMD.js} +2 -3
  37. package/dist/{chunk-7ZMOP5LS.js → chunk-TS5WKTFB.js} +18 -1
  38. package/dist/{chunk-PBFGJFGC.js → chunk-UV6IH6HK.js} +32 -10
  39. package/dist/{chunk-KD4W3I6C.js → chunk-VJNA5AOB.js} +39 -7
  40. package/dist/{chunk-S6ATKIT2.js → chunk-VUIXQ6X4.js} +4 -4
  41. package/dist/{chunk-53R7QNZG.js → chunk-X24XN2M2.js} +97 -57
  42. package/dist/{chunk-AQBRI7TM.js → chunk-X3FHIDLT.js} +2 -2
  43. package/dist/chunk-YNMP647F.js +24 -0
  44. package/dist/{chunk-7YP4O4XE.js → chunk-ZRCRGPGT.js} +2 -2
  45. package/dist/cli/dev.js +1 -1
  46. package/dist/cli/index.js +1 -1
  47. package/dist/client-26RSMUAX.js +16 -0
  48. package/dist/{client-pure-CJKM3ACP.js → client-pure-FLKP6PTI.js} +2 -2
  49. package/dist/{compactor-B6IVMDES.js → compactor-HYBSSFKP.js} +14 -14
  50. package/dist/dynamic-context-2VLACYK4.js +50 -0
  51. package/dist/{events-GMHRX4LF.js → events-D33QXGSX.js} +7 -7
  52. package/dist/{folding-7K2GRPF4.js → folding-QOKBKUU6.js} +5 -5
  53. package/dist/{http-client-ROVNSHF6.js → http-client-MGAJTGPR.js} +3 -3
  54. package/dist/launch-KFENN5GS.js +53 -0
  55. package/dist/{manager-NOKJQTTF.js → manager-VRHX7BVI.js} +4 -4
  56. package/dist/mcp-YU7AM3UV.js +120 -0
  57. package/dist/{model-catalog-ZLOWTQA7.js → model-catalog-2PCKTYGW.js} +2 -2
  58. package/dist/{oauth-provider-DGBC2YM6.js → oauth-provider-VHUS2KEG.js} +8 -2
  59. package/dist/{orchestrator-WLNW7TS7.js → orchestrator-PLTAEOMR.js} +30 -30
  60. package/dist/package.json +1 -1
  61. package/dist/{path-security-YTPZ35SL.js → path-security-4WQJGU3Z.js} +11 -11
  62. package/dist/{platform-EMKWQTZX.js → platform-MWWJ6OKV.js} +3 -3
  63. package/dist/plugins-registry.json +12 -0
  64. package/dist/{processor-ZMXU5MOT.js → processor-K4WHZKE6.js} +31 -31
  65. package/dist/{profiles-HXGJIPF2.js → profiles-EBCVFYG7.js} +2 -2
  66. package/dist/{protocol-DCMH9Ufp.d.ts → protocol-BMUoqwYH.d.ts} +8 -3
  67. package/dist/{protocol-TLODRZSB.js → protocol-Y7MRKRUO.js} +5 -3
  68. package/dist/provider/index.d.ts +4 -4
  69. package/dist/{provider-QIOTESLB.js → provider-CVM33XHW.js} +10 -10
  70. package/dist/{provider-manager-FVI22EWM.js → provider-manager-7NL5EQLG.js} +10 -10
  71. package/dist/{registry-Z4GVYFTV.js → registry-KFFZFIZT.js} +3 -3
  72. package/dist/{serve-7M4HCERR.js → serve-KQAPXO5X.js} +42 -40
  73. package/dist/server/index.d.ts +18 -3
  74. package/dist/server/index.js +39 -37
  75. package/dist/server-RPKVKMDD.js +56 -0
  76. package/dist/{service-F4TWYTKE.js → service-7TLOSFAO.js} +12 -12
  77. package/dist/{session-overrides-4WAD3X5Q.js → session-overrides-U573CU2V.js} +5 -5
  78. package/dist/session-status-UFO5IFI7.js +10 -0
  79. package/dist/{sessions-D7VRCHQY.js → sessions-LUVWWRFO.js} +4 -4
  80. package/dist/{settings-VOWWO4VG.js → settings-LMAMF2X7.js} +2 -2
  81. package/dist/shared/index.d.ts +3 -3
  82. package/dist/shared/index.js +1 -1
  83. package/dist/skill-defaults/browser/SKILL.md +20 -1
  84. package/dist/{tasks-36WHYN3H.js → tasks-FA6Z52H2.js} +12 -12
  85. package/dist/{tools-WAUWG4PE.js → tools-SSPC3UQZ.js} +34 -34
  86. package/dist/{types-PAKlAzp9.d.ts → types-BIkpxjaj.d.ts} +11 -2
  87. package/dist/{types-BWuVnaBw.d.ts → types-CoY8Ka1C.d.ts} +1 -1
  88. package/dist/{update-AIVWCRXR.js → update-QRGK67DB.js} +2 -2
  89. package/dist/web/assets/{index-DtQsFnyQ.js → index-0PVQNOfW.js} +88 -88
  90. package/dist/web/assets/{index-CbX8veVW.css → index-Ds94aclE.css} +1 -1
  91. package/dist/web/index.html +2 -2
  92. package/dist/web/sw.js +1 -1
  93. package/package.json +1 -1
  94. package/plugins-registry.json +12 -0
  95. package/dist/chunk-BQNQJE57.js +0 -151
  96. package/dist/chunk-ES5635OP.js +0 -7
  97. package/dist/chunk-RSB4AR2A.js +0 -108
  98. package/dist/client-VDXO7IIV.js +0 -16
  99. package/dist/dynamic-context-4WVMNJTZ.js +0 -28
  100. package/dist/launch-AKIMGAGC.js +0 -53
  101. package/dist/server-RX5JM7IT.js +0 -56
@@ -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-OWCJRSOU.js";
27
+ } from "./chunk-DYIDDGMN.js";
20
28
  import {
21
29
  TEMPLATE_VARIABLES
22
- } from "./chunk-VHMZBKWY.js";
30
+ } from "./chunk-2OMXXCJJ.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-53R7QNZG.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-X24XN2M2.js";
67
46
  import {
68
47
  checkoutBranchFromSharedSource,
69
48
  deleteWorkspace,
@@ -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-FYXZRVOL.js";
90
+ } from "./chunk-JSONBGSY.js";
111
91
  import {
112
92
  getSessionDisabledServers,
113
93
  setSessionDisabledServers
114
- } from "./chunk-7YP4O4XE.js";
94
+ } from "./chunk-ZRCRGPGT.js";
115
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-64N4CZFT.js";
117
+ import {
118
+ ALWAYS_ALLOWED,
119
+ ALWAYS_ALLOWED_FOR_SUBAGENTS,
120
+ TOP_LEVEL_ONLY_TOOLS
121
+ } from "./chunk-CGCEAKG5.js";
122
+ import {
123
+ EventEmitter,
124
+ getPendingQuestionsForSession,
125
+ provideAnswer
126
+ } from "./chunk-JURM3RPZ.js";
127
+ import {
128
+ getPendingConfirmationsBySession,
129
+ providePathConfirmation
130
+ } from "./chunk-VJNA5AOB.js";
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-AYCB32NI.js";
149
+ import {
150
+ buildMessagesFromStoredEvents
151
+ } from "./chunk-JKRMMNUR.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-XAAEP5XM.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-B2RPFZ3U.js";
175
- import {
176
- VERSION
177
- } from "./chunk-ES5635OP.js";
177
+ } from "./chunk-X3FHIDLT.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-JHA2SPCU.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-6QZVAOSA.js";
207
+ import {
208
+ createProviderManager,
209
+ detectModel,
210
+ getLlmStatus,
211
+ parseDefaultModelSelection
212
+ } from "./chunk-CYQUM42E.js";
213
+ import {
214
+ buildOpenFoxMcpBootstrap
215
+ } from "./chunk-YNMP647F.js";
216
+ import {
217
+ VERSION
218
+ } from "./chunk-3OBZ55SX.js";
219
+ import {
220
+ deleteProject,
200
221
  getProject,
201
222
  getProjectByWorkdir,
202
223
  updateProject
203
224
  } from "./chunk-3B232XG4.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-OXMOKQPW.js";
217
239
  import {
218
240
  deleteSetting,
219
241
  getSetting,
220
242
  setSetting
221
- } from "./chunk-N2Q3KYWM.js";
243
+ } from "./chunk-3DZSHRXE.js";
222
244
  import {
223
245
  initDatabase
224
246
  } from "./chunk-OYBUJIC3.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-KSDWQ6IZ.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,7 @@ var SessionManager = class {
2761
2812
  providerManager;
2762
2813
  dynamicContextChangedStore = /* @__PURE__ */ new Map();
2763
2814
  debugDumpStore = /* @__PURE__ */ new Map();
2815
+ announcedPromptHashStore = /* @__PURE__ */ new Map();
2764
2816
  warmedUpSessions = /* @__PURE__ */ new Set();
2765
2817
  switchLocks = /* @__PURE__ */ new Map();
2766
2818
  workspaceCreationLocks = /* @__PURE__ */ new Map();
@@ -3580,7 +3632,7 @@ var SessionManager = class {
3580
3632
  const state = getSessionState(sessionId, this.getCurrentModelContext(sessionId));
3581
3633
  const maxTokens = this.getCurrentModelContext(sessionId);
3582
3634
  const currentTokens = promptTokens + completionTokens;
3583
- const compactionCount = state?.contextState.compactionCount ?? 0;
3635
+ const compactionCount = subAgentId ? 0 : state?.contextState.compactionCount ?? 0;
3584
3636
  const dynamicContextChanged = this.getDynamicContextChanged(sessionId);
3585
3637
  emitContextState(
3586
3638
  sessionId,
@@ -3839,6 +3891,16 @@ var SessionManager = class {
3839
3891
  getDynamicContextChanged(sessionId) {
3840
3892
  return this.dynamicContextChangedStore.get(sessionId) ?? false;
3841
3893
  }
3894
+ /**
3895
+ * The instructions/skills/model hash last announced to the model via a
3896
+ * system-prompt-change reminder. Drives exactly-once prompt-drift reminders.
3897
+ */
3898
+ getAnnouncedPromptHash(sessionId) {
3899
+ return this.announcedPromptHashStore.get(sessionId);
3900
+ }
3901
+ setAnnouncedPromptHash(sessionId, hash) {
3902
+ this.announcedPromptHashStore.set(sessionId, hash);
3903
+ }
3842
3904
  /**
3843
3905
  * @deprecated Use addTokensUsed instead
3844
3906
  */
@@ -4748,10 +4810,766 @@ function createWorkflowRoutes(configDir, config3, projectDir) {
4748
4810
  return createCrudRoutes(crudConfig, configDir, projectDir);
4749
4811
  }
4750
4812
 
4751
- // src/server/routes/dev-server.ts
4813
+ // src/server/mcp/server/endpoint.ts
4752
4814
  import { Router as Router4 } from "express";
4753
- function createDevServerRoutes() {
4815
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4816
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4817
+
4818
+ // src/server/mcp/server/tools.ts
4819
+ import { z } from "zod";
4820
+ var CONTINUE_PROMPT = "Continue working on the acceptance criteria.";
4821
+ var MAX_DETAIL_CONTENT_LENGTH_DEFAULT = 400;
4822
+ var MIN_DETAIL_CONTENT_LENGTH = 50;
4823
+ var MAX_DETAIL_CONTENT_LENGTH_HARD_CAP = 1e4;
4824
+ var WAIT_TIMEOUT_SECONDS_DEFAULT = 60;
4825
+ var WAIT_TIMEOUT_SECONDS_MIN = 5;
4826
+ var WAIT_TIMEOUT_SECONDS_MAX = 600;
4827
+ var WAIT_POLL_INTERVAL_MS = 500;
4828
+ var WAIT_IDLE_GRACE_MS = 2e3;
4829
+ var METADATA_SCHEMAS = {
4830
+ criteria: {
4831
+ description: "Acceptance criteria that drive workflow transitions",
4832
+ fields: {
4833
+ id: "string (auto-generated)",
4834
+ description: "string \u2014 what needs to be done and how to verify it",
4835
+ status: "pending | completed | passed | failed"
4836
+ },
4837
+ defaultStatus: "pending"
4838
+ },
4839
+ todos: {
4840
+ description: "Task tracking items for the builder",
4841
+ fields: {
4842
+ id: "string (auto-generated)",
4843
+ description: "string \u2014 task description",
4844
+ status: "pending | in_progress | completed"
4845
+ },
4846
+ defaultStatus: "pending"
4847
+ },
4848
+ review_findings: {
4849
+ description: "Code review findings from the code_reviewer sub-agent",
4850
+ fields: {
4851
+ id: "string (auto-generated)",
4852
+ description: "string \u2014 finding description",
4853
+ status: "open | resolved | dismissed",
4854
+ severity: "minor | major | critical (optional)"
4855
+ },
4856
+ defaultStatus: "open"
4857
+ }
4858
+ };
4859
+ function computeSettlement(session, pendingQuestionsCount, pendingConfirmationsCount, activeExecutionStatus, completedEvidence = false) {
4860
+ if (session.phase === "done") return { settled: true, outcome: "completed" };
4861
+ if (session.phase === "blocked") return { settled: true, outcome: "blocked" };
4862
+ const waitingForUser = pendingQuestionsCount > 0 || pendingConfirmationsCount > 0 || activeExecutionStatus === "waiting";
4863
+ if (waitingForUser) return { settled: true, outcome: "waiting" };
4864
+ if (completedEvidence && !session.isRunning) return { settled: true, outcome: "completed" };
4865
+ return { settled: false, outcome: null };
4866
+ }
4867
+ function workflowPayload(execution) {
4868
+ if (!execution) return null;
4869
+ const pausedAtUserStep = execution.status === "waiting" && (execution.pendingChoices?.length ?? 0) > 0;
4870
+ return {
4871
+ id: execution.id,
4872
+ name: execution.workflowName,
4873
+ status: execution.status,
4874
+ ...execution.currentStepId ? { currentStepId: execution.currentStepId } : {},
4875
+ ...execution.currentStepName ? { currentStepName: execution.currentStepName } : {},
4876
+ stepOutput: execution.stepOutput,
4877
+ ...execution.pendingChoices ? { pendingChoices: execution.pendingChoices } : {},
4878
+ ...execution.subGroup ? { subGroup: execution.subGroup } : {},
4879
+ ...pausedAtUserStep ? { resumeHint: "Call openfox_resume_workflow with choice set to one of the pendingChoices ids above." } : {}
4880
+ };
4881
+ }
4882
+ function normalizeMetadataId(id) {
4883
+ if (typeof id !== "string" && typeof id !== "number") return void 0;
4884
+ if (id === "") return void 0;
4885
+ return String(id);
4886
+ }
4887
+ function resolveWaitTimeout(value) {
4888
+ if (typeof value !== "number" || !Number.isFinite(value)) return WAIT_TIMEOUT_SECONDS_DEFAULT;
4889
+ return Math.max(WAIT_TIMEOUT_SECONDS_MIN, Math.min(WAIT_TIMEOUT_SECONDS_MAX, Math.floor(value)));
4890
+ }
4891
+ var sleep2 = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
4892
+ function ok(payload) {
4893
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
4894
+ }
4895
+ function fail(message) {
4896
+ return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
4897
+ }
4898
+ function truncate(content, maxLength) {
4899
+ return content.length > maxLength ? `${content.slice(0, maxLength)}...` : content;
4900
+ }
4901
+ function resolveMaxContentLength(value) {
4902
+ if (typeof value !== "number" || !Number.isFinite(value)) return MAX_DETAIL_CONTENT_LENGTH_DEFAULT;
4903
+ return Math.max(MIN_DETAIL_CONTENT_LENGTH, Math.min(MAX_DETAIL_CONTENT_LENGTH_HARD_CAP, Math.floor(value)));
4904
+ }
4905
+ function buildSessionStatusPayload(sessionId, deps) {
4906
+ const session = deps.sessionManager.getSession(sessionId);
4907
+ if (!session) return null;
4908
+ const pendingQuestions = deps.pendingQuestions(sessionId);
4909
+ const pendingConfirmations = deps.pendingConfirmations(sessionId);
4910
+ const activeExecution = deps.sessionManager.getActiveWorkflowExecution(sessionId);
4911
+ const status = projectSessionStatus({
4912
+ session,
4913
+ pendingQuestionsCount: pendingQuestions.length,
4914
+ pendingConfirmationsCount: pendingConfirmations.length,
4915
+ activeWorkflowStepName: activeExecution?.currentStepName ?? null
4916
+ });
4917
+ return {
4918
+ ...status,
4919
+ isRunning: session.isRunning,
4920
+ pending: { questions: pendingQuestions, confirmations: pendingConfirmations },
4921
+ workflow: workflowPayload(activeExecution)
4922
+ };
4923
+ }
4924
+ function createOpenFoxMcpTools(deps) {
4925
+ const { sessionManager } = deps;
4926
+ const requireSession = (sessionId) => {
4927
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
4928
+ return { ok: false, error: "sessionId is required" };
4929
+ }
4930
+ const session = sessionManager.getSession(sessionId);
4931
+ if (!session) {
4932
+ return { ok: false, error: `Session not found: ${sessionId}` };
4933
+ }
4934
+ return { ok: true, session };
4935
+ };
4936
+ const requireSessionAndId = (args) => {
4937
+ const check = requireSession(args["sessionId"]);
4938
+ if (!check.ok) return check;
4939
+ return { ok: true, session: check.session, sessionId: check.session.id };
4940
+ };
4941
+ const resolvePendingCallId = (sessionId, callId, pending, noun) => {
4942
+ if (typeof callId !== "string" || callId.length === 0) {
4943
+ return { kind: "list" };
4944
+ }
4945
+ if (!pending.some((item) => item.callId === callId)) {
4946
+ return { kind: "error", error: `No pending ${noun} with callId "${callId}" for session ${sessionId}` };
4947
+ }
4948
+ return { kind: "found", callId };
4949
+ };
4950
+ const tools = [
4951
+ {
4952
+ name: "openfox_projects",
4953
+ description: "List all OpenFox projects.",
4954
+ inputSchema: {},
4955
+ handler: async () => {
4956
+ const projects = deps.listProjects();
4957
+ return ok(projects);
4958
+ }
4959
+ },
4960
+ {
4961
+ name: "openfox_create_project",
4962
+ description: "Create a new OpenFox project at the given directory. Creates the directory and initializes git if missing, then registers the project.",
4963
+ inputSchema: {
4964
+ name: z.string().describe("Project name (letters, numbers, hyphens, underscores, dots, spaces)"),
4965
+ workdir: z.string().describe("Absolute path of the project directory")
4966
+ },
4967
+ handler: async (args) => {
4968
+ const name = args["name"];
4969
+ const workdir = args["workdir"];
4970
+ if (typeof name !== "string" || name.trim().length === 0) {
4971
+ return fail("name is required");
4972
+ }
4973
+ if (typeof workdir !== "string" || workdir.trim().length === 0) {
4974
+ return fail("workdir is required");
4975
+ }
4976
+ try {
4977
+ const project = await deps.createProject(name.trim(), workdir.trim());
4978
+ return ok({ project });
4979
+ } catch (error) {
4980
+ return fail(error instanceof Error ? error.message : String(error));
4981
+ }
4982
+ }
4983
+ },
4984
+ {
4985
+ name: "openfox_delete_project",
4986
+ description: "Delete an OpenFox project and all its sessions by id.",
4987
+ inputSchema: {
4988
+ projectId: z.string().describe("Project id to delete")
4989
+ },
4990
+ handler: async (args) => {
4991
+ const projectId = args["projectId"];
4992
+ if (typeof projectId !== "string" || projectId.length === 0) {
4993
+ return fail("projectId is required");
4994
+ }
4995
+ const deleted = deps.deleteProject(projectId);
4996
+ return ok({ deleted });
4997
+ }
4998
+ },
4999
+ {
5000
+ name: "openfox_create_session",
5001
+ description: "Create a new OpenFox session in a project, optionally selecting an agent (e.g. planner or builder) as the session mode.",
5002
+ inputSchema: {
5003
+ projectId: z.string().describe("Project to create the session in"),
5004
+ title: z.string().optional().describe("Optional session title"),
5005
+ agentId: z.string().optional().describe("Optional agent to use as the session mode (validated against available top-level agents)")
5006
+ },
5007
+ handler: async (args) => {
5008
+ const projectId = args["projectId"];
5009
+ if (typeof projectId !== "string" || projectId.length === 0) {
5010
+ return fail("projectId is required");
5011
+ }
5012
+ const title = typeof args["title"] === "string" ? args["title"] : void 0;
5013
+ const rawAgentId = args["agentId"];
5014
+ const agentId = typeof rawAgentId === "string" && rawAgentId.trim().length > 0 ? rawAgentId : void 0;
5015
+ try {
5016
+ const session = sessionManager.createSession(projectId, title, null, null);
5017
+ if (agentId) {
5018
+ const project = sessionManager.getProject(projectId);
5019
+ const topLevelIds = project ? await deps.topLevelAgentIds(project.workdir) : [];
5020
+ if (!topLevelIds.includes(agentId)) {
5021
+ return fail(
5022
+ `Invalid agentId "${agentId}". Must be one of: ${topLevelIds.join(", ") || "(none available)"}`
5023
+ );
5024
+ }
5025
+ sessionManager.setMode(session.id, agentId);
5026
+ }
5027
+ return ok({
5028
+ sessionId: session.id,
5029
+ projectId,
5030
+ title: session.metadata?.title ?? title ?? null,
5031
+ mode: agentId ?? session.mode
5032
+ });
5033
+ } catch (error) {
5034
+ return fail(error instanceof Error ? error.message : String(error));
5035
+ }
5036
+ }
5037
+ },
5038
+ {
5039
+ name: "openfox_set_mode",
5040
+ 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.",
5041
+ inputSchema: {
5042
+ sessionId: z.string().describe("Session to switch"),
5043
+ mode: z.string().describe("Target mode: one of the session project\u2019s top-level agents")
5044
+ },
5045
+ handler: async (args) => {
5046
+ const check = requireSession(args["sessionId"]);
5047
+ if (!check.ok) return fail(check.error);
5048
+ const session = check.session;
5049
+ const mode = args["mode"];
5050
+ if (typeof mode !== "string" || mode.trim().length === 0) {
5051
+ return fail("mode is required");
5052
+ }
5053
+ const project = sessionManager.getProject(session.projectId);
5054
+ const topLevelIds = project ? await deps.topLevelAgentIds(project.workdir) : [];
5055
+ if (!topLevelIds.includes(mode)) {
5056
+ return fail(`Invalid mode "${mode}". Must be one of: ${topLevelIds.join(", ") || "(none available)"}`);
5057
+ }
5058
+ const updated = sessionManager.setMode(session.id, mode);
5059
+ return ok({ sessionId: session.id, mode: updated?.mode ?? mode });
5060
+ }
5061
+ },
5062
+ {
5063
+ name: "openfox_sessions",
5064
+ description: "List sessions, optionally scoped to a project, with running state and pending interaction counts.",
5065
+ inputSchema: {
5066
+ projectId: z.string().optional().describe("Scope the listing to a project"),
5067
+ limit: z.number().int().positive().optional().describe("Maximum sessions to return (default 20)"),
5068
+ offset: z.number().int().nonnegative().optional().describe("Number of sessions to skip (default 0)")
5069
+ },
5070
+ handler: async (args) => {
5071
+ const limit = typeof args["limit"] === "number" ? args["limit"] : 20;
5072
+ const offset = typeof args["offset"] === "number" ? args["offset"] : 0;
5073
+ const projectId = args["projectId"];
5074
+ const result = typeof projectId === "string" && projectId.length > 0 ? sessionManager.listSessionsByProject(projectId, limit, offset) : sessionManager.listSessionsLimited(limit, offset);
5075
+ const sessions = result.sessions.map((s) => ({
5076
+ id: s.id,
5077
+ projectId: s.projectId,
5078
+ title: s.title ?? null,
5079
+ isRunning: s.isRunning,
5080
+ pendingQuestionCount: deps.pendingQuestions(s.id).length,
5081
+ pendingConfirmationCount: deps.pendingConfirmations(s.id).length,
5082
+ pausedWorkflowStep: sessionManager.getActiveWorkflowExecution(s.id)?.status === "waiting",
5083
+ updatedAt: s.updatedAt
5084
+ }));
5085
+ return ok({ sessions, hasMore: result.hasMore });
5086
+ }
5087
+ },
5088
+ {
5089
+ name: "openfox_session_status",
5090
+ 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.",
5091
+ inputSchema: {
5092
+ sessionId: z.string().describe("Session to inspect")
5093
+ },
5094
+ handler: async (args) => {
5095
+ const check = requireSession(args["sessionId"]);
5096
+ if (!check.ok) return fail(check.error);
5097
+ const payload = buildSessionStatusPayload(check.session.id, deps);
5098
+ if (!payload) return fail(`Session not found: ${check.session.id}`);
5099
+ return ok(payload);
5100
+ }
5101
+ },
5102
+ {
5103
+ name: "openfox_session_detail",
5104
+ description: "Read the most recent messages of a session (content truncated) so progress can be followed without a live WebSocket.",
5105
+ inputSchema: {
5106
+ sessionId: z.string().describe("Session to read"),
5107
+ limit: z.number().int().positive().optional().describe("Maximum messages to return (default 20, capped at 50)"),
5108
+ maxContentLength: z.number().int().positive().optional().describe("Maximum content length per message (default 400, clamped to 50\u201310000)")
5109
+ },
5110
+ handler: async (args) => {
5111
+ const check = requireSession(args["sessionId"]);
5112
+ if (!check.ok) return fail(check.error);
5113
+ const limit = typeof args["limit"] === "number" ? args["limit"] : 20;
5114
+ const maxContentLength = resolveMaxContentLength(args["maxContentLength"]);
5115
+ const { messages, hiddenCount } = deps.recentMessages(check.session.id, limit);
5116
+ return ok({
5117
+ messages: messages.map((message) => ({
5118
+ id: message.id,
5119
+ role: message.role,
5120
+ content: truncate(message.content ?? "", maxContentLength),
5121
+ ...message.toolCalls && message.toolCalls.length > 0 ? { toolCalls: message.toolCalls.map((toolCall) => toolCall.name) } : {}
5122
+ })),
5123
+ hiddenCount
5124
+ });
5125
+ }
5126
+ },
5127
+ {
5128
+ name: "openfox_session_metadata",
5129
+ 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.",
5130
+ inputSchema: {
5131
+ sessionId: z.string().describe("Session to manage"),
5132
+ action: z.enum(["get", "list", "add", "update", "remove", "schema"]).describe("Operation to perform"),
5133
+ key: z.string().optional().describe("Metadata key (required for get/add/update/remove/schema)"),
5134
+ id: z.string().optional().describe("Item id (required for update and remove)"),
5135
+ description: z.string().optional().describe("Item description (required for add)"),
5136
+ status: z.string().optional().describe("Item status (for add/update)")
5137
+ },
5138
+ handler: async (args) => {
5139
+ const check = requireSession(args["sessionId"]);
5140
+ if (!check.ok) return fail(check.error);
5141
+ const session = check.session;
5142
+ const action = args["action"];
5143
+ const key = typeof args["key"] === "string" && args["key"].length > 0 ? args["key"] : void 0;
5144
+ const entries = session.metadataEntries ?? {};
5145
+ if (action === "list") {
5146
+ return ok({ keys: Object.entries(entries).map(([k, v]) => ({ key: k, count: v.length })) });
5147
+ }
5148
+ if (action === "schema") {
5149
+ if (!key) return fail("key is required for schema");
5150
+ const schema = METADATA_SCHEMAS[key];
5151
+ if (!schema) return ok({ key, generic: true });
5152
+ return ok({ key, description: schema.description, fields: schema.fields });
5153
+ }
5154
+ if (action === "get") {
5155
+ if (!key) return fail("key is required for get");
5156
+ return ok({ key, entries: entries[key] ?? [] });
5157
+ }
5158
+ if (action === "add") {
5159
+ if (!key) return fail("key is required for add");
5160
+ const description = args["description"];
5161
+ if (typeof description !== "string" || description.trim().length === 0) {
5162
+ return fail("description is required for add");
5163
+ }
5164
+ const current = entries[key] ?? [];
5165
+ const newEntry = {
5166
+ id: normalizeMetadataId(args["id"]) ?? (current.length + 1).toString(),
5167
+ description,
5168
+ status: typeof args["status"] === "string" ? args["status"] : METADATA_SCHEMAS[key]?.defaultStatus ?? "pending"
5169
+ };
5170
+ const updated = [...current, newEntry];
5171
+ deps.setMetadataEntries(session.id, key, updated);
5172
+ return ok({ key, added: newEntry, entries: updated });
5173
+ }
5174
+ if (action === "update") {
5175
+ if (!key) return fail("key is required for update");
5176
+ const id = normalizeMetadataId(args["id"]);
5177
+ if (!id) return fail("id is required for update");
5178
+ const current = entries[key];
5179
+ if (!current) return fail(`Key "${key}" not found`);
5180
+ const idx = current.findIndex((e) => e.id === id);
5181
+ if (idx === -1) return fail(`Item "${id}" not found in "${key}"`);
5182
+ const description = args["description"];
5183
+ const status = args["status"];
5184
+ if (typeof description !== "string" && typeof status !== "string") {
5185
+ return fail("provide at least one of description or status for update");
5186
+ }
5187
+ const updated = current.map(
5188
+ (e, i) => i === idx ? {
5189
+ ...e,
5190
+ ...typeof description === "string" ? { description } : {},
5191
+ ...typeof status === "string" ? { status } : {}
5192
+ } : e
5193
+ );
5194
+ deps.setMetadataEntries(session.id, key, updated);
5195
+ return ok({ key, updated: id, entries: updated });
5196
+ }
5197
+ if (action === "remove") {
5198
+ if (!key) return fail("key is required for remove");
5199
+ const id = normalizeMetadataId(args["id"]);
5200
+ if (!id) return fail("id is required for remove");
5201
+ const current = entries[key];
5202
+ if (!current) return fail(`Key "${key}" not found`);
5203
+ const updated = current.filter((e) => e.id !== id);
5204
+ if (updated.length === current.length) return fail(`Item "${id}" not found in "${key}"`);
5205
+ deps.setMetadataEntries(session.id, key, updated);
5206
+ return ok({ key, removed: id, entries: updated });
5207
+ }
5208
+ return fail(`Unknown action "${String(action)}". Must be one of: get, list, add, update, remove, schema`);
5209
+ }
5210
+ },
5211
+ {
5212
+ name: "openfox_send_message",
5213
+ 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.",
5214
+ inputSchema: {
5215
+ sessionId: z.string().describe("Session to message"),
5216
+ content: z.string().optional().describe("Message content"),
5217
+ attachments: z.array(z.record(z.string(), z.unknown())).optional().describe("Optional attachments"),
5218
+ messageKind: z.string().optional().describe("Optional message kind tag")
5219
+ },
5220
+ handler: async (args) => {
5221
+ const check = requireSession(args["sessionId"]);
5222
+ if (!check.ok) return fail(check.error);
5223
+ const content = typeof args["content"] === "string" ? args["content"] : void 0;
5224
+ const rawAttachments = args["attachments"];
5225
+ const attachments = Array.isArray(rawAttachments) ? rawAttachments : void 0;
5226
+ const messageKind = typeof args["messageKind"] === "string" ? args["messageKind"] : void 0;
5227
+ if (!content?.trim() && !(attachments && attachments.length > 0)) {
5228
+ return fail("content or attachments is required");
5229
+ }
5230
+ sessionManager.queueMessage(check.session.id, "asap", content, attachments, messageKind);
5231
+ return ok({ queued: true, queueState: sessionManager.getQueueState(check.session.id) });
5232
+ }
5233
+ },
5234
+ {
5235
+ name: "openfox_continue",
5236
+ 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).",
5237
+ inputSchema: {
5238
+ sessionId: z.string().describe("Session to continue")
5239
+ },
5240
+ handler: async (args) => {
5241
+ const check = requireSessionAndId(args);
5242
+ if (!check.ok) return fail(check.error);
5243
+ const { session, sessionId } = check;
5244
+ if (session.isRunning) {
5245
+ return fail(`Session ${sessionId} is already running`);
5246
+ }
5247
+ if (session.phase === "blocked") {
5248
+ sessionManager.setPhase(sessionId, "build");
5249
+ sessionManager.queueMessage(sessionId, "asap", CONTINUE_PROMPT, void 0, "auto-prompt");
5250
+ return ok({ accepted: true });
5251
+ }
5252
+ return ok({
5253
+ accepted: true,
5254
+ note: `Session ${sessionId} is idle \u2014 use openfox_send_message to give it work.`
5255
+ });
5256
+ }
5257
+ },
5258
+ {
5259
+ name: "openfox_wait",
5260
+ 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.",
5261
+ inputSchema: {
5262
+ sessionId: z.string().describe("Session to wait on"),
5263
+ timeout: z.number().int().positive().optional().describe("Maximum wait in seconds (default 60, clamped 5\u2013600)")
5264
+ },
5265
+ handler: async (args) => {
5266
+ const check = requireSession(args["sessionId"]);
5267
+ if (!check.ok) return fail(check.error);
5268
+ const sessionId = check.session.id;
5269
+ const timeoutSeconds = resolveWaitTimeout(args["timeout"]);
5270
+ const startedAt = Date.now();
5271
+ const deadline = startedAt + timeoutSeconds * 1e3;
5272
+ const graceUntil = startedAt + WAIT_IDLE_GRACE_MS;
5273
+ let sawRunning = false;
5274
+ while (true) {
5275
+ const session = sessionManager.getSession(sessionId);
5276
+ if (!session) return fail(`Session not found: ${sessionId}`);
5277
+ if (session.isRunning) sawRunning = true;
5278
+ const pendingQuestions = deps.pendingQuestions(sessionId);
5279
+ const pendingConfirmations = deps.pendingConfirmations(sessionId);
5280
+ const activeExecution = sessionManager.getActiveWorkflowExecution(sessionId);
5281
+ const now = Date.now();
5282
+ const hasWork = (session.messages?.length ?? 0) > 0;
5283
+ const completedEvidence = sawRunning || hasWork && now >= graceUntil;
5284
+ const settlement = computeSettlement(
5285
+ session,
5286
+ pendingQuestions.length,
5287
+ pendingConfirmations.length,
5288
+ activeExecution?.status ?? null,
5289
+ completedEvidence
5290
+ );
5291
+ if (settlement.settled) {
5292
+ return ok({
5293
+ settled: true,
5294
+ outcome: settlement.outcome,
5295
+ waitedMs: now - startedAt,
5296
+ status: buildSessionStatusPayload(sessionId, deps)
5297
+ });
5298
+ }
5299
+ if (now >= deadline) {
5300
+ return ok({
5301
+ settled: false,
5302
+ timedOut: true,
5303
+ waitedMs: now - startedAt,
5304
+ status: buildSessionStatusPayload(sessionId, deps)
5305
+ });
5306
+ }
5307
+ await sleep2(WAIT_POLL_INTERVAL_MS);
5308
+ }
5309
+ }
5310
+ },
5311
+ {
5312
+ name: "openfox_stop",
5313
+ description: "Stop a session completely: drains the queue, aborts the active execution, and cancels pending interactions.",
5314
+ inputSchema: {
5315
+ sessionId: z.string().describe("Session to stop")
5316
+ },
5317
+ handler: async (args) => {
5318
+ const check = requireSession(args["sessionId"]);
5319
+ if (!check.ok) return fail(check.error);
5320
+ deps.stopSession(check.session.id);
5321
+ return ok({ stopped: true });
5322
+ }
5323
+ },
5324
+ {
5325
+ name: "openfox_workflows",
5326
+ description: "List the workflows available to a project (builtin, user, and project scope) with their parameters.",
5327
+ inputSchema: {
5328
+ projectDir: z.string().optional().describe("Project directory for project-scoped workflows")
5329
+ },
5330
+ handler: async (args) => {
5331
+ const projectDir = typeof args["projectDir"] === "string" ? args["projectDir"] : void 0;
5332
+ return ok(await deps.listWorkflows(projectDir));
5333
+ }
5334
+ },
5335
+ {
5336
+ name: "openfox_launch_workflow",
5337
+ 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.",
5338
+ inputSchema: {
5339
+ sessionId: z.string().describe("Session to run the workflow in"),
5340
+ workflowId: z.string().optional().describe("Workflow to run (default: the active workflow)"),
5341
+ params: z.record(z.string(), z.string()).optional().describe("Workflow parameters"),
5342
+ subGroup: z.string().optional().describe("Run only a sub-group slice of the workflow"),
5343
+ scope: z.enum(["builtin", "user", "project"]).optional().describe("Workflow scope to resolve from"),
5344
+ resumeFrom: z.string().optional().describe("Step id to resume a paused workflow from"),
5345
+ stepOutput: z.record(z.string(), z.string()).optional().describe("Step output supplied when resuming"),
5346
+ userChoice: z.string().optional().describe("Branch chosen by the user at a paused user step"),
5347
+ content: z.string().optional().describe("Optional extra instruction for the run")
5348
+ },
5349
+ handler: async (args) => {
5350
+ const check = requireSessionAndId(args);
5351
+ if (!check.ok) return fail(check.error);
5352
+ const { session, sessionId } = check;
5353
+ const payload = {
5354
+ ...typeof args["workflowId"] === "string" ? { workflowId: args["workflowId"] } : {},
5355
+ ...args["params"] && typeof args["params"] === "object" ? { params: args["params"] } : {},
5356
+ ...typeof args["subGroup"] === "string" ? { subGroup: args["subGroup"] } : {},
5357
+ ...typeof args["scope"] === "string" ? { scope: args["scope"] } : {},
5358
+ ...typeof args["resumeFrom"] === "string" ? { resumeFrom: args["resumeFrom"] } : {},
5359
+ ...args["stepOutput"] && typeof args["stepOutput"] === "object" ? { stepOutput: args["stepOutput"] } : {},
5360
+ ...typeof args["userChoice"] === "string" ? { userChoice: args["userChoice"] } : {},
5361
+ ...typeof args["content"] === "string" ? { content: args["content"] } : {}
5362
+ };
5363
+ if (payload.workflowId && !payload.resumeFrom) {
5364
+ const workflows = await deps.listWorkflows(session.workdir);
5365
+ const known = workflows.some((w) => w.id === payload.workflowId);
5366
+ if (!known) {
5367
+ return fail(`Workflow "${payload.workflowId}" not found for project ${session.workdir}`);
5368
+ }
5369
+ }
5370
+ if (session.isRunning) {
5371
+ let fullContent = payload.content ?? "";
5372
+ if (payload.workflowId) {
5373
+ const workflowInfo = `// Workflow: ${payload.workflowId}`;
5374
+ fullContent = fullContent ? `${workflowInfo}
5375
+
5376
+ ${fullContent}` : workflowInfo;
5377
+ }
5378
+ if (!fullContent) {
5379
+ return fail("Session is running \u2014 give a workflowId or content to queue for the next turn boundary.");
5380
+ }
5381
+ sessionManager.queueMessage(sessionId, "asap", fullContent, void 0, "workflow-launch");
5382
+ return ok({ queued: true, queueState: sessionManager.getQueueState(sessionId) });
5383
+ }
5384
+ if (session.phase === "blocked") {
5385
+ sessionManager.setPhase(sessionId, "build");
5386
+ }
5387
+ deps.launchWorkflow(sessionId, payload);
5388
+ return ok({ launched: true });
5389
+ }
5390
+ },
5391
+ {
5392
+ name: "openfox_resume_workflow",
5393
+ 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.',
5394
+ inputSchema: {
5395
+ sessionId: z.string().describe("Session paused at a workflow user step"),
5396
+ choice: z.string().describe("One of the pendingChoices surfaced by openfox_session_status (id or label)")
5397
+ },
5398
+ handler: async (args) => {
5399
+ const check = requireSession(args["sessionId"]);
5400
+ if (!check.ok) return fail(check.error);
5401
+ const sessionId = check.session.id;
5402
+ const choice = args["choice"];
5403
+ if (typeof choice !== "string" || choice.trim().length === 0) {
5404
+ return fail("choice is required \u2014 pick one of the pendingChoices surfaced by openfox_session_status");
5405
+ }
5406
+ const execution = sessionManager.getActiveWorkflowExecution(sessionId);
5407
+ if (!execution || execution.status !== "waiting" || !execution.currentStepId) {
5408
+ return fail(`Session ${sessionId} is not paused at a workflow user step`);
5409
+ }
5410
+ const pendingChoices = execution.pendingChoices ?? [];
5411
+ if (pendingChoices.length === 0) {
5412
+ return fail(`No choices available for paused step "${execution.currentStepName ?? execution.currentStepId}"`);
5413
+ }
5414
+ const match = pendingChoices.find((c) => c.id === choice || c.label === choice);
5415
+ if (!match) {
5416
+ return fail(`Invalid choice "${choice}". Available: ${pendingChoices.map((c) => c.id).join(", ")}`);
5417
+ }
5418
+ deps.launchWorkflow(sessionId, {
5419
+ workflowId: execution.workflowId,
5420
+ resumeFrom: execution.currentStepId,
5421
+ userChoice: match.id,
5422
+ stepOutput: execution.stepOutput
5423
+ });
5424
+ return ok({
5425
+ resumed: true,
5426
+ choice: match.id,
5427
+ step: execution.currentStepName ?? execution.currentStepId
5428
+ });
5429
+ }
5430
+ },
5431
+ {
5432
+ name: "openfox_stop_workflow",
5433
+ 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.",
5434
+ inputSchema: {
5435
+ sessionId: z.string().describe("Session whose workflow run should be aborted")
5436
+ },
5437
+ handler: async (args) => {
5438
+ const check = requireSession(args["sessionId"]);
5439
+ if (!check.ok) return fail(check.error);
5440
+ const stopped = deps.stopWorkflow(check.session.id);
5441
+ if (!stopped) return fail("No active workflow run to stop");
5442
+ return ok({ stopped: true, aborted: stopped.aborted });
5443
+ }
5444
+ },
5445
+ {
5446
+ name: "openfox_answer",
5447
+ description: "Answer or skip a pending ask_user question of a session by callId. Without a callId, lists the session pending questions.",
5448
+ inputSchema: {
5449
+ sessionId: z.string().describe("Session the question belongs to"),
5450
+ callId: z.string().optional().describe("Pending question to answer (omit to list)"),
5451
+ answer: z.string().optional().describe("The answer to provide"),
5452
+ skip: z.boolean().optional().describe("Skip the question instead of answering it")
5453
+ },
5454
+ handler: async (args) => {
5455
+ const check = requireSession(args["sessionId"]);
5456
+ if (!check.ok) return fail(check.error);
5457
+ const session = check.session;
5458
+ const pending = deps.pendingQuestions(session.id);
5459
+ const resolved = resolvePendingCallId(session.id, args["callId"], pending, "question");
5460
+ if (resolved.kind === "list") return ok({ questions: pending });
5461
+ if (resolved.kind === "error") return fail(resolved.error);
5462
+ const callId = resolved.callId;
5463
+ const skip = args["skip"] === true;
5464
+ const answer = typeof args["answer"] === "string" ? args["answer"] : "";
5465
+ if (!skip && answer.length === 0) {
5466
+ return fail("answer is required unless skip is true");
5467
+ }
5468
+ const found = deps.answerQuestion(callId, skip ? "" : answer, skip || void 0);
5469
+ if (!found) {
5470
+ return fail(`Question ${callId} is no longer pending`);
5471
+ }
5472
+ return ok({ answered: true, callId, skipped: skip || void 0 });
5473
+ }
5474
+ },
5475
+ {
5476
+ name: "openfox_confirm",
5477
+ description: "Resolve a pending path confirmation of a session (approve/deny, optionally always-allow the paths). Without a callId, lists the session pending confirmations.",
5478
+ inputSchema: {
5479
+ sessionId: z.string().describe("Session the confirmation belongs to"),
5480
+ callId: z.string().optional().describe("Pending confirmation to resolve (omit to list)"),
5481
+ approved: z.boolean().optional().describe("Approve or deny the requested access"),
5482
+ alwaysAllow: z.boolean().optional().describe("Permanently allow the paths for this session")
5483
+ },
5484
+ handler: async (args) => {
5485
+ const check = requireSession(args["sessionId"]);
5486
+ if (!check.ok) return fail(check.error);
5487
+ const session = check.session;
5488
+ const pending = deps.pendingConfirmations(session.id);
5489
+ const resolved = resolvePendingCallId(session.id, args["callId"], pending, "confirmation");
5490
+ if (resolved.kind === "list") return ok({ confirmations: pending });
5491
+ if (resolved.kind === "error") return fail(resolved.error);
5492
+ const callId = resolved.callId;
5493
+ const approved = args["approved"];
5494
+ if (typeof approved !== "boolean") {
5495
+ return fail("approved (boolean) is required");
5496
+ }
5497
+ const alwaysAllow = args["alwaysAllow"];
5498
+ const found = deps.confirmPath(callId, approved, alwaysAllow);
5499
+ if (!found) {
5500
+ return fail(`Confirmation ${callId} is no longer pending`);
5501
+ }
5502
+ return ok({ confirmed: true, callId, approved });
5503
+ }
5504
+ }
5505
+ ];
5506
+ return tools;
5507
+ }
5508
+
5509
+ // src/server/mcp/server/endpoint.ts
5510
+ function buildOpenFoxMcpServer(deps) {
5511
+ const server = new McpServer({
5512
+ name: "openfox",
5513
+ version: "1.0.0"
5514
+ });
5515
+ for (const tool of createOpenFoxMcpTools(deps)) {
5516
+ const config3 = { description: tool.description };
5517
+ if (Object.keys(tool.inputSchema).length > 0) {
5518
+ config3["inputSchema"] = tool.inputSchema;
5519
+ }
5520
+ server.registerTool(
5521
+ tool.name,
5522
+ config3,
5523
+ async (args) => tool.handler(args ?? {})
5524
+ );
5525
+ }
5526
+ return server;
5527
+ }
5528
+ function createOpenFoxMcpRouter(options) {
4754
5529
  const router = Router4();
5530
+ router.all("/", async (req, res) => {
5531
+ try {
5532
+ if (options.isAuthRequired() && !await options.isAuthorized(req)) {
5533
+ res.status(401).json({ error: "Unauthorized" });
5534
+ return;
5535
+ }
5536
+ const deps = options.resolveDeps();
5537
+ if (!deps) {
5538
+ res.status(503).json({ error: "MCP server is not ready" });
5539
+ return;
5540
+ }
5541
+ const server = buildOpenFoxMcpServer(deps);
5542
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
5543
+ await server.connect(transport);
5544
+ await transport.handleRequest(req, res, req.body);
5545
+ } catch (error) {
5546
+ logger.error("MCP endpoint request failed", {
5547
+ error: error instanceof Error ? error.message : String(error)
5548
+ });
5549
+ if (!res.headersSent) {
5550
+ res.status(500).json({ error: "MCP request failed" });
5551
+ }
5552
+ }
5553
+ });
5554
+ return router;
5555
+ }
5556
+ function extractSessionToken(req) {
5557
+ const header = req.headers["x-session-token"];
5558
+ if (typeof header === "string" && header.length > 0) {
5559
+ return header;
5560
+ }
5561
+ const authorization = req.headers["authorization"];
5562
+ if (typeof authorization === "string" && authorization.startsWith("Bearer ")) {
5563
+ const token = authorization.slice("Bearer ".length).trim();
5564
+ return token.length > 0 ? token : null;
5565
+ }
5566
+ return null;
5567
+ }
5568
+
5569
+ // src/server/routes/dev-server.ts
5570
+ import { Router as Router5 } from "express";
5571
+ function createDevServerRoutes() {
5572
+ const router = Router5();
4755
5573
  router.get("/", async (req, res) => {
4756
5574
  const workdir = req.query["workdir"];
4757
5575
  if (!workdir) return res.status(400).json({ error: "workdir required" });
@@ -4849,7 +5667,7 @@ function createDevServerRoutes() {
4849
5667
  }
4850
5668
 
4851
5669
  // src/server/routes/workspace-config.ts
4852
- import { Router as Router5 } from "express";
5670
+ import { Router as Router6 } from "express";
4853
5671
  import { stat as stat2, mkdir as mkdir3, readdir, access as access3 } from "fs/promises";
4854
5672
  import { constants as constants3 } from "fs";
4855
5673
  import { resolve as resolve4, isAbsolute as isAbsolute3, join as join5, win32 } from "path";
@@ -4946,7 +5764,7 @@ async function findOrphanedWorkspaces(dir) {
4946
5764
  return results;
4947
5765
  }
4948
5766
  function createWorkspaceConfigRoutes(sessionManager) {
4949
- const router = Router5();
5767
+ const router = Router6();
4950
5768
  router.get("/config", async (req, res) => {
4951
5769
  const workdir = req.query["workdir"];
4952
5770
  if (!workdir) return res.status(400).json({ error: "workdir required" });
@@ -5080,9 +5898,9 @@ function createWorkspaceConfigRoutes(sessionManager) {
5080
5898
  }
5081
5899
 
5082
5900
  // src/server/routes/terminals.ts
5083
- import { Router as Router6 } from "express";
5901
+ import { Router as Router7 } from "express";
5084
5902
  function createTerminalRoutes() {
5085
- const router = Router6();
5903
+ const router = Router7();
5086
5904
  router.get("/", (req, res) => {
5087
5905
  const projectId = req.query["projectId"];
5088
5906
  const sessions = projectId ? terminalManager.getByProject(projectId) : [];
@@ -5121,11 +5939,11 @@ function createTerminalRoutes() {
5121
5939
  }
5122
5940
 
5123
5941
  // src/server/routes/directories.ts
5124
- import { Router as Router7 } from "express";
5942
+ import { Router as Router8 } from "express";
5125
5943
  import { readdir as readdir2 } from "fs/promises";
5126
5944
  import { resolve as resolve5, join as join6, dirname as dirname4, basename as basename2 } from "path";
5127
5945
  function createDirectoryRoutes() {
5128
- const router = Router7();
5946
+ const router = Router8();
5129
5947
  const DEFAULT_BASE_PATH = process.cwd();
5130
5948
  router.get("/", async (req, res) => {
5131
5949
  const path = req.query["path"] || DEFAULT_BASE_PATH;
@@ -5160,7 +5978,7 @@ function createDirectoryRoutes() {
5160
5978
  }
5161
5979
 
5162
5980
  // src/server/routes/file-search.ts
5163
- import { Router as Router8 } from "express";
5981
+ import { Router as Router9 } from "express";
5164
5982
  import fg from "fast-glob";
5165
5983
  var IGNORED_PATTERNS = [
5166
5984
  "**/node_modules/**",
@@ -5173,7 +5991,7 @@ var IGNORED_PATTERNS = [
5173
5991
  "**/.openfox/**"
5174
5992
  ];
5175
5993
  function createFileSearchRoutes() {
5176
- const router = Router8();
5994
+ const router = Router9();
5177
5995
  router.get("/", async (req, res) => {
5178
5996
  const query = req.query["q"] || "";
5179
5997
  const workdir = req.query["workdir"] || process.cwd();
@@ -5265,7 +6083,7 @@ function fuzzyScore(filePath, query) {
5265
6083
  }
5266
6084
 
5267
6085
  // src/server/routes/auto-update.ts
5268
- import { Router as Router9 } from "express";
6086
+ import { Router as Router10 } from "express";
5269
6087
  import { spawn as spawn2 } from "child_process";
5270
6088
  var updateInProgress = false;
5271
6089
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -5362,7 +6180,7 @@ async function getLatestDevelopVersion() {
5362
6180
  });
5363
6181
  }
5364
6182
  function createAutoUpdateRoutes(options = {}) {
5365
- const router = Router9();
6183
+ const router = Router10();
5366
6184
  router.get("/check", async (req, res) => {
5367
6185
  const isService = isRunningAsService();
5368
6186
  const isDev = isDevMode();
@@ -5479,9 +6297,9 @@ function createAutoUpdateRoutes(options = {}) {
5479
6297
  }
5480
6298
 
5481
6299
  // src/server/routes/provider-auth.ts
5482
- import { Router as Router10 } from "express";
6300
+ import { Router as Router11 } from "express";
5483
6301
  function createProviderAuthRoutes(config3, providerManager, registry) {
5484
- const router = Router10();
6302
+ const router = Router11();
5485
6303
  function resolveAuthContext(providerId, res) {
5486
6304
  const provider = providerManager.getProviders().find((item) => item.id === providerId);
5487
6305
  if (!provider) {
@@ -5710,7 +6528,7 @@ async function loadProviderPlugins(options) {
5710
6528
  }
5711
6529
 
5712
6530
  // src/server/routes/plugins.ts
5713
- import { Router as Router11 } from "express";
6531
+ import { Router as Router12 } from "express";
5714
6532
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL2 } from "url";
5715
6533
  import { dirname as dirname6, resolve as resolve7, join as join8 } from "path";
5716
6534
  import { existsSync } from "fs";
@@ -5727,7 +6545,7 @@ async function openFolderRoute(dir, res) {
5727
6545
  }
5728
6546
  }
5729
6547
  function createPluginRoutes(options) {
5730
- const router = Router11();
6548
+ const router = Router12();
5731
6549
  const { config: config3, providerAdapters, pluginDiagnostics, logger: logger2 } = options;
5732
6550
  let registryCache = null;
5733
6551
  router.get("/registry", async (_req, res) => {
@@ -5968,6 +6786,10 @@ function detectWsl() {
5968
6786
 
5969
6787
  // src/server/index.ts
5970
6788
  var __dirname3 = dirname7(fileURLToPath6(import.meta.url));
6789
+ async function announceContextDrift(sessionManager, sessionIds) {
6790
+ const { injectContextDriftRemindersForSessions } = await import("./dynamic-context-2VLACYK4.js");
6791
+ await injectContextDriftRemindersForSessions(sessionManager, sessionIds);
6792
+ }
5971
6793
  async function createServerHandle(config3) {
5972
6794
  setRuntimeConfig(config3);
5973
6795
  setLogLevel(config3.logging?.level ?? void 0, config3.mode);
@@ -6072,7 +6894,7 @@ async function createServerHandle(config3) {
6072
6894
  toolRegistry = createToolRegistry();
6073
6895
  logger.info("MCP tools registered", { count: mcpTools.length });
6074
6896
  }
6075
- const { signalMcpReady } = await import("./server-RX5JM7IT.js");
6897
+ const { signalMcpReady } = await import("./server-RPKVKMDD.js");
6076
6898
  signalMcpReady();
6077
6899
  });
6078
6900
  const app = express();
@@ -6095,6 +6917,25 @@ async function createServerHandle(config3) {
6095
6917
  };
6096
6918
  app.use("/api", authMiddleware);
6097
6919
  app.use(express.json({ limit: "75mb" }));
6920
+ let mcpToolDeps = null;
6921
+ const mcpAuth = {
6922
+ isAuthRequired: () => {
6923
+ const cfg = getAuthConfig();
6924
+ return cfg?.strategy === "network" && cfg.encryptedPassword != null;
6925
+ },
6926
+ isAuthorized: async (req) => {
6927
+ const token = extractSessionToken(req);
6928
+ return token ? isValidToken(token) : false;
6929
+ }
6930
+ };
6931
+ app.use(
6932
+ "/mcp",
6933
+ createOpenFoxMcpRouter({
6934
+ resolveDeps: () => mcpToolDeps,
6935
+ isAuthRequired: mcpAuth.isAuthRequired,
6936
+ isAuthorized: mcpAuth.isAuthorized
6937
+ })
6938
+ );
6098
6939
  app.get("/api/health", (_req, res) => {
6099
6940
  res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
6100
6941
  });
@@ -6245,12 +7086,12 @@ async function createServerHandle(config3) {
6245
7086
  res.json({ project: updated });
6246
7087
  });
6247
7088
  app.delete("/api/projects/:id", async (req, res) => {
6248
- const { getProject: getProject2, deleteProject } = await import("./projects-TPSLCJP2.js");
7089
+ const { getProject: getProject2, deleteProject: deleteProject2 } = await import("./projects-TPSLCJP2.js");
6249
7090
  const project = getProject2(req.params.id);
6250
7091
  if (!project) {
6251
7092
  return res.status(404).json({ error: "Project not found" });
6252
7093
  }
6253
- deleteProject(req.params.id);
7094
+ deleteProject2(req.params.id);
6254
7095
  res.json({ success: true });
6255
7096
  });
6256
7097
  app.put("/api/projects/:id/star", async (req, res) => {
@@ -6268,9 +7109,9 @@ async function createServerHandle(config3) {
6268
7109
  const sessionFavoriteRouter = express.Router();
6269
7110
  registerSessionFavoriteRoute(sessionFavoriteRouter, sessionManager);
6270
7111
  app.use("/api", sessionFavoriteRouter);
6271
- const { createTasksService } = await import("./service-F4TWYTKE.js");
6272
- const { registerTaskRoutes } = await import("./tasks-36WHYN3H.js");
6273
- const { setTasksService } = await import("./tools-WAUWG4PE.js");
7112
+ const { createTasksService } = await import("./service-7TLOSFAO.js");
7113
+ const { registerTaskRoutes } = await import("./tasks-FA6Z52H2.js");
7114
+ const { setTasksService } = await import("./tools-SSPC3UQZ.js");
6274
7115
  const tasksService = createTasksService({
6275
7116
  sessionManager,
6276
7117
  config: config3,
@@ -6302,7 +7143,7 @@ async function createServerHandle(config3) {
6302
7143
  }
6303
7144
  try {
6304
7145
  await checkoutBranch(project.workdir, branch);
6305
- const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-D7VRCHQY.js");
7146
+ const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-LUVWWRFO.js");
6306
7147
  const allSessions = sessionManager.listSessions();
6307
7148
  for (const s of allSessions) {
6308
7149
  if (sessionManager.getEffectiveWorkdir(s.id) === project.workdir) {
@@ -6338,7 +7179,7 @@ async function createServerHandle(config3) {
6338
7179
  );
6339
7180
  }
6340
7181
  await createBranch(project.workdir, name, sb);
6341
- const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-D7VRCHQY.js");
7182
+ const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-LUVWWRFO.js");
6342
7183
  const allSessions = sessionManager.listSessions();
6343
7184
  for (const s of allSessions) {
6344
7185
  if (sessionManager.getEffectiveWorkdir(s.id) === project.workdir) {
@@ -6370,7 +7211,7 @@ async function createServerHandle(config3) {
6370
7211
  if (!branch || typeof branch !== "string") return res.status(400).json({ error: "branch is required" });
6371
7212
  const effectiveWorkdir = sessionManager.getEffectiveWorkdir(req.params.id);
6372
7213
  const { checkoutBranch, validateRef: validateRef2 } = await import("./workspace-6CUE26YA.js");
6373
- const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-D7VRCHQY.js");
7214
+ const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-LUVWWRFO.js");
6374
7215
  try {
6375
7216
  await validateRef2(effectiveWorkdir, branch);
6376
7217
  await checkoutBranch(effectiveWorkdir, branch);
@@ -6413,7 +7254,7 @@ All file and git operations should use this branch.
6413
7254
  if (!name || typeof name !== "string") return res.status(400).json({ error: "name is required" });
6414
7255
  const effectiveWorkdir = sessionManager.getEffectiveWorkdir(req.params.id);
6415
7256
  const { validateRef: validateRef2 } = await import("./workspace-6CUE26YA.js");
6416
- const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-D7VRCHQY.js");
7257
+ const { updateSessionBranch: updateSessionBranch2 } = await import("./sessions-LUVWWRFO.js");
6417
7258
  try {
6418
7259
  await validateRef2(effectiveWorkdir, name);
6419
7260
  if (sourceBranch) {
@@ -6468,8 +7309,8 @@ All file and git operations should use this branch.
6468
7309
  res.json({ workspaces: workspacesList });
6469
7310
  });
6470
7311
  app.get("/api/sessions", async (req, res) => {
6471
- const { getRecentUserPromptsForSession } = await import("./events-GMHRX4LF.js");
6472
- const { getPendingConfirmationsBySession } = await import("./path-security-YTPZ35SL.js");
7312
+ const { getRecentUserPromptsForSession } = await import("./events-D33QXGSX.js");
7313
+ const { getPendingConfirmationsBySession: getPendingConfirmationsBySession2 } = await import("./path-security-4WQJGU3Z.js");
6473
7314
  const projectId = req.query["projectId"];
6474
7315
  const rawLimit = req.query["limit"];
6475
7316
  const offset = parseInt(req.query["offset"]) || 0;
@@ -6492,7 +7333,7 @@ All file and git operations should use this branch.
6492
7333
  ...session,
6493
7334
  recentUserPrompts: getRecentUserPromptsForSession(session.id, 10)
6494
7335
  }));
6495
- const allPending = getPendingConfirmationsBySession();
7336
+ const allPending = getPendingConfirmationsBySession2();
6496
7337
  const pendingConfirmationsBySession = {};
6497
7338
  for (const s of sessions) {
6498
7339
  const confs = allPending[s.id];
@@ -6524,7 +7365,7 @@ All file and git operations should use this branch.
6524
7365
  disabledServers = mcpManager.getAllServers().filter((s) => s.config.disabled).map((s) => s.name);
6525
7366
  }
6526
7367
  if (disabledServers.length > 0) {
6527
- const { setSessionDisabledServers: setSessionDisabledServers2 } = await import("./session-overrides-4WAD3X5Q.js");
7368
+ const { setSessionDisabledServers: setSessionDisabledServers2 } = await import("./session-overrides-U573CU2V.js");
6528
7369
  setSessionDisabledServers2(session.id, disabledServers);
6529
7370
  }
6530
7371
  } catch {
@@ -6597,22 +7438,22 @@ All file and git operations should use this branch.
6597
7438
  }
6598
7439
  });
6599
7440
  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-WAUWG4PE.js");
6603
- const { getMaxVisibleItems } = await import("./settings-VOWWO4VG.js");
7441
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEventsWithSnapshot2 } = await import("./events-D33QXGSX.js");
7442
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2, foldPendingConfirmations } = await import("./folding-QOKBKUU6.js");
7443
+ const { getPendingQuestionsForSession: getPendingQuestionsForSession2 } = await import("./tools-SSPC3UQZ.js");
7444
+ const { getMaxVisibleItems } = await import("./settings-LMAMF2X7.js");
6604
7445
  const session = sessionManager.getSession(req.params.id);
6605
7446
  if (!session) {
6606
7447
  return res.status(404).json({ error: "Session not found" });
6607
7448
  }
6608
7449
  const eventStore = getEventStore2();
6609
7450
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(req.params.id);
6610
- const events = combineEventsWithSnapshot(req.params.id, snapshot, eventsSinceSnapshot);
7451
+ const events = combineEventsWithSnapshot2(req.params.id, snapshot, eventsSinceSnapshot);
6611
7452
  const maxVisibleItems = req.query["full"] === "true" ? void 0 : getMaxVisibleItems() || void 0;
6612
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems);
7453
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems);
6613
7454
  const contextState = sessionManager.getContextState(req.params.id);
6614
7455
  const queueState = sessionManager.getQueueState(req.params.id);
6615
- const pendingQuestions = getPendingQuestionsForSession(req.params.id);
7456
+ const pendingQuestions = getPendingQuestionsForSession2(req.params.id);
6616
7457
  const pendingConfirmations = foldPendingConfirmations(events);
6617
7458
  const activeWorkflowExecution = sessionManager.getDisplayWorkflowExecution(req.params.id);
6618
7459
  res.json({
@@ -6627,10 +7468,10 @@ All file and git operations should use this branch.
6627
7468
  });
6628
7469
  });
6629
7470
  app.get("/api/sessions/:id/status", async (req, res) => {
6630
- const { projectSessionStatus } = await import("./session-status-6Q5B5PEN.js");
6631
- const { getPendingQuestionsForSession } = await import("./tools-WAUWG4PE.js");
6632
- const { getEventStore: getEventStore2, combineEventsWithSnapshot } = await import("./events-GMHRX4LF.js");
6633
- const { foldPendingConfirmations } = await import("./folding-7K2GRPF4.js");
7471
+ const { projectSessionStatus: projectSessionStatus2 } = await import("./session-status-UFO5IFI7.js");
7472
+ const { getPendingQuestionsForSession: getPendingQuestionsForSession2 } = await import("./tools-SSPC3UQZ.js");
7473
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEventsWithSnapshot2 } = await import("./events-D33QXGSX.js");
7474
+ const { foldPendingConfirmations } = await import("./folding-QOKBKUU6.js");
6634
7475
  const sessionId = req.params["id"];
6635
7476
  if (!sessionId) {
6636
7477
  return res.status(400).json({ error: "Session id is required" });
@@ -6641,12 +7482,12 @@ All file and git operations should use this branch.
6641
7482
  }
6642
7483
  const activeWorkflowExecution = sessionManager.getActiveWorkflowExecution(sessionId);
6643
7484
  const activeWorkflowStepName = activeWorkflowExecution?.currentStepName ?? null;
6644
- const pendingQuestions = getPendingQuestionsForSession(sessionId);
7485
+ const pendingQuestions = getPendingQuestionsForSession2(sessionId);
6645
7486
  const eventStore = getEventStore2();
6646
7487
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6647
- const events = combineEventsWithSnapshot(sessionId, snapshot, eventsSinceSnapshot);
7488
+ const events = combineEventsWithSnapshot2(sessionId, snapshot, eventsSinceSnapshot);
6648
7489
  const pendingConfirmations = foldPendingConfirmations(events);
6649
- const status = projectSessionStatus({
7490
+ const status = projectSessionStatus2({
6650
7491
  session,
6651
7492
  pendingQuestionsCount: pendingQuestions.length,
6652
7493
  pendingConfirmationsCount: pendingConfirmations.length,
@@ -6660,8 +7501,8 @@ All file and git operations should use this branch.
6660
7501
  if (!session) {
6661
7502
  return res.status(404).json({ error: "Session not found" });
6662
7503
  }
6663
- const { stopSessionExecution } = await import("./chat-handler-3O6AIOID.js");
6664
- const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-WAUWG4PE.js");
7504
+ const { stopSessionExecution } = await import("./chat-handler-4WCKNCQV.js");
7505
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-SSPC3UQZ.js");
6665
7506
  sessionManager.clearMessageQueue(sessionId);
6666
7507
  stopSessionExecution(sessionId, sessionManager);
6667
7508
  abortSession(sessionId);
@@ -6690,9 +7531,9 @@ All file and git operations should use this branch.
6690
7531
  res.json({ success: true });
6691
7532
  });
6692
7533
  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");
7534
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-D33QXGSX.js");
7535
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-QOKBKUU6.js");
7536
+ const { getMaxVisibleItems } = await import("./settings-LMAMF2X7.js");
6696
7537
  const sessionId = req.params.id;
6697
7538
  const session = sessionManager.getSession(sessionId);
6698
7539
  if (!session) {
@@ -6709,19 +7550,20 @@ All file and git operations should use this branch.
6709
7550
  const resolvedModel = model ?? provider?.models?.[0]?.id ?? "auto";
6710
7551
  sessionManager.setSessionProvider(sessionId, providerId, resolvedModel, true, reasoningEffort);
6711
7552
  sessionManager.setSessionProviderActive(sessionId, true);
7553
+ await announceContextDrift(sessionManager, [sessionId]);
6712
7554
  const contextState = sessionManager.getContextState(sessionId);
6713
7555
  const eventStore = getEventStore2();
6714
7556
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6715
7557
  const events = combineEv(sessionId, snapshot, eventsSinceSnapshot);
6716
7558
  const maxVisibleItems = getMaxVisibleItems();
6717
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
7559
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems || void 0);
6718
7560
  const updatedSession = sessionManager.getSession(sessionId);
6719
7561
  res.json({ session: toClientSession(updatedSession), messages, hiddenCount, contextState });
6720
7562
  });
6721
7563
  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");
7564
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-D33QXGSX.js");
7565
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-QOKBKUU6.js");
7566
+ const { getMaxVisibleItems } = await import("./settings-LMAMF2X7.js");
6725
7567
  const sessionId = req.params.id;
6726
7568
  const session = sessionManager.getSession(sessionId);
6727
7569
  if (!session) {
@@ -6729,11 +7571,12 @@ All file and git operations should use this branch.
6729
7571
  }
6730
7572
  sessionManager.setSessionProvider(sessionId, null, null, false, null);
6731
7573
  sessionManager.setSessionProviderActive(sessionId, true);
7574
+ await announceContextDrift(sessionManager, [sessionId]);
6732
7575
  const eventStore = getEventStore2();
6733
7576
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6734
7577
  const events = combineEv(sessionId, snapshot, eventsSinceSnapshot);
6735
7578
  const maxVisibleItems = getMaxVisibleItems();
6736
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
7579
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems || void 0);
6737
7580
  const updatedSession = sessionManager.getSession(sessionId);
6738
7581
  res.json({ session: toClientSession(updatedSession), messages, hiddenCount });
6739
7582
  });
@@ -6850,9 +7693,9 @@ All file and git operations should use this branch.
6850
7693
  res.json({ success: true });
6851
7694
  });
6852
7695
  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");
7696
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEv } = await import("./events-D33QXGSX.js");
7697
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-QOKBKUU6.js");
7698
+ const { getMaxVisibleItems } = await import("./settings-LMAMF2X7.js");
6856
7699
  const sessionId = req.params.id;
6857
7700
  const session = sessionManager.getSession(sessionId);
6858
7701
  if (!session) {
@@ -6872,7 +7715,7 @@ All file and git operations should use this branch.
6872
7715
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6873
7716
  const events = combineEv(sessionId, snapshot, eventsSinceSnapshot);
6874
7717
  const maxVisibleItems = getMaxVisibleItems();
6875
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
7718
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems || void 0);
6876
7719
  const updatedSession = sessionManager.getSession(sessionId);
6877
7720
  res.json({ session: toClientSession(updatedSession), messages, hiddenCount });
6878
7721
  });
@@ -6910,7 +7753,15 @@ All file and git operations should use this branch.
6910
7753
  return res.status(400).json({ error: "disabledServers must be an array of strings" });
6911
7754
  }
6912
7755
  setSessionDisabledServers(sessionId, disabledServers);
6913
- sessionManager.setDynamicContextChanged(sessionId, true);
7756
+ const messages = session.messages ?? [];
7757
+ if (messages.length === 0) {
7758
+ const { applyDynamicContext } = await import("./dynamic-context-2VLACYK4.js");
7759
+ const modelName = session.providerModel ?? providerManager.getCurrentModel();
7760
+ await applyDynamicContext(sessionManager, sessionId, modelName);
7761
+ } else {
7762
+ sessionManager.setDynamicContextChanged(sessionId, true);
7763
+ }
7764
+ await announceContextDrift(sessionManager, [sessionId]);
6914
7765
  const state = sessionManager.getContextState(sessionId);
6915
7766
  wssExports.broadcastForSession(sessionId, createContextStateMessage(state));
6916
7767
  res.json({ disabledServers: getSessionDisabledServers(sessionId) });
@@ -6938,7 +7789,7 @@ All file and git operations should use this branch.
6938
7789
  if (alwaysAllow !== void 0 && typeof alwaysAllow !== "boolean") {
6939
7790
  return res.status(400).json({ error: "alwaysAllow must be a boolean if provided" });
6940
7791
  }
6941
- const { providePathConfirmation, getConfirmationSessionId } = await import("./tools-WAUWG4PE.js");
7792
+ const { providePathConfirmation: providePathConfirmation2, getConfirmationSessionId } = await import("./tools-SSPC3UQZ.js");
6942
7793
  const pendingSessionId = getConfirmationSessionId(callId);
6943
7794
  if (!pendingSessionId) {
6944
7795
  return res.status(404).json({ error: "No pending path confirmation with that ID" });
@@ -6946,22 +7797,22 @@ All file and git operations should use this branch.
6946
7797
  if (pendingSessionId !== sessionId) {
6947
7798
  return res.status(403).json({ error: "Confirmation does not belong to this session" });
6948
7799
  }
6949
- const result = providePathConfirmation(callId, approved, alwaysAllow);
7800
+ const result = providePathConfirmation2(callId, approved, alwaysAllow);
6950
7801
  if (!result.found) {
6951
7802
  return res.status(404).json({ error: "No pending path confirmation with that ID" });
6952
7803
  }
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-TLODRZSB.js");
6956
- const { getPendingQuestionsForSession } = await import("./tools-WAUWG4PE.js");
6957
- const { getMaxVisibleItems } = await import("./settings-VOWWO4VG.js");
7804
+ const { getEventStore: getEventStore2, combineEventsWithSnapshot: combineEvents } = await import("./events-D33QXGSX.js");
7805
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2, foldPendingConfirmations } = await import("./folding-QOKBKUU6.js");
7806
+ const { createSessionStateMessage } = await import("./protocol-Y7MRKRUO.js");
7807
+ const { getPendingQuestionsForSession: getPendingQuestionsForSession2 } = await import("./tools-SSPC3UQZ.js");
7808
+ const { getMaxVisibleItems } = await import("./settings-LMAMF2X7.js");
6958
7809
  const eventStore = getEventStore2();
6959
7810
  const { snapshot, events: eventsSinceSnapshot } = eventStore.getEventsSinceSnapshot(sessionId);
6960
7811
  const events = combineEvents(sessionId, snapshot, eventsSinceSnapshot);
6961
7812
  const maxVisibleItems = getMaxVisibleItems();
6962
- const { messages, hiddenCount } = buildMessagesFromStoredEvents(events, maxVisibleItems || void 0);
7813
+ const { messages, hiddenCount } = buildMessagesFromStoredEvents2(events, maxVisibleItems || void 0);
6963
7814
  const pendingConfirmations = foldPendingConfirmations(events);
6964
- const pendingQuestions = getPendingQuestionsForSession(sessionId);
7815
+ const pendingQuestions = getPendingQuestionsForSession2(sessionId);
6965
7816
  const session = sessionManager.getSession(sessionId);
6966
7817
  if (session) {
6967
7818
  const stateMsg = createSessionStateMessage(
@@ -6991,8 +7842,8 @@ All file and git operations should use this branch.
6991
7842
  if (!skip && typeof answer !== "string") {
6992
7843
  return res.status(400).json({ error: "answer is required when not skipping" });
6993
7844
  }
6994
- const { provideAnswer } = await import("./tools-WAUWG4PE.js");
6995
- const found = provideAnswer(callId, answer ?? "", skip ?? false);
7845
+ const { provideAnswer: provideAnswer2 } = await import("./tools-SSPC3UQZ.js");
7846
+ const found = provideAnswer2(callId, answer ?? "", skip ?? false);
6996
7847
  if (!found) {
6997
7848
  return res.status(404).json({ error: "No pending question with that ID" });
6998
7849
  }
@@ -7024,7 +7875,7 @@ All file and git operations should use this branch.
7024
7875
  if (!session) {
7025
7876
  return res.status(404).json({ error: "Session not found" });
7026
7877
  }
7027
- const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-VOWWO4VG.js");
7878
+ const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-LMAMF2X7.js");
7028
7879
  if (getSetting2(SETTINGS_KEYS.CACHE_WARMING) !== "true") {
7029
7880
  return res.json({ success: false, reason: "disabled" });
7030
7881
  }
@@ -7057,7 +7908,7 @@ All file and git operations should use this branch.
7057
7908
  backend: activeProvider?.backend ?? llmClient.getBackend(),
7058
7909
  model: llmClient.getModel()
7059
7910
  };
7060
- const { runAgentTurn, TurnMetrics } = await import("./orchestrator-WLNW7TS7.js");
7911
+ const { runAgentTurn, TurnMetrics } = await import("./orchestrator-PLTAEOMR.js");
7061
7912
  runAgentTurn(
7062
7913
  {
7063
7914
  sessionManager,
@@ -7095,15 +7946,15 @@ All file and git operations should use this branch.
7095
7946
  if (!session) {
7096
7947
  return res.status(404).json({ error: "Session not found" });
7097
7948
  }
7098
- const { stopSessionExecution } = await import("./chat-handler-3O6AIOID.js");
7099
- const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-WAUWG4PE.js");
7949
+ const { stopSessionExecution } = await import("./chat-handler-4WCKNCQV.js");
7950
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-SSPC3UQZ.js");
7100
7951
  const queuedMessages = sessionManager.getQueueState(sessionId);
7101
7952
  sessionManager.clearMessageQueue(sessionId);
7102
7953
  stopSessionExecution(sessionId, sessionManager);
7103
7954
  abortSession(sessionId);
7104
7955
  cancelQuestionsForSession(sessionId, "Session stopped by user");
7105
7956
  cancelPathConfirmationsForSession(sessionId, "Session stopped by user");
7106
- const eventStore = (await import("./events-GMHRX4LF.js")).getEventStore();
7957
+ const eventStore = (await import("./events-D33QXGSX.js")).getEventStore();
7107
7958
  eventStore.append(sessionId, { type: "running.changed", data: { isRunning: false } });
7108
7959
  res.json({ success: true, queuedMessages });
7109
7960
  });
@@ -7117,7 +7968,7 @@ All file and git operations should use this branch.
7117
7968
  if (typeof messageIndex !== "number" || messageIndex < 0) {
7118
7969
  return res.status(400).json({ error: "messageIndex must be a non-negative number" });
7119
7970
  }
7120
- const { truncateSessionMessages } = await import("./events-GMHRX4LF.js");
7971
+ const { truncateSessionMessages } = await import("./events-D33QXGSX.js");
7121
7972
  truncateSessionMessages(sessionId, messageIndex);
7122
7973
  res.json({ success: true });
7123
7974
  });
@@ -7137,11 +7988,11 @@ All file and git operations should use this branch.
7137
7988
  if (attachments !== void 0 && !Array.isArray(attachments)) {
7138
7989
  return res.status(400).json({ error: "attachments must be an array if provided" });
7139
7990
  }
7140
- const { getEventStore: getEventStore2 } = await import("./events-GMHRX4LF.js");
7141
- const { buildMessagesFromStoredEvents } = await import("./folding-7K2GRPF4.js");
7991
+ const { getEventStore: getEventStore2 } = await import("./events-D33QXGSX.js");
7992
+ const { buildMessagesFromStoredEvents: buildMessagesFromStoredEvents2 } = await import("./folding-QOKBKUU6.js");
7142
7993
  const eventStore = getEventStore2();
7143
7994
  const events = eventStore.getEvents(sessionId);
7144
- const { messages } = buildMessagesFromStoredEvents(events);
7995
+ const { messages } = buildMessagesFromStoredEvents2(events);
7145
7996
  const msgIndex = messages.findIndex((m) => m.id === messageId);
7146
7997
  if (msgIndex === -1) {
7147
7998
  return res.status(400).json({ error: "Message not found" });
@@ -7150,7 +8001,7 @@ All file and git operations should use this branch.
7150
8001
  if (msg.role !== "user" || msg.isSystemGenerated) {
7151
8002
  return res.status(400).json({ error: "Can only replay user messages" });
7152
8003
  }
7153
- const { truncateSessionMessages } = await import("./events-GMHRX4LF.js");
8004
+ const { truncateSessionMessages } = await import("./events-D33QXGSX.js");
7154
8005
  truncateSessionMessages(sessionId, msgIndex - 1);
7155
8006
  sessionManager.queueMessage(
7156
8007
  sessionId,
@@ -7212,7 +8063,7 @@ All file and git operations should use this branch.
7212
8063
  res.json({ accepted: true });
7213
8064
  });
7214
8065
  app.get("/api/settings", async (req, res) => {
7215
- const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-VOWWO4VG.js");
8066
+ const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-LMAMF2X7.js");
7216
8067
  const keysParam = req.query["keys"];
7217
8068
  if (!keysParam) {
7218
8069
  return res.status(400).json({ error: "keys query parameter is required" });
@@ -7225,19 +8076,19 @@ All file and git operations should use this branch.
7225
8076
  res.json(result);
7226
8077
  });
7227
8078
  app.get("/api/settings/:key", async (req, res) => {
7228
- const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-VOWWO4VG.js");
8079
+ const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-LMAMF2X7.js");
7229
8080
  const key = req.params.key;
7230
8081
  const value = getSetting2(key) ?? SETTINGS_DEFAULTS[key] ?? null;
7231
8082
  res.json({ key, value });
7232
8083
  });
7233
8084
  app.get("/api/settings/:key", async (req, res) => {
7234
- const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-VOWWO4VG.js");
8085
+ const { getSetting: getSetting2, SETTINGS_DEFAULTS } = await import("./settings-LMAMF2X7.js");
7235
8086
  const key = req.params.key;
7236
8087
  const value = getSetting2(key) ?? SETTINGS_DEFAULTS[key] ?? null;
7237
8088
  res.json({ key, value });
7238
8089
  });
7239
8090
  app.put("/api/settings/:key", async (req, res) => {
7240
- const { setSetting: setSetting2 } = await import("./settings-VOWWO4VG.js");
8091
+ const { setSetting: setSetting2 } = await import("./settings-LMAMF2X7.js");
7241
8092
  const key = req.params.key;
7242
8093
  const { value } = req.body;
7243
8094
  if (value === void 0) {
@@ -7264,7 +8115,7 @@ All file and git operations should use this branch.
7264
8115
  }
7265
8116
  });
7266
8117
  app.get("/api/tools/shells", async (_req, res) => {
7267
- const { listAvailableShells } = await import("./platform-EMKWQTZX.js");
8118
+ const { listAvailableShells } = await import("./platform-MWWJ6OKV.js");
7268
8119
  res.json({ shells: listAvailableShells() });
7269
8120
  });
7270
8121
  app.get("/api/config", async (_req, res) => {
@@ -7338,6 +8189,7 @@ All file and git operations should use this branch.
7338
8189
  ...m.requestBody !== void 0 && { requestBody: m.requestBody },
7339
8190
  ...m.reasoningEfforts !== void 0 && { reasoningEfforts: m.reasoningEfforts },
7340
8191
  ...m.reasoningEffortOverride !== void 0 && { reasoningEffortOverride: m.reasoningEffortOverride },
8192
+ ...m.modes !== void 0 && { modes: m.modes },
7341
8193
  ...m.supportsVision !== void 0 && { supportsVision: m.supportsVision },
7342
8194
  ...m.thinkingEnabled !== void 0 && { thinkingEnabled: m.thinkingEnabled },
7343
8195
  ...m.thinkingLevel !== void 0 && { thinkingLevel: m.thinkingLevel },
@@ -7356,7 +8208,7 @@ All file and git operations should use this branch.
7356
8208
  }));
7357
8209
  }
7358
8210
  app.post("/api/proxy/test", async (_req, res) => {
7359
- const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-VOWWO4VG.js");
8211
+ const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-LMAMF2X7.js");
7360
8212
  const proxyUrl = getSetting2(SETTINGS_KEYS.PROXY_URL);
7361
8213
  if (!proxyUrl) {
7362
8214
  return res.status(400).json({ success: false, error: "No proxy URL configured" });
@@ -7387,7 +8239,7 @@ All file and git operations should use this branch.
7387
8239
  res.json({
7388
8240
  success: true,
7389
8241
  url,
7390
- backend: reqBackend || "unknown",
8242
+ backend: reqBackend !== "unknown" && reqBackend ? reqBackend : detectBackendFromUrl(url) ?? "unknown",
7391
8243
  model
7392
8244
  });
7393
8245
  } catch (error) {
@@ -7448,7 +8300,7 @@ All file and git operations should use this branch.
7448
8300
  }
7449
8301
  });
7450
8302
  app.post("/api/proxy/test", async (_req, res) => {
7451
- const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-VOWWO4VG.js");
8303
+ const { getSetting: getSetting2, SETTINGS_KEYS } = await import("./settings-LMAMF2X7.js");
7452
8304
  const proxyUrl = getSetting2(SETTINGS_KEYS.PROXY_URL);
7453
8305
  if (!proxyUrl) {
7454
8306
  return res.status(400).json({ success: false, error: "No proxy URL configured" });
@@ -7475,9 +8327,9 @@ All file and git operations should use this branch.
7475
8327
  const backend = req.query["backend"];
7476
8328
  if (!url) return res.status(400).json({ error: "url is required" });
7477
8329
  try {
7478
- const { fetchModelsWithContext } = await import("./provider-manager-FVI22EWM.js");
7479
- const { getModelProfile: getModelProfile2 } = await import("./profiles-HXGJIPF2.js");
7480
- const { getCatalogEntry } = await import("./model-catalog-ZLOWTQA7.js");
8330
+ const { fetchModelsWithContext } = await import("./provider-manager-7NL5EQLG.js");
8331
+ const { getModelProfile: getModelProfile2 } = await import("./profiles-EBCVFYG7.js");
8332
+ const { getCatalogEntry } = await import("./model-catalog-2PCKTYGW.js");
7481
8333
  const models = await fetchModelsWithContext(
7482
8334
  url,
7483
8335
  apiKey,
@@ -7493,6 +8345,7 @@ All file and git operations should use this branch.
7493
8345
  return {
7494
8346
  id: m.id,
7495
8347
  contextWindow: m.contextWindow,
8348
+ supportsVision: m.supportsVision ?? profile.supportsVision,
7496
8349
  defaultTemperature: profile.temperature,
7497
8350
  defaultTopP: profile.topP,
7498
8351
  defaultTopK: profile.topK,
@@ -7514,7 +8367,7 @@ All file and git operations should use this branch.
7514
8367
  if (!url) return res.status(400).json({ error: "url is required" });
7515
8368
  if (!models?.length) return res.status(400).json({ error: "models is required" });
7516
8369
  try {
7517
- const { autoConfig } = await import("./auto-config-454YE6KC.js");
8370
+ const { autoConfig } = await import("./auto-config-5VSNLVNI.js");
7518
8371
  const result = await autoConfig({
7519
8372
  url,
7520
8373
  ...apiKey ? { apiKey } : {},
@@ -7546,10 +8399,10 @@ All file and git operations should use this branch.
7546
8399
  });
7547
8400
  return res.json({ success: true, message: { content: response2.content }, raw: response2 });
7548
8401
  }
7549
- const { getModelProfile: getModelProfile2 } = await import("./profiles-HXGJIPF2.js");
7550
- const { getBackendCapabilities } = await import("./backend-4YPEC3J2.js");
7551
- const { buildNonStreamingCreateParams } = await import("./client-pure-CJKM3ACP.js");
7552
- const { OpenAIHttpClient } = await import("./http-client-ROVNSHF6.js");
8402
+ const { getModelProfile: getModelProfile2 } = await import("./profiles-EBCVFYG7.js");
8403
+ const { getBackendCapabilities } = await import("./backend-NDBY3DAQ.js");
8404
+ const { buildNonStreamingCreateParams } = await import("./client-pure-FLKP6PTI.js");
8405
+ const { OpenAIHttpClient } = await import("./http-client-MGAJTGPR.js");
7553
8406
  const { ensureVersionPrefix } = await import("./url-utils-C5BM7NF7.js");
7554
8407
  const profile = getModelProfile2(model);
7555
8408
  const capabilities = getBackendCapabilities(backend || "unknown");
@@ -7565,7 +8418,7 @@ All file and git operations should use this branch.
7565
8418
  modelSettings["queryParams"] = JSON.parse(rawQP);
7566
8419
  } else {
7567
8420
  const modeEnabled = mode === "thinking" ? modelConfig?.thinkingEnabled : modelConfig?.nonThinkingEnabled;
7568
- if (modeEnabled) {
8421
+ if (modeEnabled && capabilities.supportsChatTemplateKwargs) {
7569
8422
  modelSettings["chatTemplateKwargs"] = mode === "thinking" ? { enable_thinking: true } : { enable_thinking: false };
7570
8423
  }
7571
8424
  }
@@ -7622,7 +8475,7 @@ All file and git operations should use this branch.
7622
8475
  try {
7623
8476
  const { loadGlobalConfig, saveGlobalConfig, addProvider, setDefaultModelSelection } = await import("./config-LOK3LXFH.js");
7624
8477
  const globalConfig = await loadGlobalConfig(config3.mode ?? "production", config3.globalConfigPath);
7625
- const providerBackend = backend;
8478
+ const providerBackend = backend === "unknown" ? detectBackendFromUrl(url) ?? "unknown" : backend;
7626
8479
  const providerModels = modelConfigs?.length ? buildModelConfigs(modelConfigs) : model ? [{ id: model, contextWindow: 2e5, source: "user" }] : [];
7627
8480
  const configWithProvider = addProvider(globalConfig, {
7628
8481
  name,
@@ -7679,15 +8532,15 @@ All file and git operations should use this branch.
7679
8532
  }
7680
8533
  });
7681
8534
  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()
8535
+ const { z: z2 } = await import("zod");
8536
+ const visionFallbackUpdateSchema = z2.object({
8537
+ enabled: z2.boolean().optional(),
8538
+ url: z2.string().optional(),
8539
+ model: z2.string().optional(),
8540
+ timeout: z2.number().positive().optional(),
8541
+ backend: z2.enum(["ollama", "openai"]).optional(),
8542
+ providerModelRef: z2.string().optional(),
8543
+ apiKey: z2.string().optional()
7691
8544
  });
7692
8545
  const parsed = visionFallbackUpdateSchema.safeParse(req.body);
7693
8546
  if (!parsed.success) {
@@ -7722,14 +8575,14 @@ All file and git operations should use this branch.
7722
8575
  }
7723
8576
  });
7724
8577
  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()
8578
+ const { z: z2 } = await import("zod");
8579
+ const testSchema = z2.object({
8580
+ url: z2.string().optional(),
8581
+ model: z2.string().optional(),
8582
+ backend: z2.enum(["ollama", "openai"]).optional(),
8583
+ providerModelRef: z2.string().optional(),
8584
+ timeout: z2.number().positive().optional(),
8585
+ apiKey: z2.string().optional()
7733
8586
  });
7734
8587
  const parsed = testSchema.safeParse(req.body);
7735
8588
  if (!parsed.success) {
@@ -7966,7 +8819,7 @@ All file and git operations should use this branch.
7966
8819
  });
7967
8820
  async function rebuildMcpTools() {
7968
8821
  const { createMcpTools: createMcpTools2 } = await import("./tool-adapter-FJAFLDZA.js");
7969
- const { setMcpTools: setMcpTools2 } = await import("./tools-WAUWG4PE.js");
8822
+ const { setMcpTools: setMcpTools2 } = await import("./tools-SSPC3UQZ.js");
7970
8823
  const mcpTools = createMcpTools2(mcpManager);
7971
8824
  setMcpTools2(mcpTools);
7972
8825
  toolRegistry = createToolRegistry();
@@ -8058,6 +8911,10 @@ All file and git operations should use this branch.
8058
8911
  for (const s of sessions) {
8059
8912
  sessionManager.setDynamicContextChanged(s.id, true);
8060
8913
  }
8914
+ await announceContextDrift(
8915
+ sessionManager,
8916
+ sessions.map((s) => s.id)
8917
+ );
8061
8918
  }
8062
8919
  const allServers = mcpManager.getAllServers();
8063
8920
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: allServers }));
@@ -8136,6 +8993,10 @@ All file and git operations should use this branch.
8136
8993
  for (const s of sessions) {
8137
8994
  sessionManager.setDynamicContextChanged(s.id, true);
8138
8995
  }
8996
+ await announceContextDrift(
8997
+ sessionManager,
8998
+ sessions.map((s) => s.id)
8999
+ );
8139
9000
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: mcpManager.getAllServers() }));
8140
9001
  res.json({ server: mcpManager.getServer(name) });
8141
9002
  } catch (error) {
@@ -8168,11 +9029,15 @@ All file and git operations should use this branch.
8168
9029
  for (const s of sessions) {
8169
9030
  sessionManager.setDynamicContextChanged(s.id, true);
8170
9031
  }
9032
+ await announceContextDrift(
9033
+ sessionManager,
9034
+ sessions.map((s) => s.id)
9035
+ );
8171
9036
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: mcpManager.getAllServers() }));
8172
9037
  res.json({ success: true });
8173
9038
  });
8174
9039
  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>`;
9040
+ 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
9041
  }
8177
9042
  function parseOAuthResponse(value) {
8178
9043
  let params;
@@ -8189,14 +9054,19 @@ All file and git operations should use this branch.
8189
9054
  async function applyMcpOAuthResult(name) {
8190
9055
  await mcpManager.connectServer(name);
8191
9056
  await rebuildMcpTools();
8192
- for (const s of sessionManager.listSessions()) {
9057
+ const sessions = sessionManager.listSessions();
9058
+ for (const s of sessions) {
8193
9059
  sessionManager.setDynamicContextChanged(s.id, true);
8194
9060
  }
9061
+ await announceContextDrift(
9062
+ sessionManager,
9063
+ sessions.map((s) => s.id)
9064
+ );
8195
9065
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: mcpManager.getAllServers() }));
8196
9066
  }
8197
9067
  async function completeMcpOAuth(name, serverUrl, code) {
8198
9068
  const { auth } = await import("@modelcontextprotocol/sdk/client/auth.js");
8199
- const { McpOAuthProvider: McpOAuthProvider2 } = await import("./oauth-provider-DGBC2YM6.js");
9069
+ const { McpOAuthProvider: McpOAuthProvider2 } = await import("./oauth-provider-VHUS2KEG.js");
8200
9070
  const { clearMcpOAuthAuthorizationState } = await import("./oauth-store-KECHUT54.js");
8201
9071
  const provider = new McpOAuthProvider2(name, serverUrl);
8202
9072
  try {
@@ -8229,8 +9099,9 @@ All file and git operations should use this branch.
8229
9099
  }
8230
9100
  try {
8231
9101
  const { auth } = await import("@modelcontextprotocol/sdk/client/auth.js");
8232
- const { McpOAuthProvider: McpOAuthProvider2 } = await import("./oauth-provider-DGBC2YM6.js");
9102
+ const { McpOAuthProvider: McpOAuthProvider2, rejectStaleOAuthClient } = await import("./oauth-provider-VHUS2KEG.js");
8233
9103
  const provider = new McpOAuthProvider2(name, server.config.url);
9104
+ await rejectStaleOAuthClient(provider);
8234
9105
  const result = await auth(provider, { serverUrl: server.config.url });
8235
9106
  if (result === "AUTHORIZED") {
8236
9107
  await applyMcpOAuthResult(name);
@@ -8286,8 +9157,8 @@ All file and git operations should use this branch.
8286
9157
  if (!parsed.code || !parsed.state) {
8287
9158
  return res.status(400).json({ error: "Paste the whole callback URL, both code and state are needed" });
8288
9159
  }
8289
- const { readMcpOAuthEntry } = await import("./oauth-store-KECHUT54.js");
8290
- const entry = await readMcpOAuthEntry(name, serverUrl);
9160
+ const { readMcpOAuthEntry: readMcpOAuthEntry2 } = await import("./oauth-store-KECHUT54.js");
9161
+ const entry = await readMcpOAuthEntry2(name, serverUrl);
8291
9162
  if (!entry?.state || entry.state !== parsed.state) {
8292
9163
  return res.status(400).json({ error: "This callback does not match the pending authorization" });
8293
9164
  }
@@ -8341,6 +9212,10 @@ All file and git operations should use this branch.
8341
9212
  for (const s of sessions) {
8342
9213
  sessionManager.setDynamicContextChanged(s.id, true);
8343
9214
  }
9215
+ await announceContextDrift(
9216
+ sessionManager,
9217
+ sessions.map((s) => s.id)
9218
+ );
8344
9219
  wssExports.broadcastAll(createServerMessage("mcp.servers.changed", { servers: mcpManager.getAllServers() }));
8345
9220
  res.json({ success: true });
8346
9221
  } catch (error) {
@@ -8374,13 +9249,13 @@ All file and git operations should use this branch.
8374
9249
  })
8375
9250
  );
8376
9251
  app.get("/api/sessions/:id/background-processes", async (req, res) => {
8377
- const { getSessionProcesses } = await import("./manager-NOKJQTTF.js");
9252
+ const { getSessionProcesses } = await import("./manager-VRHX7BVI.js");
8378
9253
  const sessionId = req.params.id;
8379
9254
  const processes = getSessionProcesses(sessionId);
8380
9255
  res.json({ processes });
8381
9256
  });
8382
9257
  app.post("/api/sessions/:id/background-process/:processId/stop", async (req, res) => {
8383
- const { stopProcess } = await import("./manager-NOKJQTTF.js");
9258
+ const { stopProcess } = await import("./manager-VRHX7BVI.js");
8384
9259
  const sessionId = req.params.id;
8385
9260
  const processId = req.params.processId;
8386
9261
  const session = sessionManager.getSession(sessionId);
@@ -8520,7 +9395,7 @@ All file and git operations should use this branch.
8520
9395
  );
8521
9396
  const wss = wssExports.wss;
8522
9397
  deferTasksBroadcast = (projectId, payload) => wssExports.broadcastForProject(projectId, "", { type: "tasks.update", payload });
8523
- const { launchWorkflowRun, abortRunnerRun } = await import("./launch-AKIMGAGC.js");
9398
+ const { launchWorkflowRun, abortRunnerRun } = await import("./launch-KFENN5GS.js");
8524
9399
  deferTasksLaunchWorkflow = (sessionId, launch) => {
8525
9400
  const effective = sessionManager.resolveEffectiveProviderModel(sessionId);
8526
9401
  let llmClient = getLLMClient();
@@ -8547,9 +9422,14 @@ All file and git operations should use this branch.
8547
9422
  broadcastForSession: wssExports.broadcastForSession
8548
9423
  },
8549
9424
  {
8550
- workflowId: launch.workflowId,
9425
+ ...launch.workflowId ? { workflowId: launch.workflowId } : {},
8551
9426
  ...launch.params && Object.keys(launch.params).length > 0 ? { params: launch.params } : {},
9427
+ ...launch.subGroup ? { subGroup: launch.subGroup } : {},
8552
9428
  ...launch.scope ? { scope: launch.scope } : {},
9429
+ ...launch.resumeFrom ? { resumeFrom: launch.resumeFrom } : {},
9430
+ ...launch.stepOutput ? { stepOutput: launch.stepOutput } : {},
9431
+ ...launch.userChoice ? { userChoice: launch.userChoice } : {},
9432
+ ...launch.content ? { content: launch.content } : {},
8553
9433
  ...launch.attachments && launch.attachments.length > 0 ? { attachments: launch.attachments } : {}
8554
9434
  }
8555
9435
  );
@@ -8560,7 +9440,7 @@ All file and git operations should use this branch.
8560
9440
  const state = sessionManager.getContextState(sessionId);
8561
9441
  wssExports.broadcastForSession(sessionId, createContextStateMessage(state));
8562
9442
  });
8563
- const { QueueProcessor } = await import("./processor-ZMXU5MOT.js");
9443
+ const { QueueProcessor } = await import("./processor-K4WHZKE6.js");
8564
9444
  const queueProcessor = new QueueProcessor({
8565
9445
  sessionManager,
8566
9446
  providerManager,
@@ -8581,6 +9461,80 @@ All file and git operations should use this branch.
8581
9461
  }
8582
9462
  return aborted;
8583
9463
  };
9464
+ mcpToolDeps = {
9465
+ sessionManager,
9466
+ listProjects: () => listProjects(),
9467
+ createProject: async (name, workdir) => {
9468
+ const { createDirectoryWithGit } = await import("./project-creator-JF3YVQAH.js");
9469
+ return createDirectoryWithGit(name, workdir);
9470
+ },
9471
+ deleteProject: (projectId) => {
9472
+ const project = getProject(projectId);
9473
+ if (!project) return false;
9474
+ deleteProject(projectId);
9475
+ return true;
9476
+ },
9477
+ listWorkflows: (projectDir2) => listAvailableWorkflows(getGlobalConfigDir(config3.mode ?? "production"), projectDir2),
9478
+ topLevelAgentIds: async (workdir) => {
9479
+ const agents = await loadAllAgentsDefault(workdir);
9480
+ return getTopLevelAgents(agents).map((a) => a.metadata.id);
9481
+ },
9482
+ launchWorkflow: (sessionId, launch) => deferTasksLaunchWorkflow(sessionId, launch),
9483
+ stopSession: (sessionId) => {
9484
+ void (async () => {
9485
+ const { stopSessionExecution } = await import("./chat-handler-4WCKNCQV.js");
9486
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-SSPC3UQZ.js");
9487
+ sessionManager.clearMessageQueue(sessionId);
9488
+ stopSessionExecution(sessionId, sessionManager);
9489
+ abortSession(sessionId);
9490
+ cancelQuestionsForSession(sessionId, "Session stopped by user");
9491
+ cancelPathConfirmationsForSession(sessionId, "Session stopped by user");
9492
+ getEventStore().append(sessionId, { type: "running.changed", data: { isRunning: false } });
9493
+ })().catch((error) => {
9494
+ logger.error(`MCP stopSession failed for ${sessionId}`, {
9495
+ error: error instanceof Error ? error.message : String(error)
9496
+ });
9497
+ });
9498
+ },
9499
+ stopWorkflow: (sessionId) => {
9500
+ const runningAborted = abortRunnerRun(sessionId);
9501
+ if (runningAborted) return { aborted: "running" };
9502
+ const execution = sessionManager.getActiveWorkflowExecution(sessionId);
9503
+ if (execution && execution.status === "waiting") {
9504
+ sessionManager.cancelWorkflow(
9505
+ sessionId,
9506
+ execution.id,
9507
+ execution.workflowId,
9508
+ execution.workflowName,
9509
+ execution.workflowColor
9510
+ );
9511
+ return { aborted: "paused" };
9512
+ }
9513
+ return null;
9514
+ },
9515
+ answerQuestion: (callId, answer, skip) => provideAnswer(callId, answer, skip),
9516
+ pendingQuestions: (sessionId) => getPendingQuestionsForSession(sessionId),
9517
+ confirmPath: (callId, approved, alwaysAllow) => providePathConfirmation(callId, approved, alwaysAllow).found,
9518
+ pendingConfirmations: (sessionId) => getPendingConfirmationsBySession()[sessionId] ?? [],
9519
+ setMetadataEntries: (sessionId, key, entries) => sessionManager.setMetadataEntries(sessionId, key, entries),
9520
+ recentMessages: (sessionId, limit) => {
9521
+ const eventStore = getEventStore();
9522
+ const { snapshot, events } = eventStore.getEventsSinceSnapshot(sessionId);
9523
+ const combined = combineEventsWithSnapshot(sessionId, snapshot, events);
9524
+ return buildMessagesFromStoredEvents(combined, Math.max(1, Math.min(limit, 50)));
9525
+ }
9526
+ };
9527
+ let mcpActualPort = null;
9528
+ setMcpBootstrapForTools(async () => {
9529
+ const authRequired = mcpAuth.isAuthRequired();
9530
+ const sessionToken = authRequired ? await currentSessionToken() : null;
9531
+ return buildOpenFoxMcpBootstrap({
9532
+ host: config3.server.host ?? "127.0.0.1",
9533
+ port: mcpActualPort ?? config3.server.port,
9534
+ authRequired,
9535
+ sessionToken
9536
+ });
9537
+ });
8584
9538
  return {
8585
9539
  httpServer,
8586
9540
  ctx: { config: config3, sessionManager, llmClient: getLLMClient(), toolRegistry, providerManager },
@@ -8591,9 +9545,13 @@ All file and git operations should use this branch.
8591
9545
  const addr = httpServer.address();
8592
9546
  const actualPort = typeof addr === "object" && addr ? addr.port : listenPort;
8593
9547
  setMcpOAuthServerPort(actualPort);
9548
+ mcpActualPort = actualPort;
8594
9549
  const client = getLLMClient();
8595
9550
  logger.info(`OpenFox server running at http://${host}:${actualPort}`);
8596
9551
  logger.info(`WebSocket available at ws://${host}:${actualPort}/ws`);
9552
+ logger.info(
9553
+ `MCP endpoint available at http://${host}:${actualPort}/mcp \u2014 run 'openfox mcp' for a paste-ready client config`
9554
+ );
8597
9555
  logger.info(`LLM backend: ${client.getBackend()}, model: ${client.getModel()}, url: ${config3.llm.baseUrl}`);
8598
9556
  resolve9({ port: actualPort });
8599
9557
  });
@@ -8646,4 +9604,4 @@ export {
8646
9604
  createServerHandle,
8647
9605
  createServer
8648
9606
  };
8649
- //# sourceMappingURL=chunk-4MXY7ELN.js.map
9607
+ //# sourceMappingURL=chunk-DI5GTIAU.js.map