gobby 0.2.5__py3-none-any.whl

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 (383) hide show
  1. gobby/__init__.py +3 -0
  2. gobby/adapters/__init__.py +30 -0
  3. gobby/adapters/base.py +93 -0
  4. gobby/adapters/claude_code.py +276 -0
  5. gobby/adapters/codex.py +1292 -0
  6. gobby/adapters/gemini.py +343 -0
  7. gobby/agents/__init__.py +37 -0
  8. gobby/agents/codex_session.py +120 -0
  9. gobby/agents/constants.py +112 -0
  10. gobby/agents/context.py +362 -0
  11. gobby/agents/definitions.py +133 -0
  12. gobby/agents/gemini_session.py +111 -0
  13. gobby/agents/registry.py +618 -0
  14. gobby/agents/runner.py +968 -0
  15. gobby/agents/session.py +259 -0
  16. gobby/agents/spawn.py +916 -0
  17. gobby/agents/spawners/__init__.py +77 -0
  18. gobby/agents/spawners/base.py +142 -0
  19. gobby/agents/spawners/cross_platform.py +266 -0
  20. gobby/agents/spawners/embedded.py +225 -0
  21. gobby/agents/spawners/headless.py +226 -0
  22. gobby/agents/spawners/linux.py +125 -0
  23. gobby/agents/spawners/macos.py +277 -0
  24. gobby/agents/spawners/windows.py +308 -0
  25. gobby/agents/tty_config.py +319 -0
  26. gobby/autonomous/__init__.py +32 -0
  27. gobby/autonomous/progress_tracker.py +447 -0
  28. gobby/autonomous/stop_registry.py +269 -0
  29. gobby/autonomous/stuck_detector.py +383 -0
  30. gobby/cli/__init__.py +67 -0
  31. gobby/cli/__main__.py +8 -0
  32. gobby/cli/agents.py +529 -0
  33. gobby/cli/artifacts.py +266 -0
  34. gobby/cli/daemon.py +329 -0
  35. gobby/cli/extensions.py +526 -0
  36. gobby/cli/github.py +263 -0
  37. gobby/cli/init.py +53 -0
  38. gobby/cli/install.py +614 -0
  39. gobby/cli/installers/__init__.py +37 -0
  40. gobby/cli/installers/antigravity.py +65 -0
  41. gobby/cli/installers/claude.py +363 -0
  42. gobby/cli/installers/codex.py +192 -0
  43. gobby/cli/installers/gemini.py +294 -0
  44. gobby/cli/installers/git_hooks.py +377 -0
  45. gobby/cli/installers/shared.py +737 -0
  46. gobby/cli/linear.py +250 -0
  47. gobby/cli/mcp.py +30 -0
  48. gobby/cli/mcp_proxy.py +698 -0
  49. gobby/cli/memory.py +304 -0
  50. gobby/cli/merge.py +384 -0
  51. gobby/cli/projects.py +79 -0
  52. gobby/cli/sessions.py +622 -0
  53. gobby/cli/tasks/__init__.py +30 -0
  54. gobby/cli/tasks/_utils.py +658 -0
  55. gobby/cli/tasks/ai.py +1025 -0
  56. gobby/cli/tasks/commits.py +169 -0
  57. gobby/cli/tasks/crud.py +685 -0
  58. gobby/cli/tasks/deps.py +135 -0
  59. gobby/cli/tasks/labels.py +63 -0
  60. gobby/cli/tasks/main.py +273 -0
  61. gobby/cli/tasks/search.py +178 -0
  62. gobby/cli/tui.py +34 -0
  63. gobby/cli/utils.py +513 -0
  64. gobby/cli/workflows.py +927 -0
  65. gobby/cli/worktrees.py +481 -0
  66. gobby/config/__init__.py +129 -0
  67. gobby/config/app.py +551 -0
  68. gobby/config/extensions.py +167 -0
  69. gobby/config/features.py +472 -0
  70. gobby/config/llm_providers.py +98 -0
  71. gobby/config/logging.py +66 -0
  72. gobby/config/mcp.py +346 -0
  73. gobby/config/persistence.py +247 -0
  74. gobby/config/servers.py +141 -0
  75. gobby/config/sessions.py +250 -0
  76. gobby/config/tasks.py +784 -0
  77. gobby/hooks/__init__.py +104 -0
  78. gobby/hooks/artifact_capture.py +213 -0
  79. gobby/hooks/broadcaster.py +243 -0
  80. gobby/hooks/event_handlers.py +723 -0
  81. gobby/hooks/events.py +218 -0
  82. gobby/hooks/git.py +169 -0
  83. gobby/hooks/health_monitor.py +171 -0
  84. gobby/hooks/hook_manager.py +856 -0
  85. gobby/hooks/hook_types.py +575 -0
  86. gobby/hooks/plugins.py +813 -0
  87. gobby/hooks/session_coordinator.py +396 -0
  88. gobby/hooks/verification_runner.py +268 -0
  89. gobby/hooks/webhooks.py +339 -0
  90. gobby/install/claude/commands/gobby/bug.md +51 -0
  91. gobby/install/claude/commands/gobby/chore.md +51 -0
  92. gobby/install/claude/commands/gobby/epic.md +52 -0
  93. gobby/install/claude/commands/gobby/eval.md +235 -0
  94. gobby/install/claude/commands/gobby/feat.md +49 -0
  95. gobby/install/claude/commands/gobby/nit.md +52 -0
  96. gobby/install/claude/commands/gobby/ref.md +52 -0
  97. gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
  98. gobby/install/claude/hooks/hook_dispatcher.py +364 -0
  99. gobby/install/claude/hooks/validate_settings.py +102 -0
  100. gobby/install/claude/hooks-template.json +118 -0
  101. gobby/install/codex/hooks/hook_dispatcher.py +153 -0
  102. gobby/install/codex/prompts/forget.md +7 -0
  103. gobby/install/codex/prompts/memories.md +7 -0
  104. gobby/install/codex/prompts/recall.md +7 -0
  105. gobby/install/codex/prompts/remember.md +13 -0
  106. gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
  107. gobby/install/gemini/hooks-template.json +138 -0
  108. gobby/install/shared/plugins/code_guardian.py +456 -0
  109. gobby/install/shared/plugins/example_notify.py +331 -0
  110. gobby/integrations/__init__.py +10 -0
  111. gobby/integrations/github.py +145 -0
  112. gobby/integrations/linear.py +145 -0
  113. gobby/llm/__init__.py +40 -0
  114. gobby/llm/base.py +120 -0
  115. gobby/llm/claude.py +578 -0
  116. gobby/llm/claude_executor.py +503 -0
  117. gobby/llm/codex.py +322 -0
  118. gobby/llm/codex_executor.py +513 -0
  119. gobby/llm/executor.py +316 -0
  120. gobby/llm/factory.py +34 -0
  121. gobby/llm/gemini.py +258 -0
  122. gobby/llm/gemini_executor.py +339 -0
  123. gobby/llm/litellm.py +287 -0
  124. gobby/llm/litellm_executor.py +303 -0
  125. gobby/llm/resolver.py +499 -0
  126. gobby/llm/service.py +236 -0
  127. gobby/mcp_proxy/__init__.py +29 -0
  128. gobby/mcp_proxy/actions.py +175 -0
  129. gobby/mcp_proxy/daemon_control.py +198 -0
  130. gobby/mcp_proxy/importer.py +436 -0
  131. gobby/mcp_proxy/lazy.py +325 -0
  132. gobby/mcp_proxy/manager.py +798 -0
  133. gobby/mcp_proxy/metrics.py +609 -0
  134. gobby/mcp_proxy/models.py +139 -0
  135. gobby/mcp_proxy/registries.py +215 -0
  136. gobby/mcp_proxy/schema_hash.py +381 -0
  137. gobby/mcp_proxy/semantic_search.py +706 -0
  138. gobby/mcp_proxy/server.py +549 -0
  139. gobby/mcp_proxy/services/__init__.py +0 -0
  140. gobby/mcp_proxy/services/fallback.py +306 -0
  141. gobby/mcp_proxy/services/recommendation.py +224 -0
  142. gobby/mcp_proxy/services/server_mgmt.py +214 -0
  143. gobby/mcp_proxy/services/system.py +72 -0
  144. gobby/mcp_proxy/services/tool_filter.py +231 -0
  145. gobby/mcp_proxy/services/tool_proxy.py +309 -0
  146. gobby/mcp_proxy/stdio.py +565 -0
  147. gobby/mcp_proxy/tools/__init__.py +27 -0
  148. gobby/mcp_proxy/tools/agents.py +1103 -0
  149. gobby/mcp_proxy/tools/artifacts.py +207 -0
  150. gobby/mcp_proxy/tools/hub.py +335 -0
  151. gobby/mcp_proxy/tools/internal.py +337 -0
  152. gobby/mcp_proxy/tools/memory.py +543 -0
  153. gobby/mcp_proxy/tools/merge.py +422 -0
  154. gobby/mcp_proxy/tools/metrics.py +283 -0
  155. gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
  156. gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
  157. gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
  158. gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
  159. gobby/mcp_proxy/tools/orchestration/review.py +736 -0
  160. gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
  161. gobby/mcp_proxy/tools/session_messages.py +1056 -0
  162. gobby/mcp_proxy/tools/task_dependencies.py +219 -0
  163. gobby/mcp_proxy/tools/task_expansion.py +591 -0
  164. gobby/mcp_proxy/tools/task_github.py +393 -0
  165. gobby/mcp_proxy/tools/task_linear.py +379 -0
  166. gobby/mcp_proxy/tools/task_orchestration.py +77 -0
  167. gobby/mcp_proxy/tools/task_readiness.py +522 -0
  168. gobby/mcp_proxy/tools/task_sync.py +351 -0
  169. gobby/mcp_proxy/tools/task_validation.py +843 -0
  170. gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
  171. gobby/mcp_proxy/tools/tasks/_context.py +112 -0
  172. gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
  173. gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
  174. gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
  175. gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
  176. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
  177. gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
  178. gobby/mcp_proxy/tools/tasks/_search.py +215 -0
  179. gobby/mcp_proxy/tools/tasks/_session.py +125 -0
  180. gobby/mcp_proxy/tools/workflows.py +973 -0
  181. gobby/mcp_proxy/tools/worktrees.py +1264 -0
  182. gobby/mcp_proxy/transports/__init__.py +0 -0
  183. gobby/mcp_proxy/transports/base.py +95 -0
  184. gobby/mcp_proxy/transports/factory.py +44 -0
  185. gobby/mcp_proxy/transports/http.py +139 -0
  186. gobby/mcp_proxy/transports/stdio.py +213 -0
  187. gobby/mcp_proxy/transports/websocket.py +136 -0
  188. gobby/memory/backends/__init__.py +116 -0
  189. gobby/memory/backends/mem0.py +408 -0
  190. gobby/memory/backends/memu.py +485 -0
  191. gobby/memory/backends/null.py +111 -0
  192. gobby/memory/backends/openmemory.py +537 -0
  193. gobby/memory/backends/sqlite.py +304 -0
  194. gobby/memory/context.py +87 -0
  195. gobby/memory/manager.py +1001 -0
  196. gobby/memory/protocol.py +451 -0
  197. gobby/memory/search/__init__.py +66 -0
  198. gobby/memory/search/text.py +127 -0
  199. gobby/memory/viz.py +258 -0
  200. gobby/prompts/__init__.py +13 -0
  201. gobby/prompts/defaults/expansion/system.md +119 -0
  202. gobby/prompts/defaults/expansion/user.md +48 -0
  203. gobby/prompts/defaults/external_validation/agent.md +72 -0
  204. gobby/prompts/defaults/external_validation/external.md +63 -0
  205. gobby/prompts/defaults/external_validation/spawn.md +83 -0
  206. gobby/prompts/defaults/external_validation/system.md +6 -0
  207. gobby/prompts/defaults/features/import_mcp.md +22 -0
  208. gobby/prompts/defaults/features/import_mcp_github.md +17 -0
  209. gobby/prompts/defaults/features/import_mcp_search.md +16 -0
  210. gobby/prompts/defaults/features/recommend_tools.md +32 -0
  211. gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
  212. gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
  213. gobby/prompts/defaults/features/server_description.md +20 -0
  214. gobby/prompts/defaults/features/server_description_system.md +6 -0
  215. gobby/prompts/defaults/features/task_description.md +31 -0
  216. gobby/prompts/defaults/features/task_description_system.md +6 -0
  217. gobby/prompts/defaults/features/tool_summary.md +17 -0
  218. gobby/prompts/defaults/features/tool_summary_system.md +6 -0
  219. gobby/prompts/defaults/research/step.md +58 -0
  220. gobby/prompts/defaults/validation/criteria.md +47 -0
  221. gobby/prompts/defaults/validation/validate.md +38 -0
  222. gobby/prompts/loader.py +346 -0
  223. gobby/prompts/models.py +113 -0
  224. gobby/py.typed +0 -0
  225. gobby/runner.py +488 -0
  226. gobby/search/__init__.py +23 -0
  227. gobby/search/protocol.py +104 -0
  228. gobby/search/tfidf.py +232 -0
  229. gobby/servers/__init__.py +7 -0
  230. gobby/servers/http.py +636 -0
  231. gobby/servers/models.py +31 -0
  232. gobby/servers/routes/__init__.py +23 -0
  233. gobby/servers/routes/admin.py +416 -0
  234. gobby/servers/routes/dependencies.py +118 -0
  235. gobby/servers/routes/mcp/__init__.py +24 -0
  236. gobby/servers/routes/mcp/hooks.py +135 -0
  237. gobby/servers/routes/mcp/plugins.py +121 -0
  238. gobby/servers/routes/mcp/tools.py +1337 -0
  239. gobby/servers/routes/mcp/webhooks.py +159 -0
  240. gobby/servers/routes/sessions.py +582 -0
  241. gobby/servers/websocket.py +766 -0
  242. gobby/sessions/__init__.py +13 -0
  243. gobby/sessions/analyzer.py +322 -0
  244. gobby/sessions/lifecycle.py +240 -0
  245. gobby/sessions/manager.py +563 -0
  246. gobby/sessions/processor.py +225 -0
  247. gobby/sessions/summary.py +532 -0
  248. gobby/sessions/transcripts/__init__.py +41 -0
  249. gobby/sessions/transcripts/base.py +125 -0
  250. gobby/sessions/transcripts/claude.py +386 -0
  251. gobby/sessions/transcripts/codex.py +143 -0
  252. gobby/sessions/transcripts/gemini.py +195 -0
  253. gobby/storage/__init__.py +21 -0
  254. gobby/storage/agents.py +409 -0
  255. gobby/storage/artifact_classifier.py +341 -0
  256. gobby/storage/artifacts.py +285 -0
  257. gobby/storage/compaction.py +67 -0
  258. gobby/storage/database.py +357 -0
  259. gobby/storage/inter_session_messages.py +194 -0
  260. gobby/storage/mcp.py +680 -0
  261. gobby/storage/memories.py +562 -0
  262. gobby/storage/merge_resolutions.py +550 -0
  263. gobby/storage/migrations.py +860 -0
  264. gobby/storage/migrations_legacy.py +1359 -0
  265. gobby/storage/projects.py +166 -0
  266. gobby/storage/session_messages.py +251 -0
  267. gobby/storage/session_tasks.py +97 -0
  268. gobby/storage/sessions.py +817 -0
  269. gobby/storage/task_dependencies.py +223 -0
  270. gobby/storage/tasks/__init__.py +42 -0
  271. gobby/storage/tasks/_aggregates.py +180 -0
  272. gobby/storage/tasks/_crud.py +449 -0
  273. gobby/storage/tasks/_id.py +104 -0
  274. gobby/storage/tasks/_lifecycle.py +311 -0
  275. gobby/storage/tasks/_manager.py +889 -0
  276. gobby/storage/tasks/_models.py +300 -0
  277. gobby/storage/tasks/_ordering.py +119 -0
  278. gobby/storage/tasks/_path_cache.py +110 -0
  279. gobby/storage/tasks/_queries.py +343 -0
  280. gobby/storage/tasks/_search.py +143 -0
  281. gobby/storage/workflow_audit.py +393 -0
  282. gobby/storage/worktrees.py +547 -0
  283. gobby/sync/__init__.py +29 -0
  284. gobby/sync/github.py +333 -0
  285. gobby/sync/linear.py +304 -0
  286. gobby/sync/memories.py +284 -0
  287. gobby/sync/tasks.py +641 -0
  288. gobby/tasks/__init__.py +8 -0
  289. gobby/tasks/build_verification.py +193 -0
  290. gobby/tasks/commits.py +633 -0
  291. gobby/tasks/context.py +747 -0
  292. gobby/tasks/criteria.py +342 -0
  293. gobby/tasks/enhanced_validator.py +226 -0
  294. gobby/tasks/escalation.py +263 -0
  295. gobby/tasks/expansion.py +626 -0
  296. gobby/tasks/external_validator.py +764 -0
  297. gobby/tasks/issue_extraction.py +171 -0
  298. gobby/tasks/prompts/expand.py +327 -0
  299. gobby/tasks/research.py +421 -0
  300. gobby/tasks/tdd.py +352 -0
  301. gobby/tasks/tree_builder.py +263 -0
  302. gobby/tasks/validation.py +712 -0
  303. gobby/tasks/validation_history.py +357 -0
  304. gobby/tasks/validation_models.py +89 -0
  305. gobby/tools/__init__.py +0 -0
  306. gobby/tools/summarizer.py +170 -0
  307. gobby/tui/__init__.py +5 -0
  308. gobby/tui/api_client.py +281 -0
  309. gobby/tui/app.py +327 -0
  310. gobby/tui/screens/__init__.py +25 -0
  311. gobby/tui/screens/agents.py +333 -0
  312. gobby/tui/screens/chat.py +450 -0
  313. gobby/tui/screens/dashboard.py +377 -0
  314. gobby/tui/screens/memory.py +305 -0
  315. gobby/tui/screens/metrics.py +231 -0
  316. gobby/tui/screens/orchestrator.py +904 -0
  317. gobby/tui/screens/sessions.py +412 -0
  318. gobby/tui/screens/tasks.py +442 -0
  319. gobby/tui/screens/workflows.py +289 -0
  320. gobby/tui/screens/worktrees.py +174 -0
  321. gobby/tui/widgets/__init__.py +21 -0
  322. gobby/tui/widgets/chat.py +210 -0
  323. gobby/tui/widgets/conductor.py +104 -0
  324. gobby/tui/widgets/menu.py +132 -0
  325. gobby/tui/widgets/message_panel.py +160 -0
  326. gobby/tui/widgets/review_gate.py +224 -0
  327. gobby/tui/widgets/task_tree.py +99 -0
  328. gobby/tui/widgets/token_budget.py +166 -0
  329. gobby/tui/ws_client.py +258 -0
  330. gobby/utils/__init__.py +3 -0
  331. gobby/utils/daemon_client.py +235 -0
  332. gobby/utils/git.py +222 -0
  333. gobby/utils/id.py +38 -0
  334. gobby/utils/json_helpers.py +161 -0
  335. gobby/utils/logging.py +376 -0
  336. gobby/utils/machine_id.py +135 -0
  337. gobby/utils/metrics.py +589 -0
  338. gobby/utils/project_context.py +182 -0
  339. gobby/utils/project_init.py +263 -0
  340. gobby/utils/status.py +256 -0
  341. gobby/utils/validation.py +80 -0
  342. gobby/utils/version.py +23 -0
  343. gobby/workflows/__init__.py +4 -0
  344. gobby/workflows/actions.py +1310 -0
  345. gobby/workflows/approval_flow.py +138 -0
  346. gobby/workflows/artifact_actions.py +103 -0
  347. gobby/workflows/audit_helpers.py +110 -0
  348. gobby/workflows/autonomous_actions.py +286 -0
  349. gobby/workflows/context_actions.py +394 -0
  350. gobby/workflows/definitions.py +130 -0
  351. gobby/workflows/detection_helpers.py +208 -0
  352. gobby/workflows/engine.py +485 -0
  353. gobby/workflows/evaluator.py +669 -0
  354. gobby/workflows/git_utils.py +96 -0
  355. gobby/workflows/hooks.py +169 -0
  356. gobby/workflows/lifecycle_evaluator.py +613 -0
  357. gobby/workflows/llm_actions.py +70 -0
  358. gobby/workflows/loader.py +333 -0
  359. gobby/workflows/mcp_actions.py +60 -0
  360. gobby/workflows/memory_actions.py +272 -0
  361. gobby/workflows/premature_stop.py +164 -0
  362. gobby/workflows/session_actions.py +139 -0
  363. gobby/workflows/state_actions.py +123 -0
  364. gobby/workflows/state_manager.py +104 -0
  365. gobby/workflows/stop_signal_actions.py +163 -0
  366. gobby/workflows/summary_actions.py +344 -0
  367. gobby/workflows/task_actions.py +249 -0
  368. gobby/workflows/task_enforcement_actions.py +901 -0
  369. gobby/workflows/templates.py +52 -0
  370. gobby/workflows/todo_actions.py +84 -0
  371. gobby/workflows/webhook.py +223 -0
  372. gobby/workflows/webhook_executor.py +399 -0
  373. gobby/worktrees/__init__.py +5 -0
  374. gobby/worktrees/git.py +690 -0
  375. gobby/worktrees/merge/__init__.py +20 -0
  376. gobby/worktrees/merge/conflict_parser.py +177 -0
  377. gobby/worktrees/merge/resolver.py +485 -0
  378. gobby-0.2.5.dist-info/METADATA +351 -0
  379. gobby-0.2.5.dist-info/RECORD +383 -0
  380. gobby-0.2.5.dist-info/WHEEL +5 -0
  381. gobby-0.2.5.dist-info/entry_points.txt +2 -0
  382. gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
  383. gobby-0.2.5.dist-info/top_level.txt +1 -0
