flock-core 0.4.0b48__py3-none-any.whl → 0.4.0b50__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 (32) hide show
  1. flock/__init__.py +45 -3
  2. flock/modules/mem0/mem0_module.py +63 -0
  3. flock/modules/mem0graph/__init__.py +1 -0
  4. flock/modules/mem0graph/mem0_graph_module.py +63 -0
  5. flock/webapp/app/api/execution.py +105 -47
  6. flock/webapp/app/chat.py +315 -24
  7. flock/webapp/app/config.py +15 -1
  8. flock/webapp/app/dependencies.py +22 -0
  9. flock/webapp/app/main.py +414 -14
  10. flock/webapp/app/services/flock_service.py +38 -13
  11. flock/webapp/app/services/sharing_models.py +43 -0
  12. flock/webapp/app/services/sharing_store.py +156 -0
  13. flock/webapp/static/css/chat.css +57 -0
  14. flock/webapp/templates/base.html +91 -1
  15. flock/webapp/templates/chat.html +93 -5
  16. flock/webapp/templates/partials/_agent_detail_form.html +3 -3
  17. flock/webapp/templates/partials/_chat_messages.html +1 -1
  18. flock/webapp/templates/partials/_chat_settings_form.html +22 -0
  19. flock/webapp/templates/partials/_execution_form.html +28 -1
  20. flock/webapp/templates/partials/_flock_properties_form.html +2 -2
  21. flock/webapp/templates/partials/_results_display.html +15 -11
  22. flock/webapp/templates/partials/_share_chat_link_snippet.html +11 -0
  23. flock/webapp/templates/partials/_share_link_snippet.html +35 -0
  24. flock/webapp/templates/partials/_structured_data_view.html +2 -2
  25. flock/webapp/templates/shared_run_page.html +143 -0
  26. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/METADATA +4 -2
  27. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/RECORD +31 -24
  28. flock/modules/zep/zep_module.py +0 -187
  29. /flock/modules/{zep → mem0}/__init__.py +0 -0
  30. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/WHEEL +0 -0
  31. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/entry_points.txt +0 -0
  32. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/licenses/LICENSE +0 -0
flock/webapp/app/main.py CHANGED
@@ -1,14 +1,29 @@
1
1
  # src/flock/webapp/app/main.py
2
2
  import json
3
+ import os # Added import
3
4
  import shutil
4
5
  import urllib.parse
6
+
7
+ # Added for share link creation
8
+ import uuid
5
9
  from contextlib import asynccontextmanager
6
10
  from pathlib import Path
7
11
 
8
- from fastapi import FastAPI, File, Form, Query, Request, UploadFile
12
+ import markdown2 # Import markdown2
13
+ from fastapi import (
14
+ Depends,
15
+ FastAPI,
16
+ File,
17
+ Form,
18
+ HTTPException,
19
+ Query,
20
+ Request,
21
+ UploadFile,
22
+ )
9
23
  from fastapi.responses import HTMLResponse, RedirectResponse
10
24
  from fastapi.staticfiles import StaticFiles
11
25
  from fastapi.templating import Jinja2Templates
26
+ from pydantic import BaseModel
12
27
 
13
28
  from flock.core.api.endpoints import create_api_router
14
29
  from flock.core.api.run_store import RunStore
@@ -16,6 +31,7 @@ from flock.core.api.run_store import RunStore
16
31
  # Import core Flock components and API related modules
17
32
  from flock.core.flock import Flock # For type hinting
18
33
  from flock.core.logging.logging import get_logger # For logging
34
+ from flock.core.util.spliter import parse_schema
19
35
 
20
36
  # Import UI-specific routers
