feat(mcp): enhance multi-platform notification resilience and reactive wakeups
- AgentApiNotifier: Sort candidate process sessions by creation time descending to prioritize the latest active IDE session over stale PIDs. Implement automatic retry across candidate address/token pairs and persist working credentials to shared environment files (~/.gemini/jetski/.jetski_env and ~/.artemis/.artemis_env). - DesktopNotifier: Enable native OS desktop toast notifications by default on macOS, Linux, and Windows unless explicitly disabled or running in CI. Ensure MCP installer sets ARTEMIS_DESKTOP_NOTIFY=true in generated IDE configurations. - ScriptNotifier: Add universal command hook adapter (ARTEMIS_NOTIFY_CMD / MCP_NOTIFY_COMMAND) for custom IDE and CLI integration. - Fault Isolation: Wrap CompositeNotifier and global notify entrypoint in strict try-except blocks to guarantee zero-crash execution even if any individual notifier fails. - Tests & Docs: Add unit tests for session recovery, fault tolerance, script hook execution, and document multi-platform notification architecture in README.
This commit is contained in:
1 parent
4927c4f0b9
commit
c1a4b49a21
9 files changed
+468
-110
No files matched your search
@@ -0,0 +1,90 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Custom Script / Command Hook Notifier for universal IDE and editor integration."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
from mcp_server.notifiers.base import BaseNotifier
|
||||
|
||||
logger = logging.getLogger("mcp_server.notifiers.script")
|
||||
|
||||
|
||||
class ScriptNotifier(BaseNotifier):
|
||||
"""Notifier that executes a user-defined command or script when an event occurs.
|
||||
|
||||
This adapter enables universal integration with any custom IDE, editor (Neovim/Emacs),
|
||||
or automation platform by allowing users to define ARTEMIS_NOTIFY_CMD or MCP_NOTIFY_COMMAND.
|
||||
Placeholders like {title}, {message}, {conversation_id}, {event_type}, and {trace_id}
|
||||
are automatically replaced before execution.
|
||||
"""
|
||||
|
||||
ENV_VARS = [
|
||||
"ARTEMIS_NOTIFY_CMD",
|
||||
"MCP_NOTIFY_COMMAND",
|
||||
]
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "script"
|
||||
|
||||
def _get_command_template(self) -> str | None:
|
||||
for var in self.ENV_VARS:
|
||||
val = os.getenv(var)
|
||||
if val and val.strip():
|
||||
return val.strip()
|
||||
return None
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return self._get_command_template() is not None
|
||||
|
||||
def notify(
|
||||
self,
|
||||
conversation_id: str,
|
||||
message: str,
|
||||
title: str | None = None,
|
||||
event_type: str = "completed",
|
||||
payload: dict[str, Any] | None = None,
|
||||
) -> bool:
|
||||
cmd_template = self._get_command_template()
|
||||
if not cmd_template:
|
||||
return False
|
||||
|
||||
trace_id = (payload or {}).get("trace_id", "")
|
||||
formatted_title = title or f"Artemis Task {event_type.capitalize()}"
|
||||
|
||||
# Replace template placeholders safely
|
||||
try:
|
||||
cmd = (
|
||||
cmd_template.replace("{title}", str(formatted_title))
|
||||
.replace("{message}", str(message))
|
||||
.replace("{conversation_id}", str(conversation_id))
|
||||
.replace("{event_type}", str(event_type))
|
||||
.replace("{trace_id}", str(trace_id))
|
||||
)
|
||||
subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=10,
|
||||
)
|
||||
logger.info(f"Custom script notification command executed: {cmd[:60]}...")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to execute custom script notification: {e}")
|
||||
return False
|
||||
Reference in new issue
Block a user