gobby/servers/http.py ADDED
@@ -0,0 +1,636 @@
1
+ """
2
+ HTTP server for Gobby daemon.
3
+
4
+ Provides a FastAPI-based HTTP server for REST endpoints, MCP tool proxying,
5
+ and session management. Local-first version: no platform auth, no remote sync.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import time
11
+ from collections.abc import AsyncGenerator
12
+ from contextlib import asynccontextmanager
13
+ from typing import Any
14
+
15
+ from fastapi import FastAPI, HTTPException, Request
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.responses import JSONResponse
18
+
19
+ from gobby.adapters.codex import CodexAdapter
20
+ from gobby.hooks.broadcaster import HookEventBroadcaster
21
+ from gobby.hooks.hook_manager import HookManager
22
+ from gobby.llm import LLMService, create_llm_service
23
+ from gobby.mcp_proxy.registries import setup_internal_registries
24
+ from gobby.mcp_proxy.semantic_search import SemanticToolSearch
25
+ from gobby.mcp_proxy.server import GobbyDaemonTools, create_mcp_server
26
+ from gobby.mcp_proxy.services.tool_filter import ToolFilterService
27
+ from gobby.memory.manager import MemoryManager
28
+
29
+ # Re-export for backward compatibility
30
+ from gobby.servers.models import SessionRegisterRequest # noqa: F401
31
+ from gobby.storage.sessions import LocalSessionManager
32
+ from gobby.storage.tasks import LocalTaskManager
33
+ from gobby.sync.tasks import TaskSyncManager
34
+ from gobby.utils.metrics import get_metrics_collector
35
+ from gobby.utils.version import get_version
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ class HTTPServer:
41
+ """
42
+ FastAPI HTTP server for Gobby daemon.
43
+
44
+ Handles MCP tool proxying, session management, and admin endpoints.
45
+ Local-first version: no platform authentication, uses local SQLite storage.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ port: int = 8000,
51
+ test_mode: bool = False,
52
+ mcp_manager: Any | None = None,
53
+ mcp_db_manager: Any | None = None,
54
+ config: Any | None = None,
55
+ codex_client: Any | None = None,
56
+ session_manager: LocalSessionManager | None = None,
57
+ websocket_server: Any | None = None,
58
+ task_manager: LocalTaskManager | None = None,
59
+ task_sync_manager: TaskSyncManager | None = None,
60
+ message_processor: Any | None = None,
61
+ message_manager: Any | None = None, # LocalSessionMessageManager
62
+ memory_manager: "MemoryManager | None" = None,
63
+ llm_service: "LLMService | None" = None,
64
+ memory_sync_manager: Any | None = None,
65
+ task_expander: Any | None = None,
66
+ task_validator: Any | None = None,
67
+ metrics_manager: Any | None = None,
68
+ agent_runner: Any | None = None,
69
+ worktree_storage: Any | None = None,
70
+ ) -> None:
71
+ """
72
+ Initialize HTTP server.
73
+
74
+ Args:
75
+ port: Server port
76
+ test_mode: Run in test mode (disable features that conflict with testing)
77
+ mcp_manager: MCPClientManager instance for multi-server support
78
+ mcp_db_manager: LocalMCPManager instance for SQLite-based storage of MCP
79
+ server configurations and tool schemas. Used by ToolsHandler for
80
+ progressive tool discovery. Optional; defaults to None.
81
+ config: DaemonConfig instance for configuration
82
+ codex_client: CodexAppServerClient instance for Codex integration
83
+ session_manager: LocalSessionManager for session storage
84
+ websocket_server: Optional WebSocketServer instance for event broadcasting
85
+ task_manager: LocalTaskManager instance
86
+ task_sync_manager: TaskSyncManager instance
87
+ message_processor: SessionMessageProcessor instance
88
+ message_manager: LocalSessionMessageManager instance for retrieval
89
+ memory_manager: MemoryManager instance
90
+ llm_service: LLMService instance
91
+ """
92
+ self.port = port
93
+ self.test_mode = test_mode
94
+ self.mcp_manager = mcp_manager
95
+ self.config = config
96
+ self.codex_client = codex_client
97
+ self.session_manager = session_manager
98
+ self.task_manager = task_manager
99
+ self.task_sync_manager = task_sync_manager
100
+ self.message_processor = message_processor
101
+ self.message_manager = message_manager
102
+ self.memory_manager = memory_manager
103
+ self.websocket_server = websocket_server
104
+ self.llm_service = llm_service
105
+ self.memory_sync_manager = memory_sync_manager
106
+ self.task_expander = task_expander
107
+ self.task_validator = task_validator
108
+ self.metrics_manager = metrics_manager
109
+ self.agent_runner = agent_runner
110
+ self.worktree_storage = worktree_storage
111
+
112
+ # Initialize WebSocket broadcaster
113
+ # Note: websocket_server might be None if disabled
114
+ self.broadcaster = HookEventBroadcaster(websocket_server, config)
115
+
116
+ self._start_time: float = time.time()
117
+
118
+ # Create LLM service if not provided
119
+ if not self.llm_service and config:
120
+ try:
121
+ self.llm_service = create_llm_service(config)
122
+ logger.debug(
123
+ f"LLM service initialized with providers: {self.llm_service.enabled_providers}"
124
+ )
125
+ except Exception as e:
126
+ logger.error(f"Failed to initialize LLM service: {e}")
127
+
128
+ # Create MCP server instance
129
+ self._mcp_server = None
130
+ self._internal_manager = None
131
+ self._tools_handler = None
132
+ self._mcp_db_manager = mcp_db_manager
133
+ if mcp_manager:
134
+ # Determine WebSocket port
135
+ ws_port = 8766
136
+ if config and hasattr(config, "websocket") and config.websocket:
137
+ ws_port = config.websocket.port
138
+
139
+ # Create a lazy getter for tool_proxy that will be available after
140
+ # GobbyDaemonTools is created. This allows in-process agents to route
141
+ # tool calls through the MCP proxy.
142
+ def tool_proxy_getter() -> Any:
143
+ if self._tools_handler is not None:
144
+ return self._tools_handler.tool_proxy
145
+ return None
146
+
147
+ # Create merge managers if db available
148
+ merge_storage = None
149
+ merge_resolver = None
150
+ if mcp_db_manager:
151
+ from gobby.storage.merge_resolutions import MergeResolutionManager
152
+ from gobby.worktrees.merge.resolver import MergeResolver
153
+
154
+ merge_storage = MergeResolutionManager(mcp_db_manager.db)
155
+ merge_resolver = MergeResolver()
156
+ merge_resolver._llm_service = self.llm_service
157
+ logger.debug("Merge resolution subsystems initialized")
158
+
159
+ # Setup internal registries (gobby-tasks, gobby-memory, etc.)
160
+ self._internal_manager = setup_internal_registries(
161
+ _config=config,
162
+ _session_manager=None, # Not needed for internal registries
163
+ memory_manager=memory_manager,
164
+ task_manager=task_manager,
165
+ sync_manager=task_sync_manager,
166
+ task_expander=self.task_expander,
167
+ task_validator=self.task_validator,
168
+ message_manager=message_manager,
169
+ local_session_manager=session_manager,
170
+ metrics_manager=self.metrics_manager,
171
+ llm_service=self.llm_service,
172
+ agent_runner=self.agent_runner,
173
+ worktree_storage=self.worktree_storage,
174
+ git_manager=None, # Created per-project, not at daemon startup
175
+ merge_storage=merge_storage,
176
+ merge_resolver=merge_resolver,
177
+ project_id=None, # Project-specific, not global
178
+ tool_proxy_getter=tool_proxy_getter,
179
+ )
180
+ registry_count = len(self._internal_manager)
181
+ logger.debug(f"Internal registries initialized: {registry_count} registries")
182
+
183
+ # Initialize tool summarizer config
184
+ if config:
185
+ from gobby.tools.summarizer import init_summarizer_config
186
+
187
+ init_summarizer_config(config.tool_summarizer)
188
+ logger.debug("Tool summarizer config initialized")
189
+
190
+ # Create semantic search instance if db available
191
+ semantic_search = None
192
+ if mcp_db_manager:
193
+ semantic_search = SemanticToolSearch(db=mcp_db_manager.db)
194
+ logger.debug("Semantic tool search initialized")
195
+
196
+ # Create tool filter for workflow phase restrictions
197
+ tool_filter = None
198
+ if mcp_db_manager:
199
+ tool_filter = ToolFilterService(db=mcp_db_manager.db)
200
+ logger.debug("Tool filter service initialized")
201
+
202
+ # Create fallback resolver for alternative tool suggestions on error
203
+ fallback_resolver = None
204
+ if semantic_search and self.metrics_manager:
205
+ from gobby.mcp_proxy.services.fallback import ToolFallbackResolver
206
+
207
+ fallback_resolver = ToolFallbackResolver(
208
+ semantic_search=semantic_search,
209
+ metrics_manager=self.metrics_manager,
210
+ )
211
+ logger.debug("Fallback resolver initialized")
212
+
213
+ # Create tools handler
214
+ self._tools_handler = GobbyDaemonTools(
215
+ mcp_manager=mcp_manager,
216
+ daemon_port=port,
217
+ websocket_port=ws_port,
218
+ start_time=self._start_time,
219
+ internal_manager=self._internal_manager,
220
+ config=config,
221
+ llm_service=self.llm_service,
222
+ session_manager=session_manager,
223
+ memory_manager=memory_manager,
224
+ config_manager=mcp_db_manager,
225
+ semantic_search=semantic_search,
226
+ tool_filter=tool_filter,
227
+ fallback_resolver=fallback_resolver,
228
+ )
229
+ self._mcp_server = create_mcp_server(self._tools_handler)
230
+ logger.debug("MCP server initialized and will be mounted at /mcp")
231
+
232
+ self.app = self._create_app()
233
+ self._running = False
234
+ self._background_tasks: set[asyncio.Task[Any]] = set()
235
+ self._metrics = get_metrics_collector()
236
+ self._daemon: Any = None # Set externally by daemon
237
+
238
+ def _resolve_project_id(self, project_id: str | None, cwd: str | None) -> str:
239
+ """
240
+ Resolve project_id from cwd if not provided.
241
+
242
+ If project_id is given, returns it directly.
243
+ Otherwise, looks up project from .gobby/project.json in the cwd.
244
+
245
+ Args:
246
+ project_id: Optional explicit project ID
247
+ cwd: Current working directory path
248
+
249
+ Returns:
250
+ Project ID from .gobby/project.json
251
+
252
+ Raises:
253
+ ValueError: If no project.json found (project not initialized)
254
+ """
255
+ from pathlib import Path
256
+
257
+ if project_id:
258
+ return project_id
259
+
260
+ # Get cwd or use current directory
261
+ working_dir = Path(cwd) if cwd else Path.cwd()
262
+
263
+ # Look up project from .gobby/project.json
264
+ from gobby.utils.project_context import get_project_context
265
+
266
+ project_context = get_project_context(working_dir)
267
+ if project_context and project_context.get("id"):
268
+ return str(project_context["id"])
269
+
270
+ # No project.json found - require explicit initialization
271
+ raise ValueError(
272
+ f"No .gobby/project.json found in {working_dir} or parents. "
273
+ "Run 'gobby init' to initialize a project."
274
+ )
275
+
276
+ def _create_app(self) -> FastAPI:
277
+ """
278
+ Create and configure FastAPI application.
279
+
280
+ Returns:
281
+ Configured FastAPI app instance
282
+ """
283
+
284
+ # Create MCP app first if available (needed for lifespan)
285
+ mcp_app = None
286
+ if self._mcp_server:
287
+ mcp_app = self._mcp_server.streamable_http_app()
288
+ logger.debug("MCP HTTP app created")
289
+
290
+ @asynccontextmanager
291
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
292
+ """Handle application startup and shutdown with combined lifespans."""
293
+ logger.debug("Starting Gobby HTTP server on port %d", self.port)
294
+ self._running = True
295
+ self._start_time = time.time()
296
+
297
+ # Startup operations
298
+ if self.test_mode:
299
+ logger.debug("Running in test mode - external connections disabled")
300
+
301
+ # Initialize HookManager singleton with logging config
302
+ hook_manager_kwargs: dict[str, Any] = {
303
+ "daemon_host": "localhost",
304
+ "daemon_port": self.port,
305
+ "llm_service": self.llm_service,
306
+ "config": self.config,
307
+ "broadcaster": self.broadcaster,
308
+ "mcp_manager": self.mcp_manager,
309
+ "message_processor": self.message_processor,
310
+ "memory_sync_manager": self.memory_sync_manager,
311
+ "task_sync_manager": self.task_sync_manager,
312
+ }
313
+ if self.config:
314
+ # Pass full log file path from config
315
+ hook_manager_kwargs["log_file"] = self.config.logging.hook_manager
316
+ hook_manager_kwargs["log_max_bytes"] = self.config.logging.max_size_mb * 1024 * 1024
317
+ hook_manager_kwargs["log_backup_count"] = self.config.logging.backup_count
318
+
319
+ app.state.hook_manager = HookManager(**hook_manager_kwargs)
320
+ logger.debug("HookManager initialized in daemon")
321
+
322
+ # Wire up stop_registry to WebSocket server for stop_request handling
323
+ if (
324
+ self.websocket_server
325
+ and hasattr(app.state, "hook_manager")
326
+ and hasattr(app.state.hook_manager, "_stop_registry")
327
+ ):
328
+ self.websocket_server.stop_registry = app.state.hook_manager._stop_registry
329
+ logger.debug("Stop registry connected to WebSocket server")
330
+
331
+ # Store server instance for dependency injection
332
+ app.state.server = self
333
+
334
+ # Initialize CodexAdapter for session tracking
335
+ app.state.codex_adapter = None
336
+ if self.codex_client and CodexAdapter.is_codex_available():
337
+ codex_adapter = CodexAdapter(hook_manager=app.state.hook_manager)
338
+ codex_adapter.attach_to_client(self.codex_client)
339
+ app.state.codex_adapter = codex_adapter
340
+ logger.debug("CodexAdapter attached to CodexAppServerClient")
341
+
342
+ # Sync existing Codex sessions when client is connected
343
+ if self.codex_client.is_connected:
344
+ try:
345
+ synced = await codex_adapter.sync_existing_sessions()
346
+ logger.debug(f"Synced {synced} existing Codex sessions")
347
+ except Exception as e:
348
+ logger.warning(f"Failed to sync existing Codex sessions: {e}")
349
+
350
+ # If MCP app exists, wrap its lifespan
351
+ if mcp_app is not None:
352
+ # Use router.lifespan_context for stable FastMCP version
353
+ async with mcp_app.router.lifespan_context(app):
354
+ logger.debug("MCP server lifespan initialized")
355
+ yield
356
+ logger.debug("MCP server lifespan shutdown complete")
357
+ else:
358
+ yield
359
+
360
+ # Shutdown operations
361
+ logger.debug("Shutting down Gobby HTTP server")
362
+
363
+ # Cleanup CodexAdapter
364
+ if hasattr(app.state, "codex_adapter") and app.state.codex_adapter:
365
+ app.state.codex_adapter.detach_from_client()
366
+ logger.debug("CodexAdapter detached")
367
+
368
+ # Cleanup HookManager
369
+ if hasattr(app.state, "hook_manager"):
370
+ app.state.hook_manager.shutdown()
371
+ logger.debug("HookManager shutdown complete")
372
+
373
+ # Process graceful shutdown (tasks, MCP connections)
374
+ await self._process_shutdown()
375
+
376
+ self._running = False
377
+
378
+ app = FastAPI(
379
+ title="Gobby Daemon",
380
+ description="Local-first HTTP server for MCP and session management",
381
+ version=get_version(),
382
+ lifespan=lifespan,
383
+ )
384
+
385
+ # Add CORS middleware for cross-origin requests
386
+ app.add_middleware(
387
+ CORSMiddleware,
388
+ allow_origins=["*"], # Allow all origins for local development
389
+ allow_credentials=True,
390
+ allow_methods=["*"],
391
+ allow_headers=["*"],
392
+ )
393
+
394
+ # Register exception handlers
395
+ self._register_exception_handlers(app)
396
+
397
+ # Register routes
398
+ self._register_routes(app)
399
+
400
+ # Mount MCP server if available
401
+ if mcp_app is not None:
402
+ app.mount("/mcp", mcp_app)
403
+ logger.debug("MCP server mounted at /mcp")
404
+
405
+ return app
406
+
407
+ def _register_exception_handlers(self, app: FastAPI) -> None:
408
+ """
409
+ Register global exception handlers.
410
+
411
+ All exceptions return 200 OK to prevent Claude Code hook failures.
412
+
413
+ Args:
414
+ app: FastAPI application instance
415
+ """
416
+
417
+ @app.exception_handler(Exception)
418
+ async def global_exception_handler(
419
+ request: Request,
420
+ exc: Exception,
421
+ ) -> JSONResponse:
422
+ """Handle all uncaught exceptions.
423
+
424
+ HTTPException is re-raised to let FastAPI's built-in handler
425
+ return proper status codes (404, 422, etc.). All other exceptions
426
+ return 200 OK to prevent hook failures.
427
+ """
428
+ # Let HTTPException pass through to FastAPI's built-in handler
429
+ # so proper status codes (404, 422, etc.) are returned
430
+ if isinstance(exc, HTTPException):
431
+ raise exc
432
+
433
+ logger.error(
434
+ "Unhandled exception in HTTP server: %s",
435
+ exc,
436
+ exc_info=True,
437
+ extra={
438
+ "path": request.url.path,
439
+ "method": request.method,
440
+ "client": request.client.host if request.client else None,
441
+ },
442
+ )
443
+
444
+ # Return 200 OK to prevent hook failure for non-HTTP exceptions
445
+ return JSONResponse(
446
+ status_code=200,
447
+ content={
448
+ "status": "error",
449
+ "message": "Internal error occurred but request acknowledged",
450
+ "error_logged": True,
451
+ },
452
+ )
453
+
454
+ def _register_routes(self, app: FastAPI) -> None:
455
+ """
456
+ Register HTTP routes using extracted router modules.
457
+
458
+ Args:
459
+ app: FastAPI application instance
460
+ """
461
+ from gobby.servers.routes import (
462
+ create_admin_router,
463
+ create_hooks_router,
464
+ create_mcp_router,
465
+ create_plugins_router,
466
+ create_sessions_router,
467
+ create_webhooks_router,
468
+ )
469
+
470
+ # Include all routers
471
+ app.include_router(create_admin_router(self))
472
+ app.include_router(create_sessions_router(self))
473
+ app.include_router(create_mcp_router())
474
+ app.include_router(create_hooks_router(self))
475
+ app.include_router(create_plugins_router())
476
+ app.include_router(create_webhooks_router())
477
+
478
+ async def _process_shutdown(self) -> None:
479
+ """
480
+ Background task to perform graceful daemon shutdown.
481
+ """
482
+ start_time = time.perf_counter()
483
+
484
+ try:
485
+ logger.debug("Processing graceful shutdown")
486
+
487
+ # Wait for pending background tasks to complete
488
+ pending_tasks_count = len(self._background_tasks)
489
+ if pending_tasks_count > 0:
490
+ logger.debug(
491
+ "Waiting for pending background tasks to complete",
492
+ extra={"pending_tasks": pending_tasks_count},
493
+ )
494
+
495
+ max_wait = 30.0
496
+ wait_start = time.perf_counter()
497
+
498
+ while (
499
+ len(self._background_tasks) > 0
500
+ and (time.perf_counter() - wait_start) < max_wait
501
+ ):
502
+ await asyncio.sleep(0.5)
503
+
504
+ completed_wait = time.perf_counter() - wait_start
505
+ remaining_tasks = len(self._background_tasks)
506
+
507
+ if remaining_tasks > 0:
508
+ logger.warning(
509
+ "Shutdown timeout - some background tasks still pending",
510
+ extra={
511
+ "remaining_tasks": remaining_tasks,
512
+ "wait_seconds": completed_wait,
513
+ },
514
+ )
515
+ else:
516
+ logger.debug(
517
+ "All background tasks completed",
518
+ extra={"wait_seconds": completed_wait},
519
+ )
520
+
521
+ # Disconnect all MCP servers
522
+ if self.mcp_manager:
523
+ logger.debug("Disconnecting MCP servers...")
524
+ try:
525
+ await self.mcp_manager.disconnect_all()
526
+ logger.debug("MCP servers disconnected")
527
+ except Exception as e:
528
+ logger.warning(f"Error disconnecting MCP servers: {e}")
529
+
530
+ duration_seconds = time.perf_counter() - start_time
531
+ self._metrics.inc_counter("shutdown_succeeded_total")
532
+
533
+ logger.debug(
534
+ "Shutdown processed",
535
+ extra={"duration_seconds": duration_seconds},
536
+ )
537
+
538
+ except Exception as e:
539
+ duration_seconds = time.perf_counter() - start_time
540
+ self._metrics.inc_counter("shutdown_failed_total")
541
+
542
+ logger.error(
543
+ "Shutdown processing failed: %s",
544
+ e,
545
+ exc_info=True,
546
+ extra={"duration_seconds": duration_seconds},
547
+ )
548
+
549
+
550
+ async def create_server(
551
+ port: int = 8765,
552
+ test_mode: bool = False,
553
+ mcp_manager: Any | None = None,
554
+ config: Any | None = None,
555
+ session_manager: LocalSessionManager | None = None,
556
+ ) -> HTTPServer:
557
+ """
558
+ Create HTTP server instance.
559
+
560
+ Args:
561
+ port: Port to listen on
562
+ test_mode: Enable test mode
563
+ mcp_manager: MCP client manager
564
+ config: Daemon configuration
565
+ session_manager: Local session manager
566
+
567
+ Returns:
568
+ Configured HTTPServer instance
569
+ """
570
+ return HTTPServer(
571
+ port=port,
572
+ test_mode=test_mode,
573
+ mcp_manager=mcp_manager,
574
+ config=config,
575
+ session_manager=session_manager,
576
+ )
577
+
578
+
579
+ async def run_server(
580
+ server: HTTPServer,
581
+ host: str = "0.0.0.0", # nosec B104 - local daemon needs network access
582
+ workers: int = 1,
583
+ limit_concurrency: int | None = 1000,
584
+ limit_max_requests: int | None = None,
585
+ timeout_keep_alive: int = 5,
586
+ timeout_graceful_shutdown: int = 30,
587
+ ) -> None:
588
+ """
589
+ Run HTTP server with production-ready Uvicorn configuration.
590
+
591
+ Args:
592
+ server: HTTPServer instance
593
+ host: Host to bind to (default: 0.0.0.0 for all interfaces)
594
+ workers: Number of worker processes (default: 1 for async)
595
+ limit_concurrency: Max concurrent connections (default: 1000)
596
+ limit_max_requests: Max requests before worker restart (None = unlimited)
597
+ timeout_keep_alive: Keep-alive timeout in seconds (default: 5)
598
+ timeout_graceful_shutdown: Graceful shutdown timeout in seconds (default: 30)
599
+ """
600
+ import uvicorn
601
+
602
+ config = uvicorn.Config(
603
+ server.app,
604
+ host=host,
605
+ port=server.port,
606
+ log_level="info",
607
+ access_log=True,
608
+ log_config=None,
609
+ limit_concurrency=limit_concurrency,
610
+ limit_max_requests=limit_max_requests,
611
+ timeout_keep_alive=timeout_keep_alive,
612
+ timeout_graceful_shutdown=timeout_graceful_shutdown,
613
+ backlog=2048,
614
+ workers=workers,
615
+ loop="auto",
616
+ h11_max_incomplete_event_size=16384,
617
+ )
618
+
619
+ uvicorn_server = uvicorn.Server(config)
620
+
621
+ async def shutdown_handler() -> None:
622
+ """Handle graceful shutdown of HTTP server."""
623
+ logger.debug("Initiating HTTP server shutdown...")
624
+ if hasattr(server, "_daemon") and server._daemon is not None:
625
+ try:
626
+ server._daemon.graceful_shutdown(timeout=timeout_graceful_shutdown)
627
+ except Exception as e:
628
+ logger.warning(f"Error during daemon shutdown: {e}")
629
+
630
+ try:
631
+ await uvicorn_server.serve()
632
+ except (KeyboardInterrupt, SystemExit):
633
+ logger.debug("Received shutdown signal")
634
+ await shutdown_handler()
635
+ finally:
636
+ logger.debug("HTTP server stopped")
@@ -0,0 +1,31 @@
1
+ """
2
+ Pydantic models for HTTP server request/response schemas.
3
+ """
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class SessionRegisterRequest(BaseModel):
9
+ """Request model for session registration endpoint."""
10
+
11
+ external_id: str = Field(
12
+ ..., description="External session identifier (e.g., from Claude Code)"
13
+ )
14
+ machine_id: str | None = Field(None, description="Unique machine identifier")
15
+
16
+ # Session metadata
17
+ jsonl_path: str | None = Field(None, description="Path to JSONL transcript file")
18
+ title: str | None = Field(None, description="Natural language session summary/title")
19
+ source: str | None = Field(
20
+ None, description="Session source (e.g., 'Claude Code', 'Agent SDK')"
21
+ )
22
+ parent_session_id: str | None = Field(
23
+ None, description="Parent session ID for session lineage tracking"
24
+ )
25
+ status: str | None = Field(None, description="Session status (active, paused, etc.)")
26
+ project_id: str | None = Field(None, description="Project ID to associate with session")
27
+ project_path: str | None = Field(
28
+ None, description="Project root directory path (for git extraction)"
29
+ )
30
+ git_branch: str | None = Field(None, description="Current git branch name")
31
+ cwd: str | None = Field(None, description="Current working directory")
@@ -0,0 +1,23 @@
1
+ """
2
+ FastAPI route modules for Gobby HTTP server.
3
+
4
+ Each module contains an APIRouter with related endpoints.
5
+ """
6
+
7
+ from gobby.servers.routes.admin import create_admin_router
8
+ from gobby.servers.routes.mcp import (
9
+ create_hooks_router,
10
+ create_mcp_router,
11
+ create_plugins_router,
12
+ create_webhooks_router,
13
+ )
14
+ from gobby.servers.routes.sessions import create_sessions_router
15
+
16
+ __all__ = [
17
+ "create_admin_router",
18
+ "create_hooks_router",
19
+ "create_mcp_router",
20
+ "create_plugins_router",
21
+ "create_sessions_router",
22
+ "create_webhooks_router",
23
+ ]