flock-core 0.4.0b48__py3-none-any.whl → 0.4.0b49__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
@@ -1,14 +1,28 @@
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
+ from fastapi import (
13
+ Depends,
14
+ FastAPI,
15
+ File,
16
+ Form,
17
+ HTTPException,
18
+ Query,
19
+ Request,
20
+ UploadFile,
21
+ )
9
22
  from fastapi.responses import HTMLResponse, RedirectResponse
10
23
  from fastapi.staticfiles import StaticFiles
11
24
  from fastapi.templating import Jinja2Templates
25
+ from pydantic import BaseModel
12
26
 
13
27
  from flock.core.api.endpoints import create_api_router
14
28
  from flock.core.api.run_store import RunStore
@@ -16,6 +30,7 @@ from flock.core.api.run_store import RunStore
16
30
  # Import core Flock components and API related modules
17
31
  from flock.core.flock import Flock # For type hinting
18
32
  from flock.core.logging.logging import get_logger # For logging
33
+ from flock.core.util.spliter import parse_schema
19
34
 
20
35
  # Import UI-specific routers
21
36
  from flock.webapp.app.api import (
@@ -27,6 +42,7 @@ from flock.webapp.app.api import (
27
42
  from flock.webapp.app.config import (
28
43
  DEFAULT_THEME_NAME,
29
44
  FLOCK_FILES_DIR,
45
+ SHARED_LINKS_DB_PATH,
30
46
  THEMES_DIR,
31
47
  get_current_theme_name,
32
48
  )
@@ -34,7 +50,9 @@ from flock.webapp.app.config import (
34
50
  # Import dependency management and config
35
51
  from flock.webapp.app.dependencies import (
36
52
  get_pending_custom_endpoints_and_clear,
53
+ get_shared_link_store,
37
54
  set_global_flock_services,
55
+ set_global_shared_link_store,
38
56
  )
39
57
 
40
58
  # Import service functions (which now expect app_state)
@@ -47,6 +65,13 @@ from flock.webapp.app.services.flock_service import (
47
65
  # Note: get_current_flock_instance/filename are removed from service,
48
66
  # as main.py will use request.app.state for this.
49
67
  )
68
+
69
+ # Added for share link creation
70
+ from flock.webapp.app.services.sharing_models import SharedLinkConfig
71
+ from flock.webapp.app.services.sharing_store import (
72
+ SharedLinkStoreInterface,
73
+ SQLiteSharedLinkStore,
74
+ )
50
75
  from flock.webapp.app.theme_mapper import alacritty_to_pico
51
76
 
52
77
  logger = get_logger("webapp.main")
@@ -113,6 +138,78 @@ async def lifespan(app: FastAPI):
113
138
  # by `start_unified_server` in `webapp/run.py` *before* uvicorn starts the app.
114
139
  # The call to `set_global_flock_services` also happens there.
115
140
 
141
+ # Initialize and set the SharedLinkStore
142
+ try:
143
+ logger.info(f"Initializing SharedLinkStore with DB path: {SHARED_LINKS_DB_PATH}")
144
+ shared_link_store = SQLiteSharedLinkStore(db_path=str(SHARED_LINKS_DB_PATH))
145
+ await shared_link_store.initialize() # Create tables if they don't exist
146
+ set_global_shared_link_store(shared_link_store)
147
+ logger.info("SharedLinkStore initialized and set globally.")
148
+ except Exception as e:
149
+ logger.error(f"Failed to initialize SharedLinkStore: {e}", exc_info=True)
150
+
151
+ # Configure chat features based on environment variables
152
+ # These are typically set by __init__.py when launching with --chat or --web --chat
153
+ flock_start_mode = os.environ.get("FLOCK_START_MODE")
154
+ flock_chat_enabled_env = os.environ.get("FLOCK_CHAT_ENABLED", "false").lower() == "true"
155
+
156
+ should_enable_chat_routes = False
157
+ if flock_start_mode == "chat":
158
+ should_enable_chat_routes = True
159
+ app.state.initial_redirect_to_chat = True # Signal dashboard to redirect
160
+ logger.info("FLOCK_START_MODE='chat'. Chat routes will be enabled and initial redirect to chat is set.")
161
+ elif flock_chat_enabled_env:
162
+ should_enable_chat_routes = True
163
+ logger.info("FLOCK_CHAT_ENABLED='true'. Chat routes will be enabled.")
164
+
165
+ app.state.chat_enabled = should_enable_chat_routes # For context in templates
166
+
167
+ if should_enable_chat_routes:
168
+ try:
169
+ from flock.webapp.app.chat import router as chat_router
170
+ app.include_router(chat_router, tags=["Chat"])
171
+ logger.info("Chat routes included in the application.")
172
+ except Exception as e:
173
+ logger.error(f"Failed to include chat routes during lifespan startup: {e}", exc_info=True)
174
+
175
+ # If in standalone chat mode, strip non-essential UI routes
176
+ if flock_start_mode == "chat":
177
+ from fastapi.routing import APIRoute
178
+ logger.info("FLOCK_START_MODE='chat'. Stripping non-chat UI routes.")
179
+
180
+ # Define tags for routes to KEEP.
181
+ # "Chat" for primary chat functionality.
182
+ # "Chat Sharing" for shared chat links & pages.
183
+ # API tags might be needed if chat agents make internal API calls or for general health/docs.
184
+ # Public static files (/static/...) are typically handled by app.mount and not in app.router.routes directly this way.
185
+ allowed_tags_for_chat_mode = {
186
+ "Chat",
187
+ "Chat Sharing",
188
+ "Flock API Core", # Keep core API for potential underlying needs
189
+ "Flock API Custom Endpoints" # Keep custom API endpoints
190
+ }
191
+
192
+ def _route_is_allowed_in_chat_mode(route: APIRoute) -> bool:
193
+ # Keep documentation (e.g. /docs, /openapi.json - usually no tags or specific tags)
194
+ # and non-API utility routes (often no tags).
195
+ if not hasattr(route, "tags") or not route.tags:
196
+ # Check common doc paths explicitly as they might not have tags or might have default tags
197
+ if route.path in ["/docs", "/openapi.json", "/redoc"]:
198
+ return True
199
+ # Allow other untagged routes for now, assuming they are essential (e.g. static mounts if they appeared here)
200
+ # This might need refinement if untagged UI routes exist.
201
+ return True
202
+ return any(tag in allowed_tags_for_chat_mode for tag in route.tags)
203
+
204
+ original_route_count = len(app.router.routes)
205
+ app.router.routes = [r for r in app.router.routes if _route_is_allowed_in_chat_mode(r)]
206
+ num_removed = original_route_count - len(app.router.routes)
207
+ logger.info(f"Stripped {num_removed} routes for chat-only mode. {len(app.router.routes)} routes remaining.")
208
+
209
+ if num_removed > 0 and hasattr(app, "openapi_schema"):
210
+ app.openapi_schema = None # Clear cached OpenAPI schema to regenerate
211
+ logger.info("Cleared OpenAPI schema cache due to route removal.")
212
+
116
213
  # Add custom routes if any were passed during server startup
117
214
  # These are retrieved from the dependency module where `start_unified_server` stored them.
118
215
  pending_endpoints = get_pending_custom_endpoints_and_clear()
@@ -147,6 +244,301 @@ app.include_router(agent_management.router, prefix="/ui/api/flock", tags=["UI Ag
147
244
  app.include_router(execution.router, prefix="/ui/api/flock", tags=["UI Execution"])
148
245
  app.include_router(registry_viewer.router, prefix="/ui/api/registry", tags=["UI Registry"])
149
246
 
247
+ # --- Share Link API Models and Endpoint ---
248
+ class CreateShareLinkRequest(BaseModel):
249
+ agent_name: str
250
+
251
+ class CreateShareLinkResponse(BaseModel):
252
+ share_url: str
253
+
254
+ @app.post("/api/v1/share/link", response_model=CreateShareLinkResponse, tags=["UI Sharing"])
255
+ async def create_share_link(
256
+ request: Request,
257
+ request_data: CreateShareLinkRequest,
258
+ store: SharedLinkStoreInterface = Depends(get_shared_link_store)
259
+ ):
260
+ """Creates a new shareable link for an agent."""
261
+ share_id = uuid.uuid4().hex
262
+ agent_name = request_data.agent_name
263
+
264
+ if not agent_name: # Basic validation
265
+ raise HTTPException(status_code=400, detail="Agent name cannot be empty.")
266
+
267
+ current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
268
+ current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
269
+
270
+ if not current_flock_instance or not current_flock_filename:
271
+ logger.error("Cannot create share link: No Flock is currently loaded in the application state.")
272
+ raise HTTPException(status_code=400, detail="No Flock loaded. Cannot create share link.")
273
+
274
+ if agent_name not in current_flock_instance.agents:
275
+ logger.error(f"Agent '{agent_name}' not found in currently loaded Flock '{current_flock_instance.name}'.")
276
+ raise HTTPException(status_code=404, detail=f"Agent '{agent_name}' not found in current Flock.")
277
+
278
+ try:
279
+ flock_file_path = FLOCK_FILES_DIR / current_flock_filename
280
+ if not flock_file_path.is_file():
281
+ logger.warning(f"Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
282
+ flock_definition_str = current_flock_instance.to_yaml()
283
+ else:
284
+ flock_definition_str = flock_file_path.read_text()
285
+ except Exception as e:
286
+ logger.error(f"Failed to get flock definition for sharing: {e}", exc_info=True)
287
+ raise HTTPException(status_code=500, detail="Could not retrieve Flock definition for sharing.")
288
+
289
+ config = SharedLinkConfig(
290
+ share_id=share_id,
291
+ agent_name=agent_name,
292
+ flock_definition=flock_definition_str
293
+ )
294
+ try:
295
+ await store.save_config(config)
296
+ share_url = f"/ui/shared-run/{share_id}" # Relative URL for client-side navigation
297
+ logger.info(f"Created share link for agent '{agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {share_url}")
298
+ return CreateShareLinkResponse(share_url=share_url)
299
+ except Exception as e:
300
+ logger.error(f"Failed to create share link for agent '{agent_name}': {e}", exc_info=True)
301
+ raise HTTPException(status_code=500, detail=f"Failed to create share link: {e!s}")
302
+
303
+ # --- End Share Link API ---
304
+
305
+ # --- HTMX Endpoint for Generating Share Link Snippet ---
306
+ @app.post("/ui/htmx/share/generate-link", response_class=HTMLResponse, tags=["UI Sharing HTMX"])
307
+ async def htmx_generate_share_link(
308
+ request: Request,
309
+ start_agent_name: str | None = Form(None),
310
+ store: SharedLinkStoreInterface = Depends(get_shared_link_store)
311
+ ):
312
+ if not start_agent_name:
313
+ logger.warning("HTMX generate share link: Agent name not provided.")
314
+ return templates.TemplateResponse(
315
+ "partials/_share_link_snippet.html",
316
+ {"request": request, "error_message": "No agent selected to share."}
317
+ )
318
+
319
+ current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
320
+ current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
321
+
322
+ if not current_flock_instance or not current_flock_filename:
323
+ logger.error("HTMX: Cannot create share link: No Flock is currently loaded.")
324
+ return templates.TemplateResponse(
325
+ "partials/_share_link_snippet.html",
326
+ {"request": request, "error_message": "No Flock loaded. Cannot create share link."}
327
+ )
328
+
329
+ if start_agent_name not in current_flock_instance.agents:
330
+ logger.error(f"HTMX: Agent '{start_agent_name}' not found in Flock '{current_flock_instance.name}'.")
331
+ return templates.TemplateResponse(
332
+ "partials/_share_link_snippet.html",
333
+ {"request": request, "error_message": f"Agent '{start_agent_name}' not found in current Flock."}
334
+ )
335
+
336
+ try:
337
+ flock_file_path = FLOCK_FILES_DIR / current_flock_filename
338
+ if not flock_file_path.is_file():
339
+ logger.warning(f"HTMX: Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
340
+ flock_definition_str = current_flock_instance.to_yaml()
341
+ else:
342
+ flock_definition_str = flock_file_path.read_text()
343
+ except Exception as e:
344
+ logger.error(f"HTMX: Failed to get flock definition for sharing: {e}", exc_info=True)
345
+ return templates.TemplateResponse(
346
+ "partials/_share_link_snippet.html",
347
+ {"request": request, "error_message": "Could not retrieve Flock definition for sharing."}
348
+ )
349
+
350
+ share_id = uuid.uuid4().hex
351
+ config = SharedLinkConfig(
352
+ share_id=share_id,
353
+ agent_name=start_agent_name,
354
+ flock_definition=flock_definition_str
355
+ )
356
+
357
+ try:
358
+ await store.save_config(config)
359
+ base_url = str(request.base_url)
360
+ full_share_url = f"{base_url.rstrip('/')}/ui/shared-run/{share_id}"
361
+
362
+ 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}")
363
+ return templates.TemplateResponse(
364
+ "partials/_share_link_snippet.html",
365
+ {"request": request, "share_url": full_share_url, "flock_name": current_flock_instance.name, "agent_name": start_agent_name}
366
+ )
367
+ except Exception as e:
368
+ logger.error(f"HTMX: Failed to create share link for agent '{start_agent_name}': {e}", exc_info=True)
369
+ return templates.TemplateResponse(
370
+ "partials/_share_link_snippet.html",
371
+ {"request": request, "error_message": f"Could not generate link: {e!s}"}
372
+ )
373
+ # --- End HTMX Endpoint ---
374
+
375
+ # --- HTMX Endpoint for Generating SHARED CHAT Link Snippet ---
376
+ @app.post("/ui/htmx/share/chat/generate-link", response_class=HTMLResponse, tags=["UI Sharing HTMX"])
377
+ async def htmx_generate_share_chat_link(
378
+ request: Request,
379
+ agent_name: str | None = Form(None), # This is the chat agent
380
+ message_key: str | None = Form(None), # Changed default to None
381
+ history_key: str | None = Form(None), # Changed default to None
382
+ response_key: str | None = Form(None), # Changed default to None
383
+ store: SharedLinkStoreInterface = Depends(get_shared_link_store)
384
+ ):
385
+ if not agent_name:
386
+ logger.warning("HTMX generate share chat link: Agent name not provided.")
387
+ return templates.TemplateResponse(
388
+ "partials/_share_chat_link_snippet.html", # Will create this template
389
+ {"request": request, "error_message": "No agent selected for chat sharing."}
390
+ )
391
+
392
+ current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
393
+ current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
394
+
395
+ if not current_flock_instance or not current_flock_filename:
396
+ logger.error("HTMX Chat Share: Cannot create share link: No Flock is currently loaded.")
397
+ return templates.TemplateResponse(
398
+ "partials/_share_chat_link_snippet.html",
399
+ {"request": request, "error_message": "No Flock loaded. Cannot create share link."}
400
+ )
401
+
402
+ if agent_name not in current_flock_instance.agents:
403
+ logger.error(f"HTMX Chat Share: Agent '{agent_name}' not found in Flock '{current_flock_instance.name}'.")
404
+ return templates.TemplateResponse(
405
+ "partials/_share_chat_link_snippet.html",
406
+ {"request": request, "error_message": f"Agent '{agent_name}' not found in current Flock."}
407
+ )
408
+
409
+ try:
410
+ flock_file_path = FLOCK_FILES_DIR / current_flock_filename
411
+ if not flock_file_path.is_file():
412
+ logger.warning(f"HTMX Chat Share: Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
413
+ flock_definition_str = current_flock_instance.to_yaml()
414
+ else:
415
+ flock_definition_str = flock_file_path.read_text()
416
+ except Exception as e:
417
+ logger.error(f"HTMX Chat Share: Failed to get flock definition for sharing: {e}", exc_info=True)
418
+ return templates.TemplateResponse(
419
+ "partials/_share_chat_link_snippet.html",
420
+ {"request": request, "error_message": "Could not retrieve Flock definition for sharing."}
421
+ )
422
+
423
+ share_id = uuid.uuid4().hex
424
+
425
+ # Explicitly convert empty strings from form to None for optional keys
426
+ actual_message_key = message_key if message_key else None
427
+ actual_history_key = history_key if history_key else None
428
+ actual_response_key = response_key if response_key else None
429
+
430
+ config = SharedLinkConfig(
431
+ share_id=share_id,
432
+ agent_name=agent_name, # agent_name from form is the chat agent
433
+ flock_definition=flock_definition_str,
434
+ share_type="chat",
435
+ chat_message_key=actual_message_key,
436
+ chat_history_key=actual_history_key,
437
+ chat_response_key=actual_response_key
438
+ )
439
+
440
+ try:
441
+ await store.save_config(config)
442
+ base_url = str(request.base_url)
443
+ # Link to the new /chat/shared/{share_id} endpoint
444
+ full_share_url = f"{base_url.rstrip('/')}/chat/shared/{share_id}"
445
+
446
+ 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}")
447
+ return templates.TemplateResponse(
448
+ "partials/_share_chat_link_snippet.html", # Will create this template
449
+ {"request": request, "share_url": full_share_url, "flock_name": current_flock_instance.name, "agent_name": agent_name}
450
+ )
451
+ except Exception as e:
452
+ logger.error(f"HTMX Chat Share: Failed to create share link for agent '{agent_name}': {e}", exc_info=True)
453
+ return templates.TemplateResponse(
454
+ "partials/_share_chat_link_snippet.html",
455
+ {"request": request, "error_message": f"Could not generate chat link: {e!s}"}
456
+ )
457
+
458
+ # --- Route for Shared Run Page ---
459
+ @app.get("/ui/shared-run/{share_id}", response_class=HTMLResponse, tags=["UI Sharing"])
460
+ async def page_shared_run(
461
+ request: Request,
462
+ share_id: str,
463
+ store: SharedLinkStoreInterface = Depends(get_shared_link_store),
464
+ ):
465
+ logger.info(f"Accessed shared run page with share_id: {share_id}")
466
+ shared_config = await store.get_config(share_id)
467
+
468
+ if not shared_config:
469
+ logger.warning(f"Share ID {share_id} not found.")
470
+ return templates.TemplateResponse(
471
+ "error_page.html",
472
+ {"request": request, "error_title": "Link Not Found", "error_message": "The shared link does not exist or may have expired."},
473
+ status_code=404
474
+ )
475
+
476
+ agent_name_from_link = shared_config.agent_name
477
+ flock_definition_str = shared_config.flock_definition
478
+ context: dict[str, Any] = {"request": request, "is_shared_run_page": True, "share_id": share_id}
479
+
480
+ try:
481
+ from flock.core.flock import Flock as ConcreteFlock
482
+ loaded_flock = ConcreteFlock.from_yaml(flock_definition_str)
483
+
484
+ # Store the loaded_flock instance in app.state for later retrieval
485
+ if not hasattr(request.app.state, 'shared_flocks'):
486
+ request.app.state.shared_flocks = {}
487
+ request.app.state.shared_flocks[share_id] = loaded_flock
488
+ logger.info(f"Shared Run Page: Stored Flock instance for share_id {share_id} in app.state.")
489
+
490
+ context["flock"] = loaded_flock
491
+ context["selected_agent_name"] = agent_name_from_link # For pre-selection & hidden field
492
+ # flock_definition_str is no longer needed in the template for a hidden field if we reuse the instance
493
+ # context["flock_definition_str"] = flock_definition_str
494
+ logger.info(f"Shared Run Page: Loaded Flock '{loaded_flock.name}' for agent '{agent_name_from_link}'.")
495
+
496
+ if agent_name_from_link not in loaded_flock.agents:
497
+ context["error_message"] = f"Agent '{agent_name_from_link}' not found in the shared Flock definition."
498
+ logger.warning(context["error_message"])
499
+ else:
500
+ agent = loaded_flock.agents[agent_name_from_link]
501
+ input_fields = []
502
+ if agent.input and isinstance(agent.input, str):
503
+ try:
504
+ parsed_spec = parse_schema(agent.input) # parse_schema is imported at top of main.py
505
+ for name, type_str, description in parsed_spec:
506
+ field_info = {"name": name, "type": type_str.lower(), "description": description or ""}
507
+ if "bool" in field_info["type"]: field_info["html_type"] = "checkbox"
508
+ elif "int" in field_info["type"] or "float" in field_info["type"]: field_info["html_type"] = "number"
509
+ elif "list" in field_info["type"] or "dict" in field_info["type"]:
510
+ field_info["html_type"] = "textarea"; field_info["placeholder"] = f"Enter JSON for {field_info['type']}"
511
+ else: field_info["html_type"] = "text"
512
+ input_fields.append(field_info)
513
+ context["input_fields"] = input_fields
514
+ except Exception as e_parse:
515
+ logger.error(f"Shared Run Page: Error parsing input for '{agent_name_from_link}': {e_parse}", exc_info=True)
516
+ context["error_message"] = f"Could not parse inputs for agent '{agent_name_from_link}'."
517
+ else:
518
+ context["input_fields"] = [] # Agent has no inputs defined
519
+
520
+ except Exception as e_load:
521
+ logger.error(f"Shared Run Page: Failed to load Flock from definition for share_id {share_id}: {e_load}", exc_info=True)
522
+ context["error_message"] = f"Fatal: Could not load the shared Flock configuration: {e_load!s}"
523
+ context["flock"] = None
524
+ context["selected_agent_name"] = agent_name_from_link # Still pass for potential error display
525
+ context["input_fields"] = []
526
+ # context["flock_definition_str"] = flock_definition_str # Not needed if not sent to template
527
+
528
+ try:
529
+ current_theme_name = get_current_theme_name()
530
+ context["theme_css"] = generate_theme_css_web(current_theme_name)
531
+ context["active_theme_name"] = current_theme_name or DEFAULT_THEME_NAME
532
+ except Exception as e_theme:
533
+ logger.error(f"Shared Run Page: Error generating theme: {e_theme}", exc_info=True)
534
+ context["theme_css"] = ""
535
+ context["active_theme_name"] = DEFAULT_THEME_NAME
536
+
537
+ # The shared_run_page.html will now be a simple wrapper that includes _execution_form.html
538
+ return templates.TemplateResponse("shared_run_page.html", context)
539
+
540
+ # --- End Route for Shared Run Page ---
541
+
150
542
  def generate_theme_css_web(theme_name: str | None) -> str:
151
543
  if not THEME_LOADER_AVAILABLE or THEMES_DIR is None: return ""
152
544
 
@@ -227,6 +619,7 @@ def get_base_context_web(
227
619
  current_flock_filename_from_state: str | None = getattr(request.app.state, "flock_filename", None)
228
620
  theme_name = get_current_theme_name()
229
621
  theme_css = generate_theme_css_web(theme_name)
622
+
230
623
  return {
231
624
  "request": request,
232
625
  "current_flock": flock_instance_from_state,
@@ -236,13 +629,18 @@ def get_base_context_web(
236
629
  "ui_mode": ui_mode,
237
630
  "theme_css": theme_css,
238
631
  "active_theme_name": theme_name,
239
- "chat_enabled": getattr(request.app.state, "chat_enabled", False),
632
+ "chat_enabled": getattr(request.app.state, "chat_enabled", False), # Reverted to app.state
240
633
  }
241
634
 
242
635
  @app.get("/", response_class=HTMLResponse, tags=["UI Pages"])
243
636
  async def page_dashboard(
244
637
  request: Request, error: str = None, success: str = None, ui_mode: str = Query(None)
245
638
  ):
639
+ # Handle initial redirect if flagged during app startup
640
+ if getattr(request.app.state, "initial_redirect_to_chat", False):
641
+ logger.info("Initial redirect to CHAT page triggered from dashboard (FLOCK_START_MODE='chat').")
642
+ return RedirectResponse(url="/chat", status_code=307)
643
+
246
644
  effective_ui_mode = ui_mode
247
645
  flock_is_preloaded = hasattr(request.app.state, "flock_instance") and request.app.state.flock_instance is not None
248
646
 
@@ -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
+ }