flock-core 0.4.0b43__py3-none-any.whl → 0.4.0b45__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.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (44) hide show
  1. flock/cli/manage_agents.py +19 -4
  2. flock/core/api/__init__.py +1 -2
  3. flock/core/api/endpoints.py +150 -218
  4. flock/core/api/main.py +134 -653
  5. flock/core/api/service.py +214 -0
  6. flock/core/flock.py +192 -134
  7. flock/core/flock_agent.py +31 -0
  8. flock/webapp/app/api/agent_management.py +135 -164
  9. flock/webapp/app/api/execution.py +76 -85
  10. flock/webapp/app/api/flock_management.py +60 -33
  11. flock/webapp/app/chat.py +233 -0
  12. flock/webapp/app/config.py +6 -3
  13. flock/webapp/app/dependencies.py +95 -0
  14. flock/webapp/app/main.py +320 -906
  15. flock/webapp/app/services/flock_service.py +183 -161
  16. flock/webapp/run.py +176 -100
  17. flock/webapp/static/css/chat.css +227 -0
  18. flock/webapp/static/css/components.css +167 -0
  19. flock/webapp/static/css/header.css +39 -0
  20. flock/webapp/static/css/layout.css +46 -0
  21. flock/webapp/static/css/sidebar.css +127 -0
  22. flock/webapp/templates/base.html +6 -1
  23. flock/webapp/templates/chat.html +60 -0
  24. flock/webapp/templates/chat_settings.html +20 -0
  25. flock/webapp/templates/flock_editor.html +1 -1
  26. flock/webapp/templates/partials/_agent_detail_form.html +8 -7
  27. flock/webapp/templates/partials/_agent_list.html +3 -3
  28. flock/webapp/templates/partials/_agent_manager_view.html +3 -4
  29. flock/webapp/templates/partials/_chat_container.html +9 -0
  30. flock/webapp/templates/partials/_chat_messages.html +13 -0
  31. flock/webapp/templates/partials/_chat_settings_form.html +65 -0
  32. flock/webapp/templates/partials/_execution_form.html +2 -2
  33. flock/webapp/templates/partials/_execution_view_container.html +1 -1
  34. flock/webapp/templates/partials/_flock_properties_form.html +2 -2
  35. flock/webapp/templates/partials/_registry_viewer_content.html +3 -3
  36. flock/webapp/templates/partials/_sidebar.html +17 -1
  37. flock/webapp/templates/registry_viewer.html +3 -3
  38. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/METADATA +1 -1
  39. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/RECORD +42 -31
  40. flock/webapp/static/css/custom.css +0 -612
  41. flock/webapp/templates/partials/_agent_manager_view_old.html +0 -19
  42. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/WHEEL +0 -0
  43. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/entry_points.txt +0 -0
  44. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/licenses/LICENSE +0 -0
@@ -1,28 +1,44 @@
1
+ # src/flock/webapp/app/api/flock_management.py
1
2
  from pathlib import Path
3
+ from typing import TYPE_CHECKING
2
4
 
3
- from fastapi import APIRouter, Form, Request
5
+ from fastapi import APIRouter, Depends, Form, Request # Added Depends
4
6
  from fastapi.responses import HTMLResponse
5
7
  from fastapi.templating import Jinja2Templates
6
8
 
9
+ if TYPE_CHECKING:
10
+ from flock.core.flock import Flock
11
+
12
+ # Import the dependency to get the current Flock instance
13
+ from flock.webapp.app.dependencies import (
14
+ get_flock_instance,
15
+ )
16
+
17
+ # Service functions now take app_state
7
18
  from flock.webapp.app.services.flock_service import (
8
- get_current_flock_filename,
9
- get_current_flock_instance,
10
19
  save_current_flock_to_file_service,
11
20
  update_flock_properties_service,
21
+ # get_current_flock_filename IS NO LONGER IMPORTED
22
+ # get_current_flock_instance IS NO LONGER IMPORTED
12
23
  )
13
24
 
14
25
  router = APIRouter()
15
- BASE_DIR = Path(__file__).resolve().parent.parent.parent # Points to flock-ui/
26
+ BASE_DIR = Path(__file__).resolve().parent.parent.parent
16
27
  templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
17
28
 
18
29
 
19
30
  @router.get("/htmx/flock-properties-form", response_class=HTMLResponse)
