diff --git a/ai-hub/ai_hub.egg-info/PKG-INFO b/ai-hub/ai_hub.egg-info/PKG-INFO new file mode 100644 index 0000000..91ff11c --- /dev/null +++ b/ai-hub/ai_hub.egg-info/PKG-INFO @@ -0,0 +1,42 @@ +Metadata-Version: 2.4 +Name: ai-hub +Version: 0.1.0 +Summary: An AI Model Hub Service with PostgreSQL and FAISS integration. +Author: Jerry Xie +Author-email: axieyangb@google.com +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.11 +Requires-Dist: fastapi +Requires-Dist: uvicorn[standard] +Requires-Dist: google-generativeai +Requires-Dist: python-dotenv +Requires-Dist: openai +Requires-Dist: pytest +Requires-Dist: requests +Requires-Dist: anyio +Requires-Dist: sqlalchemy +Requires-Dist: psycopg2-binary +Requires-Dist: pytest-asyncio +Requires-Dist: pytest-tornasync +Requires-Dist: pytest-trio +Requires-Dist: pytest-mock +Requires-Dist: numpy +Requires-Dist: faiss-cpu +Requires-Dist: aioresponses +Requires-Dist: python-multipart +Requires-Dist: PyJWT +Requires-Dist: tenacity +Requires-Dist: litellm +Requires-Dist: tiktoken +Requires-Dist: grpcio +Requires-Dist: grpcio-tools +Requires-Dist: grpcio-reflection +Requires-Dist: croniter +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary diff --git a/ai-hub/ai_hub.egg-info/SOURCES.txt b/ai-hub/ai_hub.egg-info/SOURCES.txt new file mode 100644 index 0000000..4a307fb --- /dev/null +++ b/ai-hub/ai_hub.egg-info/SOURCES.txt @@ -0,0 +1,59 @@ +pyproject.toml +setup.py +ai_hub.egg-info/PKG-INFO +ai_hub.egg-info/SOURCES.txt +ai_hub.egg-info/dependency_links.txt +ai_hub.egg-info/entry_points.txt +ai_hub.egg-info/requires.txt +ai_hub.egg-info/top_level.txt +app/__init__.py +app/app.py +app/config.py +app/main.py +app/utils.py +app/api/__init__.py +app/api/dependencies.py +app/api/schemas.py +app/api/routes/__init__.py +app/api/routes/admin.py +app/api/routes/agent_update.py +app/api/routes/agents.py +app/api/routes/api.py +app/api/routes/documents.py +app/api/routes/general.py +app/api/routes/nodes.py +app/api/routes/sessions.py +app/api/routes/skills.py +app/api/routes/stt.py +app/api/routes/tts.py +app/api/routes/user.py +app/db/__init__.py +app/db/database.py +app/db/migrate.py +app/db/session.py +app/db/models/__init__.py +app/db/models/agent.py +app/db/models/asset.py +app/db/models/document.py +app/db/models/node.py +app/db/models/session.py +app/db/models/user.py +app/protos/__init__.py +app/protos/agent_pb2.py +app/protos/agent_pb2_grpc.py +app/protos/browser_pb2.py +app/protos/browser_pb2_grpc.py +scripts/__init__.py +scripts/seed_prompts.py +tests/__init__.py +tests/test_app.py +tests/test_config.py +tests/test_utils.py +tests/core/__init__.py +tests/core/pipelines/__init__.py +tests/core/pipelines/test_rag_pipeline.py +tests/core/vector_store/__init__.py +tests/core/vector_store/conftest.py +tests/core/vector_store/test_embedder_factory.py +tests/core/vector_store/test_faiss_store.py +tests/core/vector_store/test_mock_embedder.py \ No newline at end of file diff --git a/ai-hub/ai_hub.egg-info/dependency_links.txt b/ai-hub/ai_hub.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ai-hub/ai_hub.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/ai-hub/ai_hub.egg-info/entry_points.txt b/ai-hub/ai_hub.egg-info/entry_points.txt new file mode 100644 index 0000000..ac8eb49 --- /dev/null +++ b/ai-hub/ai_hub.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +ai-hub-server = app.main:app diff --git a/ai-hub/ai_hub.egg-info/requires.txt b/ai-hub/ai_hub.egg-info/requires.txt new file mode 100644 index 0000000..6c678f6 --- /dev/null +++ b/ai-hub/ai_hub.egg-info/requires.txt @@ -0,0 +1,26 @@ +fastapi +uvicorn[standard] +google-generativeai +python-dotenv +openai +pytest +requests +anyio +sqlalchemy +psycopg2-binary +pytest-asyncio +pytest-tornasync +pytest-trio +pytest-mock +numpy +faiss-cpu +aioresponses +python-multipart +PyJWT +tenacity +litellm +tiktoken +grpcio +grpcio-tools +grpcio-reflection +croniter diff --git a/ai-hub/ai_hub.egg-info/top_level.txt b/ai-hub/ai_hub.egg-info/top_level.txt new file mode 100644 index 0000000..025bffe --- /dev/null +++ b/ai-hub/ai_hub.egg-info/top_level.txt @@ -0,0 +1,3 @@ +app +scripts +tests diff --git a/ai-hub/app/api/routes/agents.py b/ai-hub/app/api/routes/agents.py index 41e89c0..9f4bfdf 100644 --- a/ai-hub/app/api/routes/agents.py +++ b/ai-hub/app/api/routes/agents.py @@ -266,9 +266,10 @@ instance.total_runs = 0 instance.successful_runs = 0 + instance.total_input_tokens = 0 + instance.total_output_tokens = 0 instance.total_tokens_accumulated = 0 - instance.total_running_time_seconds = 0.0 - # By setting this to an empty dict but doing an in-place update the ORM sees it + instance.total_running_time_seconds = 0 instance.tool_call_counts = {} db.commit() diff --git a/ai-hub/app/api/routes/sessions.py b/ai-hub/app/api/routes/sessions.py index 2c330df..242c369 100644 --- a/ai-hub/app/api/routes/sessions.py +++ b/ai-hub/app/api/routes/sessions.py @@ -180,7 +180,8 @@ token_limit = get_model_limit(effective_provider, model_name=resolved_model) except ValueError as e: # Model not configured โ return a graceful 200 with error hint - # The frontend can use this to show an inline "configure model" prompt + import logging + logging.warning(f"[Tokens] Limit resolution failed for {effective_provider}/{resolved_model}: {e}") return schemas.SessionTokenUsageResponse( token_count=0, token_limit=0, @@ -190,7 +191,12 @@ validator = Validator(token_limit=token_limit) token_count = validator.get_token_count(combined_text) - percentage = round((token_count / token_limit) * 100, 2) if token_limit > 0 else 0.0 + + # Defensive check: if token_limit is still 0 (shouldn't happen with get_model_limit fallback), avoid DivZero + if token_limit <= 0: + token_limit = 10000 + + percentage = round((token_count / token_limit) * 100, 2) return schemas.SessionTokenUsageResponse( token_count=token_count, diff --git a/ai-hub/app/core/orchestration/agent_loop.py b/ai-hub/app/core/orchestration/agent_loop.py index a387693..2260809 100644 --- a/ai-hub/app/core/orchestration/agent_loop.py +++ b/ai-hub/app/core/orchestration/agent_loop.py @@ -173,6 +173,11 @@ if evaluator: await evaluator.log_event("Execution Initialized", "Agent loop warming up for primary task execution.") + + # Track cumulative metrics for this entire execution run (across all rework rounds) + total_task_input_tokens = 0 + total_task_output_tokens = 0 + total_task_tool_counts = {} # --- MAIN REWORK LOOP --- loop_start = time.time() # Handle scope for exception reporting @@ -184,14 +189,15 @@ round_sub_events = [] try: registry = getattr(services.rag_service, "node_registry_service", None) - final_tool_counts = {} - final_input_tokens = 0 - final_output_tokens = 0 + round_tool_counts = {} + round_input_tokens = 0 + round_output_tokens = 0 final_answer = "" last_assistant_msg_id = None instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() instance.last_reasoning = "" + instance.status = "active" instance.evaluation_status = f"๐ค Main Agent (Rd {current_attempt + 1}): Executing..." if not safe_commit(): return @@ -215,14 +221,14 @@ ): if event.get("type") == "finish": last_assistant_msg_id = event.get("message_id") - final_tool_counts = event.get("tool_counts", {}) - final_input_tokens = event.get("input_tokens", 0) - final_output_tokens = event.get("output_tokens", 0) + round_tool_counts = event.get("tool_counts", {}) + round_input_tokens += event.get("input_tokens", 0) + round_output_tokens += event.get("output_tokens", 0) final_answer = event.get("full_answer", "") elif event.get("type") == "token_counted": usage = event.get("usage", {}) - final_input_tokens += usage.get("prompt_tokens", 0) - final_output_tokens += usage.get("completion_tokens", 0) + round_input_tokens += usage.get("prompt_tokens", 0) + round_output_tokens += usage.get("completion_tokens", 0) elif event.get("type") in ("reasoning", "content"): new_content = event.get("content", "") if event.get("type") == "content": @@ -257,6 +263,20 @@ if not safe_commit(): return content_buffer = "" + # Accumulate round metrics into task totals + total_task_input_tokens += round_input_tokens + total_task_output_tokens += round_output_tokens + for tool, counts in round_tool_counts.items(): + if tool not in total_task_tool_counts: + total_task_tool_counts[tool] = {"calls": 0, "successes": 0, "failures": 0} + if isinstance(counts, dict): + total_task_tool_counts[tool]["calls"] += counts.get("calls", 0) + total_task_tool_counts[tool]["successes"] += counts.get("successes", 0) + total_task_tool_counts[tool]["failures"] += counts.get("failures", 0) + else: # Legacy int support + total_task_tool_counts[tool]["calls"] += counts + total_task_tool_counts[tool]["successes"] += counts + exec_duration = time.time() - execution_start round_sub_events.append({"name": "Agent execution", "duration": round(exec_duration, 2), "timestamp": time.time()}) @@ -275,6 +295,14 @@ instance.evaluation_status = "evaluating" if not safe_commit(): return + # Initial status write to feedback.md so it's not "Session Started" + evaluator.assistant.write( + evaluator.mesh_node_id, + ".cortex/feedback.md", + f"# ๐ต๏ธ Co-Worker Audit (Attempt {current_attempt + 1})\n\nAudit initiated. Reviewing technical accuracy and alignment...", + session_id=evaluator.sync_workspace_id + ) + # Stage 2A: Blind Rating instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() instance.evaluation_status = f"๐ต๏ธ Co-Worker (Rd {current_attempt + 1}): Auditing result against criteria..." @@ -454,6 +482,9 @@ if instance: instance.status = "error_suspended" instance.last_error = str(e) + # Even on error, try to sync tokens used so far + instance.total_input_tokens = (instance.total_input_tokens or 0) + total_task_input_tokens + instance.total_output_tokens = (instance.total_output_tokens or 0) + total_task_output_tokens if not safe_commit(): return return { "status": "error", @@ -463,19 +494,27 @@ # Final loop cleanup & Stats instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() - if instance and instance.status == "active": - instance.status = "idle" # Completed work - instance.successful_runs = (instance.successful_runs or 0) + 1 - + if instance: + # Update metrics regardless of final status (as long as we finished the loop) elapsed = int(time.time() - loop_start) instance.total_running_time_seconds = (instance.total_running_time_seconds or 0) + elapsed - instance.total_input_tokens = (instance.total_input_tokens or 0) + (final_input_tokens if final_result else 0) - instance.total_output_tokens = (instance.total_output_tokens or 0) + (final_output_tokens if final_result else 0) + instance.total_input_tokens = (instance.total_input_tokens or 0) + total_task_input_tokens + instance.total_output_tokens = (instance.total_output_tokens or 0) + total_task_output_tokens - if final_tool_counts: + # Success calculation + final_score = getattr(instance, 'latest_quality_score', 0) or 0 + threshold = rework_threshold or 80 + + if instance.status == "active": + instance.status = "idle" + # Only increment successful runs if we didn't end in an error state and passed threshold (or were unchecked) + if final_score >= threshold or not co_worker_enabled: + instance.successful_runs = (instance.successful_runs or 0) + 1 + + if total_task_tool_counts: import copy current_counts = copy.deepcopy(instance.tool_call_counts or {}) - for k, v in final_tool_counts.items(): + for k, v in total_task_tool_counts.items(): if k in current_counts and isinstance(current_counts[k], int): current_counts[k] = {"calls": current_counts[k], "successes": current_counts[k], "failures": 0} if not isinstance(v, dict): diff --git a/ai-hub/app/core/orchestration/harness_evaluator.py b/ai-hub/app/core/orchestration/harness_evaluator.py index 2f88e0b..5d3c3c2 100644 --- a/ai-hub/app/core/orchestration/harness_evaluator.py +++ b/ai-hub/app/core/orchestration/harness_evaluator.py @@ -442,6 +442,22 @@ ): if event["type"] == "content": final_answer += event["content"] + # Stream to feedback.md for UI visibility during evaluation + if self.assistant: + self.assistant.write( + self.mesh_node_id, + ".cortex/feedback.md", + f"# ๐ต๏ธ Co-Worker Audit in Progress...\n\n{final_answer}\n\n*(Analyzing results against rubric...)*", + session_id=self.sync_workspace_id + ) + elif event["type"] == "reasoning": + # Also include reasoning thoughts in the live feedback + thought = event["content"] + if self.assistant: + # Prepend reasoning if we want, or just append. + # Let's just use final_answer for now to keep it clean, + # but maybe add a header for the thoughts. + pass elif event["type"] == "error": logger.error(f"[HarnessEvaluator] Sub-evaluator fault: {event['content']}") diff --git a/backend.log b/backend.log new file mode 100644 index 0000000..d973a0e --- /dev/null +++ b/backend.log @@ -0,0 +1,77 @@ +INFO: Will watch for changes in these directories: ['/app/ai-hub'] +INFO: Uvicorn running on http://0.0.0.0:8001 (Press CTRL+C to quit) +INFO: Started reloader process [5892] using WatchFiles +INFO:app.core.tools.registry:Registered dynamic tool plugin: 'browser_automation_agent' +INFO:app.core.tools.registry:Registered dynamic tool plugin: 'mesh_file_explorer' +INFO:app.core.tools.registry:Registered dynamic tool plugin: 'mesh_inspect_drift' +INFO:app.core.tools.registry:Registered dynamic tool plugin: 'mesh_sync_control' +INFO:app.core.tools.registry:Registered dynamic tool plugin: 'mesh_terminal_control' +INFO:app.core.tools.registry:Registered dynamic tool plugin: 'mesh_wait_tasks' +INFO:app.core.tools.registry:Registered dynamic tool plugin: 'read_skill_artifact' +INFO: Started server process [5928] +INFO: Waiting for application startup. +INFO:app.db.migrate:Starting database migrations... +INFO:app.db.migrate:Column 'audio_path' already exists in 'messages'. +INFO:app.db.migrate:Column 'model_response_time' already exists in 'messages'. +INFO:app.db.migrate:Column 'token_count' already exists in 'messages'. +INFO:app.db.migrate:Column 'reasoning_content' already exists in 'messages'. +INFO:app.db.migrate:Column 'stt_provider_name' already exists in 'sessions'. +INFO:app.db.migrate:Column 'tts_provider_name' already exists in 'sessions'. +INFO:app.db.migrate:Column 'sync_workspace_id' already exists in 'sessions'. +INFO:app.db.migrate:Column 'attached_node_ids' already exists in 'sessions'. +INFO:app.db.migrate:Column 'node_sync_status' already exists in 'sessions'. +INFO:app.db.migrate:Column 'sync_config' already exists in 'sessions'. +INFO:app.db.migrate:Column 'is_cancelled' already exists in 'sessions'. +INFO:app.db.migrate:Column 'restrict_skills' already exists in 'sessions'. +INFO:app.db.migrate:Column 'allowed_skill_names' already exists in 'sessions'. +INFO:app.db.migrate:Column 'system_prompt_override' already exists in 'sessions'. +INFO:app.db.migrate:Column 'is_locked' already exists in 'sessions'. +INFO:app.db.migrate:Adding column 'co_worker_quality_gate' to 'agent_templates' table... +INFO:app.db.migrate:Successfully added 'co_worker_quality_gate' to agent_templates. +INFO:app.db.migrate:Adding column 'rework_threshold' to 'agent_templates' table... +INFO:app.db.migrate:Successfully added 'rework_threshold' to agent_templates. +INFO:app.db.migrate:Adding column 'max_rework_attempts' to 'agent_templates' table... +INFO:app.db.migrate:Successfully added 'max_rework_attempts' to agent_templates. +INFO:app.db.migrate:Adding column 'total_runs' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'total_runs' to agent_instances. +INFO:app.db.migrate:Adding column 'successful_runs' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'successful_runs' to agent_instances. +INFO:app.db.migrate:Adding column 'total_tokens_accumulated' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'total_tokens_accumulated' to agent_instances. +INFO:app.db.migrate:Adding column 'total_input_tokens' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'total_input_tokens' to agent_instances. +INFO:app.db.migrate:Adding column 'total_output_tokens' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'total_output_tokens' to agent_instances. +INFO:app.db.migrate:Adding column 'total_running_time_seconds' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'total_running_time_seconds' to agent_instances. +INFO:app.db.migrate:Adding column 'tool_call_counts' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'tool_call_counts' to agent_instances. +INFO:app.db.migrate:Adding column 'last_reasoning' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'last_reasoning' to agent_instances. +INFO:app.db.migrate:Adding column 'last_error' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'last_error' to agent_instances. +INFO:app.db.migrate:Adding column 'evaluation_status' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'evaluation_status' to agent_instances. +INFO:app.db.migrate:Adding column 'current_rework_attempt' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'current_rework_attempt' to agent_instances. +INFO:app.db.migrate:Adding column 'latest_quality_score' to 'agent_instances' table... +INFO:app.db.migrate:Successfully added 'latest_quality_score' to agent_instances. +INFO:app.db.migrate:Database migrations complete. +INFO:app.core.services.node_registry:[NodeRegistry] Reset all DB node statuses to 'offline'. +INFO:app.core.grpc.services.grpc_server:๐ CORTEX gRPC Orchestrator starting on [::]:50051 +INFO:app.app:[M6] Agent Orchestrator gRPC server started on port 50051. +INFO:app.core.orchestration.scheduler:[Scheduler] Agent background services (Zombie Sweeper & CRON) started. +INFO:app.core.skills.bootstrap:Checking for system skills bootstrapping... +INFO:app.core.skills.bootstrap:System skills bootstrap completed. +INFO:app.core.orchestration.scheduler:[Scheduler] CRON WAKEUP: Triggering Agent cb0fe6f9-0329-40df-a864-4bd7488f882c (Cron: 30) +INFO: Application startup complete. +ERROR:app.core.grpc.services.grpc_server:[๐โ ๏ธ] Mirror Cleanup Thread Error: (sqlite3.OperationalError) no such column: sessions.auto_clear_history +[SQL: SELECT sessions.id AS sessions_id, sessions.user_id AS sessions_user_id, sessions.title AS sessions_title, sessions.provider_name AS sessions_provider_name, sessions.stt_provider_name AS sessions_stt_provider_name, sessions.tts_provider_name AS sessions_tts_provider_name, sessions.feature_name AS sessions_feature_name, sessions.created_at AS sessions_created_at, sessions.is_archived AS sessions_is_archived, sessions.is_cancelled AS sessions_is_cancelled, sessions.sync_workspace_id AS sessions_sync_workspace_id, sessions.attached_node_ids AS sessions_attached_node_ids, sessions.node_sync_status AS sessions_node_sync_status, sessions.sync_config AS sessions_sync_config, sessions.restrict_skills AS sessions_restrict_skills, sessions.allowed_skill_names AS sessions_allowed_skill_names, sessions.system_prompt_override AS sessions_system_prompt_override, sessions.is_locked AS sessions_is_locked, sessions.auto_clear_history AS sessions_auto_clear_history +FROM sessions +WHERE sessions.is_archived = 0 AND sessions.sync_workspace_id IS NOT NULL] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +ERROR:app.app:[๐๐งน] Ghost Mirror periodic cleanup fail: (sqlite3.OperationalError) no such column: sessions.auto_clear_history +[SQL: SELECT sessions.id AS sessions_id, sessions.user_id AS sessions_user_id, sessions.title AS sessions_title, sessions.provider_name AS sessions_provider_name, sessions.stt_provider_name AS sessions_stt_provider_name, sessions.tts_provider_name AS sessions_tts_provider_name, sessions.feature_name AS sessions_feature_name, sessions.created_at AS sessions_created_at, sessions.is_archived AS sessions_is_archived, sessions.is_cancelled AS sessions_is_cancelled, sessions.sync_workspace_id AS sessions_sync_workspace_id, sessions.attached_node_ids AS sessions_attached_node_ids, sessions.node_sync_status AS sessions_node_sync_status, sessions.sync_config AS sessions_sync_config, sessions.restrict_skills AS sessions_restrict_skills, sessions.allowed_skill_names AS sessions_allowed_skill_names, sessions.system_prompt_override AS sessions_system_prompt_override, sessions.is_locked AS sessions_is_locked, sessions.auto_clear_history AS sessions_auto_clear_history +FROM sessions +WHERE sessions.sync_workspace_id IS NOT NULL] +(Background on this error at: https://sqlalche.me/e/20/e3q8) diff --git a/frontend.log b/frontend.log new file mode 100644 index 0000000..37d4712 --- /dev/null +++ b/frontend.log @@ -0,0 +1,159 @@ + +> cortex-frontend@0.1.0 start +> react-scripts start + +Attempting to bind to HOST environment variable: 0.0.0.0 +If this was unintentional, check that you haven't mistakenly set it in your shell. +Learn more here: https://cra.link/advanced-config + +Browserslist: browsers data (caniuse-lite) is 8 months old. Please run: + npx update-browserslist-db@latest + Why you should do it regularly: https://github.com/browserslist/update-db#readme +(node:5956) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option. +(Use `node --trace-deprecation ...` to show where the warning was created) +(node:5956) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option. +Starting the development server... + +Compiled with warnings. + +[eslint] +src/App.js + Line 35:10: 'userId' is assigned a value but never used no-unused-vars + Line 81:6: React Hook useEffect has missing dependencies: 'currentPage' and 'pathToPage'. Either include them or remove the dependency array react-hooks/exhaustive-deps + Line 142:6: React Hook useEffect has a missing dependency: 'authenticatedPages'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/features/agents/components/AgentDrillDown.js + Line 35:12: 'cortexFiles' is assigned a value but never used no-unused-vars + Line 220:8: React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/features/agents/components/AgentHarnessPage.js + Line 2:21: 'getAgentTelemetry' is defined but never used no-unused-vars + Line 3:10: 'AreaChart' is defined but never used no-unused-vars + Line 3:21: 'Area' is defined but never used no-unused-vars + Line 3:27: 'XAxis' is defined but never used no-unused-vars + Line 3:34: 'YAxis' is defined but never used no-unused-vars + Line 3:41: 'Tooltip' is defined but never used no-unused-vars + Line 3:50: 'ResponsiveContainer' is defined but never used no-unused-vars + +src/features/chat/components/ChatWindow.js + Line 43:18: The ref value 'audioRef.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'audioRef.current' to a variable inside the effect, and use that variable in the cleanup function react-hooks/exhaustive-deps + +src/features/nodes/pages/NodesPage.js + Line 12:12: 'groups' is assigned a value but never used no-unused-vars + +src/features/profile/pages/ProfilePage.js + Line 18:12: 'providerStatuses' is assigned a value but never used no-unused-vars + Line 153:11: 'handleGeneralPreferenceUpdate' is assigned a value but never used no-unused-vars + +src/features/settings/components/cards/IdentityGovernanceCard.js + Line 12:7: 'loadGroups' is assigned a value but never used no-unused-vars + +src/features/settings/components/cards/NetworkIdentityCard.js + Line 11:7: 'fileInputRef' is assigned a value but never used no-unused-vars + +src/features/settings/pages/SettingsPage.js + Line 113:8: React Hook useEffect has a missing dependency: 'loadUserProfile'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/features/swarm/hooks/useSwarmControl.js + Line 110:6: React Hook useEffect has a missing dependency: 'onNewSessionCreated'. Either include it or remove the dependency array. If 'onNewSessionCreated' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps + Line 194:6: React Hook useCallback has missing dependencies: 'onNewSessionCreated' and 'userConfigData?.effective?.llm?.active_provider'. Either include them or remove the dependency array. If 'onNewSessionCreated' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps + +src/features/swarm/pages/SwarmControlPage.js + Line 3:26: 'FileSystemNavigator' is defined but never used no-unused-vars + Line 7:3: 'detachNodeFromSession' is defined but never used no-unused-vars + Line 106:10: 'sessionNodeStatus' is assigned a value but never used no-unused-vars + Line 249:6: React Hook useEffect has a missing dependency: 'fetchNodeInfo'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/features/voice/hooks/useVoiceChat.js + Line 8:3: 'createSession' is defined but never used no-unused-vars + Line 213:6: React Hook useEffect has a missing dependency: 'fetchTokenUsage'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/services/api/userService.js + Line 4:7: 'USERS_LOGOUT_ENDPOINT' is assigned a value but never used no-unused-vars + Line 5:7: 'USERS_ME_ENDPOINT' is assigned a value but never used no-unused-vars + +src/shared/components/FileSystemNavigator.js + Line 114:8: React Hook useEffect has a missing dependency: 'handleView'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/shared/components/MultiNodeConsole.js + Line 60:8: React Hook useEffect has missing dependencies: 'isAIProcessing', 'onMount', and 'onUnmount'. Either include them or remove the dependency array. If 'onMount' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps + Line 208:25: Expected a default case default-case + Line 251:8: React Hook useEffect has a missing dependency: 'attachedNodeIds'. Either include it or remove the dependency array react-hooks/exhaustive-deps + Line 251:9: React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps + +src/shared/components/SessionSidebar.js + Line 19:8: React Hook useEffect has a missing dependency: 'fetchSessions'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +Search for the keywords to learn more about each warning. +To ignore, add // eslint-disable-next-line to the line before. + +WARNING in [eslint] +src/App.js + Line 35:10: 'userId' is assigned a value but never used no-unused-vars + Line 81:6: React Hook useEffect has missing dependencies: 'currentPage' and 'pathToPage'. Either include them or remove the dependency array react-hooks/exhaustive-deps + Line 142:6: React Hook useEffect has a missing dependency: 'authenticatedPages'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/features/agents/components/AgentDrillDown.js + Line 35:12: 'cortexFiles' is assigned a value but never used no-unused-vars + Line 220:8: React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/features/agents/components/AgentHarnessPage.js + Line 2:21: 'getAgentTelemetry' is defined but never used no-unused-vars + Line 3:10: 'AreaChart' is defined but never used no-unused-vars + Line 3:21: 'Area' is defined but never used no-unused-vars + Line 3:27: 'XAxis' is defined but never used no-unused-vars + Line 3:34: 'YAxis' is defined but never used no-unused-vars + Line 3:41: 'Tooltip' is defined but never used no-unused-vars + Line 3:50: 'ResponsiveContainer' is defined but never used no-unused-vars + +src/features/chat/components/ChatWindow.js + Line 43:18: The ref value 'audioRef.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'audioRef.current' to a variable inside the effect, and use that variable in the cleanup function react-hooks/exhaustive-deps + +src/features/nodes/pages/NodesPage.js + Line 12:12: 'groups' is assigned a value but never used no-unused-vars + +src/features/profile/pages/ProfilePage.js + Line 18:12: 'providerStatuses' is assigned a value but never used no-unused-vars + Line 153:11: 'handleGeneralPreferenceUpdate' is assigned a value but never used no-unused-vars + +src/features/settings/components/cards/IdentityGovernanceCard.js + Line 12:7: 'loadGroups' is assigned a value but never used no-unused-vars + +src/features/settings/components/cards/NetworkIdentityCard.js + Line 11:7: 'fileInputRef' is assigned a value but never used no-unused-vars + +src/features/settings/pages/SettingsPage.js + Line 113:8: React Hook useEffect has a missing dependency: 'loadUserProfile'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/features/swarm/hooks/useSwarmControl.js + Line 110:6: React Hook useEffect has a missing dependency: 'onNewSessionCreated'. Either include it or remove the dependency array. If 'onNewSessionCreated' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps + Line 194:6: React Hook useCallback has missing dependencies: 'onNewSessionCreated' and 'userConfigData?.effective?.llm?.active_provider'. Either include them or remove the dependency array. If 'onNewSessionCreated' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps + +src/features/swarm/pages/SwarmControlPage.js + Line 3:26: 'FileSystemNavigator' is defined but never used no-unused-vars + Line 7:3: 'detachNodeFromSession' is defined but never used no-unused-vars + Line 106:10: 'sessionNodeStatus' is assigned a value but never used no-unused-vars + Line 249:6: React Hook useEffect has a missing dependency: 'fetchNodeInfo'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/features/voice/hooks/useVoiceChat.js + Line 8:3: 'createSession' is defined but never used no-unused-vars + Line 213:6: React Hook useEffect has a missing dependency: 'fetchTokenUsage'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/services/api/userService.js + Line 4:7: 'USERS_LOGOUT_ENDPOINT' is assigned a value but never used no-unused-vars + Line 5:7: 'USERS_ME_ENDPOINT' is assigned a value but never used no-unused-vars + +src/shared/components/FileSystemNavigator.js + Line 114:8: React Hook useEffect has a missing dependency: 'handleView'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +src/shared/components/MultiNodeConsole.js + Line 60:8: React Hook useEffect has missing dependencies: 'isAIProcessing', 'onMount', and 'onUnmount'. Either include them or remove the dependency array. If 'onMount' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps + Line 208:25: Expected a default case default-case + Line 251:8: React Hook useEffect has a missing dependency: 'attachedNodeIds'. Either include it or remove the dependency array react-hooks/exhaustive-deps + Line 251:9: React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps + +src/shared/components/SessionSidebar.js + Line 19:8: React Hook useEffect has a missing dependency: 'fetchSessions'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +webpack compiled with 1 warning +Terminated diff --git a/frontend/src/features/agents/components/AgentDrillDown.js b/frontend/src/features/agents/components/AgentDrillDown.js index 607e53f..8cae428 100644 --- a/frontend/src/features/agents/components/AgentDrillDown.js +++ b/frontend/src/features/agents/components/AgentDrillDown.js @@ -1,461 +1,27 @@ -import React, { useState, useEffect } from 'react'; -import ChatWindow from '../../chat/components/ChatWindow'; +import React, { useState } from 'react'; import FileSystemNavigator from '../../../shared/components/FileSystemNavigator'; -import BuddyAvatar from './BuddyAvatar'; -import { getAgents, getSessionMessages, fetchWithAuth, updateAgentConfig, getUserConfig, clearSessionHistory, getSessionTokenStatus, getAgentTriggers, createAgentTrigger, deleteAgentTrigger, getUserAccessibleNodes, getSkills, resetAgentMetrics, getAgentCortexFiles, getAgentCortexFile } from '../../../services/apiService'; +import { useAgentDrillDown } from '../hooks/useAgentDrillDown'; +import DrillDownHeader from './drilldown/DrillDownHeader'; +import ChatTracker from './drilldown/ChatTracker'; +import EvaluationPanel from './drilldown/EvaluationPanel'; +import ConfigPanel from './drilldown/ConfigPanel'; +import MetricsPanel from './drilldown/MetricsPanel'; export default function AgentDrillDown({ agentId, onNavigate }) { - const [agent, setAgent] = useState(null); - const [chatHistory, setChatHistory] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [overrideText, setOverrideText] = useState(""); - - // Day 2 Configurations - const [activeTab, setActiveTab] = useState('config'); // workspace or config - const [editConfig, setEditConfig] = useState(null); - const [saving, setSaving] = useState(false); - const [userConfig, setUserConfig] = useState(null); - const [tokenUsage, setTokenUsage] = useState({ token_count: 0, token_limit: 0, percentage: 0 }); - const [tokenError, setTokenError] = useState(null); - const [clearing, setClearing] = useState(false); - const [triggers, setTriggers] = useState([]); - const [newTriggerType, setNewTriggerType] = useState('cron'); - const [newCronValue, setNewCronValue] = useState('0 * * * *'); - const [newIntervalValue, setNewIntervalValue] = useState(600); - const [newDefaultPrompt, setNewDefaultPrompt] = useState(''); - const [creatingTrigger, setCreatingTrigger] = useState(false); - const [modalConfig, setModalConfig] = useState(null); - const [copiedId, setCopiedId] = useState(null); - const [nodes, setNodes] = useState([]); - const [allSkills, setAllSkills] = useState([]); - const [flippedCards, setFlippedCards] = useState({ runtime: false, tokens: false }); - - // Evaluation Hub State - const [cortexFiles, setCortexFiles] = useState([]); - const [feedbackContent, setFeedbackContent] = useState(""); - const [rubricContent, setRubricContent] = useState(""); - const [coworkerContent, setCoworkerContent] = useState(""); - const [historyLog, setHistoryLog] = useState([]); - const [savingGroundTruth, setSavingGroundTruth] = useState(false); - const [selectedAuditId, setSelectedAuditId] = useState(null); // null means 'Live State' from node - - // Monitoring & Timer States - const [runningSeconds, setRunningSeconds] = useState(0); - const [lastTotalConsumption, setLastTotalConsumption] = useState(null); - const [previousStatus, setPreviousStatus] = useState('idle'); - const [currentAction, setCurrentAction] = useState(null); - const [lastAction, setLastAction] = useState(null); - const [lastActionDuration, setLastActionDuration] = useState(null); - const [actionStartTime, setActionStartTime] = useState(0); - - // Helper: Convert cron expression to human-readable text - const describeCron = (expr) => { - if (!expr) return ''; - if (/^\d+$/.test(expr)) { - const secs = parseInt(expr); - if (secs < 60) return `Every ${secs} seconds`; - if (secs < 3600) return `Every ${Math.round(secs/60)} minute${secs >= 120 ? 's' : ''}`; - return `Every ${Math.round(secs/3600)} hour${secs >= 7200 ? 's' : ''}`; - } - // Standard cron expressions - const parts = expr.split(' '); - if (parts.length >= 5) { - if (expr === '* * * * *') return 'Every minute'; - if (expr === '0 * * * *') return 'Every hour'; - if (expr === '0 0 * * *') return 'Every day at midnight'; - if (parts[0].startsWith('*/')) return `Every ${parts[0].slice(2)} minute${parts[0].slice(2) !== '1' ? 's' : ''}`; - } - return expr; - }; - - const formatTimeLocal = (utcString) => { - if (!utcString) return 'Never'; - const dateStr = utcString.endsWith('Z') || utcString.includes('+') ? utcString : utcString + 'Z'; - return new Date(dateStr).toLocaleString(undefined, { - month: 'short', day: 'numeric', - hour: '2-digit', minute: '2-digit', second: '2-digit' - }); - }; - - useEffect(() => { - const loadConf = async () => { - try { - const conf = await getUserConfig(); - setUserConfig(conf); - } catch (e) {} - try { - const nList = await getUserAccessibleNodes(); - setNodes(nList); - } catch (e) {} - try { - const sList = await getSkills(); - setAllSkills(sList); - } catch (e) {} - }; - loadConf(); - }, []); - - const fetchData = async () => { - try { - // Find agent - const allAgents = await getAgents(); - const found = allAgents.find(a => a.id === agentId); - if (!found) throw new Error("Agent not found"); - setAgent(found); - - // Populate form only on first load using the agent context - setEditConfig(prev => prev || { - name: found.template?.name || "", - system_prompt: found.template?.system_prompt_content || found.template?.system_prompt_path || "", - max_loop_iterations: found.template?.max_loop_iterations || 20, - mesh_node_id: found.mesh_node_id || "", - provider_name: found.session?.provider_name || "", - restrict_skills: found.session?.restrict_skills || false, - allowed_skill_ids: found.session?.skills ? found.session.skills.map(s => s.id) : [], - is_locked: found.session?.is_locked || false, - auto_clear_history: found.session?.auto_clear_history || false, - co_worker_quality_gate: found.template?.co_worker_quality_gate || false, - rework_threshold: found.template?.rework_threshold || 80, - max_rework_attempts: found.template?.max_rework_attempts || 3 - }); - - // Fetch chat history if session exists - if (found.session_id) { - const historyResp = await getSessionMessages(found.session_id); - const formatted = (historyResp.messages || []).map(m => ({ - text: m.content, - isUser: m.sender === 'user', - reasoning: m.reasoning_content, - status: null, - sender: m.sender, - timestamp: m.created_at, - id: m.id, - tool_calls: m.tool_calls, - message_metadata: m.message_metadata - })); - setChatHistory(formatted); - - try { - const usage = await getSessionTokenStatus(found.session_id); - if (usage.error) { - setTokenError(usage.error); - setTokenUsage({ token_count: 0, token_limit: 0, percentage: 0 }); - } else { - setTokenUsage(usage); - setTokenError(null); - } - } catch(e) { - setTokenError(e.message); - } - } - - try { - const tList = await getAgentTriggers(agentId); - setTriggers(tList); - } catch(e) {} - - // Fetch Evaluation Hub data (.cortex/) - const sid = found.session?.sync_workspace_id || found.session_id; - const nodeId = found.mesh_node_id || "hub"; - - if (sid) { - try { - const cFilesListing = await getAgentCortexFiles(agentId, nodeId, sid); - const files = cFilesListing.files || []; - setCortexFiles(files); - - const fileExists = (name) => files.some(f => f.name === name || f.path === `.cortex/${name}`); - - // Stream feedback.md - if (fileExists("feedback.md")) { - try { - const feedback = await getAgentCortexFile(agentId, nodeId, sid, "feedback.md"); - setFeedbackContent(feedback?.content || ""); - } catch (e) {} - } - - // Display rubric.md - if (fileExists("rubric.md")) { - try { - const rubric = await getAgentCortexFile(agentId, nodeId, sid, "rubric.md"); - setRubricContent(rubric?.content || ""); - } catch (e) {} - } - - // Display coworker.md (Ground Truth) - try { - const coworker = await getAgentCortexFile(agentId, nodeId, sid, ".coworker.md"); - setCoworkerContent(coworker?.content || ""); - } catch (e) {} - - // Display history.log - if (fileExists("history.log")) { - try { - const logs = await getAgentCortexFile(agentId, nodeId, sid, "history.log"); - if (logs?.content) { - try { - const parsed = JSON.parse(logs.content); - setHistoryLog(Array.isArray(parsed) ? parsed : []); - } catch (e) { - // Fallback to raw lines if not JSON - setHistoryLog(logs.content.split('\n').filter(l => l.trim()).map(line => ({ message: line }))); - } - } - } catch (e) {} - } - } catch (e) {} - } - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchData(); - const interval = setInterval(fetchData, 2500); // 2.5s is sufficient and less noisy - return () => clearInterval(interval); - }, [agentId]); - - useEffect(() => { - let interval = null; - if (agent?.status === 'active') { - // Reset timer if we just transitioned to active - if (previousStatus !== 'active') { - setRunningSeconds(0); - setLastTotalConsumption(null); - setCurrentAction(null); - setLastAction(null); - setLastActionDuration(null); - setActionStartTime(0); - } - interval = setInterval(() => { - setRunningSeconds(s => s + 1); - }, 1000); - - // Track Action/Phase changes - const rawStatus = agent.evaluation_status || 'Orchestrating task payload...'; - const cleanStatus = rawStatus.toLowerCase().includes('audit') || rawStatus.toLowerCase().includes('worker') - ? `๐ก๏ธ Co-Worker Audit: ${rawStatus}` - : `๐ค Main Agent: ${rawStatus}`; - - if (rawStatus !== (currentAction?.raw || '')) { - // Shift current to last - if (currentAction) { - setLastAction(currentAction); - setLastActionDuration(runningSeconds - actionStartTime); - } - setCurrentAction({ display: cleanStatus, raw: rawStatus }); - setActionStartTime(runningSeconds); - } - } else if (previousStatus === 'active' && agent?.status !== 'active') { - // Captured finished state - setLastTotalConsumption(runningSeconds); - } - setPreviousStatus(agent?.status || 'idle'); - return () => clearInterval(interval); - }, [agent?.status, agent?.evaluation_status, runningSeconds, previousStatus, currentAction, actionStartTime]); - - const [skipEval, setSkipEval] = useState(false); - - const handleInjectOverride = async (e) => { - e.preventDefault(); - if (!overrideText.trim() || !agent?.session_id) return; - - try { - await fetchWithAuth(`/agents/${agentId}/run`, { - method: "POST", - body: { prompt: overrideText, skip_coworker: skipEval } - }); - setOverrideText(""); - fetchData(); - } catch (err) { - setModalConfig({ title: 'Injection Failed', message: err.message, type: 'error' }); - } - }; - - const handleClearHistory = () => { - if (!agent?.session_id) return; - setModalConfig({ - title: 'Confirm Memory Wipe', - message: "Are you sure you want to clear the agent's memory? This cannot be undone.", - type: 'error', - confirmText: 'Clear Memory', - confirmAction: async () => { - try { - setClearing(true); - if (agent?.session?.is_locked && editConfig?.is_locked === false) { - await updateAgentConfig(agent.id, { - is_locked: false, - mesh_node_id: agent.mesh_node_id || "hub" - }); - } - await clearSessionHistory(agent.session_id); - setChatHistory([]); - setSelectedAuditId(null); - fetchData(); - } catch (err) { - setModalConfig({ title: 'Clear Failed', message: err.message, type: 'error' }); - } finally { - setClearing(false); - } - } - }); - }; - - const handleResetMetrics = () => { - if (!agent?.id) return; - setModalConfig({ - title: 'Confirm Reset Metrics', - message: "Are you sure you want to reset all execution metrics for this agent? This cannot be undone.", - type: 'error', - confirmText: 'Reset Metrics', - confirmAction: async () => { - try { - setClearing(true); // Re-use the clearing state to block duplicate clicks - await resetAgentMetrics(agent.id); - fetchData(); - } catch (err) { - setModalConfig({ title: 'Reset Failed', message: err.message, type: 'error' }); - } finally { - setClearing(false); - } - } - }); - }; - - const handleSaveConfig = async () => { - try { - setSaving(true); - const payload = { - name: editConfig.name, - system_prompt: editConfig.system_prompt, - max_loop_iterations: parseInt(editConfig.max_loop_iterations, 10) || 20, - mesh_node_id: editConfig.mesh_node_id - }; - if (editConfig.provider_name) { - payload.provider_name = editConfig.provider_name; - } - if (editConfig.restrict_skills !== undefined) { - payload.restrict_skills = editConfig.restrict_skills; - } - if (editConfig.allowed_skill_ids !== undefined) { - payload.allowed_skill_ids = editConfig.allowed_skill_ids; - } - if (editConfig.is_locked !== undefined) { - payload.is_locked = editConfig.is_locked; - } - if (editConfig.auto_clear_history !== undefined) { - payload.auto_clear_history = editConfig.auto_clear_history; - } - if (editConfig.co_worker_quality_gate !== undefined) { - payload.co_worker_quality_gate = editConfig.co_worker_quality_gate; - } - if (editConfig.rework_threshold !== undefined) { - payload.rework_threshold = parseInt(editConfig.rework_threshold, 10); - } - if (editConfig.evaluator_prompt !== undefined) { - payload.evaluator_prompt = editConfig.evaluator_prompt; - } - - // Explicitly pause the agent loop during update as requested by the user - try { - await fetchWithAuth(`/agents/${agentId}/status`, { method: "PATCH", body: { status: "idle" } }); - } catch (e) {} - - await updateAgentConfig(agentId, payload); - fetchData(); - setModalConfig({ title: 'Success', message: 'Configuration Saved Successfully!', type: 'success' }); - } catch (err) { - setModalConfig({ title: 'Save Failed', message: err.message, type: 'error' }); - } finally { - setSaving(false); - } - }; - - const handleSaveGroundTruth = async () => { - try { - setSavingGroundTruth(true); - const sid = agent.session?.sync_workspace_id || agent.session_id; - const nodeId = agent.mesh_node_id || "hub"; - - // Use the general node FS API to write .coworker.md - await fetchWithAuth(`/nodes/${nodeId}/fs/touch?X-User-ID=${userConfig?.id || 'agent_ui'}`, { - method: "POST", - body: { - path: ".coworker.md", - content: coworkerContent, - is_dir: false, - session_id: sid - } - }); - setModalConfig({ title: 'Success', message: 'Auditor Guidelines synced to node workspace.', type: 'success' }); - fetchData(); - } catch (err) { - setModalConfig({ title: 'Update Failed', message: err.message, type: 'error' }); - } finally { - setSavingGroundTruth(false); - } - }; - - const handleAddTrigger = async () => { - try { - setCreatingTrigger(true); - const payload = { - trigger_type: newTriggerType, - default_prompt: newDefaultPrompt - }; - if (newTriggerType === 'cron') payload.cron_expression = newCronValue; - if (newTriggerType === 'interval') payload.interval_seconds = parseInt(newIntervalValue) || 600; - - await createAgentTrigger(agentId, payload); - setNewDefaultPrompt(''); - setNewCronValue('0 * * * *'); - setNewIntervalValue(600); - fetchData(); - } catch (err) { - setModalConfig({ title: 'Trigger Failed', message: err.message, type: 'error' }); - } finally { - setCreatingTrigger(false); - } - }; - - const handleDeleteTrigger = async (triggerId) => { - try { - await deleteAgentTrigger(triggerId); - fetchData(); - } catch (err) { - setModalConfig({ title: 'Delete Failed', message: err.message, type: 'error' }); - } - }; - - const handleFireTrigger = async (triggerPrompt) => { - try { - await fetchWithAuth(`/agents/${agentId}/run`, { - method: 'POST', - body: { prompt: triggerPrompt } - }); - setModalConfig({ title: 'Success', message: 'Agent manual execution started successfully!', type: 'success' }); - fetchData(); - } catch (err) { - setModalConfig({ title: 'Execution Failed', message: err.message, type: 'error' }); - } - }; - - const handleFireWebhook = async (token, triggerPrompt) => { - try { - await fetchWithAuth(`/agents/${agentId}/webhook?token=${token}`, { - method: 'POST', - body: { prompt: triggerPrompt || "Manual test from UI" } - }); - setModalConfig({ title: 'Success', message: 'Webhook test trigger sent successfully!', type: 'success' }); - fetchData(); - } catch (err) { - setModalConfig({ title: 'Webhook Failed', message: err.message, type: 'error' }); - } - }; + const hookData = useAgentDrillDown(agentId); + const { + agent, chatHistory, loading, error, activeTab, setActiveTab, + editConfig, setEditConfig, saving, userConfig, tokenUsage, tokenError, + clearing, triggers, newTriggerType, setNewTriggerType, newCronValue, setNewCronValue, + newIntervalValue, setNewIntervalValue, newDefaultPrompt, setNewDefaultPrompt, + creatingTrigger, modalConfig, setModalConfig, nodes, allSkills, flippedCards, setFlippedCards, + feedbackContent, rubricContent, coworkerContent, setCoworkerContent, + historyLog, savingGroundTruth, selectedAuditId, setSelectedAuditId, + runningSeconds, lastTotalConsumption, currentAction, lastAction, lastActionDuration, + handleAction, handleClearHistory, handleSaveConfig, handleSaveGroundTruth, fetchData, + handleAddTrigger, handleDeleteTrigger, handleFireTrigger, handleFireWebhook, + handleResetMetrics, overrideText, setOverrideText, handleInjectOverride + } = hookData; if (loading && !agent) return (
- {`${window.location.host.includes('localhost') ? 'http://' : 'https://'}${window.location.host}/api/v1/agents/${agentId}/webhook?token=${t.webhook_secret}`}
-
- {/* Payload Tooltip */}
-
- {JSON.stringify({ "prompt": "...custom prompt here..." }, null, 2)}
-
- {"{"}"prompt": "..."{"}"} in the body.
-
- Enable autonomous evaluation loops for this agent
-Changes to the system prompt will be immediately picked up by the agent loop on its next invocation turn.
-- {entry.reason || entry.message} -
- - {entry.sub_events?.length > 0 && ( -- {entry.details} -
- )} -| Tool Name | -Calls | -Success | -Failed | -
|---|---|---|---|
| {tool} | -{calls} | -{successes} | -{failures > 0 ? failures : '-'} | -
| No tool calls recorded yet | -|||
{modalConfig.message}
-{modalConfig.message}
++ {agent.last_error} +
+{agent.last_error}
+Force the agent to process a specific prompt immediately.
++ {t.trigger_type === 'cron' ? t.cron_expression : t.trigger_type === 'interval' ? `Every ${t.interval_seconds}s` : 'REST API Token'} +
+Prompt: {t.default_prompt || '(using session default)'}
++ {entry.reason || entry.message} +
+ + {entry.sub_events?.length > 0 && ( ++ {entry.details} +
+ )} +