feat: final platform plugin parity — webhook delivery, platform hints, docs
Closes remaining functional gaps and adds documentation.
webhook.py: Cross-platform delivery now checks the plugin registry
for unknown platform names instead of hardcoding 15 names in a tuple.
Plugin platforms can receive webhook-routed deliveries.
prompt_builder: Platform hints (system prompt LLM guidance) now fall
back to the plugin registry's platform_hint field. Plugin platforms
can tell the LLM 'you're on IRC, no markdown.'
PlatformEntry: Added platform_hint field for LLM guidance injection.
IRC adapter: Added acquire_scoped_lock/release_scoped_lock in
connect/disconnect to prevent two profiles from using the same IRC
identity. Added platform_hint for IRC-specific LLM guidance.
Removed dead token-empty-warning extension for plugin platforms
(plugin adapters handle their own env vars via check_fn).
website/docs/developer-guide/adding-platform-adapters.md:
- Added 'Plugin Path (Recommended)' section with full code examples,
PLUGIN.yaml template, config.yaml examples, and a table showing all
18 integration points the plugin system handles automatically
- Renamed built-in checklist to clarify it's for core contributors
gateway/platforms/ADDING_A_PLATFORM.md:
- Added Plugin Path section pointing to the reference implementation
and full docs guide
- Clarified built-in path is for core contributors only
This commit is contained in:
33
run_agent.py
33
run_agent.py
@@ -2151,7 +2151,7 @@ class AIAgent:
|
||||
# Context engine reset (works for both built-in compressor and plugins)
|
||||
if hasattr(self, "context_compressor") and self.context_compressor:
|
||||
self.context_compressor.on_session_reset()
|
||||
|
||||
|
||||
def _ensure_lmstudio_runtime_loaded(self, config_context_length: Optional[int] = None) -> None:
|
||||
"""
|
||||
Preload the LM Studio model with at least Hermes' minimum context.
|
||||
@@ -2935,7 +2935,7 @@ class AIAgent:
|
||||
|
||||
# Check if there's any non-whitespace content remaining
|
||||
return bool(cleaned.strip())
|
||||
|
||||
|
||||
def _strip_think_blocks(self, content: str) -> str:
|
||||
"""Remove reasoning/thinking blocks from content, returning only visible text.
|
||||
|
||||
@@ -3153,8 +3153,8 @@ class AIAgent:
|
||||
marker in assistant_text for marker in workspace_markers
|
||||
)
|
||||
return (user_targets_workspace or assistant_targets_workspace) and assistant_mentions_action
|
||||
|
||||
|
||||
|
||||
|
||||
def _extract_reasoning(self, assistant_message) -> Optional[str]:
|
||||
"""
|
||||
Extract reasoning/thinking content from an assistant message.
|
||||
@@ -3727,7 +3727,7 @@ class AIAgent:
|
||||
|
||||
# Return everything up to (not including) the last assistant message
|
||||
return messages[:last_assistant_idx]
|
||||
|
||||
|
||||
def _format_tools_for_system_message(self) -> str:
|
||||
"""
|
||||
Format tool definitions for the system message in the trajectory format.
|
||||
@@ -3751,7 +3751,7 @@ class AIAgent:
|
||||
formatted_tools.append(formatted_tool)
|
||||
|
||||
return json.dumps(formatted_tools, ensure_ascii=False)
|
||||
|
||||
|
||||
def _convert_to_trajectory_format(self, messages: List[Dict[str, Any]], user_query: str, completed: bool) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Convert internal message format to trajectory format for saving.
|
||||
@@ -3916,7 +3916,7 @@ class AIAgent:
|
||||
i += 1
|
||||
|
||||
return trajectory
|
||||
|
||||
|
||||
def _save_trajectory(self, messages: List[Dict[str, Any]], user_query: str, completed: bool):
|
||||
"""
|
||||
Save conversation trajectory to JSONL file.
|
||||
@@ -3931,7 +3931,7 @@ class AIAgent:
|
||||
|
||||
trajectory = self._convert_to_trajectory_format(messages, user_query, completed)
|
||||
_save_trajectory_to_file(trajectory, self.model, completed)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _summarize_api_error(error: Exception) -> str:
|
||||
"""Extract a human-readable one-liner from an API error.
|
||||
@@ -4243,7 +4243,7 @@ class AIAgent:
|
||||
except Exception as e:
|
||||
if self.verbose_logging:
|
||||
logging.warning(f"Failed to save session log: {e}")
|
||||
|
||||
|
||||
def interrupt(self, message: str = None) -> None:
|
||||
"""
|
||||
Request the agent to interrupt its current tool-calling loop.
|
||||
@@ -4311,7 +4311,7 @@ class AIAgent:
|
||||
logger.debug("Failed to propagate interrupt to child agent: %s", e)
|
||||
if not self.quiet_mode:
|
||||
print("\n⚡ Interrupt requested" + (f": '{message[:40]}...'" if message and len(message) > 40 else f": '{message}'" if message else ""))
|
||||
|
||||
|
||||
def clear_interrupt(self) -> None:
|
||||
"""Clear any pending interrupt request and the per-thread tool interrupt signal."""
|
||||
self._interrupt_requested = False
|
||||
@@ -4532,7 +4532,7 @@ class AIAgent:
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def commit_memory_session(self, messages: list = None) -> None:
|
||||
"""Trigger end-of-session extraction without tearing providers down.
|
||||
Called when session_id rotates (e.g. /new, context compression);
|
||||
@@ -4728,7 +4728,7 @@ class AIAgent:
|
||||
if not self.quiet_mode:
|
||||
self._vprint(f"{self.log_prefix}📋 Restored {len(last_todo_response)} todo item(s) from history")
|
||||
_set_interrupt(False)
|
||||
|
||||
|
||||
@property
|
||||
def is_interrupted(self) -> bool:
|
||||
"""Check if an interrupt has been requested."""
|
||||
@@ -4912,6 +4912,15 @@ class AIAgent:
|
||||
platform_key = (self.platform or "").lower().strip()
|
||||
if platform_key in PLATFORM_HINTS:
|
||||
prompt_parts.append(PLATFORM_HINTS[platform_key])
|
||||
elif platform_key:
|
||||
# Check plugin registry for platform-specific LLM guidance
|
||||
try:
|
||||
from gateway.platform_registry import platform_registry
|
||||
_entry = platform_registry.get(platform_key)
|
||||
if _entry and _entry.platform_hint:
|
||||
prompt_parts.append(_entry.platform_hint)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return "\n\n".join(p.strip() for p in prompt_parts if p.strip())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user