diff --git a/cron/scheduler.py b/cron/scheduler.py index 876000a89..2041411b5 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -12,6 +12,7 @@ import asyncio import concurrent.futures import json import logging +import re import os import subprocess import sys @@ -544,6 +545,55 @@ def _run_job_script(script_path: str) -> tuple[bool, str]: except Exception as exc: return False, f"Script execution failed: {exc}" +# --------------------------------------------------------------------------- +# Cloud-context warning for local-service references (#378, #456) +# --------------------------------------------------------------------------- + +_LOCAL_SERVICE_PATTERNS = [ + re.compile(r'localhost:\d+', re.IGNORECASE), + re.compile(r'127\.0\.0\.1:\d+'), + re.compile(r'check\s+ollama', re.IGNORECASE), + re.compile(r'ollama\s+(is\s+)?respond', re.IGNORECASE), + re.compile(r'curl\s+localhost', re.IGNORECASE), + re.compile(r'curl\s+127\.', re.IGNORECASE), + re.compile(r'curl\s+local', re.IGNORECASE), + re.compile(r'ping\s+localhost', re.IGNORECASE), + re.compile(r'poll(ing)?\s+local', re.IGNORECASE), + re.compile(r'check\s+service\s+respond', re.IGNORECASE), + re.compile(r'11434'), # Ollama default port + re.compile(r'11435'), # common alt Ollama port +] + + +def _detect_local_service_refs(prompt: str) -> list[str]: + """Return list of local-service reference descriptions found in prompt.""" + refs = [] + for pat in _LOCAL_SERVICE_PATTERNS: + m = pat.search(prompt) + if m: + refs.append(m.group(0)) + return refs + + +def _inject_cloud_context(prompt: str, refs: list[str], provider: str) -> str: + """Prepend a SYSTEM NOTE so the agent knows it cannot reach localhost.""" + refs_str = ", ".join(f'"{r}"' for r in refs) + warning = ( + "[SYSTEM NOTE — cloud endpoint] +" + f"You are running on a cloud inference endpoint ({provider}). " + f"Your prompt references local services: {refs_str}. " + "You CANNOT reach localhost or any local network address from this endpoint. " + "Do NOT attempt curl, ping, SSH, or any network calls to localhost. " + "Instead, report to the user that this job requires a local model endpoint " + "to check local services, and suggest they re-run with a local provider. + +" + ) + return warning + prompt + + + def _build_job_prompt(job: dict) -> str: """Build the effective prompt for a cron job, optionally loading one or more skills first.""" @@ -817,6 +867,18 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: job_name, ) + # Inject cloud-context warning when prompt references local services (#378) + if _is_cloud: + _local_refs = _detect_local_service_refs(prompt) + if _local_refs: + _provider_name = turn_route["runtime"].get("provider", "cloud") + prompt = _inject_cloud_context(prompt, _local_refs, _provider_name) + logger.info( + "Job '%s': injected cloud-context warning for local refs: %s", + job_name, + _local_refs, + ) + _agent_kwargs = _safe_agent_kwargs({ "model": turn_route["model"], "api_key": turn_route["runtime"].get("api_key"),