21
37
  from flock.webapp.app.api import (
@@ -27,6 +43,7 @@ from flock.webapp.app.api import (
27
43
  from flock.webapp.app.config import (
28
44
  DEFAULT_THEME_NAME,
29
45
  FLOCK_FILES_DIR,
46
+ SHARED_LINKS_DB_PATH,
30
47
  THEMES_DIR,
31
48
  get_current_theme_name,
32
49
  )
@@ -34,7 +51,9 @@ from flock.webapp.app.config import (
34
51
  # Import dependency management and config
35
52
  from flock.webapp.app.dependencies import (
36
53
  get_pending_custom_endpoints_and_clear,
54
+ get_shared_link_store,
37
55
  set_global_flock_services,
56
+ set_global_shared_link_store,
38
57
  )
39
58
 
40
59
  # Import service functions (which now expect app_state)
@@ -47,6 +66,13 @@ from flock.webapp.app.services.flock_service import (
47
66
  # Note: get_current_flock_instance/filename are removed from service,
48
67
  # as main.py will use request.app.state for this.
49
68
  )
69
+
70
+ # Added for share link creation
71
+ from flock.webapp.app.services.sharing_models import SharedLinkConfig
72
+ from flock.webapp.app.services.sharing_store import (
73
+ SharedLinkStoreInterface,
74
+ SQLiteSharedLinkStore,
75
+ )
50
76
  from flock.webapp.app.theme_mapper import alacritty_to_pico
51
77
 
52
78
  logger = get_logger("webapp.main")
@@ -113,6 +139,78 @@ async def lifespan(app: FastAPI):
113
139
  # by `start_unified_server` in `webapp/run.py` *before* uvicorn starts the app.
114
140
  # The call to `set_global_flock_services` also happens there.
115
141
 
142
+ # Initialize and set the SharedLinkStore
143
+ try:
144
+ logger.info(f"Initializing SharedLinkStore with DB path: {SHARED_LINKS_DB_PATH}")
145
+ shared_link_store = SQLiteSharedLinkStore(db_path=str(SHARED_LINKS_DB_PATH))
146
+ await shared_link_store.initialize() # Create tables if they don't exist
147
+ set_global_shared_link_store(shared_link_store)
148
+ logger.info("SharedLinkStore initialized and set globally.")
149
+ except Exception as e:
150
+ logger.error(f"Failed to initialize SharedLinkStore: {e}", exc_info=True)
151
+
152
+ # Configure chat features based on environment variables
153
+ # These are typically set by __init__.py when launching with --chat or --web --chat
154
+ flock_start_mode = os.environ.get("FLOCK_START_MODE")
155
+ flock_chat_enabled_env = os.environ.get("FLOCK_CHAT_ENABLED", "false").lower() == "true"
156
+
157
+ should_enable_chat_routes = False
158
+ if flock_start_mode == "chat":
159
+ should_enable_chat_routes = True
160
+ app.state.initial_redirect_to_chat = True # Signal dashboard to redirect
161
+ logger.info("FLOCK_START_MODE='chat'. Chat routes will be enabled and initial redirect to chat is set.")
162
+ elif flock_chat_enabled_env:
163
+ should_enable_chat_routes = True
164
+ logger.info("FLOCK_CHAT_ENABLED='true'. Chat routes will be enabled.")
165
+
166
+ app.state.chat_enabled = should_enable_chat_routes # For context in templates
167
+
168
+ if should_enable_chat_routes:
169
+ try:
170
+ from flock.webapp.app.chat import router as chat_router
171
+ app.include_router(chat_router, tags=["Chat"])
172
+ logger.info("Chat routes included in the application.")
173
+ except Exception as e:
174
+ logger.error(f"Failed to include chat routes during lifespan startup: {e}", exc_info=True)
175
+
176
+ # If in standalone chat mode, strip non-essential UI routes
177
+ if flock_start_mode == "chat":
178
+ from fastapi.routing import APIRoute
179
+ logger.info("FLOCK_START_MODE='chat'. Stripping non-chat UI routes.")
180
+
181
+ # Define tags for routes to KEEP.
182
+ # "Chat" for primary chat functionality.
183
+ # "Chat Sharing" for shared chat links & pages.
184
+ # API tags might be needed if chat agents make internal API calls or for general health/docs.
185
+ # Public static files (/static/...) are typically handled by app.mount and not in app.router.routes directly this way.
186
+ allowed_tags_for_chat_mode = {
187
+ "Chat",
188
+ "Chat Sharing",
189
+ "Flock API Core", # Keep core API for potential underlying needs
190
+ "Flock API Custom Endpoints" # Keep custom API endpoints
191
+ }
192
+
193
+ def _route_is_allowed_in_chat_mode(route: APIRoute) -> bool:
194
+ # Keep documentation (e.g. /docs, /openapi.json - usually no tags or specific tags)
195
+ # and non-API utility routes (often no tags).
196
+ if not hasattr(route, "tags") or not route.tags:
197
+ # Check common doc paths explicitly as they might not have tags or might have default tags
198
+ if route.path in ["/docs", "/openapi.json", "/redoc"]:
199
+ return True
200
+ # Allow other untagged routes for now, assuming they are essential (e.g. static mounts if they appeared here)
201
+ # This might need refinement if untagged UI routes exist.
202
+ return True
203
+ return any(tag in allowed_tags_for_chat_mode for tag in route.tags)
204
+
205
+ original_route_count = len(app.router.routes)
206
+ app.router.routes = [r for r in app.router.routes if _route_is_allowed_in_chat_mode(r)]
207
+ num_removed = original_route_count - len(app.router.routes)
208
+ logger.info(f"Stripped {num_removed} routes for chat-only mode. {len(app.router.routes)} routes remaining.")
209
+
210
+ if num_removed > 0 and hasattr(app, "openapi_schema"):
211
+ app.openapi_schema = None # Clear cached OpenAPI schema to regenerate
212
+ logger.info("Cleared OpenAPI schema cache due to route removal.")
213
+
116
214
  # Add custom routes if any were passed during server startup
117
215
  # These are retrieved from the dependency module where `start_unified_server` stored them.
118
216
  pending_endpoints = get_pending_custom_endpoints_and_clear()
@@ -140,6 +238,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent
140
238
  app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
141
239
  templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
142
240
 
241
+ # Add markdown2 filter to Jinja2 environment
242
+ def markdown_filter(text):
243
+ return markdown2.markdown(text, extras=["tables", "fenced-code-blocks"])
244
+
245
+ templates.env.filters['markdown'] = markdown_filter
246
+
143
247
  core_api_router = create_api_router()
144
248
  app.include_router(core_api_router, prefix="/api", tags=["Flock API Core"])
145
249
  app.include_router(flock_management.router, prefix="/ui/api/flock", tags=["UI Flock Management"])
@@ -147,6 +251,301 @@ app.include_router(agent_management.router, prefix="/ui/api/flock", tags=["UI Ag
147
251
  app.include_router(execution.router, prefix="/ui/api/flock", tags=["UI Execution"])
148
252
  app.include_router(registry_viewer.router, prefix="/ui/api/registry", tags=["UI Registry"])
149
253
 
254
+ # --- Share Link API Models and Endpoint ---
255
+ class CreateShareLinkRequest(BaseModel):
256
+ agent_name: str
257
+
258
+ class CreateShareLinkResponse(BaseModel):
259
+ share_url: str
260
+
261
+ @app.post("/api/v1/share/link", response_model=CreateShareLinkResponse, tags=["UI Sharing"])
262
+ async def create_share_link(
263
+ request: Request,
264
+ request_data: CreateShareLinkRequest,
265
+ store: SharedLinkStoreInterface = Depends(get_shared_link_store)
266
+ ):
267
+ """Creates a new shareable link for an agent."""
268
+ share_id = uuid.uuid4().hex
269
+ agent_name = request_data.agent_name
270
+
271
+ if not agent_name: # Basic validation
272
+ raise HTTPException(status_code=400, detail="Agent name cannot be empty.")
273
+
274
+ current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
275
+ current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
276
+
277
+ if not current_flock_instance or not current_flock_filename:
278
+ logger.error("Cannot create share link: No Flock is currently loaded in the application state.")
279
+ raise HTTPException(status_code=400, detail="No Flock loaded. Cannot create share link.")
280
+
281
+ if agent_name not in current_flock_instance.agents:
282
+ logger.error(f"Agent '{agent_name}' not found in currently loaded Flock '{current_flock_instance.name}'.")
283
+ raise HTTPException(status_code=404, detail=f"Agent '{agent_name}' not found in current Flock.")
284
+
285
+ try:
286
+ flock_file_path = FLOCK_FILES_DIR / current_flock_filename
287
+ if not flock_file_path.is_file():
288
+ logger.warning(f"Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
289
+ flock_definition_str = current_flock_instance.to_yaml()
290
+ else:
291
+ flock_definition_str = flock_file_path.read_text()
292
+ except Exception as e:
293
+ logger.error(f"Failed to get flock definition for sharing: {e}", exc_info=True)
294
+ raise HTTPException(status_code=500, detail="Could not retrieve Flock definition for sharing.")
295
+
296
+ config = SharedLinkConfig(
297
+ share_id=share_id,
298
+ agent_name=agent_name,
299
+ flock_definition=flock_definition_str
300
+ )
301
+ try:
302
+ await store.save_config(config)
303
+ share_url = f"/ui/shared-run/{share_id}" # Relative URL for client-side navigation
304
+ logger.info(f"Created share link for agent '{agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {share_url}")
305
+ return CreateShareLinkResponse(share_url=share_url)
306
+ except Exception as e:
307
+ logger.error(f"Failed to create share link for agent '{agent_name}': {e}", exc_info=True)
308
+ raise HTTPException(status_code=500, detail=f"Failed to create share link: {e!s}")
309
+
310
+ # --- End Share Link API ---
311
+
312
+ # --- HTMX Endpoint for Generating Share Link Snippet ---
313
+ @app.post("/ui/htmx/share/generate-link", response_class=HTMLResponse, tags=["UI Sharing HTMX"])
314
+ async def htmx_generate_share_link(
315
+ request: Request,
316
+ start_agent_name: str | None = Form(None),
317
+ store: SharedLinkStoreInterface = Depends(get_shared_link_store)
318
+ ):
319
+ if not start_agent_name:
320
+ logger.warning("HTMX generate share link: Agent name not provided.")
321
+ return templates.TemplateResponse(
322
+ "partials/_share_link_snippet.html",
323
+ {"request": request, "error_message": "No agent selected to share."}
324
+ )
325
+
326
+ current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
327
+ current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
328
+
329
+ if not current_flock_instance or not current_flock_filename:
330
+ logger.error("HTMX: Cannot create share link: No Flock is currently loaded.")
331
+ return templates.TemplateResponse(
332
+ "partials/_share_link_snippet.html",
333
+ {"request": request, "error_message": "No Flock loaded. Cannot create share link."}
334
+ )
335
+
336
+ if start_agent_name not in current_flock_instance.agents:
337
+ logger.error(f"HTMX: Agent '{start_agent_name}' not found in Flock '{current_flock_instance.name}'.")
338
+ return templates.TemplateResponse(
339
+ "partials/_share_link_snippet.html",
340
+ {"request": request, "error_message": f"Agent '{start_agent_name}' not found in current Flock."}
341
+ )
342
+
343
+ try:
344
+ flock_file_path = FLOCK_FILES_DIR / current_flock_filename
345
+ if not flock_file_path.is_file():
346
+ logger.warning(f"HTMX: Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
347
+ flock_definition_str = current_flock_instance.to_yaml()
348
+ else:
349
+ flock_definition_str = flock_file_path.read_text()
350
+ except Exception as e:
351
+ logger.error(f"HTMX: Failed to get flock definition for sharing: {e}", exc_info=True)
352
+ return templates.TemplateResponse(
353
+ "partials/_share_link_snippet.html",
354
+ {"request": request, "error_message": "Could not retrieve Flock definition for sharing."}
355
+ )
356
+
357
+ share_id = uuid.uuid4().hex
358
+ config = SharedLinkConfig(
359
+ share_id=share_id,
360
+ agent_name=start_agent_name,
361
+ flock_definition=flock_definition_str
362
+ )
363
+
364
+ try:
365
+ await store.save_config(config)
366
+ base_url = str(request.base_url)
367
+ full_share_url = f"{base_url.rstrip('/')}/ui/shared-run/{share_id}"
368
+
369
+ logger.info(f"HTMX: Generated share link for agent '{start_agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {full_share_url}")
370
+ return templates.TemplateResponse(
371
+ "partials/_share_link_snippet.html",
372
+ {"request": request, "share_url": full_share_url, "flock_name": current_flock_instance.name, "agent_name": start_agent_name}
373
+ )
374
+ except Exception as e:
375
+ logger.error(f"HTMX: Failed to create share link for agent '{start_agent_name}': {e}", exc_info=True)
376
+ return templates.TemplateResponse(
377
+ "partials/_share_link_snippet.html",
378
+ {"request": request, "error_message": f"Could not generate link: {e!s}"}
379
+ )
380
+ # --- End HTMX Endpoint ---
381
+
382
+ # --- HTMX Endpoint for Generating SHARED CHAT Link Snippet ---
383
+ @app.post("/ui/htmx/share/chat/generate-link", response_class=HTMLResponse, tags=["UI Sharing HTMX"])
384
+ async def htmx_generate_share_chat_link(
385
+ request: Request,
386
+ agent_name: str | None = Form(None), # This is the chat agent
387
+ message_key: str | None = Form(None), # Changed default to None
388
+ history_key: str | None = Form(None), # Changed default to None
389
+ response_key: str | None = Form(None), # Changed default to None
390
+ store: SharedLinkStoreInterface = Depends(get_shared_link_store)
391
+ ):
392
+ if not agent_name:
393
+ logger.warning("HTMX generate share chat link: Agent name not provided.")
394
+ return templates.TemplateResponse(
395
+ "partials/_share_chat_link_snippet.html", # Will create this template
396
+ {"request": request, "error_message": "No agent selected for chat sharing."}
397
+ )
398
+
399
+ current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
400
+ current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
401
+
402
+ if not current_flock_instance or not current_flock_filename:
403
+ logger.error("HTMX Chat Share: Cannot create share link: No Flock is currently loaded.")
404
+ return templates.TemplateResponse(
405
+ "partials/_share_chat_link_snippet.html",
406
+ {"request": request, "error_message": "No Flock loaded. Cannot create share link."}
407
+ )
408
+
409
+ if agent_name not in current_flock_instance.agents:
410
+ logger.error(f"HTMX Chat Share: Agent '{agent_name}' not found in Flock '{current_flock_instance.name}'.")
411
+ return templates.TemplateResponse(
412
+ "partials/_share_chat_link_snippet.html",
413
+ {"request": request, "error_message": f"Agent '{agent_name}' not found in current Flock."}
414
+ )
415
+
416
+ try:
417
+ flock_file_path = FLOCK_FILES_DIR / current_flock_filename
418
+ if not flock_file_path.is_file():
419
+ logger.warning(f"HTMX Chat Share: Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
420
+ flock_definition_str = current_flock_instance.to_yaml()
421
+ else:
422
+ flock_definition_str = flock_file_path.read_text()
423
+ except Exception as e:
424
+ logger.error(f"HTMX Chat Share: Failed to get flock definition for sharing: {e}", exc_info=True)
425
+ return templates.TemplateResponse(
426
+ "partials/_share_chat_link_snippet.html",
427
+ {"request": request, "error_message": "Could not retrieve Flock definition for sharing."}
428
+ )
429
+
430
+ share_id = uuid.uuid4().hex
431
+
432
+ # Explicitly convert empty strings from form to None for optional keys
433
+ actual_message_key = message_key if message_key else None
434
+ actual_history_key = history_key if history_key else None
435
+ actual_response_key = response_key if response_key else None
436
+
437
+ config = SharedLinkConfig(
438
+ share_id=share_id,
439
+ agent_name=agent_name, # agent_name from form is the chat agent
440
+ flock_definition=flock_definition_str,
441
+ share_type="chat",
442
+ chat_message_key=actual_message_key,
443
+ chat_history_key=actual_history_key,
444
+ chat_response_key=actual_response_key
445
+ )
446
+
447
+ try:
448
+ await store.save_config(config)
449
+ base_url = str(request.base_url)
450
+ # Link to the new /chat/shared/{share_id} endpoint
451
+ full_share_url = f"{base_url.rstrip('/')}/chat/shared/{share_id}"
452
+
453
+ logger.info(f"HTMX: Generated share CHAT link for agent '{agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {full_share_url}")
454
+ return templates.TemplateResponse(
455
+ "partials/_share_chat_link_snippet.html", # Will create this template
456
+ {"request": request, "share_url": full_share_url, "flock_name": current_flock_instance.name, "agent_name": agent_name}
457
+ )
458
+ except Exception as e:
459
+ logger.error(f"HTMX Chat Share: Failed to create share link for agent '{agent_name}': {e}", exc_info=True)
460
+ return templates.TemplateResponse(
461
+ "partials/_share_chat_link_snippet.html",
462
+ {"request": request, "error_message": f"Could not generate chat link: {e!s}"}
463
+ )
464
+
465
+ # --- Route for Shared Run Page ---
466
+ @app.get("/ui/shared-run/{share_id}", response_class=HTMLResponse, tags=["UI Sharing"])
467
+ async def page_shared_run(
468
+ request: Request,
469
+ share_id: str,
470
+ store: SharedLinkStoreInterface = Depends(get_shared_link_store),
471
+ ):
472
+ logger.info(f"Accessed shared run page with share_id: {share_id}")
473
+ shared_config = await store.get_config(share_id)
474
+
475
+ if not shared_config:
476
+ logger.warning(f"Share ID {share_id} not found.")
477
+ return templates.TemplateResponse(
478
+ "error_page.html",
479
+ {"request": request, "error_title": "Link Not Found", "error_message": "The shared link does not exist or may have expired."},
480
+ status_code=404
481
+ )
482
+
483
+ agent_name_from_link = shared_config.agent_name
484
+ flock_definition_str = shared_config.flock_definition
485
+ context: dict[str, Any] = {"request": request, "is_shared_run_page": True, "share_id": share_id}
486
+
487
+ try:
488
+ from flock.core.flock import Flock as ConcreteFlock
489
+ loaded_flock = ConcreteFlock.from_yaml(flock_definition_str)
490
+
491
+ # Store the loaded_flock instance in app.state for later retrieval
492
+ if not hasattr(request.app.state, 'shared_flocks'):
493
+ request.app.state.shared_flocks = {}
494
+ request.app.state.shared_flocks[share_id] = loaded_flock
495
+ logger.info(f"Shared Run Page: Stored Flock instance for share_id {share_id} in app.state.")
496
+
497
+ context["flock"] = loaded_flock
498
+ context["selected_agent_name"] = agent_name_from_link # For pre-selection & hidden field
499
+ # flock_definition_str is no longer needed in the template for a hidden field if we reuse the instance
500
+ # context["flock_definition_str"] = flock_definition_str
501
+ logger.info(f"Shared Run Page: Loaded Flock '{loaded_flock.name}' for agent '{agent_name_from_link}'.")
502
+
503
+ if agent_name_from_link not in loaded_flock.agents:
504
+ context["error_message"] = f"Agent '{agent_name_from_link}' not found in the shared Flock definition."
505
+ logger.warning(context["error_message"])
506
+ else:
507
+ agent = loaded_flock.agents[agent_name_from_link]
508
+ input_fields = []
509
+ if agent.input and isinstance(agent.input, str):
510
+ try:
511
+ parsed_spec = parse_schema(agent.input) # parse_schema is imported at top of main.py
512
+ for name, type_str, description in parsed_spec:
513
+ field_info = {"name": name, "type": type_str.lower(), "description": description or ""}
514
+ if "bool" in field_info["type"]: field_info["html_type"] = "checkbox"
515
+ elif "int" in field_info["type"] or "float" in field_info["type"]: field_info["html_type"] = "number"
516
+ elif "list" in field_info["type"] or "dict" in field_info["type"]:
517
+ field_info["html_type"] = "textarea"; field_info["placeholder"] = f"Enter JSON for {field_info['type']}"
518
+ else: field_info["html_type"] = "text"
519
+ input_fields.append(field_info)
520
+ context["input_fields"] = input_fields
521
+ except Exception as e_parse:
522
+ logger.error(f"Shared Run Page: Error parsing input for '{agent_name_from_link}': {e_parse}", exc_info=True)
523
+ context["error_message"] = f"Could not parse inputs for agent '{agent_name_from_link}'."
524
+ else:
525
+ context["input_fields"] = [] # Agent has no inputs defined
526
+
527
+ except Exception as e_load:
528
+ logger.error(f"Shared Run Page: Failed to load Flock from definition for share_id {share_id}: {e_load}", exc_info=True)
529
+ context["error_message"] = f"Fatal: Could not load the shared Flock configuration: {e_load!s}"
530
+ context["flock"] = None
531
+ context["selected_agent_name"] = agent_name_from_link # Still pass for potential error display
532
+ context["input_fields"] = []
533
+ # context["flock_definition_str"] = flock_definition_str # Not needed if not sent to template
534
+
535
+ try:
536
+ current_theme_name = get_current_theme_name()
537
+ context["theme_css"] = generate_theme_css_web(current_theme_name)
538
+ context["active_theme_name"] = current_theme_name or DEFAULT_THEME_NAME
539
+ except Exception as e_theme:
540
+ logger.error(f"Shared Run Page: Error generating theme: {e_theme}", exc_info=True)
541
+ context["theme_css"] = ""
542
+ context["active_theme_name"] = DEFAULT_THEME_NAME
543
+
544
+ # The shared_run_page.html will now be a simple wrapper that includes _execution_form.html
545
+ return templates.TemplateResponse("shared_run_page.html", context)
546
+
547
+ # --- End Route for Shared Run Page ---
548
+
150
549
  def generate_theme_css_web(theme_name: str | None) -> str:
151
550
  if not THEME_LOADER_AVAILABLE or THEMES_DIR is None: return ""
152
551
 
@@ -227,6 +626,7 @@ def get_base_context_web(
227
626
  current_flock_filename_from_state: str | None = getattr(request.app.state, "flock_filename", None)
228
627
  theme_name = get_current_theme_name()
229
628
  theme_css = generate_theme_css_web(theme_name)
629
+
230
630
  return {
231
631
  "request": request,
232
632
  "current_flock": flock_instance_from_state,
@@ -236,13 +636,18 @@ def get_base_context_web(
236
636
  "ui_mode": ui_mode,
237
637
  "theme_css": theme_css,
238
638
  "active_theme_name": theme_name,
239
- "chat_enabled": getattr(request.app.state, "chat_enabled", False),
639
+ "chat_enabled": getattr(request.app.state, "chat_enabled", False), # Reverted to app.state
240
640
  }
241
641
 
242
642
  @app.get("/", response_class=HTMLResponse, tags=["UI Pages"])
243
643
  async def page_dashboard(
244
644
  request: Request, error: str = None, success: str = None, ui_mode: str = Query(None)
245
645
  ):
646
+ # Handle initial redirect if flagged during app startup
647
+ if getattr(request.app.state, "initial_redirect_to_chat", False):
648
+ logger.info("Initial redirect to CHAT page triggered from dashboard (FLOCK_START_MODE='chat').")
649
+ return RedirectResponse(url="/chat", status_code=307)
650
+
246
651
  effective_ui_mode = ui_mode
247
652
  flock_is_preloaded = hasattr(request.app.state, "flock_instance") and request.app.state.flock_instance is not None
248
653
 
@@ -360,11 +765,10 @@ async def ui_load_flock_by_name_action(request: Request, selected_flock_filename
360
765
  ui_mode_query = request.query_params.get("ui_mode", "standalone")
361
766
  if loaded_flock:
362
767
  success_message_text = f"Flock '{loaded_flock.name}' loaded from '{selected_flock_filename}'."
363
- response_headers["HX-Push-Url"] = "/ui/editor/properties?ui_mode=" + request.query_params.get("ui_mode", "standalone")
768
+ response_headers["HX-Push-Url"] = "/ui/editor/execute?ui_mode=" + ui_mode_query
364
769
  response_headers["HX-Trigger"] = json.dumps({"flockLoaded": None, "notify": {"type": "success", "message": success_message_text}})
365
- # Use get_base_context_web to ensure all necessary context vars are present for the partial
366
770
  context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
367
- return templates.TemplateResponse("partials/_flock_properties_form.html", context, headers=response_headers)
771
+ return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
368
772
  else:
369
773
  error_message_text = f"Failed to load flock file '{selected_flock_filename}'."
370
774
  response_headers["HX-Trigger"] = json.dumps({"notify": {"type": "error", "message": error_message_text}})
@@ -392,16 +796,14 @@ async def ui_load_flock_by_upload_action(request: Request, flock_file_upload: Up
392
796
  loaded_flock = load_flock_from_file_service(filename_to_load, request.app.state)
393
797
  if loaded_flock:
394
798
  success_message_text = f"Flock '{loaded_flock.name}' loaded from '{filename_to_load}'."
395
- response_headers["HX-Push-Url"] = f"/ui/editor/properties?ui_mode={ui_mode_query}"
799
+ response_headers["HX-Push-Url"] = f"/ui/editor/execute?ui_mode={ui_mode_query}"
396
800
  response_headers["HX-Trigger"] = json.dumps({"flockLoaded": None, "flockFileListChanged": None, "notify": {"type": "success", "message": success_message_text}})
397
- # CORRECTED CALL:
398
801
  context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
399
- return templates.TemplateResponse("partials/_flock_properties_form.html", context, headers=response_headers)
802
+ return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
400
803
  else: error_message_text = f"Failed to process uploaded '{filename_to_load}'."
401
804
 
402
805
  final_error_msg = error_message_text or "Upload failed."
403
806
  response_headers["HX-Trigger"] = json.dumps({"notify": {"type": "error", "message": final_error_msg}})
404
- # CORRECTED CALL:
405
807
  context = get_base_context_web(request, error=final_error_msg, ui_mode=ui_mode_query)
406
808
  return templates.TemplateResponse("partials/_create_flock_form.html", context, headers=response_headers)
407
809
 
@@ -409,16 +811,14 @@ async def ui_load_flock_by_upload_action(request: Request, flock_file_upload: Up
409
811
  async def ui_create_flock_action(request: Request, flock_name: str = Form(...), default_model: str = Form(None), description: str = Form(None)):
410
812
  ui_mode_query = request.query_params.get("ui_mode", "standalone")
411
813
  if not flock_name.strip():
412
- # CORRECTED CALL:
413
814
  context = get_base_context_web(request, error="Flock name cannot be empty.", ui_mode=ui_mode_query)
414
815
  return templates.TemplateResponse("partials/_create_flock_form.html", context)
415
816
 
416
817
  new_flock = create_new_flock_service(flock_name, default_model, description, request.app.state)
417
- success_msg_text = f"New flock '{new_flock.name}' created. Configure properties and save."
418
- response_headers = {"HX-Push-Url": f"/ui/editor/properties?ui_mode={ui_mode_query}", "HX-Trigger": json.dumps({"flockLoaded": None, "notify": {"type": "success", "message": success_msg_text}})}
419
- # CORRECTED CALL:
818
+ success_msg_text = f"New flock '{new_flock.name}' created. Navigating to Execute page. Configure properties and agents as needed."
819
+ response_headers = {"HX-Push-Url": f"/ui/editor/execute?ui_mode={ui_mode_query}", "HX-Trigger": json.dumps({"flockLoaded": None, "notify": {"type": "success", "message": success_msg_text}})}
420
820
  context = get_base_context_web(request, success=success_msg_text, ui_mode=ui_mode_query)
421
- return templates.TemplateResponse("partials/_flock_properties_form.html", context, headers=response_headers)
821
+ return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
422
822
 
423
823
  # --- Settings Page & Endpoints ---
424
824
  @app.get("/ui/settings", response_class=HTMLResponse, tags=["UI Pages"])
@@ -1,5 +1,5 @@
1
1
  # src/flock/webapp/app/services/flock_service.py
2
- from typing import TYPE_CHECKING, Optional
2
+ from typing import TYPE_CHECKING, Any, Optional
3
3
 
4
4
  import yaml
5
5
 
@@ -248,25 +248,50 @@ def remove_agent_from_current_flock_service(agent_name: str, app_state: object)
248
248
 
249
249
 
250
250
  async def run_current_flock_service(
251
- start_agent_name: str, inputs: dict, app_state: object
252
- ) -> dict | str:
253
- """Runs the Flock instance from app_state."""
254
- current_flock: Flock | None = getattr(app_state, 'flock_instance', None)
251
+ start_agent_name: str,
252
+ inputs: dict[str, Any],
253
+ app_state: Any,
254
+ ) -> dict[str, Any]:
255
+ """Runs the specified agent from the current flock instance in app_state."""
256
+ logger.info(f"Attempting to run agent: {start_agent_name} using flock from app_state.")
257
+
258
+ current_flock: Flock | None = getattr(app_state, "flock_instance", None)
259
+ run_store: RunStore | None = getattr(app_state, "run_store", None)
260
+
255
261
  if not current_flock:
256
- logger.error("Service: Execution failed, no flock loaded.")
257
- return "Error: No flock loaded."
258
- if not start_agent_name or start_agent_name not in current_flock.agents:
259
- logger.error(f"Service: Execution failed, start agent '{start_agent_name}' not found in flock '{current_flock.name}'.")
260
- return f"Error: Start agent '{start_agent_name}' not found."
262
+ logger.error("Run service: No Flock instance available in app_state.")
263
+ return {"error": "No Flock loaded in the application."}
264
+ if not run_store:
265
+ logger.error("Run service: No RunStore instance available in app_state.")
266
+ # Attempt to initialize a default run_store if missing and this service is critical
267
+ # This might indicate an issue in the application lifecycle setup
268
+ logger.warning("Run service: Initializing a default RunStore as none was found in app_state.")
269
+ run_store = RunStore()
270
+ setattr(app_state, "run_store", run_store)
271
+ # Also update global DI if this is how it's managed elsewhere for consistency,
272
+ # though ideally DI setup handles this more centrally.
273
+ # from flock.webapp.app.dependencies import set_global_flock_services
274
+ # set_global_flock_services(current_flock, run_store)
275
+
276
+
277
+ if start_agent_name not in current_flock.agents:
278
+ logger.error(f"Run service: Agent '{start_agent_name}' not found in current flock '{current_flock.name}'.")
279
+ return {"error": f"Agent '{start_agent_name}' not found."}
280
+
261
281
  try:
262
- logger.info(f"Service: Running flock '{current_flock.name}' starting with agent '{start_agent_name}'.")
282
+ logger.info(f"Executing agent '{start_agent_name}' from flock '{current_flock.name}' using app_state.")
283
+ # Direct execution using the flock from app_state
263
284
  result = await current_flock.run_async(
264
285
  start_agent=start_agent_name, input=inputs, box_result=False
265
286
  )
287
+ # Store run details using the run_store from app_state
288
+ if hasattr(run_store, "add_run_details"): # Check if RunStore has this method
289
+ run_id = result.get("run_id", "unknown_run_id") # Assuming run_async result might contain run_id
290
+ run_store.add_run_details(run_id=run_id, agent_name=start_agent_name, inputs=inputs, outputs=result)
266
291
  return result
267
292
  except Exception as e:
268
- logger.error(f"Service: Error during flock execution for '{current_flock.name}': {e}", exc_info=True)
269
- return f"Error: {e!s}"
293
+ logger.error(f"Run service: Error during agent execution: {e}", exc_info=True)
294
+ return {"error": f"An error occurred: {e}"}
270
295
 
271
296
 
272
297
  def get_registered_items_service(item_type: str) -> list[dict]:
@@ -0,0 +1,43 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class SharedLinkConfig(BaseModel):
7
+ """Configuration for a shared Flock agent execution link or chat session."""
8
+
9
+ share_id: str = Field(..., description="Unique identifier for the shared link.")
10
+ agent_name: str = Field(..., description="The name of the agent being shared (for run) or the chat agent (for chat).")
11
+ flock_definition: str = Field(..., description="The YAML/JSON string definition of the Flock the agent belongs to.")
12
+ created_at: datetime = Field(
13
+ default_factory=datetime.utcnow, description="Timestamp of when the link was created."
14
+ )
15
+ share_type: str = Field(default="agent_run", description="Type of share: 'agent_run' or 'chat'")
16
+
17
+ # Chat-specific settings (only relevant if share_type is 'chat')
18
+ chat_message_key: str | None = Field(None, description="Message key for chat input mapping.")
19
+ chat_history_key: str | None = Field(None, description="History key for chat input mapping.")
20
+ chat_response_key: str | None = Field(None, description="Response key for chat output mapping.")
21
+
22
+ # Placeholder for future enhancement: pre-filled input values
23
+ # input_values: Optional[Dict[str, Any]] = Field(
24
+ # None, description="Optional pre-filled input values for the agent."
25
+ # )
26
+
27
+ model_config = {
28
+ "from_attributes": True,
29
+ "json_schema_extra": {
30
+ "examples": [
31
+ {
32
+ "share_id": "abcdef123456",
33
+ "agent_name": "MyChatAgent",
34
+ "flock_definition": "name: MySharedFlock\nagents:\n MyChatAgent:\n input: 'message: str'\n output: 'response: str'\n # ... rest of flock YAML ...",
35
+ "created_at": "2023-10-26T10:00:00Z",
36
+ "share_type": "chat",
37
+ "chat_message_key": "user_input",
38
+ "chat_history_key": "conversation_history",
39
+ "chat_response_key": "agent_output"
40
+ }
41
+ ]
42
+ }
43
+ }