diff --git a/ai-hub/app/api/routes/user.py b/ai-hub/app/api/routes/user.py index f5c6acb..235d017 100644 --- a/ai-hub/app/api/routes/user.py +++ b/ai-hub/app/api/routes/user.py @@ -365,8 +365,6 @@ ): from app.core.providers.factory import get_tts_provider from app.config import settings - import logging - logger = logging.getLogger(__name__) if not user_id: raise HTTPException(status_code=401, detail="Unauthorized") @@ -419,8 +417,6 @@ ): from app.core.providers.factory import get_stt_provider from app.config import settings - import logging - logger = logging.getLogger(__name__) if not user_id: raise HTTPException(status_code=401, detail="Unauthorized") diff --git a/ai-hub/app/api/schemas.py b/ai-hub/app/api/schemas.py index 7bddfb9..2eebe6c 100644 --- a/ai-hub/app/api/schemas.py +++ b/ai-hub/app/api/schemas.py @@ -601,6 +601,9 @@ total_output_tokens: Optional[int] = 0 total_running_time_seconds: Optional[int] = 0 tool_call_counts: Optional[dict] = {} + last_reasoning: Optional[str] = None + last_error: Optional[str] = None + model_config = ConfigDict(from_attributes=True) diff --git a/ai-hub/app/core/orchestration/agent_loop.py b/ai-hub/app/core/orchestration/agent_loop.py index 77f3869..85cb4b9 100644 --- a/ai-hub/app/core/orchestration/agent_loop.py +++ b/ai-hub/app/core/orchestration/agent_loop.py @@ -23,9 +23,11 @@ # Acquire Lease instance.last_heartbeat = datetime.utcnow() instance.status = "active" + instance.last_error = None instance.total_runs = (instance.total_runs or 0) + 1 db.commit() + # Launch secondary heartbeat task async def heartbeat(): while True: @@ -47,6 +49,7 @@ template = db.query(AgentTemplate).filter(AgentTemplate.id == instance.template_id).first() if not template: instance.status = "error_suspended" + instance.last_error = f"Template '{instance.template_id}' not found." db.commit() return @@ -86,6 +89,10 @@ final_output_tokens = 0 final_answer = "" + instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() + instance.last_reasoning = "" + db.commit() + # We consume the generator completely to let it execute all tools and generate reasoning async for event in rag_service.chat_with_rag( db=db, @@ -100,6 +107,29 @@ final_input_tokens = event.get("input_tokens", 0) final_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) + elif event.get("type") in ("reasoning", "content"): + # Stream real-time thoughts for UI observability + instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() + content = event.get("content", "") + if not instance.last_reasoning: instance.last_reasoning = "" + instance.last_reasoning += content + + # Forward to Swarm Registry so the Node List/Swarm Control UI sees it + registry = getattr(rag_service, "node_registry_service", None) + if registry and instance.mesh_node_id: + registry.emit(instance.mesh_node_id, "reasoning", { + "content": content, + "agent_id": agent_id, + "session_id": instance.session_id + }) + + # Throttle DB updates to avoid saturation + if (final_input_tokens + final_output_tokens) % 5 == 0: + db.commit() # Execution complete instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() @@ -135,6 +165,9 @@ from sqlalchemy.orm.attributes import flag_modified flag_modified(instance, "tool_call_counts") + # Clear reasoning as the task is now complete + instance.last_reasoning = None + print(f"[AgentExecutor] Saved metrics for {agent_id} (Session: {session_id}). Tool calls: {final_tool_counts}") db.commit() @@ -146,6 +179,7 @@ print(traceback.format_exc()) instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() instance.status = "error_suspended" + instance.last_error = str(e) db.commit() except Exception as e: @@ -153,6 +187,7 @@ instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() if instance: instance.status = "error_suspended" + instance.last_error = f"Unhandled loop error: {str(e)}" db.commit() finally: heartbeat_task.cancel() diff --git a/ai-hub/app/core/orchestration/architect.py b/ai-hub/app/core/orchestration/architect.py index 9c8c63d..25f7a76 100644 --- a/ai-hub/app/core/orchestration/architect.py +++ b/ai-hub/app/core/orchestration/architect.py @@ -189,12 +189,7 @@ messages.append({"role": "user", "content": "WATCHDOG: .ai_todo.md has open items. Please continue until all are marked [COMPLETED]."}) continue - # Turn duration report (Natural Exit) - turn_duration = time.time() - turn_start_time - total_duration = time.time() - session_start_time - duration_marker = f"\n\n> **âąī¸ Turn {turn} Duration:** {turn_duration:.1f}s | **Total:** {total_duration:.1f}s\n" - yield {"type": "reasoning", "content": duration_marker} - yield {"type": "status", "content": f"Turn {turn} finished in **{turn_duration:.1f}s**. (Session Total: **{total_duration:.1f}s**)"} + # Natural Exit return # Natural exit # F. Execute Tools @@ -221,11 +216,7 @@ else: yield event - turn_duration = time.time() - turn_start_time - total_duration = time.time() - session_start_time - duration_marker = f"\n\n> **âąī¸ Turn {turn} Duration:** {turn_duration:.1f}s | **Total:** {total_duration:.1f}s\n" - yield {"type": "reasoning", "content": duration_marker} - yield {"type": "status", "content": f"Turn {turn} finished in **{turn_duration:.1f}s**. (Session Total: **{total_duration:.1f}s**)"} + pass except Exception as e: import traceback diff --git a/ai-hub/app/core/orchestration/body.py b/ai-hub/app/core/orchestration/body.py index bf26379..1d8a7ea 100644 --- a/ai-hub/app/core/orchestration/body.py +++ b/ai-hub/app/core/orchestration/body.py @@ -2,7 +2,7 @@ import logging import json import sys -from typing import List, Dict, Any, AsyncGenerator +from typing import List, Dict, Any, AsyncGenerator, Optional class ToolExecutor: """Handles parallel tool dispatching and event drainage.""" diff --git a/ai-hub/app/core/orchestration/profiles.py b/ai-hub/app/core/orchestration/profiles.py index ea4e2f0..f0f6848 100644 --- a/ai-hub/app/core/orchestration/profiles.py +++ b/ai-hub/app/core/orchestration/profiles.py @@ -35,7 +35,8 @@ ## 📂 Infrastructure & Ghost Mirror: - **Node Sync Path**: All synced files are at `/tmp/cortex-sync/{{session_id}}/` on agent nodes. - **Hub Mirror**: Use `mesh_file_explorer` with `session_id` to read/list files from the central mirror (~1ms speed). -- **Binary File Awareness**: DO NOT attempt to `cat`, `read`, or `view` non-text/binary files (images, videos, audio, libraries, archives). If a file has an extension like .jpg, .png, .zip, .so, etc., only PERFORM operations (move, copy, delete) or describe their existence. NEVER attempt to dump their raw content into the terminal history. +- **Importing to File Explorer**: To make a node-local file (from NAS/System paths) appear in the Web UI File Explorer, you MUST copy it to the local sync workspace path: `/tmp/cortex-sync/{{session_id}}/`. Use `mesh_terminal_control` with `cp` for this. +- **Binary File Awareness**: DO NOT attempt to `cat`, `read`, or `view` non-text/binary files (.jpg, .png, .zip, .so, etc.). NEVER use `base64` or `cat` to ingest binary files into your context for the purpose of moving/copying them. Instead, bridge physical and sync paths via native terminal `cp` or `mv`. Infrastructure Context (Mesh): {mesh_context} diff --git a/ai-hub/app/core/services/node_registry.py b/ai-hub/app/core/services/node_registry.py index 5a2e64a..ca82e70 100644 --- a/ai-hub/app/core/services/node_registry.py +++ b/ai-hub/app/core/services/node_registry.py @@ -54,6 +54,7 @@ "sync_recovery": "đŸĨ Recovery", "sync_locked": "🔒 Workspace Locked", "sync_unlocked": "🔓 Workspace Unlocked", + "reasoning": "🧠 Thinking", "info": "â„šī¸ Info", } @@ -427,6 +428,11 @@ if len(clean_output) > 100_000: clean_output = clean_output[:100_000] + "\n[... Output Truncated ...]\n" node.terminal_history.append(clean_output) + elif event_type == "reasoning" and isinstance(data, dict): + content = data.get("content", "") + if content: + # Append reasoning as a distinct "thought" block in terminal history + node.terminal_history.append(content) # Keep a rolling buffer of 150 terminal interaction chunks if len(node.terminal_history) > 150: diff --git a/ai-hub/app/core/services/rag.py b/ai-hub/app/core/services/rag.py index f017093..170f31e 100644 --- a/ai-hub/app/core/services/rag.py +++ b/ai-hub/app/core/services/rag.py @@ -253,6 +253,16 @@ current_assistant_msg.reasoning_content = "" current_assistant_msg.reasoning_content += event["content"] + # Forward to Swarm Registry so the Swarm Control / Node Dash views see it too + registry = getattr(self, "node_registry_service", None) + if registry and session.attached_node_ids: + for node_id in session.attached_node_ids: + registry.emit(node_id, "reasoning", { + "content": event.get("content", ""), + "session_id": session_id, + "type": event["type"] + }) + # Commit every 5 chunks to provide smooth UI streaming without hammering the DB if (input_tokens + output_tokens) % 5 == 0: try: diff --git a/ai-hub/app/core/services/tool.py b/ai-hub/app/core/services/tool.py index 172d5e8..dd015cc 100644 --- a/ai-hub/app/core/services/tool.py +++ b/ai-hub/app/core/services/tool.py @@ -120,8 +120,6 @@ fm = yaml.safe_load(parts[1]) parameters = fm.get("config", {}).get("parameters", {}) except Exception as e: - import logging - logger = logging.getLogger(__name__) logger.warning(f"Error parsing SKILL.md frontmatter for {ds.name}: {e}") # If no parameters found in frontmatter, try parsing markdown directly @@ -169,8 +167,6 @@ if p_req: parameters["required"].append(p_name) except Exception as e: - import logging - logger = logging.getLogger(__name__) logger.warning(f"Error parsing SKILL.md markdown for {ds.name}: {e}") # Automatically inject logical node parameters into the schema for all tools diff --git a/ai-hub/app/db/migrate.py b/ai-hub/app/db/migrate.py index 79dc1a8..df78b0f 100644 --- a/ai-hub/app/db/migrate.py +++ b/ai-hub/app/db/migrate.py @@ -263,13 +263,45 @@ mesh_node_id TEXT, status TEXT DEFAULT 'idle', current_workspace_jail TEXT, - last_heartbeat DATETIME DEFAULT CURRENT_TIMESTAMP + last_heartbeat DATETIME DEFAULT CURRENT_TIMESTAMP, + total_runs INTEGER DEFAULT 0, + successful_runs INTEGER DEFAULT 0, + total_tokens_accumulated INTEGER DEFAULT 0, + total_input_tokens INTEGER DEFAULT 0, + total_output_tokens INTEGER DEFAULT 0, + total_running_time_seconds INTEGER DEFAULT 0, + tool_call_counts TEXT DEFAULT '{}', + last_reasoning TEXT, + last_error TEXT ) """)) conn.commit() logger.info("Table 'agent_instances' created.") except Exception as e: logger.error(f"Failed to create 'agent_instances': {e}") + else: + # Multi-iteration: Table exists — ensure all metrics & diagnostic columns are present + instance_columns = [c["name"] for c in inspector.get_columns("agent_instances")] + instance_required_columns = [ + ("total_runs", "INTEGER DEFAULT 0"), + ("successful_runs", "INTEGER DEFAULT 0"), + ("total_tokens_accumulated", "INTEGER DEFAULT 0"), + ("total_input_tokens", "INTEGER DEFAULT 0"), + ("total_output_tokens", "INTEGER DEFAULT 0"), + ("total_running_time_seconds", "INTEGER DEFAULT 0"), + ("tool_call_counts", "TEXT DEFAULT '{}'"), + ("last_reasoning", "TEXT"), + ("last_error", "TEXT") + ] + for col_name, col_type in instance_required_columns: + if col_name not in instance_columns: + logger.info(f"Adding column '{col_name}' to 'agent_instances' table...") + try: + conn.execute(text(f"ALTER TABLE agent_instances ADD COLUMN {col_name} {col_type}")) + conn.commit() + logger.info(f"Successfully added '{col_name}' to agent_instances.") + except Exception as e: + logger.error(f"Failed to add column '{col_name}' to agent_instances: {e}") if not inspector.has_table("agent_triggers"): logger.info("Creating table 'agent_triggers'...") diff --git a/ai-hub/app/db/models/agent.py b/ai-hub/app/db/models/agent.py index 1bb171f..245d47a 100644 --- a/ai-hub/app/db/models/agent.py +++ b/ai-hub/app/db/models/agent.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, String, Integer, ForeignKey, DateTime, Enum, JSON, Boolean +from sqlalchemy import Column, String, Integer, ForeignKey, DateTime, Enum, JSON, Boolean, Text from sqlalchemy.orm import relationship import uuid import datetime @@ -34,6 +34,9 @@ total_output_tokens = Column(Integer, default=0) total_running_time_seconds = Column(Integer, default=0) tool_call_counts = Column(JSON, default={}) + last_reasoning = Column(Text, nullable=True) # Real-time thought stream + last_error = Column(String, nullable=True) # Diagnostic message if status="error_suspended" + template = relationship("AgentTemplate", back_populates="instances") session = relationship("Session", primaryjoin="AgentInstance.session_id == Session.id") diff --git a/ai-hub/docs/api_reference/agent-nodes.md b/ai-hub/docs/api_reference/agent-nodes.md index f08a24c..bcc0006 100644 --- a/ai-hub/docs/api_reference/agent-nodes.md +++ b/ai-hub/docs/api_reference/agent-nodes.md @@ -449,7 +449,7 @@ #### Request Body -**Required:** Yes +**Required:** No - **Media Type:** `application/json` - **Schema:** `UserNodePreferences` (Define in Models) @@ -535,10 +535,10 @@ ## GET `/nodes/provision/{node_id}` -**Summary:** Headless Provisioning Script +**Summary:** Headless Provisioning Script (Python) **Description:** Returns a Python script that can be piped into python3 to automatically -install and start the agent node. +install and start the python-source agent node. Usage: curl -sSL https://.../provision/{node_id}?token={token} | python3 @@ -566,6 +566,92 @@ --- +## GET `/nodes/provision/sh/{node_id}` + +**Summary:** Headless Provisioning Script (Bash Binary) + +**Description:** Returns a Bash script that curls and executes the compiled standalone binary. + +Usage: curl -sSL https://.../provision/sh/{node_id}?token={token} | bash + +#### Parameters + +| Name | In | Required | Type | Description | +|------|----|----------|------|-------------| +| `node_id` | path | Yes | string | | +| `token` | query | Yes | string | | + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | +| `422` | Validation Error | + +#### Example Usage + +```bash +curl -X 'GET' \ + 'http://localhost:8000/api/v1/nodes/provision/sh/{node_id}?token=' \ + -H 'accept: application/json' +``` + +--- + +## GET `/nodes/provision/binary/{node_id}/{arch}` + +**Summary:** Download Self-Contained Binary ZIP Bundle + +**Description:** Dynamically zips the specified architecture's cortex-agent binary +with the autogenerated agent_config.yaml for secure, direct GUI downloads. + +#### Parameters + +| Name | In | Required | Type | Description | +|------|----|----------|------|-------------| +| `node_id` | path | Yes | string | | +| `arch` | path | Yes | string | | +| `token` | query | Yes | string | | + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | +| `422` | Validation Error | + +#### Example Usage + +```bash +curl -X 'GET' \ + 'http://localhost:8000/api/v1/nodes/provision/binary/{node_id}/{arch}?token=' \ + -H 'accept: application/json' +``` + +--- + +## GET `/nodes/provision/binaries/status` + +**Summary:** Check binary availability status + +**Description:** Check binary availability status + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | + +#### Example Usage + +```bash +curl -X 'GET' \ + 'http://localhost:8000/api/v1/nodes/provision/binaries/status' \ + -H 'accept: application/json' +``` + +--- + ## GET `/nodes/admin/{node_id}/download` **Summary:** [Admin] Download Agent Node Bundle (ZIP) @@ -858,3 +944,116 @@ --- +## POST `/nodes/{node_id}/fs/move` + +**Summary:** Move/Rename File or Directory + +**Description:** Atomic move/rename within the mesh workspace. + +#### Parameters + +| Name | In | Required | Type | Description | +|------|----|----------|------|-------------| +| `node_id` | path | Yes | string | | +| `X-User-ID` | header | Yes | string | | + +#### Request Body + +**Required:** Yes + +- **Media Type:** `application/json` +- **Schema:** `FileMoveRequest` (Define in Models) + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | +| `422` | Validation Error | + +#### Example Usage + +```bash +curl -X 'POST' \ + 'http://localhost:8000/api/v1/nodes/{node_id}/fs/move' \ + -H 'accept: application/json' \ + -H 'X-User-ID: ' \ + -H 'Content-Type: application/json' \ + -d '{}' +``` + +--- + +## POST `/nodes/{node_id}/fs/copy` + +**Summary:** Copy File or Directory + +**Description:** Atomic copy within the mesh workspace. + +#### Parameters + +| Name | In | Required | Type | Description | +|------|----|----------|------|-------------| +| `node_id` | path | Yes | string | | +| `X-User-ID` | header | Yes | string | | + +#### Request Body + +**Required:** Yes + +- **Media Type:** `application/json` +- **Schema:** `FileCopyRequest` (Define in Models) + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | +| `422` | Validation Error | + +#### Example Usage + +```bash +curl -X 'POST' \ + 'http://localhost:8000/api/v1/nodes/{node_id}/fs/copy' \ + -H 'accept: application/json' \ + -H 'X-User-ID: ' \ + -H 'Content-Type: application/json' \ + -d '{}' +``` + +--- + +## GET `/nodes/{node_id}/fs/stat` + +**Summary:** Get File Metadata (Stat) + +**Description:** Get file metadata directly from the Hub mirror. + +#### Parameters + +| Name | In | Required | Type | Description | +|------|----|----------|------|-------------| +| `node_id` | path | Yes | string | | +| `path` | query | Yes | string | | +| `session_id` | query | No | string | | +| `X-User-ID` | header | Yes | string | | + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | +| `422` | Validation Error | + +#### Example Usage + +```bash +curl -X 'GET' \ + 'http://localhost:8000/api/v1/nodes/{node_id}/fs/stat?path=&session_id=' \ + -H 'accept: application/json' \ + -H 'X-User-ID: ' +``` + +--- + diff --git a/ai-hub/docs/api_reference/agent-update.md b/ai-hub/docs/api_reference/agent-update.md index 9c7a29c..8356a5c 100644 --- a/ai-hub/docs/api_reference/agent-update.md +++ b/ai-hub/docs/api_reference/agent-update.md @@ -69,3 +69,32 @@ --- +## GET `/agent/binary/{arch}` + +**Summary:** Download Standalone Binary + +**Description:** Serves the All-in-One compiled binary for linux_amd64, linux_arm64, or darwin. + +#### Parameters + +| Name | In | Required | Type | Description | +|------|----|----------|------|-------------| +| `arch` | path | Yes | string | | + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | +| `422` | Validation Error | + +#### Example Usage + +```bash +curl -X 'GET' \ + 'http://localhost:8000/api/v1/agent/binary/{arch}' \ + -H 'accept: application/json' +``` + +--- + diff --git a/ai-hub/docs/api_reference/agents.md b/ai-hub/docs/api_reference/agents.md index 7e6e5af..7d84e88 100644 --- a/ai-hub/docs/api_reference/agents.md +++ b/ai-hub/docs/api_reference/agents.md @@ -174,6 +174,44 @@ |------|----|----------|------|-------------| | `id` | path | Yes | string | | | `token` | query | No | string | | +| `sync` | query | No | boolean | | + +#### Request Body + +**Required:** Yes + +- **Media Type:** `application/json` + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | +| `422` | Validation Error | + +#### Example Usage + +```bash +curl -X 'POST' \ + 'http://localhost:8000/api/v1/agents/{id}/webhook?token=&sync=' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{}' +``` + +--- + +## POST `/agents/{id}/run` + +**Summary:** Manual Trigger + +**Description:** Manual Trigger + +#### Parameters + +| Name | In | Required | Type | Description | +|------|----|----------|------|-------------| +| `id` | path | Yes | string | | #### Request Body @@ -192,7 +230,7 @@ ```bash curl -X 'POST' \ - 'http://localhost:8000/api/v1/agents/{id}/webhook?token=' \ + 'http://localhost:8000/api/v1/agents/{id}/run' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{}' @@ -296,6 +334,35 @@ --- +## POST `/agents/{id}/metrics/reset` + +**Summary:** Reset Agent Metrics + +**Description:** Reset Agent Metrics + +#### Parameters + +| Name | In | Required | Type | Description | +|------|----|----------|------|-------------| +| `id` | path | Yes | string | | + +#### Responses + +| Status Code | Description | +|-------------|-------------| +| `200` | Successful Response | +| `422` | Validation Error | + +#### Example Usage + +```bash +curl -X 'POST' \ + 'http://localhost:8000/api/v1/agents/{id}/metrics/reset' \ + -H 'accept: application/json' +``` + +--- + ## GET `/agents/{id}/telemetry` **Summary:** Get Telemetry diff --git a/ai-hub/docs/api_reference/models.md b/ai-hub/docs/api_reference/models.md index 442c9b1..ef1d8c3 100644 --- a/ai-hub/docs/api_reference/models.md +++ b/ai-hub/docs/api_reference/models.md @@ -13,6 +13,10 @@ | `max_loop_iterations` | `anyOf` | | | `mesh_node_id*` | `string` | | | `provider_name` | `anyOf` | | +| `allowed_skill_ids` | `anyOf` | | +| `restrict_skills` | `anyOf` | | +| `is_locked` | `anyOf` | | +| `auto_clear_history` | `anyOf` | | ## `AgentInstanceCreate` @@ -41,6 +45,15 @@ | `last_heartbeat` | `anyOf` | | | `template` | `anyOf` | | | `session` | `anyOf` | | +| `total_runs` | `anyOf` | | +| `successful_runs` | `anyOf` | | +| `total_tokens_accumulated` | `anyOf` | | +| `total_input_tokens` | `anyOf` | | +| `total_output_tokens` | `anyOf` | | +| `total_running_time_seconds` | `anyOf` | | +| `tool_call_counts` | `anyOf` | | +| `last_reasoning` | `anyOf` | | +| `last_error` | `anyOf` | | ## `AgentInstanceStatusUpdate` @@ -327,6 +340,16 @@ |----------|------|-------------| | `message*` | `string` | | +## `FileCopyRequest` + +**Type:** `object` + +| Property | Type | Description | +|----------|------|-------------| +| `old_path*` | `string` | | +| `new_path*` | `string` | | +| `session_id` | `anyOf` | | + ## `FileDeleteRequest` **Type:** `object` @@ -336,6 +359,16 @@ | `path*` | `string` | | | `session_id` | `anyOf` | | +## `FileMoveRequest` + +**Type:** `object` + +| Property | Type | Description | +|----------|------|-------------| +| `old_path*` | `string` | | +| `new_path*` | `string` | | +| `session_id` | `anyOf` | | + ## `FileNodeInfo` **Type:** `object` @@ -349,6 +382,21 @@ | `hash` | `anyOf` | | | `is_synced` | `boolean` | | +## `FileStatResponse` + +**Type:** `object` + +| Property | Type | Description | +|----------|------|-------------| +| `path*` | `string` | | +| `name*` | `string` | | +| `size*` | `integer` | | +| `is_dir*` | `boolean` | | +| `is_file*` | `boolean` | | +| `is_link*` | `boolean` | | +| `mtime*` | `number` | | +| `exists` | `boolean` | | + ## `FileWriteRequest` **Type:** `object` @@ -633,6 +681,7 @@ | `allowed_skill_names` | `anyOf` | | | `system_prompt_override` | `anyOf` | | | `is_locked` | `boolean` | | +| `auto_clear_history` | `boolean` | | ## `SessionCreate` @@ -688,6 +737,7 @@ | `allowed_skill_names` | `anyOf` | | | `system_prompt_override` | `anyOf` | | | `is_locked` | `anyOf` | | +| `auto_clear_history` | `anyOf` | | ## `SkillConfig` @@ -753,6 +803,7 @@ | Property | Type | Description | |----------|------|-------------| | `external_endpoint` | `anyOf` | | +| `grpc_endpoint` | `anyOf` | | ## `SystemStatus` diff --git a/ai-hub/test.db-shm b/ai-hub/test.db-shm index 8b2b390..16a8a19 100644 --- a/ai-hub/test.db-shm +++ b/ai-hub/test.db-shm Binary files differ diff --git a/ai-hub/test.db-wal b/ai-hub/test.db-wal index f0968ad..56bd098 100644 --- a/ai-hub/test.db-wal +++ b/ai-hub/test.db-wal Binary files differ diff --git a/run_integration_tests.sh b/run_integration_tests.sh index 8b6fced..4a5d572 100755 --- a/run_integration_tests.sh +++ b/run_integration_tests.sh @@ -71,7 +71,7 @@ export ENVIRONMENT="development" export PATH_PREFIX="/api/v1" # Purge local test database - rm -f ai-hub/data/ai-hub-test.db + rm -f ai-hub/test.db mkdir -p ai-hub/data pkill -f uvicorn || true @@ -103,6 +103,11 @@ echo "==========================================" echo " EXECUTING E2E INTEGRATION SUITE " echo "==========================================" +if [ "$NATIVE" = 1 ]; then + export SYNC_TEST_BASE_URL="http://127.0.0.1:8000/api/v1" + export TEST_HUB_URL="http://127.0.0.1:8000" + export TEST_GRPC_ENDPOINT="127.0.0.1:50051" +fi source /tmp/venv/bin/activate || echo "No venv found, hoping pytest is in global PATH." TEST_TARGETS=() diff --git a/skills/mesh-file-explorer/SKILL.md b/skills/mesh-file-explorer/SKILL.md index f1ea5e1..46635eb 100644 --- a/skills/mesh-file-explorer/SKILL.md +++ b/skills/mesh-file-explorer/SKILL.md @@ -96,5 +96,11 @@ ### Pattern: Fast Context Recovery Before reading a large file to check its existence, use `stat`. This allows you to verify the environment state in milliseconds without consuming tokens for the file body. +### Pattern: The Physical-to-Mesh Bridge (Importing) +To "import" a file from a node's physical disk (outside the sandbox) into the Web UI Explorer: +1. Identify the source absolute path (e.g., `/mnt/data/photo.jpg`). +2. Use `mesh_terminal_control` to copy it into the sync workspace: `cp "/mnt/data/photo.jpg" "/tmp/cortex-sync/{{session_id}}/photo.jpg"`. +3. The File Explorer UI will automatically detect and sync this new file. + > [!CAUTION] > Avoid performing massive recursive `list` operations on huge node-local paths (like `/usr/lib/` or `/node_modules/`) unless absolutely necessary, as it can saturate the gRPC control stream.