20
31
  async def htmx_get_flock_properties_form(
21
- request: Request, update_message: str = None, success: bool = None
32
+ request: Request,
33
+ update_message: str = None,
34
+ success: bool = None,
35
+ current_flock: "Flock" = Depends(get_flock_instance) # Expect flock to be loaded for this form
22
36
  ):
23
- flock = get_current_flock_instance()
24
- if not flock:
25
- # This case should ideally not be reached if editor page properly redirects
37
+ # current_flock is now injected by FastAPI
38
+ # Get the filename from app.state, as it's managed there
39
+ current_filename: str | None = getattr(request.app.state, 'flock_filename', None)
40
+
41
+ if not current_flock: # Should be caught by Depends if get_flock_instance raises error
26
42
  return HTMLResponse(
27
43
  "<div class='error'>Error: No flock loaded. Please load or create one first.</div>"
28
44
  )
@@ -30,8 +46,8 @@ async def htmx_get_flock_properties_form(
30
46
  "partials/_flock_properties_form.html",
31
47
  {
32
48
  "request": request,
33
- "flock": flock,
34
- "current_filename": get_current_flock_filename(),
49
+ "flock": current_flock,
50
+ "current_filename": current_filename,
35
51
  "update_message": update_message,
36
52
  "success": success,
37
53
  },
@@ -44,58 +60,69 @@ async def htmx_update_flock_properties(
44
60
  flock_name: str = Form(...),
45
61
  default_model: str = Form(...),
46
62
  description: str = Form(""),
63
+ # current_flock: Flock = Depends(get_flock_instance) # Service will use app_state
47
64
  ):
65
+ # Pass request.app.state to the service function
48
66
  success_update = update_flock_properties_service(
49
- flock_name, default_model, description
67
+ flock_name, default_model, description, request.app.state
50
68
  )
51
- flock = get_current_flock_instance() # Get updated instance
52
- # Re-render the form with a message
69
+
70
+ # Retrieve updated flock and filename from app.state for rendering the form
71
+ updated_flock: Flock | None = getattr(request.app.state, 'flock_instance', None)
72
+ updated_filename: str | None = getattr(request.app.state, 'flock_filename', None)
73
+
53
74
  return templates.TemplateResponse(
54
75
  "partials/_flock_properties_form.html",
55
76
  {
56
77
  "request": request,
57
- "flock": flock,
58
- "current_filename": get_current_flock_filename(),
78
+ "flock": updated_flock,
79
+ "current_filename": updated_filename,
59
80
  "update_message": "Flock properties updated!"
60
81
  if success_update
61
- else "Failed to update properties.",
82
+ else "Failed to update properties. Check logs.",
62
83
  "success": success_update,
63
84
  },
64
85
  )
65
86
 
66
87
 
67
88
  @router.post("/htmx/save-flock", response_class=HTMLResponse)
68
- async def htmx_save_flock(request: Request, save_filename: str = Form(...)):
69
- if not save_filename.strip(): # Basic validation
70
- flock = get_current_flock_instance()
89
+ async def htmx_save_flock(
90
+ request: Request,
91
+ save_filename: str = Form(...),
92
+ # current_flock: Flock = Depends(get_flock_instance) # Service will use app_state
93
+ ):
94
+ current_flock_from_state: Flock | None = getattr(request.app.state, 'flock_instance', None)
95
+ current_filename_from_state: str | None = getattr(request.app.state, 'flock_filename', None)
96
+
97
+ if not save_filename.strip():
71
98
  return templates.TemplateResponse(
72
99
  "partials/_flock_properties_form.html",
73
100
  {
74
101
  "request": request,
75
- "flock": flock,
76
- "current_filename": get_current_flock_filename(),
102
+ "flock": current_flock_from_state,
103
+ "current_filename": current_filename_from_state,
77
104
  "save_message": "Filename cannot be empty.",
78
105
  "success": False,
79
106
  },
80
107
  )
81
108
 
82
- if not (
83
- save_filename.endswith(".yaml")
84
- or save_filename.endswith(".yml")
85
- or save_filename.endswith(".flock")
86
- ):
87
- save_filename += ".flock.yaml" # Add default extension
109
+ if not (save_filename.endswith(".yaml") or save_filename.endswith(".yml") or save_filename.endswith(".flock")):
110
+ save_filename += ".flock.yaml"
111
+
112
+ # Pass request.app.state to the service function
113
+ success, message = save_current_flock_to_file_service(save_filename, request.app.state)
114
+
115
+ # Retrieve potentially updated flock and filename from app.state
116
+ saved_flock: Flock | None = getattr(request.app.state, 'flock_instance', None)
117
+ saved_filename: str | None = getattr(request.app.state, 'flock_filename', None)
118
+
88
119
 
89
- success, message = save_current_flock_to_file_service(save_filename)
90
- flock = get_current_flock_instance()
91
120
  return templates.TemplateResponse(
92
121
  "partials/_flock_properties_form.html",
93
122
  {
94
123
  "request": request,
95
- "flock": flock,
96
- "current_filename": get_current_flock_filename()
97
- if success
98
- else get_current_flock_filename(), # Update filename if save was successful
124
+ "flock": saved_flock, # Use the instance from app_state
125
+ "current_filename": saved_filename, # Use the filename from app_state (updated on successful save)
99
126
  "save_message": message,
100
127
  "success": success,
101
128
  },
@@ -0,0 +1,233 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import datetime
5
+ from uuid import uuid4
6
+
7
+ from fastapi import APIRouter, Form, Request
8
+ from fastapi.responses import HTMLResponse
9
+ from pydantic import BaseModel
10
+
11
+ from flock.webapp.app.main import get_base_context_web, templates
12
+
13
+ router = APIRouter()
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # In-memory session store (cookie-based). Not suitable for production scale.
17
+ # ---------------------------------------------------------------------------
18
+ _chat_sessions: dict[str, list[dict[str, str]]] = {}
19
+
20
+ COOKIE_NAME = "chat_sid"
21
+
22
+
23
+ def _ensure_session(request: Request):
24
+ """Returns (sid, history_list) tuple and guarantees cookie presence."""
25
+ sid: str | None = request.cookies.get(COOKIE_NAME)
26
+ if not sid:
27
+ sid = uuid4().hex
28
+ if sid not in _chat_sessions:
29
+ _chat_sessions[sid] = []
30
+ return sid, _chat_sessions[sid]
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Chat configuration (per app instance)
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ class ChatConfig(BaseModel):
39
+ agent_name: str | None = None # Name of the Flock agent to chat with
40
+ message_key: str = "message"
41
+ history_key: str = "history"
42
+ response_key: str = "response"
43
+
44
+
45
+ # Store a single global chat config on the FastAPI app state
46
+ def get_chat_config(request: Request) -> ChatConfig:
47
+ if not hasattr(request.app.state, "chat_config"):
48
+ request.app.state.chat_config = ChatConfig()
49
+ return request.app.state.chat_config
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Routes
54
+ # ---------------------------------------------------------------------------
55
+
56
+
57
+ @router.get("/chat", response_class=HTMLResponse, tags=["Chat"])
58
+ async def chat_page(request: Request):
59
+ """Full-page chat UI (works even when the main UI is disabled)."""
60
+ sid, history = _ensure_session(request)
61
+ cfg = get_chat_config(request)
62
+ context = get_base_context_web(request, ui_mode="standalone")
63
+ context.update({"history": history, "chat_cfg": cfg, "chat_subtitle": f"Agent: {cfg.agent_name}" if cfg.agent_name else "Echo demo"})
64
+ response = templates.TemplateResponse("chat.html", context)
65
+ # Set cookie if not already present
66
+ if COOKIE_NAME not in request.cookies:
67
+ response.set_cookie(COOKIE_NAME, sid, max_age=60 * 60 * 24 * 7)
68
+ return response
69
+
70
+
71
+ @router.get("/chat/messages", response_class=HTMLResponse, tags=["Chat"], include_in_schema=False)
72
+ async def chat_history_partial(request: Request):
73
+ """HTMX endpoint that returns the rendered message list."""
74
+ _, history = _ensure_session(request)
75
+ return templates.TemplateResponse(
76
+ "partials/_chat_messages.html",
77
+ {"request": request, "history": history, "now": datetime.now}
78
+ )
79
+
80
+
81
+ @router.post("/chat/send", response_class=HTMLResponse, tags=["Chat"])
82
+ async def chat_send(request: Request, message: str = Form(...)):
83
+ """Echo-back mock implementation. Adds user msg + bot reply to history."""
84
+ _, history = _ensure_session(request)
85
+ current_time = datetime.now().strftime('%H:%M')
86
+ cfg = get_chat_config(request)
87
+ history.append({"role": "user", "text": message, "timestamp": current_time})
88
+ start_time = datetime.now()
89
+
90
+ flock_inst = getattr(request.app.state, "flock_instance", None)
91
+ bot_agent = cfg.agent_name if cfg.agent_name else None
92
+ bot_text: str
93
+ if bot_agent and flock_inst and bot_agent in getattr(flock_inst, "agents", {}):
94
+ # Build input according to mapping keys
95
+ run_input: dict = {}
96
+ if cfg.message_key:
97
+ run_input[cfg.message_key] = message
98
+ if cfg.history_key:
99
+ # Provide history without timestamps to keep things small
100
+ run_input[cfg.history_key] = [h["text"] for h in history]
101
+
102
+ try:
103
+ result_dict = await flock_inst.run_async(start_agent=bot_agent, input=run_input, box_result=False)
104
+ except Exception as e:
105
+ bot_text = f"Error: {e}"
106
+ else:
107
+ if cfg.response_key:
108
+ bot_text = str(result_dict.get(cfg.response_key, result_dict))
109
+ else:
110
+ bot_text = str(result_dict)
111
+ else:
112
+ # Fallback echo behavior
113
+ bot_text = f"Echo: {message}"
114
+
115
+ duration_ms = int((datetime.now() - start_time).total_seconds() * 1000)
116
+ history.append({"role": "bot", "text": bot_text, "timestamp": current_time, "agent": bot_agent or "echo", "duration_ms": duration_ms})
117
+ # Return updated history partial
118
+ return templates.TemplateResponse(
119
+ "partials/_chat_messages.html",
120
+ {"request": request, "history": history, "now": datetime.now}
121
+ )
122
+
123
+
124
+ @router.get("/ui/htmx/chat-view", response_class=HTMLResponse, tags=["Chat"], include_in_schema=False)
125
+ async def chat_container_partial(request: Request):
126
+ _ensure_session(request)
127
+ return templates.TemplateResponse("partials/_chat_container.html", {"request": request})
128
+
129
+
130
+ # ---------------------------------------------------------------------------
131
+ # Chat settings management
132
+ # ---------------------------------------------------------------------------
133
+
134
+
135
+ @router.get("/ui/htmx/chat-settings-form", response_class=HTMLResponse, include_in_schema=False)
136
+ async def chat_settings_form(request: Request):
137
+ """Returns the form for configuring chat behaviour (HTMX partial)."""
138
+ cfg = get_chat_config(request)
139
+ flock_inst = getattr(request.app.state, "flock_instance", None)
140
+ input_fields, output_fields = [], []
141
+ if cfg.agent_name and flock_inst and cfg.agent_name in flock_inst.agents:
142
+ agent_obj = flock_inst.agents[cfg.agent_name]
143
+ # Expect signatures like "field: type | desc, ..." or "field: type" etc.
144
+ def _extract(sig: str):
145
+ fields = []
146
+ for seg in sig.split(','):
147
+ parts = seg.strip().split(':')
148
+ if parts:
149
+ fields.append(parts[0].strip())
150
+ return [f for f in fields if f]
151
+ input_fields = _extract(agent_obj.input) if getattr(agent_obj, 'input', '') else []
152
+ output_fields = _extract(agent_obj.output) if getattr(agent_obj, 'output', '') else []
153
+
154
+ context = get_base_context_web(request)
155
+ context.update({
156
+ "chat_cfg": cfg,
157
+ "current_flock": flock_inst,
158
+ "input_fields": input_fields,
159
+ "output_fields": output_fields,
160
+ })
161
+ return templates.TemplateResponse("partials/_chat_settings_form.html", context)
162
+
163
+
164
+ @router.post("/chat/settings", response_class=HTMLResponse, include_in_schema=False)
165
+ async def chat_settings_submit(
166
+ request: Request,
167
+ agent_name: str | None = Form(default=None),
168
+ message_key: str = Form("message"),
169
+ history_key: str = Form("history"),
170
+ response_key: str = Form("response"),
171
+ ):
172
+ """Apply submitted chat config, then re-render the form with a success message."""
173
+ cfg = get_chat_config(request)
174
+ cfg.agent_name = agent_name or None
175
+ cfg.message_key = message_key
176
+ cfg.history_key = history_key
177
+ cfg.response_key = response_key
178
+
179
+ headers = {
180
+ "HX-Trigger": json.dumps({"notify": {"type": "success", "message": "Chat settings saved"}}),
181
+ "HX-Redirect": "/chat"
182
+ }
183
+ # Response body empty; HTMX will redirect
184
+ return HTMLResponse("", headers=headers)
185
+
186
+
187
+ # --- Stand-alone Chat HTML page access to settings --------------------------
188
+
189
+
190
+ @router.get("/chat/settings-standalone", response_class=HTMLResponse, tags=["Chat"], include_in_schema=False)
191
+ async def chat_settings_standalone(request: Request):
192
+ """Standalone page to render chat settings (used by full-page chat HTML)."""
193
+ cfg = get_chat_config(request)
194
+ context = get_base_context_web(request, ui_mode="standalone")
195
+ context.update({
196
+ "chat_cfg": cfg,
197
+ "current_flock": getattr(request.app.state, "flock_instance", None),
198
+ })
199
+ return templates.TemplateResponse("chat_settings.html", context)
200
+
201
+
202
+ # ---------------------------------------------------------------------------
203
+ # Stand-alone HTMX partials (chat view & settings) for in-page swapping
204
+ # ---------------------------------------------------------------------------
205
+
206
+
207
+ @router.get("/chat/htmx/chat-view", response_class=HTMLResponse, include_in_schema=False)
208
+ async def htmx_chat_view(request: Request):
209
+ """Return chat container partial for standalone page reload via HTMX."""
210
+ _ensure_session(request)
211
+ return templates.TemplateResponse("partials/_chat_container.html", {"request": request})
212
+
213
+
214
+ @router.get("/chat/htmx/settings-form", response_class=HTMLResponse, include_in_schema=False)
215
+ async def htmx_chat_settings_partial(request: Request):
216
+ cfg = get_chat_config(request)
217
+ # Allow temporarily selecting agent via query param without saving
218
+ agent_override = request.query_params.get("agent_name")
219
+ if agent_override is not None:
220
+ cfg = cfg.copy()
221
+ cfg.agent_name = agent_override or None
222
+
223
+ flock_inst = getattr(request.app.state, "flock_instance", None)
224
+ input_fields, output_fields = [], []
225
+ if cfg.agent_name and flock_inst and cfg.agent_name in flock_inst.agents:
226
+ agent_obj = flock_inst.agents[cfg.agent_name]
227
+ def _extract(sig: str):
228
+ return [seg.strip().split(':')[0].strip() for seg in sig.split(',') if seg.strip()]
229
+ input_fields = _extract(agent_obj.input) if getattr(agent_obj, 'input', '') else []
230
+ output_fields = _extract(agent_obj.output) if getattr(agent_obj, 'output', '') else []
231
+
232
+ context = {"request": request, "chat_cfg": cfg, "current_flock": flock_inst, "input_fields": input_fields, "output_fields": output_fields}
233
+ return templates.TemplateResponse("partials/_chat_settings_form.html", context)
@@ -1,8 +1,11 @@
1
1
  import os
2
2
  import random # Added for random theme selection
3
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from flock.core.flock import Flock
4
8
 
5
- from flock.core import Flock # Add type hint
6
9
  from flock.core.logging.formatters.themes import OutputTheme
7
10
 
8
11
  FLOCK_FILES_DIR = Path(os.getenv("FLOCK_FILES_DIR", "./.flock_ui_projects"))
@@ -18,10 +21,10 @@ FLOCK_BASE_DIR = FLOCK_WEBAPP_DIR.parent # src/flock/
18
21
  THEMES_DIR = FLOCK_BASE_DIR / "themes"
19
22
 
20
23
  # Global state for MVP - NOT SUITABLE FOR PRODUCTION/MULTI-USER
21
- CURRENT_FLOCK_INSTANCE: Flock | None = None
24
+ CURRENT_FLOCK_INSTANCE: "Flock | None" = None
22
25
  CURRENT_FLOCK_FILENAME: str | None = None
23
26
 
24
- DEFAULT_THEME_NAME = OutputTheme.ayu.value # Default if random fails or invalid theme specified
27
+ DEFAULT_THEME_NAME = OutputTheme.everblush.value # Default if random fails or invalid theme specified
25
28
 
26
29
  def list_available_themes() -> list[str]:
27
30
  """Scans the THEMES_DIR for .toml files and returns their names (without .toml)."""
@@ -0,0 +1,95 @@
1
+ # src/flock/webapp/app/dependencies.py
2
+ from collections.abc import Sequence
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ # Import FlockEndpoint for type hinting
6
+ from flock.core.api.custom_endpoint import FlockEndpoint
7
+
8
+ if TYPE_CHECKING:
9
+ from flock.core.api.run_store import RunStore
10
+ from flock.core.flock import Flock
11
+
12
+
13
+ # These will be set once when the FastAPI app starts, via set_global_flock_services
14
+ _flock_instance: Optional["Flock"] = None
15
+ _run_store_instance: Optional["RunStore"] = None
16
+
17
+ # Global-like variable (scoped to this module) to temporarily store custom endpoints
18
+ # before the app is fully configured and the lifespan event runs.
19
+ _pending_custom_endpoints: list[FlockEndpoint] = []
20
+
21
+
22
+ def add_pending_custom_endpoints(endpoints: Sequence[FlockEndpoint] | None):
23
+ """Temporarily stores custom endpoints. Called by `start_unified_server`
24
+ before the FastAPI app's lifespan event can process them.
25
+ """
26
+ global _pending_custom_endpoints
27
+ if endpoints:
28
+ # Ensure we are adding FlockEndpoint instances
29
+ for ep in endpoints:
30
+ if not isinstance(ep, FlockEndpoint):
31
+ # If it's a dict, try to convert it, assuming it was the old format
32
+ if isinstance(ep, dict) and "path" in ep and "callback" in ep:
33
+ try:
34
+ # Attempt to create FlockEndpoint from dict - assumes correct keys
35
+ # This is a basic conversion; more robust parsing might be needed
36
+ # if the dict structure is very different.
37
+ _pending_custom_endpoints.append(FlockEndpoint(**ep)) # type: ignore
38
+ except Exception as e:
39
+ print(f"Warning: Could not convert custom endpoint dict to FlockEndpoint: {ep}. Error: {e}")
40
+ else:
41
+ print(f"Warning: Custom endpoint is not a FlockEndpoint instance and cannot be automatically converted: {type(ep)}")
42
+ else:
43
+ _pending_custom_endpoints.append(ep)
44
+ print(f"Dependencies: Added {len(endpoints)} pending custom endpoints.")
45
+
46
+
47
+ def get_pending_custom_endpoints_and_clear() -> list[FlockEndpoint]:
48
+ """Retrieves and clears pending custom endpoints.
49
+ Called by the FastAPI app's lifespan event manager in `webapp/app/main.py`.
50
+ """
51
+ global _pending_custom_endpoints
52
+ endpoints_to_add = _pending_custom_endpoints[:] # Create a copy
53
+ _pending_custom_endpoints = [] # Clear the pending list
54
+ if endpoints_to_add:
55
+ print(f"Dependencies: Retrieved {len(endpoints_to_add)} pending custom endpoints for app registration.")
56
+ return endpoints_to_add
57
+
58
+
59
+ def set_global_flock_services(flock: Optional["Flock"], run_store: "RunStore"):
60
+ """Called once at application startup to set the global Flock and RunStore.
61
+ This is typically invoked by the server startup script (e.g., in webapp/run.py).
62
+ """
63
+ global _flock_instance, _run_store_instance
64
+ from flock.core.logging.logging import (
65
+ get_logger, # Local import to avoid circularity at module level
66
+ )
67
+ logger = get_logger("dependencies")
68
+
69
+ if _flock_instance is not None or _run_store_instance is not None:
70
+ logger.warning("Global Flock services are being re-initialized in dependencies.py.")
71
+
72
+ _flock_instance = flock
73
+ _run_store_instance = run_store
74
+ logger.info(f"Global services set in dependencies: Flock='{flock.name if flock else 'None'}', RunStore type='{type(run_store)}'")
75
+
76
+
77
+ def get_flock_instance() -> "Flock":
78
+ """FastAPI dependency to get the globally available Flock instance."""
79
+ if _flock_instance is None:
80
+ # This can happen if accessed before `set_global_flock_services` or if it was cleared.
81
+ # The application should handle this gracefully, e.g. by redirecting to a setup page.
82
+ raise RuntimeError("Flock instance has not been initialized or is not currently available for DI.")
83
+ return _flock_instance
84
+
85
+ def get_optional_flock_instance() -> Optional["Flock"]:
86
+ """FastAPI dependency to optionally get the Flock instance. Returns None if not set."""
87
+ return _flock_instance
88
+
89
+
90
+ def get_run_store() -> "RunStore":
91
+ """FastAPI dependency to get the globally available RunStore instance."""
92
+ if _run_store_instance is None:
93
+ # Similar to Flock instance, should be initialized at app startup.
94
+ raise RuntimeError("RunStore instance has not been initialized in the application.")
95
+ return _run_store_instance