flock-core 0.4.515__py3-none-any.whl → 0.4.517__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.

flock/webapp/app/main.py CHANGED
@@ -3,7 +3,6 @@ import asyncio
3
3
  import json
4
4
  import os # Added import
5
5
  import shutil
6
- import urllib.parse
7
6
 
8
7
  # Added for share link creation
9
8
  import uuid
@@ -59,6 +58,7 @@ from flock.webapp.app.dependencies import (
59
58
  )
60
59
 
61
60
  # Import service functions (which now expect app_state)
61
+ from flock.webapp.app.middleware import ProxyHeadersMiddleware
62
62
  from flock.webapp.app.services.flock_service import (
63
63
  clear_current_flock_service,
64
64
  create_new_flock_service,
@@ -293,7 +293,13 @@ async def lifespan(app: FastAPI):
293
293
 
294
294
  app = FastAPI(title="Flock Web UI & API", lifespan=lifespan, docs_url="/docs",
295
295
  openapi_url="/openapi.json", root_path=os.getenv("FLOCK_ROOT_PATH", ""))
296
- logger.info("FastAPI booting complete.")
296
+
297
+ # Add middleware for handling proxy headers (HTTPS detection)
298
+ # You can force HTTPS by setting FLOCK_FORCE_HTTPS=true
299
+ force_https = os.getenv("FLOCK_FORCE_HTTPS", "false").lower() == "true"
300
+ app.add_middleware(ProxyHeadersMiddleware, force_https=force_https)
301
+ logger.info(f"FastAPI booting complete with proxy headers middleware (force_https={force_https}).")
302
+
297
303
  BASE_DIR = Path(__file__).resolve().parent.parent
298
304
  app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
299
305
  templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
@@ -706,7 +712,9 @@ async def page_dashboard(
706
712
  # Handle initial redirect if flagged during app startup
707
713
  if getattr(request.app.state, "initial_redirect_to_chat", False):
708
714
  logger.info("Initial redirect to CHAT page triggered from dashboard (FLOCK_START_MODE='chat').")
709
- return RedirectResponse(url="/chat", status_code=307)
715
+ # Use url_for to respect the root_path setting
716
+ chat_url = str(request.url_for("page_chat"))
717
+ return RedirectResponse(url=chat_url, status_code=307)
710
718
 
711
719
  effective_ui_mode = ui_mode
712
720
  flock_is_preloaded = hasattr(request.app.state, "flock_instance") and request.app.state.flock_instance is not None
@@ -714,7 +722,11 @@ async def page_dashboard(
714
722
  if effective_ui_mode is None:
715
723
  effective_ui_mode = "scoped" if flock_is_preloaded else "standalone"
716
724
  if effective_ui_mode == "scoped":
717
- return RedirectResponse(url=f"/?ui_mode=scoped&initial_load=true", status_code=307)
725
+ # Manually construct URL with root_path to ensure it works with proxy setups
726
+ root_path = request.scope.get("root_path", "")
727
+ redirect_url = f"{root_path}/?ui_mode=scoped&initial_load=true"
728
+ logger.info(f"Dashboard redirect: {redirect_url} (root_path: '{root_path}')")
729
+ return RedirectResponse(url=redirect_url, status_code=307)
718
730
 
719
731
  if effective_ui_mode == "standalone" and flock_is_preloaded:
720
732
  clear_current_flock_service(request.app.state) # Pass app.state
@@ -724,9 +736,9 @@ async def page_dashboard(
724
736
  flock_in_state = hasattr(request.app.state, "flock_instance") and request.app.state.flock_instance is not None
725
737
 
726
738
  if effective_ui_mode == "scoped":
727
- context["initial_content_url"] = "/ui/htmx/execution-view-container" if flock_in_state else "/ui/htmx/scoped-no-flock-view"
739
+ context["initial_content_url"] = str(request.url_for("htmx_get_execution_view_container")) if flock_in_state else str(request.url_for("htmx_scoped_no_flock_view"))
728
740
  else:
729
- context["initial_content_url"] = "/ui/htmx/load-flock-view"
741
+ context["initial_content_url"] = str(request.url_for("htmx_get_load_flock_view"))
730
742
  return templates.TemplateResponse("base.html", context)
731
743
 
732
744
  @app.get("/ui/editor/{section:path}", response_class=HTMLResponse, tags=["UI Pages"])
@@ -736,31 +748,36 @@ async def page_editor_section(
736
748
  flock_instance_from_state: Flock | None = getattr(request.app.state, "flock_instance", None)
737
749
  if not flock_instance_from_state:
738
750
  err_msg = "No flock loaded. Please load or create a flock first."
739
- redirect_url = f"/?error={urllib.parse.quote(err_msg)}"
740
- if ui_mode == "scoped": redirect_url += "&ui_mode=scoped"
751
+ # Use url_for to respect the root_path setting
752
+ redirect_url = str(request.url_for("page_dashboard").include_query_params(error=err_msg))
753
+ if ui_mode == "scoped":
754
+ redirect_url = str(request.url_for("page_dashboard").include_query_params(error=err_msg, ui_mode="scoped"))
741
755
  return RedirectResponse(url=redirect_url, status_code=303)
742
756
 
743
757
  context = get_base_context_web(request, error, success, ui_mode)
758
+ root_path = request.scope.get("root_path", "")
744
759
  content_map = {
745
- "properties": "/ui/api/flock/htmx/flock-properties-form",
746
- "agents": "/ui/htmx/agent-manager-view",
747
- "execute": "/ui/htmx/execution-view-container"
760
+ "properties": f"{root_path}/ui/api/flock/htmx/flock-properties-form",
761
+ "agents": f"{root_path}/ui/htmx/agent-manager-view",
762
+ "execute": f"{root_path}/ui/htmx/execution-view-container"
748
763
  }
749
- context["initial_content_url"] = content_map.get(section, "/ui/htmx/load-flock-view")
764
+ context["initial_content_url"] = content_map.get(section, f"{root_path}/ui/htmx/load-flock-view")
750
765
  if section not in content_map: context["error_message"] = "Invalid editor section."
751
766
  return templates.TemplateResponse("base.html", context)
752
767
 
753
768
  @app.get("/ui/registry", response_class=HTMLResponse, tags=["UI Pages"])
754
769
  async def page_registry(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
755
770
  context = get_base_context_web(request, error, success, ui_mode)
756
- context["initial_content_url"] = "/ui/htmx/registry-viewer"
771
+ root_path = request.scope.get("root_path", "")
772
+ context["initial_content_url"] = f"{root_path}/ui/htmx/registry-viewer"
757
773
  return templates.TemplateResponse("base.html", context)
758
774
 
759
775
  @app.get("/ui/create", response_class=HTMLResponse, tags=["UI Pages"])
760
776
  async def page_create(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
761
777
  clear_current_flock_service(request.app.state) # Pass app.state
762
778
  context = get_base_context_web(request, error, success, "standalone")
763
- context["initial_content_url"] = "/ui/htmx/create-flock-form"
779
+ root_path = request.scope.get("root_path", "")
780
+ context["initial_content_url"] = f"{root_path}/ui/htmx/create-flock-form"
764
781
  return templates.TemplateResponse("base.html", context)
765
782
 
766
783
  @app.get("/ui/htmx/sidebar", response_class=HTMLResponse, tags=["UI HTMX Partials"])
@@ -885,7 +902,8 @@ async def ui_create_flock_action(request: Request, flock_name: str = Form(...),
885
902
  @app.get("/ui/settings", response_class=HTMLResponse, tags=["UI Pages"])
886
903
  async def page_settings(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
887
904
  context = get_base_context_web(request, error, success, ui_mode)
888
- context["initial_content_url"] = "/ui/htmx/settings-view"
905
+ root_path = request.scope.get("root_path", "")
906
+ context["initial_content_url"] = f"{root_path}/ui/htmx/settings-view"
889
907
  return templates.TemplateResponse("base.html", context)
890
908
 
891
909
  def _prepare_env_vars_for_template_web():
@@ -0,0 +1,113 @@
1
+
2
+ # Custom middleware for handling proxy headers
3
+ from starlette.middleware.base import BaseHTTPMiddleware
4
+ from starlette.requests import Request as StarletteRequest
5
+
6
+ #from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
7
+
8
+ class ProxyHeadersMiddleware(BaseHTTPMiddleware):
9
+ """Middleware to handle proxy headers for HTTPS detection.
10
+ This ensures url_for() generates HTTPS URLs when behind an HTTPS proxy.
11
+ """
12
+ def __init__(self, app, force_https: bool = False):
13
+ super().__init__(app)
14
+ self.force_https = force_https
15
+
16
+ async def dispatch(self, request: StarletteRequest, call_next):
17
+ import json
18
+ import logging
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Log original scheme and relevant headers for debugging
22
+ original_scheme = request.scope.get("scheme", "unknown")
23
+ original_host = request.headers.get("host")
24
+ logger.info(f"ProxyHeadersMiddleware - Original scheme: {original_scheme}, Host: {original_host}, URL: {request.url}")
25
+
26
+ # If force_https is enabled, always use HTTPS
27
+ if self.force_https:
28
+ request.scope["scheme"] = "https"
29
+ logger.info("ProxyHeadersMiddleware - Force HTTPS enabled, setting scheme to https")
30
+ else:
31
+ # Check for common proxy headers that indicate HTTPS
32
+ forwarded_proto = request.headers.get("x-forwarded-proto")
33
+ forwarded_scheme = request.headers.get("x-forwarded-scheme")
34
+ forwarded_host = request.headers.get("x-forwarded-host")
35
+ cloudflare_proto = request.headers.get("cf-visitor")
36
+ forwarded_ssl = request.headers.get("x-forwarded-ssl")
37
+ front_end_https = request.headers.get("front-end-https")
38
+
39
+ # Log proxy headers for debugging
40
+ proxy_headers = {
41
+ "x-forwarded-proto": forwarded_proto,
42
+ "x-forwarded-scheme": forwarded_scheme,
43
+ "x-forwarded-host": forwarded_host,
44
+ "cf-visitor": cloudflare_proto,
45
+ "x-forwarded-ssl": forwarded_ssl,
46
+ "front-end-https": front_end_https,
47
+ "x-forwarded-for": request.headers.get("x-forwarded-for"),
48
+ "host": request.headers.get("host")
49
+ }
50
+ logger.info(f"ProxyHeadersMiddleware - Proxy headers: {proxy_headers}")
51
+
52
+ scheme_updated = False
53
+
54
+ # Handle X-Forwarded-Proto header (most common)
55
+ if forwarded_proto:
56
+ request.scope["scheme"] = forwarded_proto.lower()
57
+ scheme_updated = True
58
+ logger.info(f"ProxyHeadersMiddleware - Updated scheme from X-Forwarded-Proto: {forwarded_proto}")
59
+
60
+ # Handle X-Forwarded-Scheme header
61
+ elif forwarded_scheme:
62
+ request.scope["scheme"] = forwarded_scheme.lower()
63
+ scheme_updated = True
64
+ logger.info(f"ProxyHeadersMiddleware - Updated scheme from X-Forwarded-Scheme: {forwarded_scheme}")
65
+
66
+ # Handle X-Forwarded-SSL header (on/off)
67
+ elif forwarded_ssl and forwarded_ssl.lower() == "on":
68
+ request.scope["scheme"] = "https"
69
+ scheme_updated = True
70
+ logger.info(f"ProxyHeadersMiddleware - Updated scheme from X-Forwarded-SSL: on -> https")
71
+
72
+ # Handle Front-End-Https header (on/off)
73
+ elif front_end_https and front_end_https.lower() == "on":
74
+ request.scope["scheme"] = "https"
75
+ scheme_updated = True
76
+ logger.info(f"ProxyHeadersMiddleware - Updated scheme from Front-End-Https: on -> https")
77
+
78
+ # Handle Cloudflare's CF-Visitor header (JSON format)
79
+ elif cloudflare_proto:
80
+ try:
81
+ visitor_info = json.loads(cloudflare_proto)
82
+ if visitor_info.get("scheme"):
83
+ request.scope["scheme"] = visitor_info["scheme"].lower()
84
+ scheme_updated = True
85
+ logger.info(f"ProxyHeadersMiddleware - Updated scheme from CF-Visitor: {visitor_info['scheme']}")
86
+ except (json.JSONDecodeError, KeyError) as e:
87
+ logger.warning(f"ProxyHeadersMiddleware - Failed to parse CF-Visitor header: {e}")
88
+
89
+ if not scheme_updated:
90
+ logger.info("ProxyHeadersMiddleware - No proxy headers found, keeping original scheme")
91
+
92
+ # Handle X-Forwarded-Host for proper host handling
93
+ forwarded_host = request.headers.get("x-forwarded-host")
94
+ if forwarded_host:
95
+ # Update the server scope to reflect the original host
96
+ request.scope["server"] = (forwarded_host, 443 if request.scope.get("scheme") == "https" else 80)
97
+ logger.info(f"ProxyHeadersMiddleware - Updated host from X-Forwarded-Host: {forwarded_host}")
98
+
99
+ # Handle X-Forwarded-For for client IP (optional but good practice)
100
+ forwarded_for = request.headers.get("x-forwarded-for")
101
+ if forwarded_for:
102
+ # Take the first IP in the chain (the original client)
103
+ client_ip = forwarded_for.split(",")[0].strip()
104
+ request.scope["client"] = (client_ip, request.scope.get("client", ["", 0])[1])
105
+
106
+ # Log final scheme and reconstructed URL
107
+ final_scheme = request.scope.get("scheme")
108
+ final_server = request.scope.get("server", ("unknown", 0))
109
+ logger.info(f"ProxyHeadersMiddleware - Final scheme: {final_scheme}, server: {final_server}")
110
+ logger.info(f"ProxyHeadersMiddleware - Reconstructed URL would be: {final_scheme}://{final_server[0]}{request.url.path}")
111
+
112
+ response = await call_next(request)
113
+ return response
@@ -5,7 +5,7 @@
5
5
  {% endif %}
6
6
  {% if flock.agents %}
7
7
  <ul class="item-list">
8
- {% for agent_name, agent in flock.agents.items() %} <li hx-get="{{ url_for('htmx_get_agent_details_form', agent_name=agent.name) }}" hx-target="#agent-detail-panel" hx-swap="innerHTML" hx-indicator="#agent-detail-loading" onclick="this.closest('ul').querySelectorAll('li').forEach(li => li.classList.remove('selected-item')); this.classList.add('selected-item');">
8
+ {% for agent_name, agent in flock.agents.items() %} <li hx-get="{{ url_for('htmx_get_agent_details_form', agent_name=agent.name) }}" hx-target="#agent-detail-panel" hx-swap="innerHTML" hx-indicator="#agent-detail-loading-indicator" onclick="this.closest('ul').querySelectorAll('li').forEach(li => li.classList.remove('selected-item')); this.classList.add('selected-item');">
9
9
  <strong>{{ agent.name }}</strong><br>
10
10
  <small>{{ agent.resolved_description|truncate(80) if agent.resolved_description else 'No description' }}</small>
11
11
  </li>
@@ -38,9 +38,7 @@ window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agen
38
38
  <progress indeterminate></progress> Loading input form...
39
39
  </div>
40
40
 
41
- <button type="submit" {% if not flock.agents %}disabled{% endif %}>Run Flock</button>
42
-
43
- <div id="share-agent-link-container" style="margin-top: 0.5rem;"> <a href="#"
41
+ <button type="submit" {% if not flock.agents %}disabled{% endif %}>Run Flock</button> <div id="share-agent-link-container" style="margin-top: 0.5rem;"> <a href="#"
44
42
  id="shareAgentHtmxLink"
45
43
  hx-post="{{ url_for('htmx_generate_share_link') }}"
46
44
  hx-target="#shareLinkDisplayArea"
@@ -50,6 +48,9 @@ window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agen
50
48
  style="text-decoration: underline; cursor: pointer; color: var(--pico-primary);">
51
49
  Create shareable link...
52
50
  </a>
51
+ <span id="share-loading-indicator" class="htmx-indicator">
52
+ <progress indeterminate></progress> Generating link...
53
+ </span>
53
54
  </div>
54
55
 
55
56
  <span id="run-loading-indicator" class="htmx-indicator">
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flock-core
3
- Version: 0.4.515
3
+ Version: 0.4.517
4
4
  Summary: Declarative LLM Orchestration at Scale
5
5
  Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
6
6
  License-File: LICENSE
@@ -495,7 +495,8 @@ flock/webapp/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
495
495
  flock/webapp/app/chat.py,sha256=d5a_mr3H2nuWNFSpSlI_HyqX-J_4krndd4A-8S25EKM,28679
496
496
  flock/webapp/app/config.py,sha256=lqmneujnNZk-EFJV5cWpvxkqisxH3T3zT_YOI0JYThE,4809
497
497
  flock/webapp/app/dependencies.py,sha256=JUcwY1N6SZplU141lMN2wk9dOC9er5HCedrKTJN9wJk,5533
498
- flock/webapp/app/main.py,sha256=54DHCA4jaOBrW3Lly0t-r_gSOEBj96G4Izt5pkbFZSw,57142
498
+ flock/webapp/app/main.py,sha256=J3NiwW4fFTrp_YKa-HFXs3gLtLC6qu9LjIb_i-jJHMk,58426
499
+ flock/webapp/app/middleware.py,sha256=5gkM-gqD7C6-JrsoTB1_UWpf05JI1N8KIWajn60QZk0,5721
499
500
  flock/webapp/app/models_ui.py,sha256=vrEBLbhEp6FziAgBSFOLT1M7ckwadsTdT7qus5_NduE,329
500
501
  flock/webapp/app/theme_mapper.py,sha256=QzWwLWpED78oYp3FjZ9zxv1KxCyj43m8MZ0fhfzz37w,34302
501
502
  flock/webapp/app/utils.py,sha256=RF8DMKKAj1XPmm4txUdo2OdswI1ATQ7cqUm6G9JFDzA,2942
@@ -523,7 +524,7 @@ flock/webapp/templates/index.html,sha256=1SDfAfaGBNJhI26bc6z7qBo9mrOK7HKQIS9aRGv
523
524
  flock/webapp/templates/registry_viewer.html,sha256=Rq0Ao3r6mqwgF6bWM73d-KmnslwQoz79a2PGbL5M4Fo,3349
524
525
  flock/webapp/templates/shared_run_page.html,sha256=RX1J3hgEuIJXaGYlquY7m54yxPTIrJJAZH2HB6v33WY,8109
525
526
  flock/webapp/templates/partials/_agent_detail_form.html,sha256=jpcbj2-zN_eWH6vlhWGBAviObmK2IZvezU_b2_UW-Tc,6067
526
- flock/webapp/templates/partials/_agent_list.html,sha256=2Gvm84BPX5Wm3IwmuqTNX-I5c46P18obC-k0quEQ8VQ,1048
527
+ flock/webapp/templates/partials/_agent_list.html,sha256=0TcutldXJncQP6jMsXh8cnbjdzaVuEF-VTddQ6DZli0,1058
527
528
  flock/webapp/templates/partials/_agent_manager_view.html,sha256=8oJlxjtgeOkCQcfhaAmE4s_tklk25Jw0XScXpS_MVr0,2571
528
529
  flock/webapp/templates/partials/_agent_tools_checklist.html,sha256=T60fb7OrJYHUw0hJLC_otskgvbH9dZXbv5klgWBkSWk,686
529
530
  flock/webapp/templates/partials/_chat_container.html,sha256=MHj9yRHy_wVZD6f_Csc0bZBhtxJMfr2V5bRRAaXiqtU,843
@@ -536,7 +537,7 @@ flock/webapp/templates/partials/_dashboard_flock_properties_preview.html,sha256=
536
537
  flock/webapp/templates/partials/_dashboard_upload_flock_form.html,sha256=UWU_WpIsq6iw5FwK989ANmhzcCOcDKVjdakZ4kxY8lU,961
537
538
  flock/webapp/templates/partials/_dynamic_input_form_content.html,sha256=WYr2M7Mb5vKITFhIVXXEHppRx9IjGew91yLo1_I9kkI,1230
538
539
  flock/webapp/templates/partials/_env_vars_table.html,sha256=st8bRQpIJ3TJ_znEdyOwDT43ZhO2QTLne2IZNxQdQNM,1106
539
- flock/webapp/templates/partials/_execution_form.html,sha256=0Tgqk-JWW8tzSJPKZ4CFT4ZvNCB02Zesib2QN6bEiUk,3526
540
+ flock/webapp/templates/partials/_execution_form.html,sha256=yQbYgFYlfva4hgRAs791MR2IFgZnQBLbo4vvlPrDGeU,3686
540
541
  flock/webapp/templates/partials/_execution_view_container.html,sha256=w4QEr-yPs0k1vF2oxMhhPoOr-ki0YJzzUltGmZgfRfY,1672
541
542
  flock/webapp/templates/partials/_flock_file_list.html,sha256=3F1RE1EaeRSQeyIWeA2ALHU2j4oGwureDtY6k1aZVjQ,1261
542
543
  flock/webapp/templates/partials/_flock_properties_form.html,sha256=kUHoxB6_C_R9nMDD_ClLRDL_9zQ6E8gFCrlkEkqcUYo,2904
@@ -561,8 +562,8 @@ flock/workflow/agent_execution_activity.py,sha256=Gy6FtuVAjf0NiUXmC3syS2eJpNQF4R
561
562
  flock/workflow/flock_workflow.py,sha256=iSUF_soFvWar0ffpkzE4irkDZRx0p4HnwmEBi_Ne2sY,9666
562
563
  flock/workflow/temporal_config.py,sha256=3_8O7SDEjMsSMXsWJBfnb6XTp0TFaz39uyzSlMTSF_I,3988
563
564
  flock/workflow/temporal_setup.py,sha256=YIHnSBntzOchHfMSh8hoLeNXrz3B1UbR14YrR6soM7A,1606
564
- flock_core-0.4.515.dist-info/METADATA,sha256=7nfOA03KGxxfxZ5-sm_GuTHXqhhD1NRxo_hvp6d8zBI,22786
565
- flock_core-0.4.515.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
566
- flock_core-0.4.515.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
567
- flock_core-0.4.515.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
568
- flock_core-0.4.515.dist-info/RECORD,,
565
+ flock_core-0.4.517.dist-info/METADATA,sha256=ofFT82sCre9PCN_3qdS4c9jz8mwCAAEUb4SYX01jQBY,22786
566
+ flock_core-0.4.517.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
567
+ flock_core-0.4.517.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
568
+ flock_core-0.4.517.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
569
+ flock_core-0.4.517.dist-info/RECORD,,